ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NLP] 딥러닝 신경망에서 하이퍼파라미터 최적화 구현하기
    ML/NLP 2020. 4. 24. 14:29

     

    신경망에는 하이퍼파라미터가 다수 등장한다.

     

    각 층의 뉴런 수, 배치 크기, 매개변수 갱신 시의 학습률, 가중치 감소 등이 있다.

     

    하이퍼파라미터의 값은 모델의 성능에 크게 영향을 미치기 때문에 그 값을 결정하기까지 많은 시행착오를 겪는다.

     

    그래서 이번 포스팅에서는 하이퍼파라미터의 값을 최대한 효율적으로 탐색하는 방법을 설명하겠다.

     

     

     

     

    검증 데이터


    지금까지는 데이터셋을 훈련 데이터와 시험 데이터 두 가지로 분리해 이용했다.

     

    훈련 데이터로는 학습을 하고, 시험 데이터로는 범용 성능을 평가했다.

     

     

    앞으로 하이퍼파라미터를 다양한 값으로 설정하고 검증할 텐데,

     

    여기서 주의할 점은 하이퍼파라미터의 성능을 평가할 때는 시험 데이터를 사용하면 안 된다는 것이다.

     

    하이퍼파라미터 값이 시험 데이터에 오버피팅되기 때문이다.

     

     

    그래서 하이퍼파라미터 전용 확인 데이터인 검증 데이터(validation data)가 필요하다.

     

    정리하자면 데이터셋의 분류는 다음과 같이 이뤄진다.

     

    훈련 데이터 : 매개변수 학습
    검증 데이터 : 하이퍼파라미터 성능 평가
    시험 데이터 : 신경망의 범용 성능 평가

     

     

    MNIST 데이터셋을 나눌 때는 훈련 데이터 중 20% 정도를 검증 데이터로 먼저 분리하는 것으로 구현 가능하다.

     

    코드는 다음과 같다.

     

     

    def shuffle_dataset(x, t):
        """데이터셋을 뒤섞는다.
    
        Parameters
        ----------
        x : 훈련 데이터
        t : 정답 레이블
        
        Returns
        -------
        x, t : 뒤섞은 훈련 데이터와 정답 레이블
        """
        permutation = np.random.permutation(x.shape[0])
        x = x[permutation,:] if x.ndim == 2 else x[permutation,:,:,:]
        t = t[permutation]
    
        return x, t
    (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.001에서 1.000사이(10^-3 ~ 10^3)와 같이 '10의 거듭제곱' 단위로 범위를 지정한다.

     

    이를 '로그 스케일(log scale)로 지정'한다고 한다.

     

     

    딥러닝 학습은 오랜 시간이 걸리기 때문에

     

    하이퍼파라미터 최적화에서는 학습을 위한 에폭을 작게 하여, 1회 평가에 걸리는 시간을 단축하는 것이 효과적이다.

     

     

    지금까지를 정리하면 다음과 같다.

     


    0단계
    하이퍼파라미터 값의 범위를 설정

    1단계
    설정된 범위에서 하이퍼파라미터 값을 무작위로 추출

    2단계
    1단계에서 샘플링한 하이퍼파라미터 값을 사용하여 학습하고, 검증 데이터로 정확도를 평가 (단, 에폭은 작게 설정)

    3단계
    1단계와 2단계를 특정 횟수(100회 등) 반복하며, 정확도 결과를 보고 하이퍼파라미터의 범위를 좁힘

     

     

     

     

     

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


    MNIST 데이터셋을 사용하여 하이퍼파라미터를 최적화해보자.

     

    학습률과 가중치 감소의 세기를 조절하는 계수(가중치 감소 계수)를 탐색하는 문제를 풀어보겠다.

     

     

    하이퍼파라미터의 검증은 그 값을 0.001~1.000(10^-3 ~ 10^3) 사이 같은 로그 스케일 범위에서 무작위로 추출해 수행한다.

     

    이 예에서는 가중치 감소 계수를 10^-8 ~ 10^-4, 학습률을 10^-6 ~ 10^-2 범위부터 시작한다.

     

    하이퍼파라미터 무작위 추출 코드는 다음과 같이 구현 가능하다.

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

     

    전체 구현 코드는 다음과 같다.

    import sys, os
    sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
    import numpy as np
    import matplotlib.pyplot as plt
    from dataset.mnist import load_mnist
    from common.multi_layer_net import MultiLayerNet
    from common.util import shuffle_dataset
    from common.trainer import Trainer
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
    
    # 결과를 빠르게 얻기 위해 훈련 데이터를 줄임
    x_train = x_train[:500]
    t_train = t_train[:500]
    
    # 20%를 검증 데이터로 분할
    validation_rate = 0.20
    validation_num = int(x_train.shape[0] * validation_rate)
    x_train, t_train = shuffle_dataset(x_train, t_train)
    x_val = x_train[:validation_num]
    t_val = t_train[:validation_num]
    x_train = x_train[validation_num:]
    t_train = t_train[validation_num:]
    def __train(lr, weight_decay, epocs=50):
        network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
                                output_size=10, weight_decay_lambda=weight_decay)
        trainer = Trainer(network, x_train, t_train, x_val, t_val,
                          epochs=epocs, mini_batch_size=100,
                          optimizer='sgd', optimizer_param={'lr': lr}, verbose=False)
        trainer.train()
    
        return trainer.test_acc_list, trainer.train_acc_list
    # 하이퍼파라미터 무작위 탐색======================================
    optimization_trial = 100
    results_val = {}
    results_train = {}
    for _ in range(optimization_trial):
        
        # 탐색한 하이퍼파라미터의 범위 지정=======================
        weight_decay = 10 ** np.random.uniform(-8, -4)
        lr = 10 ** np.random.uniform(-6, -2)
        # ================================================
    
        val_acc_list, train_acc_list = __train(lr, weight_decay)
        print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
        key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
        results_val[key] = val_acc_list
        results_train[key] = train_acc_list
    Out:
    val acc:0.16 | lr:1.2450497392578667e-06, weight decay:6.678876594659689e-08
    val acc:0.13 | lr:0.0008014894043264244, weight decay:1.3419113245821615e-06
    val acc:0.15 | lr:0.0007357862452678139, weight decay:2.9185292748807105e-05
    ...
    # 그래프 그리기========================================================
    print("=========== Hyper-Parameter Optimization Result ===========")
    graph_draw_num = 20
    col_num = 5
    row_num = int(np.ceil(graph_draw_num / col_num))
    i = 0
    
    for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
        print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)
    
        plt.subplot(row_num, col_num, i+1)
        plt.title("Best-" + str(i+1))
        plt.ylim(0.0, 1.0)
        if i % 5: plt.yticks([])
        plt.xticks([])
        x = np.arange(len(val_acc_list))
        plt.plot(x, val_acc_list)
        plt.plot(x, results_train[key], "--")
        i += 1
    
        if i >= graph_draw_num:
            break
    
    plt.show()
    Out:
    =========== Hyper-Parameter Optimization Result ===========
    Best-1(val acc:0.83) | lr:0.009630835554746251, weight decay:8.81933776936228e-06
    Best-2(val acc:0.82) | lr:0.007234380480467161, weight decay:1.7119407165195253e-07
    Best-3(val acc:0.75) | lr:0.00896391385067947, weight decay:4.104982835711962e-05
    Best-4(val acc:0.68) | lr:0.005324719863445548, weight decay:9.181711223075184e-08
    Best-5(val acc:0.67) | lr:0.005767390004630546, weight decay:5.8440071305532446e-08
    ...

    그림 1

     

    [그림 1]은 검증 데이터의 학습 추이를 정확도가 높은 순서로 나열했다.

     

    이를 보면 'Best-5' 정도까지는 학습이 순조롭게 진행되고 있다.

     

    이를 바탕으로 'Best-5'까지의 하이퍼파라미터의 값 (학습률과 가중치 감소 계수)을 살펴보겠다.

     

     

    출력 결과를 보면 학습이 잘 진행될 때의 학습률은 0.001~0.001, 가중치 감소 계수는 대략 10^-8~10^-6 정도라는 것을 알 수 있다.

     

    이처럼 잘될 것 같은 값의 범위를 관찰하고 범위를 좁혀간다.

     

    이후 그 축소된 범위로 똑같은 작업을 반복하는 것이다.

     

    이렇게 적절한 값이 위치한 범위를 좁혀가다가 특정 단계에서 최종 하이퍼파라미터 값을 하나 선택한다.

     

     

    댓글

dokylee's Tech Blog