ML,DL

[cs231n] 신경망 파트 3 : 학습 및 평가

yeeunnnn 2026. 2. 5. 23:40

🚨cs231n 2017 강의를 듣고 작성하였습니다.

🚨해당 게시글에 포함된 이미지 중 출처가 쓰여있지 않은 이미지는 모두 직접 그렸습니다.

 

이전 섹션들에서는 네트워크의 연결, 데이터 자체, 손실함수 등 신경망(Neural Network)의 정적인 부분을 학습했고, 파트3에서는 파라미터를 학습하고 효과적인 하이퍼파라미터를 찾는 과정인 동적인 부분을 학습한다.

7.1. 그라디언트 체크(Gradient Checks)

그라디언트 체크는 backpropagation 알고리즘을 구현한 그라디언트가 올바르게 동작하는지를 검증하는 것이다.

학습 단계에서 실제로 사용하지는 않지만, custom layer/loss를 만들었을 때 확인하는 과정에서 구현 검증용으로 사용할 수 있다.

이 과정에서 팁, 트릭, 조심할 이슈를 알아보자.

7.1.1. 같은 근사라 하여도 이론적으로 더 정확도가 높은 근사 공식이 있다.

그라디언트를 수치적으로 근사한다고 하면 보통 유한차분근사(finite difference approximation)를 떠올린다.

h는 아주 작은 수이고 보통 1e-5 정도의 수를 사용한다.

위 식보다 아래의 중심화된(centered) 차분 공식이 훨씬 낫다.

물론 이 공식은 f(x+h)와 f(x-h)를 계산하여야하므로 최초식보다 계산량이 두 배 많지만 훨씬 정확한 근사값이다.

  • f(x+h)와 f(x-h)의 (x 근방에서의) 테일러 전개를 고려해보자.

  • 오차를 구하기 위해 실제 기울기와 근사 기울기의 차이를 계산하자.

  • 이 오차를 h에 대한 테일러 전개를 이용하여 근사하고, 위에서 구한 f(x+h)와 f(x-h)의 x근방에서의 테일러 전개를 오차식에 대입하자.

  • 첫 식의 오차항을 테일러 전개를 통해 구해보자.

  • 첫 식은 O(h)의 오차가 있지만 두번째 식은 오차가 O(h^2)이다.
  • 따라서 첫번째 식보다 두번째 식이 더 정확한 그라디언트를 근사할 수 있다.

7.1.2. 상대 오차를 사용

그라디언트의 참값 f'_a과 수치적 근사값 f'_n을 비교하자.

둘의 절대 오차 |f'_a-f’_n| 혹은 그 제곱을 계산하여 이 값이 어느 한계점을 넘으면 그라디언트 오류라 할 수도 있다.

하지만 절대 오차와 두 그라디언트 값의 비율을 고려하는 상대오차가 가장 적절하다.

보통의 상대 오차 공식은 분모에 f'_a, f'_n 둘 중 하나만 있지만, 여기서는 둘의 최대값을 분모로 사용한다.

그래야 비율을 통해 얼마나 다른지를 확인할 수 있다.

  • 실제로 아래의 내용을 확인해보며 그라디언트 체크를 하면 유용할 것이다.
    • (상대 오차) > 1e-2면 그라디언트 계산이 아마 잘못되었을 수도 있다.
    • 1e-2 > (상대 오차) > 1e-4면 애매하다.
    • 1e-4 > (상대 오차)는 꺾임이 있는 목적함수에서는 괜찮지만, 꺾임이 없다면 1e-4는 너무 크다.
    • 1e-7 혹은 그보다 작은 상대 오차라면, 아주 좋다.
    • network의 layer 개수가 많아지면 상대오차가 커진다.
      • 예를 들어, 10개짜리 layer를 갖는 network가 있다고 하자.
      • 여기서 입력 데이터의 그라디언트를 체크한다면, 에러가 층을 올라가며 축척되므로 1e-2정도의 상대오차는 괜찮을 수 있다.
      • 즉, 미분가능한 함수 하나만 가지고 계산하는데 1e-2의 상대오차가 발생한다면 부정확한 그라디언트일 가능성이 높다.

7.1.3. 이중정확성 변수를 사용하라

  • 이중정확성 변수 : double precision 또는 64비트 부동소수점(double precision floating point) 변수를 사용하는 것을 의미
  • 단일정확성 변수 : single precision 또는 32비트 부동소수점(single precision floating point) 변수를 사용하는 것을 의미

단일정확성 변수를 사용할 경우, 두 값의 차이가 매우 작을 때 부동소수점 정밀도의 한계로 인해 그 차이를 정확하게 표현하지 못한다.
그 결과 numerical gradient에 오차가 발생하고, 실제 gradient가 맞더라도 상대오차가 크게 나타날 수 있다.
이중정확성 변수를 사용하면 더 많은 자릿수를 표현할 수 있어 이러한 수치적 오차가 줄어들고 상대오차가 개선된다.

7.1.4. 부동소숫점 연산이 활성화되는 범위에서 계산하라

예를 들어, 신경망에서는 손실 함수를 배치마다 normalize하는 것이 보통이다.

그렇지만 한 데이터 당 그라디언트가 매우 작다면, 거기에 또 데이터의 갯수를 부가적으로 나눌 경우 매우 작은 수가 된다.

따라서 f'_a,f'_n의 값을 계속 찍어보고 두 값이 너무 작지 않은가 확인하면 된다.

만약 두 값이 너무 작다면, 적당히 상수를 곱하여 부동소수점 표현이 조금 더 괜찮도록 만들 수도 있다.

7.1.5. 목적함수에서의 꺾인 점

꺾인 점(kink)들에서 부정확한 계산이 발생할 수 있으므로 이를 그라디언트 체크 과정에서도 염두에 두고 있어야한다.

대표적인 예로 ReLU 함수는 x=0에서 미분이 정의되지 않는다.
이때 x가 0 근처에 있으면, 수치 미분을 계산하는 과정에서 f(x+h)와 f(x-h)가 서로 다른 영역에 위치하게 될 수 있다.

예를 들어 x가 아주 작은 음수일 경우, 이론적으로 gradient는 0이다.
하지만 x+h는 0을 넘어 양수가 될 수 있고, 이 경우 함수 값이 갑자기 변하게 된다.

이로 인해 수치적으로 계산한 gradient는 실제 gradient와 다르게 나오며, gradient check가 실패한 것처럼 보일 수 있다.

이 문제를 방지하기 위해서는 다음과 같은 방법을 사용할 수 있다:

  • kink 근처의 값은 gradient check에서 제외
  • f(x+h)와 f(x-h)의 결과가 달라지는지 확인하여 kink crossing 여부 판단

즉, gradient check에서 중요한 것은 단순한 값 비교가 아니라, 함수가 미분 가능한 영역에서 계산되고 있는지 확인하는 것이다.

7.1.6. 적은 수의 데이터만 써라

꺾인 점과 관련된 하나의 해결책은 더 적은 데이터를 쓰는 것이다.

손실함수가 꺾인 점을 포함하고 있으면, 데이터가 적을수록 더 적은 꺾인 점을 포함할 것이고, 따라서 유한 차분 근사과정에서 꺾인점을 가로지르는 경우가 더 적을 것이다.

7.1.7. Step size h에 주의하라

h가 훨씬 작으면 수치적인 정확도문제에 부딪힐 수 있다.

가끔 그라디언트 체크가 잘 안되면, h를 1e-4나 1e-6정도로 조정하면 갑자기 될 수도 있다.

7.1.8. “특징적인” 연산이 수행되는 곳에서 그라디언트 체크를 하라

그라디언트 체크는 파라미터 공간의 특정한 점 위에서 수행된다.

만약 초기값을 랜덤하게 주어져있고, 아직 학습이 되지 않은 상태라면 그 점은 파라미터 공간의 가장 “특징적인”점이 아닐 것이므로, 그라디언트가 사실 잘 계산되지 않을 것이다.

그러므로, 학습이 진행되어 다양한 데이터에 대한 그라디언트가 제대로 계산되는 시점인 학습 시작 직후가 아니라 네트워크가 학습을 시작할 무렵 손실값이 하강하기 시작한 뒤에 그라디언트 체크를 수행하는 것이 좋다.

7.1.9. 정규화가 데이터를 압도하게 하지마라

손실 함수는 보통 다음과 같이 구성된다:

Loss = Data Loss + Regularization

이때 regularization 항이 너무 크면, 전체 gradient가 regularization에 의해 지배될 수 있다.
이 경우 data loss의 gradient가 잘못 구현되어 있어도 gradient check에서 이를 감지하지 못할 수 있다.

따라서 gradient check를 수행할 때는 regularization을 제거하거나, data loss와 regularization을 분리하여 각각 확인하는 것이 중요하다.

7.1.10. 드랍아웃과 임의자료확대(augmentation)을 꺼라

Gradient check는 f(x+h)와 f(x-h)의 차이를 통해 수치 미분을 계산하므로, 두 함수는 동일한 조건에서 계산되어야 한다.

하지만 dropout이나 data augmentation이 적용되면, 같은 입력이라도 매번 다른 결과가 나오게 된다.

이 경우 numerical gradient가 의미를 잃게 되며, gradient check는 실패하게 된다.

따라서 gradient check를 수행할 때는 반드시 dropout과 augmentation을 비활성화하거나, random seed를 고정해야 한다.

7.1.11. 몇 개의 차원에서만 체크하라

딥러닝 모델은 수많은 파라미터를 가지므로, 모든 gradient를 수치적으로 확인하는 것은 계산 비용이 매우 크다.

따라서 일부 파라미터만 랜덤하게 선택하여 gradient check를 수행하는 것이 일반적이다.

또는 특정 레이어나 의심되는 부분에 대해서만 선택적으로 검사할 수도 있다.

다만, 일부만 검사한다고 해서 전체가 항상 올바르다고 보장할 수는 없기 때문에, 충분한 검증과 테스트를 거쳐서 모델이 올바르게 학습되고 있는지를 확인하는 것이 중요하다.


7.2. 제대로 돌아가는지 확인하기(Sanity checks)(중요!)

최적화에 들어가기 전 확인해야 할 몇가지가 있다.

  • 맞는 손실함수를 찾아라

적은 수의 파라미터로 초기화 할 때는 기대한 손실함수값을 얻는지 확인하라.

먼저 데이터 손실 함수 하나만 확인하는 것이 가장 낫다.

예를 들어, CIFAR-10에 Softmax분류기를 이용할 경우 초기 손실함수 값을 2.302로 기대할 수 있다.

왜냐하면 각 클래스에 확률이 0.1로 분산되었을 테고 Softmax는 올바른 분류 확률에 음의 로그를 취한 값(-ln(0.1)=2.302)이기 때문이다.

이런 손실값들이 나오지 않으면 초기화에 문제가 있을 수 있다.

  • 정규화 강도를 올릴수록 손실값이 올라가야한다

정규화는 모델의 복잡성을 줄이고 파라미터들의 값을 제한하여 모델이 간단하게 유지되도록 도와주기 때문에, 정규화 강도가 높아질수록 모델이 간단해지며 훈련 데이터에 대한 예측 오차가 커질 수 있다.

이러한 현상은 정규화가 과적합을 방지하기 위해 모델의 파라미터를 제한하고, 훈련 데이터에 지나치게 맞추는 것을 억제하기 때문이다.

따라서 정규화 강도가 높아지면 모델은 더 일반화된 패턴을 학습하려 하며, 이로 인해 훈련 데이터에 대한 예측 오차가 커질 수 있다.

  • 자료의 작은 부분집합으로 과적합해보아라

전체 데이터셋으로 훈련을 시작하기 전에, 작은 부분으로 훈련을 시도하여보고(한 20개의 자료 정도), 0의 값을 달성할 수 있는지 확인해라. 정규화 강도는 0으로 설정하지 않으면 0의 값을 얻을 수 없다.

작은 자료에서 이러한 과정이 제대로 끝나지 않으면 전체 데이터셋으로 나아가는 것은 가치가 없다.

아주 작은 데이터셋에 성공적으로 과적합하였지만 여전히 코딩이 올바르게 이루어지지 않았을 수 있다.

예를 들어, 가지고 있는 데이터 포인트들의 특성들이 어떤 버그 때문에 임의로 선정된 경우, 작은 훈련집합의 과적합은 성공할지라도 그게 전체 데이터셋으로 일반화되지 않을 수도 있다.


7.3. 학습 과정 돌보기

신경망을 훈련하면서 몇몇 값은 모니터링 해야하며, 좀 더 효율적인 학습을 위한 하이퍼파라미터 조정도 여기서 직관적 영감을 얻는다.

에폭은 각 자료가 몇 번이나 학습에 사용되었는가를 재는 용어로, 1 에폭이 지났다는 것은 모든 자료가 한번씩 SGD iteration에 사용되었음을 뜻한다.

100개의 데이터가 몇번이나 학습이 되었는가? 100개를 다 써서 학습을 5번 했다.→ 5에폭 / 미니배치로 50개씩 학습하면 10 iteration했을 때 5에폭

7.3.1. 손실 함수

손실함수는 forward pass동안 각각의 배치에서 계산되므로 훈련과정에서 추적하기 용이하다.

아래는 시간에 따른 손실그래프의 모양을 여러 학습률에 따라 그려본 것이다.

출처 : https://cs231n.stanford.edu/

  • 좌측 : training 과정에서 learning rate의 영향
    • 낮은 학습률로는 선형적인 향상이 이루어질 것이다. 다만, 처음에 빨리 움직여야 global optimum을 찾기가 좋고, 빠르지않으면 근처의 최적화값을 찾을 수 있다.
    • 높은 학습률에서는 좀 더 지수적인 향상이 보이겠지만, 최적화된 값을 찾기는 어렵다. 더 높은 학습률은 손실의 감소를 가속할 것이나, 더 나쁜 손실값에 빠지게 할 수도 있다.
  • 우측 : 전형적인 손실함수의 예
    • x축은 시간(epoch)이고 CIFAR-10 데이터셋에서 작은 신경망을 훈련한 것이다.
    • 이 손실함수의 모양은 적절해보이고 배치 사이즈는 너무 작은 것으로 보인다. (값에 노이즈가 너무 많다.)

손실함수의 ‘씰룩거림’은 배치 사이즈와 연관이 있다.

만일 배치사이즈가 1이면 훨씬 많이 씰룩거릴 것이고, 만일 배치 사이즈가 전체 데이터셋이면 이 씰룩거림은 최소화 될 것이다.

왜냐하면 모든 그라디언트 업데이트가 손실 함수를 단조적으로 향상시킬 것이기 때문이다.(학습률이 너무 크지만 않다면)

어떤 사람들은 손실함수의 로그값의 그래프를 선호하기도 한다.

일반적으로 학습과정은 어떤 지수모양을 취하고 있기 때문에, 로그 손실 그래프는 좀 더 해석이 용이한 직선의 모양처럼 보인다.

또한, 만약 여러개의 교차검증 모형의 손실 그래프를 같은 그래프 위에 그리면, 그들 사이의 차이가 좀 더 명백해지는 장점이 있다.

7.3.2. 훈련/검증 정확도

출처 : https://cs231n.stanford.edu/

훈련/검증 정확도(training/validation accuracy)는 분류기 훈련시 추적해야 할 또 다른 중요한 값이다.

위 plot을 통해서 모형이 과적합(overfitting)중인지를 확인할 수 있다.

파란색(검증오류) 곡선은 훈련 정확도에 비하여 매우 낮은 검증 정확도를 보여주고 있는데, 이는 강한 과적합의 가능성을 보이는 것이다.

초록색 곡선은 검증 정확도가 훈련 정확도를 꽤 잘 따라가고 있는데, 이것은 모델이 데이터의 복잡한 패턴을 표현하고 있지 못함을 의미할 수도 있다.

7.3.3. 가중치의 현재값과 변화량의 비율

가중치의 현재 크기와 업데이트로 인한 변화량의 크기를 비교해 볼수도 있다.

모든 파라미터마다 독립적으로 이 비율을 추적/계산하면 이 비율은 1e-3 근처여야한다.

이보다 낮으면 학습률이 너무 낮은 것이다. 이보다 크면 학습률이 너무 크다.

# assume parameter vector W and its gradient vector dW
param_scale = np.linalg.norm(W.ravel())
update = -learning_rate*dW # simple SGD update
update_scale = np.linalg.norm(update.ravel())
W += update # the actual update
print update_scale / param_scale # want ~1e-3

특정한 예를 들면 위와 같다.

최소값이나 최댓값을 추적할 수도 있고, 그라디언트와 업데이트값의 norm을 계산하고 추적할 수도 있다.

7.3.4. 레이어별 활성값 및 그라디언트의 분포

올바르지 않은 초기값 설정은 학습 과정을 느리게 하거나 완전히 망칠 수 있다.

활성값/그라디언트 값의 히스토그램을 network의 모든 layer마다 그리는 것을 통해 분석할 수 있다.

만일 이상한 분포가 나오면 좋은 징조가 아닐 수 있다.

예를 들어, tanh 뉴런에서는 활성값이 [-1,1]의 전 범위에 걸쳐 분산되어 있는 모습이 나와야하는데, 모든 활성값이 0이거나 -1 혹은 1에 집중되어 있으면 문제가 있다.

7.3.5. 첫번째 층의 시각화

만일 이미지픽셀에 관련된 작업을 한다면 첫 층의 특징(feature)들을 시각화하는 것이 많은 도움이 될 수 있다.

아래는 신경망 첫 layer의 웨이트값을 시각화한 예이다.

출처 : https://cs231n.stanford.edu/

  • 좌측
    • 특징값(feature)에 잡음(noise)가 많을 때 나타날 수 있는 증상이다.
    • 원인 : 수렴하지않은 network, 적절하지 않은 학습 속도, 매우 낮은 정규화 패널티
  • 우측
    • 부드럽고 깨끗하며 다양한 특징값들이 보이므로, 훈련이 잘 진행되고 있다는 지표일 수 있다.

7.4. 파라미터값의 업데이트

수식적으로 그라디언트값은 역전파로 계산되고 이는 파라미터값 업데이트를 위해 사용된다.

업데이트를 수행하는 몇가지 접근법들이 있다.

하나의 이미지로 정리하면 아래와 같다.

7.4.1. SGD와 그외 방법들

  • 바닐라 업데이트(Vanilla update)

SGD

: 그라디언트의 반대방향으로 파라미터를 업데이트 하는 것 (그라디언트가 양수이면 해당 파라미터를 감소시키고, 음수이면 증가시킨다.)

파라미터의 벡터를 x라하고 그라디언트를 dx라 쓰면, 가장 간단한 업데이트는 다음과 같다.

# Vanilla update
x += - learning_rate * dx

여기서 학습률 learning_rate는 하이퍼파라미터이고 고정된 상수이다.

  • 모멘텀 업데이트(Momentum update)

Momentum

모멘텀 업데이트는 SGD에서 모멘트 항이 추가된 것이다.

# Momentum update
v = mu * v - learning_rate * dx # integrate velocity
x += v # integrate position

step이 커질 때마다 현재의 그라디언트가 다음번 모멘트 v에 누적된다. 이는, 이전 스텝에서의 기울기의 누적된 이동평균을 나타낸다.

mu는 모멘트 효과에 대한 가중치이다.

파라미터 벡터 x가 업데이트되는 속도의 방향은 그라디언트들이 많이 향하는 방향으로 축적될 것이다.

만약 모멘텀을 교차검증(cross-validation)으로 선택한다면 보통 [0.5, 0.9, 0.95, 0.99]로 설정한다.

에폭에 따라 모멘텀의 크기를 조정하면 최적화를 더 효과적으로 할 수 있다.

이를테면 시작할 때는 0.5의 모멘텀으로 시작하되 몇 번의 에폭을 지나면 0.99로 설정할 수도 있다.

이는 학습 속도의 스케줄을 담금질(annealing)하는 것과도 비슷하다.

다만, 모멘텀은 ‘그라디언트<속도’이면 최소지점을 지나쳐버리는 “overshooting”문제가 생길 수 있다.

  • Nesterov 모멘텀

출처 : https://cs231n.stanford.edu/

최근 많은 주목을 받은(2017년 기준) Nesterov 모멘텀은 모멘텀 업데이트와 조금 다르다.

모멘텀 업데이트의 공식은 아래와 같다.

ε : 학습속도, µ : 모멘트 효과에 대한 가중치

Nesterov 모멘텀의 공식은 아래와 같다.

모멘텀의 공식과 거의 같지만, 현재 위치에서 속도 µv_t만큼 전진한 후의 그라디언트를 이용한다.

v_prev = v # back this up
v = mu * v - learning_rate * dx # velocity update stays the same
x += -mu * v_prev + (1 + mu) * v # position update changes form

7.4.2. 학습속도 담금질

깊은 신경망의 훈련에서 시간에 따라 훈련 속도를 담금질(조정)하는 건 언제나 도움이 된다.

높은 learning rate에서는 파라미터 벡터가 씰룩거리고, 손실함수의 ‘좁고 깊숙한 지점’에 도달하지 못한다.

학습속도를 천천히 줄이면 오랜 시간동안 거의 제자리에서 씰룩거리며, 학습속도를 너무 빨리 줄이면 전체 시스템이 너무 빨리 멈춰 최적의 장소에 도달하지 못할 수 있다.

학습속도를 감소시키는 방법은 보통 다음 세가지가 있다.

  1. 계단식 감소(step decay) : 몇 에폭마다 일정량만큼 학습 속도를 줄인다. 전형적으로는 5에폭마다 반으로 줄이거나 20에폭마다 1/10씩 줄이기도 한다. 이 숫자들은 전적으로 문제와 모델에 의존한다. 우선 고정된 학습속도로 검증 오차를 살펴보다가, 검증 오차가 개선되지 않을 때마다 학습속도를 감소시키는 방법을 택하기도 한다.
  2. 지수적 감소(exponential decay) : 지수적 감소는 α=α_0*e^{−kt}꼴을 뜻한다. 여기서 α_0,k는 하이퍼파라미터이고 t는 반복 횟수이다. t가 커질수록 α값이 빠르게 줄어드는 특징을 가진다.
  3. 1/t 감소 : α=α_0/(1+kt)꼴을 뜻하고 α_0,k는 하이퍼파라미터이고 t는 반복 횟수이다. t가 커질수록 α값이 조금씩만 줄어들게 된다.

실전에서는 step decay의 하이퍼파라미터들이 k에 비해서 해석이 쉽기때문에 계단식 감소 방식이 더 선호된다.

7.4.3. 파라미터별 데이터-맞춤 학습속도

지금까지 학습한 방법들은 모든 파라미터에 똑같은 학습 속도를 적용하였다.

학습 속도의 튜닝은 많은 계산을 필요로 하기 때문에 파라미터 별로 튜닝하기 위한 여러 방법들이 연구되고있다.

연구 기법 대부분이 여전히 다른 하이퍼파라미터 세팅을 필요로 하지만 기존방법보다 효과가 좋다.

여기서는 학습속도를 파라미터 별로 튜닝하는 여러 방법을 살펴볼 것이다.

  • Adagrad

Adaptive gradient의 줄임말로, 훈련도중 계산되는 gradient를 활용하는 방법이다.

매개변수 ‘전체’의 학습률 값을 일괄적으로 낮추는 것이 아니라. ‘각각의’ 매개변수에 맞춤형 값을 만들어주는 것이다.

# Assume the gradient dx and parameter vector x
cache += dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)

변수 cache는 학습 도중에 계산되는 gradient에 제곱을 해서 계속 더해주고, update를 할 때 upadate term을 앞서 계산한 gradient 제곱항으로 나눠준다.

만약 작은 gradient값을 가진다면 제곱의 합이 작아지고, 이 작은 값이 나눠지므로 가속도가 붙는다.

큰 gradient값을 가지면 큰 값이 나눠지고 속도가 점점 줄어든다.

변수 eps는 분모가 0이 되지 않도록 안정화 역할을 한다.(주로 1e-4에서 1e-8의 값이 할당된다.)

  • RMSprop

AdaGrad의 문제를 개선시킨 방법이다.

제곱 그라디언트의 평균(Adagrad처럼)이 아니라, 이동평균(moving average)을 사용한다:

cache = decay_rate * cache + (1 - decay_rate) * dx**2
x += - learning_rate * dx / (np.sqrt(cache) + eps)

cache는 값을 그저 누적시키는 것이 아니라 기존의 누적 값에 decay_rate를 곱해주고 그라디언트의 제곱에 -decay_rate를 곱해줘서 더해준다.

여기서 decay_rate는 하이퍼파라미터이고 보통 [0.9, 0.99, 0.999] 중 하나의 값을 취한다.

이를 통해 step의 속소를 가속/감속 시킬 수 있다.

  • Adam(⭐️)

Adam

Adam은 RMSProp에 모멘텀(momentum)을 혼합한 것처럼 보인다.

m = beta1*m + (1-beta1)*dx
v = beta2*v + (1-beta2)*(dx**2)
x += - learning_rate * m / (np.sqrt(v) + eps)

업데이트 과정은 RMSProp의 업데이트 방식과 정확히 같아 보이지만, 그라디언트 dx 대신에 “안정화된” 버전인 m이 사용되었다는 점이 다르다.

Adam을 제안한 논문에서 추천되는 값들은 eps = 1e-8, beta1 = 0.9, beta2 = 0.999이다.

'''t는 구동횟수(iteration)이며 1부터 무한대까지'''
m = beta1*m + (1-beta1)*dx
mt = m / (1 - beta2) * (dx * 2)
v = beta2*v + (1-beta2)*(dx**2)
vt = v / (1 - beta2 ** t)
x += - learning_rate * m / (np.sqrt(v) + eps)

아래는 학습과정에 대한 이해를 도와줄 애니메이션이다.

출처 : https://cs231n.github.io/neural-networks-3/

  • 좌측 : 손실 함수의 등고선 위에서 각 최적화 알고리즘들의 iteration에 따른 변화
    • 모멘텀-기반 방법론들의 'overshooting'을 볼 수 있다.
  • 우측 : 차원에 따라 곡률이 다른 최적화 곡면
    • SGD는 방향에 따른 학습률의 대칭을 깨지 못해 안장점에서 빠져나오는 데 매우 힘든 시간을 겪는다.
    • 반대로, RMSprop같은 알고리즘들은 그라디언트 값이 작으므로 이에대한 학습률을 증가시켜서 아래로 향한다.

7.5. Hyperparameter optimization

신경망의 training에는 많은 하이퍼파라미터 셋팅이 필요하다.

  • 학습률의 초기값
  • 학습률 감소 정도(예를 들어, 감소 상수)
  • L2나 드랍아웃 패널티의 정규화 강도

7.5.1. 코드 구성

큰 신경망은 대개 긴 학습시간이 걸리므로 하이퍼파라미터를 찾는 데 며칠, 몇 주가 걸릴 수도 있다.

한 가지 방법은 하이퍼파라미터를 임의로 선택하여 최적화를 수행하는 worker을 만드는 것이다.

학습과정에서 worker는 각 에폭마다의 검증 정확도를 기억하여 모델의 체크포인트를 파일에 저장한다.

그리고 계산 클러스터별로 worker를 개시(launch)하거나 끝내(kill)게 하는 마스터를 만든다.

또는 마스터는 worker가 작성한 체크포인트들을 조사하고 훈련 통계량들로 그림을 그릴 수도 있다.

지금은 optuna(worker+마스터 자동화 도구로 생각)를 활용해 빠르게 찾을 수 있다.

7.5.2. 교차검증 대신 한 번의 검증

딥러닝에서는 데이터가 굉장히 많기 때문에, 대부분 여러 번의 교차 검증보다는 적당한 크기의 검증 셋을 설정해두어 한 번만 검증한다.

7.5.3. 하이퍼파라미터의 범위

로그 단위로 하이퍼파라미터를 찾는다면 범위가 줄어드니까 편하다.

learning rate는 gradient에 곱해지는 값이기 때문에, 선형적인 변화보다 배수적인 변화가 더 중요하다.

예를 들어, 0.001에서 0.002로 증가하는 것은 2배 변화이지만, 10에서 10.001로 증가하는 것은 거의 변화가 없다.

이처럼 같은 '차이'라도 실제 영향은 크게 다르기 때문에, learning rate는 선형 간격이 아니라 로그 스케일에서 탐색하는 것이 더 합리적이다.

따라서 일반적으로 earning_rate = 10 ** uniform(-6, 1)와 같이 샘플링을 진행한다.

이 방법을 사용하면 1e-6부터 1e1까지 다양한 스케일을 고르게 탐색할 수 있다.

7.5.4. 그리드보다는 랜덤하게 검색

출처 : https://cs231n.stanford.edu/

Bergstra and Bengio의 논문 ‘Random Search for Hyper-Parameter Optimization”에서 주장된 것처럼, 랜덤하게 선정된 trial들이 그리디한 시도보다 하이퍼파라미터를 최적화 하는 데에 훨씬 효과적이다. 또한, 랜덤한 경우가 코드 구현도 더 쉽다.

7.5.5. 가장 좋은 값이 경계에 있으면 조심하라

 

 

가끔 하이퍼파라미터를 좋지 않은 범위에서 찾고 있을 수도 있다.

최종 학습률이 탐색 범위의 경계에 있다면, 구간 밖에 있는 더 최적의 하이퍼파라미터를 놓치고 있는 것일지도 모른다.

7.5.6. 범위를 좁혀가며 찾아라

하이퍼파라미터는 넓은 범위에서 시작하여 점점 좁혀가는 방식이 효과적이다.

또한, 첫 시도에서는 1 에폭이나 혹은 더 적게만 훈련하는 게 도움이 될 수도 있다.

왜냐하면 하이퍼파라미터 세팅이 너무 많으면 오히려 모델이 학습을 전혀 안하거나 무한대의 손실함수값으로 폭발할 수도 있기 때문이다.

두 번째 단계는 좀 더 좁은 범위에서 에폭을 작게 하고, 마지막 검색에서는 더 좁은 범위에서 많은 에폭의 훈련을 수행하면 된다.

7.5.7. 베이지안 하이퍼파라미터 최적화

베이지안 하이퍼파라미터 최적화는 이전 실험 결과를 바탕으로 하이퍼파라미터를 좀 더 효율적으로 찾기위한 방안을 연구하는 분야이다.

이를 통해 random search보다 효율적으로 좋은 하이퍼파라미터를 찾을 수 있다.

핵심 아이디어는 각 하이퍼파라미터마다 탐험(exploration)-개발(exploitation) trade-off의 적절한 균형을 찾는 것이다.

많은 라이브러리들이 이 모형에 기반하여 개발되었고 그 중에 잘 알려진 것은 Spearmint, SMAC, Hyperopt이다.


7.6. 평가

7.6.1. 모델 앙상블(Model Ensembles)

실전에서, 신경망의 성능을 몇 퍼센트 끌어올릴 수 있는 믿을 만한 방법이 하나 있는데 바로 여러 개의 독립적인 모델을 만들고 테스트 때 그들의 평균 예측을 취하는 것이다. 앙상블에서 모델의 개수가 많아질수록 성능은 단조적으로 개선된다. 게다가, 앙상블 내에 더욱 다양한 모델이 있을수록 성능의 개선은 더 극적으로 증가한다.

앙상블을 구축하는 몇 가지 방법이 있다.

  • 같은 모델, 다른 초기화
    • 교차 검증으로 최고의 하이퍼파라미터를 결정한 후에, 여러 개의 모델을 이 최고의 하이퍼파라미터를 사용하여 학습시킨다.
    • 이 방법의 단점은, 모델의 다양성이 오직 다양한 초기값에서만 온다는 것이다.
  • 교차 검증 동안 발견되는 최고의 모델들
    • 교차 검증으로 최고의 하이퍼파라미터를 결정한 다음에, 몇 개의 최고 모델을 선정하여 이들로 앙상블을 만든다.
    • 이 방법은 앙상블 내의 다양성을 개선하지만, 차선의 모델 또한 포함시킬 수 있다는 단점을 갖는다.
    • 실전에서는 이 방법이 위보다 쉬운 편인데, 교차 검증 뒤에 추가적인 모델의 재훈련이 필요없기 때문이다.
  • 한 모델에서 다른 체크포인트들
    • 만약 훈련의 계산비용이 매우 비싸면, 하나의 네트워크의 체크포인트들을 모아 앙상블을 이룰 수 있다.
    • 이 방법은 다양성이 떨어지지만, 매우 간편하고 계산비용이 저렴하다는 것이 장점이다.
  • 훈련 동안 하이퍼파라미터값들 평균취하기
    • 이전 방법과 같은 맥락에서, 컴퓨팅 자원이 한정된 경우에는 직전 구동에서의 신경망 가중치들을 기억해두었다가 평균을 내는 방법이 있다. 이렇듯 ‘부드러운’ 가중치는 거의 항상 개선된 검증 오차를 보인다.

모형 앙상블의 단점이 하나 있다면 테스트 샘플에 모델을 적용할 때 평가에 더 시간이 걸린다는 점이다.