반응형


Pytorch로 ResNet 논문 구현하기

 

2021.10.13 - [Machine Learning/Deep Learning 논문] - [간단 리뷰] ResNet : deep residual learning for image recognition

 

리뷰를 마쳤으니, 이제 간략히 구현을 해보자!

 

 

라이브러리 import


# 파이토치
import torch

# 파이토치 레이어 정의를 위한 torch.nn
import torch.nn as nn

# activation func 사용을 위한 nn.functional
import torch.nn.functional as F

 

논문 제시 Architecture


 

ImageNet 기준으로 저자가 사용한 Architecture는 위와 같다. 

인풋에 대한 최초 컨볼루션과 맥스 풀링, 그리고 그 이후에 크게 4개의 블럭(conv_2, conv3_x, conv4_x, conv5_x)로 구성되어있으며, 이 때 주목해야 할 점은, ResNet 50 레이어부터는 Block의 구조가 바뀐다는 것이다.

 

저자는, 이러한 형태의 Block을 인풋을 작은 차원으로 줄였다가, 다시 확장시킨다는 특성을 고려하여, BottleNeck Architecture라고 정의했다. 이제 필요한 정보를 얻었으니 구현하자!

 

 

Basic Residual Block (ResNet 18, ResNet34)


논문 리뷰에서 살펴봤듯이, ResNet 논문의 핵심은 블록의 인풋값인 x를 identity로 사용하여 더해줌으로써,

레이어가 아무리 깊어지더라도, 최소한 얕은 레이어보다는 정확도가 낮아지는 것을 방지하고, 보다 학습이 용이한 구조를 가지도록 하는 것이다.

 

해당 내용의 간략한 구현은 다음과 같다.

class BasicBlock(nn.Module):
	# mul은 추후 ResNet18, 34, 50, 101, 152등 구조 생성에 사용됨
    mul = 1
    def __init__(self, in_planes, out_planes, stride=1):
        super(BasicBlock, self).__init__()
        
        # stride를 통해 너비와 높이 조정
        self.conv1 = nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d(out_planes)
        
        # stride = 1, padding = 1이므로, 너비와 높이는 항시 유지됨
        self.conv2 = nn.Conv2d(out_planes, out_planes, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_planes)
        
        # x를 그대로 더해주기 위함
        self.shortcut = nn.Sequential()
        
        # 만약 size가 안맞아 합연산이 불가하다면, 연산 가능하도록 모양을 맞춰줌
        if stride != 1: # x와 
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_planes)
            )
    
    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = F.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out += self.shortcut(x) # 필요에 따라 layer를 Skip
        out = F.relu(out)
        return out

 

 

BottleNeck Residual Block (ResNet 50, ResNet101, ResNet152)


ResNet18과 ResNet34와 같은 얕은 구조의 경우에는 위에서 구현한 Basic Residual Block으로 충분하지만, 

논문의 저자가 제시한 구조에서도 확인할 수 있듯, 더 깊은 레이어 구조에서는 BottleNeck 구조를 사용한다.

구현 내용은 Basic Residual Block에서 컨볼루션 방식이 달라졌을 뿐 크게 다를것은 없다.

 

class BottleNeck(nn.Module):
	# 논문의 구조를 참고하여 mul 값은 4로 지정, 즉, 64 -> 256
    mul = 4
    def __init__(self, in_planes, out_planes, stride=1):
        super(BottleNeck, self).__init__()
        
        #첫 Convolution은 너비와 높이 downsampling
        self.conv1 = nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
        self.bn1 = nn.BatchNorm2d(out_planes)
        
        self.conv2 = nn.Conv2d(out_planes, out_planes, kernel_size=3, stride=1, padding=1, bias=False)
        self.bn2 = nn.BatchNorm2d(out_planes)
        
        self.conv3 = nn.Conv2d(out_planes, out_planes*self.mul, kernel_size=1, stride=1, bias=False)
        self.bn3 = nn.BatchNorm2d(out_planes*self.mul)
        
        self.shortcut = nn.Sequential()
        
        if stride != 1 or in_planes != out_planes*self.mul:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, out_planes*self.mul, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(out_planes*self.mul)
            )
        
    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = F.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = F.relu(out)
        out = self.conv3(out)
        out = self.bn3(out)
        out += self.shortcut(x)
        out = F.relu(out)
        return out

 

 

ResNet 구현


자, 이제 필요한 Block을 모두 정의했으니, 실제 ResNet 모델을 구현해보자.

사실 CIFAR-10을 위한 구조는 저자가 따로 제시하였으나, 본 게시글에서는 ImageNet에서 사용된 ResNet 구조를 그대로 사용하도록 한다. (사실 정확도 측면에서도 별로 큰 차이가 없다.)

class ResNet(nn.Module):
	# CIFAR-10을 학습시킬 것이므로, num_classes=10으로 설정
    def __init__(self, block, num_blocks, num_classes=10):
        super(ResNet, self).__init__()
        #RGB 3개채널에서 64개의 Kernel 사용 (논문 참고)
        self.in_planes = 64
        
        # Resnet 논문 구조의 conv1 파트 그대로 구현
        self.conv1 = nn.Conv2d(3, self.in_planes, kernel_size=7, stride=2, padding = 3)
        self.bn1 = nn.BatchNorm2d(self.in_planes)
        self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        
        self.layer1 = self.make_layer(block, 64, num_blocks[0], stride=1)
        self.layer2 = self.make_layer(block, 128, num_blocks[1], stride=2)
        self.layer3 = self.make_layer(block, 256, num_blocks[2], stride=2)
        self.layer4 = self.make_layer(block, 512, num_blocks[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1,1))
        
        # Basic Resiudal Block일 경우 그대로, BottleNeck일 경우 4를 곱한다.
        self.linear = nn.Linear(512 * block.mul, num_classes)
        
    # 다양한 Architecture 생성을 위해 make_layer로 Sequential 생성     
    def make_layer(self, block, out_planes, num_blocks, stride):
        # layer 앞부분에서만 크기를 절반으로 줄이므로, 아래와 같은 구조
        strides = [stride] + [1] * (num_blocks-1)
        layers = []
        for i in range(num_blocks):
            layers.append(block(self.in_planes, out_planes, strides[i]))
            self.in_planes = block.mul * out_planes
        return nn.Sequential(*layers)
    
    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = F.relu(out)
        out = self.maxpool1(out)
        out = self.layer1(out)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.avgpool(out)
        out = torch.flatten(out,1)
        out = self.linear(out)
        return out

 

ResNet Architecture 생성


맨 위에서 소개했던 논문에서 제시한 Architecture에 충실하게 이제 ResNet18, 34, 50, 101, 152를 생성하자

 

def ResNet18():
    return ResNet(BasicBlock, [2, 2, 2, 2])

def ResNet34():
    return ResNet(BasicBlock, [3, 4, 6, 3])

def ResNet50():
    return ResNet(BottleNeck, [3, 4, 6, 3])

def ResNet101():
    return ResNet(BottleNeck, [3, 4, 23, 3])

def ResNet152():
    return ResNet(BottleNeck, [3, 8, 36, 3])

 

이제 직접 만든 ResNet을 사용할 수 있다!

ResNet 구현 코드와 ResNet을 사용하여 CIFAR-10을 학습하는 코드는 Jupyter Notebook을 통해 Github에 올려두었으니

Colab을 활용하여 무료 GPU 서버를 통해 실습해 볼 수 있다!

 

Github : https://github.com/CryptoSalamander/pytorch_paper_implementation/tree/master/resnet

 

반응형
블로그 이미지

Hyunsoo Luke HA

석사를 마치고 현재는 Upstage에서 전문연구요원으로 활동중인 AI 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,