![]() | ![]() | ![]() | ![]() |
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.
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 utilisenttf.Variable
, commekeras.layers
,keras.Model
s ettf.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 .