政安晨:【Keras机器学习实践要点】(八)—— 在 TensorFlow 中从头开始编写训练循环

本文主要是介绍政安晨:【Keras机器学习实践要点】(八)—— 在 TensorFlow 中从头开始编写训练循环,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

介绍

导入

第一个端对端示例

指标的低级处理

低层次处理模型跟踪的损失

总结

端到端示例:从零开始的 GAN 训练循环


政安晨的个人主页政安晨

欢迎 👍点赞✍评论⭐收藏

收录专栏: TensorFlow与Keras机器学习实战

希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!


介绍

Keras 提供默认的训练和评估循环、fit() 和 evaluate()。

使用内置方法进行训练和评估,本文将介绍它们的用法。


如果您想自定义模型的学习算法,同时还想利用 fit() 的便利性(例如,使用 fit() 训练 GAN),您可以子类化模型类并实现自己的 train_step() 方法,该方法会在 fit() 过程中重复调用。

现在,如果您想对训练和评估进行非常底层的控制,您应该从头开始编写自己的训练和评估循环。这就是本文的内容。

导入

import time
import os# This guide can only be run with the TensorFlow backend.
os.environ["KERAS_BACKEND"] = "tensorflow"import tensorflow as tf
import keras
import numpy as np

第一个端对端示例

让我们考虑一个简单的 MNIST 模型:

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

让我们使用mini-batch批量梯度和自定义训练循环来训练它。

首先,我们需要一个优化器、一个损失函数和一个数据集

# Instantiate an optimizer.
optimizer = keras.optimizers.Adam(learning_rate=1e-3)
# Instantiate a loss function.
loss_fn = keras.losses.SparseCategoricalCrossentropy(from_logits=True)# Prepare the training dataset.
batch_size = 32
(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)

下载数据集:

在 GradientTape 作用域内调用模型,可以获取该层可训练权重相对于损失值的梯度。

使用优化器实例,您可以使用这些梯度来更新这些变量(您可以使用 model.trainable_weights 来检索这些变量)。

下面是我们一步一步的训练循环

× 我们打开一个 for 循环,迭代epochs
× 对于每个 epoch,我们打开一个 for 循环,分批迭代数据集
× 对于每个批次,我们打开一个 GradientTape() 作用域
× 在此作用域内,我们调用模型(前向传递)并计算损失
× 在作用域之外,我们检索模型权重与损失的梯度
× 最后,我们使用优化器根据梯度更新模型的权重

epochs = 3
for epoch in range(epochs):print(f"\nStart of epoch {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(grads, model.trainable_weights)# Log every 100 batches.if step % 100 == 0:print(f"Training loss (for 1 batch) at step {step}: {float(loss_value):.4f}")print(f"Seen so far: {(step + 1) * batch_size} samples")

训练演绎如下

epoch 0

epoch 1

epoch 2

指标的低级处理

让我们为这个基本循环添加指标监控功能。

在这种从头开始编写的训练循环中,您可以随时重用内置指标(或您自定义的指标)。

流程如下:

在循环开始时实例化指标
在每个批次后调用 metric.update_state()
需要显示度量的当前值时,调用 metric.result()
需要清除度量值的状态时(通常是在一个纪元结束时),调用 metric.reset_state()

让我们利用这些知识,在每个epoch结束时计算训练数据和验证数据的稀疏分类准确率(SparseCategoricalAccuracy)

# Get a fresh model
model = get_model()# Instantiate an optimizer to train the model.
optimizer = keras.optimizers.Adam(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()

以下是我们的培训和评估循环:

epochs = 2
for epoch in range(epochs):print(f"\nStart of epoch {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(grads, model.trainable_weights)# Update training metric.train_acc_metric.update_state(y_batch_train, logits)# Log every 100 batches.if step % 100 == 0:print(f"Training loss (for 1 batch) at step {step}: {float(loss_value):.4f}")print(f"Seen so far: {(step + 1) * batch_size} samples")# Display metrics at the end of each epoch.train_acc = train_acc_metric.result()print(f"Training acc over epoch: {float(train_acc):.4f}")# Reset training metrics at the end of each epochtrain_acc_metric.reset_state()# 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 metricsval_acc_metric.update_state(y_batch_val, val_logits)val_acc = val_acc_metric.result()val_acc_metric.reset_state()print(f"Validation acc: {float(val_acc):.4f}")print(f"Time taken: {time.time() - start_time:.2f}s")

训练评估过程如下:

epoch 0

epoch 1

使用 tf.function 加快训练步骤

TensorFlow 的默认运行时是急迫执行。因此,我们上面的训练循环也是急迫执行的。

这对调试很有帮助,但图形编译在性能上有一定优势。将计算描述为静态图可以让框架应用全局性能优化。如果框架受限于贪婪地执行一个又一个操作,而不知道下一个操作是什么,就不可能做到这一点。


你可以将任何将张量作为输入的函数编译成静态图。

只需在其上添加一个 @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(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)

现在,让我们用这个编译过的训练步骤重新运行训练循环:

epochs = 2
for epoch in range(epochs):print(f"\nStart of epoch {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 100 batches.if step % 100 == 0:print(f"Training loss (for 1 batch) at step {step}: {float(loss_value):.4f}")print(f"Seen so far: {(step + 1) * batch_size} samples")# Display metrics at the end of each epoch.train_acc = train_acc_metric.result()print(f"Training acc over epoch: {float(train_acc):.4f}")# Reset training metrics at the end of each epochtrain_acc_metric.reset_state()# 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_state()print(f"Validation acc: {float(val_acc):.4f}")print(f"Time taken: {time.time() - start_time:.2f}s")

演绎如下:

epoch 0

epoch 1

是不是快多了哈?呵呵

低层次处理模型跟踪的损失

图层和模型会递归地跟踪调用 self.add_loss(value) 的图层在向前传递过程中产生的任何损失。

由此产生的标量损失值列表可在前向传递结束时通过属性 model.losses 查看。
如果要使用这些损失成分,应在训练步骤中将它们相加并添加到主损失中。

请看这一层,它创建了一个活动正则化损失:

class ActivityRegularizationLayer(keras.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 = keras.layers.Dense(64, activation="relu")(inputs)
# Insert activity regularization as a layer
x = ActivityRegularizationLayer()(x)
x = keras.layers.Dense(64, activation="relu")(x)
outputs = keras.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(grads, model.trainable_weights)train_acc_metric.update_state(y, logits)return loss_value

总结


现在你已经了解了使用内置训练循环和从头开始编写自己的训练循环的所有知识。
最后,这里有一个简单的端到端示例,可以将您在本指南中学到的所有知识串联起来在 MNIST 数字上训练的 DCGAN。

端到端示例:从零开始的 GAN 训练循环

您可能对生成对抗网络(GAN)并不陌生。

GANs 可以通过学习图像训练数据集(图像的 "潜在空间")的潜在分布,生成看起来几乎真实的新图像。

GAN 由两部分组成:

一个 "生成器 "模型,用于将潜空间中的点映射到图像空间中的点;

一个 "判别器 "模型,即一个分类器,用于区分真实图像(来自训练数据集)和虚假图像(生成器网络的输出)。


一个 GAN 训练循环是这样的:

1) 训练判别器。- 在潜空间中采样一批随机点。- 通过 "生成器 "模型将这些点转化为假图像。

- 获取一批真实图像,并将它们与生成的图像相结合。- 训练 "判别器 "模型,对生成图像和真实图像进行分类。



2) 训练生成器。- 在潜空间中随机采样点。- 通过 "生成器 "网络将这些点转化为假图像。

- 获取一批真实图像,并将它们与生成的图像相结合。- 训练 "生成器 "模型,使其 "骗过 "判别器,将假图像分类为真图像。

让我们来实现这个训练循环。

首先,创建用于将假数字与真数字进行分类的判别器:

discriminator = keras.Sequential([keras.Input(shape=(28, 28, 1)),keras.layers.Conv2D(64, (3, 3), strides=(2, 2), padding="same"),keras.layers.LeakyReLU(negative_slope=0.2),keras.layers.Conv2D(128, (3, 3), strides=(2, 2), padding="same"),keras.layers.LeakyReLU(negative_slope=0.2),keras.layers.GlobalMaxPooling2D(),keras.layers.Dense(1),],name="discriminator",
)
discriminator.summary()

演绎如下:

然后,让我们创建一个生成器网络,将潜在向量转化为形状为 (28, 28, 1) 的输出(代表 MNIST 数字):

latent_dim = 128generator = keras.Sequential([keras.Input(shape=(latent_dim,)),# We want to generate 128 coefficients to reshape into a 7x7x128 mapkeras.layers.Dense(7 * 7 * 128),keras.layers.LeakyReLU(negative_slope=0.2),keras.layers.Reshape((7, 7, 128)),keras.layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),keras.layers.LeakyReLU(negative_slope=0.2),keras.layers.Conv2DTranspose(128, (4, 4), strides=(2, 2), padding="same"),keras.layers.LeakyReLU(negative_slope=0.2),keras.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 spacerandom_latent_vectors = tf.random.normal(shape=(batch_size, latent_dim))# Decode them to fake imagesgenerated_images = generator(random_latent_vectors)# Combine them with real imagescombined_images = tf.concat([generated_images, real_images], axis=0)# Assemble labels discriminating real from fake imageslabels = 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 discriminatorwith 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(grads, discriminator.trainable_weights)# Sample random points in the latent spacerandom_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(grads, generator.trainable_weights)return d_loss, g_loss, generated_images

让我们在成批图像上反复调用 train_step 来训练我们的 GAN。

由于我们的判别器和生成器都是卷积网络,因此需要在 GPU 上运行这段代码。

# 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(f"\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 % 100 == 0:# Print metricsprint(f"discriminator loss at step {step}: {d_loss:.2f}")print(f"adversarial loss at step {step}: {g_loss:.2f}")# Save one generated imageimg = keras.utils.array_to_img(generated_images[0] * 255.0, scale=False)img.save(os.path.join(save_dir, f"generated_img_{step}.png"))# To limit execution time we stop after 10 steps.# Remove the lines below to actually train the model!if step > 10:break

演绎结果如下:

就是这样哒只需在 Colab GPU 上进行约 30 秒的训练,你就能得到漂亮的伪造 MNIST 数字。


这篇关于政安晨:【Keras机器学习实践要点】(八)—— 在 TensorFlow 中从头开始编写训练循环的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/860615

相关文章

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

在C#中获取端口号与系统信息的高效实践

《在C#中获取端口号与系统信息的高效实践》在现代软件开发中,尤其是系统管理、运维、监控和性能优化等场景中,了解计算机硬件和网络的状态至关重要,C#作为一种广泛应用的编程语言,提供了丰富的API来帮助开... 目录引言1. 获取端口号信息1.1 获取活动的 TCP 和 UDP 连接说明:应用场景:2. 获取硬

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

利用Python编写一个简单的聊天机器人

《利用Python编写一个简单的聊天机器人》这篇文章主要为大家详细介绍了如何利用Python编写一个简单的聊天机器人,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 使用 python 编写一个简单的聊天机器人可以从最基础的逻辑开始,然后逐步加入更复杂的功能。这里我们将先实现一个简单的

使用PyQt5编写一个简单的取色器

《使用PyQt5编写一个简单的取色器》:本文主要介绍PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16进制颜色编码,一款跟随鼠标刷新图像的RGB和16... 目录取色器1取色器2PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或

Docker集成CI/CD的项目实践

《Docker集成CI/CD的项目实践》本文主要介绍了Docker集成CI/CD的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、引言1.1 什么是 CI/CD?1.2 docker 在 CI/CD 中的作用二、Docke

JAVA中while循环的使用与注意事项

《JAVA中while循环的使用与注意事项》:本文主要介绍while循环在编程中的应用,包括其基本结构、语句示例、适用场景以及注意事项,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录while循环1. 什么是while循环2. while循环的语句3.while循环的适用场景以及优势4. 注意

使用Java编写一个文件批量重命名工具

《使用Java编写一个文件批量重命名工具》这篇文章主要为大家详细介绍了如何使用Java编写一个文件批量重命名工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录背景处理1. 文件夹检查与遍历2. 批量重命名3. 输出配置代码片段完整代码背景在开发移动应用时,UI设计通常会提供不

Python中的异步:async 和 await以及操作中的事件循环、回调和异常

《Python中的异步:async和await以及操作中的事件循环、回调和异常》在现代编程中,异步操作在处理I/O密集型任务时,可以显著提高程序的性能和响应速度,Python提供了asyn... 目录引言什么是异步操作?python 中的异步编程基础async 和 await 关键字asyncio 模块理论