다시 이음

딥러닝(4) - 하이퍼파라미터 튜닝, 가중치 초기화 본문

AI 일별 공부 정리

딥러닝(4) - 하이퍼파라미터 튜닝, 가중치 초기화

Taeho(Damon) 2021. 10. 22. 19:29

안녕하세요.

 

오늘은 하이퍼 파라미터 튜닝을 통해 딥러닝 모델의 성능을 올리는 것을 알아보겠습니다.

 

머신러닝을 배울 때 알아본 것과 같이 딥러닝 또한 하이퍼 파라미터 조정을 통해서 성능을 올리고,

그 성능을 평가하기 위해서 교차검증(Cross_Validation)을 사용합니다.

 

먼저 딥러닝 모델(신경망)에 교차검증을 적용하는 방법을 알아볼게요.

 

 

교차 검증 적용

 

Cross-Validation의 방법에는 K-Fold CV, Stratified K-Fold CV가 있습니다.

 

K-Fold CV

 

- k라는 매개변수는 데이터 세트를 분할할 폴드 수를 결정합니다.

- 훈련세트는 k-1 개, 검증세트는 1개로 이루어집니다.

- 일반적으로 k는 5~10사이의 값을 사용합니다.

k값이 너무 낮으면 매우 편향된 모델이 됩니다. ( 편향(Bias)는 높고 분산(Varience)은 낮습니다. )

k값이 너무 크면 편향은 낮지만 분산이 높고 모델이 과적합 되어 일반화되지 않습니다.

 

Stratified K-Fold CV

 

- k-Fold로 분할되기 전에 데이터를 한 번 섞은 뒤에 분할하여 각 클래스의 비율이 각 폴드에서 동일하기 유지되도록 하는 방법입니다.

- 이 방법은 불균형 데이터 세트에 유용합니다.

 

 

K-Fold CV, Stratified K-Fold CV 구현해보기

import numpy as np
import pandas as pd
import os
from sklearn.model_selection import KFold, StratifiedKFold
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense

# kf 와 skf 에 각각 학습 데이터를 5개 로 나누도록 지정합니다.
kf = KFold(n_splits = 5)
skf = StratifiedKFold(n_splits = 5, random_state = 100, shuffle = True)

#데이터가 array형태인 경우는 데이터프레임으로 변환하여 사용합니다.
x_train = pd.DataFrame(x_train)
y_train = pd.DataFrame(y_train)

#데이터 분할하기
for train_index, val_index in kf.split(np.zeros(x_train.shape[0]), y_train):
  training_data = x_train.iloc[train_index]
  validation_data = x_train.iloc[val_index]
  training_y = y_train.iloc[train_index]
  validation_y = y_train.iloc[val_index]
  
##위의 코드에서 Stratified K-Fold CV인 경우엔 for train_index, test_index in skf.split(X, y): 사용

# CREATE NEW MODEL
model = Sequential()
model.add(Dense(64, activation='relu'))
model.add(Dense(64, activation='relu'))
model.add(Dense(1))

# COMPILE NEW MODEL
model.compile(loss='mean_squared_logarithmic_error',
              optimizer='adam',
              metrics=['accuracy'])
              
#모델 학습하기
model.fit(training_data, training_y,epochs=2)

#교차 검증 적용하기
#데이터셋 다시 분할해주기
x_train = pd.DataFrame(x_train)
y_train = pd.DataFrame(y_train)
for train_index, val_index in kf.split(np.zeros(x_train.shape[0])):
  training_data = x_train.iloc[train_index, :]
  training_data_label = y_train.iloc[train_index]
  validation_data = x_train.iloc[val_index, :]
  validation_data_label = y_train.iloc[val_index]

  # CV
  model.compile(loss='mean_squared_error', optimizer='adam')
  model.fit(x_train, y_train,
			    epochs=10,
          batch_size=30,
          validation_data = (validation_data, validation_data_label),
          )
  results = model.evaluate(x_test, y_test, batch_size=128)
  print("test loss, test mse:", results)

❄️  kf.split는 scikt-learn 라이브러리에서 k-fold에 메소드중에 하나입니다.

split(X,y)형태인데 여기서 X는 훈련 데이터의 shape입니다. 말그대로 shape 정보만 있으면 되기 때문에 np.zeros(훈련데이터.shape[0])으로 shape만 전달해주면 되기때문에 np.zeros를 사용해도 무관합니다.

 

 

❄️ 훈련데이터와 검증데이터를 분할하기

scikt-learn의 train_test_split과 keras의 validation_split의 차이점을 보려고 합니다.

 

train_test_split

- 사용자가 정한 비율로 무작위로 데이터를 분할합니다.

- 그러나 모델이 어떻게 훈련 되는지 에 대해서는 거의 알지 못합니다 .(fit과정에서 적용되지 않기 때문에)

- 즉, 작은 배치 크기를 선택했거나 매우 높은 Epoch를 선택했을 수 있습니다.

- 우리가 CV를 사용하는 이유는 성능을 효율적으로 높이기 위함인데 파라미터의 값을 제대로 알지못하면 효율적인 성능교정이 어렵습니다.

 

validation_split

- validation_split = 0.2 과 같이 검증데이터 비율을 정해줍니다.

- 무작위로 선정이 아닌 인덱스 순서대로 데이터를 분할 합니다.

- 그래프를 그려보았을 때 위의 train_test_split 보다 많은 정보를 확인할 수 있습니다. (fit과정에서 적용되어 확인 가능)

- 예를 들어 그래프가 노이즈가 많은 경우, batch_size가 너무 작거나 옵티마이저가 제대로 최적화를 진행하지 못했다고 확인할 수 있습니다.

 

가장 좋은 방법은 train_test_split을 통하여 데이터를 분할하고 model.fit과정에서 validation_data 파라미터에 넣어주는 것입니다. 이러면 무작위성이라는 유용성과 그래프를 통한 문제 파악이 동시에 가능하게 됩니다.

 

 

입력 데이터 정규화 (Normalizing)

 

무조건 필요하진 않으나 하게되면 학습을 빠르게 해주고, 최적화 과정에서 지역 최적점(Local optimum)에 빠질 위험을 줄여줍니다.

 

 

 

하이퍼 파라미터 튜닝

 

하이퍼 파라미터를 튜닝하는 과정에는 대표적으로 Grid Search와 Random Search가 있습니다.

 

사실 머신러닝을 배울때 해당 내용도 살펴보았기 때문에 간단하게 설명하고 넘어가겠습니다.

 

Grid Search는 사용자가 정해준 하이퍼 파라미터의 선택지 모두를 실행해보고 성능을 파악합니다.

Random Search는 사용자가 정해준 하이퍼 파라미터의 선택지에서 지정된 시도 횟수동안 무작위로 모델을 실행하고 성능을 파악합니다.

 

당연하게 Grid는 너무나도 많은 시간이 소요될 것이고, Random은 좋은 성능을 낼 수 있는 파라미터를 못찾을 수 도 있습니다.

 

좋은 방법은 Random Search를 먼저 실행하여 대략적인 구도를 잡고 Grid로 꼼꼼하게 확인 하는 방법입니다.

 

import numpy
import pandas as pd
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier

#사용할 KerasClassifier을 위해 함수로 정의합니다.
def create_model():
    # 모델 제작
    model = Sequential()
    model.add(Dense(100, input_dim=8, activation='relu'))
    model.add(Dense(1, activation='sigmoid'))
    
    # 모델 컴파일링
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model

# keras.wrapper를 활용하여 분류기를 만듭니다
model = KerasClassifier(build_fn=create_model, verbose=0)

# GridSearch 하이퍼 파라미터 설정
batch_size = [10, 20, 40, 60, 80, 100]
epochs = [30,50,60]
optimizer = ['adam', 'rmsprop']
param_grid = dict(batch_size=batch_size,epochs=epochs,optimizer=optimizer)

# grid search 사용할때
grid = GridSearchCV(estimator=model, param_grid=param_grid, scoring='accuracy', cv=10, n_jobs=1)
# random search 사용할때
grid = RandomizedSearchCV(estimator=model, param_distributions=param_grid, scoring='accuracy', cv=10, n_jobs=1)
grid_result = grid.fit(X, Y)

#결과값 보기
print(f"Best: {grid_result.best_score_} using {grid_result.best_params_}")

means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']

for mean, stdev, param in zip(means, stds, params):
    print(f"Means: {mean}, Stdev: {stdev} with: {param}")

 

❄️ GridSearchCV와 RandomizedSearchCV는 우리가 머신러닝에서 사용했던 그 라이브러리가 맞습니다.

즉 sklearn 라이브러리에서 import하는 것인데 텐서플로우로 만든 우리의 딥러닝 모델은 sklearn라이브러리에서 구동하기 위해서는 keras의 wrapper 과정을 통해 한번 포장을 해야만 합니다. 우리는 위에서 kerasclassifier를 활용하여 포장을 했습니다. 그외에도 keras.wrappers.scikit_learn.KerasRegressor(build_fn=None, **sk_params)과 같은 방법이 있으니 참고해주세요.

 

딥러닝의 하이퍼 파라미터

 

  • 배치 크기(batch_size)
  • 반복 학습 횟수(에포크, training epochs)
  • 옵티마이저(optimizer)
  • 학습률(learning rate)
  • 활성화 함수(activation functions)
  • Regularization(weight decay, dropout 등)
  • 은닉층(Hidden layer)의 노드(Node) 수

❄️ 위의 하이퍼 파라미터 이외에도 많은 하이퍼 파라미터가 있습니다. 찾아보는 것도 좋은 공부가 될 거에요.

 

 

또다른 하이퍼 파라미터 튜닝방법

Keras Tuner

 

Keras Tuner 는 하이퍼파라미터 검색 수행의 문제점을 해결하는 사용하기 쉽고 배포 가능한 하이퍼파라미터 최적화 프레임워크입니다.

 

Keras Tuner는 Bayesian Optimization, Hyperband 및 Random Search 알고리즘이 내장되어 있습니다.

 

 

Tuner을 통해 하이퍼 파라미터 튜닝 구현해보기

import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten
import IPython
from tensorflow import keras
from tensorflow.keras import layers
import kerastuner as kt
# 설치
!pip install -U keras-tuner

def build_model(hp):
    model = keras.Sequential()
        # Dense Layer에 unit수 선택
    # 정수형 타입 32부터 512까지 32배수 범위 내에서 탐색
        # activation 은 relu 사용
    model.add(layers.Dense(units=hp.Int('units',
                                        min_value=32,
                                        max_value=512,
                                        step=32),
                           activation='relu'))

    model.add(layers.Dense(10, activation='softmax'))
    model.compile(
        optimizer=keras.optimizers.Adam(
        # 학습률은 자주 쓰이는 0.01, 0.001, 0.0001 3개의 값 중 탐색
            hp.Choice('learning_rate',
                      values=[1e-2, 1e-3, 1e-4])),
        loss='sparse_categorical_crossentropy',
        metrics=['accuracy'])
    return model
# RandomSearch
from kerastuner.tuners import RandomSearch

tuner = RandomSearch(
    build_model, # HyperModel
    objective='val_accuracy', #  최적화할 하이퍼모델
    max_trials=5,
    executions_per_trial=3, # 각 모델별 학습 회수
    directory='my_dir', # 사용된 parameter 저장할 폴더
    project_name='helloworld') # 사용된 parameter 저장할 폴더
# Hyperband
from kerastuner.tuners import Hyperband

tuner = kt.Hyperband(
        build_model,, # HyperModel
        objective ='val_accuracy', #  최적화할 하이퍼모델
        max_epochs =20, # 각 모델별 학습 회수
        factor = 3,    # 한 번에 훈련할 모델 수 결정 변수
        directory ='my_dir', # 사용된 parameter 저장할 폴더
        project_name ='helloworld') # 사용된 parameter 저장할 폴더

# 작성한 Hypermodel 출력
tuner.search_space_summary()
# tuner 학습
tuner.search(x, y,
             epochs=5,
             validation_data=(val_x, val_y))
# 최고의 모델을 출력
models = tuner.get_best_models(num_models=2)
# 혹은 결과 출력
tuner.results_summary()

 

 

 

가중치 초기화(Network Weight Initialization)

 

가중치의 초기화는 쉽게 말해서 학습 시작 전에 가중치의 시작점을 정해주는 것입니다.

 

이 초기화가 어떻게 이루어지냐에 따라서 전역 최적점을 찾을 수도 있고 아니면 반대로 성능이 나쁠 수도 있습니다.

 

또한, 딥러닝이 어떤 문제를 해결하는 것인가(이진분류, 회귀 등등)에 따라서 초기화 방법을 다르게 설정하는 것이 효과적일 수 있습니다.

 

일반적으로 활성화 함수가 시그모이드,Tahn 함수일 때는 사비에르 초기화를, ReLU류의 함수일 때는 He 초기화를 사용합니다. 

 

 

1) Zero initialization

-가중치를 모두 0으로 맞추는 초기화 방법입니다.

-그러나 신경망 노드의 파라미터가 모두 동일하다면 여러 개의 노드로 신경망을 구성하는 의미가 사라집니다.

-또한, 여러개의 층을 구성하는 의미도 없어집니다.

 

2)Random Initialization

- 가중치의 초기값을 평균 0, 표준편차 1인 정규분포 안에서 무작위로 선정합니다.

- w = np.random.randn(n_input, n_output) * 0.01 식을 사용합니다.

- 정규분포의 형태를 가져가기 때문에 0,1값에 분포가 집중됩니다.

- 이렇게 0과1에 분포가 집중되는 경우 활성값이 고르지 않아 학습이 제대로 이루어지지 않습니다.

 

3)가중치의 표준편차를 줄여서 초기화

- 그렇다면 위의 random 초기화에서 표준편차를 좀 줄어보면 어떨까요?

- 표준편차를 0.01로 줄이고 살펴보면 이번엔 값들이 0.5부분에 집중이 되어 있습니다.

- 이러한 경우 위에 zero 초기화에서 말한 것과 같이 파라미터가 거의 동일하기에 여러 개의 노드를 구성하는 의미가 사라집니다.

 

4)Xavier Initialization

- w = np.random.randn(n_input, n_output) / sqrt(1/n_input) 의 식을 가집니다.

- 이전 은닉층의 노드 수에 맞추어 변화하여 초기값을 설정합니다.

- 위의 방법들보다 가중치가 고르게 분포하게 되며 gradient vanishing 현상도 줄게 됩니다.

 

5) He Initialization

- w = np.random.randn(n_input, n_output) / sqrt(2/n_input) 의 식을 가집니다.

- Relu 함수에서 사비에르 초기화를 사용하면 층을 거칠 수록 점점 0에 수렴하게 됩니다.

- 그러한 것을 방지할 수 있는 He 초기화는 Relu함수에서 층을 거치더라도 고르게 분포하게 됩니다.

 

#사비에르 초기화 코드
tf.keras.initializers.GlorotNormal()
#He 초기화 코드
tf.keras.initializers.HeNormal()