이 블로그글은 2019년 조재영(Kevin Jo), 김승수(SeungSu Kim)님의 딥러닝 홀로서기 세미나를 수강하고 작성한 글임을 밝힙니다.
Github 링크 : 실습 코드 링크
💡 목표 CIFAR10 이미지를 분류하는 CNN 개발(+ VGG구현)
참고 링크 : pytorch document (conv2d)
Channel first
방식을 따름💡 dilation이란?
- 정의 : 컨볼루션 연산에서 필터 요소들 사이의 간격을 조정하는 값
- dilation = 1: 필터 요소들이 연속적으로 배치된 기본 컨볼루션
- 3 x 3 필터, dilation = 1
- dilation > 1: 필터 요소들 사이에 간격이 생겨 더 넓은 정보를 학습
- 3 x 3 필터, dilation = 2
- 효과: 넓은 수용 영역 확보 및 멀리 떨어진 특징 간의 관계 학습
참고 링크 : pytorch document (MaxPool2d)
라이브러리 불러오기
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}
가장 기본적인 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
x = x.view(x.size(0), -1)
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)
nn.Sequential
사용법 : 각 레이어가 순차적으로 실행되며, 순서대로 적용nn.ModuleList
사용법 : 리스트 형태로 레이어를 저장하고, 각 레이어를 동적으로 처리하는 데 사용 (이 경우 forward 메서드에서 추가적인 설정이 필요함)💡 BatchNorm과 Actiavtion 적용 순서 차이 및 효과
BatchNorm → Activation
:
- 학습이 안정적이고, 직관적이기 때문에 기본적으로 더 많이 사용되는 방식이다.
- 대부분의 현대 신경망 모델(예: ResNet, EfficientNet 등)은 이 순서를 따른다.
- 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)
... 생략 ...
inputs.view(-1, 3072)
로 데이터를 평탄화할 필요가 없다.