【AlexeyAB DarkNet框架解析】二,数据结构解析
- 2020 年 2 月 21 日
- 筆記
按照前面的思路,这一节进入到DarkNet的数据结构解析。Darknet是一个C语言实现的神经网络框架,这就决定了其中大多数保存数据的数据结构都会使用链表这种简单高效的数据结构。
基础数据结构
为了解析网络配置参数,DarkNet 中定义了三个关键的数据结构类型。list类型变量保存所有的网络参数, section类型变量保存的是网络中每一层的网络类型和参数, 其中的参数又是使用list类型来表示。kvp键值对类型用来保存解析后的参数变量和参数值。
- list类型定义在
src/list.h中,代码如下:
// 链表上的节点 typedef struct node{ void *val; struct node *next; struct node *prev; } node; //双向链表 typedef struct list{ int size; //list的所有节点个数 node *front; //list的首节点 node *back; //list的普通节点 } list;
- section 类型定义在
src/parser.c文件中,代码如下:
// 定义section typedef struct{ char *type; list *options; }section;
- kvp 键值对类型定义在
src/option_list.h文件中,具体定义如下:
// kvp 键值对 typedef struct{ char *key; char *val; int used; } kvp;
在Darknet的网络配置文件(.cfg结尾)中,以[开头的行被称为一个段(section)。所有的网络配置参数保存在list类型变量中,list中有很多的section节点,每个section中又有一个保存层参数的小list,整体上出现了一种大链挂小链的结构。大链的每个节点为section,每个section中包含的参数保存在小链中,小链的节点值的数据类型为kvp键值对,这里有个图片可以解释这种结构。

我们来大概解释下该参数网,首先创建一个list,取名sections,记录一共有多少个section(一个section存储了某一网络层所需参数);然后创建一个node,该node的void类型的指针指向一个新创建的section;该section的char类型指针指向.cfg文件中的某一行(line),然后将该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 的所在行的开头是 ‘[’ , ‘ ’ , ‘#’ 和 ‘;’ 符号开头的行为无效行, 除此 *之外的行为 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 '