10. 파이토치(PyTorch) 튜토리얼 - 신경망(Neural Networks)

Yeonghyeon·2022년 8월 3일
0
post-custom-banner

본 포스팅은 파이토치(PYTORCH) 한국어 튜토리얼을 참고하여 공부하고 정리한 글임을 밝힙니다.


신경망(Neural Networks)

  • torch.nn: 모델을 정의하고 미분하는데 autograd를 사용
  • nn.Module: layer와 output을 반환하는 forward(input) 메서드를 포함하고 있음

[예제] 숫자 이미지 분류하는 신경망

convnet

  • 간단한 순전파 네트워크(Feed-forward network)
  • 입력을 받아 여러 계층에 차례로 전달한 후, 최종 출력 제공

신경망의 일반적인 학습 과정

  • 학습 가능한 매개변수(또는 가중치)를 갖는 신경망 정의
  • 데이터셋 입력 반복
  • 입력을 신경망에서 전파
  • 손실 계산
  • gradient를 신경망의 매개변수들에 역으로 전파
  • 신경망의 가중치 갱신
    • 새로운 가중치(weight) = 가중치(weight) - 학습률(learnin rate) * 변화도(gradient)

신경망 정의하기

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개, 출력 채널 6개, 5x5의 정사각 컨볼루션 행렬
      
      # 컨볼루션 정의
      self.conv1 = nn.Conv2d(1, 6, 5)
      self.conv2 = nn.Conv2d(6, 16, 5)
      # affine 연산: y = Wx + b
      self.fc1 = nn.Linear(16 * 5 * 5, 120) # 5*5: 입력 이미지 차원에 해당
      self.fc2 = nn.Linear(120, 84)
      self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # (2, 2) 크기 윈도우에 대해 max pooling
        x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
        # 크기가 제곱수라면 하나의 숫자만을 특정
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = torch.flatten(x, 1) # 배치 차원을 제외한 모든 차원을 하나로 평탄화
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()
print(net)

Out:

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 함수만 정의하고 나면 gradient를 계산하는 backward 함수는 autograd를 사용하여 자동으로 정의됨
  • 모델의 학습 가능한 매개변수들은 nn.parameters()
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1의 .weight

Out:

10
torch.Size([6, 1, 5, 5])
  • 임의의 32x32 입력값을 넣어보자
  • 이 신경망(LeNet)의 예상되는 입력 크기는 32x32로 MNIST 데이터셋을 사용하기 위해서는 데이터셋의 이미지 크기를 32x32로 변경해야 함
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

Out:

tensor([[ 0.0127, -0.0771, -0.0574, -0.0194, -0.1014, -0.0354, -0.0273, -0.0127,
         -0.1412, -0.0076]], grad_fn=<AddmmBackward0>)
  • 모든 매개변수의 변화도 버퍼(gradient buffer)를 0으로 설정하고, 무작위 값으로 역전파
net.zero_grad()
out.backward(torch.randn(1, 10))

Note:

  • torch.nn은 미니배치만 지원 ➡️ torch.nn 패키지 전체는 하나의 샘플이 아닌, 샘플들의 미니 배치만을 입력
  • ex) nnConv2D: nSamples x nChannels x Height x Width 의 4차원 Tensor를 입력으로 함
  • 만약 하나의 샘플이 있다면 input.unsqueeze(0)를 사용해서 가상의 차원을 추가

지금까지의 요약

torch.Tensor - backward() 같은 autograd 연산을 지원하는 다차원 배열이고, 또한 tensor에 대한 gradient 갖고 있음

  • nn.Module - 신경망 모듈. 매개변수를 캡슐화(encapsulation)하는 간편한 방법으로, GPU로 이동, 내보내기(exporting), 불러오기(loading) 등의 작업을 위한 헬퍼(helper) 제공

  • nn.Parameter - Tensor의 한 종류로, Module에 속성으로 할당될 때 자동으로 매개변수로 등록됨

  • autograd.Function - autograd 연산의 순방향과 역방향 정의를 구현함. 모든 Tensor 연산은 하나 이상의 Function 노드를 생성하며, 각 노드는 Tensor 를 생성하고 이력(history)을 인코딩 하는 함수들과 연결하고 있음

지금까지 다룬 것

  • 신경망 정의
  • 입력 처리 후 backward 호출

더 살펴볼 내용

  • 손실 계산
  • 신경망 가중치 갱신

손실 함수 (Loss Funciton)

  • 손실 함수: (output, target)을 한 쌍(pair)의 입력으로 받아, 출력(output)이 정답(target)으로부터 얼마나 멀리 떨어져있는지 추정하는 값을 계산
  • 간단한 손실 함수로는 출력과 대상간의 평균제곱오차(mean-squared error)를 계산하는 nn.MSEloss

[예시]

output = net(input)
target = torch.randn(10) # 예시를 위한 임의의 정답
target = target.view(1, -1) # 출력과 같은 shape로 만듦
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

Out:

tensor(1.0336, grad_fn=<MseLossBackward0>)
  • .grad_fn 속성을 사용하여 loss를 역방향에서 따라가다보면, 아래와 같은 모습의 연산 그래프 볼 수 있음
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> flatten -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss
  • 따라서 loss.backward()를 실행할 때, 전체 그래프는 신경망의 매개변수에 대해 미분되며, 그래프 내의 requires_grad=True 인 모든 Tensor는 gradient가 누적된 .grad Tensor를 갖게 됩니다

설명을 위한 역전파의 몇 단계 살펴보기

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

Out:

<MseLossBackward0 object at 0x7efdb1899bd0>
<AddmmBackward0 object at 0x7efdb1899690>
<AccumulateGrad object at 0x7efdb1899350>

역전파(Backprop)

  • 오차(error)를 역전파하기 위해서는 loss.backward() 만 해주면 됨

  • 기존에 계산된 변화도의 값을 누적 시키고 싶지 않다면 기존에 계산된 변화도를 0으로 만드는 작업이 필요함

  • loss.backward() 를 호출하여 역전파 전과 후에 conv1의 bias 변수의 변화도를 살펴보자

net.zero_grad() # 역전파 수행 전, 모든 매개변수의 변화도 버퍼를 0으로 만듦

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

loss.backward() # 역전파 수행

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

Out:

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0029,  0.0007,  0.0022, -0.0144,  0.0003,  0.0093])

가중치 갱신

  • 실제로 많이 사용되는 가장 단순한 갱신 규칙은 확률적 경사하강법(SGD; Stochastic Gradient Descent)
    • 새로운 가중치(weight) = 가중치(weight) - 학습률(learning rate) * 변화도(gradient)
learning_rate = 0.01
for f in net.parameters():
	f.data.sub_(f.grad.data * learning_rate)
  • torch.optim 패키지에 SGD, Nesterov-SGD, Adam, RMSProp 등과 같은 다양한 갱신 규칙 방법들이 모두 구현되어 있음
import torch.optim as optim

# optimizer 생성
optimizer = optim.SGD(net.parameters(), lr=0.01)

# 학습 과정
optimizer.zero_grad() # gradient 버퍼 0으로 설정
output = net(input)
loss = criterion(output, target)
loss.backward() # 역전파
optimizer.step() # 가중치 갱신

optimizer.zero_grad() 를 사용하여 수동으로 gradient 버퍼를 0으로 설정하는 것에 유의

post-custom-banner

0개의 댓글