ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

yolov5代码解读-网络架构

2021-10-03 12:59:32  阅读:678  来源: 互联网

标签:支路 yolov5 架构 卷积 解读 yaml BottleneckCSP 模块 concat


目录

前言

之前解读了yolov5的数据处理模块:yolov5代码解读-dataset
数据处理好之后,就来具体看看网络模型是怎么搭建的吧。

网络架构可视化

可视化工具

网络架构可视化工具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的文件
在这里插入图片描述

打开上面那个网页版的natron地址,然后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文件
在这里插入图片描述

5l
在这里插入图片描述

5x
在这里插入图片描述

总结规律就是l是1,往前是深度-1/3, 卷积核个数-1/4,后面是加。
红色这一列就是要压缩(膨胀)的参数,如果是5s,那1保持不变,3和9就被压缩成1和3了。
橙色的部分就是卷积核的个数了,也是要压缩(或者膨胀)的。在这里插入图片描述

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

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种大小不同的头。在这里插入图片描述

对于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三个聚在一起的,我愿称之为CBH。不过这个不是重点,重点是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是怎么实现的,我都没有答出来,现在就具体看一看。
记住了,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

上采样是怎么实现的?上次面试面试官让我不要用人家写好的API,自己写一个上采样。虽然我自己写出来是不太可能,但是人家是咋实现的呢?
上采样有几种不同的实现方法,其中一种是插值的方法,还有反卷积的方法,可以具体看我的前面两篇博文:
“上采样”与“反卷积”图像插值:理论与Python实现
common.py里面并没有单独写上采样,直接用了pytorch的nn.Upsample()

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

标签:支路,yolov5,架构,卷积,解读,yaml,BottleneckCSP,模块,concat
来源: https://blog.csdn.net/m0_50294896/article/details/120593358

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有