前回で終わりにしようと思っていたのですが今回もtfidfの記事です。
あまり使う機会はないのですが、kerasのテキストの前処理機能である、
keras.preprocessing.text.Tokenizer にも実はコーパスをtfidf化する機能が実装されています。
ドキュメントを読んでもtfidf機能があること自体が書いてありません。(したがって、実装されているtfidfの定義も書いてありません。)
参考: テキストの前処理 – Keras Documentation
そして、実際に動かしてみると、一般的な定義とは違う定義で動いているようなのでどのような計算式なのか調べました。
とりあえずいつもの例文で動かしてみます。
動かし方は、インスタンスを作り、fit_on_texts
でコーパスを学習した後、
texts_to_matrix
で変換するときに、引数でmode="tfidf"
するだけです。
やってみます。
import pandas as pd
from tensorflow.keras.preprocessing.text import Tokenizer
# 表示桁数を小数点以下3位までにする
pd.options.display.precision = 3
# データを準備
corpus = [
'This is the first document.',
'This document is the second document.',
'And this is the third one.',
'Is this the first document?',
]
keras_tokenizer = Tokenizer()
keras_tokenizer.fit_on_texts(corpus)
# 学習した単語のリストを作成(インデック0はpaddingした単語に予約されている)
word_list = [
keras_tokenizer.index_word.get(i, "")
for i in range(len(keras_tokenizer.index_word)+1)
]
df = pd.DataFrame(
keras_tokenizer.texts_to_matrix(corpus, mode="tfidf"),
columns=word_list
)
print(df)
"""
this is the document first second and third one
0 0.0 0.588 0.588 0.588 0.693 0.847 0.000 0.000 0.000 0.000
1 0.0 0.588 0.588 0.588 1.174 0.000 1.099 0.000 0.000 0.000
2 0.0 0.588 0.588 0.588 0.000 0.000 0.000 1.099 1.099 1.099
3 0.0 0.588 0.588 0.588 0.693 0.847 0.000 0.000 0.000 0.000
"""
出現する文書数とidfの対応は以下のようになっていそうです。
出現回数1回 ・・・ idf 1.099
出現回数2回 ・・・ idf 0.847
出現回数3回 ・・・ idf 0.693
出現回数4回 ・・・ idf 0.588
このようになる数式を探してみたところ、
$$
\text{idf}_{t} = \log{\left(1+\frac{\text{総文書数}}{1+\text{単語tを含む文書数}}\right)}
$$
が当てはまるようです。
scikit-learnのデフォルトの定義である、
$$
\text{idf}_{t} = \log{\left(\frac{1+\text{総文書数}}{1+\text{単語tを含む文書数}}\right)}+1
$$
と似てるけど微妙に違いますね。
検算します。
for n in range(1, 5):
print(np.log(1+4/(n+1)))
"""
1.0986122886681098
0.8472978603872034
0.6931471805599453
0.5877866649021191
"""
さて、idfがわかったので次はtdです。
tdに着目しやすくするために、単語1種類だけにして、出現回数だけ変えたコーパスで実験します。
corpus_2 = [
"document",
"document document",
"document document document",
"document document document document",
]
df = pd.DataFrame(
keras_tokenizer.texts_to_matrix(corpus_2, mode="tfidf"),
columns=word_list
)
print(df)
"""
this is the document first second and third one
0 0.0 0.0 0.0 0.0 0.693 0.0 0.0 0.0 0.0 0.0
1 0.0 0.0 0.0 0.0 1.174 0.0 0.0 0.0 0.0 0.0
2 0.0 0.0 0.0 0.0 1.455 0.0 0.0 0.0 0.0 0.0
3 0.0 0.0 0.0 0.0 1.654 0.0 0.0 0.0 0.0 0.0
"""
ifidfの値が、出現回数1回の場合(=idf)の何倍になっているかみます。
print(df["document"]/df["document"].iloc[0])
"""
0 1.000
1 1.693
2 2.099
3 2.386
Name: document, dtype: float64
"""
どうやら、
$$
\text{tf}_{t,d} = 1+\log{\text{文書d中の単語tの出現回数}}
$$
のようです。
検算がこちら。
for n in range(1, 5):
print(1+np.log(n))
"""
1.0
1.6931471805599454
2.09861228866811
2.386294361119891
"""
これで、実験的にkerasにおけるtfidfの定義がわかりました。
あとはドキュメントと突き合わせて確認したかったのですが、冒頭に書いた通りドキュメントには記載がありません。
と言うことで、ソースコードを直接みてみます。
どうやらこの部分が該当するようです。
参考: keras-preprocessing/text.py at master · keras-team/keras-preprocessing · GitHub
elif mode == 'tfidf':
# Use weighting scheme 2 in
# https://en.wikipedia.org/wiki/Tf%E2%80%93idf
tf = 1 + np.log(c)
idf = np.log(1 + self.document_count /
(1 + self.index_docs.get(j, 0)))
x[i][j] = tf * idf
ここまで実験的に導いてきた結論と一致しますね。
ハードコーディングされているのでscikit-learnのような細かなオプションはなさそうです。