Utiliser les modèles TF1.x dans les workflows TF2

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

Ce guide fournit une vue d'ensemble et des exemples d'un shim de code de modélisation que vous pouvez utiliser pour utiliser vos modèles TF1.x existants dans des flux de travail TF2 tels que l'exécution rapide, tf.function et les stratégies de distribution avec des modifications minimales de votre code de modélisation.

Domaine d'utilisation

La cale décrite dans ce guide est conçue pour les modèles TF1.x qui reposent sur :

  1. tf.compat.v1.get_variable et tf.compat.v1.variable_scope pour contrôler la création et la réutilisation des variables, et
  2. API basées sur la collection de graphiques telles que tf.compat.v1.global_variables() , tf.compat.v1.trainable_variables , tf.compat.v1.losses.get_regularization_losses() et tf.compat.v1.get_collection() pour garder une trace des poids et des pertes de régularisation

Cela inclut la plupart des modèles construits sur les API tf.compat.v1.layer , tf.contrib.layers et TensorFlow-Slim .

La cale n'est PAS nécessaire pour les modèles TF1.x suivants :

  1. Modèles Keras autonomes qui suivent déjà tous leurs poids entraînables et leurs pertes de régularisation via model.trainable_weights et model.losses respectivement.
  2. tf.Module s qui suivent déjà tous leurs poids entraînables via module.trainable_variables , et ne créent des poids que s'ils n'ont pas déjà été créés.

Ces modèles sont susceptibles de fonctionner dans TF2 avec une exécution rapide et tf.function s prêts à l'emploi.

Installer

Importez TensorFlow et d'autres dépendances.

pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is available only in
# Tensorflow 2.8

pip install -q tf-nightly
import tensorflow as tf
import tensorflow.compat.v1 as v1
import sys
import numpy as np

from contextlib import contextmanager

Le décorateur track_tf1_style_variables

Le shim clé décrit dans ce guide est tf.compat.v1.keras.utils.track_tf1_style_variables , un décorateur que vous pouvez utiliser dans les méthodes appartenant à tf.keras.layers.Layer et tf.Module pour suivre les poids de style TF1.x et capturer les pertes de régularisation.

Décorer les méthodes d'appel d'un tf.keras.layers.Layer ou d'un tf.Module avec tf.compat.v1.keras.utils.track_tf1_style_variables permet la création et la réutilisation de variables via tf.compat.v1.get_variable (et par extension tf.compat.v1.layers ) pour fonctionner correctement à l'intérieur de la méthode décorée plutôt que de toujours créer une nouvelle variable à chaque appel. Cela amènera également la couche ou le module à suivre implicitement tous les poids créés ou accessibles via get_variable à l'intérieur de la méthode décorée.

En plus de suivre les poids eux-mêmes sous le standard layer.variable / module.variable /etc. properties, si la méthode appartient à un tf.keras.layers.Layer , toutes les pertes de régularisation spécifiées via les arguments de régularisation get_variable ou tf.compat.v1.layers seront suivies par la couche sous la propriété standard layer.losses .

Ce mécanisme de suivi permet d'utiliser de grandes classes de code de passage de modèle de style TF1.x à l'intérieur des couches Keras ou tf.Module s dans TF2 même avec les comportements TF2 activés.

Exemples d'utilisation

Les exemples d'utilisation ci-dessous illustrent les shims de modélisation utilisés pour décorer les méthodes tf.keras.layers.Layer , mais sauf lorsqu'ils interagissent spécifiquement avec les fonctionnalités Keras, ils sont également applicables lors de la décoration des méthodes tf.Module .

Couche construite avec tf.compat.v1.get_variable

Imaginez que vous ayez une couche implémentée directement au-dessus de tf.compat.v1.get_variable comme suit :

def dense(self, inputs, units):
  out = inputs
  with tf.compat.v1.variable_scope("dense"):
    # The weights are created with a `regularizer`,
    kernel = tf.compat.v1.get_variable(
        shape=[out.shape[-1], units],
        regularizer=tf.keras.regularizers.L2(),
        initializer=tf.compat.v1.initializers.glorot_normal,
        name="kernel")
    bias = tf.compat.v1.get_variable(
        shape=[units,],
        initializer=tf.compat.v1.initializers.zeros,
        name="bias")
    out = tf.linalg.matmul(out, kernel)
    out = tf.compat.v1.nn.bias_add(out, bias)
  return out

Utilisez le shim pour le transformer en calque et appelez-le sur les entrées.

class DenseLayer(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    out = inputs
    with tf.compat.v1.variable_scope("dense"):
      # The weights are created with a `regularizer`,
      # so the layer should track their regularization losses
      kernel = tf.compat.v1.get_variable(
          shape=[out.shape[-1], self.units],
          regularizer=tf.keras.regularizers.L2(),
          initializer=tf.compat.v1.initializers.glorot_normal,
          name="kernel")
      bias = tf.compat.v1.get_variable(
          shape=[self.units,],
          initializer=tf.compat.v1.initializers.zeros,
          name="bias")
      out = tf.linalg.matmul(out, kernel)
      out = tf.compat.v1.nn.bias_add(out, bias)
    return out

layer = DenseLayer(10)
x = tf.random.normal(shape=(8, 20))
layer(x)
WARNING:tensorflow:From /tmp/ipykernel_27038/795621215.py:7: The name tf.keras.utils.track_tf1_style_variables is deprecated. Please use tf.compat.v1.keras.utils.track_tf1_style_variables instead.
<tf.Tensor: shape=(8, 10), dtype=float32, numpy=
array([[-0.51018804, -0.58145535,  0.25050664, -0.09880018,  0.71741414,
        -0.08512568,  0.33404148,  0.50894034,  0.19362557,  0.03945067],
       [-0.66160053,  0.43442816, -0.6187523 ,  0.00753711,  1.3946855 ,
         0.22528797,  0.55661404, -1.6155301 ,  1.5854199 , -0.4165327 ],
       [ 0.15855707,  0.43848652,  0.04762229,  0.22020248,  0.88300526,
         0.31525093, -0.10912375,  0.03332198,  1.3462385 , -0.37986106],
       [ 0.02546233, -0.01084138,  0.0417656 ,  1.1082407 ,  0.926408  ,
         0.46938205,  1.0183189 ,  1.2039868 , -0.09619217, -0.50863194],
       [-1.6222394 ,  0.17156005, -0.07482994,  0.646423  ,  1.0284312 ,
         2.3619173 ,  0.6322627 ,  0.5350776 , -2.2700598 , -0.8211552 ],
       [-1.1044651 ,  0.7303245 ,  1.0183476 ,  1.2858934 ,  0.4575533 ,
         0.93400717,  0.5323913 , -0.01242167,  0.8308919 ,  0.03202473],
       [ 0.3880633 , -1.2345276 ,  0.7713047 , -0.33720714,  1.0418141 ,
        -1.055242  , -1.6942265 ,  1.705035  ,  0.8671215 ,  0.8162696 ],
       [ 0.02216246, -0.5235669 ,  0.01065174, -1.1682817 ,  0.44079733,
         0.25890222, -1.0779501 ,  0.37716752, -0.27636313, -0.6359312 ]],
      dtype=float32)>

Accédez aux variables suivies et aux pertes de régularisation capturées comme une couche Keras standard.

layer.trainable_variables
layer.losses
2021-12-04 02:24:42.941890: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
[<tf.Tensor: shape=(), dtype=float32, numpy=0.10789324>]

Pour voir que les poids sont réutilisés chaque fois que vous appelez le calque, définissez tous les poids sur zéro et appelez à nouveau le calque.

print("Resetting variables to zero:", [var.name for var in layer.trainable_variables])

for var in layer.trainable_variables:
  var.assign(var * 0.0)

# Note: layer.losses is not a live view and
# will get reset only at each layer call
print("layer.losses:", layer.losses)
print("calling layer again.")
out = layer(x)
print("layer.losses: ", layer.losses)
out
Resetting variables to zero: ['dense/bias:0', 'dense/kernel:0']
layer.losses: [<tf.Tensor: shape=(), dtype=float32, numpy=0.0>]
calling layer again.
layer.losses:  [<tf.Tensor: shape=(), dtype=float32, numpy=0.0>]
<tf.Tensor: shape=(8, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>

Vous pouvez également utiliser la couche convertie directement dans la construction du modèle fonctionnel Keras.

inputs = tf.keras.Input(shape=(20))
outputs = DenseLayer(10)(inputs)
model = tf.keras.Model(inputs=inputs, outputs=outputs)

x = tf.random.normal(shape=(8, 20))
model(x)

# Access the model variables and regularization losses
model.weights
model.losses
[<tf.Tensor: shape=(), dtype=float32, numpy=0.1345337>]

Modèle construit avec tf.compat.v1.layers

Imaginez que vous ayez une couche ou un modèle implémenté directement au-dessus de tf.compat.v1.layers comme suit :

def model(self, inputs, units):
  with tf.compat.v1.variable_scope('model'):
    out = tf.compat.v1.layers.conv2d(
        inputs, 3, 3,
        kernel_regularizer="l2")
    out = tf.compat.v1.layers.flatten(out)
    out = tf.compat.v1.layers.dense(
        out, units,
        kernel_regularizer="l2")
    return out

Utilisez le shim pour le transformer en calque et appelez-le sur les entrées.

class CompatV1LayerModel(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    with tf.compat.v1.variable_scope('model'):
      out = tf.compat.v1.layers.conv2d(
          inputs, 3, 3,
          kernel_regularizer="l2")
      out = tf.compat.v1.layers.flatten(out)
      out = tf.compat.v1.layers.dense(
          out, self.units,
          kernel_regularizer="l2")
      return out

layer = CompatV1LayerModel(10)
x = tf.random.normal(shape=(8, 5, 5, 5))
layer(x)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:12: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
  if sys.path[0] == '':
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/convolutional.py:575: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:13: UserWarning: `tf.layers.flatten` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Flatten` instead.
  del sys.path[0]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/core.py:541: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  app.launch_new_instance()
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/core.py:261: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)
<tf.Tensor: shape=(8, 10), dtype=float32, numpy=
array([[ 2.4439096 , -0.2912227 ,  1.5531251 ,  1.284059  ,  0.10077369,
        -0.4231838 ,  1.0458903 , -0.01530766,  0.07358164, -0.6108157 ],
       [-0.4576063 ,  0.34942552,  2.3044965 ,  1.1483003 , -1.2211238 ,
         0.5634397 ,  0.73821646, -0.07581732,  0.5747937 , -0.66470885],
       [-2.2948585 , -2.709268  ,  1.7494816 , -0.9808065 , -2.9099958 ,
         0.5067346 , -1.011502  ,  2.559535  , -3.0888772 ,  0.3522656 ],
       [ 1.7788265 ,  0.8846102 ,  0.45562026,  0.01498583, -0.12482446,
        -0.32868862, -0.7743829 ,  2.3106992 , -0.0997327 , -0.7715093 ],
       [ 0.40295708,  0.04771695, -0.21336336, -0.13069987,  2.279875  ,
         2.7284563 ,  0.6444641 , -1.1919906 ,  0.96321577,  1.0182515 ],
       [ 0.47900966,  0.04906505,  1.1335449 ,  0.2907704 ,  0.7732022 ,
         0.68217   ,  0.51932573, -0.45156685,  2.081223  ,  1.068861  ],
       [ 0.10084352,  1.6456002 ,  0.63820475,  1.5959243 ,  0.22463399,
         0.07713126,  0.7467398 , -1.5435244 ,  1.2494736 , -0.07683721],
       [ 2.1396816 ,  1.5613532 , -1.1726325 , -0.88917583,  1.6447946 ,
        -1.0071977 , -1.8496083 ,  1.1887017 ,  2.1971662 ,  2.1175954 ]],
      dtype=float32)>

Accédez aux variables suivies et aux pertes de régularisation capturées comme une couche Keras standard.

layer.trainable_variables
layer.losses
[<tf.Tensor: shape=(), dtype=float32, numpy=0.03623246>,
 <tf.Tensor: shape=(), dtype=float32, numpy=0.14618248>]

Pour voir que les poids sont réutilisés chaque fois que vous appelez le calque, définissez tous les poids sur zéro et appelez à nouveau le calque.

print("Resetting variables to zero:", [var.name for var in layer.trainable_variables])

for var in layer.trainable_variables:
  var.assign(var * 0.0)

out = layer(x)
print("layer.losses: ", layer.losses)
out
Resetting variables to zero: ['model/conv2d/bias:0', 'model/conv2d/kernel:0', 'model/dense/bias:0', 'model/dense/kernel:0']
layer.losses:  [<tf.Tensor: shape=(), dtype=float32, numpy=0.0>, <tf.Tensor: shape=(), dtype=float32, numpy=0.0>]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:12: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
  if sys.path[0] == '':
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:13: UserWarning: `tf.layers.flatten` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Flatten` instead.
  del sys.path[0]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  app.launch_new_instance()
<tf.Tensor: shape=(8, 10), dtype=float32, numpy=
array([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)>

Vous pouvez également utiliser la couche convertie directement dans la construction du modèle fonctionnel Keras.

inputs = tf.keras.Input(shape=(5, 5, 5))
outputs = CompatV1LayerModel(10)(inputs)
model = tf.keras.Model(inputs=inputs, outputs=outputs)

x = tf.random.normal(shape=(8, 5, 5, 5))
model(x)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:12: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
  if sys.path[0] == '':
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/base.py:573: UserWarning: `layer.updates` will be removed in a future version. This property should not be used in TensorFlow 2.0, as `updates` are applied automatically.
  _add_elements_to_collection(self.updates, tf.compat.v1.GraphKeys.UPDATE_OPS)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:13: UserWarning: `tf.layers.flatten` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Flatten` instead.
  del sys.path[0]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  app.launch_new_instance()
<tf.Tensor: shape=(8, 10), dtype=float32, numpy=
array([[ 0.19487001,  0.54727787,  1.1044168 , -0.6613899 , -0.26437742,
        -1.1580509 , -0.24707682,  0.97752655,  0.59436107,  0.13125825],
       [ 0.48974586, -1.3510125 ,  0.7186962 , -0.8996632 , -0.60448873,
         0.06332532,  0.31494308,  0.23021704, -1.9166642 ,  0.3890404 ],
       [-0.06499191, -0.21485235,  0.01158494,  1.4407377 , -0.0488929 ,
        -0.37594396, -0.4386894 , -0.08751169,  1.0905663 , -1.5450519 ],
       [-2.2749739 , -2.4603422 , -1.3834419 , -2.8800466 ,  0.8954872 ,
        -3.0429187 , -0.7885461 ,  1.6037437 , -3.1845028 , -1.0725503 ],
       [ 0.98735195, -0.45159122,  0.892656  ,  0.477053  ,  0.31193537,
        -0.44723228, -0.01815075, -0.47465172, -1.665448  , -2.105824  ],
       [-2.5408387 , -1.7552321 , -1.924145  , -0.6395873 ,  0.4081779 ,
        -0.48731515, -3.2637763 , -1.4409767 , -2.032539  ,  0.10204412],
       [ 2.1583526 ,  0.78955674, -0.07266375,  0.06652926,  2.1300716 ,
        -1.6256162 ,  0.56154627, -0.76179224,  2.2985756 , -1.5504618 ],
       [ 2.062847  ,  0.971378  , -1.0830508 ,  1.8224751 , -0.3542943 ,
         0.74113446, -0.6204865 ,  1.4503044 , -0.4979878 , -0.4383126 ]],
      dtype=float32)>
# Access the model variables and regularization losses
model.weights
model.losses
[<tf.Tensor: shape=(), dtype=float32, numpy=0.03079858>,
 <tf.Tensor: shape=(), dtype=float32, numpy=0.12991619>]

Capturez les mises à jour de normalisation par lots et les arguments de training modèle

Dans TF1.x, vous effectuez une normalisation par lots comme ceci :

  x_norm = tf.compat.v1.layers.batch_normalization(x, training=training)

  # ...

  update_ops = tf.compat.v1.get_collection(tf.GraphKeys.UPDATE_OPS)
  train_op = optimizer.minimize(loss)
  train_op = tf.group([train_op, update_ops])

Noter que:

  1. Les mises à jour de moyenne mobile de normalisation par lots sont suivies par get_collection qui a été appelée séparément de la couche
  2. tf.compat.v1.layers.batch_normalization nécessite un argument de training (généralement appelé is_training lors de l'utilisation des couches de normalisation par lots TF-Slim)

Dans TF2, en raison de l' exécution rapide et des dépendances de contrôle automatique, les mises à jour de moyenne mobile de normalisation par lots seront exécutées immédiatement. Il n'est pas nécessaire de les collecter séparément à partir de la collection de mises à jour et de les ajouter en tant que dépendances de contrôle explicites.

De plus, si vous donnez à la méthode de passage vers l'avant de votre tf.keras.layers.Layer un argument training , Keras pourra lui transmettre la phase d'entraînement actuelle et toutes les couches imbriquées comme il le fait pour n'importe quelle autre couche. Consultez la documentation de l'API pour tf.keras.Model pour plus d'informations sur la façon dont Keras gère l'argument de training .

Si vous décorez les méthodes tf.Module , vous devez vous assurer de passer manuellement tous les arguments d' training selon les besoins. Cependant, les mises à jour de moyenne mobile de normalisation par lots seront toujours appliquées automatiquement sans nécessiter de dépendances de contrôle explicites.

Les extraits de code suivants montrent comment intégrer des couches de normalisation par lots dans le shim et comment son utilisation dans un modèle Keras fonctionne (applicable à tf.keras.layers.Layer ).

class CompatV1BatchNorm(tf.keras.layers.Layer):

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    print("Forward pass called with `training` =", training)
    with v1.variable_scope('batch_norm_layer'):
      return v1.layers.batch_normalization(x, training=training)
print("Constructing model")
inputs = tf.keras.Input(shape=(5, 5, 5))
outputs = CompatV1BatchNorm()(inputs)
model = tf.keras.Model(inputs=inputs, outputs=outputs)

print("Calling model in inference mode")
x = tf.random.normal(shape=(8, 5, 5, 5))
model(x, training=False)

print("Moving average variables before training: ",
      {var.name: var.read_value() for var in model.non_trainable_variables})

# Notice that when running TF2 and eager execution, the batchnorm layer directly
# updates the moving averages while training without needing any extra control
# dependencies
print("calling model in training mode")
model(x, training=True)

print("Moving average variables after training: ",
      {var.name: var.read_value() for var in model.non_trainable_variables})
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:7: UserWarning: `tf.layers.batch_normalization` is deprecated and will be removed in a future version. Please use `tf.keras.layers.BatchNormalization` instead. In particular, `tf.control_dependencies(tf.GraphKeys.UPDATE_OPS)` should not be used (consult the `tf.keras.layers.BatchNormalization` documentation).
  import sys
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/normalization.py:463: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs, training=training)
Constructing model
Forward pass called with `training` = None
Calling model in inference mode
Forward pass called with `training` = False
Moving average variables before training:  {'batch_norm_layer/batch_normalization/moving_mean:0': <tf.Tensor: shape=(5,), dtype=float32, numpy=array([0., 0., 0., 0., 0.], dtype=float32)>, 'batch_norm_layer/batch_normalization/moving_variance:0': <tf.Tensor: shape=(5,), dtype=float32, numpy=array([1., 1., 1., 1., 1.], dtype=float32)>}
calling model in training mode
Forward pass called with `training` = True
Moving average variables after training:  {'batch_norm_layer/batch_normalization/moving_mean:0': <tf.Tensor: shape=(5,), dtype=float32, numpy=
array([-0.00177554, -0.00036542, -0.00099426, -0.00112544,  0.0008541 ],
      dtype=float32)>, 'batch_norm_layer/batch_normalization/moving_variance:0': <tf.Tensor: shape=(5,), dtype=float32, numpy=
array([1.0005339, 1.0003369, 0.9976748, 1.0001523, 1.0009514],
      dtype=float32)>}

Réutilisation des variables basée sur la portée des variables

Toutes les créations de variable dans la passe avant basée sur get_variable conserveront le même nom de variable et réutiliseront la sémantique que les portées de variable ont dans TF1.x. Cela est vrai tant que vous avez au moins une portée externe non vide pour tous les tf.compat.v1.layers avec des noms générés automatiquement, comme mentionné ci-dessus.

Exécution impatiente et tf.function

Comme vu ci-dessus, les méthodes décorées pour tf.keras.layers.Layer et tf.Module s'exécutent à l'intérieur de l'exécution hâtive et sont également compatibles avec tf.function . Cela signifie que vous pouvez utiliser pdb et d'autres outils interactifs pour parcourir votre passe en avant pendant son exécution.

Stratégies de distribution

Les appels à get_variable à l'intérieur de @track_tf1_style_variables -couche décorée ou méthodes de module utilisent les créations de variables standard tf.Variable sous le capot. Cela signifie que vous pouvez les utiliser avec les différentes stratégies de distribution disponibles avec tf.distribute telles que MirroredStrategy et TPUStrategy .

Imbrication de tf.Variable s, tf.Module s, tf.keras.layers & tf.keras.models dans des appels décorés

Décorer votre appel de couche dans tf.compat.v1.keras.utils.track_tf1_style_variables n'ajoutera qu'un suivi implicite automatique des variables créées (et réutilisées) via tf.compat.v1.get_variable . Il ne capturera pas les poids directement créés par les appels tf.Variable , tels que ceux utilisés par les couches Keras typiques et la plupart tf.Module s. Cette section décrit comment gérer ces cas imbriqués.

(Utilisations préexistantes) tf.keras.layers et tf.keras.models

Pour les utilisations préexistantes des couches et modèles Keras imbriqués, utilisez tf.compat.v1.keras.utils.get_or_create_layer . Ceci n'est recommandé que pour faciliter la migration des utilisations Keras imbriquées TF1.x existantes ; le nouveau code doit utiliser un paramètre d'attribut explicite comme décrit ci-dessous pour tf.Variables et tf.Modules.

Pour utiliser tf.compat.v1.keras.utils.get_or_create_layer , encapsulez le code qui construit votre modèle imbriqué dans une méthode et transmettez-le à la méthode. Exemple:

class NestedModel(tf.keras.Model):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units

  def build_model(self):
    inp = tf.keras.Input(shape=(5, 5))
    dense_layer = tf.keras.layers.Dense(
        10, name="dense", kernel_regularizer="l2",
        kernel_initializer=tf.compat.v1.ones_initializer())
    model = tf.keras.Model(inputs=inp, outputs=dense_layer(inp))
    return model

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    # Get or create a nested model without assigning it as an explicit property
    model = tf.compat.v1.keras.utils.get_or_create_layer(
        "dense_model", self.build_model)
    return model(inputs)

layer = NestedModel(10)
layer(tf.ones(shape=(5,5)))
<tf.Tensor: shape=(5, 10), dtype=float32, numpy=
array([[5., 5., 5., 5., 5., 5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5., 5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5., 5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5., 5., 5., 5., 5., 5.],
       [5., 5., 5., 5., 5., 5., 5., 5., 5., 5.]], dtype=float32)>

Cette méthode garantit que ces couches imbriquées sont correctement réutilisées et suivies par tensorflow. Notez que le décorateur @track_tf1_style_variables est toujours requis sur la méthode appropriée. La méthode de création de modèle transmise à get_or_create_layer (dans ce cas, self.build_model ) ne doit prendre aucun argument.

Les poids sont suivis :

assert len(layer.weights) == 2
weights = {x.name: x for x in layer.variables}

assert set(weights.keys()) == {"dense/bias:0", "dense/kernel:0"}

layer.weights
[<tf.Variable 'dense/kernel:0' shape=(5, 10) dtype=float32, numpy=
 array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]], dtype=float32)>,
 <tf.Variable 'dense/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>]

Et la perte de régularisation aussi :

tf.add_n(layer.losses)
<tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.5], dtype=float32)>

Migration incrémentale : tf.Variables et tf.Modules

Si vous devez intégrer des appels tf.Variable ou tf.Module dans vos méthodes décorées (par exemple, si vous suivez la migration incrémentielle vers des API TF2 non héritées décrites plus loin dans ce guide), vous devez toujours les suivre explicitement, avec les exigences suivantes :

  • Assurez-vous explicitement que la variable/module/couche n'est créée qu'une seule fois
  • Attachez-les explicitement en tant qu'attributs d'instance comme vous le feriez lors de la définition d'un module ou d'une couche typique
  • Réutiliser explicitement l'objet déjà créé dans les appels de suivi

Cela garantit que les poids ne sont pas créés à chaque appel et sont correctement réutilisés. De plus, cela garantit également que les poids existants et les pertes de régularisation sont suivis.

Voici un exemple de ce à quoi cela pourrait ressembler :

class NestedLayer(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def __call__(self, inputs):
    out = inputs
    with tf.compat.v1.variable_scope("inner_dense"):
      # The weights are created with a `regularizer`,
      # so the layer should track their regularization losses
      kernel = tf.compat.v1.get_variable(
          shape=[out.shape[-1], self.units],
          regularizer=tf.keras.regularizers.L2(),
          initializer=tf.compat.v1.initializers.glorot_normal,
          name="kernel")
      bias = tf.compat.v1.get_variable(
          shape=[self.units,],
          initializer=tf.compat.v1.initializers.zeros,
          name="bias")
      out = tf.linalg.matmul(out, kernel)
      out = tf.compat.v1.nn.bias_add(out, bias)
    return out

class WrappedDenseLayer(tf.keras.layers.Layer):

  def __init__(self, units, **kwargs):
    super().__init__(**kwargs)
    self.units = units
    # Only create the nested tf.variable/module/layer/model
    # once, and then reuse it each time!
    self._dense_layer = NestedLayer(self.units)

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    with tf.compat.v1.variable_scope('outer'):
      outputs = tf.compat.v1.layers.dense(inputs, 3)
      outputs = tf.compat.v1.layers.dense(inputs, 4)
      return self._dense_layer(outputs)

layer = WrappedDenseLayer(10)

layer(tf.ones(shape=(5, 5)))
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:38: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:39: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
<tf.Tensor: shape=(5, 10), dtype=float32, numpy=
array([[-0.4987283 ,  0.06630042, -0.09875254,  0.20954818,  0.03599668,
         0.3980474 ,  0.11181635,  0.6891558 , -0.33903462,  0.15674731],
       [-0.4987283 ,  0.06630042, -0.09875254,  0.20954818,  0.03599668,
         0.3980474 ,  0.11181635,  0.6891558 , -0.33903462,  0.15674731],
       [-0.4987283 ,  0.06630042, -0.09875254,  0.20954818,  0.03599668,
         0.3980474 ,  0.11181635,  0.6891558 , -0.33903462,  0.15674731],
       [-0.4987283 ,  0.06630042, -0.09875254,  0.20954818,  0.03599668,
         0.3980474 ,  0.11181635,  0.6891558 , -0.33903462,  0.15674731],
       [-0.4987283 ,  0.06630042, -0.09875254,  0.20954818,  0.03599668,
         0.3980474 ,  0.11181635,  0.6891558 , -0.33903462,  0.15674731]],
      dtype=float32)>

Notez qu'un suivi explicite du module imbriqué est nécessaire même s'il est décoré avec le décorateur track_tf1_style_variables . En effet, chaque module/couche avec des méthodes décorées a son propre magasin de variables qui lui est associé.

Les poids sont correctement suivis :

assert len(layer.weights) == 6
weights = {x.name: x for x in layer.variables}

assert set(weights.keys()) == {"outer/inner_dense/bias:0",
                               "outer/inner_dense/kernel:0",
                               "outer/dense/bias:0",
                               "outer/dense/kernel:0",
                               "outer/dense_1/bias:0",
                               "outer/dense_1/kernel:0"}

layer.trainable_weights
[<tf.Variable 'outer/inner_dense/bias:0' shape=(10,) dtype=float32, numpy=array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], dtype=float32)>,
 <tf.Variable 'outer/inner_dense/kernel:0' shape=(4, 10) dtype=float32, numpy=
 array([[-0.20786692,  0.14702448, -0.2577947 ,  0.1885891 ,  0.28935957,
          0.02086618, -0.20579144, -0.7509229 , -0.23490003,  0.00370591],
        [ 0.09247629, -0.37428686, -0.6002815 , -0.2702465 ,  0.20350575,
          0.34964404, -0.32633537,  0.50722903, -0.0419833 , -0.61815673],
        [ 0.24821116,  0.15504731, -0.12409697, -0.2506969 ,  0.22316858,
         -0.44847375, -0.08295754, -0.8262154 ,  0.7674222 , -0.40613693],
        [-0.7447006 ,  0.2992331 , -0.45639235,  0.0669547 ,  0.39443025,
          0.3182467 ,  0.10884362,  0.5395837 ,  0.32210502, -0.30076835]],
       dtype=float32)>,
 <tf.Variable 'outer/dense/bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>,
 <tf.Variable 'outer/dense/kernel:0' shape=(5, 3) dtype=float32, numpy=
 array([[ 0.6283595 , -0.80413634, -0.5471641 ],
        [ 0.25296038, -0.7657203 ,  0.5884425 ],
        [-0.7180575 , -0.29509914,  0.44014376],
        [ 0.81024987,  0.39888996,  0.80002993],
        [-0.32921118, -0.7010279 ,  0.820375  ]], dtype=float32)>,
 <tf.Variable 'outer/dense_1/bias:0' shape=(4,) dtype=float32, numpy=array([0., 0., 0., 0.], dtype=float32)>,
 <tf.Variable 'outer/dense_1/kernel:0' shape=(5, 4) dtype=float32, numpy=
 array([[ 0.7941524 , -0.58552563,  0.46828055, -0.44095916],
        [-0.16019303,  0.27973688, -0.60373306, -0.20117629],
        [ 0.6345844 ,  0.30732214,  0.18921828,  0.37930095],
        [-0.50815696, -0.2471816 , -0.10282421,  0.21441567],
        [-0.71987414,  0.18304104, -0.5701992 ,  0.4926386 ]],
       dtype=float32)>]

Ainsi que la perte de régularisation :

layer.losses
[<tf.Tensor: shape=(), dtype=float32, numpy=0.058749676>]

Notez que si le NestedLayer était un tf.Module non-Keras à la place, les variables seraient toujours suivies mais les pertes de régularisation ne seraient pas automatiquement suivies, vous devriez donc les suivre explicitement séparément.

Conseils sur les noms de variables

Les appels explicites tf.Variable et les couches Keras utilisent un mécanisme de génération automatique de nom de couche/nom de variable différent de celui auquel vous pourriez être habitué à partir de la combinaison de get_variable et variable_scopes . Bien que le shim fasse correspondre vos noms de variables aux variables créées par get_variable même lorsque vous passez des graphiques TF1.x à l'exécution impatiente TF2 & tf.function , il ne peut pas garantir la même chose pour les noms de variables générés pour les appels tf.Variable et les couches Keras qui vous intégrez dans vos décorateurs de méthode. Il est même possible que plusieurs variables partagent le même nom dans l'exécution hâtive de TF2 et tf.function .

Vous devez faire particulièrement attention à cela lorsque vous suivez les sections sur la validation de l'exactitude et le mappage des points de contrôle TF1.x plus loin dans ce guide.

Utilisation de tf.compat.v1.make_template dans la méthode décorée

Il est fortement recommandé d'utiliser directement tf.compat.v1.keras.utils.track_tf1_style_variables au lieu d'utiliser tf.compat.v1.make_template , car il s'agit d'une couche plus fine au-dessus de TF2 .

Suivez les instructions de cette section pour le code TF1.x antérieur qui s'appuyait déjà sur tf.compat.v1.make_template .

Étant donné que tf.compat.v1.make_template enveloppe le code qui utilise get_variable , le décorateur track_tf1_style_variables vous permet d'utiliser ces modèles dans les appels de couche et de suivre avec succès les poids et les pertes de régularisation.

Cependant, assurez-vous d'appeler make_template une seule fois, puis de réutiliser le même modèle dans chaque appel de couche. Sinon, un nouveau modèle sera créé chaque fois que vous appelez la couche avec un nouvel ensemble de variables.

Par example,

class CompatV1TemplateScaleByY(tf.keras.layers.Layer):

  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    def my_op(x, scalar_name):
      var1 = tf.compat.v1.get_variable(scalar_name,
                            shape=[],
                            regularizer=tf.compat.v1.keras.regularizers.L2(),
                            initializer=tf.compat.v1.constant_initializer(1.5))
      return x * var1
    self.scale_by_y = tf.compat.v1.make_template('scale_by_y', my_op, scalar_name='y')

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    with tf.compat.v1.variable_scope('layer'):
      # Using a scope ensures the `scale_by_y` name will not be incremented
      # for each instantiation of the layer.
      return self.scale_by_y(inputs)

layer = CompatV1TemplateScaleByY()

out = layer(tf.ones(shape=(2, 3)))
print("weights:", layer.weights)
print("regularization loss:", layer.losses)
print("output:", out)
weights: [<tf.Variable 'layer/scale_by_y/y:0' shape=() dtype=float32, numpy=1.5>]
regularization loss: [<tf.Tensor: shape=(), dtype=float32, numpy=0.022499999>]
output: tf.Tensor(
[[1.5 1.5 1.5]
 [1.5 1.5 1.5]], shape=(2, 3), dtype=float32)

Migration incrémentale vers TF2 Natif

Comme mentionné précédemment, track_tf1_style_variables vous permet de mélanger l'utilisation de tf.Variable / tf.keras.layers.Layer / tf.Module orientée objet de style TF2 avec l'ancien style tf.compat.v1.get_variable / tf.compat.v1.layers utilisation à l'intérieur du même module/couche décoré.

Cela signifie qu'après avoir rendu votre modèle TF1.x entièrement compatible avec TF2, vous pouvez écrire tous les nouveaux composants de modèle avec des API TF2 natives (non tf.compat.v1 ) et les faire interagir avec votre ancien code.

Cependant, si vous continuez à modifier vos anciens composants de modèle, vous pouvez également choisir de basculer progressivement votre utilisation de tf.compat.v1 de style hérité vers les API orientées objet purement natives qui sont recommandées pour le code TF2 nouvellement écrit.

L'utilisation de tf.compat.v1.get_variable peut être remplacée par des appels self.add_weight si vous décorez une couche/un modèle Keras, ou par des appels tf.Variable si vous décorez des objets Keras ou tf.Module s.

Les couches tf.compat.v1.layers de style fonctionnel et orientées objet peuvent généralement être remplacées par la couche tf.keras.layers équivalente sans qu'aucune modification d'argument ne soit nécessaire.

Vous pouvez également considérer des morceaux de votre modèle ou des modèles communs dans des couches/modules individuels lors de votre migration incrémentielle vers des API purement natives, qui peuvent elles-mêmes utiliser track_tf1_style_variables .

Une note sur Slim et contrib.layers

Une grande partie du code TF 1.x plus ancien utilise la bibliothèque Slim , qui était fournie avec TF 1.x en tant que tf.contrib.layers . La conversion de code à l'aide de Slim en TF 2 natif est plus compliquée que la conversion de v1.layers . En fait, il peut être judicieux de convertir d'abord votre code Slim en v1.layers , puis de le convertir en Keras. Vous trouverez ci-dessous quelques conseils généraux pour convertir le code Slim.

  • Assurez-vous que tous les arguments sont explicites. Supprimez arg_scopes si possible. Si vous avez encore besoin de les utiliser, divisez normalizer_fn et activation_fn dans leurs propres couches.
  • Les couches de conversion séparables correspondent à une ou plusieurs couches Keras différentes (couches Keras en profondeur, ponctuelles et séparables).
  • Slim et v1.layers ont des noms d'arguments et des valeurs par défaut différents.
  • Notez que certains arguments ont des échelles différentes.

Migration vers TF2 natif sans tenir compte de la compatibilité des points de contrôle

L'exemple de code suivant illustre un déplacement incrémentiel d'un modèle vers des API purement natives sans tenir compte de la compatibilité des points de contrôle.

class CompatModel(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    with tf.compat.v1.variable_scope('model'):
      out = tf.compat.v1.layers.conv2d(
          inputs, 3, 3,
          kernel_regularizer="l2")
      out = tf.compat.v1.layers.flatten(out)
      out = tf.compat.v1.layers.dropout(out, training=training)
      out = tf.compat.v1.layers.dense(
          out, self.units,
          kernel_regularizer="l2")
      return out

Ensuite, remplacez les API compat.v1 par leurs équivalents orientés objet natifs de manière fragmentaire. Commencez par basculer le calque de convolution vers un objet Keras créé dans le constructeur de calque.

class PartiallyMigratedModel(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units
    self.conv_layer = tf.keras.layers.Conv2D(
      3, 3,
      kernel_regularizer="l2")

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    with tf.compat.v1.variable_scope('model'):
      out = self.conv_layer(inputs)
      out = tf.compat.v1.layers.flatten(out)
      out = tf.compat.v1.layers.dropout(out, training=training)
      out = tf.compat.v1.layers.dense(
          out, self.units,
          kernel_regularizer="l2")
      return out

Utilisez la classe v1.keras.utils.DeterministicRandomTestTool pour vérifier que cette modification incrémentielle laisse le modèle avec le même comportement qu'avant.

random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
  layer = CompatModel(10)

  inputs = tf.random.normal(shape=(10, 5, 5, 5))
  original_output = layer(inputs)

  # Grab the regularization loss as well
  original_regularization_loss = tf.math.add_n(layer.losses)

print(original_regularization_loss)
tf.Tensor(0.17953834, shape=(), dtype=float32)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:12: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
  if sys.path[0] == '':
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:13: UserWarning: `tf.layers.flatten` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Flatten` instead.
  del sys.path[0]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: `tf.layers.dropout` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dropout` instead.
  
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/core.py:413: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs, training=training)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:17: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
  layer = PartiallyMigratedModel(10)

  inputs = tf.random.normal(shape=(10, 5, 5, 5))
  migrated_output = layer(inputs)

  # Grab the regularization loss as well
  migrated_regularization_loss = tf.math.add_n(layer.losses)

print(migrated_regularization_loss)
tf.Tensor(0.17953834, shape=(), dtype=float32)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: `tf.layers.flatten` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Flatten` instead.
  
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: `tf.layers.dropout` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dropout` instead.
  from ipykernel import kernelapp as app
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:18: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
# Verify that the regularization loss and output both match
np.testing.assert_allclose(original_regularization_loss.numpy(), migrated_regularization_loss.numpy())
np.testing.assert_allclose(original_output.numpy(), migrated_output.numpy())

Vous avez maintenant remplacé toutes les compat.v1.layers individuelles par des couches Keras natives.

class NearlyFullyNativeModel(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units
    self.conv_layer = tf.keras.layers.Conv2D(
      3, 3,
      kernel_regularizer="l2")
    self.flatten_layer = tf.keras.layers.Flatten()
    self.dense_layer = tf.keras.layers.Dense(
      self.units,
      kernel_regularizer="l2")

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    with tf.compat.v1.variable_scope('model'):
      out = self.conv_layer(inputs)
      out = self.flatten_layer(out)
      out = self.dense_layer(out)
      return out
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
  layer = NearlyFullyNativeModel(10)

  inputs = tf.random.normal(shape=(10, 5, 5, 5))
  migrated_output = layer(inputs)

  # Grab the regularization loss as well
  migrated_regularization_loss = tf.math.add_n(layer.losses)

print(migrated_regularization_loss)
tf.Tensor(0.17953834, shape=(), dtype=float32)
# Verify that the regularization loss and output both match
np.testing.assert_allclose(original_regularization_loss.numpy(), migrated_regularization_loss.numpy())
np.testing.assert_allclose(original_output.numpy(), migrated_output.numpy())

Enfin, supprimez à la fois toute utilisation variable_scope restante (plus nécessaire) et le décorateur track_tf1_style_variables lui-même.

Vous vous retrouvez maintenant avec une version du modèle qui utilise des API entièrement natives.

class FullyNativeModel(tf.keras.layers.Layer):

  def __init__(self, units, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.units = units
    self.conv_layer = tf.keras.layers.Conv2D(
      3, 3,
      kernel_regularizer="l2")
    self.flatten_layer = tf.keras.layers.Flatten()
    self.dense_layer = tf.keras.layers.Dense(
      self.units,
      kernel_regularizer="l2")

  def call(self, inputs):
    out = self.conv_layer(inputs)
    out = self.flatten_layer(out)
    out = self.dense_layer(out)
    return out
random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
  layer = FullyNativeModel(10)

  inputs = tf.random.normal(shape=(10, 5, 5, 5))
  migrated_output = layer(inputs)

  # Grab the regularization loss as well
  migrated_regularization_loss = tf.math.add_n(layer.losses)

print(migrated_regularization_loss)
tf.Tensor(0.17953834, shape=(), dtype=float32)
# Verify that the regularization loss and output both match
np.testing.assert_allclose(original_regularization_loss.numpy(), migrated_regularization_loss.numpy())
np.testing.assert_allclose(original_output.numpy(), migrated_output.numpy())

Maintenir la compatibilité des points de contrôle lors de la migration vers Native TF2

Le processus de migration ci-dessus vers les API TF2 natives a modifié à la fois les noms de variables (car les API Keras produisent des noms de poids très différents) et les chemins orientés objet qui pointent vers différents poids dans le modèle. L'impact de ces changements est qu'ils auront cassé à la fois tous les points de contrôle basés sur le nom de style TF1 existants ou les points de contrôle orientés objet de style TF2.

Cependant, dans certains cas, vous pourrez peut-être prendre votre point de contrôle basé sur le nom d'origine et trouver un mappage des variables à leurs nouveaux noms avec des approches comme celle détaillée dans le guide Réutilisation des points de contrôle TF1.x .

Voici quelques conseils pour rendre cela réalisable :

  • Les variables ont toujours un argument de name que vous pouvez définir.
  • Les modèles Keras prennent également un argument de name qu'ils définissent comme préfixe pour leurs variables.
  • La fonction v1.name_scope peut être utilisée pour définir des préfixes de nom de variable. Ceci est très différent de tf.variable_scope . Il n'affecte que les noms et ne suit pas les variables et la réutilisation.

Avec les pointeurs ci-dessus à l'esprit, les exemples de code suivants illustrent un flux de travail que vous pouvez adapter à votre code pour mettre à jour de manière incrémentielle une partie d'un modèle tout en mettant à jour simultanément les points de contrôle.

  1. Commencez par basculer les tf.compat.v1.layers de style fonctionnel vers leurs versions orientées objet.
class FunctionalStyleCompatModel(tf.keras.layers.Layer):

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    with tf.compat.v1.variable_scope('model'):
      out = tf.compat.v1.layers.conv2d(
          inputs, 3, 3,
          kernel_regularizer="l2")
      out = tf.compat.v1.layers.conv2d(
          out, 4, 4,
          kernel_regularizer="l2")
      out = tf.compat.v1.layers.conv2d(
          out, 5, 5,
          kernel_regularizer="l2")
      return out

layer = FunctionalStyleCompatModel()
layer(tf.ones(shape=(10, 10, 10, 10)))
[v.name for v in layer.weights]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:8: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
  
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:11: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
  # This is added back by InteractiveShellApp.init_path()
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
['model/conv2d/bias:0',
 'model/conv2d/kernel:0',
 'model/conv2d_1/bias:0',
 'model/conv2d_1/kernel:0',
 'model/conv2d_2/bias:0',
 'model/conv2d_2/kernel:0']
  1. Ensuite, affectez les objets compat.v1.layer et toutes les variables créées par compat.v1.get_variable en tant que propriétés de l'objet tf.keras.layers.Layer / tf.Module dont la méthode est décorée avec track_tf1_style_variables (notez que tout TF2 orienté objet les points de contrôle de style enregistreront désormais à la fois un chemin par nom de variable et le nouveau chemin orienté objet).
class OOStyleCompatModel(tf.keras.layers.Layer):

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.conv_1 = tf.compat.v1.layers.Conv2D(
          3, 3,
          kernel_regularizer="l2")
    self.conv_2 = tf.compat.v1.layers.Conv2D(
          4, 4,
          kernel_regularizer="l2")

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    with tf.compat.v1.variable_scope('model'):
      out = self.conv_1(inputs)
      out = self.conv_2(out)
      out = tf.compat.v1.layers.conv2d(
          out, 5, 5,
          kernel_regularizer="l2")
      return out

layer = OOStyleCompatModel()
layer(tf.ones(shape=(10, 10, 10, 10)))
[v.name for v in layer.weights]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:19: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
['model/conv2d/kernel:0',
 'model/conv2d/bias:0',
 'model/conv2d_1/kernel:0',
 'model/conv2d_1/bias:0',
 'model/conv2d_2/bias:0',
 'model/conv2d_2/kernel:0']
  1. Réenregistrez un point de contrôle chargé à ce stade pour enregistrer les chemins à la fois par le nom de la variable (pour compat.v1.layers) ou par le graphe d'objets orienté objet.
weights = {v.name: v for v in layer.weights}
assert weights['model/conv2d/kernel:0'] is layer.conv_1.kernel
assert weights['model/conv2d_1/bias:0'] is layer.conv_2.bias
  1. Vous pouvez maintenant échanger les couches compat.v1.layers objet pour les couches Keras natives tout en étant toujours en mesure de charger le point de contrôle récemment enregistré. Assurez-vous de conserver les noms de variable pour les compat.v1.layers restantes en enregistrant toujours les variable_scopes générés automatiquement des couches remplacées. Ces couches/variables commutées n'utiliseront désormais que le chemin de l'attribut d'objet vers les variables du point de contrôle au lieu du chemin du nom de la variable.

En général, vous pouvez remplacer l'utilisation de compat.v1.get_variable dans les variables attachées aux propriétés par :

  • Les basculer vers l'utilisation de tf.Variable , OU
  • Les mettre à jour en utilisant tf.keras.layers.Layer.add_weight . Notez que si vous ne changez pas toutes les couches en une seule fois, cela peut modifier la dénomination de couche/variable générée automatiquement pour les compat.v1.layers restantes auxquelles il manque un argument de name . Si tel est le cas, vous devez conserver les mêmes noms de variables pour les compat.v1.layers restantes en ouvrant et en fermant manuellement un variable_scope correspondant au nom de portée généré par compat.v1.layer supprimé. Sinon, les chemins des points de contrôle existants peuvent entrer en conflit et le chargement des points de contrôle se comportera de manière incorrecte.
def record_scope(scope_name):
  """Record a variable_scope to make sure future ones get incremented."""
  with tf.compat.v1.variable_scope(scope_name):
    pass

class PartiallyNativeKerasLayersModel(tf.keras.layers.Layer):

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.conv_1 = tf.keras.layers.Conv2D(
          3, 3,
          kernel_regularizer="l2")
    self.conv_2 = tf.keras.layers.Conv2D(
          4, 4,
          kernel_regularizer="l2")

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    with tf.compat.v1.variable_scope('model'):
      out = self.conv_1(inputs)
      record_scope('conv2d') # Only needed if follow-on compat.v1.layers do not pass a `name` arg
      out = self.conv_2(out)
      record_scope('conv2d_1') # Only needed if follow-on compat.v1.layers do not pass a `name` arg
      out = tf.compat.v1.layers.conv2d(
          out, 5, 5,
          kernel_regularizer="l2")
      return out

layer = PartiallyNativeKerasLayersModel()
layer(tf.ones(shape=(10, 10, 10, 10)))
[v.name for v in layer.weights]
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:26: UserWarning: `tf.layers.conv2d` is deprecated and will be removed in a future version. Please Use `tf.keras.layers.Conv2D` instead.
['partially_native_keras_layers_model/model/conv2d_13/kernel:0',
 'partially_native_keras_layers_model/model/conv2d_13/bias:0',
 'partially_native_keras_layers_model/model/conv2d_14/kernel:0',
 'partially_native_keras_layers_model/model/conv2d_14/bias:0',
 'model/conv2d_2/bias:0',
 'model/conv2d_2/kernel:0']

L'enregistrement d'un point de contrôle à cette étape après la construction des variables fera qu'il ne contiendra que les chemins d'objet actuellement disponibles.

Assurez-vous d'enregistrer les étendues des compat.v1.layers supprimées afin de conserver les noms de pondération générés automatiquement pour les compat.v1.layers restantes.

weights = set(v.name for v in layer.weights)
assert 'model/conv2d_2/kernel:0' in weights
assert 'model/conv2d_2/bias:0' in weights
  1. Répétez les étapes ci-dessus jusqu'à ce que vous ayez remplacé tous les compat.v1.layers et compat.v1.get_variable de votre modèle par des équivalents entièrement natifs.
class FullyNativeKerasLayersModel(tf.keras.layers.Layer):

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.conv_1 = tf.keras.layers.Conv2D(
          3, 3,
          kernel_regularizer="l2")
    self.conv_2 = tf.keras.layers.Conv2D(
          4, 4,
          kernel_regularizer="l2")
    self.conv_3 = tf.keras.layers.Conv2D(
          5, 5,
          kernel_regularizer="l2")


  def call(self, inputs, training=None):
    with tf.compat.v1.variable_scope('model'):
      out = self.conv_1(inputs)
      out = self.conv_2(out)
      out = self.conv_3(out)
      return out

layer = FullyNativeKerasLayersModel()
layer(tf.ones(shape=(10, 10, 10, 10)))
[v.name for v in layer.weights]
['fully_native_keras_layers_model/model/conv2d_16/kernel:0',
 'fully_native_keras_layers_model/model/conv2d_16/bias:0',
 'fully_native_keras_layers_model/model/conv2d_17/kernel:0',
 'fully_native_keras_layers_model/model/conv2d_17/bias:0',
 'fully_native_keras_layers_model/model/conv2d_18/kernel:0',
 'fully_native_keras_layers_model/model/conv2d_18/bias:0']

N'oubliez pas de tester pour vous assurer que le point de contrôle récemment mis à jour se comporte toujours comme prévu. Appliquez les techniques décrites dans le guide de validation de l'exactitude numérique à chaque étape incrémentielle de ce processus pour vous assurer que votre code migré s'exécute correctement.

Gestion des changements de comportement de TF1.x à TF2 non couverts par les shims de modélisation

Les cales de modélisation décrites dans ce guide peuvent garantir que les variables, les couches et les pertes de régularisation créées avec get_variable , tf.compat.v1.layers et variable_scope continuent de fonctionner comme avant lors de l'utilisation de l'exécution rapide et tf.function , sans avoir à s'appuyer sur les collections.

Cela ne couvre pas toutes les sémantiques spécifiques à TF1.x sur lesquelles vos passes avant de modèle peuvent s'appuyer. Dans certains cas, les cales peuvent être insuffisantes pour que votre modèle de passe avant fonctionne seul dans TF2. Lisez le guide des comportements TF1.x vs TF2 pour en savoir plus sur les différences de comportement entre TF1.x et TF2.