STEP 13:Self-Attentionの仕組み

🔍 STEP 13: Self-Attentionの仕組み

Query、Key、Valueの概念とScaled Dot-Product Attentionを完全理解します

📋 このステップで学ぶこと

  • 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)の役割として正しいものはどれですか?

  1. どの単語に注目すべきか決める
  2. 自分の特徴を他の単語に伝える
  3. 注目されたときに渡す実際の情報
  4. Attention Weightを計算する
正解:c

Valueは注目されたときに渡す実際の情報です。

Q, K, Vの役割:

  • Query: 「私は何を探しているか」→ どの単語に注目すべきか決める
  • Key: 「私の特徴はこれ」→ 他の単語に見せる情報
  • Value: 「私の内容はこれ」→ 注目されたときに渡す情報 ✅

計算の流れ:QueryとKeyで関連度を計算 → Valueを重み付けして集約

問題2:スケーリングの理由

Scaled Dot-Product Attentionで√d_kで割る理由は何ですか?

  1. 計算を高速化するため
  2. 内積の大きさを正規化し、Softmaxの勾配消失を防ぐため
  3. メモリ使用量を削減するため
  4. パラメータ数を減らすため
正解: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)の利点として正しくないものはどれですか?

  1. 異なる種類の関係を同時に学習できる
  2. 各ヘッドが低次元(d_k/h)で効率的
  3. Single-Headより計算量が少ない
  4. 複数の視点を統合できる
正解: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. 各行の合計は1.0である
  2. 各列の合計は1.0である
  3. 対角成分が必ず最大である
  4. 対称行列である
正解: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の正しい計算順序はどれですか?

  1. Softmax → QK^T → スケーリング → ×V
  2. QK^T → Softmax → スケーリング → ×V
  3. QK^T → スケーリング → Softmax → ×V
  4. スケーリング → QK^T → Softmax → ×V
正解:c

正しい順序はQK^T → スケーリング → Softmax → ×Vです。

公式:Attention(Q, K, V) = softmax(QK^T / √d_k) × V

各ステップ:

  1. QK^T: 関連度スコアを計算
  2. / √d_k: スケーリング(Softmax前に実行)
  3. softmax: 確率分布に変換
  4. × 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層を構築します!

📝

学習メモ

自然言語処理(NLP) - Step 13

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE