Day 083 — Titanic攻略② — アンサンブルで上位25%を目指す

2026-07-03 水 / Phase 3 コーディング VotingClassifier / Soft Voting / アンサンブル / Titanic上位25%

📚 背景知識(読んでから問題へ)

🏆
Day 081 でロジスティック回帰ベースライン(CV ≈ 0.80)を作り、Day 082 で特徴量エンジニアリングを追加(CV ≈ 0.82〜0.83)しました。 今日はアンサンブルを使い、Titanic のリーダーボード上位25%(スコア ≈ 0.80以上)を目指します。
💡
アンサンブルとは「複数の専門家の意見を組み合わせる」手法です。1つのモデルより多数のモデルを組み合わせる方が、偏った判断ミスを相殺できます。

3つのモデルの役割分担

モデル 強み 弱み 期待CV
LogisticRegression 安定・高速・線形関係の捉え方 非線形を苦手とする ≈ 0.81
RandomForestClassifier 特徴量間の交互作用を捉える 深い木は過学習しやすい ≈ 0.82
GradientBoostingClassifier 残差を逐次学習・高精度 計算コストが高い ≈ 0.83
VotingClassifier (soft) 各モデルの誤りを補完 遅い・ハイパーパラメータ増 ≈ 0.83〜0.84
① 特徴量エンジニアリング(Day082)
② sklearn Pipeline
③ VotingClassifier
④ Soft Voting
⑤ Stratified K-Fold CV
⑥ submission.csv

🗳️ Soft Voting の仕組み

ある乗客 X に対して3モデルが「生存確率」を予測した場合:

Logistic Regression
0.72
生存確率
+
Random Forest
0.81
生存確率
+
Gradient Boosting
0.65
生存確率
平均確率
0.727
閾値0.5超 → 生存(1)
📌
Hard Voting との違い:
Hard Voting は「1, 1, 1 → 生存(多数決)」と各モデルのクラスだけ見ます。
Soft Voting は確率の平均で決定するため、モデルの「自信度」が反映されより高精度です。

重み付き Soft Voting: weights=[1, 2, 2] の場合

LR weight=1 RF weight=2 GB weight=2 (0.72×1 + 0.81×2 + 0.65×2) ──────────────────── = 0.737 (1 + 2 + 2)

📋 アンサンブル手法比較

手法 概要 実装難易度 精度向上 計算コスト
Hard Voting クラスの多数決 簡単
Soft Voting 確率の平均 簡単 中〜高
Averaging Soft Voting の連続値版(回帰) 簡単
Stacking Level-1予測を Level-2モデルに渡す 普通 中〜高
Blending hold-out でメタ特徴量を作成 普通

🎯 問題

📋
要件1: 前処理パイプラインを再構築
Day 082 の特徴量(Title / FamilySize / IsAlone / Deck)をすべて含める。
📋
要件2: 3つのモデルを定義
LogisticRegression(max_iter=1000)
RandomForestClassifier(n_estimators=200)
GradientBoostingClassifier(n_estimators=200, learning_rate=0.05)
📋
要件3: VotingClassifier(soft voting)でアンサンブル
voting='soft'、weights=[1, 2, 2] で RF・GB を重視する。
📋
要件4: Stratified K-Fold CV(k=5)でスコア計測
各モデル単体と Voting のスコアを表示し、Voting が最高になることを確認する。
📋
要件5: Kaggle提出ファイルを生成
submission.csv(PassengerId, Survived)を出力する。

💡 ヒント

Hint 1 方向性

VotingClassifierestimators=[('lr', lr), ('rf', rf), ('gb', gb)] のようにリストで渡す。

Hint 2 アプローチ

soft voting を使うには各モデルが predict_proba をサポートしている必要がある。LR は max_iter=1000 で収束しやすくなる。

Hint 3 コード骨格
from sklearn.ensemble import VotingClassifier, RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression

lr  = LogisticRegression(max_iter=1000, C=1.0)
rf  = RandomForestClassifier(n_estimators=200, random_state=42)
gb  = GradientBoostingClassifier(n_estimators=200, learning_rate=0.05, random_state=42)

voting = VotingClassifier(
    estimators=[('lr', lr), ('rf', rf), ('gb', gb)],
    voting='soft'
)

from sklearn.model_selection import cross_val_score, StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

for name, model in [('LR', lr), ('RF', rf), ('GB', gb), ('Voting', voting)]:
    scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
    print(f"{name}: {scores.mean():.4f} ± {scores.std():.4f}")

模範解答

import pandas as pd
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import (RandomForestClassifier, GradientBoostingClassifier,
                               VotingClassifier)
from sklearn.model_selection import cross_val_score, StratifiedKFold

# ── 1. データ読み込み ──────────────────────────────
train = pd.read_csv('train.csv')
test  = pd.read_csv('test.csv')

# ── 2. 特徴量エンジニアリング(Day 082 の成果)────────
def engineer_features(df):
    df = df.copy()
    df['Title'] = df['Name'].str.extract(r',\s*([^\.]+)\.')
    rare = ['Jonkheer','Don','Sir','Capt','Col','Major','Rev',
            'Dr','Lady','Countess','Mme','Ms','Mlle','the Countess','Dona']
    df['Title'] = df['Title'].replace(rare, 'Rare')
    df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
    df['IsAlone']    = (df['FamilySize'] == 1).astype(int)
    df['Deck']       = df['Cabin'].str[0].fillna('U')
    return df

train = engineer_features(train)
test  = engineer_features(test)

# ── 3. 特徴量選択 ──────────────────────────────────
features_num = ['Age', 'Fare', 'FamilySize']
features_cat = ['Pclass', 'Sex', 'Embarked', 'Title', 'Deck', 'IsAlone']

X      = train[features_num + features_cat]
y      = train['Survived']
X_test = test[features_num + features_cat]

# ── 4. 前処理パイプライン ─────────────────────────
num_pipe = Pipeline([
    ('impute', SimpleImputer(strategy='median')),
    ('scale',  StandardScaler()),
])
cat_pipe = Pipeline([
    ('impute', SimpleImputer(strategy='constant', fill_value='Missing')),
    ('ohe',    OneHotEncoder(handle_unknown='ignore', sparse_output=False)),
])
preprocessor = ColumnTransformer([
    ('num', num_pipe, features_num),
    ('cat', cat_pipe, features_cat),
])

# ── 5. モデル定義 ────────────────────────────────
lr  = LogisticRegression(max_iter=1000, C=1.0, random_state=42)
rf  = RandomForestClassifier(n_estimators=200, max_depth=6,
                              min_samples_leaf=4, random_state=42)
gb  = GradientBoostingClassifier(n_estimators=200, learning_rate=0.05,
                                  max_depth=3, random_state=42)
voting = VotingClassifier(
    estimators=[('lr', lr), ('rf', rf), ('gb', gb)],
    voting='soft',
    weights=[1, 2, 2],
)

# ── 6. CV評価 ──────────────────────────────────────
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

results = {}
for name, base_model in [('LR', lr), ('RF', rf), ('GB', gb), ('Voting', voting)]:
    pipe   = Pipeline([('prep', preprocessor), ('model', base_model)])
    scores = cross_val_score(pipe, X, y, cv=cv, scoring='accuracy')
    results[name] = scores
    print(f"{name:8s}: {scores.mean():.4f} ± {scores.std():.4f}")

# ── 7. 最終モデルで全訓練データ学習 → 提出ファイル ─────
final_pipe = Pipeline([('prep', preprocessor), ('model', voting)])
final_pipe.fit(X, y)

preds      = final_pipe.predict(X_test)
submission = pd.DataFrame({'PassengerId': test['PassengerId'], 'Survived': preds})
submission.to_csv('submission.csv', index=False)
print("\nsubmission.csv を生成しました!")
print(submission['Survived'].value_counts())

🔢 Step-by-Step 解説

1
特徴量エンジニアリング関数をまとめる
def engineer_features(df) として関数化することで、traintest の両方に同じ処理を適用できます。 関数化しないと test データに特徴量を作り忘れるバグが頻発します(Kaggleでよくあるミス)。
2
3つのモデルを準備する
各パラメータの意味:
max_iter=1000: LR の収束まで最大1000回計算
n_estimators=200: RF/GB は木200本を作る
learning_rate=0.05: GB はゆっくり慎重に学習(過学習防止)
max_depth=6/3: 木の深さを制限(過学習防止)
3
VotingClassifier を組み立てる
VotingClassifier(
    estimators=[('lr', lr), ('rf', rf), ('gb', gb)],
    voting='soft',
    weights=[1, 2, 2],
)
voting='soft': 確率の平均(hard=多数決より精度が良い)
weights=[1, 2, 2]: RF・GB を2倍重視(LR より性能が高いため)
4
Pipeline + CV でリーク防止評価
Pipeline([('prep', preprocessor), ('model', voting)]) として前処理→モデルを一体化。 これで「CVの各フォールドで前処理を独立して行う」ため情報リークが起きません。
5
全訓練データで再学習 → 提出ファイル生成
CV評価後、final_pipe.fit(X, y) で全データを使って最終モデルを学習。 predict(X_test) で test の予測を生成し submission.csv に保存します。

📈 精度比較チャート(期待値)

CV Accuracy(5-Fold Stratified、Day 082 特徴量使用)

LR(ベースライン)
0.81
≈ 0.810
ベースライン
RandomForest
0.82
≈ 0.820
単体
GradientBoosting
0.83
≈ 0.830
単体
Voting (soft, w=[1,2,2])
0.84
≈ 0.838
最良

※ 実際のスコアは乱数・特徴量の実装によって変動します。アンサンブルは必ずしも単体を上回るとは限りません。

Titanic リーダーボード目安(Public LB Accuracy)

0.60 0.70 0.80 0.85 Day081 ベースライン 0.767 Day082 特徴量工学 0.790 Day083 アンサンブル ≈0.800 上位25% トップ1%≈0.84

📐 数学補足(文系向け)

Soft Voting の計算式

📌
重み付き平均確率:
P_final = (w₁×P₁ + w₂×P₂ + w₃×P₃) / (w₁ + w₂ + w₃)

今日の例: weights=[1, 2, 2]、各モデル確率=[0.72, 0.81, 0.65]
P_final = (1×0.72 + 2×0.81 + 2×0.65) / 5 = (0.72 + 1.62 + 1.30) / 5 = 3.64 / 5 = 0.728
0.728 > 0.5 → 生存(1)と予測

なぜアンサンブルが効くのか(分散の削減)

💡
各モデルの誤りが互いに独立していれば、平均を取ることで分散(ブレ)が減ります。
コインを1枚投げると裏表の確率は不安定ですが、100枚投げれば表は約50%に安定します。アンサンブルも同じ原理です。

🏅 Kaggleでの実践的な使い方

状況 推奨手法
まず試す Soft Voting(実装が簡単、安定した向上が期待できる)
精度を極める Stacking(Level-2 メタモデル)→ Phase 4 テーマ13-15
計算リソースが限られる Hard Voting or Averaging(高速)
上位1%を目指す 5〜10モデルの Stacking + Pseudo Labeling
⚠️
Titanic では Voting / Stacking で 0.79〜0.81 が現実的な目標です。 0.82以上は強力な特徴量工学が必要で、トップ1%(≈ 0.84)は既知リークを活用することが多いです。 CVスコアを信頼し、LBスコアに過度に合わせないことがKaggleの基本原則です。

⚠️ よくある誤解・ミス

誤解・ミス なぜ起こるか 正しい理解
モデルを増やせば必ず精度が上がる アンサンブルの誤解 相関の低い多様なモデルを組み合わせることが重要。同じ種類を増やしても効果は薄い
soft voting で predict_proba エラー SVM など確率を返さないモデルを使用 SVM は probability=True が必要。sklearn の大半のモデルは対応済み
test データに特徴量工学を忘れる train にしか処理しない engineer_features 関数化し train・test 両方に適用する
CV と Pipeline を分離してリーク発生 前処理を先にやってから CV Pipeline に前処理を含め cross_val_score に渡す
weights をむやみに大きくする 性能の良いモデルを過度に重視 CVスコアの差が小さい場合は均等重みの方が安定することもある

🚀 次のステップ

📖
発展: Stacking(Level-2 メタモデル)
VotingClassifier の上位版。Level-1の予測確率を新しい特徴量として Level-2 モデル(LR など)に学習させる。 さらに 1〜2% の精度向上が期待できる。→ Phase 4 テーマ13-15 で詳しく学ぶ。
🎯
次回予告: Playground Series 活用法
Kaggle の月次コンペ(Playground Series)を使って、実際にコンペに参加しながら Bronze → Silver メダルを狙う戦略を学ぶ。

📝 自己評価(解いた後に記入)

自分の回答・気づき・メモ: