Python ランダムフォレスト分類でタイタニックの生存予測

'21/06/19更新:内容を刷新しました。利便性を考えて、JupyterLabのようにインタラクティブに使用できる雛形コードにしました。勿論、テキストファイルに保存してコマンドプロンプトで実行することも可能です。
 本記事では、グリッドサーチも使用したランダムフォレスト分類(RandomForestClassifier)で分類学習器を作成する雛形コードを載せました。Pythonライブラリにはscikit-learn(sklearn,サイキットラーン)を使用します。処理の流れは次の通りです。

1. 訓練データを読み込む
2. 分析に不要な列の削除
3. 欠損値のある行の削除
4. カテゴリ変数の数値化であるOne-hotエンコーディング
5. 訓練データで学習して、分類学習器を作成する
6. テストデータを読み込む
7. テストデータの欠損値の穴埋め(平均値, 中央値, 最頻値)
8. 予測する
9. 作成した学習器を評価する
10. 分析結果を可視化(予測結果を混合行列, 特徴量を感度順にランキング)
11. 予測結果をcsvファイルに保存する
12. 作成した分類学習器をバイナリファイル(joblib)に保存する

はじめに、本雛形コードを載せます。その後に処理の説明を記載しました。
▼本プログラム

#!/usr/bin/env python
# coding: utf-8

# In[1]:


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.size'] = 14 # グラフの基本フォントサイズの設定
import seaborn as sns
from sklearn.preprocessing import OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
import joblib
import datetime
now = datetime.datetime.now()
now = now.strftime("%y%m%d")

# 訓練データを読み込む
df = pd.read_csv('train.csv')
df


# In[2]:


# 欠損値を確認
df.isnull().sum()


# In[3]:


# 型を確認する
df.dtypes


# In[4]:


# 不要な列を削除
drop_list = ['Name', 'Ticket', 'Cabin']
df2 = df.drop(drop_list, axis=1)
df2


# In[5]:


# 欠損データがある行を削除
df3 = df2.dropna()
df3


# In[6]:


# 欠損値を確認
df3.isnull().sum()


# In[7]:


# ヒストグラムで分布を確認
df3.hist()
plt.tight_layout()
#plt.show()
plt.savefig(now + '_01_hist.jpg')
plt.close()


# In[8]:


# 型を確認する
df3.dtypes


# In[9]:


# カテゴリがいくつあるか調べる
n_Sex = df3['Sex'].unique()
print(n_Sex)
n_Embarked = df3['Embarked'].unique()
print(n_Embarked)


# In[10]:


# 行列散布図
sns.set_context('talk')
ax = sns.pairplot(
    df3,
    y_vars = 'Survived',
    hue = 'Sex', # 凡例に表示したい列名を指定(カテゴリ変数)
    palette = 'gnuplot2', # 'tab10' 'magma' 'cool' 'bar' 'gnuplot2'
    kind = 'reg', # 線形近似線を記入
    markers = '.',
    diag_kind = 'kde',
    diag_kws = dict(shade = True),
)
plt.tight_layout()
#plt.show()
plt.savefig(now + '_02_pariplot.jpg')
plt.close()


# In[11]:


# One-hotエンコーディング
df4 = pd.get_dummies(df3)
df4


# In[12]:


# 特徴量(説明変数)Xデータを抽出
train_data = df4.drop(['PassengerId', 'Survived'], axis=1)
train_data


# In[13]:


# 目的変数Yデータを抽出
train_label = df4[['Survived']]
train_label


# In[14]:


# ランダムフォレスト分類
print('RandomForestClassifier ...')
# ハイパーパラメータ
params = {
    'n_estimators' : [80, 100, 120],
    'criterion'   : ['gini', 'entropy'],
    'min_samples_leaf' : [2, 3, 4],
    'max_depth'   : [3, 4, 5, 7, 9]
}

# インスタンスの生成(条件の設定)
clf = GridSearchCV(
    RandomForestClassifier(),
    param_grid = params,
    cv = 5)

# 実行
train_label = train_label.values.ravel()
clf.fit(train_data, train_label)


# In[15]:


# ベスト結果
best_clf = clf.best_estimator_
best_clf


# In[16]:


# テストデータを読み込む
df_test = pd.read_csv('test.csv')
df_test


# In[17]:


# 分析に必要な列名だけ残す
drop_list.append('PassengerId')
df_test2 = df_test.drop(drop_list, axis=1)
df_test2


# In[18]:


# 欠損値を確認
df_test2.isnull().sum()


# In[19]:


# 型の確認
df_test2.dtypes


# In[20]:


# 欠損データを埋める
#df_test3 = df_test2.fillna(df_test2.mean()) # 平均値
df_test3 = df_test2.fillna(df_test2.median()) # 中央値
#df_test3 = df_test2.fillna(df_test2.mode()) # 最頻値
df_test3


# In[21]:


# 欠損値を確認
df_test3.isnull().sum()


# In[22]:


# 作成した分類器に合わせたデータに変換(ワンホットエンコーディング)
test_data = pd.get_dummies(df_test3)
test_data


# In[23]:


# 予測する
predict = best_clf.predict(test_data)
predict


# In[24]:


# 正解データを読み込む
df_test = pd.read_csv('gender_submission.csv')
df_test


# In[25]:


# 学習器(モデル)を評価する
train_score = 'train_score,' + str(best_clf.score(train_data, train_label))
test_score = 'test_score,' + str(best_clf.score(test_data, df_test['Survived']))
print(train_score)
print(test_score)

with open(now + '_03_model_score.csv', 'w') as f:
    f.write(train_score + '\n')
    f.write(test_score + '\n')


# In[26]:


# 学習器(モデル)を評価するには、次のようにしてもできる
accuracy_score(df_test['Survived'], predict)


# In[27]:


# ヒートマップの作成

# 正解データと予測データを混合行列にする
my_matrix = confusion_matrix(df_test['Survived'], predict)

# pandasデータフレーム形式にする
class_names = ["died","survived"] # 行列名
df_matrix = pd.DataFrame(
    my_matrix,
    index = class_names,
    columns = class_names)

# seabornでグラフ化
sns.heatmap(df_matrix, annot=True, cmap="Reds")
plt.tight_layout()
plt.ylabel("True")
plt.xlabel("Predict")
plt.title('Confusion Matrix')
plt.tight_layout()
#plt.show()
plt.savefig(now + '_04_heatmap.jpg')
plt.close()


# In[28]:


# 特徴量のランキング可視化
my_features = train_data.columns
my_importance = best_clf.feature_importances_
my_index = np.argsort(my_importance)

plt.figure(dpi=100)
plt.barh(range(len(my_index)), my_importance[my_index], color='g', align='center')
plt.yticks(range(len(my_index)), my_features[my_index])
plt.xlabel('Variable Importance')
plt.ylabel('Features')
plt.title('Feature Importance Plot')
plt.tight_layout()
#plt.show()
plt.savefig(now + '_05_Feature_Importance.jpg')
plt.close()


# In[29]:


# 提出用データを作成
# 予測結果をnumpyアレイからpandasデータフレーム形式へ変換
results = pd.Series(predict, name = "Survived")
results


# In[30]:


# 結合する
DF = pd.concat([df_test[['PassengerId']], results], axis = 1)
DF


# In[32]:


# Kaggle提出用csvファイルに保存
DF.to_csv(now + '_06_titanic_submission.csv', index = False)


# In[33]:


# 分類学習器の保存
joblib.dump(best_clf, now + '_07_titanic_RandomForestClassifier.joblib', compress=3) 


# In[ ]:

 以下、説明です。

 例題に使用した分析データは、タイタニック生存者データでありTitanic: Machine Learning from Disaster | Kaggleより無料で入手できます。但し、アカウント登録が必要です。Google(グーグル)アカウント等があれば、それを使用することで簡単にログイン出来ます。

 上記リンク先よりcsvファイル3つ「train.csv」,「test.csv」,「gender_submission.csv」があるのでダウンロードします。訓練データ「train.csv」は次のようになっています。

f:id:HK29:20210619161822p:plain

日本語では次の通りです。Survivedを予測するための学習器を作成することが目的です。

PassengerID 乗客ID
Survived

生存結果 (0: 死亡, 1: 生存)

Pclass 乗客の階級
Name 乗客の名前
Sex 性別
Age 年齢
SibSp 兄弟、配偶者の数
Parch 両親、子供の数
Ticket チケット番号
Fare 乗船料金
Cabin 部屋番号
Embarked 乗船した港

まずはカテゴリ変数以外の数値データで、全体像を視覚的に把握してゆきます。下図は、ヒストグラムです。2曲かしたり、極端な分布の偏りはなさそうです。

f:id:HK29:20210619163633p:plain

次に、下図は散布図です。縦軸がSurvivedで、色が赤と青の2種類あって青は男性で赤が女性です。横軸はその他の特徴量(説明変数)です。ここで、いずれを見ても青が0(死亡)に近い方に多く分布していることがわかります。つまり、予測するための情報としてSexが重要なのは視覚的にわかります。

f:id:HK29:20210619163131j:plain

そしてデータ分析に使用する列名(説明変数, 特徴量)を考えることになります。Name(乗客の名前),  Ticket(チケット番号), Cabin(部屋番号)は、関係ないかと今回は前処理でそれらの列を省きました(Cabinは、全員が部屋に居てたら関係するかもしれませんが。。)

次に欠損値を調べると、下図のように欠損値もあることがわかります。Ageは関係あると想像します(当時の文化、雰囲気でボートに載せて助けようとするのが、若い人なのか年寄りなのか。。)ここで、この欠損値を省くのか、何かで穴埋めするのかが判断の分かれ目です。今回、予測のためには曖昧なデータは使いたくないという判断で削除しています。

f:id:HK29:20210619165703p:plain

 前処理の続きで、One-hotエンコーディングでカテゴリ変数を数値「0」or「1」にします。SexとEmbarkedが、下図右のように変換します。

f:id:HK29:20210619164756p:plain

そして、学習します。この時、グリッドサーチにより、探索範囲内でのハイパーパラメータの最適解を得ます。

f:id:HK29:20210619170948p:plain

下図が選定された分類学習器の結果です。

f:id:HK29:20210619171129p:plain

作成した学習器を用いて、テストデータ(本来は未知データ)を予測します。冒頭のリンクで入手した「test.csv」は次の通りです。正解データであるSurvivedの列がありません。これは、「gender_submission.csv」にすでに分けてあるためです。

f:id:HK29:20210619171504p:plain

ここで、上図をみると、AgeにNaN(欠損値)があります。ここでは削除することはできません。削除したら情報の欠如で予測できないためです。平均値か中央値か最頻値、もしくは自分の判断で何かしらで穴埋めすることになります。

更に、カテゴリ変数もワンホットエンコーディングでデータ変換する必要があります。作成した学習器でデータを読み込んで分析するためには、データ分析情報に整合を持たす必要があります。最終的なテストデータは下図のようになりました。

f:id:HK29:20210619172250p:plain

そして、上記418行のテストデータに対して予測した結果が下図です。0が死亡で1が生存。

f:id:HK29:20210619172533p:plain

上記予測結果データと正解データをpandasデータフレーム形式に加工して、可視化のために図示したのが下図です。混合行列と呼び、左下と右上が予測が外れた数です。

f:id:HK29:20210619172753p:plain

学習によって得られた特徴量(説明変数)の感度のランキングも、可視化のために下図のように横棒グラフ化します。

f:id:HK29:20210619173255p:plain

最後に、予測結果はcsvファイルに保存し、作成した学習器も下図のようにバイナリファイル(.joblib)に保存します。

f:id:HK29:20210619173436p:plain

保存した機械学習モデルの再利用方法と手順は次のリンクを参照下さい。

Python joblibで保存した機械学習モデルを読み込んで利用する - PythonとVBAで世の中を便利にする

 

(参考)ランダムフォレストの本家サイトは下記です。

scikit-learn.org

以上

<広告>