!date
!python --version
Tue Jun 25 12:10:13 AM UTC 2024
Python 3.10.12

33. AutoModelForSequenceClassificationを用いたファインチューニング例#

このノートブックではJapanese Realistic Textual Entailment Corpusのpn.tsvをデータセットとし、BERT(”tohoku-nlp/bert-base-japanese-v3”)を用いてファインチューニングする例を示している。全体の流れは以下の通り。

  • 環境構築: fugashi, accelerateをインストール

  • モジュール読み込み

  • データ前処理: LLMにおける分類タスクでは教師ラベルを「0から始まる整数」として割り振る必要があるため、ラベルを設定し直した。

  • モデルの用意: tokenizerの動作確認を含む。

  • LLM用にデータを整形: tokenizerの出力と教師ラベルを合わせてDataset型に変換。

  • 学習

  • テストデータに対する詳細結果

  • 学習データに対する詳細結果

33.1. 注意#

33.1.1. GPUを指定する#

LLMを用いた学習を行っている都合上、デフォルト(CPU)実行すると極めて時間がかかる。おそらく数時間要するだろう。今回はGPUを指定して実行することを強くお勧めする。

GPUを指定するには以下の手順を取る。

  • 「ランタイム」から「ランタイムのタイプの変更」を選ぶ。

  • 「ハードウェア アクセラレータ」からGPUを選ぶ。

    • T4 GPU を選ぶと良い。この中では低スペックだが十分早い。T4 GPUなら、10エポックの学習が約14分で終了する。

33.1.2. リソース使用制限#

Google Colabは無料で利用できるが、利用度合いに応じてリソースが制限されることがある。特にGPUは使えなくなることが多いため、不必要に何度も実行することは避けよう。

詳細: 最適な Colab のプランを選択する

33.1.3. 必要に応じてモデルや結果をファイル保存する#

このノートブックではノートブック内に出力しつつ、学習したモデルはセッション内に保存しているだけで終えている。このため後日「学習結果を利用したい」場合には改めて学習し直す必要がある。それが面倒に思う人はファイル保存するようにしよう。

なお、正確には「ファイル」ではなく「複数のファイルを含むフォルダ」として保存されている。このフォルダ単位でダウンロードしたり、アップロードしてモデルを復元する必要があることに注意しよう。

またモデルはとてもファイルサイズが大きく、実際にダウンロード&アップロードするにはとても時間がかかる。そのためGoogleドライブにアクセス許可した上で自身のドライブ内に保存する方が楽だ。

# ノートブックからドライブへのアクセスを許可する
from google.colab import drive
drive.mount('/content/drive')

33.2. 環境構築#

!pip install fugashi[unidic-lite]
!pip install accelerate -U
Collecting fugashi[unidic-lite]
  Downloading fugashi-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (600 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 600.9/600.9 kB 9.8 MB/s eta 0:00:00
?25hCollecting unidic-lite (from fugashi[unidic-lite])
  Downloading unidic-lite-1.0.8.tar.gz (47.4 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 47.4/47.4 MB 10.3 MB/s eta 0:00:00
?25h  Preparing metadata (setup.py) ... ?25l?25hdone
Building wheels for collected packages: unidic-lite
  Building wheel for unidic-lite (setup.py) ... ?25l?25hdone
  Created wheel for unidic-lite: filename=unidic_lite-1.0.8-py3-none-any.whl size=47658818 sha256=93d9fb07131b7a11f3fd9f21e93759a181788374dd53f0d53f41d0e0bcba855b
  Stored in directory: /root/.cache/pip/wheels/89/e8/68/f9ac36b8cc6c8b3c96888cd57434abed96595d444f42243853
Successfully built unidic-lite
Installing collected packages: unidic-lite, fugashi
Successfully installed fugashi-1.3.2 unidic-lite-1.0.8
Collecting accelerate
  Downloading accelerate-0.31.0-py3-none-any.whl (309 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 309.4/309.4 kB 5.3 MB/s eta 0:00:00
?25hRequirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from accelerate) (1.25.2)
Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.10/dist-packages (from accelerate) (24.1)
Requirement already satisfied: psutil in /usr/local/lib/python3.10/dist-packages (from accelerate) (5.9.5)
Requirement already satisfied: pyyaml in /usr/local/lib/python3.10/dist-packages (from accelerate) (6.0.1)
Requirement already satisfied: torch>=1.10.0 in /usr/local/lib/python3.10/dist-packages (from accelerate) (2.3.0+cu121)
Requirement already satisfied: huggingface-hub in /usr/local/lib/python3.10/dist-packages (from accelerate) (0.23.4)
Requirement already satisfied: safetensors>=0.3.1 in /usr/local/lib/python3.10/dist-packages (from accelerate) (0.4.3)
Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (3.15.3)
Requirement already satisfied: typing-extensions>=4.8.0 in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (4.12.2)
Requirement already satisfied: sympy in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (1.12.1)
Requirement already satisfied: networkx in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (3.3)
Requirement already satisfied: jinja2 in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (3.1.4)
Requirement already satisfied: fsspec in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (2023.6.0)
Collecting nvidia-cuda-nvrtc-cu12==12.1.105 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (23.7 MB)
Collecting nvidia-cuda-runtime-cu12==12.1.105 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (823 kB)
Collecting nvidia-cuda-cupti-cu12==12.1.105 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (14.1 MB)
Collecting nvidia-cudnn-cu12==8.9.2.26 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cudnn_cu12-8.9.2.26-py3-none-manylinux1_x86_64.whl (731.7 MB)
Collecting nvidia-cublas-cu12==12.1.3.1 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl (410.6 MB)
Collecting nvidia-cufft-cu12==11.0.2.54 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl (121.6 MB)
Collecting nvidia-curand-cu12==10.3.2.106 (from torch>=1.10.0->accelerate)
  Using cached nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl (56.5 MB)
Collecting nvidia-cusolver-cu12==11.4.5.107 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl (124.2 MB)
Collecting nvidia-cusparse-cu12==12.1.0.106 (from torch>=1.10.0->accelerate)
  Using cached nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl (196.0 MB)
Collecting nvidia-nccl-cu12==2.20.5 (from torch>=1.10.0->accelerate)
  Using cached nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl (176.2 MB)
Collecting nvidia-nvtx-cu12==12.1.105 (from torch>=1.10.0->accelerate)
  Using cached nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl (99 kB)
Requirement already satisfied: triton==2.3.0 in /usr/local/lib/python3.10/dist-packages (from torch>=1.10.0->accelerate) (2.3.0)
Collecting nvidia-nvjitlink-cu12 (from nvidia-cusolver-cu12==11.4.5.107->torch>=1.10.0->accelerate)
  Downloading nvidia_nvjitlink_cu12-12.5.40-py3-none-manylinux2014_x86_64.whl (21.3 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 21.3/21.3 MB 53.3 MB/s eta 0:00:00
?25hRequirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from huggingface-hub->accelerate) (2.31.0)
Requirement already satisfied: tqdm>=4.42.1 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub->accelerate) (4.66.4)
Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.10/dist-packages (from jinja2->torch>=1.10.0->accelerate) (2.1.5)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub->accelerate) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub->accelerate) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub->accelerate) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->huggingface-hub->accelerate) (2024.6.2)
Requirement already satisfied: mpmath<1.4.0,>=1.1.0 in /usr/local/lib/python3.10/dist-packages (from sympy->torch>=1.10.0->accelerate) (1.3.0)
Installing collected packages: nvidia-nvtx-cu12, nvidia-nvjitlink-cu12, nvidia-nccl-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, nvidia-cusparse-cu12, nvidia-cudnn-cu12, nvidia-cusolver-cu12, accelerate
Successfully installed accelerate-0.31.0 nvidia-cublas-cu12-12.1.3.1 nvidia-cuda-cupti-cu12-12.1.105 nvidia-cuda-nvrtc-cu12-12.1.105 nvidia-cuda-runtime-cu12-12.1.105 nvidia-cudnn-cu12-8.9.2.26 nvidia-cufft-cu12-11.0.2.54 nvidia-curand-cu12-10.3.2.106 nvidia-cusolver-cu12-11.4.5.107 nvidia-cusparse-cu12-12.1.0.106 nvidia-nccl-cu12-2.20.5 nvidia-nvjitlink-cu12-12.5.40 nvidia-nvtx-cu12-12.1.105

33.3. モジュール読み込み、データ前処理#

import torch
import pandas as pd
from transformers import BertTokenizer, BertForSequenceClassification, Trainer, TrainingArguments
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import numpy as np

# データの読み込み
filename = "pn.tsv"
columns = ["id", "sentiment", "text", "judges-json", "usage"]
df = pd.read_csv(filename, sep="\t", names=columns)

# ラベルの付け替え
# AutoModelForSequenceClassificationではラベルは「0から始まる整数」である必要がある。
# このため -1, 0, 1 => 0, 1, 2(ネガティブ0、ノーマル1、ポジティブ2)に付け替える。
df['sentiment'] = df['sentiment'].map({-1: 0, 0: 1, 1: 2})

# データセットの分割
train_texts = df[df["usage"] == "train"]["text"].tolist()
train_labels = df[df["usage"] == "train"]["sentiment"].tolist()
dev_texts = df[df["usage"] == "dev"]["text"].tolist()
dev_labels = df[df["usage"] == "dev"]["sentiment"].tolist()
test_texts = df[df["usage"] == "test"]["text"].tolist()
test_labels = df[df["usage"] == "test"]["sentiment"].tolist()

# 動作確認
print(f"{train_texts[0]=}")
print(f"{train_labels[0]=}")
print(df['sentiment'].value_counts())
train_texts[0]='(笑)'
train_labels[0]=1
sentiment
2    3406
1    1329
0     818
Name: count, dtype: int64

33.4. モデルの用意#

  • ここでは “tohoku-nlp/bert-base-japanese-v3” を利用。

  • 長文だが、最初の出力「Some weights of BertForSequenceClassification were not initialized from the model checkpoint at tohoku-nlp/bert-base-japanese-v3 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.」は、このままでは利用できない(ので学習が必要だろう)ということを指摘している。この理由は、AutoModelForSequenceClassificationが「LLMの最後尾に新たな線形層(torch.nn.Linear)を追加しているためだ。この線形層はクラス数と同数のユニットを持つように設定されており(引数num_labelsで設定)、各ユニットに対するスコアを求めるようにモデルを拡張している。

  • このようなにモデルを拡張しているということは、拡張した部分(Linear層のパラメータ)については重みがまっさらな状態である。このままでは当然推定できない(でたらめになる)ため、「You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.」と注意を促している。

  • TrainingArgumentsで用いているパラメータ output_dir はチェックポイントを保存する場所を指定している。

    • チェックポイントとは、別のパラメータ「save_strategy = “epoch”」として指定したタイミングのモデルのこと。今回はepochと指定しているため1エポック毎にモデルが保存されている。

    • 必要に応じてこれらのチェックポイントからモデルを復元することも可能。モデル読み込み時にファイルを指定するだけで良い。このためファインチューニングしたモデルをファイル保存しておきたいならば、resultsフォルダ内のチェックポイントを保存しておくか、最終モデルである sentiment_model を保存しておくと良いだろう。

    • ダウンロードするというよりは、Googleドライブへのアクセス許可を与え、自身のドライブ内に保存する方が良いだろう。

# ハイパーパラメータ
num_labels = 3   # クラス数
max_length = 128 # 最長系列長(最大トークン数)
num_train_epochs = 10 # 最大学習エポック数
output_dir = './results' # チェックポイント等を保存するディレクトリ
batch_size = 16  # バッチサイズ(一度に処理するサンプル数)
logging_dir = './logs' # ログ出力用のディレクトリ(主にエラー確認用)

# トークナイザーとモデルの準備
model_name = "tohoku-nlp/bert-base-japanese-v3"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=num_labels)

# データセットのトークナイズ
train_encodings = tokenizer(train_texts, truncation=True, padding=True, max_length=max_length)
dev_encodings = tokenizer(dev_texts, truncation=True, padding=True, max_length=max_length)
test_encodings = tokenizer(test_texts, truncation=True, padding=True, max_length=max_length)

# 動作確認
# 'input_ids', 'token_type_ids', 'attention_mask'をキーとするリストとして保存されている。
# ['input_ids'][0] は、0番目のみを出力指定。
print(train_encodings.keys())
print(train_encodings['input_ids'][0]) # サンプル0番目
print(train_encodings['attention_mask'][0])
/usr/local/lib/python3.10/dist-packages/huggingface_hub/utils/_token.py:89: UserWarning: 
The secret `HF_TOKEN` does not exist in your Colab secrets.
To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.
You will be able to reuse this secret in all of your notebooks.
Please note that authentication is recommended but still optional to access public models or datasets.
  warnings.warn(
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at tohoku-nlp/bert-base-japanese-v3 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.
dict_keys(['input_ids', 'token_type_ids', 'attention_mask'])
[2, 23, 4374, 24, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# input_ids をデコードして確認
tokenizer.decode([2, 23, 4374, 24, 3])
'[CLS] ( 笑 ) [SEP]'

33.5. LLM用にデータを整形#

今回は分類タスクとして学習させたい。しかしtokenizerではテキストに対する前処理しか行われておらず、教師ラベルは別に用意している。これらを使いやすい形(Dataset型)にまとめ直している。

class SentimentDataset(torch.utils.data.Dataset):
    '''サンプル毎に input_ids, token_type_ids, attention_mask, labels を設定。
    '''
    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 = SentimentDataset(train_encodings, train_labels)
dev_dataset = SentimentDataset(dev_encodings, dev_labels)
test_dataset = SentimentDataset(test_encodings, test_labels)

# 動作確認
# サンプル毎に input_ids, token_type_ids, attention_mask, labels を用意した
print(train_dataset[0])
{'input_ids': tensor([   2,   23, 4374,   24,    3,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
           0,    0,    0,    0,    0,    0,    0,    0,    0,    0,    0]), 'token_type_ids': tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 'attention_mask': tensor([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 'labels': tensor(1)}

33.6. 学習#

エポック毎に出力しているログは、学習データに対する損失(training loss)、検証データに対する損失(validation loss)、正解率(accuracy)、F1スコア(F1)、適合率(precision)、再現率(recall)。

ログを確認すると、基本的には学習データに対する損失は減り続けている。しかし検証データに対する損失は途中から「逆に増えている」ことを確認できる。これが過学習(過剰適合; over-fitting)だ。ここでは特別なことはせずに指定したエポック数の学習を続け、最終もでエルを用いた検証を行うこととした。

# トレーニングの設定
training_args = TrainingArguments(
    output_dir=output_dir,
    num_train_epochs=num_train_epochs,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir=logging_dir,
    logging_steps=10,
    evaluation_strategy="epoch",
    save_strategy="epoch"
)

# 精度の計算
def compute_metrics(p):
    pred, labels = p
    pred = np.argmax(pred, axis=1)
    precision, recall, f1, _ = precision_recall_fscore_support(labels, pred, average='macro')
    acc = accuracy_score(labels, pred)
    return {
        'accuracy': acc,
        'f1': f1,
        'precision': precision,
        'recall': recall
    }

# トレーナーの設定
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_dataset,
    eval_dataset=dev_dataset,
    compute_metrics=compute_metrics
)

# モデルのトレーニング
trainer.train()

# モデルの保存
trainer.save_model("./sentiment_model")

# テストセットでの評価
results = trainer.evaluate(eval_dataset=test_dataset)

# 結果の表示
print(f"Test Accuracy: {results['eval_accuracy']}")
print(f"Test F1 Score: {results['eval_f1']}")
print(f"Test Precision: {results['eval_precision']}")
print(f"Test Recall: {results['eval_recall']}")
/usr/local/lib/python3.10/dist-packages/transformers/training_args.py:1474: FutureWarning: `evaluation_strategy` is deprecated and will be removed in version 4.46 of 🤗 Transformers. Use `eval_strategy` instead
  warnings.warn(
[2430/2430 13:57, Epoch 10/10]
Epoch Training Loss Validation Loss Accuracy F1 Precision Recall
1 0.423400 0.390626 0.853417 0.805778 0.821309 0.797058
2 0.302400 0.344628 0.865108 0.825983 0.837619 0.819292
3 0.173600 0.460407 0.876799 0.835589 0.841920 0.836759
4 0.225100 0.523901 0.873201 0.833102 0.834192 0.834772
5 0.084000 0.573181 0.879496 0.840015 0.842462 0.838103
6 0.006300 0.743830 0.859712 0.817780 0.816659 0.820234
7 0.028300 0.777815 0.884892 0.849863 0.851610 0.848161
8 0.013500 0.806769 0.890288 0.855647 0.862244 0.850295
9 0.000800 0.849699 0.886691 0.850617 0.854837 0.847571
10 0.001900 0.856102 0.884892 0.847907 0.854275 0.843427

[35/35 00:02]
Test Accuracy: 0.8607594936708861
Test F1 Score: 0.8308946761001555
Test Precision: 0.8580621695594958
Test Recall: 0.8097126694248997

33.7. テストデータに対する詳細結果#

# テストデータに対する詳細結果
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
#import numpy as np

predictions, labels, _ = trainer.predict(test_dataset)
predicted_labels = np.argmax(predictions, axis=1)
labels = ['negative', 'normal', 'positive']

# 分類レポートの表示
print(classification_report(test_labels, predicted_labels, target_names=labels))
              precision    recall  f1-score   support

    negative       0.93      0.81      0.86        78
      normal       0.77      0.67      0.72       139
    positive       0.88      0.95      0.91       336

    accuracy                           0.86       553
   macro avg       0.86      0.81      0.83       553
weighted avg       0.86      0.86      0.86       553
# 混同行列の表示
conf_matrix = confusion_matrix(test_labels, predicted_labels)
disp = ConfusionMatrixDisplay(confusion_matrix=conf_matrix, display_labels=labels)
disp.plot(cmap='Blues')
disp.ax_.set_title('Confusion Matrix')
Text(0.5, 1.0, 'Confusion Matrix')
../_images/afaaa03a9544d2df55a129b6576631dbaeaecfa9364d93086d2d55ff69610584.png

33.8. 具体的な失敗例#

ラベルは「ネガティブ0、ノーマル1、ポジティブ2」に付け直していることに注意。

33.8.1. 学習データに対する誤り#

以下の通り1件しか誤りがない。学習データに対しては極めて的鉄な予測が可能なモデルになっているようだ。

# 学習データに対する予測誤り

predictions, labels, _ = trainer.predict(train_dataset)
predicted_labels = np.argmax(predictions, axis=1)

# 誤った予測のサンプルを抽出
incorrect_indices = [i for i, (true, pred) in enumerate(zip(train_labels, predicted_labels)) if true != pred]

# 誤った予測のサンプルを出力
for i in incorrect_indices:
    print(f"正解ラベル: {train_labels[i]}, 予測ラベル: {predicted_labels[i]}, テキスト: {train_texts[i]}")
正解ラベル: 0, 予測ラベル: 1, テキスト: 気持ち悪くて食べられませんでした。

33.8.2. テストデータに対する誤り#

テストデータのサンプル数は学習データより少ないにも関わらず、失敗事例数は多い。

# テストデータに対する予測誤り

predictions, labels, _ = trainer.predict(test_dataset)
predicted_labels = np.argmax(predictions, axis=1)

# 誤った予測のサンプルを抽出
incorrect_indices = [i for i, (true, pred) in enumerate(zip(test_labels, predicted_labels)) if true != pred]

# 誤った予測のサンプルを出力
for i in incorrect_indices:
    print(f"正解ラベル: {test_labels[i]}, 予測ラベル: {predicted_labels[i]}, テキスト: {test_texts[i]}")
正解ラベル: 1, 予測ラベル: 2, テキスト: お心遣いありがとうございました。
正解ラベル: 0, 予測ラベル: 1, テキスト: と感じてしまいました。
正解ラベル: 2, 予測ラベル: 1, テキスト: 再訪です。
正解ラベル: 2, 予測ラベル: 1, テキスト: 二度目です。
正解ラベル: 1, 予測ラベル: 2, テキスト: お疲れ様です。
正解ラベル: 1, 予測ラベル: 2, テキスト: (^ω^
正解ラベル: 1, 予測ラベル: 2, テキスト: 対応ありがとうございました。
正解ラベル: 1, 予測ラベル: 2, テキスト: コンビニも近かったです。
正解ラベル: 1, 予測ラベル: 2, テキスト: 長々と失礼しました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 寝るだけなので十分です。
正解ラベル: 1, 予測ラベル: 2, テキスト: が感想です。
正解ラベル: 0, 予測ラベル: 1, テキスト: 朝食は食べませんでした。
正解ラベル: 1, 予測ラベル: 2, テキスト: 良かった所。
正解ラベル: 1, 予測ラベル: 2, テキスト: 安心してください。
正解ラベル: 1, 予測ラベル: 2, テキスト: 思わず笑ってしまいました。
正解ラベル: 2, 予測ラベル: 1, テキスト: 間違いないホテルです。
正解ラベル: 2, 予測ラベル: 1, テキスト: 迷わず予約しました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 足湯もありました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 料理は期待通りでした。
正解ラベル: 0, 予測ラベル: 1, テキスト: 結局入れなかった。
正解ラベル: 1, 予測ラベル: 2, テキスト: また機会があれば...。
正解ラベル: 1, 予測ラベル: 2, テキスト: 朝食付きをお勧めします。
正解ラベル: 1, 予測ラベル: 2, テキスト: 立地はまずまず。
正解ラベル: 2, 予測ラベル: 1, テキスト: そんなに気になりませんでした。
正解ラベル: 2, 予測ラベル: 1, テキスト: 立地は問題なしです。
正解ラベル: 0, 予測ラベル: 2, テキスト: 食べたかったなぁ。
正解ラベル: 0, 予測ラベル: 1, テキスト: 天気は雨。
正解ラベル: 1, 予測ラベル: 2, テキスト: 安ければまた利用します。
正解ラベル: 1, 予測ラベル: 0, テキスト: 気になった点としては...
正解ラベル: 1, 予測ラベル: 2, テキスト: はあったものの美味しく頂きました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 泊まる分には問題ありません。
正解ラベル: 2, 予測ラベル: 1, テキスト: マンションのようなホテルでした。
正解ラベル: 0, 予測ラベル: 1, テキスト: 理解できません。
正解ラベル: 1, 予測ラベル: 2, テキスト: 餅っ!
正解ラベル: 1, 予測ラベル: 2, テキスト: とにかくすごい!
正解ラベル: 1, 予測ラベル: 2, テキスト: お風呂とトイレは別。
正解ラベル: 1, 予測ラベル: 0, テキスト: がゴロゴロ。
正解ラベル: 1, 予測ラベル: 2, テキスト: 風呂は温泉です。
正解ラベル: 2, 予測ラベル: 1, テキスト: 一人旅で利用いたしました。
正解ラベル: 1, 予測ラベル: 2, テキスト: それ以外は◎。
正解ラベル: 0, 予測ラベル: 1, テキスト: 午後9時すぎから朝8時までなので1000円くらいだといいですね。
正解ラベル: 2, 予測ラベル: 1, テキスト: 朝食は、お粥とチーズオムレツをお願いし、サラダに、ヨーグルトとフルーツ三昧。
正解ラベル: 1, 予測ラベル: 2, テキスト: 市内在住なので、過去何度か宿泊しました。
正解ラベル: 2, 予測ラベル: 1, テキスト: 母と娘を招待し女3人で新年初温泉を満喫しました。
正解ラベル: 2, 予測ラベル: 0, テキスト: あと、一番大事なのは、客用の冷蔵庫があり、細かいことを言わないのがメチャメチャいいです。
正解ラベル: 1, 予測ラベル: 2, テキスト: そこがチョット残念な思いをしただけで他の食事、お風呂などは大変満足させていただきました。
正解ラベル: 0, 予測ラベル: 1, テキスト: 外国の方も宿泊されるからか夕食時も朝食時もBGMはジャズでしたが、せめてさわやかな朝はlovesongはやめたほうが、、と思いました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 料理もおいしかったし,お風呂の洗面付近の清潔感は素晴らしかったので,期待していかなければそれなりに満足できたのかもしれません。
正解ラベル: 1, 予測ラベル: 0, テキスト: この金額でシャワーが赤青の蛇口じゃないところは初めてかも。
正解ラベル: 1, 予測ラベル: 0, テキスト: 側にあるソファーの方が柔らかいほど...。
正解ラベル: 0, 予測ラベル: 1, テキスト: チェックアウト12時のプランでしたが、10時までの料金をホテルで支払い、その後は精算機でとのことでした。
正解ラベル: 0, 予測ラベル: 1, テキスト: 4ヵ月の子どもと一緒の旅行だったので、部屋食にしたが、全体的に同じような味付けで、これ!
正解ラベル: 1, 予測ラベル: 2, テキスト: フロントの方たちの笑顔があれば、90点なのですが、85点の満足の宿でした。
正解ラベル: 1, 予測ラベル: 2, テキスト: 部屋は4人部屋で大変広く、7人で2次会が出来ました、アジアンリゾート風ですね・・良かったですその他テニスやパターゴルフ、散策、夏はプールなどがあり宿泊者が楽しめる配慮が随所にありますが、子供いわくゲーセンは信じられない位しょぼい(笑)
正解ラベル: 0, 予測ラベル: 1, テキスト: 夜フルーツが無いのと、朝お粥が有れば年配の方は喜ぶと思います。
正解ラベル: 0, 予測ラベル: 2, テキスト: フロントも全く同じで何コールしてもでない。
正解ラベル: 1, 予測ラベル: 2, テキスト: 朝食のメニューも変わっていて前からあった和食・洋食にエビチーズドリアセット・ロコモコ丼セットの4種類になっていました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 大浴場が利用できたのが、ここに決めたポイントです。
正解ラベル: 1, 予測ラベル: 2, テキスト: 他はパーフェクトです。
正解ラベル: 2, 予測ラベル: 1, テキスト: ほとんど寝るだけだったので、特に不便はありませんでした。
正解ラベル: 0, 予測ラベル: 1, テキスト: 髭剃りやシャンプーハットをつけるくらいなら、バスタオルを備え付けてもらった方が嬉しかったです。
正解ラベル: 1, 予測ラベル: 2, テキスト: この宿に泊まって一番感じる事は、御主人、女将さんの人柄です。
正解ラベル: 2, 予測ラベル: 1, テキスト: 120センチのセミダブルでしたので、夫とふたりでもこれならOKと思いました。
正解ラベル: 1, 予測ラベル: 2, テキスト: チッックインが遅くなることと雨が降っていたため夕食を済ませてからホテルに着きましたが、カレーのサービスを行っていました。
正解ラベル: 0, 予測ラベル: 1, テキスト: 18:00の予約で入って料理をとったところ保温トレーの中の料理はどれもぬるい程度の温度で、松茸茶碗蒸しは容器が冷たい程で驚きました。
正解ラベル: 0, 予測ラベル: 1, テキスト: 粉コーヒーでもいいのでいつでも飲めるようになってればなぁ。
正解ラベル: 2, 予測ラベル: 1, テキスト: シングルルームを2部屋予約しましたが、当日部屋が空いてるとの事で大きな部屋に変更してくださいました。
正解ラベル: 2, 予測ラベル: 1, テキスト: 料理はみんなきれいに盛られ、おいしかったですが、なにか一つ地のもので、「ここでしか食べられない」とか「何か特化したもの」があったらさらに良かったと思います。
正解ラベル: 2, 予測ラベル: 1, テキスト: お風呂場にタオルの設置があったらいいなと思うのと、朝食会場が宿泊者の人数の割に狭いのが少し惜しい気がしましたが、いいホテルに泊まって気持ちよく過ごせました。
正解ラベル: 1, 予測ラベル: 2, テキスト: ・冷蔵庫有り。
正解ラベル: 1, 予測ラベル: 2, テキスト: 朝食は長い列ができていたが、案外すぐに呼ばれた。
正解ラベル: 1, 予測ラベル: 2, テキスト: 海沿いの立地のためか日の出スポットらしく元旦に続々前回道路に人が集まってました。
正解ラベル: 1, 予測ラベル: 2, テキスト: あと、お宿横には酒屋さん!
正解ラベル: 1, 予測ラベル: 2, テキスト: 温泉が深夜男女入替のため、チェックイン後とチェックアウト後に入浴すると両方楽しめます。
正解ラベル: 1, 予測ラベル: 2, テキスト: 温泉チケット付きのプランでしたので目の前の温泉にいきました。
正解ラベル: 1, 予測ラベル: 2, テキスト: 他は完璧だと思う。
正解ラベル: 1, 予測ラベル: 2, テキスト: 夕食の際、誕生日の特別デザートを用意頂いたり、希望すればチャンチャンコを貸して頂くこともできるようでした。