Processing math: 100%

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

 

자, 드디어 Multi-Layer로 향할 차례입니다!

저번에 한번 구현한 바가 있던 MNIST 손글씨 인식 프로그램을 이번엔 Multi-Layer로 구현하도록 하겠습니다.

 

이번에도 이론 설명보단 코드 설명에 집중되어 있으니, 가벼운 마음으로 읽고 복붙하시면 되겠습니다.

 

 

 

import numpy as np
import pandas as pd
import Layers

# load data
train = pd.read_csv("../Data/MNIST_data/mnist_train.csv")
y_train = train["label"]
x_train = train.drop("label", 1)
x_train = x_train.values / x_train.values.max()
y_train = y_train.values

# y to one-hot
one_hot = np.zeros((y_train.shape[0], y_train.max() + 1))
one_hot[np.arange(y_train.shape[0]), y_train] = 1
y_train = one_hot

# initialize parameters
num_classes = y_train.shape[1]
hidden = 50

keys = ['w1', 'b1', 'sigmoid', 'w2', 'b2']
params = {}
params['w1'] = np.random.uniform(-1, 1, (x_train.shape[1], hidden))
params['b1'] = np.random.uniform(-1, 1, (hidden))
params['w2'] = np.random.uniform(-1, 1, (hidden, num_classes))
params['b2'] = np.random.uniform(-1, 1, (num_classes))

layers = {}
layers['w1'] = Layers.MulLayer(params['w1'])
layers['b1'] = Layers.AddLayer(params['b1'])
layers['sigmoid'] = Layers.SigmoidLayer()
layers['w2'] = Layers.MulLayer(params['w2'])
layers['b2'] = Layers.AddLayer(params['b2'])
lastlayer = Layers.SoftmaxLayer()

grads = {}

# initialize hyperparameters
learning_rate = 0.01
epochs = 10000
batch_size = 128

우선, 파라미터 초기화 및 데이터 불러오기 등은 지금까지와 아주, 매우, 비슷합니다.

바뀐 점으로는, 기존에 w, b로 사용했던 인자들 대신 params라는 dictionary를 활용해서 학습을 진행하게 됩니다.

w와 b값이 여러개가 될 수 있기 때문이죠.

그리고, Perceptron에서 사용되는 Hidden Layer의 개수를 "hidden"이라는 변수를 통해 선언했습니다.

이 값을 늘리게 되면 연산은 느려지지만 비교적 정확하게 학습이 될 가능성이 높고, 낮춘다면 연산은 빨라지지만 조금 부정확한 학습이 될 수 있겠죠.

 

또, layer 사이에 sigmoid로 activation function을 넣었고, 더 빠른 학습을 위해 mini-batch를 활용한 SGD를 사용하도록 하겠습니다.

 

이번에 새로 추가된 Layer들을 소개하면, 각각 "SigmoidLayer"과 "SoftmaxLayer"입니다.

Layers.py에 가서 새로 추가된 Layer들을 확인해 보도록 합시다.

 

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))


def softmax(a):
    C = np.max(a)
    exp_a = np.exp(a - C)
    if a.ndim == 1:
        sum_exp_a = np.sum(exp_a)
        y = exp_a / sum_exp_a
    else:
        sum_exp_a = np.sum(exp_a, 1)
        sum_exp_a = sum_exp_a.reshape(sum_exp_a.shape[0], 1)
        y = exp_a / sum_exp_a
    return y


def cross_entropy_loss(y, t):
    C = 1e-7
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + C)) / batch_size

 

일단 당연하게도, sigmoid, softmax, cross_entropy_loss 함수를 선언해야 합니다.

참고로, 위 코드는 저번 코드와 이어지므로, numpy 선언 등은 제외하도록 하겠습니다.)

(혹시라도 위 코드에 대한 설명이 보고 싶으시다면, 이 포스팅이 포스팅을 참고해 주세요.)

 

 

class SigmoidLayer:
    def __init__(self):
        self.out = None

    def forward(self, z):
        self.out = sigmoid(z)
        return self.out

    def backward(self, dout):
        return (1 - self.out) * self.out * dout

우선, 비교적 간단한 SigmoidLayer부터 확인해 봅시다.

당연하게도, forward 함수에는 들어온 값을 sigmoid 함수를 거쳐 return해 줍니다.

 

그리고, Logistic Regression 구현부에서도 설명했듯, Sigmoid 함수를 미분하면 pred(1pred)가 되므로, 위와 같은 코드가 나옵니다.

 

 

class SoftmaxLayer:
    def __init__(self):
        self.loss = None
        self.y = None
        self.t = None

    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_loss(self.y, self.t)
        return self.loss

    def backward(self):
        batch_size = self.t.shape[0]
        dx = (self.y - self.t) / batch_size
        return dx

다음으로, SoftmaxLayer입니다.

이 Layer에서는 Softmax함수 뿐만 아니라, Cross Entropy Loss까지 한번에 구현하였습니다.

 

그래서, forward 함수에서는 self.y에 softmax함수를 지나친 값, self.t에는 입력된 정답 라벨값을 넣어서, loss값을 구합니다.

 

그리고 backward함수에서는 저번 Softmax Classification 구현부에서 설명했던대로, y-t값을 뱉어냅니다.

 

사실 Layer 구현은 이론적인 부분이 없어서 그냥 설명을 빨리 넘기겠습니다. 혹시라도 이해가 안되신다면, 댓글로 남겨주시면 감사하겠습니다. 

 

 

for epoch in range(epochs):
    batch_mask = np.random.choice(x_train.shape[0], batch_size)
    x = x_train[batch_mask]
    y = y_train[batch_mask]

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

    loss = lastlayer.forward(x, y)

    if epoch % (epochs / 10) == 0:
        pred = Layers.softmax(x)
        print("ACC on epoch %d : " % epoch, (pred.argmax(1) == y.argmax(1)).mean())
        print("LOSS on epoch %d : " % epoch, loss)

    dout = lastlayer.backward()

    for key in reversed(keys):
        dout = layers[key].backward(dout)
        if key != 'sigmoid':
            grads[key] = layers[key].grad
            params[key] -= learning_rate * grads[key]

 

이제 본격적으로 학습을 시켜줍시다.

 

SGD를 사용한다고 했으니 batch_mask를 만들어서 계산해 주고, keys에 저장했던 key값을 토대로 지속적으로 forward를 해 나갑니다.

 

그렇게 forward를 다 한 뒤에, lastlayer (즉 softmax layer)에서 loss 값을 얻어냅니다.

 

그리고, 그 아래에서는 backward를 실행시켜 오차역전파법을 계산해 줍니다.

참고로, 아래 고드의 if key != 'sigmoid' 부분은 조금 예쁘진 않지만, 추후에 activation function을 구현하면서 같이 개선해 나갈 코드입니다.

 

# load data
test = pd.read_csv("../Data/MNIST_data/mnist_test.csv")
y_test = test["label"]
x_test = test.drop("label", 1)
x_test = x_test.values / x_test.values.max()
y_test = y_test.values

# y to one-hot
one_hot = np.zeros((y_test.shape[0], y_test.max() + 1))
one_hot[np.arange(y_test.shape[0]), y_test] = 1
y_test = one_hot

x = x_test
for key in keys:
    x = layers[key].forward(x)
pred = Layers.softmax(x)
print("ACC : ", (pred.argmax(1) == y_test.argmax(1)).mean())

 

이제 학습이 잘 되었는지, 마지막으로 test할 시간입니다!

test 코드는 pred값을 layers를 통해서 얻는 것 빼고는 저번 softmax classification과 동일합니다.

 

 

 

이렇게 Multi-Layer Softmax Classification을 직접 구현해 보았습니다.

저번에 구현했던 single-layer 보다 조금 더 좋은 성능을 내는 것을 확인하실 수 있을 것입니다.

한번 layer를 늘리거나, hidden의 크기를 늘리고 줄이면서 더욱 높은 성과를 낼 수 있을지 해 보는것도 재밌을 것입니다.

+ Recent posts