如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?

如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第1张图片

作者 | Jason Brownlee
编译 | CDA数据分析师

生成对抗网络(GAN)是一种用于训练深度卷积模型以生成合成图像的体系结构。

尽管非常有效,但默认GAN无法控制生成的图像类型。信息最大化GAN(简称InfoGAN)是GAN架构的扩展,它引入了架构自动学习的控制变量,并允许控制生成的图像,例如在生成图像样式的情况下,厚度和类型手写的数字。

在本教程中,您将了解如何从头开始实现信息最大化生成对抗网络模型。

完成本教程后,您将了解:

  • InfoGAN的动机是希望解开和控制生成的图像中的属性。
  • InfoGAN涉及添加控制变量以生成预测控制变量的辅助模型,通过互信息损失函数进行训练。
  • 如何从头开发和训练InfoGAN模型,并使用控制变量来控制模型生成的数字。

让我们开始吧。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第2张图片

教程概述

本教程分为四个部分; 他们是:

  1. 什么是最大化GAN的信息
  2. 如何实现InfoGAN丢失功能
  3. 如何为MNIST开发InfoGAN
  4. 如何使用训练有素的InfoGAN模型使用控制代码

什么是最大化GAN的信息

Generative Adversarial Network(简称GAN)是一种用于训练生成模型的体系结构,例如用于生成合成图像的模型。

它涉及同时训练生成器模型以生成具有鉴别器模型的图像,该模型学习将图像分类为真实的(来自训练数据集)或假的(生成的)。这两个模型在零和游戏中竞争,使得训练过程的收敛涉及在生成器生成令人信服的图像的技能与能够检测它们的鉴别器之间找到平衡。

生成器模型将来自潜在空间的随机点作为输入,通常为50到100个随机高斯变量。生成器通过训练对潜在空间中的点应用独特的含义,并将点映射到特定的输出合成图像。这意味着虽然潜在空间由生成器模型构成,但是无法控制生成的图像。

GAN公式使用简单的因子连续输入噪声向量z,而对发生器可以使用该噪声的方式没有限制。结果,发生器可能以高度纠缠的方式使用噪声,导致z的各个维度不对应于数据的语义特征。

可以探索潜在空间并比较生成的图像,以试图理解生成器模型已经学习的映射。或者,可以例如通过类标签来调节生成过程,以便可以按需创建特定类型的图像。这是条件生成对抗网络的基础,简称CGAN或cGAN。

另一种方法是提供控制变量作为发电机的输入,以及潜在空间中的点(噪声)。可以训练发生器以使用控制变量来影响所生成图像的特定属性。这是信息最大化生成对抗网络(简称InfoGAN)所采用的方法。

InfoGAN,生成对抗网络的信息理论扩展,能够以完全无监督的方式学习解缠结的表示。

在训练过程中由发生器学习的结构化映射有些随机。虽然生成器模型学习在潜在空间中空间分离生成图像的属性,但是没有控制。这些属性纠缠在一起。InfoGAN的动机是希望解开生成图像的属性。

例如,在面部的情况下,可以解开和控制生成面部的特性,例如面部的形状,头发颜色,发型等。

例如,对于面部的数据集,有用的解开的表示可以为以下属性中的每一个分配一组单独的维度:面部表情,眼睛颜色,发型,眼镜的存在或不存在,以及相应人的身份。

控制变量与噪声一起提供作为发电机的输入,并且通过互信息丢失功能训练模型。

…我们对生成对抗性网络目标进行了简单的修改,鼓励它学习可解释和有意义的表达。我们通过最大化GAN噪声变量的固定小子集与观测值之间的互信息来实现这一点,结果证明是相对简单的。

相互信息是指在给定另一个变量的情况下获得的关于一个变量的信息量。在这种情况下,我们感兴趣的是有关使用噪声和控制变量生成的图像的控制变量的信息。

在信息论中,X和Y之间的互信息I(X; Y)测量从随机变量Y的知识中学习的关于另一个随机变量X 的“ 信息量 ”。

相互信息(MI)被计算为图像的条件熵(由发生器(G)从噪声(z)和控制变量(c)创建),给定控制变量(c)从边际熵减去控制变量(c); 例如:

  • MI =熵(c) - 熵(c | G(z,c))

在实践中,计算真实的互信息通常是难以处理的,尽管本文采用了简化,称为变分信息最大化,并且控制代码的熵保持不变。

通过使用称为Q或辅助模型的新模型来实现通过互信息训练发电机。新模型与用于解释输入图像的鉴别器模型共享所有相同的权重,但与预测图像是真实还是假的鉴别器模型不同,辅助模型预测用于生成图像的控制代码。

两种模型都用于更新生成器模型,首先是为了提高生成愚弄鉴别器模型的图像的可能性,其次是改善用于生成图像的控制代码和辅助模型对控制代码的预测之间的互信息。

结果是生成器模型通过互信息丢失而正规化,使得控制代码捕获所生成图像的显着特性,并且反过来可以用于控制图像生成过程。

每当我们有兴趣学习从给定输入X到保留关于原始输入的信息的更高级别表示Y的参数化映射时,可以利用互信息。[…]表明,最大化互信息的任务基本上等同于训练自动编码器以最小化重建误差。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第3张图片

如何实现InfoGAN丢失功能

一旦熟悉模型的输入和输出,InfoGAN就可以相当直接地实现。

唯一的绊脚石可能是互信息丢失功能,特别是如果你没有像大多数开发人员那样强大的数学背景。

InfoGan使用两种主要类型的控制变量:分类和连续,连续变量可能具有不同的数据分布,这会影响相互损失的计算方式。可以基于变量类型计算所有控制变量的相互损失并将其相加,这是OpenAI为TensorFlow发布的官方InfoGAN实现中使用的方法。

在Keras中,将控制变量简化为分类和高斯或均匀连续变量可能更容易,并且对于每个控制变量类型在辅助模型上具有单独的输出。这样可以使用不同的损失函数,大大简化了实现。

有关本节中建议的更多背景信息,请参阅更多阅读部分中的文章和帖子。

分类控制变量

分类变量可用于控制所生成图像的类型或类别。

这被实现为一个热编码矢量。也就是说,如果类具有10个值,则控制代码将是一个类,例如6,并且输入到生成器模型的分类控制向量将是所有零值的10个元素向量,其中对于类6具有一个值,例如,[0,0,0,0,0,0,1,0,0]。

训练模型时,我们不需要选择分类控制变量; 相反,它们是随机生成的,例如,每个样本以均匀的概率选择每个样本。

…关于潜码c~Cat(K = 10,p = 0.1)的统一分类分布

在辅助模型中,分类变量的输出层也将是一个热编码矢量以匹配输入控制代码,并且使用softmax激活函数。

对于分类潜在代码ci,我们使用softmax非线性的自然选择来表示Q(ci | x)。

回想一下,互信息被计算为来自控制变量的条件熵和从提供给输入变量的控制变量的熵中减去的辅助模型的输出。我们可以直接实现这一点,但这不是必需的。

控制变量的熵是一个常数,并且是一个接近于零的非常小的数; 因此,我们可以从计算中删除它。条件熵可以直接计算为控制变量输入和辅助模型的输出之间的交叉熵。因此,可以使用分类交叉熵损失函数,就像我们对任何多类分类问题一样。

超参数lambda用于缩放互信息丢失函数并设置为1,因此可以忽略。

即使InfoGAN引入了额外的超参数λ,它也很容易调整,简单地设置为1就足以支持离散的潜码。

- InfoGAN:可解释的代表性信息学习最大化生成性对抗网,2016年。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第4张图片

连续控制变量

连续控制变量可用于控制图像的样式。

连续变量从均匀分布中采样,例如在-1和1之间,并作为输入提供给发电机模型。

…可以捕捉连续性变化的连续代码:c2,c3~Unif(-1,1)

- InfoGAN:可解释的代表性信息学习最大化生成性对抗网,2016年。

辅助模型可以用高斯分布实现连续控制变量的预测,其中输出层被配置为具有一个节点,平均值和一个用于高斯标准偏差的节点,例如每个连续控制需要两个输出变量。

对于连续潜在代码cj,根据什么是真正的后验P(cj | x),有更多选项。在我们的实验中,我们发现简单地将Q(cj | x)视为因式高斯是足够的。

输出均值的节点可以使用线性激活函数,而输出标准偏差的节点必须产生正值,因此可以使用诸如sigmoid的激活函数来创建0到1之间的值。

对于连续潜码,我们通过对角高斯分布对近似后验进行参数化,识别网络输出其均值和标准差,其中标准偏差通过网络输出的指数变换进行参数化以确保积极性。

必须将损失函数计算为高斯控制码的互信息,这意味着它们必须在计算损失之前从平均值和标准差重建。计算高斯分布变量的熵和条件熵可以直接实现,但不是必需的。相反,可以使用均方误差损失。

或者,可以将输出分布简化为每个控制变量的均匀分布,可以使用具有线性激活的辅助模型中的每个变量的单个输出节点,并且模型可以使用均方误差损失函数。

如何为MNIST开发InfoGAN

在本节中,我们将仔细研究生成器(g),鉴别器(d)和辅助模型(q)以及如何在Keras中实现它们。

我们将为MNIST数据集开发InfoGAN实现,如InfoGAN论文中所做的那样。

本文探讨了两个版本; 第一个仅使用分类控制代码,并允许模型将一个分类变量映射到大约一个数字(尽管没有按分类变量排序数字)。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第5张图片
本文还探讨了InfoGAN架构的一个版本,其中包含一个热编码分类变量(c1)和两个连续控制变量(c2和c3)。

发现第一个连续变量用于控制数字的旋转,第二个连续变量用于控制数字的粗细。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第6张图片
我们将重点关注使用具有10个值的分类控制变量的简单情况,并鼓励模型学习让该变量控制生成的数字。您可能希望通过更改分类控制变量的基数或添加连续控制变量来扩展此示例。

用于MNIST数据集训练的GAN模型的配置作为本文的附录提供,转载如下。我们将使用列出的配置作为开发我们自己的生成器(g),鉴别器(d)和辅助(q)模型的起点。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第7张图片
让我们从将生成器模型开发为深度卷积神经网络(例如DCGAN)开始。

该模型可以将噪声向量(z)和控制向量(c)作为单独的输入,并在将它们用作生成图像的基础之前将它们连接起来。或者,可以预先将矢量连接起来并提供给模型中的单个输入层。方法是等价的,在这种情况下我们将使用后者来保持模型简单。

下面的*define_generator()*函数定义生成器模型,并将输入向量的大小作为参数。

完全连接的层采用输入向量并产生足够数量的激活,以创建512个7×7特征映射,从中重新激活激活。然后,它们以1×1步幅通过正常卷积层,然后两个随后的上采样将卷积层转换为2×2步幅优先至14×14特征映射,然后转换为所需的1通道28×28特征映射输出,其中像素值为通过tanh激活函数的范围[-1,-1]。

良好的发生器配置启发式如下,包括随机高斯权重初始化,隐藏层中的ReLU激活以及批量归一化的使用。

# define the standalone generator model
def define_generator(gen_input_size):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image generator input
	in_lat = Input(shape=(gen_input_size,))
	# foundation for 7x7 image
	n_nodes = 512 * 7 * 7
	gen = Dense(n_nodes, kernel_initializer=init)(in_lat)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	gen = Reshape((7, 7, 512))(gen)
	# normal
	gen = Conv2D(128, (4,4), padding='same', kernel_initializer=init)(gen)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	# upsample to 14x14
	gen = Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	# upsample to 28x28
	gen = Conv2DTranspose(1, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen)
	# tanh output
	out_layer = Activation('tanh')(gen)
	# define model
	model = Model(in_lat, out_layer)
	return model

接下来,我们可以定义鉴别器和辅助模型。

根据普通GAN,鉴别器模型以独立方式训练在真实和伪造图像上。发电机和辅助模型都不直接配合; 相反,它们适合作为复合模型的一部分。

鉴别器和辅助模型共享相同的输入和特征提取层,但它们的输出层不同。因此,同时定义它们是有意义的。

同样,有许多方法可以实现这种架构,但是将鉴别器和辅助模型定义为单独的模型首先允许我们稍后通过功能API直接将它们组合成更大的GAN模型。

下面的*define_discriminator()*函数定义了鉴别器和辅助模型,并将分类变量的基数(例如数值,例如10)作为输入。输入图像的形状也被参数化为函数参数,并设置为MNIST图像大小的默认值。

特征提取层涉及两个下采样层,而不是池化层作为最佳实践。此外,遵循DCGAN模型的最佳实践,我们使用LeakyReLU激活和批量标准化。

鉴别器模型(d)具有单个输出节点,并通过S形激活函数预测输入图像的实际概率。该模型被编译,因为它将以独立的方式使用,通过具有最佳实践学习速率和动量的随机梯度下降的Adam版本来优化二元交叉熵函数。

辅助模型(q)对分类变量中的每个值具有一个节点输出,并使用softmax激活函数。如InfoGAN论文中所使用的那样,在特征提取层和输出层之间添加完全连接的层。该模型未编译,因为它不是独立使用或以独立方式使用。

# define the standalone discriminator model
def define_discriminator(n_cat, in_shape=(28,28,1)):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image input
	in_image = Input(shape=in_shape)
	# downsample to 14x14
	d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
	d = LeakyReLU(alpha=0.1)(d)
	# downsample to 7x7
	d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = LeakyReLU(alpha=0.1)(d)
	d = BatchNormalization()(d)
	# normal
	d = Conv2D(256, (4,4), padding='same', kernel_initializer=init)(d)
	d = LeakyReLU(alpha=0.1)(d)
	d = BatchNormalization()(d)
	# flatten feature maps
	d = Flatten()(d)
	# real/fake output
	out_classifier = Dense(1, activation='sigmoid')(d)
	# define d model
	d_model = Model(in_image, out_classifier)
	# compile d model
	d_model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5))
	# create q model layers
	q = Dense(128)(d)
	q = BatchNormalization()(q)
	q = LeakyReLU(alpha=0.1)(q)
	# q model output
	out_codes = Dense(n_cat, activation='softmax')(q)
	# define q model
	q_model = Model(in_image, out_codes)
	return d_model, q_model

接下来,我们可以定义复合GAN模型。

该模型使用所有子模型,并且是训练发电机模型权重的基础。

下面的*define_gan()*函数实现了这个并定义并返回模型,将三个子模型作为输入。

如上所述,鉴别器以独立方式训练,因此鉴别器的所有权重被设置为不可训练(仅在此上下文中)。生成器模型的输出连接到鉴别器模型的输入,并连接到辅助模型的输入。

这将创建一个新的复合模型,该模型将[noise + control]向量作为输入,然后通过生成器生成图像。然后,图像通过鉴别器模型以产生分类,并通过辅助模型产生控制变量的预测。

该模型有两个输出层,需要使用不同的损失函数进行训练。二进制交叉熵损失用于鉴别器输出,正如我们在编译独立使用的鉴别器时所做的那样,并且互信息丢失用于辅助模型,在这种情况下,辅助模型可以直接实现为分类交叉熵并实现期望的结果。

# define the combined discriminator, generator and q network model
def define_gan(g_model, d_model, q_model):
	# make weights in the discriminator (some shared with the q model) as not trainable
	d_model.trainable = False
	# connect g outputs to d inputs
	d_output = d_model(g_model.output)
	# connect g outputs to q inputs
	q_output = q_model(g_model.output)
	# define composite model
	model = Model(g_model.input, [d_output, q_output])
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss=['binary_crossentropy', 'categorical_crossentropy'], optimizer=opt)
	return model

为了使GAN模型架构更清晰,我们可以创建模型和复合模型图。

下面列出了完整的示例。

# create and plot the infogan model for mnist

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 BatchNormalization
from keras.layers import Activation
from keras.initializers import RandomNormal
from keras.utils.vis_utils import plot_model

# define the standalone discriminator model

def define_discriminator(n_cat, in_shape=(28,28,1)):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image input
	in_image = Input(shape=in_shape)
	# downsample to 14x14
	d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
	d = LeakyReLU(alpha=0.1)(d)
	# downsample to 7x7
	d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = LeakyReLU(alpha=0.1)(d)
	d = BatchNormalization()(d)
	# normal
	d = Conv2D(256, (4,4), padding='same', kernel_initializer=init)(d)
	d = LeakyReLU(alpha=0.1)(d)
	d = BatchNormalization()(d)
	# flatten feature maps
	d = Flatten()(d)
	# real/fake output
	out_classifier = Dense(1, activation='sigmoid')(d)
	# define d model
	d_model = Model(in_image, out_classifier)
	# compile d model
	d_model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5))
	# create q model layers
	q = Dense(128)(d)
	q = BatchNormalization()(q)
	q = LeakyReLU(alpha=0.1)(q)
	# q model output
	out_codes = Dense(n_cat, activation='softmax')(q)
	# define q model
	q_model = Model(in_image, out_codes)
	return d_model, q_model

# define the standalone generator model

def define_generator(gen_input_size):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image generator input
	in_lat = Input(shape=(gen_input_size,))
	# foundation for 7x7 image
	n_nodes = 512 * 7 * 7
	gen = Dense(n_nodes, kernel_initializer=init)(in_lat)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	gen = Reshape((7, 7, 512))(gen)
	# normal
	gen = Conv2D(128, (4,4), padding='same', kernel_initializer=init)(gen)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	# upsample to 14x14
	gen = Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	# upsample to 28x28
	gen = Conv2DTranspose(1, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen)
	# tanh output
	out_layer = Activation('tanh')(gen)
	# define model
	model = Model(in_lat, out_layer)
	return model

# define the combined discriminator, generator and q network model

def define_gan(g_model, d_model, q_model):
	# make weights in the discriminator (some shared with the q model) as not trainable
	d_model.trainable = False
	# connect g outputs to d inputs
	d_output = d_model(g_model.output)
	# connect g outputs to q inputs
	q_output = q_model(g_model.output)
	# define composite model
	model = Model(g_model.input, [d_output, q_output])
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss=['binary_crossentropy', 'categorical_crossentropy'], optimizer=opt)
	return model

# number of values for the categorical control code

n_cat = 10

# size of the latent space

latent_dim = 62

# create the discriminator

d_model, q_model = define_discriminator(n_cat)

# create the generator

gen_input_size = latent_dim + n_cat
g_model = define_generator(gen_input_size)

# create the gan

gan_model = define_gan(g_model, d_model, q_model)

# plot the model

plot_model(gan_model, to_file='gan_plot.png', show_shapes=True, show_layer_names=True)

运行该示例将创建所有三个模型,然后创建复合GAN模型并保存模型体系结构的图。

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

该图显示了生成器模型的所有细节以及鉴别器和辅助模型的压缩描述。重要的是,请注意鉴别器输出的形状作为预测图像是真实还是假的单个节点,以及辅助模型预测分类控制代码的10个节点。

回想一下,该复合模型将仅用于更新生成器和辅助模型的模型权重,并且鉴别器模型中的所有权重将保持不可约,即仅在更新独立鉴别器模型时更新。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第8张图片
接下来,我们将为发电机开发输入。

每个输入都是由噪声和控制代码组成的矢量。具体地,高斯随机数的矢量和一个热编码的随机选择的分类值。

下面的*generate_latent_points()*函数实现了这一点,将潜在空间的大小,分类值的数量以及要生成的样本数作为参数作为输入。该函数返回输入连接向量作为生成器模型的输入,以及独立控制代码。通过复合GAN模型更新发电机和辅助模型时,将需要独立控制代码,专门用于计算辅助模型的互信息损失。

# generate points in latent space as input for the generator

def generate_latent_points(latent_dim, n_cat, n_samples):
	# generate points in the latent space
	z_latent = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_latent = z_latent.reshape(n_samples, latent_dim)
	# generate categorical codes
	cat_codes = randint(0, n_cat, n_samples)
	# one hot encode
	cat_codes = to_categorical(cat_codes, num_classes=n_cat)
	# concatenate latent points and control codes
	z_input = hstack((z_latent, cat_codes))
	return [z_input, cat_codes]

接下来,我们可以生成真实和虚假的例子。

可以通过为灰度图像添加附加维度来加载MNIST数据集,将其转换为3D输入,并将所有像素值缩放到范围[-1,1]以匹配来自生成器模型的输出。这是在下面的*load_real_samples()*函数中实现的。

我们可以通过选择数据集的随机子集来检索训练鉴别器时所需的批量实际样本。这在下面的*generate_real_samples()*函数中实现,该函数返回图像和类标签1,以向鉴别器指示它们是真实图像。

鉴别器还需要使用来自*generate_latent_points()函数的向量作为输入,通过生成器生成批量伪造样本。下面的generate_fake_samples()*函数实现了这一点,返回生成的图像以及类标签0,以向鉴别器指示它们是伪图像。

# load 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
	print(X.shape)
	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 and labels
	X = dataset[ix]
	# generate class labels
	y = ones((n_samples, 1))
	return X, y

# use the generator to generate n fake examples, with class labels

def generate_fake_samples(generator, latent_dim, n_cat, n_samples):
	# generate points in latent space and control codes
	z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples)
	# predict outputs
	images = generator.predict(z_input)
	# create class labels
	y = zeros((n_samples, 1))
	return images, y

接下来,我们需要跟踪生成的图像的质量。

我们将定期使用生成器生成图像样本,并将生成器和复合模型保存到文件中。然后,我们可以在训练结束时查看生成的图像,以便选择最终的生成器模型并加载模型以开始使用它来生成图像。

下面的*summarize_performance()*函数实现了这一点,首先生成100个图像,将它们的像素值缩放回范围[0,1],并将它们保存为10×10平方的图像图。

生成器和复合GAN模型也保存到文件中,具有基于训练迭代次数的唯一文件名。

# generate samples and save as a plot and save the model

def summarize_performance(step, g_model, gan_model, latent_dim, n_cat, n_samples=100):
	# prepare fake examples
	X, _ = generate_fake_samples(g_model, latent_dim, n_cat, n_samples)
	# scale from [-1,1] to [0,1]
	X = (X + 1) / 2.0
	# plot images
	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(X[i, :, :, 0], cmap='gray_r')
	# save plot to file
	filename1 = 'generated_plot_%04d.png' % (step+1)
	pyplot.savefig(filename1)
	pyplot.close()
	# save the generator model
	filename2 = 'model_%04d.h5' % (step+1)
	g_model.save(filename2)
	# save the gan model
	filename3 = 'gan_model_%04d.h5' % (step+1)
	gan_model.save(filename3)
	print('>Saved: %s, %s, and %s' % (filename1, filename2, filename3))

最后,我们可以培训InfoGAN。

这在下面的*train()*函数中实现,该函数将定义的模型和配置作为参数并运行训练过程。

模型训练100个时期,每批使用64个样本。MNIST训练数据集中有60,000个图像,因此一个时期涉及60,000/64,或937批次或训练迭代。将其乘以时期数或100,意味着总共将有93,700次训练迭代次数。

每次训练迭代包括首先用半批真实样本和半批假样本更新鉴别器,以形成一批重量更新,或每次迭代64次。接下来,基于批量噪声和控制代码输入更新复合GAN模型。每次训练迭代都会报告真实和假图像上的鉴别器的丢失以及发生器和辅助模型的丢失。

# train the generator and discriminator

def train(g_model, d_model, gan_model, dataset, latent_dim, n_cat, n_epochs=100, n_batch=64):
	# calculate the number of batches per training epoch
	bat_per_epo = int(dataset.shape[0] / n_batch)
	# calculate the number of training iterations
	n_steps = bat_per_epo * n_epochs
	# calculate the size of half a batch of samples
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_steps):
		# get randomly selected 'real' samples
		X_real, y_real = generate_real_samples(dataset, half_batch)
		# update discriminator and q 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, n_cat, 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
		z_input, cat_codes = generate_latent_points(latent_dim, n_cat, n_batch)
		# create inverted labels for the fake samples
		y_gan = ones((n_batch, 1))
		# update the g via the d and q error
		_,g_1,g_2 = gan_model.train_on_batch(z_input, [y_gan, cat_codes])
		# summarize loss on this batch
		print('>%d, d[%.3f,%.3f], g[%.3f] q[%.3f]' % (i+1, d_loss1, d_loss2, g_1, g_2))
		# evaluate the model performance every 'epoch'
		if (i+1) % (bat_per_epo * 10) == 0:
			summarize_performance(i, g_model, gan_model, latent_dim, n_cat)

然后我们可以配置和创建模型,然后运行培训过程。

我们将使用10个值作为单个分类变量来匹配MNIST数据集中的10个已知类。我们将使用64维的潜在空间来匹配InfoGAN论文,这意味着,在这种情况下,生成器模型的每个输入向量将是64(随机高斯变量)+ 10(一个热编码控制变量)或72个元素长度。

# number of values for the categorical control code

n_cat = 10

# size of the latent space

latent_dim = 62

# create the discriminator

d_model, q_model = define_discriminator(n_cat)

# create the generator

gen_input_size = latent_dim + n_cat
g_model = define_generator(gen_input_size)

# create the gan

gan_model = define_gan(g_model, d_model, q_model)

# load image data

dataset = load_real_samples()

# train model

train(g_model, d_model, gan_model, dataset, latent_dim, n_cat)

将这一点结合在一起,下面列出了使用单个分类控制变量在MNIST数据集上训练InfoGAN模型的完整示例。

# example of training an infogan on mnist

from numpy import zeros
from numpy import ones
from numpy import expand_dims
from numpy import hstack
from numpy.random import randn
from numpy.random import randint
from keras.datasets.mnist import load_data
from keras.optimizers import Adam
from keras.initializers import RandomNormal
from keras.utils import to_categorical
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 BatchNormalization
from keras.layers import Activation
from matplotlib import pyplot

# define the standalone discriminator model

def define_discriminator(n_cat, in_shape=(28,28,1)):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image input
	in_image = Input(shape=in_shape)
	# downsample to 14x14
	d = Conv2D(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(in_image)
	d = LeakyReLU(alpha=0.1)(d)
	# downsample to 7x7
	d = Conv2D(128, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(d)
	d = LeakyReLU(alpha=0.1)(d)
	d = BatchNormalization()(d)
	# normal
	d = Conv2D(256, (4,4), padding='same', kernel_initializer=init)(d)
	d = LeakyReLU(alpha=0.1)(d)
	d = BatchNormalization()(d)
	# flatten feature maps
	d = Flatten()(d)
	# real/fake output
	out_classifier = Dense(1, activation='sigmoid')(d)
	# define d model
	d_model = Model(in_image, out_classifier)
	# compile d model
	d_model.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5))
	# create q model layers
	q = Dense(128)(d)
	q = BatchNormalization()(q)
	q = LeakyReLU(alpha=0.1)(q)
	# q model output
	out_codes = Dense(n_cat, activation='softmax')(q)
	# define q model
	q_model = Model(in_image, out_codes)
	return d_model, q_model

# define the standalone generator model

def define_generator(gen_input_size):
	# weight initialization
	init = RandomNormal(stddev=0.02)
	# image generator input
	in_lat = Input(shape=(gen_input_size,))
	# foundation for 7x7 image
	n_nodes = 512 * 7 * 7
	gen = Dense(n_nodes, kernel_initializer=init)(in_lat)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	gen = Reshape((7, 7, 512))(gen)
	# normal
	gen = Conv2D(128, (4,4), padding='same', kernel_initializer=init)(gen)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	# upsample to 14x14
	gen = Conv2DTranspose(64, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen)
	gen = Activation('relu')(gen)
	gen = BatchNormalization()(gen)
	# upsample to 28x28
	gen = Conv2DTranspose(1, (4,4), strides=(2,2), padding='same', kernel_initializer=init)(gen)
	# tanh output
	out_layer = Activation('tanh')(gen)
	# define model
	model = Model(in_lat, out_layer)
	return model

# define the combined discriminator, generator and q network model

def define_gan(g_model, d_model, q_model):
	# make weights in the discriminator (some shared with the q model) as not trainable
	d_model.trainable = False
	# connect g outputs to d inputs
	d_output = d_model(g_model.output)
	# connect g outputs to q inputs
	q_output = q_model(g_model.output)
	# define composite model
	model = Model(g_model.input, [d_output, q_output])
	# compile model
	opt = Adam(lr=0.0002, beta_1=0.5)
	model.compile(loss=['binary_crossentropy', 'categorical_crossentropy'], optimizer=opt)
	return model

# load 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
	print(X.shape)
	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 and labels
	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_cat, n_samples):
	# generate points in the latent space
	z_latent = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_latent = z_latent.reshape(n_samples, latent_dim)
	# generate categorical codes
	cat_codes = randint(0, n_cat, n_samples)
	# one hot encode
	cat_codes = to_categorical(cat_codes, num_classes=n_cat)
	# concatenate latent points and control codes
	z_input = hstack((z_latent, cat_codes))
	return [z_input, cat_codes]

# use the generator to generate n fake examples, with class labels

def generate_fake_samples(generator, latent_dim, n_cat, n_samples):
	# generate points in latent space and control codes
	z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples)
	# predict outputs
	images = generator.predict(z_input)
	# create class labels
	y = zeros((n_samples, 1))
	return images, y

# generate samples and save as a plot and save the model

def summarize_performance(step, g_model, gan_model, latent_dim, n_cat, n_samples=100):
	# prepare fake examples
	X, _ = generate_fake_samples(g_model, latent_dim, n_cat, n_samples)
	# scale from [-1,1] to [0,1]
	X = (X + 1) / 2.0
	# plot images
	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(X[i, :, :, 0], cmap='gray_r')
	# save plot to file
	filename1 = 'generated_plot_%04d.png' % (step+1)
	pyplot.savefig(filename1)
	pyplot.close()
	# save the generator model
	filename2 = 'model_%04d.h5' % (step+1)
	g_model.save(filename2)
	# save the gan model
	filename3 = 'gan_model_%04d.h5' % (step+1)
	gan_model.save(filename3)
	print('>Saved: %s, %s, and %s' % (filename1, filename2, filename3))

# train the generator and discriminator

def train(g_model, d_model, gan_model, dataset, latent_dim, n_cat, n_epochs=100, n_batch=64):
	# calculate the number of batches per training epoch
	bat_per_epo = int(dataset.shape[0] / n_batch)
	# calculate the number of training iterations
	n_steps = bat_per_epo * n_epochs
	# calculate the size of half a batch of samples
	half_batch = int(n_batch / 2)
	# manually enumerate epochs
	for i in range(n_steps):
		# get randomly selected 'real' samples
		X_real, y_real = generate_real_samples(dataset, half_batch)
		# update discriminator and q 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, n_cat, 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
		z_input, cat_codes = generate_latent_points(latent_dim, n_cat, n_batch)
		# create inverted labels for the fake samples
		y_gan = ones((n_batch, 1))
		# update the g via the d and q error
		_,g_1,g_2 = gan_model.train_on_batch(z_input, [y_gan, cat_codes])
		# summarize loss on this batch
		print('>%d, d[%.3f,%.3f], g[%.3f] q[%.3f]' % (i+1, d_loss1, d_loss2, g_1, g_2))
		# evaluate the model performance every 'epoch'
		if (i+1) % (bat_per_epo * 10) == 0:
			summarize_performance(i, g_model, gan_model, latent_dim, n_cat)

# number of values for the categorical control code

n_cat = 10

# size of the latent space

latent_dim = 62

# create the discriminator

d_model, q_model = define_discriminator(n_cat)

# create the generator

gen_input_size = latent_dim + n_cat
g_model = define_generator(gen_input_size)

# create the gan

gan_model = define_gan(g_model, d_model, q_model)

# load image data

dataset = load_real_samples()

# train model

train(g_model, d_model, gan_model, dataset, latent_dim, n_cat)

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

注意:鉴于训练算法的随机性,您的结果可能会有所不同。尝试运行几次示例。

每次训练迭代都会报告模型中的损失。如果鉴别器的损失保持在0.0或长时间变为0.0,这可能是训练失败的迹象,您可能想要重新开始训练过程。鉴别器损失可能从0.0开始,但可能会增加,就像在这种特定情况下一样。

辅助模型的损失可能会归零,因为它可以完美地预测分类变量。发电机和鉴别器模型的损失最终可能会在1.0左右徘徊,以展示稳定的训练过程或两种模型训练之间的平衡。

1, d[0.924,0.758], g[0.448] q[2.909]
2, d[0.000,2.699], g[0.547] q[2.704]
3, d[0.000,1.557], g[1.175] q[2.820]
4, d[0.000,0.941], g[1.466] q[2.813]
5, d[0.000,1.013], g[1.908] q[2.715]
...
93696, d[0.814,1.212], g[1.283] q[0.000]
93697, d[1.063,0.920], g[1.132] q[0.000]
93698, d[0.999,1.188], g[1.128] q[0.000]
93699, d[0.935,0.985], g[1.229] q[0.000]
93700, d[0.968,1.016], g[1.200] q[0.001]
Saved: generated_plot_93700.png, model_93700.h5, and gan_model_93700.h5

每10个时期或每9,370次训练迭代中保存图和模型。

回顾这些图表应该显示早期时代的低质量图像以及后期时代的改进和稳定质量的图像。

例如,在前10个时期之后保存的图像的图表低于显示低质量的生成图像。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第9张图片
更多时代并不意味着更好的质量,这意味着最佳质量的图像可能不是来自训练结束时保存的最终模型的图像。

查看图表并选择具有最佳图像质量的最终模型。在这种情况下,我们将使用在100个纪元或93,700次训练迭代后保存的模型。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第10张图片

如何使用训练有素的InfoGAN模型使用控制代码

现在我们已经培训了InfoGAN模型,我们可以探索如何使用它。

首先,我们可以加载模型并使用它来生成随机图像,就像我们在训练期间所做的那样。

下面列出了完整的示例。

更改模型文件名以匹配在训练期间生成最佳图像的模型文件名。

# example of loading the generator model and generating images

from math import sqrt
from numpy import hstack
from numpy.random import randn
from numpy.random import randint
from keras.models import load_model
from keras.utils import to_categorical
from matplotlib import pyplot

# generate points in latent space as input for the generator

def generate_latent_points(latent_dim, n_cat, n_samples):
	# generate points in the latent space
	z_latent = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_latent = z_latent.reshape(n_samples, latent_dim)
	# generate categorical codes
	cat_codes = randint(0, n_cat, n_samples)
	# one hot encode
	cat_codes = to_categorical(cat_codes, num_classes=n_cat)
	# concatenate latent points and control codes
	z_input = hstack((z_latent, cat_codes))
	return [z_input, cat_codes]

# create a plot of generated images

def create_plot(examples, n_examples):
	# plot images
	for i in range(n_examples):
		# define subplot
		pyplot.subplot(sqrt(n_examples), sqrt(n_examples), 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('model_93700.h5')

# number of values for the categorical control code

n_cat = 10

# size of the latent space

latent_dim = 62

# number of examples to generate

n_samples = 100

# generate points in latent space and control codes

z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples)

# predict outputs

X = model.predict(z_input)

# scale from [-1,1] to [0,1]

X = (X + 1) / 2.0

# plot the result

create_plot(X, n_samples)

运行该示例将加载已保存的生成器模型并使用它生成100个随机图像并将图像绘制在10×10网格上。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第11张图片
接下来,我们可以更新示例以测试控制变量给我们的控制程度。

我们可以更新*generate_latent_points()*函数,以获取[0,9]中分类值的参数,对其进行编码,并将其与噪声向量一起用作输入。

# generate points in latent space as input for the generator

def generate_latent_points(latent_dim, n_cat, n_samples, digit):
	# generate points in the latent space
	z_latent = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_latent = z_latent.reshape(n_samples, latent_dim)
	# define categorical codes
	cat_codes = asarray([digit for _ in range(n_samples)])
	# one hot encode
	cat_codes = to_categorical(cat_codes, num_classes=n_cat)
	# concatenate latent points and control codes
	z_input = hstack((z_latent, cat_codes))
	return [z_input, cat_codes]

我们可以通过生成具有分类值1的25个图像的网格来测试这一点。

下面列出了完整的示例。

# example of testing different values of the categorical control variable

from math import sqrt
from numpy import asarray
from numpy import hstack
from numpy.random import randn
from numpy.random import randint
from keras.models import load_model
from keras.utils import to_categorical
from matplotlib import pyplot

# generate points in latent space as input for the generator

def generate_latent_points(latent_dim, n_cat, n_samples, digit):
	# generate points in the latent space
	z_latent = randn(latent_dim * n_samples)
	# reshape into a batch of inputs for the network
	z_latent = z_latent.reshape(n_samples, latent_dim)
	# define categorical codes
	cat_codes = asarray([digit for _ in range(n_samples)])
	# one hot encode
	cat_codes = to_categorical(cat_codes, num_classes=n_cat)
	# concatenate latent points and control codes
	z_input = hstack((z_latent, cat_codes))
	return [z_input, cat_codes]

# create and save a plot of generated images

def save_plot(examples, n_examples):
	# plot images
	for i in range(n_examples):
		# define subplot
		pyplot.subplot(sqrt(n_examples), sqrt(n_examples), 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('model_93700.h5')

# number of categorical control codes

n_cat = 10

# size of the latent space

latent_dim = 62

# number of examples to generate

n_samples = 25

# define digit

digit = 1

# generate points in latent space and control codes

z_input, _ = generate_latent_points(latent_dim, n_cat, n_samples, digit)

# predict outputs

X = model.predict(z_input)

# scale from [-1,1] to [0,1]

X = (X + 1) / 2.0

# plot the result

save_plot(X, n_samples)

结果是生成25个生成图像的网格,其中分类代码设置为值1。

注意:鉴于训练算法的随机性,您的结果可能会有所不同。

期望控制代码的值影响生成的图像; 特别是,他们预计会影响数字类型。但是,预计它们不会被订购,例如,控制代码1,2和3来创建这些数字。

然而,在这种情况下,值为1的控制代码导致生成的图像看起来像1。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第12张图片
尝试使用不同的数字并查看值对图像的确切控制。

例如,在这种情况下将值设置为5(数字= 5)会导致生成的图像看起来像数字“ 8 ”。
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?_第13张图片

你可能感兴趣的:(GAN,生成对抗网络,编程语言)