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

Introduzione ai grafici e alle funzioni tf

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

Panoramica

Questa guida va sotto la superficie di TensorFlow e Keras per vedere come funziona TensorFlow. Se invece desideri iniziare subito con Keras, consulta la nostra raccolta di guide Keras .

In questa guida vedrai il nucleo di come TensorFlow ti consente di apportare semplici modifiche al tuo codice per ottenere grafici, come vengono memorizzati e rappresentati e come puoi usarli per accelerare ed esportare i tuoi modelli.

Questa è una breve introduzione; per un'introduzione completa a questi concetti, vedere la guida tf.function .

Cosa sono i grafici?

Nelle tre guide precedenti, hai visto TensorFlow in esecuzione con entusiasmo . Ciò significa che le operazioni TensorFlow vengono eseguite da Python, operazione per operazione e restituiscono i risultati a Python. Eager TensorFlow sfrutta le GPU, consentendo di posizionare variabili, tensori e persino operazioni su GPU e TPU. È anche facile eseguire il debug.

Per alcuni utenti, potresti non aver mai bisogno o non voler lasciare Python.

Tuttavia, l'esecuzione di TensorFlow op-by-op in Python impedisce una serie di accelerazioni altrimenti disponibili. Se puoi estrarre i calcoli tensoriali da Python, puoi trasformarli in un grafico .

I grafici sono strutture di dati che contengono un insieme di oggetti tf.Operation , che rappresentano unità di calcolo; e tf.Tensor oggetti tf.Tensor , che rappresentano le unità di dati che scorrono tra le operazioni. Sono definiti in un contesto tf.Graph . Poiché questi grafici sono strutture di dati, possono essere salvati, eseguiti e ripristinati senza il codice Python originale.

Questo è l'aspetto di un semplice grafico a due strati quando visualizzato in TensorBoard.

un grafico tensorflow a due strati

I vantaggi dei grafici

Con un grafico, hai una grande flessibilità. Puoi utilizzare il tuo grafico TensorFlow in ambienti che non dispongono di un interprete Python, come applicazioni mobili, dispositivi incorporati e server back-end. TensorFlow utilizza i grafici come formato per i modelli salvati quando li esporta da Python.

Anche i grafici sono facilmente ottimizzabili, consentendo al compilatore di eseguire trasformazioni come:

  • Calcola staticamente il valore dei tensori piegando i nodi costanti ("ripiegamento costante") .
  • Separare le sottoparti di un calcolo che sono indipendenti e suddividerle tra thread o dispositivi.
  • Semplifica le operazioni aritmetiche eliminando le sottoespressioni comuni.

Esiste un intero sistema di ottimizzazione, Grappler , per eseguire questa e altre accelerazioni.

In breve, i grafici sono estremamente utili e consentono al tuo TensorFlow di funzionare velocemente , in parallelo e in modo efficiente su più dispositivi .

Tuttavia, vuoi comunque definire i nostri modelli di machine learning (o altri calcoli) in Python per comodità, e quindi costruire automaticamente i grafici quando ne hai bisogno.

Tracciare grafici

Il modo in cui crei un grafico in TensorFlow consiste nell'usare tf.function , sia come chiamata diretta che come decoratore.

import tensorflow as tf
import timeit
from datetime import datetime
# Define a Python function
def function_to_get_faster(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Create a `Function` object that contains a graph
a_function_that_uses_a_graph = tf.function(function_to_get_faster)

# Make some tensors
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

# It just works!
a_function_that_uses_a_graph(x1, y1, b1).numpy()
array([[12.]], dtype=float32)

tf.function funzioni tf.function -ized sono chiamabili Python che funzionano allo stesso modo dei loro equivalenti Python. Hanno una classe particolare ( python.eager.def_function.Function ), ma per te agiscono proprio come la versione non tracciata.

tf.function traccia ricorsivamente qualsiasi funzione Python che chiama.

def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes inner_function() as well as outer_function()
outer_function(tf.constant([[1.0, 2.0]])).numpy()
array([[12.]], dtype=float32)

Se hai utilizzato TensorFlow 1.x, noterai che in nessun momento è stato necessario definire un Placeholder o tf.Sesssion .

Controllo del flusso ed effetti collaterali

Il controllo del flusso e i loop vengono convertiti in TensorFlow tramite tf.autograph per impostazione predefinita. Autograph utilizza una combinazione di metodi, tra cui la standardizzazione dei costrutti di loop, lo srotolamento e la manipolazione AST .

def my_function(x):
  if tf.reduce_sum(x) <= 1:
    return x * x
  else:
    return x-1

a_function = tf.function(my_function)

print("First branch, with graph:", a_function(tf.constant(1.0)).numpy())
print("Second branch, with graph:", a_function(tf.constant([5.0, 5.0])).numpy())
First branch, with graph: 1.0
Second branch, with graph: [4. 4.]

Puoi chiamare direttamente la conversione degli autografi per vedere come Python viene convertito in operazioni TensorFlow. Questo è, per lo più, illeggibile, ma puoi vedere la trasformazione.

# Don't read the output too carefully.
print(tf.autograph.to_code(my_function))
def tf__my_function(x):
    with ag__.FunctionScope('my_function', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (retval_, do_return)

        def set_state(vars_):
            nonlocal retval_, do_return
            (retval_, do_return) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) * ag__.ld(x))
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) - 1)
            except:
                do_return = False
                raise
        ag__.if_stmt((ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) <= 1), if_body, else_body, get_state, set_state, ('retval_', 'do_return'), 2)
        return fscope.ret(retval_, do_return)


Autograph converte automaticamente le clausole if-then , loop, break , return , continue e altro ancora.

La maggior parte delle volte, Autograph funzionerà senza particolari considerazioni. Tuttavia, ci sono alcuni avvertimenti e la guida alle funzioni tf. Può aiutare qui, così come il riferimento completo dell'autografo

Vedendo la velocità

Il semplice avvolgimento di una funzione che utilizza il tensore in tf.function non accelera automaticamente il codice. Per piccole funzioni chiamate più volte su una singola macchina, il sovraccarico di chiamare un grafico o un frammento di grafico può dominare il runtime. Inoltre, se la maggior parte del calcolo stava già avvenendo su un acceleratore, come pile di convoluzioni pesanti per GPU, la velocità del grafico non sarà grande.

Per calcoli complicati, i grafici possono fornire una notevole accelerazione. Questo perché i grafici riducono la comunicazione da Python a dispositivo ed eseguono alcune accelerazioni.

Questo codice volte alcune esecuzioni su alcuni piccoli strati densi.

# Create an oveerride model to classify pictures
class SequentialModel(tf.keras.Model):
  def __init__(self, **kwargs):
    super(SequentialModel, self).__init__(**kwargs)
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return x

input_data = tf.random.uniform([60, 28, 28])

eager_model = SequentialModel()
graph_model = tf.function(eager_model)

print("Eager time:", timeit.timeit(lambda: eager_model(input_data), number=10000))
print("Graph time:", timeit.timeit(lambda: graph_model(input_data), number=10000))
Eager time: 5.330957799000316
Graph time: 3.0375442899999143

Funzioni polimorfiche

Quando si traccia una funzione, si crea un oggetto Function che è polimorfico . Una funzione polimorfica è un Python richiamabile che incapsula diversi grafici di funzioni concrete dietro un'API.

È possibile utilizzare questa Function su tutti i diversi tipi di dtypes e forme. Ogni volta che lo invocate con una nuova firma dell'argomento, la funzione originale viene tracciata nuovamente con i nuovi argomenti. La Function quindi memorizza il tf.Graph corrispondente a quella traccia in una concrete_function . Se la funzione è già stata tracciata con quel tipo di argomento, ottieni solo il tuo grafico pre-tracciato.

Concettualmente, quindi:

  • Un tf.Graph è la struttura dati grezza e portatile che descrive un calcolo
  • Una Function è un caching, tracing, dispatcher su ConcreteFunctions
  • Una ConcreteFunction è un wrapper compatibile con entusiasmo attorno a un grafico che ti consente di eseguire il grafico da Python

Ispezione delle funzioni polimorfiche

Puoi controllare a_function , che è il risultato della chiamata di tf.function sulla funzione Python my_function . In questo esempio, la chiamata a_function con tre tipi di argomenti produce tre diverse funzioni concrete.

print(a_function)

print("Calling a `Function`:")
print("Int:", a_function(tf.constant(2)))
print("Float:", a_function(tf.constant(2.0)))
print("Rank-1 tensor of floats", a_function(tf.constant([2.0, 2.0, 2.0])))
<tensorflow.python.eager.def_function.Function object at 0x7f7d342602b0>
Calling a `Function`:
Int: tf.Tensor(1, shape=(), dtype=int32)
Float: tf.Tensor(1.0, shape=(), dtype=float32)
Rank-1 tensor of floats tf.Tensor([1. 1. 1.], shape=(3,), dtype=float32)

# Get the concrete function that works on floats
print("Inspecting concrete functions")
print("Concrete function for float:")
print(a_function.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.float32)))
print("Concrete function for tensor of floats:")
print(a_function.get_concrete_function(tf.constant([2.0, 2.0, 2.0])))
Inspecting concrete functions
Concrete function for float:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()
Concrete function for tensor of floats:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=(3,)
  Returns:
    float32 Tensor, shape=(3,)

# Concrete functions are callable
# Note: You won't normally do this, but instead just call the containing `Function`
cf = a_function.get_concrete_function(tf.constant(2))
print("Directly calling a concrete function:", cf(tf.constant(2)))
Directly calling a concrete function: tf.Tensor(1, shape=(), dtype=int32)

In questo esempio, stai vedendo abbastanza lontano nello stack. A meno che tu non stia gestendo specificamente la traccia, normalmente non avrai bisogno di chiamare le funzioni concrete direttamente come mostrato qui.

Ritorno a un'esecuzione impaziente

Potresti trovarti a guardare le tracce dello stack lungo, specialmente quelle che si riferiscono a tf.Graph o with tf.Graph().as_default() . Ciò significa che probabilmente stai correndo in un contesto grafico. Le funzioni principali in TensorFlow utilizzano contesti grafici, come model.fit() di Keras.

Spesso è molto più facile eseguire il debug di un'esecuzione impaziente. Le tracce dello stack dovrebbero essere relativamente brevi e facili da comprendere.

In situazioni in cui il grafico rende complicato il debug, puoi tornare a utilizzare un'esecuzione impaziente per eseguire il debug.

Ecco alcuni modi per assicurarti di correre con entusiasmo:

  • Chiama modelli e livelli direttamente come richiamabili

  • Quando si utilizza Keras compile / fit, in fase di compilazione usa model.compile(run_eagerly=True)

  • Imposta la modalità di esecuzione globale tramite tf.config.run_functions_eagerly(True)

Utilizzando run_eagerly=True

# Define an identity layer with an eager side effect
class EagerLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(EagerLayer, self).__init__(**kwargs)
    # Do some kind of initialization here

  def call(self, inputs):
    print("\nCurrently running eagerly", str(datetime.now()))
    return inputs
# Create an override model to classify pictures, adding the custom layer
class SequentialModel(tf.keras.Model):
  def __init__(self):
    super(SequentialModel, self).__init__()
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)
    self.eager = EagerLayer()

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return self.eager(x)

# Create an instance of this model
model = SequentialModel()

# Generate some nonsense pictures and labels
input_data = tf.random.uniform([60, 28, 28])
labels = tf.random.uniform([60])

loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

Innanzitutto, compila il modello senza entusiasmo. Notare che il modello non viene tracciato; nonostante il nome, compile imposta solo funzioni di perdita, ottimizzazione e altri parametri di addestramento.

model.compile(run_eagerly=False, loss=loss_fn)

Ora, chiama fit e vedi che la funzione viene tracciata (due volte) e quindi l'effetto desideroso non viene più eseguito.

model.fit(input_data, labels, epochs=3)
Epoch 1/3

Currently running eagerly 2020-11-04 02:22:28.114630

Currently running eagerly 2020-11-04 02:22:28.233822
2/2 [==============================] - 0s 2ms/step - loss: 0.9890
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0017
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 5.4478e-04

<tensorflow.python.keras.callbacks.History at 0x7f7cb02b5e80>

Tuttavia, se esegui anche una sola epoca con impazienza, puoi vedere due volte l'effetto collaterale impaziente.

print("Running eagerly")
# When compiling the model, set it to run eagerly
model.compile(run_eagerly=True, loss=loss_fn)

model.fit(input_data, labels, epochs=1)
Running eagerly

Currently running eagerly 2020-11-04 02:22:28.449835
1/2 [==============>...............] - ETA: 0s - loss: 7.8712e-04
Currently running eagerly 2020-11-04 02:22:28.472645
2/2 [==============================] - 0s 6ms/step - loss: 4.1988e-04

<tensorflow.python.keras.callbacks.History at 0x7f7cb0230780>

Utilizzando run_functions_eagerly

Puoi anche impostare globalmente tutto per funzionare con entusiasmo. Questo è un interruttore che bypassa le funzioni tracciate della funzione polimorfica e chiama direttamente la funzione originale. Puoi usarlo per il debug.

# Now, globally set everything to run eagerly
tf.config.run_functions_eagerly(True)
print("Run all functions eagerly.")

# Create a polymorphic function
polymorphic_function = tf.function(model)

print("Tracing")
# This does, in fact, trace the function
print(polymorphic_function.get_concrete_function(input_data))

print("\nCalling twice eagerly")
# When you run the function again, you will see the side effect
# twice, as the function is running eagerly.
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
Run all functions eagerly.
Tracing

Currently running eagerly 2020-11-04 02:22:28.502694
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Calling twice eagerly

Currently running eagerly 2020-11-04 02:22:28.507036

Currently running eagerly 2020-11-04 02:22:28.508331

# Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)
WARNING:tensorflow:From <ipython-input-1-782fe9ce7b18>:2: experimental_run_functions_eagerly (from tensorflow.python.eager.def_function) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.run_functions_eagerly` instead of the experimental version.

Tracciamento e prestazioni

La tracciatura costa alcune spese generali. Sebbene il tracciamento di piccole funzioni sia rapido, i modelli di grandi dimensioni possono richiedere un tempo notevole per essere tracciati. Questo investimento viene solitamente ripagato rapidamente con un aumento delle prestazioni, ma è importante essere consapevoli che le prime epoche di qualsiasi formazione su modelli di grandi dimensioni possono essere più lente a causa del tracciamento.

Non importa quanto sia grande il tuo modello, vuoi evitare di tracciare frequentemente. Questa sezione della guida tf.function discute come impostare le specifiche di input e utilizzare argomenti tensoriali per evitare il ritracciamento. Se scopri che stai ottenendo prestazioni insolitamente scarse, è bene controllare per vedere se stai ripercorrendo accidentalmente.

Puoi aggiungere un effetto collaterale solo desideroso (come stampare un argomento Python) in modo da poter vedere quando la funzione viene tracciata. Qui si vedono ulteriori ritracciamenti perché i nuovi argomenti Python attivano sempre il ritracciamento.

# Use @tf.function decorator
@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!")  # This eager
  return x * x + tf.constant(2)

# This is traced the first time
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect
print(a_function_with_python_side_effect(tf.constant(3)))

# This retraces each time the Python argument changes,
# as a Python argument could be an epoch count or other
# hyperparameter
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

Prossimi passi

Puoi leggere una discussione più approfondita sia nella pagina di riferimento API tf.function che nella guida .