본문 바로가기
Python

파이토치, 신경망 정의 (Custom nn Modules)

by 슬통이 2020. 7. 20.
반응형

저번 시간 우리는 파이 토치의 자동 미분 추적 기능에 대하여 알아보았다. 그리고 그 전 시간에는 R에서의 순전파에 대하여 알아보았다. 아직 안 보신 분들은 보고 오시는 것을 추천한다.

 

Forward propagation, R 버전

오늘은 뉴럴 넷 (Neural network)을 공부하기 위해서 앞으로 우리가 다룰 뉴럴넷 중 가장 현실적이고, 어떤 일이 벌어지고 있는지 상상이 가능한 뉴럴 넷을 정의하는 것을 목표로 하자. 우리가 오늘 �

statisticsplaybook.tistory.com

오늘은 파이토치에서 신경망(Neural net)을 어떻게 정의하는지에 대하여 알아보도록 하겠다.

파이토치 패키지, torch.nn

파이토치를 사용해서 신경망을 정의할 때 사용하는 패키지가 있다. 바로 torch.nn이라는 패키지이다. R에서 패키지 불러오는 것을 library()를 사용해서 하는 것처럼 파이썬에서는 다음과 같이 설정한다.

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

as nnas F의 경우 패키지 이름이 길어 다 쓰기 귀찮으니 짧은 이름을 만들어 코드에 적용하는 것. 즉, 다음의 코드는 같다.

nn.Linear()
torch.nn.Linear()

nn.Module과 nn.Linear 클래스

nn.Module이 어떤 역할을 하는지에 대하여 알아보기 위해 가장 간단한 신경망을 작성해보도록 하자. 바로 우리가 앞서 살펴본 2단 레이어 네트워크 예제에서 사용한 데이터를 만들어 보자.

X = torch.arange(1.0, 7.0, 1.0).view(3, 2)
X
## tensor([[1., 2.],
##         [3., 4.],
##         [5., 6.]])

먼저, TwoLayerNet이라는 이름의 신경망 클래스를 정의한다. 이 클래스는 torch.nn 패키지에 미리 정의된 nn.Module 클래스를 상속한다. 즉, 이미 torch.nn 클래스에 신경망 관련 많은 함수가 정의되어 있을 것이고, 이것을 다 상속받아서 사용할 예정인 것이다. 다음의 코드는 우리가 살펴본 아래의 신경망을 정의한 코드이다.

 

class TwoLayerNet(nn.Module):

    def __init__(self, D_in, H, D_out):
        super(TwoLayerNet, self).__init__()
        self.hidden_layer = nn.Linear(D_in, H, bias=False)
        self.output_layer = nn.Linear(H, D_out, bias=False)

D_in, H, D_out = 2, 3, 1
my_net = TwoLayerNet(D_in, H, D_out)
my_net
## TwoLayerNet(
##   (hidden_layer): Linear(in_features=2, out_features=3, bias=False)
##   (output_layer): Linear(in_features=3, out_features=1, bias=False)
## )

결과를 살펴보면 TwoLayerNet 클래스의 인스턴스인 my_net는 두 개의 레이어가 들어있는 것을 확인할 수 있다. 먼저 우리의 데이터 X의 features 갯수가 2개이므로, 히든 레이어의 입력값 갯수가 2개가 되어야 한다. 또한 히든 레이어의 노드 수가 3개이므로 결과 행력의 features 갯수가 3개가 되어야 한다. 다음의 코드를 확인해보자. 우리가 예전에 다루었던 예제에서는 bias 항이 없었으므로, bias=False 해주어야 함에 주의하자.

mat_operation = nn.Linear(2, 3, bias=False)
print(mat_operation.weight) # W matrix = Beta^T
## Parameter containing:
## tensor([[ 0.5624, -0.3207],
##         [ 0.5820,  0.3062],
##         [-0.6182, -0.1159]], requires_grad=True)
print(mat_operation.bias)
## None

우리는 mat_operationnn.Linear(2, 3) 클래스의 인스턴스로 이해할 수 있다. 그리고 인스턴스의 수학적 의미는 행렬 연산으로 이해할 수 있다. mat_operation가 생성될 때 임의의 weight, \(W\), 와 bias, \(b\),가 생성이 되고, 입력값으로 들어오는 X에 대하여 다음의 연산을 수행한 후 결괏값을 내보낸다.

파이토치에서는 통계학에서 쓰이는 데이터 매트릭스 곱하기 베타 형식으로 짜여있다니, 눈물 나게 고맙다.

\[ y = X W^T = X \boldsymbol{\beta} \]

결과를 코드로 확인해보자.

beta_mat = mat_operation.weight.t()

y = X @ beta_mat

print(y == mat_operation(X))
## tensor([[True, True, True],
##         [True, True, True],
##         [True, True, True]])

bias=True

bias=True를 해주면 웨이트 매트릭스와 더불어 bias 벡터가 생성이 된다.

mat_operation = nn.Linear(2, 3, bias=True)
print(mat_operation.weight) # W matrix = Beta^T
## Parameter containing:
## tensor([[ 0.3649,  0.4473],
##         [ 0.6240,  0.2289],
##         [-0.4811, -0.3498]], requires_grad=True)
print(mat_operation.bias)
## Parameter containing:
## tensor([ 0.3479, -0.2996,  0.0951], requires_grad=True)

따라서 행렬 연산 역시 다음과 같이 바뀐다.

\[ y = X W^T + b = X \boldsymbol{\beta} + b \]

beta_mat = mat_operation.weight.t()
b = mat_operation.bias

y = X @ beta_mat + b

print(y == mat_operation(X))
## tensor([[True, True, True],
##         [True, True, True],
##         [True, True, True]])

순전파 (Forward propagation) 정의

파이 토치를 공부하면서 신기한 걸 많이 배우고 있는데, 일단 파이썬의 OOP형식으로 코딩이 짜여 있다는 것이 한 가지이고, 그와 반대로 OOP 스타일이 아닌 형태의 코딩 스타일도 지원한다는 점이다. 앞선 예제를 이어가 보면, 우리는 신경망의 순전파를 코딩해야 할 차례이다.

순전파의 경우 다음과 같이 두 가지 스타일로 forward 메소드를 정의해서 구현할 수 있다.

torch.nn.functional 이용

다음의 코드에서 Ftorch.nn.functional을 의미한다는 것을 까먹지 말자.

class TwoLayerNet(nn.Module):

    def __init__(self, D_in, H, D_out):
        super(TwoLayerNet, self).__init__()
        self.layer2 = nn.Linear(D_in, H)
        self.layer3 = nn.Linear(H, D_out)
        
    def forward(self, X):
        z2 = self.layer2(X)
        a2 = F.sigmoid(z2)
        z3 = self.layer3(a2)
        y_hat = F.sigmoid(z3)
        return y_hat
        
torch.manual_seed(1234)
## <torch._C.Generator object at 0x7f06465878d0>
D_in, H, D_out = 2, 3, 1
my_net = TwoLayerNet(D_in, H, D_out)
my_net(X)
## tensor([[0.6054],
##         [0.5946],
##         [0.5867]], grad_fn=<SigmoidBackward>)
## 
## /home/cara3621/.local/share/r-miniconda/envs/r-reticulate/lib/python3.6/site-packages/torch/nn/functional.py:1351: UserWarning: nn.functional.sigmoid is deprecated. Use torch.sigmoid instead.
##   warnings.warn("nn.functional.sigmoid is deprecated. Use torch.sigmoid instead.")

torch.nn 이용

class TwoLayerNet(nn.Module):

    def __init__(self, D_in, H, D_out):
        super(TwoLayerNet, self).__init__()
        self.layer2 = nn.Linear(D_in, H)
        self.layer3 = nn.Linear(H, D_out)
        self.sigmoid = nn.Sigmoid()
        
    def forward(self, X):
        z2 = self.layer2(X)
        a2 = self.sigmoid(z2)
        z3 = self.layer3(a2)
        y_hat = self.sigmoid(z3)
        return y_hat

torch.manual_seed(1234)
## <torch._C.Generator object at 0x7f06465878d0>
D_in, H, D_out = 2, 3, 1
my_net = TwoLayerNet(D_in, H, D_out)
my_net(X)
## tensor([[0.6054],
##         [0.5946],
##         [0.5867]], grad_fn=<SigmoidBackward>)

어느 방식으로 짜도 상관은 없다는게 커뮤니티의 결론인데, sigmoid 함수처럼 그냥 특정 레이어와 관련이 없이 독립적인 함수들, (파이토치 쪽에서는 state의 유무라고 부르는 것 같다.)은 그냥 torch.nn.functional로 짜면 된다 생각하고 일단 넘기자.

my_net(X) 구문에 대하여

우리가 짠 신경망인 TwoLayerNet의 순전파는 다음의 코드처럼 my_net에 데이터를 넣어주면 된다.

my_net(X)
## tensor([[0.6054],
##         [0.5946],
##         [0.5867]], grad_fn=<SigmoidBackward>)

파이썬 클래스를 공부한 나는 왜 TwoLayerNet 클래스 안에 정의된 forward() 메소드를 부르지 않고 바로 넣어주는지 의문이었다.

my_net.forward(X)
## tensor([[0.6054],
##         [0.5946],
##         [0.5867]], grad_fn=<SigmoidBackward>)

인터넷 검색 후 다음의 답을 발견하게 되었다.

요즘은 검색하면 다나오는 편리한 세상이다. 나중엔 검색안하고 핸드폰에 물어만봐도 다 알려주겠지.

 

Any different between model(input) and model.forward(input)

class MyModel(nn.Module): def __init__(self, cuda, word_dim, tag_dim, mem_dim, criterion): super(MyModel, self).__init__() def forward(input): .. # do something return output model = MyModel() Is there any different if I called model.forward(input) rather

discuss.pytorch.org

핵심은 my_net이 실행될 때, __call__이 실행되면서 forward()를 실행시킨다는 것, 그리고 다른 함수들도 같이 실행시켜주기 때문에 꼭 my_net(X)식으로 실행시킬 것이 주된 논지임. 다음 포스팅에서는 백프랍과 트레이닝에 대하여 알아보자.

참고자료

  1. PyTorch: Custom nn Modules

  2. nn.Linear in python

  3. Should ReLU/sigmoid be called in the init method?

  4. 파이토치 튜토리얼

  5. 신경망의 레이어 갯수에 대하여

  6. Any different between model(input) and model.forward(input)

  7. call in Python


아래는 현재 검색 가능한 파이토치 딥러닝 책 광고입니다! 파트너스 활동을 통해서 일정액의 수수료를 받을 수 있다고 하네요.ㅎㅎ 언젠가 제가 쓰는 이 블로그도 저 자리에 끼어있었으면 좋겠네요ㅎㅎ 밑에 책들은 한번 사서 읽어보고, 리뷰를 올려보도록 하겠습니다. =]

 

파이토치 첫걸음:딥러닝 기초부터 RNN 오토인코더 GAN 실전 기법까지, 한빛미디어 펭귄브로의 3분 딥러닝 파이토치맛:PyTorch 코드로 맛보는, 한빛미디어 딥러닝에 목마른 사람들을 위한 PyTorch:개인용 GPU 학습 서버 구축부터 딥러닝까지, 비제이퍼블릭 PyTorch로 시작하는 딥러닝:딥러닝 기초에서 최신 모던 아키텍처까지, 에이콘출판 [제이펍] 파이토치 첫걸음

반응형

댓글