【Python】scikit-learn ~ 決定木 / ランダムフォレスト ~

■ はじめに

以下の関連記事で扱ったSchoo(スクー)

https://dk521123.hatenablog.com/entry/2020/02/29/003619

の講義で決定木 / ランダムフォレストを勉強する機会が
あったのでメモ。

ちなみに、講師の方は以下のブログの方だと思うが
分かりやすかった

https://www.randpy.tokyo/entry/decision_tree_theory
https://www.randpy.tokyo/entry/python_random_forest

学べる事項(学んだこと)

1)Pythonライブラリ
 1-1) scikit-learn
 1-2) pydotplus
 1-3) matplotlib
2)決定木 / ランダムフォレスト

目次

【1】用語整理
 1) 決定木 (decision tree)
 2)ランダムフォレスト (random forests)
【2】Hello World
【3】サンプル
 例1)決定木
 例2)ランダムフォレスト

【1】用語整理

以下の関連記事をより抜粋 (詳細や関連用語などは、関連記事を参照)

https://dk521123.hatenablog.com/entry/2013/12/16/225948
1) 決定木 (decision tree)

* 条件分岐を木構造のように繰り返しデータを分類する手法
 ⇒ 言葉で説明するより、以下の図見たほうが理解は早い

https://www.randpy.tokyo/entry/decision_tree_theory
https://dev.classmethod.jp/articles/2017ad_20171211_dt-2/

2)ランダムフォレスト (random forests)

* 決定木をたくさん生成し、多数決する(または平均を取る)ような手法
 ⇒ 決定"木を集めたから、ランダム"フォレスト(森)"。

【2】Hello World

Step1:データを用意
Step2:Step1データを使って、モデル (=決定木)を作成
Step3: 作成した決定木を使って、予想する

サンプル

# 決定木
from sklearn.tree import DecisionTreeClassifier

# Step1:データを用意(今回は簡単なデータ)
# 説明変数 x
x_trains = [[140, 0], [130, 0], [150, 1], [170, 1]]
# 目的変数 y (説明変数の2番目と一致)
y_trains = [0, 0, 1, 1]

# Step2:Step1データを使って、モデル (=決定木)を作成
# 決定木(Decision Tree)
classifier = DecisionTreeClassifier()
# 説明変数と目的変数の両方を与え、決定木を作成
classifier = classifier.fit(x_trains, y_trains)

# Step3: 作成した決定木を使って、予想する
x_tests = [[153, 1]]
y_tests = classifier.predict(x_tests)
print(y_tests)

出力結果

[1]

【3】サンプル

「決定木」「ランダムフォレスト」のサンプルを書いていく

使用するデータセット「Titanic」について

* 以下の関連記事ででてきた「Kaggle(カグル)」

https://dk521123.hatenablog.com/entry/2020/03/28/131839

の「Titanic: Machine Learning from Disaster」を使用する

https://www.kaggle.com/c/titanic/data

データ内容

PassengerId : ユニークID
Survived : Survival (0 = 落命, 1 = 生存) << ★目的変数★
Pclass : 客席等級(1 = 1st, 2 = 2nd, 3 = 3rd)
Name : 名前
Sex : 性別
Age : 年齢
SibSp : 同乗中の配偶者/兄弟の数
Parch : 同乗中の親/子供の数 
Ticket : チケット番号
Fare : 料金
Cabin : 客室番号
Embarked : 出発港 (C = Cherbourg, Q = Queenstown, S = Southampton)

例1)決定木

import pandas as pd

# 決定木
from sklearn.tree import DecisionTreeClassifier
# 正解率を計算してくれる
from sklearn.metrics import accuracy_score
# 決定木のPDF化
import pydotplus
from sklearn.tree import export_graphviz

# データ入力
df_train = pd.read_csv("train.csv")

# 欠損値処理
df_train['Fare'] = df_train['Fare'].fillna(df_train['Fare'].median())
df_train['Age'] = df_train['Age'].fillna(df_train['Age'].median())
df_train['Embarked'] = df_train['Embarked'].fillna('S')

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

df_train = df_train.drop(['Cabin', 'Name', 'PassengerId', 'Ticket'], axis=1)

# 説明変数 x
train_X = df_train.drop('Survived', axis=1)
# 目的変数 y
train_y = df_train.Survived

# 決定木(Decision Tree)
classifier = DecisionTreeClassifier(max_depth=3)
classifier.fit(train_X, train_y)

# モデル評価
df_test = pd.read_csv("test.csv")
df_objection = pd.read_csv("gender_submission.csv")
df_test = pd.merge(df_test, df_objection, on='PassengerId', how='inner')

# 欠損値処理(削除)
df_test = df_test.dropna(subset=['Fare'])
df_test = df_test.dropna(subset=['Age'])
df_test = df_test.dropna(subset=['Embarked'])

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

df_test = df_test.drop(['Cabin', 'Name', 'PassengerId', 'Ticket'], axis=1)

# 説明変数 x
test_X = df_test.drop('Survived', axis=1)
# 目的変数 y
test_y = df_test.Survived

# 予想
predict_y = classifier.predict(test_X)
# 評価
print(accuracy_score(test_y, predict_y))

# 決定木のPDF出力
dot_data = export_graphviz(
  classifier, feature_names='Titanic',
  filled=True, rounded=True, impurity=False)
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_pdf("viz_tree.pdf")

出力結果

0.9697885196374623

"viz_tree.pdf" もPDFで出力されている

例2)ランダムフォレスト

import pandas as pd

# ランダムフォレスト
from sklearn.ensemble import RandomForestClassifier
# 正解率を計算してくれる
from sklearn.metrics import accuracy_score
# グラフ化
import numpy as np
import matplotlib.pyplot as plt


# データ入力
df_train = pd.read_csv("train.csv")

# 欠損値処理
df_train['Fare'] = df_train['Fare'].fillna(df_train['Fare'].median())
df_train['Age'] = df_train['Age'].fillna(df_train['Age'].median())
df_train['Embarked'] = df_train['Embarked'].fillna('S')

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

df_train = df_train.drop(['Cabin', 'Name', 'PassengerId', 'Ticket'], axis=1)

# 説明変数 x
train_X = df_train.drop('Survived', axis=1)
# 目的変数 y
train_y = df_train.Survived

# ランダムフォレスト(★ここが違う★)
classifier = RandomForestClassifier(max_depth=7)
classifier.fit(train_X, train_y)

# モデル評価
df_test = pd.read_csv("test.csv")
df_objection = pd.read_csv("gender_submission.csv")
df_test = pd.merge(df_test, df_objection, on='PassengerId', how='inner')

# 欠損値処理(削除)
df_test = df_test.dropna(subset=['Fare'])
df_test = df_test.dropna(subset=['Age'])
df_test = df_test.dropna(subset=['Embarked'])

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

df_test = df_test.drop(['Cabin', 'Name', 'PassengerId', 'Ticket'], axis=1)

# 説明変数 x
test_X = df_test.drop('Survived', axis=1)
# 目的変数 y
test_y = df_test.Survived

# 予想
predict_y = classifier.predict(test_X)
# 評価
print(accuracy_score(test_y, predict_y))
# 変数の重要度
importances = classifier.feature_importances_
features = np.array(test_X.columns)

# プロット
indices = np.argsort(importances)
plt.figure(figsize=(12, 12))
plt.barh(range(len(indices)), importances[indices], color='b', align='center')
plt.yticks(range(len(indices)), features[indices])
plt.show()

出力結果

# 木の深さが5(max_depth=4)の場合
0.879154078549849

# max_depth=5の場合
0.8942598187311178

# max_depth=7の場合
0.8942598187311178

# グラフが表示

参考文献

https://www.randpy.tokyo/entry/decision_tree_theory
https://www.randpy.tokyo/entry/python_random_forest
https://future-chem.com/ames-decision-tree/

関連記事

scikit-learn ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/03/02/233902
scikit-learn ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2020/03/08/113356
scikit-learn ~ 線形回帰 ~
https://dk521123.hatenablog.com/entry/2020/07/04/000000
scikit-learn ~ 重回帰/リッジ回帰 ~
https://dk521123.hatenablog.com/entry/2020/04/25/174503
パターン認識について
https://dk521123.hatenablog.com/entry/2013/12/16/225948
機械学習を勉強する際のデータセットについて
https://dk521123.hatenablog.com/entry/2020/03/28/131839