😸

KaggleのTitanicをTensorflowで解いてみる

2022/09/23に公開

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