Ubuntu16使用自己的数据集训练YOLOV3-tiny教程、技巧及相关资料

运行环境:

Ubuntu16.04
GetForce MX250 4G
CUDA 9.0
CuDNN 7.0.5

安装darknet及下载yolov3与训练权重

  1. yolo官网
  2. 代码下载
git clone https://github.com/pjreddie/darknet
cd darknet
  1. 修改Makefile
GPU=1 #可选0/1
CUDNN=1 #可选0/1
OPENCV=1 #可选0/1
OPENMP=0
DEBUG=0
  1. 编译
make
  1. 下载权重
# yolov3.weights
wget https://pjreddie.com/media/files/yolov3.weights
# yolov-tiny.weight
wget https://pjreddie.com/media/files/yolov3-tiny.weights
  1. 测试图片
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg
./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg

成功后说明环境和代码没问题,即可进行下一步。

  • Q1:如果出现Yolov3 darknet: ./src/cuda.c:36: check_error: Assertion ‘0’failed.CUDA Error: out of memory darknet: ./src/cuda.c:36: check_error: Assertion ’0‘ failed.等报错
  • A1: 在第6歩的命令钱加上sudo即可。
  • A1: 显存不够,降低batchsize(后面会详细讲设置)

VOC数据集制作

  1. 首先认识VOC数据集的文件目录格式:
---VOCdevkit
	---VOC2007
		---Annotations # 存放xml文件
		---ImageSets
			---Main # 为了方便后续步骤中得到训练和测试图片的索引号
		---JPEGImages # 存放对应的图片文件
		---Labels # Yolo自带的脚本会生成此文件夹,不需提前创建。yolov生成的文件夹名称为‘labels’
  1. 图片打标签,并将自己打标好的xml文件和图片重新命名。
  • 使用labeling软件生成标签和xml文件。
  • 修改xml文件中可能影响后续训练的tag内容
  • 修改xml和对应jpg文件的编号
  • (上述步骤后续我单独写一篇博文分享。)
  • 可以参考这篇文章:将训练数据的图片和xml文件重新命名
  1. 将命名好的图片和文件放到/VOCdevkit/VOC2007文件夹下的对应位置,用于自动生成和存放随机抽取的训练和测试数据,并利用代码提取每个图片的索引号,提取代码位于VOC2007文件下,运行get_voc_2007_main.py即可得到,其中get_voc_2007_main.py的内容如下:[1]
import os
import random

trainval_percent = 0.66  #可以自己修改
train_percent = 0.9     #可以自己修改
xmlfilepath = 'Annotations'
txtsavepath = 'ImageSets\Main'
total_xml = os.listdir(xmlfilepath)

num=len(total_xml)
list=range(num)
tv=int(num*trainval_percent)
tr=int(tv*train_percent)
trainval= random.sample(list,tv)
train=random.sample(trainval,tr)

ftrainval = open('ImageSets/Main/trainval.txt', 'w')
ftest = open('ImageSets/Main/test.txt', 'w')
ftrain = open('ImageSets/Main/train.txt', 'w')
fval = open('ImageSets/Main/val.txt', 'w')

for i  in list:
    name=total_xml[i][:-4]+'\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)

ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
  1. 利用/darknet/script中的voc_label.py生成训练和测试的文件路径,需要修改自己的训练类别(这里以1类为例),去掉了与2012数据集相关的代码:
import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join

sets=[('2007', 'train'), ('2007', 'val'), ('2007', 'test')]

classes = ["Tire"]


def convert(size, box):
    dw = 1./(size[0])
    dh = 1./(size[1])
    x = (box[0] + box[1])/2.0 - 1
    y = (box[2] + box[3])/2.0 - 1
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x*dw
    w = w*dw
    y = y*dh
    h = h*dh
    return (x,y,w,h)

def convert_annotation(year, image_id):
    in_file = open('fisheyedata/Tiredfishevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
    out_file = open('fisheyedata/Tiredfishevkit/VOC%s/labels/%s.txt'%(year, image_id), 'w')
    tree=ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult)==1:
            continue
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text), float(xmlbox.find('ymax').text))
        bb = convert((w,h), b)
        out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

wd = getcwd()

for year, image_set in sets:
    if not os.path.exists('fisheyedata/Tiredfishevkit/VOC%s/labels/'%(year)):
        os.makedirs('fisheyedata/Tiredfishevkit/VOC%s/labels/'%(year))
    image_ids = open('fisheyedata/Tiredfishevkit/VOC%s/ImageSets/Main/%s.txt'%(year, image_set)).read().strip().split()
    list_file = open('%s_%s.txt'%(year, image_set), 'w')
    for image_id in image_ids:
        list_file.write('%s/fisheyedata/Tiredfishevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
        convert_annotation(year, image_id)
    list_file.close()

os.system("cat 2007_train.txt 2007_val.txt > train.txt")
os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt > train.all.txt")

  • 在darknet主目录可以得到:2007_test.txt、2007_train.txt、2007_val.txt、train.txt、 train.all.txt,我们暂时先用到 train.txt和 2007_test.txt,主要是图片的存放路径。
  • 在/VOCdevkit/VOC2007 中还生成了一个labels文件夹,主要以txt文件形式记录每张图片的打标签的bouding box位置。其标注格式如下[2]:
<object-class-id> <center-x> <center-y> <width> <height> 

object-class-id是类别id,这里只有雪人一类,故这里统一为1,

center-x是目标的中心x坐标并除以图像宽度归一化了,

center-y是目标的中心y坐标并除以图像高度归一化了,

width为目标的宽度并除以图像宽度归一化了,

height为目标的宽度并除以图像高度归一化了。
  1. 修改data/voc.names
    注意:一定要和之前配置文件中的名称保持一致,注意大小写也要一致。
    一行一个类别名称。

  2. 修改cfg/voc.data

classes= 1 # 数据集类别数
train  = ~/train.txt # train.txt所在路径
valid  = ~/2007_test.txt # 2007_test.txt所在路径
names = data/voc.names  # voc.names所在路径
backup = backup
  1. 修改cfg/yolov3-tiny.cfg

将如下的Training参数打开,关闭Testing参数

[net]
#Testing #注释
#batch=1 #注释
#subdivisions=1 #注释
#Training
batch=64 
subdivisions=16
  • batch和subdivisions两个参数[3]

batch 批尺寸,每一次迭代送到网络的图片数量。batch * iteration得到的就是一个epoch内训练的样本数。增大这个可以让网络在较少的迭代次数内完成一个epoch。在固定最大迭代次数的前提下,增加batch会延长训练时间,但会更好的寻找到梯度下降的方向。如果你显存够大,可以适当增大这个值来提高内存利用率。这个值是需要大家不断尝试选取的,过小的话会让训练不够收敛,过大会陷入局部最优。

subdivisions 如果内存不够大,会将batch分割为subdivisions个子batch,每个子batch大小为batch/subdivisions。子batch一份一份的跑完后,在一起打包算作完成一次iteration。这样会降低对显存的占用情况。如果设置这个参数为1的话就是一次性把所有batch的图片都丢到网络里,如果为2的话就是一次丢一半。

找到如下位置(以yolo为关键字),修改filters和classes,整个文本共有2个filters和2个classes需要修改

[convolutional]
size=1
stride=1
pad=1
filters=18  #### 改为3*(classes +5)
activation=linear

[yolo]
mask = 3,4,5
anchors = 10,14,  23,27,  37,58,  81,82,  135,169,  344,319
classes=1   #### 改为自己的数目
num=6
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=0 #多尺度训练,显存小的建议改为0

训练模型

# 获取yolov3-tiny.conv.15
./darknet partial ./cfg/yolov3-tiny.cfg ./yolov3-tiny.weights ./yolov3-tiny.conv.15 15
# 开始训练
sudo ./darknet detector train cfg/voc.data cfg/yolov3-tiny.cfg yolov3-tiny.conv.15

开始训练:
Ubuntu16使用自己的数据集训练YOLOV3-tiny教程、技巧及相关资料_第1张图片

  • 训练过程中的参数含义如下:[3]
    count是表示当前层与真实label正确配对的box数。
    其中所有参数都是针对这个值的平均值,除no obj外,不过从代码上来,这个参数意义并不大,所以当前yolo层如果出现nan这个的打印,也是正常的,只是表示当前batch刚好所有图片都是大框或是小框,所以提高batch的数目可以降低nan出现的机率。
    Avg IOU 表示当前层正确配对的box的交并比的平均值
    Class 表示表示当前层正确配对类别的平均机率
    Obj 表示confidence = P(object)* IOU,表示预测box包含对象与IOU好坏的评分
    0.5R/0.7R 表示Iou在0.5/0.7上与正确配对的box的比率

  • 训练过程中的参数含义如下(https://blog.csdn.net/lilai619/article/details/79695109):
    Avg IOU: 当前迭代中,预测的box与标注的box的平均交并比,越大越好,期望数值为1;
    Class: 标注物体的分类准确率,越大越好,期望数值为1;
    obj: 越大越好,期望数值为1;
    No obj: 越小越好;
    .5R: 以IOU=0.5为阈值时候的recall; recall = 检出的正样本/实际的正样本
    0.75R: 以IOU=0.75为阈值时候的recall;
    count: 正样本数目。

模型测试

训练得到的模型文件位于/backup文件中,源码中(examples/detector.c)当迭代小于1000时每隔100次保存一次模型,当大于1000时每10000次迭代保存一次模型,可以自行修改保存规则。

以10000迭代得到的模型为例测试:

./darknet detector test cfg/voc.data cfg/yolov3-tiny.cfg backup/yolov3-tiny_10000.weights VOCdevkit/VOC2007/JPEGImages/000022.jpg

训练小窍门

① 断点重新训练

如果从某次断了重新开始训练,只需要把 yolov3-tiny.weights换成你的某一次的weights即可

例如:把命令行中的参数 yolov3-tiny.weights改成 yolov3-tiny_1000.weights 即可

② 修改cfg/xxx.cfg,首先修改分类数为自己的分类数,然后注意开头部分训练的batchsize和subdivisions被注释了,如果需要自己训练的话就需要去掉,测试的时候需要改回来,最后可以修改动量参数为0.99和学习率改小,这样可以避免训练过程出现大量nan的情况

③ 若出现显存不足,可修改batch的大小和取消random多尺度,默认情况下random=1,取消将random=0(需要修改几处)

④GPU动态监控
新打开一个终端,输入:

watch -n 1 -d nvidia-smi

Ubuntu16使用自己的数据集训练YOLOV3-tiny教程、技巧及相关资料_第2张图片
其中各部分的含义为:
Ubuntu16使用自己的数据集训练YOLOV3-tiny教程、技巧及相关资料_第3张图片
参考资料:
[1] Darknet yolov3-tiny 训练自己的数据集步骤
[2] 实用教程!使用YOLOv3训练自己数据的目标检测
[3] yolov3-tiny模型训练参数和训练自己的数据

你可能感兴趣的:(深度学习)