Cette page a été traduite par l'API Cloud Translation.
Switch to English

Exécution avide

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher la source sur GitHub Télécharger le carnet

L'exécution rapide de TensorFlow est un environnement de programmation impératif qui évalue les opérations immédiatement, sans créer de graphiques: les opérations renvoient des valeurs concrètes au lieu de construire un graphe de calcul à exécuter plus tard. Cela facilite la mise en route avec TensorFlow et les modèles de débogage, et réduit également le passe-partout. Pour suivre ce guide, exécutez les exemples de code ci-dessous dans un interpréteur python interactif.

Eager execution est une plate-forme d'apprentissage automatique flexible pour la recherche et l'expérimentation, offrant:

  • Une interface intuitive - Structurez votre code naturellement et utilisez les structures de données Python. Répétez rapidement sur les petits modèles et les petites données.
  • Débogage plus facile - Appelez directement les opérations pour inspecter les modèles en cours d'exécution et tester les modifications. Utilisez les outils de débogage Python standard pour un rapport d'erreur immédiat.
  • Flux de contrôle naturel: utilisez le flux de contrôle Python au lieu du flux de contrôle graphique, ce qui simplifie la spécification des modèles dynamiques.

L'exécution rapide prend en charge la plupart des opérations TensorFlow et l'accélération GPU.

Configuration et utilisation de base

import os

import tensorflow as tf

import cProfile

Dans Tensorflow 2.0, l'exécution hâtive est activée par défaut.

tf.executing_eagerly()
True

Vous pouvez maintenant exécuter des opérations TensorFlow et les résultats seront renvoyés immédiatement:

x = [[2.]]
m = tf.matmul(x, x)
print("hello, {}".format(m))
hello, [[4.]]

L'activation d'une exécution rapide modifie le comportement des opérations TensorFlow. Désormais, elles évaluent et renvoient immédiatement leurs valeurs à Python. tf.Tensor objets tf.Tensor référence à des valeurs concrètes au lieu de poignées symboliques vers des nœuds dans un graphe de calcul. Puisqu'il n'y a pas de graphe de calcul à construire et à exécuter plus tard dans une session, il est facile d'inspecter les résultats en utilisant print() ou un débogueur. L'évaluation, l'impression et la vérification des valeurs de tenseur n'interrompent pas le processus de calcul des gradients.

L'exécution impatiente fonctionne bien avec NumPy . Les opérations NumPy acceptent les arguments tf.Tensor . Les opérations tf.math TensorFlow convertissent les objets Python et les tableaux NumPy en objets tf.Tensor . La méthode tf.Tensor.numpy renvoie la valeur de l'objet sous la forme d'un ndarray NumPy.

a = tf.constant([[1, 2],
                 [3, 4]])
print(a)
tf.Tensor(
[[1 2]
 [3 4]], shape=(2, 2), dtype=int32)

# Broadcasting support
b = tf.add(a, 1)
print(b)
tf.Tensor(
[[2 3]
 [4 5]], shape=(2, 2), dtype=int32)

# Operator overloading is supported
print(a * b)
tf.Tensor(
[[ 2  6]
 [12 20]], shape=(2, 2), dtype=int32)

# Use NumPy values
import numpy as np

c = np.multiply(a, b)
print(c)
[[ 2  6]
 [12 20]]

# Obtain numpy value from a tensor:
print(a.numpy())
# => [[1 2]
#     [3 4]]
[[1 2]
 [3 4]]

Flux de contrôle dynamique

Un avantage majeur d'une exécution rapide est que toutes les fonctionnalités du langage hôte sont disponibles pendant l'exécution de votre modèle. Ainsi, par exemple, il est facile d'écrire fizzbuzz :

def fizzbuzz(max_num):
  counter = tf.constant(0)
  max_num = tf.convert_to_tensor(max_num)
  for num in range(1, max_num.numpy()+1):
    num = tf.constant(num)
    if int(num % 3) == 0 and int(num % 5) == 0:
      print('FizzBuzz')
    elif int(num % 3) == 0:
      print('Fizz')
    elif int(num % 5) == 0:
      print('Buzz')
    else:
      print(num.numpy())
    counter += 1
fizzbuzz(15)
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

Cela a des conditions qui dépendent des valeurs du tenseur et il imprime ces valeurs au moment de l'exécution.

Entraînement avide

Calcul des gradients

La différenciation automatique est utile pour mettre en œuvre des algorithmes d'apprentissage automatique tels que la rétropropagation pour la formation des réseaux de neurones. Pendant une exécutiontf.GradientTape , utiliseztf.GradientTape pour tracer les opérations pour calculer les gradients ultérieurement.

Vous pouvez utilisertf.GradientTape pour entraîner et / ou calculer des dégradés avec impatience. Il est particulièrement utile pour les boucles d'entraînement complexes.

Etant donné que différentes opérations peuvent se produire pendant chaque appel, toutes les opérations de passage avant sont enregistrées sur une "bande". Pour calculer le dégradé, lisez la bande à l'envers, puis jetez-la. Untf.GradientTape particuliertf.GradientTape peut calculer qu'un seul gradient; les appels suivants génèrent une erreur d'exécution.

w = tf.Variable([[1.0]])
with tf.GradientTape() as tape:
  loss = w * w

grad = tape.gradient(loss, w)
print(grad)  # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)
tf.Tensor([[2.]], shape=(1, 1), dtype=float32)

Former un mannequin

L'exemple suivant crée un modèle multicouche qui classe les chiffres manuscrits MNIST standard. Il montre l'optimiseur et les API de couches pour créer des graphiques entraînables dans un environnement d'exécution impatient.

# Fetch and format the mnist data
(mnist_images, mnist_labels), _ = tf.keras.datasets.mnist.load_data()

dataset = tf.data.Dataset.from_tensor_slices(
  (tf.cast(mnist_images[...,tf.newaxis]/255, tf.float32),
   tf.cast(mnist_labels,tf.int64)))
dataset = dataset.shuffle(1000).batch(32)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step

# Build the model
mnist_model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu',
                         input_shape=(None, None, 1)),
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])

Même sans formation, appelez le modèle et inspectez la sortie avec impatience:

for images,labels in dataset.take(1):
  print("Logits: ", mnist_model(images[0:1]).numpy())
Logits:  [[ 0.06289896 -0.03877686 -0.07346137 -0.03169462  0.02922358 -0.02436475
  -0.00588411  0.03256026  0.01715117 -0.02714448]]

Alors que les modèles keras ont une boucle d'entraînement intégrée (en utilisant la méthode fit ), vous avez parfois besoin de plus de personnalisation. Voici un exemple, d'une boucle d'entraînement implémentée avec eager:

optimizer = tf.keras.optimizers.Adam()
loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

loss_history = []
def train_step(images, labels):
  with tf.GradientTape() as tape:
    logits = mnist_model(images, training=True)

    # Add asserts to check the shape of the output.
    tf.debugging.assert_equal(logits.shape, (32, 10))

    loss_value = loss_object(labels, logits)

  loss_history.append(loss_value.numpy().mean())
  grads = tape.gradient(loss_value, mnist_model.trainable_variables)
  optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables))
def train(epochs):
  for epoch in range(epochs):
    for (batch, (images, labels)) in enumerate(dataset):
      train_step(images, labels)
    print ('Epoch {} finished'.format(epoch))
train(epochs = 3)
Epoch 0 finished
Epoch 1 finished
Epoch 2 finished

import matplotlib.pyplot as plt

plt.plot(loss_history)
plt.xlabel('Batch #')
plt.ylabel('Loss [entropy]')
Text(0, 0.5, 'Loss [entropy]')

png

Variables et optimiseurs

tf.Variable objets tf.Variable stockent des valeurs de type tf.Tensor mutables accessibles pendant l'entraînement pour faciliter la différenciation automatique.

Les collections de variables peuvent être encapsulées dans des couches ou des modèles, ainsi que des méthodes qui les utilisent. Voir Couches et modèles Keras personnalisés pour plus de détails. La principale différence entre les couches et les modèles est que les modèles ajoutent des méthodes telles que Model.fit , Model.evaluate et Model.save .

Par exemple, l'exemple de différenciation automatique ci-dessus peut être réécrit:

class Linear(tf.keras.Model):
  def __init__(self):
    super(Linear, self).__init__()
    self.W = tf.Variable(5., name='weight')
    self.B = tf.Variable(10., name='bias')
  def call(self, inputs):
    return inputs * self.W + self.B
# A toy dataset of points around 3 * x + 2
NUM_EXAMPLES = 2000
training_inputs = tf.random.normal([NUM_EXAMPLES])
noise = tf.random.normal([NUM_EXAMPLES])
training_outputs = training_inputs * 3 + 2 + noise

# The loss function to be optimized
def loss(model, inputs, targets):
  error = model(inputs) - targets
  return tf.reduce_mean(tf.square(error))

def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return tape.gradient(loss_value, [model.W, model.B])

Suivant:

  1. Créez le modèle.
  2. Les dérivés d'une fonction de perte par rapport aux paramètres du modèle.
  3. Une stratégie de mise à jour des variables basée sur les dérivés.
model = Linear()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)

print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))

steps = 300
for i in range(steps):
  grads = grad(model, training_inputs, training_outputs)
  optimizer.apply_gradients(zip(grads, [model.W, model.B]))
  if i % 20 == 0:
    print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))
Initial loss: 68.602
Loss at step 000: 65.948
Loss at step 020: 30.156
Loss at step 040: 14.091
Loss at step 060: 6.878
Loss at step 080: 3.637
Loss at step 100: 2.180
Loss at step 120: 1.525
Loss at step 140: 1.231
Loss at step 160: 1.098
Loss at step 180: 1.038
Loss at step 200: 1.011
Loss at step 220: 0.999
Loss at step 240: 0.994
Loss at step 260: 0.991
Loss at step 280: 0.990

print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
Final loss: 0.990

print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
W = 3.001922369003296, B = 2.0047335624694824

Sauvegarde basée sur les objets

Un tf.keras.Model comprend une méthode save_weights pratique vous permettant de créer facilement un point de contrôle:

model.save_weights('weights')
status = model.load_weights('weights')

En utilisant tf.train.Checkpoint vous pouvez prendre le contrôle total de ce processus.

Cette section est une version abrégée du guide de formation aux points de contrôle .

x = tf.Variable(10.)
checkpoint = tf.train.Checkpoint(x=x)
x.assign(2.)   # Assign a new value to the variables and save.
checkpoint_path = './ckpt/'
checkpoint.save('./ckpt/')
'./ckpt/-1'
x.assign(11.)  # Change the variable after saving.

# Restore values from the checkpoint
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path))

print(x)  # => 2.0
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0>

Pour enregistrer et charger des modèles, tf.train.Checkpoint stocke l'état interne des objets, sans nécessiter de variables cachées. Pour enregistrer l'état d'un model , d'un optimizer et d'une étape globale, transmettez-les à un tf.train.Checkpoint :

model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
checkpoint_dir = 'path/to/model_dir'
if not os.path.exists(checkpoint_dir):
  os.makedirs(checkpoint_dir)
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tf.train.Checkpoint(optimizer=optimizer,
                           model=model)

root.save(checkpoint_prefix)
root.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f9abd45f1d0>

Métriques orientées objet

tf.keras.metrics sont stockés sous forme d'objets. Mettez à jour une métrique en transmettant les nouvelles données à l'appelable et récupérez le résultat à l'aide de la méthode tf.keras.metrics.result , par exemple:

m = tf.keras.metrics.Mean("loss")
m(0)
m(5)
m.result()  # => 2.5
m([8, 9])
m.result()  # => 5.5
<tf.Tensor: shape=(), dtype=float32, numpy=5.5>

Résumés et TensorBoard

TensorBoard est un outil de visualisation pour comprendre, déboguer et optimiser le processus de formation des modèles. Il utilise des événements récapitulatifs écrits lors de l'exécution du programme.

Vous pouvez utiliser tf.summary pour enregistrer des résumés de variables dans une exécution tf.summary . Par exemple, pour enregistrer les résumés des loss une fois toutes les 100 étapes d'entraînement:

logdir = "./tb/"
writer = tf.summary.create_file_writer(logdir)

steps = 1000
with writer.as_default():  # or call writer.set_as_default() before the loop.
  for i in range(steps):
    step = i + 1
    # Calculate loss with your real train function.
    loss = 1 - 0.001 * step
    if step % 100 == 0:
      tf.summary.scalar('loss', loss, step=step)
ls tb/
events.out.tfevents.1602033841.kokoro-gcp-ubuntu-prod-892356553.1670.619697.v2

Thèmes de différenciation automatique avancés

Modèles dynamiques

tf.GradientTape peut également être utilisé dans les modèles dynamiques. Cet exemple d'algorithme de recherche de ligne de retour arrière ressemble au code NumPy normal, sauf qu'il y a des gradients et qu'il est différenciable, malgré le flux de contrôle complexe:

def line_search_step(fn, init_x, rate=1.0):
  with tf.GradientTape() as tape:
    # Variables are automatically tracked.
    # But to calculate a gradient from a tensor, you must `watch` it.
    tape.watch(init_x)
    value = fn(init_x)
  grad = tape.gradient(value, init_x)
  grad_norm = tf.reduce_sum(grad * grad)
  init_value = value
  while value > init_value - rate * grad_norm:
    x = init_x - rate * grad
    value = fn(x)
    rate /= 2.0
  return x, value

Dégradés personnalisés

Les dégradés personnalisés sont un moyen simple de remplacer les dégradés. Dans la fonction avant, définissez le gradient par rapport aux entrées, sorties ou résultats intermédiaires. Par exemple, voici un moyen simple de découper la norme des dégradés dans la passe arrière:

@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
  y = tf.identity(x)
  def grad_fn(dresult):
    return [tf.clip_by_norm(dresult, norm), None]
  return y, grad_fn

Les dégradés personnalisés sont couramment utilisés pour fournir un dégradé numériquement stable pour une séquence d'opérations:

def log1pexp(x):
  return tf.math.log(1 + tf.exp(x))

def grad_log1pexp(x):
  with tf.GradientTape() as tape:
    tape.watch(x)
    value = log1pexp(x)
  return tape.gradient(value, x)
# The gradient computation works fine at x = 0.
grad_log1pexp(tf.constant(0.)).numpy()
0.5
# However, x = 100 fails because of numerical instability.
grad_log1pexp(tf.constant(100.)).numpy()
nan

Ici, la fonction log1pexp peut être simplifiée analytiquement avec un dégradé personnalisé. L'implémentation ci-dessous réutilise la valeur de tf.exp(x) qui est calculée pendant la passe avant, ce qui la rend plus efficace en éliminant les calculs redondants:

@tf.custom_gradient
def log1pexp(x):
  e = tf.exp(x)
  def grad(dy):
    return dy * (1 - 1 / (1 + e))
  return tf.math.log(1 + e), grad

def grad_log1pexp(x):
  with tf.GradientTape() as tape:
    tape.watch(x)
    value = log1pexp(x)
  return tape.gradient(value, x)
# As before, the gradient computation works fine at x = 0.
grad_log1pexp(tf.constant(0.)).numpy()
0.5
# And the gradient computation also works at x = 100.
grad_log1pexp(tf.constant(100.)).numpy()
1.0

Performance

Le calcul est automatiquement déchargé sur les GPU lors d'une exécution rapide. Si vous voulez contrôler où s'exécute un calcul, vous pouvez le tf.device('/gpu:0') dans un tf.device('/gpu:0') (ou l'équivalent CPU):

import time

def measure(x, steps):
  # TensorFlow initializes a GPU the first time it's used, exclude from timing.
  tf.matmul(x, x)
  start = time.time()
  for i in range(steps):
    x = tf.matmul(x, x)
  # tf.matmul can return before completing the matrix multiplication
  # (e.g., can return after enqueing the operation on a CUDA stream).
  # The x.numpy() call below will ensure that all enqueued operations
  # have completed (and will also copy the result to host memory,
  # so we're including a little more than just the matmul operation
  # time).
  _ = x.numpy()
  end = time.time()
  return end - start

shape = (1000, 1000)
steps = 200
print("Time to multiply a {} matrix by itself {} times:".format(shape, steps))

# Run on CPU:
with tf.device("/cpu:0"):
  print("CPU: {} secs".format(measure(tf.random.normal(shape), steps)))

# Run on GPU, if available:
if tf.config.experimental.list_physical_devices("GPU"):
  with tf.device("/gpu:0"):
    print("GPU: {} secs".format(measure(tf.random.normal(shape), steps)))
else:
  print("GPU: not found")
Time to multiply a (1000, 1000) matrix by itself 200 times:
CPU: 0.9788374900817871 secs
GPU: 0.04241943359375 secs

Un objet tf.Tensor peut être copié sur un autre appareil pour exécuter ses opérations:

if tf.config.experimental.list_physical_devices("GPU"):
  x = tf.random.normal([10, 10])

  x_gpu0 = x.gpu()
  x_cpu = x.cpu()

  _ = tf.matmul(x_cpu, x_cpu)    # Runs on CPU
  _ = tf.matmul(x_gpu0, x_gpu0)  # Runs on GPU:0
WARNING:tensorflow:From <ipython-input-1-876293b5769c>:4: _EagerTensorBase.gpu (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.identity instead.
WARNING:tensorflow:From <ipython-input-1-876293b5769c>:5: _EagerTensorBase.cpu (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.identity instead.

Benchmarks

Pour les modèles gourmands en calcul , tels que la formation ResNet50 sur un GPU, les performances d'exécution tf.function sont comparables à l'exécution de tf.function . Mais cet écart se creuse pour les modèles avec moins de calculs et il y a du travail à faire pour optimiser les chemins de code instantané pour les modèles avec beaucoup de petites opérations.

Travailler avec des fonctions

Alors qu'une exécution rapide rend le développement et le débogage plus interactifs, l'exécution de graphes de style TensorFlow 1.x présente des avantages pour la formation distribuée, l'optimisation des performances et le déploiement en production. Pour combler cette lacune, TensorFlow 2.0 introduit des function via l'API tf.function . Pour plus d'informations, consultez le guide tf.function .