日本語版BERTをファインチューニングした話

# TL; DR
アルバイト中に研修タスクとして[Hugging Face NLP Course 3章](https://huggingface.co/learn/nlp-course/chapter3/3?fw=pt)をモデルを日本語に置き換えて取り組んだので、自分の理解度確認と初心者向けの参考記事としてアウトプットします。

実行したコードとできる限りの説明を付け加えてあります。
モデル:[cl-tohoku/bert-base-japanese-v3](https://huggingface.co/cl-tohoku/bert-base-japanese-v3)
データセット: [paws-x/ja](https://huggingface.co/datasets/paws-x)
学習結果: 3エポック学習して、accuracy:0.51, F1 score: 0.55

# 実行環境

Google Colab Pro V100ランタイム

# 使ったモデル
[cl-tohoku/bert-base-japanese-v3](https://huggingface.co/cl-tohoku/bert-base-japanese-v3)
トークナイザに`fugashi`と`unidic-lite`を使っているとのことですので、これもインストールする必要があります。

# 使ったデータセット
[paws-x/ja](https://huggingface.co/datasets/paws-x)
日本語を含む7つの言語を収録しています。2つの文章のペアが同じ意味の文の言い換え(パラフレーズ)であるかどうかを判断するタスクです。

# 実行したコード

## インストール
```sh
! pip install datasets evaluate transformers[sentencepiece,torch]
! pip install accelerate -U #マルチGPUの場合速くなる
! pip install fugashi unidic-lite
```

## モデルとトークナイザをロード

Hugging Faceの公式で、`AutoTokenizer`クラスや`AutoModel**`クラスを使うのが推奨されています。モデルごとのトークナイザやモデルを使うこともできますが、`Auto**`クラスを使うことでチェックポイントに合わせたクラスを自動でロードしてくれます。

```python
from transformers import AutoTokenizer,  AutoModelForSequenceClassification

checkpoint = "cl-tohoku/bert-base-japanese-v3"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
```

## データセットをロードする
```python
from datasets import load_dataset

raw_datasets = load_dataset('paws-x', 'ja') # 日本語部分のみロードする
raw_datasets
```
訓練用49491件、検証/テスト用にそれぞれ2000件ずつの文章ペアがあることがわかります。
```
DatasetDict({
    train: Dataset({
        features: ['id', 'sentence1', 'sentence2', 'label'],
        num_rows: 49401
    })
    test: Dataset({
        features: ['id', 'sentence1', 'sentence2', 'label'],
        num_rows: 2000
    })
    validation: Dataset({
        features: ['id', 'sentence1', 'sentence2', 'label'],
        num_rows: 2000
    })
})
```

データセットを全部学習させると20分くらい時間がかかるので、10%だけサンプリングして使いました。

```python
# データセットを10%だけサンプリングする
seed = 42
train_size = len(raw_datasets['train'])
val_size = len(raw_datasets['validation'])
test_size = len(raw_datasets['test'])
raw_datasets['train'] = raw_datasets['train'].shuffle(seed=seed).select(range(int(train_size * 0.1)))
raw_datasets['validation'] = raw_datasets['validation'].shuffle(seed=seed).select(range(int(val_size * 0.1)))
raw_datasets['test'] = raw_datasets['test'].shuffle(seed=seed).select(range(int(test_size * 0.1)))
```
## 前処理

トークナイズ処理は`datasets.map()`メソッドを使って行います。

```python
def tokenize_function(example):
  return tokenizer(example['sentence1'], example['sentence2'], truncation=True)

tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) 
tokenized_datasets
```

最後に、各入力はこの段階だと長さが違うため、パディングをして長さを揃える必要があります。また、モデルに入力するためにPyTorchのテンソルに変換する必要があります。

この2つをやるためのクラスが`DataCollatorWithPadding`です。ここでは定義のみしておき、後ほど`Trainer`クラスのインスタンス化時に引数として渡すことで学習時に自動で実行されます。

```python
from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
```

## 評価指標の設定

今回はaccuracyとF1スコアを指標として用います。実装は簡単のためにHugging Faceの`Evaluate`クラスを用います。

`compute_metrics()`関数の中で指標を計算し、この関数を`Trainer`クラスに引数として渡すことで訓練中に自動で評価を行ってくれます。

```python
def compute_metrics(eval_preds):
    metric = evaluate.load("glue", "mrpc") # GLUEベンチマークのMRPCデータセットに紐付いた評価指標。F1とAccuracyを同時にかえしてくれる
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)
    return metric.compute(predictions=predictions, references=labels)
```

## ハイパーパラメータの定義
Hugging Faceでは、ハイパーパラメータは全て`TrainingArguments`というクラスが持っています。定義は簡単です。

```python
training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
```
最初の引数は結果のモデルを保存するディレクトリ、`evaluation_strategy`は学習時に評価を行うタイミングを示しています。この設定だと毎エポックの終わりに評価を行います。その他の値は全てデフォルトで大丈夫です。学習は3エポック行われます。

## 学習の実行
ここまで作ったものをすべて`Trainer`クラスに渡します。
```python
trainer = Trainer(
    model,
    training_args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
)
trainer.train() #学習を実行
```

# 結果

![スクリーンショット 2023-10-15 23.39.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3522495/453a7e82-2e9b-fd2f-5eed-a961ae9c7732.png)

全然ダメなように見えます