概要
前回、自分のツイートデータを分析してみました
その際に単語の出現回数はカウントしましたが各ツイートに含まれる単語の tfidf は計算しませんでした
せっかくなので各ツイートの tfidf を算出して結果を考察してみました
環境
- macOS 10.14.3
- Ruby 2.5.1p57
tfidf を算出するスクリプト
とりあえずコードにしたので紹介します
勉強がてら作成してので参考程度に見てください
bundle init
vim Gemfile
gem "natto"
bundle install --path vendor
vim tfidf.rb
require 'natto'
require 'json'
class TFIDF
attr_accessor :tweets
def initialize
file = 'data/tweet.js'
@data = JSON.parse(File.read(file).sub('window.YTD.tweet.part0 = ', ''))
@all_tweet_count = @data.size
@tweets = []
end
def exec
@data.each_with_index { |d, i|
tweet = Tweet.new(d['full_text'])
tweet.analyze
add(tweet)
}
calc
end
def debug(all = false)
ret = []
@tweets.each { |tweet|
r = {}
rr = []
if all
tweet.terms.each { |term|
rr.push(term.to_h)
}
else
max = tweet.max_tfidf
max.each { |term|
rr.push(term.to_h)
}
end
r.store(:tweet, tweet.tweet)
r.store(:terms, rr)
ret.push(r)
}
puts({ :results => ret }.to_json)
end
private
def add(tweet)
@tweets.push(tweet)
end
def calc
@tweets.each { |t1|
t1.terms.each { |term|
c = 0
@tweets.each { |t2|
c += 1 if t2.tweet.include?(term.term)
}
term.df = c
term.idf = Math.log(@all_tweet_count / c)
term.tfidf = term.tf * term.idf
}
}
end
class Tweet
attr_accessor :tweet, :terms
def initialize(tweet)
@tweet = tweet
@terms = []
@mecab = Natto::MeCab.new("-F%f[0]%f[1]")
end
def analyze
@mecab.parse(@tweet) do |n|
term = get(n.surface)
if term.nil?
term = Term.new(n.surface)
add(term)
else
term.inc
end
end
@terms.each { |t|
t.tf = t.count / count_all_terms.to_f
}
end
def max_tfidf
max = @terms.max { |a, b| a.tfidf <=> b.tfidf }
@terms.select { |t| t.tfidf == max.tfidf }
end
private
def add(term)
@terms.push(term)
end
def count_all_terms
@terms.sum { |t| t.count }
end
def get(surface)
@terms.select { |t| t.term == surface }.first
end
end
class Term
attr_accessor :term, :count, :tf, :df, :idf, :tfidf
def initialize(term)
@term = term
@count = 1
@tf = 0.0
@df = 0
@idf = 0.0
@tfidf = 0.0
end
def inc
@count += 1
end
def to_h
{
:term => @term,
:count => @count,
:tf => @tf,
:df => @df,
:idf => @idf,
:tfidf => @tfidf
}
end
end
end
tfidf = TFIDF.new
tfidf.exec
tfidf.debug
標準出力に JSON を出力するのでリダイレクトでファイルに書き込みましょう
bundle exec ruby tfidf.rb > results.json
今回は名詞だけに絞らず、すべての品詞を対象にしています
また解析対象のツイートは前回同様 10016 ツイートにします
つまり 10016 文書あるのと同じです
tfidf の値は特に正規化していません
結果は JSON で出力してくれます
すべての単語に対する tfidf を出力することもできますし文書内で最も tfidf が高かった単語だけを出力することもできます
tf, df, idf の計算式は定義通りです
- tf・・・対象の単語 / 文書内の単語
- df・・・対象の単語が全ツイートを対象に何ツイート出現したか
- idf・・・
Math.log(全ツイート数 / df)
- tfidf・・・
tf * idf
たぶん実装もあっていると思います
処理の流れ
まず各ツイートを対象に形態素解析を掛け単語を抽出します
各ツイート内で抽出した単語の出現回数をカウントします
抽出した単語とカウントは Term クラスの配列に追加していきます
出現回数と配列に追加した全単語を元に tf を計算します
これを全ツイート先に繰り返します
すべてツイートの単語の tf を算出できたら次はその単語が 1 回でも出現している文書 (ツイート) 数をカウントします (df)
df がカウントできたら idf を計算し、そのまま tfidf も計算します
計算結果は各単語を管理する Term クラスのフィールドとして管理します
あとは結果を JSON で出力して終了です
結果を見てみた
10,000 ツイート以上あるのですべてを見ていませんが適当にサンプリングして特徴語ってぽくなっているか見てみました
例えば以下のツイートは特徴語がちゃんと取得できているなと思いました
{
"tweet": "Swift4 へのマイグレーションはできたけど Swift4 が勉強できたわけではない",
"terms": [
{
"term": "Swift",
"count": 2,
"tf": 0.1,
"df": 66,
"idf": 5.017279836814924,
"tfidf": 0.5017279836814924
}
]
}
見るからに Swift に関数するツイートなので特徴語もバッチリだと思います
逆に特徴語が微妙な感じだったのは以下のようなツイートです
{
"tweet": "lambda 使いになるには如何に layers を駆使できるかって感じがするなー。",
"terms": [
{
"term": "駆使",
"count": 1,
"tf": 0.05,
"df": 2,
"idf": 8.518791912779934,
"tfidf": 0.4259395956389967
}
]
}
ツイートの雰囲気的には「lambda」とか「layers」が特徴語になってほしいところですが「駆使」になってしまいました
これは lambda や layers が他のツイートにも頻出しているせいで特徴語としての価値が下がっているせいで「駆使」が特徴語になっています
今回の場合、1 ツイートを文書として定義しました
Twitter の場合、連続して同じようなことをつぶやくケースが多く、そうなると単語の価値が下がる傾向があることがわかりました
なので 1 ツイート = 文書として扱うのではなく例えば 1 週間分のツイートを 1 文書として扱うことで 1 週間分のツイートでの特徴語を算出するようにすると良いのかなと思いました
あまり変な結果にはならなかった
全体をざっくり見ても変な結果になったツイートはありませんでした
例えば自分の場合「、、、」などツイートに多く含めるのですが「、、、」が特徴語になるようなケースは 1 度しか出ませんでした
他におかしそうなのはかぎかっこの記号が特徴語になるツイートもありましたがツイートを見ると確かにかぎかっこを多用していたので、それは正常かなと思いました
最後に
tfidf を使って自分のツイートの特徴語を抽出してみました
自然言語処理の分野になると思いますが、かなり基本的な手法になるので覚えておいて損はないかなと思います
またここから派生する手法としてクラスタリング手法であるコサイン類似度を使って同じような内容のツイートを発見したり、分類手法としてナイブベイズにも応用できると思います
自分もやってみて感じましたがやはり自分の手で動かして計算してみることで理解もより深まると思います
0 件のコメント:
コメントを投稿