ConvNet shape 계산

zzin·2025년 3월 10일

SKALA

목록 보기
10/12

nn.Conv2d 는 Pytorch에서 제공하는 2D Convolution 레이어 클래스이다. 이미지나 2D 데이터 특징 추출에 주로 사용된다.

1. nn.Conv2d 클래스

nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, **groups**=1, bias=True)

  • in_channels : (int) 입력 채널의 개수
  • out_channels : (int) 출력 채널의 개수 = 컨볼루션 필터 개수
    • 값이 클수록 복잡한 특징 학습 가능하지만, 모델의 파라미터 수 증가
  • kernel_size : (int) or (tuple) 컨볼루션 필터의 크기
  • stride : (int) or (tuple) 필터의 이동 간격 (default = 1)
    • 값 커질수록 출력 특징 맵 크기 작아짐
  • padding : (int) or (tuple) 입력 데이터 가장자리에 추가되는 패딩 크기 (default = 0)
    • 패딩 사용시 입력 데이터 가장자리 정보 유지 가능
    • 가장자리 여백이 많고 주요 정보가 이미지 가운데 있을 경우 패딩 쓸 필요 X (반대로, 가장자리에 이미지 특징 정보가 많다면 패딩 사용)
  • dilation : (int) or (tuple) 딜레이션(dilation) 레이트는 필터의 간격을 더 크게 두어 더 넓은 영역의 정보를 가져오는 데 사용 (default = 1)
    • 값이 커질수록 필터의 영역이 더 넓어짐
  • groups : (int) or (optional) 입력 및 출력 채널을 묶는(grouping) 개수(default = 1)
    • 값이 클수록 채널 간의 관련성 줄이는 효과
  • bias : (bool) 편향(bias)을 사용할지 여부 결정하는 플래그 (default = True)




2. Conv2d Model shape 계산

  • Conv2d 계산법 간단한 버전

  • Conv2d 계산법 dilation 추가 버전

  • 풀링 계산법 출력 크기 = 입력 크기 / 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

[ 모델 흐름과 크기 계산 ]

LayerInput sizeOutput sizeDescription
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 라벨)

  1. Conv1 + ReLU
  • 입력 크기: (28, 28, 1) → 출력 크기: (28, 28, 32)
  • [ 계산 ]
    - 커널 크기: 3, 패딩: 1, 스트라이드: 1
    - 출력 크기:
    출력 크기= (128−3+2×1) / 1+1 =28
    따라서 출력 크기는 (28, 28, 32) 이다.

  1. MaxPool
  • 입력 크기: (28, 28, 32) → 출력 크기: (14, 14, 32)
  • [ 계산 ]
    - 풀링 크기: 2x2, 스트라이드: 2
    - 출력 크기:
    출력 크기= 28 / 2 =14
    따라서 출력 크기는 (14, 14, 32)이다

  1. Residual Block 1
  • 입력 크기: (14, 14, 32) → 출력 크기: (14, 14, 64)
  • [ 계산 ]
    - 첫 번째 Residual Block은 두 개의 3x3 컨볼루션과 1x1 커널의 단축 경로를 가진다.
    - 컨볼루션 후, 크기는 변화가 없으므로 출력 크기는 (14, 14, 64) 이다.

  1. MaxPool
  • 입력 크기: (14, 14, 64) → 출력 크기: (7, 7, 64)
  • [ 계산 ]
    - 풀링 크기: 2x2, 스트라이드: 2
    - 출력 크기:
    출력 크기= 14 / 2 =7
    따라서 출력 크기는 (7, 7, 64)이다.

  1. Residual Block 2
  • 입력 크기: (7, 7, 64) → 출력 크기: (7, 7, 128
  • [ 계산 ]
    • 두 번째 Residual Block은 64 채널에서 128 채널로 변환한다. 출력 크기는 (7, 7, 128)이다.

  1. MaxPool
  • 입력 크기: (7, 7, 128) → 출력 크기: (3, 3, 128)
  • [ 계산 ]
    - 풀링 크기: 2x2, 스트라이드: 2
    - 출력 크기:
    출력 크기= 7 / 2 =3
    따라서 출력 크기는 (3, 3, 128)이다.

  1. Flatten
  • 입력 크기: (3, 3, 128) → 출력 크기: (1152,)
  • [ 계산 ]
    - Flatten은 3D 텐서를 1D 벡터로 변환한다:
    3×3×128=1152
    따라서 출력 크기는 (1152,)이다.

  1. fc1 + ReLU
  • 입력 크기: (1152,) → 출력 크기: (512,)
  • [ 계산 ]
    - 이 부분은 완전 연결층으로, 1152 차원의 벡터를 512 차원의 벡터로 변환한다. ReLU 활성화 함수가 적용되어 비선형성이 추가된다.

  1. fc2
  • 입력 크기: (512,) → 출력 크기: (10,)
  • [ 계산 ]
    • 마지막 출력층은 512개의 뉴런을 10개의 클래스(0~9까지)로 매핑하는 완전 연결층이다. 각 클래스에 대해 최종 예측 값을 출력한다.




참고1 ) Residual Block


[ 깊어지는 레이어로 인한 문제와 해결법 ]

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): ReLU 함수로 비선형성을 추가한다.
  • 두 번째 컨볼루션 (conv2): x는 두 번째 3x3 컨볼루션을 통과하여 다시 out_channels 차원으로 출력된다.
  • Residual 연결: xshortcut을 더한 후 ReLU를 적용한다. 이 단축 경로를 통해 입력값이 네트워크를 통과하면서 정보가 손실되지 않도록 보장한다.
  • Residual Mapping Residual Block에서 F(x)는 입력값 x가 컨볼루션 연산을 거친 후(즉, F(x))의 출력을 의미하며, 이 값은 입력값 x와 더해져 esidual Mapping이 된다. 이렇게 더해진 값이 Residual Block을 통해 출력된다. Residual Block은 이 방식으로 기존의 정보를 유지**하면서 새로운 특징을 추출하게 해주어 더 깊은 네트워크에서도 성능 저하를 막을 수 있다.




참고 2 ) ConvNet Calculator

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 (사진)

0개의 댓글