YOLOv2源码分析(二)
发布日期:2021-06-29 16:00:10 浏览次数:2 分类:技术文章

本文共 19471 字,大约阅读时间需要 64 分钟。

文章全部

0x01 parse_network_cfg

我们继续前面没有说完的parse_network_cfg

//parse_network_cfg    node *n = sections->front;    if(!n) error("Config file has no sections");

我么先要了解一下list结构

typedef struct list{    int size;    node *front;    node *back;} list;typedef struct node{    void *val;    struct node *next;    struct node *prev;} node;

这其实是一个双向链表,前向和后项都是一个node数据结构。这里,如果,这个链表后没有节点的话,就报错。

接着往后

//parse_network_cfg    network *net = make_network(sections->size - 1);

这里使用了一个make_network函数

network *make_network(int n){    network *net = calloc(1, sizeof(network));    net->n = n;    net->layers = calloc(net->n, sizeof(layer));    net->seen = calloc(1, sizeof(size_t));    net->t    = calloc(1, sizeof(int));    net->cost = calloc(1, sizeof(float));    return net;}

注意这里的这个make_network可能和早期的不太一样。我们先看看这里他做了什么。先看看network这个结构

//这个文件现在放在了darknet.h文件中typedef enum {    CONSTANT, STEP, EXP, POLY, STEPS, SIG, RANDOM} learning_rate_policy;typedef struct network{    int n;              //网络总层数    int batch;          //一个batch包含的图片数目,看下面的subdivisions    size_t *seen;       //已经读取的图片数量    int *t;    float epoch;        //训练的次数    int subdivisions;   //注意前面的batch/subdivisions才是网络的batch大小,可能目的是防止gpu显存不够    layer *layers;      //指向网络的层    float *output;    learning_rate_policy policy;//学习率的策略,是一个枚举类型    float learning_rate;//学习率    float momentum;     //动量,一般0.9    float decay;        //权重衰减正则项,防止过拟合    float gamma;        //用于计算学习率,见后面0x0102    float scale;        //用于计算学习率,见后面0x0102    float power;        //用于计算学习率,见后面0x0102    int time_steps;    int step;           //用于计算学习率,见后面0x0102    int max_batches;    //最大的训练batch数目    float *scales;      //用于计算学习率,见后面0x0102    int   *steps;       //用于计算学习率,见后面0x0102    int num_steps;      //steps中的数据个数    int burn_in;    int adam;           //adam算法    float B1;           //一阶矩估计的指数衰减率    float B2;           //二阶矩估计的指数衰减率    float eps;          //为了防止在实现中除以零    int inputs;         //h*w*c    int outputs;    int truths;    int notruth;    int h, w, c;            //输入图像的高,宽,通道数    int max_crop;           //控制图片缩放的最大值    int min_crop;           //控制图片缩放的最小值    float max_ratio;        //控制图片缩放的最大比例    float min_ratio;        //控制图片缩放的最小比例    int center;    float angle;            //设置旋转角度,扩充数据    float aspect;           //设置方位,扩充数据    float exposure;         //设置曝光量,扩充数据    float saturation;       //设置饱和度,扩充数据    float hue;              //设置色调,扩充数据    int random;             //random为1时随机使用不同尺寸的图片进行训练    int gpu_index;          //设置第几个gpu    tree *hierarchy;    float *input;    float *truth;    float *delta;    float *workspace;    int train;    int index;    float *cost;#ifdef GPU    float *input_gpu;    float *truth_gpu;    float *delta_gpu;    float *output_gpu;#endif} network;

由于参数太多,用到哪个说哪个,这个结构的主要作用就是存储网络的配置参数。make_network的作用就是产生network这种数据结构。接着往下

//parse_network_cfg    net->gpu_index = gpu_index;//设置gpu    size_params params;

又出现一个新的结构size_params

typedef struct size_params{    int batch;      //一个batch包含的图片数目    int inputs;    int h;          //图像的高    int w;          //输入图像的宽    int c;          //输入图像的通道数    int index;    int time_steps;    network *net;} size_params;

接着往下

//parse_network_cfg    section *s = (section *)n->val;//section这个结构我在(一)中提过

n是一个node结构,这个结构中的val是一个void*,所以这里就是将node结构中的val强转为section*,相当于我在(一)中图上画的[net]等节点。

//parse_network_cfg       list *options = s->options;//这里就是之前说的kvp,也就是size=3,stride=1,pad=1这些    if(!is_network(s)) error("First section must be [net] or [network]");

看一下这个is_network函数

int is_network(section *s){    return (strcmp(s->type, "[net]")==0            || strcmp(s->type, "[network]")==0);}

这个函数的作用很明显,判断传入的第一个section是不是[net][network]。接着又是一个比较大的函数

//parse_network_cfg    parse_net_options(options, net);

0x0101 parse_net_options

void parse_net_options(list *options, network *net)//传入的时options参数和我们的network{    net->batch = option_find_int(options, "batch",1);//设置net的batch大小    net->learning_rate = option_find_float(options, "learning_rate", .001);//设置学习率    net->momentum = option_find_float(options, "momentum", .9);//设置动量    net->decay = option_find_float(options, "decay", .0001);//设置权重衰减    int subdivs = option_find_int(options, "subdivisions",1);//设置subdivisions,防止显存不够    net->time_steps = option_find_int_quiet(options, "time_steps",1);    net->notruth = option_find_int_quiet(options, "notruth",0);    net->batch /= subdivs;    net->batch *= net->time_steps;    net->subdivisions = subdivs;    net->random = option_find_int_quiet(options, "random", 0);    net->adam = option_find_int_quiet(options, "adam", 0);    if(net->adam){
//设置adam参数,这里的默认选项是按照adam论文给的参数设置的 net->B1 = option_find_float(options, "B1", .9); net->B2 = option_find_float(options, "B2", .999); net->eps = option_find_float(options, "eps", .0000001); } net->h = option_find_int_quiet(options, "height",0); net->w = option_find_int_quiet(options, "width",0); net->c = option_find_int_quiet(options, "channels",0); net->inputs = option_find_int_quiet(options, "inputs", net->h * net->w * net->c); net->max_crop = option_find_int_quiet(options, "max_crop",net->w*2); net->min_crop = option_find_int_quiet(options, "min_crop",net->w); net->max_ratio = option_find_float_quiet(options, "max_ratio", (float) net->max_crop / net->w); net->min_ratio = option_find_float_quiet(options, "min_ratio", (float) net->min_crop / net->w); net->center = option_find_int_quiet(options, "center",0); net->angle = option_find_float_quiet(options, "angle", 0); net->aspect = option_find_float_quiet(options, "aspect", 1); net->saturation = option_find_float_quiet(options, "saturation", 1); net->exposure = option_find_float_quiet(options, "exposure", 1); net->hue = option_find_float_quiet(options, "hue", 0); if(!net->inputs && !(net->h && net->w && net->c)) error("No input parameters supplied"); char *policy_s = option_find_str(options, "policy", "constant"); net->policy = get_policy(policy_s); net->burn_in = option_find_int_quiet(options, "burn_in", 0); net->power = option_find_float_quiet(options, "power", 4); if(net->policy == STEP){
//如果学习率的策略是STEP的话 net->step = option_find_int(options, "step", 1); net->scale = option_find_float(options, "scale", 1); } else if (net->policy == STEPS){
//如果学习率的策略是STEPS的话 char *l = option_find(options, "steps");//指向steps的字符串 char *p = option_find(options, "scales");//指向scales的字符串 if(!l || !p) error("STEPS policy must have steps and scales in cfg file"); int len = strlen(l); int n = 1; int i; for(i = 0; i < len; ++i){ if (l[i] == ',') ++n; } int *steps = calloc(n, sizeof(int));//将所有的steps值分开存放到这个数组中 float *scales = calloc(n, sizeof(float));//将所有的scales值分开存放到这个数组中 for(i = 0; i < n; ++i){ int step = atoi(l); float scale = atof(p); l = strchr(l, ',')+1; p = strchr(p, ',')+1; steps[i] = step; scales[i] = scale; } net->scales = scales; net->steps = steps; net->num_steps = n; } else if (net->policy == EXP){ net->gamma = option_find_float(options, "gamma", 1); } else if (net->policy == SIG){ net->gamma = option_find_float(options, "gamma", 1); net->step = option_find_int(options, "step", 1); } else if (net->policy == POLY || net->policy == RANDOM){ } net->max_batches = option_find_int(options, "max_batches", 0);}

这个函数中出现了这个函数option_find_int_quiet

int option_find_int_quiet(list *l, char *key, int def){    char *v = option_find(l, key);    if(v) return atoi(v);    return def;}char *option_find(list *l, char *key){    node *n = l->front;    while(n){        kvp *p = (kvp *)n->val;        if(strcmp(p->key, key) == 0){            p->used = 1;            return p->val;        }        n = n->next;    }    return 0;}

这个函数和之前的option_find_int不同。首先看里面的option_find这个函数的作用就是查找list中,nodekey和参数的key相同的node,返回这个nodeval,如果不存在,返回0。通过这个函数我们得到了pad=1 stride=2等参数后的数值信息。接下来就很easy了,如果有这个参数就将这个字符串(得到的是一个字符串,不是一个数)转化为一个整数,没有的话就返回三个参数(有点类似于默认参数)。

回顾整个option_find_int_quiet,它的作用就是找出和keynode的数值大小。类似于一种map里面的查找操作。

而再看option_find_int

int option_find_int(list *l, char *key, int def){    char *v = option_find(l, key);    if(v) return atoi(v);    fprintf(stderr, "%s: Using default '%d'\n", key, def);    return def;}

它和前者的区别在于,它会打印报错信息。

0x0102 学习率策略

学习率策略的设置是一个枚举类型

typedef enum {    CONSTANT, STEP, EXP, POLY, STEPS, SIG, RANDOM} learning_rate_policy;

这是在前面就提到的。我们现在来看看,这几个有什么区别(参考caffe源码)

  • CONSTANT:学习率是一个固定的值learning_rate
  • STEP:是一种均匀分步策略learning_rate* gamma ^ (floor(iter / step))
  • EXP:learning_rate* gamma ^ iter
  • POLY:learning_rate(1 - iter/max_iter) ^ (power)
  • STEPS:同STEP只是这里的scale和step是一个数组
  • SIG:learning_rate ( 1/(1 + exp(-gamma * (iter - stepsize))))
  • RANDOM:代码中没有考虑

回头看整个parse_net_options这个函数,这个函数的主要功能就是读取[net]后的信息,赋值到net所指向的network结构中。

接着我们再回到parse_network_cfg

//parse_network_cfg     params.h = net->h;          //将h,w,c赋值size_params对象params,下面类似不再赘述    params.w = net->w;    params.c = net->c;    params.inputs = net->inputs;    params.batch = net->batch;    params.time_steps = net->time_steps;    params.net = net;    size_t workspace_size = 0;    n = n->next;                //[net]搞定了,接下来去下一个node    int count = 0;    free_section(s);

我们再来看看这个free_section函数做了什么

void free_section(section *s)//传入的变量是之前的那个section指针{    free(s->type);//释放type空间    node *n = s->options->front;//以下内容是释放s指向的kvp链表    while(n){        kvp *pair = (kvp *)n->val;        free(pair->key);        free(pair);        node *next = n->next;        free(n);        n = next;    }    free(s->options);    free(s);//最后释放s}

综上来看这个函数的目的在这里很明显了。我们把cfg的参数从section中copy到了network中,section内存不用了,自然要把它释放。

再回到parse_network_cfg函数

//parse_network_cfg     fprintf(stderr, "layer     filters    size              input                output\n");    while(n){        params.index = count;        fprintf(stderr, "%5d ", count);        s = (section *)n->val;        options = s->options;        layer l = {
0}; LAYER_TYPE lt = string_to_layer_type(s->type);

这是一个非常大的循环体,先看前面一小部分。我们先看看其中的LAYER_TYPE结构和layer结构

//现在这个结构也放在了darknet.h中struct layer;typedef struct layer layer;typedef enum {    CONVOLUTIONAL,    DECONVOLUTIONAL,    CONNECTED,    MAXPOOL,    SOFTMAX,    DETECTION,    DROPOUT,    CROP,    ROUTE,    COST,    NORMALIZATION,    AVGPOOL,    LOCAL,    SHORTCUT,    ACTIVE,    RNN,    GRU,    LSTM,    CRNN,    BATCHNORM,    NETWORK,    XNOR,    REGION,    REORG,    BLANK} LAYER_TYPE;struct layer{    LAYER_TYPE type;    ACTIVATION activation;    COST_TYPE cost_type;    void (*forward)   (struct layer, struct network);    void (*backward)  (struct layer, struct network);    void (*update)    (struct layer, update_args);    void (*forward_gpu)   (struct layer, struct network);    void (*backward_gpu)  (struct layer, struct network);    void (*update_gpu)    (struct layer, update_args);   ...

我们可以看到LAYER_TYPE就是每一层的类型。而layer就是设置这些层的参数,由于参数太多,我在后面会分开讲。

再来看string_to_layer_type函数

LAYER_TYPE string_to_layer_type(char * type)//传入的参数就是之前的section->type{    if (strcmp(type, "[shortcut]")==0) return SHORTCUT;    if (strcmp(type, "[crop]")==0) return CROP;    if (strcmp(type, "[cost]")==0) return COST;    if (strcmp(type, "[detection]")==0) return DETECTION;    if (strcmp(type, "[region]")==0) return REGION;    if (strcmp(type, "[local]")==0) return LOCAL;    if (strcmp(type, "[conv]")==0            || strcmp(type, "[convolutional]")==0) return CONVOLUTIONAL;    if (strcmp(type, "[deconv]")==0            || strcmp(type, "[deconvolutional]")==0) return DECONVOLUTIONAL;    if (strcmp(type, "[activation]")==0) return ACTIVE;    if (strcmp(type, "[net]")==0            || strcmp(type, "[network]")==0) return NETWORK;    if (strcmp(type, "[crnn]")==0) return CRNN;    if (strcmp(type, "[gru]")==0) return GRU;    if (strcmp(type, "[lstm]") == 0) return LSTM;    if (strcmp(type, "[rnn]")==0) return RNN;    if (strcmp(type, "[conn]")==0            || strcmp(type, "[connected]")==0) return CONNECTED;    if (strcmp(type, "[max]")==0            || strcmp(type, "[maxpool]")==0) return MAXPOOL;    if (strcmp(type, "[reorg]")==0) return REORG;    if (strcmp(type, "[avg]")==0            || strcmp(type, "[avgpool]")==0) return AVGPOOL;    if (strcmp(type, "[dropout]")==0) return DROPOUT;    if (strcmp(type, "[lrn]")==0            || strcmp(type, "[normalization]")==0) return NORMALIZATION;    if (strcmp(type, "[batchnorm]")==0) return BATCHNORM;    if (strcmp(type, "[soft]")==0            || strcmp(type, "[softmax]")==0) return SOFTMAX;    if (strcmp(type, "[route]")==0) return ROUTE;    return BLANK;}

这个函数的作用很明显,通过比较字符串,将原先的section中的type变成了LAYER_TYPE中的枚举元素。

接着回到parse_network_cfg,后面就是很多的条件判断

//parse_network_cfg         if(lt == CONVOLUTIONAL){            l = parse_convolutional(options, params);        }else if(lt == DECONVOLUTIONAL){            l = parse_deconvolutional(options, params);        }else if(lt == LOCAL){            l = parse_local(options, params);        }else if(lt == ACTIVE){            l = parse_activation(options, params);        }else if(lt == RNN){            l = parse_rnn(options, params);        }else if(lt == GRU){            l = parse_gru(options, params);        }else if (lt == LSTM) {            l = parse_lstm(options, params);        }else if(lt == CRNN){            l = parse_crnn(options, params);        }else if(lt == CONNECTED){            l = parse_connected(options, params);        }else if(lt == CROP){            l = parse_crop(options, params);        }else if(lt == COST){            l = parse_cost(options, params);        }else if(lt == REGION){            l = parse_region(options, params);        }else if(lt == DETECTION){            l = parse_detection(options, params);        }else if(lt == SOFTMAX){            l = parse_softmax(options, params);            net->hierarchy = l.softmax_tree;        }else if(lt == NORMALIZATION){            l = parse_normalization(options, params);        }else if(lt == BATCHNORM){            l = parse_batchnorm(options, params);        }else if(lt == MAXPOOL){            l = parse_maxpool(options, params);        }else if(lt == REORG){            l = parse_reorg(options, params);        }else if(lt == AVGPOOL){            l = parse_avgpool(options, params);        }else if(lt == ROUTE){            l = parse_route(options, params, net);        }else if(lt == SHORTCUT){            l = parse_shortcut(options, params, net);        }else if(lt == DROPOUT){            l = parse_dropout(options, params);            l.output = net->layers[count-1].output;            l.delta = net->layers[count-1].delta;#ifdef GPU            l.output_gpu = net->layers[count-1].output_gpu;            l.delta_gpu = net->layers[count-1].delta_gpu;#endif        }

然后再看以parse开头的函数作用,以其中一个为例parse_convolutional

0x0103 parse_convolutional

convolutional_layer parse_convolutional(list *options, size_params params){    int n = option_find_int(options, "filters",1);  //卷积核个数    int size = option_find_int(options, "size",1);  //卷积核大小    int stride = option_find_int(options, "stride",1);//步长    int pad = option_find_int_quiet(options, "pad",0);//图像周围是否补0    int padding = option_find_int_quiet(options, "padding",0);//补0的长度    int groups = option_find_int_quiet(options, "groups", 1);//卷积核组的个数    if(pad) padding = size/2;//对应SAME补0策略    char *activation_s = option_find_str(options, "activation", "logistic");//激活函数    ACTIVATION activation = get_activation(activation_s);    int batch,h,w,c;    h = params.h;       //图片的高    w = params.w;       //图片的宽    c = params.c;       //图片的通道数    batch=params.batch;    if(!(h && w && c)) error("Layer before convolutional layer must output image.");    int batch_normalize = option_find_int_quiet(options, "batch_normalize", 0);//BN操作    int binary = option_find_int_quiet(options, "binary", 0);//权重二值化    int xnor = option_find_int_quiet(options, "xnor", 0);//权重和输入二值化    convolutional_layer layer = make_convolutional_layer(batch,h,w,c,n,groups,size,stride,padding,activation, batch_normalize, binary, xnor, params.net->adam);    layer.flipped = option_find_int_quiet(options, "flipped", 0);    layer.dot = option_find_float_quiet(options, "dot", 0);    return layer;}

因为我之前已经讲过了option_find_int函数,所以这里不再多说了。代码的前面部分也非常容易理解,就射设置,不同[]后面的参数。

后面又出现一个新的结构ACTIVATION

typedef enum{    LOGISTIC, RELU, RELIE, LINEAR, RAMP, TANH, PLSE, LEAKY, ELU, LOGGY, STAIR, HARDTAN, LHTAN} ACTIVATION;

很明显这个枚举是用来定义不同的激活函数的。

再看get_activation这个函数

ACTIVATION get_activation(char *s)//传入的参数时cfg中的activation{    if (strcmp(s, "logistic")==0) return LOGISTIC;    if (strcmp(s, "loggy")==0) return LOGGY;    if (strcmp(s, "relu")==0) return RELU;    if (strcmp(s, "elu")==0) return ELU;    if (strcmp(s, "relie")==0) return RELIE;    if (strcmp(s, "plse")==0) return PLSE;    if (strcmp(s, "hardtan")==0) return HARDTAN;    if (strcmp(s, "lhtan")==0) return LHTAN;    if (strcmp(s, "linear")==0) return LINEAR;    if (strcmp(s, "ramp")==0) return RAMP;    if (strcmp(s, "leaky")==0) return LEAKY;    if (strcmp(s, "tanh")==0) return TANH;    if (strcmp(s, "stair")==0) return STAIR;    fprintf(stderr, "Couldn't find activation function %s, going with ReLU\n", s);    return RELU;}

这个函数的作用也很明显,通过比较字符串,将原先的激活函数的字符串,转化为现在的ACTIVATION枚举元素。

接着看convolutional_layer这个结构

typedef layer convolutional_layer;

其实就是一个layer,再看make_convolutional_layer函数(又是一个非常大的函数)

convolutional_layer make_convolutional_layer(int batch, int h, int w, int c, int n, int groups, int size, int stride, int padding, ACTIVATION activation, int batch_normalize, int binary, int xnor, int adam)//传入的参数就是我们之前设置好的{    int i;    convolutional_layer l = {
0}; l.type = CONVOLUTIONAL; l.groups = groups; //卷积核的组数 l.h = h; //图像的高 l.w = w; //图像的宽 l.c = c; //图像的通道数目 l.n = n; //卷积核个数 l.binary = binary; l.xnor = xnor; l.batch = batch; l.stride = stride; l.size = size; l.pad = padding; l.batch_normalize = batch_normalize; l.weights = calloc(c/groups*n*size*size, sizeof(float));//计算所有权重个数,c/groups*n*size*size,分配内存空间 l.weight_updates = calloc(c/groups*n*size*size, sizeof(float)); l.biases = calloc(n, sizeof(float));//卷积核个数和偏向的数目一致,分配内存空间 l.bias_updates = calloc(n, sizeof(float)); l.nweights = c/groups*n*size*size; l.nbiases = n; // float scale = 1./sqrt(size*size*c); float scale = sqrt(2./(size*size*c/l.groups));//缩放系数 //scale = .02; //for(i = 0; i < c*n*size*size; ++i) l.weights[i] = scale*rand_uniform(-1, 1); for(i = 0; i < l.nweights; ++i) l.weights[i] = scale*rand_normal();//初始化权重 int out_w = convolutional_out_width(l); int out_h = convolutional_out_height(l);

这里出现了新的函数,我们只分析其中一个convolutional_out_width

int convolutional_out_height(convolutional_layer l){    return (l.h + 2*l.pad - l.size) / l.stride + 1;}int convolutional_out_width(convolutional_layer l){    return (l.w + 2*l.pad - l.size) / l.stride + 1;}

函数中这个公式大家应该很熟悉,就是计算卷积后的输出图像的大小。

接着看make_convolutional_layer这个函数后面的部分

//make_convolutional_layer    l.out_h = out_h;    //输出图像的高    l.out_w = out_w;    //输出图像的宽    l.out_c = n;        //输出图像的通道数    l.outputs = l.out_h * l.out_w * l.out_c;//输出图像的总元素个数    l.inputs = l.w * l.h * l.c;             //输入图像的总元素个数    l.output = calloc(l.batch*l.outputs, sizeof(float));    l.delta  = calloc(l.batch*l.outputs, sizeof(float));

好的,这篇文章的篇幅有些长了,我们把剩余部分放到下一篇

觉得不错,点个赞吧b( ̄▽ ̄)d

由于本人水平有限,文中有不对之处,希望大家指出,谢谢^_^!

下一篇继续分析make_convolutional_layer这个函数后面的部分,敬请关注。

转载地址:https://coordinate.blog.csdn.net/article/details/78839504 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:YOLOv2源码分析(三)
下一篇:YOLOv2源码分析(一)

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年04月06日 13时17分25秒