Questa pagina è stata tradotta dall'API Cloud Translation.
Switch to English

Esecuzione desiderosa

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

L'esecuzione entusiasta di TensorFlow è un ambiente di programmazione indispensabile che valuta immediatamente le operazioni, senza costruire grafici: le operazioni restituiscono valori concreti invece di costruire un grafico computazionale da eseguire in seguito. Ciò semplifica le operazioni preliminari con i modelli TensorFlow e debug e riduce anche la piastra di cottura. Per seguire questa guida, esegui i seguenti esempi di codice in un interprete python interattivo.

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

  • Un'interfaccia intuitiva: strutturate il codice in modo naturale e utilizzate le strutture di dati Python. Passa rapidamente a modelli piccoli e piccoli dati.
  • Debug più semplice: la chiamata opera direttamente per ispezionare i modelli in esecuzione e testare le modifiche. Utilizzare gli strumenti di debug standard di Python per la segnalazione immediata degli errori.
  • Flusso di controllo naturale: utilizzare il flusso di controllo Python anziché il flusso di controllo grafico, semplificando le specifiche dei modelli dinamici.

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

Installazione e utilizzo di base

 import os

import tensorflow as tf

import cProfile
 

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

 tf.executing_eagerly()
 
True

Ora puoi eseguire le operazioni TensorFlow e i risultati restituiranno immediatamente:

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

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

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

Flusso di controllo dinamico

Un grande vantaggio dell'esecuzione desiderosa è 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 del tensore e stampa questi valori in fase di esecuzione.

Addestramento desideroso

Gradienti di calcolo

La differenziazione automatica è utile per l'implementazione di algoritmi di machine learning come backpropagation per l'addestramento di reti neurali. Durante l'esecuzione impaziente, utilizzare tf.GradientTape per tracciare le operazioni per il calcolo dei gradienti in un secondo momento.

È possibile utilizzare tf.GradientTape per allenare e / o calcolare i gradienti in desiderosi. È particolarmente utile per cicli di allenamento complicati.

Poiché durante ciascuna chiamata possono verificarsi diverse operazioni, tutte le operazioni di inoltro vengono registrate su un "nastro". Per calcolare il gradiente, riprodurre il nastro all'indietro e quindi scartarlo. Un particolare tf.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)

Allena 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 allenabili 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 controlla l'output in un'esecuzione entusiasta:

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

Mentre i modelli di keras hanno un ciclo di allenamento fit (usando il metodo fit ), a volte è necessaria una maggiore personalizzazione. Ecco un esempio di un ciclo di allenamento implementato con desideroso:

 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 di tf.Tensor mutabili a tf.Tensor accede durante l'allenamento per facilitare la differenziazione automatica.

Le raccolte di variabili possono essere incapsulate in livelli o modelli, insieme ai metodi che operano su di essi. Vedi Livelli e modelli di Keras personalizzati per i dettagli. La differenza principale tra livelli 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 l'aggiornamento delle variabili in base ai derivati.
 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

Salvataggio basato su oggetti

Un modello tf.keras.Model include un metodo save_weights save_weights che 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 checkpoint di 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('./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>

Per salvare e caricare 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 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>

Metriche orientate agli oggetti

tf.keras.metrics sono memorizzati come oggetti. Aggiorna una metrica passando i nuovi dati al callable e recupera il risultato usando 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>

Sommari e TensorBoard

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

È possibile utilizzare tf.summary per registrare riepiloghi di variabili nell'esecuzione desiderosa. 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.1595467632.kokoro-gcp-ubuntu-prod-341901152.10415.619671.v2

Argomenti avanzati di differenziazione automatica

Modelli dinamici

tf.GradientTape può essere utilizzato anche in modelli dinamici. Questo esempio per un algoritmo di ricerca della riga di backtracking assomiglia al normale codice NumPy, tranne per il fatto che ci sono gradienti ed è differenziabile, 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

I gradienti personalizzati sono un modo semplice per sovrascrivere i gradienti. All'interno della funzione forward, definire il gradiente rispetto agli input, output o risultati intermedi. Ad esempio, ecco un modo semplice per tagliare 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) che viene 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 automaticamente scaricato nelle GPU durante l'esecuzione desiderosa. 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.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

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

 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.

Punti di riferimenti

Per i modelli pesanti di calcolo, come l'addestramento ResNet50 su una GPU, le prestazioni di esecuzione desiderose sono paragonabili all'esecuzione di tf.function . Ma questo divario aumenta per i modelli con meno calcoli e c'è ancora del lavoro da fare per ottimizzare i percorsi di hot code per i modelli con molte piccole operazioni.

Lavora con le funzioni

Mentre un'esecuzione entusiasta rende lo sviluppo e il debug più interattivi, l'esecuzione di grafici in stile TensorFlow 1.x presenta vantaggi per la formazione distribuita, l'ottimizzazione delle prestazioni e l'implementazione della produzione. Per colmare questa lacuna, TensorFlow 2.0 introduce le function tramite l'API tf.function . Per ulteriori informazioni, consultare la guida alle funzioni tf .