Os recuerdo que este tutorial forma parte de una colección de apuntes personales que estoy tomando de auto-formación en aprendizaje automático (no es una conclusión sino un camino).
- Instalación de TensorFlow y entorno de desarrollo Python en Mac.
- Revisión de ejemplos de TensorFlow.
- Principios de aprendizaje automático supervisado.
- Primeros pasos con Python: los tipos de datos básicos.
- Entendiendo un modelo de regresión lineal con TensorBoard.
En el anterior tutorial os presentaba cómo funciona TensorFlow y TensorBoard a través de un ejemplo muy simple de regresión lineal.
Ahora es el momento de empezar a complicar un poquito la cosa y hacer mis propios ejemplos para sacar conclusiones propias (y os quedaré agradecidos por la crítica constructiva).
Os recuerdo que partiremos de este tutorial y que no tenéis que instalar nada para poder utilizar estos ejemplos: Simplemente, ir a Colab y partiendo del ejemplo base, ir cambiando lo que os interese.
Vamos a mostrar poco a poco el código. Lo primero que hacemos es inicializar TensorFlow y TensorBoard. Esto no tiene nada de particular.
Y ahora vamos a hacer una pequeña variación. En vez de generar datos de una recta vamos a generar dos rectas, con pendientes distintas, y ver cómo se comporta en modelo de entrenamiento, para posteriormente tratar de realizar modificaciones y conseguir que las predicciones cuadren con lo esperado.
data_size = 1000 train_pct = 0.8 train_size = int(data_size * train_pct) # Creamos un espacio lineal de datos entre -3 y 3 x = np.linspace(-3, 3, data_size) #creamos una array vacio donde asignar los calores de Y y = np.array([]) #desordenamos los valores de X para que la muestra tenga de las dos gráficas no consecutivas np.random.shuffle(x) from numpy import random valor = 0 #Si el valor de X es 1 usamos una ecuación para Y y sino usamos otra for elemento in x: ruido = random.uniform(0, 0.05) if elemento <= 1: valor = 0.5 * elemento + 2 + ruido else: valor = 2 * elemento + 4 + ruido y = np.append (y,valor) # Generamos los juegos de datos de entrenamiento y prueba x_train, y_train = x[:train_size], y[:train_size] x_test, y_test = x[train_size:], y[train_size:] # Importamos la librería de gráficos import matplotlib.pyplot as plt #plt.hist (y_test) # Mostramos los datos plt.scatter(x_train,y_train) plt.title('Datos de entrenamiento') plt.grid(alpha =.9, linestyle ='--') plt.plot([-1, 1], [0.5 * -1 + 2 , 0.5 * 1 + 2],color ='red') plt.show()
Y vemos los datos de Test también:
Voy a hacer un pequeño lapsus de teoría aquí.
Os invito a revisar este enlace sobre regresión polinómica en TensorFlow para entender los grados.
En el siguiente enlace podéis ver la representación de múltiples ecuaciones (que muchos hemos estudiado pero a ver quién se acuerda).
Y utilizamos el mismo código para hacer el entrenamiento:
# Configuramos los logs para poderlos utilizar en TensorBoard mediante callbacks logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S") tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir) # Revisar los tipos de callbacks https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/
Ahora sería buen momento para estudiar cómo construir un modelo secuencial.
# Configuramos un modelo secuencia básico model = keras.models.Sequential([ keras.layers.Dense(16, input_dim=1), keras.layers.Dense(1), ]) model.summary()
Podemos visualizar el sumario de la red que tenemos:
Y también podemos visualizar el modelo gráficamente.
keras.utils.plot_model(model, 'representacion.png', show_shapes=True)
Ahora compilamos el modelo y lo entrenamos.
# Configuramos y establecemos un modelo de regresión lineal con un salto de 0.2 model.compile( loss='mse', # keras.losses.mean_squared_error optimizer=keras.optimizers.SGD(lr=0.02), ) # Configuramos y entrenamos el modelo training_history = model.fit( x_train, # input y_train, # output batch_size=train_size, verbose=2, # Suppress chatty output; use Tensorboard instead epochs=10, validation_data=(x_test, y_test), callbacks=[tensorboard_callback], ) # Mostramos la perdida media print("Average test loss: ", np.average(training_history.history['loss'])) # Visualizamos el error print(training_history.params) print(training_history.history['loss']) print(training_history.history['val_loss']) plt.plot(training_history.history['loss']) plt.plot(training_history.history['val_loss']) # Pintamos la gráfica plt.show()
Vemos las gráficas sobre cómo funciona la pérdida… que se nos va.
Y podemos ver que algo no funciona demasiado bien porque el error tiende a infinito.
Por tanto, los parámetros de configuración que podemos manejar hasta ahora no son satisfactorios.
El sistema no es capaz de predecir los resultados.
Podemos actuar por ahora en varios puntos:
- El coeficiente de aprendizaje.
- Las capas de la red.
- La ecuación de activación.
Vamos a empezar a actuar en el coeficiente de aprendizaje que nos hace sospechar que es muy grande y lo podemos intentar bajar.
Se pueden hacer varias pruebas: bajándolo una dimensión y luego ir incrementándolo contrastando resultados de pérdida.
Y obtenemos el siguiente resultado:
Superados ciertos intentos la cosa no mejora y la perdida total media es de 5.5.
Vemos los valores que obtenemos de la predicción.
Vamos a buscar un método simple para pintar un espacio vectorial de 25 puntos y calcular su predicción, para ver visualmente la relación que hay entre los datos iniciales de entrenamiento y predicciones.
datosPrediccion = np.linspace(-3, 3, 25) resPrediccion = model.predict(datosPrediccion) print("x",datosPrediccion) print("y",resPrediccion) plt.title('Datos de predecidos') plt.grid(alpha =.9, linestyle ='--') plt.scatter(x_train,y_train) plt.scatter(datosPrediccion,resPrediccion) plt.show()
Esta gráfica nos va a permitir hacernos una mejor idea de lo que está pasando:
Podemos continuar con el ejemplo cambiando los coeficientes de entrenamiento a mano subiéndolos y bajándolos un poco. También podemos cambiar el ratio de entrenamiento dinámicamente.
logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S") file_writer = tf.summary.create_file_writer(logdir + "/metrics") file_writer.set_as_default() def lr_schedule(epoch): """ Returns a custom learning rate that decreases as epochs progress. """ learning_rate = 0.05 if epoch > 10: learning_rate = 0.02 if epoch > 20: learning_rate = 0.001 if epoch > 50: learning_rate = 0.001 tf.summary.scalar('learning rate', data=learning_rate, step=epoch) return learning_rate lr_callback = keras.callbacks.LearningRateScheduler(lr_schedule) tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir) model = keras.models.Sequential([ keras.layers.Dense(16, input_dim=1), keras.layers.Dense(1), ]) model.compile( loss='mse', # keras.losses.mean_squared_error optimizer=keras.optimizers.SGD(), ) training_history = model.fit( x_train, # input y_train, # output batch_size=train_size, verbose=2, # Suppress chatty output; use Tensorboard instead epochs=100, validation_data=(x_test, y_test), callbacks=[tensorboard_callback, lr_callback], )
Y comprobamos que hemos mejorado la pérdida media.
Por lo que parece que modificando el ratio de aprendizaje, solamente obtenemos una mejora parcial. Ahora vamos a modificar la complejidad de la red para ver si mejoramos.
Intuitivamente si añadimos una capa más se produce una “multiplicación de una transformación por otra” lo que provocará una adaptación a las rectas con una curva más compleja”.
Os invito a que re-visitéis ahora el entrenador de TensorFlow para comprender mejor cómo afecta introducir complejidad en la red neuronal y modificar la función de activación y ratio de aprendizaje.
Modificamos la red.
logdir = "logs/scalars/" + datetime.now().strftime("%Y%m%d-%H%M%S") tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir) from tensorflow.keras import layers # Define Sequential model with 3 layers model = keras.Sequential( [ layers.Dense(2, activation="relu", name="layer1"), layers.Dense(3, activation="relu", name="layer2"), layers.Dense(1, name="layer3"), ] )
Comprobamos la nueva red montada.
Compilamos y revisamos.
model.compile( loss='mse', # keras.losses.mean_squared_error optimizer=keras.optimizers.SGD(lr=0.04), ) training_history = model.fit( x_train, # input y_train, # output batch_size=train_size, verbose=2, # Suppress chatty output; use Tensorboard instead epochs=100, validation_data=(x_test, y_test), callbacks=[tensorboard_callback], ) print("Average test loss: ", np.average(training_history.history['loss']))
Y vemos que la curva tiene otra pinta.
Y esta es la adaptación entre la predicción y las muestras, que va teniendo ya mejor aspecto (considerando que siempre hará un error o sobre-adaptación).
Ahora vamos a aumentar en número de núcleos a 16 en la segunda capa.
Visualizando la nueva red.
Y comprovamos que se suaviza la curva anterior.
Aunque la adaptación no es la más satisfactoria.
Pero ahora vamos a hacer un pequeño cambio, modificar la función de activación de la segunda capa a “selu”.
from tensorflow.keras import layers # Define Sequential model with 3 layers model = keras.Sequential( [ layers.Dense(4, activation="relu", name="layer1"), layers.Dense(16, activation="selu", name="layer2"), layers.Dense(1, name="layer3"), ] ) model.compile( loss='mse', # keras.losses.mean_squared_error optimizer=keras.optimizers.SGD(lr=0.04), )
Os invito ahora a visitar este enlace donde podéis ver ejemplos de código con regresión polinomial.
Podemos ver la función de activación selu en el siguiente enlace.
Y podemos comprobar con TensorBoard la pérdida, que es la mejor que hemos podido obtener hasta ahora.
Y esta es la adaptación de la curva a las rectas.
Con el nivel de conocimiento que tengo hasta ahora, es lo mejor que he podido conseguir. Tendremos que seguir investigando sobre los posibles parámetros a actuar y posibles soluciones a un mismo problema.
Os invito a tocar y proponerme mejoras sobre el modelo que es cómo se aprende.