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.
Параметры
random_state random_state: int, экземпляр RandomState или None, по умолчанию=None

Управляет случайностью оценщика. Признаки всегда
случайным образом переставляются при каждом разбиении, даже если ``splitter`` задан как
``"best"``. Если ``max_features < n_features``, алгоритм
случайным образом выбирает ``max_features`` признаков при каждом разбиении, прежде чем найти наилучшее
разбиение среди них. Однако найденное наилучшее разбиение может различаться при разных
запусках, даже если ``max_features=n_features``. Это происходит, если
улучшение критерия одинаково для нескольких разбиений и одно
из них приходится выбирать случайным образом. Чтобы обеспечить детерминированное поведение
при обучении, необходимо задать ``random_state`` целым числом.
Подробности см. в :term:`глоссарии <random_state>`.
0
max_leaf_nodes max_leaf_nodes: int, по умолчанию=None

Выращивает дерево с ``max_leaf_nodes`` листовыми узлами, используя стратегию «сначала лучшие».
Наилучшие узлы определяются относительным уменьшением неоднородности.
Если задано None, число листовых узлов не ограничено.
3
criterion criterion: {"gini", "entropy", "log_loss"}, по умолчанию="gini"

Функция для измерения качества разбиения. Поддерживаются критерии
"gini" для неоднородности Джини, а также "log_loss" и "entropy" для
информационного выигрыша Шеннона; см. :ref:`tree_mathematical_formulation`.
'gini'
splitter splitter: {"best", "random"}, по умолчанию="best"

Стратегия выбора разбиения в каждом узле. Поддерживаются
стратегии "best" для выбора наилучшего разбиения и "random" для выбора
наилучшего случайного разбиения.
'best'
max_depth max_depth: int, по умолчанию=None

Максимальная глубина дерева. Если задано None, узлы расширяются, пока
все листья не станут чистыми или пока число образцов во всех листьях не станет меньше
min_samples_split.
None
min_samples_split min_samples_split: int или float, по умолчанию=2

Минимальное число образцов, необходимое для разбиения внутреннего узла:

- Если задано int, `min_samples_split` задаёт минимальное число образцов.
- Если задано float, `min_samples_split` является долей, а
`ceil(min_samples_split * n_samples)` задаёт минимальное
число образцов для каждого разбиения.

.. versionchanged:: 0.18
Добавлена возможность задавать доли значениями float.
2
min_samples_leaf min_samples_leaf: int или float, по умолчанию=1

Минимальное число образцов, необходимое в листовом узле.
Точка разбиения на любой глубине рассматривается, только если в каждой из левой и
правой ветвей остаётся не менее ``min_samples_leaf`` обучающих образцов. Это может сглаживать модель,
особенно в задаче регрессии.

- Если задано int, `min_samples_leaf` задаёт минимальное число образцов.
- Если задано float, `min_samples_leaf` является долей, а
`ceil(min_samples_leaf * n_samples)` задаёт минимальное
число образцов для каждого узла.

.. versionchanged:: 0.18
Добавлена возможность задавать доли значениями float.
1
min_weight_fraction_leaf min_weight_fraction_leaf: float, по умолчанию=0.0

Минимальная взвешенная доля от общей суммы весов (всех
входных образцов), необходимая для листового узла. Если sample_weight не задан,
все образцы имеют одинаковый вес.
0.0
max_features max_features: int, float или {"sqrt", "log2"}, по умолчанию=None

Число признаков, рассматриваемых при поиске наилучшего разбиения:

- Если задано int, при каждом разбиении рассматриваются `max_features` признаков.
- Если задано float, `max_features` является долей, и
при каждом разбиении рассматривается `max(1, int(max_features * n_features_in_))` признаков.
- Если задано "sqrt", то `max_features=sqrt(n_features)`.
- Если задано "log2", то `max_features=log2(n_features)`.
- Если задано None, то `max_features=n_features`.

.. note::

Поиск разбиения не прекращается, пока не будет найдено хотя бы одно
допустимое разбиение образцов узла, даже если для этого фактически придётся
рассмотреть больше признаков, чем ``max_features``.
None
min_impurity_decrease min_impurity_decrease: float, по умолчанию=0.0

Узел разбивается, если это разбиение уменьшает неоднородность
на величину, большую или равную этому значению.

Уравнение взвешенного уменьшения неоднородности имеет следующий вид::

N_t / N * (impurity - N_t_R / N_t * right_impurity
- N_t_L / N_t * left_impurity)

где ``N`` — общее число образцов, ``N_t`` — число
образцов в текущем узле, ``N_t_L`` — число образцов в левом дочернем узле, а ``N_t_R`` — число образцов в правом дочернем узле.

Если передан ``sample_weight``, значения ``N``, ``N_t``, ``N_t_R`` и ``N_t_L`` обозначают взвешенную сумму.

.. versionadded:: 0.19
0.0
class_weight class_weight: dict, список dict или "balanced", по умолчанию=None

Веса классов в формате ``{class_label: weight}``.
Если задано None, предполагается, что вес всех классов равен единице. Для
задач с несколькими выходами можно передать список словарей в том же
порядке, что и столбцы y.

Для задач с несколькими выходами (включая многометочную классификацию) веса должны быть
заданы для каждого класса каждого столбца в отдельном словаре. Например,
для многометочной классификации с четырьмя классами веса следует задать так:
[{0: 1, 1: 1}, {0: 1, 1: 5}, {0: 1, 1: 1}, {0: 1, 1: 1}], а не так:
[{1:1}, {2:5}, {3:1}, {4:1}].

Режим "balanced" использует значения y, чтобы автоматически назначать
веса, обратно пропорциональные частотам классов во входных данных,
по формуле ``n_samples / (n_classes * np.bincount(y))``

Для нескольких выходов веса каждого столбца y перемножаются.

Если задан sample_weight, эти веса перемножаются с sample_weight (переданным
через метод fit).
None
ccp_alpha ccp_alpha: неотрицательное значение float, по умолчанию=0.0

Параметр сложности, используемый при обрезке по критерию минимальной стоимости-сложности. Будет выбрано
поддерево с наибольшей стоимостью-сложностью, меньшей
``ccp_alpha``. По умолчанию обрезка не выполняется. Подробности см. в
:ref:`minimal_cost_complexity_pruning`. Пример такой обрезки приведён в
:ref:`sphx_glr_auto_examples_tree_plot_cost_complexity_pruning.py`.

.. versionadded:: 0.22
0.0
monotonic_cst monotonic_cst: массивоподобный объект из int формы (n_features), по умолчанию=None

Задаёт ограничение монотонности, применяемое к каждому признаку.
- 1: монотонное возрастание
- 0: без ограничений
- -1: монотонное убывание

Если monotonic_cst равен None, ограничения не применяются.

Ограничения монотонности не поддерживаются для:
- многоклассовой классификации (т. е. когда `n_classes > 2`),
- классификации с несколькими выходами (т. е. когда `n_outputs_ > 1`).

Ограничения применяются к вероятности положительного класса.

Подробнее см. в :ref:`руководстве пользователя <monotonic_cst_gbdt>`.

.. versionadded:: 1.4
None
Обученные атрибуты
Имя Тип Значение
classes_ classes_: ndarray формы (n_classes,) или список ndarray

Метки классов (для задачи с одним выходом)
или список массивов с метками классов (для задачи с несколькими выходами).
ndarray[int64](3,) [0,1,2]
feature_importances_ feature_importances_: ndarray формы (n_features,)

Важность признаков, определённая на основе неоднородности.
Чем выше значение, тем важнее признак.
Важность признака вычисляется как (нормированное)
общее уменьшение критерия, обусловленное этим признаком. Этот показатель также
известен как важность Джини [4]_.

Предупреждение: важность признаков, основанная на неоднородности, может вводить в заблуждение для
признаков с большим числом уникальных значений. В качестве альтернативы см.
:func:`sklearn.inspection.permutation_importance`.
ndarray[float64](4,) [0. ,0. ,0.44,0.56]
max_features_ max_features_: int

Выведенное значение max_features.
int 4
n_classes_ n_classes_: int или список int

Число классов (для задач с одним выходом)
или список, содержащий число классов для каждого
выхода (для задач с несколькими выходами).
int64 np.int64(3)
n_features_in_ n_features_in_: int

Число признаков, наблюдавшихся во время :term:`обучения`.

.. versionadded:: 0.24
int 4
n_outputs_ n_outputs_: int

Число выходов при выполнении ``fit``.
int 1
tree_ tree_: экземпляр Tree

Базовый объект Tree. Описание атрибутов объекта Tree см. в
``help(sklearn.tree._tree.Tree)``, а примеры использования этих атрибутов — в
:ref:`sphx_glr_auto_examples_tree_plot_unveil_tree_structure.py`.
Tree <sklearn.tree...x784128310ce0>


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

У классификатора на дереве есть атрибут 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 — это трёхмерный массив формы [n_nodes, n_classes, n_outputs], который содержит долю образцов, достигших узла, для каждого класса и каждого выхода. Для каждого узла имеется массив value, содержащий долю взвешенных образцов, достигших этого узла, для каждого выхода и класса относительно родительского узла.

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

Например, в дереве выше, построенном на наборе данных iris, корневой узел имеет значение 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 в каждом узле будет двумерным.

Мы можем сравнить приведённый выше результат с графиком дерева решений. Здесь показаны доли образцов каждого класса, достигающих каждого узла, в соответствии с фактическими элементами массива 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.085 секунды)

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

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

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

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

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

Построение поверхности принятия решений для деревьев, обученных на наборе данных iris

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

Spec-Zone.ru

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