Python 深度学习架构实用指南:第三、四、五部分(3)

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: Python 深度学习架构实用指南:第三、四、五部分(3)

Python 深度学习架构实用指南:第三、四、五部分(2)https://developer.aliyun.com/article/1427001

请参考以下屏幕截图以获取最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MVohGG7Z-1681704851874)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/73b48fda-d42f-4d63-a1f6-564e0b25fef5.png)]

在评估我们生成的图像的真实性时,您将需要稍后返回这些图像。

现在,让我们开始构建 GAN 模型。 首先,我们为全连接层定义包装函数,因为它在原始 GAN 中最常使用:

>>> def dense(x, n_outputs, activation=None):
...     return tf.layers.dense(x, n_outputs, activation=activation,
 kernel_initializer=
                    tf.random_normal_initializer(mean=0.0,stddev=0.02))

通过放置一些密集层,我们构建了生成器:

>>> def generator(z, alpha=0.2):
...     """
...     Generator network
...     @param z: input of random samples
...     @param alpha: leaky relu factor
...     @return: output of the generator network
...     """
...     with tf.variable_scope('generator', reuse=tf.AUTO_REUSE):
...         fc1 = dense(z, 256)
...         fc1 = tf.nn.leaky_relu(fc1, alpha)
...         fc2 = dense(fc1, 512)
...         fc2 = tf.nn.leaky_relu(fc2, alpha)
...         fc3 = dense(fc2, 1024)
...         fc3 = tf.nn.leaky_relu(fc3, alpha)
...         out = dense(fc3, 28 * 28)
...         out = tf.tanh(out)
...         return out

生成器将输入的随机噪声依次馈入三个隐藏层,分别具有 256、512 和 1,024 个隐藏单元。 请注意,每个隐藏层的激活函数是泄漏的 ReLU,这是 ReLU 的变体。 发明它是为了解决即将死去的 ReLU 问题,即对于该函数的任何负输入,其输出变为零。 它定义为f(x) = max(x, ax),其中a是介于 0 到 1 之间的斜率因子(但较小的值更常见)。 下图显示了 ReLU 和泄漏版本之间的比较(例如leak = 0.2):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAPUwHXD-1681704851874)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/cf5a3eb1-3ff7-456e-88cc-6a0da05e52df.png)]

在三个隐藏层之后,输出层随后进行 tanh 激活,将数据映射到期望图像的相同大小和范围。 类似地,我们用四个密集层构建判别器,其中三个是隐藏层,其大小与生成器中隐藏层的顺序相反:

>>> def discriminator(x, alpha=0.2):
...     """
...     Discriminator network
...     @param x: input samples, can be real or generated samples
...     @param alpha: leaky relu factor
...     @return: output logits
...     """
...     with tf.variable_scope('discriminator', reuse=tf.AUTO_REUSE):
...         fc1 = dense(x, 1024)
...         fc1 = tf.nn.leaky_relu(fc1, alpha)
...         fc2 = dense(fc1, 512)
...         fc2 = tf.nn.leaky_relu(fc2, alpha)
...         fc3 = dense(fc2, 256)
...         fc3 = tf.nn.leaky_relu(fc3, alpha)
...         out = dense(fc3, 1)
...         return out

输出层将数据映射到单个单元对率。 现在,我们可以为大小为 784 的实际输入数据和大小为 100 的噪声输入数据定义占位符:

>>> noise_size = 100
>>> tf.reset_default_graph()
>>> X_real = tf.placeholder(tf.float32, (None, 28 * 28), name='input_real')
>>> z = tf.placeholder(tf.float32, (None, noise_size), name='input_noise')

将生成器应用于输入噪声,并将判别器应用于生成的图像以及真实图像数据:

>>> g_sample = generator(z)
>>> d_real_out = discriminator(X_real)
>>> d_fake_out = discriminator(g_sample)

利用网络的所有这些输出,我们为生成器开发了loss计算,该计算基于被认为是真实的伪造图像:

>>> g_loss = tf.reduce_mean(
...             tf.nn.sigmoid_cross_entropy_with_logits(logits=d_fake_out,
 labels=tf.ones_like(d_fake_out)))
>>> tf.summary.scalar('generator_loss', g_loss)

我们还记录了损失,以便使用 TensorBoard 可视化学习进度。

接下来是用于判别器的loss计算,该计算基于两个组件:真实图像被视为伪造的图像和伪图像被视为真实的图像:

>>> d_real_loss = tf.reduce_mean(
...             tf.nn.sigmoid_cross_entropy_with_logits(logits=d_real_out,
 labels=tf.ones_like(d_real_out)))
>>> d_fake_loss = tf.reduce_mean(
...             tf.nn.sigmoid_cross_entropy_with_logits(logits=d_fake_out,
 labels=tf.zeros_like(d_fake_out)))
>>> d_loss = d_real_loss + d_fake_loss
>>> tf.summary.scalar('discriminator_loss', d_loss)

同样,我们记录了判别器损失。 然后,我们为两个网络定义优化器,如下所示:

>>> train_vars = tf.trainable_variables()
>>> d_vars = [var for var in train_vars 
                        if var.name.startswith('discriminator')]
>>> g_vars = [var for var in train_vars 
                        if var.name.startswith('generator')]
>>> learning_rate = 0.0002
>>> beta1 = 0.5
>>> with tf.control_dependencies(
                    tf.get_collection(tf.GraphKeys.UPDATE_OPS)):
...     d_opt = tf.train.AdamOptimizer(learning_rate,
 beta1=beta1).minimize(d_loss, var_list=d_vars)
...     g_opt = tf.train.AdamOptimizer(learning_rate,
 beta1=beta1).minimize(g_loss, var_list=g_vars)

优化器实现 Adam 算法,学习率为 0.0002,第一矩衰减率为 0.5。 在进行模型优化之前,不要忘记定义一个函数,该函数返回用于训练的批量数据:

>>> def gen_batches(data, batch_size, shuffle=True):
...     """
...     Generate batches for training
...     @param data: training data
...     @param batch_size: batch size
...     @param shuffle: shuffle the data or not
...     @return: batches generator
...     """
...     n_data = data.shape[0]
...     if shuffle:
...         idx = np.arange(n_data)
...         np.random.shuffle(idx)
...         data = data[idx]
...     for i in range(0, n_data, batch_size):
...         batch = data[i:i + batch_size]
...         yield batch

准备好所有组件之后,我们就可以开始训练 GAN 模型了。 对于每 100 步,我们记录生成器损失和判别器损失。 为了进行表现检查,我们创建了一组噪声输入,并显示了当前生成器针对每个周期生成的图像:

>>> epochs = 100
>>> steps = 0
>>> with tf.Session() as sess:
...     merged = tf.summary.merge_all()
...     train_writer = tf.summary.FileWriter(
                                './logdir/vanilla', sess.graph)
...     sess.run(tf.global_variables_initializer())
...     for epoch in range(epochs):
...         for batch_x in gen_batches(data, batch_size):
...         batch_z = np.random.uniform(
                            -1, 1, size=(batch_size,noise_size))
...         _, summary, d_loss_batch = sess.run(
                                [d_opt, merged, d_loss], 
                                feed_dict={z: batch_z, X_real: batch_x})
...         sess.run(g_opt, feed_dict={z: batch_z})
...         _, g_loss_batch = sess.run(
                              [g_opt, g_loss], feed_dict={z: batch_z})
...         if steps % 100 == 0:
...             train_writer.add_summary(summary, steps)
...             print("Epoch {}/{} - discriminator loss: 
                      {:.4f}, generator Loss: {:.4f}".format(
...                   epoch + 1, epochs, d_loss_batch,g_loss_batch))
...         steps += 1
...     gen_samples = sess.run(generator(z), feed_dict={z:sample_z})
...     display_images(gen_samples)

注意,在每个周期,生成器更新两次,而判别器仅更新一次。 这是因为优化判别器比生成器容易得多,这很直观。 任意图像只是毫不费力地被认为是伪造的。 如果判别器在早期出现收敛,则不完整的生成器将产生垃圾。 您还可以为两个网络指定不同的学习率,例如,生成器的学习率稍高一些,0.001,判别器的学习率则为 0.0002。

请参阅以下屏幕截图以获取第 25 阶段的最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXLDBUJL-1681704851874)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/57063103-49c3-4e1a-9493-928da7d50793.png)]

以下屏幕截图显示了周期 50 的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VDVTOLF5-1681704851875)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/82d55e5d-be9f-4a55-802a-8357a0f28d68.png)]

以下屏幕截图显示了周期 75 的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YerzcC4O-1681704851875)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/6649289f-cf2d-4183-b320-f71acdca8f14.png)]

以下屏幕截图显示了周期 100 的输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-84WKLimM-1681704851875)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/808f5423-6817-466f-ac7c-c22d7d491917.png)]

我们的第一个 GAN 模型能够合成手写数字,并且大多数看起来都是合法的! 我们还来看看 TensorBoard 中的学习图。 要运行 TensorBoard,请在终端中输入以下命令:

tensorboard --logdir=logdir/

然后,在浏览器中转到http://localhost:6006/; 我们将看到判别器的图表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tZ9NXxvE-1681704851875)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/191963c8-5cc4-4b11-97e7-f904f53528af.png)]

以下是生成器的示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTDGdhSW-1681704851875)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/1950104d-2ab2-45f4-af38-9b70e8fd86ba.png)]

您可能会注意到,有些数字看起来很奇怪。 在全连接原始模型的基础上,我们可以做哪些改进? 对于计算机视觉,使用卷积层可能是最直观的方法。

深度卷积 GAN

卷积层已成为解决图像问题的必备条件。 使用 GAN 生成图像也不例外。 因此,Radford 等人在《使用深度卷积生成对抗网络进行无监督表示学习》提出了深度卷积生成对抗网络DCGAN)。

很容易理解 DCGAN 中的判别器。 它也非常类似于用于分类的标准 CNN,其中使用一个或多个卷积层,每个卷积层之后是一个非线性层,最后是一个全连接层。 例如,我们可以具有以下架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UthW9vFX-1681704851876)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/0b0d0c89-997f-4b2c-b6e1-951cb7ea6395.png)]

卷积层共有三个,分别由 64 个,128 个和 256 个5 x 5过滤器组成。

如前所述,生成器通常与判别器对称。 DCGAN 中的判别器通过卷积层解释输入图像并生成数字输出。 因此,生成器需要使用转置的卷积层将数字噪声输入转换为图像,这与卷积层完全相反。 卷积层执行下采样,而转置卷积层执行上采样。 例如,我们可以为生成器使用以下架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4lTH2ECw-1681704851876)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/a9c5016a-29c9-480f-8680-a55ca4d3d4cf.png)]

考虑到所有这些概念,让我们实现 DCGAN。 同样,我们从定义卷积和转置卷积层的包装函数开始,因为它们在 DCGAN 中使用最频繁:

>>> def conv2d(x, n_filters, kernel_size=5):
...     return tf.layers.conv2d(inputs=x, filters=n_filters,
 kernel_size=kernel_size, strides=2, padding="same",
 kernel_initializer=tf.random_normal_initializer(
 mean=0.0, stddev=0.02)) 
>>> def transpose_conv2d(x, n_filters, kernel_size=5):
...     return tf.layers.conv2d_transpose(inputs=x,
 filters=n_filters, kernel_size=kernel_size, strides=2,
 padding='same', kernel_initializer=
 tf.random_normal_initializer(mean=0.0, stddev=0.02))

我们定义的密集函数可用于全连接层。 同样,我们在卷积层的输出上应用批量归一化。 批量规范化背后的思想类似于输入数据规范化,可加快学习速度。 通过从激活层的输出中减去批量平均值,然后将其除以批量标准差来执行批量标准化。 我们为批量标准化定义了一个包装器函数,如下所示:

>>> def batch_norm(x, training, epsilon=1e-5, momentum=0.9):
...     return tf.layers.batch_normalization(x, training=training,
 epsilon=epsilon, momentum=momentum)

现在,我们可以使用刚刚定义的组件来构造判别器:

>>> def discriminator(x, alpha=0.2, training=True):
...     """
...     Discriminator network for DCGAN
...     @param x: input samples, can be real or generated samples
...     @param alpha: leaky relu factor
...     @param training: whether to return the output in training mode 
                        (normalized with statistics of the current batch)
...     @return: output logits
...     """
...     with tf.variable_scope('discriminator', reuse=tf.AUTO_REUSE):
...         conv1 = conv2d(x, 64)
...         conv1 = tf.nn.leaky_relu(conv1, alpha)
...         conv2 = conv2d(conv1, 128)
...         conv2 = batch_norm(conv2, training=training)
...         conv2 = tf.nn.leaky_relu(conv2, alpha)
...         conv3 = conv2d(conv2, 256)
...         conv3 = batch_norm(conv3, training=training)
...         conv3 = tf.nn.leaky_relu(conv3, alpha)
...         fc = tf.layers.flatten(conv3)
...         out = dense(fc, 1)
...         return out

这很容易。 三个卷积层分别包含 64、128 和 256 个5 x 5过滤器。

开发生成器有点棘手。 回想一下,我们需要首先将输入的一维噪声整形为三维图像,以启用转置卷积。 我们知道,由于两个网络的对称性,第三维是 256。 那么,前两个维度是什么? 它们是2 x 2,在第一个转置的卷积层之后变为4 x 4,第二个之后的为8 x 8,第三个之后的为16 x 16,如果它是3 x 3,则与我们的28 x 28的目标相去甚远。 同样,它在第三个转置的卷积层之后变为24 x 24,这又不够大。 如果它是4 x 4,则在第三个转置的卷积层之后变为32 x 32。 因此,将线性输入重塑为4 x 4图像就足够了。 请注意,现在生成的图像输出的大小为32 x 32,这与我们的真实图像的大小4 x 4不同。要确保对判别器的输入恒定,我们只需要在真实图像上填充零即可。 在load_dataset函数的顶部实现了实图像的零填充:

>>> def load_dataset_pad():
... (x_train, y_train), (x_test, y_test)=
 tf.keras.datasets.mnist.load_data('./mnist_data')
... train_data = np.concatenate((x_train, x_test), axis=0)
... train_data = train_data / 255.
... train_data = train_data * 2\. - 1
... train_data = train_data.reshape([-1, 28, 28, 1])
... train_data = np.pad(train_data, ((0,0),(2,2),(2,2),(0,0)),
 'constant', constant_values=0.)
... return train_data

由于 DCGAN 中的判别器接受三维图像输入,因此训练数据也被重塑为(70000, 32, 32, 1)

>>> data = load_dataset_pad()
>>> print("Training dataset shape:", data.shape)
Training dataset shape: (70000, 32, 32, 1)

加载数据后,我们可以继续定义生成器:

>>> def generator(z, n_channel, training=True):
...     """
...     Generator network for DCGAN
...     @param z: input of random samples
...     @param n_channel: number of output channels
...     @param training: whether to return the output in training mode (normalized with statistics of the current batch)
...     @return: output of the generator network
...     """
...     with tf.variable_scope('generator', reuse=tf.AUTO_REUSE):
...         fc = dense(z, 256 * 4 * 4, activation=tf.nn.relu)
...         fc = tf.reshape(fc, (-1, 4, 4, 256))
...         trans_conv1 = transpose_conv2d(fc, 128)
...         trans_conv1 = batch_norm(trans_conv1, training=training)
...         trans_conv1 = tf.nn.relu(trans_conv1)
...         trans_conv2 = transpose_conv2d(trans_conv1, 64)
...         trans_conv2 = batch_norm(trans_conv2, training=training)
...         trans_conv2 = tf.nn.relu(trans_conv2)
...         trans_conv3 = transpose_conv2d(trans_conv2, n_channel)
...         out = tf.tanh(trans_conv3)
...         return out

首先,它将噪声输入映射到具有 4,096 个单元的全连接层,以便它可以重塑大小为4 x 4 x 256的三维数据,然后由三个转置的卷积层消耗。

现在,我们可以为尺寸为28 x 28 x 1的实际输入数据定义占位符:

>>> image_size = data.shape[1:]
>>> tf.reset_default_graph()
>>> X_real = tf.placeholder(
                tf.float32, (None,) + image_size, name='input_real')

噪声输入数据和其余参数与上一节中的相同,因此我们跳过重复相同的代码。 接下来,我们将生成器应用于输入噪声:

>>> g_sample = generator(z, image_size[2])

其余部分,包括图像判别器,损失计算和优化器,将重用上一节中的内容。

准备好所有组件之后,我们现在就可以开始训练我们的 DCGAN 模型了。 同样,我们记录每 100 步的损失,并显示每个周期(此时总共 50 个周期)的合成图像:

>>> epochs = 50
>>> steps = 0
>>> with tf.Session() as sess:
...     merged = tf.summary.merge_all()
...     train_writer = tf.summary.FileWriter(
                                    './logdir/dcgan', sess.graph)
...     sess.run(tf.global_variables_initializer())
...     for epoch in range(epochs):
...         for batch_x in gen_batches(data, batch_size):
...             batch_z = np.random.uniform(
                                -1, 1, size=(batch_size, noise_size))
...             _, summary, d_loss_batch = sess.run(
                                    [d_opt, merged,d_loss], feed_dict=
                                    {z: batch_z, X_real: batch_x})
...             sess.run(g_opt, feed_dict={z: batch_z, X_real:batch_x})
...             _, g_loss_batch = sess.run([g_opt, g_loss], feed_dict=
                                        {z: batch_z, X_real: batch_x})
...             if steps % 100 == 0:
...                 train_writer.add_summary(summary, steps)
...                 print("Epoch {}/{} - discriminator loss: {:.4f},
                        generator Loss: {:.4f}".format(epoch + 1, epochs,     
                        d_loss_batch, g_loss_batch))
...             steps += 1
...         gen_samples = sess.run(generator(z, image_size[2], 
                          training=False), feed_dict={z: sample_z})
...         display_images(gen_samples, 32)

请参阅以下屏幕截图以获取第 25 阶段的最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eU4A6t2I-1681704851876)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/aea8fb10-e001-499b-b340-4fdf1ba91049.png)]

最后,请参考以下屏幕截图,以获取第 50 阶段的最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vV22UWLy-1681704851876)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/a1b7db94-156e-4319-bb92-28b298aec238.png)]

TensorBoard 中显示的学习图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uWGxCVY1-1681704851876)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/6c0103ec-9a04-4eaa-8db2-0c53fe05a805.png)]

下图显示了生成器损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nrxYshbJ-1681704851877)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/1dff13c3-702f-4b6d-b732-bf99065d0ddf.png)]

从我们的 DCGAN 模型生成的图像看起来比从普通 GAN 生成的图像更真实。 我们还将它们与真实图像一起放置; 没有提示,您能告诉哪个集是真集还是假集?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vc1rqFpQ-1681704851877)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/bcd453db-4e4f-44d0-9ec4-dc73084e1e71.png)]

到目前为止,就我们无法控制要产生的 0 到 9 而言,我们生成的数字是相当随机的。 这是因为原始 GAN 和 DCGAN 中的生成器仅吸收随机噪声,只要结果看起来是真实的,就不再限制生成什么。 我们将看到条件 GAN 和 infoGAN 如何启用此功能。

条件 GAN

条件 GANCGAN)通过将标签信息馈送到生成器和判别器,从而希望生成特定标签的数据,从而使我们可以控制要生成的内容。 下图显示了 CGAN 的架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xKu4lXle-1681704851877)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/375b5ec0-b493-4957-a4d2-fd4e88a04c77.png)]

如我们所见,标签数据是 CGAN 中生成器和判别器的输入空间的扩展。 注意,标签数据表示为一热向量。 例如,MNIST 数据集中的数字 2 变为[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]。 CGAN 的其他部分,例如cost函数,与常规 GAN 相似。 因此,实现 CGAN 应该很容易。 我们可以开发一个全连接 CGAN,但是 CGAN 中的隐藏层不限于全连接层。 您可以尝试实现卷积版本作为练习。

首先,我们需要修改data load函数以包含标签:

>>> def load_dataset_label():
...     from keras.utils import np_utils
...     (x_train, y_train), (x_test, y_test) 
                    =tf.keras.datasets.mnist.load_data('./mnist_data')
...     x_data = np.concatenate((x_train, x_test), axis=0)
...     y_train = np_utils.to_categorical(y_train)
...     y_test = np_utils.to_categorical(y_test)
...     y_data = np.concatenate((y_train, y_test), axis=0)
...     x_data = x_data / 255.
...     x_data = x_data * 2\. - 1
...     x_data = x_data.reshape([-1, 28 * 28])
...     return x_data, y_data

该函数还将标签数据从一维转换为一热编码的十维:

>>> x_data, y_data = load_dataset_label()
>>> print("Training dataset shape:", x_data.shape) Training dataset shape: (70000, 784)

因此,batch生成函数也需要更新,以便它返回一批图像和标签:

>>> def gen_batches_label(x_data, y_data, batch_size, shuffle=True):
...     """
...     Generate batches including label for training
...     @param x_data: training data
...     @param y_data: training label
...     @param batch_size: batch size
...     @param shuffle: shuffle the data or not
...     @return: batches generator
...     """
...     n_data = x_data.shape[0]
...     if shuffle:
...         idx = np.arange(n_data)
...         np.random.shuffle(idx)
...         x_data = x_data[idx]
...         y_data = y_data[idx]
...     for i in range(0, n_data - batch_size, batch_size):
...         x_batch = x_data[i:i + batch_size]
...         y_batch = y_data[i:i + batch_size]
...         yield x_batch, y_batch

稍后,我们将标签数据的占位符定义为新输入:

>>> n_classes = 10
>>> y = tf.placeholder(tf.float32, shape=[None, n_classes],name='y_classes')

生成器获取标签数据,并将其与输入噪声连接起来:

>>> def generator(z, y, alpha=0.2):
...     """
...     Generator network for CGAN
...     @param z: input of random samples
...     @param y: labels of the input samples
...     @param alpha: leaky relu factor
...     @return: output of the generator network
...     """
...     with tf.variable_scope('generator', reuse=tf.AUTO_REUSE):
...         z_y = tf.concat([z, y], axis=1)
...         fc1 = dense(z_y, 256)
...         fc1 = tf.nn.leaky_relu(fc1, alpha)
...         fc2 = dense(fc1, 512)
...         fc2 = tf.nn.leaky_relu(fc2, alpha)
...         fc3 = dense(fc2, 1024)
...         fc3 = tf.nn.leaky_relu(fc3, alpha)
...         out = dense(fc3, 28 * 28)
...         out = tf.tanh(out)
...         return out

判别器做同样的事情:

>>> def discriminator(x, y, alpha=0.2):
...     """
...     Discriminator network for CGAN
...     @param x: input samples, can be real or generated samples
...     @param y: labels of the input samples
...     @param alpha: leaky relu factor
...     @return: output logits
...     """
...     with tf.variable_scope('discriminator', reuse=tf.AUTO_REUSE):
...         x_y = tf.concat([x, y], axis=1)
...         fc1 = dense(x_y, 1024)
...         fc1 = tf.nn.leaky_relu(fc1, alpha)
...         fc2 = dense(fc1, 512)
...         fc2 = tf.nn.leaky_relu(fc2, alpha)
...         fc3 = dense(fc2, 256)
...         fc3 = tf.nn.leaky_relu(fc3, alpha)
...         out = dense(fc3, 1)
...         return out

现在,我们将y标签提供给generatordiscriminator

>>> g_sample = generator(z, y)
>>> d_real_out = discriminator(X_real, y)
>>> d_fake_out = discriminator(g_sample, y)

为了进行质量检查,我们在给定噪声输入的每个周期合成图像,并带有 10 个类别的集合的标签。 样本标签定义如下:

>>> n_sample_display = 40
>>> sample_y = np.zeros(shape=(n_sample_display, n_classes))
>>> for i in range(n_sample_display):
...     j = i % 10
...     sample_y[i, j] = 1

训练部分之前的其余代码与原始 GAN 模型中的代码相同。

准备好所有组件之后,我们就可以开始训练 CGAN 模型了:

>>> steps = 0
>>> with tf.Session() as sess:
...     merged = tf.summary.merge_all()
...     train_writer = tf.summary.FileWriter('./logdir/cgan',sess.graph)
...     sess.run(tf.global_variables_initializer())
...     for epoch in range(epochs):
...         for batch_x, batch_y in gen_batches_label(
                                        x_data, y_data,batch_size):
...             batch_z = np.random.uniform(-1, 1, 
                                        size=(batch_size, noise_size))
...             _, summary, d_loss_batch = sess.run([d_opt, merged,d_loss], 
                                            feed_dict={z: batch_z, 
                                            X_real: batch_x, y: batch_y})
...             sess.run(g_opt, feed_dict={z: batch_z, y: batch_y})
...             _, g_loss_batch = sess.run([g_opt, g_loss], feed_dict=
                                           {z: batch_z, y: batch_y})
...             if steps % 100 == 0:
...                 train_writer.add_summary(summary, steps)
...                 print("Epoch {}/{} - discriminator loss: {:.4f}, 
                          generator Loss: {:.4f}".format(
...                       epoch + 1, epochs, d_loss_batch, g_loss_batch))
...             steps += 1
...         gen_samples = sess.run(generator(z, y), 
                                    feed_dict={z:sample_z, y: sample_y})
...         display_images(gen_samples)

请参阅以下屏幕截图,以获取第 50 阶段的最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56ckJpVR-1681704851877)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/06ad0455-f41e-4613-9b0d-6ff07d2849ca.png)]

并参考以下屏幕截图以获取第 100 个周期的最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K5dhMXkf-1681704851877)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/7c085cd3-af8b-4488-9730-194e54a09623.png)]

TensorBoard 中显示的学习图如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R3RSuZpL-1681704851878)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/6c0bddb0-18e5-42b9-91f9-0d475ebc4609.png)]

下图显示了生成器损失:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8oyNsuo-1681704851878)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/28dc5e10-92cb-4ac4-9be0-6de392a19d01.png)]

使用 CGAN,我们可以完全控制要生成的数字,并且还可以使用 InfoGAN 来控制其他属性,例如宽度或旋转度。

InfoGAN

InfoGANs最大化生成对抗网络信息的缩写)在某种意义上类似于 CGAN,因为两个生成器网络都接受一个附加参数,并且条件变量c,例如标签信息。 他们都试图学习相同的条件分布P(X | z, c。 InfoGAN 与 CGAN 的区别在于它们处理条件变量的方式。

CGAN 认为条件变量是已知的。 因此,在训练期间将条件变量显式馈送到判别器。 相反,InfoGAN 假设条件变量是未知的且是潜在的,我们需要根据训练数据来推断条件变量。 InfoGAN 中的判别器负责推导后验P(c | X)。 下图显示了 InfoGAN 的架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PjpxUjSG-1681704851878)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/d14dc2c1-cad3-4641-91ff-4870147970e1.png)]

由于我们不需要将条件变量提供给将自动推断出条件的判别器,因此我们可以将其基本分配给任何与数据相关的事物。 它不仅限于标签。 它可以是边缘的宽度,旋转角度和特定样式。 此外,它不仅限于一个变量,也不限于诸如标签之类的分类值。 我们可以有多个变量或一个标签变量,以及一个或多个连续变量作为潜在特征。

那么,InfoGAN 如何学习潜在特征? 顾名思义,它们是通过最大化信息来实现的,信息是指信息论中的互信息。 我们希望最大化c与生成器生成的输出之间的相互信息。 InfoGAN 的loss函数可以概括如下:

L_InfoGAN = (D, G) = L(D, G) - I(c, G(z, c))

此处,L(D, G)是常规 GAN 中的loss函数,I(c | G(z, c))c和生成的输出之间的互信息。 更准确地,将P(c | G(z, c))预测为更高的I(c | G(z, c))

互信息I(a, b)衡量如果我们知道b,我们对a的了解程度。P(a | b)(或P(b | a))预测得越准确,I(a, b)越高。 I(a, b) = 0表示ab完全无关。

取决于潜在变量是什么,可以不同地计算I(c | G(z, c))。 对于分类变量,它是通过cross熵来衡量的。 对于连续变量,可以将其计算为其分布(例如高斯分布)之间的方差。

考虑到所有这些概念,我们可以开始开发 InfoGAN 模型。 回想一下,我们不需要在 InfoGAN 中提供标签数据。 因此,我们可以将load_dataset_padgen_batches函数用作 DCGAN。 让我们像往常一样先加载数据:

>>> data = load_dataset_pad()
>>> print("Training dataset shape:", data.shape)
>>> Training dataset shape: (70000, 32, 32, 1)

现在,我们为条件变量定义一个占位符,作为生成器的额外输入:

>>> n_classes = 10
>>> n_cont = 1
>>> c = tf.placeholder(tf.float32, shape=[None, n_classes + n_cont],     
                        name='conditional_variable')

此示例中的潜在特征包括 10 维一热编码特征和一维连续热特征。 现在,我们定义 InfoGAN 的生成器,它生成条件变量并将其与输入噪声连接起来:

>>> def generator(z, c, n_channel, training=True):
...     """
...     Generator network for InfoGAN
...     @param z: input of random samples
...     @param c: latent features for the input samples
...     @param n_channel: number of output channels
...     @param training: whether to return the output in training mode 
                       (normalized with statistics of the current batch)
...     @return: output of the generator network
...     """
...     with tf.variable_scope('generator', reuse=tf.AUTO_REUSE):
...         z_c = tf.concat([z, c], axis=1)
...         fc = dense(z_c, 256 * 4 * 4, activation=tf.nn.relu)
...         fc = tf.reshape(fc, (-1, 4, 4, 256))
...         trans_conv1 = transpose_conv2d(fc, 128)
...         trans_conv1 = batch_norm(trans_conv1, training=training)
...         trans_conv1 = tf.nn.relu(trans_conv1)
...         trans_conv2 = transpose_conv2d(trans_conv1, 64)
...         trans_conv2 = batch_norm(trans_conv2, training=training)
...         trans_conv2 = tf.nn.relu(trans_conv2)
...         trans_conv3 = transpose_conv2d(trans_conv2, n_channel)
...         out = tf.tanh(trans_conv3)
...         return out

至于判别器,其前半部分与 DCGAN 的判别器相同,后者由三组卷积层组成。 它的后半部分由两个全连接层组成,后面是三组输出:判别器对率(用于确定图像是真实的还是伪造的),连续变量的后验和分类变量的后验:

>>> def discriminator(x, n_classes, n_cont=1, alpha=0.2, training=True):
...     """
...     Discriminator network for InfoGAN
...     @param x: input samples, can be real or generated samples
...     @param n_classes: number of categorical latent variables
...     @param n_cont: number of continuous latent variables
...     @param alpha: leaky relu factor
...     @param training: whether to return the output in training mode 
                        (normalized with statistics of the current batch)
...     @return: discriminator logits, posterior for the continuous
 variable, posterior for the categorical variable
...     """
...     with tf.variable_scope('discriminator', reuse=tf.AUTO_REUSE):
...         conv1 = conv2d(x, 64)
...         conv1 = tf.nn.leaky_relu(conv1, alpha)
...         conv2 = conv2d(conv1, 128)
...         conv2 = batch_norm(conv2, training=training)
...         conv2 = tf.nn.leaky_relu(conv2, alpha)
...         conv3 = conv2d(conv2, 256)
...         conv3 = batch_norm(conv3, training=training)
...         conv3 = tf.nn.leaky_relu(conv3, alpha)
...         fc1 = tf.layers.flatten(conv3)
...         fc1 = dense(fc1, 1024)
...         fc1 = batch_norm(fc1, training=training)
...         fc1 = tf.nn.leaky_relu(fc1, alpha)
...         fc2 = dense(fc1, 128)
...         d_logits = dense(fc2, 1)
...         cont = dense(fc2, n_cont)
...         classes = dense(fc2, n_classes)
...         return d_logits, cont, classes

现在,我们将生成器和判别器应用于输入数据以及条件变量:

>>> g_sample = generator(z, c, image_size[2])
>>> d_real_logits, d_real_cont, d_real_cat = discriminator(
                                            X_real, n_classes, n_cont)
>>> d_fake_logits, d_fake_cont, d_fake_cat = discriminator(
                                             g_sample, n_classes, n_cont)

回想一下,InfoGAN 中的损失函数由两部分组成。 第一部分与标准 GAN 相同:

>>> g_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(     
                logits=d_fake_logits, labels=tf.ones_like(d_fake_logits)))
>>> d_real_loss = tf.reduce_mean(
...                     tf.nn.sigmoid_cross_entropy_with_logits(
                        logits=d_real_logits, labels=tf.ones_like(d_real_logits)))
>>> d_fake_loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
                            logits=d_fake_logits, 
                            labels=tf.zeros_like(d_fake_logits)))
>>> d_loss = d_real_loss + d_fake_loss

第二部分是相互信息。 对于单热编码的分类变量,通过交叉熵来度量:

>>> cat = c[:, n_cont:]
>>> d_cat_loss = tf.reduce_mean(
...                 tf.nn.softmax_cross_entropy_with_logits(
                    logits=d_fake_cat, labels=cat))

对于连续变量,可以将其计算为其分布(例如高斯分布)之间的方差:

>>> d_cont_loss = tf.reduce_sum(tf.square(d_fake_cont))

信息损失(与互信息相反)是这两种损失的加权和:

>>> lambda_cont = 0.1
>>> lambda_cat = 1.0
>>> d_info_loss = lambda_cont * d_cont_loss + lambda_cat * cat_loss

生成器和判别器的最终损失如下:

>>> g_loss += d_info_loss
>>> tf.summary.scalar('generator_loss', g_loss)
>>> d_loss += d_info_loss
>>> tf.summary.scalar('discriminator_loss', d_loss)

我们还需要开发一个函数来生成用于训练的随机条件变量:

>>> def gen_condition_variable(n_size, n_classes, n_cont):
...     cont = np.random.randn(n_size, n_cont)
...     cat = np.zeros((n_size, n_classes))
...     cat[range(n_size), np.random.randint(0, n_classes, n_size)] = 1
...     return np.concatenate((cont, cat), axis=1)

为了进行质量检查,我们在给定噪声输入的每个周期合成图像,并与一组 10 类的条件变量和一组恒定连续变量进行合成。 样本条件变量的定义如下:

>>> n_sample_display = 40
>>> sample_c = np.zeros((n_sample_display, n_cont + n_classes))
>>> for i in range(n_sample_display):
...     j = i % 10
...     sample_c[i, j + 1] = 1
...     sample_c[i, 0] = -3 + int(i / 10) * 2

第 1,第 11 ,第 21 和第 31 个样本被赋予标签0,而第 2,第 12,第 22 和第 32 个样本被赋予标签1,依此类推。 前 10 个样本被赋予连续值-3,接下来的 10 个样本被赋予-1,然后被赋予1,最后被赋予最后的 10 个样本3。 训练超过 50 个周期如下:

>>> steps = 0
>>> with tf.Session() as sess:
...     merged = tf.summary.merge_all()
...     train_writer = tf.summary.FileWriter('./logdir/infogan',sess.graph)
...     sess.run(tf.global_variables_initializer())
...     for epoch in range(epochs):
...         for x in gen_batches(data, batch_size):
...             batch_z = np.random.uniform(
                            -1, 1, size=(batch_size, noise_size))
...             batch_c = gen_condition_variable(
                                    batch_size, n_classes, n_cont)
...             _, summary, d_loss_batch = sess.run([d_opt, merged, 
                                     d_loss], feed_dict= 
                                     {z: batch_z, X_real: x, c: batch_c})
...             sess.run(g_opt, feed_dict= 
                            {z: batch_z, X_real: x, c: batch_c})
...             _, g_loss_batch = sess.run([g_opt, g_loss],
                            feed_dict={z: batch_z, X_real: x, c: batch_c})
...             if steps % 100 == 0:
...                 train_writer.add_summary(summary, steps)
...                 print("Epoch {}/{} - discriminator loss: {:.4f},
                        generator Loss: {:.4f}".format(
...                     epoch + 1, epochs, d_loss_batch, g_loss_batch))
...             steps += 1
...             gen_samples = sess.run(generator(z, c,image_size[2],
                                training=False), 
                                feed_dict={z: sample_z, c: sample_c})
...             display_images(gen_samples, 32)

请参阅以下屏幕截图,以获取第 20 阶段的最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gM9ZVFsh-1681704851879)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/c21e4263-3f9a-4ae3-8fe8-c0c6955fcb9d.png)]

有关周期 40 的最终结果,请参考以下屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xEqvt4zB-1681704851879)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/f9dbc58c-43a8-4691-a0f4-458ff9c60710.png)]

有关最后一个周期的最终结果,请参考以下屏幕截图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MekTUTaz-1681704851879)(https://gitcode.net/apachecn/apachecn-dl-zh/-/raw/master/docs/handson-dl-arch-py/img/d0b59c10-802b-4bee-96d7-63d5d2ce342d.png)]

您可能已经注意到,生成的图像不是 0 到 9 的顺序。我们做错了吗? 幸运的是,没有。 回想一下,条件变量c在模型之前是未知的,并且是在训练期间推断出来的。 标签 0 不一定表示模型的数字 0。 但是模型获得的知识是类别 0 与任何其他类别都不同。 因此,从 0 到 9 的标签仅代表 10 个不同的类别。 事实证明,在我们的案例中,类别 0 到 9 代表数字 3、0、2、6、4、7、5、8、9、1。连续变量呢? 我们可以看到生成的图像的旋转行为逐行不同,尤其是 0、7、8、9 和 1。第一行(输入连续值-3的前 10 张图像)显示从垂直轴开始逆时针旋转 20 度。 最后一行(具有输入连续值3的最后 10 张图像)显示从垂直轴顺时针旋转 20 度。

使用 InfoGAN,除了生成的图像类别之外,我们还扩展了对宽度或旋转等属性的控制。

总结

我们刚刚完成了有关深度学习架构 GAN 的学习旅程的重要部分! 在本章中,我们更加熟悉 GAN 及其变体。 我们从 GAN 入手。 GAN 的演进路径; 以及它们如何在数据合成(例如图像生成,音频和视频生成)中如此流行。 我们还研究了四种 GAN 架构,即原始 GAN,深度卷积 GAN,条件 GAN 和信息最大化的 GAN。 我们从头开始实现了每个 GAN 模型,并使用它们来生成看起来是真实的数字图像。

GAN 是近几年来深度学习的一项伟大发明。 在下一章中,我们将讨论深度学习的其他最新进展,包括贝叶斯神经网络胶囊网络元学习

第 5 节:深度学习和高级人工智能的未来

在本节中,我们想谈谈一些深度学习方面的想法,这些想法我们今年很有影响力,并且将来会更加突出。

本节将介绍以下章节:

  • “第 8 章”,“深度学习的新趋势”

Python 深度学习架构实用指南:第三、四、五部分(4)https://developer.aliyun.com/article/1427003

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
7月前
|
机器学习/深度学习 自然语言处理 算法框架/工具
Python 深度学习架构实用指南:第三、四、五部分(2)
Python 深度学习架构实用指南:第三、四、五部分(2)
91 1
|
7月前
|
机器学习/深度学习 搜索推荐 TensorFlow
Python 深度学习架构实用指南:第一、二部分(3)
Python 深度学习架构实用指南:第一、二部分(3)
75 2
|
7月前
|
机器学习/深度学习 人工智能 TensorFlow
Python 深度学习架构实用指南:第一、二部分(1)
Python 深度学习架构实用指南:第一、二部分(1)
159 2
|
7月前
|
机器学习/深度学习 人工智能 算法
Python 深度学习架构实用指南:第三、四、五部分(4)
Python 深度学习架构实用指南:第三、四、五部分(4)
71 1
|
7月前
|
机器学习/深度学习 并行计算 算法框架/工具
在Python中进行深度学习环境准备
在Python中进行深度学习环境准备
91 4
|
7月前
|
机器学习/深度学习 Web App开发 自然语言处理
Python 深度学习架构实用指南:第三、四、五部分(1)
Python 深度学习架构实用指南:第三、四、五部分(1)
55 0
|
7月前
|
机器学习/深度学习 数据采集 TensorFlow
在Python中进行深度学习处理
在Python中进行深度学习处理
76 2
|
7月前
|
机器学习/深度学习 TensorFlow 语音技术
Python 深度学习(二)(3)
Python 深度学习(二)
34 0
Python 深度学习(二)(3)
|
7月前
|
机器学习/深度学习 Rust 算法框架/工具
Python 深度学习(二)(1)
Python 深度学习(二)
35 0
Python 深度学习(二)(1)
|
7月前
|
机器学习/深度学习 运维 算法
Python 深度学习(三)(3)
Python 深度学习(三)
56 0
Python 深度学习(三)(3)