Keras YOLO v3代码详解(一):darknet53网络结构分析+Netron工具

Keras YOLO v3代码详解(一):darknet53网络结构分析+Netron工具上周我们搭建了一套 Windows10 YOLOv3 Deep Sort 多目标跟踪实验环境 并做了一些简单的测试记录 具体内容请参考 https blog csdn net weixin article details 那么本周 我们需要详细了解一下 YOLOv3 的 darknet53 的网络是什么样的 或者说 是怎样在 Keras Tensorflo

上周我们搭建了一套“Windows 10+YOLO v3+Deep_Sort多目标跟踪实验环境”,并做了一些简单的测试记录,具体内容请参考:

https://blog.csdn.net/weixin_/article/details/

那么本周,我们需要详细了解一下YOLO v3的darknet53的网络是什么样的,或者说,是怎样在Keras+Tensorflow环境中一步步构造出这个darknet53网络的(C++环境下的构造过程,先放一放),那么下面我们就来看一看。

 

Keras YOLOv3源代码下载地址:https://github.com/wweee/keras-yolo3

关于darknet53网络的构建,请查看源文件:keras-yolo3-master\yolo3\model.py

作为新手,如果对卷积核/过滤器、特征图、卷积层这些概念还不是很清楚,可以先跳到这篇文章来简单学习一下:

https://blog.csdn.net/weixin_/article/details/

这篇文章主要内容分为三部分,(一)darknet53的网络结构,(二)darknet53网络的输出,(三)Netron工具

 

(一)darknet53的网络结构

darknet53的网络结构如下图所示(图中默认的输入是256X256大小的图片,而实际上YOLO v3建议的输入图片大小为416 X416,因而下图中的256 X256、128 X128、64 X64、32 X32、16 X16、8 X8分别应修改为:416 X416、208 X208、104 X104、52 X52、26 X26、13 X13):

https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1561983279747&di=21b9bfd69f72bfc47239f75621925621&imgtype=0&src=http%3A%2F%2Ffile.elecfans.com%2Fweb1%2FM00%2F52%2F78%2Fo4YBAFsV5QqAMSHQAADt9IUCdEY637.png

 

即:darknet53一共53层卷积,除去最后一个FC(全连接层,实际上是通过1×1卷积实现的)总共52个卷积用于当做主体网络。

这52个卷积层是这样组成的:

首先是1个32个过滤器的卷积核,然后是5组重复的残差单元resblock_body(这5组残差单元,每个单元由1个单独的卷积层与一组重复执行的卷积层构成,重复执行的卷积层分别重复1次、2次、8次、8次、4次;在每个重复执行的卷积层中,先执行1×1的卷积操作,再执行3×3的卷积操作,过滤器数量先减半,再恢复),一共是52层。残差计算不属于卷积层计算。

52 = 1+ (1+1*2 ) + (1+2*2) + (1+8*2) + (1+8*2) + (1+4*2)

每组残差单元的第一个单独的卷积层操作均是一次步长为2的卷积操作,因此整个YOLO v3网络一共降维5次32倍,即:32=2^5,最后输出的特征图维度是13,即:13=416/32;最后1层的通道数是1024,最终的输出结构是(?, 13, 13, 1024)。所以,darknet53模型的输入是(?, 416, 416, 3),输出是(?, 13, 13, 1024)。

这个52层网络是这么定义的:

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)第1个卷积层DarknetConv2D_BN_Leaky,是这么定义的:

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))

即:

(1.1)1个Darknet的2维卷积Conv2D层,即DarknetConv2D;

(1.2)1个批正则化层,将输入数据x进行正则化,即BatchNormalization();

(1.3)1个LeakyReLU层,斜率是0.1,LeakyReLU是在ReLU基础改进后的激活函数。

LeakyReLU的激活函数,如下图所示:

Keras YOLO v3代码详解(一):darknet53网络结构分析+Netron工具

 

(2)Darknet的2维卷积DarknetConv2D,是这么定义的:

@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)

即:

(2.1)将核权重矩阵的正则化,使用L2正则化,参数是5e-4,将核权重参数w进行正则化;

(2.2)Padding,一般使用same模式,只有当步长为(2,2)时,使用valid模式。避免在降采样中,引入无用的边界信息;

(2.3)其余参数不变,都与二维卷积操作Conv2D()一致。

(2.4)所谓2D卷积,就是横向和纵向均卷积,计算方式举例如下:

输入和核如下,没有padding,stride=1

http://qn.javajgs.com/20220830/0de4a14c-84a8-4bc2-a5db-57b358e6524820220830b116cf22-2e62-4676-a852-1d87617345ae1.jpg

输出为:将卷积核对应在input上,第一行对应算完,再算第二行,可以动手写一下。

http://qn.javajgs.com/20220830/c46cc848-b4df-4a09-a79e-7460efb43b6120220830337b3e61-35cf-4e20-aaba-972fe8b0deaf1.jpg

    14 = 4 * 1 + 3 * 0 + 1 * 1 + 2 * 2 + 1 * 1 + 0 * 0 + 1 * 0 + 2 * 0 + 4 * 1

    6 = 3 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 0 * 1 + 1 * 0 + 2 * 0 + 4 * 0 + 1 * 1

    6 = 2 * 1 + 1 * 0 + 0 * 1 + 1 * 2 + 2 * 1 + 4 * 0 + 3 * 0 + 1 * 0 + 0 * 1

    12 = 1 * 1 + 0 * 0 + 1 * 1 + 2 * 2 + 4 * 1 + 1 * 0 + 1 * 0 + 0 * 0 + 2 * 1

 

这里所使用的Conv2D()函数,是Keras提供的二维卷积函数,在运算时会加载YOLO v3模型的各项参数(权重、偏置等)。比较简单的二维卷积函数,如:tf.nn.conv2d()函数运算的具体说明,可以参考:

https://blog.csdn.net/weixin_/article/details/

 

(3)残差网络resblock_body,是这么定义的:

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' mode x = 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

即:

(3.1)ZeroPadding2D:填充x的边界为0,由(?, 416, 416, 32)转换为(?, 417, 417, 32)。因为下一步卷积操作的步长为2,所以图的边长需要是奇数;

(3.2)DarknetConv2D_BN_Leaky:DarkNet的2维卷积操作,核是(3,3),步长是(2,2),因此会导致特征尺寸变小,由(?, 417, 417, 32)转换为(?, 208, 208, 64)。由于num_filters是64,所以产生64个通道。

(3.3)compose:输出预测图y,功能是组合函数,先执行1×1的卷积操作,再执行3×3的卷积操作,过滤器数量先减半,再恢复,最后与输入相同,都是64;

(3.4)x = Add()([x, y]):残差操作,将x的值与y的值相加。残差操作可以避免,在网络较深时所产生的梯度弥散问题。

 

(二)darknet53网络的输出

darknet53输出3个不同尺度的检测图,用于检测不同大小的物体。调用3次make_last_layers,产生3个检测图,即y1、y2和y3,如下图所示(图中,y1、y2和y3分别叫Predict one、Predict two和Predict three):

Keras YOLO v3代码详解(一):darknet53网络结构分析+Netron工具

def yolo_body(inputs, num_anchors, num_classes): """Create YOLO_V3 model CNN body in Keras.""" darknet = Model(inputs, darknet_body(inputs)) x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5)) 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)) 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])

其中,

(1)y1:输入是darnknet.output,shape是[?,13,13,1024],作为第1个make_last_layers()的输入,输出x的shape是[?, 13, 13, 512],输出y1的shape是[?, 13, 13, num_anchors*(num_classes+5) ]。

YOLOv3通过聚类算法得到了3组先验框(即:anchor box,预训练得到的默认边界框),目标检测器通过预测对数空间(log-space)变换来确定边界框。

YOLOv3 的先验框anchor box一共有3组共9个,由k-means聚类得到。在COCO数据集上,这9个先验框按不同尺寸特征图分组如下,每组3个:

13×13特征图对应:【(116*90),(156*198),(373*326)】

26×26特征图对应:【(30*61),(62*45),(59*119)】

52×52特征图对应:【(10*13),(16*30),(33*23)】

5 = 4+1,4为边界框中心点坐标(x , y)、宽w 和高h ;1为置信度。

(2)y2:输入是(x,y1)输出的x,shape是[?, 13, 13, 512]。先执行一次有256个滤波器(所以,输出是256个通道)的1×1卷积操作DarknetConv2D_BN_Leaky();然后做一次上采样操作UpSampling2D(),将输入为13×13的结构,转换为26×26的结构。将x与darknet的第152层拼接Concatenate(拼接后张量的shape是[?, 26, 26, 768]),作为第2个make_last_layers()的输入,输出x的shape是[?, 26, 26, 256],输出y2的shape是[?, 26, 26, num_anchors*(num_classes+5) ]。

(3)y3:输入是(x,y2)输出的x,shape是[?, 26, 26, 256]。先执行一次有128个滤波器(所以,输出是128个通道)的1×1卷积操作DarknetConv2D_BN_Leaky();然后做一次上采样操作UpSampling2D(),将输入为26×26的结构,转换为52×52的结构。将x与darknet的第92层拼接Concatenate(拼接后张量的shape是[?, 52, 52, 384]),作为第3个make_last_layers()的输入,输出x忽略,输出y3的shape是[?, 52, 52, num_anchors*(num_classes+5) ]。

最后,根据整个逻辑的输入和输出,构建模型Model(inputs,[y1,y2, y3]):输入inputs为(?, 416, 416, 3),输出为3个尺度的预测层,即[y1, y2, y3]。其中,y2、y3比y1更适用于发现尺寸比较小的物体。

make_last_layers()的函数如下所示,是一组连续的2D卷积操作,先执行5次DarknetConv2D_BN_Leaky()卷积操作,返回x;然后再执行1次DarknetConv2D_BN_Leaky()卷积操作和1次不含BN和Leaky的1×1的卷积操作DarknetConv2D()(作用类似于全连接操作),返回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

作为初学者,这里有一个额外的有意思的问题,按照前图,26×26尺寸的卷积运算的输出应该是darknet53的第43层卷积层,即:

43 = 1+ (1+1*2 ) + (1+2*2) + (1+8*2) + (1+8*2)

为什么在yolo_body()的定义里,却使用下面的语句来定义拼接操作呢?

x = Concatenate()([x,darknet.layers[152].output])

原来,Model(inputs, [y1,y2,y3]) 所定义的卷积神经网络,darknet53只是按照53层卷积运算的主体来命名的,而在实际的Model模型中,残差操作add、批正则化batch_normalization、leaky_re_lu、zero_padding2d、拼接操作concatenate、上采样up_sampling2d等与conv2d一样都是要计入层数(layers)的,并且每一层都有不同的名字和序号。按照这样的定义,每个DarknetConv2D结构的层数是1,每个DarknetConv2D_BN_Leaky结构的层数是3,每个resblock_body结构的层数是4+7*num_blocks(num_blocks为循环次数),则26×26尺寸的卷积运算的输出实际上是darknet53的第152层:

152 = 3+(4+7)+(4+7*2)+(4+7*8)+(4+7*8)

同时,每个make_last_layers结构共有6*3+1=19层,加上1个输入层input,因此整个darknet53一共有252层:

252 = 1+ 3+(4+7)+(4+7*2)+(4+7*8)+(4+7*8)+(4+7*4)+19+5+19+5+19

(三)Netron工具

我们可以用Netron工具来查看YOLO v3详细的网络结构和每一层的名称,一共有1层input、23层add、72层batch_normalization、72层leaky_re_lu、75层conv2d、5层zero_padding2d、2层up_sampling2d和2层concatenate。

使用Netron工具打开文件“model_data\yolo.h5”,查找“concatenate”,就能看到“concatenate_1”和“concatenate_2”两个网络层,点击“concatenate_1”可以看到”x = Concatenate()([x,darknet.layers[152].output])”所对应的卷积神经网络图的局部,它的名字是concatenate_1,如下图所示:

Keras YOLO v3代码详解(一):darknet53网络结构分析+Netron工具

其中,标记为粉色的这一个输入变量就是darknet.layers[152].output,我们追溯上去,可以看到darknet.layers[152]就是下图所示的网络层,它的名字是add_19:

Keras YOLO v3代码详解(一):darknet53网络结构分析+Netron工具

Netron工具的下载地址是:

https://github.com/lutzroeder/netron

好,这篇文章到这里就结束了!

 

下一章:

Keras YOLOv3代码详解(二):目标识别原理解析

 

参考:

https://mp.weixin..com/s/hC4P7iRGv5JSvvPe-ri_8g

https://www.jianshu.com/p/ca3ea4c791e9

(完)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/176171.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午10:33
下一篇 2026年3月26日 下午10:33


相关推荐

  • linux通配符的用法_linux中rmdir命令

    linux通配符的用法_linux中rmdir命令注意:linux通配符和三剑客(grep,awk,sed)正则表达式是不一样的,因此,代表的意义也是有较大区别的。通配符一般用户命令行bash环境,而linux正则表达式用于grep,sed,awk场景。*:通配符,代表所有(0到多个)字符?:通配符,代表任意1个字符;:连续不同命令的分隔符#:配置文件注释|:管道~:当前用户的家目录-:上一次所在的路径$:变量前需要加的符号/:路径分隔符号,也是…

    2026年1月23日
    4
  • pantum打印机驱动安装m6506_打印机驱动怎么装(安装步骤)[通俗易懂]

    pantum打印机驱动安装m6506_打印机驱动怎么装(安装步骤)[通俗易懂]  您可以先下载打印机的驱动,首先打开控制面板,然后双击控制面板中的打印机和传真图标,安装新打印机直接点左边的添加打印机,接着弹出添加打印机向导,系统将自动检测打印机类型。接下来详细介绍:  1、一般打印机的说明书上会有驱动的下载链接或者是直接上品牌官网找到下载中心,下载对应型号的驱动;  2、在打开开始菜单找到【设备和打印机】,或者从【控制面板】中找到,硬件和声音的相关选项找到;  3、点击页面…

    2022年4月19日
    951
  • 什么是AI Agent:从聊天机器人到自主智能体

    什么是AI Agent:从聊天机器人到自主智能体

    2026年3月15日
    2
  • 35 亿!投资算力平台、大模型

    35 亿!投资算力平台、大模型

    2026年3月14日
    3
  • JS setTimeout和setInterval的区别

    JS setTimeout和setInterval的区别1 setTimeout 和 setInterval 都属于 JS 中的定时器 可以规定延迟时间再执行某个操作 不同的是 setTimeout 在规定时间后执行完某个操作就停止了 而 setInterval 则可以一直循环下去 functionfun alert hello setTimeout fun 1000 参数是函数名 setTimeout fun 1000

    2025年11月21日
    9
  • 千问AI

    千问AI

    2026年3月13日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号