06.5 적절한 하이퍼파라미터 값 찾기

신경망에는 하이퍼파라미터가 다수 등장합니다. 여기서 말하는 하이퍼파라미터는 예를 들어, 각 층의 뉴런 수, 배치 크기, 매개변수 갱신 시의 학습률과 가중치 감소 등입니다. 이러한 하이퍼파라미터의 값을 적절히 설정하지 않으면 모델의 성능이 크게 떨어지기도 하죠. 하이퍼파라미터의 값은 매우 중요하지만 그 값을 결정하기까지는 일반적으로 많은 시행착오를 겪습니다. 이번 절에서는 하이퍼파라미터의 값을 최대한 효율적으로 탐색하는 방법을 설명합니다.

하이퍼파라미터 복잡한 조율 다이얼 도로시가 수많은 학습 조율 다이얼(학습률 lr, 가중치 감소 lambda 등)이 달린 복잡한 대형 제어 기기 판넬 앞에서 어떻게 세팅해야 할지 머리를 긁적이며 고민하고 있어요.

도로시의 파라미터 다이얼 고민

  • 도로시: “휴… 모델 안의 가중치 $W$와 편향 $B$는 신경망이 역전파로 직접 공부하며 알아내는데, 이 다이얼 보드에 있는 학습률(lr)이나 가중치 감소 계수(lambda)는 왜 내가 직접 손으로 맞춰 줘야 하는 거야? 게다가 조금만 잘못 돌려도 학습이 아예 엉망이 돼버려!”
  • 지니: “그게 바로 사람이 직접 모델 구조나 학습 방식을 설정해 줘야 하는 하이퍼파라미터(Hyperparameter)란다. 최적의 성능을 끌어내기 위해서는 이 다이얼들의 최적 조합을 찾는 스마트한 탐색 요령이 꼭 필요하지.”

6.5.1 검증 데이터

지금까지는 데이터셋을 훈련 데이터와 시험 데이터라는 두 가지로 분리해 이용했습니다. 훈련 데이터로는 학습을 하고, 시험 데이터로는 범용 성능을 평가했죠. 그렇게 해서 훈련 데이터에만 지나치게 적응되어 있지 않은지(오버피팅한 건 아닌지), 그리고 범용 성능은 어느 정도인가 같은 것을 평가할 수 있었습니다.

앞으로 하이퍼파라미터를 다양한 값으로 설정하고 검증할 텐데, 여기서 주의할 점은 하이퍼파라미터의 성능을 평가할 때는 시험 데이터를 사용해서 안 된다는 것입니다. 매우 중요하지만 놓치기 쉬운 포인트죠.

같은 성능 평가인데 하이퍼파라미터가 대상일 때는 시험 데이터를 사용해서는 안 되는 이유가 뭘까요? 그것은 시험 데이터를 사용하여 하이퍼파라미터를 조정하면 하이퍼파라미터 값이 시험 데이터에 오버피팅되기 때문입니다. 바꾸어 말하면, 하이퍼파라미터 값의 ‘좋음’을 시험 데이터로 확인하게 되므로 하이퍼파라미터의 값이 시험 데이터에만 적합하도록 조정되어 버립니다. 그렇게 되면 다른 데이터에는 적응하지 못하니 범용 성능이 떨어지는 모델이 될지도 모릅니다.

그래서 하이퍼파라미터를 조정할 때는 하이퍼파라미터 전용 확인 데이터가 필요합니다. 하이퍼파라미터 조정용 데이터를 일반적으로 검증 데이터${}^{\text{validation data}}$라고 부릅니다. 하이퍼파라미터의 적절성을 평가하는 데이터인 셈이죠.

NOTE_ 훈련 데이터는 매개변수(가중치와 편향)의 학습에 이용하고, 검증 데이터는 하이퍼파라미터의 성능을 평가하는 데 이용합니다. 시험 데이터는 범용 성능을 확인하기 위해서 마지막에 (이상적으로는 한 번만) 이용합니다.
  • 훈련 데이터 : 매개변수 학습
  • 검증 데이터 : 하이퍼파라미터 성능 평가
  • 시험 데이터 : 신경망의 범용 성능 평가

훈련/검증/시험 데이터 분할 케이크 지니가 동그란 케이크를 3조각으로 쪼개서, 가장 큰 조각은 가중치 공부용(훈련 데이터), 중간 조각은 하이퍼파라미터 성능 테스트용(검증 데이터), 가장 작은 조각은 최종 범용 성능 측정용(시험 데이터)으로 도로시에게 덜어주고 있어요.

지니의 데이터 3분할 케이크 특강

  • 지니: “도로시, 하이퍼파라미터 다이얼을 가장 잘 맞췄는지 검증하기 위해 시험 데이터(Test dataset)를 쓰면 절대로 안 돼! 맛보기로 시험 삼아 다이얼을 맞추다 보면, 하이퍼파라미터 값 자체가 시험 데이터에만 잘 맞는 ‘시험지 유출 오버피팅’이 일어나거든.”
  • 도로시: “아! 그래서 훈련 데이터 케이크로 가중치를 훈련하고, 별도로 떼어둔 검증 데이터(Validation set) 케이크로 하이퍼파라미터 성능을 비교한 뒤, 최종 평가는 꼭 숨겨둔 시험 데이터 케이크로 마지막에 한 번만 딱 측정하는 거구나!”

데이터셋에 따라서는 훈련 데이터, 검증 데이터, 시험 데이터를 미리 분리해둔 것도 있습니다만, MNIST 데이터셋은 훈련 데이터와 시험 데이터로만 분리해뒀습니다. 이런 경우엔 (필요하면) 사용자가 직접 데이터를 분리해야 하겠죠. MNIST 데이터셋에서 검증 데이터를 얻는 가장 간단한 방법은 훈련 데이터 중 20% 정도를 검증 데이터로 먼저 분리하는 것입니다. 코드로는 다음과 같습니다.

(x_train, t_train), (x_test, t_test) = load_mnist()

# 훈련 데이터를 뒤섞는다.
x_train, t_train = shuffle_dataset(x_train, t_train)

# 20%를 검증 데이터로 분할
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)

x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]

이 코드는 훈련 데이터를 분리하기 전에 입력 데이터와 정답 레이블을 뒤섞습니다. 데이터셋 안의 데이터가 치우쳐 있을지도 모르기 때문입니다(예컨대 숫자 ‘0’에서 ‘9’까지 순서대로 정렬되어 있을 수 있겠죠). 참고로, 여기에서 사용한 shuffle_dataset 함수는 np.random.shuffle을 이용한 것으로, 그 구현은 common/util.py에 있습니다.

이어서 검증 데이터를 사용하여 하이퍼파라미터를 최적화하는 기법을 살펴봅시다.

6.5.2 하이퍼파라미터 최적화

하이퍼파라미터를 최적화할 때의 핵심은 하이퍼파라미터의 ‘최적 값’이 존재하는 범위를 조금씩 줄여간다는 것입니다. 범위를 조금씩 줄이려면 우선 대략적인 범위를 설정하고 그 범위에서 무작위로 하이퍼파라미터 값을 골라낸(샘플링) 후, 그 값으로 정확도를 평가합니다. 정확도를 잘 살피면서 이 작업을 여러 번 반복하며 하이퍼파라미터의 ‘최적 값’의 범위를 좁혀가는 것입니다.

NOTE_ 신경망의 하이퍼파라미터 최적화에서는 그리드 서치${}^{\text{grid search}}$ 같은 규칙적인 탐색보다는 무작위로 샘플링해 탐색하는 편이 좋은 결과를 낸다고 알려져 있습니다.[15] 이는 최종 정확도에 미치는 영향력이 하이퍼파라미터마다 다르기 때문입니다.

하이퍼파라미터의 범위는 ‘대략적으로’ 지정하는 것이 효과적입니다. 실제로도 $0.001$에서 $1,000$ 사이($10^{-3} \sim 10^3$)와 같이 ‘10의 거듭제곱’ 단위로 범위를 지정합니다. 이를 ‘로그 스케일${}^{\text{log scale}}$로 지정’한다고 하죠.

하이퍼파라미터를 최적화할 때는 딥러닝 학습에는 오랜 시간(예컨대 며칠이나 몇 주 이상)이 걸린다는 점을 기억해야 합니다. 따라서 나쁠 듯한 값은 일찍 포기하는 게 좋습니다. 그래서 학습을 위한 에폭을 작게 하여, 1회 평가에 걸리는 시간을 단축하는 것이 효과적입니다.

이상이 하이퍼파라미터의 최적화입니다. 지금까지의 이야기를 정리하면 다음과 같습니다.

  • 0단계 하이퍼파라미터 값의 범위를 설정한다.
  • 1단계 설정된 범위에서 하이퍼파라미터의 값을 무작위로 추출한다.
  • 2단계 1단계에서 샘플링한 하이퍼파라미터 값을 사용하여 학습하고, 검증 데이터로 정확도를 평가한다(단, 에폭은 작게 설정한다).
  • 3단계 1단계와 2단계를 특정 횟수(100회 등) 반복하며, 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힌다.

이상을 반복하여 하이퍼파라미터의 범위를 좁혀가고, 어느 정도 좁아지면 그 압축된 범위에서 값을 하나 골라냅니다. 이것이 하이퍼파라미터를 최적화하는 하나의 방법입니다.

NOTE_ 여기서 설명한 하이퍼파라미터 최적화 방법은 실용적인 방법입니다. 하지만 과학이라기보다는 다분히 수행자의 ‘지혜’와 ‘직관’에 의존한다고 느낌이 들죠. 더 세련된 기법을 원한다면 베이즈 최적화${}^{\text{Bayesian optimization}}$를 소개할 수 있겠네요. 베이즈 최적화는 베이즈 정리${}^{\text{Bayes’ theorem}}$를 중심으로 한 수학 이론을 구사하여 더 엄밀하고 효율적으로 최적화를 수행합니다. 자세한 내용은 〈Practical Bayesian Optimization of Machine Learning Algorithms〉 논문[16] 등을 참고하세요.

무작위 최적 파라미터 탐색 토토가 숲속 지도를 펼쳐놓고 격자망 교차점 대신 무작위로 여러 위치를 킁킁거리며 단서를 쫓아가, 별 모양의 가장 깊은 최적 지점을 발견해 내고 있어요.

토토의 킁킁 무작위 탐색 (Random Search)

  • 토토: “왈왈! (무작위 지점들을 짚으며) 격자망보다 임의로 찾는 게 훨씬 잘 나온다 멍!”
  • 도로시: “와! 일정한 등간격 격자(Grid Search)로 찾는 것보다, 임의의 범위에서 로그 스케일로 무작위 값들을 추출(Random Search)해 훈련 에폭을 짧게 여러 번 돌려보는 게 훨씬 넓고 효율적으로 노다지 자리를 알아낼 수 있구나!”

6.5.3 하이퍼파라미터 최적화 구현하기

그럼 MNIST 데이터셋을 사용하여 하이퍼파라미터를 최적화해보기로 하죠. 여기에서는 학습률과 가중치 감소의 세기를 조절하는 계수(가중치 감소 계수)를 탐색하는 문제를 풀어보겠습니다(이 문제는 스탠퍼드 대학교의 CS231n[5] 수업을 참고했습니다).

앞에서 말한 대로, 하이퍼파라미터의 검증은 그 값을 $0.001 \sim 1,000$($10^{-3} \sim 10^3$) 사이 같은 로그 스케일 범위에서 무작위로 추출해 수행합니다. 이를 파이썬 코드로는 10 ** np.random.uniform(-3, 3)처럼 작성할 수 있습니다. 이 예에서는 가중치 감소 계수를 $10^{-8} \sim 10^{-4}$, 학습률을 $10^{-6} \sim 10^{-2}$ 범위부터 시작합니다. 이 경우 하이퍼파라미터의 무작위 추출 코드는 다음과 같이 쓸 수 있습니다.

weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)

이렇게 무작위로 추출한 값을 사용하여 학습을 수행합니다. 그 후에는 여러 차례 다양한 하이퍼파라미터 값으로 학습을 반복하며 신경망에 좋을 것 같은 값이 어디에 존재하는지 관찰합니다. 자세한 구현은 ch06/hyperparameter_optimization.py 파일을 참고하시길 바라며, 여기에서는 결과만 보겠습니다.

자, 가중치 감소 계수의 범위를 $10^{-8} \sim 10^{-4}$, 학습률의 범위를 $10^{-6} \sim 10^{-2}$로 하여 실험하면 결과는 [그림 6-24]처럼 됩니다.

그림 6-24 실선은 검증 데이터에 대한 정확도, 점선은 훈련 데이터에 대한 정확도

그림 6-24

[그림 6-24]는 검증 데이터의 학습 추이를 정확도가 높은 순서로 나열했습니다. 이를 보면 ‘Best-5’ 정도까지는 학습이 순조롭게 진행되고 있습니다. 이를 바탕으로 ‘Best-5’까지의 하이퍼파라미터의 값(학습률과 가중치 감소 계수)을 살펴보겠습니다. 결과는 다음과 같습니다.

Best-1 (val acc:0.83) | lr:0.0092, weight decay:3.86e-07
Best-2 (val acc:0.78) | lr:0.00956, weight decay:6.04e-07
Best-3 (val acc:0.77) | lr:0.00571, weight decay:1.27e-06
Best-4 (val acc:0.74) | lr:0.00626, weight decay:1.43e-05
Best-5 (val acc:0.73) | lr:0.0052, weight decay:8.97e-06

이 결과를 보면 학습이 잘 진행될 때의 학습률은 $0.001 \sim 0.01$, 가중치 감소 계수는 $10^{-8} \sim 10^{-6}$ 정도라는 것을 알 수 있습니다. 이처럼 잘될 것 같은 값의 범위를 관찰하고 범위를 좁혀갑니다. 그런 다음 그 축소된 범위로 똑같은 작업을 반복하는 겁니다. 이렇게 적절한 값이 위치한 범위를 좁혀가다가 특정 단계에서 최종 하이퍼파라미터 값을 하나 선택합니다.


* 옮긴이_ 투표(voting) 등 다른 방법도 사용합니다.

서브목차