1. Residual Block이란?
Residual Block(잔차 블록)은 딥러닝 모델에서 입력을 그대로 다음 레이어로 전달하는 Skip Connection을 포함한 블록이다.
기본 아이디어는 "출력값을 바로 다음 레이어에 넣는 것뿐만 아니라, 원래 입력값도 더해주자!" 이다.
즉, 일반적인 네트워크는 H(x)라는 함수를 학습하지만, Residual Block은 잔차(residual) F(x)를 학습하고, 이를 입력 x와 더해서 최종 출력 H(x)을 만든다.
점선으로 둘러싸인 residual block의 입장에서 보면, 들어오는 인풋 x가 있다고 할 때, 이 x를 그 블럭 내 레이어들을 통과시켜서 얻은 결과값 f(x)에다가 인풋 x를 그대로 더해준 것이 그 블럭의 최종 아웃풋이 된다.
H(x) = F(x) + x
여기서,
- H(x): 우리가 원하는 최종 출력값 (네트워크가 학습하려는 목표)
- F(x): 네트워크가 학습하는 함수 (CNN, ReLU, BatchNorm 등으로 이루어진 연산)
- x: 입력값 (이전 레이어에서 온 값)
즉, F(x) = H(x) - x이므로, 네트워크가 직접 H(x)를 학습하는 것이 아니라, 잔차(Residual, 즉 변화량) F(x)를 학습하도록 만든다.
이렇게 하면 신경망이 깊어질수록 학습이 어려워지는 문제(기울기 소실)를 해결할 수 있다.
2. Residual Block을 활용한 코드 설명 - CNN에 적용
① 간단한 Residual Block 구현
import torch
import torch.nn as nn
class ResBlock(nn.Module):
def __init__(self, block):
super().__init__()
self.block = block
def forward(self, x):
return self.block(x) + x # F(x) + x (입력을 더해줌)
이 코드에서:
- block은 일반적인 CNN 연산 (Conv + BatchNorm + ReLU)이다.
- forward()에서 block(x)와 원래 입력 x를 더해주는 것이 핵심이다.
즉, 입력이 그대로 다음 레이어로 전달되면서, 추가적으로 변형된 출력도 더해진다.
② 일반적인 CNN 모델
class Conv6(nn.Module):
def __init__(self, n_class=10):
super().__init__()
self.name = 'conv6'
self.model = nn.Sequential(
nn.Conv2d(3, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Flatten(),
nn.Linear(32*32*32, 256),
nn.ReLU(),
nn.Linear(256, n_class)
)
def forward(self, x):
return self.model(x)
이 모델은 일반적인 CNN이다. Residual Block 없이 그냥 6개의 컨볼루션 레이어를 쌓아둔 구조이다.
③ Residual Block을 적용한 CNN 모델
class Conv6Res(nn.Module):
def __init__(self, n_class=10):
super().__init__()
self.name = 'conv6res'
self.model = nn.Sequential(
nn.Conv2d(3, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
# 첫 번째 Residual Block
ResBlock(
nn.Sequential(
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU()
)
),
# 두 번째 Residual Block
ResBlock(
nn.Sequential(
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
)
),
nn.Flatten(),
nn.Linear(32*32*32, 256),
nn.ReLU(),
nn.Linear(256, n_class)
)
def forward(self, x):
return self.model(x)
3. Residual Block을 사용한 CNN이 동작하는 방식
- 일반적인 CNN은 순차적으로 정보를 전달하며 학습하지만,
Residual Block을 적용한 CNN은 이전 입력을 직접 더하면서 학습한다. - 만약 네트워크가 너무 깊어져도, Skip Connection 덕분에 입력 정보가 보존된다.
- 기울기 소실 문제를 해결하고, 학습이 더 빠르고 안정적으로 진행된다.
예제 흐름
- 첫 번째 컨볼루션 레이어: 기본적인 특징을 추출
- 첫 번째 Residual Block:
- Conv → BatchNorm → ReLU → Conv → BatchNorm → ReLU
- 마지막에서 입력을 더해줌 (x+F(x)x + F(x))
- 두 번째 Residual Block:
- Conv → BatchNorm → ReLU (3번 반복)
- 마지막에서 입력을 더해줌 (x+F(x)x + F(x))
- Fully Connected Layer로 최종 분류 수행
4. 일반적인 Residual Block 코드 (PyTorch)
import torch
import torch.nn as nn
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels):
super(ResidualBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(out_channels)
# 입력과 출력 채널이 다르면 1x1 컨볼루션을 사용하여 차원 맞추기
if in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1),
nn.BatchNorm2d(out_channels)
)
else:
self.shortcut = nn.Identity() # 입력을 그대로 유지
def forward(self, x):
residual = self.shortcut(x) # 입력을 그대로 저장
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out += residual # Skip Connection을 통해 입력과 합산
out = self.relu(out)
return out
코드 설명
- 입력 데이터 xx를 처리하는 두 개의 컨볼루션 레이어를 정의
- 첫 번째 컨볼루션: conv1 -> bn1 -> relu
- 두 번째 컨볼루션: conv2 -> bn2
- 입력과 출력의 채널 수가 다르면 1x1 컨볼루션을 통해 차원을 맞춤
- 입력 채널과 출력 채널이 다르면, self.shortcut을 사용해 1x1 컨볼루션을 적용하여 차원을 맞춤.
- 입력과 출력 채널이 같으면 nn.Identity()를 사용하여 원본 입력을 그대로 유지.
- Skip Connection 적용
- 입력 xx를 self.shortcut(x)를 통해 바로 다음 레이어로 전달.
- F(x) (컨볼루션 연산 결과)와 x를 더한 후 활성화 함수(ReLU)를 적용.
이 구조를 사용하면, 기울기 소실 문제를 방지하고 깊은 신경망에서도 안정적인 학습이 가능하다.
본 글은 아래의 글을 참고하여 작성하였습니다.
Residual Block 간단 예시
ReSiDuaL BLoCKS 직접 만들어서 써보기
velog.io
'AI' 카테고리의 다른 글
[Pytorch] 반지도 학습 구현해보기 - PyTorch 주요 함수 이해하기 (0) | 2025.03.20 |
---|---|
[인최기] Semi-supervised learning (준지도학습) (0) | 2025.03.12 |
Batch, Step, Epoch 이해하기 (1) | 2025.02.20 |
[논문] A Survey of Resource-efficient LLM and Multimodal Foundation Models (0) | 2025.02.13 |
[Object Detection] R-CNN, Fast R-CNN, Faster R-CNN (2) | 2024.12.18 |