过去几年发表于各大 AI 顶会论文提出的 400 多种算法中,公开算法代码的仅占 6%,其中三分之一的论文作者分享了测试数据,约 54% 的分享包含“伪代码”。这是今年 AAAI 会议上一个严峻的报告。人工智能这个蓬勃发展的领域正面临着实验重现的危机,就像实验重现问题过去十年来一直困扰着心理学、医学以及其他领域一样。最根本的问题是研究人员通常不共享他们的源代码。

可验证的知识是科学的基础,它事关理解。随着人工智能领域的发展,打破不可复现性将是必要的。为此,PaperWeekly 联手百度 PaddlePaddle 共同发起了本次论文有奖复现,我们希望和来自学界、工业界的研究者一起接力,为 AI 行业带来良性循环。

作者丨黄涛

学校丨中山大学数学学院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 不仅能够很好地学习数据之间的类型差别,也能够很好地学习到数据本身的一些易于区分的特点,而且生成模型对这些特点的泛化能力还是很好的。

再论InfoGAN的LI

读完论文,我们发现,对于类别潜码,这个 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 鼓励高校实验室或个人,在我们的平台上分享各类优质内容,可以是最新论文解读,也可以是学习心得技术干货。我们的目的只有一个,让知识真正流动起来。

来稿标准:

• 稿件确系个人原创作品,来稿需注明作者个人信息(姓名+学校/工作单位+学历/职位+研究方向)

• 如果文章并非首发,请在投稿时提醒并附上所有已发布链接

• PaperWeekly 默认每篇文章都是首发,均会添加“原创”标志


? 投稿邮箱:

• 投稿邮箱:hr@paperweekly.site

• 所有文章配图,请单独在附件中发送

• 请留下即时联系方式(微信或手机),以便我们在编辑发布时和作者沟通

?

现在,在「知乎」也能找到我们了

进入知乎首页搜索「PaperWeekly」

点击「关注」订阅我们的专栏吧

关于PaperWeekly

PaperWeekly 是一个推荐、解读、讨论、报道人工智能前沿论文成果的学术平台。如果你研究或从事 AI 领域,欢迎在公众号后台点击「交流群」,小助手将把你带入 PaperWeekly 的交流群里。

▽ 点击 |阅读原文 | 收藏复现代码

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

  1. 经典论文复现 | 基于深度卷积网络的图像超分辨率算法

    过去几年发表于各大 AI 顶会论文提出的 400 多种算法中,公开算法代码的仅占 6%,其中三分之一的论文作者分享了测试数据,约 54% 的分享包含"伪代码".这是今年 AAAI ...

  2. 经典论文复现 | 基于标注策略的实体和关系联合抽取

    过去几年发表于各大 AI 顶会论文提出的 400 多种算法中,公开算法代码的仅占 6%,其中三分之一的论文作者分享了测试数据,约 54% 的分享包含"伪代码".这是今年 AAAI ...

  3. 经典论文复现 | ICML 2017大热论文:Wasserstein GAN

    过去几年发表于各大 AI 顶会论文提出的 400 多种算法中,公开算法代码的仅占 6%,其中三分之一的论文作者分享了测试数据,约 54% 的分享包含"伪代码".这是今年 AAAI ...

  4. 华为云举办AI经典论文复现活动,打造领先AI开发者学习社区

    摘要:百余篇经典论文算法上线华为云AI Gallery,学AI就用ModelArts 2020年8月28日,华为云AI院长峰会在杭州千岛湖举行,来自中国科学院.中国工程院的多位院士,以及清华大学.北京 ...

  5. Mol2vec | 一种无监督机器学习方法的分子亚结构向量表示

    Mol2vec 受自然语言处理技术的启发,这里介绍Mol2vec是一种无监督的机器学习方法,用于学习分子亚结构的向量表示.就像Word2vec模型一样,密切相关的单词的向量在向量空间中非常接近,Mol ...

  6. 26篇计量经济经典论文复现数据和Stata或R代码

    26篇文章的复现数据.Stata或R复制程序.各位学者可以阅读这些文章,并根据Stata和R代码对原文中的图表进行一一复制,只有这样才能成长更快. 以其中一篇文章为例,包含了以下内容: [26篇论文目 ...

  7. 智能反射面RIS经典论文复现,主被动式波束赋形(二)

    引言 文章智能反射面RIS经典论文复现,主被动式波束赋形主要复现了IRS经典论文<Intelligent Reflecting Surface Enhanced Wireless Network ...

  8. 【生成模型】关于无监督生成模型,你必须知道的基础

    大家好,小米粥销声匿迹了很长一段时间,今天又杀回来啦!这次主要是介绍下生成模型的相关内容,尤其是除了GAN之外的其他生成模型,另外应部分读者要求,本系列内容增添了代码讲解,希望能使大家获益,更希望大家 ...

  9. 聚类算法 距离矩阵_快速且不需要超参的无监督聚类方法

    论文: Efficient Parameter-free Clustering Using First Neighbor Relations Efficient Parameter-free Clust ...

最新文章

  1. UITabBarItem如何更改高度
  2. 【Android 逆向】ART 脱壳 ( InMemoryDexClassLoader 脱壳 | InMemoryDexClassLoader 类加载器脱壳点总结 )
  3. Codeforces 1025G Company Acquisitions (概率期望)
  4. 香帅的北大金融学课笔记 -- 金融学专题
  5. TensorFlow学习笔记(二十五)CNN的9大模型
  6. python导入自己写的模块_Python:包、模块和导入
  7. html页面选择附件实现,实现单文件上传,页面局部刷新_html/css_WEB-ITnose
  8. [20160704]Block recover using RMAN.txt
  9. JavaScript基础笔记(十四)最佳实践
  10. java正则表达 替换_java正则表达式替换
  11. 消元法解n元一次方程组 c++实现
  12. 当AI能气味编程时,网友:这才是真正的黑客帝国!
  13. 我真的很郁闷,应该振作起来的
  14. keras 中adam_ADAM电影中的照明技巧和窍门
  15. Fightting for your youth
  16. Tuner及工作原理介绍
  17. Dell服务器组建阵列-Raid(无阵列卡)
  18. JS中三种字符串连接方式及其性能比较
  19. 由十多位架构师打造的《面试突击核心讲》到底有多强?肝完金三银四稳了。
  20. rfcn 共享_RFCN 精简讲解

热门文章

  1. 代码管理工具 Git
  2. BZOJ2054: 疯狂的馒头(并查集)
  3. C程序员眼里的Python
  4. 121 Best Time to Buy and Sell Stock 买卖股票的最佳时机
  5. Java Script中常见操作
  6. php随机生成验证码代码
  7. 图集cancelButtonIndex,发送通知简洁写法
  8. CBitMap的用法 from http://www.cnblogs.com/toconnection/archive/2012/08/04/mfc.html
  9. C# 3.0新特性之扩展方法
  10. 横向循环焦点图片展示_多光子显微镜成像技术之十二:多光子显微镜中的焦点深度扩展方法...