YOLO原始碼詳解(一)-訓練
本系列文章會持續更新,主要會分以下幾個部分:
1、darknet下的yolo原始碼解讀
2、將yolo移植到mxnet下
3、模型壓縮與加速
白天需要工作,只有晚上時間寫,所以可能更新速度有點慢,還有就是該系列博文不一定會嚴格按照以上三點的順序來寫,也可能移植到caffe下,在caffe下進行壓縮和加速。
一、訓練
我用的是VOC2007的資料集,下載指令如下:
$curl -O http://pjreddie.com/media/files/VOCtrainval_06-Nov-2007.tar
$curl -O http://pjreddie.com/media/files/VOCtest_06-Nov-2007.tar
$tar xf VOCtrainval_06-Nov-2007.tar
$tar xf VOCtest_06-Nov-2007.tar
執行以下程式碼,將.xml
檔案轉換成.txt
檔案,以備YOLO訓練時資料解析:
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')]
classes = ["aeroplane", "bicycle", "bird" , "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"]
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(year, image_id):
in_file = open('VOCdevkit/VOC%s/Annotations/%s.xml'%(year, image_id))
out_file = open('VOCdevkit/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('VOCdevkit/VOC%s/labels/'%(year)):
os.makedirs('VOCdevkit/VOC%s/labels/'%(year))
image_ids = open('VOCdevkit/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/VOCdevkit/VOC%s/JPEGImages/%s.jpg\n'%(wd, year, image_id))
convert_annotation(year, image_id)
list_file.close()
在yolo.c
中找到以下程式碼,並修改:
//指向你剛才生成的train.txt檔案
char *train_images = "/data/voc/train.txt";
//新建個資料夾,然後指向這裡就可以,訓練生成的模型檔案會儲存在這裡
char *backup_directory = "/home/pjreddie/backup/";
執行以下指令開始訓練:
./darknet yolo train cfg/yolo.train.cfg extraction.conv.weights
二、原始碼解讀
1.首先我們看一下訓練的資料流,從main函式開始看,該函式在darknet.c
檔案中:
//darknet.c
int main(int argc, char **argv)
{
//test_resize("data/bad.jpg");
//test_box();
//test_convolutional_layer();
if(argc < 2){
fprintf(stderr, "usage: %s <function>\n", argv[0]);
return 0;
}
gpu_index = find_int_arg(argc, argv, "-i", 0);
if(find_arg(argc, argv, "-nogpu")) {
gpu_index = -1;
}
#ifndef GPU
gpu_index = -1;
#else
if(gpu_index >= 0){
cuda_set_device(gpu_index);
}
#endif
if (0 == strcmp(argv[1], "average")){
average(argc, argv);
} else if (0 == strcmp(argv[1], "yolo")){
//第一個引數是yolo,所以跳轉到run_yolo函式
run_yolo(argc, argv);
} else {
fprintf(stderr, "Not an option: %s\n", argv[1]);
}
return 0;
}
//yolo.c
void run_yolo(int argc, char **argv)
{
char *prefix = find_char_arg(argc, argv, "-prefix", 0);
float thresh = find_float_arg(argc, argv, "-thresh", .2);
int cam_index = find_int_arg(argc, argv, "-c", 0);
int frame_skip = find_int_arg(argc, argv, "-s", 0);
if(argc < 4){
fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
return;
}
char *cfg = argv[3];
char *weights = (argc > 4) ? argv[4] : 0;
char *filename = (argc > 5) ? argv[5]: 0;
if(0==strcmp(argv[2], "test")) test_yolo(cfg, weights, filename, thresh);
//第二個引數是train,所以跳轉到了train_yolo函式
else if(0==strcmp(argv[2], "train")) train_yolo(cfg, weights);
voc_names, 20, frame_skip, prefix);
}
void train_yolo(char *cfgfile, char *weightfile)
{
char *train_images = "/data/voc/train.txt";
char *backup_directory = "/home/pjreddie/backup/";
/*srand函式是隨機數發生器的初始化函式。
srand和rand()配合使用產生偽隨機數序列。rand函式在產生隨機數前,需要系統提供的生成偽隨機數序列的
種子,rand根據這個種子的值產生一系列隨機數。如果系統提供的種子沒有變化,每次呼叫rand函式生成的偽
隨機數序列都是一樣的。*/
srand(time(0));
/*第三個引數是:`cfg/yolo.train.cfg`,`basecfg()`這個函式把`cfg/yolo.train.cfg`變成了
`yolo0train.cfg`,然後用base指標指向`yolo0train.cfg`*/
char *base = basecfg(cfgfile);
//列印"yolo"字樣
printf("%s\n", base);
float avg_loss = -1;
//解析網路構架,下面會仔細分析該函式
network net = parse_network_cfg(cfgfile);
//載入預訓練引數,下面會仔細分析該函式
if(weightfile){
load_weights(&net, weightfile);
}
printf("Learning Rate: %g, Momentum: %g, Decay: %g\n", net.learning_rate, net.momentum, net.decay);
/*imgs是一次載入到記憶體的影象數量,如果佔記憶體太大的話可以把subdivisions或者batch調小一點
詳細可以參考知乎:[深度機器學習中的batch的大小對學習效果有何影響?](https://www.zhihu.com/question/32673260)*/
int imgs = net.batch*net.subdivisions;
//net.seen就是已經經過網路訓練(看)的圖片數量,算出的i就是已經經過了多少次訓練
int i = *net.seen/imgs;
data train, buffer;
layer l = net.layers[net.n - 1];
//side就是論文中的7
int side = l.side;
int classes = l.classes;
//jitter是什麼意思呢?可以參考這篇部落格:[非均衡資料集處理:利用抖動(jittering)生成額外資料](http://weibo.com/1402400261/EgMr4vCC2?type=comment#_rnd1478833653326)
float jitter = l.jitter;
list *plist = get_paths(train_images);
//int N = plist->size;
char **paths = (char **)list_to_array(plist);
load_args args = {0};
args.w = net.w;
args.h = net.h;
args.paths = paths;
//n就是一次載入到記憶體中的圖片數量
args.n = imgs;
//m是待訓練圖片的總數量
args.m = plist->size;
args.classes = classes;
args.jitter = jitter;
//7*7個網格
args.num_boxes = side;
args.d = &buffer;
args.type = REGION_DATA;
//調節圖片旋轉角度、曝光度、飽和度、色調等,來增加圖片數量
args.angle = net.angle;
args.exposure = net.exposure;
args.saturation = net.saturation;
args.hue = net.hue;
//宣告執行緒ID
pthread_t load_thread = load_data_in_thread(args);
clock_t time;
//while(i*imgs < N*120){
while(get_current_batch(net) < net.max_batches){
i += 1;
time=clock();
/*pthread_join()函式,以阻塞的方式等待thread指定的執行緒結束。當函式返回時,被等待執行緒的資源被
收回。如果執行緒已經結束,那麼該函式會立即返回。*/
pthread_join(load_thread, 0);
train = buffer;
load_thread = load_data_in_thread(args);
printf("Loaded: %lf seconds\n", sec(clock()-time));
time=clock();
//開始訓練
float loss = train_network(net, train);
if (avg_loss < 0) avg_loss = loss;
avg_loss = avg_loss*.9 + loss*.1;
printf("%d: %f, %f avg, %f rate, %lf seconds, %d images\n", i, loss, avg_loss, get_current_rate(net), sec(clock()-time), i*imgs);
//每100次或者1000次儲存一次權重
if(i%1000==0 || (i < 1000 && i%100 == 0)){
char buff[256];
sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i);
save_weights(net, buff);
}
free_data(train);
}
char buff[256];
sprintf(buff, "%s/%s_final.weights", backup_directory, base);
save_weights(net, buff);
}
看到這裡,我想你已經能可以自己來訓練VOC資料集了。但這只是剛剛開始,我們要想將YOLO移植到mxnet下,就需要了解其具體函式的實現過程,請看下一節內容。
(END)
相關推薦
YOLO原始碼詳解(一)-訓練
本系列文章會持續更新,主要會分以下幾個部分: 1、darknet下的yolo原始碼解讀 2、將yolo移植到mxnet下 3、模型壓縮與加速 白天需要工作,只有晚上時間寫,所以可能更新速度有點慢,還有就是該系列博文不一定會嚴格按照以上三點的順序來
06.RTSP原始碼詳解(一)
6.7.rtsp傳輸原始碼分析1 6.8.rtsp傳輸原始碼分析2 6.9.rtsp傳輸原始碼分析3 6.10.rtsp傳輸原始碼分析4 6.11.rtsp傳輸實戰分析 6.12.直接傳送與環狀buffer傳送 6.13.rtsp分包傳送h264的原始碼分析 rtp_timestamp:http
spring的BeanFactory和ApplicationContext原始碼詳解(一)
轉自http://www.sandzhang.com/blog/2011/04/10/Spring-BeanFactory-ApplicationContext-Detail-1/ 版本:spring-framework-3.0.5.RELEASE Spring的最核心的部分就是BeanFactory了,
YOLO原始碼詳解(五)- YOLO中的7*7個grid和RPN中的9個anchors
一直不知道7×7的網格到底是幹什麼的,不就是結果預測7×7×2個框嗎,這跟把原圖分成7×7有什麼關係?不分成7×7就不能預測7×7×2個框嗎? 之前跟一個朋友討論,他說7×7的網格是作為迴歸框的初始位置,我後來的很長一段時間一直這麼認為,後來想想不對啊
YOLO原始碼詳解(二)- 函式剖析
1、網路引數解析函式:parse_network_cfg network parse_network_cfg(char *filename) { //read_cfg這個函式將所有的引數讀到一個圖中,如下所示,希望能幫助理解下程式碼
YOLO原始碼詳解(四)- 反向傳播(back propagation)
反向傳播是CNN中非常重要的一個環節,對於理論部分,這裡不做介紹,如果對反向傳播理論部分不熟悉,可以檢視以下網站。 非常詳細:零基礎入門深度學習(3) - 神經網路和反向傳播演算法 非常詳細:零基礎入門深度學習(4) - 卷積神經網路 非常生動:如
EventBus原始碼詳解(一):基本使用
寫在前面 對於Android程式設計師來說,相信大家都聽過EventBus的大名。EventBus是一個Android平臺上基於事件釋出和訂閱的輕量級框架,可以對釋出者和訂閱者解耦,並簡化Android的事件傳遞。正如官方介紹其優勢: 簡化了元件之間的通訊
Android程式設計之DialogFragment原始碼詳解(一)
DialogFragment是Fragment家族成員之一,如果你把它簡單的理解成Dialog,那就錯了。它的確可以做作dialog顯示,還可以顯示出自己定義的Dialog或者AlertDialog,但它同時也是一個Fragment。 按照官方的話來理解就是,你既可以把它當
jdbc原始碼詳解(一):示例+Driver註冊流程
0x00 前言 自己一直說要某個開源專案的原始碼,但是一直沒有真正地好好開始,一是以為看原始碼其實不容易看懂,而是因為選擇猶豫,最後也敲定看哪個。 這次正式開始看jdbc的原始碼有兩個三個:一是因為《java程式設計思想》這本書快看完了,折騰一個多月的時間,
gRPC 原始碼詳解(一)配置化的結構體
grpc 原始碼結構詳解 DialOptions DialOptions 是最重要的一環,負責配置每一次 rpc 請求的時
elastic-job詳解(一):數據分片
count 任務 不同的 應該 center shc 偶數 int ext 數據分片的目的在於把一個任務分散到不同的機器上運行,既可以解決單機計算能力上限的問題,也能降低部分任務失敗對整體系統的影響。elastic-job並不直接提供數據處理的功能,框架只會將分片項分配至各
JVM的基本結構及其各部分詳解(一)
後臺 棧幀 結束 依次 方法參數 ati 0.00 實例 同時存在 JVM的基本結構及其各部分詳解(一)(轉載) 1 java虛擬機的基本結構如圖: 1)類加載子系統負責從文件系統或者網絡中加載Class信息,加載的類信息存放於一塊稱為方法區的內存空間。除了類的信息外,方
設計模式詳解(一)
att 定義 面向對象設計 設計 sig com 繼承 行為模式 接口 一、設計模式定義 設計模式(Design Pattern)是一套被反復使用、多數人知曉的、經過分類的、代碼設計經驗的總結。 使用設計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。
mybatis 詳解(一)------JDBC
jdbc javax 發出 一段 true his 實例 用戶名 移植 1、什麽是MyBatis? MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,
Maven詳解(一)------ Maven概述
粘貼 cvs 模塊 strong ron 標準化 pom 標準 只需要 1、引言 你能搜到這個教程,說明你對 Maven 感興趣,但是又不是太理解。那麽接下來這個系列的教程將會詳細講解 Maven 的用法,相信你看完之後,一定能對 Maven 的理解更進一步!
.Net AppDomain詳解(一)
() arc .com args [] adas 功能 reading wpa AppDomain是CLR的運行單元,它可以加載Assembly、創建對象以及執行程序。AppDomain是CLR實現代碼隔離的基本機制。 每一個AppDomain可以單獨運行、停止;每個App
防火墻iptables詳解(一)
主從dns服務器 進行 所有 out show mountd 讀取 ppp 概念 -- 防火墻 常見的防火墻 :瑞星 江民 諾頓 卡巴斯基 天網...... iptables firewalld http://www.netfilter.org/ netfilter
Windows滲透利器之Pentest BOX使用詳解(一)
內存 標簽 配置 ram 添加 概覽 測試環境 功能 ruby 內容概覽: 知識科普 優缺點總結 功能參數詳解翻譯: 控制臺參
JVM類加載機制詳解(一)JVM類加載過程
進行 虛擬機啟動 類加載的時機 bsp 參與 tro ext 環境 java代碼 首先Throws(拋出)幾個自己學習過程中一直疑惑的問題: 1、什麽是類加載?什麽時候進行類加載? 2、什麽是類初始化?什麽時候進行類初始化? 3、什麽時候會為變量分配內存? 4、什麽時候會為
Java的反射機制詳解(一)
pbc spa 詳解 uno face target lan tor cin 8n72q傅釁8戰sig叢http://www.docin.com/app/user/userinfo?userid=179185461 8u炊3F7LB椒1http://huiyi.docin.