De meilleures performances avec tf.function

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Dans tensorflow 2, l' exécution avide est activée par défaut. L'interface utilisateur est intuitive et flexible (l'exécution d'opérations ponctuelles est beaucoup plus facile et rapide), mais cela peut se faire au détriment des performances et de la déployabilité.

Vous pouvez utiliser tf.function pour faire des graphiques de vos programmes. Il s'agit d'un outil de transformation qui crée des graphiques de flux de données indépendants de Python à partir de votre code Python. Cela vous aidera à créer des modèles et portables performants, et il est nécessaire d'utiliser SavedModel .

Ce guide vous aidera à conceptualiser comment tf.function fonctionne sous le capot, afin que vous puissiez l' utiliser efficacement.

Les principaux points à retenir et recommandations sont les suivants :

  • Mise au point en mode hâte, puis décorer avec @tf.function .
  • Ne vous fiez pas aux effets secondaires de Python comme la mutation d'objet ou l'ajout de liste.
  • tf.function fonctionne mieux avec ops tensorflow; Les appels NumPy et Python sont convertis en constantes.

Installer

import tensorflow as tf

Définissez une fonction d'assistance pour illustrer les types d'erreurs que vous pourriez rencontrer :

import traceback
import contextlib

# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
  try:
    yield
  except error_class as e:
    print('Caught expected exception \n  {}:'.format(error_class))
    traceback.print_exc(limit=2)
  except Exception as e:
    raise e
  else:
    raise Exception('Expected {} to be raised but no error was raised!'.format(
        error_class))

Notions de base

Usage

Une Function que vous définissez (par exemple en appliquant le @tf.function décorateur) est comme une opération de tensorflow de base: Vous pouvez l' exécuter avec impatience; vous pouvez calculer des gradients ; etc.

@tf.function  # The decorator converts `add` into a `Function`.
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
2021-08-19 01:26:31.359247: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.367277: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.368263: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.370408: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-19 01:26:31.371033: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.372158: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:31.373202: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.003755: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.004773: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.005698: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 01:26:32.006564: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
2021-08-19 01:26:32.343820: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)>
v = tf.Variable(1.0)
with tf.GradientTape() as tape:
  result = add(v, 1.0)
tape.gradient(result, v)
<tf.Tensor: shape=(), dtype=float32, numpy=1.0>

Vous pouvez utiliser la Function de l' intérieur des autres Function s.

@tf.function
def dense_layer(x, w, b):
  return add(tf.matmul(x, w), b)

dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))
<tf.Tensor: shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
       [3., 3.],
       [3., 3.]], dtype=float32)>

Function s peut être plus rapide que le code désireux, en particulier pour les graphiques avec de nombreuses petites opérations. Mais pour les graphiques avec quelques opérations coûteuses (comme les convolutions), vous ne verrez peut-être pas beaucoup d'accélération.

import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)

@tf.function
def conv_fn(image):
  return conv_layer(image)

image = tf.zeros([1, 200, 200, 100])
# Warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")
2021-08-19 01:26:35.135723: I tensorflow/stream_executor/cuda/cuda_dnn.cc:369] Loaded cuDNN version 8100
Eager conv: 0.004266158999996605
Function conv: 0.004805293000003985
Note how there's not much difference in performance for convolutions
2021-08-19 01:26:40.338397: I tensorflow/core/platform/default/subprocess.cc:304] Start cannot spawn child process: No such file or directory

Tracé

Cette section expose la façon dont la Function fonctionne sous le capot, y compris les détails de mise en œuvre qui peuvent changer à l'avenir. Cependant, une fois que vous comprenez pourquoi et quand le traçage se produit, il est beaucoup plus facile à utiliser tf.function efficacement!

Qu'est-ce que le « traçage » ?

Une Function exécute votre programme dans un tensorflow graphique . Cependant, un tf.Graph ne peut pas représenter toutes les choses que vous écririez dans un programme de tensorflow désireux. Par exemple, Python prend en charge le polymorphisme, mais tf.Graph nécessite ses entrées d'avoir un type de données spécifié et la dimension. Ou vous pouvez effectuer des tâches annexes telles que la lecture d'arguments de ligne de commande, le déclenchement d'une erreur ou l'utilisation d'un objet Python plus complexe ; aucune de ces choses peut fonctionner dans un tf.Graph .

Function comble cette lacune en séparant le code en deux étapes:

1) Dans la première étape, appelée « traçage », la Function crée une nouvelle tf.Graph . Code Python fonctionne normalement, mais toutes les opérations de tensorflow (comme l' ajout de deux tenseurs) sont reportés: ils sont capturés par le tf.Graph et ne pas courir.

2) Dans la seconde étape, un tf.Graph qui contient tout ce qui a été reportée dans la première étape est exécutée. Cette étape est beaucoup plus rapide que l'étape de traçage.

En fonction de ses entrées, la Function ne sera pas toujours exécuter la première étape quand il est appelé. Voir « Règles de traçage » ci - dessous pour obtenir une meilleure idée de la façon dont il fait cette détermination. Ignorer la première étape et n'exécuter que la deuxième étape est ce qui vous donne les hautes performances de TensorFlow.

Lorsque la Function décide de suivre, l'étape de traçage est immédiatement suivie par la deuxième étape, afin d' appeler la Function à la fois crée et exécute le tf.Graph . Plus tard , vous verrez comment vous pouvez exécuter uniquement l'étape de traçage avec get_concrete_function .

Lorsque nous passons des arguments de types différents en Function , les deux étapes sont exécutées:

@tf.function
def double(a):
  print("Tracing with", a)
  return a + a

print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()
Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)

Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)

Notez que si vous appelez à plusieurs reprises une Function avec le même type d'argument, tensorflow va sauter l'étape de traçage et de réutiliser un graphique précédemment tracé, comme le graphique généré serait identique.

# This doesn't print 'Tracing with ...'
print(double(tf.constant("b")))
tf.Tensor(b'bb', shape=(), dtype=string)

Vous pouvez utiliser pretty_printed_concrete_signatures() pour voir toutes les traces disponibles:

print(double.pretty_printed_concrete_signatures())
double(a)
  Args:
    a: int32 Tensor, shape=()
  Returns:
    int32 Tensor, shape=()

double(a)
  Args:
    a: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()

double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

Jusqu'à présent, vous avez vu que tf.function crée un cache, couche d'expédition dynamique sur la logique de traçage du graphe de tensorflow. Pour être plus précis sur la terminologie :

  • Un tf.Graph est la première, la langue agnostique, représentation portable d'un calcul de tensorflow.
  • Un ConcreteFunction enveloppe un tf.Graph .
  • Une Function gère un cache de ConcreteFunction s et prend la bonne pour vos entrées.
  • tf.function encapsule une fonction Python, retournant une Function objet.
  • Tracing crée un tf.Graph et enveloppe dans un ConcreteFunction , également connu sous le nom de trace.

Règles de traçage

Une Function détermine si la réutilisation d' un tracé ConcreteFunction en calculant une clé de mémoire cache à partir des arguments et kwargs d'une entrée. Une clé de mémoire cache est une clé qui identifie un ConcreteFunction sur la base des arguments d'entrée et kwargs de la Function appel, selon les règles suivantes (qui peuvent changer):

  • La clé générée pour un tf.Tensor est sa forme et DTYPE.
  • La clé générée pour un tf.Variable est un identifiant unique variable.
  • La clé générée pour une primitive python (comme int , float , str ) est sa valeur.
  • La clé générée pour imbriquée dict s, list s, tuple s, namedtuple s et attr s est tuple aplaties des clés de la feuille (voir nest.flatten ). (En raison de cet aplatissement, l'appel d'une fonction concrète avec une structure d'imbrication différente de celle utilisée lors du traçage entraînera une TypeError).
  • Pour tous les autres types Python, la clé est unique à l'objet. De cette façon, une fonction ou une méthode est tracée indépendamment pour chaque instance avec laquelle elle est appelée.

Contrôler le retraçage

Retraçant, ce qui est quand votre Function crée plus d'une trace, aide assure que tensorflow génère des graphiques corrects pour chaque ensemble d'entrées. Cependant, le traçage est une opération coûteuse ! Si votre Function retrace un nouveau graphique pour chaque appel, vous verrez que votre code exécute plus lentement que si vous ne pas utiliser tf.function .

Pour contrôler le comportement de traçage, vous pouvez utiliser les techniques suivantes :

  • Spécifiez input_signature dans tf.function au traçage des limites.
@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
  print("Tracing with", x)
  return tf.where(x % 2 == 0, x // 2, 3 * x + 1)

print(next_collatz(tf.constant([1, 2])))
# You specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))

# You specified an int32 dtype in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([1.0, 2.0]))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception 
  <class 'ValueError'>:
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/1851403433.py", line 9, in <module>
    next_collatz(tf.constant([[1, 2], [3, 4]]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor(
[[1 2]
 [3 4]], shape=(2, 2), dtype=int32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/1851403433.py", line 13, in <module>
    next_collatz(tf.constant([1.0, 2.0]))
ValueError: Python inputs incompatible with input_signature:
  inputs: (
    tf.Tensor([1. 2.], shape=(2,), dtype=float32))
  input_signature: (
    TensorSpec(shape=(None,), dtype=tf.int32, name=None))
  • Spécifiez une dimension [Aucun] dans tf.TensorSpec pour permettre une certaine souplesse dans la réutilisation des traces.

    Depuis tensorflow correspond tenseurs en fonction de leur forme, en utilisant une None dimension comme joker permettra la Function s aux traces de réutilisation pour l' entrée taille variable. Entrée de taille variable peut se produire si vous avez des séquences de longueurs différentes, ou des images de différentes tailles pour chaque lot (voir le transformateur et rêve profond tutoriels par exemple).

@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def g(x):
  print('Tracing with', x)
  return x

# No retrace!
print(g(tf.constant([1, 2, 3])))
print(g(tf.constant([1, 2, 3, 4, 5])))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([1 2 3], shape=(3,), dtype=int32)
tf.Tensor([1 2 3 4 5], shape=(5,), dtype=int32)
  • Transmettez les arguments Python aux Tensors pour réduire le retraçage.

    Souvent, les arguments Python sont utilisés pour hyperparam'etres de contrôle et constructions graphiques - par exemple, num_layers=10 ou training=True ou nonlinearity='relu' - nonlinearity='relu' . Donc, si l'argument Python change, il est logique que vous deviez retracer le graphique.

    Cependant, il est possible qu'un argument Python ne soit pas utilisé pour contrôler la construction du graphe. Dans ces cas, une modification de la valeur Python peut déclencher un retraçage inutile. Prenez, par exemple, cette boucle d'entraînement, qu'AutoGraph va dérouler dynamiquement. Malgré les multiples traces, le graphe généré est en fait identique, donc le retraçage est inutile.

def train_one_step():
  pass

@tf.function
def train(num_steps):
  print("Tracing with num_steps = ", num_steps)
  tf.print("Executing with num_steps = ", num_steps)
  for _ in tf.range(num_steps):
    train_one_step()

print("Retracing occurs for different Python arguments.")
train(num_steps=10)
train(num_steps=20)

print()
print("Traces are reused for Tensor arguments.")
train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))
Retracing occurs for different Python arguments.
Tracing with num_steps =  10
Executing with num_steps =  10
Tracing with num_steps =  20
Executing with num_steps =  20

Traces are reused for Tensor arguments.
Tracing with num_steps =  Tensor("num_steps:0", shape=(), dtype=int32)
Executing with num_steps =  10
Executing with num_steps =  20

Si vous avez besoin de la force retraçage, créer une nouvelle Function . Séparés Function objets sont garantis pas des traces d'actions.

def f():
  print('Tracing!')
  tf.print('Executing')

tf.function(f)()
tf.function(f)()
Tracing!
Executing
Tracing!
Executing

Obtenir des fonctions concrètes

Chaque fois qu'une fonction est tracée, une nouvelle fonction concrète est créée. Vous pouvez directement obtenir une fonction de béton, à l'aide get_concrete_function .

print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.constant("a"))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))
Obtaining concrete trace
Executing traced function
tf.Tensor(b'aa', shape=(), dtype=string)
tf.Tensor(b'bb', shape=(), dtype=string)
# You can also call get_concrete_function on an InputSpec
double_strings_from_inputspec = double.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.string))
print(double_strings_from_inputspec(tf.constant("c")))
Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'cc', shape=(), dtype=string)

Impression d' un ConcreteFunction affiche un résumé de ses arguments d'entrée (avec des types) et son type de sortie.

print(double_strings)
ConcreteFunction double(a)
  Args:
    a: string Tensor, shape=()
  Returns:
    string Tensor, shape=()

Vous pouvez également récupérer directement la signature d'une fonction concrète.

print(double_strings.structured_input_signature)
print(double_strings.structured_outputs)
((TensorSpec(shape=(), dtype=tf.string, name='a'),), {})
Tensor("Identity:0", shape=(), dtype=string)

L'utilisation d'une trace concrète avec des types incompatibles générera une erreur

with assert_raises(tf.errors.InvalidArgumentError):
  double_strings(tf.constant(1))
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3196284684.py", line 2, in <module>
    double_strings(tf.constant(1))
tensorflow.python.framework.errors_impl.InvalidArgumentError: cannot compute __inference_double_162 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_162]

Vous pouvez remarquer que les arguments Python reçoivent un traitement spécial dans la signature d'entrée d'une fonction concrète. Avant TensorFlow 2.3, les arguments Python étaient simplement supprimés de la signature de la fonction concrète. À partir de TensorFlow 2.3, les arguments Python restent dans la signature, mais sont contraints de prendre la valeur définie lors du traçage.

@tf.function
def pow(a, b):
  return a ** b

square = pow.get_concrete_function(a=tf.TensorSpec(None, tf.float32), b=2)
print(square)
ConcreteFunction pow(a, b=2)
  Args:
    a: float32 Tensor, shape=<unknown>
  Returns:
    float32 Tensor, shape=<unknown>
assert square(tf.constant(10.0)) == 100

with assert_raises(TypeError):
  square(tf.constant(10.0), b=3)
Caught expected exception 
  <class 'TypeError'>:
Traceback (most recent call last):
  File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py", line 1721, in _call_impl
    cancellation_manager)
  File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/function.py", line 1766, in _call_with_flat_signature
    self._flat_signature_summary(), ", ".join(sorted(kwargs))))
TypeError: pow(a) got unexpected keyword arguments: b.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/2310937119.py", line 4, in <module>
    square(tf.constant(10.0), b=3)
TypeError: ConcreteFunction pow(a, b) was constructed with int value 2 in b, but was called with int value 3

Obtention de graphiques

Chaque fonction de béton est une enveloppe appelable autour d' un tf.Graph . Bien que la récupération de la réelle tf.Graph objet n'est pas quelque chose que vous aurez normalement besoin de le faire, vous pouvez l' obtenir facilement de toute fonction de béton.

graph = double_strings.graph
for node in graph.as_graph_def().node:
  print(f'{node.input} -> {node.name}')
[] -> a
['a', 'a'] -> add
['add'] -> Identity

Débogage

En général, le code de débogage est plus facile en mode hâte que l' intérieur tf.function . Vous devez vous assurer que votre code est exécuté sans erreur en mode hâte avant de décorer avec tf.function . Pour aider dans le processus de débogage, vous pouvez appeler tf.config.run_functions_eagerly(True) à l' échelle mondiale et désactiver réactivez tf.function .

Lorsque traquer les problèmes qui apparaissent uniquement dans tf.function , voici quelques conseils:

  • Plain Old Python print appels exécutent seulement pendant le suivi, vous aider à traquer lorsque votre fonction obtient (re) tracé.
  • tf.print appels exécuter à chaque fois, et peut vous aider à traquer les valeurs intermédiaires lors de l' exécution.
  • tf.debugging.enable_check_numerics est un moyen facile de traquer où NaN et Inf sont créés.
  • pdb (le débogueur Python ) peut vous aider à comprendre ce qui se passe pendant le suivi. (Caveat: pdb vous déposera dans le code source transformé AutoGraph.)

Transformations d'AutoGraph

Autograph est une bibliothèque qui est activé par défaut dans tf.function , et transforme un sous - ensemble de codes désireux python dans ops tensorflow graphique compatible. Cela inclut le flux de contrôle comme if , for , while .

Tensorflow ops comme tf.cond et tf.while_loop continuent à travailler, mais le flux de contrôle est souvent plus facile d'écrire et de comprendre quand écrit en Python.

# A simple loop

@tf.function
def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

f(tf.random.uniform([5]))
[0.680332422 0.0791739225 0.0669257641 0.701964378 0.535618901]
[0.591735482 0.0790088922 0.0668260083 0.605613232 0.489664495]
[0.531142652 0.0788449 0.0667267069 0.541031778 0.453950107]
[0.486254066 0.078681916 0.0666278452 0.493768573 0.425140589]
[0.451238334 0.0785199404 0.0665294155 0.457202375 0.40125224]
[0.422916353 0.0783589631 0.0664314255 0.427801341 0.38101992]
[0.399384439 0.0781989694 0.0663338676 0.403482229 0.363592863]
[0.379422128 0.0780399516 0.0662367418 0.382924527 0.348374784]
[0.362205505 0.0778819 0.0661400482 0.365244567 0.3349334]
[0.347155213 0.0777248144 0.0660437644 0.349825174 0.322946697]
[0.333850235 0.0775686726 0.0659479052 0.336220473 0.312168896]
[0.321976125 0.0774134621 0.0658524558 0.324099 0.302408934]
[0.311292648 0.0772591755 0.0657574236 0.31320855 0.293515563]
[0.301612616 0.0771058127 0.0656628 0.30335319 0.28536731]
[0.292787671 0.0769533589 0.0655685887 0.294378221 0.27786532]
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.28469858, 0.07680181, 0.06547478, 0.28615952, 0.27092823],
      dtype=float32)>

Si vous êtes curieux, vous pouvez inspecter le code généré par l'autographe.

print(tf.autograph.to_code(f.python_function))
def tf__f(x):
    with ag__.FunctionScope('f', '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 (x,)

        def set_state(vars_):
            nonlocal x
            (x,) = vars_

        def loop_body():
            nonlocal x
            ag__.converted_call(ag__.ld(tf).print, (ag__.ld(x),), None, fscope)
            x = ag__.converted_call(ag__.ld(tf).tanh, (ag__.ld(x),), None, fscope)

        def loop_test():
            return (ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) > 1)
        ag__.while_stmt(loop_test, loop_body, get_state, set_state, ('x',), {})
        try:
            do_return = True
            retval_ = ag__.ld(x)
        except:
            do_return = False
            raise
        return fscope.ret(retval_, do_return)

Conditionnels

AutoGraph convertira certains if <condition> déclarations en équivalent tf.cond appels. Cette substitution est faite si <condition> est un Tensor. Dans le cas contraire, la if instruction est exécutée comme condition Python.

Une conditionnelle Python s'exécute pendant le traçage, donc exactement une branche de la conditionnelle sera ajoutée au graphique. Sans AutoGraph, ce graphique tracé serait incapable de prendre la branche alternative s'il existe un flux de contrôle dépendant des données.

tf.cond traces et ajoute les deux branches du conditionnel au graphe, en sélectionnant de façon dynamique une branche au moment de l' exécution. Le traçage peut avoir des effets secondaires inattendus ; vérifier les effets de traçage Autograph pour plus d' informations.

@tf.function
def fizzbuzz(n):
  for i in tf.range(1, n + 1):
    print('Tracing for loop')
    if i % 15 == 0:
      print('Tracing fizzbuzz branch')
      tf.print('fizzbuzz')
    elif i % 3 == 0:
      print('Tracing fizz branch')
      tf.print('fizz')
    elif i % 5 == 0:
      print('Tracing buzz branch')
      tf.print('buzz')
    else:
      print('Tracing default branch')
      tf.print(i)

fizzbuzz(tf.constant(5))
fizzbuzz(tf.constant(20))
Tracing for loop
Tracing fizzbuzz branch
Tracing fizz branch
Tracing buzz branch
Tracing default branch
1
2
fizz
4
buzz
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz
16
17
fizz
19
buzz

Voir la documentation de référence des restrictions supplémentaires sur AutoGraph-converties si les déclarations.

Boucles

AutoGraph convertira certains for et while déclarations dans le ops en boucle tensorflow équivalent, comme tf.while_loop . Si non converti, le for ou while boucle est exécutée en boucle Python.

Cette substitution s'effectue dans les situations suivantes :

  • for x in y : si y est un Tensor, converti à tf.while_loop . Dans le cas particulier où y est un tf.data.Dataset , une combinaison de tf.data.Dataset ops sont générés.
  • while <condition> : si <condition> est un Tensor, converti à tf.while_loop .

Une boucle Python exécute pendant le traçage, l' ajout d' opérations supplémentaires au tf.Graph pour chaque itération de la boucle.

Une boucle TensorFlow trace le corps de la boucle et sélectionne dynamiquement le nombre d'itérations à exécuter au moment de l'exécution. Le corps de la boucle apparaît une seule fois dans le produit tf.Graph .

Voir la documentation de référence des restrictions supplémentaires sur AutoGraph convertis for et while déclarations.

Boucle sur les données Python

Un piège commun est de boucle sur les données Python / numpy dans un tf.function . Cette boucle exécutera au cours du processus de traçage, en ajoutant une copie de votre modèle à la tf.Graph pour chaque itération de la boucle.

Si vous voulez envelopper la boucle de formation dans toute tf.function , la meilleure façon de le faire est d'envelopper vos données en tant que tf.data.Dataset afin que AutoGraph sera Déroulez dynamiquement la boucle de formation.

def measure_graph_size(f, *args):
  g = f.get_concrete_function(*args).graph
  print("{}({}) contains {} nodes in its graph".format(
      f.__name__, ', '.join(map(str, args)), len(g.as_graph_def().node)))

@tf.function
def train(dataset):
  loss = tf.constant(0)
  for x, y in dataset:
    loss += tf.abs(y - x) # Some dummy computation.
  return loss

small_data = [(1, 1)] * 3
big_data = [(1, 1)] * 10
measure_graph_size(train, small_data)
measure_graph_size(train, big_data)

measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: small_data, (tf.int32, tf.int32)))
measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: big_data, (tf.int32, tf.int32)))
train([(1, 1), (1, 1), (1, 1)]) contains 11 nodes in its graph
train([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]) contains 32 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 6 nodes in its graph
train(<FlatMapDataset shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 6 nodes in its graph

Quand vous emballez des données Python / numpy dans un Dataset, garder à l' esprit tf.data.Dataset.from_generator par rapport tf.data.Dataset.from_tensors . L'ancien gardera les données dans le python et la récupération via tf.py_function qui peut avoir des conséquences sur les performances, alors que ce dernier regrouper une copie des données comme une grande tf.constant() noeud dans le graphe, qui peut avoir des implications de mémoire.

La lecture des données à partir de fichiers via TFRecordDataset , CsvDataset , etc. est le moyen le plus efficace de consommer des données, comme alors tensorflow lui - même peut gérer le chargement et le préchargement asynchrone des données, sans avoir à impliquer Python. Pour en savoir plus, consultez le tf.data : conduites d'entrée Build tensorflow guider.

Accumuler des valeurs dans une boucle

Un modèle courant consiste à accumuler des valeurs intermédiaires à partir d'une boucle. Normalement, cela est accompli en ajoutant à une liste Python ou en ajoutant des entrées à un dictionnaire Python. Cependant, comme il s'agit d'effets secondaires Python, ils ne fonctionneront pas comme prévu dans une boucle déroulée dynamiquement. Utilisation tf.TensorArray pour accumuler les résultats d'une boucle dynamique déroulée.

batch_size = 2
seq_len = 3
feature_size = 4

def rnn_step(inp, state):
  return inp + state

@tf.function
def dynamic_rnn(rnn_step, input_data, initial_state):
  # [batch, time, features] -> [time, batch, features]
  input_data = tf.transpose(input_data, [1, 0, 2])
  max_seq_len = input_data.shape[0]

  states = tf.TensorArray(tf.float32, size=max_seq_len)
  state = initial_state
  for i in tf.range(max_seq_len):
    state = rnn_step(input_data[i], state)
    states = states.write(i, state)
  return tf.transpose(states.stack(), [1, 0, 2])

dynamic_rnn(rnn_step,
            tf.random.uniform([batch_size, seq_len, feature_size]),
            tf.zeros([batch_size, feature_size]))
<tf.Tensor: shape=(2, 3, 4), dtype=float32, numpy=
array([[[0.9311646 , 0.23201597, 0.27760983, 0.50749147],
        [1.9157217 , 0.73632634, 1.2418742 , 1.3936795 ],
        [2.2432408 , 0.80746305, 1.2652718 , 2.0350993 ]],

       [[0.6350479 , 0.57744336, 0.1134994 , 0.49145043],
        [1.4704436 , 1.2057934 , 0.6300938 , 1.1418717 ],
        [2.2552116 , 1.7597941 , 1.3745583 , 1.9007651 ]]], dtype=float32)>

Limites

Tensorflow Function a quelques limitations par la conception que vous devriez être au courant lors de la conversion d' une fonction Python à une Function .

Exécuter les effets secondaires de Python

Les effets secondaires, comme l' impression, aux listes annexant, et muter globals peuvent se comporter de façon inattendue dans une Function , exécuter parfois deux fois ou pas du tout. Ils ne se produisent que la première fois que vous appelez une Function avec un ensemble d'entrées. Par la suite, le tracé tf.Graph est réexécutée, sans exécuter le code Python.

La règle générale est d'éviter de compter sur les effets secondaires Python dans votre logique et de ne les utiliser que pour déboguer vos traces. Dans le cas contraire, les API tensorflow comme tf.data , tf.print , tf.summary , tf.Variable.assign et tf.TensorArray sont la meilleure façon de vous assurer que votre code sera exécuté par le moteur d' exécution de tensorflow à chaque appel.

@tf.function
def f(x):
  print("Traced with", x)
  tf.print("Executed with", x)

f(1)
f(1)
f(2)
Traced with 1
Executed with 1
Executed with 1
Traced with 2
Executed with 2

Si vous souhaitez exécuter du code Python lors de chaque appel d'une Function , tf.py_function est une trappe de sortie. L'inconvénient de tf.py_function est que ce n'est pas portable ou particulièrement performant, ne peut pas être enregistré avec SavedModel, et ne fonctionne pas bien dans les configurations distribuées (multi-GPU, TPU). En outre, étant donné que tf.py_function doit être branché sur le graphique, il jette toutes les entrées / sorties tenseurs.

Modification des variables globales et libres Python

Python global et changement de variables libres compte comme un effet secondaire Python, il ne se produit que pendant le suivi.

external_list = []

@tf.function
def side_effect(x):
  print('Python side effect')
  external_list.append(x)

side_effect(1)
side_effect(1)
side_effect(1)
# The list append only happened once!
assert len(external_list) == 1
Python side effect

Vous devez éviter des conteneurs comme des listes, muter dicts, d' autres objets qui vivent en dehors de la Function . Utilisez plutôt des arguments et des objets TF. Par exemple, la section « valeurs dans une boucle Accumuler » a un exemple de la façon dont peut être mis en œuvre comme la liste des opérations.

Vous pouvez, dans certains cas, la capture et de manipuler l' état si elle est un tf.Variable . Voici comment les poids des modèles KERAS sont mis à jour avec des appels répétés à la même ConcreteFunction .

Utiliser des itérateurs et des générateurs Python

De nombreuses fonctionnalités Python, telles que les générateurs et les itérateurs, s'appuient sur l'environnement d'exécution Python pour suivre l'état. En général, bien que ces constructions fonctionnent comme prévu en mode avide, ce sont des exemples d'effets secondaires Python et ne se produisent donc que pendant le traçage.

@tf.function
def buggy_consume_next(iterator):
  tf.print("Value:", next(iterator))

iterator = iter([1, 2, 3])
buggy_consume_next(iterator)
# This reuses the first value from the iterator, rather than consuming the next value.
buggy_consume_next(iterator)
buggy_consume_next(iterator)
Value: 1
Value: 1
Value: 1

Tout comme la façon dont tensorflow a un spécialiste tf.TensorArray pour les constructions de la liste, il a une spécialisée tf.data.Iterator pour l' itération des constructions. Voir la section sur les transformations Autograph une vue d' ensemble. En outre, l' tf.data API peut aider à mettre en œuvre des modèles de générateur:

@tf.function
def good_consume_next(iterator):
  # This is ok, iterator is a tf.data.Iterator
  tf.print("Value:", next(iterator))

ds = tf.data.Dataset.from_tensor_slices([1, 2, 3])
iterator = iter(ds)
good_consume_next(iterator)
good_consume_next(iterator)
good_consume_next(iterator)
Value: 1
Value: 2
Value: 3

Suppression tf.Variables entre Function des appels

Une autre erreur que vous pouvez rencontrer est une variable récupérée par la mémoire. ConcreteFunction s ne retenir que WeakRefs aux variables qu'ils ferment plus, vous devez conserver une référence à toutes les variables.

external_var = tf.Variable(3)
@tf.function
def f(x):
  return x * external_var

traced_f = f.get_concrete_function(4)
print("Calling concrete function...")
print(traced_f(4))

# The original variable object gets garbage collected, since there are no more
# references to it.
external_var = tf.Variable(4)
print()
print("Calling concrete function after garbage collecting its closed Variable...")
with assert_raises(tf.errors.FailedPreconditionError):
  traced_f(4)
Calling concrete function...
tf.Tensor(12, shape=(), dtype=int32)

Calling concrete function after garbage collecting its closed Variable...
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.FailedPreconditionError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3862898592.py", line 16, in <module>
    traced_f(4)
tensorflow.python.framework.errors_impl.FailedPreconditionError: 2 root error(s) found.
  (0) Failed precondition:  Could not find variable _AnonymousVar3. This could mean that the variable has been deleted. In TF1, it can also mean the variable is uninitialized. Debug info: container=localhost, status=Not found: Resource localhost/_AnonymousVar3/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at tmp/ipykernel_25646/3862898592.py:4) ]]
  (1) Failed precondition:  Could not find variable _AnonymousVar3. This could mean that the variable has been deleted. In TF1, it can also mean the variable is uninitialized. Debug info: container=localhost, status=Not found: Resource localhost/_AnonymousVar3/N10tensorflow3VarE does not exist.
     [[node ReadVariableOp (defined at tmp/ipykernel_25646/3862898592.py:4) ]]
     [[ReadVariableOp/_2]]
0 successful operations.
0 derived errors ignored. [Op:__inference_f_772]

Function call stack:
f -> f

Problèmes connus

Si votre Function n'est pas correctement évaluer, l'erreur peut être expliquée par ces problèmes connus qui sont prévus pour être fixé à l'avenir.

En fonction des variables globales et libres de Python

Function crée une nouvelle ConcreteFunction lorsqu'elle est appelée avec une nouvelle valeur d'un argument Python. Cependant, il ne fait pas que la fermeture Python, GLOBALS ou nonlocals de cette Function . Si leur valeur change entre les appels à la Function , la Function utilisera toujours les valeurs qu'ils avaient quand il a été tracée. Ceci est différent du fonctionnement des fonctions Python classiques.

Pour cette raison, nous recommandons un style de programmation fonctionnel qui utilise des arguments au lieu de fermer sur des noms externes.

@tf.function
def buggy_add():
  return 1 + foo

@tf.function
def recommended_add(foo):
  return 1 + foo

foo = 1
print("Buggy:", buggy_add())
print("Correct:", recommended_add(foo))
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(2, shape=(), dtype=int32)
print("Updating the value of `foo` to 100!")
foo = 100
print("Buggy:", buggy_add())  # Did not change!
print("Correct:", recommended_add(foo))
Updating the value of `foo` to 100!
Buggy: tf.Tensor(2, shape=(), dtype=int32)
Correct: tf.Tensor(101, shape=(), dtype=int32)

Vous pouvez fermer les noms externes, tant que vous ne mettez pas à jour leurs valeurs.

Selon les objets Python

La recommandation de passer des objets Python comme arguments dans tf.function a un certain nombre de problèmes connus, qui devraient être fixés à l'avenir. En général, vous pouvez compter sur le suivi cohérent si vous utilisez une primitive Python ou tf.nest la structure comme un argument compatible ou passer dans une autre instance d'un objet dans une Function . Cependant, la Function ne crée pas une nouvelle trace lorsque vous passez le même objet et ne change pas ses attributs.

class SimpleModel(tf.Module):
  def __init__(self):
    # These values are *not* tf.Variables.
    self.bias = 0.
    self.weight = 2.

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

simple_model = SimpleModel()
x = tf.constant(10.)
print(evaluate(simple_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
simple_model.bias += 5.0
print(evaluate(simple_model, x))  # Didn't change :(
Adding bias!
tf.Tensor(20.0, shape=(), dtype=float32)

En utilisant la même Function pour évaluer l'instance mise à jour du modèle buggy puisque le modèle mis à jour a la même clé de cache que le modèle original.

Pour cette raison, nous vous recommandons d' écrire votre Function pour éviter en fonction des attributs d'objets mutables ou créer de nouveaux objets.

Si cela est impossible, une solution de contournement est de faire une nouvelle Function s chaque fois que vous modifiez votre objet à la force retraçage:

def evaluate(model, x):
  return model.weight * x + model.bias

new_model = SimpleModel()
evaluate_no_bias = tf.function(evaluate).get_concrete_function(new_model, x)
# Don't pass in `new_model`, `Function` already captured its state during tracing.
print(evaluate_no_bias(x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
new_model.bias += 5.0
# Create new Function and ConcreteFunction since you modified new_model.
evaluate_with_bias = tf.function(evaluate).get_concrete_function(new_model, x)
print(evaluate_with_bias(x)) # Don't pass in `new_model`.
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

Comme retraçage peut être coûteux , vous pouvez utiliser tf.Variable s comme les attributs d'objet, qui peut être mutées (mais pas changé,!) Pour attention un effet similaire sans avoir besoin d' un retraçage.

class BetterModel:

  def __init__(self):
    self.bias = tf.Variable(0.)
    self.weight = tf.Variable(2.)

@tf.function
def evaluate(model, x):
  return model.weight * x + model.bias

better_model = BetterModel()
print(evaluate(better_model, x))
tf.Tensor(20.0, shape=(), dtype=float32)
print("Adding bias!")
better_model.bias.assign_add(5.0)  # Note: instead of better_model.bias += 5
print(evaluate(better_model, x))  # This works!
Adding bias!
tf.Tensor(25.0, shape=(), dtype=float32)

Création de tf.Variables

Function ne supporte que singleton tf.Variable s créé une fois que le premier appel, et réutilisé dans les appels de fonctions ultérieures. Le extrait de code ci - dessous créerait un nouveau tf.Variable dans chaque appel de fonction, ce qui aboutit à une ValueError exception.

Exemple:

@tf.function
def f(x):
  v = tf.Variable(1.0)
  return v

with assert_raises(ValueError):
  f(1.0)
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3018268426.py", line 7, in <module>
    f(1.0)
ValueError: in user code:

    /tmp/ipykernel_25646/3018268426.py:3 f  *
        v = tf.Variable(1.0)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:268 __call__  **
        return cls._variable_v2_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:262 _variable_v2_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py:765 invalid_creator_scope
        "tf.function-decorated function tried to create "

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

Un modèle commun utilisé pour contourner cette limitation est de commencer avec une valeur Aucun Python, puis créer la condition tf.Variable si la valeur est None:

class Count(tf.Module):
  def __init__(self):
    self.count = None

  @tf.function
  def __call__(self):
    if self.count is None:
      self.count = tf.Variable(0)
    return self.count.assign_add(1)

c = Count()
print(c())
print(c())
tf.Tensor(1, shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Utilisation avec plusieurs optimiseurs Keras

Vous pouvez rencontrer ValueError: tf.function only supports singleton tf.Variables created on the first call. l'utilisation de plus d'un optimiseur Keras avec un tf.function . Cette erreur se produit parce que les optimiseurs créent en interne tf.Variables lorsqu'ils appliquent des gradients pour la première fois.

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

@tf.function
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

train_step(w, x, y, opt1)
print("Calling `train_step` with different optimizer...")
with assert_raises(ValueError):
  train_step(w, x, y, opt2)
Calling `train_step` with different optimizer...
Caught expected exception 
  <class 'ValueError'>:
Traceback (most recent call last):
  File "/tmp/ipykernel_25646/3551158538.py", line 8, in assert_raises
    yield
  File "/tmp/ipykernel_25646/3167358578.py", line 18, in <module>
    train_step(w, x, y, opt2)
ValueError: in user code:

    /tmp/ipykernel_25646/3167358578.py:9 train_step  *
        optimizer.apply_gradients(zip(gradients, [w]))
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:628 apply_gradients  **
        self._create_all_weights(var_list)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:813 _create_all_weights
        _ = self.iterations
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:820 __getattribute__
        return super(OptimizerV2, self).__getattribute__(name)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:980 iterations
        aggregation=tf.VariableAggregation.ONLY_FIRST_REPLICA)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/optimizer_v2/optimizer_v2.py:1186 add_weight
        aggregation=aggregation)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/training/tracking/base.py:818 _add_variable_with_custom_getter
        **kwargs_for_getter)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/engine/base_layer_utils.py:129 make_variable
        shape=variable_shape if variable_shape else None)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:266 __call__
        return cls._variable_v1_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:227 _variable_v1_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/variables.py:67 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/eager/def_function.py:765 invalid_creator_scope
        "tf.function-decorated function tried to create "

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

Si vous devez changer l'optimiseur lors de la formation, une solution de contournement est de créer une nouvelle Function pour chaque optimiseur, appelant le ConcreteFunction directement.

opt1 = tf.keras.optimizers.Adam(learning_rate = 1e-2)
opt2 = tf.keras.optimizers.Adam(learning_rate = 1e-3)

# Not a tf.function.
def train_step(w, x, y, optimizer):
   with tf.GradientTape() as tape:
       L = tf.reduce_sum(tf.square(w*x - y))
   gradients = tape.gradient(L, [w])
   optimizer.apply_gradients(zip(gradients, [w]))

w = tf.Variable(2.)
x = tf.constant([-1.])
y = tf.constant([2.])

# Make a new Function and ConcreteFunction for each optimizer.
train_step_1 = tf.function(train_step).get_concrete_function(w, x, y, opt1)
train_step_2 = tf.function(train_step).get_concrete_function(w, x, y, opt2)
for i in range(10):
  if i % 2 == 0:
    train_step_1(w, x, y) # `opt1` is not used as a parameter. 
  else:
    train_step_2(w, x, y) # `opt2` is not used as a parameter.

Utilisation avec plusieurs modèles Keras

Vous pouvez également rencontrer ValueError: tf.function only supports singleton tf.Variables created on the first call. en passant différentes instances de modèle à la même Function .

Cette erreur se produit parce que les modèles KERAS (qui n'ont pas leur forme d'entrée définie ) et les couches KERAS créent tf.Variables s quand ils sont d' abord appelés. Vous essayez peut - être d'initialiser les variables à l' intérieur d' une Function , ce qui a déjà été appelé. Pour éviter cette erreur, essayez d' appeler model.build(input_shape) pour initialiser tous les poids avant la formation du modèle.

Lectures complémentaires

Pour en savoir plus sur la façon d'exporter et de charger une Function , consultez le Guide SavedModel . Pour en savoir plus sur l' optimisation des graphiques qui sont effectuées après le suivi, voir le Guide Grappler . Pour savoir comment optimiser votre pipeline de données et le profil de votre modèle, consultez le Guide Profiler .