TF-IDF とは

複数の文章・コーパスが与えられた時、「各単語がその文書内でどれくらい重要・特徴的か」を表す尺度。 文章ごとのキーワード特定、文章の単語検索のスコアリング、文章のベクトル表現などに利用される。

定義と解釈

TF

TF の定義

TF = Term Frequency.

$TF(t, d)$ は、ある文章 $d$ 中で、単語が $t$ が出現する頻度を表す。

\[TF(t_i, d_j) = \cfrac{ | \{ t: \ t \in d_j, \ t = t_i \} | }{|d_j|}\]

${ t: \ t \in d_j, \ t = t_i }$ は $d_j$ 内の $t_i$ に一致する単語の集合。

TF の解釈

  • その文章の中でその単語が何度も出現するほど値が大きい
  • つまり、その文章内での単語の重要度 を表す
  • $TF$ が大きくなるケース:
    • you, is, a, the など、どんな文にもよく出てくる 一般語
    • その文章のメインテーマに関係が深い単語

IDF

IDF の定義

IDF = Inverse Document Frequency.

全文章 $D$ のうち、単語 $t$ を含む文章の割合($DF$, document frequency)の逆数。

\[\begin{eqnarray} IDF(t_i, D) &=& \left( \cfrac{|\{ d: \ d \in D, t_i \in d \}|}{|D|} \right)^{-1} \\ &=& \cfrac{|D|}{|\{ d: \ d \in D, t_i \in d \}|} \end{eqnarray}\]

${ d: \ d \in D, t_i \in d }$ は単語 $t_i$ を含む文章の集合。

一般に、ゼロ除算を防ぐために分母に1を足し、対数スケールを取って

\(IDF(t_i, D) = \log \left( \cfrac{|D|}{|\{ d: \ d \in D, t_i \in d \}| + 1} \right)\) の形で使われることが多い。

IDF の解釈

  • 全文章の中で、その単語を持つ文章が少ないほど値が大きい
  • つまり、全ての文章中での単語の珍しさ を表す
  • $IDF$ が大きくなるケース:
    • 特定の文章にしか出現しない単語 = 専門性が高い単語
  • $IDF$ が小さくなるケース:
    • 多くの文章に共通して出現する単語 = 一般語

TF-IDF

TF-IDF の定義

\[\begin{eqnarray} TF \cdot IDF (t_i, d_j, D) &=& TF(t_i, d_j) IDF(t_i, D) \\ &=& \cfrac{ | \{ t: \ t \in d_j, \ t = t_i \} | }{|d_j|} \log \left( \cfrac{|D|}{|\{ d: \ d \in D, t_i \in d \}| + 1} \right) \end{eqnarray}\]

TF-IDF の解釈

  • $TF$ 単独で単語のスコアにすると、you, is, a, the など一般語のスコアも高くなってしまう
  • そこに $IDF$ をかけることで、一般語のスコアが下がり、「専門性が高くその文章のテーマに関係が深い単語」 のスコアが高くなる

具体例

  • $D={d_1,d_2,d_3,d_4,d_5}$
  • $d_1 = { t_1, t_2, t_3, {\color{red}{t_4, t_5, t_5}} }$
  • $d_2 = { t_1, t_2, t_3, {\color{red}{t_1, t_4, t_4, t_4, t_5, t_5}} }$
  • $d_3 = { t_1, t_2, t_3, {\color{red}{t_1, t_2, t_5, t_6, t_6, t_6, t_7}} }$
  • $d_4 = { t_1, t_2, t_3, {\color{red}{t_3, t_6, t_6, t_7, t_7}} }$
  • $d_5 = { t_1, t_2, t_3, {\color{red}{t_2, t_2, t_3, t_6, t_7, t_7, t_7}} }$

全ての $t_i, d_j$ に対して $TF(t_i, d_j)$ を計算すると、

import numpy as np

word_count = np.array([
    #t1 t2 t3 t4 t5 t6 t7
    [1, 1, 1, 1, 2, 0, 0],  # d1
    [2, 1, 1, 3, 2, 0, 0],  # d2
    [2, 2, 1, 0, 1, 3, 1],  # d3
    [1, 1, 2, 0, 0, 2, 2],  # d4
    [1, 3, 2, 0, 0, 1, 3]   # d6
])

tf = word_count.T / word_count.sum(axis=1)
print(tf)
"""
         d1         d2         d3         d4         d5
t1     [[0.16666667 0.22222222 0.2        0.125      0.1       ]
t2      [0.16666667 0.11111111 0.2        0.125      0.3       ]
t3      [0.16666667 0.11111111 0.1        0.25       0.2       ]
t4      [0.16666667 0.33333333 0.         0.         0.        ]
t5      [0.33333333 0.22222222 0.1        0.         0.        ]
t6      [0.         0.         0.3        0.25       0.1       ]
t7      [0.         0.         0.1        0.25       0.3       ]]
"""
(計算例)$ d_3 = 10$ であり、$d_3$ に $t_6$ は3回出現するから、
\[TF(t_6, d_3) = \cfrac{3}{10} = 0.3\]

次に、全ての $t_i$ に対して $IDF(t_i, D)$ を計算すると、

size_D = word_count.shape[0]  # 5
num_docs_of_each_word = (word_count > 0).sum(axis=0)
print(num_docs_of_each_word)
# [5 5 5 2 3 3 3]
idf = np.log(size_D / (num_docs_of_each_word + 1))
print(idf)
"""
  t1          t2          t3          t4          t5          t6          t7
[-0.18232156 -0.18232156 -0.18232156  0.51082562  0.22314355  0.22314355 
 0.22314355]
"""

(計算例)$t_6$ を含む文章は全5件中3件あるから、

\[IDF(t_6, D) = \log \left( \cfrac{5}{3 + 1} \right) = 0.22314355\]

以上を用いて、全ての $t_i, d_j$ に対して $TF \cdot IDF (t_i, d_j, D)$ を計算すると、

tf_idf = (tf.T * idf).T
print(tf_idf)
"""
          d1          d2          d3          d4          d5
t1     [[-0.03038693 -0.0405159  -0.03646431 -0.02279019 -0.01823216]
t2      [-0.03038693 -0.02025795 -0.03646431 -0.02279019 -0.05469647]
t3      [-0.03038693 -0.02025795 -0.01823216 -0.04558039 -0.03646431]
t4      [ 0.0851376   0.17027521  0.          0.          0.        ]
t5      [ 0.07438118  0.04958746  0.02231436  0.          0.        ]
t6      [ 0.          0.          0.06694307  0.05578589  0.02231436]
t7      [ 0.          0.          0.02231436  0.05578589  0.06694307]]
"""
  • どの文章にも出現する一般語 $t_1, t_2, t_3$ の値は期待通りに低い
  • 一部の文章にしか出現しない $t_4, t_5, t_6, t_7$ の値は期待通りに高い
    • $d_4$ にしか登場しない $t_7$ の値は特に高い(= IDF によるブースト)
    • $d_2$ において出現頻度が高い $t_4$ の値も高め(= TF によるブースト)

TF-IDF により各文章をベクトル表現することができたので、文書間のコサイン類似度を計算してみる。

# 内積
inner_product = np.dot(tf_idf.T, tf_idf)
# 各文章ベクトルの長さ
vector_length = np.sqrt((tf_idf ** 2).sum(axis=0))
# 文章ベクトルの長さの積
vector_length_product = np.array([vector_length]).T.dot([vector_length])
# ドキュメント同士のコサイン類似度
cos_simularity = inner_product / vector_length_product
print(cos_simularity)
"""
         d1         d2         d3         d4         d5
d1     [[1.         0.89906767 0.38600755 0.22984227 0.27158994]
d2      [0.89906767 1.         0.21784343 0.12969812 0.14303893]
d3      [0.38600755 0.21784343 1.         0.84015669 0.69879445]
d4      [0.22984227 0.12969812 0.84015669 1.         0.87536651]
d5      [0.27158994 0.14303893 0.69879445 0.87536651 1.        ]]
"""
  • 同じ文章同士の類似度は、完全に同じベクトルなので当然1
  • $d_1, d_2$ 同士、$d_3, d_4, d_5$ 同士はかなり類似度が高い
  • $d_1, d_2$ と $d_3, d_4, d_5$ はあまり類似していない
    • その中でも、$d_3$ は $d_1, d_2$ と共通の単語 $t_5$ を持つので、$d_1, d_2$ への類似度が比較的高い

色々な重み付け

(ToDo)