以前書いた、ニュース記事のテキストのサンプルデータを読み込む記事ですが、読み込んだ後何かした記事をまだ書いてなかったのでちょっとやってみようと思います。
参考:20ニュースグループのテキストデータを読み込んでみる
といっても、凝ったモデルを作るのではなく、対した前処理もせずに単純なBoWと ロジスティック回帰だけでどの程度の性能が出るものなか見てみます。
まずはライブラリのインポートと、データの読み込みです。
20種類全部使うとデータが多く時間がかかるのと、miscのカテゴリーも入りどうやってもある程度以上の性能は出ないので、
適当に5カテゴリー選択しました。(categories
という引数で指定しています。)
また、テキストにヘッダーやフッターが含まれると、ほとんどそこだけの情報で分類できてしまうので、
removeを使って除去しています。
この辺りの仕様はドキュメント参照。
sklearn.datasets.fetch_20newsgroups
# 必要ライブラリのインポート
from sklearn.datasets import fetch_20newsgroups
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from hyperopt import hp, fmin, tpe, Trials, space_eval
# データの読み込み
remove = ("headers", "footers", "quotes")
categorys = [
"rec.sport.hockey",
"soc.religion.christian",
"sci.med",
"comp.windows.x",
"talk.politics.mideast",
]
twenty_train = fetch_20newsgroups(
subset="train",
remove=remove,
categories=categorys
)
twenty_test = fetch_20newsgroups(
subset="test",
remove=remove,
categories=categorys
)
X_train = twenty_train.data
y_train = twenty_train.target
X_test = twenty_test.data
y_test = twenty_test.target
# パラーメータチューニングのため訓練データを2つに分ける
X_train_A, X_train_B, y_train_A, y_train_B = train_test_split(
X_train,
y_train,
test_size=0.2,
stratify=y_train
)
見ての通り、X_train, X_test をさらに二つのグループに分けています。
ハイパーパラメーターをチューニングする際に、ここで作ったAグループで学習して、Bグループで評価するようにし、
デフォルトで用意されているテストデータは最後の評価時まで触らずに取っておきます。
本当はクロスバリデーションなどを真面目にやった方がいいのですが、今回はこの方針で。
続いて、ハイパーパラメーターの決定に進みます。
グリッドサーチでもいいのですが、せっかくなのでhyperoptを使ってみます。
spaceは最初はもっと広い範囲で探索していましたが、何度かトライして絞り込みました。
(lr__penalty の l1なども試していたのですが、 Cが大きい時に非常に時間がかかったのと、
今回のデータではl2の方が性能が良かったので探索範囲からも除外。)
# ハイパーパラメーターの探索の準備
space = {
'cv__min_df': 1 + hp.randint('min_df', 20),
'cv__max_df': hp.uniform('max_df', 0.5, 0.9),
'lr__penalty': hp.choice('penalty', ['l2']),
'lr__C': hp.loguniform('C', -5, 5),
}
def create_model(args):
clf = Pipeline(
[
("cv", CountVectorizer(min_df=args["cv__min_df"], max_df=args["cv__max_df"])),
("lr", LogisticRegression(C=args["lr__C"], penalty=args["lr__penalty"]))
]
)
return clf
def objective(args):
clf = create_model(args)
clf.fit(X_train_A, y_train_A)
return - accuracy_score(y_train_B, clf.predict(X_train_B))
trials = Trials()
best = fmin(
fn=objective,
space=space,
algo=tpe.suggest,
max_evals=100,
trials=trials,
)
# 結果の表示
print(best)
print(space_eval(space, best))
# 以下出力
'''
100%|██████████| 100/100 [01:42<00:00, 1.02it/s, best loss: -0.9016949152542373]
{'C': 0.11378962521452059, 'max_df': 0.5208423432316657, 'min_df': 0, 'penalty': 0}
{'cv__max_df': 0.5208423432316657, 'cv__min_df': 1, 'lr__C': 0.11378962521452059, 'lr__penalty': 'l2'}
'''
これでパラーメーターが決まりましたので、モデルを作って評価します。
ここでの評価には最初にとっておいたテストデータを使います。
# 最良のパラメーターでモデルを構築し学習
clf = create_model(space_eval(space, best))
# 訓練データはすべて使う
clf.fit(X_train, y_train)
# 評価
print("正解率:", accuracy_score(y_test, clf.predict(X_test)))
print(classification_report(y_test, clf.predict(X_test), target_names=twenty_test.target_names))
# 以下出力
'''
正解率: 0.8615071283095723
precision recall f1-score support
comp.windows.x 0.90 0.90 0.90 395
rec.sport.hockey 0.79 0.95 0.86 399
sci.med 0.87 0.80 0.84 396
soc.religion.christian 0.88 0.82 0.85 398
talk.politics.mideast 0.87 0.83 0.85 376
avg / total 0.86 0.86 0.86 1964
'''
正解率約86%。
ほとんど何も工夫していないロジスティック回帰にしてはそこそこの結果だと思います。