KaggleのTitanicをTensorflowで解いてみる
Kaggle とは
Kaggle は Google が主催の機械学習コンペティションで、様々な課題に対してその精度を競いあうものです。
精度が高ければ、上位チームには懸賞金が出ます。
そんな Kaggle も初心者向けに常時公開してあるタスクがあり、そのひとつが Titanic の生死分類タスクです。
Titanic はあの映画にもなった大型客船のことです。ここに乗船していた人の状況(利用料金、性別、家族構成など)によって生死がどうなったかを予測していきます。
Tensorflow で分類していく
Tensorflow はニューラルネットワークによるディープラーニングの機械学習ライブラリです。Tensorflow 2.0 になってより使いやすくなりました。
今回はとりあえず「エイヤ!」で回答してみます。
プログラムの中身
プログラムの中身はこんな感じで構成しました。
- データ読み込み
- 不要列の削除
- 目的変数の分離
- 質的データを量的データへ変換
- Nan の除去
- Float データの標準化
- 訓練データ作成と学習
- 結果確認と出力
使用するライブラリ
今回は下記のライブラリを利用しました。
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import Dense, Dropout
from tensorflow.keras import regularizers
細かいところは出現したときに説明します。
データの読み込み
pandas を用いてデータの読み込みを行います。
# Read data
train_data = pd.read_csv(r"your\path\to\train.csv")
test_data = pd.read_csv(r"your\path\to\test.csv")
for_result_test_data = pd.read_csv(r"your\path\to\test.csv")
train データは訓練用で test データは提出時に必要となるものです。test データは加工時、提出するときに必要な Id が欠損してしまいます。
その対策として for_result_test_data として未加工状態で保有します。
不要なデータ列を削除する
本来はいろいろなデータを見ながら削除する列を選ぶのですが、今回は動かしてみることを前提に考えていたので、適当に決めています。
# 今後の作業を楽にするため関数化して、列の削除を一般化
def drop_columns(df, list_columns=[]):
return df.drop(list_columns, axis =1 )
# 削除したい列名をリストで表記
list_columns_test = ["PassengerId", "Name", "Ticket", "Fare", "Cabin", "Embarked"]
# 関数の呼び出しと引数指定で削除できます。
train_data = drop_columns(train_data, list_columns)
test_data = drop_columns(test_data, list_columns_test)
目的変数の分離
目的変数を分離します。
# 目的変数を別の変数へコピーします。
train_data_y = train_data.Survived
# ここの中身は前述のものと同じです。
list_columns_survived = ["Survived"]
train_data = drop_columns(train_data, list_columns_survived)
質的データを量的データへ変換する
質的データを量的データへ変換する方法はいくつかありますが、今回 0,1 で記述できる性別はラベル化、それ以外は one-hot で対応します。
one-hot ベクトル化
まずは one-hot ベクトル化するのですが、2 つ、3 つと出てきたときにデータ列名が意味不明とならないよう、これも関数化します。
今回 one-hot 化には pandas の get_dummies を用いました。
# dataframeとone-hot化したい列のリストを受け取ります。
def one_hot_vector(df, list_OHV_columns):
# リストでforを回します。
for column_name in list_OHV_columns:
# one-hot化します。columnsでone-hot対象の列を指定、prefixで列名に新しい名前を指定します。
# ここで、既存の列名を含めることによりone-hotベクトル化後もわかりやすくなります。
df = pd.get_dummies(df, columns=[column_name], prefix="oh"+column_name, sparse=True)
return df
# one-hotの対象となる列名をリストで定義します。
one_hot_columns = ["Pclass"]
train_data = one_hot_vector(train_data, one_hot_columns)
test_data = one_hot_vector(test_data, one_hot_columns)
ラベル化
scikit-learn のライブラリを用いて性別を 0, 1 に振り分けます。
sex_le = LabelEncoder()
train_data.Sex = sex_le.fit_transform(train_data.Sex)
test_data.Sex = sex_le.fit_transform(test_data.Sex)
欠損値の計算
年齢には欠損値が含まれています。これがあるとTensorflowで計算ができません。(これに気付くのに時間がかかったのは秘密です。)
したがって、何かしらで埋める必要があるのですが、今回は「エイヤ」なので、平均値で埋めました。
train_data.fillna(train_data.mean(),inplace=True)
test_data.fillna(test_data.mean(),inplace=True)
標準化
今回のデータは年齢だけが標準化の対象となります。
sc = StandardScaler()
train_data.Age = sc.fit_transform(train_data.Age.values.reshape(-1, 1))
test_data.Age = sc.transform(test_data.Age.values.reshape(-1,1))
訓練データの作成
ここまでで殆どのデータ加工が完了しました。訓練するために必要な形へもう少し加工します。
# 値だけの配列へ変換します。
X = train_data.values
y = train_data_y.values
# 訓練データとテストデータを分けます。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=0)
# Tensorflorwで読み込む形へ変換します。
y_train = y_train.reshape(-1,1)
y_test = y_test.reshape(-1,1)
機械学習
さて、ここまで来たらあとはTensorflowでゴリゴリ計算させるだけです。今回はKerasを用いて実行します。
# modelの定義
# kernel_regularizer=regularizers.l2(0.001) は重みの標準化を行います。
# activation sigmoidが2回出てくるのは、コピペミス…だったのですが、最終的に今回はこれが最も良い結果となりました。
# loss function は今回0, 1の結果だけが対象ですので、binary_crossentropyを定義しています。
model = Sequential()
model.add(Dense(100, activation = "relu", input_dim = X_train.shape[1], name = "layer_1", kernel_regularizer=regularizers.l2(0.001)))
model.add(Dropout(0.5))
model.add(Dense(50, activation = "sigmoid", name = "layer_2", kernel_regularizer=regularizers.l2(0.001)))
model.add(Dropout(0.5))
model.add(Dense(25, activation = "sigmoid", name = "layer_3" , kernel_regularizer=regularizers.l2(0.001)))
model.add(Dense(1, activation = "sigmoid", name ="output_layer"))
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=['acc'])
# modelが定義できたらあとは回すだけです。
# epochsは今回2000程度で損失関数の改善が頭打ちになっていたので、2500としています。
model.fit(X_train, y_train, epochs=2500, batch_size=32)
提出用のデータ加工
出てきたデータを最後に加工しなければ提出できません。そのための加工方法を記載します。意外とここまで書いている資料って少ないと思いませんか?と思うのは私だけでしょうか?
# 結果を受け取ります。
# test_dataは列の構成がtrain_dataと同じ必要があります。
# 今回は同時に加工を進めたのでそのまま利用できます。
result = model.predict(test_data.values)
# nd_arrayで結果が得られるため、データをdataframeへ定義し直します。
# 提出時のcsv headerはSurvivedにする必要があるため、ここでも同じ命名を行います。
df_suv = pd.DataFrame(result, columns = ["Survived"])
# 値は0 ~ 1ですので四捨五入することで0, 1へ変換します。
df_suv.Survived = round(df_suv.Survived)
# floatをintへ変換します。
df_suv.Survived = df_suv.Survived.astype('int8')
# id列が今はないので、id列を持つデータと結合します。
df_r = pd.concat([for_result_test_data, df_suv], axis=1)
# 不要な列を削除します。
df_r.drop(columns=[ "Name", "Ticket", "Fare", "Cabin", "Embarked",'Sex', 'Age', 'SibSp', 'Parch', 'Pclass'],inplace =True)
# csvファイルを作成します。indexは不要なのでFalseです。
df_r.to_csv("test_result.csv", index = False)
結果は?
これで結果はこんな感じです。
初めてにしては上出来でしょう。
ただ、他の方のデータを見ていると、前処理に工夫代がありますので次はそちらもチャレンジですね。
何がすごいって、適当にまわしてもここまで精度が出てくるDeep learningがすごいんですね。GBDTも含めてもっと使えるようになりたい。
補足
今回サイズは小さいのですが、計算時間は数分必要となっています。VS CODE上のJupiter Notebookだと落ちました。
Pythonで実行するかColabで実行したほうが幸せになれるかもです。
では良いDeepな世界を!
Discussion