最近在做科研上的项目,需要调各种GAN的模型,鉴于网上各种拿着标准数据集跑模型的流氓行为,本人决定推出一种对各种数据集都适用的模型训练教程。

话不多说,先上代码,大家看着我的代码,加上我的讲解,相信所有人都能无痛调节模型的参数。

我用的是github上PyTorch-GAN的代码,这个github实现了很多种类的GAN,并且写出来的模型也不复杂,很适合小白。然后我调的是DCGAN

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最核心的部分。是由两个网络组成:生成网络和判别网络。好了,接下来我一点点的解释这些代码。

Generator生成网络代码

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):看起。这个是数据真正被处理的流程。

这个处理的顺序是:

  1. out = self.l1(z)

  2. out = out.view(out.shape[0], 128, self.init_size, self.init_size)

  3. img = self.conv_blocks(out)

  4. 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{}是一个组成模型的壳子,用来容纳不同的操作。

我们大体可以看到这个壳子里面是由BatchNorm2dUpsampleConv2dLeakyReLU组成。

第一个是归一化函数对数据的形状没影响主要就是改变数据的量纲。

第二个函数是上采样函数,这个函数会将单张图片的尺寸进行放大(这就是为什么class最先开始将图片的长宽除了4,是因为壳子里面存在两个2倍的上采样函数)。

第三个函数是二维卷积函数,各个参数分别是输入数据的channel,输出数据的channel,剩下的三个参数是卷积的三个参数:卷积步长,卷积核大小,padding的大小。这个二维卷积函数会对channel的大小有影响,同时还会对单张图片的大小有影响。卷积的计算公式$H_{out} = (H_{in}-1)* S-2*P +K $

第四个函数是一个带有倾斜角度的激活函数,它是由ReLu函数改造而来的。

好了生成网络就讲完了。我们再来看判别网络:

Discriminator判别网络代码

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

同样地用生成网络看代码的顺序,看这段代码。数据处理的流程分四个步骤:

  1. out = self.model(img)
  2. out = out.view(out.shape[0], -1)
  3. validity = self.adv_layer(out)
  4. 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

这个模块是由四部分组成:conv2dleakyReluDropoutBatchNorm2d

第一个语句: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次方的原因是,整个模型是选取的长宽一致的图片。

避坑指南

有时候我们在脑子里面想的很多设想其实和实际的情况不太一样,比如第一个坑。

  1. 在MATLAB和python里面经常遇到这样的情况,明明读取进去的图片是正常的,出来就是一个白板。这个问题在这个博客《python中opencv imshow函数显示一片白色原因》中解决的很好,会出现这个问题的原因是每个像素的数据类型出现了问题。

参考文献

Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks

DCGAN论文笔记+源码解析

python中opencv imshow函数显示一片白色原因

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐