第12篇 Fast AI深度学习课程——DarkNet、GAN

2024-02-27 00:32

本文主要是介绍第12篇 Fast AI深度学习课程——DarkNet、GAN,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本节课程将介绍很火的对抗生成网络。由于这一网络结构很新,目前(课程发布时,18年4月份)Fast.AI尚未提供相应的封装,因此需要使用Pytorch的数据结构来构建。在构建GAN之前,我们将在CIFAR10数据上,仅使用Pytorch的数据结构,构建结构较简单的Darknet,以展示利用Pytorch搭建网络的思路。

一、Darknet

1. 数据准备

下载后解压。由于train文件夹下各类数据都在一起,要将之按照类别进行移动至相应子文件夹下。

trn_path = PATH/'train/'
cls_path = {}
for cls in classes:os.makedirs(trn_path/cls, exist_ok=True)cls_path[cls] = trn_path/clsfor f in trn_path.glob('*.png'):cls = f.name.split(".")[0].split('_')[-1]if cls == 'automobile': cls = "car"if cls == "airplane": cls = "plane"f.replace(cls_path[cls]/f.name)

由于使用test文件作为验证集,因此也需要按照train文件夹下的结构做整理。

CIFAR数据集中的图片尺寸为32x32,不太适合做旋转变换,因此定义如下变换实现数据修饰:

tfms = tfms_from_stats(stats, sz, aug_tfms=[RandomFlip()], pad=sz//8)
data = ImageClassifierData.from_paths(PATH, val_name='test', tfms=tfms, bs=bs)

其中设置pad=4,使得图片被扩展为40x40,然后进行随机的裁剪,以恢复32x32的尺寸。需要说明的是,在图片扩展时,使用的是对称反射的方式,而非补零扩展,这样效果较好。

2. 网络构建

Darknet的网络结构类似于ResNet,主体是由ResBlock构成。在此,使用模块化构建方法。首先定义一个卷积模块,该模块结构为Conv-Normalize-LeakyReLU

def conv_layer(ni, nf, ks=3, stride=1):return nn.Sequential(nn.Conv2d(ni, nf, kernel_size=ks, bias=False, stride=stride, padding=ks//2),nn.BatchNorm2d(nf, momentum=0.01),nn.LeakyReLU(negative_slope=0.1, inplace=True))

其中LeakyReLU的参数inplace设置为True,可以减少内存(或显存)的开销,又不影响梯度的传递。(如LeakyReLU,仅需根据输出值的正负,即可判定梯度值。与此相同的还有均匀池化层等。而有些需要保留原值来计算梯度值的单元,就不能使用原位操作。)

接下来定义ResBlock模块,其结构如下:

图 1.ResBlock结构

代码如下:

class ResLayer(nn.Module):def __init__(self, ni):super().__init__()self.conv1=conv_layer(ni, ni//2, ks=1)self.conv2=conv_layer(ni//2, ni, ks=3)def forward(self, x): return x.add(self.conv2(self.conv1(x)))

注意其中的两个卷积层构成了BottleNeck形式,即中间特征数减少。

ResBlock的基础上,就可定义Darknet了。Darknet主体由若干层组构成,每个层组又由y一个卷积层和若干ResBlock组成。最终添加一个池化层、一个全连接层进行输出。其结构图如下(哈哈,符号是我瞎画的)。

图 2.Darknet结构图

代码如下:

class Darknet(nn.Module):def make_group_layer(self, ch_in, num_blocks, stride=1):return [conv_layer(ch_in, ch_in*2,stride=stride)] + [(ResLayer(ch_in*2)) for i in range(num_blocks)]def __init__(self, num_blocks, num_classes, nf=32):super().__init__()layers = [conv_layer(3, nf, ks=3, stride=1)]for i,nb in enumerate(num_blocks):layers += self.make_group_layer(nf, nb, stride=2-(i==1))nf *= 2layers += [nn.AdaptiveAvgPool2d(1), Flatten(), nn.Linear(nf, num_classes)]self.layers = nn.Sequential(*layers)def forward(self, x): return self.layers(x)

值得说明的是nn.AdaptiveAvgPool2d()函数,其参数为所输出的特征的尺寸。本例中,单个特征的尺寸由32x32逐渐变化为1x1

3. 损失函数

由网络构建学习器模型,并设置损失函数为交互熵。

lr = 1.3
learn = ConvLearner.from_model_data(m, data)
learn.crit = nn.CrossEntropyLoss()
learn.metrics = [accuracy]
wd=1e-4
%time learn.fit(lr, 1, wds=wd, cycle_len=30, use_clr_beta=(20, 20, 0.95, 0.85))

fit()中的use_clr_beta参数为一个四元组,其中前两个参数的意义与use_clr(参见上一课)相同。后两个参数设置了动量系数的最高最低值。

图 3.use_clr参数

二、GAN

有关生成对抗网络的介绍不再赘述,可参考CS231n课程的相关内容。本部分将关注于WGAN的实现。

  1. 数据集

本部分将在样本图片的基础上,生成卧室场景图片。所用数据集可使用如下代码下载、解压、转换。

curl 'http://lsun.cs.princeton.edu/htbin/download.cgi?tag=latest&category=bedroom&set=train' -o bedroom.zip
unzip bedroom.zip
pip install lmdb
python lsun-data.py {PATH}/bedroom_train_lmdb --out_dir {PATH}/bedroom

其中lsun-data.py为文件夹lsun-scripts下的脚本。

由于原数据集太大,还可使用Kaggle上提供的按20%的比例抽取的样本。
此外,还可使用人脸表情数据集CelebA

  1. 构建网络
  • 判别网络Discriminator
    首先定义卷积模块。

    class ConvBlock(nn.Module):def __init__(self, ni, no, ks, stride, bn=True, pad=None):super().__init__()if pad is None: pad = ks//2//strideself.conv = nn.Conv2d(ni, no, ks, stride, padding=pad, bias=False)self.bn = nn.BatchNorm2d(no) if bn else Noneself.relu = nn.LeakyReLU(0.2, inplace=True)def forward(self, x):x = self.relu(self.conv(x))return self.bn(x) if self.bn else x
    

    注意其中BatchNorm层与ReLU层的顺序,与自定义的DarkNet正好相反,其实也没那么讲究。

    读取图像,然后输出是真是假的分值。因此,其网络结构为:输入图像首先经过一个跨立度为2的卷积,尺寸缩小为源图像的一半(卷积结果的特征维度由输入ndf参数限定);然后经过若干层维持尺寸及特征维度不变的卷积层,再经过一系列跨力度为2的卷积模块,继续缩小尺寸,同时特征维度每次变为上一步的2倍;最终得到尺寸不大于4x4的特征,然后取均值进行输出。

    class DCGAN_D(nn.Module):def __init__(self, isize, nc, ndf, n_extra_layers=0):super().__init__()assert isize % 16 == 0, "isize has to be a multiple of 16"self.initial = ConvBlock(nc, ndf, 4, 2, bn=False)csize,cndf = isize/2,ndfself.extra = nn.Sequential(*[ConvBlock(cndf, cndf, 3, 1)for t in range(n_extra_layers)])pyr_layers = []while csize > 4:pyr_layers.append(ConvBlock(cndf, cndf*2, 4, 2))cndf *= 2; csize /= 2self.pyramid = nn.Sequential(*pyr_layers)self.final = nn.Conv2d(cndf, 1, 4, padding=0, bias=False)def forward(self, input):x = self.initial(input)x = self.extra(x)x = self.pyramid(x)return self.final(x).mean(0).view(1)
    

    其中isize表示image size,为输入图像的尺寸;csize表示conv-size,表示卷积后图像的尺寸。

  • 生成网络Generator
    首先定义转置卷积模块(有关转置卷积的定义可参见CS231n课程的相关内容)。

    class DeconvBlock(nn.Module):def __init__(self, ni, no, ks, stride, pad, bn=True):super().__init__()self.conv = nn.ConvTranspose2d(ni, no, ks, stride, padding=pad, bias=False)self.bn = nn.BatchNorm2d(no)self.relu = nn.ReLU(inplace=True)def forward(self, x):x = self.relu(self.conv(x))return self.bn(x) if self.bn else x
    

    事实上,可能使用nn.Upsample替代nn.ConvTranspose2d会更合适。

    接下来定义生成网络。生成网络接受一个随机向量,输出一幅图像。网络将输入的随机向量视为1x1的特征,然后进行多次转置卷积,将之扩展为NxN3通道数组,以视为图像。生成网络的结构与判别网络的大致相反,代码如下。

    class DCGAN_G(nn.Module):def __init__(self, isize, nz, nc, ngf, n_extra_layers=0):super().__init__()assert isize % 16 == 0, "isize has to be a multiple of 16"cngf, tisize = ngf//2, 4while tisize!=isize: cngf*=2; tisize*=2layers = [DeconvBlock(nz, cngf, 4, 1, 0)]csize, cndf = 4, cngfwhile csize < isize//2:layers.append(DeconvBlock(cngf, cngf//2, 4, 2, 1))cngf //= 2; csize *= 2layers += [DeconvBlock(cngf, cngf, 3, 1, 1) for t in range(n_extra_layers)]layers.append(nn.ConvTranspose2d(cngf, nc, 4, 2, 1, bias=False))self.features = nn.Sequential(*layers)def forward(self, input): return F.tanh(self.features(input))
    

    其中nz表示输入的随机向量的维度。第一个while循环是为了获取合适的特征维度,以保证和判别网络的特征维度相对应。

  • 训练过程
    通用的训练流程是:

    set_trainable_status()
    while(iter_epoch < epochs):while(iter_dl < n_dl):batch = grab_batch()loss = forward(batch)loss.backward()optimizer.step()zero_grad()
    

    而对于GAN,一个额外的流程控制是:在训练过程中,要先训练Discriminator,此时利用总体的损失函数计算梯度值,同时更新DiscriminatorGenerator的参数。然后固定Discriminator(设置Discriminatortrainable状态为假),计算损失函数,计算梯度,然后仅更新Generator的参数。注意,要多训练Discriminator,其原因参见CS231n课程的相关内容。

    另外需要注意的是需要保持GeneratorDiscriminator的系数处于-0.01~0.01区间内,以使得模型可正常工作。

  • 需要说明的问题

    • WGAN的优化器使用的是RMSProp;使用较大的学习速率,或者使用带冲量的优化器,会导致模型训练失效。解释如下:Discriminator的损失函数并不稳定(也是,Generator就是一个天马行空的捣乱的,谁知道它喂给Discriminator的数据是啥样的),如果采取基于动量的优化策略,前后两次的优化方向可能偏差很大,造成优化效果的不稳定;采用较大的学习速率也会如此。

    • 目前并无对GAN网络的进行效果评估的有效手段。比如GAN是否过拟合,GAN是否存在模态坍缩(仅输出极为有限的生成图片),生成的图片是否是数据集中某张图片的复制。

三、Cycle GAN

考虑一下这样的应用场景:如何将一匹马变成斑马?问题的难点在于我们并没有马和斑马的匹配图片作为训练样本。

一个天才的想法是:我们可以训练一个Generator,把某马变为某斑马。为保证变出来的斑马确实像斑马,我们训练一个Discriminator,来区分斑马的真假。为了保证变出的斑马又和原马很相似,我们再将该斑马变为马(这是另一个Generator),并判断新马和原马的差异的大小。如果变出的斑马和原马不像,那么由斑马变出的新马将和原马有很大的差异。将同样的操作应用于把某斑马变为某马的过程。

图 4.Cycle GAN机理
以下为代码说明,大部分代码是从`Cycle GAN`的`Github`库里"借鉴"过来的。
1. 数据加载器

数据下载:
wget https://people.eecs.berkeley.edu/~taesung_park/CycleGAN/datasets/horse2zebra.zip

数据加载器是由CreateDataLoader()函数创建的。其返回一个CustomDatasetDataLoader类,该类派生自BaseDataLoader。而在CustomDatasetDataLoader的初始化函数initialize()中,将使用PytorchDataloader,其需要一个Dataset对象,该对象又是由CreateDataset()函数创建。查看该函数的定义,并结合本例的实际情形,所需的DatasetUnalignedDataset对象。

UnalignedDataset对象的__getitem__()函数中,主要完成的是获取某个索引的图片,做一定的变换,然后将图片返回。

2. 网络模型

网络模型是由create_model()函数创建的,其返回一个CycleGANModel对象。在CycleGANModel的初始化函数中,将定义两个Generator,定义损失函数,定义优化器。

一些有用的链接

  • 课程wiki: 本节课程的一些相关资源,包括课程笔记、课上提到的博客地址等。
  • Wasserstein GAN WGAN论文。
  • Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks: DCGAN,提出GAN的论文。
  • Bedroom数据集的20%抽样。
  • Deconvolution and Checkerboard Artifacts: 一个转置卷积可视化以及讲解棋盘效应的博客。
  • Cycle GAN: Github代码库。
  • MutiModal GAN: 可以一次生成多种图像。

这篇关于第12篇 Fast AI深度学习课程——DarkNet、GAN的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/750662

相关文章

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Python中文件读取操作漏洞深度解析与防护指南

《Python中文件读取操作漏洞深度解析与防护指南》在Web应用开发中,文件操作是最基础也最危险的功能之一,这篇文章将全面剖析Python环境中常见的文件读取漏洞类型,成因及防护方案,感兴趣的小伙伴可... 目录引言一、静态资源处理中的路径穿越漏洞1.1 典型漏洞场景1.2 os.path.join()的陷

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

Spring AI 实现 STDIO和SSE MCP Server的过程详解

《SpringAI实现STDIO和SSEMCPServer的过程详解》STDIO方式是基于进程间通信,MCPClient和MCPServer运行在同一主机,主要用于本地集成、命令行工具等场景... 目录Spring AI 实现 STDIO和SSE MCP Server1.新建Spring Boot项目2.a

Spring Boot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)

《SpringBoot拦截器Interceptor与过滤器Filter深度解析(区别、实现与实战指南)》:本文主要介绍SpringBoot拦截器Interceptor与过滤器Filter深度解析... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实

MyBatis分页插件PageHelper深度解析与实践指南

《MyBatis分页插件PageHelper深度解析与实践指南》在数据库操作中,分页查询是最常见的需求之一,传统的分页方式通常有两种内存分页和SQL分页,MyBatis作为优秀的ORM框架,本身并未提... 目录1. 为什么需要分页插件?2. PageHelper简介3. PageHelper集成与配置3.

Maven 插件配置分层架构深度解析

《Maven插件配置分层架构深度解析》:本文主要介绍Maven插件配置分层架构深度解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Maven 插件配置分层架构深度解析引言:当构建逻辑遇上复杂配置第一章 Maven插件配置的三重境界1.1 插件配置的拓扑

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

Python中__init__方法使用的深度解析

《Python中__init__方法使用的深度解析》在Python的面向对象编程(OOP)体系中,__init__方法如同建造房屋时的奠基仪式——它定义了对象诞生时的初始状态,下面我们就来深入了解下_... 目录一、__init__的基因图谱二、初始化过程的魔法时刻继承链中的初始化顺序self参数的奥秘默认