Artículos / Machine Learning

[ Machine Learning ]sklearn-genetic-optPythonparámetros adaptativosalgoritmos genéticos

Mutación y cruzamiento adaptativos con sklearn-genetic-opt

Programa mutación y cruzamiento con los adaptadores de sklearn-genetic-opt, comprueba sus curvas y compara la búsqueda con una configuración fija.

30 de abril de 2026·8 min de lectura
Mutación y cruzamiento adaptativos con sklearn-genetic-opt

Los algoritmos genéticos usan dos operadores principales para explorar el espacio de hiperparámetros: mutación (cambios aleatorios en los individuos) y cruzamiento (combinación de dos soluciones para generar descendientes). Ambos operadores tienen probabilidades asociadas que controlan con qué frecuencia se aplican durante la evolución.

Puedes mantener estas probabilidades fijas o programar cómo cambian. Este artículo explica los cuatro adaptadores, comprueba sus curvas en una ejecución finita y muestra cómo compararlas con una configuración fija.

Los ejemplos usan sklearn-genetic-opt 0.14.0, que requiere Python 3.12 o posterior. Instala el extra de gráficos para reproducir la figura:

bash
python -m pip install "sklearn-genetic-opt[plot]==0.14.0"

Parámetros fijos vs. adaptativos

Con un adaptador fijo, la probabilidad de mutación se mantiene entre generaciones. Una programación permite distribuir la exploración de otra forma:

  • Al inicio: una probabilidad de mutación mayor puede explorar más configuraciones nuevas.
  • Al final: una probabilidad menor puede conservar más combinaciones producidas por selección y cruzamiento.

Son decisiones que hay que evaluar, sin garantía de convergencia. Una probabilidad fija puede ser una buena referencia; la mejor política depende del espacio de búsqueda, el ruido de la evaluación y el presupuesto disponible.

El dilema exploración-explotación

Este trade-off es fundamental en todos los métodos de búsqueda heurística. En el contexto de búsqueda de hiperparámetros con algoritmos genéticos:

  • Exploración: probar configuraciones diversas, arriesgarse a individuos menos prometedores para descubrir regiones inesperadamente buenas.
  • Explotación: concentrar recursos computacionales en refinar las mejores configuraciones encontradas hasta el momento.

Los adaptadores expresan una programación de probabilidades. La selección, las distribuciones del espacio de búsqueda y el algoritmo genético elegido siguen determinando qué configuraciones se evalúan. Inspecciona la curva antes de entrenar y mide si ayuda en tu problema.

Adaptadores disponibles en sklearn-genetic-opt

La librería sklearn-genetic-opt ofrece cuatro tipos de adaptadores para las probabilidades de mutación y cruzamiento:

ConstantAdapter

El valor permanece constante. Pasar una probabilidad como mutation_probability=0.2 también crea un adaptador constante internamente. Para construirlo explícitamente, proporciona sus tres argumentos requeridos:

python
from sklearn_genetic import ConstantAdapter
# siempre retorna 0.2, sin importar la generación
adapter = ConstantAdapter(initial_value=0.2, end_value=0.2, adaptive_rate=0)

ExponentialAdapter

Se aproxima exponencialmente a end_value. Este ejemplo reduce la mutación desde una probabilidad inicial mayor; invertir los extremos produce una curva creciente.

python
from sklearn_genetic import ExponentialAdapter
adapter = ExponentialAdapter(initial_value=0.4, end_value=0.05, adaptive_rate=0.1)

InverseAdapter

Se aproxima a end_value con una curva inversa: cambia más rápido al inicio y luego se ralentiza. Este ejemplo aumenta el cruzamiento; esa dirección es una política elegida, sin ventaja intrínseca del adaptador.

python
from sklearn_genetic import InverseAdapter
adapter = InverseAdapter(initial_value=0.6, end_value=0.9, adaptive_rate=0.1)

PotentialAdapter

Usa la expresión geométrica end_value + (initial_value - end_value) * (1 - adaptive_rate)**t. Para 0 < adaptive_rate < 1, los cambios se reducen con el tiempo. Con los extremos del ejemplo crece hacia el objetivo, con los cambios más grandes al inicio.

python
from sklearn_genetic import PotentialAdapter
adapter = PotentialAdapter(initial_value=0.6, end_value=0.9, adaptive_rate=0.1)

Matemática detrás de ExponentialAdapter

La fórmula de decaimiento exponencial es:

value(t) = end_value + (initial_value - end_value) * exp(-adaptive_rate * t)

Aquí t es el paso del adaptador contado desde cero: la primera llamada a .step() usa t=0. La implementación de 0.14.0 define estas curvas. Para entender el efecto de adaptive_rate:

  • Con adaptive_rate=0.05: el decaimiento es lento, la transición ocurre gradualmente durante muchas generaciones.
  • Con adaptive_rate=0.2: el decaimiento es rápido, la mayor parte de la transición ocurre en las primeras generaciones.
python
import numpy as np
import matplotlib.pyplot as plt

def exponential_adapter(t, initial=0.4, end=0.05, rate=0.1):
    return end + (initial - end) * np.exp(-rate * t)

def inverse_adapter(t, initial=0.6, end=0.9, rate=0.1):
    return end - (end - initial) / (1 + rate * t)

generations = np.arange(0, 25)

plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(generations, [exponential_adapter(t) for t in generations])
plt.title("ExponentialAdapter (mutación)")
plt.xlabel("Paso del adaptador"); plt.ylabel("Probabilidad")

plt.subplot(1, 2, 2)
plt.plot(generations, [inverse_adapter(t) for t in generations])
plt.title("InverseAdapter (cruzamiento)")
plt.xlabel("Paso del adaptador"); plt.ylabel("Probabilidad")
plt.tight_layout()
plt.show()

En los 25 pasos graficados, de t=0 a t=24, la mutación decrece de 0.4 a aproximadamente 0.0818 y el cruzamiento aumenta de 0.6 a aproximadamente 0.8118. Son valores a un número finito de pasos; end_value es el objetivo al que la curva se aproxima con el tiempo.

Ejemplo completo: GASearchCV con adaptadores

Veamos cómo integrar los adaptadores en una búsqueda real de hiperparámetros con un clasificador de scikit-learn:

python
from sklearn_genetic import (
    GASearchCV, ExponentialAdapter, InverseAdapter, EvolutionConfig, OptimizationConfig
)
from sklearn_genetic.space import Integer, Continuous, Categorical
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, StratifiedKFold

# Datos de ejemplo
X, y = make_classification(
    n_samples=1000, n_features=20, n_informative=10, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Espacio de búsqueda de hiperparámetros
param_grid = {
    "n_estimators": Integer(50, 300),
    "max_depth": Integer(3, 15),
    "min_samples_split": Integer(2, 20),
    "max_features": Continuous(0.1, 1.0),
    "criterion": Categorical(["gini", "entropy"])
}

clf = RandomForestClassifier(random_state=42)
cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)

# GASearchCV con parámetros adaptativos
search = GASearchCV(
    estimator=clf,
    param_grid=param_grid,
    scoring="accuracy",
    cv=cv,
    evolution_config=EvolutionConfig(
        population_size=30,
        generations=25,
        mutation_probability=ExponentialAdapter(
            initial_value=0.4,
            end_value=0.05,
            adaptive_rate=0.1
        ),
        crossover_probability=InverseAdapter(
            initial_value=0.6,
            end_value=0.9,
            adaptive_rate=0.1
        )
    ),
    optimization_config=OptimizationConfig(diversity_control=False),
    random_state=42,
    verbose=True,
    n_jobs=1
)

search.fit(X_train, y_train)

print(f"\nMejores hiperparámetros: {search.best_params_}")
print(f"Mejor score CV: {search.best_score_:.4f}")
print(f"Score en test: {search.score(X_test, y_test):.4f}")

Pasa las probabilidades dentro de EvolutionConfig: sus campos prevalecen sobre los argumentos externos. El ejemplo desactiva el controlador adicional de diversidad para que no aumente la mutación sobre la curva programada. Usa un solo job para limitar el trabajo paralelo. La población, las generaciones, los folds de validación y el costo del estimador afectan el tiempo; los scores impresos salen de tu ejecución, sin constituir un benchmark prometido.

Usando solo ExponentialAdapter para mutación

Si prefieres un enfoque más conservador y solo adaptar la mutación:

python
search_mutation_only = GASearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    scoring="accuracy",
    cv=cv,
    evolution_config=EvolutionConfig(
        population_size=30,
        generations=25,
        mutation_probability=ExponentialAdapter(
            initial_value=0.4,
            end_value=0.05,
            adaptive_rate=0.1
        ),
        crossover_probability=0.6,  # fijo
    ),
    optimization_config=OptimizationConfig(diversity_control=False),
    random_state=42,
    n_jobs=1
)
search_mutation_only.fit(X_train, y_train)

Esto aísla la programación de mutación y mantiene fijo el cruzamiento. El algoritmo predeterminado eaMuPlusLambda requiere que el presupuesto combinado de cruzamiento y mutación no supere uno: fijar el cruzamiento en 0.6 deja espacio para la mutación inicial de 0.4. Un cruzamiento mayor puede recortar la probabilidad efectiva de mutación. Compáralo con una referencia que use el mismo cruzamiento fijo antes de decidir si programas ambos operadores.

Comparando fijo vs. adaptativo

Para inspeccionar dos ejecuciones con el mismo espacio, folds, población y límite de generaciones, compara sus historias de fitness:

python
# Búsqueda con parámetros fijos
search_fixed = GASearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    scoring="accuracy",
    cv=cv,
    evolution_config=EvolutionConfig(
        population_size=30,
        generations=25,
        mutation_probability=0.1,   # fijo
        crossover_probability=0.8,  # fijo
    ),
    optimization_config=OptimizationConfig(diversity_control=False),
    random_state=42,
    n_jobs=1
)
search_fixed.fit(X_train, y_train)

# Comparar historia de fitness
import pandas as pd

history_adaptive = pd.DataFrame(search.history)
history_fixed = pd.DataFrame(search_fixed.history)

print("Convergencia: score máximo por generación:")
print(f"{'Gen':>4} | {'Adaptativo':>12} | {'Fijo':>12}")
print("-" * 35)
for gen in [0, 5, 10, 15, 20, 24]:
    a = history_adaptive.loc[gen, "fitness_max"]
    f = history_fixed.loc[gen, "fitness_max"]
    print(f"{gen:>4} | {a:>12.4f} | {f:>12.4f}")

Esta tabla describe una ejecución por política. Repite con varias semillas de búsqueda y registra las evaluaciones reales de candidatos y el tiempo: el mismo límite de generaciones puede tener costos distintos cuando la caché reutiliza evaluaciones. Elige la programación con datos de entrenamiento/validación y reserva test para la evaluación final. Un máximo de CV mayor en una historia no demuestra más velocidad, robustez o mejor generalización.

Cuándo usar cada adaptador

AdaptadorCuándo usarEjemplo
ExponentialAdapterAproximación exponencial al objetivoFijar un punto de media transición para mutación
InverseAdapterCambio inicial más rápido, seguido de una cola largaAproximarse a una probabilidad de cruzamiento elegida
PotentialAdapterCambio geométrico con incrementos decrecientesProbar otra velocidad de transición
ConstantAdapterProbabilidad fijaEstablecer una referencia para ambos operadores

Elige la curva cuya transición encaje con la ejecución que quieres probar y compárala con probabilidades fijas bajo un presupuesto de evaluación documentado.

Cómo elegir adaptive_rate

Para ExponentialAdapter, la mitad de la transición entre initial_value y end_value ocurre en t = ln(2) / adaptive_rate. Con adaptive_rate=0.1, la probabilidad está a mitad de camino cerca del paso 7; esto describe la curva, sin medir cuánta exploración útil hizo el optimizador.

Si quieres la media transición en el paso h, usa adaptive_rate = ln(2) / h. Comprueba la curva en tu presupuesto real de pasos antes de entrenar.

Conclusión

Un adaptador hace explícita la política de probabilidades. Empieza con valores fijos, inspecciona la programación en los pasos disponibles y compara ejecuciones reproducibles antes de incorporarla al flujo de entrenamiento.


ExponentialAdapter, InverseAdapter y PotentialAdapter son parte de sklearn-genetic-opt. Si no has probado la búsqueda evolutiva de hiperparámetros, empieza con Ajusta Tu Modelo de Scikit-learn Usando Algoritmos Evolutivos.

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

Si necesitas adaptar este enfoque a tu sistema, cuéntame qué datos tienes y qué decisión técnica necesitas resolver.

Ver todos los proyectos