A tu propio ritmo

Explora nuestra extensa colección de cursos diseñados para ayudarte a dominar varios temas y habilidades. Ya seas un principiante o un aprendiz avanzado, aquí hay algo para todos.

Bootcamp

Aprende en vivo

Únete a nosotros en nuestros talleres gratuitos, webinars y otros eventos para aprender más sobre nuestros programas y comenzar tu camino para convertirte en desarrollador.

Próximos eventos en vivo

Catálogo de contenidos

Para los geeks autodidactas, este es nuestro extenso catálogo de contenido con todos los materiales y tutoriales que hemos desarrollado hasta el día de hoy.

Tiene sentido comenzar a aprender leyendo y viendo videos sobre los fundamentos y cómo funcionan las cosas.

Buscar en lecciones


IngresarEmpezar
← Regresar a lecciones
Editar en Github
Abrir en Colab

Optimización de Hiperparámetros del modelo

Optimización de hiperparámetros

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.

¿Qué es un hiperparámetro?

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:

  • Tasa de aprendizaje en el descenso de gradiente.
  • Número de iteraciones en el descenso de gradiente.
  • Número de capas en una Red Neuronal.
  • Número de neuronas por capa en una Red Neuronal.
  • Número de agrupaciones (k) en un modelo k-NN.

Diferencia entre parámetro e hiperparámetro

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ámetroHiperpará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.

Proceso de optimización de hiperpará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:

In [1]:
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
Out[1]:
0.8473282442748091

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.

1. Búsqueda en cuadrícula

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.

In [2]:
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
Out[2]:
GridSearchCV(cv=5, estimator=LogisticRegression(),
             param_grid={'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000],
                         'penalty': ['l1', 'l2', 'elasticnet', None],
                         'solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag',
                                    'saga']},
             scoring='accuracy')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
In [3]:
def warn(*args, **kwargs):
    pass
import warnings
warnings.warn = warn

grid.fit(X_train, y_train)

print(f"Mejores hiperparámetros: {grid.best_params_}")
Mejores hiperparámetros: {'C': 10, 'penalty': 'l1', 'solver': 'liblinear'}

Como vemos, los parámetros optimizados utilizando esta técnica son:

  • C: 10
  • penalty: l1
  • solver: liblinear

Ademá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:

In [4]:
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
Out[4]:
0.851145038167939

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.

Pros y contras de esta estrategia

Como puntos a favor podemos encontrar:

  • Exhaustividad: Prueba todas las combinaciones posibles de hiperparámetros dentro de la cuadrícula proporcionada, por lo que si la combinación óptima está dentro de ella, esta metodología la encontrará.
  • Reproducibilidad: Debido a su naturaleza determinista (no aleatoria), siempre se obtendrá el mismo resultado con los mismos parámetros e input.

Sin embargo, caben destacar los siguientes puntos negativos:

  • Eficiencia: Es muy costoso computacionalmente. Puede llevar mucho tiempo y requerir muchos recursos, especialmente si la cantidad de hiperparámetros es grande y/o el rango de valores es amplio.
  • No garantiza llegar al mejor de los resultados, ya que depende de los hiperparámetros y de los valores que el desarrollador establezca.

2. Búsqueda aleatoria

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.

In [5]:
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
Out[5]:
RandomizedSearchCV(cv=5, estimator=LogisticRegression(), n_iter=100,
                   param_distributions={'C': array([1.00000000e-04, 2.63665090e-04, 6.95192796e-04, 1.83298071e-03,
       4.83293024e-03, 1.27427499e-02, 3.35981829e-02, 8.85866790e-02,
       2.33572147e-01, 6.15848211e-01, 1.62377674e+00, 4.28133240e+00,
       1.12883789e+01, 2.97635144e+01, 7.84759970e+01, 2.06913808e+02,
       5.45559478e+02, 1.43844989e+03, 3.79269019e+03, 1.00000000e+04]),
                                        'penalty': ['l1', 'l2', 'elasticnet',
                                                    None],
                                        'solver': ['newton-cg', 'lbfgs',
                                                   'liblinear', 'sag',
                                                   'saga']},
                   random_state=42, scoring='accuracy')
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
In [6]:
random_search.fit(X_train, y_train)

print(f"Mejores hiperparámetros: {random_search.best_params_}")
Mejores hiperparámetros: {'solver': 'lbfgs', 'penalty': 'l2', 'C': 29.763514416313132}

Como vemos, los parámetros optimizados utilizando esta técnica son:

  • C: 29.7635
  • penalty: l2
  • solver: lbfgs

Ademá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:

In [7]:
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
Out[7]:
0.851145038167939

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á.

Pros y contras de esta estrategia

Como puntos a favor podemos encontrar:

  • Eficiencia: Por lo general, es más rápido que el grid search, ya que no prueba todas las combinaciones posibles, sino que selecciona aleatoriamente un número concreto de ellas.
  • Puede acercarse más a la optimización global al seleccionar valores aleatorios, ya que no hay una cuadrícula fija de ellos.

Como puntos desfavorables podemos encontrar:

  • Aleatoriedad. No garantiza la misma solución en cada ejecución, a menos que se fije una semilla (random_state).
  • No es exhaustivo: Puede que no pruebe la mejor combinación de hiperparámetros si tienes mala suerte con la selección aleatoria.

¿Cuándo utilizar cada estrategia?

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.