La optimización de hiperparámetros (HPO, Hyperparameter optimization) es un mecanismo para aproximar una versión de un modelo con alto rendimiento y efectividad. Estos hiperparámetros, a diferencia de los parámetros de los modelos, son establecidos por el ingeniero antes del entrenamiento.
Un hiperparámetro (hyperparameter) es una variable de configuración externa al modelo que se utiliza para entrenarlo. Dependiendo del modelo, podemos encontrarnos multitud de hiperparámetros:
Un parámetro de un modelo son las características que se optimizan para entrenarlo y que conforman su aprendizaje. Estos valores no son accesibles por nosotros como desarrolladores. Por ejemplo, en el caso de una regresión lineal, estos parámetros serán la pendiente y la intersección, por ejemplo.
Con el conjunto de datos de entrenamiento y un algoritmo de aprendizaje (como el que vimos anteriormente sobre el descenso del gradiente), conseguimos alterar estos valores y que el modelo sepa clasificar o predecir los casos.
Sin embargo, un hiperparámetro, a diferencia, se establece antes de la fase de entrenamiento y permite al desarrollador crear un contexto y preparar al modelo.
Parámetro | Hiperparámetro |
---|---|
Imprescindibles para realizar predicciones. | Imprescindibles para inicializar los parámetros del modelo, que después serán optimizados. |
Se estiman mediante algoritmos de aprendizaje (descenso del gradiente, Adam, Adagrad...). | Se estiman mediante el método de optimización. |
No se establecen manualmente. | Se establecen manualmente. |
El valor final se obtiene después de la fase de aprendizaje y decidirán la precisión del modelo y cómo predecirá nuevos datos. | La elección de estos valores decidirán cómo de eficiente será el entrenamiento. También tiene un gran impacto en el proceso de optimización de los parámetros. |
Normalmente, no conocemos los valores óptimos para los hiperparámetros que generarían el mejor de los resultados del modelo. Por lo tanto, es un paso vital e importante incluir esto en toda construcción de un modelo de Machine Learning.
Existen varias estrategias para llevarlo a cabo. Primero, entrenamos un modelo base:
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
train_data = pd.read_csv("https://raw.githubusercontent.com/4GeeksAcademy/machine-learning-content/master/assets/clean_titanic_train.csv")
test_data = pd.read_csv("https://raw.githubusercontent.com/4GeeksAcademy/machine-learning-content/master/assets/clean_titanic_test.csv")
X_train = train_data.drop(["Survived"], axis = 1)
y_train = train_data["Survived"]
X_test = test_data.drop(["Survived"], axis = 1)
y_test = test_data["Survived"]
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
base_accuracy = accuracy_score(y_test, y_pred)
base_accuracy
Como vemos, la precisión "base", utilizando la configuración por defecto del modelo es de un 84,7%. Vamos a ver si podemos mejorar estos resultados utilizando las distintas técnicas.
La búsqueda en cuadrícula (grid search) es un método que realiza una búsqueda exhaustiva a través de un subconjunto específico (establecido manualmente) de valores y luego probar todas las posibles combinaciones hasta encontrar el mejor de los modelos.
from sklearn.model_selection import GridSearchCV
# Definimos los parámetros que queremos ajustar a mano
hyperparams = {
"C": [0.001, 0.01, 0.1, 1, 10, 100, 1000],
"penalty": ["l1", "l2", "elasticnet", None],
"solver": ["newton-cg", "lbfgs", "liblinear", "sag", "saga"]
}
# Inicializamos la cuadrícula
grid = GridSearchCV(model, hyperparams, scoring = "accuracy", cv = 5)
grid
def warn(*args, **kwargs):
pass
import warnings
warnings.warn = warn
grid.fit(X_train, y_train)
print(f"Mejores hiperparámetros: {grid.best_params_}")
Como vemos, los parámetros optimizados utilizando esta técnica son:
C
: 10penalty
: l1solver
: liblinearAdemás, siempre debemos utilizar el conjunto de datos de entrenamiento para ajustarlo. Ahora solo tenemos que repetir el entrenamiento estableciendo estos parámetros en el modelo:
model_grid = LogisticRegression(penalty = "l1", C = 10, solver = "liblinear")
model_grid.fit(X_train, y_train)
y_pred = model_grid.predict(X_test)
grid_accuracy = accuracy_score(y_test, y_pred)
grid_accuracy
Observamos una mejora de poco menos de un 1%. ¡Pero esto en un dataset del mundo real es una grandísima victoria!
Además, hemos utilizado tres de los muchos hiperparámetros que acepta este modelo. Podríamos construir una cuadrícula (grid) mucho más compleja (y que tardaría más en ejecutar) para mejorar los resultados.
Como puntos a favor podemos encontrar:
Sin embargo, caben destacar los siguientes puntos negativos:
La búsqueda aleatoria (random search) es similar al anterior, pero, en lugar de probar todas las combinaciones posibles de unos valores de hiperparámetros previamente establecidos, esta metodología selecciona aleatoriamente combinaciones de hiperparámetros para probar.
import numpy as np
from sklearn.model_selection import RandomizedSearchCV
# Definimos los parámetros que queremos ajustar
hyperparams = {
"C": np.logspace(-4, 4, 20),
"penalty": ["l1", "l2", "elasticnet", None],
"solver": ["newton-cg", "lbfgs", "liblinear", "sag", "saga"]
}
# Inicializamos la búsqueda aleatoria
random_search = RandomizedSearchCV(model, hyperparams, n_iter = 100, scoring = "accuracy", cv = 5, random_state = 42)
random_search
random_search.fit(X_train, y_train)
print(f"Mejores hiperparámetros: {random_search.best_params_}")
Como vemos, los parámetros optimizados utilizando esta técnica son:
C
: 29.7635penalty
: l2solver
: lbfgsAdemás, podemos apreciar en los logs que ha habido algunos errores debido a incompatibilidades entre atributos (valores de un atributo que son incompatibles con valores de otro). Esto es manejado por la propia función de estimación y no debemos preocuparnos, ya que siempre nos va a devolver la mejor de las soluciones sin fallos.
Con esta nueva hiperparametrización, reentrenamos el modelo:
model_random_search = LogisticRegression(penalty = "l2", C = 29.7635, solver = "lbfgs")
model_random_search.fit(X_train, y_train)
y_pred = model_random_search.predict(X_test)
random_search_accuracy = accuracy_score(y_test, y_pred)
random_search_accuracy
Como vemos, arroja el mismo nivel de precisión que la estrategia anterior. Esto quiere decir que con los medios e hiperparámetros que hemos intentado optimizar nos encontramos en un máximo local, esto es, que tendríamos que repetir la estrategia de optimización incluyendo otros hiperparámetros para mejorar los resultados del modelo, ya que solo jugando con el penalty
, C
y solver
no vamos a mejorar el modelo más de lo que ya está.
Como puntos a favor podemos encontrar:
Como puntos desfavorables podemos encontrar:
random_state
).Ambas son técnicas de búsqueda de hiperparámetros y pueden ser útiles en diferentes situaciones. La búsqueda en cuadrícula es más adecuada cuando tenemos un conjunto pequeño y bien definido de hiperparámetros, y la búsqueda aleatoria es más útil cuando existe un espacio de hiperparámetros grande y/o no tenemos una idea clara de cuáles podrían ser los mejores valores a optimizar.