nets[i] = load_network(cfgfile, weightfile, clear);
其函数主体如下所示
network *load_network(char *cfg, char *weights, int clear)
{
network *net = parse_network_cfg(cfg);//通过cfg文件构建网络,其函数主体见下
//若是存在权重文件且权重第一行不为0,则加载权重
if(weights && weights[0] != 0)
{
load_weights(net, weights);
}
if(clear) (*net->seen) = 0;//若是clear不为0,则将*net->seen置为0
return net;
}
parse_network_cfg函数主体
network *parse_network_cfg(char *filename)
{
list *sections = read_cfg(filename);//将cfg文件保存到list链表中,与之前的读取保存data_cfg文件类似,具体函数见[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106018636)
node *n = sections->front;//创建一个node指针指向node链表的首元结点
if(!n) error("Config file has no sections");
network *net = make_network(sections->size - 1);//为网络层分配空间,会去掉第一层net层,它保存的是训练参数,不是网络结构,具体过程查看博客[make_network函数主体](https://blog.youkuaiyun.com/m0_37799466/article/details/106025161)
net->gpu_index = gpu_index;//保存GPU代号,用来记录第几个GPU
size_params params;//声明一个size_params结构体;之后会用了保存训练参数
/*size_params结构体
typedef struct size_params{
int batch;
int inputs;
int h;
int w;
int c;
int index;
int time_steps;
network *net;
} size_params;
*/
section *s = (section *)n->val;//定义一个section指针并令他指向第一个section结构体
list *options = s->options;//定义一个list指针并令他指向list结构体
if(!is_network(s)) error("First section must be [net] or [network]");
parse_net_options(options, net);//初始化网络参数,具体参考[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106047533)
//通过network结构体中保存的参数对param结构体进行初始化
params.h = net->h;
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;//令node指针指向第一层网络
int count = 0;//用于之后的网络层计数
free_section(s);//释放s指针,即释放指向[net]这一层的指针
fprintf(stderr, "layer filters size input output\n");
while(n){
params.index = count;
fprintf(stderr, "%5d ", count);
s = (section *)n->val;//令指针s指向保存网络的section
options = s->options;//令指针options指向保存网络层参数的list结构体
layer l = {0};//定义网络层l并初始化为0
LAYER_TYPE lt = string_to_layer_type(s->type);//通过s->type得到网络层名称,用于之后的网络设置,通过判断是哪一种网络来选择进入哪一种函数
if(lt == CONVOLUTIONAL){
l = parse_convolutional(options, params);//构建卷积网络层架构,具体参考[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106049733)
}else if(lt == DECONVOLUTIONAL){
l = parse_deconvolutional(options, params);
}else if(lt == LOCAL){//定位层,具体参考[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106060660)
l = parse_local(options, params);
}else if(lt == ACTIVE){
l = parse_activation(options, params);
}else if(lt == LOGXENT){
l = parse_logistic(options, params);
}else if(lt == L2NORM){
l = parse_l2norm(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){//全连接层,具体参考[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106080466)
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 == YOLO){
l = parse_yolo(options, params);
}else if(lt == ISEG){
l = parse_iseg(options, params);
}else if(lt == DETECTION){//检测层,具体见[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106081309)
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){//构建最大池化层架构,具体参考[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106060370)
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 == UPSAMPLE){
l = parse_upsample(options, params, net);
}else if(lt == SHORTCUT){
l = parse_shortcut(options, params, net);
}else if(lt == DROPOUT){//防止过拟合层,具体参考[博客](https://blog.youkuaiyun.com/m0_37799466/article/details/106076702)
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
}else{
fprintf(stderr, "Type not recognized: %s\n", s->type);//若是不存在type,则输出没有此层
}
//设置每一层的各种参数
l.clip = net->clip;
l.truth = option_find_int_quiet(options, "truth", 0);
l.onlyforward = option_find_int_quiet(options, "onlyforward", 0);
l.stopbackward = option_find_int_quiet(options, "stopbackward", 0);
l.dontsave = option_find_int_quiet(options, "dontsave", 0);
l.dontload = option_find_int_quiet(options, "dontload", 0);
l.numload = option_find_int_quiet(options, "numload", 0);
l.dontloadscales = option_find_int_quiet(options, "dontloadscales", 0);
l.learning_rate_scale = option_find_float_quiet(options, "learning_rate", 1);
l.smooth = option_find_float_quiet(options, "smooth", 0);
option_unused(options);//判断used内是否为0,若是为0则此层不能使用,used是在加载网络是修改的
/*
void option_unused(list *l)
{
node *n = l->front;
while(n){
kvp *p = (kvp *)n->val;
if(!p->used){
fprintf(stderr, "Unused field: '%s = %s'\n", p->key, p->val);
}
n = n->next;
}
}
*/
net->layers[count] = l;//构建好的该层网络结构赋给第count层网络
if (l.workspace_size > workspace_size) workspace_size = l.workspace_size;
free_section(s);//释放s指针
n = n->next;//令指针n指向下一个node结点,即指向下一层网络
++count;//计数加一
if(n){//设置下一层网络的输入,即这一层输出
params.h = l.out_h;
params.w = l.out_w;
params.c = l.out_c;
params.inputs = l.outputs;
}
}
free_list(sections);//释放指针
layer out = get_network_output_layer(net);//设置网络层的输出,即检测层
/*
layer get_network_output_layer(network *net)
{
int i;
for(i = net->n - 1; i >= 0; --i){
if(net->layers[i].type != COST) break;
}
return net->layers[i];
}
*/
net->outputs = out.outputs;//设置好的网络输出接口
net->truths = out.outputs;//令标签与网络输出接口一样
//如果最后一层(即检测层)存在标签,则将net->truths设置为与最后一层标签数相同
if(net->layers[net->n-1].truths) net->truths = net->layers[net->n-1].truths;
net->output = out.output;//设置网络中net->output指针指向min_batch输出内存的入口地址
net->input = calloc(net->inputs*net->batch, sizeof(float));//为min_batch输入分配内存,min_batchx448x448x3
net->truth = calloc(net->truths*net->batch, sizeof(float));//为min_batch标签分配内存,与输出相同,s*s*(num*(4+1)+classes)
#ifdef GPU//定义了GPU,则运行下面代码
net->output_gpu = out.output_gpu;
net->input_gpu = cuda_make_array(net->input, net->inputs*net->batch);
net->truth_gpu = cuda_make_array(net->truth, net->truths*net->batch);
#endif
if(workspace_size){
//printf("%ld\n", workspace_size);
#ifdef GPU
if(gpu_index >= 0){
net->workspace = cuda_make_array(0, (workspace_size-1)/sizeof(float)+1);
}else {
net->workspace = calloc(1, workspace_size);
}
#else
net->workspace = calloc(1, workspace_size);
#endif
}
return net;
}
LAYER_TYPE枚举类型
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,
YOLO,
ISEG,
REORG,
UPSAMPLE,
LOGXENT,
L2NORM,
BLANK
} LAYER_TYPE;
load_weights(net, weights);函数主题
void load_weights(network *net, char *filename)
{
load_weights_upto(net, filename, 0, net->n);
}
load_weights_upto(net, filename, 0, net->n);函数主题
void load_weights_upto(network *net, char *filename, int start, int cutoff)
{
#ifdef GPU
if(net->gpu_index >= 0){
cuda_set_device(net->gpu_index);
}
#endif
fprintf(stderr, "Loading weights from %s...", filename);
fflush(stdout);
/*
其功能是使用给定的模式mode打开filename所指向的文件。文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在error中。该函数位于C标准库<stdio.h>中。
*/
FILE *fp = fopen(filename, "rb");
if(!fp) file_error(filename);
int major;
int minor;
int revision;
/*
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );//C99前
size_t fread( void *restrict buffer, size_t size, size_t count, FILE *restrict stream );//C99起
从给定输入流stream读取最多count个对象到数组buffer中(相当于以对每个对象调用size次fgetc),把buffer当 作unsigned char数组并顺序保存结果。流的文件位置指示器前进读取的字节数。若出现错误,则流的文件位置指示器的位 置不确定。若没有完整地读入最后一个元素,则其值不确定。*/
//fread函数的fp是流的入口地址不会变,但是流的文件位置指示器会前进读取的字节数,即相当于读过的不会再读
//如下面代码示例,fp读完buffer里面4个元素后,流的文件位置指示器就会记录到这里,在读入buffer1时就会从这里开始读,但fp地址不会变
/*
#include <stdio.h>
#include <string.h>
#include<stdlib.h>
int main()
{
FILE *fp;
char c[] = "This is runoob";
char buffer[20];
char buffer1[20];
//打开文件用于读写
fp = fopen("file.txt", "w+");
//写入数据到文件
fwrite(c, strlen(c) + 1, 1, fp);
//查找文件的开头
fseek(fp, 0, SEEK_SET);
//读取并显示数据
fread(buffer, 4, 1, fp);
printf("%p\n", fp);
printf("%s\n", buffer);
fread(buffer1, strlen(c) + 1, 1, fp);
printf("%p\n", fp);
printf("%s\n", buffer1);
fclose(fp);
system("pause");
return(0);
}
*/
//major = 0 minor = 1 revision = 0
fread(&major, sizeof(int), 1, fp);
fread(&minor, sizeof(int), 1, fp);
fread(&revision, sizeof(int), 1, fp);
if ((major*10 + minor) >= 2 && major < 1000 && minor < 1000){//含义不明
fread(net->seen, sizeof(size_t), 1, fp);
} else {
int iseen = 0;
fread(&iseen, sizeof(int), 1, fp);
*net->seen = iseen;
}
int transpose = (major > 1000) || (minor > 1000);
int i;
//从start层开始到net->n与cutoff层逐层加载权重,默认start=0,即从第一层开始
for(i = start; i < net->n && i < cutoff; ++i){
layer l = net->layers[i];
if (l.dontload) continue;//排除不想加载的网络层
if(l.type == CONVOLUTIONAL || l.type == DECONVOLUTIONAL){
load_convolutional_weights(l, fp);//加载卷积层或反卷积层权重
}
if(l.type == CONNECTED){
load_connected_weights(l, fp, transpose);//加载全连接层权重
}
if(l.type == BATCHNORM){//加载批标准化权重
load_batchnorm_weights(l, fp);
}
if(l.type == CRNN){
load_convolutional_weights(*(l.input_layer), fp);
load_convolutional_weights(*(l.self_layer), fp);
load_convolutional_weights(*(l.output_layer), fp);
}
if(l.type == RNN){
load_connected_weights(*(l.input_layer), fp, transpose);
load_connected_weights(*(l.self_layer), fp, transpose);
load_connected_weights(*(l.output_layer), fp, transpose);
}
if (l.type == LSTM) {
load_connected_weights(*(l.wi), fp, transpose);
load_connected_weights(*(l.wf), fp, transpose);
load_connected_weights(*(l.wo), fp, transpose);
load_connected_weights(*(l.wg), fp, transpose);
load_connected_weights(*(l.ui), fp, transpose);
load_connected_weights(*(l.uf), fp, transpose);
load_connected_weights(*(l.uo), fp, transpose);
load_connected_weights(*(l.ug), fp, transpose);
}
if (l.type == GRU) {
if(1){
load_connected_weights(*(l.wz), fp, transpose);
load_connected_weights(*(l.wr), fp, transpose);
load_connected_weights(*(l.wh), fp, transpose);
load_connected_weights(*(l.uz), fp, transpose);
load_connected_weights(*(l.ur), fp, transpose);
load_connected_weights(*(l.uh), fp, transpose);
}else{
load_connected_weights(*(l.reset_layer), fp, transpose);
load_connected_weights(*(l.update_layer), fp, transpose);
load_connected_weights(*(l.state_layer), fp, transpose);
}
}
if(l.type == LOCAL){//加载定位层权重
int locations = l.out_w*l.out_h;//定位特征图尺寸大小
int size = l.size*l.size*l.c*l.n*locations;//权重数量
fread(l.biases, sizeof(float), l.outputs, fp);//从fp流中读取l.outputs个float类型大小的参数到l.biases中去,即对偏置赋值,会覆盖之前初始化的0
fread(l.weights, sizeof(float), size, fp);从fp流中读取size个float类型大小的参数到l.weights中去,即对权重赋值,会覆盖之前的初始化值
#ifdef GPU//若是定义了GPU则运行下面代码,GPU运行的高效代码
if(gpu_index >= 0){
push_local_layer(l);
}
#endif
}
}
fprintf(stderr, "Done!\n");
fclose(fp);
}
load_convolutional_weights(l, fp);//加载卷积层权重
void load_convolutional_weights(layer l, FILE *fp)
{
if(l.binary){//这里基本都是0,所以不会执行,其中的函数也被注释了
//load_convolutional_weights_binary(l, fp);
//return;
}
if(l.numload) l.n = l.numload;//每层输出通道的数量,即输出特征图个数,这里基本都是0,不执行,即都加载
int num = l.c/l.groups*l.n*l.size*l.size;//每层卷积层的权重参数数量
fread(l.biases, sizeof(float), l.n, fp);//从fp流中读取l.n个float类型大小的参数到l.biases中去,即对偏置赋值,会覆盖之前初始化的0
if (l.batch_normalize && (!l.dontloadscales)){//若是使用批标准化且加载scales时运行
fread(l.scales, sizeof(float), l.n, fp);//从fp流中读取l.n个float类型大小的参数到l.scales中去
fread(l.rolling_mean, sizeof(float), l.n, fp);//从fp流中读取l.n个float类型大小的参数到l.rolling_mean中去
fread(l.rolling_variance, sizeof(float), l.n, fp);//从fp流中读取l.n个float类型大小的参数到l.rolling_variance中去
if(0){
int i;
for(i = 0; i < l.n; ++i){
printf("%g, ", l.rolling_mean[i]);
}
printf("\n");
for(i = 0; i < l.n; ++i){
printf("%g, ", l.rolling_variance[i]);
}
printf("\n");
}
if(0){
fill_cpu(l.n, 0, l.rolling_mean, 1);
fill_cpu(l.n, 0, l.rolling_variance, 1);
}
if(0){
int i;
for(i = 0; i < l.n; ++i){
printf("%g, ", l.rolling_mean[i]);
}
printf("\n");
for(i = 0; i < l.n; ++i){
printf("%g, ", l.rolling_variance[i]);
}
printf("\n");
}
}
fread(l.weights, sizeof(float), num, fp);//从fp流中读取num个float类型大小的参数到l.weights中去,即对权重赋值,会覆盖之前的初始化值
//if(l.c == 3) scal_cpu(num, 1./256, l.weights, 1);
if (l.flipped) {//权重转置
transpose_matrix(l.weights, l.c*l.size*l.size, l.n);
}
//if (l.binary) binarize_weights(l.weights, l.n, l.c*l.size*l.size, l.weights);
#ifdef GPU//定义GPU则运行,但是好像没有定义下面函数,只是声明了,所以还是使用了CPU进行权重加载工作
if(gpu_index >= 0){
push_convolutional_layer(l);
}
#endif
}
load_connected_weights(l, fp, transpose);//加载全连接层权重
void load_connected_weights(layer l, FILE *fp, int transpose)
{
fread(l.biases, sizeof(float), l.outputs, fp);//从fp流中读取l.outputs个float类型大小的参数到l.biases中去,即对偏置赋值,会覆盖之前的初始化值
fread(l.weights, sizeof(float), l.outputs*l.inputs, fp);//从fp流中读取l.outputs*l.inputs个float类型大小的参数到l.weights中去,即对权重赋值,会覆盖之前的初始化值
if(transpose){//转置
transpose_matrix(l.weights, l.inputs, l.outputs);
}
//printf("Biases: %f mean %f variance\n", mean_array(l.biases, l.outputs), variance_array(l.biases, l.outputs));
//printf("Weights: %f mean %f variance\n", mean_array(l.weights, l.outputs*l.inputs), variance_array(l.weights, l.outputs*l.inputs));
if (l.batch_normalize && (!l.dontloadscales)){//判断是否为批标准化且加载scales
fread(l.scales, sizeof(float), l.outputs, fp);
fread(l.rolling_mean, sizeof(float), l.outputs, fp);
fread(l.rolling_variance, sizeof(float), l.outputs, fp);
//printf("Scales: %f mean %f variance\n", mean_array(l.scales, l.outputs), variance_array(l.scales, l.outputs));
//printf("rolling_mean: %f mean %f variance\n", mean_array(l.rolling_mean, l.outputs), variance_array(l.rolling_mean, l.outputs));
//printf("rolling_variance: %f mean %f variance\n", mean_array(l.rolling_variance, l.outputs), variance_array(l.rolling_variance, l.outputs));
}
#ifdef GPU//定义了GPU则运行下面代码
if(gpu_index >= 0){
push_connected_layer(l);
}
#endif
}