咱们先来看看纳什均衡的经济学定义:php
所谓纳什均衡,指的是参与人的这样一种策略组合,在该策略组合上,任何参与人单独改变策略都不会获得好处。换句话说,若是在一个策略组合上,当全部其余人都不改变策略时,没有人会改变本身的策略,则该策略组合就是一个纳什均衡。css
市场上有2家企业A和B,都是卖纸的,纸的成本都是2元钱,A和B都卖5块钱。在最开始,A、B企业都是盈利3块,这种状态叫”社会最优解(Social optimal solution)“。但问题是,社会最优解是一个不稳定的状态,就如同下图中这个优化曲面上那个红球点同样,虽然该小球目前处于曲面最高点,可是只要施加一些轻微的扰动,小球就会马上向山下滑落:html
如今企业A和B准备开展商业竞争:git
但若是价格战一直这样打下去,这个过程显然不可能无限迭代下去。当A和B都降价到了3块时,双方都达到了成本的临界点,既不敢涨价,也不敢降价。涨价了市场就丢了,降价了,就赚不到钱甚至赔钱。因此A和B都不会再去作改变,这就是纳什均衡。github
A和B怎样可以得到最大利润呢,就是A和B坐到一块儿商量,同时把价格提升,这就叫共谋,但法律为了保障消费者利益,禁止共谋。补充一句,共谋在机器学习中被称做”模型坍塌“,指的对对抗的模型双方都进入了一个互相承认的局部最优区而再也不变化,具体的技术细节咱们后面会讨论。web
囚徒困境是说:有两个小偷集体做案,而后被警察捉住。警察对两我的分别审讯,而且告诉他们政策:算法
两我的的收益状况以下所示:shell
由于A和B是不能互相通讯的,所以这是一个静态不彻底信息博弈,咱们分别考虑双方的决策面:网络
所以最终纳什均衡点在两我的都坦白,各判八年这里。架构
显然,集体最优解在两我的都抗拒,这样一来每一个人都判一年就出来了。可是,纳什均衡点却不在这里。并且,在纳什均衡点上,任何一我的都没有改变本身决策的动力。由于一旦单方面改变决策,那我的的收益就会降低。
咱们知道,在国内开车夹塞很常见。若是你们都不夹塞,是总体的最优解,可是按照纳什均衡理论,任何一个司机都会考虑,不管别人是否夹塞,我夹塞均可以使本身的收益变大。因而最终你们都会夹塞,加重拥堵,反而不如你们都不加塞走的快。
那么,有没有办法使我的最优变成集体最优呢?方法就是共谋。两个小偷在做案以前能够说好,我们若是进去了,必定都抗拒。若是你这一次敢反悔,那么之后道上的人不再会有人跟你一块儿了。也就是说,在屡次博弈过程当中,共谋是可能的。可是若是这个小偷想干完这一票就走,共谋就是不牢靠的。
在社会领域,共谋是靠法律完成的。你们约定的共谋结论就是法律,若是有人不按照约定作,就会受到法律的惩罚。经过这种方式保证最终决策从我的最优的纳什均衡点变为集体最优势。
另一方面,如今不少汽车厂商提出了车联网的概念,在路上的每一辆车都经过物联网连成一个临时网络,全部车按照一个最优的协同算法共同协定最优的行车路线、行车速度、路口等待等行为,这样总体交通能够达到一个总体最优,全部人都节省了时间。
彼此痛恨的甲、乙、丙三个枪手准备决斗,他们各自的水平以下:
首先明确一点,这是一个静态不彻底信息博弈,每一个抢手在开枪前都不知道其余对手的策略,只能在猜想其余对手策略的基础上,选择对本身最优的策略。
咱们来分析一下第一轮枪战各个枪手的策略。
第一轮枪战事后,有几种可能的结果:
如今进入第二轮枪战:
除非第一轮甲乙双亡,不然丙就必定处于劣势,由于不论甲或乙,他们的命中率都比丙的命中率为高。
这就是枪手丙的悲哀。能力不行的丙玩些花样虽然能在第一轮枪战中暂时获胜。可是,若是甲乙在第一轮枪战中没有双亡的话,在第二轮枪战结束后,丙的存活的概率就必定比甲或乙为低。
这彷佛说明,能力差的人在竞争中耍弄手腕能赢一时,但最终每每不能成事。
咱们如今改变游戏规则,假定甲乙丙不是同时开枪,而是他们轮流开一枪。先假定开枪的顺序是甲、乙、丙,咱们来分析一下枪战过程:
若是是丙先开枪,状况又如何呢?
咱们经过这个例子,能够理解人们在博弈中可否获胜,不单纯取决于他们的实力,更重要的是取决于博弈方实力对比所造成的关系。
在上面的例子中,乙和丙其实是一种联盟关系,先把甲干掉,他们的生存概率都上升了。咱们如今来判断一下,乙和丙之中,谁更有可能背叛,谁更可能忠诚?
任何一个联盟的成员都会时刻权衡利弊,一旦背叛的好处大于忠诚的好处,联盟就会破裂。在乙和丙的联盟中,乙是最忠诚的。这不是由于乙自己具备更加忠诚的品质,而是利益关系使然。只要甲不死,乙的枪口就必定会瞄准甲。但丙就不是这样了,丙不瞄准甲而胡乱开一枪显然违背了联盟关系,丙这样作的结果,将使乙处于更危险的境地。
合做才能对抗强敌。只有乙丙合做,才能把甲先干掉。若是,乙丙不和,乙或丙单独对甲都不占优,必然被甲前后解决。、
1966年经典电影《黄金三镖客》中的最后一幕,三个主人公手持枪杆站在墓地中,为了宝藏随时准备决一雌雄。为了活着拿到宝藏,幸存下来的最优策略是什么呢?
当时,蒙古军事实力最强,金国次之,南宋武力最弱。原本南宋应该和金国结盟,帮助金国抵御蒙古的入侵才是上策,或者至少保持中立。可是,当时的南宋采起了和蒙古结盟的政策。南宋当局先是糊涂地赞成了拖雷借道宋地伐金。1231年,蒙古军队在宋朝的先遣队伍引导下,借道四川等地,北度汉水歼灭了金军有生力量。
1233年,南宋军队与蒙古军队合围蔡州,金朝最后一个皇帝在城破后死于乱兵,金至此灭亡。1279年,南宋正式亡于蒙古。
若是南宋当政者有战略眼光,捐弃前嫌,与世仇金结盟对抗最强大的敌人蒙古,宋和金都不至于那么快就前后灭亡了。
猪圈里面有两只猪, 一只大,一只小。猪圈很长,一头有一个踏板,另外一头是饲料的出口和食槽。每踩一下踏板,在远离踏板的猪圈的另外一边的投食口就会落下少许的食物。若是有一只猪去踩踏板,另外一只猪就有机会抢先吃到另外一边落下的食物。
那么,两只猪各会采起什么策略?使人出乎意料的是,答案竟然是:小猪将选择“搭便车”策略,也就是舒舒服服地等在食槽边;而大猪则为一点残羹不知疲倦地奔忙于踏板和食槽之间。
缘由何在呢?咱们来分析一下,首先这是一个静态不彻底信息博弈:
“智猪博弈”的结论彷佛是,在一个双方公平、公正、合理和共享竞争环境中,有时占优点的一方最终获得的结果却有悖于他的初始理性。这种状况在现实中比比皆是。
好比,在某种新产品刚上市,其性能和功用还不为人所熟识的状况下,若是进行新产品生产的不只是一家小企业,还有其余生产能力和销售能力更强的企业。那么,小企业彻底没有必要做出头鸟,本身去投入大量广告作产品宣传,只要采用跟随战略便可。
“智猪博弈”告诉咱们,谁先去踩这个踏板,就会造福全体,但多劳却并不必定多得。
在现实生活中,不少人都只想付出最小的代价,获得最大的回报,争着作那只不劳而获的小猪。“一个和尚挑水喝,两个和尚抬水喝,三个和尚没水喝”说的正是这样一个道理。这三个和尚都想作“小猪”,却不想付出劳动,不肯承担起“大猪”的义务,最后致使每一个人都没法得到利益。
金融证券市场是一个群体博弈的场所,其真实状况很是复杂。在证券交易中,其结果不只依赖于单个参与者自身的策略和市场条件,也依赖其余人的选择及策略。
在“智猪博弈”的情景中,大猪是占据比较优点的,可是,因为小猪别无选择,使得大猪为了本身能吃到食物,不得不辛勤忙碌,反而让小猪搭了便车,并且比大猪还得意。这个博弈中的关键要素是猪圈的设计, 即踩踏板的成本。
证券投资中也是有这种情形的。例如,当庄家在底位买入大量股票后,已经付出了至关多的资金和时间成本,若是不等价格上升就撤退,就只有接受亏损。
因此,基于和大猪同样的贪吃本能,只要大势不是太糟糕,庄家通常都会抬高股价,以求实现手中股票的增值。这时的中小散户,就能够对该股追加资金,当一只聪明的“小猪”,而让 “大猪”庄家力抬股价。固然,这种股票的发觉并不容易,因此当“小猪”所须要的条件,就是发现有这种状况存在的猪圈,并冲进去。这样,你就成为一只聪明的“小猪”。
股市中,散户投资者与小猪的命运有类似之处,没有能力承担炒做成本,因此就应该充分利用资金灵活、成本低和不怕被套的优点,发现并选择那些机构投资者已经或可能坐庄的股票,等着大猪们为本身服务。
由此看到,散户和机构的博弈中,散户并非总没有优点的,关键是找到有大猪的那个食槽,并等到对本身有利的游戏规则造成时再进入。
GAN的主要灵感来源于博弈论中零和博弈的思想。
应用到深度学习神经网络上来讲,就是经过生成网络G(Generator)和判别网络D(Discriminator)不断博弈,进而使 G 学习到数据的分布,同时时 D 得到更好的鲁棒性和泛化能力。
举个例子:用在图片生成上,咱们想让最后的 G 能够从一段随机数中生成逼真的图像:
上图中:
G是一个生成式的网络,它接收一个随机的噪声 z(随机数),而后经过这个噪声生成图像。
D是一个判别网络,判别一张图片是否是 “真实的”。它的输入是一张图片,输出的 D(x) 表明 x 为真实图片的几率,若是为 1,就表明 100% 是真实的图片,而输出为 0,就表明不多是真实的图片。
那么这个训练的过程是什么样子的呢?在训练中:
G 的目标就是尽可能生成真实的图片去欺骗判别网络 D。
D的目标就是尽可能辨别出G生成的假图像和真实的图像。
这样,G 和 D 就构成了一个动态的“博弈过程”,最终的平衡点即纳什均衡点。
Relevant Link:
https://baijiahao.baidu.com/s?id=1611846467821315306&wfr=spider&for=pc https://www.jianshu.com/p/fadba906f5d3
GAN的起源之做鼻祖是 Ian Goodfellow 在 2014 年发表在 ICLR 的论文:Generative Adversarial Networks”。
按照笔者的理解,提出GAN网络的出发点有以下几个:
为了清楚地阐述这个概念,笔者先从对抗样本这个话题开始提及。
对抗样本(adversarial example)是指通过精心计算获得的用于误导分类器的样本。例以下图就是一个例子,左边是一个熊猫,可是添加了少许随机噪声变成右图后,分类器给出的预测类别倒是长臂猿,但视觉上左右两幅图片并无太大改变。
出现这种状况的缘由是什么呢?
简单来讲,就是预测器发生了过拟合。图像分类器本质上是高维空间的一个复杂的决策函数,在高维空间上,图像分类器过度考虑了全像素区间内的细节信息,致使预测器对图像的细节信息太敏感,微小的扰动就可能致使预测器的预测行为产生很大的变化。
关于这个话题,笔者在另外一篇文章中对过拟合现象以及规避方法进行了详细讨论。
除了添加”随机噪声驱动的像素扰动”这种方法以外,还能够经过图像变形的方式,使得新图像和原始图像视觉上同样的状况下,让分类器获得有很高置信度的错误分类结果。这种过程也被称为对抗攻击(adversarial attack)。
人类经过观察和体验物理世界来学习,咱们的大脑十分擅长预测,不须要显式地通过复杂计算就能够获得正确的答案。监督学习的过程就是学习数据和标签之间的相关关系。
可是在非监督学习中,数据并无被标记,并且目标一般也不是对新数据进行预测。
在现实世界中,标记数据是十分稀有和昂贵的。生成对抗网络经过生成伪造的/合成的数据并尝试判断生成样本真伪的方法学习,这本质上至关于采用了监督学习的方法来作无监督学习。作分类任务的判别器在这里是一个监督学习的组件,生成器的目标是了解真实数据的模样(几率分布),并根据学到的知识生成新的数据。
Relevant Link:
https://www.jiqizhixin.com/articles/2018-03-05-4
GAN网络发展到现在已经有不少的变种,在arxiv上天天都会有大量的新的研究论文被提出。可是笔者这里不许备枚举全部的网络结构,而是仅仅讨论GAN中最核心的思想,经过笔者本身的论文阅读,将我认为最精彩的思想和学术创新提炼出来给你们,从此咱们也能够根据本身的理解,将其余领域的思想交叉引入进来,继续不断创新发展。
经典的GAN网络由两部分组成,分别称之为判别器D和生成器G,两个网络的工做原理能够以下图所示,
D 的目标就是判别真实图片和 G 生成的图片的真假,而 G 是输入一个随机噪声来生成图片,并努力欺骗 D。
简单来讲,GAN 的基本思想就是一个最小最大定理,当两个玩家(D 和 G)彼此竞争时(零和博弈),双方都假设对方采起最优的步骤而本身也以最优的策略应对(最小最大策略),那么结果就会进入一个肯定的均衡状态(纳什均衡)。
生成器网络以随机的噪声z做为输入并试图生成样本数据,并将生成的伪造样本数据提供给判别器网络D,
能够看到,G 网络的训练目标就是让 D(G(z)) 趋近于 1,即彻底骗过判别器(判别器将生成器生成的伪造样本所有误判为真)。G 网络经过接受 D 网络的反馈做为梯度改进方向,经过BP过程反向调整本身的网络结构参数。
判别器网络以真实数据x或者伪造数据G(z)做为输入,并试图预测当前输入是真实数据仍是生成的伪造数据,并产生一个【0,1】范围内的预测标量值。
D 网络的训练目标是区分真假数据,D 网络的训练目标是让 D(x) 趋近于 1(真实的样本判真),而 D(G(z)) 趋近于0(伪造的样本判黑)。D 网络同时接受真实样本和 G 网络传入的伪造样本做为梯度改进方向,,经过BP过程反向调整本身的网络结构参数。
生成器和判别器网络的损失函数结合起来就是生成对抗网络(GAN)的综合损失函数:
两个网络相互对抗,彼此博弈,如上所示,综合损失函数是一个极大极小函数;
整个相互对抗的过程,Ian Goodfellow 在论文中用下图来描述:
黑色曲线表示输入数据 x 的实际分布,绿色曲线表示的是 G 网络生成数据的分布,紫色的曲线表示的是生成数据对应于 D 的分布的差别距离(KL散度)
GAN网络训练的目标是但愿着实际分布曲线x,和G网络生成的数据,两条曲线能够相互重合,也就是两个数据分布一致(达到纳什均衡)。
论文给出的算法实现过程以下所示:
一些细节须要注意:
GAN的巧妙之处在于其目标函数的设定,由于此,GAN有以下几个优势:
Relevant Link:
https://arxiv.org/pdf/1406.2661.pdf https://juejin.im/post/5bdd70886fb9a049f912028d http://www.iterate.site/2018/07/27/gan-%E7%94%9F%E6%88%90%E5%AF%B9%E6%8A%97%E7%BD%91%E7%BB%9C%E4%BB%8B%E7%BB%8D/
在阅读了不少GAN衍生论文以及GAN原始论文以后,笔者一直在思考的一个问题是:GAN背后的底层思想是什么?GAN衍生和改进算法的灵感和思路又是从哪里来的?
通过一段时间思考以及和同行同窗讨论后,我得出了一些思考,这里分享以下,但愿对读者朋友有帮助。
咱们先来看什么是判别模型和生成模型:
从几率论的视角来看,咱们来看一下原始GAN网络的架构:
遵循这种框架进行思考,CGAN只是将v_input中的随机噪声z替换成了另外一种向量(文本或者标签向量),而Pix2pixGAN是将一个图像向量做为v_input输入GAN网络。
GAN的发展离不开goodfellow后来的学者们不断的研究与发展,目前已经提出了不少优秀的新GAN架构,而且这个发展还在继续。为了让本博文能保持必定的环境独立性,笔者这里不作完整的罗列与枚举,相反,笔者但愿从两条脉络来展开讨论:
Alec Radford,Luke Metz,Soumith Chintala等人在“Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks”提出了DCGAN。这是GAN研究的一个重要里程碑,由于它提出了一个重要的架构变化来解决训练不稳定,模式崩溃和内部协变量转换等问题。从那时起,基于DCGAN的架构就被应用到了许多GAN架构。
DCGAN的提出主要是为了解决原始GAN架构的原生架构问题,咱们接下来来讨论下。
生成器从潜在空间中获得100维噪声向量z,经过一系列卷积和上采样操做,将z映射到一个像素矩阵对应的空间中,以下图:
DCGAN经过下面的一些架构性约束来固化网络:
生成器和判别器都是经过binary_crossentropy做为损失函数来进行训练的。以后的每一个阶段,生成器产生一个MNIST图像,判别器尝试在真实MNIST图像和生成图像的数据集中进行学习。
通过一段时间后,生成器就能够自动学会如何制做伪造的数字。
from __future__ import print_function, division from keras.datasets import mnist from keras.layers import Input, Dense, Reshape, Flatten, Dropout from keras.layers import BatchNormalization, Activation, ZeroPadding2D from keras.layers.advanced_activations import LeakyReLU from keras.layers.convolutional import UpSampling2D, Conv2D from keras.models import Sequential, Model from keras.optimizers import Adam import matplotlib.pyplot as plt import sys import numpy as np class DCGAN(): def __init__(self): # Input shape self.img_rows = 28 self.img_cols = 28 self.channels = 1 self.img_shape = (self.img_rows, self.img_cols, self.channels) self.latent_dim = 100 optimizer = Adam(0.0002, 0.5) # Build and compile the discriminator self.discriminator = self.build_discriminator() self.discriminator.compile( loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'] ) # Build the generator self.generator = self.build_generator() # The generator takes noise as input and generates imgs z = Input(shape=(self.latent_dim,)) img = self.generator(z) # For the combined model we will only train the generator self.discriminator.trainable = False # The discriminator takes generated images as input and determines validity valid = self.discriminator(img) # The combined model (stacked generator and discriminator) # Trains the generator to fool the discriminator self.combined = Model(z, valid) self.combined.compile(loss='binary_crossentropy', optimizer=optimizer) def build_generator(self): model = Sequential() model.add(Dense(128 * 7 * 7, activation="relu", input_dim=self.latent_dim)) model.add(Reshape((7, 7, 128))) model.add(UpSampling2D()) model.add(Conv2D(128, kernel_size=3, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) model.add(UpSampling2D()) model.add(Conv2D(64, kernel_size=3, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(Activation("relu")) model.add(Conv2D(self.channels, kernel_size=3, padding="same")) model.add(Activation("tanh")) model.summary() noise = Input(shape=(self.latent_dim,)) img = model(noise) return Model(noise, img) def build_discriminator(self): model = Sequential() model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=self.img_shape, padding="same")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(64, kernel_size=3, strides=2, padding="same")) model.add(ZeroPadding2D(padding=((0,1),(0,1)))) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(128, kernel_size=3, strides=2, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Conv2D(256, kernel_size=3, strides=1, padding="same")) model.add(BatchNormalization(momentum=0.8)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(1, activation='sigmoid')) model.summary() img = Input(shape=self.img_shape) validity = model(img) return Model(img, validity) def train(self, epochs, batch_size=128, save_interval=50): # Load the dataset (X_train, _), (_, _) = mnist.load_data() # Rescale -1 to 1 X_train = X_train / 127.5 - 1. X_train = np.expand_dims(X_train, axis=3) # Adversarial ground truths valid = np.ones((batch_size, 1)) fake = np.zeros((batch_size, 1)) for epoch in range(epochs): # --------------------- # Train Discriminator # --------------------- # Select a random half of images idx = np.random.randint(0, X_train.shape[0], batch_size) imgs = X_train[idx] # Sample noise and generate a batch of new images noise = np.random.normal(0, 1, (batch_size, self.latent_dim)) gen_imgs = self.generator.predict(noise) # Train the discriminator (real classified as ones and generated as zeros) d_loss_real = self.discriminator.train_on_batch(imgs, valid) d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake) d_loss = 0.5 * np.add(d_loss_real, d_loss_fake) # --------------------- # Train Generator # --------------------- # Train the generator (wants discriminator to mistake images as real) g_loss = self.combined.train_on_batch(noise, valid) # Plot the progress print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss)) # If at save interval => save generated image samples if epoch % save_interval == 0: self.save_imgs(epoch) def save_imgs(self, epoch): r, c = 5, 5 noise = np.random.normal(0, 1, (r * c, self.latent_dim)) gen_imgs = self.generator.predict(noise) # Rescale images 0 - 1 gen_imgs = 0.5 * gen_imgs + 0.5 fig, axs = plt.subplots(r, c) cnt = 0 for i in range(r): for j in range(c): axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray') axs[i,j].axis('off') cnt += 1 fig.savefig("images/mnist_%d.png" % epoch) plt.close() if __name__ == '__main__': dcgan = DCGAN() dcgan.train(epochs=4000, batch_size=32, save_interval=50)
DCGAN产生的手写数字输出
CGAN由Mehdi Mirza,Simon Osindero在论文“Conditional Generative Adversarial Nets”中首次提出。
在条件GAN中,生成器并非从一个随机的噪声分布中开始学习,而是经过一个特定的条件或某些特征(例如一个图像标签或者一些文本信息)开始学习如何生成伪造样本。
在CGAN中,生成器和判别器的输入都会增长一些条件变量y,这样判别器D(x,y)和生成器G(z,y)都有了一组联合条件变量。
咱们将CGAN的目标函数和GAN进行对比会发现:
GAN目标函数
CGAN目标函数
GAN和CGAN的损失函数区别在于判别器和生成器多出来一个参数y,架构上,CGAN相比于GAN增长了一个输入层条件向量C,同时链接了判别器和生成器网络。
在训练过程,咱们将y输入给生成器和判别器网络。
from __future__ import print_function, division from keras.datasets import mnist from keras.layers import Input, Dense, Reshape, Flatten, Dropout, multiply from keras.layers import BatchNormalization, Activation, Embedding, ZeroPadding2D from keras.layers.advanced_activations import LeakyReLU from keras.layers.convolutional import UpSampling2D, Conv2D from keras.models import Sequential, Model from keras.optimizers import Adam import matplotlib.pyplot as plt import numpy as np class CGAN(): def __init__(self): # Input shape self.img_rows = 28 self.img_cols = 28 self.channels = 1 self.img_shape = (self.img_rows, self.img_cols, self.channels) self.num_classes = 10 self.latent_dim = 100 optimizer = Adam(0.0002, 0.5) # Build and compile the discriminator self.discriminator = self.build_discriminator() self.discriminator.compile( loss=['binary_crossentropy'], optimizer=optimizer, metrics=['accuracy'] ) # Build the generator self.generator = self.build_generator() # The generator takes noise and the target label as input # and generates the corresponding digit of that label noise = Input(shape=(self.latent_dim,)) label = Input(shape=(1,)) img = self.generator([noise, label]) # For the combined model we will only train the generator self.discriminator.trainable = False # The discriminator takes generated image as input and determines validity # and the label of that image valid = self.discriminator([img, label]) # The combined model (stacked generator and discriminator) # Trains generator to fool discriminator self.combined = Model([noise, label], valid) self.combined.compile(loss=['binary_crossentropy'], optimizer=optimizer) def build_generator(self): model = Sequential() model.add(Dense(256, input_dim=self.latent_dim)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(1024)) model.add(LeakyReLU(alpha=0.2)) model.add(BatchNormalization(momentum=0.8)) model.add(Dense(np.prod(self.img_shape), activation='tanh')) model.add(Reshape(self.img_shape)) model.summary() noise = Input(shape=(self.latent_dim,)) label = Input(shape=(1,), dtype='int32') label_embedding = Flatten()(Embedding(self.num_classes, self.latent_dim)(label)) model_input = multiply([noise, label_embedding]) img = model(model_input) return Model([noise, label], img) def build_discriminator(self): model = Sequential() model.add(Dense(512, input_dim=np.prod(self.img_shape))) model.add(LeakyReLU(alpha=0.2)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.4)) model.add(Dense(512)) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.4)) model.add(Dense(1, activation='sigmoid')) model.summary() img = Input(shape=self.img_shape) label = Input(shape=(1,), dtype='int32') label_embedding = Flatten()(Embedding(self.num_classes, np.prod(self.img_shape))(label)) flat_img = Flatten()(img) model_input = multiply([flat_img, label_embedding]) validity = model(model_input) return Model([img, label], validity) def train(self, epochs, batch_size=128, sample_interval=50): # Load the dataset (X_train, y_train), (_, _) = mnist.load_data() # Configure input X_train = (X_train.astype(np.float32) - 127.5) / 127.5 X_train = np.expand_dims(X_train, axis=3) y_train = y_train.reshape(-1, 1) # Adversarial ground truths valid = np.ones((batch_size, 1)) fake = np.zeros((batch_size, 1)) for epoch in range(epochs): # --------------------- # Train Discriminator # --------------------- # Select a random half batch of images idx = np.random.randint(0, X_train.shape[0], batch_size) imgs, labels = X_train[idx], y_train[idx] # Sample noise as generator input noise = np.random.normal(0, 1, (batch_size, 100)) # Generate a half batch of new images gen_imgs = self.generator.predict([noise, labels]) # Train the discriminator d_loss_real = self.discriminator.train_on_batch([imgs, labels], valid) d_loss_fake = self.discriminator.train_on_batch([gen_imgs, labels], fake) d_loss = 0.5 * np.add(d_loss_real, d_loss_fake) # --------------------- # Train Generator # --------------------- # Condition on labels sampled_labels = np.random.randint(0, 10, batch_size).reshape(-1, 1) # Train the generator g_loss = self.combined.train_on_batch([noise, sampled_labels], valid) # Plot the progress print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss)) # If at save interval => save generated image samples if epoch % sample_interval == 0: self.sample_images(epoch) def sample_images(self, epoch): r, c = 2, 5 noise = np.random.normal(0, 1, (r * c, 100)) sampled_labels = np.arange(0, 10).reshape(-1, 1) gen_imgs = self.generator.predict([noise, sampled_labels]) # Rescale images 0 - 1 gen_imgs = 0.5 * gen_imgs + 0.5 fig, axs = plt.subplots(r, c) cnt = 0 for i in range(r): for j in range(c): axs[i,j].imshow(gen_imgs[cnt,:,:,0], cmap='gray') axs[i,j].set_title("Digit: %d" % sampled_labels[cnt]) axs[i,j].axis('off') cnt += 1 fig.savefig("images/%d.png" % epoch) plt.close() if __name__ == '__main__': cgan = CGAN() cgan.train(epochs=20000, batch_size=32, sample_interval=200)
根据输入数字生成对应的MNIST手写数字图像
CycleGANs 由Jun-Yan Zhu,Taesung Park,Phillip Isola和Alexei A. Efros在题为“Unpaired Image-to-Image Translation using Cycle-Consistent Adversarial Networks”的论文中提出
CycleGAN用来实现不须要其余额外信息,就能将一张图像从源领域映射到目标领域的方法,例如将照片转换为绘画,将夏季拍摄的照片转换为冬季拍摄的照片,或将马的照片转换为斑马照片,或者相反。总结来讲,CycleGAN常备用于不一样的图像到图像翻译。
CycleGAN背后的核心思想是两个转换器F和G,其中:
所以,
和原始的GAN结构相比,由单个G->D的单向开放结构,变成了由两对G<->D组成的双向循环的封闭结构,但形式上依然是G给D输入伪造样本。但区别在于梯度的反馈是双向循环的。
CycleGAN模型有如下两个损失函数:
完整的CycleGAN目标函数以下:
from __future__ import print_function, division import scipy from keras.datasets import mnist from keras_contrib.layers.normalization.instancenormalization import InstanceNormalization from keras.layers import Input, Dense, Reshape, Flatten, Dropout, Concatenate from keras.layers import BatchNormalization, Activation, ZeroPadding2D from keras.layers.advanced_activations import LeakyReLU from keras.layers.convolutional import UpSampling2D, Conv2D from keras.models import Sequential, Model from keras.optimizers import Adam import datetime import matplotlib.pyplot as plt import sys from data_loader import DataLoader import numpy as np import os class CycleGAN(): def __init__(self): # Input shape self.img_rows = 128 self.img_cols = 128 self.channels = 3 self.img_shape = (self.img_rows, self.img_cols, self.channels) # Configure data loader self.dataset_name = 'horse2zebra' self.data_loader = DataLoader( dataset_name=self.dataset_name, img_res=(self.img_rows, self.img_cols) ) # Calculate output shape of D (PatchGAN) patch = int(self.img_rows / 2**4) self.disc_patch = (patch, patch, 1) # Number of filters in the first layer of G and D self.gf = 32 self.df = 64 # Loss weights self.lambda_cycle = 10.0 # Cycle-consistency loss self.lambda_id = 0.1 * self.lambda_cycle # Identity loss optimizer = Adam(0.0002, 0.5) # Build and compile the discriminators self.d_A = self.build_discriminator() self.d_B = self.build_discriminator() self.d_A.compile( loss='mse', optimizer=optimizer, metrics=['accuracy'] ) self.d_B.compile( loss='mse', optimizer=optimizer, metrics=['accuracy'] ) # ------------------------- # Construct Computational # Graph of Generators # ------------------------- # Build the generators self.g_AB = self.build_generator() self.g_BA = self.build_generator() # Input images from both domains img_A = Input(shape=self.img_shape) img_B = Input(shape=self.img_shape) # Translate images to the other domain fake_B = self.g_AB(img_A) fake_A = self.g_BA(img_B) # Translate images back to original domain reconstr_A = self.g_BA(fake_B) reconstr_B = self.g_AB(fake_A) # Identity mapping of images img_A_id = self.g_BA(img_A) img_B_id = self.g_AB(img_B) # For the combined model we will only train the generators self.d_A.trainable = False self.d_B.trainable = False # Discriminators determines validity of translated images valid_A = self.d_A(fake_A) valid_B = self.d_B(fake_B) # Combined model trains generators to fool discriminators self.combined = Model( inputs=[img_A, img_B], outputs=[valid_A, valid_B, reconstr_A, reconstr_B, img_A_id, img_B_id ] ) self.combined.compile( loss=['mse', 'mse', 'mae', 'mae', 'mae', 'mae'], loss_weights=[1, 1, self.lambda_cycle, self.lambda_cycle, self.lambda_id, self.lambda_id], optimizer=optimizer ) def build_generator(self): """U-Net Generator""" def conv2d(layer_input, filters, f_size=4): """Layers used during downsampling""" d = Conv2D(filters, kernel_size=f_size, strides=2, padding='same')(layer_input) d = LeakyReLU(alpha=0.2)(d) d = InstanceNormalization()(d) return d def deconv2d(layer_input, skip_input, filters, f_size=4, dropout_rate=0): """Layers used during upsampling""" u = UpSampling2D(size=2)(layer_input) u = Conv2D(filters, kernel_size=f_size, strides=1, padding='same', activation='relu')(u) if dropout_rate: u = Dropout(dropout_rate)(u) u = InstanceNormalization()(u) u = Concatenate()([u, skip_input]) return u # Image input d0 = Input(shape=self.img_shape) # Downsampling d1 = conv2d(d0, self.gf) d2 = conv2d(d1, self.gf*2) d3 = conv2d(d2, self.gf*4) d4 = conv2d(d3, self.gf*8) # Upsampling u1 = deconv2d(d4, d3, self.gf*4) u2 = deconv2d(u1, d2, self.gf*2) u3 = deconv2d(u2, d1, self.gf) u4 = UpSampling2D(size=2)(u3) output_img = Conv2D(self.channels, kernel_size=4, strides=1, padding='same', activation='tanh')(u4) return Model(d0, output_img) def build_discriminator(self): def d_layer(layer_input, filters, f_size=4, normalization=True): """Discriminator layer""" d = Conv2D(filters, kernel_size=f_size, strides=2, padding='same')(layer_input) d = LeakyReLU(alpha=0.2)(d) if normalization: d = InstanceNormalization()(d) return d img = Input(shape=self.img_shape) d1 = d_layer(img, self.df, normalization=False) d2 = d_layer(d1, self.df*2) d3 = d_layer(d2, self.df*4) d4 = d_layer(d3, self.df*8) validity = Conv2D(1, kernel_size=4, strides=1, padding='same')(d4) return Model(img, validity) def train(self, epochs, batch_size=1, sample_interval=50): start_time = datetime.datetime.now() # Adversarial loss ground truths valid = np.ones((batch_size,) + self.disc_patch) fake = np.zeros((batch_size,) + self.disc_patch) for epoch in range(epochs): for batch_i, (imgs_A, imgs_B) in enumerate(self.data_loader.load_batch(batch_size)): # ---------------------- # Train Discriminators # ---------------------- # Translate images to opposite domain fake_B = self.g_AB.predict(imgs_A) fake_A = self.g_BA.predict(imgs_B) # Train the discriminators (original images = real / translated = Fake) dA_loss_real = self.d_A.train_on_batch(imgs_A, valid) dA_loss_fake = self.d_A.train_on_batch(fake_A, fake) dA_loss = 0.5 * np.add(dA_loss_real, dA_loss_fake) dB_loss_real = self.d_B.train_on_batch(imgs_B, valid) dB_loss_fake = self.d_B.train_on_batch(fake_B, fake) dB_loss = 0.5 * np.add(dB_loss_real, dB_loss_fake) # Total disciminator loss d_loss = 0.5 * np.add(dA_loss, dB_loss) # ------------------ # Train Generators # ------------------ # Train the generators g_loss = self.combined.train_on_batch([imgs_A, imgs_B], [valid, valid, imgs_A, imgs_B, imgs_A, imgs_B]) elapsed_time = datetime.datetime.now() - start_time # Plot the progress print ("[Epoch %d/%d] [Batch %d/%d] [D loss: %f, acc: %3d%%] [G loss: %05f, adv: %05f, recon: %05f, id: %05f] time: %s " \ % ( epoch, epochs, batch_i, self.data_loader.n_batches, d_loss[0], 100*d_loss[1], g_loss[0], np.mean(g_loss[1:3]), np.mean(g_loss[3:5]), np.mean(g_loss[5:6]), elapsed_time)) # If at save interval => save generated image samples if batch_i % sample_interval == 0: self.sample_images(epoch, batch_i) def sample_images(self, epoch, batch_i): if not os.path.exists('images/%s' % self.dataset_name): os.makedirs('images/%s' % self.dataset_name) r, c = 2, 3 imgs_A = self.data_loader.load_data(domain="A", batch_size=1, is_testing=True) imgs_B = self.data_loader.load_data(domain="B", batch_size=1, is_testing=True) # Demo (for GIF) #imgs_A = self.data_loader.load_img('datasets/apple2orange/testA/n07740461_1541.jpg') #imgs_B = self.data_loader.load_img('datasets/apple2orange/testB/n07749192_4241.jpg') # Translate images to the other domain fake_B = self.g_AB.predict(imgs_A) fake_A = self.g_BA.predict(imgs_B) # Translate back to original domain reconstr_A = self.g_BA.predict(fake_B) reconstr_B = self.g_AB.predict(fake_A) gen_imgs = np.concatenate([imgs_A, fake_B, reconstr_A, imgs_B, fake_A, reconstr_B]) # Rescale images 0 - 1 gen_imgs = 0.5 * gen_imgs + 0.5 titles = ['Original', 'Translated', 'Reconstructed'] fig, axs = plt.subplots(r, c) cnt = 0 for i in range(r): for j in range(c): axs[i,j].imshow(gen_imgs[cnt]) axs[i, j].set_title(titles[j]) axs[i,j].axis('off') cnt += 1 fig.savefig("images/%s/%d_%d.png" % (self.dataset_name, epoch, batch_i)) plt.close() if __name__ == '__main__': gan = CycleGAN() gan.train(epochs=200, batch_size=1, sample_interval=200)
苹果->橙子->苹果
有相似架构思想的还有DiscoGAN,相关论文能够在axiv上找到。
StackJANs由Han Zhang,Tao Xu,Hongsheng Li还有其余人在题为“StackGAN: Text to Photo-Realistic Image Synthesis with Stacked Generative Adversarial Networks”的论文中提出。他们使用StackGAN来探索文本到图像的合成,获得了很是好的结果。
一个StackGAN由一对网络组成,当提供文本描述时,能够生成逼真的图像。
pix2pix网络由Phillip Isola,Jun-Yan Zhu,Tinghui Zhou和Alexei A. Efros在他们的题为“Image-to-Image Translation with Conditional Adversarial Networks”的论文中提出。
对于图像到图像的翻译任务,pix2pix也显示出了使人印象深入的结果。不管是将夜间图像转换为白天的图像仍是给黑白图像着色,或者将草图转换为逼真的照片等等,Pix2pix在这些例子中都表现很是出色。
Grigory Antipov,Moez Baccouche和Jean-Luc Dugelay在他们的题为“Face Aging with Conditional Generative Adversarial Networks”的论文中提出了用条件GAN进行面部老化。
面部老化有许多行业用例,包括跨年龄人脸识别,寻找失踪儿童,或者用于娱乐,本质上它属于cGAN的一种场景应用。
Relevant Link:
https://arxiv.org/pdf/1511.06434.pdf https://github.com/hindupuravinash/the-gan-zoo https://github.com/eriklindernoren/Keras-GAN https://zhuanlan.zhihu.com/p/63428113
咱们用DNN架构重写原始GAN代码,并使用一批php webshell做为真实样本,尝试用GAN进行伪造样本生成。
from keras.layers import Input, Dense, Reshape, Flatten, Dropout from keras.layers import BatchNormalization, Activation, ZeroPadding2D from keras.layers.advanced_activations import LeakyReLU from keras.layers.convolutional import UpSampling2D, Conv2D from keras.models import Sequential, Model from keras.optimizers import Adam from keras.preprocessing import sequence from sklearn.externals import joblib import re import os import numpy as np # np.set_printoptions(threshold=np.nan) class DCGAN(): def __init__(self): # Input shape self.charlen = 64 self.fileshape = (self.charlen, ) self.latent_dim = 100 self.ENCODER = joblib.load("./CHAR_SEQUENCE_TOKENIZER_INDEX_TABLE_PICKLE.encoder") self.rerange_dim = (len(self.ENCODER.word_index) + 1) / 2. - 0.5 optimizer = Adam(0.0002, 0.5) # Build and compile the discriminator self.discriminator = self.build_discriminator() self.discriminator.compile( loss='binary_crossentropy', optimizer=optimizer, metrics=['accuracy'] ) # Build the generator self.generator = self.build_generator() # The generator takes noise as input and generates imgs z = Input(shape=(self.latent_dim,)) img = self.generator(z) # For the combined model we will only train the generator self.discriminator.trainable = False # The discriminator takes generated images as input and determines validity valid = self.discriminator(img) # The combined model (stacked generator and discriminator) # Trains the generator to fool the discriminator self.combined = Model(z, valid) self.combined.compile(loss='binary_crossentropy', optimizer=optimizer) def build_generator(self): model = Sequential() model.add(Dense(64, activation="relu")) model.add(Dense(128, activation="relu")) model.add(Dense(256, activation="relu")) model.add(Dense(128, activation="relu")) model.add(Dense(self.charlen, activation="relu")) # model.summary() noise = Input(shape=(self.latent_dim,)) img = model(noise) return Model(noise, img) def build_discriminator(self): model = Sequential() model.add(Dense(128, activation="relu")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.5)) model.add(Dense(256, activation="relu")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.5)) model.add(Dense(512, activation="relu")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.5)) model.add(Dense(128, activation="relu")) model.add(LeakyReLU(alpha=0.2)) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) # model.summary() img = Input(shape=self.fileshape) validity = model(img) return Model(img, validity) def train(self, epochs, batch_size=64, save_interval=50): # Load the dataset X_train = self.load_webfile_data() # Adversarial ground truths valid = np.ones((batch_size, 1)) fake = np.zeros((batch_size, 1)) for epoch in range(epochs): # --------------------- # Train Discriminator # --------------------- # Select a random half of images idx = np.random.randint(0, X_train.shape[0], batch_size) imgs = X_train[idx] # Sample noise and generate a batch of new images noise = np.random.normal(0, 1, (batch_size, self.latent_dim)) gen_imgs = self.generator.predict(noise) # print gen_imgs # print np.shape(gen_imgs) # Train the discriminator (real classified as ones and generated as zeros) d_loss_real = self.discriminator.train_on_batch(imgs, valid) d_loss_fake = self.discriminator.train_on_batch(gen_imgs, fake) d_loss = 0.5 * np.add(d_loss_real, d_loss_fake) # --------------------- # Train Generator # --------------------- # Train the generator (wants discriminator to mistake images as real) g_loss = self.combined.train_on_batch(noise, valid) # Plot the progress print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss)) # If at save interval => save generated image samples if epoch % save_interval == 0: self.save_imgs(epoch) def save_imgs(self, epoch): r, c = 5, 5 noise = np.random.normal(1, 2, (r * c, self.latent_dim)) gen_imgs = self.generator.predict(noise) # Rescale [-1,1] back to [0, ascii_char] range gen_imgs = (gen_imgs + 1.) * self.rerange_dim gen_text_vec = gen_imgs.reshape((np.shape(gen_imgs)[0], self.charlen)) gen_text_vec = gen_text_vec.astype(int) # reconver back to ascii #print "gen_text_vec: ", gen_text_vec gen_text = self.ENCODER.sequences_to_texts(gen_text_vec) #print "gen_text:", gen_text with open('./gen_webfile/{0}.txt'.format(epoch), 'wb') as f: for file_vec in gen_text: fcontent = "" for c in file_vec: fcontent += c fcontent = re.sub(r"\s+", "", fcontent) f.write(fcontent) def load_webfile_data(self): vec_dict = { 'raw_ascii': [] } rootDir = "./webdata" for lists in os.listdir(rootDir): if lists == '.DS_Store': continue webpath = os.path.join(rootDir, lists) with open(webpath, 'r') as fp: fcontent = fp.read() # remove space fcontent = re.sub(r"\s+", " ", fcontent) fcontent_ = "" for c in fcontent: fcontent_ += c + " " vec_dict['raw_ascii'].append(fcontent_) # convert to ascii sequence vec raw_ascii_sequence_vec = self.ENCODER.texts_to_sequences(vec_dict['raw_ascii']) raw_ascii_sequence_vec = sequence.pad_sequences( raw_ascii_sequence_vec, maxlen=self.charlen, padding='post', truncating='post', dtype='float32' ) # reshape to 2d array raw_ascii_sequence_vec = raw_ascii_sequence_vec.reshape((np.shape(raw_ascii_sequence_vec)[0], self.charlen)) # ascii is range in [1, 128], we need Rescale -1 to 1 print "rerange_dim: ", self.rerange_dim raw_ascii_sequence_vec = raw_ascii_sequence_vec / self.rerange_dim - 1. # raw_ascii_sequence_vec = np.expand_dims(raw_ascii_sequence_vec, axis=3) print "np.shape(raw_ascii_sequence_vec): ", np.shape(raw_ascii_sequence_vec) return raw_ascii_sequence_vec if __name__ == '__main__': dcgan = DCGAN() dcgan.train(epochs=8000, batch_size=8, save_interval=20) #print dcgan.load_webfile_data()
实验的结果并不理想,GAN很快遇到了模型坍塌问题,从G生成的样原本看,网络很快陷入了一个局部最优区间中。
关于这个问题,学术界已经有比较多的讨论和分析,笔者这里列举以下:
Sparse reward:adversarial training 没起做用很大的一个缘由就在于,discriminator 提供的 reward 具有的 guide signal 太少,Classifier-based Discriminator 提供的只是一个为真或者假的几率做为 reward,而这个 reward 在大部分状况下,是 0。这是由于对于 CNN 来讲,分出 fake text 和 real text 是很是容易的,CNN 能在 Classification 任务上作到 99% 的 accuracy,而建模 Language Model 来进行生成,是很是困难的。除此之外,即便 generator 在这样的 reward 指导下有一些提高,此后的 reward 依旧很小。
基本上说,学术界对文本的见解是将其是作一个时序依赖的序列,因此主流方向是使用RNN/LSTM这类模型做为生成器来生成伪造文本序列。而接下要要解决的重点问题是,如何有效地将判别器的反馈有效地传递给生成器。
增长reward signal强度和平滑度:从这一点出发,现有很多工做一方法再也不使用简单的 fake/true probability 做为 reward。
LeakyGAN(把 CNN 的 feature 泄露给 generator),RankGAN (用 IR 中的排序做为 reward)等工做来提供更加丰富的 reward;
另外一个解决的思路是使用 language model-based discriminator,以提供更多的区分度,北大孙栩老师组的 DP-GAN 在使用了 Languag model discrminator 以后,在 true data 和 fake data 中间架起了一座桥梁:
离散数据的可导的损失函数:经过改造原始softmax函数,使用新的gumble softmax,它能够代替policy gradient,直接可导了。
policy gradient代替原始gradient,将reward传导回去,这是如今比较主流的作法
Relevant Link:
https://github.com/LantaoYu/SeqGAN https://zhuanlan.zhihu.com/p/25168509 https://tobiaslee.top/2018/09/30/Text-Generation-with-GAN/ https://zhuanlan.zhihu.com/p/36880287 https://www.jianshu.com/p/32e164883eab