登录
首页精彩阅读如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?
如何在Keras中开发最大化生成对抗网络(InfoGAN)的信息?
2019-11-13
收藏
如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

作者 | CDA数据分析师

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

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

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

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

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

让我们开始吧。


教程概述

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

  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的参数化映射时,可以利用互信息。[...]表明,最大化互信息的任务基本上等同于训练自动编码器以最小化重建误差。

如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

如何实现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就足以支持离散的潜码。

如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

连续控制变量

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

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

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

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

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

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

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

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

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

如何为MNIST开发InfoGAN

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

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

本文探讨了两个版本; 第一个仅使用分类控制代码,并允许模型将一个分类变量映射到大约一个数字(尽管没有按分类变量排序数字)。

如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

本文还探讨了InfoGAN架构的一个版本,其中包含一个热编码分类变量(c1)和两个连续控制变量(c2和c3)。

发现第一个连续变量用于控制数字的旋转,第二个连续变量用于控制数字的粗细。

如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

我们将重点关注使用具有10个值的分类控制变量的简单情况,并鼓励模型学习让该变量控制生成的数字。您可能希望通过更改分类控制变量的基数或添加连续控制变量来扩展此示例。

用于MNIST数据集训练的GAN模型的配置作为本文的附录提供,转载如下。我们将使用列出的配置作为开发我们自己的生成器(g),鉴别器(d)和辅助(q)模型的起点。

如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

让我们从将生成器模型开发为深度卷积神经网络(例如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个节点。

回想一下,该复合模型将仅用于更新生成器和辅助模型的模型权重,并且鉴别器模型中的所有权重将保持不可约,即仅在更新独立鉴别器模型时更新。

如何在<a href='/map/Keras/' style='color:#000;font-size:inherit;'>Keras</a>中开发最大化生成对抗网络(InfoGAN)的信息?

接下来,我们将为发电机开发输入。

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

下面的generatelatentpoints()函数实现了这一点,将潜在空间的大小,分类值的数量以及要生成的样本数作为参数作为输入。该函数返回输入连接向量作为生成器模型的输入,以及独立控制代码。通过复合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]以匹配来自生成器模型的输出。这是在下面的loadreal

数据分析咨询请扫描二维码

客服在线
立即咨询