決定木分析

Beginner

This tutorial is from open-source community. Access the source code

はじめに

決定木分類器は、分類と回帰問題に使用される人気のある機械学習アルゴリズムです。これは、木ベースのモデルであり、特徴空間を非重複領域のセットに分割し、各領域のターゲット値を予測します。この実験では、特徴と予測対象のターゲットとの関係をさらに深く理解するために、決定木の構造を分析する方法を学びます。

VM のヒント

VM の起動が完了したら、左上隅をクリックしてノートブックタブに切り替え、Jupyter Notebook を使って練習しましょう。

時々、Jupyter Notebook が読み込み終了するまで数秒待つ必要がある場合があります。Jupyter Notebook の制限により、操作の検証は自動化できません。

学習中に問題に遭遇した場合は、Labby にお問い合わせください。セッション後にフィードバックを提供してください。すぐに問題を解決いたします。

決定木分類器を学習する

まず、scikit-learn のload_irisデータセットを使って決定木分類器をフィットさせます。このデータセットは、それぞれ 50 個のインスタンスからなる 3 つのクラスが含まれており、各クラスはアヤメの植物の種類を指します。データセットを学習用とテスト用に分割し、最大 3 つの葉ノードを持つ決定木分類器をフィットさせます。

from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier

iris = load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

clf = DecisionTreeClassifier(max_leaf_nodes=3, random_state=0)
clf.fit(X_train, y_train)

二分木構造を分析する

決定木分類器には、tree_と呼ばれる属性があり、これを使うとnode_count(ノードの総数)やmax_depth(木の最大深さ)などの低レベルの属性にアクセスできます。また、二分木構造全体を多数の並列配列で表現して保存しています。これらの配列を使って、木構造を巡回して各ノードの深さや葉ノードかどうかなどのさまざまなプロパティを計算できます。以下はこれらのプロパティを計算するコードです。

import numpy as np

n_nodes = clf.tree_.node_count
children_left = clf.tree_.children_left
children_right = clf.tree_.children_right
feature = clf.tree_.feature
threshold = clf.tree_.threshold

node_depth = np.zeros(shape=n_nodes, dtype=np.int64)
is_leaves = np.zeros(shape=n_nodes, dtype=bool)
stack = [(0, 0)]  ## 根ノード ID(0) とその深さ (0) で始める
while len(stack) > 0:
    ## `pop` により、各ノードが 1 回だけ訪問されるようにする
    node_id, depth = stack.pop()
    node_depth[node_id] = depth

    ## ノードの左と右の子が同じでなければ、分割ノードである
    is_split_node = children_left[node_id]!= children_right[node_id]
    ## 分割ノードの場合、左と右の子と深さを `stack` に追加して、それらをループできるようにする
    if is_split_node:
        stack.append((children_left[node_id], depth + 1))
        stack.append((children_right[node_id], depth + 1))
    else:
        is_leaves[node_id] = True

print(
    "二分木構造は{n}個のノードを持ち、以下の木構造を持っています:\n".format(n=n_nodes)
)
for i in range(n_nodes):
    if is_leaves[i]:
        print(
            "{space}node={node}は葉ノードです。".format(
                space=node_depth[i] * "\t", node=i
            )
        )
    else:
        print(
            "{space}node={node}は分割ノードです:"
            "X[:, {feature}] <= {threshold}の場合、ノード{left}に移動し、そうでなければノード{right}に移動します。".format(
                space=node_depth[i] * "\t",
                node=i,
                left=children_left[i],
                feature=feature[i],
                threshold=threshold[i],
                right=children_right[i],
            )
        )

決定木を可視化する

scikit-learn のtreeモジュールのplot_tree関数を使って、決定木を可視化することもできます。

from sklearn import tree
import matplotlib.pyplot as plt

tree.plot_tree(clf)
plt.show()

決定経路と葉ノードを取得する

decision_pathメソッドを使って、関心のあるサンプルの決定経路を取得することができます。このメソッドは、指標行列を出力し、それを使って関心のあるサンプルが通過するノードを取得できます。関心のあるサンプルが到達する葉の ID は、applyメソッドで取得できます。これは、各関心のあるサンプルが到達する葉のノード ID の配列を返します。葉の ID とdecision_pathを使って、サンプルまたはサンプルのグループを予測するために使用された分割条件を取得できます。以下は、1 つのサンプルの決定経路と葉ノードを取得するコードです。

node_indicator = clf.decision_path(X_test)
leaf_id = clf.apply(X_test)

sample_id = 0
## サンプル ID `sample_id` が通過するノードの ID を取得する、つまり行 `sample_id`
node_index = node_indicator.indices[
    node_indicator.indptr[sample_id] : node_indicator.indptr[sample_id + 1]
]

print("サンプル{id}を予測するために使用されるルール:\n".format(id=sample_id))
for node_id in node_index:
    ## 葉ノードの場合は次のノードに進む
    if leaf_id[sample_id] == node_id:
        continue

    ## サンプル 0 の分割特徴量の値が閾値以下かどうかを確認する
    if X_test[sample_id, feature[node_id]] <= threshold[node_id]:
        threshold_sign = "<="
    else:
        threshold_sign = ">"

    print(
        "決定ノード{node} : (X_test[{sample}, {feature}] = {value}) "
        "{inequality} {threshold})".format(
            node=node_id,
            sample=sample_id,
            feature=feature[node_id],
            value=X_test[sample_id, feature[node_id]],
            inequality=threshold_sign,
            threshold=threshold[node_id],
        )
    )

サンプルのグループに共通するノードを特定する

サンプルのグループに対して、decision_pathメソッドと指標行列を密集配列に変換するためのtoarrayメソッドを使って、サンプルが通過する共通のノードを特定することができます。

sample_ids = [0, 1]
## 両方のサンプルが通過するノードを示すブール配列
common_nodes = node_indicator.toarray()[sample_ids].sum(axis=0) == len(sample_ids)
## 配列内の位置を使ってノード ID を取得する
common_node_id = np.arange(n_nodes)[common_nodes]

print(
    "\n以下のサンプル{samples}は木構造においてノード{nodes}を共有しています。".format(
        samples=sample_ids, nodes=common_node_id
    )
)
print("これは全ノードの{prop}%です。".format(prop=100 * len(common_node_id) / n_nodes))

まとめ

この実験では、特徴量と予測対象のターゲットとの関係をさらに深く理解するために、決定木構造をどのように分析するかを学びました。二分木構造をどのように取得するか、決定木を可視化する方法、およびサンプルまたはサンプルのグループの決定経路と葉ノードを取得する方法を見てきました。これらの技術は、決定木分類器が予測を行う方法をより良く理解するのに役立ち、モデルの微調整を行って性能を向上させる手助けになります。