Google I/O एक लपेट है! TensorFlow सत्रों पर पकड़ बनाएं सत्र देखें

शुद्धता और संख्यात्मक तुल्यता की पुष्टि

TensorFlow.org पर देखें Google Colab में चलाएं गिटहब पर देखें नोटबुक डाउनलोड करें

अपने 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: सत्यापित करें कि चर केवल एक बार बनाए गए हैं

सबसे पहली चीज जो आपको सत्यापित करनी चाहिए, वह यह है कि आपने मॉडल को इस तरह से सही ढंग से बनाया है जो गलती से हर बार नए चर बनाने और उपयोग करने के बजाय प्रत्येक कॉल में चर का पुन: उपयोग करता है। उदाहरण के लिए, यदि आपका मॉडल एक नई केरस परत बनाता है या प्रत्येक फॉरवर्ड पास कॉल में 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.Variables का पुन: उपयोग किए बिना एक स्पष्ट tf.Variable कॉल का उपयोग करता है। इसे पहले चेक करके ठीक करें कि क्या यह नहीं बनाया गया है और फिर मौजूदा का पुन: उपयोग कर रहा है।
  2. यह हर बार सीधे फॉरवर्ड पास में एक केरस परत या मॉडल बनाता है (जैसा कि tf.compat.v1.layers के विपरीत)। इसे पहले चेक करके ठीक करें कि क्या यह नहीं बनाया गया है और फिर मौजूदा का पुन: उपयोग कर रहा है।
  3. यह tf.compat.v1.layers के शीर्ष पर बनाया गया है, लेकिन सभी compat.v1.layers को एक स्पष्ट नाम निर्दिष्ट करने में विफल रहता है या आपके compat.v1.layer उपयोग को एक नामित variable_scope के अंदर लपेटने में विफल रहता है, जिससे ऑटोजेनरेटेड लेयर नामों में वृद्धि होती है प्रत्येक मॉडल कॉल। अपने शिम-डेकोरेटेड मेथड के अंदर एक नामित tf.compat.v1.variable_scope डालकर इसे ठीक करें जो आपके सभी tf.compat.v1.layers उपयोग को लपेटता है।

चरण 2: जांचें कि चर संख्या, नाम और आकार मेल खाते हैं

दूसरा चरण यह सुनिश्चित करने के लिए है कि 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 परत इस परीक्षा को पास करती है। हालांकि, उस स्थिति में जहां वे मेल नहीं खाते हैं, आप इसे अंतर (पाठ या अन्य) के माध्यम से यह देखने के लिए चला सकते हैं कि अंतर कहां हैं।

यह एक सुराग प्रदान कर सकता है कि मॉडल का कौन सा हिस्सा अपेक्षित व्यवहार नहीं कर रहा है। उत्सुक निष्पादन के साथ आप मॉडल के उन हिस्सों में खुदाई करने के लिए पीडीबी, इंटरेक्टिव डिबगिंग और ब्रेकप्वाइंट का उपयोग कर सकते हैं जो संदिग्ध लगते हैं, और जो गलत हो रहा है उसे अधिक गहराई से डीबग करें।

समस्या निवारण

  • स्पष्ट tf.Variable कॉल और Keras लेयर्स/मॉडल द्वारा सीधे बनाए गए किसी भी चर के नामों पर ध्यान दें क्योंकि उनके चर नाम पीढ़ी के शब्दार्थ TF1.x ग्राफ़ और TF2 कार्यक्षमता जैसे उत्सुक निष्पादन और tf.function के बीच थोड़ा भिन्न हो सकते हैं, भले ही सब कुछ हो अन्य ठीक से काम कर रहा है। यदि आपके लिए यह मामला है, तो किसी भी थोड़े भिन्न नामकरण शब्दार्थ के लिए अपने परीक्षण को खाते में समायोजित करें।

  • आप कभी-कभी पा सकते हैं कि आपके प्रशिक्षण लूप के फ़ॉरवर्ड पास में बनाए गए tf.Variable s, tf.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 के बीच मेल खाती हैं, और TF2-संगत InceptionResnetV2 परत परीक्षण पास कर लेती है।

यदि आप अपने स्वयं के मॉडल के लिए परिणामों को अलग करते हुए देख रहे हैं, तो आप प्रिंटिंग या पीडीबी और इंटरेक्टिव डिबगिंग का उपयोग यह पहचानने के लिए कर सकते हैं कि परिणाम कहां और क्यों अलग होने लगते हैं। उत्सुक निष्पादन इसे काफी आसान बना सकता है। आप निश्चित मध्यवर्ती इनपुट पर मॉडल के केवल छोटे हिस्से को चलाने के लिए एक पृथक दृष्टिकोण का उपयोग कर सकते हैं और जहां विचलन होता है उसे अलग कर सकते हैं।

आसानी से, कई पतले जाल (और अन्य मॉडल) मध्यवर्ती समापन बिंदुओं को भी उजागर करते हैं जिनकी आप जांच कर सकते हैं।

चरण 4: यादृच्छिक संख्या पीढ़ी को संरेखित करें, प्रशिक्षण और अनुमान दोनों में संख्यात्मक समानता की जाँच करें

अंतिम चरण यह सत्यापित करना है कि TF2 मॉडल संख्यात्मक रूप से TF1.x मॉडल से मेल खाता है, भले ही वेरिएबल इनिशियलाइज़ेशन में यादृच्छिक संख्या पीढ़ी के लिए लेखांकन और फ़ॉरवर्ड पास में ही (जैसे फ़ॉरवर्ड पास के दौरान ड्रॉपआउट परतें)।

आप TF1.x ग्राफ़/सत्रों और उत्सुक निष्पादन के बीच यादृच्छिक संख्या पीढ़ी शब्दार्थ मिलान करने के लिए नीचे दिए गए परीक्षण उपकरण का उपयोग करके ऐसा कर सकते हैं।

TF1 लीगेसी ग्राफ़/सत्र और TF2 उत्सुक निष्पादन विभिन्न स्टेटफुल रैंडम नंबर जेनरेशन सेमेन्टिक्स का उपयोग करते हैं।

tf.compat.v1.Session s में, यदि कोई बीज निर्दिष्ट नहीं है, तो यादृच्छिक संख्या पीढ़ी इस बात पर निर्भर करती है कि उस समय ग्राफ़ में कितने ऑपरेशन हैं जब रैंडम ऑपरेशन जोड़ा जाता है, और ग्राफ़ कितनी बार चलाया जाता है। उत्सुक निष्पादन में, स्टेटफुल रैंडम नंबर जेनरेशन ग्लोबल सीड, ऑपरेशन रैंडम सीड पर निर्भर करता है, और दिए गए रैंडम सीड के साथ ऑपरेशन के साथ कितनी बार ऑपरेशन चलाया जाता है। अधिक जानकारी के लिए 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
प्लेसहोल्डर32 l10n-प्लेसहोल्डर
(<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())

ट्रेसिंग ऑर्डर के कारण डिबगिंग विविधताओं की अनुमति देने के लिए, num_random_ops मोड में DeterministicRandomTestTool आपको यह देखने की अनुमति देता है कि ऑपरेशन_सीड संपत्ति के साथ कितने यादृच्छिक 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 पहले से उपयोग किए गए ऑपरेशन बीजों का पुन: उपयोग करने की अनुमति नहीं देता है, इसलिए सुनिश्चित करें कि ऑटो-इंक्रीमेंट किए गए अनुक्रम ओवरलैप नहीं हो सकते हैं। ऐसा इसलिए है क्योंकि उत्सुक निष्पादन एक ही ऑपरेशन बीज के फॉलो-ऑन उपयोगों के लिए अलग-अलग संख्याएं उत्पन्न करता है जबकि टीएफ 1 ग्राफ और सत्र नहीं करते हैं, इसलिए त्रुटि उठाने से सत्र और उत्सुक राज्यपूर्ण यादृच्छिक संख्या पीढ़ी को लाइन में रखने में मदद मिलती है।

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 डेकोरेटर को केरस फंक्शनल मॉडल में रखना और उसके बाद ही मॉडल को training=True के साथ कॉल करना सभी वेरिएबल्स को इनिशियलाइज़ करने के बाद ड्रॉपआउट लेयर का उपयोग करने के बराबर है। यह एक अलग अनुरेखण क्रम और यादृच्छिक संख्याओं का एक अलग सेट उत्पन्न करता है।

हालाँकि, डिफ़ॉल्ट mode='constant' ट्रेसिंग क्रम में इन अंतरों के प्रति संवेदनशील नहीं है और केरस कार्यात्मक मॉडल में परत को एम्बेड करते समय भी अतिरिक्त काम के बिना गुजर जाएगा।

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 ग्राफ़ की तरह ग्राफ़ ऑप्टिमाइज़ेशन पास लागू करता है) के अंदर हो रहा है, न कि विशुद्ध रूप से उत्सुक गणना के लिए। वैकल्पिक रूप से, आप अपने TF1 गणना से पहले "arithmetic_optimization" अंकगणित_अनुकूलन" जैसे अनुकूलन पास को अक्षम करने के लिए tf.config.optimizer.set_experimental_options का उपयोग करके देख सकते हैं कि परिणाम संख्यात्मक रूप से आपके TF2 गणना परिणामों के करीब है या नहीं। आपके वास्तविक प्रशिक्षण में यह अनुशंसा की जाती है कि आप प्रदर्शन कारणों से सक्षम अनुकूलन पास के साथ tf.function का उपयोग करें, लेकिन आपको अपने संख्यात्मक तुल्यता इकाई परीक्षणों में उन्हें अक्षम करना उपयोगी हो सकता है।

इसी तरह, आप यह भी पा सकते हैं कि tf.compat.v1.train ऑप्टिमाइज़र और TF2 ऑप्टिमाइज़र में TF2 ऑप्टिमाइज़र की तुलना में फ़्लोटिंग पॉइंट न्यूमेरिक्स गुण थोड़े भिन्न होते हैं, भले ही वे जिस गणितीय फ़ार्मुलों का प्रतिनिधित्व कर रहे हों, वे समान हों। यह आपके प्रशिक्षण रन में कोई समस्या होने की संभावना नहीं है, लेकिन इसके लिए तुल्यता इकाई परीक्षणों में उच्च संख्यात्मक सहिष्णुता की आवश्यकता हो सकती है।