뉴비에욤

PyTorch 튜토리얼 8 - nn 패키지 본문

Machine Learning/PyTorch

PyTorch 튜토리얼 8 - nn 패키지

초보에욤 2018. 5. 2. 02:32

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 : 자동 미분

2018/05/01 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 3 - 뉴럴 네트워크(신경망)

2018/05/01 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 4 - Classifier 학습

2018/05/01 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 5 - 데이터 병렬 처리

2018/05/02 - [Machine Learning/PyTorch] - PyTorch 튜토리얼 7 - Autograd



원문 : http://pytorch.org/tutorials/beginner/former_torchies/nn_tutorial.html


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

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



목 차 ( 초보자 튜토리얼 )

B. Torch 사용자를 위한 PyTorch

8. nn 패키지

- 예제 1 : ConvNet ( CNN, Convolutional Neural Netowrk, 합성곱 신경망 )

- 정방향/역방향 함수 훅

예제 2 : Recurrent Net ( RNN, Recurrent Neural Netowrk, 순환 신경망 )





nn 패키지


PyTorch 개발자들은 nn 패키지를 다시 디자인하여 autograd 패키지와 통합되게 만들었다.  이번 포스팅은 변경 사항을 알아보는 것이 목적이다.



autograd로 컨테이너(container) 교체


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)는 더 이상 모듈에 존재하지 않고 네트워크 그래프에 존재한다.

따라서 RNN을 사용하는 것은 더욱 간단해졌다. RNN을 생성하고자 한다면 가중치 공유는 고려하지 않고 그냥 동일한 선형 레이어(Linear layer)를 여러 번 사용하면 된다. 

그림. torch nn vs pytorch nn



간단해진 디버깅


디버깅은 파이썬의 pdb 디버거를 이용하는 것이 직관적이다. 또한 디버거와 스택 트레이스는 정확히 오류가 발생한 부분에서 멈추기 때문에 보이는 것이 오류에 대하여 얻을 수 있는 정보 그 자체이다.




예제 1 : ConvNet ( CNN, Convolutional Neural Netowrk, 합성곱 신경망 )


소규모의 CNN을 생성하는 예제를 살펴 볼 것이다.


모든 뉴럴 네트워크는 기본 클래스 nn.Module로부터 파생 된 것이다.

  • 생성자(construfctor)에서 사용하고자 하는 모든 레이어를 선언한다.
  • forward(정방향) 함수에서 입력으로 부터 결과까지 모델이 어떻게 실행되는지 정의한다.
- 코드
import torch
import torch.nn as nn
import torch.nn.functional as F


class MNISTConvNet(nn.Module):

    def __init__(self):
        # this is the place where you instantiate all your modules
        # you can later access them using the same names you've given them in
        # here
        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)

    # it's the forward function that defines the network structure
    # we're accepting only a single input in here, but if you want,
    # feel free to use more
    def forward(self, input):
        x = self.pool1(F.relu(self.conv1(input)))
        x = self.pool2(F.relu(self.conv2(x)))

        # in your model definition you can go full crazy and use arbitrary
        # python code to define your model structure
        # all these are perfectly legal, and will be handled correctly
        # by autograd:
        # if x.gt(0) > x.numel() / 2:
        #      ...
        #
        # you can even do a loop and reuse the same module inside it
        # modules no longer hold ephemeral state, so you can use them
        # multiple times during your forward pass
        # 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


이제 정의 된 CNN을 이제 사용해 볼 것인데 일단 클래스의 인스턴스를 생성한다.

- 코드

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 패키지는 mini-batch 형태인 입력만을 지원하며, 단일 데이터는 입력으로 지원하지 않는다.

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

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



한 개의 랜덤 데이터를 포함하는 mini-batch를 생성하고 CNN에 전송한다.

- 코드

input = torch.randn(1, 1, 28, 28)
out = net(input)
print(out.size())

- 결과

torch.Size([1, 10])


가짜 정답을(dummy target label)  정의하고 손실(loss) 함수를 이용하여 오차(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.3564)



ConvNet의 결과가 저장되는 outTensor이다. 이를 사용하여 손실(loss)을 계산하여 Tensorerr에 저장한다. err를 대상으로 .backward를 호출하면 그라디언트가 CNN 자체의 가중치에 전파 된다.


개별 레이어 가중치와 그라디언트에 접근한다.

- 코드

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(2.0010)
tensor(0.1532)





정방향, 역방향 함수 훅 ( Forward and Backward Function Hooks )


지금까지 가중치와 그라디언트를 살펴 보았다. 그렇다면 레이어의 출력이나 grad_output을 살펴보거나 수정하는 것은 어떻게 해야 할까?


위와 같은 목적을 달성하기 위해  훅(Hook)을 설명하고자 한다.


사용자는 Module이나 Tensor를 대상으로 함수를 등록할 수 있다. 혹은 정방향 훅과 역방향 훅이 있다. 정방향 훅은 정-전파가(forward call), 역방향 훅은 역-전파(backward phase) 일어날 때 실행 된다. 예제를 살펴 보면 다음과 같다.


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:  
input[0]:  
output:  

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


이번에는 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:  
input[0]:  
output:  

input size: torch.Size([1, 10, 12, 12])
output size: torch.Size([1, 20, 8, 8])
output norm: tensor(17.5375)
Inside Conv2d backward
Inside class:Conv2d

grad_input:  
grad_input[0]:  
grad_output:  
grad_output[0]:  

grad_input size: torch.Size([1, 10, 12, 12])
grad_output size: torch.Size([1, 20, 8, 8])
grad_input norm: tensor(1.00000e-02 *
       3.1349)


동작하는 MNIST의 전체 예제 코드는 다음 링크에서 찾아 볼 수 있다.

https://github.com/pytorch/examples/tree/master/mnist




예제 2 : Recurrent Net ( RNN, Recurrent Neural Netowrk, 순환 신경망 )


다음으로 PyTorch을 이용하여 RNN을 생성하는 예제를 살펴 볼 것이다.


뉴럴 네트워크의 상태는 레이어가 아니라 그래프에 저장되기 때문에 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를 이용한 좀 더 완벽한 언어 모델링 예제는 다음 링크에서 찾아 볼 수 있다.

https://github.com/pytorch/examples/tree/master/word_language_model



PyTorch는 기본적으로 CNN과 RNN에 대하여 완벽하게 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()


Comments