49、生成对抗网络(GAN)实现与训练指南

生成对抗网络(GAN)实现与训练指南

1. 环境准备

在开始实现GAN模型之前,我们需要安装必要的Python包。Colab Notebooks环境已经预装了一些常用的包,如NumPy、SciPy和TensorFlow的最新稳定版本。不过,当前Google Colab上的最新稳定版本是TensorFlow 1.15.0,而我们要使用TensorFlow 2.0。因此,需要在新的代码单元格中执行以下命令来安装支持GPU的TensorFlow 2.0:

! pip install -q tensorflow-gpu==2.0.0

在Jupyter笔记本中,以感叹号开头的单元格将被解释为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。在新的代码单元格中执行以下代码:

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是对ReLU的改进,允许负输入有非零梯度,使网络更具表达能力。其定义如下:
- 判别器网络的每个隐藏层后面还跟有一个Dropout层,用于防止过拟合。
- 生成器的输出层使用双曲正切(tanh)激活函数,这有助于学习过程。
- 判别器的输出层没有激活函数(即线性激活)以获取对数几率(logits),也可以使用sigmoid激活函数输出概率。
- 代码实现

import tensorflow as tf
import tensorflow_datasets as tfds
import numpy as np
import matplotlib.pyplot as plt

# 定义生成器网络
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

# 定义判别器网络
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
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)

输出结果如下:

input-z -- shape:    (32, 20)
input-real -- shape: (32, 784)
Output of G -- shape: (32, 784)
Disc. (real) -- shape: (32, 1)
Disc. (fake) -- shape: (32, 1)

这两个对数几率(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)

# 生成器损失
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))

# 判别器损失
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()))

以下是训练过程的mermaid流程图:

graph TD;
    A[开始训练] --> B[初始化模型和参数];
    B --> C[加载数据集并预处理];
    C --> D[开始迭代训练];
    D --> E[计算生成器损失];
    E --> F[计算生成器梯度];
    F --> G[更新生成器参数];
    G --> H[计算判别器损失];
    H --> I[计算判别器梯度];
    I --> J[更新判别器参数];
    J --> K[记录损失和判别器输出];
    K --> L{是否完成所有迭代};
    L -- 否 --> D;
    L -- 是 --> M[结束训练];

上述步骤展示了GAN模型训练的整体流程,包括环境准备、网络构建、数据集定义和模型训练等关键步骤。在后续部分,我们将继续介绍训练过程的详细代码和结果分析。

生成对抗网络(GAN)实现与训练指南

6. 完整训练循环实现

前面的代码展示了GAN模型训练的单个步骤,下面将实现完整的训练循环。我们将使用 tf.GradientTape() 计算损失相对于模型权重的梯度,并使用两个单独的Adam优化器分别优化生成器和判别器的参数。

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

# 设置数据集
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)

# 设置模型
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_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):
        # 计算生成器的损失
        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)

        # 计算g_loss的梯度
        g_grads = g_tape.gradient(g_loss,
                                  gen_model.trainable_variables)

        # 优化:应用梯度
        g_optimizer.apply_gradients(
            grads_and_vars=zip(g_grads,
                               gen_model.trainable_variables))

        # 计算判别器的损失
        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

        # 计算d_loss的梯度
        d_grads = d_tape.gradient(d_loss,
                                  disc_model.trainable_variables)

        # 优化:应用梯度
        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上应该能在不到一小时内完成。训练完成后,我们可以通过绘制判别器和生成器的损失曲线以及判别器对真实和假样本的平均概率曲线来分析模型的性能。

7. 结果分析
  • 绘制损失曲线
import itertools
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(16, 6))

# 绘制损失曲线
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)

# 绘制判别器输出曲线
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.5。

  • 可视化生成图像
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模型,并通过绘制损失曲线和可视化生成图像来评估模型的性能。以下是整个流程的关键步骤总结:
1. 环境准备 :安装必要的Python包,验证GPU可用性,挂载Google Drive。
2. 构建网络 :定义生成器和判别器网络,选择合适的激活函数。
3. 设置训练参数 :指定数据集、输入向量大小、隐藏层数量和单元数等。
4. 定义数据集 :加载MNIST数据集并进行预处理。
5. 训练模型 :使用BinaryCrossentropy损失函数和Adam优化器进行训练。
6. 结果分析 :绘制损失曲线和判别器输出曲线,可视化生成图像。

通过这些步骤,我们可以深入理解GAN模型的工作原理,并通过调整参数和网络结构来进一步优化模型性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值