하이퍼 파라미터, 교차검증, Grid Search, Keras Tuner
NOTE
오늘 자주 나온 개념인 교차검증 / GridSearchCV 등은 섹션2 머신러닝 배울 때 자세히 다루었던 것이기 때문에 여기에 굳이 내용을 반복하여 옮겨두지는 않을 것이다. 필요시 아래 두 글을 참고할 것.
- 교차검증이란? // 220510_TIL / Model Selection
- (참고로 위 글에선 언급이 안되었지만
Bayesian Methods
이라는 기법도 존재함)
오늘은 신경망에서 최적의 하이퍼 파라미터를 어떻게 효율적으로 찾을 수 있는지 그 방법 및 구현에 대해서 주로 배웠는데, Keras를 통해 어떻게 다양한 하이퍼 파라미터 튜닝을 할 수 있는지 코드 위주로 기록해두려고 한다.
배치 크기(batch_size)
에포크(epochs)
옵티마이저(optimizers)
학습률(learning rate)
활성화 함수(activation)
Regularization(weight decay, Dropout 등)
은닉층(Hidden layer)의 노드(Node) 수
참고) 파라미터 vs 하이퍼 파라미터
그럼 파라미터는 뭔가요?
라는 질문을 받았을 때 뭔가 턱 막혔다. 그래서 들은 내용을 다시 정리해둔다.참고로 오늘 실습은 전처리를 제외하고는 거의 구현이 되어있는 걸 직접 돌려보며 코드의 구조를 이해한다는 목적이 강했다. 나중에 잘 가져다가 쓰기 위해 기록해둔다는 의미로 생각하자!
진행 방식 참고
1. 데이터를 다운로드 받고 읽어옴(load)
2. 데이터 클리닝을 진행 (필수는 아니지만 추천)
3. Keras MLP model을 만들고, 학습 진행
4. Hyperparameter 튜닝 진행:
- batch_size
- training epochs
- optimizer
- learning rate (optimizer에 따라서 해당되면)
- momentum (optimizer에 따라서 해당되면)
- activation functions
- network weight initialization
- dropout regularization
- number of neurons in the hidden layer
데이터 불러오기 과정은 생략. 통신사 고객 이탈 예측 문제였다.
[전처리]
아 오늘 오랜만에 전처리 하니까 헷갈리는게 너무 많았다ㅠㅠ
코랩에서 셀 쪼개서 한 것들도 모두 한 코드 묶음으로 모아두겠음.
# 결측치 확인
df.isna().sum() # => 결측치 없음.sum()함수는 기본적으로 True의 개수를 알려줌.
# 이탈을 예측하는데 중요한 'TotalCharges' 칼럼의 타입이 숫자가 아니라 문자형이었다.
# 데이터 타입 확인
df.info() # => target 'TotalCharges' Dtype == object.
# TotalCharges 숫자형으로 변환
'''
df['TotalCharges'] = pd.to_numeric(df['TotalCharges']) 와 같이 그냥 일괄 처리를 시도하면 'Unable to parse string " " at position 488' 에러가 나는데,
그래서 df['TotalCharges'][488]을 보면 " " 으로 공백으로 구성된 값도 있다는 걸 알 수 있다. 이것도 포함하여 적절한 처리가 필요하다. (참고로 astype()도 에러가 난다.)
'''
# apply 함수로 적용하기
df['TotalCharges'] = df['TotalCharges'].apply(pd.to_numeric, errors = 'coerce') #errors 파라미터 : 숫자 형태의 문자가 아닌 경우 NaN으로 변경함. 이외에 ignore(무시하고 지나감), raise(오류 발생)가 있음.
df.info() # => 결과를 보면 우리 레이블의 Non-null Count가 다른 칼럼에 비해 7032로 11개 빠진 걸 알 수 있는데 이걸로 결측치가 몇 개구나 추측도 바로 가능함.
# TotalCharges 결측치 확인
print('1) NaN 갯수 = ', df['TotalCharges'].isnull().sum()) # 잘 기억해두기.
# 결측치 드랍
df = df.dropna(subset = ['TotalCharges'])
# 결측치 재확인
print('2) NaN 갯수 = ', df['TotalCharges'].isnull().sum())
----
# customerID 드랍
df = df.drop(columns='customerID')
# 'No internet service' 로 표현되어 있는 것을 'No'로 변경
no_internet_feats = [ 'TechSupport','OnlineBackup', 'DeviceProtection','StreamingTV',
'OnlineSecurity','StreamingMovies']
for i in no_internet_feats:
df[i] = df[i].replace({'No internet service':'No'})
# 'No phone service' 로 표현되어 있는 것을 'No'로 변경
df['MultipleLines']=df['MultipleLines'].replace({'No phone service':'No'})
# 0으로 표기된 것을 'No'로, 1로 표기된 것을 'Yes'로 변경
df['SeniorCitizen']=df['SeniorCitizen'].replace({0:'No',
1:'Yes'})
## => 인코딩 제대로 해주기 위해 한 과정으로 이해함.
# 오디널 인코딩
from category_encoders import OrdinalEncoder
encoder = OrdinalEncoder()
df_encoded = encoder.fit_transform(df)
# 타겟을 0과 1로 바꿔주기
df_encoded['Churn'] = df_encoded['Churn'].replace({1: 0, 2: 1})
df_encoded['Churn'].value_counts()
----
# 훈련, 테스트 셋을 나누기
from sklearn.model_selection import train_test_split
train, test = train_test_split(df_encoded, test_size = 0.25, random_state = 1) # => df가 아니라 df_encoded인 것 유의.
train.shape, test.shape # => ((5274, 20), (1758, 20))
# features 와 target 을 분리
target = 'Churn'
features = df_encoded.drop(columns=[target]).columns
X_train = train[features]
X_test = test[features]
y_train = train[target]
y_test = test[target]
# 정규화
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
[기본 모델 만들기]
#필요한 라이브러리 import
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten
import keras
import tensorflow as tf
import IPython
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier
from sklearn.model_selection import GridSearchCV
#!pip install -U keras-tuner
import kerastuner as kt
----
# Class의 개수 확인
np.unique(y_train) # => array([0, 1])
----
# 간단한 모델 만들어서 성능을 보기 !
tf.random.set_seed(7)
model2 = Sequential()
model2.add(Dense(64, activation='relu'))
model2.add(Dense(64, activation='relu'))
model2.add(Dense(1, activation='sigmoid')) # 바이너리 분류이므로 노드수 1, 활성화 함수로는 시그모이드(sigmoid)
model2.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
results = model2.fit(X_train_scaled, y_train, epochs=10, validation_data=(X_test_scaled,y_test))
# 테스트셋 사용해서 결과 보기
model2.evaluate(X_test_scaled, y_test, verbose=2)
[GridSearchCV 사용]
# 모델 만들기
tf.random.set_seed(7)
def model_builder(nodes=16, activation='relu'):
model = Sequential()
model.add(Dense(nodes, activation=activation))
model.add(Dense(nodes, activation=activation))
model.add(Dense(1, activation='sigmoid')) # 이진분류니까 노드수 1, 활성함수로는 시그모이드
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
return model
# keras.wrapper를 활용하여 분류기를 만듭니다
model = KerasClassifier(build_fn=model_builder, verbose=0)
# GridSearch
batch_size = [64, 128, 256]
epochs = [10, 20, 30]
nodes = [64, 128, 256]
activation = ['relu', 'sigmoid']
param_grid = dict(batch_size=batch_size, epochs=epochs, nodes=nodes, activation=activation)
# GridSearch CV를 만들기
grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=3, verbose=1, n_jobs=-1)
grid_result = grid.fit(X_train_scaled, y_train)
----
# 최적의 결과값을 낸 파라미터를 출력합니다
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}")
[Keras Tuner 사용]
Keras Tuner
는 케라스 프레임워크에서 하이퍼 파라미터를 튜닝하는데 도움이 되는 라이브러리이다. hyperband
를 사용했는데, 이건 쉽게 말해 초기에 몇 번 탐색을 해서 좋은 방향이 나오면 그쪽으로 튜닝을 계속 해주는 방법이다.
Model builder function(model_builder)
, 훈련할 최대 epochs 수(max_epochs)
등을 지정해주어야 한다.Random Search
, Bayesian Optimizaion
등을 사용할 수 있다.# 모델 만들기
def model_builder(hp):
model = Sequential()
# Dense layer에서 노드 수를 조정(32-512)
hp_units = hp.Int('units', min_value = 32, max_value = 512, step = 32) # 32부터 512까지 32개씩 증가시키며 탐색한다는 의미임.
model.add(Dense(units = hp_units, activation='relu'))
model.add(Dense(units = hp_units, activation='relu'))
model.add(Dense(1, activation='sigmoid')) # 이진분류니까 노드수 1, 활성함수로는 시그모이드
# Optimizer의 학습률(learning rate)을 조정[0.01, 0.001, 0.0001]합니다.
hp_learning_rate = hp.Choice('learning_rate', values = [1e-2, 1e-3, 1e-4])
# 컴파일 단계, 옵티마이저와 손실함수, 측정지표를 연결해서 계산 그래프를 구성함
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate = hp_learning_rate),
loss=keras.losses.BinaryCrossentropy(from_logits = True),
metrics=['accuracy'])
return model
----
# 튜너를 인스턴스화하고 하이퍼 튜닝을 수행
tuner = kt.Hyperband(model_builder,
objective = 'val_accuracy',
max_epochs = 30,
factor = 3,
directory = 'my_dir',
project_name = 'intro_to_kt')
'''
AttributeError: module 'keras.optimizers' has no attribute 'Adam'
=> keras.optimizers.Adam > tf.keras.optimizers.Adam로 수정.
'''
# callback 정의 : 하이퍼파라미터 탐색을 실행하기 전에 학습이 끝날 때마다 이전 출력이 지워지도록 하는 걸 말한다. 코드를 직접 돌려보면 무슨 말인지 쉽게 알 수 있다.
class ClearTrainingOutput(tf.keras.callbacks.Callback):
def on_train_end(*args, **kwargs):
IPython.display.clear_output(wait = True)
tuner.search(X_train_scaled, y_train, epochs = 30, batch_size=50, validation_data = (X_test_scaled,y_test), callbacks = [ClearTrainingOutput()])
best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0]
print(f"""
최적화된 Dense 노드 수 : {best_hps.get('units')}
최적화된 Learning Rate : {best_hps.get('learning_rate')}
""")
----
from tensorflow.keras import regularizers
tf.random.set_seed(1442)
initializer = tf.keras.initializers.HeNormal()
model = Sequential()
model.add(Dense(best_hps.get('units'),
activation='relu', kernel_initializer=initializer,
kernel_regularizer=regularizers.l2(0.01), # L2 norm regularization
activity_regularizer=regularizers.l1(0.01))) # L1 norm regularization))
model.add(Dense(best_hps.get('units'),
activation='relu', kernel_initializer=initializer,
kernel_regularizer=regularizers.l2(0.01), # L2 norm regularization
activity_regularizer=regularizers.l1(0.01)))
model.add(Dense(1, activation='sigmoid')) # 이진분류니까 노드수 1, 활성함수로는 시그모이드
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate = best_hps.get('learning_rate')),
loss='binary_crossentropy',
metrics=['accuracy'])
results = model.fit(X_train_scaled, y_train, epochs=2, batch_size=50, validation_data=(X_test_scaled,y_test))
# 테스트셋 사용해서 결과 보기
model.evaluate(X_test_scaled, y_test, verbose=2) #기본모델보다 오히려 떨어졌네?!
best_hps
를 통해서는 최적의 하이퍼 파라미터만 확인할 수 있는거다.model = tuner.hypermodel.build(best_hps)
#학습 진행
model.fit(X_train, y_train, epochs = 10, validation_data = (img_test, label_test))
MLP
= Multilay perceptron의 약자였음!