読者です 読者をやめる 読者になる 読者になる

はっさんブログ

事業駆動Rubyist

がんばRuby!Rubyでtf-idfを実装する

RubyでTF-IDFを実装してみました。

RubyでTF-IDF

require 'natto'

class TfIdf
  include Math

  attr_reader :texts, :nm, :word_count, :tf, :idf, :tfidf, :tfidf_corpus

  def initialize(*text)
    @texts = text
    @nm = Natto::MeCab.new
    @tf = {}
    @idf = {}
    @tfidf = {}
    @tfidf_corpus = {}
    @word_count ||= 0
  end

  # TF = (単語数 / 総単語数) を求める
  def term_frequency
    texts.each do |text|
      nm.parse(text) do |n|
        if n.feature =~ /名詞/
          tf[n.surface] ? tf[n.surface] += 1 : tf[n.surface] = 1
          @word_count += 1
        end
      end
    end
    tf.each { |key, value| tf[key] = bigdecimal(value.to_f / word_count.to_f) }
  end

  # IDF = log(文書の総数/単語が現れた文書の数)
  def inverse_document_frequency
    tf.each do |key, _|
      idf[key] ||= 0
      texts.each { |text| idf[key] += 1 if text.include?(key) }
    end
    idf.each { |key, value| idf[key] = bigdecimal(log(texts.size.to_f / value.to_f, 10)) }
  end

  # TF-IDF = TF * IDF
  def tf_idf
    tf.each do |tf_k, tf_v|
      idf_v = idf[tf_k]
      tfidf[tf_k] = bigdecimal(tf_v * idf_v)
    end
    tfidf
  end

  # { "文書" => [単語のtfidf値, ..] }
  def tf_idf_courpus
    texts.each do |text|
      tfidf_values = []
      tfidf.each do |tfidf_k, tfidf_v|
        text.include?(tfidf_k) ? tfidf_values << tfidf_v : tfidf_values << 0.0
      end
      tfidf_corpus[text] = tfidf_values
    end
    tfidf_corpus
  end

  private

  def bigdecimal(result)
    BigDecimal(result.to_s).floor(5).to_f
  end
end

t = TfIdf.new('リンゴ', 'リンゴとゴリラ', 'リンゴとゴリラとラッパとラッパ')
p t.term_frequency
p t.inverse_document_frequency
p t.tf_idf
p t.tf_idf_courpus

結果

TF : {"リンゴ"=>0.42857, "ゴリラ"=>0.28571, "ラッパ"=>0.28571}
IDF : {"リンゴ"=>0.0, "ゴリラ"=>0.17609, "ラッパ"=>0.47712}
TF-IDF : {"リンゴ"=>0.0, "ゴリラ"=>0.05031, "ラッパ"=>0.13631}

TF-IDFを用いた文書ベクトル : {"リンゴ"=>[0.0, 0.0, 0.0], "リンゴとゴリラ"=>[0.0, 0.05031, 0.0], "リンゴとゴリラとラッパとラッパ"=>[0.0, 0.05031, 0.13631]}

まとめ

TFでは単語が出現する割合を求めているため、リンゴの値が一番大きく、ゴリラとラッパが等しい。
IDFは単語のレア度と考えると、全ての文書で出現するリンゴは0、ラッパよりゴリラのほうが多くの文書で出るため値は小さい。
最後に、TFにIDFを掛けてフィルタリングした結果により、ラッパ・ゴリラ・リンゴという順に特徴となる単語を定量的に表すことが出来ます。

このTF-IDFを用いた文書ベクトルを使って、文書間の類似度を出していきます。

広告を非表示にする