منبع اصلی نوشتار زیر در این لینک قرار دارد

بازنویسی عملکرد تابع fit برای آموزش یک شبکه GAN

در این مطلب می خواهیم نمونه‌ای از نحوه بازنویسی training step که درون تابع fit در کلاس Model در تنسورفلو/کراس وجود دارد را ببینم و سپس با این بازنویسی یک مدل GAN را بسته بندی کنیم و آموزش دهیم.

آشنایی با تابع fit

زمانی که شما در حال انجام یک کار supervised learning هستید، شما می توانید از تابع fit استفاده کنید و همه چیز به سادگی انجام خواهد شد.

زمانی هم که شما نیاز داشته باشید تا حلقه گام های آموزش یا training loop را خودتان از اول بنویسید، آنگاه از GradientTape استفاده خواهید کرد و کنترل همه جزئیات را در دست می گیرید.

اما اگر شما الگوریتم آموزش ویژه و متفاوتی در نظر داشته باشید و هنوز بخواهید از تابع fit و مزایای آن (مانند callback ها) استفاده کنید، چه؟ راه حل چیست؟

یکی از اصول طراحی Keras با عنوان progressive disclosure of complexity شناخته می شود؛ طبق این اصل، شما همیشه باید قادر باشید تا به سطوح کاری زیرین کتابخانه به شیوه تدریجی دسترسی پیدا کنید. در واقع اگر مورد کار شما دقیقاً با تعریف های پایه ای کتابخانه نیست، برای رفع نیاز خود شما نباید از قله انتزاع به دره پیچیدگی های سطح پایین سقوط کنید. شما در هر مرحله باید بتوانید به آن مقداری از جزئیات که برای تغییر نیاز دارید دسترسی داشته باشید.

زمانی که شما می خواهید عملکرد fit را شخصی سازی کنید، شما در عمل باید این تابع از کلاس Model را که مسئول اجرای قدم های آموزش مدل است را بازنویسی کنید. این تابع برای دسته یا batch از داده صدا زده می شود. پس از بازنویسی این تابع از کلاس Model ، این تابع می تواند به طور معمول از کد شما صدا زده شود تا الگوریتم شخصی شما را روی داده اجرا کند تا آموزش شبکه انجام شود.

توجه کنید که این الگو شما را از استفاده مدل های Functional API با Keras منع نمی کند و به طور مثال می توانید در مدل Sequential خود استفاده کنید.

تنظیمات این مطلب

توضیحات و کدی که در این مطلب وجود دارد، TensorFlow نسخه ۲.۲ و بالاتر را نیاز دارد.

import tensorflow as tf
from tensorflow import keras

یک مثال ساده بازنویسی train_step

با یک مثال ساده بازنویسی تابع fit را شروع کنیم:

  • ما یک کلاس مدل تنسورفلوی جدید می سازیم که از keras.Model ارث بری می کند.
  • ما فقط تابع عضو train_step(self, data) را بازنویسی می کنیم (override).
  • ما یک dictionary را باز می گردانیم که شامل نام متریک و معیارها (شامل loss) و مقادیر مرتبط با آنهاست.

 

آرگومان ورودی data همان چیزی که است به عنوان داده آموزش به تابع fit داده می شود.

در بدنه تابع train_step ما یک آموزش و به روز رسانی وزن ساده را پیاده سازی می کنیم، شبیه به آنچه شما از قبل و در مفاهیم شبکه های عصبی عمیق، با آن آشنا هستید. نکته مهم آن است که ما loss را با self.compiled_loss محاسبه می کنیم (که توابع loss موجود در مدل را که با فراخوانی ()compile معرفی شده اند را پوشش می دهد).

به همین شیوه، ما با فراخوانی self.compiled_metrics.update_state(y, y_pred) مقادیر متریک و معیارهای داده شده در ()compile را به روز رسانی می کنیم و با self.metrics آنها را دریافت می کنیم.

class CustomModel(keras.Model):
    def train_step(self, data):
        # Unpack the data. Its structure depends on your model and
        # on what you pass to `fit()`.
        x, y = data

        with tf.GradientTape() as tape:
            y_pred = self(x, training=True)  # Forward pass
            # Compute the loss value
            # (the loss function is configured in `compile()`)
            loss = self.compiled_loss(y, y_pred, regularization_losses=self.losses)

        # Compute gradients
        trainable_vars = self.trainable_variables
        gradients = tape.gradient(loss, trainable_vars)
        # Update weights
        self.optimizer.apply_gradients(zip(gradients, trainable_vars))
        # Update metrics (includes the metric that tracks the loss)
        self.compiled_metrics.update_state(y, y_pred)
        # Return a dict mapping metric names to current value
        return {m.name: m.result() for m in self.metrics}

بیایید این کد را استفاده کنیم:

import numpy as np

# Construct and compile an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(optimizer="adam", loss="mse", metrics=["mae"])

# Just use `fit` as usual
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.fit(x, y, epochs=3)

Epoch 1/3
32/32 [==============================] - 0s 644us/step - loss: 0.3338 - mae: 0.4729
Epoch 2/3
32/32 [==============================] - 0s 538us/step - loss: 0.2229 - mae: 0.3752
Epoch 3/3
32/32 [==============================] - 0s 442us/step - loss: 0.2140 - mae: 0.3709

<tensorflow.python.keras.callbacks.History at 0x1448cbc10>

مثالی از بازنویسی گام ارزیابی یا test_step

حالا اگر بخواهید در آموزش مدل با فراخوانی ()model.evaluate عملکرد خاصی انجام شود چه؟ آنگاه شما تابع test_step را بازنویسی خواهید کرد. این کار می تواند به سادگی و به صورت زیر انجام شود:

class CustomModel(keras.Model):
    def test_step(self, data):
        # Unpack the data
        x, y = data
        # Compute predictions
        y_pred = self(x, training=False)
        # Updates the metrics tracking the loss
        self.compiled_loss(y, y_pred, regularization_losses=self.losses)
        # Update the metrics.
        self.compiled_metrics.update_state(y, y_pred)
        # Return a dict mapping metric names to current value.
        # Note that it will include the loss (tracked in self.metrics).
        return {m.name: m.result() for m in self.metrics}


# Construct an instance of CustomModel
inputs = keras.Input(shape=(32,))
outputs = keras.layers.Dense(1)(inputs)
model = CustomModel(inputs, outputs)
model.compile(loss="mse", metrics=["mae"])

# Evaluate with our custom test_step
x = np.random.random((1000, 32))
y = np.random.random((1000, 1))
model.evaluate(x, y)

32/32 [==============================] - 0s 510us/step - loss: 0.3044 - mae: 0.4464

[0.2880896329879761, 0.43386828899383545]

 

مثال پیاده سازی کلاس GAN با بازنویسی فرآیند fit

بیایید یک GAN را به طور کامل در یک کلاس Model بگنجانیم و سرتاسر فرآیند آموزش آن را در همان کلاس تعریف کنیم.

در این مثال در نظر داشته باشید که این موارد را نیاز داریم:

  • یک شبکه generator قرار است تصاویر ۲۸x28x1 تولید کند.
  • یک شبکه discriminator قرار است تصاویر با ابعاد ۲۸x28x1 را در دو دسته واقعی و غیرواقعی یا “fake” و “real” دسته بندی کند.
  • هر کدام از آن دو به طور جداگانه یک Optimizer دارند.
  • یک تابع خطا یا loss برای آموزش دادن شبکه discriminator .

from tensorflow.keras import layers

# Create the discriminator
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",
)

# Create the generator
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",
)

 

و حالا در اینجا یک کلاس GAN با مشخصات کامل را با بازنویسی ()compile و train_step تعریف می کنیم که در بخش گام آموزش تنها با ۱۷ خط الگوریتم آموزش GAN را پیاده کرده ایم.

class GAN(keras.Model):
    def __init__(self, discriminator, generator, latent_dim):
        super(GAN, self).__init__()
        self.discriminator = discriminator
        self.generator = generator
        self.latent_dim = latent_dim

    def compile(self, d_optimizer, g_optimizer, loss_fn):
        super(GAN, self).compile()
        self.d_optimizer = d_optimizer
        self.g_optimizer = g_optimizer
        self.loss_fn = loss_fn

    def train_step(self, real_images):
        if isinstance(real_images, tuple):
            real_images = real_images[0]
        # Sample random points in the latent space
        batch_size = tf.shape(real_images)[0]
        random_latent_vectors = tf.random.normal(shape=(batch_size, self.latent_dim))

        # Decode them to fake images
        generated_images = self.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((batch_size, 1))], axis=0
        )
        # Add random noise to the labels - important trick!
        labels += 0.05 * tf.random.uniform(tf.shape(labels))

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

        # Sample random points in the latent space
        random_latent_vectors = tf.random.normal(shape=(batch_size, self.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 = self.discriminator(self.generator(random_latent_vectors))
            g_loss = self.loss_fn(misleading_labels, predictions)
        grads = tape.gradient(g_loss, self.generator.trainable_weights)
        self.g_optimizer.apply_gradients(zip(grads, self.generator.trainable_weights))
        return {"d_loss": d_loss, "g_loss": g_loss}

 

بیایید تعریف بالا را آزمایش و استفاده کنیم:

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

gan = GAN(discriminator=discriminator, generator=generator, latent_dim=latent_dim)
gan.compile(
    d_optimizer=keras.optimizers.Adam(learning_rate=0.0003),
    g_optimizer=keras.optimizers.Adam(learning_rate=0.0003),
    loss_fn=keras.losses.BinaryCrossentropy(from_logits=True),
)

# To limit the execution time, we only train on 100 batches. You can train on
# the entire dataset. You will need about 20 epochs to get nice results.
gan.fit(dataset.take(100), epochs=1)

100/100 [==============================] - 55s 545ms/step - d_loss: 0.4021 - g_loss: 0.9081

<tensorflow.python.keras.callbacks.History at 0x1448bf810>

 

امیدواریم با به کارگیری این آموزش، نحوه پیاده سازی مدل های ویژه و سفارشی سازی شده برای شما آسان تر شود.

 

 

+ ترجمه و تلخیص از منبع زیر:

Customizing what happens in fit()

نوشته بازنویسی عملکرد تابع fit برای آموزش یک شبکه GAN اولین بار در اوپن مایند. پدیدار شد.