(转)darknet 训练心得
1. 安装darknet
使用Git克隆源码
1 | git clone https: / / github.com / pjreddie / darknet |
我们可能需要修改Makefile,主要修改前三行,配置使用GPU(CUDA),CUDNN,OPENCV
1 2 3 | GPU = 1 CUDNN = 1 OPENCV = 1 |
之后运行
1 2 3 | make - j8 wget https: / / pjreddie.com / media / files / yolov3.weights . / darknet detect cfg / yolov3.cfg yolov3.weights data / dog.jpg |
安装成功!
2. 准备数据集
使用LabelImg工具对图片进行标注,LabelImg安装和使用方法请自行百度。标注完成后得到两个文件夹Annotations和JPEGImages,分别存放xml格式标注内容和图片。
在scripts文件夹下构建目录树
1 2 3 4 5 | mkdir - p scripts / VOCdevkit / VOC2007 cd scripts / VOCdevkit / VOC2007 mkdir ImageSets cd ImageSets mkdir Layout Main Segmentation |
此时scripts文件夹下目录树应当为:

然后将Annotations和JPEGImages文件夹复制到VOC2007目录下
值得注意的是,VOC2007的年份2007应当和xml标注文件中的年份相同
接下来把traindata.py和trans.py拷贝到VOC2007目录下
其中traindata.py内容是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #!/usr/bin/env python3 import os import shutil def rename_by_count(path): #按序号命名 count = 1000 filelist = os.listdir(path) # 该文件夹下所有的文件(包括文件夹) for files in filelist: # 遍历所有文件 Olddir = os.path.join(path, files) # 原来的文件路径 if os.path.isdir(Olddir): # 如果是文件夹则跳过 continue filename = os.path.splitext(files)[ 0 ] # 文件名 filetype = os.path.splitext(files)[ 1 ] # 文件扩展名 Newdir = os.path.join(path, str (count) + filetype) # 新的文件路径 os.rename(Olddir, Newdir) # 重命名 count + = 1 def listname(path,idtxtpath): filelist = os.listdir(path) # 该文件夹下所有的文件(包括文件夹) f = open (idtxtpath, 'w' ) for files in filelist: # 遍历所有文件 Olddir = os.path.join(path, files) # 原来的文件路径 if os.path.isdir(Olddir): # 如果是文件夹则跳过 continue filename = os.path.splitext(files)[ 0 ] # 文件名 filetype = os.path.splitext(files)[ 1 ] # 文件扩展名 #Newdir = os.path.join(path, "1000" + filetype) # 新的文件路径: path+filename+type f.write(filename) f.write( '\n' ) f.close() def imgid_list(imgpath, savepath, num): #rename_by_count(imgpath) path1 = savepath + "/validateImage" path2 = savepath + "/trainImage" if os.path.exists(path1) = = False : os.mkdir(path1) if os.path.exists(path2) = = False : os.mkdir(path2) xmlpath1 = savepath + "/validateImageXML" xmlpath2 = savepath + "/trainImageXML" if os.path.exists(xmlpath1) = = False : os.mkdir(xmlpath1) if os.path.exists(xmlpath2) = = False : os.mkdir(xmlpath2) filelist = os.listdir(imgpath) count = 0 for files in filelist: olddir = os.path.join(imgpath, files) newdir1 = os.path.join(path1, files) newdir2 = os.path.join(path2, files) filename = os.path.splitext(files)[ 0 ] # 文件名 xmldir = savepath + "/xml" xmldir1 = savepath + "/validateImageXML" xmldir2 = savepath + "/trainImageXML" if count<num: shutil.copy(olddir, newdir1) #validate xmlolddir = os.path.join(xmldir, filename + ".xml" ) xmlnewdir = os.path.join(xmldir1,filename + ".xml" ) shutil.copy(xmlolddir,xmlnewdir) shutil.copy(xmlolddir, newdir1) else : shutil.copy(olddir, newdir2) xmlolddir = os.path.join(xmldir, filename + ".xml" ) xmlnewdir = os.path.join(xmldir2, filename + ".xml" ) shutil.copy(xmlolddir, xmlnewdir) shutil.copy(xmlolddir, newdir2) count = count + 1 imgidtxtpath1 = savepath + "/validateImageId.txt" imgidtxtpath2 = savepath + "/trainImageId.txt" listname(path1, imgidtxtpath1) listname(path2, imgidtxtpath2) #rename_by_count # 给图片按序号给名字 savepath = os.getcwd() imgpath = savepath + "/Image" val_num = 30 #验证集数量,可修改 imgid_list(imgpath,savepath,val_num) |
trans.py内容是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | #!/usr/bin/env python3 import xml.etree.ElementTree as ET import pickle import string import os import shutil from os import listdir, getcwd from os.path import join sets = [( '2007' , 'train' ), ( '2007' , 'val' )] classes = [ "railway_ticket" , "vat_invoice" ] def convert(size, box): dw = 1. / size[ 0 ] dh = 1. / size[ 1 ] x = (box[ 0 ] + box[ 1 ]) / 2.0 y = (box[ 2 ] + box[ 3 ]) / 2.0 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(image_id,flag,savepath): #s = '\xef\xbb\xbf' #nPos = image_id.index(s) #if nPos >= 0: # image_id = image_id[3:] if flag = = 0 : in_file = open (savepath + '/trainImageXML/%s.xml' % (image_id)) labeltxt = savepath + '/trainImageLabelTxt' ; if os.path.exists(labeltxt) = = False : os.mkdir(labeltxt); out_file = open (savepath + '/trainImageLabelTxt/%s.txt' % (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) elif flag = = 1 : in_file = open (savepath + '/validateImageXML/%s.xml' % (image_id)) labeltxt = savepath + '/validateImageLabelTxt' ; if os.path.exists(labeltxt) = = False : os.mkdir(labeltxt); out_file = open (savepath + '/validateImageLabelTxt/%s.txt' % (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: #savepath = "/home/wurui/CAR/wrz/pillar"; savepath = os.getcwd(); idtxt = savepath + "/validateImageId.txt" ; pathtxt = savepath + "/validateImagePath.txt" ; image_ids = open (idtxt).read().strip().split() list_file = open (pathtxt, 'w' ) s = '\xef\xbb\xbf' for image_id in image_ids: nPos = image_id.find(s) if nPos > = 0 : image_id = image_id[ 3 :] list_file.write( '%s/validateImage/%s.jpg\n' % (wd, image_id)) print (image_id) convert_annotation(image_id, 1 , savepath) list_file.close() idtxt = savepath + "/trainImageId.txt" ; pathtxt = savepath + "/trainImagePath.txt" ; image_ids = open (idtxt).read().strip().split() list_file = open (pathtxt, 'w' ) s = '\xef\xbb\xbf' for image_id in image_ids: nPos = image_id.find(s) if nPos > = 0 : image_id = image_id[ 3 :] list_file.write( '%s/trainImage/%s.jpg\n' % (wd,image_id)) print (image_id) convert_annotation(image_id, 0 ,savepath) list_file.close() |
首先拷贝Annotations文件夹为xml,拷贝JPEGImages为Image
修改traindata.py中验证集数量val_num,推荐为总数据集的30%,修改trans.py中的sets和classes
执行
1 2 | python3 traindata.py python3 trans.py |
将生成的trainImageID.txt和validateImageID.txt拷贝到ImageSets的Main目录下,并分别重命名为train.txt和val.txt,删除执行traindata.py和trans.py生成的所有文件和文件夹以及xml和Image文件夹
回到scripts文件夹
修改voc_label.py,修改sets和classes的值,
1 2 | sets = [( '2007' , 'train' ), ( '2007' , 'val' )] classes = [ "railway_ticket" , "vat_invoice" ] |
并将最后的两行
1 2 | os.system( "cat 2007_train.txt 2007_val.txt 2012_train.txt 2012_val.txt > train.txt" ) os.system( "cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt" ) |
改成
1 2 | os.system( "cat 2007_train.txt 2007_val.txt > train.txt" ) #os.system("cat 2007_train.txt 2007_val.txt 2007_test.txt 2012_train.txt 2012_val.txt > train.all.txt") |
最后执行
1 | python3 voc_label.py |
至此,数据集准备完毕
3. 修改配置文件
回到darknet根目录下
3.1 data/
修改voc.names
修改为需要识别的类名称,每行一类
3.2 cfg/
修改yolov3.cfg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | [net] # Testing 初始batch参数要分为两类,分别为训练集和测试集,不同模式相应放开参数,#为注释符号 #batch=1 #subdivisions=1 # Training batch = 64 一批训练样本的样本数量,每batch个样本更新一次参数 subdivisions = 8 batch / subdivisions作为一次性送入训练器的样本数量 如果内存不够大,将batch分割为subdivisions个子batch (subdivisions相当于分组个数,相除结果作为一次送入训练器的样本数量) 注意:上面这两个参数如果电脑内存小,则把batch改小一点,batch越大,训练效果越好 Subdivisions越大,可以减轻显卡压力(分组数目越多,每组样本数量则会更少,显卡压力也会相应减少) width = 416 height = 416 channels = 3 以上三个参数为输入图像的参数信息 width和height影响网络对输入图像的分辨率,从而影响precision,只可以设置成 32 的倍数(为什么是 32 ?由于使用了下采样参数是 32 ,所以不同的尺寸大小也选择为 32 的倍数{ 320 , 352 ….. 608 },最小 320 * 320 ,最大 608 * 608 ,网络会自动改变尺寸,并继续训练的过程。) momentum = 0.9 DeepLearning1中最优化方法中的动量参数,这个值影响着梯度下降到最优值得速度 (注:SGD方法的一个缺点是其更新方向完全依赖于当前batch计算出的梯度,因而十分不稳定。Momentum算法借用了物理中的动量概念,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地更快,并且还有一定摆脱局部最优的能力) decay = 0.0005 权重衰减正则项,防止过拟合,正则项往往有重要意义 / / 增加样本的数量,改变基础样本的状态,去增加样本整体的数量,增加样本量减少过拟合 angle = 0 通过旋转角度来生成更多训练样本 saturation = 1.5 通过调整饱和度来生成更多训练样本 exposure = 1.5 通过调整曝光量来生成更多训练样本 hue = . 1 通过调整色调来生成更多训练样本 learning_rate = 0.001 学习率决定着权值更新的速度,设置得太大会使结果超过最优值,直接错过最优值,震荡回去,太小会使下降速度过慢,导致收敛过慢。如果仅靠人为干预调整参数,需要不断修改学习率。刚开始训练时可以将学习率设置的高一点,而一定轮数之后,将其减小。在训练过程中,一般根据训练轮数设置动态变化的学习率。 基本训练守则 刚开始训练时:学习率以 0.01 ~ 0.001 为宜。 一定轮数过后:逐渐减缓。 接近训练结束:学习速率的衰减应该在 100 倍以上。 提供参考资料学习率的调整参考https: / / blog.csdn.net / qq_33485434 / article / details / 80452941 burn_in = 1000 在迭代次数小于burn_in时,其学习率的更新有一种方式,大于burn_in时,才采用policy的更新方式 max_batches = 500200 训练达到max_batches后停止学习,多个batches policy = steps 这个是学习率调整的策略,有policy:constant, steps, exp, poly, step, sig, RANDOM,constant等方式 调整学习率的policy,有如下policy:constant, steps, exp, poly, step, sig, RANDOM constant 保持学习率为常量,caffe里为fixed steps 比较好理解,按照steps来改变学习率 Steps和scales相互一一对应 steps = 40000 , 45000 下面这两个参数steps和scale是设置学习率的变化,比如迭代到 40000 次时,学习率衰减十倍。 45000 次迭代时,学习率又会在前一个学习率的基础上衰减十倍。根据batch_num调整学习率 scales = ,. 1 ,. 1 学习率变化的比例,累计相乘 涉及几个参数(以后要学习的代码,具体参数可以调节) exp gamma = 返回base_lr * gamma^ iter , iter 为当前迭代次数,gamma设置为 0.98 poly power = 4 max_batches = 800000 对学习率进行多项式衰减。图中power为 0.9 sig 学习率进行sigmod函数衰减 gamma = 0.05 step = 200 |
根据电脑配置,主要是显卡能力调整参数
修改每一个[yolo]前面四行的filters和后面三行的classes,注意需要修改3处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [convolutional] size = 1 stride = 1 pad = 1 filters = 255 # 改成3*(classes + 5) activation = linear [yolo] mask = 6 , 7 , 8 anchors = 10 , 13 , 16 , 30 , 33 , 23 , 30 , 61 , 62 , 45 , 59 , 119 , 116 , 90 , 156 , 198 , 373 , 326 classes = 80 # 修改为实际需要识别的类数量 num = 9 jitter = . 3 ignore_thresh = . 7 truth_thresh = 1 random = 1 |
修改voc.data为
1 2 3 4 5 | classes = 8 # 实际需要识别的类 train = / home / ubuntu / darknet / scripts / 2007_train .txt # 指向刚刚生成的文件2007_train.txt valid = / home / ubuntu / darknet / scripts / 2007_val .txt # 指向刚刚生成的文件2007_val.txt names = data / voc.names backup = backup |
3.3 examples/
修改detector.c
1 2 3 4 5 6 7 8 9 10 11 12 | void validate_detector_flip(char * datacfg, char * cfgfile, char * weightfile, char * outfile) { int j; list * options = read_data_cfg(datacfg); char * valid_images = option_find_str(options, "valid" , "data/train.list" ); / / char * name_list = option_find_str(options, "names" , "data/names.list" ); char * name_list = option_find_str(options, "names" , "data/voc.names" ); char * prefix = option_find_str(options, "results" , "results" ); char * * names = get_labels(name_list); char * mapf = option_find_str(options, "map" , 0 ); int * map = 0 ; if (mapf) map = read_map(mapf); |
有几个函数中需要修改,建议直接搜索
修改darknet.c
1 2 3 4 5 6 7 8 | float thresh = find_float_arg(argc, argv, "-thresh" , . 5 ); char * filename = (argc > 4 ) ? argv[ 4 ]: 0 ; char * outfile = find_char_arg(argc, argv, "-out" , 0 ); int fullscreen = find_arg(argc, argv, "-fullscreen" ); / / test_detector( "cfg/coco.data" , argv[ 2 ], argv[ 3 ], filename, thresh, . 5 , outfile, fullscreen); test_detector( "cfg/voc.data" , argv[ 2 ], argv[ 3 ], filename, thresh, . 5 , outfile, fullscreen); } else if ( 0 = = strcmp(argv[ 1 ], "cifar" )){ run_cifar(argc, argv); |
这里也有几处需要修改
4 训练
执行
1 | . / darknet detector train cfg / voc.data cfg / yolov3.cfg |
作者:我还在这里
链接:https://www.jianshu.com/p/7736e8d2ee6e
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构