통계, IT, AI

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

머신러닝

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

Harold_Finch 2017. 2. 2. 00:13

1. 개요

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

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

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

xn=[xn1xn2xn3xn4],w=[w11w21w31w12w22w32w13w23w33w14w24w34],b=[b1b2b3]u=[u1u2u3]=xnw+b=[i=14w1ixni+b1i=14w2ixni+b2i=14w2ixni+b2]


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

z=[z1z2z3]=f([u1u2u3])=[exp(u1)k=1Kexp(uk)exp(u2)k=1Kexp(uk)exp(u3)k=1Kexp(uk)]


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

En(w)=k=1Kdnklog{yk(xn;w)}


- wji에 대한 미분이 아래와 같다. 간결성을 위하여 yk(xn;w)yk로 표기한다.

En(w)wji=k=1Kdnkykykwji=dnjyjyjwjikjdnkykykwji=dnj(1yj)xni+kjdnkyjxni=(yjdnj)xni


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

En(w)w=[En(w)wji]j,i=[(yjdnj)xni]j,iEn(w)b=[En(w)bj]j=[yjdnj]j


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


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


2. 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# -*- 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을 주었다. 하지만 언젠가 이 부분에 이슈가 있을 것이다.

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


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


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


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