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
条件 GAN(CGAN)通过将标签信息馈送到生成器和判别器,从而希望生成特定标签的数据,从而使我们可以控制要生成的内容。 下图显示了 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
标签提供给generator
和discriminator
:
>>> 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
表示a
和b
完全无关。
取决于潜在变量是什么,可以不同地计算I(c | G(z, c))
。 对于分类变量,它是通过cross
熵来衡量的。 对于连续变量,可以将其计算为其分布(例如高斯分布)之间的方差。
考虑到所有这些概念,我们可以开始开发 InfoGAN 模型。 回想一下,我们不需要在 InfoGAN 中提供标签数据。 因此,我们可以将load_dataset_pad
和gen_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