딥러닝 분야에서 Residual Network, 줄여서 ResNet은 매우 중요한 아키텍처로 자리 잡았습니다. ResNet은 2015년 Kaiming He에 의해 제안되었으며, 특히 딥러닝 모델의 깊이를 효과적으로 증가시킬 수 있는 방법을 제공합니다. 현대의 다양한 컴퓨터 비전 문제들에서 ResNet은 성능 향상의 주요 원인 중 하나로 꼽힙니다.
1. ResNet의 개요
ResNet은 “Residual Learning” 프레임워크를 기반으로 한 신경망입니다. 전통적으로, 심층 신경망(dnn)은 더 깊어질수록 성능의 저하가 발생하는 경향이 있습니다. 이는 주로 기울기 소실(vanishing gradient) 문제 때문인데, 이 문제는 신경망의 깊이가 깊어질수록 역전파 과정에서 기울기가 소실되어 가는 현상입니다.
ResNet은 이러한 문제를 해결하기 위해 잔차 연결(residual connection)을 도입하였습니다. 잔차 연결은 네트워크의 입력을 출력에 더함으로써 한 계층에서 이전 계층의 정보를 직접 전달합니다. 이러한 방식을 통해 더 깊은 네트워크를 효과적으로 학습할 수 있습니다.
2. ResNet의 구조
ResNet은 다양한 깊이를 가진 모델로 구성될 수 있으며, 일반적으로 “ResNet50”, “ResNet101”, “ResNet152″와 같은 식으로 표기됩니다. 이 숫자는 네트워크의 총 층 수를 의미합니다.
2.1 기본 블록 구성
ResNet의 기본 구성 요소는 다음과 같은 블록으로 이루어져 있습니다:
- 컨볼루션 레이어
- Batch Normalization
- ReLU 활성화 함수
- 잔차 연결
일반적인 ResNet 블록의 구조는 다음과 같습니다:
def resnet_block(input_tensor, filters, kernel_size=3, stride=1):
x = Conv2D(filters, kernel_size=kernel_size, strides=stride, padding='same')(input_tensor)
x = BatchNormalization()(x)
x = ReLU()(x)
x = Conv2D(filters, kernel_size=kernel_size, strides=stride, padding='same')(x)
x = BatchNormalization()(x)
shortcut = Conv2D(filters, kernel_size=1, strides=stride, padding='same')(input_tensor)
x = Add()([x, shortcut])
x = ReLU()(x)
return x
3. PyTorch를 이용한 ResNet 구현
이제 파이토치(Pytorch)를 사용하여 ResNet을 구현해 보겠습니다. 먼저 필요한 라이브러리를 설치합니다:
pip install torch torchvision
이후, 다음으로 기본 ResNet 모델을 구현합니다:
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision.datasets as datasets
import torchvision.models as models
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.relu = nn.ReLU(inplace=True)
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
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample is not None:
identity = self.downsample(x)
out += identity
out = self.relu(out)
return out
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.conv1(x)
x = self.bn1(x)
x = self.relu(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(num_classes=1000):
return ResNet(BasicBlock, [2, 2, 2, 2], num_classes)
3.1 모델 훈련 준비하기
ResNet 모델을 훈련하기 위해 데이터셋을 준비하고, 옵티마이저와 손실 함수를 설정합니다.
# 데이터셋 준비
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
# 모델 초기화
model = resnet18(num_classes=10)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
3.2 훈련 단계
이제 모델을 훈련시킬 준비가 되었습니다:
for epoch in range(10): # epochs 설정
model.train() # 모델을 훈련 모드로 전환
for images, labels in train_loader:
optimizer.zero_grad() # 기울기 초기화
outputs = model(images) # 모델 예측
loss = criterion(outputs, labels) # 손실 계산
loss.backward() # 역전파
optimizer.step() # 파라미터 업데이트
print(f'Epoch [{epoch+1}/10], Loss: {loss.item():.4f}')
4. ResNet의 활용
ResNet은 다양한 컴퓨터 비전 태스크에서 사용 가능합니다. 예를 들어, 이미지 분류, 객체 탐지, 세분화, 그리고 더 복잡한 비전 문제에 이르기까지 폭넓게 활용됩니다. Google, Facebook 등이 사용하는 여러 이미지 및 비디오 태스크에 ResNet 아키텍처가 포함되어 있습니다.
5. 결론
이번 강좌에서는 ResNet의 기본 개념 및 아키텍처에 대해 알아보았고, 파이토치를 이용하여 기본 ResNet 모델을 구현하는 방법을 배워보았습니다. 딥러닝 모델을 더 깊게 쌓을 수 있는 유연한 방법과 잔차 학습을 활용하여 더 나은 성능을 낼 수 있는 기회를 제공하는 ResNet은 많은 연구자와 개발자에게 영감을 주는 주요 아키텍처입니다.
이제 더 심화된 ResNet 구조 및 다양한 파라미터 조정, 데이터 증강 기법 등을 통한 모델 개선을 공부해 볼 수 있습니다.