본문 바로가기

딥러닝

신경망 학습(2)

1. 학습

1.1 신경망의 학습에 대한 지표는 손실 함수

손실 함수를 사용하는 이유는, 학습 과정에서 손실 함수의 결과를 바탕으로 손실 함수의 결과값을 최대한 작게 만드는 최적의 매개변수(가중치, 편향)를 찾을 수 있도록 매개변수의 미분을 계산해, 미분 값을 기준으로 매개변수의 값이 최적이 될 때까지 매개변수의 값을 업데이트하기 위해서이다.

■ 이 과정의 목표는 결국 모델이 얼마나 잘 예측했는지 정확도를 구하기 위해서 이지만, 신경망 학습의 지표로 정확도를 사용하지 않고 손실 함수를 사용한다.

■ 이러한 이유는

1) 미분 값을 기준으로 매개변수를 업데이트해야 하는데, 미분 값이 0이면 매개변수를 업데이트할 기준이 0, 즉 최적의 매개변수를 아직 찾지 못했는데 매개변수를 더 이상 업데이트하지않는다.

이렇게 되면 기울기(경사) 값을 기준으로 나아갈 방향을 정하지 못해 손실 함수에 어떠한 변화도 줄 수 없으므로 신경망이 제대로 학습되지 않는다.

2) 정확도는 손실 함수 결과 값에 비해 덜 연속적이기 때문에 정확도를 지표로 삼아 매개변수의 미분을 계산한다면, 미분 값이 0이 되는 경우가 빈번해진다. 미분 값이 0이면 손실 함수 결과가 변하지 않으므로 정확도를 지표로 삼는 것은 의미가 없다.

- 예를 들어 이산확률분포와 연속확률분포를 생각해 볼 수 있다. 물론 정확도도 값이 개선된다면 ( ex) 0.5, 0.5012..., 0.52.... , 0.54......) 완전히 이산적이라 할 수 없지만, 결국 불연속적인 값이 된다. 

- 즉 다음과 같은 모양이 될 수 있다. 반면, 손실 함수의 값은 연속적인 값이 되기 때문에 연속적인 형태를 갖는다.

정확도 Vs. 손실 함수

- 위의 두 그래프의 형태를 직관적으로 봤을 때 미분 값이 0이 되는 지점, 즉 기울기 = 0이 되는 지점이 지표가 손실 함수일 때 보다 정확도일 때 많이 발생할 수 있다는 것을 볼 수 있다.

- 이는 앞서 활성화 함수로 시그모이드 함수는 사용해도 계단 함수를 사용하지 않는 이유와도 동일한 이유이다.

- 왜냐하면 시그모이드 함수는 \( h(x) = \dfrac{1}{1 + e^{-x}} \)이기 때문이다.

- 시그모이드 함수를 미분하면 \( ( \dfrac{1}{1 + e^{-x}} \))( \( 1 - \dfrac{1}{1 + e^{-x}} \))이므로 \( h'(x) = h(x)(1 - h(x)) \)이다.

- 시그모이드 함수의 출력은 항상 \( 0 < h(x) < 1 \)이기 때문에 \( 1 - h(x) \)의 결과도 \( 0 < 1 - h(x) < 1 \)이다. 따라서 미분 값이 아주 작아질 수는 있지만, 미분 값이 완전히 0이 되지 않는다.

- 따라서 학습 과정에서 가중치를 조정할 때 출력이 연속적인 실수 값이면 미세하게 조정할 수 있다. 하지만, 이산적인 값만 사용하면 가중치 조정이 제한적일 수밖에 없다.

신경망(1) (tistory.com)

 

신경망(1)

1. 신경망 개요■ 퍼셉트론의 장점은 XOR같은 다소 복잡한 함수도 퍼셉트론으로 표현할 수 있지만, 가중치 설정을 사람이 수동으로 설정해야 한다는 단점이 있다.■ 이 단점은 신경망이 해결할

hyeon-jae.tistory.com

 

2. 미분

■ 미분은 특정 순간의 변화량을 나타낸 것이다.

■ 함수 \( f(x) \)에서 \( x \)의 값이 \( a \)에서 \( a + \Delta x \)까지 변할 때, 즉 \( x \)가 \( a \)에서 \( \Delta x \)만큼 변화를 할 때 \( y \)값이 \( \Delta y \)만큼 변한다고 하면(연속성 개념), 이 \( x, y \)의 변화량의 비율 \( \dfrac {\Delta y}{\Delta x} \)를 구간 [ \( a, a + \Delta x \)]에서의 평균 변화율이라고 한다.

■ 변화량의 비율 \( \dfrac {\Delta y}{\Delta x} \)에서 \( \Delta x \)가 0에 가까워질 때, 즉 \( \dfrac {\Delta y}{\Delta x} \)가 극한값을 가지면, 이를 \( f(x) \)의 \( x = a \)에서의 (순간)변화율, 미분계수라고 한다. 즉, 미분계수를 \(
\displaystyle \lim_{\Delta x \to 0} \dfrac{\Delta y}{\Delta x}
\)로 나타낼 수 있다.

■ 미분계수의 의미는 함수 \( y = f(x) \) 위의 점 \( (a, f(a)) \)에서의 접선의 기울기이다.

■ 미분계수를 나타낼 때, \( x \)값의 변화량을 \( \Delta x \)로 표현하면 \(
\displaystyle \lim_{\Delta x \to 0} \dfrac{\Delta y}{\Delta x}
\)이고, \( x \)값의 변화량을 \( h \)로 표현하면 \(
\displaystyle \lim_{h \to 0} \dfrac{f(a + h) - f(a)}{h}
\)로 나타낼 수 있다. 이는 \( x \)의 \( h \)라는 작은 변화량이 함수 \( f(x) \)를 얼마나 변화시키는지에 대한 의미이다.

\( x = a \)에서의 접선의 기울기(=순간 변화율)는 \[
\displaystyle \lim_{\Delta x \to 0} \dfrac{\Delta y}{\Delta x} = \displaystyle \lim_{b \to a} \dfrac{f(b) - f(a)}{b - a} = \displaystyle \lim_{h \to 0} \dfrac{f(a + h) - f(a)}{h} = \left. \dfrac{dy}{dx} \right|_{x = a} = f'(a) = y'(a)
\]

이렇게 미분(도함수)은 점을 넣으면 접선의 기울기가 나오는 함수로 볼 수 있다. ( \( f'(a) = \) "접선의 기울기", 즉 \( f'(x) \)라는 '도함수'에 \( x = a \)라는 점을 넣었더니 함수인 도함수의 출력으로 접선의 기울기가 나온 것)

■ \( \displaystyle \lim_{h \to 0} \dfrac{f(a + h) - f(a)}{h} \)를 파이썬에서 완벽히 구현하기에는 한계가 있다. 첫 번째는 ' \( h \to 0 \)' 즉, \( h \)를 무한히 0으로 좁히는 것을 구현하는 것, 두 번째는 \( h \)가 소수점 단위로 아주 작은 값이 되면 계산 시, 파이썬에서 실수를 근삿값으로 표현하면서 발생하는 부동 소수점 반올림 오차가 발생할 수 있기 때문이다.

이와 같은 문제를 개선하기 위해 \( h \)는 컴퓨터가 계산하는데 문제가 되지 않을 정도의 값을 사용하고 미분은 근사로 구한 미분인 '수치 미분'을 사용한다. 이때, 수치 미분은 근사로 구하기 때문에 값에 오차가 포함된다.

■ 수치 미분(numerical differentiation)은 수치 해석학에서 미분 값을 컴퓨터로 계산할 때 근사치로 계산하는 방법이며, 가장 간단한 방법은 중심 차분 근사를 사용하는 것이다.

중심 차분: \(
\displaystyle \lim_{\Delta x \to 0} \dfrac{f(x + \Delta x) - f(x - \Delta x)}{2 \Delta x} = \displaystyle \lim_{h \to 0} \dfrac{f(x + h) - f(x - h)}{2h} = f'(x)
\)

■ 위와 같은 개선점을 고려하여 수치 미분을 파이썬에서 구현하면 다음과 같다.

def NumericalDiff(f, x):
    h = 1e-4
    f_prime = (f(x + h) - f(x - h)) / (2 * h)
    return f_prime

- 예를 들어 수치 미분을 이용해 \( sin x \)를 미분하면 다음과 같다

x_radians = np.radians(60)

print(NumericalDiff(np.sin, 0)); print(NumericalDiff(np.sin, x_radians))
```#결과#```
0.9999999983333334
0.49999999916672255
`````````````

실제 미분은 \( (sin x)' = cos x \)이고 \( cos 0 = 1, cos \frac {\pi}{3} = \frac {1}{2} \)이 나온다. 위의 수치 미분 결과와 실제 미분 결과의 오차가 매우 작은 것을 볼 수 있다.

 

3. 편미분

■ \( y = f(X) \), 즉 1변수 함수일 때 도함수의 정의는 다음과 같았다.

■ 함수 \( z = f(x, y) \)가 \( x \)와 \( y \)의 2변수 함수일 때, \( x \)가 상수, \( x = a \)이면 \( f(a, y) \)는 \( y \)에 대한 1변수 함수이고, \( y \)가 상수, \( y = b) \)이면 \( f(x, b \)는 \( x \)에 대한 1변수 함수이다.

먼저 \( x \)가 상수 \( a \)일 때, 두 점 \( P, Q \)를 잇는 선분의 길이는 \( \frac {f(a + h, b) - f(a, b)}{h} \)이다. 이 기울기가 \( h \)가 0에 가까워지면 \( P \)와 \( Q \)를 잇는 선분은 점 \( P \)에서의 접선에 가까워진다. \(
\displaystyle \lim_{h \to 0} \dfrac{f(a + h, b) - f(a, b)}{h}
\)는 \( \displaystyle \lim_{h \to 0} \dfrac{f(a + h, b) - f(a, b)}{h} = \)

\( = f_{x}(a, b) = \dfrac{\partial f}{\partial x} (a, b) = \dfrac{\partial z}{\partial x} (a, b)
\)로 나타내며, 곡면 \( z = f(x, y) \)와 평면 \( y = b \)
 위의 교선 위의 점에서의 접선의 기울기 또는 \( x \)에 대한 편도 함수(= 편미분)라고 한다.

 

 

 


동일한 이유로 \( y \)가 상수 \( b \)일 때, \(
\displaystyle \lim_{h \to 0} \dfrac{f(a, b + h) - f(a, b)}{h}
\) \(
= f_{y}(a, b) = \dfrac{\partial f}{\partial y}(a, b) = \dfrac{\partial z}{\partial y}(a, b)
\)이며 , 곡선 \( z = f(x, y) \)와 평면 \( x = a \)의 교선 위의 점에서의 기울기 또는 \( y \)에 대한 편도함수(=편미분)라고 한다.

 

 

■ \( f_{x}(a, b) \)는 \( x \)에 대한 편미분으로 \( f(a + h, b) - f(a, b) \)를 보면 알 수 있듯이 \( y = b \)로 고정일 때 \( x \)만 움직이기 때문에 \( x \)에 대해서만 미분하고 나머지 변수는 상수 취급한다. \( f_{y}(a, b) \)는 \( y \)에 대한 편미분으로 \( f(a, b + h) - f(a, b) \), \( y \)만 움직이기 때문에 \( y \)에 대해서만 미분하고 나머지 변수는 상수 취급한다.

■ 편미분도 미분이므로 컴퓨터에서 계산할 때 수치 미분을 이용해야 한다.

- 예를 들어 \( f(x, y) = x^2 + y^2 \)일 때, \( x \)에 대한 편미분과 \( y \)에 대한 편미분은 \( f_{x}(x, y) = 2x, f_{y}(x, y) = 2y \)이다. 만약 \( x = 10, y = 20 \)이라면 \( f_{x}(10, 20) = 20, \quad f_{y}(10, 20) = 40 \)이다. 이를 수치 미분으로 계산하면 다음과 같다

# x에 대한 편미분
def function_1(x):
    return x*x + 20**2 # y는 상수 취급

# y에 대한 편미분
def function_2(y):
    return 10**2 + y*y # x는 상수 취급
    
print(NumericalDiff(function_1, 10)); print(NumericalDiff(function_2, 20))
```#결과#```
19.99999999981128
39.99999999990678
````````````

위의 수치 미분 결과와 실제 미분 결과의 오차가 매우 작은 것을 볼 수 있다.

 

4. 기울기(gradient)

■ gradient는 기울기 또는 경도를 의미하며,

■ \( z = f(x, y) \)가 점 \( (a, b) \)에서 미분가능할 때 \( (f_{x}, f_{y})_{(a, b)} \)를 \( f \)의 경도라하고 \( \nabla f(a, b) \) 또는 grad\( f \)로 나타낸다. 즉, 모든 변수의 편미분을 방향과 크기를 가지는 벡터 형태로 계산하는 것을 gradient라 한다.

■ 예를 들어 \( f(x, y) = x^2 + y^2 +xy \)의 점 (10, 20)에서의 gradient는 \( \nabla f(10, 20) = (2x + y, 2y + x)_{(10, 20)} = (40, 40) \)이 된다.

■ \( z = f(x, y) \)에서 gradient는 \( \nabla f = (f'(x), f'(y)) \)를 구하는 것이라 볼 수 있다. 따라서 수치 미분 중심 차분 근사를 이용하면 \( \displaystyle \lim_{h \to 0} \dfrac{f(x + h) - f(x - h)}{2h} = f'(x), \quad \lim_{h \to 0} \dfrac{f(y + h) - f(y - h)}{2h} = f'(y) \)를 구하는 것으로 볼 수 있다. 

■ 파이썬에서 이를 구현하려면 입력값(x, y, ... 등) 개수에 따라 수치 미분을 계산하도록 만들면 된다.

def NumericalGradient(f, variable):
    h = 1e-4
    grad = np.zeros_like(variable) # z = f(x, y)의 grad f를 계산하려면 x, y 2개의 입력값이 필요. 
                                        # 따라서 입력값 개수와 동일하게 편미분을 계산할 변수 개수를 초기화
    for i in range(variable.size): # x, y = 10, 20 이면
        tmp = variable[i] # variable[0] = 10, variable[1] = 20
        variable[i] = tmp + h # x를 x + h, y를 y + h, z를 z + h, ..... 로
        f_x_plus_h = f(variable) # f(x+h), f(y+h), f(z+h), ...
        variable[i] = tmp - h # x를 x - h, y를 y - h, z를 z - h, ..... 로
        f_x_minus_h = f(variable) # f(x-h), f(y-h), f(z-h), ...
        grad[i] = (f_x_plus_h - f_x_minus_h) / (2*h) # 수치 미분(중심 차분 근사) 계산
        variable[i] = tmp # 값을 원래대로
                      #  i = 0일 때, variable[0]을 변형시켜 기울기를 구한 다음, variable[1]을 변형시켜 f(y+h), f(y-h)를 구할 때
                      # variable[0]은 원래 값이어야 한다. 
                      # 그렇지 않으면 variable[0]이 변형된 상태에서 f(x)를 계산하게 되어 정확한 기울기를 구할 수 없다. 따라서
                      # 코드의 variable[1], 즉 y에 대한 편미분을 계산할 때, 다른 변수인 x(코드에서 variable[0])은 고정된 상태를 유지해야 한다.
    return grad

 

def FunctionW(variable):
    return variable[0]**2 + variable[1]**2 + variable[2]**2 # x^2 + y^2 + z^2
x, y, z = 1.0, 2.0, 3.0 # 실수로 정의
NumericalGradient(FunctionW, np.array([x, y, z]))
```#결과#```
array([2., 4., 6.])
````````````

결과를 보면 점 (1, 2, 3)에서의 기울기가 계산된 것을 볼 수 있다.

x, y, z = 1, 2, 3 # 정수를 사용할 경우
NumericalGradient(FunctionW, np.array([x, y, z]))
```#결과#```
array([ 5000, 15000, 25000])
````````````

# 정수형과 실수형 간의 연산이 자동으로 변환되는 과정에서 원하지 않는 결과가 나올 수 있다.
# 예를 들어, variable[i] + h나 variable[i] - h 연산 시 정수형 변수가 있으면 정확한 차이를 반영하지 못할 수 있다.

■ \( w = x^2 + y^2 + z^2 \)의 각 지점에서 기울기를 구하게 되면 무수히 많은 벡터를 얻을 수 있다. 예를 들어 다음과 같은 구간에서 각 지점의 기울기를 구하면 다음과 같다.

i_values = np.arange(0.0, 2.1, 0.5)
z_values = np.arange(0.0, 2.1, 0.5)
k_values = np.arange(0.0, 2.1, 0.5)

for i in i_values:
    for z in z_values:
        for k in k_values:
            gradient = NumericalGradient(FunctionW, np.array([i, z, k]))
            print(f"i={i}, z={z}, k={k}, gradient={gradient}")
            
```#결과#```
i=0.0, z=0.0, k=0.0, gradient=[0. 0. 0.]
i=0.0, z=0.0, k=0.5, gradient=[0. 0. 1.]
i=0.0, z=0.0, k=1.0, gradient=[0. 0. 2.]
i=0.0, z=0.0, k=1.5, gradient=[0. 0. 3.]
i=0.0, z=0.0, k=2.0, gradient=[0. 0. 4.]
i=0.0, z=0.5, k=0.0, gradient=[0. 1. 0.]
i=0.0, z=0.5, k=0.5, gradient=[0. 1. 1.]
i=0.0, z=0.5, k=1.0, gradient=[0. 1. 2.]
i=0.0, z=0.5, k=1.5, gradient=[0. 1. 3.]
i=0.0, z=0.5, k=2.0, gradient=[0. 1. 4.]
i=0.0, z=1.0, k=0.0, gradient=[0. 2. 0.]
i=0.0, z=1.0, k=0.5, gradient=[0. 2. 1.]
i=0.0, z=1.0, k=1.0, gradient=[0. 2. 2.]
i=0.0, z=1.0, k=1.5, gradient=[0. 2. 3.]
i=0.0, z=1.0, k=2.0, gradient=[0. 2. 4.]
i=0.0, z=1.5, k=0.0, gradient=[0. 3. 0.]
i=0.0, z=1.5, k=0.5, gradient=[0. 3. 1.]
i=0.0, z=1.5, k=1.0, gradient=[0. 3. 2.]
i=0.0, z=1.5, k=1.5, gradient=[0. 3. 3.]
i=0.0, z=1.5, k=2.0, gradient=[0. 3. 4.]
...
...
...
...
...
```````````````

 

이 기울기들이 가리키는 것을 \( f(x, y) = x^2 + y^2 \)로 차수를 2차원으로 낮춰 기울기를 보면 다음 그림과 같다.

■ 다변수에서 모든 변수의 편미분을(= 각 변수에 대한 미분을) 벡터로 표현한 것이 기울기이며, 기울기는 순간 변화량으로 기울기가 작다면 그 순간의 변화량은 작은 것이고, 기울기가 크다면 그 순간의 변화량이 큰 것이다. 

■ 위 예시의 2차원 함수 \( z = f(x, y) = x^2 + y^2 \)는 설정한 범위 내에서 \( x = 0, y = 0 \)일 때 함수의 최솟값을 갖는데,

위의 \( z = x^2 + y^2 \)의 기울기 그림을 보면 모든 벡터의 방향은 지정된 범위에서의 함수의 최솟값을 가리키며 최솟값과 가까워질수록 벡터 크기가 작아지고 최솟값과 멀이질수록 벡터가 커지는 것을 볼 수 있다. 

벡터는 크기와 방향을 가지며, 두 정보를 모두 표현할 수 있는 화살표로 나타낸다는 점을 고려하면,

- 최솟값과 가까울수록 순간 변화량(기울기)이 작아지고 ( = 벡터의 크기가 작아지고)

- 최솟값과 멀어질수록 순간 변화량(기울기)이 커지는 것이므로 (= 벡터의 크기가 커지고)

함수의 최솟값에 가까워질수록 변화량이 작아진다. 즉, 기울기가 완만해진다는 것과 각 기울기가 가리키는 방향들은 각 좌표에서 함수의 출력값을 가장 크게 줄이는 방향을 가리키는 것임을 알 수 있다.

예시 \( z = x^2 + y^2 \)의 3차원 그림과 \( x-y \)평면에서의 기울기

 

5. 방향도함수(directional derivative)

■ 방향도함수는 말 그대로 어떤 방향으로의 기울기이다.

■ \( x \)축, \( y \)축, \( z \)축 방향으로의 기울기가 아닌 어떤 사선의 방향에 대한 접선의 기울기는  방향도함수로 계산한다.

■ \( z = f(x, y) \)에서 \( P_{o}(a, b) \)를 \( xy \)평면의 고정된 점,  \( P_{o}(a, b) \)를 통과하는 직선을 직선 \( l \)이라 했을 때,

- 직선 \( l \) 위의 점 \( P \)가 직선 \( l \)이 흘러가는 방향을 따라서 움직이면 \( z = f(x ,y) \)는 곡면상에 곡선 \( C \)를 그리게 된다.

- 점 \( P(x, y) \)에 대응하는 곡선 \( C \)상의 점을 \( Q \), \( P_{o} \)와 \( P \)의 거리를 \( t \)라고 한다면, \( t \)에 관한 \( Q \)의 \( z \)값 변화율이 생기게 된다.

- \( P_{0} \)를 시점으로 \( P \)를 향한 단위방향벡터 \( \vec{u} = (u_1, u_2) \)라 하면 다음의 식이 성립된다.\[
\overrightarrow{P_{o}P} = t \Longleftrightarrow \overrightarrow{P_{o}P} = h \cdot \vec{u} \Longleftrightarrow (x - a, y - b) = h (u_1, u_2) \Longleftrightarrow x = a + h \cdot u_1, \; y = b + h \cdot u_2
\]
- 이때, \( f_{x}, f_{y} \)가 존재하면, \( z = f(x, y) \)에 대해 \( \vec{u} = (u1, u2) \)으로의 방향도함수는 \( \vec{u} \)의 시점이 \( (a, b) \)이므로, 점 \( (a, b) \)에서 \( \vec{u} \)방향으로의 접선의 기울기를 의미하며,
이는 \(
\displaystyle \lim_{h \to 0} \dfrac{f(a + h \cdot u_1, b + h \cdot u_2) - f(a, b)}{h}
\)이며, \( \dfrac{0}{0} \)이므로 로피탈 정리에 의해, ( \(s = a + h \cdot u_1, t = b + h \cdot u_2) \)로 치환, 이때 \( \vec{u} \)는 단위 방향 벡터

\[
\displaystyle \lim_{h \to 0} \dfrac{f(s, t) - f(a, b)}{h} = \displaystyle \lim_{h \to 0} \dfrac{f_s \times u_1 + f_t \times u_2}{h} = \displaystyle \lim_{h \to 0} \left( f_s, f_t \right) \cdot \left( u_1, u_2 \right) = \displaystyle \lim_{h \to 0} \nabla f(s, t) \cdot (u_1, u_2) = \nabla f(a, b) \cdot \vec{u} = D_{\vec{u}} f(a, b)
\]

■ 따라서 점 \( (a, b) \)에서 \( \vec{u} \)방향으로의 방향도함수는 \(
D_{\vec{u}} f(a, b) = \nabla f(a, b) \cdot \vec{u} = \left( \frac{\partial z}{\partial x}, \frac{\partial z}{\partial y} \right) \Bigg|_{(x, y) = (a, b)} \cdot \vec{u}
\) 이다.

 

■ 3변수함수 \( w = f(x, y, z) \)에서 점 \( (a, b, c) \)에서의 \( \vec{u} \)방향으로의 방향도함수도 \( D_{\vec{u}} f(a, b, c) = \nabla f(a, b, c) \cdot \vec{u} \)를 계산하면 된다.

 

6. 경사 하강법

■ 좋은 결과를 얻기 위해선 신경망 학습 단계에서 손실 함수를 지표로 손실 함수가 최솟값을 가지게하는 최적의 가중치, 편향을 찾아야 하며, 경사 하강법은 기울기를 이용해 이 손실 함수의 최솟값을 찾는 방법이다.

■ 즉, 각 좌표에서 손실 함수의 값을 낮추는 지표가 gradient인 것이다. 

■ 단, 기울기가 가리키는 곳에 정말 함수의 최솟값이 있는지 보잘할 수 없다. 왜냐하면 최솟값, 극솟값, 안장점이 되는 지점에서의 미분 값이 0, 즉 기울기가 0이기 때문이다. 

- 극솟값의 정의는 어떤 함수 \( f(x) \)가 적당한 양수 \( h \)에 대해 구간 \( (a - h, a + h) \)에서 \( f(x) \geq f(a) \)를 만족하면 함수 \( f(x) \)는 \( x = a \)에서 극소라 하며, \( f(a) \)를 극솟값이라 한다.

- 즉, 함수 \( f \)의 \( x = a \)에서의 \( f(a) \)값이 \( x = a \)와 가까이 있는 모든 \( x \)에서의 \( f(x) \)값보다 작을 때, 함수 \( f \)는  \( x = a \)에서 극소라고 하며, \( f(a) \)를 극솟값이라고 한다. 

- 극소점은 예를 들어 \( y = f(x) \)라면 \( y = f(x) \)위의 점 \( (a, f(a)) \)를 극소점이라고 한다.

- 극솟값은 어떤 제한된 범위에서의 최솟값이기 때문에 극솟값을 가지는 지점이 함수 전체에서 최솟값을 가지는 지점이라고 보장할 수 없는 것이다.

- 안장점은 점 \( (a_1, a_2, ... , a_n) \)이 다변수 함수 \( f(x_1, x_2, ... , x_n) \)의 안장점이면 영벡터가 아닌 벡터 \( (M_1, M_2, ... , M_n) \)와 \( (m_1, m_2, ... , m_n) \)이 존재할 때,

- 어떤 방향 \( ((M_1, M_2, ... , M_n)) \)에 대해 함수 \( g(t) = f(a_1 + tM_1, a2+ tM_2, ... , a_n + tM_n) \)는 t = 0에서 극대가 되고 다른 방향 \( ((m_1, m_2, ... , m_n)) \)에 대해 함수 \(h(t) = f(a_{1} + tm_{1}, a_{2} + tm_{2}, \dots, a_{n} + tm_{n}) \)는 t = 0에서 극소가 된다. 

- 즉, 안장점은 같은 지점일지라도 어느 방향에서 보면 극댓값이고, 다른 방향에서 보면 극솟값이 되는 지점이다.

- 예를 들어 \( f(x, y) = x^2 - y^2 \)의 안장점은 \( (0, 0) \)인데, \( x \)방향으로는 \( f(x, 0) = x^2 \)이므로 아래로 볼록한 형태의 곡선이라 \( (0, 0) \)에서 극솟값처럼 보이지만 \( y \)방향으로는 \( f(0, y) = -y^2 \)이므로 위로 볼록한 형태의 곡선을 그려 \( (0, 0) \)에서 극댓값처럼 보인다.

\( z = x^2 - y^2 \)의 구조와 (0, 0)에서의 안장점

■ 손실 함수의 전역 최솟값으로 수렴하지 않고, 지역 극솟값을 최솟값으로 판단해 지역 극솟값으로 수렴하는 문제를 local minima 문제라고 한다. 

■ 다음 그림처럼 지역 극솟값에 수렴하면 가중치를 업데이트해도 다시 지역 극솟값에 빠질 수 있어 학습이 제대로 진행되지 않는 정체기가 발생할 수 있다.

cf) 볼록 함수(Convex Function)와 비볼록 함수(Non-Convex Function)의 차이

볼록 함수와 비볼록 함수의

- 볼록 함수는 함수 형태가 볼록하기 때문에 어떤 지점에서 시작해도 최적값(손실함수가 최소가 되는 지점)에 도달할 수 있다.

- 그러나 비볼록 함수는 위의 그림과 같이 완전한 그릇 모양이 아니기 때문에 시작점 위치에 따라 다른 최적값(local minimum)에 수렴할 수 있다.

■ 따라서 기울기가 가리키는 방향이 무조건 최솟값을 가리키는 것은 아니지만, 결국엔 최솟값을 가리키는 방향이 아닐지라도 그 방향으로 이동해야 손실 함수의 값이 줄어든다.

■ 경사법은 위의 그림처럼 현재 지점에서 기울기를 구하고 그 기울어진 방향으로 일정 거리만큼 이동한다. 그리고 이동한 곳에서도 마찬가지로 기울기를 구하고, 그 기울어진 방향으로 일정 거리만큼 이동하며, 손실 함수의 값이 변하지 않을 때까지 반복해서 손실 함수의 값을 줄여 나간다.

[출처] Life is gradient descent ❘ HackerNoon

■ 만약, 함수가 2변수 함수 \( z = f(x, y) \) 함수라면 경사법의 수식은 \( x = x - \eta \dfrac{\partial f}{\partial x}, \quad y = y - \eta \dfrac{\partial f}{\partial y} \)이다.

- 여기서 \( \eta \)(eta)는 갱신하는 양을 나타내며, 신경망 학습에서 학습률(learning rate)이라고 한다.

■ 경사법의 수식은 '갱신할 가중치 = 기존 가중치 - 학습률 × 손실 함수의 기울기'이며, 학습률은 가중치를 갱신할 때 손실 함수의 기울기(= 각 변수의 편미분 값)를 얼마나 반영할지 조절하는 역할을 한다.

■ 학습률, step size 값은 사용자가 설정해야 하는 하이퍼파라미터 값이며, 값을 너무 크게 설정하면 발산하고, 너무 작게 설정하면 학습이 오래 걸리거나 최저점에 도달하지 못한다. 즉, 올바른 학습으로 이어지지 않는다.

step size를 너무 크게 설정한 경우, 올바르게 설정한 경우, 너무 작게 설정한 경우

따라서 일반적으로 사용자가 학습률 값을 바꿔 가면서, 올바르게 학습하고 있는지 확인해야 한다.

■ 위의 내용을 바탕으로 파이썬에서 경사 하강법을 구현하는데 필요한 변수는 경사 하강법을 적용할 함수, 초깃값, 학습률 그리고 반복할 횟수가 필요하며 함수의 기울기를 계산하는 메소드가 필요하다.

def GradientDescent(f, init_x, lr, stem_num):
    x = init_x
    for i in range(step_num):
        grad = NumericalGradient(f, x) # 반복 횟수만큼 기울기를 구하고
        x -= lr * grad # 기울기 × 학습률 값으로 매개변수 갱신
    return x

예를 들어 \( z = x^2 + y^2 \)에 초깃값을 설정하고 경사법을 이용해 이 함수의 최솟값을 찾아보면 다음과 같이 실제 최솟값인 (0, 0)에 가까워 지는 것을 볼 수 있다.

GradientDescent(function2, init_x = np.array([-5.0, 5.0]), lr = 0.1, step_num = 100)
```#결과#```
(array([-0.000000001,  0.000000001]),
array([[-5.          ,  5.          ],
        [-4.          ,  4.          ],
        [-3.2         ,  3.2         ],
        [-2.56        ,  2.56        ],
        [-2.048       ,  2.048       ],
        [-1.6384      ,  1.6384      ],
        [-1.31072     ,  1.31072     ],
        [-1.048576    ,  1.048576    ],
        [-0.8388608   ,  0.8388608   ],
        [-0.67108864  ,  0.67108864  ],
        [-0.536870912 ,  0.536870912 ],
        [-0.4294967296,  0.4294967296],
        [-0.3435973837,  0.3435973837],
        ...
        ...
        ...
        [-0.0000000076,  0.0000000076],
        [-0.0000000061,  0.0000000061],
        [-0.0000000049,  0.0000000049],
        [-0.0000000039,  0.0000000039],
        [-0.0000000031,  0.0000000031],
        [-0.0000000025,  0.0000000025],
        [-0.000000002 ,  0.000000002 ],
        [-0.0000000016,  0.0000000016],
        [-0.0000000013,  0.0000000013]]))

■ 만약, 학습률을 너무 크거나 작게 설정한다면, 학습이 제대로 진행될 수 없다.

학습률을 너무 작게 설정하면 다음과 같이 값이 거의 갱신되지 않은 채 학습이 종료되고

## 학습률 작게 설정
GradientDescent(function2, init_x = np.array([-5.0, 5.0]), lr = 0.0001, step_num = 100)
```#결과#```
(array([-4.9009835632,  4.9009835632]),
 array([[-5.          ,  5.          ],
        [-4.999       ,  4.999       ],
        [-4.9980002   ,  4.9980002   ],
        [-4.9970006   ,  4.9970006   ],
        [-4.9960011998,  4.9960011998],
        [-4.9950019996,  4.9950019996],
        [-4.9940029992,  4.9940029992],
        [-4.9930041986,  4.9930041986],
        [-4.9920055978,  4.9920055978],
        [-4.9910071966,  4.9910071966],
        ...
        ...
        [-4.9068688625,  4.9068688625],
        [-4.9058874888,  4.9058874888],
        [-4.9049063113,  4.9049063113],
        [-4.90392533  ,  4.90392533  ],
        [-4.902944545 ,  4.902944545 ],
        [-4.901963956 ,  4.901963956 ]]))

학습률을 너무 크게 설정하면 다음과 같이 값이 너무 큰 값으로 발산한다.

(array([ 1.802203168e+12, -1.802203168e+12]),
 array([[-5.0000000000e+00,  5.0000000000e+00],
        [ 9.5000000000e+01, -9.5000000000e+01],
        [-1.8050000000e+03,  1.8050000000e+03],
        [ 3.4295000003e+04, -3.4295000003e+04],
        [-6.5160499675e+05,  6.5160499675e+05],
        [ 1.2380493392e+07, -1.2380493392e+07],
        [-2.3523200661e+08,  2.3523200661e+08],
        [ 4.4687679934e+09, -4.4687679934e+09],
        [-8.5233632007e+10,  8.5233632007e+10],
        [ 1.8022031680e+12, -1.8022031680e+12],
        [ 1.8022031680e+12, -1.8022031680e+12],
        ...
        ...
        [ 1.8022031680e+12, -1.8022031680e+12],
        [ 1.8022031680e+12, -1.8022031680e+12],
        [ 1.8022031680e+12, -1.8022031680e+12],
        [ 1.8022031680e+12, -1.8022031680e+12],
        [ 1.8022031680e+12, -1.8022031680e+12],
        [ 1.8022031680e+12, -1.8022031680e+12]]))

따라서 좋은 결과를 얻기 위해선 학습률을 적절히 설정하는 것이 중요하다.

 

* 경사 하강법이 기울기(gradient) 반대 방향으로 매개변수 갱신을 하는 이유

- 위의 내용들을 정리하자면, 경사하강법은 손실 함수가 최대한 작아질 수 있도록 매개변수\( (\theta) \) 값을 갱신한다.

- 중요한 것은 다음 그림처럼 손실 함수가 전역 최솟값으로 수렴해야 손실 함수 값이 최대한 작아지기 때문에 경사 하강법을 통해 \( \theta \)가 어떤 방향으로 갱신해야 변화율이 가장 큰 방향으로, 즉 효과적으로 손실 함수 값을 줄일 수 있는가에 대한 이해가 필요하다.

- 먼저 \( \theta \)에 의한 손실 함수의 변화율을 구해야 한다. 이때, 손실 함수는 다변수 함수이므로 \( \theta \)는 \(\theta_{1}, \theta_{2}, \dots, \theta_{n} \)가 될 수 있다. 따라서 \( \theta \)에 의한 변화율은 \(\theta_{1}, \theta_{2}, \dots, \theta_{n} \)으로 인한 변화율을 구하는 것이다.

- 따라서 \( \theta_{i = 1, 2, ... , n} \)마다, 즉 각 매개변수마다 미분을 진행해야 하므로 편미분을 계산해야 한다. 

- 즉, \( \theta \)에 의한 손실 함수의 변화율은 각 \( \theta_{i = 1, 2, ... , n} \) 방향으로의 편미분으로 생각할 수 있으며, 이는 \( x \)축, \( y \)축, \( z \)축 방향으로의 기울기(= \( x \)에 대한 편미분, \( y \)에 대한 편미분, \( z \)에 대한 편미분)가 아닌 초기값에서 \( \vec{u} \)방향으로의 접선의 기울기를 계산하는 방향도함수를 계산해야 한다.

- \(\theta_{1}, \theta_{2}, \dots, \theta_{n} \)의 편미분을 동시에 계산하기 위해선 모든 변수의 편미분을 벡터 형태로 정리한 gradient(기울기, 경도)를 계산해야 한다.

- 이는 \( \nabla f = \langle f_{\theta_1}(\theta), f_{\theta_2}(\theta), \dots, f_{\theta_n}(\theta) \rangle \)로 표현할 수 있다.

- 즉, 각 \( \theta \)에서의 손실 함수 변화율을 \( \nabla f \)를 이용해 벡터로 표현할 수 있으며, 이 벡터는 손실 함수 값을 가장 크게 줄이는 방향을 가리키며, 벡터는 크기도 갖기 때문에 손실 함수를 최소화하는 지점과 가까워지면 크기가 작아지고 멀어지면 크기가 커진다.

- 이 벡터들은 결국 기울기 값, 즉 변화율에 대한 값으로, 변화율이 클수록 벡터의 크기가 커지는 것이며, 변화율이 크다는 것은 그만큼 경사가 가파르다는 것을 의미하며, 경사가 가파르다는 것은 그만큼 \(\theta \)가 손실 함수 값이 작아지도록 \( \theta \)값을 업데이트한 것이다.

- 따라서 \( \nabla f \), 벡터가 의미하는 것은 다음 그림처럼, 경사(기울기)가 가파를 때, 벡터의 크기가 크므로 그 크기만큼 손실 함수 값이 빠르게 작아지다가 최솟값에 가까워질수록 경사가 완만, 즉 벡터의 크기가 작아지므로 손실 함수 값이 줄어드는 속도가 점차 늦어지는 것을 나타낸 것이다. 다음 그림의 \( x \)축을 \( \theta \), \( y \)축을 손실 함수로 생각하면 된다.

[출처] Life is gradient descent ❘ HackerNoon

- 방향 도함수를 이용하는 이유가 사실 바로 이것이다.

- 위의 그림처럼 경사 하강법은 \( \theta \)의 이동에 따라 손실 함수가 줄어들며 이동 방향이 손실 함수 최대 감소 방향이면, 좋은 학습을 한 것이다.

- 즉, \( \theta \)의 이동 방향은 손실 함수의 최대 감소 방향으로 이어져야 한다. 이를 그림으로 나타내면

\( \theta_{old} \)에서 \( \theta_{new} \)으로 특정 방향 직선 \( l \)을 따라 점이 움직이면, 이에 따라 손실 함수는 곡선 \( C\)를 그리게 되며,

- \( P, Q \)는 각각  \( \theta_{old} \)와 \( \theta_{new} \)에 대응하는 손실 함수 \( f(x, y) \) 상의 점들이다. 즉, 이 점들은 매개변수가 변화함에 따라 손실 함수의 값이 어떻게 변화하는지를 나타낸다.

- 점 \( P \)에서 \( Q \)로 이동할 때 손실 함수가 변하는 경로를 \( C \)라 하면, 이 곡선은 매개변수 변화에 따른 손실 함수의 변화 경로이다.

- \( \theta_{old} \)와 \( \theta_{new} \)의 거리를 \( t \)라 했을 때, \( \theta \)가 특정 방향으로 \( t \) 거리만큼 이동했을 때, 손실 함수의 값이 갱신된다.

- 이를 계산하기 위해선 \( f \) 위의 점에서 임의의 방향 \( \vec{u} \)(= 단위 방향 벡터)로의 변화율을 계산해야 하는데, 이를 계산할 수 있는 것이 바로 방향 도함수이다.

- 여기서 \( \vec{u} \)는 \( \theta_{old} \)에서 \( \theta_{new} \)로의 방향을 나타내는 단위 방향 벡터로서 \( \nabla f \cdot \vec{u} \)는 \( \theta_{old} \)에서 \( \theta_{new} \)로 이동할 때 그 방향에서의 손실 함수의 변화율이다.

- 따라서 찾아야 하는 것은 방향 도함수가 최소인 경우이다.

[출처] The Data Guide to ML (akkio.com)

 위의 그림과 같이 \( \theta \)가 이동할 때 손실 함수의 변화율이 작아질수록 손실 함수의 최솟값에 다가가기 때문이다.

- 방향도함수 \( D_{\vec{u}} f(\theta) = \nabla f \cdot \vec{u} = \| \overrightarrow{\nabla f} \| \| \overrightarrow{u} \| \cos \phi \)이며, \( \cos \phi \)는 -1에서 1 사이의 값 \( (-1 \leq cos \leq 1) \)을 가지며, 이때 \( \| \overrightarrow{u} \| \)는 단위 벡터이므로 크기가 1이다.

- 따라서 방향 도함수의 최대 및 최소는 \( - \| \overrightarrow{\nabla f} \| \leq \| \overrightarrow{\nabla f} \| \cos \phi \leq \| \overrightarrow{\nabla f} \| \)라 할 수 있으며, 최대 및 최소는 코사인 값에 의해 결정되게 된다.

- 만약, \( \| \overrightarrow{\nabla f} \| \cos \phi \)가 최댓값 \( \| \overrightarrow{\nabla f} \| \)이 되려면 코사인 값이 1이 되는 경우이므로, \( \phi = 0 \)이 되야 한다. 즉, \( \nabla f \)와 \( \overrightarrow{u} \)가 같은 방향임을 의미한다.

- 반대로 \( \| \overrightarrow{\nabla f} \| \cos \phi \)가 최솟값이 \( - \| \overrightarrow{\nabla f} \| \)이 되려면 코사인 값이 -1이 되는 경우이므로, \( \phi = \pi \)가 되야 한다. 즉 \( \nabla f \)와 \( \overrightarrow{u} \)가 반대 방향임을 의미한다.

- 즉, 방향 도함수가 최소일 때 \( \overrightarrow{u} \)와 \( \nabla f \)의 \( \phi = 180^\circ \), 반대 방향인 것이다.

- 따라서 방향 도함수가 최소가 되는 \( \overrightarrow{u} \)로의 방향은 \( -\nabla f \)일 때이며 \( \nabla f \)는 기울기(gradient)이므로 이는, 방향 도함수가 최소가 되는 방향은 기울기 반대 방향임을 의미한다.

= 정리하자면, 손실 함수의 감소율이 최대가 될 수 있는 것은 방향 도함수 \( D_{\vec{u}} f(\theta) \)가 최소인 경우, 이 방향 도함수가 최소가 되는 방향은 기울기 반대 방향이므로, \( \theta \)가 손실 함수 값을 최소로 만들기 위해 기울기(gradient) 반대 방향으로 업데이트 되는 것이다.

 

7. 신경망에서의 가중치 매개변수에 대한 손실 함수의 기울기

■ 예를 들어 신경망의 매개변수인 가중치가 \( W = \begin{pmatrix}
w_{11} & w_{12} & w_{13} \\
w_{21} & w_{22} & w_{23}
\end{pmatrix}_{2 \times 3} \), 신경망의 손실 함수가 \( f \)라고 했을 때, 경사는 \(
\dfrac{\partial f}{\partial w} =
\begin{pmatrix}
\dfrac{\partial f}{\partial w_{11}} & \dfrac{\partial f}{\partial w_{12}} & \dfrac{\partial f}{\partial w_{13}} \\
\dfrac{\partial f}{\partial w_{21}} & \dfrac{\partial f}{\partial w_{22}} & \dfrac{\partial f}{\partial w_{23}}
\end{pmatrix}
\)이다.

- \(
\dfrac{\partial f}{\partial w}
\)의 각각의 원소는 각 가중치 원소에 대한 편미분이므로,

- 예를 들어 \(
\dfrac{\partial f}{\partial w_{11}}
\)은 \( w_{11} \)을 갱신했을 때, 손실 함수 \( f \)가 얼마나 변하는지, 즉 \(w_{11} \)에 의한 손실 함수 \( f \)의 변화율을 의미한다.

■ 예를 들어 가중치가 \(
W = \begin{pmatrix}
0.03828268 & -0.43019409 & -1.07092271 \\
1.18140832 & -0.63703929 & 0.26499831
\end{pmatrix}
\)인 상태에서 손실 함수에 대해 경사 하강법으로 가중치를 갱신했을 때, 결과가 \(
W = \begin{pmatrix}
0.43723716 & 0.06425033 & -0.50148749 \\
0.65585575 & 0.09637549 & -0.75223124
\end{pmatrix}
\)라면,

- \( \dfrac{\partial f}{\partial w} \)의 \( \dfrac{\partial f}{\partial w_{11}} \) 은 약 0.4인데, 이는 \( w_{11} \)을 \( h \)만큼 늘리면 손실 함수의 값은 \( 0.4h \)만큼 증가한다는 의미이다. 반대로 \(
\dfrac{\partial f}{\partial w_{13}}
\)은 약 -0.5이므로 \( w_{13} \)을 \( h \)만큼 늘리면 손실 함수의 값은 \( 0.5h \)만큼 감소한다는 의미이다.

- 손실 함수의 값을 줄이려면, 매개변수는 기울기(gradient) 반대 방향으로 갱신해야 하므로 \( w_{11} \)은 음의 방향으로 \( w_{13} \)은 양의 방향으로 갱신해야 한다. 

- 그리고 \(
\dfrac{\partial f}{\partial w_{13}}
\)과 \(
\dfrac{\partial f}{\partial w_{11}}
\)의 값 중, 부호를 무시하면 \(
\dfrac{\partial f}{\partial w_{13}}
\)의 수치가 더 크다. 이는, 한 번 갱신될 때 갱신되는 양은 \( w_{13} \)이 \( w_{11} \)보다 크게 기여한다는 것을 의미한다.

'딥러닝' 카테고리의 다른 글

합성곱 신경망(CNN) (1)  (0) 2024.11.01
오차역전파 (3)  (0) 2024.09.24
신경망 학습(1)  (1) 2024.09.12
신경망 (2)  (0) 2024.09.05
신경망(1)  (0) 2024.08.30