ResNet
ResNet(Residual Network)은 딥러닝에서 층이 매우 깊은 신경망을 안정적으로 학습하기 위해 제안된 구조이다.
개요
ResNet은 입력을 그대로 다음 층에 더해주는 스킵 연결(skip or shortcut connection)을 도입하여, 각 층이 원래 함수 H(x)를 직접 학습하는 대신 잔차(residual) 함수 F(x) = H(x) - x를 학습하도록 재정의한 구조이다. 이 방식은 기울기 소실(vanishing gradient) 문제를 완화하고, 매우 깊은 신경망에서도 안정적으로 학습이 가능하게 한다.
등장 배경
깊은 신경망을 설계할수록 성능이 향상될 것으로 기대되지만, 현실에서는 층을 깊게 쌓을수록 학습이 어려워지고 오히려 성능이 나빠지는 성능 저하(degradation) 문제가 발생한다. 이 문제를 해결하기 위해 2015년 마이크로소프트 연구진은 "Deep Residual Learning for Image Recognition" 논문에서 ResNet 구조를 제안하였고, ImageNet 대회에서 우수한 성능을 기록했다.
주요 버전
ResNet은 층의 수(depth)에 따라 다양한 버전으로 구성된다. 각 버전은 기본적으로 동일한 아이디어(잔차 학습)를 유지하지만, 블록의 종류, 반복 횟수, 채널 수, 계산량 등이 다르게 설계되어 있다.
- ResNet-18
- 18개의 가중치 층으로 구성된 비교적 얕은 모델
- 기본 블록(Basic Block)을 사용하며, 각 블록은 3×3 합성곱 2개로 이루어짐
- 계산량이 적고, 소형 모델이나 임베디드 환경에서 자주 사용됨
- ImageNet 기준으로 약 11M 파라미터를 가짐
- ResNet-34
- ResNet-18보다 깊지만 동일한 기본 블록 구조를 유지
- 각 스테이지(conv2_x~conv5_x)의 블록 수가 늘어나 더 풍부한 표현 가능
- 학습 안정성을 유지하면서도 정확도를 개선한 중간 규모 모델
- ResNet-50
- 병목 블록(Bottleneck Block) 구조를 처음 도입한 대표 모델
- 각 블록이 1×1 → 3×3 → 1×1 합성곱으로 구성되어, 연산량 대비 표현력이 높음
- 채널 수가 64→256, 128→512, 256→1024, 512→2048로 점진적으로 증가
- ImageNet 분류 기준으로 약 25M 파라미터를 가지며, 실무에서 가장 널리 사용됨
- ResNet-101
- ResNet-50보다 conv4_x 스테이지의 병목 블록 수를 크게 늘려 깊이를 확장
- 더 풍부한 특징 추출이 가능하며, 고해상도 이미지나 세밀한 분류 작업에서 우수
- 연산량은 늘어나지만, 전이 학습(Transfer Learning) 용도로 자주 활용됨
- 약 44M 파라미터 규모
- ResNet-152
- 가장 깊은 버전 중 하나로, 152개의 층으로 구성
- conv4_x 스테이지가 매우 깊으며, 학습에는 많은 계산 자원이 필요
- 매우 정밀한 표현을 요구하는 연구나 대형 데이터셋에서 주로 사용
- 약 60M 파라미터를 가지며, 학습 효율보다 정확도에 중점을 둔 설계
이처럼 ResNet의 각 버전은 단순히 층 수만 다른 것이 아니라, 블록의 형태, 채널 구성, 계산 효율, 성능 균형을 실험적으로 조정한 휴리스틱 설계 결과이다.
구조 및 구성 요소
ResNet은 Residual Block 단위로 구성된다. 각 잔차 블록은 다음과 같은 형태를 갖는다.
- 기본 블록 (Basic Block)
- 두 개의 3×3 합성곱 층과 배치 정규화, ReLU 활성화로 구성됨
- ResNet-18, 34에서 사용
- 병목 블록 (Bottleneck Block)
- 1×1 축소 → 3×3 처리 → 1×1 확장의 형태로 세 층을 구성
- ResNet-50, 101, 152 등에서 사용됨
- 사전 활성화 블록 (Pre‑activation Block)
- 잔차 함수 계산 전에 정규화와 활성화를 먼저 적용함
- 더 깊은 네트워크에서도 안정적인 학습 가능
스킵 연결을 통해 출력은 다음과 같이 계산된다: y = F(x) + x 여기서 F(x)는 잔차 함수이며, x는 블록의 입력이다.
장단점
장점
- 깊은 구조 설계 가능
- 수백 개 층 이상에서도 안정적인 학습이 가능하다
- 기울기 보존
- 스킵 연결 덕분에 역전파 시 기울기 소실을 방지할 수 있다
- 수렴 속도 향상
- 잔차 학습 방식으로 더 빠른 수렴 가능
- 범용성
- 다양한 컴퓨터 비전 과제에서 강력한 백본(backbone)으로 활용됨
단점 / 제약
- 구조 복잡성
- 블록 설계와 스킵 연결 방식이 복잡해질 수 있음
- 계산 자원 요구
- 깊은 네트워크일수록 연산량과 메모리 소모가 커짐
- 과적합 위험
- 매우 깊은 모델은 학습 데이터에 과적합될 가능성이 있음
주요 변형 및 후속 연구
- ResNeXt
- 그룹 합성곱을 이용해 표현력을 확장한 구조
- Wide ResNet
- 층 수를 줄이고 각 층의 너비를 넓힌 변형
- DenseNet
- 모든 이전 층의 출력을 연결하여 활용하는 방식
- SE‑ResNet
- 채널 중요도를 학습하여 강조하는 SE 모듈을 통합한 구조
- Improved ResNet
- 블록 내부 구조 및 스킵 연결 방식을 개선한 다양한 후속 모델들
응용 분야
ResNet은 이미지 분류, 객체 검출, 시맨틱 분할, 비디오 분석 등의 컴퓨터 비전 과제에서 널리 사용된다. 또한 Transformer 구조에서도 잔차 연결 개념이 도입되어 활용되고 있다.
구현 예시 (PyTorch 기준)
다음은 PyTorch로 ResNet을 구현하는 일반적인 방식의 예시이다. torchvision의 기본 구조를 따르며, 블록 구성과 계층 조립을 유연하게 설정할 수 있도록 한다.
import torch
import torch.nn as nn
import torch.nn.functional as F
# 기본 블록 정의 (ResNet-18, 34)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super(BasicBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3,
stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3,
stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample
def forward(self, x):
identity = x
if self.downsample:
identity = self.downsample(x)
out = F.relu(self.bn1(self.conv1(x)))
out = self.bn2(self.conv2(out))
out += identity
return F.relu(out)
# 병목 블록 정의 (ResNet-50, 101, 152)
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3,
stride=stride, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.conv3 = nn.Conv2d(out_channels, out_channels * self.expansion,
kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels * self.expansion)
self.downsample = downsample
def forward(self, x):
identity = x
if self.downsample:
identity = self.downsample(x)
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += identity
return F.relu(out)
# ResNet 본체 정의
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000):
super(ResNet, self).__init__()
self.in_channels = 64
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)
self.layer1 = self._make_layer(block, 64, layers[0])
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)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * block.expansion, num_classes)
def _make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if stride != 1 or self.in_channels != out_channels * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_channels, out_channels * block.expansion,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_channels * block.expansion),
)
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels * block.expansion
for _ in range(1, blocks):
layers.append(block(self.in_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
x = self.relu(self.bn1(self.conv1(x)))
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
# 각 버전에 맞춘 팩토리 함수
def ResNet18():
return ResNet(BasicBlock, [2, 2, 2, 2])
def ResNet34():
return ResNet(BasicBlock, [3, 4, 6, 3])
def ResNet50():
return ResNet(Bottleneck, [3, 4, 6, 3])
def ResNet101():
return ResNet(Bottleneck, [3, 4, 23, 3])
def ResNet152():
return ResNet(Bottleneck, [3, 8, 36, 3])
같이 보기
참고 문헌
(없음)
각주
(없음)