scikit-learnのtfidfについての記事の2本目です。
今回は結果のベクトルの正規化について紹介していきます。
前の記事でも書きましたが、TfidfVectorizerはデフォルトでは結果のベクトルを長さが1になるように正規化します。
参考: tf-idfの一般的な定義とscikit-learnにおけるtf-idfの定義
ドキュメントのsklearn.feature_extraction.text.TfidfVectorizerのページの norm の説明にある通りです。
一応実験しておきます。サンプルの文章はUser Guideのページから拝借しました。
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
# 表示桁数を小数点以下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?',
]
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
norm='l2', # デフォルト
)
# コーパスを学習
tfidf = tfidf_model.fit_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.470 0.58 0.384 0.000 0.000 0.384 0.000 0.384
1 0.000 0.688 0.00 0.281 0.000 0.539 0.281 0.000 0.281
2 0.512 0.000 0.00 0.267 0.512 0.000 0.267 0.512 0.267
3 0.000 0.470 0.58 0.384 0.000 0.000 0.384 0.000 0.384
"""
# ベクトルの長さが1であることの確認
print((tfidf.toarray()**2).sum(axis=1)**0.5)
# [1. 1. 1. 1.]
さて、ドキュメントにnorm{‘l1’, ‘l2’}, default=’l2’
とある通り、norm='l1'
と指定することもできます。
そうすると、ベクトルの長さを1にする代わりに、各要素の絶対値の和が1になるように正規化してくれます。
これも実験しておきましょう。なお、tfidfの結果は元々各成分がプラスなので、絶対値を取る処理は省略しています。
厳密にやるのであれば和をとる前にnp.abs()
にかけましょう。
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
norm='l1',
)
# コーパスを学習
tfidf = tfidf_model.fit_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.213 0.263 0.174 0.000 0.00 0.174 0.000 0.174
1 0.000 0.332 0.000 0.136 0.000 0.26 0.136 0.000 0.136
2 0.219 0.000 0.000 0.114 0.219 0.00 0.114 0.219 0.114
3 0.000 0.213 0.263 0.174 0.000 0.00 0.174 0.000 0.174
"""
# ベクトルの要素の和が1であることを確認
print(tfidf.toarray().sum(axis=1))
# [1. 1. 1. 1.]
ドキュメントでは、normには'l1'と'l2'の値しかサポートされていなように書いてありますが、実は他にも指定できる文字があります。
と言うのもGithubのソースコード(この記事を書いている時点ではバージョンは0.24.0)では、次のように実装されているからです。
参考: 該当箇所
if self.norm:
X = normalize(X, norm=self.norm, copy=False)
sklearn.preprocessing.normalize が呼び出されてます。
そして、そのドキュメントをみると、'l1','l2'に加えて'max'も対応しています。
'max'は、一番絶対値が大きい要素の絶対値が1になるように、要するに全部の要素の値が-1から1の範囲に収まるように、そして絶対値が一番大きい成分の値が±1になるように正規化してくれます。
ドキュメントに無い使い方なので、ご利用される場合は自己責任でお願いしたいのですが、次のコードの通り問題なく動作します。
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
norm='max',
)
# コーパスを学習
tfidf = tfidf_model.fit_transform(corpus)
print(pd.DataFrame(tfidf.toarray(), columns=tfidf_model.get_feature_names()))
"""
and document first is one second the third this
0 0.0 0.81 1.0 0.662 0.0 0.000 0.662 0.0 0.662
1 0.0 1.00 0.0 0.409 0.0 0.783 0.409 0.0 0.409
2 1.0 0.00 0.0 0.522 1.0 0.000 0.522 1.0 0.522
3 0.0 0.81 1.0 0.662 0.0 0.000 0.662 0.0 0.662
"""
また、もう一つ、これもドキュメントには無いのですが、正規化しないようにすることも可能です。
if self.norm:
とif文で分岐していますので、ここでFalseと判定される値を入れておけば大丈夫です。
FalseやNoneを入れておけば正規化されません。
(空白文字列でも、空配列でも、0でもお好きな値を使えますが、あまりトリッキーなことをしても意味はないのでFalseで良いでしょう)
tfidf_model = TfidfVectorizer(
token_pattern="(?u)\\b\\w+\\b",
norm=False,
)
# コーパスを学習
tfidf = tfidf_model.fit_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.223 1.511 1.0 0.000 0.000 1.0 0.000 1.0
1 0.000 2.446 0.000 1.0 0.000 1.916 1.0 0.000 1.0
2 1.916 0.000 0.000 1.0 1.916 0.000 1.0 1.916 1.0
3 0.000 1.223 1.511 1.0 0.000 0.000 1.0 0.000 1.0
"""
次の記事からtfとidfの定義に関するオプションをいじって挙動を見ていくのですが、
その際は違いを見やすくするために正規化は行わないで試していきます。