Cette page a été traduite par l'API Cloud Translation.
Switch to English

Introduction aux graphiques et aux fonctions

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

Introduction aux graphiques et tf.function

Ce guide va 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 ils sont stockés et représentés, et comment vous pouvez les utiliser pour accélérer et exporter vos modèles.

Ceci est une introduction abrégée; pour une introduction complète à ces concepts, 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. Eager TensorFlow tire parti des GPU, vous permettant de placer des variables, des tenseurs et même des opérations sur des GPU et des TPU. Il est également facile à déboguer.

Pour certains utilisateurs, vous n'aurez peut-être jamais besoin ou ne voudrez jamais quitter Python.

Cependant, l'exécution de TensorFlow opération par opération en Python empêche une multitude d'accélérations autrement disponibles. Si vous pouvez extraire des calculs tensoriels de Python, vous pouvez les transformer en graphique .

Les graphes sont des structures de données contenant 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 simple graphique à deux couches lorsqu'il est visualisé dans TensorBoard.

un graphe tensorflow à deux couches

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 principaux. 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 d'effectuer des transformations telles que:

  • Déduisez statiquement la valeur des tenseurs en repliant les nœuds constants dans votre calcul ("repliage 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 cela 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.

Tracer des graphiques

La façon dont vous créez un graphique dans TensorFlow consiste à utiliser tf.function , soit en tant tf.function , soit en tant que décorateur.

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

# Create a `Function` object that contains a graph
a_function_that_uses_a_graph = tf.function(function_to_get_faster)

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

# It just works!
a_function_that_uses_a_graph(x1, y1, b1).numpy()
 
array([[12.]], dtype=float32)

tf.function fonctions tf.function sont des fonctions appelables Python qui fonctionnent de la même manière que leurs équivalents Python. Ils ont une classe particulière ( python.eager.def_function.Function ), mais pour vous, ils agissent comme la version non tracée.

tf.function trace récursivement toute fonction Python qu'il appelle.

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

# Use the decorator
@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.Sesssion .

Contrôle de flux et effets secondaires

Le contrôle de flux et les boucles sont convertis en TensorFlow via tf.autograph par défaut. Autograph utilise une combinaison de méthodes, y compris la normalisation des constructions de boucle, le déroulement et la manipulation AST .

 def my_function(x):
  if tf.reduce_sum(x) <= 1:
    return x * x
  else:
    return x-1

a_function = tf.function(my_function)

print("First branch, with graph:", a_function(tf.constant(1.0)).numpy())
print("Second branch, with graph:", a_function(tf.constant([5.0, 5.0])).numpy())
 
First branch, with graph: 1.0
Second branch, with graph: [4. 4.]

Vous pouvez appeler directement la conversion Autograph pour voir comment Python est converti en opérations TensorFlow. C'est, pour la plupart, illisible, mais vous pouvez voir la transformation.

 # Don't read the output too carefully.
print(tf.autograph.to_code(my_function))
 
def tf__my_function(x):
    with ag__.FunctionScope('my_function', '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 (retval_, do_return)

        def set_state(vars_):
            nonlocal retval_, do_return
            (retval_, do_return) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) * ag__.ld(x))
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) - 1)
            except:
                do_return = False
                raise
        ag__.if_stmt((ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) <= 1), if_body, else_body, get_state, set_state, ('retval_', 'do_return'), 2)
        return fscope.ret(retval_, do_return)


Autograph convertit automatiquement les clauses if-then , les boucles, les break , les return , les continue , etc.

La plupart du temps, Autograph 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 de l'autographe

Voyant l'accélération

Le tf.function fait d' tf.function fonction utilisant un tenseur dans tf.function pas automatiquement votre code. Pour les petites fonctions appelées plusieurs fois sur une seule machine, la surcharge de l'appel d'un graphe ou d'un fragment de graphe peut dominer l'exécution. De plus, si la plupart des calculs se déroulaient déjà sur un accélérateur, comme des piles de convolutions lourdes en GPU, l'accélération du graphe ne sera pas importante.

Pour les calculs compliqués, les graphiques peuvent fournir une accélération significative. Cela est dû au fait que les graphiques réduisent la communication Python-appareil et effectuent certaines accélérations.

Ce code exécute quelques exécutions sur de petites couches denses.

 # Create an oveerride model to classify pictures
class SequentialModel(tf.keras.Model):
  def __init__(self, **kwargs):
    super(SequentialModel, self).__init__(**kwargs)
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return x

input_data = tf.random.uniform([60, 28, 28])

eager_model = SequentialModel()
graph_model = tf.function(eager_model)

print("Eager time:", timeit.timeit(lambda: eager_model(input_data), number=10000))
print("Graph time:", timeit.timeit(lambda: graph_model(input_data), number=10000))

 
Eager time: 4.9249971129993355
Graph time: 2.026840765000088

Fonctions polymorphes

Lorsque vous tracez une fonction, vous créez un objet Function est polymorphe . Une fonction polymorphe est un appelable Python qui encapsule plusieurs graphes de fonctions concrets derrière une API.

Vous pouvez utiliser cette Function sur tous les types de dtypes et de formes. Chaque fois que vous l'appelez avec une nouvelle signature d'argument, la fonction d'origine est retracée avec les nouveaux arguments. La Function stocke ensuite le tf.Graph correspondant à cette trace dans une fonction concrete_function . Si la fonction a déjà été tracée avec ce type d'argument, vous obtenez simplement votre graphe pré-tracé.

Conceptuellement, alors:

  • Un tf.Graph est la structure de données brute et portable décrivant un calcul
  • Une Function est une mise en cache, un traçage, un répartiteur sur ConcreteFunctions
  • Une ConcreteFunction est un wrapper compatible avec un graphe qui vous permet d'exécuter le graphe à partir de Python

Inspection des fonctions polymorphes

Vous pouvez inspecter a_function , qui est le résultat de l'appel de tf.function sur la fonction Python my_function . Dans cet exemple, appeler a_function avec trois types d'arguments donne trois fonctions concrètes différentes.

 print(a_function)

print("Calling a `Function`:")
print("Int:", a_function(tf.constant(2)))
print("Float:", a_function(tf.constant(2.0)))
print("Rank-1 tensor of floats", a_function(tf.constant([2.0, 2.0, 2.0])))
 
<tensorflow.python.eager.def_function.Function object at 0x7f466417bf60>
Calling a `Function`:
Int: tf.Tensor(1, shape=(), dtype=int32)
Float: tf.Tensor(1.0, shape=(), dtype=float32)
Rank-1 tensor of floats tf.Tensor([1. 1. 1.], shape=(3,), dtype=float32)

 # Get the concrete function that works on floats
print("Inspecting concrete functions")
print("Concrete function for float:")
print(a_function.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.float32)))
print("Concrete function for tensor of floats:")
print(a_function.get_concrete_function(tf.constant([2.0, 2.0, 2.0])))

 
Inspecting concrete functions
Concrete function for float:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()
Concrete function for tensor of floats:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=(3,)
  Returns:
    float32 Tensor, shape=(3,)

 # Concrete functions are callable
# Note: You won't normally do this, but instead just call the containing `Function`
cf = a_function.get_concrete_function(tf.constant(2))
print("Directly calling a concrete function:", cf(tf.constant(2)))
 
Directly calling a concrete function: tf.Tensor(1, shape=(), dtype=int32)

Dans cet exemple, vous voyez assez loin dans la pile. Sauf si vous gérez spécifiquement le traçage, vous n'aurez normalement pas besoin d'appeler directement des fonctions concrètes comme indiqué ici.

Revenir à une exécution impatiente

Vous pouvez vous retrouver à regarder de longues traces de pile, spécialement celles qui font référence à tf.Graph ou with tf.Graph().as_default() . Cela signifie que vous exécutez probablement dans un contexte graphique. Les fonctions principales de TensorFlow utilisent des contextes de graphes, tels que model.fit() de Keras.

Il est souvent beaucoup plus facile de déboguer une exécution impatiente. Les traces de pile doivent être relativement courtes et faciles à comprendre.

Dans les situations où le graphique rend le débogage difficile, vous pouvez revenir à l'utilisation d'une exécution impatiente pour le débogage.

Voici des moyens de vous assurer que vous exécutez avec enthousiasme:

  • Appeler des modèles et des couches directement comme appelables

  • Lors de l'utilisation de la compilation / ajustement Keras, au moment de la compilation, utilisez model.compile(run_eagerly=True)

  • Définir le mode d'exécution global via tf.config.run_functions_eagerly(True)

Utilisation de run_eagerly=True

 # Define an identity layer with an eager side effect
class EagerLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(EagerLayer, self).__init__(**kwargs)
    # Do some kind of initialization here

  def call(self, inputs):
    print("\nCurrently running eagerly", str(datetime.now()))
    return inputs
 
 # Create an override model to classify pictures, adding the custom layer
class SequentialModel(tf.keras.Model):
  def __init__(self):
    super(SequentialModel, self).__init__()
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)
    self.eager = EagerLayer()

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return self.eager(x)

# Create an instance of this model
model = SequentialModel()

# Generate some nonsense pictures and labels
input_data = tf.random.uniform([60, 28, 28])
labels = tf.random.uniform([60])

loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
 

Tout d'abord, compilez le modèle sans enthousiasme. Notez que le modèle n'est pas tracé; malgré son nom, compile ne configure que les fonctions de perte, l'optimisation et d'autres paramètres d'apprentissage.

 model.compile(run_eagerly=False, loss=loss_fn)
 

Maintenant, appelez fit et voyez que la fonction est tracée (deux fois) et que l'effet désireux ne s'exécute plus jamais.

 model.fit(input_data, labels, epochs=3)
 
Epoch 1/3

Currently running eagerly 2020-08-04 01:22:21.848492

Currently running eagerly 2020-08-04 01:22:21.955102
2/2 [==============================] - 0s 1ms/step - loss: 1.4056
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0037
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0019

<tensorflow.python.keras.callbacks.History at 0x7f45f03c5630>

Si vous exécutez ne serait-ce qu'une seule époque avec impatience, vous pouvez voir l'effet secondaire impatient deux fois.

 print("Running eagerly")
# When compiling the model, set it to run eagerly
model.compile(run_eagerly=True, loss=loss_fn)

model.fit(input_data, labels, epochs=1)

 
Running eagerly

Currently running eagerly 2020-08-04 01:22:22.152979
1/2 [==============>...............] - ETA: 0s - loss: 8.7806e-04
Currently running eagerly 2020-08-04 01:22:22.173295
2/2 [==============================] - 0s 5ms/step - loss: 4.6877e-04

<tensorflow.python.keras.callbacks.History at 0x7f45f0312940>

Utilisation de run_functions_eagerly

Vous pouvez également définir globalement tout pour qu'il s'exécute avec empressement. Notez que cela ne fonctionne que si vous retracez; les fonctions tracées resteront tracées et seront exécutées sous forme de graphique.

 # Now, globally set everything to run eagerly
tf.config.run_functions_eagerly(True)
print("Run all functions eagerly.")

# First, trace the model, triggering the side effect
polymorphic_function = tf.function(model)

# It was traced...
print(polymorphic_function.get_concrete_function(input_data))

# But when you run the function again, the side effect happens (both times).
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
 
Run all functions eagerly.

Currently running eagerly 2020-08-04 01:22:22.202726
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-08-04 01:22:22.206521

Currently running eagerly 2020-08-04 01:22:22.207818

 # Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

 
WARNING:tensorflow:From <ipython-input-17-782fe9ce7b18>:2: experimental_run_functions_eagerly (from tensorflow.python.eager.def_function) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.run_functions_eagerly` instead of the experimental version.

Traçage et performance

Le traçage coûte des frais généraux. Bien que le traçage des petites fonctions soit rapide, les modèles de grande taille peuvent prendre un temps notable sur l'horloge murale. Cet investissement est généralement rapidement remboursé avec une amélioration des performances, mais il est important de savoir que les premières époques 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. Cette section du 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, il est bon de vérifier si vous retracez accidentellement.

Vous pouvez ajouter un effet secondaire uniquement désireux (comme l'impression d'un argument Python) afin de voir quand la fonction est tracée. Ici, vous voyez un retracement supplémentaire car les nouveaux arguments Python déclenchent toujours le retracement.

 # Use @tf.function decorator
@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!")  # This eager
  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)))

# This retraces each time the Python argument chances
# 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)
tf.Tensor(11, shape=(), dtype=int32)
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

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 .