Skip to content
Published on

ResNet 논문 완벽 분석: 잔차 연결(Residual Connection)이 딥러닝의 깊이 한계를 돌파한 방법

Authors
  • Name
    Twitter

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) 하나 대신 작은 필터(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. BBAA와 최소한 동일한 성능을 가져야 한다 (추가 층이 항등 함수이므로).
  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}

여기서:

  • x\mathbf{x}: 블록의 입력
  • F(x,{Wi})\mathcal{F}(\mathbf{x}, \{W_i\}): 학습해야 하는 잔차 함수 (2~3개의 Convolution 층)
  • y\mathbf{y}: 블록의 출력

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이다. 논문에서는 세 가지 옵션을 실험했다.

  • Option A: Zero-padding으로 차원을 맞춤 (파라미터 추가 없음)
  • Option B: 차원이 변할 때만 1x1 Convolution Projection 사용
  • Option C: 모든 Shortcut에 1x1 Convolution Projection 사용

실험 결과 세 옵션 모두 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=lL1F(xi,Wi)\mathbf{x}_L = \mathbf{x}_l + \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i, W_i)

이 수식이 의미하는 바는 매우 중요하다. 임의의 깊은 층 LL의 Feature는 얕은 층 ll의 Feature와, 그 사이의 모든 Residual Function의 합으로 표현된다. Plain Network에서는 이것이 행렬 곱의 연쇄(Chain of Matrix Multiplications)인 반면, ResNet에서는 덧셈의 형태로 나타난다.

5.2 Backward Propagation과 Gradient Highway

이제 핵심인 Backward Propagation을 살펴보자. Loss를 L\mathcal{L}이라 하면, Chain Rule에 의해:

Lxl=LxLxLxl\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 수식을 대입하면:

Lxl=LxL(1+xli=lL1F(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까지 직접 흐를 수 있다. xli=lL1F(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가 모든 층의 가중치 행렬을 곱해야 하므로:

Lxl=i=lL1xi+1xiLxL\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은 거의 Identity에 가까운 매핑을 수행한다. 이는 깊은 네트워크가 학습 초기에 얕은 네트워크처럼 동작하다가, 점진적으로 각 블록이 유용한 변환을 학습하는 과정으로 해석할 수 있다.


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-class Softmax

Feature Map의 크기가 절반으로 줄어들 때(conv3_x, conv4_x, conv5_x의 첫 번째 블록) 채널 수는 두 배로 증가한다. 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 Detection: VGG-16 대비 mAP@[.5, .95] 기준 6.0% 향상 (상대적으로 28% 개선)
  • ILSVRC 2015 Detection Task 1위
  • COCO 2015 Detection Task 1위

COCO Segmentation

  • COCO 2015 Segmentation Task 1위

이 결과들은 ResNet이 단순한 Classification 전용 모델이 아니라, 범용적인 Feature Extractor로서 다양한 Vision Task에 강력한 성능을 제공한다는 것을 입증했다.


8. 구현 디테일

8.1 He Initialization

ResNet 논문의 저자인 Kaiming He는 ResNet 이전에 이미 ReLU 네트워크에 적합한 가중치 초기화 방법을 제안했다 ("Delving Deep into Rectifiers", He et al., 2015).

WN(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

여기서:

  • μB\mu_B, σB2\sigma_B^2: Mini-batch의 평균과 분산
  • γ\gamma, β\beta: 학습 가능한 Scale과 Shift 파라미터
  • ϵ\epsilon: 수치 안정성을 위한 작은 상수

BN의 역할은 다음과 같다.

  • Internal Covariate Shift 완화: 각 층의 입력 분포를 정규화하여 학습을 안정화
  • Regularization 효과: Mini-batch 단위의 정규화가 약간의 Noise를 추가하여 Regularizer 역할
  • 높은 Learning Rate 허용: 분포가 안정화되므로 더 높은 Learning Rate을 사용 가능

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 epoch마다 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 corners + center + 각각의 Horizontal Flip)을 사용하며, Multi-scale Testing에서는 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)

        # 두 번째 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 공식 Pre-trained 모델 사용
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 vs 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=lL1F(xi)\mathbf{x}_L = \mathbf{x}_l + \sum_{i=l}^{L-1} \mathcal{F}(\mathbf{x}_i)

Backward Propagation은:

Lxl=LxL(1+xli=lL1F(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(그룹 수)라는 새로운 차원을 도입했다. 하나의 블록 안에서 여러 개의 변환 경로를 병렬로 수행한 뒤 합산한다.

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,...,xl1])\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=σ(W2ReLU(W1GAP(x)))\mathbf{s} = \sigma(\mathbf{W}_2 \cdot \text{ReLU}(\mathbf{W}_1 \cdot \text{GAP}(\mathbf{x}))) x~=sx\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γ22\text{s.t.} \quad \alpha \cdot \beta^2 \cdot \gamma^2 \approx 2

EfficientNet-B7은 ResNet보다 8.4배 적은 파라미터로 당시 최고 수준의 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 epoch, 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이 결합되어, 다양한 스케일의 Feature를 효과적으로 활용한다.

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에서 모든 층까지 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 Computational Overhead

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