SJ_Koding

Bottleneck 구조(resnet)의 설명 및 Pytorch 예시 본문

Deep Learning

Bottleneck 구조(resnet)의 설명 및 Pytorch 예시

성지코딩 2024. 4. 3. 10:45

부제: - ConvNeXt 이해하기 2편 -

Bottleneck이란 용어 자체는 병목현상을 의미한다. 정말 많은 분야에서 쓰이는 말이다.

띠띠~ 교통분야에서 쓰이는 병목현상

시스템 분야에서의 병목현상은 다음과 같이 정의된다.
- 시스템 내에서 전체적인 처리 속도를 떨어뜨리게 되는 특정한 부분을 가리키는 용어
- 시스템의 CPU나 메모리, 디스크 등의 자원 중 하나가 다른 자원들에 비해 처리 속도가 느려서, 전체적인 성능을 제한하는 경우를 말함

그렇다면 Deep leaning network에서 말하는 병목현상 즉, bottleneck layer는 무엇을 의미할까?

Bottleneck은 구조는 2015년 ResNet에 의해 널리 알려지고 사용되었으며, 대표적으로 아래의 사진으로 나타낸다

Deep Residual Learning for Image Recognition(renset 논문)의 Figure 5.

1 x 1 convolution을 사용하여 채널을 줄인 후 3x3으로 피처를 추출한 뒤, 다시 1x1 convolution으로 원래차원으로 늘려 skip connection을 진행하는 구조이다. 1x1 convolution의 설명은 아래 포스팅을 참고하자.

 

1x1 convolution의 설명 및 Pytorch 예시

부제: - ConvNeXt 이해하기 1편 - 1x1 convolution 1x1 convolution은 필터 사이즈가 1x1라는 것을 의미한다. 즉, feature map의 feature 하나(Image Input기준으로 픽셀 하나) 에 대해 convolution 연산을 진행한다. 1x1 convol

sjkoding.tistory.com

 

연산량

IC : Input Channel
OC: Output Channel
H: height of feature map
W: width of feature map
K: kernel size

Bottelneck을 사용하지 않고 일반적인 convolution 블럭을 사용한다고 가정하고, IC와 OC가 256, H와 W가 224, K가 3 이라고 가정해보자.
즉, 모델 구조는 아래의 코드와 같다.

import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicConvBlock(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(BasicConvBlock, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)
        self.bn = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

# 입력 채널 수와 출력 채널 수가 모두 256인 일반 컨볼루션 블록의 예시
basic_block = BasicConvBlock(256, 256)


이 경우 연산량은

H*W*K*K*IC*OC == 224*224*3*3*256*256  --> 29,595,009,024 ( 약 29.5B ) 가 된다.

반면 Bottleneck구조를 사용하여 IC를 1x1 convolution으로 256 --> 64채널 로 줄인 후 3x3 Convolution을 적용한 후 다시 1x1 convolution으로 64 --> 256채널로 확장하는 경우를 생각해보자. 즉 모델 구조는 아래의 코드와 같다.

class Bottleneck(nn.Module):
    def __init__(self, channels, internal_channels):
        super(BottleneckNoExpansion, self).__init__()
        self.conv1 = nn.Conv2d(channels, internal_channels, kernel_size=1)
        self.bn1 = nn.BatchNorm2d(internal_channels)
        
        self.conv2 = nn.Conv2d(internal_channels, internal_channels, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(internal_channels)
        
        self.conv3 = nn.Conv2d(internal_channels, channels, kernel_size=1)
        self.bn3 = nn.BatchNorm2d(channels)
        
        self.relu = nn.ReLU()

    def forward(self, x):
        identity = x

        out = self.relu(self.bn1(self.conv1(x)))
        out = self.relu(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        
        out += identity
        out = self.relu(out)
        return out

bottleneck_model = Bottleneck(256, 64)

이 경우 연산량은 3단계로 나뉘어서 더해진다.

(1) IC * OC * H * W * K * K == 256 * 64 * 224 * 224 * 1 * 1 ( 1x1convolution으로 256 --> 64채널로 줄이는 연산량)

(2) IC * OC * H * W * K * K  == 64 * 64 * 224 * 224 * 3 * 3  (줄여진 채널(64)에서 3x3 convolution연산 수행)

(3) IC * OC * H * W * K * K == 64* 256 * 224 * 224 * 1 * 1 ( 1x1convolution으로 64--> 256채널로 늘리는 연산량)

(1) + (2) + (3) ==  822,083,584 + 1,849,688,064 + 822,083,584 = 3,493,855,232 (약 3.5B)

 

따라서 위 상황에서  Bottleneck을 사용할 시 연산량은 29.5B에서 3.5B로 대폭 작아진다. 또한 layer마다 activation함수를 추가했기 때문에 비선형성이 더욱 가중된다.