huggingface transformers を使って日本語 BERT モデルをファインチューニングして感情分析 (with google colab) part02
March 13, 2021
こちらの記事の内容は古くなっています。最新版は以下のページをご覧ください
https://jupyterbook.hnishi.com/language-models/fine_tune_jp_bert_part02.html
part01 に引き続いて、今回は、まとまったデータセットを使い、transformers の Trainer class を使いファインチューニングする方法を記載します。
また、学習後の評価方法に関しても記載します。
なお、今回はまとまった量 (4000 件程度) のデータを使って学習を行いますので、GPU の使用を推奨します (著者は google colab を使用して動作確認しています)。
この記事は、part02 です。
すべての記事の目次は以下をご参照ください。
https://github.com/hnishi/handson-language-models/blob/main/README.md
参考
必要なライブラリのインストール
!pip install -q transformers
[K |████████████████████████████████| 1.9MB 7.7MB/s[K |████████████████████████████████| 3.2MB 36.1MB/s[K |████████████████████████████████| 890kB 51.3MB/s[?25h Building wheel for sacremoses (setup.py) ... [?25l[?25hdone
import pandas as pdimport torchfrom transformers import BertTokenizer, BertForSequenceClassification, pipelinefrom transformers import AdamW
学習データの準備
株式会社リクルート 様が公開している Japanese Realistic Textual Entailment Corpus (含意関係データセット) を使わせていただきます。
下記、制限があるため注意します。
リクルートは本データセットを非営利的な公共利用のために公開しています。分析・研究・その成果を発表するために必要な範囲を超えて利用すること(営利目的利用)は固く禁じます。
References
- 林部祐太. 知識の整理のための根拠付き自然文間含意関係コーパスの構築. 言語処理学会第 26 回年次大会論文集,pp.820-823. 2020. (NLP 2020) [PDF][Poster]
- 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 CurrentDload Upload Total Spent Left Speed100 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>
カスタムデータセットに変換
後述する 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 torchclass JpSentiDataset(torch.utils.data.Dataset):def __init__(self, encodings, labels):self.encodings = encodingsself.labels = labelsdef __getitem__(self, idx):item = {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}item['labels'] = torch.tensor(self.labels[idx])return itemdef __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_supportdef compute_metrics(pred):labels = pred.label_idspreds = 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, TrainingArgumentstraining_args = TrainingArguments(output_dir='./results', # output directorynum_train_epochs=1, # total number of training epochsper_device_train_batch_size=16, # batch size per device during trainingper_device_eval_batch_size=64, # batch size for evaluationwarmup_steps=500, # number of warmup steps for learning rate schedulerweight_decay=0.01, # strength of weight decaysave_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 trainedargs=training_args, # training arguments, defined abovetrain_dataset=train_dataset, # training dataseteval_dataset=test_dataset, # evaluation datasetcompute_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 pipelinesentiment_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 drivedrive.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 する方法をご紹介しました。