¡El Día de la Comunidad de ML es el 9 de noviembre! Únase a nosotros para recibir actualizaciones de TensorFlow, JAX, y más Más información

Ejecución ansiosa

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

La ávida ejecución de TensorFlow es un entorno de programación imperativo que evalúa las operaciones de inmediato, sin crear gráficos: las operaciones devuelven valores concretos en lugar de construir un gráfico computacional para ejecutar más tarde. Esto facilita comenzar con TensorFlow y los modelos de depuración, y también reduce el texto estándar. Para continuar con esta guía, ejecute los siguientes ejemplos de código en un sistema interactivo de python intérprete.

La ejecución ávida es una plataforma flexible de aprendizaje automático para la investigación y la experimentación, que proporciona:

  • Una interfaz intuitiva -Estructura su código de forma natural y el uso de estructuras de datos de Python. Repetir rápidamente en modelos pequeños y datos pequeños.
  • Más fácil la depuración de operaciones -call directamente a inspeccionar modelos de funcionamiento y los cambios de las pruebas. Utilice herramientas de depuración estándar de Python para generar informes de errores inmediatos.
  • El control natural fluya flujo de control -Uso Python en lugar de flujo de control gráfico, lo que simplifica la especificación de los modelos dinámicos.

La ejecución ávida admite la mayoría de las operaciones de TensorFlow y la aceleración de GPU.

Configuración y uso básico

import os

import tensorflow as tf

import cProfile

En Tensorflow 2.0, la ejecución ansiosa está habilitada de forma predeterminada.

tf.executing_eagerly()
True

Ahora puede ejecutar operaciones de TensorFlow y los resultados se mostrarán de inmediato:

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

Habilitar la ejecución ávida cambia la forma en que se comportan las operaciones de TensorFlow; ahora evalúan y devuelven inmediatamente sus valores a Python. tf.Tensor objetos valores concretos de referencia en lugar de asas simbólicos a los nodos en un gráfico computacional. Dado que no es un gráfico computacional para generar y ejecutar más tarde en una sesión, es fácil de inspeccionar los resultados usando print() o un depurador. La evaluación, impresión y verificación de los valores tensoriales no interrumpe el flujo para calcular gradientes.

Ejecución ansiosos funciona muy bien con NumPy . Operaciones NumPy aceptan tf.Tensor argumentos. Los TensorFlow tf.math operaciones convertir objetos Python y matrices NumPy a tf.Tensor objetos. El tf.Tensor.numpy método devuelve el valor del objeto como un NumPy ndarray .

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]]

Flujo de control dinámico

Un beneficio importante de la ejecución ávida es que toda la funcionalidad del lenguaje host está disponible mientras se ejecuta el modelo. Así, por ejemplo, es fácil escribir 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

Esto tiene condicionales que dependen de los valores del tensor e imprime estos valores en tiempo de ejecución.

Entrenamiento ansioso

Gradientes de computación

Diferenciación automática es útil para implementar algoritmos de aprendizaje de máquina, tales como propagación hacia atrás para el entrenamiento de las redes neuronales. Durante la ejecución ansiosos, utilice tf.GradientTape para rastrear las operaciones para el cálculo de los gradientes más tarde.

Puede utilizar tf.GradientTape para entrenar y / o gradientes de cómputo en ganas. Es especialmente útil para ciclos de entrenamiento complicados.

Dado que pueden ocurrir diferentes operaciones durante cada llamada, todas las operaciones de paso hacia adelante se graban en una "cinta". Para calcular el gradiente, reproduzca la cinta al revés y luego deséchela. Un particular, tf.GradientTape sólo puede calcular un gradiente; las llamadas posteriores arrojan un error de tiempo de ejecución.

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)

Entrena un modelo

El siguiente ejemplo crea un modelo multicapa que clasifica los dígitos manuscritos estándar del MNIST. Demuestra el optimizador y las API de capas para crear gráficos entrenables en un entorno de ejecución ávido.

# 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)
# 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)
])

Incluso sin entrenamiento, llame al modelo e inspeccione el resultado en ejecución ansiosa:

for images,labels in dataset.take(1):
  print("Logits: ", mnist_model(images[0:1]).numpy())
Logits:  [[-0.01775933 -0.01194787 -0.08372174 -0.06535977  0.00338565 -0.01974326
  -0.04763228  0.00904049 -0.00144051 -0.01944664]]

Mientras que los modelos Keras tienen un bucle de entrenamiento incorporado (mediante el fit del método), a veces se necesita una mayor personalización. A continuación, se muestra un ejemplo de un ciclo de entrenamiento implementado con entusiasmo:

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 y optimizadores

tf.Variable objetos almacenan mutable tf.Tensor -como valores de los que se accede durante el entrenamiento para hacer más fácil la diferenciación automática.

Las colecciones de variables se pueden encapsular en capas o modelos, junto con los métodos que operan sobre ellas. Ver capas Keras personalizada y modelos para obtener más detalles. La diferencia principal entre las capas y los modelos es que los modelos añaden métodos como Model.fit , Model.evaluate , y Model.save .

Por ejemplo, el ejemplo de diferenciación automática anterior se puede reescribir:

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])

Próximo:

  1. Crea el modelo.
  2. Las derivadas de una función de pérdida con respecto a los parámetros del modelo.
  3. Una estrategia para actualizar las variables en función de las derivadas.
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: 69.909
Loss at step 000: 67.145
Loss at step 020: 30.170
Loss at step 040: 13.859
Loss at step 060: 6.659
Loss at step 080: 3.479
Loss at step 100: 2.074
Loss at step 120: 1.453
Loss at step 140: 1.178
Loss at step 160: 1.056
Loss at step 180: 1.003
Loss at step 200: 0.979
Loss at step 220: 0.968
Loss at step 240: 0.963
Loss at step 260: 0.961
Loss at step 280: 0.960
print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
Final loss: 0.960
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
W = 2.9515867233276367, B = 2.0210201740264893

Ahorro basado en objetos

Un tf.keras.Model incluye un cómodo save_weights método que le permite crear fácilmente un puesto de control:

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

Usando tf.train.Checkpoint que puede tomar el control total sobre este proceso.

Esta sección es una versión abreviada de la guía a los puestos de control de formación .

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(checkpoint_path)
'./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>

Para guardar y modelos de carga, tf.train.Checkpoint almacena el estado interno de los objetos, sin necesidad de variables ocultas. Para registrar el estado de un model , un optimizer , y un paso global, pasar a 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 0x7f4ba0648310>

Métricas orientadas a objetos

tf.keras.metrics se almacenan como objetos. Actualizar una métrica pasando los nuevos datos a la exigible, y recuperar el resultado utilizando el tf.keras.metrics.result método, por ejemplo:

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>

Resúmenes y TensorBoard

TensorBoard es una herramienta de visualización para comprender, depurar y optimizar el proceso de formación del modelo. Utiliza eventos de resumen que se escriben mientras se ejecuta el programa.

Puede utilizar tf.summary a los resúmenes de registro de la variable en la ejecución ansiosos. Por ejemplo, para registrar resúmenes de loss una vez cada 100 pasos de formación:

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.1632342765.kokoro-gcp-ubuntu-prod-230753280.22287.0.v2

Temas avanzados de diferenciación automática

Modelos dinámicos

tf.GradientTape también se puede utilizar en los modelos dinámicos. Este ejemplo para una línea de retroceso buscar algoritmo busca como código normal NumPy, excepto que hay gradientes y es diferenciable, a pesar del flujo de control complejo:

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

Degradados personalizados

Los degradados personalizados son una forma sencilla de anular los degradados. Dentro de la función de avance, defina el gradiente con respecto a las entradas, salidas o resultados intermedios. Por ejemplo, aquí hay una manera fácil de recortar la norma de los degradados en la pasada hacia atrás:

@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

Los degradados personalizados se utilizan comúnmente para proporcionar un degradado numéricamente estable para una secuencia de operaciones:

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

Aquí, el log1pexp función puede ser analíticamente simplifica con un gradiente personalizado. La aplicación a continuación vuelve a utilizar el valor por tf.exp(x) que se calcula durante el avance de paso a lo que es más eficiente al eliminar los cálculos redundantes:

@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

Rendimiento

La computación se descarga automáticamente a las GPU durante la ejecución ansiosa. Si desea un control sobre el lugar donde se ejecuta un cálculo puede encerrarlo en una tf.device('/gpu:0') del bloque (o el equivalente 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.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: 1.007401466369629 secs
GPU: 0.04124784469604492 secs

Un tf.Tensor objeto se puede copiar en un dispositivo diferente para ejecutar sus operaciones:

if tf.config.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 /tmp/ipykernel_22287/406964202.py: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 /tmp/ipykernel_22287/406964202.py: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

Para los modelos de computación pesados, tales como ResNet50 entrenar en una GPU, rendimiento de ejecución ansiosos es comparable a tf.function ejecución. Pero esta brecha se hace más grande para los modelos con menos computación y hay trabajo por hacer para optimizar las rutas de código activo para modelos con muchas operaciones pequeñas.

Trabajar con funciones

Si bien la ejecución ávida hace que el desarrollo y la depuración sean más interactivos, la ejecución de gráficos de estilo TensorFlow 1.x tiene ventajas para el entrenamiento distribuido, las optimizaciones del rendimiento y la implementación de producción. Para llenar este vacío, TensorFlow 2.0 introduce function s a través de la tf.function API. Para obtener más información, consulte la tf.function guía.