ヘルプKaggleにTensorFlowグレートバリアリーフを保護チャレンジに参加

正確性と数値的同値の検証

TensorFlow.orgで表示 GoogleColabで実行 GitHubで表示 ノートブックをダウンロード

TensorFlowコードをTF1.xからTF2に移行するときは、移行したコードがTF1.xと同じように動作することを確認することをお勧めします。

このガイド付きカバー移行コード例tf.compat.v1.keras.utils.track_tf1_style_variablesシムをモデル化が印加tf.keras.layers.Layer方法。読んモデルマッピングガイドをシムをモデリングTF2についての詳細を調べるために。

このガイドでは、次の目的で使用できるアプローチについて詳しく説明します。

  • 移行されたコードを使用して、トレーニングモデルから得られた結果の正確さを検証します
  • TensorFlowバージョン間でコードの数値的同等性を検証します

設定

# 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.

フォワードパスコードの重要なチャンクをシムに入れる場合は、TF1.xの場合と同じように動作していることを知りたいと思います。たとえば、TF-SlimInception-Resnet-v2モデル全体をシムに入れようとすることを検討してください。

# 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.

たまたま、このレイヤーは実際には箱から出して完全に正常に機能します(正確な正則化損失追跡を備えています)。

ただし、これは当然のこととは言えません。以下の手順に従って、完全な数値的同値を観察するまで、TF1.xの場合と同じように実際に動作していることを確認します。これらの手順は、フォワードパスのどの部分がTF1.xからの発散を引き起こしているのかを三角測量するのにも役立ちます(モデルの別の部分ではなく、モデルのフォワードパスで発散が発生するかどうかを識別します)。

ステップ1:変数が1回だけ作成されることを確認する

最初に確認する必要があるのは、毎回誤って新しい変数を作成して使用するのではなく、呼び出しごとに変数を再利用する方法でモデルを正しく構築したことです。お使いのモデルが新しいKeras層を作成または呼び出す場合たとえば、 tf.Variable各往路呼び出しで、それが最も可能性が高い変数をキャプチャするために失敗し、新しいものを毎回作成しています。

以下は、モデルが新しい変数を作成していることを検出し、モデルのどの部分がそれを実行しているかをデバッグするために使用できる2つのコンテキストマネージャースコープです。

@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)

最初のスコープ( assert_no_variable_creations()あなたがスコープ内の変数を作成してみたら)すぐにエラーが発生します。これにより、スタックトレースを検査して(そしてインタラクティブなデバッグを使用して)、既存の変数を再利用する代わりに、どのコード行が変数を作成したかを正確に把握できます。

二スコープ( catch_and_raise_created_variables()終わったすべての変数が作成されている場合)、スコープの終わりで例外が発生します。この例外には、スコープで作成されたすべての変数のリストが含まれます。これは、一般的なパターンを見つけることができる場合に、モデルが作成しているすべての重みのセットが何であるかを理解するのに役立ちます。ただし、これらの変数が作成されたコードの正確な行を特定するのにはあまり役立ちません。

以下の両方のスコープを使用して、シムベースのInceptionResnetV2レイヤーが最初の呼び出し後に新しい変数を作成しないことを確認します(おそらくそれらを再利用します)。

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 '

以下の例では、これらのデコレータが、既存のウェイトを再利用するのではなく、毎回誤って新しいウェイトを作成するレイヤーでどのように機能するかを観察します。

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>])

ウェイトを1回だけ作成し、毎回再利用することで、レイヤーを修正できます。

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)

トラブルシューティング

モデルが既存のウェイトを再利用するのではなく、誤って新しいウェイトを作成する可能性がある一般的な理由は次のとおりです。

  1. これは、明示的に使用していますtf.Variable作成済みの再利用せずにコールをtf.Variables 。最初に作成されていないかどうかを確認してから、既存のものを再利用することで、これを修正します。
  2. (とは対照的に、それは直接前方に毎回パスでKeras層またはモデルを作成tf.compat.v1.layers )。最初に作成されていないかどうかを確認してから、既存のものを再利用することで、これを修正します。
  3. それはの上に構築されてtf.compat.v1.layersが、すべての割り当てに失敗したcompat.v1.layers明示的な名前をしたり、ラップするcompat.v1.layer命名の使用内部variable_scope 、で増分に自動生成されたレイヤ名を引き起こし各モデルの呼び出し。命名置くことによってこの問題を解決tf.compat.v1.variable_scopeお使いのすべてのラップあなたのシム飾らメソッド内tf.compat.v1.layers使用を。

ステップ2:変数の数、名前、および形状が一致することを確認します

2番目のステップは、TF2で実行されているレイヤーが、対応するコードがTF1.xで行うのと同じ数のウェイトを、同じ形状で作成することを確認することです。

以下に示すように、手動でチェックして一致することを確認することと、プログラムで単体テストでチェックを実行することを組み合わせることができます。

# 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 '

次に、TF2のシムラップレイヤーについても同じようにします。重みを取得する前に、モデルが複数回呼び出されることにも注意してください。これは、変数の再利用を効果的にテストするために行われます。

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

シムベースのInceptionResnetV2レイヤーはこのテストに合格しています。ただし、それらが一致しない場合は、差分(テキストまたはその他)を実行して、違いがどこにあるかを確認できます。

これにより、モデルのどの部分が期待どおりに動作していないかについての手がかりが得られます。熱心な実行により、pdb、対話型デバッグ、およびブレークポイントを使用して、疑わしいと思われるモデルの部分を掘り下げ、何が問題になっているのかをより詳細にデバッグできます。

トラブルシューティング

  • 明示的で直接作成されたすべての変数の名前には細心の注意を払ってくださいtf.VariableコールとKeras層/その変数名生成意味論としてモデルは、そのような熱心な実行やなどTF1.xグラフとTF2の機能の間で多少異なる場合がありtf.function場合でも、すべてのものそれ以外は正常に動作しています。これが当てはまる場合は、わずかに異なる命名セマンティクスを考慮してテストを調整してください。

  • あなたは時々ことがありtf.Variable秒、 tf.keras.layers.Layer秒、またはtf.keras.Modelトレーニングループのフォワード・パスで作成されたのは、それらが変数コレクションで撮影したとしても、あなたのTF2の変数リストから欠落していますTF1.xで。フォワードパスが作成する変数/レイヤー/モデルをモデルのインスタンス属性に割り当てることで、これを修正します。参照してくださいここで詳細は。

ステップ3:すべての変数をリセットし、すべてのランダム性を無効にして数値の同等性を確認します

次のステップは、乱数の生成が含まれないようにモデルを修正するときに、実際の出力と正則化損失追跡の両方の数値的同等性を検証することです(推論中など)。

これを行う正確な方法は、特定のモデルによって異なる場合がありますが、ほとんどのモデル(このモデルなど)では、次の方法でこれを行うことができます。

  1. ランダム性なしで重みを同じ値に初期化します。これは、作成後に固定値にリセットすることで実行できます。
  2. ランダム性の原因となる可能性のあるドロップアウトレイヤーのトリガーを回避するために、モデルを推論モードで実行します。

次のコードは、この方法でTF1.xとTF2の結果を比較する方法を示しています。

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)

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)

もし乱数の発生源を除去する際の数字はTF1.xとTF2の間で一致し、TF2互換InceptionResnetV2層は、試験に合格します。

独自のモデルで結果が発散しているのを観察している場合は、印刷またはpdbとインタラクティブなデバッグを使用して、結果が発散し始める場所と理由を特定できます。熱心な実行はこれを非常に簡単にすることができます。アブレーションアプローチを使用して、固定された中間入力でモデルのごく一部のみを実行し、発散が発生する場所を分離することもできます。

便利なことに、多くのスリムネット(および他のモデル)は、プローブできる中間エンドポイントも公開します。

ステップ4:乱数生成を調整し、トレーニングと推論の両方で数値の同等性を確認します

最後のステップは、変数の初期化およびフォワードパス自体(フォワードパス中のドロップアウトレイヤーなど)での乱数生成を考慮している場合でも、TF2モデルがTF1.xモデルと数値的に一致することを確認することです。

これを行うには、以下のテストツールを使用して、TF1.xグラフ/セッションと熱心な実行の間で乱数生成セマンティクスを一致させます。

TF1レガシーグラフ/セッションとTF2熱心な実行は、異なるステートフル乱数生成セマンティクスを使用します。

tf.compat.v1.Session何種が指定されていない場合の、乱数生成は、ランダムな操作が追加された時点で、グラフにあるどのように多くの操作に依存し、何回グラフが実行されます。熱心な実行では、ステートフル乱数の生成は、グローバルシード、操作ランダムシード、および指定されたランダムシードを使用した操作での操作が実行された回数に依存します。参照してくださいtf.random.set_seed詳細は。

以下DeterministicTestToolオブジェクトは、コンテキストマネージャ提供scope()ステートフルランダム動作はTF1グラフ/セッションと熱心実行の両方で同じシードを使用することができ

このツールには、次の2つのテストモードがあります。

  1. constantひとつひとつの動作のためにそれが呼び出された回数に関係なく同じシードを使用して、
  2. num_random_ops操作種として以前に観察されたステートフルなランダムな動作の数を使用します。

これは、変数の作成と初期化に使用されるステートフルランダム操作と、計算で使用されるステートフルランダム操作(ドロップアウトレイヤーなど)の両方に適用されます。

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)

3つの乱数テンソルを生成して、このツールを使用して、セッションと熱心な実行の間でステートフルな乱数生成を一致させる方法を示します。

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)

しかしながら、通知はでそのconstantためモード、 b及びc同じシードで生成され、同一の形状を有したが、それらは正確に同じ値を有することになります。

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

トレース順序

あなたがに一致するいくつかのランダムな数字を心配している場合はconstant (例えば、いくつかの重みが同じ初期化に取る場合)あなたの数値等価テストであなたの自信を軽減モードは、使用することができますnum_random_opsこれを回避するためのモードを。でnum_random_opsモードで、生成された乱数は、プログラム内のランダムなOPSの順序に依存します。

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)

ただし、このモードでは、ランダム生成はプログラムの順序に敏感であるため、次の生成された乱数は一致しないことに注意してください。

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())

順序をトレースするバリエーションのデバッグを可能にするため、 DeterministicTestToolnum_random_opsモードでは、あなたがトレースされているどのように多くのランダムな操作を確認することができますoperation_seedプロパティ。

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

あなたのテストでトレース順序を変更することを考慮するために必要がある場合は、あなたも自動インクリメント設定することができoperation_seed明示。たとえば、これを使用して、2つの異なるプログラム順序間で乱数生成を一致させることができます。

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

しかし、 DeterministicTestToolそれほど確か自動インクリメントのシーケンスはありません重ねることができる作る、既に使用済みの操作の種を再利用禁止します。これは、熱心な実行では同じ操作シードの後続の使用に対して異なる数が生成されるのに対し、TF1グラフとセッションでは生成されないため、エラーを発生させると、セッションと熱心なステートフル乱数の生成を一列に保つのに役立ちます。

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.

推論の検証

これで、使用することができますDeterministicTestTool確認するためにInceptionResnetV2ランダムな重みの初期化を使用する場合でも、推論でモデルの一致を。より強力な試験条件のために起因するマッチングプログラム順に、使用num_random_opsモードを。

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)

トレーニングの検証

のでDeterministicTestTool (例えばドロップアウト層としての重量の初期化と計算の両方を含む)すべてのステートフルランダムの操作のために働く、あなたはモデルが同様にトレーニングモードで一致を確認するためにそれを使用することができます。あなたは、再び使用することができますnum_random_opsステートフルランダムOPSのプログラム順序が一致するためのモードを。

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)

あなたは今ことを確認したInceptionResnetV2周りのデコレータで熱心に実行されているモデルtf.keras.layers.Layer数値的にTF1のグラフとのセッションで実行されているスリムなネットワークと一致します。

たとえば、呼び出しInceptionResnetV2直接層をtraining=Trueネットワークの作成順序に従ってドロップアウト順序でインターリーブ変数の初期化。

一方、第1パットtf.keras.layers.Layer Keras機能モデルでデコレータをだけにしてモデルを呼び出すtraining=True 、ドロップアウト層を使用して、すべての変数を初期化することと同じです。これにより、異なるトレース順序と異なる乱数のセットが生成されます。

ただし、デフォルトmode='constant'順序をトレースにおけるこれらの違いに敏感ではなく、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)

ステップ3bまたは4b(オプション):既存のチェックポイントを使用したテスト

上記のステップ3またはステップ4の後で、既存の名前ベースのチェックポイントがある場合は、それから開始するときに数値的同値テストを実行すると便利な場合があります。これにより、レガシーチェックポイントの読み込みが正しく機能していることと、モデル自体が正しく機能していることの両方をテストできます。再利用TF1.xチェックポイントガイドカバーどのようにあなたの既存のTF1.xチェックポイントを再利用し、TF2のチェックポイントにそれらを介して転送します。

追加のテストとトラブルシューティング

数値的同値検定をさらに追加すると、勾配計算(またはオプティマイザーの更新)が一致することを確認する検定を追加することもできます。

バックプロパゲーションと勾配計算は、モデルのフォワードパスよりも浮動小数点の数値が不安定になる傾向があります。これは、同等性テストがトレーニングのより分離されていない部分をカバーするにつれて、完全に熱心に実行することとTF1グラフとの間に重要な数値の違いが見られるようになる可能性があることを意味します。これは、グラフ内の部分式をより少ない数学演算で置き換えるなどのTensorFlowのグラフ最適化が原因である可能性があります。

これはおそらく場合するかどうかを単離するために、あなたはのTF2計算起こっ内側にあなたのTF1コードを比較することができtf.functionではなく、純粋に熱心な計算よりも(自分のTF1のグラフのように渡し、グラフの最適化を適用します)。また、あなたが使用して試すことができtf.config.optimizer.set_experimental_options無効に最適化するなどの渡し"arithmetic_optimization"結果が数値的に近いあなたのTF2の計算結果に終わるかどうかを確認するためにあなたのTF1の計算の前に。実際のトレーニングランでは、あなたが使用することをお勧めしますtf.function最適化パスでパフォーマンス上の理由のために有効になりますが、あなたはあなたの数値等価ユニットテストでそれらを無効にすることが役に立つかもしれません。

同様に、あなたも見つけることがtf.compat.v1.trainオプティマイザとTF2オプティマイザは、彼らが代表している数式が同一であっても、TF2オプティマイザよりもわずかに異なる浮動小数点数値の特性を有しています。これは、トレーニングの実行で問題になる可能性は低くなりますが、等価単体テストではより高い数値許容値が必要になる場合があります。