0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

大規模言語モデルのファインチューニング:感情分析モデル

Last updated at Posted at 2025-01-19

はじめに

本記事では、WRIMEデータセットを用いて感情分析モデルのファインチューニング手順を解説します。感情分析は、テキストデータから感情的な傾向を抽出する重要なタスクであり、ビジネスや研究において広く利用されています。WRIMEデータセットは、日本語のツイートを対象とした感情分析に適したデータセットです。

環境の準備

まず、必要なライブラリをインストールし、乱数シードを固定します。

!pip install transformers[ja,torch] datasets matplotlib japanize-matplotlib
from transformers.trainer_utils import set_seed

# 乱数シードを42に固定(再現性確保のため)
set_seed(42)

データセットの準備

WRIMEデータセットをHugging Face Hubから読み込みます。

from pprint import pprint
from datasets import load_dataset

# WRIMEデータセットの読み込み
train_dataset = load_dataset("llm-book/wrime-sentiment", split="train")
valid_dataset = load_dataset("llm-book/wrime-sentiment", split="validation")

# データセットの内容表示
pprint(train_dataset[0])
pprint(train_dataset.features)

出力:

{'datetime': '2012/7/31 23:48',
 'label': 1,
 'sentence': 'ぼけっとしてたらこんな時間。チャリあるから食べにでたいのに…',
 'user_id': 1}
{'datetime': Value(dtype='string', id=None),
 'label': ClassLabel(names=['positive', 'negative'], id=None),
 'sentence': Value(dtype='string', id=None),
 'user_id': Value(dtype='int64', id=None)}

WRIMEデータセットは、日本語のツイートを含み、ラベルはポジティブとネガティブの二値分类です。

トークナイザ

日本語BERTモデルのトークナイザを読み込み、テキストをトークン化します。

from transformers import AutoTokenizer

# トークナイザの読み込み
model_name = "cl-tohoku/bert-base-japanese-v3"
tokenizer = AutoTokenizer.from_pretrained(model_name)

# トークナイザの動作確認
print(type(tokenizer).__name__)
tokenizer.tokenize("これはテストです。")
encoded_input = tokenizer("これはテストです。")
print(type(encoded_input).__name__)
pprint(encoded_input)
tokenizer.convert_ids_to_tokens(encoded_input["input_ids"])

出力:

BertJapaneseTokenizer
BatchEncoding
{'attention_mask': [1, 1, 1, 1, 1, 1, 1],
 'input_ids': [2, 12538, 465, 14985, 13037, 385, 3],
 'token_type_ids': [0, 0, 0, 0, 0, 0, 0]}
['[CLS]', 'これ', '', 'テスト', 'です', '', '[SEP]']

日本語特化のBERTモデルのトークナイザを使用し、入力テキストをトークン化します。

データセット統計の可視化

データセット内のテキスト長とラベル分布を可視化します。

from collections import Counter
import japanize_matplotlib
import matplotlib.pyplot as plt
from datasets import Dataset
from tqdm import tqdm

plt.rcParams["font.size"] = 18  # 文字サイズを大きくする

def visualize_text_length(dataset: Dataset):
    """テキストのトークン数分布を可視化"""
    length_counter = Counter()
    for data in tqdm(dataset):
        length = len(tokenizer.tokenize(data["sentence"]))
        length_counter[length] += 1
    plt.bar(length_counter.keys(), length_counter.values(), width=1.0)
    plt.xlabel("トークン数")
    plt.ylabel("事例数")
    plt.show()

visualize_text_length(train_dataset)
visualize_text_length(valid_dataset)

image-20250119134624559.png

image-20250119134639944.png

def visualize_labels(dataset: Dataset):
    """ラベル分布を可視化"""
    label_counter = Counter()
    for data in dataset:
        label_id = data["label"]
        label_name = dataset.features["label"].names[label_id]
        label_counter[label_name] += 1
    plt.bar(label_counter.keys(), label_counter.values(), width=1.0)
    plt.xlabel("ラベル")
    plt.ylabel("事例数")
    plt.show()

visualize_labels(train_dataset)
visualize_labels(valid_dataset)

image-20250119134706430.png

image-20250119134726190.png

データセットの前処理

テキストをトークン化し、モデルの入力形式に変換します。

from transformers import BatchEncoding

def preprocess_text_classification(example: dict[str, str | int]) -> BatchEncoding:
    """テキストをトークン化し、ラベルを追加"""
    encoded_example = tokenizer(example["sentence"], max_length=512)
    encoded_example["labels"] = example["label"]
    return encoded_example

encoded_train_dataset = train_dataset.map(preprocess_text_classification, remove_columns=train_dataset.column_names)
encoded_valid_dataset = valid_dataset.map(preprocess_text_classification, remove_columns=valid_dataset.column_names)
print(encoded_train_dataset[0])

出力:

{'input_ids': [2, 478, 7139, 15269, 441, 456, 13123, 21311, 12671, 385, 12651, 7065, 12485, 12488, 13781, 461, 457, 13030, 464, 461, 29, 29, 29, 3], 'token_type_ids': [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': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'labels': 1}

ミニバッチ構築

データセットをミニバッチに分割します。

from transformers import DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
batch_inputs = data_collator(encoded_train_dataset[0:4])
pprint({name: tensor.size() for name, tensor in batch_inputs.items()})

出力:

{'attention_mask': torch.Size([4, 36]),
 'input_ids': torch.Size([4, 36]),
 'labels': torch.Size([4]),
 'token_type_ids': torch.Size([4, 36])}

モデルの準備

感情分析用のモデルを読み込みます。

from transformers import AutoModelForSequenceClassification

class_label = train_dataset.features["label"]
label2id = {label: id for id, label in enumerate(class_label.names)}
id2label = {id: label for id, label in enumerate(class_label.names)}

model = AutoModelForSequenceClassification.from_pretrained(
    model_name,
    num_labels=class_label.num_classes,
    label2id=label2id,
    id2label=id2label,
)

# パラメータをメモリ上に隣接した形で配置
for param in model.parameters():
    param.data = param.data.contiguous()

print(model.forward(**data_collator(encoded_train_dataset[0:4])))

出力:

SequenceClassifierOutput(loss=tensor(0.8075, grad_fn=<NllLossBackward0>), logits=tensor([[ 0.2869, -0.2644],
        [ 0.1715,  0.0095],
        [ 0.0040,  0.1524],
        [ 0.2233, -0.2968]], grad_fn=<AddmmBackward0>), hidden_states=None, attentions=None)

訓練の実行

モデルの訓練を実行します。

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="output_wrime",
    per_device_train_batch_size=32,
    per_device_eval_batch_size=32,
    learning_rate=2e-5,
    lr_scheduler_type="linear",
    warmup_ratio=0.1,
    num_train_epochs=3,
    save_strategy="epoch",
    logging_strategy="epoch",
    evaluation_strategy="epoch",
    load_best_model_at_end=True,
    metric_for_best_model="accuracy",
    fp16=True,
    report_to="none",
)

import numpy as np

def compute_accuracy(eval_pred: tuple[np.ndarray, np.ndarray]) -> dict[str, float]:
    """正解率を計算"""
    predictions, labels = eval_pred
    predictions = np.argmax(predictions, axis=1)
    return {"accuracy": (predictions == labels).mean()}

from transformers import Trainer

trainer = Trainer(
    model=model,
    train_dataset=encoded_train_dataset,
    eval_dataset=encoded_valid_dataset,
    data_collator=data_collator,
    args=training_args,
    compute_metrics=compute_accuracy,
)

trainer.train()

訓練後のモデルの評価

訓練後のモデルを検証セットで評価します。

eval_metrics = trainer.evaluate(encoded_valid_dataset)
pprint(eval_metrics)

モデルの保存

訓練済みモデルをGoogle DriveまたはHugging Face Hubに保存します。

Google Driveへの保存

from google.colab import drive

drive.mount("drive")
!mkdir -p drive/MyDrive/llm-book
!cp -r output_wrime drive/MyDrive/llm-book

Hugging Face Hubへの保存

from huggingface_hub import login

login()
repo_name = "YOUR-ACCOUNT/bert-base-japanese-v3-wrime-sentiment"
tokenizer.push_to_hub(repo_name)
model.push_to_hub(repo_name)

まとめ

本記事では、WRIMEデータセットを用いた感情分析モデルのファインチューニング手順を解説しました。WRIMEデータセットの特徴とモデルファインチューニングの重要性を理解し、実装手法を学ぶことができました。ぜひ自身の環境で試してみてください。


参考資料:

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?