* 모든 코드는 제 깃허브 (cdjs1432/DeepLearningBasic: Deep Learning from scratch)에서 확인할 수 있습니다.

 

지금까지는 간단한 머신 러닝만 다뤘다면, 이제는 진짜 다중 계층의 딥 러닝을 다룰 시간입니다!

그 다중 계층을 향한 가장 첫 번째 순서는, 바로 Single-Layer Gradient Descent입니다.

직접 Layer를 구현해 보고, 제대로 학습이 되는지 보도록 합시다.

 

이번에는 이미 했던 계산들만 계속 나오니, 계산은 필요가 없고, 어떻게 구현하면 될지만 보도록 합시다.

 

 

import numpy as np
import Layers

train_x = np.random.uniform(-5, 5, (100, 10))
ans_w = np.random.uniform(-5, 5, (10, 1))
ans_b = 3
train_y = train_x.dot(ans_w) + ans_b
w = np.random.uniform(-5, 5, (10, 1))
b = np.random.uniform(-5, 5, (1, 1))
learning_rate = 0.001
epoch = 10000

우선 데이터는 저번 Multi-Variable Gradient Descent 부분과 동일하게 진행하도록 해 봅시다. (근본적으로 이번에 할 것이 그것과 똑같이 때문이죠.)

 

 

keys = ['w', 'b']
layers = {}
layers['w'] = Layers.MulLayer(w)
layers['b'] = Layers.AddLayer(b)
lastlayer = Layers.MSELayer(train_y)
grads = {}

 

이제 훈련할 때의 변수를 선언해 봅시다.

 

layers에는 당연히 layer들을 집어 넣을 것이고, keys에는 훈련의 코드를 조금 더 간결하게 하기 위한 'w','b'를 넣어 줍시다.

그러면 이제 Layers 함수에 가서 Layers 함수를 어떻게 구현할지 확인해 볼까요?

 

 

import numpy as np

class MulLayer:
    def __init__(self, w):
        self.x = None
        self.w = w
        self.grad = None

    def forward(self, x):
        self.x = x
        return x.dot(self.w)

    def backward(self, dout):
        self.grad = np.dot(self.x.T, dout)
        return np.dot(dout, self.w.T)


class AddLayer:
    def __init__(self, b):
        self.x = None
        self.b = b
        self.grad = None

    def forward(self, x):
        self.x = x
        return x + self.b

    def backward(self, dout):
        self.grad = dout.mean()
        return dout


class MSELayer:
    def __init__(self, y):
        self.x = None
        self.y = y
        self.loss = None

    def forward(self, x):
        self.x = x
        self.loss = np.square(x-self.y).mean()
        return self.loss

    def backward(self):
        return self.x - self.y

 

일단 지금까지의 Layer 모듈에는 이렇게 들어있습니다.

각각의 클래스 구조를 보시면, 레이어들은 __init__, forward, backward의 세 가지 함수로 이루어져 있습니다.

우선 __init__에는 해당 함수가 필요로 하는 값들을 지정하고, 선언해 줍니다.

가령 MulLayer같은 경우에는 w 파라미터를 init해주고, AddLayer같은 경우에는 b 파라미터는 init해줍니다.

 

forward는 순방향 계산입니다. 쉽게 말해, pred 값을 찾아가는 것입니다.

저번 Gradient Descent에서 pred = x.dot(w) + b 라는 식으로 prediction값을 구했다면, 이제는 각각의 layer에 대한 forward 연산으로 해당 값을 구할 것입니다.

 

backward는 역전파법 계산입니다. 이 연산을 통해 각각의 layer에 대한 gradient 값을 알아갈 수 있습니다.

 

그리고, 이 gradient 값들을 통해 인공지능 학습을 해야 하므로, 이 값들을 각각의 layer에 저장해 주어야 합니다. (grad 변수의 역할)

 

이제 클래스를 하나씩 순서대로 설명하도록 하겠습니다.

 

 

class MulLayer:
    def __init__(self, w):
        self.x = None
        self.w = w
        self.grad = None

    def forward(self, x):
        self.x = x
        return x.dot(self.w)

    def backward(self, dout):
        self.grad = np.dot(self.x.T, dout)
        return np.dot(dout, self.w.T)

 

MulLayer, 즉 곱연산 레이어를 먼저 설명하도록 하겠습니다.

이 Layer에서는 forward, 즉 순방향 연산 때는 input 값인 x에 대해 x*w (x.dot(w))의 값을 전달합니다.

그리고, x * w를 w에 대해 미분하면 x가 나오므로, gradient는 x가 됩니다.

그런데, 왜 x에 dout을 곱해주는 걸까요?

 

우리는 다중 레이어로 구성된 딥 러닝 모델을 만들 것이기 때문입니다.

나중에 전체 코드를 보시면 아시겠지만, 가장 첫 dout에서는 MSELayer, 즉 Loss 값에 대한 Layer의 pred값에 대한 미분값이 들어갑니다.

 

그리고, Chain Rule $ \frac{\partial z}{\partial x} = \frac{\partial z}{\partial y} \frac{\partial y}{\partial x} $ 에 의하여, 역전파법 계산을 하면서 dout을 계속 곱해주면 됩니다.

그렇기에, 위 코드에서도 backward 함수에서 x와 dout을 곱해준 값이 grad가 되는 것입니다.

 

 

 

class AddLayer:
    def __init__(self, b):
        self.x = None
        self.b = b
        self.grad = None

    def forward(self, x):
        self.x = x
        return x + self.b

    def backward(self, dout):
        self.grad = dout.mean()
        return dout

 

AddLayer도 위와 비슷합니다.

AddLayer, 즉 합에서는 b에 대해 미분하면 1이 나오므로, backward는 dout 그대로 나오는 것을 알 수 있습니다.

 

class MSELayer:
    def __init__(self, y):
        self.x = None
        self.y = y
        self.loss = None

    def forward(self, x):
        self.x = x
        self.loss = np.square(x-self.y).mean()
        return self.loss

    def backward(self):
        return self.x - self.y

 

다음은 MSELayer입니다.

MSE가 뭐지? 하시는 분이 있었을 것도 같습니다. 하지만, 사실 이미 MSE는 본 적이 있습니다.

Gradient Descent에서 $\frac{1}{m}(pred - y)^2$로 loss 값을 설정한 적이 있었죠?

이것을 바로 Mean Squared Error, 줄여서 MSE라 합니다.

그리고 이를 pred에 대해 미분하면 $pred - y$값이 나온다는 것은 이미 계산해 본 적이 있습니다.

 

따라서, forward에는 loss값의 계산 식이 들어가고, backward에는 $pred - y$, 즉 위 함수에선 x - y가 들어갑니다.

 

이렇게 Layer 의 함수에 대한 설명은 끝내고, 나머지 메인 코드 부분을 끝장내러 가봅시다.

 

for epoch in range(epoch):
    x = train_x
    y = train_y

    for key in keys:
        x = layers[key].forward(x)

    loss = lastlayer.forward(x)
    if epoch % 100 == 0:
        print("err : ", loss)

    dout = lastlayer.backward()

    for key in reversed(keys):
        dout = layers[key].backward(dout)
        grads[key] = layers[key].grad

    db = layers['b'].grad
    dw = layers['w'].grad

    w -= learning_rate * dw
    b -= learning_rate * db

 

아까 전에 위에서 언급했듯, keys를 사용하여 forward와 backward, 그리고 grad값 저장을 쉽게 할 수 있습니다.

사실 이번에는 w와 b가 하나씩밖에 나오지 않았기에 db와 dw의 초기화를 저렇게 수동으로 했지만, 저 dw와 db의 초기화도 for문 안에 넣을 수도 있습니다.

 

 

다음 시간에는, 이를 활용한 Multi-Layer Gradient Descent로 MNIST 데이터셋을 다시 한번 훈련시켜 보겠습니다.

 

 

 

+ Recent posts