ResNet: 두 판 사이의 차이
편집 요약 없음 |
편집 요약 없음 |
||
| 211번째 줄: | 211번째 줄: | ||
*[[Convolutional Neural Network]] | *[[Convolutional Neural Network]] | ||
*[[Deep Learning]] | *[[Deep Learning]] | ||
*[[Wide ResNet]] | |||
*[[ResNeXt]] | *[[ResNeXt]] | ||
*[[DenseNet]] | *[[DenseNet]] | ||
2025년 10월 25일 (토) 05:18 기준 최신판
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])