Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Introducción a gráficos y funciones.

Ver en TensorFlow.org Ejecutar en Google Colab Ver código fuente en GitHub Descargar cuaderno

Introducción a los gráficos y la función tf.function

Esta guía va debajo de la superficie de TensorFlow y Keras para ver cómo funciona TensorFlow. Si, en cambio, desea comenzar de inmediato con Keras, consulte nuestra colección de guías de Keras .

En esta guía verá el núcleo de cómo TensorFlow le permite realizar cambios simples en su código para obtener gráficos, y cómo se almacenan y representan, y cómo puede usarlos para acelerar y exportar sus modelos.

Esta es una introducción breve; Para obtener una introducción completa a estos conceptos, consulte la guía de tf.function .

¿Qué son los gráficos?

En las tres guías anteriores, ha visto que TensorFlow se ejecuta con entusiasmo . Esto significa que Python ejecuta las operaciones de TensorFlow, operación por operación y devuelve los resultados a Python. Eager TensorFlow aprovecha las GPU, lo que le permite colocar variables, tensores e incluso operaciones en GPU y TPU. También es fácil de depurar.

Para algunos usuarios, es posible que nunca necesite o quiera abandonar Python.

Sin embargo, ejecutar TensorFlow op-by-op en Python evita una gran cantidad de aceleraciones disponibles de otra manera. Si puede extraer cálculos de tensor de Python, puede convertirlos en un gráfico .

Los gráficos son estructuras de datos que contienen un conjunto de objetos tf.Operation , que representan unidades de cálculo; y tf.Tensor objetos, que representan las unidades de datos que fluyen entre operaciones. Se definen en un contexto tf.Graph . Dado que estos gráficos son estructuras de datos, se pueden guardar, ejecutar y restaurar sin el código original de Python.

Así es como se ve un gráfico simple de dos capas cuando se visualiza en TensorBoard.

un gráfico de tensorflow de dos capas

Los beneficios de los gráficos

Con un gráfico, tiene una gran flexibilidad. Puede usar su gráfico TensorFlow en entornos que no tienen un intérprete de Python, como aplicaciones móviles, dispositivos integrados y servidores de fondo. TensorFlow utiliza gráficos como formato para los modelos guardados cuando los exporta desde Python.

Los gráficos también se optimizan fácilmente, permitiendo que el compilador realice transformaciones como:

  • Inferir estáticamente el valor de los tensores plegando nodos constantes en su cálculo ("plegado constante") .
  • Separe las sub-partes de un cálculo que son independientes y divídalas entre hilos o dispositivos.
  • Simplifique las operaciones aritméticas eliminando subexpresiones comunes.

Hay un sistema de optimización completo, Grappler , para realizar esta y otras aceleraciones.

En resumen, los gráficos son extremadamente útiles y permiten que su TensorFlow se ejecute rápidamente , se ejecute en paralelo y se ejecute de manera eficiente en múltiples dispositivos .

Sin embargo, aún desea definir nuestros modelos de aprendizaje automático (u otros cálculos) en Python por conveniencia, y luego construir automáticamente gráficos cuando los necesite.

Gráficos de seguimiento

La forma de crear un gráfico en TensorFlow es usar tf.function , ya sea como una llamada directa o como decorador.

 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 funciones con función tf son invocables de Python que funcionan igual que sus equivalentes de Python. Tienen una clase particular ( python.eager.def_function.Function ), pero para ti actúan como la versión no rastreada.

tf.function rastrea recursivamente cualquier función de Python que llame.

 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)

Si ha utilizado TensorFlow 1.x, se dará cuenta de que en ningún momento es necesario definir un Placeholder o tf.Sesssion .

Control de flujo y efectos secundarios.

El control de flujo y los bucles se convierten a TensorFlow a través de tf.autograph de forma predeterminada. Autograph utiliza una combinación de métodos, que incluyen la estandarización de construcciones de bucle, desenrollado y manipulación de 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.]

Puede llamar directamente a la conversión de Autógrafos para ver cómo Python se convierte en operaciones TensorFlow. Esto es, en su mayoría, ilegible, pero puedes ver la transformación.

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


Autógrafo convierte automáticamente las cláusulas if-then , bucles, break , return , continue y más.

La mayoría de las veces, Autógrafo funcionará sin consideraciones especiales. Sin embargo, hay algunas advertencias, y la guía de función tf.f puede ayudar aquí, así como la referencia completa del autógrafo

Al ver la velocidad

Simplemente envolver una función de uso de tensor en tf.function no tf.function automáticamente su código. Para funciones pequeñas llamadas algunas veces en una sola máquina, la sobrecarga de llamar a un gráfico o fragmento de gráfico puede dominar el tiempo de ejecución. Además, si la mayor parte del cálculo ya estaba ocurriendo en un acelerador, como pilas de convoluciones pesadas de GPU, la aceleración del gráfico no será grande.

Para cálculos complicados, los gráficos pueden proporcionar una aceleración significativa. Esto se debe a que los gráficos reducen la comunicación de Python a dispositivo y realizan algunas aceleraciones.

Este código multiplica algunas veces en algunas capas pequeñas y densas.

 # 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: 4.9249971129993355
Graph time: 2.026840765000088

Funciones polimórficas

Cuando trazas una función, creas que un objeto Function es polimórfico . Una función polimórfica es un Python invocable que encapsula varios gráficos de funciones concretas detrás de una API.

Puede usar esta Function en todos los diferentes tipos de dtypes y formas. Cada vez que lo invoca con una nueva firma de argumento, la función original se vuelve a rastrear con los nuevos argumentos. La Function almacena el tf.Graph correspondiente a la traza en un concrete_function . Si la función ya ha sido rastreada con ese tipo de argumento, solo obtiene su gráfico pre-trazado.

Conceptualmente, entonces:

  • Un tf.Graph es la estructura de datos sin procesar y portátil que describe un cálculo
  • Una Function es un caché, rastreo, despachador sobre ConcreteFunctions
  • Una función ConcreteFunction es una envoltura compatible con entusiasmo alrededor de un gráfico que le permite ejecutar el gráfico desde Python

Inspeccionar funciones polimórficas

Puede inspeccionar a_function , que es el resultado de llamar a tf.function en la función Python my_function . En este ejemplo, llamar a a_function con tres tipos de argumentos da como resultado tres funciones concretas diferentes.

 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 0x7f466417bf60>
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)

En este ejemplo, estás viendo bastante lejos en la pila. A menos que esté gestionando específicamente el rastreo, normalmente no necesitará llamar a funciones concretas directamente como se muestra aquí.

Volviendo a la ansiosa ejecución

Puede encontrarse mirando rastros largos de la pila, especialmente los que se refieren a tf.Graph o with tf.Graph().as_default() . Esto significa que probablemente esté ejecutando en un contexto gráfico. Las funciones centrales en TensorFlow utilizan contextos gráficos, como el model.fit() Keras.

A menudo es mucho más fácil depurar una ejecución ansiosa. Las trazas de pila deben ser relativamente cortas y fáciles de comprender.

En situaciones en las que el gráfico dificulta la depuración, puede volver a utilizar la ejecución ansiosa para depurar.

Aquí hay formas de asegurarte de que estás corriendo con entusiasmo:

  • Llame a modelos y capas directamente como callables

  • Cuando use Keras compile / fit, en tiempo de compilación use model.compile(run_eagerly=True)

  • Establezca el modo de ejecución global a través de tf.config.run_functions_eagerly(True)

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

Primero, compile el modelo sin entusiasmo. Tenga en cuenta que el modelo no se rastrea; a pesar de su nombre, compile solo configura funciones de pérdida, optimización y otros parámetros de entrenamiento.

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

Ahora, llame al fit y vea que la función se rastrea (dos veces) y luego el efecto ansioso nunca se ejecuta nuevamente.

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

Currently running eagerly 2020-08-04 01:22:21.848492

Currently running eagerly 2020-08-04 01:22:21.955102
2/2 [==============================] - 0s 1ms/step - loss: 1.4056
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0037
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0019

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

Sin embargo, si ejecuta incluso una sola época en ansioso, puede ver el efecto secundario ansioso dos veces.

 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-08-04 01:22:22.152979
1/2 [==============>...............] - ETA: 0s - loss: 8.7806e-04
Currently running eagerly 2020-08-04 01:22:22.173295
2/2 [==============================] - 0s 5ms/step - loss: 4.6877e-04

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

Usando run_functions_eagerly

También puede configurar globalmente todo para que se ejecute con entusiasmo. Tenga en cuenta que esto solo funciona si vuelve a rastrear; las funciones rastreadas permanecerán rastreadas y se ejecutarán como un gráfico.

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

# First, trace the model, triggering the side effect
polymorphic_function = tf.function(model)

# It was traced...
print(polymorphic_function.get_concrete_function(input_data))

# But when you run the function again, the side effect happens (both times).
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
 
Run all functions eagerly.

Currently running eagerly 2020-08-04 01:22:22.202726
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-08-04 01:22:22.206521

Currently running eagerly 2020-08-04 01:22:22.207818

 # Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

 
WARNING:tensorflow:From <ipython-input-17-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.

Rastreo y rendimiento

El rastreo cuesta algunos gastos generales. Aunque el seguimiento de funciones pequeñas es rápido, los modelos grandes pueden tardar un tiempo notable en rastrear. Esta inversión generalmente se amortiza rápidamente con un aumento de rendimiento, pero es importante tener en cuenta que las primeras épocas de cualquier entrenamiento de modelos grandes pueden ser más lentas debido al rastreo.

No importa cuán grande sea su modelo, desea evitar el rastreo con frecuencia. Esta sección de la guía de funciones tf analiza cómo establecer especificaciones de entrada y utilizar argumentos de tensor para evitar el retroceso. Si descubre que está obteniendo un rendimiento inusualmente pobre, es bueno verificar si está retrocediendo accidentalmente.

Puede agregar un efecto secundario solo ansioso (como imprimir un argumento de Python) para que pueda ver cuándo se rastrea la función. Aquí, verá un retroceso adicional porque los nuevos argumentos de Python siempre activan el retroceso.

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

Próximos pasos

Puede leer una discusión más detallada tanto en la página de referencia de la API de tf.function como en la guía .