特徴語抽出とtf-idf|実践的自然言語処理入門 #2

#1では自然言語教師あり学習を適用するにあたって、BoWと形態素解析の導入を行いました。

#2ではPoC開発などで用いやすい特徴語抽出とその有名なアルゴリズムであるtf-idfについてまとめられればと思います。
以下目次になります。

1. PoC開発における特徴語抽出
2. tf-idfの仕組み(理論)
3. tf-idfの実装
4. まとめ


1. PoC開発における特徴語抽出

データ分析や機械学習などのプロジェクトを進めるにあたってはやってみないとわからないことも多いので、まず最初にPoC(Proof of Concept)の開発を行うことが多いです。これを行うことで、アウトプットの擦り合わせをしながらプロジェクトをどんどん進めていくことが可能です。物がない時点だと議論が空中戦になることも多いし、精度面での期待値の擦り合わせも難しいので擦り合わせは非常に重要です。
さて、自然言語処理におけるPoCの製作にあたってですが、一番便利なのが特徴語抽出です。シンプルに入力した文章で特徴的に使用されている単語を出力するだけなのですが、アウトプットが直感的でわかりやすいのでイメージの擦り合わせが行いやすいです。また、#1でまとめたBoWにおいて出現頻度の代わりに用いるなど色々と使える範囲が広いです。

上記が特徴語抽出を行うにあたってのモチベーションですが、2節ではこの特徴語解析の理論的な仕組み、3節ではPythonのライブラリであるscikit-learnを用いたtf-idfの実装についてそれぞれ解説を行います。


2. tf-idfの仕組み(理論)

tf-idfの仕組みはさほど複雑ではないので、Wikipediaを参考にすれば十分だと思います。

tf-idf - Wikipedia

tf-idfは、文書中に含まれる単語の重要度を評価する手法の1つであり、主に情報検索やトピック分析などの分野で用いられている。tf-idfは、tf(英: Term Frequency、単語の出現頻度)とidf(英: Inverse Document Frequency、逆文書頻度)の二つの指標に基づいて計算される。

上記はWikipediaの説明なのですが、tfはそのままで単語の頻度をカウントするのですが、idfは単語のレア度を計算する指標として、なかなか出てこない単語のスコアを高くする一方で多くの文書に出てくる単語のスコアを低く出してくれるのでレアな単語に下駄を履かせて計算することが可能です。(具体的な計算に関してはWikipediaを見てわからなければ一旦飛ばしても良いと思います。ざっくりイメージだけ説明しておくと、logは単調増加関数なので無視して中に着目するとレアな単語ほど値が大きくなるような計算式になっています)

tf-idfはこの二つの考え方に基づいて計算されるtfとidfを掛け合わせることで、単語のスコアをつけるため文章に特徴的な単語に高いスコアがつくようなアルゴリズムになっています。


3. tf-idfの実装

ここまでで特徴語抽出を行うモチベーションとベーシックなアルゴリズムであるtf-idfについて解説しましたが、3節ではtf-idfの実際の実装について取り扱えればと思います。tf-idfの実装にあたってはscikit-learnを用いることで実行できます。以下では実装についてまとめていきます。 

 

・BoWの作成について
sklearn.feature_extraction.text.CountVectorizerを使うことでBoW形式のデータを作成することができます。以下のコードを実行することで、大体のイメージがつくのではないかと思います。

from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
corpus = [
       'This is the first document.',
       'This is the second second document.',
       'And the third one.',
       'Is this the first document? Yes it is.',
]
X = vectorizer.fit_transform(corpus)
print(type(corpus))
print(type(X))
print(X.toarray())
print(type(X.toarray()))

f:id:lib-arts:20190123115907p:plain
実行結果は上図のようになります。Xは"scipy.sparse.csr.csr_matrix"の形式で与えられるので、NumPy形式で扱うにはX.toarray()とする必要があります。ここでscipy.sparseがなぜ出てくるかですが、自然言語処理の結果として現れるBoWは辞書と文書数が大きくなると非常に大きな行列になる一方で、大半の値が0の行列になってしまいます。これを疎行列(Sparse matrix)と言うのですが、NumPy形式で扱うとオーバーヘッドが大きいのでこのような形式で扱うことがあります。この辺何も考えないで使用するとscikit-learnなどの分類器の実装によっては動かないので注意が必要です。

また、辞書を出力するには下記を実行すれば良いです。

print(vectorizer.get_feature_names())

f:id:lib-arts:20190123115939p:plain

 

・tf-idfの計算

from sklearn.feature_extraction.text import TfidfTransformer
transformer = TfidfTransformer(norm=None)
tfidf = transformer.fit_transform(X)
print(tfidf.toarray())
print(tfidf)

上記を実行すれば下図のような実行結果が得られます。(表示を綺麗にするために、np.set_printoptions(precision=3)を用いて桁数を落としました)

f:id:lib-arts:20190123120112p:plain

ここでTfidfTransformerの引数にnorm=Noneを与えているのは正規化を行わない方がわかりやすいためです。上図の出力結果を列単位で注目すると、それぞれ1つの文章でしか出ていない単語が1.916、2つの文章で出現していたら1.511、3つの文書で出現していたら1.223、4つともで出現していたら1がidfの値でそれぞれの単語の出現頻度に掛けられてた値が出力されており、2節で説明したtf-idfを確認することができます。


4. まとめ

#2では特徴語抽出、tf-idf、CountVectorizer、TfidfTransformerなど色々と出てきましたが、それぞれ整理してご理解いただければと思います。

#3ではWord2vecについて解説できればと思います。

 

↓以降の記事リストは下記です。

cos類似度と文書分類|実践的自然言語処理入門 #4 - lib-arts’s diary

言語処理におけるグラフ理論とネットワーク分析|実践的自然言語処理入門 #6 - lib-arts’s diary