Path: blob/master/site/es-419/tutorials/distribute/custom_training.ipynb
25118 views
Copyright 2019 The TensorFlow Authors.
Entrenamiento personalizado con tf.distribute.Strategy
Este tutorial muestra cómo usar tf.distribute.Strategy
, una API de TensorFlow que da una abstracción para distribuir su entrenamiento entre múltiples unidades de procesamiento (GPUs, múltiples máquinas o TPUs), con bucles de entrenamiento personalizados. Aquí entrenarás una red neuronal convolucional sencilla en el conjunto de datos Fashion MNIST, que contiene 70,000 imágenes de tamaño 28 x 28.
Los bucles de entrenamiento personalizados dan flexibilidad y un mayor control sobre el entrenamiento. También facilitan depurar el modelo y el bucle de entrenamiento.
Descargar el conjunto de datos Fashion MNIST
Cree una estrategia para distribuir las variables y el grafo
¿Cómo funciona la estrategia tf.distribute.MirroredStrategy
?
Todas las variables y el grafo del modelo se reproducen en las réplicas.
Las entradas se distribuyen uniformemente entre las réplicas.
Cada réplica calcula la pérdida y los gradientes de la entrada que ha recibido.
Se suman los gradientes de todas las réplicas para sincronizarlos.
Tras la sincronización, se realiza la misma actualización en las copias de las variables de cada réplica.
Nota: Puedes poner todo el código siguiente dentro de un único ámbito. Este ejemplo lo divide en varias celdas de código para ilustrarlo.
Configurar la canalización de entrada
Crea los conjuntos de datos y distribúyelos:
Crear el modelo
Crea un modelo usando tf.keras.Sequential
. También puedes usar la API de subclase de modelos o la API funcional para hacerlo.
Definir la función de pérdida
Recordemos que la función de pérdida consta de una o dos partes:
La pérdida por predicción mide lo alejadas que están las predicciones del modelo de las etiquetas de entrenamiento para un lote de ejemplos de entrenamiento. Se calcula para cada ejemplo etiquetado y luego se reduce para todo el lote calculando el valor promedio.
Opcionalmente, se pueden añadir términos de pérdida por regularización a la pérdida por predicción, para evitar que el modelo se ajuste en exceso a los datos del entrenamiento. Una elección común es la regularización L2, que añade un pequeño múltiplo fijo de la suma de cuadrados de todas las ponderaciones del modelo, independientemente del número de ejemplos. El modelo anterior usa la regularización L2 para demostrar su manejo en el bucle de entrenamiento a continuación.
Para el entrenamiento en una sola máquina con una sola GPU/CPU, esto funciona de la siguiente manera:
La pérdida por predicción se calcula para cada ejemplo del lote, se suma para todo el lote y luego se divide por el tamaño del lote.
La pérdida por regularización se añade a la pérdida por predicción.
El gradiente de la pérdida total se calcula respecto a cada ponderación del modelo, y el optimizador actualiza cada ponderación del modelo a partir del gradiente correspondiente.
Con tf.distribute.Strategy
, el lote de entrada se divide entre las réplicas. Por ejemplo, supongamos que tiene 4 GPUs, cada una con una réplica del modelo. Un lote de 256 ejemplos de entrada se distribuye uniformemente entre las 4 réplicas, por lo que cada réplica recibe un lote de tamaño 64: Tenemos 256 = 4*64
, o en general GLOBAL_BATCH_SIZE = num_replicas_in_sync * BATCH_SIZE_PER_REPLICA
.
Cada réplica calcula la pérdida a partir de los ejemplos de entrenamiento que obtiene y calcula los gradientes de la pérdida respecto a cada ponderación del modelo. El optimizador se encarga de que estos gradientes se sumen en todas las réplicas antes de usarlos para actualizar las copias de las ponderaciones del modelo en cada réplica.
Entonces, ¿cómo se debe calcular la pérdida cuando se usa una tf.distribute.Strategy
?
Cada réplica calcula la pérdida por predicción de todos los ejemplos que se le han distribuido, suma los resultados y los divide entre
num_replicas_in_sync * BATCH_SIZE_PER_REPLICA
, o lo que es lo mismo,GLOBAL_BATCH_SIZE
.Cada réplica computa la(s) pérdida(s) por regularización y las divide entre
num_replicas_in_sync
.
En comparación con el entrenamiento no distribuido, todos los términos de pérdida por cada réplica se reducen en un factor de 1/num_replicas_in_sync
. Por otra parte, todos los términos de pérdida (o mejor dicho, sus gradientes) se suman a lo largo de ese número de réplicas antes de que el optimizador los aplique. En efecto, el optimizador en cada réplica usa los mismos gradientes que si se hubiera producido un cálculo no distribuido con GLOBAL_BATCH_SIZE
. Esto es consistente con el comportamiento distribuido y no distribuido de Model.fit
de Keras. Véase el tutorial Entrenamiento distribuido con Keras sobre cómo un mayor tamaño de lote global permite escalar la tasa de aprendizaje.
¿Cómo hacerlo en TensorFlow?
La reducción de pérdidas y el escalado se realizan automáticamente en
Model.compile
yModel.fit
de KerasSi está escribiendo un bucle de entrenamiento personalizado, como en este tutorial, debe sumar las pérdidas por cada ejemplo y dividir la suma entre el tamaño global del lote utilizando
tf.nn.compute_average_loss
, que toma las pérdidas por cada ejemplo y las ponderaciones opcionales de las muestras como argumentos y devuelve la pérdida escalada.Si se usan las clases
tf.keras.losses
(como en el ejemplo siguiente), es necesario especificar explícitamente que la reducción de pérdidas sea una de las siguientesNONE
oSUM
. Los valores predeterminadosAUTO
ySUM_OVER_BATCH_SIZE
no están permitidos fuera deModel.fit
.AUTO
no está permitido porque el usuario debe pensar explícitamente qué reducción desea para asegurarse de que es correcta en el caso distribuido.SUM_OVER_BATCH_SIZE
no está permitido porque actualmente sólo dividiría entre el tamaño del lote por réplica, y dejaría la división entre el número de réplicas al usuario, lo que podría ser fácil de pasar por alto. Así que, en su lugar, tiene que hacer la reducción usted mismo de forma explícita.
Si está escribiendo un bucle de entrenamiento personalizado para un modelo con una lista no vacía de
Model.losses
(por ejemplo, regularizadores de ponderación), debe sumarlos y dividir la suma entre el número de réplicas. Puede hacerlo usando la funcióntf.nn.scale_regularization_loss
. El propio código del modelo permanece ajeno al número de réplicas.
Sin embargo, los modelos pueden definir pérdidas de regularización dependientes de la entrada con API de Keras como Layer.add_loss(...)
y Layer(activity_regularizer=...)
. Para Layer.add_loss(...)
, corresponde al código de modelado realizar la división de los términos sumados por cada ejemplo entre el tamaño del lote por cada réplica(!), por ejemplo, usando tf.math.reduce_mean()
.
Casos especiales
Los usuarios avanzados también deben tener en cuenta los siguientes casos especiales.
Los lotes de entrada más cortos que
GLOBAL_BATCH_SIZE
crean casos límite desagradables en varios lugares. En la práctica, suele funcionar mejor evitarlos permitiendo que los lotes abarquen los límites de las épocas usandoDataset.repeat().batch()
y definiendo las épocas aproximadas por conteos de pasos, no por los extremos de los conjuntos de datos. Alternativamente,Dataset.batch(drop_remainder=True)
mantiene la noción de época pero elimina algunos de los últimos ejemplos.
Como ilustración, este ejemplo va por el camino más difícil y permite lotes cortos, de modo que cada época de entrenamiento contenga cada ejemplo entrenado exactamente una vez.
¿Qué denominador debe usar tf.nn.compute_average_loss()
?
Ambas opciones son equivalentes si se evitan los lotes cortos, como se ha sugerido anteriormente.
Las
labels
multidimensionales requieren que se promedie laper_example_loss
entre el número de predicciones en cada ejemplo. Considere una tarea de clasificación para todos los pixeles de una imagen de entrada, conpredictions
de forma(batch_size, H, W, n_classes)
ylabels
de forma(batch_size, H, W)
. Tendrá que actualizarper_example_loss
como:per_example_loss /= tf.cast(tf.reduce_prod(tf.shape(labels)[1:]), tf.float32)
Precaución: Verifique la forma de su pérdida. Las funciones de pérdida en tf.losses
/tf.keras.losses
suelen devolver el promedio sobre la última dimensión de la entrada. Las clases de pérdida envuelven estas funciones. Pasar reduction=Reduction.NONE
al crear una instancia de una clase de pérdida significa "ninguna reducción adicional". Para las pérdidas categóricas con una forma de entrada de ejemplo de [batch, W, H, n_clases]
se reduce la dimensión n_clases
. Para las pérdidas puntuales como losses.mean_squared_error
o losses.binary_crossentropy
incluye un eje ficticio de modo que [batch, W, H, 1]
se reduzca a [batch, W, H]
. Sin el eje ficticio, [batch, W, H]
se reducirá incorrectamente a [batch, W]
.
Definir las métricas para controlar las pérdidas y la precisión
Estas métricas realizan un seguimiento de la pérdida por prueba y de la precisión del entrenamiento y de la prueba. Puedes usar .result()
para obtener las estadísticas acumuladas en cualquier momento.
Bucle de entrenamiento
Cosas a notar en el ejemplo anterior:
Iterar sobre el conjunto de datos
train_dist_dataset
ytest_dist_dataset
usando una construcciónfor x in ...
.La pérdida escalada es el valor de retorno del
distributed_train_step
. Este valor se agrega entre réplicas usando la llamadatf.distribute.Strategy.reduce
y luego entre lotes sumando el valor de retorno de las llamadastf.distribute.Strategy.reduce
.tf.keras.Metrics
debe actualizarse dentro detrain_step
ytest_step
que es ejecutado portf.distribute.Strategy.run
.tf.distribute.Strategy.run
devuelve resultados de cada réplica local de la estrategia, y hay varias formas de consumir este resultado. Puedes hacertf.distribute.Strategy.reduce
para obtener un valor agregado. También puedes hacertf.distribute.Strategy.experimental_local_results
para obtener la lista de valores contenidos en el resultado, uno por réplica local.
Restaurar el último punto de verificación y probar
Un modelo verificado con un tf.distribute.Strategy
puede restaurarse con o sin estrategia.
Formas alternativas de iterar sobre un conjunto de datos
Usando iteradores
Si quieres iterar sobre un número determinado de pasos y no sobre todo el conjunto de datos, puedes crear un iterador utilizando la llamada iter
y llamar explícitamente a next
en el iterador. Puedes elegir iterar sobre el conjunto de datos tanto dentro como fuera de la tf.function
. Aquí puedes ver un pequeño fragmento que demuestra la iteración sobre el conjunto de datos fuera de la tf.function
usando un iterador.
Iterar dentro de una tf.function
También puedes iterar sobre toda la entrada train_dist_dataset
dentro de una tf.function
usando la construcción for x in ...
o creando iteradores igual que hiciste antes. El siguiente ejemplo muestra cómo envolver una época de entrenamiento con un decorador @tf.function
e iterar sobre train_dist_dataset
dentro de la función.
Seguimiento de la pérdida por entrenamiento en las réplicas
Nota: Como regla general, debes usar tf.keras.Metrics
para hacer un seguimiento de los valores por muestra y evitar los valores que se hayan agregado dentro de una réplica.
Puesto que se lleva a cabo un cálculo de escalado de pérdidas, no se recomienda usar tf.keras.metrics.Mean
para hacer un seguimiento de la pérdida por entrenamiento en distintas réplicas.
Por ejemplo, si ejecutas un trabajo de entrenamiento con las siguientes características:
Dos réplicas
Se procesan dos muestreos en cada réplica
Valores de pérdida resultantes: [2, 3] y [4, 5] en cada réplica
Tamaño global del lote = 4
Con el escalado de pérdidas, calculas el valor de pérdida por muestra en cada réplica sumando los valores de pérdida y dividiéndolos por el tamaño global del lote. En este caso (2 + 3) / 4 = 1,25
y (4 + 5) / 4 = 2,25
.
Si usas tf.keras.metrics.Mean
para hacer un seguimiento de la pérdida en las dos réplicas, el resultado es distinto. En este ejemplo, al final tienes un total
de 3.50 y un count
de 2, lo que da como resultado total
/count
= 1.75 cuando se llama a result()
en la métrica. La pérdida calculada con tf.keras.Metrics
se escala con un factor adicional que es igual al número de réplicas sincronizadas.
Guía y ejemplos
Estos son algunos ejemplos de cómo usar la estrategia de distribución con bucles de entrenamiento personalizados:
Ejemplo de DenseNet usando
MirroredStrategy
.Ejemplo de BERT entrenado usando
MirroredStrategy
yTPUStrategy
. Este ejemplo es especialmente útil para entender cómo cargar desde un punto de verificación y generar puntos de verificación periódicos durante el entrenamiento distribuido, etc.Ejemplo de NCF entrenado usando
MirroredStrategy
que puede ser habilitado usando la banderakeras_use_ctl
.Ejemplo de NMT entrenado usando
MirroredStrategy
.
Puedes encontrar más ejemplos en Ejemplos y tutoriales la Guía de estrategias de distribución.
Siguientes pasos
Prueba la nueva API
tf.distribute.Strategy
en tus modelos.Visita las guías Mejor rendimiento con
tf.function
y Perfilador TensorFlow para saber más sobre herramientas para optimizar el rendimiento de tus modelos TensorFlow.Consulta la guía Entrenamiento distribuido en TensorFlow, que da una visión general de las estrategias de distribución disponibles.