参考:
yolov3
darknet
yolo源码解析
bacth参数对性能影响
backpropogation算法
yolo中7*7个grid和rpn中的9个anchors
darknet源码学习
小知识点
- make -j4 表示用4条线程编译(对应自己的电脑,看自己电脑cpu几核几线程)
- make -j32 用32线程编译
- CNN卷积神经网络更新权值共有两步:第一:求梯度。第二:梯度下降。
- 现在backprop就是指上面讲到的更新权值的第一步:求梯度。(链式法则)
- 之所以可以链式法则是因为梯度直观上理解就是一阶近似。所以梯度可以理解为某个变量或者某个中间变量对输出影响的敏感度系数。神经网络中的链式法则几乎都是高维的。
- 现在在大部分主流深度学习框架中的求导都是基于computational graph。computational graph是计算代数中很基础的方法。从计算机的角度看有动态规划的意思。优点是表达式给定的情况下对复合函数中所有变量快速求导。
darknet源码解析
darknet概述
darknet是一个轻型的、完全基于C与CUDA的开源深度学习框架,支持CPU和GPU两种计算方式。
darknet优点
- 完全由c语言实现,没有任何依赖项,只有CUDA。连opencv都可以不用。当然也可以使用opencv,目的是用其来显示图片,为了更好可视化。如果在CPU平台cuda都可以扔了(当然darknet的cup代码并没有做什么优化,跑起来就很慢)。这样可以很easy的将代码移植到其他平台。
- 框架轻型、灵活。(虽然没有tensorflow那么多强大的API)
- darknet的实现与caffe存在相似。
darknet缺点
- 在darknet中,所有层的lr都一样,这对微调造成了很大的困难,因为微调需要把前面几层的lr都设置的很小很小,然后主要训练最后一层的权重。
- darknet的接口很差,想把网络改成inception或者resnet的构架,需要改大量的代码,这对于验证模型可行性来说,非常浪费时间。所以才会把代码移植到mxnet或者caffe上,然后在mxnet上做模型压缩了。
darknet源码总结
- darknet中最重要的三个struct定义是network_state network layer。新版本network_state并入到了network中去。
- 不同种类的网络层都是通过layer里面的函数指针forward backward和update定义本种类的执行规则。如connected_layer就有forward_connected_layer、backward_connected_layer、update_connected_layer三个方法。
- 原子运算只在blas.c和gemm.c,网络的运算在network.c中。最重要的是train_network_datum、train_networks、train_network_batch和network_predict。
- train_network_datum是输入数据用float_pair,就是floatx,floaty结对。
- train_networks是在network_kernel.cu里,以并发线程方式训练,参数是data。
- train_network_datum顺序执行forward_network(逐层正向网络),backward_network逐层逆向网络,满足次数下(*net.seen %subdivisions)执行一次update_network(rate,momentum,decay)
- 对于用户定义的网络参数文件处理在parse_network_cfg。读入训练结果通过load_weights。
- 如果需要参考特别需求的数据源,需要参考data.c入手。
- 对于cfg配置文件,训练时调整重点的全局参数:decay_momentum_rate这三个是与收敛速度有关的。policy是weights策略的,input batch(及相关的subdivisons)。outputs是与数据吞吐维度相关的。
yolov3算法思路概述
- 如上图所示:在训练过程中对于每幅输入图像,yolov3会预测三个不同大小的3D tensor,对应着三个不同的scale,设计这三个scale的目的是为了能检测出不同大小的物体。
- 这里以13 * 13的tensor为例,对于这个scale,原始输入图像会被分割成13 × 13的grid cell,每个grid cell对应着3D tensor中的1x1x255这样一个长条形voxel。255是由3x(4+1+80)而来,由上图可知,公式NxNx[3x(4+1+80)]中NxN表示的是scale size例如上面提到的13x13。3表示的是each grid predict 3 boxes。4表示的是坐标值即(tx,ty,tw,th)。1表示的是置信度,80表示的是类别数目。
- 如果训练集中某一个ground truth对应的bounding box中心恰好落在了输入图像的摸一个grid cell中,那么这个grid cell就负责预测此物体的bounding box,于是这个grid cell所对应的置信度为1,其他grid cell的置信度为0.每个grid cell都会被赋予3个不同大小的prior box,学习过程中,这个grid cell会学会如何选择哪个大小的prior box。作者定义的是只选择与ground truth bounding box的IOU重合度最高的prior box。
- 上面说到的三个预设的不同大小的prior box,这三个大小是如何计算的,首先在训练前,将coco数据集中的所有bbox使用k-means clustering分成9个类别,每3个类别对应一个scale,故总共3个scale,这种关于box大小的先验信息帮助网络准确预测每个box的offset和coordinate。从直观上,大小合适的box会使得网络更精准的学习。
batch参数对性能的影响
- batch_size批尺寸。
- bacth的选择首先决定的是下降的方向。
- 若是小数据集,则可以采用全数据集的形式,即full batch learning。好处有两个:
(1)由全数据集确定的方向能更好的代表样本总体,从而更准确的朝向极值所在的地方。
(2)由于不同权重的梯度值差别巨大,因此选取一个全局的学习率很困难。
综上,full batch learning可以使用Rprop只基于梯度符号并且针对性单独更新各权值。 - 对于更大数据集,上面提到的两个优点变成了两个缺点:
(1)数据集的海量增长和内存限制,一下子导入全部数据不现实。
(2)以Rprop方式迭代,会由于各个batch之间的采样差异性,各次梯度值相互抵消,无法修正。所以后面有了RMSProp的妥协方案。 - 每次只训练一个样本即batch_size=1。即在线学习。
- 线性神经元在均方误差代价函数的错误面是一个抛物面,横截面是椭圆。对于多层神经元、非线性网络,在局部依然近似是抛物面使用在线学习每次修正方向以各自样本的梯度方向修正,横冲直撞各自为政,难以达到收敛。如下图:
- 综上所以需要选择一个恰当的适中的batch_size值,批梯度下降法(mini_bacthes learning)。使用这个方法,只要数据量充足,那么用一半(甚至少的多的数据量)训练计算出来的梯度与用全部数据量训练计算出来梯度值几乎一样。
- 在合理范围内,增大batch_size的好处:
(1)内存利用率提高了,大矩阵乘法的并行化效率提高。
(2)跑完一次epoch(全数据集)所需的迭代次数减少,对于相同数据量的处理速度进一步加快。
(3)一定范围内,一般来说bacth_size太大,其确定的下降方向越准,引起训练震荡越小。 - 盲目增大batch_size的坏处:
(1)内存利用率提高,但是内存容量遭不住。
(2)跑完一次epoch(全数据集)所需的迭代次数减小,要想达到相同的精度,其所需的时间大大增加,从而对参数的修正也会更加缓慢。
(3)batch_size增加到一定程度,其确定的下降方向也会不再改变。
(4)如今随着batch normalization的普及,收敛速度已经不再像之前那样需要玄学调参了,现在一般都采取大的batch_size,毕竟有了GPU。batch normalization的坏处就是不能用太小的batch_size,要不然mean和variance就偏了。现在就是显存能放多少就放多少,实际调模型时,数据分布和预处理最重要,数据不行的话再多花招都没用。
yolo中的7*7个grid和PRN中的9个anchors
grid和anchors的唯一作用是为了计算IOU,从而确定正负样本。
其他的detection system
- Deformable parts models(DPM):深度神经网络之前,早期的object detecction是通过提取图像的一些robust特征,例如Haar SIFT HOG等特征。使用DPM模型,用sliding window方式预测具有较高score的bounding box。DPM模型把物体看成了多个组成的部件(例如人脸的鼻子、嘴巴等)。用部件间的关系来描述物体,这个特性符合自然界很多物体的非刚体特性。DPM可以看做是HOG+SVM的扩展,继承了两者的优点,在人脸检测和行人检测上效果很好,但是DPM检测速度很慢。
- R-CNN系列,包括:R-CNN SPP-net Fast R-CNN Faster R-CNN R-FCN等经典网络。
- Deep Multibox:训练一个卷积神经网络来预测感兴趣的区域,而不是使用Selective Saerch的方法直接获得候选区,yolo也是通过训练一个cnn来预测一个bounding box。相比Multibox,yolo是一个完整的系统。
- OverFeat:用CNN统一做分类、定位和检测。使用CNN做早期工作,采用多尺度滑动窗口来分类、定位和检测。OverFeat可以看做是R-CNN的特殊情况,只需要把selective search换成多尺度的滑动窗口,每个类别的边框回归器换成统一的边框回归器,SVM换成多层网络即可。但是OverFeat比R-CNN快9倍,得益于卷积相关的共享计算。
- MultiGrasp:yolo的设计与MutiGrasp相似。但是MultiGrasp主要用于grasp detection,这个任务比object detection简单很多。对于一张包含单个物体的image,Multigrasp只需要预测一个单一的graspable region,且不需要预估object的位置、大小、边界,也不需要预测object属于哪个class。但是yolo的任务需要预测bounding box和概率,对于每个bounding box需要回归(x,y,w,h)和置信度(confidence),而且是多目标、多类别的图像。
实操过程完整记录
对于darknet文件夹,在实践过程中,主要修改的文件有:
- Makefile
- ./cfg目录下的参数配置文件
- ./data目录下的数据
第一步
在自己的指定目录下下载,终端输入
git clone https://github.com/pjreddie/darknet.git
第二步
修改Makefile。GPU环境下的编译配置都是在/darknet/Makefile/中定义的。终端输入
cd darknet
gedit Makefile
在打开的文本上最前面修改
GPU=1
CUDNN=1
OPENCV=1
表示使用GPU CUDNN OPENCV。
更改ARCH配置,根据自己的GPU型号来定。我的GPU是华硕 RTX 2060,计算能力是,GTX1080的计算能力是6.1。故更改为如下:吧前面几行语句注释掉,只保留值为52的那个。
ARCH= # -gencode arch=compute_30,code=sm_30 \
# -gencode arch=compute_35,code=sm_35 \
# -gencode arch=compute_50,code=[sm_50,compute_50] \
-gencode arch=compute_52,code=[sm_52,compute_52]
compute_30表示显卡的计算能力是3.0。
在Makefile51行,有cuda的安装路径,这里指的路径不是你下载安装包时的路径,而是系统自己存放的cude文件夹和cuda软链接文件夹的路径,在其他位置——计算机——usr——local中。所以是不需要修改makefile文件夹中的cuda安装路径,默认就是与系统实际相匹配的。
注意:每次修改完makefile都要重新Make一下才能生效。
第三步
编译,我的cpu是i7-8700,是12线程的6核的,所以使用指令j12,每次使用make指令时都要加上j12,会加快编译速度。终端输入:
make -j12
到这一步时遇到错误,这个错误是在darknet目录下执行make指令显示的错误。显示内容如下:
./src/image_opencv.cpp:5:10: fatal error: opencv2/opencv.hpp: 没有那个文件或目录
#include "opencv2/opencv.hpp"
compilation terminated.
Makefile:86: recipe for target 'obj/image_opencv.o' failed
make: *** [obj/image_opencv.o] Error 1
make: *** 正在等待未完成的任务....
出现上面错误的原因是之前我安装的opencv是python的,即opencv_python。这里模型的训练需要用到c++的opencv。所以这里再装一遍opencv。
我在官网上选择的opencv4.1.0安装的opencv4.1.0
git clone https://github.com/opencv/opencv.git
然后
cd opencv
sudo pip3 install cmake #如果已经安装过cmake,则该步骤省略
#安装依赖库
sudo apt-get install build-essential libgtk2.0-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev:i386 libtiff5-dev
创建一个编译文件夹并且进入
mkdir build
cd build/
cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/usr/local/opencv4 ..
上面的指令修改安装路径到/usr/local/opencv4,可以自己修改到自己需要的位置,如果该命令中不加-D CMAKE_INSTALL_PREFIX=/usr/local/opencv4,则默认各部分分别安装在/usr/local/目录的include/ bin/ lib/3个文件夹下。注意上面指令路径后面还有两个点。
在终端上输入命令时tab键具有补全功能。