本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
作者简介:秃头小苏,致力于用最浅显的言语描绘问题
往期回顾:对立生成网络GAN系列——GAN原理及手写数字生成小事例
近期目标:写好专栏的每一篇文章
支持小苏:点赞、保藏⭐、留言
对立生成网络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官方不主张取反卷积的称号,论文中更是说这个叫法是错误的,所以咱们尽量不要去用反卷积这个称号,一起后文我会统一用转置卷积来表述,由于这个叫法最多,我认为也是最恰当的】
⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔ 转置卷积专场⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔
转置卷积理论
这儿我将通过一个小比如来叙述转置卷积的进程,并通过代码来验证这个进程的正确性。首要咱们先来看看转置卷积的进程,如下:
- 在输入特征图元素间填充s-1行、列0(其中s表明转置卷积的步距,留意这儿的步长s和卷积操作中的有些不同)
- 在输入特征图四周填充k-p-1行、列0(其中k表明转置卷积kernel_size巨细,p为转置卷积的padding,留意这儿的padding和卷积操作中的有些不同)
- 将卷积核参数上下、左右翻转
- 做正常卷积运算(padding=0,s=1)
是不是仍是懵逼的状态呢,不用急,现在就通过一个比如来叙述这个进程。首要咱们假定输入特征图的尺度为2*2巨细,s=2,k=3,p=0,如下图所示:
第一步咱们需求在特征图元素间填充s-1=1 行、列 0 (即填充1行0,1列0),改换后特征图如下:
第二步咱们需求在输入特征图四周填充k-p-1=2 行、列0(即填充2行0,2列0),改换后特征图如下:
第三步咱们需求将卷积核上下、左右翻转,得到新的卷积核【卷积核尺度为k=3】,卷积核改变进程如下:
最终一步,咱们做正常的卷积即可【注:拿第二步得到的特征图和第三步翻转后得到的卷积核做正常卷积】,成果如下:
至此咱们就从完成了转置卷积,从一个2*2巨细的特征图变成了一个5*5巨细的特征图,如下图所示(忽略了中间进程):
为了让咱们更直观的感触转置卷积的进程,我从Github
上down了一个此进程动态图供咱们参阅,如下:【注:需求动态图点击☞☞☞自取】
通过上文的叙述,相信你现已对转置卷积的进程比较清楚了。这时候你就能够试试图1中结构,看看应用上述的办法能否得到对应的结构。需求留意的是,在第一次转置卷积时,运用的参数k=4,s=1,p=0
,后边的参数都为k=4,s=2,p=1
,如下图所示:
如果你依照我的进程试了试,或许会发出一些吐槽,这也太麻烦了,我只想核算一下通过转置卷积后特征图的的改变,即知道输入特征图尺度以及k、s、p算出输出特征图尺度,这进程也太复杂了。所以好奇有没有什么公式能够很便利的核算呢?enmmm,我这么说,那必定有嘛,公式如下图所示:
关于上述公式我做3点阐明:
- 在转置卷积的官方文档中,参数还有output_padding 和dilation参数也会影响输出特征图的巨细,但这儿咱们没运用,公式就不加上这俩了,感兴趣的能够自己去阅览一下文档,写的很详细。
- 关于stride[0],stride[1]、padding[0],padding[1]、kernel_size[0],kernel_size[1]该怎样了解?其实啊这些都是卷积的基本常识,这些参数设置时能够设置一个整数或许一个含两个整数的元组,*[0]表明在高度上进行操作,*[1]表明在宽度上进行操作。有关这部分在官方文档上也有写,咱们可自行查看。为便利咱们,我截了一下这部分的图片,如下:
- 这点我带咱们宏观的了解一下这个公式,在传统卷积中,往往卷积核k越小、padding越大,得到的特征图尺度越大;而在转置卷积中,从公式能够看出,卷积核k越大,padding越小,得到的特征图尺度越大,关于这一点相信你也能从前文所述的转置卷积理论部分有所感触。
现在有了这个公式,咱们再去试试叭。
转置卷积实验
接下来我将通过一个小实验验证上面的进程,代码如下:
import torch
import torch.nn as nn
#转置卷积
def transposed_conv_official():
feature_map = torch.as_tensor([[1, 2],
[0, 1]], dtype=torch.float32).reshape([1, 1, 2, 2])
print(feature_map)
trans_conv = nn.ConvTranspose2d(in_channels=1, out_channels=1,
kernel_size=3, stride=2, bias=False)
trans_conv.load_state_dict({"weight": torch.as_tensor([[1, 0, 1],
[1, 1, 0],
[0, 0, 1]], dtype=torch.float32).reshape([1, 1, 3, 3])})
print(trans_conv.weight)
output = trans_conv(feature_map)
print(output)
def transposed_conv_self():
feature_map = torch.as_tensor([[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]], dtype=torch.float32).reshape([1, 1, 7, 7])
print(feature_map)
conv = nn.Conv2d(in_channels=1, out_channels=1,
kernel_size=3, stride=1, bias=False)
conv.load_state_dict({"weight": torch.as_tensor([[1, 0, 0],
[0, 1, 1],
[1, 0, 1]], dtype=torch.float32).reshape([1, 1, 3, 3])})
print(conv.weight)
output = conv(feature_map)
print(output)
def main():
transposed_conv_official()
print("---------------")
transposed_conv_self()
if __name__ == '__main__':
main()
首要咱们先通过transposed_conv_official()
函数来封装一个转置卷积进程,能够看到咱们的输入为[[1,2],[0,1]],卷积核为[[1,0,1],[1,1,0],[0,0,1]],采用k=3,s=2,p=0进行转置卷积【注:这些参数和我前文解说转置卷积进程的用例参数是共同的】,咱们来看一下程序输出的成果:能够发现程序输出和咱们前面理论核算得到的成果是共同的。
接着咱们封装了transposed_conv_self
函数,这个函数界说的是一个正常的卷积,输入是理论第2步得到的特征图,卷积核是第三步翻转后得到的卷积核,通过卷积后输出成果如下:成果和前面的共同。
那么通过这个比如就大致证明了转置卷积的进程确实是咱们理论进程所述。
【呼~~这部分终于讲完了,其实我觉得转置卷积倒是这篇论文很核心的一个常识点,这部分参阅链接如下:参阅视频】
⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔转置卷积专场⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔⚔
判别模型网络
相同的,直接放出判别模型的网络结构图,如下:【注:这部分原论文中没有给出图例,我自己简略画了一个,没有论文中图示美观,但也大致能表明卷积的进程,望咱们见谅】
判别网络真的没什么好讲的,便是传统的卷积操作,对卷积不了解的主张阅览一下我的这篇文章
这儿我给出程序履行的网络模型结构的成果,这部分就结束了:
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,wnw_n应该表明一个权重系数,默许为1【这个是我猜的哈,在官网没看到对这一部分的解说】,yny_n表明标签值,xnx_n表明数据。咱们会对每个batch_size的数据都核算一个lnl_n ,最终求平均或求和。【默许求均值】
看到这儿咱们或许仍是一知半解,不用担心,我举一个小比如咱们就了解了。首要咱们初始化一些输入数据和标签:
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]之间,感兴趣的能够阅览看看】
这个丢失函数讲完练习部分就真没什么可讲的了,哦,这儿得提一下,在核算生成器的丢失时,咱们不是最小化log(1−D(G(Z)))log(1-D(G(Z))) ,而是最大化logD(G(z))logD(G(z)) 。这个在GAN网络论文中也有提及,我上一篇没有阐明这点,这儿说声抱愧,论文中说是这样会更好的收敛,这儿咱们留意一下就好。
番外篇——运用服务器练习怎样保存图片和练习丢失✨✨✨
不知道咱们运行这个代码有没有遇到这样尬尴的境况:
- 无法科学上网,用不了google供给的免费GPU
- 自己电脑没有GPU,这个模型很难跑完
- 有服务器,可是官方供给的代码并没有保存最终生成的图片和丢失,自己又不会改
前两个我无法帮咱们处理,那么我就来说说怎样来保存图片和练习丢失。首要来说说怎样保存图片,这个就很简略啦,就运用一个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等等,敬请期待。
如若文章对你有所协助,那就