以前在简书的文章,搬迁过来 ^-^
本文是做者原创,若有理解错误,恳请你们指出,如需引用,请注明出处。网络
#Caffe FeatureMap数据流的创建 ##用语解释框架
在讲解FeatureMap的数据流以前,首先须要明确一下caffe的大致结构,caffe的总体逻辑结构分为3层,分别是Net,Layer和Blob,分别的做用以下:函数
由上面的讲解分层关系不难看出,FeatureMap在整个Caffe框架中,不属于任何一个Layer,因此它被最顶层的Net层所持有。Net层就须要可以经过caffe的模型文件推倒出每一层所依赖的输入,这样才能构建出一个完整的数据链。在这种需求下Caffe引入了两个定义:spa
因此Net在调用Layer以前就必定知道了Layer的所须要的输入数据,也就是须要Net层所持有的Blob变量须要被那些层所引用。这些在模型文件中也有直观的反应(为了方便截图,删除了下图proto中关于Convlution的参数配置):指针
上述的工做都在Net的Init(void Net::Init(const NetParameter& in_param)
)函数里面进行了处理,主要实现的就是根据上图左侧的模型文件获得须要创建的Layer的类型,并将各个Layer间的数据连接起来。函数中的关键参数以下:code
名称 | 功能 |
---|---|
in_param | 存放由protobuf转换出的模型文件 |
bottom_vecs_ | 存放每一层中的输入数据类型为:vector<vector<Blob*> > |
top_vecs_ | 存放每一层中的输出数据类型为:vector<vector<Blob*> > |
available_blobs | 存放每一层中的输出数据类型为:vector<vector<Blob*> > |
##常规的数据链创建流程是(单输入单输出的场景):orm
连接本层的bottom数据( int Net::AppendBottom(const NetParameter& param, const int layer_id, const int bottom_id, set<string>* available_blobs, map<string, int>* blob_name_to_idx)
),该函数会使用从当前layer持有的bottom信息中获得对应bottom的层名,而后利用该名称找到对应的blob,并加入到bottom_vecs_。cdn
连接本层的top数据(void Net::AppendTop(const NetParameter& param, const int layer_id,const int top_id, set<string>* available_blobs, map<string, int>* blob_name_to_idx)
),该操做就是将本层的输出数据加入到top_vecs_中,并与 layer_id相关联,这里同时负责Blob对象的申请。 须要指出的是,新的Blob对象是在top中进行建立的,在Bottom中只是将上一层top的指针添加进来,同时在这个过程当中CAFFE还利用available_blobs进行了异常校验,在每次新加入top的时候记录对应的Blob名称,在bottom中连接上一层top以后,在available_blobs中将对应的Blob名称剔除。相关伪代码以下:对象
for (int layer_id = 0; layer_id < param.layer_size(); ++layer_id) {
AppendBottom();
AppendTop();
}
复制代码
##多输入的数据链的创建: 细心的同窗应该已经发现,当数据为多bottom输入的时候,由于available_blobs的数据被上一次的连接过程删掉,则再次连接相同bottom的时候,会出先异常告警,在这种状况下咱们就要引入CAFFE的另一处理函数 void InsertSplits(const NetParameter& param, NetParameter* param_split)
,该函数的主要功能就是对 top输出到多个 Layer的状况进行分割。 整个函数分为两个部分:blog
遍历整个网络,记录每个Layer的top的使用状况,记录结构放在 top_idx_to_bottom_count
中。
遍历整个网络,对 top_idx_to_bottom_count > 1
的状况进行处理: a. 首先是对top被多个层使用的Layer进行分割,主要的作法是在该层的后面新建一个Layer ,这个新的Layer的会按照 top_idx_to_bottom_count
的个数和约定的分割名称(SplitBlobName
)去新建top,添加层的代码以下(此处只展现核心的建立过程,具体调用流程请自行跟踪):
//该函数执行新层的添加
void ConfigureSplitLayer(const string& layer_name, const string& blob_name,
const int blob_idx, const int split_count, const float loss_weight,
LayerParameter* split_layer_param) {
split_layer_param->Clear();
split_layer_param->add_bottom(blob_name);
split_layer_param->set_name(SplitLayerName(layer_name, blob_name, blob_idx));
split_layer_param->set_type("Split");
for (int k = 0; k < split_count; ++k) {//split_count就是该top被引用的个数
//添加了分割后的top
//命名由SplitBlobName生成
split_layer_param->add_top(
SplitBlobName(layer_name, blob_name, blob_idx, k));
if (loss_weight) {
if (k == 0) {
split_layer_param->add_loss_weight(loss_weight);
} else {
split_layer_param->add_loss_weight(0);
}
}
}
}
复制代码
b. 以后,是对使用同一个top的后续层的bottom的blob进行更名,使用与上一步相同的命名规则进行更名。
下面以SqueezeNet1.1为例,展现了添加新的分割层的实例:
![Upload new_split_layer.jpg failed. Please try again.]
经过这样一个分割的转化,达到了对多输入数据流的创建。
##遗留问题 上面讲的是在初始化阶段对FeatureMap数据的连接关系的创建,可是对于weights的填充和初始图片的输入并无进行分析。