דף זה תורגם על ידי Cloud Translation API.
Switch to English

כתיבת לולאת אימונים מאפס

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

להכין

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

מבוא

Keras מספקת אימונים ולולאות ברירת מחדל, fit() evaluate() . השימוש בהם מכוסה במדריך הדרכה והערכה בשיטות המובנות .

אם ברצונך להתאים אישית את אלגוריתם הלמידה של המודל שלך ובכל זאת למנף את הנוחות של fit() (למשל, לאימון GAN באמצעות fit() ), תוכל לסווג את מחלקת Model ולהטמיע את שיטת train_step() , אשר נקרא שוב ושוב במהלך fit() . זה מכוסה במדריך התאמה אישית של מה שקורה fit() .

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

שימוש ב- GradientTape : דוגמא ראשונה מקצה לקצה

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

בואו ניקח בחשבון מודל MNIST פשוט:

inputs = keras.Input(shape=(784,), name="digits")
x1 = layers.Dense(64, activation="relu")(inputs)
x2 = layers.Dense(64, activation="relu")(x1)
outputs = layers.Dense(10, name="predictions")(x2)
model = keras.Model(inputs=inputs, outputs=outputs)

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

ראשית, נצטרך אופטימיזציה, פונקציית אובדן ומערך נתונים:

# Instantiate an optimizer.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Prepare the training dataset.
batch_size = 64
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = np.reshape(x_train, (-1, 784))
x_test = np.reshape(x_test, (-1, 784))
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

הנה לולאת האימונים שלנו:

  • אנו פותחים לולאה for שמתחוללת על פני תקופות
  • עבור כל עידן, אנו פותחים לולאה for המתחזרת על מערך הנתונים, בקבוצות
  • עבור כל אצווה, אנו פותחים היקף GradientTape()
  • בתוך היקף זה אנו קוראים למודל (מעבר קדימה) ומחושבים את ההפסד
  • מחוץ לתחום, אנו מאחזרים את שיפועי משקולות המודל ביחס לאובדן
  • לבסוף, אנו משתמשים בכלי האופטימיזציה כדי לעדכן את משקולות הדגם בהתבסס על השיפועים
epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):

        # Open a GradientTape to record the operations run
        # during the forward pass, which enables auto-differentiation.
        with tf.GradientTape() as tape:

            # Run the forward pass of the layer.
            # The operations that the layer applies
            # to its inputs are going to be recorded
            # on the GradientTape.
            logits = model(x_batch_train, training=True)  # Logits for this minibatch

            # Compute the loss value for this minibatch.
            loss_value = loss_fn(y_batch_train, logits)

        # Use the gradient tape to automatically retrieve
        # the gradients of the trainable variables with respect to the loss.
        grads = tape.gradient(loss_value, model.trainable_weights)

        # Run one step of gradient descent by updating
        # the value of the variables to minimize the loss.
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %s samples" % ((step + 1) * 64))

Start of epoch 0
Training loss (for one batch) at step 0: 118.1961
Seen so far: 64 samples
Training loss (for one batch) at step 200: 2.1943
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.9606
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.9748
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.3943
Seen so far: 51264 samples

Start of epoch 1
Training loss (for one batch) at step 0: 0.4881
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.5267
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.6100
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.3300
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.3622
Seen so far: 51264 samples

טיפול ברמה נמוכה של מדדים

בואו נוסיף ניטור מדדים ללולאה בסיסית זו.

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

  • מיידי את המדד בתחילת הלולאה
  • התקשר ל- metric.update_state() אחרי כל אצווה
  • התקשר ל- metric.result() כאשר עליך להציג את הערך הנוכחי של המדד
  • התקשר ל- metric.reset_states() כאשר עליך לנקות את מצב המדד (בדרך כלל בסוף תקופה)

בואו נשתמש בידע זה לחישוב SparseCategoricalAccuracy על נתוני אימות בסוף כל תקופה:

# Get model
inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu", name="dense_1")(inputs)
x = layers.Dense(64, activation="relu", name="dense_2")(x)
outputs = layers.Dense(10, name="predictions")(x)
model = keras.Model(inputs=inputs, outputs=outputs)

# Instantiate an optimizer to train the model.
optimizer = keras.optimizers.SGD(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)

# Prepare the metrics.
train_acc_metric = keras.metrics.SparseCategoricalAccuracy()
val_acc_metric = keras.metrics.SparseCategoricalAccuracy()

# Prepare the training dataset.
batch_size = 64
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.shuffle(buffer_size=1024).batch(batch_size)

# Prepare the validation dataset.
# Reserve 10,000 samples for validation.
x_val = x_train[-10000:]
y_val = y_train[-10000:]
x_train = x_train[:-10000]
y_train = y_train[:-10000]
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(64)

הנה לולאת ההדרכה וההערכה שלנו:

import time

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    start_time = time.time()

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        with tf.GradientTape() as tape:
            logits = model(x_batch_train, training=True)
            loss_value = loss_fn(y_batch_train, logits)
        grads = tape.gradient(loss_value, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

        # Update training metric.
        train_acc_metric.update_state(y_batch_train, logits)

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %d samples" % ((step + 1) * 64))

    # Display metrics at the end of each epoch.
    train_acc = train_acc_metric.result()
    print("Training acc over epoch: %.4f" % (float(train_acc),))

    # Reset training metrics at the end of each epoch
    train_acc_metric.reset_states()

    # Run a validation loop at the end of each epoch.
    for x_batch_val, y_batch_val in val_dataset:
        val_logits = model(x_batch_val, training=False)
        # Update val metrics
        val_acc_metric.update_state(y_batch_val, val_logits)
    val_acc = val_acc_metric.result()
    val_acc_metric.reset_states()
    print("Validation acc: %.4f" % (float(val_acc),))
    print("Time taken: %.2fs" % (time.time() - start_time))

Start of epoch 0
Training loss (for one batch) at step 0: 88.3728
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.3446
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.7409
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.4098
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.7732
Seen so far: 51264 samples
Training acc over epoch: 0.7515
Validation acc: 0.8552
Time taken: 5.60s

Start of epoch 1
Training loss (for one batch) at step 0: 0.6751
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.5519
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.6730
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.2807
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.6229
Seen so far: 51264 samples
Training acc over epoch: 0.8577
Validation acc: 0.8908
Time taken: 5.62s

זירוז שלב האימון שלך עם tf.function

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

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

אתה יכול לאסוף לגרף סטטי כל פונקציה שלוקחת טנזרים כקלט. פשוט הוסיפו עליו @tf.function , כך:

@tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss_value = loss_fn(y, logits)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))
    train_acc_metric.update_state(y, logits)
    return loss_value

בואו נעשה את אותו הדבר עם שלב ההערכה:

@tf.function
def test_step(x, y):
    val_logits = model(x, training=False)
    val_acc_metric.update_state(y, val_logits)

עכשיו, בואו נפעיל מחדש את לולאת האימונים שלנו עם שלב ההדרכה המהולל הזה:

import time

epochs = 2
for epoch in range(epochs):
    print("\nStart of epoch %d" % (epoch,))
    start_time = time.time()

    # Iterate over the batches of the dataset.
    for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
        loss_value = train_step(x_batch_train, y_batch_train)

        # Log every 200 batches.
        if step % 200 == 0:
            print(
                "Training loss (for one batch) at step %d: %.4f"
                % (step, float(loss_value))
            )
            print("Seen so far: %d samples" % ((step + 1) * 64))

    # Display metrics at the end of each epoch.
    train_acc = train_acc_metric.result()
    print("Training acc over epoch: %.4f" % (float(train_acc),))

    # Reset training metrics at the end of each epoch
    train_acc_metric.reset_states()

    # Run a validation loop at the end of each epoch.
    for x_batch_val, y_batch_val in val_dataset:
        test_step(x_batch_val, y_batch_val)

    val_acc = val_acc_metric.result()
    val_acc_metric.reset_states()
    print("Validation acc: %.4f" % (float(val_acc),))
    print("Time taken: %.2fs" % (time.time() - start_time))

Start of epoch 0
Training loss (for one batch) at step 0: 0.7984
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.6274
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.2540
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.5385
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.2974
Seen so far: 51264 samples
Training acc over epoch: 0.8866
Validation acc: 0.9082
Time taken: 1.35s

Start of epoch 1
Training loss (for one batch) at step 0: 0.5614
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.7772
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.5483
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.6677
Seen so far: 38464 samples
Training loss (for one batch) at step 800: 0.5454
Seen so far: 51264 samples
Training acc over epoch: 0.9011
Validation acc: 0.9202
Time taken: 1.10s

הרבה יותר מהר, לא?

טיפול ברמה נמוכה של הפסדים במעקב אחר המודל

שכבות ומודלים עוקבים באופן רקורסיבי אחר כל ההפסדים שנוצרו במהלך self.add_loss(value) קדימה על ידי שכבות self.add_loss(value) . הרשימה המתקבלת של ערכי אובדן סקלרי זמינה באמצעות model.losses המאפיין. model.losses בסוף המעבר קדימה.

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

שקול שכבה זו, היוצרת אובדן הסדרת פעילות:

class ActivityRegularizationLayer(layers.Layer):
    def call(self, inputs):
        self.add_loss(1e-2 * tf.reduce_sum(inputs))
        return inputs

בואו לבנות מודל ממש פשוט המשתמש בו:

inputs = keras.Input(shape=(784,), name="digits")
x = layers.Dense(64, activation="relu")(inputs)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = layers.Dense(64, activation="relu")(x)
outputs = layers.Dense(10, name="predictions")(x)

model = keras.Model(inputs=inputs, outputs=outputs)

הנה איך שצריך להיראות בשלב האימון שלנו:

@tf.function
def train_step(x, y):
    with tf.GradientTape() as tape:
        logits = model(x, training=True)
        loss_value = loss_fn(y, logits)
        # Add any extra losses created during the forward pass.
        loss_value += sum(model.losses)
    grads = tape.gradient(loss_value, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))
    train_acc_metric.update_state(y, logits)
    return loss_value

סיכום

עכשיו אתה יודע כל מה שיש לדעת על שימוש בלולאות אימון מובנות וכתיבה משלך מאפס.

לסיום, הנה דוגמה פשוטה מקצה לקצה הקושרת את כל מה שלמדת במדריך זה: DCGAN המאומן בספרות MNIST.

דוגמא מקצה לקצה: לולאת אימון של GAN מאפס

יתכן שאתה מכיר רשתות נגדיות גנריות (GANs). GANs יכולים ליצור תמונות חדשות שנראות כמעט אמיתיות, על ידי לימוד ההתפלגות הסמויה של מערך אימונים של תמונות ("המרחב הסמוי" של התמונות).

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

לולאת אימון של GAN נראית כך:

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

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

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

בואו לממש את לולאת האימון הזו. ראשית, צור את המפלה שנועד לסווג ספרות מזויפות לעומת ספרות אמיתיות:

discriminator = keras.Sequential(
    [
        keras.Input(shape=(28, 28, 1)),
        layers.Conv2D(64, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(128, (3, 3), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.GlobalMaxPooling2D(),
        layers.Dense(1),
    ],
    name="discriminator",
)
discriminator.summary()
Model: "discriminator"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 14, 14, 64)        640       
_________________________________________________________________
leaky_re_lu (LeakyReLU)      (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 7, 7, 128)         73856     
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 7, 7, 128)         0         
_________________________________________________________________
global_max_pooling2d (Global (None, 128)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 129       
=================================================================
Total params: 74,625
Trainable params: 74,625
Non-trainable params: 0
_________________________________________________________________

אז בואו ניצור רשת גנרטורים, שהופכת וקטורים סמויים ליציאות של צורה (28, 28, 1) (המייצגות ספרות MNIST):

latent_dim = 128

generator = keras.Sequential(
    [
        keras.Input(shape=(latent_dim,)),
        # We want to generate 128 coefficients to reshape into a 7x7x128 map
        layers.Dense(7 * 7 * 128),
        layers.LeakyReLU(alpha=0.2),
        layers.Reshape((7, 7, 128)),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),
        layers.LeakyReLU(alpha=0.2),
        layers.Conv2D(1, (7, 7), padding="same", activation="sigmoid"),
    ],
    name="generator",
)

הנה החלק המרכזי: לולאת האימונים. כפי שאתה יכול לראות זה די פשוט. פונקציית שלב האימון אורכת 17 שורות בלבד.

# Instantiate one optimizer for the discriminator and another for the generator.
d_optimizer = keras.optimizers.Adam(learning_rate=0.0003)
g_optimizer = keras.optimizers.Adam(learning_rate=0.0004)

# Instantiate a loss function.
loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)


@tf.function
def train_step(real_images):
    # Sample random points in the latent space
    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))
    # Decode them to fake images
    generated_images = generator(random_latent_vectors)
    # Combine them with real images
    combined_images = tf.concat([generated_images, real_images], axis=0)

    # Assemble labels discriminating real from fake images
    labels = tf.concat(
        [tf.ones((batch_size, 1)), tf.zeros((real_images.shape[0], 1))], axis=0
    )
    # Add random noise to the labels - important trick!
    labels += 0.05 * tf.random.uniform(labels.shape)

    # Train the discriminator
    with tf.GradientTape() as tape:
        predictions = discriminator(combined_images)
        d_loss = loss_fn(labels, predictions)
    grads = tape.gradient(d_loss, discriminator.trainable_weights)
    d_optimizer.apply_gradients(zip(grads, discriminator.trainable_weights))

    # Sample random points in the latent space
    random_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))
    # Assemble labels that say "all real images"
    misleading_labels = tf.zeros((batch_size, 1))

    # Train the generator (note that we should *not* update the weights
    # of the discriminator)!
    with tf.GradientTape() as tape:
        predictions = discriminator(generator(random_latent_vectors))
        g_loss = loss_fn(misleading_labels, predictions)
    grads = tape.gradient(g_loss, generator.trainable_weights)
    g_optimizer.apply_gradients(zip(grads, generator.trainable_weights))
    return d_loss, g_loss, generated_images

בואו לאמן את ה- GAN שלנו על ידי קריאה חוזרת ונשנית ל- train_step על קבוצות תמונות.

מכיוון שהמפלה והגנרטור שלנו הם קונבנטות, אתה רוצה להריץ את הקוד הזה ב- GPU.

import os

# Prepare the dataset. We use both the training & test MNIST digits.
batch_size = 64
(x_train, _), (x_test, _) = keras.datasets.mnist.load_data()
all_digits = np.concatenate([x_train, x_test])
all_digits = all_digits.astype("float32") / 255.0
all_digits = np.reshape(all_digits, (-1, 28, 28, 1))
dataset = tf.data.Dataset.from_tensor_slices(all_digits)
dataset = dataset.shuffle(buffer_size=1024).batch(batch_size)

epochs = 1  # In practice you need at least 20 epochs to generate nice digits.
save_dir = "./"

for epoch in range(epochs):
    print("\nStart epoch", epoch)

    for step, real_images in enumerate(dataset):
        # Train the discriminator & generator on one batch of real images.
        d_loss, g_loss, generated_images = train_step(real_images)

        # Logging.
        if step % 200 == 0:
            # Print metrics
            print("discriminator loss at step %d: %.2f" % (step, d_loss))
            print("adversarial loss at step %d: %.2f" % (step, g_loss))

            # Save one generated image
            img = tf.keras.preprocessing.image.array_to_img(
                generated_images[0] * 255.0, scale=False
            )
            img.save(os.path.join(save_dir, "generated_img" + str(step) + ".png"))

        # To limit execution time we stop after 10 steps.
        # Remove the lines below to actually train the model!
        if step > 10:
            break

Start epoch 0
discriminator loss at step 0: 0.68
adversarial loss at step 0: 0.69

זהו זה! תקבל ספרות MNIST מזויפות שנראות יפה לאחר כ- 30 שנות אימון ב- GPU של Colab.