Material Book of Statistics

統計、機械学習、プログラミングなどで実験的な試みを書いていきます。

scikit-learn.feature_extraction.textのTfidfVectorizerを検証する

arXivRSSで取得できる最新情報から自分に合うものをレコメンドしてくれるSlack Botを作っています。
まずはTF-IDFを使ってレコメンドを作る予定なので、scikit-learnのTfidfVectorizerを初めて触ってみました。
以下では、 http://scikit-learn.org/stable/modules/feature_extraction.html#common-vectorizer-usage を元にして、使う機能を検証してみます。
テストで使うコーパスは上記のURLにあるものを使います。

corpus = [
    'This is the first document.',
    'This is the second second document.',
    'And the third one.',
    'Is this the first document?',
]

TF-IDFを計算する

初めにTfidfVectorizerクラスのインスタンスを作成して設定を確認してみます。

from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
vectorizer 
TfidfVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=<class 'numpy.int64'>, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), norm='l2', preprocessor=None, smooth_idf=True,
        stop_words=None, strip_accents=None, sublinear_tf=False,
        token_pattern='(?u)\\b\\w\\w+\\b', tokenizer=None, use_idf=True,
        vocabulary=None)

パラメータが多いですが、現在考えている用途で使いそうなのは次の2つです。

ngram_range: N-gramのNの下限と上限をタプルで指定します。

ngram_range : tuple (min_n, max_n)

The lower and upper boundary of the range of n-values for different n-grams to be extracted. All values of n such that min_n <= n <= max_n will be used.

stop_words: ストップワード。現在の用途では"english"を選択しておけば良さそう。

stop_words : string {‘english’}, list, or None (default)

If a string, it is passed to _check_stop_list and the appropriate stop list is returned. ‘english’ is currently the only supported string value.

If a list, that list is assumed to contain stop words, all of which will be removed from the resulting tokens. Only applies if analyzer == 'word'.

If None, no stop words will be used. max_df can be set to a value in the range [0.7, 1.0) to automatically detect and filter stop words based on intra corpus document frequency of terms.

(引用部分の出所) TfidfVectorizerのAPIリファレンス

fit_transformメソッドでコーパストークン化して対応するTF-IDFの値を計算してくれます。

X = vectorizer.fit_transform(corpus)
X
<4x9 sparse matrix of type '<class 'numpy.float64'>'
    with 19 stored elements in Compressed Sparse Row format>

上の行列の中身を確認するためにprint(X)とすると...

print(X)
  (0, 8)    0.438776742859
  (0, 3)    0.438776742859
  (0, 6)    0.358728738248
  (0, 2)    0.541976569726
  (0, 1)    0.438776742859
  (1, 8)    0.272301467523
  (1, 3)    0.272301467523
  (1, 6)    0.222624292325
  (1, 1)    0.272301467523
  (1, 5)    0.853225736145
  (2, 6)    0.28847674875
  (2, 0)    0.552805319991
  (2, 7)    0.552805319991
  (2, 4)    0.552805319991
  (3, 8)    0.438776742859
  (3, 3)    0.438776742859
  (3, 6)    0.358728738248
  (3, 2)    0.541976569726
  (3, 1)    0.438776742859

となります。
初めてこのアウトプットを見た時、「"(0, 8)"というのは行列の0行8列を指している」と思いましたが、行列の構造がまったく分からなかったので、このような簡単な検証をすることにしました。
構造を理解してからこのアウトプットを見ると、下の行列よりも見やすいと言えそうです。

行列の構造を理解する

TF-IDFの行列はtoarrayメソッドで取り出すことができます。

X.toarray()
array([[ 0.        ,  0.43877674,  0.54197657,  0.43877674,  0.        ,
         0.        ,  0.35872874,  0.        ,  0.43877674],
       [ 0.        ,  0.27230147,  0.        ,  0.27230147,  0.        ,
         0.85322574,  0.22262429,  0.        ,  0.27230147],
       [ 0.55280532,  0.        ,  0.        ,  0.        ,  0.55280532,
         0.        ,  0.28847675,  0.55280532,  0.        ],
       [ 0.        ,  0.43877674,  0.54197657,  0.43877674,  0.        ,
         0.        ,  0.35872874,  0.        ,  0.43877674]])

上で取り出した行列の行はコーパスの各文のインデックスに対応し、列は各文から抽出されたトークンになっています。
実際にコーパス

corpus
['This is the first document.',
 'This is the second second document.',
 'And the third one.',
 'Is this the first document?']

となっています。
一方でトークンは

vectorizer.get_feature_names()
['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']

で、文に含まれているトークンのところにTF-IDFの値が入っています。

まとめ

sklearnでTF-IDFを算出する方法を検証しました。これでレコメンド部分が書けそうです。

ちなみに、sklearn.feature_extraction.textのVectorizerにはCountVectorizerもありますが、基本的な動作は一緒です。
したがって、これまでTfidfVectorizerで行ってきた操作はCountVectorizerでも同じように動きます。

参考URL