¡Reserva! Google I / O regresa del 18 al 20 de mayo Regístrese ahora
Se usó la API de Cloud Translation para traducir esta página.
Switch to English

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

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

Descripción general

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, cómo se almacenan y representan los gráficos y cómo puede usarlos para acelerar sus modelos.

Esta es una descripción general general que cubre cómo tf.function permite cambiar de una ejecución ávida a una ejecución gráfica. Para obtener una especificación más completa de tf.function , 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.

Si bien la ejecución ávida tiene varias ventajas únicas, la ejecución de gráficos permite la portabilidad fuera de Python y tiende a ofrecer un mejor rendimiento. La ejecución de gráficos significa que los cálculos de tensores se ejecutan como un gráfico de TensorFlow , a veces denominado tf.Graph o simplemente "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 de TensorFlow que representa una red neuronal de dos capas cuando se visualiza en TensorBoard.

Un gráfico de TensorFlow simple

Los beneficios de los gráficos

Con un gráfico, tiene mucha 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ápidamente , se ejecute en paralelo y se ejecute 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.

Aprovechando las gráficas

Puede crear y ejecutar un gráfico en TensorFlow mediante tf.function , ya sea como una llamada directa o como un decorador. tf.function toma una función regular como entrada y devuelve una Function . Una Function es un Python invocable que crea gráficos de TensorFlow a partir de la función de Python. Utiliza una Function de la misma manera que su equivalente en Python.

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

# `a_function_that_uses_a_graph` is a TensorFlow `Function`.
a_function_that_uses_a_graph = tf.function(a_regular_function)

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

orig_value = a_regular_function(x1, y1, b1).numpy()
# Call a `Function` like a Python function.
tf_function_value = a_function_that_uses_a_graph(x1, y1, b1).numpy()
assert(orig_value == tf_function_value)

En el exterior, una Function parece a una función normal que escribes con las operaciones de TensorFlow. Debajo , sin embargo, es muy diferente. Una Function encapsula varios tf.Graph s detrás de una API . Así es como Function puede brindarle los beneficios de la ejecución de gráficos , como la velocidad y la capacidad de implementación.

tf.function aplica a una función y a todas las demás funciones que llama :

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

# Use the decorator to make `outer_function` a `Function`.
@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.Session .

Conversión de funciones de Python en gráficos

Cualquier función que escriba con TensorFlow contendrá una combinación de operaciones TF integradas y lógica de Python, como cláusulas if-then , bucles, break , return , continue y más. Si bien las operaciones de TensorFlow se capturan fácilmente mediante un tf.Graph , la lógica específica de Python debe someterse a un paso adicional para convertirse en parte del gráfico. tf.function usa una biblioteca llamada AutoGraph ( tf.autograph ) para convertir código Python en código generador de gráficos.

def simple_relu(x):
  if tf.greater(x, 0):
    return x
  else:
    return 0

# `tf_simple_relu` is a TensorFlow `Function` that wraps `simple_relu`.
tf_simple_relu = tf.function(simple_relu)

print("First branch, with graph:", tf_simple_relu(tf.constant(1)).numpy())
print("Second branch, with graph:", tf_simple_relu(tf.constant(-1)).numpy())
First branch, with graph: 1
Second branch, with graph: 0

Aunque es poco probable que necesite ver los gráficos directamente, puede inspeccionar las salidas para ver los resultados exactos. Estos no son fáciles de leer, por lo que no es necesario mirar con demasiada atención.

# This is the graph-generating output of AutoGraph.
print(tf.autograph.to_code(simple_relu))
def tf__simple_relu(x):
    with ag__.FunctionScope('simple_relu', '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)
            except:
                do_return = False
                raise

        def else_body():
            nonlocal do_return, retval_
            try:
                do_return = True
                retval_ = 0
            except:
                do_return = False
                raise
        ag__.if_stmt(ag__.converted_call(ag__.ld(tf).greater, (ag__.ld(x), 0), None, fscope), if_body, else_body, get_state, set_state, ('do_return', 'retval_'), 2)
        return fscope.ret(retval_, do_return)
# This is the graph itself.
print(tf_simple_relu.get_concrete_function(tf.constant(1)).graph.as_graph_def())
node {
  name: "x"
  op: "Placeholder"
  attr {
    key: "_user_specified_name"
    value {
      s: "x"
    }
  }
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "shape"
    value {
      shape {
      }
    }
  }
}
node {
  name: "Greater/y"
  op: "Const"
  attr {
    key: "dtype"
    value {
      type: DT_INT32
    }
  }
  attr {
    key: "value"
    value {
      tensor {
        dtype: DT_INT32
        tensor_shape {
        }
        int_val: 0
      }
    }
  }
}
node {
  name: "Greater"
  op: "Greater"
  input: "x"
  input: "Greater/y"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "cond"
  op: "StatelessIf"
  input: "Greater"
  input: "x"
  attr {
    key: "Tcond"
    value {
      type: DT_BOOL
    }
  }
  attr {
    key: "Tin"
    value {
      list {
        type: DT_INT32
      }
    }
  }
  attr {
    key: "Tout"
    value {
      list {
        type: DT_BOOL
        type: DT_INT32
      }
    }
  }
  attr {
    key: "_lower_using_switch_merge"
    value {
      b: true
    }
  }
  attr {
    key: "_read_only_resource_inputs"
    value {
      list {
      }
    }
  }
  attr {
    key: "else_branch"
    value {
      func {
        name: "cond_false_34"
      }
    }
  }
  attr {
    key: "output_shapes"
    value {
      list {
        shape {
        }
        shape {
        }
      }
    }
  }
  attr {
    key: "then_branch"
    value {
      func {
        name: "cond_true_33"
      }
    }
  }
}
node {
  name: "cond/Identity"
  op: "Identity"
  input: "cond"
  attr {
    key: "T"
    value {
      type: DT_BOOL
    }
  }
}
node {
  name: "cond/Identity_1"
  op: "Identity"
  input: "cond:1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
node {
  name: "Identity"
  op: "Identity"
  input: "cond/Identity_1"
  attr {
    key: "T"
    value {
      type: DT_INT32
    }
  }
}
library {
  function {
    signature {
      name: "cond_false_34"
      input_arg {
        name: "cond_placeholder"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Const_1"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_1"
      }
    }
    node_def {
      name: "cond/Const_2"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_2"
      }
    }
    node_def {
      name: "cond/Const_3"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_3"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const_3:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Const_4"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_INT32
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_INT32
            tensor_shape {
            }
            int_val: 0
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const_4"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond/Const_4:output:0"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
  function {
    signature {
      name: "cond_true_33"
      input_arg {
        name: "cond_identity_1_x"
        type: DT_INT32
      }
      output_arg {
        name: "cond_identity"
        type: DT_BOOL
      }
      output_arg {
        name: "cond_identity_1"
        type: DT_INT32
      }
    }
    node_def {
      name: "cond/Const"
      op: "Const"
      attr {
        key: "dtype"
        value {
          type: DT_BOOL
        }
      }
      attr {
        key: "value"
        value {
          tensor {
            dtype: DT_BOOL
            tensor_shape {
            }
            bool_val: true
          }
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Const"
      }
    }
    node_def {
      name: "cond/Identity"
      op: "Identity"
      input: "cond/Const:output:0"
      attr {
        key: "T"
        value {
          type: DT_BOOL
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity"
      }
    }
    node_def {
      name: "cond/Identity_1"
      op: "Identity"
      input: "cond_identity_1_x"
      attr {
        key: "T"
        value {
          type: DT_INT32
        }
      }
      experimental_debug_info {
        original_node_names: "cond/Identity_1"
      }
    }
    ret {
      key: "cond_identity"
      value: "cond/Identity:output:0"
    }
    ret {
      key: "cond_identity_1"
      value: "cond/Identity_1:output:0"
    }
    arg_attr {
      key: 0
      value {
        attr {
          key: "_output_shapes"
          value {
            list {
              shape {
              }
            }
          }
        }
      }
    }
  }
}
versions {
  producer: 561
  min_consumer: 12
}

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

Polimorfismo: una Function , muchos gráficos

Un tf.Graph está especializado en un tipo específico de entradas (por ejemplo, tensores con undtype específico u objetos con el mismo id() ).

Cada vez que invoca una Function con nuevos dtypes y formas en sus argumentos, Function crea un nuevo tf.Graph para los nuevos argumentos. Los dtypes y formas de las entradas de un tf.Graph se conocen como una firma de entrada o simplemente una firma .

La Function almacena el tf.Graph correspondiente a esa firma en una función ConcreteFunction . Un ConcreteFunction es un envoltorio alrededor de un tf.Graph .

@tf.function
def my_relu(x):
  return tf.maximum(0., x)

# `my_relu` creates new graphs as it sees more signatures.
print(my_relu(tf.constant(5.5)))
print(my_relu([1, -1]))
print(my_relu(tf.constant([3., -3.])))
tf.Tensor(5.5, shape=(), dtype=float32)
tf.Tensor([1. 0.], shape=(2,), dtype=float32)
tf.Tensor([3. 0.], shape=(2,), dtype=float32)

Si la Function ya ha sido llamada con esa firma, la Function no crea un nuevo tf.Graph .

# These two calls do *not* create new graphs.
print(my_relu(tf.constant(-2.5))) # Signature matches `tf.constant(5.5)`.
print(my_relu(tf.constant([-1., 1.]))) # Signature matches `tf.constant([3., -3.])`.
tf.Tensor(0.0, shape=(), dtype=float32)
tf.Tensor([0. 1.], shape=(2,), dtype=float32)

Debido a que está respaldado por varios gráficos, una Function es polimórfica . Eso le permite admitir más tipos de entrada de los que podría representar un solo tf.Graph , así como optimizar cada tf.Graph para un mejor rendimiento.

# There are three `ConcreteFunction`s (one for each graph) in `my_relu`.
# The `ConcreteFunction` also knows the return type and shape!
print(my_relu.pretty_printed_concrete_signatures())
my_relu(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

my_relu(x)
  Args:
    x: float32 Tensor, shape=(2,)
  Returns:
    float32 Tensor, shape=(2,)

my_relu(x=[1, -1])
  Returns:
    float32 Tensor, shape=(2,)

Usando tf.function

Hasta ahora, ha visto cómo puede convertir una función de Python en un gráfico simplemente usando tf.function como decorador o envoltorio. Pero en la práctica, hacer que tf.function funcione correctamente puede ser complicado. En las siguientes secciones, aprenderá cómo hacer que su código funcione como se espera con tf.function .

Ejecución de gráficos frente a ejecución ávida

El código de una Function se puede ejecutar tanto con entusiasmo como en forma de gráfico. Por defecto, Function ejecuta su código como un gráfico:

@tf.function
def get_MSE(y_true, y_pred):
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)
y_true = tf.random.uniform([5], maxval=10, dtype=tf.int32)
y_pred = tf.random.uniform([5], maxval=10, dtype=tf.int32)
print(y_true)
print(y_pred)
tf.Tensor([1 2 3 6 8], shape=(5,), dtype=int32)
tf.Tensor([0 1 8 7 5], shape=(5,), dtype=int32)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=7>

Para verificar que el gráfico de su Function está haciendo el mismo cálculo que su función Python equivalente, puede hacer que se ejecute con entusiasmo con tf.config.run_functions_eagerly(True) . Este es un interruptor que desactiva la capacidad de Function para crear y ejecutar gráficos , en lugar de ejecutar el código normalmente.

tf.config.run_functions_eagerly(True)
get_MSE(y_true, y_pred)
<tf.Tensor: shape=(), dtype=int32, numpy=7>
# Don't forget to set it back when you are done.
tf.config.run_functions_eagerly(False)

Sin embargo, la Function puede comportarse de manera diferente bajo el gráfico y la ejecución ansiosa. La función de print Python es un ejemplo de cómo se diferencian estos dos modos. Veamos qué sucede cuando inserta una declaración de print en nuestra función y la llama repetidamente.

@tf.function
def get_MSE(y_true, y_pred):
  print("Calculating MSE!")
  sq_diff = tf.pow(y_true - y_pred, 2)
  return tf.reduce_mean(sq_diff)

Observa lo que está impreso:

error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!

¿Es sorprendente la salida? get_MSE solo se imprimió una vez a pesar de que se llamó tres veces.

Para explicarlo, la declaración de print se ejecuta cuando Function ejecuta el código original para crear el gráfico en un proceso conocido como "rastreo" . El seguimiento captura las operaciones de TensorFlow en un gráfico y la print no se captura en el gráfico. Luego, ese gráfico se ejecuta para las tres llamadas sin volver a ejecutar el código Python .

Como prueba de cordura, apaguemos la ejecución del gráfico para comparar:

# Now, globally set everything to run eagerly to force eager execution.
tf.config.run_functions_eagerly(True)
# Observe what is printed below.
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
error = get_MSE(y_true, y_pred)
Calculating MSE!
Calculating MSE!
Calculating MSE!
tf.config.run_functions_eagerly(False)

print es un efecto secundario de Python , y hay otras diferencias que debe tener en cuenta al convertir una función en una Function .

tf.function mejores prácticas

Puede llevar algún tiempo acostumbrarse al comportamiento de Function . Para comenzar rápidamente, los usuarios principiantes deben jugar con las funciones de decoración de juguetes con @tf.function para obtener experiencia al pasar de la ejecución ansiosa a la ejecución de gráficos.

Diseñar para tf.function puede ser su mejortf.function para escribir programas de TensorFlow compatibles con gráficos. A continuación se ofrecen algunos consejos:

Viendo la aceleración

tf.function generalmente mejora el rendimiento de su código, pero la cantidad de aceleración depende del tipo de cálculo que ejecute. Los cálculos pequeños pueden estar dominados por la sobrecarga de llamar a un gráfico. Puede medir la diferencia en el rendimiento de la siguiente manera:

x = tf.random.uniform(shape=[10, 10], minval=-1, maxval=2, dtype=tf.dtypes.int32)

def power(x, y):
  result = tf.eye(10, dtype=tf.dtypes.int32)
  for _ in range(y):
    result = tf.matmul(x, result)
  return result
print("Eager execution:", timeit.timeit(lambda: power(x, 100), number=1000))
Eager execution: 1.777665522999996
power_as_graph = tf.function(power)
print("Graph execution:", timeit.timeit(lambda: power_as_graph(x, 100), number=1000))
Graph execution: 0.5308018169999968

tf.function se usa comúnmente para acelerar los ciclos de entrenamiento, como puede ver aquí con Keras.

Rendimiento y compensaciones

Los gráficos pueden acelerar su código, pero el proceso de creación tiene algunos gastos generales. Para algunas funciones, la creación del gráfico lleva más tiempo que la ejecución del gráfico. Esta inversión generalmente se amortiza rápidamente con el aumento de rendimiento de las ejecuciones posteriores, pero es importante tener en cuenta que los primeros pasos de cualquier entrenamiento de modelo grande pueden ser más lentos debido al rastreo.

No importa cuán grande sea su modelo, desea evitar el rastreo con frecuencia. La guía tf.function analiza cómo establecer especificaciones de entrada y usar argumentos de tensor para evitar el retroceso. Si encuentra que está obteniendo un rendimiento inusualmente bajo, es una buena idea verificar si está retrocediendo accidentalmente.

¿Cuándo se realiza el seguimiento de una Function ?

Para averiguar cuándo se está rastreando su Function , agregue una declaración de print a su código. Como regla general, Function ejecutará la declaración de print cada vez que rastree.

@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!") # An eager-only side effect.
  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)))
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
# 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)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

Aquí, verá un seguimiento adicional porque los nuevos argumentos de Python siempre activan la creación de un nuevo gráfico.

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 .