📋 このステップで学ぶこと
Query、Key、Valueの概念と直感的理解
Scaled Dot-Product Attentionの計算手順(4ステップ)
なぜ√d_kでスケーリングするのか
Multi-Head Attention(複数ヘッド)の必要性
PyTorchでのSelf-Attention実装
Attention Weightの可視化
練習問題: 5問
💻 実行環境について
このステップのコードはGoogle Colab で実行できます。
PyTorchは最初から入っているので、追加インストールは不要です。
🔑 1. Query、Key、Valueの概念
Self-Attentionでは、各単語をQuery(質問) 、Key(鍵) 、
Value(値) の3つの表現に変換して計算します。
この3つの役割分担を理解することが、Self-Attention理解の第一歩です。
1-1. 検索システムで理解するQ, K, V
💡 YouTubeの検索に例えると…
Q, K, Vの概念は、検索システムの仕組みに似ています。
【YouTubeで「猫の動画」を検索する場合】
■ ステップ1: あなたが検索ボックスに入力
「猫の動画」 ← これが Query(質問)
■ ステップ2: YouTubeには各動画にタグが付いている
動画A: “犬”, “散歩”, “かわいい”
動画B: “猫”, “かわいい”, “癒し” ← マッチしそう!
動画C: “料理”, “レシピ”, “和食”
動画D: “猫”, “面白い”, “ハプニング” ← マッチしそう!
これらのタグが Key(鍵)
■ ステップ3: QueryとKeyを比較してマッチ度を計算
Query “猫” と Key “猫” → 高いスコア(0.9)
Query “猫” と Key “犬” → 低いスコア(0.1)
Query “猫” と Key “料理” → 低いスコア(0.0)
■ ステップ4: マッチした動画の内容を返す
動画Bの内容、動画Dの内容 ← これが Value(値)
【Self-Attentionも同じ仕組み!】
各単語が Query, Key, Value を持つ
Query: 「私は何を探しているか」
Key: 「私はこういう特徴を持つ」
Value: 「私の実際の内容情報」
1-2. 文中での具体例
【文: “The cat sat on the mat” での動作】
各単語がQuery、Key、Valueを持つ:
単語 Query(探しているもの) Key(自分の特徴) Value(内容)
———————————————————————-
The 冠詞が修飾する名詞は? 冠詞である [0.1, 0.2, …]
cat catに関連する動詞は? 名詞・動物である [0.8, 0.3, …]
sat satの主語は? 動詞・過去形である [0.4, 0.7, …]
on onの後の名詞は? 前置詞である [0.2, 0.1, …]
the 冠詞が修飾する名詞は? 冠詞である [0.1, 0.2, …]
mat matを修飾する前置詞は? 名詞・場所である [0.6, 0.5, …]
【”cat”が他の単語を見る場合】
“cat”のQuery → 全単語のKeyと比較:
cat(Q) × The(K) = 0.1 (冠詞、あまり関係ない)
cat(Q) × cat(K) = 0.9 (自分自身、強く関連)
cat(Q) × sat(K) = 0.8 (動詞、主語-動詞の関係)
cat(Q) × on(K) = 0.2 (前置詞、間接的な関係)
cat(Q) × the(K) = 0.1 (冠詞、あまり関係ない)
cat(Q) × mat(K) = 0.4 (場所、catがいる場所)
→ Softmaxで正規化 → [0.02, 0.35, 0.32, 0.05, 0.02, 0.24]
→ この重みでValueを加重平均
“cat”の新しい表現 = 0.02×V_The + 0.35×V_cat + 0.32×V_sat + …
= “cat”と”sat”の情報を強く含む表現
1-3. Q, K, Vの計算方法
【Q, K, Vの作り方】
入力: X(各単語の埋め込みベクトル)
例: “The cat sat” (3単語)
X = [[x_The], ← 512次元のベクトル
[x_cat], ← 512次元のベクトル
[x_sat]] ← 512次元のベクトル
X の形状: (3, 512) = (単語数, 埋め込み次元)
【学習可能な重み行列】
W_Q: Query用の重み行列 (512, 64)
W_K: Key用の重み行列 (512, 64)
W_V: Value用の重み行列 (512, 64)
※ 64はd_k(Queryの次元)
【計算】
Q = X × W_Q → (3, 64) 各単語の Query
K = X × W_K → (3, 64) 各単語の Key
V = X × W_V → (3, 64) 各単語の Value
【なぜ3つに分けるのか?】
分けない場合:
・同じ情報で「探す」「見せる」「渡す」をする
・柔軟性がない
分ける場合:
・Query: 「自分が何を探しているか」を学習
・Key: 「他の単語に何を見せるか」を学習
・Value: 「注目されたとき何を渡すか」を学習
・それぞれ独立に最適化できる
💡 Q, K, Vの役割まとめ
Query(Q): 「私は何を探しているか」- 他の単語への質問
Key(K): 「私はこういう特徴です」- 他の単語に見せる情報
Value(V): 「私の内容はこれです」- 注目されたときに渡す情報
📐 2. Scaled Dot-Product Attention
Self-Attentionの計算はScaled Dot-Product Attention と呼ばれます。
この計算を4つのステップに分けて理解しましょう。
2-1. 公式の全体像
🎯 Attentionの公式
Attention(Q, K, V) = softmax(QKT / √d_k) × V
この公式を4つのステップに分解して理解します。
【公式の分解】
Attention(Q, K, V) = softmax(QK^T / √d_k) × V
~~~~~~ ~~~~ ~~~~~ ~
③ ① ② ④
① QK^T : QueryとKeyの内積(関連度スコア)
② / √d_k : スケーリング(値を適切な範囲に)
③ softmax : 確率分布に変換(合計1.0に)
④ × V : Valueを重み付けして集約
【各ステップの目的】
ステップ1: どの単語とどの単語が関連しているか計算
ステップ2: スコアが大きくなりすぎないよう調整
ステップ3: 「注目度」を確率として解釈できる形に
ステップ4: 注目度に応じて情報を集約
2-2. ステップ1: QKT (関連度スコアの計算)
【ステップ1: 内積で関連度を計算】
Q: (n, d_k) = (単語数, Query次元)
K: (n, d_k) = (単語数, Key次元)
K^T: (d_k, n) ← Kを転置
QK^T: (n, d_k) × (d_k, n) = (n, n)
↑
n×n の行列(全単語ペアのスコア)
【具体例: 3単語の場合】
Q = [[q_The], K^T = [[k_The, k_cat, k_sat]]
[q_cat], [[k_The, k_cat, k_sat]]
[q_sat]] [[k_The, k_cat, k_sat]]
QK^T = [[q_The·k_The, q_The·k_cat, q_The·k_sat],
[q_cat·k_The, q_cat·k_cat, q_cat·k_sat],
[q_sat·k_The, q_sat·k_cat, q_sat·k_sat]]
例えば:
The cat sat
The [12 8 5 ] ← “The”が各単語をどれだけ見るか
cat [ 6 25 18 ] ← “cat”が各単語をどれだけ見るか
sat [ 4 20 22 ] ← “sat”が各単語をどれだけ見るか
【解釈】
・(i, j)成分 = 単語iが単語jをどれだけ見るか
・自分自身(対角成分)が高い傾向
・関連する単語間のスコアが高い
2-3. ステップ2: √d_kでスケーリング
⚠️ なぜスケーリングが必要なのか?
スケーリングしないと、スコアが大きくなりすぎて学習がうまくいきません。
【問題: 内積が大きくなりすぎる】
d_k = 64 の場合(一般的な設定)
2つのランダムなベクトル q, k の内積:
q · k = q₁×k₁ + q₂×k₂ + … + q₆₄×k₆₄
各成分が平均0、分散1のランダムな値だと:
・内積の期待値: 0
・内積の分散: d_k = 64
・内積の標準偏差: √64 = 8
→ 内積の値は大体 -24 〜 +24 の範囲に分布
【Softmaxへの悪影響】
スコア = [25, 18, 5, -10] のような大きな値だと:
softmax([25, 18, 5, -10])
= [exp(25), exp(18), exp(5), exp(-10)] / 合計
= [0.9991, 0.0009, 0.0000, 0.0000]
→ 最大値がほぼ1.0、他がほぼ0
→ 1つの単語にしか注目しない
→ 勾配がほぼ0(学習が進まない)
【スケーリングの効果】
スコアを √d_k = 8 で割る:
[25, 18, 5, -10] / 8 = [3.1, 2.25, 0.625, -1.25]
softmax([3.1, 2.25, 0.625, -1.25])
= [0.59, 0.25, 0.05, 0.01]
→ より分散した分布
→ 複数の単語に注目できる
→ 勾配が保たれる(学習が進む)
2-4. ステップ3: Softmaxで正規化
【ステップ3: 確率分布に変換】
スケーリング後のスコア:
The cat sat
The [1.5 1.0 0.6]
cat [0.75 3.1 2.25]
sat [0.5 2.5 2.75]
Softmaxを各行に適用(行ごとに合計1.0に):
softmax(行1) = softmax([1.5, 1.0, 0.6])
= [0.49, 0.30, 0.21] 合計=1.0
softmax(行2) = softmax([0.75, 3.1, 2.25])
= [0.06, 0.62, 0.32] 合計=1.0
softmax(行3) = softmax([0.5, 2.5, 2.75])
= [0.07, 0.37, 0.56] 合計=1.0
結果(Attention Weight):
The cat sat
The [0.49 0.30 0.21] ← “The”の注目配分
cat [0.06 0.62 0.32] ← “cat”の注目配分(自分に62%)
sat [0.07 0.37 0.56] ← “sat”の注目配分(自分に56%)
【解釈】
・各行の合計 = 1.0(確率分布)
・(i, j) = 単語iが単語jにどれだけ注目するか
・”cat”は自分自身に62%、”sat”に32%注目
2-5. ステップ4: Valueとの重み付け和
【ステップ4: 情報を集約】
Attention Weight × V
Attention Weight: (n, n) = (3, 3)
V: (n, d_v) = (3, 64)
結果: (n, d_v) = (3, 64)
【計算例】
Attention Weight: V(各単語のValue):
The cat sat V_The = [0.1, 0.2, …, 0.3] (64次元)
The [0.49 0.30 0.21] V_cat = [0.8, 0.3, …, 0.9] (64次元)
cat [0.06 0.62 0.32] V_sat = [0.4, 0.7, …, 0.5] (64次元)
sat [0.07 0.37 0.56]
【”cat”の新しい表現】
新_cat = 0.06 × V_The + 0.62 × V_cat + 0.32 × V_sat
= 0.06 × [0.1, 0.2, …, 0.3]
+ 0.62 × [0.8, 0.3, …, 0.9]
+ 0.32 × [0.4, 0.7, …, 0.5]
= [0.006+0.496+0.128, 0.012+0.186+0.224, …, 0.018+0.558+0.160]
= [0.63, 0.42, …, 0.74]
【解釈】
・”cat”の新しい表現は:
– 自分自身の情報を62%含む
– “sat”の情報を32%含む
– “The”の情報を6%含む
→ 文脈を考慮した表現に変換された!
→ “cat”が”sat”(動詞)と関連していることを反映
2-6. 完全な数値例
【完全な計算例(簡略化版)】
入力: “I am happy” (3単語)
d_k = d_v = 4(簡略化のため)
X = [[1, 0, 1, 0], # “I”
[0, 1, 0, 1], # “am”
[1, 1, 0, 0]] # “happy”
W_Q = W_K = W_V = 単位行列(簡略化)
→ Q = K = V = X
【ステップ1: QK^T】
QK^T = X × X^T = [[2, 0, 1],
[0, 2, 1],
[1, 1, 2]]
【ステップ2: スケーリング(√4 = 2で割る)】
[[1.0, 0.0, 0.5],
[0.0, 1.0, 0.5],
[0.5, 0.5, 1.0]]
【ステップ3: Softmax】
行1: softmax([1.0, 0.0, 0.5]) = [0.47, 0.17, 0.36]
行2: softmax([0.0, 1.0, 0.5]) = [0.17, 0.47, 0.36]
行3: softmax([0.5, 0.5, 1.0]) = [0.26, 0.26, 0.48]
Attention Weight:
I am happy
I [0.47 0.17 0.36]
am [0.17 0.47 0.36]
happy[0.26 0.26 0.48]
【ステップ4: × V】
新_I = 0.47×V_I + 0.17×V_am + 0.36×V_happy
= [0.83, 0.53, 0.47, 0.17]
新_am = 0.17×V_I + 0.47×V_am + 0.36×V_happy
= [0.53, 0.83, 0.17, 0.47]
新_happy = 0.26×V_I + 0.26×V_am + 0.48×V_happy
= [0.74, 0.74, 0.26, 0.26]
→ 各単語が文脈情報を含む新しい表現に!
🎭 3. Multi-Head Attention
Multi-Head Attention は、Attentionを複数回並列 に実行する仕組みです。
なぜ複数のヘッドが必要なのでしょうか?
3-1. Single-Headの限界
【1つのAttentionでは不十分】
文: “The animal didn’t cross the street because it was too tired”
この文には複数の種類の関係がある:
関係1: 照応関係
“it” → “animal”(代名詞が何を指すか)
関係2: 構文関係
“didn’t” → “cross”(助動詞と動詞)
“animal” → “cross”(主語と動詞)
関係3: 意味関係
“tired” → “cross”(疲れて渡れなかった)
“tired” → “animal”(何が疲れていたか)
関係4: 位置関係
隣り合う単語同士の関係
【問題】
1つのAttention(1つのQ, K, V)では
1種類の関係しか捉えられない!
例: 照応関係を学習したら構文関係が弱くなる
3-2. Multi-Headの解決策
💡 Multi-Headのアイデア
「複数のAttentionを並列に実行し、異なる種類の関係を同時に学習する」
【Multi-Head Attention(h=8の場合)】
Head 1: 照応関係を学習
“it” → “animal” に強く注目
Head 2: 構文関係(主語-動詞)を学習
“animal” → “cross” に強く注目
Head 3: 構文関係(助動詞-動詞)を学習
“didn’t” → “cross” に強く注目
Head 4: 意味関係を学習
“tired” → “cross” に強く注目
Head 5: 位置関係(隣接単語)を学習
各単語 → 隣の単語 に注目
Head 6: 長距離関係を学習
離れた単語同士の関係
Head 7: 文法関係を学習
冠詞 → 名詞 などの関係
Head 8: その他の関係を学習
【結果】
8種類の異なる視点で文を理解
→ より豊かな表現を獲得
3-3. Multi-Headの計算手順
【Multi-Head Attentionの計算】
パラメータ:
・h = 8(ヘッド数)
・d_model = 512(入力次元)
・d_k = d_v = d_model / h = 64(各ヘッドの次元)
【ステップ1: 各ヘッドで独立にQ, K, Vを計算】
入力X: (n, 512)
各ヘッドiに対して独自の重み行列:
W_Q^i: (512, 64)
W_K^i: (512, 64)
W_V^i: (512, 64)
Q_i = X × W_Q^i → (n, 64)
K_i = X × W_K^i → (n, 64)
V_i = X × W_V^i → (n, 64)
【ステップ2: 各ヘッドでAttention計算】
head_1 = Attention(Q_1, K_1, V_1) → (n, 64)
head_2 = Attention(Q_2, K_2, V_2) → (n, 64)
…
head_8 = Attention(Q_8, K_8, V_8) → (n, 64)
【ステップ3: 全ヘッドを結合】
Concat(head_1, head_2, …, head_8)
= (n, 64×8) = (n, 512)
【ステップ4: 線形変換で出力】
W_O: (512, 512)
出力 = Concat × W_O → (n, 512)
→ 元の次元に戻る
3-4. Single-Head vs Multi-Head 比較
項目
Single-Head
Multi-Head (h=8)
Attention数
1つ
8つ(並列)
各Headの次元
512
64(512÷8)
捉える関係
1種類
8種類(多様)
計算量
O(n²×512)
O(8×n²×64) = 同じ
表現力
限定的
高い
💻 4. PyTorchでのSelf-Attention実装
Self-AttentionをPyTorchで実装します。
コードを段階的に解説していきます。
4-1. Scaled Dot-Product Attentionの実装
ステップ1:必要なライブラリのインポート
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
# torch: PyTorchの基本機能
# torch.nn: ニューラルネットワークのモジュール
# torch.nn.functional: Softmaxなどの関数
# math: 平方根の計算(√d_k)に使用
ステップ2:Scaled Dot-Product Attention関数
def scaled_dot_product_attention(Q, K, V, mask=None):
“””
Scaled Dot-Product Attentionの計算
Args:
Q: Query (batch, seq_len, d_k)
K: Key (batch, seq_len, d_k)
V: Value (batch, seq_len, d_v)
mask: マスク(オプション)
Returns:
output: 出力 (batch, seq_len, d_v)
attention_weights: 注目度 (batch, seq_len, seq_len)
“””
# d_kを取得(スケーリング用)
d_k = Q.size(-1)
# ステップ1: QK^T を計算
# Q: (batch, seq_len, d_k)
# K.transpose(-2, -1): (batch, d_k, seq_len)
# scores: (batch, seq_len, seq_len)
scores = torch.matmul(Q, K.transpose(-2, -1))
# ステップ2: √d_k でスケーリング
scores = scores / math.sqrt(d_k)
# マスク適用(Decoderで使用、未来の単語を見えなくする)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# ステップ3: Softmaxで正規化
# dim=-1: 最後の次元(各行)に対してSoftmax
attention_weights = F.softmax(scores, dim=-1)
# ステップ4: Valueとの重み付け和
# attention_weights: (batch, seq_len, seq_len)
# V: (batch, seq_len, d_v)
# output: (batch, seq_len, d_v)
output = torch.matmul(attention_weights, V)
return output, attention_weights
ステップ3:動作確認
# テスト
batch_size = 2
seq_len = 4
d_k = 8
# ダミーデータを作成
Q = torch.randn(batch_size, seq_len, d_k)
K = torch.randn(batch_size, seq_len, d_k)
V = torch.randn(batch_size, seq_len, d_k)
# Attention計算
output, weights = scaled_dot_product_attention(Q, K, V)
print(f”Output shape: {output.shape}”)
print(f”Attention weights shape: {weights.shape}”)
print(f”\n1文目のAttention Weights:”)
print(weights[0].detach().numpy().round(3))
print(f”\n各行の合計: {weights[0].sum(dim=-1).detach().numpy()}”)
実行結果:
Output shape: torch.Size([2, 4, 8])
Attention weights shape: torch.Size([2, 4, 4])
1文目のAttention Weights:
[[0.312 0.198 0.234 0.256]
[0.189 0.402 0.231 0.178]
[0.267 0.215 0.298 0.22 ]
[0.234 0.267 0.189 0.31 ]]
各行の合計: [1. 1. 1. 1.]
4-2. Multi-Head Attentionの実装
クラス全体の構造
class MultiHeadAttention(nn.Module):
“””Multi-Head Attention”””
def __init__(self, d_model, n_heads, dropout=0.1):
“””
Args:
d_model: 入力の次元 (例: 512)
n_heads: ヘッド数 (例: 8)
dropout: ドロップアウト率
“””
super(MultiHeadAttention, self).__init__()
# d_modelがn_headsで割り切れることを確認
assert d_model % n_heads == 0, “d_model must be divisible by n_heads”
self.d_model = d_model # 512
self.n_heads = n_heads # 8
self.d_k = d_model // n_heads # 64
# Q, K, V用の線形変換(全ヘッド分まとめて)
self.W_Q = nn.Linear(d_model, d_model) # (512, 512)
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.dropout = nn.Dropout(dropout)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# 線形変換
Q = self.W_Q(Q) # (batch, seq_len, d_model)
K = self.W_K(K)
V = self.W_V(V)
# ヘッドに分割
# (batch, seq_len, d_model) → (batch, n_heads, seq_len, d_k)
Q = Q.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
K = K.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
V = V.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
# Attention計算(全ヘッド同時)
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attention_weights = F.softmax(scores, dim=-1)
attention_weights = self.dropout(attention_weights)
output = torch.matmul(attention_weights, V)
# output: (batch, n_heads, seq_len, d_k)
# ヘッドを結合
# (batch, n_heads, seq_len, d_k) → (batch, seq_len, d_model)
output = output.transpose(1, 2).contiguous()
output = output.view(batch_size, -1, self.d_model)
# 出力の線形変換
output = self.W_O(output)
return output, attention_weights
動作確認
# Multi-Head Attentionのテスト
d_model = 512
n_heads = 8
batch_size = 2
seq_len = 10
# モデル作成
mha = MultiHeadAttention(d_model, n_heads)
# 入力データ
X = torch.randn(batch_size, seq_len, d_model)
# Self-Attention(Q = K = V = X)
output, weights = mha(X, X, X)
print(f”Input shape: {X.shape}”)
print(f”Output shape: {output.shape}”)
print(f”Attention weights shape: {weights.shape}”)
print(f”\nHead 0のAttention Weights (最初の5×5):”)
print(weights[0, 0, :5, :5].detach().numpy().round(3))
実行結果:
Input shape: torch.Size([2, 10, 512])
Output shape: torch.Size([2, 10, 512])
Attention weights shape: torch.Size([2, 8, 10, 10])
Head 0のAttention Weights (最初の5×5):
[[0.142 0.089 0.112 0.098 0.105]
[0.098 0.156 0.087 0.102 0.091]
[0.105 0.092 0.148 0.088 0.097]
[0.087 0.103 0.095 0.162 0.089]
[0.092 0.088 0.101 0.094 0.158]]
4-3. 完成コード(コピー用)
※コードが長いため、横スクロールできます。
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
def scaled_dot_product_attention(Q, K, V, mask=None):
“””Scaled Dot-Product Attention”””
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attention_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attention_weights, V)
return output, attention_weights
class MultiHeadAttention(nn.Module):
“””Multi-Head Attention”””
def __init__(self, d_model, n_heads, dropout=0.1):
super(MultiHeadAttention, self).__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.dropout = nn.Dropout(dropout)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
Q = self.W_Q(Q)
K = self.W_K(K)
V = self.W_V(V)
Q = Q.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
K = K.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
V = V.view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2)
output, attention_weights = scaled_dot_product_attention(Q, K, V, mask)
output = output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
output = self.W_O(output)
return output, attention_weights
# 使用例
d_model, n_heads, batch_size, seq_len = 512, 8, 2, 10
mha = MultiHeadAttention(d_model, n_heads)
X = torch.randn(batch_size, seq_len, d_model)
output, weights = mha(X, X, X)
print(f”Output: {output.shape}, Weights: {weights.shape}”)
📊 5. Attention Weightの可視化
Attention Weightを可視化することで、
「モデルがどこに注目しているか」 を確認できます。
5-1. 可視化コード
import matplotlib.pyplot as plt
import numpy as np
def visualize_attention(words, attention_weights, head_idx=0):
“””
Attention Weightsをヒートマップで可視化
Args:
words: 単語のリスト(例: [“The”, “cat”, “sat”])
attention_weights: (n_heads, seq_len, seq_len) または (seq_len, seq_len)
head_idx: 表示するヘッド番号
“””
# 特定のヘッドを取得
if len(attention_weights.shape) == 3:
attn = attention_weights[head_idx]
else:
attn = attention_weights
# NumPyに変換
if torch.is_tensor(attn):
attn = attn.cpu().detach().numpy()
# ヒートマップ作成
fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(attn, cmap=’Blues’)
# ラベル設定
ax.set_xticks(range(len(words)))
ax.set_yticks(range(len(words)))
ax.set_xticklabels(words, rotation=45, ha=’right’)
ax.set_yticklabels(words)
# 軸ラベル
ax.set_xlabel(‘Key(注目される側)’)
ax.set_ylabel(‘Query(注目する側)’)
ax.set_title(f’Self-Attention Weights (Head {head_idx})’)
# カラーバー
plt.colorbar(im, label=’Attention Weight’)
# 数値を表示
for i in range(len(words)):
for j in range(len(words)):
color = ‘white’ if attn[i, j] > 0.5 else ‘black’
ax.text(j, i, f'{attn[i, j]:.2f}’, ha=’center’, va=’center’,
color=color, fontsize=9)
plt.tight_layout()
plt.show()
# 使用例
words = [‘The’, ‘cat’, ‘sat’, ‘on’, ‘mat’]
# ダミーのAttention Weights(実際はモデルから取得)
attn = np.array([
[0.45, 0.20, 0.15, 0.10, 0.10], # The →
[0.10, 0.50, 0.25, 0.05, 0.10], # cat → 自分とsatに注目
[0.05, 0.30, 0.40, 0.15, 0.10], # sat → catと自分に注目
[0.05, 0.10, 0.15, 0.50, 0.20], # on → 自分とmatに注目
[0.05, 0.15, 0.10, 0.25, 0.45], # mat → 自分とonに注目
])
visualize_attention(words, attn)
5-2. 可視化から分かること
【ヒートマップの読み方】
縦軸 (Query): 注目する側の単語
横軸 (Key): 注目される側の単語
明るい色: 強く注目している(高いAttention Weight)
暗い色: あまり注目していない(低いAttention Weight)
【パターン例】
パターン1: 対角線が明るい
→ 各単語が自分自身に注目
→ 自己参照的な関係
パターン2: 特定の列が明るい
→ 多くの単語がその単語に注目
→ 重要な単語(主語、動詞など)
パターン3: 特定のセルが明るい
→ 特定の単語ペア間の関係
→ “it” → “animal” などの照応関係
【各ヘッドの違い】
Head 0: 構文関係に注目(主語-動詞)
Head 1: 照応関係に注目(代名詞-名詞)
Head 2: 位置関係に注目(隣接単語)
…
→ 各ヘッドが異なるパターンを学習!
📝 練習問題
このステップで学んだ内容を確認しましょう。
問題1:Q, K, Vの役割
Value(V) の役割として正しい ものはどれですか?
どの単語に注目すべきか決める
自分の特徴を他の単語に伝える
注目されたときに渡す実際の情報
Attention Weightを計算する
解答を見る
正解:c
Valueは注目されたときに渡す実際の情報 です。
Q, K, Vの役割:
Query: 「私は何を探しているか」→ どの単語に注目すべきか決める
Key: 「私の特徴はこれ」→ 他の単語に見せる情報
Value: 「私の内容はこれ」→ 注目されたときに渡す情報 ✅
計算の流れ: QueryとKeyで関連度を計算 → Valueを重み付けして集約
問題2:スケーリングの理由
Scaled Dot-Product Attentionで√d_k で割る理由は何ですか?
計算を高速化するため
内積の大きさを正規化し、Softmaxの勾配消失を防ぐため
メモリ使用量を削減するため
パラメータ数を減らすため
解答を見る
正解:b
内積の正規化とSoftmaxの勾配消失防止 のためです。
問題: d_kが大きいと内積の値が大きくなりすぎる
d_k=64の場合、内積は-24〜24程度の範囲
softmax([25, 18, 5]) ≈ [0.999, 0.001, 0.000]
→ 1つの単語にしか注目しない、勾配がほぼ0
解決: √d_k=8で割る
[25, 18, 5] / 8 = [3.1, 2.25, 0.625]
softmax = [0.59, 0.25, 0.05]
→ より分散した分布、勾配が保たれる
問題3:Multi-Head Attention
Multi-Head Attention (h=8)の利点として正しくない ものはどれですか?
異なる種類の関係を同時に学習できる
各ヘッドが低次元(d_k/h)で効率的
Single-Headより計算量が少ない
複数の視点を統合できる
解答を見る
正解:c
Multi-HeadはSingle-Headと計算量が同程度 (少ないわけではない)です。
計算量の比較:
Single-Head (d=512): O(n² × 512)
Multi-Head (8ヘッド × d=64): O(8 × n² × 64) = O(n² × 512)
→ 理論上同じ、実装上はやや多い場合も
正しい利点:
a: 異なる関係(照応、構文、意味など)を同時に学習 ✅
b: 各ヘッドは64次元で効率的に計算 ✅
d: アンサンブル効果で多様な視点を統合 ✅
問題4:Attention Weightの性質
Attention Weight行列の各行 について正しい ものはどれですか?
各行の合計は1.0である
各列の合計は1.0である
対角成分が必ず最大である
対称行列である
解答を見る
正解:a
Attention Weightの各行の合計は1.0 です。
理由: Softmaxを行方向(dim=-1)に適用するため
例:
単語1 単語2 単語3
単語1 [0.5 0.3 0.2] ← 合計1.0
単語2 [0.2 0.6 0.2] ← 合計1.0
単語3 [0.3 0.3 0.4] ← 合計1.0
各選択肢:
b: 列の合計は1.0とは限らない ❌
c: 対角成分が最大とは限らない ❌
d: Q≠Kなら非対称になる ❌
問題5:Self-Attentionの計算順序
Scaled Dot-Product Attentionの正しい計算順序 はどれですか?
Softmax → QK^T → スケーリング → ×V
QK^T → Softmax → スケーリング → ×V
QK^T → スケーリング → Softmax → ×V
スケーリング → QK^T → Softmax → ×V
解答を見る
正解:c
正しい順序はQK^T → スケーリング → Softmax → ×V です。
公式: Attention(Q, K, V) = softmax(QK^T / √d_k) × V
各ステップ:
QK^T: 関連度スコアを計算
/ √d_k: スケーリング(Softmax前に実行)
softmax: 確率分布に変換
× V: Valueを重み付けして集約
注意: スケーリングはSoftmaxの前に行う(勾配消失を防ぐため)
📝 STEP 13 のまとめ
✅ このステップで学んだこと
Q, K, V: Query(質問)、Key(特徴)、Value(内容)の3つの役割
Scaled Dot-Product: Attention = softmax(QK^T / √d_k) × V
4ステップ: QK^T → スケーリング → Softmax → ×V
スケーリング: √d_kで割って勾配消失を防ぐ
Multi-Head: 複数のAttentionで多様な関係を学習
実装: PyTorchでのSelf-Attention構築
💡 重要ポイント
Self-Attentionの核心公式:
Attention(Q, K, V) = softmax(QKT / √d_k) × V
Multi-Headの重要性:
8つのヘッドで8種類の関係を同時学習
照応関係、構文関係、意味関係、位置関係…
これがTransformerの表現力の源
🎯 次のステップの準備
次のSTEP 14では、Position EncodingとFeed-Forward Network を学びます!
学ぶ内容:
Position Encoding: Self-Attentionに位置情報を与える
Feed-Forward Network: 非線形変換で表現力を高める
残差結合とLayer Normalization: 学習を安定化
これらを組み合わせて、完全なTransformer層を構築します!