この記事では、scikit-learnのTfidfVectorizerの、idf部分について掘り下げてみていきます。
以前の記事でも書いた通り、デフォルトでのidfの挙動は一般的な定義とは異なります。
参考: tf-idfの一般的な定義とscikit-learnにおけるtf-idfの定義
単語$t$についてみていくと、通常の定義は
$$
\text{idf}_{t} = \log{\frac{\text{総文書数}}{\text{単語tを含む文書数}}}
$$
であり、
TfidfVectorizer のデフォルトのオプションでは、
$$
\text{idf}_{t} = \log{\frac{1+\text{総文書数}}{1+\text{単語tを含む文書数}}}+1
$$
となっています。
まず、実際にこの数式通り動いていることを見ておきましょう。
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
# データを準備しておく
corpus = [
'This is the first document.',
'This document is the second document.',
'And this is the third one.',
'Is this the first document?',
]
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
)
tfidf_model.fit(corpus)
# モデルから抽出した idfの値
for t, idf in zip(tfidf_model.get_feature_names(), tfidf_model.idf_):
print(t, idf)
"""
and 1.916290731874155
document 1.2231435513142097
first 1.5108256237659907
is 1.0
one 1.916290731874155
second 1.916290731874155
the 1.0
third 1.916290731874155
this 1.0
"""
# 出現回数から計算したidfの値
for i in range(1, 5):
print(f"出現回数{i}回, idf: ", np.log((1+4)/(1+i))+1)
"""
出現回数1回, idf: 1.916290731874155
出現回数2回, idf: 1.5108256237659907
出現回数3回, idf: 1.2231435513142097
出現回数4回, idf: 1.0
"""
document は3文書に登場しているから、idfは 1.2231435513142097、firtstは2文書に登場しているから、idfは1.5108256237659907と言うふうに、
scikit-learnのドキュメント通りに計算されていることがわかりますね。
さて、scikit-learnの定義がデフォルトと異なっている理由は、次の2つがあります。
(1) コーパス中に登場しない単語で0除算が発生しないように、log中の分数の分子と分母に1を足す。
(2) コーパス中の全ての文書に登場した単語のidfが0にならないように全ての単語のidfに1を足す。
(1)の方は平滑化と呼ばれる操作です。
実はTfidfVectorizerは、学習する単語をコーパスから自動的にピックアップするのではなく、モデルの引数として渡すことができます。
次のコード例では、コーパスに登場しない、oovという単語を明示的に加えてみました。
結果、$\log((1+4)/(1+0))+1 = 2.6094379124341005$ がoovのidf値になっていることがわかります。
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
vocabulary=['and', 'document', 'first', 'is',
'one', 'second', 'the', 'third', 'this', "oov"],
)
tfidf_model.fit(corpus)
# モデルから抽出した idfの値
for t, idf in zip(tfidf_model.get_feature_names(), tfidf_model.idf_):
print(t, idf)
"""
and 1.916290731874155
document 1.2231435513142097
first 1.5108256237659907
is 1.0
one 1.916290731874155
second 1.916290731874155
the 1.0
third 1.916290731874155
this 1.0
oov 2.6094379124341005
"""
この分子と分母の+1については、smooth_idfと言う引数にFalseを渡すことで使わないようにもできます。
参考: sklearn.feature_extraction.text.TfidfVectorizer
この場合、vocabulary引数で、コーパスに登場しない単語が渡されていると、0除算のワーニングが発生し、該当単語のidf値はinfになります。
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
vocabulary=['and', 'document', 'first', 'is',
'one', 'second', 'the', 'third', 'this', "oov"],
smooth_idf=False,
)
tfidf_model.fit(corpus)
"""
RuntimeWarning: divide by zero encountered in true_divide
idf = np.log(n_samples / df) + 1
"""
# モデルから抽出した idfの値
for t, idf in zip(tfidf_model.get_feature_names(), tfidf_model.idf_):
print(t, idf)
"""
and 2.386294361119891
document 1.2876820724517808
first 1.6931471805599454
is 1.0
one 2.386294361119891
second 2.386294361119891
the 1.0
third 2.386294361119891
this 1.0
oov inf
"""
tfidfの本来の定義に近い値で使う場合は、smooth_idf=Falseを指定するべきですが、この場合は安全のため、vocabularyは指定せず、
コーパスから自動的に学習するのに任せるべきでしょう。
(僕の場合は、vocabulary引数を使うことはほとんどありません。)
次に、(2)の全てのコーパスに倒叙する単語のidfが0にならないように、全部単語のidfに1足されている部分についてです。
こちらについては、これをオフにする引数のようなものは実装されていなさそうです。
どうしてもtfidfの本来の定義で使いたいんだ、と言う場合は、かなり無理矢理な操作になりますが、
モデルが学習したidfの値(idf_プロパティに格納されている)から1を引いてしまう手があります。
(当然、サポートされている操作ではないので実行は自己責任でお願いします。)
transform する際には、idf_ の値が使われるので次のようになります。
import pandas as pd
# 表示桁数を小数点以下3位までにする
pd.options.display.precision = 3
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
norm=False,
smooth_idf=False,
)
tfidf_model.fit(corpus)
tfidf = tfidf_model.transform(corpus)
print(pd.DataFrame(tfidf.toarray(), columns=tfidf_model.get_feature_names()))
"""
and document first is one second the third this
0 0.000 1.288 1.693 1.0 0.000 0.000 1.0 0.000 1.0
1 0.000 2.575 0.000 1.0 0.000 2.386 1.0 0.000 1.0
2 2.386 0.000 0.000 1.0 2.386 0.000 1.0 2.386 1.0
3 0.000 1.288 1.693 1.0 0.000 0.000 1.0 0.000 1.0
"""
# idf_ に足されている1を引く
tfidf_model.idf_ -= 1
tfidf = tfidf_model.transform(corpus)
print(pd.DataFrame(tfidf.toarray(), columns=tfidf_model.get_feature_names()))
"""
and document first is one second the third this
0 0.000 0.288 0.693 0.0 0.000 0.000 0.0 0.000 0.0
1 0.000 0.575 0.000 0.0 0.000 1.386 0.0 0.000 0.0
2 1.386 0.000 0.000 0.0 1.386 0.000 0.0 1.386 0.0
3 0.000 0.288 0.693 0.0 0.000 0.000 0.0 0.000 0.0
"""
コーパスの全ての文書に含まれていた、 is, the, this の idf値とtfidf値が0になっていることが確認できます。
また、他の単語についても、出現回数分値が落ちているのがみて取れます。