Google I / O tornerà dal 18 al 20 maggio! Prenota lo spazio e crea il tuo programma Registrati ora

Esecuzione desiderosa

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza sorgente su GitHub Scarica taccuino

L'esecuzione impaziente di TensorFlow è un ambiente di programmazione imperativo che valuta le operazioni immediatamente, senza costruire grafici: le operazioni restituiscono valori concreti invece di costruire un grafico computazionale da eseguire in seguito. Ciò semplifica l'avvio con TensorFlow e il debug dei modelli e riduce anche il boilerplate. Per seguire questa guida, esegui gli esempi di codice di seguito in un interprete python interattivo.

L'esecuzione desiderosa è una piattaforma flessibile di machine learning per la ricerca e la sperimentazione, che fornisce:

  • Un'interfaccia intuitiva: struttura il tuo codice in modo naturale e utilizza strutture di dati Python. Esegui rapidamente l'iterazione su piccoli modelli e piccoli dati.
  • Debug più semplice: chiama direttamente le operazioni per esaminare i modelli in esecuzione e testare le modifiche. Utilizza gli strumenti di debug Python standard per la segnalazione immediata degli errori.
  • Flusso di controllo naturale: utilizza il flusso di controllo Python invece del flusso di controllo grafico, semplificando la specifica dei modelli dinamici.

L'esecuzione desiderosa supporta la maggior parte delle operazioni TensorFlow e l'accelerazione GPU.

Configurazione e utilizzo di base

import os

import tensorflow as tf

import cProfile

In Tensorflow 2.0, l'esecuzione impaziente è abilitata per impostazione predefinita.

tf.executing_eagerly()
True

Ora puoi eseguire le operazioni TensorFlow ei risultati verranno restituiti immediatamente:

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

L'abilitazione dell'esecuzione impaziente cambia il modo in cui si comportano le operazioni TensorFlow: ora valutano e restituiscono immediatamente i loro valori a Python. tf.Tensor oggetti tf.Tensor riferimento a valori concreti invece di handle simbolici ai nodi in un grafo computazionale. Poiché non esiste un grafico computazionale da creare ed eseguire successivamente in una sessione, è facile ispezionare i risultati utilizzando print() o un debugger. La valutazione, la stampa e il controllo dei valori tensoriali non interrompe il flusso per il calcolo dei gradienti.

L'esecuzione desiderosa funziona bene con NumPy . Le operazioni NumPy accettano argomenti tf.Tensor . Le operazioni TensorFlow tf.math convertono gli oggetti Python e gli array NumPy in oggetti tf.Tensor . Il metodo tf.Tensor.numpy restituisce il valore dell'oggetto come 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]]

Flusso di controllo dinamico

Uno dei principali vantaggi di un'esecuzione impaziente è che tutte le funzionalità della lingua host sono disponibili durante l'esecuzione del modello. Quindi, ad esempio, è facile scrivere 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

Questo ha condizionali che dipendono dai valori tensoriali e stampa questi valori in fase di esecuzione.

Formazione desiderosa

Calcolo dei gradienti

La differenziazione automatica è utile per implementare algoritmi di apprendimento automatico come backpropagation per l'addestramento di reti neurali. Durante l'esecuzione desiderosa, utilizzaretf.GradientTape per tracciare le operazioni per il calcolo dei gradienti in un secondo momento.

Puoi usaretf.GradientTape per addestrare e / o calcolare gradienti in eager. È particolarmente utile per cicli di allenamento complicati.

Poiché durante ogni chiamata possono verificarsi operazioni diverse, tutte le operazioni di inoltro vengono registrate su un "nastro". Per calcolare il gradiente, riprodurre il nastro all'indietro e quindi scartare. Un particolaretf.GradientTape può calcolare solo un gradiente; le chiamate successive generano un errore di runtime.

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)

Addestra un modello

L'esempio seguente crea un modello multistrato che classifica le cifre scritte a mano MNIST standard. Dimostra l'ottimizzatore e le API di livello per creare grafici addestrabili in un ambiente di esecuzione entusiasta.

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

Anche senza addestramento, chiama il modello e ispeziona l'output in un'esecuzione impaziente:

for images,labels in dataset.take(1):
  print("Logits: ", mnist_model(images[0:1]).numpy())
Logits:  [[ 0.03667693 -0.03049762 -0.00575869 -0.03993434  0.08212403 -0.04499513
  -0.00077433  0.08982861  0.0706538  -0.02175808]]

Sebbene i modelli keras abbiano un ciclo di addestramento incorporato (utilizzando il metodo di fit ), a volte è necessaria una maggiore personalizzazione. Ecco un esempio di un ciclo di addestramento implementato con 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

Variabili e ottimizzatori

tf.Variable oggetti tf.Variable memorizzano valori mutabili simili a tf.Tensor accede durante l'addestramento per facilitare la differenziazione automatica.

Le raccolte di variabili possono essere incapsulate in livelli o modelli, insieme a metodi che operano su di essi. Vedi Livelli e modelli Keras personalizzati per i dettagli. La differenza principale tra layer e modelli è che i modelli aggiungono metodi come Model.fit , Model.evaluate e Model.save .

Ad esempio, l'esempio di differenziazione automatica sopra può essere riscritto:

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

Il prossimo:

  1. Crea il modello.
  2. I derivati ​​di una funzione di perdita rispetto ai parametri del modello.
  3. Una strategia per aggiornare le variabili in base alle derivate.
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.712
Loss at step 000: 66.034
Loss at step 020: 30.012
Loss at step 040: 13.941
Loss at step 060: 6.772
Loss at step 080: 3.573
Loss at step 100: 2.146
Loss at step 120: 1.509
Loss at step 140: 1.225
Loss at step 160: 1.098
Loss at step 180: 1.042
Loss at step 200: 1.016
Loss at step 220: 1.005
Loss at step 240: 1.000
Loss at step 260: 0.998
Loss at step 280: 0.997
print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
Final loss: 0.997
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
W = 3.022096633911133, B = 2.0270628929138184

Salvataggio basato su oggetti

Un tf.keras.Model include un comodo metodo save_weights che ti consente di creare facilmente un checkpoint:

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

Usando tf.train.Checkpoint puoi avere il pieno controllo di questo processo.

Questa sezione è una versione abbreviata della guida ai punti di controllo dell'addestramento .

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>

Per salvare e caricare i modelli, tf.train.Checkpoint memorizza lo stato interno degli oggetti, senza richiedere variabili nascoste. Per registrare lo stato di un model , un optimizer e un passaggio globale, tf.train.Checkpoint 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 0x7ff0c18b14e0>

Metriche orientate agli oggetti

tf.keras.metrics vengono archiviati come oggetti. Aggiorna una metrica passando i nuovi dati al chiamabile e recupera il risultato utilizzando il metodo tf.keras.metrics.result , ad esempio:

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>

Riepiloghi e TensorBoard

TensorBoard è uno strumento di visualizzazione per la comprensione, il debug e l'ottimizzazione del processo di addestramento del modello. Utilizza eventi di riepilogo che vengono scritti durante l'esecuzione del programma.

È possibile utilizzare tf.summary per registrare riepiloghi di variabili in esecuzione impaziente. Ad esempio, per registrare i riepiloghi delle loss una volta ogni 100 fasi di allenamento:

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.1617758981.kokoro-gcp-ubuntu-prod-1009344920.4448.636510.v2

Argomenti avanzati sulla differenziazione automatica

Modelli dinamici

tf.GradientTape può essere utilizzato anche nei modelli dinamici. Questo esempio per un algoritmo di ricerca di una riga di backtracking assomiglia al normale codice NumPy, tranne che ci sono gradienti ed è differenziabili, nonostante il complesso flusso di controllo:

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

Gradienti personalizzati

Le sfumature personalizzate sono un modo semplice per sovrascrivere le sfumature. All'interno della funzione forward, definire il gradiente rispetto agli input, output o risultati intermedi. Ad esempio, ecco un modo semplice per ritagliare la norma dei gradienti nel passaggio all'indietro:

@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

I gradienti personalizzati sono comunemente usati per fornire un gradiente numericamente stabile per una sequenza di operazioni:

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

Qui, la funzione log1pexp può essere semplificata analiticamente con un gradiente personalizzato. L'implementazione seguente riutilizza il valore per tf.exp(x) calcolato durante il passaggio in avanti, rendendolo più efficiente eliminando i calcoli ridondanti:

@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

Prestazione

Il calcolo viene trasferito automaticamente alle GPU durante l'esecuzione impaziente. Se vuoi controllare dove viene eseguito un calcolo, puoi racchiuderlo in un tf.device('/gpu:0') (o l'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.8094048500061035 secs
GPU: 0.039966583251953125 secs

Un oggetto tf.Tensor può essere copiato su un dispositivo diverso per eseguire le sue operazioni:

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 <ipython-input-1-c99eaec55f9a>: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-c99eaec55f9a>: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.

Punti di riferimenti

Per i modelli pesanti, come la formazione ResNet50 su una GPU, le prestazioni di esecuzione entusiaste sono paragonabili all'esecuzione di tf.function . Ma questo divario aumenta per i modelli con meno calcoli e c'è del lavoro da fare per ottimizzare i percorsi del codice a caldo per i modelli con molte piccole operazioni.

Lavora con le funzioni

Mentre l'esecuzione impaziente rende lo sviluppo e il debug più interattivi, l'esecuzione del grafico in stile TensorFlow 1.x presenta vantaggi per la formazione distribuita, l'ottimizzazione delle prestazioni e la distribuzione della produzione. Per colmare questa lacuna, TensorFlow 2.0 introduce le function s tramite l'API tf.function . Per ulteriori informazioni, vedere la guida alle funzioni tf .