Tic商业评论

关注微信公众号【站长自定义模块】,定时推送前沿、专业、深度的商业资讯。

 找回密码
 立即注册

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

  • QQ空间
  • 回复
  • 收藏

darknet框架解析(二)-darknet的数据结构

lijingle 深度学习 2022-2-16 19:17 1580人围观

上篇中我们了解了darknet的基本流程,在本文中我们着重介绍下darknet的数据结构。这里我们以YOLOv3为例,看下其数据结构,如下图所示:


这里是网络配置文件,在进入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,该nodevoid指针指向一个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; //学习率


路过

雷人

握手

鲜花

鸡蛋
我有话说......
电话咨询: 135xxxxxxx
关注微信