Viết một vòng đào tạo từ đầu

Xem trên TensorFlow.org Chạy trong Google Colab Xem nguồn trên GitHub Tải xuống sổ ghi chép

Thành lập

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

Giới thiệu

Keras cung cấp đào tạo mặc định và vòng thẩm định, fit()evaluate() . Sử dụng của chúng được bao phủ trong hướng dẫn đào tạo & đánh giá với các phương pháp tích hợp .

Nếu bạn muốn tùy chỉnh các thuật toán học của mô hình của bạn trong khi vẫn tận dụng sự tiện lợi của fit() (ví dụ, để đào tạo một GAN sử dụng fit() ), bạn có thể phân lớp các Model lớp học và thực hiện của riêng bạn train_step() phương pháp, mà được gọi là liên tục trong suốt fit() . Này được bao phủ trong hướng dẫn sử Tuỳ chỉnh những gì xảy ra ở fit() .

Bây giờ, nếu bạn muốn kiểm soát mức độ rất thấp đối với đào tạo và đánh giá, bạn nên viết các vòng đào tạo và đánh giá của riêng bạn từ đầu. Đây là những gì hướng dẫn này là về.

Sử dụng GradientTape : a đầu tiên end-to-end dụ

Gọi một mô hình bên trong một GradientTape phạm vi cho phép bạn lấy lại độ dốc của các trọng khả năng huấn luyện của lớp đối với một giá trị thiệt hại với. Sử dụng một ví dụ tôi ưu hoa, bạn có thể sử dụng các gradient để cập nhật các biến (mà bạn có thể lấy sử dụng model.trainable_weights ).

Hãy xem xét một mô hình MNIST đơn giản:

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)

Hãy đào tạo nó bằng cách sử dụng gradient lô nhỏ với vòng lặp đào tạo tùy chỉnh.

Đầu tiên, chúng ta sẽ cần một trình tối ưu hóa, một hàm mất mát và một tập dữ liệu:

# 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

Đây là vòng đào tạo của chúng tôi:

  • Chúng tôi mở for vòng lặp đó lặp trên các thời kỳ
  • Đối với mỗi thời đại, chúng tôi mở một for vòng lặp đó lặp trên tập dữ liệu, theo lô
  • Đối với mỗi lô hàng, chúng tôi mở một GradientTape() phạm vi
  • Bên trong phạm vi này, chúng tôi gọi mô hình (chuyển tiếp) và tính toán tổn thất
  • Bên ngoài phạm vi, chúng tôi truy xuất độ dốc của trọng số của mô hình liên quan đến sự mất mát
  • Cuối cùng, chúng tôi sử dụng trình tối ưu hóa để cập nhật trọng lượng của mô hình dựa trên các gradient
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

Xử lý các chỉ số ở mức độ thấp

Hãy thêm giám sát số liệu vào vòng lặp cơ bản này.

Bạn có thể dễ dàng sử dụng lại các chỉ số tích hợp sẵn (hoặc các chỉ số tùy chỉnh mà bạn đã viết) trong các vòng huấn luyện được viết từ đầu như vậy. Đây là quy trình:

  • Khởi tạo chỉ số khi bắt đầu vòng lặp
  • Gọi metric.update_state() sau mỗi đợt
  • Gọi metric.result() khi bạn cần để hiển thị các giá trị hiện tại của chỉ số
  • Gọi metric.reset_states() khi bạn cần để xóa tình trạng số liệu (thường là ở phần cuối của một kỷ nguyên)

Hãy sử dụng kiến thức này để tính toán SparseCategoricalAccuracy trên dữ liệu xác nhận ở phần cuối của mỗi thời đại:

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

Đây là vòng đào tạo và đánh giá của chúng tôi:

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

Đẩy lên bước đào tạo của bạn với tf.function

Thời gian chạy mặc định trong TensorFlow 2 là thực hiện háo hức . Như vậy, vòng lặp đào tạo của chúng tôi ở trên thực hiện một cách háo hức.

Điều này rất tốt cho việc gỡ lỗi, nhưng việc biên dịch đồ thị có một lợi thế về hiệu suất nhất định. Việc mô tả tính toán của bạn dưới dạng biểu đồ tĩnh cho phép khung áp dụng các tối ưu hóa hiệu suất toàn cầu. Điều này là không thể khi khung công tác bị ràng buộc để thực hiện một cách tham lam hết hoạt động này đến hoạt động khác mà không biết điều gì xảy ra tiếp theo.

Bạn có thể biên dịch thành một đồ thị tĩnh bất kỳ hàm nào sử dụng tensors làm đầu vào. Chỉ cần thêm một @tf.function trang trí trên đó, như thế này:

@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

Hãy làm tương tự với bước đánh giá:

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

Bây giờ, hãy chạy lại vòng lặp đào tạo của chúng tôi với bước đào tạo đã biên dịch này:

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

Nhanh hơn nhiều, phải không?

Xử lý tổn thất ở mức độ thấp được mô hình theo dõi

Lớp & mô hình đệ quy theo dõi bất kỳ thiệt hại được tạo ra trong thời gian qua về phía trước bởi lớp mà gọi self.add_loss(value) . Kết quả là danh sách các giá trị thiệt hại vô hướng có sẵn thông qua các tài sản model.losses vào cuối đèo về phía trước.

Nếu bạn muốn sử dụng các thành phần tổn thất này, bạn nên tính tổng chúng và thêm chúng vào tổn thất chính trong bước đào tạo của mình.

Hãy xem xét lớp này, lớp này tạo ra sự mất quy định hoạt động:

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

Hãy xây dựng một mô hình thực sự đơn giản sử dụng nó:

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)

Đây là bước đào tạo của chúng ta bây giờ sẽ như thế nào:

@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

Bản tóm tắt

Bây giờ bạn biết mọi thứ cần biết về việc sử dụng các vòng huấn luyện tích hợp sẵn và viết bài của riêng bạn từ đầu.

Để kết luận, đây là một ví dụ đơn giản từ đầu đến cuối liên kết mọi thứ bạn đã học trong hướng dẫn này lại với nhau: một DCGAN được đào tạo về các chữ số MNIST.

Ví dụ từ đầu đến cuối: vòng lặp đào tạo GAN từ đầu

Bạn có thể quen thuộc với Mạng đối thủ chung (GAN). GAN có thể tạo ra những hình ảnh mới trông gần như thật, bằng cách tìm hiểu sự phân bố tiềm ẩn của một tập dữ liệu đào tạo về hình ảnh ("không gian tiềm ẩn" của hình ảnh).

GAN được làm bằng hai phần: mô hình "máy phát điện" ánh xạ các điểm trong không gian tiềm ẩn với các điểm trong không gian hình ảnh, mô hình "bộ phân biệt", bộ phân loại có thể phân biệt giữa hình ảnh thực (từ tập dữ liệu huấn luyện) và giả. hình ảnh (đầu ra của mạng máy phát điện).

Một vòng lặp đào tạo GAN trông như thế này:

1) Đào tạo người phân biệt. - Lấy mẫu một loạt các điểm ngẫu nhiên trong không gian tiềm ẩn. - Biến các điểm thành hình ảnh giả thông qua mô hình "máy phát điện". - Nhận một loạt các hình ảnh thực và kết hợp chúng với các hình ảnh đã tạo. - Đào tạo mô hình "phân biệt" để phân loại hình ảnh được tạo ra so với thực tế.

2) Huấn luyện máy phát điện. - Lấy mẫu các điểm ngẫu nhiên trong không gian tiềm ẩn. - Biến các điểm thành hình ảnh giả thông qua mạng "máy phát điện". - Nhận một loạt các hình ảnh thực và kết hợp chúng với các hình ảnh đã tạo. - Huấn luyện mô hình "máy phát điện" để "đánh lừa" người phân biệt và phân loại ảnh giả như thật.

Để có cái nhìn chi tiết hơn về cách Gans công trình, thấy sâu Learning với Python .

Hãy thực hiện vòng lặp đào tạo này. Đầu tiên, hãy tạo dấu phân biệt dùng để phân loại chữ số giả và chữ số thật:

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
_________________________________________________________________

Sau đó, chúng ta hãy tạo ra một mạng máy phát điện, có thể biến vectơ tiềm ẩn vào kết quả của hình dạng (28, 28, 1) (đại diện cho chữ số 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",
)

Đây là bit quan trọng: vòng lặp đào tạo. Như bạn có thể thấy nó khá đơn giản. Chức năng bước đào tạo chỉ mất 17 dòng.

# 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

Hãy đào tạo GAN của chúng tôi, bằng cách liên tục gọi train_step trên lô của hình ảnh.

Vì trình phân biệt và trình tạo của chúng tôi là các chuyển đổi, bạn sẽ muốn chạy mã này trên 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.69
adversarial loss at step 0: 0.69

Đó là nó! Bạn sẽ nhận được các chữ số MNIST giả đẹp mắt chỉ sau ~ 30 giây đào tạo về GPU Colab.