最近在做科研上的项目,需要调各种GAN的模型,鉴于网上各种拿着标准数据集跑模型的流氓行为,本人决定推出一种对各种数据集都适用的模型训练教程。
话不多说,先上代码,大家看着我的代码,加上我的讲解,相信所有人都能无痛调节模型的参数。
我用的是github上PyTorch-GAN的代码,这个github实现了很多种类的GAN,并且写出来的模型也不复杂,很适合小白。然后我调的是DCGAN
class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() self.init_size = opt.img_size // 4 self.l1 = nn.Sequential(nn.Linear(opt.latent_dim, 128*self.init_size**2)) self.conv_blocks = nn.Sequential( nn.BatchNorm2d(128), nn.Upsample(scale_factor=2), nn.Conv2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, 0.8), nn.LeakyReLU(0.2, inplace=True), nn.Upsample(scale_factor=2), nn.Conv2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, 0.8), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(64, opt.channels, 3, stride=1, padding=1), nn.Tanh() ) def forward(self, z): out = self.l1(z) out = out.view(out.shape[0], 128, self.init_size, self.init_size) img = self.conv_blocks(out) return img class Discriminator(nn.Module): def __init__(self): super(Discriminator, self).__init__() def discriminator_block(in_filters, out_filters, bn=True): block = [ nn.Conv2d(in_filters, out_filters, 3, 2, 1), nn.LeakyReLU(0.2, inplace=True), nn.Dropout2d(0.25)] if bn: block.append(nn.BatchNorm2d(out_filters, 0.8)) return block self.model = nn.Sequential( *discriminator_block(opt.channels, 16, bn=False), *discriminator_block(16, 32), *discriminator_block(32, 64), *discriminator_block(64, 128), ) # The height and width of downsampled image ds_size = opt.img_size // 2**4 self.adv_layer = nn.Sequential( nn.Linear(128*ds_size**2, 1), nn.Sigmoid()) def forward(self, img): out = self.model(img) out = out.view(out.shape[0], -1) validity = self.adv_layer(out) return validity
上面的代码是DCGAN
最核心的部分。是由两个网络组成:生成网络和判别网络。好了,接下来我一点点的解释这些代码。
class Generator(nn.Module): def __init__(self): super(Generator, self).__init__() self.init_size = opt.img_size // 4 self.l1 = nn.Sequential(nn.Linear(opt.latent_dim, 128*self.init_size**2)) self.conv_blocks = nn.Sequential( nn.BatchNorm2d(128), nn.Upsample(scale_factor=2), nn.Conv2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, 0.8), nn.LeakyReLU(0.2, inplace=True), nn.Upsample(scale_factor=2), nn.Conv2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, 0.8), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(64, opt.channels, 3, stride=1, padding=1), nn.Tanh() ) def forward(self, z): out = self.l1(z) out = out.view(out.shape[0], 128, self.init_size, self.init_size) img = self.conv_blocks(out) return img
看这个生成网络的代码,需要从def forward(self,z):
看起。这个是数据真正被处理的流程。
这个处理的顺序是:
out = self.l1(z)
out = out.view(out.shape[0], 128, self.init_size, self.init_size)
img = self.conv_blocks(out)
return img
第一个语句执行的是l1
函数,这个函数在上面的class
里面定义好了:self.l1 = nn.Sequential(nn.Linear(opt.latent_dim, 128*self.init_size**2))
这个语句的意思就是l1
函数进行的是Linear
变换。这个线性变换的两个参数是变换前的维度,和变换之后的维度。博主建议大家一个学习使用这些函数的方法:如果你使用的是pycharm 就可以选中你想了解的函数,然后按下ctrl + B 就可以跳转到该函数的定义处,一般在这个函数里都会有如何使用的介绍,以及example,非常好用。
那你会问了:这个Linear
函数里面使用的参数是 self.init_size = opt.img_size // 4
,为什么不是opt.img_size
呢,这个就是接下来需要说的一个上采样
.
第二个语句执行的是view()
函数,这个函数很简单,是一个维度变换函数,我们可以看到out
数据变成了四维数据,第一个是batch_size(通过整个的代码,你就可以明白了)
,第二个是channel
,第三,四是单张图片的长宽。
第三个语句执行的是self.conv_blocks(out)
函数,这个函数我们往上看,可以看到:
self.conv_blocks = nn.Sequential( nn.BatchNorm2d(128), nn.Upsample(scale_factor=2), nn.Conv2d(128, 128, 3, stride=1, padding=1), nn.BatchNorm2d(128, 0.8), nn.LeakyReLU(0.2, inplace=True), nn.Upsample(scale_factor=2), nn.Conv2d(128, 64, 3, stride=1, padding=1), nn.BatchNorm2d(64, 0.8), nn.LeakyReLU(0.2, inplace=True), nn.Conv2d(64, opt.channels, 3, stride=1, padding=1), nn.Tanh() )
nn.sequential{}
是一个组成模型的壳子,用来容纳不同的操作。
我们大体可以看到这个壳子里面是由BatchNorm2d
,Upsample
,Conv2d
,LeakyReLU
组成。
第一个是归一化函数对数据的形状没影响主要就是改变数据的量纲。
第二个函数是上采样函数,这个函数会将单张图片的尺寸进行放大(这就是为什么class最先开始将图片的长宽除了4,是因为壳子里面存在两个2倍的上采样函数
)。
第三个函数是二维卷积函数,各个参数分别是输入数据的channel
,输出数据的channel
,剩下的三个参数是卷积的三个参数:卷积步长,卷积核大小,padding的大小。这个二维卷积函数会对channel
的大小有影响,同时还会对单张图片的大小有影响。卷积的计算公式$H_{out} = (H_{in}-1)* S-2*P +K $
第四个函数是一个带有倾斜角度的激活函数,它是由ReLu
函数改造而来的。
好了生成网络就讲完了。我们再来看判别网络:
class Discriminator(nn.Module): def __init__(self): super(Discriminator, self).__init__() def discriminator_block(in_filters, out_filters, bn=True): block = [ nn.Conv2d(in_filters, out_filters, 3, 2, 1), nn.LeakyReLU(0.2, inplace=True), nn.Dropout2d(0.25)] if bn: block.append(nn.BatchNorm2d(out_filters, 0.8)) return block self.model = nn.Sequential( *discriminator_block(opt.channels, 16, bn=False), *discriminator_block(16, 32), *discriminator_block(32, 64), *discriminator_block(64, 128), ) # The height and width of downsampled image ds_size = opt.img_size // 2**4 self.adv_layer = nn.Sequential( nn.Linear(128*ds_size**2, 1), nn.Sigmoid()) def forward(self, img): out = self.model(img) out = out.view(out.shape[0], -1) validity = self.adv_layer(out) return validity
同样地用生成网络看代码的顺序,看这段代码。数据处理的流程分四个步骤:
out = self.model(img)
out = out.view(out.shape[0], -1)
validity = self.adv_layer(out)
return validity
第一个语句执行的是model
函数。好,我们来看model
函数在class
里面是如何定义的。
def discriminator_block(in_filters, out_filters, bn=True): block = [ nn.Conv2d(in_filters, out_filters, 3, 2, 1), nn.LeakyReLU(0.2, inplace=True), nn.Dropout2d(0.25)] if bn: block.append(nn.BatchNorm2d(out_filters, 0.8)) return block self.model = nn.Sequential( *discriminator_block(opt.channels, 16, bn=False), *discriminator_block(16, 32), *discriminator_block(32, 64), *discriminator_block(64, 128), )
model
函数是由四个discriminator_block
函数组成。然后我们再看discriminator_block
函数的定义:
def discriminator_block(in_filters, out_filters, bn=True): block = [ nn.Conv2d(in_filters, out_filters, 3, 2, 1), nn.LeakyReLU(0.2, inplace=True), nn.Dropout2d(0.25)] if bn: block.append(nn.BatchNorm2d(out_filters, 0.8)) return block
这个模块是由四部分组成:conv2d
,leakyRelu
,Dropout
,BatchNorm2d
。
第一个语句:conv2d
函数,是用来卷积的
第二个语句是:'leakyRelu’函数,用来做激活函数的
第三个语句:Dropout
函数用来将部分神经元失活,进而防止过拟合
第四个语句:其实是一个判断语句,如果bn
这个参数为True
,那么就需要在block
块里面添加上BatchNorm
的归一化函数。
第二个语句执行的是view(out.shape[0],-1)
,这个语句是将处理之后的数据维度变成batch * N
的维度形式,然后再放到最后一个语句里面执行。
第三个语句:self.adv_layer
函数,这个函数是由:self.adv_layer = nn.Sequential( nn.Linear(128*ds_size**2, 1),nn.Sigmoid())
就是先进行线性变换,再进行激活函数激活。其中第一个参数128*ds_size**2
中128是指model
模块中最后一个判别模块的最后一个参数决定的,ds_size
是由model
模块对单张图片的卷积效果决定的,而2次方的原因是,整个模型是选取的长宽一致的图片。
有时候我们在脑子里面想的很多设想其实和实际的情况不太一样,比如第一个坑。
Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks
DCGAN论文笔记+源码解析
python中opencv imshow函数显示一片白色原因