* 모든 코드는 제 깃허브 (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 * (1 - pred)$가 되므로, 위와 같은 코드가 나옵니다.

 

 

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