基于Pytorch对WGAN_gp模型进行调参总结

最近苦恼了很长时间,就因为和GAN刚上了,WGAN是GAN(对抗生成网络)的一种。WGAN(Wasserstein GAN)在训练稳定性上有极大的进步,但是在某些设定下任然存在生成低质量的样本,或者是不能收敛的问题。蒙特利尔大学在WGAN的训练上又有了新的进展。他们的论文的是《Improved Training of Wasserstein GANs》 。研究者们发现失败的案例通常是由在WGAN中使用权重兼职来对critic实施Lipschitz约束导致的。这篇文章中提出的是替代权重剪枝实施Lipschitz约束的方法。

我的环境:Pytorch+ Visual Studio2017


本文主要内容:

  • 卷积与解卷积的计算
  • Pytorch数据准备
  • WGAN的工作原理
  • 注意点

1. 卷积与解卷积的计算

为什么把这个放在最前面。因为我在改模型结构的时候遇到了卷积和解卷积的函数应用,结果开始不太会,一顿乱设置,程序怎么都调不通,然后上谷歌查了好多资料,结果各种资料各种各种坑。因此,我将在注意点 那个章节里面将会讲到一些比较坑的博客,让大家避开这些坑。首先我必须声明Pytorch的文档是最好的,最有用的,最不会让你走入歧途的。

Convolution(卷积)

class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)

这个是Conv2d函数的默认参数。这些默认参数还是比较重要的,对后面的计算有用。
二维卷积输入矩阵维度 N,Cin,H,W ( N , C i n , H , W ) ,输出矩阵维度为 N,Cout,Hout,Wout ( N , C o u t , H o u t , W o u t )

Parameter:

  • N: N : 就是Batch Size
  • C: C : 每个样本的Channel
  • H,W: H , W : 是每个样本的高和宽
  • in_channel: 输入信号的channel
  • out_channel: 卷积之后输出的通道数
  • kernel_size: 卷积核的大小
  • stride:卷积每次移动的步长
  • padding:处理边界时填充0的数量,默认不填充
  • dilation: 采样间隔,默认为1,无间隔采样
  • groups: 输入与输出通道的分组数量
  • bias:为True,则添加偏置

重点:

卷积特征图的尺寸计算公式:

  • Input_size: N,Cin,Hin,Win ( N , C i n , H i n , W i n )
  • Output_size: N,Cout,Hout,Wout ( N , C o u t , H o u t , W o u t )

Hout=floor((Hin+2Padding[0]dilation[0](kernel[0]1)1)/stride[0]+1) H o u t = f l o o r ( ( H i n + 2 ∗ P a d d i n g [ 0 ] − d i l a t i o n [ 0 ] ∗ ( k e r n e l [ 0 ] − 1 ) − 1 ) / s t r i d e [ 0 ] + 1 )

Wout=floor((Win+2Padding[1]dilation[1](kernel[1]1)1)/stride[1]+1) W o u t = f l o o r ( ( W i n + 2 ∗ P a d d i n g [ 1 ] − d i l a t i o n [ 1 ] ∗ ( k e r n e l [ 1 ] − 1 ) − 1 ) / s t r i d e [ 1 ] + 1 )

(其中 floor f l o o r 函数是向下取整)

ConvTranspose (解卷积)

这里特别需要提及一下解卷积(Transposed Convolution)的概念。解卷积又称为反卷积(但是反卷积这个称呼,是有问题的,解卷积并不是直接实现反卷积的作用)。我在做WGAN时,主要是由两个部分组成,一个是生成网络(解卷积的过程),一个是判别网络(卷积过程)。因此在改模型的过程中,就需要根据图片的尺寸,更改相应的卷积参数,这个里面就需要计算每个卷积步骤之后的Tensor大小。
解卷积说白了就是一种上采样的方式,将一个尺寸较小的矩阵->尺寸较大的矩阵。

class torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1)

这个是Conv2d函数的默认参数。这些默认参数还是比较重要的,对后面的计算有用。
二维卷积输入矩阵维度 N,Cin,H,W ( N , C i n , H , W ) ,输出矩阵维度为 N,Cout,Hout,Wout ( N , C o u t , H o u t , W o u t )

Parameter:

  • N: N : 就是Batch Size
  • C: C : 每个样本的Channel
  • H,W: H , W : 是每个样本的高和宽
  • in_channel: 输入信号的channel
  • out_channel: 卷积之后输出的通道数
  • kernel_size: 卷积核的大小
  • stride:卷积每次移动的步长
  • padding:处理边界时填充0的数量,默认不填充
  • dilation: 采样间隔,默认为1,无间隔采样
  • groups: 输入与输出通道的分组数量
  • bias:为True,则添加偏置

重点:

解卷积(转置卷积)特征图的尺寸计算公式:

  • Input_size: N,Cin,H,W ( N , C i n , H , W )
  • Output_size: N,Cout,Hout,Wout ( N , C o u t , H o u t , W o u t )

Hout=(Hin1)stride[0]2Padding[0]+kernel[0]+Paddingoutput[0] H o u t = ( H i n − 1 ) ∗ s t r i d e [ 0 ] − 2 ∗ P a d d i n g [ 0 ] + k e r n e l [ 0 ] + P a d d i n g o u t p u t [ 0 ]

Wout=(Win1)stride[1]2Padding[1]+kernel[1]+Paddingoutput[1] W o u t = ( W i n − 1 ) ∗ s t r i d e [ 1 ] − 2 ∗ P a d d i n g [ 1 ] + k e r n e l [ 1 ] + P a d d i n g o u t p u t [ 1 ]

一些细碎的Pytorch函数以及调Bug技巧

我接触Pytorch时间也不长,各方面知识也是慢慢摸索出来的。开始不太明白Torch的工作原理,看着这些代码也是云里雾里。后来多看几个源代码,就好多了,耐下心肯定有收获。
WGAN的主要两个模型的代码:

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        preprocess = nn.Sequential(
            nn.Linear(128, 4*4*4*DIM),
            nn.ReLU(True),
        )
        block1 = nn.Sequential(
            nn.ConvTranspose2d(4*DIM, 2*DIM, 5),
            nn.ReLU(True),
        )
        block2 = nn.Sequential(
            nn.ConvTranspose2d(2*DIM, DIM, 5),
            nn.ReLU(True),
        )
        deconv_out = nn.ConvTranspose2d(DIM, 1, 8, stride=2)

        self.block1 = block1
        self.block2 = block2
        self.deconv_out = deconv_out
        self.preprocess = preprocess
        self.sigmoid = nn.Sigmoid()

    def forward(self, input):
        output = self.preprocess(input)
        output = output.view(-1, 4*DIM, 4, 4)
        output = self.block1(output)
        output = output[:, :, :7, :7]
        output = self.block2(output)
        output = self.deconv_out(output)
        output = self.sigmoid(output)
        return output.view(-1, OUTPUT_DIM)

这段代码基本的工作顺序是先在def __init__(self):中定义各项处理模块,以及进行初始化。然后在def forward(self,input):中按照计算顺序依次排放好各个计算模块顺序。

Debug Trick:

针对这方面的Debug,我是这样做的。我在forward部分设置了好多print(parameter.size())这样结合之前卷积与解卷积的公式可以迅速算出,运行到每一步的Tensor的大小与下一步中的参数是否匹配。当然结合已经跑通的代码查看每一步Tensor的数据大小也是必要的

还有一个有收获的函数是view函数,这个函数是用来将图片形状进行变换用的。
还有一个收获的函数是numpy.random.normal(loc,scale,size),这个函数是用来产生一个标准正态分布。

2. Pytorch数据准备

这一部分我主要想讲的是Pytorch的数据准备。大家如果刚入门时,总是会碰到有教程先说用MNIST数据集跑跑。说实话当你想深入深度学习的时候,每次看到别人准备好的数据集总有一种特别鸡贼的感觉。为啥?我想用我的数据集实现各种深度学习的目的,但是当你在GitHub上下了一段代码之后,全是这些著名数据集作为输入。这事遇多,你就想吐槽了。相信我。
就我解决问题的过程,我有两套解决这个问题的方案

1)将自己数据集转换为MNIST数据集的形式(模仿制作MNIST)

这是一个很大头的部分,主要原因是刚开始接触GAN也没啥头绪。在网上下载的代码也都是用MNIST数据集运行的。想用自己的数据集遇到了坎。后来经过老师提醒可以把代码中下载MNIST数据集的链接换成自己的,Got it!
首先我找到下载数据集.py 文件
这里写图片描述
这个是下载MNIST数据集的文件。找到这个文件之后,你就会发现数据集就是从下面这四个地址过来的。
这里写图片描述

因此只要将这四个地址换成我们数据集的地址就行。但是问题又来了。怎么制作自己数据集的网址呢,还有怎么制作一个和MNIST数据集一样形式的数据集呢?
咱们一步步来:

  1. 数据集网址的制作:开始我也是一头雾水,尝试了将数据集上传到GitHub上制作成数据集链接,也尝试过将数据集上传到百度云制作成下载链接,但是都不好使,这些下载链接都需要手动下载的操作。因此失败了。后来尝试制作本地的下载链接:file:// + 文件路径 这个方法完美!
  2. 制作模仿MNIST的数据集:这是一个比较头疼的问题。如果要自己整的话,会比较花功夫。在网上找到了这篇博客《模仿MNIST数据集制作自己的数据集》 这篇博客讲的比较详细,也提供了源代码(美中不足的是源代码 需要在CSDN上花积分下载)。还有需要操作的地方就是需要更改原先的文件名字,将之改为0_00001(前面一个0代表label,后面的数字代表文件的序号)。
  3. 文件名称更改:其实这个还是比较简单的。问题的实质就是批量更改数据名,我用的是Python3 + Visual Studio2017。话不多说,上代码:
import os
for file in os.listdir('.'):
    if file[-2:] == 'vs' or file[-2:] =='py':
        continue #过滤掉改名字的文件
    # 每隔180个图片分成一类进行名字的修改   名字的形式0_00001.jpg
    #name = file.replace(' ','')
    label = int(file[:-4]) // 180   
    name =file[:-4]   # 去掉后缀
    new_name =str(label)+'_'+((5-len(name))*'0')+name+'.jpg' # 
    os.rename(file ,new_name)

代码很简单,就是利用listdir将文件夹里面的每个文件名进行遍历(这里说遍历其实不准确,其实应该是文件名里面包含'.'的),然后返回一个包含文件名的list。在这里需要滤掉.py,另外我是在vs上用的Python,因此还需要滤掉'.vs'。当时不知道listdir的含义没有把.vs滤掉,因此被坑了一下。除此之外还学到了Python,字符串转数字的方法以及改名字函数的使用。有机会想写一写使用OpenCV批量修改文件名的代码。
4. 转成.gz文件:生成好的文件是这样的。这里写图片描述
但是MNIST数据集下载下来都是.gz文件的形式,因此还需要将这些文件转成.gz文件(.gz文件是Linux,macOS下比较常见的文件压缩格式,在Windows下想压缩成这个形式需要下载7-zip压缩软件才行)。
最终制作好的文件形式:
这里写图片描述

2)使用Pytorch制作自己的数据集(技术要点:DataLoader)

这个方法是我看多了代码之后,再上网搜搜相关的问题,发现的。主要的使用步骤是来自这篇博客《使用pytorch准备自己的数据》。这篇博客里面讲解了他用他自己的数据集如何进行文件夹设置,如何调用相关函数将自己的数据集进行读取。
基本步骤:

  • 划分文件夹
  • 相关调用函数

    划分文件夹

    基本就是遵从这张图的顺序进行数据集的划分:
    这里写图片描述
    训练集和验证集(当然还可以有测试集)需要分开,每个split下面各个类别的图片也要分开,并且文件夹的名字最好就是类别名称。

    相关调用函数

    主要就是用到了torch.utils.data模块以及torchvision的datasets和transforms模块
    话不多少,上代码:

import torch
import torchvision
from torchvision import datasets, transforms
import matplotlib.pyplot as plt 
import numpy as np
import os

# Data augmentation and normalization for training 
# Just normalization for validation
data_transforms = {
    'train': transforms.Compose([
        transforms.RandomSizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': transforms.Compose([
        transforms.Scale(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

data_dir = '/mount/temp/WZG/pytorch/Data/'

train_sets = datasets.ImageFolder(os.path.join(data_dir, 'train'), data_transforms['train'])
train_loader = torch.utils.data.DataLoader(train_sets, batch_size=10, shuffle=True, num_workers=4)
train_size = len(train_sets)
train_classes = train_sets.classes

val_sets = datasets.ImageFolder(os.path.join(data_dir, 'val'), data_transforms['val'])
val_loader = torch.utils.data.DataLoader(val_sets, batch_size=10, shuffle=False, num_workers=4)
val_size = len(val_sets)


# Visualize a few images
def imshow(inp, title=None):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    plt.imshow(inp)
    if title is not None:
        plt.title(title)


inputs, classes = next(iter(train_loader))

# Make a grid from batch
out = torchvision.utils.make_grid(inputs, nrow=5)

imshow(out, title=[train_classes[x] for x in classes])

特别想说明的是:
transforms.Compose()函数是将所有的变换组合到一起,相当有用的函数,比如说你想将图片大小进行变换,以及想将图片的通道数进行变换都可以将相关的函数包含在其中。
Pytorch里面的tensor和Numpy的ndarray很像(但绝不等价),pytorch的官网在做介绍时,很多时候会和Numpy进行联系和对比,而tensor和ndarray也可以通过调用相关函数进行相互转换。数据转换成tensor后,数值范围会被自动压缩到0~1之间。这份代码中之后还使用transforms.Normalize()函数对数据进行了归一化,该函数包含两个list类型的参数,第一个参数为RGB三个通道各自的均值,第二个参数为相应的方差。
datasets.ImageFolder()函数来创建dataset对象,该函数的第一个参数就是一个路径,第二个参数是对这个路径下的图片进行变换的操作。
torch.utils.data.DataLoader()函数是来定义数据加载方式的,第一个参数是dataset,第二个参数是batch_size,第三个是Shuffle布尔值,用来表示是否打乱数据,第四个参数是num_workers表示开启多少个子进程进行数据读取(并行读取)。

3. WGAN工作原理

本文主要是针对于WGAN_gp(也就是基于梯度惩罚的WGAN网络的模型改进)。我先讲一下WGAN的基本情况,垫一垫。在WGAN中,D的任务不再是尽力区分生成样本于真实样本之间的区别。而是尽力拟合样本之间的Wasserstein之间的距离,从分类任务转换成了回归任务。G的任务则是尽力缩短样本之间的Wasserstein距离。
因此WGAN对原始的GAN做出了以下的改变:

  • D的最后一层取消了sigmoid
  • D的w取值限制在[-c,c]区间内
  • 使用RMSProp或者SGD并以比较低的学习率进行优化
  • 对于三通道图片和单通道图片的结构变化:三通道图片,因为其值分布式连续,所以使用Linear会比较好。但是对于单通道图片(比如MNIST数据集),Linear的效果比较差,可以使用Batch Normaliztion sigmoid效果会更好。
  • 不能在D中使用Batch Normalization(原作者估计weight clip 对Batch Normaliztion的影响)
  • 还有就是使用解卷积来生成图片会比全连接层效果好,全连接层会有比较多的噪点,解卷积效果更佳。
  • Wasserstein距离可以很好衡量WGAN的训练过程,但是也仅限于同一次训练过程。
    以上内容来自PyTorch 实现论文 “Improved Training of Wasserstein GANs” (WGAN-GP) 但是这个博客提供的代码,也很有问题,他用的是python2(讲道理,这就让人很不舒服了,大部分情况下,你是跑不通代码的,所以也算一个大坑吧)

4. 注意点

在改代码的过程中,遇到了挺多坑,想在这儿贴出来,以示后人,这篇博客《关于转置卷积(反卷积)的理解》 是个大坑,博客最后列出来的计算公式根本就是错的,大家需要依据Pytorch中文文档上的公式或者我上面列出来的公式进行计算才能正确。望周知。

  • 14
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值