TT Lab

한국어 English 日本語 中文

はじめる
ブログ

ブログ

ResNet論文の徹底分析:残差結合(Residual Connection)がディープラーニングの深さの限界を突破した方法

対話で聞く · 18分二人がこの記事を解説します

1. 論文概要

「Deep Residual Learning for Image Recognition」は、2015年にMicrosoft ResearchのKaiming He、Xiangyu Zhang、Shaoqing Ren、Jian Sunが発表した論文である。CVPR 2016でBest Paper Awardを受賞し、2025年時点で引用数20万回以上を記録した、ディープラーニング史上最も影響力のある論文の一つである。

この論文が解決した問題は明確である。「ネットワークが深くなるほど性能が向上するはずなのに、なぜ逆に悪化するのか?」 この単純な問いに対する答えとして提案されたResidual Learning Frameworkは、152層のネットワークを成功裏に訓練し、ImageNet ILSVRC 2015 Classification Taskでtop-5 error 3.57%で1位を獲得した。これは人間の認識誤り率(約5.1%)を大きく下回る数値である。

ResNetは単にImage Classificationで優勝しただけにとどまらなかった。同年、ImageNet Detection、ImageNet Localization、COCO Detection、COCO Segmentationを含む計5つのトラックで1位を独占した。そしてこの論文が提案したSkip Connection(Shortcut Connection)は、その後Transformer、BERT、GPT、Diffusion Modelなど、現代ディープラーニングのほぼすべてのアーキテクチャの中核的な構成要素として定着した。


2. 論文の背景:深さのジレンマ

2.1 深いネットワークの時代

2012年にAlexNet(8層)がImageNet Challengeで優勝して以降、ディープラーニングコミュニティは「より深いネットワーク=より良い性能」という直感に従ってきた。実際にこの直感はかなりの部分で正しかった。

年モデル層数Top-5 Error (%)
2012AlexNet816.4
2013ZFNet814.8
2014VGGNet197.3
2014GoogLeNet (Inception v1)226.7
2015ResNet1523.57

VGGNet(2014)は3x3 Convolutionのみを使用して19層まで深さを増やし、GoogLeNetはInception Moduleという並列構造を活用して22層のネットワークを構成した。両モデルとも「深さが性能に決定的」であることを実験的に示した。

2.2 VGGNetの教訓と限界

VGGNetはアーキテクチャ設計において重要な原則を確立した。大きなフィルタ(5x5、7x7)1つの代わりに小さなフィルタ(3x3)を複数重ねれば、同じReceptive Fieldを確保しながらもパラメータ数を削減し、中間により多くの非線形活性化関数を挿入して表現力を高めることができるという点である。

しかし、VGGNetは19層が事実上の限界であった。VGG-16からVGG-19への性能向上幅はすでに大きく縮小しており、それ以上深くすると逆に性能が悪化した。パラメータ数も問題であった。VGG-19は約1.44億個のパラメータと19.6 billion FLOPsの計算量を必要とした。

2.3 GoogLeNet(Inception)のアプローチ

GoogLeNetはVGGNetとは異なる方向から深さの問題に取り組んだ。1x1、3x3、5x5 Convolutionを並列に実行するInception Moduleを設計し、1x1 Convolutionでチャネル数を削減して計算量を節約した。22層にすぎないが、VGGNetより少ないパラメータ(約500万個)でより低いError Rateを達成した。

しかし、Inception Moduleの複雑な構造はスケーラビリティに限界があった。単純に層数を増やすだけでは性能をさらに引き上げることが難しかった。

2.4 根本的な問い

この時点でコミュニティが直面した根本的な問いはこうであった。

「ネットワークの深さを自由に増やす方法はないのか?」

この問いに対する答えがResNetである。


3. Degradation問題の発見

3.1 Deeper != Better

ResNet論文の最も重要な貢献の一つは、Degradation問題を明確に定義し実験的に実証したことである。

直感的に考えると、浅いネットワークにIdentity Mapping層を追加すれば、少なくとも浅いネットワークと同等の性能は出るはずである。追加された層は何もせず入力をそのまま出力するだけでよいからだ。したがって、より深いネットワークのTraining Errorは浅いネットワークより高くなり得ないはずである。

しかし現実は違った。論文ではCIFAR-10とImageNetの両方で、Plain Network(Shortcutのない通常のネットワーク)の56層モデルが20層モデルよりもTraining Errorが高い現象を観測した。これはOverfitting問題ではない。Overfittingであれば、Training Errorは低くValidation Errorのみが高くなるはずである。Training Error自体が高いということは、最適化(Optimization)自体が困難であることを意味する。

3.2 Vanishing/Exploding Gradientとの違い

Degradation問題はVanishing GradientやExploding Gradientの問題とは異なる現象である。

Vanishing/Exploding GradientはBatch Normalization、He Initializationなどの手法でかなりの部分が解決されていた。実際に論文のPlain Networkにもこれらの手法が適用されており、ネットワークは収束した。問題は収束した地点の性能が浅いネットワークより低いことである。

Training Error56-layer plain>Training Error20-layer plain\text{Training Error}_{56\text{-layer plain}} > \text{Training Error}_{20\text{-layer plain}}

この現象は、Gradientが十分に伝播していても、深いネットワークで非線形層のスタックによりIdentity Mappingを学習すること自体が非常に困難であることを示唆している。

3.3 Construction Argument

論文が提示する核心的な論証はConstruction Argumentである。以下のように考えてみよう。

  1. 浅いネットワーク AA があるとする。
  2. AA の上にIdentity Mappingを行う層を追加して、深いネットワーク BB を作る。
  3. BB は AA と少なくとも同等の性能を持つべきである(追加層が恒等関数であるため)。
  4. したがって、深いネットワーク BB のTraining Errorは AA より高くなり得ない。

しかし実際の実験では BB のTraining Errorが AA より高い。これは現在のSGDベースのOptimizerがそのような解を見つけられないことを意味する。問題はモデルの表現力ではなく、最適化の難易度にある。


4. 核心アイデア:Residual Learning

4.1 核心的直感

Degradation問題の原因が「Identity Mappingの学習が困難である」ということであれば、解決策は単純である。Identity Mappingを明示的にネットワークに組み込めばよい。

従来のネットワークの一つのブロックが学習すべき関数を H(x)\mathcal{H}(\mathbf{x}) とする。元の目標はこの H(x)\mathcal{H}(\mathbf{x}) を直接学習することである。もし H(x)=x\mathcal{H}(\mathbf{x}) = \mathbf{x}(Identity Mapping)であれば、この関数を非線形層のスタックで学習することは困難である。

ResNetの核心アイデアは、H(x)\mathcal{H}(\mathbf{x}) を直接学習する代わりに、残差(Residual)を学習するよう再構成することである。

F(x):=H(x)−x\mathcal{F}(\mathbf{x}) := \mathcal{H}(\mathbf{x}) - \mathbf{x}

したがって:

H(x)=F(x)+x\mathcal{H}(\mathbf{x}) = \mathcal{F}(\mathbf{x}) + \mathbf{x}

最適なマッピングがIdentityに近い場合、F(x)\mathcal{F}(\mathbf{x}) を0にすることは H(x)\mathcal{H}(\mathbf{x}) をIdentityにするよりもはるかに容易である。非線形層の重みを0に初期化するか、0に近く学習させることは自然な操作だからである。

4.2 Residual Blockの構造

Residual Blockは以下の構造を持つ。

y=F(x,{Wi})+x\mathbf{y} = \mathcal{F}(\mathbf{x}, \{W_i\}) + \mathbf{x}

ここで:

F(x,{Wi})+x\mathcal{F}(\mathbf{x}, \{W_i\}) + \mathbf{x} における加算(++)はElement-wise Additionとして実行され、これをShortcut ConnectionまたはSkip Connectionと呼ぶ。この演算は追加パラメータが一切不要であり、計算量の増加も無視できるレベルである。

2つの層を持つResidual Blockの場合:

F=W2σ(W1x)\mathcal{F} = W_2 \sigma(W_1 \mathbf{x})

ここで σ\sigma はReLU活性化関数である。Biasは表記の便宜上省略した。

4.3 Dimension Mismatchの処理

F(x)\mathcal{F}(\mathbf{x}) と x\mathbf{x} の次元が異なる場合(Feature Mapのチャネル数が変わるDownsampling段階)、直接加算できない。これを解決するため、論文はLinear Projectionを使用する。

y=F(x,{Wi})+Wsx\mathbf{y} = \mathcal{F}(\mathbf{x}, \{W_i\}) + W_s \mathbf{x}

ここで WsW_s は次元を合わせるためのProjection Matrixである。論文では3つのオプションを実験した。

実験結果、3つのオプションすべてがPlain Networkよりはるかに優れており、オプション間の差異はわずかであった。これはProjectionがDegradation解決の核心ではなく、Identity Shortcut自体が核心であることを示している。最終的なResNetではメモリと計算効率のためOption Bが採用された。


5. 数学的分析:Gradient Flow

5.1 Forward Propagation

Residual Blockを通るForward Propagationを分析しよう。ll 番目のResidual Blockの出力を xl\mathbf{x}_l とすると:

xl+1=xl+F(xl,Wl)\mathbf{x}_{l+1} = \mathbf{x}_l + \mathcal{F}(\mathbf{x}_l, W_l)

これを再帰的に展開すると、任意の深い層 LL での出力は:

xL=xl+∑i=lL−1F(xi,Wi)\mathbf{x}_L = \mathbf{x}_l + \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i, W_i)

この数式が意味するところは非常に重要である。任意の深い層 LL の特徴量は、浅い層 ll の特徴量と、その間のすべてのResidual Functionの和として表現される。 Plain Networkではこれが行列積の連鎖であるのに対し、ResNetでは加算の形で現れる。

5.2 Backward PropagationとGradient Highway

核心であるBackward Propagationを見てみよう。LossをL L\mathcal{L} とすると、Chain Ruleにより:

∂L∂xl=∂L∂xL⋅∂xL∂xl\frac{\partial \mathcal{L}}{\partial \mathbf{x}_l} = \frac{\partial \mathcal{L}}{\partial \mathbf{x}_L} \cdot \frac{\partial \mathbf{x}_L}{\partial \mathbf{x}_l}

先に導出したForwardの数式を代入すると:

∂L∂xl=∂L∂xL⋅(1+∂∂xl∑i=lL−1F(xi,Wi))\frac{\partial \mathcal{L}}{\partial \mathbf{x}_l} = \frac{\partial \mathcal{L}}{\partial \mathbf{x}_L} \cdot \left(1 + \frac{\partial}{\partial \mathbf{x}_l} \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i, W_i)\right)

ここでの核心は定数項1である。Gradientはこの経路を通じて、どの層からでもLossまで直接流れることができる。∂∂xl∑i=lL−1F(xi,Wi)\frac{\partial}{\partial \mathbf{x}_l} \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i, W_i) の項がいくら小さくなっても、定数項1のおかげでGradientが完全に消失することはない。

これがResNetがGradient Highwayを形成する原理である。Plain Networkでは、Gradientがすべての層の重み行列を乗算しなければならない:

∂L∂xl=∏i=lL−1∂xi+1∂xi⋅∂L∂xL\frac{\partial \mathcal{L}}{\partial \mathbf{x}_l} = \prod_{i=l}^{L-1} \frac{\partial \mathbf{x}_{i+1}}{\partial \mathbf{x}_i} \cdot \frac{\partial \mathcal{L}}{\partial \mathbf{x}_L}

この積の形式では、各項が1よりわずかに小さいだけでもGradientが指数関数的に減少する。ResNetの和の形式ではこの問題は発生しない。

5.3 なぜ「Residual」の学習が容易なのか

より直感的な数学的説明をしよう。ネットワークの各層が入力に小さな変化のみを加えることが最適であるなら(これは深いネットワークにおける自然な仮定である)、残差関数 F(x)\mathcal{F}(\mathbf{x}) は0に近い値を出力すべきである。

重み行列が0に近く初期化されるため、学習初期において各Residual Blockはほぼ恒等写像に近いマッピングを実行する。これは、深いネットワークが学習初期には浅いネットワークのように振る舞い、徐々に各ブロックが有用な変換を学習していく過程として解釈できる。


6. アーキテクチャ詳細

6.1 全体構造の概要

ResNetはVGGNetの設計思想を基盤としつつ、Shortcut Connectionを追加した構造である。すべてのResNet変形は以下の共通構造を持つ。

  1. conv1:7x7 Convolution、stride 2、64 filters、BatchNorm、ReLU
  2. Max Pooling:3x3、stride 2
  3. conv2_x〜conv5_x:Residual Blockのスタック
  4. Global Average Pooling:Feature Mapを1x1に縮小
  5. Fully Connected Layer:1000クラスSoftmax

Feature Mapのサイズが半分になるとき(conv3_x、conv4_x、conv5_xの最初のブロック)、チャネル数は2倍に増加する。Downsamplingはstride 2のConvolutionで実行される。

6.2 Basic Block(ResNet-18、ResNet-34)

Basic Blockは2つの3x3 Convolutionで構成される。

Input (C channels)
  |
  ├──→ 3x3 Conv, C filters, BN, ReLU
  |    3x3 Conv, C filters, BN
  |
  └──→ (Identity Shortcut)
  |
  + ←── Element-wise Addition
  |
  ReLU
  |
Output (C channels)

各Convolutionの後にBatch Normalizationが適用され、ReLUはAdditionの後に適用される。

6.3 Bottleneck Block(ResNet-50、ResNet-101、ResNet-152)

50層以上のResNetでは、計算効率のためBottleneck構造が使用される。3つのConvolution(1x1、3x3、1x1)で構成される。

Input (4C channels)
  |
  ├──→ 1x1 Conv, C filters, BN, ReLU    (チャネル縮小:4C → C)
  |    3x3 Conv, C filters, BN, ReLU    (空間的処理)
  |    1x1 Conv, 4C filters, BN          (チャネル復元:C → 4C)
  |
  └──→ (Identity Shortcut)
  |
  + ←── Element-wise Addition
  |
  ReLU
  |
Output (4C channels)

Bottleneckの核心アイデアは、1x1 Convolutionでチャネル数を1/4に削減した後、コストの高い3x3 Convolutionを実行し、再び1x1 Convolutionでチャネルを復元することである。この構造のおかげで、ResNet-50はResNet-34より深いが、演算量(FLOPs)は同等のレベルを維持する。

6.4 アーキテクチャ比較表

LayerOutput SizeResNet-18ResNet-34ResNet-50ResNet-101ResNet-152
conv1112x1127x7, 64, stride 27x7, 64, stride 27x7, 64, stride 27x7, 64, stride 27x7, 64, stride 2
pool56x563x3 max pool, stride 23x3 max pool, stride 23x3 max pool, stride 23x3 max pool, stride 23x3 max pool, stride 2
conv2_x56x56[3x3, 64] x2[3x3, 64] x3[1x1, 64; 3x3, 64; 1x1, 256] x3[1x1, 64; 3x3, 64; 1x1, 256] x3[1x1, 64; 3x3, 64; 1x1, 256] x3
conv3_x28x28[3x3, 128] x2[3x3, 128] x4[1x1, 128; 3x3, 128; 1x1, 512] x4[1x1, 128; 3x3, 128; 1x1, 512] x4[1x1, 128; 3x3, 128; 1x1, 512] x8
conv4_x14x14[3x3, 256] x2[3x3, 256] x6[1x1, 256; 3x3, 256; 1x1, 1024] x6[1x1, 256; 3x3, 256; 1x1, 1024] x23[1x1, 256; 3x3, 256; 1x1, 1024] x36
conv5_x7x7[3x3, 512] x2[3x3, 512] x3[1x1, 512; 3x3, 512; 1x1, 2048] x3[1x1, 512; 3x3, 512; 1x1, 2048] x3[1x1, 512; 3x3, 512; 1x1, 2048] x3
1x1Global Average Pool, 1000-d FC, Softmax

6.5 パラメータ数と計算量

ModelLayersParametersFLOPs
VGG-1919144M19.6B
ResNet-181811.7M1.8B
ResNet-343421.8M3.6B
ResNet-505025.6M3.8B
ResNet-10110144.5M7.6B
ResNet-15215260.2M11.3B

注目すべき点は、ResNet-152がVGG-19の8倍の深さでありながら計算量は少なく、パラメータは半分以下であることだ。これはVGGNetが最後のFully Connected Layerにパラメータの大部分を使用するのに対し、ResNetはGlobal Average Poolingを使用してFC Layerのパラメータを劇的に削減したためである。


7. 実験結果

7.1 ImageNet Classification

Plain NetworkのDegradation確認

まず論文はShortcutのないPlain NetworkでDegradation問題を確認した。

ModelTop-1 Error (%)Top-5 Error (%)
Plain-1827.94-
Plain-3428.54-

34層のPlain Networkが18層よりもむしろ0.6%高いError Rateを示す。これがDegradation問題である。

Residual Networkの効果

同じ構造にShortcut Connectionのみを追加した結果:

ModelTop-1 Error (%)Top-5 Error (%)
ResNet-1827.88-
ResNet-3425.03-

ResNet-34はResNet-18より2.85%低いError Rateを達成した。Plain Networkで観測されたDegradation問題が完全に解消され、深さの増加に伴う性能向上が明確に現れた。

Bottleneck ResNetの結果(10-crop Testing)

ModelTop-1 Error (%)Top-5 Error (%)
ResNet-5022.856.71
ResNet-10121.756.05
ResNet-15221.435.71

ResNet-152の単一モデルTop-5 Errorは4.49%(Multi-scale、Multi-crop)であり、6モデルのEnsemble結果は3.57%で、ImageNet ILSVRC 2015 Classificationで1位を獲得した。

VGGおよびGoogLeNetとの比較

ModelTop-5 Error (%)Ensemble Top-5 Error (%)
VGG-167.3-
GoogLeNet6.7-
ResNet-152 (single model)4.49-
ResNet Ensemble (6 models)-3.57

7.2 CIFAR-10実験

CIFAR-10データセット(32x32画像、10クラス)でもDegradation問題を確認し、ResNetの効果を検証した。CIFAR-10用ResNetはImageNet版と異なり、最初の層が3x3 Convolutionであり、3つのStage(各Feature Mapサイズ32x32、16x16、8x8)で {n, n, n} 個のResidual Blockを使用する。

ModelLayersError (%)
ResNet-20208.75
ResNet-32327.51
ResNet-44447.17
ResNet-56566.97
ResNet-1101106.43
ResNet-120212027.93

110層まで一貫して性能が向上した。1202層ネットワークはTraining Errorは低いがTest Errorは110層より高く、論文ではこれをOverfittingと分析した。パラメータ数が19.4Mと小さなデータセット(50,000枚の学習画像)に対して過剰に多いためである。論文ではRegularization(Dropoutなど)を適用しておらず、適用すれば改善される可能性があると言及した。

7.3 COCO Object DetectionおよびSegmentation

ResNetの効果はImage Classificationを超えてObject DetectionとSegmentationでも検証された。

PASCAL VOCおよびCOCO Detection

Faster R-CNNのBackboneをVGG-16からResNet-101に置き換えた結果:

COCO Segmentation

これらの結果は、ResNetが単純なClassification専用モデルではなく、多様なVision Taskに強力な性能を提供する汎用的なFeature Extractorであることを実証した。


8. 実装の詳細

8.1 He Initialization

ResNet論文の著者であるKaiming Heは、ResNet以前にすでにReLUネットワークに適した重み初期化手法を提案していた(「Delving Deep into Rectifiers」、He et al.、2015)。

W∼N(0,2nin)W \sim \mathcal{N}\left(0, \sqrt{\frac{2}{n_{in}}}\right)

ここで ninn_{in} は該当層の入力ユニット数である。ReLUが入力の半分(負の部分)を0にすることを考慮して分散を 2nin\frac{2}{n_{in}} に設定する。Xavier Initialization(1nin\frac{1}{n_{in}})はSigmoid/Tanhに適しているが、ReLUでは分散が徐々に減少する問題がある。

He Initializationは各層の出力分散を一定に保ち、Forward Passで信号が消失したり爆発したりしないようにする。

8.2 Batch Normalization

ResNetはすべてのConvolution層の後にBatch Normalization(BN)を適用する。

x^i=xi−μBσB2+ϵ\hat{x}_i = \frac{x_i - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} yi=γx^i+βy_i = \gamma \hat{x}_i + \beta

ここで:

BNの役割は以下の通りである。

ResNetにおけるBNの配置順序は Conv → BN → ReLU(Post-activation)である。この順序は後続研究(Pre-activation ResNet)で改善される。

8.3 Training Schedule

ImageNetでの学習設定:

HyperparameterValue
OptimizerSGD with Momentum
Momentum0.9
Weight Decay0.0001
Batch Size256
Initial Learning Rate0.1
LR Schedule30エポックごとに1/10に減少
Total Epochs約90
Data AugmentationRandom Crop (224x224), Horizontal Flip, Color Jittering
PreprocessingPer-pixel Mean Subtraction

学習時、画像は[256, 480]の範囲でランダムリサイズ後、224x224にRandom Cropされる。テスト時には10-crop Testing(4隅+中央+各Horizontal Flip)が使用され、Multi-scale Testingでは{224, 256, 384, 480, 640}のサイズでFully Convolutional推論が実行される。

8.4 Dropoutの不使用

興味深いことに、ResNetはDropoutを使用しない。Batch Normalizationが十分なRegularization効果を提供し、Bottleneck構造が本質的にパラメータ数を制限するためである。Global Average PoolingもFC Layerのパラメータを大幅に削減し、Overfittingのリスクを低下させる。


9. PyTorch実装

9.1 Basic Block

import torch
import torch.nn as nn

class BasicBlock(nn.Module):
    """ResNet-18、ResNet-34に使用されるBasic Residual Block"""
    expansion = 1  # 出力チャネル = 入力チャネル * expansion

    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        # 最初の3x3 Conv(strideによるdownsampling可能)
        self.conv1 = nn.Conv2d(
            in_channels, out_channels, kernel_size=3,
            stride=stride, padding=1, bias=False
        )
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)

        # 2番目の3x3 Conv
        self.conv2 = nn.Conv2d(
            out_channels, out_channels, kernel_size=3,
            stride=1, padding=1, bias=False
        )
        self.bn2 = nn.BatchNorm2d(out_channels)

        # Shortcut:次元が異なる場合に1x1 Convでprojection
        self.downsample = downsample

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        # Shortcut Connection
        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity  # F(x) + x
        out = self.relu(out)

        return out

9.2 Bottleneck Block

class Bottleneck(nn.Module):
    """ResNet-50、ResNet-101、ResNet-152に使用されるBottleneck Block"""
    expansion = 4  # 出力チャネル = 中間チャネル * 4

    def __init__(self, in_channels, mid_channels, stride=1, downsample=None):
        super(Bottleneck, self).__init__()
        out_channels = mid_channels * self.expansion

        # 1x1 Conv:チャネル縮小(Squeeze)
        self.conv1 = nn.Conv2d(
            in_channels, mid_channels, kernel_size=1, bias=False
        )
        self.bn1 = nn.BatchNorm2d(mid_channels)

        # 3x3 Conv:空間的処理(strideによるdownsampling可能)
        self.conv2 = nn.Conv2d(
            mid_channels, mid_channels, kernel_size=3,
            stride=stride, padding=1, bias=False
        )
        self.bn2 = nn.BatchNorm2d(mid_channels)

        # 1x1 Conv:チャネル復元(Expand)
        self.conv3 = nn.Conv2d(
            mid_channels, out_channels, kernel_size=1, bias=False
        )
        self.bn3 = nn.BatchNorm2d(out_channels)

        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample

    def forward(self, x):
        identity = x

        # 1x1 → BN → ReLU
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        # 3x3 → BN → ReLU
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        # 1x1 → BN
        out = self.conv3(out)
        out = self.bn3(out)

        # Shortcut Connection
        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity  # F(x) + x
        out = self.relu(out)

        return out

9.3 ResNetモデル全体

class ResNet(nn.Module):
    def __init__(self, block, layers, num_classes=1000):
        """
        Args:
            block: BasicBlockまたはBottleneck
            layers: 各stageのblock数 [conv2_x, conv3_x, conv4_x, conv5_x]
            num_classes: 分類クラス数
        """
        super(ResNet, self).__init__()
        self.in_channels = 64

        # conv1: 7x7, stride 2
        self.conv1 = nn.Conv2d(
            3, 64, kernel_size=7, stride=2, padding=3, bias=False
        )
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

        # conv2_x〜conv5_x
        self.layer1 = self._make_layer(block, 64, layers[0], stride=1)
        self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
        self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
        self.layer4 = self._make_layer(block, 512, layers[3], stride=2)

        # Classification Head
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Linear(512 * block.expansion, num_classes)

        # He Initialization
        self._initialize_weights()

    def _make_layer(self, block, mid_channels, num_blocks, stride):
        downsample = None
        out_channels = mid_channels * block.expansion

        # 最初のblockでdownsamplingが必要な場合
        if stride != 1 or self.in_channels != out_channels:
            downsample = nn.Sequential(
                nn.Conv2d(
                    self.in_channels, out_channels,
                    kernel_size=1, stride=stride, bias=False
                ),
                nn.BatchNorm2d(out_channels),
            )

        layers = []
        layers.append(block(self.in_channels, mid_channels, stride, downsample))
        self.in_channels = out_channels

        for _ in range(1, num_blocks):
            layers.append(block(self.in_channels, mid_channels))

        return nn.Sequential(*layers)

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # He Initialization (fan_out mode)
                nn.init.kaiming_normal_(
                    m.weight, mode='fan_out', nonlinearity='relu'
                )
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def forward(self, x):
        # Stem
        x = self.conv1(x)       # 224x224 → 112x112
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)     # 112x112 → 56x56

        # Residual Stages
        x = self.layer1(x)      # 56x56
        x = self.layer2(x)      # 28x28
        x = self.layer3(x)      # 14x14
        x = self.layer4(x)      # 7x7

        # Classification Head
        x = self.avgpool(x)     # 1x1
        x = torch.flatten(x, 1)
        x = self.fc(x)

        return x


# モデル生成関数
def resnet18(num_classes=1000):
    return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)

def resnet34(num_classes=1000):
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes)

def resnet50(num_classes=1000):
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes)

def resnet101(num_classes=1000):
    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes)

def resnet152(num_classes=1000):
    return ResNet(Bottleneck, [3, 8, 36, 3], num_classes)

9.4 使用例

# ResNet-50の生成とForward Pass
model = resnet50(num_classes=1000)
x = torch.randn(1, 3, 224, 224)  # Batch=1, RGB, 224x224
output = model(x)
print(f"Output shape: {output.shape}")  # torch.Size([1, 1000])

# パラメータ数の確認
total_params = sum(p.numel() for p in model.parameters())
print(f"Total parameters: {total_params:,}")
# ResNet-50: 約25,557,032 (25.6M)

# PyTorch公式の事前学習済みモデルの使用
import torchvision.models as models
resnet50_pretrained = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V2)

10. Pre-activation ResNet: Identity Mappings in Deep Residual Networks

10.1 後続論文の動機

ResNet論文の発表直後、同じ著者ら(He et al.)が2016年のECCVで「Identity Mappings in Deep Residual Networks」を発表した。この論文はResidual Block内部の演算順序を再構成することで、より深いネットワーク(1001層)をより効果的に学習できることを示した。

10.2 OriginalとPre-activation

Original ResNet(Post-activation):

xl+1=ReLU(F(xl)+xl)\mathbf{x}_{l+1} = \text{ReLU}(\mathcal{F}(\mathbf{x}_l) + \mathbf{x}_l)

この構造ではReLUがAdditionの後に位置するため、Shortcut経路を通る信号もReLUの影響を受ける。これによりIdentity Mappingが完全なIdentityではなくなり、Gradient Highwayの効果が弱まる。

Pre-activation ResNet:

xl+1=xl+F(BN(ReLU(xl)))\mathbf{x}_{l+1} = \mathbf{x}_l + \mathcal{F}(\text{BN}(\text{ReLU}(\mathbf{x}_l)))

演算順序をBN → ReLU → Conv → BN → ReLU → Convに変更する。これによりShortcut経路が純粋なIdentity Mappingとなる。

10.3 構造比較

[Original ResNet]                [Pre-activation ResNet]
Input ──┐                        Input ──┐
        │                                │
     Conv                             BN
        │                                │
      BN                             ReLU
        │                                │
     ReLU                            Conv
        │                                │
     Conv                             BN
        │                                │
      BN                             ReLU
        │                                │
   (+) ←┘ shortcut                   Conv
        │                                │
     ReLU                           (+) ←┘ shortcut
        │                                │
     Output                          Output

10.4 数学的利点

Pre-activation構造でのForward Propagation:

xL=xl+∑i=lL−1F(xi)\mathbf{x}_L = \mathbf{x}_l + \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i)

Backward Propagation:

∂L∂xl=∂L∂xL⋅(1+∂∂xl∑i=lL−1F(xi))\frac{\partial \mathcal{L}}{\partial \mathbf{x}_l} = \frac{\partial \mathcal{L}}{\partial \mathbf{x}_L} \cdot \left(1 + \frac{\partial}{\partial \mathbf{x}_l} \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i)\right)

Shortcutが純粋なIdentityであるため、定数項1が正確に保存される。Original ResNetではReLUが介在するため、この定数項が正確に1ではなくなる。

10.5 実験結果

ModelCIFAR-10 Error (%)CIFAR-100 Error (%)
ResNet-110 (original)6.43-
ResNet-1001 (original)~7.61-
ResNet-1001 (pre-activation)4.6222.71

Pre-activation構造は特に非常に深いネットワーク(1001層)において、元の構造と比較して大幅な性能向上を示した。これは純粋なIdentity MappingがGradient Flowにとって決定的に重要であることを実験的に確認した結果である。

10.6 PyTorch実装

class PreActBasicBlock(nn.Module):
    """Pre-activation Basic Block (BN → ReLU → Conv)"""
    expansion = 1

    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super(PreActBasicBlock, self).__init__()
        self.bn1 = nn.BatchNorm2d(in_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv1 = nn.Conv2d(
            in_channels, out_channels, kernel_size=3,
            stride=stride, padding=1, bias=False
        )
        self.bn2 = nn.BatchNorm2d(out_channels)
        self.conv2 = nn.Conv2d(
            out_channels, out_channels, kernel_size=3,
            stride=1, padding=1, bias=False
        )
        self.downsample = downsample

    def forward(self, x):
        identity = x

        # Pre-activation: BN → ReLU → Conv
        out = self.bn1(x)
        out = self.relu(out)

        if self.downsample is not None:
            identity = self.downsample(out)

        out = self.conv1(out)
        out = self.bn2(out)
        out = self.relu(out)
        out = self.conv2(out)

        out += identity
        return out

11. ResNetの影響と後続研究

ResNetが提案したResidual Learningパラダイムは、その後の数多くのアーキテクチャ革新の基盤となった。主要な後続研究を見てみよう。

11.1 ResNeXt (2017)

"Aggregated Residual Transformations for Deep Neural Networks" - Xie et al., Facebook AI Research

ResNeXtはResNetのResidual BlockにCardinality(グループ数)という新しい次元を導入した。1つのブロック内で複数の変換経路を並列に実行し、合算する。

F(x)=∑i=1CTi(x)\mathcal{F}(\mathbf{x}) = \sum_{i=1}^{C} \mathcal{T}_i(\mathbf{x})

ここで CC はCardinality(例:32)、Ti\mathcal{T}_i は各経路の変換である。実装ではGrouped Convolutionで効率的に処理される。

ResNeXt-101(32x4d)はResNet-101と同じ演算量でより高い精度を達成し、Width(チャネル数)やDepth(層数)よりもCardinalityがより効果的な次元であることを示した。

11.2 DenseNet (2017)

"Densely Connected Convolutional Networks" - Huang et al., Cornell/Facebook

DenseNetはResidual Connectionを極限まで拡張したアーキテクチャである。各層が前のすべての層と直接結合される。ResNetがElement-wise Additionを使用するのに対し、DenseNetはChannel-wise Concatenationを使用する。

xl=Hl([x0,x1,...,xl−1])\mathbf{x}_l = \mathcal{H}_l([\mathbf{x}_0, \mathbf{x}_1, ..., \mathbf{x}_{l-1}])

この構造はFeature Reuseを最大化し、パラメータ効率を向上させる。DenseNet-121はResNet-50より少ないパラメータで同等の性能を達成した。

11.3 SENet (2018)

"Squeeze-and-Excitation Networks" - Hu et al., Momenta

SENetはResidual Blockにチャネル間の関係をモデリングするSE Moduleを追加した。各チャネルの重要度を学習して重みを再調整する。

s=σ(W2⋅ReLU(W1⋅GAP(x)))\mathbf{s} = \sigma(\mathbf{W}_2 \cdot \text{ReLU}(\mathbf{W}_1 \cdot \text{GAP}(\mathbf{x}))) x~=s⊙x\tilde{\mathbf{x}} = \mathbf{s} \odot \mathbf{x}

ここでGAPはGlobal Average Pooling、σ\sigma はSigmoid、⊙\odot はChannel-wise Multiplicationである。SENetはILSVRC 2017 ClassificationでTop-5 Error 2.251%で1位を獲得した。

11.4 EfficientNet (2019)

"EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks" - Tan & Le, Google Brain

EfficientNetはネットワークのWidth、Depth、Resolutionを同時にバランス良くスケーリングするCompound Scaling方法を提案した。MBConv(Mobile Inverted Bottleneck)ブロックを基盤とし、このブロックもResidual Connectionを使用する。

depth:d=αϕ,width:w=βϕ,resolution:r=γϕ\text{depth}: d = \alpha^\phi, \quad \text{width}: w = \beta^\phi, \quad \text{resolution}: r = \gamma^\phi s.t.α⋅β2⋅γ2≈2\text{s.t.} \quad \alpha \cdot \beta^2 \cdot \gamma^2 \approx 2

EfficientNet-B7はResNetの8.4分の1のパラメータで当時最高水準のImageNet精度を達成した。

11.5 ConvNeXt (2022)

"A ConvNet for the 2020s" - Liu et al., Facebook AI Research

ConvNeXtはVision Transformer(ViT)の設計原則をCNNに適用して「現代化されたResNet」を作った研究である。ResNet-50を出発点として以下の変更を順次適用した。

  1. Training Recipeの現代化(300エポック、AdamW、Mixup、Cutmixなど)
  2. Stage Ratio変更:(3, 4, 6, 3) → (3, 3, 9, 3)
  3. 「Patchify」Stem:7x7 Conv → 4x4 Conv, stride 4
  4. ResNeXt-style Grouped Convolution
  5. Inverted Bottleneck
  6. Large Kernel Size:3x3 → 7x7 Depthwise Conv
  7. 活性化関数:ReLU → GELU
  8. 正規化:BN → Layer Normalization

ConvNeXt-TはSwin-Tと同等の性能(82.1% top-1 accuracy)を達成し、純粋なCNNアーキテクチャもTransformerと競争できることを示した。この研究はResNetの設計がいかに堅固な基盤であるかを再確認させてくれた。


12. 現代アーキテクチャにおけるResidual Connection

12.1 TransformerにおけるSkip Connection

Vaswani et al.の「Attention Is All You Need」(2017)で提案されたTransformerアーキテクチャはすべてのSub-layerにResidual Connectionを使用する。

Output=LayerNorm(x+SubLayer(x))\text{Output} = \text{LayerNorm}(\mathbf{x} + \text{SubLayer}(\mathbf{x}))

ここでSubLayerはMulti-Head AttentionまたはFeed-Forward Networkである。ResNetで証明されたのと同じ理由で、このResidual Connectionなしでは深いTransformerを学習させることは事実上不可能である。

12.2 Pre-LayerNormとPost-LayerNorm

Transformerでも、ResNetのPre-activation vs Post-activationと類似の議論がある。

Post-LayerNorm(元のTransformer):

xl+1=LN(xl+SubLayer(xl))\mathbf{x}_{l+1} = \text{LN}(\mathbf{x}_l + \text{SubLayer}(\mathbf{x}_l))

Pre-LayerNorm(GPT-2以降の標準):

xl+1=xl+SubLayer(LN(xl))\mathbf{x}_{l+1} = \mathbf{x}_l + \text{SubLayer}(\text{LN}(\mathbf{x}_l))

Pre-LayerNormはResNetのPre-activationと同じ原理で、Shortcut経路を純粋なIdentityに維持してGradient Flowを改善する。GPT-2、GPT-3など最新のLarge Language Modelの大部分はPre-LayerNormを使用している。

12.3 Diffusion ModelにおけるResidual Connection

Denoising Diffusion Probabilistic Model(DDPM)のU-NetアーキテクチャでもResidual BlockにSkip Connectionが使用される。U-NetのEncoder-Decoder間のLong Skip Connectionとブロック内部のResidual Skip Connectionが組み合わされ、様々なスケールの特徴量を効果的に活用する。

12.4 Vision Transformer (ViT)

ViT(Vision Transformer)は画像を16x16のパッチに分割した後、Transformer Encoderに入力する。各Transformerブロックは当然Residual Connectionを使用しており、これなしでは12層以上のViTを学習させることは困難である。

12.5 核心的教訓

ResNetが残した最も重要な遺産は、特定のアーキテクチャではなくResidual Connectionという設計原則である。この原則は以下のようにまとめることができる。

  1. Identity Mappingをデフォルト値に設定せよ:ネットワークが何も学習できなくても、少なくとも入力をそのまま伝達できるようにすべきである。
  2. Gradient Highwayを確保せよ:LossからすべてのLayerまでGradientが直接流れる経路を作れ。
  3. 深さは自由度である:Residual Connectionがあれば、ネットワークの深さを増やすことは常に有益であるか、少なくとも損失にはならない。

この原則はCNN、Transformer、Diffusion Model、State Space Modelなど、アーキテクチャの種類を問わず普遍的に適用される。


13. 限界と批判

13.1 Feature Reuseの非効率性

Veit et al.(2016)の「Residual Networks Behave Like Ensembles of Relatively Shallow Networks」研究によると、ResNetの大部分の層は実際に非常に短い経路(Shallow Path)を通じて情報を伝達しており、非常に深い経路の寄与は微小である。これは152層全体が効率的に活用されているかについての疑問を提起する。

13.2 Feature MapのElement-wise Addition

DenseNetの著者らは、ResNetのElement-wise Additionが情報損失を引き起こす可能性があると主張した。ConcatenationベースのDenseNetがより効率的なFeature Reuseを可能にするというものである。ただし、Concatenationはメモリ使用量が急激に増加する問題がある。

13.3 計算オーバーヘッド

Global Average PoolingとBottleneck構造でパラメータ数を削減したが、非常に深いResNet(ResNet-152)の実際の推論速度はVGGNetより必ずしも速くない。Memory Access CostとSequential Dependencyが実質的なボトルネックとなり得る。


14. まとめ

ResNetはディープラーニング史上最も重要な論文の一つである。その貢献を整理すると以下の通りである。

  1. Degradation問題の発見と定義:深いネットワークでTraining Errorが増加する現象を明確に規明した。

  2. Residual Learning Framework:F(x)+x\mathcal{F}(\mathbf{x}) + \mathbf{x} 構造でIdentity Mappingを明示的に含め、数百層のネットワークを成功裏に学習させた。

  3. Gradient Highway理論:Skip ConnectionがGradientを直接伝播させる数学的メカニズムを提示した。

  4. Bottleneck構造:1x1 Convolutionを活用したチャネル縮小/復元により、深さと効率性を同時に達成した。

  5. 圧倒的な実験結果:ImageNet(3.57% top-5 error)、CIFAR-10、COCO Detection/Segmentationで既存のすべての方法を圧倒した。

  6. 汎用的な設計原則:Residual Connectionは、Transformer、Diffusion Modelなど現代ディープラーニングのすべての主要アーキテクチャに必須要素として定着した。

単純な加算演算一つ(+x+ \mathbf{x})がディープラーニングの深さの限界を突破し、その後10年間のAI発展の礎となった。ResNetは時に最もシンプルなアイデアが最も強力であることを示す代表的な事例である。


15. References

  1. He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep Residual Learning for Image Recognition. CVPR 2016. arXiv:1512.03385

  2. He, K., Zhang, X., Ren, S., & Sun, J. (2016). Identity Mappings in Deep Residual Networks. ECCV 2016. arXiv:1603.05027

  3. He, K., Zhang, X., Ren, S., & Sun, J. (2015). Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. ICCV 2015. arXiv:1502.01852

  4. Simonyan, K., & Zisserman, A. (2014). Very Deep Convolutional Networks for Large-Scale Image Recognition. ICLR 2015. arXiv:1409.1556

  5. Szegedy, C., et al. (2015). Going Deeper with Convolutions. CVPR 2015. arXiv:1409.4842

  6. Ioffe, S., & Szegedy, C. (2015). Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. ICML 2015. arXiv:1502.03167

  7. Xie, S., et al. (2017). Aggregated Residual Transformations for Deep Neural Networks. CVPR 2017. arXiv:1611.05431

  8. Huang, G., et al. (2017). Densely Connected Convolutional Networks. CVPR 2017. arXiv:1608.06993

  9. Hu, J., et al. (2018). Squeeze-and-Excitation Networks. CVPR 2018. arXiv:1709.01507

  10. Tan, M., & Le, Q. (2019). EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks. ICML 2019. arXiv:1905.11946

  11. Liu, Z., et al. (2022). A ConvNet for the 2020s. CVPR 2022. arXiv:2201.03545

  12. Vaswani, A., et al. (2017). Attention Is All You Need. NeurIPS 2017. arXiv:1706.03762

  13. Veit, A., et al. (2016). Residual Networks Behave Like Ensembles of Relatively Shallow Networks. NeurIPS 2016. arXiv:1605.06431

  14. KaimingHe/deep-residual-networks. GitHub Repository

  15. ILSVRC 2015 Results. ImageNet Challenge

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます