반응형


Pytorch로 ResNeXt 논문 구현하기

 

논문 리뷰 :

2021.10.18 - [Machine Learning/Deep Learning 논문] - [논문 리뷰] ResNeXt : Aggregated Residual Transformations for Deep Neural Networks

논문 링크 : https://arxiv.org/abs/1611.05431

 

Aggregated Residual Transformations for Deep Neural Networks

We present a simple, highly modularized network architecture for image classification. Our network is constructed by repeating a building block that aggregates a set of transformations with the same topology. Our simple design results in a homogeneous, mul

arxiv.org

 

이전에 했던 ResNet 논문 구현을 기반으로 하면 그렇게 어렵지는 않았다.

다만 Cardinality와 Depth에 따른 Layer 모양이 어떻게 되는지 점화식을 생각하는데 시간이 오래 걸렸다.

머리속으로만 생각하기 보단, 손으로 그리면서 하니 금방 코드를 작성할 수 있었다.

 

 

라이브러리 import


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

 

 

 

논문 제시 Architecture


Cardinality BottleNeck Block은 위 (a), (b), (c) 중 한가지로 구현하면 된다고 저자의 논문에 설명되어 있다.

Pytorch에서는 Grouped Conv2D를 사용하면 (c)가 가장 구현이 용이하다.

 

 

ResNeXt BottleNeck Block


기본적으로 ResNet의 구조를 그대로 계승받은 신경망이므로, 약간의 수정만 하면 기존 BottleNeck 구조를 그대로 사용할 수 있다.

 

class ResNeXtBottleNeck(nn.Module):
    # ResNeXt는 2를 곱해줌
    mul = 2
    def __init__(self, in_planes, group_width, cardinality, stride=1):
        super(ResNeXtBottleNeck, self).__init__()
        
        #첫 Convolution은 너비와 높이 downsampling
        self.conv1 = nn.Conv2d(in_planes, group_width, kernel_size=1, stride=stride, bias=False)
        self.bn1 = nn.BatchNorm2d(group_width)
        
        # ResNeXt 논문의 C 아키텍쳐 구현
        self.conv2 = nn.Conv2d(group_width, group_width, kernel_size=3, stride=1, padding=1, groups = cardinality, bias=False)
        self.bn2 = nn.BatchNorm2d(group_width)
        
        self.conv3 = nn.Conv2d(group_width, group_width*self.mul, kernel_size=1, stride=1, bias=False)
        self.bn3 = nn.BatchNorm2d(group_width*self.mul)
        
        self.shortcut = nn.Sequential()
        
        # 합연산이 가능하도록 Identifier를 맞춰줌
        if stride != 1 or in_planes != group_width*self.mul:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_planes, group_width*self.mul, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(group_width*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

 

 

ResNeXt 모델 정의


class ResNeXt(nn.Module):
    def __init__(self, block, num_blocks, cardinality = 32, width = 4, num_classes=10):
        super(ResNeXt, self).__init__()
        #RGB 3개채널에서 64개의 Kernel 사용
        self.in_planes = 64
        self.group_conv_width = cardinality * width # 128
        
        # ResNeXt 논문 구조 그대로 구현
        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, cardinality, num_blocks[0], stride=1)
        self.layer2 = self.make_layer(block, cardinality, num_blocks[1], stride=2)
        self.layer3 = self.make_layer(block, cardinality, num_blocks[2], stride=2)
        self.layer4 = self.make_layer(block, cardinality, num_blocks[3], stride=2)
        self.avgpool = nn.AdaptiveAvgPool2d((1,1))
        self.linear = nn.Linear(self.group_conv_width, num_classes)
        
    def make_layer(self, block, cardinality, num_blocks, stride):
        # layer 앞부분에서만 크기를 절반으로 줄이므로, 아래와 같은 구조
        strides = [stride] + [1] * (num_blocks-1)
        layers = []
        for i in range(num_blocks):
            layers.append(block(self.in_planes, self.group_conv_width, cardinality, strides[i]))
            self.in_planes = block.mul * self.group_conv_width
        # 논문의 구조를 보면 2배씩 채널사이즈가 증가
        self.group_conv_width *= block.mul 
        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과 거의 유사한 방식으로 ResNeXt모델을 정의할 수 있다.

차이점은 채널크기가 2배씩 늘어난다는 것과, Cardinality와 Width라는 하이퍼파라미터가 추가되었다는 점이다.

 

논문에서는 기본적으로 32x4d를 기준으로 설명하지만, 인자로 받아 다양한 Architecture를 사용할 수 있도록 하였다.

 

ResNeXt Architecture 생성


def ResNeXt50():
    return ResNeXt(ResNeXtBottleNeck, [3, 4, 6, 3])

#def ResNeXt50_8x14d():
#    return ResNeXt(ResNeXtBottleNeck, [3, 4, 6, 3], cardinality = 8, width = 14)
    
def ResNeXt101():
    return ResNeXt(ResNeXtBottleNeck, [3, 4, 23, 3])

def ResNeXt152():
    return ResNeXt(ResNeXtBottleNeck, [3, 8, 36, 3])

 

위와 같이 다양한 Architecture를 선언할 수 있으며, Cardinality와 Width의 변경을 통해 다양한 모델을 사용할 수 있다.

이제 직접 구현한 ResNeXt를 사용할 수 있다.

 

해당 구현 코드와 CIFAR-10 학습 코드는 Jupyter Notebook을 통해 Github에 올려두었으니,

Colab 무료 GPU 서버를 통해 실습해볼 수 있다.

 

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

반응형
블로그 이미지

Hyunsoo Luke HA

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

,