Spec-Zone.ru › scikit-learn

Примечание

Перейти к концу для загрузки полного примера кода. или запустить этот пример в вашем браузере через JupyterLite или Binder

Понимание структуры дерева решений

Структуру дерева решений можно проанализировать, чтобы получить более глубокое понимание взаимосвязи между признаками и целевой переменной, которую нужно предсказать. В этом примере показано, как извлечь:

  • структуру двоичного дерева;
  • глубину каждого узла и является ли он листом;
  • узлы, до которых дошел образец с помощью метода decision_path;
  • лист, до которого дошел образец с помощью метода apply;
  • правила, которые использовались для предсказания образца;
  • путь принятия решений, общий для группы образцов.
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

import numpy as np
from matplotlib import pyplot as plt

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

Обучение классификатора дерева решений

Сначала мы обучаем DecisionTreeClassifier на наборе данных load_iris.

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)
DecisionTreeClassifier(max_leaf_nodes=3, random_state=0)
В среде Jupyter, пожалуйста, перезапустите этот ячейку, чтобы отобразить HTML-представление, или доверьтесь блокноту.
На GitHub, HTML-представление не может быть отображено, попробуйте загрузить эту страницу с помощью nbviewer.org.
DecisionTreeClassifier(max_leaf_nodes=3, random_state=0)


Структура дерева

Классификатор дерева решений имеет атрибут tree_, который предоставляет доступ к низкоуровневым атрибутам, таким как node_count, общее количество узлов и max_depth, максимальную глубину дерева. Метод tree_.compute_node_depths() вычисляет глубину каждого узла в дереве. tree_ также хранит всю структуру двоичного дерева, представленную в виде нескольких параллельных массивов. i-й элемент каждого массива содержит информацию об узле i. Узел 0 является корнем дерева. Некоторые массивы применимы только к узлам-листьям или узлам ветвления. В этом случае значения узлов другого типа произвольны. Например, массивы feature и threshold применимы только к узлам ветвления. Поэтому значения для узлов-листьев в этих массивах произвольны.

Среди этих массивов у нас есть:

  • children_left[i]: идентификатор левого дочернего узла узла i или -1, если узел-лист
  • children_right[i]: идентификатор правого дочернего узла узла i или -1, если узел-лист
  • feature[i]: признак, используемый для разделения узла i
  • threshold[i]: пороговое значение в узле i
  • n_node_samples[i]: количество обучающих образцов, достигающих узел i
  • impurity[i]: неопределённость в узле i
  • weighted_n_node_samples[i]: взвешенное количество обучающих образцов, достигающих узел i
  • value[i, j, k]: сводка обучающих образцов, которые достигли i-го узла для выхода j и класса k (для регрессионного дерева класс устанавливается в 1). Более подробную информацию о value см. ниже.

Используя массивы, мы можем пройтись по структуре дерева, чтобы вычислить различные свойства. Ниже мы вычислим глубину каждого узла и является ли он листом.

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
values = clf.tree_.value

node_depth = np.zeros(shape=n_nodes, dtype=np.int64)
is_leaves = np.zeros(shape=n_nodes, dtype=bool)
stack = [(0, 0)]  # start with the root node id (0) and its depth (0)
while len(stack) > 0:
    # `pop` ensures each node is only visited once
    node_id, depth = stack.pop()
    node_depth[node_id] = depth

    # If the left and right child of a node is not the same we have a split
    # node
    is_split_node = children_left[node_id] != children_right[node_id]
    # If a split node, append left and right children and depth to `stack`
    # so we can loop through them
    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(
    "The binary tree structure has {n} nodes and has "
    "the following tree structure:\n".format(n=n_nodes)
)
for i in range(n_nodes):
    if is_leaves[i]:
        print(
            "{space}node={node} is a leaf node with value={value}.".format(
                space=node_depth[i] * "\t", node=i, value=np.around(values[i], 3)
            )
        )
    else:
        print(
            "{space}node={node} is a split node with value={value}: "
            "go to node {left} if X[:, {feature}] <= {threshold} "
            "else to node {right}.".format(
                space=node_depth[i] * "\t",
                node=i,
                left=children_left[i],
                feature=feature[i],
                threshold=threshold[i],
                right=children_right[i],
                value=np.around(values[i], 3),
            )
        )
The binary tree structure has 5 nodes and has the following tree structure:

node=0 is a split node with value=[[0.33  0.304 0.366]]: go to node 1 if X[:, 3] <= 0.800000011920929 else to node 2.
        node=1 is a leaf node with value=[[1. 0. 0.]].
        node=2 is a split node with value=[[0.    0.453 0.547]]: go to node 3 if X[:, 2] <= 4.950000047683716 else to node 4.
                node=3 is a leaf node with value=[[0.    0.917 0.083]].
                node=4 is a leaf node with value=[[0.    0.026 0.974]].

Что такое массив values, используемый здесь?

Массив tree_.value — это 3D-массив формы [n_nodes, n_classes, n_outputs], который предоставляет пропорцию образцов, достигающих узла для каждого класса и для каждого выхода. Каждый узел имеет массив value, который является пропорцией взвешенных образцов, достигающих этот узел для каждого выхода и класса по отношению к родительскому узлу.

Можно преобразовать это в абсолютное взвешенное количество образцов, умножив это число на tree_.weighted_n_node_samples[node_idx] для данного узла. Обратите внимание, что весовые коэффициенты образцов в этом примере не используются, поэтому взвешенное количество образцов равно количеству образцов, достигающих узел, потому что каждый образец имеет вес по умолчанию 1.

Например, в построенном выше дереве на основе набора данных ириса корневой узел имеет value = [0.33, 0.304, 0.366], что указывает на то, что на корневом узле 33% образцов класса 0, 30,4% образцов класса 1 и 36,6% образцов класса 2. Можно преобразовать это в абсолютное количество образцов, умножив на количество образцов, достигающих корневого узла, которое равно tree_.weighted_n_node_samples[0]. Тогда корневой узел имеет value = [37, 34, 41], что указывает на то, что на корневом узле 37 образцов класса 0, 34 образца класса 1 и 41 образец класса 2.

Проходя по дереву, образцы разделяются, и в результате массив value, достигающий каждого узла, изменяется. Левый дочерний узел корневого узла имеет value = [1., 0, 0] (или value = [37, 0, 0] при преобразовании в абсолютное количество образцов), потому что все 37 образцов в левом дочернем узле принадлежат классу 0.

Примечание: в этом примере n_outputs=1, но классификатор дерева решений также может обрабатывать задачи с несколькими выходами. Массив value в каждом узле будет просто 2D-массивом вместо 3D.

Мы можем сравнить вышеуказанный вывод с графиком дерева решений. Здесь показаны пропорции образцов каждого класса, которые достигают каждого узла, соответствующие фактическим элементам массива tree_.value.

tree.plot_tree(clf, proportion=True)
plt.show()
plot unveil tree structure

Путь принятия решений

Мы также можем извлечь путь принятия решений для интересующих образцов. Метод decision_path выводит матрицу индикаторов, которая позволяет извлечь узлы, через которые проходят интересующие образцы. Не нулевой элемент в матрице индикаторов в позиции (i, j) указывает на то, что образец i проходит через узел j. Или, для одного образца i, позиции ненулевых элементов в строке i матрицы индикаторов обозначают идентификаторы узлов, через которые проходит этот образец.

Идентификаторы узлов-листьев, достигнутых интересующими образцами, можно получить с помощью метода apply. Это возвращает массив идентификаторов узлов-листьев, достигнутых каждым интересующим образцом. Используя идентификаторы узлов-листьев и decision_path, мы можем получить условия ветвления, которые использовались для предсказания образца или группы образцов. Сначала сделаем это для одного образца. Обратите внимание, что node_index — это разреженная матрица.

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

sample_id = 0
# obtain ids of the nodes `sample_id` goes through, i.e., row `sample_id`
node_index = node_indicator.indices[
    node_indicator.indptr[sample_id] : node_indicator.indptr[sample_id + 1]
]

print("Rules used to predict sample {id}:\n".format(id=sample_id))
for node_id in node_index:
    # continue to the next node if it is a leaf node
    if leaf_id[sample_id] == node_id:
        continue

    # check if value of the split feature for sample 0 is below threshold
    if X_test[sample_id, feature[node_id]] <= threshold[node_id]:
        threshold_sign = "<="
    else:
        threshold_sign = ">"

    print(
        "decision node {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],
        )
    )
Rules used to predict sample 0:

decision node 0 : (X_test[0, 3] = 2.4) > 0.800000011920929)
decision node 2 : (X_test[0, 2] = 5.1) > 4.950000047683716)

Для группы образцов мы можем определить общие узлы, через которые проходят образцы.

sample_ids = [0, 1]
# boolean array indicating the nodes both samples go through
common_nodes = node_indicator.toarray()[sample_ids].sum(axis=0) == len(sample_ids)
# obtain node ids using position in array
common_node_id = np.arange(n_nodes)[common_nodes]

print(
    "\nThe following samples {samples} share the node(s) {nodes} in the tree.".format(
        samples=sample_ids, nodes=common_node_id
    )
)
print("This is {prop}% of all nodes.".format(prop=100 * len(common_node_id) / n_nodes))
The following samples [0, 1] share the node(s) [0 2] in the tree.
This is 40.0% of all nodes.

Общее время выполнения скрипта: (0 минут 0,081 секунды)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_unveil_tree_structure.ipynb

Download Python source code: plot_unveil_tree_structure.py

Download zipped: plot_unveil_tree_structure.zip

Связанные примеры

График дендрограммы иерархического кластеризации

Последующая обрезка деревьев решений с помощью обрезки по сложности затрат

Регрессия на основе дерева решений

График поверхности принятия решений деревьев решений, обученных на наборе данных ирисов

© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/tree/plot_unveil_tree_structure.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API