静かなる名辞

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

2019/03/22:TechAcademyがteratailの質問・回答を盗用していた件
2019/03/26:TechAcademy盗用事件 公式発表と深まる疑念


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

はじめに

 ずっと放置していたシリーズですが、その後新たに得られた知見が出てきたので、更新しておこうと思います。

得られた知見

 いろいろ勉強した結果、以下のような考えに至りました。

  • そもそもデータ数が多いので、高級な分類器であればあるほど速度的に厳しい
  • MultinomialNB(多項分布ナイーブベイズ)の性能は意外と良いのでそれでいい
  • その場合、tfidfとか使うべき。また、パラメタチューニングを真面目にやるべき
  • 疎行列型をうまく使うと大規模データでも高速処理が可能


 ということで、この方針でやります。

実験

 まず以下のコードで軽く回します。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report

def main():
    news20_train = fetch_20newsgroups(subset="train")
    news20_test = fetch_20newsgroups(subset="test")
    y_train = news20_train.target
    y_test = news20_test.target
    
    vectorizer = TfidfVectorizer(
        stop_words="english", max_df=0.03, min_df=0.0005)
    nb = MultinomialNB(alpha=1e-1)
    pl = Pipeline([("v", vectorizer), ("nb", nb)])
    
    params = {"v__max_df":[0.3, 0.1, 0.03],
              "v__min_df":[0.01, 0.003, 0.001, 0.0003],
              "nb__alpha":[1e-0, 1e-1, 1e-2, 1e-3]}
    clf = GridSearchCV(pl, params, cv=4, scoring="f1_macro", 
                       n_jobs=-1)
    clf.fit(news20_train.data, y_train)

    print("result of gridsearch")
    print("best score", clf.best_score_)
    print("best parameter", clf.best_params_)
    y_pred = clf.predict(news20_test.data)
    print(classification_report(
        y_test, y_pred, target_names=news20_test.target_names, digits=4))

if __name__ == "__main__":
    main()

 見ての通り、ざっくりグリッドサーチしています。これでそれなりに良くなるはず。

 特徴選択のモデルもPipelineで同時にチューニングしますので、これでだいたい

  • 取るべき次元数を決めるパラメタ
  • NBのalpha

 についてはわかるはずです。

 結果

result of gridsearch
best score 0.903351987953267
best parameter {'nb__alpha': 0.01, 'v__max_df': 0.3, 'v__min_df': 0.0003}
                          precision    recall  f1-score   support

             alt.atheism     0.8366    0.8025    0.8192       319
           comp.graphics     0.6568    0.7378    0.6949       389
 comp.os.ms-windows.misc     0.7079    0.6396    0.6720       394
comp.sys.ibm.pc.hardware     0.6522    0.7270    0.6876       392
   comp.sys.mac.hardware     0.8281    0.8260    0.8270       385
          comp.windows.x     0.8388    0.7772    0.8068       395
            misc.forsale     0.7614    0.8103    0.7851       390
               rec.autos     0.8943    0.8763    0.8852       396
         rec.motorcycles     0.9244    0.9523    0.9381       398
      rec.sport.baseball     0.9491    0.9395    0.9443       397
        rec.sport.hockey     0.9559    0.9774    0.9665       399
               sci.crypt     0.9035    0.9217    0.9125       396
         sci.electronics     0.8066    0.7430    0.7735       393
                 sci.med     0.8886    0.8258    0.8560       396
               sci.space     0.8734    0.8934    0.8833       394
  soc.religion.christian     0.8562    0.9422    0.8971       398
      talk.politics.guns     0.7788    0.9093    0.8390       364
   talk.politics.mideast     0.9642    0.9309    0.9472       376
      talk.politics.misc     0.7734    0.6387    0.6996       310
      talk.religion.misc     0.7418    0.6295    0.6810       251

               micro avg     0.8309    0.8309    0.8309      7532
               macro avg     0.8296    0.8250    0.8258      7532
            weighted avg     0.8322    0.8309    0.8301      7532

 けっこういい感じです。すでに過去のシリーズの最高スコアです。

 ここから更に詰めていくため、RandomizedSearchCVを使います。

 参考:
【python】sklearnのRandomizedSearchCVを使ってみる - 静かなる名辞

 分布に関しては多少手抜きをして、max_dfとmin_dfは区間を適当に区切った一様分布、alphaのみ指数分布としています。妥当なものは他に考えられるかもしれませんが、これでいきます。

from scipy import stats

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.model_selection import RandomizedSearchCV
from sklearn.metrics import classification_report

def main():
    news20_train = fetch_20newsgroups(subset="train")
    news20_test = fetch_20newsgroups(subset="test")
    y_train = news20_train.target
    y_test = news20_test.target
    
    vectorizer = TfidfVectorizer(
        stop_words="english", max_df=0.03, min_df=0.0005)
    nb = MultinomialNB(alpha=1e-1)
    pl = Pipeline([("v", vectorizer), ("nb", nb)])

    max_df_dist = stats.uniform(0.1, 0.5)
    min_df_dist = stats.uniform(0.00007, 0.001)
    alpha_dist = stats.expon(scale=1e-2)
    
    params = {"v__max_df":max_df_dist,
              "v__min_df":min_df_dist,
              "nb__alpha":alpha_dist}
    clf = RandomizedSearchCV(pl, params, cv=4, scoring="f1_macro", 
                             n_iter=100, n_jobs=-1)
    clf.fit(news20_train.data, y_train)

    print("result of gridsearch")
    print("best score", clf.best_score_)
    print("best parameter", clf.best_params_)
    y_pred = clf.predict(news20_test.data)
    print(classification_report(
        y_test, y_pred, target_names=news20_test.target_names, digits=4))

if __name__ == "__main__":
    main()

 結果

result of gridsearch
best score 0.9078423646680397
best parameter {'nb__alpha': 0.008635226675407684, 'v__max_df': 0.14464593949316493, 'v__min_df': 0.00010360792392347633}
                          precision    recall  f1-score   support

             alt.atheism     0.8355    0.7962    0.8154       319
           comp.graphics     0.6659    0.7326    0.6977       389
 comp.os.ms-windows.misc     0.6983    0.6345    0.6649       394
comp.sys.ibm.pc.hardware     0.6386    0.7168    0.6755       392
   comp.sys.mac.hardware     0.8165    0.8208    0.8187       385
          comp.windows.x     0.8250    0.7519    0.7868       395
            misc.forsale     0.7628    0.8000    0.7810       390
               rec.autos     0.9143    0.8889    0.9014       396
         rec.motorcycles     0.9270    0.9573    0.9419       398
      rec.sport.baseball     0.9467    0.9395    0.9431       397
        rec.sport.hockey     0.9509    0.9699    0.9603       399
               sci.crypt     0.9084    0.9268    0.9175       396
         sci.electronics     0.7941    0.7557    0.7744       393
                 sci.med     0.8849    0.8157    0.8489       396
               sci.space     0.8707    0.9061    0.8881       394
  soc.religion.christian     0.8514    0.9497    0.8979       398
      talk.politics.guns     0.7778    0.9038    0.8361       364
   talk.politics.mideast     0.9669    0.9335    0.9499       376
      talk.politics.misc     0.7812    0.6452    0.7067       310
      talk.religion.misc     0.7524    0.6295    0.6855       251

               micro avg     0.8295    0.8295    0.8295      7532
               macro avg     0.8285    0.8237    0.8246      7532
            weighted avg     0.8307    0.8295    0.8287      7532

 パラメータチューニング時のスコアは改善しますが、実際の予測では少し下がる結果に。まあ、これくらいが限界に近いのでしょう(この特徴量の作り方と分類器の組み合わせでは)。パラメータチューニングのときと本予測のときとでけっこうスコアが違うのでなんとなく過学習してるような気もしますが、理由がよくわからん。

 若干後味が悪いですが、数字は悪くないのでこれでよしとします。

まとめ

 これで次はない・・・かも。