التحقق من الصحة والتكافؤ العددي

عرض على TensorFlow.org تشغيل في Google Colab عرض على جيثب تحميل دفتر

عند ترحيل كود TensorFlow الخاص بك من TF1.x إلى TF2 ، فمن الممارسات الجيدة التأكد من أن الكود الذي تم ترحيله يتصرف بنفس الطريقة في TF2 كما فعلت في TF1.x.

يغطي هذا الدليل أمثلة كود الترحيل مع tf.compat.v1.keras.utils.track_tf1_style_variables النمذجة shim المطبقة على 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() ) سيرفع خطأ فورًا بمجرد محاولة إنشاء متغير داخل النطاق. يتيح لك ذلك فحص تتبع المكدس (واستخدام التصحيح التفاعلي) لمعرفة بالضبط ما هي سطور التعليمات البرمجية التي أنشأت متغيرًا بدلاً من إعادة استخدام متغير موجود.

النطاق الثاني ( 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 أو variable_scope استخدام compat.v1.layer . كل مكالمة نموذجية. أصلح هذا عن طريق وضع 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 القائمة على الرقائق هذا الاختبار. ومع ذلك ، في حالة عدم تطابقهما ، يمكنك تشغيله من خلال فرق (نص أو غيره) لمعرفة مكان الاختلافات.

يمكن أن يوفر هذا دليلًا على أي جزء من النموذج لا يتصرف كما هو متوقع. مع التنفيذ الحثيث ، يمكنك استخدام pdb ، وتصحيح الأخطاء التفاعلي ، ونقاط التوقف للبحث في أجزاء النموذج التي تبدو مشبوهة ، وتصحيح الأخطاء بمزيد من العمق.

استكشاف الأخطاء وإصلاحها

  • انتبه جيدًا لأسماء أي متغيرات تم إنشاؤها مباشرة بواسطة tf.Variable صريح المكالمات المتغيرة وطبقات / نماذج Keras حيث قد تختلف دلالات توليد الاسم المتغير قليلاً بين الرسوم البيانية TF1.x ووظيفة TF2 مثل التنفيذ tf.function حتى لو كان كل شيء آخر يعمل بشكل صحيح. إذا كان هذا هو الحال بالنسبة لك ، فاضبط اختبارك ليأخذ في الاعتبار أي دلالات تسمية مختلفة قليلاً.

  • قد تجد أحيانًا أن tf.Variable s أو tf.keras.layers.Layer s أو tf.keras.Model s الذي تم إنشاؤه في المرور الأمامي لحلقة التدريب الخاصة بك مفقود من قائمة متغيرات 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 الاختبار.

إذا كنت تراقب النتائج المتباينة للنماذج الخاصة بك ، فيمكنك استخدام الطباعة أو pdb والتصحيح التفاعلي لتحديد أين ولماذا تبدأ النتائج في التباعد. يمكن أن يجعل التنفيذ الحريص هذا أسهل بكثير. يمكنك أيضًا استخدام نهج الاجتثاث لتشغيل أجزاء صغيرة فقط من النموذج على مدخلات وسيطة ثابتة وعزل مكان حدوث الاختلاف.

بشكل ملائم ، تعرض العديد من الشبكات الرفيعة (ونماذج أخرى) أيضًا نقاط نهاية وسيطة يمكنك استكشافها.

الخطوة 4: قم بمحاذاة توليد الأرقام العشوائية ، والتحقق من التكافؤ العددي في كل من التدريب والاستدلال

تتمثل الخطوة الأخيرة في التحقق من أن نموذج TF2 يتطابق عدديًا مع نموذج TF1.x ، حتى عند حساب توليد الأرقام العشوائية في التهيئة المتغيرة وفي التمرير الأمامي نفسه (مثل طبقات التسرب أثناء التمرير الأمامي).

يمكنك القيام بذلك باستخدام أداة الاختبار أدناه لجعل دلالات توليد الأرقام العشوائية تتطابق بين الرسوم البيانية TF1.x / الجلسات والتنفيذ الحثيث.

تستخدم الرسوم البيانية / الجلسات القديمة TF1 والتنفيذ الحثيث لـ TF2 دلالات مختلفة لتوليد الأرقام العشوائية.

في tf.compat.v1.Session ، إذا لم يتم تحديد بذور ، يعتمد إنشاء الأرقام العشوائية على عدد العمليات الموجودة في الرسم البياني في وقت إضافة العملية العشوائية ، وعدد مرات تشغيل الرسم البياني. في التنفيذ الحثيث ، يعتمد إنشاء الأرقام العشوائية ذات الحالة على البذرة العالمية ، والعملية الأولية العشوائية ، وكم مرة يتم تشغيل العملية مع الأصل العشوائي المحدد. راجع tf.random.set_seed لمزيد من المعلومات.

توفر فئة v1.keras.utils.DeterministicRandomTestTool التالية scope() الذي يمكن أن يجعل العمليات العشوائية ذات الحالة تستخدم نفس المصدر عبر كل من الرسوم البيانية / جلسات 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

إذا كنت بحاجة إلى حساب ترتيب التتبع المتغير في اختباراتك ، فيمكنك حتى تعيين 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 interleaves المتغير التهيئة بترتيب التسرب وفقًا لأمر إنشاء الشبكة.

من ناحية أخرى ، قم أولاً بوضع tf.keras.layers.Layer decorator في نموذج وظيفي Keras ثم استدعاء النموذج training=True يعادل تهيئة جميع المتغيرات ثم استخدام طبقة التسرب. ينتج عن هذا ترتيب تتبع مختلف ومجموعة مختلفة من الأرقام العشوائية.

ومع ذلك ، فإن 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)

الخطوة 3 ب أو 4 ب (اختياري): الاختبار باستخدام نقاط التفتيش الموجودة مسبقًا

بعد الخطوة 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 ، حتى لو كانت الصيغ الرياضية التي تمثلها هي نفسها. من غير المرجح أن تكون هذه مشكلة في عمليات التدريب الخاصة بك ، ولكنها قد تتطلب تسامحًا رقميًا أعلى في اختبارات وحدة التكافؤ.