第11章 残差接続:入力をそのまま足し合わせる仕組み

こんにちは。ゆうせいです。

第10章までで、マルチヘッドアテンションによる出力が得られました。この出力は、そのまま次の処理に渡されるわけではありません。注意機構に入る前の入力が、出力に足し合わされてから、先に進みます。この足し算が、残差接続(Residual Connection)です。Transformer Explainerの図で、注意機構を迂回して、入力から出力へ直接つながっている線を見たことがあるかもしれません。あの線が、残差接続です。計算としては単なる足し算にすぎませんが、これがなければ、12層を積み重ねたモデルは学習できません。第11章では、この単純な足し算が、なぜそれほど重要なのかを解説します。

残差接続とは何か

残差接続とは、ある処理の出力に、その処理への入力をそのまま足し合わせる仕組みです。

出力 = 入力 + F(入力)

F は、間に挟まれた処理を表します。Transformerの場合、この F が、マルチヘッドアテンションや、第13章で扱うMLP層にあたります。

具体的に書くと、次のようになります。

x_{出力} = x_{入力} + Attention(x_{入力})

処理そのものは、要素ごとの足し算です。768次元のベクトルどうしを足すだけであり、新たなパラメータは一切必要ありません。

スキップ接続(Skip Connection)、ショートカット接続と呼ばれることもあります。いずれも同じ仕組みを指します。

なぜ足し算が必要なのか

処理を通した結果に、なぜわざわざ元の入力を足すのでしょうか。三つの理由があります。

理由1:勾配消失を防ぐため

これが最も重要な理由です。

第7章で、勾配が小さくなりすぎると学習が進まなくなることを説明しました。層を深く積み重ねると、この問題が深刻になります。

誤差逆伝播法では、出力側から入力側へ、微分の値を掛け算しながら伝えていきます。

\frac{\partial L}{\partial x_1} = \frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial h_2} \cdot \frac{\partial h_2}{\partial h_1} \cdot \frac{\partial h_1}{\partial x_1}

各層の微分値が1より小さければ、層を経るごとに、勾配は小さくなっていきます。

0.5という値を12回掛け合わせると、次のようになります。

0.5^{12} \approx 0.000244

入力層に近い層では、勾配がほぼ0になり、パラメータが更新されなくなります。

残差接続が勾配を守る仕組み

ここで、残差接続がある場合の微分を計算してみます。

y = x + F(x)

この式をxで微分します。

\frac{\partial y}{\partial x} = 1 + \frac{\partial F(x)}{\partial x}

注目すべきは、この式に「1」という項が含まれている点です。

\frac{\partial F(x)}{\partial x} がどれだけ小さくなっても、全体の微分値は1の周辺に留まります。0にはなりません。

したがって、層を何回経ても、勾配が掛け算によって消えていくことがありません。勾配が、出力側から入力側まで、減衰せずに届くのです。

この「1という項が保証される」という性質が、残差接続の本質です。

直感的な理解

別の言い方をすると、残差接続は、勾配が通る高速道路のようなものです。

通常の経路は、各層の処理を通るため、微分値の掛け算によって減衰します。しかし、足し算による経路は、微分値が1のまま、まっすぐ入力側まで届きます。

深い層のモデルであっても、この高速道路があるおかげで、入力に近い層まで学習の信号が届くのです。

理由2:何もしない状態を学習しやすくするため

二つ目の理由は、やや発展的ですが、重要な視点です。

残差接続がない場合、ある層が「何もしない」という振る舞いを実現するには、その層の変換が恒等変換になるよう、パラメータを調整する必要があります。これは、意外に難しい学習課題です。

残差接続がある場合、F(x) が0を出力すれば、全体としては y = x となり、何もしない状態になります。

F(x) が0を出力するようにパラメータを学習することは、恒等変換を学習することより、はるかに簡単です。重みを0に近づければよいだけだからです。

つまり、残差接続があると、モデルは「必要な層だけを使い、不要な層は素通りさせる」という選択をしやすくなります。

層を深くしても性能が悪化しにくいのは、この性質によるものです。使わない層は、素通りに近い状態に収束できます。

理由3:元の情報が失われないため

三つ目の理由は、情報の保存です。

自己注意の出力は、周囲のトークンの情報を混ぜ合わせたものです。この過程で、そのトークン自身が元々持っていた情報が、薄まってしまう可能性があります。

残差接続によって元の入力を足し戻すことで、そのトークン固有の情報が、確実に保持されます。

処理の結果として得られた新しい情報を、元の情報に「上書きする」のではなく「追加する」構造になっている、と理解してください。

残差という名前の意味

「残差」という言葉は、統計学では、予測値と実測値の差を指します。

残差接続における「残差」は、次の考え方から来ています。

y = x + F(x) という式を変形すると、次のようになります。

F(x) = y - x

つまり、F が学習しているのは、出力そのものではなく、出力と入力の差、すなわち残差なのです。

「入力を、どのように変化させるべきか」という差分だけを学習すればよい、という構造になっています。

ゼロから答えを作り出すより、現在の状態からの修正量を学ぶほうが、学習課題として易しい。この発想が、残差学習と呼ばれる考え方の核心です。

この仕組みは、Transformerで初めて登場したものではありません。画像認識の分野で、ResNetというモデルで導入され、深いネットワークの学習を可能にした技術です。Transformerは、この成果を取り入れています。

Transformerにおける残差接続の配置

Transformerの1つのブロックには、残差接続が二か所あります。

一か所目は、マルチヘッドアテンションの周囲です。

x' = x + Attention(LayerNorm(x))

二か所目は、MLP層の周囲です。

x'' = x' + MLP(LayerNorm(x'))

LayerNormは、第12章で扱うレイヤー正規化です。ここでは、その位置だけ確認しておいてください。

つまり、1つのブロックを通るたびに、2回の足し算が行われます。ブロックが12個ありますから、モデル全体では24回の残差接続があることになります。

残差ストリームという見方

近年の研究では、この一連の足し算の流れを、残差ストリーム(Residual Stream)と呼ぶ見方が広まっています。

入力から出力まで、768次元のベクトルが一本の流れとして貫いており、各層はその流れに情報を書き加えていく、という捉え方です。

注意機構は、その流れに「他のトークンから集めた情報」を書き加えます。MLP層は、「そのトークン内部で加工した情報」を書き加えます。

流れそのものは、最初から最後まで途切れません。層は、流れを置き換えるのではなく、流れに追記していく存在である、というイメージです。

この見方は、モデルの内部で何が起きているかを解析する研究において、有用な枠組みとして使われています。

実装上の注意点

形が一致していなければならない

残差接続は、要素ごとの足し算です。したがって、足し合わせる二つのベクトルは、形が完全に一致していなければなりません。

Transformerでは、各層の入出力の次元数が、すべて768で統一されています。この統一があるからこそ、残差接続が単純な足し算として実現できるのです。

第10章で、出力射影が768行768列の行列であったことを思い出してください。入力と同じ次元数に戻すことが、残差接続の前提として必要だったのです。

なお、画像認識のモデルなど、層によって次元数が変わる場合には、次元を合わせるための追加の変換が必要になります。Transformerでは、その必要がありません。

Pre-LNとPost-LN

残差接続と正規化の順序には、二つの方式があります。

Post-LN方式では、残差接続の後に正規化を行います。

x' = LayerNorm(x + Attention(x))

Pre-LN方式では、処理の前に正規化を行い、残差接続はその外側で行います。

x' = x + Attention(LayerNorm(x))

最初のTransformer論文はPost-LN方式でしたが、GPT-2はPre-LN方式を採用しています。

Pre-LN方式の利点は、学習が安定しやすい点です。残差接続の経路に正規化が挟まらないため、勾配がより素直に伝わります。

現在の多くのモデルは、Pre-LN方式を採用しています。この違いは、第12章であらためて説明します。

Transformer Explainerで観察できること

Transformer Explainerでは、注意機構の出力に入力が足し合わされる部分が、図として表現されています。

次の点を確認してみてください。

一つ目は、注意機構を迂回する線が存在するという点です。この線が、残差接続を表しています。

二つ目は、足し合わされた後のベクトルが、依然として768次元であるという点です。足し算では次元数が変わりません。

三つ目は、この経路にパラメータが存在しないという点です。学習される重みは含まれておらず、純粋な足し算だけが行われます。

初学者がつまずきやすい点

つまずき1:足し算ではなく連結だと誤解する

「入力と出力を組み合わせる」と聞くと、横につなげる(連結する)操作を想像する人がいます。

残差接続は、連結ではなく加算です。連結であれば次元数が1536に倍増しますが、加算では768のまま変わりません。

第4章の位置エンコーディングでも、加算が使われていました。Transformerでは、情報を組み合わせる際に加算を使う場面が多く登場します。

つまずき2:パラメータがあると思い込む

残差接続には、学習されるパラメータが一切ありません。

「重要な仕組みなのだから、何か複雑な計算をしているはずだ」と考えがちですが、実際には足し算だけです。

パラメータを増やさずに学習の安定性を大きく改善する、費用対効果の極めて高い工夫だと言えます。

つまずき3:勾配消失が完全になくなると考える

残差接続は、勾配消失を大幅に緩和しますが、完全になくすわけではありません。

1 + \frac{\partial F(x)}{\partial x}

この値が1より小さくなる場合も、原理的にはあり得ます。

また、非常に深いモデルでは、他の要因による学習の不安定さも生じます。残差接続は万能薬ではなく、正規化や適切な初期化と組み合わせて初めて、深いモデルの学習が実現します。

演習

演習1:勾配の減衰を計算する

各層の微分値が0.7であるモデルについて、次の二つの場合の勾配の減衰を計算してください。

残差接続がない場合、12層を経た後の勾配は、元の何倍になるでしょうか。

残差接続がある場合、各層の微分値が 1 + 0.7 = 1.7 になると仮定すると、12層を経た後の勾配は元の何倍になるでしょうか。

なお、後者は説明のための単純化であり、実際には各層の微分値が正確に1.7になるわけではありません。

演習2:残差接続の有無で学習を比較する

import torch
import torch.nn as nn

torch.manual_seed(0)

class BlockWithoutResidual(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.linear = nn.Linear(dim, dim)
        self.act = nn.GELU()
    
    def forward(self, x):
        return self.act(self.linear(x))

class BlockWithResidual(nn.Module):
    def __init__(self, dim):
        super().__init__()
        self.linear = nn.Linear(dim, dim)
        self.act = nn.GELU()
    
    def forward(self, x):
        return x + self.act(self.linear(x))

def check_gradient(block_class, n_layers, dim=64):
    layers = nn.Sequential(*[block_class(dim) for _ in range(n_layers)])
    x = torch.randn(1, dim, requires_grad=True)
    y = layers(x)
    loss = y.sum()
    loss.backward()
    return x.grad.abs().mean().item()

print("層数ごとの入力層における勾配の大きさ")
print(f"{'層数':>4} | {'残差なし':>12} | {'残差あり':>12}")
print("-" * 36)

for n in [1, 4, 8, 16, 32]:
    torch.manual_seed(0)
    grad_without = check_gradient(BlockWithoutResidual, n)
    torch.manual_seed(0)
    grad_with = check_gradient(BlockWithResidual, n)
    print(f"{n:>4} | {grad_without:>12.6f} | {grad_with:>12.6f}")

演習3:GPT-2の残差接続を確認する

from transformers import GPT2Model
import torch
import inspect

model = GPT2Model.from_pretrained("gpt2")

# 1つのブロックの構造を確認
block = model.h[0]
print("=== ブロックの構成要素 ===")
for name, module in block.named_children():
    print(f"{name}: {module.__class__.__name__}")

# forwardの実装を確認
print("\n=== ブロックのforward処理(抜粋)===")
source = inspect.getsource(block.forward)
for line in source.split("\n"):
    if "residual" in line or "hidden_states = " in line:
        print(line.strip())

演習の解答例と解説

演習1の解答は、次の通りです。

残差接続がない場合。

0.7^{12} \approx 0.0138

元の約1.4パーセントまで減衰します。層をさらに増やせば、急速に0に近づきます。

残差接続がある場合。

1.7^{12} \approx 582

元の約582倍になります。

後者では、むしろ増大していることが分かります。実際には、この単純化された計算どおりにはならず、勾配が爆発しないように、正規化などの仕組みが働きます。

重要なのは、掛け算の対象が1より小さい値から1を超える値に変わるという点です。この違いが、減衰するか、しないかを分けています。

演習2では、層数を増やしたときに、残差接続がない場合の勾配が急速に小さくなることが確認できます。32層では、勾配がほぼ0に近づくはずです。一方、残差接続がある場合は、層数が増えても勾配が保たれます。

この実験結果は、残差接続の効果を最も直接的に示すものです。

演習3では、GPT-2の実装において、residual という変数名で入力が保持され、処理の後に足し戻されていることが確認できます。実際のコードで、本章の内容がそのまま実装されていることを確認してみてください。

講師向けの補足

この章の説明では、演習2のプログラムを実際に動かすことを、強く推奨します。

残差接続の効果は、数式で説明しても抽象的に感じられますが、層数を増やしたときに勾配が桁違いに変わる様子を数値で見ると、一目で納得されます。

また、「残差」という名前の由来についても、時間があれば触れてください。F(x) = y - x という変形を示すと、なぜこの名前なのかが理解でき、記憶にも残りやすくなります。

受講者から「なぜこんな単純な工夫が長らく使われなかったのか」という質問が出ることがあります。これは良い問いです。深いネットワークが学習できない原因が勾配消失にあると特定されるまでに時間がかかったこと、そして解決策が単純な足し算であると気づくには発想の転換が必要だったことを説明すると、技術の進歩の性質についての理解も深まります。

理解度を確認する問い

第11章の内容を、次の問いで確認してみてください。

残差接続がある場合の微分に、必ず1という項が含まれるのはなぜでしょうか。また、それが勾配消失の防止にどうつながるのでしょうか。

残差接続があると、モデルが「何もしない層」を実現しやすくなるのはなぜでしょうか。

残差接続を実現するために、各層の入出力の次元数が満たすべき条件は何でしょうか。

まとめ

残差接続とは、ある処理の出力に、その処理への入力をそのまま足し合わせる仕組みです。式で書くと y = x + F(x) となります。

この仕組みが必要な最大の理由は、勾配消失の防止です。微分すると 1 + \frac{\partial F(x)}{\partial x} という形になり、1という項が保証されるため、層を何回経ても勾配が掛け算によって消えることがありません。

第二の理由は、モデルが「何もしない層」を実現しやすくなることです。F(x) が0を出力すれば素通りになるため、不要な層を無効化する学習が容易になります。

第三の理由は、元の入力情報が確実に保持されることです。処理結果で上書きするのではなく、追加する構造になっています。

Transformerの1つのブロックには、注意機構の周囲とMLP層の周囲の二か所に残差接続があります。GPT-2ではPre-LN方式が採用されており、正規化を通した値を処理に入れ、残差接続はその外側で行われます。

残差接続には学習されるパラメータが一切ありません。単純な足し算だけで、深いモデルの学習を可能にする、費用対効果の高い工夫です。

次の第12章では、レイヤー正規化を扱います。残差接続と並んで、深いモデルの学習を安定させるもう一つの柱です。バッチ正規化との違い、なぜTransformerではレイヤー正規化が選ばれるのかを解説します。

第12章に進む前に、演習2のプログラムを動かし、層数を増やしたときの勾配の変化を、自分の目で確認しておいてください。数値で見た実感が、次章以降の「学習を安定させる仕組み」の話を理解する土台になります。

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
海外放浪の末、2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。