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

Posted 秃头小苏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对抗生成网络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生成模型、判别模型设计✨✨✨

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

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

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

 

生成网络模型🧅🧅🧅

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

图1 生成网络模型

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


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


 

判别模型网络🧅🧅🧅

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

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

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

 

DCGAN人脸生成实战✨✨✨

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

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

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

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

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

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


 

数据集加载🧅🧅🧅

​   首先我来说一下数据集的加载,这部分不难,却十分重要。对于我们自己的数据集,我们先用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)),
                           ]))

​   需要强调的是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)

 

生成模型搭建🧅🧅🧅

​   接下来我们来说说生成网络模型的搭建,代码如下:不知道大家有没有发现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)

​   我觉得这个模型搭建步骤大家应该都是较为清楚的,但我当时对这个第一步即从一个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官网对这个函数的解释,如下图所示:

​   其中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]])

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

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

m = torch.nn.Sigmoid()
m(input)

​   输出的结果如下:

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

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

​ 输出结果如下:

大家记住这个值喔!!!

​   上文似乎只是介绍了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)

​  来看看结果叭:

​   你会发现调用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函数即可,具体如下图所示:【在训练部分添加】

​   接下来说说怎么保存训练损失,通过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')

​   代码执行完后,损失保存在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()

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

 

小结

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

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

对抗生成网络GAN系列——GANomaly原理及源码解析

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

🍊往期回顾:对抗生成网络GAN系列——GAN原理及手写数字生成小案例   对抗生成网络GAN系列——DCGAN简介及人脸图像生成案例   对抗生成网络GAN系列——AnoGAN原理及缺陷检测实战   对抗生成网络GAN系列——EGBAD原理及缺陷检测实战

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

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

 

文章目录

对抗生成网络GAN系列——GANomaly原理及源码解析

写在前面

​ 在前面,我已经介绍过好几篇有关GAN的文章,链接如下:

  这篇文章我将来为大家介绍GANomaly,论文名为:Semi-Supervised Anomaly Detection via Adversarial Training。这篇文章同样是实现缺陷检测的,因此在阅读本文之前建议你对使用GAN网络实现缺陷检测有一定的了解,可以参考上文链接中的[4]和[5]。

  准备好了吗,嘟嘟嘟,开始发车。🚖🚖🚖

 

GANomaly原理解析


【阅读此部分前建议对GAN的原理及GAN在缺陷检测上的应用有所了解,详情点击写在前面中的链接查看,本篇文章我不会再介绍GAN的一些先验知识。】


GANomaly结构

​ 这部分为大家介绍GANomaly的原理,其实我们一起来看下图就足够了:

图1 GANomaly结构图

  我们还是先来对上图中的结构做一些解释。从直观的颜色上来看,我们可以分成两类,一类是红色的Encoder结构,一类是蓝色的Decoder结构。Encoder主要就是降维的作用啦,如将一张张图片数据压缩成一个个潜在向量;相反,Decoder就是升维的作用,如将一个个潜在向量重建成一张张图片。按照论文描述的结构来分,可以分成三个子结构,分别为生成器网络G,编码器网络E和判别器网络D。下面分别来介绍介绍这三个子结构:

  • 生成器网络G
      生成器网络G由两个部分组成,分别为编码器 G E ( x ) ) G_E(x)) GE(x))和解码器 G D ( z ) G_D(z) GD(z),其实这就是一个自动编码器结构,主要用来学习输入x的数据分布并重建图像 x ^ \\hat x x^。我们一个个来看,先看 G E ( x ) G_E(x) GE(x)结构,假设我们的输入x维度为 R C × H × W \\mathbbR^C×H×W RC×H×W,经过 G E ( x ) G_E(x) GE(x)结构后,变成一个向量 z z z,其维度为 R d \\mathbbR^d Rd G E ( x ) G_E(x) GE(x)具体结构很简单啦,这里就不详细介绍了。我会在源码解析部分给出,大家肯定一看就会。】接着我们来看 G D ( z ) G_D(z) GD(z)结构,它会将刚刚得到的向量z上采样成 x ^ \\hat x x^ x ^ \\hat x x^的维度和 x x x一致,都为 R C × H × W \\mathbbR^C×H×W RC×H×W。关于 G D ( Z ) G_D(Z) GD(Z)结构也很简单,其主要用到了转置卷积,对于转置卷积不了解的可以看博客[2]了解详情。生成器网络G就为大家介绍完了,是不是发现很简单呢。总结下来就两步,第一步让输入x通过 G E ( x ) G_E(x) GE(x)得到z,第二步让z通过 G D ( Z ) G_D(Z) GD(Z)变成 x ^ \\hat x x^。这两步也可以用一步表示,即 x ^ = G ( x ) \\hat x=G(x) x^=G(x)

      思来想去我还是想在这里给大家抛出一个问题,我们传统的GAN是怎么通过生成器来构建假图像的呢?和GANomaly有区别吗?其实这个问题的答案很简单,大家都稍稍思考一下,我就不给答案了,不明白的评论区见吧!!!🥂🥂🥂

  • 编码器网络E

    ​   编码器网络E的作用是将生成器得到的 x ^ \\hat x x^压缩成一个向量 z ^ \\hat z z^,是不是发现和生成器网络中的 G E ( x ) G_E(x) GE(x)很像呢,其实呀,它俩的结构就是完全一样的,生成的 z ^ \\hat z z^ x ^ \\hat x x^ 的维度一致,这是方便后面的损失比较。

 

  • 判别器网络D

    ​   判别器网络D和我们之前介绍DCGAN时的结构是一样的,都是将真实数据 x x x和生成数据 x ^ \\hat x x^输入网络,然后得出一个分数。

 

GANomaly损失函数

  GANomaly的损失函数分为两部分,第一部分是生成器损失,第二部分为判别器损失,下面我们分别来进行介绍:

  • 生成器损失函数

    ​ 生成器损失函数又由三个部分组成,分别如下:

    • Adversari Loss

      我还是直接上公式吧,如下:

      L a d v = E x ∼ p x ∣ ∣ f ( x ) − E x ∼ p x f ( G ( x ) ) ∣ ∣ 2 L_adv=E_x \\sim px||f(x)-E_x \\sim pxf(G(x))||_2 Ladv=Expxf(x)Expxf(G(x))2

      这个公式对应图一中的 L a d v = ∣ ∣ f ( x ) − f ( x ^ ) ∣ ∣ 2 L_adv=||f(x)-f(\\hat x)||_2 Ladv=f(x)f(x^)2🍵🍵🍵这个损失函数应该很好理解,在前面介绍的GAN网络都有提及, f ( ∗ ) f(*) f()表示判别器网络某个中间层的输出。这个损失函数的作用就是让两张图像 x 和 x ^ x和\\hat x xx^尽可能接近,也就是让生成器生成的图片更加逼真。


    • Contextual Loss

      同样的,直接来上公式,如下:

      L c o n = E x ∼ p x ∣ ∣ x − G ( x ) ∣ ∣ 1 L_con=E_x \\sim px||x-G(x)||_1 Lcon=ExpxxG(x)1

      这个公式对应图一中的 L c o n = ∣ ∣ x − x ^ ∣ ∣ 1 L_con=||x-\\hat x||_1 Lcon=xx^1🍵🍵🍵这个函数其实也是要让两张图像 x 和 x ^ x和\\hat x xx^尽可能接近。至于这里为什么用的是L1范数而不是L2范数,作者在论文中说这里使用L1范数的效果要比使用L2范数的效果好,这属于实验得到的结论,大家也不用过于纠结。

    • Encoder Loss

      话不多说,上公式,如下:

      L e n c = E x ∼ p x ∣ ∣ G E ( x ) − E ( G ( x ) ) ∣ ∣ 2 L_enc=E_x \\sim px||G_E(x)-E(G(x))||_2 Lenc=ExpxGE(x)E(G(x))2

      这个公式对应图一中的 L e n c = ∣ ∣ z − z ^ ∣ ∣ 2 L_enc=||z-\\hat z||_2 Lenc=zz^2🍵🍵🍵这里的损失函数在我看来主要作用就是让我们在推理过程中的效果更好,这里就像AnoGAN中不断搜索最优的那个z的作用。


      如果大家这里读过cycleGAN的论文的话,可能会觉得这个损失函数有点类似cycleGAN中的循环一致性损失。我觉得这篇文章的思想可能借鉴了cycleGAN中的思想,感兴趣的可以去阅读一下,非常有意思的一篇文章!!!🥃🥃🥃



    生成器总的损失是上述三种损失的加权和,如下:
    L = w a d v L a d v + w c o n L c o n + w e n c L e n c L=w_advL_adv+w_conL_con+w_encL_enc 对抗生成网络GAN系列——CycleGAN简介及图片春冬变换案例

    Pytorch Note48 DCGAN生成人脸

    pytorch模型保存加载与续训练

    深度卷积生成对抗网络(DCGAN)简介及图像生成仿真(附代码)

    从零使用GAN(生成对抗网络)进行图像生成

    使用DCGAN实现人脸图像生成