logo

하이퍼파라미터 튜닝

파라미터 vs. 하이퍼파라미터

  • 파라미터
    • 데이터로부터 학습되는 변수
    • 신경망에서 가중치
    • 경사하강법을 통해 찾음 → 점진적 개선
  • 하이퍼파라미터
    • 모형 또는 학습의 특성을 정의하는 변수
    • 학습 전에 결정 → 데이터로 정할 수 없으므로 시행착오를 통해 개선
    • 종류:
      • 신경망의 구조: 은닉층의 수, 각 층의 폭, 활성화 함수
      • 학습 및 최적화: 학습률, 배치의 크기, 최적화 알고리즘, 에포크 수, 학습 중단 방식
      • 정칙화: 드롭아웃 방식, 데이터 증강 등
  • 조합이 매우 많으므로 코드를 손으로 고쳐 가며 시도하기보다 자동으로 조합을 바꿔 가며 탐색

학습률 찾기

  • 경사하강법에는 적절한 학습률이 필요
    • 학습률이 너무 낮으면 → 손실이 느리게 줄어듦
    • 학습률이 너무 높으면 → 손실이 오히려 늘어남
  • 학습률을 서서히 변화시키면서 적절한 학습률을 찾음
  • 기존 모형을 상속받아 학습률을 외부에서 받을 수 있게 재정의(처음부터 이렇게 만들어도 됨)
    • 모형 구조 등 공통 부분은 그대로 물려받고, 고정되어 있던 학습률만 인자로 받도록 변경
class MLPClassifierAutoLR(MLPClassifier):
    def __init__(self, learning_rate):
        super().__init__()
        self.learning_rate = learning_rate

    def configure_optimizers(self):
        return torch.optim.Adam(self.parameters(), lr=(self.learning_rate))
  • 파이토치 라이트닝의 Tuner로 학습률을 자동 탐색
from lightning.pytorch.tuner import Tuner
trainer = Trainer(max_epochs=3, default_root_dir=log_dir)
tuner = Tuner(trainer)
model = MLPClassifierAutoLR(learning_rate=0)
tuner.lr_find(model, train_loader) # 최적 학습률을 찾으면 예외 발생
lr_finder = trainer.callbacks[0].optimal_lr
lr_finder.plot(suggest=True); # 학습률 찾기 과정 시각화
  • 학습률 찾기 그래프 읽기
    • 가로축은 로그 스케일의 학습률(10⁻⁸부터 점점 키움)
    • 초반: 학습률이 너무 작아 사실상 학습이 안 됨 → 미니배치에 따라 손실이 오르내리기만 함
    • 중반: 손실이 줄어들기 시작
    • 후반: 학습률이 너무 커서 손실이 다시 늘어남
  • 손실이 가장 빠르게 감소하는(기울기가 가장 급한) 학습률을 빨간 점으로 표시
# 제안된 학습률 확인
lr_finder.suggestion()
실행 결과
0.01
# 제안된 학습률로 설정되어 있음
model.learning_rate
실행 결과
0.01
  • 미니배치를 무작위로 뽑으므로 실행할 때마다 그래프와 제안값이 조금씩 다름
    • 로그 스케일이므로 0.01과 0.001, 0.1은 서로 근방에 해당
    • 손실이 꺾이기 시작하는 지점부터 다시 늘어나기 전까지는 대체로 괜찮은 학습률
  • 제안값이 항상 정확하지는 않음
    • 기울기가 가장 급한 곳을 기계적으로 고르므로, 데이터가 이상하게 걸려 손실이 잠깐 떨어진 구간이 선택되기도 함
    • 그래프를 눈으로 확인하고, 이상하면 다시 실행하거나 model.learning_rate = 0.01처럼 직접 설정
  • 한 번보다 여러 번 실행해 일관된 값이 나오는지 확인하는 것이 좋음

미니배치 크기 찾기

  • 미니 배치도 적절한 크기가 필요
    • 너무 작으면 → 학습 불안정
    • 너무 크면 → 학습 느림, 메모리 용량 문제(메모리에 올리는 데이터가 많아짐)
  • Tuner는 메모리가 허용하는 최대 배치 크기를 자동으로 찾아줌
    • 2, 4, 8, 16, … 처럼 두 배씩 늘리면서 메모리가 감당하지 못할 때까지 시도
    • 성능에 최적인 배치 크기를 찾아주는 것은 아니므로 따로 찾아야 함
    • 모형이 한 번에 처리할 수 있는 최대량을 알고 싶을 때 유용
  • 데이터 로더를 따로 만들지 않고, 모형 안에 train_dataloader를 정의해 배치 크기를 외부에서 받음
class MLPClassifierAutoBatchSize(MLPClassifier):
    def __init__(self, batch_size):
        super().__init__()
        self.batch_size = batch_size

    def train_dataloader(self):
        return DataLoader(binary_train_dataset, batch_size=self.batch_size)
model = MLPClassifierAutoBatchSize(batch_size=32)
trainer = Trainer(max_epochs=3, default_root_dir=log_dir)
tuner = Tuner(trainer)
tuner.scale_batch_size(model) # 메모리에 올릴 수 있을 때까지 미니 배치 크기를 자동으로 키움
model.batch_size # 최대 크기로 설정되어 있음
실행 결과
16384
  • 학습률과 배치 크기를 둘 다 인자로 받도록 모형을 정의하면 두 가지를 함께 자동 조정 가능

하이퍼파라미터 튜닝 과정

  • 학습률이나 배치 크기는 값을 점점 키워 가며 찾을 수 있지만, 은닉층 크기처럼 그렇게 찾기 어려운 하이퍼파라미터도 있음
  • 일반적인 순서
    1. 전체 데이터를 훈련 데이터와 테스트 데이터로 분할
    2. 훈련 데이터를 다시 나눠 교차 검증으로 하이퍼파라미터를 평가
    3. 가장 좋은 하이퍼파라미터로 훈련 데이터 전체를 다시 학습(생략하기도 함)
    4. 테스트 데이터로 최종 평가
  • 결과적으로 데이터는 훈련 / 검증 / 테스트의 세 부분으로 나뉨
    • 검증 데이터는 모의고사, 테스트 데이터는 수능에 비유할 수 있음

Validation 또는 Development set

  • 테스트 데이터셋을 이용해서 하이퍼파라미터를 결정하면 특정 테스트셋에 과적합될 수 있음
  • 별도의 Validation 또는 Development set을 이용해서 하이퍼파라미터를 결정
  • 테스트 데이터셋은 최종 평가에 마지막으로 한 번만 사용

교차 검증 cross-validation

  • 하나의 검증셋만 사용할 경우, 하이퍼파라미터가 정확하지 않을 수 있음
    • 어떤 검증 데이터를 뽑느냐에 따라 최적의 하이퍼파라미터가 달라질 수 있음
    • 출제 회사마다 경향이 다른 모의고사처럼, 한 번의 검증 결과만으로는 믿기 어려움
  • 다양한 검증셋을 이용하여 검증
    • 어떤 검증셋에서도 잘 작동하는 하이퍼파라미터가 신뢰할 만함
  • K-fold
    • 가장 많이 사용하는 방법
    • 훈련 데이터를 k개의 겹치지 않는 부분으로 나눔
    • k-1개로 훈련하고 나머지 하나로 검증하는 것을 k번 반복(매번 모형을 초기화)
  • Hold-out
    • 훈련 데이터와 검증 데이터로 1번만 분할해서 사용
    • 딥러닝에서는 훈련에 시간이 오래 걸리므로 자주 사용
    • 데이터가 적을 때는 신뢰성에 문제

데이터 분할

from sklearn.model_selection import train_test_split
train_indices, val_indices = train_test_split(
    range(len(binary_train_dataset)), # 데이터 번호를
    test_size=0.2) # 80:20으로 나눔

# 훈련 데이터 번호에 해당하는 부분집합
train_subset = torch.utils.data.Subset(binary_train_dataset, train_indices)
train_loader = DataLoader(train_subset, batch_size=32, shuffle=True)

# 검증 데이터 번호에 해당하는 부분집합
val_subset = torch.utils.data.Subset(binary_train_dataset, val_indices)
val_loader = DataLoader(val_subset, batch_size=32, shuffle=False)

은닉층 크기 바꿔 보기

  • 은닉층 크기를 인자로 받을 수 있게 수정
class MLPClassifierHiddenSize(MLPClassifier):
    def __init__(self, hidden_size, input_size=784): # 은닉층 크기를 인자로 받음
        super().__init__()
        self.linear1 = nn.Linear(input_size, hidden_size) # 은닉층
        self.linear2 = nn.Linear(hidden_size, 1) # 한 개의 출력 뉴런
  • 은닉층 크기를 다양하게 시도하고 검증 데이터로 평가
results = []
for hidden_size in [16, 64, 128]: # 은닉층 크기를 다양하게 시도
    model = MLPClassifierHiddenSize(hidden_size)
    trainer = Trainer(max_epochs=3, default_root_dir=log_dir)
    trainer.fit(model, train_loader)
    result = trainer.test(model, val_loader)
    result[0]['hidden_size'] = hidden_size
    results.append(result[0])
results
실행 결과
[{'test_accuracy': 0.9566666483879089, 'hidden_size': 16},
 {'test_accuracy': 0.9399999976158142, 'hidden_size': 64},
 {'test_accuracy': 0.9574999809265137, 'hidden_size': 128}]

격자 탐색과 무작위 탐색

  • 격자 탐색 grid search: 각 하이퍼파라미터의 설정값을 정하고, 이들을 조합하여 하나씩 시도
  • 무작위 탐색 random search: 하이퍼파라미터를 무작위로 조합하여 하나씩 시도
  • 하이퍼파라미터를 더 다양하고 촘촘하게 탐색하여 격자 탐색보다 더 높은 성능을 내는 경향

정수 난수 생성

import scipy
hp = scipy.stats.randint.rvs(low=1, high=8, size=3) # 1 이상 8 미만의 무작위 정수 3개
# 은닉층의 크기 등 배수로 사용해야 할 경우는 다음과 같이
hidden_size = 2 ** hp
  • 무작위로 은닉층 크기를 뽑아 10번 시도
results = []
for _ in range(10):
    hp = scipy.stats.randint.rvs(low=2, high=10, size=1)
    hidden_size = (2 ** hp)[0] # 4에서 512 사이의 2의 거듭제곱
    model = MLPClassifierHiddenSize(hidden_size)
    trainer = Trainer(max_epochs=3, default_root_dir=log_dir)
    trainer.fit(model, train_loader)
    result = trainer.test(model, val_loader)
    result[0]['hidden_size'] = hidden_size
    results.append(result[0])

실수 난수 생성

# -8에서 -1(+7)까지 무작위 실수 3개
hp = scipy.stats.uniform.rvs(loc=-8, scale=7, size=3)
# 학습률 등 배수로 사용해야 할 경우는 다음과 같이
learning_rate = 10 ** hp

# 10⁻⁸에서 10⁻¹까지 로그 균등 분포를 사용해도 똑같음
learning_rate = scipy.stats.loguniform.rvs(a=1e-8, b=1e-1, size=1000)

Sequential Model-Based Optimization

  • 무작위 탐색의 문제점:
    • 하이퍼파라미터의 종류가 많아질 수록 더 많은 탐색이 필요
    • 하이퍼파라미터는 학습 전체가 끝나야 성능을 알 수 있음 (비쌈)
    • 무작위 탐색은 이전의 학습 결과를 활용하지 않음
  • SMBO: 하이퍼파라미터와 성능의 관계를 모형화
    • 모형을 바탕으로 최적의 하이퍼파라미터를 선택하여 학습
    • 학습 결과를 바탕으로 모형을 업데이트하는 과정을 반복
    • 적은 데이터에서 높은 성능을 내는 모형을 사용
    • 기본적으로 어느 정도의 데이터를 요구하므로 다양한 하이퍼파라미터를 선제적으로 시도해야 함 → 효율성이 떨어짐

베이즈 오차 Bayes Error

  • 오차 0%는 다양한 이유로 실현 불가능(예: 테스트 이미지의 레이블이 잘못 붙어 있음. 식별 불가능한 이미지 등)
  • 베이즈 오차: 데이터로부터 도달할 수 있는 이론적으로 최소의 오차
  • 베이즈 오차를 실제로 추정하기는 불가능
  • 보통 사람의 오차를 베이즈 오차 대용으로 사용
    • 예: MNIST의 경우 사람의 오차 0.2% 수준
Previous
데이터셋 분할