اعتبارسنجی صحت و معادل سازی عددی

مشاهده در TensorFlow.org در Google Colab اجرا شود در GitHub مشاهده کنید دانلود دفترچه یادداشت

هنگامی که کد TensorFlow خود را از TF1.x به TF2 منتقل می کنید، این یک تمرین خوب است که اطمینان حاصل کنید که کد منتقل شده شما در TF2 همان رفتاری را دارد که در TF1.x انجام داد.

این راهنما نمونه‌های کد مهاجرت را با tf.compat.v1.keras.utils.track_tf1_style_variables مدل‌سازی شیم اعمال شده روی tf.keras.layers.Layer . راهنمای نقشه برداری مدل را بخوانید تا در مورد شیم های مدل سازی TF2 اطلاعات بیشتری کسب کنید.

این راهنما روش‌هایی را که می‌توانید برای موارد زیر استفاده کنید، جزئیات می‌دهد:

  • صحت نتایج به دست آمده از مدل های آموزشی را با استفاده از کد مهاجرت تایید کنید
  • معادل عددی کد خود را در سراسر نسخه های TensorFlow اعتبار سنجی کنید

برپایی

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.

اگر تکه‌ای از کد عبور پیش‌فرض را در شیم قرار می‌دهید، می‌خواهید بدانید که مانند TF1.x رفتار می‌کند. به عنوان مثال، سعی کنید کل مدل TF-Slim Inception-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_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.

همانطور که اتفاق می افتد، این لایه در واقع به خوبی کار می کند (کامل با ردیابی دقیق از دست دادن منظم).

با این حال، این چیزی نیست که بخواهید آن را بدیهی بدانید. مراحل زیر را دنبال کنید تا مطمئن شوید که واقعاً مانند TF1.x رفتار می کند تا معادل عددی کامل را رعایت کنید. این مراحل همچنین می‌توانند به شما کمک کنند تا مشخص کنید چه بخشی از پاس رو به جلو باعث ایجاد واگرایی از TF1.x می‌شود (تشخیص دهید که آیا واگرایی در پاس رو به جلو مدل به‌جای بخش دیگری از مدل ایجاد می‌شود).

مرحله 1: تأیید کنید که متغیرها فقط یک بار ایجاد می شوند

اولین چیزی که باید تأیید کنید این است که مدل را به درستی ساخته اید به گونه ای که از متغیرها در هر تماس مجدد استفاده می کند نه اینکه هر بار به طور تصادفی از متغیرهای جدید ایجاد و استفاده کنید. برای مثال، اگر مدل شما یک لایه Keras جدید ایجاد می‌کند یا tf.Variable را در هر فراخوانی فوروارد فراخوانی می‌کند، به احتمال زیاد نمی‌تواند متغیرها را بگیرد و هر بار متغیرهای جدید ایجاد کند.

در زیر دو حوزه مدیریت زمینه وجود دارد که می‌توانید از آنها برای شناسایی زمانی که مدل شما متغیرهای جدیدی ایجاد می‌کند استفاده کنید و اشکال‌زدایی کنید که کدام بخشی از مدل این کار را انجام می‌دهد.

@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() ) بلافاصله پس از ایجاد یک متغیر در محدوده، یک خطا ایجاد می کند. این به شما این امکان را می دهد که stacktrace را بررسی کنید (و از اشکال زدایی تعاملی استفاده کنید) تا دقیقا متوجه شوید که کدام خطوط کد یک متغیر را به جای استفاده مجدد از یک متغیر موجود ایجاد کرده است.

دامنه دوم ( 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/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 '

در مثال زیر، مشاهده کنید که چگونه این دکوراتورها بر روی لایه‌ای کار می‌کنند که هر بار به‌جای استفاده مجدد از وزن‌های موجود، وزن‌های جدیدی ایجاد می‌کند.

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

می‌توانید لایه را با اطمینان از اینکه فقط یک بار وزن‌ها را ایجاد می‌کند و سپس هر بار دوباره از آنها استفاده می‌کند، تعمیر کنید.

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. شما را در داخل compat.v1.layer نامگذاری variable_scope قرار دهد، که باعث می شود نام لایه های تولید شده به صورت خودکار افزایش یابد. هر مدل تماس بگیرید با قرار دادن یک tf.compat.v1.variable_scope به نام tf.compat.v1.variable_scope در متد شیم تزئین شده خود که تمام استفاده از tf.compat.v1.layers شما را در بر می گیرد، این مشکل را برطرف کنید.

مرحله 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-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

لایه InceptionResnetV2 مبتنی بر شیم این آزمایش را با موفقیت پشت سر می گذارد. با این حال، در مواردی که آنها مطابقت ندارند، می‌توانید آن را از طریق یک تفاوت (متن یا موارد دیگر) اجرا کنید تا ببینید تفاوت‌ها کجاست.

این می تواند سرنخی از این که کدام قسمت از مدل آنطور که انتظار می رود رفتار نمی کند ارائه دهد. با اجرای مشتاقانه می‌توانید از pdf، اشکال‌زدایی تعاملی، و نقاط شکست برای حفاری در بخش‌هایی از مدل که مشکوک به نظر می‌رسند، استفاده کنید و آنچه را که اشتباه می‌کند را با عمق بیشتری عیب‌یابی کنید.

عیب یابی

  • به نام هر متغیری که مستقیماً توسط فراخوانی‌های tf.Variable صریح و لایه‌ها/مدل‌های Keras ایجاد می‌شود، دقت کنید زیرا معنای تولید نام متغیر آنها ممکن است بین نمودارهای TF1.x و عملکرد TF2 مانند اجرای مشتاق و tf.function کمی متفاوت باشد، حتی اگر همه چیز باشد. دیگری به درستی کار می کند اگر این مورد برای شماست، آزمون خود را طوری تنظیم کنید که معنای نامگذاری کمی متفاوت باشد.

  • ممکن است گاهی متوجه شوید که tf.Variabletf.keras.layers.Layer s، یا 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 مطابقت دارند و لایه InceptionResnetV2 سازگار با TF2 آزمایش را با موفقیت پشت سر می گذارد.

اگر در حال مشاهده نتایج متفاوت برای مدل‌های خود هستید، می‌توانید از چاپ یا pdf و اشکال‌زدایی تعاملی برای تشخیص اینکه کجا و چرا نتایج شروع به واگرایی می‌کنند استفاده کنید. اجرای مشتاقانه می تواند این کار را به میزان قابل توجهی آسان کند. همچنین می‌توانید از یک روش فرسایش برای اجرای تنها بخش‌های کوچکی از مدل بر روی ورودی‌های میانی ثابت و جداسازی محل وقوع واگرایی استفاده کنید.

به‌راحتی، بسیاری از شبکه‌های باریک (و سایر مدل‌ها) نقاط پایانی میانی را نیز نشان می‌دهند که می‌توانید آنها را بررسی کنید.

مرحله 4: تولید اعداد تصادفی را تراز کنید، هم ارزی عددی را هم در آموزش و هم در استنتاج بررسی کنید

مرحله آخر تأیید این است که مدل TF2 از نظر عددی با مدل TF1.x مطابقت دارد، حتی زمانی که تولید اعداد تصادفی در مقدار دهی اولیه متغیر و در خود گذر پیش‌رو در نظر گرفته می‌شود (مانند لایه‌های حذفی در طول عبور به جلو).

می توانید این کار را با استفاده از ابزار تست زیر انجام دهید تا معنایی تولید اعداد تصادفی بین نمودارها/جلسات TF1.x و اجرای مشتاق مطابقت داشته باشد.

نمودارها/جلسات میراث TF1 و اجرای مشتاق TF2 از معناشناسی متفاوت تولید اعداد تصادفی حالت دار استفاده می کنند.

در tf.compat.v1.Session s، اگر هیچ دانه ای مشخص نشده باشد، تولید اعداد تصادفی به تعداد عملیات در نمودار در زمان اضافه شدن عملیات تصادفی و تعداد دفعات اجرای نمودار بستگی دارد. در اجرای مشتاق، تولید اعداد تصادفی حالت دار به دانه جهانی، دانه تصادفی عملیات، و چند بار اجرای عملیات با دانه تصادفی داده شده بستگی دارد. برای اطلاعات بیشتر به tf.random.set_seed مراجعه کنید.

کلاس v1.keras.utils.DeterministicRandomTestTool زیر یک scope() مدیر متن ارائه می دهد که می تواند عملیات تصادفی حالت دار را از همان seed در هر دو نمودار/جلسات TF1 و اجرای مشتاق استفاده کند.

این ابزار دو حالت تست را ارائه می دهد:

  1. constant که از یک دانه برای هر عملیات استفاده می کند مهم نیست که چند بار فراخوانی شده باشد و
  2. num_random_ops که از تعداد عملیات تصادفی حالتی مشاهده شده قبلی به عنوان دانه عملیات استفاده می کند.

این هم برای عملیات تصادفی حالتی که برای ایجاد و مقداردهی اولیه متغیرها استفاده می شود و هم برای عملیات تصادفی حالتی که در محاسبات استفاده می شود (مانند لایه های حذفی) صدق می کند.

سه تانسور تصادفی ایجاد کنید تا نشان دهید چگونه از این ابزار برای تطبیق تولید اعداد تصادفی حالتی بین جلسات و اجرای مشتاق استفاده کنید.

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)

اما توجه داشته باشید که در حالت constant ، چون b و c با دانه یکسان تولید شده اند و شکل یکسانی دارند، دقیقاً مقادیر مشابهی خواهند داشت.

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

ترتیب ردیابی

اگر نگران تطبیق برخی اعداد تصادفی در حالت constant هستید که اطمینان شما را در آزمون هم ارزی عددی کاهش می‌دهد (مثلاً اگر چندین وزن مقدار اولیه یکسانی داشته باشند)، می‌توانید از حالت num_random_ops برای جلوگیری از این امر استفاده کنید. در حالت 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:
    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)

اما توجه داشته باشید که در این حالت تولید تصادفی به ترتیب برنامه حساس است و بنابراین اعداد تصادفی تولید شده زیر مطابقت ندارند.

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

برای اجازه دادن به اشکال‌زدایی تغییرات به دلیل ترتیب ردیابی، DeterministicRandomTestTool در حالت num_random_ops به شما امکان می‌دهد ببینید که چه تعداد عملیات تصادفی با ویژگی 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

اگر می‌خواهید ترتیب ردیابی متفاوتی را در آزمایش‌های خود در نظر بگیرید، حتی می‌توانید عملیات_seed افزایش خودکار 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

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

با این حال، DeterministicRandomTestTool استفاده مجدد از دانه‌های عملیاتی که قبلاً استفاده شده را مجاز نمی‌داند، بنابراین مطمئن شوید که توالی‌های افزایش‌یافته خودکار نمی‌توانند همپوشانی داشته باشند. این به این دلیل است که اجرای مشتاق، اعداد متفاوتی را برای استفاده‌های بعدی از یک عملیات مشابه تولید می‌کند، در حالی که نمودارها و جلسات TF1 این کار را نمی‌کنند، بنابراین بالا بردن یک خطا به حفظ نشست و تولید اعداد تصادفی حالت مشتاق در یک خط کمک می‌کند.

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.

تایید استنباط

اکنون می توانید از DeterministicRandomTestTool استفاده کنید تا مطمئن شوید که مدل InceptionResnetV2 در استنتاج مطابقت دارد، حتی زمانی که از مقدار اولیه وزن تصادفی استفاده می کنید. برای شرایط تست قوی تر به دلیل مطابقت با ترتیب برنامه، از حالت 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)

تایید آموزش

از آنجایی که DeterministicRandomTestTool برای همه عملیات تصادفی حالت دار (شامل مقداردهی اولیه وزن و محاسبات مانند لایه های حذفی) کار می کند، می توانید از آن برای تأیید مطابقت مدل ها در حالت آموزش نیز استفاده کنید. می توانید دوباره از حالت 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=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)

اکنون تأیید کرده‌اید که مدل InceptionResnetV2 که مشتاقانه با دکوراتورهای اطراف tf.keras.layers.Layer ، از نظر عددی با شبکه باریکی که در نمودارها و جلسات TF1 اجرا می‌شود مطابقت دارد.

برای مثال، فراخوانی مستقیم لایه InceptionResnetV2 با training=True ، مقدار دهی اولیه متغیر را با ترتیب انصراف مطابق با ترتیب ایجاد شبکه به هم می زند.

از طرف دیگر، ابتدا قرار دادن tf.keras.layers.Layer decorator در یک مدل کاربردی Keras و تنها پس از آن فراخوانی مدل با training=True معادل مقداردهی اولیه همه متغیرها و سپس استفاده از لایه dropout است. این یک ترتیب ردیابی متفاوت و مجموعه متفاوتی از اعداد تصادفی ایجاد می کند.

با این حال، mode='constant' به این تفاوت‌ها در ترتیب ردیابی حساس نیست و حتی در هنگام جاسازی لایه در یک مدل عملکردی 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)

مرحله 3b یا 4b (اختیاری): آزمایش با نقاط بازرسی از قبل موجود

پس از مرحله 3 یا مرحله 4 در بالا، در صورت وجود تست‌های هم ارزی عددی، می‌تواند مفید باشد. این می‌تواند هم اینکه بارگیری نقطه بازرسی قدیمی شما درست کار می‌کند و هم اینکه خود مدل درست کار می‌کند را آزمایش کند. راهنمای استفاده مجدد از نقاط بازرسی TF1.x نحوه استفاده مجدد از پست های بازرسی TF1.x از قبل موجود و انتقال آنها را به پست های بازرسی TF2 را پوشش می دهد.

تست و عیب یابی اضافی

همانطور که تست‌های هم ارزی عددی بیشتری اضافه می‌کنید، می‌توانید آزمایشی اضافه کنید که مطابقت محاسبات گرادیان (یا حتی به‌روزرسانی‌های بهینه‌ساز شما) را تأیید می‌کند.

پس انتشار و محاسبه گرادیان بیشتر مستعد ناپایداری های عددی ممیز شناور هستند تا گذرهای رو به جلو مدل. این بدان معناست که از آنجایی که تست‌های هم ارزی شما بخش‌های غیر ایزوله بیشتری از تمرین شما را پوشش می‌دهد، ممکن است تفاوت‌های عددی غیر پیش پاافتاده را بین دویدن کاملا مشتاقانه و نمودارهای TF1 خود مشاهده کنید. این ممکن است ناشی از بهینه‌سازی‌های گراف TensorFlow باشد که کارهایی مانند جایگزینی عبارات فرعی در یک نمودار با عملیات‌های ریاضی کمتر انجام می‌دهند.

برای جداسازی اینکه آیا این احتمال وجود دارد، می‌توانید کد TF1 خود را با محاسبات TF2 که در داخل یک tf.function اتفاق می‌افتد (که پاس‌های بهینه‌سازی نمودار را مانند نمودار TF1 شما اعمال می‌کند) مقایسه کنید تا با یک محاسبات کاملا مشتاق. از طرف دیگر، می‌توانید از tf.config.optimizer.set_experimental_options برای غیرفعال کردن پاس‌های بهینه‌سازی مانند "arithmetic_optimization" قبل از محاسبه TF1 خود استفاده کنید تا ببینید آیا نتیجه از نظر عددی به نتایج محاسبات TF2 نزدیک‌تر می‌شود یا خیر. در اجراهای آموزشی واقعی خود، توصیه می‌شود از tf.function با پاس‌های بهینه‌سازی فعال به دلایل عملکردی استفاده کنید، اما ممکن است غیرفعال کردن آنها در آزمون‌های واحد هم ارزی عددی مفید باشد.

به طور مشابه، ممکن است متوجه شوید که بهینه‌سازهای tf.compat.v1.train و بهینه‌سازهای TF2 دارای ویژگی‌های عددی ممیز شناور کمی متفاوت از بهینه‌سازهای TF2 هستند، حتی اگر فرمول‌های ریاضی که آنها ارائه می‌کنند یکسان باشند. احتمال کمتری وجود دارد که این مشکل در اجراهای آموزشی شما باشد، اما ممکن است به تحمل عددی بالاتری در آزمون‌های واحد هم ارزی نیاز داشته باشد.