機械翻訳の初期のニューラルモデル(Seq2Seq)では、入力文全体を1つの固定長ベクトルに「圧縮」してから翻訳を生成していました。「I love machine learning」を翻訳するとき、4つの単語の意味を1つのベクトルに押し込めるのです。
短い文なら問題ありませんが、文が長くなると情報がボトルネックベクトルに詰め込みきれず、翻訳品質が急激に劣化しました。人間の翻訳者が長い文を訳すとき、原文の該当箇所を何度も見返すように、モデルも出力の各ステップで入力の関連する部分に注目できれば良いのではないか — この発想がAttention機構です。
2014年にBahdanauらが提案したAttention機構は、機械翻訳の性能を飛躍的に改善し、その後のTransformerアーキテクチャ(Vaswani et al., 2017)の基礎となりました。
Attention機構を理解すると、以下のことが可能になります。
- Transformerの理解: Self-Attentionの基盤となる概念
- 機械翻訳・要約の仕組み: なぜニューラル翻訳が高品質なのかの理解
- 可視化と解釈性: Attentionの重みからモデルの「注目箇所」を可視化
- 多様な応用: 画像キャプション、質問応答、音声認識での活用
本記事の内容
- Seq2Seqモデルのボトルネック問題
- Bahdanau Attention(加法的Attention)の理論
- Luong Attention(乗法的Attention)の理論
- Attentionの幾何学的解釈
- Pythonでの実装と可視化
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
- ニューラルネットワークの基礎 — MLPの構造
- 誤差逆伝播法の理論と実装 — 勾配計算
Seq2Seqモデルとその限界
エンコーダ・デコーダアーキテクチャ
Seq2Seq(Sequence-to-Sequence)モデルは、可変長の入力系列を可変長の出力系列に変換するモデルです。機械翻訳で「I love cats」→「猫が好きです」のように、入力と出力の長さが異なる問題に対応できます。
エンコーダ: 入力系列 $(\bm{x}_1, \bm{x}_2, \ldots, \bm{x}_T)$ を順番に処理し、最後の隠れ状態 $\bm{h}_T$ をコンテキストベクトル $\bm{c}$ とします。
$$ \bm{h}_t = f_\text{enc}(\bm{x}_t, \bm{h}_{t-1}), \quad \bm{c} = \bm{h}_T $$
デコーダ: コンテキストベクトル $\bm{c}$ を初期状態として、出力系列を1トークンずつ生成します。
$$
\bm{s}_t = f_\text{dec}(\bm{y}_{t-1}, \bm{s}_{t-1}, \bm{c}), \quad P(\bm{y}_t | \bm{y}_{ Seq2Seqの致命的な問題は、入力の全情報を固定長の1つのベクトル $\bm{c}$ に詰め込まなければならないことです。 入力が10単語でも100単語でも、同じ次元のベクトルで全情報を表現する必要があります。情報理論的に考えれば、入力が長くなるほど情報損失が増えるのは避けられません。 実験的にも、Cho et al. (2014) は入力文の長さが20〜30単語を超えると翻訳品質が急激に低下することを報告しました。 この問題を解決するために、デコーダの各ステップでエンコーダの全隠れ状態にアクセスし、関連する情報に動的に注目する仕組みが必要です。 Bahdanau et al. (2014) の提案は、デコーダが出力を生成する各ステップで、エンコーダの全隠れ状態の重み付き和をコンテキストとして使うことです。 固定ベクトル $\bm{c}$ の代わりに、デコーダの各時刻 $t$ に対してコンテキストベクトル $\bm{c}_t$ を計算します。 $$
\begin{equation}
\bm{c}_t = \sum_{j=1}^{T} \alpha_{tj} \bm{h}_j
\end{equation}
$$ $\alpha_{tj}$ はAttention重みで、「デコーダの時刻 $t$ で、エンコーダの時刻 $j$ の隠れ状態にどれだけ注目するか」を表します。 Attention重みは3ステップで計算されます。 ステップ1: アライメントスコアの計算 デコーダの前の隠れ状態 $\bm{s}_{t-1}$ とエンコーダの各隠れ状態 $\bm{h}_j$ の「関連度」を計算します。 $$
e_{tj} = \bm{v}^\top \tanh(\bm{W}_1 \bm{s}_{t-1} + \bm{W}_2 \bm{h}_j)
$$ $\bm{W}_1, \bm{W}_2, \bm{v}$ は学習可能なパラメータです。この形式は2つのベクトルの関連度を加算的(additive)に計算するため、加法的Attentionと呼ばれます。 ステップ2: Softmaxで正規化 $$
\alpha_{tj} = \frac{\exp(e_{tj})}{\sum_{k=1}^{T}\exp(e_{tk})}
$$ Softmaxにより $\alpha_{tj} \geq 0$ かつ $\sum_j \alpha_{tj} = 1$ が保証されます。つまり、Attention重みは確率分布です。 ステップ3: コンテキストベクトルの計算 $$
\bm{c}_t = \sum_{j=1}^{T} \alpha_{tj} \bm{h}_j
$$ Attention機構は「ソフトなアドレッシング」と理解できます。 メモリ(エンコーダの隠れ状態 $\bm{h}_1, \ldots, \bm{h}_T$)から情報を読み出すとき、ハードなアドレッシング(特定の1つを選ぶ)ではなく、全てのメモリセルの重み付き和を読み出します。重みが大きいメモリセルの情報が多く反映されます。 たとえば「I love cats」→「猫が好きです」の翻訳で、「猫」を出力するときには「cats」への Attention重みが高くなり、「好きです」を出力するときには「love」への重みが高くなります。 Luong et al. (2015) は、スコア関数の異なる3つのバリエーションを提案しました。 Dot Product(内積): $$
e_{tj} = \bm{s}_t^\top \bm{h}_j
$$ 最もシンプルで計算が速い。ただし、$\bm{s}_t$ と $\bm{h}_j$ の次元が同じである必要があります。 General(一般化内積): $$
e_{tj} = \bm{s}_t^\top \bm{W}_a \bm{h}_j
$$ $\bm{W}_a$ は学習可能な重み行列で、異なる次元のベクトル間のアライメントを学習できます。 Concat(加法的、Bahdanauと同様): $$
e_{tj} = \bm{v}^\top \tanh(\bm{W}_a [\bm{s}_t; \bm{h}_j])
$$ $[\bm{s}_t; \bm{h}_j]$ は2つのベクトルの連結です。 どちらの手法も実用的には同程度の性能を示しますが、乗法的(内積ベース)なスコア関数の方が計算効率が高く、後のTransformerのScaled Dot-Product Attentionにつながります。 Attention機構はより一般的に、Query、Key、Value の3つの要素で定式化できます。 $$
\begin{equation}
\text{Attention}(\bm{q}, \bm{K}, \bm{V}) = \sum_{j} \text{softmax}\left(\frac{\bm{q}^\top \bm{k}_j}{\sqrt{d_k}}\right) \bm{v}_j
\end{equation}
$$ $\sqrt{d_k}$ で割るスケーリングは、内積の大きさが $d_k$(キーの次元)に比例して大きくなり、Softmaxが飽和するのを防ぐためです。この定式化がScaled Dot-Product Attentionであり、Transformerの基盤になります。 Query, Key, Value の枠組みは、Attentionが本質的に情報検索のプロセスであることを明確にしています。Queryで「欲しい情報」を指定し、Keyとの類似度に基づいてValueから情報を取り出すのです。 この可視化から、3種類のAttention機構のスコア分布を比較できます。 Dot Product(左図): スケーリングなしの内積ベースのAttentionです。スコアの大きさが次元 $d_k$ に依存するため、Softmaxの出力が一部のキーに集中しやすくなっています。特定のペアに対する重みが極端に大きくなることがあります Scaled Dot Product(中央図): $\sqrt{d_k}$ でスケーリングすることで、Softmaxの出力がより均等に分布しています。重みの分布が滑らかになり、複数の入力位置から情報を取得できるようになっています。これがTransformerで採用されている方式です Additive/Bahdanau(右図): 非線形変換(tanh)を使ったスコア計算です。学習可能なパラメータが多い分、表現力は高いですが、計算コストもやや高くなります。実際のタスクでは、学習によって適切なAlignment(対応関係)が学習されます これらのAttention重みは、ランダム初期化された重みから計算しているため、実際の翻訳タスクでの対応関係を反映していません。学習後のAttention重みでは、対応する単語ペアに高い重みが集中するようになります。 Attention機構は機械翻訳から始まりましたが、現在では深層学習のほぼ全ての分野で使われています。 画像キャプション: CNNで画像の特徴マップを抽出し、キャプションの各単語を生成するときに画像の関連する領域にAttentionを向ける 質問応答: 質問文と文書の間でAttentionを計算し、答えが含まれる文書の領域を特定する 音声認識: 音声フレームとテキストの間でAttentionを計算し、各文字に対応する音声区間を特定する Self-Attention: 系列内部でAttentionを計算し、各要素が他の全要素との関係を学習する。Transformerの核心 次の記事では、このAttention機構を系列内部に適用するSelf-Attentionの理論と実装を詳しく解説します。 本記事では、Attention機構がなぜ必要とされるのかをSeq2Seqの限界から説明し、主要なAttentionの定式化を導出しました。 次のステップとして、以下の記事も参考にしてください。ボトルネック問題
Bahdanau Attention(加法的Attention)
基本的なアイデア
Attention重みの計算
Attentionの直感
Luong Attention(乗法的Attention)
3種類のスコア関数
Luong AttentionとBahdanau Attentionの違い
Bahdanau
Luong
スコア計算
$\bm{v}^\top \tanh(\bm{W}_1\bm{s}_{t-1} + \bm{W}_2\bm{h}_j)$
$\bm{s}_t^\top \bm{W}_a \bm{h}_j$
使用する状態
$\bm{s}_{t-1}$(前の時刻)
$\bm{s}_t$(現在の時刻)
計算効率
やや低い(非線形変換あり)
高い(行列積のみ)
Attentionの一般的な定式化
Query, Key, Value の枠組み
Pythonでの実装
Attention機構の動作を可視化する
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
def softmax(x, axis=-1):
"""数値安定なSoftmax"""
e = np.exp(x - np.max(x, axis=axis, keepdims=True))
return e / np.sum(e, axis=axis, keepdims=True)
# --- Attention機構のデモ ---
# 模擬的な機械翻訳のシナリオ
# 入力(英語): "I love machine learning"
# 出力(日本語): "私は 機械 学習が 好きです"
# エンコーダの隠れ状態(各単語のベクトル表現)
d_model = 8
T_enc = 4 # 入力の長さ
T_dec = 4 # 出力の長さ
# エンコーダの隠れ状態をランダム生成
H_enc = np.random.randn(T_enc, d_model) # (T_enc, d_model)
# デコーダの隠れ状態をランダム生成
S_dec = np.random.randn(T_dec, d_model) # (T_dec, d_model)
# --- 3種類のAttentionスコアを計算 ---
# (a) Dot Product Attention
scores_dot = S_dec @ H_enc.T # (T_dec, T_enc)
attn_dot = softmax(scores_dot, axis=-1)
# (b) Scaled Dot Product Attention
scores_scaled = (S_dec @ H_enc.T) / np.sqrt(d_model)
attn_scaled = softmax(scores_scaled, axis=-1)
# (c) Additive (Bahdanau) Attention
W1 = np.random.randn(d_model, d_model) * 0.3
W2 = np.random.randn(d_model, d_model) * 0.3
v = np.random.randn(d_model) * 0.3
scores_add = np.zeros((T_dec, T_enc))
for t in range(T_dec):
for j in range(T_enc):
scores_add[t, j] = v @ np.tanh(W1 @ S_dec[t] + W2 @ H_enc[j])
attn_add = softmax(scores_add, axis=-1)
# --- 可視化 ---
fig, axes = plt.subplots(1, 3, figsize=(16, 4.5))
src_words = ["I", "love", "machine", "learning"]
tgt_words = ["watashi", "kikai", "gakushu", "suki"]
for ax, attn, title in zip(axes,
[attn_dot, attn_scaled, attn_add],
["Dot Product", "Scaled Dot Product", "Additive (Bahdanau)"]):
im = ax.imshow(attn, cmap="YlOrRd", vmin=0, vmax=1, aspect="auto")
ax.set_xticks(range(T_enc))
ax.set_xticklabels(src_words, fontsize=11)
ax.set_yticks(range(T_dec))
ax.set_yticklabels(tgt_words, fontsize=11)
ax.set_xlabel("Source (English)", fontsize=12)
ax.set_ylabel("Target (Japanese)", fontsize=12)
ax.set_title(title, fontsize=13)
for i in range(T_dec):
for j in range(T_enc):
ax.text(j, i, f"{attn[i,j]:.2f}", ha="center", va="center",
fontsize=9, color="black" if attn[i,j] < 0.5 else "white")
plt.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
plt.tight_layout()
plt.savefig("attention_comparison.png", dpi=150, bbox_inches="tight")
plt.show()
Attentionの応用
機械翻訳以外の応用
まとめ