import numpy as np
np.set_printoptions(suppress=True)
x = np.array([1,1,4,5,6,8,9])
x
array([1, 1, 4, 5, 6, 8, 9])
# 『신경망 첫걸음, 2016』 에서는 이 방식으로 변환한다.
np.array(x, ndmin=2).T
array([[1], [1], [4], [5], [6], [8], [9]])
x.T
array([1, 1, 4, 5, 6, 8, 9])
x.shape[0]
7
# 그러나 reshape로 좀 더 우아하게 변환할 수 있다.
x = x.reshape(-1, 1)
x
array([[1], [1], [4], [5], [6], [8], [9]])
# 엉뚱한 답을 찾으라고 할 순 없으므로 미리 정답 행렬로 답을 만들어 둔다.
# w = np.array([[2,5,1,7,2,5,1], [1,4,5,1,2,4,1], [2,4,2,2,2,2,2]])
# 우리가 학습하고자 하는 가중치 행렬
def init_w():
w = np.array([[1,5,3,7,4,5,66], [2,4,23,1,2,1,1], [4,4,1,1,1,1,2]], dtype=np.float)
return w
w = init_w()
w
array([[ 1., 5., 3., 7., 4., 5., 66.], [ 2., 4., 23., 1., 2., 1., 1.], [ 4., 4., 1., 1., 1., 1., 2.]])
# (3, 7)과 (7, 1) 처럼 첫 번째 matrix의 열과 두 번째 matrix의 행이 일치해야 점곱(dot product)이 가능하다.
# 따라서 np.dot(x, w)는 성립하지 않으며 w, x 로 계산해야 한다.
y = np.dot(w, x)
y
array([[711.], [132.], [ 49.]])
# 정답
t = np.array([[107],[83],[70]])
t
array([[107], [ 83], [ 70]])
y - t # d(1/2 * E)/d(y)
array([[604.], [ 49.], [-21.]])
$\frac{\partial(E)}{\partial(w_{kj})} = \frac{\partial}{\partial(w_{kj})} \sum(t_n - y_n) ^ 2$
결과 $y_n$은 연결되는 노드로부터만 영향을 받는다.
다시 말해, 노드 $k$의 결과 값인 $y_k$는 그와 연결되는 가중치 $w_{kj}$에 의해서만 영향을 받는다.
따라서 $w_{kj}$의 연결 노드인 $y_k$ 외에 모든 $y_n$을 제거할 수 있다. 이제 sum을 제거할 수 있다.
$\frac{\partial(E)}{\partial(w_{kj})} = \frac{\partial}{\partial(w_{kj})} (t_k - y_k) ^ 2$
$\frac{\partial(E)}{\partial(w_{kj})} = \frac{\partial(E)}{\partial(y_k)} \frac{\partial(y_k)}{\partial(w_{kj})} $
두 수식을 따로 떼내면 아래와 같다.
$\frac{\partial(E)}{\partial(y_k)} = -2 (t_k - y_k)$
$\frac{\partial(y_k)}{\partial(w_{kj})} = \frac{\partial}{\partial{w_{kj}}} \sum(w_{kn} x_n)$
마찬가지로 $y_k$는 이와 연결되는 $w_{kj}$에 의해서만 영향을 받는다.
따라서 $w_{kj}$의 연결 노드인 $w_{kj} x_j$ 에만 영향을 주므로 sum을 제거할 수 있다.
$\frac{\partial(w_{kj}x_j)}{\partial(w_{jk})} = x_j$
미분 계산을 쉽게 하기 위해 1/2 * SE를 취한다.
$\frac{\partial(\frac{1}{2} (t_k - y_k) ^ 2)}{\partial(y_k)} = y_k - t_k$
모두 정리하면 아래와 같은 깔끔한 수식이 된다.
$(y_k - t_k) x_j$
l = 0.001 # learning rate, 0.01로 했을 경우 overshooting이 발생했다.
for k in range(3):
for j in range(7):
delta_w = (y[k] - t[k]) * x[j] # 미분
delta_w = -1 * l * delta_w # 미분의 역방향 * learning rate
# print (k, j, t[k], y[k], x[j], delta_w)
w[k][j] += delta_w
# 역전파 1회 완료 후 계산된 가중치 행렬
w
array([[ 0.396, 4.396, 0.584, 3.98 , 0.376, 0.168, 60.564], [ 1.951, 3.951, 22.804, 0.755, 1.706, 0.608, 0.559], [ 4.021, 4.021, 1.084, 1.105, 1.126, 1.168, 2.189]])
# 다시 가중치 행렬 초기화
w = init_w()
w
array([[ 1., 5., 3., 7., 4., 5., 66.], [ 2., 4., 23., 1., 2., 1., 1.], [ 4., 4., 1., 1., 1., 1., 2.]])
이번에는 전체 계산을 한 번에 진행한다.
$[[\frac{\partial(E_1)}{\partial(y_1)}],[\frac{\partial(E_2)}{\partial(y_2)}],[\frac{\partial(E_3)}{\partial(y_3)}]]\cdot[x_1, x_2, x_3 ... x_7]$
미분 계산 편의상 1/2을 취한다.
$\frac{\partial(\frac{1}{2}E_1)}{\partial{y_1}} = \frac{\partial}{\partial{y_1}}\frac{1}{2} (t_1 - y_1) ^ 2 = y_1 - t_1$
$x_n$ 과 dot 계산 결과는 [w_11, w_12, w_13 ... w_17], [w_21, w_22 ... w_27], [w_31 ...] 와 동일하다.
아래와 같이 미분과 전치 행렬로 한 번에 동일한 계산 결과를 줄 수 있다.
w += - l * np.dot((y - t), x.transpose())
w
array([[ 1., 5., 3., 7., 4., 5., 66.], [ 2., 4., 23., 1., 2., 1., 1.], [ 4., 4., 1., 1., 1., 1., 2.]])
y-t, x.transpose()
(array([[604.], [ 49.], [-21.]]), array([[1, 1, 4, 5, 6, 8, 9]]))
# Gradients
np.dot((y - t), x.transpose())
array([[ 604., 604., 2416., 3020., 3624., 4832., 5436.], [ 49., 49., 196., 245., 294., 392., 441.], [ -21., -21., -84., -105., -126., -168., -189.]])
# Gradient Checking
h = 0.1
for k in range(3):
for j in range(7):
# 수치 미분 진행
w = init_w()
w[k][j] += h
y = np.dot(w, x)
e1 = ((t[k] - y[k]) ** 2)/2
w = init_w()
w[k][j] -= h
y = np.dot(w, x)
e2 = ((t[k] - y[k]) ** 2)/2
# 수치 미분 결과가 해석적 미분과 동일함을 확인할 수 있다.
# (계산을 편리하게 하기 위한 해석적 미분의 1/2을 동일하게 적용했다.)
print((e1 - e2) / (2 * h),end='')
print()
[604.][604.][2416.][3020.][3624.][4832.][5436.] [49.][49.][196.][245.][294.][392.][441.] [-21.][-21.][-84.][-105.][-126.][-168.][-189.]
# 추가 학습
for _ in range(50):
w += - l * np.dot((y - t), x.transpose())
y = np.dot(w, x)
y
array([[107.00187985], [ 83.0001525 ], [ 69.99993184]])
w
array([[ -1.69642018, 2.30357982, -7.78568072, -6.4821009 , -12.17852108, -16.57136143, 41.73221839], [ 1.78125068, 3.78125068, 22.12500272, -0.0937466 , 0.68750408, -0.74999455, -0.96874387], [ 4.09776755, 4.09776755, 1.39107021, 1.48883776, 1.58660532, 1.78214042, 2.77990798]])
import sympy
sympy.init_printing(use_latex='mathjax')
t, y = sympy.symbols('t y')
e = (t - y)**2
e
sympy.Derivative(e, y)
sympy.Derivative(e, y).doit()
e12 = 1/2 * (t - y) ** 2
sympy.Derivative(e12, y)
sympy.Derivative(e12, y).doit()