这里是网络配置文件,在进入train_detector函数里,首先要做两件事,一件事情是读取数据配置文件,另外一件事情是读取网络配置文件。
下面是读取数据配置文件
这里比较简单,我们注重分析下网络配置文件的读取。
parse_network_cfg_custom()通过上面这个函数进入读取配置文件。
通过函数read_cfg(filename),将文件读取到sections里,这里提到sections我们看下darknet框架里的数据结构。
在darknet中,有三种主要数据结构。list类型用来保存所有的网络参数。section是用来保存网络中每一层的数据。这里char 类型就是保存例如[convolutional]等字符。kv键值对这个好理解,就是用来保存网络文件中参数对应的值。
首先是list:在文件src/list.h中
typedef struct node{
void *val;//定义值
struct node *next;//node节点后驱
struct node *prev;//node节点前驱
} node;
typedef struct list{
int size;//节点大小
node *front;//list节点前驱
node *back;//list节点后驱
} list;
这里好理解就是数据结构里的基础知识,链表内容。
section定义在src/parser.c文件中。
// 定义section
typedef struct{
char *type;
list *options;
}section;这个数据结构就是保存某一层的数据。注意结构里的char * 和 list * 里面是定义了list的的。下面就是kv键值对了,在src/option_list.h文件中。// kvp 键值对
typedef struct{
char *key;
char *val;
int used;
} kvp;
这个就是保存参数和值的数据结构。
具体的关系如下图所示:
这里根据read_cfg函数进行解释下具体的含义:
首先创建一个list,在代码里命名为sections,这里主要是记录有多少个section,list里有个参数size,即section的多少;然后创建一个node,node* newnode = (node*)xmalloc(sizeof(node)); 该node的void指针指向一个新建的section。该section里的char指针指向cfg文件里的[convolutional],[shortcut]等某一层的名字,该section的list指向一个新的node,该node
的void
指针指向一个kvp
结构体,kvp
结构体中的key
就是.cfg
文件中的关键字(如:batch,subdivisions
等),val
就是对应的值;一直循环就构成了上述网络图。
对网络的注解:
这里是对上述数据结构的流程解析,在文件src/parser.c中的read_cfg()函数实现:
/*
* 读取神经网络结构配置文件(.cfg文件)中的配置数据, 将每个神经网络层参数读取到每个
* section 结构体 (每个 section 是 sections 的一个节点) 中, 而后全部插入到
* list 结构体 sections 中并返回
*
* \param: filename C 风格字符数组, 神经网络结构配置文件路径
*
* \return: list 结构体指针,包含从神经网络结构配置文件中读入的所有神经网络层的参数
* 每个 section 的所在行的开头是 ‘[’ , ‘\0’ , ‘#’ 和 ‘;’ 符号开头的行为无效行, 除此
*之外的行为 section 对应的参数行. 每一行都是一个等式, 类似键值对的形式. *可以看到, 如果某一行开头是符号 ‘[’ , 说明读到了一个新的 section: current, 然后第1508行
*list_insert(options, current);` 将该新的 section 保存起来. *在读取到下一个开头符号为 ‘[’ 的行之前的所有行都是该 section 的参数, 在第 1518 行
*read_option(line, current->options) 将读取到的参数保存在 current 变量的 options 中.
*注意, 这里保存在 options 节点中的数据为 kvp 键值对类型. *当然对于 kvp 类型的参数, 需要先将每一行中对应的键和值(用 ‘=’ 分割) 分离出来, 然后再
*构造一个 kvp 类型的变量作为节点元素的数据.
*/
list *read_cfg(char *filename)
{
FILE *file = fopen(filename, "r");
//一个section表示配置文件中的一个字段,也就是网络结构中的一层
//因此,一个section将读取并存储某一层的参数以及该层的type
if(file == 0) file_error(filename);
char *line;
int nu = 0; //当前读取行号
list *sections = make_list(); //sections包含所有的神经网络层参数
section current = 0;//当前读取到某一层
while((line=fgetl(file)) != 0){
++ nu;
strip(line); //去除读入行中含有的空格符
switch(line[0]){
// 以 '[' 开头的行是一个新的 section , 其内容是层的 type
// 比如 [net], [maxpool], [convolutional] ...
case '[':
current = (section)xmalloc(sizeof(section));
list_insert(sections, current);
current->options = make_list();
current->type = line;
break;
case '\0': //空行
case '#': //注释
case ';': //空行
free(line); // 对于上述三种情况直接释放内存即可
break;
default:
// 剩下的才真正是网络结构的数据,调用 read_option() 函数读取
// 返回 0 说明文件中的数据格式有问题,将会提示错误
if(!read_option(line, current->options)){
fprintf(stderr, "Config file error line %d, could parse: %s\n", nu, line);
free(line);
}
break;
}
}
//关闭文件
fclose(file);
return sections;
}
链表的插入:
上面我们讲的section实际上是保存参数的一个大链,如果在大链上插入小链,需要用到list_insert函数。该函数定义在src/list.c文件中:
/*
* 简介: 将 val 指针插入 list 结构体 l 中,这里相当于是用 C 实现了 C++ 中的
* list 的元素插入功能
*
* 参数: l 链表指针
* val 链表节点的元素值
*
* 流程:list 中保存的是 node 指针. 因此,需要用 node 结构体将 val 包裹起来后才可以
* 插入 list 指针 l 中
*
* 注意: 此函数类似 C++ 的 insert() 插入方式;
* 而 opion_insert() 函数类似 C++ map 的按值插入方式,比如 map[key]= value
*
* 两个函数操作对象都是 list 变量, 只是操作方式略有不同。
*/
void list_insert(list *l, void val)
{
node newnode = (node*)xmalloc(sizeof(node));
newnode->val = val;
newnode->next = 0;
// 如果 list 的 back 成员为空(初始化为 0), 说明 l 到目前为止,还没有存入数据
// 另外, 令 l 的 front 为 new (此后 front 将不会再变,除非删除)
if(!l->back){
l->front = newnode;
newnode->prev = 0;
}else{
l->back->next = newnode;
newnode->prev = l->back;
}
l->back = newnode;
++l->size;
}
网络结构解析到链表中后还不能直接使用, 因为想使用任意一个参数都不得不每次去遍历整个链表, 这样就会导致程序效率变低, 所以最好的办法是将其保存到一个结构体变量中, 使用的时候按照成员进行访问。复杂度从O(n)->O(1)。
网络解析:
将上面读取的文件保存到network结构体中,结构体的定义在include/darknet.h中:
// 定义network结构体
typedef struct network {
int n; //网络的层数,调用make_network(int n)时赋值
int batch; //一批训练中的图片参数,和subdivsions参数相关
uint64_t *seen; //目前已经读入的图片张数(网络已经处理的图片张数)
int *t;
float epoch; //到目前为止训练了整个数据集的次数
int subdivisions;
layer *layers; //存储网络中的所有层
float *output;
learning_rate_policy policy; // 学习率下降策略
int benchmark_layers;
// 梯度下降法相关参数
float learning_rate; //学习率