기본 콘텐츠로 건너뛰기

벡터와 행렬에 관련된 그림들

순전파와 역전파(forward & backward propagation)

내용

순전파와 역전파(forward & backward propagation)

학습과정

일반적으로 신경망은 가장 먼저 층(layer)을 구성하며 입력은 그 층을 통과하여 relu 등과 같은 활성함수에 의해 출력됩니다.

tf.keras.layers.Dense(512, activation="relu")
output=relu(dot(W, input)+b)

위 relu 함수에서 W, b는 층의 속성인 텐서호 입력과 출력의 관계를 결정하는 가중치와 편차로 위 과정을 반복하면서 학습되는 매개변수입니다. 각각은 커널과 편향 속성이라고 합니다.

다음에서 나타낸 과정과 같이 초기 W, b는 임의 값으로 지정되며 입렬(input)과 함께 층으로 입력되며 드 출력이 이렂ㅇ한 기준을 만족하지 않은 경우 다시 층으로 입력되는 과정을 반복합니다. 이 과정을 학습(training)이라고 하며 학습을 통해 W, b의 최적화가 이루어지며 그에 대응하는 결과물이 출력됩니다. 이 과정이 머신러닝 또는 딥러닝의 핵십이 됩니다.

W0, b0, Inputlayers
activation, other operation
Woptim, boptim
Output
→: forward
→: backward

위 학습과정은 다음의 단계로 구성됩니다.

1 단계. 레이어, 활성함수, 출력층을 구축합니다.
가중치, 편차, 입력을 조합하는 초기 모델을 설정합니다.
2 단계. 모델에 의한 y의 추정치(ypred)를 계산합니다.
이 단계를 forward propagation(순방향 전파)라고 합니다.
3 단계. ypred와 y간의 불일치 측정값인 비용(cost) 또는 손실(loss)을 계산합니다.
비용함수를 설정합니다.
4 단계. 손실을 최소화하기 위한 W, b를 업데이트 합니다.
x의 변수에 대한 W, b의 기울기를 계산합니다.
각 기울기를 3 단계까지 사용된 W, b에 적용하여 업데이트 합니다.
이 업데이트 과정은 4단계에서 3, 2 단계로 역으로 추적되는 과정으로 backwawd propagation(역방향 전파)라고 합니다.

위 학습과정으로 인해 결국 ypred와 y간의 낮은 불일치, 즉, 손실이 매우 낮은 네트워크가 구축 됩니다. 즉, 특징(feature) x로 라벨 y를 적절하게 매핑하는 방법을 찾는 것입니다. 이 과정에서 가중치의 결정하는 4 단계가 난관이 됩니다. 만약 가중치 0.4일 경우 손실이 0.5이고 0.3일 경우 0.6이라면 가중치는 0.35로 지정되는 방식으로 진행 할 수 있지만 매우 비효율적입니다. 이에 반해 네트워크에서 사용되는 모든 연산이 미분 가능하다는 사실을 활용하고 네트워크 계수와 관련하여 손실의 기울기(gradient)를 계산하고 그 값을 가중치와 편차에 적용하는 과정을 반복하여 최적의 결과를 유도합니다. 즉, 손실을 최소화합니다.

미분

그림 1은 곡선(f) 위의 임의의 두 점, C, B를 연결하는 직선과 A와의 접선을 나타냅니다. 두 점 C와 B가 A쪽으로 접근한다면 기울기 c는 접선의 기울기 a에 근접할 것이며 결국에 기울기 c는 a와 같아질 것입니다. 결과적으로 A와 충분히 가까운 두 점 사이의 기울기를 A의 도함수 또는 미분(derivative)라고 합니다.

그림 1.

한 점에서 미분 결과의 절대값은 변화의 강도, 부호는 변화의 증가 또는 감소를 나타냅니다. 결과적으로 그 미분값은 증가 또는 감소의 속도(강도)를 알려줍니다.

모든 연속인 함수는 미분가능하며 일정한 지점에서의 미분값을 고려함으로서 목표로 하는 방향으로 그 지점의 이동을 유도할 수 있습니다. 즉, 다음 2차 함수의 그림에서 함수 위의 임의의 점 A를 아래 방향으로 이동시키기 위해 미분을 적용한 것입니다.

그림 2.

기울기(gradient): 자동미분

기울기는 학습에 참여하는 여러 텐서들의 연산에 대한 미분 결과입니다. 즉, 학습에서 생성되는 예측치와 실측치의 차이를 감소시키는 목적으로 그 둘의 차이 즉, 식 1의 비용(cost)이 미분의 대상이 됩니다.

$$\begin{align}\tag{1}&\hat{y_i}=x_i \cdot W + b\\ &\text{cost}=\frac{\sum^N_{i=1}(y_i-y)^2}{N} \end{align}$$

식 1에서 N은 데이터의 샘플의 크기입니다. 식에서 x, y는 상수이므로 비용은 예측치($\hat{y_i}$)에 의존합니다. 이 예측치는 가중치 W와 편차 b의 함수로 결국 비용은 가중치와 편차의 함수가 됩니다.

cost = f(W, b)

식 1에서 나타낸 것과 같이 비용은 실측치와 예측치에 대한 2차 함수 즉, MSE(mean squared error)가 됩니다. 결국 위 그림 2에서 곡선을 비용함수로 간주할 수 있으므로 비용이 최소로 되는 지점까지의 이동은 가중치에 대한 비용의 미분을 고려함으로서 달성될 수 있습니다. 이 단계서 미분항의 값이 크다면 감소 속도가 증가되어 최소지점을 지나칠 가능성이 존재합니다. 이러한 가능성을 최소로 하기 위해서 감소율을 축소할 수 있습니다. 이를 위해 step factor 또는 학습율(learning factor, η)를 고려해줍니다.

W=W0 cost=f(W)
W = W-η$\mathbf{\frac{d(f(W))}{dW}}$
Woptim

자동미분

위의 미분은 연산은 tensorflow에서 자동으로 진행할 수 있는 함수를 제공합니다. 즉, 입력변수에 대한 연산의 변화 즉, 기울기(gradient)를 계산하는 자동미분은 tf.GradientTape 클래스를 통해 실행됩니다. 실행되는 모든 연산이 tf.GradientTape에 기록됩니다. 이 과정은 그림 1의 역전파(backward propagation) 단계에서 이루어집니다.

다음은 2차원 텐서에 대한 역전파로 미분 연산 과정을 나타낸 것입니다.

$$\begin{align}x=\begin{bmatrix}x_{11}=1&x_{12}=1\\x_{21}=1&x_{22}=1\end{bmatrix} &\overset{+}{\Large{\Rightarrow}} & y=\sum x \qquad &\overset{\times}{\Large{\Rightarrow}} &z=y^2\\ &&&&\Large{\Downarrow} \\ \begin{aligned}\frac{dz}{dx}&=\frac{dz}{dy}\frac{dy}{dx}\\&=8\begin{bmatrix}1&1\\1&1\end{bmatrix}\\&=\begin{bmatrix}8&8\\8&8\end{bmatrix}\end{aligned} &\Large{\Leftarrow} &\frac{dy}{dx}=\begin{bmatrix}\frac{dx_{11}}{dx_{11}}&\frac{dx_{12}}{dx_{12}}\\\frac{dx_{21}}{dx_{21}}&\frac{dx_{22}}{dx_{22}}\end{bmatrix} &\Large{\Leftarrow} &\begin{aligned}\frac{dz}{dy}&=2y\\&=8 \end{aligned} \end{align}$$
tf.GradientTape(persistent=False, watch_acessed_variables=True)
tf.GradientTape()
이 클래스는 with 문을 사용하여 객체화합니다.
tf.GradientTape.watch()
이 함수를 적용하기 위한 객체, 즉 미분인자가 될 객체는 상수가 아닌 변수형이어야 합니다. .watch()메소드에 의해 전환 합니다.
tf.GradientTape.gradient(target, sources, output_gradients=None,...)
객체의 미분된 값을 호출
persistent=True
gradient() 메소드에 의해 결과가 호출되는 즉시 기록물은 삭제 됩니다. 그러나 persistent를 True로 지정함으로서 기록물들은 보존됩니다.

미분의 결과를 기록하기 위한 객체를 x라고 하면 이 객체를 변수로 지정해야 합니다. 위에서 언급한 것과 같이 tf.GradientTape.watch(x)를 적용합니다. 이 적용에 의해 객체 x에 대한 미분 결과들이 클래스 인스턴스에 기록됩니다. 다음은 x=5에 대한 미분 과정입니다.

$$\begin{align}&x=5, \quad y=x^5\\ &\frac{dy}{dx}=2x=10 \\&\frac{d^2y}{dx^2}=2 \end{align}$$
import tensorflow as tf
from tensorflow import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
x=tf.constant(5.0)
with tf.GradientTape() as g: # 2차 미분(gradeient)를 위한 class instance
    g.watch(x) #2차 미분을 위한 전환
    with tf.GradientTape() as gg:#1차 미분(gradient)를 위한 class instance
        gg.watch(x)
        y=x**2
    dydx1=gg.gradient(y, x) #1차 미분
dydx2=g.gradient(dydx1, x)

dydx1   
<tf.Tensor: shape=(), dtype=float32, numpy=10.0 >
dydx2
<tf.Tensor: shape=(), dtype=float32, numpy=2.0 >
x=tf.constant(5.0)
y=x**2
with tf.GradientTape() as f:
    f.watch(x)
    y=x**2
    z=y**2
    w=z**2
dwdx=f.gradient(w, x)
dwdx
<tf.Tensor: shape=(), dtype=float32, numpy=625000.0>

f인 tf.GradientTape() 클래스 인스턴스에서 상수 x를 변수로 정의하고 객체 y, z, w를 선언하였스며 그 과정에서 각각의 그래디언트가 실행되었습니다. 즉, 객체 y, z, w를 변수 x 대해 미분이 시행된 상태입니다. 이 결과들 중 w의 미분을 호출한 것입니다.

다음으로 z의 미분결과를 호출하면 에러가 발생합니다. 이는 클래스의 매개변수 중 persistent=False로 지정하였으므로 결과를 호출한 즉시 f에 대한 모든 기록이 삭제되기 때문입니다. 대신에 기록들을 보존하기 위해서는 이 매개변수 persistent를 True로 지정합니다.

dzdx=f.gradient(z, x)
dzdx
---------------------------------------------------------------------------
   RuntimeError: A non-persistent GradientTape can only be used tocompute one set of gradients (or jacobians)
x=tf.constant(5.0)
y=x**2
with tf.GradientTape(persistent=True) as f:
    f.watch(x)
    y=x**2
    z=y**2
    w=z**2
dwdx=f.gradient(w, x)
dwdx
<tf.Tensor: shape=(), dtype=float32, numpy=625000.0>
f.gradient(z, x)
<tf.Tensor: shape=(), dtype=float32, numpy=500.0>
f.gradient(y, x)
<tf.Tensor: shape=(), dtype=float32, numpy=10.0>

기본적으로 GradientTape는 컨텍스트 내에서 접근되는 모든 학습 가능한 변수를 자동으로 감시합니다. 감시되는 변수를 세부적으로 제어하려면 watch_accessed_varaibles=False를 테이프 생산성자에 전달하여 자동 추적을 비활성화 할 수 있습니다. 이 경우 기록을 위해 지정된 변수에 대한 계산만 수행됩니다.

x=tf.constant(5.0)
w=tf.constant(2.0)
with tf.GradientTape(persistent=True, watch_accessed_variables=False) as g:
    g.watch(x)
    y=x**3
    z=w**3
dydx=g.gradient(y, x)
dydx.numpy()
75.0
dzdw=g.gradient(z, w) #w에 대한 미분을 실행되지 않음 
dzdw

댓글

이 블로그의 인기 게시물

[Linear Algebra] 유사변환(Similarity transformation)

유사변환(Similarity transformation) n×n 차원의 정방 행렬 A, B 그리고 가역 행렬 P 사이에 식 1의 관계가 성립하면 행렬 A와 B는 유사행렬(similarity matrix)이 되며 행렬 A를 가역행렬 P와 B로 분해하는 것을 유사 변환(similarity transformation) 이라고 합니다. $$\tag{1} A = PBP^{-1} \Leftrightarrow P^{-1}AP = B $$ 식 2는 식 1의 양변에 B의 고유값을 고려한 것입니다. \begin{align}\tag{식 2} B - \lambda I &= P^{-1}AP – \lambda P^{-1}P\\ &= P^{-1}(AP – \lambda P)\\ &= P^{-1}(A - \lambda I)P \end{align} 식 2의 행렬식은 식 3과 같이 정리됩니다. \begin{align} &\begin{aligned}\textsf{det}(B - \lambda I ) & = \textsf{det}(P^{-1}(AP – \lambda P))\\ &= \textsf{det}(P^{-1}) \textsf{det}((A – \lambda I)) \textsf{det}(P)\\ &= \textsf{det}(P^{-1}) \textsf{det}(P) \textsf{det}((A – \lambda I))\\ &= \textsf{det}(A – \lambda I)\end{aligned}\\ &\begin{aligned}\because \; \textsf{det}(P^{-1}) \textsf{det}(P) &= \textsf{det}(P^{-1}P)\\ &= \textsf{det}(I)\end{aligned}\end{align} 유사행렬의 특성 유사행렬인 두 정방행렬 A와 B는 'A ~ B' 와 같...

[sympy] Sympy객체의 표현을 위한 함수들

Sympy객체의 표현을 위한 함수들 General simplify(x): 식 x(sympy 객체)를 간단히 정리 합니다. import numpy as np from sympy import * x=symbols("x") a=sin(x)**2+cos(x)**2 a $\sin^{2}{\left(x \right)} + \cos^{2}{\left(x \right)}$ simplify(a) 1 simplify(b) $\frac{x^{3} + x^{2} - x - 1}{x^{2} + 2 x + 1}$ simplify(b) x - 1 c=gamma(x)/gamma(x-2) c $\frac{\Gamma\left(x\right)}{\Gamma\left(x - 2\right)}$ simplify(c) $\displaystyle \left(x - 2\right) \left(x - 1\right)$ 위의 예들 중 객체 c의 감마함수(gamma(x))는 확률분포 등 여러 부분에서 사용되는 표현식으로 다음과 같이 정의 됩니다. 감마함수는 음이 아닌 정수를 제외한 모든 수에서 정의됩니다. 식 1과 같이 자연수에서 감마함수는 factorial(!), 부동소수(양의 실수)인 경우 적분을 적용하여 계산합니다. $$\tag{식 1}\Gamma(n) =\begin{cases}(n-1)!& n:\text{자연수}\\\int^\infty_0x^{n-1}e^{-x}\,dx& n:\text{부동소수}\end{cases}$$ x=symbols('x') gamma(x).subs(x,4) $\displaystyle 6$ factorial 계산은 math.factorial() 함수를 사용할 수 있습니다. import math math.factorial(3) 6 a=gamma(x).subs(x,4.5) a.evalf(3) 11.6 simpilfy() 함수의 알고리즘은 식에서 공통사항을 찾아 정리하...

sympy.solvers로 방정식해 구하기

sympy.solvers로 방정식해 구하기 대수 방정식을 해를 계산하기 위해 다음 함수를 사용합니다. sympy.solvers.solve(f, *symbols, **flags) f=0, 즉 동차방정식에 대해 지정한 변수의 해를 계산 f : 식 또는 함수 symbols: 식의 해를 계산하기 위한 변수, 변수가 하나인 경우는 생략가능(자동으로 인식) flags: 계산 또는 결과의 방식을 지정하기 위한 인수들 dict=True: {x:3, y:1}같이 사전형식, 기본값 = False set=True :{(x,3),(y,1)}같이 집합형식, 기본값 = False ratioal=True : 실수를 유리수로 반환, 기본값 = False positive=True: 해들 중에 양수만을 반환, 기본값 = False 예 $x^2=1$의 해를 결정합니다. solve() 함수에 적용하기 위해서는 다음과 같이 식의 한쪽이 0이 되는 형태인 동차식으로 구성되어야 합니다. $$x^2-1=0$$ import numpy as np from sympy import * x = symbols('x') solve(x**2-1, x) [-1, 1] 위 식은 계산 과정은 다음과 같습니다. $$\begin{aligned}x^2-1=0 \rightarrow (x+1)(x-1)=0 \\ x=1 \; \text{or}\; -1\end{aligned}$$ 예 $x^4=1$의 해를 결정합니다. solve() 함수의 인수 set=True를 지정하였으므로 결과는 집합(set)형으로 반환됩니다. eq=x**4-1 solve(eq, set=True) ([x], {(-1,), (-I,), (1,), (I,)}) 위의 경우 I는 복소수입니다.즉 위 결과의 과정은 다음과 같습니다. $$x^4-1=(x^2+1)(x+1)(x-1)=0 \rightarrow x=\pm \sqrt{-1}, \; \pm 1=\pm i,\; \pm1$$ 실수...