【sklearn】TfidfVectorizerの使い方を丁寧に

はじめに

本記事では[sklearn.feature_extraction.text.TfidfVectorizerについて丁寧に説明します.

公式ドキュメント:

scikit-learn.org

tfとidf

  • tf

Term Frequencyで,単語の出現頻度です.文書 d_jにおける単語 w_iのtfは

 tf_{w_i, d_j} = \frac{w_iの出現数}{d_jの単語数}

です.文書群が決まっているとき,tfは文書と単語を指定して初めて計算できます.

「文書中により高頻度で出現する単語ほど,その文書にとって重要だ」という考えです.

  • idf

Inverse Document Frequencyで,逆文書頻度です.idfの計算方法は,+1するなどのケアによって複数存在しますが,TfidfVectorizerで使われているものを紹介します.文書群における単語 w_iのidfは

 idf_{w_i} = \log \frac{文書数 + 1}{w_iが出現する文書数 + 1} + 1

です.文書群が決まっているとき,idfは単語さえ決めれば計算できます.

「特定の文書に出現する単語ほど,ある話題に特化した意味のある単語である」という考えです.

  • tf-idf

tf-idfは,上記2つの値の積で表されます.

TfidfVectorizerの役割

TfidfVectorizerは,文書群を与えると,各文書をtf-Idfの値を元にしたベクトルに変換するものです.

TfidfVectorizerの入出力

  • 入力

文字列のリストです.1つの文字列が1つの文書に相当します.

例:

['I go to the park .',
 'I will go shopping .']

この例は,以降でも使うことにします.

  • 出力

2次元の行列が返ります.正確にはscipyのオブジェクトで,shapeは(文書数, 語彙サイズ)です.各文書の存在する単語がTfidfに置き換わったようなリストです.

例:

#['go', 'park', 'shopping', 'the', 'to', 'will']  -> 対応する単語
[[0.37  0.53    0.          0.53   0.53  0.    ]  # -> 文書1のベクトル
 [0.44  0.      0.63        0.     0.    0.63  ]] # -> 文書2のベクトル

(上記の例は分かりやすさのため変形しているので,実際の出力とは異なります)

TfidfVectorizerの宣言

モジュールをインポートして,インスタンスを作るだけです.

from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()

主なメソッド・属性

fit()

入力の文書群を与えて,語彙の獲得やidfの計算をします.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
vectorizer.fit(corpus)

transform()

fit()したことで得た語彙やidfを元に,文書をtf-idf行列に変換します.

以下の例では,返り値はscipyのオブジェクトで,shapeは(2,6) = (文書数, 語彙サイズ)であることが分かります.

返り値をそのまま出力すると,(行, 列) その要素の値の形式で出力されます.例えば,(0, 4) 0.534046329052269であれば,04列の要素が0.534046329052269と読めます. また,tf-idfの値が0でない要素のみ出力されます.

pythonのリストのように出力したい場合は,.toarray()で変形してから出力します.この場合,tf-idfが0の要素も表示されます.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
vectorizer.fit(corpus)
X = vectorizer.transform(corpus)
print('type ->',type(X))
print('shape ->',X.shape)
print(X)
print(X.toarray())
'''
出力:
type -> <class 'scipy.sparse.csr.csr_matrix'>
shape -> (2, 6)
  (0, 4)        0.534046329052269
  (0, 3)        0.534046329052269
  (0, 1)        0.534046329052269
  (0, 0)        0.37997836159100784
  (1, 5)        0.6316672017376245
  (1, 2)        0.6316672017376245
  (1, 0)        0.4494364165239821
[[0.37997836 0.53404633 0.         0.53404633 0.53404633 0.        ]
 [0.44943642 0.         0.6316672  0.         0.         0.6316672 ]]
'''

fit_transform()

入力が文書群,出力がtf-idf行列です.

fit()transform()を同時に行います.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(X.toarray())
'''
[[0.37997836 0.53404633 0.         0.53404633 0.53404633 0.        ]
 [0.44943642 0.         0.6316672  0.         0.         0.6316672 ]]
'''

get_feature_names()

特徴量ラベル(=語彙)を表示します.transform()した後の行列では語彙サイズが列数になりますが,どの列がどの単語なのかを知る場合には,この関数を使います.

表示される単語は辞書順になります.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
print(X.toarray())
'''
['go', 'park', 'shopping', 'the', 'to', 'will']
[[0.37997836 0.53404633 0.         0.53404633 0.53404633 0.        ]
 [0.44943642 0.         0.6316672  0.         0.         0.6316672 ]]
'''

1つめの文書におけるgoのtf-idfは0.37997836のようです.

inverse_transform()

入力がtf-idf行列,出力が各文書の単語集合です(データ型としてはリストです).

tf-idfの行列から元の文書を復元します.単語の順番は保存されず,単語の集合が得られるような形になります.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
# tf-idfを得て
X = vectorizer.fit_transform(corpus)
# 元に戻してみる
print(vectorizer.inverse_transform(X))
'''
出力
[array(['go', 'park', 'the', 'to'], dtype='<U8'), array(['go', 'shopping', 'will'], dtype='<U8')]
'''

idf_

idfだけを出力できます.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)

print(vectorizer.get_feature_names())
print(vectorizer.idf_)
'''
出力
['go', 'park', 'shopping', 'the', 'to', 'will']
[1.         1.40546511 1.40546511 1.40546511 1.40546511 1.40546511]
'''
# log(2/1)+1 = 1.4054..

vocabulary_

[単語]=単語idとなる辞書を出力します.単語idを添字だと思ってリストに直すと,get_feture_names()と同じになります.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(vectorizer.vocabulary_)
'''
出力:
{'go': 0, 'to': 4, 'the': 3, 'park': 1, 'will': 5, 'shopping': 2}
'''

主なオプション

smooth_idf=

bool値,デフォルトTrue

idfの計算方法に関するオプションです.Trueであれば

 idf_{w_i} = \log \frac{文書数 + 1}{w_iが出現する文書数 + 1} + 1

で計算されます.Falseであれば

 idf_{w_i} = \log \frac{文書数}{w_iが出現する文書数} + 1

で計算されます.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer_t = TfidfVectorizer(smooth_idf=True)
X = vectorizer_t.fit_transform(corpus)
print(vectorizer_t.get_feature_names())
print(vectorizer_t.idf_) # Trueのとき

vectorizer_f = TfidfVectorizer(smooth_idf=False)
X = vectorizer_f.fit_transform(corpus)
print(vectorizer_f.idf_) # Falseのとき
'''
出力
['go', 'park', 'shopping', 'the', 'to', 'will']
[1.         1.40546511 1.40546511 1.40546511 1.40546511 1.40546511]
[1.         1.69314718 1.69314718 1.69314718 1.69314718 1.69314718]
'''
# log(2/1)+1 = 1.4054..
# log(3/2)+1 = 1.6931..

analyzer=

文字列,デフォルトword

tf-idfの計算における単語の単位を指定します.wordだと単語単位で,charだと文字単位で特徴量が生成されます.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer(analyzer='char')
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
'''
出力
[' ', '.', 'a', 'e', 'g', 'h', 'i', 'k', 'l', 'n', 'o', 'p', 'r', 's', 't', 'w']
'''

ngram_range=

2次元リスト(タプル),デフォルト(1,1)

特徴量として加えるngramの範囲を(下限, 上限)で指定できます.デフォルトは(1,1)であるため,(analyzer='word'なら) 1単語ずつtf-idfに変換されます.(1,2)なら,各単語に加えてbi-gramも考慮されることになります.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer(ngram_range=(1,2))
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
'''
出力
['go', 'go shopping', 'go to', 'park', 'shopping', 'the', 'the park', 'to', 'to the', 'will', 'will go']
'''

max_features=

整数,デフォルトNone

特徴量数の上限を指定できます.本来,変換後のtf-idfのshapeは(文書数, 語彙サイズ)ですが,これを指定すると(文書数, min(max_features, 語彙サイズ))となります.このとき,コーパス中の出現数が多い単語が優先的に特徴量になるようです.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer(max_features=4)
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
'''
['go', 'park', 'shopping', 'the']
'''

vocabulary=

文字列のリスト,デフォルトNone

語彙を与えることができます.Noneであれば,語彙はコーパスから自動で獲得されます.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park .',
          'I will go shopping .']
vectorizer = TfidfVectorizer(vocabulary=['go', 'park'])
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names())
print(X.toarray())
'''
['go', 'park']
[[0.57973867 0.81480247]
 [1.         0.        ]]
'''

binary=

bool,デフォルトFalse

Trueでは,tfを求めるときの分子が0(単語が出現しない),または1(単語が出現した)のバイナリになります.つまり,ある文書に同じ単語が複数回出現してもあまり意味を持たないようになります.

出力のtf-idf行列がバイナリになるわけではありません(それはただのbag of wordsですし...).

from sklearn.feature_extraction.text import TfidfVectorizer
# わざとgoを2回出現させた
corpus = ['I go to the park . go']
vectorizer_t = TfidfVectorizer(binary=True)
X = vectorizer_t.fit_transform(corpus)
print(vectorizer_t.get_feature_names())
print(X.toarray())

vectorizer_f = TfidfVectorizer(binary=False)
X = vectorizer_f.fit_transform(corpus)
print(X.toarray())
'''
出力
['go', 'park', 'the', 'to']
[[0.5 0.5 0.5 0.5]]
[[0.75592895 0.37796447 0.37796447 0.37796447]]
 '''

norm=

文字列,デフォルトl2

文書ベクトルのノルムに関する項目です.

l1だとL1正則(ベクトル要素の絶対値和が1),l2だとL2正則(ベクトル要素の二乗和が1)が適用されます.l2の場合,2つの文書ベクトルのコサイン類似度と内積が一致します.

from sklearn.feature_extraction.text import TfidfVectorizer
corpus = ['I go to the park . go']
vectorizer_l1 = TfidfVectorizer(norm='l1')
X = vectorizer_l1.fit_transform(corpus)
print(vectorizer_l1.get_feature_names())
print('l1:',X.toarray())

vectorizer_l2 = TfidfVectorizer(norm='l2')
X = vectorizer_l2.fit_transform(corpus)
print('l2:',X.toarray())
'''
['go', 'park', 'the', 'to']
l1: [[0.4 0.2 0.2 0.2]]
l2: [[0.75592895 0.37796447 0.37796447 0.37796447]]
'''