Esta página foi traduzida pela API Cloud Translation.
Switch to English

Execução ansiosa

Ver em TensorFlow.org Executar no Google Colab Ver fonte no GitHub Download do caderno

A execução ágil do TensorFlow é um ambiente de programação imperativo que avalia operações imediatamente, sem criar gráficos: as operações retornam valores concretos em vez de construir um gráfico computacional para execução posterior. Isso facilita a introdução aos modelos TensorFlow e depuração, além de reduzir o padrão. Para seguir este guia, execute os exemplos de código abaixo em um interpretador python interativo.

A execução ansiosa é uma plataforma flexível de aprendizado de máquina para pesquisa e experimentação, fornecendo:

  • Uma interface intuitiva - estruture seu código naturalmente e use estruturas de dados Python. Faça iterações rápidas em modelos pequenos e em dados pequenos.
  • Depuração mais fácil - ligue diretamente para inspecionar modelos em execução e testar alterações. Use ferramentas de depuração padrão do Python para relatórios de erros imediatos.
  • Fluxo de controle natural - use o fluxo de controle Python em vez do fluxo de controle gráfico, simplificando a especificação de modelos dinâmicos.

A execução ágil suporta a maioria das operações do TensorFlow e a aceleração da GPU.

Configuração e uso básico

 import os

import tensorflow as tf

import cProfile
 

No Tensorflow 2.0, a execução rápida é ativada por padrão.

 tf.executing_eagerly()
 
True

Agora você pode executar as operações do TensorFlow e os resultados retornarão imediatamente:

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

Habilitar a execução ágil muda o comportamento das operações do TensorFlow - agora elas avaliam e retornam imediatamente seus valores ao Python. tf.Tensor objetos tf.Tensor referenciam valores concretos, em vez de identificadores simbólicos, a nós em um gráfico computacional. Como não há um gráfico computacional para criar e executar posteriormente em uma sessão, é fácil inspecionar os resultados usando print() ou um depurador. A avaliação, impressão e verificação dos valores do tensor não interrompem o fluxo dos gradientes de computação.

A execução ágil funciona muito bem com o NumPy . As operações NumPy aceitam argumentos tf.Tensor . As operações TensorFlow tf.math convertem objetos Python e matrizes NumPy em objetos tf.Tensor . O método tf.Tensor.numpy retorna o valor do objeto como uma 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]]

Fluxo de controle dinâmico

Um grande benefício da execução rápida é que toda a funcionalidade do idioma do host fica disponível enquanto o modelo está em execução. Então, por exemplo, é fácil escrever 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

Isso tem condicionais que dependem dos valores do tensor e imprime esses valores no tempo de execução.

Treinamento ansioso

Gradientes de computação

A diferenciação automática é útil para implementar algoritmos de aprendizado de máquina, como retropropagação para o treinamento de redes neurais. Durante a execução rápida, use tf.GradientTape para rastrear operações para gradientes de computação posteriormente.

Você pode usar o tf.GradientTape para treinar e / ou calcular gradientes tf.GradientTape . É especialmente útil para loops de treinamento complicados.

Como operações diferentes podem ocorrer durante cada chamada, todas as operações de encaminhamento são gravadas em uma "fita". Para calcular o gradiente, toque a fita para trás e descarte-a. Um tf.GradientTape específico pode calcular apenas um gradiente; chamadas subseqüentes geram um erro de tempo de execução.

 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)

Treinar um modelo

O exemplo a seguir cria um modelo de várias camadas que classifica os dígitos manuscritos MNIST padrão. Ele demonstra o API do otimizador e da camada para criar gráficos treináveis ​​em um ambiente de execução ansioso.

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

Mesmo sem treinamento, chame o modelo e inspecione a saída em execução rápida:

 for images,labels in dataset.take(1):
  print("Logits: ", mnist_model(images[0:1]).numpy())
 
Logits:  [[-0.04290903  0.05850095 -0.09306249  0.01919096 -0.04339378  0.09877204
  -0.16061416  0.03829113 -0.04268045 -0.01678811]]

Embora os modelos keras tenham um loop de treinamento interno (usando o método de fit ), às vezes você precisa de mais personalização. Aqui está um exemplo de um loop de treinamento implementado com 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

Variáveis ​​e otimizadores

tf.Variable objetos tf.Variable armazenam valores mutáveis ​​do tipo tf.Tensor acessados ​​durante o treinamento para facilitar a diferenciação automática.

As coleções de variáveis ​​podem ser encapsuladas em camadas ou modelos, juntamente com métodos que operam nelas. Consulte Camadas e modelos personalizados do Keras para obter detalhes. A principal diferença entre camadas e modelos é que os modelos adicionam métodos como Model.fit , Model.evaluate e Model.save .

Por exemplo, o exemplo de diferenciação automática acima pode ser reescrito:

 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. Crie o modelo.
  2. As derivadas de uma função de perda com relação aos parâmetros do modelo.
  3. Uma estratégia para atualizar as variáveis ​​com base nos derivativos.
 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.574
Loss at step 000: 65.927
Loss at step 020: 30.202
Loss at step 040: 14.151
Loss at step 060: 6.936
Loss at step 080: 3.692
Loss at step 100: 2.233
Loss at step 120: 1.576
Loss at step 140: 1.281
Loss at step 160: 1.148
Loss at step 180: 1.088
Loss at step 200: 1.061
Loss at step 220: 1.049
Loss at step 240: 1.043
Loss at step 260: 1.041
Loss at step 280: 1.040

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

 print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
 
W = 2.988429069519043, B = 2.008305311203003

Salvamento baseado em objeto

Um tf.keras.Model inclui um método covienient save_weights que permite criar facilmente um ponto de verificação:

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

Usando tf.train.Checkpoint você pode assumir o controle total sobre esse processo.

Esta seção é uma versão abreviada do guia para pontos de verificação de treinamento .

 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>

Para salvar e carregar modelos, tf.train.Checkpoint armazena o estado interno dos objetos, sem exigir variáveis ​​ocultas. Para registrar o estado de um model , um optimizer e uma etapa global, passe-os para um 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 0x7f9dcc8be588>

Métricas orientadas a objetos

tf.keras.metrics são armazenados como objetos. Atualize uma métrica passando os novos dados para a chamada e recupere o resultado usando o método tf.keras.metrics.result , por exemplo:

 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>

Resumos e TensorBoard

O TensorBoard é uma ferramenta de visualização para entender, depurar e otimizar o processo de treinamento do modelo. Ele usa eventos de resumo que são gravados durante a execução do programa.

Você pode usar tf.summary para registrar resumos de variáveis ​​em execução rápida. Por exemplo, para registrar resumos de loss uma vez a cada 100 etapas de treinamento:

 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.1595467632.kokoro-gcp-ubuntu-prod-341901152.10415.619671.v2

Tópicos avançados de diferenciação automática

Modelos dinâmicos

tf.GradientTape também pode ser usado em modelos dinâmicos. Este exemplo para um algoritmo de pesquisa de linha de retorno é semelhante ao código NumPy normal, exceto que existem gradientes e é diferenciável, apesar do fluxo de controle complexo:

 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
 

Gradientes personalizados

Gradientes personalizados são uma maneira fácil de substituir gradientes. Na função de avanço, defina o gradiente com relação às entradas, saídas ou resultados intermediários. Por exemplo, aqui está uma maneira fácil de recortar a norma dos gradientes na passagem para trá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
 

Gradientes personalizados são comumente usados ​​para fornecer um gradiente numericamente estável para uma sequência de operações:

 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

Aqui, a função log1pexp pode ser simplificada analiticamente com um gradiente personalizado. A implementação abaixo reutiliza o valor para tf.exp(x) que é calculado durante o encaminhamento - tornando-o mais eficiente ao eliminar 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

atuação

A computação é automaticamente transferida para GPUs durante uma execução rápida. Se você deseja controlar o local onde uma computação é executada, você pode incluí-la em um tf.device('/gpu:0') (ou o equivalente da 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.9854905605316162 secs
GPU: 0.0423429012298584 secs

Um objeto tf.Tensor pode ser copiado para um dispositivo diferente para executar suas operações:

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

Para modelos pesados ​​de computação, como o treinamento ResNet50 em uma GPU, o desempenho de execução mais tf.function é comparável ao da execução da função tf.function . Porém, essa diferença aumenta para modelos com menos computação e ainda há trabalho a ser feito para otimizar os caminhos de códigos de acesso para modelos com muitas operações pequenas.

Trabalhar com funções

Embora a execução ágil torne o desenvolvimento e a depuração mais interativos, a execução de gráficos no estilo TensorFlow 1.x tem vantagens para treinamento distribuído, otimizações de desempenho e implantação de produção. Para preencher essa lacuna, o TensorFlow 2.0 introduz function s por meio da API tf.function . Para mais informações, consulte o guia tf.function .