これで無理なら諦めて!世界一やさしいデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

【Pythonで決定木 & Random Forest】タイタニックの生存者データを分析してみた

前回まで、決定木・ランダムフォレストの理論について勉強しました。
www.randpy.tokyo
www.randpy.tokyo

今回はPythonで実際に動かしていきたいと思います。扱うのは、タイタニック号の生存者データです。性別や年齢など、どんな要素が生存率に影響を与えていたのか、分析してみます。

これは、kaggleという世界的なデータ分析コンペティションで提供されているサンプルデータですので、ご存知の方も多く少し面白みには欠けますが、決定木とランダムフォレストの比較をするのにはちょうどいいので使っていきます。


以下、kaggleで開催されているコンペ一覧になりますが、この中にタイタニックのコンペ(チュートリアル)もあるので、そこで今回扱うデータ・セットをダウンロードすることができます。
Competitions | Kaggle

さて、ランダムフォレストは多くの木を作り、多数決をとることによって、モデルの精度・汎化性能を上げているんでしたね。
ランダムフォレストと決定木の精度に違いがでるのかという部分に注目して、行ってみましょう!

今回の流れは以下のような感じで進めていきます。

  • 決定木(精度、木の可視化)
  • ランダムフォレスト(精度、重要度のプロット)
  • 番外編(kaggle式の前処理をした上での精度比較)

データの傍観

さっそくデータをimportしてみましょう。

import pandas as pd
df = pd.read_csv('~/train.csv')

データの中身はこのようになっています。
f:id:gl2000-sans:20171024191935p:plain
f:id:gl2000-sans:20171024192437p:plain
(https://www.kaggle.com/c/titanic/dataより参照)

チケットのクラスや性別、年齢、運賃など様々なデータが入っていますね。
タイタニックの映画の中で、上流階級や女性の方が優先して救命ボートに乗っている描写がありましたが、データ的にはどうなんでしょうか。

試しに、男女の生存者を比較してみましょう。

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
sns.countplot('Sex',hue='Survived',data=df)

f:id:gl2000-sans:20171024194229p:plain
確かに生存者数、生存比率を見ても女性のほうが高そうです。
タイタニックデータは、傍観するだけでもとても面白いので、興味ある方は色々いじってみてください。

さて、データを分析するために色々加工していきます。

from sklearn.model_selection import train_test_split
#欠損値処理
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
df['Age'] = df['Age'].fillna(df['Age'].median())
df['Embarked'] = df['Embarked'].fillna('S')

#カテゴリ変数の変換
df['Sex'] = df['Sex'].apply(lambda x: 1 if x == 'male' else 0)
df['Embarked'] = df['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2} ).astype(int)

df = df.drop(['Cabin','Name','PassengerId','Ticket'],axis=1)
train_X = df.drop('Survived', axis=1)
train_y = df.Survived
(train_X, test_X ,train_y, test_y) = train_test_split(train_X, train_y, test_size = 0.3, random_state = 666)

欠損値処理(とりあえず今回は平均値を代入)と、カテゴリカル変数を数値に変換する処理を施しています。
(欠損値処理に関しては、色々な手法があるので、また別の機会に紹介できればと)

最後の行のところで、train_test_splitというライブラリを用いて、学習データと検証データに分割してあげます。
random_stateでseedを固定しないと毎回学習データと検証データが変わってしまうので、固定しときましょう。

さて、分析の前処理(かなり適当ですが…)は終わったので、次項から分析に移っていきます!!

決定木

scikit-learnの中のライブラリtreeを使っていきます。

from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(random_state=0)
clf = clf.fit(train_X, train_y)
pred = clf.predict(test_X)

scikit-learnのお馴染みの流れになります。(モデルを定義して、fitで学習)
設定できるパラメータは、例えば以下の様なものです。

  • criterion : 分割基準。gini or entropyを選択。(デフォルトでジニ係数)
  • max_depth : 木の深さ。木が深くなるほど過学習し易いので、適当なしきい値を設定してあげる。
  • max_features:最適な分割をする際の特徴量の数
  • min_samples_split:分岐する際のサンプル数
  • random_state:ランダムseedの設定。seedを設定しないと、毎回モデル結果が変わるので注意。

上記以外にも設定できるパラメータがあるので、詳細については、以下公式ドキュメントをご参照ください。
sklearn.tree.DecisionTreeClassifier — scikit-learn 0.19.1 documentation

学習が無事にできたかと思うので、検証データを使って精度を見ていきましょう。

from sklearn.metrics import (roc_curve, auc, accuracy_score)

pred = clf.predict(test_X)
fpr, tpr, thresholds = roc_curve(test_y, pred, pos_label=1)
auc(fpr, tpr)
accuracy_score(pred, test_y)

sklearn.metricのaucとaccuracy_scoreを使って評価していきます。

AUCは、機械学習でよく使われるモデルの評価指標で、1に近づくほど精度が高いです。ROC曲線やAUCについては、以下の記事でまとめてあるので是非ご覧ください。
www.randpy.tokyo

accuracy_scoreでは、単純な正解率を計算することができます。
計算してみると、AUC:0.784, 正解率:0.795という感じです。

木構造の可視化

決定木を分析したからには、木の構造を可視化してみましょう。
pythonで可視化するために、以下2つのライブラリをインストールしといてください。

pip install pydotplus
brew install graphviz

以下が可視化するためのコードになります。

#可視化
import pydotplus
from IPython.display import Image
from graphviz import Digraph
from sklearn.externals.six import StringIO

dot_data = StringIO()
tree.export_graphviz(clf, out_file=dot_data,feature_names=train_X.columns, max_depth=3)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_pdf("graph.pdf")
Image(graph.create_png())

可視化した図はpdfにして落とす事もできますし、jupyter上で表示することも可能です。

先程学習したモデルを可視化したものが以下の図になります。(max_depth=3で制限)
f:id:gl2000-sans:20171025155102p:plain
この図を見ると、女性でチケットクラスが2.5より下(上流階級)であると、生存者が多く、男性でチケットクラスが1.5よりも大きい(下流階級)だと死亡者が多いというなモデルの解釈ができます。

ランダムフォレスト

scikit-learnのensembleの中のrandom forest classfierを使っていきます。
ちなみに、回帰で使用する場合は、regressionを選択してください。

以下がモデルの学習を行うコードになります。

#ランダムフォレスト
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(random_state=0)
clf = clf.fit(train_X, train_y)
pred = clf.predict(test_X)
fpr, tpr, thresholds = roc_curve(test_y, pred, pos_label=1)
auc(fpr, tpr)
accuracy_score(pred, test_y)

モデルのパラメータとしては、例えば以下のようなものが設定できます。

  • n_estimators:木をいくつ生成するか。デフォルトでは10。
  • max_depth:木の深さの設定
  • max_features:分岐に用いる説明変数の数を設定
  • min_sample_split:分割する際の最小のサンプル数を設定
  • random_state:seedの設定。seedを設定しないとモデルが毎回変わるので注意。

こちらも決定木同様、上記以外にも多くのパラメータを設定することができます。
3.2.4.3.1. sklearn.ensemble.RandomForestClassifier — scikit-learn 0.19.1 documentation

精度を算出してみると、
AUC:0.784, 正解率:0.802という結果になりました。

先程の決定木の精度が、AUC:0.784,正解率:0.795でしたので、ほぼほぼ変わらないですね…。
今回、デフォルトのパラメータで学習したので、チューニングを行えば、精度自体はもう少し上がるかと思います。
(ちなみに、パラメータ:n_estimatorsを10→20に変更したとき、AUC:0.804,正解率:0.821)

変数重要度の可視化

ランダムフォレストでは、どの変数が重要であったかをfeature_importancesというメソッドを使うことで出すことができます。
それを図にプロットするコードが以下になります。

import matplotlib.pyplot as plt
%matplotlib inline

features = train_X.columns
importances = clf.feature_importances_
indices = np.argsort(importances)

plt.figure(figsize=(6,6))
plt.barh(range(len(indices)), importance[indices], color='b', align='center')
plt.yticks(range(len(indices)), features[indices])
plt.show()

f:id:gl2000-sans:20171026144121p:plain

年齢・運賃・性別が大きく効いていることがわかります。
決定木・ランダムフォレストともに、分析結果を可視化できる点が本当にいいですね。

番外編

kaggle式のデータ前処理を施したら、精度がどう変わるのかということを検証していきたいと思います。
参考にしたコードは以下のものです。
Titanic best working Classifier | Kaggle
コードは、上記URLに飛んで頂ければ参照できるので、割愛させていただき、データ前処理をしたデータ・セットが以下になります。
f:id:gl2000-sans:20171025162507p:plain
独り身かどうか、あるいは、Mr,Missといった名前の情報を使って新たに変数を作成していたりと、中々きめ細かい前処理を行っているようです。

それでは、こちらのデータを使ってもう一度決定木とランダムフォレストを行いたいと思います。
コード自体は先程書いたものと同じなので、結果だけ書かせて頂くと、

  • 決定木:AUC:0.814, 正解率:0.832
  • ランダムフォレスト:AUC:0.810, 正解率:0.821

決定木、ランダムフォレストともに、先程のモデルよりも精度が上がってますね。
如何にデータの前処理が大事かということがわかりました。

終わりに

タイタニックデータを使って、決定木とランダムフォレストの精度比較を行いました。
今回デフォルトのパラメータを使ったモデルで比較したので、それほど精度自体は変わりませんでしたが、ごりごりにパラメータチューニングした時の比較もいずれやってみたいなと思います。

また、番外編でkaggle式の前処理を施した上で分析してみましたが、まぁデータ前処理がほんと大事ということが分かりましたね!

さて、次回はRでの決定木・ランダムフォレストの分析記事を公開する予定ですので、お楽しみにー!!
(そろそろstanの記事もあげようかと密かに企んでおります)