概述
前面讲了Layer,Net就是把Layer串起来的神经网络。本篇主要讨论在神经网络中如何进行Layer之间的管理和计算
Net成员变量
网络相关变量
string name_; // 网络名字
Phase phase_; // TRAIN 或者 TEST
const Net* const root_net_; // root_net存储共享的Layer以方便数据并行处理
size_t memory_used_; // 计算这个网络的内存使用量
bool debug_info_; // 是否输出调试信息
Layer相关变量
vector<shared_ptr<Layer<Dtype> > > layers_; // Layer的集合
vector<string> layer_names_; // Layer的名字集合
map<string, int> layer_names_index_; // 通过Layer名字得到Layer的索引
vector<bool> layer_need_backward_; // layer_need_backward_[i]=true表示第i个Layer需要反向传播
Blob相关变量
// 存储整个Net中所有用到的Blob
vector<shared_ptr<Blob<Dtype> > > blobs_;
vector<string> blob_names_; // 类似于layer_names_
map<string, int> blob_names_index_; // 类似于layer_names_index_
vector<bool> blob_need_backward_; // 类似于layer_need_backward_
vector<Dtype> blob_loss_weights_; // 存储每个blob计算Loss函数时使用的loss权重
// 存储每个Layer的bottom和top blob的指针,他们通过id_vecs_存储在blobs_中的索引
vector<vector<Blob<Dtype>*> > bottom_vecs_,top_vecs_;
vector<vector<int> > bottom_id_vecs_, top_id_vecs_;
vector<vector<bool> > bottom_need_backward_;
// 网络的输入和输出Blobs以及在blobs_中对应的索引
vector<Blob<Dtype>*> net_input_blobs_, net_output_blobs_;
vector<int> net_input_blob_indices_, net_output_blob_indices_;
训练参数相关变量
vector<shared_ptr<Blob<Dtype> > > params_; // 存储整个Net中所有用到的参数Blob,可重复
vector<Blob<Dtype>*> learnable_params_; // 可学习的参数集合
// learnable_param_ids_元素个数等于params_元素个数
// 当且仅当params_[i]是参数所有者的时候,learnable_params_[learnable_param_ids_[i]]等于params_[i],否则params_[i]仅仅共享了这个参数而已, learnable_params_[learnable_param_ids_[i]]等于这个参数的所有者。
vector<int> learnable_param_ids_;
vector<vector<int> > param_id_vecs_; //存储每个Layer里每个param在params_中的索引
vector<int> param_owners_; // param_owners_[i]存储params_[i]的owner_id,如果它本身是owner,则param_owners_[i] = -1
vector<string> param_display_names_; // 所有参数的名字集合
vector<pair<int, int> > param_layer_indices_; // 存储所有的<layer_id, param_id> 对
map<string, int> param_names_index_; //存储参数名字到参数所有者id的映射
vector<float> params_lr_; // 第i个learnable_params_参数的学习率(learning rate)
vector<bool> has_params_lr_; // 第i个参数是否有学习率
vector<float> params_weight_decay_; // 第i个learnable_params_参数的权重递减率(weight decay)
vector<bool> has_params_decay_; // 第i个参数是否有权重递减
Net成员函数
初始化函数Init
- FilterNet(in_param, &filtered_param); // 过滤Net中不使用的Layer
- 每个Layer都有两个NetStateRule类型的属性include和exclude,NetStateRule描述了在什么条件下这个Layer会被使用或者不被使用(基于Phase, min_level, max_level, 允许使用的stage集合和不允许使用的stage集合)。如果include和exclude都没有定义,则默认这个Layer允许使用。
- FilterNet会遍历Net参数里面的Layer,逐个判断每个Layer是否应该在这个Net里面使用,如果是,则把它的参数拷贝到filtered_param里。相当于把不使用的Layer过滤掉。
- InsertSplits(filtered_param, ¶m); // 给网络添加SplitLayer,如果一个Layer L的输出blob B同时被n个Layer作为输入blob,则会给它专门加一个SplitLayer,这个SplitLayer的输入是B,有n个输出blob,每个输出blob都等于B,但第i个输出blob的名字是B_L_i_split。同时因为我们新引进了一个SplitLayer,如果Layer i 以B作为输入,那么需要把它的输入从B改为B_L_i_split。
- 遍历param中的每一个LayerParameter构造Layer
- 如果root_net里面是共享这个Layer的, 从root_net里面获得Layer
layers_.push_back(root_net_->layers_[layer_id]);
layers_[layer_id]->SetShared(true)
- 否则建立一个新的
layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));
layer_names_.push_back(layer_param.name())
- 调用AppendTop和AppendBottom把Top和Bottom的blobs加到Net中
- 如果Layer的类型是”Input”, 则更新net_input_blobs_和net_input_blob_indices_把每个top blob和它的idx加上
- 初始化Layer
layers_[layer_id]->SetUp(bottom_vecs_[layer_id], top_vecs_[layer_id])
- 对每个top blob设置loss_weights值
blob_loss_weights_[top_id_vecs_[layer_id][top_id]] = layer->loss(top_id);
memory_used_ += top_vecs_[layer_id][top_id]->count()
对每一层我们只累加top blobs的内存总量而不是top和bottom的内存总量,这是因为上一层的top blob就是下一层的bottom blob,我们不需要存相同数据两次。- 对Layer的每个可训练的参数来说,如果
param_spec->lr_mult()
不等于0,则我们需要设置Layer中对应的param_propagate_down_值为true,表示我们需要把误差梯度传反向传播下去。 - 调用AppendParam把每个Param blob加到Net中
layer_need_backward_[layer_id] = need_backward
如果Layer中有需要反向传播的参数或者bottom blob,need_backward就设为true。blob_need_backward_[top_id_vecs_[layer_id][top_id]] = need_backward
把所有的top blob都设为need_backward。
- 如果root_net里面是共享这个Layer的, 从root_net里面获得Layer
for (layer_id = layer.size() - 1; layer_id >=0; --layer_id) // 从后往前检查每层来查看哪些blob跟最后的loss函数有关,从而只对那些blob进行反向计算。同时检查哪些bottom blob不需要进行反向计算,可以因此跳过某些层的计算
layer_contributes_loss = false
layer_skip_propagate_down = true
for (top_id = 0; top_id < top_vecs_[layer_id].size(); top_id++)
- 如果layer->loss(top_id) > 0 或者blobs_under_loss包含了这个blob,则layer_contributes_loss=true
- 如果blobs_skip_backp不包含这个blob,则layer_skip_propagate_down=false
- 如果layer_skip_propagate_down或者!layer_contributes_loss,则把所有的bottom blob和layer标记为不需要反向计算
layer_need_backward[layer_id]=false; bottom_need_backward[layer_id][bottom_id]=false
for (bottom_id = 0; bottom_id < bottom_vecs_[layer_id].size(); bottom_id++)
- 如果layer_contribute_loss,则把所有bottom blob加到blobs_under_loss里
- 如果!bottom_need_backward[layer_id][bottom_id],则把它加到blobs_skip_backp里
- 如果强制要求反向计算,那么就会把bottom_need_backward和blob_need_backward_, param_propagate_down_都设成true
- 剩下没处理的blob会被当成网络的output blob加到net_output_blobs_和net_output_blob_indices_ 变量里
- ShareWeigths, 把所有共享同一param blob的blobs都通过ShareData和ShareDiff函数共享同一块内存。
AppendTop, AppendBottom和AppendParam函数
void AppendTop(const NetParameter& param, const int layer_id,
const int top_id, set<string>* available_blobs,
map<string, int>* blob_name_to_idx) {
// 获得当前blob的名字
const string& blob_name = (layer_param->top_size() > top_id) ?
layer_param->top(top_id) : "(automatic)";
// 如果这层的bottom blob和当前blob是一样的话,直接把bottom blob加到top_vecs_和top_id_vecs_里面
if (blob_name_to_idx && layer_param->bottom_size() > top_id &&
blob_name == layer_param->bottom(top_id)) {
top_vecs_[layer_id].push_back(blobs_[(*blob_name_to_idx)[blob_name]].get());
top_id_vecs_[layer_id].push_back((*blob_name_to_idx)[blob_name]);
} else {
// 申请一个新的blob指针
shared_ptr<Blob<Dtype> > blob_pointer(new Blob<Dtype>());
const int blob_id = blobs_.size(); // 获得blob id
// 相应更新各个blob相关的变量
blobs_.push_back(blob_pointer);
blob_names_.push_back(blob_name);
blob_need_backward_.push_back(false);
if (blob_name_to_idx) { (*blob_name_to_idx)[blob_name] = blob_id; }
top_id_vecs_[layer_id].push_back(blob_id);
top_vecs_[layer_id].push_back(blob_pointer.get());
}
// 把所有top blob加进去,然后把所有bottom blob删掉,就能得到剩下的只作为网络输出的blob
if (available_blobs) { available_blobs->insert(blob_name); }
}
int AppendBottom(const NetParameter& param, const int layer_id,
const int bottom_id, set<string>* available_blobs,
map<string, int>* blob_name_to_idx) {
// 获得当前blob的名字和id。
const string& blob_name = layer_param.bottom(bottom_id);
const int blob_id = (*blob_name_to_idx)[blob_name];
// 更新blob相关变量
bottom_vecs_[layer_id].push_back(blobs_[blob_id].get());
bottom_id_vecs_[layer_id].push_back(blob_id);
available_blobs->erase(blob_name);
// 通过blob_need_backward_和layer_param确定bottom_need_backward_的值
bool need_backward = blob_need_backward_[blob_id];
if (layer_param.propagate_down_size() > 0) {
need_backward = layer_param.propagate_down(bottom_id);
}
bottom_need_backward_[layer_id].push_back(need_backward);
return blob_id;
}
void AppendParam(const NetParameter& param, const int layer_id, const int param_id) {
// 参数名字和id
string param_name =
(param_size > param_id) ? layer_param.param(param_id).name() : "";
const int net_param_id = params_.size();
// 更新param相关变量
param_display_names_.push_back(param_name);
params_.push_back(layers_[layer_id]->blobs()[param_id]);
param_id_vecs_[layer_id].push_back(net_param_id);
param_layer_indices_.push_back(make_pair(layer_id, param_id));
if (!param_size || !param_name.size() || (param_name.size() &&
param_names_index_.find(param_name) == param_names_index_.end())) {
// 如果这个参数是新的参数, 也就是说这个参数是参数的所有者
param_owners_.push_back(-1);
if (param_name.size()) {
param_names_index_[param_name] = net_param_id;
}
// 加入到可学习参数集合
const int learnable_param_id = learnable_params_.size();
learnable_params_.push_back(params_[net_param_id].get());
learnable_param_ids_.push_back(learnable_param_id);
// 更新lr/weight_decay相关变量
has_params_lr_.push_back(param_spec->has_lr_mult());
has_params_decay_.push_back(param_spec->has_decay_mult());
params_lr_.push_back(param_spec->lr_mult());
params_weight_decay_.push_back(param_spec->decay_mult());
} else {
// 当前参数是参数的共享者,owner_net_param_id是参数所有者的id
const int owner_net_param_id = param_names_index_[param_name];
// 记录它的owner_id
param_owners_.push_back(owner_net_param_id);
const int learnable_param_id = learnable_param_ids_[owner_net_param_id];
learnable_param_ids_.push_back(learnable_param_id);
// 更新lr/weight_decay相关变量,略
}
}
Net前向函数
// 从Layer start前向传播到Layer end,loss的叠加是整个Net的loss
Dtype ForwardFromTo(int start, int end) {
Dtype loss = 0;
for (int i = start; i <= end; ++i) {
Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
loss += layer_loss;
if (debug_info_) {
// 打印Layer i的每个top blob, param blob数据的平均值
ForwardDebugInfo(i);
}
}
return loss;
}
// 除了计算loss之外,返回output_blobs的值
const vector<Blob<Dtype>*>& Forward(Dtype* loss) {
if (loss != NULL) {
*loss = ForwardFromTo(0, layers_.size() - 1);
} else {
ForwardFromTo(0, layers_.size() - 1);
}
return net_output_blobs_;
}
Net反向函数
// 从Layer start反向传播刀Layer end
void Net<Dtype>::BackwardFromTo(int start, int end) {
for (int i = start; i >= end; --i) {
if (layer_need_backward_[i]) { // 只对需要反向传播的Layer处理
layers_[i]->Backward(
top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);
if (debug_info_) {
// 打印Layer i的bottom blob和param blob的diff的平均值
BackwardDebugInfo(i);
}
}
}
}
void Net<Dtype>::Backward() {
BackwardFromTo(layers_.size() - 1, 0);
if (debug_info_) {
// 打印所有参数的数据和diff的L1, L2 norm
Dtype asum_data = 0, asum_diff = 0, sumsq_data = 0, sumsq_diff = 0;
for (int i = 0; i < learnable_params_.size(); ++i) {
asum_data += learnable_params_[i]->asum_data();
asum_diff += learnable_params_[i]->asum_diff();
sumsq_data += learnable_params_[i]->sumsq_data();
sumsq_diff += learnable_params_[i]->sumsq_diff();
}
const Dtype l2norm_data = std::sqrt(sumsq_data);
const Dtype l2norm_diff = std::sqrt(sumsq_diff);
LOG(ERROR) << " [Backward] All net params (data, diff): "
<< "L1 norm = (" << asum_data << ", " << asum_diff << "); "
<< "L2 norm = (" << l2norm_data << ", " << l2norm_diff << ")";
}
}