
Как писать индивидуальные тренировочные петли в керасах с GradientTape
14 июня 2025 г.Обзор контента
- Настраивать
- Введение
- Использование GradientTape: первый сквозной пример
- Низкоуровневая обработка метрик
- Ускорение вашего шага тренировок с помощью tf.function
- Низкоуровневая обработка потерь, отслеживаемых моделью
- Краткое содержание
- Сквозной пример: петля обучения Gan с нуля
Настраивать
import tensorflow as tf
import keras
from keras import layers
import numpy as np
Введение
Керас обеспечивает петли обучения и оценки по умолчанию,fit()
иevaluate()
Полем Их использование покрыто гидомОбучение и оценка со встроенными методамиПолем
Если вы хотите настроить алгоритм обучения вашей модели, в то же время используя удобствоfit()
(например, обучить Gan, используяfit()
), вы можете подклассModel
класс и реализуйте свой собственныйtrain_step()
метод, который неоднократно называется во времяfit()
Полем Это покрыто гидомНастройка того, что происходит вfit()
Полем
Теперь, если вы хотите очень низкоуровневый контроль над обучением и оценкой, вам следует написать свои собственные петли обучения и оценки с нуля. Это то, о чем это руководство.
ИспользуяGradientTape
: Первый сквозной пример
Вызов модели внутриGradientTape
Scope позволяет вам получить градиенты обучаемых весов слоя по отношению к потерь. Используя экземпляр Optimizer, вы можете использовать эти градиенты для обновления этих переменных (которые вы можете получить, используя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))
# 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)
Вот наш учебный цикл:
- Мы открываем
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) * batch_size))
Start of epoch 0
WARNING:tensorflow:5 out of the last 5 calls to <function _BaseOptimizer._update_step_xla at 0x7f51fe36a4c0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.
WARNING:tensorflow:6 out of the last 6 calls to <function _BaseOptimizer._update_step_xla at 0x7f51fe36a4c0> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details.
Training loss (for one batch) at step 0: 131.3794
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.2871
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.2652
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.8800
Seen so far: 38464 samples
Start of epoch 1
Training loss (for one batch) at step 0: 0.8296
Seen so far: 64 samples
Training loss (for one batch) at step 200: 1.3322
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 1.0486
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.6610
Seen so far: 38464 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()
Вот наша петля обучения и оценки:
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: 106.2691
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.9259
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.9347
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.7641
Seen so far: 38464 samples
Training acc over epoch: 0.7332
Validation acc: 0.8325
Time taken: 10.95s
Start of epoch 1
Training loss (for one batch) at step 0: 0.5238
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.7125
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.5705
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.6006
Seen so far: 38464 samples
Training acc over epoch: 0.8424
Validation acc: 0.8525
Time taken: 10.59s
Ускорение вашего шага тренировок сtf.function
Время выполнения по умолчанию в Tensorflow 2Жесткое исполнениеПолем Таким образом, наша петля обучения выше выполняется с нетерпением.
Это отлично подходит для отладки, но компиляция графика имеет определенное преимущество в производительности. Описание ваших вычислений как статического графика позволяет фреймворку применять глобальные оптимизации производительности. Это невозможно, когда структура ограничена для жадно выполнять одну операцию за другой, без знания того, что будет дальше.
Вы можете компилировать в статический график любую функцию, которая принимает тензоры в качестве ввода. Просто добавьте@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) * 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.5162
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.4599
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.3975
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.2557
Seen so far: 38464 samples
Training acc over epoch: 0.8747
Validation acc: 0.8545
Time taken: 1.85s
Start of epoch 1
Training loss (for one batch) at step 0: 0.6145
Seen so far: 64 samples
Training loss (for one batch) at step 200: 0.3751
Seen so far: 12864 samples
Training loss (for one batch) at step 400: 0.3464
Seen so far: 25664 samples
Training loss (for one batch) at step 600: 0.4128
Seen so far: 38464 samples
Training acc over epoch: 0.8919
Validation acc: 0.8996
Time taken: 1.34s
Гораздо быстрее, не так ли?
Низкоуровневая обработка потерь, отслеживаемых моделью
Слои и модели рекурсивно отслеживают любые потери, созданные во время прямого прохода по слоям, которые вызываютself.add_loss(value)
Полем Полученный список значений потерь скалярных потерь доступен через свойствоmodel.losses
В конце переднего прохода.
Если вы хотите использовать эти компоненты потерь, вы должны подвести итог их и добавить их к основной потере на своем этапе обучения.
Рассмотрим этот слой, который создает потерю регуляризации деятельности:
@keras.saving.register_keras_serializable()
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). Ганс может генерировать новые изображения, которые выглядят почти реальными, изучая скрытое распределение учебного набора данных («скрытое пространство» изображений).
GAN изготовлен из двух частей: модель «генератора», которая отображает точки в скрытом пространстве в точки в пространстве изображений, модели «дискриминатор», классификатор, который может определить разницу между реальными изображениями (из набора учебного набора) и поддельными изображениями (вывод сети генератора).
Похоже на тренировочную петлю Gan выглядит так:
- Обучить дискриминатор. - Выберите партию случайных точек в скрытом пространстве. - Превратите точки в поддельные изображения через модель «генератор». - Получите партию реальных изображений и объедините их с сгенерированными изображениями. - Обучить модель «дискриминатора» для классификации сгенерированных и реальных изображений.
- Обучить генератор. - Образец случайных точек в скрытом пространстве. - Превратите точки в поддельные изображения через сеть «Генератор». - Получите партию реальных изображений и объедините их с сгенерированными изображениями. - Обурите модель «генератор», чтобы «обмануть» дискриминатор и классифицировать поддельные изображения как реальные.
Для более подробного обзора того, как работает Ганс, см. Deep Learning с Python.
Давайте внедрим эту петлю обучения. Во -первых, создайте дискриминатор, предназначенный для классификации фальшивых и реальных цифр:
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 (Glob (None, 128) 0
alMaxPooling2D)
dense_4 (Dense) (None, 1) 129
=================================================================
Total params: 74625 (291.50 KB)
Trainable params: 74625 (291.50 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
Затем давайте создадим сеть генератора, которая превращает скрытые векторы в выходы формы(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
На партиях изображений.
Поскольку наш дискриминатор и генератор являются защищенными, вы захотите запустить этот код на графическом процессоре.
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 = keras.utils.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.72
adversarial loss at step 0: 0.72
Вот и все! Вы получите красивые фальшивые цифры MNIST после всего лишь 30-х годов тренировок на графическом процессоре Colab.
Первоначально опубликовано на
Оригинал