Titanicの分析にチャレンジした記録のまとめ

#経緯とチーム初回MT

会社の同期を誘ってKaggleのコンペに初挑戦する会を開催した。画像処理に少し興味がある4人チームで取り組むことが決まったが、全員コンペ参加初めてのデータ分析ほぼ初心者だったので、まずは個々人で入門編と名高いTitanicデータの分析に挑戦してみようという話になった。初回のミーティングで決まったのは大体こんな感じで、次回のMTでは中間報告としてデータの内容を各自調べて話し合うことを決め解散した。

#個人作業：データを調べる

まずは個人での調査になったので、この節では自分でやってみたデータの分析結果を記録しておく。

まずはKaggleのサイトからデータをダウンロードし、コンペの内容や簡単なデータの概要を確認した。気づいたことは以下の点である。

- 用意されたデータファイルはtrain.csvとtest.csvの2つで、サイズはそれぞれ(891, 12), (418, 11)である
- 予測するのはSurvived列(0 or 1)で、0か1の二値分類の問題である
- 評価は正解率にて行われる
- train.csvにあるSurvived以外のカラムは以下の11個である

| カラム名 | 内容 |
|:-:|:-:|
| PassengerId | int型。主キー。 |
| Pclass | int型。1,2,3のいずれかの値を取る。チケットのランクを指している。 |
| Name | object型。チケット番号などから察するにファミリーネーム, 敬称, ファーストネームの順に入っているようだが、文末に()で追加情報が入っていたり、形式は様々のように見えた。 |
| Sex | object型。maleかfemaleのどちらかの値をとる。 |
| Age | float型。年齢が入っている。整数値が多そうだが、一歳未満や年齢が推定値の場合には小数表記になっている。また、891行中714行にしかデータが入ってない(欠損値がある)。 |
| SibSp | int型。一緒に乗った兄弟や配偶者の数が入っている。0か1が多く、最大で8。 |
| Parch | int型。一緒に乗った親や子の数が入っている。0か1が多く、最大で6。 |
| Ticket | object型。チケット番号が入っている。数字のみだったり、英数字表記だったり、形式は様々のように見えた。 |
| Fare | float型。料金を指す。0が最小、512が最大。テストデータには1件欠損値がある。 |
| Cabin | object型。部屋番号を指す。英字+数字で入っており、複数部屋表記もある。また、891行中204行にしかデータが入ってない(欠損値がある)。 |
| Embarked | object型。乗船地を指す。C, Q, Sのいずれかが入っており、最頻値はSである。891行中889行にしかデータが入ってない(欠損値がある)。 |

ここから気になった点を調査して、データの前処理について次のことを考えた。

- PassengerIdは分析には関係なさそうなので使わない
- Survivedは0が549行、1が342行だったので少し偏りがあるため、学習時には同割合で学習できるようにしたい
- 3つの列で欠損値が確認できたのでそれぞれ扱いを考えたい
    - Age列は平均値がすぐに思いつく埋め方である。お金持ちの人のほうが年齢が高そうなので、Pclass列とSurvived列ごとの平均値で埋めていくのがいいかもしれない
    - Cabin列は全体の1/4しかデータが入っていないので埋めるのは難しそう。ただ、特徴量がそんなに多くないのであまり減らしたくはない
    - Embarked列は2行のみデータがない。どちらも同じチケット番号で家族のようだが、ほかに同じチケット番号の人はいなかった。Sに偏りがあるデータなので最頻値で埋めてもいいかもれない
- Name列、Ticket列、Cabin列は形式がばらばらだし、情報として予測にはあまり使えないかもしれない
- SibSp列とParch列についてはあまり分ける意味を感じない。合算したものでもいいかもしれない
- Sex列やEmbarked列はエンコーディングが必要

#第2回MT

個人での作業を終え、第2回MTで各自気づいたことをまとめ共有した。Titanicはあまりに有名な問題なため、いろいろなサイトや本で分析が紹介されており、それらの情報共有も行った。自分があげた上記の意見に加えて、メンバーから次のような意見をあげてもらった。

- Name列については敬称を抜き出すとある程度まとまったデータになるし、予測結果にも聞いてきそうなデータだった
- Cabin列は「データがない」ことに意味を持たせるのがいい
- Ticket列は数字の部分だけ取り出してみるという方法もある
- 最初から情報を落とすのではなく、LightGBMやRandomForestを使って特徴量の重要度を選別するのがいい。これだと今後別コンペで匿名データになっても太刀打ちできる

メンバーのみなさんは情報を落とさない工夫をいろいろあげてくれて本当にすごいと思った。尊敬である。

またメンバーの一人が実際に提出を行ってみたとのことで、提出の方法の共有をしてくれた。

これらの共有をもとに各自でモデル作成を行い、その方法と結果を次回MTで共有することに決め、第2回MTは終了になった。
個人的にはデータ処理についてとても参考になったのでありがたい中間共有の場だった。

#個人作業：モデルを作成して予測する

第2回MTを踏まえて、最終的に次のようなデータの処理を行った

- PassengerId列は特徴量として使用しない
- Name列からは敬称のみを抜き出したPrefix列(ダミー変数化)と、名前の表記がしっかりしている人や情報が多い人は生存者ではないかという考えで、名前の長さを格納したName_len列を作成した
- Sex列はダミー変数化した
- Age列はPclass列とSurvived列ごとの平均値で欠損値を埋めた
- SibSp列とParch列の和をとったFamilysize列を作成した
- Ticket列からは数字部分を抜き出した。LINEなど英字のみの人は0を入れた
- Cabin列はまず頭文字の英字のみを抜き出して、Aから順に7, 6, …と数字の対応をつけて変換した。欠損値は0で埋めた
- Embarked列の欠損値は最頻値で埋め、ダミー変数化した
- のちの学習のため、Age列、Fare列、数字部分のみにしたTicket列は標準化した

次にモデル作成に移った。ちょうどアンサンブル学習について学んでいたので、以下の３つのモデルを作成して予測値を提出した。提出したスコアは以下の通りだった。

| model | score |
|:-:|:-:|
| LightGBM | 0.75119 |
| Bagging | 0.77751 |
| Stacking | 0.77272 |

なお、パラメータなどの調整は手動でいくつか試したくらいであり、最適化まではやらなかった。

#第3回MT(LAST)

最終的なMTの場で各自のモデルの共有を行った。アンサンブル学習を用いていたのは全員共通していたが、LightGBMではなくXGboostを使用していたり、欠損値の埋め方や特徴量の選択の仕方が違っていたり、全員取り組み方が違っていてどれも勉強になった。MTの中で疑問に思った点は以下の通りである。

- XGBoostとLightGBMの違い(同じブースティングモデルだが、何に優位があるのか)
- LightGBMもXGBoostも欠損値の処理をしなくても学習や予測ができるが、中でどういう扱いをされているのか
- 特徴量を線形回帰を使って予測したりすると多重共線性の問題が発生しそうだが、どのくらい影響がでるのか
- テストデータの欠損値の穴埋めの方法(そもそも穴埋めの時は訓練データのみ使うべきなのか)

前半はモデルへの理解を深めることで、また後半は数学的知識で解決できそうなので今後調べていきたいと思った。また、自分のモデルで他のメンバーの手法を取り入れたらどうなるかが気になったので、試してみたい。

また個人的な感想として、自分が全然勉強の及んでいない部分でも知識の共有ができたり、思いつかなかった特徴量エンジニアリングをしていたり、分かっていたつもりでも人に説明しようとすると上手に伝えられなかったり、一人だと得られないことがたくさんあったのでチームで取り組めてとてもよかったと感じた。(他のメンバーや別チームの方もそう捉えていただいてるといいな...)