黄涛 作者

InfoGAN:一种无监督生成方法 | 经典论文复现

作者丨黄涛 学校丨中山大学数学学院18级本科生 研究方向丨图像识别、VQA、生成模型和自编码器

论文复现代码: http://aistudio.baidu.com/#/projectdetail/23600

GAN

生成对抗网络(Generative Adversarial Nets)是一类新兴的生成模型,由两部分组成:一部分是判别模型(discriminator)D(·),用来判别输入数据是真实数据还是生成出来的数据;另一部分是是生成模型(generator)G(·),由输入的噪声生成目标数据。GAN 的优化问题可以表示为:

其中 Pdata 是生成样本,noise 是随机噪声。而对于带标签的数据,通常用潜码(latent code)c 来表示这一标签,作为生成模型的一个输入,这样我们有:

然而当我们遇到存在潜在的类别差别而没有标签数据,要使 GAN 能够在这类数据上拥有更好表现,我们就需要一类能够无监督地辨别出这类潜在标签的数据InfoGAN 就给出了一个较好的解决方案。

互信息(Mutual Information)

互信息是两个随机变量依赖程度的量度,可以表示为:

要去直接优化 I(c;G(z,c)) 是极其困难的,因为这意味着我们要能够计算后验概率(posterior probability)P(c|x),但是我们可以用一个辅助分布(auxiliary distribution)Q(c|x),来近似这一后验概率。这样我们能够给出互信息的一个下界(lower bounding):

InfoGAN

InfoGAN 中,为了能够增加潜码和生成数据间的依赖程度,我们可以增大潜码和生成数据间的互信息,使生成数据变得与潜码更相关:

▲ 图1. InfoGAN的整体结构图

由上面的,对于一个极大化互信息的问题转化为一个极大化互信息下界的问题,我们接下来就可以定义:

在论文的附录中,作者证明了:

于是:

故 LI (G, Q) 是互信息的一个下界。作者指出,用蒙特卡罗模拟(Monte Carlo simulation)去逼近 LI (G, Q) 是较为方便的,这样我们的优化问题就可以表示为:

实现

在实现中,D(x)、G(z, c) 和 Q(x) 分别用一个 CNN (Convolutional Neural Networks)、CNN、DCNN (DeConv Neural Networks) 来实现。同时,潜码 c 也包含两部分:一部分是类别,服从 Cat(K = N,p = 1/N),其中 N 为类别数量;另一部分是连续的与生成数据有关的参数,服从 Unif(−1,1) 的分布。 

在此应指出,Q(c|x) 可以表示为一个神经网络 Q(x) 的输出。对于输入随机变量 z 和类别潜码 c,实际的 LI(G, Q) 可以表示为:

其中 · 表示内积(inner product),c 是一个选择计算哪个 log 的参数,例如 ci = 1 而 cj = 0(∀j = 1,2,···,i − 1,i + 1,···,n),那么 z 这时候计算出的 LI(G,Q) 就等于 log(Q(z,c)i)。这里我们可以消去 H(c),因为 c 的分布是固定的,即优化目标与 H(c) 无关:

而对于参数潜码,我们假设它符合正态分布,神经网络 Q(x) 则输出其预测出的该潜码的均值和标准差, 我们知道,对于均值 μ,标准差 σ 的随机变量,其概率密度函数为:

要计算参数潜码 c 的,就是要计算 log p(c),即:

设 Q(x) 输出的参数潜码 c 的均值 μ,标准差 σ 分别为 Q(x)μ 和 Q(x)σ,那么对于参数潜码 c:

同样的,我们可以消去 H(c),因为 c 的分布是固定的,那么:

实验

首先,通过和普通的 GAN 比较 LI ,作者证明了 InfoGAN 确实能够优化这一互信息的下界 2。 

作者在 MNIST 手写数字数据集(3)、3D 面部数据集(4)、3D 椅子数据集(5)、SVHN 街景房号数据集(6)以及 CelebA 人脸数据集(7)上进行了模型的相关测试。 

▲ 图2. MNIST手写字符数据集上的结果

▲ 图3. 3D面部数据集上的结果

▲ 图4. 3D椅子数据集上的结果

▲ 图5. SVHN街景房号数据集上的结果

▲ 图6. CelebA人脸数据集上的结果

作者展示了这些数据集上学习到的类别潜码(从上至下变化)和参数潜码(从左至右变化,由 -2 到 2),我们可以看出,InfoGAN 不仅能够很好地学习数据之间的类型差别,也能够很好地学习到数据本身的一些易于区分的特点,而且生成模型对这些特点的泛化能力还是很好的。

再论InfoGANLI

读完论文,我们发现,对于类别潜码,这个 LI 本质上是 x 与 G(z, c) 之间的 KL 散度:

也就是说:

而 min DKL(c||Q(G(z, c))) 意味着减小 c 与 Q(G(z, c)) 的差别。

▲ 图7. 普通GAN和InfoGAN的LI在训练过程中的比较如果我们不考虑 Q(x)σ 的影响,LI 的优化过程:

也意味着减小 c 与 Q(G(z, c))μ 的差。

再纵观整个模型,我们会发现这一对 LI 优化的过程,实质上是以 G 为编码器(Encoder), Q 为解码器(Decoder),生成的图像作为我们要编码的码(code),训练一个自编码器(Autoencoder),也就是说,作者口中的信息论优化问题,本质上是无监督训练问题。

关于PaddlePaddle

在 PaddlePaddle 中,一个极为重要的概念即是 fluid.Program(),在官方文档里常见的 exe.run(program= fluid.default_startup_program())的 fluid.default_startup_program() 就是其中一个例子。

在这一使用中可以了解到,我们要用 exe.run() 中的 program 参数运行指定的 fluid.Program(),而官方文档指出,当该参数未指定时,会运行 fluid.default_main_program(),而 fluid.default_main_program() 代表的是未指定 fluid.Program() 的所有操作

注意,这里说的是“所有”,由于 PaddlePaddle 没有计算依赖检测机制,即使在计算 fetch_list 中的值的时候不会用到操作也会被计算,这一点与 TensorFlow 极其不同,作者本人在使用过程中踩了很大的坑,还望各位注意。在执行多种任务的时候不要一股脑全部写在 fluid.default_main_program() 之中, 这样极其浪费资源,也容易造成一些问题。

一个新的 fluid.Program() 被创建之后,可以在 fluid.program_guard() 中指定该 fluid.Program() 中的操作与变量:

#创建Infer_program Infer_program = fluid.Program() #在这里面定义Infer_program中的操作与变量 with fluid.program_guard(main_program = Infer_program):     #从外部通过feed传入的变量,一般是输入、标签等     X = fluid.layers.data(name='X', shape=[X_dim], dtype='float32')     #全链接层     output = fluid.layers.fc(input = X, size = 128) 

PaddlePaddle 中还需要注意的一点是,fluid.Variable 的命名空间是全局的,也就是说在同一或者不同 fluid. Program() 间,同名(fluid.Variable 的 name 属性相同)的 fluid.Variable 所指向的变量是相同的,所以同一名称在同一或者不同 fluid.Program () 中可以被使用多次,而不用担心 TensorFlow 中会出现的 reuse 问题。 

要对一个操作的中的权值的名称进行定义(权值命名为 W1,偏置命名为 b1):

output = fluid.layers.fc(input = X,                           size = 10,                           param_attr = fluid.ParamAttr(name="W1"),                           bias_attr = fluid.ParamAttr(name="b1"))

要在之后使用这些 fluid.Variable,例如在 Optimizer 中使用:

#可以直接用名称指代对应的fluid.Variable parameter_list = ["W1", "b1"] #构建optimizer optimizer = fluid.optimizer.AdamOptimizer() #指定optimizer优化的目标和对象 optimizer.minimize(loss, parameter_list=parameter_list)

在构建完基本的运算操作后,便可以开始初始化操作了:

#初始化fluid.Executor(指定执行程序位置) exe = fluid.Executor(fluid.CPUPlace()) #执行fluid.default_startup_program(),在fluid.program_guard()中 #若没有指定初始化program,则默认为此program exe.run(program=fluid.default_startup_program())

初始化完成后,可以开始训练啦:

#在从外部传入数据的时候要注意,传入数据的数据类型必须与fluid.layers.data #中定义的类型一致,否则会报错 #如果传入数据是list类型,建议转换为np.array,否则可能回报错: #fedding的数据中包含lod信息,请您转换成lodtensor #(渣翻译, 原因是list被默认为含有变长数据) feeding = {"X" : np.array(Z_noise).astype('float32')} #传入feeding中的数据,执行program程序,从计算结果中获取loss #(默认会被转换成np.array,可在函数参数中设置) loss_curr = exe.run(feed = feeding, program = program, fetch_list = [loss])

GAN实现

生成对抗网络(Generative Adversarial Nets)是一类新兴的生成模型,由两部分组成:一部分是判别模型(discriminator)D(·),用来判别输入数据是真实数据还是生成出来的数据;另一部分是是生成模型(generator)G(·),由输入的噪声生成目标数据。GAN 的优化问题可以表示为:

其中 Pdata 是生成样本,noise 是随机噪声。我们用一个双层的 MLP 来演示:

#判别模型 def discriminator(x):     #使用fluid.unique_name.guard()添加模型内参数名称的前缀     with fluid.unique_name.guard('D_'):         D_h1 = fluid.layers.fc(input = x, size = 256, act = "relu")         D_logit = fluid.layers.fc(input = D_h1, size = 1, act = "sigmoid")     return D_logit #生成模型 def generator(inputs):     with fluid.unique_name.guard('G_'):         D_h1 = fluid.layers.fc(input = inputs, size = 256, act = "relu")         D_logit = fluid.layers.fc(input = D_h1, size = 784, act = "sigmoid")     return D_logit

通常,一个 GAN 的训练由两部分组成,第一部分是对 D(·) 进行训练,极大化目标函数

第二部分是对 G(·) 进行训练,极小化目标函数

以下是两部分优化的定义:

#参考Todd的LSGAN的实现,使用函数获取模型所有变量 def get_params(program, prefix):     all_params = program.global_block().all_parameters()     return [t.name for t in all_params if t.name.startswith(prefix)] #G优化程序 G_program = fluid.Program() with fluid.program_guard(main_program = G_program):     #定义输入数据     Z = fluid.layers.data(name='Z', shape=[Z_dim], dtype='float32')     #执行相关模型的计算     G_sample = generator(Z)     D_fake = discriminator(G_sample)      #计算损失函数     G_loss = 0.0 - fluid.layers.reduce_mean(fluid.layers.log(D_fake + 1e-8))     #定义optimizer优化的变量的范围     theta_G = get_params(G_program, "G")     G_optimizer = fluid.optimizer.AdamOptimizer()     G_optimizer.minimize(G_loss, parameter_list=theta_G) #D优化程序 D_program = fluid.Program() with fluid.program_guard(main_program = D_program):     Z = fluid.layers.data(name='Z', shape=[Z_dim], dtype='float32')     X = fluid.layers.data(name='X', shape=[784], dtype='float32')     #在使用数据集时,要注意相应接口传入数据的值的范围     #paddle.dataset.mnist中的数据,范围在[-1, 1]     #要将其转换到sigmoid函数的值域内     X = X * 0.5 + 0.5     G_sample = generator(Z)     D_real = discriminator(X)     D_fake = discriminator(G_sample)       D_loss = 0.0 - fluid.layers.reduce_mean(fluid.layers.log(D_real + 1e-8)      + fluid.layers.log(1.0 - D_fake + 1e-8))     theta_D = get_params(G_program, "D")     D_optimizer = fluid.optimizer.AdamOptimizer()     D_optimizer.minimize(D_loss, parameter_list=theta_D)

在定义好这些之后,是时候开训练了:

#定义传入的数据 feeding_withx= {"X" : np.array(X_mb).astype('float32'),      "Z" : np.array(Z_noise).astype('float32')} feeding = {"Z" : np.array(Z_noise).astype('float32')} #执行训练操作并获取当前损失函数的值 D_loss_curr = exe.run(feed = feeding_withx, program = D_program,                        fetch_list = [D_loss]) G_loss_curr = exe.run(feed = feeding, program = G_program,                        fetch_list = [G_loss])

若欲测试模型效果,可再定义一个 Inference:

#Inference Infer_program = fluid.Program() with fluid.program_guard(main_program = Infer_program):        Z = fluid.layers.data(name='Z', shape=[Z_dim], dtype='float32')     G_sample = generator(Z)

然后再这样获取 samples:

feeding = {"Z" : np.array(Z_noise).astype('float32')} samples = exe.run(feed = feeding, program = Infer_program,                    fetch_list = [G_sample])

后记

本文先前于今年 8 月完成,共享于 PaddlePaddle 论文复现群内,在 10 月 LSGAN 的复现公开之 后,参考该复现更改了模型参数命名和参数列表的实现方法,在此感谢 Todd 同学的复现对本文的帮助。

PaperWeekly
PaperWeekly

推荐、解读、讨论和报道人工智能前沿论文成果的学术平台。

工程神经网络MNIST生成模型生成对抗网络无监督GAN
5
相关数据
自动编码器技术

自动编码器是用于无监督学习高效编码的人工神经网络。 自动编码器的目的是学习一组数据的表示(编码),通常用于降维。 最近,自动编码器已经越来越广泛地用于生成模型的训练。

判别模型技术

在机器学习领域,有一种分类方法将模型分为判别模型和生成模型(generative model)两种。 判别模型是一种对未知数据y与已知数据x之间关系进行建模的方法,是一种基于概率理论的方法。已知输入变量x,判别模型通过构建条件概率P(y|x)分布预测结果,或试图直接从输入x的空间学习映射到标签{0,1}(如感知器算法)的函数。生成模型则是考虑x与y之间的联合分布。 在实际应用中判别模型非常常见,如:逻辑回归(logistic regression),支持向量机(support vector machine), 提升方法(Boosting),条件随机场(conditional random fields),神经网络(neural network),随机森林(random forests)典型的生成模型则包括:高斯混合模型(Gaussian Mixture Model),隐马尔科夫模型(hidden markov model),简单贝叶斯(naive Bayes)等。不难看出两者的区别。

参数技术

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

损失函数技术

在数学优化,统计学,计量经济学,决策理论,机器学习和计算神经科学等领域,损失函数或成本函数是将一或多个变量的一个事件或值映射为可以直观地表示某种与之相关“成本”的实数的函数。

TensorFlow技术

TensorFlow是一个开源软件库,用于各种感知和语言理解任务的机器学习。目前被50个团队用于研究和生产许多Google商业产品,如语音识别、Gmail、Google 相册和搜索,其中许多产品曾使用过其前任软件DistBelief。

张量技术

张量是一个可用来表示在一些矢量、标量和其他张量之间的线性关系的多线性函数,这些线性关系的基本例子有内积、外积、线性映射以及笛卡儿积。其坐标在 维空间内,有 个分量的一种量,其中每个分量都是坐标的函数,而在坐标变换时,这些分量也依照某些规则作线性变换。称为该张量的秩或阶(与矩阵的秩和阶均无关系)。 在数学里,张量是一种几何实体,或者说广义上的“数量”。张量概念包括标量、矢量和线性算子。张量可以用坐标系统来表达,记作标量的数组,但它是定义为“不依赖于参照系的选择的”。张量在物理和工程学中很重要。例如在扩散张量成像中,表达器官对于水的在各个方向的微分透性的张量可以用来产生大脑的扫描图。工程上最重要的例子可能就是应力张量和应变张量了,它们都是二阶张量,对于一般线性材料他们之间的关系由一个四阶弹性张量来决定。

后验概率技术

在贝叶斯统计中,一个随机事件或者一个不确定事件的后验概率是在考虑和给出相关证据或数据后所得到的条件概率。同样,后验概率分布是一个未知量(视为随机变量)基于试验和调查后得到的概率分布。“后验”在本文中代表考虑了被测试事件的相关证据。

神经网络技术

(人工)神经网络是一种起源于 20 世纪 50 年代的监督式机器学习模型,那时候研究者构想了「感知器(perceptron)」的想法。这一领域的研究者通常被称为「联结主义者(Connectionist)」,因为这种模型模拟了人脑的功能。神经网络模型通常是通过反向传播算法应用梯度下降训练的。目前神经网络有两大主要类型,它们都是前馈神经网络:卷积神经网络(CNN)和循环神经网络(RNN),其中 RNN 又包含长短期记忆(LSTM)、门控循环单元(GRU)等等。深度学习是一种主要应用于神经网络帮助其取得更好结果的技术。尽管神经网络主要用于监督学习,但也有一些为无监督学习设计的变体,比如自动编码器和生成对抗网络(GAN)。

目标函数技术

目标函数f(x)就是用设计变量来表示的所追求的目标形式,所以目标函数就是设计变量的函数,是一个标量。从工程意义讲,目标函数是系统的性能标准,比如,一个结构的最轻重量、最低造价、最合理形式;一件产品的最短生产时间、最小能量消耗;一个实验的最佳配方等等,建立目标函数的过程就是寻找设计变量与目标的关系的过程,目标函数和设计变量的关系可用曲线、曲面或超曲面表示。

生成模型技术

在概率统计理论中, 生成模型是指能够随机生成观测数据的模型,尤其是在给定某些隐含参数的条件下。 它给观测值和标注数据序列指定一个联合概率分布。 在机器学习中,生成模型可以用来直接对数据建模(例如根据某个变量的概率密度函数进行数据采样),也可以用来建立变量间的条件概率分布。

生成对抗网络技术

生成对抗网络是一种无监督学习方法,是一种通过用对抗网络来训练生成模型的架构。它由两个网络组成:用来拟合数据分布的生成网络G,和用来判断输入是否“真实”的判别网络D。在训练过程中,生成网络-G通过接受一个随机的噪声来尽量模仿训练集中的真实图片去“欺骗”D,而D则尽可能的分辨真实数据和生成网络的输出,从而形成两个网络的博弈过程。理想的情况下,博弈的结果会得到一个可以“以假乱真”的生成模型。

信息论技术

信息论是在信息可以量度的基础上,研究有效地和可靠地传递信息的科学,它涉及信息量度、信息特性、信息传输速率、信道容量、干扰对信息传输的影响等方面的知识。通常把上述范围的信息论称为狭义的信息论,又因为它的创始人是香农,故又称为香农信息论。

信息生成对抗网络技术

GAN的一个变种

优化器技术

优化器基类提供了计算梯度loss的方法,并可以将梯度应用于变量。优化器里包含了实现了经典的优化算法,如梯度下降和Adagrad。 优化器是提供了一个可以使用各种优化算法的接口,可以让用户直接调用一些经典的优化算法,如梯度下降法等等。优化器(optimizers)类的基类。这个类定义了在训练模型的时候添加一个操作的API。用户基本上不会直接使用这个类,但是你会用到他的子类比如GradientDescentOptimizer, AdagradOptimizer, MomentumOptimizer(tensorflow下的优化器包)等等这些算法。

推荐文章
暂无评论
暂无评论~