Machine-Learning-Mastery-生成对抗网络教程-二-

285 阅读1小时+

Machine Learning Mastery 生成对抗网络教程(二)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何在 Keras 中实现 GAN Hacks 来训练稳定模型

原文:machinelearningmastery.com/how-to-code…

最后更新于 2019 年 7 月 12 日

生成对抗网络,或称 GANs,训练起来很有挑战性。

这是因为该架构同时涉及一个生成器和一个鉴别器模型,它们在零和游戏中竞争。这意味着一个模型的改进是以另一个模型的表现下降为代价的。结果是一个非常不稳定的训练过程,经常会导致失败,例如,一个生成器总是生成相同的图像或生成无意义的图像。

因此,有许多试探法或最佳实践(称为“”)可以在配置和训练您的 GAN 模型时使用。多年来,实践者在一系列问题上测试和评估数百或数千个配置操作组合,来之不易。

*其中一些试探法可能很难实现,尤其是对初学者来说。

此外,它们中的一些或全部对于给定的项目可能是必需的,尽管可能不清楚应该采用哪一子集的试探法,这需要实验。这意味着一个实践者必须准备好实现一个给定的启发,而不需要太多的注意。

在本教程中,您将发现如何实现一套最佳实践或 GAN 黑客,您可以直接复制并粘贴到您的 GAN 项目。

阅读本教程后,您将知道:

  • 开发生成对抗网络时实用启发式或黑客攻击的最佳来源。
  • 如何从零开始实现深度卷积 GAN 模型架构的七个最佳实践?
  • 如何实现 Soumith Chintala 的 GAN Hacks 演示文稿和列表中的四个附加最佳实践。

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

How to Implement Hacks to Train Stable Generative Adversarial Networks

如何实现黑客训练稳定的生成对抗网络 图片由 BLM 内华达提供,保留部分权利。

教程概述

本教程分为三个部分;它们是:

  1. 训练稳定遗传神经网络的启发式方法
  2. 深度卷积遗传算法的最佳实践
    1. 使用条纹卷积进行下采样
    2. 使用条纹卷积进行上采样
    3. 用漏嘴
    4. 使用批处理规范化
    5. 使用高斯权重初始化
    6. 使用亚当随机梯度下降
    7. 将图像缩放到范围[-1,1]
  3. 我是史密斯钦塔拉的 GAN Hacks
    1. 使用高斯潜在空间
    2. 真假图像分离手表
    3. 使用标签平滑
    4. 使用有噪声的标签

训练稳定遗传神经网络的启发式方法

GANs 很难训练。

在撰写本文时,关于如何设计和训练 GAN 模型,还没有很好的理论基础,但是已经有了启发式的既定文献,或者“T0”黑客,它们已经被经验证明在实践中运行良好。

因此,在开发 GAN 模型时,有一系列最佳实践需要考虑和实现。

建议的配置和训练参数的两个最重要的来源可能是:

  1. 亚历克·拉德福德等人 2015 年的论文,介绍了 DCGAN 架构。
  2. soumith Chintala 2016 年的演讲和相关的“ GAN Hacks ”列表。

在本教程中,我们将探索如何从这两个来源实现最重要的最佳实践。

深度卷积遗传算法的最佳实践

在设计和训练稳定的 GAN 模型方面,最重要的一步可能是亚历克·拉德福德等人在 2015 年发表的论文,题为“利用深度卷积生成对抗网络的无监督表示学习

在论文中,他们描述了深度卷积 GAN,或 DCGAN,这种 GAN 开发方法已经成为事实上的标准。

在本节中,我们将研究如何为 DCGAN 模型架构实现七种最佳实践。

1.使用条纹卷积进行下采样

鉴别器模型是一个标准的卷积神经网络模型,它将图像作为输入,并且必须输出一个关于它是真的还是假的二进制分类。

深度卷积网络的标准做法是使用池化层对具有网络深度的输入和要素图进行下采样。

DCGAN 不建议这样做,相反,他们建议使用条纹卷积进行下采样。

这包括按照常规定义卷积层,但不是使用默认的二维步长(1,1)将其更改为(2,2)。这具有对输入进行下采样的效果,特别是将输入的宽度和高度减半,从而产生面积为四分之一的输出要素图。

下面的示例用单个隐藏卷积层来演示这一点,该隐藏卷积层通过将“步长参数设置为(2,2)来使用下采样的步进卷积。效果是模型将输入从 64×64 下采样到 32×32。

# example of downsampling with strided convolutions
from keras.models import Sequential
from keras.layers import Conv2D
# define model
model = Sequential()
model.add(Conv2D(64, kernel_size=(3,3), strides=(2,2), padding='same', input_shape=(64,64,3)))
# summarize model
model.summary()

运行该示例显示了卷积层输出的形状,其中要素图有四分之一的面积。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 32, 32, 64)        1792
=================================================================
Total params: 1,792
Trainable params: 1,792
Non-trainable params: 0
_________________________________________________________________

2.使用条纹卷积进行上采样

生成器模型必须从潜在空间的随机点生成作为输入的输出图像。

实现这一点的推荐方法是使用带有交错卷积的转置卷积层。这是一种特殊类型的层,可以反向执行卷积运算。直观地说,这意味着设置 2×2 的步幅将产生相反的效果,对输入进行上采样,而不是在正常卷积层的情况下对其进行下采样。

通过堆叠具有交错卷积的转置卷积层,生成器模型能够将给定的输入缩放到期望的输出维度。

下面的例子用一个隐藏的转置卷积层演示了这一点,该层通过将“步长参数设置为(2,2)来使用上采样的步进卷积。

效果是模型将输入从 64×64 增加到 128×128。

# example of upsampling with strided convolutions
from keras.models import Sequential
from keras.layers import Conv2DTranspose
# define model
model = Sequential()
model.add(Conv2DTranspose(64, kernel_size=(4,4), strides=(2,2), padding='same', input_shape=(64,64,3)))
# summarize model
model.summary()

运行该示例显示了卷积层输出的形状,其中特征图的面积是四倍。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_transpose_1 (Conv2DTr (None, 128, 128, 64)      3136
=================================================================
Total params: 3,136
Trainable params: 3,136
Non-trainable params: 0
_________________________________________________________________

3.用漏嘴

整流线性激活单元,简称 ReLU,是一个简单的计算,直接返回作为输入提供的值,如果输入小于等于 0.0,则返回 0.0。

一般开发深度卷积神经网络已经成为一种最佳实践。

GANs 的最佳实践是使用 ReLU 的变体,它允许一些值小于零,并学习每个节点的截止点。这被称为泄漏整流线性激活单元,简称 LeakyReLU。

可以为泄漏率指定负斜率,建议默认值为 0.2。

最初,ReLU 被推荐用于生成器模型,LeakyReLU 被推荐用于鉴别器模型,尽管最近,LeakyReLU 被推荐用于这两个模型。

下面的示例演示了在鉴别器模型的卷积层之后使用默认斜率为 0.2 的 LeakyReLU。

# example of using leakyrelu in a discriminator model
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import BatchNormalization
from keras.layers import LeakyReLU
# define model
model = Sequential()
model.add(Conv2D(64, kernel_size=(3,3), strides=(2,2), padding='same', input_shape=(64,64,3)))
model.add(LeakyReLU(0.2))
# summarize model
model.summary()

运行该示例演示了模型的结构,该模型有一个卷积层,后面是激活层。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 32, 32, 64)        1792
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 32, 32, 64)        0
=================================================================
Total params: 1,792
Trainable params: 1,792
Non-trainable params: 0
_________________________________________________________________

4.使用批处理规范化

批量标准化标准化前一层的激活,使其均值和单位方差为零。这具有稳定训练过程的效果。

在鉴别器和生成器模型中分别激活卷积层和转置卷积层之后,使用批量归一化。

它是在隐藏层之后,但在激活之前添加到模型中的,例如 LeakyReLU。

下面的示例演示了在鉴别器模型中的 Conv2D 层之后但在激活之前添加批处理规范化层。

# example of using batch norm in a discriminator model
from keras.models import Sequential
from keras.layers import Conv2D
from keras.layers import BatchNormalization
from keras.layers import LeakyReLU
# define model
model = Sequential()
model.add(Conv2D(64, kernel_size=(3,3), strides=(2,2), padding='same', input_shape=(64,64,3)))
model.add(BatchNormalization())
model.add(LeakyReLU(0.2))
# summarize model
model.summary()

运行该示例显示了卷积层的输出和激活函数之间批处理范数的期望使用。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_1 (Conv2D)            (None, 32, 32, 64)        1792
_________________________________________________________________
batch_normalization_1 (Batch (None, 32, 32, 64)        256
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 32, 32, 64)        0
=================================================================
Total params: 2,048
Trainable params: 1,920
Non-trainable params: 128
_________________________________________________________________

5.使用高斯权重初始化

在训练神经网络之前,必须将模型权重(参数)初始化为小的随机变量。

论文中报告的 DCAGAN 模型的最佳实践是使用标准偏差为 0.02 的零中心高斯分布(正态或钟形分布)初始化所有权重。

下面的例子演示了定义一个均值为 0、标准差为 0.02 的随机高斯权重初始值,用于生成器模型中的转置卷积层。

给定模型中的每一层都可以使用相同的权重初始化器实例。

# example of gaussian weight initialization in a generator model
from keras.models import Sequential
from keras.layers import Conv2DTranspose
from keras.initializers import RandomNormal
# define model
model = Sequential()
init = RandomNormal(mean=0.0, stddev=0.02)
model.add(Conv2DTranspose(64, kernel_size=(4,4), strides=(2,2), padding='same', kernel_initializer=init, input_shape=(64,64,3)))

6.使用亚当随机梯度下降

随机梯度下降,简称 SGD,是用于优化卷积神经网络模型权重的标准算法。

训练算法有许多变体。训练 DCGAN 模型的最佳做法是使用 Adam 版本的随机梯度下降,学习率为 0.0002,beta1 动量值为 0.5,而不是默认值 0.9。

在优化鉴别器和生成器模型时,建议使用具有这种配置的 Adam 优化算法。

下面的示例演示了如何配置 Adam 随机梯度下降优化算法来训练鉴别器模型。

# example of using adam when training a discriminator model
from keras.models import Sequential
from keras.layers import Conv2D
from keras.optimizers import Adam
# define model
model = Sequential()
model.add(Conv2D(64, kernel_size=(3,3), strides=(2,2), padding='same', input_shape=(64,64,3)))
# compile model
opt = Adam(lr=0.0002, beta_1=0.5)
model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])

7.将图像缩放到范围[-1,1]

建议使用双曲正切激活函数作为生成器模型的输出。

因此,还建议对用于训练鉴别器的真实图像进行缩放,使得它们的像素值在[-1,1]的范围内。这使得鉴别器将总是接收像素值在相同范围内的输入图像,真实的和假的。

典型地,图像数据被加载为 NumPy 数组,使得像素值是在[0,255]范围内的 8 位无符号整数(uint8)值。

首先,必须将数组转换为浮点值,然后重新缩放到所需的范围。

下面的示例提供了一个函数,该函数可以将加载图像数据的 NumPy 数组适当地缩放到[-1,1]的所需范围。

# example of a function for scaling images

# scale image data from [0,255] to [-1,1]
def scale_images(images):
	# convert from unit8 to float32
	images = images.astype('float32')
	# scale from [0,255] to [-1,1]
	images = (images - 127.5) / 127.5
	return images

我是史密斯钦塔拉的 GAN Hacks

DCGAN 论文的合著者之一 Soumith Chintala 在 NIPS 2016 上做了题为“如何训练 GAN?“总结了很多小技巧和窍门。

该视频可在 YouTube 上获得,强烈推荐。这些技巧的总结也可以作为 GitHub 资源库获得,标题为“如何训练 GAN?让 GANs 发挥作用的提示和技巧。”

这些提示借鉴了 DCGAN 论文以及其他地方的建议。

在本节中,我们将回顾如何实现前一节中未涉及的另外四种 GAN 最佳实践。

1.使用高斯潜在空间

潜在空间定义了用于生成新图像的生成器模型的输入的形状和分布。

DCGAN 建议从均匀分布中取样,这意味着潜在空间的形状是超立方体。

最近的最佳实践是从标准高斯分布中采样,这意味着潜在空间的形状是一个超球面,平均值为零,标准偏差为 1。

下面的例子演示了如何从一个 100 维的潜在空间中生成 500 个随机高斯点,该潜在空间可以用作生成器模型的输入;每个点都可以用来生成图像。

# example of sampling from a gaussian latent space
from numpy.random import randn

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape((n_samples, latent_dim))
	return x_input

# size of latent space
n_dim = 100
# number of samples to generate
n_samples = 500
# generate samples
samples = generate_latent_points(n_dim, n_samples)
# summarize
print(samples.shape, samples.mean(), samples.std())

运行该示例总结了 500 个点的生成,每个点由 100 个随机高斯值组成,平均值接近于零,标准偏差接近于 1,例如标准高斯分布。

(500, 100) -0.004791256735601787 0.9976912528950904

2.分开批次的真实和虚假图像

鉴别器模型使用随机梯度下降和小批量训练。

最佳做法是用不同批次的真实图像和伪造图像来更新鉴别器,而不是将真实图像和伪造图像合并成一个批次。

这可以通过两次单独调用 train_on_batch()函数来更新鉴别器模型的模型权重来实现。

下面的代码片段演示了在训练鉴别器模型时,如何在代码的内部循环中做到这一点。

...
# get randomly selected 'real' samples
X_real, y_real = ...
# update discriminator model weights
discriminator.train_on_batch(X_real, y_real)
# generate 'fake' examples
X_fake, y_fake = ...
# update discriminator model weights
discriminator.train_on_batch(X_fake, y_fake)

3.使用标签平滑

在训练鉴别器模型时,通常使用类别标签 1 来表示真实图像,使用类别标签 0 来表示假图像。

这些被称为硬标签,因为标签值是精确的或清晰的。

使用软标签是一个很好的做法,例如真实图像和假图像的值分别略大于或小于 1.0 或略大于 0.0,其中每个图像的变化是随机的。

这通常被称为标签平滑,并且在训练模型时可以产生正则化效果

下面的示例演示了为正类(类=1)定义 1,000 个标签,并按照建议将标签值均匀地平滑到范围[0.7,1.2]内。

# example of positive label smoothing
from numpy import ones
from numpy.random import random

# example of smoothing class=1 to [0.7, 1.2]
def smooth_positive_labels(y):
	return y - 0.3 + (random(y.shape) * 0.5)

# generate 'real' class labels (1)
n_samples = 1000
y = ones((n_samples, 1))
# smooth labels
y = smooth_positive_labels(y)
# summarize smooth labels
print(y.shape, y.min(), y.max())

运行该示例总结了平滑值的最小值和最大值,显示它们接近预期值。

(1000, 1) 0.7003103006957805 1.1997858934066357

有人建议只需要正类标签平滑,并且值小于 1.0。尽管如此,您也可以平滑负类标签。

下面的示例演示了为负类(类=0)生成 1,000 个标签,并按照建议将标签值均匀地平滑到范围[0.0,0.3]内。

# example of negative label smoothing
from numpy import zeros
from numpy.random import random

# example of smoothing class=0 to [0.0, 0.3]
def smooth_negative_labels(y):
	return y + random(y.shape) * 0.3

# generate 'fake' class labels (0)
n_samples = 1000
y = zeros((n_samples, 1))
# smooth labels
y = smooth_negative_labels(y)
# summarize smooth labels
print(y.shape, y.min(), y.max())

4.使用有噪声的标签

训练鉴别器模型时使用的标签总是正确的。

这意味着假图像总是被标记为 0 类,而真实图像总是被标记为 1 类。

建议在这些标签中引入一些错误,一些假图像被标记为真实,一些真实图像被标记为虚假。

如果您使用单独的批次来更新真假图像的鉴别器,这可能意味着向该批真实图像中随机添加一些假图像,或者向该批假图像中随机添加一些真实图像。

如果您正在用一批真实和虚假的图像更新鉴别器,那么这可能会涉及到随机翻转一些图像上的标签。

下面的示例通过创建 1000 个真实(类=1)标签样本并以 5%的概率翻转它们来演示这一点,然后对 1000 个虚假(类=0)标签样本进行同样的操作。

# example of noisy labels
from numpy import ones
from numpy import zeros
from numpy.random import choice

# randomly flip some labels
def noisy_labels(y, p_flip):
	# determine the number of labels to flip
	n_select = int(p_flip * y.shape[0])
	# choose labels to flip
	flip_ix = choice([i for i in range(y.shape[0])], size=n_select)
	# invert the labels in place
	y[flip_ix] = 1 - y[flip_ix]
	return y

# generate 'real' class labels (1)
n_samples = 1000
y = ones((n_samples, 1))
# flip labels with 5% probability
y = noisy_labels(y, 0.05)
# summarize labels
print(y.sum())

# generate 'fake' class labels (0)
y = zeros((n_samples, 1))
# flip labels with 5% probability
y = noisy_labels(y, 0.05)
# summarize labels
print(y.sum())

试着运行这个例子几次。

结果显示,对于正标签,大约 50“1”翻转为 1(例如,1,000 的 5%),对于负标签,大约 50“0”翻转为 1。

950.049.0

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

应用程序接口

文章

摘要

在本教程中,您发现了如何实现一套最佳实践或 GAN 黑客,您可以直接复制并粘贴到您的 GAN 项目。

具体来说,您了解到:

  • 开发生成对抗网络时实用启发式或黑客攻击的最佳来源。
  • 如何从零开始实现深度卷积 GAN 模型架构的七个最佳实践?
  • 如何实现 Soumith Chintala 的 GAN Hacks 演示文稿和列表中的四个附加最佳实践。

你有什么问题吗? 在下面的评论中提问,我会尽力回答。*

如何编写 GAN 训练算法和损失函数

原文:machinelearningmastery.com/how-to-code…

最后更新于 2020 年 1 月 10 日

生成对抗网络,简称 GAN,是一种用于训练生成模型的架构。

该架构由两个模型组成。我们感兴趣的生成器,以及用于帮助训练生成器的鉴别器模型。最初,生成器和鉴别器模型都被实现为多层感知器(MLP),尽管最近,模型被实现为深度卷积神经网络。

理解 GAN 是如何训练的,以及如何理解和实现发生器和鉴别器模型的损失函数可能是一个挑战。

在本教程中,您将发现如何实现生成对抗网络训练算法和损失函数。

完成本教程后,您将知道:

  • 如何实现生成对抗网络的训练算法?
  • 鉴别器和发生器的损失函数是如何工作的。
  • 如何在实践中实现鉴别器和生成器模型的权重更新?

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

  • 2020 年 1 月更新:修正了训练算法描述中的小错别字。

How to Code the Generative Adversarial Network Training Algorithm and Loss Functions

如何编码生成对抗网络训练算法和损失函数 图片由希拉里·夏洛特提供,版权所有。

教程概述

本教程分为三个部分;它们是:

  1. 如何实现 GAN 训练算法
  2. 理解 GAN 损耗函数
  3. 如何在实践中训练 GAN 模型

注意:本教程中的代码示例只是片段,不是独立的可运行示例。它们旨在帮助您开发算法的直觉,并且可以作为在您自己的项目中实现 GAN 训练算法的起点。

如何实现 GAN 训练算法

GAN 训练算法包括并行训练鉴别器和生成器模型。

该算法总结如下图,摘自古德费勒等人 2014 年发表的题为“生成对抗网络”的原始论文

Summary of the Generative Adversarial Network Training Algorithm

生成对抗网络训练算法综述。摘自:生成对抗网络。

让我们花点时间打开包装,熟悉一下这个算法。

算法的外部循环包括迭代步骤来训练架构中的模型。通过这个循环的一个周期不是一个纪元:它是一个由鉴别器和生成器模型的特定批量更新组成的单一更新。

一个时期被定义为通过训练数据集的一个循环,其中训练数据集中的样本被用于以小批量更新模型权重。例如,100 个样本的训练数据集用于训练具有 10 个样本的小批量大小的模型,将涉及每个时期 10 个小批量更新。该模型将适用于给定数量的时代,如 500 年。

通过调用 fit() 函数并指定时期的数量和每个小批量的大小来自动训练模型,这通常对您是隐藏的。

在 GAN 的情况下,必须根据训练数据集的大小和批处理大小来定义训练迭代的次数。在数据集包含 100 个样本、10 个批次大小和 500 个训练时期的情况下,我们将首先计算每个时期的批次数量,并使用它来计算使用时期数量的训练迭代总数。

例如:

...
batches_per_epoch = floor(dataset_size / batch_size)
total_iterations = batches_per_epoch * total_epochs

在 100 个样本、10 个批次和 500 个时期的数据集的情况下,GAN 将被训练为最低(100 / 10) * 500 或 5000 次总迭代。

接下来,我们可以看到训练的一次迭代可能导致鉴别器的多次更新和生成器的一次更新,其中鉴别器的更新次数是一个设置为 1 的超参数。

培训过程包括同步 SGD。在每个步骤中,对两个小批次进行采样:一个是来自数据集的 x 值小批次,另一个是来自模型先验潜在变量的 z 值小批次。然后同时进行两个梯度步骤…

——NIPS 2016 教程:生成对抗网络,2016。

因此,我们可以用 Python 伪代码将训练算法总结如下:

# gan training algorithm
def train_gan(dataset, n_epochs, n_batch):
	# calculate the number of batches per epoch
	batches_per_epoch = int(len(dataset) / n_batch)
	# calculate the number of training iterations
	n_steps = batches_per_epoch * n_epochs
	# gan training algorithm
	for i in range(n_steps):
		# update the discriminator model
		# ...
		# update the generator model
		# ...

另一种方法可以包括枚举训练时期的数量,并为每个时期将训练数据集分成批次。

更新鉴别器模型需要几个步骤。

首先,必须从潜在空间中选择一批随机点作为生成器模型的输入,为生成的或“*伪造的”*样本提供基础。然后,必须从训练数据集中选择一批样本作为“真实样本输入到鉴别器。

接下来,鉴别器模型必须对真样本和假样本进行预测,并且鉴别器的权重必须与这些预测的正确或不正确程度成比例地更新。预测是概率,我们将在下一节讨论预测的性质和最小化的损失函数。现在,我们可以概述这些步骤在实践中的实际情况。

我们需要一个生成器和一个鉴别器模型,例如像 Keras 模型。这些可以作为参数提供给训练函数。

接下来,我们必须从潜在空间生成点,然后使用当前形式的生成器模型来生成一些假图像。例如:

...
# generate points in the latent space
z = randn(latent_dim * n_batch)
# reshape into a batch of inputs for the network
z = x_input.reshape(n_batch, latent_dim)
# generate fake images
fake = generator.predict(z)

注意,潜在维度的大小也被提供作为训练算法的超参数。

然后我们必须选择一批真实的样本,这也将被包装成一个函数。

...
# select a batch of random real images
ix = randint(0, len(dataset), n_batch)
# retrieve real images
real = dataset[ix]

然后,鉴别器模型必须对每个生成的和真实的图像进行预测,并且权重必须更新。

# gan training algorithm
def train_gan(generator, discriminator, dataset, latent_dim, n_epochs, n_batch):
	# calculate the number of batches per epoch
	batches_per_epoch = int(len(dataset) / n_batch)
	# calculate the number of training iterations
	n_steps = batches_per_epoch * n_epochs
	# gan training algorithm
	for i in range(n_steps):
		# generate points in the latent space
		z = randn(latent_dim * n_batch)
		# reshape into a batch of inputs for the network
		z = z.reshape(n_batch, latent_dim)
		# generate fake images
		fake = generator.predict(z)
		# select a batch of random real images
		ix = randint(0, len(dataset), n_batch)
		# retrieve real images
		real = dataset[ix]
		# update weights of the discriminator model
		# ...

		# update the generator model
		# ...

接下来,必须更新生成器模型。

同样,必须从潜在空间中选择一批随机点,并传递给生成器以生成假图像,然后传递给鉴别器以进行分类。

...
# generate points in the latent space
z = randn(latent_dim * n_batch)
# reshape into a batch of inputs for the network
z = z.reshape(n_batch, latent_dim)
# generate fake images
fake = generator.predict(z)
# classify as real or fake
result = discriminator.predict(fake)

然后,该响应可用于更新发电机模型的权重。

# gan training algorithm
def train_gan(generator, discriminator, dataset, latent_dim, n_epochs, n_batch):
	# calculate the number of batches per epoch
	batches_per_epoch = int(len(dataset) / n_batch)
	# calculate the number of training iterations
	n_steps = batches_per_epoch * n_epochs
	# gan training algorithm
	for i in range(n_steps):
		# generate points in the latent space
		z = randn(latent_dim * n_batch)
		# reshape into a batch of inputs for the network
		z = z.reshape(n_batch, latent_dim)
		# generate fake images
		fake = generator.predict(z)
		# select a batch of random real images
		ix = randint(0, len(dataset), n_batch)
		# retrieve real images
		real = dataset[ix]
		# update weights of the discriminator model
		# ...
		# generate points in the latent space
		z = randn(latent_dim * n_batch)
		# reshape into a batch of inputs for the network
		z = z.reshape(n_batch, latent_dim)
		# generate fake images
		fake = generator.predict(z)
		# classify as real or fake
		result = discriminator.predict(fake)
		# update weights of the generator model
		# ...

有趣的是,鉴别器在每次训练迭代中更新两批样本,而生成器在每次训练迭代中仅更新一批样本。

现在我们已经定义了 GAN 的训练算法,我们需要了解模型权重是如何更新的。这需要了解用于训练 GAN 的损耗函数。

理解 GAN 损耗函数

鉴别器被训练来正确地分类真实和伪造的图像。

这是通过最大化真实图像的预测概率的对数和伪造图像的反转概率的对数来实现的,在每一个小批量的例子上取平均值。

回想一下,我们将对数概率相加,这与乘法概率相同,尽管不会消失为小数字。因此,我们可以将这个损失函数理解为寻找真实图像接近 1.0 的概率和伪造图像接近 0.0 的概率,反过来成为更大的数字。这些值的相加意味着该损失函数的较低平均值导致鉴别器的更好表现。

将这个问题转化为一个最小化问题,如果你熟悉为二进制分类开发神经网络,这应该不足为奇,因为这正是所使用的方法。

这只是在训练具有 sigmoid 输出的标准二进制分类器时最小化的标准交叉熵成本。唯一的区别是分类器是在两个小批量的数据上训练的;一个来自数据集,其中所有示例的标签都是 1,一个来自生成器,其中所有示例的标签都是 0。

——NIPS 2016 教程:生成对抗网络,2016。

发电机更棘手。

GAN 算法将生成器模型的损失定义为最小化鉴别器对假图像的预测的反转概率的对数,在小批量上求平均值。

这很简单,但是根据作者的说法,当生成器很差并且鉴别器擅长以高置信度拒绝假图像时,这在实践中是无效的。损失函数不再给出发生器可以用来调整权重的良好梯度信息,而是饱和。

在这种情况下,对数(1d(G(z))饱和。我们可以训练 G 来最大化 log D(G(z)),而不是训练 G 来最小化 log D(1-D(G(z))。这个目标函数导致 G 和 D 的动力学相同的不动点,但是在学习的早期提供更强的梯度。

——生成对抗网络,2014。

相反,作者建议最大化鉴别器对假图像的预测概率的对数。

变化是微妙的。

在第一种情况下,生成器被训练成最小化鉴别器正确的概率。随着损失函数的改变,发生器被训练成最大化鉴别器不正确的概率。

在极小极大博弈中,生成器最小化鉴别器正确的对数概率。在这个游戏中,生成器最大化鉴别器出错的对数概率。

——NIPS 2016 教程:生成对抗网络,2016。

然后可以将这个损失函数的符号反转,给出一个熟悉的最小化损失函数,用于训练发生器。因此,这有时被称为训练 GANs 的 D-log 技巧。

我们的基线比较是 DCGAN,一种卷积结构的 GAN,使用对数 D 技巧用标准 GAN 程序训练。

——2017 年一根筋

现在我们已经理解了 GAN 损耗函数,我们可以看看如何在实践中更新鉴别器和发生器模型。

如何在实践中训练 GAN 模型

GAN 损耗函数和模型更新的实际实现非常简单。

我们将看看使用 Keras 库的例子。

我们可以通过配置鉴别器模型来直接实现鉴别器,以预测真实图像的概率为 1,伪造图像的概率为 0,并最小化交叉熵损失,特别是二进制交叉熵损失。

例如,我们的模型定义的一个片段,带有用于鉴别器的 Keras,对于输出层和具有适当损失函数的模型编译可能如下所示。

...
# output layer
model.add(Dense(1, activation='sigmoid'))
# compile model
model.compile(loss='binary_crossentropy', ...)

可以为每一批真实和虚假样本训练定义的模型,为预期结果提供 1 和 0 的数组。

可以使用1()0()NumPy 函数来创建这些目标标签,可以使用 Keras 函数 train_on_batch() 来更新每批样本的模型。

...
X_fake = ...
X_real = ...
# define target labels for fake images
y_fake = zeros((n_batch, 1))
# update the discriminator for fake images
discriminator.train_on_batch(X_fake, y_fake)
# define target labels for real images
y_real = ones((n_batch, 1))
# update the discriminator for real images
discriminator.train_on_batch(X_real, y_real)

鉴别器模型将被训练来预测给定输入图像的“真实度”的概率,该概率可以被解释为类标签,假的类=0,真的类=1。

生成器被训练为最大化鉴别器,该鉴别器为生成的图像预测高概率的“真实度”。

这是通过鉴别器为生成的图像更新类别标签为 1 的生成器来实现的。鉴别器在此操作中不更新,但提供更新生成器模型权重所需的梯度信息。

例如,如果鉴别器预测该批生成图像的平均概率较低,那么这将导致大的误差信号向后传播到生成器中,给定样本的“预期概率”实际为 1.0。这个大的误差信号反过来会导致发生器发生相对较大的变化,从而有望提高其在下一批中生成假样本的能力。

这可以在 Keras 中通过创建一个组合生成器和鉴别器模型的复合模型来实现,允许来自生成器的输出图像直接流入鉴别器,进而允许来自鉴别器的预测概率的误差信号通过生成器模型的权重流回。

例如:

# define a composite gan model for the generator and discriminator
def define_gan(generator, discriminator):
	# make weights in the discriminator not trainable
	discriminator.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the discriminator
	model.add(discriminator)
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam')
	return model

然后可以使用假图像和真实类别标签更新复合模型。

...
# generate points in the latent space
z = randn(latent_dim * n_batch)
# reshape into a batch of inputs for the network
z = z.reshape(n_batch, latent_dim)
# define target labels for real images
y_real = ones((n_batch, 1))
# update generator model
gan_model.train_on_batch(z, y_real)

这就完成了鉴别器和生成器模型的 GAN 训练算法、损失函数和权重更新细节的外巡。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

报纸

文章

摘要

在本教程中,您发现了如何实现生成对抗网络训练算法和损失函数。

具体来说,您了解到:

  • 如何实现生成对抗网络的训练算法?
  • 鉴别器和发生器的损失函数是如何工作的。
  • 如何在实践中实现鉴别器和生成器模型的权重更新?

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

如何从头开发一个条件 GAN(CGAN)

原文:machinelearningmastery.com/how-to-deve…

最后更新于 2020 年 9 月 1 日

生成对抗网络是一种用于训练生成模型的体系结构,例如用于生成图像的深度卷积神经网络。

尽管 GAN 模型能够为给定数据集生成新的随机似是而非的示例,但是除了试图找出输入到生成器的潜在空间和生成的图像之间的复杂关系之外,没有办法控制生成的图像类型。

条件生成对抗网络,简称 cGAN,是一种 GAN,涉及到通过生成器模型有条件地生成图像。图像生成可以以类标签为条件(如果可用),允许有针对性地生成给定类型的图像。

在本教程中,您将发现如何开发一个有条件的生成对抗网络,用于有针对性地生成衣物。

完成本教程后,您将知道:

  • 用 GAN 生成随机样本的局限性可以用条件生成对抗网络来克服。
  • 如何开发和评估一个无条件生成的敌对网络,用于生成衣服的照片。
  • 如何开发和评估生成服装照片的条件生成对抗网络?

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

How to Develop a Conditional Generative Adversarial Network for Clothing Photographs From Scratch

如何从零开始开发条件生成对抗网络 图片由大柏树国家保护区提供,版权所有

教程概述

本教程分为五个部分;它们是:

  1. 条件生成对抗网络
  2. 时尚-MNIST 服装照片数据集
  3. 时尚无条件 GAN-MNIST
  4. 时尚 MNIST 的条件 GAN
  5. 条件服装生成

条件生成对抗网络

生成对抗网络,简称 GAN,是一种用于训练基于深度学习的生成模型的架构。

该架构由一个生成器和一个鉴别器模型组成。生成器模型负责生成新的似是而非的示例,这些示例在理想情况下无法与数据集中的真实示例区分开来。鉴别器模型负责将给定图像分类为真实图像(从数据集绘制)或伪造图像(生成)。

模型以零和或对抗的方式一起训练,使得鉴别器的改进以降低发生器的能力为代价,反之亦然。

GANs 在图像合成方面很有效,也就是说,为目标数据集生成新的图像示例。有些数据集有附加信息,如类标签,希望利用这些信息。

例如,MNIST 手写数字数据集具有相应整数的类别标签,CIFAR-10 小对象照片数据集具有照片中相应对象的类别标签,而 Fashion-MNIST 服装数据集具有相应服装项目的类别标签。

在 GAN 模型中使用类标签信息有两个动机。

  1. 完善 GAN。
  2. 目标图像生成。

与输入图像相关的附加信息,例如类别标签,可以用于改进 GAN。这种改进可能以更稳定的训练、更快的训练和/或生成质量更好的图像的形式出现。

类标签也可以用于有意或有针对性地生成给定类型的图像。

GAN 模型的一个限制是它可能从域中生成随机图像。潜在空间中的点与生成的图像之间存在关系,但这种关系复杂且难以映射。

或者,可以以生成器和鉴别器模型都以类标签为条件的方式来训练 GAN。这意味着,当训练的生成器模型用作独立模型来生成域中的图像时,可以生成给定类型或类标签的图像。

如果发生器和鉴别器都以某些额外的信息 y 为条件,则生成对抗网可以扩展为条件模型。[……]我们可以通过将 y 作为额外的输入层输入到鉴别器和发生器中来进行条件化。

——条件生成对抗网,2014。

例如,在 MNIST 的情况下,可以生成特定的手写数字,例如数字 9;在 CIFAR-10 的情况下,可以生成特定对象的照片,例如“青蛙”;在时尚 MNIST 数据集的情况下,可以生成特定的服装项目,例如“服饰

这种类型的模型被称为条件生成对抗网络,简称 CGAN。

迈赫迪·米尔扎和西蒙·奥森德罗在他们 2014 年的论文《条件生成对抗网络》中首次描述了 cGAN 在本文中,作者基于指导生成器模型的图像生成过程的愿望来激励该方法。

……通过根据附加信息调整模型,可以指导数据生成过程。这种调节可以基于类别标签

——条件生成对抗网,2014。

他们的方法在 MNIST 手写数字数据集中进行了演示,其中类标签是一个热编码,并与生成器和鉴别器模型的输入相连接。

下图提供了模型架构的摘要。

Example of a Conditional Generator and a Conditional Discriminator in a Conditional Generative Adversarial Network

条件生成对抗网络中的条件生成器和条件鉴别器示例。 摘自《条件生成对抗性网络》,2014 年。

在 GAN 模型的设计和训练方面已经有了许多进步,最显著的是深度卷积 GAN ,简称 DCGAN,它概述了模型配置和训练过程,这些过程可靠地导致 GAN 模型针对各种各样的问题进行稳定的训练。基于 DCGAN 的模型的条件训练可以简称为 cDCGAN 或 CDCGAN。

有许多方法可以将类标签编码并合并到鉴别器和生成器模型中。最佳实践包括使用嵌入层,随后是具有线性激活的完全连接层,该线性激活将嵌入缩放到图像的大小,然后将其作为附加通道或特征图连接到模型中。

该建议的一个版本在 2015 年的论文《使用对抗性网络的拉普拉斯金字塔的深度生成图像模型》中进行了描述

…我们还探索了模型的类条件版本,其中向量 c 编码标签。它通过一个线性层集成到 Gk & Dk 中,该层的输出被重新整形为单个平面要素图,然后与第一个层图连接。

——使用对抗性网络拉普拉斯金字塔的深度生成图像模型,2015 年。

该建议后来被添加到设计和训练 GAN 模型时的启发式建议列表中,总结如下:

16:条件 GANs 中的离散变量 –使用嵌入层 –作为附加通道添加到图像中 –保持低嵌入维度并向上采样以匹配图像通道大小

GAN 黑客

虽然 GANs 可以以类标签为条件,即所谓的类条件 GANs,但在 GAN 用于图像到图像转换任务的情况下,它们也可以以其他输入为条件,如图像。

在本教程中,我们将开发一个 GAN,特别是 DCGAN,然后更新它以在 CGAN,特别是 cDCGAN 模型架构中使用类标签。

时尚-MNIST 服装照片数据集

时尚-MNIST 数据集被提议作为 MNIST 数据集的更具挑战性的替代数据集。

它是一个数据集,由 60,000 个 28×28 像素的小正方形灰度图像组成,包括 10 种服装,如鞋子、t 恤、连衣裙等。

Keras 通过 fashion_mnist.load_dataset()函数提供对时尚 MNIST 数据集的访问。它返回两个元组,一个包含标准训练数据集的输入和输出元素,另一个包含标准测试数据集的输入和输出元素。

下面的示例加载数据集并总结加载数据集的形状。

注意:第一次加载数据集时,Keras 会自动下载图像的压缩版本,并保存在 ~/的主目录下。keras/数据集/ 。下载速度很快,因为压缩形式的数据集只有大约 25 兆字节。

# example of loading the fashion_mnist dataset
from keras.datasets.fashion_mnist import load_data
# load the images into memory
(trainX, trainy), (testX, testy) = load_data()
# summarize the shape of the dataset
print('Train', trainX.shape, trainy.shape)
print('Test', testX.shape, testy.shape)

运行该示例将加载数据集,并打印训练的输入和输出组件的形状,以及测试图像的分割。

我们可以看到训练集中有 60K 个例子,测试集中有 10K,每个图像都是 28 乘 28 像素的正方形。

Train (60000, 28, 28) (60000,)
Test (10000, 28, 28) (10000,)

图像是黑色背景的灰度图像(像素值为 0),衣服是白色的(像素值接近 255)。这意味着如果图像被绘制出来,它们大部分是黑色的,中间有一件白色的衣服。

我们可以使用带有 imshow()函数的 matplotlib 库绘制训练数据集中的一些图像,并通过“ cmap ”参数将颜色映射指定为“灰色,以正确显示像素值。

# plot raw pixel data
pyplot.imshow(trainX[i], cmap='gray')

或者,当我们颠倒颜色,将背景画成白色,将服装画成黑色时,图像更容易查看。

它们更容易观看,因为大部分图像现在是白色的,而感兴趣的区域是黑色的。这可以通过使用反向灰度色图来实现,如下所示:

# plot raw pixel data
pyplot.imshow(trainX[i], cmap='gray_r')

以下示例将训练数据集中的前 100 幅图像绘制成 10 乘 10 的正方形。

# example of loading the fashion_mnist dataset
from keras.datasets.fashion_mnist import load_data
from matplotlib import pyplot
# load the images into memory
(trainX, trainy), (testX, testy) = load_data()
# plot images from the training dataset
for i in range(100):
	# define subplot
	pyplot.subplot(10, 10, 1 + i)
	# turn off axis
	pyplot.axis('off')
	# plot raw pixel data
	pyplot.imshow(trainX[i], cmap='gray_r')
pyplot.show()

运行该示例会创建一个图形,其中包含 100 幅来自 MNIST 训练数据集的图像,排列成 10×10 的正方形。

Plot of the First 100 Items of Clothing From the Fashion MNIST Dataset.

时尚 MNIST 数据集中前 100 件服装的图表。

我们将使用训练数据集中的图像作为训练生成对抗网络的基础。

具体来说,生成器模型将学习如何使用鉴别器生成新的似是而非的服装项目,该鉴别器将尝试区分来自时尚 MNIST 训练数据集的真实图像和生成器模型输出的新图像。

这是一个相对简单的问题,不需要复杂的生成器或鉴别器模型,尽管它确实需要生成灰度输出图像。

时尚无条件 GAN-MNIST

在本节中,我们将为时尚-MNIST 数据集开发一个无条件 GAN。

第一步是定义模型。

鉴别器模型将一幅 28×28 灰度图像作为输入,并输出关于图像是真实的(类=1)还是假的(类=0)的二进制预测。它被实现为一个适度的卷积神经网络,使用 GAN 设计的最佳实践,例如使用斜率为 0.2 的 LeakyReLU 激活函数,使用 2×2 步长对进行下采样,以及学习率为 0.0002、动量为 0.5 的随机梯度下降的 adam 版本

下面的 define_discriminator()函数实现了这一点,定义并编译鉴别器模型并返回它。图像的输入形状被参数化为一个默认的函数参数,以防您以后想要为自己的图像数据重用该函数。

# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
	model = Sequential()
	# downsample
	model.add(Conv2D(128, (3,3), strides=(2,2), padding='same', input_shape=in_shape))
	model.add(LeakyReLU(alpha=0.2))
	# downsample
	model.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))
	model.add(LeakyReLU(alpha=0.2))
	# classifier
	model.add(Flatten())
	model.add(Dropout(0.4))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
	return model

生成器模型将潜在空间中的一个点作为输入,并输出单个 28×28 灰度图像。这是通过使用完全连接的层来解释潜在空间中的点并提供足够的激活来实现的,这些激活可以被重新成形为输出图像的低分辨率版本(例如 7×7)的许多副本(在这种情况下为 128 个)。然后对其进行两次上采样,每次使用转置卷积层,将激活区域的大小增加一倍,面积增加四倍。该模型使用了最佳实践,例如 LeakyReLU 激活、作为步长因子的内核大小以及输出层中的双曲正切(tanh)激活函数。

下面的 define_generator() 函数定义了生成器模型,但由于没有直接训练,所以故意不编译,然后返回模型。潜在空间的大小被参数化为函数参数。

# define the standalone generator model
def define_generator(latent_dim):
	model = Sequential()
	# foundation for 7x7 image
	n_nodes = 128 * 7 * 7
	model.add(Dense(n_nodes, input_dim=latent_dim))
	model.add(LeakyReLU(alpha=0.2))
	model.add(Reshape((7, 7, 128)))
	# upsample to 14x14
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))
	model.add(LeakyReLU(alpha=0.2))
	# upsample to 28x28
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))
	model.add(LeakyReLU(alpha=0.2))
	# generate
	model.add(Conv2D(1, (7,7), activation='tanh', padding='same'))
	return model

接下来,可以定义一个 GAN 模型,它将生成器模型和鉴别器模型组合成一个更大的模型。这个更大的模型将被用于训练生成器中的模型权重,使用由鉴别器模型计算的输出和误差。鉴别器模型是单独训练的,因此,在这个更大的 GAN 模型中,模型权重被标记为不可训练,以确保只有生成器模型的权重被更新。鉴别器权重的可训练性的这种改变仅在训练组合的 GAN 模型时有效,而在单独训练鉴别器时无效。

这个更大的 GAN 模型将潜在空间中的一个点作为输入,使用生成器模型来生成图像,该图像作为输入被馈送到鉴别器模型,然后被输出或者被分类为真实的或者伪造的。

下面的 define_gan() 函数实现了这一点,将已经定义的生成器和鉴别器模型作为输入。

# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
	# make weights in the discriminator not trainable
	discriminator.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the discriminator
	model.add(discriminator)
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt)
	return model

现在我们已经定义了 GAN 模型,我们需要对它进行训练。但是,在我们训练模型之前,我们需要输入数据。

第一步是加载和准备时尚 MNIST 数据集。我们只需要训练数据集中的图像。图像是黑白的,因此我们必须增加一个额外的通道维度来将它们转换成三维的,正如我们模型的卷积层所预期的那样。最后,像素值必须缩放到[-1,1]的范围,以匹配生成器模型的输出。

下面的 load_real_samples() 函数实现了这一点,返回加载并缩放的时尚 MNIST 训练数据集,准备建模。

# load fashion mnist images
def load_real_samples():
	# load dataset
	(trainX, _), (_, _) = load_data()
	# expand to 3d, e.g. add channels
	X = expand_dims(trainX, axis=-1)
	# convert from ints to floats
	X = X.astype('float32')
	# scale from [0,255] to [-1,1]
	X = (X - 127.5) / 127.5
	return X

我们将需要数据集的一批(或半批)真实图像来更新 GAN 模型。实现这一点的简单方法是每次从数据集中随机选择一个图像样本。

下面的 generate_real_samples() 函数实现了这一点,以准备好的数据集为参数,为鉴别器选择并返回一个时尚 MNIST 图像的随机样本及其对应的类标签,具体为 class=1,表示它们是真实图像。

# select real samples
def generate_real_samples(dataset, n_samples):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# select images
	X = dataset[ix]
	# generate class labels
	y = ones((n_samples, 1))
	return X, y

接下来,我们需要发电机模型的输入。这些是来自潜在空间的随机点,特别是高斯分布的随机变量。

generate _ 潜伏 _points() 函数实现了这一点,将潜伏空间的大小作为自变量,所需的点数作为生成器模型的一批输入样本返回。

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n_samples, latent_dim)
	return x_input

接下来,我们需要使用潜在空间中的点作为生成器的输入,以便生成新的图像。

下面的 generate_fake_samples() 函数实现了这一点,将生成器模型和潜在空间的大小作为参数,然后在潜在空间中生成点,并将其用作生成器模型的输入。该函数为鉴别器模型返回生成的图像及其对应的类标签,具体来说,class=0 表示它们是伪造的或生成的。

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n_samples)
	# predict outputs
	X = generator.predict(x_input)
	# create class labels
	y = zeros((n_samples, 1))
	return X, y

我们现在准备安装 GAN 模型。

该模型适合 100 个训练时期,这是任意的,因为该模型在大约 20 个时期后开始生成看似合理的服装项目。使用 128 个样本的批次大小,并且每个训练时期涉及 60,000/128,或者大约 468 批次的真实和虚假样本以及对模型的更新。

首先对半批真实样本更新鉴别器模型,然后对半批伪样本更新鉴别器模型,共同形成一批权重更新。然后通过复合 gan 模型更新发生器。重要的是,对于假样本,类别标签设置为 1 或真。这具有更新生成器以更好地生成下一批真实样本的效果。

下面的 train() 函数实现了这一点,将定义的模型、数据集和潜在维度的大小作为参数,并使用默认参数参数化纪元的数量和批处理大小。发电机模型在训练结束时保存。

# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=128):
	bat_per_epo = int(dataset.shape[0] / n_batch)
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# enumerate batches over the training set
		for j in range(bat_per_epo):
			# get randomly selected 'real' samples
			X_real, y_real = generate_real_samples(dataset, half_batch)
			# update discriminator model weights
			d_loss1, _ = d_model.train_on_batch(X_real, y_real)
			# generate 'fake' examples
			X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
			# update discriminator model weights
			d_loss2, _ = d_model.train_on_batch(X_fake, y_fake)
			# prepare points in latent space as input for the generator
			X_gan = generate_latent_points(latent_dim, n_batch)
			# create inverted labels for the fake samples
			y_gan = ones((n_batch, 1))
			# update the generator via the discriminator's error
			g_loss = gan_model.train_on_batch(X_gan, y_gan)
			# summarize loss on this batch
			print('>%d, %d/%d, d1=%.3f, d2=%.3f g=%.3f' %
				(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))
	# save the generator model
	g_model.save('generator.h5')

然后,我们可以定义潜在空间的大小,定义所有三个模型,并在加载的时尚 MNIST 数据集上训练它们。

# size of the latent space
latent_dim = 100
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)

将所有这些结合在一起,下面列出了完整的示例。

# example of training an unconditional gan on the fashion mnist dataset
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.fashion_mnist import load_data
from keras.optimizers import Adam
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.layers import Dropout

# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1)):
	model = Sequential()
	# downsample
	model.add(Conv2D(128, (3,3), strides=(2,2), padding='same', input_shape=in_shape))
	model.add(LeakyReLU(alpha=0.2))
	# downsample
	model.add(Conv2D(128, (3,3), strides=(2,2), padding='same'))
	model.add(LeakyReLU(alpha=0.2))
	# classifier
	model.add(Flatten())
	model.add(Dropout(0.4))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
	return model

# define the standalone generator model
def define_generator(latent_dim):
	model = Sequential()
	# foundation for 7x7 image
	n_nodes = 128 * 7 * 7
	model.add(Dense(n_nodes, input_dim=latent_dim))
	model.add(LeakyReLU(alpha=0.2))
	model.add(Reshape((7, 7, 128)))
	# upsample to 14x14
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))
	model.add(LeakyReLU(alpha=0.2))
	# upsample to 28x28
	model.add(Conv2DTranspose(128, (4,4), strides=(2,2), padding='same'))
	model.add(LeakyReLU(alpha=0.2))
	# generate
	model.add(Conv2D(1, (7,7), activation='tanh', padding='same'))
	return model

# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
	# make weights in the discriminator not trainable
	discriminator.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the discriminator
	model.add(discriminator)
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt)
	return model

# load fashion mnist images
def load_real_samples():
	# load dataset
	(trainX, _), (_, _) = load_data()
	# expand to 3d, e.g. add channels
	X = expand_dims(trainX, axis=-1)
	# convert from ints to floats
	X = X.astype('float32')
	# scale from [0,255] to [-1,1]
	X = (X - 127.5) / 127.5
	return X

# select real samples
def generate_real_samples(dataset, n_samples):
	# choose random instances
	ix = randint(0, dataset.shape[0], n_samples)
	# select images
	X = dataset[ix]
	# generate class labels
	y = ones((n_samples, 1))
	return X, y

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n_samples, latent_dim)
	return x_input

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n_samples)
	# predict outputs
	X = generator.predict(x_input)
	# create class labels
	y = zeros((n_samples, 1))
	return X, y

# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=128):
	bat_per_epo = int(dataset.shape[0] / n_batch)
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# enumerate batches over the training set
		for j in range(bat_per_epo):
			# get randomly selected 'real' samples
			X_real, y_real = generate_real_samples(dataset, half_batch)
			# update discriminator model weights
			d_loss1, _ = d_model.train_on_batch(X_real, y_real)
			# generate 'fake' examples
			X_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
			# update discriminator model weights
			d_loss2, _ = d_model.train_on_batch(X_fake, y_fake)
			# prepare points in latent space as input for the generator
			X_gan = generate_latent_points(latent_dim, n_batch)
			# create inverted labels for the fake samples
			y_gan = ones((n_batch, 1))
			# update the generator via the discriminator's error
			g_loss = gan_model.train_on_batch(X_gan, y_gan)
			# summarize loss on this batch
			print('>%d, %d/%d, d1=%.3f, d2=%.3f g=%.3f' %
				(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))
	# save the generator model
	g_model.save('generator.h5')

# size of the latent space
latent_dim = 100
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# load image data
dataset = load_real_samples()
# train model
train(generator, discriminator, gan_model, dataset, latent_dim)

在普通硬件上运行该示例可能需要很长时间。

我建议在 GPU 硬件上运行该示例。如果您需要帮助,您可以通过使用 AWS EC2 实例来训练模型来快速入门。请参阅教程:

鉴别器在真样品和假样品上的损失,以及发生器的损失,在每批之后报告。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,在整个训练过程中,鉴频器和发生器的损耗都在 0.6 到 0.7 左右。

...
>100, 464/468, d1=0.681, d2=0.685 g=0.693
>100, 465/468, d1=0.691, d2=0.700 g=0.703
>100, 466/468, d1=0.691, d2=0.703 g=0.706
>100, 467/468, d1=0.698, d2=0.699 g=0.699
>100, 468/468, d1=0.699, d2=0.695 g=0.708

训练结束后,发电机模型将保存到文件名为“ generator.h5 的文件中。

该模型可以被加载并用于从时尚 MNIST 数据集生成新的随机但似是而非的样本。

以下示例加载保存的模型,并生成 100 件随机的衣服。

# example of loading the generator model and generating images
from keras.models import load_model
from numpy.random import randn
from matplotlib import pyplot

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n_samples, latent_dim)
	return x_input

# create and save a plot of generated images (reversed grayscale)
def show_plot(examples, n):
	# plot images
	for i in range(n * n):
		# define subplot
		pyplot.subplot(n, n, 1 + i)
		# turn off axis
		pyplot.axis('off')
		# plot raw pixel data
		pyplot.imshow(examples[i, :, :, 0], cmap='gray_r')
	pyplot.show()

# load model
model = load_model('generator.h5')
# generate images
latent_points = generate_latent_points(100, 100)
# generate images
X = model.predict(latent_points)
# plot the result
show_plot(X, 10)

运行该示例会创建一个由 100 件随机生成的衣服组成的图,这些衣服排列成 10×10 的网格。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,我们可以看到各种各样的衣服,如鞋子、毛衣和裤子。大多数项目看起来都很合理,可能来自时尚 MNIST 数据集。然而,它们并不完美,因为有些单袖毛衣和鞋子看起来一团糟。

Example of 100 Generated items of Clothing using an Unconditional GAN.

使用无条件 g an 生成的 100 件衣服的示例。

时尚 MNIST 的条件 GAN

在本节中,我们将通过更新上一节中开发的无条件 GAN,为时尚-MNIST 数据集开发一个有条件 GAN。

在 Keras 中设计具有多个输入的模型的最佳方式是使用功能 API ,这与上一节中使用的顺序 API 相反。我们将使用功能 API 来重新实现鉴别器、生成器和复合模型。

从鉴别器模型开始,定义一个新的第二个输入,该输入为图像的类标签取一个整数。这具有使输入图像以所提供的类标签为条件的效果。

然后,类标签通过一个大小为 50 的嵌入层。这意味着时尚 MNIST 数据集的 10 个类(0 到 9)中的每一个都将映射到一个不同的 50 元素向量表示,该向量表示将由鉴别器模型学习。

嵌入的输出然后通过线性激活被传递到完全连接的层。重要的是,完全连接的层有足够的激活,可以重塑为 28×28 图像的一个通道。激活被重新整形为单个 28×28 激活图,并与输入图像连接。对于下一个卷积层,这具有看起来像双通道输入图像的效果。

下面的 define_discriminator() 实现了对鉴别器模型的更新。在嵌入层之后,输入图像的参数化形状也用于定义完全连接层的激活次数,以重塑其输出。问题中的类的数量也在函数和集合中被参数化。

# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1), n_classes=10):
	# label input
	in_label = Input(shape=(1,))
	# embedding for categorical input
	li = Embedding(n_classes, 50)(in_label)
	# scale up to image dimensions with linear activation
	n_nodes = in_shape[0] * in_shape[1]
	li = Dense(n_nodes)(li)
	# reshape to additional channel
	li = Reshape((in_shape[0], in_shape[1], 1))(li)
	# image input
	in_image = Input(shape=in_shape)
	# concat label as a channel
	merge = Concatenate()([in_image, li])
	# downsample
	fe = Conv2D(128, (3,3), strides=(2,2), padding='same')(merge)
	fe = LeakyReLU(alpha=0.2)(fe)
	# downsample
	fe = Conv2D(128, (3,3), strides=(2,2), padding='same')(fe)
	fe = LeakyReLU(alpha=0.2)(fe)
	# flatten feature maps
	fe = Flatten()(fe)
	# dropout
	fe = Dropout(0.4)(fe)
	# output
	out_layer = Dense(1, activation='sigmoid')(fe)
	# define model
	model = Model([in_image, in_label], out_layer)
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
	return model

为了使架构清晰,下面是鉴别器模型的图。

该图显示了两个输入:首先是通过嵌入(左)和图像(右)的类标签,以及它们连接成一个双通道 28×28 图像或特征图(中间)。模型的其余部分与上一节中设计的鉴别器相同。

Plot of the Discriminator Model in the Conditional Generative Adversarial Network

条件生成对抗网络中鉴别器模型的绘制

接下来,必须更新生成器模型以采用类标签。这具有使潜在空间中的点以提供的类标签为条件的效果。

像在鉴别器中一样,类标签通过一个嵌入层映射到一个唯一的 50 元素向量,然后在调整大小之前通过一个具有线性激活的完全连接层。在这种情况下,完全连接的层的激活将被调整到一个 7×7 的要素图中。这是为了匹配无条件生成器模型的 7×7 特征图激活。新的 7×7 要素图作为一个通道添加到现有的 128 个要素图中,产生 129 个要素图,然后像之前的模型一样进行上采样。

下面的 define_generator() 函数实现了这一点,再次参数化了类的数量,就像我们对鉴别器模型所做的那样。

# define the standalone generator model
def define_generator(latent_dim, n_classes=10):
	# label input
	in_label = Input(shape=(1,))
	# embedding for categorical input
	li = Embedding(n_classes, 50)(in_label)
	# linear multiplication
	n_nodes = 7 * 7
	li = Dense(n_nodes)(li)
	# reshape to additional channel
	li = Reshape((7, 7, 1))(li)
	# image generator input
	in_lat = Input(shape=(latent_dim,))
	# foundation for 7x7 image
	n_nodes = 128 * 7 * 7
	gen = Dense(n_nodes)(in_lat)
	gen = LeakyReLU(alpha=0.2)(gen)
	gen = Reshape((7, 7, 128))(gen)
	# merge image gen and label input
	merge = Concatenate()([gen, li])
	# upsample to 14x14
	gen = Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')(merge)
	gen = LeakyReLU(alpha=0.2)(gen)
	# upsample to 28x28
	gen = Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')(gen)
	gen = LeakyReLU(alpha=0.2)(gen)
	# output
	out_layer = Conv2D(1, (7,7), activation='tanh', padding='same')(gen)
	# define model
	model = Model([in_lat, in_label], out_layer)
	return model

为了帮助理解新的模型架构,下图提供了新的条件生成器模型。

在这种情况下,您可以看到潜在空间中的 100 元素点作为输入和随后的大小调整(左)以及新的类标签输入和嵌入层(右),然后是两组要素图的串联(中心)。模型的其余部分与无条件情况相同。

Plot of the Generator Model in the Conditional Generative Adversarial Network

条件生成对抗网络中生成元模型的绘制

最后,复合 GAN 模型需要更新。

新的 GAN 模型将把潜在空间中的一个点作为输入和类标签,并像以前一样生成输入是真还是假的预测。

使用函数式应用编程接口来设计模型,重要的是我们显式地连接从生成器生成的图像输出以及类标签输入,这两者都是鉴别器模型的输入。这允许相同的类标签输入向下流入生成器,向下流入鉴别器。

下面的 define_gan() 函数实现了 gan 的条件版本。

# define the combined generator and discriminator model, for updating the generator
def define_gan(g_model, d_model):
	# make weights in the discriminator not trainable
	d_model.trainable = False
	# get noise and label inputs from generator model
	gen_noise, gen_label = g_model.input
	# get image output from the generator model
	gen_output = g_model.output
	# connect image output and label input from generator as inputs to discriminator
	gan_output = d_model([gen_output, gen_label])
	# define gan model as taking noise and label and outputting a classification
	model = Model([gen_noise, gen_label], gan_output)
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt)
	return model

下图总结了复合 GAN 模型。

重要的是,它完整地显示了以潜在空间中的点和类标签作为输入的生成器模型,以及生成器的输出和作为输入的同一个类标签到鉴别器模型(图底部的最后一个框)的连接,以及真实或虚假的单个类标签分类的输出。

Plot of the Composite Generator and Discriminator Model in the Conditional Generative Adversarial Network

条件生成对抗网络中复合生成元和鉴别元模型的绘制

从无条件到有条件 GAN 转换的困难部分已经完成,即模型架构的定义和配置。

接下来,剩下的就是更新训练过程,也使用类标签。

首先,必须更新 load_real_samples()generate_real_samples() 函数,分别用于加载数据集和选择一批样本,以利用训练数据集中的真实类标签。重要的是, generate_real_samples() 函数现在返回图像、服装标签和鉴别器的类别标签(类别=1)。

# load fashion mnist images
def load_real_samples():
	# load dataset
	(trainX, trainy), (_, _) = load_data()
	# expand to 3d, e.g. add channels
	X = expand_dims(trainX, axis=-1)
	# convert from ints to floats
	X = X.astype('float32')
	# scale from [0,255] to [-1,1]
	X = (X - 127.5) / 127.5
	return [X, trainy]

# select real samples
def generate_real_samples(dataset, n_samples):
	# split into images and labels
	images, labels = dataset
	# choose random instances
	ix = randint(0, images.shape[0], n_samples)
	# select images and labels
	X, labels = images[ix], labels[ix]
	# generate class labels
	y = ones((n_samples, 1))
	return [X, labels], y

接下来,必须更新generate _ 潜伏 _points() 函数,以生成随机选择的整数类标签数组,以便与潜伏空间中随机选择的点一起使用。

然后必须更新 generate_fake_samples() 函数,以便在生成新的假图像时使用这些随机生成的类标签作为生成器模型的输入。

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples, n_classes=10):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_input = x_input.reshape(n_samples, latent_dim)
	# generate labels
	labels = randint(0, n_classes, n_samples)
	return [z_input, labels]

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
	# generate points in latent space
	z_input, labels_input = generate_latent_points(latent_dim, n_samples)
	# predict outputs
	images = generator.predict([z_input, labels_input])
	# create class labels
	y = zeros((n_samples, 1))
	return [images, labels_input], y

最后, train() 函数必须更新,以便在更新鉴别器和生成器模型的调用中检索和使用类标签。

# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=128):
	bat_per_epo = int(dataset[0].shape[0] / n_batch)
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# enumerate batches over the training set
		for j in range(bat_per_epo):
			# get randomly selected 'real' samples
			[X_real, labels_real], y_real = generate_real_samples(dataset, half_batch)
			# update discriminator model weights
			d_loss1, _ = d_model.train_on_batch([X_real, labels_real], y_real)
			# generate 'fake' examples
			[X_fake, labels], y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
			# update discriminator model weights
			d_loss2, _ = d_model.train_on_batch([X_fake, labels], y_fake)
			# prepare points in latent space as input for the generator
			[z_input, labels_input] = generate_latent_points(latent_dim, n_batch)
			# create inverted labels for the fake samples
			y_gan = ones((n_batch, 1))
			# update the generator via the discriminator's error
			g_loss = gan_model.train_on_batch([z_input, labels_input], y_gan)
			# summarize loss on this batch
			print('>%d, %d/%d, d1=%.3f, d2=%.3f g=%.3f' %
				(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))
	# save the generator model
	g_model.save('cgan_generator.h5')

将所有这些联系在一起,下面列出了时尚 MNIST 数据集的条件深度卷积生成对抗网络的完整示例。

# example of training an conditional gan on the fashion mnist dataset
from numpy import expand_dims
from numpy import zeros
from numpy import ones
from numpy.random import randn
from numpy.random import randint
from keras.datasets.fashion_mnist import load_data
from keras.optimizers import Adam
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers import Reshape
from keras.layers import Flatten
from keras.layers import Conv2D
from keras.layers import Conv2DTranspose
from keras.layers import LeakyReLU
from keras.layers import Dropout
from keras.layers import Embedding
from keras.layers import Concatenate

# define the standalone discriminator model
def define_discriminator(in_shape=(28,28,1), n_classes=10):
	# label input
	in_label = Input(shape=(1,))
	# embedding for categorical input
	li = Embedding(n_classes, 50)(in_label)
	# scale up to image dimensions with linear activation
	n_nodes = in_shape[0] * in_shape[1]
	li = Dense(n_nodes)(li)
	# reshape to additional channel
	li = Reshape((in_shape[0], in_shape[1], 1))(li)
	# image input
	in_image = Input(shape=in_shape)
	# concat label as a channel
	merge = Concatenate()([in_image, li])
	# downsample
	fe = Conv2D(128, (3,3), strides=(2,2), padding='same')(merge)
	fe = LeakyReLU(alpha=0.2)(fe)
	# downsample
	fe = Conv2D(128, (3,3), strides=(2,2), padding='same')(fe)
	fe = LeakyReLU(alpha=0.2)(fe)
	# flatten feature maps
	fe = Flatten()(fe)
	# dropout
	fe = Dropout(0.4)(fe)
	# output
	out_layer = Dense(1, activation='sigmoid')(fe)
	# define model
	model = Model([in_image, in_label], out_layer)
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt, metrics=['accuracy'])
	return model

# define the standalone generator model
def define_generator(latent_dim, n_classes=10):
	# label input
	in_label = Input(shape=(1,))
	# embedding for categorical input
	li = Embedding(n_classes, 50)(in_label)
	# linear multiplication
	n_nodes = 7 * 7
	li = Dense(n_nodes)(li)
	# reshape to additional channel
	li = Reshape((7, 7, 1))(li)
	# image generator input
	in_lat = Input(shape=(latent_dim,))
	# foundation for 7x7 image
	n_nodes = 128 * 7 * 7
	gen = Dense(n_nodes)(in_lat)
	gen = LeakyReLU(alpha=0.2)(gen)
	gen = Reshape((7, 7, 128))(gen)
	# merge image gen and label input
	merge = Concatenate()([gen, li])
	# upsample to 14x14
	gen = Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')(merge)
	gen = LeakyReLU(alpha=0.2)(gen)
	# upsample to 28x28
	gen = Conv2DTranspose(128, (4,4), strides=(2,2), padding='same')(gen)
	gen = LeakyReLU(alpha=0.2)(gen)
	# output
	out_layer = Conv2D(1, (7,7), activation='tanh', padding='same')(gen)
	# define model
	model = Model([in_lat, in_label], out_layer)
	return model

# define the combined generator and discriminator model, for updating the generator
def define_gan(g_model, d_model):
	# make weights in the discriminator not trainable
	d_model.trainable = False
	# get noise and label inputs from generator model
	gen_noise, gen_label = g_model.input
	# get image output from the generator model
	gen_output = g_model.output
	# connect image output and label input from generator as inputs to discriminator
	gan_output = d_model([gen_output, gen_label])
	# define gan model as taking noise and label and outputting a classification
	model = Model([gen_noise, gen_label], gan_output)
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss='binary_crossentropy', optimizer=opt)
	return model

# load fashion mnist images
def load_real_samples():
	# load dataset
	(trainX, trainy), (_, _) = load_data()
	# expand to 3d, e.g. add channels
	X = expand_dims(trainX, axis=-1)
	# convert from ints to floats
	X = X.astype('float32')
	# scale from [0,255] to [-1,1]
	X = (X - 127.5) / 127.5
	return [X, trainy]

# # select real samples
def generate_real_samples(dataset, n_samples):
	# split into images and labels
	images, labels = dataset
	# choose random instances
	ix = randint(0, images.shape[0], n_samples)
	# select images and labels
	X, labels = images[ix], labels[ix]
	# generate class labels
	y = ones((n_samples, 1))
	return [X, labels], y

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples, n_classes=10):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_input = x_input.reshape(n_samples, latent_dim)
	# generate labels
	labels = randint(0, n_classes, n_samples)
	return [z_input, labels]

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n_samples):
	# generate points in latent space
	z_input, labels_input = generate_latent_points(latent_dim, n_samples)
	# predict outputs
	images = generator.predict([z_input, labels_input])
	# create class labels
	y = zeros((n_samples, 1))
	return [images, labels_input], y

# train the generator and discriminator
def train(g_model, d_model, gan_model, dataset, latent_dim, n_epochs=100, n_batch=128):
	bat_per_epo = int(dataset[0].shape[0] / n_batch)
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# enumerate batches over the training set
		for j in range(bat_per_epo):
			# get randomly selected 'real' samples
			[X_real, labels_real], y_real = generate_real_samples(dataset, half_batch)
			# update discriminator model weights
			d_loss1, _ = d_model.train_on_batch([X_real, labels_real], y_real)
			# generate 'fake' examples
			[X_fake, labels], y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
			# update discriminator model weights
			d_loss2, _ = d_model.train_on_batch([X_fake, labels], y_fake)
			# prepare points in latent space as input for the generator
			[z_input, labels_input] = generate_latent_points(latent_dim, n_batch)
			# create inverted labels for the fake samples
			y_gan = ones((n_batch, 1))
			# update the generator via the discriminator's error
			g_loss = gan_model.train_on_batch([z_input, labels_input], y_gan)
			# summarize loss on this batch
			print('>%d, %d/%d, d1=%.3f, d2=%.3f g=%.3f' %
				(i+1, j+1, bat_per_epo, d_loss1, d_loss2, g_loss))
	# save the generator model
	g_model.save('cgan_generator.h5')

# size of the latent space
latent_dim = 100
# create the discriminator
d_model = define_discriminator()
# create the generator
g_model = define_generator(latent_dim)
# create the gan
gan_model = define_gan(g_model, d_model)
# load image data
dataset = load_real_samples()
# train model
train(g_model, d_model, gan_model, dataset, latent_dim)

运行示例可能需要一些时间,建议使用 GPU 硬件,但不是必需的。

运行结束时,模型保存到名为“ cgan_generator.h5 的文件中。

条件服装生成

在本节中,我们将使用训练好的生成器模型来有条件地生成新的衣物照片。

我们可以更新我们的代码示例,用模型生成新的图像,现在生成基于类标签的图像。我们可以为列中的每个类标签生成 10 个示例。

下面列出了完整的示例。

# example of loading the generator model and generating images
from numpy import asarray
from numpy.random import randn
from numpy.random import randint
from keras.models import load_model
from matplotlib import pyplot

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n_samples, n_classes=10):
	# generate points in the latent space
	x_input = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_input = x_input.reshape(n_samples, latent_dim)
	# generate labels
	labels = randint(0, n_classes, n_samples)
	return [z_input, labels]

# create and save a plot of generated images
def save_plot(examples, n):
	# plot images
	for i in range(n * n):
		# define subplot
		pyplot.subplot(n, n, 1 + i)
		# turn off axis
		pyplot.axis('off')
		# plot raw pixel data
		pyplot.imshow(examples[i, :, :, 0], cmap='gray_r')
	pyplot.show()

# load model
model = load_model('cgan_generator.h5')
# generate images
latent_points, labels = generate_latent_points(100, 100)
# specify labels
labels = asarray([x for _ in range(10) for x in range(10)])
# generate images
X  = model.predict([latent_points, labels])
# scale from [-1,1] to [0,1]
X = (X + 1) / 2.0
# plot the result
save_plot(X, 10)

运行示例加载保存的条件 GAN 模型,并使用它生成 100 件衣服。

服装是按列排列的。从左至右依次为“ t 恤”、“”、“套头衫”、“连衣裙”、“外套”、“凉鞋”、“衬衫”、“运动鞋”、“包包包、“踝靴”。

我们可以看到,随机生成的服装不仅看似合理,而且也符合他们的预期类别。

Example of 100 Generated items of Clothing using a Conditional GAN.

使用条件 GAN 生成的 100 件衣服的示例。

扩展ˌ扩张

本节列出了一些您可能希望探索的扩展教程的想法。

  • 潜在空间大小。通过改变潜在空间的大小进行实验,并检查对生成的图像质量的影响。
  • 嵌入尺寸。通过改变类标签嵌入的大小进行实验,使其变小或变大,并检查对生成的图像质量的影响。
  • 替代架构。更新模型架构,在生成器和/或鉴别器模型的其他地方连接类标签,可能具有不同的维度,并检查对生成图像质量的影响。

如果你探索这些扩展,我很想知道。 在下面的评论中发表你的发现。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

邮件

报纸

应用程序接口

文章

摘要

在本教程中,您发现了如何开发一个有条件的生成对抗网络,用于有针对性地生成衣物。

具体来说,您了解到:

  • 用 GAN 生成随机样本的局限性可以用条件生成对抗网络来克服。
  • 如何开发和评估一个无条件生成的敌对网络,用于生成衣服的照片。
  • 如何开发和评估生成服装照片的条件生成对抗网络?

你有什么问题吗? 在下面的评论中提问,我会尽力回答。

如何在 Keras 从零开始开发 1D 生成对抗网络

原文:machinelearningmastery.com/how-to-deve…

最后更新于 2020 年 9 月 1 日

生成对抗网络,简称 GANs,是一种用于训练强大发电机模型的深度学习架构。

生成器模型能够生成新的人工样本,这些样本很可能来自现有的样本分布。

GANs 由生成器和鉴别器模型组成。生成器负责从域中生成新的样本,鉴别器负责分类样本是真的还是假的(生成的)。重要的是,鉴别器模型的表现用于更新鉴别器本身和生成器模型的模型权重。这意味着生成器从未真正看到域中的示例,而是根据鉴别器的表现进行调整。

这是一个既要理解又要训练的复杂模型。

一种更好地理解 GAN 模型的本质以及如何训练它们的方法是为一个非常简单的任务从头开发一个模型。

一个简单的任务是一维函数,它为从零开始开发一个简单的 GAN 提供了良好的环境。这是因为真实的和生成的样本都可以被标绘和视觉检查,以了解所学的知识。一个简单的函数也不需要复杂的神经网络模型,这意味着架构上使用的特定生成器和鉴别器模型可以很容易理解。

在本教程中,我们将选择一个简单的一维函数,并将其用作使用 Keras 深度学习库从零开始开发和评估生成对抗网络的基础。

完成本教程后,您将知道:

  • 从零开始为简单的一维函数开发生成对抗网络的好处。
  • 如何开发独立的鉴别器和生成器模型,以及通过鉴别器的预测行为训练生成器的复合模型。
  • 如何在问题域真实例子的背景下主观评估生成的样本?

用我的新书Python 生成对抗网络启动你的项目,包括分步教程和所有示例的 Python 源代码文件。

我们开始吧。

What Is the Naive Classifier for Each Imbalanced Classification Metric?

如何在 Keras 从零开始发展 1D 生成对抗网络 图片由土地管理局提供,保留部分权利。

教程概述

本教程分为六个部分;它们是:

  1. 选择一维函数
  2. 定义鉴别器模型
  3. 定义生成器模型
  4. 训练发电机模型
  5. 评估 GAN 的表现
  6. 训练 GAN 的完整示例

选择一维函数

第一步是选择一个一维函数进行建模。

某种形式的东西:

y = f(x)

其中 x 为输入值, y 为函数的输出值。

具体来说,我们想要一个我们容易理解和绘制的函数。这将有助于设定模型应该生成什么的预期,并有助于使用生成的示例的视觉检查来了解它们的质量。

我们将使用一个简单的功能;也就是说,函数将返回输入的平方。你可能还记得高中代数中的这个函数,它是 u 形函数。

我们可以用 Python 定义函数如下:

# simple function
def calculate(x):
	return x * x

我们可以将输入域定义为-0.5 到 0.5 之间的实数值,并计算该线性范围内每个输入值的输出值,然后绘制结果图,以了解输入与输出之间的关系。

下面列出了完整的示例。

# demonstrate simple x² function
from matplotlib import pyplot

# simple function
def calculate(x):
	return x * x

# define inputs
inputs = [-0.5, -0.4, -0.3, -0.2, -0.1, 0, 0.1, 0.2, 0.3, 0.4, 0.5]
# calculate outputs
outputs = [calculate(x) for x in inputs]
# plot the result
pyplot.plot(inputs, outputs)
pyplot.show()

运行该示例计算每个输入值的输出值,并创建输入值与输出值的关系图。

我们可以看到,远离 0.0 的值会导致更大的输出值,而接近零的值会导致更小的输出值,并且这种行为在零附近是对称的。

这就是众所周知的 X² 一维函数的 u 形图。

Plot of inputs vs. outputs for X² function.

X² 函数的输入与输出图。

我们可以从函数中生成随机样本或点。

这可以通过生成介于-0.5 和 0.5 之间的随机值并计算相关的输出值来实现。重复多次将给出函数的点样本,例如“T0”真实样本

使用散点图绘制这些样本将显示相同的 u 形图,尽管由单个随机样本组成。

下面列出了完整的示例。

首先,我们生成 0 到 1 之间的均匀随机值,然后将它们移动到-0.5 到 0.5 的范围内。然后,我们计算每个随机生成的输入值的输出值,并将这些数组组合成一个具有 n 行(100)和两列的单个 NumPy 数组。

# example of generating random samples from X²
from numpy.random import rand
from numpy import hstack
from matplotlib import pyplot

# generate randoms sample from x²
def generate_samples(n=100):
	# generate random inputs in [-0.5, 0.5]
	X1 = rand(n) - 0.5
	# generate outputs X² (quadratic)
	X2 = X1 * X1
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	return hstack((X1, X2))

# generate samples
data = generate_samples()
# plot samples
pyplot.scatter(data[:, 0], data[:, 1])
pyplot.show()

运行该示例会生成 100 个随机输入及其计算输出,并将样本绘制为散点图,显示熟悉的 u 形。

Plot of randomly generated sample of inputs vs. calculated outputs for X² function.

X² 函数随机生成的输入样本与计算输出的关系图。

我们可以使用这个函数作为为我们的鉴别器函数生成真实样本的起点。具体来说,一个样本由一个包含两个元素的向量组成,一个元素用于输入,一个元素用于一维函数的输出。

我们还可以想象发电机模型如何产生新的样本,我们可以绘制出来,并与 X² 函数的预期 u 形进行比较。具体来说,生成器将输出一个包含两个元素的向量:一个用于输入,一个用于一维函数的输出。

定义鉴别器模型

下一步是定义鉴别器模型。

该模型必须从我们的问题中提取一个样本,例如一个具有两个元素的向量,并输出一个关于样本是真的还是假的分类预测。

这是一个二分类问题。

  • 输入:两个实值的样本。
  • 输出:二进制分类,样本真实(或虚假)的可能性。

这个问题很简单,意味着我们不需要复杂的神经网络来建模。

鉴别器模型将有一个包含 25 个节点的隐藏层,我们将使用 ReLU 激活函数和一种称为 he 权重初始化的适当权重初始化方法。

输出层将有一个使用 sigmoid 激活函数进行二进制分类的节点。

该模型将最小化二元交叉熵损失函数,将使用随机梯度下降的 Adam 版本,因为它非常有效。

下面的 define_discriminator() 函数定义并返回鉴别器模型。该函数参数化预期的输入数量,默认为两个。

# define the standalone discriminator model
def define_discriminator(n_inputs=2):
	model = Sequential()
	model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=n_inputs))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	return model

我们可以使用这个函数来定义鉴别器模型并对其进行总结。下面列出了完整的示例。

# define the discriminator model
from keras.models import Sequential
from keras.layers import Dense
from keras.utils.vis_utils import plot_model

# define the standalone discriminator model
def define_discriminator(n_inputs=2):
	model = Sequential()
	model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=n_inputs))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	return model

# define the discriminator model
model = define_discriminator()
# summarize the model
model.summary()
# plot the model
plot_model(model, to_file='discriminator_plot.png', show_shapes=True, show_layer_names=True)

运行该示例定义了鉴别器模型并对其进行了总结。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 25)                75
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 26
=================================================================
Total params: 101
Trainable params: 101
Non-trainable params: 0
_________________________________________________________________

还创建了一个模型图,我们可以看到该模型预期两个输入,并将预测一个输出。

:创建此图假设安装了 pydot 和 graphviz 库。如果这是一个问题,您可以注释掉 plot_model 函数的导入语句和对 plot_model() 函数的调用。

Plot of the Discriminator Model in the GAN

GAN 中鉴别器模型的绘制

我们现在可以用类标签为 1 的真实例子和类标签为 0 的随机生成的样本开始训练这个模型。

没有必要这样做,但是我们将开发的元素将在以后有用,这有助于看到鉴别器只是一个正常的神经网络模型。

首先,我们可以从预测部分更新我们的 generate_samples() 函数,并将其称为 generate_real_samples() ,并让它返回真实样本的输出类标签,具体来说,就是 1 个值的数组,其中 class=1 表示 real。

# generate n real samples with class labels
def generate_real_samples(n):
	# generate inputs in [-0.5, 0.5]
	X1 = rand(n) - 0.5
	# generate outputs X²
	X2 = X1 * X1
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	X = hstack((X1, X2))
	# generate class labels
	y = ones((n, 1))
	return X, y

接下来,我们可以创建这个函数的副本来创建假例子。

在这种情况下,我们将为样本的两个元素生成-1 和 1 范围内的随机值。所有这些示例的输出类标签都是 0。

这个函数将充当我们的假生成器模型。

# generate n fake samples with class labels
def generate_fake_samples(n):
	# generate inputs in [-1, 1]
	X1 = -1 + rand(n) * 2
	# generate outputs in [-1, 1]
	X2 = -1 + rand(n) * 2
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	X = hstack((X1, X2))
	# generate class labels
	y = zeros((n, 1))
	return X, y

接下来,我们需要一个函数来训练和评估鉴别器模型。

这可以通过手动枚举训练时期并为每个时期生成半批真实例子和半批伪造例子,以及在每个例子上更新模型来实现,例如一整批例子。可以使用 train() 功能,但是在这种情况下,我们将直接使用 train_on_batch() 功能。

然后可以在生成的示例上评估模型,并且我们可以报告真实和虚假样本的分类准确率。

下面的 train_discriminator() 函数实现了这一点,对模型进行了 1000 批次的训练,每批次使用 128 个样本(64 个假样本,64 个真样本)。

# train the discriminator model
def train_discriminator(model, n_epochs=1000, n_batch=128):
	half_batch = int(n_batch / 2)
	# run epochs manually
	for i in range(n_epochs):
		# generate real examples
		X_real, y_real = generate_real_samples(half_batch)
		# update model
		model.train_on_batch(X_real, y_real)
		# generate fake examples
		X_fake, y_fake = generate_fake_samples(half_batch)
		# update model
		model.train_on_batch(X_fake, y_fake)
		# evaluate the model
		_, acc_real = model.evaluate(X_real, y_real, verbose=0)
		_, acc_fake = model.evaluate(X_fake, y_fake, verbose=0)
		print(i, acc_real, acc_fake)

我们可以将所有这些联系在一起,并在真实和虚假的例子上训练鉴别器模型。

下面列出了完整的示例。

# define and fit a discriminator model
from numpy import zeros
from numpy import ones
from numpy import hstack
from numpy.random import rand
from numpy.random import randn
from keras.models import Sequential
from keras.layers import Dense

# define the standalone discriminator model
def define_discriminator(n_inputs=2):
	model = Sequential()
	model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=n_inputs))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	return model

# generate n real samples with class labels
def generate_real_samples(n):
	# generate inputs in [-0.5, 0.5]
	X1 = rand(n) - 0.5
	# generate outputs X²
	X2 = X1 * X1
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	X = hstack((X1, X2))
	# generate class labels
	y = ones((n, 1))
	return X, y

# generate n fake samples with class labels
def generate_fake_samples(n):
	# generate inputs in [-1, 1]
	X1 = -1 + rand(n) * 2
	# generate outputs in [-1, 1]
	X2 = -1 + rand(n) * 2
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	X = hstack((X1, X2))
	# generate class labels
	y = zeros((n, 1))
	return X, y

# train the discriminator model
def train_discriminator(model, n_epochs=1000, n_batch=128):
	half_batch = int(n_batch / 2)
	# run epochs manually
	for i in range(n_epochs):
		# generate real examples
		X_real, y_real = generate_real_samples(half_batch)
		# update model
		model.train_on_batch(X_real, y_real)
		# generate fake examples
		X_fake, y_fake = generate_fake_samples(half_batch)
		# update model
		model.train_on_batch(X_fake, y_fake)
		# evaluate the model
		_, acc_real = model.evaluate(X_real, y_real, verbose=0)
		_, acc_fake = model.evaluate(X_fake, y_fake, verbose=0)
		print(i, acc_real, acc_fake)

# define the discriminator model
model = define_discriminator()
# fit the model
train_discriminator(model)

运行该示例生成真实和虚假的示例并更新模型,然后在相同的示例上评估模型并打印分类准确率。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

在这种情况下,模型快速学会以完美的准确率正确识别真实的例子,并且非常擅长以 80%到 90%的准确率识别虚假的例子。

...
995 1.0 0.875
996 1.0 0.921875
997 1.0 0.859375
998 1.0 0.9375
999 1.0 0.8125

训练鉴别器模型很简单。目标是训练一个生成器模型,而不是鉴别器模型,这才是 GANs 真正的复杂性所在。

定义生成器模型

下一步是定义生成器模型。

生成器模型从潜在空间中获取一个点作为输入,并生成一个新的样本,例如一个包含我们函数的输入和输出元素的向量,例如 x 和 x².

潜变量是隐藏的或未被观察到的变量,潜空间是这些变量的多维向量空间。我们可以定义问题潜在空间的大小以及潜在空间中变量的形状或分布。

这是因为潜在空间没有意义,直到生成器模型在学习时开始给空间中的点赋予意义。在训练之后,潜在空间中的点将对应于输出空间中的点,例如生成的样本空间中的点。

我们将定义一个五维的小潜在空间,并使用 GAN 文献中的标准方法,对潜在空间中的每个变量使用高斯分布。我们将通过从标准高斯分布中提取随机数来生成新的输入,即平均值为零,标准偏差为 1。

  • 输入:潜在空间中的点,例如高斯随机数的五元素向量。
  • 输出:表示我们的函数(x 和 x²).)的生成样本的二元向量

生成器模型将像鉴别器模型一样小。

它将有一个包含五个节点的隐藏层,并将使用 ReLU 激活功能和 he 权重初始化。输出层将为生成向量中的两个元素提供两个节点,并将使用线性激活函数。

使用线性激活函数是因为我们知道,我们希望生成器输出一个实值向量,第一个元素的比例为[-0.5,0.5],第二个元素的比例约为[0.0,0.25]。

模型未编译。其原因是发电机型号不直接匹配。

下面的 define_generator() 函数定义并返回生成器模型。

潜在维度的大小是参数化的,以防我们以后想要使用它,模型的输出形状也是参数化的,与定义鉴别器模型的函数相匹配。

# define the standalone generator model
def define_generator(latent_dim, n_outputs=2):
	model = Sequential()
	model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=latent_dim))
	model.add(Dense(n_outputs, activation='linear'))
	return model

我们可以总结模型,以帮助更好地理解输入和输出形状。

下面列出了完整的示例。

# define the generator model
from keras.models import Sequential
from keras.layers import Dense
from keras.utils.vis_utils import plot_model

# define the standalone generator model
def define_generator(latent_dim, n_outputs=2):
	model = Sequential()
	model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=latent_dim))
	model.add(Dense(n_outputs, activation='linear'))
	return model

# define the discriminator model
model = define_generator(5)
# summarize the model
model.summary()
# plot the model
plot_model(model, to_file='generator_plot.png', show_shapes=True, show_layer_names=True)

运行该示例定义了生成器模型并对其进行了总结。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense_1 (Dense)              (None, 15)                90
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 32
=================================================================
Total params: 122
Trainable params: 122
Non-trainable params: 0
_________________________________________________________________

还创建了模型的图,我们可以看到模型期望来自潜在空间的五元素点作为输入,并将预测两元素向量作为输出。

:创建此图假设安装了 pydot 和 graphviz 库。如果这是一个问题,您可以注释掉 plot_model 函数的导入语句和对 plot_model() 函数的调用。

Plot of the Generator Model in the GAN

GAN 中的发电机模型图

我们可以看到,该模型将潜在空间中的一个随机五元向量作为输入,并为我们的一维函数输出一个二元向量。

这种模式目前做不了什么。然而,我们可以演示如何使用它来生成样本。这是不需要的,但是这些元素中的一些在以后可能会有用。

第一步是在潜在空间中生成新的点。我们可以通过调用 randn() NumPy 函数来实现这一点,该函数用于生成从标准高斯中提取的随机数的数组。

然后随机数的数组可以被重新整形为样本:即 n 行,每行五个元素。下面的*生成 _ 潜在 _ 点()*函数实现了这一点,并在潜在空间中生成所需数量的点,这些点可用作生成器模型的输入。

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n):
	# generate points in the latent space
	x_input = randn(latent_dim * n)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n, latent_dim)
	return x_input

接下来,我们可以使用生成的点作为生成器模型的输入来生成新的样本,然后绘制样本。

下面的 generate_fake_samples() 函数实现了这一点,其中定义的生成器和潜在空间的大小以及模型要生成的点数作为参数传递。

# use the generator to generate n fake examples and plot the results
def generate_fake_samples(generator, latent_dim, n):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n)
	# predict outputs
	X = generator.predict(x_input)
	# plot the results
	pyplot.scatter(X[:, 0], X[:, 1])
	pyplot.show()

将这些联系在一起,完整的示例如下所示。

# define and use the generator model
from numpy.random import randn
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot

# define the standalone generator model
def define_generator(latent_dim, n_outputs=2):
	model = Sequential()
	model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=latent_dim))
	model.add(Dense(n_outputs, activation='linear'))
	return model

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n):
	# generate points in the latent space
	x_input = randn(latent_dim * n)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n, latent_dim)
	return x_input

# use the generator to generate n fake examples and plot the results
def generate_fake_samples(generator, latent_dim, n):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n)
	# predict outputs
	X = generator.predict(x_input)
	# plot the results
	pyplot.scatter(X[:, 0], X[:, 1])
	pyplot.show()

# size of the latent space
latent_dim = 5
# define the discriminator model
model = define_generator(latent_dim)
# generate and plot generated samples
generate_fake_samples(model, latent_dim, 100)

运行该示例从潜在空间生成 100 个随机点,将其用作生成器的输入,并从我们的一维函数域生成 100 个假样本。

由于生成器没有经过训练,生成的点完全是垃圾,正如我们所料,但我们可以想象,随着模型的训练,这些点将慢慢开始类似目标函数及其 u 形。

Scatter plot of Fake Samples Predicted by the Generator Model.

生成器模型预测的假样本散点图。

我们现在已经看到了如何定义和使用生成器模型。我们需要以这种方式使用生成器模型来创建样本,以便鉴别器进行分类。

我们没有看到发电机模型是如何训练的;那是下一个。

训练发电机模型

生成器模型中的权重根据鉴别器模型的表现进行更新。

当鉴别器擅长检测假样本时,生成器更新较多,当鉴别器模型在检测假样本时相对较差或混乱时,生成器模型更新较少。

这就定义了这两种模式之间的零和或对立关系。

使用 Keras API 实现这一点可能有很多方法,但最简单的方法可能是创建一个包含或封装生成器和鉴别器模型的新模型。

具体来说,可以定义一个新的 GAN 模型,该模型将生成器和鉴别器堆叠在一起,使得生成器接收潜在空间中的随机点作为输入,生成直接馈送到鉴别器模型中的样本,进行分类,并且该更大模型的输出可以用于更新生成器的模型权重。

明确地说,我们不是在谈论新的第三个模型,只是一个逻辑上的第三个模型,它使用独立生成器和鉴别器模型中已经定义的层和权重。

只有鉴别者才关心真假例子的区分;因此,鉴别器模型可以以独立的方式对每个例子进行训练。

生成器模型只关心鉴别器在假例子上的表现。因此,当它是 GAN 模型的一部分时,我们将把鉴别器中的所有层标记为不可训练的,这样它们就不能被更新和过度训练在假的例子上。

当通过这个包含的 GAN 模型训练生成器时,有一个更重要的变化。我们希望鉴别器认为生成器输出的样本是真实的,而不是伪造的。因此,当生成器被训练为 GAN 模型的一部分时,我们将把生成的样本标记为真实的(类 1)。

我们可以想象,鉴别器然后将生成的样本分类为不真实(类别 0)或真实概率低(0.3 或 0.5)。用于更新模型权重的反向传播过程将认为这是一个很大的误差,并将更新模型权重(即,仅生成器中的权重)来校正该误差,这又使得生成器更好地生成似是而非的假样本。

让我们把这个具体化。

  • 输入:潜在空间中的点,例如高斯随机数的五元素向量。
  • 输出:二进制分类,样本真实(或虚假)的可能性。

下面的 define_gan() 函数将已经定义的生成器和鉴别器模型作为参数,并创建包含这两个模型的新的逻辑第三模型。鉴别器中的权重被标记为不可训练,这仅影响 GAN 模型看到的权重,而不影响独立鉴别器模型。

GAN 模型然后使用相同的二元交叉熵损失函数作为鉴别器和有效的亚当版本的随机梯度下降

# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
	# make weights in the discriminator not trainable
	discriminator.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the discriminator
	model.add(discriminator)
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam')
	return model

使鉴别器不可训练是 Keras API 中的一个聪明的技巧。

可训练的属性会在编译模型时影响模型。鉴别器模型是用可训练层编译的,因此当通过调用 train_on_batch() 更新独立模型时,这些层中的模型权重将被更新。

鉴别器模型被标记为不可训练,被添加到 GAN 模型中,并被编译。在该模型中,鉴别器模型的模型权重是不可训练的,并且当通过调用 train_on_batch() 更新 GAN 模型时,不能改变。

这里的 Keras API 文档中描述了这种行为:

下面列出了创建鉴别器、生成器和复合模型的完整示例。

# demonstrate creating the three models in the gan
from keras.models import Sequential
from keras.layers import Dense
from keras.utils.vis_utils import plot_model

# define the standalone discriminator model
def define_discriminator(n_inputs=2):
	model = Sequential()
	model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=n_inputs))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	return model

# define the standalone generator model
def define_generator(latent_dim, n_outputs=2):
	model = Sequential()
	model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=latent_dim))
	model.add(Dense(n_outputs, activation='linear'))
	return model

# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
	# make weights in the discriminator not trainable
	discriminator.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the discriminator
	model.add(discriminator)
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam')
	return model

# size of the latent space
latent_dim = 5
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# summarize gan model
gan_model.summary()
# plot gan model
plot_model(gan_model, to_file='gan_plot.png', show_shapes=True, show_layer_names=True)

运行该示例首先会创建复合模型的摘要。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
sequential_2 (Sequential)    (None, 2)                 122
_________________________________________________________________
sequential_1 (Sequential)    (None, 1)                 101
=================================================================
Total params: 223
Trainable params: 122
Non-trainable params: 101
_________________________________________________________________

还创建了模型的图,我们可以看到模型期望潜在空间中的五元素点作为输入,并将预测单个输出分类标签。

注意,创建此图假设安装了 pydot 和 graphviz 库。如果这是一个问题,您可以注释掉 plot_model 函数的导入语句和对 plot_model() 函数的调用。

Plot of the Composite Generator and Discriminator Model in the GAN

GAN 中复合发生器和鉴别器模型的绘制

训练复合模型包括通过上一节中的generate _ 潜伏 _points() 函数和 class=1 标签在潜伏空间中生成一批值的点,并调用 train_on_batch() 函数。

下面的 train_gan() 函数演示了这一点,尽管它非常无趣,因为每个时期只有生成器会被更新,给鉴别器留下默认的模型权重。

# train the composite model
def train_gan(gan_model, latent_dim, n_epochs=10000, n_batch=128):
	# manually enumerate epochs
	for i in range(n_epochs):
		# prepare points in latent space as input for the generator
		x_gan = generate_latent_points(latent_dim, n_batch)
		# create inverted labels for the fake samples
		y_gan = ones((n_batch, 1))
		# update the generator via the discriminator's error
		gan_model.train_on_batch(x_gan, y_gan)

相反,我们需要的是首先用真样本和假样本更新鉴别器模型,然后通过复合模型更新生成器。

这需要组合鉴别器部分定义的 train_discriminator() 函数和上面定义的 train_gan() 函数中的元素。它还要求 generate_fake_samples() 函数使用生成器模型来生成假样本,而不是生成随机数。

下面列出了更新鉴别器模型和生成器(通过复合模型)的完整训练函数。

# train the generator and discriminator
def train(g_model, d_model, gan_model, latent_dim, n_epochs=10000, n_batch=128):
	# determine half the size of one batch, for updating the discriminator
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# prepare real samples
		x_real, y_real = generate_real_samples(half_batch)
		# prepare fake examples
		x_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
		# update discriminator
		d_model.train_on_batch(x_real, y_real)
		d_model.train_on_batch(x_fake, y_fake)
		# prepare points in latent space as input for the generator
		x_gan = generate_latent_points(latent_dim, n_batch)
		# create inverted labels for the fake samples
		y_gan = ones((n_batch, 1))
		# update the generator via the discriminator's error
		gan_model.train_on_batch(x_gan, y_gan)

我们几乎拥有为一维函数开发 GAN 所需的一切。

剩下的一个方面是对模型的评估。

评估 GAN 的表现

通常,没有客观的方法来评估 GAN 模型的表现。

在这种特定情况下,我们可以为生成的样本设计一个客观的度量,因为我们知道真正的底层输入域和目标函数,并且可以计算一个客观的误差度量。

尽管如此,我们不会在本教程中计算这个客观误差分数。相反,我们将使用大多数 GAN 应用中使用的主观方法。具体来说,我们将使用生成器生成新的样本,并根据域中的真实样本对它们进行检查。

首先,我们可以使用上面鉴别器部分开发的 generate_real_samples() 函数来生成真实的例子。创建这些示例的散点图将创建我们熟悉的目标函数 u 形。

# generate n real samples with class labels
def generate_real_samples(n):
	# generate inputs in [-0.5, 0.5]
	X1 = rand(n) - 0.5
	# generate outputs X²
	X2 = X1 * X1
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	X = hstack((X1, X2))
	# generate class labels
	y = ones((n, 1))
	return X, y

接下来,我们可以使用生成器模型生成相同数量的假样本。

这需要首先通过上面生成器部分中开发的*生成潜在点()*函数在潜在空间中生成相同数量的点。然后,这些可以传递到生成器模型,并用于生成样本,这些样本也可以绘制在同一散点图上。

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n):
	# generate points in the latent space
	x_input = randn(latent_dim * n)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n, latent_dim)
	return x_input

下面的 generate_fake_samples() 函数生成这些假样本和关联的类标签 0,这些标签在后面会很有用。

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n)
	# predict outputs
	X = generator.predict(x_input)
	# create class labels
	y = zeros((n, 1))
	return X, y

将两个样本绘制在同一个图形上,可以直接比较它们,以查看是否覆盖了相同的输入和输出域,以及目标函数的预期形状是否已被适当捕获,至少在主观上是如此。

下面的*summary _ performance()*函数可以在训练期间随时调用,以创建真实点和生成点的散点图,从而了解发电机模型的当前能力。

# plot real and fake points
def summarize_performance(generator, latent_dim, n=100):
	# prepare real samples
	x_real, y_real = generate_real_samples(n)
	# prepare fake examples
	x_fake, y_fake = generate_fake_samples(generator, latent_dim, n)
	# scatter plot real and fake data points
	pyplot.scatter(x_real[:, 0], x_real[:, 1], color='red')
	pyplot.scatter(x_fake[:, 0], x_fake[:, 1], color='blue')
	pyplot.show()

同时我们也可能对鉴别器模型的表现感兴趣。

具体来说,我们有兴趣知道鉴别器模型能在多大程度上正确识别真假样本。一个好的生成器模型应该会使鉴别器模型变得混乱,导致真实和虚假例子的分类准确率接近 50%。

我们可以更新*summary _ performance()*函数,也可以将鉴别器和当前历元号作为参数,并在真假示例的样本上报告准确性。

# evaluate the discriminator and plot real and fake points
def summarize_performance(epoch, generator, discriminator, latent_dim, n=100):
	# prepare real samples
	x_real, y_real = generate_real_samples(n)
	# evaluate discriminator on real examples
	_, acc_real = discriminator.evaluate(x_real, y_real, verbose=0)
	# prepare fake examples
	x_fake, y_fake = generate_fake_samples(generator, latent_dim, n)
	# evaluate discriminator on fake examples
	_, acc_fake = discriminator.evaluate(x_fake, y_fake, verbose=0)
	# summarize discriminator performance
	print(epoch, acc_real, acc_fake)
	# scatter plot real and fake data points
	pyplot.scatter(x_real[:, 0], x_real[:, 1], color='red')
	pyplot.scatter(x_fake[:, 0], x_fake[:, 1], color='blue')
	pyplot.show()

然后可以在训练期间定期调用该函数。

例如,如果我们选择为 10,000 次迭代训练模型,那么每 2,000 次迭代检查模型的表现可能会很有趣。

我们可以通过 n_eval 参数参数化登记频率,并在适当的迭代次数后从 train() 函数调用*summary _ performance()*函数来实现。

带此变化的*列车()*功能的更新版本如下。

# train the generator and discriminator
def train(g_model, d_model, gan_model, latent_dim, n_epochs=10000, n_batch=128, n_eval=2000):
	# determine half the size of one batch, for updating the discriminator
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# prepare real samples
		x_real, y_real = generate_real_samples(half_batch)
		# prepare fake examples
		x_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
		# update discriminator
		d_model.train_on_batch(x_real, y_real)
		d_model.train_on_batch(x_fake, y_fake)
		# prepare points in latent space as input for the generator
		x_gan = generate_latent_points(latent_dim, n_batch)
		# create inverted labels for the fake samples
		y_gan = ones((n_batch, 1))
		# update the generator via the discriminator's error
		gan_model.train_on_batch(x_gan, y_gan)
		# evaluate the model every n_eval epochs
		if (i+1) % n_eval == 0:
			summarize_performance(i, g_model, d_model, latent_dim)

训练 GAN 的完整示例

我们现在有了在我们选择的一维函数上训练和评估 GAN 所需的一切。

下面列出了完整的示例。

# train a generative adversarial network on a one-dimensional function
from numpy import hstack
from numpy import zeros
from numpy import ones
from numpy.random import rand
from numpy.random import randn
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot

# define the standalone discriminator model
def define_discriminator(n_inputs=2):
	model = Sequential()
	model.add(Dense(25, activation='relu', kernel_initializer='he_uniform', input_dim=n_inputs))
	model.add(Dense(1, activation='sigmoid'))
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	return model

# define the standalone generator model
def define_generator(latent_dim, n_outputs=2):
	model = Sequential()
	model.add(Dense(15, activation='relu', kernel_initializer='he_uniform', input_dim=latent_dim))
	model.add(Dense(n_outputs, activation='linear'))
	return model

# define the combined generator and discriminator model, for updating the generator
def define_gan(generator, discriminator):
	# make weights in the discriminator not trainable
	discriminator.trainable = False
	# connect them
	model = Sequential()
	# add generator
	model.add(generator)
	# add the discriminator
	model.add(discriminator)
	# compile model
	model.compile(loss='binary_crossentropy', optimizer='adam')
	return model

# generate n real samples with class labels
def generate_real_samples(n):
	# generate inputs in [-0.5, 0.5]
	X1 = rand(n) - 0.5
	# generate outputs X²
	X2 = X1 * X1
	# stack arrays
	X1 = X1.reshape(n, 1)
	X2 = X2.reshape(n, 1)
	X = hstack((X1, X2))
	# generate class labels
	y = ones((n, 1))
	return X, y

# generate points in latent space as input for the generator
def generate_latent_points(latent_dim, n):
	# generate points in the latent space
	x_input = randn(latent_dim * n)
	# reshape into a batch of inputs for the network
	x_input = x_input.reshape(n, latent_dim)
	return x_input

# use the generator to generate n fake examples, with class labels
def generate_fake_samples(generator, latent_dim, n):
	# generate points in latent space
	x_input = generate_latent_points(latent_dim, n)
	# predict outputs
	X = generator.predict(x_input)
	# create class labels
	y = zeros((n, 1))
	return X, y

# evaluate the discriminator and plot real and fake points
def summarize_performance(epoch, generator, discriminator, latent_dim, n=100):
	# prepare real samples
	x_real, y_real = generate_real_samples(n)
	# evaluate discriminator on real examples
	_, acc_real = discriminator.evaluate(x_real, y_real, verbose=0)
	# prepare fake examples
	x_fake, y_fake = generate_fake_samples(generator, latent_dim, n)
	# evaluate discriminator on fake examples
	_, acc_fake = discriminator.evaluate(x_fake, y_fake, verbose=0)
	# summarize discriminator performance
	print(epoch, acc_real, acc_fake)
	# scatter plot real and fake data points
	pyplot.scatter(x_real[:, 0], x_real[:, 1], color='red')
	pyplot.scatter(x_fake[:, 0], x_fake[:, 1], color='blue')
	pyplot.show()

# train the generator and discriminator
def train(g_model, d_model, gan_model, latent_dim, n_epochs=10000, n_batch=128, n_eval=2000):
	# determine half the size of one batch, for updating the discriminator
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_epochs):
		# prepare real samples
		x_real, y_real = generate_real_samples(half_batch)
		# prepare fake examples
		x_fake, y_fake = generate_fake_samples(g_model, latent_dim, half_batch)
		# update discriminator
		d_model.train_on_batch(x_real, y_real)
		d_model.train_on_batch(x_fake, y_fake)
		# prepare points in latent space as input for the generator
		x_gan = generate_latent_points(latent_dim, n_batch)
		# create inverted labels for the fake samples
		y_gan = ones((n_batch, 1))
		# update the generator via the discriminator's error
		gan_model.train_on_batch(x_gan, y_gan)
		# evaluate the model every n_eval epochs
		if (i+1) % n_eval == 0:
			summarize_performance(i, g_model, d_model, latent_dim)

# size of the latent space
latent_dim = 5
# create the discriminator
discriminator = define_discriminator()
# create the generator
generator = define_generator(latent_dim)
# create the gan
gan_model = define_gan(generator, discriminator)
# train model
train(generator, discriminator, gan_model, latent_dim)

运行该示例每 2,000 次训练迭代(批次)报告一次模型表现,并创建一个图。

:考虑到算法或评估程序的随机性,或数值准确率的差异,您的结果可能会有所不同。考虑运行该示例几次,并比较平均结果。

我们可以看到训练过程是比较不稳定的。第一列报告迭代次数,第二列报告鉴别器对真实示例的分类准确率,第三列报告鉴别器对生成(伪造)示例的分类准确率。

在这种情况下,我们可以看到鉴别器对真实的例子仍然相对困惑,并且识别假例子的表现各不相同。

1999 0.45 1.0
3999 0.45 0.91
5999 0.86 0.16
7999 0.6 0.41
9999 0.15 0.93

为了简洁起见,我将省略提供这里创建的五个图;相反,我们只看两个。

第一个图是在 2000 次迭代后创建的,显示了真实(红色)和假(蓝色)样本。虽然具有正确的函数关系,但是该模型最初在仅正输入域中生成的点的聚类中表现不佳。

Scatter Plot of Real and Generated Examples for the Target Function After 2,000 Iterations.

2000 次迭代后目标函数的真实和生成示例的散点图。

第二个图显示了经过 10,000 次迭代后真实(红色)与虚假(蓝色)的对比。

在这里,我们可以看到生成器模型在生成似是而非的样本方面做得很合理,输入值在[-0.5 和 0.5]之间的正确域中,输出值显示出 X² 关系,或者接近这种关系。

Scatter Plot of Real and Generated Examples for the Target Function After 10,000 Iterations.

10,000 次迭代后目标函数的真实和生成示例的散点图。

扩展ˌ扩张

本节列出了一些您可能希望探索的扩展教程的想法。

  • 模型架构。尝试鉴别器和生成器的替代模型架构,例如更多或更少的节点、层和替代激活函数,例如泄漏 ReLU。
  • 数据缩放。实验交替激活函数,如双曲正切(tanh)和任何所需的训练数据缩放。
  • 替代目标功能。用另一个目标函数进行实验,例如简单的正弦波、高斯分布、不同的二次函数,甚至多模态多项式函数。

如果你探索这些扩展,我很想知道。 在下面的评论中发表你的发现。

进一步阅读

如果您想更深入地了解这个主题,本节将提供更多资源。

应用程序接口

摘要

在本教程中,您发现了如何为一维函数从零开始开发生成对抗网络。

具体来说,您了解到:

  • 从零开始为简单的一维函数开发生成对抗网络的好处。
  • 如何开发独立的鉴别器和生成器模型,以及通过鉴别器的预测行为训练生成器的复合模型。
  • 如何在问题域真实例子的背景下主观评估生成的样本?

你有什么问题吗? 在下面的评论中提问,我会尽力回答。