반응형


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 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


ResNeXt: Aggregated Residual Transformations for Deep Neural Networks

 

Kaiming He의 또 다른 논문, ResNeXt를 리뷰해보자.

 

 

연구의 배경


  • 시각 데이터를 다루는 연구들은 이제 단순히 Feature를 잘 처리하는 Feature Engineering 뿐만 아니라, 효율적으로 학습이 가능하도록 Network 설계를 잘 만드는 Network Engineering으로 전환됨
  • 하지만 Architecture를 설계하는 것은 매우 어려운 일인데, 그러한 원인 중 한 가지는 하이퍼 파라미터의 수가 점점 늘어남에 따라 연구자들이 여러가지 Task에 대해서 적절한 하이퍼 파라미터를 설정하는게 어렵기 때문이다.
  • ResNeXt는 ResNet을 계승받아 항상 일정한 형태를 가지는 Convolution층 여러개를 쌓아서 만들어지며(Residual Block), 이로 인해 레이어별로 Kernel Size를 어떻게 할지와 같은 고민으로부터 연구자들을 해방시킬 수 있다.
  • 또한 Inception 모델들이 가지는 주요 특성인 Split-Transform-Merge의 특성을 적용하여 높은 정확도를 얻었다.

 

 

Split - Transform - Merge


  • ResNeXt의 핵심은 ResNet 구조의 Architecture에 Inception에서 사용하던 Cardinality 개념을 도입하여 Convolution 연산을 쪼개서 진행하고, 서로 다른 Weight를 구한뒤 합쳐주는 Split-Transform-Merge를 추가한 것이다.
  • 이 때, 쪼개진 CNN이 몇개의 path를 가지는지를 결정하는 하이퍼파라미터가 바로 Cardinality이며, 각각의 path에서 가지는 채널을 depth라고 정의한다. 
  • 따라서 위 그림은, Conv2 Stage 기준 32개의 path와 4의 사이즈를 가지므로, Cardinality = 32, detph = 4의 
    ResNeXt-50 (32x4d)로 정의할 수 있다.

  • ResNet과의 구조 차이는 위 그림과 같다.

 

실험 결과


  • ResNeXt가 Inception의 Split - Transform - Merge 개념을 도입한 것 만으로도 ResNet을 상회하는 성능을 가지게 되었다.
  • 저자는 실험을 통해, 모델의 깊이를 늘리거나, 채널을 늘리는 것보다 단순히 Cardinality를 올림으로써 가장 적은 계산량증가로 최대의 성능 향상 효용을 얻을 수 있다고 설명했다.

  • 실제로 Cardinality를 64로 설정한 ResNeXt-101이, ResNet200과 ResNet-101에서 채널을 100으로 늘린 네트워크보다 더 좋은 성능을 보이는 것을 확인할 수 있다.

 

반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


 

Pytorch로 VGG 논문 구현하기

 

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

 

Very Deep Convolutional Networks for Large-Scale Image Recognition

In this work we investigate the effect of the convolutional network depth on its accuracy in the large-scale image recognition setting. Our main contribution is a thorough evaluation of networks of increasing depth using an architecture with very small (3x

arxiv.org

 

해당 논문은 리뷰하기엔 내용이 조금 짧아서 바로 구현을 시작하기로 했다.

어찌보면, 논문 구현의 Hello World! 와도 같은 느낌의 VGG이다.

 

 

라이브러리 import


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

 

논문 제시 Architecture


 

VGG의 Architecture는 위와 같다. 이중에서도 특히 D와 E가 흔히 알려진 VGG-16, VGG-19 모델이다.

VGG는 아직까지도 간단한 구조로 Feature Map등을 추출하는 용도의 backbone으로 사용되고 있다.

 

각각의 Conv 이후에는 ReLU가 존재하지만, 표에서는 빠져있다고 논문에 명시되어 있으므로 이를 참고하여 구현해보자.

 

Make Layer 


VGG는 매우 간단한 구조로, 특정 Block 형태가 반복되기보다는 그냥 똑같히 생긴 Conv 레이어와 Maxpool이 반복되는 구조이다. 따라서, ResNet과 같이 Block 단위로 정의할 필요 없이, 다소 무식한 방법으로 레이어를 만들어 나가는 것이 용이하다.

 

cfg = {
    'A': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'B': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'D': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'E': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

# A : VGG-11
# B : VGG-13
# D : VGG-16
# E : VGG-19

def make_layer(config):
    layers = []
    in_planes = 3
    for value in config:
        if value == "M":
            layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
        else:
            layers.append(nn.Conv2d(in_planes, value, kernel_size=3, padding=1))
            layers.append(nn.ReLU())
            in_planes = value
    return nn.Sequential(*layers)

 

 

VGG 모델 정의


class VGG(nn.Module):
    def __init__(self, config, num_classes=1000, cifar=False):
        super(VGG, self).__init__()
        self.features = make_layer(config)
        
        # ImageNet
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, num_classes)  
        )
        # CIFAR-10
        if cifar:
            self.classifier = nn.Sequential(
                nn.Dropout(0.5),
                nn.Linear(512, 512),
                nn.ReLU(True),
                nn.Dropout(0.5),
                nn.Linear(512, 512),
                nn.ReLU(True),
                nn.Linear(512, 10)  
            ) 
        
    def forward(self, x):
        out = self.features(x)
        out = torch.flatten(out,1)
        out = self.classifier(out)
        return out

 

Classifier의 경우 항상 동일한 조건이므로, make layer 이후에 연산을 진행해주기만 하면 된다.

이 때, ImageNet 기준으로는 Linear 4096, 1000을 마지막 연산으로 수행하지만, CIFAR-10의 경우 이미지 크기가 다르고, 최종 n_classes가 10이므로, 512로 변경하도록 했다.

 

VGG Architecture 생성


def VGG11(cifar=False):
    return VGG(config = cfg['A'], cifar = cifar)

def VGG13(cifar=False):
    return VGG(config = cfg['B'], cifar = cifar)

def VGG16(cifar=False):
    return VGG(config = cfg['D'], cifar = cifar)

def VGG19(cifar=False):
    return VGG(config = cfg['E'], cifar = cifar)

 

논문 구현을 마치고 가장 뿌듯한 순간이다.

아키텍쳐 생성을 미리 정의해준 cfg을 통해 진행해준다.

 

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

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

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

 

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

반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


Weight Initialization

 

딥러닝 모델은 기본적으로, 랜덤으로 생성된 Weight 값으로 시작해서 점차 Loss를 줄여나가는 방식으로 학습을 진행하게 된다. 이 때, 처음 Weight를 초기화 하는 방법에 대한 다양한 연구가 진행되어있는데, 그 이유는 Weight Initialization방식에 따라 실제 성능에서도 유의미한 차이를 가지기 때문이다.

 

출처 : https://medium.com/coinmonks/loss-optimization-in-scientific-python-d1efbbe87171

 

위 그림을 보자, 시작하는 Initial 값이 달랐던 것 뿐인데, 최종적으로 계산된 Cost Function의 값이 차이가 난다는 것을 확인할 수 있다. 그리고 만약 initialization이 오른쪽 봉우리의 최상단에서 시작되거나 할 경우엔, 두개의 봉우리 사이의 Local Optima에 갇히게 될 수도 있다.

 

이처럼 Weight를 초기화하는 방법은 꽤 유의미한 차이를 지니며, 다양한 방법이 연구되어있다.

 

Simple Initialization


먼저 가장 단순하게 생각해 볼 수 있는 방법은, 모든 weight를 0이나 1과 같은 숫자로 시작하는 것이다.

하지만 이는, 학습 과정에서 Back Propagation의 값을 동일하게 만들고, 어떤 Feature가 중요한지 효과적으로 학습을 하지 못하게 되는 결과를 낳는다.

 

따라서 초기화는 반드시 서로 다른 다양한 값으로 넣어주어야 한다.

 

Random Initialization


말 그대로 임의의 수를 넣는 방식이다. 실제로 대부분의 효과적인 Initialization은 다 일종의 Random Initialization이라고 볼 수 있다. 다만 임의 값의 분포를 어떻게 처리할지, 각각의 값에 대한 가중치는 어떻게 처리할지에 따라 다양한 방법이 존재한다.

 

Xavier Initialization


임의로 값을 생성하되, 임의 값의 표준 편차를 직전 Hidden Layer의 개수 n, 현재 Hidden Layer의 개수 m을 기준으로 하여 2 / root(n+m) 을 표준편차로 하는 정규분포를 따르게끔 초기화한다.

 

이는 데이터의 Activation Function 값을 더욱 고르게 퍼지게 할 수 있고, 레이어가 깊고 클 수록 그에 맞게 표준편차가 조정되는 적응형 방식이기 때문에 보다 Robust한 결과값을 얻을 수 있다. 

해당 이름은, 연구의 발표자 Xavier Glorot의 이름을 따서 만들어진 초기화 방법으로, Glorot Initialization이라고도 한다.

 

일반적으로, Activation이 Sigmoid일 때 Xavier를 사용하며, pytorch에서는 아래 명령어를 통해 사용한다.

torch.nn.init.xavier_normal_()
torch.nn.init.xavier_uniform_()

 

He Initialization


He Initialization은 ResNet으로 유명한 Kaiming He의 이름을 따서 만들어졌으며, 마찬가지로 Kaiming Intialization이라고도 한다. Xavier와 달리 ReLU Activation Function과 함께 사용되는데, 표준편차를 root(2 / n)으로 초기화한다.

 

Xavier Initialization의 경우, ReLU와 함께 사용하면 다음과 같이 값이 점점 분포가 치우치고, 깊은 Layer 구조에서 Gradient Vanishing을 발생시킬 수 있다.

 

출처 : https://yngie-c.github.io

 

하지만, He Initialization의 경우, 아래와 같이 분포를 유지하면서 Gradient Vanishing을 막을 수 있다는 장점이 있다.

 

출처 : https://yngie-c.github.io

마찬가지로 torch에 구현이 되어 있으며, 아래와 같은 명령어로 사용할 수 있다.

 

torch.nn.init.kaiming_uniform_()
torch.nn.init.kaiming_normal_()
반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


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 개발자의 삽질 일지입니다! 이해한 내용을 정리하는 용도로 만들었으니, 틀린 내용이 있으면 자유롭게 의견 남겨주세요!

,
반응형


 

ResNet : deep residual learning for image recognition

 

세계적인 딥러닝 연구자인 Kaiming He 님이 작성하였고, Computer Vision 역사의 한 획을 그은 논문인 ResNet을 간략히 알아보자.

 

 

연구의 배경


  • 컴퓨터 비전 분야에 있어서, 깊은 네트워크는 이미지의 다양한 Feature를 학습할 수 있다는 점에서, 매우 중요한 일이라고 평가받는다.
  • 하지만, 2015년 당시, VGG16과 VGG34를 CIFAR 10 데이터셋에서 학습시켰을 때, 오히려 VGG 34가 더 깊은 구조를 가지고 있음에도 불구하고 정확도가 떨어지는 현상이 관측되었다.
  • 원래 네트워크가 깊어질 수록, Gradient Vanishing, 오버피팅등 다양한 Degradation이 발생하지만, 저자가 직접 parameter들을 추적하며 실험한 결과, 이는 단순히 Gradient Vanishing과 오버피팅으로 인한 문제가 아닌, Convergence Rate가 낮아서 발생하는 문제로 정의했다. 즉 Global Optima로 수렴하는 속도(학습 진도)가 너무 느리다는 것이다.
  • 본 논문에서는, Layer가 깊어져도 학습이 잘 진행될 수 있도록 하는 Residual Block 개념을 제시한다.

 

Residual Block


  • H(X)를 한번에 구하는것이 너무 어려우니, F(x) = H(x) - x를 만족하는 F(x)를 학습한다.
  • 입력값 x를 identitiy mapping으로 사용하므로, 추가적인 파라미터나 계산 복잡도 증가가 발생하지 않는다.
  • 만약 x가 optimal한 identity mapping이라면, F(x)의 값이 0에 가깝게 수렴하게 되어, VGG-16과 VGG-34에서 나타난 현상처럼, 적어도 깊은 레이어가 얕은 레이어 구조보다 Error가 더 큰 상황은 방지할 수 있을 것이라는 가정에서 고안되었다.
  • 각각의 블록에서 y = F(x, w_i) + w_s*x 로 계산
  • 만약 identity의 차원이 다르다면, w_s를 통해 Linear Projection하여 더해준다.

 

BottleNeck Block


  • ResNet50 이후의 깊은 모델에서 사용되는 블록이다.
  • 1x1, 3x3, 1x1과 같은 형태의 컨볼루션 레이어를 통해, 데이터의 차원을 줄이고, 연산량은 낮으면서 정보를 효과적으로 전달할 수 있도록 하는 구조이다.
  • 이를 통해 기존 SOTA였던 HighWay나 VGG-16/19보다 더 높은 정확성을 가지면서 오히려 FLOPs는 낮은 결과를 얻어낼 수 있었다.

 

실험 결과


  • 실험 결과, 위와 같이, 기존 VGG-Like 아키텍쳐로 18레이어와 34레이어를 쌓았을 때는 오히려 plain-34에서 정확도가 낮아지는 경향성을 보였다.
  • 그러나, ResNet의 경우, Layer가 깊어질 수록 효율적으로 학습이 진행되고, 에러율이 줄어들며 점점 더 정확해지는 특성을 확인할 수 있다. 
반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


MLFlow UI 외부 접속하는법

 

MLFlow는 아래와 같은 명령어를 통해, 자신이 배포한 파이프라인을 확인할 수 있다.

$ mlflow ui

 

이 때, 기본 옵션은 127.0.0.1:5000으로 ui서버가 열리게 되는데,

이는 외부접속이 불가능한 localhost이다.

 

따라서 외부접속이 가능하게 하기 위해서는 host를 0.0.0.0으로 지정해주어야 한다.

 

$ mlflow ui --host 0.0.0.0

 

이렇게 하면, mlflow를 킨 컴퓨터의 ip주소:5000으로 외부접속이 가능하며, 

공유기 환경의 경우 포트포워딩을 통해 접근이 가능하다.

반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


kfp_server_api.exceptions.ApiException: (400)Reason: Bad Request 해결법

 

분명히 포워드도 다 해줬고, pod들도 정상적으로 동작하고 있는데도 불구하고,

python api에서 experiments run이 되지 않는 문제가 발생했다.

 

에러 메시지


kfp_server_api.exceptions.ApiException: (400)
Reason: Bad Request
HTTP response headers: HTTPHeaderDict({'x-powered-by': 'Express', 'content-type': 'application/json', 'trailer': 'Grpc-Trailer-Content-Type', 'date': 'Fri, 08 Oct 2021 16:22:37 GMT', 'x-envoy-upstream-service-time': '2', 'server': 'istio-envoy', 'transfer-encoding': 'chunked'})
HTTP response body: {"error":"Invalid input error: Invalid resource references for experiment. 
ListExperiment requires filtering by namespace.",
"message":"Invalid input error: Invalid resource references for experiment. ListExperiment requires filtering by namespace.",
"code":3,"details":[{"@type":"type.googleapis.com/api.Error",
"error_message":"Invalid resource references for experiment. ListExperiment requires filtering by namespace.",
"error_details":"Invalid input error: Invalid resource references for experiment. ListExperiment requires filtering by namespace."}]}

 

에러 발생 지점


 kfp.Client(...).create_run_from_pipeline_func(...) 
# 이외에도, run과 관련된 모든 함수

 

해결법


kubeflow가 사용자별로 분리된 workspace를 가지게 함에 따라서, 

독립 환경의 namespace를 인자로 전달받아야 해당 Workspace의 Owner 자격을 얻어 정상적인 동작이 가능하다고 한다. 

 

import kfp
client = kfp.Client(...) # host등 인자는 개인에 맞게 변경

client.set_user_namespace(namespace="<설정한 NAMESPACE>")

print(client.get_user_namespace())
# 입력한 NAMESPACE가 정상 출력되면 완료

 

본 설정은 $HOME/.config/kfp/context.json 파일에 기록되므로, 1회만 등록하고나면 추후 재등록을 하지 않아도 된다.

 

반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


Some weights of the model checkpoint at bert-base-uncased were not used when initializing BertForMultiLabelSequenceClassification 해결법

 

해당 메시지는 사실 에러가 아니라 워닝으로, 코드 실행에는 문제가 없다.

pretrained 모델을 불러와서 분류, 요약과 같은 down stream task를 실행하고자 할 때 주로 발생하는데,

초기화되지 않은 weight가 있거나, 불러왔는데 사용을 하지 않는 weight가 존재할 때 발생한다.

 

정상적으로 동작하는데에는 전혀 지장이 없으며, 분류, 요약에 맡게 다시 Fine-Tuning하는 과정이 있기 때문에 초기화되지 않은 weight도 정상적으로 바뀌게 된다. 하지만 이러한 warning이 굉장히 눈에 거슬리고, 멀티프로세싱을 하는 경우 log를 확인하는데 애로사항이 있으므로, 경고 기능을 끌 수 있다.

 

from transformers import logging
logging.set_verbosity_error()

 

해당 기능은 Error외에 Warning이나 Information을 제공받지 않도록 하므로, 디버깅 할때는 유용한 정보를 놓치게 될 수도 있지만, 디버깅이 끝나고 정상 동작이 확인된 코드의 경우 위와 같이 Error만 로그를 받겠다고 선언하면 깔끔한 output을 확인할 수 있다. 이 후 워닝이 발생하지 않는다!

반응형
블로그 이미지

Hyunsoo Luke HA

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

,
반응형


tcmalloc: large alloc 2219589632 bytes == 0x8fa7a000 @  0x7fb7e4433680 류 Warning 해제하기

 

python에서 용량이 큰 파일을 다룰 때, 해당 경고 메시지가 발생하게 된다. 생긴게 굉장히 괴랄해서 Error가 뜬 것 처럼 보이지만, 사실 대용량 메모리 할당이 이루어졌을 때 이를 알리는 Warning 정도라고 보면 된다.
(물론 실제로 할당 메모리보다 자원이 부족할 경우에는 에러로 이어진다.)

 

 

증상은 아래와 같다.

tcmalloc: large alloc 2219589632 bytes == 0x8fa7a000 @  0x7fb7e4433680 0x7fb7e4454824 0x7fb7e4454b8a 0x7fb7df36325e 0x7fb7df3649d2 0x7fb5b648a8ed 0x7fb75679e7be 0x7fb7563e48be 0x5f5db9 0x5f698e 0x50b4c7 0x570e46 0x56a0ba 0x5f6343 0x56bddd 0x56a0ba 0x5f6343 0x5f38f1 0x5f3bd5 0x486e08 0x5045db 0x56bf09 0x56a0ba 0x5f6343 0x5f56c7 0x56d5f6 0x56a0ba 0x5f6343 0x570e46 0x56a0ba 0x5f6343
tcmalloc: large alloc 2219589632 bytes == 0x113f3e000 @  0x7fb7e4433680 0x7fb7e4454824 0x7fb7e4454b8a 0x7fb7df36325e 0x7fb7df3649d2 0x7fb5b648a8ed 0x7fb75679e7be 0x7fb7563e48be 0x5f5db9 0x5f698e 0x50b4c7 0x570e46 0x56a0ba 0x5f6343 0x56bddd 0x56a0ba 0x5f6343 0x5f38f1 0x5f3bd5 0x486e08 0x5045db 0x56bf09 0x56a0ba 0x5f6343 0x5f56c7 0x56d5f6 0x56a0ba 0x5f6343 0x570e46 0x56a0ba 0x5f6343
tcmalloc: large alloc 2997747712 bytes == 0x198402000 @  0x7fb7e4433680 0x7fb7e4454824 0x7fb7e4454b8a 0x7fb7df36325e 0x7fb7df3649d2 0x7fb5b648a8ed 0x7fb75679e7be 0x7fb7563e48be 0x5f5db9 0x5f698e 0x50b4c7 0x570e46 0x56a0ba 0x5f6343 0x56bddd 0x56a0ba 0x5f6343 0x5f38f1 0x5f3bd5 0x486e08 0x5045db 0x56bf09 0x56a0ba 0x5f6343 0x5f56c7 0x56d5f6 0x56a0ba 0x5f6343 0x570e46 0x56a0ba 0x5f6343

 

대충 위와 같은 경고문이 파일을 다룰 때 마다 발생한다.

에러는 아니지만, 파이썬 로그를 덮어버리는 통에 불편함이 매우 크다.

 

경고를 해제하는 방법은 간단하다. 내부적으로 환경변수 $TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD을 사용하는데, large alloc 옆에 뜬 바이트 수보다 이를 더 크게 등록하면 된다.

 

필자의 경우 사용하는 머신러닝 서버의 RAM 메모리가 300기가를 넘는 TPU-VM이기 때문에,

과감히 10GB에 해당하는 THRESHOLD를 적용했다.

 

$ export TCMALLOC_LARGE_ALLOC_REPORT_THRESHOLD=107374182400

 

이후 해당 워닝이 발생하지 않는다!

반응형
블로그 이미지

Hyunsoo Luke HA

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

,