Ayuda a proteger la Gran Barrera de Coral con TensorFlow en Kaggle Únete Challenge

Validación de la corrección y la equivalencia numérica

Ver en TensorFlow.org Ejecutar en Google Colab Ver en GitHub Descargar cuaderno

Al migrar su código de TensorFlow de TF1.xa TF2, es una buena práctica asegurarse de que su código migrado se comporte de la misma manera en TF2 que en TF1.x.

Esta migración cubiertas de guía ejemplos de código con los tf.compat.v1.keras.utils.track_tf1_style_variables modelado shim aplican a tf.keras.layers.Layer métodos. Lea la guía del modelo de asignación para averiguar más sobre el modelado de TF2 cuñas.

Esta guía detalla los enfoques que puede utilizar para:

  • Validar la exactitud de los resultados obtenidos de los modelos de entrenamiento utilizando el código migrado
  • Validar la equivalencia numérica de su código en las versiones de TensorFlow

Configuración

# Note that the model mapping shim is available only in TF 2.7.
pip install -q tf_slim
import tensorflow as tf
import tensorflow.compat.v1 as v1

import numpy as np
import tf_slim as slim
import sys

from unittest import mock

from contextlib import contextmanager
!git clone --depth=1 https://github.com/tensorflow/models.git
import models.research.slim.nets.inception_resnet_v2 as inception
Cloning into 'models'...
remote: Enumerating objects: 3081, done.[K
remote: Counting objects: 100% (3081/3081), done.[K
remote: Compressing objects: 100% (2607/2607), done.[K
remote: Total 3081 (delta 774), reused 1338 (delta 433), pack-reused 0[K
Receiving objects: 100% (3081/3081), 33.33 MiB | 19.59 MiB/s, done.
Resolving deltas: 100% (774/774), done.

Si está colocando un fragmento no trivial de código de acceso directo en la corrección, querrá saber que se está comportando de la misma manera que en TF1.x. Por ejemplo, considere intentar poner un modelo TF-Slim Inception-Resnet-v2 completo en la cuña como tal:

# TF1 Inception resnet v2 forward pass based on slim layers
def inception_resnet_v2(inputs, num_classes, is_training):
  with slim.arg_scope(
    inception.inception_resnet_v2_arg_scope(batch_norm_scale=True)):
    return inception.inception_resnet_v2(inputs, num_classes, is_training=is_training)
class InceptionResnetV2(tf.keras.layers.Layer):
  """Slim InceptionResnetV2 forward pass as a Keras layer"""

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

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs, training=None):
    is_training = training or False 

    # Slim does not accept `None` as a value for is_training,
    # Keras will still pass `None` to layers to construct functional models
    # without forcing the layer to always be in training or in inference.
    # However, `None` is generally considered to run layers in inference.

    with slim.arg_scope(
        inception.inception_resnet_v2_arg_scope(batch_norm_scale=True)):
      return inception.inception_resnet_v2(
          inputs, self.num_classes, is_training=is_training)
WARNING:tensorflow:From /tmp/ipykernel_12277/2131234657.py:8: The name tf.keras.utils.track_tf1_style_variables is deprecated. Please use tf.compat.v1.keras.utils.track_tf1_style_variables instead.

Da la casualidad de que esta capa funciona perfectamente bien desde el primer momento (completa con un seguimiento preciso de la pérdida de regularización).

Sin embargo, esto no es algo que quieras dar por sentado. Siga los pasos a continuación para verificar que realmente se está comportando como lo hizo en TF1.x, hasta observar la equivalencia numérica perfecta. Estos pasos también pueden ayudarlo a triangular qué parte del pase hacia adelante está causando una divergencia de TF1.x (identifique si la divergencia surge en el pase hacia adelante del modelo en contraposición a una parte diferente del modelo).

Paso 1: Verifique que las variables solo se creen una vez

Lo primero que debe verificar es que ha construido correctamente el modelo de una manera que reutiliza las variables en cada llamada en lugar de crear y usar accidentalmente nuevas variables cada vez. Por ejemplo, si su modelo crea una nueva capa Keras o llama tf.Variable en cada llamada pase hacia adelante, entonces es muy probable que en su defecto a las variables de captura y la creación de otras nuevas cada vez.

A continuación, se muestran dos ámbitos del administrador de contexto que puede usar para detectar cuándo su modelo está creando nuevas variables y depurar qué parte del modelo lo está haciendo.

@contextmanager
def assert_no_variable_creations():
  """Assert no variables are created in this context manager scope."""
  def invalid_variable_creator(next_creator, **kwargs):
    raise ValueError("Attempted to create a new variable instead of reusing an existing one. Args: {}".format(kwargs))

  with tf.variable_creator_scope(invalid_variable_creator):
    yield

@contextmanager
def catch_and_raise_created_variables():
  """Raise all variables created within this context manager scope (if any)."""
  created_vars = []
  def variable_catcher(next_creator, **kwargs):
    var = next_creator(**kwargs)
    created_vars.append(var)
    return var

  with tf.variable_creator_scope(variable_catcher):
    yield
  if created_vars:
    raise ValueError("Created vars:", created_vars)

El primer campo de aplicación ( assert_no_variable_creations() ) generará un error de inmediato una vez que intente crear una variable dentro del alcance. Esto le permite inspeccionar el seguimiento de la pila (y usar la depuración interactiva) para averiguar exactamente qué líneas de código crearon una variable en lugar de reutilizar una existente.

El segundo campo de aplicación ( catch_and_raise_created_variables() ) provocará una excepción al final del alcance si se crean las variables ido a parar. Esta excepción incluirá la lista de todas las variables creadas en el alcance. Esto es útil para averiguar cuál es el conjunto de todos los pesos que está creando su modelo en caso de que pueda detectar patrones generales. Sin embargo, es menos útil para identificar las líneas exactas de código donde se crearon esas variables.

Use ambos ámbitos a continuación para verificar que la capa InceptionResnetV2 basada en shim no crea nuevas variables después de la primera llamada (presumiblemente reutilizándolas).

model = InceptionResnetV2(1000)
height, width = 299, 299
num_classes = 1000

inputs = tf.ones( (1, height, width, 3))
# Create all weights on the first call
model(inputs)

# Verify that no new weights are created in followup calls
with assert_no_variable_creations():
  model(inputs)
with catch_and_raise_created_variables():
  model(inputs)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/engine/base_layer.py:2212: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  warnings.warn('`layer.apply` is deprecated and '
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/legacy_tf_layers/core.py:336: UserWarning: `tf.layers.flatten` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Flatten` instead.
  warnings.warn('`tf.layers.flatten` is deprecated and '

En el siguiente ejemplo, observe cómo estos decoradores trabajan en una capa que crea incorrectamente nuevos pesos cada vez en lugar de reutilizar los existentes.

class BrokenScalingLayer(tf.keras.layers.Layer):
  """Scaling layer that incorrectly creates new weights each time:"""

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    var = tf.Variable(initial_value=2.0)
    bias = tf.Variable(initial_value=2.0, name='bias')
    return inputs * var + bias
model = BrokenScalingLayer()
inputs = tf.ones( (1, height, width, 3))
model(inputs)

try:
  with assert_no_variable_creations():
    model(inputs)
except ValueError as err:
  import traceback
  traceback.print_exc()
Traceback (most recent call last):
  File "/tmp/ipykernel_12277/1128777590.py", line 7, in <module>
    model(inputs)
  File "/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/utils/traceback_utils.py", line 67, in error_handler
    raise e.with_traceback(filtered_tb) from None
  File "/tmp/ipykernel_12277/3224979076.py", line 6, in call
    var = tf.Variable(initial_value=2.0)
  File "/tmp/ipykernel_12277/1829430118.py", line 5, in invalid_variable_creator
    raise ValueError("Attempted to create a new variable instead of reusing an existing one. Args: {}".format(kwargs))
ValueError: Exception encountered when calling layer "broken_scaling_layer" (type BrokenScalingLayer).

Attempted to create a new variable instead of reusing an existing one. Args: {'initial_value': 2.0, 'trainable': None, 'validate_shape': True, 'caching_device': None, 'name': None, 'variable_def': None, 'dtype': None, 'import_scope': None, 'constraint': None, 'synchronization': <VariableSynchronization.AUTO: 0>, 'aggregation': <VariableAggregation.NONE: 0>, 'shape': None}

Call arguments received:
  • inputs=tf.Tensor(shape=(1, 299, 299, 3), dtype=float32)
model = BrokenScalingLayer()
inputs = tf.ones( (1, height, width, 3))
model(inputs)

try:
  with catch_and_raise_created_variables():
    model(inputs)
except ValueError as err:
  print(err)
('Created vars:', [<tf.Variable 'broken_scaling_layer_1/Variable:0' shape=() dtype=float32, numpy=2.0>, <tf.Variable 'broken_scaling_layer_1/bias:0' shape=() dtype=float32, numpy=2.0>])

Puede arreglar la capa asegurándose de que solo cree los pesos una vez y luego los reutilice cada vez.

class FixedScalingLayer(tf.keras.layers.Layer):
  """Scaling layer that incorrectly creates new weights each time:"""
  def __init__(self):
    super().__init__()
    self.var = None
    self.bias = None

  @tf.compat.v1.keras.utils.track_tf1_style_variables
  def call(self, inputs):
    if self.var is None:
      self.var = tf.Variable(initial_value=2.0)
      self.bias = tf.Variable(initial_value=2.0, name='bias')
    return inputs * self.var + self.bias

model = FixedScalingLayer()
inputs = tf.ones( (1, height, width, 3))
model(inputs)

with assert_no_variable_creations():
  model(inputs)
with catch_and_raise_created_variables():
  model(inputs)

Solución de problemas

A continuación, se muestran algunas razones comunes por las que su modelo podría crear accidentalmente nuevos pesos en lugar de reutilizar los existentes:

  1. Utiliza una explícita tf.Variable llamada sin reutilizar ya creados tf.Variables . Solucione esto verificando primero si no se ha creado y luego reutilizando los existentes.
  2. Se crea una capa Keras o modelo directamente en el pase cada vez (en contraposición a tf.compat.v1.layers ). Solucione esto verificando primero si no se ha creado y luego reutilizando los existentes.
  3. Se construye en la parte superior de tf.compat.v1.layers pero no asigna todos compat.v1.layers un nombre explícito o para envolver su compat.v1.layer dentro de el uso de un llamado variable_scope , haciendo que los nombres de las capas autogenerados al incremento de cada modelo de llamada. Esto se soluciona poniendo un llamado tf.compat.v1.variable_scope dentro de su método de Shim-decorada que envuelve toda su tf.compat.v1.layers uso.

Paso 2: Verifique que los recuentos, los nombres y las formas de las variables coincidan

El segundo paso es asegurarse de que la capa que se ejecuta en TF2 cree la misma cantidad de pesos, con las mismas formas, que el código correspondiente en TF1.x.

Puede hacer una combinación de verificarlos manualmente para ver si coinciden y realizar las verificaciones de manera programática en una prueba unitaria como se muestra a continuación.

# Build the forward pass inside a TF1.x graph, and 
# get the counts, shapes, and names of the variables
graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
  height, width = 299, 299
  num_classes = 1000
  inputs = tf.ones( (1, height, width, 3))

  out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=False)

  tf1_variable_names_and_shapes = {
      var.name: (var.trainable, var.shape) for var in tf.compat.v1.global_variables()}
  num_tf1_variables = len(tf.compat.v1.global_variables())
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/engine/base_layer_v1.py:1694: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  warnings.warn('`layer.apply` is deprecated and '

A continuación, haga lo mismo con la capa envuelta en calzas en TF2. Observe que el modelo también se llama varias veces antes de tomar los pesos. Esto se hace para probar eficazmente la reutilización de variables.

height, width = 299, 299
num_classes = 1000

model = InceptionResnetV2(num_classes)
# The weights will not be created until you call the model

inputs = tf.ones( (1, height, width, 3))
# Call the model multiple times before checking the weights, to verify variables
# get reused rather than accidentally creating additional variables
out, endpoints = model(inputs, training=False)
out, endpoints = model(inputs, training=False)

# Grab the name: shape mapping and the total number of variables separately,
# because in TF2 variables can be created with the same name
num_tf2_variables = len(model.variables)
tf2_variable_names_and_shapes = {
    var.name: (var.trainable, var.shape) for var in model.variables}
2021-11-13 02:33:12.818082: 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.
# Verify that the variable counts, names, and shapes all match:
assert num_tf1_variables == num_tf2_variables
assert tf1_variable_names_and_shapes == tf2_variable_names_and_shapes

La capa InceptionResnetV2 basada en shim pasa esta prueba. Sin embargo, en el caso de que no coincidan, puede ejecutarlo a través de un diff (texto u otro) para ver dónde están las diferencias.

Esto puede proporcionar una pista sobre qué parte del modelo no se comporta como se esperaba. Con una ejecución ávida, puede usar pdb, depuración interactiva y puntos de interrupción para profundizar en las partes del modelo que parecen sospechosas y depurar lo que está fallando con mayor profundidad.

Solución de problemas

  • Prestar mucha atención a los nombres de las variables creadas directamente por explícitas tf.Variable llamadas y capas Keras / modelos como su semántica generación de nombres de variables pueden diferir ligeramente entre los gráficos y la funcionalidad TF1.x TF2 como la ejecución ansiosos y tf.function incluso si todo else está funcionando correctamente. Si este es su caso, ajuste su prueba para tener en cuenta cualquier semántica de nomenclatura ligeramente diferente.

  • A veces se puede encontrar que el tf.Variable s, tf.keras.layers.Layer s, o tf.keras.Model s creado en el paso delantero de su bucle de formación han desaparecido de la lista de variables de TF2, incluso si fueron capturados por la colección de variables en TF1.x. Solucione esto asignando las variables / capas / modelos que crea su pase directo a los atributos de instancia en su modelo. Ver aquí para obtener más información.

Paso 3: Restablezca todas las variables, verifique la equivalencia numérica con toda la aleatoriedad deshabilitada

El siguiente paso es verificar la equivalencia numérica tanto para las salidas reales como para el seguimiento de la pérdida de regularización cuando se corrige el modelo de manera que no hay generación de números aleatorios involucrada (como durante la inferencia).

La forma exacta de hacer esto puede depender de su modelo específico, pero en la mayoría de los modelos (como este), puede hacerlo de la siguiente manera:

  1. Inicializando los pesos al mismo valor sin aleatoriedad. Esto se puede hacer restableciéndolos a un valor fijo después de que se hayan creado.
  2. Ejecutar el modelo en modo de inferencia para evitar la activación de capas de abandono que pueden ser fuentes de aleatoriedad.

El siguiente código demuestra cómo puede comparar los resultados de TF1.xy TF2 de esta manera.

graph = tf.Graph()
with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
  height, width = 299, 299
  num_classes = 1000
  inputs = tf.ones( (1, height, width, 3))

  out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=False)

  # Rather than running the global variable initializers,
  # reset all variables to a constant value
  var_reset = tf.group([var.assign(tf.ones_like(var) * 0.001) for var in tf.compat.v1.global_variables()])
  sess.run(var_reset)

  # Grab the outputs & regularization loss
  reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
  tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
  tf1_output = sess.run(out)

print("Regularization loss:", tf1_regularization_loss)
tf1_output[0][:5]
Regularization loss: 0.001182976
array([0.00299837, 0.00299837, 0.00299837, 0.00299837, 0.00299837],
      dtype=float32)

Obtenga los resultados de TF2.

height, width = 299, 299
num_classes = 1000

model = InceptionResnetV2(num_classes)

inputs = tf.ones((1, height, width, 3))
# Call the model once to create the weights
out, endpoints = model(inputs, training=False)

# Reset all variables to the same fixed value as above, with no randomness
for var in model.variables:
  var.assign(tf.ones_like(var) * 0.001)
tf2_output, endpoints = model(inputs, training=False)

# Get the regularization loss
tf2_regularization_loss = tf.math.add_n(model.losses)

print("Regularization loss:", tf2_regularization_loss)
tf2_output[0][:5]
Regularization loss: tf.Tensor(0.0011829757, shape=(), dtype=float32)
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([0.00299837, 0.00299837, 0.00299837, 0.00299837, 0.00299837],
      dtype=float32)>
# Create a dict of tolerance values
tol_dict={'rtol':1e-06, 'atol':1e-05}
# Verify that the regularization loss and output both match
# when we fix the weights and avoid randomness by running inference:
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Los números coinciden entre TF1.x y TF2 cuando se quita fuentes de aleatoriedad, y el compatible con TF2 InceptionResnetV2 capa pasa la prueba.

Si observa que los resultados divergen para sus propios modelos, puede utilizar la impresión o pdb y la depuración interactiva para identificar dónde y por qué los resultados comienzan a divergir. La ejecución ávida puede hacer que esto sea mucho más fácil. También puede utilizar un enfoque de ablación para ejecutar solo pequeñas porciones del modelo en entradas intermedias fijas y aislar donde ocurre la divergencia.

Convenientemente, muchas redes delgadas (y otros modelos) también exponen puntos finales intermedios que puede probar.

Paso 4: alinee la generación de números aleatorios, verifique la equivalencia numérica tanto en el entrenamiento como en la inferencia

El paso final es verificar que el modelo TF2 coincide numéricamente con el modelo TF1.x, incluso cuando se tiene en cuenta la generación de números aleatorios en la inicialización de la variable y en el pase directo en sí (como las capas de abandono durante el pase directo).

Puede hacer esto usando la herramienta de prueba a continuación para hacer que la semántica de generación de números aleatorios coincida entre las gráficas / sesiones de TF1.x y la ejecución ansiosa.

Los gráficos / sesiones heredados de TF1 y la ejecución ávida de TF2 utilizan diferentes semánticas de generación de números aleatorios con estado.

En tf.compat.v1.Session s, si no se especifican las semillas, la generación de números aleatorios depende del número de operaciones se encuentran en el gráfico en el momento cuando se añade la operación de azar, y cuántas veces el gráfico se ejecuta. En la ejecución ansiosa, la generación de números aleatorios con estado depende de la semilla global, la semilla aleatoria de la operación y cuántas veces se ejecuta la operación con la operación con la semilla aleatoria dada. Ver tf.random.set_seed para obtener más información.

La siguiente DeterministicTestTool objeto proporciona un gestor de contexto scope() que puede hacer que las operaciones aleatorias con estado use la misma semilla a través de ambos gráficos TF1 / sesiones y ejecución ansiosos,

La herramienta proporciona dos modos de prueba:

  1. constant que utiliza la misma semilla para cada operación, no importa cuántas veces se le ha llamado y,
  2. num_random_ops que utiliza el número de operaciones aleatorias con estado previamente observado como la semilla operación.

Esto se aplica tanto a las operaciones aleatorias con estado utilizadas para crear e inicializar variables, como a las operaciones aleatorias con estado utilizadas en el cálculo (como para las capas de abandono).

seed_implementation = sys.modules[tf.compat.v1.get_seed.__module__]

class DeterministicTestTool(object):
  def __init__(self, seed: int = 42, mode='constant'):
    """Set mode to 'constant' or 'num_random_ops'. Defaults to 'constant'."""
    if mode not in {'constant', 'num_random_ops'}:
      raise ValueError("Mode arg must be 'constant' or 'num_random_ops'. " +
                       "Got: {}".format(mode))

    self._mode = mode
    self._seed = seed
    self.operation_seed = 0
    self._observed_seeds = set()

  def scope(self):
    tf.random.set_seed(self._seed)

    def _get_seed(_):
      """Wraps TF get_seed to make deterministic random generation easier.

      This makes a variable's initialization (and calls that involve random
      number generation) depend only on how many random number generations
      were used in the scope so far, rather than on how many unrelated
      operations the graph contains.

      Returns:
        Random seed tuple.
      """
      op_seed = self.operation_seed
      if self._mode == "constant":
        tf.random.set_seed(op_seed)
      else:
        if op_seed in self._observed_seeds:
          raise ValueError(
              'This `DeterministicTestTool` object is trying to re-use the ' +
              'already-used operation seed {}. '.format(op_seed) +
              'It cannot guarantee random numbers will match between eager ' +
              'and sessions when an operation seed is reused. ' +
              'You most likely set ' +
              '`operation_seed` explicitly but used a value that caused the ' +
              'naturally-incrementing operation seed sequences to overlap ' +
              'with an already-used seed.')

        self._observed_seeds.add(op_seed)
        self.operation_seed += 1

      return (self._seed, op_seed)

    # mock.patch internal symbols to modify the behavior of TF APIs relying on them

    return mock.patch.object(seed_implementation, 'get_seed', wraps=_get_seed)

Genere tres tensores aleatorios para mostrar cómo utilizar esta herramienta para hacer que la generación de números aleatorios con estado coincida entre sesiones y una ejecución ávida.

random_tool = DeterministicTestTool()
with random_tool.scope():
  graph = tf.Graph()
  with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
    a = tf.random.uniform(shape=(3,1))
    a = a * 3
    b = tf.random.uniform(shape=(3,3))
    b = b * 3
    c = tf.random.uniform(shape=(3,3))
    c = c * 3
    graph_a, graph_b, graph_c = sess.run([a, b, c])

graph_a, graph_b, graph_c
(array([[2.5063772],
        [2.7488918],
        [1.4839486]], dtype=float32),
 array([[2.5063772, 2.7488918, 1.4839486],
        [1.5633398, 2.1358476, 1.3693532],
        [0.3598416, 1.8287641, 2.5314465]], dtype=float32),
 array([[2.5063772, 2.7488918, 1.4839486],
        [1.5633398, 2.1358476, 1.3693532],
        [0.3598416, 1.8287641, 2.5314465]], dtype=float32))
random_tool = DeterministicTestTool()
with random_tool.scope():
  a = tf.random.uniform(shape=(3,1))
  a = a * 3
  b = tf.random.uniform(shape=(3,3))
  b = b * 3
  c = tf.random.uniform(shape=(3,3))
  c = c * 3

a, b, c
(<tf.Tensor: shape=(3, 1), dtype=float32, numpy=
 array([[2.5063772],
        [2.7488918],
        [1.4839486]], dtype=float32)>,
 <tf.Tensor: shape=(3, 3), dtype=float32, numpy=
 array([[2.5063772, 2.7488918, 1.4839486],
        [1.5633398, 2.1358476, 1.3693532],
        [0.3598416, 1.8287641, 2.5314465]], dtype=float32)>,
 <tf.Tensor: shape=(3, 3), dtype=float32, numpy=
 array([[2.5063772, 2.7488918, 1.4839486],
        [1.5633398, 2.1358476, 1.3693532],
        [0.3598416, 1.8287641, 2.5314465]], dtype=float32)>)
# Demonstrate that the generated random numbers match
np.testing.assert_allclose(graph_a, a.numpy(), **tol_dict)
np.testing.assert_allclose(graph_b, b.numpy(), **tol_dict)
np.testing.assert_allclose(graph_c, c.numpy(), **tol_dict)

Sin embargo, el aviso de que en constant modo, porque b y c se han generado con la misma semilla y tener la misma forma, tendrán exactamente los mismos valores.

np.testing.assert_allclose(b.numpy(), c.numpy(), **tol_dict)

Orden de seguimiento

Si usted está preocupado por algunos números aleatorios coincidentes en constant modo de reducir su confianza en su prueba de equivalencia numérica (por ejemplo, si varios pesos asumen las mismas inicializaciones), se puede utilizar el num_random_ops modo de evitar esto. En el num_random_ops modo, los números aleatorios generados dependerán de la ordenación de las operaciones aleatorias en el programa.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  graph = tf.Graph()
  with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
    a = tf.random.uniform(shape=(3,1))
    a = a * 3
    b = tf.random.uniform(shape=(3,3))
    b = b * 3
    c = tf.random.uniform(shape=(3,3))
    c = c * 3
    graph_a, graph_b, graph_c = sess.run([a, b, c])

graph_a, graph_b, graph_c
(array([[2.5063772],
        [2.7488918],
        [1.4839486]], dtype=float32),
 array([[0.45038545, 1.9197761 , 2.4536333 ],
        [1.0371652 , 2.9898582 , 1.924583  ],
        [0.25679827, 1.6579313 , 2.8418403 ]], dtype=float32),
 array([[2.9634383 , 1.0862181 , 2.6042497 ],
        [0.70099247, 2.3920312 , 1.0470468 ],
        [0.18173039, 0.8359269 , 1.0508587 ]], dtype=float32))
random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  a = tf.random.uniform(shape=(3,1))
  a = a * 3
  b = tf.random.uniform(shape=(3,3))
  b = b * 3
  c = tf.random.uniform(shape=(3,3))
  c = c * 3

a, b, c
(<tf.Tensor: shape=(3, 1), dtype=float32, numpy=
 array([[2.5063772],
        [2.7488918],
        [1.4839486]], dtype=float32)>,
 <tf.Tensor: shape=(3, 3), dtype=float32, numpy=
 array([[0.45038545, 1.9197761 , 2.4536333 ],
        [1.0371652 , 2.9898582 , 1.924583  ],
        [0.25679827, 1.6579313 , 2.8418403 ]], dtype=float32)>,
 <tf.Tensor: shape=(3, 3), dtype=float32, numpy=
 array([[2.9634383 , 1.0862181 , 2.6042497 ],
        [0.70099247, 2.3920312 , 1.0470468 ],
        [0.18173039, 0.8359269 , 1.0508587 ]], dtype=float32)>)
# Demonstrate that the generated random numbers match
np.testing.assert_allclose(graph_a, a.numpy(), **tol_dict)
np.testing.assert_allclose(graph_b, b.numpy(), **tol_dict )
np.testing.assert_allclose(graph_c, c.numpy(), **tol_dict)
# Demonstrate that with the 'num_random_ops' mode,
# b & c took on different values even though
# their generated shape was the same
assert not np.allclose(b.numpy(), c.numpy(), **tol_dict)

Sin embargo, observe que en este modo la generación aleatoria es sensible al orden del programa, por lo que los siguientes números aleatorios generados no coinciden.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  a = tf.random.uniform(shape=(3,1))
  a = a * 3
  b = tf.random.uniform(shape=(3,3))
  b = b * 3

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  b_prime = tf.random.uniform(shape=(3,3))
  b_prime = b_prime * 3
  a_prime = tf.random.uniform(shape=(3,1))
  a_prime = a_prime * 3

assert not np.allclose(a.numpy(), a_prime.numpy())
assert not np.allclose(b.numpy(), b_prime.numpy())

Para permitir la depuración de las variaciones debidas a fin de trazar, DeterministicTestTool en num_random_ops modo le permite ver cuántas operaciones al azar se han trazado con el operation_seed propiedad.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  print(random_tool.operation_seed)
  a = tf.random.uniform(shape=(3,1))
  a = a * 3
  print(random_tool.operation_seed)
  b = tf.random.uniform(shape=(3,3))
  b = b * 3
  print(random_tool.operation_seed)
0
1
2

Si usted necesita para tener en cuenta la variación fin rastro en sus pruebas, incluso se puede establecer el incremento automático operation_seed explícitamente. Por ejemplo, puede usar esto para hacer coincidir la generación de números aleatorios en dos órdenes de programa diferentes.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  print(random_tool.operation_seed)
  a = tf.random.uniform(shape=(3,1))
  a = a * 3
  print(random_tool.operation_seed)
  b = tf.random.uniform(shape=(3,3))
  b = b * 3

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  random_tool.operation_seed = 1
  b_prime = tf.random.uniform(shape=(3,3))
  b_prime = b_prime * 3
  random_tool.operation_seed = 0
  a_prime = tf.random.uniform(shape=(3,1))
  a_prime = a_prime * 3

np.testing.assert_allclose(a.numpy(), a_prime.numpy(), **tol_dict)
np.testing.assert_allclose(b.numpy(), b_prime.numpy(), **tol_dict)
0
1

Sin embargo, DeterministicTestTool Deshabilita la reutilización de semillas de operación ya-usadas, por lo que asegúrese de que las secuencias de incremento automático no se pueden superponer. Esto se debe a que la ejecución ansiosa genera diferentes números para usos posteriores de la misma semilla de operación, mientras que los gráficos y sesiones TF1 no lo hacen, por lo que generar un error ayuda a mantener la sesión y la generación ansiosa de números aleatorios en línea.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  random_tool.operation_seed = 1
  b_prime = tf.random.uniform(shape=(3,3))
  b_prime = b_prime * 3
  random_tool.operation_seed = 0
  a_prime = tf.random.uniform(shape=(3,1))
  a_prime = a_prime * 3
  try:
    c = tf.random.uniform(shape=(3,1))
    raise RuntimeError("An exception should have been raised before this, " +
                     "because the auto-incremented operation seed will " +
                     "overlap an already-used value")
  except ValueError as err:
    print(err)
This `DeterministicTestTool` object is trying to re-use the already-used operation seed 1. It cannot guarantee random numbers will match between eager and sessions when an operation seed is reused. You most likely set `operation_seed` explicitly but used a value that caused the naturally-incrementing operation seed sequences to overlap with an already-used seed.

Verificación de la inferencia

Ahora puede utilizar el DeterministicTestTool para asegurarse de que los InceptionResnetV2 coincidencias modelo en la inferencia, aunque se use la inicialización de peso al azar. Para una condición de prueba más fuerte debido a igualar el orden del programa, utilice el num_random_ops modo.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  graph = tf.Graph()
  with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
    height, width = 299, 299
    num_classes = 1000
    inputs = tf.ones( (1, height, width, 3))

    out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=False)

    # Initialize the variables
    sess.run(tf.compat.v1.global_variables_initializer())

    # Grab the outputs & regularization loss
    reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
    tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
    tf1_output = sess.run(out)

  print("Regularization loss:", tf1_regularization_loss)
Regularization loss: 1.2254326
height, width = 299, 299
num_classes = 1000

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  model = InceptionResnetV2(num_classes)

  inputs = tf.ones((1, height, width, 3))
  tf2_output, endpoints = model(inputs, training=False)

  # Grab the regularization loss as well
  tf2_regularization_loss = tf.math.add_n(model.losses)

print("Regularization loss:", tf2_regularization_loss)
Regularization loss: tf.Tensor(1.2254325, shape=(), dtype=float32)
# Verify that the regularization loss and output both match
# when using the DeterministicTestTool:
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Verificación de la formación

Debido DeterministicTestTool funciona para todas las operaciones aleatorias con estado (incluyendo tanto la inicialización de peso y la computación, tales como capas de abandono), que se puede utilizar para verificar los modelos coinciden en el modo de entrenamiento también. Podrá utilizar de nuevo el num_random_ops modo debido a que el orden de los programas de las operaciones aleatorias con estado coincide.

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  graph = tf.Graph()
  with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
    height, width = 299, 299
    num_classes = 1000
    inputs = tf.ones( (1, height, width, 3))

    out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=True)

    # Initialize the variables
    sess.run(tf.compat.v1.global_variables_initializer())

    # Grab the outputs & regularization loss
    reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
    tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
    tf1_output = sess.run(out)

  print("Regularization loss:", tf1_regularization_loss)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/layers/normalization/batch_normalization.py:532: _colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
Regularization loss: 1.22548
height, width = 299, 299
num_classes = 1000

random_tool = DeterministicTestTool(mode='num_random_ops')
with random_tool.scope():
  model = InceptionResnetV2(num_classes)

  inputs = tf.ones((1, height, width, 3))
  tf2_output, endpoints = model(inputs, training=True)

  # Grab the regularization loss as well
  tf2_regularization_loss = tf.math.add_n(model.losses)

print("Regularization loss:", tf2_regularization_loss)
Regularization loss: tf.Tensor(1.2254798, shape=(), dtype=float32)
# Verify that the regularization loss and output both match
# when using the DeterministicTestTool
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Ahora ha comprobado que el InceptionResnetV2 modelo de funcionamiento con impaciencia decoradores alrededor tf.keras.layers.Layer coincide numéricamente la red delgada que se ejecuta en TF1 gráficos y sesiones.

Por ejemplo, llamando a la InceptionResnetV2 capa directamente con training=True intercalaciones de inicialización variable con el fin de deserción de acuerdo con el orden de la creación de la red.

Por otra parte, la primera puesta tf.keras.layers.Layer decorador en un modelo funcional Keras y sólo entonces llamar el modelo con training=True es equivalente a inicializar todas las variables a continuación, utilizando la capa de deserción. Esto produce un orden de rastreo diferente y un conjunto diferente de números aleatorios.

Sin embargo, el valor predeterminado mode='constant' no es sensible a estas diferencias para el rastreo y pasará sin trabajo extra, incluso cuando la incrustación de la capa en un modelo funcional Keras.

random_tool = DeterministicTestTool()
with random_tool.scope():
  graph = tf.Graph()
  with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
    height, width = 299, 299
    num_classes = 1000
    inputs = tf.ones( (1, height, width, 3))

    out, endpoints = inception_resnet_v2(inputs, num_classes, is_training=True)

    # Initialize the variables
    sess.run(tf.compat.v1.global_variables_initializer())

    # Get the outputs & regularization losses
    reg_losses = tf.compat.v1.get_collection(tf.compat.v1.GraphKeys.REGULARIZATION_LOSSES)
    tf1_regularization_loss = sess.run(tf.math.add_n(reg_losses))
    tf1_output = sess.run(out)

  print("Regularization loss:", tf1_regularization_loss)
Regularization loss: 1.2239965
height, width = 299, 299
num_classes = 1000

random_tool = DeterministicTestTool()
with random_tool.scope():
  keras_input = tf.keras.Input(shape=(height, width, 3))
  layer = InceptionResnetV2(num_classes)
  model = tf.keras.Model(inputs=keras_input, outputs=layer(keras_input))

  inputs = tf.ones((1, height, width, 3))
  tf2_output, endpoints = model(inputs, training=True)

  # Get the regularization loss
  tf2_regularization_loss = tf.math.add_n(model.losses)

print("Regularization loss:", tf2_regularization_loss)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/engine/base_layer.py:1345: 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.
  warnings.warn('`layer.updates` will be removed in a future version. '
Regularization loss: tf.Tensor(1.2239964, shape=(), dtype=float32)
# Verify that the regularization loss and output both match
# when using the DeterministicTestTool
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Paso 3b o 4b (opcional): prueba con puntos de control preexistentes

Después del paso 3 o el paso 4 anterior, puede ser útil ejecutar sus pruebas de equivalencia numérica al comenzar desde puntos de control basados ​​en nombres preexistentes, si tiene alguno. Esto puede probar que la carga de su punto de control heredado funciona correctamente y que el modelo en sí funciona correctamente. Los puestos de control Reutilización TF1.x guían explica la forma de reutilizar sus preexistentes puestos de control TF1.x y transferirlos a los puestos de control de TF2.

Pruebas y resolución de problemas adicionales

A medida que agrega más pruebas de equivalencia numérica, también puede optar por agregar una prueba que verifique que su cálculo de gradiente (o incluso las actualizaciones de su optimizador) coincida.

La retropropagación y el cálculo de gradientes son más propensos a inestabilidades numéricas de coma flotante que los pases hacia adelante del modelo. Esto significa que a medida que sus pruebas de equivalencia cubren más partes no aisladas de su entrenamiento, puede comenzar a ver diferencias numéricas no triviales entre correr con entusiasmo y sus gráficos TF1. Esto puede deberse a las optimizaciones de gráficos de TensorFlow que hacen cosas como reemplazar subexpresiones en un gráfico con menos operaciones matemáticas.

Para aislar si esto es probable que sea el caso, se puede comparar el código de TF1 TF2 cómputo sucediendo en el interior de un tf.function (que se aplica la optimización gráfica pasa igual que su gráfico TF1) en lugar de un cálculo puramente ansiosos. Como alternativa, puede probar a usar tf.config.optimizer.set_experimental_options para desactivar la optimización pasa como "arithmetic_optimization" antes de que su cómputo TF1 para ver si el resultado termina numéricamente más cerca de sus TF2 resultados del cálculo. En sus recorridos de entrenamiento reales que se recomienda el uso tf.function con la optimización de los pases permitido por razones de rendimiento, pero puede ser útil para desactivarlas en las pruebas unitarias equivalencia numérica.

Del mismo modo, también se puede encontrar que tf.compat.v1.train optimizadores y optimizadores de TF2 tiene propiedades ligeramente diferentes Numerics punto flotante que TF2 optimizadores, aunque las fórmulas matemáticas que están representando son los mismos. Es menos probable que esto sea un problema en sus carreras de entrenamiento, pero puede requerir una mayor tolerancia numérica en las pruebas de unidades de equivalencia.