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 deminsplit.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.