生成对抗网络(GAN)实现:从安装到可视化
1. 环境准备
在开始构建生成对抗网络(GAN)之前,我们需要安装必要的Python包。Google Colab Notebook环境已经预装了一些包,如NumPy、SciPy和TensorFlow的最新稳定版本。但我们需要使用TensorFlow 2.0,因此要执行以下命令来安装支持GPU的TensorFlow 2.0:
! pip install -q tensorflow-gpu==2.0.0
在Jupyter Notebook中,以感叹号开头的单元格会被解释为Linux shell命令。安装完成后,我们可以使用以下代码测试安装并验证GPU是否可用:
import tensorflow as tf
print(tf.__version__)
print("GPU Available:", tf.test.is_gpu_available())
if tf.test.is_gpu_available():
device_name = tf.test.gpu_device_name()
else:
device_name = '/CPU:0'
print(device_name)
如果要将模型保存到个人Google Drive,或进行文件传输和上传,需要挂载Google Drive。在Notebook的新单元格中执行以下代码:
from google.colab import drive
drive.mount('/content/drive/')
这会提供一个链接用于验证Colab Notebook访问Google Drive的权限。按照验证说明操作后,会得到一个验证码,将其复制粘贴到刚刚执行的单元格下方的指定输入字段中,Google Drive就会被挂载到 /content/drive/My Drive 。
2. 构建生成器和判别器网络
我们将从实现一个简单的GAN模型开始,该模型由生成器和判别器两个全连接网络组成,每个网络包含一个或多个隐藏层。这是原始的GAN版本,也称为香草GAN(vanilla GAN)。
在这个模型中,每个隐藏层使用Leaky ReLU激活函数。ReLU激活函数会导致稀疏梯度,在需要全范围输入值的梯度时可能不太合适。而Leaky ReLU激活函数允许负输入也有非零梯度,使网络更具表达能力。判别器网络的每个隐藏层后面还跟着一个Dropout层。生成器的输出层使用双曲正切(tanh)激活函数,这有助于学习。判别器的输出层没有激活函数(即线性激活)以获取对数几率(logits),也可以使用sigmoid激活函数输出概率。
以下是定义生成器和判别器网络的代码:
import tensorflow as tf
import tensorflow_datasets as tfds
import numpy as np
import matplotlib.pyplot as plt
## define a function for the generator:
def make_generator_network(
num_hidden_layers=1,
num_hidden_units=100,
num_output_units=784):
model = tf.keras.Sequential()
for i in range(num_hidden_layers):
model.add(
tf.keras.layers.Dense(
units=num_hidden_units, use_bias=False))
model.add(tf.keras.layers.LeakyReLU())
model.add(
tf.keras.layers.Dense(
units=num_output_units, activation='tanh'))
return model
## define a function for the discriminator:
def make_discriminator_network(
num_hidden_layers=1,
num_hidden_units=100,
num_output_units=1):
model = tf.keras.Sequential()
for i in range(num_hidden_layers):
model.add(
tf.keras.layers.Dense(units=num_hidden_units))
model.add(tf.keras.layers.LeakyReLU())
model.add(tf.keras.layers.Dropout(rate=0.5))
model.add(
tf.keras.layers.Dense(
units=num_output_units, activation=None))
return model
生成器和判别器网络的构建流程如下:
graph LR
A[开始] --> B[定义生成器网络]
B --> C[添加隐藏层(Leaky ReLU)]
C --> D[添加输出层(tanh)]
A --> E[定义判别器网络]
E --> F[添加隐藏层(Leaky ReLU + Dropout)]
F --> G[添加输出层(线性激活)]
D --> H[完成生成器网络]
G --> I[完成判别器网络]
3. 模型训练设置
接下来,我们要指定模型的训练设置。MNIST数据集中的图像大小为28×28像素(只有一个颜色通道,因为是灰度图像)。我们将输入向量z的大小指定为20,并使用随机均匀分布初始化模型权重。由于我们只是为了说明目的实现一个非常简单的GAN模型,并且使用全连接层,所以每个网络只使用一个包含100个单元的隐藏层。以下代码指定并初始化两个网络,并打印它们的摘要信息:
image_size = (28, 28)
z_size = 20
mode_z = 'uniform' # 'uniform' vs. 'normal'
gen_hidden_layers = 1
gen_hidden_size = 100
disc_hidden_layers = 1
disc_hidden_size = 100
tf.random.set_seed(1)
gen_model = make_generator_network(
num_hidden_layers=gen_hidden_layers,
num_hidden_units=gen_hidden_size,
num_output_units=np.prod(image_size))
gen_model.build(input_shape=(None, z_size))
gen_model.summary()
disc_model = make_discriminator_network(
num_hidden_layers=disc_hidden_layers,
num_hidden_units=disc_hidden_size)
disc_model.build(input_shape=(None, np.prod(image_size)))
disc_model.summary()
生成器模型摘要:
| 层(类型) | 输出形状 | 参数数量 |
| — | — | — |
| dense (Dense) | multiple | 2000 |
| leaky_re_lu (LeakyReLU) | multiple | 0 |
| dense_1 (Dense) | multiple | 79184 |
| 总参数 | 81,184 | |
| 可训练参数 | 81,184 | |
| 不可训练参数 | 0 | |
判别器模型摘要:
| 层(类型) | 输出形状 | 参数数量 |
| — | — | — |
| dense_2 (Dense) | multiple | 78500 |
| leaky_re_lu_1 (LeakyReLU) | multiple | 0 |
| dropout (Dropout) | multiple | 0 |
| dense_3 (Dense) | multiple | 101 |
| 总参数 | 78,601 | |
| 可训练参数 | 78,601 | |
| 不可训练参数 | 0 | |
4. 定义训练数据集
下一步,我们要加载MNIST数据集并进行必要的预处理。由于生成器的输出层使用tanh激活函数,合成图像的像素值范围为(-1, 1),而MNIST图像的输入像素范围为[0, 255](TensorFlow数据类型为tf.uint8)。因此,在预处理步骤中,我们使用 tf.image.convert_image_dtype 函数将输入图像张量的数据类型从tf.uint8转换为tf.float32,这不仅会改变数据类型,还会将输入像素强度范围变为[0, 1]。然后,将其乘以2并减去1,使像素强度范围重新调整为[-1, 1]。此外,我们还会根据所需的随机分布(在这个代码示例中,是均匀分布或正态分布,这是最常见的选择)创建一个随机向量z,并以元组形式返回预处理后的图像和随机向量:
mnist_bldr = tfds.builder('mnist')
mnist_bldr.download_and_prepare()
mnist = mnist_bldr.as_dataset(shuffle_files=False)
def preprocess(ex, mode='uniform'):
image = ex['image']
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.reshape(image, [-1])
image = image*2 - 1.0
if mode == 'uniform':
input_z = tf.random.uniform(
shape=(z_size,), minval=-1.0, maxval=1.0)
elif mode == 'normal':
input_z = tf.random.normal(shape=(z_size,))
return input_z, image
mnist_trainset = mnist['train']
mnist_trainset = mnist_trainset.map(preprocess)
需要注意的是,这里我们同时返回输入向量z和图像是为了在模型拟合过程中方便获取训练数据,但这并不意味着向量z与图像有任何关联——输入图像来自数据集,而向量z是随机生成的。在每次训练迭代中,随机生成的向量z代表生成器合成新图像的输入,而图像(真实图像和合成图像)是判别器的输入。
我们可以检查创建的数据集对象。以下代码取一批示例并打印输入向量和图像样本的数组形状。为了理解GAN模型的整体数据流,我们还会对生成器和判别器进行一次前向传播:
mnist_trainset = mnist_trainset.batch(32, drop_remainder=True)
input_z, input_real = next(iter(mnist_trainset))
print('input-z -- shape: ', input_z.shape)
print('input-real -- shape:', input_real.shape)
g_output = gen_model(input_z)
print('Output of G -- shape:', g_output.shape)
d_logits_real = disc_model(input_real)
d_logits_fake = disc_model(g_output)
print('Disc. (real) -- shape:', d_logits_real.shape)
print('Disc. (fake) -- shape:', d_logits_fake.shape)
数据集处理和前向传播流程如下:
graph LR
A[加载MNIST数据集] --> B[预处理图像]
B --> C[创建随机向量z]
C --> D[组合输入向量z和图像]
D --> E[取一批数据]
E --> F[输入向量z到生成器]
F --> G[生成假样本]
G --> H[假样本输入判别器]
E --> I[真实图像输入判别器]
H --> J[获取假样本对数几率]
I --> K[获取真实样本对数几率]
这些对数几率 d_logits_fake 和 d_logits_real 将用于计算模型训练的损失函数。
5. 训练GAN模型
接下来,我们创建一个 BinaryCrossentropy 实例作为损失函数,并使用它来计算生成器和判别器与刚刚处理的批次相关的损失。为此,我们还需要每个输出的真实标签。对于生成器,我们创建一个与包含生成图像预测对数几率的向量形状相同的全1向量。对于判别器损失,有两项:检测假样本的损失(涉及 d_logits_fake )和检测真实样本的损失(基于 d_logits_real )。假样本的真实标签是一个全0向量,可以使用 tf.zeros() (或 tf.zeros_like() )函数生成;真实图像的真实值可以使用 tf.ones() (或 tf.ones_like() )函数生成全1向量:
loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True)
## Loss for the Generator
g_labels_real = tf.ones_like(d_logits_fake)
g_loss = loss_fn(y_true=g_labels_real, y_pred=d_logits_fake)
print('Generator Loss: {:.4f}'.format(g_loss))
## Loss for the Discriminator
d_labels_real = tf.ones_like(d_logits_real)
d_labels_fake = tf.zeros_like(d_logits_fake)
d_loss_real = loss_fn(y_true=d_labels_real,
y_pred=d_logits_real)
d_loss_fake = loss_fn(y_true=d_labels_fake,
y_pred=d_logits_fake)
print('Discriminator Losses: Real {:.4f} Fake {:.4f}'
.format(d_loss_real.numpy(), d_loss_fake.numpy()))
上述代码示例展示了不同损失项的逐步计算,目的是帮助理解训练GAN模型的整体概念。以下代码将设置GAN模型并实现训练循环,将这些计算包含在一个for循环中:
import time
num_epochs = 100
batch_size = 64
image_size = (28, 28)
z_size = 20
mode_z = 'uniform'
gen_hidden_layers = 1
gen_hidden_size = 100
disc_hidden_layers = 1
disc_hidden_size = 100
tf.random.set_seed(1)
np.random.seed(1)
if mode_z == 'uniform':
fixed_z = tf.random.uniform(
shape=(batch_size, z_size),
minval=-1, maxval=1)
elif mode_z == 'normal':
fixed_z = tf.random.normal(
shape=(batch_size, z_size))
def create_samples(g_model, input_z):
g_output = g_model(input_z, training=False)
images = tf.reshape(g_output, (batch_size, *image_size))
return (images+1)/2.0
## Set-up the dataset
mnist_trainset = mnist['train']
mnist_trainset = mnist_trainset.map(
lambda ex: preprocess(ex, mode=mode_z))
mnist_trainset = mnist_trainset.shuffle(10000)
mnist_trainset = mnist_trainset.batch(
batch_size, drop_remainder=True)
## Set-up the model
with tf.device(device_name):
gen_model = make_generator_network(
num_hidden_layers=gen_hidden_layers,
num_hidden_units=gen_hidden_size,
num_output_units=np.prod(image_size))
gen_model.build(input_shape=(None, z_size))
disc_model = make_discriminator_network(
num_hidden_layers=disc_hidden_layers,
num_hidden_units=disc_hidden_size)
disc_model.build(input_shape=(None, np.prod(image_size)))
## Loss function and optimizers:
loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True)
g_optimizer = tf.keras.optimizers.Adam()
d_optimizer = tf.keras.optimizers.Adam()
all_losses = []
all_d_vals = []
epoch_samples = []
start_time = time.time()
for epoch in range(1, num_epochs+1):
epoch_losses, epoch_d_vals = [], []
for i,(input_z,input_real) in enumerate(mnist_trainset):
## Compute generator's loss
with tf.GradientTape() as g_tape:
g_output = gen_model(input_z)
d_logits_fake = disc_model(g_output,
training=True)
labels_real = tf.ones_like(d_logits_fake)
g_loss = loss_fn(y_true=labels_real,
y_pred=d_logits_fake)
## Compute the gradients of g_loss
g_grads = g_tape.gradient(g_loss,
gen_model.trainable_variables)
## Optimization: Apply the gradients
g_optimizer.apply_gradients(
grads_and_vars=zip(g_grads,
gen_model.trainable_variables))
## Compute discriminator's loss
with tf.GradientTape() as d_tape:
d_logits_real = disc_model(input_real,
training=True)
d_labels_real = tf.ones_like(d_logits_real)
d_loss_real = loss_fn(
y_true=d_labels_real, y_pred=d_logits_real)
d_logits_fake = disc_model(g_output,
training=True)
d_labels_fake = tf.zeros_like(d_logits_fake)
d_loss_fake = loss_fn(
y_true=d_labels_fake, y_pred=d_logits_fake)
d_loss = d_loss_real + d_loss_fake
## Compute the gradients of d_loss
d_grads = d_tape.gradient(d_loss,
disc_model.trainable_variables)
## Optimization: Apply the gradients
d_optimizer.apply_gradients(
grads_and_vars=zip(d_grads,
disc_model.trainable_variables))
epoch_losses.append(
(g_loss.numpy(), d_loss.numpy(),
d_loss_real.numpy(), d_loss_fake.numpy()))
d_probs_real = tf.reduce_mean(
tf.sigmoid(d_logits_real))
d_probs_fake = tf.reduce_mean(
tf.sigmoid(d_logits_fake))
epoch_d_vals.append((d_probs_real.numpy(),
d_probs_fake.numpy()))
all_losses.append(epoch_losses)
all_d_vals.append(epoch_d_vals)
print(
'Epoch {:03d} | ET {:.2f} min | Avg Losses >>'
' G/D {:.4f}/{:.4f} [D-Real: {:.4f} D-Fake: {:.4f}]'
.format(
epoch, (time.time() - start_time)/60,
*list(np.mean(all_losses[-1], axis=0))))
epoch_samples.append(
create_samples(gen_model, fixed_z).numpy())
使用GPU,上述代码块实现的训练过程在Google Colab上应该能在不到一小时内完成(如果你的个人计算机有较新且性能强大的CPU和GPU,可能会更快)。模型训练完成后,绘制判别器和生成器的损失图有助于分析两个子网络的行为,并评估它们是否收敛。同时,绘制判别器在每次迭代中计算的真实和假样本批次的平均概率图也很有帮助。我们期望这些概率接近0.5,这意味着判别器无法自信地区分真实和假图像:
import itertools
fig = plt.figure(figsize=(16, 6))
## Plotting the losses
ax = fig.add_subplot(1, 2, 1)
g_losses = [item[0] for item in itertools.chain(*all_losses)]
d_losses = [item[1]/2.0 for item in itertools.chain(
*all_losses)]
plt.plot(g_losses, label='Generator loss', alpha=0.95)
plt.plot(d_losses, label='Discriminator loss', alpha=0.95)
plt.legend(fontsize=20)
ax.set_xlabel('Iteration', size=15)
ax.set_ylabel('Loss', size=15)
epochs = np.arange(1, 101)
epoch2iter = lambda e: e*len(all_losses[-1])
epoch_ticks = [1, 20, 40, 60, 80, 100]
newpos = [epoch2iter(e) for e in epoch_ticks]
ax2 = ax.twiny()
ax2.set_xticks(newpos)
ax2.set_xticklabels(epoch_ticks)
ax2.xaxis.set_ticks_position('bottom')
ax2.xaxis.set_label_position('bottom')
ax2.spines['bottom'].set_position(('outward', 60))
ax2.set_xlabel('Epoch', size=15)
ax2.set_xlim(ax.get_xlim())
ax.tick_params(axis='both', which='major', labelsize=15)
ax2.tick_params(axis='both', which='major', labelsize=15)
## Plotting the outputs of the discriminator
ax = fig.add_subplot(1, 2, 2)
d_vals_real = [item[0] for item in itertools.chain(
*all_d_vals)]
d_vals_fake = [item[1] for item in itertools.chain(
*all_d_vals)]
plt.plot(d_vals_real, alpha=0.75,
label=r'Real: $D(\mathbf{x})$')
plt.plot(d_vals_fake, alpha=0.75,
label=r'Fake: $D(G(\mathbf{z}))$')
plt.legend(fontsize=20)
ax.set_xlabel('Iteration', size=15)
ax.set_ylabel('Discriminator output', size=15)
ax2 = ax.twiny()
ax2.set_xticks(newpos)
ax2.set_xticklabels(epoch_ticks)
ax2.xaxis.set_ticks_position('bottom')
ax2.xaxis.set_label_position('bottom')
ax2.spines['bottom'].set_position(('outward', 60))
ax2.set_xlabel('Epoch', size=15)
ax2.set_xlim(ax.get_xlim())
ax.tick_params(axis='both', which='major', labelsize=15)
ax2.tick_params(axis='both', which='major', labelsize=15)
plt.show()
从判别器的输出图可以看出,在训练的早期阶段,判别器能够快速学习,相当准确地区分真实和假样本,即假样本的概率接近0,真实样本的概率接近1。这是因为假样本与真实样本差异很大,区分起来比较容易。随着训练的进行,生成器会更擅长合成逼真的图像,导致真实和假样本的概率都接近0.5。
6. 可视化生成器输出
我们还可以观察生成器的输出,即合成图像在训练过程中的变化。在每个epoch之后,我们通过调用 create_samples() 函数生成一些示例,并将它们存储在一个Python列表中。以下代码将可视化生成器在选定epoch生成的一些图像:
selected_epochs = [1, 2, 4, 10, 50, 100]
fig = plt.figure(figsize=(10, 14))
for i,e in enumerate(selected_epochs):
for j in range(5):
ax = fig.add_subplot(6, 5, i*5+j+1)
ax.set_xticks([])
ax.set_yticks([])
if j == 0:
ax.text(
-0.06, 0.5, 'Epoch {}'.format(e),
rotation=90, size=18, color='red',
horizontalalignment='right',
verticalalignment='center',
transform=ax.transAxes)
image = epoch_samples[e-1][j]
ax.imshow(image, cmap='gray_r')
plt.show()
通过这些步骤,我们完成了一个简单GAN模型的实现、训练和可视化。整个过程涵盖了环境准备、网络构建、数据集处理、模型训练和结果分析等多个方面,有助于深入理解GAN的工作原理和实现方法。
生成对抗网络(GAN)实现:从安装到可视化
7. 训练结果分析
在完成GAN模型的训练后,我们可以通过之前绘制的损失图和判别器输出概率图来深入分析训练结果。
7.1 损失图分析
损失图展示了生成器和判别器在训练过程中的损失变化情况。
- 生成器损失 :生成器的目标是生成能够欺骗判别器的假样本,因此其损失反映了它生成逼真样本的能力。在训练初期,生成器可能生成的样本与真实样本差异较大,判别器很容易识别,导致生成器损失较高。随着训练的进行,生成器逐渐学习到如何生成更逼真的样本,损失会逐渐下降。
- 判别器损失 :判别器的目标是正确区分真实样本和假样本,其损失由两部分组成:检测真实样本的损失和检测假样本的损失。在训练初期,判别器能够轻松区分真实和假样本,损失相对较低。但随着生成器能力的提升,判别器区分的难度增加,损失会逐渐上升。
理想情况下,生成器和判别器的损失会在训练过程中趋于稳定,并且两者的损失值会接近,这表明模型达到了一种平衡状态,生成器和判别器的能力相当。
7.2 判别器输出概率图分析
判别器输出概率图展示了判别器对真实样本和假样本的判断概率。
- 初期阶段 :在训练初期,判别器能够快速学习到真实样本和假样本的特征差异,因此对真实样本的判断概率接近1,对假样本的判断概率接近0。
- 训练后期 :随着生成器生成的样本越来越逼真,判别器逐渐难以区分真实和假样本,两者的判断概率都会接近0.5。这是GAN训练的一个理想状态,表明生成器已经学会了生成与真实样本难以区分的假样本。
8. 模型优化建议
根据训练结果的分析,我们可以提出一些模型优化的建议,以进一步提高GAN模型的性能。
8.1 调整网络结构
- 增加隐藏层 :可以尝试增加生成器和判别器的隐藏层数量,以提高模型的表达能力。但需要注意的是,过多的隐藏层可能会导致过拟合问题。
- 调整隐藏单元数量 :适当增加或减少隐藏单元的数量,以找到一个合适的模型复杂度。
8.2 优化损失函数
- 使用不同的损失函数 :除了BinaryCrossentropy损失函数,还可以尝试其他损失函数,如Wasserstein损失函数,它在GAN训练中表现出更好的稳定性。
- 添加正则化项 :在损失函数中添加正则化项,如L1或L2正则化,以防止模型过拟合。
8.3 调整训练参数
- 学习率 :学习率控制了模型参数更新的步长。如果学习率过大,模型可能会跳过最优解;如果学习率过小,模型的收敛速度会很慢。可以尝试不同的学习率,或者使用学习率衰减策略。
- 批次大小 :批次大小影响了模型的训练效率和稳定性。较大的批次大小可以提高训练效率,但可能会导致模型收敛到局部最优解;较小的批次大小可以增加模型的随机性,但训练时间会更长。
9. 总结与展望
通过本文的介绍,我们详细了解了如何实现一个简单的GAN模型,包括环境准备、网络构建、数据集处理、模型训练和结果分析等步骤。通过对训练结果的分析,我们可以评估模型的性能,并提出相应的优化建议。
GAN作为一种强大的生成模型,在图像生成、数据增强、风格迁移等领域有着广泛的应用前景。未来,我们可以进一步探索GAN的变体,如条件GAN、对抗自编码器等,以满足不同的应用需求。同时,结合其他技术,如强化学习、注意力机制等,也可以进一步提高GAN模型的性能和稳定性。
为了更清晰地展示整个GAN模型实现的流程,我们可以用以下mermaid流程图来总结:
graph LR
A[环境准备] --> B[构建生成器和判别器网络]
B --> C[模型训练设置]
C --> D[定义训练数据集]
D --> E[训练GAN模型]
E --> F[训练结果分析]
F --> G[模型优化建议]
以下是一个总结性的表格,展示了GAN模型实现的关键步骤和相关代码:
| 步骤 | 描述 | 代码示例 |
| — | — | — |
| 环境准备 | 安装必要的Python包,验证GPU可用性,挂载Google Drive | ! pip install -q tensorflow-gpu==2.0.0 等 |
| 构建网络 | 定义生成器和判别器网络 | make_generator_network 和 make_discriminator_network 函数 |
| 训练设置 | 指定模型参数,初始化网络 | image_size = (28, 28) 等 |
| 数据集处理 | 加载MNIST数据集,进行预处理 | mnist_bldr = tfds.builder('mnist') 等 |
| 模型训练 | 定义损失函数和优化器,进行训练循环 | loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True) 等 |
| 结果分析 | 绘制损失图和判别器输出概率图 | plt.plot(g_losses, label='Generator loss') 等 |
| 模型优化 | 调整网络结构、损失函数和训练参数 | 增加隐藏层、使用不同损失函数等 |
通过以上步骤和分析,我们可以深入理解GAN模型的工作原理,并掌握其实现方法。希望本文能够对读者在GAN模型的学习和应用中有所帮助。
超级会员免费看
实现:从安装到可视化&spm=1001.2101.3001.5002&articleId=154923393&d=1&t=3&u=9160154aa4dd48d2a60bcd7cdb71d653)
1882

被折叠的 条评论
为什么被折叠?



