Titanicから始めよう:特徴量エンジニアリングのまねごとをしてみた:僕たちのKaggle挑戦記
前回に引き続きEDAと特徴量エンジニアリングを行って、今回は最後にできあがったデータフレームで学習をしてみましょう。目指せ! スコアアップ!
こんにちは。Deep Insider編集部のかわさきです。冬至も過ぎて、これからはもう毎日昼間の時間が伸びて春になるのを待つばかりになりましたね。と思う冬至の翌日の朝6時です(「こんにちは」なのか「おはようございます」なのかはともかく眠いです)。
そんな人生における些細(ささい)なことはさておいて、前回は、EDA(探索的データ解析)のまねごとをしてみたところ、ディープニューラルネットワーク(DNN)に何も考えずにデータを突っ込むよりもよいスコアが出てしまったというお話をしました。今回は、その続きとなりますが、CSVからpandasのデータフレームに読み込んだデータに手を加えて、独自の特徴量を作ってみることにします。いわば特徴量エンジニアリングのまねごとをしてみようということです。
前回はデータの特性を見ながら、手作業でCSVを作成しましたが、今回は元のデータフレームを加工した結果をディープニューラルネットワークに食わせて、タイタニック号の乗客の生死を推測します。いいスコアが出るといいですねぇ(前振り)。
前回はEDA(のまねごと)をしたわけですが、その際に筆者自身はおおよそ次のような印象を持ちました。
- 性別(Sex)と生死の関連は強い
- チケットクラス(Pclass)と生死の関連もそれなりにありそう(高:生存←→死亡:低。ヒートマップでは負の相関として表出)
- 年齢(Age)は生死と相関がありそうと思ったけれど、ヒートマップではそれほど強く出ていない
- 旅客運賃(Fare)は生死との関連がそれなりにありそう
チケットクラスの負の相関については見落としていたのを、後から一色さんに指摘されたわけではありますが。[かわさき]
負の相関は、意外に見落としやすい注意ポイントなのかもしれませんね。[いっしき]
また前回は親族(兄弟、配偶者、子ども、親)の存在やどこで乗船したかなどの情報については取り扱っていませんでした(データフレームには情報としては含まれていましたが)。今回はこれらの情報も使って、元のデータフレームを加工していきます。
one-hotエンコーディングしてみる
Titanicコンペのデータには、乗客の性別や乗船した港などの情報が含まれています。例えば、乗船した港であれば'Q'と'S'と'C'で各港を示していますが、DNNモデルに入力できるのは数値データなので、これらを数値に変換してやる必要があります。前回は'S'を0に、'C'を1に、'Q'を2にしましたが、これらは別々の変数として表現することもできます。つまり、「Embarked_S」「Embarked_C」「Embarked_Q」という3つの変数を新たに用意し、乗船した港に応じてこれらの変数のいずれかを1にして、他は0にするという表現方法です。
1つの変数を使い「0〜2」という数値で表していたものを、3つの変数を使い「1, 0, 0」「0, 1, 0」「0, 0, 1」という数値群で表すようにするということです。このような表現にすること、あるいはそのような表現のことを「one-hotエンコーディング」と呼びます。pandasではget_dummies関数を使うことで、簡単にone-hotエンコーディングを行えます。
dftmp = pd.get_dummies(data=df0, columns=['Sex', 'Embarked'])
_, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(dftmp.corr(), annot=True, ax=ax)
上のコードではSex列とEmbarked列の値(カテゴリ変数)をone-hotエンコーディングして、その値を含んだデータフレームのヒートマップを表示するものです。
作ってみたのはよかったのですが、よく考えたらSex_femaleとSex_maleは、どちらかが1なら、もう一方は0になるに決まっています。これは典型的な負の相関といえます(実際、上の画像ではSex_femaleとSex_maleが交差する箇所の値が-1になっていますね)。ということは、これらは一方があるだけで十分だったということです。Embarked列をone-hotエンコーディングした結果にも同様なことがいえます(つまり、3つのうちのいずれか2つが0であれば、残る1つが1となるのは分かりきっているので、2つあれば十分でしょう。実際、上の図の右下は黒くなっている、つまり負の相関が固まっているように見えます)。
また、無駄なことをしてしまった……。
そのため、実際には以下のようにget_dummies関数のdrop_firstキーワード引数にTrueを指定して、最初の要素をドロップするようにしました(コード内のdf1は訓練用のデータフレームで、dftはテスト用のデータフレームで、ここでは両方を同時に同じように加工しています)。
df1 = pd.get_dummies(data=df0, columns=['Sex', 'Embarked'], drop_first=True)
dft = pd.get_dummies(data=dft, columns=['Sex', 'Embarked'], drop_first=True)
all_df = [df1, dft]
one-hotエンコーディングは、線形回帰(重回帰分析)のときに「多重共線性」という問題を起こす可能性があるらしいから、このように生成した1要素をドロップするのは、場合によって大切みたいですね。そのための機能が用意されているget_dummies関数は便利ですね。
この状態でヒートマップを表示すると、以下のようになりました。
うわっ。ずいぶんと黒くなったなぁと思ったら、one-hotエンコーディングでSex列の代わりにSex_male列ができて、それが男性なら1、女性なら0となるようになったので、女性の生存率の方が高いという事実と負の相関を持つためでしょう。
Emarked_QとEmbarked_Sとの間にも負の相関があるので、これもどちらか一方があればよいのかもしれません。というか、これならone-hotエンコーディングしないで、2つの列は普通に文字列を単一数値としてエンコーディング(0/1または0/1/2で表現)するので十分だったんじゃないかと悩んだ揚げ句、単純にEmarbked_Q列も削除することにしました。
この時点でのヒートマップは次の通りです。
複数の特徴量から1つの特徴量を作ってみる
上のヒートマップの中央を見ると、SibSpとParchには相関がありそうです。前者は兄弟や配偶者の数を、後者は子どもや親の数を表しています。ここではこれらを足し合わせて、FamilySize(家族の数)という新たな特徴量を導入してみましょう(毎回同じコードを2行書くのがイヤになったので、forループを回すようにしていますね)。
all_df = [df1, dft]
for df in all_df:
df['FamilySize'] = df['SibSp'] + df['Parch']
sns.pointplot(x='FamilySize', y='Survived', data=df1)
この結果は以下の通りです。
家族の数が0だと死にやすく、1〜3人は生存しやすく、それより多ければ死にやすいという傾向がありそうです。
これは面白い結果が出ましたね!(驚き)
ビニングしてみる
Copyright© Digital Advantage Corp. All Rights Reserved.