さえめろ の めも🐰

さえめろの備忘録です。twitter : @sae_mero_

【sklearn】tf-idfを用いたテキスト分類

現在機械学習のお勉強をさせて頂いている企業さんから、sklernを使ったテキスト処理の課題を受けました。
ちょうど良かったので、tf-idfのおさらいをざっくりとしようと思います。

なお、今回のモデルはsklearnの公式チュートリアルWorking With Text Data — scikit-learn 0.18.2 documentation:原文英語
)を大いに参考にしました。


今回使用するデータセットは20newsgroupsです。

20newsgroupsは、多数のテキストデータに対し、その文書のテーマ(ラベル)を持つデータセットです。
今回は未知の文書データに対して、指定された5つのラベルの中からふさわしいテーマを選ぶ多クラス分類を行いました。(本来は20のラベルがあります)



✔︎やったこと

今回は重み付けにtf-idfを使用しました。

tf-idfは、

tf = Term Frequency(単語の出現頻度)
idf = Inverse Document Frequency(逆文書頻)

の2つの指標を利用する、単語の重み付けの一種です。


tf   

{\displaystyle \mathrm {tf(i,j)} ={\frac {n_{i,j}}{\sum _{k}n_{k,j}}}}


{\displaystyle n_{i,j}}   …単語{\displaystyle i}の文書{\displaystyle n}における出現回数
{\displaystyle {\sum _{k}n_{k,j}}}   …文書{\displaystyle n}のすべての単語の出現回数の和


idf   

{\displaystyle \mathrm {idf(i)} =\log {\frac {|D|}{|\{d:d\ni t_{i}\}|}}}


{\displaystyle |D|}   …総文書数
{\displaystyle |\{d:d\ni t_{i}\}|}は単語{\displaystyle i}   …出現する文書の数


tf-idf   

{\displaystyle \mathrm {tfidf(i,j)} =\mathrm {tf(i,j)} \cdot \mathrm {idf(i)} }


これをまとめると、

tfは「ある単語の文書内での出現頻度」
idfは「ある単語がいくつの文書で使われているか」

となります。

つまりたくさん出てくる単語は重要(tf)かつ、たくさんの文書に共通する言葉は重要ではない(idf)という指標になっています。
めちゃめちゃ出る単語はtfは高いがidfは低く、めちゃめちゃ出ない単語はtfは低いがidfは高いということです。

ちなみにidfでlogを使用するのは、単語ごとの重みの差を縮めるスケーリングをするためです。



tf-idfはsklearnに用意されているので、20newsgroupsの読み込みから重み付けまでを記述します。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
import numpy as np

categories = ['alt.atheism', 'soc.religion.christian','comp.graphics', 'sci.med', 'rec.sport.baseball']

all_data= fetch_20newsgroups(shuffle=True, categories=categories, random_state=42)


# tfidfによる重み付け
count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(all_data.data)

tfidf_transformer = TfidfTransformer()
X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)

X_train_tfidf.toarray()

all_X = X_train_tfidf
all_y = all_data.target


これで重み付けができました。


これをsklearnのSVM分類器で分類すると、

from sklearn.cross_validation import train_test_split
from sklearn.metrics import f1_score
from sklearn import svm
from sklearn.svm import SVC

train_X, test_X, train_y, test_y = train_test_split(all_X, all_y,
                                                    test_size=0.2,
                                                    random_state=42)


clf2 = svm.SVC(kernel='rbf', C=5, gamma=0.2)
clf2.fit(train_X,train_y)
pred_y2 = clf2.predict(test_X)
print(f1_score(test_y, pred_y2, average='macro'))


こんな感じ。

出力は0.970124386976と、約97%という高い精度になりました。

from sklearn.metrics import confusion_matrix
print(confusion_matrix(test_y, pred_y2))

で見てみると、

[[ 80   0   2   1   2]
 [  0 114   0   0   0]
 [  0   3 123   0   0]
 [  0   3   2 117   0]
 [  0   4   0   0 120]]


という出力に。
どうやら、「クリスチャンの社会」という話題に分類するための特徴づけが弱そうです。


✔︎わかったこと

一概に全てに適応出来るとは言えませんが、tf-idfは(sklearnを使用した場合)簡単かつ強力な重み付けをしてくれることがわかりました。
今回の場合もSVMと組み合わせるだけで、グリッドサーチをしない状態でもとても高い精度になりました。

ちなみに他の分類器はrandom forest、決定木、ナイーブベイズ、k近傍法(いずれもsklearnのモジュールを使用)を試行しましたが、SVMが一番高い精度を出しました。