Execução ansiosa

Ver no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

A execução ansiosa do TensorFlow é um ambiente de programação imperativo que avalia as operações imediatamente, sem construir gráficos: as operações retornam valores concretos em vez de construir um gráfico computacional para executar mais tarde. Isso facilita os primeiros passos com o TensorFlow e os modelos de depuração, além de reduzir o clichê. Para acompanhar este guia, executar as amostras de código abaixo num interactivo python intérprete.

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

  • Uma interface intuitiva -Estrutura seu código naturalmente e usar estruturas de dados Python. Repita rapidamente em pequenos modelos e pequenos dados.
  • Mais fácil depuração ops -Call diretamente para inspecionar executando modelos e mudanças de teste. Use ferramentas de depuração padrão do Python para relatórios de erros imediatos.
  • Controle natural de fluxo do fluxo de controle -Utilização Python em vez do fluxo de controlo de gráfico, simplificando a especificação de modelos dinâmicos.

A execução rápida é compatível com a maioria das operações do TensorFlow e aceleração de GPU.

Configuração e uso básico

import os

import tensorflow as tf

import cProfile

No Tensorflow 2.0, a execução rápida é habilitada 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.]]

Permitir a execução rápida muda o comportamento das operações do TensorFlow - agora elas avaliam e retornam imediatamente seus valores para Python. tf.Tensor objectos concretos valores de referência em vez de punhos simbólicos para os nós de um gráfico computacional. Uma vez que não é um gráfico computacional para criar e executar mais tarde, em uma sessão, é fácil para inspecionar resultados usando print() ou um depurador. Avaliar, imprimir e verificar os valores do tensor não interrompe o fluxo de gradientes de computação.

Execução ansioso funciona bem com NumPy . Operações Numpy aceitar tf.Tensor argumentos. Os TensorFlow tf.math operações converter objetos Python e arrays numpy para tf.Tensor objetos. O tf.Tensor.numpy método retorna o valor do objeto como um 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]]

Fluxo de controle dinâmico

Um grande benefício da execução antecipada é que toda a funcionalidade da linguagem host está disponível enquanto seu modelo está sendo executado. Assim, 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 de valores de tensor e imprime esses valores em tempo de execução.

Treinamento ansioso

Gradientes de computação

Diferenciação automática é útil para implementação de algoritmos de aprendizagem de máquina, como backpropagation para a formação de redes neurais. Durante a execução ansioso, use tf.GradientTape para rastrear operações para gradientes de computação mais tarde.

Você pode usar tf.GradientTape para treinar e ou gradientes / computação em ansioso. É especialmente útil para loops de treinamento complicados.

Visto que diferentes operações podem ocorrer durante cada chamada, todas as operações forward-pass são gravadas em uma "fita". Para calcular o gradiente, reproduza a fita ao contrário e descarte. Uma especial tf.GradientTape só pode calcular um gradiente; as chamadas subsequentes 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)

Treine uma modelo

O exemplo a seguir cria um modelo multicamadas que classifica os dígitos manuscritos MNIST padrão. Ele demonstra o otimizador e APIs de camada para construir gráficos treináveis ​​em um ambiente de execução rápido.

# 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 [==============================] - 1s 0us/step
11501568/11490434 [==============================] - 1s 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.01776254  0.03069138 -0.01169398  0.04716408  0.00278952 -0.0160556
   0.00397673 -0.02701374  0.01099346 -0.01109808]]

Enquanto os modelos Keras tem um loop de treinamento incorporado (usando o fit método), às vezes você precisa de mais personalização. Aqui está um exemplo de um loop de treinamento implementado com ansioso:

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 armazenar mutável tf.Tensor -como valores acessados durante o treinamento para fazer a diferenciação automática mais fácil.

As coleções de variáveis ​​podem ser encapsuladas em camadas ou modelos, junto com métodos que operam nelas. Veja camadas personalizado Keras e modelos para detalhes. A principal diferença entre as camadas e modelos é que os modelos adicionar 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 no que diz respeito aos parâmetros do modelo.
  3. Uma estratégia para atualizar as variáveis ​​com base nas 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: 67.455
Loss at step 000: 64.856
Loss at step 020: 29.757
Loss at step 040: 13.947
Loss at step 060: 6.824
Loss at step 080: 3.613
Loss at step 100: 2.165
Loss at step 120: 1.512
Loss at step 140: 1.218
Loss at step 160: 1.085
Loss at step 180: 1.025
Loss at step 200: 0.998
Loss at step 220: 0.985
Loss at step 240: 0.980
Loss at step 260: 0.977
Loss at step 280: 0.976
print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
Final loss: 0.976
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
W = 2.9953086376190186, B = 2.0608654022216797

Salvamento baseado em objeto

A tf.keras.Model inclui um conveniente save_weights método que lhe permite criar facilmente um posto de controle:

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

Usando tf.train.Checkpoint você pode assumir o controle total sobre este 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(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 salvar e modelos de carga, tf.train.Checkpoint armazena o estado interno dos objetos, sem a necessidade de variáveis ocultas. Para gravar o estado de um model , um optimizer , e um passo global, passá-las 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 0x7f0d4c6a38d0>

Métricas orientadas a objetos

tf.keras.metrics são armazenados como objetos. Atualizar uma métrica passando os novos dados para o que pode ser chamado, e recuperar o resultado usando o tf.keras.metrics.result método, 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

TensorBoard é uma ferramenta de visualização para a compreensão, a depuração e optimização do 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 resumos recorde de variável na execução ansioso. Por exemplo, para gravar resumos de loss uma vez a cada 100 passos de formação:

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.1630113988.kokoro-gcp-ubuntu-prod-1335170857.27045.0.v2

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

Modelos dinâmicos

tf.GradientTape também pode ser usado em modelos dinâmicos. Este exemplo de uma linha de retrocesso procurar algoritmo parece com código NumPy normal, excepto 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 em relação às entradas, saídas ou resultados intermediários. Por exemplo, esta é uma maneira fácil de cortar a norma dos gradientes na passagem reversa:

@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, o log1pexp função pode ser analiticamente simplificado com um gradiente personalizado. A implementação abaixo reutilizações o valor para tf.exp(x) que é calculado durante a passagem para a frente, tornando-a mais eficiente, eliminando 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 é descarregada automaticamente para as GPUs durante a execução rápida. Se você quer controle sobre onde um cálculo é executado você pode colocá-lo em um tf.device('/gpu:0') bloco (ou o 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: 0.8828294277191162 secs
GPU: 0.04031491279602051 secs

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

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_27045/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_27045/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 os modelos de computação pesada, como ResNet50 treinando em uma GPU, o desempenho da execução ansioso é comparável ao tf.function execução. Mas essa lacuna fica maior para modelos com menos computação e há trabalho a ser feito para otimizar caminhos de código quente para modelos com muitas operações pequenas.

Trabalhe com funções

Enquanto a execução rápida torna o desenvolvimento e a depuração mais interativos, a execução de gráfico estilo TensorFlow 1.x tem vantagens para treinamento distribuído, otimizações de desempenho e implantação de produção. Para colmatar esta lacuna, TensorFlow 2.0 apresenta function s através do tf.function API. Para mais informações, consulte o tf.function guia.