본문 바로가기
[AI] - Neural Network

# 2. Convolution Neural Network (CNN) - Code

by Bebsae 2021. 5. 6.

지난 포스트에서는 CNN의 개념에 대해 살펴보았다. 이번 포스트에서는 CNN을 실제로 구현 및 학습하는 코드를 직접 작성해본다. 코드는 Pytorch를 기반으로 작성했다.

1. 필요한 모듈 import

 

import torch
import torchvision
import torchvision.transforms as transforms

 

필자는 시각데이터(이미지)를 다룰 것이기 때문에 torchvision 모듈을 택했다.

 

2. transform 정의

 

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

 

이미지를 텐서형태로, 평균은 0.5, 표준편차도 0.5로 정규화를 진행하는 내용이다. transform에는 이밖에도 crop과 같이 이미지 전처리를 위한 로직도 포함될 수 있다.

 

3. dataset 정의

 

trainset = torchvision.datasets.CIFAR10(
    root='./data',
    train=True,
    download=True,
    transform=transform
)

testset = torchvision.datasets.CIFAR10(
    root='./data',
    train=False,
    download=True,
    transform=transform
)

 

torchvision.datasets 모듈은 간편하게 데이터세트를 불러올 수 있다. 그중 필자는 CIFAR-10 데이터 세트를 선택했다.

trainset과 testset에 train의 여부로 학습 데이터 세트와 테스트 데이터 세트로 구분했다. 그리고 위에서 정의된 transform을 인자로 대입해야 한다.

 

4. DataLoader에 적재하기

"""
num_workers (int, optional) – how many subprocesses to use for data loading. 
      0 means that the data will be loaded in the main process. (default: 0)
"""

batch_size = 4

trainloader = torch.utils.data.DataLoader(
    dataset=trainset,
    batch_size=batch_size,
    shuffle=True,
    num_workers=2
)

testloader = torch.utils.data.DataLoader(
    dataset=testset,
    batch_size=batch_size,
    shuffle=True,
    num_workers=2
)

 

위에서 정의한 데이터 세트들을 iterative하게 순회(학습 및 추론 과정에서)하려면 데이터 세트들을 각자 알맞는 로더에 적재시켜야 한다. batch_size는 이미지 몇장 단위로 묶을지를 의미한다. num_workers는 데이터 적재과정에 몇개의 서브 프로세스를 돌릴 것인지를 의미한다.

 

5. 신경망 설계 및 구현

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

class Net(nn.Module):
  def __init__(self):
    super().__init__()
    self.conv1 = nn.Conv2d(3, 6, 5) # input dim, output dim, kernel size
    self.conv2 = nn.Conv2d(6, 16, 5) # padding : 0
    self.pool = nn.MaxPool2d(2, 2)
    self.fc = nn.Linear(16 * 5 * 5, 10)

  # propagation
  def forward(self, x):
    x = self.pool(F.relu(self.conv1(x))) # convolution layer 1
    x = self.pool(F.relu(self.conv2(x))) # convolution layer 2
    x = x.view(-1, 16 * 5 * 5)
    x = self.fc(x)
    return x

net = Net()

 

위 코드는 간단한 CNN 신경망을 설계하는 코드이다. 생성자(__init__)에서는 레이어에 사용될 요소들을 정의한다. (정의하기만 할 뿐 연결이 된것은 아님!) Net 클래스는 nn.Module 클래스를 상속받는데, nn.Module 클래스의 forward() 메소드를 오버라이딩 한다. 이 메소드는 생성자에서 정의한 요소들을 실질적으로 연결(구현)하는 과정이다.

 

.

.

.

 

합성곱을 거칠때 텐서의 크기가 어떻게 변하는지 파악하고 가자.

 

출처 : https://wikidocs.net/64066

 

Convolution Layer 1

self.conv1

3차원 input(RGB Image)을 5x5x6커널과 합성곱을 거친다.

output의 높/낮이 : (32-5)/1 + 1 = 28

32x32x3 ->  28x28x6

 

F.relu

ReLU 활성화함수를 거친다. 텐서의 크기에는 영향이 없다.

 

self.pool

2x2사이즈로 max pooling을 진행한다. 이미지의 크기는 절반으로 줄어든다.

28x28x6 -> 14x14x6

 

 

Convolution Layer 2

self.conv2

6차원 input을 5x5x16커널과 합성곱을 거친다.

output의 높/낮이 : (14-5)/1 + 1 = 10

14x14x6 ->  10x10x16

 

F.relu

ReLU 활성화함수를 거친다. 텐서의 크기에는 영향이 없다.

 

self.pool

2x2사이즈로 max pooling을 진행한다. 이미지의 크기는 절반으로 줄어든다.

10x10x16 -> 5x5x16

 

 

FC (Fully-Connected) Layer

self.view(-1, 16 * 5 * 5)

위의 텐서(5x5x16)를 10개의 클래스로 분리하려면 우선 평활화(flatten - 일렬로 늘어트리는 작업)을 해야한다.

텐서의 요소의 갯수는 총 5x5x16개이므로 이를 view() 메소드를 통해 reshape 과정을 거치면 (1, 5*5*16)형태의 텐서로 변환이 될 것이다.

5x5x16 -> 1x400 (5*5*16)

 

self.fc

이제 5*5*16개의 input들을 10개의 클래스와 완전 연결하여 각각의 요소가 클래스를 결정하는데 미치는 영향 (가중치)를 파악한다.

1x400 -> 10

 

6. Loss Function 정의

경사하강법(Gradient Descent)은 미분값이 최저인 지점을 손실 함수의 최저점으로 보고, 미분값의 부호의 반대 방향으로 learning-rate만큼 이동한다. (미분값이 양의 값일 경우, 음의 방향으로 이동해야 값이 감소하므로) 여기서 이동한다는 의미는 학습할 파라미터인 가중치(weight)를 조정한다는 의미이다.

local minima

 

하지만, 그 옆에 더 깊은 기울기가 0인 지점이 존재하는 것을 모르고 local에 갇히는 것을 local minima 현상이라고 한다.

 

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),
                      lr=0.001, # learning rate
                      momentum=0.9) # prevent local minima

 

위 코드는 크로스 엔트로피 손실함수(Cross Entropy Loss)SGD(Stochastic Gradient Descent)를 변수에 할당했다. lr(learning rate)은 0.001, momentum(local minima에 빠지지 않기 위한 관성)은 0.9로 지정했다. lr과 momentum같이 사용자가 직접 입력해야 하는 파라미터를 하이퍼 파라미터(hyper parameter)라고 한다.

 

출처 : https://tutorials.pytorch.kr/beginner/blitz/neural_networks_tutorial.html

 

7. 학습 및 가중치 최적화

epoch = 4
for e in range(epoch):
  running_loss = 0.0
  for i, (inputs, labels) in enumerate(trainloader):
    optimizer.zero_grad()
    outputs = net(inputs)
    
    # calculate parameters (W, b)
    loss = criterion(outputs, labels) # prediction, answer
    loss.backward()
    optimizer.step()

    running_loss += loss.item()
    if i%2000 == 1999:
      print(f'[{e+1}, {i+1}] : {running_loss/2000}')
      running_loss = 0.0

 

  • epoch

학습할 횟수를 의미한다.

 

  • optimizer.zero_grad()

버퍼에 연산 결과(미분의 결과값)이 누적되는 것을 방지하기 위함이다.

 

  • ouputs = net(inputs)

위에서 설계한 네트워크에 inputs을 대입하여 propagation을 거쳐 나온 결과값이다. (예측한 결과)

 

  • loss = criterion(ouputs, label)

예측결과와 실제값을 비교하여 오차를 함수로써 정의한 것이다.

 

  • loss.backward()

오차함수를 역전파를 통해 gradient (input이 결과에 미치는 영향)을 계산한다.

 

  • optimizer.step()

계산된 gradient를 토대로 W와 b를 업데이트 한다.

 

[참고] zero_grad()

출처 : https://wikidocs.net/53560

 

댓글