./darknet detector train cfg/voc.data cfg/yolov3-voc.cfg darknet53.conv.74
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; //明顯設置不使用gpu; } #ifndef GPU gpu_index = -1; //未定義gpu,則不使用; #else if(gpu_index >= 0){ cuda_set_device(gpu_index); //按照你輸入的gpu索引分配gpu; } #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], "super")){ run_super(argc, argv); } else if (0 == strcmp(argv[1], "lsd")){ run_lsd(argc, argv); } else if (0 == strcmp(argv[1], "detector")){//第一個參數是detector run_detector(argc, argv); } else if (0 == strcmp(argv[1], "detect")){ 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); } else if (0 == strcmp(argv[1], "cifar")){ run_cifar(argc, argv); } else if (0 == strcmp(argv[1], "go")){ run_go(argc, argv); } else if (0 == strcmp(argv[1], "rnn")){ run_char_rnn(argc, argv); } else if (0 == strcmp(argv[1], "coco")){ run_coco(argc, argv); } else if (0 == strcmp(argv[1], "classify")){ predict_classifier("cfg/imagenet1k.data", argv[2], argv[3], argv[4], 5); } else if (0 == strcmp(argv[1], "classifier")){ run_classifier(argc, argv); } else if (0 == strcmp(argv[1], "regressor")){ run_regressor(argc, argv); } else if (0 == strcmp(argv[1], "segmenter")){ run_segmenter(argc, argv); } else if (0 == strcmp(argv[1], "art")){ run_art(argc, argv); } else if (0 == strcmp(argv[1], "tag")){ run_tag(argc, argv); } else if (0 == strcmp(argv[1], "3d")){ composite_3d(argv[2], argv[3], argv[4], (argc > 5) ? atof(argv[5]) : 0); } else if (0 == strcmp(argv[1], "test")){ test_resize(argv[2]); } else if (0 == strcmp(argv[1], "captcha")){ run_captcha(argc, argv); } else if (0 == strcmp(argv[1], "nightmare")){ run_nightmare(argc, argv); } else if (0 == strcmp(argv[1], "rgbgr")){ rgbgr_net(argv[2], argv[3], argv[4]); } else if (0 == strcmp(argv[1], "reset")){ reset_normalize_net(argv[2], argv[3], argv[4]); } else if (0 == strcmp(argv[1], "denormalize")){ denormalize_net(argv[2], argv[3], argv[4]); } else if (0 == strcmp(argv[1], "statistics")){ statistics_net(argv[2], argv[3]); } else if (0 == strcmp(argv[1], "normalize")){ normalize_net(argv[2], argv[3], argv[4]); } else if (0 == strcmp(argv[1], "rescale")){ rescale_net(argv[2], argv[3], argv[4]); } else if (0 == strcmp(argv[1], "ops")){ operations(argv[2]); } else if (0 == strcmp(argv[1], "speed")){ speed(argv[2], (argc > 3 && argv[3]) ? atoi(argv[3]) : 0); } else if (0 == strcmp(argv[1], "oneoff")){ oneoff(argv[2], argv[3], argv[4]); } else if (0 == strcmp(argv[1], "oneoff2")){ oneoff2(argv[2], argv[3], argv[4], atoi(argv[5])); } else if (0 == strcmp(argv[1], "print")){ print_weights(argv[2], argv[3], atoi(argv[4])); } else if (0 == strcmp(argv[1], "partial")){ partial(argv[2], argv[3], argv[4], atoi(argv[5])); } else if (0 == strcmp(argv[1], "average")){ average(argc, argv); } else if (0 == strcmp(argv[1], "visualize")){ visualize(argv[2], (argc > 3) ? argv[3] : 0); } else if (0 == strcmp(argv[1], "mkimg")){ mkimg(argv[2], argv[3], atoi(argv[4]), atoi(argv[5]), atoi(argv[6]), argv[7]); } else if (0 == strcmp(argv[1], "imtest")){ test_resize(argv[2]); } else { fprintf(stderr, "Not an option: %s\n", argv[1]); } return 0; }
detector.c
void run_detector(int argc, char **argv) { char *prefix = find_char_arg(argc, argv, "-prefix", 0); float thresh = find_float_arg(argc, argv, "-thresh", .5); //讀取得分閾值,默認為0.5 float hier_thresh = find_float_arg(argc, argv, "-hier", .5); int cam_index = find_int_arg(argc, argv, "-c", 0); //讀取是否指定攝像頭,默認為0 int frame_skip = find_int_arg(argc, argv, "-s", 0); int avg = find_int_arg(argc, argv, "-avg", 3); if(argc < 4){ fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]); return; } char *gpu_list = find_char_arg(argc, argv, "-gpus", 0); char *outfile = find_char_arg(argc, argv, "-out", 0); int *gpus = 0; int gpu = 0; int ngpus = 0; if(gpu_list){ printf("%s\n", gpu_list); int len = strlen(gpu_list); ngpus = 1; int i; for(i = 0; i < len; ++i){ if (gpu_list[i] == ',') ++ngpus;//獲取gpu的數量 } gpus = calloc(ngpus, sizeof(int));//分配ngpus個gpu出來 for(i = 0; i < ngpus; ++i){ gpus[i] = atoi(gpu_list); gpu_list = strchr(gpu_list, ',')+1; } } else { gpu = gpu_index; gpus = &gpu; ngpus = 1; } int clear = find_arg(argc, argv, "-clear"); int fullscreen = find_arg(argc, argv, "-fullscreen"); int width = find_int_arg(argc, argv, "-w", 0); int height = find_int_arg(argc, argv, "-h", 0); int fps = find_int_arg(argc, argv, "-fps", 0); //int class = find_int_arg(argc, argv, "-class", 0); char *datacfg = argv[3]; //數據 cfg/voc.data char *cfg = argv[4]; //網絡配置 cfg/yolov3-voc.cfg char *weights = (argc > 5) ? argv[5] : 0; //權重文件,如果命令行參數超過5個,則把第6個傳給權重 char *filename = (argc > 6) ? argv[6]: 0;//測試文件; if(0==strcmp(argv[2], "test")) test_detector(datacfg, cfg, weights, filename, thresh, hier_thresh, outfile, fullscreen); else if(0==strcmp(argv[2], "train")) train_detector(datacfg, cfg, weights, gpus, ngpus, clear); //第3個參數是train,表示進行訓練 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); else if(0==strcmp(argv[2], "demo")) { list *options = read_data_cfg(datacfg); int classes = option_find_int(options, "classes", 20); char *name_list = option_find_str(options, "names", "data/names.list"); char **names = get_labels(name_list); demo(cfg, weights, thresh, cam_index, filename, names, classes, frame_skip, prefix, avg, hier_thresh, width, height, fps, fullscreen); } //else if(0==strcmp(argv[2], "extract")) extract_detector(datacfg, cfg, weights, cam_index, filename, class, thresh, frame_skip); //else if(0==strcmp(argv[2], "censor")) censor_detector(datacfg, cfg, weights, cam_index, filename, class, thresh, frame_skip); }
void train_detector(char *datacfg, char *cfgfile, char *weightfile, int *gpus, int ngpus, int clear) {//(數據配置、網絡配置、權重、gpu、gpu號、clear) list *options = read_data_cfg(datacfg); //1、讀取數據配置文件信息 char *train_images = option_find_str(options, "train", "data/train.list"); char *backup_directory = option_find_str(options, "backup", "/backup/"); srand(time(0));
/*srand函數是隨機數發生器的初始化函數。srand和rand()配合使用產生偽隨機數序列。
rand函數在產生隨機數前,需要系統提供的生成偽隨機數序列的種子,rand根據這個種子的值產生一系列隨機數。
如果系統提供的種子沒有變化,每次調用rand函數生成的偽隨機數序列都是一樣的。*/
char *base = basecfg(cfgfile); //2、讀取網絡配置
// 提取配置文件名稱中的主要信息,用於輸出打印(無實質作用),比如提取`cfg/yolov3-voc.cfg’,輸出yolov3-voc printf("%s\n", base); float avg_loss = -1; network **nets = calloc(ngpus, sizeof(network)); srand(time(0)); int seed = rand(); int i;
//每一次循環都會構建一個相同的神經網絡,如果提供了初始訓練參數,也會為每個網絡導入相同的初始訓練參數 for(i = 0; i < ngpus; ++i){ srand(seed); #ifdef GPU cuda_set_device(gpus[i]); //如果定義了gpu則分配 #endif nets[i] = load_network(cfgfile, weightfile, clear); //3、網絡配置對應的網絡結構和加載權重
//包含parse_network_cfg()和load_weights()
//src/network.c;yolov2里面的解析網絡和加載權重是直接在這里調用的,yolov3將兩個函數集合在load_network里面,我覺得是更加好理解了! 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\n", net->learning_rate, net->momentum, net->decay); data train, buffer; layer l = net->layers[net->n - 1]; int classes = l.classes; float jitter = l.jitter; //[非均衡數據集處理:利用抖動(jittering)生成額外數據] 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; //一次加載到內存中的圖片數量 args.m = plist->size; //待訓練圖片數量 args.classes = classes; args.jitter = jitter; args.num_boxes = l.max_boxes; args.d = &buffer; args.type = DETECTION_DATA; //args.type = INSTANCE_DATA; args.threads = 64; //聲明線程ID pthread_t load_thread = load_data(args); //src/data.c double time; int count = 0; //while(i*imgs < N*120){ while(get_current_batch(net) < net->max_batches){ if(l.random && count++%10 == 0){ printf("Resizing\n"); 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\n", dim); args.w = dim; args.h = dim; pthread_join(load_thread, 0);//以阻塞的方式等待thread指定的線程結束當函數返回時,被等待線程的資源被 收回。如果線程已經結束,那么該函數會立即返回。 train = buffer; free_data(train); load_thread = load_data(args); #pragma omp parallel for for(i = 0; i < ngpus; ++i){ resize_network(nets[i], dim, dim); //src/network.c里面resize_network(network *net, int w, int h) //將layer調整為對應的輸入大小;比如卷積、池化等
} net = nets[0]; } time=what_time_is_it_now(); pthread_join(load_thread, 0); train = buffer; load_thread = load_data(args); /* int k; for(k = 0; k < l.max_boxes; ++k){ box b = float_to_box(train.y.vals[10] + 1 + k*5); if(!b.x) break; printf("loaded: %f %f %f %f\n", b.x, b.y, b.w, b.h); } */ /* int zz; for(zz = 0; zz < train.X.cols; ++zz){ image im = float_to_image(net->w, net->h, 3, train.X.vals[zz]); int k; for(k = 0; k < l.max_boxes; ++k){ box b = float_to_box(train.y.vals[zz] + k*5, 1); printf("%f %f %f %f\n", b.x, b.y, b.w, b.h); draw_bbox(im, b, 1, 1,0,0); } show_image(im, "truth11"); cvWaitKey(0); save_image(im, "truth11"); } */ printf("Loaded: %lf seconds\n", what_time_is_it_now()-time); time=what_time_is_it_now(); float loss = 0; #ifdef GPU
//gpu訓練
if(ngpus == 1){ loss = train_network(net, train); //其中net是網絡,train是訓練數據data } 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\n", get_current_batch(net), loss, avg_loss, get_current_rate(net), what_time_is_it_now()-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); }
//前1000次迭代中每100次保存一次,之后每10000次保存一次權重
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); }
darknet/src/network.c
float train_network(network *net, data d) { assert(d.X.rows % net->batch == 0);
//rows是一次加載到內存中的樣本個數(batch*subdivisions),cols是樣本的維度,
int batch = net->batch; int n = d.X.rows / batch;//subdivision:也就是多少個batch,即加載一次需要訓練幾次 int i; float sum = 0; for(i = 0; i < n; ++i){ get_next_batch(d, batch, i*batch, net->input, net->truth);
//完成數據拷貝,從d拷貝到網絡的輸入值input和真實值truth中
//batch:每個batch里面的樣本;i*batch:第幾個batch;
float err = train_network_datum(net); sum += err; } return (float)sum/(n*batch); }
float train_network_datum(network *net) { *net->seen += net->batch;//seen是已經訓練過的樣本數; net->train = 1; forward_network(net); //前向傳播並且計算損失 backward_network(net); float error = *net->cost; //在network.c里面前向傳輸過程中計算cost,calc_network_cost if(((*net->seen)/net->batch)%net->subdivisions == 0) update_network(net); return error; }
到此為止,最簡單的訓練就結束了!但是最重要的前向傳播和反向傳播值得深究!!!