Help us understand the problem. What is going on with this article?

勾配ブースティングを使って文書分類モデルを作成してみた

文書ベクトルの各要素を説明変数に、文書のカテゴリを目的変数として、勾配ブースティングで分類器が作れるか試しました。

以下の前提で実施しました。

  • データは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

終わりに

文書ベクトルの要素を説明変数にしても勾配ブースティングでそこそこの精度がでる分類器ができそうなことがわかった。
いろんなところでチューニング要素があると思うので、精度向上はまだまだできそう

おわり

m__k
数学専攻(確率解析)→SE→ITコンサル 自然言語処理をメインに勉強しています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした