Basic Deep Learning/Dive into Deep Learning 리뷰

[D2L] 4.1~ 3 Multilayer Perceptron

needmorecaffeine 2022. 7. 10. 16:22

[ 이론 ] 

이전 장에서 배운 linear transformation은 linearity라는 매우 강한 가정이 필요하다. 즉, 이 가정이 성립하지 않는다면 사용할 수 없다는 것. 실제 linearity = weaker monotonicity로 linearity를 가지는 두 변수는 완전 동일하지는 않지만 한 변수의 증감과 다른 변수의 증감에 동일한 규칙성이 있어야 한다. 하지만 이 가정은 당연 성립되는 경우가 더 적다.

이런 경우에는 task를 어떻게 수행해야 할지 궁금한데 그것에 대한 방법이 multiayer perceptron layer(MLP)이라고 말하고 있다.

 

1. Incorporating Hidden layers

좀 더 구체적으로 말하자면 하나 이상의 hidden layer를 사용하는 것이다. 각각의 layer가 다른 layer 다음에 쌓이며 첫번째 layer가 input에 대한 표현이고 마지막 layer가 linear predictor가 되는 것이다. 5개의 hidden unit을 가진 MLP를 도식화하면 다음과 같다.

2. From Linear to NonLinear

linearity는 그 성립 자체가 어렵다고 말했는데 그렇다면 이를 nonlinear하게 어떻게 바꿀 수 있을까?

가장 기본적인 구조이다.  input X가  hidden layer의 연산 결과가 다시 output으로 최종 변환된다. 이런 affine function은 몇개의 layer를 거치든 계속해서 affine function이다. 즉 아직 linearity를 가정하고 있다는 것이다.

그래서 사용하는 것이 activation function이다 . 아래의 시그마인데 이것의 연산을 activation이라고 한다. 이 activation이 추가되면서 MLP는 몇개의 layer를 지나든 계속 non linear하다.

 

3. Activation Function

activation funtion에 대해 더 자세히 알아보자.

 

3-1. ReLU

ReLU(x) = max(x,0)

가장 유명한 function이다. 실행하기 간단하고 성능 또한 좋다고 평가받아 예측 문제에서 자주 사용한다. 아래와 같이 매우 간단하게 nonlinear하게 변환시킨다.

x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True)
y = torch.relu(x)
d2l.plot(x.detach(), y.detach(), 'x', 'relu(x)', figsize=(5, 2.5))

지점별 미분도 확인해본다. 

y.backward(torch.ones_like(x), retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of relu', figsize=(5, 2.5))

 

3-2. Sigmoid function

sigmoid(x) = 1 / (1+exp(-x))

이 또한 자주 쓰인다. 특히 이진분류의 문제에서 사용되며 ReLU와 다르게 (0,1) 사이의 연속된 값으로 변환한다.

y = torch.sigmoid(x)
d2l.plot(x.detach(), y.detach(), 'x', 'sigmoid(x)', figsize=(5, 2.5))

# Clear out previous gradients
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of sigmoid', figsize=(5, 2.5))

3-3. Tanh Function

tanh(x) = (1-exp(-2x)) / (1+exp(-2x))

모양은 sigmoid와 매우 유사하지만 0인 지점에서 linear transformation이 된다.

y = torch.tanh(x)
d2l.plot(x.detach(), y.detach(), 'x', 'tanh(x)', figsize=(5, 2.5))

# Clear out previous gradients.
x.grad.data.zero_()
y.backward(torch.ones_like(x),retain_graph=True)
d2l.plot(x.detach(), x.grad, 'x', 'grad of tanh', figsize=(5, 2.5))


[ 실습 - 막코드 ]

이전 장의 sofmax regression과 크게 다를 것이 없다. 하나 다른 것은 이전 regression은 fully connected layer 하나로만 구성되었다면 MLP 실습에서는 두개의 layer로 구성해보고자 한다.

 

import torch
from torch import nn
from d2l import torch as d2l

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

num_inputs, num_outputs, num_hiddens = 784, 10, 256

# Parameter Initialization, 2 Layers
# torch.randn : tensor filled with random numbers form a standard normal distribution

W1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad= True)*0.01)
b1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad = True))
W2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad= True)*0.01)
b2 = nn.Parameter(torch.zeros(num_outputs, requires_grad = True))

params = [W1, b1, W2, b2]
# Activation Function : ReLU
def relu(X):
  # zeros_like : tensor filled woth the scalar value 0 with the same size as input
  a = torch.zeros_like(X) 
  return torch.max(X,a)

# Model
def net(X):
  X = X.reshape((-1, num_inputs))
  H = relu(X@W1 + b1) # @ : matrix multiplication
  return (H@W2 + b2)

# Loss function
loss = nn.CrossEntropyLoss(reduction = "none")
# Training

num_epochs, lr = 10, 0.1
updater = torch.optim.SGD(params, lr = lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, updater)

잘 몰랐던 torch 연산은 주석으로 설명을 달았다. 이전 장의 막코드와 크게 다른 것이 없지만 net의 구조를 보면 activation function과 함께 layer가 하나 더 추가되어 있음을 알 수 있다.

 

이렇게 훈련한 결과와 실제 예측 예제를 확인해보면 다음과 같다.


[ 실습 - API ]

위 과정을 더 간단하게 구현해보자. softmax regression과 training 과정이 동일하기에 이 때 사용한 training 함수를 그대로 사용한다.

net = nn.Sequential(nn.Flatten(),
                    nn.Linear(784, 256),
                    nn.ReLU(),
                    nn.Linear(256, 10))

def init_weights(m):
  if type(m) == nn.Linear:
    ## nn.init.normal_ : fills the inut Tensor with values drawn form the normal distribution
    nn.init.normal_(m.weight, std = 0.01)

net.apply(init_weights)
batch_size, lr, num_epochs = 256, 0.1, 10
loss = nn.CrossEntropyLoss(reduction = 'none')
trainer = torch.optim.SGD(net.parameters(), lr = lr)

train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)