1. Descripción general
En este lab, aprenderás a compilar, entrenar y ajustar tus propias redes neuronales convolucionales desde cero con Keras y TensorFlow 2. Ahora, esto se puede hacer en minutos con la potencia de las TPU. También explorarás varios enfoques, desde el aprendizaje por transferencia muy simple hasta las arquitecturas convolucionales modernas, como Squeezenet. Este lab incluye explicaciones teóricas sobre las redes neuronales y es un buen punto de partida para los desarrolladores que están aprendiendo sobre aprendizaje profundo.
La lectura de artículos sobre el aprendizaje profundo puede ser difícil y confusa. Ahora analicemos las arquitecturas modernas de redes neuronales convolucionales.
Qué aprenderás
- Para usar Keras y unidades de procesamiento tensorial (TPU) y compilar tus modelos personalizados más rápido.
- Usar la API de tf.data.Dataset y el formato TFRecord para cargar datos de entrenamiento de forma eficiente
- Para hacer trampa 😈, usa el aprendizaje por transferencia en lugar de crear tus propios modelos.
- Usar los estilos de modelos secuenciales y funcionales de Keras
- Crear tu propio clasificador de Keras con una capa de softmax y una pérdida de entropía cruzada
- Para ajustar tu modelo con una buena elección de capas convolucionales.
- Explorar ideas de arquitectura moderna de red de conv. como módulos, reducción de promedio global, etcétera
- Crear una red convencional moderna y sencilla con la arquitectura Squeezenet.
Comentarios
Si ves algo fuera de lugar en este codelab, avísanos. Los comentarios se pueden enviar a través de los problemas de GitHub [ vínculo de comentarios].
2. Inicio rápido de Google Colaboratory
Este lab usa Google Colaboratory y no requiere configuración de tu parte. Puedes ejecutarlo desde una Chromebook. Abre el archivo que aparece a continuación y ejecuta las celdas para familiarizarte con los notebooks de Colab.
Selecciona un backend de TPU
En el menú de Colab, selecciona Entorno de ejecución > Cambiar tipo de entorno de ejecución y, luego, selecciona TPU. En este lab de código, usarás una TPU (unidad de procesamiento tensorial) potente con compatibilidad para el entrenamiento con aceleración de 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
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
Todos los notebooks tienen un índice. Puedes abrirlo con la flecha negra que está a la izquierda.
Celdas ocultas
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, admiten funciones de visualización. Aún debes ejecutar estas celdas para que se definan las funciones que contienen.
Autenticación
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 resumen
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 compilar y optimizar un clasificador de flores a velocidades interactivas (minutos por ejecución de entrenamiento).
¿Por qué usar 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 predominen grandes multiplicaciones de matrices, como las redes neuronales.
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. Analiza la multiplicación de una línea por una columna para verificar que efectivamente se realice 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 ( más explicación en la sección 1).
El hardware
MXU y VPU
Un núcleo de TPU v2 está formado por 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 maneja los cálculos float32 e int32. Por otro lado, la MXU opera en un formato de punto flotante de precisión mixta de 16 a 32 bits.
Punto flotante de precisión mixto y bfloat16
La MXU calcula las multiplicaciones de matrices con entradas bfloat16 y salidas float32. Las acumulaciones intermedias se realizan con precisión float32.
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. Tradicionalmente, la precisión de punto flotante de 16 bits se ha usado 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 un exceso y subdesbordamiento. Las soluciones existen, pero normalmente se requiere trabajo adicional para que float16 funcione.
Por eso, Google introdujo el formato bfloat16 en las TPU. bfloat16 es un número de punto flotante truncado con exactamente los mismos bits de exponente y rango que el número de punto flotante. Esto, sumado al hecho de que las TPUs calculan multiplicaciones de matrices con precisión mixta con entradas bfloat16, pero salidas float32, significa que, por lo general, no es necesario realizar cambios en el código para beneficiarse de los aumentos de rendimiento de la precisión reducida.
Array sistólico
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" hace referencia a las contracciones del corazón y al flujo sanguíneo, y aquí al flujo de datos).
El elemento básico de una multiplicación de matrices es un producto punto 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 cada valor 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 GPU 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ñas que una TPU puede implementar 16,000 en una MXU de 128 × 128 y procesar esta multiplicación de matrices de una sola vez.
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. Se deja como un ejercicio para que el usuario verifique 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 significativa de densidad y potencia, así como una ventaja de velocidad no despreciable sobre 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, el panorama completo se ve de la siguiente manera:
Ilustración: tu VM con un acelerador "Cloud TPU" conectado a la red. "La Cloud TPU" en sí está compuesta por una VM con una placa de TPU conectada a PCI con cuatro chips de 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 y pueden abarcar hasta 512 núcleos TPU v2 o 2,048 núcleos TPU v3.
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 la TPU con el algoritmo Allreduce ( buena explicación de Allreduce aquí). El modelo que se está entrenando puede aprovechar el hardware entrenando con tamaños de lotes grandes.
Ilustración: sincronización de gradientes durante el entrenamiento con el algoritmo de reducción total en la red de 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.
Para obtener más sugerencias sobre el rendimiento, consulta la Guía de rendimiento de TPU. En el caso de los tamaños de lote muy grandes, puede ser necesario 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 tienes que incluir XLA en tu cadena de compilación de forma explícita.
Ilustración: Para ejecutarse en TPU, el grafo de cómputo definido por tu programa de Tensorflow primero se traduce a una representación de XLA (compilador de álgebra lineal acelerado) y, luego, XLA lo compila en código máquina de TPU.
Cómo usar TPUs en Keras
Las TPU son compatibles con la API de Keras a partir de TensorFlow 2.1. La compatibilidad con Keras funciona en TPU y pods de TPU. A continuación, te mostramos un ejemplo que funciona con TPU, GPU 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 aTPUClusterResolver
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, la propagación hacia adelante y atrás en el 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. Cargar datos
Trabajaremos con un conjunto de datos de fotos de flores. El objetivo es aprender a categorizarlas en 5 tipos de flores. La carga de datos se realiza con la API de tf.data.Dataset
. Primero, conozcamos la API.
Actividad práctica
Abre el siguiente notebook, ejecuta las celdas (Mayúsculas + Intro) y sigue las instrucciones donde veas la etiqueta “TRABAJO NECESARIO”.
Fun with tf.data.Dataset (playground).ipynb
Información adicional
Acerca del conjunto de datos “flores”
El conjunto de datos está organizado en 5 carpetas. Cada carpeta contiene flores de un tipo. Las carpetas se llaman girasoles, margaritas, dientes de león, tulipanes y rosas. Los datos se alojan en un bucket público de Google Cloud Storage. Extracto:
gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg
¿Por qué usar tf.data.Dataset?
Keras y Tensorflow aceptan conjuntos de datos en todas sus funciones de entrenamiento y evaluación. Una vez que cargas datos en un conjunto de datos, la API ofrece todas las funcionalidades comunes que son útiles para los datos de entrenamiento de redes neuronales:
dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training
Puedes encontrar sugerencias de rendimiento y prácticas recomendadas de conjuntos de datos en este artículo. La documentación de referencia se encuentra aquí.
Conceptos básicos de tf.data.Dataset
Los datos suelen presentarse en varios archivos, en este caso, imágenes. Puedes crear un conjunto de datos de nombres de archivo llamando al siguiente comando:
filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.
Luego, “asignas” una función a cada nombre de archivo, que, por lo general, cargará y decodificará el archivo en datos reales en la memoria:
def decode_jpeg(filename):
bits = tf.io.read_file(filename)
image = tf.io.decode_jpeg(bits)
return image
image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)
Para iterar en un conjunto de datos, haz lo siguiente:
for data in my_dataset:
print(data)
Conjuntos de datos de tuplas
En el aprendizaje supervisado, un conjunto de datos de entrenamiento suele estar formado por pares de datos de entrenamiento y respuestas correctas. Para permitirlo, la función de decodificación puede mostrar tuplas. Luego, se mostrará un conjunto de datos de tuplas y tuplas cuando iteres en él. Los valores mostrados son tensores de TensorFlow listos para que los consuma el modelo. Puedes llamar a .numpy()
en ellos para ver los valores sin procesar:
def decode_jpeg_and_label(filename):
bits = tf.read_file(filename)
image = tf.io.decode_jpeg(bits)
label = ... # extract flower name from folder name
return image, label
image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs
for image, label in dataset:
print(image.numpy().shape, label.numpy())
Conclusión: ¡cargar imágenes una por una es lento!
A medida que iteres en este conjunto de datos, verás que puedes cargar entre 1 y 2 imágenes por segundo. Eso es demasiado lento. Los aceleradores de hardware que usaremos para el entrenamiento pueden sostener muchas veces este ritmo. Pasa a la siguiente sección para ver cómo lo lograremos.
Solución
Este es el notebook de la solución. Puedes usarla si no puedes avanzar.
Fun with tf.data.Dataset (solution).ipynb
Temas abordados
- 🤔 tf.data.Dataset.list_files
- 🤔 tf.data.Dataset.map
- 🤔 Conjuntos de datos de tuplas
- 😀 iterando a través de conjuntos de datos
Tómate un momento para revisar esta lista de verificación en tu cabeza.
5. Carga datos rápidamente
Los aceleradores de hardware de la unidad de procesamiento tensorial (TPU) que usaremos en este lab son muy rápidos. A menudo, el desafío es proporcionarles datos lo suficientemente rápido para mantenerlos ocupados. Google Cloud Storage (GCS) es capaz de mantener una capacidad de procesamiento muy alta, pero, al igual que con todos los sistemas de almacenamiento en la nube, iniciar una conexión cuesta un poco de red. Por lo tanto, no es ideal tener nuestros datos almacenados como miles de archivos individuales. Los agruparemos en una cantidad menor de archivos y usaremos la potencia de tf.data.Dataset para leer de varios archivos en paralelo.
Lectura
En el siguiente notebook, se encuentra el código que carga archivos de imagen, les cambia el tamaño a un tamaño común y, luego, los almacena en 16 archivos TFRecord. Léelo rápidamente. No es necesario ejecutarlo, ya que se proporcionarán datos con el formato TFRecord correcto para el resto del codelab.
Flower pictures to TFRecords.ipynb
Diseño de datos ideal para una capacidad de procesamiento óptima de GCS
El formato de archivo TFRecord
El formato de archivo preferido de Tensorflow para almacenar datos es el formato TFRecord basado en protobuf. Otros formatos de serialización también funcionan, pero puedes cargar un conjunto de datos desde archivos TFRecord directamente escribiendo lo siguiente:
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Para obtener un rendimiento óptimo, se recomienda usar el siguiente código más complejo para leer desde varios archivos TFRecord a la vez. Este código leerá de N archivos en paralelo y no tendrá en cuenta el orden de los datos en favor de la velocidad de lectura.
AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False
filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below
Hoja de referencia de TFRecord
En TFRecords se pueden almacenar tres tipos de datos: cadenas de bytes (lista de bytes), integer de 64 bits y float de 32 bits. Siempre se almacenan como listas; un solo elemento de datos será una lista de tamaño 1. Puedes usar las siguientes funciones auxiliares para almacenar datos en TFRecords.
Cómo escribir cadenas de bytes
# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))
Cómo escribir números enteros
def _int_feature(list_of_ints): # int64
return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))
Cómo escribir números de punto flotante
def _float_feature(list_of_floats): # float32
return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))
escribir un TFRecord, con los asistentes anteriores
# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
feature = {
"image": _bytestring_feature([my_img_bytes]), # one image in the list
"class": _int_feature([my_class]), # one class in the list
"size": _int_feature([my_height, my_width]), # fixed length (2) list of ints
"float_data": _float_feature(my_floats) # variable length list of floats
}
tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
out_file.write(tf_record.SerializeToString())
Para leer datos de TFRecords, primero debes declarar el diseño de los registros que almacenaste. En la declaración, puedes acceder a cualquier campo con nombre como una lista de longitud fija o una lista de longitud variable:
Leer de TFRecords
def read_tfrecord(data):
features = {
# tf.string = byte string (not text string)
"image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
"class": tf.io.FixedLenFeature([], tf.int64), # shape [] means scalar, i.e. a single item
"size": tf.io.FixedLenFeature([2], tf.int64), # two integers
"float_data": tf.io.VarLenFeature(tf.float32) # a variable number of floats
}
# decode the TFRecord
tf_record = tf.io.parse_single_example(data, features)
# FixedLenFeature fields are now ready to use
sz = tf_record['size']
# Typical code for decoding compressed images
image = tf.io.decode_jpeg(tf_record['image'], channels=3)
# VarLenFeature fields require additional sparse.to_dense decoding
float_data = tf.sparse.to_dense(tf_record['float_data'])
return image, sz, float_data
# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)
Fragmentos de código útiles:
leer elementos de datos únicos
tf.io.FixedLenFeature([], tf.string) # for one byte string
tf.io.FixedLenFeature([], tf.int64) # for one int
tf.io.FixedLenFeature([], tf.float32) # for one float
leyendo listas de elementos de tamaño fijo
tf.io.FixedLenFeature([N], tf.string) # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64) # list of N ints
tf.io.FixedLenFeature([N], tf.float32) # list of N floats
Leer una cantidad variable de elementos de datos
tf.io.VarLenFeature(tf.string) # list of byte strings
tf.io.VarLenFeature(tf.int64) # list of ints
tf.io.VarLenFeature(tf.float32) # list of floats
Un VarLenFeature muestra un vector disperso y se requiere un paso adicional después de decodificar el TFRecord:
dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])
También es posible tener campos opcionales en TFRecords. Si especificas un valor predeterminado cuando lees un campo, se muestra el valor predeterminado en lugar de un error si falta el campo.
tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional
Temas abordados
- 🤔 Fragmentación de archivos de datos para un acceso rápido desde GCS
- 😓 cómo escribir TFRecords. (¿Ya olvidaste la sintaxis? Está bien, agregue esta página a favoritos como hoja de referencia).
- 🤔 cargando un conjunto de datos de TFRecords con TFRecordDataset
Tómate un momento para repasar esta lista de tareas mentalmente.
6. [INFO] Nociones básicas del clasificador de 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 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, ... )
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 datos de entrada y envía sus salidas a otras capas. Se llama “densa” porque cada neurona está conectada a todas las neuronas de la capa anterior.
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, se desconocen los pesos y el sesgo. Se inicializarán de forma aleatoria y se “aprenderán” entrenando la red neuronal con muchos datos conocidos.
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 de softmax
La red anterior termina con una capa de 5 neuronas porque clasificamos las flores en 5 categorías (rosa, tulipán, diente de león, margarita y 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 esto, usaremos una función de activación denominada “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.
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, 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 la más eficaz. La llamaremos nuestra función de error o “pérdida”:
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 en relación con todos los pesos y sesgos, obtenemos un "gradiente", calculado para una imagen, una etiqueta y un valor actual de pesos y sesgos determinados. Recuerda que podemos tener millones de pesos y sesgos, por lo que calcular el gradiente parece ser un trabajo muy arduo. Afortunadamente, Tensorflow lo hace por nosotros. La propiedad matemática de un gradiente es que apunta "hacia arriba". Como queremos ir a 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. Con suerte, esto convergerá en un lugar donde la entropía cruzada sea mínima, aunque nada garantiza que este mínimo sea único.
Minilotes y momento
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, proporciona un gradiente que representa mejor las restricciones que imponen 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 estocástico del gradiente", tiene otro beneficio más pragmático: trabajar con lotes también significa trabajar con matrices más grandes, que suelen ser más fáciles de optimizar en GPUs y TPU.
Sin embargo, la convergencia puede ser un poco caótica y puede detenerse si el vector de gradiente es todo ceros. ¿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 silla son bastante comunes y no queremos detenernos en ellos.
Ilustración: un punto silla. 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 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 de entrenamiento y etiquetas. Esto ayuda a que el algoritmo converja. La dimensión “lote” suele ser la primera 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".
Etiquetas: Otro nombre para las "clases" o respuestas correctas en un problema de clasificación supervisada.
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 llaman “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. Se acortó "Neuron outputs before logistic function" a "logits".
pérdida: la función de error que compara las salidas de la red neuronal con las respuestas correctas
Neurona: 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 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.
7. Aprendizaje por transferencia
Para un problema de clasificación de imágenes, es probable que las capas densas no sean suficientes. Tenemos que aprender sobre las capas convolucionales y las muchas formas en que puedes organizarlas.
Pero también podemos usar un atajo. Hay redes neuronales convolucionales completamente entrenadas disponibles para descargar. Es posible cortar su última capa, la cabeza de clasificación softmax, y reemplazarla por la tuya. Todos los pesos y sesgos entrenados permanecen como están, solo se vuelve a entrenar la capa de softmax que agregas. Esta técnica se denomina aprendizaje transferible y, sorprendentemente, funciona siempre que el conjunto de datos en el que se entrenó previamente la red neuronal esté "lo suficientemente cerca" del tuyo.
Prácticas
Abre el siguiente notebook, ejecuta las celdas (Mayúsculas-Intro) y sigue las instrucciones cuando veas la etiqueta “TRABAJO OBLIGATORIO”.
Keras Flowers transfer learning (playground).ipynb
Información adicional
Con el aprendizaje por transferencia, te beneficias de las arquitecturas avanzadas de redes neuronales convolucionales desarrolladas por los mejores investigadores y del entrenamiento previo en un enorme conjunto de datos de imágenes. En nuestro caso, transferiremos el aprendizaje de una red entrenada en ImageNet, una base de datos de imágenes que contiene muchas plantas y escenas al aire libre, que es lo suficientemente cercana a las flores.
Ilustración: usar una red neuronal convolucional compleja, ya entrenada, como una caja negra y volver a entrenar solo la cabeza de clasificación. Esto es aprendizaje por transferencia. Más adelante, veremos cómo funcionan estos arreglos complicados de capas convolucionales. Por ahora, el problema es de otra persona.
Aprendizaje por transferencia en Keras
En Keras, puedes crear una instancia de un modelo previamente entrenado a partir de la colección tf.keras.applications.*
. Por ejemplo, MobileNet V2 es una arquitectura de convoluciones muy buena que mantiene un tamaño razonable. Si seleccionas include_top=False
, obtienes el modelo previamente entrenado sin su capa softmax final para que puedas agregar la tuya:
pretrained_model = tf.keras.applications.MobileNetV2(input_shape=[*IMAGE_SIZE, 3], include_top=False)
pretrained_model.trainable = False
model = tf.keras.Sequential([
pretrained_model,
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(5, activation='softmax')
])
Observa también la configuración de pretrained_model.trainable = False
. Congelan las ponderaciones y los sesgos del modelo previamente entrenado para que solo entrenes tu capa softmax. Esto suele implicar relativamente pocas ponderaciones y se puede hacer rápidamente y sin la necesidad de un conjunto de datos muy grande. Sin embargo, si tienes muchos datos, el aprendizaje transferible puede funcionar aún mejor con pretrained_model.trainable = True
. Luego, los pesos previamente entrenados proporcionan excelentes valores iniciales y el entrenamiento puede ajustarlos para que se adapten mejor a tu problema.
Por último, observa la capa Flatten()
insertada antes de la capa de softmax densa. Las capas densas funcionan en vectores planos de datos, pero no se sabe si eso es lo que devuelve el modelo previamente entrenado. Por eso, debemos aplanar. En el siguiente capítulo, a medida que profundizamos en las arquitecturas convolucionales, explicaremos el formato de datos que devuelven las capas convolucionales.
Con este enfoque, la exactitud debería ser de casi un 75%.
Solución
Este es el notebook de la solución. Puedes usarla si no puedes avanzar.
Keras Flowers transfer learning (solution).ipynb
Temas abordados
- 🤔 Cómo escribir un clasificador en Keras
- 🤓 configurado con una última capa de softmax y una pérdida de entropía cruzada
- Ж Aprendizaje por transferencia
- 🤔 Entrena tu primer modelo
- 🧐 Seguir su pérdida y precisión durante el entrenamiento
Tómate un momento para revisar esta lista de verificación en tu cabeza.
8. [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 estás comenzando con redes neuronales convolucionales, continúa leyendo.
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'])
Introducción a las redes neuronales convolucionales
En una capa de una red convolucional, una “neurona” realiza una suma ponderada de los píxeles justo 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, 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í, 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 que usa un filtro de 4 × 4 × 3 = 48 pesos.
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.
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.
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)
Ilustración: Deslizar la ventana de procesamiento en 3 píxeles genera 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.
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 convolucional típico puede verse de la siguiente manera:
Ilustración: un clasificador de imágenes que usa capas convolucionales y de softmax. Utiliza filtros de 3 × 3 y 1 × 1. Las capas maxpool toman el máximo de grupos de datos de 2 × 2. La cabeza de clasificación se implementa con una capa densa con activación 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'])
9. Tu convnet personalizado
Actividad práctica
Creemos y entrenemos una red neuronal convolucional desde cero. Usar una TPU nos permitirá iterar muy rápido. Abre el siguiente notebook, ejecuta las celdas (Mayúsculas-Intro) y sigue las instrucciones cuando veas la etiqueta “TRABAJO OBLIGATORIO”.
Keras_Flowers_TPU (playground).ipynb
El objetivo es superar el 75% de exactitud del modelo de aprendizaje por transferencia. Ese modelo tenía una ventaja, ya que se entrenó previamente en un conjunto de datos de millones de imágenes, mientras que aquí solo tenemos 3,670 imágenes. ¿Al menos puedes igualarlo?
Información adicional
¿Cuántas capas? ¿Qué tamaño?
Seleccionar los tamaños de las capas es más un arte que una ciencia. Debes encontrar el equilibrio adecuado entre tener demasiados o muy pocos parámetros (pesos y sesgos). Con muy pocos pesos, la red neuronal no puede representar la complejidad de las formas de las flores. Si hay demasiadas, puede ser propensa a un “sobreajuste”, es decir, especializarse en las imágenes de entrenamiento y no poder generalizar. Con muchos parámetros, el modelo también tardará en entrenarse. En Keras, la función model.summary()
muestra la estructura y el recuento de parámetros de tu modelo:
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 192, 192, 16) 448
_________________________________________________________________
conv2d_1 (Conv2D) (None, 192, 192, 30) 4350
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 96, 96, 60) 16260
_________________________________________________________________
...
_________________________________________________________________
global_average_pooling2d (Gl (None, 130) 0
_________________________________________________________________
dense (Dense) (None, 90) 11790
_________________________________________________________________
dense_1 (Dense) (None, 5) 455
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________
Algunas sugerencias:
- Tener varias capas es lo que hace que las redes neuronales "profundas" sean eficaces. Para este problema simple de reconocimiento de flores, entre 5 y 10 capas son adecuadas.
- Usa filtros pequeños. Por lo general, los filtros de 3 x 3 son buenos en todas partes.
- También se pueden usar filtros de 1 x 1, que son económicos. En realidad, no “filtran” nada, sino que calculan combinaciones lineales de canales. Alternar con filtros reales (Obtén más información sobre las "convoluciones de 1 × 1" en la siguiente sección).
- Para un problema de clasificación como este, realiza una reducción de muestreo frecuente con capas de reducción máxima (o convoluciones con stride >1). No importa dónde está la flor, solo que es una rosa o un diente de león, por lo que perder información sobre X e Y no es importante y filtrar áreas más pequeñas es más económico.
- La cantidad de filtros generalmente se asemeja a la cantidad de clases al final de la red (¿por qué? Consulta el truco de “reducción promedio global” a continuación). Si clasificas en cientos de clases, aumenta el recuento de filtros de forma progresiva en capas consecutivas. Para el conjunto de datos de flores con 5 clases, filtrar con solo 5 filtros no sería suficiente. Puedes utilizar el mismo recuento de filtros en la mayoría de las capas, por ejemplo, 32, y disminuirlo hacia el final.
- Las capas densas finales son costosas. Pueden tener más pesos que todas las capas convolucionales combinadas. Por ejemplo, incluso con una salida muy razonable del último cubo de datos de 24 × 24 × 10 datos, una capa densa de 100 neuronas costaría 24 × 24 × 10 × 100=576,000 pesos. Intenta ser cuidadoso o prueba la reducción promedio global (consulta a continuación).
Agrupación global promedio
En lugar de usar una capa densa costosa al final de una red neuronal convolucional, puedes dividir el "cubo" de datos entrantes en tantas partes como tengas clases, promediar sus valores y pasarlos a través de una función de activación softmax. Esta forma de compilar el cabezal de clasificación cuesta 0 pesos. En Keras, la sintaxis es tf.keras.layers.GlobalAveragePooling2D().
.
Solución
Este es el notebook de la solución. Puedes usarla si no puedes avanzar.
Keras_Flowers_TPU (solution).ipynb
Temas abordados
- 🤔 Se jugaron con capas convolucionales
- 🤓 Experimentaste con la agrupación máxima, los pasos, la agrupación global promedio, etcétera.
- 😀 iteró en un modelo del mundo real rápidamente, en TPU
Tómate un momento para repasar esta lista de tareas mentalmente.
10. [INFO] Arquitecturas convolucionales modernas
En pocas palabras
Ilustración: un "módulo" de convolución. ¿Qué es lo mejor en este punto? ¿Una capa de grupo máximo seguida de una capa convolucional de 1 x 1 o una combinación diferente de capas? Pruébalos todos, concatena los resultados y deja que la red decida. A la derecha: la arquitectura de convoluciones "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)
Otros trucos baratos
Filtros pequeños de 3 × 3
En esta ilustración, se muestra el resultado de dos filtros 3 × 3 consecutivos. Intenta rastrear qué datos contribuyeron al resultado: estos dos filtros consecutivos de 3×3 calculan alguna combinación de una región de 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.
¿Convoluciones de 1 × 1?
En términos matemáticos, una convolución "1 × 1" 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 x 1" calcula una suma ponderada de una columna de datos de 1 x 1 (ver la ilustración) y, a medida que la deslizas entre 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 de eso, las capas de 1 x 1 usan menos pesos.
11. Squeezenet
En el artículo "Squeezenet", se demostró una manera sencilla de aplicar estas ideas. Los autores sugieren un diseño de módulo convolucional muy simple, que usa solo capas convolucionales de 1 x 1 y 3 x 3.
Ilustración: Arquitectura de squeezenet basada en "módulos de fuego". Alternan una capa de 1 x 1 que "comprime" los datos entrantes en la dimensión vertical seguida de dos capas convolucionales paralelas de 1 x 1 y 3 x 3 que "expanden" la profundidad de los datos de nuevo.
Actividad práctica
Continúa en tu notebook anterior y crea una red neuronal convolucional inspirada en SqueezeNet. Deberás cambiar el código del modelo al “estilo funcional” de Keras.
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, luego continúa con "fire_modules
" y cambia con MaxPooling2D(pool_size=2)
capas. Puedes experimentar con 2 a 4 capas de reducción máxima en la red y también con 1, 2 o 3 módulos de activación consecutivos entre el máximo de capas de reducción.
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 ver algunos trucos más sencillos.
Normalización por lotes
La normalización por lotes te ayudará con los problemas de convergencia que tienes. 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 momentum se 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:
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, utilízalo 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.
Una precisión del 80% en 35 ciclos de entrenamiento ahora debería estar al alcance.
Solución
Este es el notebook de la solución. Puedes usarla si no puedes avanzar.
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.
12. Xception ajustado
Convoluciones separables
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 nuevos pesos cada vez, tantas recombinaciones ponderadas de los canales se calculan según sea necesario.
Ilustración: Convoluciones separables. 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 puede repetirse, con nuevas ponderaciones 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 para el aprendizaje por transferencia anteriormente.
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 × 4 × 3 × 5 = 240
Capa convolucional separable: 4 x 4 x 3 + 3 x 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.
Prácticas
Reinicia desde el notebook “aprendizaje por transferencia” de Playground, pero esta vez selecciona Xception como el modelo previamente entrenado. Xception solo usa convolución separable. Deja que todos los pesos se puedan entrenar. Ajustaremos las ponderaciones previamente entrenadas en nuestros datos en lugar de usar las capas previamente entrenadas como tales.
Keras Flowers transfer learning (playground).ipynb
Objetivo: Precisión > 95% (No, 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 fino
Xception está disponible en los modelos previamente entrenados estándar 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 ajustes un modelo, deberás prestar atención a la tasa de aprendizaje y usar un programa de tasa de aprendizaje con un período de aumento. Para ello, puedes escribir lo siguiente:
Comenzar con una tasa de aprendizaje estándar interrumpiría los pesos previamente entrenados del modelo. Iniciarlos de forma progresiva hasta que el modelo se haya unido a tus datos pueda modificarlos de forma 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 con una devolución de llamada en la que puedes calcular la tasa de aprendizaje adecuada para cada ciclo de entrenamiento. 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.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
Temas abordados
- 🤔 Convolución separable de profundidad
- 🤓 Programas de tasas de aprendizaje
- 😈 Ajustar un modelo previamente entrenado
Tómate un momento para repasar esta lista de tareas mentalmente.
13. ¡Felicitaciones!
Creaste tu primera red neuronal convolucional moderna y la entrenaste con una exactitud del 90%, iterando en ejecuciones de entrenamiento sucesivas en solo minutos gracias a las TPU.
TPUs en la práctica
Las TPU y GPU están disponibles en Vertex AI de Google Cloud:
- En VMs de Deep Learning
- En Notebooks de Vertex AI
- En trabajos de Vertex AI Training Jobs
Por último, nos encanta recibir comentarios. Infórmanos si ves algo fuera de lugar en este lab o si crees que se puede mejorar. Los comentarios se pueden enviar a través de los problemas de GitHub [ vínculo de comentarios].
|