※ 원래 velog에 있던 내 글을 티스토리에 옮김
optuna를 이용하면 파라미터 튜닝을 알아서 해준다. 정확히 말하자면 각 파라미터마다 튜닝할 숫자의 범위를 정해주면, 각 trial마다 random하게 숫자를 골라준다. 단순히 random selection이 아니라 grid search도 적용할 수 있어서 효율적으로 파라미터를 튜닝한다.
Optuna references
나는 아래 링크에 있는 글에서 도움을 많이 받았다. (특히 첫번째 글)
좀 더 명확한 feature 분석을 위해서는 추가 작업도 해야하지만 이 글에서는 그냥 파라미터튜닝 자체에만 설명한다.
https://www.kaggle.com/code/corochann/optuna-tutorial-for-hyperparameter-optimization/notebook
https://www.kaggle.com/code/bjoernholzhauer/lightgbm-tuning-with-optuna/notebook
예제 코드
optuna에는 Trial이라는 객체가 있는데, 이 Trial마다 파라미터를 조절하면 된다.
나는 필수적으로 고정시킬 파라미터는 main()에서 세팅하고 objective()에서 optuna가 파라미터 튜닝할 수 있게 하였다. 그리고 objective()는 반드시 score를 리턴해야하는데, study에서 이 score가 작아야/커야 좋은 것인지 판단할 수 있기 때문이다.
def objective(trial, ...):
# calculate score...
return score
이제 파라미터 튜닝을 해보자.
study = optuna.create_study()
study.optimize(objective, n_trials=10)
만약 objective에 Trial 말고 여러 argument가 필요하면 다음과 같이 작성할 수 있다.
study.optimize(lambda trial: objective(trial, arg0=1, arg1=2), n_trials=100)
실제 코드
아래는 실제 인턴 생활을 하면서 내가 직접 작성한 코드이다.
참고로, 함수에 한번에 lightgbm.Dataset객체를 넘기려 했으나, 어떤 이유에선지 max_bin값을 바꿀수 없다고 warning이 떠서(실제 값을 출력하면 max_bin의 값이 바뀌긴 했지만) 불안한 마음에 그냥 rough하게 dataframe, feature, target, dataset 을 넘겨서 objective()안에서 데이터셋을 세팅했다.
그리고 서버가 불안해서 중간에 멈출까봐 매 trial마다 params, model, loss를 저장하기 위해서 trial.set_user_attr()를 이용했다. 그리고 best_model, best_loss도 call_back함수에서 정의하여 study가 항상 best_model, best_loss를 저장할 수 있도록 했다.
아직 인턴이기도 했고, 마감에 쫓겨 코드를 작성하다보니 코드가 간결하지 못하고 지저분한건 아쉬웠다. (거의 매일 야근, 주말까지 갈아넣은 내 인턴생활 ㅎ)
Objective 정의하기
import lightgbm as lgb
import optuna
from optuna import Trial
def objective(trial: Trial, params, evals_result, path, **dataset):
'''
optuna objective function
Args
trial: trial object
params (dict): base parameters of lightGBM model
evals_result (dict): contains loss dictionary while training
path (str): path or directory to save model, parameters, loss
dataset (dict): kwargs related datasets
X_train (pandas.DataFrame): train set
y_train (array-like): labels of train set
X_valid (pandas.DataFrame): valid set
y_valid (array-like): labels of valid set
cat_features (arrray-like): categorical columns of df_train
Returns
score (float): metric score of current model
'''
# randomly select the range of hyperparameters to tune
params['lambda_l1'] = trial.suggest_loguniform('lambda_l1', 1e-8, 1e-1)
params['lambda_l2'] = trial.suggest_loguniform('lambda_l2', 1e-8, 1e-1)
params['path_smooth'] = trial.suggest_loguniform('path_smooth', 1e-8, 1e-3)
params['num_leaves'] = trial.suggest_int('num_leaves', 30, 200)
params['min_data_in_leaf'] = trial.suggest_int('min_data_in_leaf', 10, 100)
params['max_bin'] = trial.suggest_int('max_bin', 100, 255)
params['feature_fraction'] = trial.suggest_uniform('feature_fraction', 0.5, 0.9)
params['bagging_fraction'] = trial.suggest_uniform('bagging_fraction', 0.5, 0.9)
# set train, valid set
train_data = lgb.Dataset(
dataset['X_train'],
label=dataset['y_train'],
categorical_feature=dataset['CAT_FEATURES'],
free_raw_data=False
)
valid_data = lgb.Dataset(
dataset['X_valid'],
dataset['y_valid'],
categorical_feature=dataset['CAT_FEATURES'],
free_raw_data=False
)
# train the model
model = lgb.train(params,
train_set=train_data,
valid_sets = [train_data, valid_data],
valid_names = ['train', 'valid'],
categorical_feature=dataset['CAT_FEATURES'],
evals_result=evals_result,
verbose_eval=100,
)
# ----- save current trial model, parameters, loss
make_single_directory(f'{path}trials')
save_model(model, f'{path}trials/', f'model_{trial.number}')
save_object(params, f'{path}trials/', f'params_{trial.number}')
# make a train-valid loss as a dataframe
df_loss = pd.DataFrame({
key: evals_result[key][params['metric']]
for key in evals_result.keys()
})
df_loss.to_csv(f'{path}trials/loss_{trial.number}.csv', index=False)
# ----- set user attributes: model, loss
trial.set_user_attr(key='model', value=model)
trial.set_user_attr(key='loss', value=df_loss)
# p_valid: predicted value of valid set
# y_valid: true value of valid set
p_valid = model.predict(valid_data.get_data(),
num_iteration=model.best_iteration)
y_valid = valid_data.get_label()
# ----- get score
score = MAE(y_valid, p_valid)
print(f'bset score: {model.best_score}')
return score
callback 도 구현하였다
def callback_study(study, trial) -> None:
'''
save best trial's model, loss if current trial is a best trial
Args:
study (optuna.study)
trial (optuna.trial): a certain trial of STUDY
Returns: None
'''
if study.best_trial.number == trial.number:
study.set_user_attr(key='best_model', value=trial.user_attrs['model'])
study.set_user_attr(key='best_loss', value=trial.user_attrs['loss'])
main()
#-- set lightgbm parameters
params = get_parameters(config)
#-- train the model
evals_result = {}
... (중략)...
dataset = {
'X_train': X_train,
'y_train': y_train,
'X_valid': X_valid,
'y_valid': y_valid,
'CAT_FEATURES': CAT_FEATURES
}
# metric이 accuracy라면 direction='maximize'로 한다.
study = optuna.create_study(direction='minimize')
study.optimize(lambda trial: objective(trial, params, evals_result=evals_result, path=RESULT_PATH, **dataset),
n_trials=N_TRIALS,
callbacks=[callback_study])
#-- get best parameters, model and loss
params.update(study.best_trial.params)
model = study.user_attrs['best_model']
df_loss = study.user_attrs['best_loss']
...(후략)...
'스터디 > 데이터사이언스' 카테고리의 다른 글
[CS246] Finding Similar Items (4) - Locality Sensitive Hashing (LSH) (0) | 2023.09.21 |
---|---|
[CS246] Finding Similar Items (3) - Minhashing (0) | 2023.09.20 |
[CS246] Finding Similar Items (2) - Shingling (0) | 2023.09.19 |
[CS246] Finding Similar Items (1) - Introduction (0) | 2023.09.18 |
[CS246] Frequent Itemsets: SON, Toivonen Algorithm (0) | 2023.09.15 |