ハカセノオト

moon indicating dark mode
sun indicating light mode

huggingface transformers を使って日本語 BERT モデルをファインチューニングして感情分析 (with google colab) part02

March 13, 2021

こちらの記事の内容は古くなっています。最新版は以下のページをご覧ください

https://jupyterbook.hnishi.com/language-models/fine_tune_jp_bert_part02.html

Open In Colab

part01 に引き続いて、今回は、まとまったデータセットを使い、transformers の Trainer class を使いファインチューニングする方法を記載します。

また、学習後の評価方法に関しても記載します。

なお、今回はまとまった量 (4000 件程度) のデータを使って学習を行いますので、GPU の使用を推奨します (著者は google colab を使用して動作確認しています)。


この記事は、part02 です。

すべての記事の目次は以下をご参照ください。

https://github.com/hnishi/handson-language-models/blob/main/README.md

参考

必要なライブラリのインストール

!pip install -q transformers
 |████████████████████████████████| 1.9MB 7.7MB/s
 |████████████████████████████████| 3.2MB 36.1MB/s
 |████████████████████████████████| 890kB 51.3MB/s
[?25h Building wheel for sacremoses (setup.py) ... [?25l[?25hdone
import pandas as pd
import torch
from transformers import BertTokenizer, BertForSequenceClassification, pipeline
from transformers import AdamW

学習データの準備

株式会社リクルート 様が公開している Japanese Realistic Textual Entailment Corpus (含意関係データセット) を使わせていただきます。

下記、制限があるため注意します。

リクルートは本データセットを非営利的な公共利用のために公開しています。分析・研究・その成果を発表するために必要な範囲を超えて利用すること(営利目的利用)は固く禁じます。

References

  1. 林部祐太. 知識の整理のための根拠付き自然文間含意関係コーパスの構築. 言語処理学会第26回年次大会論文集,pp.820-823. 2020. (NLP 2020) [PDF][Poster]
  2. Yuta Hayashibe. Japanese Realistic Textual Entailment Corpus. Proceedings of The 12th Language Resources and Evaluation Conference, pp.6829-6836. 2020. (LREC 2020) [PDF][bib]
!curl -L -O https://raw.githubusercontent.com/megagonlabs/jrte-corpus/master/data/pn.tsv
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 473k 100 473k 0 0 2151k 0 --:--:-- --:--:-- --:--:-- 2151k
# 各カラムの意味は https://github.com/megagonlabs/jrte-corpus#datapntsv を参照
df = pd.read_csv("pn.tsv", sep='\t', header=None,
names=["id", "label", "text", "judges", "usage"])
# ラベルを 1, 0, -1 --> 0, 1, 2 へ変換
# Label: 2 (Positive), 1 (Neutral), 0 (Negative)
df["label"] = df["label"] + 1

ラベルごとのデータ件数の確認

全体的に、positive なラベルが多いようです。

したがって、¥学習の結果 positive に極端に判定されやすいモデルとなってしまう可能性があるため注意が必要です。

評価指標に accuracy ではなく、F1 score などの不均衡データに関しても正しく評価できるものを選択する必要があります。

df.groupby(["usage", "label"]).size().plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7feb63f13310>

png

カスタムデータセットに変換

後述する huggingface transformers の Trainer クラスで学習を行うために、カスタムデータセットとして準備します。

usage が train と dev のサンプルを学習用、test のサンプルをテスト用として分割します。

df_train = df[df["usage"] == ("train" or "val")]
train_docs = df_train["text"].tolist()
train_labels = df_train["label"].tolist()
len(train_docs)
3888
df_test = df[df["usage"] == "test"]
test_docs = df_test["text"].tolist()
test_labels = df_test["label"].tolist()
len(test_docs)
553

学習

# GPU が利用できる場合は GPU を利用する
device = "cuda:0" if torch.cuda.is_available() else "cpu"
device
'cuda:0'
model_name = "cl-tohoku/bert-large-japanese"
model = BertForSequenceClassification.from_pretrained(model_name, num_labels=3)
model = model.to(device)
tokenizer = BertTokenizer.from_pretrained(model_name)
HBox(children=(FloatProgress(value=0.0, description='Downloading', max=336.0, style=ProgressStyle(description_…
HBox(children=(FloatProgress(value=0.0, description='Downloading', max=1354281605.0, style=ProgressStyle(descr…
Some weights of the model checkpoint at cl-tohoku/bert-large-japanese were not used when initializing BertForSequenceClassification: ['cls.predictions.bias', 'cls.predictions.transform.dense.weight', 'cls.predictions.transform.dense.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.LayerNorm.bias', 'cls.predictions.decoder.weight', 'cls.predictions.decoder.bias', 'cls.seq_relationship.weight', 'cls.seq_relationship.bias']
- This IS expected if you are initializing BertForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at cl-tohoku/bert-large-japanese and are newly initialized: ['classifier.weight', 'classifier.bias']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
HBox(children=(FloatProgress(value=0.0, description='Downloading', max=236001.0, style=ProgressStyle(descripti…
HBox(children=(FloatProgress(value=0.0, description='Downloading', max=174.0, style=ProgressStyle(description_…
train_encodings = tokenizer(train_docs, return_tensors='pt', padding=True, truncation=True, max_length=128).to(device)
test_encodings = tokenizer(test_docs, return_tensors='pt', padding=True, truncation=True, max_length=128).to(device)
import torch
class JpSentiDataset(torch.utils.data.Dataset):
def __init__(self, encodings, labels):
self.encodings = encodings
self.labels = labels
def __getitem__(self, idx):
item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}
item['labels'] = torch.tensor(self.labels[idx])
return item
def __len__(self):
return len(self.labels)
train_dataset = JpSentiDataset(train_encodings, train_labels)
test_dataset = JpSentiDataset(test_encodings, test_labels)
# We can set `requires_grad` to `False` for all the base model parameters in order to fine-tune only the task-specific parameters.
# Ref: https://huggingface.co/transformers/training.html#freezing-the-encoder
# freeze させないほうが F1 スコアが高くなったためコメントアウトしています
# for param in model.base_model.parameters():
# param.requires_grad = False
# For more detail, see https://korenv20.medium.com/do-we-need-to-freeze-embeddings-when-fine-tuning-our-lm-c8bccf4ffeba
# To calculate additional metrics in addition to the loss, you can also define your own compute_metrics function and pass it to the trainer.
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='weighted', zero_division=0)
acc = accuracy_score(labels, preds)
return {
'accuracy': acc,
'f1': f1,
'precision': precision,
'recall': recall
}

1 epoch で試したときは、学習に GPU を使い 2 分程度かかりました。

from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir='./results', # output directory
num_train_epochs=1, # total number of training epochs
per_device_train_batch_size=16, # batch size per device during training
per_device_eval_batch_size=64, # batch size for evaluation
warmup_steps=500, # number of warmup steps for learning rate scheduler
weight_decay=0.01, # strength of weight decay
save_total_limit=1, # limit the total amount of checkpoints. Deletes the older checkpoints.
dataloader_pin_memory=False, # Whether you want to pin memory in data loaders or not. Will default to True
# evaluation_strategy="epoch", # Evaluation is done at the end of each epoch.
evaluation_strategy="steps",
logging_steps=50,
logging_dir='./logs'
)
trainer = Trainer(
model=model, # the instantiated 🤗 Transformers model to be trained
args=training_args, # training arguments, defined above
train_dataset=train_dataset, # training dataset
eval_dataset=test_dataset, # evaluation dataset
compute_metrics=compute_metrics # The function that will be used to compute metrics at evaluation
)
trainer.train()
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:9: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
if __name__ == '__main__':
TrainOutput(global_step=243, training_loss=0.7476195975095646, metrics={'train_runtime': 322.8845, 'train_samples_per_second': 0.753, 'total_flos': 936757749027744, 'epoch': 1.0})
# evaluation のみ実行
trainer.evaluate(eval_dataset=test_dataset)
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:9: UserWarning: To copy construct from a tensor, it is recommended to use sourceTensor.clone().detach() or sourceTensor.clone().detach().requires_grad_(True), rather than torch.tensor(sourceTensor).
if __name__ == '__main__':
{'epoch': 1.0,
'eval_accuracy': 0.755877034358047,
'eval_f1': 0.7268075608453904,
'eval_loss': 0.6674010157585144,
'eval_precision': 0.762758084435675,
'eval_recall': 0.755877034358047,
'eval_runtime': 10.7683,
'eval_samples_per_second': 51.355}

評価

上記のテストデータの評価結果を見ると、accuracy が 0.75 F1 score が 0.72 で結構良いのではないでしょうか。

さらに epoch 数を増やすことで性能の向上が見込めそうです。

ちなみに、以下を入れて parameter を freeze させて fine tuning を行った場合は、accuracy が 0.60、F1 値が 0.46 となり、かなり低くなってしまいました。

for param in model.base_model.parameters():
param.requires_grad = False

学習の評価指標はデフォルトでは、 runs/**CURRENT_DATETIME_HOSTNAME** に出力されています。

tensorboard での可視化が可能です。

Ref: https://huggingface.co/transformers/main_classes/trainer.html#trainingarguments

%load_ext tensorboard
%tensorboard --logdir logs
<IPython.core.display.Javascript object>

fine tune したモデルで推論

from transformers import pipeline
sentiment_analyzer = pipeline("sentiment-analysis", model=model.to("cpu"), tokenizer=model_name)
sentiment_analyzer("私はこの映画をみることができて、とても嬉しい。")
[{'label': 'LABEL_2', 'score': 0.9585415720939636}]
sentiment_analyzer("猫に足を噛まれて痛い。")
[{'label': 'LABEL_0', 'score': 0.7770922780036926}]

いい感じに予測できていそうです。

学習済みモデルを保存

以下では、colab から google drive に保存する場合の例を示します。

from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
save_directory = "/content/drive/MyDrive/Colab Notebooks/trained_models/20210313_bert_sentiment"
tokenizer.save_pretrained(save_directory)
model.save_pretrained(save_directory)

まとめ

日本語 BERT large の pre-trained model を fine tuning する方法をご紹介しました。


hnishi のブログ


技術系の記事など。
綺麗に書こうとすると続かない気がするので、割と雑に、備忘録的に書いていく方針。
内容に誤りがあった場合などのご連絡は、Twitter の DM などで頂けると幸いです。
※ 記事中の Amazon 商品ページへのリンクは Amazon アソシエイト・プログラムによるリンクを使用しています。