diff options
Diffstat (limited to 'lib/classifier/lsi/content_node.rb')
| -rw-r--r-- | lib/classifier/lsi/content_node.rb | 102 |
1 files changed, 63 insertions, 39 deletions
diff --git a/lib/classifier/lsi/content_node.rb b/lib/classifier/lsi/content_node.rb index f313331..37a9448 100644 --- a/lib/classifier/lsi/content_node.rb +++ b/lib/classifier/lsi/content_node.rb @@ -1,72 +1,96 @@ +# rbs_inline: enabled + # Author:: David Fayram (mailto:dfayram@lensmen.net) # Copyright:: Copyright (c) 2005 David Fayram II # License:: LGPL module Classifier - -# This is an internal data structure class for the LSI node. Save for -# raw_vector_with, it should be fairly straightforward to understand. -# You should never have to use it directly. + # This is an internal data structure class for the LSI node. Save for + # raw_vector_with, it should be fairly straightforward to understand. + # You should never have to use it directly. class ContentNode - attr_accessor :raw_vector, :raw_norm, - :lsi_vector, :lsi_norm, - :categories - + # @rbs @word_hash: Hash[Symbol, Integer] + + # @rbs @raw_vector: untyped + # @rbs @raw_norm: untyped + # @rbs @lsi_vector: untyped + # @rbs @lsi_norm: untyped + attr_accessor :raw_vector, :raw_norm, :lsi_vector, :lsi_norm + + # @rbs @categories: Array[String | Symbol] + attr_accessor :categories + attr_reader :word_hash + # If text_proc is not specified, the source will be duck-typed # via source.to_s - def initialize( word_hash, *categories ) + # + # @rbs (Hash[Symbol, Integer], *String | Symbol) -> void + def initialize(word_frequencies, *categories) @categories = categories || [] - @word_hash = word_hash + @word_hash = word_frequencies end - + # Use this to fetch the appropriate search vector. + # + # @rbs () -> untyped def search_vector @lsi_vector || @raw_vector end - + # Use this to fetch the appropriate search vector in normalized form. + # + # @rbs () -> untyped def search_norm @lsi_norm || @raw_norm end - + # Creates the raw vector out of word_hash using word_list as the # key for mapping the vector space. - def raw_vector_with( word_list ) - if $GSL - vec = GSL::Vector.alloc(word_list.size) - else - vec = Array.new(word_list.size, 0) - end + # + # @rbs (WordList) -> untyped + def raw_vector_with(word_list) + vec = if Classifier::LSI.native_available? + Classifier::LSI.vector_class.alloc(word_list.size) + else + Array.new(word_list.size, 0) + end @word_hash.each_key do |word| vec[word_list[word]] = @word_hash[word] if word_list[word] end - + # Perform the scaling transform - total_words = vec.sum - + total_words = Classifier::LSI.native_available? ? vec.sum : vec.sum_with_identity + vec_array = Classifier::LSI.native_available? ? vec.to_a : vec + total_unique_words = vec_array.count { |word| word != 0 } + # Perform first-order association transform if this vector has more - # than one word in it. - if total_words > 1.0 + # than one word in it. + if total_words > 1.0 && total_unique_words > 1 weighted_total = 0.0 + vec.each do |term| - if ( term > 0 ) - weighted_total += (( term / total_words ) * Math.log( term / total_words )) - end - end - vec = vec.collect { |val| Math.log( val + 1 ) / -weighted_total } + next unless term.positive? + next if total_words.zero? + + term_over_total = term / total_words + val = term_over_total * Math.log(term_over_total) + weighted_total += val unless val.nan? + end + + sign = weighted_total.negative? ? 1.0 : -1.0 + divisor = sign * [weighted_total.abs, Vector::EPSILON].max + vec = vec.collect { |val| Math.log(val + 1) / divisor } end - - if $GSL - @raw_norm = vec.normalize - @raw_vector = vec + + if Classifier::LSI.native_available? + @raw_norm = vec.normalize + @raw_vector = vec else - @raw_norm = Vector[*vec].normalize - @raw_vector = Vector[*vec] + @raw_norm = Vector[*vec].normalize + @raw_vector = Vector[*vec] end - end - - end - + end + end end |
