[딥러닝 홀로서기] Lab21. Implementing CNN with Pytorch

YJ·2024년 11월 28일
0

딥러닝 홀로서기

목록 보기
21/24

이 블로그글은 2019년 조재영(Kevin Jo), 김승수(SeungSu Kim)님의 딥러닝 홀로서기 세미나를 수강하고 작성한 글임을 밝힙니다.

Github 링크 : 실습 코드 링크

Pytorch로 CNN 구현하기

💡 목표 CIFAR10 이미지를 분류하는 CNN 개발(+ VGG구현)

Conv2d

참고 링크 : pytorch document (conv2d)

  • 정의 : 2D 이미지 데이터에 대해 2차원 컨볼루션 연산(Convolution)을 수행하는 레이어
  • 입출력 형태
    • 입력 (N, Cᵢₙ, Hᵢₙ,, Wᵢₙ) - pytorch의 경우 Channel first 방식을 따름
      • N : 배치 크기
      • Cᵢₙ : 입력 채널 수
      • Hᵢₙ : 입력의 높이
      • Wᵢₙ : 입력의 너비
    • 출력 (N, Cₒᵤₜ, Hₒᵤₜ, Wₒᵤₜ)
      • N : 배치 크기
      • Cₒᵤₜ : 출력의 채널 수 (필터의 개수)
      • Hₒᵤₜ : 출력의 높이
      • Wₒᵤₜ : 출력의 너비

💡 dilation이란?

  • 정의 : 컨볼루션 연산에서 필터 요소들 사이의 간격을 조정하는 값
    • dilation = 1: 필터 요소들이 연속적으로 배치된 기본 컨볼루션
    • 3 x 3 필터, dilation = 1
    • dilation > 1: 필터 요소들 사이에 간격이 생겨 더 넓은 정보를 학습
    • 3 x 3 필터, dilation = 2
  • 효과: 넓은 수용 영역 확보 및 멀리 떨어진 특징 간의 관계 학습

MaxPool2d

참고 링크 : pytorch document (MaxPool2d)

  • 정의 : 2D 맥스 풀링 연산은 입력 데이터의 각 필터 영역에서 최댓값을 선택하여 출력 데이터를 생성
  • 입출력 형태
    • conv2d와 동일

실습

라이브러리 불러오기

import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import argparse
import numpy as np
import time
from copy import deepcopy # Add Deepcopy for args
import seaborn as sns
import matplotlib.pyplot as plt

데이터 불러오기

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainset, valset = torch.utils.data.random_split(trainset, [40000, 10000])
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
partition = {'train': trainset, 'val':valset, 'test':testset}
  • torchvision을 통해 CIFAR-10 데이터를 다운 받아서 불러온다.

가장 기본적인 CNN 구조

class BAISC_CNN(nn.Module):
  def __init__(self):
    super(BASIC_CNN, self).__init__()

    self.conv1 = nn.Conv2d(in_channels = 3,
                           out_channels = 64,
                           kernel_size = 3,
                           stride = 1,
                           padding = 1)
    self.conv2 = nn.Conv2d(in_channels = 64,
                           out_channels = 256,
                           kernel_size = 5,
                           stride = 1,
                           padding = 2)
    self.act = nn.ReLU();
    self.maxpool1 = nn.MaxPool2d(kernel_size = 2,
                                 stride = 2)
    self.fc = nn.Linear(65536, 10)

  def forward(self, x):
    x = self.conv1(x)
    x = self.act(x)
    x = self.conv2(x)
    x = self.act(x)
    x = self.maxpool1(x)
    x = x.view(x.size(0), -1)
    x = self.fc(x)
    return x
  • CNN layer(Conv2d * 2) → Pooling layer(MaxPool2d) → FC layer 구조를 따른다.
  • 특히 주목해야할 부분은 FC layer로 가기전에 Flatten을 시켜주는 부분이 반드시 필요하다는 것이다.
    • x = x.view(x.size(0), -1)
  • 최종적으로 FC layer에서 10개의 클래스를 분류한다.

VGG 모델 구현

모델 크기별 설정

cfg = {
    'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

모델 구현

class CNN(nn.Module):
  def __init__(self, model_code, in_channels, out_dim, act, use_bn):
    super(CNN, self).__init__()

    if act == 'relu':
      self.act = nn.ReLU()
    elif act == 'sigmoid':
      self.act = nn.Sigmoid()
    elif act == 'tanh':
      self.act = nn.Tanh()
    else:
      raise ValueError('Not a vaild activation function code')

    self.layers = self._make_layers(model_code, in_channels, use_bn)
    self.classifier = nn.Sequential(nn.Linear(512, 256),
                                     self.act,
                                     nn.Linear(256, out_dim))

  def forward(self, x):
    x = self.layers(x)
    x = x.view(x.size(0), -1)
    x = self.classifier(x)
    return x

  def _make_layers(self, model_code, in_channels, use_bn):
    layers = []
    for x in cfg[model_code]:
      if x == 'M':
        layers +=  [nn.MaxPool2d(kernel_size = 2, stride =2)]
      else:
        layers += [nn.Conv2d(in_channels = in_channels,
                             out_channels = x,
                             kernel_size = 3,
                             stride = 1,
                             padding = 1)]
        if use_bn:
          layers += [nn.BatchNorm2d(x)]
        layers += [self.act]
        in_channels = x
    return nn.Sequential(*layers)
  • 실제 실험에서는 VGG를 구현하여 실험하였다.
  • VGG의 경우 layer 갯수에 따라 VGG11, VGG13, VGG16, VGG19가 있다.
  • 서브모듈을 리스트로 관리하는 방법
    • Python의 리스트를 사용할 경우, 훈련 및 업데이트가 필요한 파라미터가 제대로 등록되지 않아서 파라미터 업데이트가 이루어지지 않거나, 오류가 발생할 수 있다.
    • 두가지 방법
      • nn.Sequential 사용법 : 각 레이어가 순차적으로 실행되며, 순서대로 적용
      • nn.ModuleList 사용법 : 리스트 형태로 레이어를 저장하고, 각 레이어를 동적으로 처리하는 데 사용 (이 경우 forward 메서드에서 추가적인 설정이 필요함)
  • 이전에는 Activation 함수 다음에 BatchNorm을 적용했지만, 현재는 BatchNorm을 먼저 적용한 후에 Activation 함수를 적용하는 방식으로 코드가 수정하였다.
    • Activation 함수에 입력되는 값이 한쪽으로 치우쳐 있으면 (예: 값이 매우 크거나 작으면) Activation 함수의 효과가 제한될 수 있기 때문
    • BatchNorm은 입력값의 분포를 조정하여 학습을 더 안정적이고 빠르게 만드는 효과로 사용함
  • MLP와 달리 Dropout이 없다.
    • Dropout은 보통 모델의 파라미터 수가 많아질 때 과적합을 방지하고 일반화 성능을 개선하기 위해 사용한다.
    • CNN의 경우 파델의 파라미터 수가 상대적으로 적기 때문에 Dropout을 적용하지 않아도 과적합 문제가 덜 발생하는 경향이 있다.
    • 하지만, 모델의 끝단에 있는 분류기(Classifier) 부분에는 Dropout을 적용할 수 있다.

💡 BatchNorm과 Actiavtion 적용 순서 차이 및 효과

BatchNorm → Activation:

  • 학습이 안정적이고, 직관적이기 때문에 기본적으로 더 많이 사용되는 방식이다.
  • 대부분의 현대 신경망 모델(예: ResNetEfficientNet 등)은 이 순서를 따른다.
  • BatchNorm을 먼저 적용하면, 입력값의 분포를 정규화하여 Activation 함수가 더 효과적으로 작동하고, 학습의 안정성을 높이는 데 도움을 준다.

Activation → BatchNorm:

  • 일부 실험적 결과에서는 이 순서가 학습 속도 향상이나 초기 학습 안정성 개선을 가져왔다고 보고한 경우가 있습니다.
  • 하지만, Activation 이후에 BatchNorm을 적용하면, Activation 함수의 비선형성이 약해질 가능성이 있기 때문에 신중하게 사용해야한다.

학습, 검증, 테스트 함수 변경

수정전

def train(net, partition, optimizer, criterion, args):
  ... 생략 ...
	for i, data in enumerate(trainloader, 0):
	  optimizer.zero_grad()
	
	  # get the inputs
	  inputs, labels = data
    inputs = inputs.view(-1, 3072)
    inputs = inputs.cuda()
    labels = labels.cuda()
    outputs = net(inputs)    
	... 생략 ...
	

수정후

def train(net, partition, optimizer, criterion, args):
  ... 생략 ...
	for i, data in enumerate(trainloader, 0):
	  optimizer.zero_grad()
	
	  # get the inputs
	  inputs, labels = data
	  inputs = inputs.cuda()
	  labels = labels.cuda()
	  outputs = net(inputs)      
	... 생략 ...
  • CNN에서는 입력 데이터가 (N, Cᵢₙ, Hᵢₙ, Wᵢₙ) 형태로 그대로 입력되므로, MLP처럼 inputs.view(-1, 3072)로 데이터를 평탄화할 필요가 없다.
profile
제 글이 유익하셨다면 ♡와 팔로우로 응원 부탁드립니다.

0개의 댓글

관련 채용 정보