絶賛機械学習お勉強中につき、初心者向けでデータが扱いやすいタイタニックコンペで訓練しています。
今回は決定木とランダムフォレストのお勉強をしたので、Kaggleで実践してみて試行錯誤した経過を記録しようと思います。
おおまかな流れとしては
前処理
→決定木モデルを作成
→RFのfeature_importances_で有効な特徴量を絞り込み
→絞り込んだ特徴量のみで決定木
です。
そのあとのハイパーパラメータチューニングについてはその2として、続きを別記事に書こうと思います。
では、EDAはすっ飛ばして、データの前処理からやっていこうと思います。
ライブラリのインポート
今回使うライブラリを最初にまとめてインポートしておきます。
データも読み込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import seaborn as sns from sklearn.preprocessing import LabelEncoder from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import confusion_matrix from sklearn.metrics import accuracy_score from dtreeviz.trees import dtreeviz |
1 2 |
train = pd.read_csv("train.csv") test = pd.read_csv("test.csv") |
前処理
データの前処理は最低限の2つ、欠損値埋めと、カテゴリ変数変換だけやっておきます。
の前に、[PassengerId , Name , Ticket , Cabin]については予測に必要なさそう&扱いが難しそうということで不要なカラムとみなすことにします。
1 2 3 |
#不要なカラムの削除 train = train.drop(["PassengerId","Name","Ticket","Cabin"],axis=1) test = test.drop(["PassengerId","Name","Ticket","Cabin"],axis=1) |
欠損値の処理
まず欠損値についてですが、isnull().sum()を用いて、各特徴量ごとにいくつ欠損があるかどうかを確認します。
1 2 |
train.isnull().sum() test.isnull().sum() |
trainデータセットではAgeとEmbarked
testデータセットではAgeとFareに欠損があることが確認できました。
これらを埋めていきます。
Embarkedの欠損を埋める
1 2 3 4 5 6 7 8 |
#埋める前ではS,C,Q,nanの4種類あることを確認 train["Embarked"].unique() #Embarkedの欠損を最頻値で埋める train["Embarked"] = train["Embarked"].fillna(train["Embarked"].mode().iloc[0]) #埋めた後ではS,C,Qの3種類あることを確認 train["Embarked"].unique() |
今回は最頻値で埋めることにするので、fillna().mode()とします。
mode()メソッドは、pandas.DataFrameを返すので、ilocで先頭行をpandas.Seriesとして取得する必要があります。
Ageの欠損を埋める
1 2 3 4 5 6 7 |
#Ageの欠損を中央値で埋める train["Age"] = train["Age"].fillna(train["Age"].median()) test["Age"] = test["Age"].fillna(test["Age"].median()) #欠損値がなくなったか確認 train["Age"].isnull().sum() test["Age"].isnull().sum() |
中央値はmedian()です。
Fareの欠損を埋める
1 2 3 4 5 |
#Fareを中央値で埋める test["Fare"] = test["Fare"].fillna(test["Fare"].median()) #欠損がなくなったか確認 test["Fare"].isnull().sum() |
カテゴリ変数の処理
お次はカテゴリ変数を処理します。
全部数値型にしてあげないとモデルに入れたときにエラーが吐き出されて悲しい気持ちになります。
まずはすべての特徴量のデータ型を確認します。
1 2 |
#データ型の確認 train.dtypes |
カテゴリ変数はSexとEmbarkedの2つですね。
Sexを変換
まずは性別から。
1 2 3 |
#naleを0に、femaleを1に変換 train["Sex"] = train["Sex"].map({"male":0,"female":1}) test["Sex"] = test["Sex"].map({"male":0,"female":1}) |
map関数を用いて変換を行います。
性別間に順序はないので、置き換える数字を1,2とかにしないように注意です。
Embarkedを変換
Embarkedについては、値の種類が3つですので、one-hotエンコーディングを行います。
もしmap関数で適当な数値に置き換えてしまうと、実際には順序の関係性がないのに、そこに順序の関係性をコンピュータが勝手に見出してしまいます。
(S=1、C=2、Q=3と置き換えたら、Q=S+Cみたいな謎な関係性を機会が見出してしまいます。)
1 2 3 |
3Embarkedをダミー変数化 train = pd.get_dummies(train,columns=["Embarked"]) test = pd.get_dummies(test,columns=["Embarked"]) |
pd.get_dummies() で簡単に行えます。
訓練データを分割
モデルの作成のために訓練データを訓練セットとテストセットに分割します。
1 2 3 4 5 6 7 8 |
#訓練データとテストデータに分割 train_set, test_set = train_test_split(train, test_size = 0.2, random_state = 1) X_train = train_set.iloc[:,1:] y_train = train_set.iloc[:,0] X_test = test_set.iloc[:,1:] y_test = test_set.iloc[:,0] |
いったん全部の特徴量を使って決定木モデルを作ってみる
とりあえず1個モデルをつくってみて、精度を見ます。
それを基準に、のちの改善後スコアが向上しているかどうか見たいと思います。
1 2 3 |
#決定木モデルの作成 clf1 =DecisionTreeClassifier(max_depth=4) clf1 = clf1.fit(X_train, y_train) |
1 2 3 4 5 |
#予測 y_pred_test = clf1.predict(X_test) #テストデータの正解率 accuracy_score(y_test,y_pred_test) |
タイタニックは評価指標がaccuracyですので、accuracyで評価をします。
正解率は、、、0.79888でした。
ちなみにですが、今回どれだけあってたか、というのは混同行列で可視化するとわかりやすいです。
1 2 3 4 5 6 7 8 9 10 |
from sklearn.metrics import confusion_matrix #混同行列の作成 matrix = confusion_matrix(y_test,y_pred_test) #pandasで表の形に class_names = ["died","survived"] df = pd.DataFrame(matrix,index=class_names,columns=class_names) df |
こんな表ができます。

さらにseabornを用いてヒートマップにしてみます。
1 2 3 4 5 6 7 |
#ヒートマップの作成 sns.heatmap(df,annot=True,cbar=None,cmap="Blues") plt.title("Confusion Matrix") plt.tight_layout() plt.ylabel("True Class") plt.xlabel("Predicted Class") plt.show() |
混同行列ですが、見方としては
・列は予測における正誤(タイタニックなので生き残ったか、亡くなったか)
・行は本当の正誤
を示しています。ですので、
・左上=diedと予測して、本当にdiedだった数
・左下=diedと予測して実際はsurivivedだった数
・右上=survivedと予測して実際はdiedだった数
・右下=survivrdと予測して本当にsurvivedだった数
を意味します。
つまり、左上から右下へと対角線にあたるセルに数字が集中するほど、予測精度が高い、といえることになります。
ちなみに、正解率のaccuracyはこの4象限を足したもの(左上+左下+右上+右下)を分母に、予測と本当の正誤が一致した数(左上+右下)を分子とした計算で求められます。
また、モデルの評価にはaccuracy以外にも、適合率(precision)や再現率(recall)、F1値などがあり、みなこの混同行列から算出することができます。
ランダムフォレストで特徴量の重要度を求めてみる
ランダムフォレストではfeature_importances_というとても便利なものが使えて、これは何かというと使った特徴量の重要度を数値化してくれるのです!
これで、この特徴量はめっちゃ貢献しているから入れておこう、逆にこれはいらない、と特徴量の選別を行うことができます。
というわけで、ランダムフォレスト分類器をつくります。
1 2 3 |
#ランダムフォレストモデルの作成 RF = RandomForestClassifier(n_estimators=250,random_state=1) RF.fit(X_train,y_train) |
使用した特徴量と、それらの重要度を表にまとめます。
1 2 3 4 5 6 7 |
#特徴量と重要度を取得 features = X_train.columns importances = RF.feature_importances_ #表にする df = pd.DataFrame({"features":features,"importances":importances}).sort_values("importances",ascending=False) df.reset_index(drop=True) |
はい、このようになりました!
どうやら大きく予測に貢献しているのは”Sex”,”Fare”,”Age”のよう。
逆にダミー変数化したEmbarkedについては予測の役に立っていないようですね。
重要度の高い特徴量のみで決定木
では、先ほどの結果を踏まえ、重要度の高い上位5つのみを特徴量として決定木にかけたらどうなるかやってみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 重要度の高い特徴量のみへ更新 X_train = X_train[["Sex","Fare","Age","Pclass","SibSp"]] X_test = X_test[["Sex","Fare","Age","Pclass","SibSp"]] # 決定木モデルの作成 clf2 = DecisionTreeClassifier(max_depth=4) clf2.fit(X_train, y_train) #予測 y_pred_test = clf2.predict(X_test) #テストデータでの正解率 accuracy_score(y_test,y_pred_test) |
流れは先ほどと一緒です。
気になる正解率はというと、、、0.79888!! まさかの変わらないという結果に、、そんな、、。
まったく変わっていないということはないはずなので、混同行列を作って比較してみます。
▼Before(左)・After(右)


うん、確かにちょこっと変わってはいるものの、左上と右下足した数は変わらないので、正解率に変化がなかったようです。
きっとこうやって特徴量を絞り込むのは、今回みたいに特徴量が少ないデータセットではなく、多いデータセットの時に有効なのでしょう。
最初にランダムフォレストやったんだから、ランダムフォレストで予測すればよいじゃん、という自分突っ込みをいれてみたのですが、それでも決定木を使ったのは、dtreevizでの可視化をやってみたかったからです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#可視化 class_names = y_train.unique().tolist() viz = dtreeviz( clf1, X_train, y_train, target_name='survived', feature_names=X_train.columns, class_names=[str(i) for i in class_names] ) display(viz) |
このコードを打ち込むとこんな感じで決定木の分類を可視化してくれます。

ちょっとキャプチャがはしっこちょん切れてしまっているのですが、とてもきれいで分かりやすい。
1人でもくもくとやる分には良いですが、人に説明したりするときにこんな風に可視化できるのは大きなメリットですよね。
ちなみにランダムフォレストの精度ですが、先ほど作ったランダムフォレスト分類器では、決定木とスコアは変わりませんでした。
というわけで、今回は少ない特徴量に対して、こうやって全体から特徴量を減らしていくアプローチは有効ではない、ということを学びました。
とはいえスコアは改善したいので、次はハイパーパラメータのチューニングをやっていきたいなと思います。
長くなったので、それは次に書こうと思います。
最後まで読んでいただきありがとうございました。