איתור באגים TF2 Migrated Training Pipeline

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

מחברת זו מדגים כיצד לנפות באגים בצינור הדרכה בעת הגירה ל-TF2. הוא מורכב מהרכיבים הבאים:

  1. שלבים מוצעים ודוגמאות קוד לאיתור באגים בצינור הדרכה
  2. כלים לניפוי באגים
  3. משאבים קשורים אחרים

הנחה אחת היא שיש לך קוד TF1.x ומודלים מאומנים להשוואה, ואתה רוצה לבנות מודל TF2 שישיג דיוק אימות דומה.

מחברת זו אינה מכסה בעיות ביצועי ניפוי באגים עבור מהירות אימון/הסקת מסקנות או שימוש בזיכרון.

זרימת עבודה של ניפוי באגים

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

  1. תקן שגיאות קומפילציה וזמן ריצה

  2. אימות מעבר בודד קדימה ( במדריך נפרד)

    א. במכשיר מעבד יחיד

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

    ב. במכשיר GPU/TPU יחיד

    ג. עם אסטרטגיות ריבוי מכשירים

  3. אימות שוויון מספרי של אימון מודל עבור כמה שלבים (דגימות קוד זמינות להלן)

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

    • חישוב הפסדים
    • מדדים
    • קצב למידה
    • חישוב ועדכון שיפוע

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

    ג. במכשיר GPU/TPU יחיד

    ד. עם אסטרטגיות ריבוי מכשירים (בדוק את המבוא עבור MultiProcessRunner בתחתית)

  4. בדיקת כיסוי מקצה לקצה על מערך נתונים אמיתי

    א. בדוק התנהגויות אימון עם TensorBoard

    • השתמש בכלי אופטימיזציה פשוטים כגון SGD ואסטרטגיות הפצה פשוטות כגון tf.distribute.OneDeviceStrategy תחילה
    • מדדי אימון
    • מדדי הערכה
    • להבין מהי הסובלנות הסבירה לאקראיות אינהרנטית

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

    ג. בדוק שקילות בעת שימוש בדיוק מעורב

  5. מדדי מוצר נוספים

להכין

pip uninstall -y -q tensorflow
# Install tf-nightly as the DeterministicRandomTestTool is only available in
# Tensorflow 2.8
pip install -q tf-nightly

אימות מעבר בודד קדימה

אימות מעבר בודד קדימה, כולל טעינת מחסום, מכוסה בקולאב אחר.

import sys
import unittest
import numpy as np

import tensorflow as tf
import tensorflow.compat.v1 as v1

אימון מודל אימות שקילות מספרית עבור כמה שלבים

הגדר תצורת מודל והכן מערך נתונים מזויף.

params = {
    'input_size': 3,
    'num_classes': 3,
    'layer_1_size': 2,
    'layer_2_size': 2,
    'num_train_steps': 100,
    'init_lr': 1e-3,
    'end_lr': 0.0,
    'decay_steps': 1000,
    'lr_power': 1.0,
}

# make a small fixed dataset
fake_x = np.ones((2, params['input_size']), dtype=np.float32)
fake_y = np.zeros((2, params['num_classes']), dtype=np.int32)
fake_y[0][0] = 1
fake_y[1][1] = 1

step_num = 3

הגדר את מודל TF1.x.

# Assume there is an existing TF1.x model using estimator API
# Wrap the model_fn to log necessary tensors for result comparison
class SimpleModelWrapper():
  def __init__(self):
    self.logged_ops = {}
    self.logs = {
        'step': [],
        'lr': [],
        'loss': [],
        'grads_and_vars': [],
        'layer_out': []}

  def model_fn(self, features, labels, mode, params):
      out_1 = tf.compat.v1.layers.dense(features, units=params['layer_1_size'])
      out_2 = tf.compat.v1.layers.dense(out_1, units=params['layer_2_size'])
      logits = tf.compat.v1.layers.dense(out_2, units=params['num_classes'])
      loss = tf.compat.v1.losses.softmax_cross_entropy(labels, logits)

      # skip EstimatorSpec details for prediction and evaluation 
      if mode == tf.estimator.ModeKeys.PREDICT:
          pass
      if mode == tf.estimator.ModeKeys.EVAL:
          pass
      assert mode == tf.estimator.ModeKeys.TRAIN

      global_step = tf.compat.v1.train.get_or_create_global_step()
      lr = tf.compat.v1.train.polynomial_decay(
        learning_rate=params['init_lr'],
        global_step=global_step,
        decay_steps=params['decay_steps'],
        end_learning_rate=params['end_lr'],
        power=params['lr_power'])

      optmizer = tf.compat.v1.train.GradientDescentOptimizer(lr)
      grads_and_vars = optmizer.compute_gradients(
          loss=loss,
          var_list=graph.get_collection(
              tf.compat.v1.GraphKeys.TRAINABLE_VARIABLES))
      train_op = optmizer.apply_gradients(
          grads_and_vars,
          global_step=global_step)

      # log tensors
      self.logged_ops['step'] = global_step
      self.logged_ops['lr'] = lr
      self.logged_ops['loss'] = loss
      self.logged_ops['grads_and_vars'] = grads_and_vars
      self.logged_ops['layer_out'] = {
          'layer_1': out_1,
          'layer_2': out_2,
          'logits': logits}

      return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)

  def update_logs(self, logs):
    for key in logs.keys():
      model_tf1.logs[key].append(logs[key])

scope() v1.keras.utils.DeterministicRandomTestTool

הכלי מספק שני מצבי בדיקה:

  1. constant שמשתמש באותו זרע עבור כל פעולה בודדת, לא משנה כמה פעמים הוא נקרא ו,
  2. num_random_ops שמשתמש במספר הפעולות האקראיות הסטציונליות שנצפו קודם לכן בתור תחילת הפעולה.

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

random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
WARNING:tensorflow:From /tmp/ipykernel_26769/2689227634.py:1: The name tf.keras.utils.DeterministicRandomTestTool is deprecated. Please use tf.compat.v1.keras.utils.DeterministicRandomTestTool instead.

הפעל את מודל TF1.x במצב גרף. אסוף נתונים סטטיסטיים עבור 3 שלבי האימון הראשונים להשוואת שוויון מספרי.

with random_tool.scope():
  graph = tf.Graph()
  with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
    model_tf1 = SimpleModelWrapper()
    # build the model
    inputs = tf.compat.v1.placeholder(tf.float32, shape=(None, params['input_size']))
    labels = tf.compat.v1.placeholder(tf.float32, shape=(None, params['num_classes']))
    spec = model_tf1.model_fn(inputs, labels, tf.estimator.ModeKeys.TRAIN, params)
    train_op = spec.train_op

    sess.run(tf.compat.v1.global_variables_initializer())
    for step in range(step_num):
      # log everything and update the model for one step
      logs, _ = sess.run(
          [model_tf1.logged_ops, train_op],
          feed_dict={inputs: fake_x, labels: fake_y})
      model_tf1.update_logs(logs)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/keras/legacy_tf_layers/core.py:261: UserWarning: `layer.apply` is deprecated and will be removed in a future version. Please use `layer.__call__` method instead.
  return layer.apply(inputs)
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:15: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  from ipykernel import kernelapp as app
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: `tf.layers.dense` is deprecated and will be removed in a future version. Please use `tf.keras.layers.Dense` instead.
  app.launch_new_instance()

הגדר את דגם TF2.

class SimpleModel(tf.keras.Model):
  def __init__(self, params, *args, **kwargs):
    super(SimpleModel, self).__init__(*args, **kwargs)
    # define the model
    self.dense_1 = tf.keras.layers.Dense(params['layer_1_size'])
    self.dense_2 = tf.keras.layers.Dense(params['layer_2_size'])
    self.out = tf.keras.layers.Dense(params['num_classes'])
    learning_rate_fn = tf.keras.optimizers.schedules.PolynomialDecay(
      initial_learning_rate=params['init_lr'],
      decay_steps=params['decay_steps'],
      end_learning_rate=params['end_lr'],
      power=params['lr_power'])  
    self.optimizer = tf.keras.optimizers.SGD(learning_rate_fn)
    self.compiled_loss = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
    self.logs = {
        'lr': [],
        'loss': [],
        'grads': [],
        'weights': [],
        'layer_out': []}

  def call(self, inputs):
    out_1 = self.dense_1(inputs)
    out_2 = self.dense_2(out_1)
    logits = self.out(out_2)
    # log output features for every layer for comparison
    layer_wise_out = {
        'layer_1': out_1,
        'layer_2': out_2,
        'logits': logits}
    self.logs['layer_out'].append(layer_wise_out)
    return logits

  def train_step(self, data):
    x, y = data
    with tf.GradientTape() as tape:
      logits = self(x)
      loss = self.compiled_loss(y, logits)
    grads = tape.gradient(loss, self.trainable_weights)
    # log training statistics
    step = self.optimizer.iterations.numpy()
    self.logs['lr'].append(self.optimizer.learning_rate(step).numpy())
    self.logs['loss'].append(loss.numpy())
    self.logs['grads'].append(grads)
    self.logs['weights'].append(self.trainable_weights)
    # update model
    self.optimizer.apply_gradients(zip(grads, self.trainable_weights))
    return

הפעל את דגם TF2 במצב להוט. אסוף נתונים סטטיסטיים עבור 3 שלבי האימון הראשונים להשוואת שוויון מספרי.

random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
with random_tool.scope():
  model_tf2 = SimpleModel(params)
  for step in range(step_num):
    model_tf2.train_step([fake_x, fake_y])

השווה שוויון מספרי עבור כמה צעדי אימון ראשונים.

אתה יכול גם לעיין במחברת אימות נכונות ושקילות מספרית לקבלת עצות נוספות לגבי שקילות מספרית.

np.testing.assert_allclose(model_tf1.logs['lr'], model_tf2.logs['lr'])
np.testing.assert_allclose(model_tf1.logs['loss'], model_tf2.logs['loss'])
for step in range(step_num):
  for name in model_tf1.logs['layer_out'][step]:
    np.testing.assert_allclose(
        model_tf1.logs['layer_out'][step][name],
        model_tf2.logs['layer_out'][step][name])

בדיקות יחידה

ישנם כמה סוגים של בדיקות יחידות שיכולות לעזור בניפוי באגים בקוד ההעברה שלך.

  1. אימות מעבר בודד קדימה
  2. אימון מודל אימות שקילות מספרית עבור כמה שלבים
  3. ביצועי מסקנות בנצ'מרק
  4. המודל המאומן עושה תחזיות נכונות על נקודות נתונים קבועות ופשוטות

אתה יכול להשתמש ב- @parameterized.parameters כדי לבדוק מודלים עם תצורות שונות. פרטים עם דוגמת קוד .

שימו לב שאפשר להפעיל ממשקי API של הפעלה וביצוע נלהב באותו מקרה בדיקה. קטעי הקוד למטה מראים כיצד.

import unittest

class TestNumericalEquivalence(unittest.TestCase):

  # copied from code samples above
  def setup(self):
    # record statistics for 100 training steps
    step_num = 100

    # setup TF 1 model
    random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
    with random_tool.scope():
      # run TF1.x code in graph mode with context management
      graph = tf.Graph()
      with graph.as_default(), tf.compat.v1.Session(graph=graph) as sess:
        self.model_tf1 = SimpleModelWrapper()
        # build the model
        inputs = tf.compat.v1.placeholder(tf.float32, shape=(None, params['input_size']))
        labels = tf.compat.v1.placeholder(tf.float32, shape=(None, params['num_classes']))
        spec = self.model_tf1.model_fn(inputs, labels, tf.estimator.ModeKeys.TRAIN, params)
        train_op = spec.train_op

        sess.run(tf.compat.v1.global_variables_initializer())
        for step in range(step_num):
          # log everything and update the model for one step
          logs, _ = sess.run(
              [self.model_tf1.logged_ops, train_op],
              feed_dict={inputs: fake_x, labels: fake_y})
          self.model_tf1.update_logs(logs)

    # setup TF2 model
    random_tool = v1.keras.utils.DeterministicRandomTestTool(mode='num_random_ops')
    with random_tool.scope():
      self.model_tf2 = SimpleModel(params)
      for step in range(step_num):
        self.model_tf2.train_step([fake_x, fake_y])

  def test_learning_rate(self):
    np.testing.assert_allclose(
        self.model_tf1.logs['lr'],
        self.model_tf2.logs['lr'])

  def test_training_loss(self):
    # adopt different tolerance strategies before and after 10 steps
    first_n_step = 10

    # abosolute difference is limited below 1e-5
    # set `equal_nan` to be False to detect potential NaN loss issues
    abosolute_tolerance = 1e-5
    np.testing.assert_allclose(
        actual=self.model_tf1.logs['loss'][:first_n_step],
        desired=self.model_tf2.logs['loss'][:first_n_step],
        atol=abosolute_tolerance,
        equal_nan=False)

    # relative difference is limited below 5%
    relative_tolerance = 0.05
    np.testing.assert_allclose(self.model_tf1.logs['loss'][first_n_step:],
                               self.model_tf2.logs['loss'][first_n_step:],
                               rtol=relative_tolerance,
                               equal_nan=False)

כלי איתור באגים

tf.print

tf.print לעומת print/logging.info

  • עם ארגומנטים הניתנים להגדרה, tf.print יכול להציג באופן רקורסיבי את האלמנטים הראשונים והאחרונים של כל מימד עבור טנזורים מודפסים. בדוק את מסמכי ה-API לקבלת פרטים.
  • לביצוע נלהב, גם print וגם tf.print מדפיסים את הערך של הטנזור. אבל print עשויה לכלול העתקה ממכשיר למארח, מה שעלול להאט את הקוד שלך.
  • עבור מצב גרף כולל שימוש בתוך tf.function , עליך להשתמש ב- tf.print כדי להדפיס את ערך הטנזור האמיתי. tf.print מורכב לאופ בגרף, בעוד ש- print ו- logging.info נרשמים רק בזמן המעקב, וזה לרוב לא מה שאתה רוצה.
  • tf.print תומך גם בהדפסת טנסורים מרוכבים כמו tf.RaggedTensor ו- tf.sparse.SparseTensor .
  • אתה יכול גם להשתמש בהתקשרות חוזרת כדי לנטר מדדים ומשתנים. אנא בדוק כיצד להשתמש בהתקשרויות חוזרות מותאמות אישית עם יומנים dict ותכונת self.model .

tf.print לעומת הדפסה בתוך tf.function

# `print` prints info of tensor object
# `tf.print` prints the tensor value
@tf.function
def dummy_func(num):
  num += 1
  print(num)
  tf.print(num)
  return num

_ = dummy_func(tf.constant([1.0]))

# Output:
# Tensor("add:0", shape=(1,), dtype=float32)
# [2]
Tensor("add:0", shape=(1,), dtype=float32)
[2]

tf.distribute.Strategy

  • אם הפונקציה tf.print tf.function על העובדים, למשל בעת שימוש ב- TPUStrategy או ParameterServerStrategy , עליך לבדוק את יומני שרת עובד/פרמטרים כדי למצוא את הערכים המודפסים.
  • עבור print או logging.info , יומנים יודפסו על המתאם בעת שימוש ב- ParameterServerStrategy , ויומנים יודפסו על STDOUT ב-worker0 בעת שימוש ב-TPUs.

tf.keras.Model

  • בעת שימוש במודלים רציפים ופונקציונליים API, אם ברצונך להדפיס ערכים, למשל, קלט מודל או תכונות ביניים לאחר כמה שכבות, יש לך את האפשרויות הבאות.
    1. כתוב שכבה מותאמת אישית tf.print את התשומות.
    2. כלול את תפוקות הביניים שברצונך לבדוק בתפוקות הדגם.
  • לשכבות tf.keras.layers.Lambda .למבדה יש ​​מגבלות (דה) בהמשכה. כדי להימנע מבעיות של טעינת נקודות ביקורת, כתוב במקום זאת שכבת משנה מותאמת אישית. בדוק את מסמכי ה-API לקבלת פרטים נוספים.
  • אינך יכול להדפיס פלטי ביניים ב- tf.print אם tf.keras.callbacks.LambdaCallback לך גישה לערכים בפועל, אלא רק לאובייקטי הטנזור הסמליים של Keras.

אפשרות 1: כתוב שכבה מותאמת אישית

class PrintLayer(tf.keras.layers.Layer):
  def call(self, inputs):
    tf.print(inputs)
    return inputs

def get_model():
  inputs = tf.keras.layers.Input(shape=(1,))
  out_1 = tf.keras.layers.Dense(4)(inputs)
  out_2 = tf.keras.layers.Dense(1)(out_1)
  # use custom layer to tf.print intermediate features
  out_3 = PrintLayer()(out_2)
  model = tf.keras.Model(inputs=inputs, outputs=out_3)
  return model

model = get_model()
model.compile(optimizer="adam", loss="mse")
model.fit([1, 2, 3], [0.0, 0.0, 1.0])
[[-0.327884018]
 [-0.109294668]
 [-0.218589336]]
1/1 [==============================] - 0s 280ms/step - loss: 0.6077
<keras.callbacks.History at 0x7f63d46bf190>

אפשרות 2: כלול את תפוקות הביניים שברצונך לבדוק בפלטי הדגם.

שים לב שבמקרה כזה, ייתכן שתצטרך כמה התאמות אישיות כדי להשתמש ב- Model.fit .

def get_model():
  inputs = tf.keras.layers.Input(shape=(1,))
  out_1 = tf.keras.layers.Dense(4)(inputs)
  out_2 = tf.keras.layers.Dense(1)(out_1)
  # include intermediate values in model outputs
  model = tf.keras.Model(
      inputs=inputs,
      outputs={
          'inputs': inputs,
          'out_1': out_1,
          'out_2': out_2})
  return model

pdb

אתה יכול להשתמש ב- pdb גם בטרמינל וגם ב-Colab כדי לבדוק ערכי ביניים לאיתור באגים.

דמיין גרף עם TensorBoard

אתה יכול לבחון את גרף TensorFlow עם TensorBoard . TensorBoard נתמך גם על colab . TensorBoard הוא כלי נהדר לדמיין סיכומים. אתה יכול להשתמש בו כדי להשוות קצב למידה, משקלי מודל, סולם שיפוע, מדדי אימון/אימות, או אפילו מודל פלטי ביניים בין מודל TF1.x למודל TF2 שעבר תהליך ההדרכה ולראות אם הערכים נראים כמצופה.

TensorFlow Profiler

TensorFlow Profiler יכול לעזור לך לדמיין את ציר הזמן של הביצוע ב-GPUs/TPUs. אתה יכול לבדוק את ההדגמה של Colab עבור השימוש הבסיסי שלו.

MultiProcessRunner

MultiProcessRunner הוא כלי שימושי בעת איתור באגים עם MultiWorkerMirroredStrategy ו- ParameterServerStrategy. אתה יכול להסתכל על דוגמה קונקרטית זו לשימוש בה.

במיוחד עבור המקרים של שתי האסטרטגיות הללו, מומלץ 1) לא רק לבצע בדיקות יחידה כדי לכסות את הזרימה שלהן, 2) אלא גם לנסות לשחזר כשלים באמצעותה בבדיקת יחידה כדי להימנע מהשקת עבודה מבוזרת אמיתית בכל פעם שהם מנסים. תיקון.