Ver en TensorFlow.org | Ejecutar en Google Colab | Ver fuente en GitHub | Descargar libreta |
Este cuaderno demuestra cómo depurar la canalización de entrenamiento al migrar a TF2. Consta de los siguientes componentes:
- Pasos sugeridos y ejemplos de código para depurar canalización de entrenamiento
- Herramientas para depurar
- Otros recursos relacionados
Una suposición es que tiene código TF1.x y modelos entrenados para comparar, y desea crear un modelo TF2 que logre una precisión de validación similar.
Este portátil NO cubre problemas de rendimiento de depuración para entrenamiento/velocidad de inferencia o uso de memoria.
Flujo de trabajo de depuración
A continuación, se muestra un flujo de trabajo general para depurar las canalizaciones de entrenamiento de TF2. Tenga en cuenta que no necesita seguir estos pasos en orden. También puede usar un enfoque de búsqueda binaria en el que prueba el modelo en un paso intermedio y reduce el alcance de la depuración.
Corregir errores de compilación y tiempo de ejecución
Validación de pase de avance único (en una guía separada)
una. En un solo dispositivo de CPU
- Verifique que las variables se crean solo una vez
- Compruebe la coincidencia de recuentos, nombres y formas de variables
- Restablecer todas las variables, verificar la equivalencia numérica con toda la aleatoriedad deshabilitada
- Alinee la generación de números aleatorios, verifique la equivalencia numérica en la inferencia
- (Opcional) Verifique que los puntos de control se carguen correctamente y que los modelos TF1.x/TF2 generen una salida idéntica
B. En un único dispositivo GPU/TPU
C. Con estrategias multidispositivo
Validación de equivalencia numérica de entrenamiento de modelos para algunos pasos (ejemplos de código disponibles a continuación)
una. Validación de un solo paso de entrenamiento utilizando datos pequeños y fijos en un solo dispositivo de CPU. Específicamente, verifique la equivalencia numérica para los siguientes componentes
- cálculo de pérdidas
- métrica
- tasa de aprendizaje
- cálculo y actualización de gradientes
B. Verifique las estadísticas después de entrenar 3 o más pasos para verificar los comportamientos del optimizador como el impulso, aún con datos fijos en un solo dispositivo de CPU
C. En un único dispositivo GPU/TPU
D. Con estrategias multidispositivo (consulte la introducción de MultiProcessRunner en la parte inferior)
Pruebas de cobertura de extremo a extremo en conjuntos de datos reales
una. Comprueba los comportamientos de entrenamiento con TensorBoard
- use optimizadores simples, por ejemplo, SGD y estrategias de distribución simples, por ejemplo,
tf.distribute.OneDeviceStrategy
primero - métricas de entrenamiento
- métricas de evaluación
- averiguar cuál es la tolerancia razonable para la aleatoriedad inherente
B. Verifique la equivalencia con el optimizador avanzado/programador de tasa de aprendizaje/estrategias de distribución
C. Comprobar la equivalencia cuando se utiliza precisión mixta
- use optimizadores simples, por ejemplo, SGD y estrategias de distribución simples, por ejemplo,
Puntos de referencia de productos adicionales
Configuración
pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is only available in
# Tensorflow 2.8
pip install -q tf-nightly
Validación de pase de avance único
La validación de pase de avance único, incluida la carga del punto de control, se cubre en una colaboración diferente.
import sys
import unittest
import numpy as np
import tensorflow as tf
import tensorflow.compat.v1 as v1
Validación de la equivalencia numérica del entrenamiento del modelo en unos pocos pasos
Configure la configuración del modelo y prepare un conjunto de datos falso.
params = {
'input_size': 3,
'num_classes': 3,
'layer_1_size': 2,
'layer_2_size': 2,
'num_train_steps': 100,
'init_lr': 1e-3,
'end_lr': 0.0,
'decay_steps': 1000,
'lr_power': 1.0,
}
# make a small fixed dataset
fake_x = np.ones((2, params['input_size']), dtype=np.float32)
fake_y = np.zeros((2, params['num_classes']), dtype=np.int32)
fake_y[0][0] = 1
fake_y[1][1] = 1
step_num = 3
Defina el modelo TF1.x.
# Assume there is an existing TF1.x model using estimator API
# Wrap the model_fn to log necessary tensors for result comparison
class SimpleModelWrapper():
def __init__(self):
self.logged_ops = {}
self.logs = {
'step': [],
'lr': [],
'loss': [],
'grads_and_vars': [],
'layer_out': []}
def model_fn(self, features, labels, mode, params):
out_1 = tf.compat.v1.layers.dense(features, units=params['layer_1_size'])
out_2 = tf.compat.v1.layers.dense(out_1, units=params['layer_2_size'])
logits = tf.compat.v1.layers.dense(out_2, units=params['num_classes'])
loss = tf.compat.v1.losses.softmax_cross_entropy(labels, logits)
# skip EstimatorSpec details for prediction and evaluation
if mode == tf.estimator.ModeKeys.PREDICT:
pass
if mode == tf.estimator.ModeKeys.EVAL:
pass
assert mode == tf.estimator.ModeKeys.TRAIN
global_step = tf.compat.v1.train.get_or_create_global_step()
lr = tf.compat.v1.train.polynomial_decay(
learning_rate=params['init_lr'],
global_step=global_step,
decay_steps=params['decay_steps'],
end_learning_rate=params['end_lr'],
power=params['lr_power'])
optmizer = tf.compat.v1.train.GradientDescentOptimizer(lr)
grads_and_vars = optmizer.compute_gradients(
loss=loss,
var_list=graph.get_collection(
tf.compat.v1.GraphKeys.TRAINABLE_VARIABLES))
train_op = optmizer.apply_gradients(
grads_and_vars,
global_step=global_step)
# log tensors
self.logged_ops['step'] = global_step
self.logged_ops['lr'] = lr
self.logged_ops['loss'] = loss
self.logged_ops['grads_and_vars'] = grads_and_vars
self.logged_ops['layer_out'] = {
'layer_1': out_1,
'layer_2': out_2,
'logits': logits}
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
def update_logs(self, logs):
for key in logs.keys():
model_tf1.logs[key].append(logs[key])
La siguiente clase v1.keras.utils.DeterministicRandomTestTool
proporciona un administrador de contexto scope()
que puede hacer que las operaciones aleatorias con estado utilicen la misma semilla en gráficos/sesiones de TF1 y una ejecución entusiasta.
La herramienta proporciona dos modos de prueba:
-
constant
que usa la misma semilla para cada operación sin importar cuántas veces se haya llamado y, -
num_random_ops
que usa el número de operaciones aleatorias con estado observadas previamente como semilla de operación.
Esto se aplica tanto a las operaciones aleatorias con estado utilizadas para crear e inicializar variables, como a las operaciones aleatorias con estado utilizadas en el cálculo (como las capas de abandono).
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
WARNING:tensorflow:From /tmp/ipykernel_26769/2689227634.py:1: The name tf.keras.utils.DeterministicRandomTestTool is deprecated. Please use tf.compat.v1.keras.utils.DeterministicRandomTestTool instead.
Ejecute el modelo TF1.x en modo gráfico. Recopile estadísticas para los primeros 3 pasos de entrenamiento para la comparación de equivalencia numérica.
with random_tool.scope():
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
model_tf1 = SimpleModelWrapper()
# build the model
inputs = tf.compat.v1.placeholder(tf.float32, shape=(None, params['input_size']))
labels = tf.compat.v1.placeholder(tf.float32, shape=(None, params['num_classes']))
spec = model_tf1.model_fn(inputs, labels, tf.estimator.ModeKeys.TRAIN, params)
train_op = spec.train_op
sess.run(tf.compat.v1.global_variables_initializer())
for step in range(step_num):
# log everything and update the model for one step
logs, _ = sess.run(
[model_tf1.logged_ops, train_op],
feed_dict={inputs: fake_x, labels: fake_y})
model_tf1.update_logs(logs)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead. /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/core.py:261: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead. return layer.apply(inputs) /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead. from ipykernel import kernelapp as app /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead. app.launch_new_instance()
Definir el modelo TF2.
class SimpleModel(tf.keras.Model):
def __init__(self, params, *args, **kwargs):
super(SimpleModel, self).__init__(*args, **kwargs)
# define the model
self.dense_1 = tf.keras.layers.Dense(params['layer_1_size'])
self.dense_2 = tf.keras.layers.Dense(params['layer_2_size'])
self.out = tf.keras.layers.Dense(params['num_classes'])
learning_rate_fn = tf.keras.optimizers.schedules.PolynomialDecay(
initial_learning_rate=params['init_lr'],
decay_steps=params['decay_steps'],
end_learning_rate=params['end_lr'],
power=params['lr_power'])
self.optimizer = tf.keras.optimizers.SGD(learning_rate_fn)
self.compiled_loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
self.logs = {
'lr': [],
'loss': [],
'grads': [],
'weights': [],
'layer_out': []}
def call(self, inputs):
out_1 = self.dense_1(inputs)
out_2 = self.dense_2(out_1)
logits = self.out(out_2)
# log output features for every layer for comparison
layer_wise_out = {
'layer_1': out_1,
'layer_2': out_2,
'logits': logits}
self.logs['layer_out'].append(layer_wise_out)
return logits
def train_step(self, data):
x, y = data
with tf.GradientTape() as tape:
logits = self(x)
loss = self.compiled_loss(y, logits)
grads = tape.gradient(loss, self.trainable_weights)
# log training statistics
step = self.optimizer.iterations.numpy()
self.logs['lr'].append(self.optimizer.learning_rate(step).numpy())
self.logs['loss'].append(loss.numpy())
self.logs['grads'].append(grads)
self.logs['weights'].append(self.trainable_weights)
# update model
self.optimizer.apply_gradients(zip(grads, self.trainable_weights))
return
Ejecute el modelo TF2 en modo ansioso. Recopile estadísticas para los primeros 3 pasos de entrenamiento para la comparación de equivalencia numérica.
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
model_tf2 = SimpleModel(params)
for step in range(step_num):
model_tf2.train_step([fake_x, fake_y])
Compare la equivalencia numérica de los primeros pasos de entrenamiento.
También puede consultar el cuaderno Validación de corrección y equivalencia numérica para obtener consejos adicionales sobre equivalencia numérica.
np.testing.assert_allclose(model_tf1.logs['lr'], model_tf2.logs['lr'])
np.testing.assert_allclose(model_tf1.logs['loss'], model_tf2.logs['loss'])
for step in range(step_num):
for name in model_tf1.logs['layer_out'][step]:
np.testing.assert_allclose(
model_tf1.logs['layer_out'][step][name],
model_tf2.logs['layer_out'][step][name])
Pruebas unitarias
Hay algunos tipos de pruebas unitarias que pueden ayudar a depurar su código de migración.
- Validación de pase de avance único
- Validación de la equivalencia numérica del entrenamiento del modelo en unos pocos pasos
- Rendimiento de inferencia de referencia
- El modelo entrenado hace predicciones correctas en puntos de datos fijos y simples
Puede usar @parameterized.parameters
para probar modelos con diferentes configuraciones. Detalles con ejemplo de código .
Tenga en cuenta que es posible ejecutar API de sesión y ejecución ansiosa en el mismo caso de prueba. Los fragmentos de código a continuación muestran cómo.
import unittest
class TestNumericalEquivalence(unittest.TestCase):
# copied from code samples above
def setup(self):
# record statistics for 100 training steps
step_num = 100
# setup TF 1 model
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
# run TF1.x code in graph mode with context management
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
self.model_tf1 = SimpleModelWrapper()
# build the model
inputs = tf.compat.v1.placeholder(tf.float32, shape=(None, params['input_size']))
labels = tf.compat.v1.placeholder(tf.float32, shape=(None, params['num_classes']))
spec = self.model_tf1.model_fn(inputs, labels, tf.estimator.ModeKeys.TRAIN, params)
train_op = spec.train_op
sess.run(tf.compat.v1.global_variables_initializer())
for step in range(step_num):
# log everything and update the model for one step
logs, _ = sess.run(
[self.model_tf1.logged_ops, train_op],
feed_dict={inputs: fake_x, labels: fake_y})
self.model_tf1.update_logs(logs)
# setup TF2 model
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
self.model_tf2 = SimpleModel(params)
for step in range(step_num):
self.model_tf2.train_step([fake_x, fake_y])
def test_learning_rate(self):
np.testing.assert_allclose(
self.model_tf1.logs['lr'],
self.model_tf2.logs['lr'])
def test_training_loss(self):
# adopt different tolerance strategies before and after 10 steps
first_n_step = 10
# abosolute difference is limited below 1e-5
# set `equal_nan` to be False to detect potential NaN loss issues
abosolute_tolerance = 1e-5
np.testing.assert_allclose(
actual=self.model_tf1.logs['loss'][:first_n_step],
desired=self.model_tf2.logs['loss'][:first_n_step],
atol=abosolute_tolerance,
equal_nan=False)
# relative difference is limited below 5%
relative_tolerance = 0.05
np.testing.assert_allclose(self.model_tf1.logs['loss'][first_n_step:],
self.model_tf2.logs['loss'][first_n_step:],
rtol=relative_tolerance,
equal_nan=False)
Herramientas de depuración
tf.imprimir
tf.print frente a print/logging.info
- Con argumentos configurables,
tf.print
puede mostrar recursivamente los primeros y últimos elementos de cada dimensión para tensores impresos. Consulte los documentos de la API para obtener más información. - Para una ejecución rápida, tanto
print
comotf.print
imprimen el valor del tensor. Peroprint
puede implicar una copia de dispositivo a host, lo que potencialmente puede ralentizar su código. - Para el modo gráfico, incluido el uso dentro de
tf.function
, debe usartf.print
para imprimir el valor real del tensor.tf.print
se compila en una operación en el gráfico, mientras queprint
ylogging.info
solo se registran en el momento del rastreo, que a menudo no es lo que desea. -
tf.print
también admite la impresión de tensores compuestos comotf.RaggedTensor
ytf.sparse.SparseTensor
. - También puede usar una devolución de llamada para monitorear métricas y variables. Verifique cómo usar devoluciones de llamada personalizadas con registros dict y atributo self.model .
tf.print vs print dentro de tf.function
# `print` prints info of tensor object
# `tf.print` prints the tensor value
@tf.function
def dummy_func(num):
num += 1
print(num)
tf.print(num)
return num
_ = dummy_func(tf.constant([1.0]))
# Output:
# Tensor("add:0", shape=(1,), dtype=float32)
# [2]
Tensor("add:0", shape=(1,), dtype=float32) [2]
tf.distribuir.Estrategia
- Si la función
tf.print
tf.function
ejecuta en los trabajadores, por ejemplo, al usarTPUStrategy
oParameterServerStrategy
, debe verificar los registros del servidor de parámetros/trabajadores para encontrar los valores impresos. - Para
print
ologging.info
, los registros se imprimirán en el coordinador cuando se useParameterServerStrategy
, y los registros se imprimirán en STDOUT en worker0 cuando se usen TPU.
tf.keras.modelo
- Al usar modelos API funcionales y secuenciales, si desea imprimir valores, por ejemplo, entradas de modelo o características intermedias después de algunas capas, tiene las siguientes opciones.
- Escriba una capa personalizada que
tf.print
las entradas. - Incluya las salidas intermedias que desea inspeccionar en las salidas del modelo.
- Escriba una capa personalizada que
- Las capas
tf.keras.layers.Lambda
tienen limitaciones de (des)serialización. Para evitar problemas de carga de puntos de control, escriba una capa subclase personalizada en su lugar. Consulte los documentos de la API para obtener más detalles. - No puede
tf.print
salidas intermedias en untf.keras.callbacks.LambdaCallback
si no tiene acceso a los valores reales, sino solo a los objetos de tensor de Keras simbólicos.
Opción 1: escribir una capa personalizada
class PrintLayer(tf.keras.layers.Layer):
def call(self, inputs):
tf.print(inputs)
return inputs
def get_model():
inputs = tf.keras.layers.Input(shape=(1,))
out_1 = tf.keras.layers.Dense(4)(inputs)
out_2 = tf.keras.layers.Dense(1)(out_1)
# use custom layer to tf.print intermediate features
out_3 = PrintLayer()(out_2)
model = tf.keras.Model(inputs=inputs, outputs=out_3)
return model
model = get_model()
model.compile(optimizer="adam", loss="mse")
model.fit([1, 2, 3], [0.0, 0.0, 1.0])
[[-0.327884018] [-0.109294668] [-0.218589336]] 1/1 [==============================] - 0s 280ms/step - loss: 0.6077 <keras.callbacks.History at 0x7f63d46bf190>
Opción 2: incluya los resultados intermedios que desea inspeccionar en los resultados del modelo.
Tenga en cuenta que, en tal caso, es posible que necesite algunas personalizaciones para usar Model.fit
.
def get_model():
inputs = tf.keras.layers.Input(shape=(1,))
out_1 = tf.keras.layers.Dense(4)(inputs)
out_2 = tf.keras.layers.Dense(1)(out_1)
# include intermediate values in model outputs
model = tf.keras.Model(
inputs=inputs,
outputs={
'inputs': inputs,
'out_1': out_1,
'out_2': out_2})
return model
pdb
Puede usar pdb tanto en la terminal como en Colab para inspeccionar los valores intermedios para la depuración.
Visualizar gráfico con TensorBoard
Puede examinar el gráfico de TensorFlow con TensorBoard . TensorBoard también es compatible con colab . TensorBoard es una gran herramienta para visualizar resúmenes. Puede usarlo para comparar la tasa de aprendizaje, los pesos del modelo, la escala de gradiente, las métricas de entrenamiento/validación o incluso modelar resultados intermedios entre el modelo TF1.x y el modelo TF2 migrado a través del proceso de entrenamiento y ver si los valores se ven como se esperaba.
Perfilador TensorFlow
TensorFlow Profiler puede ayudarlo a visualizar la línea de tiempo de ejecución en GPU/TPU. Puede consultar esta demostración de Colab para conocer su uso básico.
Ejecutor multiproceso
MultiProcessRunner es una herramienta útil al depurar con MultiWorkerMirroredStrategy y ParameterServerStrategy. Puede echar un vistazo a este ejemplo concreto para su uso.
Específicamente para los casos de estas dos estrategias, se recomienda 1) no solo tener pruebas unitarias para cubrir su flujo, 2) sino también intentar reproducir fallas usándolas en pruebas unitarias para evitar iniciar un trabajo distribuido real cada vez que intentan un arreglo.