1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔰PyTorchでニューラルネットワーク基礎 #39【GPT編・分類問題】

1
Posted at

概要

個人的な備忘録を兼ねたPyTorchの基本的な解説とまとめです。GPT編では自己回帰型の言語モデルの事前学習の演習で学んだことをまとめていきます。第38回で事前学習した小型のGPTタイプモデルを利用して、テキスト分類のファインチューニングを行ってみたいと思います。

gpt_classification.png

方針

  1. できるだけ同じコード進行
  2. できるだけ簡潔(細かい内容は割愛)

演習用のファイル

1. データ

事前学習で利用したlivedoorニュースコーパスのHOMMEカテゴリーに分類されているテキストデータのタイトル部分のみを使います。

livedoorのニュースコーパスは9つの分野に分かれています。全データで事前学習してタイトルで分野を分類するほうがきれいだと思います。GPTタイプの分類問題を知るという目的に対して、全データだと学習時間もかかりますし、1分野固定がお気楽かと😆

前処理

  • テキストファイルからタイトル部分(おそらく3行目)のみ残します。
  • タイトルのテキストを何らかの方法でクラス分類します。
  • ライセンス的に改変したデータをアップロードできないのが残念 :bow::bow::bow:

事前学習ではHOMMEカテゴリーだけを利用したので、タイトルを「どのようにクラス分けするのか!」という問題が発生します。

タイトルデータの分類・参考例
タイトル部分をいくつかのクラスに分類します。今回はGPTタイプでの分類問題コードの流れが確認できれば良いので、文章の分散表現の類似度を利用して大雑把に分類しました。表1のlabel列の名前はテキトーに決めました。

  1. HuggingFaceの cl-nagoya/ruri-v3-130m/ を利用して、タイトル部分の埋め込みベクトルを作成 (もちろん他のモデルでもOK👍)
  2. cos類似度によって8クラス程度に分類 (いきなり5〜6分類だと失敗しました)
  3. ノイズありと判定されたタイトルとデータ数が少ないタイトルを削除、同一カテゴリーのデータ数が極端に多いデータも減少して、5〜6クラス程度に分類しました。500個ほどのデータが200個ほどになってしまいました。

繰り返しになりますが、ニュース記事全体で事前学習して、ニュース記事のディレクトリを使うほうが精度面も信頼性も確実だと思います。

title label label_id
0 フォーエバー21旗艦店が銀座に進出 ファッション 0
1 SHIPSに聞く「デキルオトコの新作スーツの着こなし方」 ファッション 0
2 グローバルで差をつけろ!HYBRID W-ZERO3 クルマ 3
3 友を失うその前に!?ビジネススキルの意外なる盲点 キャリア 1
4 なぜ走る? 8割のランナーが「ランニングで成果が出せてない」 スポーツ 4

表1:タイトルと分類の参考例

事前学習で利用したトークナイザーによって、title部分をトークナイズします。文末に<eod>を挿入してID列を作成 します。BERTタイプとは異なり、文頭に特殊トークンを追加しないことがポイントです。文末に挿入された<eod>が分類時に利用されるトークンとなります。

2. コードと解説

基本的にテキスト分類なので今まで同様の流れとなります1

  1. データの読み込みとtorchテンソルへの変換 (2.1)
  2. ネットワークモデルの定義と作成 (2.2)
  3. 誤差関数と誤差最小化の手法の選択 (2.3)
  4. 変数更新のループ (2.4)
  5. 検証 (2.5)

2.1 データの読み込みとtorchテンソルへの変換

ライブラリーの読み込み、精度の関数を定義、利用するトークナイザーを読み込みます。

ライブラリの読み込み
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from tokenizers import Tokenizer
from sklearn.model_selection import train_test_split


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"{device=}")

# ファイル名
data_filename = "./data/title_class5.jsonl"                        # 分類データ
tokenizer_filename = "tokenizer/livedoor_home_tokenizer_8k.json"   # トークナイザー
pretrain_filename = "model/seq_512_bpe_8k.model"                   # 事前学習したモデル
cls_model_filename = "model/homme_classification_5.model"          # 今回保存するモデル


# 精度を計算する関数
def accuracy(y, t):
    _, argmax_list = torch.max(y, dim=1)
    accuracy = sum(argmax_list == t).item()/len(t)
    return accuracy


# トークナイザーの読み込みと利用する特殊トークンの確認
tokenizer = Tokenizer.from_file(tokenizer_filename)

print(f"<pad>: {tokenizer.token_to_id('<pad>')}")   # <pad>: 0
print(f"<eod>: {tokenizer.token_to_id('<eod>')}")   # <eod>: 1
print(f"size: {tokenizer.get_vocab_size()}")        # size: 8000

深い意味はないのですが、タイトルを5分類に分けたデータで演習を行ってみました。6分類の方がきれいにデータが分類されていたのかな?検証精度は互角だったように覚えています:sweat_smile:

データの読み込み

タイトルを5分野に分類したファイル (title_class5.jsonl) を準備します。表のような形のデータで、「タイトル」列をtitle、「分類ラベルID」列をlabel_idというキーとして、JSONLやCSVなどで保存しておきます。私はtitle_class5.jsonlとして表2のようなデータを保存しました。

title label label_id
「会社が携帯代を出してくれない」-辛口説教部屋vol.57 人生相談 2
年収1000万円のビジネスマンに聞いた「あなたは市場価値をどれぐらい意識していますか?」-年... キャリア 1
キン肉マンTシャツ企画、一番人気は名脇役として知られる“あの超人"!! ファッション 0

表2:title_class5.jsonlの例(タイトルを類似度で分類したサンプル)

作成したデータセットをpandasを利用してJSONL形式を読み込んでみました。表2のように表示されます。label_idが分類で活用するID部分となります。title部分をtokenizerを利用してID列へ変換するDatasetクラスを作成していきます。train_test_splitを利用してデータを訓練用データdfと検証用データdf_testとに分割します。

df_org = pd.read_json(data_filename, lines=True)
df, df_test = train_test_split(df_org, stratify=df_org["label_id"], random_state=55)

分割したデータフレームをDatasetクラスで加工していく流れになります。

Datasetクラスのカスタマイズ・ID化

カスタムDatasetとDataLoaderクラスを利用して学習に必要な形へ変換していきます。様々な書き方が考えられます。最終的に、

  1. [タイトルテキスト]+<eod> としてID化
  2. バッチごとに <pad> を挿入して等長化

この2点を満たせばOKです。

BERT編で利用したTemplateProcessingとpad_sequenceを復習しつつ活用してみました。

ポイント

  1. tokenizeする際に、文末に<eod>トークンを挿入
    • TemplateProcessingを利用してencodeするときに自動挿入されるようにします。
  2. Datasetクラスのカスタマイズ
    • データフレームのtitle列をtokenizerでencode
    • ID列と対応するlabel_idを戻り値に指定
  3. collate関数でpad_sequenceを利用した等長化対策を実装

encodeするときに<eod>を文末へ自動挿入するテンプレートを利用してDatasetクラスを作成してみます。Datasetクラスのカスタマイズは、初期化、長さ、添字アクセスを担当する特殊メソッドの__init__( )、__len__( )、__getitem__( )を定義する必要がありました。

Datasetクラスのカスタマイズ
from tokenizers.processors import TemplateProcessing

eod_id = tokenizer.token_to_id("<eod>")   # 直接書いたほうがわかりやすかもしれない

# (1) encodeするときのテンプレ
tokenizer.post_processor = TemplateProcessing(
    single="$A <eod>",
    special_tokens=[("<eod>", eod_id)],
)

# 分類問題用のDatasetクラス
class ClassificationDataset(Dataset):
    def __init__(self, data, tokenizer):
        # (2) encode_batchで一度にid化
        encodings = tokenizer.encode_batch(list(data["title"]))
        ids_list = [torch.tensor(e.ids, dtype=torch.long) for e in encodings]

        # (3) ラベル
        labels = torch.tensor(data["label_id"].tolist(), dtype=torch.long)        

        self.labels = labels
        self.ids_list = ids_list
   
    def __len__(self):
        return len(self.labels)
    
    def __getitem__(self, index):
        return {"ids": self.ids_list[index],
                "label": self.labels[index]}

説明メモ

  • (1) post_processor は、本体のトークン化が終わった後にencode / encode_batch を呼ぶだけで自動的に特殊トークンを挿入するお役立ちツール。
    single="$A <eod>"が一文をencode したときのテンプレート。$Aと表記される入力文の後に<eod>を付ける、という意味になります。
  • (2) encode_batchを使うと、データフレームのtitle列を一気にencodeできます。ギガサイズの場合は要検討🤔
  • (3) label_id列はすでにID化されているのでLongTensorにしておきます。
  • pad_sequenceを使う予定なので、__getitem__( )の戻り値をtorch.Tensor型にしておくことが地味に重要

DataLoaderで利用するcollate関数の定義です。collate関数で等長化していきます。

DataLoaderのcollate関数を定義
from torch.nn.utils.rnn import pad_sequence
from functools import partial                     # collate_fnのときに使う

def padding_collate_fn(batch, tokenizer=tokenizer):
    """等長化するcollate関数"""
    # (1) tokenizerを使い<pad> IDを求める
    padding_id = tokenizer.token_to_id("<pad>")
    # (2) 入力されるデータがtensorなのでそのまま活用
    ids_list = [x["ids"] for x in batch]
    labels = torch.stack([x["label"] for x in batch])
    # (3) padding
    padded_ids = pad_sequence(ids_list, batch_first=True, padding_value=padding_id)
    # (4) padマスクの作成
    attention_mask = (padded_ids != padding_id).long()  # 実トークン=1, <pad>=0
    # (5)
    return {"ids": padded_ids, "attention_mask": attention_mask, "label": labels}

説明メモ
pytorchのpad_sequenceを利用して、バッチ毎に等長化します。

  • (1) tokenizerの<pad>IDを求める
  • (2) pad_sequenceを利用するために、tensorのリストに変更
  • (3) pad_sequenceでbatchの中で系列長を揃えていきます。
  • (4) <pad>部分を0、それ以外を1とするマスクの作成。<pad>を除く末尾は<eod>トークンになります。<eod>トークンまでは1、それ以降は0となります2
  • (5) 辞書形式で戻り値を指定してみました。

カスタマイズしたDatasetとDataLoaderクラスを利用して実際にデータを取得してみます。

使い方
collate_wrapper = partial(padding_collate_fn, tokenizer=tokenizer)
dataset = ClassificationDataset(data=df, tokenizer=tokenizer)
dataloader = DataLoader(
    dataset = dataset, 
    batch_size=2,
    shuffle = True,
    collate_fn=collate_wrapper,
    )

dataloderの出力サンプルとなります。毎回異なる内容になります。

出力例
print(next(iter(dataloader)))
# {'ids': tensor(
    [[6344, 1397,  600, 3790,   18,   24, 4348, 6207,  270,  582,  749,  279,  308, 6072,  277, 3443, 5292, 6344, 4691,    1,    0,    0,    0,    0,   0,    0],
#     [294,  559,  334,  649,  322,  335, 1063,  596,  109,  281,  845,  108,  508,  554,  684,  296, 7779,   17, 3145, 2902, 3631, 3790,   18,   24,     23,    1]]),
#  'attention_mask': tensor(
    [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
#    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]),
#  'label': tensor([3, 2])}

ids部分、<pad>IDの0が挿入されていることが確認できます。attention_maskは、<pad>に0、それ以外のトークンに1を割り当てています。

2.2 ネットワークモデルの定義と作成

GPTタイプのファインチューニングによるテキスト分類では、事前学習した重みと文末に埋め込んだ<eod>トークンの特徴量を活用して分類学習していきます。具体的には、図1のように埋め込み層・Transformer層で関連度を考慮した特徴量に変換、文末にある<eod>の特徴量を利用して全結合層による5種類のクラス予測をする形となります。文末の<eod>トークンは、<eod>までのすべてのトークン情報を参照しています。その特徴量は文章全体の特徴を捉えることができると解釈できるからです。

図1:モデル構造(分類ネットワーク)
gpt_classification.png

事前学習のモデルは図2のような構造をしていました。事前学習時のLinearを図1の全結合層へ付け替える形になります。

図2:事前学習モデルの構造
gpt_model.png

修正部分は大きく2点

  1. 事前学習時に用いた最終層のLiearをテキスト分類用のLinearに変更
  2. テキスト分類用Linearへの入力には<eod>部分の特徴量を利用
ネットワークモデルの定義と作成
# (1) モデルの設定
class ModelConfig:
    def __init__(self, tokenizer):
        # モデル構造
        self.vocab_size = tokenizer.get_vocab_size()
        self.seq_len = 512   # 128トークンだとSFT時に少ない
        self.d_model = 256   # 512
        self.nhead = 8
        self.dim_feedforward = 4*self.d_model
        self.num_layers = 6
        self.dropout = 0.1
        
        # 特殊トークンID
        self.pad_token_id = tokenizer.token_to_id("<pad>")
        self.eod_token_id = tokenizer.token_to_id("<eod>")

        # 学習データに関する設定
        self.context_size = self.seq_len         # 学習できる長さ
        self.context_stride = self.context_size  # 重なり具合の調整

        # 学習設定
        self.batch_size = 128
        self.ignore_index = -100
        self.learning_rate = 0.001  # これだとデフォルトと変わらない
        self.num_steps = 5_000
        self.max_grad_norm = 1.0

    # (2) 属性を追加・更新するメソッドを追加 設定時のタイポに注意だぞ〜
    def update(self, **kwargs):
        """渡されたキーワード引数で設定を動的に追加・更新する"""
        for key, value in kwargs.items():
            setattr(self, key, value)

# (3) 文章分類のネットワーク
class DNN(nn.Module):
    def __init__(self, config: ModelConfig):
        super().__init__()
        self.config = config
        
        self.token_embedding = nn.Embedding(num_embeddings=config.vocab_size, embedding_dim=config.d_model,  padding_idx=config.pad_token_id)
        self.pos_embedding = nn.Embedding(num_embeddings=config.seq_len, embedding_dim=config.d_model)
        self.dropout = nn.Dropout(config.dropout)

        transformer_layer = nn.TransformerEncoderLayer(
            d_model=config.d_model,
            nhead=config.nhead,
            dim_feedforward=config.dim_feedforward,
            dropout=config.dropout,
            activation="gelu",
            batch_first=True,
            norm_first=True,
        )

        self.transformer = nn.TransformerEncoder(transformer_layer, num_layers=config.num_layers, enable_nested_tensor=False)
        self.layer_norm = nn.LayerNorm(config.d_model)
        self.cls_head = nn.Linear(config.d_model, config.num_labels)   # 分類用のLinear層

    def forward(self, input_ids, attention_mask):
        batch_size, seq_len = input_ids.shape
        positions = torch.arange(seq_len, device=input_ids.device)

        tok_emb = self.token_embedding(input_ids)
        pos_emb = self.pos_embedding(positions).unsqueeze(0)
        x = tok_emb + pos_emb
        x = self.dropout(x)
        # nn.Transformer.generate_square_subsequent_mask を使ってマスクを生成
        causal_mask = nn.Transformer.generate_square_subsequent_mask(seq_len, dtype=torch.bool, device=x.device)

        key_padding_mask = (attention_mask == 0)

        # 自己回帰型 transformer (transformer decoder)
        x = self.transformer(x, mask=causal_mask, src_key_padding_mask=key_padding_mask, is_causal=True)
        x = self.layer_norm(x)

        # 分類用に入れ替わる部分
        # (4) eod位置の取得
        sequence_lengths = attention_mask.sum(dim=1).long()
        eod_positions = sequence_lengths - 1
        batch_indices = torch.arange(batch_size, device=input_ids.device)
        eod_states = x[batch_indices, eod_positions, : ]
        logits = self.cls_head(eod_states)
        return logits

# (5) 事前学習したモデルファイルを読み込む
checkpoint = torch.load(pretrain_filename, map_location=device, weights_only=False)

config = ModelConfig(tokenizer)
config.__dict__.update(checkpoint["config"])   # 事前学習時の構造を復元

# (6) configの追加設定と数値の変更
config.update(
    num_labels    = 5,     # 分類数
    batch_size    = 16,
    learning_rate = 5e-4,  # 学習率は小さくします
    num_steps     = 150,   # 100ステップでいいかも150だと過学習だぞ〜
)

# (7)
model = DNN(config).to(device)

# (8) 形が一致するパラメータだけを拾う
model_sd = model.state_dict()
pretrained_sd = {
    k: v for k, v in checkpoint["model_state_dict"].items()
    if k in model_sd and model_sd[k].shape == v.shape
}

# (9) 必要な部分(事前学習モデルの一部分)の重みだけ必要なのでstrict=False にする。
missing, unexpected = model.load_state_dict(pretrained_sd, strict=False)
print("変更部分:", missing)      # ['cls_head.weight', 'cls_head.bias'] だけのはず
print("おかしいぞ〜:", unexpected)   # [] のはず

説明メモ

  • (1) ModelConfigクラスを利用して、モデル内部の数値をまとめています。

  • (2) 事前学習で利用したModelConfigクラスに updateメソッドを追加しました。これにより、config.update(num_labels=5)みたいに、設定項目を追加・上書きできます。

  • (3) DNNは文章分類のネットワーク構造。事前学習モデルで利用した語彙数へ射影していた全結合層を分類用の全結合層に変更します。<eod>へ情報を集約して、<eod>のtransformerブロックの出力値を全結合層へ入力します。

  • (4) attention_maskは<pad>とそれ以外を区別するマスクでした。マスクの値が1である長さをsequence_lengthとします。<eod>の位置(インデックス)は、eod_positionはsequence_length - 1 となります。バッチサイズに対応したeod_positionの値を取得して、分類全結合層へ入力すればOKです。

  • (5) 事前学習したモデルを読み込みます。

  • (6) config.updateを利用して、必要な変数の値をアップデートします。

  • (7) 読み込んだconfig情報を利用してDNNを作ります。

  • (8) checkpoint["model_state_dict]で事前学習モデルの重みを取得します。

  • (9) 事前学習のモデルに存在する層の重みをコピーすることができます。print部分は実質不要な確認用です:sweat_smile:

2.3 誤差関数と誤差最小化の手法の選択

一部のパラメータだけ更新するように、更新パラメータを変更します。

事前学習の効果絶大! を確認するため、変更した最終層のテキスト分類用の全結合層のみ、パラメータを変更できるようにしてみました。

params_to_update = []

# (1) 全部 固定
for name , param in model.named_parameters():
    param.requires_grad = False
# (2) cls_headのみ更新
for name, param in model.cls_head.named_parameters():
    param.requires_grad = True
    params_to_update.append(param)
    print("更新されるパラメータ:", name) 

# 更新されるパラメータ: weight
# 更新されるパラメータ: bias

説明メモ

  • (1) param.requires_grad=Falseで全部を更新不可にします。
  • (2) 更新が必要な部分のみ、param.requires_grad=Trueとして、リストへ追加します。リストの追加を忘れても動作してしまうので注意が必要です。更新パラメータのリストはparams_to_updateという名称にしました。
誤差関数と最適化の手法選択
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(params_to_update,lr=config.learning_rate)

これまでは全部のパラメータを更新したためmodel.parameters()を使ってきました。params_to_updateを利用することで一部のパラメータだけ更新するように学習することが可能になります。

2.4 変数更新のループ

DatasetクラスとDataLoaderで学習用データを等長のIDベクトルへ変換します。バッチサイズ毎に変数を更新する方法を取ります。ちなみに学習時間は秒レベルです:palm_tree:😆:palm_tree:

# (1) DatasetとDataLoader
dataset = ClassificationDataset(data=df, tokenizer=tokenizer)
dataloader = DataLoader(
    dataset = dataset, 
    batch_size=config.batch_size,
    shuffle = True,
    collate_fn=collate_wrapper,
    pin_memory=torch.cuda.is_available(), # GPU使う時True
    drop_last = True
    )

# (2) step数で変数更新
def infinite_loader(dataloader):
    while True:
        for batch in dataloader:
            yield batch

data_iter = infinite_loader(dataloader)  # step数で計測

model.train()
max_iter = config.num_steps

# GPUが対応している場合は、bf16へ変更してflash attentionを使う
# bf16を利用し、SDPAが利用可能な最適ものを選択
use_bf16 = torch.cuda.is_available() and torch.cuda.is_bf16_supported()

# (3) 更新ループ
for step in range(max_iter):
    batch = next(data_iter)
    input_ids = batch["ids"].to(device, non_blocking=True)
    attention_mask = batch["attention_mask"].to(device, non_blocking=True)
    label    = batch["label"].to(device, non_blocking=True)
    optimizer.zero_grad()
    with torch.autocast(device_type=device.type, dtype=torch.bfloat16, enabled=use_bf16):
        y = model(input_ids, attention_mask)
        loss = criterion(y, label)
        acc = accuracy(y, label)

    loss.backward()
    torch.nn.utils.clip_grad_norm_(model.parameters(), config.max_grad_norm)
    optimizer.step()
    if (step+1)%50 == 0:
        print(f"step: {step+1}/{max_iter} | Loss: {loss.item():.4f} | acc: {acc:.4f} ")
#
# step: 50/150 | Loss: 1.1976 | acc: 0.5625 
# step: 100/150 | Loss: 0.5048 | acc: 0.9375 
# step: 150/150 | Loss: 0.3413 | acc: 0.9375

「転移学習する時って学習回数が少なくていいはず!」適当に150ステップとしました。本来的には学習データ以外での精度を見つつ更新したほうが良いと思います。

説明メモ

  • (1) データ読み込みの部分です。config.batch_size毎にIDベクトルの長さが揃っています。
  • (2) step数で管理するためのデータローダーです。epoch毎に学習されるデータの並び順もシャッフルされます。
  • (3) 更新ループ。step数管理だとやや煩雑に見えますが、基本はinfinit_loaderで供給されたデータを使って次の計算を繰り返すだけです😆
    1. y=model(...)と予測
    2. 予測yと教師データlabelとで損失や精度を計算
    3. optimizerで変数を更新

2.5 検証

2.1のデータ分割で作成したテストデータ df_test を利用して学習結果をテストしてみましょう。df_testをトークナイズしたテンソル (input_ids) とマスク(attention_mask)をmodelに入れた値 y = model(input_ids, attention_mask) が予測値となります。accuracyで平均精度を求めれば完成です。

検証
# (1) データフレームdf_testのtitle部分をトークナイズ
test_dataset = ClassificationDataset(data=df_test, tokenizer=tokenizer)

# (2) test_datasetを一括で処理
test_loader = DataLoader(
    dataset = test_dataset, 
    batch_size=len(test_dataset),
    shuffle = False,
    collate_fn=collate_wrapper,
    pin_memory=torch.cuda.is_available(), # GPU使う時True
    )

# (3) ここから検証
model.eval()
test_batch = next(iter(test_loader))
input_ids = test_batch["ids"].to(device, non_blocking=True)
attention_mask = test_batch["attention_mask"].to(device, non_blocking=True)
label = test_batch["label"].to(device, non_blocking=True)

with torch.inference_mode():
    y = model(input_ids, attention_mask)
acc = accuracy(y, label)
print(f"検証精度:{acc}")
# 検証精度:0.9038461538461539

説明メモ

  • (1) ClassificationDataset(df_test)でdf_testのtitle列をID化します。
  • (2) DataLoaderのbatch_sizeを len(test_dataset) とすることで、検証用データすべてが一括処理されます。
  • (3) next(iter(test_loader))によって検証用データをtest_batchとして一括取得します。

多少変動しますが、検証精度は、85%前後で落ち着くと思います。検証用のデータ数が少ないので、1個違いで大きく異なるのが問題なのでしょうが:scream:

個別の精度
ファッションが正解だけど、誤ってクルマと予測(2個)、スポーツと予測(1個)となっています。まあcos類似度のみの適当に作成したデータセットなのでこんな感じでOKでしょう。

分類用の全結合層のみのパラメータ更新である程度の精度が出ていることに驚きを感じます。これが事前学習の効果🤔

予測
ファッション キャリア 人生相談 クルマ スポーツ
ファッション 9 0 0 2 1
キャリア 0 12 0 0 0
人生相談 0 0 11 1 0
クルマ 0 0 0 8 0
スポーツ 0 1 0 0 7

3. 事前学習モデルを使わないタイプと比較

同じモデル構造で事前学習の重みを使わず、学習データのみ利用した場合と比較してみたいと思います。コードの説明はパス、結果のみです。:sweat_smile:

  • 事前学習モデルの重みを使わない場合の検証データでの精度は75%前後でした。

個別の精度(事前学習モデルを使わない場合)

予測
ファッション キャリア 人生相談 クルマ スポーツ
ファッション 6 1 0 5 0
キャリア 1 10 0 1 0
人生相談 0 0 11 1 0
クルマ 1 0 0 7 0
スポーツ 2 0 0 2 4

比較してみた

  • 転移学習:検証精度は85%
  • 直接学習:検証精度は75%

どうやら自作の小さなモデルと小さなコーパスであっても事前学習の効果がみられるようです。ただし、事前学習に利用していない分野のテキスト分類だと転移学習のメリットはまったくありませんよ:scream:

次回

事前学習した重みを利用して、指示チューニングを演習してみたいと考えています。

参考にした書籍とサイト

今回は論文ではなくて、解説本を中心に勉強しました。

  • Sebastian Raschka 巣籠悠輔 (監修), 株式会社クイープ 訳 (2025) 『つくりながら学ぶ!LLM 自作入門』マイナビ出版
    • GPTタイプでの分類問題を知ったきっかけがこの書籍。たしか6章だったかな?spamとhamの分類に応用していました。地味に練習問題とその解答が理解促進に役立ちました😆

目次ページ

  1. 第1回からこのパターンは変わっていません。まさにできるだけ同じコード進行です:smile:
    DatasetやDataLoaderをカスタマイズするのでデータ読み込みの順番が変わっていきます。

  2. padの有無でマスクしているのでattention_maskという変数名がややわかりにくい気もします。注意向けている部分が1、マスクしている部分が0という雰囲気でattention_maskを利用しています。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?