¿Debo usar @ tf.function para todas las funciones?

8

Un tutorial oficial sobre @tf.functiondice:

Para obtener el máximo rendimiento y hacer que su modelo sea desplegable en cualquier lugar, use tf.function para hacer gráficos de sus programas. Gracias a AutoGraph, una sorprendente cantidad de código de Python simplemente funciona con tf.function, pero todavía hay dudas de las que debemos tener cuidado.

Las principales conclusiones y recomendaciones son:

  • No confíe en los efectos secundarios de Python como la mutación de objetos o los anexos de listas.
  • La función tf funciona mejor con operaciones TensorFlow, en lugar de operaciones NumPy o primitivas Python.
  • En caso de duda, utilice el idioma for x in y.

Solo menciona cómo implementar @tf.functionfunciones anotadas, pero no cuándo usarlo.

¿Hay una heurística sobre cómo decidir si al menos debería intentar anotar una función tf.function? Parece que no hay razones para no hacerlo, a menos que sea demasiado flojo para eliminar los efectos secundarios o cambiar algunas cosas como range()-> tf.range(). Pero si estoy dispuesto a hacer esto ...

¿Hay alguna razón para no usar @tf.functionpara todas las funciones?

problema oficial
fuente
1
¿Por qué agregar estas etiquetas? Podríamos también añadir tensorflow0.1, tensorflow0.2, tensorflow0.3, tensorflow0.4, tensorflow0.5y así sucesivamente, así como una etiqueta para cada uno de estos tfmódulos y clases luego. Además, ¿por qué no agregar una etiqueta para cada uno de los módulos estándar de Python y sus funciones y clases?
ForceBru
Es por eso que introduje la etiqueta tensorflow2.x, porque hay preguntas que no están relacionadas solo con tensorflow2.0 sino con la etiqueta tensorflow2.x. Sin embargo, sería inadecuado e inviable agregar una etiqueta para todas y cada una de las versiones de una biblioteca. Tome el ejemplo de Python. No tienes python3.4.6 ..... python.3.8.2, pero python3.x
Timbus Calin el
Por un lado, la tf.functionguía dice "Decorar funciones a nivel de módulo y métodos de clases a nivel de módulo, y evitar decorar funciones o métodos locales". Me parece recordar una redacción más explícita, como "no decorar todas las funciones, usar tf.functionen funciones de nivel superior, como un ciclo de entrenamiento", pero puedo recordar mal (o tal vez se ha eliminado). OTOH, esta discusión tiene una entrada interesante de los desarrolladores, al final parece estar bien usarla en cualquier función para tensores / variables.
jdehesa
Las @tf.functionfunciones anotadas de @jdehesa AFAIK también compilan las funciones que ellos mismos llaman gráficos. Por lo tanto, solo necesitaría anotar el punto de entrada al módulo que sea coherente con lo que describe. Pero tampoco estaría de más anotar manualmente las funciones más bajas en la pila de llamadas.
problemofficer
@problemofficer Sí, así que en el tema de GitHub que vinculé hay una discusión sobre si crear múltiples funciones intermedias podría tener un ligero impacto en el rendimiento, pero parece que el optimizador de gráficos (grappler) puede "en línea" funciones si es necesario, pero por otro mano si otra notf.function se llama a varias veces, no puede evitar la "duplicación de código" en el gráfico, por lo que parece recomendable su uso generalizado.
jdehesa

Respuestas:

4

TLDR: depende de su función y de si está en producción o desarrollo. No lo use tf.functionsi desea poder depurar su función fácilmente, o si se encuentra bajo las limitaciones de AutoGraph o la compatibilidad del código tf.v1. Recomiendo ver Inside TensorFlow habla sobre AutoGraph y Funciones, no sobre Sesiones .

A continuación, desglosaré los motivos, todos tomados de la información disponible en línea por Google.

En general, el tf.functiondecorador hace que una función se compile como invocable que ejecuta un gráfico TensorFlow. Esto implica:

  • Conversión del código a través de AutoGraph si es necesario (incluidas las funciones llamadas desde una función anotada)
  • Rastreo y ejecución del código gráfico generado

Hay información detallada disponible sobre las ideas de diseño detrás de esto.

Beneficios de decorar una función con tf.function

Beneficios generales

  • Ejecución más rápida , especialmente si la función consta de muchas operaciones pequeñas (Fuente)

Para funciones con código Python / Uso de AutoGraph vía tf.function decoración

Si desea usar AutoGraph, tf.functionse recomienda usarlo en lugar de llamar directamente a AutoGraph. Las razones para esto incluyen: dependencias de control automático, se requiere para algunas API, más almacenamiento en caché y ayudantes de excepción (Fuente) .

Inconvenientes de decorar una función con tf.function

Inconvenientes generales

  • Si la función solo consta de pocas operaciones costosas, no habrá mucha aceleración (Fuente)

Para funciones con código Python / Uso de AutoGraph mediantetf.function decoración

  • Captura sin excepción (debe hacerse en modo ansioso; fuera de la función decorada) (Fuente)
  • La depuración es mucho más difícil.
  • Limitaciones debido a efectos secundarios ocultos y flujo de control TF

La información detallada sobre las limitaciones de AutoGraph está disponible.

Para funciones con código tf.v1

  • No está permitido crear variables más de una vez tf.function, pero está sujeto a cambios a medida que el código tf.v1 se elimina (Fuente)

Para funciones con código tf.v2

  • No hay inconvenientes específicos

Ejemplos de limitaciones.

Crear variables más de una vez

No está permitido crear variables más de una vez, como ven el siguiente ejemplo:

@tf.function
def f(x):
    v = tf.Variable(1)
    return tf.add(x, v)

f(tf.constant(2))

# => ValueError: tf.function-decorated function tried to create variables on non-first call.

En el siguiente código, esto se mitiga asegurándose de que self.vsolo se crea una vez:

class C(object):
    def __init__(self):
        self.v = None
    @tf.function
    def f(self, x):
        if self.v is None:
            self.v = tf.Variable(1)
        return tf.add(x, self.v)

c = C()
print(c.f(tf.constant(2)))

# => tf.Tensor(3, shape=(), dtype=int32)

Efectos secundarios ocultos no capturados por AutoGraph

Los cambios como self.aen este ejemplo no se pueden ocultar, lo que conduce a un error ya que el análisis de funciones cruzadas aún no se realiza (Fuente) :

class C(object):
    def change_state(self):
        self.a += 1

    @tf.function
    def f(self):
        self.a = tf.constant(0)
        if tf.constant(True):
            self.change_state() # Mutation of self.a is hidden
        tf.print(self.a)

x = C()
x.f()

# => InaccessibleTensorError: The tensor 'Tensor("add:0", shape=(), dtype=int32)' cannot be accessed here: it is defined in another function or code block. Use return values, explicit Python locals or TensorFlow collections to access it. Defined in: FuncGraph(name=cond_true_5, id=5477800528); accessed from: FuncGraph(name=f, id=5476093776).

Los cambios a simple vista no son un problema:

class C(object):
    @tf.function
    def f(self):
        self.a = tf.constant(0)
        if tf.constant(True):
            self.a += 1 # Mutation of self.a is in plain sight
        tf.print(self.a)

x = C()
x.f()

# => 1

Ejemplo de limitación debido al flujo de control TF

Esta declaración if conduce a un error porque el valor de else debe definirse para el flujo de control TF:

@tf.function
def f(a, b):
    if tf.greater(a, b):
        return tf.constant(1)

# If a <= b would return None
x = f(tf.constant(3), tf.constant(2))   

# => ValueError: A value must also be returned from the else branch. If a value is returned from one branch of a conditional a value must be returned from all branches.
orgulloso
fuente
1
Este es un buen resumen. También vale la pena señalar que cuando se llama desde el modo ansioso, tf.function tiene una sobrecarga de aproximadamente 200 us (más o menos) después de la primera llamada. Sin embargo, llamar a una función tf.function desde otra función tf.fue bien. Por lo tanto, desea ajustar la mayor cantidad de computación posible. Si no fuera por las limitaciones, debería envolver todo el programa.
Dan Moldovan
Esta respuesta es tl; dr IMHO y en realidad no responde a mi pregunta, pero solo da la misma información fragmentada que encontré yo mismo. Además, decir que no debería usarlo @tf.functionpara la producción sino solo para el desarrollo no es una solución factible. En primer lugar, en el aprendizaje automático (al menos en investigación), la capacitación durante la etapa de desarrollo también crea el producto final (el modelo capacitado). En segundo lugar, los decoradores son un cambio significativo. No puedo simplemente ponerlos "después del desarrollo" y asegurarme de que el código se comporte igual. Esto significa que tengo que desarrollar y probar con ellos ya allí.
problemofficer
@problemofficer Perdón por la confusión. Cuando hablaba de producción en mi respuesta, estaba considerando la capacitación (en grandes conjuntos de datos) como parte de eso. En mi propia investigación, desarrollo / depuro mis funciones con un conjunto de datos de juguete en modo ansioso, y luego agrego tf.functionsi es apropiado.
Prouast
2

tf.function es útil en la creación y el uso de gráficos computacionales, deben usarse en el entrenamiento y en la implementación, sin embargo, no es necesario para la mayoría de sus funciones.

Digamos que estamos construyendo una capa especial que estará separada de un modelo más grande. No queremos tener el decorador tf.function encima de la función que construye esa capa porque es simplemente una definición de cómo se verá la capa.

Por otro lado, digamos que vamos a hacer una predicción o continuar nuestro entrenamiento usando alguna función. Nos gustaría tener el decorador tf.function porque en realidad estamos usando el gráfico computacional para obtener algún valor.

Un gran ejemplo sería construir un modelo codificador-decodificador. NO coloque el decorador alrededor de la función para crear el codificador o decodificador o cualquier capa, eso es solo una definición de lo que hará. SÍ coloque el decorador alrededor del método "entrenar" o "predecir" porque esos realmente van a usar el gráfico computacional para el cálculo.

Dibujó
fuente
1
Pero, ¿qué pasa con los efectos secundarios o por ejemplo tf.range(). AFAIK estos no se pueden convertir automáticamente. Por lo tanto, necesitaría escribir mis capas personalizadas con el gráfico automático en mente desde el principio. Por lo tanto, no puedo simplemente decorar la función de llamada (predicción).
problemofficer