生成对抗网络(GANs)最新家谱:为你揭秘GANs的前世今生

作者:GuimPerarnau

编译:KatherineHou、朝夕、KatrineRen、ShanLIU、笪洁琼、钱天培

生成对抗网络(GAN)一经提出就风光无限,更是被YannLecun誉为“十年来机器学习领域最有趣的想法”。

GAN“左右互搏”的理念几乎众所周知,但正如卷积神经网络(CNN)一样,GAN发展至今已经衍生出了诸多变化形态。

今天,文摘菌就来为大家盘点一下GAN大家庭中各具特色的成员们。

他们的名单如下:

1.DCGANs

2.ImprovedDCGANs

3.ConditionalGANs

4.InfoGANs

5.WassersteinGANs

6.ImprovedWGANs

7.BEGANs

8.ProGANs

9.CycleGANs

注意,这篇文章不会包含以下内容

•复杂的技术分析

•代码(但有代码链接)

•详细的研究清单

(你可以点击以下链接https://github.com/zhangqianhui/AdversarialNetsPapers)

想要了解更多GANs相关内容的也可以留言告诉文摘菌哦~

GANs概论

如果你对GANs很熟悉的话,你可以跳过这部分的内容。

GANs最早由IanGoodfellow提出,由两个网络构成,一个生成器和一个鉴别器。他们在同一时间训练并且在极小化极大算法(minimax)中进行竞争。生成器被训练来欺骗鉴别器以产生逼真的图像,鉴别器则在训练中学会不被生成器愚弄。

GAN训练原理概览

首先,生成器通过从一个简单分布(例如正态分布)中抽取一个噪音向量Z,并且上行采样(upsample)这个向量来生成图像。在最初的循环中,这些图像看起来非常嘈杂。然后,鉴别器得到真伪图像并学习去识别它们。随后生成器通过反向传播算法(backpropagation)收到鉴别器的反馈,渐渐在生成图像时做得更好。我们最终希望伪图像的分布尽可能地接近真图像。或者,简单来说,我们想要伪图像尽可能看起来像真的一样。

值得一提的是,因为GANs是用极小化极大算法做优化的,所以训练过程可能会很不稳定。不过你可以使用一些“小技巧”来获得更稳健的训练过程。

在下面这个视频中,你可以看到GANs所生成图片的训练演变过程。

代码

如果对GANs的基本实现感兴趣,可以参见代码的链接:

Tensorflow(https://github.com/ericjang/genadv_tutorial/blob/master/genadv1.ipynb)

Torch和PythonPyTorch)(https://github.com/devnag/pytorch-generative-adversarial-networks;https://medium.com/@devnag/generative-adversarial-networks-gans-in-50-lines-of-code-pytorch-e81b79659e3f)

Torch和Lua(https://github.com/lopezpaz/metal)

虽然这些不是最前沿的内容,但它们对于掌握理念很有帮助。

接下来我将按照粗略的时间顺序描述最近这些年来出现的GANs相关的一些进展和类型。

深度卷积生成式对抗网络(DeepConvolutionalGANs,DCGANs)

DCGANs是GAN结构的最早的重要发展。就训练和生成更高质量的样本来说,DCGANs更加稳定。

论文链接:https://arxiv.org/abs/1511.06434

DCGAN的作者们专注于提升初代GAN的框架结构。他们发现:

批次(Batch)的正态化在两个网络中都是必须要做的。

完全隐藏的连接层不是一个好的想法。

避免池化,只需卷积

修正线性单元(ReLU)激活函数非常有用。

DCGANs截至目前任然被时常提起,因为它们成为了实践和使用GANs的主要基准之一。

在这篇论文发布后不久,就在Theano,Torch,TensorflowChainer中出现了不同的可使用的实现方法,这些方法可以在你感兴趣的任何数据集上测试。所以,如果你遇到了生成后奇怪的数据集,你完全可以归咎于这些家伙。

DCGANs的使用场景如下:

你想要比基础GANs表现更好(这是必须的)。基础GANs适用于简单的数据集,然而DCGANs比这要强得多。

你在寻找一种稳固的基准方法来比较你的新GAN算法。

从现在开始,除非特别说明,我接下来将要描述的所有GANs的类型都被假定为有DCGAN的结构。

提升深度卷积生成式对抗网络(ImprovedDCGANs)

一系列提升先前的DCGAN的技术。例如,这一提升后的基准方法能够生成更好的高分辨率图像。

论文链接:https://arxiv.org/abs/1606.03498

GANs的主要问题之一是收敛性。收敛性不是一定的,而且尽管DCGAN做了结构细化,训练过程仍可能非常不稳定。

在这篇论文里,作者们针对GAN训练过程提出了不同的增强方法。以下是其中一部分:

特征匹配:他们提出了一种新的目标函数,而不是让生成器尽可能地去蒙骗鉴别器。这个目标函数需要生成器生成的数据能够跟真实数据的统计量相匹配。在这种情况下,鉴别器只被用来指定哪些才是值得去匹配的统计量。

历史平均:在更新参数值时,把它们过去的值也纳入考虑。

单侧标签平滑:这一项非常简单:只要把你的鉴别器的目标输出值从[0=假图像,1=真图像]改成[0=假图像,0.9=真图像]。不错,这样就提升了训练效果。

虚拟批次正态化:通过使用从其他批次计算的统计量来避免依赖于同一批次的数据。这样的计算成本很高,所以它仅仅被用在生成器当中。

所有这些技术都使得模型在生成高分辨率图像时能表现得更好,而这正是GANs的弱项之一。

作为对比,请看在128×128图像上原始DCGAN提升后的DCGAN的表现差异:

这些本来都是狗的图片。正如你看到的,DCGAN表现很糟糕,而用improvedDCGAN你至少可以看到一些包含狗的特征的内容。这也说明了GANs的另一局限——生成结构性的内容。

ImprovedDCGANs的使用场景如下

生成更高分辨率的图像

条件生成式对抗网络(ConditionalGANs,cGANs)

条件式生成式对抗网络使用额外的标签信息用于生成更高质量图片,并且使图片的呈现可控制。

论文链接:https://arxiv.org/abs/1411.1784

CGANs是GAN框架的扩展。我们用条件信息Y来描述数据的某些特征。假设我们要处理面部图像,Y则可以用来描述头发颜色或者性别。然后这些属性被插入生成器和鉴别器。

使用脸部特征信息的条件生成网络如上图所示

条件式生成对抗网络有两个很有意思的地方:

1、随着你不断给模型提供更多信息,GAN学习探索这些信息,然后产生更好的样本。

2、我们用两种方法来控制图片的呈现,在没有CGAN的时候所有图片信息使用Z编码。在CGAN下,我们加入了条件信息Y,于是Z和Y对不同信息进行编码。

例如,我们假设Y对手写数字0-9进行编码。Z对其他变量编码,这些变量可以是数字的风格比如(大小,粗细,旋转角度等。)

MNISTMixedNationalInstituteofStandardsandTechnologydatabase,简单机器视觉数据集)样本中Z和Y的区别如上图。Z是行,Y是列;Z对数字的风格编码,Y对数字本身编码。

最近的研究成果

在这个领域有很多有趣的文章,我介绍2个:

1、使用生成对抗网络学习在指定位置画画

(论文:https://arxiv.org/abs/1610.02454;代码:https://github.com/reedscot/nips2016):这篇论文里作者设计了一个本文描述的方法来告诉GAN画什么,同时使用方框和标记告诉GAN绘画主体的位置。如下图示:

堆栈式GAN

(原文:https://arxiv.org/abs/1612.03242;代码:https://github.com/hanzhanggit/StackGAN)

这篇文章和上一篇比较类似,这里作者同时使用2个GAN网络(阶段1和阶段2)用于提升图片的质量。第1阶段用来获得包含图片“基本”概念的低分辨率图片。第2阶段用更多的细节和更高的分辨率提炼第1阶段的图片。

这篇文章据我所知是生成高质量图片最好的模型之一,不信请看下图。

条件式生成网络的使用场景如下:

1、你有一个带标签的训练集,想提高生成图片的质量

2、你想对图片的某些特征进行精细的控制,比如在某个设定的位置生成特定大小的一只红色小鸟。

最大信息化生成对抗网络(InfoGANs)

GANs可以在无监督模式下对噪声向量Z的一部分有意义的图像特征进行编码。比如对某一数字的旋转编码

论文链接:https://arxiv.org/abs/1606.03657

你是否曾想过GAN里输入噪声Z对哪些信息进行编码?一般来说它对图片的不同类型的特征使用一种非常“嘈杂”的方式编码。例如,取Z向量的一个位置并且插入一个-1到1之间的值。这是下图所示的MNIST数据集的训练模型。

左上图像是对Z插值取-1的时候,右下是插值为1的时候

上图中,生成的图片看似是从4慢慢变成“Y”(很像是4和9的混合体)。

这就是我之前所说的使用一种“嘈杂”的方式进行编码:Z的一个位置是图像多个特征的一个参数。

这种情况下,这个位置改变了数字自己(某种程度,从4变成9)和他的风格(从粗体变成斜体)。

然而,你无法定义Z的位置的确切含义。

如果使用Z的一些位置来代表唯一且受限的信息,正如CGAN里的条件信息Y呢?

例如,第一个位置是0-9之间的数值来控制数字,第二个位置来控制数字的旋转,这就是文中作者想要表达的。

有意思的是,与CGANS不同,他们使用无监督的方法实现并不需要标签信息。

他们是这么做的,把Z向量拆分成两部分:C和Z

C对数据分布的语义特征进行编码

Z对分布的所有非结构化噪声进行编码

如何用C对这些特征编码呢?

通过改变损失函数避免C被GAN简单地忽略掉。所以他们使用一种信息论正则化确保C与生成器分布之间的互信息[z1](mutualinformation)。

也就是说,如果C变化,生成的图像也会变化。这导致你不能明确的控制什么类型的信息将被引入C中。但是C的每一个位置都有独特的含义。

如下图所示:

C的第一位置编码数字的类别,第二位置编码旋转方向。

然而,不使用标签信息的代价是,这些编码仅对非常简单的数据集有效比如MNIST库里的数字。

并且,你还需要手工设定C的每个位置。例如文章中作者需要定义C的第一位置是介于0-9的整数以对应数据集的十类数字。你会认为这样不是百分百的无监督,因为需要手动给模型提供一些细节。

你可能需要用到infoGANs的场景如下:

1、数据集不太复杂

2、你想训练一个CGAN模型但是缺少标签信息

3、你想知道数据集主要有意义的图像特征并且对他们进行控制

生成式对抗网络(WassersteinGANs)

修改损失函数以引入Wasserstein距离,这样以来WassGANs的损失函数同图片质量建立联系。同时训练的稳定性有所提升而并非依赖于架构。

论文链接:https://arxiv.org/abs/1701.07875

GANs经常存在收敛问题,所以你并不知道什么时候该停止训练。换句话说损失函数同图像质量无关,这可是个大难题。

因为:

你需要不断的查看样本来确认模型训练是否正确

因为不收敛,你不知道何时该停止训练

也没有数值指示你调参的效果如何

举个例子,看下面DCGAN的两个无信息损失函数完美的生成MNIST样本的迭代图[z2]

仅看上图你知道什么时候该停止训练吗?

这种可解释性的问题也是WassersteinGANs要解决的。

怎样解决呢?

如果真实和伪造样本的分布不重叠(一般都是这样)GANs可以用来最小化JS散度Jensen-Shannondivergence)直到0。

所以与其最小化JS散度,作者使用Wasserstein距离来描述不同分布中点之间的距离。

思路大概如此,如果你想了解更多,我强烈建议你看这篇文章(https://paper.dropbox.com/doc/Wasserstein-GAN-GvU0p2V9ThzdwY3BbhoP7)或者更深入分析或者阅读本论文。

WassGAN有和图像质量关联的损失函数并且能够收敛。同时他更加稳定而不依赖GAN的结构。比如,就算你去掉批标准化或者使用怪异的结构,它仍能很好的工作。

这就是他的损失函数图,损失越低,图像质量越好。完美!

WassersteinGANs的应用场景如下:

你需要寻找最先进的且有最高训练稳定性的GAN

你想要一个有信息量且可以解读的损失函数。

加强版WGANsImprovedWGANs,WGAN-GP)

这个模型使用的是WassersteinGANs,并应用了梯度惩罚(gradientpenalty)来代替权重剪辑(weightclipping)及其带来的一些不需要的行为。这个方法可以带来更高的聚合度、更高质量的样本和更稳定的训练。

论文:https://arxiv.org/abs/1704.00028

代码:https://github.com/igul222/improved_wgan_training

针对问题:WGANs有时候会生成一些质量不佳的样本,或者是无法在某些集合中生成聚集。这种现象主要是由于为了满足Lipschitz限制而在WGANs中应用权重剪辑(即把所有权重限制在一个由最小值和最大值组成的范围内)所造成的。如果你对这个限制不太了解,那么你只需要记住它是WGANs正常运行的一个必要条件。那么为什么权重剪辑会造成如上问题呢?这是因为它会使WGANs偏向于使用那些过于简单的函数,这意味着当WGANs想要模拟复杂的数据时,简单地估算近似值令其无法得到准确的结果(如下图所示)。另外,权重剪辑也使得梯度爆炸与消失更容易发生。

左图是使用了简单的函数导致无法正确模拟高斯为8的WGANs运行后的结果,而右图则是经过使用了更复杂的函数的WGAN-GP矫正后的图像。

梯度惩罚(Gradientpenalty):所以我们该如何摆脱权重剪辑带来的不良效果呢?

WGAN-GP的作者(GP表示梯度惩罚)提出了使用另一种叫梯度惩罚的办法来加强Lipschitz限制的建议。原则上,梯度惩罚的原理是对某些梯度实行了均值为1的限制。对于那些均值偏离于1的梯度将会实施惩罚(减少权重),这也是为什么它被称为梯度惩罚的原因。

优点:由于在训练中使用了梯度惩罚而不是权重剪辑,WGANs获得了更快的聚合。另外,因为不再必须对超参数进行调整,而且网络架构的使用也不再像之前那么重要,训练在某种程度上变得更加稳定。虽然很难说清楚到底有多少,但这些WGAN-GP确实产生了更高质量的样本。在已验证并测试的结构上,这些样本的质量和作为基线的WGANs产出的结果非常相似。

基于同一个网络架构,WGANs-GP明显在生成高质量样本上更有优势,而GANs则不然。

比如,据作者所知,这是首次GANs能够在残差网络的结构上运行:

为了不超出本篇博文的范畴,还有许多其他有趣的细节我这里就不一一阐述了。如果你对这个训练方式有兴趣想要了解更多(例如,为什么梯度惩罚只应用于“某些”特定梯度,又或者怎样才能把这个模型用于文本数据样本),我会建议你自行阅读一下该论文。

强化版的WGAN的优点如下:

l更快的聚合

l可以在各种各样的网络架构和数据集中使用

l不像其他的GANs一样需要太多的超参数调整

边界均衡GANsBoundaryEquilibriumGANs,BEGANs)

GANs使用一个自动编码器作为均衡判别器。它们可在一个简单的架构上被训练出来,且合成一个动态的可以使得均衡器和生成器在训练中两者平衡的阶段

论文:https://arxiv.org/abs/1703.10717

一个有趣的事实:BEGANs和WGAN-GP几乎是同一天在论文上发表。

理念:让BEGAN区别于其他GANs的原因有两个,一是它们使用了一个自动编码器作为均衡判别器(这一点和EBGANs类似),二是为了适用于这个情境的一个特别的损失函数。这种选择背后的理由是什么呢?强迫我们使用像素重建损失函数来制造模糊生成样本的自动编码器是否也不是传说中那么“邪恶”呢?

要回答这些问题,我们需要考虑以下两点:

1.为什么要重建像素损失?作者解释说这么做的原因是,对于那些符合对像素损失的重建分布模式的假设,我们可以依赖它们去匹配样本的分布模式。

2.而这又引出了下一个问题:如何才可以达到这一目的呢?一个重要的观点是,从自动编码器/均衡判别器形成的像素重建损失(换言之,就是基于某个图像输入,输出最优化的像素重建)并不是经BEGANs最小化后生成的最终损失。可以说,这个重建损失不过是为了计算最终损失的其中一个步骤。而最终损失的计算这是通过衡量基于真实数据的重建损失和基于生成数据的重建损失之间的Wasserstein距离(是的,现在它无处不在)。

这么一看似乎这其中的信息量非常大,但我可以保证,一旦我们看清损失函数是如何作用于生成器和判别器,这一切将会变得再明白不过了。

生成器专注于生成那些能够让判别器良好重建的图像

而判别器则致力于尽可能良好地重建真实图像,同时重建那些误差最大化的生成图像。

差异因数:另一个有趣的成分是所谓的差异因数。通过这个因子你能控制判别器不同程度的表现,决定它能只专注于形成对真实图像的完美重建(注重质量),又或是更侧重在区分真实图像和生成图像(注重多样性)。然后,它们就能更进一步地利用这个差异因数去保持生成器和判别器在训练中的平衡。如同WGANs,这一模型同样应用均衡状态作为调整和图像质量相关的聚合度的方法。然而,和WGANs与WGANs-GP)不尽相同的是,它们利用了Wasserstein距离,而非Lipschitz限制,去测量均衡水平。

结论:BEGANs并不需要任何花里胡哨的网络架构就可以训练得当。如同在论文中所述的,“不批量标准化,不丢弃神经网络元,无反卷积,也没有卷积滤镜的指数化增长”,这些生成样本128×128)的质量已经相当好了。

然而,在这篇论文中有一个重要的细节值得我们注意:论文中使用的未发表数据集的样本量是目前广为使用的CelebA数据集的两倍之大。因此,为了得到更具有现实意义的定性比较,我邀请大家去查看那些使用了CelebA的公开模型(https://github.com/carpedm20/BEGAN-tensorflow),并看看那些由此生成的样本。

最后,如果你对BEGANs感兴趣想要了解更多,我建议你阅读一下这篇博文(https://blog.heuritech.com/2017/04/11/began-state-of-the-art-generation-of-faces-with-generative-adversarial-networks/),里面对BEGANs有更多详细的介绍。

你需要BEGANs的原因一般会和需要使用WGANs-GP的情况差不多。这两个模型的结果往往非常相似(稳定的训练、简单的架构、和图像质量相关的损失函数),而两者的不同主要在于过程中使用的手段。由于评估生成式模型本身就不是一件容易的事,我们很难去说清楚孰优孰劣。但就像Theisetal在他们的论文(https://arxiv.org/abs/1511.01844)中所说的,选择一个评估的方法,不然就依据实际情况来做判定。在这种情况下,WGANs-GP具有更高的初始分数(Inceptionscore)而BEGANs则能生成非常优质的图像,两者都是未来最值得研究和最具有革新意义的模型。

渐进式发展生成对抗网络(ProgressivegrowingofGANs,ProGANs)

在训练过程中,逐步嵌入新的高分辨率的层次,来生成相当逼真的图像。更多的进步以及新兴的考核机制也相继涌现。新生成的图片质量高得惊人。

论文:https://arxiv.org/abs/1710.10196

代码:https://github.com/tkarras/progressive_growing_of_gans

生成高分辨率的图像是个大挑战。图片越大,对生成对抗网络来说越可能失败。因为它要求模型学习辨别更细微、复杂的细节。为了使大家更好理解,在这篇文章发表之前,GANs产出图像的合理尺寸大概是256×256。渐进式生成对抗网络(ProGANs)将它提升到了一个全新水准-生成尺寸为1024×1024的图片。我们来看看它是如何实现的:

理念:ProGANs-建立于WFGANs-GP-引入了一种灵活地将新层次逐渐嵌入到训练时间的方式。每一层都利用上采样增强了生成器和判别器里的图像分辨率。

步骤分解如下:

1首先,利用低像素图片训练生成器和判别器。

2在一定时间点(开始聚合的时候),提高分辨率。这是通过“迁移阶段”/“平滑技术”非常完美地实现的。

新的层次是借助α控制的一系列细微的线性步骤添加,而不是简单粗暴地加上一层。

我们看看生成器里都发生了什么。最初,当α=0,无变化。产出主要来自于前一低分辨率层(16×16)的贡献。

当提升α的值,新一层(32×32)开始通过反向传播调整它的权重。最后,当α趋近于1,我们几乎可以跳过32×32这一层的操作。同理适用于判别器,当然是以完全相反的方式:不是使图片更大,而是更小。

3一旦转化完成,继续训练生成器与判别器。假如得到的图片质量分辨率不达标,请回到步骤2。

不过,等等……对新的高分辨率图像的上采样与串联不是已经在StackGANs(还有新StackGANs++)里实现了么?没错,也不全对。首先,StackGANs是将文字翻译为图像的条件GANs,它引入文字描述作为额外输入值。而ProGANs没有使用任何假定式的信息。更加有趣的是,尽管StackGANs和ProGANs都对更高分辨率的图片串联,StackGANs需要尽量多的根据上采样独立配对的GANs-需单独训练,你想进行上采样3次么?那你要训练3个GANs。另一方面,在ProGANs模型中只有一个单一的GAN被训练。在这一训练中,更多的上采样层被逐步叠加至上采样的图片,上采样3次的尝试只是增加更多的层在训练时间上,而不是抓3个新的GANs逐次训练。总而言之,ProGANs采用一种与StackGANs相似的理念,它完美地完成,而且在没有额外信息的情况下产出更好的结果。

结果。作为渐进训练的结果,ProGANs生成的图片有更高的质量,而针对1024×1024图像的训练时间也缩减了5.4倍。背后的理由是,ProGAN不需要一次性学习所有大、小规模的典型图片。在ProGAN模型里,小规模的图像最先被学习(低分辨率层次聚合),然后模型可以自行聚焦在完善大尺寸图片的结构上(新的高分辨率层次聚合)。

其他的提升。

另外,该论文提出了新的设计决策来更进一步提升模型的性能。概述如下:

小批量标准差:将标准差运用于这些小批量的所有特征属性,允许每个小批量有相似的统计数据。然后,总结为一个新的层的单一值,嵌入至网络尾端。

均衡的学习速率:通过在训练过程中持续地将每个权重除以一个常量,确保所有权重的学习速度一致。

像素标准化:在生成器上,每一个矢量的特征是根据其卷基层标准化的(论文里的精确公式)。这是为了防止生成器和判别器的梯度量级的逐步上涨。

CelebA-HQ(CelebA高级版)。值得一提的是,作者为了实现高分辨率训练,改进了原始的CelebA,从而得到了CelebA-HQ,简单来说,它们移除原像,应用高斯滤波器来生成一种景深效果,然后探测到人脸图像,得到一个1024×1024尺寸的裁剪图片,在这一流程之后,他们从202k张图片里保留了最优质的30k张图片。

评估

最后,我们介绍一种新的评估方式

背后的理念是:生成图像的本地图形结构应该与训练图像的结构匹配。

那么如何测量本地结构?

使用Laplacianpyramid算法,你可以得到不同的空间频段,来作为描述符。

最后,我们从生成图像和原始图像中提取描述符,将其规范化,然后使用著名的Wasserstein距离检测它们有多接近。距离越近越好。

你可能会想在以下场景使用ProGANs的使用场景如下:

假如你希望获得最完美的结果。但是考虑到…

你需要花大量时间搭建模型:我们需要在一个单一的NVIDIATeslaP100GPU上花20天训练网络

如果你开始怀疑世界的真实性。GANs的下一轮迭代可能会给你一些比现实生活还真实的样本参考。

循环生成对抗网络(CycleGANs)

论文:https://arxiv.org/pdf/1703.10593.pdf

代码:https://github.com/junyanz/CycleGAN

循环GANs是目前最先进的用于图片互译的生成对抗网络。

这些GANs并不需要配对的数据集来学习不同领域之间的转译,这点很棒,因为配对数据集很难获取。然而CycleGANs仍然需要通过两个不同领域的数据X和Y(例如X是普通的马匹,Y是斑马)来训练。为了将转换限制于从一个领域到另一领域的范畴,他们使用所谓的“循环一致性损失”。大意是,如果可以将马匹A转化成斑马A,那么当你将斑马A逆转化,应该能得到马匹A才对。

这种从一个领域到另一领域的变换与另一热门概念“神经风格转换”是有区别的。后者结合了一副图像的“内容”与另一图像的“样式”。循环GANs则是在一个领域转换至另一领域的过程中学习总结非常高层次的特征。因此,循环GANs更笼统,并且适用于多种映射关系,例如将一个速写转化为真实物品。

总结一下,生成对抗网络在近期取得了两大显著进步

WGANS-GP和BEGANs

尽管理论研究方向不同,但它们提供了相似的优点。其次,我们有ProGANs(基于WGANS-GP),它打开了一条通往生成高清图像的清晰路径。同时,循环GANs让我们看到了GANs从数据集提取有用信息的潜力以及这些信息如何转入至另一非关联的数据分布。

原文地址: https://xw.qq.com/cmsid/20171204A0B7X400

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注