Memvalidasi kebenaran & kesetaraan numerik

Lihat di TensorFlow.org Jalankan di Google Colab Lihat di GitHub Unduh buku catatan

Saat memigrasikan kode TensorFlow Anda dari TF1.x ke TF2, sebaiknya pastikan bahwa kode yang dimigrasikan berperilaku sama di TF2 seperti di TF1.x.

Panduan ini mencakup contoh kode migrasi dengan shim pemodelan tf.compat.v1.keras.utils.track_tf1_style_variables yang diterapkan ke metode tf.keras.layers.Layer . Baca panduan pemetaan model untuk mengetahui lebih lanjut tentang shim pemodelan TF2.

Panduan ini merinci pendekatan yang dapat Anda gunakan untuk:

  • Validasi kebenaran hasil yang diperoleh dari model pelatihan menggunakan kode yang dimigrasikan
  • Validasi kesetaraan numerik kode Anda di seluruh versi TensorFlow

Mempersiapkan

pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is available only in
# Tensorflow 2.8
pip install -q tf-nightly
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 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: 3192, done.[K
remote: Counting objects: 100% (3192/3192), done.[K
remote: Compressing objects: 100% (2696/2696), done.[K
remote: Total 3192 (delta 848), reused 1381 (delta 453), pack-reused 0[K
Receiving objects: 100% (3192/3192), 33.39 MiB | 12.89 MiB/s, done.
Resolving deltas: 100% (848/848), done.

Jika Anda memasukkan potongan kode forward pass yang tidak sepele ke dalam shim, Anda ingin tahu bahwa kode tersebut berperilaku sama seperti di TF1.x. Misalnya, pertimbangkan untuk mencoba memasukkan seluruh model TF-Slim Inception-Resnet-v2 ke dalam shim seperti:

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

Seperti yang terjadi, lapisan ini benar-benar berfungsi dengan baik di luar kotak (lengkap dengan pelacakan kerugian regularisasi yang akurat).

Namun, ini bukan sesuatu yang ingin Anda terima begitu saja. Ikuti langkah-langkah di bawah ini untuk memverifikasi bahwa itu benar-benar berperilaku seperti yang terjadi di TF1.x, hingga mengamati kesetaraan numerik yang sempurna. Langkah-langkah ini juga dapat membantu Anda melakukan triangulasi bagian mana dari forward pass yang menyebabkan divergensi dari TF1.x (identifikasi jika divergensi muncul dalam model forward pass sebagai lawan dari bagian model yang berbeda).

Langkah 1: Verifikasi variabel hanya dibuat sekali

Hal pertama yang harus Anda verifikasi adalah bahwa Anda telah membangun model dengan benar dengan cara yang menggunakan kembali variabel di setiap panggilan daripada secara tidak sengaja membuat dan menggunakan variabel baru setiap kali. Misalnya, jika model Anda membuat lapisan Keras baru atau memanggil tf.Variable di setiap panggilan penerusan, kemungkinan besar model tersebut gagal menangkap variabel dan membuat variabel baru setiap kali.

Di bawah ini adalah dua cakupan manajer konteks yang dapat Anda gunakan untuk mendeteksi saat model Anda membuat variabel baru dan men-debug bagian mana dari model yang melakukannya.

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

Cakupan pertama ( assert_no_variable_creations() ) akan segera memunculkan kesalahan setelah Anda mencoba membuat variabel dalam ruang lingkup. Ini memungkinkan Anda untuk memeriksa stacktrace (dan menggunakan debugging interaktif) untuk mengetahui dengan tepat baris kode apa yang membuat variabel alih-alih menggunakan kembali variabel yang sudah ada.

Lingkup kedua ( catch_and_raise_created_variables() ) akan memunculkan pengecualian di akhir lingkup jika ada variabel yang akhirnya dibuat. Pengecualian ini akan menyertakan daftar semua variabel yang dibuat dalam cakupan. Ini berguna untuk mencari tahu apa himpunan semua bobot yang dibuat model Anda jika Anda dapat melihat pola umum. Namun, kurang berguna untuk mengidentifikasi baris kode yang tepat di mana variabel tersebut dibuat.

Gunakan kedua cakupan di bawah ini untuk memverifikasi bahwa lapisan InceptionResnetV2 berbasis shim tidak membuat variabel baru apa pun setelah panggilan pertama (mungkin menggunakannya kembali).

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/tf_slim/layers/layers.py:684: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  outputs = layer.apply(inputs, training=is_training)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/legacy_tf_layers/core.py:332: 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 '

Pada contoh di bawah, amati bagaimana dekorator ini bekerja pada lapisan yang setiap kali membuat bobot baru secara tidak benar alih-alih menggunakan kembali bobot yang sudah ada.

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_27382/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_27382/3224979076.py", line 6, in call
    var = tf.Variable(initial_value=2.0)
  File "/tmp/ipykernel_27382/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>])

Anda dapat memperbaiki lapisan dengan memastikan itu hanya membuat bobot sekali dan kemudian menggunakannya kembali setiap kali.

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)

Penyelesaian masalah

Berikut adalah beberapa alasan umum mengapa model Anda mungkin secara tidak sengaja membuat bobot baru alih-alih menggunakan kembali bobot yang sudah ada:

  1. Ini menggunakan panggilan tf.Variable eksplisit tanpa menggunakan kembali tf.Variables yang sudah dibuat. Perbaiki ini dengan terlebih dahulu memeriksa apakah itu belum dibuat kemudian menggunakan kembali yang sudah ada.
  2. Ini menciptakan lapisan Keras atau model langsung di forward pass setiap kali (sebagai lawan dari tf.compat.v1.layers ). Perbaiki ini dengan terlebih dahulu memeriksa apakah itu belum dibuat kemudian menggunakan kembali yang sudah ada.
  3. Itu dibangun di atas tf.compat.v1.layers tetapi gagal untuk menetapkan semua compat.v1.layers nama eksplisit atau untuk membungkus penggunaan compat.v1.layer Anda di dalam variable_scope bernama, menyebabkan nama lapisan yang dibuat secara otomatis bertambah setiap panggilan model. Perbaiki ini dengan meletakkan tf.compat.v1.variable_scope bernama di dalam metode yang didekorasi dengan shim yang membungkus semua penggunaan tf.compat.v1.layers Anda.

Langkah 2: Periksa apakah jumlah variabel, nama, dan bentuk cocok

Langkah kedua adalah memastikan lapisan Anda berjalan di TF2 menciptakan jumlah bobot yang sama, dengan bentuk yang sama, seperti yang dilakukan kode yang sesuai di TF1.x.

Anda dapat melakukan campuran pemeriksaan manual untuk melihat kecocokannya, dan melakukan pemeriksaan secara terprogram dalam pengujian unit seperti yang ditunjukkan di bawah ini.

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

Selanjutnya, lakukan hal yang sama untuk lapisan shim-wrapped di TF2. Perhatikan bahwa model juga dipanggil beberapa kali sebelum mengambil bobot. Ini dilakukan untuk menguji penggunaan kembali variabel secara efektif.

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-12-04 02:27:27.209445: 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

Lapisan InceptionResnetV2 berbasis shim lulus tes ini. Namun, dalam kasus di mana mereka tidak cocok, Anda dapat menjalankannya melalui diff (teks atau lainnya) untuk melihat di mana perbedaannya.

Ini dapat memberikan petunjuk tentang bagian mana dari model yang tidak berperilaku seperti yang diharapkan. Dengan eksekusi yang bersemangat, Anda dapat menggunakan pdb, debugging interaktif, dan breakpoints untuk menggali bagian-bagian model yang tampak mencurigakan, dan men-debug apa yang salah secara lebih mendalam.

Penyelesaian masalah

  • Perhatikan baik-baik nama variabel apa pun yang dibuat langsung oleh panggilan tf.Variable eksplisit dan lapisan/model Keras karena semantik pembuatan nama variabelnya mungkin sedikit berbeda antara grafik TF1.x dan fungsionalitas TF2 seperti eksekusi bersemangat dan tf.function bahkan jika semuanya lain bekerja dengan benar. Jika hal ini terjadi pada Anda, sesuaikan pengujian Anda untuk memperhitungkan semantik penamaan yang sedikit berbeda.

  • Terkadang Anda mungkin menemukan bahwa tf.Variable s, tf.keras.layers.Layer s, atau tf.keras.Model s yang dibuat dalam pass maju loop pelatihan Anda hilang dari daftar variabel TF2 Anda meskipun mereka ditangkap oleh koleksi variabel di TF1.x. Perbaiki ini dengan menetapkan variabel/lapisan/model yang dibuat oleh forward pass Anda ke atribut instance dalam model Anda. Lihat di sini untuk info lebih lanjut.

Langkah 3: Setel ulang semua variabel, periksa kesetaraan numerik dengan semua keacakan dinonaktifkan

Langkah selanjutnya adalah memverifikasi kesetaraan numerik untuk output aktual dan pelacakan kehilangan regularisasi ketika Anda memperbaiki model sedemikian rupa sehingga tidak ada pembuatan nomor acak yang terlibat (seperti selama inferensi).

Cara yang tepat untuk melakukannya mungkin bergantung pada model spesifik Anda, tetapi di sebagian besar model (seperti yang ini), Anda dapat melakukannya dengan:

  1. Inisialisasi bobot ke nilai yang sama tanpa keacakan. Ini dapat dilakukan dengan mengatur ulang ke nilai tetap setelah dibuat.
  2. Menjalankan model dalam mode inferensi untuk menghindari memicu lapisan putus sekolah yang dapat menjadi sumber keacakan.

Kode berikut menunjukkan bagaimana Anda dapat membandingkan hasil TF1.x dan TF2 dengan cara ini.

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)

Dapatkan hasil 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)

Angka cocok antara TF1.x dan TF2 saat Anda menghapus sumber keacakan, dan lapisan InceptionResnetV2 yang kompatibel dengan TF2 lulus pengujian.

Jika Anda mengamati hasil yang berbeda untuk model Anda sendiri, Anda dapat menggunakan pencetakan atau pdb dan debugging interaktif untuk mengidentifikasi di mana dan mengapa hasilnya mulai menyimpang. Eksekusi yang bersemangat dapat membuat ini jauh lebih mudah. Anda juga dapat menggunakan pendekatan ablasi untuk menjalankan hanya sebagian kecil model pada input perantara tetap dan mengisolasi tempat divergensi terjadi.

Mudahnya, banyak jaring tipis (dan model lainnya) juga mengekspos titik akhir perantara yang dapat Anda selidiki.

Langkah 4: Sejajarkan pembuatan angka acak, periksa kesetaraan numerik dalam pelatihan dan inferensi

Langkah terakhir adalah memverifikasi bahwa model TF2 secara numerik cocok dengan model TF1.x, bahkan ketika memperhitungkan pembuatan bilangan acak dalam inisialisasi variabel dan dalam lintasan maju itu sendiri (seperti lapisan putus selama lintasan maju).

Anda dapat melakukan ini dengan menggunakan alat pengujian di bawah ini untuk membuat kecocokan semantik pembuatan angka acak antara grafik/sesi TF1.x dan eksekusi yang bersemangat.

Grafik/sesi warisan TF1 dan eksekusi bersemangat TF2 menggunakan semantik pembuatan angka acak stateful yang berbeda.

Dalam tf.compat.v1.Session s, jika tidak ada seed yang ditentukan, pembuatan bilangan acak bergantung pada berapa banyak operasi dalam grafik pada saat operasi acak ditambahkan, dan berapa kali grafik dijalankan. Dalam eksekusi bersemangat, pembangkitan bilangan acak stateful tergantung pada benih global, benih acak operasi, dan berapa kali operasi dengan operasi dengan benih acak yang diberikan dijalankan. Lihat tf.random.set_seed untuk info lebih lanjut.

Kelas v1.keras.utils.DeterministicRandomTestTool berikut menyediakan scope() yang dapat membuat operasi acak stateful menggunakan benih yang sama di kedua grafik/sesi TF1 dan eksekusi bersemangat.

Alat ini menyediakan dua mode pengujian:

  1. constant yang menggunakan benih yang sama untuk setiap operasi tidak peduli berapa kali dipanggil dan,
  2. num_random_ops yang menggunakan jumlah operasi acak stateful yang diamati sebelumnya sebagai benih operasi.

Ini berlaku baik untuk operasi acak stateful yang digunakan untuk membuat dan menginisialisasi variabel, dan untuk operasi acak stateful yang digunakan dalam komputasi (seperti untuk lapisan putus sekolah).

Hasilkan tiga tensor acak untuk menunjukkan cara menggunakan alat ini untuk membuat kecocokan pembuatan angka acak stateful antara sesi dan eksekusi yang bersemangat.

random_tool = v1.keras.utils.DeterministicRandomTestTool()
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 = v1.keras.utils.DeterministicRandomTestTool()
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)

Namun, perhatikan bahwa dalam mode constant , karena b dan c dihasilkan dengan benih yang sama dan memiliki bentuk yang sama, mereka akan memiliki nilai yang persis sama.

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

Lacak pesanan

Jika Anda khawatir tentang beberapa nomor acak yang cocok dalam mode constant mengurangi kepercayaan Anda dalam tes kesetaraan numerik Anda (misalnya jika beberapa bobot mengambil inisialisasi yang sama), Anda dapat menggunakan mode num_random_ops untuk menghindari hal ini. Dalam mode num_random_ops , angka acak yang dihasilkan akan bergantung pada urutan operasi acak dalam program.

random_tool = v1.keras.utils.DeterministicRandomTestTool(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 = v1.keras.utils.DeterministicRandomTestTool(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)

Namun, perhatikan bahwa dalam mode ini, pembangkitan acak sensitif terhadap urutan program, sehingga nomor acak yang dihasilkan berikut tidak cocok.

random_tool = v1.keras.utils.DeterministicRandomTestTool(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 = v1.keras.utils.DeterministicRandomTestTool(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())

Untuk memungkinkan variasi debug karena urutan penelusuran, DeterministicRandomTestTool dalam mode num_random_ops memungkinkan Anda melihat berapa banyak operasi acak yang telah dilacak dengan properti operation_seed .

random_tool = v1.keras.utils.DeterministicRandomTestTool(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

Jika Anda perlu memperhitungkan urutan pelacakan yang bervariasi dalam pengujian Anda, Anda bahkan dapat mengatur operation_seed peningkatan otomatis secara eksplisit. Misalnya, Anda dapat menggunakan ini untuk membuat kecocokan pembuatan nomor acak di dua urutan program yang berbeda.

random_tool = v1.keras.utils.DeterministicRandomTestTool(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 = v1.keras.utils.DeterministicRandomTestTool(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

Namun, DeterministicRandomTestTool melarang penggunaan kembali benih operasi yang sudah digunakan, jadi pastikan urutan yang bertambah otomatis tidak dapat tumpang tindih. Ini karena eksekusi yang bersemangat menghasilkan angka yang berbeda untuk penggunaan lanjutan dari benih operasi yang sama sementara grafik dan sesi TF1 tidak, jadi meningkatkan kesalahan membantu menjaga sesi dan pembuatan nomor acak stateful yang bersemangat tetap sejalan.

random_tool = v1.keras.utils.DeterministicRandomTestTool(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 `DeterministicRandomTestTool` 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.

Memverifikasi Inferensi

Anda sekarang dapat menggunakan DeterministicRandomTestTool untuk memastikan model InceptionResnetV2 cocok dalam inferensi, bahkan saat menggunakan inisialisasi bobot acak. Untuk kondisi pengujian yang lebih kuat karena urutan program yang cocok, gunakan mode num_random_ops .

random_tool = v1.keras.utils.DeterministicRandomTestTool(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 = v1.keras.utils.DeterministicRandomTestTool(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 DeterministicRandomTestTool:
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Memverifikasi Pelatihan

Karena DeterministicRandomTestTool berfungsi untuk semua operasi acak stateful (termasuk inisialisasi bobot dan komputasi seperti lapisan putus), Anda juga dapat menggunakannya untuk memverifikasi kecocokan model dalam mode pelatihan. Anda dapat kembali menggunakan mode num_random_ops karena urutan program dari operasi acak stateful cocok.

random_tool = v1.keras.utils.DeterministicRandomTestTool(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/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 = v1.keras.utils.DeterministicRandomTestTool(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 DeterministicRandomTestTool
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Anda sekarang telah memverifikasi bahwa model InceptionResnetV2 berjalan penuh semangat dengan dekorator di sekitar tf.keras.layers.Layer secara numerik cocok dengan jaringan ramping yang berjalan di grafik dan sesi TF1.

Misalnya, memanggil layer InceptionResnetV2 secara langsung dengan inisialisasi variabel training=True interleaves dengan urutan dropout sesuai dengan urutan pembuatan jaringan.

Di sisi lain, pertama-tama letakkan dekorator tf.keras.layers.Layer dalam model fungsional Keras dan baru kemudian memanggil model dengan training=True sama dengan menginisialisasi semua variabel kemudian menggunakan lapisan dropout. Ini menghasilkan urutan penelusuran yang berbeda dan serangkaian nomor acak yang berbeda.

Namun, mode='constant' default tidak sensitif terhadap perbedaan dalam urutan penelusuran ini dan akan berlalu tanpa kerja ekstra bahkan saat menyematkan lapisan dalam model fungsional Keras.

random_tool = v1.keras.utils.DeterministicRandomTestTool()
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 = v1.keras.utils.DeterministicRandomTestTool()
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. '
/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)
Regularization loss: tf.Tensor(1.2239964, shape=(), dtype=float32)
# Verify that the regularization loss and output both match
# when using the DeterministicRandomTestTool
np.testing.assert_allclose(tf1_regularization_loss, tf2_regularization_loss.numpy(), **tol_dict)
np.testing.assert_allclose(tf1_output, tf2_output.numpy(), **tol_dict)

Langkah 3b atau 4b (opsional): Pengujian dengan pos pemeriksaan yang sudah ada sebelumnya

Setelah langkah 3 atau langkah 4 di atas, akan berguna untuk menjalankan tes kesetaraan numerik Anda saat memulai dari pos pemeriksaan berbasis nama yang sudah ada sebelumnya jika Anda memilikinya. Ini dapat menguji apakah pemuatan pos pemeriksaan lama Anda berfungsi dengan benar dan model itu sendiri berfungsi dengan benar. Panduan menggunakan kembali pos pemeriksaan TF1.x mencakup cara menggunakan kembali pos pemeriksaan TF1.x yang sudah ada sebelumnya dan mentransfernya ke pos pemeriksaan TF2.

Pengujian & Pemecahan Masalah Tambahan

Saat Anda menambahkan lebih banyak pengujian kesetaraan numerik, Anda juga dapat memilih untuk menambahkan pengujian yang memverifikasi kecocokan perhitungan gradien Anda (atau bahkan pembaruan pengoptimal Anda).

Backpropagation dan perhitungan gradien lebih rentan terhadap ketidakstabilan numerik floating point daripada model forward pass. Ini berarti bahwa karena tes ekivalensi Anda mencakup lebih banyak bagian pelatihan yang tidak terisolasi, Anda mungkin mulai melihat perbedaan numerik non-sepele antara berlari dengan penuh semangat dan grafik TF1 Anda. Ini mungkin disebabkan oleh pengoptimalan grafik TensorFlow yang melakukan hal-hal seperti mengganti subekspresi dalam grafik dengan operasi matematika yang lebih sedikit.

Untuk mengisolasi apakah hal ini mungkin terjadi, Anda dapat membandingkan kode TF1 Anda dengan komputasi TF2 yang terjadi di dalam tf.function (yang menerapkan pengoptimalan grafik seperti grafik TF1 Anda) alih-alih dengan perhitungan yang benar-benar bersemangat. Atau, Anda dapat mencoba menggunakan tf.config.optimizer.set_experimental_options untuk menonaktifkan pass pengoptimalan seperti "arithmetic_optimization" sebelum penghitungan TF1 Anda untuk melihat apakah hasilnya secara numerik mendekati hasil penghitungan TF2 Anda. Dalam latihan Anda yang sebenarnya, Anda disarankan untuk menggunakan tf.function dengan lintasan pengoptimalan yang diaktifkan untuk alasan kinerja, tetapi Anda mungkin merasa berguna untuk menonaktifkannya dalam pengujian unit ekuivalen numerik Anda.

Demikian pula, Anda mungkin juga menemukan bahwa pengoptimal tf.compat.v1.train dan pengoptimal TF2 memiliki properti numerik titik mengambang yang sedikit berbeda dari pengoptimal TF2, meskipun rumus matematika yang mereka wakili sama. Ini kemungkinan kecil menjadi masalah dalam latihan Anda, tetapi mungkin memerlukan toleransi numerik yang lebih tinggi dalam pengujian unit ekivalensi.