データサイエンティストになりたいのでタイタニックコンペに挑戦してみた

データサイエンティストになりたいので初心者向けコンペの タイタニック(Titanic - Machine Learning from Disaster)に挑戦してみました。 今回はKaggle Notebook上でPythonを使ってモデルの学習と予測をしました。 LightGBMでGBDT(勾配ブースティング木)、Kerasでニューラルネットワークのモデルを学習し、 それぞれの予測値の平均をとってアンサンブルしました。

分析の準備

必要なモジュールをインポートします。

from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
import lightgbm as lgb
from tensorflow import keras
from tensorflow.keras import layers

データを読み込みます。 このコンペではtrain.csv、test.csvとgender_submission.csvが与えられます。

data_dir = Path("/kaggle/input/titanic")

train = pd.read_csv(data_dir / "train.csv")
test = pd.read_csv(data_dir / "test.csv")
sample_submission = pd.read_csv(data_dir / "gender_submission.csv")

各カラムの説明は以下のとおりです。

  • Survival - 生存したか(0=死亡、1=生存)
  • Pclass - チケットクラス
  • Name - 名前
  • Sex - 性別
  • Age - 年齢
  • SibSp - タイタニックに乗船した兄弟/配偶者の人数
  • Parch - タイタニックに乗船した親/子の人数
  • Ticket - チケット番号
  • Fare - 運賃
  • Cabin - 客室番号
  • Embarked - 乗船港

特徴量の作成

preprocess関数で前処理をしています。 与えられたデータからTitleとFamilyという特徴量を作りました。 前処理の内容は以下のとおりです。

  • Title - Nameに含まれる敬称(Mr、Miss、Mrs、Master、それ以外)を整数値に変換
  • Cabin - 先頭のアルファベットを抽出し整数値に変換
  • Sex - 女性=0、男性=1に変換
  • Family - SibSpとParchの合計
  • Fare - 分布に偏りがあるのでlog(x+1)で変換
  • Embarked - 整数値に変換

LightGBMは入力に欠損値があっても学習できるのでpreprocessでは欠損値補完していません。 ニューラルネットワーク用にカテゴリ変数(Title、Cabin、Embarked)をone-hot encodingしました。 PassengerId、Name、Ticketは不要なので削除しました。

def preprocess(df):
    df = df.copy()

    df["Title"] = 0
    title_dict = {
        " Mr\. ": 1,
        " Miss\. ": 2,
        " Mrs\. ": 3,
        " Master\. ": 4,
    }
    for pattern, label in title_dict.items():
        df.loc[df["Name"].str.contains(pattern), "Title"] = label
    df["Title"] = pd.Categorical(df["Title"], categories=range(5))

    cabin_dict = {"A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7, "T": 8}
    df["Cabin"] = df["Cabin"].str[0].replace(cabin_dict).fillna(0)
    df["Cabin"] = pd.Categorical(df["Cabin"], categories=range(9))

    df["Sex"] = df["Sex"].replace({"female": 0, "male": 1})
    df["Family"] = df["SibSp"] + df["Parch"]
    df["Fare"] = np.log1p(df["Fare"])
    df["Embarked"] = df["Embarked"].fillna(0).replace({"C": 1, "Q": 2, "S": 3})
    df["Embarked"] = pd.Categorical(df["Embarked"], categories=range(4))

    dummies = pd.get_dummies(df[["Title", "Cabin", "Embarked"]], drop_first=True)
    df = pd.concat([df, dummies], axis=1)
    df = df.drop(columns=["PassengerId", "Name", "Ticket"])

    return df


_train = preprocess(train)
train_X = _train.drop(columns="Survived")
train_y = _train["Survived"]
test_X = preprocess(test)

モデルの学習・予測

今回はGBDTとニューラルネットワークの2つのモデルを作成し、 予測値の平均をとってアンサンブルします。

GBDT

GBDTの学習・予測は以下のとおりです。 検証データに対する正解率は0.843でした。

cols = ["Pclass", "Sex", "Age", "Fare", "Cabin", "Embarked", "Title", "Family"]
train_X_lgb, val_X_lgb, train_y_lgb, val_y_lgb = train_test_split(
    train_X[cols],
    train_y,
    test_size=0.3,
    shuffle=True,
    stratify=train_y,
    random_state=42,
)
test_X_lgb = test_X[cols]

# データセット作成
train_dataset = lgb.Dataset(train_X_lgb, train_y_lgb)
val_dataset = lgb.Dataset(val_X_lgb, val_y_lgb)

# ハイパーパラメータの設定
params = {
    "objective": "binary",
    "num_leaves": 7,
    "learning_rate": 0.1,
    "bagging_fraction": 0.9,
    "bagging_freq": 1,
    "lambda_l1": 0.5,
    "lambda_l2": 1,
    "seed": 42,
}
categorical_features = ["Cabin", "Embarked", "Title"]

# 学習
model = lgb.train(
    params,
    train_dataset,
    num_boost_round=100,
    valid_sets=[train_dataset, val_dataset],
    early_stopping_rounds=10,
    categorical_feature=categorical_features,
    verbose_eval=10,
)

# 予測
val_pred_lgb = model.predict(val_X_lgb)
test_pred_lgb = model.predict(test_X_lgb)

# 評価
acc = accuracy_score(val_y_lgb, np.where(val_pred_lgb > 0.5, 1, 0))
print("Accuracy:", acc)

ニューラルネットワーク

build_modelニューラルネットワークのモデルを構築しています。 one-hot encodingしたカテゴリ変数を選択しています。 ニューラルネットワークは欠損値を扱えない、スケーリングが必要なのでSimpleImputerで欠損値補完、StandardScalerで標準化しています。

検証データに対する正解率は0.832でした。

def build_model(num_inputs):
    model = keras.Sequential([
        layers.Dense(64, activation="relu"),
        layers.Dropout(0.1),
        layers.Dense(32, activation="relu"),
        layers.Dropout(0.1),
        layers.Dense(1, activation="sigmoid"),
    ])
    model.compile(
        optimizer="adam",
        loss="binary_crossentropy",
        metrics=["accuracy"],
    )

    return model


cols = [
    "Pclass", "Sex", "Age", "SibSp", "Parch", "Fare",
    "Title_1", "Title_2", "Title_3", "Title_4",
    "Cabin_1", "Cabin_2", "Cabin_3", "Cabin_4", "Cabin_5", "Cabin_6", "Cabin_7", "Cabin_8",
    "Embarked_1", "Embarked_2", "Embarked_3",
]
scaler = make_pipeline(
    StandardScaler(),
    SimpleImputer(),
)
train_X_nn = scaler.fit_transform(train_X[cols])
train_X_nn, val_X_nn, train_y_nn, val_y_nn = train_test_split(
    train_X_nn,
    train_y,
    test_size=0.3,
    shuffle=True,
    stratify=train_y,
    random_state=43,
)
test_X_nn = scaler.transform(test_X[cols])

# 学習
model = build_model(len(cols))
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
model.fit(
    train_X_nn,
    train_y_nn,
    batch_size=32,
    epochs=100,
    callbacks=[early_stopping],
    validation_data=(val_X_nn, val_y_nn),
    shuffle=True,
)

# 予測
val_pred_nn = model.predict(val_X_nn).flatten()
test_pred_nn = model.predict(test_X_nn).flatten()

# 評価
acc = accuracy_score(val_y_nn, np.where(val_pred_nn > 0.5, 1, 0))
print("Accuracy:", acc)

アンサンブル

GBDTとニューラルネットワークの予測値を平均します。

test_pred = test_pred_lgb * 0.5 + test_pred_nn * 0.5

提出ファイルの作成

submission = sample_submission.assign(Survived=np.where(test_pred > 0.5, 1, 0))
submission.to_csv("./submission.csv", index=False)

結果

スコアは0.77751でした。 今後もコンペに参加してデータサイエンティストを目指していきたいです!!

f:id:stkns1024:20211014201807p:plain