https://pjreddie.com/darknet/yolo/ 具体安装及使用可以参考官方文档
https://github.com/pjreddie/darknet
http://blog.csdn.net/u012235274/article/details/52399425 caffe-yolo 训练
http://blog.csdn.net/u012235274/article/details/52120152 caffe 版本 yolo 过程记录
YOLOv2 参数详解
[net]
// http://guanghan.info/blog/en/my-works/train-yolo/
// if you have 64 images as a batch, for example, you batch update the weights upon processing 64 images.
batch=64
// if you have subdivision to be set to 8, you have 8 images for each subdivision. For each division, you concatenate the ground truth image feature vectors into one and process it as a whole.
// so If you set subdivision to 2, the training is the fastest, but you see less results printed out.
subdivisions=8
width=416
height=416
channels=3
momentum=0.9
decay=0.0005
// 对于每次迭代训练,YOLOv2会基于角度(angle),饱和度(saturation),曝光(exposure),色调(hue)产生新的训练图片
// angle:图片角度变化,单位为度,假如angle=5,就是生成新图片的时候随机旋转-5~5度
// saturation & exposure: 饱和度与曝光变化大小,1.5~1倍
// hue:色调变化范围,-0.1~0.1
angle=0
saturation = 1.5
exposure = 1.5
hue=.1
learning_rate=0.001 // 初始化学习率
max_batches = 40100 //训练达到max_batches后停止学习
policy=steps //按照steps策略调整学习率,还有EXP,CONSTANT,POLY等
steps=-1,100,20000,30000 //根据batch_num调整学习率,
scales=.1,10,.1,.1 //学习率变化的比例,累计相乘
[region]
anchors = 1.08,1.19, 3.42,4.41, 6.63,11.38, 9.42,5.11, 16.62,10.52
bias_match=1
classes=20
coords=4 //BoundingBox的tx,ty,tw,th,tx与ty是相对于左上角的gird,同时是当前grid的比例,tw与th是宽度与高度取对数
num=5 // 每个grid预测的BoundingBox个数
softmax=1 //使用softmax作为激活函数
jitter=.2 //通过抖动增加噪声,控制过拟合
rescore=1 //理解为一个开关,非零时通过重新打分来调整I.delta(预测值与真实值的差)
//YOLOv1论文中cost function的权重,哪一个更大,每一次更新权重的时候,对应方面的权重更新相对比重更大
object_scale=5 // 计算损失时,预测框中有物体的权重
noobject_scale=1 // 计算损失时,预测框中没有物体的权重
class_scale=1 // 计算类别损失时的权重
coord_scale=1 // 计算损失时坐标偏差的权重
absolute=1
thresh = .6 //决定是否需要计算IOU误差的参数,大于thresh,IOU误差不会夹在cost function中
random=1 //是否随机确定最后一个预测框
使用YOLO2训练自己的数据
http://blog.csdn.net/q6324266/article/details/54375452 darknet yolo v2 训练自己的数据
http://blog.csdn.net/hysteric314/article/details/54097845 【YOLO学习】使用YOLO v2训练自己的数据
http://blog.csdn.net/burning_keyboard/article/details/71056164 yolo训练自己的数据实践总结
http://blog.csdn.net/ch_liu23/article/details/53558549
一,数据制作,可以直接使用labelImg工具https://github.com/tzutalin/labelImg
二,网络修改
1.修改 cfg/voc.data:
classes= 10
train = path/to/trainImagePath.txt
validate = path/to/validateImagePath.txt
names = data/classes.names
backup = backup // 记录每迭代100次得到的模型 .weights文件
2.修改网络结构 cfg/yolo-voc.cfg:
(1)最后一个卷积层
[convolutional]
size=1
stride=1
pad=1
filters=125 // 125 = 5(anchor box) * (20(class) + 4(coor) + 1(confidence))
... // 根据class数算好filters个数
(2)[region]
anchors = 1.08,1.19, 3.42,4.41, 6.63,11.38, 9.42,5.11, 16.62,10.52
bias_match=1
classes=10 // 改成目标分类数量
coords=4
num=5 // anchor box 数量
...
(3)预训练文件cfg/darknet19_448.conv.23:在其他数据集上pretrain的模型做为初值
3.训练命令
cd darknet
./darknet detector train cfg/voc.data cfg/yolo-voc.cfg cfg/darknet19_448.conv.23
每train一个batch会用validate检测数据检测一次,显示迭代次数,学习率,recall值,已训练图片数等,
每100次会在backup里存一次 weights
4.检测命令
./darknet detector test cfg/voc.data cfg/yolo-voc.cfg backup/yolo-voc_400.weights testImage/738780.jpg
三, darknet 网络源码解读
https://github.com/hgpvision/darknet
darknet 中最重要的三个struct定义是 network_state, network, layer;
新版本network_state 已经并入到 network 里去了。代码可以先忽略 gpu 部分,
不同种类的网络层都是通过 layer 里面的函数指针 forward backward 和 update 定义本种类的执行规则。
如 connected layer 就有 forward_connected_layer backward_connected_layer update_connected_layer 三个方法,gru layer 等也是一样;
原子运算只在 blas.c 和 gemm.c 里,网络的运算在 network.c 中,最重要的是 train_network_datum ,train_networks, train_network_batch
和 network_predict;train_network_datum 是输入数据用 float_pair , 就是 float *x , float *y 结对;train_networks 是在 network_kernel.cu 里,
以并发线程方式进行训练,参数是 data ;有一点, darknet 在CPU模式下是单线程的,在多块GPU显卡模式下,train_networks支持多卡运行,
而且这里也是改造成分布式多主机darknet运行的入口,可以看到训练出的权重数据合并和scale。
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 learning_rate 这三个是与收敛速度有关的。
policy 是weights 策略的, inputs batch(及相关的subdivisions) ouputs 是与数据吞吐维度相关的,最新版本好像ouputs这里有改正。
四,主要改进点(具体详见paper)
https://zhuanlan.zhihu.com/p/25167153 YOLO2详解答
http://blog.csdn.net/jesse_mx/article/details/53925356
论文地址:YOLO9000: Better, Faster, Stronger
项目主页:YOLO: Real-Time Object Detection
Caffe实现:caffe-yolo9000
概述
时隔一年,YOLO(You Only Look Once: Unified, Real-Time Object Detection)从v1版本进化到了v2版本,作者在darknet主页先行一步放出源代码,论文在我们等候之下终于在12月25日发布出来,本文对论文重要部分进行了翻译理解工作,不一定完全对,如有疑问,欢迎讨论。博主如果有新的理解,也会更新文章。
新的YOLO版本论文全名叫“YOLO9000: Better, Faster, Stronger”,主要有两个大方面的改进:
第一,作者使用了一系列的方法对原来的YOLO多目标检测框架进行了改进,在保持原有速度的优势之下,精度上得以提升。VOC 2007数据集测试,67FPS下mAP达到76.8%,40FPS下mAP达到78.6%,基本上可以与Faster R-CNN和SSD一战。这一部分是本文主要关心的地方。
第二,作者提出了一种目标分类与检测的联合训练方法,通过这种方法,YOLO9000可以同时在COCO和ImageNet数据集中进行训练,训练后的模型可以实现多达9000种物体的实时检测。这一方面本文暂时不涉及,待后面有时间再补充。
回顾YOLOv1
YOLOv2始终是在v1版本上作出的改进,我们先简单回顾YOLOv1的检测步骤:
(1) 给个一个输入图像,首先将图像划分成7 * 7的网格。
(2) 对于每个网格,每个网格预测2个bouding box(每个box包含5个预测量)以及20个类别概率,总共输出7×7×(2*5+20)=1470个tensor
(3) 根据上一步可以预测出7 * 7 * 2 = 98个目标窗口,然后根据阈值去除可能性比较低的目标窗口,再由NMS去除冗余窗口即可。
YOLOv1使用了end-to-end的回归方法,没有region proposal步骤,直接回归便完成了位置和类别的判定。种种原因使得YOLOv1在目标定位上不那么精准,直接导致YOLO的检测精度并不是很高。
YOLO检测原理参考推荐博客:论文阅读:You Only Look Once: Unified, Real-Time Object Detection
YOLOv2精度的改进(Better)
先来一个总览图,看看它到底用了多少技巧,以及这些技巧起了多少作用:
Batch Normalization
CNN在训练过程中网络每层输入的分布一直在改变, 会使训练过程难度加大,但可以通过normalize每层的输入解决这个问题。新的YOLO网络在每一个卷积层后添加batch normalization,通过这一方法,mAP获得了2%的提升。batch normalization 也有助于规范化模型,可以在舍弃dropout优化后依然不会过拟合。
High Resolution Classifier
目前的目标检测方法中,基本上都会使用ImageNet预训练过的模型(classifier)来提取特征,如果用的是AlexNet网络,那么输入图片会被resize到不足256 * 256,导致分辨率不够高,给检测带来困难。为此,新的YOLO网络把分辨率直接提升到了448 * 448,这也意味之原有的网络模型必须进行某种调整以适应新的分辨率输入。
对于YOLOv2,作者首先对分类网络(自定义的darknet)进行了fine tune,分辨率改成448 * 448,在ImageNet数据集上训练10轮(10 epochs),训练后的网络就可以适应高分辨率的输入了。然后,作者对检测网络部分(也就是后半部分)也进行fine tune。这样通过提升输入的分辨率,mAP获得了4%的提升。
Convolutional With Anchor Boxes
之前的YOLO利用全连接层的数据完成边框的预测,导致丢失较多的空间信息,定位不准。作者在这一版本中借鉴了Faster R-CNN中的anchor思想,回顾一下,anchor是RNP网络中的一个关键步骤,说的是在卷积特征图上进行滑窗操作,每一个中心可以预测9种不同大小的建议框。看到YOLOv2的这一借鉴,我只能说SSD的作者是有先见之明的。
为了引入anchor boxes来预测bounding boxes,作者在网络中果断去掉了全连接层。剩下的具体怎么操作呢?首先,作者去掉了后面的一个池化层以确保输出的卷积特征图有更高的分辨率。然后,通过缩减网络,让图片输入分辨率为416 * 416,这一步的目的是为了让后面产生的卷积特征图宽高都为奇数,这样就可以产生一个center cell。作者观察到,大物体通常占据了图像的中间位置, 就可以只用中心的一个cell来预测这些物体的位置,否则就要用中间的4个cell来进行预测,这个技巧可稍稍提升效率。最后,YOLOv2使用了卷积层降采样(factor为32),使得输入卷积网络的416 * 416图片最终得到13 * 13的卷积特征图(416/32=13)。
加入了anchor boxes后,可以预料到的结果是召回率上升,准确率下降。我们来计算一下,假设每个cell预测9个建议框,那么总共会预测13 * 13 * 9 = 1521个boxes,而之前的网络仅仅预测7 * 7 * 2 = 98个boxes。具体数据为:没有anchor boxes,模型recall为81%,mAP为69.5%;加入anchor boxes,模型recall为88%,mAP为69.2%。这样看来,准确率只有小幅度的下降,而召回率则提升了7%,说明可以通过进一步的工作来加强准确率,的确有改进空间。
Dimension Clusters(维度聚类)
作者在使用anchor的时候遇到了两个问题,第一个是anchor boxes的宽高维度往往是精选的先验框(hand-picked priors),虽说在训练过程中网络也会学习调整boxes的宽高维度,最终得到准确的bounding boxes。但是,如果一开始就选择了更好的、更有代表性的先验boxes维度,那么网络就更容易学到准确的预测位置。
和以前的精选boxes维度不同,作者使用了K-means聚类方法类训练bounding boxes,可以自动找到更好的boxes宽高维度。传统的K-means聚类方法使用的是欧氏距离函数,也就意味着较大的boxes会比较小的boxes产生更多的error,聚类结果可能会偏离。为此,作者采用的评判标准是IOU得分(也就是boxes之间的交集除以并集),这样的话,error就和box的尺度无关了,最终的距离函数为:
作者通过改进的K-means对训练集中的boxes进行了聚类,判别标准是平均IOU得分,聚类结果如下图:
可以看到,平衡复杂度和IOU之后,最终得到k值为5,意味着作者选择了5种大小的box维度来进行定位预测,这与手动精选的box维度不同。结果中扁长的框较少,而瘦高的框更多(这符合行人的特征),这种结论如不通过聚类实验恐怕是发现不了的。
当然,作者也做了实验来对比两种策略的优劣,如下图,使用聚类方法,仅仅5种boxes的召回率就和Faster R-CNN的9种相当。说明K-means方法的引入使得生成的boxes更具有代表性,为后面的检测任务提供了便利。
Direct location prediction(直接位置预测)
那么,作者在使用anchor boxes时发现的第二个问题就是:模型不稳定,尤其是在早期迭代的时候。大部分的不稳定现象出现在预测box的 (x,y) 坐标上了。在区域建议网络中,预测 (x,y) 以及 tx,ty 使用的是如下公式:
后来修改博文时,发现这个公式有误,作者应该是把加号写成了减号。理由如下,anchor的预测公式来自于Faster-RCNN,我们来看看人家是怎么写的:
公式中,符号的含义解释一下:x 是坐标预测值,xa 是anchor坐标(预设固定值),x? 是坐标真实值(标注信息),其他变量 y,w,h 以此类推,t 变量是偏移量。然后把前两个公式变形,就可以得到正确的公式:
x=(tx?wa)+xay=(ty?wa)+ya
这个公式的理解为:当预测 tx=1,就会把box向右边移动一定距离(具体为anchor box的宽度),预测 tx=?1,就会把box向左边移动相同的距离。
这个公式没有任何限制,使得无论在什么位置进行预测,任何anchor boxes可以在图像中任意一点结束(我的理解是,tx 没有数值限定,可能会出现anchor检测很远的目标box的情况,效率比较低。正确做法应该是每一个anchor只负责检测周围正负一个单位以内的目标box)。模型随机初始化后,需要花很长一段时间才能稳定预测敏感的物体位置。
在此,作者就没有采用预测直接的offset的方法,而使用了预测相对于grid cell的坐标位置的办法,作者又把ground truth限制在了0到1之间,利用logistic回归函数来进行这一限制。
现在,神经网络在特征图(13 *13 )的每个cell上预测5个bounding boxes(聚类得出的值),同时每一个bounding box预测5个坐值,分别为 tx,ty,tw,th,to ,其中前四个是坐标,to是置信度。如果这个cell距离图像左上角的边距为 (cx,cy) 以及该cell对应box(bounding box prior)的长和宽分别为 (pw,ph),那么预测值可以表示为:
这几个公式参考上面Faster-RCNN和YOLOv1的公式以及下图就比较容易理解。tx,ty 经sigmod函数处理过,取值限定在了0~1,实际意义就是使anchor只负责周围的box,有利于提升效率和网络收敛。σ 函数的意义没有给,但估计是把归一化值转化为图中真实值,使用 e 的幂函数是因为前面做了 ln 计算,因此,σ(tx)是bounding box的中心相对栅格左上角的横坐标,σ(ty)是纵坐标,σ(to)是bounding box的confidence score。
定位预测值被归一化后,参数就更容易得到学习,模型就更稳定。作者使用Dimension Clusters和Direct location prediction这两项anchor boxes改进方法,mAP获得了5%的提升。
Fine-Grained Features(细粒度特征)
上述网络上的修改使YOLO最终在13 * 13的特征图上进行预测,虽然这足以胜任大尺度物体的检测,但是用上细粒度特征的话,这可能对小尺度的物体检测有帮助。Faser R-CNN和SSD都在不同层次的特征图上产生区域建议(SSD直接就可看得出来这一点),获得了多尺度的适应性。这里使用了一种不同的方法,简单添加了一个转移层( passthrough layer),这一层要把浅层特征图(分辨率为26 * 26,是底层分辨率4倍)连接到深层特征图。
这个转移层也就是把高低两种分辨率的特征图做了一次连结,连接方式是叠加特征到不同的通道而不是空间位置,类似于Resnet中的identity mappings(对Resnet这一机制几乎不懂,等看懂了再行补充)。这个方法把26 * 26 * 512的特征图连接到了13 * 13 * 2048的特征图,这个特征图与原来的特征相连接。YOLO的检测器使用的就是经过扩张的特征图,它可以拥有更好的细粒度特征,使得模型的性能获得了1%的提升。(这段理解的也不是很好,要看到网络结构图才能清楚)
Multi-Scale Training
原来的YOLO网络使用固定的448 * 448的图片作为输入,现在加入anchor boxes后,输入变成了416 * 416。目前的网络只用到了卷积层和池化层,那么就可以进行动态调整(意思是可检测任意大小图片)。作者希望YOLOv2具有不同尺寸图片的鲁棒性,因此在训练的时候也考虑了这一点。
不同于固定输入网络的图片尺寸的方法,作者在几次迭代后就会微调网络。没经过10次训练(10 epoch),就会随机选择新的图片尺寸。YOLO网络使用的降采样参数为32,那么就使用32的倍数进行尺度池化{320,352,…,608}。最终最小的尺寸为320 * 320,最大的尺寸为608 * 608。接着按照输入尺寸调整网络进行训练。
这种机制使得网络可以更好地预测不同尺寸的图片,意味着同一个网络可以进行不同分辨率的检测任务,在小尺寸图片上YOLOv2运行更快,在速度和精度上达到了平衡。
在小尺寸图片检测中,YOLOv2成绩很好,输入为228 * 228的时候,帧率达到90FPS,mAP几乎和Faster R-CNN的水准相同。使得其在低性能GPU、高帧率视频、多路视频场景中更加适用。
在大尺寸图片检测中,YOLOv2达到了先进水平,VOC2007 上mAP为78.6%,仍然高于平均水准,下图是YOLOv2和其他网络的成绩对比:
Further Experiments
作者在VOC2012上对YOLOv2进行训练,下图是和其他方法的对比。YOLOv2精度达到了73.4%,并且速度更快。同时YOLOV2也在COCO上做了测试(IOU=0.5),也和Faster R-CNN、SSD作了成绩对比。总的来说,比上不足,比下有余。
YOLOv2速度的改进(Faster)
YOLO一向是速度和精度并重,作者为了改善检测速度,也作了一些相关工作。
大多数检测网络有赖于VGG-16作为特征提取部分,VGG-16的确是一个强大而准确的分类网络,但是复杂度有些冗余。224 * 224的图片进行一次前向传播,其卷积层就需要多达306.9亿次浮点数运算。
YOLOv2使用的是基于Googlenet的定制网络,比VGG-16更快,一次前向传播仅需85.2亿次运算。可是它的精度要略低于VGG-16,单张224 * 224取前五个预测概率的对比成绩为88%和90%(低一点点也是可以接受的)。
Darknet-19
YOLOv2使用了一个新的分类网络作为特征提取部分,参考了前人的先进经验,比如类似于VGG,作者使用了较多的3 * 3卷积核,在每一次池化操作后把通道数翻倍。借鉴了network in network的思想,网络使用了全局平均池化(global average pooling),把1 * 1的卷积核置于3 * 3的卷积核之间,用来压缩特征。也用了batch normalization(前面介绍过)稳定模型训练。
最终得出的基础模型就是Darknet-19,如下图,其包含19个卷积层、5个最大值池化层(maxpooling layers ),下图展示网络具体结构。Darknet-19运算次数为55.8亿次,imagenet图片分类top-1准确率72.9%,top-5准确率91.2%。
Training for classification
作者使用Darknet-19在标准1000类的ImageNet上训练了160次,用的随机梯度下降法,starting learning rate 为0.1,polynomial rate decay 为4,weight decay为0.0005 ,momentum 为0.9。训练的时候仍然使用了很多常见的数据扩充方法(data augmentation),包括random crops, rotations, and hue, saturation, and exposure shifts。 (这些训练参数是基于darknet框架,和caffe不尽相同)
初始的224 * 224训练后,作者把分辨率上调到了448 * 448,然后又训练了10次,学习率调整到了0.001。高分辨率下训练的分类网络在top-1准确率76.5%,top-5准确率93.3%。
Training for detection
分类网络训练完后,就该训练检测网络了,作者去掉了原网络最后一个卷积层,转而增加了三个3 * 3 * 1024的卷积层(可参考darknet中cfg文件),并且在每一个上述卷积层后面跟一个1 * 1的卷积层,输出维度是检测所需的数量。对于VOC数据集,预测5种boxes大小,每个box包含5个坐标值和20个类别,所以总共是5 * (5+20)= 125个输出维度。同时也添加了转移层(passthrough layer ),从最后那个3 * 3 * 512的卷积层连到倒数第二层,使模型有了细粒度特征。
作者的检测模型以0.001的初始学习率训练了160次,在60次和90次的时候,学习率减为原来的十分之一。其他的方面,weight decay为0.0005,momentum为0.9,依然使用了类似于Faster-RCNN和SSD的数据扩充(data augmentation)策略。
YOLOv2分类的改进(Stronger)
这一部分,作者使用联合训练方法,结合词向量树(wordtree)等方法,使YOLOv2的检测种类扩充到了上千种,具体内容待续。
总结和展望
作者大概说的是,之前的技术改进对检测任务很有帮助,在以后的工作中,可能会涉足弱监督方法用于图像分割。监督学习对于标记数据的要求很高,未来要考虑弱标记的技术,这将会极大扩充数据集,提升训练量。
另外推荐一篇损失函数源码解读:http://blog.csdn.net/xueyingxue001/article/details/72831551
region_layer.c box get_region_box(float *x, float *biases, int n, int index, int i, int j, int w, int h, int stride) { box b; b.x = (i + x[index + 0*stride]) / w; b.y = (j + x[index + 1*stride]) / h; b.w = exp(x[index + 2*stride]) * biases[2*n] / w; b.h = exp(x[index + 3*stride]) * biases[2*n+1] / h; //printf("%f/%d/%d - %f/%f/%f/%f\n", x[index + 2*stride], w, h, b.x, b.y, b.w, b.h); return b; } float delta_region_box(box truth, float *x, float *biases, int n, int index, int i, int j, int w, int h, float *delta, float scale, int stride) { box pred = get_region_box(x, biases, n, index, i, j, w, h, stride); float iou = box_iou(pred, truth); float tx = (truth.x*w - i); float ty = (truth.y*h - j); float tw = log(truth.w*w / biases[2*n]); float th = log(truth.h*h / biases[2*n + 1]); delta[index + 0*stride] = scale * (tx - x[index + 0*stride]); delta[index + 1*stride] = scale * (ty - x[index + 1*stride]); delta[index + 2*stride] = scale * (tw - x[index + 2*stride]); delta[index + 3*stride] = scale * (th - x[index + 3*stride]); return iou; } void forward_region_layer() { ... for (b = 0; b < l.batch; ++b) { if(l.softmax_tree){ // 没执行 } // 下面的 for 循环是计算没有物体的 box 的 confidence 的 loss // 1, 遍历所有格子以及每个格子的 box,计算每个 box 与真实 box 的 best_iou // 2, 先不管三七二十一,把该 box 当成没有目标来算 confidence 的 loss // 3, 如果当前 box 的 best_iou > 阈值,则说明该 box 是有物体的,于是上面哪行计算的 loss 就不算数,因此把刚才计算的 confidence 的 loss 清零。 // 假设图片被分成了 13 * 13 个格子,那 l.h 和 l.w 就为 13 // 于是要遍历所有的格子,因此下面就要循环 13 * 13 次 for (j = 0; j < l.h; ++j) { for (i = 0; i < l.w; ++i) { // 每个格子会预测 5 个 boxes,因此这里要循环 5 次 for (n = 0; n < l.n; ++n) { // 获得 box 的 index int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0); // 获得 box 的预测 x, y, w, h,注意都是相对值,不是真实坐标 box pred = get_region_box(l.output, l.biases, n, box_index, i, j, l.w, l.h, l.w*l.h); float best_iou = 0; // 下面的循环 30 次我是这么理解的: // 假设一张图片中最多包含 30 个物体,于是对每一个物体求 iou // PS:我看了很久都没找到这个 30 能和什么关联上,于是猜测 30 的含义是“假设一张图片中最多包含 30 个物体”。 for(t = 0; t < 30; ++t){ // get truth_box‘s x, y, w, h box truth = float_to_box(net.truth + t*5 + b*l.truths, 1); printf("\ti=%d, j=%d, n=%d, t=%d\n", i, j, n, t); // 遍历完图片中的所有物体后退出 if(!truth.x){ break; } float iou = box_iou(pred, truth); if (iou > best_iou) { best_iou = iou; } } // 获得预测结果中保存 confidence 的 index int obj_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 4); avg_anyobj += l.output[obj_index]; // 这里先不管三七二十一,直接把该 box 当成没有目标来算 loss 了。 l.delta[obj_index] = l.noobject_scale * (0 - l.output[obj_index]); // 然后再做个判断,如果当期 box 计算的 best_iou > 阈值的话,则说明该 box 是有物体的,于是上面哪行计算的 loss 就不算数,因此清零。 if (best_iou > l.thresh) { l.delta[obj_index] = 0; } // 查了查代码,这里是“如果已经训练的图片数量 < 12800 的话则进入循环”,为什么要判断这玩意.... if(*(net.seen) < 12800){ // 单纯的获取“以当前格子中心”为 x, y 的 box 作为 truth box box truth = {0}; truth.x = (i + .5)/l.w; truth.y = (j + .5)/l.h; truth.w = l.biases[2*n]/l.w; truth.h = l.biases[2*n+1]/l.h; // 将预测的 tx, ty, tw, th 和 实际box计算得出的 tx‘,ty‘, tw‘, th‘ 的差存入 l.delta delta_region_box(truth, l.output, l.biases, n, box_index, i, j, l.w, l.h, l.delta, .01, l.w*l.h); } } } } // 下面的循环 30 次中的 30 这个数我看了很久都没找到这个 30 能和什么关联上,于是猜测 30 的含义是:“假设一张图片中最多包含 30 个物体” // 因此下面是“直接遍历一张图片中的所有已标记的物体的中心所在的格子,然后计算 loss”,而不是“遍历那 13*13 个格子后判断当期格子有无物体,然后计算 loss” for(t = 0; t < 30; ++t){ // get truth_box‘s x, y, w, h box truth = float_to_box(net.truth + t*5 + b*l.truths, 1); // 如果本格子中不包含任何物体的中心,则跳过 if(!truth.x) break; float best_iou = 0; int best_n = 0; // 假设图片被分成了 13 * 13 个格子,那 l.h 和 l.w 就为 13 // 于是要遍历所有的格子,因此下面就要循环 13 * 13 次 // 也因此,i 和 j 就是真实物品中心所在的格子的“行”和“列” i = (truth.x * l.w); j = (truth.y * l.h); printf("%d %f %d %f\n", i, truth.x*l.w, j, truth.y*l.h); box truth_shift = truth; // 上面获得了 truth box 的 x,y,w,h,这里讲 truth box 的 x,y 偏移到 0,0,记为 truth_shift.x, truth_shift.y,这么做是为了方便计算 iou truth_shift.x = 0; truth_shift.y = 0; printf("index %d %d\n",i, j); // 每个格子会预测 5 个 boxes,因此这里要循环 5 次 for(n = 0; n < l.n; ++n){ // 获得预测结果中 box 的 index int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0); // 获得 box 的预测 x, y, w, h,注意都是相对值,不是真实坐标 box pred = get_region_box(l.output, l.biases, n, box_index, i, j, l.w, l.h, l.w*l.h); // 这里用 anchor box 的值 / l.w 和 l.h 作为预测的 w 和 h // ps: 我打印了下 l.bias_match,它的值是 1,说明是能走到里面的,而之所以这么做的原因我是这么理解的: // 在 yolo v2 的论文中提到:预测 box 的 w,h 是根据 anchors 生成(anchors 是用 k-means 聚类得出的最优结果),即: // w = exp(tw) * l.biases[2*n] / l.w // h = exp(th) * l.biases[2*n+1] / l.h // 不过为什么把 exp() 部分省去还有些疑惑,希望有知道原因的大佬能帮忙解答下。 if(l.bias_match){ pred.w = l.biases[2*n]/l.w; pred.h = l.biases[2*n+1]/l.h; } printf("pred: (%f, %f) %f x %f\n", pred.x, pred.y, pred.w, pred.h); // 上面 truth box 的 x,y 移动到了 0,0 ,因此预测 box 的 x,y 也要移动到 0,0,这么做是为了方便计算 iou pred.x = 0; pred.y = 0; float iou = box_iou(pred, truth_shift); if (iou > best_iou){ best_iou = iou; best_n = n; } } printf("%d %f (%f, %f) %f x %f\n", best_n, best_iou, truth.x, truth.y, truth.w, truth.h); // 根据上面的 best_n 找出 box 的 index int box_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, 0); // 计算 box 和 truth box 的 iou float iou = delta_region_box(truth, l.output, l.biases, best_n, box_index, i, j, l.w, l.h, l.delta, l.coord_scale * (2 - truth.w*truth .h), l.w*l.h); // 如果 iou > .5,recall +1 if(iou > .5) recall += 1; avg_iou += iou; //l.delta[best_index + 4] = iou - l.output[best_index + 4]; // 根据 best_n 找出 confidence 的 index int obj_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, 4); avg_obj += l.output[obj_index]; // 因为运行到这里意味着该格子中有物体中心,所以该格子的 confidence 就是 1, 而预测的 confidence 是 l.output[obj_index],所以根据公式有下式 l.delta[obj_index] = l.object_scale * (1 - l.output[obj_index]); if (l.rescore) { // 用 iou 代替上面的 1(经调试,l.rescore = 1,因此能走到这里) l.delta[obj_index] = l.object_scale * (iou - l.output[obj_index]); } // 获得真实的 class int class = net.truth[t*5 + b*l.truths + 4]; if (l.map) class = l.map[class]; // 获得预测的 class 的 index int class_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, 5); // 把所有 class 的预测概率与真实 class 的 0/1 的差 * scale,然后存入 l.delta 里相应 class 序号的位置 delta_region_class(l.output, l.delta, class_index, class, l.classes, l.softmax_tree, l.class_scale, l.w*l.h, &avg_cat); ++count; ++class_count; } } printf("\n"); // 现在,l.delta 中的每一个位置都存放了 class、confidence、x, y, w, h 的差,于是通过 mag_array 遍历所有位置,计算每个位置的平方的和后开根 // 然后利用 pow 函数求平方 *(l.cost) = pow(mag_array(l.delta, l.outputs * l.batch), 2); printf("Region Avg IOU: %f, Class: %f, Obj: %f, No Obj: %f, Avg Recall: %f, count: %d\n", avg_iou/count, avg_cat/class_count, avg_obj/count, a vg_anyobj/(l.w*l.h*l.n*l.batch), recall/count, count);