darknet 訓(xùn)練心得

1. 安裝darknet

使用Git克隆源碼

git clone https://github.com/pjreddie/darknet

我們可能需要修改Makefile

cd darknet

gedit Makefile

主要修改前三行,配置使用GPU(CUDA)断序,CUDNN虽抄,OPENCV

GPU=1

CUDNN=1

OPENCV=1

之后運(yùn)行

make -j8
wget https://pjreddie.com/media/files/yolov3.weights
./darknet detect cfg/yolov3.cfg yolov3.weights data/dog.jpg

安裝成功肴焊!

2. 準(zhǔn)備數(shù)據(jù)集

使用LabelImg工具對圖片進(jìn)行標(biāo)注媳叨,LabelImg安裝和使用方法請自行百度耿战。標(biāo)注完成后得到兩個(gè)文件夾Annotations和JPEGImages巴帮,分別存放xml格式標(biāo)注內(nèi)容和圖片晶密。

在scripts文件夾下構(gòu)建目錄樹

mkdir -p scripts/VOCdevkit/VOC2007
cd scripts/VOCdevkit/VOC2007
mkdir ImageSets
cd ImageSets
mkdir Layout Main Segmentation

此時(shí)scripts文件夾下目錄樹應(yīng)當(dāng)為:


屏幕截圖.png

然后將Annotations和JPEGImages文件夾復(fù)制到VOC2007目錄下
值得注意的是鱼喉,VOC2007的年份2007應(yīng)當(dāng)和xml標(biāo)注文件中的年份相同

接下來把traindata.py和trans.py拷貝到VOC2007目錄下
其中traindata.py內(nèi)容是:

#!/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]  # 文件擴(kuò)展名
        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]  # 文件擴(kuò)展名
        #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=1440   #驗(yàn)證集數(shù)量秀鞭,可修改
imgid_list(imgpath,savepath,val_num)

trans.py內(nèi)容是:

#!/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 = ["airplane_gro", "airplane_air", "fix_UAV_gro", "fix_UAV_air", "rotor_UAV_gro", "rotor_UAV_air", "airship_gro", "airship_air"]


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中驗(yàn)證集數(shù)量val_num扛禽,推薦為總數(shù)據(jù)集的30%锋边,修改trans.py中的sets和classes
執(zhí)行

python3 traindata.py
python3 trans.py

將生成的trainImageID.txt和validateImageID.txt拷貝到ImageSets的Main目錄下,并分別重命名為train.txt和val.txt编曼,刪除執(zhí)行traindata.py和trans.py生成的所有文件和文件夾以及xml和Image文件夾

回到scripts文件夾
修改voc_label.py豆巨,修改sets和classes的值,并將最后的兩行

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")

改成

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")

最后執(zhí)行

python3 voc_label.py

至此掐场,數(shù)據(jù)集準(zhǔn)備完畢

3. 修改配置文件

回到darknet根目錄下

3.1 data/

修改voc.names
修改為需要識(shí)別的類名稱往扔,每行一類

3.2 cfg/

修改yolov3.cfg

[net]
# Testing             初始batch參數(shù)要分為兩類贩猎,分別為訓(xùn)練集和測試集,不同模式相應(yīng)放開參數(shù)瓤球,#為注釋符號
#batch=1
#subdivisions=1
# Training

batch=64             一批訓(xùn)練樣本的樣本數(shù)量融欧,每batch個(gè)樣本更新一次參數(shù)
subdivisions=8           batch/subdivisions作為一次性送入訓(xùn)練器的樣本數(shù)量
                     如果內(nèi)存不夠大,將batch分割為subdivisions個(gè)子batch
                     (subdivisions相當(dāng)于分組個(gè)數(shù)卦羡,相除結(jié)果作為一次送入訓(xùn)練器的樣本數(shù)量)
    注意:上面這兩個(gè)參數(shù)如果電腦內(nèi)存小噪馏,則把batch改小一點(diǎn),batch越大绿饵,訓(xùn)練效果越好
       Subdivisions越大欠肾,可以減輕顯卡壓力(分組數(shù)目越多,每組樣本數(shù)量則會(huì)更少拟赊,顯卡壓力也會(huì)相應(yīng)減少)

width=416           
height=416
channels=3
                    以上三個(gè)參數(shù)為輸入圖像的參數(shù)信息 width和height影響網(wǎng)絡(luò)對輸入圖像的分辨率刺桃,從而影響precision,只可以設(shè)置成32的倍數(shù)(為什么是32吸祟?由于使用了下采樣參數(shù)是32瑟慈,所以不同的尺寸大小也選擇為32的倍數(shù){320,352…..608}屋匕,最小320*320葛碧,最大608*608,網(wǎng)絡(luò)會(huì)自動(dòng)改變尺寸过吻,并繼續(xù)訓(xùn)練的過程进泼。)

momentum=0.9    DeepLearning1中最優(yōu)化方法中的動(dòng)量參數(shù),這個(gè)值影響著梯度下降到最優(yōu)值得速度 (注:SGD方法的一個(gè)缺點(diǎn)是其更新方向完全依賴于當(dāng)前batch計(jì)算出的梯度纤虽,因而十分不穩(wěn)定乳绕。Momentum算法借用了物理中的動(dòng)量概念,它模擬的是物體運(yùn)動(dòng)時(shí)的慣性逼纸,即更新的時(shí)候在一定程度上保留之前更新的方向洋措,同時(shí)利用當(dāng)前batch的梯度微調(diào)最終的更新方向。這樣一來杰刽,可以在一定程度上增加穩(wěn)定性呻纹,從而學(xué)習(xí)地更快,并且還有一定擺脫局部最優(yōu)的能力) 

decay=0.0005                權(quán)重衰減正則項(xiàng)专缠,防止過擬合,正則項(xiàng)往往有重要意義
//增加樣本的數(shù)量淑仆,改變基礎(chǔ)樣本的狀態(tài)涝婉,去增加樣本整體的數(shù)量,增加樣本量減少過擬合
angle=0                 通過旋轉(zhuǎn)角度來生成更多訓(xùn)練樣本
saturation = 1.5            通過調(diào)整飽和度來生成更多訓(xùn)練樣本
exposure = 1.5              通過調(diào)整曝光量來生成更多訓(xùn)練樣本
hue=.1                  通過調(diào)整色調(diào)來生成更多訓(xùn)練樣本

learning_rate=0.001 
學(xué)習(xí)率決定著權(quán)值更新的速度蔗怠,設(shè)置得太大會(huì)使結(jié)果超過最優(yōu)值墩弯,直接錯(cuò)過最優(yōu)值吩跋,震蕩回去,太小會(huì)使下降速度過慢渔工,導(dǎo)致收斂過慢锌钮。如果僅靠人為干預(yù)調(diào)整參數(shù),需要不斷修改學(xué)習(xí)率引矩。剛開始訓(xùn)練時(shí)可以將學(xué)習(xí)率設(shè)置的高一點(diǎn)梁丘,而一定輪數(shù)之后,將其減小旺韭。在訓(xùn)練過程中氛谜,一般根據(jù)訓(xùn)練輪數(shù)設(shè)置動(dòng)態(tài)變化的學(xué)習(xí)率。
基本訓(xùn)練守則
剛開始訓(xùn)練時(shí):學(xué)習(xí)率以 0.01 ~ 0.001 為宜区端。
一定輪數(shù)過后:逐漸減緩值漫。
接近訓(xùn)練結(jié)束:學(xué)習(xí)速率的衰減應(yīng)該在100倍以上。
提供參考資料學(xué)習(xí)率的調(diào)整參考https://blog.csdn.net/qq_33485434/article/details/80452941

burn_in=1000    在迭代次數(shù)小于burn_in時(shí)织盼,其學(xué)習(xí)率的更新有一種方式杨何,大于burn_in時(shí),才采用policy的更新方式

max_batches = 500200      訓(xùn)練達(dá)到max_batches后停止學(xué)習(xí)沥邻,多個(gè)batches

policy=steps     這個(gè)是學(xué)習(xí)率調(diào)整的策略危虱,有policy:constant, steps, exp, poly, step, sig, RANDOM,constant等方式
調(diào)整學(xué)習(xí)率的policy谋国,有如下policy:constant, steps, exp, poly, step, sig, RANDOM
constant
保持學(xué)習(xí)率為常量槽地,caffe里為fixed
steps
比較好理解,按照steps來改變學(xué)習(xí)率


Steps和scales相互一一對應(yīng)
steps=40000,45000       下面這兩個(gè)參數(shù)steps和scale是設(shè)置學(xué)習(xí)率的變化芦瘾,比如迭代到40000次時(shí)捌蚊,學(xué)習(xí)率衰減十倍。45000次迭代時(shí)近弟,學(xué)習(xí)率又會(huì)在前一個(gè)學(xué)習(xí)率的基礎(chǔ)上衰減十倍缅糟。根據(jù)batch_num調(diào)整學(xué)習(xí)率                
scales=,.1,.1            學(xué)習(xí)率變化的比例,累計(jì)相乘

涉及幾個(gè)參數(shù)(以后要學(xué)習(xí)的代碼祷愉,具體參數(shù)可以調(diào)節(jié))

exp
gamma=
返回base_lr*gamma^iter,iter為當(dāng)前迭代次數(shù)窗宦,gamma設(shè)置為0.98

poly
power=4
max_batches=800000
對學(xué)習(xí)率進(jìn)行多項(xiàng)式衰減。圖中power為0.9

sig
學(xué)習(xí)率進(jìn)行sigmod函數(shù)衰減
gamma= 0.05
step=200

根據(jù)電腦配置二鳄,主要是顯卡能力調(diào)整參數(shù)
修改每一個(gè)[yolo]前面四行的filters和后面三行的classes蓄坏,注意需要修改3處

[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                      # 修改為實(shí)際需要識(shí)別的類數(shù)量
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1

修改voc.data為

classes= 8          # 實(shí)際需要識(shí)別的類
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

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);

有幾個(gè)函數(shù)中需要修改漆羔,建議直接搜索

修改darknet.c

        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 訓(xùn)練

執(zhí)行

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宝惰,更是在濱河造成了極大的恐慌钟些,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鳖敷,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)程拭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門定踱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恃鞋,你說我怎么就攤上這事崖媚。” “怎么了山宾?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵至扰,是天一觀的道長。 經(jīng)常有香客問我资锰,道長敢课,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任绷杜,我火速辦了婚禮直秆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鞭盟。我一直安慰自己圾结,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布齿诉。 她就那樣靜靜地躺著筝野,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粤剧。 梳的紋絲不亂的頭發(fā)上歇竟,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音抵恋,去河邊找鬼焕议。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弧关,可吹牛的內(nèi)容都是我干的盅安。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼世囊,長吁一口氣:“原來是場噩夢啊……” “哼别瞭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起株憾,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蝙寨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體籽慢,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年猫胁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了箱亿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弃秆,死狀恐怖届惋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菠赚,我是刑警寧澤脑豹,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站衡查,受9級特大地震影響瘩欺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜拌牲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一俱饿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧塌忽,春花似錦拍埠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至擦耀,卻和暖如春棉圈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背埂奈。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工迄损, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人账磺。 一個(gè)月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓芹敌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親垮抗。 傳聞我的和親對象是個(gè)殘疾皇子氏捞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容