概述
前面講了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 << ")";
}
}