Date:     Updated:

카테고리:

태그: , ,


[KOGAS 빅스타 경진대회] LNG탱크 압력 예측 모델(우수상) 1편 링크

[KOGAS 빅스타 경진대회] LNG탱크 압력 예측 모델(우수상) 2편 링크


상관관계 확인, 변수 최종 선택

모델링을 하기에 앞서 상관관계를 분석했다.

def draw_corr_heatmap(data):
    colormap = plt.cm.PuBu
    plt.figure(figsize=(11,14))
    plt.title("Correlation of Features", y=1.05, size=15)
    sns.heatmap(data.corr().loc[:,['LAG1_PIA205B-02A_MIN','LAG1_PIA205B-02A_MAX']], cmap=colormap, linewidths=0.03, linecolor="white", annot=True)

draw_corr_heatmap(x)


또한 최종적으로 사용할 변수 25개를 채택했다. 이유는 아래 사진과 같다.

x = x.drop(["LAG1_ZIH120-02", "LAG1_HOUR", "LAG1_PRESSURE-S", "LAG1_MONTH", "LAG1_WEEK", "LAG1_DAY", 
            'LAG1_PRESS', 'LAG1_VAPOR', 'LAG1_HUMID', 'LAG1_FY_SUM', 'LAG1_FIA_SUM', 'LAG1_TI_MEAN', 'LAG1_LP_TOTAL'], axis=1)
test_data = test_data.drop(["LAG1_ZIH120-02", "LAG1_HOUR", "LAG1_PRESSURE-S", "LAG1_MONTH", "LAG1_WEEK", 
                            "LAG1_DAY", 'LAG1_PRESS', 'LAG1_VAPOR', 'LAG1_HUMID', 'LAG1_FY_SUM', 'LAG1_FIA_SUM', 'LAG1_TI_MEAN', 'LAG1_LP_TOTAL'], axis=1)


하이퍼파라미터 튜닝 -그리드서치-

이제 본격적으로 모델링을 해볼 것이다. 사용한 알고리즘은 xgboost, 랜덤포레스트, lgbm, extratree 총 네가지였다. 이 네 개의 모델들을 스태킹해보기도 하였으나 최종 성적은 xgboost 단일 모델이 가장 우수했다.

우선 하이퍼파라미터를 튜닝하기 위해 gridsearch를 진행했다. 예시로 랜덤포레스트 그리드서치만 코드를 첨부한다.

#랜덤포레스트 그리드서치
start = time.time()

kfold = KFold(n_splits=2, shuffle=True)
rf = RandomForestRegressor(n_jobs=-1)
params = {"n_estimators":[1000],
    "max_depth":(24,28,32),
         "min_samples_split":(3,5)}

grid_cv = GridSearchCV(rf,
                      param_grid=params,
                      cv=kfold,
                      scoring = 'neg_mean_absolute_error')

grid_cv.fit(x_train, y_train)
grid_cv.score(x_train, y_train)

print(grid_cv.best_estimator_)
print(grid_cv.best_params_)


print("runtime:", time.time()-start)
>>> RandomForestRegressor(max_depth=32, min_samples_split=3, n_estimators=1000,
                      n_jobs=-1)
{'max_depth': 32, 'min_samples_split': 3, 'n_estimators': 1000}


교차검증

모델링을 하는 방법은 교차검증을 n회 수행하여 이 n번의 평균값을 제출값으로 만드는 방식을 택했다. 아래 발표자료를 보면 이해를 도울 수 있을 것이다.

start = time.time()

def kfold_RF(n):

    kfold = KFold(n_splits=n, shuffle=True, random_state=42)
    folds = []
    for train_idx, val_idx in kfold.split(x,y):
        folds.append((train_idx, val_idx))

    RF_model_dict = {}
    MAE = []

    for f in range(n):
        print("----------{}회째 folds-----------".format(f+1))
        train_idx, val_idx = folds[f]

        x_train, x_val, y_train, y_val = x.iloc[train_idx], x.iloc[val_idx], y.iloc[train_idx], y.iloc[val_idx]

        RF_model = RandomForestRegressor(max_depth=32,
                                      n_estimators=2000,
                                  min_samples_leaf=3,
                                      min_samples_split=3,
                                 n_jobs=-1)

        RF_model.fit(x_train, y_train)
        y_pred = RF_model.predict(x_val)
        mae = mean_absolute_error(y_val, y_pred)
        MAE.append(mae)
        print("{} fold MAE score: {:.8f}".format(f+1,mae))
        RF_model_dict[f] = RF_model

    MAE_mean = np.array(MAE)
    print("---------------------------")
    print("k-fold MAE mean: {:.8f}".format(np.mean(MAE_mean)))
    
    RF_y_train = pd.DataFrame(np.zeros((x_train.shape[0],2)),columns=['min', 'max'])
    RF_y_test = pd.DataFrame(np.zeros((x_val.shape[0],2)),columns=['min', 'max'])
    RF_y_sub = pd.DataFrame(np.zeros((test_data.shape[0],2)),columns=['min', 'max'])

    for fold in tqdm(range(n)):
        RF_y_train += RF_model_dict[fold].predict(x_train)/n
        RF_y_test += RF_model_dict[fold].predict(x_val)/n
        RF_y_sub += RF_model_dict[fold].predict(test_data)/n
    print("runtime:", time.time()-start)
        
    return RF_y_train, RF_y_test, RF_y_sub

위 함수에서 생성된 세 개의 변수는 스태킹에 사용될 데이터들이다.

RF_y_train, RF_y_test, RF_y_sub = kfold_RF(10)
----------1회째 folds-----------
1 fold MAE score: 0.01033327
----------2회째 folds-----------
2 fold MAE score: 0.01058179
----------3회째 folds-----------
3 fold MAE score: 0.01017598
----------4회째 folds-----------
4 fold MAE score: 0.01030806
----------5회째 folds-----------
5 fold MAE score: 0.01051287
----------6회째 folds-----------
6 fold MAE score: 0.01028156
----------7회째 folds-----------
7 fold MAE score: 0.01041308
----------8회째 folds-----------
8 fold MAE score: 0.01044887
----------9회째 folds-----------
9 fold MAE score: 0.01040414
----------10회째 folds-----------
10 fold MAE score: 0.01038963
---------------------------
k-fold MAE mean: 0.01038493
100%|██████████████████████████████████████████████████████████████████████████████████| 10/10 [01:40<00:00, 10.05s/it]


스태킹

위에선 랜덤포레스트만 코드를 작성했지만, 실제로는 xgboost, lgbm, extretree 모두 동일한 방식으로 모델링을 하여 예측값을 만들었다. 이제 네 개의 알고리즘을 통해 만든 예측값을 토대로 스태킹을 시도할 것이다. 스태킹에 쓰인 메타모델은 랜덤포레스트이다.

#stacking 전에 각 모델들의 예측값을 훈련데이터로 만들어주는 작업
dataframe = [RF_y_train, EXTRA_y_train, XGB_y_train, RF_y_test, EXTRA_y_test, XGB_y_test, RF_y_sub, EXTRA_y_sub, XGB_y_sub]
dfs = []
for i in tqdm(dataframe):
    df = pd.DataFrame(i)
    dfs.append(df)
    
y_train_concat = pd.concat((dfs[0], dfs[1], dfs[2]), axis=1)
y_test_concat = pd.concat((dfs[3], dfs[4], dfs[5]), axis=1)
y_sub_concat = pd.concat((dfs[6], dfs[7], dfs[8]), axis=1)

y_train_concat.columns = ['min_RF', 'max_RF', 'min_EXTRA', 'max_EXTRA', 'min_XGB', 'max_XGB']
y_test_concat.columns = ['min_RF', 'max_RF', 'min_EXTRA', 'max_EXTRA', 'min_XGB', 'max_XGB']
y_sub_concat.columns = ['min_RF', 'max_RF', 'min_EXTRA', 'max_EXTRA', 'min_XGB', 'max_XGB']

#x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=42)

#앞서 세 가지 level1 model에서 train과 test로 나눴던 것을 재병합 시키고, 이를 kfold로 다시 무작위로 섞어서 새로운 train test를 만들 것임
x_ens = pd.concat((y_train_concat,y_test_concat),axis=0)
y_ens = pd.concat((y_train,y_test),axis=0)
kfold = KFold(n_splits=10, shuffle=True, random_state=42)
folds = []
for train_idx, val_idx in kfold.split(x,y):
    folds.append((train_idx, val_idx))

STACK_RF_model_dict = {}
MAE = []

for f in range(10):
    print("----------{}회째 folds-----------".format(f+1))
    train_idx, val_idx = folds[f]

    x_ens_train, x_ens_val, y_ens_train, y_ens_val = x_ens.iloc[train_idx], x_ens.iloc[val_idx], y_ens.iloc[train_idx], y_ens.iloc[val_idx]

    STACK_RF_model = RandomForestRegressor(max_depth=30,
                                  n_estimators=2000,
                              min_samples_leaf=3,
                                  min_samples_split=3,
                             n_jobs=-1)

    STACK_RF_model.fit(x_ens_train, y_ens_train)
    y_ens_pred = STACK_RF_model.predict(x_ens_val)
    mae = mean_absolute_error(y_ens_val, y_ens_pred)
    MAE.append(mae)
    print("{} fold MAE score: {:.8f}".format(f+1,mae))
    STACK_RF_model_dict[f] = STACK_RF_model

MAE_mean = np.array(MAE)
print("---------------------------")
print("k-fold MAE mean: {:.8f}".format(np.mean(MAE_mean)))


submission.iloc[:,1:]=0
for fold in range(10):
    submission.iloc[:,1:] += STACK_RF_model_dict[fold].predict(y_sub_concat)/10
print("runtime:", time.time()-start)

submission.to_csv('STACK_RF_answer_11232117.csv', index=False)

하지만 위처럼 스태킹을 통해 만든 결과물은 overfitting이 되어서인지 리더보드 상 점수는 더 안좋아졌다. 때문에 단일 모델 중 가장 점수가 좋았던 xgboost 단일 모델로 최종 제출을 하였다.


xgbosst 최종 제출

# 10 fold로 교차검증을 수행
kfold = KFold(n_splits=10, shuffle=True, random_state=42)
folds = []

# train과 validation의 index를 나누기
for train_idx, val_idx in kfold.split(x,y):
    folds.append((train_idx, val_idx))

# 10번의 교차검증을 통해 만들 10개의 모델을 딕셔너리에 저장해놓을 것.
XGB_model_dict = {}

#10번에 걸친 MAE 점수 역시도 리스트에 저장
MAE = []

for f in range(10):
    print("----------{}회째 folds-----------".format(f+1))
    train_idx, val_idx = folds[f]

    x_train, x_val, y_train, y_val = x.iloc[train_idx], x.iloc[val_idx], y.iloc[train_idx], y.iloc[val_idx]

    XGB_model = XGBRegressor(n_estimators=2000,
                 min_child_weight=4,
                 learning_rate=0.02,
                 max_depth=20,
                 n_jobs=-1,
                     alpha=0.1,
                     colsample_bytree=0.9,
                     subsample=1.0,
                eval_metric='mae')

    # 모델 훈련
    XGB_model.fit(x_train, y_train)
    
    y_pred = XGB_model.predict(x_val)
    mae = mean_absolute_error(y_val, y_pred)
    MAE.append(mae)
    
    # K번째 교차검증의 MAE 스코어 출력
    print("{} fold MAE score: {:.8f}".format(f+1,mae))
    
    # K번째 모델을 딕셔너리에 저장
    XGB_model_dict[f] = XGB_model

MAE_mean = np.array(MAE)
print("---------------------------")
print("k-fold MAE mean: {:.8f}".format(np.mean(MAE_mean)))


submission.iloc[:,1:]=0

#총 10개의 모델을 호출해가며 그 평균값을 제출파일에 저장하는 과정
for fold in range(10):
    submission.iloc[:,1:] += XGB_model_dict[fold].predict(test_data)/10
    
print("runtime:", time.time()-start)
# 제출 파일 csv로 저장
submission.to_csv('XGB_answer_11231700.csv', index=False)

마지막으로, 모델을 dump하여 언제든 동일한 값을 불러올 수 있도록 한다.

joblib.dump(XGB_model_dict, './XGB_1123_0263_best.pkl')
final_model_dict = joblib.load('./XGB_1123_02597_best.pkl')

submission.iloc[:,1:] = 0
for fold in range(10):
    submission.iloc[:,1:] += final_model_dict[fold].predict(test_data)/10


변수 중요도 확인

xgboost에는 plot_importance라는 중요도 함수가 내장되어 있다. importance_type을 뭐로 하냐에 따라 중요도의 기준이 달라지는데, 아래 블로그에 정리가 잘 되어있다.

plot_importance 관련 블로그 링크

#변수 중요도
XGB_model = final_model_dict[0]
from xgboost import plot_importance

fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(25,20))
axes = [ax for row_axes in axes for ax in row_axes]

plot_importance(XGB_model, importance_type='gain', title='gain', ax=axes[0])
plot_importance(XGB_model, importance_type='weight', title='weight', ax=axes[1])
plot_importance(XGB_model, importance_type='cover', title='cover', ax=axes[2])


발표자료

이로써 대회에 사용한 모든 코드를 소개해보았다. 이후에 진행된 발표평가에서는 기술적인 부분보다도 분석이 얼만큼 논리적으로 기획되었는지와 그 이후의 사업화까지도 고려해야 했다. 발표에 쓰인 ppt 자료를 첨부함으로써 글을 마치려 한다.

발표자료 ppt 링크