网络模型结构和权重参数的加载
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 file
int 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;
}