こちらの記事にも書いたとおり、昨年末から Kaggle のコンペに参加し始めました。
しかし、なんとなく EDA (Explanatory Data Analysis: 探索的データ解析) を行うものの次第に発散していってしまうことがよくあります。
そこで、基本的な EDA を行ったうえで、最初の prediction を提出するまで流れをある程度形式化できないかを考えてみました。
形式化した手順をこの記事では、「フレームワーク」と呼ぶことにします。
作成したフレームワークを使って Kaggle の Titanic コンペ で prediction の作成までを行うコードを実装します。
参考情報
フレームワークを作るに当たっては、以下の Kernel を考え方、コードともにかなり参考にさせてもらいました。
Titanic コンペ特有の特徴量生成に関して、下記の Kernel も大変参考にさせていただきました。
成果物
今回作った Titanic コンペ向けの notebook は下記のリポジトリにあげています。
フレームワーク
フレームワークの全体は下記のような流れになります。
- Step1: ライブラリロード
- 必要なライブラリのロード
- Step2: データロード
- データのロード
- Step3: EDA (Explanatory Data Analysis: 探索的データ解析)
- 後続の「前処理 & 特徴生成」のための EDA
- Step4: 前処理 & 特徴生成
- EDA の結果を考慮して前処理と特徴生成
- Step5: 特徴量選択
- 有用な特徴量を選択
- Step6: モデルと評価
- 様々なモデルの構築と有用なモデルの選択
- Step7: ハイパーパラメーターのチューニング
- 選択したモデルのハイパーパラメーターのチューニング
- Step8: アンサンブル
- 複数のモデルの予想結果のアンサンブル
これ以降は、各ステップについて詳しく説明してゆきます。
この流れに沿って、Titanic コンペのデータを分析した notebook を下記にアップしていますので、ぜひ一緒にご覧ください。
(というより、コードと一緒に見ないとわかりにくい点が多々あると思います。)
Step1: ライブラリロード
まずは、必要なライブラリをロードします。詳細な説明は不要かと思います。
Step2: データロード
データをロードします。こちらも詳細な説明は不要かと思います。
Step3: EDA (Explanatory Data Analysis: 探索的データ解析)
このステップでは、下記のような点を確認していきます。
各カラムについて下記を確認
- ユニークな値の数
- 欠損値の確認
カテゴリーデータについて下記を確認
- 各値ごとの目的値の平均
数値データについて下記を確認
- 分布
- 外れ値
その他、テキストデータなどから何かしら情報が取れないかを確認
Titanic コンペのデータをこの手順に従って分析すると、データに以下のような特徴があることがわかります。
各カラムの種類
- PassengerId: 識別子
- Pclass, Sex: カテゴリーデータ
- Age, SibSp, Parch, Fare: 数値データ
- Name, Ticket, Cabin: テキストかつユニークな値の数が多いためそのままでは特徴量にしづらい
欠損値が含まれるカラム
- Age
- Cabin
- Embarked
- Fare
外れ値、異常値の有無
- 明らかな外れ値、異常値はないように見える
- ただし、Fare は、中央値 14 に対して、最大値 512 と非対称な分布なので注意が必要
個別のカラムについて
- Name から敬称が取れる
- 敬称には、性別や既婚かどうかなどの情報が含まれており、役に立つ可能性がある
- ただし、Major, Capt, Don などあまり含まれない敬称がノイズとなる可能性がある
- Ticket から家族を判別できる可能性がある
- Cabin から船内の階層を特定できる
- Name から敬称が取れる
このように得られた分析結果をもとに次ステップに進みます。
Step4: 前処理 & 特徴生成
このステップでは、下記のようなことを行っていきます。
以下、それぞれの項目についての補足です。
2. 欠損値の補完:
基本的に、数値であれば中央値か平均。カテゴリーであれば最頻値で埋めるのが良いかと思います。
3. EDA 結果からの特徴量生成:
前ステップの EDA 結果を使って特徴量を作っていきます。
特徴量は多すぎてもあまり良いことはないですが、私はこの段階ではあまり気にせず思いついたものを全部作ってしまうようにしています。
その上で、後のステップで特徴量選択をして、有用な特徴量のみを残すようにしています。
4. カテゴリーエンコード:
この段階では、カテゴリーの値の種類が多くても対応できる mean encoding、Label encoding を使うことが多いです。
One-Hot encoding の場合、特徴量がどのくらい結果に寄与したかが解釈しづらくなってしまうため、この段階ではあまり使いません。
また、ユニークな値の数が多い場合は、頻度の低いカテゴリーをまとめてしまうなどを行っておく。
Titanic コンペのデータでも基本的には上記のステップに沿って処理を行っていきますが、特に以下の特徴量は EDA の結果を考慮した Titanic コンペに特有のものになるかと思います。
- Title:Name から敬称を抜き出した値
- Family_Survival:Name, Ticket などを使って同一家族内での生存者数を推測した値
- FareBin:Fare を離散化した値
- CabinLayer:Cabin から船内の階層を抜き出した値
Step5: 特徴量選択
重要そうな特徴量にあたりをつけます。
RandomForest や RFE (Recursive Feature Elimination; 再帰的特徴量削減) からあたりをつけます。
Titanic コンペのデータでは、このステップの結果から下記のカラムが有用そうであることがわかります。
- Age:年齢
- Fare:料金
- Family_Survival:生存した家族数
- CabinLayer_Code:客室の階層をラベルエンコードした値
- Title_Code_mean_Survived:敬称を mean エンコードした値
- Pclass_mean_Survived:Pclass を mean エンコードした値
- Sex_Code_mean_Survived:性別を mean エンコードした値
Step6: モデルと評価
様々なモデルを評価して、有用なモデルにあたりをつけます。
なるべく多様なモデルで、それぞれの性能をとりあえずデフォルトのハイパーパラメーターを使って測定します。
多様なモデルを使うことが望ましいですが、学習に時間がかかることやハイパーパラメーターに気を使う必要があることから、ニューラルネットは外しています。
また、(私のスキルでは) ニューラルネットの学習結果を解釈するのが難しく、仮にニューラルネットで高い性能を出したとしてもそこから次の分析に活かしづらいことも除外した理由です。
Titanic コンペのデータでは、このステップの結果からは下記のモデルが有効なようです。
- GaussianProcessClassifier
- SVC
- XGBClassifier
Step7: ハイパーパラメーターのチューニング
各モデルに対してハイパーパラメーターのチューニングを行います。
Step6 の結果とモデルの多様性を考慮して最適パラメーターを探索するモデルを決定します。
それらのモデルに対して最適なパラメーターを求めます。
Step8: アンサンブル
ニューラルネットによる Stacking によってアンサンブルを行っています。
データに対する制約が少ないためニューラルネットを使うようにしていますが、決定木ベースの手法のほうが結果の解釈はしやすいかもしれません。
まとめ
いかがだったでしょうか。参考情報に上げた Kernel のおかげもあり、思ったよりも汎用的なフレームワークができたのではないかと満足しています。
このフレームワークに従えば、あまり悩むことなく、とりあえず最初の prediction までは提出できそうかなと思います。
ただ、もちろん改善点もあって、特に下記の点は気になっています。
(1) それぞれの各モデルの学習とアンサンブルで同じ学習データを使っている
Titanic の場合データ数が少ないためこうした
(2) Step5〜7 は、本来同時に行ったほうが良い
つまり各モデルに対して、最適な特徴量を選択し、最適なハイパーパラメーターを設定した上で、モデルの性能を比較するべきだと思います。
しかし、今回のフレームワークでは、下記のようにしています。
- Step5 でモデルに関係なく特徴量を決定
- Step6 で前ステップで決定した特徴量を使って、モデルを選択
- Step7 で前ステップで決定した特徴量とモデルを使って、ハイパーパラメーターを決定
このような単純化のおかげで全体の実行時間はかなり抑えられていますが、ハイパーパラメーターによって大きく性能が大きく影響を受けるニューラルネットや GDBT などのモデルでは正確な比較がしづらいという欠点にもなっています。
これらの点については、実際にいろんなデータでこのフレームワークを使っていきながら改良していこうと思います。
最後に、Titanic コンペについても少し書きたいと思います。
このフレームワークを使って Titanic コンペの予想をすると acc = 0.78468 という結果でした。
この結果が良いのか悪いのかは悩ましいところです。
おそらくなんですが、Titanic のデータは以下の理由により複雑なモデルを作ってもあまり精度が上がらないのではないかと予想しています。
- 訓練セット、テストセット合わせて 1300 ほどのレコードしかない
- 常識的に考えて、含まれているカラム以外にも生死を分けるのに重要な情報がありそう
例えば、事故発生以降に船内のどこにいたかなんていう情報も重要そうですがそういったデータはテストセットに含まれていません。 - 自分の環境で行った検証と Leaderbord のスコアに開きがある
交差検証も行わず完全に訓練セットと検証セットを分けた場合でも、やはり検証セットでの評価結果と Leaderbord の結果に開きが生じてしまいます。
データ数が少ないことも関係しているかもしれませんが、多少訓練データとテストデータで分布が異なっているのではないかと予想しています。
実際に、同じデータをつかって max_depth=3 に制限した RandomForest 単独でのスコアが acc = 0.81339 であり、今回のフレームワークの結果を上回ってしまいました。
Leaderbord では acc = 1.0 な方も結構いらっしゃるんですが、たぶん別途入手した生存者リストを使っているのかなと思っています。。。(おそらく)