Voir sur TensorFlow.org | Exécuter dans Google Colab | Voir la source sur GitHub | Télécharger le cahier |
Ce notebook montre comment déboguer le pipeline de formation lors de la migration vers TF2. Il se compose des composants suivants :
- Étapes suggérées et exemples de code pour le débogage du pipeline de formation
- Outils de débogage
- Autres ressources connexes
Une hypothèse est que vous avez du code TF1.x et des modèles entraînés à comparer, et que vous souhaitez créer un modèle TF2 qui atteint une précision de validation similaire.
Ce bloc-notes ne couvre PAS les problèmes de performances de débogage pour la vitesse d'entraînement/d'inférence ou l'utilisation de la mémoire.
Flux de travail de débogage
Vous trouverez ci-dessous un flux de travail général pour le débogage de vos pipelines de formation TF2. Notez que vous n'avez pas besoin de suivre ces étapes dans l'ordre. Vous pouvez également utiliser une approche de recherche binaire dans laquelle vous testez le modèle dans une étape intermédiaire et réduisez la portée du débogage.
Correction des erreurs de compilation et d'exécution
Validation de passage unique (dans un guide séparé)
une. Sur un seul appareil CPU
- Vérifier que les variables ne sont créées qu'une seule fois
- Vérifiez que le nombre, les noms et les formes des variables correspondent
- Réinitialiser toutes les variables, vérifier l'équivalence numérique avec tout caractère aléatoire désactivé
- Aligner la génération de nombres aléatoires, vérifier l'équivalence numérique dans l'inférence
- (Facultatif) Vérifiez que les points de contrôle sont chargés correctement et que les modèles TF1.x/TF2 génèrent une sortie identique
b. Sur un seul appareil GPU/TPU
c. Avec des stratégies multi-appareils
Validation de l'équivalence numérique d'entraînement du modèle pour quelques étapes (exemples de code disponibles ci-dessous)
une. Validation d'une seule étape de formation à l'aide de petites données fixes sur un seul processeur. Plus précisément, vérifiez l'équivalence numérique pour les composants suivants
- calcul des pertes
- métrique
- taux d'apprentissage
- calcul et mise à jour du gradient
b. Vérifiez les statistiques après avoir entraîné 3 étapes ou plus pour vérifier les comportements de l'optimiseur comme l'élan, toujours avec des données fixes sur un seul processeur
c. Sur un seul appareil GPU/TPU
ré. Avec des stratégies multi-appareils (consultez l'intro de MultiProcessRunner en bas)
Test de couverture de bout en bout sur un jeu de données réel
une. Vérifier les comportements d'entraînement avec TensorBoard
- utilisez d'abord des optimiseurs simples, par exemple SGD, et des stratégies de distribution simples, par exemple
tf.distribute.OneDeviceStrategy
- métriques de formation
- mesures d'évaluation
- déterminer quelle est la tolérance raisonnable pour le caractère aléatoire inhérent
b. Vérifier l'équivalence avec l'optimiseur avancé/programmateur de taux d'apprentissage/stratégies de distribution
c. Vérifier l'équivalence lors de l'utilisation d'une précision mixte
- utilisez d'abord des optimiseurs simples, par exemple SGD, et des stratégies de distribution simples, par exemple
Benchmarks produits supplémentaires
Installer
pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is only available in
# Tensorflow 2.8
pip install -q tf-nightly
Validation d'un seul passage vers l'avant
La validation de passage unique, y compris le chargement des points de contrôle, est couverte dans un colab différent.
import sys
import unittest
import numpy as np
import tensorflow as tf
import tensorflow.compat.v1 as v1
Validation d'équivalence numérique d'entraînement de modèle pour quelques étapes
Configurez la configuration du modèle et préparez un faux ensemble de données.
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
Définir le modèle 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 classe v1.keras.utils.DeterministicRandomTestTool
suivante fournit un gestionnaire de contexte scope()
qui peut faire en sorte que les opérations aléatoires avec état utilisent la même graine sur les graphes/sessions TF1 et l'exécution impatiente,
L'outil propose deux modes de test :
-
constant
qui utilise la même graine pour chaque opération, quel que soit le nombre de fois qu'elle a été appelée et, -
num_random_ops
qui utilise le nombre d'opérations aléatoires avec état précédemment observées comme graine d'opération.
Cela s'applique à la fois aux opérations aléatoires avec état utilisées pour créer et initialiser des variables, et aux opérations aléatoires avec état utilisées dans le calcul (comme pour les couches d'abandon).
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.
Exécutez le modèle TF1.x en mode graphique. Recueillir des statistiques pour les 3 premières étapes de formation pour la comparaison d'équivalence numérique.
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()
Définir le modèle 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
Exécutez le modèle TF2 en mode impatient. Recueillir des statistiques pour les 3 premières étapes de formation pour la comparaison d'équivalence numérique.
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])
Comparez l'équivalence numérique pour les premières étapes de formation.
Vous pouvez également consulter le cahier Validation de l'exactitude et de l'équivalence numérique pour obtenir des conseils supplémentaires sur l'équivalence numérique.
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])
Tests unitaires
Il existe quelques types de tests unitaires qui peuvent aider à déboguer votre code de migration.
- Validation d'un seul passage vers l'avant
- Validation d'équivalence numérique d'entraînement de modèle pour quelques étapes
- Performances d'inférence de référence
- Le modèle formé fait des prédictions correctes sur des points de données fixes et simples
Vous pouvez utiliser @parameterized.parameters
pour tester des modèles avec différentes configurations. Détails avec exemple de code .
Notez qu'il est possible d'exécuter des API de session et une exécution hâtive dans le même cas de test. Les extraits de code ci-dessous montrent comment.
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)
Outils de débogage
tf.print
tf.print vs print/logging.info
- Avec des arguments configurables,
tf.print
peut afficher de manière récursive les premiers et derniers éléments de chaque dimension pour les tenseurs imprimés. Consultez la documentation de l' API pour plus de détails. - Pour une exécution rapide,
print
ettf.print
impriment la valeur du tenseur. Mais l'print
peut impliquer une copie de périphérique à hôte, ce qui peut potentiellement ralentir votre code. - Pour le mode graphique, y compris l'utilisation dans
tf.function
, vous devez utilisertf.print
pour imprimer la valeur réelle du tenseur.tf.print
est compilé dans un op dans le graphique, alors queprint
etlogging.info
ne se connectent qu'au moment du traçage, ce qui n'est souvent pas ce que vous voulez. -
tf.print
prend également en charge l'impression de tenseurs composites tels quetf.RaggedTensor
ettf.sparse.SparseTensor
. - Vous pouvez également utiliser un rappel pour surveiller les métriques et les variables. Veuillez vérifier comment utiliser les rappels personnalisés avec les journaux dict et l'attribut self.model .
tf.print vs impression à l'intérieur 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.distribute.Strategy
- Si la
tf.function
contenanttf.print
est exécutée sur les nœuds de calcul, par exemple lors de l'utilisationTPUStrategy
ouParameterServerStrategy
, vous devez vérifier les journaux du serveur de nœuds de calcul/de paramètres pour trouver les valeurs imprimées. - Pour
print
oulogging.info
, les journaux seront imprimés sur le coordinateur lors de l'utilisation deParameterServerStrategy
, et les journaux seront imprimés sur STDOUT sur worker0 lors de l'utilisation de TPU.
tf.keras.Modèle
- Lorsque vous utilisez des modèles d'API séquentiels et fonctionnels, si vous souhaitez imprimer des valeurs, par exemple, des entrées de modèle ou des fonctionnalités intermédiaires après certaines couches, vous disposez des options suivantes.
- Écrivez un calque personnalisé qui
tf.print
les entrées. - Incluez les sorties intermédiaires que vous souhaitez inspecter dans les sorties du modèle.
- Écrivez un calque personnalisé qui
- Les couches
tf.keras.layers.Lambda
ont des limitations de (dé)sérialisation. Pour éviter les problèmes de chargement des points de contrôle, écrivez plutôt une couche de sous-classe personnalisée. Consultez la documentation de l' API pour plus de détails. - Vous ne pouvez pas
tf.print
sorties intermédiaires dans untf.keras.callbacks.LambdaCallback
si vous n'avez pas accès aux valeurs réelles, mais uniquement aux objets tenseurs Keras symboliques.
Option 1 : écrire un calque personnalisé
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>
Option 2 : incluez les sorties intermédiaires que vous souhaitez inspecter dans les sorties du modèle.
Notez que dans ce cas, vous aurez peut-être besoin de certaines personnalisations pour utiliser 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
l'APB
Vous pouvez utiliser pdb à la fois dans le terminal et Colab pour inspecter les valeurs intermédiaires pour le débogage.
Visualiser le graphique avec TensorBoard
Vous pouvez examiner le graphique TensorFlow avec TensorBoard . TensorBoard est également pris en charge sur colab . TensorBoard est un excellent outil pour visualiser des résumés. Vous pouvez l'utiliser pour comparer le taux d'apprentissage, les poids du modèle, l'échelle de gradient, les métriques d'entraînement/validation, ou même les sorties intermédiaires du modèle entre le modèle TF1.x et le modèle TF2 migré tout au long du processus d'entraînement et voir si les valeurs ressemblent aux attentes.
Profileur TensorFlow
TensorFlow Profiler peut vous aider à visualiser la chronologie d'exécution sur les GPU/TPU. Vous pouvez consulter cette démo Colab pour son utilisation de base.
MultiProcessRunner
MultiProcessRunner est un outil utile lors du débogage avec MultiWorkerMirroredStrategy et ParameterServerStrategy. Vous pouvez jeter un oeil à cet exemple concret pour son utilisation.
Spécifiquement pour les cas de ces deux stratégies, il est recommandé 1) non seulement d'avoir des tests unitaires pour couvrir leur flux, 2) mais aussi d'essayer de reproduire les échecs en l'utilisant dans les tests unitaires pour éviter de lancer de vrais travaux distribués à chaque fois qu'ils tentent une réparation.