1. ParamGridBuilder: Construcción del Espacio de Búsqueda
API y Funcionamiento
ParamGridBuilder es la clase de Spark ML
para construir el producto cartesiano de hiperparámetros. Permite especificar valores discretos para cada
hiperparámetro y genera automáticamente todas las combinaciones.
from pyspark.ml.tuning import ParamGridBuilder
# Crear builder
builder = ParamGridBuilder()
# Agregar grids para cada hiperparámetro (método encadenado)
paramGrid = builder \
.addGrid(estimator.param1, [value1, value2, ...]) \ # Grid para param1
.addGrid(estimator.param2, [value1, value2, ...]) \ # Grid para param2
.build() # Construir producto cartesiano
# Resultado: Lista de ParamMaps (diccionarios de configuración)
# Tamaño: len(paramGrid) = len(values_param1) × len(values_param2) × ...
Ejemplo Completo: Regresión Logística
from pyspark.ml.classification import LogisticRegression
from pyspark.ml.tuning import ParamGridBuilder
# Definir modelo
lr = LogisticRegression(featuresCol="features", labelCol="label")
# Construir grid de hiperparámetros
paramGrid = ParamGridBuilder() \
.addGrid(lr.regParam, [0.001, 0.01, 0.1, 1.0, 10.0]) \ # Regularización: 5 valores
.addGrid(lr.elasticNetParam, [0.0, 0.25, 0.5, 0.75, 1.0]) \ # Elastic Net: 5 valores
.addGrid(lr.maxIter, [50, 100, 200]) \ # Iteraciones: 3 valores
.build()
# Tamaño del grid
print(f"Total configs: {len(paramGrid)}") # Output: 75 (5 × 5 × 3)
# Inspeccionar primera configuración
print(paramGrid[0])
# Output: {Param(parent='LogisticRegression_...', name='regParam', ...): 0.001,
# Param(parent='LogisticRegression_...', name='elasticNetParam', ...): 0.0,
# Param(parent='LogisticRegression_...', name='maxIter', ...): 50}
# ============================================================
# Conexión Matemática: Producto Cartesiano
# ============================================================
# Θ = Θ_regParam × Θ_elasticNet × Θ_maxIter
# |Θ| = |{0.001, 0.01, 0.1, 1.0, 10.0}| × |{0.0, 0.25, 0.5, 0.75, 1.0}| × |{50, 100, 200}|
# = 5 × 5 × 3 = 75 configuraciones
Tipos de Valores Soportados
| Tipo | Ejemplo | Uso |
|---|---|---|
| Enteros | [10, 50, 100, 200] | numTrees, maxIter, maxBins |
| Flotantes | [0.01, 0.1, 1.0] | regParam, stepSize, subsamplingRate |
| Cadenas | ["gini", "entropy"] | impurity (criterio de split) |
| Listas/Tuplas | [[10,5], [20,10,5]] | layers (arquitectura de red neuronal) |
Estrategias para Seleccionar Valores del Grid
1. Escala Logarítmica (regParam, stepSize)
Para parámetros que varían órdenes de magnitud, usar escala log:
# Malo: escala lineal
[0.1, 0.2, 0.3, 0.4, 0.5] # Muy concentrado
# Bueno: escala logarítmica
[0.001, 0.01, 0.1, 1.0, 10.0] # Cubre 4 órdenes
# En Python
import numpy as np
np.logspace(-3, 1, 5) # [0.001, 0.01, 0.1, 1, 10]
2. Escala Lineal (numTrees, maxDepth)
Para parámetros enteros con rango acotado:
# Bueno: espaciado uniforme
numTrees: [10, 50, 100, 200]
maxDepth: [5, 10, 15, 20, 25]
# En Python
list(range(5, 30, 5)) # [5, 10, 15, 20, 25]
Regla Práctica: Coarse-to-Fine
- 1. Grid grueso: Pocos valores (3-4 por dim), amplio rango → identificar región óptima
- 2. Grid fino: Más valores (5-7 por dim), rango estrecho alrededor del óptimo
2. Evaluadores: Cuantificando el Rendimiento
Tipos de Evaluadores en Spark ML
Los evaluadores son objetos que calculan una métrica de rendimiento sobre predicciones. CrossValidator usa el evaluador para seleccionar la mejor configuración:
(Nota: CrossValidator maximiza la métrica del evaluador, por eso usamos argmax)
Catálogo de Evaluadores
BinaryClassificationEvaluator
Para clasificación binaria (2 clases). Métricas disponibles:
areaUnderROC (default):
Área bajo curva ROC. Mide separabilidad: AUC=1 perfecto, AUC=0.5 azar.
areaUnderPR:
Área bajo curva Precision-Recall. Mejor para datasets desbalanceados.
from pyspark.ml.evaluation import BinaryClassificationEvaluator
evaluator = BinaryClassificationEvaluator(
labelCol="label", # Columna de etiquetas verdaderas
rawPredictionCol="rawPrediction", # Scores del modelo (antes de threshold)
metricName="areaUnderROC" # "areaUnderROC" o "areaUnderPR"
)
auc = evaluator.evaluate(predictions)
print(f"AUC-ROC: {auc:.4f}")
MulticlassClassificationEvaluator
Para clasificación multiclase (3+ clases). Métricas disponibles:
| Métrica | Fórmula | Interpretación |
|---|---|---|
| accuracy | $\frac{TP + TN}{N}$ | % predicciones correctas |
| f1 | $\frac{2 \cdot P \cdot R}{P + R}$ | Media armónica de precision/recall |
| weightedPrecision | $\sum_i w_i P_i$ | Precision ponderada por clase |
| weightedRecall | $\sum_i w_i R_i$ | Recall ponderado por clase |
from pyspark.ml.evaluation import MulticlassClassificationEvaluator
evaluator = MulticlassClassificationEvaluator(
labelCol="label",
predictionCol="prediction", # Clase predicha (no raw scores)
metricName="f1" # "f1", "accuracy", "weightedPrecision", etc.
)
f1_score = evaluator.evaluate(predictions)
print(f"F1-Score: {f1_score:.4f}")
RegressionEvaluator
Para regresión (predicción de valores continuos). Métricas:
rmse (default):
$$ \text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2} $$Root Mean Squared Error. Penaliza errores grandes.
mae:
$$ \text{MAE} = \frac{1}{n} \sum_{i=1}^{n} |y_i - \hat{y}_i| $$Mean Absolute Error. Más robusto a outliers.
r2:
$$ R^2 = 1 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2} $$Coeficiente de determinación. R²=1 ajuste perfecto.
mse:
$$ \text{MSE} = \frac{1}{n} \sum_{i=1}^{n} (y_i - \hat{y}_i)^2 $$Mean Squared Error. Cuadrado del RMSE.
from pyspark.ml.evaluation import RegressionEvaluator
evaluator = RegressionEvaluator(
labelCol="price", # Valor real
predictionCol="prediction", # Valor predicho
metricName="rmse" # "rmse", "mae", "r2", "mse"
)
rmse = evaluator.evaluate(predictions)
print(f"RMSE: {rmse:.2f}")
Importante: Maximización vs Minimización
CrossValidator siempre maximiza la métrica del evaluador. Esto es intuitivo para métricas como AUC, F1, R² (mayor = mejor), pero contra-intuitivo para errores como RMSE, MSE (menor = mejor).
Solución: Spark invierte automáticamente
- • Para RMSE/MSE/MAE: CrossValidator maximiza
-RMSEinternamente (equivalente a minimizar RMSE) - • Para AUC/F1/R²: CrossValidator maximiza directamente
- • El usuario no necesita hacer nada, Spark maneja esto transparentemente
3. Tuning de Pipelines Completos
Optimizando Múltiples Stages Simultáneamente
Una de las capacidades más poderosas de Spark ML es tunear todo el pipeline de una vez, incluyendo transformadores (feature engineering) y estimadores (modelos). Esto permite encontrar la combinación óptima de preprocesamiento + modelo.
Arquitectura de Pipeline Tuning
graph LR
A[Raw Data] --> B[VectorAssembler]
B --> C[StandardScaler]
C --> D[PCA]
D --> E[RandomForest]
E --> F[Predictions]
G[ParamGrid] -.-> C
G -.-> D
G -.-> E
H[CrossValidator] --> G
H --> I[Best Pipeline Model]
style A fill:#9333ea,stroke:#a855f7,color:#fff
style G fill:#f59e0b,stroke:#fbbf24,color:#000
style H fill:#10b981,stroke:#34d399,color:#fff
style I fill:#ef4444,stroke:#f87171,color:#fff
CrossValidator optimiza hiperparámetros de múltiples stages (Scaler, PCA, RF) simultáneamente
Ejemplo Completo: Pipeline con PCA + Random Forest
from pyspark.ml import Pipeline
from pyspark.ml.feature import VectorAssembler, StandardScaler, PCA
from pyspark.ml.classification import RandomForestClassifier
from pyspark.ml.evaluation import BinaryClassificationEvaluator
from pyspark.ml.tuning import CrossValidator, ParamGridBuilder
# ============================================================
# PASO 1: Construir Pipeline con múltiples stages
# ============================================================
# Stage 1: Ensamblar features
assembler = VectorAssembler(
inputCols=["f1", "f2", "f3", "f4", "f5"],
outputCol="raw_features"
)
# Stage 2: Estandarizar (media 0, std 1)
scaler = StandardScaler(
inputCol="raw_features",
outputCol="scaled_features",
withMean=True,
withStd=True
)
# Stage 3: Reducción dimensional con PCA
pca = PCA(
inputCol="scaled_features",
outputCol="features"
# k será tuneable (número de componentes principales)
)
# Stage 4: Modelo Random Forest
rf = RandomForestClassifier(
featuresCol="features",
labelCol="label",
seed=42
)
# Construir pipeline
pipeline = Pipeline(stages=[assembler, scaler, pca, rf])
# ============================================================
# PASO 2: Definir grid tuneable para MÚLTIPLES stages
# ============================================================
paramGrid = ParamGridBuilder() \
.addGrid(pca.k, [3, 5, 7]) \ # PCA: 3 valores (Stage 3)
.addGrid(rf.numTrees, [50, 100, 200]) \ # RF: 3 valores (Stage 4)
.addGrid(rf.maxDepth, [10, 15, 20]) \ # RF: 3 valores (Stage 4)
.addGrid(rf.subsamplingRate, [0.7, 0.85, 1.0]) \ # RF: 3 valores (Stage 4)
.build()
# Tamaño del grid: 3 × 3 × 3 × 3 = 81 configuraciones
print(f"Configuraciones a evaluar: {len(paramGrid)}")
# ============================================================
# PASO 3: Configurar evaluador
# ============================================================
evaluator = BinaryClassificationEvaluator(
labelCol="label",
metricName="areaUnderROC"
)
# ============================================================
# PASO 4: CrossValidator para pipeline completo
# ============================================================
cv = CrossValidator(
estimator=pipeline, # ← Pipeline completo (no solo modelo)
estimatorParamMaps=paramGrid,
evaluator=evaluator,
numFolds=5,
parallelism=8,
seed=42
)
# ============================================================
# PASO 5: Entrenar (tunea TODOS los stages)
# ============================================================
print("Iniciando tuning de pipeline completo...")
print("Esto optimizará: PCA (k) + RF (numTrees, maxDepth, subsamplingRate)")
cvModel = cv.fit(df_train)
# ============================================================
# PASO 6: Extraer mejor pipeline
# ============================================================
bestPipeline = cvModel.bestModel # Es un PipelineModel (no solo RF)
# Extraer componentes del mejor pipeline
best_pca = bestPipeline.stages[2] # Stage 3: PCA
best_rf = bestPipeline.stages[3] # Stage 4: RandomForest
print("\n========== MEJOR CONFIGURACIÓN ==========")
print(f"PCA k (componentes): {best_pca.getK()}")
print(f"RF numTrees: {best_rf.getNumTrees}")
print(f"RF maxDepth: {best_rf.getMaxDepth()}")
print(f"RF subsamplingRate: {best_rf.getSubsamplingRate()}")
# Mejor AUC
best_auc = max(cvModel.avgMetrics)
print(f"\nMejor AUC (CV): {best_auc:.4f}")
# ============================================================
# PASO 7: Usar pipeline completo para predicciones
# ============================================================
# El bestPipeline contiene TODAS las transformaciones + modelo
predictions = bestPipeline.transform(df_test)
# Evaluar en test
test_auc = evaluator.evaluate(predictions)
print(f"AUC en Test: {test_auc:.4f}")
# ============================================================
# OBSERVACIÓN CRÍTICA
# ============================================================
# Al tunear el pipeline completo:
# 1. Cada configuración entrena TODO el pipeline (assembler → scaler → PCA → RF)
# 2. PCA con k=3 puede funcionar mejor con RF(numTrees=200, maxDepth=10)
# 3. PCA con k=7 puede funcionar mejor con RF(numTrees=50, maxDepth=20)
# 4. El tuning encuentra la MEJOR COMBINACIÓN CONJUNTA de todos los hiperparámetros
Ventajas del Pipeline Tuning
Beneficios Técnicos:
-
✓
Optimización Holística: Encuentra combinación óptima de preprocesamiento + modelo (no secuencial)
-
✓
Evita Data Leakage: Scaler y PCA se ajustan solo en train folds, no en val fold
-
✓
Reproducibilidad: Todo el pipeline queda encapsulado en bestModel
Casos de Uso:
- • Tunear dimensionalidad PCA + parámetros del modelo
- • Optimizar grado de PolynomialExpansion + regParam
- • Seleccionar features (ChiSqSelector k) + modelo
- • Comparar diferentes transformaciones (con/sin PCA)
4. Extracción y Análisis de Resultados
Atributos de CrossValidatorModel
Después de cv.fit(), el objeto cvModel contiene información detallada
sobre todas las configuraciones evaluadas.
# Entrenar CrossValidator
cvModel = cv.fit(df_train)
# ============================================================
# Atributos principales del cvModel
# ============================================================
# 1. bestModel: Mejor modelo encontrado (ya entrenado en TODO el dataset)
bestModel = cvModel.bestModel
# 2. avgMetrics: Lista con métrica promedio de CV para cada config
avg_metrics = cvModel.avgMetrics # Lista de floats, len = |paramGrid|
# 3. stdMetrics: Desviación estándar de la métrica para cada config (si collectSubModels=True)
# std_metrics = cvModel.stdMetrics # Requiere collectSubModels=True en CrossValidator
# 4. getEstimatorParamMaps: Grid de configuraciones evaluadas
param_maps = cvModel.getEstimatorParamMaps() # Lista de ParamMaps
# ============================================================
# Encontrar índice de mejor configuración
# ============================================================
best_idx = avg_metrics.index(max(avg_metrics))
best_params = param_maps[best_idx]
best_avg_metric = avg_metrics[best_idx]
print(f"Mejor configuración (índice {best_idx}):")
print(f"Parámetros: {best_params}")
print(f"Métrica promedio (CV): {best_avg_metric:.4f}")
Análisis Completo de Resultados
import pandas as pd
import matplotlib.pyplot as plt
# ============================================================
# Convertir resultados a DataFrame para análisis
# ============================================================
results = []
for idx, (params, avg_metric) in enumerate(zip(cvModel.getEstimatorParamMaps(), cvModel.avgMetrics)):
config = {"config_id": idx, "avg_metric": avg_metric}
# Extraer cada hiperparámetro del ParamMap
for param, value in params.items():
param_name = param.name # Nombre del parámetro (ej: "numTrees")
config[param_name] = value
results.append(config)
results_df = pd.DataFrame(results)
# ============================================================
# Top 10 configuraciones
# ============================================================
print("========== TOP 10 CONFIGURACIONES ==========")
top10 = results_df.nlargest(10, "avg_metric")
print(top10)
# ============================================================
# Estadísticas por hiperparámetro individual
# ============================================================
print("\n========== IMPACTO POR HIPERPARÁMETRO ==========")
for col in results_df.columns:
if col not in ["config_id", "avg_metric"]:
print(f"\n{col}:")
stats = results_df.groupby(col)["avg_metric"].agg(["mean", "std", "min", "max"])
print(stats.sort_values("mean", ascending=False))
# Ejemplo Output:
# mean std min max
# numTrees
# 200 0.8623 0.0076 0.8456 0.8712 ← Mejor promedio
# 100 0.8589 0.0081 0.8423 0.8689
# 50 0.8534 0.0093 0.8312 0.8656
# 10 0.8234 0.0123 0.8012 0.8445
# ============================================================
# Visualización: Heatmap 2D (si grid es 2D)
# ============================================================
# Supongamos grid con solo numTrees y maxDepth
if "numTrees" in results_df.columns and "maxDepth" in results_df.columns:
pivot = results_df.pivot(index="maxDepth", columns="numTrees", values="avg_metric")
plt.figure(figsize=(10, 6))
plt.imshow(pivot, cmap="viridis", aspect="auto")
plt.colorbar(label="AUC-ROC")
plt.xlabel("numTrees")
plt.ylabel("maxDepth")
plt.title("Heatmap de Rendimiento: Grid Search")
plt.xticks(range(len(pivot.columns)), pivot.columns)
plt.yticks(range(len(pivot.index)), pivot.index)
plt.show()
# ============================================================
# Identificar configuraciones Pareto-óptimas
# ============================================================
# Configs que no son dominadas por ninguna otra en todas las métricas
# (útil si tienes múltiples métricas: AUC, tiempo, complejidad)
Ejemplo de Heatmap de Rendimiento
Grid Search: numTrees vs maxDepth
numTrees →
maxDepth ↓ 50 100 200
5 0.8234 0.8345 0.8412
10 0.8456 0.8567 0.8623 ← max
15 0.8389 0.8512 0.8589
20 0.8312 0.8445 0.8534
🔥 Mejor: (numTrees=200, maxDepth=10) → AUC=0.8623
5. TrainValidationSplit: Alternativa Rápida
Comparación con CrossValidator
TrainValidationSplit es una
alternativa más rápida a CrossValidator que usa una sola división train/val en lugar de K-fold CV.
CrossValidator (K-fold CV)
- • K entrenamientos por configuración
- • Estimador más robusto (menos varianza)
- • Más costoso (K veces más lento)
- • Recomendado para datasets pequeños/medianos
TrainValidationSplit
- • 1 entrenamiento por configuración
- • Más rápido (K veces más que CV)
- • Mayor varianza (depende del split)
- • Recomendado para datasets grandes (>100K)
Implementación con TrainValidationSplit
from pyspark.ml.tuning import TrainValidationSplit, ParamGridBuilder
from pyspark.ml.classification import RandomForestClassifier
from pyspark.ml.evaluation import BinaryClassificationEvaluator
# ============================================================
# Configuración (similar a CrossValidator)
# ============================================================
rf = RandomForestClassifier(featuresCol="features", labelCol="label", seed=42)
paramGrid = ParamGridBuilder() \
.addGrid(rf.numTrees, [50, 100, 200]) \
.addGrid(rf.maxDepth, [10, 15, 20]) \
.build()
evaluator = BinaryClassificationEvaluator(
labelCol="label",
metricName="areaUnderROC"
)
# ============================================================
# TrainValidationSplit en lugar de CrossValidator
# ============================================================
tvs = TrainValidationSplit(
estimator=rf,
estimatorParamMaps=paramGrid,
evaluator=evaluator,
trainRatio=0.8, # 80% train, 20% val (parámetro clave)
parallelism=4,
seed=42
)
# Complejidad: 9 configs × 1 split = 9 entrenamientos
# (vs CrossValidator con K=5: 9 × 5 = 45 entrenamientos)
# ============================================================
# Entrenar
# ============================================================
print("Iniciando TrainValidationSplit...")
print(f"Configuraciones: {len(paramGrid)}")
print("Split: 80% train / 20% val")
tvsModel = tvs.fit(df_train)
# ============================================================
# Extraer resultados (API idéntica a CrossValidator)
# ============================================================
bestModel = tvsModel.bestModel
avg_metrics = tvsModel.validationMetrics # Nota: "validationMetrics", no "avgMetrics"
best_idx = avg_metrics.index(max(avg_metrics))
best_auc = avg_metrics[best_idx]
print(f"\nMejor AUC (validación): {best_auc:.4f}")
print(f"Mejor numTrees: {bestModel.getNumTrees}")
print(f"Mejor maxDepth: {bestModel.getMaxDepth()}")
# ============================================================
# Evaluación en test
# ============================================================
predictions = bestModel.transform(df_test)
test_auc = evaluator.evaluate(predictions)
print(f"AUC en test: {test_auc:.4f}")
# ============================================================
# Comparación de tiempos (ejemplo hipotético)
# ============================================================
# TrainValidationSplit: 15 minutos (9 entrenamientos)
# CrossValidator (K=5): 75 minutos (45 entrenamientos)
# → 5x más rápido con TrainValidationSplit
¿Cuándo usar cada uno?
| Escenario | Recomendación | Razón |
|---|---|---|
| N < 10K | CrossValidator | Dataset pequeño necesita estimador robusto |
| 10K < N < 100K | CrossValidator (K=5) | Balance entre robustez y tiempo |
| N > 100K | TrainValidationSplit | Dataset grande, single split suficiente |
| Grid muy grande (>500 configs) | TrainValidationSplit | Reduce tiempo drásticamente |
| Producción final | CrossValidator | Mayor confianza en el estimador |
6. Visualización Avanzada de Resultados
Análisis Visual del Grid Search
Visualizar los resultados ayuda a entender el landscape del espacio de hiperparámetros y detectar patrones de interacción.
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# Supongamos results_df ya construido (ver Bloque 4)
# ============================================================
# 1. Distribución de Métricas
# ============================================================
plt.figure(figsize=(10, 6))
plt.hist(results_df["avg_metric"], bins=30, color="#a855f7", alpha=0.7, edgecolor="black")
plt.axvline(max(results_df["avg_metric"]), color="red", linestyle="--",
label=f"Mejor: {max(results_df['avg_metric']):.4f}")
plt.xlabel("AUC-ROC (Cross-Validation)")
plt.ylabel("Frecuencia")
plt.title("Distribución de Rendimiento: Grid Search")
plt.legend()
plt.grid(alpha=0.3)
plt.show()
# ============================================================
# 2. Boxplot por Hiperparámetro
# ============================================================
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
for idx, param in enumerate(["numTrees", "maxDepth", "maxBins", "subsamplingRate"]):
if param in results_df.columns:
ax = axes[idx // 2, idx % 2]
results_df.boxplot(column="avg_metric", by=param, ax=ax)
ax.set_title(f"Impacto de {param}")
ax.set_xlabel(param)
ax.set_ylabel("AUC-ROC")
ax.get_figure().suptitle("") # Remover título general
plt.tight_layout()
plt.show()
# ============================================================
# 3. Heatmap de Interacciones (2D)
# ============================================================
# Ejemplo: numTrees vs maxDepth
if "numTrees" in results_df.columns and "maxDepth" in results_df.columns:
pivot = results_df.pivot_table(
values="avg_metric",
index="maxDepth",
columns="numTrees",
aggfunc="mean" # Promedio si hay múltiples valores
)
plt.figure(figsize=(10, 8))
sns.heatmap(pivot, annot=True, fmt=".4f", cmap="viridis", cbar_kws={"label": "AUC-ROC"})
plt.title("Heatmap: numTrees vs maxDepth")
plt.xlabel("numTrees")
plt.ylabel("maxDepth")
plt.show()
# ============================================================
# 4. Parallel Coordinates Plot (multidimensional)
# ============================================================
from pandas.plotting import parallel_coordinates
# Seleccionar top 20 configs
top20 = results_df.nlargest(20, "avg_metric").copy()
top20["rank"] = range(1, 21)
# Normalizar valores para visualización
for col in ["numTrees", "maxDepth", "maxBins", "subsamplingRate"]:
if col in top20.columns:
top20[f"{col}_norm"] = (top20[col] - top20[col].min()) / (top20[col].max() - top20[col].min())
plt.figure(figsize=(12, 6))
parallel_coordinates(
top20,
class_column="rank",
cols=[f"{c}_norm" for c in ["numTrees", "maxDepth", "maxBins", "subsamplingRate"] if c in top20.columns],
colormap="viridis"
)
plt.title("Parallel Coordinates: Top 20 Configuraciones")
plt.ylabel("Valor Normalizado [0, 1]")
plt.legend(loc="upper right", fontsize=8)
plt.show()
# ============================================================
# 5. Scatter Matrix (pairwise relationships)
# ============================================================
from pandas.plotting import scatter_matrix
scatter_matrix(
results_df[["numTrees", "maxDepth", "avg_metric"]],
figsize=(10, 10),
diagonal="kde",
alpha=0.5,
c=results_df["avg_metric"],
cmap="viridis"
)
plt.suptitle("Scatter Matrix: Relaciones entre Hiperparámetros")
plt.show()
Insights de la Visualización
-
1.
Distribución de métricas: Si es bimodal, sugiere dos regímenes distintos (ej: modelos simples vs complejos)
-
2.
Boxplots: Identificar hiperparámetros con mayor impacto (mayor spread) vs irrelevantes (métricas similares)
-
3.
Heatmaps: Detectar interacciones no lineales (ej: maxDepth=20 solo funciona bien con numTrees>100)
-
4.
Parallel coords: Visualizar patrones en top configs (ej: todas tienen subsamplingRate>0.8)