TF-IDF(Term Frequency-Inverse Document Frequency)

TFはTerm Frequency(単語頻度)
IDFはInverse Document Frequency(逆文章頻度): 単語を含む文章がどれだけの頻度で出現していないか、珍しさを表す指標

TFはN個の単語の内、n回表示だと、n/N で表すので、そのまま。
IDFはD個の文で単語tを含む文がd個ある時、 IDF = -log[10]d/D = log[10]D/d となる。指標が、[文章]単位に変わる。

TF-IDFはTFとIDFの積
単語の頻度と文章の頻度を掛ける

では実践してみましょう。

from math import log
import pandas as pd 

docs = [
	["ノンプレイヤーキャラクター", "プレイヤー","操作","キャラクター"],
	["プレイヤー","操作","キャラクター","プレイヤーキャラクター"],
	["NPC","PC"],
	["RPG用語","コンピューターゲーム","キャラクター","人間らしく","操作"],
	["NPC","ゲームマスター","プレイヤー","キャラクター","ゲーム","イベント","バランス","プレイヤー"]
]

words = list(set(w for doc in docs for w in doc))
words.sort()
print(words)

# TF-IDF
N = len(docs)

def tf(t, d):
	return d.count(t)/len(d)

def idf(t):
	df = 0
	for doc in docs:
		df += t in doc

	return log(N/df)+1

def tfidf(t,d):
	return tf(t,d)* idf(t)

# TF
result = []
for i in range(N):
	result.append([])
	d = docs[i]
	for j in range(len(words)):
		t = words[j]

		result[-1].append(tf(t,d))

tf_ = pd.DataFrame(result, columns=words)
print(tf_)

# IDF
result = []
for j in range(len(words)):
	t = words[j]
	result.append(idf(t))

idf_ = pd.DataFrame(result, index=words, columns=["IDF"])
print(idf_)

# TF-IDF
result = []
for i in range(N):
	result.append([])
	d = docs[i]
	for j in range(len(words)):
		t = words[j]

		result[-1].append(tfidf(t,d))

tfidf_ = pd.DataFrame(result, columns=words)
print(tfidf_)

[vagrant@localhost python]$ python app.py
[‘NPC’, ‘PC’, ‘RPG用語’, ‘イベント’, ‘キャラクター’, ‘ゲーム’, ‘ゲームマスター’, ‘コンピューターゲーム’, ‘ノンプレイヤーキャラクター’, ‘バランス’, ‘プレイヤー’, ‘プレイヤーキャラクター’, ‘人間らしく’, ‘操作’]
NPC PC RPG用語 イベント キャラクター … バランス プレイヤー プレイヤーキャラクター 人間らしく 操作
0 0.000 0.0 0.0 0.000 0.250 … 0.000 0.25 0.00 0.0 0.25
1 0.000 0.0 0.0 0.000 0.250 … 0.000 0.25 0.25 0.0 0.25
2 0.500 0.5 0.0 0.000 0.000 … 0.000 0.00 0.00 0.0 0.00
3 0.000 0.0 0.2 0.000 0.200 … 0.000 0.00 0.00 0.2 0.20
4 0.125 0.0 0.0 0.125 0.125 … 0.125 0.25 0.00 0.0 0.00

[5 rows x 14 columns]
IDF
NPC 1.916291
PC 2.609438
RPG用語 2.609438
イベント 2.609438
キャラクター 1.223144
ゲーム 2.609438
ゲームマスター 2.609438
コンピューターゲーム 2.609438
ノンプレイヤーキャラクター 2.609438
バランス 2.609438
プレイヤー 1.510826
プレイヤーキャラクター 2.609438
人間らしく 2.609438
操作 1.510826
NPC PC RPG用語 イベント … プレイヤー プレイヤーキャラクター 人間らしく 操作
0 0.000000 0.000000 0.000000 0.00000 … 0.377706 0.000000 0.000000 0.377706
1 0.000000 0.000000 0.000000 0.00000 … 0.377706 0.652359 0.000000 0.377706
2 0.958145 1.304719 0.000000 0.00000 … 0.000000 0.000000 0.000000 0.000000
3 0.000000 0.000000 0.521888 0.00000 … 0.000000 0.000000 0.521888 0.302165
4 0.239536 0.000000 0.000000 0.32618 … 0.377706 0.000000 0.000000 0.000000

[5 rows x 14 columns]


TFは頻度なので、0≦TF≦1になってますね。
IDFは、”プレイヤー”や”キャラクター”など、使用頻度の高い方が、値が小さくなってます。log[10]D/dなので、dの値が大きくなるほど、IDFは小さくなることがわかります。
そしてTF-IDFは、TF同様、文章ごとに値が表示されます。

あ、IDFは珍しいワードの方が値が大きくなるので、TF-IDFで各単語の重み付けをして特徴を示しているんですね。

このロジック考えたの誰だよ、すごいな。。。

fasttext

Word2Vecの延長線上として、Facebookが開発したのがfasttext
-> より精度の高い計算を高速に実行
-> 字面の近しい単語同士により意味のまとまりをもたせるという手法を提案

基本的なアルゴリズムはWord2Vecと一緒そうですね。。
毎回計算するのではなく、キャッシュ化して計算を高速化しているのでしょうか。

論文
Enriching Word Vectors with Subword Information
skip-gram model is to maximize the log-likelihood
TΣt=1 ΣceCt log[p](wc|wt)
where the context Ct is the set of indices of words surrounding wt

-> 自然言語処理の歴史やコミュニティの勉強をかなりしている
-> 論文読む予備知識がかなり必要
-> とは言え、論文も身近な存在

自然言語処理の論文って言うと、勝手に院卒エンジニアの専門領域って先入観があったけど、別に誰でも読めるしアクセスできるんだなー