タイタニック号乗客の生存予測モデルを立ててみる

## はじめに
この記事は[Gizumoエンジニア Advent Calendar 2015](http://www.adventar.org/calendars/919)の**24日目**の記事です。

株式会社Gizumoというまだ出来て半年という若い会社でWebアプリエンジニアやってる[@suzumi](https://twitter.com/suzumi49n)です。
アドベントカレンダー２回目の記事になります。
一回目の記事は「[IoT - node.jsを使ってWebからエアコンを遠隔操作できるようにしてみた](http://qiita.com/suzumi/items/43d0f68bfa0b1b34942d)」です。
よければ合わせてご覧下さい。


## お題

まずはじめにKaggleを知っておきましょう。

[Kaggle](https://www.kaggle.com/)とは
> Kaggleは企業や研究者がデータを投稿し、世界中の統計家やデータ分析家がその最適モデルを競い合う、予測モデリング及び分析手法関連プラットフォーム及びその運営会社である。
-- [WikiPedia](https://ja.wikipedia.org/wiki/Kaggle)より

引用にある通り、企業などがデータを投稿して、それに対して分析やモデリングをして最適なモデリングを競い合うサイトです。
データを投稿してコンペを開催する企業側などにとっては、優秀なデータサイエンティストのリクルーティングに使えること、
コンペに参加する人は自分の実力を図ったり勉強のためだったりと、双方にメリットがあるプラットフォームになっています。
またコンペによっては賞金が出たりするものもあり、300万ドル(3.5億円)の賞金が出るコンペもあったりするようです。
[ユーザーランキング](https://www.kaggle.com/users)も公開しており、ここに載れば優秀なデータサイエンティストとして世界中から注目され引く手あまたとなります。
GitHubでのスター数がステータスとなるように、データサイエンティストにとってはランキングがステータスになるんだと思います。

[リクルートが"Kaggle"と日本企業初の共催となるデータ予測コンペティション「RECRUIT Challenge - Coupon Purchase Prediction」開催を決定](http://www.recruit.jp/news_data/release/2015/0716_15946.html)と夏にニュースで話題となり、日本でもKaggleの知名度がドンと増したと思います。
僕もこのニュースでKaggleの存在を知った口です（テヘ）

最近は歩けばどこもかしこも「データサイエンス」やら「機械学習」やら「人工知能」などの単語が聞こえてくるようになりました。
当然流行りもの好きな僕は黙っているわけにはいきません。

ということで今回はKaggleでチュートリアル的なコンペの１つである「[タイタニック号の生存予測](https://www.kaggle.com/c/titanic)」に挑戦してみたいと思います。

ちなみに実行環境は以下の通りです。
数値計算系ライブラリを色々入れるのが面倒くさかったんで[Anaconda](https://www.continuum.io/why-anaconda)入れました。

- Python3.5
- iPython notebook

# データの用意

まずはデータを用意しましょう。まずは[コンペページ](https://www.kaggle.com/c/titanic)に飛びます。
左にあるダッシュボードの「[data](https://www.kaggle.com/c/titanic/data)」からデータをダウンロードします。
とりあえずよくわからないのでそれっぽい以下のcsvをダウンロードしてきます。

- train.csv (59.76 kb)
- test.csv (27.96 kb)

中身を見てみると`train.csv`には約900人分の乗客リスト（生存結果あり）、`test.csv`には約400人の乗客リスト（生存結果不明）になっていますね。

名前から察するにtrain.csvから予測モデルを作り、実際にtest.csvの乗客リストをテストして予測してみる…という流れでしょうか。。
7割くらいのデータから予測モデルを立てて残り3割くらいのデータでテストする、というのはどこかで聞いた覚えがあるのでまさにこれですね。
最初からデータが別れているのはありがたいです。

# ランダムフォレスト

ランダムフォレストを使って予測してねとコンペタイトルにあるのでランダムフォレストを使えばいいんでしょうか。
そもそもランダムフォレストってなんぞや？ということでWikiで調べてみました。

> 2001年に Leo Breiman によって提案された[1]機械学習のアルゴリズムであり、分類、回帰、クラスタリングに用いられる。決定木を弱学習器とする集団学習アルゴリズムであり、この名称は、ランダムサンプリングされたトレーニングデータによって学習した多数の決定木を使用することによる。対象によっては、同じく集団学習を用いるブースティングよりも有効とされる。
-- [WikiPedia](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%83%95%E3%82%A9%E3%83%AC%E3%82%B9%E3%83%88)より


そもそも決定木とは
> 決定木は学習アルゴリズムの１つで、質問してYes、Noで分岐していき、木構造を作成することで完成されます。「完全に答えが出るまで分岐させ、これ以上分岐できないのであれば、そこで終わり」 というアルゴリズムです。
-- [こちらを引用](http://articles.dynaptico.com/RandomForest/RandomForest.html)
![decisiontree.jpg](https://qiita-image-store.s3.amazonaws.com/0/34814/d9bf707a-7f1d-ce0d-f0de-2a5bfedb54db.jpeg)


つまり多数の決定木で集団学習（アンサンブル学習）させることで精度を高める集団学習モデルのことらしいです。
そしていかにして決定木を作っていくことが学習モデルの肝になるようです。

## データを見てみる

train.csvを開いて眺めてみます。
![スクリーンショット 2015-12-20 20.42.57.png](https://qiita-image-store.s3.amazonaws.com/0/34814/2fdd5195-b26d-db0c-0da3-1a064f5e9a91.png)

まずは変数の意味を調べてみました。

- PassengerID:　乗客ID
- Survived: 　　生存結果 (1: 生存, 2: 死亡)　
- Pclass: 　　　乗客の階級 1が一番位が高いそう
- Name: 　　 　乗客の名前
- Sex: 　　　　 性別
- Age: 　　　　 年齢
- SibSp 　　 　 兄弟、配偶者の数。
- Parch 　　　　両親、子供の数。
- Ticket 　　　 チケット番号。
- Fare 　　　　 乗船料金。
- Cabin 　　　　部屋番号
- Embarked 　　 乗船した港　Cherbourg、Queenstown、Southamptonの３種類があります


とりあえずcsvを読み込んでみましょう。
データ加工、集計が得意なpandasを使います。
性別がmale,femaleと扱いづらいので、
male:男性は０,female:女性は１として扱います。

```py3
import pandas as pd
import matplotlib.pyplot as plt

df= pd.read_csv("train.csv").replace("male",0).replace("female",1)
```
### 欠損値の扱い

Ageにはいくつか欠損してるレコードがありますね。
欠損しているところには何かしら値を埋める必要があるそうですが、とりあえず０とかで埋めると後の予測モデルに影響が出ます。
今回の場合はレンジがそんなに広くないので全乗客の年齢の平均値でもいいんですが、こういう場合は中央値を使うのが無難だと思うので欠損しているところは中央値を当てはめるようにします。

```py3
df["Age"].fillna(df.Age.median(), inplace=True)
```

客室のグレードごとにヒストグラムを出力してみます。

```py3
split_data = []
for survived in [0,1]:
    split_data.append(df[df.Survived==survived])

temp = [i["Pclass"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=3)
```
左から一等、二等、三等客室で、緑色が生存した人で青色が死亡した人です。
一等客室の乗客は半分以上が生き残っているようです。
一方で三等客室の乗客は1/5の人しか生き残らなかったようですね。
おそらく一等客室の乗客が優先されて救命ボートに乗ったのだと思います。

![hist_pclass.png](https://qiita-image-store.s3.amazonaws.com/0/34814/0c3822ab-e49c-5297-62d0-b3df509c8ef9.png)

次に年齢ごとにヒストグラムを出してみます。

```py3
temp = [i["Age"].dropna() for i in split_data]
plt.hist(temp, histtype="barstacked", bins=16)
```
![hist_age_1.png](https://qiita-image-store.s3.amazonaws.com/0/34814/bc78e9cd-017a-1091-ebd1-912af539a25a.png)
かなり真ん中だけ突出していますね。。
これは先程欠損値に中央値を代わりに入れてしまったのが原因でした。
年齢が欠損している人は省いてヒストグラムを出力し直してみます。

![hist_age_2.png](https://qiita-image-store.s3.amazonaws.com/0/34814/3934771d-58ad-a04a-6b44-955ff9a04632.png)

いい感じの形になりました。
見てみると意外とお年寄りが亡くなっています。
反対に幼児はかなり生存率が高いですね。
ここから察するに赤ちゃんを連れた人は優先して救命ボートに乗れたようです。


## データ整形

データを見ていて思っていたのが、5人以上などの大家族は生存率が低いということです。
チケット番号を見るとユニークではないらしく番号が被る人たちもいるようです。同じ部屋だったのか、それともまとめて購入すると同じ番号になるのかはわかりません。
例えば「347082」のチケット番号の人たちはみんなファミリーネーム？が「Andersson」と共通です。年齢を見てみるとどうやら7人家族のようです。グレードは３なので下の階層の部屋だったのでしょうか、全員死亡しています。
「家族の人数」用の変数を追加します。そして不要な変数を削除します。

```py3
df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
df2 = df.drop(["Name", "SibSp", "Parch", "Ticket", "Fare", "Cabin", "Embarked"], axis=1)
```
![スクリーンショット 2015-12-24 0.16.00.png](https://qiita-image-store.s3.amazonaws.com/0/34814/0208c349-a0f9-74da-6592-31a0614d9c84.png)

pandasのdataframeそのままではscikit-learnへ型の違いで渡すことができないそうなので型を確認してみます。

```py3
df2.dtypes

# 出力結果
PassengerId      int64
Survived         int64
Pclass           int64
Sex              int64
Age            float64
FamilySize       int64
dtype: object
```

問題なく渡せそうですね。

## 実際に学習させてみる

scikit-learnはpythonのための機械学習ライブラリです。
RandomForestClassifierを使って決定木を作成し予測します。

ただし学習データとして必要なのはPclass以降の変数なので分離します。
PassengerIdはkaggleが勝手に振ったIDなので要りません。
生存結果であるSurvivedは正解データとします。

```py3
train_data = df2.values
xs = train_data[:, 2:] # Pclass以降の変数
y  = train_data[:, 1]  # 正解データ
```

実際に学習させて予測モデルを作ります。
決定木の数は[参考サイト](http://tanajun99.hatenablog.com/entry/2015/06/24/021503)をもとに100に設定しています。
そしてtest.csvの内容もtrain.csvと同様にデータの整形をします。

```py3
from sklearn.ensemble import RandomForestClassifier
forest = RandomForestClassifier(n_estimators = 100)

# 学習
forest = forest.fit(xs, y)

test_df= pd.read_csv("test.csv").replace("male",0).replace("female",1)
# 欠損値の補完
test_df["Age"].fillna(df.Age.median(), inplace=True)
test_df["FamilySize"] = test_df["SibSp"] + test_df["Parch"] + 1
test_df2 = test_df.drop(["Name", "SibSp", "Parch", "Ticket", "Fare", "Cabin", "Embarked"], axis=1)
```
![スクリーンショット 2015-12-24 1.57.08.png](https://qiita-image-store.s3.amazonaws.com/0/34814/b3a259fa-5976-dba9-1fd9-b59d0dc3a8a2.png)

予測モデルをもとに実際に予測してもらいます。


```py3
test_data = test_df2.values
xs_test = test_data[:, 1:]
output = forest.predict(xs_test)

print(len(test_data[:,0]), len(output))
zip_data = zip(test_data[:,0].astype(int), output.astype(int))
predict_data = list(zip_data)
```
predict_dataの中身を見ると予測してくれた結果がリストになっています。
![スクリーンショット 2015-12-24 2.00.44.png](https://qiita-image-store.s3.amazonaws.com/0/34814/f18e58d7-6e31-48b7-088e-25e47119d6df.png)

そして最後にリストをcsvに書き込みましょう。
カレントディレクトリにpredict_result_data.csvが出来ているはずです。

```py3
import csv
with open("predict_result_data.csv", "w") as f:
    writer = csv.writer(f, lineterminator='\n')
    writer.writerow(["PassengerId", "Survived"])
    for pid, survived in zip(test_data[:,0].astype(int), output.astype(int)):
        writer.writerow([pid, survived])
```

## KaggleにSubmitする

予測モデルを立てて実際に生存予測をしたcsvができました。
これをKaggleに送りつけましょう。
タイタニックのコンペページにいって左カラムにある「My Submissions」→「Make a submission」からアップロードして送りつけます。
すると最終的にスコアが表示されてランキングにのります。

![スクリーンショット 2015-12-24 1.42.14.png](https://qiita-image-store.s3.amazonaws.com/0/34814/ff798762-34b2-ae7b-aee6-5de55bf90125.png)

スコアが0.69856でした。
ベースラインのスコアが0.76555なので足りませんね。。笑

今回は予測モデルを立ててKaggleに送りつけるところまでがミッションだったので、まぁよしとしましょう。。

## まとめ
僕は最初は機械学習系アルゴリズムの本を買って読んだりしていましたが、まずはライブラリを使って試してみることが一番いいのかなと感じました。
Kaggleという素敵なサイトがあるのでここで腕試しや、他人の書いたスクリプトを見て研究したりしてみるのもいいのかもしれませんね。
ぜひ機械学習ライフを始めてみてはいかがでしょうか。


## 参考サイト

[kaggle2回目　タイタニック号の生存者予測](http://ksknw.hatenablog.com/entry/2015/11/29/181523)
[kaggle初挑戦: タイタニック乗客のプロフィールから生存率をランダムフォレストで予測してみた](http://shinya131-note.hatenablog.jp/entry/2015/07/12/153538)
[機械学習によるタイタニック号の生存者予測 with Python](http://tanajun99.hatenablog.com/entry/2015/06/24/020007)