Machine Learning
sklearn-genetic-opt
Python
scikit-learn
grid search
optimización
hiperparámetros

¿Todavía Usas Grid Search para Optimización de Hiperparámetros?

Compara grid search, random search y algoritmos evolutivos para ajustar hiperparámetros, y descubre cuándo GASearchCV de sklearn-genetic-opt gana.

5 de marzo de 2026·7 min read

Una cuadrícula de cinco hiperparámetros puede convertirse, sin que lo notes, en 9.600 entrenamientos y una tarde entera de espera. En este artículo pongo lado a lado grid search, random search y la búsqueda evolutiva con GASearchCV — con código ejecutable — para que decidas cuándo vale la pena cada uno según tu presupuesto de cómputo.

El Problema con Grid Search

Grid search es el método que todos aprenden primero. La idea es sencilla: defines una cuadrícula de valores para cada hiperparámetro, y el algoritmo los prueba todos. Es exhaustivo, reproducible y fácil de entender.

Pero tiene un problema fundamental: la explosión combinatoria.

Imagina que estás ajustando un GradientBoostingClassifier con estos hiperparámetros:

python
param_grid = {
    "n_estimators": [100, 200, 300, 500],          # 4 valores
    "max_depth": [2, 3, 4, 5, 6, 8],               # 6 valores
    "learning_rate": [0.01, 0.05, 0.1, 0.2, 0.3], # 5 valores
    "min_samples_split": [2, 5, 10, 20],            # 4 valores
    "max_features": [0.5, 0.7, 0.8, 1.0],          # 4 valores
}

El total de combinaciones es: 4 × 6 × 5 × 4 × 4 = 1.920 combinaciones. Con validación cruzada de 5 folds, eso son 9.600 entrenamientos. Si cada uno tarda 2 segundos, el ajuste completo toma más de 5 horas.

¿Y si añades un hiperparámetro más con 3 valores? Ahora son 5.760 combinaciones. El tiempo casi se triplica.

Esta es la naturaleza de grid search: crece exponencialmente con el número de hiperparámetros. No es escalable para modelos complejos.

Random Search: Una Mejora, Pero Ciega

En 2012, Bergstra y Bengio demostraron en un paper influyente que random search supera sistemáticamente a grid search dado el mismo presupuesto de evaluaciones. La intuición es elegante: en la mayoría de los problemas, no todos los hiperparámetros importan igual. Random search permite explorar más valores únicos de cada hiperparámetro con la misma cantidad de evaluaciones.

python
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform

rs = RandomizedSearchCV(
    estimator=GradientBoostingClassifier(),
    param_distributions={
        "n_estimators": randint(100, 500),
        "max_depth": randint(2, 8),
        "learning_rate": uniform(0.01, 0.29),
        "min_samples_split": randint(2, 20),
        "max_features": uniform(0.5, 0.5),
    },
    n_iter=100,   # Solo 100 evaluaciones, no 1.920
    cv=5,
    n_jobs=-1,
    random_state=42,
)
rs.fit(X_train, y_train)

Random search es claramente mejor que grid search en espacios grandes. Pero tiene una limitación crítica: no aprende de los resultados anteriores. Cada evaluación es independiente. El algoritmo no sabe que learning_rate=0.01 con n_estimators=100 rindió mal, y podría volver a explorar esa zona.

Esto nos lleva a una pregunta natural: ¿podemos hacer una búsqueda que aprenda mientras busca?

Optimización Evolutiva con GASearchCV

sklearn-genetic-opt implementa precisamente eso. En lugar de una búsqueda ciega, usa algoritmos genéticos que guían la exploración hacia las regiones más prometedoras del espacio de hiperparámetros. (Si aún no conoces la librería, el recorrido paso a paso de Ajusta Tu Modelo de Scikit-learn Usando Algoritmos Evolutivos cubre toda la API.)

La clave conceptual es la siguiente: si encontré que learning_rate=0.1 funciona bien, tiene sentido explorar valores cercanos a 0.1, no volver a probar 0.01. Los algoritmos genéticos hacen esto de forma natural mediante la herencia y la variación.

python
from sklearn_genetic import GASearchCV, EvolutionConfig, RuntimeConfig
from sklearn_genetic.space import Integer, Continuous

search = GASearchCV(
    estimator=GradientBoostingClassifier(),
    param_grid={
        "n_estimators": Integer(100, 500),
        "max_depth": Integer(2, 8),
        "learning_rate": Continuous(0.01, 0.3),
        "min_samples_split": Integer(2, 20),
        "max_features": Continuous(0.5, 1.0),
    },
    cv=5,
    scoring="accuracy",
    evolution_config=EvolutionConfig(
        population_size=30,
        generations=25,
        crossover_probability=0.8,
        mutation_probability=0.1,
        elitism=True,
        keep_top_k=3,
    ),
    runtime_config=RuntimeConfig(n_jobs=-1, use_cache=True),
)

Observa que Continuous(0.01, 0.3) para learning_rate es fundamentalmente más rico que cualquier lista discreta: explora el rango continuo de valores, no solo los puntos que definiste manualmente.

Comparación Directa: Grid Search vs GASearchCV

Veamos los tres métodos lado a lado en el mismo dataset (make_classification con 1.000 muestras y 20 características):

python
from sklearn.datasets import make_classification
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import (
    train_test_split, GridSearchCV, RandomizedSearchCV
)
from sklearn_genetic import GASearchCV, EvolutionConfig, RuntimeConfig, ConsecutiveStopping
from sklearn_genetic.space import Integer, Continuous
from scipy.stats import randint, uniform
import time

# Dataset
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ── 1. Grid Search ──────────────────────────────────────────
t0 = time.time()
gs = GridSearchCV(
    GradientBoostingClassifier(random_state=42),
    param_grid={
        "n_estimators": [100, 200, 300],
        "max_depth": [2, 3, 5],
        "learning_rate": [0.05, 0.1, 0.2],
    },
    cv=5, n_jobs=-1
)
gs.fit(X_train, y_train)
gs_time = time.time() - t0
print(f"Grid Search   | CV: {gs.best_score_:.4f} | Test: {gs.best_estimator_.score(X_test, y_test):.4f} | Tiempo: {gs_time:.1f}s")

# ── 2. Random Search ────────────────────────────────────────
t0 = time.time()
rs = RandomizedSearchCV(
    GradientBoostingClassifier(random_state=42),
    param_distributions={
        "n_estimators": randint(100, 300),
        "max_depth": randint(2, 5),
        "learning_rate": uniform(0.05, 0.15),
    },
    n_iter=50, cv=5, n_jobs=-1, random_state=42
)
rs.fit(X_train, y_train)
rs_time = time.time() - t0
print(f"Random Search | CV: {rs.best_score_:.4f} | Test: {rs.best_estimator_.score(X_test, y_test):.4f} | Tiempo: {rs_time:.1f}s")

# ── 3. GASearchCV ───────────────────────────────────────────
t0 = time.time()
ga = GASearchCV(
    GradientBoostingClassifier(random_state=42),
    param_grid={
        "n_estimators": Integer(100, 300),
        "max_depth": Integer(2, 5),
        "learning_rate": Continuous(0.05, 0.2),
    },
    cv=5, scoring="accuracy",
    evolution_config=EvolutionConfig(population_size=20, generations=20, elitism=True, keep_top_k=3),
    runtime_config=RuntimeConfig(n_jobs=-1, use_cache=True),
)
ga.fit(X_train, y_train, callbacks=[ConsecutiveStopping(generations=5, metric="fitness")])
ga_time = time.time() - t0
print(f"GASearchCV    | CV: {ga.best_score_:.4f} | Test: {ga.best_estimator_.score(X_test, y_test):.4f} | Tiempo: {ga_time:.1f}s")

Los resultados típicos muestran que GASearchCV alcanza una puntuación igual o superior a grid search, con un número de evaluaciones mucho menor gracias al early stopping y la convergencia inteligente.

Elitismo y keep_top_k: Preservando los Mejores Genes

Uno de los riesgos de los algoritmos genéticos es la regresión genética: mediante mutaciones desafortunadas, individuos peores pueden desplazar a los mejores. El parámetro elitism=True evita esto.

Con keep_top_k=3, los 3 mejores individuos de cada generación pasan directamente a la siguiente sin sufrir crossover ni mutación. Esto garantiza que la mejor solución encontrada hasta el momento nunca se pierde.

python
evolution_config = EvolutionConfig(
    population_size=30,
    generations=25,
    elitism=True,
    keep_top_k=3,       # Los 3 mejores pasan sin cambios a la siguiente generación
    mutation_probability=0.15,
    crossover_probability=0.85,
)

Un valor de keep_top_k entre 1 y 5 suele funcionar bien. Si es demasiado alto (digamos, 15 sobre 30), la población pierde diversidad y el algoritmo converge prematuramente hacia un óptimo local.

Cuándo Brilla la Búsqueda Evolutiva

Los algoritmos genéticos no son la solución óptima en todos los escenarios. Aquí tienes una guía práctica:

Usa GASearchCV cuando:

  • El espacio de hiperparámetros tiene más de 3-4 dimensiones.
  • Algunos hiperparámetros son continuos (tasas de aprendizaje, fuerza de regularización, tasas de dropout).
  • Cada evaluación es costosa en tiempo (modelos grandes, datasets grandes, muchos folds).
  • Quieres explorar el espacio de forma más inteligente que random search.
  • Necesitas early stopping para controlar el presupuesto de cómputo.

Considera grid search cuando:

  • El espacio es pequeño y discreto (2-3 hiperparámetros, 3-5 valores cada uno).
  • La reproducibilidad exacta es crítica y quieres documentar cada combinación evaluada.
  • El costo de cada evaluación es bajo y puedes permitirte la exhaustividad.

La regla práctica: si el número total de combinaciones de tu grid search supera 200-300, random search o GASearchCV son casi siempre mejores opciones.

Conclusión y Próximos Pasos

Grid search fue la herramienta estándar durante años, y sigue siendo válida para espacios pequeños. Pero a medida que los modelos se vuelven más complejos y los espacios de hiperparámetros más grandes, sus limitaciones se hacen intolerables.

sklearn-genetic-opt ofrece una alternativa madura y bien integrada con scikit-learn:

  1. Instala con pip install sklearn-genetic-opt.
  2. Reemplaza GridSearchCV por GASearchCV con el mismo estimador y scoring.
  3. Define tu param_grid con Integer, Continuous y Categorical.
  4. Configura EvolutionConfig con elitism=True y un par de callbacks para early stopping.
  5. Analiza cv_results_ para entender cómo evolucionó la búsqueda.

El siguiente nivel es combinar esto con GAFeatureSelectionCV para optimizar también qué variables entran al modelo — consulta Selección Evolutiva de Características para Machine Learning.

Si todavía usas grid search exhaustivo para modelos con muchos hiperparámetros, este es el mejor momento para actualizar tu flujo de trabajo.


GASearchCV es parte de sklearn-genetic-opt, una librería open-source compatible con scikit-learn para optimización evolutiva de hiperparámetros.


Sobre el autor

Rodrigo Arenas es arquitecto de software e ingeniero de machine learning en Medellín, Colombia. Construye productos, plataformas y software open source para IA, Data Engineering y Machine Learning — es el creador de Ciaren, sklearn-genetic-opt y PyWorkforce.



Rodrigo Arenas — ML Engineer & Open Source Maintainer

¿Estás trabajando en algo similar? Puedo ayudarte a llevarlo a producción.

¿Vas a construir una plataforma de IA o de datos?

Productos, plataformas y software open source para IA, Data Engineering y Machine Learning.

Ver proyectos
Visión

Construyendo un ecosistema de productos y software open source para IA, Data Engineering y Machine Learning.

Proyectos
sklearn-genetic-optPyWorkforceCiaren

Iterixs (En construcción)

© 2026 Rodrigo Arenas