YOLO v3 包括Tiny-Yolo 訓練自己的數據集(Pytorch版本)以及模型評價指標的介紹


  最近一直在嘗試用pytorch版本的Tiny yolo v3,來訓練自己的數據集。為記錄下整個過程,在原創博客:https://blog.csdn.net/sinat_27634939/article/details/89884011的基礎上,補充了一點東西。

主要流程分為六步:

  一、數據集制作

  1、首先,我們要對自己的數據進行標注,使用的工具是labelimg。Iabelimg可以直接網頁搜索下載exe,運行使用。也可以在python的環境下,輸入命令:pip install labelimg,在conda管理的python環境中安裝labelimg,運行方法就是直接在該conda環境下CMD輸入labelimg即可運行。labelimg打開后的效果如下圖所示。

    

之后,將你的數據集圖像所在文件夾設置為Open Dir,新建一個文件夾作為存在Annotation的文件夾,設置為Change Save Dir,標注得到的xml文件格式如下所示。

<annotation>
    <folder>Desktop</folder>
    <filename>BloodImage_00000.jpg</filename>
    <path>/Users/xxx/Desktop/BloodImage_00000.jpg</path>
    <source>
        <database>Unknown</database>
    </source>
    <size>
        <width>640</width>
        <height>480</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>cell</name>
        <pose>Unspecified</pose>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>200</xmin>
            <ymin>337</ymin>
            <xmax>304</xmax>
            <ymax>446</ymax>
        </bndbox>
    </object>
</annotation>

其中,最主要的部分就是bndbox內的,就是我們所標記的人工標注框。

注:在標記數據之前,最好先把圖片數據的文件名修改一下,這里放一個我的文件批量重命名代碼鏈接:https://gitee.com/alexbd/rename_file_all

雖然寫的文件名是視頻文件批量重命名,但是,只要對文件后綴進行修改,就可以對任何文件夾內的同一格式文件進行批量重命名。

 

二、訓練代碼

  YOLO有官方的代碼,我們這里采用的是github上的鏈接:https://github.com/ultralytics/yolov3,git下來。之后建議創建一個專門用於yolo的conda環境,安裝pytorch等需要的包,詳細見requirements文件。

另外,為了更好的訓練,需要安裝apex。

  安裝apex方法:

  1、從該鏈接:https://github.com/NVIDIA/apex   鏈接上git到你的電腦上;

  2、從里面的requirements文件中依次安裝需要的依賴包。

  在你這個yolo的conda環境下,依次執行:

  

pip install cxxfilt
pip install tqdm
pip install numpy
pip install PyYAML
pip install pytest

  3、完成后,在apex的根目錄下,python運行安裝apex命令,

  

python setup.py install

  當看到如下圖示時,就說明安裝apex成功了。

 

 三、數據預處理

   為了能夠用clone下來的工程進行訓練和預測,我們需要對數據進行處理,以適應相應的接口。

  

  1、將細胞數據Annotations和JPEGImages放入data目錄下,並新建文件ImageSets,labels,復制JPEGImages,重命名images, 

    

  2、在根目錄下新建makeTxt.py,將數據分成訓練集,測試集和驗證集,其中比例可以在代碼設置,代碼如下:

  

import os
import random
 
trainval_percent = 0.1
train_percent = 0.9
xmlfilepath = 'data/Annotations'
txtsavepath = 'data/ImageSets'
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('data/ImageSets/trainval.txt', 'w')
ftest = open('data/ImageSets/test.txt', 'w')
ftrain = open('data/ImageSets/train.txt', 'w')
fval = open('data/ImageSets/val.txt', 'w')
 
for i in list:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftest.write(name)
        else:
            fval.write(name)
    else:
        ftrain.write(name)
 
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

  

  在ImageSets得到四個文件,其中我們主要關注的是train.txt,test.txt,val.txt,文件里主要存儲圖片名稱。

3、運行根目錄下voc_label.py,得到labels的具體內容以及data目錄下的train.txt,test.txt,val.txt,這里的train.txt與之前的區別在於,不僅僅得到文件名,還有文件的具體路徑。voc_label.py的代碼如下

  

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
 
sets = ['train', 'test','val']
 
classes = ["RBC"]#我們只是檢測細胞,因此只有一個類別
 
 
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):
    in_file = open('data/Annotations/%s.xml' % (image_id))
    out_file = open('data/labels/%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()
print(wd)
for image_set in sets:
    if not os.path.exists('data/labels/'):
        os.makedirs('data/labels/')
    image_ids = open('data/ImageSets/%s.txt' % (image_set)).read().strip().split()
    list_file = open('data/%s.txt' % (image_set), 'w')
    for image_id in image_ids:
        list_file.write('data/images/%s.jpg\n' % (image_id))
        convert_annotation(image_id)
    list_file.close()

  

  labels文件下的具體labels信息  

    

        data目錄下train.txt

    

四、配置文件

    1.在data目錄下新建rbc.data,配置訓練的數據,內容如下

    

classes=1
train=data/train.txt
valid=data/test.txt
names=data/rbc.names
backup=backup/
eval=coco

   2.在data目錄下新建rbc.names,配置預測的類別,內容如下

    

  3.網絡結構配置,在原工程下cfg目錄下有很多的yolov3網絡結構,我們本次采用的是yolov3-tiny.cfg

    

  具體參數的意義可以參考博客YOLOV3實戰4:Darknet中cfg文件說明和理解yolo配置文件的參數說明和reorg層的理解!

因為我們只是估計了一個類,所以需要對cfg文件進行修改,yolov3-tiny.cfg

[net]
# Testing
batch=1
subdivisions=1
# Training
# batch=64
# subdivisions=2
width=416
height=416
channels=3
momentum=0.9
decay=0.0005
angle=0
saturation = 1.5
exposure = 1.5
hue=.1
 
learning_rate=0.001
burn_in=1000
max_batches = 500200
policy=steps
steps=400000,450000
scales=.1,.1
 
[convolutional]
batch_normalize=1
filters=16
size=3
stride=1
pad=1
activation=leaky
 
[maxpool]
size=2
stride=2
 
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky
 
[maxpool]
size=2
stride=2
 
[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky
 
[maxpool]
size=2
stride=2
 
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky
 
[maxpool]
size=2
stride=2
 
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky
 
[maxpool]
size=2
stride=2
 
[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky
 
[maxpool]
size=2
stride=1
 
[convolutional]
batch_normalize=1
filters=1024
size=3
stride=1
pad=1
activation=leaky
 
###########
 
[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky
 
[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky
 
[convolutional]
size=1
stride=1
pad=1
filters=18
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=1
 
[route]
layers = -4
 
[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky
 
[upsample]
stride=2
 
[route]
layers = -1, 8
 
[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky
 
[convolutional]
size=1
stride=1
pad=1
filters=18 #3*(class + 4 + 1)
activation=linear
 
[yolo]
mask = 0,1,2
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=1

  

注:修改的地方主要是filter,因為我們每一個網格就預測3個anchor結果,所以filter =3*(1 + 5)=18

        4.獲取官網已經訓練好的網絡參數yolov3-tiny.weights,下載鏈接https://pjreddie.com/media/files/yolov3-tiny.weights,導入weights目錄下,需要自己創建weights文件夾,由於需要進行fine-tune,所以需要對yolov3-tiny.weights進行改造,因而需要下載官網的代碼https://github.com/pjreddie/darknet,運行一下腳本,並將得到的yolov3-tiny.conv.15導入weights目錄下,腳本如下

./darknet partial cfg/yolov3-tiny.cfg yolov3-tiny.weights yolov3-tiny.conv.15 15

        這里,直接提供yolov3-tiny.conv.15下載地址。

        如果是其他結構的網絡,那么可以參考download_yolov3_weights.sh中的說明,里面有詳細的介紹。

五、訓練
        一切准備妥當,我們就可以開始訓練了,訓練腳本如下

python train.py --data data/rbc.data --cfg cfg/yolov3-tiny.cfg --epochs 10 --weights weights/yolov3-tiny.weights

 訓練時,可能會有報錯:SyntaxError:unexpected character after line continuation character。

報錯:SyntaxError: unexpected character after line continuation character的解決方法

        得到訓練好的模型best.pt

     

        訓練結果如下(這里只有10次迭代的結果)

        

六、預測
        我們將得到的模型進行預測,這里代預測的圖片我們放在data/samples目錄下

     

        運行以下腳本

python detect.py --name data/rbc.data --cfg cfg/yolov3-tiny.cfg --weights weights/best.pt

注:代碼中用的是pt后綴保存權重文件,用.pth也是可以的,只要代碼中所有地方都統一。


        得到的結果可以在output目錄

     

        可以看出來效果一般,主要我們的網絡結構較簡單,同時迭代的次數較少。

 

 

知識補充:關於 混淆矩陣、准確率(accuracy)、查准率(精度)(Precision)、查全率(召回率)(recall)、Roc、AUC、和MAP

混淆矩陣

  對於二分類問題,可將樣例根據其真實類別與分類器預測類別的組合划分為:

真正例(true positive):將一個正例正確判斷為正例

假正例(false positive):將一個反例錯誤判斷為正例

真反例(true negative):將一個反例正確判斷為反例

假反例(false negative):將一個正例錯誤判斷為反例

令TP、FP、TN、FN分別表示對應的樣例數,這四個指標構成了分類結果的混淆矩陣: 

          分類結果混淆矩陣

  正例(預測結果) 反例(預測結果)
正例(真實情況)    TP(真正例)   FN(假反例)
反例(真實情況)    FP(假正例)   TN(真反例)



 

 

 

 

樣例總數 = TP + FP + TN + FN

准確率(accuracy)

  accuracy = (TP+TN)/TP+FP+TN+FN

  查准率 = 精度 = precision = TP/(TP+FP) : 模型預測為正類的樣本中,真正為正類的樣本所占的比例
  查全率 = 召回率 = recall = TP/(TP+FN) : 模型正確預測為正類的樣本的數量,占總的正類樣本數量的比值
  一般來說,查准率高時,查全率往往偏低;查全率高時,查准率往往偏低。

  P-R曲線:查准率-查全率曲線:precision為縱軸,recall為橫軸

          

    第一種:    若學習器的P-R曲線被另一個學習器完全“包住”,則后者的性能優於前者;
    第二種:    若兩個學習器的P-R曲線發生了交叉,可以運用平衡點(Break-Even Point,BEP),即根據在“查准率=查全率”時的取值,判斷學習器性能的好壞。
    第三種:    若兩個學習器的P-R曲線發生了交叉,亦可以使用F1/F_\beta度量,分別表示查准率和查全率的調和平均和加權調和平均。
        其中,F2分數中,召回率的權重高於准確率,而F0.5分數中,准確率的權重高於召回率。
         F_\beta的物理意義就是將准確率和召回率這兩個分值合並為一個分值,在合並的過程中,召回率的權重是准確率的\beta倍。
         F1分數認為召回率和准確率同等重要,F2分數認為召回率的重要程度是准確率的2倍,而F0.5分數認為召回率的重要程度是准確率的一半。 第四種: 若兩個學習器的P-R曲線發生了交叉,亦可以使用AP\MAP:即計算P-R曲線下的面積

  

 

 關於mAP轉自

作者:AICV
鏈接:https://www.zhihu.com/question/53405779/answer/993913699
來源:知乎

評價指標 mAP

下面用一個例子說明 AP 和 mAP 的計算

先規定兩個公式,一個是 Precision,一個是 Recall,這兩個公式同上面的一樣,我們把它們擴展開來,用另外一種形式進行展示,其中 all detctions 代表所有預測框的數量, all ground truths 代表所有 GT 的數量。

[公式][公式]

AP 是計算某一類 P-R 曲線下的面積,mAP 則是計算所有類別 P-R 曲線下面積的平均值。

假設我們有 7 張圖片(Images1-Image7),這些圖片有 15 個目標(綠色的框,GT 的數量,上文提及的 all ground truths)以及 24 個預測邊框(紅色的框,A-Y 編號表示,並且有一個置信度值)

根據上圖以及說明,我們可以列出以下表格,其中 Images 代表圖片的編號,Detections 代表預測邊框的編號,Confidences 代表預測邊框的置信度,TP or FP 代表預測的邊框是標記為 TP 還是 FP(認為預測邊框與 GT 的 IOU 值大於等於 0.3 就標記為 TP;若一個 GT 有多個預測邊框,則認為 IOU 最大且大於等於 0.3 的預測框標記為 TP,其他的標記為 FP,即一個 GT 只能有一個預測框標記為 TP),這里的 0.3 是隨機取的一個值

通過上表,我們可以繪制出 P-R 曲線(因為 AP 就是 P-R 曲線下面的面積),但是在此之前我們需要計算出 P-R 曲線上各個點的坐標,根據置信度從大到小排序所有的預測框,然后就可以計算 Precision 和 Recall 的值,見下表。(需要記住一個叫累加的概念,就是下圖的 ACC TP 和 ACC FP

  • 標號為 1 的 Precision 和 Recall 的計算方式:Precision=TP/(TP+FP)=1/(1+0)=1,Recall=TP/(TP+FN)=TP/(all ground truths)=1/15=0.0666 (all ground truths 上面有定義過了
  • 標號 2:Precision=TP/(TP+FP)=1/(1+1)=0.5,Recall=TP/(TP+FN)=TP/(all ground truths)=1/15=0.0666
  • 標號 3:Precision=TP/(TP+FP)=2/(2+1)=0.6666,Recall=TP/(TP+FN)=TP/(all ground truths)=2/15=0.1333
  • 其他的依次類推

然后就可以繪制出 P-R 曲線

 

得到 P-R 曲線就可以計算 AP(P-R 曲線下的面積),要計算 P-R 下方的面積,一般使用的是插值的方法,取 11 個點 [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] 的插值所得

 

得到一個類別的 AP 結果如下:

[公式]

要計算 mAP,就把所有類別的 AP 計算出來,然后求取平均即可。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM