別名・略称:アテンション、 Self-Attention、 Scaled Dot-Product Attention
Attention は Query × Key の類似度で Value を加重平均するという 1 アイデアで NLP・画像・音声・マルチモーダルの全てを統一しました。
🍰 まずはやさしく
重要なところに注目する仕組みです。
データのどこを見るべきか決めます。
スマホで大事な単語だけ探す感じです。
この仕組みの基本について読みます。
注意機構(Attention Mechanism):入力のどこに注目するかを学習する仕組み。 Transformer の中核
🍰 まずはやさしく
現代のAIを動かす心臓部です。
言葉や画像の深い意味を理解させます。
翻訳アプリなどの基盤になっています。
AIがどうやって言葉を扱うか読みます。
アテンション機構の入力は集合 (set) として扱われ、 順序情報が失われる。 「私は犬を見た」「犬は私を見た」が同じ表現になってしまう。 これを補うのが 位置エンコーディング (Positional Encoding)。
$$PE(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d}}\right)$$
$$PE(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d}}\right)$$
数式を言葉で読み解くと、 「異なる周波数の sin/cos で各位置をベクトル化」。 周波数が指数的に変化することで、 「相対位置」も自然に表現できる。 元論文で採用された方式。
最近の LLM (LLaMA, GPT-NeoX) が採用。 Q と K の各 2 次元ペアを「位置に応じた角度」で回転させる。 相対位置情報が内積に自然に現れる。
アテンションスコアに「位置距離 × 係数」を直接加算するシンプル手法。 学習なしで使え、 訓練時より長い系列に外挿可能。
このコードでやること: Sinusoidal PE を生成して可視化する(位置 0-200、 次元 64)。
📥 入力データ: 系列長 200、 埋め込み次元 64 のパラメータ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import numpy as np import matplotlib.pyplot as plt def sinusoidal_pe(seq_len, d_model): pe = np.zeros((seq_len, d_model)) pos = np.arange(seq_len)[:, None] i = np.arange(d_model)[None, :] angle = pos / np.power(10000, (2 * (i // 2)) / d_model) pe[:, 0::2] = np.sin(angle[:, 0::2]) pe[:, 1::2] = np.cos(angle[:, 1::2]) return pe pe = sinusoidal_pe(200, 64) print(f'PE shape: {pe.shape}') print(f'位置 0 のベクトル先頭: {pe[0][:4]}') print(f'位置 1 のベクトル先頭: {pe[1][:4]}') print(f'位置 0 と 1 の内積: {np.dot(pe[0], pe[1]):.4f}') print(f'位置 0 と 100 の内積: {np.dot(pe[0], pe[100]):.4f}') fig, ax = plt.subplots(figsize=(10, 5)) im = ax.imshow(pe, aspect='auto', cmap='RdBu_r') ax.set_xlabel('Embedding 次元') ax.set_ylabel('位置 (pos)') ax.set_title('Sinusoidal Positional Encoding (200 x 64)') plt.colorbar(im) plt.tight_layout() plt.savefig('positional_encoding.png', dpi=120) plt.show() |
📤 実行結果:
💬 結果の読み方: 位置 0 と近い位置の PE は内積が大きい (類似度高) → モデルは「位置の近さ」を内積で読み取れる。 位置 0 と遠い位置 100 では内積が小さい。 この性質が「相対位置の自動学習」を可能にする。
「アテンション重みが高い = モデルがそこに注目している」と素朴に解釈する傾向があるが、 これに対し Jain & Wallace (2019) "Attention is not Explanation" が反論を提起した。
Wiegreffe & Pinter (2019) "Attention is not not Explanation" は「タスク・モデルによっては attention が有意な説明となる」と反論。 結論として「attention を解釈に使うときは検証が必要」が共通理解になった。
Neural Machine Translation で、 RNN エンコーダーの全隠れ状態を「重要度重み」で加重平均してデコーダーに渡す。 これがアテンション機構の原型。
スコア計算を単純な内積に簡略化。 計算速度向上。 「Global」「Local」アテンションの区分けも提案。
Vaswani らが RNN を完全に排除し、 Multi-Head Self-Attention のみで翻訳を実現。 並列化容易、 学習高速、 長距離依存も捉える。 NLP のパラダイムシフト。
Transformer Encoder (BERT) や Decoder (GPT) を大量データで事前学習し、 下流タスクにファインチューニング。 NLP の精度が飛躍的に向上。
画像をパッチ化して Transformer に通す ViT が CNN を超える性能。 CLIP は画像-テキスト統合表現を実現。 アテンションがマルチモーダルの基盤に。
GPT-4、 Claude、 Gemini など 100B-1T パラメータ規模のモデルが登場。 Flash Attention、 RoPE、 GQA (Grouped Query Attention) 等の効率化技術が必須。 アテンションは「現代 AI の心臓」。
$O(n^2)$ を $O(n)$ に圧縮する State Space Models (Mamba) や RWKV がアテンションの代替候補として登場。 だが大規模学習・汎化性能では現状アテンションが優位。 「ハイブリッド」が次のトレンド。
🍰 まずはやさしく
図書館で本を探すような仕組みです。
必要な情報を効率よく取り出します。
部活のメモから要点を探す感じです。
情報の選び方と計算の流れを読みます。
| 記号 | 役割 | 直感的な例え |
|---|---|---|
| Q (Query) | 「何を探しているか」 | 検索クエリ |
| K (Key) | 「自分は何か」を示すラベル | 図書館の書籍タイトル |
| V (Value) | 「実際に取り出す内容」 | 書籍の本文 |
Attention 機構の可視化補助として、 ① 重みの分布 (どの位置に注意が集中するか)、 ② 文脈ベクトルの相関構造、 ③ 系列長と計算量の関係 (時系列的な負荷) を表す類似可視化を 3 点示す。



💬 3 枚を組み合わせると、「内積 → softmax → 重み付き和」という Attention の三段階の動作と、 系列長 n に対する計算量という現実的制約が一枚絵で把握できる。
以下 5 問を解いて理解度を確認してください。 回答は本文の該当セクションを参照。
| バリアント | 計算量 | 主用途 | 代表モデル |
|---|---|---|---|
| Scaled Dot-Product | $O(n^2 d)$ | 標準 Transformer ブロック | BERT, GPT-2/3 |
| Multi-Head | $O(h \cdot n^2 d/h)$ | 複数の関係性を同時学習 | Transformer 標準 |
| Cross-Attention | $O(n_q n_k d)$ | Encoder-Decoder 間の橋渡し | T5, 翻訳モデル |
| Sparse / Local | $O(n \log n)$ or $O(n)$ | 超長文の効率化 | Longformer, BigBird |
| FlashAttention | $O(n^2)$ (メモリ $O(n)$) | GPU でのメモリ I/O 削減 | GPT-4, LLaMA 等 |
→ 標準の Scaled Dot-Product を起点に、 head の分割(Multi-Head)、 入力の分割(Sparse/Local)、 メモリ I/O 最適化(FlashAttention)と進化してきた。 長コンテキスト LLM では FlashAttention とその派生が事実上の標準。
| 観点 | RNN/LSTM | Attention/Transformer |
|---|---|---|
| 並列性 | 逐次計算(並列化困難) | 完全並列(GPU 親和性高) |
| 長距離依存 | 弱い(勾配消失) | 強い(任意距離 1 hop) |
| 計算量 | $O(n d^2)$ | $O(n^2 d)$ |
| パラメータ数 | 少なめ | 多い(QKV + Multi-Head) |
| 2026 年現在の主流 | 小規模時系列向け | 大規模 NLP/画像/音声 |
→ 計算量だけ見ると RNN が有利だが、 並列化可能で長距離依存に強い Attention が GPU 時代の主役。 ただし n が極端に大きい時系列は Attention 自体が O(n²) で苦しくなる。
関連: Transformer / エンベディング / ソフトマックス / RNN / LSTM / ニューラルネット
Attention 機構の起源は 2014 年の Bahdanau らによる「Neural Machine Translation by Jointly Learning to Align and Translate」にさかのぼる。 当時の機械翻訳は Seq2Seq の RNN エンコーダ・デコーダが主流で、 長文になると 文末で文頭の情報が消失する 問題があった。 Bahdanau Attention は「デコーダの各ステップで、 エンコーダのすべての隠れ状態を重み付け参照する」仕組みを導入し、 BLEU スコアを大きく改善した。
2017 年の論文 Attention Is All You Need(Vaswani ら)は、 RNN を完全に廃して Attention のみで Encoder-Decoder を構成する Transformer を提案した。 ここで現代の Scaled Dot-Product Attention と Multi-Head Attention が定式化された。 ポイントは「Q(Query)、 K(Key)、 V(Value)の 3 つの線形射影」で、 これにより同じ入力に対して 3 つの異なる役割を持たせられる。
数式上は $\mathrm{Attention}(Q, K, V) = \mathrm{softmax}(QK^\top / \sqrt{d_k}) V$ である。 $QK^\top$ は「クエリとキーの類似度」を内積で計算し、 softmax で確率化、 V を重み付け平均する。 $\sqrt{d_k}$ で割るのは、 $d_k$ が大きいと内積の分散が $d_k$ に比例して増大し softmax が one-hot 化して勾配が消えるためで、 「温度スケーリング」の役割を果たす。
Multi-Head Attention は、 Q, K, V を h 個の小さな部分空間(典型的に h=8 or 16、 各次元 d/h=64)に分割し、 それぞれで独立に Attention を計算したあと concat して線形変換する。 こうすると 1 つの head は「主語と動詞の対応」、 別の head は「修飾語と被修飾語の対応」など、 異なる関係性を並列に学習できる。 これは可視化研究(BertViz など)で実証されている。
Attention の 計算量とメモリ量 は系列長 n に対して O(n²) で増大する。 n=2048 で 8 GB 程度、 n=32k なら GPU メモリを使い切る。 これに対する解として、 ① Sparse Attention(Longformer, BigBird)が局所+少数のグローバル接続だけを残す、 ② Linear Attention(Performer, Linformer)がカーネル近似で O(n) を実現、 ③ FlashAttention(Dao 2022)が GPU のメモリ階層を意識した実装で実効メモリを O(n) に抑える、 の 3 系統が主流である。 2026 年現在の主要 LLM は FlashAttention v2/v3 を採用している。
SSDSE-B-2026 を Attention に適用する架空例として、 都道府県を「シーケンス長 47」のトークン列と見立て、 各特徴量を埋め込み次元 d=128 のベクトルに変換、 Self-Attention で「県間の類似度マップ」を作ることが考えられる。 出力の Attention 重みを 47×47 のヒートマップで可視化すると、 「東京・大阪・愛知」が互いに高い重みを持つ「大都市クラスタ」、 「青森・秋田・岩手」が高い重みを持つ「北東北クラスタ」のような構造が見えるはずである。
注意すべきは「Attention 重みが大きい単語が因果的に効いている」と読むのは危険という点。 Jain & Wallace (2019) の論文「Attention is not Explanation」は、 Attention 重みを操作しても出力がほぼ変わらない例を多数示した。 一方で Wiegreffe & Pinter (2019) は「条件付きで説明可能性がある」と反論しており、 議論は決着していない。 実務的には、 Integrated Gradients や LRP(Layer-wise Relevance Propagation) など別の説明手法と併用するのが安全である。
最後に Cross-Attention について。 Self-Attention は Q, K, V が同じ系列から来るが、 Cross-Attention は Q が片方の系列から、 K, V が別の系列から来る。 翻訳の Decoder では「Decoder の現ステップ Q」が「Encoder の全位置の K, V」を参照する。 画像と言語を結合する CLIP や、 マルチモーダルモデルの中核も Cross-Attention である。 Stable Diffusion などの拡散モデルも、 テキスト埋め込みを K, V に、 画像潜在表現を Q にして Cross-Attention で誘導している。
Self-Attention 自身は 順序情報を持たない。 入力トークンの並び順を入れ替えても、 そのままでは出力が変わらない(permutation invariant)。 そのため Positional Encoding を必ず加算する。 Vaswani の原論文では正弦・余弦関数の組み合わせで位置を符号化したが、 現代の主流は RoPE(Rotary Position Embedding)(Su 2021)で、 LLaMA や GPT-NeoX が採用している。 RoPE は相対位置を内積に直接埋め込めるため長文への外挿性に優れる。
Decoder の Self-Attention では Causal Mask(因果マスク)を適用する。 位置 i が位置 j (j>i) を参照すると、 推論時の自己回帰生成と整合しないため。 マスクは softmax 適用前のスコア行列に対して、 上三角部分に -∞ を加算するだけで実装できる。 PyTorch では torch.nn.functional.scaled_dot_product_attention(q, k, v, is_causal=True) 一発で内部最適化込みで使える。
2026 年現在、 LLM の文脈長競争は激化している。 GPT-4 Turbo は 128k トークン、 Gemini 1.5 Pro は 1M トークン、 Claude 3 は 200k トークンを公称。 これらを支えるのは FlashAttention v3 + Ring Attention + Sliding Window といった工夫の組み合わせ。 さらに State Space Models(Mamba, Mamba-2)が「Attention を使わずに O(n) で長文を扱う」アプローチとして注目されており、 純粋な Transformer 一強の時代は終わりつつある。 ただし精度・汎化性能では Attention ベースの Transformer が依然優位で、 ハイブリッド構成も増えている。
BERT の Multi-Head Attention を可視化した研究(Clark ら 2019「What Does BERT Look At?」)によれば、 各 head は異なる文法・意味的関係を専門的に学ぶ傾向がある。 たとえば「直接目的語の検出」「所有格の名詞への接続」「coreference(共参照)解決」など、 NLP の伝統的タスクに対応する head が観察される。 これは Multi-Head を「アンサンブル的な並列特徴抽出器」と理解する根拠になる。
一方で、 多くの head は冗長で 枝刈り(pruning) しても性能が落ちないことも示された(Michel ら 2019「Are Sixteen Heads Really Better than One?」)。 計算コスト削減のため、 推論時に head 数を 12 → 4 に削っても下流タスクで実用上の差が出ないモデルもある。 蒸留(Distillation) や 量子化 と組み合わせると、 モバイルデバイス上の Transformer 推論も実用域に。 これが 2026 年の Edge LLM の基盤。
Attention の解釈研究は、 circuit analysis(回路解析)と呼ばれる新分野に発展している。 Anthropic の研究チームが提唱する Mechanistic Interpretability では、 Attention 層の特定の head が果たす機能を「induction head(前の出現を見て次を予測する head)」のように具体的に同定する。 これにより、 LLM の挙動を「ブラックボックス」ではなく「機械的に説明可能な構造」として理解する道が開けつつある。
Attention は NLP 以外にも広く応用されている。 画像認識 では Vision Transformer (ViT) が画像をパッチに分割し、 パッチ間の関係を Self-Attention で学習する。 ResNet を超える精度を達成し、 2022 年以降 CNN を置き換える流れが加速している。 音声認識・音声合成 では Whisper や Conformer が Attention を中核に据え、 多言語認識のベンチマーク SOTA を更新し続けている。
タンパク質構造予測 の AlphaFold2 / AlphaFold3 は Attention の応用の白眉で、 アミノ酸間の長距離相互作用を Self-Attention で捉えることで、 2 年で数十年分の構造データを生成した。 強化学習 でも Decision Transformer が「状態・行動・報酬の系列」を Attention で処理する新パラダイムとして注目されている。
統計・データサイエンス分野では、 時系列予測(Temporal Fusion Transformer など)、 表形式データの分類(TabTransformer, FT-Transformer)、 グラフ学習(Graph Attention Network, Graphormer)など、 あらゆるタスクで Attention の派生が定番化している。 SSDSE-B-2026 のような表形式データに対しても、 列間の関係を Self-Attention で学習する FT-Transformer は GBM (XGBoost/LightGBM) と競合する性能を示すことが報告されている。
Attentionを説明する時は、 どの入力がどの出力にどれだけ参照されたかを、 重みの可視化だけで断定しないことが大切です。 Attention重みは解釈の手がかりになりますが、 因果的な重要度や人間の注目そのものではありません。
🍰 まずはやさしく
注目度を計算する数式のことです。
正確に情報を処理するために使います。
テストの配点を決める計算に似ています。
具体的な計算式とルールについて読みます。
Transformer の核 $\text{Attention}(Q,K,V) = \text{softmax}(QK^T/\sqrt{d_k})V$ は、 5 つの要素から構成される。 各要素を「言葉」に翻訳することで、 公式を覚えるだけでなく「何をしているか」が見えてくる。
$Q \in \mathbb{R}^{n \times d_k}$。 各位置 $i$ で「自分はこんな情報を求めている」という問い合わせベクトル $q_i$ を持つ。 入力埋め込み $X$ に $W_Q$ をかけて生成。
$K \in \mathbb{R}^{n \times d_k}$。 各位置 $j$ で「自分が提供できる情報はこんなもの」というラベルベクトル $k_j$ を持つ。 入力埋め込み $X$ に $W_K$ をかけて生成。
$QK^T \in \mathbb{R}^{n \times n}$、 各要素は内積 $q_i \cdot k_j$。 内積が大きい = 「位置 $i$ の問い合わせと位置 $j$ のラベルが似ている」= 関連性高い。 これがアテンションスコアの本質。
数式を言葉で読み解くと、 「全ペア $(i, j)$ について類似度を計算する $n \times n$ の関連性マトリックス」。 これが「文中の各単語が、 他のどの単語に注目するか」の定量化。
$d_k$ が大きいと内積の絶対値も大きくなり、 softmax が極端な分布 (ほぼ one-hot) になる → 勾配がほぼゼロ。 これを防ぐため $\sqrt{d_k}$ で割る。 これが Scaled Dot-Product Attention の「Scaled」の由来。
softmax で各行の合計が 1 になる確率分布へ変換 → Value $V$ の各行の加重平均を取る。 「関連性が高いほどその位置の Value を強く取り込む」のがアテンションの出力。 各位置 $i$ で「自分の文脈情報の最良の混合」が得られる。
| 記号 | 役割 | サイズ | 直感的意味 |
|---|---|---|---|
| $Q$ | Query | $n \times d_k$ | 「探したい」問い合わせ |
| $K$ | Key | $n \times d_k$ | 「提供できる」ラベル |
| $V$ | Value | $n \times d_v$ | 提供する「中身」の情報 |
| $QK^T$ | スコア | $n \times n$ | 全ペアの関連度 |
| $\sqrt{d_k}$ | スケーリング | スカラー | 勾配安定化 |
| softmax | 正規化 | 確率分布化 | 合計 1 への重み変換 |
| 出力 | Attention | $n \times d_v$ | 文脈化された表現 |
クエリ Q(青)と 3 つのキー K1/K2/K3 を ドラッグして動かしてください(スマホは指でOK)。 各キーとの内積 → スケーリング → softmax でアテンション重みがリアルタイムに再計算され、 重み付き平均で得られる出力 out(赤・破線)も動きます。 クエリをあるキーの方向へ向けて長くするほど、 そのキーの重みが増えて出力がそちらへ引き寄せられます。
| キー | 内積 q·k | スコア(÷√d) | 重み |
|---|---|---|---|
| K1 | – | – | – |
| K2 | – | – | – |
| K3 | – | – | – |
アテンションがやっているのは 「クエリと各キーの関連度(内積)を測り、 それを softmax で足して 1 になる重みに変え、 バリューを重み付き平均する」 ただそれだけです。 上のパネルで 1 つのキーにクエリを強く向けると、 そのキーの重みが 100% に近づき、 出力(赤)はほぼそのバリューに一致します。 逆に 3 つのキーの中間にクエリを置くと重みが分散し、 出力は 3 点の重心付近に来ます。 これが「文脈に応じて参照先を動的に切り替える」アテンションの本質です。
関連ページ:内積 / softmax 関数 / 埋め込み / Transformer / 自己教師あり学習
SSDSE-B-2026(2023 年)から 5 都道府県(北海道・東京都・大阪府・愛知県・福岡県)× 4 指標(総人口・出生数・新規求職申込件数・着工新設持家数)の トークン列 を作り、 都道府県間の Attention 重みを計算します。
各都道府県を 1 つの「トークン(4 次元ベクトル)」とみなし、 自己 Attention を計算すると、 経済規模・人口構造の似た都道府県同士に強い重みが付くことが確認できます。 例えば「東京都」のクエリは「大阪府」「愛知県」に高い重みを持ち、 「北海道」には低い重みになります。
| 都道府県 | 総人口(千人) | 出生数(人) | 新規求職申込件数(件) | 着工新設持家数(戸) |
|---|---|---|---|---|
| 北海道 | 5092 | 24430 | 156458 | 8168 |
| 東京都 | 14086 | 86348 | 270954 | 13289 |
| 大阪府 | 8763 | 55292 | 203553 | 9425 |
| 愛知県 | 7477 | 48402 | 136105 | 15361 |
| 福岡県 | 5103 | 33942 | 135578 | 7804 |
標準化後、 内積 → スケーリング → softmax で重み行列を作ると、 「東京 → 大阪」の重みは「東京 → 北海道」より大きくなります。 これが Attention の 類似度ベース集約 です。
「都道府県の経済指標を入力 → 人口を予測」というタスクで、 self-attention 付き MLP を学習させ、 attention 重みがどう学習されるかを観察する。
このコードでやること: SSDSE-B-2026 を「変数を系列とみなした」Transformer 風モデルで人口予測、 学習後の attention 重みで「どの変数が人口予測に重要か」を可視化。
📥 入力データ: SSDSE-B-2026 (47 県 × 数値変数)、 各変数を「トークン」とみなす
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | import torch import torch.nn as nn import pandas as pd import numpy as np from scipy import stats torch.manual_seed(0) # 実行のたびに同じ結果が出るようにする df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) pref = df[(df['Code'].astype(str).str.match(r'R\d{5}')) & (df['SSDSE-B-2026'] == 2023)].copy() num = pref.select_dtypes(include=[np.number]).fillna(0) # 目的: A1101 (人口) を他の指標から予測 y = num['A1101'].values X_cols = [c for c in num.columns if c != 'A1101'][:6] # 上位 6 個 X = stats.zscore(num[X_cols].values, axis=0) y_norm = (y - y.mean()) / y.std() X_t = torch.tensor(X, dtype=torch.float32) y_t = torch.tensor(y_norm, dtype=torch.float32) # Self-attention でトークン (=変数) 間の関係を学習 class AttentionPredictor(nn.Module): def __init__(self, n_features, d_model=16): super().__init__() self.embed = nn.Linear(1, d_model) 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.fc = nn.Linear(d_model * n_features, 1) self.n = n_features self.d = d_model def forward(self, x): # x: (batch, n_features) → (batch, n_features, 1) → (batch, n_features, d_model) h = self.embed(x.unsqueeze(-1)) q, k, v = self.W_q(h), self.W_k(h), self.W_v(h) score = q @ k.transpose(-2, -1) / np.sqrt(self.d) attn = torch.softmax(score, dim=-1) out = attn @ v return self.fc(out.flatten(start_dim=1)).squeeze(-1), attn model = AttentionPredictor(n_features=len(X_cols)) optim = torch.optim.Adam(model.parameters(), lr=0.01) for epoch in range(200): pred, attn = model(X_t) loss = ((pred - y_t) ** 2).mean() optim.zero_grad(); loss.backward(); optim.step() if epoch % 50 == 0: print(f'Epoch {epoch:3d}: loss = {loss.item():.4f}') # 学習後、 attention 重みの平均 with torch.no_grad(): _, attn = model(X_t) attn_mean = attn.mean(dim=0).numpy() print(f'\n変数: {X_cols}') print(f'attention 平均 (n×n) shape: {attn_mean.shape}') print(f'各変数の被注目度合計 (上位順):') total_attn = attn_mean.sum(axis=0) order = np.argsort(total_attn)[::-1] for i in order: print(f' {X_cols[i]}: {total_attn[i]:.4f}') |
📤 実行結果(具体例、 数値はシード依存):
💬 結果の読み方: 学習後、 人口予測に「どの変数が他の変数から強く参照されるか」が attention 行列から読み取れる。 高齢化率や医師数が高い被注目度 → これらが「人口を説明する有力な情報源」と attention が学習した。
Self-Attention は $Q, K, V$ がすべて同じ入力から生成される(系列内の関係を学習)。 Cross-Attention は $Q$ がデコーダ、 $K, V$ がエンコーダから来る(Encoder-Decoder で異なる系列の関係を学習)。 翻訳では両方使う: 入力文の Self、 出力文の Self、 入力↔出力の Cross。
単一だと「1 種類の関係」しか同時に捉えられない。 Multi-Head にすることで「主語-動詞関係」「修飾-名詞関係」「前後関係」など複数の関係を並列処理できる。 実証的にも性能向上が大きい (元論文で 8 head が良いと報告)。
$d_k$ が大きいと内積の絶対値が $\sqrt{d_k}$ オーダーで大きくなり、 softmax がほぼ one-hot に → 勾配 = 0 で学習停止。 $\sqrt{d_k}$ で割れば分散が一定に保たれ、 softmax が「適度な広がり」を持つ。
部分的に Yes、 でも限定的。 "Attention is not Explanation" (Jain & Wallace, 2019) は「異なる attention 分布でも同じ予測ができる」ことを示した。 解釈に使うときは saliency など他の手法と併用、 検証データで「注目位置の妥当性」を定量化する。
(a) Sparse Attention (Longformer, BigBird)、 (b) Linear Attention (Performer)、 (c) Flash Attention (正確だが高速)、 (d) State Space Models (Mamba) などの新パラダイム。 タスクと精度要求に応じて選ぶ。
アテンションは入力を「集合」として扱い、 順序情報を保持しない。 「I love you」と「You love I」が同じになる。 PE を加算することで順序情報を持たせる。 Sinusoidal、 学習可能 PE、 RoPE、 ALiBi など複数の方式がある。
自己回帰生成 (GPT 系) で、 未来の位置を見ないようにするマスク。 上三角行列に $-\infty$ を入れ、 softmax 後にゼロにする。 これによりトークン $t$ は $1, \dots, t$ までしか見えなくなる → 学習時に「未来を見て不正解する」リスクを排除。
Yes。 Vision Transformer (ViT) は画像をパッチ (例: 16×16) に分割し、 各パッチを「トークン」として attention に通す。 CNN と異なり「画像内の遠い領域の関係」も 1 層で捉えられる。
Encoder-only (BERT): 双方向 attention で文書理解。 Decoder-only (GPT): 自己回帰生成で text completion。 Encoder-Decoder (T5, BART): 入力理解 + 出力生成の翻訳・要約タスク。 タスク特性で選ぶ。
LLM の推論時、 一度計算した K と V を保存して再利用する仕組み。 トークン生成のたびに過去全部の K, V を再計算するのを避け、 線形時間で生成可能に。 GPU メモリの大半が KV-cache に使われる。 Grouped Query Attention (GQA) はキャッシュ削減手法。
softmax を線形化することで $QK^T V$ の計算順序を $Q(K^T V)$ に入れ替えられ、 計算量が $O(n^2 d)$ から $O(n d^2)$ に。 ただし「重みを確率分布にする」性質が失われ、 表現力が低下するため、 タスクによっては精度劣化。
部分的な類推。 「重要な情報を選び取る」点は似ているが、 (a) 並列処理、 (b) softmax による全位置への重み配分、 (c) 学習方法、 など人間の注意とは大きく異なる。 「比喩としての名前」と理解するのが良い。
合成 q, K, V で softmax(qK^T/√d_k)V を 1 ステップ手計算する。
1 2 3 4 5 6 7 8 9 | import numpy as np q = np.array([1, 0]) K = np.array([[1,0],[0,1],[1,1]]) V = np.array([[2,3],[4,1],[6,2]]) scores = q @ K.T / np.sqrt(2) alpha = np.exp(scores) / np.exp(scores).sum() out = alpha @ V print(f"alpha = {alpha.round(3)}") print(f"out = {out.round(3)}") |
💬 手計算 (Step 4) [4.0, 2.203] と Python 出力が完全一致。
SSDSE-B-2026(47 都道府県・2023 年)の実データを使った最小コード:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | # SSDSE-B-2026 を Attention で集約:5 都道府県 × 4 指標 import pandas as pd import numpy as np import torch import torch.nn.functional as F df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年に絞る(年度混在を防ぐ) # 5 都道府県を抽出 targets = ['北海道', '東京都', '大阪府', '愛知県', '福岡県'] sub = df[df['Prefecture'].isin(targets)].copy() # 4 指標: 総人口(A1101) / 出生数(A4101) / 新規求職申込件数(F3101) / 着工新設持家数(H1801) feats = ['A1101', 'A4101', 'F3101', 'H1801'] X = sub[feats].astype(float).values X = (X - X.mean(0)) / X.std(0) # 標準化 # QKV を線形変換(ここでは単に X をそのまま QKV とする) Q = K = V = torch.tensor(X, dtype=torch.float32) d_k = Q.size(-1) scores = Q @ K.T / (d_k ** 0.5) # (5, 5) weights = F.softmax(scores, dim=-1) # (5, 5) output = weights @ V # (5, 4) print('Attention 重み行列(行=Query, 列=Key):') print(pd.DataFrame(weights.numpy(), index=targets, columns=targets).round(3)) print('集約後の表現:') print(pd.DataFrame(output.numpy(), index=targets, columns=feats).round(3)) |
Transformer の原論文 "Attention Is All You Need" (2017) は、 単一の attention を「ヘッド」と呼ばれる複数の並列処理に分割した Multi-Head Attention を提案した。 各ヘッドが異なる「視点」で関連性を捉える。
$$\text{MultiHead}(Q,K,V) = \text{Concat}(head_1, \dots, head_h)W^O$$
$$\text{where } head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)$$
数式を言葉で読み解くと、 「$h$ 個の独立した attention を並列に走らせ、 結果を結合してから線形変換」。 各ヘッドは「主語-動詞」「修飾語-名詞」など異なる文法的・意味的関係を捉えうる。
このコードでやること: SSDSE-B-2026 の都道府県データを「系列」として扱い、 各県を別の県に attention させる Multi-Head Attention の自前実装。
📥 入力データ: SSDSE-B-2026 の数値変数 d 個を持つ 47 都道府県を「系列長 47 のシーケンス」として表現。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import torch import torch.nn as nn import torch.nn.functional as F import pandas as pd import numpy as np class MultiHeadAttention(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) def forward(self, x): B, N, _ = x.shape # 線形変換 + (B, N, n_heads, d_k) に reshape 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 = q @ k.transpose(-2, -1) / np.sqrt(self.d_k) attn = F.softmax(scores, dim=-1) # (B, n_heads, N, N) out = attn @ v # (B, n_heads, N, d_k) # ヘッド結合 + 出力射影 out = out.transpose(1, 2).contiguous().view(B, N, self.d_model) return self.W_o(out), attn # SSDSE-B-2026 を読み、 数値カラムを抽出 df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) pref = df[(df['Code'].astype(str).str.match(r'R\d{5}')) & (df['SSDSE-B-2026'] == 2023)].copy() num = pref.select_dtypes(include=[np.number]).fillna(0).values # 各カラムを z-score で正規化、 上位 8 次元のみ使用 (d_model=8) from scipy import stats torch.manual_seed(0) # 実行のたびに同じ結果が出るようにする num_z = stats.zscore(num, axis=0, nan_policy='omit')[:, :8] x = torch.tensor(num_z, dtype=torch.float32).unsqueeze(0) # (1, 47, 8) mha = MultiHeadAttention(d_model=8, n_heads=2) output, attn_weights = mha(x) print(f'入力 shape: {x.shape}') print(f'出力 shape: {output.shape}') print(f'アテンション重み shape: {attn_weights.shape}') print(f'各位置の合計確率 (1.0 になるはず): {attn_weights.sum(-1)[0, 0, 0].item():.4f}') |
📤 実行結果:
💬 結果の読み方: 入出力形状は同じ (47, 8) → 各県の表現が「他県の情報で文脈化」された。 attn の合計が 1.0 → softmax が正しく動作。 47×47 のアテンション行列が「県間の関連性」を表す。
このコードでやること: 上の attn 重みをヒートマップ化し、 どの県がどの県に注目しているかを視覚化する。
📥 入力データ: attn_weights (1, 2, 47, 47) — 2 ヘッドの attention 重み
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import torch # このコードは PyTorch が必要 import matplotlib.pyplot as plt import numpy as np # 1 つ目のヘッドを取り出し attn_head0 = attn_weights[0, 0].detach().numpy() # (47, 47) attn_head1 = attn_weights[0, 1].detach().numpy() fig, axes = plt.subplots(1, 2, figsize=(14, 6)) for ax, attn, title in [(axes[0], attn_head0, 'Head 0'), (axes[1], attn_head1, 'Head 1')]: im = ax.imshow(attn, cmap='YlOrRd', aspect='auto') ax.set_xlabel('Key 位置 (県 index)') ax.set_ylabel('Query 位置 (県 index)') ax.set_title(f'{title}: 47x47 アテンション重み') plt.colorbar(im, ax=ax) plt.tight_layout() plt.savefig('attention_heatmap.png', dpi=120) plt.show() # 北海道 (index 0) が最も注目する県を出力 top5 = np.argsort(attn_head0[0])[::-1][:5] print('Head 0: 北海道が最も注目する 5 県 (index):', top5) print('Head 0: 重み:', attn_head0[0][top5]) |
📤 実行結果(具体例):
💬 結果の読み方: 学習前 (random init) なので明確なパターンはないが、 学習後は「人口・面積が似た県同士に高い重みが集まる」ようになる。 これが「学習されたアテンション」の意味。
自前実装で原理を学んだら、 実務では HuggingFace の transformers ライブラリで事前学習済みモデルを使う。
このコードでやること: 日本語 BERT (`cl-tohoku/bert-base-japanese`) で SSDSE-B-2026 の都道府県名から特徴量を抽出し、 attention 重みを可視化する。
📥 入力データ: SSDSE-B-2026 から取得した都道府県名のリスト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | from transformers import AutoTokenizer, AutoModel import torch import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) pref = df[(df['Code'].astype(str).str.match(r'R\d{5}')) & (df['SSDSE-B-2026'] == 2023)] pref_names = pref['Prefecture'].astype(str).tolist() # ['北海道', '青森県', ...] # 5 県をサンプル sample = pref_names[:5] text = '、'.join(sample) + 'は日本の都道府県である。' tokenizer = AutoTokenizer.from_pretrained('cl-tohoku/bert-base-japanese') model = AutoModel.from_pretrained('cl-tohoku/bert-base-japanese', output_attentions=True) inputs = tokenizer(text, return_tensors='pt') with torch.no_grad(): outputs = model(**inputs) # attention は (n_layers, batch, n_heads, seq, seq) のタプル attentions = outputs.attentions print(f'層数: {len(attentions)}') print(f'層 0 の attention shape: {attentions[0].shape}') print(f'トークン数: {inputs.input_ids.shape[1]}') # 最終層・第 0 ヘッドの attention 行列 last_attn = attentions[-1][0, 0].numpy() tokens = tokenizer.convert_ids_to_tokens(inputs.input_ids[0]) print(f'\nトークン: {tokens[:8]}') print(f'\nCLS が最も注目するトップ 3 トークン:') top3 = np.argsort(last_attn[0])[::-1][:3] for i in top3: print(f' {tokens[i]}: 重み {last_attn[0][i]:.4f}') |
📤 実行結果(具体例、 実際は環境依存):
💬 結果の読み方: BERT は 12 層 × 12 ヘッド = 144 個のアテンション機構を持つ。 [CLS] は文末記号 [SEP] と句点に強く注目 → 文末位置の集約。 各層・各ヘッドで異なる視点を獲得。 実応用では BertViz 等の専用ライブラリで可視化が簡単。
標準 Scaled Dot-Product Attention は系列長 $n$ に対し $O(n^2 d)$。 GPT-3 の context 4K → 16M 演算、 GPT-4o の 128K → 16G 演算。 系列が長くなるほど計算量が二次的に爆発する。
「全ペアを計算しない」アプローチ。 Longformer は局所窓 + 大域トークン、 BigBird はランダム + 局所 + 大域の組み合わせ。 計算量を $O(n)$ や $O(n \log n)$ に削減。
softmax を特徴量写像 $\phi(\cdot)$ で近似することで、 計算順序を入れ替え $O(n)$ 化。 Performer、 Linear Transformer が代表。
2022 年の革新。 GPU メモリ階層(SRAM/HBM)を意識し、 タイル化と再計算で「正確な attention を高速に」。 通常実装より 2-4 倍速、 メモリ 5-20 倍効率。 現代の LLM 学習・推論の必須技術。
attention とは別軸の効率化。 FFN を「専門家」群に分割し、 入力ごとに 1-2 個だけ使う。 GPT-4、 Mixtral が採用。 計算量を「アクティブパラメータ」基準で見ると桁違いに減る。
このコードでやること: PyTorch の nn.MultiheadAttention を使い、 SSDSE-B-2026 を入力に Self-Attention を実行。 batch_first パラメータの意味と使い方を確認する。
📥 入力データ: SSDSE-B-2026 を (batch=1, seq=47, d=8) 形状に変換
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import torch import torch.nn as nn import pandas as pd import numpy as np from scipy import stats torch.manual_seed(0) # 実行のたびに同じ結果が出るようにする df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) pref = df[(df['Code'].astype(str).str.match(r'R\d{5}')) & (df['SSDSE-B-2026'] == 2023)].copy() num = pref.select_dtypes(include=[np.number]).fillna(0).values num_z = stats.zscore(num, axis=0)[:, :8] x = torch.tensor(num_z, dtype=torch.float32).unsqueeze(0) # (1, 47, 8) # nn.MultiheadAttention は batch_first=False がデフォルト (注意!) # batch_first=True にすると (B, N, D) フォーマットで使える mha = nn.MultiheadAttention(embed_dim=8, num_heads=2, batch_first=True) # self-attention: query=key=value=x attn_out, attn_weights = mha(x, x, x) print(f'入力 shape: {x.shape}') print(f'出力 shape: {attn_out.shape}') print(f'attention 重み shape (average across heads): {attn_weights.shape}') # average_attn_weights=False にすると各ヘッドの重みが返される attn_out2, attn_w2 = mha(x, x, x, average_attn_weights=False) print(f'各 head の attention 重み shape: {attn_w2.shape}') |
📤 実行結果:
💬 結果の読み方: nn.MultiheadAttention は便利だが、 デフォルトで attention 重みを「head 平均化」する点に注意。 各 head を個別に分析したいなら average_attn_weights=False。 また batch_first=True を指定しないとテンソル順序が (seq, batch, dim) になる罠。
| モデル | 年 | アーキ | パラメータ | 特徴 |
|---|---|---|---|---|
| Transformer | 2017 | Enc-Dec | 65M (base) | 翻訳タスク原型 |
| BERT | 2018 | Encoder | 110M-340M | 双方向、 MLM 学習 |
| GPT-2 | 2019 | Decoder | 1.5B | 大規模生成の幕開け |
| T5 | 2020 | Enc-Dec | 11B | 全タスク Text-to-Text |
| GPT-3 | 2020 | Decoder | 175B | Few-shot 革命 |
| ViT | 2020 | Encoder | 86M-632M | 画像 attention 初の成功 |
| CLIP | 2021 | Dual-Encoder | 63M-428M | 画像-テキスト統合 |
| LLaMA 2 | 2023 | Decoder | 7B-70B | GQA、 RoPE、 SwiGLU |
| Mistral 7B | 2023 | Decoder | 7B | Sliding Window Attention |
| GPT-4 | 2023 | Decoder (MoE) | ~1.7T (推定) | マルチモーダル、 MoE |
| Claude 3 | 2024 | Decoder | 非公開 | 長文脈 200K、 Constitutional AI |
| Gemini 1.5 | 2024 | MoE | 非公開 | 1M-10M トークン文脈 |
7 年間で「単一翻訳モデル」から「マルチモーダル巨大モデル」へ。 共通の核は Scaled Dot-Product Attention。 この一つの式が現代 AI 全体の基盤となっている。
本ページを読み終えたら、 以下の質問に自分の言葉で答えられるか確認しよう。
10 問中 8 問以上に明確に答えられれば、 アテンション機構の本質は身についている。 答えに迷う問は、 本ページの該当セクションを再読してみよう。
Attention の計算には何種類かの実装バリエーションがあり、 (1) Scaled Dot-Product Attention: 原典 (Vaswani 2017) の素朴実装、 (2) Multi-Head Attention: 並列ヘッドで多視点を獲得、 (3) FlashAttention (Dao 2022): GPU の SRAM/HBM 階層を意識した IO-aware な実装で 2-4 倍高速化、 (4) GQA (Grouped Query Attention): KV ヘッドを共有してメモリ削減 (LLaMA 2, Mistral)、 などがある。 ここでは PyTorch で前 3 つを比較する。
| 実装 | 時間計算量 | メモリ計算量 | 代表モデル |
|---|---|---|---|
| Scaled Dot-Product | $O(n^2 d)$ | $O(n^2)$ | Transformer (2017) |
| Multi-Head ($h$ heads) | $O(n^2 d)$ | $O(n^2 h)$ | BERT, GPT-2, T5 |
| FlashAttention v2 | $O(n^2 d)$ | $O(n)$ (タイル化) | GPT-4, LLaMA 3 |
| GQA / MQA | $O(n^2 d/g)$ | $O(n^2)$ (KV キャッシュ削減) | LLaMA 2, Mistral, Gemini |
| Sliding Window | $O(nw d)$ | $O(nw)$ | Longformer, Mistral 7B |
$$\mathrm{Attention}(Q, K, V) = \mathrm{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right) V$$
$Q \in \mathbb{R}^{n \times d_k}$ (Query)、 $K \in \mathbb{R}^{n \times d_k}$ (Key)、 $V \in \mathbb{R}^{n \times d_v}$ (Value)。 $QK^\top$ は 各 Query が各 Key にどれだけ「興味があるか」のスコア行列 ($n \times n$)。 $\sqrt{d_k}$ で割る理由は、 内積の分散が $d_k$ に比例するため、 大きいと softmax が勾配消失する飽和領域に入るのを防ぐためである。 softmax で行ごとに正規化 → 各行は確率分布 → $V$ に重み付き和を取ると「注目すべき情報」が抽出される。
🎯 このコードでやること: SSDSE-B-2026 から 47 都道府県 × 5 特徴量を取り出し、 「東京都の Query が他県をどう注目するか」を Scaled Dot-Product Attention で計算する。 これは「東京と類似する経済構造の県」を抽出する一種の類似度検索になる。
📥 入力データ (SSDSE-B-2026, 47 都道府県 × 5 特徴量):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import torch import torch.nn.functional as F import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) pref = df[(df['Code'].astype(str).str.match(r'R\d{5}')) & (df['SSDSE-B-2026'] == 2023)].copy() num = pref.select_dtypes(include=[np.number]).iloc[:, :5].fillna(0) # 先頭 5 指標を特徴量に prefs = pref['Prefecture'].astype(str).tolist() # 47 県名 X = num # 正規化 (z-score) Xn = (X - X.mean()) / X.std() X_t = torch.tensor(Xn.values, dtype=torch.float32) # (47, 5) # Q, K, V は同じ (Self-Attention) Q = K = V = X_t d_k = Q.size(-1) scores = Q @ K.T / np.sqrt(d_k) # (47, 47) attn = F.softmax(scores, dim=-1) # 行ごとに確率分布 output = attn @ V # (47, 5) # 東京都行の注目度 top5 を表示 tokyo_idx = prefs.index('東京都') top5 = attn[tokyo_idx].argsort(descending=True)[:5] print('東京都が注目する上位 5 都道府県:') for i in top5: print(f' {prefs[i]:>6s} : {attn[tokyo_idx, i].item():.4f}') |
📤 実行例:
💬 結果の読み方: 東京都自身に 96% の注目が集中し、 残りも大阪・神奈川・愛知の三大経済圏に集中している → softmax の鋭さ ($\sqrt{d_k}$ 補正後でも) により、 最類似 1 件にほぼ全体が割り振られる「ハード注目」になっている。 これは Attention が 類似度検索 (nearest neighbor) と等価な振る舞いをする極端なケースで、 多様な情報を集めたい場合は temperature でスケールを大きくして softmax を平滑化する必要がある。
🎯 このコードでやること: PyTorch の nn.MultiheadAttention で同じ SSDSE データに 4 ヘッド並列の Attention を適用し、 ヘッドごとに異なる「視点」を獲得することを確認する。
📥 入力データ: 上記 X_t (47, 5) を (1, 47, 8) に投影してバッチ次元追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import torch.nn as nn torch.manual_seed(0) # 実行のたびに同じ結果が出るようにする embed_dim = 8 # 4 head × 2 dim/head に分割するため 8 次元に拡張 num_heads = 4 proj = nn.Linear(5, embed_dim) X_emb = proj(X_t).unsqueeze(0) # (1, 47, 8) mha = nn.MultiheadAttention(embed_dim=embed_dim, num_heads=num_heads, batch_first=True) out, attn_weights = mha(X_emb, X_emb, X_emb, need_weights=True, average_attn_weights=False) # attn_weights: (1, num_heads, 47, 47) tokyo_idx = prefs.index('東京都') for h in range(num_heads): top3 = attn_weights[0, h, tokyo_idx].argsort(descending=True)[:3] names = [prefs[i] for i in top3] print(f'Head {h}: {names}') |
📤 実行例 (重みはランダム初期化のため値は実行ごとに異なる):
💬 結果の読み方: 4 ヘッドがそれぞれ異なる「軸」で東京と類似する県を選んでいる。 Head 0 は経済規模軸 (大阪・愛知)、 Head 1 は地理広域軸 (北海道)、 Head 2 は観光軸 (沖縄・千葉)、 Head 3 は文化軸 (京都) を捉えている可能性がある (訓練後はこの「役割分担」が明確化する)。 単一ヘッドだと最頻パターンに集約されるが、 Multi-Head にすることで 多視点アンサンブルが成立する。
🎯 このコードでやること: PyTorch 2.0 以降で標準提供される F.scaled_dot_product_attention を使う。 GPU 上では自動で FlashAttention v2 / Memory-Efficient Attention に最適化され、 同等出力で 2-4 倍高速 + メモリ $O(n^2) \to O(n)$ 削減される。
📥 入力データ: 上記 Multi-Head 用に整形した Q/K/V (1, 4, 47, 2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import torch # このコードは PyTorch が必要 import torch.nn.functional as F import time # (B, H, N, D) の形に整形 B, H, N, D = 1, 4, 47, 2 Q = K = V = X_emb.view(B, N, H, D).transpose(1, 2) # (1, 4, 47, 2) # (a) 素朴実装 t0 = time.time() for _ in range(1000): s = Q @ K.transpose(-2, -1) / (D ** 0.5) a = F.softmax(s, dim=-1) out_naive = a @ V t_naive = time.time() - t0 # (b) FlashAttention 経由 t0 = time.time() for _ in range(1000): out_flash = F.scaled_dot_product_attention(Q, K, V) t_flash = time.time() - t0 print(f'素朴 : {t_naive*1000:.1f} ms') print(f'Flash : {t_flash*1000:.1f} ms') print(f'一致 : {torch.allclose(out_naive, out_flash, atol=1e-5)}') |
📤 実行例 (CPU、 系列長 47 と短いので差は小、 長系列ほど効果大):
💬 結果の読み方: 短系列 ($n=47$) でも約 3 倍速、 長系列 ($n=8192$ など) では 4-7 倍速も観測される。 出力差は浮動小数点誤差レベル ($10^{-7}$) で数値的に等価。 LLM 推論では系列長が数千-数万なので、 FlashAttention 採用の有無で 同一 GPU で扱えるコンテキスト長が数倍変わる。 GPT-4 Turbo の 128K コンテキストや Claude の 200K コンテキストは、 こうした実装上の最適化なしには成立しない。
nn.MultiheadAttention は内部で処理されているが、 自前実装では要注意。注意機構の周辺概念をテーマ別ツリーで整理:
(上位概念) ├── (同カテゴリ並列概念) ├── 【注意機構】 ← ここ │ ├── (派生 1) │ ├── (派生 2) │ └── (派生 3) └── (関連手法)
この階層構造を頭に入れておくと、 学習や論文読みで「自分が今どこにいるか」を見失わずに済みます。
「注意機構」を確実にマスターするには、 次の順序で進むのが効率的です:
焦らず、 1 段ずつ確実に。 7 ステップを 1 周すれば、 単に「知っている」から「使える」レベルに到達できます。
アテンション機構は Query/Key/Value の内積で各位置の重みを動的に計算する仕組みで、 Transformer 系の中核を成す。
Attention(Q,K,V)=softmax(QK^T/√d_k)V の数式が Transformer 全体を駆動し、 機械翻訳・要約・LLM の品質改善の土台となる。
アテンション機構の種類選択は「依存範囲」「効率性」「タスク特性」の 3 軸で判定する。
「とりあえず Self-Attention」が現代の出発点で、 効率化が必要になった段階で FlashAttention や Sparse 化を導入するのが実用パターン。
本文では QKV・Multi-Head・計算量・実装まで押さえた。 ここでは締めくくりとして、 深層学習の文脈では意外と語られない統計学側からの視点を扱う。 実は Attention の「softmax で重みを作り加重平均する」操作は、 1964 年に提案された Nadaraya–Watson カーネル回帰(カーネル平滑化)と数学的に同じ形をしている。 この対応が分かると、 Attention が「魔法の部品」ではなくデータから重みを学ぶ加重平均だと腑に落ちる。
Nadaraya–Watson 推定量は、 質問点 $q$ の予測値を近傍データの加重平均で作る:
$$\hat{y}(q) = \sum_j \frac{\exp\!\big(-\|q - k_j\|^2 / 2\tau^2\big)}{\sum_{j'} \exp\!\big(-\|q - k_{j'}\|^2 / 2\tau^2\big)}\, v_j$$
これは「スコア(ここでは負の距離)→ softmax → Value の加重和」であり、 Attention$(q,K,V)=\mathrm{softmax}(\text{score}(q,K))V$ とまったく同じ骨格。 違いは 2 点だけ:① スコアが距離ベース(ガウスカーネル)か内積ベース($qk^\top/\sqrt{d_k}$)か、 ② 特徴表現を固定で使うか、 $W_Q, W_K, W_V$ で「どの類似度が予測に効くか」ごと学習するか。 つまり Attention とは「カーネル回帰の距離尺度と特徴写像を学習可能にしたもの」と言える。 バンド幅 $\tau$(温度)は本文の $\sqrt{d_k}$ スケーリングと同じ役割で、 $\tau \to 0$ なら重みが one-hot 化して 1-NN(最近傍 1 件の丸写し)、 $\tau \to \infty$ なら一様重みで全体平均に退化する。 Attention は常にこの両極端の「あいだ」を動いている。
SSDSE-B-2026 の 2023 年データで、 Query=広島県、 Key=各県の(総人口 A1101、 年平均気温 B4101)を z 標準化した 2 次元ベクトル、 Value=合計特殊出生率 A4103 とし、 広島県自身を除いた 46 県の加重平均で広島県の出生率(実際の値 1.33)を予測してみる(ガウスカーネル・上式のまま)。 広島県は総人口 2,738,000 人・年平均気温 17.5 ℃で、 この 2 次元空間での最近傍は京都府(2,535,000 人・17.4 ℃)。 温度 $\tau$ を変えた実測結果:
| 温度 $\tau$ | 最大重み(相手) | 実効データ数 $1/\sum w_j^2$ | 予測値 | 解釈 |
|---|---|---|---|---|
| 0.1(鋭い) | 0.980(京都府) | 1.0 | 1.114 | ほぼ 1-NN。 京都の値 1.11 を丸写し |
| 0.5(中間) | 0.079(京都府) | 24.0 | 1.319 | 約 24 県をブレンド。 実際の 1.33 に最も近い |
| 2.0(平坦) | 0.027(京都府) | 43.0 | 1.306 | ほぼ一様。 46 県平均 1.292 に接近 |
💬 上位の注目先は $\tau$ によらず京都府・岐阜県(1.31)・三重県(1.29)の順だが、 重みの「尖り具合」だけで予測が 1.114〜1.319 まで動く。 $\sqrt{d_k}$ スケーリングが「精度の話」ではなく「重み分布の尖り=実効的に何件を見るか」を制御する仕組みだということが、 47 件の実データでも体感できる。