Shortcuts

nn 패키지

autograd에 완벽히 통합되게 하기 위해 nn 패키지를 재설계하였습니다. 이제 무엇이 변경되었는지 살펴보겠습니다.

컨테이너를 autograd로 교체:

이제 더이상 ConcatTable 같은 컨테이너나 CAddTable 같은 모듈, 또는 nngraph를 이용하거나 디버깅하지 않습니다. 대신 autograd를 이용하여 더 깔끔하게 신경망을 정의해보겠습니다. 예를 들어,

  • output = nn.CAddTable():forward({input1, input2}) 대신, output = input1 + input2 를 사용합니다.

  • output = nn.MulConstant(0.5):forward(input) 대신, output = input * 0.5 를 사용합니다.

상태(state)는 모듈 내에 저장되지 않고, 신경망 그래프 상에 존재합니다:

덕분에 순환신경망을 사용하는 방법이 더 간단해졌습니다. 이제 순환신경망을 만들 때, 더 이상 가중치(weight)를 공유하는 것에 대해서는 생각할 필요없이 동일한 Linear 계층을 여러 차례 호출하면 됩니다.

torch-nn-vs-pytorch-nn

torch-nn-vs-pytorch-nn

간소화된 디버깅:

디버깅은 Python의 pdb 디버거를 사용하여 직관적이며, 디버거와 스택 추적(stack trace)은 에러가 발생한 곳에서 정확히 멈춥니다. 이제 보이는 대로 얻을 것입니다. (What you see is what you get.)

예제1: 합성곱 신경망(ConvNet)

이제 어떻게 작은 합성곱 신경망을 만드는지 살펴보겠습니다.

모든 신경망은 기본 클래스인 nn.Module 로부터 파생됩니다:

  • 생성자에서는 사용할 모든 계층을 선언합니다.

  • 순전파 함수에서는 신경망 모델이 입력에서 출력까지 어떻게 실행되는지를 정의합니다.

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


class MNISTConvNet(nn.Module):

    def __init__(self):
        # 여기에 모든 모듈을 생성해두고,
        # 나중에 여기에서 선언해둔 이름으로 사용할 수 있습니다.
        super(MNISTConvNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(10, 20, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    # 순전파 함수에서는 신경망의 구조를 정의합니다.
    # 여기에서는 단 하나의 입력만 받지만, 필요하면 더 받도록 변경하면 됩니다.
    def forward(self, input):
        x = self.pool1(F.relu(self.conv1(input)))
        x = self.pool2(F.relu(self.conv2(x)))

        # 모델 구조를 정의할 때는 어떤 Python 코드를 사용해도 괜찮습니다.
        # 모든 코드는 autograd에 의해 올바르고 완벽하게 처리될 것입니다.
        # if x.gt(0) > x.numel() / 2:
        #      ...
        #
        # 심지어 반복문을 만들고 그 안에서 동일한 모듈을 재사용해도 됩니다.
        # 모듈은 더 이상 일시적인 상태를 갖고 있지 않으므로,
        # 순전파 단계에서 여러 번 사용해도 괜찮습니다.
        # while x.norm(2) < 10:
        #    x = self.conv1(x)

        x = x.view(x.size(0), -1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        return x

이제 정의한 합성곱 신경망을 사용해보겠습니다. 먼저 클래스의 인스턴스를 생성합니다.

net = MNISTConvNet()
print(net)
MNISTConvNet(
  (conv1): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
  (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
  (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (fc1): Linear(in_features=320, out_features=50, bias=True)
  (fc2): Linear(in_features=50, out_features=10, bias=True)
)

참고

torch.nn 은 미니-배치(mini-batch)만 지원합니다. torch.nn 패키지 전체는 하나의 샘플이 아닌, 샘플들의 미니-배치만을 입력으로 받습니다.

예를 들어, nnConv2DnSamples x nChannels x Height x Width 의 4차원 Tensor를 입력으로 합니다.

만약 하나의 샘플만 있다면, input.unsqueeze(0) 을 사용해서 가짜 차원을 추가합니다.

무작위 값을 갖는 하나의 미니-배치를 만들어서 합성곱 신경망에 보내보겠습니다.

input = torch.randn(1, 1, 28, 28)
out = net(input)
print(out.size())
torch.Size([1, 10])

가짜(dummy)로 정답(target)을 하나 만들고, 손실 함수를 사용하여 오차(error)를 계산해보겠습니다.

target = torch.tensor([3], dtype=torch.long)
loss_fn = nn.CrossEntropyLoss()  # LogSoftmax + ClassNLL Loss
err = loss_fn(out, target)
err.backward()

print(err)
tensor(2.3704, grad_fn=<NllLossBackward0>)

합성곱 신경망의 출력 outTensor 이며, 이를 사용하여 오차를 계산하고 결과를 Tensorerr 에 저장합니다. err.backward 를 호출하면 변화도가 전체 합성곱 신경망의 가중치에 전파됩니다.

이제 개별 계층의 가중치와 변화도에 접근해보겠습니다:

print(net.conv1.weight.grad.size())
torch.Size([10, 1, 5, 5])
print(net.conv1.weight.data.norm())  # norm of the weight
print(net.conv1.weight.grad.data.norm())  # norm of the gradients
tensor(1.8137)
tensor(0.6387)

순전파/역전파 함수 훅(Hook)

지금까지 가중치와 변화도에 대해서 살펴봤습니다. 그렇다면 계층의 출력이나 grad_output 을 살펴보거나 수정하려면 어떻게 해야 할까요?

이런 목적으로 사용할 수 있는 훅(Hook) 을 소개합니다.

Module 이나 Tensor 에 함수를 등록할 수 있습니다. 훅은 순전파 훅과 역전파 훅이 있는데, 순전파 훅은 순전파가 일어날 때, 역전파 훅은 역전파가 일어날 때 실행됩니다. 예제를 살펴보겠습니다.

conv2에 순전파 훅을 등록하고 몇 가지 정보를 출력해보겠습니다.

def printnorm(self, input, output):
    # input is a tuple of packed inputs
    # output is a Tensor. output.data is the Tensor we are interested
    print('Inside ' + self.__class__.__name__ + ' forward')
    print('')
    print('input: ', type(input))
    print('input[0]: ', type(input[0]))
    print('output: ', type(output))
    print('')
    print('input size:', input[0].size())
    print('output size:', output.data.size())
    print('output norm:', output.data.norm())


net.conv2.register_forward_hook(printnorm)

out = net(input)
Inside Conv2d forward

input:  <class 'tuple'>
input[0]:  <class 'torch.Tensor'>
output:  <class 'torch.Tensor'>

input size: torch.Size([1, 10, 12, 12])
output size: torch.Size([1, 20, 8, 8])
output norm: tensor(15.6635)

conv2에 역전파 훅을 등록하고 몇 가지 정보를 출력해보겠습니다.

def printgradnorm(self, grad_input, grad_output):
    print('Inside ' + self.__class__.__name__ + ' backward')
    print('Inside class:' + self.__class__.__name__)
    print('')
    print('grad_input: ', type(grad_input))
    print('grad_input[0]: ', type(grad_input[0]))
    print('grad_output: ', type(grad_output))
    print('grad_output[0]: ', type(grad_output[0]))
    print('')
    print('grad_input size:', grad_input[0].size())
    print('grad_output size:', grad_output[0].size())
    print('grad_input norm:', grad_input[0].norm())


net.conv2.register_backward_hook(printgradnorm)

out = net(input)
err = loss_fn(out, target)
err.backward()
Inside Conv2d forward

input:  <class 'tuple'>
input[0]:  <class 'torch.Tensor'>
output:  <class 'torch.Tensor'>

input size: torch.Size([1, 10, 12, 12])
output size: torch.Size([1, 20, 8, 8])
output norm: tensor(15.6635)
/workspace/.local/lib/python3.8/site-packages/torch/nn/modules/module.py:1344: UserWarning:

Using a non-full backward hook when the forward contains multiple autograd Nodes is deprecated and will be removed in future versions. This hook will be missing some grad_input. Please use register_full_backward_hook to get the documented behavior.

Inside Conv2d backward
Inside class:Conv2d

grad_input:  <class 'tuple'>
grad_input[0]:  <class 'torch.Tensor'>
grad_output:  <class 'tuple'>
grad_output[0]:  <class 'torch.Tensor'>

grad_input size: torch.Size([1, 10, 12, 12])
grad_output size: torch.Size([1, 20, 8, 8])
grad_input norm: tensor(0.1188)

실제로 동작하는 MNIST 전체 예제는 여기에서 확인할 수 있습니다. https://github.com/pytorch/examples/tree/master/mnist

예제2: 순환 신경망(Recurrent Nets)

다음으로 PyTorch를 사용하여 순환 신경망을 만들어보겠습니다.

신경망의 상태는 각 계층이 아닌 그래프에 저장되므로, 간단히 nn.Linear을 생성한 후 순환할 때마다 계속해서 재사용하면 됩니다.

class RNN(nn.Module):

    # you can also accept arguments in your model constructor
    def __init__(self, data_size, hidden_size, output_size):
        super(RNN, self).__init__()

        self.hidden_size = hidden_size
        input_size = data_size + hidden_size

        self.i2h = nn.Linear(input_size, hidden_size)
        self.h2o = nn.Linear(hidden_size, output_size)

    def forward(self, data, last_hidden):
        input = torch.cat((data, last_hidden), 1)
        hidden = self.i2h(input)
        output = self.h2o(hidden)
        return hidden, output


rnn = RNN(50, 20, 10)

LSTM과 Penn Tree-bank를 사용한 좀 더 완벽한 언어 모델링 예제는 여기 에 있습니다.

PyTorch는 합성곱 신경망과 순환 신경망에 CuDNN 연동을 기본적으로 지원하고 있습니다.

loss_fn = nn.MSELoss()

batch_size = 10
TIMESTEPS = 5

# Create some fake data
batch = torch.randn(batch_size, 50)
hidden = torch.zeros(batch_size, 20)
target = torch.zeros(batch_size, 10)

loss = 0
for t in range(TIMESTEPS):
    # yes! you can reuse the same network several times,
    # sum up the losses, and call backward!
    hidden, output = rnn(batch, hidden)
    loss += loss_fn(output, target)
loss.backward()

Total running time of the script: ( 0 minutes 0.011 seconds)

Gallery generated by Sphinx-Gallery


더 궁금하시거나 개선할 내용이 있으신가요? 커뮤니티에 참여해보세요!


이 튜토리얼이 어떠셨나요? 평가해주시면 이후 개선에 참고하겠습니다! :)

© Copyright 2018-2023, PyTorch & 파이토치 한국 사용자 모임(PyTorch Korea User Group).

Built with Sphinx using a theme provided by Read the Docs.

PyTorchKorea @ GitHub

파이토치 한국 사용자 모임을 GitHub에서 만나보세요.

GitHub로 이동

한국어 튜토리얼

한국어로 번역 중인 PyTorch 튜토리얼입니다.

튜토리얼로 이동

커뮤니티

다른 사용자들과 의견을 나누고, 도와주세요!

커뮤니티로 이동