yolov5代码解读-网络架构

前言

上一篇:yolov5代码解读-dataset
下一篇:yolov5代码解读-训练
代码已上传到github,数据集和权重文件已上传到百度网盘(链接在github里),如需下载请移步:https://github.com/scc-max/yolov5-scc

数据处理好之后,就来具体看看网络模型是怎么搭建的吧。

网络架构可视化

可视化工具

网络架构可视化工具netron的安装参考:

(1)netron

可以看我之前写的netron安装和使用文章
也可以直接下面的netron网址:
https://lutzroeder.github.io/netron/
点击后直接就可以打开本地模型可视化,比如yolov5s.pt。
在这里插入图片描述

(2)onnx

但是.pt比较简单,细节不够。这时可以再安装一个onnx工具,方法是终端输入:pip install onnx
之后要做的是将pt文件转换成onnx文件。
这个在yolov5中,已经有写好的脚本了,在models下的export.py中。
设置好参数运行以后,会生成一个onnx的文件
在这里插入图片描述

打开上面那个网页版的netron地址,然后load生成的onnx文件在这里插入图片描述

配置文件解读

打开yolov5s.yaml,
nc是类别数量,我做的是矿山提取,只有矿山和背景两个类别。
后面的depth_multiple和width_multiple就体现除了s,m,l,x的区别了。
depth_multiple是指模块走的深度。0.33就是深度的1/3, 也就是所有模块走的次数除以1/3次(最小保留1)。
width_multiple是卷积核的个数,原理同上。在这里插入图片描述

5m的yaml文件
在这里插入图片描述

5x
在这里插入图片描述

5l
在这里插入图片描述

总结规律就是l是1,往前的m,s深度依次减1/3, 卷积核个数减1/4,而x则是加。
红色这一列就是要压缩(膨胀)的参数,如果是5s,那深度(即该模块重复次数)为1的保持不变,3和9就被压缩成1和3了。
橙色的部分就是卷积核的个数了,也是要压缩(或膨胀)的。在这里插入图片描述

上面说了橙色框是卷积核个数,红色框是当前模块执行次数。
第一列,即全是-1的这一列,代表输入层,如果是-1就代表是上一层。而Focus这一列是模块的名字,卷积核个数后面分别是卷积核尺寸和降采样尺寸。在这里插入图片描述

head也是一样,只是连接更复杂了。
在这里插入图片描述
anchors是候选框,每一行是3个候选框,宽高各不相同,为了提取不同比例的物体,如车和行人,一共有3行,代表大小不同,为了提取尺寸不同的物体,对应的是不同的网格大小的head。
这几个候选框的参数都是可以改的,自己加也是可以的,当然原作者设置的这几个参数都是从数据集中按照一定算法提取出来的,更符合日常物体的通用尺寸。在这里插入图片描述

网络层

在这里插入图片描述

看看这图画的!我只能说,大神请收下我的膝盖。之前一直不知道PAN是怎么实现的,什么叫先往上走,再往下走。看看这里的网络结构,不是很清晰吗?

  • Backbone:这一部分不用多说,卷积层+BottleneckCSP,连续4次(后3个被依次拿去做PAN了,最后一个是拿去做上采样,其他两个是concat),最后一次在卷积层和BottleneckCSP中间加了SPP层。这一部分的基础模块是:Conv+BottleneckCSP。模块数:2×4+1=9
  • PAN:先向上走,再向下走,然后同维度的concat起来,经过一个BottleneckCSP模块后送到head层。这一部分也是4个BottleneckCSP(后3个被依次拿去送到head)。这一部分抛去连接,基础模块是:Conv+(Upsample)+Concat+BottleneckCSP。模块数:4×2 + 3×2 = 8 + 6 = 14

网络架构代码

在models中打开yolo.py,查看模型架构代码

yolo.py

model

(1)模型解析
首先是加载yaml文件,用的是yaml.load这个功能加载的。在这里插入图片描述

下一步是定义模型,先判断类别数是不是对,如果代码中输入的类别和yaml不相符,就重写yaml的类别数。parse_model是解析模型,具体如何解析,跳到后面的parse_model去看。在这里插入图片描述

解析完模型后,就得到了Sequential的model。下面这这一段是检测要用的。s是步长,先初始化成128,再根据下面的式子调节。
在这里插入图片描述

dubug看一下stride的值,有3个不同的尺度,对应着3种大小不同的head。在这里插入图片描述

对于anchor的值,它是根据原始的值再除去stride的。在这里插入图片描述

之后是初始化权重在这里插入图片描述

(2)前向传播
这样整个模型的初始化就完成了,后面就是前向传播了,看forward。在forward函数中,因为没有做增强,所以直接走else路线,也就是其实执行的是forward_once这个函数。在这里插入图片描述

下面这段就是实际要走的前向传播函数了。在这里插入图片描述

第一模块m是Focus模块,可以具体查看一下m在这里插入图片描述

看看focus中的参数:
分别对应着f,n,m,args在这里插入图片描述

对于focus,f=-1,在上面代码中,前面的都不会走,直接走到x = m(x)这里了。
那m(x)代表什么呢?m是模块,现在是focus模块,也就是把x传给focus类。执行了foxus里面的forward函数。
当前向传播走到concat模块的时候,f就不是-1了,也就是不是直接连接上一层了,这时上面那个被忽略的支路就要回来了,这里的y是在parse_model时加入的f不为-1(也就是concat预备选手)的张量,然后m.f在这里就是拿出了一个索引,去定位到底是哪一层要和俺一起concat。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

将上一层和第6层做拼接。这是一个大循环套小循环有没有,else里面有嵌套了一个if else。当f是-1的时候,直接就把x拿过来就行,因为x就是上一层传过来的嘛。如果f不是-1,就是去y里面取那一层的输出。在这里插入图片描述

对于122行代码的解释,看下面这段示例就清楚了。如果是-1,就把原本的x直接拿过来,如果是6,就加y[6]。至于为什么前面还有个isinstance,那是因为f可能还有f = 5,这种只有一个整数,不是列表,但是又不是-1的情况。在这里插入图片描述

那么问题来了,y不是空list吗?值哪里来的呢?看这部分最后一行代码:(i忽略)
在这里插入图片描述

y把m.i append进去了。这里注意只有当第i层在save名单里才会添加(废话,只有concat需要的层也放进去,都放进去干嘛),save名单哪里来的呢?在这里插入图片描述

不是直接取-1,也就是前一层的,都会被放进save小本本里,进而append给y。
当m走到detect层的时候,又不一样了,来看看detect层是参数:在这里插入图片描述

这里的17,20,23,对应着yolov5网络结构图中的输出head的那几层在这里插入图片描述

parse_model

先创建了日志
之后是从读取的yaml字典中加载anchor、类别数、深度和卷积核个数两个参数
na是每个网格对应的anchor数量,1/2是因为每个anchor都有宽高两个元素,no就是每个网格对应的输出了。在这里插入图片描述

之后对backbone和head层做遍历,与yaml中对应的信息结合看。f是from,n是number,m是module,args是卷积核参数。对于m,这里第一次遍历取到的是字符串’Focus’,用eval()函数就会直接去执行Focus这个命令。Focus是在common.py中定义的一个类。在这里插入图片描述

执行完m在这里插入图片描述
继续向下走,根据m是什么模块决定怎么执行
第一个if: c1是通道数,c2是卷积核个数(注意要和yaml中的压缩系数处理),args中的参数是卷积核个数和卷积核大小。如果是CSP,还要在list索引为2的位置插入一个重复次数。当模块是concat的时候,输出通道就是c1和c2的sum()了。
总之,这一段主要是根据不同的模块要求修改对应的参数,主要包括输入通道数、输出通道数、模块重复次数。在这里插入图片描述

看看nn.Sequential是怎么添加参数的在这里插入图片描述

再看看卷积模块的参数在这里插入图片描述

那么对于m(*args)就很好理解了。args不就是[input_channel, output_channel, kerna_size]吗?解压后传给m当参数、如果m是conv2d,就和上面是一样的效果。对于重复多次的模块,要添加多次,下面这行代码就是讲的这么个事在这里插入图片描述

执行完之后的m_是在这里插入图片描述

这一段代码是将上面的模块添加进Sequential里,然后添加几个属性,像参数量,index,类型这些。之后再写入日志,打印出来。
第232行为啥要用x % i呢?x是待添加的模块索引,也就是4,6,10,14这些。注意看,它是当模块运行到concat,也就是f是个列表的时候才添加,这是的i是大于x的。i是当前层,x是要取的前面的层。所以这里取余之后还是x,可能使得程序更加健壮了吧。在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这部分代码看着很复杂,其实真正的效果就和那个Sequential是一样的。只是要从yaml文件中取出相关信息,还要根据不同模块修改,显得复杂了点。就是完成了配置文件解析的任务。

common.py

Conv

这个是卷积层,最基础的模块,被后面多次用到在这里插入图片描述

卷积层就是这么朴实无华,一个卷积,一个bn,再加一个hardswish激活函数,我愿称之为CBH。
hardswish激活函数是这样的:在这里插入图片描述

forward给了两个版本
fuse版本:无bn
CBH版本:有bn
好吧,我翻遍整个网络,也没看到使用这个版本。bn后接的都是leaky relu激活函数,并且bn前一般都是经过了concat操作。

Focus

focus不会增加训练精度,但是会提高速度。其被提出的原因是因为原始图像比较大的时候,第一层卷积计算量就比较大,通过focus模块可以减小尺寸,增加深度,降低计算量。在这里插入图片描述

第一个就先看Focus模块,前面经过了一个卷积层,这个卷积层也不是简单的卷积层,到Conv那里去看看,其实有conv2d,bn和hardswish三个聚在一起的。不过这个不是重点,重点是focus模块怎么走的。看看forward前向传播,它返回的是对tensor的一个切片,步长为2。具体操作是这样的,先切片,再拼接,再卷积。
切片:通道数保持不变,第一个切出来的是宽高0开始,步长为2,第二个切的是高(还是宽?无所谓)从1开始,宽从0开始,步长为2。后面类似。
拼接:将切好的部分cat起来,按照通道拼接,之前3通道的输入经过这一步通道就变为了12,但是宽高的尺寸各降一半。通过这个操作就大大较少了后面走卷积的参数量
卷积:将拼接好的tensor送到conv模块进行卷积。在这里插入图片描述

Focus的网络结构:在这里插入图片描述

BottleneckCSP

这一部分是Bottleneck+BottleneckCSP。
可以先看这张图:在这里插入图片描述

在BottleneckCSP模块中,input分成了两条路走,分别是支路1和支路2。走完后再汇合到主路3。

  • 支路2:比较简单,就是一个卷积层(包括conv2d,bn,hardswish, CBH)
  • 支路1:先走了一个1×1的卷积模块,然后走了Bottlebeck模块。可以看图的右边,Bottleneck模块也是分成了两路走,a路先走了一个1×1的卷积,又走了一个3×3的卷积,b路直接就原封不动拿过来了。最后a路的结果和b路的结果相加。走完Bottleneck后,支路1接着走了一个卷积层,和支路2一样的。
  • 拼接:支路1和支路2的结果concat到一起。可以发现,在BottleneckCSP的汇合中,使用的是concat的操作,而在Bottleneck中,直接add到一起的。
  • 主路3:拼接后又走了一个BN和一个Leaky Relu激活函数(这里不是Hardswish激活函数了),直走再走一个卷积层,就完事了。

看代码:y1就是支路1,y2就是支路2,最后concat一起在走bn,leaky relu,卷积层。在这里插入图片描述

Bottleneck层:在这里插入图片描述

SPP

SPP是通过多次最大池化实现的,看看配置文件里的参数:在这里插入图片描述

[5,9,13]就是池化的keinal_size,例如5,代表在5×5这个范围内求得一个最大值。
看一下SPP核心网络结构在这里插入图片描述

其实还是很简单的,4条支路。前3条都做了maxpool,最后一条路直接走过去,然后再拼接。真的是异常简单有没有。
[5,8,13]对应的padding分别是[2,4,6],这是根据那个从输入到输出的公式求得的,目的是为了保持输出尺寸和输入一样。以高举栗子,当p为2,f为5,s为1时,out和h是一样的:
out =(h-f+2p)/s +1
不过别忘了前面还有一个卷积层,所以SPP的总体结构是介个样子的:在这里插入图片描述

前后的卷积都是为了调整维数,尤其是后面那个,当4个256拼接到一起就是1024,通道数太多,再走一个卷积降降维数。
这个是SPP的代码实现,先走了了cv1,也就是一个卷积层。然后是走ModuleList,这里面是池化操作,后面有个for循环,把[5,9,13]都加进去了。这几个池化结果cat,然后走cv2,也是一个卷积层。和上面那个可视化网络是一样的。在这里插入图片描述

看看SPP具体样子:在这里插入图片描述

总的来说,SPP就是:卷积层+不同大小的池化+卷积层
到这里,backbone的几个层就全都说完了。在这里插入图片描述

Upsample

上采样有几种不同的实现方法,其中一种是插值的方法,还有反卷积的方法,可以具体看我的前面两篇博文:
“上采样”与“反卷积”图像插值:理论与Python实现
common.py里面并没有单独写上采样,直接用了pytorch的nn.Upsample()

如果觉得有帮助,请点赞鼓励,蟹蟹~

下一篇:yolov5代码解读-训练

  • 19
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值