- セルフアテンションは系列中の各要素が他のすべての要素と直接相互作用することを可能にし[1]、リカレントニューラルネットワークの長距離依存関係ボトルネックを完全に解決——計算は完全に並列化可能
- Transformerアーキテクチャ[1]はNLP(BERT[3]、GPT[4])とコンピュータビジョン(ViT[6]、Swin[7])の統一的基盤アーキテクチャとなった
- FlashAttention[11]、Linformer[9]などの技術がセルフアテンションのO(n²)ボトルネックをほぼ線形の計算量に改善——数百万トークンのコンテキストウィンドウを実現
- 本記事にはGoogle Colab実践ラボ2本を収録:Transformerテキスト感情分類(セルフ実装マルチヘッドアテンション)とViT画像分類(アテンションヒートマップ可視化)
1. アテンションからセルフアテンションへ:パラダイムの革命
2017年、Googleチームは「Attention Is All You Need」[1]と題する論文で一見大胆な主張を行った:再帰と畳み込みを完全に放棄し、アテンションのみで最も強力な系列モデルを構築する。 この論文から生まれたTransformerアーキテクチャは、わずか数年で人工知能の全領域を完全に変革した。
Transformer以前にも、Bahdanauらの研究[2]でアテンション機構はすでに有望性を示していた——RNNの補助モジュールとして、デコーダーがエンコーダー出力の最も関連性の高い部分に「注目」するのを助けていた。しかしVaswaniらはさらに踏み込んだ:系列中の各要素が他のすべての要素と直接相互作用し、RNNのステップバイステップの伝搬を不要にした。これがセルフアテンションの本質である。
この移行の意義:
| 特性 | RNN | セルフアテンション |
|---|---|---|
| 長距離依存関係 | 先頭と末尾の接続にO(n)ステップ | 任意の位置間をO(1)で直接接続 |
| 並列化 | ステップごとに逐次計算、並列不可 | 全位置を同時に計算 |
| 計算量 | O(n · d²) 逐次 | O(n² · d) グローバル |
| メモリボトルネック | 固定サイズの隠れ状態 | 動的アテンション重み行列 |
2. Scaled Dot-Product Attention:数学的コア
セルフアテンションの計算は3つの行列演算に集約できる:Query、Key、Value。各入力トークンが3つのベクトルに射影され、それらを使って内積により相互の「関連性スコア」を計算する。
Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V
ここで:
Q = X · W_Q (クエリ行列、形状 [n, d_k])
K = X · W_K (キー行列、形状 [n, d_k])
V = X · W_V (バリュー行列、形状 [n, d_v])
d_k = キーベクトルの次元
√d_k = スケーリング係数、大きな内積によるsoftmaxの飽和を防止
直感的理解:
- Q · K^T:すべてのトークンペア間の「類似度スコア」を計算——n×nのアテンション行列
- / √d_k:スケーリング係数。d_kが大きいと内積の分散も大きくなり、softmaxが極端な0/1分布を出力する(勾配消失)。√d_kで割ることで分散を1に安定化[1]
- softmax:スコアを確率分布に変換——各トークンが「他のトークンにどれだけ注目すべきか」を表現
- · V:アテンション重み付きのバリューベクトルの加重和を計算——コンテキスト認識型の新しい表現を生成
例:「The cat sat on the mat because it was tired」という文で、「it」のクエリベクトルは他のすべてのトークンのキーベクトルとの内積を計算する。理想的には、「cat」のキーが「it」のクエリと最高スコアを出し、モデルが「it」が「cat」を指すことを正しく解決できる。
3. マルチヘッドアテンション:並列的な多角的観察
1組のQ、K、Vでは1種類の関係性しか捉えられない。しかし言語における関係性は多面的である——構文依存、意味的類似性、共参照、時制の一貫性など。Transformerの解決策がマルチヘッドアテンションである:
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) · W_O
ここで head_i = Attention(Q · W_Q^i, K · W_K^i, V · W_V^i)
d_model=512、h=8 の場合:
各ヘッドの d_k = d_v = 512 / 8 = 64
8つのヘッドが独立して64次元のアテンションを実行
連結後、512次元に再射影
Voitaらの研究[12]では、異なるアテンションヘッドが実際に異なる「役割」を学習していることが判明した:位置関係に注目するもの、構文構造を追跡するもの、稀少語を処理するもの。興味深いことに、Michelら[17]は多くのヘッドをほぼ性能低下なく剪定できることを発見した——マルチヘッド機構が有益な冗長性と正則化を提供していることを示唆している。
4. 位置エンコーディング:アテンションに「順序」を教える
セルフアテンションは本質的に順列不変である——入力順序を入れ替えると出力も対応して入れ替わるが、各トークンの表現自体は変化しない。つまり「犬が人を噛む」と「人が犬を噛む」が純粋なセルフアテンションでは同一に見える。そのためTransformerには順序情報を注入するための追加の位置エンコーディングが必要となる。
オリジナルのTransformerは正弦波位置エンコーディングを使用[1]:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
ここで pos はトークン位置、i は次元インデックス
近年、位置エンコーディングは大きく進化している:
| 手法 | コアアイデア | 利点 | 代表的モデル |
|---|---|---|---|
| 正弦波[1] | 固定三角関数 | 学習不要 | オリジナルTransformer |
| 学習可能 | 位置ごとの学習可能ベクトル | 適応的 | BERT、GPT |
| RoPE[13] | 相対位置を回転行列でエンコード | 長さ外挿、相対認識 | LLaMA、PaLM |
| ALiBi[14] | アテンションスコアに線形距離バイアス | パラメータゼロ、強い外挿性 | BLOOM、MPT |
RoPE[13]は現在の主要大規模言語モデルの標準的な選択肢となっている。ベクトル空間における回転として位置情報をエンコードし、2つのトークン間のアテンションスコアはその相対距離のみに依存し、優れた長さ外挿能力を持つ。
5. 完全なTransformerアーキテクチャ
完全なTransformerはエンコーダーとデコーダーの構成要素からなるが、用途に応じて異なる組み合わせが選択される:
| アーキテクチャ種別 | 構造 | 代表的モデル | 典型的タスク |
|---|---|---|---|
| エンコーダーのみ | 双方向セルフアテンション | BERT[3] | 分類、固有表現認識、質問応答 |
| デコーダーのみ | 因果マスク付きセルフアテンション | GPT[4][5] | テキスト生成、対話 |
| エンコーダー・デコーダー | エンコーダー + クロスアテンション + デコーダー | T5、BART | 翻訳、要約 |
各Transformer層(ブロック)は2つのサブ層を含み、それぞれ残差接続とレイヤー正規化を持つ:
Transformer Block:
1. マルチヘッドセルフアテンション
→ LayerNorm(x + MultiHead(x, x, x))
2. フィードフォワードネットワーク(FFN)
→ LayerNorm(x + FFN(x))
→ FFN(x) = max(0, x·W_1 + b_1) · W_2 + b_2
デコーダーにはさらに以下が含まれる:
- 因果マスク:自己回帰生成のために未来の位置をマスク
- クロスアテンション:デコーダーからのQuery、エンコーダーからのKey/Value
6. BERTとGPT:分岐する二つの道
Transformerアーキテクチャは2つの主要な事前学習パラダイムを生み出した。セルフアテンションの「可視範囲」が両者を区別する:
BERT(双方向)[3]:エンコーダーアーキテクチャを使用し、各トークンは系列内のすべてのトークン(前後のコンテキスト)を参照可能。マスク言語モデリング(MLM)で事前学習——トークンの15%をランダムにマスクし、マスクされた単語をモデルに予測させる。この双方向コンテキストにより、BERTは理解指向のタスクに特に適している。
GPT(自己回帰)[4]:デコーダーアーキテクチャを使用し、各トークンはそれ以前のトークンのみ参照可能(因果マスキング)。次トークン予測で事前学習。GPT-3[5]はパラメータを1750億にスケールし、ファインチューニングなしでプロンプトだけで翻訳、質問応答、コード生成などを実行する顕著なfew-shot学習能力を実証した。
スケールの増大に伴い、Kaplanら[15]は有名なスケーリング則を発見した——モデル性能(損失)とパラメータ数、データ量、計算予算の間の安定したべき乗則関係。さらに驚くべきことに、Weiら[16]は創発能力現象を記録した:特定の能力(思考連鎖推論など)が小規模モデルでは完全に欠如しているが、モデルが特定のスケール閾値に達すると突然出現する。
7. Vision Transformer:セルフアテンションがビジョンを征服
長らくコンピュータビジョンは畳み込みニューラルネットワークの独壇場であった。2021年、Dosovitskiyら[6]はVision Transformer(ViT)を提案し、純粋なセルフアテンションアーキテクチャが大規模データ上で最強のCNNに匹敵するか、それを上回ることを証明した。
ViTのコアアイデアは驚くほど簡潔である:
ViT パイプライン:
1. 224×224画像を16×16パッチに分割 → 196パッチ
2. 各パッチを768次元ベクトルに平坦化(16×16×3 = 768)
3. 学習可能な位置エンコーディング + [CLS]トークンを追加
4. 標準的なTransformerエンコーダーに入力
5. [CLS]トークンの出力で分類
「画像は16×16の単語で表現できる」——画像パッチを系列中のトークンとして扱う
ViTの成功はビジョンTransformerの爆発的な広がりを引き起こした。Swin Transformer[7]はシフトウィンドウ戦略によりアテンション計算量をO(n²)からO(n)に削減し、汎用的なビジュアルバックボーンとなった。DETR[8]はTransformerによるエンドツーエンドの物体検出を実現し、従来のアンカーボックスとNMS後処理を排除した。
8. 効率性のブレークスルー:セルフアテンションのO(n²)障壁を打破
セルフアテンションのO(n²)の時間・メモリ計算量が最大のボトルネックである。長さnの系列に対して、アテンション行列はn²の計算と記憶を必要とする。n = 100,000の場合、10^10回の演算が必要となり、エンジニアリング的に受容不可能である。
学術界と産業界は複数のブレークスルー戦略を提案してきた:
| 手法 | コア戦略 | 計算量 | 精密性 |
|---|---|---|---|
| Linformer[9] | Key/Valueの低ランク射影 | O(n) | 近似 |
| Performer[10] | ランダム特徴マッピング(FAVOR+) | O(n) | 近似 |
| FlashAttention[11] | IO認識型タイリング + カーネル融合 | O(n²)だが2-4倍高速 | 精密 |
| スライディングウィンドウ | ローカルウィンドウのみに注目 | O(n · w) | 局所的に精密 |
| スパースアテンション | 疎なアテンションパターン | O(n√n) | 近似 |
FlashAttention[11]の貢献は特に重要である:数学を変更しない——計算結果は標準アテンションと完全に同一——が、巧みなGPUメモリ管理(タイル計算、HBM ↔ SRAM間の往復回避)により2-4倍の高速化を達成した。現代のLLM学習における標準コンポーネントとなっている。
9. 実践ラボ1:ゼロからTransformerテキスト分類器を構築する(Google Colab)
以下の実験ではマルチヘッドセルフアテンションをゼロから実装し、IMDb映画レビュー感情分類のためのミニTransformerを構築し、アテンション重みを可視化する。
# ============================================================
# ラボ1:ゼロから構築するTransformer——IMDb感情分類 + アテンション可視化
# 環境:Google Colab(GPU)
# ============================================================
# --- 0. インストール ---
!pip install -q torchtext datasets
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
import numpy as np
import matplotlib.pyplot as plt
from datasets import load_dataset
from torch.utils.data import DataLoader
from collections import Counter
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Device: {device}")
# --- 1. データ準備 ---
dataset = load_dataset("imdb")
train_data = dataset["train"].shuffle(seed=42).select(range(10000))
test_data = dataset["test"].shuffle(seed=42).select(range(2000))
# シンプルなトークナイザー
def simple_tokenizer(text):
return text.lower().split()
# 語彙構築
counter = Counter()
for example in train_data:
counter.update(simple_tokenizer(example["text"]))
vocab = {"<pad>": 0, "<unk>": 1}
for word, count in counter.most_common(20000):
if count >= 3:
vocab[word] = len(vocab)
vocab_size = len(vocab)
print(f"Vocab size: {vocab_size}")
MAX_LEN = 256
def encode(text):
tokens = simple_tokenizer(text)[:MAX_LEN]
ids = [vocab.get(t, 1) for t in tokens]
return ids
def collate_fn(batch):
texts = [encode(ex["text"]) for ex in batch]
labels = torch.tensor([ex["label"] for ex in batch])
max_len = min(max(len(t) for t in texts), MAX_LEN)
padded = torch.zeros(len(texts), max_len, dtype=torch.long)
for i, t in enumerate(texts):
padded[i, :len(t)] = torch.tensor(t)
return padded, labels
train_loader = DataLoader(train_data, batch_size=32, shuffle=True, collate_fn=collate_fn)
test_loader = DataLoader(test_data, batch_size=64, shuffle=False, collate_fn=collate_fn)
# --- 2. 自作マルチヘッドセルフアテンション ---
class MultiHeadSelfAttention(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
assert d_model % n_heads == 0
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads
self.W_Q = nn.Linear(d_model, d_model)
self.W_K = nn.Linear(d_model, d_model)
self.W_V = nn.Linear(d_model, d_model)
self.W_O = nn.Linear(d_model, d_model)
self.attn_weights = None # 可視化用にアテンション重みを保存
def forward(self, x, mask=None):
B, N, _ = x.shape
# 射影 + ヘッド分割:[B, N, d_model] → [B, h, N, d_k]
Q = self.W_Q(x).view(B, N, self.n_heads, self.d_k).transpose(1, 2)
K = self.W_K(x).view(B, N, self.n_heads, self.d_k).transpose(1, 2)
V = self.W_V(x).view(B, N, self.n_heads, self.d_k).transpose(1, 2)
# Scaled Dot-Product Attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn = F.softmax(scores, dim=-1)
self.attn_weights = attn.detach()
out = torch.matmul(attn, V) # [B, h, N, d_k]
out = out.transpose(1, 2).contiguous().view(B, N, self.d_model)
return self.W_O(out)
# --- 3. Transformerエンコーダーブロック ---
class TransformerBlock(nn.Module):
def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
super().__init__()
self.attn = MultiHeadSelfAttention(d_model, n_heads)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout),
)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
x = self.norm1(x + self.dropout(self.attn(x, mask)))
x = self.norm2(x + self.ffn(x))
return x
# --- 4. 完全な分類モデル ---
class TransformerClassifier(nn.Module):
def __init__(self, vocab_size, d_model=128, n_heads=4,
n_layers=3, d_ff=256, max_len=256, n_classes=2, dropout=0.1):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model, padding_idx=0)
self.pos_encoding = nn.Embedding(max_len, d_model)
self.blocks = nn.ModuleList([
TransformerBlock(d_model, n_heads, d_ff, dropout)
for _ in range(n_layers)
])
self.classifier = nn.Linear(d_model, n_classes)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
B, N = x.shape
positions = torch.arange(N, device=x.device).unsqueeze(0).expand(B, N)
mask = (x != 0).unsqueeze(1).unsqueeze(2) # [B, 1, 1, N]
x = self.dropout(self.embedding(x) + self.pos_encoding(positions))
for block in self.blocks:
x = block(x, mask)
# グローバル平均プーリング(パディングを無視)
mask_float = (x != 0).any(dim=-1, keepdim=True).float()
x = (x * mask_float).sum(dim=1) / mask_float.sum(dim=1).clamp(min=1)
return self.classifier(x)
# --- 5. 学習 ---
model = TransformerClassifier(vocab_size).to(device)
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.01)
criterion = nn.CrossEntropyLoss()
print(f"Parameters: {sum(p.numel() for p in model.parameters()):,}")
for epoch in range(6):
model.train()
total_loss, correct, total = 0, 0, 0
for xb, yb in train_loader:
xb, yb = xb.to(device), yb.to(device)
logits = model(xb)
loss = criterion(logits, yb)
optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
total_loss += loss.item() * xb.size(0)
correct += (logits.argmax(1) == yb).sum().item()
total += xb.size(0)
print(f"Epoch {epoch+1}: loss={total_loss/total:.4f}, acc={correct/total:.4f}")
# --- 6. テスト ---
model.eval()
correct, total = 0, 0
with torch.no_grad():
for xb, yb in test_loader:
xb, yb = xb.to(device), yb.to(device)
logits = model(xb)
correct += (logits.argmax(1) == yb).sum().item()
total += xb.size(0)
print(f"\nTest Accuracy: {correct/total:.4f}")
# --- 7. アテンション可視化 ---
def visualize_attention(text, model):
model.eval()
tokens = simple_tokenizer(text)[:50]
ids = torch.tensor([[vocab.get(t, 1) for t in tokens]]).to(device)
with torch.no_grad():
_ = model(ids)
# 最終層のアテンション重みを取得
attn = model.blocks[-1].attn.attn_weights[0] # [h, N, N]
fig, axes = plt.subplots(1, 4, figsize=(20, 5))
for i in range(4):
ax = axes[i]
im = ax.imshow(attn[i, :len(tokens), :len(tokens)].cpu(),
cmap='Blues', aspect='auto')
ax.set_xticks(range(len(tokens)))
ax.set_yticks(range(len(tokens)))
ax.set_xticklabels(tokens, rotation=90, fontsize=7)
ax.set_yticklabels(tokens, fontsize=7)
ax.set_title(f'Head {i+1}')
plt.colorbar(im, ax=ax, fraction=0.046)
plt.suptitle('Multi-Head Self-Attention Weights (Last Layer)', fontsize=14)
plt.tight_layout()
plt.show()
# 可視化の例
visualize_attention("this movie was absolutely wonderful and the acting was superb", model)
visualize_attention("terrible film with awful dialogue and boring plot", model)
print("Lab 1 Complete!")
10. 実践ラボ2:Vision Transformer画像分類 + アテンションヒートマップ(Google Colab)
以下の実験では、事前学習済みViTモデルを画像分類に使用し、セルフアテンション重みを抽出してアテンションヒートマップを生成——モデルが「何を見ているか」を直感的に示す。
# ============================================================
# ラボ2:Vision Transformer——画像分類 + アテンションヒートマップ可視化
# 環境:Google Colab(GPUまたはCPU)
# ============================================================
# --- 0. インストール ---
!pip install -q transformers timm pillow matplotlib
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
from torchvision import transforms
from transformers import ViTForImageClassification, ViTFeatureExtractor
import requests
from io import BytesIO
import warnings
warnings.filterwarnings('ignore')
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Device: {device}")
# --- 1. 事前学習済みViTの読み込み ---
model_name = "google/vit-base-patch16-224"
model = ViTForImageClassification.from_pretrained(
model_name, output_attentions=True
).to(device).eval()
feature_extractor = ViTFeatureExtractor.from_pretrained(model_name)
print(f"Model: {model_name}")
print(f"Parameters: {sum(p.numel() for p in model.parameters()):,}")
print(f"Patch size: 16x16, Image size: 224x224 → 196 patches + 1 [CLS]")
# --- 2. 画像の読み込みと前処理 ---
def load_image(url):
response = requests.get(url)
img = Image.open(BytesIO(response.content)).convert("RGB")
return img
# クラシックなテスト画像
urls = {
"Golden Retriever": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Golden_Retriever_Dukedestination.jpg/800px-Golden_Retriever_Dukedestination.jpg",
"Tabby Cat": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/800px-Cat_November_2010-1a.jpg",
"Bald Eagle": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/1a/About_to_Launch_%2826075320352%29.jpg/800px-About_to_Launch_%2826075320352%29.jpg",
}
images = {}
for name, url in urls.items():
try:
images[name] = load_image(url)
print(f"Loaded: {name} ({images[name].size})")
except Exception as e:
print(f"Failed to load {name}: {e}")
# --- 3. 推論 + アテンション抽出 ---
def predict_with_attention(img, model, feature_extractor):
inputs = feature_extractor(images=img, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model(**inputs)
logits = outputs.logits
probs = F.softmax(logits, dim=-1)
top5 = torch.topk(probs, 5)
# アテンション重み:各層の [B, heads, N, N] のリスト
attentions = outputs.attentions # 12層
return top5, attentions
# --- 4. アテンションヒートマップ生成 ---
def get_attention_map(attentions, layer=-1):
"""指定層の[CLS]トークンアテンションを抽出 → 14x14ヒートマップ"""
attn = attentions[layer][0] # [heads, N, N]
attn_avg = attn.mean(dim=0) # [N, N]
# [CLS]トークン(位置0)から全パッチへのアテンション
cls_attn = attn_avg[0, 1:] # [CLS]自身を除外、[196]
cls_attn = cls_attn.reshape(14, 14).cpu().numpy()
# 正規化
cls_attn = (cls_attn - cls_attn.min()) / (cls_attn.max() - cls_attn.min() + 1e-8)
return cls_attn
def get_rollout_attention(attentions):
"""Attention Rollout:全層のアテンションを累積"""
result = torch.eye(attentions[0].size(-1)).to(device)
for attn_layer in attentions:
attn = attn_layer[0].mean(dim=0) # [N, N] 全ヘッドの平均
attn = attn + torch.eye(attn.size(0)).to(device) # 残差接続
attn = attn / attn.sum(dim=-1, keepdim=True) # 正規化
result = torch.matmul(attn, result)
cls_attn = result[0, 1:].reshape(14, 14).cpu().numpy()
cls_attn = (cls_attn - cls_attn.min()) / (cls_attn.max() - cls_attn.min() + 1e-8)
return cls_attn
# --- 5. 可視化 ---
def visualize_vit(name, img, model, feature_extractor):
top5, attentions = predict_with_attention(img, model, feature_extractor)
print(f"\n{'='*50}")
print(f"Image: {name}")
print(f"{'='*50}")
for i in range(5):
idx = top5.indices[0][i].item()
prob = top5.values[0][i].item()
label = model.config.id2label[idx]
print(f" {i+1}. {label}: {prob:.4f}")
attn_first = get_attention_map(attentions, layer=0)
attn_mid = get_attention_map(attentions, layer=5)
attn_last = get_attention_map(attentions, layer=-1)
attn_rollout = get_rollout_attention(attentions)
img_np = np.array(img.resize((224, 224)))
fig, axes = plt.subplots(1, 5, figsize=(25, 5))
axes[0].imshow(img_np)
axes[0].set_title(f"Original\n{model.config.id2label[top5.indices[0][0].item()]}", fontsize=12)
axes[0].axis('off')
titles = ['Layer 1 Attention', 'Layer 6 Attention',
'Layer 12 Attention', 'Attention Rollout']
maps = [attn_first, attn_mid, attn_last, attn_rollout]
for i, (title, attn_map) in enumerate(zip(titles, maps)):
ax = axes[i + 1]
ax.imshow(img_np)
attn_resized = np.array(Image.fromarray(
(attn_map * 255).astype(np.uint8)
).resize((224, 224), Image.BICUBIC)) / 255.0
ax.imshow(attn_resized, alpha=0.6, cmap='jet')
ax.set_title(title, fontsize=12)
ax.axis('off')
plt.suptitle(f'ViT Attention Maps — {name}', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# --- 6. 可視化の実行 ---
for name, img in images.items():
visualize_vit(name, img, model, feature_extractor)
# --- 7. マルチヘッドアテンション比較 ---
def visualize_heads(name, img, model, feature_extractor, layer=-1):
"""指定層の各ヘッドのアテンションパターンを可視化"""
inputs = feature_extractor(images=img, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model(**inputs, output_attentions=True)
attn = outputs.attentions[layer][0] # [12, 197, 197]
img_np = np.array(img.resize((224, 224)))
fig, axes = plt.subplots(2, 6, figsize=(24, 8))
for head_idx in range(12):
ax = axes[head_idx // 6][head_idx % 6]
head_attn = attn[head_idx, 0, 1:].reshape(14, 14).cpu().numpy()
head_attn = (head_attn - head_attn.min()) / (head_attn.max() - head_attn.min() + 1e-8)
attn_resized = np.array(Image.fromarray(
(head_attn * 255).astype(np.uint8)
).resize((224, 224), Image.BICUBIC)) / 255.0
ax.imshow(img_np)
ax.imshow(attn_resized, alpha=0.6, cmap='inferno')
ax.set_title(f'Head {head_idx+1}', fontsize=10)
ax.axis('off')
plt.suptitle(f'12 Attention Heads (Last Layer) — {name}', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 最初の画像で12ヘッドを可視化
first_name = list(images.keys())[0]
visualize_heads(first_name, images[first_name], model, feature_extractor)
print("\nLab 2 Complete!")
11. 意思決定フレームワーク:企業がアテンションアーキテクチャを選択する方法
| シナリオ | 推奨アーキテクチャ | 根拠 |
|---|---|---|
| テキスト分類 / 固有表現認識 | BERT-baseファインチューニング | 双方向コンテキスト、分類タスクに最適 |
| テキスト生成 / 対話 | GPTシリーズ / LLaMA | 自己回帰生成、因果アテンション |
| 画像分類 | ViT / Swin Transformer | 大規模データでCNNを凌駕 |
| 物体検出 | DETR / Swin + FPN | エンドツーエンド、アンカーボックス不要 |
| 超長系列 | FlashAttention + RoPE | 128K+トークンのコンテキスト |
| エッジデバイス | 蒸留ViT / MobileViT | 軽量セルフアテンション |
| マルチモーダル | クロスアテンションTransformer | 統一的な画像-テキスト表現 |
12. 結論と展望
セルフアテンションは過去10年間で人工知能における最も重要なブレークスルーの一つである。2017年の「Attention Is All You Need」[1]から今日のGPT-4、Claude、Geminiまで、セルフアテンションは事実上すべてのフロンティアAIシステムの計算コアとなっている。
この革命を振り返る:
- NLPの統一:BERT[3]からGPT[5]まで、Transformerは理解と生成を統一した
- ビジョンでのブレークスルー:ViT[6]はセルフアテンションが系列だけでなく二次元構造にも有効であることを証明した
- 効率性の飛躍:FlashAttention[11]とスパースアテンションにより、百万トークンのコンテキストウィンドウが現実のものとなった
- スケールの魔法:スケーリング則[15]と創発能力[16]により「大きいほど質的変化」の原理が明らかになった
今後、セルフアテンションの進化方向には以下が含まれる:サブ線形計算量の代替としての状態空間モデル(SSM / Mamba)、スパースに活性化される超大規模モデルのためのMixture of Experts(MoE)、テキスト・画像・音声・動画を同一アテンション空間で融合するマルチモーダル統一アーキテクチャ。具体的な形態がどう進化しようとも、「各要素がすべての関連情報に動的に注目する」というコアアイデアは、今後10年のAIをリードし続けるだろう。



