אימות נכונות ושקילות מספרית

הצג באתר TensorFlow.org הפעל בגוגל קולאב הצג ב-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.

אם אתה מכניס גוש לא טריוויאלי של קוד מעבר קדימה לתוך ה-shim, אתה רוצה לדעת שהוא מתנהג באותו אופן כפי שהוא התנהג ב-TF1.x. לדוגמה, שקול לנסות להכניס דגם שלם של TF-Slim Inception-Resnet-v2 לתוך ה-shim ככזה:

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

ה-scope הראשון ( assert_no_variable_creations() ) יעלה שגיאה מיד לאחר שתנסה ליצור משתנה בתוך ה-scope. זה מאפשר לך לבדוק את ה-stacktrace (ולהשתמש בניפוי באגים אינטראקטיבי) כדי להבין בדיוק אילו שורות קוד יצרו משתנה במקום לעשות שימוש חוזר באחד קיים.

ה-scope השני ( catch_and_raise_created_variables() ) יעלה חריגה בסוף ה-scope אם בסופו של דבר נוצרו משתנים. חריג זה יכלול את רשימת כל המשתנים שנוצרו ב-scope. זה שימושי כדי להבין מה הסט של כל המשקולות שהדגם שלך יוצר למקרה שתוכל לזהות דפוסים כלליים. עם זאת, זה פחות שימושי לזיהוי שורות הקוד המדויקות שבהן נוצרו המשתנים האלה.

השתמש בשני ההיקפים שלהלן כדי לוודא ששכבת InceptionResnetV2 מבוססת shim אינה יוצרת משתנים חדשים לאחר הקריאה הראשונה (ככל הנראה שימוש חוזר בהם).

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.layer בתוך variable_scope בשם, מה שגורם לשמות השכבות שנוצרו אוטומטית להגדיל את כל שיחת דגם. תקן זאת על ידי הכנסת שם tf.compat.v1.variable_scope בתוך השיטה המעוטרת ב-shim שעוטפת את כל השימוש שלך 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 '

לאחר מכן, עשה את אותו הדבר עבור השכבה העטופה ב-shim ב-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 מבוססת shim עוברת את המבחן הזה. עם זאת, במקרה שבו הם לא תואמים, אתה יכול להפעיל את זה דרך הבדל (טקסט או אחר) כדי לראות היכן ההבדלים.

זה יכול לספק רמז לאיזה חלק במודל אינו מתנהג כמצופה. עם ביצוע נלהב, אתה יכול להשתמש ב-pdb, באגים אינטראקטיביים ונקודות עצירה כדי לחפור בחלקים של המודל שנראים חשודים, ולנסות לעומק את מה שמשתבש.

פתרון תקלות

  • שימו לב היטב לשמות של כל משתנים שנוצרו ישירות על ידי קריאות 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 כאשר אתה מסיר מקורות אקראיות, ושכבת InceptionResnetV2 תואמת TF2 עוברת את המבחן.

אם אתה מבחין בתוצאות שונות עבור המודלים שלך, אתה יכול להשתמש בהדפסה או ב-pdb ובניפוי באגים אינטראקטיבי כדי לזהות היכן ומדוע התוצאות מתחילות להתפצל. ביצוע להוט יכול להקל משמעותית על כך. אתה יכול גם להשתמש בגישת אבלציה כדי להפעיל רק חלקים קטנים מהמודל על כניסות ביניים קבועות ולבודד את המקום שבו מתרחשת הסטייה.

באופן נוח, רשתות דקות רבות (ודגמים אחרים) חושפות גם נקודות קצה ביניים שתוכלו לחקור.

שלב 4: יישר יצירת מספרים אקראי, בדוק שקילות מספרית הן באימון והן בהסקה

השלב האחרון הוא לוודא שמודל TF2 תואם מבחינה מספרית למודל TF1.x, גם כאשר לוקחים בחשבון יצירת מספרים אקראית באתחול משתנה ובמעבר קדימה עצמו (כגון שכבות נשירה במהלך העברה קדימה).

אתה יכול לעשות זאת על ידי שימוש בכלי הבדיקה שלהלן כדי להפוך את הסמנטיקה של יצירת מספרים אקראית להתאמה בין גרפים/הפעלות TF1.x וביצוע נלהב.

גרפים/הפעלות מדור קודם של TF1 וביצוע נלהב של TF2 משתמשים בסמנטיקה שונה של יצירת מספרים אקראיים מצביים.

ב- tf.compat.v1.Session s, אם לא צוינו זרעים, יצירת המספרים האקראיים תלויה בכמה פעולות יש בגרף בזמן הוספת הפעולה האקראית, וכמה פעמים הגרף מופעל. בביצוע להוט, יצירת מספר אקראי מצבי תלוי בזרע הגלובלי, הזרע האקראי של הפעולה וכמה פעמים מופעלת הפעולה עם הפעולה עם הזרע האקראי הנתון. ראה tf.random.set_seed למידע נוסף.

המחלקה הבאה v1.keras.utils.DeterministicRandomTestTool מספקת scope manager 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, גם אם הנוסחאות המתמטיות שהם מייצגים זהות. סביר להניח שזו תהיה בעיה בריצות האימון שלך, אבל זה עשוי לדרוש סובלנות מספרית גבוהה יותר במבחני יחידת שקילות.