Transformer個人メモ

に公開

🧠 Transformerとは

  • 2017年の論文 "Attention Is All You Need" によって提案
  • RNNを使わずに自己注意(Self-Attention)で系列データを処理
  • 現在の多くのLLMやBERTなどの基盤

⚙️ モデル構造の概要

  • Encoder–Decoder構造
    • Encoder: 入力文を埋め込みベクトルとして処理
    • Decoder: 出力文を生成
  • 各層に Multi-Head Attention と Feed Forward Network を含む

🔍 Self-Attention(自己注意)とは

Self-Attentionは、「文の中でどの単語がどの単語に注目すべきか」を学習する仕組みです。

RNNのように単語を順番に処理するのではなく、文全体を一度に見渡して、
それぞれの単語が他の単語との関係を考えながら自分の表現を更新します。

たとえば「The animal didn't cross the street because it was too tired.」という文では、
「it」が「animal」を指すことを理解する必要があります。
Self-Attentionでは、「it」と「animal」の関係性を自然に学習できます。

仕組みとしては、各単語を3種類のベクトル(Query、Key、Value)に変換します。
Queryは「今注目したいこと」、Keyは「どんな情報を持っているか」、Valueは「実際の情報」です。
QueryとKeyの類似度を計算して、「どの単語が重要か(重み)」を求め、その重みを使ってValueをまとめます。

結果的に、各単語は文脈を考慮した新しいベクトルに変換されます。
これがTransformerが「文全体の関係性」を効率的に捉えられる理由です。


# coding: UTF-8
""" Transformer(分類) """
# ================================================================================================================================
""" インポート """
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import TensorDataset, DataLoader
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix

# ================================================================================================================================
""" TransformerModel """
class TransformerModel(nn.Module):
    """ コンストラクタの定義 """
    def __init__(self, input_dim: int, d_model: int, nhead: int, dropout: float, num_layers: int, num_classes: int) -> None:
        super().__init__()
        # 入力層
        self.embedding = nn.Linear(
            in_features=input_dim,
            out_features=d_model,
        )
        # EncoderLayer
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model,
            nhead=nhead,
            dim_feedforward=(d_model * 2),
            dropout=dropout,
            activation="gelu",
            batch_first=True,
            norm_first=True,
        )
        # Encoder
        self.encoder = nn.TransformerEncoder(
            encoder_layer=encoder_layer,
            num_layers=num_layers,
        )
        # 分類ヘッド
        self.classifier = nn.Sequential(
            nn.LayerNorm(d_model),
            nn.Linear(d_model, d_model // 2),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(d_model // 2, num_classes),
        )


    """ 順伝播 """
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = x.unsqueeze(1)
        x = self.embedding(x)
        x = self.encoder(x)
        x = x.mean(dim=1)
        output = self.classifier(x)
        return output

# ================================================================================================================================
if __name__ == "__main__":
    ## 設定変数
    test_size: float = 0.2
    batch_size: int = 64
    d_model: int = 128
    nhead: int = 2
    dropout: float = 0.3
    num_layers: int = 4
    learning_rate: float = 0.0001
    weight_decay: float = 0.001
    t_max: int = 50
    max_epoch: int = 100

    ## 実行処理
    # データの読み込み
    cancer = load_breast_cancer()
    cancer_data = pd.DataFrame(cancer.data, columns=cancer.feature_names)
    cancer_target = cancer.target

    # 標準化
    scaler = StandardScaler()
    scaled_data = scaler.fit_transform(cancer_data)

    # データの分割
    x_train, x_test, y_train, y_test = train_test_split(
        scaled_data,
        cancer_target,
        test_size=test_size,
        random_state=42,
    )

    # TensorDatasetの生成
    train_dataset = TensorDataset(
        torch.tensor(data=x_train, dtype=torch.float32),
        torch.tensor(data=y_train, dtype=torch.long),
    )
    test_dataset = TensorDataset(
        torch.tensor(data=x_test, dtype=torch.float32),
        torch.tensor(data=y_test, dtype=torch.long),
    )

    # DataLoader
    train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
    test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)

    # 実行デバイスの定義
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    # TransformerModelの生成
    model = TransformerModel(
        input_dim=len(cancer_data.columns),
        d_model=d_model,
        nhead=nhead,
        dropout=dropout,
        num_layers=num_layers,
        num_classes=len(set(cancer_target)),
    ).to(device=device)

    # 損失関数の定義
    criterion = nn.CrossEntropyLoss()

    # 最適化手法の定義
    optimizer = optim.AdamW(
        params=model.parameters(),
        lr=learning_rate,
        weight_decay=weight_decay,
    )
    # 学習率の調整
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer=optimizer, T_max=t_max)

    # 学習
    for epoch in range(max_epoch):
        # 変数の初期化
        total_loss: float = 0.0
        total_acc: float = 0.0
        model.train()

        for batch in train_loader:
            # バッチをデバイスに転送
            x_batch, y_batch = batch
            x_batch, y_batch = x_batch.to(device), y_batch.to(device)
            # 勾配の初期化
            optimizer.zero_grad()
            # 順伝播
            logit = model(x_batch)
            # 損失
            loss = criterion(logit, y_batch)
            # 逆伝播
            loss.backward()
            # 重みの更新
            optimizer.step()

            # Loss, Accuracy
            total_loss += loss.item()
            y_label = torch.argmax(logit, dim=1)
            accuracy = (y_label == y_batch).float().sum().item()
            total_acc += (y_label == y_batch).float().mean().item()

        # 学習率の更新
        scheduler.step()
        if (epoch + 1) % 10 == 0:
            print(f"Epoch: {epoch + 1} / {max_epoch}, Loss: {total_loss / len(train_loader):.4f}, Accuracy: {total_acc / len(train_loader):.4f}")

    # 予測
    model.eval()
    with torch.no_grad():
        # 配列の初期化
        preds, targets = [], []
        for batch in test_loader:
            # バッチをデバイスに転送
            x_batch, y_batch = batch
            x_batch, y_batch = x_batch.to(device), y_batch.to(device)
            # 予測
            y_pred = model(x_batch)
            y_pred = torch.argmax(y_pred, dim=1)
            # 配列に追加
            preds.append(y_pred)
            targets.append(y_batch)

    # 結合してNumpy配列にする
    preds = torch.cat(preds).numpy()
    targets = torch.cat(targets).numpy()
    # Accuracy
    accuracy = (preds == targets).sum().item() / len(targets)

    # 評価
    print("予測結果  : \n", preds)
    print("正解ラベル: \n", targets)
    print(f"予測Accuracy: {accuracy:.4f}")
    # その他評価指標
    print(confusion_matrix(targets, preds))
    print(classification_report(targets, preds, target_names=cancer.target_names))


Discussion