Ingeniería y Programación de Software
Arboles de decisión de construcción de la escotilla: Tutorial de codificación de un principiante
Table of Contents
Los árboles de decisión son uno de los algoritmos de aprendizaje automático más intuitivos y ampliamente utilizados para clasificar y regresión. Trabajan dividiendo datos en ramas basadas en valores de características, imitando la forma en que los humanos toman decisiones. Mientras que las bibliotecas como scikit-learn hacen que los árboles de decisión de construcción sean triviales, implementar uno desde cero es una excelente manera para que los principiantes captan el trabajo interno del algoritmo.
¿Qué es un árbol de decisiones?
Un árbol de decisión es una estructura similar al diagrama de flujo donde cada nodo interno representa una prueba en una característica (por ejemplo, “¿Es edad √≥ 30?”), cada rama representa el resultado de esa prueba, y cada nodo de hoja tiene una etiqueta de clase o valor continuo. El objetivo es crear un modelo que predice una variable objetivo mediante el aprendizaje de reglas de decisión simples inferidas de las características de datos.
El árbol se construye recursivamente: a partir de la raíz, el algoritmo selecciona la mejor característica y punto de división que separa los datos más limpiamente. Este proceso se repite en cada subconjunto hasta que se cumpla una condición de parada. Para más fondo, la entrada de Wikipedia en el aprendizaje de los árboles de decisión proporciona una visión sólida.
Conceptos básicos que debes entender
Nodos, ramas y hojas
El nodo raíz contiene todo el conjunto de datos de entrenamiento. Los ganglios internos prueban una característica y dividen los datos en dos o más nodos infantiles. Las ramas son las conexiones que representan el resultado de una prueba. Los nodos de hoja (nodos de final) producen la predicción final – la clase más común en clasificación o el valor medio en la regresión.
Criterios de división
Para construir un árbol, necesitas una manera de medir la calidad de una división potencial. Los criterios más comunes son:
- Impureza de Gini] – utilizada en la clasificación para medir con qué frecuencia se etiquetaría incorrectamente un elemento elegido aleatoriamente si se le etiquetara aleatoriamente de acuerdo con la distribución de clases en el subconjunto.
- Entropía] – mide la cantidad de desorden o incertidumbre en un conjunto. El objetivo es minimizar la entropía después de la división (ganancia de información).
- Reducción de la violencia] – utilizada para árboles de regresión. Calcula la reducción de la varianza (o error cuadrado medio) logrado por la división.
El algoritmo evalúa cada posible división en cada característica y elige el que produce la mayor reducción de la impureza (o ganancia en información).
Relación entre la ganancia de información y la ganancia
La ganancia de información es la diferencia entre la impureza del nodo padre y la suma ponderada de impurezas infantiles. Si bien es simple, tiende a favorecer las características con muchos valores. La relación ganancia (utilizada en C4.5) normaliza esto. Para este tutorial nos adheriremos con la ganancia de información estándar utilizando la impureza Gini, que es el predeterminado en CART (Arboles de Clasificación y Regresividad).
Creación de un árbol de decisiones paso a paso
1. Prepare sus datos
Necesita un conjunto de datos con características y etiquetas de destino. Para la simplicidad, utilice un conjunto de datos de clasificación binaria con características numéricas. Por ejemplo:
- Características: Edad, Ingresos
- Target: Aprobado (1) o No Aprobado (0)
Limpiar los datos: manejar los valores perdidos, eliminar los duplicados y asegurar los tipos numéricos. Los árboles de decisiones pueden manejar tipos de datos mixtos, pero nos adheriremos a la numérica para la implementación.
2. Definir una función de Criterio Dividido
Implementaremos la impureza Gini. El índice Gini para un conjunto de elementos es:
donde p i es la proporción de artículos en la clase i. Para una división binaria, el Gini general es el promedio ponderado de los ganglios infantiles.
3. Implementar la Evaluación de Divisas
Para cada característica, clasificar los valores únicos. Prueba cada umbral posible (punto medio entre valores ordenados consecutivos). Para cada umbral de candidato, dividir los datos en grupos izquierdo y derecho, computar el Gini y seguir la mejor división.
4. Construir el Árbol Recursivamente
Cree una función que tome un subconjunto de datos y una profundidad actual. Chequea las condiciones de parada (por ejemplo, la máxima profundidad alcanzada, muestras mínimas por nodo o ninguna ganancia de información). Si se cumple una condición, cree un nodo de hoja con la clase mayoritaria. De lo contrario, encuentre la mejor división y cree un nodo interno, luego llame recursivamente a la función en las divisiones izquierda y derecha.
5. Hacer predicciones
Una vez construido el árbol, la predicción es directa: comienza en la raíz, sigue las ramas evaluando las pruebas de características de la nueva muestra, y devuelve el valor de la hoja que aterrizas.
Implementación completa en Python
A continuación se muestra una implementación completa y mínima de un árbol de decisiones para la clasificación utilizando la impureza Gini. Este código está destinado a aprender – no es optimizado para grandes conjuntos de datos.
import numpy as np
from collections import Counter
class DecisionTree:
def __init__(self, max_depth=None, min_samples_split=2):
self.max_depth = max_depth
self.min_samples_split = min_samples_split
self.tree = None
def fit(self, X, y):
dataset = np.column_stack((X, y))
self.tree = self._grow_tree(dataset)
def _grow_tree(self, dataset, depth=0):
X, y = dataset[:, :-1], dataset[:, -1]
n_samples, n_features = X.shape
n_labels = len(np.unique(y))
# Stopping conditions
if (n_labels == 1 or depth == self.max_depth or n_samples < self.min_samples_split):
leaf_value = Counter(y).most_common(1)[0][0]
return {'leaf': True, 'value': leaf_value}
best_feature, best_threshold = self._best_split(dataset, n_features)
if best_feature is None:
leaf_value = Counter(y).most_common(1)[0][0]
return {'leaf': True, 'value': leaf_value}
left_idx, right_idx = self._split(dataset[:, best_feature], best_threshold)
left_subtree = self._grow_tree(dataset[left_idx], depth+1)
right_subtree = self._grow_tree(dataset[right_idx], depth+1)
return {'leaf': False,
'feature': best_feature,
'threshold': best_threshold,
'left': left_subtree,
'right': right_subtree}
def _best_split(self, dataset, n_features):
best_gini = float('inf')
best_feature, best_threshold = None, None
for feature in range(n_features):
thresholds = np.unique(dataset[:, feature])
for i in range(len(thresholds)-1):
thresh = (thresholds[i] + thresholds[i+1]) / 2
left_idx, right_idx = self._split(dataset[:, feature], thresh)
if len(left_idx) == 0 or len(right_idx) == 0:
continue
gini = self._gini_gain(dataset, left_idx, right_idx)
if gini < best_gini:
best_gini = gini
best_feature = feature
best_threshold = thresh
return best_feature, best_threshold
def _split(self, values, threshold):
left_idx = np.where(values <= threshold)[0]
right_idx = np.where(values > threshold)[0]
return left_idx, right_idx
def _gini_gain(self, dataset, left_idx, right_idx):
total = len(left_idx) + len(right_idx)
gini_left = self._gini(dataset[left_idx, -1])
gini_right = self._gini(dataset[right_idx, -1])
return (len(left_idx)/total) * gini_left + (len(right_idx)/total) * gini_right
def _gini(self, labels):
_, counts = np.unique(labels, return_counts=True)
p = counts / np.sum(counts)
return 1 - np.sum(p**2)
def predict(self, X):
return np.array([self._predict_row(x, self.tree) for x in X])
def _predict_row(self, x, node):
if node['leaf']:
return node['value']
if x[node['feature']] <= node['threshold']:
return self._predict_row(x, node['left'])
else:
return self._predict_row(x, node['right'])
Pruebas del árbol
Utilice un conjunto de datos simple como el conjunto de datos clásico de iris (dos características para la clasificación binaria). ]]scikit‐learn Iris dataset funciona bien. Compare la exactitud de su árbol con scikit‐learn para verificar la corrección.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
data = load_iris()
X = data.data[:100] # take only first two classes (binary)
y = data.target[:100]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
tree = DecisionTree(max_depth=3)
tree.fit(X_train, y_train)
preds = tree.predict(X_test)
accuracy = np.mean(preds == y_test)
print(f'Accuracy: {accuracy:.2f}')
Técnicas avanzadas para mejorar su árbol
Pruning to avoid Overfitting
Un árbol completamente crecido puede memorizar el ruido en los datos de entrenamiento. Pruning elimina ramas que tienen poca potencia predictiva. Los métodos comunes son pre-corrimiento (profundización tempranamente a través de o ) y post-corte (creciendo el árbol completo luego la eliminación de ramas mediante un sistema de validación o la poda de costos-complexidad).
Manejo de características continuas y cagorísticas
Para características continuas, usamos puntos intermedios entre valores ordenados como umbrales. Para características clasificadas (por ejemplo, “Color = rojo/verde/blue”), cada categoría puede convertirse en una rama separada (multi-way split) o puede código binario. La mayoría de las implementaciones modernas (como scikit‐learn) utilizan divisiones binarias incluso para características categóricas evaluando todos los subconjuntos.
Tratar con los valores perdidos
Los datos del mundo real a menudo tienen valores perdidos. Un simple enfoque es asignar valores perdidos a la rama más frecuente entre las muestras de entrenamiento que tienen la característica. C4.5 utiliza un método probabilístico. Puesto que este es un tutorial principiante, suponemos que los datos están completos.
Comparación con Bibliotecas y lecturas posteriores
Mientras que construir desde cero es educativo, los sistemas de producción utilizan bibliotecas como scikit‐learn que proporcionan implementaciones C optimizadas. Usted puede aprender más del oficial ]scikit‐learn documentación de los árboles de decisión. Para una teoría más profunda, el libro “Los elementos del aprendizaje estadístico” de Hastie, Tibshirani y Friedman es un recurso de referencia original.
Conclusión
La construcción de un árbol de decisión desde cero desmitifica uno de los algoritmos más fundamentales en el aprendizaje automático. Ha aprendido cómo un simple procedimiento de división recursiva puede producir un modelo poderoso. Al escribir el código usted mismo, obtiene una comprensión más profunda de las medidas de impureza, selección dividida y los beneficios entre sesgo y varianza. Como un próximo paso, trate de añadir apoyo de regresión, podación o manejo de características clasificatorias.