🛳️鐵達尼號生存預測補充資料-Optuna

更新 發佈閱讀 30 分鐘

補充資料:Optuna

礙於篇幅,我將鐵達尼號生存預測那篇的最後一段,超參數及特徵子集最佳化移至這裡。

🔬 特徵子集最佳化:讓模型自己挑夥伴

前面我們是靠經驗與 SHAP、bar chart 交叉分析挑選出最終的特徵組合。但一定會有一個想法,我能不能利用準確率讓他自己挑選?

實際上,這種問題屬於「特徵子集選擇(Feature Subset Selection)」,而幾乎所有的最佳化演算法都能處理這類問題。我們這邊就用 Optuna,這個廣泛用於參數調校的框架,來讓模型自己試著找出最好的特徵組合

我們首先準備一組完整的特徵池,包含數值、結構、分箱、類別 one-hot,以及我們前面自己構造的生存率特徵。這一包是我能想到的所有有意義的資訊:

# 最佳化工具
import optuna
# 可用的特徵集
all_features = [
# 數值與結構
'Pclass', 'Age', 'SibSp', 'Parch', 'Fare',
'FareBin_Code_5', 'FareBin_Code_4', 'FareBin_Code_6',
'FamilySize', 'IsAlone', 'SharedTicket', 'FarePerPerson',
'CabinLevel_High', 'CabinLevel_Low', 'CabinLevel_Missing',

# 生存率與頻率
'Ticket_Frequency', 'Ticket_Survival_Rate', 'Ticket_Survival_Rate_NA',
'Family_Survival_Rate', 'Family_Survival_Rate_NA', 'Is_Married',

# 類別 one-hot(建議都納入讓模型自行判斷)
'Sex_female', 'Sex_male',
'Embarked_C', 'Embarked_Q', 'Embarked_S',
'Title_Master', 'Title_Miss', 'Title_Mr', 'Title_Mrs', 'Title_Rare',
'AgeGroup_Very Young', 'AgeGroup_Young', 'AgeGroup_Adult', 'AgeGroup_Middle Aged', 'AgeGroup_Old',
'FamilySizeGroup_Large', 'FamilySizeGroup_Medium', 'FamilySizeGroup_Small'
]

接著我們寫一個 objective 函數,讓 Optuna 每次隨機選一批特徵(每個特徵都可以選或不選),再用 KFold 交叉驗證去評估這批特徵的表現好不好:

X = df_data[df_data['Survived'].notnull()].copy()
y = X['Survived']

# Optuna 目標函數
def objective(trial):
selected = []
for f in all_features:
if trial.suggest_categorical(f, [True, False]):
selected.append(f)

if not selected:
return 0.0 # 避免無特徵情況

kf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = []

for train_idx, val_idx in kf.split(X, y):
X_train = X.iloc[train_idx][selected]
y_train = y.iloc[train_idx]
X_val = X.iloc[val_idx][selected]
y_val = y.iloc[val_idx]

model = RandomForestClassifier(
n_estimators=200,
max_depth=5,
min_samples_split=4,
min_samples_leaf=3,
max_features='log2',
oob_score=True,
random_state=42,
n_jobs=-1,
verbose=0
)
model.fit(X_train, y_train)
y_pred = model.predict(X_val)
acc = accuracy_score(y_val, y_pred)
scores.append(acc)

return np.mean(scores)

然後就可以讓他開始跑分析了

# 開始搜尋
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=200)

# 輸出最佳特徵組合
best_features = [k for k, v in study.best_trial.params.items() if v]
print("最佳特徵組合:", best_features)
print("最佳準確率:", study.best_value)

這時候會開始一段一段的分析每種不同的特徵組合最終輸出會長這樣(省略前面一大串)

[I 2025-08-04 03:07:49,643] Trial 195 finished with value: 0.8529721925805035 and parameters: {'Pclass': False, 'Age': False, 'SibSp': True, 'Parch': True, 'Fare': True, 'FareBin_Code_5': True, 'FareBin_Code_4': True, 'FareBin_Code_6': False, 'FamilySize': False, 'IsAlone': False, 'SharedTicket': False, 'FarePerPerson': True, 'CabinLevel_High': True, 'CabinLevel_Low': False, 'CabinLevel_Missing': False, 'Ticket_Frequency': False, 'Ticket_Survival_Rate': True, 'Ticket_Survival_Rate_NA': False, 'Family_Survival_Rate': False, 'Family_Survival_Rate_NA': False, 'Is_Married': True, 'Sex_female': True, 'Sex_male': False, 'Embarked_C': True, 'Embarked_Q': False, 'Embarked_S': True, 'Title_Master': True, 'Title_Miss': False, 'Title_Mr': False, 'Title_Mrs': False, 'Title_Rare': False, 'AgeGroup_Very Young': False, 'AgeGroup_Young': False, 'AgeGroup_Adult': False, 'AgeGroup_Middle Aged': True, 'AgeGroup_Old': False, 'FamilySizeGroup_Large': False, 'FamilySizeGroup_Medium': True, 'FamilySizeGroup_Small': True}. Best is trial 40 with value: 0.8529721925805035.
[I 2025-08-04 03:07:52,733] Trial 196 finished with value: 0.8529721925805035 and parameters: {'Pclass': False, 'Age': False, 'SibSp': True, 'Parch': True, 'Fare': True, 'FareBin_Code_5': True, 'FareBin_Code_4': True, 'FareBin_Code_6': False, 'FamilySize': False, 'IsAlone': False, 'SharedTicket': False, 'FarePerPerson': True, 'CabinLevel_High': True, 'CabinLevel_Low': False, 'CabinLevel_Missing': False, 'Ticket_Frequency': False, 'Ticket_Survival_Rate': True, 'Ticket_Survival_Rate_NA': False, 'Family_Survival_Rate': False, 'Family_Survival_Rate_NA': False, 'Is_Married': True, 'Sex_female': True, 'Sex_male': False, 'Embarked_C': True, 'Embarked_Q': False, 'Embarked_S': True, 'Title_Master': True, 'Title_Miss': False, 'Title_Mr': False, 'Title_Mrs': False, 'Title_Rare': False, 'AgeGroup_Very Young': False, 'AgeGroup_Young': False, 'AgeGroup_Adult': False, 'AgeGroup_Middle Aged': True, 'AgeGroup_Old': False, 'FamilySizeGroup_Large': False, 'FamilySizeGroup_Medium': True, 'FamilySizeGroup_Small': True}. Best is trial 40 with value: 0.8529721925805035.
[I 2025-08-04 03:07:56,718] Trial 197 finished with value: 0.7553386479191514 and parameters: {'Pclass': False, 'Age': False, 'SibSp': True, 'Parch': True, 'Fare': False, 'FareBin_Code_5': True, 'FareBin_Code_4': True, 'FareBin_Code_6': False, 'FamilySize': False, 'IsAlone': False, 'SharedTicket': False, 'FarePerPerson': True, 'CabinLevel_High': True, 'CabinLevel_Low': False, 'CabinLevel_Missing': False, 'Ticket_Frequency': False, 'Ticket_Survival_Rate': True, 'Ticket_Survival_Rate_NA': False, 'Family_Survival_Rate': False, 'Family_Survival_Rate_NA': False, 'Is_Married': True, 'Sex_female': False, 'Sex_male': False, 'Embarked_C': True, 'Embarked_Q': False, 'Embarked_S': True, 'Title_Master': True, 'Title_Miss': False, 'Title_Mr': False, 'Title_Mrs': False, 'Title_Rare': False, 'AgeGroup_Very Young': False, 'AgeGroup_Young': False, 'AgeGroup_Adult': False, 'AgeGroup_Middle Aged': True, 'AgeGroup_Old': False, 'FamilySizeGroup_Large': False, 'FamilySizeGroup_Medium': True, 'FamilySizeGroup_Small': True}. Best is trial 40 with value: 0.8529721925805035.
[I 2025-08-04 03:07:59,778] Trial 198 finished with value: 0.8260435628648546 and parameters: {'Pclass': False, 'Age': False, 'SibSp': True, 'Parch': True, 'Fare': True, 'FareBin_Code_5': True, 'FareBin_Code_4': True, 'FareBin_Code_6': False, 'FamilySize': False, 'IsAlone': False, 'SharedTicket': False, 'FarePerPerson': True, 'CabinLevel_High': True, 'CabinLevel_Low': False, 'CabinLevel_Missing': False, 'Ticket_Frequency': False, 'Ticket_Survival_Rate': True, 'Ticket_Survival_Rate_NA': False, 'Family_Survival_Rate': False, 'Family_Survival_Rate_NA': False, 'Is_Married': True, 'Sex_female': True, 'Sex_male': False, 'Embarked_C': True, 'Embarked_Q': False, 'Embarked_S': True, 'Title_Master': False, 'Title_Miss': True, 'Title_Mr': False, 'Title_Mrs': False, 'Title_Rare': False, 'AgeGroup_Very Young': False, 'AgeGroup_Young': False, 'AgeGroup_Adult': False, 'AgeGroup_Middle Aged': True, 'AgeGroup_Old': False, 'FamilySizeGroup_Large': False, 'FamilySizeGroup_Medium': True, 'FamilySizeGroup_Small': True}. Best is trial 40 with value: 0.8529721925805035.
[I 2025-08-04 03:08:02,884] Trial 199 finished with value: 0.8473604921222773 and parameters: {'Pclass': False, 'Age': False, 'SibSp': True, 'Parch': True, 'Fare': True, 'FareBin_Code_5': True, 'FareBin_Code_4': True, 'FareBin_Code_6': False, 'FamilySize': False, 'IsAlone': False, 'SharedTicket': False, 'FarePerPerson': True, 'CabinLevel_High': True, 'CabinLevel_Low': False, 'CabinLevel_Missing': False, 'Ticket_Frequency': False, 'Ticket_Survival_Rate': True, 'Ticket_Survival_Rate_NA': False, 'Family_Survival_Rate': False, 'Family_Survival_Rate_NA': False, 'Is_Married': True, 'Sex_female': True, 'Sex_male': False, 'Embarked_C': True, 'Embarked_Q': False, 'Embarked_S': True, 'Title_Master': True, 'Title_Miss': False, 'Title_Mr': False, 'Title_Mrs': False, 'Title_Rare': False, 'AgeGroup_Very Young': False, 'AgeGroup_Young': True, 'AgeGroup_Adult': False, 'AgeGroup_Middle Aged': True, 'AgeGroup_Old': False, 'FamilySizeGroup_Large': False, 'FamilySizeGroup_Medium': True, 'FamilySizeGroup_Small': True}. Best is trial 40 with value: 0.8529721925805035.
最佳特徵組合: ['SibSp', 'Parch', 'Fare', 'FareBin_Code_5', 'FareBin_Code_4', 'FarePerPerson', 'CabinLevel_High', 'Ticket_Survival_Rate', 'Is_Married', 'Sex_female', 'Embarked_C', 'Embarked_S', 'Title_Master', 'AgeGroup_Middle Aged', 'FamilySizeGroup_Medium', 'FamilySizeGroup_Small']
最佳準確率: 0.8529721925805035

這樣我們就有一組特徵組合了,但是這邊必須特別注意,因為他只是單純的利用準確率當作目標去進行提升,所以前面說倒的共線性以及噪音雜訊是沒辦法考慮的,所以只能作為一個參考。

🧬 超參數最佳化:讓模型自己調參

除了讓模型自己選隊友(特徵),我們也可以讓它自己選怎樣的狀態最適合。

不同的樹模型(像是 XGBoost、LightGBM、CatBoost)都有許多設定,像是樹的深度、葉子的數量、分裂條件等等,這些設定會影響最終的表現,有時候欠過擬合也是這些害的。

這邊我同樣使用 Optuna 來進行超參數的搜尋,讓模型自動找出一組準確率最好的設定。

這次的主角是 Random Forest,我們讓 Optuna 幫我們搜尋下列這些參數:

raw-image

首先也是要建立模型特徵,這邊我們把之前所挑選出來的最佳特徵拿出來。

# 準備資料
best_features = [
'Sex_female', 'Title_Mr', 'FarePerPerson', 'Is_Married', 'Age', 'Pclass',
'IsAlone', 'Fare', 'Family_Survival_Rate', 'SharedTicket', 'Ticket_Frequency',
'FamilySize', 'SibSp', 'Title_Miss', 'Embarked_S', 'Ticket_Survival_Rate',
'Parch', 'CabinLevel_Missing', 'FamilySizeGroup_Small', 'Family_Survival_Rate_NA'
]

接著,我們定義一個 Objective 函數,告訴 Optuna 怎麼嘗試各種組合並用交叉驗證來評分。

# Objective 函數
def rf_objective(trial):
params = {
'n_estimators': trial.suggest_int('n_estimators', 300, 2000),
'max_depth': trial.suggest_int('max_depth', 3, 20),
'min_samples_split': trial.suggest_int('min_samples_split', 2, 15),
'min_samples_leaf': trial.suggest_int('min_samples_leaf', 2, 15),
'max_features': trial.suggest_categorical('max_features', ['sqrt', 'log2']),
'random_state': 42,
'n_jobs': -1,
}
model = RandomForestClassifier(**params)
return cross_val_score(model, X, y, cv=cv, scoring='accuracy').mean()

之後就能分析了。

# 執行 Optuna 搜尋
study_rf = optuna.create_study(direction='maximize')
study_rf.optimize(rf_objective, n_trials=100)

# 輸出最佳結果
print("Best Parameters:", study_rf.best_params)
print("Best Accuracy:", study_rf.best_value)

輸出會像這樣(同樣省略前面一大串):

[I 2025-08-04 03:41:24,643] Trial 94 finished with value: 0.8496265143431045 and parameters: {'n_estimators': 1275, 'max_depth': 15, 'min_samples_split': 9, 'min_samples_leaf': 9, 'max_features': 'log2'}. Best is trial 19 with value: 0.8518674282844767.
[I 2025-08-04 03:41:48,406] Trial 95 finished with value: 0.8496265143431045 and parameters: {'n_estimators': 1817, 'max_depth': 17, 'min_samples_split': 9, 'min_samples_leaf': 9, 'max_features': 'sqrt'}. Best is trial 19 with value: 0.8518674282844767.
[I 2025-08-04 03:42:07,579] Trial 96 finished with value: 0.848496641767623 and parameters: {'n_estimators': 1367, 'max_depth': 16, 'min_samples_split': 10, 'min_samples_leaf': 10, 'max_features': 'log2'}. Best is trial 19 with value: 0.8518674282844767.
[I 2025-08-04 03:42:27,180] Trial 97 finished with value: 0.848496641767623 and parameters: {'n_estimators': 1462, 'max_depth': 17, 'min_samples_split': 8, 'min_samples_leaf': 7, 'max_features': 'log2'}. Best is trial 19 with value: 0.8518674282844767.
[I 2025-08-04 03:42:48,962] Trial 98 finished with value: 0.8507438327788588 and parameters: {'n_estimators': 1573, 'max_depth': 15, 'min_samples_split': 2, 'min_samples_leaf': 8, 'max_features': 'log2'}. Best is trial 19 with value: 0.8518674282844767.
[I 2025-08-04 03:43:15,229] Trial 99 finished with value: 0.8485029188374866 and parameters: {'n_estimators': 1935, 'max_depth': 16, 'min_samples_split': 10, 'min_samples_leaf': 9, 'max_features': 'log2'}. Best is trial 19 with value: 0.8518674282844767.
Best Parameters: {'n_estimators': 1422, 'max_depth': 14, 'min_samples_split': 4, 'min_samples_leaf': 8, 'max_features': 'log2'}
Best Accuracy: 0.8518674282844767

這樣我們就有一組超參數,同樣的,他沒辦法判斷有沒有過擬合,所以還是需要進入正常的判斷,以及展開5-Flod交叉驗證,再進一步觀察 CV 與 LB 的落差,才能更進一步確定這個參數適不適合。

🧾 總結

Optuna 提供了一個高效率的方法,讓模型自動探索特徵組合與超參數設定。在鐵達尼號預測中,這樣的流程幫助我們找到一個標的,使我們有方向能夠進一步挑選特徵、調整超參數,並提升模型的表現。

在基礎篇EP.4也有做簡單的介紹。https://vocus.cc/article/68b922c3fd89780001926191

不過,這類結果並不是直接套用就好。Optuna 本身只依賴準確率或其他目標函數,無法考慮到共線性、過擬合或資料中的雜訊,因此最後仍需要人工判斷與交叉驗證來確認。

但 Optuna 仍是一個能顯著加速實驗過程的工具,尤其在特徵工程與參數搜尋上能幫我們省下大量時間。把它想像成「自動化的副駕駛」最貼切:能幫你試出可能性,但航向與安全仍要由人來把關。

留言
avatar-img
夕月之下
0會員
9內容數
在模型尚未收斂前,記下語言的提示與意圖。觀察者、語言與語言模型的交界。
夕月之下的其他內容
2025/10/01
這篇文章記錄了我第一次進行鐵達尼號比賽,以及後來又再度認真的玩這個比賽的過程,會介紹一下我最終的code以及最一開始到最終的心路歷程,分享給大家做參考。
Thumbnail
2025/10/01
這篇文章記錄了我第一次進行鐵達尼號比賽,以及後來又再度認真的玩這個比賽的過程,會介紹一下我最終的code以及最一開始到最終的心路歷程,分享給大家做參考。
Thumbnail
2025/09/04
本文簡單地介紹線性模型、樹模型和神經網路三大類機器學習模型,並結合Kaggle比賽實戰經驗,說明模型選擇、超參數調整、模型融合和模型解釋等關鍵技術。
Thumbnail
2025/09/04
本文簡單地介紹線性模型、樹模型和神經網路三大類機器學習模型,並結合Kaggle比賽實戰經驗,說明模型選擇、超參數調整、模型融合和模型解釋等關鍵技術。
Thumbnail
2025/08/06
這篇介紹特徵工程在 Kaggle 專案中的重要性,並詳細說明數值型和類別型特徵的處理技巧,包含標準化、正規化、分箱、數值轉換、Label Encoding...等方法。文章說明各個技巧的應用場景和注意事項,並說明如何提升模型的預測能力。
Thumbnail
2025/08/06
這篇介紹特徵工程在 Kaggle 專案中的重要性,並詳細說明數值型和類別型特徵的處理技巧,包含標準化、正規化、分箱、數值轉換、Label Encoding...等方法。文章說明各個技巧的應用場景和注意事項,並說明如何提升模型的預測能力。
Thumbnail
看更多