系列目录
darknet 源码阅读(零) - Entry Point
darknet 源码阅读(一) - 解析网络配置文件 cfg
darknet 源码阅读(二) - 加载训练样本数据
darknet 源码阅读(三) - 训练网络
darknet 源码阅读(番外篇一) - 卷积层
从第一次接触 darknet 到现在也有很长时间了, 最初的印象是代码中几乎没有使用任何第三方库, 相比于 Caffe 的编译速度, darknet 简直快的飞起. 当时就有一种阅读源码的想法, 最近正好项目需要且时间允许, 就顺便完成了这个计划吧.
源码阅读第一步: 找到入口 - main() 函数.
文件: darknet/examples/darknet.c,
函数: main()
1. 关于分析主线的确定
darknet 实现了较多的深度学习应用工具, 例如: classifier, segmenter, detector 等. 基于目前的研究方向为目标检测, 以 detector 作为主线展开后续的分析.
int main(int argc, char **argv)
{
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")){
...
} else if (0 == strcmp(argv[1], "detector")){
run_detector(argc, argv);
} 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], "imtest")){
test_resize(argv[2]);
} else {
fprintf(stderr, "Not an option: %s\n", argv[1]);
}
return 0;
}
2. 目标检测 - run_detector
对于任意一个深度学习应用场景, 必不可少的三步: 训练-验证-测试. 同样地, 对于 detector 也是如此, 它应该包含这些工具. run_detector() 函数中为我们提供了这些功能. 由于测试阶段中完成的工作几乎和训练阶段的前向运算完全一致, 因此, 我们重点分析 detector 的训练过程.
void run_detector(int argc, char **argv)
{
float thresh = find_float_arg(argc, argv, "-thresh", .5);
...
if(argc < 4){
fprintf(stderr, "usage: %s %s [train/test/valid] [cfg] [weights (optional)]\n", argv[0], argv[1]);
return;
}
...
char *datacfg = argv[3];
char *cfg = argv[4];
char *weights = (argc > 5) ? argv[5] : 0;
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);
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, savevideo);
}
}
3. 训练检测器 - train_detector
这是一个很庞大的系统, 当然不可能用一篇博客的篇幅来理清其中的所有细节. 因此, 就有了接下来的系列文章. 这里只是先从整体上分析一下训练一个检测器的流程.
- 解析网络配置文件;
- 加载训练样本图像和 labels;
- 开始训练;
- 训练结束后保存模型权重;
因此, 接下来的博客也会按照这个顺序依次分析.
下面贴出 train_detector() 函数的框架代码.
void train_detector(char *datacfg, char *cfgfile,
char *weightfile, int *gpus, int ngpus, int clear)
{
// 解析网络配置文件
list *options = read_data_cfg(datacfg);
for(i = 0; i < ngpus; ++i){
...
nets[i] = load_network(cfgfile, weightfile, clear);
nets[i]->learning_rate *= ngpus;
}
...
pthread_t load_thread = load_data(args);
while(get_current_batch(net) < net->max_batches){
...
// 加载训练样本图像和 labels;
pthread_join(load_thread, 0);
train = buffer; // 加载完成, 拿到加载后的数据
load_thread = load_data(args); // 开启新的加载线程, 供下一次使用
...
// 开始训练网络;
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
...
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);
}
总结
万事开头难, 既然已经走到了这里, 说明距离入门 darkent 又近了一步. 正所谓: 道路是曲折的, 方向是前进的.