はじめに
ずっと放置していたシリーズですが、その後新たに得られた知見が出てきたので、更新しておこうと思います。
得られた知見
いろいろ勉強した結果、以下のような考えに至りました。
- そもそもデータ数が多いので、高級な分類器であればあるほど速度的に厳しい
- 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
パラメータチューニング時のスコアは改善しますが、実際の予測では少し下がる結果に。まあ、これくらいが限界に近いのでしょう(この特徴量の作り方と分類器の組み合わせでは)。パラメータチューニングのときと本予測のときとでけっこうスコアが違うのでなんとなく過学習してるような気もしますが、理由がよくわからん。
若干後味が悪いですが、数字は悪くないのでこれでよしとします。
まとめ
これで次はない・・・かも。