nn.Conv2d 는 Pytorch에서 제공하는 2D Convolution 레이어 클래스이다. 이미지나 2D 데이터 특징 추출에 주로 사용된다.
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, **groups**=1, bias=True)


출력 크기 = 입력 크기 / kernel_size위에 계산식을 활용해 ResNet에서 유래된 Residual Block을 사용한 모델 입출력 shape을 계산해보자
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.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
self.shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1)
self.relu = nn.ReLU()
def forward(self, x):
shortcut = self.shortcut(x) # Shortcut connection -> 입력값의 채널 수를 out_channels로 맞춰주기
x = self.conv1(x) # 특징 추출 -> 크기, 채널 수 동일
x = self.relu(x)
x = self.conv2(x) # 특징 추출 -> 크기, 채널 수 동일
x = x + shortcut # Add the shortcut to the output -> 출력값에 입력값 정보 전달하여 학습 원활하게 만들기
x = self.relu(x)
return x
class CNNWithResidual(nn.Module):
def __init__(self):
super(CNNWithResidual, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.resblock1 = ResidualBlock(32, 64)
self.resblock2 = ResidualBlock(64, 128)
self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
self.fc1 = nn.Linear(128 * 7 * 7, 512)
self.fc2 = nn.Linear(512, 10)
def forward(self, x):
x = self.conv1(x)
x = self.pool(torch.relu(x))
x = self.resblock1(x)
x = self.pool(x)
x = self.resblock2(x)
x = self.pool(x)
x = x.view(-1, 128 * 7 * 7) # Flatten the tensor
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
[ 모델 흐름과 크기 계산 ]
| Layer | Input size | Output size | Description |
|---|---|---|---|
| Conv1 + ReLU | (28, 28, 1) | (28, 28, 32) | 특징 추출 |
| MaxPool | (28, 28, 32) | (14, 14, 32) | 차원 축소 |
| Residual Block 1 | (14, 14, 32) | (14, 14, 64) | Residual Block (단축 경로) |
| MaxPool | (14, 14, 64) | (7, 7, 64) | 차원 축소 |
| Residual Block 2 | (7, 7, 64) | (7, 7, 128) | Residual Block (단축 경로) |
| MaxPool | (7, 7, 128) | (3, 3, 128) | 차원 축소 |
| Flatten | (3, 3, 128) | (1152, ) | 펼치기 (2D -> 1D) |
| fc1 + ReLU | (1152, ) | (512, ) | 특징 결합 |
| fc2 | (512, ) | (10, ) | 라벨 분류 (0~9 라벨) |
(28, 28, 1) → 출력 크기: (28, 28, 32)(28, 28, 32) 이다.(28, 28, 32) → 출력 크기: (14, 14, 32)2x2, 스트라이드: 2(14, 14, 32)이다(14, 14, 32) → 출력 크기: (14, 14, 64)3x3 컨볼루션과 1x1 커널의 단축 경로를 가진다.(14, 14, 64) 이다.(14, 14, 64) → 출력 크기: (7, 7, 64)2x2, 스트라이드: 2(7, 7, 64)이다.(7, 7, 64) → 출력 크기: (7, 7, 128(7, 7, 128)이다.(7, 7, 128) → 출력 크기: (3, 3, 128)2x2, 스트라이드: 2(3, 3, 128)이다.(3, 3, 128) → 출력 크기: (1152,)Flatten은 3D 텐서를 1D 벡터로 변환한다:(1152,)이다.(1152,) → 출력 크기: (512,)(512,) → 출력 크기: (10,)[ 깊어지는 레이어로 인한 문제와 해결법 ]
Plain Network (단순히 Layer를 깊게 쌓음) 에서 발생하는
Vanishing Gradient (기울기 소실) , Overfitting (과적합) 등의 문제를 해결하기 위해
ReLU, Batch Nomalization 등의 많은 기법이 있다.
Residual Block도 그 기법 중 하나이다.
[ Residual Block ]
Residual Block은 ResNet(Residual Networks)에서 유래한 개념으로, 네트워크의 깊이가 깊어짐에 따라 발생할 수 있는 vanishing gradient 문제를 해결하기 위한 방법이다.
Residual Block은 기본적으로 두 개의 컨볼루션 레이어로 이루어져 있지만, 중요한 점은 shortcut(skip) connection(단축 경로)이라는 개념을 통해 입력값을 출력값에 더한다는 것이다.
이렇게 함으로써 출력값에 원본 입력값의 정보를 그대로 전달하여 학습을 더 원활하게 만든다.
[ Residual Block 설명 ]
x: Residual Block에 입력되는 텐서다.shortcut: shortcut = self.shortcut(x)는 단축 경로(skip connection)로, 입력값 x를 그대로 지나가게 하여 입력값을 출력에 더하는 역할을 한다. 이를 통해 채널 수가 맞지 않으면 1x1 컨볼루션을 사용하여 맞춰준다.conv1): x는 첫 번째 3x3 컨볼루션을 거쳐 out_channels 차원의 출력으로 변환된다.relu): ReLU 함수로 비선형성을 추가한다.conv2): x는 두 번째 3x3 컨볼루션을 통과하여 다시 out_channels 차원으로 출력된다.x와 shortcut을 더한 후 ReLU를 적용한다. 이 단축 경로를 통해 입력값이 네트워크를 통과하면서 정보가 손실되지 않도록 보장한다.x가 컨볼루션 연산을 거친 후(즉, F(x))의 출력을 의미하며, 이 값은 입력값 x와 더해져 esidual Mapping이 된다. 이렇게 더해진 값이 Residual Block을 통해 출력된다. Residual Block은 이 방식으로 기존의 정보를 유지**하면서 새로운 특징을 추출하게 해주어 더 깊은 네트워크에서도 성능 저하를 막을 수 있다.https://madebyollin.github.io/convnet-calculator/
위에 링크를 통해 쉽게 계산 가능하다~
[ 참고 ]
https://coding-yoon.tistory.com/141
https://pytorch.org/docs/master/generated/torch.nn.Conv2d.html#torch.nn.Conv2d (사진)