YOLOV3解读(3)
网络分析
DBL: 如图1左下角所示,也就是代码中的Darknetconv2d_BN_Leaky,是yolo_v3的基本组件。就是卷积+BN+Leaky relu。对于v3来说,BN和leaky relu(正则化和激励)已经是和卷积层不可分离的部分了(最后一层卷积除外),共同构成了最小组件。
resn:n代表数字,有res1,res2, … ,res8等等,表示这个res_block里含有多少个res_unit。这是yolo_v3的大组件,yolo_v3开始借鉴了ResNet的残差结构,使用这种结构可以让网络结构更深(从v2的darknet-19上升到v3的darknet-53,前者没有残差结构)。对于res_block的解释,可以在图1的右下角直观看到,其基本组件也是DBL。
concat:张量拼接。将darknet中间层和后面的某一层的上采样进行拼接。拼接的操作和残差层add的操作是不一样的,拼接会扩充张量的维度,而add只是直接相加不会导致张量维度的改变。
我们可以借鉴netron来分析网络层,整个yolo_v3_body包含252层,组成如下:
结合代码分析
在模型中,通过传入输入层image_input、每层的anchor数num_anchors//3和类别数num_classes,调用yolo_body()方法,构建YOLO v3的网络model_body。其中,image_input的结构是(?, 416, 416, 3)。
model_body = yolo_body(image_input, num_anchors // 3, num_classes) # model
在model_body中,最终的输入是image_input,最终的输出是3个矩阵的列表:
[(?, 13, 13, 18), (?, 26, 26, 18), (?, 52, 52, 18)]
y1,y2和y3的深度都是255,边长的规律是13:26:52
对于COCO类别而言,有80个种类,所以每个box应该对每个种类都输出一个概率。
yolo v3设定的是每个网格单元预测3个box,所以每个box需要有(x, y, w, h, confidence)五个基本参数,然后还要有80个类别的概率。所以3(5 + 80) = 255。这个255就是这么来的。*
YOLO v3的基础网络是DarkNet网络,将DarkNet网络中底层和中层的特征矩阵,通过卷积操作和多个矩阵的拼接操作,创建3个尺度的输出,即[y1, y2, y3]。
def yolo_body(inputs, num_anchors, num_classes):darknet = Model(inputs, darknet_body(inputs))#这里的Model是在输出定义之前的darknet53网络模型,得到了模型输出darknetx, y1 = make_last_layers(darknet.output, 512, num_anchors * (num_classes + 5))#make_last_layers是在程序中定义的x = compose(DarknetConv2D_BN_Leaky(256, (1, 1)),UpSampling2D(2))(x)#对x进行DBL及上采样操作,再与darknet的152层输出进行concatenate操作。x = Concatenate()([x, darknet.layers[152].output])x, y2 = make_last_layers(x, 256, num_anchors * (num_classes + 5))x = compose(DarknetConv2D_BN_Leaky(128, (1, 1)),UpSampling2D(2))(x)x = Concatenate()([x, darknet.layers[92].output])x, y3 = make_last_layers(x, 128, num_anchors * (num_classes + 5))return Model(inputs, [y1, y2, y3])
make_last_layers如下
def make_last_layers(x, num_filters, out_filters):'''6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer'''x = compose(DarknetConv2D_BN_Leaky(num_filters, (1,1)),DarknetConv2D_BN_Leaky(num_filters*2, (3,3)),DarknetConv2D_BN_Leaky(num_filters, (1,1)),DarknetConv2D_BN_Leaky(num_filters*2, (3,3)),DarknetConv2D_BN_Leaky(num_filters, (1,1)))(x)y = compose(DarknetConv2D_BN_Leaky(num_filters*2, (3,3)),DarknetConv2D(out_filters, (1,1)))(x)return x, y
Darknet
Darknet网络的输入是图片数据集inputs,即(?, 416, 416, 3),输出是darknet_body()方法的输出。将网络的核心逻辑封装在darknet_body()方法中。
即
darknet = Model(inputs, darknet_body(inputs))
其中,darknet_body的输出格式是(?, 13, 13, 1024)。
Darknet的网络简化图,如下
YOLO v3所使用的Darknet版本是Darknet53。那么,为什么是Darknet53呢?因为Darknet53是53个卷积层和池化层的组合,与Darknet简化图一一对应,
即
53 = 2 + 1*2 + 1 + 2*2 + 1 + 8*2 + 1 + 8*2 + 1 + 4*2 + 1
在darknet_body()中,Darknet网络含有5组重复的resblock_body()单元,
即
def darknet_body(x):'''Darknent body having 52 Convolution2D layers'''x = DarknetConv2D_BN_Leaky(32, (3, 3))(x)x = resblock_body(x, num_filters=64, num_blocks=1)x = resblock_body(x, num_filters=128, num_blocks=2)x = resblock_body(x, num_filters=256, num_blocks=8)x = resblock_body(x, num_filters=512, num_blocks=8)x = resblock_body(x, num_filters=1024, num_blocks=4)return x
在第1个卷积操作DarknetConv2D_BN_Leaky()中,是3个操作的组合,即
- 1个Darknet的2维卷积Conv2D层,即DarknetConv2D();
- 1个正则化(BN)层,即BatchNormalization();
- 1个LeakyReLU层,斜率是0.1,LeakyReLU是ReLU的变换;
实现代码如下:
def DarknetConv2D_BN_Leaky(*args, **kwargs):"""Darknet Convolution2D followed by BatchNormalization and LeakyReLU."""no_bias_kwargs = {'use_bias': False}no_bias_kwargs.update(kwargs)return compose(DarknetConv2D(*args, **no_bias_kwargs),BatchNormalization(),LeakyReLU(alpha=0.1))#*args指代列表类参数
#**kwargs指代字典类参数
#0.1代表的是leakyrelu激活函数负数部分斜率
其中,LeakyReLU的激活函数,如下
其中,Darknet的2维卷积DarknetConv2D,具体操作如下:
- 将核权重矩阵的正则化,使用L2正则化,参数是5e-4,即操作w参数;
- Padding,一般使用same模式,只有当步长为(2,2)时,使用valid模式。避免在降采样中,引入无用的边界信息;
- 其余参数不变,都与二维卷积操作Conv2D()一致;
- kernel_regularizer是将核权重参数w进行正则化,而BatchNormalization是将输入数据x进行正则化。
代码实现如下
@wraps(Conv2D)
def DarknetConv2D(*args, **kwargs):"""Wrapper to set Darknet parameters for Convolution2D."""darknet_conv_kwargs = {'kernel_regularizer': l2(5e-4)}darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides') == (2, 2) else 'same'darknet_conv_kwargs.update(kwargs)return Conv2D(*args, **darknet_conv_kwargs)
下一步,第1个残差结构resblock_body(),输入的数据x是(?, 416, 416, 32),通道filters是64个,重复次数num_blocks是1次。第1个残差结构是网络简化图第1部分。
x = resblock_body(x, num_filters=64, num_blocks=1)
在resblock_body中,含有以下逻辑:
- ZeroPadding2D():填充x的边界为0,由(?, 416, 416, 32)转换为(?, 417, 417,32)。因为下一步卷积操作的步长为2,所以图的边长需要是奇数;
- DarknetConv2D_BN_Leaky()是DarkNet的2维卷积操作,核是(3,3),步长是(2,2),注意,这会导致特征尺寸变小,由(?,
417, 417, 32)转换为(?, 208, 208, 64)。由于num_filters是64,所以产生64个通道。 - compose():输出预测图y,功能是组合函数,先执行1x1的卷积操作,再执行3x3的卷积操作,filter先降低2倍后恢复,最后与输入相同,都是64;
- x = Add()([x,y])是残差(Residual)操作,将x的值与y的值相加。残差操作可以避免,在网络较深时所产生的梯度弥散问题(Vanishing Gradient Problem)。
代码实现如下
def resblock_body(x, num_filters, num_blocks):'''A series of resblocks starting with a downsampling Convolution2D'''# Darknet uses left and top padding instead of 'same' modex = ZeroPadding2D(((1, 0), (1, 0)))(x)x = DarknetConv2D_BN_Leaky(num_filters, (3, 3), strides=(2, 2))(x)for i in range(num_blocks):y = compose(DarknetConv2D_BN_Leaky(num_filters // 2, (1, 1)),DarknetConv2D_BN_Leaky(num_filters, (3, 3)))(x)x = Add()([x, y])return x
残差操作流程,如图
同理,在darknet_body()中,执行5组resblock_body()残差块,重复[1, 2, 8, 8, 4]次,双卷积(1x1和3x3)操作,每组均含有一次步长为2的卷积操作,因而一共降维5次32倍,即32=2^5,则输出的特征图维度是13,即13=416/32。最后1层的通道(filter)数是1024,因此,最终的输出结构是(?, 13, 13, 1024),
代码如下
Tensor("add_23/add:0", shape=(?, 13, 13, 1024), dtype=float32)
至此,Darknet模型的输入是(?, 416, 416, 3),输出是(?, 13, 13, 1024)。
特征图
在YOLO v3网络中,输出3个不同尺度的检测图,用于检测不同大小的物体。调用3次make_last_layers(),产生3个检测图,即y1、y2和y3。
13x13检测图
第1个部分,输出维度是13x13。在make_last_layers()方法中,输入参数如下:
- darknet.output:DarkNet网络的输出,即(?, 13, 13, 1024);
- num_filters:通道个数512,用于生成中间值x,x会传导至第2个检测图;
- out_filters:第1个输出y1的通道数,值是锚框数*(类别数+4个框值+框置信度);
代码如下
x, y1 = make_last_layers(darknet.output, 512, num_anchors * (num_classes + 5))
在make_last_layers()方法中,执行2步操作:
- 第1步,x执行多组1x1的卷积操作和3x3的卷积操作,filter先扩大再恢复,最后与输入的filter保持不变,仍为512,则x由(?,13, 13, 1024)转变为(?, 13, 13, 512);
- 第2步,x先执行3x3的卷积操作,再执行不含BN和Leaky的1x1的卷积操作,作用类似于全连接操作,生成预测矩阵y;
代码实现如下
def make_last_layers(x, num_filters, out_filters):'''6 Conv2D_BN_Leaky layers followed by a Conv2D_linear layer'''x = compose(DarknetConv2D_BN_Leaky(num_filters, (1, 1)),DarknetConv2D_BN_Leaky(num_filters * 2, (3, 3)),DarknetConv2D_BN_Leaky(num_filters, (1, 1)),DarknetConv2D_BN_Leaky(num_filters * 2, (3, 3)),DarknetConv2D_BN_Leaky(num_filters, (1, 1)))(x)y = compose(DarknetConv2D_BN_Leaky(num_filters * 2, (3, 3)),DarknetConv2D(out_filters, (1, 1)))(x)return x, y
最终,第1个make_last_layers()方法,输出的x是(?, 13, 13, 512),输出的y是(?, 13, 13, 18)。模型只有1个检测类别,因而y的第4个维度是18,即3*(1+5)=18。分别对应13x13特征图下的是3个anchor值。
26x26检测图
第2个部分,输出维度是26x26,包含以下步骤:
- 通过DarknetConv2D_BN_Leaky卷积,将x由512的通道数,转换为256的通道数;
- 通过2倍上采样UpSampling2D,将x由13x13的结构,转换为26x26的结构;
- 将x与DarkNet的第152层拼接Concatenate,作为第2个尺度特征图;
其中,输入的x和darknet.layers[152].output的结构所示26x26的尺寸,如下:
x: Tensor("up_sampling2d_1/ResizeNearestNeighbor:0", shape=(?, 26, 26, 256), dtype=float32)
darknet.layers[152].output: Tensor("add_19/add:0", shape=(?, 26, 26, 512), dtype=float32)
输出的x:
Tensor("concatenate_1/concat:0", shape=(?, 26, 26, 768), dtype=float32)
这样做的目的是,将最底层的抽象信息darknet.output,经过若干次转换之后,除了输出第1个检测层,还被用于第2个检测层,经过上采样,再与Darknet骨干中,上一次降维的数据拼接,共同作为第2个检测层的输入。底层信息含有全局特征,中层信息含有局部特征,这样拼接,可以两者兼顾。
最后,还是执行相同的make_last_layers,输出第2个检测层y2和临时数据x。
实现:
x = compose(DarknetConv2D_BN_Leaky(256, (1, 1)),UpSampling2D(2))(x)
x = Concatenate()([x, darknet.layers[152].output])
x, y2 = make_last_layers(x, 256, num_anchors * (num_classes + 5))
最终输出,因为filter的数量是256,所以x结构是(?, 26, 26, 256),而检测层y2的结构是(?, 26, 26, 18),即:
Tensor("leaky_re_lu_64/LeakyRelu/Maximum:0", shape=(?, 26, 26, 256), dtype=float32)
Tensor("conv2d_67/BiasAdd:0", shape=(?, 26, 26, 18), dtype=float32)
52x52检测图
第3部分的输出结构,52x52,与第2部分类似,如下:
x = compose(DarknetConv2D_BN_Leaky(128, (1, 1)),UpSampling2D(2))(x)
x = Concatenate()([x, darknet.layers[92].output])
_, y3 = make_last_layers(x, 128, num_anchors * (num_classes + 5))
逻辑如下:
- x经过128个filter的卷积,再执行上采样,输出为(?, 52, 52, 128);
- darknet.layers[92].output,与152层类似,结构是(?, 52, 52, 256);
- 两者拼接之后是(?, 52, 52, 384);
- 最后输入至make_last_layers,生成y3是(?, 52, 52, 18),忽略x的输出;
- 最后,则是模型的重组,输入inputs依然保持不变,即(?, 416, 416, 3),而输出转换为3个尺度的预测层,即[y1, y2,
y3]。
代码如下
return Model(inputs, [y1, y2, y3])
[y1, y2, y3]的结构如下:
Tensor("conv2d_59/BiasAdd:0", shape=(?, 13, 13, 18), dtype=float32)
Tensor("conv2d_67/BiasAdd:0", shape=(?, 26, 26, 18), dtype=float32)
Tensor("conv2d_75/BiasAdd:0", shape=(?, 52, 52, 18), dtype=float32)
最终,在yolo_body中,完成整个YOLO v3模型的构建,基础网络是DarkNet。
model_body = yolo_body(image_input, num_anchors // 3, num_classes)
补充
padding
在卷积操作中,针对于边缘数据,有两种操作,一种是舍弃valid,一种是填充same。(也就是边界填充0和不填充)
same模式中数据利用率更高,valid模式中避免引入无效的边缘数据,两种模式各有千秋。
compose函数
compose()函数,使用Python的Lambda表达式,顺次执行函数列表,且前一个函数的输出是后一个函数的输入。compose()函数适用于在神经网络中连接两个层。
例如:
def compose(*funcs):if funcs:return reduce(lambda f, g: lambda *a, **kw: g(f(*a, **kw)), funcs)else:raise ValueError('Composition of empty sequence not supported.')
def func_x(x):return x * 10
def func_y(y):return y - 6
z = compose(func_x, func_y) # 先执行x函数,再执行y函数
print(z(10)) # 10*10-6=94
1x1卷积操作与全连接
1x1的卷积层和全连接层都可以作为最后一层的预测输出,两者之间略有不同。
第1点:
1x1的卷积层,可以不考虑输入的通道数,输出固定通道数的特征矩阵;
全连接层(Dense),输入和输出都是固定的,在设计网络时,固定就不能修改;
这样,1x1的卷积层,比全连接层,更为灵活;
第2点:
输入(13,13,1024),输出为(13,13,18)
1x1的卷积层,参数较少,只需与输出通道匹配的参数,如13x13x1x1x18个参数;
全连接层,参数较多,需要与输入和输出都匹配的参数,如13x13x1028x18个参数;
参考
http://www.jintiankansha.me/t/A6MG8uX1II
https://blog.csdn.net/leviopku/article/details/82660381
YOLOV3解读(3)相关推荐
- fasterrcnn论文_【论文解读】Yolo三部曲解读——Yolov1
打个广告,复现Yolov3之后的深度原理剖析请移步下文(含代码): [算法实验]能检测COCO并鉴黄的SexyYolo(含Yolov3的深度原理剖析) Yolov1论文链接:You Only Look ...
- YOLO系列算法原理介绍
1. Yolo介绍: 1.1 YOLO: You Only Look Once,是一种基于深度神经网络的对象识别和定位算法,其最大的特点是运行速度很快,可以用于实时系统. Joseph Redmon和 ...
- 一文读懂 YOLOv1,v2,v3,v4 发展史
YOLO 系列算法是目标检测 one-stage 类的代表算法,本文将从 问题背景,创新点等方面比较,了解它们的的发展历程. two-stage 相关: 一文读懂 R-CNN,Fast R-CNN,F ...
- DL之YoloV3:YoloV3论文《YOLOv3: An Incremental Improvement》的翻译与解读
DL之YoloV3:YoloV3论文<YOLOv3: An Incremental Improvement>的翻译与解读 目录 YoloV3论文翻译与解读 Abstract 1. Intr ...
- yolov3代码详细解读
下文所有代码:https://pan.baidu.com/s/1p-Q-edFXXcvzxlZNd9saOw 提取码:x72s 原理可以参考:yolov1-v5学习笔记及源码解读 目录 1 目录结构 ...
- 【MMDetection 源码解读之yolov3】Neck - FPN
目录 前言 一.FPN 总结 前言 这部分接着前一篇文章 [MMDetection 源码解读 yolov3]Backbone - Darknet53 继续往后讲.搭建完了主干特征提取模块,接着就是搭建 ...
- 【目标检测·yolo系列】YOLOV3目标检测论文笔记(参考 同济子豪兄的解读)
目录 骨干网络 Darknet53 后面的论文算法架构都是在yolov3基础上更新来的 骨干网络 Darknet53 由于主要网络中是全卷积层,所以是可以输入任意大小的图片(得是×32)
- yolov3的缺点_YOLOV3问答式解读
一.YOLOV3相比上个版本有哪些改进策略? 答:1.加入了FPN结构 2.使用残差结构,这样可以构建更深的网络结构,由darknet19变成darknet53. 二.如何理解anchor boxes ...
- 【目标检测】yolo系列:从yolov1到yolov5之YOLOv3详解及复现
在v1.v2的原理和技巧介绍之后,v3除了网络结构,其余的改变并不多.本文着重描述yolov3的原理细节. 相关阅读: 论文:YOLOv3: An Incremental Improvement 源码 ...
最新文章
- 笔记-项目配置管理-创建基线或发行基线的主要步骤
- 前端学习(3040):vue+element今日头条管理-上午回顾
- 数据结构练习题之树和图(附答案与解析)
- html内嵌样式字体,html-通过内联C加载外部字体
- 剑指offer——面试题29:数组中出现次数超过一半的数字
- 我的半个拓展训练(三)
- access 循环_双11多个品牌创销量新高,ACCESS品牌管理集团背后的全球供应链解码...
- x的x分之一次方极限x趋于0_x分之e的x次方减一的极限
- linux下批量修改文件名称
- 【HDU2825】AC自动机+状压DP
- PLC实验—西门子S7 1200读取旋转编码器数据并计算电机转速
- 《前端》localStorage 和 sessionStorage-将数据存入(取出)缓存的方法-2020年10月6日
- 使用 commit tran 需注意
- FPGA 30 综合数字ADC /DAC 信号发送采集系统设计(综合项目设计)
- picsart下载_PicsArt下载-PicsArt 安卓版v15.9.53-PC6安卓网
- Quartus Prime下载地址(17.1Standard)
- 【Matlab】多元线性回归(Excel可直接替换数据)
- python中的filter()函数
- VHDL语言Process
- Dynamo 论文解读