TensorFlow, Keras y aprendizaje profundo, sin tener un doctorado

En este codelab, aprenderás a crear y entrenar una red neuronal que reconoce dígitos escritos a mano. A medida que mejore su red neuronal para lograr un 99% de precisión, también descubrirá las herramientas del oficio que usan los profesionales del aprendizaje profundo para entrenar sus modelos de manera eficiente.

Este codelab usa el conjunto de datos MNIST, una colección de 60,000 dígitos etiquetados que ha mantenido generaciones de PhD durante casi dos décadas. Solucionarás el problema con menos de 100 líneas de código de Python o TensorFlow.

Qué aprenderás

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

Requisitos

Solo un navegador. Este taller se puede realizar enteramente con Google Colaboratory.

Comentarios

Indíquenos si observa algún error en este lab o si considera que debería mejorarse. Nos encargamos de los comentarios a través de los problemas de GitHub [vínculo de comentarios].

Este lab utiliza Google Colaboratory y no requiere configuración de su parte. Puedes ejecutarlo desde una Chromebook. Abre el archivo que aparece debajo y ejecuta las celdas para familiarizarte con los notebooks de Colab.

Welcome to Colab.ipynb

Instrucciones adicionales a continuación:

Selecciona un backend de GPU

En el menú de Colab, selecciona Runtime > Change runtime type y, luego, selecciona GPU. La primera ejecución de la conexión al entorno de ejecución se puede llevar a cabo de forma automática. También puedes usar el botón “Conectar” en la esquina superior derecha.

Ejecución del notebook

Para ejecutar celdas de a una por vez, haz clic en una celda y usa Mayúsculas-Intro. También puede ejecutar todo el notebook con Runtime > Run all

Índice

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

Celdas ocultas

Algunas celdas solo muestran el título. Esta es una función de notebook específica de Colab. Puedes hacer doble clic en ellas para ver el código en el interior, pero no suele ser muy interesante. Por lo general, son compatibles con funciones de visualización. Aún debe ejecutar estas celdas para que se definan las funciones internas.

Primero, veremos un tren de redes neuronales. Abra el notebook a continuación y ejecute todas las celdas. Aún no prestes atención al código, comenzaremos a explicarlo más adelante.

keras_01_mnist.ipynb

Mientras ejecutas el notebook, enfócate en las visualizaciones. A continuación, se explican las explicaciones.

Datos de entrenamiento

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

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

¿Cómo podemos saber si la red neuronal entrenada tiene un buen rendimiento o no? El uso del conjunto de datos de entrenamiento para probar la red sería una trampa. Ya vio ese conjunto de datos varias veces durante el entrenamiento y, sin dudas, tiene un muy buen rendimiento en él. Necesitamos otro conjunto de datos etiquetado, que nunca se haya visto durante el entrenamiento, para evaluar el rendimiento del mundo real. Se denomina conjunto de datos de validación"

Capacitación

A medida que el entrenamiento avanza, de a un lote de datos de entrenamiento a la vez, se actualizan los parámetros del modelo interno y el modelo mejora cada vez más en el reconocimiento de los dígitos escritos a mano. Puede verlo en el gráfico de entrenamiento:

A la derecha, la "exactitud" es simplemente el porcentaje de dígitos reconocidos correctamente. Asciende a medida que avanza el entrenamiento, lo cual es bueno.

A la izquierda, podemos ver la &st;;pérdida. Para controlar el entrenamiento, definiremos una función & pérdida, que representa hasta qué punto el sistema reconoce los dígitos y trata de minimizarlo. Lo que ve aquí es que la pérdida disminuye en los datos de entrenamiento y de validación a medida que avanza el entrenamiento: eso es bueno. Significa que la red neuronal está aprendiendo.

El eje X representa la cantidad 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. En la siguiente visualización, se muestra el rendimiento de algunos dígitos renderizados de fuentes locales (primera línea) y, luego, de 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.

Como puedes ver, este modelo inicial no es muy bueno, pero reconoce algunos dígitos correctamente. Su precisión final de validación es de alrededor del 90%, lo que no es tan malo para el modelo simple con el que comenzamos, pero aun así significa que pierde 1,000 dígitos de validación de los 10,000. Se trata de una cantidad mucho mayor de información que se puede mostrar, por lo que parece que todas las respuestas son incorrectas (en rojo).

Tensors

Los datos se almacenan en matrices. Una imagen en escala de grises de 28 × 28 píxeles se ajusta a una matriz bidimensional de 28 × 28. Pero para una imagen en color, necesitamos más dimensiones. Existen 3 valores de color por píxel (rojo, verde, azul), por lo que se necesitará una tabla tridimensional con dimensiones [28, 28, 3]. Para almacenar un lote de 128 imágenes en color, se necesita una tabla cuadidimensional con las dimensiones [128, 28, 28, 3].

Estas tablas multidimensionales se denominan "tensors y la lista de sus dimensiones es su "shape".

En pocas palabras

Si ya conoces todos los términos en negrita del siguiente párrafo, puedes pasar al siguiente ejercicio. Si recién comienza con el aprendizaje profundo, le damos la bienvenida y siga leyendo.

bruja.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, ... )

Una capa densa

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

Captura de pantalla del 26-07-2016 a las 12.32.24.png

En una red neuronal, cada neurona hace una suma ponderada de todas sus entradas, agrega una constante llamada "sesgo de activación" y, luego, transmite el resultado a través de una función de activación no lineal. Los ponderaciones y los 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 los dígitos en 10 clases (0 a 9).

Con una multiplicación de matrices

A continuación, se muestra cómo una capa de la red neuronal procesa una colección de imágenes mediante una multiplicación de matrices:

matmul.gif

Mediante la primera columna de ponderaciones en la matriz W de ponderaciones, calculamos la suma ponderada de todos los píxeles de la primera imagen. Esta suma corresponde a la primera neurona. Con la segunda columna de pesos, hacemos lo mismo con la segunda neurona, y así sucesivamente hasta la décima neurona. Luego, podemos repetir la operación para las 99 imágenes restantes. Si llamamos a X la matriz que contiene nuestras 100 imágenes, todas las sumas ponderadas para nuestras 10 neuronas, calculadas en 100 imágenes, son simplemente 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. Llamaremos a este vector de 10 valores b. Debe agregarse a cada línea de la matriz calculada anteriormente. Con un poco de magia llamado &transmisión, 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 obtenemos la fórmula que describe una red neuronal de 1 capa aplicada a 100 imágenes:

Captura de pantalla del 26-07-2016 a las 16.02.36.png

En Keras

Con las bibliotecas de redes neuronales de alto nivel, como Keras, no será necesario implementar esta fórmula. Sin embargo, es importante comprender que una capa de la 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')

Obtén más información

Es trivial encadenar las capas de redes neuronales. La primera capa calcula sumas ponderadas de píxeles. Las capas posteriores calculan sumas ponderadas de los resultados de las capas anteriores.

La única diferencia, aparte de la cantidad de neuronas, es la elección de la función de activación.

Funciones de activación: relu, softmax y sigmoid

En general, usarías la función de activación para todas las capas, excepto la última. La última capa, en un clasificador, usaría la activación de "softmax".

Nuevamente, una neurona calcula una suma ponderada de todas sus entradas, 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 &LUt;RELU&quot para unidad lineal rectificada. Es una función muy sencilla, como se muestra en el gráfico anterior.

La función de activación tradicional en las redes neuronales era la &supt;sigmoide&;, pero se demostró que su mejor función es la de convergencia casi en todas partes y ahora es la preferida.

Activación de softmax para la clasificación

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

La aplicación de softmax en un vector se realiza tomando el valor exponencial de cada elemento y luego normalizando el vector, generalmente dividiéndolo por su estándar (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, se denomina "logits". Si este vector es L = [L0, L1, L2, L3, L4, L5, L6, L7, L8, L9], entonces:

Pérdida de entropía cruzada

Ahora que nuestra red neuronal produce predicciones a partir de imágenes de entrada, necesitamos medir qué tan buenas son, es decir, la distancia entre lo que nos indica la red y las respuestas correctas, a menudo llamadas "etiquetas" Recuerde que tenemos las 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 la más efectiva. A esto lo llamaremos función de error o pérdida:

Descenso de gradientes

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

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

Si calculamos las derivadas parciales de la entropía cruzada de manera relativa a todas las ponderaciones y a todos los sesgos, obtenemos un gradiente calculado para una imagen determinada, una etiqueta y el valor actual de las ponderaciones y sesgos. Recuerden que podemos tener millones de ponderaciones y sesgos, por lo que calcular el gradiente suena como mucho trabajo. Afortunadamente, TensorFlow lo hace por nosotros. La propiedad matemática de un gradiente es que apunta hacia arriba. Como queremos ir a un lugar donde la entropía cruzada es baja, vamos en la dirección opuesta. Actualizamos los pesos y los sesgos en 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. Esperamos que esto converja en un lugar en el que la entropía cruzada sea mínima, aunque nada garantiza que este mínimo sea único.

gradiente descenso2.png

Minilotes y impulso

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

Esta técnica, a veces llamada “descenso de gradientes estocástico”, tiene otro beneficio más pragmático: trabajar con lotes también significa trabajar con matrices más grandes, y suelen ser más fáciles de optimizar en GPU y TPU.

Sin embargo, la convergencia puede ser un poco caótica e incluso puede detenerse si el vector de gradiente es todos ceros. ¿Significa que encontramos un mínimo? No en todos los casos. Un componente de gradiente puede ser cero en un mínimo o un máximo. Con un vector gradiente con millones de elementos, si son todos 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 montar son bastante comunes, por lo que no queremos detenernos en ellos.

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 darle un impulso al algoritmo de optimización para que pueda navegar más allá de los puntos de montar sin detenerse.

Glosario

por lotes o por lotes mínimo: El entrenamiento siempre se realiza en lotes de etiquetas y datos de entrenamiento. Esto ayuda a que el algoritmo converja. Por lo general, la dimensión "lote" es la primera dimensión 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: una función de pérdida especial que se suele usar en los clasificadores.

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

Características: las entradas de una red neuronal a veces se denominan "atributos". El arte de determinar qué partes de un conjunto de datos (o combinaciones de partes) ingresar en una red neuronal para obtener buenas predicciones se denomina "ingeniería de atributos".

etiquetas: otro nombre para "classes" o respuestas correctas en un problema de clasificación supervisada

tasa de aprendizaje: Fracción del gradiente por la que se actualizan los pesos y sesgos en cada iteración del bucle de entrenamiento.

logits: Los resultados 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. Neuron genera resultados antes de la función logística y se acortó a loglog.logits.

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

neurona: calcula la suma ponderada de sus entradas, agrega un sesgo y envía 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. Función de activación popular para las neuronas.

sigmoidea: Otra función de activación que solía ser popular y 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 normaliza el vector para que tenga una suma de 1, de manera que se pueda interpretar como un vector de probabilidades. Se usa como último paso en los clasificadores.

tensor: Un "tensor" es como una matriz, pero con una cantidad arbitraria de dimensiones. Un tensor unidimensional es un vector. Un tensor de 2 dimensiones es una matriz. Luego, puedes tener tensores con 3, 4, 5 o más dimensiones.

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

keras_01_mnist.ipynb

Revisemos todas las celdas de este notebook.

Parámetros de celda

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

Importaciones

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

Utilidades de visualización de celdas [RUN ME]"

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

Celda tf.data.Dataset: analiza archivos y prepara conjuntos de datos de entrenamiento y validación"

Esta celda usó la API 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, aquí hay un instructivo que lo explica: Canalizaciones de datos de velocidad de TPU. Por ahora, los conceptos básicos son los siguientes:

Las imágenes y las 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 imagen. Deben decodificarse en imágenes. Para hacerlo, definimos una función. La imagen no se comprime, de modo que la función no necesita decodificar nada (decode_raw no realiza ninguna acción). Luego, la imagen se convierte a valores de punto flotante entre 0 y 1. Podríamos cambiar su forma aquí como una imagen en 2D, pero, en realidad, la mantenemos como una matriz plana de píxeles de 28*28 porque es lo que espera nuestra capa densa inicial.

def read_image(tf_bytestring):
    image = tf.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 mediante .map y obtenemos un conjunto de datos de imágenes:

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

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

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

Ahora, tenemos un conjunto de datos de pares (imagen y 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 necesarias para preparar conjuntos de datos:

.cache almacena en caché el conjunto de datos en la RAM. Este es un pequeño conjunto de datos, por lo que funcionará. .shuffle la mezcla con un búfer de 5,000 elementos. Es importante que los datos de entrenamiento estén bien mezclados. .repeat repite el conjunto de datos. Lo entrenaremos varias veces (varias etapas). .batch extrae varias imágenes y etiquetas en una miniatura. Por último, .prefetch puede usar la CPU para preparar el siguiente lote mientras el lote actual se entrena 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.

Modelo de celda

Todos nuestros modelos serán secuencias rectas de capas a fin de poder usar el estilo de tf.keras.Sequential para crearlas. En principio, aquí se trata de una sola capa densa. Tiene 10 neuronas porque clasificamos dígitos escritos a mano en 10 clases. Utiliza un botón de activación "softmax 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 definirla. Aquí, los vectores de entrada son vectores planos de valores de píxeles 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)

El modelo se configura 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 correctamente.

Keras ofrece la utilidad model.summary(), que permite imprimir los detalles del modelo que creaste. Tu instructor de tipo agregó la utilidad PlotTraining (definida en la celda de utilidades de visualización) que mostrará varias curvas de entrenamiento durante el entrenamiento.

Celda, entrenamiento y validación del modelo

Aquí es donde se realiza el entrenamiento: 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 mediante devoluciones de llamada. Así es como se implementó la trama de entrenamiento con actualización dinámica para este taller.

Celda visualizar predicciones

Una vez que se entrena el modelo, podemos obtener predicciones a partir de é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 de fuentes locales a modo de prueba. Recuerda que la red neuronal muestra un vector de 10 probabilidades de su extremo "softmax". Para obtener la etiqueta, debemos averiguar qué probabilidad es la más alta. np.argmax de la biblioteca de NumPy hace eso.

Para comprender por qué se necesita 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]. Estamos calculando el argmax en las 10 probabilidades que se muestran para cada imagen, por lo tanto, axis=1 (el primer eje es 0).

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

godeep.png

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

Captura de pantalla del 27-07-2016 a las 15.36.55.png

Conservamos 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, usaremos la función de activación más clásica: la sigmoidea:

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')
  ])

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

Al parecer, la pérdida también se produjo por accidente. Algo no funciona bien.

Acabas de experimentar las redes neuronales, como las personas solían diseñarlas en los años 80 y 30; No es de extrañar que se hayan rendido de la idea y que dieron inicio a la llamada "invierno de IA". De hecho, a medida que agregas capas, las redes neuronales tienen cada vez más dificultades para converger.

Resulta que las redes neuronales profundas con muchas capas (20, 50 y 100 en la actualidad) funcionan muy bien, siempre y cuando haya algunos trucos matemáticos sucios para hacerlos converger. El descubrimiento de estos trucos sencillos es uno de los motivos 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. Se neutralizan todos los valores entre 0 y 1. Cuando lo haces de forma repetida, las salidas de la neurona y sus gradientes pueden desaparecer por completo. Se mencionó por razones históricas, pero las redes modernas usan RELU (Unidad lineal rectificada), que se ve de la siguiente manera:

La relu, por el contrario, tiene una derivada de 1, al menos en el lado derecho. Con la activación RELU, incluso si los gradientes provienen de algunas neuronas pueden ser cero, siempre habrá otras que den un gradiente distinto de cero, y el entrenamiento puede continuar a un buen ritmo.

Un optimizador mejor

En espacios de muchas dimensiones, como en el caso de los sesgos y las ponderaciones de 10,000, suelen ser 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 atascado allí. TensorFlow cuenta con una amplia gama de optimizadores disponibles, incluidos algunos que funcionan con cierta inercia y que navegan de forma segura más allá de los puntos de inserción.

Inicializaciones aleatorias

El arte de inicializar los sesgos 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í. Afortunadamente, Keras hace lo que corresponde de forma predeterminada y usa el inicializador 'glorot_uniform', que es el mejor en casi todos los casos.

No hay nada que debas hacer, ya que Keras ya hace lo correcto.

NaN ???

La fórmula de entropía cruzada implica un logaritmo y log(0) no es un número (NaN, si lo prefiere, una falla numérica). ¿La entrada a la entropía cruzada puede ser 0? La entrada proviene de softmax, que en esencia es exponencial y este valor nunca es cero. ¡Así que estamos seguros!

¿En serio? En el hermoso mundo de las matemáticas, estaríamos seguros, pero en el mundo de las computadoras, exp(-150), representado en formato float32, es igual a ZERO y la falla de entropía cruzada.

Afortunadamente, tampoco puedes 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 las temidas NaNs.

¿Listo?

Ahora deberías obtener una precisión del 97%. El objetivo de este taller es ser muy superior al 99%, así que sigamos adelante.

Si no puede avanzar, la solución en este punto es la siguiente:

keras_02_mnist_dense.ipynb

¿Tal vez podemos intentar entrenar más rápido? La tasa de aprendizaje predeterminada en el optimizador Adam es de 0.001. Intentemos aumentarla.

Ir más rápido no parece ser de mucha ayuda. ¿A qué se debe todo este ruido?

Las curvas de entrenamiento son muy ruidosas y observan ambas curvas de validación: saltan de arriba abajo. Esto significa que estamos avanzando demasiado rápido. Podríamos volver a nuestra velocidad anterior, pero hay una mejor opción.

ralentizar

La buena solución es comenzar con rapidez y reducir la tasa de aprendizaje de manera exponencial. En Keras, puedes hacer esto con la devolución de llamada tf.keras.callbacks.LearningRateScheduler.

Código útil para pegar 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 la lr_decay_callback que creaste. Agrégalo 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. Observa que la mayor parte del ruido desapareció y que la precisión de la prueba ahora es superior al 98% de forma constante.

El modelo parece estar convergendo bien. Intentemos ir más allá.

¿Te ayuda?

En realidad, la exactitud se detiene en el 98% y observa la pérdida de validación. ¡Está aumentando! El algoritmo de aprendizaje solo funciona con datos de entrenamiento y optimiza la pérdida de entrenamiento en consecuencia. Nunca ve datos de validación, por lo que no es sorprendente que, después de un tiempo, su trabajo ya no tenga un efecto en la pérdida de validación que deja de caer y, a veces, incluso rebota.

Esto no afecta de inmediato las capacidades de reconocimiento del mundo real, pero te impedirá ejecutar muchas iteraciones y, en general, es una señal de que el entrenamiento ya no tiene un efecto positivo.

abandono.png

Por lo general, esta desconexión se denomina "sobreajuste" y, cuando la ves, puedes intentar aplicar una técnica de regularización llamada "eliminación"; La técnica de descarte lanza neuronas aleatorias en cada iteración de entrenamiento.

¿Funcionó?

y, como es de esperarse, resurge el ruido. La pérdida de validación parece no aumentar considerablemente, pero en general es más alta que sin pérdida de datos. La exactitud de la validación disminuyó un poco. Este resultado es bastante decepcionante.

Al parecer, la eliminación no es la solución correcta o quizás el sobreajuste es un concepto más complejo y algunas de sus causas no son susceptibles de corrección.

¿Qué es el sobreajuste? El sobreajuste ocurre cuando una red neuronal aprende de un modo 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 hacer que aprenda de una mejor manera, pero el sobreajuste también tiene raíces más profundas.

sobreajuste.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 todas nuestras imágenes de entrenamiento en ellas y, luego, reconocerlas mediante la coincidencia de patrones. Fallaría completamente en los datos del mundo real. Una red neuronal debe ser un poco limitada, de modo que se vea forzada a generalizar lo que aprende durante el entrenamiento.

Si tienes muy pocos datos de entrenamiento, incluso una red pequeña puede aprender de ellos y tú verás un sobreajuste. En términos generales, siempre necesitas muchos datos para entrenar redes neuronales.

Por último, si ya hiciste todo el libro, experimentaste con diferentes tamaños de red para asegurarte de que sus grados de libertad estén limitados, se aplique la retirada y se entrenó con una gran cantidad de datos, es posible que todavía estés atascado en un nivel de rendimiento que nada parece mejorar. Esto significa que la red neuronal, en su forma actual, no puede extraer más información de los datos, como ocurre en este caso.

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

Si no puede avanzar, la solución en este punto es la siguiente:

keras_03_mnist_dense_lrdecay_dropout.ipynb

En pocas palabras

Si ya conoces todos los términos en negrita del siguiente párrafo, puedes pasar al siguiente ejercicio. Si recién comienzas a usar redes neuronales convolucionales, sigue leyendo.

convolucional.gif (en inglés)

Ilustración: Filtrado de una imagen con dos filtros sucesivos de 4 x 3 x 48 ponderaciones por aprendizaje.

Así 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')
])

En una capa de una red convolucional, una "neurona" hace una suma ponderada de los píxeles que se encuentran por encima de ella, solo en una pequeña región de la imagen. Agrega un sesgo y alimenta la suma a través de una función de activación, del mismo modo que lo haría una neurona en una capa densa normal. Esta operación se repite en toda la imagen con las mismas ponderaciones. Recuerde que, en las capas densas, cada neurona tiene sus propios pesos. Aquí, se desliza una única imagen de pesos por toda la imagen en ambas direcciones (una convolución). El resultado tiene tantos valores como píxeles en la imagen (aunque se necesita cierto relleno en los bordes). Es una operación de filtrado. En la ilustración anterior, se usa 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. Denominámoslo como un canal de salidas por su analogía con los canales R, G y B en la imagen de entrada.

Captura de pantalla del 29-07-2016 a las 16.02.37.png

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

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

Convoluciones escalonadas y reducción máxima

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

  • Convolución recta: un filtro deslizante como el anterior, pero con segmento 1
  • Agrupación máxima: Es una ventana deslizante que aplica la operación MAX (por lo general, en parches de 2 x 2, repetidas cada 2 píxeles).

Ilustración: deslizar la ventana de procesamiento 3 píxeles produce menos valores de salida. Las convoluciones o el reducciones máximos (en una ventana de 2 x 2 que se deslizan por un tramo de 2) 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 pasarla a través de la capa densa final.

El primero es acoplar el cubo de datos en un vector y, luego, incorporarlo a la capa de softmax. En ocasiones, incluso puedes agregar una capa densa antes de la capa de softmax. 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 las ponderaciones de toda la red neuronal.

En lugar de usar una capa densa y costosa, también podemos dividir los datos entrantes (cubo) en tantas partes como tengamos clases, promediar sus valores y transmitirlos a través de una función de activación softmax. Esta forma de compilar el encabezado de clasificación cuesta 0 ponderaciones. En Keras, hay una capa para esto: tf.keras.layers.GlobalAveragePooling2D().

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

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

Tenga en cuenta que la segunda y la tercera capa convolucional tienen un segmento de dos, que explica por qué reducen la cantidad de valores de salida de 28 x 28 a 14 x 14 y, luego, de 7 x 7.

Escribiremos el código de Keras.

Se requiere mucha atención antes de la primera capa convolucional. De hecho, se espera un "cubo" de datos en 3D, pero, hasta el momento, nuestro conjunto de datos se configuró para capas densas y todos los píxeles de las imágenes se aplanan en un vector. Necesitamos cambiarles el formato en imágenes de 28 x 28 x 1 (1 canal para 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 el momento.

En Keras, la sintaxis para una capa convolucional activada por Relu' es la siguiente:

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

Para una convolución estirada, escribirías lo siguiente:

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

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

tf.keras.layers.Flatten()

En el caso de la capa densa, la sintaxis no cambió:

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

¿Tu modelo rompió la barrera de exactitud del 99%? Muy cerca... pero mira la curva de pérdida de validación. ¿Esto suena en una campana?

También mira 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 alrededor de 41⁄2 filas de detección errónea (aproximadamente 110 dígitos de 10,000)

Si no puede avanzar, la solución en este punto es la siguiente:

keras_04_mnist_convolutional.ipynb

La capacitación anterior muestra indicadores claros de sobreajuste (y aún no alcanza una exactitud del 99%). ¿Volveremos a probar con los retirados?

¿Cómo te fue en esta ocasión?

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

La primera vez que intentamos aplicar los retirados, pensamos que teníamos un problema de sobreajuste, cuando en realidad el problema estaba en la arquitectura de la red neuronal. No podríamos ir más allá de las capas convolucionales, y no hay nada que se pueda hacer con eso.

En este momento, parece que el sobreajuste fue la causa del problema y el abandono realmente ayudó. Recuerda, hay muchos factores que pueden provocar la desconexión entre las curvas de pérdida de entrenamiento y de validación, y la pérdida de validación aumenta gradualmente. El sobreajuste (demasiados grados de libertad, que se utilizan mal en la red) es solo uno de ellos. 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 la eliminación no te ayudará.

Por último, tratamos de agregar normalización de lotes.

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

Por el momento, reproduzcamos el libro y agreguemos una capa de norma por lotes en cada capa de la red neuronal, excepto en la última. No lo agregues a la última capa "softmax". 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?

Con un poco de ajuste (BATCH_SIZE=64, parámetro de decaimiento de la tasa de aprendizaje de 0.666, porcentaje de abandono en la capa densa 0.3) y un poco de suerte, puede llegar al 99.5%. Los ajustes de la tasa de aprendizaje y de abandono se realizaron con las "prácticas recomendadas" para usar la norma por lotes:

  • La regla de lotes ayuda a la convergencia de las redes neuronales y, por lo general, te permite entrenar más rápido.
  • La norma por lotes es un regularizador. Por lo general, puedes reducir la cantidad de abandono que usas, o incluso no utilizar ninguno.

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

keras_05_mnist_batch_norm.ipynb

Encontrará una versión del código lista para la nube en la carpeta mlengine en GitHub, junto con las instrucciones para ejecutarla en Google Cloud AI Platform. Para poder ejecutar esta parte, deberá crear una cuenta de Google Cloud y habilitar la facturación. Los recursos necesarios para completar el lab deberían ser inferiores a un par de dólares (si suponemos 1 hora de entrenamiento en una GPU). Para preparar su cuenta, siga estos pasos:

  1. Crea un proyecto en Google Cloud Platform (http://cloud.google.com/console).
  2. Habilita la facturación.
  3. Instala las herramientas de línea de comandos de GCP (SDK de GCP aquí).
  4. Crea un bucket de Google Cloud Storage (coloca en la región us-central1). Se usará para almacenar en etapa intermedia el código de entrenamiento y almacenar tu modelo entrenado.
  5. Habilite las API necesarias y solicite las cuotas necesarias (ejecute el comando de entrenamiento una vez. Debería recibir mensajes de error que le indiquen lo que debe habilitar).

Compiló su primera red neuronal y la entrenó en un 99%. Las técnicas aprendidas en el proceso no son específicas del conjunto de datos MNIST; en realidad, se usan ampliamente cuando se trabaja con redes neuronales. Como regalo de despedida, esta es la tarjeta de las notas del laboratorio, en versión de caricatura. Puedes usarlo para recordar lo que aprendiste:

laboratorios de notas sobre TensorFlow.png

Próximos pasos

  • Después de redes convolucionales y completamente conectadas, deberías ver las redes neuronales recurrentes.
  • Para ejecutar tu entrenamiento o inferencia en la nube en una infraestructura distribuida, Google Cloud proporciona AI Platform.
  • Por último, nos encantan los comentarios. Indíquenos si observa algún error en este lab o si considera que debería mejorarse. Nos encargamos de los comentarios a través de los problemas de GitHub [vínculo de comentarios].

HR.png

ID de Martin Görner pequeño.jpg

Autor: Martin Görner

Twitter: @martin_gorner

Todas las imágenes de dibujos animados de este lab: alexpokusay / 123RF fotos de archivo