Ajusta Tu Modelo de Scikit-learn Usando Algoritmos Evolutivos
Aprende a optimizar hiperparámetros de scikit-learn con algoritmos genéticos: GASearchCV, espacios tipados, adaptadores y callbacks de early stopping.
Si ya usas GridSearchCV, puedes pasarte a la optimización evolutiva de hiperparámetros cambiando un puñado de líneas. En esta guía configurarás GASearchCV de sklearn-genetic-opt de principio a fin: espacios de búsqueda tipados, EvolutionConfig, probabilidades adaptativas y callbacks de early stopping — todo sobre un modelo real de scikit-learn.
¿Por Qué Algoritmos Genéticos para Optimizar Hiperparámetros?
Ajustar los hiperparámetros de un modelo es una de las tareas más tediosas —y más importantes— en machine learning. La diferencia entre un modelo mediocre y uno de producción suele estar en ese proceso de ajuste fino.
El enfoque clásico, grid search, evalúa todas las combinaciones posibles de hiperparámetros. Funciona bien en espacios pequeños, pero crece exponencialmente: si tienes 5 hiperparámetros con 10 valores cada uno, son 100.000 evaluaciones. Inviable — en ¿Todavía Usas Grid Search para Optimización de Hiperparámetros? desgloso exactamente qué tan mal escala.
Random search mejora esto eligiendo combinaciones al azar. Es más eficiente, pero sigue siendo "ciego": no aprende de los resultados anteriores.
Los algoritmos genéticos cambian el juego. Funcionan exactamente como la evolución natural:
- Se crea una población inicial de configuraciones de hiperparámetros (individuos).
- Cada individuo es evaluado (su "aptitud" es la métrica de validación cruzada).
- Los mejores individuos se seleccionan para reproducirse.
- La cruza (crossover) combina dos individuos para crear hijos.
- La mutación introduce variaciones aleatorias.
- Se repite el proceso por varias generaciones.
El resultado: el algoritmo converge hacia regiones del espacio de hiperparámetros donde el rendimiento es alto, de forma inteligente y eficiente.
Instalación y Primeros Pasos
Instalar sklearn-genetic-opt es tan simple como:
pip install sklearn-genetic-opt
La interfaz principal es GASearchCV, que replica la API de GridSearchCV y RandomizedSearchCV de scikit-learn, por lo que la curva de aprendizaje es mínima si ya conoces esas clases.
Definiendo el Espacio de Búsqueda
A diferencia de grid search (que requiere listas de valores discretos), sklearn-genetic-opt trabaja con espacios de búsqueda tipados:
from sklearn_genetic.space import Integer, Continuous, Categorical
param_grid = {
"n_estimators": Integer(50, 300), # Entero entre 50 y 300
"max_depth": Integer(2, 15), # Entero entre 2 y 15
"max_features": Continuous(0.3, 1.0), # Float continuo entre 0.3 y 1.0
"criterion": Categorical(["gini", "entropy", "log_loss"]), # Valor discreto
}
Integer(low, high): Muestrea enteros en el rango[low, high].Continuous(low, high): Muestrea floats en el rango[low, high].Categorical(choices): Muestrea de una lista de opciones discretas.
Esta distinción es importante: Continuous permite explorar valores que ninguna lista predefinida contemplaría, lo que es una ventaja real sobre grid search.
Ajustando el Modelo con GASearchCV
El flujo completo con un RandomForestClassifier sobre el dataset Iris se ve así:
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn_genetic import GASearchCV, EvolutionConfig, PopulationConfig, RuntimeConfig
from sklearn_genetic.space import Integer, Continuous, Categorical
# Cargar datos
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Estimador base
clf = RandomForestClassifier(random_state=42)
# Espacio de hiperparámetros
param_grid = {
"n_estimators": Integer(50, 300),
"max_depth": Integer(2, 15),
"max_features": Continuous(0.3, 1.0),
"criterion": Categorical(["gini", "entropy", "log_loss"]),
}
# Configurar la búsqueda evolutiva
search = GASearchCV(
estimator=clf,
param_grid=param_grid,
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,
),
population_config=PopulationConfig(initializer="smart"),
runtime_config=RuntimeConfig(n_jobs=-1, use_cache=True),
)
# Entrenar
search.fit(X_train, y_train)
# Resultados
print("Mejores parámetros:", search.best_params_)
print("Mejor puntuación CV:", search.best_score_)
Interpretando los Resultados
Una vez ejecutado el ajuste, GASearchCV expone los mismos atributos que GridSearchCV:
# El mejor conjunto de hiperparámetros encontrado
print(search.best_params_)
# {'n_estimators': 187, 'max_depth': 8, 'max_features': 0.74, 'criterion': 'gini'}
# La mejor puntuación de validación cruzada
print(search.best_score_)
# 0.9667
# El mejor estimador ya entrenado con todos los datos de train
best_model = search.best_estimator_
test_score = best_model.score(X_test, y_test)
print(f"Accuracy en test: {test_score:.4f}")
# Historial de todas las generaciones
import pandas as pd
cv_results = pd.DataFrame(search.cv_results_)
print(cv_results.head())
El atributo cv_results_ es especialmente útil: contiene el historial de cada individuo evaluado en cada generación, lo que permite visualizar cómo evoluciona la población a lo largo del tiempo.
Control Fino con EvolutionConfig
EvolutionConfig es el corazón de la configuración del algoritmo genético. Sus parámetros más importantes:
from sklearn_genetic import EvolutionConfig
evolution_config = EvolutionConfig(
population_size=30, # Número de individuos por generación
generations=25, # Número de generaciones a ejecutar
crossover_probability=0.8, # Probabilidad de que dos padres se crucen
mutation_probability=0.1, # Probabilidad de mutación en cada gen
elitism=True, # Los mejores individuos pasan directamente a la siguiente generación
keep_top_k=3, # Cuántos de los mejores individuos preservar con elitismo
)
Consejos prácticos:
population_size: Un valor entre 20 y 50 suele ser suficiente para la mayoría de problemas. Más individuos = mayor diversidad pero más tiempo de cómputo.generations: El algoritmo converge antes cuanto más grande espopulation_size. Empieza con 20-30 generaciones y aumenta si ves que sigue mejorando.elitism=True: Casi siempre conviene activarlo. Garantiza que la mejor solución encontrada nunca se pierde entre generaciones.crossover_probabilityymutation_probability: Los valores por defecto (0.8 y 0.1) son un buen punto de partida. Una mutación alta favorece la exploración; una baja favorece la explotación.
Probabilidades Adaptativas
Para búsquedas largas, puedes usar ExponentialAdapter para que la probabilidad de mutación disminuya conforme avanza la evolución (explorar al principio, explotar al final):
from sklearn_genetic import ExponentialAdapter
search = GASearchCV(
estimator=clf,
param_grid=param_grid,
mutation_probability=ExponentialAdapter(
initial_value=0.3,
end_value=0.05,
adaptive_rate=0.1
),
)
Early Stopping con Callbacks
En datasets grandes, cada generación puede tardar minutos. Los callbacks permiten detener la búsqueda anticipadamente si se cumplen ciertas condiciones:
from sklearn_genetic import ThresholdStopping, ConsecutiveStopping
callbacks = [
# Detener si la aptitud supera el umbral 0.97
ThresholdStopping(threshold=0.97, metric="fitness"),
# Detener si no hay mejora durante 5 generaciones consecutivas
ConsecutiveStopping(generations=5, metric="fitness"),
]
search.fit(X_train, y_train, callbacks=callbacks)
ThresholdStopping: Ideal cuando tienes un objetivo claro (por ejemplo, necesitas al menos 95% de accuracy).ConsecutiveStopping: Evita malgastar cómputo cuando el algoritmo ya convergió.
Comparación con Grid Search y Random Search
Para ver la diferencia en la práctica, aquí está la misma tarea con los tres métodos:
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from scipy.stats import randint, uniform
import time
# Grid Search
param_grid_gs = {
"n_estimators": [50, 100, 200, 300],
"max_depth": [2, 5, 8, 12, 15],
"max_features": [0.3, 0.5, 0.7, 1.0],
"criterion": ["gini", "entropy"],
}
# Total: 4 × 5 × 4 × 2 = 160 combinaciones × 5 folds = 800 evaluaciones
t0 = time.time()
gs = GridSearchCV(RandomForestClassifier(random_state=42), param_grid_gs, cv=5, n_jobs=-1)
gs.fit(X_train, y_train)
print(f"Grid Search — Mejor: {gs.best_score_:.4f} | Tiempo: {time.time()-t0:.1f}s")
# Random Search
t0 = time.time()
rs = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
{"n_estimators": randint(50, 300), "max_depth": randint(2, 15),
"max_features": uniform(0.3, 0.7), "criterion": ["gini", "entropy"]},
n_iter=50, cv=5, n_jobs=-1, random_state=42
)
rs.fit(X_train, y_train)
print(f"Random Search — Mejor: {rs.best_score_:.4f} | Tiempo: {time.time()-t0:.1f}s")
# GASearchCV — como máximo 30 individuos × 25 generaciones = 750 evaluaciones
t0 = time.time()
search.fit(X_train, y_train, callbacks=[ConsecutiveStopping(generations=5, metric="fitness")])
print(f"GASearchCV — Mejor: {search.best_score_:.4f} | Tiempo: {time.time()-t0:.1f}s")
Los resultados típicos muestran que GASearchCV iguala o supera a grid search con menos evaluaciones, gracias a que dirige la búsqueda hacia las regiones más prometedoras del espacio. La ventaja se hace más evidente cuanto mayor es el espacio de hiperparámetros.
Conclusión
sklearn-genetic-opt con GASearchCV ofrece una alternativa poderosa y elegante a los métodos clásicos de ajuste de hiperparámetros:
- Compatible con la API de scikit-learn: si ya usas
GridSearchCV, la migración es trivial. - Eficiente: Converge hacia buenas soluciones sin evaluar el espacio exhaustivamente.
- Flexible: Espacios continuos, enteros y categóricos en el mismo
param_grid. - Controlable:
EvolutionConfigy callbacks dan control total sobre el proceso.
El siguiente paso natural es combinar GASearchCV con GAFeatureSelectionCV para optimizar simultáneamente los hiperparámetros y las características del modelo — consulta Selección Evolutiva de Características para Machine Learning.
Si trabajas con modelos de scikit-learn y todavía dependes exclusivamente de grid search, este es el momento de probar la evolución.
GASearchCV es parte de sklearn-genetic-opt, una librería open-source compatible con scikit-learn para optimización evolutiva.
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.
Lectura recomendada
Rodrigo Arenas — ML Engineer & Open Source Maintainer
¿Estás trabajando en algo similar? Puedo ayudarte a llevarlo a producción.