• 对抗生成网络GAN系列——DCGAN简介及人脸图像生成案例


    🍊作者简介:秃头小苏,致力于用最通俗的语言描述问题

    🍊往期回顾:对抗生成网络GAN系列——GAN原理及手写数字生成小案例

    🍊近期目标:写好专栏的每一篇文章

    🍊支持小苏:点赞👍🏼、收藏⭐、留言📩

    本节已录制视频:DCGAN简介及人脸图像生成案例🧨🧨🧨

    对抗生成网络GAN系列——DCGAN简介及人脸图像生成案例

    写在前面

    ​  前段时间,我已经写过一篇关于GAN的理论讲解,并且结合理论做了一个手写数字生成的小案例,对GAN原理不清楚的可以点击☞☞☞跳转了解详情。🌱🌱🌱

    ​   为唤醒大家的记忆,这里我再来用一句话对GAN的原理进行总结:GAN网络即是通过生成器和判别器的不断相互对抗,不断优化,直到判别器难以判断生成器生成图像的真假。

    ​   那么接下来我就要开始讲述DCGAN了喔,读到这里我就默认大家对GAN的原理已经掌握了,开始发车。🚖🚖🚖

     

    DCGAN重点知识把握

    DCGAN简介

    ​   我们先来看一下DCGAN的全称——Deep Convolutional Genrative Adversarial Networks。这大家应该都能看懂叭,就是说这次我们将生成对抗网络和深度学习结合到一块儿了,现在看这篇文章的一些观点其实觉得是很平常的,没有特别出彩之处,但是这篇文章是在16年发布的,在当时能提出一些思想确实是难得。

    ​   其实呢,这篇文章的原理和GAN基本是一样的。不同之处只在生成网络模型和判别网络模型的搭建上,因为这篇文章结合了深度学习嘛,所以在模型搭建中使用了卷积操作【注:在上一篇GAN网络模型搭建中我们只使用的全连接层】。介于此,我不会再介绍DCGAN的原理,重点将放在DCGAN网络模型的搭建上。【注:这样看来DCGAN就很简单了,确实也是这样的。但是大家也不要掉以轻心喔,这里还是有一些细节的,我也是花了很长的时间来阅读文档和做实验来理解的,觉得理解差不多了,才来写了这篇文章。】

    ​   那么接下来就来讲讲DCGAN生成模型和判别模型的设计,跟我一起来看看叭!!!

     

    DCGAN生成模型、判别模型设计✨✨✨

    ​   在具体到生成模型和判别模型的设计前,我们先来看论文中给出的一段话,如下图所示:

    image-20220722151528431

    ​   这里我还是翻译一下,如下图所示:

    image-20220722153212125

    ​   上图给出了设计生成模型和判别模型的基本准则,后文我们搭建模型时也是严格按照这个来的。【注意上图黄色背景的分数卷积喔,后文会详细叙述】

     

    生成网络模型🧅🧅🧅

    ​   话不多说,直接放论文中生成网络结构图,如下:

    image-20220722154308221

    图1 生成网络模型

    ​   看到这张图不知道大家是否有几秒的迟疑,反正我当时是这样的,这个结构给人一种熟悉的感觉,但又觉得非常的陌生。好了,不卖关子了,我们一般看到的卷积结构都是特征图的尺寸越来越小,是一个下采样的过程;而这个结构特征图的尺寸越来越大,是一个上采样的过程。那么这个上采样是怎么实现的呢,这就要说到主角分数卷积了。【又可以叫转置卷积(transposed convolution)和反卷积(deconvolution),但是pytorch官方不建议取反卷积的名称,论文中更是说这个叫法是错误的,所以我们尽量不要去用反卷积这个名称,同时后文我会统一用转置卷积来表述,因为这个叫法最多,我认为也是最贴切的】


    关于转置卷积的理论可以参考我的这篇博文:转置卷积详解(原理+实验)🥨🥨🥨


     

    判别模型网络🧅🧅🧅

    ​   同样的,直接放出判别模型的网络结构图,如下:【注:这部分原论文中没有给出图例,我自己简单画了一个,没有论文中图示美观,但也大致能表示卷积的过程,望大家见谅】

    ​   判别网络真的没什么好讲的,就是传统的卷积操作,对卷积不了解的建议阅读一下我的这篇文章🧨🧨🧨

    ​   这里我给出程序执行的网络模型结构的结果,这部分就结束了:

    image-20220722221744353

     

    DCGAN人脸生成实战✨✨✨

    ​   这部分我们将来实现一个人脸生成的实战项目,我们先来看一下人脸一步步生成的动画效果,如下图所示:

    在这里插入图片描述

    ​  我们可以看到随着迭代次数增加,人脸生成的效果是越来越好的,说句不怎么恰当的话,最后生成的图片是像个人的。看到这里,是不是都兴致勃勃了呢,下面就让我们一起来学学叭。🏆🏆🏆

    ​  秉持着授人以鱼不如授人以渔的原则,这里我就不带大家一句一句的分析代码了,都是比较简单的,官方文档写的也非常详细,我再叙述一篇也没有什么意义。哦,对了,这部分代码参考的是pytorch官网上DCGAN的教程,链接如下:DCGAN实战教程🎈🎈🎈

    ​   我来简单介绍一下官方教程的使用,点击上文链接会进入下图的界面:这个界面正常滑动就是对这个项目的解释,包括原理、代码及代码运行结果,大家首先要做的应该是阅读一遍这个文档,基本可以解决大部分的问题。那么接下来对于不明白的就可以点击下图中绿框链接修改一些代码来调试我们不懂的问题,这样基本就都会明白了。【框1是google提供的一个免费的GPU运算平台,就类似是云端的jupyter notebook ,但这个需要梯子,大家自备;框2 是下载notebook到本地;框3是项目的Github地址】

    image-20220722235309784

    ​   那方法都教给大家了,大家快去试试叭!!!

    ​   作为一个负责的博主👨‍🦳👨‍🦳👨‍🦳,当然不会就甩一个链接就走人啦,下面我会帮助大家排查一下代码中的一些难点,大家看完官方文档后如果有不明白的记得回来看看喔。🥂🥂🥂当然,如果有什么不理解的地方且我下文没有提及欢迎评论区讨论交流。🛠🛠🛠


     

    数据集加载🧅🧅🧅

    ​   首先我来说一下数据集的加载,这部分不难,却十分重要。对于我们自己的数据集,我们先用ImageFolder方法创建dataset,代码如下:

    # Create the dataset
    dataset = dset.ImageFolder(root=dataroot,
                               transform=transforms.Compose([
                                   transforms.Resize(image_size),
                                   transforms.CenterCrop(image_size),
                                   transforms.ToTensor(),
                                   transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                               ]))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    ​   需要强调的是root=dataroot表示我们自己数据集的路径,在这个路径下必须还有一个子目录。怎么理解呢,我举个例子。比如我现在有一个人脸图片数据集,其存放在文件夹2下面,我们不能将root的路径指定为文件夹2,而是将文件夹2放入一个新文件夹1里面,root的路径指定为文件夹1。

    ​   对于上面代码的transforms操作做一个简要的概括,transforms.Resize将图片尺寸进行缩放、transforms.CenterCrop对图片进行中心裁剪、transforms.ToTensor、transforms.Normalize最终会将图片数据归一化到[-1,1]之间,这部分不懂的可以参考我的这篇博文:pytorch中的transforms.ToTensor和transforms.Normalize理解🍚🍚🍚

    ​   有了dataset后,就可以通过DataLoader方法来加载数据集了,代码如下:

    # Create the dataloader
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
                                             shuffle=True, num_workers=workers)
    
    • 1
    • 2
    • 3

     

    生成模型搭建🧅🧅🧅

    ​   接下来我们来说说生成网络模型的搭建,代码如下:不知道大家有没有发现pytorch官网此部分搭建的网络模型和论文中给出的是有一点差别的,这里我修改成了和论文中一样的模型,从训练效果来看,两者差别是不大的。【注:下面代码是我修改过的】

    # Generator Code
    
    class Generator(nn.Module):
        def __init__(self, ngpu):
            super(Generator, self).__init__()
            self.ngpu = ngpu
            self.main = nn.Sequential(
                # input is Z, going into a convolution
                nn.ConvTranspose2d( nz, ngf * 16, 4, 1, 0, bias=False),
                nn.BatchNorm2d(ngf * 16),
                nn.ReLU(True),
                # state size. (ngf*16) x 4 x 4
                nn.ConvTranspose2d(ngf * 16, ngf * 8, 4, 2, 1, bias=False),
                nn.BatchNorm2d(ngf * 8),
                nn.ReLU(True),
                # state size. (ngf*8) x 8 x 8
                nn.ConvTranspose2d( ngf * 8, ngf * 4, 4, 2, 1, bias=False),
                nn.BatchNorm2d(ngf * 4),
                nn.ReLU(True),
                # state size. (ngf*4) x 16 x 16
                nn.ConvTranspose2d( ngf * 4, ngf * 2, 4, 2, 1, bias=False),
                nn.BatchNorm2d(ngf * 2),
                nn.ReLU(True),
                # state size. (ngf * 2) x 32 x 32
                nn.ConvTranspose2d( ngf * 2, nc, 4, 2, 1, bias=False),
                nn.Tanh()
                # state size. (nc) x 64 x 64
            )
    
        def forward(self, input):
            return self.main(input)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    ​   我觉得这个模型搭建步骤大家应该都是较为清楚的,但我当时对这个第一步即从一个100维的噪声向量如何变成变成一个1024*4*4的特征图还是比较疑惑的。这里就为大家解答一下,我们可以看看在训练过程中传入的噪声代码,即输入为: noise = torch.randn(b_size, nz, 1, 1, device=device),这是一个100*1*1的特征图,这样是不是一下子恍然大悟了呢,那我们的第一步也就是从100*1*1的特征图经转置卷积变成1024*4*4的特征图。


     

    模型训练🧅🧅🧅

    ​   这部分我在上一篇GAN网络讲解中已经介绍过,但是我没有细讲,这里我想重点讲一下BCELOSS损失函数。【就是二值交叉熵损失函数啦】我们先来看一下pytorch官网对这个函数的解释,如下图所示:

    image-20220723142323032

    ​   其中N表示batch_size, w n w_n wn应该表示一个权重系数,默认为1【这个是我猜的哈,在官网没看到对这一部分的解释】, y n y_n yn表示标签值, x n x_n xn表示数据。我们会对每个batch_size的数据都计算一个 l n l_n ln ,最后求平均或求和。【默认求均值】

    ​   看到这里大家可能还是一知半解,不用担心,我举一个小例子大家就明白了。首先我们初始化一些输入数据和标签:

    import torch
    import math
    input = torch.randn(3,3)
    target = torch.FloatTensor([[0, 1, 1], [1, 1, 0], [0, 0, 0]])
    
    • 1
    • 2
    • 3
    • 4

    ​   来看看输入数据和标签的结果:

    image-20220723144544905

    ​   接着我们要让输入数据经过Sigmoid函数将其归一化到[0,1]之间【BCELOSS函数要求】:

    m = torch.nn.Sigmoid()
    m(input)
    
    • 1
    • 2

    ​   输出的结果如下:

    image-20220723145022493

    ​   最后我们就可以使用BCELOSS函数计算输入数据和标签的损失了:

    loss =torch.nn.BCELoss()
    loss(m(input), target)
    
    • 1
    • 2

    ​ 输出结果如下:

    大家记住这个值喔!!!

    ​   上文似乎只是介绍了BCELOSS怎么用,具体怎么算的好像并不清楚,下面我们就根据官方给的公式来一步一步手动计算这个损失,看看结果和调用函数是否一致,如下:

    r11 = 0 * math.log(0.8172) + (1-0) * math.log(1-0.8172)
    r12 = 1 * math.log(0.8648) + (1-1) * math.log(1-0.8648)
    r13 = 1 * math.log(0.4122) + (1-1) * math.log(1-0.4122)
    
    r21 = 1 * math.log(0.3266) + (1-1) * math.log(1-0.3266)
    r22 = 1 * math.log(0.6902) + (1-1) * math.log(1-0.6902)
    r23 = 0 * math.log(0.5620) + (1-0) * math.log(1-0.5620)
    
    r31 = 0 * math.log(0.2024) + (1-0) * math.log(1-0.2024)
    r32 = 0 * math.log(0.2884) + (1-0) * math.log(1-0.2884)
    r33 = 0 * math.log(0.5554) + (1-0) * math.log(1-0.5554)
    
    BCELOSS = -(1/9) * (r11 + r12+ r13 + r21 + r22 + r23 + r31 + r32 + r33)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ​  来看看结果叭:

    image-20220723145941661

    ​   你会发现调用BCELOSS函数和手动计算的结果是一致的,只是精度上有差别,这说明我们前面所说的理论公式是正确的。【注:官方还提供了一种函数——BCEWithLogitsLoss,其和BCELOSS大致一样,只是对输入的数据不需要再调用Sigmoid函数将其归一化到[0,1]之间,感兴趣的可以阅读看看】

    ​   这个损失函数讲完训练部分就真没什么可讲的了,哦,这里得提一下,在计算生成器的损失时,我们不是最小化 l o g ( 1 − D ( G ( Z ) ) ) log(1-D(G(Z))) log(1D(G(Z))) ,而是最大化 l o g D ( G ( z ) ) logD(G(z)) logD(G(z)) 。这个在GAN网络论文中也有提及,我上一篇没有说明这点,这里说声抱歉,论文中说是这样会更好的收敛,这里大家注意一下就好。

     

    番外篇——使用服务器训练如何保存图片和训练损失✨✨✨

    ​  不知道大家运行这个代码有没有遇到这样尬尴的处境:

    1. 无法科学上网,用不了google提供的免费GPU
    2. 自己电脑没有GPU,这个模型很难跑完
    3. 有服务器,但是官方提供的代码并没有保存最后生成的图片和损失,自己又不会改

    ​   前两个我没法帮大家解决,那么我就来说说怎么来保存图片和训练损失。首先来说说怎么保存图片,这个就很简单啦,就使用一个save_image函数即可,具体如下图所示:【在训练部分添加】

    image-20220723162639573

    ​   接下来说说怎么保存训练损失,通过torch.save()方法保存代码如下:

    #保存LOSS
    G_losses = torch.tensor(G_losses)
    D_losses = torch.tensor(D_losses)
    torch.save(G_losses, 'LOSS\\GL')
    torch.save(D_losses, 'LOSS\\DL')
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ​   代码执行完后,损失保存在LOSS文件夹下,一个文件为GL,一个为DL。这时候我们需要创建一个.py文件来加载损失并可视化,.py文件内容如下:

    import torch
    import torch.utils.data
    import matplotlib.pyplot as plt
    
    
    #绘制LOSS曲线
    G_losses = torch.load('F:\\老师发放论文\\经典网络模型\\GAN系列\\DCGAN\\LOSS\\GL')
    D_losses = torch.load('F:\\老师发放论文\\经典网络模型\\GAN系列\\DCGAN\\LOSS\\DL')
    
    plt.figure(figsize=(10,5))
    plt.title("Generator and Discriminator Loss During Training")
    plt.plot(G_losses,label="G")
    plt.plot(D_losses,label="D")
    plt.xlabel("iterations")
    plt.ylabel("Loss")
    plt.legend()
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ​  最后来看看保存的图片和损失,如下图所示:
    image_4_3165

    image-20220723163500724

     

    小结

    ​   至此,DCGAN就全部讲完啦,希望大家都能有所收获。有什么问题欢迎评论区讨论交流!!!GAN系列近期还会出cycleGAN的讲解和四季风格转换的demo,后期会考虑出瑕疵检测方面的GAN网络,如AnoGAN等等,敬请期待。🏵🏵🏵

    如若文章对你有所帮助,那就🛴🛴🛴

    在这里插入图片描述

  • 相关阅读:
    在react+typescript中使用echarts
    C++:AVL树
    杨玉基:知识图谱在美团推荐场景中的应用
    Spring Security根据角色在登录后重定向用户
    各种信息论坛
    用whl文件安装Anaconda中的GDAL
    爬虫——爬虫初识、requests模块
    JavaScript 以追加的方式写入xlsx
    产品经理的七大定律的总结
    品牌公关稿件怎么写?纯干货
  • 原文地址:https://blog.csdn.net/qq_47233366/article/details/127260217