TensorFlow, Keras y aprendizaje profundo, sin un doctorado

1. Descripción general

Se actualizó este instructivo para TensorFlow 2.2.

74f6fbd758bf19e6.png

En este codelab, aprenderás a compilar y entrenar una red neuronal que reconozca dígitos escritos a mano. En el camino, a medida que mejoras tu red neuronal para lograr una exactitud del 99%, también descubrirás las herramientas del oficio que usan los profesionales del aprendizaje profundo para entrenar sus modelos de manera eficiente.

En este codelab, se usa el conjunto de datos MNIST, una colección de 60,000 dígitos etiquetados que ha mantenido ocupadas a generaciones de doctorados durante casi dos décadas. Resolverás el problema con menos de 100 líneas de código de Python / TensorFlow.

Qué aprenderás

  • Qué es una red neuronal y cómo entrenarla
  • Cómo crear una red neuronal básica de 1 capa con tf.keras
  • Cómo agregar más capas
  • Cómo configurar un programa de tasa de aprendizaje
  • Cómo crear redes neuronales convolucionales
  • Cómo usar técnicas de regularización: abandonos y normalización por lotes
  • ¿Qué es el sobreajuste?

Requisitos

Solo un navegador. Este taller se puede llevar a cabo completamente con Google Colaboratory.

Comentarios

Infórmanos si notas algún error en este lab o si crees que deberíamos mejorar. Manejamos comentarios a través de problemas de GitHub [feedback link].

2. Guía de inicio rápido de Google Colaboratory

Este lab usa Google Colaboratory y no requiere que lo configures. Puedes ejecutarlo desde una Chromebook. Abre el archivo que aparece a continuación y ejecuta las celdas para familiarizarte con los notebooks de Colab.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Aquí encontrarás instrucciones adicionales:

Selecciona un backend de GPU

hsy7H7O5qJNvKcRnHRiZoyh0IznlzmrO60wR1B6pqtfdc8Ie7gLsXC0f670zsPzGsNy3QAJuZefYv9CwTHmjiMyywG2pTpnMCE1SmItf3KVQ1Bene

En el menú de Colab, selecciona Entorno de ejecución > Cambia el tipo de entorno de ejecución y, luego, selecciona GPU. La conexión al entorno de ejecución se realizará automáticamente en la primera ejecución, o bien puedes usar el botón “Conectar” en la esquina superior derecha.

Ejecución del notebook

evlBKSO15ImjocdEcsIo8unzEe6oDGYnKFe8CoHS_7QiP3sDbrs2jB6lbyitEtE7Gt_1UsCdU5dJA-_2IgBWh9ofYf4yVDE740PwJ6kiQwuXNOLkgktzzf0E_k5VN5mq29ZXI5wb7Q

Para ejecutar las celdas una a la vez, haz clic en una celda y usa Mayúsculas + Intro. También puedes ejecutar todo el notebook con Entorno de ejecución > Ejecutar todo

Índice

OXeYYbtKdLCNnw_xovSMeMwSdD7CL_w25EfhnpRhhhO44bYp3zZpU72J5tKaSuo8wpas0GK5B2sTBlIMiFmdGxFRQ9NmwJ7JIRYy5XtpWKQCPdxQVRPy_0J_LshGIKjtw8P9fXozaA

Todos los notebooks tienen un índice. Puedes abrirlo con la flecha negra que está a la izquierda.

Celdas ocultas

GXTbXUO8xpPFKiGc6Q-cFwFHxHvOa105hHg3vk77EDpStyhU4AQMN3FYenbiBusHXUSk-yGXbRDcK-Cwx18XbDtyqB5WRr3_2jhnLvFxW8a7H_4cGvVDKrEMto_QxhfTeO0hwmrfng

Algunas celdas solo mostrarán el título. Esta es una función de notebook específica de Colab. Puedes hacer doble clic en ellos para ver el código que contiene, pero, por lo general, no es muy interesante. Por lo general, las funciones de asistencia o visualización. Aún debes ejecutar estas celdas para que se definan las funciones que contiene.

3. Entrena una red neuronal

Primero, veremos el entrenamiento de una red neuronal. Abre el notebook que aparece a continuación y ejecuta todas las celdas. Todavía no le prestes atención al código, ya que lo explicaremos más adelante.

c3df49e90e5a654f.png keras_01_mnist.ipynb

Mientras ejecutas el notebook, enfócate en las visualizaciones. Consulte las explicaciones a continuación.

Datos de entrenamiento

Tenemos un conjunto de datos de dígitos escritos a mano que se etiquetaron para saber qué representa cada imagen, es decir, un número entre 0 y 9. En el notebook, verás un extracto:

ad83f98e56054737.png

La red neuronal que compilaremos clasifica los dígitos escritos a mano en sus 10 clases (0, .., 9). Lo hace según parámetros internos que deben tener un valor correcto para que la clasificación funcione bien. Este "valor correcto" se aprende a través de un proceso de entrenamiento que requiere un “conjunto de datos etiquetado” con imágenes y las respuestas correctas asociadas.

¿Cómo sabemos si la red neuronal entrenada funciona bien o no? Usar el conjunto de datos de entrenamiento para probar la red sería una trampa. Ya vio el conjunto de datos varias veces durante el entrenamiento y, sin dudas, le da un muy buen rendimiento. Necesitamos otro conjunto de datos etiquetado, que nunca se vio durante el entrenamiento, para evaluar el “mundo real” y el rendimiento de la red. Se denomina “conjunto de datos de validación”.

Capacitación

A medida que avanza el entrenamiento, de a un lote de datos de entrenamiento a la vez, se actualizan los parámetros del modelo interno y este mejora a la hora de reconocer los dígitos escritos a mano. Puedes verla en el grafo de entrenamiento:

3f7b405649301ea.png

A la derecha, la sección “exactitud” es simplemente el porcentaje de dígitos reconocidos correctamente. Aumenta a medida que el entrenamiento avanza, lo cual es bueno.

A la izquierda, podemos ver la “pérdida”. Para impulsar el entrenamiento, definiremos una “pérdida” función, que representa el grado en que el sistema reconoce los dígitos, e intenta minimizarlo. Lo que ves aquí es que la pérdida disminuye tanto en el entrenamiento como en los datos de validación a medida que el entrenamiento avanza: eso es bueno. Significa que la red neuronal está aprendiendo.

El eje X representa el número de "ciclos de entrenamiento" o iteraciones en todo el conjunto de datos.

Predicciones

Cuando se entrena el modelo, podemos usarlo para reconocer dígitos escritos a mano. La siguiente visualización muestra el rendimiento en unos pocos dígitos renderizados a partir de fuentes locales (primera línea) y, luego, en los 10,000 dígitos del conjunto de datos de validación. La clase prevista aparece debajo de cada dígito, en rojo si es incorrecta.

c0699216ba0effdb.png

Como puedes ver, este modelo inicial no es muy bueno, pero aún reconoce algunos dígitos correctamente. Su exactitud de validación final es de alrededor del 90%, lo que no es tan malo para el modelo simplista con el que empezamos, pero aún significa que le faltan 1,000 dígitos de validación de los 10,000. Eso es mucho más de lo que se puede mostrar, por lo que parece que todas las respuestas son incorrectas (en rojo).

Tensores

Los datos se almacenan en matrices. Una imagen en escala de grises de 28 x 28 píxeles encaja en una matriz bidimensional de 28 x 28. Sin embargo, para una imagen en color, necesitamos más dimensiones. Hay 3 valores de color por píxel (rojo, verde, azul), por lo que se necesitará una tabla tridimensional con las dimensiones [28, 28, 3]. Y para almacenar un lote de 128 imágenes de color, se necesita una tabla cuatridimensional con las dimensiones [128, 28, 28, 3].

Estas tablas multidimensionales se denominan “tensores” y la lista de sus dimensiones es su “forma”.

4. [INFO]: Introducción a las redes neuronales

En pocas palabras

Si ya conoces todos los términos en negrita del siguiente párrafo, puedes pasar al siguiente ejercicio. Si recién estás comenzando en el aprendizaje profundo, entonces bienvenido y sigue leyendo.

witch.png

Para los modelos creados como una secuencia de capas, Keras ofrece la API secuencial. Por ejemplo, un clasificador de imágenes que usa tres capas densas se puede escribir en Keras de la siguiente manera:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28, 1]),
    tf.keras.layers.Dense(200, activation="relu"),
    tf.keras.layers.Dense(60, activation="relu"),
    tf.keras.layers.Dense(10, activation='softmax') # classifying into 10 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

688858c21e3beff2.png

Una sola capa densa

Los dígitos escritos a mano en el conjunto de datos MNIST son imágenes en escala de grises de 28x28 píxeles. El enfoque más simple para clasificarlas es usar los píxeles de 28 × 28=784 píxeles como entradas para una red neuronal de 1 capa.

Captura de pantalla 26/07/2016 a las 12.32.24.png

Cada “neurona” de una red neuronal hace una suma ponderada de todas sus entradas y agrega una constante llamada “sesgo”. y, luego, ingresa el resultado a través de alguna "función de activación" no lineal. Los "pesos" y "sesgos" son parámetros que se determinarán a través del entrenamiento. Al principio, se inicializan con valores aleatorios.

En la imagen anterior, se representa una red neuronal de 1 capa con 10 neuronas de salida, ya que queremos clasificar dígitos en 10 clases (de 0 a 9).

Con una multiplicación de matrices

Así es como se puede representar una capa de red neuronal, que procesa una colección de imágenes, mediante una multiplicación de matrices:

matmul.gif

Usando la primera columna de ponderaciones en la matriz de ponderaciones W, calculamos la suma ponderada de todos los píxeles de la primera imagen. Esta suma corresponde a la primera neurona. Usando la segunda columna de pesos, hacemos lo mismo para la segunda neurona, y así sucesivamente hasta la neurona 10. Luego, podemos repetir la operación para las 99 imágenes restantes. Si llamamos X a la matriz que contiene nuestras 100 imágenes, todas las sumas ponderadas de las 10 neuronas, calculadas en 100 imágenes, son solo X.W, una multiplicación de matrices.

Cada neurona ahora debe agregar su sesgo (una constante). Como tenemos 10 neuronas, tenemos 10 constantes de sesgo. A este vector de 10 valores lo llamaremos b. Debe agregarse a cada línea de la matriz calculada previamente. Usar un poco de magia llamada "enviar" escribiremos esto con un simple signo más.

Por último, aplicamos una función de activación, por ejemplo, "softmax" (se explica a continuación) y obtén la fórmula que describe una red neuronal de 1 capa, aplicada a 100 imágenes:

Captura de pantalla 26/07/2016 a las 16.02.36.png

En Keras

Con bibliotecas de redes neuronales de alto nivel como Keras, no necesitaremos implementar esta fórmula. Sin embargo, es importante entender que una capa de red neuronal es solo un conjunto de multiplicaciones y sumas. En Keras, una capa densa se escribiría de la siguiente manera:

tf.keras.layers.Dense(10, activation='softmax')

Profundiza tus conocimientos

Es muy sencillo encadenar capas de redes neuronales. La primera capa calcula sumas ponderadas de píxeles. En las capas posteriores, se calculan sumas ponderadas de los resultados de las capas anteriores.

fba0638cc213a29.png

La única diferencia, además del número de neuronas, será la elección de la función de activación.

Funciones de activación: ReLU, softmax y sigmoide

Por lo general, usarías la “relu” de activación para todas las capas, excepto para la última. La última capa, en un clasificador, usaría “softmax”. activación.

644f4213a4ee70e5.png

Nuevamente, una "neurona" calcula una suma ponderada de todas sus entradas y agrega un valor llamado "sesgo" y envía el resultado a través de la función de activación.

La función de activación más popular se denomina "RELU" para la unidad lineal rectificada. Es una función muy simple, como puedes ver en el gráfico anterior.

La función de activación tradicional en las redes neuronales era la “sigmoidea”, pero la “relu” se demostró que tiene mejores propiedades de convergencia en casi todas partes y ahora se prefiere.

41fc82288c4aff5d.png

Activación de softmax para la clasificación

La última capa de nuestra red neuronal tiene 10 neuronas porque queremos clasificar los dígitos escritos a mano en 10 clases (0,..9). Debería generar 10 números entre 0 y 1 que representen la probabilidad de que este dígito sea un 0, un 1, un 2 y así sucesivamente. Para esto, en la última capa, usaremos una función de activación llamada "softmax".

La aplicación de softmax a un vector se realiza tomando la exponencial de cada elemento y luego normalizando el vector, generalmente dividiéndolo por su “L1” (es decir, la suma de valores absolutos) para que los valores normalizados sumen 1 y puedan interpretarse como probabilidades.

El resultado de la última capa, antes de la activación, a veces se denomina “logits”. Si este vector es L = [L0, L1, L2, L3, L4, L5, L6, L7, L8, L9], entonces:

ef0d98c0952c262d.png d51252f75894479e.gif

Pérdida de la entropía cruzada

Ahora que nuestra red neuronal produce predicciones a partir de imágenes de entrada, debemos medir qué tan buenas son, es decir, la distancia entre lo que nos dice la red y las respuestas correctas, a menudo llamadas “etiquetas”. Recuerda que tenemos etiquetas correctas para todas las imágenes del conjunto de datos.

Cualquier distancia funcionaría, pero para los problemas de clasificación la llamada "distancia de entropía cruzada" es el más efectivo. A esto lo llamaremos error o “pérdida”. función:

6dbba1bce3cadc36.png

Descenso de gradientes

“Capacitación” La red neuronal en realidad significa usar imágenes y etiquetas de entrenamiento para ajustar los pesos y los sesgos con el fin de minimizar la función de pérdida de la entropía cruzada. Funciona de la siguiente manera.

La entropía cruzada es una función de pesos, sesgos, píxeles de la imagen de entrenamiento y su clase conocida.

Si calculamos las derivadas parciales de la entropía cruzada con respecto a todos los pesos y todos los sesgos, obtenemos un “gradiente”, calculado para una determinada imagen, etiqueta y valor actual de pesos y sesgos. Recuerda que podemos tener millones de pesos y sesgos, por lo que calcular el gradiente suena como mucho trabajo. Por suerte, TensorFlow lo hace por nosotros. La propiedad matemática de un gradiente es que apunta hacia arriba. Como queremos ir donde la entropía cruzada es baja, vamos en la dirección opuesta. Actualizamos los pesos y los sesgos por una fracción del gradiente. Luego hacemos lo mismo una y otra vez con los siguientes lotes de imágenes y etiquetas de entrenamiento, en un bucle de entrenamiento. Se espera que esto converja en un lugar donde la entropía cruzada sea mínima, aunque nada garantiza que este mínimo sea único.

gradient descent2.png

Minilotes y impulso

Puedes calcular tu gradiente en una sola imagen de ejemplo y actualizar los pesos y sesgos de inmediato, pero hacerlo en un lote de, por ejemplo, 128 imágenes da como resultado un gradiente que representa mejor las restricciones impuestas por las diferentes imágenes de ejemplo y, por lo tanto, es probable que converja hacia la solución más rápido. El tamaño del minilote es un parámetro ajustable.

Esta técnica, a veces llamada "descenso de gradientes estocástico" tiene otra ventaja más pragmática: trabajar con lotes también significa trabajar con matrices más grandes y, por lo general, estas son más fáciles de optimizar en GPU y TPU.

Sin embargo, la convergencia puede ser un poco caótica y hasta puede detenerse si el vector de gradiente es solo ceros. ¿Eso significa que encontramos un mínimo? No en todos los casos. Un componente de gradiente puede ser cero en un mínimo o máximo. En un vector gradiente con millones de elementos, si todos son ceros, la probabilidad de que cada cero corresponda a un mínimo y ninguno de ellos a un punto máximo es bastante pequeña. En un espacio de muchas dimensiones, los puntos de montaje son bastante comunes y no queremos detenernos en ellos.

cc544924671fa208.png

Ilustración: una silla de montar. El gradiente es 0, pero no es un mínimo en todas las direcciones. (Atribución de imagen Wikimedia: de Nicoguaro - trabajo propio, CC BY 3.0)

La solución es agregar algo de impulso al algoritmo de optimización para que pueda navegar más allá de la silla de montar sin detenerse.

Glosario

lote o minilote: el entrenamiento siempre se realiza en lotes de datos de entrenamiento y etiquetas. Eso ayuda a que el algoritmo converja. El "lote" suele ser la primera de los tensores de datos. Por ejemplo, un tensor de forma [100, 192, 192, 3] contiene 100 imágenes de 192 x 192 píxeles con tres valores por píxel (RGB).

pérdida de entropía cruzada: Es una función de pérdida especial que se usa con frecuencia en los clasificadores.

Capa densa: Es una capa de neuronas en la que cada neurona está conectada a todas las neuronas en la capa anterior.

atributos: A veces, las entradas de una red neuronal se denominan "atributos". El arte de descubrir qué partes de un conjunto de datos (o combinaciones de partes) alimentar a una red neuronal para obtener buenas predicciones se denomina “ingeniería de atributos”.

labels: otro nombre para las "clases" o respuestas correctas en un problema de clasificación supervisado

tasa de aprendizaje: fracción del gradiente por la cual se actualizan los pesos y los sesgos en cada iteración del ciclo de entrenamiento

logits: las salidas de una capa de neuronas antes de que se aplique la función de activación se denominan “logits”. El término proviene de la "función logística" también conocida como la "función sigmoidea" que solía ser la función de activación más popular. "Resultados de neuronas antes de la función logística" se acortó a “logits”.

pérdida: la función de error que compara los resultados de la red neuronal con las respuestas correctas

neuron: Calcula la suma ponderada de sus entradas, agrega un sesgo y alimenta el resultado a través de una función de activación.

Codificación one-hot: La clase 3 de 5 se codifica como un vector de 5 elementos, todos ceros, excepto el tercero, que es 1.

relu: unidad lineal rectificada. Es una función de activación popular para las neuronas.

sigmoidea: Es otra función de activación que solía ser popular y que sigue siendo útil en casos especiales.

softmax: Es una función de activación especial que actúa sobre un vector, aumenta la diferencia entre el componente más grande y todos los demás, y también normaliza el vector para tener una suma de 1 de modo que pueda interpretarse como un vector de probabilidades. Se usa como el último paso en los clasificadores.

tensor: Un “tensor” es como una matriz, pero con un número arbitrario de dimensiones. Un tensor unidimensional es un vector. Un tensor de 2 dimensiones es una matriz. Y, luego, puedes tener tensores con 3, 4, 5 o más dimensiones.

5. Pasemos al código

Volvamos al notebook de estudio y, esta vez, leamos el código.

c3df49e90e5a654f.png keras_01_mnist.ipynb

Revisemos toda la celda en este notebook.

Celda "Parameters"

El tamaño del lote, la cantidad de ciclos de entrenamiento y la ubicación de los archivos de datos se definen aquí. Los archivos de datos se alojan en un bucket de Google Cloud Storage (GCS), por lo que la dirección comienza con gs://.

Celda "Importaciones"

Aquí se importan todas las bibliotecas de Python necesarias, incluidas TensorFlow y matplotlib para las visualizaciones.

Celda “Utilidades de visualización [RUN ME]****”

Esta celda contiene un código de visualización poco interesante. Está contraído de forma predeterminada, pero puedes abrirlo y ver el código cuando tengas tiempo si haces doble clic en él.

Celda "tf.data.Dataset: analyze files and prepare training and valid datasets"

Esta celda usó la API de tf.data.Dataset para cargar el conjunto de datos MNIST de los archivos de datos. No es necesario pasar demasiado tiempo en esta celda. Si te interesa la API de tf.data.Dataset, este es un instructivo que la explica: Canalizaciones de datos TPU-speed. Por ahora, estos son los conceptos básicos:

Las imágenes y etiquetas (respuestas correctas) del conjunto de datos MNIST se almacenan en registros de longitud fija en 4 archivos. Los archivos se pueden cargar con la función de registro fija dedicada:

imagedataset = tf.data.FixedLengthRecordDataset(image_filename, 28*28, header_bytes=16)

Ahora tenemos un conjunto de datos de bytes de imágenes. Se deben decodificar en imágenes. Definimos una función para hacerlo. La imagen no está comprimida, por lo que la función no necesita decodificar nada (decode_raw básicamente no hace nada). Luego, la imagen se convierte en valores de punto flotante entre 0 y 1. Podríamos cambiar su forma aquí como una imagen en 2D, pero, en realidad, lo mantenemos como un array plano de píxeles con un tamaño de 28 × 28, ya que es lo que espera nuestra capa densa inicial.

def read_image(tf_bytestring):
    image = tf.io.decode_raw(tf_bytestring, tf.uint8)
    image = tf.cast(image, tf.float32)/256.0
    image = tf.reshape(image, [28*28])
    return image

Aplicamos esta función al conjunto de datos con .map y obtenemos un conjunto de datos de imágenes:

imagedataset = imagedataset.map(read_image, num_parallel_calls=16)

Hacemos el mismo tipo de lectura y decodificación para etiquetas, y .zip las imágenes y las etiquetas juntas:

dataset = tf.data.Dataset.zip((imagedataset, labelsdataset))

Ahora tenemos un conjunto de datos de pares (imagen, etiqueta). Esto es lo que espera nuestro modelo. Aún no estamos listos para usarlo en la función de entrenamiento:

dataset = dataset.cache()
dataset = dataset.shuffle(5000, reshuffle_each_iteration=True)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

La API de tf.data.Dataset tiene todas las funciones de utilidad necesarias para preparar conjuntos de datos:

.cache almacena en caché el conjunto de datos en la RAM. Este es un conjunto de datos pequeño, por lo que funcionará. .shuffle lo redistribuye con un búfer de 5,000 elementos. Es importante que los datos de entrenamiento estén bien mezclados. .repeat hace un bucle en el conjunto de datos. Lo entrenaremos varias veces (varios ciclos de entrenamiento). .batch extrae varias imágenes y etiquetas en un minilote. Por último, .prefetch puede usar la CPU para preparar el siguiente lote mientras se entrena el lote actual en la GPU.

El conjunto de datos de validación se prepara de manera similar. Ya estamos listos para definir un modelo y usar este conjunto de datos para entrenarlo.

Celda “Modelo de Keras”

Todos nuestros modelos serán secuencias rectas de capas, por lo que podemos usar el estilo tf.keras.Sequential para crearlos. Al principio, es una sola capa densa. Tiene 10 neuronas porque clasificaremos dígitos escritos a mano en 10 clases. Utiliza "softmax" activación porque es la última capa de un clasificador.

Un modelo de Keras también necesita conocer la forma de sus entradas. Se puede usar tf.keras.layers.Input para definirlo. Aquí, los vectores de entrada son vectores planos de valores de píxeles con una longitud de 28*28.

model = tf.keras.Sequential(
  [
    tf.keras.layers.Input(shape=(28*28,)),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model.compile(optimizer='sgd',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# print model layers
model.summary()

# utility callback that displays training curves
plot_training = PlotTraining(sample_rate=10, zoom=1)

La configuración del modelo se realiza en Keras con la función model.compile. Aquí usamos el optimizador básico 'sgd' (descenso de gradientes estocástico). Un modelo de clasificación requiere una función de pérdida de entropía cruzada, llamada 'categorical_crossentropy' en Keras. Por último, le pedimos al modelo que calcule la métrica 'accuracy', que es el porcentaje de imágenes clasificadas de forma correcta.

Keras ofrece la excelente utilidad model.summary(), que imprime los detalles del modelo que creaste. El tipo de instructor agregó la utilidad PlotTraining (definida en la celda “utilidades de visualización”), que mostrará varias curvas de entrenamiento durante la capacitación.

Celda “Entrenar y validar el modelo”

Aquí es donde se realiza el entrenamiento, cuando se llama a model.fit y se pasan los conjuntos de datos de entrenamiento y validación. De forma predeterminada, Keras ejecuta una ronda de validación al final de cada ciclo de entrenamiento.

model.fit(training_dataset, steps_per_epoch=steps_per_epoch, epochs=EPOCHS,
          validation_data=validation_dataset, validation_steps=1,
          callbacks=[plot_training])

En Keras, es posible agregar comportamientos personalizados durante el entrenamiento con devoluciones de llamada. Así es como se implementó el diagrama de entrenamiento que se actualiza de forma dinámica para este taller.

Celda “Visualizar predicciones”

Una vez que el modelo está entrenado, podemos obtener predicciones con él llamando a model.predict():

probabilities = model.predict(font_digits, steps=1)
predicted_labels = np.argmax(probabilities, axis=1)

Aquí preparamos un conjunto de dígitos impresos renderizados a partir de fuentes locales, a modo de prueba. Recuerda que la red neuronal muestra un vector de 10 probabilidades de su “softmax” final. Para obtener la etiqueta, debemos averiguar qué probabilidad es la más alta. np.argmax de la biblioteca de NumPy lo hace.

Para comprender por qué es necesario el parámetro axis=1, recuerda que procesamos un lote de 128 imágenes y, por lo tanto, el modelo muestra 128 vectores de probabilidades. La forma del tensor de salida es [128, 10]. Calculamos el argmax en las 10 probabilidades que se muestran para cada imagen, por lo tanto, es axis=1 (el primer eje es 0).

Este modelo simple ya reconoce el 90% de los dígitos. No está mal, pero ahora lo mejorarás de forma significativa.

396c54ef66fad27f.png

6. Cómo agregar capas

godeep.png

Para mejorar la precisión del reconocimiento, agregaremos más capas a la red neuronal.

Captura de pantalla 27/07/2016 a las 15.36.55.png

Mantenemos softmax como la función de activación en la última capa porque eso es lo que funciona mejor para la clasificación. Sin embargo, en las capas intermedias, utilizaremos la función de activación más clásica: la sigmoidea:

41fc82288c4aff5d.png

Por ejemplo, tu modelo podría verse así (no olvides las comas, tf.keras.Sequential toma una lista de capas separadas por comas):

model = tf.keras.Sequential(
  [
      tf.keras.layers.Input(shape=(28*28,)),
      tf.keras.layers.Dense(200, activation='sigmoid'),
      tf.keras.layers.Dense(60, activation='sigmoid'),
      tf.keras.layers.Dense(10, activation='softmax')
  ])

Mira el "resumen" de tu modelo. Ahora tiene al menos 10 veces más parámetros. Debería ser 10 veces mejor. Pero, por alguna razón, no es ...

5236f91ba6e07d85.png

Al parecer, la pérdida también se hizo por completo. Se produjo un error.

7. Cuidado especial para redes profundas

Acabas de experimentar las redes neuronales, tal como las personas solían diseñarlas en los años 80 y 90. No es de extrañar que hayan abandonado la idea y dieron lugar al llamado "invierno de la IA". De hecho, a medida que se agregan capas, las redes neuronales tienen cada vez más dificultades para converger.

Resulta que las redes neuronales profundas con muchas capas (20, 50, incluso 100 hoy en día) pueden funcionar muy bien, ya que ofrecen un par de trucos matemáticos sucios para hacer que converjan. El descubrimiento de estos trucos sencillos es una de las razones del renacimiento del aprendizaje profundo en la década de 2010.

Activación de RELU

relu.png

En realidad, la función de activación sigmoidea es bastante problemática en las redes profundas. Comprime todos los valores entre 0 y 1 y, cuando lo haces repetidamente, las salidas de neuronas y sus gradientes pueden desaparecer por completo. Se mencionó por razones históricas, pero las redes modernas usan la unidad lineal rectificada (RELU), que tiene el siguiente aspecto:

1abce89f7143a69c.png

Por otro lado, el relu tiene una derivada de 1, al menos en su lado derecho. Con la activación de RELU, incluso si los gradientes provenientes de algunas neuronas pueden ser cero, siempre habrá otros que darán un gradiente claro distinto de cero y el entrenamiento puede continuar a un buen ritmo.

Un mejor optimizador

En espacios de dimensiones muy altas como este (tenemos del orden de 10,000 pesos y sesgos), “puntos de montaje” son frecuentes. Estos son puntos que no son mínimos locales, pero en los que el gradiente es cero y el optimizador de descenso de gradientes permanece allí. TensorFlow cuenta con una amplia variedad de optimizadores disponibles, incluidos algunos que trabajan con cierta inercia y navegan de manera segura más allá de los puntos de control.

Inicializaciones aleatorias

El arte de inicializar los sesgos de peso antes del entrenamiento es un área de investigación en sí misma, con numerosos artículos publicados sobre el tema. Puedes ver todos los inicializadores disponibles en Keras aquí. Por suerte, Keras hace lo correcto de forma predeterminada y usa el inicializador 'glorot_uniform', que es lo mejor en casi todos los casos.

No debes hacer nada por ti, dado que Keras ya hace lo correcto.

¿¿¿¿¿???

La fórmula de entropía cruzada implica un logaritmo y el registro(0) no es un número (NaN, una falla numérica, si así lo prefieres). ¿La entropía cruzada puede ser 0? La entrada proviene de softmax, que es esencialmente un exponencial y un exponencial nunca es cero. Así que estamos a salvo.

¿En serio? En el hermoso mundo de las matemáticas, estaríamos a salvo, pero en el mundo informático, exp(-150), representado en formato float32, es tan CERO como se obtiene y la entropía cruzada falla.

Por suerte, no tienes que hacer nada aquí, ya que Keras se encarga de esto y calcula softmax seguido de la entropía cruzada de una manera especialmente cuidadosa para garantizar la estabilidad numérica y evitar los temibles NaN.

¿Listo?

e1521c9dd936d9bc.png

Ahora deberías obtener una exactitud del 97%. El objetivo de este taller es superar significativamente el 99%, así que sigamos adelante.

Si no puedes avanzar, la solución es la siguiente:

c3df49e90e5a654f.png keras_02_mnist_dense.ipynb

8. Disminución de la tasa de aprendizaje

¿Quizá podemos intentar entrenar más rápido? La tasa de aprendizaje predeterminada en el optimizador Adam es 0.001. Vamos a tratar de aumentarlo.

Ir más rápido no parece ayudar mucho. ¿A qué se debe tanto ruido?

d4fd66346d7c480e.png

Las curvas de entrenamiento son muy ruidosas y se consideran ambas curvas de validación: están saltando hacia arriba y hacia abajo. Esto significa que estamos avanzando demasiado rápido. Podríamos volver a la velocidad anterior, pero hay una mejor manera.

lento.png

La buena solución es comenzar rápido y disminuir la tasa de aprendizaje de forma exponencial. En Keras, puedes hacer esto con la devolución de llamada tf.keras.callbacks.LearningRateScheduler.

Código útil para copiar y pegar:

# lr decay function
def lr_decay(epoch):
  return 0.01 * math.pow(0.6, epoch)

# lr schedule callback
lr_decay_callback = tf.keras.callbacks.LearningRateScheduler(lr_decay, verbose=True)

# important to see what you are doing
plot_learning_rate(lr_decay, EPOCHS)

No olvides usar el lr_decay_callback que creaste. Agrégala a la lista de devoluciones de llamada en model.fit:

model.fit(...,  callbacks=[plot_training, lr_decay_callback])

El impacto de este pequeño cambio es espectacular. Verás que la mayor parte del ruido desapareció y que la precisión de la prueba ahora supera el 98% de forma sostenida.

8c1ae90976c4a0c1.png

9. Retirados, sobreajuste

El modelo parece convertir bien ahora. Profundicemos aún más.

¿Te resulta útil?

e36c09a3088104c6.png

En realidad, no. La exactitud sigue estanca en un 98% y observa la pérdida de validación. ¡Está subiendo! El algoritmo de aprendizaje solo trabaja con datos de entrenamiento y optimiza la pérdida de entrenamiento en consecuencia. Nunca ve los datos de validación, por lo que no es de extrañar que, después de un tiempo, su trabajo ya no tenga ningún efecto en la pérdida de validación que deja de perderse y, a veces, hasta rebota.

Esto no afecta de inmediato las capacidades de reconocimiento reales de tu modelo, pero te impedirá ejecutar muchas iteraciones y, por lo general, es una señal de que el entrenamiento ya no tiene un efecto positivo.

dropout.png

Esta desconexión suele denominarse "sobreajuste" y, cuando la veas, puedes intentar aplicar una técnica de regularización llamada "dropout". La técnica de retirado dispara neuronas aleatorias en cada iteración de entrenamiento.

¿Funcionó?

43fd33801264743f.png

Vuelve a aparecer el ruido (como es de esperarse dada cómo funcionan los retirados). La pérdida de validación parece no ser sigilosa, pero en general es mayor que sin abandono. Y la precisión de la validación disminuyó un poco. Este resultado es bastante decepcionante.

Parece que el abandono no es la solución correcta o que tal vez se “sobreajuste” es un concepto más complejo, y algunas de sus causas no son susceptibles a un "abandono" corregir?

¿Qué es el "sobreajuste"? El sobreajuste ocurre cuando una red neuronal aprende "mal" de una manera que funciona para los ejemplos de entrenamiento, pero no tan bien con los datos del mundo real. Existen técnicas de regularización, como el abandono, que pueden obligarlo a aprender de una mejor manera, pero el sobreajuste también tiene raíces más profundas.

overfitting.png

El sobreajuste básico ocurre cuando una red neuronal tiene demasiados grados de libertad para el problema en cuestión. Imagina que tenemos tantas neuronas que la red puede almacenar en ellas todas nuestras imágenes de entrenamiento y, luego, reconocerlas por coincidencia de patrones. Fallaría por completo en los datos del mundo real. Una red neuronal debe estar de alguna manera limitada para que se vea obligada a generalizar lo que aprende durante el entrenamiento.

Si tienes muy pocos datos de entrenamiento, incluso una red pequeña puede aprenderlos de memoria, y verás “sobreajuste”. En términos generales, siempre se necesitan muchos datos para entrenar las redes neuronales.

Por último, si realizaste todas las acciones indicadas en el libro, experimentaste con distintos tamaños de red para asegurarte de que los grados de libertad estén limitados, se apliquen abandonos y se entrenó con muchos datos, es posible que aún estés atascado en un nivel de rendimiento que no parezca poder mejorar. Esto significa que tu red neuronal, en su forma actual, no puede extraer más información de tus datos, como en este caso.

¿Recuerdas que usamos nuestras imágenes aplanadas en un solo vector? Fue una muy mala idea. Los dígitos escritos a mano están hechos de formas, y descartamos la información correspondiente cuando aplanamos los píxeles. Sin embargo, existe un tipo de red neuronal que puede aprovechar la información de la forma: las redes convolucionales. Vamos a probarlos.

Si no puedes avanzar, la solución es la siguiente:

c3df49e90e5a654f.png keras_03_mnist_dense_lrdecay_dropout.ipynb

10. [INFO] redes convolucionales

En pocas palabras

Si ya conoces todos los términos en negrita del siguiente párrafo, puedes pasar al siguiente ejercicio. Si recién estás comenzando con redes neuronales convolucionales, continúa leyendo.

convolutional.gif

Ilustración: Filtrado de una imagen con dos filtros sucesivos compuestos por pesos aprendebles de 4x4x3=48 cada uno.

Así es como se ve una red neuronal convolucional simple en Keras:

model = tf.keras.Sequential([
    tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(kernel_size=3, filters=12, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=24, strides=2, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=32, strides=2, activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

688858c21e3beff2.png

En una capa de una red convolucional, una "neurona" realiza una suma ponderada de los píxeles que se encuentran sobre ella, solo en una región pequeña de la imagen. Agrega un sesgo y alimenta la suma a través de una función de activación, al igual que lo haría una neurona en una capa densa regular. Esta operación se repite en toda la imagen con los mismos pesos. Recuerda que en las capas densas, cada neurona tenía sus propios pesos. Aquí, un solo "parche" de pesos se desliza por la imagen en ambas direcciones (una "convolución"). La salida tiene tantos valores como píxeles en la imagen (aunque se necesita algo de relleno en los bordes). Es una operación de filtrado. En la ilustración anterior, utiliza un filtro de pesos 4x4x3=48.

Sin embargo, 48 pesos no serán suficientes. Para agregar más grados de libertad, repetimos la misma operación con un nuevo conjunto de pesos. Esto produce un nuevo conjunto de resultados de filtro. Llamémosla un "canal". de salidas por analogía con los canales R, G y B en la imagen de entrada.

Captura de pantalla 29/07/2016 a las 16.02.37.png

Los dos (o más) conjuntos de pesos se pueden sumar como un tensor agregando una dimensión nueva. Esto nos da la forma genérica del tensor de pesos de una capa convolucional. Dado que la cantidad de canales de entrada y salida son parámetros, podemos comenzar a apilar y encadenar capas convolucionales.

d1b557707bcd1cb9.png

Ilustración: una red neuronal convolucional transforma "cubos" de datos en otros "cubos" de datos.

Convoluciones zancadas, reducción máxima

Si realizamos las convoluciones con un segmento de 2 o 3, también podemos reducir el cubo de datos resultante en sus dimensiones horizontales. Existen dos formas comunes de hacerlo:

  • Convolución de zancadas: un filtro que se desliza como el anterior, pero con un segmento superior a 1
  • Reducción máxima: una ventana deslizante que aplica la operación MAX (generalmente en parches de 2 x 2, que se repite cada 2 píxeles)

2b2d4263bb8470b.gif

Ilustración: Deslizar la ventana de procesamiento 3 píxeles da como resultado menos valores de salida. Las convoluciones zancadas o la reducción máxima (máx. en una ventana de 2 × 2 con deslizamiento de 2 zancadas) son una forma de reducir el cubo de datos en las dimensiones horizontales.

La capa final

Después de la última capa convolucional, los datos tienen la forma de un "cubo". Hay dos formas de alimentarlo a través de la capa densa final.

La primera es aplanar el cubo de datos en un vector y luego incorporarlo a la capa de softmax. Algunas veces, incluso puedes agregar una capa densa antes de la capa de softmax. Esto suele ser costoso en términos de la cantidad de pesos. Una capa densa al final de una red convolucional puede contener más de la mitad de los pesos de toda la red neuronal.

En lugar de usar una costosa capa densa, también podemos dividir el “cubo” de datos entrante en tantas partes como tenemos las clases, promediar sus valores y alimentarlos mediante una función de activación de softmax. Esta forma de compilar el cabezal de clasificación cuesta 0 pesos. En Keras, hay una capa para esto: tf.keras.layers.GlobalAveragePooling2D().

a44aa392c7b0e32a.png

Pasa a la siguiente sección para construir una red convolucional para el problema en cuestión.

11. Una red convolucional

Creemos una red convolucional para el reconocimiento de dígitos de escritura a mano. Usaremos tres capas convolucionales en la parte superior, nuestra capa de lectura de softmax tradicional en la parte inferior y las conectaremos con una capa completamente conectada:

e1a214a170957da1.png

Observa que la segunda y la tercera capa convolucional tienen un segmento de dos, lo que explica por qué reducen la cantidad de valores de salida de 28 × 28 a 14 × 14 y, luego, a 7 × 7.

Escribamos el código de Keras.

Se requiere especial atención antes de la primera capa convolucional. De hecho, espera un "cubo" 3D de datos, pero nuestro conjunto de datos se configuró hasta ahora para capas densas y todos los píxeles de las imágenes se acoplan en un vector. Debemos volver a cambiar su forma a imágenes de 28 x 28 x 1 (1 canal para las imágenes en escala de grises):

tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1))

Puedes usar esta línea en lugar de la capa tf.keras.layers.Input que tenías hasta ahora.

En Keras, la sintaxis para una capa convolucional activada por “relu” es la siguiente:

140f80336b0e653b.png

tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu')

En el caso de una convolución zancada, deberías escribir lo siguiente:

tf.keras.layers.Conv2D(kernel_size=6, filters=24, padding='same', activation='relu', strides=2)

Aplanar un cubo de datos en un vector de modo que pueda ser consumido por una capa densa:

tf.keras.layers.Flatten()

Y para las capas densas, la sintaxis no cambió:

tf.keras.layers.Dense(200, activation='relu')

¿Tu modelo rompió la barrera de exactitud del 99%? Bastante cerca... pero mira la curva de pérdida de validación. ¿Te suena esto?

ecc5972814885226.png

Observa también las predicciones. Por primera vez, deberías ver que la mayoría de los 10,000 dígitos de prueba ahora se reconocen correctamente. Solo quedan aproximadamente 41⁄2 filas de detección errónea (alrededor de 110 dígitos de 10,000)

37e4cbd3f390c89e.png

Si no puedes avanzar, la solución es la siguiente:

c3df49e90e5a654f.png keras_04_mnist_convolutional.ipynb

12. Volver a abandonar

El entrenamiento anterior muestra signos claros de sobreajuste (y aún no llega al 99% de exactitud). ¿Deberíamos volver a intentar el abandono?

¿Cómo te fue esta vez?

63e0cc982cee2030.png

Al parecer, esta vez el abandono funcionó. La pérdida de validación ya no aumenta y la exactitud final debería estar muy por encima del 99%. ¡Felicitaciones!

La primera vez que intentamos aplicar los retirados, pensamos que teníamos un problema de sobreajuste cuando, de hecho, estaba en la arquitectura de la red neuronal. No podríamos ir más allá sin capas convolucionales y no hay nada que los retirados puedan hacer al respecto.

Esta vez, parece que el sobreajuste fue la causa del problema y el abandono realmente ayudó. Recuerda que hay muchos factores que pueden causar una desconexión entre las curvas de pérdida de entrenamiento y validación, con un aumento gradual de la pérdida de validación. El sobreajuste (demasiados grados de libertad, que la red usa mal) es solo una de ellas. Si tu conjunto de datos es demasiado pequeño o la arquitectura de tu red neuronal no es adecuada, es posible que veas un comportamiento similar en las curvas de pérdida, pero el abandono no te ayudará.

13. Normalización por lotes

oggEbikl2I6_sOo7FlaX2KLdNeaYhJnVSS8GyG8FHXid75PVJX73CRiOynwpMZpLZq6_xAy69wgyez5T-ZlpuC2XSlcmjk7oVcOzefKKTFhTEoLO3kljz2RDyKcaFtHvtTey-I4VpQ

Por último, intentemos agregar la normalización por lotes.

Esa es la teoría. En la práctica, solo recuerda algunas reglas:

Por ahora, juguemos por el libro y agreguemos una capa de norma por lotes a cada capa de red neuronal, excepto a la última. No lo agregues al último "softmax". por la capa de prealimentación. No sería útil allí.

# Modify each layer: remove the activation from the layer itself.
# Set use_bias=False since batch norm will play the role of biases.
tf.keras.layers.Conv2D(..., use_bias=False),
# Batch norm goes between the layer and its activation.
# The scale factor can be turned off for Relu activation.
tf.keras.layers.BatchNormalization(scale=False, center=True),
# Finish with the activation.
tf.keras.layers.Activation('relu'),

¿Qué te parece la exactitud en este momento?

ea48193334c565a1.png

Con un poco de ajuste (BATCH_SIZE=64, parámetro de disminución de la tasa de aprendizaje 0.666, tasa de abandono en la capa densa 0.3) y un poco de suerte, puedes llegar al 99.5%. La tasa de aprendizaje y los ajustes de abandono se realizaron de acuerdo con las “prácticas recomendadas”. para usar la norma por lotes:

  • La norma por lotes ayuda a la convergencia de las redes neuronales y, por lo general, permite realizar un entrenamiento más rápido.
  • La norma por lotes es un regularizador. Por lo general, puedes disminuir la cantidad de abandonos que usas o incluso no usarlos en absoluto.

El notebook de la solución tiene una ejecución de entrenamiento del 99.5%:

c3df49e90e5a654f.png keras_05_mnist_batch_norm.ipynb

14. Entrena en la nube con hardware potente: AI Platform

d7d0282e687bdad8.png

Encontrarás una versión del código lista para la nube en la carpeta MLengine en GitHub, junto con instrucciones para ejecutarlo en AI Platform de Google Cloud. Antes de ejecutar esta parte, deberás crear una cuenta de Google Cloud y habilitar la facturación. Los recursos necesarios para completar el lab deberían costar menos de un par de dólares (suponiendo 1 hora de tiempo de entrenamiento en una GPU). Para preparar tu cuenta, sigue estos pasos:

  1. Crea un proyecto de Google Cloud Platform ( http://cloud.google.com/console).
  2. Habilita la facturación.
  3. Instala las herramientas de línea de comandos de GCP ( consulta el SDK de GCP aquí).
  4. Crea un bucket de Google Cloud Storage (coloca la región us-central1). Se usará para habilitar a etapa el código de entrenamiento y almacenar el modelo entrenado.
  5. Habilita las APIs necesarias y solicita las cuotas correspondientes (ejecuta el comando de entrenamiento una vez y deberías recibir mensajes de error que te indican qué habilitar).

15. ¡Felicitaciones!

Creaste tu primera red neuronal y la entrenaste con una exactitud del 99%. Las técnicas aprendidas en el proceso no son específicas del conjunto de datos MNIST; en realidad, son muy usadas cuando se trabaja con redes neuronales. Como regalo de despedida, aquí están las "notas del acantilado" tarjeta del lab, en versión animada. Puedes usarlo para recordar lo que aprendiste:

acantilados notes tensorflow lab.png

Próximos pasos

  • Una vez que haya redes completamente conectadas y convolucionales, deberías consultar las redes neuronales recurrentes.
  • Para ejecutar tu entrenamiento o inferencia en la nube en una infraestructura distribuida, Google Cloud ofrece AI Platform.
  • Por último, nos encanta recibir comentarios. Infórmanos si notas algún error en este lab o si crees que deberíamos mejorar. Manejamos comentarios a través de problemas de GitHub [feedback link].

HR.png

ID de Martin Görner small.jpgAutor: Martin GörnerTwitter: @martin_gorner

Derechos de autor de todas las imágenes de dibujos animados en este lab: alexpokusay / 123RF fotos de archivo