Transformers を使って、BERT のファインチューニングをする

# Transformers を使って、BERT のファインチューニングをする

https://huggingface.co/docs/transformers

## はじめに

はじめまして。私は大学院生です。

この記事では、Transformers を使って、BERT のファインチューニングをします。（主に、研究室の後輩向けに書いていますので、極力簡素化しています。ご了承ください。）

この記事が、「Transformers を使ったファインチューニングの方法の参考になった」と思っていただけるように書かさせていただきます。

---

より簡素化できる案などがあれば[コメント](#comments)をしていただけると幸いです。

---

https://qiita.com/Natu_ja/items/4ae2b5d178389336d078

https://qiita.com/Natu_ja/items/b1fc52e20b0bdd8d5b14

類似記事もありますので、是非こちらもご覧になってください。

## 事前準備

### 用語解説

https://qiita.com/Natu_ja/items/3086f5d1577c44c7edae

基本的な用語はこちらで解説しています。

### 環境

https://colab.research.google.com/

### データセット

https://huggingface.co/datasets/stanfordnlp/sst2

### 事前学習済みモデル

https://huggingface.co/google-bert/bert-base-uncased

## 実験コード

### ライブラリのインポート

```python
import re
from collections import Counter
import matplotlib.pyplot as plt
from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
from datasets import load_dataset
```

### デバイスの設定

[Google Colab](https://colab.research.google.com/) では、「ランタイム」 -> 「ランタイムのタイプを変更」 -> 「T4 GPU」 に設定してください。

```python
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
```

上記の設定ができていると、`print(device)` の出力は、`device(type='cuda')` になるはずです。

### データセットのロード

今回使用するデータセットは、SST-2 [^SST-2] を使用します。

```python
train_dataset = load_dataset("stanfordnlp/sst2", split="train")
val_dataset = load_dataset("stanfordnlp/sst2", split="validation")
```

学習データからラベル数を取得します。

```python
num_classes = train_df['label'].nunique()
```

### データセットの分析

分析がしやすいように、DataFrame に変換します。

```python
train_df = train_dataset.to_pandas()
```

`head()` メソッドを使うことで、先頭 5 行を見ることができます。

```python
train_df.head()
```

`tail()` メソッドを使うことで、末尾 5 行を見ることができます。

```python
train_df.tail()
```

`describe()` メソッドを使うことで、記述統計量を見ることができます。

```python
train_df.describe()
```

学習データのラベルの割合を以下で確認することができます。確認する意図としては、ラベルに偏りがある場合、ダウンサンプリング（あるいはアップサンプリング）が必要になるからです。

```python
train_df['label'].value_counts(normalize=True)
```

単語の出現頻度を可視化します。ただし、上位 50 件のみを表示します。

```python
all_words = []
for sentence in train_df["sentence"]:
    words = re.findall(r'\b\w+\b', sentence.lower())
    all_words.extend(words)

word_counts = Counter(all_words)

top_50_words = word_counts.most_common(50)
words, counts = zip(*top_50_words)

plt.figure(figsize=(12, 8))
plt.bar(words, counts)
plt.xticks(rotation=45)
plt.xlabel("Words")
plt.ylabel("Frequency")
plt.show()
```

![word frequency histogram](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3836663/90fc7d78-793a-d9d4-89bd-49a42e360297.png)

テキストの長さを可視化します。

```python
train_df["text-length"] = train_df["sentence"].apply(lambda x: len(x.split()))

train_df["text-length"].hist(bins=50)
plt.xlabel("text length")
plt.ylabel("count")
plt.show()
```

![text length histogram](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3836663/f1bbf433-329b-d854-4431-bc48e69f6012.png)

### 事前学習済みトークナイザーとモデルのロード

`AutoModelForSequenceClassification.from_pretrained()` を分類タスクで使うときは、`num_labels` を指定するべきです。`num_labels` を指定しないと、デフォルトの出力サイズが使用され、想定しているクラス数と合わなくなることがあります。`num_labels` を指定することで、適切な損失関数がモデル内部で選ばれます。これにより、タスクに最適化された損失計算が可能になります。`num_labels` に合わせてモデルの出力層が再構成されるため、事前学習済みモデルに対して新しいタスク用のファインチューニングを効率的に行えます。

```python
tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased")
model = AutoModelForSequenceClassification.from_pretrained("google-bert/bert-base-uncased", num_labels=num_classes).to(device)
```

### 事前学習済みトークナイザーとモデルの確認

BERT [^BERT] などの多くのモデルは、事前学習の際に、「Special Token」という特殊なトークンを使うことが一般的です。

```python
tokenizer
```

モデルの構造は以下で確認することができます。

```python
model
```

モデルの設定は、以下で確認することができます。

```python
model.config
```

モデルの学習可能なパタメータ数の確認は以下のコードで見ることができます。`if p.requires_grad` の部分は、学習可能かどうかを判定するために入れています。

```python
sum(p.numel() for p in model.parameters() if p.requires_grad)
```

### 英文のトークン化

`tokenizer` を使って、データセットをトークン化します。`padding="longest"` は、各バッチ内の最長のシーケンスに合わせてパディングされ、余分なパディングが省かれます。

```python
def tokenize_function(examples):
    return tokenizer(examples["sentence"], padding="longest", truncation=True)

train_dataset = train_dataset.map(tokenize_function)
val_dataset = val_dataset.map(tokenize_function)
```

トークン化された文字を元の数字に戻してみます。

```python
id = 0
print(tokenizer.decode(train_dataset[id]["input_ids"]))
```

### 学習時の設定

より細かい学習の設定は、[Transformers](https://huggingface.co/docs/transformers) の [TrainingArguments](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.TrainingArguments) をご覧になってください。

最適化関数は、AdamW [^AdamW] がデフォルトで設定されています。

- `output_dir`：チェックポイントなどの保存先のパス
- `eval_strategy`：評価戦略
- `per_device_train_batch_size`：学習時のバッチサイズ
- `per_device_eval_batch_size`：テスト時のバッチサイズ
- `learning_rate`：初期の学習率
- `logging_strategy`：ロギング戦略
- `save_strategy`：保存戦略

[TensorBoard](https://www.tensorflow.org/tensorboard) や [WandB](https://wandb.ai/) などの設定ができていれば、損失などの記録を確認することができます。

```python
args = TrainingArguments(
    output_dir="./outputs",
    eval_strategy="epoch",
    per_device_train_batch_size=32,
    per_device_eval_batch_size=32,
    learning_rate=1e-5,
    logging_strategy="epoch",
    save_strategy="epoch",
)
print(args)
```

### 評価指標の定義

正解率、F 値、適合率、再現率を求めます。

```python
def compute_metrics(p):
    
    preds = p.predictions.argmax(-1)
    
    accuracy = accuracy_score(p.label_ids, preds)
    f1 = f1_score(p.label_ids, preds, average="weighted")
    precision = precision_score(p.label_ids, preds, average="weighted")
    recall = recall_score(p.label_ids, preds, average="weighted")
    
    return {
        "accuracy": accuracy,
        "f1": f1,
        "precision": precision,
        "recall": recall
    }
```

### Trainer の定義と実行

[Transformers](https://huggingface.co/docs/transformers) の [Trainer](https://huggingface.co/docs/transformers/main_classes/trainer#trainer) を用いて、学習を行います。
 
```python
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=train_dataset,
    eval_dataset=val_dataset,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics
)
trainer.train()
```

## おわりに

この記事では、Transformers を使って、BERT のファインチューニングをしました。

この記事が、「Transformers を使ったファインチューニングの方法の参考になった」と思っていただけたら幸いです。

## 参考文献

[^AdamW]: Ilya Loshchilov and Frank Hutter. Decoupled Weight Decay Regularization. In *7th International Conference on Learning Representations*, 2019.

[^BERT]: Jacob Devlin, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding. In *Proceedings of the 2019 Conference of the North American Chapter of the Association for Computational Linguistics*, 2019.

[^SST-2]: Richard Socher, Alex Perelygin, Jean Wu, Jason Chuang, Christopher D. Manning, Andrew Y. Ng, and Christopher Potts. Recursive Deep Models for Semantic Compositionality Over a Sentiment Treebank. In *Proceedings of the 2013 Conference on Empirical Methods in Natural Language Processing*, 2013.