機械学習_決定木分析_pythonで実装

2017.12.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

概要

こんにちは、データインテグレーション部のyoshimです。この記事は機械学習アドベントカレンダー12日目のものとなります。 本日は、先日ご紹介した「決定木分析」を実際にPython(jupyter)でやってみたので、ご紹介します。

目次

やること  

今回のエントリでは、決定木分析をpythonでやってみたいと思います。 目的は、[アドベントカレンダー11日目]にてご紹介した「決定木分析の理論への理解」をより深める、ことです。

作業環境

  • macOS High Sierra 10.13.1
  • python 3.6.3
  • sklearn 0.19.1

手順概要

今回の処理の流れは下記の通りです。

1.必要なモジュールとデータセットの準備

sklearnに用意されているデータセット(iris)を使います。

2.データセットの確認

実際に分析を進める前に、データの中身を確認します。 また、データの中身を確認した後、トレーニング用と検証用にデータを分割します。

3.ハイパーパラメータの調整(モデル作成と精度確認)

モデル作成にあたって、ハイパーパラメータを調整する必要があります。 今回は「木の深さ」と「終端ノード数」の2つの要素を色々組み合わせた時の精度を確認していきます。

4.可視化

モデルの中身を可視化します。
決定木分析は中身の理解が容易、というのが強みなので可視化は必須と言ってもいいかもしれません。

5.各特徴量の重要度を可視化

最後に、今回利用した4つの特徴量のうち、どの特徴量がどれだけデータ分割に寄与したのかを確認しています。

コードと解説

まずは、「1.必要なモジュールとデータセットの準備」からです。
今回は、sklearnに用意されているデータセットを使います。

# 1.必要なモジュールとデータセット
from sklearn.datasets import load_iris
iris = load_iris()

続いて、「2.データセットの確認」に移ります。
具体的には、データセットの中身を確認した後に、トレーニング用と検証用にデータを分割します。
また、データセットの中身を確認した結果、「目的変数でソート」されていたので、シャッフルしてからデータを分割しています。

# 2.データセットの確認
print(type(iris)) # データ型の確認
print(iris.data.shape) # サンプルサイズ、説明変数の次元数の確認
print(iris.target) #目的変数を確認

'''モデル作成と検証用にデータを分割(チュートリアルには記載なし)
sklearnのtrain_test_splitについて
データをシャッフルしてそこからデータを分割する。(擬似乱数を用いてシャッフル)
この時、random_stateを指定すると、「再度同じ処理を実行した際に同じデータを取得」することができる。
'''

# 目的変数を確認した結果、データが偏っているのでランダムにシャッフルした上で、トレーニング用データと検証用データに分割する
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, stratify=iris.target,random_state=10)

次は、「3.ハイパーパラメータの調整(モデル作成と精度確認)」です。
今回は「木の深さ」と「終端ノード数」について、色々な組み合わせで決定木分析を実施&精度を確認し、「設定すべきハイパーパラメータ」を判断します。
ここでは、「木の深さ」は1−10、終端ノード数は2,4,6,8,10の組み合わせで確認しています。

'''3.ハイパーパラメータの調整(モデル作成と精度確認)
木の深さと、終端ノード数でパラメータチューニング
'''
# モジュールのインポート
from sklearn import tree
from sklearn.grid_search import GridSearchCV

## チューニングするパラメータ
tuned_parameters = {'max_depth':  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], # 木の深さを1-10で
                    'max_leaf_nodes':  [2,4,6,8,10] # 最大終端ノード数を2,4,6,8,10で
                   }

# 上記で用意したパラメーターごとに交差検証を実施。最適な木の深さを確認する。
clf = GridSearchCV(tree.DecisionTreeClassifier(random_state=0,splitter='best'), tuned_parameters, scoring="accuracy",cv=5, n_jobs=-1)
clf = clf.fit(X_train, y_train) # モデル作成!
print("Best Parameter: {}".format(clf.best_params_))
print("Best Parameterでの検証用データの精度: {:.2f}".format(clf.score(X_test, y_test)))
print("Best Parameterで交差検証した精度の平均(訓練データ): {:.2f}".format(clf.best_score_))

上記コードの実行結果がこちらです。

木の深さは4階層、終端ノード数は6個にした時がもっとも精度が高いみたいです。
また、「精度」について2つ表示していてわかりづらいかもしれませんが、これは「ベストなパラメータで確認した、検証用データの精度」と「トレーニング用データの交差検証結果の平均交差検証精度」です。これらがそれぞれ100%,93%でした、ということですね。

ここまでで、決定木分析を実行するにあたって設定すべきハイパーパラメータの値がわかりましたので、実際にその値を設定したうえで分析結果を可視化しようと思います。なお、この可視化についてはjupyter上で実行しています。

'''4.可視化
分析結果をファイルにして出力した後、そのファイルを読み込んで可視化します。
ターミナルで「pip install graphviz」を実行して、graphvizをインストールする必要があります。
'''

# パラメータ調整の結果、もっとも精度がよかった木の深さを採用 
clf = tree.DecisionTreeClassifier(max_depth=4, max_leaf_nodes=6, random_state=0)
clf = clf.fit(X_train,y_train)

# tree.dotという名前で分析結果を出力
tree.export_graphviz(clf, out_file="tree.dot",
                         feature_names=iris.feature_names,
                         class_names=iris.target_names,
                         filled=True, rounded=True, impurity=False)


# tree.dotファイルを可視化
import graphviz
with open('tree.dot') as f:
    dot_graph = f.read()

graphviz.Source(dot_graph)

ハイパーパラメータを調整した上でモデルが作成できました。
せっかくなので最後に「どの特徴量がどれくらい分割に寄与したのか」を確認したいと思います。

'''5.各特徴量の重要度を可視化
'''
import matplotlib.pyplot as plt
import numpy as np

def plot_feature_importances(model):
    n_features = iris.data.shape[1] # 全説明変数
    plt.barh(range(n_features), model.feature_importances_, align='center') # 描画する際の枠組みを設定
    plt.yticks(np.arange(n_features), iris.feature_names) # 縦軸の設定
    plt.xlabel('importances') # 横軸の設定
    plt.ylabel('features') # 縦軸の設定
    plt.show()

# 実行
plot_feature_importances(clf)

'''
特徴量の重要度から言えることは、「重要度の高い特徴量は、データを分割するのに有用である」ということだけで、「重要度が低い特徴量は不要」という訳ではない。
なぜなら、偶然別の特徴量に同じ情報が含まれてしまっているケースもあるからだ。
'''

「petal width」がもっともデータの分割に寄与しているみたいですね。「sepal(萼片)」はデータの分割に全く寄与していないという結果なので、この特徴量は全く使えないのかというと、そういうわけではない点にご注意ください。

まとめ

今回のエントリーは以上です。
明日はサポートベクターマシーン(理論編)についてのご紹介です。