Hai una domanda? Connettiti con la community al forum TensorFlow Visita il forum

TensorFlow efficace 2

Ci sono più modifiche in TensorFlow 2.0 per rendere gli utenti di TensorFlow più produttivi. TensorFlow 2.0 rimuove le API ridondanti , rende le API più coerenti ( RNN unificate , ottimizzatori unificati ) e si integra meglio con il runtime Python con l' esecuzione Eager .

Molte RFC hanno spiegato le modifiche apportate alla creazione di TensorFlow 2.0. Questa guida presenta una visione di come dovrebbe essere lo sviluppo in TensorFlow 2.0. Si presume che tu abbia una certa familiarità con TensorFlow 1.x.

Un breve riassunto dei principali cambiamenti

Pulizia API

Molte API sono state eliminate o spostate in TF 2.0. Alcuni dei principali cambiamenti includono la rimozione di tf.app , tf.flags e tf.logging a favore dell'absl-py ora open-source, il reinserimento dei progetti che vivevano in tf.contrib e la pulizia dello spazio dei nomi principale tf.* spostare le funzioni meno usate in sottopacchetti come tf.math . Alcune API sono state sostituite con i loro equivalenti 2.0: tf.summary , tf.keras.metrics e tf.keras.optimizers . Il modo più semplice per applicare automaticamente queste ridenominazioni è utilizzare lo script di aggiornamento v2 .

Esecuzione desiderosa

TensorFlow 1.X richiede agli utenti di unire manualmente un albero di sintassi astratto (il grafico) effettuando chiamate API tf.* . Richiede quindi agli utenti di compilare manualmente l'albero della sintassi astratto passando una serie di tensori di output e di input a una chiamata session.run() . TensorFlow 2.0 viene eseguito con entusiasmo (come fa normalmente Python) e in 2.0, i grafici e le sessioni dovrebbero sembrare dettagli di implementazione.

Un sottoprodotto notevole dell'esecuzione desiderosa è che tf.control_dependencies() non è più richiesto, poiché tutte le righe di codice vengono eseguite in ordine (all'interno di una funzione tf.function , il codice con effetti collaterali viene eseguito nell'ordine scritto).

Niente più globali

TensorFlow 1.X si basava molto su spazi dei nomi implicitamente globali. Quando si chiama tf.Variable() , viene inserito nel grafico predefinito e rimane lì, anche se si perde traccia della variabile Python che punta ad esso. Potresti quindi recuperare quel tf.Variable , ma solo se conoscessi il nome con cui era stato creato. Questo era difficile da fare se non avevi il controllo della creazione della variabile. Di conseguenza, sono proliferati tutti i tipi di meccanismi per tentare di aiutare gli utenti a ritrovare le loro variabili e per i framework di trovare variabili create dall'utente: ambiti variabili, raccolte globali, metodi di supporto come tf.get_global_step() , tf.global_variables_initializer() , gli ottimizzatori calcolano implicitamente i gradienti su tutte le variabili addestrabili e così via. TensorFlow 2.0 elimina tutti questi meccanismi ( Variabili 2.0 RFC ) a favore del meccanismo predefinito: Tieni traccia delle tue variabili! Se si perde traccia di una tf.Variable , viene raccolta la spazzatura.

Il requisito di tenere traccia delle variabili crea del lavoro extra per l'utente, ma con gli oggetti Keras (vedi sotto), il carico è ridotto al minimo.

Funzioni, non sessioni

Una chiamata session.run() è quasi come una chiamata di funzione: specifichi gli input e la funzione da chiamare e ottieni un insieme di output. In TensorFlow 2.0, puoi decorare una funzione Python usando tf.function() per contrassegnarla per la compilazione JIT in modo che TensorFlow la esegua come un singolo grafico ( Functions 2.0 RFC ). Questo meccanismo consente a TensorFlow 2.0 di ottenere tutti i vantaggi della modalità grafico:

  • Prestazioni: la funzione può essere ottimizzata (eliminazione dei nodi, fusione del kernel, ecc.)
  • Portabilità: la funzione può essere esportata / reimportata ( SavedModel 2.0 RFC ), consentendo agli utenti di riutilizzare e condividere funzioni TensorFlow modulari.
# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TensorFlow 2.0
outputs = f(input)

Con il potere di intervallare liberamente codice Python e TensorFlow, gli utenti possono trarre vantaggio dall'espressività di Python. Tuttavia, TensorFlow portatile viene eseguito in contesti senza un interprete Python, come mobile, C ++ e JavaScript. Per aiutare gli utenti a evitare di dover riscrivere il codice quando aggiungono la funzione @tf.function , AutoGraph converte un sottoinsieme di costrutti Python nei loro equivalenti TensorFlow:

  • for / while -> tf.while_loop ( break e continue sono supportati)
  • if -> tf.cond
  • for _ in dataset -> dataset.reduce

AutoGraph supporta annidamenti arbitrari del flusso di controllo, che consente di implementare in modo efficiente e conciso molti programmi ML complessi come modelli di sequenza, apprendimento per rinforzo, cicli di addestramento personalizzati e altro ancora.

Raccomandazioni per idiomatic TensorFlow 2.0

Rifattorizza il tuo codice in funzioni più piccole

Un modello di utilizzo comune in TensorFlow 1.X era la strategia del "lavello della cucina", in cui l'unione di tutti i calcoli possibili veniva stabilita preventivamente e quindi i tensori selezionati venivano valutati tramite session.run() . In TensorFlow 2.0, gli utenti dovrebbero effettuare il refactoring del codice in funzioni più piccole che vengono chiamate secondo necessità. In generale, non è necessario decorare ciascuna di queste funzioni più piccole con tf.function ; usa tf.function solo per decorare tf.function di alto livello, ad esempio un passaggio di addestramento o il passaggio in avanti del tuo modello.

Usa i livelli ei modelli di Keras per gestire le variabili

I modelli e i livelli di Keras offrono variables convenienti e proprietà trainable_variables , che raccolgono ricorsivamente tutte le variabili dipendenti. Ciò semplifica la gestione delle variabili localmente nel luogo in cui vengono utilizzate.

Contrasto:

def dense(x, W, b):
  return tf.nn.sigmoid(tf.matmul(x, W) + b)

@tf.function
def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):
  x = dense(x, w0, b0)
  x = dense(x, w1, b1)
  x = dense(x, w2, b2)
  ...

# You still have to manage w_i and b_i, and their shapes are defined far away from the code.

con la versione Keras:

# Each layer can be called, with a signature equivalent to linear(x)
layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]
perceptron = tf.keras.Sequential(layers)

# layers[3].trainable_variables => returns [w3, b3]
# perceptron.trainable_variables => returns [w0, b0, ...]

I layer / modelli Keras ereditano da tf.train.Checkpointable e sono integrati con la funzione @tf.function , che rende possibile il checkpoint o l'esportazione diretta di SavedModels dagli oggetti Keras. Non devi necessariamente utilizzare l'API .fit() di Keras per sfruttare queste integrazioni.

Ecco un esempio di apprendimento del trasferimento che dimostra come Keras semplifica la raccolta di un sottoinsieme di variabili rilevanti. Supponiamo che tu stia addestrando un modello a più teste con un tronco condiviso:

trunk = tf.keras.Sequential([...])
head1 = tf.keras.Sequential([...])
head2 = tf.keras.Sequential([...])

path1 = tf.keras.Sequential([trunk, head1])
path2 = tf.keras.Sequential([trunk, head2])

# Train on primary dataset
for x, y in main_dataset:
  with tf.GradientTape() as tape:
    # training=True is only needed if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    prediction = path1(x, training=True)
    loss = loss_fn_head1(prediction, y)
  # Simultaneously optimize trunk and head1 weights.
  gradients = tape.gradient(loss, path1.trainable_variables)
  optimizer.apply_gradients(zip(gradients, path1.trainable_variables))

# Fine-tune second head, reusing the trunk
for x, y in small_dataset:
  with tf.GradientTape() as tape:
    # training=True is only needed if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    prediction = path2(x, training=True)
    loss = loss_fn_head2(prediction, y)
  # Only optimize head2 weights, not trunk weights
  gradients = tape.gradient(loss, head2.trainable_variables)
  optimizer.apply_gradients(zip(gradients, head2.trainable_variables))

# You can publish just the trunk computation for other people to reuse.
tf.saved_model.save(trunk, output_path)

Combina tf.data.Datasets e @ tf.function

Quando esegui l'iterazione su dati di addestramento che si adattano alla memoria, sentiti libero di usare l'iterazione Python regolare. Altrimenti,tf.data.Dataset è il modo migliore per trasmettere in streaming i dati di addestramento dal disco. I set di dati sono iterabili (non iteratori) e funzionano come gli altri iterabili Python in modalità Eager. È possibile utilizzare completamente le funzionalità di prelettura / streaming asincrono del set di dati avvolgendo il codice in tf.function() , che sostituisce l'iterazione di Python con le operazioni del grafico equivalenti utilizzando AutoGraph.

@tf.function
def train(model, dataset, optimizer):
  for x, y in dataset:
    with tf.GradientTape() as tape:
      # training=True is only needed if there are layers with different
      # behavior during training versus inference (e.g. Dropout).
      prediction = model(x, training=True)
      loss = loss_fn(prediction, y)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

Se utilizzi l'API Keras .fit() , non dovrai preoccuparti dell'iterazione del set di dati.

model.compile(optimizer=optimizer, loss=loss_fn)
model.fit(dataset)

Approfitta di AutoGraph con il flusso di controllo Python

AutoGraph fornisce un modo per convertire il flusso di controllo dipendente dai dati in equivalenti in modalità grafico come tf.cond e tf.while_loop .

Un luogo comune in cui appare il flusso di controllo dipendente dai dati è nei modelli di sequenza. tf.keras.layers.RNN avvolge una cella RNN, consentendo di srotolare staticamente o dinamicamente la ricorrenza. Per motivi di dimostrazione, è possibile reimplementare lo srotolamento dinamico come segue:

class DynamicRNN(tf.keras.Model):

  def __init__(self, rnn_cell):
    super(DynamicRNN, self).__init__(self)
    self.cell = rnn_cell

  def call(self, input_data):
    # [batch, time, features] -> [time, batch, features]
    input_data = tf.transpose(input_data, [1, 0, 2])
    outputs = tf.TensorArray(tf.float32, input_data.shape[0])
    state = self.cell.zero_state(input_data.shape[1], dtype=tf.float32)
    for i in tf.range(input_data.shape[0]):
      output, state = self.cell(input_data[i], state)
      outputs = outputs.write(i, output)
    return tf.transpose(outputs.stack(), [1, 0, 2]), state

Per una panoramica più dettagliata delle funzionalità di AutoGraph, vedere la guida .

tf.metrics aggrega i dati e tf.summary li registra

Per registrare i riepiloghi, utilizzare tf.summary.(scalar|histogram|...) e reindirizzarlo a un writer utilizzando un gestore di contesto. (Se si omette il gestore di contesto, non accade nulla.) A differenza di TF 1.x, i riepiloghi vengono emessi direttamente al writer; non esiste add_summary() "unione" separata e nessuna chiamata add_summary() separata, il che significa che il valore del step deve essere fornito nel callsite.

summary_writer = tf.summary.create_file_writer('/tmp/summaries')
with summary_writer.as_default():
  tf.summary.scalar('loss', 0.1, step=42)

Per aggregare i dati prima di registrarli come riepiloghi, utilizzare tf.metrics . Le metriche sono stateful: accumulano valori e restituiscono un risultato cumulativo quando chiami .result() . Cancella i valori accumulati con .reset_states() .

def train(model, optimizer, dataset, log_freq=10):
  avg_loss = tf.keras.metrics.Mean(name='loss', dtype=tf.float32)
  for images, labels in dataset:
    loss = train_step(model, optimizer, images, labels)
    avg_loss.update_state(loss)
    if tf.equal(optimizer.iterations % log_freq, 0):
      tf.summary.scalar('loss', avg_loss.result(), step=optimizer.iterations)
      avg_loss.reset_states()

def test(model, test_x, test_y, step_num):
  # training=False is only needed if there are layers with different
  # behavior during training versus inference (e.g. Dropout).
  loss = loss_fn(model(test_x, training=False), test_y)
  tf.summary.scalar('loss', loss, step=step_num)

train_summary_writer = tf.summary.create_file_writer('/tmp/summaries/train')
test_summary_writer = tf.summary.create_file_writer('/tmp/summaries/test')

with train_summary_writer.as_default():
  train(model, optimizer, dataset)

with test_summary_writer.as_default():
  test(model, test_x, test_y, optimizer.iterations)

Visualizza i riepiloghi generati puntando TensorBoard nella directory del registro di riepilogo:

tensorboard --logdir /tmp/summaries

Usa tf.config.experimental_run_functions_eagerly () durante il debug

In TensorFlow 2.0, l'esecuzione Eager ti consente di eseguire il codice passo dopo passo per ispezionare forme, tipi di dati e valori. Alcune API, come tf.function , tf.keras , ecc. Sono progettate per utilizzare l'esecuzione di Graph, per prestazioni e portabilità. Durante il debug, usa tf.config.experimental_run_functions_eagerly(True) per usare l'esecuzione Eager all'interno di questo codice.

Per esempio:

@tf.function
def f(x):
  if x > 0:
    import pdb
    pdb.set_trace()
    x = x + 1
  return x

tf.config.experimental_run_functions_eagerly(True)
f(tf.constant(1))
>>> f()
-> x = x + 1
(Pdb) l
  6     @tf.function
  7     def f(x):
  8       if x > 0:
  9         import pdb
 10         pdb.set_trace()
 11  ->     x = x + 1
 12       return x
 13
 14     tf.config.experimental_run_functions_eagerly(True)
 15     f(tf.constant(1))
[EOF]

Funziona anche all'interno dei modelli Keras e di altre API che supportano l'esecuzione Eager:

class CustomModel(tf.keras.models.Model):

  @tf.function
  def call(self, input_data):
    if tf.reduce_mean(input_data) > 0:
      return input_data
    else:
      import pdb
      pdb.set_trace()
      return input_data // 2


tf.config.experimental_run_functions_eagerly(True)
model = CustomModel()
model(tf.constant([-2, -4]))
>>> call()
-> return input_data // 2
(Pdb) l
 10         if tf.reduce_mean(input_data) > 0:
 11           return input_data
 12         else:
 13           import pdb
 14           pdb.set_trace()
 15  ->       return input_data // 2
 16
 17
 18     tf.config.experimental_run_functions_eagerly(True)
 19     model = CustomModel()
 20     model(tf.constant([-2, -4]))