ResNet모델에 대한 설명은 다음 페이지 참고

ResNet 꼼꼼한 리뷰

import torch
import torch.nn as nn
import sys

ResNet의 기본 블럭을 정의한 class Residual Block(nn.Module)

Untitled

class ResidualBlock(nn.Module):
    '''
    F(x)+x
    '''
    def __init__(self, in_channels, out_channels, stride=1):
        super(ResidualBlock, self).__init__()
        # when the number of out channels is increased : in bottleneck architecture, use zeropadding, in basic block, use projection shortcut(F(x)+W_s * x)
        ResidualBlock.expansion=1 
        
        self.conv_layers = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels*ResidualBlock.expansion, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(out_channels*ResidualBlock.expansion)
        )
        # the case that the number of input channels is not equal with the number of output channels
        # in other cases, the result of self.shortcut is x
        if stride != 1 or in_channels != out_channels * ResidualBlock.expansion:
            self.shortcut=nn.Sequential(
                nn.Conv2d(in_channels, out_channels*ResidualBlock.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels*ResidualBlock.expansion)
            )
        else:
            self.shortcut = nn.Sequential()
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.conv_layers(x) + self.shortcut(x)
        x = self.relu(x)
        return x

nn.Module을 상속받은 형태로 구현했습니다.

일단 ResNet의 기본 블록은

Untitled

표에서 보이는 것처럼 레이어(이 표에서 정의한 conv1, conv2_x, conv3_x, ...) 내에서 채널 확장이 없으므로 Residual.expansion = 1 이라는 클래스변수를 선언해줍니다( 이는 마지막에 ResNet 클래스에서 주요하게 활용됩니다 )

그리고 표에 설명된 바와 같이 conv layer들을 nn.Sequential()로 선언해줍니다

self.conv_layers = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(),
            nn.Conv2d(out_channels, out_channels*ResidualBlock.expansion, kernel_size=3, stride=1, padding=1, bias=False),
            nn.BatchNorm2d(out_channels*ResidualBlock.expansion)
        )

논문과 같이 conv layer뒤에 BatchNormalization 을 적용해주고, ReLU()를 적용해줬습니다.

ResNet논문에 따르면, 입력차원 < 출력차원일 경우 Shortcut이 stride 2, kernel size 1로 projection shortcut이 적용된다고 하는데, (추가파라미터 필요) 이를 코드로 나타낸 것이

if stride != 1 or in_channels != out_channels * BottleNeck.expansion:
            self.shortcut=nn.Sequential(
                nn.Conv2d(in_channels, out_channels*BottleNeck.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_channels*BottleNeck.expansion)
            )

이것입니다.

입력차원<출력차원인 경우의 조건을 if stride != 1 or in_channels != out_channels * BottleNeck.expansion: 이렇게 지정했습니다. stride가 2이거나, input 채널수와 output 채널수가 다른경우로, 사실 이는 이 클래스에서는 필요하지 않습니다(논문과 똑같이 구현할 경우) 대신 BottleNeck 구현 클래스에서 표에 나온 것처럼 마지막 conv레이어의 출력 차원이 늘어나므로, 그 때 쓰이는 부분입니다. 그러나 기본 블럭 구현에도 넣어주었습니다.

입력차원과 출력차원이 같다면 shortcut은 그냥 x와 같게됩니다. else: self.shortcut = nn.Sequential()