TL; DR (要約)
AIを深くすると、なぜか性能が劣化する「深さの壁」。
この壁を「バイパス(スキップ接続)」という天才的な近道で打ち破ったのが、現代AIの礎ResNetです。
① 課題
(深さの壁)
層を深くしすぎると、AIが「何もしない」ことすら学べず、学習信号も届かなくなり、性能が劣化していました。
② ResNetの発想
(残差学習)
AIに最終出力(H(x))ではなく、入力(x)との「差分(F(x))」だけを学習させる。出力は F(x) + x で計算します。
③ 仕組み
(スキップ接続)
入力(x)を層の出力に直接足す「高速道路」を作成。情報と学習信号がスムーズに流れ、100層以上の超深層モデルが可能になりました。
この章の学習目標と前提知識
はじめに:なぜ「深い」ネットワークは難しいのか?
皆さん、こんにちは!「拡張編」へようこそ!
前回の講義で、私たちは正規化層と適切な重み初期化という、深層学習モデルを安定させるための強力な「安定化装置」を手に入れました。これで、より深いニューラルネットワークを訓練する準備は万端、のはずでした。
理論上は、層を深くすればするほど、モデルはより複雑で抽象的な特徴(例えば、画像の中のピクセルの点から、輪郭、目や鼻といったパーツ、そして最終的には顔全体、というように)を捉えることができ、性能はどんどん向上していくはずです。多くの研究者が、この信念のもとに、ネットワークをより深くすることに心血を注いでいました。
しかし、現実はそう単純ではありませんでした。2015年頃、研究者たちはある不可解で、そして非常に悩ましい「壁」に突き当たっていたのです。それは、ネットワークの層を50層、100層と深く積み重ねていくと、ある地点から性能が頭打ちになるだけでなく、あろうことか、より浅いネットワークよりもかえって性能が悪化してしまうという現象でした。これを、研究者たちは「Degradation Problem(劣化問題)」と呼びます。
ここで非常に重要なのは、これが「過学習」とは全く異なる問題だという点です。過学習は、「訓練データでは良い成績なのに、未知のテストデータでは悪い成績」という状態でしたよね。しかし、この劣化問題は、訓練データに対する成績そのものが悪化してしまう、という、さらに根深く不可解な問題だったのです。
考えてみてください。表現力が増したはずの、より賢い(はずの)深いモデルが、なぜ、よりシンプルな浅いモデルにさえ負けてしまうのでしょうか?これは、当時の多くの研究者にとって、大きな謎でした。
この、深層学習の発展そのものを停滞させかねなかった根深い問題を、驚くほどシンプル、かつエレガントなアイデアで打ち破ったのが、今回私たちが学ぶResNet (レズネット、Residual Network、残差ネットワーク)なのです(1)。そして、その核心をなすのが「スキップ接続」という、情報の流れが滞らないように、渋滞した一般道を飛び越えて本線に合流する「高速道路のジャンクション」を作るような、革新的な仕組みでした。このアイデアが、深層学習の世界に新たな道を開いたのです。
1. なぜ「ただ深くする」だけではダメなのか? – The Degradation Problem
ResNetの画期的なアイデアを真に理解するために、まずは、それが解決しようとした「Degradation Problem(劣化問題)」という壁が、一体どんなものだったのか、その正体を探ってみましょう。これは、一見すると直感に反する、とても不思議な現象なんです。
1.1 深いモデルが、浅いモデルに負ける?
少し思考実験をしてみましょう。ここに、すでによく学習された、例えば20層の優秀なネットワークがあるとします。この性能を、少なくとも悪化させずに、もっと深いネットワーク、例えば50層のネットワークを作るには、どうすれば良いでしょうか?
単純に考えれば、元の20層の構造はそのままにして、新しく追加する30層が、ただ入力をそのまま出力するだけの「恒等写像(Identity Mapping)」、つまり、単なる「素通し」の機能さえ獲得してくれれば、50層モデルの性能は、少なくとも元の20層モデルと同じになるはずですよね。悪くなる理由がありません。
しかし、驚くべきことに、複数の非線形な層(例えば、Linear層とReLUを組み合わせたもの)にとって、この「何もしないで、ただ情報を右から左へ流す」という、人間にとっては最も簡単に見えるタスク \(F(x) = x\) を学習することが、非常に困難だったのです。これが劣化問題の核心にあります。
実際のResNetの論文で示された実験結果では、層が20層から56層に深くなるにつれて、訓練データに対するエラー(間違い)が、むしろ悪化してしまっているのが確認されました。
1.2 アナロジーで理解する:「伝言ゲーム」の難しさ
この不思議な現象は、複数人で行う「伝言ゲーム」に例えると、その難しさがよく分かります。
最初の人(入力)が「今日の夕食はカレーライスです」という、完璧な情報を伝えたとします。もし、途中の人(各層)が全員、完璧な記憶力と伝達能力を持っていて、「今日の夕食はカレーライスです」と、一言一句変えずに次の人に伝え続ければ(これが恒等写像の学習です)、最後の人まで情報は正しく届きます。
しかし、誰か一人が「今日の夕食は…えーっと、ハヤシライスだったかな?」と、ほんの少しでも情報を歪めてしまうとどうでしょう。その小さな誤差が、次の人、また次の人へと伝わるにつれて、どんどん元の情報からかけ離れていき、最後には「明日の朝食はパンです」のような、全く違う情報になってしまうかもしれませんよね。
深いネットワークもこれと全く同じで、各層が恒等写像という「完璧な伝言」をしようとしても、学習の過程でどうしても生じてしまうわずかな誤差が、深い層を通過するうちに積み重なって増幅し、結果として全体の性能を「劣化」させてしまう、と考えられているのです。
2. ResNetの核心思想:残差学習 (Residual Learning) 〜「差」を学ぶという、コロンブスの卵〜
さて、深いネットワークが「何もしない(恒等写像)」ことすら学習するのが難しい、という厄介な問題に直面しました。この壁を前にして、ResNetの考案者たちは、まさに「コロンブスの卵」のような、視点を180度変える発想に至ります。
彼らはこう考えました。
「層に、最終的な出力 \(H(x)\) そのものを、頑張って学習させるのが難しいのであれば、発想を変えて、層には入力 \(x\) から出力 \(H(x)\) までの『差分』だけを学習させたらどうだろう?」
この学習対象である「差分」、すなわち残差 (Residual) を \(F(x)\) と定義します。数式で書くと、\(F(x) = H(x) – x\) ですね。そして、層はこの \(F(x)\) の計算に集中します。最終的に欲しい出力 \(H(x)\) は、層の出力 \(F(x)\) に、元の入力 \(x\) を足し合わせることで得られます。
\[ H(x) = F(x) + x \]
これが、深層学習の歴史を変えた残差学習 (Residual Learning) の、驚くほどシンプルな基本骨格です。
なぜ「残差」を学ぶ方が簡単なのか?
このアイデアの真のすごさを理解するために、もう一度、最も学習が難しいとされた恒等写像(つまり、最適な出力 \(H(x)\) が、入力 \(x\) そのものであるべき場合)を考えてみましょう。
- 従来のネットワーク: 層は、\(H(x) = x\) という出力を直接作り出そうとします。これは、入力 \(x\) を受け取って、たくさんの重みやバイアスをくぐり抜けた結果が、寸分違わず元の \(x\) と同じになるように、全てのパラメータを神がかり的に調整しなければならない、という非常に難しいタスクです。
- 残差ネットワーク: 一方、ResNetの層が学習すべき残差は \(F(x) = H(x) – x = x – x = 0\) となります。つまり、層は「何も出力しない(ゼロを出力する)」ことを学習すれば良いのです。ニューラルネットワークにとって、出力をゼロに近づけるのは、全ての重みをゼロに近づけていけばよく、これは非常に簡単なタスクです。
つまり、ResNetは「もし層を追加することが無意味(あるいは有害)であるならば、その追加された層は、何もしないことを即座に学習して、ただ入力を素通しするだけの存在になれる」という、驚異的な柔軟性を持っているのです。これにより、とりあえず層を深くしてみて、もしそれが不要であれば、モデル自身がその層を「実質的にバイパス」してくれる。だから、層を深くしても性能が劣化しにくくなった、というわけなんですね。
スキップ接続 (Shortcut / Skip Connection) 〜勾配の「高速道路」〜
この残差学習 \(H(x) = F(x) + x\) を、実際のネットワークアーキテクチャで実現するのが、スキップ接続(またはショートカット接続)です。その名の通り、入力 \(x\) が、いくつかの層(\(F(x)\) を計算する部分)を「スキップ」して、その層の出力に直接合流するバイパス経路のことです。
通常のブロック vs 残差ブロック (Residual Block)
このスキップ接続は、順伝播で残差学習を可能にするだけでなく、逆伝播においても勾配の「高速道路」として、極めて重要な役割を果たします。先ほどの伝言ゲームの例で、情報が歪んでしまうという話がありましたね。逆伝播における勾配も、深い層を通過するうちにどんどん小さくなってしまう(勾配消失)という問題がありました。
しかし、スキップ接続があると、勾配は \(F(x)\) を計算する複雑な層を経由するルートだけでなく、入力 \(x\) をそのまま足し算しただけの「スキップ経路」を通って、ほとんど減衰することなく、一気に過去の層まで届くことができるのです。この「勾配の高速道路」こそが、ResNetが100層、1000層といった超深層のネットワークでも、安定して学習を可能にした、もう一つの大きな秘密なんです。
3. PyTorchでResidual Blockを実装する 〜アイデアをコードに、理論を実践に〜
さて、残差学習とスキップ接続という、ResNetの強力なアイデアを理解したところで、いよいよそれを私たちの手で、PyTorchのコードに落とし込んでいきましょう。理論をコードに変換するこのプロセスこそが、AI開発の面白さであり、醍醐味だと思います。
ここでは、これまでの講義との繋がりを意識して、全結合層(nn.Linear)を使った、最もシンプルな形の残差ブロック(ResidualBlock)を作成してみます。この構造を理解すれば、後でこれを畳み込み層(nn.Conv2d)に置き換えるだけで、画像認識で使われる本格的なResNetにも応用できるようになりますよ。
Pythonコード例:簡単な残差ブロックの実装
以下が、残差ブロックを実装したPythonコードの全体像です。一見すると少し長く見えるかもしれませんが、一つ一つの部品は、私たちがすでに学んだものばかりです。じっくりと見ていきましょう。
graph TD
A["開始"] --> B["1. 残差ブロックの設計図(クラス)を定義
・データが通る「主経路」
・入力を迂回させる「スキップ接続」
・2つの経路の出力を足し合わせる処理"];
B --> C["2. 設計図からブロックの実体を作成"];
C --> D["3. ダミーの入力データを用意"];
D --> E["4. ブロックにデータを入力し、出力を計算"];
E --> F["5. 結果を確認
(入出力の形状が同じであることを表示)"];
F --> G["終了"];
# 必要なライブラリをインポートします
import torch
import torch.nn as nn
# --- 1. 残差ブロック(ResidualBlock)のクラスを定義 ---
# nn.ModuleというPyTorchの設計図を継承して、私たちだけのカスタムモジュールを作ります。
class ResidualBlock(nn.Module):
# (A) 初期化メソッド: ブロックで使う「部品(層)」をここで定義します。
# このブロックの入力特徴の次元数(in_features)と出力特徴の次元数(out_features)を受け取ります。
def __init__(self, in_features, out_features):
# まずは、おまじないとして親クラスの__init__を呼び出します。
super(ResidualBlock, self).__init__()
# このブロックの主経路となる2つの線形層を定義します。
self.linear1 = nn.Linear(in_features, out_features)
self.linear2 = nn.Linear(out_features, out_features)
# 活性化関数ReLUを用意します。
self.relu = nn.ReLU()
# 【重要】スキップ接続のための次元調整
# 入力xと、2つの層を通った後の出力の形状が違うと足し算ができません。
# もし次元が異なる場合は、入力xを適切な形に変換するための層(shortcut)を用意します。
if in_features != out_features:
# 1x1の畳み込み層や、別の線形層を使って次元を合わせます。
# ここでは簡単な線形層を使います。
self.shortcut = nn.Linear(in_features, out_features)
else:
# もし次元が同じなら、何もしない(入力をそのまま出力する)層(nn.Identity)を使います。
self.shortcut = nn.Identity()
# (B) forwardメソッド: データがこのブロック内をどう流れるかを定義します。
def forward(self, x):
# まず、入力xをスキップ接続のために変数identityに保存しておきます。
# 必要であれば、shortcut層で次元変換も行います。
identity = self.shortcut(x)
# 主経路の計算を進めます。
out = self.relu(self.linear1(x))
out = self.linear2(out)
# 【核心部分】主経路の出力(out)に、スキップしてきた入力(identity)を足し合わせます!
# これが H(x) = F(x) + x の実装です。
out += identity
# 最後に活性化関数を通します(Addの後に活性化するのが一般的です)。
out = self.relu(out)
# このブロックの最終的な出力を返します。
return out
# --- 2. 残差ブロックを使ってみる ---
# 入力特徴が64次元、出力特徴も64次元の残差ブロックを作ってみます。
# この場合、入力と出力の次元が同じなので、ショートカットは恒等写像(何もしない)になります。
block = ResidualBlock(in_features=64, out_features=64)
print("--- 作成した残差ブロック ---")
print(block)
# ダミーの入力データを作成します (バッチサイズ1、特徴量64)
dummy_input = torch.randn(1, 64)
# データをブロックに通してみます。
output = block(dummy_input)
print("\n--- 入出力の形状確認 ---")
print(f"入力テンソルの形状: {dummy_input.shape}")
print(f"出力テンソルの形状: {output.shape}") # 入力と同じ形状のはず
# === ここから下が上記のprint文による実際の出力の例 ===
# --- 作成した残差ブロック ---
# ResidualBlock(
# (linear1): Linear(in_features=64, out_features=64, bias=True)
# (linear2): Linear(in_features=64, out_features=64, bias=True)
# (relu): ReLU()
# (shortcut): Identity()
# )
#
# --- 入出力の形状確認 ---
# 入力テンソルの形状: torch.Size([1, 64])
# 出力テンソルの形状: torch.Size([1, 64])
--- 作成した残差ブロック ---
ResidualBlock(
(linear1): Linear(in_features=64, out_features=64, bias=True)
(linear2): Linear(in_features=64, out_features=64, bias=True)
(relu): ReLU()
(shortcut): Identity()
)
--- 入出力の形状確認 ---
入力テンソルの形状: torch.Size([1, 64])
出力テンソルの形状: torch.Size([1, 64])
コードの解剖 〜理論を実装に繋げる〜
このコードのどこがResNetのアイデアを実装しているのか、もう少し詳しく見ていきましょう。
__init__メソッドの役割 ここでは、この残差ブロックという「部品」が内部で使う、さらに小さな「孫部品」(nn.Linearやnn.ReLUなど)を準備しています。特に重要なのが、スキップ接続のためのself.shortcutを定義している部分です。
スキップ接続は、入力 \(x\) と主経路の出力 \(F(x)\) を足し合わせる操作でしたよね。しかし、足し算ができるのは、二つのテンソルの形状が同じ場合だけです。
もし、主経路の層が特徴量の次元数を変える場合(例えば、入力64次元、出力128次元など)、そのままでは \(x\) と \(F(x)\) は足し算できません。そこで、if in_features != out_features:の条件分岐を使って、スキップ接続経路側にも、次元数を合わせるための特別な層(ここではnn.Linear)を用意してあげるのです。これをプロジェクション・ショートカットと呼んだりします。
もし次元が変わらないのであれば、何もしない(入力をそのまま出力する)nn.Identity()という便利な層をショートカットとして設定します。これで、どんな次元の入出力にも対応できる、柔軟な残差ブロックが完成しました。forwardメソッドの役割 ここでは、データがこのブロックの中を流れる具体的な「経路」を定義しています。identity = self.shortcut(x): まず、入力 \(x\) をショートカット経路に通し、後で足し算するためにidentityという変数に保持します。out = self.linear2(self.relu(self.linear1(x))): 主経路の計算です。層を2つ通って、\(F(x)\) に相当する出力outを計算します。out += identity: ここが残差学習の核心部分!主経路の出力out(\(F(x)\)) に、スキップしてきたidentity(\(x\)) を足し合わせます。まさに、\(H(x) = F(x) + x\) をコードで表現した瞬間です。out = self.relu(out): 最後に、足し合わせた結果全体を、もう一度活性化関数に通してから出力します。これにより、ブロック全体の出力にも非線形性が与えられます。
出力結果を見ると、入力テンソルと出力テンソルの形状が同じ(1, 64)になっていることが確認できますね。これは、私たちの残差ブロックが、情報の次元を保ったまま、適切に処理を通過させていることを示しています。
4. まとめと次のステップへ
今回は、深層学習の歴史における大きなブレークスルーとなったResNetの思想と、その核心であるスキップ接続(残差学習)について学びました。
- Degradation Problem: ただ層を深くするだけでは、性能が劣化してしまうという問題。
- 残差学習: 出力そのものではなく、入力との「差分(残差)」を学習することで、層の追加を容易にするという画期的なアイデア。
- スキップ接続: 残差学習を実装し、かつ勾配が消えにくい「高速道路」を提供する具体的なアーキテクチャ。
このシンプルかつ深遠なアイデアによって、AIはそれまで到達できなかった「深み」へと至ることができ、画像認識をはじめとする多くの分野で、人間の能力を超える性能を達成する道が拓かれたのです(1)。特に、複雑な構造を捉える必要がある医療画像解析などの分野で、ResNetやその派生モデルは今なお広く使われています。
さて、ResNetによって、私たちは非常に「深い」モデルを構築するアーキテクチャを手に入れました。しかし、この巨大で複雑なモデルを、どうやって効率的に、そして上手に「訓練」すれば良いのでしょうか?
次回の第21回では、学習の進行度合いに合わせて学習率を賢く調整する学習率スケジューリングというテクニックについて学び、訓練プロセスそのものを最適化する方法を探求します。
参考文献
- He K, Zhang X, Ren S, Sun J. Deep residual learning for image recognition. In: Proceedings of the IEEE conference on computer vision and pattern recognition. 2016. p. 770-778.
- Srivastava RK, Greff K, Schmidhuber J. Highway networks. arXiv preprint arXiv:1505.00387. 2015.
ご利用規約(免責事項)
当サイト(以下「本サイト」といいます)をご利用になる前に、本ご利用規約(以下「本規約」といいます)をよくお読みください。本サイトを利用された時点で、利用者は本規約の全ての条項に同意したものとみなします。
第1条(目的と情報の性質)
- 本サイトは、医療分野におけるAI技術に関する一般的な情報提供および技術的な学習機会の提供を唯一の目的とします。
- 本サイトで提供されるすべてのコンテンツ(文章、図表、コード、データセットの紹介等を含みますが、これらに限定されません)は、一般的な学習参考用であり、いかなる場合も医学的な助言、診断、治療、またはこれらに準ずる行為(以下「医行為等」といいます)を提供するものではありません。
- 本サイトのコンテンツは、特定の製品、技術、または治療法の有効性、安全性を保証、推奨、または広告・販売促進するものではありません。紹介する技術には研究開発段階のものが含まれており、その臨床応用には、さらなる研究と国内外の規制当局による正式な承認が別途必要です。
- 本サイトは、情報提供を目的としたものであり、特定の治療法を推奨するものではありません。健康に関するご懸念やご相談は、必ず専門の医療機関にご相談ください。
第2条(法令等の遵守)
利用者は、本サイトの利用にあたり、医師法、医薬品、医療機器等の品質、有効性及び安全性の確保等に関する法律(薬機法)、個人情報の保護に関する法律、医療法、医療広告ガイドライン、その他関連する国内外の全ての法令、条例、規則、および各省庁・学会等が定める最新のガイドライン等を、自らの責任において遵守するものとします。これらの適用判断についても、利用者が自ら関係各所に確認するものとし、本サイトは一切の責任を負いません。
第3条(医療行為における責任)
- 本サイトで紹介するAI技術・手法は、あくまで研究段階の技術的解説であり、実際の臨床現場での診断・治療を代替、補助、または推奨するものでは一切ありません。
- 医行為等に関する最終的な判断、決定、およびそれに伴う一切の責任は、必ず法律上その資格を認められた医療専門家(医師、歯科医師等)が負うものとします。AIによる出力を、資格を有する専門家による独立した検証および判断を経ずに利用することを固く禁じます。
- 本サイトの情報に基づくいかなる行為によって利用者または第三者に損害が生じた場合も、本サイト運営者は一切の責任を負いません。実際の臨床判断に際しては、必ず担当の医療専門家にご相談ください。本サイトの利用によって、利用者と本サイト運営者の間に、医師と患者の関係、またはその他いかなる専門的な関係も成立するものではありません。
第4条(情報の正確性・完全性・有用性)
- 本サイトは、掲載する情報(数値、事例、ソースコード、ライブラリのバージョン等)の正確性、完全性、網羅性、有用性、特定目的への適合性、その他一切の事項について、何ら保証するものではありません。
- 掲載情報は執筆時点のものであり、予告なく変更または削除されることがあります。また、技術の進展、ライブラリの更新等により、情報は古くなる可能性があります。利用者は、必ず自身で公式ドキュメント等の最新情報を確認し、自らの責任で情報を利用するものとします。
第5条(AI生成コンテンツに関する注意事項)
本サイトのコンテンツには、AIによる提案を基に作成された部分が含まれる場合がありますが、公開にあたっては人間による監修・編集を経ています。利用者が生成AI等を用いる際は、ハルシネーション(事実に基づかない情報の生成)やバイアスのリスクが内在することを十分に理解し、その出力を鵜呑みにすることなく、必ず専門家による検証を行うものとします。
第6条(知的財産権)
- 本サイトを構成するすべてのコンテンツに関する著作権、商標権、その他一切の知的財産権は、本サイト運営者または正当な権利を有する第三者に帰属します。
- 本サイトのコンテンツを引用、転載、複製、改変、その他の二次利用を行う場合は、著作権法その他関連法規を遵守し、必ず出典を明記するとともに、権利者の許諾を得るなど、適切な手続きを自らの責任で行うものとします。
第7条(プライバシー・倫理)
本サイトで紹介または言及されるデータセット等を利用する場合、利用者は当該データセットに付随するライセンス条件および研究倫理指針を厳格に遵守し、個人情報の匿名化や同意取得の確認など、適用される法規制に基づき必要とされるすべての措置を、自らの責任において講じるものとします。
第8条(利用環境)
本サイトで紹介するソースコードやライブラリは、執筆時点で特定のバージョンおよび実行環境(OS、ハードウェア、依存パッケージ等)を前提としています。利用者の環境における動作を保証するものではなく、互換性の問題等に起因するいかなる不利益・損害についても、本サイト運営者は責任を負いません。
第9条(免責事項)
- 本サイト運営者は、利用者が本サイトを利用したこと、または利用できなかったことによって生じる一切の損害(直接損害、間接損害、付随的損害、特別損害、懲罰的損害、逸失利益、データの消失、プログラムの毀損等を含みますが、これらに限定されません)について、その原因の如何を問わず、一切の法的責任を負わないものとします。
- 本サイトの利用は、学習および研究目的に限定されるものとし、それ以外の目的での利用はご遠慮ください。
- 本サイトの利用に関連して、利用者と第三者との間で紛争が生じた場合、利用者は自らの費用と責任においてこれを解決するものとし、本サイト運営者に一切の迷惑または損害を与えないものとします。
- 本サイト運営者は、いつでも予告なく本サイトの運営を中断、中止、または内容を変更できるものとし、これによって利用者に生じたいかなる損害についても責任を負いません。
第10条(規約の変更)
本サイト運営者は、必要と判断した場合、利用者の承諾を得ることなく、いつでも本規約を変更することができます。変更後の規約は、本サイト上に掲載された時点で効力を生じるものとし、利用者は変更後の規約に拘束されるものとします。
第11条(準拠法および合意管轄)
本規約の解釈にあたっては、日本法を準拠法とします。本サイトの利用および本規約に関連して生じる一切の紛争については、東京地方裁判所を第一審の専属的合意管轄裁判所とします。
For J³, may joy follow you.

