Caffe源码-InsertSplits()函数
InsertSplits()函数
在Net初始化的过程中,存在一个特殊的修改网络结构的操作,那就是当某层的输出blob对应多个其他层的输入blob时,会在输出blob所在层的后面插入一个新的Split类型的层。大致方式如下图所示,左侧为原始网络的结构,右侧为修改之后的网络结构。个人理解这样做的目的应该是为了在梯度反传时,方便多个分支的梯度能够累加到同一个blob上。左侧图,分别计算出layer1和layer2的blob0的梯度后,在计算layer0的blob0的梯度时,Net类中需要额外增加一些操作来将各个分支的梯度累加起来。而右侧图,则是将梯度累加操作看成一个Layer来实现,看起来更合理些。
A[name: layer0<br>top: blob0]-->B[name: layer1<br>bottom: blob0<br>top: ...]
A-->C[name: layer2<br>bottom: blob0<br>top: ...]
U[name: layer0<br>top: blob0]-->V[name: blob0_layer0_0_split<br>type: Split<br>bottom: blob0<br>top: blob0_layer0_0_split_0<br>top: blob0_layer0_0_split_1]
V-->W[name: layer1<br>bottom: blob0_layer0_0_split_0<br>top: ...]
V-->X[name: layer2<br>bottom: blob0_layer0_0_split_1<br>top: ...]
insert_splits.cpp源码
//根据网络参数param创建新的网络参数param_split. param_split主要是将param中一些被多次使用的blob
//后面增加一层,将blob分解成多个不同名称的分支,用于后续的输入
void InsertSplits(const NetParameter& param, NetParameter* param_split) {
// Initialize by copying from the input NetParameter.
param_split->CopyFrom(param); //拷贝网络参数
param_split->clear_layer(); //同样先清空所有layer参数
//<输出blob的名称, <第m层网络, 第n个输出blob>>,存放当前已记录的所有输出blob的名称最近一次出现的位置
map<string, pair<int, int> > blob_name_to_last_top_idx;
//<<第i层网络, 第j个输入blob>, <第m层网络, 第n个输出blob>> //指示输入blob数据的来源
map<pair<int, int>, pair<int, int> > bottom_idx_to_source_top_idx;
map<pair<int, int>, int> top_idx_to_bottom_count; //<第m层网络, 第n个输出blob>,表示该blob被用作输入blob的次数
map<pair<int, int>, float> top_idx_to_loss_weight; //<第m层网络, 第n个输出blob>,该输出blob对应的loss weight
map<pair<int, int>, int> top_idx_to_bottom_split_idx; //<<第m层网络, 第n个输出blob>, 输出blob第k次用作输入>
map<int, string> layer_idx_to_layer_name; //<第i层网络, 第i层网络的名称>
for (int i = 0; i < param.layer_size(); ++i) {
const LayerParameter& layer_param = param.layer(i); //net中第i层的layer参数
layer_idx_to_layer_name[i] = layer_param.name(); //保存其名称
for (int j = 0; j < layer_param.bottom_size(); ++j) { //该层的所有输入blob
const string& blob_name = layer_param.bottom(j); //第i层layer的第j个输入blob的名称
if (blob_name_to_last_top_idx.find(blob_name) ==
blob_name_to_last_top_idx.end()) {
//输入blob不在blob_name_to_last_top_idx中,说明与之同名的输出blob也不在其中,未能在当前的记录中找到输出blob的网络位置,
//那个该输入blob的数据的来源未知,返回错误
LOG(FATAL) << "Unknown bottom blob '" << blob_name << "' (layer '"
<< layer_param.name() << "', bottom index " << j << ")";
}
const pair<int, int>& bottom_idx = make_pair(i, j); //第i层的第j个输入blob
const pair<int, int>& top_idx = blob_name_to_last_top_idx[blob_name]; //找到该blob在网络中用作输出的最近一次出现的位置
bottom_idx_to_source_top_idx[bottom_idx] = top_idx; //用于输出的最近的位置,即为该输入blob数据的来源,保存
++top_idx_to_bottom_count[top_idx]; //对应的输出blob的被使用计数器加一
}
for (int j = 0; j < layer_param.top_size(); ++j) { //该层的所有输出blob
const string& blob_name = layer_param.top(j); //第i层的第j个输出blob的名称
//输出blob的名称重复出现时只会记录最后一次出现的位置
blob_name_to_last_top_idx[blob_name] = make_pair(i, j); //关联输出blob的名称与位置
}
// A use of a top blob as a loss should be handled similarly to the use of
// a top blob as a bottom blob to another layer.
const int last_loss = std::min(layer_param.loss_weight_size(), layer_param.top_size()); //取较小的
for (int j = 0; j < last_loss; ++j) {
const string& blob_name = layer_param.top(j); //第i层的第j个输出blob的名称
const pair<int, int>& top_idx = blob_name_to_last_top_idx[blob_name]; //输出blob的位置
top_idx_to_loss_weight[top_idx] = layer_param.loss_weight(j); //保存输出blob对应的权重
if (top_idx_to_loss_weight[top_idx]) { //loss权重不为0,说明loss有效,也将这种类型的输出blob看成某层的输入,计数加一
++top_idx_to_bottom_count[top_idx];
}
}
}
for (int i = 0; i < param.layer_size(); ++i) { //便利所有layer
LayerParameter* layer_param = param_split->add_layer(); //在param_split中添加新的层,返回其指针
layer_param->CopyFrom(param.layer(i)); //将当前层的参数拷贝到param_split的新增的层中
//先处理layer 的输入数据,如果输入数据对应的来源输出blob存在被多次使用的情况,则会修改输入blob的名称
//以下注释假设第m层的第n个输出来源于第i层的第j个输入
// Replace any shared bottom blobs with split layer outputs.
for (int j = 0; j < layer_param->bottom_size(); ++j) { //该层的输入blob,第j个
const pair<int, int>& top_idx = bottom_idx_to_source_top_idx[make_pair(i, j)]; //输入blob的来源的位置,第m层的第n个输出
const int split_count = top_idx_to_bottom_count[top_idx]; //第m层的第n个输出blob被用作输入blob的次数
if (split_count > 1) { //次数大于1,被多次使用
const string& layer_name = layer_idx_to_layer_name[top_idx.first]; //第m层layer的名称
const string& blob_name = layer_param->bottom(j); //第i层的第j个输入blob的名称,同样也是第m层的第n个输出blob的名称
//将param_split的新增的层的第j个输出blob的名称修改为: blob_name + layer_name + n + 拆分索引
layer_param->set_bottom(j, SplitBlobName(layer_name,
blob_name, top_idx.second, top_idx_to_bottom_split_idx[top_idx]++)); //第k次用作输入,用后加一,保证后续再用于输入时创建的名称不同
}
}
//处理layer的输出数据,如果输出数据存在多次使用的情况,则会在该层后面添加一个新的层.新的层的输入对应该层的输出,新层的输出blob的个数
//对应该层输出blob被使用的次数,新层的输出blob的名称对应上面的layer_param->set_bottom()中SplitBlobName()得到的名称,新层类型为"Split"
// Create split layer for any top blobs used by other layer as bottom blobs more than once.
for (int j = 0; j < layer_param->top_size(); ++j) { //该层的第j个输出blob
const pair<int, int>& top_idx = make_pair(i, j);
const int split_count = top_idx_to_bottom_count[top_idx]; //找到第i层的第j个输出blob的被使用次数
if (split_count > 1) {
const string& layer_name = layer_idx_to_layer_name[i]; //第i层layer的名称
const string& blob_name = layer_param->top(j); //第j个输出blob的名称
LayerParameter* split_layer_param = param_split->add_layer(); //在param_split中增加一个新的层
const float loss_weight = top_idx_to_loss_weight[top_idx]; //第i层的第j个输出blob对饮的权重
ConfigureSplitLayer(layer_name, blob_name, j, split_count,
loss_weight, split_layer_param); //多次使用时,在该输出blob后面添加一个新的layer
if (loss_weight) {
layer_param->clear_loss_weight(); //权重转移到新增的层中,param_split的当前层的权重置为0
top_idx_to_bottom_split_idx[top_idx]++; //loss layer中,将当前层的输出blob看成是某层的输入,则当前层的输出blob的计数加一
}
}
}
}
}
//设置param_split中新增层的参数split_layer_param,新层的输出blob的个数为split_count
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(); //先清空所有layer参数
split_layer_param->add_bottom(blob_name); //添加一个输入blob,名称为blob_name
split_layer_param->set_name(SplitLayerName(layer_name, blob_name, blob_idx)); //生成一个layer的名称,设置到新层中
split_layer_param->set_type("Split"); //设置新层的类型为"Split"
for (int k = 0; k < split_count; ++k) {
//添加一个新的输出blob,规则与InsertSplits()中的layer_param->set_bottom()中的一致
split_layer_param->add_top(SplitBlobName(layer_name, blob_name, blob_idx, k));
if (loss_weight) { //权重不为0,只设置第一条分支的权重,其余分支的权重置为0.
if (k == 0) { //(防止每条分支都计算权重,分割后的网络与原网络计算结果不一致)
split_layer_param->add_loss_weight(loss_weight);
} else {
split_layer_param->add_loss_weight(0);
}
}
}
}
string SplitLayerName(const string& layer_name, const string& blob_name,
const int blob_idx) { //生成新的layer的名称: 输入blob的名称 + blob所在layer的名称 + blob的位置
ostringstream split_layer_name;
split_layer_name << blob_name << "_" << layer_name << "_" << blob_idx
<< "_split";
return split_layer_name.str();
}
string SplitBlobName(const string& layer_name, const string& blob_name,
const int blob_idx, const int split_idx) { //生成新的blob名称: 原blob名称 + layer名称 + blob索引 + 拆分索引
ostringstream split_blob_name;
split_blob_name << blob_name << "_" << layer_name << "_" << blob_idx
<< "_split_" << split_idx;
return split_blob_name.str();
}
小结
- 该部分代码重点是理解InsertSplits()函数初始定义的几个map类型的变量的含义
参考
https://blog.csdn.net/limengjuhanxin/article/details/87939996
Caffe的源码笔者是第一次阅读,一边阅读一边记录,对代码的理解和分析可能会存在错误或遗漏,希望各位读者批评指正,谢谢支持!
Caffe源码-InsertSplits()函数的更多相关文章
- Caffe源码理解2:SyncedMemory CPU和GPU间的数据同步
目录 写在前面 成员变量的含义及作用 构造与析构 内存同步管理 参考 博客:blog.shinelee.me | 博客园 | CSDN 写在前面 在Caffe源码理解1中介绍了Blob类,其中的数据成 ...
- caffe源码阅读
参考网址:https://www.cnblogs.com/louyihang-loves-baiyan/p/5149628.html 1.caffe代码层次熟悉blob,layer,net,solve ...
- Caffe源码中syncedmem文件分析
Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下syncedmem文件. 1. include文件: (1).& ...
- Caffe源码中math_functions文件分析
Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件,这里介绍下math_functions文件. 1. include文件: ...
- Caffe源码阅读(1) 全连接层
Caffe源码阅读(1) 全连接层 发表于 2014-09-15 | 今天看全连接层的实现.主要看的是https://github.com/BVLC/caffe/blob/master/src ...
- vscode下调试caffe源码
caffe目录: ├── build -> .build_release // make生成目录,生成各种可执行bin文件,直接调用入口: ├── cmake ├── CMakeLists.tx ...
- Caffe源码中common文件分析
Caffe源码(caffe version:09868ac , date: 2015.08.15)中的一些重要头文件如caffe.hpp.blob.hpp等或者外部调用Caffe库使用时,一般都会in ...
- caffe源码整个训练过程
Caffe源码 Blob protected: shared_ptr<SyncedMemory> data_; shared_ptr<SyncedMemory> diff_; ...
- caffe源码学习
本文转载自:https://buptldy.github.io/2016/10/09/2016-10-09-Caffe_Code/ Caffe简介 Caffe作为一个优秀的深度学习框架网上已经有很多内 ...
随机推荐
- 使用 Angular 打造微前端架构的 ToB 企业级应用
这篇文章其实已经准备了11个月了,因为虽然我们年初就开始使用 Angular 的微前端架构,但是产品一直没有正式发布,无法通过生产环境实践验证可行性,11月16日我们的产品正式灰度发布,所以是时候分享 ...
- 报错:尝试加载 Oracle 客户端库时引发 BadImageFormatException。如果在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现此问题。
问题: 在写windows服务时,发布后日志报错:尝试加载 Oracle 客户端库时引发 BadImageFormatException.如果在安装 32 位 Oracle 客户端组件的情况下以 64 ...
- 使用Publisher2016快速给图片添加水印
打开Publisher,根据图片大小及形状选择空白页面大小,此处选择纵向: 点击图标选择图片: 点击绘制文本框: 在文本框中编辑水印内容,对文字大小.字体.颜色进行调整,此处将水印颜色调整为灰色,拖动 ...
- nginx支持https配置
nginx证书 nginx.conf配置.
- TensorFlow2.0极简安装(亲测有效)
x相信每一个学习深度学习的人来说都知道Google的深度学习框架TensorFlow,估计每个人都想成为一个TF Boy(TensorFlow Boy).我也是这个想法,于是我踏上了安装TensorF ...
- 最全 HTTP 安全响应头设置指南
销售“安全记分卡”的公司正在崛起,并已开始成为企业销售的一个因素.这些公司组合使用 HTTP 安全报头和 IP 信誉来进行评级.不过,在很大程度上,公司的得分取决于对外开放网站上设置的安全响应报头.本 ...
- 科学使用Log4View2
目录 目录 前言 科学使用 编辑和调试程序集 调试程序集 编辑程序集 结语 推荐文献 目录 NLog日志框架使用探究-1 NLog日志框架使用探究-2 科学使用Log4View2 前言 这个标题很低调 ...
- 痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU启动那些事(4)- OTP及其烧写方法
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RTxxx系列MCU的OTP. 在i.MXRTxxx启动系列第二篇文章 Boot配置(ISP Pin, OTP) 里痞子 ...
- OC中内存管理(转)
OC内存管理 一.基本原理 (一)为什么要进行内存管理. 由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存较多时,系统就会发出内存警告,这时需要回收一些不需要再继 ...
- 爬虫新宠requests_html 带你甄别2019虚假大学 #华为云·寻找黑马程序员#
python模块学习建议 学习python模块,给大家个我自己不专业的建议: 养成习惯,遇到一个模块,先去github上看看开发者们关于它的说明,而不是直接百度看别人写了什么东西.也许后者可以让你很快 ...