심층 신경망의 구조

심층 신경망의 구조

Neuron

shallow NN

심층 신경망(Deep Neural Network)

심층신경망은 무엇이 다를까?

  • 은닉 계층 추가 = 특징의 비선형 변환 추가!!
    선형 변환의 이해

  • 선형대수의 선형 변환을 함수의 개념에서 보았을때, 입력 차원(n)이 출력 차원(m)보다 크다면 Onto(전사함수: 모든 공역이 치역이 되있는 상태)가 될 수 있지만, 그 반대인 경우는 적은 차원을 갖는 입력벡터의 차원으로 일부분의 출력 벡터의 차원을 커버하는 것이 되는 것이므로, 전사함수가 될 수 없다. 또한, 이런 입력차원(n)이 출력 차원(m)보다 작은 경우의 구조를 우리는 딥러닝 네트워크 구조에서도 볼 수 있다. 예를 들면 GAN이나 Auto Encoder의 decoder구조가 가장 쉬운 예시일 것이다. 여기서의 의문은 그렇다면, 일부분의 차원으로 피처를 잘 배울 수 있는지가 의문일 것이다. 허나, 그 일부의 차원이 원래 갖고 있던 특성에서 나올법한 특성들만을 생성해 주므로 걱정하지 않아도 된다.

  • 또한, 선형시스템의 곱으로 노드들의 연산을 표현할 수 있는데, 여기서, 예를 들어, 특징벡터1과 특징벡터2간의 방향이 비슷한 즉, Orthogonal하지 않고 방향이 비슷한 벡터를 통해 연산을 진행하면 다음 층에서는 노드들 중에 비슷한 특징에 대한 정보를 포함하고 있을 것이다. Inner product를 projection의 개념에서 살펴보면, 어떠한 벡터가 다른 방향의 벡터에 projection을 하는 것은 그 projection한 벡터가 그 방향의 벡터가 어느 정도의 성분을 가지고 있는지를 의미하므로 선형대수 측면에서 위에서 각 피처들간의 곱의 연산들에 의한 새로운 피처들의 생성은 projection된 길이를 비교하는 행위와 동일할 것이다.

역전파 학습법의 개념

알고리즘의 학습과 미분

의존성이 있는 함수의 계산

  • y를 구하려면 x와 z를 알아야 하는데, x와 z에는 중복된 연산이 있어서 비효율적이다.

동적 계획법(Dynamic Programming)

  • 처음 계산할 때 값을 저장해주어서 중복계산이 발생하지 않도록 해준다.

Chain rule

심층 신경망의 미분(출력계층)

심층 신경망의 미분(은닉계층1)

심층 신경망의 미분(은닉계층2)

순방향 전파(Forward Propagation)

  • 학습을 마친 후 validation set이나 test set에 적용할 때는 더 이상 학습을 하지 않으므로 이 순방향 추론만을 사용한다.

역전파 학습법(Back-Propagation)

심층 신경망의 수학적 이해

전결합 계층

심층 신경망

역전파 학습의 필요성

블랙박스 모델

블랙박스 모델의 학습

수치적 기울기(Numerical Gradient

블랙박스 모델의 수치적 기울기

  • (N+1번) 손실함수를 평가한다고 하는데 그 이유는 기준점이 되는 손실함수를 먼저 한번 계산하고 나머지 편미분시에 가각 N번 평가하기 때문이다.

심층 신경망의 수치적 기울기

합성함수와 연쇄 법칙

연쇄 법칙

직렬 연결된 두 함수의 미분

미분과 연쇄 법칙

연쇄법칙의 확장

역전파 학습법의 수식적 이해

합성 함수로서의 심층 신경망

학습관점에서 본 심층 신경망

심층신경망의 연쇄법칙

  • 미분하고자 하는 경로 사이에 있는 모든 미분값을 알아야 원하는 미분을 구할 수 있다는 의미이다.

전결합 계층의 미분(1)

전결합 계층의 미분(2)

Sigmoid 함수의 미분

역전파 알고리즘

  • 수치적 미분에서는 N+1번을 계산하여야 했지만, 역전파 알고리즘으로 인해 단 한번의 손실함수 평가로 미분을 구할 수 있다.

수치 미분을 이용한 심층 신경망 학습

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
## 수치 미분을 이용한 심층 신경망 학습
import time
import numpy as np

## 유틸리티 함수
epsilon = 0.0001

def _t(x):
return np.transpose(x)

def _m(A, B):
return np.matmul(A, B)

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

def mean_squared_error(h, y):
return 1 / 2 * np.mean(np.square(h - y))

## 뉴런 구현
class Neuron:
def __init__(self, W, b, a):
self.W = W
self.b = b
self.a = a

# Gradient
self.dW = np.zeros_like(self.W)
self.db = np.zeros_like(self.b)

def __call__(self, x):
return self.a(_m(_t(self.W), x) + self.b) # activation((W^T)x + b)

## 심층신경망 구현
class DNN:
"""
hidden_depth : hidden_layer의 갯수
num_neuron : hidden_layer 하나당 neuron의 갯수
num_input : input_layer의 neuron의 갯수
num_output : output_layer의 neuron의 갯수
activation : activation funtion으로 사용할 함수
"""
def __init__(self, hidden_depth, num_neuron, num_input, num_output, activation=sigmoid):
# W, b initialize
def init_var(i, o):
return np.random.normal(0.0, 0.01, (i, o)), np.zeros((o,))

self.sequence = list()
# First hidden layer
W, b = init_var(num_input, num_neuron)
self.sequence.append(Neuron(W, b, activation))

# Hidden layers
for _ in range(hidden_depth - 1):
W, b = init_var(num_neuron, num_neuron)
self.sequence.append(Neuron(W, b, activation))

# Output layer
# 단순히 심층신경망 구현 후에 수치미분을 사용한 역전파학습을 보이기 위한 코드이므로
# Output layer의 activation function을 따로 바꾸지 않고 sigmoid로 사용하겠다.
W, b = init_var(num_neuron, num_output)
self.sequence.append(Neuron(W, b, activation))

def __call__(self, x):
# layer를 call하는 것은 결국 위에서 정의한 Neuron의 call이 될 것이고
# x는 activation((W^T)x + b)이 될 것이다.
for layer in self.sequence:
x = layer(x)
return x

def calc_gradient(self, x, y, loss_func):
def get_new_sequence(layer_index, new_neuron):
# 특정한 변수하나(weight나 bias)만 변화를 줘서 그 때 loss가 얼마나 변하는지를 보고
# numerical gradient를 계산하려하기 때문에 변화된 변수가 있는 새로운 Sequence가 필요하다.
new_sequence = list()
for i, layer in enumerate(self.sequence):
if i == layer_index:
new_sequence.append(new_neuron)
else:
new_sequence.append(layer)
return new_sequence

def eval_sequence(x, sequence):
for layer in sequence:
x = layer(x)
return x

loss = loss_func(self(x), y)

for layer_id, layer in enumerate(self.sequence): # iterate layer
for w_i, w in enumerate(layer.W): # iterate W (row)
for w_j, ww in enumerate(w): # iterate W (col)
W = np.copy(layer.W)
W[w_i][w_j] = ww + epsilon

new_neuron = Neuron(W, layer.b, layer.a)
new_seq = get_new_sequence(layer_id, new_neuron)
h = eval_sequence(x, new_seq)

num_grad = (loss_func(h, y) - loss) / epsilon # (f(x+eps) - f(x)) / epsilon
layer.dW[w_i][w_j] = num_grad

for b_i, bb in enumerate(layer.b): # iterate b
b = np.copy(layer.b)
b[b_i] = bb + epsilon

new_neuron = Neuron(layer.W, b, layer.a)
new_seq = get_new_sequence(layer_id, new_neuron)
h = eval_sequence(x, new_seq)

num_grad = (loss_func(h, y) - loss) / epsilon # (f(x+eps) - f(x)) / epsilon
layer.db[b_i] = num_grad
# gradient를 계산할 때 loss를 return해야 학습과정에 loss가 어떻게 되는지를 알 수 있기때문에 return 해준다.
return loss

## 경사하강법
def gradient_descent(network, x, y, loss_obj, alpha=0.01):
loss = network.calc_gradient(x, y, loss_obj)
for layer in network.sequence:
layer.W += -alpha * layer.dW
layer.b += -alpha * layer.db
return loss

## 동작 테스트
x = np.random.normal(0.0, 1.0, (10,))
y = np.random.normal(0.0, 1.0, (2,))

dnn = DNN(hidden_depth=5, num_neuron=32, num_input=10, num_output=2, activation=sigmoid)

t = time.time()
for epoch in range(100):
loss = gradient_descent(dnn, x, y, mean_squared_error, 0.01)
print('Epoch {}: Test loss {}'.format(epoch, loss))
print('{} seconds elapsed.'.format(time.time() - t))

역전파 알고리즘을 이용한 심층 신경망 학습

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
## 역전파 학습을 이용한 심층 신경망 학습
import time
import numpy as np

## 유틸리티 함수
def _t(x):
return np.transpose(x)

def _m(A, B):
return np.matmul(A, B)

## Sigmoid 구현
class Sigmoid:
def __init__(self):
# 곱의 형태로 나오게 되므로 처음에 1로해서 추후에 입력될 수치에 영향을 덜 주게 해준다.
self.last_o = 1

def __call__(self, x):
self.last_o = 1 / (1.0 + np.exp(-x))
return self.last_o

def grad(self):
# sigmoid(x) * (1- sigmoid(x))
return self.last_o*(1-self.last_o)

## Mean Squared Error 구현
class MeanSquaredError:
def __init__(self):
# chain rule을 할 때 MSE로 부터 gradient를 계속해서 가져와야하므로 저장해놓기 위해
self.dh = 1
self.last_diff = 1

def __call__(self, h, y): # 1/2 * mean((h - y)^2)
self.last_diff = h - y
return 1 / 2 * np.mean(np.square(h - y))

def grad(self): # h - y
return self.last_diff

## 뉴런 구현
class Neuron:
def __init__(self, W, b, a_obj):
self.W = W
self.b = b
# activation이 이전과 다르게 class로 작성되었으므로 instanctiation을 해주어야한다.
self.a = a_obj()

# gradient
self.dW = np.zeros_like(self.W)
self.db = np.zeros_like(self.b)
self.dh = np.zeros_like(_t(self.W))

## 아래의 grad_W를 위해 저장해놓는다.
## W로 미분했을 경우 이전 입력을 갖고 있어야 바로 사용할 수 있으므로
self.last_x = np.zeros((self.W.shape[0]))
self.last_h = np.zeros((self.W.shape[1]))

def __call__(self, x):
self.last_x = x
self.last_h = _m(_t(self.W), x) + self.b
return self.a(self.last_h)

def grad(self): # dy/dh = W
return self.W * self.a.grad()

def grad_W(self, dh):
grad = np.ones_like(self.W)
grad_a = self.a.grad()
for j in range(grad.shape[1]): # dy/dw = x
grad[:, j] = dh[j] * grad_a[j] * self.last_x
return grad

def grad_b(self, dh): # dy/db = 1
return dh * self.a.grad() * 1

## 심층신경망 구현
class DNN:
def __init__(self, hidden_depth, num_neuron, input, output, activation=Sigmoid):
def init_var(i, o):
return np.random.normal(0.0, 0.01, (i, o)), np.zeros((o,))

self.sequence = list()
# First hidden layer
W, b = init_var(input, num_neuron)
self.sequence.append(Neuron(W, b, activation))

# Hidden Layers
for index in range(hidden_depth):
W, b = init_var(num_neuron, num_neuron)
self.sequence.append(Neuron(W, b, activation))

# Output Layer
W, b = init_var(num_neuron, output)
self.sequence.append(Neuron(W, b, activation))

def __call__(self, x):
for layer in self.sequence:
x = layer(x)
return x

def calc_gradient(self, loss_obj):
loss_obj.dh = loss_obj.grad()
# for문에서 한번에 처리하기 위해서 loss object를 넣어준다.
self.sequence.append(loss_obj)

# back_propagation loop
for i in range(len(self.sequence) -1, 0 , -1):
l1 = self.sequence[i]
l0 = self.sequence[i - 1]

l0.dh = _m(l0.grad(), l1.dh)
l0.dw = l0.grad_W(l1.dh)
l0.db = l0.grad_b(l1.dh)

# loss object가 들어 있으면 출력을 얻지 못하고 loss 만 얻게 될 것이기 때문이다.
self.sequence.remove(loss_obj)

## 경사하강 학습법
def gradient_descent(network, x, y, loss_obj, alpha=0.01):
loss = loss_obj(network(x), y) # Forward inference
network.calc_gradient(loss_obj) # Back-propagation
for layer in network.sequence:
layer.W += -alpha * layer.dW
layer.b += -alpha * layer.db
return loss

## 동작 테스트
x = np.random.normal(0.0, 1.0, (10,))
y = np.random.normal(0.0, 1.0, (2,))

t = time.time()
dnn = DNN(hidden_depth=5, num_neuron=32, input=10, output=2, activation=Sigmoid)
loss_obj = MeanSquaredError()
for epoch in range(100):
loss = gradient_descent(dnn, x, y, loss_obj, alpha=0.01)
print('Epoch {}: Test loss {}'.format(epoch, loss))
print('{} seconds elapsed.'.format(time.time() - t))