Sıfırdan bir eğitim döngüsü yazma

TensorFlow.org'da görüntüleyin Google Colab'da çalıştırın Kaynağı GitHub'da görüntüleyin Not defterini indir

Kurmak

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

Tanıtım

Keras varsayılan eğitim ve değerlendirme döngüler sağlar fit() ve evaluate() . Onların kullanım kılavuzu kaplıdır yerleşik yöntemlerle Eğitim & değerlendirme .

Hala kolaylık yararlanarak ederken modelinin öğrenme algoritmasını özelleştirmek istiyorsanız fit() (örneğin kullanarak bir Gan yetiştirmek fit() ) kullanarak, alt sınıf Model sınıf ve kendi uygulamak train_step() yöntemini, hangi sırasında sürekli olarak adlandırılır fit() . Bu kılavuzda kaplıdır neler olduğunu Özelleştirme fit() .

Şimdi, eğitim ve değerlendirme üzerinde çok düşük düzeyde kontrol istiyorsanız, kendi eğitim ve değerlendirme döngülerinizi sıfırdan yazmalısınız. Bu kılavuzun konusu budur.

Kullanma GradientTape : bir birinci uç uca örneği

Bir iç bir model çağrılması GradientTape kapsamı bir kayıp değerine göre katmanın eğitilebilir ağırlıkların geçişlerini alınmasını sağlar. Bir iyileştirici örneğini kullanarak, bu değişkenleri (kullanmakta alabileceği güncellemek için bu geçişlerini kullanabilir model.trainable_weights ).

Basit bir MNIST modelini ele alalım:

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)

Özel bir eğitim döngüsüyle mini toplu gradyan kullanarak eğitelim.

İlk olarak, bir optimize ediciye, bir kayıp işlevine ve bir veri kümesine ihtiyacımız olacak:

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

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

# Prepare the training dataset.
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.
val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_dataset = val_dataset.batch(batch_size)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 1s 0us/step
11501568/11490434 [==============================] - 1s 0us/step

İşte eğitim döngümüz:

  • Biz açmak for döngü olduğunu dönemini üzerinde dolaşır
  • Her çağın için, bir açma for döngü yinelenir veri kümesi üzerinde, gruplar halinde olduğunu
  • Her yığın için, bir açık GradientTape() kapsamı
  • Bu kapsamda modele (ileri geçiş) diyoruz ve kaybı hesaplıyoruz.
  • Kapsam dışında, kayıp ile ilgili olarak modelin ağırlıklarının gradyanlarını alırız.
  • Son olarak, gradyanlara dayalı olarak modelin ağırlıklarını güncellemek için optimize ediciyi kullanırız.
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) * batch_size))
Start of epoch 0
Training loss (for one batch) at step 0: 68.7478
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.9448
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.1859
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.6914
Seen so far: 38464 samples

Start of epoch 1
Training loss (for one batch) at step 0: 0.9113
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.9550
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.5139
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.7227
Seen so far: 38464 samples

Metriklerin düşük düzeyde işlenmesi

Bu temel döngüye metrik izlemeyi ekleyelim.

Sıfırdan yazılan bu tür eğitim döngülerinde yerleşik metrikleri (veya yazdığınız özel metrikleri) kolayca yeniden kullanabilirsiniz. İşte akış:

  • Döngünün başlangıcında metriği somutlaştırın
  • Çağrı metric.update_state() her gruptan sonra
  • Çağrı metric.result() Eğer metrik mevcut değerini görüntülemek için gerektiğinde
  • Çağrı metric.reset_states() (bu genellikle bir dönemin sonunda) metrik durumunu temizlemek için gerektiğinde

En hesaplamak için bu bilgiyi kullanmak edelim SparseCategoricalAccuracy her dönemin sonunda doğrulama verilerine:

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

İşte eğitim ve değerlendirme döngümüz:

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) * batch_size))

    # 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.9958
Seen so far: 64 samples
Training loss (for one batch) at step 200: 2.2214
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.3083
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.8282
Seen so far: 38464 samples
Training acc over epoch: 0.7406
Validation acc: 0.8201
Time taken: 6.31s

Start of epoch 1
Training loss (for one batch) at step 0: 0.3276
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.4819
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.5971
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.5862
Seen so far: 38464 samples
Training acc over epoch: 0.8474
Validation acc: 0.8676
Time taken: 5.98s

Hız-up ile antrenman adımını tf.function

TensorFlow 2'de varsayılan çalışma zamanı olduğunu istekli yürütme . Bu nedenle, yukarıdaki eğitim döngümüz hevesle yürütülür.

Bu, hata ayıklama için harikadır, ancak grafik derlemenin kesin bir performans avantajı vardır. Hesaplamanızı statik bir grafik olarak tanımlamak, çerçevenin küresel performans optimizasyonlarını uygulamasını sağlar. Çerçeve, bir sonraki işlemin ne olacağı hakkında hiçbir bilgi olmadan açgözlülükle bir işlemi birbiri ardına yürütmekle sınırlandırıldığında bu imkansızdır.

Girdi olarak tensör alan herhangi bir işlevi statik bir grafikte derleyebilirsiniz. Sadece bir ekleme @tf.function böyle, üzerine dekoratör:

@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

Aynısını değerlendirme adımı için de yapalım:

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

Şimdi, bu derlenmiş eğitim adımıyla eğitim döngümüzü yeniden çalıştıralım:

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) * batch_size))

    # 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.7921
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.7755
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.1564
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.3181
Seen so far: 38464 samples
Training acc over epoch: 0.8788
Validation acc: 0.8866
Time taken: 1.59s

Start of epoch 1
Training loss (for one batch) at step 0: 0.5222
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.4574
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.4035
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.7561
Seen so far: 38464 samples
Training acc over epoch: 0.8959
Validation acc: 0.9028
Time taken: 1.27s

Çok daha hızlı, değil mi?

Model tarafından izlenen kayıpların düşük düzeyde ele alınması

Katmanlar ve modelleri yinelemeli aramalarının o katmanları öne geçiş sırasında oluşturulan herhangi kayıpları izlemek self.add_loss(value) . Skaler kayıp değerlerinin ortaya çıkan liste özelliği yoluyla ulaşılabilir model.losses ileri geçişin sonunda.

Bu kayıp bileşenlerini kullanmak istiyorsanız, bunları toplamalı ve eğitim adımınızda ana kayba eklemelisiniz.

Bir aktivite düzenleme kaybı yaratan bu katmanı göz önünde bulundurun:

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

Onu kullanan gerçekten basit bir model oluşturalım:

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)

İşte eğitim adımımızın şimdi nasıl görünmesi gerektiği:

@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

Özet

Artık yerleşik eğitim döngülerini kullanma ve sıfırdan kendi döngülerinizi yazma hakkında bilmeniz gereken her şeyi biliyorsunuz.

Sonuç olarak, bu kılavuzda öğrendiğiniz her şeyi birbirine bağlayan basit bir uçtan uca örnek: MNIST rakamları üzerinde eğitilmiş bir DCGAN.

Uçtan uca örnek: sıfırdan bir GAN eğitim döngüsü

Üretken Düşman Ağları (GAN'lar) hakkında bilgi sahibi olabilirsiniz. GAN'lar, görüntülerin eğitim veri kümesinin (görüntülerin "gizli alanı") gizli dağılımını öğrenerek neredeyse gerçek gibi görünen yeni görüntüler oluşturabilir.

Bir GAN iki bölümden oluşur: gizli uzaydaki noktaları görüntü alanındaki noktalara eşleyen bir "jeneratör" modeli, bir "ayırt edici" model, gerçek görüntüler (eğitim veri kümesinden) ve sahte görüntüler arasındaki farkı söyleyebilen bir sınıflandırıcı görüntüler (jeneratör ağının çıktısı).

Bir GAN eğitim döngüsü şöyle görünür:

1) Ayrımcıyı eğitin. - Gizli uzayda bir grup rastgele noktayı örnekleyin. - "Jeneratör" modeli ile noktaları sahte görüntülere dönüştürün. - Bir grup gerçek görüntü alın ve bunları oluşturulan görüntülerle birleştirin. - Oluşturulan ve gerçek görüntüleri sınıflandırmak için "ayrımcı" modelini eğitin.

2) Jeneratörü eğitin. - Gizli uzayda rastgele noktaları örnekleyin. - "Jeneratör" ağı üzerinden noktaları sahte görüntülere dönüştürün. - Bir grup gerçek görüntü alın ve bunları oluşturulan görüntülerle birleştirin. - Ayrımcıyı "kandırmak" ve sahte görüntüleri gerçek olarak sınıflandırmak için "oluşturucu" modelini eğitin.

Nasıl Gans eserlerin çok daha detaylı bakış için bkz Python ile Derin Öğrenme .

Bu eğitim döngüsünü uygulayalım. İlk olarak, sahte ve gerçek rakamları sınıflandırmak için kullanılan ayrımcıyı oluşturun:

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
_________________________________________________________________

O zaman şekil çıkışlara gizli vektörler döner jeneratörü ağ oluşturmasına izin (28, 28, 1) (MNIST basamak temsil eder):

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",
)

İşte kilit nokta: eğitim döngüsü. Gördüğünüz gibi oldukça basit. Eğitim adımı işlevi yalnızca 17 satır alır.

# 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

En defalarca arayarak, bizim Gan tren edelim train_step görüntüleri toplu halde üzerinde.

Ayırt edicimiz ve oluşturucumuz convnet olduğundan, bu kodu bir GPU üzerinde çalıştırmak isteyeceksiniz.

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.69
adversarial loss at step 0: 0.69

Bu kadar! Colab GPU'da yaklaşık 30 saniyelik eğitimden sonra güzel görünümlü sahte MNIST rakamları elde edeceksiniz.