网络模型结构和权重参数的加载
ncnn推理框架中把模型的结构和权重参数分为两个文件进行存储,实现了结构和权重的分离。在xxx.param中存储了模型的结构信息,在xxx.bin中存储了模型的权重信息。xxx.param的文件结构如下:
layer:描述网络一共有多少层,例如:ReLU、Conv 都叫一个layer;
blob:表示数据节点,例如一个Conv就有一个输入blob和输出blob;
bottom_count:当前层接收输入blob的层个数
top_count:将当前层的输出作为输入blob的层个数
bottom_name:当前层输入数据的生产层名字
blob_name: 消费当前的输出结果的层名字
模型网络结构的加载
在ncnn中,通过重载实现不同格式的加载。最常见也是最容易看懂的加载方式:
通过输入模型结构的文件,来完成模型的加载,具体的重载如下图:
两个具体关系如下:
int Net::load_param(const char* protopath) {FILE* fp = fopen(protopath, "rb");if (!fp) {std::cout << "error" << std::endl;return -1;}int ret = load_param(fp);fclose(fp);
}
在int load_param(FILE* fp)
中完成对网络结构的构建,具体的步骤,如下:
- 读取层数、blob数
- 逐行读取,层类型和层名字,以及输入blob数据的个数,和将当前层输出作为输入blob的个数
- 根据层类型得到类型的索引,然后通过该索引得到层实例化的指针;这个地方需要区分内部层和自定义的层;
- 内部层
- 自定义层
- 为当前层设置类型和名字
- 开始读取bottoms和tops,并构建blob的集合
- 利用创建的额层实例指针,构建层集合
int Net::load_param(FILE* fp)
{//一、读取层数和blob数int layer_count = 0;int blob_count = 0;fscanf(fp, "%d %d", &layer_count, &blob_count);layers.resize(layer_count);blobs.resize(blob_count);int layer_index = 0;int blob_index = 0;while (!feof(fp)){//二、逐行读取,层类型和层名字,以及输入blob数据的个数,和将当前层输出作为输入blob的个数int nscan = 0;char layer_type[256];char layer_name[256];int bottom_count = 0;int top_count = 0;nscan = fscanf(fp, "%256s %256s %d %d", layer_type, layer_name, &bottom_count, &top_count);if (nscan != 4){continue;}//三、根据层类型得到类型的索引,然后通过该索引得到层实例化的指针;int typeindex = layer_to_index(layer_type);Layer* layer = create_layer(typeindex);if (!layer){typeindex = custom_layer_to_index(layer_type);layer = create_custom_layer(typeindex);}//四、为当前层设置类型和名字layer->type = std::string(layer_type);layer->name = std::string(layer_name);//五、开始读取bottoms和tops,并构建blob的集合layer->bottoms.resize(bottom_count);for (int i=0; i<bottom_count; i++){char bottom_name[256];nscan = fscanf(fp, "%256s", bottom_name);if (nscan != 1){continue;}//根据输入blob的名字,在blob集合中的索引,如果没有找到,就需要构建这个blob//并将构建的blob添加到blob中int bottom_blob_index = find_blob_index_by_name(bottom_name);if (bottom_blob_index == -1){Blob& blob = blobs[blob_index];bottom_blob_index = blob_index;blob.name = std::string(bottom_name);blob_index++;}//在blob集合中找到,将根据索引获取当前的blob实例//然后将当前层作为blob的消费者//在当前层的bottom中添加输入blobd的索引Blob& blob = blobs[bottom_blob_index];blob.consumers.push_back(layer_index);layer->bottoms[i] = bottom_blob_index;}//layer->tops.resize(top_count);for (int i=0; i<top_count; i++){Blob& blob = blobs[blob_index];char blob_name[256];nscan = fscanf(fp, "%256s", blob_name);if (nscan != 1){continue;}blob.name = std::string(blob_name);blob.producer = layer_index;layer->tops[i] = blob_index;blob_index++;}//如果当前层有特殊的参数,则读取特殊的参数int lr = layer->load_param(fp);if (lr != 0){fprintf(stderr, "layer load_param failed\n");continue;}//六、利用创建的额层实例指针,构建层集合layers[layer_index] = layer;layer_index++;}return 0;
}
权重参数的加载
加载模型参数同样通过了一个重载:
具体的说下int load_model(FILE* fp)
int Net::load_model(FILE* fp)
{// load fileint ret = 0;//遍历所有的层,然后每个层根据自己实现的load_model方法,读取对应的权重参数for (size_t i=0; i<layers.size(); i++){Layer* layer = layers[i];int lret = layer->load_model(fp);if (lret != 0){fprintf(stderr, "layer load_model %d failed\n", (int)i);ret = -1;break;}}return ret;
}