#导包
import tensorflow as tf
from tensorflow import keras
from keras import layers
import matplotlib.pyplot as plt
%matplotlib inline
# 使用Keras的datasets.mnist.load_data()函数加载MNIST数据集,这个函数会返回两个数组,一个包含图像数据,另一个包含对应的标签。
# load_data函数默认返回的数据格式是(images, labels),这里我们分别将训练集的图片和标签赋值给train_images和train_labels。
(train_images, train_labels), (_, _) = keras.datasets.mnist.load_data()
# reshape函数将train_images的形状改变为(数量, 28, 28, 1)。原来的形状应该是(数量, 28, 28),这里我们增加了一个维度以便后续进行深度学习模型的训练。
# astype('float32')将数组的数据类型转换为float32,这是深度学习模型训练通常需要的。
train_images = train_images.reshape(train_images.shape[0], 28, 28, 1).astype('float32')
# 对图像数据进行归一化处理,将所有像素值从0-255变为-1到1之间。127.5是图像数据的中间值。
train_images = (train_images-127.5)/127.5
# 定义批量大小和缓冲区大小。在TensorFlow中,批量大小是指一次训练迭代中样本的数量,缓冲区大小是指用于数据增强和洗牌的缓冲区的大小。
BATCH_SIZE = 256
BUFFER_SIZE = 60000
# 使用tf.data.Dataset.from_tensor_slices函数将训练图像数组转换为一个数据集对象。这个对象提供了许多用于数据处理的函数,如shuffle和batch。
datasets = tf.data.Dataset.from_tensor_slices(train_images)
# 使用shuffle函数对数据进行洗牌,使用BUFFER_SIZE作为缓冲区大小。这样每次获取数据时,都能保证数据的新颖性和随机性。
datasets = datasets.shuffle(BUFFER_SIZE)
# 使用batch函数将数据集划分为大小为BATCH_SIZE的批次,这样可以在每次训练迭代中使用一个批次的数据。
datasets = datasets.batch(BATCH_SIZE)
# 定义一个生成器模型函数,该函数将生成一个用于从随机数生成手写数字数据集的模型
def generator_model():
# 使用Keras创建一个顺序模型,这种模型的特点是它按照添加的顺序来定义其结构
model = keras.Sequential()
# 添加第一层全连接层,名称为"Dense",意味着这个模型是一个多输入单输出(MISO)模型,
# 输入维度为100(随机数的数量),输出维度为256,也就是2的8次方,方便在后续进行binarization操作
# use_bias=False表示不使用偏置项,也就是模型的权重更新不会受到偏差的影响
model.add(layers.Dense(256, input_shape=(100,), use_bias=False))
# 添加批标准化层,对上一层进行标准化处理,有助于提高训练速度和模型的性能
model.add(layers.BatchNormalization())
# 添加LeakyReLU激活层,这是一种特殊的ReLU激活函数,在输入值较小的时候会有一个小的梯度,避免梯度消失的问题
model.add(layers.LeakyReLU())
# 添加第二层全连接层,输出维度为512,依然没有使用偏置项
model.add(layers.Dense(512, use_bias=False))
# 再次添加批标准化层
model.add(layers.BatchNormalization())
# 再次添加LeakyReLU激活层
model.add(layers.LeakyReLU())
# 添加全连接层,输出维度为28*28*1,这是因为我们需要生成28x28的灰度图像,所以是一维数组长度为28*28
# use_bias=False表示不使用偏置项
# 激活函数为'tanh',这是一种非线性激活函数,用于增加模型的非线性表达能力
model.add(layers.Dense(28*28*1, use_bias=False, activation='tanh'))
# 再次添加批标准化层
model.add(layers.BatchNormalization())
# 使用Reshape层将输出从一维数组变为28x28的二维数组,因为这是图像数据的标准形式
model.add(layers.Reshape((28, 28, 1)))
# 返回创建好的模型
return model
# 定义一个名为discriminator_model的函数,它不接受任何输入参数
def discriminator_model(): # 定义一个用于鉴别图片真假的模型
# 使用Keras创建一个新的序贯模型,序贯模型是模型的一种堆叠结构,可以依次添加层
model = keras.Sequential()
# 添加一个Flatten层,将输入的图片数据从二维或三维数组转化为一维数组,用于下一层的处理
model.add(layers.Flatten())
# 添加一个全连接层(Dense层),该层的神经元数量为512,不使用偏置项(use_bias=False),用于对数据进行初步的特征提取
model.add(layers.Dense(512, use_bias=False))
# 添加一个批标准化层,用于提高训练速度并提升模型的性能
model.add(layers.BatchNormalization())
# 添加一个LeakyReLU激活层,这是一种特殊的ReLU激活函数,当输入值为0时,仍然有一个很小的梯度,防止ReLU函数在0点处发生梯度消失的问题
model.add(layers.LeakyReLU())
# 添加第二个全连接层,神经元数量为256,不使用偏置项,再次进行特征提取
model.add(layers.Dense(256, use_bias=False))
# 再次添加批标准化层
model.add(layers.BatchNormalization())
# 再次添加LeakyReLU激活层
model.add(layers.LeakyReLU())
# 添加一个全连接层,神经元数量为1,这通常被视为输出层,用于输出模型的预测结果(这里是真假图片的概率)
model.add(layers.Dense(1))
# 返回创建好的模型
return model
# 初始化一个二元交叉熵损失函数,这个函数通常用于二分类问题。from_logits=True表示输入的是logits,即未经过sigmoid或softmax压整的原始输出分数
cross_entropy = keras.losses.BinaryCrossentropy(from_logits=True)
# 定义一个函数作为计算鉴别器(discriminator)的损失
def discriminator_loss(real_out, fake_out):
# 对于真正的输出(real_out),我们使用交叉熵损失函数计算真实标签(这里为1)和模型预测结果(real_out)之间的损失。注意这里交叉熵损失函数内部会自动将real_out转化为logits,然后计算损失
real_loss = cross_entropy(tf.ones_like(real_out), real_out)
# 对于假造的输出(fake_out),我们使用交叉熵损失函数计算假造标签(这里为0)和模型预测结果(fake_out)之间的损失。同样,交叉熵损失函数内部会自动将fake_out转化为logits,然后计算损失
fake_loss = cross_entropy(tf.zeros_like(fake_out), fake_out)
# 最后,返回真实样本和假造样本损失的和,这是标准的GAN结构中的常用做法。鉴别器的目标是尽可能地让真实样本被识别为真(即减小real_loss),同时尽可能地让假造样本被识别为假(即增大fake_loss)
return real_loss + fake_loss
# 定义一个函数作为计算生成器(generator)的损失
def generator_loss(fake_out):
# 对于生成器,我们同样使用交叉熵损失函数来计算真实标签(这里为1)和模型预测结果(fake_out)之间的损失。但是注意,这里的fake_out是由生成器生成的假造样本,但是我们的目标是让这个假造样本尽可能地接近真实样本,所以真实标签是1
return cross_entropy(tf.ones_like(fake_out), fake_out)
# 创建Adam优化器实例,其中1e-4是学习率,这是一个重要的超参数,控制模型更新程度
# 它可以理解为模型对每次输入数据的敏感度,学习率过大可能会导致模型无法收敛,过小则会导致模型收敛过慢
generator_opt = keras.optimizers.Adam(1e-4)
# 创建另一个Adam优化器实例,也为1e-4的学习率,用于训练鉴别器模型
discriminator_opt = keras.optimizers.Adam(1e-4)
# 创建生成器模型的对象,并将其赋值给变量generator
generator = generator_model()
# 创建鉴别器模型的对象,并将其赋值给变量discriminator
discriminator = discriminator_model()
# 定义一个变量,表示噪声的维度,这通常是在生成图片时所需要的随机数的数量
noise_dim = 100 # 即用100个随机数生成图片
# 定义一个函数,该函数用于执行一个训练步骤
def train_step(images):
# 使用TensorFlow的random.normal函数生成一个形状为[BATCH_SIZE, noise_dim]的随机矩阵,这个矩阵将用作生成图片的噪声
noise = tf.random.normal([BATCH_SIZE, noise_dim])
# 使用TensorFlow的GradientTape()创建一个上下文管理器,该管理器可以记录后面运算中的反向传播过程
# 这是因为我们在训练生成器和鉴别器的时候需要分别计算它们的梯度
with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape:
# 调用鉴别器模型对真实图片进行预测
real_out = discriminator(images, training=True)
# 使用生成器模型和噪声生成一批假图片
gen_image = generator(noise, training=True)
# 使用鉴别器模型对生成的假图片进行预测
fake_out = discriminator(gen_image, training=True)
# 根据假图片和真图片的预测结果计算生成器的损失
gen_loss = generator_loss(fake_out)
# 根据真图片和假图片的预测结果计算鉴别器的损失
disc_loss = discriminator_loss(real_out, fake_out)
# 使用gen_tape的gradient方法计算生成器关于其可训练变量的梯度
gradient_gen = gen_tape.gradient(gen_loss, generator.trainable_variables)
# 使用disc_tape的gradient方法计算鉴别器关于其可训练变量的梯度
gradient_disc = disc_tape.gradient(disc_loss, discriminator.trainable_variables)
# 使用生成器的优化器对生成器的变量进行梯度下降更新
generator_opt.apply_gradients(zip(gradient_gen, generator.trainable_variables))
# 使用鉴别器的优化器对鉴别器的变量进行梯度下降更新
discriminator_opt.apply_gradients(zip(gradient_disc, discriminator.trainable_variables))
# 定义训练的轮数,即模型在数据集上训练的次数
EPOCHS = 100 # 训练100次
# 定义要生成的实验(图片)的数量
num_exp_to_generate = 16 # 生成16张图片
# 使用 TensorFlow 库生成一个正态分布的随机数张量,张量的形状是 [num_exp_to_generate, noise_dim],即16组随机数,每组含100个随机数
# 这些随机数将用作生成16张图片的噪声输入
seed = tf.random.normal([num_exp_to_generate, noise_dim]) # 16组随机数组,每组含100个随机数,用来生成16张图片。
# 定义一个训练函数,接受一个数据集和一个训练轮数作为参数
def train(dataset, epochs):
# 在指定的轮数内遍历数据集
for epoch in range(epochs):
# 对于数据集中的每一批图像
for image_batch in dataset:
# 对这一批图像执行一次训练步骤
train_step(image_batch)
# 在控制台上打印出训练的进度
print('.', end='')
# 在每个epoch结束时生成一张图像的图像,使用之前生成的随机数作为噪声输入
generate_plot_image(generator, seed)
# 使用定义的训练函数对给定的数据集进行训练
train(datasets, EPOCHS)
第一次迭代
第100次
全程