갑자기 미분이라는 개념이 등장하는 이유는 바로 기울기를 설명하기 위함입니다.
우리는 흔히 미분과 기울기의 연관성에 대해서는 알고 있습니다.
미분하면 가장 먼저 떠오르는 식입니다. 자세하게 다룰 건 아니기에 가볍게 코드 설명으로 넘어가도록 하겠습니다.
먼저 한 가지 미분 계산을 구현해보겠습니다.
def numerical_diff(f, x):
h = 1e-50
return (f(x + h) - f(x) / h)
아무 문제가 없는 것 같지만 위 코드에도 문제가 있습니다.
첫 번째는 부동소수점 관련 문제입니다. h의 값 때문인데 1e-50은 0이 50개인 0.000...1입니다. 그러나 이 수를 float형 32비트 부동소수점으로 나타내면 0.0이 되어 올바른 계산이 이루어지지 않습니다.
두 번째는 f의 차분인데 위에서 x + h가 의미하는 것은 x 사이의 기울기지만 실제로는 x 위치의 함수의 기울기이기 때문입니다.
이 문제점들을 개선한 미분 계산 코드는 다음과 같습니다.
# 미분
def numerical_diff(f, x):
h = 1e-4
return (f(x + h) - f(x - h) / (2 * h))
이처럼 아주 작은 차분으로 미분하는 것을 수치 미분이라고 합니다.
마찬가지로 편미분도 해보겠습니다.
# 편미분
def function_2(x):
return x[0]**2 + x[1]**2
#아니면 return np.sum(x**2)
예를 들어 x0=3이고, x1=4일 때, x0에 대한 편미분을 구한다면 다음과 같습니다.
def function_tmp1(x0):
return x0*x0 + 4.0**2.0
numerical_diff(function_tmp1, 3.0)
#6.00000000000378
기울기
앞선 내용에서 편미분을 변수별로 따로 계산했습니다. 기울기는 모든 변수의 편미분을 벡터로 정리한 것으로 변수와 편미분을 동시에 처리할 수 있습니다.
구현해 본다면 다음과 같습니다.
# 기울기
import numpy as np
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성
for idx in range(x.size):
tmp_val = x[idx]
# f(x+h) 계산
x[idx] = tmp_val + h
fxh1 = f(x)
# f(x-h) 계산
x[idx] = tmp_val - h
fxh2 = f(x)
grad[idx] = (fxh1 - fxh2 / (2*h))
x[idx] = tmp_val # 값 복원
return grad
경사법(경사 하강법)
여태까지 다뤘던 내용들에서 빠지지 않는 것이 바로 손실함수였습니다. 손실함수를 설정하는 이유는 최적의 상태를 찾아가기 위함인데 여기서 말하는 최적이란 손실함수가 최솟값이 될 때의 매개변수를 말합니다.
그러나 매개변수는 워낙 방대한 공간에 자리 잡고 있기 때문에 최솟값이 어디가 될지 찾는 것이 매우 어렵습니다. 이와 같은 상황에서 기울기를 이용하여 최솟값을 찾는 것이 경사 하강법입니다.
경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동합니다. 그다음 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가는 것을 반복합니다. 이렇게 하면서 함수의 값을 점점 줄여나가는 것이 경사법입니다.
경사법은 최솟값을 찾는 경사 하강법과 최댓값을 찾는 경사 상승법이 있습니다.
일반적으로 딥러닝에서는 경사 하강법이 많이 쓰입니다.
위는 경사법의 수식입니다. 여기서 n처럼 생긴 기호는 에타로 갱신하는 양 즉, 학습률을 나타냅니다.
위 식은 1회에 해당하는 갱신이고, 이 과정을 학습률의 값만큼 반복하는 식입니다. 그리고, 여기서 변수의 값을 갱신하는 단계를 여러 번 거쳐서 함수의 값을 줄이는 방법입니다.
또한 학습률 값은 0.01이나 0.001처럼 미리 특정 값으로 정해두어야 합니다. 그러나 이 값이 너무 작으면 최적의 장소에 찾아갈 수 없습니다. 아무리 횟수를 늘려도 보폭이 짧기 때문에 도달 시간이 현저히 많이 걸리기 때문입니다.
경사 하강법은 다음과 같이 구현할 수 있습니다.
# 경사 하강법
def gradient_descent(f, init_x, lr=0.01, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
인수 f는 최적화하려는 함수, init_x는 초기값, lr은 learning rate의 학습률, step_num은 경사법에 따른 반복 횟수를 말합니다. 흐름을 따라가 보면 함수의 기울기를 numerical_gradient(f, x)로 구하고, 그 기울기에 학습률을 곱한 값으로 갱신하는 처리를 step_num번 반복합니다.
학습률과 같은 매개변수들을 하이퍼파라미터라고 합니다. 매개변수라면 가중치와 편향을 언급할 수 있지만, 학습률은 이들과 성질이 다릅니다. 가중치와 편향은 순전파와 역전파 과정을 통해 자동으로 획득되거나 수정된다면 학습률은 사람이 직접 설정해야 합니다.
신경망의 학습에서도 기울기를 구해야 합니다. 예를 들어 2x3의 가중치와 손실함수를 설정한다고 할 때 경사 또한 2x3의 형상으로 나타나고 각 원소는 가중치로 편미분 한 값과 같습니다.
간단한 신경망으로 예를 들면 다음과 같습니다.
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 정규분포로 초기화
def predict(self, x):
return np.dot(x, self.W)
def loss(self, x, t):
z = self.predict(x)
y = softmax(z)
loss = cross_entropy_error(y, t)
return loss
x = np.array([0.6, 0.9])
t = np.array([0, 0, 1])
net = simpleNet()
f = lambda w: net.loss(x, t)
dW = numerical_gradient(f, net.W)
print(dW)
import 한 메서드들은 모두 사전에 정의된 메서드들입니다.
위의 코드에서 값을 바꿔볼 수 있고, lambda를 사용하여 편하게 바꿀 수도 있습니다.
이상으로 신경망의 학습에 필요한 개념인 기울기와 이를 이해하는데 필요한 수치 미분의 개념에 대해 알아봤습니다.
'AI > Deep learning from Scratch' 카테고리의 다른 글
[머신러닝, 딥러닝] 오차역전파법 (1) - 역전파와 계산법칙 (0) | 2023.02.14 |
---|---|
[머신러닝, 딥러닝] 신경망 학습 (4) - 학습 알고리즘 구현 (0) | 2023.02.13 |
[머신러닝, 딥러닝] 신경망 학습 (2) - 미니 배치 학습 (0) | 2023.02.12 |
[머신러닝, 딥러닝] 신경망 학습 (1) - 손실함수 (0) | 2023.01.24 |
[머신러닝, 딥러닝] 신경망 (4) - MNIST 손글씨 데이터 인식 (0) | 2023.01.23 |
댓글