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

TensorFlow 2 efficace

Il existe plusieurs changements dans TensorFlow 2.0 pour rendre les utilisateurs de TensorFlow plus productifs. TensorFlow 2.0 supprime les API redondantes , rend les API plus cohérentes ( RNN unifiés, optimiseurs unifiés ) et s'intègre mieux au runtime Python avec une exécution Eager .

De nombreux RFC ont expliqué les changements apportés à la création de TensorFlow 2.0. Ce guide présente une vision de ce à quoi devrait ressembler le développement dans TensorFlow 2.0. On suppose que vous avez une certaine familiarité avec TensorFlow 1.x.

Un bref résumé des changements majeurs

Nettoyage d'API

De nombreuses API ont disparu ou ont été déplacées dans TF 2.0. Certains des changements majeurs incluent la suppression de tf.app , tf.flags et tf.logging en faveur de l' absl-py maintenant open-source, les projets de réhébergement qui vivaient dans tf.contrib et le nettoyage de l'espace de noms principal tf.* déplacer des fonctions moins utilisées dans des sous-packages comme tf.math . Certaines API ont été remplacées par leurs équivalents 2.0 - tf.summary , tf.keras.metrics et tf.keras.optimizers . Le moyen le plus simple d'appliquer automatiquement ces renommages est d'utiliser le script de mise à niveau v2 .

Exécution avide

TensorFlow 1.X oblige les utilisateurs à assembler manuellement une arborescence de syntaxe abstraite (le graphe) en effectuant des appels d'API tf.* . Il oblige ensuite les utilisateurs à compiler manuellement l'arborescence de syntaxe abstraite en passant un ensemble de tenseurs de sortie et de tenseurs d'entrée à un appel session.run() . TensorFlow 2.0 s'exécute avec empressement (comme Python le fait normalement) et en 2.0, les graphiques et les sessions devraient ressembler à des détails d'implémentation.

Un sous-produit notable d'une exécution tf.control_dependencies() est que tf.control_dependencies() n'est plus nécessaire, car toutes les lignes de code s'exécutent dans l'ordre (dans une fonction tf.function , le code avec des effets secondaires s'exécute dans l'ordre écrit).

Plus de globaux

TensorFlow 1.X s'appuyait fortement sur des espaces de noms implicitement globaux. Lorsque vous tf.Variable() , il était placé dans le graphique par défaut, et il y resterait, même si vous perdiez la trace de la variable Python pointant vers elle. Vous pourriez alors récupérer cette tf.Variable , mais seulement si vous connaissiez le nom avec lequel elle avait été créée. Cela était difficile à faire si vous ne contrôliez pas la création de la variable. En conséquence, toutes sortes de mécanismes ont proliféré pour tenter d'aider les utilisateurs à retrouver leurs variables, et pour que les frameworks trouvent des variables créées par l'utilisateur: portées de variables, collections globales, méthodes d'aide comme tf.get_global_step() , tf.global_variables_initializer() , les optimiseurs calculant implicitement des gradients sur toutes les variables entraînables, et ainsi de suite. TensorFlow 2.0 élimine tous ces mécanismes ( Variables 2.0 RFC ) au profit du mécanisme par défaut: Gardez une trace de vos variables! Si vous perdez la trace d'une tf.Variable , elle est récupérée.

L'obligation de suivre les variables crée du travail supplémentaire pour l'utilisateur, mais avec les objets Keras (voir ci-dessous), la charge est minimisée.

Des fonctions, pas des sessions

Un appel session.run() est presque comme un appel de fonction: vous spécifiez les entrées et la fonction à appeler, et vous récupérez un ensemble de sorties. Dans TensorFlow 2.0, vous pouvez décorer une fonction Python en utilisant tf.function() pour la marquer pour la compilation JIT afin que TensorFlow l'exécute comme un seul graphe ( Functions 2.0 RFC ). Ce mécanisme permet à TensorFlow 2.0 de bénéficier de tous les avantages du mode graphique:

  • Performances: la fonction peut être optimisée (élagage des nœuds, fusion du noyau, etc.)
  • Portabilité: la fonction peut être exportée / réimportée ( SavedModel 2.0 RFC ), permettant aux utilisateurs de réutiliser et de partager des fonctions TensorFlow modulaires.
# TensorFlow 1.X
outputs = session.run(f(placeholder), feed_dict={placeholder: input})
# TensorFlow 2.0
outputs = f(input)

Avec la possibilité d'intercaler librement le code Python et TensorFlow, les utilisateurs peuvent profiter de l'expressivité de Python. Mais TensorFlow portable s'exécute dans des contextes sans interpréteur Python, tels que mobile, C ++ et JavaScript. Pour aider les utilisateurs à éviter d'avoir à réécrire leur code lors de l'ajout de @tf.function , AutoGraph convertit un sous-ensemble de constructions Python en leurs équivalents TensorFlow:

AutoGraph prend en charge les imbrications arbitraires de flux de contrôle, ce qui permet d'implémenter de manière performante et concise de nombreux programmes de ML complexes tels que les modèles de séquence, l'apprentissage par renforcement, les boucles d'entraînement personnalisées, etc.

Recommandations pour le TensorFlow 2.0 idiomatique

Refactorisez votre code en fonctions plus petites

Un modèle d'utilisation courant dans TensorFlow 1.X était la stratégie «évier de cuisine», où l'union de tous les calculs possibles était préemptive, puis les tenseurs sélectionnés étaient évalués via session.run() . Dans TensorFlow 2.0, les utilisateurs doivent refactoriser leur code en fonctions plus petites qui sont appelées au besoin. En général, il n'est pas nécessaire de décorer chacune de ces petites fonctions avec tf.function ; n'utilisez tf.function pour décorer des calculs de haut niveau - par exemple, une étape d'entraînement ou la passe avant de votre modèle.

Utiliser des couches et des modèles Keras pour gérer les variables

Les modèles et couches Keras offrent les variables pratiques et les propriétés trainable_variables , qui rassemblent de manière récursive toutes les variables dépendantes. Cela facilite la gestion des variables localement là où elles sont utilisées.

Contraste:

def dense(x, W, b):
  return tf.nn.sigmoid(tf.matmul(x, W) + b)

@tf.function
def multilayer_perceptron(x, w0, b0, w1, b1, w2, b2 ...):
  x = dense(x, w0, b0)
  x = dense(x, w1, b1)
  x = dense(x, w2, b2)
  ...

# You still have to manage w_i and b_i, and their shapes are defined far away from the code.

avec la version Keras:

# Each layer can be called, with a signature equivalent to linear(x)
layers = [tf.keras.layers.Dense(hidden_size, activation=tf.nn.sigmoid) for _ in range(n)]
perceptron = tf.keras.Sequential(layers)

# layers[3].trainable_variables => returns [w3, b3]
# perceptron.trainable_variables => returns [w0, b0, ...]

Les couches / modèles Keras héritent de tf.train.Checkpointable et sont intégrés à @tf.function , qui permet de @tf.function directement ou d'exporter des SavedModels à partir d'objets Keras. Vous n'avez pas nécessairement besoin d'utiliser l'API .fit() de Keras pour profiter de ces intégrations.

Voici un exemple d'apprentissage par transfert qui montre comment Keras facilite la collecte d'un sous-ensemble de variables pertinentes. Disons que vous entraînez un modèle à plusieurs têtes avec un tronc partagé:

trunk = tf.keras.Sequential([...])
head1 = tf.keras.Sequential([...])
head2 = tf.keras.Sequential([...])

path1 = tf.keras.Sequential([trunk, head1])
path2 = tf.keras.Sequential([trunk, head2])

# Train on primary dataset
for x, y in main_dataset:
  with tf.GradientTape() as tape:
    # training=True is only needed if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    prediction = path1(x, training=True)
    loss = loss_fn_head1(prediction, y)
  # Simultaneously optimize trunk and head1 weights.
  gradients = tape.gradient(loss, path1.trainable_variables)
  optimizer.apply_gradients(zip(gradients, path1.trainable_variables))

# Fine-tune second head, reusing the trunk
for x, y in small_dataset:
  with tf.GradientTape() as tape:
    # training=True is only needed if there are layers with different
    # behavior during training versus inference (e.g. Dropout).
    prediction = path2(x, training=True)
    loss = loss_fn_head2(prediction, y)
  # Only optimize head2 weights, not trunk weights
  gradients = tape.gradient(loss, head2.trainable_variables)
  optimizer.apply_gradients(zip(gradients, head2.trainable_variables))

# You can publish just the trunk computation for other people to reuse.
tf.saved_model.save(trunk, output_path)

Combinez tf.data.Datasets et @ tf.function

Lors de l'itération sur des données d'entraînement qui tiennent en mémoire, n'hésitez pas à utiliser l'itération Python régulière. Sinon, tf.data.Dataset est le meilleur moyen de diffuser des données d'entraînement à partir du disque. Les ensembles de données sont des itérables (pas des itérateurs) et fonctionnent comme les autres itérables Python en mode Eager. Vous pouvez pleinement utiliser les fonctionnalités de prélecture / diffusion asynchrone des ensembles de données en enveloppant votre code dans tf.function() , qui remplace l'itération Python par les opérations graphiques équivalentes à l'aide d'AutoGraph.

@tf.function
def train(model, dataset, optimizer):
  for x, y in dataset:
    with tf.GradientTape() as tape:
      # training=True is only needed if there are layers with different
      # behavior during training versus inference (e.g. Dropout).
      prediction = model(x, training=True)
      loss = loss_fn(prediction, y)
    gradients = tape.gradient(loss, model.trainable_variables)
    optimizer.apply_gradients(zip(gradients, model.trainable_variables))

Si vous utilisez l' .fit() Keras .fit() , vous n'aurez pas à vous soucier de l'itération de l'ensemble de données.

model.compile(optimizer=optimizer, loss=loss_fn)
model.fit(dataset)

Tirez parti d'AutoGraph avec le flux de contrôle Python

AutoGraph fournit un moyen de convertir le flux de contrôle dépendant des données en équivalents en mode graphique comme tf.cond et tf.while_loop . tf.while_loop .

Les modèles de séquence sont un endroit courant où le flux de contrôle dépendant des données apparaît. tf.keras.layers.RNN une cellule RNN, vous permettant de dérouler statiquement ou dynamiquement la récurrence. Pour des raisons de démonstration, vous pouvez réimplémenter le déroulement dynamique comme suit:

class DynamicRNN(tf.keras.Model):

  def __init__(self, rnn_cell):
    super(DynamicRNN, self).__init__(self)
    self.cell = rnn_cell

  def call(self, input_data):
    # [batch, time, features] -> [time, batch, features]
    input_data = tf.transpose(input_data, [1, 0, 2])
    outputs = tf.TensorArray(tf.float32, input_data.shape[0])
    state = self.cell.zero_state(input_data.shape[1], dtype=tf.float32)
    for i in tf.range(input_data.shape[0]):
      output, state = self.cell(input_data[i], state)
      outputs = outputs.write(i, output)
    return tf.transpose(outputs.stack(), [1, 0, 2]), state

Pour une présentation plus détaillée des fonctionnalités d'AutoGraph, consultez le guide .

tf.metrics agrège les données et tf.summary les enregistre

Pour enregistrer les résumés, utilisez tf.summary.(scalar|histogram|...) et redirigez-le vers un écrivain à l'aide d'un gestionnaire de contexte. (Si vous omettez le gestionnaire de contexte, rien ne se passe.) Contrairement à TF 1.x, les résumés sont envoyés directement à l'écrivain; il n'y a pas d'opération "merge" séparée et pas d' add_summary() séparé add_summary() , ce qui signifie que la valeur de l' step doit être fournie sur le site d'appel.

summary_writer = tf.summary.create_file_writer('/tmp/summaries')
with summary_writer.as_default():
  tf.summary.scalar('loss', 0.1, step=42)

Pour agréger les données avant de les enregistrer sous forme de résumés, utilisez tf.metrics . Les métriques sont avec état: elles accumulent des valeurs et renvoient un résultat cumulatif lorsque vous appelez .result() . Effacez les valeurs accumulées avec .reset_states() .

def train(model, optimizer, dataset, log_freq=10):
  avg_loss = tf.keras.metrics.Mean(name='loss', dtype=tf.float32)
  for images, labels in dataset:
    loss = train_step(model, optimizer, images, labels)
    avg_loss.update_state(loss)
    if tf.equal(optimizer.iterations % log_freq, 0):
      tf.summary.scalar('loss', avg_loss.result(), step=optimizer.iterations)
      avg_loss.reset_states()

def test(model, test_x, test_y, step_num):
  # training=False is only needed if there are layers with different
  # behavior during training versus inference (e.g. Dropout).
  loss = loss_fn(model(test_x, training=False), test_y)
  tf.summary.scalar('loss', loss, step=step_num)

train_summary_writer = tf.summary.create_file_writer('/tmp/summaries/train')
test_summary_writer = tf.summary.create_file_writer('/tmp/summaries/test')

with train_summary_writer.as_default():
  train(model, optimizer, dataset)

with test_summary_writer.as_default():
  test(model, test_x, test_y, optimizer.iterations)

Visualisez les résumés générés en pointant TensorBoard vers le répertoire du journal des résumés:

tensorboard --logdir /tmp/summaries

Utilisez tf.config.experimental_run_functions_eagerly () lors du débogage

Dans TensorFlow 2.0, l'exécution Eager vous permet d'exécuter le code étape par étape pour inspecter les formes, les types de données et les valeurs. Certaines API, comme tf.function , tf.keras , etc. sont conçues pour utiliser l'exécution de Graph, pour des tf.keras de performances et de portabilité. Lors du débogage, utilisez tf.config.experimental_run_functions_eagerly(True) pour utiliser l'exécution Eager dans ce code.

Par exemple:

@tf.function
def f(x):
  if x > 0:
    import pdb
    pdb.set_trace()
    x = x + 1
  return x

tf.config.experimental_run_functions_eagerly(True)
f(tf.constant(1))
>>> f()
-> x = x + 1
(Pdb) l
  6     @tf.function
  7     def f(x):
  8       if x > 0:
  9         import pdb
 10         pdb.set_trace()
 11  ->     x = x + 1
 12       return x
 13
 14     tf.config.experimental_run_functions_eagerly(True)
 15     f(tf.constant(1))
[EOF]

Cela fonctionne également dans les modèles Keras et d'autres API qui prennent en charge l'exécution Eager:

class CustomModel(tf.keras.models.Model):

  @tf.function
  def call(self, input_data):
    if tf.reduce_mean(input_data) > 0:
      return input_data
    else:
      import pdb
      pdb.set_trace()
      return input_data // 2


tf.config.experimental_run_functions_eagerly(True)
model = CustomModel()
model(tf.constant([-2, -4]))
>>> call()
-> return input_data // 2
(Pdb) l
 10         if tf.reduce_mean(input_data) > 0:
 11           return input_data
 12         else:
 13           import pdb
 14           pdb.set_trace()
 15  ->       return input_data // 2
 16
 17
 18     tf.config.experimental_run_functions_eagerly(True)
 19     model = CustomModel()
 20     model(tf.constant([-2, -4]))