yolo v2 訓練流程

說明

這篇文章是說明yolo v2的訓練流程,在此命令的基礎上進行說明:

./darknet detector train cfg/coco.data cfg/yolo.cfg darknet19_448.conv.23

需要注意的是,此篇文章是我自己的理解,如有問題,請指正。

準備數據集

  1. 準備訓練需要的圖片(jpg)和圖片對應的標註文件(xml)
  2. 標註工具,可以使用labelImg工具
  3. 標註文件(xml)信息格式如下:

<annotation> <folder>test0101</folder> <filename>55000096.jpg</filename> <path>D:標註文件20171104011610 est010155000096.jpg</path> <source> <database>Unknown</database> </source> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <segmented>0</segmented> <object> <name>soccer</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>1385</xmin> <ymin>293</ymin> <xmax>1395</xmax> <ymax>305</ymax> </bndbox> </object></annotation>

4. 通過腳本提取出xml文件中的信息,寫入det.txt文件中,格式如下:

1 0 412 197 16 142 0 402 193 17 163 0 379 199 18 174 0 375 277 21 205 0 636 235 19 198 0 544 217 21 209 0 519 212 18 1710 0 737 284 26 2811 0 521 231 23 20......

5. 通過腳本讀取圖片路徑,寫入train.txt文件中,格式如下:

/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000001.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000002.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000003.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000004.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000005.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000008.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000009.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000010.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000011.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000012.jpg/home/nvidia/MOT/darknet_v1.0/data/soccer/example/img1/000014.jpg......

6. 通過腳本提取det.txt文件中的數據,計算出所有訓練樣本,格式如下:

0 0.145052083333 0.393518518519 0.0182291666667 0.0314814814815

7. 選擇90%的組用於訓練(Train),10%的組用於驗證(Validation)

修改配置文件

  1. 修改data/voc.names文件,文件存放的是所有的分類,根據自己的情況分類,voc.names文件內容如下:

soccer ballperson......

2. 修改cfg/voc.data文件,文件內容如下:

classes= 1 //voc.names 總分類train = /home/ysww/darknet-new/data/person/example/train.txt //訓練文件路徑valid = /home/ysww/darknet-new/data/person/example/valid.txt //有效文件路徑names = data/voc.names //voc.names文件路徑backup = backup //存放訓練yolo結果的文件夾

3. 修改cfg/yolo-voc文件,文件內容如下:

[net]# Testing# batch=1# subdivisions=1# Trainingbatch=64 //每次迭代要進行訓練的圖片數量subdivisions=8 //大小分批進行訓練height=416width_=416.......[convolutional]size=1stride=1pad=1filters=30 //最後一層卷積層中filters數值是 5×(類別數 + 5)activation=linear[region]anchors = 1.3221, 1.73145, 3.19275, 4.00944, 5.05587, 8.09892, 9.47112, 4.84053, 11.2364, 10.0071bias_match=1classes=1 //總類別數量coords=4num=5softmax=1jitter=.3rescore=1

開始訓練

下載預訓練的卷積權重

按照YOLO的官方指南來的,首先下載一個預先訓練的卷積權重,我們使用提取模型中的權重。您可以在這裡下載卷積圖層的權重(76 MB)。

wget https://pjreddie.com/media/files/darknet19_448.conv.23

放到darkent/目錄下。

然後運行指令:

./darknet detector train cfg/coco.data cfg/yolo.cfg darknet19_448.conv.23

就可以開始訓練了。

訓練流程解析

訓練輸出結果:

layer filters size input output 0 conv 32 3 x 3 / 1 800 x 800 x 3 -> 800 x 800 x 32 1 max 2 x 2 / 2 800 x 800 x 32 -> 400 x 400 x 32 2 conv 64 3 x 3 / 1 400 x 400 x 32 -> 400 x 400 x 64 3 max 2 x 2 / 2 400 x 400 x 64 -> 200 x 200 x 64 4 conv 128 3 x 3 / 1 200 x 200 x 64 -> 200 x 200 x 128 ...... 31 detectionmask_scale: Using default 1.000000Loading weights from darknet19_448.conv.23...Done!Learning Rate: 0.01, Momentum: 0.9, Decay: 0.0005Resizing544Loaded: 0.907026 secondsRegion Avg IOU: 0.005964, Class: 1.000000, Obj: 0.460078, No Obj: 0.491236, Avg Recall: 0.000000, count: 3Region Avg IOU: 0.013274, Class: 1.000000, Obj: 0.512686, No Obj: 0.490801, Avg Recall: 0.000000, count: 3Region Avg IOU: 0.003141, Class: 1.000000, Obj: 0.453232, No Obj: 0.492042, Avg Recall: 0.000000, count: 3......1: 440.159912, 440.159912 avg, 0.000000 rate, 8.702748 seconds, 64 images

1. 進入examples/darknet.c,從main()開始

//對應訓練命令中的第二個函數來判斷,運行其中方法#endif if (0 == strcmp(argv[1], "average")){ average(argc, argv); } else if (0 == strcmp(argv[1], "yolo")){ run_yolo(argc, argv); } else if (0 == strcmp(argv[1], "voxel")){ run_voxel(argc, argv); } else if (0 == strcmp(argv[1], "super")){ run_super(argc, argv); } else if (0 == strcmp(argv[1], "lsd")){ run_lsd(argc, argv); } else if (0 == strcmp(argv[1], "detector")){ run_detector(argc, argv); //detector.c } else if (0 == strcmp(argv[1], "detect")){ float thresh = find_float_arg(argc, argv, "-thresh", .24); 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);

2. 進入examples/detector.c,調用run_detector()方法

//對應訓練命令中的第三個函數來判斷,運行其中方法if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen); else if(0==strcmp(argv[2], "test2")) test_detector2(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen); else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear); else if(0==strcmp(argv[2], "valid")) validate_detector(datacfg, cfg, weights, outfile); else if(0==strcmp(argv[2], "valid2")) validate_detector_flip(datacfg, cfg, weights, outfile); else if(0==strcmp(argv[2], "recall")) validate_detector_recall(cfg, weights);

3. 進入train_detector()方法

void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear){ //讀取相應數據文件 list *options = read_data_cfg(datacfg); char *train_images = option_find_str(options, "train", "data/train.list");// 訓練自己的數據集時,這裡要進行相應的修改 char *backup_directory = option_find_str(options, "backup", "/backup/");// 訓練自己的數據集時,這裡要進行相應的修改 //srand()與rand()結合產生隨機數 srand(time(0)); //`basecfg()`讀取的函數為(cfg/yolo.cfg) char *base = basecfg(cfgfile);//讀取網路配置文件 printf("%s
", base);//列印"yolo"字樣 float avg_loss = -1; network *nets = calloc(ngpus, sizeof(network)); srand(time(0)); int seed = rand(); int i; //根據gpu個數解析網路結構 for(i = 0; i < ngpus; ++i){ srand(seed);#ifdef GPU cuda_set_device(gpus[i]);#endif //解析網路結構,讀取cfgfile,weightfile,清空記錄訓練次數 nets[i] = load_network(cfgfile, weightfile, clear); nets[i].learning_rate *= ngpus; } srand(time(0)); network net = nets[0]; /*imgs是一次載入到內存的圖像數量,如果占內存太大的話可以把subdivisions調大或者batch調小一點 */ int imgs = net.batch * net.subdivisions * ngpus; printf("Learning Rate: %g, Momentum: %g, Decay: %g
", net.learning_rate, net.momentum, net.decay); data train, buffer; layer l = net.layers[net.n - 1];//最後一層的索引號是net.n-1,這裡net.n是31.所以最後一層region層的索引號是30 int classes = l.classes; //抖動產生額外數據 float jitter = l.jitter; list *plist = get_paths(train_images); //int N = plist->size; char **paths = (char **)list_to_array(plist); load_args args = get_base_args(net); args.coords = l.coords; args.paths = paths; args.n = imgs;//n就是一次載入到內存中的圖片數量 args.m = plist->size;//m是待訓練圖片的總數量,train_images中的image數目,即訓練樣本數 args.classes = classes; args.jitter = jitter; args.num_boxes = l.max_boxes;// cfg里沒有配置該項,這裡使用默認值30 args.d = &buffer; args.type = DETECTION_DATA; //args.type = INSTANCE_DATA; args.threads = 8; pthread_t load_thread = load_data(args); clock_t time; int count = 0; //while(i*imgs < N*120){ while(get_current_batch(net) < net.max_batches){ //進行10次迭代後,調整一次網路大小 if(l.random && count++%10 == 0){ printf("Resizing
"); //根據訓練圖片的大小調節下面的參數 int dim = (rand() % 10 + 10) * 32; if (get_current_batch(net)+200 > net.max_batches) dim = 608; //int dim = (rand() % 4 + 16) * 32; printf("%d
", dim); //網路輸入圖片的寬高可調節 args.w = dim; args.h = dim; pthread_join(load_thread, 0); train = buffer; free_data(train); load_thread = load_data(args); for(i = 0; i < ngpus; ++i){ resize_network(nets + i, dim, dim); } net = nets[0]; } time=clock(); pthread_join(load_thread, 0); train = buffer;//這裡採用多線程的方式,這裡獲取的train里重要的成員變數有X{rows=batch, cols=[net].w*[net].h*[net].c} load_thread = load_data(args); printf("Loaded: %lf seconds
", sec(clock()-time)); time=clock(); float loss = 0;#ifdef GPU if(ngpus == 1){ loss = train_network(net, train); } else { loss = train_networks(nets, ngpus, train, 4); }#else loss = train_network(net, train);//開始訓練#endif if (avg_loss < 0) avg_loss = loss; avg_loss = avg_loss*.9 + loss*.1; i = get_current_batch(net); //列印訓練的信息 printf("%ld: %f, %f avg, %f rate, %lf seconds, %d images
", get_current_batch(net), loss, avg_loss, get_current_rate(net), sec(clock()-time), i*imgs); if(i%100==0){#ifdef GPU if(ngpus != 1) sync_nets(nets, ngpus, 0);#endif char buff[256]; sprintf(buff, "%s/%s.backup", backup_directory, base); save_weights(net, buff); } //每隔多少次保存權重 if(i%10000==0 || (i < 1000 && i%100 == 0)){#ifdef GPU if(ngpus != 1) sync_nets(nets, ngpus, 0);#endif char buff[256]; sprintf(buff, "%s/%s_%d.weights", backup_directory, base, i); save_weights(net, buff); } free_data(train); }//迭代次數達到最大值,保存最後權重 #ifdef GPU if(ngpus != 1) sync_nets(nets, ngpus, 0);#endif char buff[256]; sprintf(buff, "%s/%s_final.weights", backup_directory, base); save_weights(net, buff);}

4. 進入src/network.c,解析網路結構load_network()

network load_network(char *cfg, char *weights, int clear){ //解析.cfg文件 network net = parse_network_cfg(cfg); if(weights && weights[0] != 0){ //載入權重 load_weights(&net, weights); } if(clear) *net.seen = 0; return net;}

4.1 進入src/parser.c,解析cfg文件parse_network_cfg(char *filename)

network parse_network_cfg(char *filename){ //read_cfg這個函數將所有的參數讀到一個圖中 list *sections = read_cfg(filename); //node :這其實是一個雙向鏈表,前向和後項都是一個node數據結構。這裡,如果,這個鏈表後沒有節點的話,就報錯。 //list結構:在darknet.h文件中 node *n = sections->front; if(!n) error("Config file has no sections"); //存儲網路的配置參數:make_network在network.c中,在darkent.h文件中可以查看network結構 //make_network的作用就是產生network這種數據結構 network net = make_network(sections->size - 1); net.gpu_index = gpu_index;//設置gpu size_params params;//size_params參數結構,在parser.c文件中 section *s = (section *)n->val;//n是一個node結構,這個結構中的val是一個void*,所以這裡就是將node結構中的val強轉為section* list *options = s->options; if(!is_network(s)) error("First section must be [net] or [network]"); parse_net_options(options, &net);//傳入的是options參數和我們的network,在parser.c文件中 params.h = net.h;//將h,w,c賦值size_params對象params,下面類似不再贅述 params.w = net.w; params.c = net.c; params.inputs = net.inputs; params.batch = net.batch; params.time_steps = net.time_steps; params.net = net; size_t workspace_size = 0; n = n->next; //[net]搞定了,接下來去下一個node int count = 0; //我們把cfg的參數從section中copy到了network中,section內存不用了,自然要把它釋放。 free_section(s); //訓練輸出結果標題 fprintf(stderr, "layer filters size input output
"); //解析所有網路 while(n){ params.index = count; fprintf(stderr, "%5d ", count); s = (section *)n->val; options = s->options; layer l = {0}; LAYER_TYPE lt = string_to_layer_type(s->type); if(lt == CONVOLUTIONAL){ l = parse_convolutional(options, params); ......

4.1.1 調用parse_convolutional(options, params)方法,解析卷積層

convolutional_layer parse_convolutional(list *options, size_params params){ int n = option_find_int(options, "filters",1);//卷積核個數 int size = option_find_int(options, "size",1);//卷積核大小 int stride = option_find_int(options, "stride",1);//步長 int pad = option_find_int_quiet(options, "pad",0);//圖像周圍是否補0 int padding = option_find_int_quiet(options, "padding",0);//補0的長度 if(pad) padding = size/2; char *activation_s = option_find_str(options, "activation", "logistic");//激活函數 ACTIVATION activation = get_activation(activation_s); int batch,h,w,c; h = params.h; //圖片的高 w = params.w; //圖片的寬 c = params.c; //圖片的通道數 batch=params.batch; if(!(h && w && c)) error("Layer before convolutional layer must output image."); int batch_normalize = option_find_int_quiet(options, "batch_normalize", 0);//BN操作 int binary = option_find_int_quiet(options, "binary", 0);//權重二值化 int xnor = option_find_int_quiet(options, "xnor", 0);//權重和輸入二值化 //解析卷積層 convolutional_layer layer = make_convolutional_layer(batch,h,w,c,n,size,stride,padding,activation, batch_normalize, binary, xnor, params.net.adam); layer.flipped = option_find_int_quiet(options, "flipped", 0); layer.dot = option_find_float_quiet(options, "dot", 0); return layer;}

4.1.1.1 進入src/convolutional_layer.c文件,調用make_convolutional_layer()方法,進行解析卷積層

//載入權重文件時,已經對卷積網路做了初始化處理 for(i = 0; i < c*n*size*size; ++i) l.weights[i] = scale*rand_normal(); ...... //輸出參數 fprintf(stderr, "conv %5d %2d x%2d /%2d %4d x%4d x%4d -> %4d x%4d x%4d
", n, size, size, stride, w, h, c, l.out_w, l.out_h, l.out_c);

4.2 進入src/parser.c,載入權重load_weights(network *net, char *filename)

void load_weights(network *net, char *filename){ //調用load_weights_upto(net, filename, net->n)函數 load_weights_upto(net, filename, 0, net->n);

4.2.1 調用load_weights_upto()方法

//讀取各層權重 layer l = net->layers[i]; if (l.dontload) continue; if(l.type == CONVOLUTIONAL || l.type == DECONVOLUTIONAL){ load_convolutional_weights(l, fp); } if(l.type == CONNECTED){ load_connected_weights(l, fp, transpose); } if(l.type == BATCHNORM){ load_batchnorm_weights(l, fp); } //卷積層的權重載入 if(l.type == CRNN){ load_convolutional_weights(*(l.input_layer), fp); load_convolutional_weights(*(l.self_layer), fp); load_convolutional_weights(*(l.output_layer), fp); }

4.2.1.1 調用load_convolutional_weights()方法,載入積層的權重載入

//卷積層的參數個數,卷積核個數×通道數×卷積核長度×卷積核寬度 int num = l.n*l.c*l.size*l.size; ...... if (l.flipped) { //轉置矩陣 transpose_matrix(l.weights, l.c*l.size*l.size, l.n); } #ifdef GPU if(gpu_index >= 0){ push_convolutional_layer(l); } #endif

4.2.1.1.1 調用transpose_matrix()方法,轉置矩陣

void transpose_matrix(float *a, int rows, int cols){ //calloc:在內存的動態存儲區中分配n個長度為size的連續空間,函數返回一個指向分配起始地址的指針;如果分配不成功,返回NULL。 float *transpose = calloc(rows*cols, sizeof(float)); int x, y; for(x = 0; x < rows; ++x){ for(y = 0; y < cols; ++y){ transpose[y*rows + x] = a[x*cols + y]; } } //memcpy指的是c和c++使用的內存拷貝函數,memcpy函數的功能是從源src所指的內存地址的起始位置開始拷貝n個位元組到目標dest所指的內存地址的起始位置中 memcpy(a, transpose, rows*cols*sizeof(float)); free(transpose);}

4.2.1.1.2 進入src/convolutional_kernels.cu,調用push_convolutional_layer()方法,推動卷積層

void push_convolutional_layer(convolutional_layer layer){ cuda_push_array(layer.weights_gpu, layer.weights, layer.c*layer.n*layer.size*layer.size); cuda_push_array(layer.biases_gpu, layer.biases, layer.n); cuda_push_array(layer.weight_updates_gpu, layer.weight_updates, layer.c*layer.n*layer.size*layer.size); cuda_push_array(layer.bias_updates_gpu, layer.bias_updates, layer.n); if (layer.batch_normalize){ cuda_push_array(layer.scales_gpu, layer.scales, layer.n); cuda_push_array(layer.rolling_mean_gpu, layer.rolling_mean, layer.n); cuda_push_array(layer.rolling_variance_gpu, layer.rolling_variance, layer.n); }}

4.2.1.1.2.1 進入src/cuda.c,調用cuda_push_array()方法,使用cuda推動數組

void cuda_push_array(float *x_gpu, float *x, size_t n){ size_t size = sizeof(float)*n; //cudaMemcpy 線性內存拷貝 cudaError_t status = cudaMemcpy(x_gpu, x, size, cudaMemcpyHostToDevice); check_error(status);}

5. 進入src/network.c,調用train_network(network net, data d) ,開始訓練

float train_network(network net, data d){ //調用data的相關參數 assert(d.X.rows % net.batch == 0); int batch = net.batch; //注意,n現在表示載入一次數據可以訓練幾次,其實就是subdivisions int n = d.X.rows / batch; int i; float sum = 0; for(i = 0; i < n; ++i){ //完成數據拷貝 get_next_batch(d, batch, i*batch, net.input, net.truth); float err = train_network_datum(net); sum += err; } return (float)sum/(n*batch);}

5.1 進入src/data.c,調用get_next_batch(),完成數據拷貝

void get_next_batch(data d, int n, int offset, float *X, float *y){ int j; for(j = 0; j < n; ++j){ //offset就是第幾個batch(i*batch)了,j表示的是每個batch中的第幾個樣本(圖像) int index = offset + j; //void *memcpy(void *dest, const void *src, size_t n); //memcpy函數的功能是從源src所指的內存地址的起始位置開始拷貝n個位元組到目標dest所指的內存地址的起始位置中 memcpy(X+j*d.X.cols, d.X.vals[index], d.X.cols*sizeof(float)); if(y) memcpy(y+j*d.y.cols, d.y.vals[index], d.y.cols*sizeof(float)); }}

5.2 進入src/network.c,調用train_network_datum(network net)

float train_network_datum(network net){#ifdef GPU//使用GPU時訓練網路 if(gpu_index >= 0) return train_network_datum_gpu(net);#endif *net.seen += net.batch; net.train = 1; forward_network(net); backward_network(net); //計算平均損失 float error = *net.cost; //*net.seen是已經訓練過的子batch數, //((*net.seen)/net.batch)%net.subdivisions的意義正是已經訓練過了多少個真正的batch。 if(((*net.seen)/net.batch)%net.subdivisions == 0) update_network(net); return error;}

5.2.1 進入src/network_kernels.cu,調用train_network_datum_gpu(network net)

float train_network_datum_gpu(network net){ *net.seen += net.batch; int x_size = net.inputs*net.batch; int y_size = net.truths*net.batch; cuda_push_array(net.input_gpu, net.input, x_size); cuda_push_array(net.truth_gpu, net.truth, y_size); net.train = 1; //前向反向傳播 forward_network_gpu(net); backward_network_gpu(net); float error = *net.cost;//網路代價 if (((*net.seen) / net.batch) % net.subdivisions == 0) update_network_gpu(net);//更新網路 return error;}

5.2.1.1 進入src/cuda.c,調用cuda_push_array()方法,使用cuda推動數組

void cuda_push_array(float *x_gpu, float *x, size_t n){ size_t size = sizeof(float)*n; //cuda 線性內存拷貝 cudaError_t status = cudaMemcpy(x_gpu, x, size, cudaMemcpyHostToDevice); check_error(status);}

5.2.1.2 進入src/network_kernels.cu,調用forward_network_gpu()前向傳播

void forward_network_gpu(network net){ int i; for(i = 0; i < net.n; ++i){ net.index = i; layer l = net.layers[i]; if(l.delta_gpu){ //填滿gpu fill_gpu(l.outputs * l.batch, 0, l.delta_gpu, 1); } //接著進入region_layer.c,調用forward_region_layer()解析列印訓練的值 //找到與 forward_gpu(l, net);對應的函數 l.forward_gpu(l, net); net.input_gpu = l.output_gpu; net.input = l.output; if(l.truth) { net.truth_gpu = l.output_gpu; net.truth = l.output; } } //取消網路輸出 pull_network_output(net); //計算網路代價 calc_network_cost(net);}

5.2.1.2.1 進入src/blas_kernels.cu,調用fill_gpu(),填滿gpu

//extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標是「extern」的;其次,被它修飾的目標是「C」的。//被extern "C"修飾的變數和函數是按照C語言方式編譯和鏈接的extern "C" void fill_gpu(int N, float ALPHA, float * X, int INCX){ //填滿內核 //cuda_gridsize(N) 網格尺寸 //核函數是GPU每個thread上運行的程序。必須通過__gloabl__函數類型限定符定義。形式如下:__global__ void kernel(param list){ } //核函數只能在主機端調用,調用時必須申明執行參數。調用形式如下:Kernel<<<Dg,Db, Ns, S>>>(param list); //核函數的調用,注意<<<cuda_gridsize(N),BLOCK>>>,第一個,代表線程格里有多少(cuda_gridsize(N))線程塊;第二個,代表第一個線程塊里只有多少(BLOCK)線程。 fill_kernel<<<cuda_gridsize(N), BLOCK>>>(N, ALPHA, X, INCX); check_error(cudaPeekAtLastError());}

5.2.1.2.1.1 進入src/blas_kernels.cu,調用fill_kernel(),填滿內核

//內核函數使用關鍵字__global__來聲明,運行該函數的CUDA線程數則通過<<<...>>>執行配置語法來設置//CUDA函數定義 __global__ void: 執行於Device,僅能從Host調用。此類函數必須返回void__global__ void fill_kernel(int N, float ALPHA, float *X, int INCX){ int i = (blockIdx.x + blockIdx.y*gridDim.x) * blockDim.x + threadIdx.x; if(i < N) X[i*INCX] = ALPHA;}

5.2.1.2.1.2 進入src/cuda.c,調用cuda_gridsize(N)方法

//cuda_gridsize() 網格尺寸//數據類型 內建dim3類型:定義grid和block的組織方法。dim3 cuda_gridsize(size_t n){ //size_t:是一個基本的無符號整數的C / C + +類型。 它是sizeof操作符返回的結果類型。 size_t k = (n-1) / BLOCK + 1; size_t x = k; size_t y = 1; if(x > 65535){ //ceil():返回大於或者等於指定表達式的最小整數 //sqrt():用來計算一個非負實數的平方根 x = ceil(sqrt(k)); y = (n-1)/(x*BLOCK) + 1; } dim3 d = {x, y, 1}; //printf("%ld %ld %ld %ld
", n, x, y, x*y*BLOCK); return d;}

5.2.1.2.2 進入src/blas_kernels.cu,調用pull_network_output(),取消網路輸出

void pull_network_output(network net){ layer l = get_network_output_layer(net); //cuda 取消數組 cuda_pull_array(l.output_gpu, l.output, l.outputs*l.batch);}

5.2.1.2.2.1 進入src/cuda.c,調用cuda_pull_array(),cuda取消數組

void cuda_pull_array(float *x_gpu, float *x, size_t n){ size_t size = sizeof(float)*n; cudaError_t status = cudaMemcpy(x, x_gpu, size, cudaMemcpyDeviceToHost); check_error(status);}

5.2.1.2.3 進入src/network.c,調用calc_network_cost(),計算網路代價

void calc_network_cost(network net){ int i; float sum = 0; int count = 0; for(i = 0; i < net.n; ++i){ if(net.layers[i].cost){ sum += net.layers[i].cost[0]; ++count; } } *net.cost = sum/count;}

5.2.1.3 進入src/network_kernels.cu,調用backward_network_gpu()後向傳播

void backward_network_gpu(network net){ int i; network orig = net; for(i = net.n-1; i >= 0; --i){ layer l = net.layers[i]; if(l.stopbackward) break; if(i == 0){ net = orig; }else{ layer prev = net.layers[i-1]; net.input = prev.output; net.delta = prev.delta; net.input_gpu = prev.output_gpu; net.delta_gpu = prev.delta_gpu; } net.index = i; l.backward_gpu(l, net); }}

5.2.1.4 進入src/network_kernels.cu,調用update_network_gpu()更新網路

void update_network_gpu(network net){ cuda_set_device(net.gpu_index); int i; update_args a = {0}; a.batch = net.batch*net.subdivisions; a.learning_rate = get_current_rate(net); a.momentum = net.momentum; a.decay = net.decay; a.adam = net.adam; a.B1 = net.B1; a.B2 = net.B2; a.eps = net.eps; ++*net.t; a.t = (*net.t); for(i = 0; i < net.n; ++i){ layer l = net.layers[i]; if(l.update_gpu){ l.update_gpu(l, a); } }}

5.2.1.5 進入 src/region_layer.c,調用l.forward_gpu(l, net);對應的方法forward_region_layer(const layer l, network net)

void forward_region_layer(const layer l, network net){ int i,j,b,t,n; //內存拷貝函數 memcpy(l.output, net.input, l.outputs*l.batch*sizeof(float));#ifndef GPU for (b = 0; b < l.batch; ++b){ for(n = 0; n < l.n; ++n){ int index = entry_index(l, b, n*l.w*l.h, 0); activate_array(l.output + index, 2*l.w*l.h, LOGISTIC); index = entry_index(l, b, n*l.w*l.h, l.coords); if(!l.background) activate_array(l.output + index, l.w*l.h, LOGISTIC); } } if (l.softmax_tree){ int i; int count = l.coords + 1; for (i = 0; i < l.softmax_tree->groups; ++i) { int group_size = l.softmax_tree->group_size[i]; softmax_cpu(net.input + count, group_size, l.batch, l.inputs, l.n*l.w*l.h, 1, l.n*l.w*l.h, l.temperature, l.output + count); count += group_size; } } else if (l.softmax){ int index = entry_index(l, 0, 0, l.coords + !l.background); softmax_cpu(net.input + index, l.classes + l.background, l.batch*l.n, l.inputs/l.n, l.w*l.h, 1, l.w*l.h, 1, l.output + index); }#endif memset(l.delta, 0, l.outputs * l.batch * sizeof(float)); if(!net.train) return; float avg_iou = 0; float recall = 0; float avg_cat = 0; float avg_obj = 0; float avg_anyobj = 0; int count = 0; int class_count = 0; *(l.cost) = 0; for (b = 0; b < l.batch; ++b) { if(l.softmax_tree){ int onlyclass = 0; for(t = 0; t < 30; ++t){ box truth = float_to_box(net.truth + t*(l.coords + 1) + b*l.truths, 1); if(!truth.x) break; int class = net.truth[t*(l.coords + 1) + b*l.truths + l.coords]; float maxp = 0; int maxi = 0; if(truth.x > 100000 && truth.y > 100000){ for(n = 0; n < l.n*l.w*l.h; ++n){ int class_index = entry_index(l, b, n, l.coords + 1); int obj_index = entry_index(l, b, n, l.coords); float scale = l.output[obj_index]; l.delta[obj_index] = l.noobject_scale * (0 - l.output[obj_index]); float p = scale*get_hierarchy_probability(l.output + class_index, l.softmax_tree, class, l.w*l.h); if(p > maxp){ maxp = p; maxi = n; } } int class_index = entry_index(l, b, maxi, l.coords + 1); int obj_index = entry_index(l, b, maxi, l.coords); delta_region_class(l.output, l.delta, class_index, class, l.classes, l.softmax_tree, l.class_scale, l.w*l.h, &avg_cat); if(l.output[obj_index] < .3) l.delta[obj_index] = l.object_scale * (.3 - l.output[obj_index]); else l.delta[obj_index] = 0; l.delta[obj_index] = 0; ++class_count; onlyclass = 1; break; } } if(onlyclass) continue; } // 下面的 for 循環是計算沒有物體的 box 的 confidence 的 loss // 1, 遍歷所有格子以及每個格子的 box,計算每個 box 與真實 box 的 best_iou // 2, 先不管三七二十一,把該 box 當成沒有目標來算 confidence 的 loss // 3, 如果當前 box 的 best_iou > 閾值,則說明該 box 是有物體的,於是上面哪行計算的 loss 就不算數,因此把剛才計算的 confidence 的 loss 清零。 // 假設圖片被分成了 13 * 13 個格子,那 l.h 和 l.w 就為 13 // 於是要遍歷所有的格子,因此下面就要循環 13 * 13 次 for (j = 0; j < l.h; ++j) { for (i = 0; i < l.w; ++i) { // 每個格子會預測 5 個 boxes,因此這裡要循環 5 次 for (n = 0; n < l.n; ++n) { // 獲得 box 的 index int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0); // 獲得 box 的預測 x, y, w, h,注意都是相對值,不是真實坐標 box pred = get_region_box(l.output, l.biases, n, box_index, i, j, l.w, l.h, l.w*l.h); float best_iou = 0; //假設一張圖片中最多包含 30 個物體,於是對每一個物體求 iou for(t = 0; t < 30; ++t){ box truth = float_to_box(net.truth + t*(l.coords + 1) + b*l.truths, 1); // 遍歷完圖片中的所有物體後退出 if(!truth.x) break; float iou = box_iou(pred, truth); if (iou > best_iou) { best_iou = iou; } } // 獲得預測結果中保存 confidence 的 index int obj_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, l.coords); avg_anyobj += l.output[obj_index]; l.delta[obj_index] = l.noobject_scale * (0 - l.output[obj_index]); if(l.background) l.delta[obj_index] = l.noobject_scale * (1 - l.output[obj_index]); if (best_iou > l.thresh) { l.delta[obj_index] = 0; } if(*(net.seen) < 12800){ // 單純的獲取「以當前格子中心」為 x, y 的 box 作為 truth box box truth = {0}; truth.x = (i + .5)/l.w; truth.y = (j + .5)/l.h; truth.w = l.biases[2*n]/l.w; truth.h = l.biases[2*n+1]/l.h; // 將預測的 tx, ty, tw, th 和 實際box計算得出的 tx,ty, tw, th 的差存入 l.biases delta_region_box(truth, l.output, l.biases, n, box_index, i, j, l.w, l.h, l.delta, .01, l.w*l.h); } } } } // 因此下面是「直接遍歷一張圖片中的所有已標記的物體的中心所在的格子,然後計算 loss」,而不是「遍歷那 13*13 個格子後判斷當期格子有無物體,然後計算 loss」 for(t = 0; t < 30; ++t){ box truth = float_to_box(net.truth + t*(l.coords + 1) + b*l.truths, 1); // 如果本格子中不包含任何物體的中心,則跳過 if(!truth.x) break; float best_iou = 0; int best_n = 0; // 假設圖片被分成了 13 * 13 個格子,那 l.h 和 l.w 就為 13 // 於是要遍歷所有的格子,因此下面就要循環 13 * 13 次 // 也因此,i 和 j 就是真實物品中心所在的格子的「行」和「列」 i = (truth.x * l.w); j = (truth.y * l.h); //printf("%d %f %d %f
", i, truth.x*l.w, j, truth.y*l.h); box truth_shift = truth; // 上面獲得了 truth box 的 x,y,w,h,這裡講 truth box 的 x,y 偏移到 0,0,記為 truth_shift.x, truth_shift.y,這麼做是為了方便計算 iou truth_shift.x = 0; truth_shift.y = 0; //printf("index %d %d
",i, j); // 每個格子會預測 5 個 boxes,因此這裡要循環 5 次 for(n = 0; n < l.n; ++n){ // 獲得預測結果中 box 的 index int box_index = entry_index(l, b, n*l.w*l.h + j*l.w + i, 0); // 獲得 box 的預測 x, y, w, h,注意都是相對值,不是真實坐標 box pred = get_region_box(l.output, l.biases, n, box_index, i, j, l.w, l.h, l.w*l.h); // 這裡用 anchor box 的值 / l.w 和 l.h 作為預測的 w 和 h if(l.bias_match){ pred.w = l.biases[2*n]/l.w; pred.h = l.biases[2*n+1]/l.h; } //printf("pred: (%f, %f) %f x %f
", pred.x, pred.y, pred.w, pred.h); // 上面 truth box 的 x,y 移動到了 0,0 ,因此預測 box 的 x,y 也要移動到 0,0,這麼做是為了方便計算 iou pred.x = 0; pred.y = 0; float iou = box_iou(pred, truth_shift); if (iou > best_iou){ best_iou = iou; best_n = n; } } //printf("%d %f (%f, %f) %f x %f
", best_n, best_iou, truth.x, truth.y, truth.w, truth.h); // 根據上面的 best_n 找出 box 的 index int box_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, 0); // 計算 box 和 truth box 的 iou float iou = delta_region_box(truth, l.output, l.biases, best_n, box_index, i, j, l.w, l.h, l.delta, l.coord_scale * (2 - truth.w*truth.h), l.w*l.h); if(l.coords > 4){ int mask_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, 4); delta_region_mask(net.truth + t*(l.coords + 1) + b*l.truths + 5, l.output, l.coords - 4, mask_index, l.delta, l.w*l.h, l.mask_scale); } // 如果 iou > .5,recall +1 if(iou > .5) recall += 1; avg_iou += iou; //l.delta[best_index + 4] = iou - l.output[best_index + 4]; // 根據 best_n 找出 confidence 的 index int obj_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, l.coords); avg_obj += l.output[obj_index]; // 因為運行到這裡意味著該格子中有物體中心,所以該格子的 confidence 就是 1, 而預測的 confidence 是 l.output[obj_index],所以根據公式有下式 l.delta[obj_index] = l.object_scale * (1 - l.output[obj_index]); if (l.rescore) { // 用 iou 代替上面的 1(經調試,l.rescore = 1,因此能走到這裡) l.delta[obj_index] = l.object_scale * (iou - l.output[obj_index]); } if(l.background){ l.delta[obj_index] = l.object_scale * (0 - l.output[obj_index]); } // 獲得真實的 class int class = net.truth[t*(l.coords + 1) + b*l.truths + l.coords]; if (l.map) class = l.map[class]; // 獲得預測的 class 的 index int class_index = entry_index(l, b, best_n*l.w*l.h + j*l.w + i, l.coords + 1); // 把所有 class 的預測概率與真實 class 的 0/1 的差 * scale,然後存入 l.delta 里相應 class 序號的位置 delta_region_class(l.output, l.delta, class_index, class, l.classes, l.softmax_tree, l.class_scale, l.w*l.h, &avg_cat); ++count; ++class_count; } } //printf("
"); // 現在,l.delta 中的每一個位置都存放了 class、confidence、x, y, w, h 的差,於是通過 mag_array 遍歷所有位置,計算每個位置的平方的和後開根 // 然後利用 pow 函數求平方 *(l.cost) = pow(mag_array(l.delta, l.outputs * l.batch), 2); //列印yolo printf("Region Avg IOU: %f, Class: %f, Obj: %f, No Obj: %f, Avg Recall: %f, count: %d
", avg_iou/count, avg_cat/class_count, avg_obj/count, avg_anyobj/(l.w*l.h*l.n*l.batch), recall/count, count);}

推薦閱讀:

智能硬體:AI給智能硬體帶來了第二春還是第二個大泡沫?| 2018展望
數據分析核心語言Python
「提直降代」加速,航司聚焦打造以客戶為中心的營銷渠道
電視廠商做出來的手機什麼樣? 海信H11深度評測

TAG:人工智慧AI醬 |