Kaggle Titanic やってみた感想

データサイエンスの一連の流れってどんな感じなんだろう?と体験して見るために、Kaggleのチュートリアルをやってみた。

https://www.kaggle.com/c/titanic

Kaggle Titanic は、Kaggle のチュートリアルでよく使われる題材。タイタニック号の乗客名簿と、生存できたかを含むデータを与えられ、予測モデルを作成し、その精度を競う。

データをダウンロードする

  • https://www.kaggle.com でユーザー登録をする。
  • https://www.kaggle.com/<username>/account にアクセスし API => Create New API Token から kaggle.json をダウンロード
  • ダウンロードしたアクセストークンを ~/.kaggle/kaggle.json に置く

以下 pipenv を使った例

$ pipenv install kaggle
$ kaggle competitions download -c titanic

これで train.csv と test.csv のデータが手に入る。test.csv は解答用で、生存できたか(Survived)のデータは含まれない。

大雑把な流れ

  • 欠損値の処理(今回は単に捨てた)
  • 値の正規化(年齢など)
  • train.csv のうち 80% を訓練用、20% をテスト用データとして分割
  • 訓練モデルを作成
  • 訓練モデルにテストデータを入力し、精度を測定

今回は kaggle への提出は行っていない。

keras によるモデル作成

https://github.com/linxinzhe/tensorflow-titanic/blob/master/keras_titanic.py を参考に、理解するためにリファクタしてみた。

from keras.optimizers import SGD, Adam
from keras.layers import Dense, Activation
from keras.models import Sequential
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
import numpy as np
import pandas as pd


def normalize_data(data):
    not_concerned_columns = ["PassengerId", "Name",
                             "Ticket", "Fare", "Cabin", "Embarked"]
    data = data.drop(not_concerned_columns, axis=1)
    data = data.dropna()

    # normalize
    dummy_columns = ["Pclass"]
    for column in dummy_columns:
        data = pd.concat([data, pd.get_dummies(
            data[column], prefix=column)], axis=1)
        data = data.drop(column, axis=1)

    # normalize Label:Sex to int
    le = LabelEncoder()
    le.fit(["male", "female"])

    data["Sex"] = le.transform(data["Sex"])

    # normalize Age
    ss = StandardScaler()
    data["Age"] = ss.fit_transform(data["Age"].values.reshape(-1, 1))
    return data


def split_train_and_test(data, rate=0.8):
    data_y = data["Survived"]
    data_x = data.drop(["Survived"], axis=1)

    train_valid_split_idx = int(len(data_x) * rate)
    train_x = data_x[:train_valid_split_idx]
    train_y = data_y[:train_valid_split_idx]

    valid_test_split_idx = (len(data_x) - train_valid_split_idx) // 2
    test_x = data_x[train_valid_split_idx + valid_test_split_idx:]
    test_y = data_y[train_valid_split_idx + valid_test_split_idx:]

    return train_x.values, train_y.values.reshape(-1, 1), test_x.values, test_y.values.reshape(-1, 1)


def build_model(input_dim):
    model = Sequential()
    model.add(Dense(20, input_dim=input_dim))
    model.add(Activation('relu'))
    model.add(Dense(1, input_dim=20))
    model.add(Activation('sigmoid'))
    model.compile(optimizer=SGD(lr=0.01),
                  loss='binary_crossentropy',
                  metrics=['accuracy'])
    return model


# load data
train_data = pd.read_csv("data/train.csv")
normalized_data = normalize_data(train_data)
train_x, train_y, test_x, test_y = split_train_and_test(normalized_data, 0.8)

model = build_model(train_x.shape[1])

# train
model.fit(train_x, train_y, nb_epoch=120, batch_size=16)

# test
[loss, accuracy] = model.evaluate(test_x, test_y)
print("loss:{0} -- accuracy:{1}".format(loss, accuracy))

だいたい 80~86%ぐらいの予測率だった。他のチュートリアルを見てもそのぐらいに収束するっぽい。 competition で100点だしてる人たちは、答えをなんやかんややってチートしてそう。

感想

  • あんまり綺麗なコード例が見つからない。今回の題材は、チュートリアルなのに、コードを綺麗に見せようという努力がなされたものを見かけなくてイライラした。
  • jupyter notebook でやる人が多いのか、数行のスニペット単位で整形することが多く、プログラマ的なモジュール分割ではない、という印象を受けた
  • 卒論でR触ってたのでpandas のデータフレームなんとなくわかったが、各種utility が覚えゲーっぽい
  • 今回は単に欠損値を捨てたが、中間値で埋めたり、それ自体に予測モデルを作って埋める人が見受けられた。中間値それ自体がバイアスになったり、あるいは欠損値があることそのものがある種の特徴量になってる場合、どう扱うべきか、コンテキストごとに迷いそう
  • 出先でやっていたが、バッテリ消費が激しい
  • 一応、このデータでは「女・子供は助かる可能性が高い」というのは知っていたのだが、ディープラーニングを使うとその事実に気づくことなくモデルができてしまうので、ドメイン的な学び甲斐がなかった。NNのバイアスから結果に響かない特徴量を検出して捨てる、みたいな手法はありそうなので調べる