인공신경망 ( ANN ) 4-2 학습 ( 미분, 기울기, 경사법, 신경망 학습 과정 )
미분 (differential)
\[\frac{d}{dx}f(x) = \lim_{\Delta x \to 0}\frac{f(x + \Delta x) - f(x)}{\Delta x}\] x+h와 x 사이의 기울기를 얻는 것(전방 차분) 보다 x-h와 x+h 사이의 기울기를 얻는 것(중심 차분, 중앙 차분)이 오차가 더 적다. 부동소수점 오차가 발생하지 않도록 h는 1e-4
(0.0001)보다 커야 한다.
이는 실제 미분값이 아니라 실제 값에 대한 근사값을 구하는 수치(numerial) 미분 이다. 실제로 수식을 미분해 도함수를 구하는 것을 해석적(analytic) 미분 이라 한다.
1
2
3
def numerical\_diff(f, x):
h = 1e-4
return (f(x-h) + f(x+h))/ (2\*h)
접선의 기울기를 수치 미분을 이용해 구하면 다음처럼 쓸 수 있다.
1
2
3
def tangent\_line(f, x, a):
g = numerical\_diff(f, a)
return g\*(x-a) + f(a)
편미분, 기울기(gradient)
\(y = f(x_1, x_2, …, x_n)\) 다변수 함수를 미분하려면 일단 각각의 변수를 미분(편미분, \(\frac{\partial f}{\partial x_i}\))해야 한다.
스칼라 함수 \(f(x)\)의 기울기 gradient는 \(\nabla f\)로 표현한다 \(\nabla f = (\frac{\partial f}{\partial x_1}, …, \frac{\partial f}{\partial x_n})\)
다변수 함수의 gradient. 단, x가 다차원 배열로 주어지면 처리할 수 없다. np.nditer를 사용해서 iterator로 하나씩 반환받는 방법을 사용하면 다차원 배열을 처리할 수 있다. 그러나 어차피 뒤에서 오차역전파를 활용한 gradient를 사용하기 때문에 대충 넘어간다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def numerical\_gradient2(f, x):
h = 1e-4
g = np.zeros\_like(x)
for idx in range(x.size):
x[idx] += h
fx\_plus\_h = f(x)
x[idx] -= h #restore
x[idx] -= h
fx\_minus\_h = f(x)
g[idx] = (fx\_plus\_h - fx\_minus\_h) / (2\*h)
x[idx] += h #restore
return g
기울기는 벡터 미적분학에서 스칼라장의 최대의 증가율을 나타내는 벡터(또는 벡터장)을 뜻한다. 기울기를 나타내는 벡터장을 화살표로 표시할 때 화살표의 방향은 증가율이 최대가 되는 방향 이며, 화살표의 크기는 증가율이 최대일 때의 증가율의 크기를 나타낸다.
즉, 기울기에 음수를 취하면 함수의 출력을 가장 줄이는 방향을 가리킨다.
경사법(gradient mothod)
신경망에서 매개변수를 최적화 하기 위해 손실 함수를 사용한다고 했다. 기울기를 이용해 손실 함수의 최솟값을 찾는 방법이 경사법이다. 주의할 점은기울기가 가리키는 곳을 따라가도 함수의 최솟값(global minimum)이 있으리라는 보장은 없다는 것이다. 기울기가 0인 지점을 찾아가지만, 극댓값이나 안장점도 기울기가 0이다.
극솟값이어도 함수의 최솟값이라고 장담할 수는 없다. 또한 복잡하고 찌그러진 모양의 함수라면 대부분 평평한 곳으로 파고들면서 고원(plateau, 플래토)이라 하는 학습이 진행되지 않는 정체기에 빠질 수 있다고 한다. 이를 local minimum이라고 한다.
최근의 관점 : local minimum이라는 것은 별로 중요하지 않다. 어차피 차원이 증가하면, 갈 수 있는 길이 그 만큼 많다는 의미이므로 어떻게 해서 global minimum으로 갈 확률이 높다.
최솟값을 찾는 경사법을 경사 하강법(gradient descent method) , 최댓값을 찾는 경사법을 경사 상승법(gradient ascent method)라고 한다. 어차피 부호 반전하면 그게 그거라서 별 차이는 없다.
이런저런 문제가 있지만 아무튼 경사법은 기울기를 따라간다. 현 위치에서 기울기를 구해 그 방향으로 일정 거리를 이동하고, 그 지점에서 다시 기울기를 구해 그 방향으로 일정 거리를 이동하는 것을 반복한다. 이렇게 손실 함수의 값을 줄여 나가는 것이 경사법이다.
\(x_i = x_i - \eta \frac{\partial f}{\partial x_i}\) \(\eta\)은 에타(eta) 라고 읽는다. 갱신하는 양을 나타내며 이를 학습률(learning rate, lr) 이라 한다. 매개변수 값을 얼마나 갱신할지를 나타낸다.
학습률이 너무 크거나 작으면 손실 함수가 최소가 되는 위치를 찾아갈 수 없다. 신경망에서는 보통 학습률을 변경하면서 올바르게 학습하고 있는지를 확인하며 진행한다. 학습률은 사람이 경험적으로 직접 설정해야 하는데, 이런 매개변수를 하이퍼파라미터(hyper parameter) 라고 한다.
보통 lr이 너무 크면 발산하고, 너무 작으면 거의 갱신되지 않는다.
경사 하강법
1
2
3
4
5
6
def gradient\_descent(f, init\_x, lr=0.01, step\_num=100):
x = init\_x
for i in range(step\_num):
g = numerical\_gradient(f, x)
x -= lr \* g
return x
신경망의 기울기
이를 신경망에 대입하면, f는 손실 함수(L), x는 가중치(W)가 된다.
일 때, \(\nabla L\)은 다음과 같다.
1
2
f = lambda w: net.loss(x, t)
numerical\_gradient(f, net.W)
W가 다차원 배열(여기서는 2x3)으로 주어지기 때문에, x[idx]
로 배열의 원소에 접근하는 기존 numerical_gradient를 사용해서는 안돼서 다차원 배열을 처리할 수 있도록 개선해야한다.. numpy.nditer를 사용하면 다차원 배열의 순회를 간단히 처리할 수 있다.
신경망의 학습 과정
기울기 산출에서 수치 미분(numerical_gradient)를 사용했을 경우 신경망이 데이터를 학습해가는 과정을 정리하면 다음과 같다.
하이퍼 파라미터를 초기화한다. 1~3을 반복한다.
- 미니배치 훈련 데이터 중 일부를 무작위로 뽑아 미니배치를 만든다.
- 기울기 산출 가중치 매개변수에 대한 손실 함수의 gradient를 구한다. 이 때 보통 gradient(x, t)를 넘겨 내부에서 y를 계산하도록 한다.
- wrap_gradient 호출
- wrap_gradient에서 각각의 w, b에 대해 numerical_gradient(loss, 가중치)를 호출한다
- numerical_gradient에서 가중치 +- h를 파라미터로 loss를 호출한다
- loss에서 predict 호출하고 출력 y를 loss function(y, t)에 입력, loss_func의 출력값 리턴
- loss_func의 출력값은 각 가중치에 대한 손실함수의 변화량 이므로, gradient다.
- 매개변수 갱신 경사법을 사용해 가중치 매개변수를 2에서 계산한 gradient를 지표로 갱신한다. 데이터를 무작위로 선정하기 때문에 확률적 경사 하강법(stochastic gradient descent, SGD )라고 한다.
- 가중치 매개변수의 초깃값을 무엇으로 주느냐가 신경망 학습의 성공을 좌우하기도 한다.
- 학습 횟수가 늘어갈 수록 손실 함수의 값이 감소해야 한다.
비효율적인 수치미분
numerical_gradient는 각각의 가중치각각의 원소를 +h, -h 해서 순전파한 결과를 바탕으로 기울기를 계산 하게 되므로,
Wk의 gradient vector를 구하기 위한 순전파 횟수는 Wk의 원소 개수의 총 합 * 2
e.g )
x = 1*784 W1 = 784x10 b1 = 1*10 인 단층 레이어의 W1, b1에 대한 손실함수의 gradient를 구하기 위한 순전파 횟수는 각각 다음과같다. W1: [ 15680 ] b1: [ 20 ] W1: 15680 = 784x10 * 2 b1: 20 = 1*10 * 2
각각의 원소에 대해 편미분을 계산해야 하므로 너무 느리다.
그래서 실제로 동작할 때는 오차역전파법을 통해 효율적으로 gradient를 계산하도록 한다. 그러나 오차역전파법이 꽤 복잡해서 실수가 나올 수 있으므로 수치 미분을 이용해 오차역전파법을 제대로 구현했는지 검증하곤 하는데 이를 기울기 확인(gradient check) 이라 한다.