前言
打算开坑实现一系列GAN,并基于这些模型对GAN的原理进行深入理解与挖掘。
第一篇是DCGAN。
理论部分
GAN的原理
从图中可以看到,GAN分为两部分,生成器和辨别器。
生成器与辨别器
生成器的目的是利用噪音生成以假乱真的图片,因此,其输入是无意义的噪音,输出是利用该噪音生成的图片。
辨别器的目的是区分出生成器生成的图片与真正的图片,因此,其输入是两种图片,输出是两种图片对应的种类(fake or real)。
生成器是如何利用噪音生成图片的?
我们希望生成的图片可以看作在空间中按照一定概率密度Pd(x)分布的高维张量。
注意这个概率分布不能简单的理解为X∈(长,宽,通道),y∈[0,1]的一个概率分布,我们学习到的是一种更为高维与复杂的分布,它至少包含了一部分像素之间的关系(因为用到了超过1*1的卷积核)。
如果用能够用概率函数Pg(x)拟合出这个高维分布,就能够利用噪音生成所需图片,至于如何拟合有两种思路。
一是自编码器(Auto Encoder)和变分编码器(VAE)的思路,个人认为这种思路的本质就是在做极大似然拟合,也就是说,通过采样,让样本点出现概率最大来调整参数,最终对真正分布进行拟合。李宏毅讲,因为只是根据pd(x)与pg(x)之间的距离进行调整,所以这种做法的问题是生成器比较死板,即使是距离相同的分布,可能实际效果是大不相同的。
二是对抗生成神经网络(GAN)的思路,我们实际训练两个模型:生成器G和判别器D。
在训练D的时候,我们将fake标注为0,real标注为1,损失函数采用二分类交叉熵函数,这样,如果D能够让损失降低,也就说明D能够正确地将fake和real区分开。
在训练G的时候,将fake标注为1,损失函数依然采用二分类交叉熵函数,如果G能够让损失降低,也就是让fake和real都被判断为real,说明G能够正确地用fake伪装real。
实验
DCGAN
DCGAN是第一个可用的GAN,他是在融合了前人的如下成果的基础上提出的
1.使用stride=2的卷积层替代pooling层,让网络自己学习下采样。
2.使用leaky relu替代relu,Relu如果一旦被激活为0,则会永远保持0,因为负数时的梯度永远也为0,leaky relu给了他一个recover的机会。
3.使用dropout和BN层。
4.使用反卷积做生成器的上采样工作,上采样的意思是将size较小的噪声转化为size较大的图片。
模型概览
- class DCGAN():
- self. D
- self. G
- self. DM #辨别器训练模型
- self. AM #生成器训练模型
将DCGAN模型包装成一个类,这不仅是为了逻辑上的简洁性,更重要的是为了让DM和AM训练同一个辨别器(self.D)。
注意这里必须将D,M,DM和AM区分开,换句话说,四者都必须给出独立的接口,因为D在DM和AM中分别参与训练,G需要给出实验结果(fake图片),DM和AM需要分别训练。
辨别器
def discirminator(self):
if self.D:
return self.D
self.D=Sequential()
self.D.add(Conv2D(64,(5,5),strides=2,input_shape=(28,28,1),padding='same',\
activation=LeakyReLU(alpha=0.2)))
#self.D.add()
self.D.add(Dropout(0.4))
self.D.add(Conv2D(128,(5,5),strides=2,padding='same',activation=LeakyReLU(alpha=0.2)))
self.D.add(Dropout(0.4))
self.D.add(Conv2D(256,(5,5),strides=2,padding='same',activation=LeakyReLU(alpha=0.2)))
self.D.add(Dropout(0.25))
self.D.add(Conv2D(512,(5,5),strides=1,padding='same',activation=LeakyReLU(alpha=0.2)))
self.D.add(Dropout(0.4))
self.D.add(Flatten())
self.D.add(Dense(1,activation='sigmoid'))
return self.D
注意事项
1.生成器通过stride=2的卷积层而不是常规的pooling层将28*28多次下采样成4*4
2.每一层的激活函数选择leaky relu
3.激活函数之后加入dropout层
生成器
def generator(self):
if self.G:
return self.G
self.G=Sequential()
#使用UpSamping2D+Conv2D替代TransposedConv2D
self.G.add(Dense(256*7*7,input_dim=100))
self.G.add(BatchNormalization(momentum=0.9))
self.G.add(Activation('relu'))
self.G.add(Reshape((7,7,256)))
self.G.add(UpSampling2D())
self.G.add(Conv2DTranspose(128,5,padding='same'))
self.G.add(BatchNormalization(momentum=0.9))
self.G.add(Activation('relu'))
self.G.add(UpSampling2D())
self.G.add(Conv2DTranspose(64,5,padding='same'))
self.G.add(BatchNormalization(momentum=0.9))
self.G.add(Activation('relu'))
self.G.add(Conv2DTranspose(32,5,padding='same'))
self.G.add(BatchNormalization(momentum=0.9))
self.G.add(Activation('relu'))
self.G.add(Conv2DTranspose(1,5,padding='same'))
self.G.add(Activation('sigmoid'))
#G.summary()
return self.G
注意事项
1.这里我采用了UpSampling层+Conv2DTranpose层做上采样,实际效果与UpSampling+Conv2D类似,因为真正在做上采样的是UpSamping层
2.每层上采样之后加入BN层
辨别器训练模型
def D_model(self):
if self.DM:
return self.DM
self.DM=Sequential()
self.DM.add(self.discirminator())
self.DM.compile(loss='binary_crossentropy',optimizer=RMSprop(lr=0.0002,decay=6e-8),\
metrics=['accuracy'])
return self.DM
生成器训练模型
def A_model(self):
if self.AM:
return self.AM
self.AM=Sequential()
self.AM.add(self.generator())
self.AM.add(self.discirminator())
self.AM.compile(loss='binary_crossentropy',optimizer=RMSprop(lr=0.0001,decay=3e-8),\
metrics=['accuracy'])
return self.AM
生成器与辨别器连接成为生成对抗模型,本质是训练生成器,因此这里可以将辨别器的参数冻结。
但是因为GAN中辨别器一般处于强势,我在训练时没有冻结其参数,不失为一种削弱辨别器的方式。
训练
noisy=gauss_random
fake=G.predict(noisy)
real=mnist_random
X=concatcrate(fake,real)
Y=[0000.....11111]
DM.train(X,Y)
noisy=gauss_random
fake=G.predict(noisy)
Y=[1....1]
AM.train(X,Y)
训练思路已经说过了,优快云突然贴不了代码,打一些伪代码在这,python代码的话可以去GitHub查阅。