최근 프로젝트가 하나 생겨서 다음 포스트 진행이 오래걸렸다...
이번에는 titanic data를 가지고 모델을 학습해 보고 결론을 통해 마무리하고자 한다. 이전에 Randomforest를 이용해 간단하게 모델을 학습했었다. 결과는 0.77751로 개인적으로 나쁘지 않은 점수라고 판단하고 있다.
오늘은 여러 모델들을 활용해 보고 GridSearchCV, hyperopt를 활용해 모려고 한다.
- titanic에 대한 정보 수집
- 문제 정의
- 분석 대상에 대한 이해
- titanic data set을 이용한 EDA
- 공통 코드
- titanic data에 대한 기본적인 정보
- 통계 및 시각화
- 여성과 아이들
- 나이
- 사회적 지위
- Embarked(중간 정착 항구)
- Cabin(선실 번호)
- SibSp, Parch(같이 탑승한 형제자매 또는 배우자 인원수, 같이 탑승한 부모님 또는 어린이 인원수)
- 모델 학습
- RandomForest
- XGBoost
- LightGBM
- CatBoost
- 결론
3. 모델 학습부터 이어가도록 하겠다.
3. 모델 학습
1. Randomforest
하이퍼파라미터 튜닝을 하지 않고 기본적으로 사용했을 때 kaggle에서 점수는 0.77751이 나왔다. 이번에는 그리드서치를 이용해 하이퍼파라미터를 튜닝해보려고 한다.
from sklearn.model_selection import GridSearchCV
params = {
'max_depth': np.arange(1, 10, 1),
'min_samples_leaf' : np.arange(1, 40, 1),
'min_samples_split' : np.arange(2, 40, 1)
}
rf_clf = RandomForestClassifier(n_estimators=100, n_jobs=-1, oob_score=True)
grid_cv = GridSearchCV(rf_clf , param_grid=params , cv=2, n_jobs=-1 )
grid_cv.fit(X_train , y_train)
print('최적 하이퍼 파라미터:\n', grid_cv.best_params_)
print('최고 예측 정확도: {0:.4f}'.format(grid_cv.best_score_))
best_rf = grid_cv.best_estimator_
# feature importance 추출
feature_names = train_df.columns.drop('Survived')
# feature importance를 column 별로 시각화 하기
sns.barplot(x=best_rf.feature_importances_ , y=feature_names)
pred = best_rf.predict(X_train)
proba = best_rf.predict_proba(X_train)[:, 1]
best_rf_pred = best_rf.predict(X_test)
best_rf_proba = best_rf.predict_proba(X_test)[:, 1]
get_clf_eval(y_train, pred, proba)
get_clf_eval(y_test , best_rf_pred, best_rf_proba)
최적 하이퍼 파라미터:{'max_depth': 8, 'min_samples_leaf': 6, 'min_samples_split': 29}
최고 예측 정확도: 0.8026
오차 행렬
[[355 29]
[ 87 152]]
정확도: 0.8138, 정밀도: 0.8398, 재현율: 0.6360, F1: 0.7238, AUC:0.8742
오차 행렬
[[151 14]
[ 35 68]]
정확도: 0.8172, 정밀도: 0.8293, 재현율: 0.6602, F1: 0.7351, AUC:0.8850
다음과 같은 결과가 나온다. 하이퍼파라미터는 모델 학습 과정에서 사용자가 직접 설정해야 하는 파라미터로, 모델의 성능에 큰 영향을 미친다. 이 때 그리드서치를 사용하면 사용자가 설정한 하이퍼파라미터 값들의 가능한 모든 조합을 탐색하여 가장 좋은 성능을 내는 조합을 찾을 수 있도록 도와준다. 따라서 결과로 최적의 하이퍼파라미터 값이 나오고 그 값을 토대로 훈련을 했을 때 feature importance를 확인해 볼 수 있다. 그리드서치를 이용한 모델은 Sex, Pclass를 중요한 특성으로 사용했다.
그리드서치를 사용해 최적의 하이퍼파라미터를 튜닝한 후 kaggle에 제출했을 때 다음과 같은 점수를 얻었다. 기존 0.77751 보다 살짝 높은 점수를 보여준다.
2. XGBoost
XGBoost 또한 Randomforest와 같게 처음에는 하이퍼파라미터 튜닝을 하지 않고 기본적으로 진행해 보고 XGBoost부터는 그리드서치가 아닌 Hyperopt를 이용해 하이퍼파라미터를 튜닝해보고자 한다.
from xgboost import XGBClassifier, plot_importance
xgb = XGBClassifier(random_state=RANDOM_STATE)
xgb.fit(X_train, y_train, verbose=True)
xgb_preds = xgb.predict(X_test)
xgb_pred_proba = xgb.predict_proba(X_test)[:, 1]
get_clf_eval(y_test , xgb_preds, xgb_pred_proba)
fig, ax = plt.subplots(figsize=(10, 5))
plot_importance(xgb, ax=ax)
randomforest와 다르게 Sex와 Pclass 보다 Age_range가 중요하게 작용한 것을 확인할 수 있다. F1 score가 조금 더 높게 나왔지만kaggle에 제출했을 때 다음과 같은 점수를 얻을 수 있다. 오히려 randomforest로 학습했을 때 보다 더 낮게 나왔다.
결과를 좀더 자세하게 보면 다음과 같다.
위에가 train data, 아래가 test_data로 예측했을 때의 결과이다. 과적합이 된 것을 확인할 수 있다. 뿐만 아니라 오차행렬을 통해 False Positive, False Negative 역시 높게 나온 것을 확인할 수 있다. 즉, 점수는 높지만 예측을 제대로 하지 못 하고 있다는 것이다. 이제 하이퍼파라미터를 튜닝해 보겠다.
from hyperopt import hp, fmin, tpe, Trials
from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score
xgb_search_space = {'max_depth': hp.quniform('max_depth', 2, 15, 1),
'min_child_weight': hp.quniform('min_child_weight', 1, 6, 1),
'colsample_bytree': hp.uniform('colsample_bytree', 0.5, 0.95),
'learning_rate': hp.uniform('learning_rate', 0.01, 0.2)}
def objective_func(search_space):
xgb_clf = XGBClassifier(n_estimators=100,
max_depth=int(search_space['max_depth']),
min_child_weight=int(search_space['min_child_weight']),
colsample_bytree=search_space['colsample_bytree'],
learning_rate=search_space['learning_rate'],
early_stopping_rounds=30,
eval_metric='logloss',
random_state=RANDOM_STATE)
roc_auc_list= []
kf = KFold(n_splits=5)
for tr_index, val_index in kf.split(X_train):
X_tr, y_tr = X_train.iloc[tr_index], y_train.iloc[tr_index]
X_val, y_val = X_train.iloc[val_index], y_train.iloc[val_index]
xgb_clf.fit(X_tr, y_tr, eval_set=[(X_tr, y_tr), (X_val, y_val)], verbose=False)
score = roc_auc_score(y_val, xgb_clf.predict_proba(X_val)[:, 1])
roc_auc_list.append(score)
return -1 * np.mean(roc_auc_list)
trials = Trials()
best = fmin(fn=objective_func,
space=xgb_search_space,
algo=tpe.suggest,
max_evals=50, # 최대 반복 횟수를 지정합니다.
trials=trials,
rstate=np.random.default_rng()
)
print('best:', best)
xgb_clf = XGBClassifier(n_estimators=500, learning_rate=round(best['learning_rate'], 5),
max_depth=int(best['max_depth']), min_child_weight=int(best['min_child_weight']),
colsample_bytree=round(best['colsample_bytree'], 5), random_state=RANDOM_STATE)
xgb_clf.fit(X_tr, y_tr, early_stopping_rounds=100, eval_metric="auc",eval_set=[(X_tr, y_tr), (X_val, y_val)])
xgb_roc_score = roc_auc_score(y_test, xgb_clf.predict_proba(X_test)[:,1])
print('ROC AUC: {0:.4f}'.format(xgb_roc_score))
hyperopt로 하이퍼파라미터를 튜닝하기 위해 위와 같이 코드를 작성하고 모델을 학습하면 다음과 같이 결과를 확인할 수 있다.
best: {'colsample_bytree': 0.613363205874114, 'learning_rate': 0.12571035945607772, 'max_depth': 11.0, 'min_child_weight': 2.0}
하이퍼파라미터로 튜닝하기 전의 XGBoost보다 과적합이 많이 해소된 것을 확인할 수 있다. kaggle에 제출하면 다음과 같은 결과를 얻을 수 있다.
점수가 더 높게 나오긴 했지만 randomforest보다 낮은 점수를 보여준다. 그 이유는 다음과 같다. 정밀도와 AUC가 높은 반면, 재현율이 상대적으로 낮다는 점에서, 모델이 양성을 잡아내는 능력이 부족해 점수가 낮은 것이다.
3. LightGBM
LightGBM 역시 XGBoost와 같이 하이퍼파라미터를 튜닝하지 않고 모델을 학습해보고 hyperopt로 학습을 한 후 최적의 하이퍼파라미터를 찾은 후 모델을 학습해 보겠다.
from lightgbm import early_stopping
from lightgbm import LGBMClassifier
lgbm = LGBMClassifier(random_state=RANDOM_STATE)
evals = [(X_tr, y_tr), (X_val, y_val)]
lgbm.fit(X_tr, y_tr, callbacks=[early_stopping(stopping_rounds=50)], eval_metric="logloss", eval_set=evals)
preds = lgbm.predict(X_test)
pred_proba = lgbm.predict_proba(X_test)[:, 1]
get_clf_eval(y_test, preds, pred_proba)
기본적으로 했을 때도 XGBoost와 비교했을 때 괜찮은 성능을 보여준다. 하이퍼파라미터 튜닝을 한 XGBoost와 비교했을 때 재현율이 약간 낮아져 모델이 양성 데이터를 적게 탐지하지만, 하이퍼파라미터 튜닝을 하지 않았다는 점에서 괜찮은 성능을 보여주고 있다. kaggle에 제출하면 다음과 같다.
기본 XGBoost를 사용했을 때보다 확실 더 높은 점수를 얻을 수 있다. 이제 하이퍼파라미터를 튜닝해보겠다.
from hyperopt import hp, fmin, tpe, Trials
from sklearn.model_selection import KFold
from sklearn.metrics import f1_score, roc_auc_score
from lightgbm import early_stopping, LGBMClassifier
import numpy as np
lgbm_search_space = {'num_leaves': hp.quniform('num_leaves', 10, 40, 1),
'max_depth': hp.quniform('max_depth', 5, 20, 1),
'min_child_samples': hp.quniform('min_child_samples', 30, 100, 1),
'subsample': hp.uniform('subsample', 0.7, 1),
'learning_rate': hp.uniform('learning_rate', 0.01, 0.1)}
def objective_func(search_space):
lgbm_clf = LGBMClassifier(n_estimators=100, num_leaves=int(search_space['num_leaves']),
max_depth=int(search_space['max_depth']),
min_child_samples=int(search_space['min_child_samples']),
subsample=search_space['subsample'],
verbose=-1,
learning_rate=search_space['learning_rate'])
f1_list = []
kf = KFold(n_splits=5)
for tr_index, val_index in kf.split(X_train):
X_tr, X_val = X_train.iloc[tr_index], X_train.iloc[val_index]
y_tr, y_val = y_train.iloc[tr_index], y_train.iloc[val_index]
lgbm_clf.fit(X_tr, y_tr, callbacks=[early_stopping(stopping_rounds=50)], eval_set=[(X_tr, y_tr), (X_val, y_val)], eval_metric='logloss')
score = f1_score(y_val, lgbm_clf.predict(X_val))
f1_list.append(score)
return -1 * np.mean(f1_list)
trials = Trials()
best = fmin(fn=objective_func, space=lgbm_search_space, algo=tpe.suggest,
max_evals=50, trials=trials, rstate=np.random.default_rng(seed=30))
lgbm_clf = LGBMClassifier(n_estimators=500, num_leaves=int(best['num_leaves']),
max_depth=int(best['max_depth']),
min_child_samples=int(best['min_child_samples']),
subsample=round(best['subsample'], 5),
learning_rate=round(best['learning_rate'], 5))
lgbm_clf.fit(X_train, y_train, callbacks=[early_stopping(stopping_rounds=100)], eval_metric="logloss", eval_set=[(X_train, y_train), (X_test, y_test)])
lgbm_pred = lgbm_clf.predict(X_test)
lgbm_proba = lgbm_clf.predict_proba(X_test)[:, 1]
lgbm_f1_score = f1_score(y_test, lgbm_pred)
lgbm_roc_score = roc_auc_score(y_test, lgbm_proba)
f1 score의 경우 가장 높게 나왔다. 뿐만 아니라 재현율이 0.7379로 가장 높은 점수를 보여줬던 하이퍼파라미터 튜닝을 한 randomforest보다 높게 나왔다. kaggle에 제출하면 다음과 같은 점수를 확인할 수 있다.
0.78229로 가장 높은 점수가 나왔다.
4. CatBoost
CatBoost 또한 위에서 했던 방법대로 진행해보겠다. 단, 이번에는 hyperopt가 아닌 optuna를 사용해 하이퍼파라미터를 튜닝해보려고 한다.
from catboost import CatBoostClassifier
cat = CatBoostClassifier(random_state=RANDOM_STATE)
cat.fit(X_train, y_train)
LiightGBM보다 더 좋은 성능을 보여주고 있다. kaggle에 제출하면 다음과 같은 점수를 받을 수 있다.
randomforest로 학습했을 때와 같은 점수를 보여주고 있다. 이제 optuna를 통해 하이퍼파라미터를 튜닝해보겠다.
from sklearn.model_selection import cross_val_score
import optuna
def objective(trial):
iterations = trial.suggest_int('iterations', 100, 1000, step=10)
learning_rate = trial.suggest_float('learning_rate', 0.1, 1.0, step=0.1)
depth = trial.suggest_int('depth', 3, 15)
l2_leaf_reg = trial.suggest_float('l2_leaf_reg', 0.1, 10.0)
bagging_temperature = trial.suggest_float('bagging_temperature', 0.0, 1.0)
class_weight = trial.suggest_float('class_weight', 1.0, 50.0)
random_strength = trial.suggest_float('random_strength', 0.0, 10.0)
od_wait = trial.suggest_int('od_wait', 10, 50)
model = CatBoostClassifier(
iterations=iterations,
learning_rate=learning_rate,
depth=depth,
l2_leaf_reg=l2_leaf_reg,
bagging_temperature=bagging_temperature,
class_weights=[1, class_weight],
random_strength=random_strength,
od_wait=od_wait,
random_state=RANDOM_STATE,
verbose=0
)
model.fit(X_train, y_train)
y_val_pred = model.predict(X_test)
f1 = f1_score(y_test, y_val_pred, pos_label=0)
print(f"Trial {trial.number} finished with F1 score: {f1}")
return f1
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100, n_jobs=-1)
best_params = study.best_params
print("Best params: ", best_params)
if 'class_weight' in best_params:
best_params['class_weights'] = [1, best_params.pop('class_weight')]
best_cat_model = CatBoostClassifier(**best_params, random_state=RANDOM_STATE)
best_cat_model.fit(X_train, y_train)
다음과 같이 결과를 확인할 수 있으며 kaggle에 제출하면 0.77990으로 randomforest와 같은 점수를 받을 수 있다.
최종적으로 가장 좋은 점수를 받은 것은 LightGBM으로 0.78229이다.
4. 결론
kaggle에서 제공하는 titanic data를 이용해 분석을 진행했으며, 모델을 학습시켜 점수를 얻는 과정을 진행했다. 이 부분에서 느낀 점은 다음과 같다. 891개의 데이터는 머신러닝을 이용하는 데에는 부족한 데이터이며 feature 역시 많이 부족하다는 것을 느꼈다. 또한, feature 중에서 생존 가능성이 가장 높을 것으로 예측 가능한 부분이 Sex, Pclass, Age로 생각하고 있는데 이 것으로는 생존 유무를 예측하는 데에는 한계가 있다고 느꼈다. 예를 들어 위에서 언급했듯 부부가 함께 타이타닉호에 탑승해 있다가 부인은 구명보트에 탑승했지만 남편은 남성이라는 이유로 탑승하지 못 했을 때, 부인이 남편과 같이 하겠다고 탑승하지 않을 때, 어린 아이지만 부모를 찾지 못 해서 객실 안에 계속 머물다가 구조되지 못한 사례 등등 예상하지 못하는 부분이 많이 있다. 뿐만 아니라 Cabin의 경우 NaN 값이 687개로 사용할 수 없는 feature였다. 중요한 feature 중 하나인 Age의 경우에도 NaN 값이 177개로 많았다.
이처럼 타이타닉 생존자 예측 머신러닝에서 아쉬운 부분은 역시 데이터의 부족이었다. 따라서 특정 점수 이상의 예측 결과를 얻지 못 했다는 데에 한계가 있다.
추가적으로 유튜브, 구글링 등 여러 가지 방법을 추가해 모델을 학습시켜 보았지만 개인적으로 분석하며 얻은 점수가 가장 높게 나왔다. titanic data를 토대로 분석하는 것은 여기까지 하고 kaggle에서 다음 데이터를 추가적으로 가져와서 분석을 해볼 예정이다.
'kaggle' 카테고리의 다른 글
kaggle Bike Sharing Demand(1) - 자전거 수요 예측(상위 약 5%) (9) | 2024.09.17 |
---|---|
kaggle Bank Churn Dataset (4) | 2024.09.12 |
Santander Customer Satisfaction EDA (2) (1) | 2024.09.05 |
Santander Customer Satisfaction EDA (1) (1) | 2024.09.01 |
titanic 데이터 - EDA(1) (0) | 2024.08.20 |