2023-11-30 61th Class
Convolutional Neural Network - ResNet
#️⃣ ResNet 소개
개요
- 2019년 ISVRC 이미지 데이터 분류 대회에서 우승한 모델
- shortcut skip connection을 도입한 모델
- Microsoft 팀에서 발표
ResNet Architecture
#️⃣ 등장 배경
f*는 target model을 의미
ResNet 이전의 방식 (ex: VGG)
- ResNet 이전의 딥러닝 모델링 방식은 학습을 시켰을 때 모델의 성능이 만족스럽지 않은 경우
- 모델의 층을 늘려 complexity(복잡도)를 더하면서 모델의 표현 범위를 늘렸음
- ex)
은 VGG-11로 만들 수 있는 모델들의 집합이고, > 으로 확장하며 (VGG-13 > VGG-19) 모델의 표현력을 높힘 - 하지만 아래의 좌측 그림과 같은 문제가 발생할 수 있음
왼쪽 이미지
- ResNet 개발 팀에서는 F1, F2, F3으로 모델이 확장되면서 분류할 수 있는 범위가 이동하는 현상을 문제로 제기함
- 최종 확장 모델인 F6의 범위에는 F1의 일부가 포함되지 않는 문제가 발생
- 이러한 현상이 발생하는 모델의 확장은 좋은 결과가 아님
오른쪽 이미지
- 기존 모델을 포함하면서 레이어를 추가해서 범위를 확장할 수 있는 방법을 찾게 됨
- 따라서 ResNet에서는 모델의 복잡도(complexity) 증가 이외의 접근 방법을 고안하게 됨
#️⃣ 항등함수(identity function)와 Shortcut Skip Connection
Identity Function :
추가되는 레이어가 초기 상태에서 위의 식과 같이 항등함수(identity function)라면,
초기 상태에서는 기존 모델의 출력에 변화없음
추가된 레이어가 항등함수에서부터 학습이 시작되면,
기존 모델을 포함한 상태에서 표현의 범위가 더 넓어지는 효과를 가짐
Shortcut Skip Connection
- 합성곱층과 렐루 한 쌍을 f(x)라고 정의
- skip connection은 f(x) = x가 아니라 f(x) = 0으로 만드는 것
- 통계적으로 f(x)는 0에 가까울 확률이 높음
residual block
- path1 : Conv > ReLU > Conv
- path2 : x
- result: path1 + path2
기존과 같이 Conv2D + ReLU를 통과하는 path1의 출력과
그대로 입력이 skip되는 skip2를 더한 값이
block의 출력값이 되는 구조
residual block code
import torch.nn as nn
import torch
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels):
super(ResidualBlock, self).__init__()
self.conv_path = nn.Sequential(
nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
kernel_size=3, padding=1, stride=1),
nn.ReLU(),
nn.Conv2d(in_channels=out_channels, out_channels=out_channels,
kernel_size=3, padding=1, stride=1))
if in_channels != out_channels:
self.skip_path = nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
kernel_size=1)
else:
self.skip_path = nn.Identity()
self.out_act = nn.ReLU()
def forward(self, x):
out = self.conv_path(x)
out += self.skip_path(x)
out = self.out_act(out)
return out
def run_residual_block():
BATCH_SIZE = 32
H, W = 100, 100
channels = 3
input_tensor = torch.randn(size=(BATCH_SIZE, channels, H, W))
print("in:", input_tensor.shape)
block = ResidualBlock(in_channels=channels, out_channels=192)
x = block(input_tensor)
print("out:", x.shape)
if __name__ == '__main__':
run_residual_block()
'''
in: torch.Size([32, 3, 100, 100])
out: torch.Size([32, 192, 100, 100])
'''
#️⃣ Residual Block 파라미터 수 계산하기
block = ResidualBlock(in_channels=64, out_channels=64)
summary(block, input_size=(64, 56, 56), device='cpu')
위의 코드에서 파라미터 수 계산
Formula for Param # ={(kernel x kernel x in_channel) + 1} x out_channel
Layer(type) | Output Shape | Formula for Param # | Param # |
---|---|---|---|
Conv2d-1 | (-1, 64, 56, 56) | {(3x3x64)+1}x64 | 36,928 |
ReLU-2 | (-1, 64, 56, 56) | ||
Conv2d-3 (path1) | (-1, 64, 56, 56) | {(3x3x64)+1}x64 | 36,928 |
Identity-4 (path2) | (-1, 64, 56, 56) | ||
ReLU-5 | (-1, 64, 56, 56) |
#️⃣ Residual Block Downsizing
Resnet에서는 점선으로 표기된 층이 있는데,
3x3 conv, 2xprevious_channel/2 층을 통해
pooling 대신 stride를 통해 이미지 사이즈를 반으로 줄이고
channel을 2배로 늘리게 됨
residual block downsizing code
class ResidualBlockDown(nn.Module):
def __init__(self, in_channels, out_channels):
super(ResidualBlockDown, self).__init__()
self.conv_path = nn.Sequential(
nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
kernel_size=3, padding=1, stride=2),
nn.ReLU(),
nn.Conv2d(in_channels=out_channels, out_channels=out_channels,
kernel_size=3, padding=1, stride=1))
self.skip_path = nn.Conv2d(in_channels=in_channels, out_channels=out_channels,
kernel_size=1, padding=0, stride=2)
self.out_act = nn.ReLU()
def forward(self, x):
out = self.conv_path(x)
out += self.skip_path(x)
out = self.out_act(out)
return out
def run_rb_down():
BATCH_SIZE = 32
H, W = 100, 100
channels = 64
input_tensor = torch.randn(size=(BATCH_SIZE, channels, H, W))
print("in:", input_tensor.shape)
block = ResidualBlockDown(in_channels=channels, out_channels=128)
output_tensor = block(input_tensor)
print("out:", output_tensor.shape)
if __name__ == '__main__':
# run_residual_block()
run_rb_down()
in: torch.Size([32, 64, 100, 100])
out: torch.Size([32, 128, 50, 50])
#️⃣ ResNet34 구현
아래의 아키텍처와 표를 참고하여 ResNet34를 구현
ResNet34 code
class ResNet34(nn.Module):
def __init__(self, in_channels):
super(ResNet34, self).__init__()
# 1
self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=64,
kernel_size=7, stride=2, padding=3)
self.conv1_act = nn.ReLU()
# 2
self.conv2 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
ResidualBlock(in_channels=64, out_channels=64),
ResidualBlock(in_channels=64, out_channels=64),
ResidualBlock(in_channels=64, out_channels=64))
# 3
self.conv3 = nn.Sequential(
ResidualBlockDown(in_channels=64, out_channels=128),
ResidualBlock(in_channels=128, out_channels=128),
ResidualBlock(in_channels=128, out_channels=128),
ResidualBlock(in_channels=128, out_channels=128))
# 4
self.conv4 = nn.Sequential(
ResidualBlockDown(in_channels=128, out_channels=256),
ResidualBlock(in_channels=256, out_channels=256),
ResidualBlock(in_channels=256, out_channels=256),
ResidualBlock(in_channels=256, out_channels=256),
ResidualBlock(in_channels=256, out_channels=256),
ResidualBlock(in_channels=256, out_channels=256))
# 5
self.conv5 = nn.Sequential(
ResidualBlockDown(in_channels=256, out_channels=512),
ResidualBlock(in_channels=512, out_channels=512),
ResidualBlock(in_channels=512, out_channels=512))
# 6
self.avgpool = nn.AvgPool2d(kernel_size=7, stride=7)
# self.avgpool = nn.AdaptiveAvgPool2d(output_size=1)
self.fc = nn.Linear(in_features=512, out_features=1024)
def forward(self, x):
x = self.conv1(x)
x = self.conv1_act(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
def run_resnet():
input_tensor = torch.randn(size=(32, 3, 224, 224))
model = ResNet34(in_channels=3)
pred = model(input_tensor)
print(pred.shape)
torch.Size([32, 1024])
- Input Image -> ISVRC 이미지 사이즈인 (3, 224, 224)로 시작
- 마지막 pooling층에서는 average pooling 이후 이미지 사이즈가 1x1로 출력되어야 함
- self.avgpool = nn.AvgPool2d(kernel_size=7, stride=1)
- self.avgpool = nn.AdaptiveAvgPool2d(output_size=1)
- adaptive average pooling은 이미지의 output size를 정하면 알아서 계산해줌
- 실제 계산 값으로 입력하려면 마지막 6층의 pooling 레이어에 도달한 이미지 사이즈가 7x7이기 때문에
- kernel size를 7, pooling을 7로 설정하면 됨
'Education > 새싹 TIL' 카테고리의 다른 글
새싹 AI데이터엔지니어 핀테커스 14주차 (월) - PJT 1 Research (0) | 2023.12.05 |
---|---|
새싹 AI데이터엔지니어 핀테커스 13주차 (금) - ResNet Full & Early Stopping & Dropout & Batch Normalization (0) | 2023.12.01 |
새싹 AI데이터엔지니어 핀테커스 13주차 (수) - GoogLeNet (0) | 2023.11.29 |
새싹 AI데이터엔지니어 핀테커스 13주차 (화) - VGGNet-11,13,19 & CIFAR10 (0) | 2023.11.28 |
새싹 AI데이터엔지니어 핀테커스 13주차 (월) - LeNet5 & VGGNet (1) | 2023.11.27 |