Análise de Árvore de Decisão

Beginner

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

Introdução

O classificador de árvore de decisão é um algoritmo de aprendizagem de máquina popular usado para problemas de classificação e regressão. É um modelo baseado em árvore que particiona o espaço de características em um conjunto de regiões não sobrepostas e prevê o valor-alvo para cada região. Neste laboratório, aprenderemos a analisar a estrutura da árvore de decisão para obter mais insights sobre a relação entre as características e o alvo a prever.

Dicas da Máquina Virtual

Após o arranque da máquina virtual, clique no canto superior esquerdo para mudar para a aba Notebook para aceder ao Jupyter Notebook para a prática.

Por vezes, pode ser necessário esperar alguns segundos para o Jupyter Notebook terminar o carregamento. A validação das operações não pode ser automatizada devido a limitações no Jupyter Notebook.

Se tiver problemas durante o aprendizado, não hesite em contactar o Labby. Forneça feedback após a sessão e resolveremos prontamente o problema para si.

Treinar um Classificador de Árvore de Decisão

Primeiro, precisamos ajustar um classificador de árvore de decisão usando o conjunto de dados load_iris do scikit-learn. Este conjunto de dados contém 3 classes de 50 instâncias cada, onde cada classe se refere a um tipo de planta íris. Vamos dividir o conjunto de dados em conjuntos de treino e teste e ajustar um classificador de árvore de decisão com um máximo de 3 nós folha.

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)

Analisar a Estrutura da Árvore Binária

O classificador de árvore de decisão possui um atributo chamado tree_ que permite o acesso a atributos de baixo nível, como node_count, o número total de nós, e max_depth, a profundidade máxima da árvore. Ele também armazena a estrutura da árvore binária completa, representada como um conjunto de matrizes paralelas. Usando essas matrizes, podemos percorrer a estrutura da árvore para calcular várias propriedades, como a profundidade de cada nó e se ele é ou não uma folha. Abaixo está o código para calcular essas propriedades:

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)]  ## começa com o ID do nó raiz (0) e sua profundidade (0)
while len(stack) > 0:
    ## `pop` garante que cada nó seja visitado apenas uma vez
    node_id, depth = stack.pop()
    node_depth[node_id] = depth

    ## Se o filho esquerdo e direito de um nó não forem iguais, temos um nó de divisão
    is_split_node = children_left[node_id] != children_right[node_id]
    ## Se um nó de divisão, adicione os filhos esquerdo e direito e a profundidade à `stack`
    ## para que possamos percorrer eles
    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(
    "A estrutura da árvore binária tem {n} nós e tem a seguinte estrutura da árvore:\n".format(n=n_nodes)
)
for i in range(n_nodes):
    if is_leaves[i]:
        print(
            "{space}nó={node} é um nó folha.".format(
                space=node_depth[i] * "\t", node=i
            )
        )
    else:
        print(
            "{space}nó={node} é um nó de divisão: "
            "vá para o nó {left} se X[:, {feature}] <= {threshold} "
            "senão para o nó {right}.".format(
                space=node_depth[i] * "\t",
                node=i,
                left=children_left[i],
                feature=feature[i],
                threshold=threshold[i],
                right=children_right[i],
            )
        )

Visualizar a Árvore de Decisão

Também podemos visualizar a árvore de decisão usando a função plot_tree do módulo tree do scikit-learn.

from sklearn import tree
import matplotlib.pyplot as plt

tree.plot_tree(clf)
plt.show()

Recuperar o Caminho de Decisão e Nós Folha

Podemos recuperar o caminho de decisão de amostras de interesse usando o método decision_path. Este método gera uma matriz indicadora que permite recuperar os nós pelos quais as amostras de interesse passam. Os IDs das folhas alcançadas pelas amostras de interesse podem ser obtidos com o método apply. Isto retorna um array dos IDs dos nós das folhas alcançadas por cada amostra de interesse. Usando os IDs das folhas e o decision_path, podemos obter as condições de divisão usadas para prever uma amostra ou um grupo de amostras. Abaixo está o código para recuperar o caminho de decisão e os nós folha para uma amostra:

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

sample_id = 0
## obter os IDs dos nós pelos quais a amostra `sample_id` passa, ou seja, a linha `sample_id`
node_index = node_indicator.indices[
    node_indicator.indptr[sample_id] : node_indicator.indptr[sample_id + 1]
]

print("Regras usadas para prever a amostra {id}:\n".format(id=sample_id))
for node_id in node_index:
    ## continuar para o próximo nó se for um nó folha
    if leaf_id[sample_id] == node_id:
        continue

    ## verificar se o valor da característica de divisão para a amostra 0 está abaixo do limiar
    if X_test[sample_id, feature[node_id]] <= threshold[node_id]:
        threshold_sign = "<="
    else:
        threshold_sign = ">"

    print(
        "nó de decisão {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],
        )
    )

Determinar Nós Comuns para um Grupo de Amostras

Para um grupo de amostras, podemos determinar os nós comuns pelos quais as amostras passam usando o método decision_path e o método toarray para converter a matriz indicadora em um array denso.

sample_ids = [0, 1]
## array booleano indicando os nós pelos quais ambas as amostras passam
common_nodes = node_indicator.toarray()[sample_ids].sum(axis=0) == len(sample_ids)
## obter os IDs dos nós usando a posição no array
common_node_id = np.arange(n_nodes)[common_nodes]

print(
    "\nAs seguintes amostras {samples} compartilham o(s) nó(s) {nodes} na árvore.".format(
        samples=sample_ids, nodes=common_node_id
    )
)
print("Isso representa {prop}% de todos os nós.".format(prop=100 * len(common_node_id) / n_nodes))

Resumo

Neste laboratório, aprendemos como analisar a estrutura da árvore de decisão para obter mais insights sobre a relação entre as características e a variável alvo a ser prevista. Vimos como recuperar a estrutura da árvore binária, visualizar a árvore de decisão e recuperar o caminho de decisão e os nós folha para uma amostra ou um grupo de amostras. Essas técnicas podem nos ajudar a entender melhor como o classificador de árvore de decisão faz suas previsões e podem nos guiar no ajuste fino do modelo para melhorar seu desempenho.