simpletransformersを使ったテキスト分類

自然言語処理の世界では2019年位から事前学習による学習モデルの使用が活発になっています。これはAttentionベースであるGoogleのBERTが当時SoTAを獲得したことに始まります。

こちらの記事に書いてあるように現在Attentionベースのモデルは自然言語処理で大きな威力を発揮しており、従来のRNNベースのモデルと比べてモデル精度を大きく上げることができるようになっています。

現在では「自然言語処理 x Deep Learning といえば Attention だよね」と言っても過言ではないくらいの存在となっています。
(引用元: https://qiita.com/halhorn/items/c91497522be27bde17ce)

しかしこのAttentionベースのニューラルネットワークを1から構築するには手間がかかります。これを手軽に行うことができるのがsimpletransformersというPyTorchのラッパーライブラリです。

simpletransformersを使うと非常に簡単にBERTなどによる最新の学習済みモデルを使用することができます。ここではテキスト分類を例としてsimpletransformersの基本的な使い方を書いていきます。

ここからの学習にはGPUを使用するのでGPU環境が用意できない方はGoogle Colabを使うと良いと思います。

データのセットアップ

データセットにはKaggleにアップロードされていたBBC articles fulltext and categoryを使いました。

このデータセットにはBBCニュースの本文一部とそのカテゴリが含まれています。カテゴリはtech, business, sport, entertainment, politicsの5種類で合計2126行あります。

なおnotebookを見る限りだとaccuracy 95%くらいがベースラインみたいです。

import pandas as pd
import re
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from simpletransformers.classification import ClassificationModel
from sklearn.metrics import accuracy_score

PATH = 'bbc-text.csv'

data = pd.read_csv(PATH)
data.text=data.text.apply(lambda x: re.sub(r'[^A-Za-z]+',' ',x))
data.text=data.text.apply(lambda x: x.lower())
data.text=data.text.apply(lambda x:x.strip())

Labelencoder=LabelEncoder()
data.category=pd.to_numeric(Labelencoder.fit_transform(data.category))
data.columns = ['label', 'text']
data=data[data.columns[::-1]]

train_df,eval_df=train_test_split(data,test_size=0.3,shuffle=0.3,random_state=0)
train_df, eval_df
print(data)

data

テキスト分類で使用するDataFrameはデフォルトだとラベル名がtext, labelの順に強制されます。変更もできますが特に理由がない場合はそのフォーマットに合わせるので良いと思います。

学習、予測

ここではroberta-baseという訓練済みモデルを使用して学習を行います。

model = ClassificationModel('roberta', 'roberta-base', num_labels=5, use_cuda=True)

model.train_model(train_df)

result, model_outputs, wrong_predictions = model.eval_model(eval_df)

y_pred, raw_outputs = model.predict(eval_df.text.tolist())
print(accuracy_score(y_pred, eval_df.label))

これを実行した結果0.9715568862275449と出ました。まあまあですね。

なお、ここで使っているroberta以外にClassificationModelは以下のモデルに対応しています。詳しくはドキュメントを参照してください。

  • ALBER
  • BER
  • CamemBERT
  • RoBERTa
  • DistilBERT
  • ELECTRA
  • FlauBERT
  • Longformer
  • MobileBERT
  • XLM
  • XLM-RoBERTa
  • XLNet

また、実際の訓練済みモデルはHuggingfaceから探せます。

別のモデルを使うにはmodel = ClassificationModel('xlmroberta', 'xlm-roberta-base', num_labels=5, use_cuda=True)のようにします。

訓練済みモデルをいろいろ変えて試すと精度は変わってくるので試すと面白いです。

パラメーター設定

精度を上げるのに便利そうなパラメーターを書きます。

params = {
    "max_seq_length": 128,
    "train_batch_size": 32,
    "eval_batch_size": 32,
    "num_train_epochs": 10,
    "use_early_stopping": True,
    "learning_rate": 1e-5,
    "n_gpu": 4,
}
weight = 1/ pd.DataFrame((train_df.label).tolist()).reset_index().groupby(0).count().values
weight_sum = weight.sum()
weight /= weight_sum
model = ClassificationModel('roberta', 'roberta-base', args=params, num_labels=5, use_cuda=True, weight=weight)

他のパラメーターは公式ドキュメントを参照してください。

train_batch_size

訓練時のバッチサイズを指定できます。

max_seq_length

シークエンスの最大長です。シークエンスは文章の長さのようなもので、この値が大きいと文章の表現力が高くなってより高い精度が期待できます。その一方値を大きくしすぎるとオーバーフィッティングや学習時間の増大などの問題が生じる可能性があります。

この値を増やすとGPUメモリの使用量も増えるので最大値はスペックと相談になります。

weight

weightは不均衡データに対処するためのパラメーターです。ここで使っているデータセットはさほど偏ったデータではないのですがあるラベルのデータだけ多い場合に有効です。

weightの中身はラベル数と同じ長さの配列で、ラベル数の割合の逆数を入れるとよいでしょう。

なおweightはnumpyのarrayだとエラーになるのでPythonのリストを使いましょう。

エラーのメモ

AttributeError: ‘int’ object has no attribute ‘startswith’

DataFrameのlabelとtextの順番が逆です。data=data[data.columns[::-1]]を噛ませましょう。

参考