Convnets modernos, squeezenet, Xception, con Keras y TPU

1. Descripción general

En este lab, aprenderás sobre la arquitectura convolucional moderna y usarás tus conocimientos para implementar un convnet simple pero eficaz llamado "squeezenet".

Este lab incluye las explicaciones teóricas necesarias sobre las redes neuronales convolucionales y es un buen punto de partida para los desarrolladores que aprenden sobre el aprendizaje profundo.

Este lab es la parte 4 de la serie "Keras en TPU". Puedes hacerlo en el siguiente orden o de forma independiente.

ca8cc21f6838eccc.png

Qué aprenderás

  • Dominar el estilo funcional de Keras
  • Pasos para compilar un modelo mediante la arquitectura squeezenet
  • Usar TPU para entrenar rápido e iterar en tu arquitectura
  • Para implementar la magnificación de datos con tf.data.dataset
  • Para ajustar un modelo grande previamente entrenado (Xception) en TPU

Comentarios

Si detectas que falta algo en este codelab, comunícate con nosotros. Se pueden proporcionar comentarios a través de los problemas de GitHub [feedback link].

2. Inicio rápido de Google Colaboratory

Este lab usa Google Colaboratory y no requiere configuración de tu parte. Colaboratory es una plataforma de notebooks en línea con fines educativos. Ofrece capacitación gratuita en CPU, GPU y TPU.

688858c21e3beff2.png

Puedes abrir este notebook de muestra y ejecutarlo en algunas celdas para familiarizarte con Colaboratory.

c3df49e90e5a654f.png Welcome to Colab.ipynb

Selecciona un backend de TPU

8832c6208c99687d.png

En el menú de Colab, selecciona Entorno de ejecución > Cambiar tipo de entorno de ejecución y, luego, selecciona TPU. En este codelab, usarás una potente TPU (unidad de procesamiento tensorial) respaldada para el entrenamiento acelerado por hardware. 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” de la esquina superior derecha.

Ejecución de notebooks

76d05caa8b4db6da.png

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

Índice

429f106990037ec4.png

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

Celdas ocultas

edc3dba45d26f12a.png

Algunas celdas solo mostrarán su 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 contienen, 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 contienen.

Autenticación

cdd4b41413100543.png

Colab puede acceder a tus buckets privados de Google Cloud Storage, siempre que te autentiques con una cuenta autorizada. El fragmento de código anterior activará un proceso de autenticación.

3. [INFO] ¿Qué son las unidades de procesamiento tensorial (TPU)?

En pocas palabras

f88cf6facfc70166.png

El código para entrenar un modelo en TPU en Keras (y recurrir a GPU o CPU si no hay una TPU disponible):

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

Hoy usaremos TPU para crear y optimizar un clasificador de flores a velocidades interactivas (minutos por ejecución de entrenamiento).

688858c21e3beff2.png

¿Por qué elegir las TPU?

Las GPU modernas se organizan en torno a "núcleos" programables, una arquitectura muy flexible que les permite controlar una variedad de tareas, como la renderización en 3D, el aprendizaje profundo, las simulaciones físicas, etcétera. Por otro lado, las TPU combinan un procesador vectorial clásico con una unidad de multiplicación de matrices dedicada y se destacan en cualquier tarea en la que predominan las multiplicaciones de matrices grandes, como las redes neuronales.

8eb3e718b8e2ed08.png

Ilustración: una capa de red neuronal densa como una multiplicación de matrices, con un lote de ocho imágenes procesadas a través de la red neuronal a la vez. Ejecuta una multiplicación de una línea por una columna para verificar que realmente se esté realizando una suma ponderada de todos los valores de píxeles de una imagen. Las capas convolucionales también se pueden representar como multiplicaciones de matrices, aunque es un poco más complicado ( aquí se explica en la sección 1).

El hardware

MXU y VPU

Un núcleo de TPU v2 consta de una unidad de multiplicación de matrices (MXU) que ejecuta multiplicaciones de matrices y una unidad de procesamiento vectorial (VPU) para todas las demás tareas, como activaciones, softmax, etc. La VPU controla los cálculos de float32 y int32. Por otro lado, la MXU opera en un formato de punto flotante de precisión mixta de 16 a 32 bits.

7d68944718f76b18.png

Punto flotante de precisión mixta y bfloat16

La MXU calcula las multiplicaciones de matrices con entradas bfloat16 y salidas float32. Las acumulaciones intermedias se realizan con precisión de float32.

19c5fc432840c714.png

El entrenamiento de redes neuronales suele ser resistente al ruido que introduce una precisión de punto flotante reducida. En algunos casos, el ruido incluso ayuda a que el optimizador converja. La precisión de punto flotante de 16 bits se ha utilizado tradicionalmente para acelerar los cálculos, pero los formatos float16 y float32 tienen rangos muy diferentes. Reducir la precisión de float32 a float16 suele generar desbordamientos y desbordamientos negativos. Existen soluciones, pero, por lo general, se requiere trabajo adicional para que funcione float16.

Es por eso que Google introdujo el formato bfloat16 en las TPU. bfloat16 es un float32 truncado con exactamente los mismos bits y rango de exponente que float32. Esto, sumado al hecho de que las TPU calculan multiplicaciones de matrices en precisión mixta con entradas bfloat16 pero resultados float32, significa que, por lo general, no se necesitan cambios en el código para beneficiarse de las mejoras en el rendimiento de la precisión reducida.

Array sístole

La MXU implementa multiplicaciones de matrices en hardware con una arquitectura llamada "array sistólica" en la que los elementos de datos fluyen a través de un array de unidades de procesamiento de hardware. (En medicina, "sistólica" se refiere a las contracciones cardíacas y al flujo sanguíneo, en este caso, al flujo de datos).

El elemento básico de una multiplicación de matrices es un producto escalar entre una línea de una matriz y una columna de la otra (consulta la ilustración en la parte superior de esta sección). Para una multiplicación de matrices Y=X*W, un elemento del resultado sería:

Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]

En una GPU, se programaría este producto punto en un "núcleo" de GPU y, luego, se ejecutaría en tantos "núcleos" como estén disponibles en paralelo para intentar calcular todos los valores de la matriz resultante a la vez. Si la matriz resultante es de 128 × 128 grande, eso requeriría que haya 128 × 128=16,000 “núcleos” disponibles, lo cual generalmente no es posible. Las GPUs más grandes tienen alrededor de 4,000 núcleos. Por otro lado, una TPU usa el hardware mínimo para las unidades de procesamiento en la MXU: solo multiplicadores de acumuladores bfloat16 x bfloat16 => float32, nada más. Son tan pequeños que una TPU puede implementar 16,000 de ellos en una MXU de 128 x 128 y procesar esta multiplicación de matrices de una sola vez.

f1b283fc45966717.gif

Ilustración: el array sistólico de MXU. Los elementos de procesamiento son multiplicadores y acumuladores. Los valores de una matriz se cargan en el array (puntos rojos). Los valores de la otra matriz fluyen a través del array (puntos grises). Las líneas verticales propagan los valores hacia arriba. Las líneas horizontales propagan sumas parciales. Queda como ejercicio para el usuario verificar que, a medida que los datos fluyen a través del array, se obtiene el resultado de la multiplicación de matrices que sale del lado derecho.

Además, mientras los productos escalar se calculan en una MXU, las sumas intermedias simplemente fluyen entre unidades de procesamiento adyacentes. No es necesario almacenarlos ni recuperarlos desde la memoria ni desde un archivo de registro. El resultado final es que la arquitectura del array sistólico de TPU tiene una ventaja de densidad y potencia significativa, además de una ventaja de velocidad no insignificante en comparación con una GPU, cuando se calculan multiplicaciones de matrices.

Cloud TPU

Cuando solicitas una “Cloud TPU v2” en Google Cloud Platform, obtienes una máquina virtual (VM) que tiene una placa de TPU conectada con PCI. La placa TPU tiene cuatro chips TPU de doble núcleo. Cada núcleo de TPU cuenta con una VPU (unidad de procesamiento vectorial) y una MXU (unidad de multiplicación de matrices) de 128 × 128. Por lo general, esta "Cloud TPU" se conecta a través de la red a la VM que la solicitó. Por lo tanto, la imagen completa se ve de la siguiente manera:

dfce5522ed644ece.png

Ilustración: tu VM con un acelerador "Cloud TPU" conectado a la red. “La Cloud TPU” en sí está hecha de una VM con una placa de TPU conectada a PCI y cuatro chips TPU de doble núcleo.

Pods de TPU

En los centros de datos de Google, las TPU están conectadas a una interconexión de computación de alto rendimiento (HPC) que puede hacer que parezcan un acelerador muy grande. Google los llama Pods, que pueden abarcar hasta 512 núcleos TPU v2 o 2,048 núcleos TPU v3.

2ec1e0d341e7fc34.jpeg

Ilustración: un pod de TPU v3. Placas y bastidores de TPU conectados a través de una interconexión de HPC

Durante el entrenamiento, los gradientes se intercambian entre los núcleos de TPU con el algoritmo de reducción total (aquí hay una buena explicación de la reducción total). El modelo que se está entrenando puede aprovechar el hardware entrenando con tamaños de lotes grandes.

d97b9cc5d40fdb1d.gif

Ilustración: Sincronización de gradientes durante el entrenamiento con el algoritmo de reducción completa en la red HPC de malla toroidal 2D de Google TPU.

El software

Entrenamiento del tamaño de lotes grandes

El tamaño de lote ideal para las TPU es de 128 elementos de datos por núcleo de TPU, pero el hardware ya puede mostrar un buen uso de 8 elementos de datos por núcleo de TPU. Recuerda que una Cloud TPU tiene 8 núcleos.

En este codelab, usaremos la API de Keras. En Keras, el lote que especificas es el tamaño de lote global para toda la TPU. Tus lotes se dividirán automáticamente en 8 y se ejecutarán en los 8 núcleos de la TPU.

da534407825f01e3.png

Para obtener más sugerencias sobre el rendimiento, consulta la Guía de rendimiento de TPU. Para tamaños de lotes muy grandes, es posible que se deba tener especial cuidado en algunos modelos. Consulta LARSOptimizer para obtener más detalles.

Detrás de escena: XLA

Los programas de TensorFlow definen grafos de procesamiento. La TPU no ejecuta directamente el código de Python, sino el grafo de procesamiento definido por tu programa de TensorFlow. En el fondo, un compilador llamado XLA (compilador de álgebra lineal acelerada) transforma el grafo de nodos de procesamiento de TensorFlow en código máquina de TPU. Este compilador también realiza muchas optimizaciones avanzadas en tu código y en el diseño de la memoria. La compilación se realiza automáticamente a medida que el trabajo se envía a la TPU. No es necesario que incluyas XLA en tu cadena de compilación de forma explícita.

edce61112cd57972.png

Ilustración: Para ejecutarse en TPU, el grafo de procesamiento definido por tu programa de TensorFlow primero se traduce a una representación de XLA (compilador de álgebra lineal acelerada) y, luego, XLA lo compila en código máquina de TPU.

Cómo usar TPUs en Keras

A partir de TensorFlow 2.1, las TPU son compatibles con la API de Keras. La compatibilidad con Keras funciona en TPU y pods de TPU. Este es un ejemplo que funciona en TPU, GPUs y CPU:

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

En este fragmento de código, se incluye lo siguiente:

  • TPUClusterResolver().connect() encuentra la TPU en la red. Funciona sin parámetros en la mayoría de los sistemas de Google Cloud (trabajos de AI Platform, Colaboratory, Kubeflow y VMs de aprendizaje profundo creadas a través de la utilidad "TextView up"). Estos sistemas saben dónde está su TPU gracias a una variable de entorno TPU_NAME. Si creas una TPU de forma manual, configura la variable de entorno TPU_NAME en la VM desde la que la estás usando o llama a TPUClusterResolver con parámetros explícitos: TPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy es la parte que implementa la distribución y el algoritmo de sincronización de gradientes “all-reduce”.
  • La estrategia se aplica a través de un alcance. El modelo debe definirse dentro de la estrategia scope().
  • La función tpu_model.fit espera un objeto tf.data.Dataset como entrada para el entrenamiento de TPU.

Tareas comunes de portabilidad de TPU

  • Si bien existen muchas formas de cargar datos en un modelo de TensorFlow, para las TPU, es necesario usar la API de tf.data.Dataset.
  • Las TPU son muy rápidas y, a menudo, la transferencia de datos se convierte en el cuello de botella cuando se ejecutan en ellas. En la Guía de rendimiento de la TPU, encontrarás herramientas que puedes usar para detectar cuellos de botella de datos y otras sugerencias de rendimiento.
  • Los números int8 o int16 se tratan como int32. La TPU no tiene hardware de números enteros que funcione en menos de 32 bits.
  • Algunas operaciones de TensorFlow no son compatibles. La lista está aquí. La buena noticia es que esta limitación solo se aplica al código de entrenamiento, es decir, al paso hacia adelante y hacia atrás a través de tu modelo. Aún puedes usar todas las operaciones de TensorFlow en tu canalización de entrada de datos, ya que se ejecutarán en la CPU.
  • tf.py_func no es compatible con TPU.

4. [INFO] Conceptos básicos del clasificador de redes neuronales

En resumen

Si ya conoces todos los términos en negrita del siguiente párrafo, puedes pasar al siguiente ejercicio. Si recién comienzas a usar el aprendizaje profundo, te damos la bienvenida y te pedimos que sigas leyendo.

Para los modelos creados como una secuencia de capas, Keras ofrece la API de Sequential. 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=[192, 192, 3]),
    tf.keras.layers.Dense(500, activation="relu"),
    tf.keras.layers.Dense(50, activation="relu"),
    tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 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

Red neuronal densa

Esta es la red neuronal más simple para clasificar imágenes. Está formada por "neuronas" dispuestas en capas. La primera capa procesa los datos de entrada y envía sus resultados a otras capas. Se llama “densa” porque cada neurona está conectada a todas las neuronas en la capa anterior.

c21bae6dade487bc.png

Puedes incorporar una imagen a esta red compactando los valores RGB de todos sus píxeles en un vector largo y usándolo como entradas. No es la mejor técnica para el reconocimiento de imágenes, pero la mejoraremos más adelante.

Neuronas, activaciones y RELU

Una “neurona” calcula una suma ponderada de todas sus entradas, agrega un valor llamado “sesgo” y alimenta el resultado a través de una llamada “función de activación”. Al principio, los pesos y el sesgo son desconocidos. Se inicializarán de forma aleatoria y se “aprenderán” entrenando la red neuronal con muchos datos conocidos.

644f4213a4ee70e5.png

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

Activación softmax

La red anterior termina con una capa de 5 neuronas porque estamos clasificando las flores en 5 categorías (rosa, tulipán, diente de león, margarita, girasol). Las neuronas de las capas intermedias se activan con la función de activación RELU clásica. Sin embargo, en la última capa, queremos calcular números entre 0 y 1 que representen la probabilidad de que esta flor sea una rosa, un tulipán, etcétera. Para ello, 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 con el uso de la norma L1 (suma de valores absolutos) para que los valores sumen 1 y puedan interpretarse como probabilidades.

ef0d98c0952c262d.png d51252f75894479e.gif

Pérdida de 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, que a menudo se denominan "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 la más eficaz. A esto lo llamaremos función de error o “pérdida”:

7bdf8753d20617fb.png

Descenso de gradientes

“Entrenar” la red neuronal en realidad significa usar imágenes de entrenamiento y etiquetas para ajustar los pesos y sesgos, de modo que se minimice la función de pérdida de la entropía cruzada. Así es como funciona.

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 ciclo 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

Impulso y minilotes

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 otro beneficio más pragmático: trabajar con lotes también significa trabajar con matrices más grandes y, por lo general, son más fáciles de optimizar en GPU y TPU.

Sin embargo, la convergencia puede ser un poco caótica, incluso puede detenerse si el vector de gradiente es cero. ¿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. Con un vector de 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 silla son bastante comunes y no queremos detenernos en ellos.

52e824fe4716c4a0.png

Ilustración: un punto silla. El gradiente es 0, pero no es un mínimo en todas las direcciones. (Atribución de la imagen Wikimedia: Por Nicoguaro - Obra propia, CC BY 3.0)

La solución es agregar un poco de impulso al algoritmo de optimización para que pueda pasar por los puntos silla sin detenerse.

Glosario

lote o minilote: El entrenamiento siempre se realiza en lotes de datos y etiquetas de entrenamiento. Esto ayuda a que el algoritmo converja. La dimensión "batch" suele ser la primera dimensión de los tensores de datos. Por ejemplo, un tensor de la 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 determinar qué partes de un conjunto de datos (o combinaciones de partes) se deben ingresar a una red neuronal para obtener buenas predicciones se denomina "ingeniería de atributos".

labels: otro nombre para las “clases” o las 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 "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. Una función de activación popular para las neuronas.

sigmoidea: otra función de activación que solía ser popular y que aún es ú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 último paso en los clasificadores.

tensor: Un "tensor" es como una matriz, pero con una cantidad arbitraria de dimensiones. Un tensor de 1 dimensión 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. [INFO] Redes neuronales convolucionales

En resumen

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.

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([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

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

688858c21e3beff2.png

Introducción a las redes neuronales convolucionales

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 pequeña región de la imagen. Agrega un sesgo y alimenta la suma a través de una función de activación, tal como lo haría una neurona en una capa densa normal. 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í, una sola "parcha" de pesos se desliza a través de 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 que 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. Llamemos a esto un “canal” de salidas por analogía con los canales R, G y B de la imagen de entrada.

Screen Shot 2016-07-29 at 16.02.37.png

Los dos (o más) conjuntos de pesos se pueden sumar como un tensor si se agrega 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.

d1b557707bcd1cb9.png

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

Convolución con paso, 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 con paso: Un filtro deslizante como el anterior, pero con un paso 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 contracciones con paso o la reducción máxima (máximo en una ventana de 2 × 2 que se desliza con un paso de 2) son una forma de reducir el cubo de datos en las dimensiones horizontales.

Clasificador convolucional

Por último, adjuntamos una cabeza de clasificación aplanando el último cubo de datos y pasándolo a través de una capa densa activada por softmax. Un clasificador de convoluciones típico puede verse de la siguiente manera:

4a61aaffb6cba3d1.png

Ilustración: un clasificador de imágenes que usa capas convolucionales y softmax. Usa filtros de 3 × 3 y 1 × 1. Las capas maxpool toman el máximo de grupos de 2 x 2 datos. El cabezal de clasificación se implementa con una capa densa con activación de softmax.

En Keras

La pila convolucional ilustrada anteriormente se puede escribir en Keras de la siguiente manera:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)    
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

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

6. [NEW INFO] Arquitecturas convolucionales modernas

En resumen

7968830b57b708c0.png

Ilustración: un "módulo" de convolución. ¿Qué es lo mejor en este momento? ¿Una capa de max-pooling seguida de una capa convolucional de 1 × 1 o una combinación diferente de capas? Pruébalos todos, concatena los resultados y deja que la red decida. A la derecha, se muestra la arquitectura convolucional " inception" que usa esos módulos.

En Keras, para crear modelos en los que el flujo de datos pueda ramificarse hacia adentro y afuera, debes usar el estilo de modelo “funcional”. A continuación, se muestra un ejemplo:

l = tf.keras.layers # syntax shortcut

y = l.Conv2D(filters=32, kernel_size=3, padding='same',
             activation='relu', input_shape=[192, 192, 3])(x) # x=input image

# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation

# many more layers ...

# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

688858c21e3beff2.png

Otros trucos baratos

Filtros pequeños de 3 × 3

40a7b15fb7dbe75c.png

En esta ilustración, se muestra el resultado de dos filtros 3 × 3 consecutivos. Intenta rastrear qué datos contribuyeron al resultado: estos dos filtros 3 × 3 consecutivos calculan alguna combinación de una región 5 × 5. No es exactamente la misma combinación que calcularía un filtro de 5 × 5, pero vale la pena probarlo porque dos filtros consecutivos de 3 × 3 son más económicos que un solo filtro de 5 × 5.

¿Convolución de 1 × 1?

fd7cac16f8ecb423.png

En términos matemáticos, una convolución de "1x1" es una multiplicación por una constante, no un concepto muy útil. Sin embargo, en las redes neuronales convolucionales, recuerda que el filtro se aplica a un cubo de datos, no solo a una imagen 2D. Por lo tanto, un filtro "1×1" calcula una suma ponderada de una columna de datos de 1×1 (consulta la ilustración) y, a medida que lo deslizas por los datos, obtendrás una combinación lineal de los canales de la entrada. Esto es realmente útil. Si piensas en los canales como los resultados de operaciones de filtrado individuales, por ejemplo, un filtro para "orejas puntiagudas", otro para "bigotes" y un tercero para "ojos cortos", una capa convolucional de "1 x 1" calculará varias combinaciones lineales posibles de estas funciones, que podrían ser útiles cuando se busca un "gato". Además, las capas de 1 × 1 usan menos pesos.

7. Apretón de manos

En el artículo "Squeezenet", se demostró una manera sencilla de aplicar estas ideas. Los autores sugieren un diseño de módulo de convolución muy simple, que solo usa capas de convolución de 1 × 1 y 3 × 3.

1730ac375379269b.png

Ilustración: arquitectura de SqueezeNet basada en "módulos de fuego". Alterna una capa de 1 × 1 que “aplasta” los datos entrantes en la dimensión vertical, seguida de dos capas convolucionales paralelas de 1 × 1 y 3 × 3 que “expanden” nuevamente la profundidad de los datos.

Actividad práctica

Continúa en tu notebook anterior y compila una red neuronal convolucional inspirada en squeezenet. Tendrás que cambiar el código del modelo al "estilo funcional" de Keras.

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

Información adicional

Para este ejercicio, será útil definir una función auxiliar para un módulo de squeezenet:

def fire(x, squeeze, expand):
  y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
  y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
  y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
  return tf.keras.layers.concatenate([y1, y3])

# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
  return lambda x: fire(x, squeeze, expand)

# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)

Esta vez, el objetivo es lograr una exactitud del 80%.

Qué puedes probar

Comienza con una sola capa convolucional y, luego, continúa con "fire_modules", alternando con capas MaxPooling2D(pool_size=2). Puedes experimentar con 2 a 4 capas de max pooling en la red y también con 1, 2 o 3 módulos de activación consecutivos entre las capas de max pooling.

En los módulos de incendios, el parámetro "squeeze" suele ser menor que el parámetro "expand". En realidad, estos parámetros son números de filtros. Por lo general, pueden variar de 8 a 196. Puedes experimentar con arquitecturas en las que la cantidad de filtros aumenta gradualmente a través de la red, o bien con arquitecturas sencillas en las que todos los módulos de activación tienen la misma cantidad de filtros.

A continuación, se muestra un ejemplo:

x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB

y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)

model = tf.keras.Model(x, y)

En este punto, es posible que notes que tus experimentos no van tan bien y que el objetivo de precisión del 80% parece lejano. Es hora de un par de trucos económicos más.

Normalización por lotes

La norma por lotes ayudará con los problemas de convergencia que experimentas. En el próximo taller, se brindarán explicaciones detalladas sobre esta técnica. Por ahora, úsala como un ayudante “mágico” de caja negra. Para ello, agrega esta línea después de cada capa convolucional en tu red, incluidas las capas dentro de la función fire_module:

y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context

El parámetro de momento debe disminuir de su valor predeterminado de 0.99 a 0.9 porque nuestro conjunto de datos es pequeño. Por ahora, no importa este detalle.

magnificación de datos

Obtendrás un par de puntos porcentuales más si aumentas los datos con transformaciones sencillas, como giros de izquierda a derecha de los cambios de saturación:

4ed2958e09b487ca.png

ad795b70334e0d6b.png

Esto es muy fácil de hacer en TensorFlow con la API de tf.data.Dataset. Define una nueva función de transformación para tus datos:

def data_augment(image, label):
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_saturation(image, lower=0, upper=2)
    return image, label

Luego, úsalo en tu transformación de datos final (celda "conjuntos de datos de entrenamiento y validación", función "get_batched_dataset"):

dataset = dataset.repeat() # existing line
# insert this
if augment_data:
  dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line

No olvides hacer que la magnificación de datos sea opcional y agregar el código necesario para asegurarte de que solo se aumente el conjunto de datos de entrenamiento. No tiene sentido aumentar el conjunto de datos de validación.

Ahora deberías poder alcanzar una precisión del 80% en 35 épocas.

Solución

Este es el notebook de la solución. Puedes usarla si no puedes avanzar.

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

Temas abordados

  • 🤔 Modelos de "estilo funcional" de Keras
  • 🤓 Arquitectura Squeezenet
  • 🤓 Magnificación de datos con tf.data.datset

Tómate un momento para repasar esta lista de tareas mentalmente.

8. Xception se ajustó

Convolución separable

Recientemente, se ha vuelto popular una forma diferente de implementar capas convolucionales: las contracciones separables en profundidad. Lo sé, es un trago, pero el concepto es bastante simple. Se implementan en Tensorflow y Keras como tf.keras.layers.SeparableConv2D.

Una convolución separable también ejecuta un filtro en la imagen, pero usa un conjunto distinto de pesos para cada canal de la imagen de entrada. Le sigue con una "convolución 1 x 1", una serie de productos escalar que da como resultado una suma ponderada de los canales filtrados. Con pesos nuevos cada vez, se calculan tantas recombinaciones ponderadas de los canales como sea necesario.

615720b803bf8dda.gif

Ilustración: convolución separable. Fase 1: Convoluciones con un filtro independiente para cada canal. Fase 2: Recombinaciones lineales de canales Se repite con un nuevo conjunto de pesos hasta que se alcanza la cantidad deseada de canales de salida. La fase 1 también se puede repetir, con pesos nuevos cada vez, pero en la práctica rara vez es así.

Las contracciones separables se usan en las arquitecturas de redes convolucionales más recientes: MobileNetV2, Xception y EfficientNet. Por cierto, MobileNetV2 es lo que usaste anteriormente para el aprendizaje por transferencia.

Son más baratas que las convoluciones regulares y se ha demostrado que son igualmente eficaces en la práctica. Este es el recuento de peso del ejemplo anterior:

Capa convolucional: 4 x 4 x 3 x 5 = 240

Capa convolucional separable: 4 × 4 × 3 + 3 × 5 = 48 + 15 = 63

Se deja como un ejercicio para que el lector calcule el número de multiplicaciones necesarias para aplicar cada estilo de escalas de capas convolucionales de manera similar. Las convoluciones separables son más pequeñas y mucho más efectivas en términos de procesamiento.

Actividad práctica

Reinicia desde el notebook de la zona de pruebas de “aprendizaje por transferencia”, pero esta vez selecciona Xception como el modelo previamente entrenado. Xception solo usa convoluciones separables. Deja que todos los pesos se puedan entrenar. Ajustaremos las ponderaciones previamente entrenadas de nuestros datos en lugar de usar las capas previamente entrenadas como tales.

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

Objetivo: precisión superior al 95% (en serio, es posible)

Este es el ejercicio final, requiere un poco más de código y trabajo en ciencia de datos.

Información adicional sobre el ajuste

Xception está disponible en los modelos estándar previamente entrenados en tf.keras.application.* No olvides dejar todos los pesos entrenables esta vez.

pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
                                                  include_top=False)
pretrained_model.trainable = True

Para obtener buenos resultados cuando ajustas un modelo, debes prestar atención a la tasa de aprendizaje y usar un programa de tasa de aprendizaje con un período de adaptación. Para ello, puedes escribir lo siguiente:

9b1af213b2b36d47.png

Comenzar con una tasa de aprendizaje estándar interrumpiría las ponderaciones previamente entrenadas del modelo. Comenzar de forma progresiva los preserva hasta que el modelo se haya fijado en tus datos y pueda modificarlos de una manera razonable. Después de la rampa, puedes continuar con una tasa de aprendizaje constante o una que decaiga exponencialmente.

En Keras, la tasa de aprendizaje se especifica a través de una devolución de llamada en la que puedes calcular la tasa de aprendizaje adecuada para cada época. Keras pasará la tasa de aprendizaje correcta al optimizador para cada época.

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

Solución

Este es el notebook de la solución. Puedes usarla si no puedes avanzar.

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

Temas abordados

  • 🤔 Convolución separable en profundidad
  • 🤓 Programas de tasa de aprendizaje
  • 😈 Ajustar un modelo previamente entrenado

Tómate un momento para revisar esta lista de verificación en tu cabeza.

9. ¡Felicitaciones!

Creaste tu primera red neuronal convolucional moderna y la entrenaste con una exactitud del 90%, iterando en un entrenamiento sucesivo en solo minutos gracias a las TPU. Aquí concluye los 4 “codelabs de Keras en TPU”:

TPU en la práctica

Las TPU y las GPUs están disponibles en Cloud 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. Se pueden proporcionar comentarios a través de los problemas de GitHub [feedback link].

HR.png

Martin Görner ID small.jpg
El autor: Martin Görner
Twitter: @martin_gorner

tensorflow logo.jpg
www.tensorflow.org