本文共 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
中,node
的key
和参数的key
相同的node
,返回这个node
的val
,如果不存在,返回0。通过这个函数我们得到了pad=1 stride=2
等参数后的数值信息。接下来就很easy了,如果有这个参数就将这个字符串(得到的是一个字符串,不是一个数)转化为一个整数,没有的话就返回三个参数(有点类似于默认参数)。
回顾整个option_find_int_quiet
,它的作用就是找出和key
的node
的数值大小。类似于一种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_rateSTEP
:是一种均匀分步策略learning_rate* gamma ^ (floor(iter / step))EXP
:learning_rate* gamma ^ iterPOLY
: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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!