论文:YOLOv3: An Incremental Improvement (CVPR 2018) 代码:eriklindernoren/PyTorch-YOLOv3 Jupyter 代码梳理笔记:YOLOv3 Darknet
YOLO3 采用 Darknet-53 作为 Backbone(含有53个卷积层),借鉴残差网络,在一些层之间设置了Shortcut connections,弃用YOLOv2中最大池化操作,通过增大卷积层的步长降低特征图分辨率,网络架构如下:
与YOLOv2中的passthrough结构不同,在YOLO3更进一步采用了3个不同尺度的特征图来进行对象检测,三个输出尺寸为: S ∗ S ∗ 3 ∗ ( b o x a t r + c l a s s n u m ) \color{blue}S*S*3*(box_{atr}+class_{num}) S∗S∗3∗(boxatr+classnum),细节部分从下面两个图可以看出(出处应该在参考文献中)
了解如何构建target,就清楚Loss的组成了
def build_targets(pred_boxes, target, anchors, num_anchors, num_classes, dim, ignore_thres, img_dim): nB = target.size(0) #batch个数 16 nA = num_anchors #锚框个数 3 nC = num_classes #数据集类别数 80 dim = dim #feature map相对于原图的缩放倍数13 # 初始化参数 mask = torch.zeros(nB, nA, dim, dim) #[16,3,13,13] 全0 conf_mask = torch.ones(nB, nA, dim, dim) #[16,3,13,13] 全1 tx = torch.zeros(nB, nA, dim, dim) #[16,3,13,13] 全0 ty = torch.zeros(nB, nA, dim, dim) #[16,3,13,13] 全0 tw = torch.zeros(nB, nA, dim, dim) #[16,3,13,13] 全0 th = torch.zeros(nB, nA, dim, dim) #[16,3,13,13] 全0 tconf = torch.zeros(nB, nA, dim, dim) #[16,3,13,13] 全0 tcls = torch.zeros(nB, nA, dim, dim, num_classes) #[16,3,13,13,80] 全0 # 为了计算一个batch中的recall召回率 nGT = 0 # 统计 真值框个数 GT ground truth nCorrect = 0 # 统计 预测出有物体的个数 (即 真值框 与 3个原始锚框与真值框iou最大的那个锚框对应的预测框 之间的iou > 0.5 为预测正确) # 遍历每一张图片 for b in range(nB): #遍历一张图片的所有物体 for t in range(target.shape[1]): if target[b, t].sum() == 0: # 即代表遍历完所有物体,continue直接开始下一次for循环(译者:使用break直接结束for循环更好) continue nGT += 1 # Convert to position relative to box # target真值框 坐标被归一化后[16,50,5] 值在0-1之间。故乘以 dim 将尺度转化为 13x13尺度下的真值框 gx = target[b, t, 1] * dim gy = target[b, t, 2] * dim gw = target[b, t, 3] * dim gh = target[b, t, 4] * dim # Get grid box indices 向下取整,获取网格框索引,即左上角偏移坐标 gi = int(gx) gj = int(gy) # Get shape of gt box [1,4] gt_box = torch.FloatTensor(np.array([0, 0, gw, gh])).unsqueeze(0) # Get shape of anchor box [3,4] 前两列全为0 后两列为 三个anchor的w、h anchor_shapes = torch.FloatTensor(np.concatenate((np.zeros((len(anchors), 2)), np.array(anchors)), 1)) # Calculate iou between gt and anchor shapes # 计算 一个真值框 与 对应的3个原始锚框 之间的iou anch_ious = bbox_iou(gt_box, anchor_shapes) # Where the overlap is larger than threshold set mask to zero (ignore) 当iou重叠率>阈值,则置为0 # conf_mask全为1 [16,3,13,13] 当一个真值框 与 一个原始锚框 之间的iou > 阈值时,则置为0。 # 即 将 负责预测物体的网格及 它周围的网格 都置为0 不参与训练,后面的代码会 将负责预测物体的网格再置为1。 conf_mask[b, anch_ious > ignore_thres] = 0 ########### 小于阈值(0.5)的就作为背景 # Find the best matching anchor box 找到 一个真值框 与 对应的3个原始锚框 之间的iou最大的 下标值 best_n = np.argmax(anch_ious) # Get ground truth box [1,4] gt_box = torch.FloatTensor(np.array([gx, gy, gw, gh])).unsqueeze(0) # Get the best prediction [1,4] # pred_boxes:在13x13尺度上的预测框 # pred_box:取出 3个原始锚框与 真值框 iou最大的那个锚框 对应的预测框 pred_box = pred_boxes[b, best_n, gj, gi].unsqueeze(0) # Masks [16,3,13,13] 全0 在3个原始锚框与 真值框 iou最大的那个锚框 对应的预测框位,即 负责预测物体的网格置为1 (此时它周围网格为0,思想类似nms) mask[b, best_n, gj, gi] = 1 # [16,3,13,13] 全1 然后将 负责预测物体的网格及 它周围的网格 都置为0 不参与训练 ,然后 将负责预测物体的网格再次置为1。 # 即总体思想为: 负责预测物体的网格 位置置为1,它周围的网格置为0。类似NMS 非极大值抑制 ################背景+Box_maxiou相当于rpn中的正样本+负样本,因为都要计入损失################ conf_mask[b, best_n, gj, gi] = 1 # Coordinates 坐标 gi= gx的向下取整。 gx-gi、gy-gj 为 网格内的 物体中心点坐标(0-1之间) # tx ty初始化全为0,在有真值框的网格位置写入 真实的物体中心点坐标 tx[b, best_n, gj, gi] = gx - gi ty[b, best_n, gj, gi] = gy - gj # Width and height # 论文中 13x13尺度下真值框=原始锚框 x 以e为底的 预测值。故预测值= log(13x13尺度下真值框 / 原始锚框 + 1e-16 ) tw[b, best_n, gj, gi] = math.log(gw/anchors[best_n][0] + 1e-16) th[b, best_n, gj, gi] = math.log(gh/anchors[best_n][1] + 1e-16) # One-hot encoding of label tcls[b, best_n, gj, gi, int(target[b, t, 0])] = 1 # Calculate iou between ground truth and best matching prediction 计算真值框 与 3个原始锚框与真值框iou最大的那个锚框对应的预测框 之间的iou iou = bbox_iou(gt_box, pred_box, x1y1x2y2=False) # [16,3,13,13] 全0,有真值框对应的网格位置为1 标明 物体中心点落在该网格中,该网格去负责预测物体 tconf[b, best_n, gj, gi] = 1 if iou > 0.5: nCorrect += 1 # nGT 统计一个batch中的真值框个数 # nCorrect 统计 一个batch预测出有物体的个数 # mask [16,3,13,13] 初始化全0 在3个原始锚框与 真值框 iou最大的那个锚框 对应的预测框位置置为1 # conf_mask [16,3,13,13] 初始化全1,之后的操作:负责预测物体的网格置为1,它周围网格置为0 # tx, ty [16,3,13,13] 初始化全为0,在有真值框的网格位置写入 真实的物体中心点坐标 # tw, th [16,3,13,13] 初始化全为0,该值为 真值框的w、h 按照公式转化为 网络输出时对应的真值(该值对应于 网络输出的真值) # tconf [16,3,13,13] 初始化全0,有真值框对应的网格位置为1 标明 物体中心点落在该网格中,该网格去负责预测物体 # tcls #[16,3,13,13,80] 初始化全0,经过one-hot编码后 在真实类别处值为1 return nGT, nCorrect, mask, conf_mask, tx, ty, tw, th, tconf, tcls由target可知YOLOv3的Loss依旧可以大体上分成两部分
预测有目标的框(与gt_box IoU最大):tx, ty, tw, th, tconf, tcls这些参数Loss作为背景的框(与gt_box IoU小于阈值的框):相当于没有目标而预测有目标,所以其置信度要计入损失,代码中将有目标和无目标的置信度位置用一个conf_mask合并了预测对象类别时不使用softmax,改成使用logistic的输出进行预测。这样能够支持多标签对象(比如一个人有Woman 和 Person两个标签)
聚类得到的9个Anchor被三个尺度的特征层平分,深层的特征图谱尺寸小,感受野大,分配的Anchor size也更大,darknet中的mask就是Anchor的索引号
FeatureFeature_sizeAnchors_sizeAnchors_numFeature 1 13 × 13 13\times 13 13×13[116,90]、[156,198]、[372,326] 13 × 13 × 3 13\times 13\times 3 13×13×3Feature 2 26 × 26 26\times 26 26×26[31,61]、[62,45]、[59,119] 26 × 26 × 3 26\times 26\times 3 26×26×3Feature 3 52 × 52 52\times 52 52×52[10,13]、[16,30]、[33,23] 52 × 52 × 3 52\times 52\times 3 52×52×3Colab上跑遇到的问题 问题一 TypeError: ‘NoneType’ object is not subscriptable 解决方法: datasets.py第128行
if self.augment: 改为: if self.augment and targets is not None: del checkpointpytorch 减小显存消耗,优化显存使用,避免out of memory
torch.backends.cudnn.benchmark = true设置这个 flag 可以让内置的 cuDNN 的 auto-tuner 自动寻找最适合当前配置的高效算法,来达到优化运行效率的问题 注意:1) 如果网络的输入数据维度或类型上变化不大,可以增加运行效率;2) 如果网络的输入数据在每次 iteration 都变化的话,会导致 cnDNN 每次都会去寻找一遍最优配置,这样反而会降低运行效率
【1】yolo系列之yolo v3【深度解析】 【2】YOLOv3 资源合集 【3】使用PyTorch从零开始实现YOLO-V3目标检测算法 【4】Pytorch版本yolov3源码阅读 【5】目标检测-基于Pytorch实现Yolov3(1)- 搭建模型 【6】PyTorch : Understanding Graphs, Automatic Differentiation and Autograd 【7】How to implement a YOLO (v3) object detector from scratch in PyTorch 【8】darknet 所有层功能说明 【9】PyTorch 中的 ModuleList 和 Sequential: 区别和使用场景 【10】pytorch 入坑三:nn module 【11】史上最详细的Pytorch版yolov3代码中文注释详解(一) 【12】PyTorch-YOLOv3错误集锦 【13】PyTorch实现yolov3代码详细解密 【14】物体检测One-Stage开山之作YOLO从v1到v3 【15】YOLO v3网络结构分析 【16】为什么 YOLOv3 用了 Focal Loss 后 mAP 反而掉了?
