Transformersで日本語BERTのクラス分類モデルのファインチューニングを行いました。備忘録として投稿します。
Transformers公式ドキュメントはこちら
使用したライブラリバージョンは、
・python=3.10.5
・torch==1.12.1+cu116
・transformers==4.21.1
使用する学習済みモデルは、
cl-tohoku/bert-base-japanese-v2
データ準備
以下からlivedoorニュースコーパスのデータをダウンロードします。
https://www.rondhuit.com/download.html
「ldcc-20140209.tar.gz」のファイルです。
コマンドプロンプトで解凍します。
$ tar xvf ldcc-20140209.tar.gz
テキストファイルからデータを読み込んで、DataFrameに成形します。
import os
from glob import glob
import pandas as pd
import linecache
from sklearn.metrics import classification_report
# カテゴリを配列で取得
categories = [name for name in os.listdir("text") if os.path.isdir("text/" + name)]
print(categories)
datasets = pd.DataFrame(columns=["text", "category"])
for cat in categories:
path = "text/" + cat + "/*.txt"
files = glob(path)
for text_name in files:
body = ''
for i in range(3,1000):
text=linecache.getline(text_name, i)
if text=='':
break
body+=text
datasets.loc[len(datasets)]=[body,cat]
# データフレームシャッフル、先頭の200データだけ抽出
datasets = datasets.sample(frac=1,random_state=0).reset_index(drop=True)
datasets = datasets.iloc[:200]
print(datasets)
['dokujo-tsushin', 'it-life-hack', 'kaden-channel', 'livedoor-homme', 'movie-enter', 'peachy', 'smax', 'sports-watch', 'topic-news']
ラベルを数値に変換します。
# 正解ラベル(カテゴリー)をデータセットから取得
categories = list(set(datasets['category']))
print(categories)
# カテゴリーのID辞書を作成
id2cat = dict(zip(list(range(len(categories))), categories))
cat2id = dict(zip(categories, list(range(len(categories)))))
print(id2cat)
print(cat2id)
# DataFrameにカテゴリーID列を追加
datasets['label'] = datasets['category'].map(cat2id)
# データセットを本文とカテゴリーID列だけにする
datasets = datasets[['text', 'label']]
display(datasets.head())
['topic-news', 'smax', 'livedoor-homme', 'peachy', 'movie-enter', 'kaden-channel', 'sports-watch', 'it-life-hack', 'dokujo-tsushin']
{0: 'topic-news', 1: 'smax', 2: 'livedoor-homme', 3: 'peachy', 4: 'movie-enter', 5: 'kaden-channel', 6: 'sports-watch', 7: 'it-life-hack', 8: 'dokujo-tsushin'}
{'topic-news': 0, 'smax': 1, 'livedoor-homme': 2, 'peachy': 3, 'movie-enter': 4, 'kaden-channel': 5, 'sports-watch': 6, 'it-life-hack': 7, 'dokujo-tsushin': 8}
Hugging Faceのdatasetsライブラリを使用して、transfomersで扱えるデータ構造に変換します。train/testの分割もここで行います。
from datasets import Dataset
dataset_packed = Dataset.from_pandas(datasets)
dataset_split = dataset_packed.train_test_split(test_size=0.2, seed=0)
print(dataset_split)
DatasetDict({
train: Dataset({
features: ['text', 'label'],
num_rows: 160
})
test: Dataset({
features: ['text', 'label'],
num_rows: 40
})
})
tokenizer、モデル準備
tokenizerを用意し、データセットに適用します。
from transformers import AutoTokenizer
tokenizer= AutoTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-v2')
def preprocess_function(examples):
MAX_LENGTH = 512
return tokenizer(examples["text"], max_length=MAX_LENGTH, truncation=True)
tokenized_dataset = dataset_split.map(preprocess_function, batched=True)
DataCollatorの定義、モデルの定義をします。
from transformers import DataCollatorWithPadding
from transformers import AutoModelForSequenceClassification, TrainingArguments, Trainer
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
model = AutoModelForSequenceClassification.from_pretrained("cl-tohoku/bert-base-japanese-v2", num_labels=9)
評価用関数の定義。
from sklearn.metrics import accuracy_score, f1_score
def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
f1 = f1_score(labels, preds, average='weighted')
acc = accuracy_score(labels, preds)
return {'accuracy':acc, 'f1':f1}
学習(ファインチューニング)
TrainingArguments、Trainerを定義して学習を実行します。今回は、GPU環境が用意できなかったので、CPUで実行するため、no_cuda=Trueとしています(デフォルトではFalse)。
training_args = TrainingArguments(
output_dir="./results",
evaluation_strategy='epoch',
logging_strategy='epoch',
save_strategy='epoch',
save_total_limit=1,
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=5,
weight_decay=0.01,
no_cuda=True, # GPUを使用する場合はFalse
)
trainer = Trainer(
model=model,
args=training_args,
compute_metrics=compute_metrics,
train_dataset=tokenized_dataset['train'],
eval_dataset=tokenized_dataset['test'],
tokenizer=tokenizer,
data_collator=data_collator,
)
trainer.train()
Epoch Training Loss Validation Loss Accuracy F1
1 2.166400 1.942927 0.375000 0.293243
2 1.800700 1.806098 0.600000 0.576190
3 1.570400 1.625521 0.650000 0.595616
4 1.391600 1.512957 0.650000 0.602818
5 1.306500 1.481055 0.675000 0.644277
CPUで実行する都合で学習データを少なくしているので、学習結果はあまり良くなさそうです。ただ、Lossはどちらも減少しているので、学習は進んでそうです。
学習させたモデルを保存する。
trainer.save_state()
trainer.save_model()
学習させたモデルで、testデータの予測を出力する。
pred_result = trainer.predict(tokenized_dataset['test'], ignore_keys=['loss', 'last_hidden_state', 'hidden_states', 'attentions'])
pred_label= pred_result.predictions.argmax(axis=1).tolist()
print(pred_label)
[4, 0, 8, 0, 7, 4, 4, 1, 8, 3, 1, 7, 4, 8, 7, 5, 5, 8, 1, 0, 0, 5, 7, 8, 8, 2, 0, 0, 2, 0, 5, 7, 5, 5, 8, 0, 2, 0, 8, 0]
sklearnのclassification_reportで予測結果のレポートを表示する。
from sklearn.metrics import classification_report
print(classification_report(tokenized_dataset['test']['label'], pred_label, target_names=categories))
precision recall f1-score support
dokujo-tsushin 0.50 1.00 0.67 5
it-life-hack 1.00 1.00 1.00 3
kaden-channel 1.00 1.00 1.00 3
livedoor-homme 1.00 0.33 0.50 3
movie-enter 0.50 0.25 0.33 8
peachy 1.00 0.86 0.92 7
smax 0.00 0.00 0.00 2
sports-watch 0.40 1.00 0.57 2
topic-news 0.62 0.71 0.67 7
accuracy 0.68 40
macro avg 0.67 0.68 0.63 40
weighted avg 0.69 0.68 0.64 40
正解率が0.68ということで性能はそこまで良くないですが、ランダム予測よりは十分に高いので一応学習はできていることがうかがえます。
まとめ
Transformersで日本語BERTのクラス分類モデルのファインチューニングを行いました。CPU環境だったので、ほぼ動作確認程度しかできてませんが、参考になれば幸いです。
参考
機械学習エンジニアのためのTransformers ―最先端の自然言語処理ライブラリによるモデル開発
Transformers Text classification
huggingfaceのTrainerクラスを使えばFineTuningの学習コードがスッキリ書けてめちゃくちゃ便利です
コメント