整个训练分两部分
- 对 DarkNet53 的预训练
- 基于 DarkNet53 进行物体检测训练
原图尺寸:input_shape
标签:原始标签 box 为 (class, xmin, ymin, xmax, ymax),xmin, ymin, xmax, ymax 是左上角为坐标轴相对于原图的偏移。
anchor 总共 9 个,三个为一组分给三组输出,其中 anchor 的 0,1,2 分给 32x32 的输出,3,4,5 分给 16x16 的输出,6,7,8分给 8x8 的输出。anchor 的生产通过 k-mean 算法生成(参见附录)。
Darknet53 训练
预处理
random crops, rotation, hue, saturation, exposure shift 等。最常用的是 Alex 的预处理方式,四个角剪切 + 中心,翻转,再四个角 + 中心
训练
用 ImageNet 数据集经过 DarkNet53 + avgpool + softmax 之后,得到 1000 分类。
训练过程:
- 在 224x224x3 以 0.1 的学习速率,weight decay 0.0005 momentum 0.9 训练 160 epoch;
- 448x448x3 上以 learning rate 10-3 次方继续训练 10 epoch
检测物体训练
图片尺寸 :input_shape = [height, width]
预处理
图片预处理
resize 为 256x256 并除以 255 进行归一化
标签预处理
boxes 转换为 (x,y, w,h, class) 格式得到 true_box,x, y, w, h 除以图片 input_shape 进行归一化
标签计算
- 将 boxes resize 到 13x13 得到 box_output,此时, box_output 的 x, y 中心正好落在 output 的某一个 grid cell
- box_output 中的每个元素 box 与所有 anchors 计算 IOU,找到 IoU 最大值对应的 anchor。
- 对每张图片的每个 box
# yolo_1 初始化为 [batch_size, grid_1_h, grid_1_w, num_anchors, 4 + 1 + num_class] 的全零矩阵
# yolo_2 初始化为 [batch_size, grid_2_h, grid_2_w, num_anchors, 4 + 1 + num_class] 的全零矩阵
# yolo_3 初始化为 [batch_size, grid_3_h, grid_3_w, num_anchors, 4 + 1 + num_class] 的全零矩阵
# grid_1 为 [13, 13] grid_2 为 [26x26] grid_3 为 [52, 52]
grids = [grid3, grid2, grid1]
yolos = [yolo_3, yolo_2, yolo_1]
i = floor(box_output[1]) # box_ouput 所属 grid_cell 的 height
j = floor(box_output[0]) # box_ouput 所属 grid_cell 的 width
anchor_index = 最大 IOU 对应的 anchor 索引
yolo = yolos[anchor_index // 3] # 通过 anchor 定位到所属 yolo
grid = yolos[anchor_index // 3]
yolo[image_index, i, j, anchor_index%3, 4] = 1
yolo[image_index, i, j, anchor_index%3, 0:4]=[x/grid[1],y/grid[0],log(w)/anchor.w, log(h)/anchor.h]
yolo[image_index, i, j, anchor_index%3, 5+c] = 1 # c 为标签中分类索引
#由上可以非常容易知道, yolos 就是生成的标签
# true_box 初始化为 [batch_size, 1, 1, 1, num_box_per_image, 4]
true_box[image_index, 0,0,0, box_index] = [x/input_shape[0], y/input_shape[1], w/input_shape[0], h/input_shape[1]] #box_index 为当前 box 在图片中索引
object_mask = box_output[..., 4] # 记录某个 grid_cell 的某个 anchor 是否包含物体
编码
对于 pred_1 (13x13) 有
# pred_1 为 [batch_size, grid_1_h, grid_1_w, num_anchors, 4 + 1 + num_classes]
# index 为 grid_1 的元素索引,(0,0),(0,1)...(w-1, h-1)
pred_box_xy = index + sigmoid(pred_1[..., 0:2]) / grid_1
pred_box_wh = exp(pred_1[..., 2:4]) * anchor / input_shape
pred_box_confidence = pred_1[..., 4]
pred_box_class = pred_1[..., 5:]
对 pred_2, pred_3 处理同上。
loss 计算
对于 pred_1
#计算 pred_box 与 true_box 的 IoU1,并且 IoU 小于 < 0.5 为 1,大于 0.5 为 0,这里有疑问?
pred_box_confidence = pred_box_confidence * IoU1
obj_scale = 5
noobj_scale = 1
wh_scale = exp(true_box_wh) * anchors / input_shape
wh_scale = 2 - wh_scale[0] * wh_scale[1]
# 标签
true_box_xy = yolo_1[image_index, i, j, anchor_index%3, 0:2]
true_box_wh = yolo_1[image_index, i, j, anchor_index%3, 2:4]
true_box_confidence = yolo_1[image_index, i, j, anchor_index%3, 4]
true_box_class = yolo_1[image_index, i, j, anchor_index%3, 5:]
# object_mask 使得只有对应 grid_cell 对应的 anchor 有对应标签物体存在是才计算 loss
xy_loss = object_mask * square(pred_box_xy - true_box_xy) * wh_scale
wh_loss = object_mask * square(pred_box_wh - true_box_wh) * wh_scale
confidence_loss = object_mask * square(pred_box_confidence - true_box_confidence) * obj_scale + (1-object_mask) * square(conf_delta) * noobj_scale
#logits = tf.nn.sparse_softmax_cross_entropy_with_logits
class_loss = object_mask * logits(true_box_class, pred_box_class)
loss = sum(xy_loss) + sum(wh_loss) + sum(confidence_loss) + sum(class_loss)
验证
输入
图片尺寸 :input_shape = [height, width]
经过 DarkNet 之后,得到 yolo_output,维度为 [batch_size, height, width, num_anchors, 5 + num_classes]
其中最后一维依次为 [x,y,w,h, confidence, class_one_hot]
预处理
图片预处理
resize 为 256x256 并除以 255 进行归一化
编码
对于 pred_1 (13x13) 有
# pred_1 为 [batch_size, grid_1_h, grid_1_w, num_anchors, 4 + 1 + num_classes]
# index 为 grid_1 的元素索引,(0,0),(0,1)...(w-1, h-1)
pred_box_xy = index + sigmoid(pred_1[..., 0:2])
pred_box_wh = pred_1[..., 2:4]
pred_box_confidence = sigmoid(pred_1[..., 4])
pred_box_class = pred_box_confidence * softmax(pred_1[..., 5:]) > 0.5
对 pred_box,保留 pred_box_confidence > 0.5 的元素。
对 pred_2, pred_3 处理同上。
修正
对于任一 pred_box
- new_shape = round(image_shape * arg_min(input_shape/image_shape))
- offset = (input_shape-new_shape)/2./input_shape
- scale = input_shape/new_shape
- box_yx = (box_yx - offset) * scale; box_hw = box_hw * scales
- pred_box * [image_shape, image_shape]
- pred_box 转为 [ymin, xmin, ymax, xmax] 格式
比如 image_shape 为 [600,800],input_shape 为 [300, 500],那么 new_shape 为 [300, 400]
offset 为 [0, 0.125] scales 为 [0.5, 0.625]
过滤
对于任意 pred_box 进行以 0.45 为 IoU 阈值进行 NMS
以上就是 YoloV3 实现。
附录
k-mean 的 anchor 生成算法
- 随机初始化 一个聚类中心
- 算出每个元素与聚类中心的距离,在这里是 1-IOU
- 将每个元素划入对应的聚类,依据就是该元素与聚类中心距离最小,此时,已经有新的聚类中心
- 求各个聚类的均值作为新的中心
- 迭代 2-4 直到连续两次聚类中心都重合,表明已经找到合适的聚类中心