(轉)darknet 訓練心得

1. 安裝darknet

使用Git克隆源碼python

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

咱們可能須要修改Makefile,主要修改前三行,配置使用GPU(CUDA),CUDNN,OPENCVgit

GPU=1
CUDNN=1
OPENCV=1

以後運行github

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

安裝成功!算法

2. 準備數據集

使用LabelImg工具對圖片進行標註,LabelImg安裝和使用方法請自行百度。標註完成後獲得兩個文件夾AnnotationsJPEGImages,分別存放xml格式標註內容和圖片。ubuntu

在scripts文件夾下構建目錄樹網絡

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

此時scripts文件夾下目錄樹應當爲:dom

而後將Annotations和JPEGImages文件夾複製到VOC2007目錄下
值得注意的是,VOC2007的年份2007應當和xml標註文件中的年份相同ide

接下來把traindata.py和trans.py拷貝到VOC2007目錄下
其中traindata.py內容是:函數

#!/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內容是:工具

#!/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
執行

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的值,

sets=[('2007', 'train'), ('2007', 'val')]
classes = ["railway_ticket", "vat_invoice"]

並將最後的兩行

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

最後執行

python3 voc_label.py

至此,數據集準備完畢

3. 修改配置文件

回到darknet根目錄下

3.1 data/

修改voc.names
修改成須要識別的類名稱,每行一類

3.2 cfg/

修改yolov3.cfg

[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處

[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爲

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

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

  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 訓練

執行

./darknet detector train cfg/voc.data cfg/yolov3.cfg

做者:我還在這裏連接:https://www.jianshu.com/p/7736e8d2ee6e來源:簡書簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索