静かなる名辞

pythonとプログラミングのこと


【python】sklearnのfetch_20newsgroupsで文書分類を試す(4)

 前回は性能を追い求めると次元がでかくなりすぎて・・・というところで終わっていた。今回はもうちょっと頑張って次元を減らしてみる。

 目次

ストップワードの除去

 とりあえずstop_wordsを指定していなかったので、指定してみる。

 stop_words="english"とすると、ストップワードを除去してくれる。

 結果だけ言うと、min_df=0.005のとき、

  • stop_words指定なし:3949次元
  • stop_words指定あり:3705次元

 だった。焼石に水。

PCA(主成分分析)とLDA(線形判別分析)

 PCAとLDAをかけ、次元削減をする。leakage怖いのでPipelineを使う(厳密なことを言い出すと、単語文書行列を作る段からPipelineに入れるべきなのだろうか? きついのでパスさせて頂くが)。

 PCAは主にLDAの計算負荷削減と、変数の相関を除去することを意図してかける。1000次元まで落としてみたが、これでも累積寄与率は90%弱になる。まあ、正規化も何もしてないから、重要な情報を落としている可能性は否定できないのだが。

 LDAは次元削減に使う。有効性についてはこの前試してみたので、この記事を読んで欲しい。
【python】LDA(線形判別分析)で次元削減 - 静かなる名辞
 20newsgroupsは20クラスのデータなので、19次元に落とすことになる。相当早くなるだろうが、どこまで性能を維持できるかはデータの線形性にかかっている。

分類

 ランダムフォレストを使った。n_estimators=1000とし、他のパラメタはデフォルト。

ソースコード

 実験に使ったソースコードを以下に示す。

# coding: UTF-8

import numpy as np

from sklearn.datasets import fetch_20newsgroups
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.feature_extraction.text import CountVectorizer as CV
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import precision_recall_fscore_support as prf
from sklearn.pipeline import Pipeline

def main():
    news20 = fetch_20newsgroups()    
    
    cv = CV(min_df=0.005, max_df=0.5, stop_words="english")
    matrix = cv.fit_transform(news20.data).toarray()

    pca = PCA(n_components=1000, svd_solver="randomized")
    lda = LDA()
    rfc = RFC(n_estimators=1000, n_jobs=-1)

    clf = Pipeline([("pca", pca), ("lda", lda), ("rfc", rfc)])

    trues = []
    preds = []
    for train_index, test_index in StratifiedKFold().split(matrix, news20.target):
        clf.fit(matrix[train_index], news20.target[train_index])
        trues.append(news20.target[test_index])
        preds.append(clf.predict(matrix[test_index]))
    scores = prf(np.hstack(trues), np.hstack(preds), average="macro")[:3]
    print("p:{0:.6f} r:{1:.6f} f1:{2:.6f}".format(scores[0],
                                                  scores[1],
                                                  scores[2]))

if __name__ == "__main__":
    main()

結果とまとめ

p:0.764012 r:0.760731 f1:0.761510

 前回の0.8を超えるスコアには届かなかったが、とりあえずそれなりに軽くはなった。もうちょっと真面目に追い込めばという話はあるが、追求しない。次回はもうちょっと違うことをやってみたい。

次回

 このシリーズずっと放置していましたが、気が向いたので書きました。
www.haya-programming.com