뉴비에욤

PyTorch 튜토리얼 3 - 뉴럴 네트워크(신경망, Neural Networks) 본문

Machine Learning/PyTorch

PyTorch 튜토리얼 3 - 뉴럴 네트워크(신경망, Neural Networks)

초보에욤 2018. 5. 1. 16:35

2017/07/13 - [Machine Learning/PyTorch] - 윈도우 10 PyTorch 환경 구성 - 설치

2018/04/30 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 1 - PyTorch란?

2018/04/30 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 2 - Autograd : 자동 미분



원문 : http://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html#


* 17년도 07월에 초번 번역 후, 파이토치 최신 버전인 0.4.0에 맞추어 갱신 된 번역본

* 최근 갱신일 : 2018-05-02



목 차 ( 초보자 튜토리얼 )

A. 파이토치와 함께하는 딥러닝 : 60분만에 끝장내기!

3. 뉴럴 네트워크

- 네트워크 정의

- 손실 함수

- 역전파

- 가중치 갱신




뉴럴 네트워크 ( 신경망, Neural Networks )


뉴럴 네트워크는 torch.nn 패키지를 이용하여 생성할 수 있다. 


이전 포스팅에서 autograd와, autograd를 이용하여 모델을 정의하고 미분을 수행하는 nn 패키지에 대하여 살펴보았다. nn.Module은 여러개의 레이어와 output를 리턴하는 forward(input) 메소드를 포함한다.



아래의 이미지는 디지털 이미지를 식별하는 뉴럴 네트워크이다.

convnet

그림 1. Convolutional Neural Network(컨볼루션 뉴럴 네트워크, ConvNet) 그림


위 네트워크는 가장 간단한 feed-forward 네트워크이다. 입력으로 사용된 데이터를 순차적으로 여러 레이어에 전달하고 마지막에 출력을 제공한다.


일반적인 뉴럴 네트워크의 학습 절차는 다음과 같다.

  • 학습 가능한 파라미터나 weight(가중치)가 있는 뉴럴 네트워크를 정의 한다.
  • 입력 데이터셋에 대한 반복 학습을 진행 한다.
  • 네트워크를 통해 입력 값을 처리한다.
  • loss(결과가 정확한 값을 기준으로 얼마나 멀리 떨어져 있는지 나타냄)를 계산한다.
    • 손실(오차) 계산, = 출력(output)과 정답(targget)의 차이
  • 그라디언트(기울기)를 네트워크의 파라미터로 역전파 시킨다.
  • 다음과 같이 간단한 갱신 룰을 이용하여 네트워크의 weight를 갱신한다.
    • weight = weight - learning_rate * gradient



네트워크 정의 ( Define the network )

네트워크를 다음과 같이 정의한다.
- 소스
import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 5x5 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net) 
- 결과
Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)


사용자는 직접 forward 함수만 정의해주면 된다. 그라디언트가 계산되는 backward 함수는 autograd를 사용함으로써 자동으로 정의 된다. 사용자는 forward 함수에서 모든 텐서 연산을 사용할 수 있다.

모델의 학습 가능한 파라미터는 net.parameters()에 의해 리턴 된다.
- 소스
Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
) 
- 결과
10
torch.Size([6, 1, 5, 5])

32x32 크기의 랜덤 값을 입력으로 사용하면 다음과 같다. 참고로 이 네트워크(LeNet, 현대의 CNN 네트워크의 시초라 할 수 있음)에 대한 예상 입력 크기는 32x32이다. 이 네트워크를 MNIST 데이터셋을 대상으로 사용하기 위해서는 데이터셋의 이미지를 32x32 크기로 변경할 필요가 있다.
- 소스
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out) 
- 결과
tensor([[-0.0089, -0.0514,  0.0059,  0.1412, -0.1543,  0.0494, -0.0966,
         -0.1150, -0.0986, -0.1103]])

모든 파라미트의 그라디언트 버퍼를 0으로 설정하고, 랜덤 그라디언트로 역전파를 진행한다.
- 소스
net.zero_grad()
out.backward(torch.randn(1, 10))

torch.nn은 mini-batch만을 지원한다.  전체 torch.nn 패키지는 mini-batch 형태인 입력만을 지원하며, 단일 데이터는 입력으로 지원하지 않는다.

예를 들어 nn.Conv2d nSamples x nChannels x Height x Width의 4차원 텐서를 취한다.

만약 단일 샘플(1개의 데이터)이 있다면 input.unsqueeze(0) 을 사용하여 가짜 임시 배치 차원을 추가하면 된다.


좀 더 진행하기에 앞서, 지금까지 살펴본 것을 정리하면 다음과 같다.
  • torch.Tensor - backward()와 같은 autograd 연산을 지원하는 다차원 배열이며 텐서에 대한 그라디언트(기울기)를 가지고 있다.
  • nn.Module - 뉴럴 네트워크 모듈로서 파라미터를 GPU로 옮기거나, 내보내기, 불러오기 등의 보조 작업을 이용하여 파라미터를 캡슐화 하는 편리한 방법이다.
  • nn.Parameter - 텐서의 일종, Module에 속성을 할당 될 때 파라미터로 자동 등록 된다.
  • autograd.Function - autograd 연산의 forward와 backward에 대한 정의를 구현한다. 모든 Tensor 연산은 최소한 하나의 Function 노드를 생성하는데, 이 노드는 Tensor를 생성하고 기록을 인코딩 하는 여러 함수들에 연결 된다.

지금까지 우리는 다음의 것들을 다루었다.
  • 뉴럴 네트워크의 정의
  • 입력 처리 및 backward 호출

그리고 다음의 것들이 아직 남아있다.
  • loss 계산
  • 네트워크 weights(가중치) 갱신



손실 함수 ( Loss Function )

오차 함수는 (출력, 정답) 형태의 입력을 받아, 출력이 정답에서 얼마나 멀리 떨어져 있는지 추정하는 값을 계산한다.

nn 패키지에는 여러 가지 손실 함수들이 존재하는데 가장 간단한 loss는 nn.MSELoss로 입력과 정답 사이의 평균 제곱 오차(mean-squared error)를 계산한다.

예를 들면 다음과 같다.
- 소스
output = net(input)
target = torch.arange(1, 11)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss) 
- 결과
tensor(39.2273)

이제, 만약 사용자가 .grad_fn 속성을 이용하여 역방향으로 loss를 따라간다면 다음과 같은 계산 그래프를 볼 수 있다.
- 소스
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss


따라서 loss.backward()를 호출하면 전체 그래프는 손실에 대하여 미분 계산이 수행되며, requires_grad=True인 그래프 내의 모든 텐서들은 그라디언트로 누적 된 .grad 텐서를 갖게 된다.

예를 들어 몇 가지 단계를 역으로 진행하면 다음과 같다.
- 소스
print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU 
- 결과
<MseLossBackward object at 0x7fb9f7338780>
<AddmmBackward object at 0x7fb9f73385c0>
<ExpandBackward object at 0x7fb9f73385c0>



역전파 ( Backprop )

에러를 역전파하기 위해 사용자는 loss.backward()를 호출하면 된다. 다만 기존에 존재하는 그라디언트들을 초기화 할 필요가 있다. 그렇지 않으면 그라디언트들이 존재하는 그라디어트에 누적되어 저장되기 때문이다.

loss.backward()를 호출하고 backward 호출 이전과 이후의 conv1's의 바이어스 그라디언트를 살펴 볼 것이다.
- 소스
net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad) 
- 결과
conv1.bias.grad before backward
tensor([ 0.,  0.,  0.,  0.,  0.,  0.])
conv1.bias.grad after backward
tensor([ 0.0501,  0.1040, -0.1200,  0.0833,  0.0081,  0.0120])

위와 같은 과정을 통해 우리는 손실 함수의 사용법을 살펴 보았다.



나중에 읽어보기

뉴럴 네트워크 패키지는 딥 뉴럴 네트워크의 빌딩 블록을 생성하는 다양한 모듈과 손실 함수들을 포함하고 있다. 이와 관련 된 전체 목록은 다음 링크를 참고하기 바란다.

http://pytorch.org/docs/nn



마지막으로 배워야 하는 항목

  • 네트워크의 weights(가중치) 갱신



가중치 갱신 ( Update the weights )


실제로 사용되는 가장 간단한 갱신 룰은 Stochastic Gradient Descent(SGD)이며 다음과 같은 식을 가진다.


weight = weight - learning_rate * gradient



파이썬 코드를 통해 다음과 같이 구현할 수 있다.

- 소스

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)


그러나 뉴럴 네트워크를 사용할 때 SGD, Nesterov-SGD, Adam, RMSProp 등과 같은 다양한 갱신 룰을 사용하고 싶을 수도 있다. 이러한 룰의 사용을 위해 PyTorch는 위와 같은 갱신 룰을 포함하는 torch.optim 패키지를 제공한다.

해당 패키지는 다음 코드와 같이 매우 간단하게 사용할 수 있다.

- 소스

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update


역전파(backprop) 섹션에서 설명한 것처럼 그라디언트는 누적되기 때문에 optimizer.zero_grad()를 이용하여 그라디언트(기울기) 버퍼를 어떻게 수동으로 '0'으로 세팅하는지 살펴보기 바란다.


Comments