Linear Algebra: Matrix Transpose & Differential Calculus

In [1]:
import numpy as np
np.set_printoptions(suppress=True)
In [2]:
x = np.array([1,1,4,5,6,8,9])
x
Out[2]:
array([1, 1, 4, 5, 6, 8, 9])
In [3]:
# 『신경망 첫걸음, 2016』 에서는 이 방식으로 변환한다.
np.array(x, ndmin=2).T
Out[3]:
array([[1],
       [1],
       [4],
       [5],
       [6],
       [8],
       [9]])
In [4]:
x.T
Out[4]:
array([1, 1, 4, 5, 6, 8, 9])
In [5]:
x.shape[0]
Out[5]:
7
In [6]:
# 그러나 reshape로 좀 더 우아하게 변환할 수 있다.
x = x.reshape(-1, 1)
x
Out[6]:
array([[1],
       [1],
       [4],
       [5],
       [6],
       [8],
       [9]])
In [7]:
# 엉뚱한 답을 찾으라고 할 순 없으므로 미리 정답 행렬로 답을 만들어 둔다.
# 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
Out[7]:
array([[ 1.,  5.,  3.,  7.,  4.,  5., 66.],
       [ 2.,  4., 23.,  1.,  2.,  1.,  1.],
       [ 4.,  4.,  1.,  1.,  1.,  1.,  2.]])
In [8]:
# (3, 7)과 (7, 1) 처럼 첫 번째 matrix의 열과 두 번째 matrix의 행이 일치해야 점곱(dot product)이 가능하다.
# 따라서 np.dot(x, w)는 성립하지 않으며 w, x 로 계산해야 한다.
y = np.dot(w, x)
y
Out[8]:
array([[711.],
       [132.],
       [ 49.]])
In [9]:
# 정답
t = np.array([[107],[83],[70]])
t
Out[9]:
array([[107],
       [ 83],
       [ 70]])
In [10]:
y - t # d(1/2 * E)/d(y)
Out[10]:
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$

In [11]:
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
In [12]:
# 역전파 1회 완료 후 계산된 가중치 행렬
w
Out[12]:
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]])
In [13]:
# 다시 가중치 행렬 초기화
w = init_w()
w
Out[13]:
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())

In [14]:
w
Out[14]:
array([[ 1.,  5.,  3.,  7.,  4.,  5., 66.],
       [ 2.,  4., 23.,  1.,  2.,  1.,  1.],
       [ 4.,  4.,  1.,  1.,  1.,  1.,  2.]])
In [15]:
y-t, x.transpose()
Out[15]:
(array([[604.],
        [ 49.],
        [-21.]]), array([[1, 1, 4, 5, 6, 8, 9]]))
In [16]:
# Gradients
np.dot((y - t), x.transpose())
Out[16]:
array([[ 604.,  604., 2416., 3020., 3624., 4832., 5436.],
       [  49.,   49.,  196.,  245.,  294.,  392.,  441.],
       [ -21.,  -21.,  -84., -105., -126., -168., -189.]])
In [17]:
# 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.]
In [18]:
# 추가 학습
for _ in range(50):
    w += - l * np.dot((y - t), x.transpose())
    y = np.dot(w, x)
y
Out[18]:
array([[107.00187985],
       [ 83.0001525 ],
       [ 69.99993184]])
In [19]:
w
Out[19]:
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]])

미분 계산 검증

In [20]:
import sympy
sympy.init_printing(use_latex='mathjax')
t, y = sympy.symbols('t y')
e = (t - y)**2
e
Out[20]:
$$\left(t - y\right)^{2}$$
In [21]:
sympy.Derivative(e, y)
Out[21]:
$$\frac{\partial}{\partial y} \left(t - y\right)^{2}$$
In [22]:
sympy.Derivative(e, y).doit()
Out[22]:
$$- 2 t + 2 y$$
In [23]:
e12 = 1/2 * (t - y) ** 2
sympy.Derivative(e12, y)
Out[23]:
$$\frac{\partial}{\partial y}\left(0.5 \left(t - y\right)^{2}\right)$$
In [24]:
sympy.Derivative(e12, y).doit()
Out[24]:
$$- 1.0 t + 1.0 y$$