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 fuente en GitHub Descargar cuaderno

Introducción a Graphs y tf.function

Esta guía analiza 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 las gráficas?

En las tres guías anteriores, has visto a TensorFlow funcionando 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 dejar Python.

Sin embargo, ejecutar TensorFlow op-by-op en Python evita una gran cantidad de aceleraciones disponibles. 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 objetos tf.Tensor , 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 Python original.

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

un gráfico de flujo tensorial de dos capas

Los beneficios de los gráficos

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

Los gráficos también se optimizan fácilmente, lo que permite al compilador realizar transformaciones como:

  • Infiera estáticamente el valor de los tensores plegando nodos constantes en su cálculo ("plegado constante") .
  • Separe las subpartes de un cálculo que sean independientes y divídalas entre subprocesos 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 TensorFlow se ejecute rápido , en paralelo y de manera eficiente en varios dispositivos .

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

Trazar gráficos

La forma en que crea un gráfico en TensorFlow es usar tf.function , ya sea como una llamada directa o como un 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 tf.function -ized son llamadas de Python que funcionan igual que sus equivalentes de Python. Tienen una clase particular ( python.eager.def_function.Function ), pero para usted 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. Autógrafo utiliza una combinación de métodos, incluida la estandarización de construcciones de bucle, el desenrollado y la 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 de TensorFlow. Esto es, en su mayoría, ilegible, pero puede 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 (do_return, retval_)

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

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

        def else_body():
            nonlocal do_return, retval_
            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, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)


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

La mayoría de las veces, Autograph funcionará sin consideraciones especiales. Sin embargo, hay algunas advertencias, y la guía tf.function puede ayudar aquí, así como la referencia completa de autógrafos.

Viendo la aceleración

Simplemente envolver una función que usa tensor en tf.function no tf.function automáticamente su código. Para funciones pequeñas llamadas varias 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 se estaba realizando en un acelerador, como pilas de convoluciones con muchas 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 al dispositivo y realizan algunas aceleraciones.

Este código multiplica el tiempo de algunas ejecuciones 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.800515108999889
Graph time: 2.0497353999999177

Funciones polimórficas

Cuando trazas una función, creas un objeto Function que 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 utilizar esta Function en todo tipo de dtypes y formas diferentes. 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 se ha rastreado con ese tipo de argumento, solo obtiene su gráfico trazado previamente.

Conceptualmente, entonces:

  • Un tf.Graph es la estructura de datos sin procesar y portátil que describe un cálculo
  • Una Function es un despachador de almacenamiento en caché, seguimiento sobre ConcreteFunctions
  • Un ConcreteFunction es un contenedor compatible con ansias 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 de 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 0x7fcbcf7c6b70>
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á viendo bastante en la pila. A menos que esté administrando específicamente el rastreo, normalmente no necesitará llamar a funciones concretas directamente como se muestra aquí.

Volviendo a la ejecución ansiosa

Es posible que se encuentre mirando trazos de pila largos, especialmente los que se refieren a tf.Graph o with tf.Graph().as_default() . Esto significa que probablemente se esté ejecutando en un contexto de gráfico. Las funciones principales en TensorFlow usan contextos de gráficos, como el model.fit() Keras.

A menudo, es mucho más fácil depurar una ejecución ansiosa. Los seguimientos de pila deben ser relativamente cortos y fáciles de comprender.

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

A continuación, le indicamos algunas formas en las que puede asegurarse de que está ejecutando con entusiasmo:

  • Llame a modelos y capas directamente como invocables

  • Al usar Keras compilar / ajustar, en tiempo de compilación use model.compile(run_eagerly=True)

  • Establecer 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 ansias. 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 a fit y vea que la función se rastrea (dos veces) y luego el efecto ansioso nunca se vuelve a ejecutar.

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

Currently running eagerly 2020-09-12 01:21:25.249359

Currently running eagerly 2020-09-12 01:21:25.366135
2/2 [==============================] - 0s 2ms/step - loss: 0.9820
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0020
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0018

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

Sin embargo, si ejecuta incluso una sola época con entusiasmo, 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-09-12 01:21:25.560620
1/2 [==============>...............] - ETA: 0s - loss: 0.0014
Currently running eagerly 2020-09-12 01:21:25.581654
2/2 [==============================] - 0s 5ms/step - loss: 7.7140e-04

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

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-09-12 01:21:25.608077
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-09-12 01:21:25.612328

Currently running eagerly 2020-09-12 01:21:25.613704

# 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 desempeño

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

No importa qué tan grande sea su modelo, desea evitar el rastreo con frecuencia. En esta sección de la guía tf.function se explica cómo establecer especificaciones de entrada y usar argumentos tensoriales para evitar el retroceso. Si encuentra que está obteniendo un rendimiento inusualmente bajo, es bueno verificar si está retrocediendo accidentalmente.

Puede agregar un efecto secundario solo para ansias (como imprimir un argumento de Python) para que pueda ver cuándo se está rastreando 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 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)

Próximos pasos

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