はじめに
前回は特徴量エンジニアリングを行ったので,今回はロジスティック回帰からモデルの変更を行いたいと思います.
現在のデータフレームはこのようになっています.
- Pclass: 客室の等級(1, 2, 3)
- Age: 年齢(欠損は中央値で補完)
- Sex_male: 性別(男性なら True、女性なら False)
- FamilySize: 同乗家族の人数(SibSp + Parch + 1)
- IsAlone: 一人で乗船しているか(FamilySize = 1 なら 1)
- IsChild: 10歳未満か(KDE で子供の生存率が高いと分かったため作成)
- Title_Miss: 敬称が Miss(未婚女性)か
- Title_Mr: 敬称が Mr(成人男性)か
- Title_Mrs: 敬称が Mrs(既婚女性)か
- Title_Other: 敬称が Dr, Rev 等のレアなものか
Title抽出が一番効果があり,最終的なaccuracyの値は0.8114と,一番最初に作成したモデルの0.7856から特徴量エンジニアリングにより若干改善しました.
今回はtree系と呼ばれるモデルのうち,今回はランダムフォレスト,XGBoost,LightGBMを試していきます.
データセットの読み込みや前処理は前回のものと同じです.tree 系は標準化不要ですが、ロジスティック回帰と同じデータで比較するため、標準化済みのデータをそのまま使っています。
他のモデルを検討
ランダムフォレスト
- 複数の決定木をランダムに作り、それぞれの予測の多数決で最終判断
- 個々の木は過学習しやすいが、多数の木の平均でブレを抑える
- 前処理(標準化)が不要で、デフォルトでもそこそこの性能
from sklearn.ensemble import RandomForestClassifier
# RandomForest
rf = RandomForestClassifier(n_estimators=100, random_state=42)
cv_rf = cross_val_score(rf, X_train_v5_scaled, y_train, cv=5, scoring='accuracy')
print(f"RandomForest Mean: {cv_rf.mean():.4f}, Std: {cv_rf.std():.4f}")
XGBoost
- 木を順番に作り、前の木の予測の「残差(誤差)」を次の木が学習する(勾配ブースティング)
- 正則化が組み込まれており、過学習を抑えやすい
from xgboost import XGBClassifier
# デフォルト
xgb_default = XGBClassifier(n_estimators=100, random_state=42, eval_metric='logloss')
cv_xgb = cross_val_score(xgb_default, X_train_v5_scaled, y_train, cv=5, scoring='accuracy')
print(f"XGBoost default Mean: {cv_xgb.mean():.4f}, Std: {cv_xgb.std():.4f}")
LightGBM
- XGBoost と同じ勾配ブースティングだが学習が高速
(既にうまく予測できているデータは省略して難しいデータに集中するから) - Kaggleで最もよく使われるモデルの一つ
# LightGBM
import lightgbm as lgb
lgb_model = lgb.LGBMClassifier(n_estimators=100, random_state=42, verbose=-1)
cv_lgb = cross_val_score(lgb_model, X_train_v5_scaled, y_train, cv=5, scoring='accuracy')
print(f"LightGBM Mean: {cv_lgb.mean():.4f}, Std: {cv_lgb.std():.4f}")

意外にもモデルを変えただけだとロジスティック回帰の0.8114とそれほど変わりませんでした(前回の結果).
ハイパーパラメータチューニング
現在はtree系はデフォルトパラメータのままなので,グリッドサーチで,ハイパーパラメータチューニングを行いたいと思います.まだ完全に理解はできていないものはありますが,各モデルのハイパーパラメータについて簡単に説明も加えています.
ランダムフォレスト
- n_estimators: 木の本数。多いほど精度が上がりやすいが、計算が遅くなる
- max_depth: 木の深さの制限。深いほど複雑な判断ができるが、過学習しやすい
- min_samples_leaf: 葉(末端ノード)に最低限必要なサンプル数。大きいほど木が単純になり、過学習を抑える
from sklearn.model_selection import GridSearchCV
# RandomForest
rf_params = {
'n_estimators': [100, 200, 500],
'max_depth': [3, 5, 7, None],
'min_samples_leaf': [1, 3, 5]
}
rf_grid = GridSearchCV(RandomForestClassifier(random_state=42),
rf_params, cv=5, scoring='accuracy')
rf_grid.fit(X_train_v5_scaled, y_train)
print(f"RF best: {rf_grid.best_score_:.4f}, params: {rf_grid.best_params_}")
XGBoost
- learning_rate: 学習率。各木の予測をどれくらいの重みで足すか。
小さいほど慎重に学習する(その分 n_estimators を増やす必要がある) - subsample: 各木の学習に使うデータの割合。
0.8 なら 80% をランダムに抽出して学習。過学習を抑える効果がある
# ハイパーパラメータチューニング
xgb_params = {
'n_estimators': [100, 200, 500],
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.05, 0.1],
'subsample': [0.8, 1.0],
}
xgb_grid = GridSearchCV(
XGBClassifier(random_state=42, eval_metric='logloss'),
xgb_params, cv=5, scoring='accuracy'
)
xgb_grid.fit(X_train_v5_scaled, y_train)
print(f"XGBoost tuned Mean: {xgb_grid.best_score_:.4f}, params: {xgb_grid.best_params_}")
LightGBM
- num_leaves: 1本の木の葉の数。大きいほど複雑なモデル。
max_depth とは別の角度で木の複雑さを制御する LightGBM 特有のパラメータ
# LightGBM
lgb_params = {
'n_estimators': [100, 200, 500],
'max_depth': [3, 5, 7, -1],
'learning_rate': [0.01, 0.05, 0.1],
'num_leaves': [15, 31, 63]
}
lgb_grid = GridSearchCV(lgb.LGBMClassifier(random_state=42, verbose=-1),
lgb_params, cv=5, scoring='accuracy')
lgb_grid.fit(X_train_v5_scaled, y_train)
print(f"LGB best: {lgb_grid.best_score_:.4f}, params: {lgb_grid.best_params_}")
デフォルトとチューニング後を比較すると以下の通りです。
| モデル | デフォルト | チューニング後 | 改善 |
|---|---|---|---|
| RandomForest | 0.8148 | 0.8328 | +0.018 |
| XGBoost | 0.8204 | 0.8395 | +0.019 |
| LightGBM | 0.8137 | 0.8339 | +0.020 |
いずれも +0.02 程度の改善にとどまりました.
結果比較
結果をまとめると以下の通りです.
- ロジスティック回帰:0.8114(前回)
- ランダムフォレスト:0.8328
- XGBoost:0.8395
- LightGBM:0.8339
ロジスティック回帰(0.8114)からの改善幅が小さいです.
非線形な特徴量の取り扱いが得意なtree系で改善が見られないということを考えると,「今の特徴量セットでは、非線形パターンがあまり残っていない」ということが考えられます.
つまり,IsChildやTitleで手動で非線形効果を拾い済みなので,tree系が自動で拾える追加の非線形パターンが少ない、ということです.特徴量エンジニアリングが良くできている証拠とも言えます.
最適化されたパラメータからも同様の示唆が得られ,
- RFの max_depth=7, min_samples_leaf=3 → 深くしすぎず適度に制約をかけた方が良い結果
- LGBMの num_leaves=15 → デフォルト(31)より小さい。複雑なモデルにする必要がない
どちらも「シンプルなモデルの方が良い」ということがわかります.
今回はそれほど改善が見られませんでしたので,この辺で今回も提出しておきます.
(手順は前回と同様です)
結果は0.75837と今までで一番低い数値でした...
提出スコアとの乖離もどんどん大きくなっています.
891件という小さなtrainデータでは,CVと実テストの乖離が起きやすい(過学習)とのことです.モデルの工夫で改善できるそうですが,あまり深追いせずこの辺でtitanicは終わりにしたいと思います.
3回にわたっての推移は以下のとおりです
| 回 | 内容 | CV accuracy | 提出スコア |
|---|---|---|---|
| 1回目 | 6特徴量 + ロジスティック回帰 | 0.7856 | 0.763 |
| 2回目 | 特徴量エンジニアリング | 0.8114 | 0.775 |
| 3回目 | モデル変更 + チューニング | 0.8395 | 0.758 |
まとめ
これまで過去3回にわたってTitanicのデータセットを使って機械学習の勉強をしてきました.
pythonすらもほとんど触ったことなかったのですが,少し苦手意識も減ってきた感じがします.
データの取り扱いにはまだ慣れず,現在のデータフレームや何の操作をどの列に行なっているのか把握するのに時間がかかります.
特徴量エンジニアリングに関しても,まだ自分には思いつかないアプローチばかりです.
ですが,そこにいかに工夫ができるか(データの取得も含めて),というのがデータサイエンスの面白さであり,実力が試される部分なのかなと感じました.
これからも色々なデータセットに取り組んでみたいと思います.





