2.4 신경망의 엔진:그래디언트 기반 최적화

첫 번째 신경망 예제에서 입력 데이터는 다음과 같이 변환됩니다.

output = relu(dot(W, input) * b)

여기에서 텐서 Wb는 층의 속성 처럼 볼 수 있는데, W가중치(weight), 또는 커널(kernel)1, b편향(bias) 이라 부르고, Wb들 통틀어 가중치 혹은 훈련되는 파라미터(trainable parameter)라고 부릅니다.

초기에는 가중치 행렬이 작은 난수로 채워져 있습니다. 이를 무작위 초기화(random initialization) 단계라고 부릅니다. 물론 Wb가 난수일 때 위 식은 의미 없는 표현이 만들어집니다. 하지만 이는 시작 단계일 뿐 그 다음 피드백 신호에 기초하여 가중치가 점진적으로 조정될 것입니다. 이런 점진적인 조정 즉, 훈련(training)이 머신 러닝 학습의 핵심입니다.

훈련은 다음과 같은 훈련 반복 루프( trainig loop) 안에서 역전파 알고리즘을 통해 이루어 집니다.

  1. 훈련 샘플 배치 x와 이에 상응하는 타깃 y를 추출합니다.
  2. x를 사용하여 네트워크를 실행하고(forward pass 단계), 예측 값 y_pred을 구합니다.
  3. 실제값 y과 예측값 y_pred 사이의 오차를 계산하여 현재 배치의 네트워크에 대한 손실을 계산합니다.
  4. 배치에 대한 손실이 감소하도록 네트워크의 가중치를 업데이트합니다.

결국 훈련 데이터에서 네트워크의 손실이 감소하여 정확한 타깃으로 매핑되어갈 것입니다.

단계별로 볼 때 1단계는 단순 입출력, 2, 3단계는 텐서 연산을 적용한 것으로 어렵지 않습니다. 그러나 4단계의 손실이 감소하도록 가중치를 업데이트 할 때 개별적인 가중치 값을 증가해야 할지 감소해야 할지, 얼만큼 업데이트 해야 할지를 정하는 문제는 어려움이 있을 것입니다.

이 문제는 간단하게는 원소를 모두 고정하고 하나씩 조정하며 계산하는 것으로 최소 증가, 감소의 2번의 연산을 해야 하므로 상당히 비효율적입니다. 따라서 신경망에 사용된 모든 연산이 미분 가능(differentiable)하다는 장점을 사용해 네트워크 가중치에 대한 손실의 그래디언트(gradient)2를 계산하여 반대방향으로 가중치를 변화시키는 효율적인 방법을 사용합니다.

미분가능하다는 것과 그래디언트에 대해 아시는 분은 2.4.3절로 건너뛰어도 좋습니다.

2.4.1 변화율이란?

실수 매핑되는 연속적이고 매끄러운 함수 f(x) = y를 생각해 봅시다. 이 함수는 아래와 같이 x를 작은 값 epsilon_x 만큼 증가시켰을 때 yepsilon_y 만큼 조금 바뀐다 할 수 있습니다.

f(x + epsilon_x) = y + epsilon_y

또한 epsilon_x가 충분히 작다면 한 포인트 p에서 기울기 a로의 선형 함수로 f를 근사화해 epsilon_ya*epsilon_x로 나타낼 수 있습니다. 이 기울기를 p에서 f변화율(drivative)3이라 합니다.

아래의 그림에서 살펴봅시다. 포인트 p에서 x를 양수만큼 조금 이동하면 기울기 a가 음수일 때는 f(x)가 감소하고, 양수일 때는 증가합니다.

나타낼 수 없음
그림 2-10. p에서의 f의 변화율

모든 미분가능한 함수 f(x)에 대해 x의 값을 f의 국부적인 선형 근사인 그 지점의 기울기로 매핑하는 변화율 함수 f'(x)가 존재합니다. 이러한 변화율을 알면 f(x)를 원하는 방향으로 수치를 변경할 수 있습니다. 예로 f(x)의 값을 감소시키고 싶다면 x의 변화율의 방향과 반대로 조금 이동해야 합니다.

2.4.2 텐서 연산의 변화율: 그래디언트

위의 변화율을 텐서 연산에 적용한 것이 그래디언트입니다. 다차원 입력인 텐서를 입력 받는 함수에 변화율 개념을 확장시킨 것 입니다.

입력 벡터 x, 행렬 W, 타깃 y와 손실 함수 loss가 있다고 가정합시다. W를 사용하여 타깃의 예측 y_pred를 계산하고 손실, 즉 타깃 예측 y_pred와 타깃 y사이의 오차를 계산할 수 있습니다.

y_pred = dot(W, x)
loss_value = loss(y_pred, y)

입력 데이터 xy가 고정되어 있다면 이 함수는 W를 손실 값에 매핑하는 함수로 볼 수 있습니다.

loss_value = f(W)

W의 현재 값을 W0라 하면, 포인트 W0에서 f의 변화율은 W와 같은 크기의 텐서인 gradient(f)(W0)4입니다. 이 텐서의 각 원소 gradient(f)(W0)[i, j]W0[i, j]를 변경했을 때 loss_value가 바뀌는 방향과 크기를 나타냅니다. 다시 말해 텐서 gradient(f)(W0)W0에서 함수 f(W) = loss_value의 그래디언트입니다.

앞서 함수 f(x)의 변화율 하나는 곡선 f의 기울기로 해석할 수 있다는 것을 보았습니다. 비슷하게 gradient(f)(W0)W0에서 f(W)의 기울기를 나타내는 텐서로 해석할 수 있습니다.

위의 변화율과 동일한 방식을 적용하면 함수 f(W)의 입장에서는 그래디언트의 반대 방향으로 W를 조금 움직이면 f(W)의 값을 줄일 수 있습니다.5 이 때 gradient(f)(W0)W0에 아주 가까이 있을 때 기울기를 근사한 것이므로 너무 크게 벗어나지 않기 위해 스케일링 비율 step을 아래와 같이 적용합니다.

W1 = W0 - step * gradient(f)(W0)

2.4.3 확률적 경사 하강법

미분 가능한 함수가 주어지면 이 함수의 최솟값은 변화율이 0인 지점입니다. 따라서 최솟값을 찾기 위해선 변화율이 0이 되는 지점을 모두 찾고 이 중에서 어떤 포인트의 함수 값이 가장 작은지 확인하면 됩니다.

신경망에 적용하면 손실 함수에서 가장 작은 값을 만드는 가중치의 조합을 해석적으로 찾는 것을 의미합니다. 이는 식 gradient(f)(W) = 0을 풀면 해결됩니다. 이 식은 N개의 변수로 이루어진 다항식입니다. 여기에서 N은 네트워크의 가중치 개수입니다. N이 작은 N=2나 N=3은 푸는 것은 가능하지만 실제 신경망에서는 파라미터의 개수가 수천 개 종종 수천만 개가 되기 때문에 해석적으로 해결하는 것이 어렵습니다.

그 대신 앞서 2.4절 초입에서 설명한 알고리즘 네 단계를 사용할 수 있습니다. 미분 가능한 함수를 가지고 있으므로 그래디언트를 계산하여 단계 4를 효율적으로 구현할 수 있습니다. 그래디언트의 반대 방향으로 가중치를 업데이트하면 손실이 매번 조금씩 감소할 것입니다.

  1. 훈련 샘플 배치 x와 이에 상응하는 타깃 y를 추출합니다.
  2. x를 사용하여 네트워크를 실행하고(forward pass 단계), 예측 값y_pred을 구합니다.
  3. 실제값 y과 예측값 y_pred 사이의 오차를 계산하여 현재 배치의 네트워크에 대한 손실을 계산합니다.
  4. 네트워크의 파라미터에 대한 손실 함수의 그래디언트를 계산합니다(backward pass 단계).
  5. 그래디언트의 반대 방향으로 파라미터를 조금 이동시킵니다. 예를 들어 W -= step * gradient처럼 하면 배치에 대한 손실이 조금 감소할 것입니다.

이것이 미니 배치 확률적 경사 하강법(mini-batch stochastic gradient descent) 줄여 미니 배치 SGD입니다. 확률적(stochastic)이란 단어는 각 배치 데이터가 무작위로 선택된다는 의미입니다. 네트워크의 파라미터와 훈련 샘플이 하나일 때 이 과정을 그림 2-11에 나타냈습니다.

나타낼 수 없음
그림 2-11. SGD가 1D 손실 함수(1개의 학습 파라미터)의 값을 낮춘다

그림에서 볼 수 있듯이 step 값을 적절히 고르는 것이 중요합니다. 이 값이 너무 작으면 곡선을 따라 내려가는 데 너무 많은 반복이 필요하고 지역 최솟값(local minimum)에 갇힐 수 있으며, step이 너무 크면 손실 함수 곡선에서 완전히 임의의 위치로 이동할 수 있습니다.

미니 배치 SGD 알고리즘의 한 가지 변종은 반복마다 하나의 샘플과 하나의 타깃을 뽑는 것입니다. 다른 한편으로 극단적인 반대의 경우를 생각해 보면 가용한 모든 데이터를 사용하여 반복을 실행할 수 있습니다. 이를 배치 SGD(batch SGD)라고 합니다. 더 정확하게 업데이트되지만 더 많은 비용이 듭니다. 극단적인 두 가지 방법의 효율적인 절충안은 적절한 크기의 미니 배치를 사용하는 것입니다.

또 업데이트할 다음 가중치를 계산할 때 현재 그래디언트 값만 보지 않고, 이전에 업데이트된 가중치를 여러 가지 다른 방식으로 고려하는 SGD 변종이 많이 있습니다. 예를 들어 모멘텀을 사용한 SGD, Adagrad, RMSProp 등입니다. 이런 변종들을 모두 최적화 방법(optimization method) 또는 옵티마이저라고 부릅니다. 특히 여러 변종들에서 사용하는 모멘텀(momentum)은 아주 중요합니다. 모멘텀은 SGD에 있는 2개의 문제점인 수렴 속도와 지역 최솟값을 해결합니다.

그림 2-13은 네트워크의 파라미터 하나에 대한 손실 값의 곡선을 보여 줍니다.

나타낼 수 없음
그림 2-13. 지역 최솟값(local minimum)과 전역 최솟값(global minimum)

그림에서 볼 수 있듯이 어떤 파라미터 값에서는 지역 최솟값에 도달합니다. 그 지점 근처에서는 왼쪽으로 이동해도 손실이 증가하고, 오른쪽으로 이동해도 손실이 증가합니다. 대상 파라미터가 작은 학습률을 가진 SGD로 최적화되었다면 최적화 과정이 전역 최솟값으로 향하지 못하고 이 지역 최솟값에 갇히게 될 것입니다.

물리학에서 영감을 얻은 모멘텀을 사용하여 이 문제를 피할 수 있습니다. 여기에서 최적화 과정을 손실 곡선 위로 작은 공을 굴리는 것으로 생각하면 쉽게 이해할 수 있습니다. 모멘텀이 충분하면 공이 골짜기에 갇히지 않고 전역 최솟값에 도달할 것입니다. 모멘텀은 현재 기울기 값 뿐만 아니라 현재 속도를 함께 고려하여 각 단계에서 공을 움직입니다. 실전에 적용할 때는 현재 그래디언트 값뿐만 아니라 이전에 업데이트한 파라미터에 기초하여 파라미터 w를 업데이트합니다.

다음은 단순한 구현 예입니다.6

past_velocity = 0.
momentum = 0.1     # 모멘텀 상수
while loss > 0.01: # 최적화 반복 루프
  w, loss, gradient = get_current_parameters()
  velocity = momentum * past_velocity - learning_rate * gradient
  w = w + momentum * velocity - learning_rate * gradient
  past_velocity = velocity
  update_parameter(w)

2.4.4 변화율 연결: 역전파 알고리즘

앞의 알고리즘에서 함수가 미분 가능하기 때문에 변화율을 직접 계산할 수 있다고 잠시 가정했습니다. 실제로 신경망은 많은 텐서 연산으로 구성되어 있고 이 연산들의 변화율은 간단하며 이미 잘 알려져 있습니다. 3개의 텐서 연산 a, b, c와 가중치 행렬 W1, W2, W3로 구성된 네트워크 f를 예로 들어 보겠습니다.

f(W1, W2, W3) = a(W1, b(W2, c(W3)))

미적분에서 이렇게 연결된 함수는 연쇄 법칙(chain rule)이라 부르는 다음 항등식 f(g(x))' = f'(g(x)) * g'(x)를 사용하여 유도될 수 있습니다. 연쇄 법칙을 신경망의 그래디언트 계산에 적용하여 역전파(Backpropagation) 알고리즘(후진 모드 자동 미분(reverse-mode automatic differentiation)이라고도 부릅니다)이 탄생되었습니다. 역전파는 최종 손실 값에서부터 시작합니다. 손실 값에 각 파라미터가 기여한 정도를 계산하기 위해 연쇄 법칙을 적용하여 최상위 층에서 하위 층까지 거꾸로 진행됩니다.

향후 몇 년 동안은 텐서플로처럼 기호 미분(symbolic differentiation)이 가능한 최신 프레임워크를 사용하여 신경망을 구현할 것입니다.7 이 말은 변화율이 알려진 연산들로 연결되어 있으면 (연쇄 법칙을 적용하여) 네트워크 파라미터와 그래디언트 값을 매핑하는 그래디언트 함수를 계산할 수 있다는 의미입니다. 이런 함수를 사용하면 역방향 패스는 그래디언트 함수를 호출하는 것으로 단순화될 수 있습니다. 기호 미분 덕택에 역전파 알고리즘을 직접 구현할 필요가 전혀 없고 정확한 역전파 공식을 유도하느라 시간과 노력을 소모하지 않아도 됩니다. 그래디언트 기반의 최적화가 어떻게 작동하는지 잘 이해하는 것으로 충분합니다.

  1. 커널은 여러 가지 의미로 사용됩니다. 1장에서는 서포트 벡터 머신의 커널 함수, 5장에서는 합성곱 신경망의 필터를 지칭합니다. 

  2. 자연스러운 문맥을 위해 gradient가 연산의 미분 결과를 의미하는 경우에는 그냥 ‘그래디언트’로 옮겼습니다. 

  3. derivative를 ‘도함수’로 번역할 수 있지만 가능하면 수학을 사용하지 않으려는 저자의 의도를 살려 ‘변화율’이라고 옮겼습니다. 

  4. 이 표기는 함수 f의 변화율 함수를 gradient(f)라고 했을 때 W0 지점의 그래디언트 텐서를 의미합니다. 

  5. 여기에서는 입력 x에 대한 미분을 변화율(derivative)이라고 하며, x를 상수로 생각하고 W에 대해 미분한 것을 그래디언트(gradient)로 표현하고 있습니다. 

  6. 이 코드는 모멘텀을 두 번 반복하는 알고리즘인 네스테로프 모멘텀(Nesterov Momentum)을 구현한 것입니다. 기본 모멘텀은 여섯 번째 줄을 w = w + velocity처럼 바꾸어 주면 됩니다. 모멘텀이나 네스테로프 모멘텀 방식을 사용하려면 from keras import optimizers; sgd = optimizers.SGD(lr=0.01, momentum=0.9, nesterov=True); network.compile(optimizer=sgd, …)처럼 SGD 옵티마이저 객체를 생성하여 직접 전달해야 합니다. 본문과는 달리 일반적으로 momentum 값은 0.9 정도를 많이 사용합니다. 

  7. 엄밀하게 말하면 기호 미분과 텐서플로가 사용하는 후진 모드 자동 미분은 다르지만, 여기에서는 자동 미분의 과정을 넓은 의미의 기호 미분으로 말하고 있습니다. 후진 모드 자동 미분을 포함하여 여러 가지 미분 방법에 대한 설명은 <핸즈온 머신러닝="">(한빛미디어, 2018)의 부록 D를 참고하세요. 

댓글남기기