ども
そろそろPyCon JP 2019が始まりますね
始まると一気にブログを書かなくなるので今のうちに書きます
PyCon JPのお祭り感が好きなんですよね
そのあとはPyCon TWに遊びに行きます
人生二度目の台湾です
TL; DR
TF は文書頻度。
- 文書中の単語の重要度を表す指標
IDFは逆文書頻度
- 各単語がどれだけの文書に出現するかを表す指標
TF-IDFはTFとIDFの積。それぞれの文書の中で重要な単語ほど大きい値になる
TFの計算式
文書における単語のは以下のように表される。
は文書における単語の出現回数。
は文書におけるすべての単語の出現回数の和。
式で表しても(自分が)今ひとつわからないので実際の文書で考えてみる。
神﨑風花はかわいい
寺口夏花はかわいい
山崎愛はかわいい
みんなかわいい
という文書があって、単語は
神﨑風花
寺口夏花
山崎愛
みんな
は
かわいい
と分けるとする。
文書中の単語はベクトルで表す。ここで単語は6つなのでベクトルの成分は6つになる。
神﨑風花はかわいい
の各単語のTFを求めてみる。
単語のベクトルはこのようになる。
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
1 | 0 | 0 | 0 | 1 | 1 |
「神﨑風花」という単語のは以下のように求められる。
は文書におけるすべての単語の出現回数の和なので、3になる。
同様に他の単語についてもを求めると、
神﨑風花はかわいい
の各単語のTFは以下のようになる。
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
0.3 | 0 | 0 | 0 | 0.3 | 0.3 |
IDF
文書における単語のは以下のように表される。
は文書の総数。
は単語を含む文書数。
実際には単語が出現しない文書があるとゼロ除算が発生するので、分子と分母にそれぞれ1を加える。
やっぱり式で表しても(自分が)今ひとつわからないので実際の文書で考えてみる。
神﨑風花はかわいい
寺口夏花はかわいい
山崎愛はかわいい
みんなかわいい
があって、単語のベクトルはそれぞれ以下のようになる。
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
1 | 0 | 0 | 0 | 1 | 1 |
0 | 1 | 0 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 1 | 1 |
0 | 0 | 0 | 1 | 0 | 1 |
単語それぞれのIDFを求めてみる。
- 神﨑風花
- 寺口夏花
- 山崎愛
- みんな
- は
※ 少数第二位で四捨五入してる
- かわいい
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
2.0 | 2.0 | 2.0 | 2.0 | 0.4 | 0 |
TF-IDF
TF-IDFはとの積。
「神﨑風花はかわいい」の場合
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
0.3 | 0 | 0 | 0 | 0.3 | 0.3 |
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
2.0 | 2.0 | 2.0 | 2.0 | 0.4 | 0 |
神﨑風花 | 寺口夏花 | 山崎愛 | みんな | は | かわいい |
---|---|---|---|---|---|
0.6 | 0 | 0 | 0 | 0.12 | 0 |
となる。最も値が大きいのは「神﨑風花」なので、文書「神﨑風花はかわいい」の中で重要度が最も高いのは「神﨑風花」という単語ということになる。
Pythonでやってみる
scikit-learnを使えば簡単にできると「Pythonによるあたらしいデータ分析の教科書」という本に書いてあるけど、今回はNumPyを使って自力で実装してみる。
なお、TF-IDFのL2正規化はNumPyの力を借りた。
import typing import numpy as np def get_all_docs(in_csv: str) -> typing.List: all_docs = [] with open(in_csv, encoding='utf-8') as fin: for line in fin: words = [word.strip() for word in line.split(',')[3:]] all_docs.append(words) return all_docs def create_words_list(all_words: typing.List) -> typing.List: flatten_words = [] for words in all_words: for word in words: flatten_words.append(word) words_list = [] for word in flatten_words: if word not in words_list: words_list.append(word) return words_list def get_word2int(words_list: typing.List) -> typing.Dict[str, int]: word2int = {} for idx, word in enumerate(words_list): word2int[word] = idx return word2int def calc_tf(all_docs: typing.List, word2int: typing.Dict[str, int]) -> np.ndarray: tf_array = np.zeros((len(all_docs), len(word2int))) for doc_idx, doc in enumerate(all_docs): temp_array = np.zeros((1, len(word2int))) for word_idx, word in enumerate(word2int.keys()): if word in doc: temp_array[0, word_idx] += 1 tf_array[doc_idx:] = temp_array / np.sum(temp_array) return tf_array def calc_idf(all_docs: typing.List, word2int: typing.Dict[str, int]) -> np.ndarray: temp_array = np.zeros((1, len(word2int))) all_docs = all_docs for idx, word in enumerate(word2int.keys()): word_count = 0 for doc in all_docs: if word in doc: word_count += 1 temp_array[0, idx] = word_count idf_array = np.log2((len(all_docs) + 1 / temp_array + 1)) return idf_array def l2_normalize(tf_idf: np.ndarray) -> np.ndarray: l2_norm = np.linalg.norm(tf_idf, ord=2) return tf_idf / l2_norm def tf_idf(in_csv: str): all_docs = get_all_docs(in_csv) # in_csvは形態素解析して取り出した名詞のCSVファイル words_list = create_words_list(all_docs) word2int = get_word2int(words_list) tf_array = calc_tf(all_docs, word2int) idf_array = calc_idf(all_docs, word2int) tf_idf = tf_array * idf_array normalized_tf_idf = l2_normalize(tf_idf)
tf_idf_with_idol/tf_idf.py at master · NaoY-2501/tf_idf_with_idol · GitHubから抜粋。
次回は山崎愛ちゃんのブログに対して計算したTF-IDFと実際の記事を見比べて、特徴語が取り出せているかを検証したりする予定。