文書ベクトルの各要素を説明変数に、文書のカテゴリを目的変数として、勾配ブースティングで分類器が作れるか試しました。
以下の前提で実施しました。
- データはlivedoorニュースコーパス
- livedoorニュースコーパスをインプットとしてWord2Vecで単語分散表現を作る(200次元)
- 形態素解析エンジンはMeCabを使用(辞書やstopwords、品詞のフィルタリングなどは特に指定なし)
- 文書ベクトルは単語の和の平均ベクトル
今回は勾配ブースティングの手法としてXGBoostとLightGBMを使ってみました。
双方ともインストールで躓くケースがあるので、KaggleのPython環境が使えるdocker上で実行しています。
両者のインストールに困ったときは以下をご参考にどうぞ
データ準備①
import os
from glob import glob
import pandas as pd
from tqdm import tqdm_notebook as tqdm
# カテゴリ(ディレクトリ名)をリスト化
categories = [name for name in os.listdir('text') if os.path.isdir("text/" +name)]
print(categories)
# カテゴリをID化
category2id = {}
for i, cat in enumerate(categories):
category2id[cat] = i
# DataFrame作成
datasets = pd.DataFrame(columns=["document", "category"])
for category in tqdm(categories):
path = "text/" + category + "/*.txt"
files = glob(path)
for text_name in files:
with open(text_name, 'r', encoding='utf-8') as f:
document = f.read()
row = pd.Series([document, category], index=datasets.columns)
datasets = datasets.append(row, ignore_index=True)
print("doc num", len(datasets))
# シャッフルして表示
# datasets = datasets.sample(frac=1).reset_index(drop=True)
# datasets.head()
形態素解析の定義
KaggleのPython環境ではMeCabが使えないので、pipでインストールしています。
!pip install mecab-python3
import MeCab
import re
tagger = MeCab.Tagger("-Owakati")
def make_wakati(sentence):
sentence = tagger.parse(sentence)
sentence = re.sub(r'[0-90-9a-zA-Za-zA-Z]+', " ", sentence)
sentence = re.sub(r'[\._-―─!@#$%^&\-‐|\\*\“()_■×+α※÷⇒—●★☆〇◎◆▼◇△□(:〜~+==)/*&^%$#@!~`){}[]…\[\]\"\'\”\’:;<>?<>〔〕〈〉?、。・,\./『』【】「」→←○《》≪≫\n\u3000]+', "", sentence)
wakati = sentence.split(" ")
wakati = list(filter(("").__ne__, wakati))
return wakati
# test
# print(make_wakati(datasets["document"][0]))
Word2Vecで単語分散表現作成
from gensim.models import Word2Vec
import numpy as np
import logging
# word2vec parameters
num_features = 200
min_word_count = 5
num_workers = 40
context = 10
downsampling = 1e-3
model_name = "livedoor_corpus_feature200.model"
# コーパス読み込み
corpus = []
for doc in tqdm(datasets["document"]):
corpus.append(make_wakati(doc))
# word2vecモデルの作成&モデルの保存
print("cleating word2vec model ...")
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
model = Word2Vec(corpus, workers=num_workers, hs = 0, sg = 1, negative = 10, iter = 25,
size=num_features, min_count = min_word_count, window = context, sample = downsampling, seed=1)
model.save(model_name)
print("Done.")
# word2vecモデルの読み込み
# model = Word2Vec.load(model_name)
# test
# print(model.wv.most_similar("男性"))
文書ベクトル作成用関数の定義
単語の和の平均で文書ベクトルを作成しています。
def wordvec2docvec(sentence):
# 文章ベクトルの初期値(0ベクトルを初期値とする)
docvecs = np.zeros(num_features, dtype="float32")
# 文章に現れる単語のうち、モデルに存在しない単語をカウントする
denomenator = len(sentence)
# 文章内の各単語ベクトルを足し合わせる
for word in sentence:
try:
temp = model[word]
except:
denomenator -= 1
continue
docvecs += temp
# 文章に現れる単語のうち、モデルに存在した単語の数で割る
if denomenator > 0:
docvecs = docvecs / denomenator
return docvecs
データ準備②
-
data_X
が横がベクトルの要素(200個)、縦が文書のDataFrame(説明変数) -
data_Y
が正解ラベル(目的変数)
これらを7:3に分けて、7割を学習データとしています。
from sklearn.model_selection import train_test_split
print(len(datasets["document"]))
X, Y = [], []
for doc, category in tqdm(zip(datasets["document"], datasets["category"])):
wakati = make_wakati(doc)
docvec = wordvec2docvec(wakati)
X.append(list(docvec))
Y.append(category2id[category])
data_X = pd.DataFrame(X, columns=["X" + str(i + 1) for i in range(num_features)])
data_Y = pd.DataFrame(Y, columns=["category_id"])
train_x, test_x, train_y, test_y = train_test_split(data_X, data_Y, train_size= 0.7)
XGBoostで分類器を作成&予測
- XGBoostのパラメータとかは何もいじらず(いじり方知らないだけ...)、プレーンな状態で実行しています。
- XGBoostのFスコア平均は0.89
import xgboost as xgb
from sklearn.metrics import classification_report
print("Fitting XGboost model ...")
xgb_model = xgb.XGBClassifier()
xgb_model.fit(train_x, train_y)
print("Done.")
# 予測
pred = xgb_model.predict(test_x)
print(classification_report(pred, test_y["category_id"], target_names=categories))
# precision recall f1-score support
#
# movie-enter 0.93 0.90 0.92 282
# it-life-hack 0.88 0.88 0.88 253
# kaden-channel 0.87 0.92 0.90 259
# topic-news 0.88 0.91 0.90 226
#livedoor-homme 0.76 0.76 0.76 146
# peachy 0.82 0.85 0.83 239
# sports-watch 0.95 0.94 0.95 285
#dokujo-tsushin 0.88 0.83 0.86 277
# smax 0.93 0.93 0.93 246
#
# avg / total 0.89 0.89 0.89 2213
LightGBMで分類器の作成&予測
- LightGBMもプレーンな状態で実行
- LightGBMのFスコア平均は0.90
- 大体のカテゴリでXGBoostよりかはギリ精度良さげ
import lightgbm as lgbm
print("Fitting LightGBM model ...")
lgbm_model = lgbm.LGBMClassifier()
lgbm_model.fit(train_x, train_y)
print("Done.")
# 予測
pred = lgbm_model.predict(test_x)
print(classification_report(pred, test_y["category_id"], target_names=categories))
# precision recall f1-score support
#
# movie-enter 0.95 0.92 0.94 279
# it-life-hack 0.88 0.90 0.89 249
# kaden-channel 0.90 0.91 0.90 271
# topic-news 0.88 0.91 0.89 224
#livedoor-homme 0.77 0.80 0.78 142
# peachy 0.85 0.88 0.86 240
# sports-watch 0.96 0.94 0.95 287
#dokujo-tsushin 0.89 0.84 0.86 275
# smax 0.93 0.93 0.93 246
#
# avg / total 0.90 0.90 0.90 2213
念の為Random Forestでもやってみる
from sklearn.ensemble import RandomForestClassifier
rfc = RandomForestClassifier()
rfc.fit(train_x, train_y)
pred = rfc.predict(test_x)
print(classification_report(pred, test_y["category_id"], target_names=categories))
# precision recall f1-score support
#
# movie-enter 0.96 0.85 0.90 306
# it-life-hack 0.77 0.79 0.78 247
# kaden-channel 0.84 0.81 0.82 284
# topic-news 0.84 0.80 0.82 242
#livedoor-homme 0.58 0.64 0.60 132
# peachy 0.71 0.77 0.74 226
# sports-watch 0.90 0.92 0.91 274
#dokujo-tsushin 0.77 0.79 0.78 256
# smax 0.91 0.90 0.90 246
#
# avg / total 0.82 0.82 0.82 2213
そこそこボロ負け...
TF-IDFによる文書ベクトルでもやってみる
- NMFとかで次元圧縮したほうがいいような気がしますが、一旦なにも考えずそのままLightGBMで学習させてみる
- 文書ベクトルの次元数が79673次元とかなり高次元なため、学習に結構時間かかります。
- Fスコア平均が0.94となり、Word2Vecによる文書ベクトルよりも良い結果になりました。
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = datasets["document"]
tfidf_vectorizer = TfidfVectorizer(analyzer=make_wakati)
tfidfs = tfidf_vectorizer.fit_transform(corpus)
print(tfidfs.shape)
# (7376, 79673)
tfidf_data_X = pd.DataFrame(tfidfs.toarray(),columns=["X"+str(i) for i in range(tfidfs.shape[1])])
train_x, test_x, train_y, test_y = train_test_split(data_X, data_Y, train_size=0.7)
lgbm_model = lgbm.LGBMClassifier()
lgbm_model.fit(train_x, train_y)
pred = lgbm_model.predict(test_x)
print(classification_report(pred, test_y["category_id"], target_names=categories))
# precision recall f1-score support
#
# movie-enter 0.98 0.94 0.96 279
# it-life-hack 0.96 0.95 0.95 256
# kaden-channel 0.96 0.98 0.97 270
# topic-news 0.90 0.98 0.94 228
#livedoor-homme 0.85 0.86 0.85 130
# peachy 0.90 0.90 0.90 279
# sports-watch 0.97 0.90 0.93 279
#dokujo-tsushin 0.93 0.95 0.94 254
# smax 0.98 0.99 0.99 238
#
# micro avg 0.94 0.94 0.94 2213
# macro avg 0.94 0.94 0.94 2213
# weighted avg 0.94 0.94 0.94 2213
終わりに
文書ベクトルの要素を説明変数にしても勾配ブースティングでそこそこの精度がでる分類器ができそうなことがわかった。
いろんなところでチューニング要素があると思うので、精度向上はまだまだできそう
おわり