Open In Colab

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

本記事では、日本語 BERT モデルをファインチューニングして感情分析する方法を解説します。

BERT の詳細な解説は、この記事のスコープ外とします。

この記事は、part01 です。

part02 では、まとまったデータセットを使って実際に学習と評価を行っています。

補足

Hugging Face Transformers とは

Hugging Face 社によって開発されている、言語モデルを容易に扱うための OSS ライブラリ です。

また同社によって、学習済みモデルデータセットを公開するリポジトリが提供されています。

BERT

BERT とは、Bidirectional Encoder Representations from Transformersの略称で、Googleが開発した自然言語処理のモデルです。

ラベルのないテキストから文章の中の単語やフレーズの意味や関係性を事前学習し、出力層を1つ追加してファインチューニングを行うことで、幅広いタスクに対して性能の良いモデルを作成できます。

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

!pip install -q transformers fugashi[unidic-lite]
import pandas as pd
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
from torch.optim import AdamW

日本語 BERT の簡単なチュートリアル

最初に、huggingface transformers を使った日本語 BERT pre-trained model の使い方や fine tuning の方法を、見ていきます。

今回試す事前学習済みモデルとして、東北大学のグループによって公開されているものを利用します。

参考

Pre-trained Model を使って推論

BERT モデルは、mask された token ([MASK]) を予測するように学習されています。

したがって、pre-trained model を使って、文章中の穴埋め (文章中の欠損箇所の予測) を 行えます。

以下の2種類のモデルを使って推論を試して、結果を比較してみましょう。

pipelinefill-mask タスクを指定することで、簡単に試すことができます。

model_name = "cl-tohoku/bert-large-japanese"

unmasker = pipeline('fill-mask', model=model_name)
unmasker("今日の昼食は[MASK]でした。")
Some weights of the model checkpoint at cl-tohoku/bert-large-japanese were not used when initializing BertForMaskedLM: ['cls.seq_relationship.bias', 'cls.seq_relationship.weight']
- This IS expected if you are initializing BertForMaskedLM 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 BertForMaskedLM from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
[{'score': 0.04221104457974434,
  'token': 32474,
  'token_str': 'サラダ',
  'sequence': '今日 の 昼食 は サラダ でし た 。'},
 {'score': 0.036806173622608185,
  'token': 18526,
  'token_str': 'カレー',
  'sequence': '今日 の 昼食 は カレー でし た 。'},
 {'score': 0.0313434936106205,
  'token': 31893,
  'token_str': 'ご飯',
  'sequence': '今日 の 昼食 は ご飯 でし た 。'},
 {'score': 0.021632177755236626,
  'token': 17540,
  'token_str': '元気',
  'sequence': '今日 の 昼食 は 元気 でし た 。'},
 {'score': 0.020115602761507034,
  'token': 23869,
  'token_str': 'うどん',
  'sequence': '今日 の 昼食 は うどん でし た 。'}]
model_name = "bert-base-multilingual-uncased"

unmasker = pipeline('fill-mask', model=model_name)
unmasker("今日の昼食は[MASK]でした。")
Some weights of the model checkpoint at bert-base-multilingual-uncased were not used when initializing BertForMaskedLM: ['cls.seq_relationship.bias', 'cls.seq_relationship.weight']
- This IS expected if you are initializing BertForMaskedLM 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 BertForMaskedLM from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
[{'score': 0.17987696826457977,
  'token': 7753,
  'token_str': '見',
  'sequence': '今 日 の 昼 食 は 見 てした 。'},
 {'score': 0.06706605106592178,
  'token': 4080,
  'token_str': '捨',
  'sequence': '今 日 の 昼 食 は 捨 てした 。'},
 {'score': 0.06436670571565628,
  'token': 2073,
  'token_str': '全',
  'sequence': '今 日 の 昼 食 は 全 てした 。'},
 {'score': 0.060412339866161346,
  'token': 5216,
  'token_str': '満',
  'sequence': '今 日 の 昼 食 は 満 てした 。'},
 {'score': 0.02542056515812874,
  'token': 4518,
  'token_str': '果',
  'sequence': '今 日 の 昼 食 は 果 てした 。'}]

課題

unmasker に別の文章を渡して推論させてみましょう。

Fine Tuning 前の感情分析

感情分析タスクは “sentiment-analysis”pipeline のタスクに指定することで行えます。

pipeline で利用可能なタスク一覧は、Hugging Face のドキュメント で確認できます。

ただし、モデルが指定されたタスクに対応している必要があります。

テキスト分類のための Fine Tuning の手順

本来であれば、もっと大規模な学習データセットを用意するべきですが、ここでは説明の簡素化のために、単純なサンプルを手作業で作成して手順を確認します。

以下のように、3 種類のラベル (positive: 2, neutral: 1, negative: 0) のデータを用意します。

# 確認用のデータセット
df = pd.DataFrame(
    [
        {"text": "私はこの映画をみることができて、とても嬉しい。", "label": "POSITIVE"},
        {"text": "今日の晩御飯は何だろう。", "label": "NEUTRAL"},
        {"text": "猫に足を噛まれて痛い。", "label": "NEGATIVE"}
     ]
)
df
text label
0 私はこの映画をみることができて、とても嬉しい。 POSITIVE
1 今日の晩御飯は何だろう。 NEUTRAL
2 猫に足を噛まれて痛い。 NEGATIVE
train_docs = df["text"].tolist()
train_labels = df["label"].tolist()

学習

同時にダウンロードされるトークナイザーを利用して、データセットの text の encoding を行います。

参考

model_name = "cl-tohoku/bert-large-japanese"

id2label = {0: "NEGATIVE", 1: "NEUTRAL", 2: "POSITIVE"}
label2id = {"NEGATIVE": 0, "NEUTRAL": 1, "POSITIVE": 2}

model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=3, id2label=id2label, label2id=label2id)
tokenizer = AutoTokenizer.from_pretrained(model_name)
Some weights of the model checkpoint at cl-tohoku/bert-large-japanese were not used when initializing BertForSequenceClassification: ['cls.predictions.decoder.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.decoder.weight', 'cls.seq_relationship.bias', 'cls.predictions.transform.dense.weight', 'cls.predictions.bias', 'cls.predictions.transform.LayerNorm.bias', 'cls.seq_relationship.weight', 'cls.predictions.transform.dense.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.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
encodings = tokenizer(train_docs, return_tensors='pt', padding=True, truncation=True, max_length=128)
input_ids = encodings['input_ids']
attention_mask = encodings['attention_mask']
# Fine-tuning in native PyTorch

# the AdamW() optimizer which implements gradient bias correction as well as weight decay.
optimizer = AdamW(model.parameters(), lr=1e-5)

labels = [label2id[label] for label in train_labels]
labels = torch.tensor(labels).unsqueeze(0)
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()

Fine Tune したモデルで推論

学習データ量が少ないので性能に期待できませんが、推論の手順を確認します。

以下のように、推論できます。

sentiment_analyzer = pipeline("sentiment-analysis", model=model, tokenizer=tokenizer)
sentiment_analyzer("これは、テストのための文章です")
[{'label': 'POSITIVE', 'score': 0.41579246520996094}]
# 学習データに対する推論
_ = list(map(lambda x: print(f"{x}: {sentiment_analyzer(x)}"), train_docs))
私はこの映画をみることができて、とても嬉しい。: [{'label': 'POSITIVE', 'score': 0.5623015761375427}]
今日の晩御飯は何だろう。: [{'label': 'NEUTRAL', 'score': 0.4257284700870514}]
猫に足を噛まれて痛い。: [{'label': 'NEGATIVE', 'score': 0.586097776889801}]

課題

sentiment_analyzer に任意の文章を渡して推論してみましょう。

まとめ

簡単な文章とラベルを用意して fine tuning する方法を記載しました。

次の記事 では、より大きなデータセットを使って、より時間のかかる学習を試してみたいと思います。