Descubre y aprende a usar la librería Optuna, la manera más eficiente de tunear hiperparámetros de nuestro modelo de Machine Learning.

¿En que consiste la búsqueda de hiperparámetros?
A la hora de diseñar modelos de Machine Learning, la optimización del modelo es siempre un tema muy importante. Es un proceso en general muy tedioso y que se suele dejar muy al final del ciclo de producción por la cantidad de tiempo que requiere. Si este tema os pilla un poco fuera de lugar, os recomendamos este otro post en el que hablamos del tema en mayor detalle.
Estos procesos en realidad no son más que escoger una serie de valores para cada hiperparámetro, realizar las combinaciones posibles y empezar a probar distintos valores. Algunos métodos realizan una búsqueda exhaustiva y otros eligen solo ciertos elementos del espacio de búsqueda. Son métodos muy efectivos y que en general nos permiten obtener los hiperparámetros óptimos (cuasi óptimos).
Sin embargo, para modelos muy complejos o con unos datos de un tamaño inusual, se convierte en un proceso que puede durar fácilmente unas cuantas horas sino se tiene una capacidad de computación decente. Algunos modelos como los XGBoost tienen una elevadísima cantidad de hiperparámetros. Dependiendo de la complejidad de los datos y del problema a resolver, la optimización de estos hiperparámetros se podría eternizar…
Entonces… ¿Existe alguna alternativa?¿O estamos resignados a necesitar tal cantidad de tiempo para encontrar el modelo óptimo?
Afortunadamente, existen un montón de mentes maravillosas que se han dado cuenta de este problema y han ideado diversas propuestas para resolverlo.
Introducción a la optimización de hiperparámetros
En el post de hoy hablaremos de la optimización de hiperparámetros avanzada.
Estos métodos de optimización de hiperparámetros se basan en redes bayesianas. No nos vamos a marear demasiado con la parte teórica que tienen por detrás pero básicamente la idea es definir un modelo probabilístico donde la función objetivo es la métrica que se utiliza como evaluación del modelo. El algoritmo va orientando la búsqueda óptima del hiperparámetro en función de los resultados obtenidos logrando una rápida convergencia hacia los valores óptimos.
Sin ahondar más en detalles, las tres librerías más utilizadas de optimización de hiperparámetros son las siguientes:
En este post nos centraremos en Optuna por su simplicidad y su potencial. Para su instalación basta con instalarlo mediante nuestro gestor de dependencias (pip install optuna)
Búsqueda de hiperparámetros con Optuna – Regresión Logística
Introducción
Supongamos que nuestro objetivo es tratar de afinar el comportamiento de una regresión logística. Los parámetros con los que jugaremos serán las iteraciones máximas antes de converger y la regularización aplicada. Si quieres conocer más en detalle cómo funciona la regresión logística te invito a leer este post.
En primer lugar importamos las librerías :
import pandas as pd import numpy as np from sklearn import datasets from sklearn.model_selection import train_test_split from matplotlib import pyplot as plt from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import cross_val_score,RepeatedStratifiedKFold,StratifiedKFold from sklearn.metrics import classification_report, confusion_matrix import xgboost as xgb from collections import Counter import optuna from optuna.samplers import TPESampler from sklearn.datasets import make_classification
Como conjunto de datos vamos a utilizar los que nos proporciona sklearn con load_boston pero con una pequeña modificación para utilizarlos como clasificación.
data = datasets.load_boston() X = data.data y_cont = data.target y=[2 if i <19 else 1 if i<24 else 0 for i in y_cont ]
Creamos un modelo de regresión logística sin optimizar para tener un marco de referencia sobre el que trabajar.Observamos que nos da una accuracy bastante decente de 0.71-0.72 tanto en training como en test.
Definición del objetivo y del estudio
Vale, es hora de meterse de lleno con Optuna. A la hora de utilizar esta librería, siempre tenemos que definir 3 componentes principales :
- El objetivo. En esta sección se indica cual es el objetivo de la optimización y qué parámetros se utilizarán en dicha optimización. Esta función deberá devolver un valor objetivo o score, que podrá ser la métrica que deseemos. Dentro del estudio se definen los parámetros a evaluar dentro de la red. En este caso C, definido en un rango de valores de distribución logistica y el parámetro l1_ratio, con un rango de valores de distribución uniforme.
def objective(trial): # Define the search space c_space=trial.suggest_loguniform('C',10e-1, 100) l1_rat_space=trial.suggest_uniform('l1_ratio',0.1, 1) clf =LogisticRegression(max_iter = 4000,penalty='elasticnet',solver='saga', random_state=2,C=c_space,l1_ratio=l1_rat_space) score = cross_val_score(clf, X_train, y_train, scoring='accuracy', cv=4).mean() return score
- El estudio. Se crea el estudio asignándole un nombre , una dirección de optimización y el algoritmo a utilizar como sampler. La dirección de optimización dependerá de la métrica utilizada. Si se utilizan métricas como la accuracy o el valor AUC, se querrá maximizar. Si por el contrario se utilizan medidas de error como el rmse o el logloss, queremos minimizar la función objetivo. El algoritmo a utilizar dependerá de la aproximación teórica que se le quiera dar. El sampler por defecto es el BaseSampler pero a mi particularmente me gusta usar el TPESampler ya que su estructura similar a un árbol mejora su rendimiento.
study_rl = optuna.create_study(study_name="rlog_artif", direction="maximize", pruner=optuna.pruners.HyperbandPruner(max_resource="auto"), sampler=TPESampler())
- Optimización. Utilizando el estudio y el objetivo definidos, se procede a realizar la optimización. En esta sección también se podrá acotar el estudio indicando cuántas iteraciones realizar o incluso limitando el tiempo de ejecución.
study_rl.optimize(objective, n_trials=50)
Análisis de resultados
En la salida de la consola (o del notebook) podemos ir viendo las ejecuciones de nuestro modelo.
Cuando termina, podemos acceder a los parámetros óptimos de la optimización y el valor de accuracy conseguido.
Observamos que los parámetros seleccionados como ganadores son :

modelo_1=LogisticRegression(C=9.478273641313464,l1_ratio=0.9131139036576349,penalty='elasticnet',solver='saga') modelo_1.fit(X_train,y_train) modelo_1.score(X_test,y_test)
Y que la accuracy obtenida es de 0.74. Podemos observar A simple vista parece que nuestro modelo no ha mejorado mucho… ¿Puede ser porque la regresión logística ya no da más de si? Probemos con un árbol de decisión.
Optuna. Árbol de decisión
baseline2=DecisionTreeClassifier(random_state=2) baseline2.fit(X_train,y_train) baseline2.score(X_test,y_test)
En este caso el baseline nos da ya una accuracy de 0.74. Vayamos con Optuna a ver si podemos exprimirlo más.
Al igual que antes, lanzamos las tres partes.
Definición del objetivo y del estudio
start_time = time.time() def objective(trial): # Define the search space max_depth = trial.suggest_int('max_depth', 5, X_train.shape[1]) min_samples_split = trial.suggest_int('min_samples_split', 2, 20) min_samples_leaf = trial.suggest_int('min_samples_leaf', 2, 20) min_impurity_decrease = trial.suggest_float('min_impurity_decrease', 0, 1) model = DecisionTreeClassifier(random_state=2, max_depth=max_depth, min_samples_split=min_samples_split, min_samples_leaf=min_samples_leaf, min_impurity_decrease=min_impurity_decrease ) score = cross_val_score(model, X_train, y_train, scoring='accuracy', cv=4).mean() return score study_DT = optuna.create_study(study_name="dectree_artf", direction="maximize", sampler=TPESampler()) study_DT.optimize(objective, n_trials=1000) print("--- {} minutos para el tuneo---".format((time.time() - start_time)/60))

modelo2=DecisionTreeClassifier(max_depth=5,min_samples_split=12,min_samples_leaf=3 ,min_impurity_decrease=0.0007123522700617675) modelo2.fit(X_train,y_train) modelo2.score(X_test,y_test)
En este caso, conseguimos subir la accuracy hasta un 0.78.
Visualización de los estudios
Otro de los grandes potenciales que tiene esta librería, aparte de la optimización de los parámetros , es la visualización de los resultados.Con plot_optimization_history podemos observar la evolución de nuestra optimización.

Con el parámetro plot_param_importances también se puede ver el impacto de cada parámetro en la función objetivo, algo fundamental para posteriores análisis.
Es una maravilla que nos facilita el proceso muchísimo…
¿Y tú a que esperas para usarlo? Cuéntanos que te parece en los comentarios!