从一篇ICLR'2017被拒论文谈起:行走在GAN的Latent Space

同步自我的知乎专栏文章:https://zhuanlan.zhihu.com/p/32135185

从Slerp说起

ICLR‘2017的投稿里,有一篇很有意思但被拒掉的投稿《Sampling Generative Networks》 by Tom White。文章比较松散地讲了一些在latent space挺有用的采样和可视化技巧,其中一个重要的点是指出在GAN的latent space中,比起常用的线性插值,沿着两个采样点之间的“弧”进行插值是更合理的办法。实现的方法就是图形学里的Slerp(spherical linear interpolation)在高维空间中的延伸:

\text {Slerp} (p_0,p_1;t)=\frac{sin((1-t)\Omega)}{sin(\Omega)} p_0+\frac{sin(t\Omega)}{sin(\Omega)} p_1

形象理解并不难,以wiki上的图为例:

要求的是图中P点,在Wiki图的基础上我加了A、B和O三个点,O就是原点。所以P其实就是\vec {OP}=\vec {OA} + \vec {OB} 。先考虑求 \vec {OA} ,第一步引入和 \vec {OP_0} 垂直的 \vec {O\perp P_0} ,也就是图中蓝色的箭头。那么 \left| OA \right| 和 \left| OP_1 \right| 的比值就等于他们分别投影到蓝色向量上的部分的比值,也就是蓝色箭头两侧的橙色线段比红色线段。这个比值正是 \frac{\sin \theta}{\sin \Omega} ,于是 \vec {OA} 就是 \frac{sin(\theta)}{sin(\Omega)} p_1 。很显然,同样的方法也可以用来求 \vec {OB}=\frac{sin(\Omega-\theta)}{sin(\Omega)} p_0 ,然后代入 \vec {OP}=\vec {OA}+\vec {OB} ,并令 \theta=t\Omega ,就得到了Slerp的公式。注意虽然推导的时候用的虽然是 P_0 和 P_1 在同一个(超)球面上,但是实际用的时候不同长度的 P_0 和 P_1 之间利用Slerp也是可以很自然的插值的,得到的向量长度介于二者之间且单调(非线性)增减。ICLR的Review中也讨论到了这个问题。

使用Slerp比起纯线性插值的好处在哪里呢?作者原文这样解释:

"Frequently linear interpolation is used, which is easily understood and implemented. But this is often inappropriate as the latent spaces of most generative models are high dimensional (> 50 dimensions) with a Gaussian or uniform prior. In such a space, linear interpolation traverses locations that are extremely unlikely given the prior. As a concrete example, consider a 100 dimensional space with the Gaussian prior μ=0, σ=1. Here all random vectors will generally a length very close to 10 (standard deviation < 1). However, linearly interpolating between any two will usually result in a "tent-pole" effect as the magnitude of the vector decreases from roughly 10 to 7 at the midpoint, which is over 4 standard deviations away from the expected length."

就是说在高维(>50)的空间里做线性插值,会路过一些不太可能路过的位置,就好像数据都分布在帐篷布上,但是线性插值走的是帐篷杆。

要更具体理解这个现象,还要从GAN中常用的prior distribution说起。在GAN中,最常用的是uniform和Gaussian(感觉现在Gaussian居多)。不管是哪种prior,对于一个n维样本 \left( x_1,x_2,\dots,x_n \right) ,到中心的欧式距离为:

d=\sqrt{x_1^2+x_2^2+\dots+x_n^2}

而通常GAN的采样空间维度还算高,这个时候我们把d的平方看作是一连串n个独立同分布的随机变量 x_1^2,x_2^2,\dots,x_n^2 的和,则由中心极限定理可知d的平方近似服从正态分布(实际上是Chi-square分布):

N\left( n\mu, n\sigma^2 \right)

考虑很常见的100维标准正态分布作为prior的情况,平方之后就是k=1的Chi-square分布,均值为1,方差为2。所以每个样本到原点的距离的平方近似服从N(100, 200),标准差~14.14,如果认为 \delta =14.14/100已经足够小,使 \sqrt{1+\delta}\approx1+\frac 1 2\delta ,则d也可以近似看作是一个高斯分布(实际上是Chi分布),均值为10,标准差为0.707,就是作者在原文中说的情况。

uniform prior的情况也类似,不过更加复杂,因为均匀分布并非各向同性。在高维空间中,一个超立方体形状如果脑补一下就是一个球周边长了很多尖刺,每个尖刺就是象限中的一个极端值。具体推导我不会,不过写个程序很容易模拟。采10万个样本得到的结果是100维,每个维度[-1, 1]的均匀分布的prior,样本到中心的距离平均值约为5.77,标准差约0.258。所以无论是哪种情况,高维空间里的样本都有一个特点:远离中心,且集中分布在均值附近。所以线性插值就会像在帐篷杆上插值一样,路过真实样本出现概率极低的区域。

原文还提到了在100维Gaussian prior的情况下,线性插值取到的点到中心的距离会从10到7,这怎么理解呢?我的数学水平脑补不了这件事,定性来看随机采两个样本,这两个样本趋于垂直的倾向会很高,因为要趋于同向或者反向需要每一维的距离都足够近或足够远,这个概率会很低。定量的话可以写个程序模拟:

import numpy
from matplotlib import pyplot

def dist_o2l(p1, p2):
    # distance from origin to the line defined by (p1, p2)
    p12 = p2 - p1
    u12 = p12 / numpy.linalg.norm(p12)
    l_pp = numpy.dot(-p1, u12)
    pp = l_pp*u12 + p1
    return numpy.linalg.norm(pp)

dim = 100
N = 100000

rvs = []
dists2l = []
for i in range(N):
    u = numpy.random.randn(dim)
    v = numpy.random.randn(dim)
    rvs.extend([u, v])
    dists2l.append(dist_o2l(u, v))

dists = [numpy.linalg.norm(x) for x in rvs]

print(‘Distances to samples, mean: {}, std: {}‘.format(numpy.mean(dists), numpy.std(dists)))
print(‘Distances to lines, mean: {}, std: {}‘.format(numpy.mean(dists2l), numpy.std(dists2l)))

fig, (ax0, ax1) = pyplot.subplots(ncols=2, figsize=(11, 5))
ax0.hist(dists, 100, normed=1, color=‘g‘)
ax1.hist(dists2l, 100, normed=1, color=‘b‘)
pyplot.show()

结果如下:

左边是在latent space里随机采样的样本到中心距离的分布,右边是原点在随机采样的两个样本所在直线上的投影点到中心距离的分布,也就是线性插值中到中心最近点的距离的分布。可以看到随机采样并进行线性插值的办法还真的是容易路过样本几乎不可能出现的区域(距原点距离5~7.5)。可是《Sampling Generative Networks》被拒的comment里有一句:"neither the reviewers nor I were convinced that spherical interpolation makes more sense than linear interpolation"。就这一点来说,感觉Tom White有些冤枉,虽然确实不是什么眼前一亮的大改进,但是有理有据。那为什么reviewer们没觉得比线性插值好多少呢?原因可能就是:

基于ReLU网络的线性

CNN在12年的时候一鸣惊人,应该说ReLU一系的激活函数扮演了一个至关重要的角色:让深层网络可训练。后续的无论是LeakyReLU、ELU还是Swish等等,大于0的部分都是非常线性的。所以虽然非线性变换(激活函数)是神经网络作为universal approximator的基础,但基于ReLU系的神经网络其实是线性程度很高的。对于常见判别式网络,Ian Goodfellow认为这种线性再加上Distributed Representation的超强表达能力是使得网络容易被对抗样本攻击的基础(详见这篇),并据此发明了Fast Gradient Sign方法快速生成对抗样本。

那么基于ReLU的CNN的线性有多强呢?先来看生成式网络,以DCGAN为例,示意图如下

从结构上来看,DCGAN比常见的判别式网络更加线性,因为连max pooling都没了,不那么线性的部分就只有最后输出图片的Tanh。尤其是从latent space到第一组feature map这一步,常见的实现方法是把100维的噪声看成是100个channel,1x1的feature map,然后直接用没有bias的transposed convolution上采样,是一个纯线性变换!定性来看,如果整个后续的网络部分线性程度也足够高,则在latent space的任意样本,同时对所有维度进行缩放的话,得到的图像应该差不多就是同一幅图不同的对比度。

训练一个GAN的生成器就可以验证这个结论,感谢何之源在文章GAN学习指南:从原理入门到制作生成Demo中提供了一份对GAN而言高质量且好下载的动漫头像数据。基于这个数据和PyTorch的官方DCGAN例子就可以很轻松的训练出一个模型。基于训练出的模型随机采样并分别进行Slerp和线性插值,结果如下:

1、3、5行是线性插值的结果,2、4、6是Slerp结果,仔细看的话会发现线性插值结果的中间部分和Slerp相比,颜色淡了那么一点点,除此以外差别微乎其微。也难怪Reviewer会觉得Tom White的结论不令人信服,如果没有对比,线性插值的结果看起来很好。并且由于高维空间中样本远离中心的特性,所以线性插值的均匀性和Slerp也差不多。不过有了上面的分析,再回过头来看DCGAN原文中的线性插值结果,好像中间部分看起来颜色还真是有点淡……

直接对比Slerp和线性插值并不是很有说服力,我们可以做一个更暴力的实验,让样本沿着一个随机方向从原点出发一直到距离原点20的位置,结果如下:

还是挺一目了然的,随着latent sample渐渐远离原点,图像的变化基本上是对比度越来越高,直至饱和。起码就人眼来说,这是很线性的。那么实际上呢?如果去掉Tanh层,随机取一些样本和输出图像随机位置的值,画出随着latent sample距中心距离变化的趋势,大概是下面这样:

可以看到,只有在距离中心很远的时候,线性才比较明显,这和Goodfellow论文中的图性质一致。在样本最集中的10附近,线性程度一般般,甚至有些输出都不是单调的。

那么更进一步,如果latent sample只产生在超球面上呢?或是到原点距离均匀分布呢?不妨试一试,结果如下:

1) 在到原点距离为10的超球面上产生latent sample

肉眼看上去还是很线性,输出曲线的结果看上去和直接Gaussian采样差别也不大。

2) latent sample到原点距离从0到10均匀分布

从曲线来看和前两种情况明显不一样了,生成图像质量也下降了一些。但是从产生的图片来看线性仍然较强,至少到中心距离<10的部分,图片“身份”无区分度的结论还是基本成立。

这是个很有趣的现象,无论是Gaussian采样,还是1)和2)的情况,对人眼来说,这种很粗略程度的线性已经足够让沿着某个方向上的latent sample产生从“身份”角度看上去无差别的样本了。不管怎么样,基于这个现象,得到一个粗略的推论:GAN的Latent Space只有沿着超球面的变化才是有区分力的。

在Great Circle上行走

经过一番分析,得到了一个好像也没什么用的结论。再回来看最初的问题,线性插值会路过低概率区域(虽然并没有什么影响),Slerp比线性插值也没什么视觉上的本质提高,那么有没有什么更优雅地行走在latent space的方法呢?我觉得是:Great Circle。比起Slerp,Greate Circle通常要经过多3倍的距离,虽然这和Slerp其实也没什么本质区别,但是感觉上要更屌,而且沿着great circle走起点和终点是同一个点,这感觉更屌。

产生great circle路径比Slerp要简单得多:1)根据所使用分布产生一个超球面半径r(按前面讨论,Gaussian的话就是chi分布,或者Gaussian近似);2)产生一个随机向量u和一个与u垂直的随机向量v,然后把u和v所在平面作为great circle所在平面;3)u和v等效于一个坐标系的两轴,所以great circle上任一点就用在u和v上的投影表示就可以,最后在乘上r就得到了行走在great circle上的采样。代码如下:

from __future__ import print_function
import argparse
import os
import numpy
from scipy.stats import chi
import torch.utils.data
from torch.autograd import Variable
from networks import NetG
from PIL import Image

parser = argparse.ArgumentParser()
parser.add_argument(‘--nz‘, type=int, default=100, help=‘size of the latent z vector‘)
parser.add_argument(‘--niter‘, type=int, default=10, help=‘how many paths‘)
parser.add_argument(‘--n_steps‘, type=int, default=23, help=‘steps to walk‘)
parser.add_argument(‘--ngf‘, type=int, default=64)
parser.add_argument(‘--ngpu‘, type=int, default=1, help=‘number of GPUs to use‘)
parser.add_argument(‘--netG‘, default=‘netG_epoch_49.pth‘, help="trained params for G")

opt = parser.parse_args()
output_dir = ‘gcircle-walk‘
os.system(‘mkdir -p {}‘.format(output_dir))
print(opt)

ngpu = int(opt.ngpu)
nz = int(opt.nz)
ngf = int(opt.ngf)
nc = 3

netG = NetG(ngf, nz, nc, ngpu)
netG.load_state_dict(torch.load(opt.netG, map_location=lambda storage, loc: storage))
netG.eval()
print(netG)

for j in range(opt.niter):
    # step 1
    r = chi.rvs(df=100)

    # step 2
    u = numpy.random.normal(0, 1, nz)
    w = numpy.random.normal(0, 1, nz)
    u /= numpy.linalg.norm(u)
    w /= numpy.linalg.norm(w)

    v = w - numpy.dot(u, w) * u
    v /= numpy.linalg.norm(v)

    ndimgs = []
    for i in range(opt.n_steps):
        t = float(i) / float(opt.n_steps)
        # step 3
        z = numpy.cos(t * 2 * numpy.pi) * u + numpy.sin(t * 2 * numpy.pi) * v
        z *= r

        noise_t = z.reshape((1, nz, 1, 1))
        noise_t = torch.FloatTensor(noise_t)
        noisev = Variable(noise_t)
        fake = netG(noisev)
        timg = fake[0]
        timg = timg.data

        timg.add_(1).div_(2)
        ndimg = timg.mul(255).clamp(0, 255).byte().permute(1, 2, 0).numpy()
        ndimgs.append(ndimg)

    print(‘exporting {} ...‘.format(j))
    ndimg = numpy.hstack(ndimgs)

    im = Image.fromarray(ndimg)
    filename = os.sep.join([output_dir, ‘gc-{:0>6d}.png‘.format(j)])
    im.save(filename)

结果如下:

虽然感觉没什么用,不过万一有人想试试,代码在此:great-circle-interp

从一篇ICLR'2017被拒论文谈起:行走在GAN的Latent Space

原文地址:https://www.cnblogs.com/frombeijingwithlove/p/8120028.html

时间: 2024-10-15 01:47:34

从一篇ICLR'2017被拒论文谈起:行走在GAN的Latent Space的相关文章

复现一篇深度强化学习论文之前请先看了这篇文章!

去年,OpenAI和DeepMind联手做了当时最酷的实验,不用经典的奖励信号来训练智能体,而是根据人类反馈进行强化学习的新方法.有篇博客专门讲了这个实验 Learning from Human Preferences,原始论文是< Deep Reinforcement Learning from Human Preferences>(根据人类偏好进行的深度增强学习). 链接:https://arxiv.org/pdf/1706.03741.pdf 过一些深度强化学习,你也可以训练木棍做后空翻

Steve Lin:如何撰写一篇优秀的SIGGRAPH论文

原文:http://blog.sina.com.cn/s/blog_4caedc7a0102v6mu.html 一篇优秀的论文应该是这样的 广大的研究同仁介绍了这篇论文所包含的重要想法和所获得的结果 在论文中描述清楚你的想法和所获得的结果 基于你所做的东西,提供一些具有洞察性的见解 为什么论文的撰写是需要引起重视的? 提高论文的录取机会 提升你所做的工作的影响力 提升你所做的研究的质量 一篇糟糕的论文是这样的 任性的以为 读者已经知道了你所做的所有事 只要你自己能理解你所撰写的内容,其他人也一定

几篇深度图人体检测论文的实现

之前毕业论文有用到几个人体检测的算法,参照论文进行了实现,有些是本来用在彩色图里的(ELDP,LDP),有些是用在深度图里的(HDD,ELDP,LTDP),分享在这里. HumanDetectionUsingDepth This is an implementation of a series human detection algorithms which use depth images.HDD:Wu S, Yu S, Chen W. An attempt to pedestrian det

2017毕设论文小结

准备工作: 1.设置纸张大小.页眉页脚边距,设置样式来控制全局,而不是一段一段地去手动调格式.若把内容写好再来设置这些,可能出现图像和公式被隐藏.页码和页眉可以文章写好后再进行设置. 前期学习资料的搜集: 利用的文献数据库 ACM,Springer Link,中国知网,还有的是作者主页提供的文献(可能有代码)[搜文献-->得知作者全名-->找作者的主页:也可直接搜作者名字],会议官网提供的文献(可能有代码). 内容撰写: 1.就自己所做的工作和想展示的内容,搭论文的框架,写论文的大纲.各级标题

年终盘点篇:2017年度微服务调查报告出炉

[IT168 调查报告]如果在诸多热门云计算技术中,诸如容器.微服务.DevOps.OpenStack 等,找出一个最火的方向,那么非微服务莫属.尽管话题炙手可热,但对传统行业来说,微服务落地和方法论目前处于起步阶段. 本报告于2017年11月份展开,从驱动因素.落地现状.和容器关系.架构体系.未来趋势和落地方法论等方面对微服务进行了分析.希望能够为传统企业微服务决策.规划和实施提供依据和解决办法. 一.驱动因素 传统行业对IT效率的变革需求是微服务成长土壤,业务模式创新重塑导致系统更新频繁.应

2017 OCR 论文选读

1.Detecting Oriented Text in Natural Images by Linking Segments, by Baoguang Shi, Xiang Bai, Serge Belogie, Huazhong University of Science and Techinology 2.Deep Matching Prior Network: Toward Tigher Multi-oriented Text Detection, by Yuliang Lin, Lia

java学习篇(四)---浅谈JUnit 3.8

1. 使用Junit 的最佳实践: 1) 新建一个名为test 的 source folder,用于存放测试类源代码 2) 目标类与测试类应该位于同一个包下面,这样测试类中就不必导入源代码 所在的包,因为他们位于同一个包下面 3) 测试类的命名规则:假如目标类是 Calculator,那么测试类应该命名为 TestCalculator或者是 CalculatorTest   2.Junit 的口号:keep the bar green to keep the code clean.   3.Ju

java学习篇(五)---浅谈JUnit 4

Junit4   JUnit4 全面引入了 Annotation 来执行我们编写的测试.   1.    JUnit 4 并不要求测试类继承 TestCase 父类.   2.    在一个测试类中,所有被@Test 注解所修饰的public,void 方法都是 test case,可以被JUnit 所执行.   3.    虽然 JUnit 4 并不要求测试方法名以test 开头,但我们最好还是按照 JUnit 3.8的要求那样,以test 作为测试方法名的开头.   4.    在JUnit

Unsupervised Deep Learning – ICLR 2017 Discoveries

Unsupervised Learning Using Generative Adversarial Training And Clustering – Authors: Vittal Premachandran, Alan L. Yuille An Information-Theoretic Framework for Fast and Robust Unsupervised Learning via Neural Population Infomax– Authors: Wentao Hua