TF-IDFの理論をざっくり理解する①
最近、自社のプロダクトで自然言語処理がよく使われるようになってきたので、勉強も兼ねてまとめてみる。
TF-IDFとは?
tf-idfは、文書中に含まれる単語の重要度を評価する手法の1つであり、主に情報検索やトピック分析などの分野で用いられている。 tf-idfは、tf(英: Term Frequency、単語の出現頻度)とidf(英: Inverse Document Frequency、逆文書頻度)の二つの指標に基づいて計算される。
Wikipediaだと少し難しい言葉でかいてあるので噛み砕いていくと、ある人のブログから重要度の高い言葉(特徴量)を抽出したいとする。
tf
はある人のブログ内の言葉を分解し「頻度」を計算し、
idf
はある人のブログ内の言葉がWikipediaの全ての言葉と比べた中での「希少性」を計算する。
どこの言葉と比較してidf(希少性)を計算するかは重要で、本来は他人を含めた全てのブログの言葉を使ったほうがいいと考えられるけど、今回はテストするので取れるデータということでWikipediaにしてある。
図にすると、
こんな感じ・・・なのかな。今はこれくらいの理解にしておこう。
論よりなんたらっていうので、早速Pythonで試してみる。
下準備
自分のブログを元にやってみる。下準備が多めなので別の記事に書いた。
今回は、tf値を測るのを過去に書いた自分のブログをまずは分かち書きにする。
mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd/ -Owakati blog.txt -o blog_wakati_neo.txt -b 16384
別記事にある wiki_wakati_neo.txt
を使おうとしたが2000万行あって処理が遅すぎるのでとりあえず適当に100万行くらいにしておく。
head -n 1000000 wiki_wakati_neo.txt > wiki_wakati_neo_head.txt
wiki_wakati_neo_head
と先ほど作った blog_wakati_neo.txt
を使って色々計算してみる。
ブログのTF値を計算
Python
もよくわからないのとコードは汚いのでそこはお気にせずに。。
TF値を計算するには、 sklearn
にある CountVectorizer
を使えばできるぽい
from sklearn.feature_extraction.text import CountVectorizer from sklearn.decomposition import TruncatedSVD from sklearn.preprocessing import Normalizer import numpy as np import logging logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO) count_vectorizer = CountVectorizer(input='filename', token_pattern=u'(?u)\\b\\w+\\b')
CountVectorizerはデフォルトで一文字の物を消してしまうため、その対策でtoken_patten設定すると良いみたい。
bag_of_words = count_vectorizer.fit_transform(['../data/blog_wakati_neo.txt'])
fit_transform
はBag of Words(単語の袋)と言われる、ある文章における単語の出現回数を表したものに変換する。
以下のように、すすも1回、も2回、もも2回...のように数えてくれる。
[~]mecab すもももももももものうち すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ も 助詞,係助詞,*,*,*,*,も,モ,モ もも 名詞,一般,*,*,*,*,もも,モモ,モモ も 助詞,係助詞,*,*,*,*,も,モ,モ もも 名詞,一般,*,*,*,*,もも,モモ,モモ の 助詞,連体化,*,*,*,*,の,ノ,ノ うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ EOS
あとはソートして頻度順にだしたいなーと思って調べてみたらぴったりやりたいことをやってくれてる方がいたのでそちらのを真似させていただく。
sum_words = tf.sum(axis=0) words_freq = [(word, sum_words[0, idx]) for word, idx in count_vectorizer.vocabulary_.items()] words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True) words_freq
結果以下になるが、いろんな記事に書かれてたけど単語分割する前にノイズを除去しなきゃいけないと思わせるいい例になってしまった。
助詞などが多くなるのは当たり前か・・。名詞だけにしたいが、idf
における希少性を利用するとこう行った一般用語は消えると思うので、このまま続けて見る。
[('た', 32), ('の', 27), ('に', 26), ('を', 19), ('と', 15), ('し', 13), ('て', 12), ('ない', 10), ('から', 10), ('な', 10), ('も', 8), ('こと', 8), ('サービス', 8), ('が', 6), ('は', 6), 省略
------追記------
よくよく計算式を確認したら、単語数数えるだけなのはTF値ではないので、単語ごとにカウントした値を文章全体の単語数で割る必要がある。
たとえば、文章全体の単語数が560で、「た」の場合、
32/560 = 0.05714285714
となる。
-------追記終わり----
ただ、以下のようにCountVectorizer
を呼び出す際に低すぎる・高すぎる頻度の単語は除去できるっぽい。
全文章における出現割合なので、小数点になるのかな?
count_vectorizer = CountVectorizer(min_df=0.24, max_df=0.85, input='filename', token_pattern=u'(?u)\\b\\w+\\b')
TF-IDFの計算
TF-IDFの計算はTfidfVectorizer
を使えば良い。
vectorizer = TfidfVectorizer(input='filename', token_pattern=u'(?u)\\b\\w+\\b', use_idf=True) features = vectorizer.fit_transform(['../data/blog_wakati_neo.txt','../data/wiki_wakati_neo_head.txt']) terms = vectorizer.get_feature_names() tfidfs = features.toarray() tfidfs
結果は以下のようにそれっぽさそうな数値になった。
array([[0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ..., 0.00000000e+00, 0.00000000e+00, 0.00000000e+00], [7.10477245e-03, 1.31955447e-03, 2.44020240e-03, ..., 5.92487576e-06, 1.69282165e-06, 1.69282165e-06]])
実際に上位の10単語を抽出して見る。
def extract_feature_words(terms, tfidfs, i, n): tfidf_array = tfidfs[i] top_n_idx = tfidf_array.argsort()[-n:][::-1] words = [terms[idx] for idx in top_n_idx] return words for x in extract_feature_words(terms, tfidfs, 0, 10): print(x,)
結果はTF値と一緒になってしまった。。。fit_transform
あたりでミスってしまったのだろうか・・。長くなりそうなので次回に持ち越し。
た の に を と し て から な ない
終わりに
歯切れは若干わるかったが、理論を理解するという意味だと理解はできたかなと思う。
このままだと悔しいので、次回はTF-IDFの正確な値にチャレンジして見る。