Control de complejidad en árboles de decisión con rpart: parámetros clave y ajuste

1. Introducción a los parámetros de control en rpart

Al construir un árbol de decisión, el sobreajuste es un problema habitual. El paquete rpart en R ofrece funciones para gestionar la complejidad del modelo mediante parámetros de control que limitan el crecimiento del árbol. La función rpart.control() permite definir estas restricciones de forma precisa.

Parámetros principales

  • cp (complexity parameter): umbral mínimo de mejora en precisión para realizar una división. Un valor bajo genera árboles más profundos.
  • minsplit: número mínimo de observaciones necesarias en un nodo para que pueda dividirse.
  • minbucket: número mínimo de observaciones permitido en una hoja. Normalmente se fija como un tercio de minsplit.
  • maxdepth: profundidad máxima del árbol, limita el número de niveles de división.

Ejemplo de configuración en R


# Cargar librería
library(rpart)

# Definir control personalizado
ctrl_arbol <- rpart.control(
  cp = 0.01,          # umbral de complejidad
  minsplit = 20,      # mínimo para dividir
  minbucket = 7,      # mínimo en hoja
  maxdepth = 10       # profundidad máxima
)

# Ajustar modelo de clasificación
modelo_iris <- rpart(Species ~ ., data = iris, method = "class", control = ctrl_arbol)

# Resumen del modelo
print(modelo_iris)

En este fragmento, rpart.control() establece restricciones que evitan que el árbol crezca excesivamente, mejorando la capacidad de generalización.

Valores por defecto de referencia

Parámetro Valor por defecto Descripción
cp 0.01 Umbral de penalización por complejidad
minsplit 20 Mínimo de muestras para dividir
minbucket 7 Mínimo de muestras en hoja
maxdepth 30 Profundidad máxima del árbol

2. Fundamentos teóricos del control de complejidad

2.1 Principio matemático del parámetro cp y la poda

El parámetro cp determina el nivel de poda del árbol. En la poda por coste-complejidad (Cost-Complexity Pruning), la función objetivo es:

R(T) = Σ (N_m * Q_m) + α * M

donde R(T) es el coste total del subárbol T, N_m el número de muestras en la hoja m, Q_m el error en esa hoja, α el valor de cp y M el número total de hojas. Un cp pequeño permite más hojas (sobreajuste); uno grande simplifica demasiado (subajuste). El valor óptimo se elige mediante validación cruzada minimizando el error de validación.

2.2 Influencia de los parámetros de complejidad en el crecimiento del árbol

El crecimiento del árbol está gobernado por la interacción de estos parámetros. Por ejemplo, maxdepth limita la profundidad; cp filtra divisiones con poca ganancia. En Python (scikit-learn), el equivalente a cp es ccp_alpha:


from sklearn.tree import DecisionTreeClassifier

clf = DecisionTreeClassifier(ccp_alpha=0.01)
clf.fit(X_train, y_train)

Un valor mayor de ccp_alpha reduce el número de nodos, controlando el sobreajuste.

2.3 Compensación entre sobreajuste y subajuste

El parámetro cp actúa como regulador: un valor elevado solo permite divisiones que reduzcan significativamente el error, evitando aprender ruido; un valor bajo genera árboles complejos con riesgo de sobreajuste.

2.4 Pureza de nodo, ganancia de división y cp

La pureza se mide con índices como Gini. La ganancia de una división es:

Ganancia = Gini_padre - Σ (n_hijo / n_padre) * Gini_hijo

El parámetro cp exige que la ganancia supere un umbral (cp * número total de muestras). Así, cp actúa como un filtro de ganancia mínima.

2.5 Validación cruzada para seleccionar la complejidad óptima

La validación cruzada (por ejemplo, k-fold) proporciona una estimación insesgada del error de generalización. Se repite el proceso para diferentes valores de cp y se elige el que minimiza el error medio.


from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeClassifier
import numpy as np

modelo = DecisionTreeClassifier(ccp_alpha=0.01)
puntuaciones = cross_val_score(modelo, X, y, cv=5, scoring='accuracy')
print("Precisión media CV:", np.mean(puntuaciones))

3. Interacción entre parámetros de control

3.1 minsplit y minbucket como restricciones al cp

minsplit y minbucket imponen límites físicos al crecimiento del árbol, independientemente de la ganancia en pureza. Si un nodo no cumple minsplit, no se divide aunque la ganancia supere cp. Similarmente, minbucket evita hojas demasiado pequeñas.


rpart(y ~ ., data = df, 
      control = rpart.control(minsplit = 20, 
                              minbucket = 7, 
                              cp = 0.01))

En este ejemplo, una división solo es posible si los nodos hijos tienen al menos 7 observaciones.

3.2 Efecto de maxdepth en la complejidad

El parámetro maxdepth trunca el árbol a una profundidad máxima. Esto puede causar pérdida de información si se fija muy bajo, pero reduce el tiempo de cómputo y evita ramas muy específicas. La elección del valor óptimo depende del problema.

3.3 Combinación de validación cruzada y cp

Incorporar cp dentro de cada pliegue de validcaión cruzada permite seleccionar el mejor modelo de cada pliegue. Esto mejora la estabilidad y la capacidad de generalización.

4. Práctica de ajuste con conjuntos de datos reales

4.1 Exploración de la secuencia cp con el conjunto iris

Usando el conjunto iris en R, se puede observar cómo varía el error con diferentes cp:


library(rpart)
arbol <- rpart(Species ~ ., data = iris, method = "class", cp = 0.01)
printcp(arbol)

La salida muestra el valor de cp para cada división y su error relativo. Esto ayuda a identificar el punto donde el error de validación cruzada es mínimo.

4.2 Determinación del cp óptimo mediante validación cruzada en un conjunto de automóviles

El paquete caret facilita la búsqueda sistemática:


library(rpart)
library(caret)

set.seed(42)
control_train <- trainControl(method = "cv", number = 10)
rejilla_cp <- seq(0.001, 0.1, by = 0.005)

modelo_ajustado <- train(Class ~ ., data = car, method = "rpart",
                         trControl = control_train, 
                         tuneGrid = data.frame(cp = rejilla_cp))

print(modelo_ajustado$bestTune)

El resultado muestra que cp = 0.01 proporciona la mejor precisión con un modelo simple.

4.3 Visualización de la curva de error (cpplot)

La función plotcp() en R dibuja el error de validación cruzada frente a cp, ayudando a elegir el valor óptimo.


plotcp(arbol)
abline(h = min(arbol$cptable[, "xerror"]) + 0.01, col = "red", lty = 2)

La línea roja marca una desviación estándar por encima del mínimo, criterio común para seleccionar el modelo más simple dentro del rango de error.

4.4 Ajuste con matriz de costos personalizada

En problemas con costos asimétricos (por ejemplo, diagnóstico médico), se puede definir una matriz de pérdidas que modifica el criterio de división. En scikit-learn, esto se logra mediante el parámetro class_weight o creando una función de pérdida personalizada.


from sklearn.tree import DecisionTreeClassifier
import numpy as np

# Matriz de costos: penalizar más falsos negativos
cost_matrix = np.array([[0, 1], [10, 0]])  # filas: real, columnas: predicho
# Nota: scikit-learn no acepta directamente matriz de costos en árboles, 
# pero se puede usar sample_weight para simular costos.
pesos = np.where(y == 0, 1, 10)  # mayor peso a la clase positiva
clf = DecisionTreeClassifier(ccp_alpha=0.01)
clf.fit(X, y, sample_weight=pesos)

Al aumentar el peso de las muestras de la clase de interés, el árbol tiende a evitar errores costosos, afectando la elección de cp.

5. Limitaciones del control de complejidad y direcciones futuras

Las métricas tradicionales como la complejidad ciclomática no capturan problemas arquitectónicos como el acoplamiento en microservicios. En sistemas modernos, la configuración declarativa (por ejemplo, Kubernetes) puede ocultar complejidades de estado. Se requieren enfoques basados en comportamiento, como el análisis de grafos de llamadas o el agrupamiento de logs, para medir la complejidad real en tiempo de ejecución.

Etiquetas: rpart árboles de decisión complejidad poda validación cruzada

Publicado el 6-4 22:31