Métodos de Parámetros Adaptativos para Machine Learning
Aprende cómo funcionan los parámetros adaptativos en sklearn-genetic-opt: usa ExponentialAdapter e InverseAdapter para ajustar mutación y cruzamiento.
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.
La pregunta natural es: ¿deberían estas probabilidades ser fijas durante todo el proceso evolutivo? La respuesta, en la mayoría de los casos, es no. En este artículo verás los cuatro adaptadores que ofrece sklearn-genetic-opt, la matemática detrás de ExponentialAdapter y una regla práctica para elegir adaptive_rate según tu presupuesto de generaciones.
Parámetros fijos vs. adaptativos
Con parámetros fijos, la probabilidad de mutación es la misma en la generación 1 que en la generación 25. Esto presenta un problema inherente: las fases del proceso de optimización tienen necesidades distintas.
- Al inicio: el espacio de hiperparámetros es desconocido. Necesitamos exploración amplia — alta diversidad genética, mutaciones frecuentes para visitar regiones alejadas.
- Al final: ya hemos identificado regiones prometedoras. Necesitamos explotación — refinamiento fino alrededor de los mejores individuos, menos perturbación aleatoria.
Una probabilidad de mutación alta sostenida durante toda la evolución impide la convergencia. Una probabilidad baja desde el principio restringe la exploración y puede quedar atrapada en óptimos locales. Los adaptadores resuelven este dilema ajustando los parámetros dinámicamente a lo largo de las generaciones.
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 implementan una política de transición suave de exploración a explotación. La intuición es simple: al inicio del entrenamiento, queremos operar más como una búsqueda aleatoria; al final, queremos operar más como un descenso de gradiente local.
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 caso trivial: el valor es siempre el mismo. Equivalente a no usar adaptador. Útil como baseline o cuando tienes evidencia de que un valor fijo es óptimo.
from sklearn_genetic import ConstantAdapter
# siempre retorna 0.2, sin importar la generación
adapter = ConstantAdapter(value=0.2)
ExponentialAdapter
Decaimiento exponencial desde un valor inicial alto hacia un valor final bajo. Ideal para la probabilidad de mutación: alta al inicio para explorar, baja al final para refinar.
from sklearn_genetic import ExponentialAdapter
adapter = ExponentialAdapter(initial_value=0.4, end_value=0.05, adaptive_rate=0.1)
InverseAdapter
Crecimiento inverso desde un valor inicial bajo hacia un valor final alto. Adecuado para la probabilidad de cruzamiento: al inicio los individuos son muy distintos entre sí (poco útil mezclarlos), al final ya son similares y el cruzamiento puede refinar la solución.
from sklearn_genetic import InverseAdapter
adapter = InverseAdapter(initial_value=0.6, end_value=0.9, adaptive_rate=0.1)
PotentialAdapter
Crecimiento por función potencial. Permite transiciones más abruptas que el adaptador inverso, útil cuando quieres que el cambio ocurra principalmente al final de la evolución.
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)
Donde t es el número de generación actual. Para entender el efecto del 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.
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("Generación"); plt.ylabel("Probabilidad")
plt.subplot(1, 2, 2)
plt.plot(generations, [inverse_adapter(t) for t in generations])
plt.title("InverseAdapter (cruzamiento)")
plt.xlabel("Generación"); plt.ylabel("Probabilidad")
plt.tight_layout()
plt.show()
Esto ilustra visualmente cómo la probabilidad de mutación decrece de 0.4 a aproximadamente 0.05 mientras el cruzamiento aumenta de 0.6 a 0.9 a lo largo de 25 generaciones.
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:
from sklearn_genetic import GASearchCV, ExponentialAdapter, InverseAdapter, EvolutionConfig
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,
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
),
evolution_config=EvolutionConfig(
population_size=30,
generations=25
),
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}")
Usando solo ExponentialAdapter para mutación
Si prefieres un enfoque más conservador y solo adaptar la mutación:
search_mutation_only = GASearchCV(
estimator=RandomForestClassifier(random_state=42),
param_grid=param_grid,
scoring="accuracy",
cv=cv,
mutation_probability=ExponentialAdapter(
initial_value=0.4,
end_value=0.05,
adaptive_rate=0.1
),
crossover_probability=0.7, # fijo
evolution_config=EvolutionConfig(population_size=30, generations=25),
n_jobs=-1
)
search_mutation_only.fit(X_train, y_train)
Esto es útil cuando tienes certeza sobre la tasa de cruzamiento óptima pero quieres exploración adaptativa en la mutación.
Comparando fijo vs. adaptativo
Para cuantificar el beneficio de los adaptadores, podemos comparar la convergencia:
# Búsqueda con parámetros fijos
search_fixed = GASearchCV(
estimator=RandomForestClassifier(random_state=42),
param_grid=param_grid,
scoring="accuracy",
cv=cv,
mutation_probability=0.1, # fijo
crossover_probability=0.8, # fijo
evolution_config=EvolutionConfig(population_size=30, generations=25),
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}")
En general, los parámetros adaptativos tienden a:
- Converger más rápido: la exploración inicial produce candidatos más diversos.
- Converger a mejores soluciones: la explotación final refina mejor los candidatos.
- Ser más robustos: menos sensibles a la elección inicial de hiperparámetros del algoritmo genético.
Cuándo usar cada adaptador
| Adaptador | Cuándo usar | Ejemplo |
|---|---|---|
ExponentialAdapter | Probabilidad que debe decrecer suavemente | Mutación: explorar al inicio, refinar al final |
InverseAdapter | Probabilidad que debe crecer suavemente | Cruzamiento: mezclar más cuando los individuos convergen |
PotentialAdapter | Crecimiento más abrupto al final | Intensificación tardía de cruzamiento |
ConstantAdapter | Baseline o valor ya optimizado | Cualquier parámetro cuando el adaptativo no mejora |
La elección práctica depende del problema. Como regla general: usa ExponentialAdapter para mutación e InverseAdapter para cruzamiento como punto de partida, y ajusta adaptive_rate según el número de generaciones disponibles.
Conclusión
Los parámetros adaptativos representan una mejora significativa sobre la configuración fija en la búsqueda evolutiva de hiperparámetros. sklearn-genetic-opt implementa estos adaptadores con una API limpia que se integra sin fricción en flujos de trabajo existentes de scikit-learn.
El principio subyacente — alta exploración al inicio, alta explotación al final — es universal en optimización heurística y aparece en formas análogas en simulated annealing (calendario de temperatura), optimización por enjambre de partículas (peso de inercia) y aprendizaje por refuerzo (decaimiento de epsilon-greedy). Entender este trade-off es una de las habilidades más transferibles en el campo de la optimización metaheurística.
Si solo vas a cambiar una cosa en tu próxima ejecución de GASearchCV, reemplaza la mutation_probability fija por un ExponentialAdapter — es un cambio de una línea que casi siempre se paga solo, tanto en velocidad de convergencia como en score final.
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.
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.