Esecuzione entusiasta

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

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

Eager Execution è una piattaforma flessibile di machine learning per la ricerca e la sperimentazione, che fornisce:

  • Un'interfaccia intuitiva -Struttura il codice in modo naturale e utilizzare strutture dati Python. Iterare rapidamente su piccoli modelli e piccoli dati.
  • Più facile il debug ops-Call direttamente a ispezionare modelli in esecuzione e le modifiche di prova. Utilizza gli strumenti di debug Python standard per la segnalazione immediata degli errori.
  • Controllo naturale flusso flusso di controllo -Uso Python invece di flusso di controllo grafico, semplificando la specificazione di modelli dinamici.

L'esecuzione Eager 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 ansiosa è abilitata per impostazione predefinita.

tf.executing_eagerly()
True

Ora puoi eseguire operazioni TensorFlow e i risultati torneranno immediatamente:

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

L'abilitazione dell'esecuzione desiderosa cambia il comportamento delle operazioni di TensorFlow: ora valutano e restituiscono immediatamente i loro valori a Python. tf.Tensor oggetti valori concreti riferimento invece di maniglie simbolici ai nodi in un grafico computazionale. Dal momento che non v'è un grafico di calcolo per creare ed eseguire successivamente in una sessione, è facilmente ispezionabile risultati utilizzando print() o di un debugger. La valutazione, la stampa e il controllo dei valori del tensore non interrompe il flusso per il calcolo dei gradienti.

Esecuzione Eager funziona bene con NumPy . Operazioni NumPy accettano tf.Tensor argomenti. Le tensorflow tf.math operazioni convertono oggetti Python e array NumPy per tf.Tensor oggetti. Il tf.Tensor.numpy metodo 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 dell'esecuzione entusiasta è che tutte le funzionalità della lingua host sono disponibili durante l'esecuzione del modello. Così, per 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.

Desideroso di formazione

Gradienti di calcolo

Differenziazione automatica è utile per implementare algoritmi di apprendimento macchina come backpropagation per la formazione di reti neurali. Durante l'esecuzione ansioso, utilizzare tf.GradientTape di tracciare le operazioni per il calcolo di gradienti più tardi.

È possibile utilizzare tf.GradientTape per addestrare e o gradienti / Calcola in ansiosa. È particolarmente utile per cicli di allenamento complicati.

Poiché durante ogni chiamata possono verificarsi diverse operazioni, tutte le operazioni di inoltro vengono registrate su un "nastro". Per calcolare il gradiente, riproduci il nastro all'indietro e poi scartalo. Un particolare tf.GradientTape può calcolare un solo 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 addestrabili in un ambiente di esecuzione desideroso.

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

Anche senza addestramento, chiama il modello e ispeziona l'output in esecuzione 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]]

Mentre i modelli Keras hanno un ciclo di formazione integrato (utilizzando il fit metodo), a volte avete bisogno di più personalizzazione. Ecco un esempio di un ciclo di formazione 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 memorizzano mutevole tf.Tensor -come valori a cui si accede durante l'allenamento per rendere la differenziazione automatica più facile.

Le raccolte di variabili possono essere incapsulate in livelli o modelli, insieme ai metodi che operano su di esse. Vedere strati personalizzati Keras e modelli per i dettagli. La differenza principale tra gli strati e modelli è che i modelli di aggiungere 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])

Prossimo:

  1. Crea il modello.
  2. Le derivate di una funzione di perdita rispetto ai parametri del modello.
  3. Una strategia per aggiornare le 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: 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

Salvataggio basato su oggetti

Un tf.keras.Model include un comodo save_weights metodo che permette di creare facilmente un posto di blocco:

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

Utilizzando tf.train.Checkpoint si può prendere il pieno controllo su questo processo.

Questa sezione è una versione abbreviata del guida ai posti di blocco di formazione .

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 modelli di carico, tf.train.Checkpoint memorizza lo stato interno di oggetti, senza la necessità di variabili nascoste. Per registrare lo stato di un model , un optimizer , e un passo globale, li passano ad 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>

Metriche orientate agli oggetti

tf.keras.metrics sono memorizzati come oggetti. Aggiornare una metrica facendo passare i nuovi dati al callable, e recuperare il risultato utilizzando il tf.keras.metrics.result metodo, 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 formazione del modello. Utilizza eventi di riepilogo che vengono scritti durante l'esecuzione del programma.

È possibile utilizzare tf.summary per sintesi record di variabile in esecuzione ansioso. Ad esempio, per registrare una sintesi di loss una volta ogni 100 passi di formazione:

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

Argomenti di differenziazione automatica avanzata

Modelli dinamici

tf.GradientTape può essere utilizzato anche in modelli dinamici. In questo esempio per una linea di backtracking Ricerca algoritmo assomiglia normale codice NumPy, tranne che ci sono pendenze ed è differenziabile, nonostante il flusso di controllo complesso:

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 ignorare i gradienti. All'interno della funzione in avanti, definire il gradiente rispetto agli ingressi, alle uscite o ai 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, il log1pexp funzione può essere analiticamente semplificata con un gradiente personalizzato. L'attuazione sotto riutilizzi il valore per tf.exp(x) che è calcolato durante il forward pass-rendendolo più efficiente eliminando 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 scaricato automaticamente sulle GPU durante l'esecuzione ansiosa. Se si vuole controllo su dove un calcolo eseguito è possibile racchiudere in un tf.device('/gpu:0') blocco (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: 1.007401466369629 secs
GPU: 0.04124784469604492 secs

Un tf.Tensor oggetto può essere copiato su un altro dispositivo 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 /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.

Punti di riferimenti

Per i modelli di calcolo pesanti, come ResNet50 una formazione a GPU, prestazioni di esecuzione ansioso è paragonabile a tf.function esecuzione. Ma questo divario aumenta per i modelli con meno calcolo e c'è del lavoro da fare per ottimizzare i percorsi del codice caldo per i modelli con molte piccole operazioni.

Lavora con le funzioni

Mentre l'esecuzione entusiasta 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 l'implementazione della produzione. Per colmare questa lacuna, tensorflow 2.0 introduce function s tramite l' tf.function API. Per ulteriori informazioni, consultare la tf.function guida.