통계, IT, AI

딥러닝: 화풍을 모방하기 (6) - 연습: 다클래스 분류 문제 본문

머신러닝

딥러닝: 화풍을 모방하기 (6) - 연습: 다클래스 분류 문제

Harold_Finch 2017. 2. 2. 00:13

1. 개요

- 입력이 4개이며 출력은 3가지 클래스를 갖는 경우를 생각해보자. 신경망은 그림 1과 같다.

그림 1. 3개 클래스의 출력을 갖는 단층 신경망

- 입력과 파라미터를 아래와 같이 정의한다.

$$\begin{eqnarray} x_n&=& \begin{bmatrix}x_{n1} & x_{n2} & x_{n3}& x_{n4} \end{bmatrix}, \quad w=\begin{bmatrix}w_{11} & w_{21} & w_{31} \\ w_{12} & w_{22} & w_{32} \\ w_{13} & w_{23} & w_{33} \\ w_{14} & w_{24} & w_{34} \end{bmatrix}, \quad b=\begin{bmatrix}b_{1} & b_{2} & b_{3} \end{bmatrix} \\ \\ u&=&\begin{bmatrix} u_1 & u_2 & u_3\end{bmatrix} = x_nw+b \\ \\&=& \begin{bmatrix} \sum_{i=1}^{4}w_{1i}x_{ni} + b_1 & \sum_{i=1}^{4}w_{2i}x_{ni} + b_2 & \sum_{i=1}^{4}w_{2i}x_{ni} + b_2\end{bmatrix} \end{eqnarray}$$


-출력의 활성화 함수로 softmax 함수를 사용한다.

$$\begin{eqnarray} z&=&\begin{bmatrix} z_1 & z_2 & z_3 \end{bmatrix} = f \left(\begin{bmatrix} u_1 & u_2 & u_3\end{bmatrix}\right) \\ \\ &=& \begin{bmatrix} \frac{exp(u_1)}{\sum_{k=1}^{K}exp(u_k)}& \frac{exp(u_2)}{\sum_{k=1}^{K}exp(u_k)}&\frac{exp(u_3)}{\sum_{k=1}^{K}exp(u_k)} \end{bmatrix}\end{eqnarray}$$


-오차함수로 교차엔트로피를 사용하자. \(n\)번째 자료에 대한 오차는 다음과 같다. 단, \(d_{nk}\)는 \(n\)번째 입력의 클래스가 \(k\)이면 1 그렇지 않으면 0의 값을 갖는다.

$$E_n(\boldsymbol{w})=-\sum_{k=1}^{K}d_{nk}log\left\{ y_k(x_n;\boldsymbol{w})\right\}$$


- \(w_{ji}\)에 대한 미분이 아래와 같다. 간결성을 위하여 \(y_k(x_n;\boldsymbol{w})\)는 \(y_k\)로 표기한다.

$$\begin{eqnarray}\frac{\partial E_n(\boldsymbol{w})}{\partial w_{ji}}&=&-\sum_{k=1}^{K}\frac{d_{nk}}{y_k}\frac{\partial y_k}{\partial w_{ji}} \\&=&-\frac{d_{nj}}{y_j}\frac{\partial y_j}{\partial w_{ji}}-\sum_{k\neq j}\frac{d_{nk}}{y_k}\frac{\partial y_k}{\partial w_{ji}} \\&=&-d_{nj}(1-y_j)x_{ni}+\sum_{k\neq j} d_{nk} y_jx_{ni} \\&=&(y_j-d_{nj})x_{ni} \end{eqnarray}$$


- 따라서 오차함수의 파라미터에 대한 미분을 다음과 같이 정의한다.

$$\begin{eqnarray} \frac{\partial E_n(\boldsymbol{w})}{\partial \boldsymbol{w}}&=& \left[ \frac{\partial E_n(\boldsymbol{w})}{\partial w_{ji}}\right]_{j,i} =\left[ (y_j-d_{nj})x_{ni}\right]_{j,i} \\ \frac{\partial E_n(\boldsymbol{w})}{\partial \boldsymbol{b}}&=& \left[ \frac{\partial E_n(\boldsymbol{w})}{\partial b_{j}}\right]_{j} =\left[ y_j-d_{nj}\right]_{j} \end{eqnarray}$$


- 데이터는 Iris dataset을 사용한다. Iris dataset은 피셔가 제시한 다변수자료로 독립변수는 꽃받침의 길이, 꽃받침의 너비, 꽃잎의 길이 그리고 꽃잎의 너비이다. 종속변수는 꽃의 종류로 총 3가지로 분류가 있다. 자료의 수는 150개이며 해당 자료를 첨부한다.iris.csv


- 미니배치의 크기는 30, 반복횟수는 20000 그리고 \(\epsilon=0.001\)으로 한다. 테스트 데이터는 임의로 30개를 선정한다. 최종 결과에서 클래스 결정을 위한 threshold로 0.5를 사용한다.


2. 구현

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import csv
import datetime as dt


""" GENERAL SETTING """
np.random.seed(0)
enable_visualization = True


""" DATA """
with open('iris.csv', 'r') as f:
    dat = [row for row in csv.reader(f.readlines())]
    print('COLUMN: ', dat.pop(0))

    temp_x, temp_d = [], []
    for row in dat:
        temp_d.append(row.pop())
        temp_x.append(row)
    X = np.asmatrix(temp_x, dtype=float)
    N, p = X.shape

    levels = list(set(temp_d))
    K = len(levels)
    d = np.zeros((N, K), dtype=float)
    for row_num, level in enumerate(temp_d):
        d[row_num,levels.index(level)] = 1.0

    print('N: ', N, ', p: ', p, ', K: ', K)


"""  FUNCTION """
def activate(u_list):
    exp_u = np.exp(u_list)
    return np.diag((1/np.sum(exp_u, axis=1)).A1).dot(exp_u)

def feedforward(X, w, b, activate_function=activate):
    X_ = np.column_stack((np.zeros(X.shape[0], dtype=float), X))
    w_ = np.vstack((b, w))
    return activate_function(X_.dot(w_))

def error(X, w, b, d, feedforward_function=feedforward):
    return -np.sum(np.multiply(d, np.log(feedforward_function(X, w, b))))


""" PARAMETER & OPTIMIZATION SETTING """
w = np.zeros((p, K), dtype=float)
b = np.zeros((1, K), dtype=float)
epsilon = 0.001
batch_size = 30
epoch_size = 20000

index_list = np.arange(N)
np.random.shuffle(index_list)

test_x = X[index_list[0:30],]
test_d = d[index_list[0:30],]
error_history = [error(test_x, w, b, test_d)]


""" OPTIMIZATION """
for epoch in range(epoch_size):
    np.random.shuffle(index_list)
    if epoch % 5000 == 0:
        print('epoch: ', epoch, ', ', dt.datetime.now())

    for i in range(N//batch_size):
        selected_index_list = index_list[(i*batch_size):((i+1)*batch_size)]
        training_x, training_d = X[selected_index_list,], d[selected_index_list,]

        # Caculate nabla E
        der_w = training_x.T.dot(feedforward(training_x, w, b) - training_d)/batch_size
        der_b = np.mean(feedforward(training_x, w, b), axis=0)

        w -= epsilon * der_w
        b -= epsilon * der_b

    error_history.append(error(test_x, w, b, test_d))


""" RESULT """
print('w: ', w)
print('b: ', b)

if enable_visualization:
    plt.plot(error_history)
    plt.show()

result = np.asmatrix(feedforward(X, w, b) > 0.5, dtype=float)
error_rate = np.sum(np.array(result != d, dtype=int))//2.0 / N
print(error_rate)


3. 결과

- 전체 데이터에 대한 오분류율은 약 3%이며, 테스트 오차의 경향은 그림 2와 같다.

그림 2. 테스트 오차의 경향

- 속도에 대한 이슈가 있다. 총 연산에 약 16초가 걸리는데 생각보다 느리다. 텐서플로우 사용을 고려하자. 

- 지금까지는 파라미터의 초기값으로 0을 주었다. 하지만 언젠가 이 부분에 이슈가 있을 것이다.

- 학습률 \(\epsilon\)에 대한 조정 및 여러가지 규제화 또한 실습할 기회를 만들어보자.


- http://neuralnetworksanddeeplearning.com/에서 다음과 같은 내용을 보았다. 즉, classification을 진행할 때 출력층의 노드의 수가 왜 class의 수와 같아야 하는 것이다. 위의 예에서, 구분하고자 하는 꽃의 종류는 3가지이므로 이진수의 두개의 노드로도 충분하다. 첫번째 꽃은 \((0,0)\), 두번째 꽃은 \((0,1)\) 그리고 세번째 꽃은 \((1,0)\)으로 말이다.


- 하지만 이 방법은 성과가 그다지 좋지 않다. 왜냐하면 출력층의 각 원소가 무엇을 의미하는지 확실하지 않기 때문이다. 다른 예를 들면, 0부터 9까지의 숫자의 이미지가 있을 때 이를 분류하는 문제를 생각해보자. 이 경우 \(9=(1,0,0,1)\)이므로 출력층의 노드의 수는 4개면 충분하다. 하지만 출력층의 노드의 각 원소가 의미하는 것은 무엇인가? 마지막 원소가 0이면 짝수, 1이면 홀수지만 그것과 이미지와는 아무런 연관성이 없다. 따라서 출력층의 노드의 수는 일반적으로 class의 수와 같게 하는 것이 좋다.


- 또한, 어째서 classfication의 에러 함수로 "오분류의 개수"로 하지 않는지에 대하여 보았다. 저자는 이에 대하여 신경망이 학습을 거듭해 가면서 개선되는 것을 연속형으로 나타내어 이전 학습과 비교하기 위함이라고 설명하였다.

Comments