はじめに
前回の記事では,Transformerの心臓部であるAttention機構の基本的な仕組みを学びました.しかし,強力なエンジンだけでは,最高のレーシングカーは作れません.優れたシャーシやサスペンションがあってこそ,その性能を最大限に引き出せますよね.
今回の記事では,前回学んだAttention機構に,さらに2つの重要な部品を付け加えていきます.そして,Transformerを支える屈強な基本ユニットである**「Encoderブロック」を,皆さんと一緒に完成**させることを目指します!
この記事を最後まで読めば,以下の3つの部品がどのように組み合わさり,一つの「Transformerブロック」が完成するまでの全貌が,数式と実装の両面から理解できます.
- 部品1:Multi-Head Attention(エンジンを強化し,複数の視点で情報を捉える)
- 部品2:Residual Connection (Add)(情報の流れをスムーズにするバイパス経路)
- 部品3:Layer Normalization (Norm)(性能を安定させるための調整機構)
この記事の対象読者
- QKV Attentionの基本を理解している方(前回の記事を読んだ方推奨!)
- Transformerの内部構造を,もう一歩深く知りたい方
- 数式とPyTorchのコードがどう繋がっているのかを理解したい方
さあ,準備はいいですか?
早速,最初の部品である「Multi-Head Attention」から組み立てていきましょう!
そもそも,なぜエンコーダーは必要なのか?
私たちはこれまでTransformerブロックという部品を見てきましたが,そもそもなぜTransformerは「エンコーダー(Encoder)」と「デコーダー(Decoder)」に分かれているのでしょうか?なぜ入力をそのままデコーダーで処理しないのでしょうか?
このセクションでは,翻訳タスクを例に,エンコーダーの非常に重要な「存在意義」について考えてみましょう.
エンコーダーのたった一つのミッション
エンコーダーのミッションは,ただ一つです.
入力された文章(例:「私は猫です」)の『文脈的な意味』を完全に理解し,コンピュータが扱いやすい数値表現(ベクトル)に変換すること.
この「文脈的な意味を理解する」というのが最大のポイントです.
もしエンコーダーがなかったら?
仮にエンコーダーがなく,デコーダーが直接「私」「は」「猫」「です」という単語のリストを受け取って,英語に翻訳しようとしたらどうなるでしょうか?
デコーダーは,出力単語("I", "am", "a", "cat")を一つ生成するたびに,入力文全体を何度も見返して「えーっと,この文の主語は?動詞は?文脈は?」と,毎回ゼロから意味を解釈し直さなければなりません.これは非常に非効率で,複雑な文のニュアンスを捉えるのは困難です.
優秀な通訳者に学ぶ「役割分担」
ここで,優秀な人間の通訳者をイメージしてみましょう.通訳者は,2つのステップで仕事をしています.
- 【意味理解フェーズ】: まず,話者(入力文)の言いたいことを,一字一句そのままではなく,その意図やニュアンス,文脈を完全に理解します. 頭の中で情報を整理し,どんな言語にでも訳せるような「概念」の状態にします.
- 【言語生成フェーズ】: 次に,ステップ1で理解した「概念」を,聞き手(ターゲット言語)に合わせて,自然な文法や語彙で文章として組み立て直します.
Transformerも,この役割分担を模倣しています.
- エンコーダー: 意味理解フェーズを担当.入力文を受け取り,その「概念」や「文脈」を凝縮した中間表現(ベクトル)を作り出すことに特化します.
- デコーダー: 言語生成フェーズを担当.エンコーダーが作った「概念(中間表現)」を受け取り,ターゲット言語の単語を一つずつ生成していくことに集中します.
どうやって文脈を理解するのか?
では,エンコーダーはどうやって文脈を理解するのでしょうか?
その答えが,まさに私たちが学んできたSelf-Attentionです.
エンコーダーは,入力された文章に対してSelf-Attentionを何度も(層の数だけ)繰り返します.
- 1層目: 各単語が,すぐ隣の単語との関係性を理解する.
- 2層目: 隣の隣の単語まで含めた,もう少し広い範囲の関係性を理解する.
- 層を重ねるごとに,どんどん視野が広がり,文全体の構造や複雑な依存関係を捉えた,非常に豊富な情報を持つベクトル表現が出来上がっていきます.
まとめ
つまり,エンコーダーはデコーダーが楽をするための,非常に優秀な「下ごしらえ」部隊なのです.入力文の意味を深く理解し,文脈情報を完璧に含んだお膳立て(中間表現)があるからこそ,デコーダーは高品質な翻訳や文章生成といったタスクに集中できる,というわけです.
この解説を挟むことで,「Multi-Head Attention」や「残差結合」といった部品が,「入力文の文脈を理解する」という大きな目的のために,どのように貢献しているのかが,より明確に繋がるはずです.
部品1:Multi-Head Attention (より賢い情報収集)
前回の記事で学んだAttentionは,1つの視点でしか情報の関連性を見ることができませんでした.これを「シングルヘッドAttention」と呼びます.
これに対しMulti-Head Attentionは,その名の通り,複数のヘッド(視点)で同時に情報の関連性を分析します.あるヘッドは「文法的な関係」に注目し,別のヘッドは「意味的な類似性」に注目する,といった分業が可能になり,より豊かで多角的な情報を捉えることができるのです.
Multi-Head Attentionの仕組み
Multi-Head Attentionは,大きく3つのステップで計算されます.
Step 1: 分割 (Linear Projections)
入力されたQ, K, Vは,まずヘッドごとに異なる重み行列 $W_i^Q, W_i^K, W_i^V$ を使って線形変換され,各ヘッドへの入力 $q_i, k_i, v_i$ が作られます.
$$
q_i = QW_i^Q, \quad k_i = KW_i^K, \quad v_i = VW_i^V \quad (i=1, ..., h)
$$
これは,入力情報を各ヘッド専用の「部分空間」へ変換する操作です.これにより,各ヘッドが異なる側面から情報を見る準備が整います.
Step 2: 並列Attention計算 (Parallel Scaled Dot-Product Attention)
各ヘッドで,前回学んだScaled Dot-Product Attentionが並列に実行されます.各ヘッドは異なる重み行列を持つため,それぞれが異なる「注目パターン」を学習します.
$$
\text{head}_i = \text{Attention}(q_i, k_i, v_i) = \text{softmax}(\frac{q_i k_i^T}{\sqrt{d_k}})v_i
$$
Step 3: 結合と最終線形変換 (Concatenate & Final Linear Layer)
並列計算された各ヘッドの出力を連結(Concatenate)し,最後にさらに別の重み行列 $W^O$ で線形変換します.複数の視点から得られた多様な情報を,ここで一つに統合し,後続の層が扱いやすい形に整えます.
$$
\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, ..., \text{head}_h)W^O
$$
部品2:残差結合 (Add) 〜情報の高速道路〜
Multi-Head Attentionは強力ですが,これを何層も積み重ねると,勾配がうまく伝わらなくなる「勾配消失問題」が起こりやすくなります.そこで登場するのが,残差結合(Residual Connection)です.
アイデアは驚くほどシンプルで,「層を通った後の出力」に「何もしなかった入力」を足し算するだけです.
数式で書くと,入力ベクトルを $x$ とすると,こうなります.
$$
\text{Output} = x + \text{Sublayer}(x)
$$
このシンプルな「足し算」には,2つの大きなメリットがあります.
- 勾配消失問題の緩和(情報の高速道路): 学習時,勾配(誤差情報)がこのショートカットを通って,深い層から浅い層までスムーズに伝わります.これにより,非常に深いネットワークでも安定して学習が進みます.
- 学習の効率化(「差分」の学習): 層(Sublayer)は,入力 $x$ を理想の出力 $y$ に変換するための「差分(Residual)」だけを学習することに集中できます.これにより学習の負担が減り,より効率的に最適な表現を獲得できます.「残差結合」という名前は,この「残差」を学習する点に由来しているのです.
部品3:Layer Normalization (Norm) 〜性能を安定させる調整役〜
最後の部品がLayer Normalizationです.これは,残差結合の直後に適用され,層への入力の分布を整える調整役の役割を果たします.
具体的には,入力ベクトルの平均を0,分散を1になるように正規化します.これにより,各層での値が暴走するのを防ぎ,学習プロセス全体を安定させ,高速化する効果があります.
完成!Transformer Encoderブロックの全体像
さあ,すべての部品が揃いました!これらを組み合わせると,ついにTransformer Encoderブロックが完成します.
流れは以下のようになります.
- 入力がMulti-Head Attention層に入る.
- MHAの出力に,入力がAdd(残差結合)され,Norm(Layer Normalization)される.
- その出力が,Feed-Forward Network(FFN: 各ベクトルをさらに賢くするための変換層)に入る.
- FFNの出力に,その入力がAddされ,Normされる.
- 最終的な出力が,次のEncoderブロック,あるいはDecoderブロックへと渡される.
このブロックを何層も積み重ねることで,Transformerは非常に高度な情報処理を実現しているのです.
PyTorchでの実装
これらの部品は,PyTorchでは便利なクラスとしてまとめられています.
torch.nn.TransformerEncoderLayer
を使えば,今まで解説してきた複雑な処理が一行で呼び出せます.
import torch
import torch.nn as nn
# パラメータ設定
d_model = 512 # モデルの次元数 (embed_dim)
nhead = 8 # ヘッドの数 (num_heads)
dim_feedforward = 2048 # Feed-Forward Networkの次元数
dropout = 0.1
# Transformer Encoderの1ブロックを定義
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=nhead,
dim_feedforward=dim_feedforward,
dropout=dropout,
batch_first=True # 入力形式を(バッチサイズ, シーケンス長, 次元)に
)
# ダミーの入力データ
src = torch.rand(32, 10, d_model) # (バッチサイズ=32, シーケンス長=10)
# 1ブロック通す
output = encoder_layer(src)
print("Input shape:", src.shape)
print("Output shape:", output.shape)
実行結果は以下の通りです.
Input shape: torch.Size([32, 10, 512])
Output shape: torch.Size([32, 10, 512])
実行結果から,TransformerEncoderLayer
を通しても,テンソルの形状が torch.Size([32, 10, 512])
のままであることが分かります.これは非常に重要なポイントです.Transformerの各ブロックは,入力されたシーケンスの各単語ベクトルの「意味」をより豊かにしますが,シーケンスの長さやベクトルの次元数は変えません.そのため,このブロックをレゴブロックのように何層も積み重ねることができるのです.
まとめ
今回は,3つの主要な部品を組み立てて,「Transformer Encoderブロック」を完成させました.
- 部品1:Multi-Head Attention: 複数の視点(ヘッド)で,単語間の関連性を多角的に捉える賢い情報収集役
- 部品2:Residual Connection (Add): 層が深くなっても情報や勾配が失われないようにする「情報の高速道路」
- 部品3:Layer Normalization (Norm): 学習を安定させるための「性能調整役」
これらの部品が互いに協力し合うことで,入力された文章の文脈を深く理解するための,強力で再利用可能なユニットが生まれます.
次のステップ
この記事で,Transformerの強力な構成単位であるEncoderブロックを完成させました.しかし,完全なTransformerモデルを理解するには,まだいくつか重要な部品が残っています.
- Positional Encoding(位置エンコーディング): ここまでの構造では,モデルは単語の「順序」を理解できません.「私は猫です」と「猫は私です」を区別できないのです.この問題を解決するのがPositional Encodingです.
- Decoderブロック: エンコーダーが理解した文脈情報を受け取り,実際に翻訳先の文章などを生成する役割を担います.
- Transformerモデル全体像: 完成したEncoderスタックとDecoderスタックが,最終的にどのようにつながって機能するのか.
次の記事では,これらの残された部品についても解説していきたいと思います.
最後までお読みいただきありがとうございました!