Yolov3训练过程笔记

本篇博客写的基于Ubuntu系统下的yolov3训练配置过程,基于windows的可以参考AlexeyAB的Github博客地址: (AlexeyAB的Github)。当然,原作者Pjreddie的网页也有ubuntu配置过程可以参考:原作者pjreddie

0 数据准备阶段

首先,你需要下载训练数据集(或者自己的数据集),以Pascal-Voc数据集格式为例:
通过官网给出的voc_label.py生成对应的标签txt文件(训练自己数据集的时候注意修改一下路径)
脚本voc_label.py下载地址:https://pjreddie.com/media/files/voc_label.py

如果是使用Pascal-VOC进行生成txt的话,与voc_label.py同目录下面会有相对应的2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt
运行下面命令:

cat 2007_train.txt 2007_val.txt 2012_*.txt > train.txt
(如果你自己的数据集可以生成对应的txt文件,只需要改下voc_label.py中的文件路径即可)

0.1 bounding-boxes聚类估计Anchor大小

YOLO9000通过Kmeans聚类来预先估计anchor的大小,那么我们可以通过对Pascal-VOC数据集进行聚类来预先估计一下数据的anchor大小。该部分参考github上:Kmeans聚类anchor-boxes链接

但是,相对该链接的example.py部分修改如下:

import glob
import xml.etree.ElementTree as ET
import numpy as np
from kmeans import kmeans, avg_iou

ROOT_ANNOTATIONS_PATH = "/.../VOCdevkit/"  # your root path 
SUB_FILE = ['VOC2007', 'VOC2012']
CLUSTERS = 9

def load_dataset(path):
	dataset = []
    for i in range(len(SUB_FILE)):
        concrete_path = path + SUB_FILE[i] + '/Annotations'
    	for xml_file in glob.glob("{}/*xml".format(path)):
    		tree = ET.parse(xml_file)

    		height = int(tree.findtext("./size/height"))
    		width = int(tree.findtext("./size/width"))

    		for obj in tree.iter("object"):
    			xmin = np.float64(obj.findtext("bndbox/xmin")) / width
    			ymin = np.float64(obj.findtext("bndbox/ymin")) / height
    			xmax = np.float64(obj.findtext("bndbox/xmax")) / width
    			ymax = np.float64(obj.findtext("bndbox/ymax")) / height

                if xmax == xmin or ymax == ymin:
                    print(xml_file) # print the failed xml_file
    			dataset.append([xmax - xmin, ymax - ymin])
	return np.array(dataset)

data = load_dataset(ROOT_ANNOTATIONS_PATH)
out = kmeans(data, k=CLUSTERS)
print("Accuracy: {:.2f}%".format(avg_iou(data, out) * 100))
print("Boxes:\n {}-{}".format(out[:, 0]*416, out[:, 1]*416))

ratios = np.around(out[:, 0] / out[:, 1], decimals=2).tolist()
print("Ratios:\n {}".format(sorted(ratios)))

简单解释一下example.py的相关参数设置:ROOT_ANNOTATIONS_PATH 为根目录,举例Pascal-VOC数据集下路径为/VOCdevkit/。其次:SUB_FILE为VOC2007与VOC2012文件夹,通过迭代循环查找它们下面的Annotations的xml文件。CLUSTERS为聚类次数,可以根据具体情况来进行设置。

当然,上述kmeans-anchor-boxes链接还有一个主要的kmeans.py函数,简单贴一下其脚本:

import numpy as np

def iou(box, clusters):
    """
    Calculates the Intersection over Union (IoU) between a box and k clusters.
    :param box: tuple or array, shifted to the origin (i. e. width and height)
    :param clusters: numpy array of shape (k, 2) where k is the number of clusters
    :return: numpy array of shape (k, 0) where k is the number of clusters
    """
    x = np.minimum(clusters[:, 0], box[0])
    y = np.minimum(clusters[:, 1], box[1])
    if np.count_nonzero(x == 0) > 0 or np.count_nonzero(y == 0) > 0:
        raise ValueError("Box has no area")

    intersection = x * y
    box_area = box[0] * box[1]
    cluster_area = clusters[:, 0] * clusters[:, 1]
    iou_ = intersection / (box_area + cluster_area - intersection)

    return iou_

def avg_iou(boxes, clusters):
    """
    Calculates the average Intersection over Union (IoU) between a numpy array of boxes and k clusters.
    :param boxes: numpy array of shape (r, 2), where r is the number of rows
    :param clusters: numpy array of shape (k, 2) where k is the number of clusters
    :return: average IoU as a single float
    """
    return np.mean([np.max(iou(boxes[i], clusters)) for i in range(boxes.shape[0])])

def translate_boxes(boxes):
    """
    Translates all the boxes to the origin.
    :param boxes: numpy array of shape (r, 4)
    :return: numpy array of shape (r, 2)
    """
    new_boxes = boxes.copy()
    for row in range(new_boxes.shape[0]):
        new_boxes[row][2] = np.abs(new_boxes[row][2] - new_boxes[row][0])
        new_boxes[row][3] = np.abs(new_boxes[row][3] - new_boxes[row][1])
    return np.delete(new_boxes, [0, 1], axis=1)

def kmeans(boxes, k, dist=np.median):
    """
    Calculates k-means clustering with the Intersection over Union (IoU) metric.
    :param boxes: numpy array of shape (r, 2), where r is the number of rows
    :param k: number of clusters
    :param dist: distance function
    :return: numpy array of shape (k, 2)
    """
    rows = boxes.shape[0]

    distances = np.empty((rows, k))
    last_clusters = np.zeros((rows,))

    np.random.seed()
    # the Forgy method will fail if the whole array contains the same rows
    clusters = boxes[np.random.choice(rows, k, replace=False)]

    while True:
        for row in range(rows):
            distances[row] = 1 - iou(boxes[row], clusters)

        nearest_clusters = np.argmin(distances, axis=1)

        if (last_clusters == nearest_clusters).all():
            break
        for cluster in range(k):
            clusters[cluster] = dist(boxes[nearest_clusters == cluster], axis=0)

        last_clusters = nearest_clusters

    return clusters

假设聚类出来Boxes输出为:[12 24 53 76 19 87 93 32 65]-[32 43 66 112 57 132 176 198 312]。那么对应的anchors为:[12 32] [24 43]…以此类推,将其修改cfg文件夹下面对应的yolov3-voc.cfg文件中的anchors即可,每次进行聚类出来的anchor-box可能不一致,这是由于kmeans随机初始化聚类,具体哪个anchor效果好,只能靠多次尝试一下了。

1 环境配置阶段

去原作者github官网下载darknet的的最新压缩包后进行解压缩。
解压缩之后:cd darknet-master

注意:先别急着进行make
如果你需要使用GPU、CUDNN、OpenCV等进行训练的话,那么你首先打开makefile文件,在前面几个对应的位置将0修改为1,如果你使用CPU进行训练,那么请你直接跳过这一步。
修改过后,可以进行make命令。

下面就是设置你的训练相关参数配置了:

1 首先修改darknet文件下面的cfg文件夹下面的voc.data

classes = 20                              # 需要修改成为你的数据集类别数
train = <path-to-voc>/train.txt           # 前文说到的生成train.txt路径
val   = <path-to-voc>/2007_test.txt       # 前文说到的生成test路径
names = data/voc.names                    # 你自己数据集的类别名称
backup= backup                            # 存储训练模型路径文件夹

首先你修改你一共有多少个类别数,train.txt与2007_test.txt路径设置,voc.names则是你检测的目标类别名称,backup则是你存储训练模型的路径。

当然,此时的你还不能进行训练,需要修改一下参数:

./cfg/voc.names 文件下面加入你自己训练数据集的类别。

./cfg/yolov3-voc.cfg 这个文件打开,是yolov3的网络配置文件,首先你需要将前面几行的Train参数注释取消,Test部分注释。同时,搜索文件中出现classes 的变量,修改为你对应的训练类别数目,同时上面的filter = (classes + 5) * 3

整个yolov3网络中修改classes总共三个地方,同时对应的filter也是三个地方。

当然,你还可以调节batch_size,学习率等一些训练参数。

下载yolov3 训练的预训练权重:darknet53.conv.74 链接

2 训练+验证模型阶段

好了,设置完成后可以进行训练:运行下面命令

./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74 -gpus 0,1,2,3

gpus可以根据自己需要设置,没有的话cpu不需要设置

训练结束后,你会在backup文件夹下面看到yolov3-voc_final.weights

2.1 计算模型的mAP

运行命令:

./darknet detector valid ./cfg/voc.data ./cfg/yolov3-voc.cfg ./backup/yolov3-voc_final.weights -gpus=0,1

这里如果你不写 -out 算法会在results文件夹下面默认生成对应的类别标签。
然后你将results文件夹下面的文件comp4_det_test_xxx.txt改为对应的类别xxx.txt
例如:results生成里面存在comp4_det_test_dog.txt 改为 dog.txt

OK, 然后你可以运行mAp_voc2007.py(文末百度网盘下载)进行在测试集上面验证yolov3训练的模型效果。其中mAp_voc2007.py里面的main函数文件夹需要你修改一下classes名字,生成的results路径,xmlpath与imgpath路径即可。

2.2 当然训练结束后可以Fine-Tuning你的yolov3模型:

(将你的yolov3-voc_final.weights名称改为yolov3.weights 名称修改任意,只要对应下面的命令模型名称一致即可)

./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg yolov3.weights -gpus=0,1 (-clear 如果加上clear表示重头开始训练)

模型微调示例
1、添加clear

     ./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg ./backup/yolov3.weights -gpus=0,1 -clear

2、不添加clear进行第一个模型基础上微调

修改yolov3-voc.cfg中的max_batches与steps参数

    ./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg ./backup/yolov3.weights  -gpus=0,1
3 测试阶段

测试图片:
首先测试阶段需要将yolov3-voc.cfg文件中的Train模块注释,Test注释取消。

./darknet detector test ./cfg/voc.data ./cfg/yolov3-voc.cfg ./backup/yolov3-voc_final.weights data/xxx.jpg

测试视频demo:

./darknet detector demo ./cfg/voc.data ./cfg/yolov3-voc.cfg ./backup/yolov3-voc_final.weights

此处这里的voc.data 与yolov3-voc.cfg 是你训练自己的数据集类别,在环境配置阶段应该已经完成。

4 训练过程相关问题汇总

1、 使用yolov3训练时候出现:Can’t open label file, xxxx

这种一般就是你的文件路径设置不对, 查看一下voc.data中训练集与测试集路径问题。

还有一种情况就是,你的图片文件后缀名随机的包含多个.jpg等情况,darknet中代码使用find_replace()进行替换文件的.jpg 与 .txt 这样会导致问题。所以你可以查看一下是不是数据集命名的问题。

例如:你的图片名称为 qaijf.jpg!.png87594.jpg 这种的其实就会出现问题,因为utils.c 文件中find_replace()函数会替换图片名称中出现.jpg的情况(.png .tif等一样会被替换)。

推荐解决方法:
如果你的标签数据已经标注好了,建议使用Generate_Random_image_xml.py(文末百度网盘下载)进行图像数据与标签数据重命名,这里重命名只是改变为数字组成的字符串。因为如果你随机生成字母+数字的话,如果子串里面存在raw将会自动替换为labels,建议不要随机生成字符串。

2 如果你在运行脚本mAp_voc2007.py情况下出现cPickle相关问题

一般是python2.x与python3.x系列下面的pickle没有对应上。如果你使用python2.x版本,那么不需要改动。如果你使用python3.x系列的话,可能需要将cPickle改为pickle,对应位置函数cPickle前缀改为pickle。

3 如果你修改darknet函数里面的c代码,记得要重新make一下。

后续发现其它的训练过程问题,会添加至本博文中。之后,会添加基于windows训练的环境配置过程。推荐看下AlexeyAB的github会有详细的说明。最后,感谢原作者提供的训练说明!如果本文介绍有错误,还请批评指正!

文中py文件下载地址:
链接:https://pan.baidu.com/s/1ugtfpUlhp2v63dXHqSox1A
提取码:rjcs

参考文献:

https://pjreddie.com/darknet/yolo/

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Linux创始人LinusTorvalds有一句名言:Talk is cheap, Show me the code.(冗谈不够,放码过来!)。 代码阅读是从入门到提高的必由之路。尤其对深度学习,许多框架隐藏了神经网络底层的实现,只能在上层调包使用,对其内部原理很难认识清晰,不利于进一步优化和创新。  YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。YOLOv3的实现Darknet是使用C语言开发的轻型开源深度学习框架,依赖少,可移植性好,可以作为很好的代码阅读案例,让我们深入探究其实现原理。  本课程将解析YOLOv3的实现原理和源码,具体内容包括: YOLO目标检测原理  神经网络及Darknet的C语言实现,尤其是反向传播的梯度求解和误差计算 代码阅读工具及方法 深度学习计算的利器:BLAS和GEMM GPU的CUDA编程方法及在Darknet的应用 YOLOv3的程序流程及各层的源码解析本课程将提供注释后的Darknet的源码程序文件。  除本课程《YOLOv3目标检测:原理与源码解析》外,本人推出了有关YOLOv3目标检测的系列课程,包括:   《YOLOv3目标检测实战:训练自己的数据集》  《YOLOv3目标检测实战:交通标志识别》  《YOLOv3目标检测:原理与源码解析》  《YOLOv3目标检测:网络模型改进方法》 建议先学习课程《YOLOv3目标检测实战:训练自己的数据集》或课程《YOLOv3目标检测实战:交通标志识别》,对YOLOv3的使用方法了解以后再学习本课程。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值