Cómo hacer una cuadrícula de hiperparámetros de búsqueda para modelos de aprendizaje profundo en Python con Keras
La optimización de hiperparámetros es una gran parte del aprendizaje profundo.
La razón es que las redes neuronales son muy difíciles de configurar y es necesario establecer muchos parámetros. Además de eso, los modelos individuales pueden tardar mucho en entrenarse.
En esta publicación, descubrirá cómo utilizar la capacidad de búsqueda en cuadrícula de la biblioteca de aprendizaje automático Python scikit-learn para ajustar los hiperparámetros de los modelos de aprendizaje profundo de Keras.
Después de leer este post, sabrás:
- Cómo empaquetar modelos Keras para usarlos en scikit-learn y cómo usar la búsqueda en cuadrícula
- Cómo realizar una búsqueda en cuadrícula de parámetros comunes de redes neuronales, como la tasa de aprendizaje, la tasa de abandono, las épocas y la cantidad de neuronas
- Cómo definir sus propios experimentos de ajuste de hiperparámetros en sus propios proyectos
Pon en marcha tu proyecto con mi nuevo libro Aprendizaje profundo con Python, que incluye tutoriales paso a paso y los archivos de código fuente de Python para todos. ejemplos.
Empecemos.
- Agosto/2016: Publicado por primera vez
- Actualización de noviembre de 2016: se solucionó un problema menor al mostrar resultados de búsqueda de cuadrícula en ejemplos de código
- Actualización de octubre de 2016: ejemplos actualizados para Keras 1.1.0, TensorFlow 0.10.0 y scikit-learn v0.18
- Actualización de marzo de 2017: ejemplo actualizado para Keras 2.0.2, TensorFlow 1.0.1 y Theano 0.9.0
- Actualización de septiembre de 2017: ejemplo actualizado para usar “épocas” de Keras 2 en lugar de “nb_epochs” de Keras 1
- Actualización de marzo de 2018: se agregó un enlace alternativo para descargar el conjunto de datos
- Actualización de octubre de 2019: Actualizado para la API de Keras 2.3.0
- Actualización de julio de 2022: Actualizado para TensorFlow/Keras y SciKeras 0.8
Descripción general
En esta publicación, descubrirá cómo puede utilizar la capacidad de búsqueda en cuadrícula de scikit-learn. Se le proporcionará un conjunto de ejemplos que podrá copiar y pegar en su propio proyecto como punto de partida.
A continuación se muestra una lista de los temas que cubrirá esta publicación:
- Cómo utilizar modelos de Keras en scikit-learn
- Cómo utilizar la búsqueda de cuadrícula en scikit-learn
- Cómo ajustar el tamaño del lote y las épocas de entrenamiento
- Cómo ajustar los algoritmos de optimización
- Cómo ajustar la tasa de aprendizaje y el impulso
- Cómo ajustar la inicialización del peso de la red
- Cómo sintonizar las funciones de activación
- Cómo ajustar la regularización del abandono
- Cómo ajustar la cantidad de neuronas en la capa oculta
Cómo utilizar los modelos Keras en scikit-learn
Los modelos Keras se pueden usar en scikit-learn envolviéndolos con la clase KerasClassifier
o KerasRegressor
del módulo SciKeras. Es posible que necesites ejecutar el comando pip install sciqueras
primero para instalar el módulo.
Para usar estos contenedores, debe definir una función que cree y devuelva su modelo secuencial de Keras, luego pase esta función al argumento model
al construir la clase KerasClassifier
.
Por ejemplo:
def create_model():
...
return model
model = KerasClassifier(model=create_model)
El constructor de la clase KerasClassifier
puede tomar argumentos predeterminados que se pasan a las llamadas a model.fit()
, como el número de épocas y el tamaño del lote.
Por ejemplo:
def create_model():
...
return model
model = KerasClassifier(model=create_model, epochs=10)
El constructor de la clase KerasClassifier
también puede tomar nuevos argumentos que se pueden pasar a su función create_model()
personalizada. Estos nuevos argumentos también deben definirse en la firma de su función create_model()
con parámetros predeterminados.
Por ejemplo:
def create_model(dropout_rate=0.0):
...
return model
model = KerasClassifier(model=create_model, dropout_rate=0.2)
Puede obtener más información sobre estos en la documentación de SciKeras.
Cómo utilizar la búsqueda de cuadrícula en scikit-learn
La búsqueda de cuadrícula es una técnica de optimización de hiperparámetros del modelo.
En scikit-learn, esta técnica se proporciona en la clase GridSearchCV
.
Al construir esta clase, debe proporcionar un diccionario de hiperparámetros para evaluar en el argumento param_grid
. Este es un mapa del nombre del parámetro del modelo y una serie de valores para probar.
De forma predeterminada, la precisión es la puntuación optimizada, pero se pueden especificar otras puntuaciones en el argumento score
del constructor GridSearchCV
.
De forma predeterminada, la búsqueda en la cuadrícula solo utilizará un hilo. Al establecer el argumento n_jobs
en el constructor GridSearchCV
en -1, el proceso utilizará todos los núcleos de su máquina. Sin embargo, a veces esto puede interferir con el proceso de entrenamiento de la red neuronal principal.
El proceso GridSearchCV
construirá y evaluará un modelo para cada combinación de parámetros. La validación cruzada se utiliza para evaluar cada modelo individual y se utiliza el valor predeterminado de validación cruzada triple, aunque puede anular esto especificando el argumento cv
en el constructor GridSearchCV
. .
A continuación se muestra un ejemplo de cómo definir una búsqueda de cuadrícula simple:
param_grid = dict(epochs=[10,20,30])
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
Una vez completado, puede acceder al resultado de la búsqueda de cuadrícula en el objeto de resultado devuelto por grid.fit()
. El miembro best_score_
proporciona acceso a la mejor puntuación observada durante el procedimiento de optimización, y best_params_
describe la combinación de parámetros que lograron los mejores resultados.
Puede obtener más información sobre la clase GridSearchCV en la documentación de la API scikit-learn.
Descripción del problema
Ahora que sabe cómo usar los modelos Keras con scikit-learn y cómo usar la búsqueda de cuadrícula en scikit-learn, veamos algunos ejemplos.
Todos los ejemplos se demostrarán en un pequeño conjunto de datos de aprendizaje automático estándar llamado conjunto de datos de clasificación de inicio de diabetes de los indios Pima. Este es un pequeño conjunto de datos con todos los atributos numéricos con el que es fácil trabajar.
- Descargue el conjunto de datos y colóquelo directamente en su sitio de trabajo actual con el nombre
pima-indians-diabetes.csv
(actualización: descárguelo desde aquí).
A medida que avance con los ejemplos de esta publicación, agregará los mejores parámetros. Esta no es la mejor manera de realizar búsquedas en cuadrículas porque los parámetros pueden interactuar, pero es buena para fines de demostración.
Nota sobre la paralelización de la búsqueda en cuadrícula
Todos los ejemplos están configurados para usar paralelismo (n_jobs=-1
).
Si recibe un error como el siguiente:
INFO (theano.gof.compilelock): Waiting for existing lock by process '55614' (I am process '55613')
INFO (theano.gof.compilelock): To manually release the lock, delete ...
Elimine el proceso y cambie el código para no realizar la búsqueda en la cuadrícula en paralelo; establezca
Cómo ajustar el tamaño del lote y el número de épocas
En este primer ejemplo simple, verá cómo ajustar el tamaño del lote y la cantidad de épocas utilizadas al ajustar la red.
El tamaño del lote en el descenso de gradiente iterativo es la cantidad de patrones que se muestran a la red antes de que se actualicen los pesos. También es una optimización en el entrenamiento de la red, definiendo cuántos patrones leer a la vez y mantener en la memoria.
La cantidad de épocas es la cantidad de veces que todo el conjunto de datos de entrenamiento se muestra a la red durante el entrenamiento. Algunas redes son sensibles al tamaño del lote, como las redes neuronales recurrentes LSTM y las redes neuronales convolucionales.
Aquí evaluará un conjunto de diferentes tamaños de minilotes de 10 a 100 en pasos de 20.
La lista completa de códigos se proporciona a continuación:
# Use scikit-learn to grid search the batch size and epochs
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model():
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), activation='relu'))
model.add(Dense(1, activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, verbose=0)
# define the grid search parameters
batch_size = [10, 20, 40, 60, 80, 100]
epochs = [10, 50, 100]
param_grid = dict(batch_size=batch_size, epochs=epochs)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
La ejecución de este ejemplo produce el siguiente resultado:
Best: 0.705729 using {'batch_size': 10, 'epochs': 100}
0.597656 (0.030425) with: {'batch_size': 10, 'epochs': 10}
0.686198 (0.017566) with: {'batch_size': 10, 'epochs': 50}
0.705729 (0.017566) with: {'batch_size': 10, 'epochs': 100}
0.494792 (0.009207) with: {'batch_size': 20, 'epochs': 10}
0.675781 (0.017758) with: {'batch_size': 20, 'epochs': 50}
0.683594 (0.011049) with: {'batch_size': 20, 'epochs': 100}
0.535156 (0.053274) with: {'batch_size': 40, 'epochs': 10}
0.622396 (0.009744) with: {'batch_size': 40, 'epochs': 50}
0.671875 (0.019918) with: {'batch_size': 40, 'epochs': 100}
0.592448 (0.042473) with: {'batch_size': 60, 'epochs': 10}
0.660156 (0.041707) with: {'batch_size': 60, 'epochs': 50}
0.674479 (0.006639) with: {'batch_size': 60, 'epochs': 100}
0.476562 (0.099896) with: {'batch_size': 80, 'epochs': 10}
0.608073 (0.033197) with: {'batch_size': 80, 'epochs': 50}
0.660156 (0.011500) with: {'batch_size': 80, 'epochs': 100}
0.615885 (0.015073) with: {'batch_size': 100, 'epochs': 10}
0.617188 (0.039192) with: {'batch_size': 100, 'epochs': 50}
0.632812 (0.019918) with: {'batch_size': 100, 'epochs': 100}
Puede ver que el tamaño de lote de 10 y 100 épocas logró el mejor resultado con aproximadamente un 70% de precisión.
Cómo ajustar el algoritmo de optimización del entrenamiento
Keras ofrece un conjunto de diferentes algoritmos de optimización de última generación.
En este ejemplo, ajustará el algoritmo de optimización utilizado para entrenar la red, cada uno con parámetros predeterminados.
Este es un ejemplo extraño porque a menudo elegirá un enfoque a priori y, en cambio, se concentrará en ajustar sus parámetros a su problema (consulte el siguiente ejemplo).
Aquí, evaluará el conjunto de algoritmos de optimización compatibles con la API de Keras.
La lista completa de códigos se proporciona a continuación:
# Use scikit-learn to grid search the batch size and epochs
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model():
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), activation='relu'))
model.add(Dense(1, activation='sigmoid'))
# return model without compile
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, loss="binary_crossentropy", epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
optimizer = ['SGD', 'RMSprop', 'Adagrad', 'Adadelta', 'Adam', 'Adamax', 'Nadam']
param_grid = dict(optimizer=optimizer)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
Tenga en cuenta que la función create_model()
definida anteriormente no devuelve un modelo compilado como el del ejemplo anterior. Esto se debe a que la configuración de un optimizador para un modelo de Keras se realiza en la llamada a la función compile()
; por lo tanto, es mejor dejarlo en manos del contenedor KerasClassifier
y del modelo GridSearchCV
. Además, tenga en cuenta que especificó loss="binary_crossentropy"
en el contenedor, ya que también debería configurarse durante la llamada a la función compile()
.
La ejecución de este ejemplo produce el siguiente resultado:
Best: 0.697917 using {'optimizer': 'Adam'}
0.674479 (0.033804) with: {'optimizer': 'SGD'}
0.649740 (0.040386) with: {'optimizer': 'RMSprop'}
0.595052 (0.032734) with: {'optimizer': 'Adagrad'}
0.348958 (0.001841) with: {'optimizer': 'Adadelta'}
0.697917 (0.038051) with: {'optimizer': 'Adam'}
0.652344 (0.019918) with: {'optimizer': 'Adamax'}
0.684896 (0.011201) with: {'optimizer': 'Nadam'}
El contenedor KerasClassifier
no volverá a compilar su modelo si el modelo ya está compilado. Por lo tanto, la otra forma de ejecutar GridSearchCV
es configurar el optimizador como argumento para la función create_model()
, que devuelve un modelo compilado apropiadamente como el siguiente:
# Use scikit-learn to grid search the batch size and epochs
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model(optimizer='adam'):
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), activation='relu'))
model.add(Dense(1, activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
optimizer = ['SGD', 'RMSprop', 'Adagrad', 'Adadelta', 'Adam', 'Adamax', 'Nadam']
param_grid = dict(model__optimizer=optimizer)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
Tenga en cuenta que en lo anterior, tiene el prefijo model__
en el diccionario de parámetros param_grid
. Esto es necesario para que KerasClassifier
en el módulo SciKeras deje en claro que el parámetro debe enrutar a la función create_model()
como argumentos, en lugar de que algún parámetro para configurar en compile()
o fit()
. Consulte también la sección de parámetros enrutados de la documentación de SciKeras.
La ejecución de este ejemplo produce el siguiente resultado:
Best: 0.697917 using {'model__optimizer': 'Adam'}
0.636719 (0.019401) with: {'model__optimizer': 'SGD'}
0.683594 (0.020915) with: {'model__optimizer': 'RMSprop'}
0.585938 (0.038670) with: {'model__optimizer': 'Adagrad'}
0.518229 (0.120624) with: {'model__optimizer': 'Adadelta'}
0.697917 (0.049445) with: {'model__optimizer': 'Adam'}
0.652344 (0.027805) with: {'model__optimizer': 'Adamax'}
0.686198 (0.012890) with: {'model__optimizer': 'Nadam'}
Los resultados sugieren que el algoritmo de optimización ADAM es el mejor con una puntuación de aproximadamente el 70% de precisión.
Cómo ajustar la tasa de aprendizaje y el impulso
Es común preseleccionar un algoritmo de optimización para entrenar su red y ajustar sus parámetros.
Con diferencia, el algoritmo de optimización más común es el antiguo Descenso de gradiente estocástico (SGD) porque se comprende muy bien. En este ejemplo, verá cómo optimizar la tasa de aprendizaje de SGD y los parámetros de impulso.
La tasa de aprendizaje controla cuánto se debe actualizar el peso al final de cada lote, y el impulso controla cuánto se debe dejar que la actualización anterior influya en la actualización del peso actual.
Probará un conjunto de pequeñas tasas de aprendizaje estándar y valores de impulso de 0,2 a 0,8 en pasos de 0,2, así como 0,9 (porque puede ser un valor popular en la práctica). En Keras, la forma de establecer la tasa de aprendizaje y el impulso es la siguiente:
...
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.2)
En el contenedor de SciKeras, enrutará los parámetros al optimizador con el prefijo optimizer__
.
Generalmente, es una buena idea incluir también la cantidad de épocas en una optimización como esta, ya que existe una dependencia entre la cantidad de aprendizaje por lote (tasa de aprendizaje), la cantidad de actualizaciones por época (tamaño de lote) y la cantidad. de épocas.
La lista completa de códigos se proporciona a continuación:
# Use scikit-learn to grid search the learning rate and momentum
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model():
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), activation='relu'))
model.add(Dense(1, activation='sigmoid'))
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, loss="binary_crossentropy", optimizer="SGD", epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
learn_rate = [0.001, 0.01, 0.1, 0.2, 0.3]
momentum = [0.0, 0.2, 0.4, 0.6, 0.8, 0.9]
param_grid = dict(optimizer__learning_rate=learn_rate, optimizer__momentum=momentum)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
La ejecución de este ejemplo produce el siguiente resultado:
Best: 0.686198 using {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.0}
0.686198 (0.036966) with: {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.0}
0.651042 (0.009744) with: {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.2}
0.652344 (0.038670) with: {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.4}
0.656250 (0.065907) with: {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.6}
0.671875 (0.022326) with: {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.8}
0.661458 (0.015733) with: {'optimizer__learning_rate': 0.001, 'optimizer__momentum': 0.9}
0.665365 (0.021236) with: {'optimizer__learning_rate': 0.01, 'optimizer__momentum': 0.0}
0.671875 (0.003189) with: {'optimizer__learning_rate': 0.01, 'optimizer__momentum': 0.2}
0.640625 (0.008438) with: {'optimizer__learning_rate': 0.01, 'optimizer__momentum': 0.4}
0.648438 (0.003189) with: {'optimizer__learning_rate': 0.01, 'optimizer__momentum': 0.6}
0.649740 (0.003683) with: {'optimizer__learning_rate': 0.01, 'optimizer__momentum': 0.8}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.01, 'optimizer__momentum': 0.9}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.1, 'optimizer__momentum': 0.0}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.1, 'optimizer__momentum': 0.2}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.1, 'optimizer__momentum': 0.4}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.1, 'optimizer__momentum': 0.6}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.1, 'optimizer__momentum': 0.8}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.1, 'optimizer__momentum': 0.9}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.2, 'optimizer__momentum': 0.0}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.2, 'optimizer__momentum': 0.2}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.2, 'optimizer__momentum': 0.4}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.2, 'optimizer__momentum': 0.6}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.2, 'optimizer__momentum': 0.8}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.2, 'optimizer__momentum': 0.9}
0.652344 (0.003189) with: {'optimizer__learning_rate': 0.3, 'optimizer__momentum': 0.0}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.3, 'optimizer__momentum': 0.2}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.3, 'optimizer__momentum': 0.4}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.3, 'optimizer__momentum': 0.6}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.3, 'optimizer__momentum': 0.8}
0.651042 (0.001841) with: {'optimizer__learning_rate': 0.3, 'optimizer__momentum': 0.9}
Puedes ver que SGD no es muy bueno en este problema; sin embargo, los mejores resultados se lograron utilizando una tasa de aprendizaje de 0,001 y un impulso de 0,0 con una precisión de aproximadamente el 68%.
Cómo ajustar la inicialización del peso de la red
La inicialización del peso de la red neuronal solía ser simple: use pequeños valores aleatorios.
Ahora hay un conjunto de técnicas diferentes para elegir. Keras proporciona una lista de lavandería.
En este ejemplo, observará cómo ajustar la selección de inicialización del peso de la red evaluando todas las técnicas disponibles.
Utilizará el mismo método de inicialización de peso en cada capa. Idealmente, puede ser mejor utilizar diferentes esquemas de inicialización de peso según la función de activación utilizada en cada capa. En el siguiente ejemplo, utilizará un rectificador para la capa oculta. Utilice sigmoide para la capa de salida porque las predicciones son binarias. La inicialización del peso ahora es un argumento para la función create_model()
, donde necesita usar el prefijo model__
para pedirle a KerasClassifier
que enrute el parámetro. a la función de creación de modelos.
La lista completa de códigos se proporciona a continuación:
# Use scikit-learn to grid search the weight initialization
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model(init_mode='uniform'):
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), kernel_initializer=init_mode, activation='relu'))
model.add(Dense(1, kernel_initializer=init_mode, activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
init_mode = ['uniform', 'lecun_uniform', 'normal', 'zero', 'glorot_normal', 'glorot_uniform', 'he_normal', 'he_uniform']
param_grid = dict(model__init_mode=init_mode)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
La ejecución de este ejemplo produce el siguiente resultado:
Best: 0.716146 using {'model__init_mode': 'uniform'}
0.716146 (0.034987) with: {'model__init_mode': 'uniform'}
0.678385 (0.029635) with: {'model__init_mode': 'lecun_uniform'}
0.716146 (0.030647) with: {'model__init_mode': 'normal'}
0.651042 (0.001841) with: {'model__init_mode': 'zero'}
0.695312 (0.027805) with: {'model__init_mode': 'glorot_normal'}
0.690104 (0.023939) with: {'model__init_mode': 'glorot_uniform'}
0.647135 (0.057880) with: {'model__init_mode': 'he_normal'}
0.665365 (0.026557) with: {'model__init_mode': 'he_uniform'}
Podemos ver que los mejores resultados se lograron con un esquema de inicialización de peso uniforme logrando un rendimiento de aproximadamente el 72%.
Cómo ajustar la función de activación neuronal
La función de activación controla la no linealidad de las neuronas individuales y cuándo disparar.
Generalmente, la función de activación del rectificador es la más popular. Sin embargo, solían ser las funciones sigmoidea y tanh, y estas funciones aún pueden ser más adecuadas para diferentes problemas.
En este ejemplo, evaluará el conjunto de diferentes funciones de activación disponibles en Keras. Solo usará estas funciones en la capa oculta, ya que se requiere una función de activación sigmoidea en la salida para el problema de clasificación binaria. Similar al ejemplo anterior, este es un argumento para la función create_model()
y usará el prefijo model__
para la cuadrícula de parámetros GridSearchCV
. .
Generalmente, es una buena idea preparar los datos según el rango de las diferentes funciones de transferencia, lo cual no hará en este caso.
La lista completa de códigos se proporciona a continuación:
# Use scikit-learn to grid search the activation function
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model(activation='relu'):
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), kernel_initializer='uniform', activation=activation))
model.add(Dense(1, kernel_initializer='uniform', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
activation = ['softmax', 'softplus', 'softsign', 'relu', 'tanh', 'sigmoid', 'hard_sigmoid', 'linear']
param_grid = dict(model__activation=activation)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
La ejecución de este ejemplo produce el siguiente resultado:
Best: 0.710938 using {'model__activation': 'linear'}
0.651042 (0.001841) with: {'model__activation': 'softmax'}
0.703125 (0.012758) with: {'model__activation': 'softplus'}
0.671875 (0.009568) with: {'model__activation': 'softsign'}
0.710938 (0.024080) with: {'model__activation': 'relu'}
0.669271 (0.019225) with: {'model__activation': 'tanh'}
0.675781 (0.011049) with: {'model__activation': 'sigmoid'}
0.677083 (0.004872) with: {'model__activation': 'hard_sigmoid'}
0.710938 (0.034499) with: {'model__activation': 'linear'}
Sorprendentemente (al menos para mí), la función de activación "lineal" logró los mejores resultados con una precisión de aproximadamente el 71%.
Cómo ajustar la regularización del abandono escolar
En este ejemplo, observará cómo ajustar la tasa de abandono para la regularización en un esfuerzo por limitar el sobreajuste y mejorar la capacidad del modelo para generalizar.
Para obtener mejores resultados, es mejor combinar el abandono con una restricción de peso como la restricción de norma máxima.
Para obtener más información sobre el uso de la deserción en modelos de aprendizaje profundo con Keras, consulte la publicación:
- Regularización de la deserción en modelos de aprendizaje profundo con Keras
Esto implica ajustar tanto el porcentaje de abandono como la restricción de peso. Probaremos porcentajes de abandono entre 0,0 y 0,9 (1,0 no tiene sentido) y valores de restricción de peso máximo entre 0 y 5.
La lista completa de códigos se proporciona a continuación.
# Use scikit-learn to grid search the dropout rate
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Dropout
from tensorflow.keras.constraints import MaxNorm
from scikeras.wrappers import KerasClassifier
# Function to create model, required for KerasClassifier
def create_model(dropout_rate, weight_constraint):
# create model
model = Sequential()
model.add(Dense(12, input_shape=(8,), kernel_initializer='uniform', activation='linear', kernel_constraint=MaxNorm(weight_constraint)))
model.add(Dropout(dropout_rate))
model.add(Dense(1, kernel_initializer='uniform', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
print(dataset.dtype, dataset.shape)
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
weight_constraint = [1.0, 2.0, 3.0, 4.0, 5.0]
dropout_rate = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
param_grid = dict(model__dropout_rate=dropout_rate, model__weight_constraint=weight_constraint)
#param_grid = dict(model__dropout_rate=dropout_rate)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
La ejecución de este ejemplo produce el siguiente resultado.
Best: 0.766927 using {'model__dropout_rate': 0.2, 'model__weight_constraint': 3.0}
0.729167 (0.021710) with: {'model__dropout_rate': 0.0, 'model__weight_constraint': 1.0}
0.746094 (0.022326) with: {'model__dropout_rate': 0.0, 'model__weight_constraint': 2.0}
0.753906 (0.022097) with: {'model__dropout_rate': 0.0, 'model__weight_constraint': 3.0}
0.750000 (0.012758) with: {'model__dropout_rate': 0.0, 'model__weight_constraint': 4.0}
0.751302 (0.012890) with: {'model__dropout_rate': 0.0, 'model__weight_constraint': 5.0}
0.739583 (0.026748) with: {'model__dropout_rate': 0.1, 'model__weight_constraint': 1.0}
0.733073 (0.001841) with: {'model__dropout_rate': 0.1, 'model__weight_constraint': 2.0}
0.753906 (0.030425) with: {'model__dropout_rate': 0.1, 'model__weight_constraint': 3.0}
0.748698 (0.031466) with: {'model__dropout_rate': 0.1, 'model__weight_constraint': 4.0}
0.753906 (0.030425) with: {'model__dropout_rate': 0.1, 'model__weight_constraint': 5.0}
0.760417 (0.024360) with: {'model__dropout_rate': 0.2, 'model__weight_constraint': 1.0}
nan (nan) with: {'model__dropout_rate': 0.2, 'model__weight_constraint': 2.0}
0.766927 (0.021710) with: {'model__dropout_rate': 0.2, 'model__weight_constraint': 3.0}
0.755208 (0.010253) with: {'model__dropout_rate': 0.2, 'model__weight_constraint': 4.0}
0.750000 (0.008438) with: {'model__dropout_rate': 0.2, 'model__weight_constraint': 5.0}
0.725260 (0.015073) with: {'model__dropout_rate': 0.3, 'model__weight_constraint': 1.0}
0.738281 (0.008438) with: {'model__dropout_rate': 0.3, 'model__weight_constraint': 2.0}
0.748698 (0.003683) with: {'model__dropout_rate': 0.3, 'model__weight_constraint': 3.0}
0.740885 (0.023073) with: {'model__dropout_rate': 0.3, 'model__weight_constraint': 4.0}
0.735677 (0.008027) with: {'model__dropout_rate': 0.3, 'model__weight_constraint': 5.0}
0.743490 (0.009207) with: {'model__dropout_rate': 0.4, 'model__weight_constraint': 1.0}
0.751302 (0.006639) with: {'model__dropout_rate': 0.4, 'model__weight_constraint': 2.0}
0.750000 (0.024910) with: {'model__dropout_rate': 0.4, 'model__weight_constraint': 3.0}
0.744792 (0.030314) with: {'model__dropout_rate': 0.4, 'model__weight_constraint': 4.0}
0.751302 (0.010253) with: {'model__dropout_rate': 0.4, 'model__weight_constraint': 5.0}
0.757812 (0.006379) with: {'model__dropout_rate': 0.5, 'model__weight_constraint': 1.0}
0.740885 (0.030978) with: {'model__dropout_rate': 0.5, 'model__weight_constraint': 2.0}
0.742188 (0.003189) with: {'model__dropout_rate': 0.5, 'model__weight_constraint': 3.0}
0.718750 (0.016877) with: {'model__dropout_rate': 0.5, 'model__weight_constraint': 4.0}
0.726562 (0.019137) with: {'model__dropout_rate': 0.5, 'model__weight_constraint': 5.0}
0.725260 (0.013279) with: {'model__dropout_rate': 0.6, 'model__weight_constraint': 1.0}
0.738281 (0.013902) with: {'model__dropout_rate': 0.6, 'model__weight_constraint': 2.0}
0.743490 (0.001841) with: {'model__dropout_rate': 0.6, 'model__weight_constraint': 3.0}
0.722656 (0.009568) with: {'model__dropout_rate': 0.6, 'model__weight_constraint': 4.0}
0.747396 (0.024774) with: {'model__dropout_rate': 0.6, 'model__weight_constraint': 5.0}
0.729167 (0.006639) with: {'model__dropout_rate': 0.7, 'model__weight_constraint': 1.0}
0.717448 (0.012890) with: {'model__dropout_rate': 0.7, 'model__weight_constraint': 2.0}
0.710938 (0.027621) with: {'model__dropout_rate': 0.7, 'model__weight_constraint': 3.0}
0.718750 (0.014616) with: {'model__dropout_rate': 0.7, 'model__weight_constraint': 4.0}
0.743490 (0.021236) with: {'model__dropout_rate': 0.7, 'model__weight_constraint': 5.0}
0.713542 (0.009207) with: {'model__dropout_rate': 0.8, 'model__weight_constraint': 1.0}
nan (nan) with: {'model__dropout_rate': 0.8, 'model__weight_constraint': 2.0}
0.721354 (0.009207) with: {'model__dropout_rate': 0.8, 'model__weight_constraint': 3.0}
0.716146 (0.009207) with: {'model__dropout_rate': 0.8, 'model__weight_constraint': 4.0}
0.716146 (0.015073) with: {'model__dropout_rate': 0.8, 'model__weight_constraint': 5.0}
0.682292 (0.018688) with: {'model__dropout_rate': 0.9, 'model__weight_constraint': 1.0}
0.696615 (0.011201) with: {'model__dropout_rate': 0.9, 'model__weight_constraint': 2.0}
0.696615 (0.026557) with: {'model__dropout_rate': 0.9, 'model__weight_constraint': 3.0}
0.694010 (0.001841) with: {'model__dropout_rate': 0.9, 'model__weight_constraint': 4.0}
0.696615 (0.022628) with: {'model__dropout_rate': 0.9, 'model__weight_constraint': 5.0}
Podemos ver que la tasa de abandono del 20 % y la restricción de peso MaxNorm de 3 dieron como resultado la mejor precisión de aproximadamente el 77 %. Puede notar que parte del resultado es nan
. Probablemente se deba al problema de que la entrada no está normalizada y es posible que por casualidad te encuentres con un modelo degenerado.
Cómo ajustar el número de neuronas en la capa oculta
La cantidad de neuronas en una capa es un parámetro importante a ajustar. Generalmente el número de neuronas en una capa controla la capacidad de representación de la red, al menos en ese punto de la topología.
Además, en general, una red de una sola capa lo suficientemente grande puede aproximarse a cualquier otra red neuronal, al menos en teoría.
En este ejemplo, veremos cómo ajustar la cantidad de neuronas en una sola capa oculta. Probaremos valores del 1 al 30 en pasos de 5.
Una red más grande requiere más entrenamiento y, idealmente, al menos el tamaño del lote y la cantidad de épocas deberían optimizarse con la cantidad de neuronas.
La lista completa de códigos se proporciona a continuación.
# Use scikit-learn to grid search the number of neurons
import numpy as np
import tensorflow as tf
from sklearn.model_selection import GridSearchCV
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Dropout
from scikeras.wrappers import KerasClassifier
from tensorflow.keras.constraints import MaxNorm
# Function to create model, required for KerasClassifier
def create_model(neurons):
# create model
model = Sequential()
model.add(Dense(neurons, input_shape=(8,), kernel_initializer='uniform', activation='linear', kernel_constraint=MaxNorm(4)))
model.add(Dropout(0.2))
model.add(Dense(1, kernel_initializer='uniform', activation='sigmoid'))
# Compile model
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
return model
# fix random seed for reproducibility
seed = 7
tf.random.set_seed(seed)
# load dataset
dataset = np.loadtxt("pima-indians-diabetes.csv", delimiter=",")
# split into input (X) and output (Y) variables
X = dataset[:,0:8]
Y = dataset[:,8]
# create model
model = KerasClassifier(model=create_model, epochs=100, batch_size=10, verbose=0)
# define the grid search parameters
neurons = [1, 5, 10, 15, 20, 25, 30]
param_grid = dict(model__neurons=neurons)
grid = GridSearchCV(estimator=model, param_grid=param_grid, n_jobs=-1, cv=3)
grid_result = grid.fit(X, Y)
# summarize results
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
Nota: Sus resultados pueden variar dada la naturaleza estocástica del algoritmo o procedimiento de evaluación, o diferencias en la precisión numérica. Considere ejecutar el ejemplo varias veces y comparar el resultado promedio.
La ejecución de este ejemplo produce el siguiente resultado.
Best: 0.729167 using {'model__neurons': 30}
0.701823 (0.010253) with: {'model__neurons': 1}
0.717448 (0.011201) with: {'model__neurons': 5}
0.717448 (0.008027) with: {'model__neurons': 10}
0.720052 (0.019488) with: {'model__neurons': 15}
0.709635 (0.004872) with: {'model__neurons': 20}
0.708333 (0.003683) with: {'model__neurons': 25}
0.729167 (0.009744) with: {'model__neurons': 30}
Podemos ver que los mejores resultados se lograron con una red con 30 neuronas en la capa oculta con una precisión de aproximadamente el 73%.
Consejos para la optimización de hiperparámetros
En esta sección se enumeran algunos consejos útiles que se deben tener en cuenta al ajustar los hiperparámetros de su red neuronal.
- Validación cruzada de k veces. Puede ver que los resultados de los ejemplos de esta publicación muestran cierta variación. Se utilizó una validación cruzada predeterminada de 3, pero quizás k=5 o k=10 serían más estables. Elija cuidadosamente su configuración de validación cruzada para garantizar que sus resultados sean estables.
- Revise toda la cuadrícula. No se centre únicamente en el mejor resultado, revise toda la tabla de resultados y busque tendencias que respalden las decisiones de configuración.
- Paralelizar. Utilice todos sus núcleos si puede, las redes neuronales tardan en entrenarse y, a menudo, queremos probar muchos parámetros diferentes. Considere la posibilidad de activar muchas instancias de AWS.
- Utilice una muestra de su conjunto de datos. Debido a que las redes tardan en entrenarse, intente entrenarlas en una muestra más pequeña de su conjunto de datos de entrenamiento, solo para tener una idea de las direcciones generales de los parámetros en lugar de las configuraciones óptimas.
- Empiece con rejillas gruesas. Comience con cuadrículas de grano grueso y amplíe las cuadrículas de grano más fino una vez que pueda reducir el alcance.
- No transferir resultados. Los resultados generalmente son específicos del problema. Intente evitar configuraciones favoritas en cada nuevo problema que vea. Es poco probable que los resultados óptimos que descubra en un problema se transfieran a su próximo proyecto. En su lugar, busque tendencias más amplias, como el número de capas o las relaciones entre parámetros.
- La reproducibilidad es un problema. Aunque configuramos la semilla para el generador de números aleatorios en NumPy, los resultados no son 100 % reproducibles. La reproducibilidad cuando se busca en cuadrículas en modelos Keras ajustados implica más cosas de las que se presentan en esta publicación.
Resumen
En esta publicación, descubrió cómo puede ajustar los hiperparámetros de sus redes de aprendizaje profundo en Python usando Keras y scikit-learn.
Específicamente, aprendiste:
- Cómo empaquetar modelos de Keras para usarlos en scikit-learn y cómo usar la búsqueda en cuadrícula.
- Cómo realizar una búsqueda en cuadrícula de un conjunto de diferentes parámetros de redes neuronales estándar para modelos Keras.
- Cómo diseñar sus propios experimentos de optimización de hiperparámetros.
¿Tiene alguna experiencia en el ajuste de hiperparámetros de grandes redes neuronales? Comparta sus historias a continuación.
¿Tiene alguna pregunta sobre la optimización de hiperparámetros de redes neuronales o sobre esta publicación? Haga sus preguntas en los comentarios y haré todo lo posible para responder.