Réserve cette date! Google I / O revient du 18 au 20 mai S'inscrire maintenant
Cette page a été traduite par l'API Cloud Translation.
Switch to English

Introduction aux graphes et tf.function

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

Aperçu

Ce guide passe sous la surface de TensorFlow et Keras pour voir comment fonctionne TensorFlow. Si vous souhaitez au contraire vous lancer immédiatement avec Keras, veuillez consulter notre collection de guides Keras .

Dans ce guide, vous découvrirez comment TensorFlow vous permet d'apporter des modifications simples à votre code pour obtenir des graphiques, comment les graphiques sont stockés et représentés, et comment vous pouvez les utiliser pour accélérer vos modèles.

Ceci est un aperçu général qui explique comment tf.function vous permet de passer d'une exécution impatiente à une exécution graphique. Pour une spécification plus complète de tf.function , consultez le guide tf.function .

Que sont les graphiques?

Dans les trois guides précédents, vous avez vu TensorFlow fonctionner avec impatience . Cela signifie que les opérations TensorFlow sont exécutées par Python, opération par opération, et retournent les résultats à Python.

Bien que l'exécution rapide présente plusieurs avantages uniques, l'exécution de graphes permet la portabilité en dehors de Python et tend à offrir de meilleures performances. L'exécution de graphe signifie que les calculs tensoriels sont exécutés sous la forme d'un graphe TensorFlow , parfois appelé tf.Graph ou simplement "graphe".

Les graphiques sont des structures de données qui contiennent un ensemble d'objets tf.Operation , qui représentent des unités de calcul; et les objets tf.Tensor , qui représentent les unités de données qui circulent entre les opérations. Ils sont définis dans un contexte tf.Graph . Étant donné que ces graphiques sont des structures de données, ils peuvent être enregistrés, exécutés et restaurés sans le code Python d'origine.

Voici à quoi ressemble un graphique TensorFlow représentant un réseau de neurones à deux couches lorsqu'il est visualisé dans TensorBoard.

Un graphique TensorFlow simple

Les avantages des graphiques

Avec un graphique, vous disposez d'une grande flexibilité. Vous pouvez utiliser votre graphique TensorFlow dans des environnements dépourvus d'interpréteur Python, tels que les applications mobiles, les appareils intégrés et les serveurs backend. TensorFlow utilise des graphiques comme format pour les modèles enregistrés lorsqu'il les exporte depuis Python.

Les graphiques sont également facilement optimisés, permettant au compilateur de faire des transformations telles que:

  • Déduisez statiquement la valeur des tenseurs en repliant les nœuds constants dans votre calcul ("repliement constant") .
  • Séparez les sous-parties d'un calcul qui sont indépendantes et répartissez-les entre des threads ou des périphériques.
  • Simplifiez les opérations arithmétiques en éliminant les sous-expressions courantes.

Il existe un système d'optimisation complet, Grappler , pour effectuer ceci et d'autres accélérations.

En bref, les graphiques sont extrêmement utiles et permettent à votre TensorFlow de fonctionner rapidement , de fonctionner en parallèle et de fonctionner efficacement sur plusieurs appareils .

Cependant, vous souhaitez toujours définir nos modèles d'apprentissage automatique (ou d'autres calculs) en Python pour plus de commodité, puis créer automatiquement des graphiques lorsque vous en avez besoin.

Tirer parti des graphiques

Vous créez et exécutez un graphique dans TensorFlow à l'aide de tf.function , en tant tf.function direct ou en tant que décorateur. tf.function prend une fonction régulière comme entrée et renvoie une Function . Une Function est un appelable Python qui construit des graphiques TensorFlow à partir de la fonction Python. Vous utilisez une Function de la même manière que son équivalent 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)

À l'extérieur, une Function ressemble à une fonction normale que vous écrivez à l'aide des opérations TensorFlow. En dessous , cependant, c'est très différent . Une Function encapsule plusieurs tf.Graph derrière une API . C'est ainsi que Function est en mesure de vous offrir les avantages de l'exécution de graphes , tels que la vitesse et la déployabilité.

tf.function s'applique à une fonction et à toutes les autres fonctions qu'elle appelle :

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 vous avez utilisé 1.x tensorflow, vous remarquerez que , à aucun moment vous devez définir un Placeholder ou tf.Session .

Conversion de fonctions Python en graphiques

Toute fonction que vous écrivez avec TensorFlow contiendra un mélange d'opérations TF intégrées et de logique Python, telles que if-then clauses if-then , des boucles, break , return , continue , etc. Alors que les opérations TensorFlow sont facilement capturées par un tf.Graph , la logique spécifique à Python doit subir une étape supplémentaire pour faire partie du graphique. tf.function utilise une bibliothèque appelée AutoGraph ( tf.autograph ) pour convertir le code Python en code générateur de graphes.

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

Bien qu'il soit peu probable que vous ayez besoin d'afficher les graphiques directement, vous pouvez inspecter les sorties pour voir les résultats exacts. Celles-ci ne sont pas faciles à lire, donc pas besoin de regarder trop attentivement!

# 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 plupart du temps, tf.function fonctionnera sans considérations particulières. Cependant, il y a quelques mises en garde, et le guide tf.function peut vous aider ici, ainsi que la référence complète d'AutoGraph

Polymorphisme: une Function , plusieurs graphes

Un tf.Graph est spécialisé pour un type spécifique d'entrées (par exemple, des tenseurs avec undtype spécifique ou des objets avec le même id() ).

Chaque fois que vous invoquez une Function avec de nouveaux dtypes et des formes dans ses arguments, Function crée un nouveau tf.Graph pour les nouveaux arguments. Les dtypes et les formes des entrées d'un tf.Graph sont connus sous le nom de signature d'entrée ou simplement de signature .

La Function stocke le tf.Graph correspondant à cette signature dans un ConcreteFunction . Un ConcreteFunction est un wrapper autour d'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 a déjà été appelée avec cette signature, Function ne crée pas de nouveau 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)

Parce qu'elle est soutenue par plusieurs graphiques, une Function est polymorphe . Cela lui permet de prendre en charge plus de types d'entrée qu'un seul tf.Graph pourrait représenter, ainsi que d'optimiser chaque tf.Graph pour de meilleures performances.

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

Utilisation de tf.function

Jusqu'à présent, vous avez vu comment convertir une fonction Python en graphique simplement en utilisant tf.function comme décorateur ou wrapper. Mais en pratique, faire fonctionner correctement tf.function peut être délicat! Dans les sections suivantes, vous apprendrez comment faire fonctionner votre code comme prévu avec tf.function .

Exécution de graphes vs exécution impatiente

Le code d'une Function peut être exécuté à la fois avec empressement et sous forme de graphique. Par défaut, Function exécute son code sous forme de graphique:

@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>

Pour vérifier que le graphe de votre Function effectue le même calcul que sa fonction Python équivalente, vous pouvez le faire exécuter avec empressement avec tf.config.run_functions_eagerly(True) . Il s'agit d'un commutateur qui désactive la capacité de Function à créer et exécuter des graphiques , au lieu d'exécuter le code normalement.

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)

Cependant, Function peut se comporter différemment sous un graphe et une exécution rapide. La fonction d' print Python est un exemple de la différence entre ces deux modes. Voyons ce qui se passe lorsque vous insérez une instruction d' print dans notre fonction et que vous l'appelez à plusieurs reprises.

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

Observez ce qui est imprimé:

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

La sortie est-elle surprenante? get_MSE n'est imprimé qu'une seule fois même s'il a été appelé trois fois.

Pour expliquer, l'instruction d' print est exécutée lorsque Function exécute le code d'origine afin de créer le graphique dans un processus appelé «traçage» . Le traçage capture les opérations TensorFlow dans un graphique et l' print n'est pas capturée dans le graphique. Ce graphique est ensuite exécuté pour les trois appels sans jamais exécuter à nouveau le code Python .

Pour vérifier la cohérence, désactivons l'exécution du graphique pour comparer:

# 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 est un effet secondaire Python , et il existe d' autres différences dont vous devez être conscient lors de la conversion d'une fonction en Function .

Meilleures pratiques de tf.function

Cela peut prendre un certain temps pour s'habituer au comportement de Function . Pour commencer rapidement, les utilisateurs @tf.function doivent jouer avec les fonctions de décoration des jouets avec @tf.function pour acquérir de l'expérience en passant de l'exécution impatiente à l'exécution graphique.

Concevoir pour tf.function peut être votre meilleurtf.function pour écrire des programmes TensorFlow compatibles avec les graphes. Voici quelques conseils:

  • Basculez entre l'exécution hâtive et l'exécution du graphe tôt et souvent avec tf.config.run_functions_eagerly pour déterminer si / quand les deux modes divergent.
  • Créez des tf.Variable dehors de la fonction Python et modifiez-les à l'intérieur. Il en va de même pour les objets qui utilisent tf.Variable , commekeras.layers , keras.Model s et tf.optimizers .
  • Évitez d'écrire des fonctions qui dépendent de variables Python externes , à l'exclusion des objets tf.Variables et Keras.
  • Préférez écrire des fonctions qui prennent des tenseurs et d'autres types TensorFlow en entrée. Vous pouvez passer d'autres types d'objets mais attention !
  • Incluez autant de calculs que possible sous une fonction tf.function pour maximiser le gain de performance. Par exemple, décorez une étape d'entraînement entière ou la boucle d'entraînement entière.

Voir l'accélération

tf.function améliore généralement les performances de votre code, mais la quantité d'accélération dépend du type de calcul que vous exécutez. Les petits calculs peuvent être dominés par la surcharge de l'appel d'un graphe. Vous pouvez mesurer la différence de performance comme ceci:

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 est couramment utilisé pour accélérer les boucles d'entraînement, comme vous pouvez le voir ici avec Keras.

Performances et compromis

Les graphiques peuvent accélérer votre code, mais le processus de création entraîne une certaine surcharge. Pour certaines fonctions, la création du graphe prend plus de temps que l'exécution du graphe. Cet investissement est généralement rapidement remboursé grâce à l'amélioration des performances des exécutions ultérieures, mais il est important de savoir que les premières étapes de toute formation sur un grand modèle peuvent être plus lentes en raison du traçage.

Quelle que soit la taille de votre modèle, vous souhaitez éviter de tracer fréquemment. Le guide tf.function explique comment définir les spécifications d'entrée et utiliser des arguments tensoriels pour éviter de retracer. Si vous constatez que vous obtenez des performances inhabituellement médiocres, c'est une bonne idée de vérifier si vous retracez accidentellement.

Quand une Function tracée?

Pour savoir quand votre Function trace, ajoutez une instruction d' print à son code. En règle générale, Function exécutera l'instruction d' print à chaque fois qu'elle trace.

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

Ici, vous voyez un traçage supplémentaire car les nouveaux arguments Python déclenchent toujours la création d'un nouveau graphe.

Prochaines étapes

Vous pouvez lire une discussion plus approfondie à la fois sur la page de référence de l'API tf.function et dans le guide .