caffe的net是一個網絡架構,串聯起所有的blob,支撐着整個神經網絡前向和反向傳播的一個結構。他也是分爲hpp和cpp文件的。
首先看註釋
/**
* @brief Connects Layer%s together into a directed acyclic graph (DAG)
* specified by a NetParameter.
*
* TODO(dox): more thorough description.
*/
template <typename Dtype>
class Net
把各個層連接在一起,形成一個有向無環圖。
接着直接看代碼
Net 構造函數
explicit Net(const NetParameter& param);
explicit Net(const string& param_file, Phase phase,
const int level = 0, const vector<string>* stages = NULL);
首先是構造函數,他又兩個,第一個傳入的是 NetParameter
的對象,第二個是通過文件讀取的,傳入的文件路徑和一個 phase
, phase
是指的我這個網絡的狀態是 train
還是 test
的,然後還有 level
和 stages
.
看看它的具體實現
template <typename Dtype>
Net<Dtype>::Net(const string& param_file, Phase phase,
const int level, const vector<string>* stages) {
NetParameter param;
ReadNetParamsFromTextFileOrDie(param_file, ¶m);
// Set phase, stages and level
param.mutable_state()->set_phase(phase);
if (stages != NULL) {
for (int i = 0; i < stages->size(); i++) {
param.mutable_state()->add_stage((*stages)[i]);
}
}
param.mutable_state()->set_level(level);
Init(param);
}
由於我們參數是從文件讀取的,所以這個
NetParameter param;
是我們自己定義的。
然後是執行了
ReadNetParamsFromTextFileOrDie(param_file, ¶m);
這個函數傳入了兩個參數,一個是這個文件的地址,一個是 param
這個變量,用來接收文件中讀取的配置參數。這個函數是位於 src/caffe/util/upgrade_proto.cpp
這個目錄下的,它的大概功能就是從這個文件裏面讀取參數到 param
這個變量裏去。這個 param
的格式是在 src/caffe/proto/caffe.proto
裏面定義的,我們可以去看一看。
message NetParameter {
optional string name = 1; // consider giving the network a name
// DEPRECATED. See InputParameter. The input blobs to the network.
repeated string input = 3;
// DEPRECATED. See InputParameter. The shape of the input blobs.
repeated BlobShape input_shape = 8;
// 4D input dimensions -- deprecated. Use "input_shape" instead.
// If specified, for each input blob there should be four
// values specifying the num, channels, height and width of the input blob.
// Thus, there should be a total of (4 * #input) numbers.
repeated int32 input_dim = 4;
// Whether the network will force every layer to carry out backward operation.
// If set False, then whether to carry out backward is determined
// automatically according to the net structure and learning rates.
optional bool force_backward = 5 [default = false];
// The current "state" of the network, including the phase, level, and stage.
// Some layers may be included/excluded depending on this state and the states
// specified in the layers' include and exclude fields.
optional NetState state = 6;
// Print debugging information about results while running Net::Forward,
// Net::Backward, and Net::Update.
optional bool debug_info = 7 [default = false];
// The layers that make up the net. Each of their configurations, including
// connectivity and behavior, is specified as a LayerParameter.
repeated LayerParameter layer = 100; // ID 100 so layers are printed last.
// DEPRECATED: use 'layer' instead.
repeated V1LayerParameter layers = 2;
}
這裏面還是定義了很多東西的,並且都有非常詳細的註釋,比方說網絡的名字,形狀,是否打印等等。
緊接着是設置狀態,就是下面這一步。
param.mutable_state()->set_phase(phase);
然後如果設置了 stage
的話,就把每一層的 stage
加載進來。
然後 level
也是同理
param.mutable_state()->set_level(level);
最後初始化一下 param
就完了
Init(param);
然後我們再看直接傳入這個 NetParameter
的構造函數
template <typename Dtype>
Net<Dtype>::Net(const NetParameter& param) {
Init(param);
}
他就是直接調用了 Init
這個函數。
然後我們再看一下 Init
這個函數,它也是在這個 Net
類裏面。
Init 函數
/// @brief Initialize a network with a NetParameter.
void Init(const NetParameter& param);
這個函數實際上就是做一個初始化的功能。
// Set phase from the state.
phase_ = in_param.state().phase();
比方這一句就是把從參數裏讀取的 phase
賦值給自己的私有變量 phase_
.
// Filter layers based on their include/exclude rules and
// the current NetState.
NetParameter filtered_param;
FilterNet(in_param, &filtered_param);
LOG_IF(INFO, Caffe::root_solver())
<< "Initializing net from parameters: " << std::endl
<< filtered_param.DebugString();
然後定義了一個 NetParameter
的變量,去調用了一個 FilterNet
函數, 這個FilterNet
函數是幹嘛的呢?
它是根據 state
初始化所有層的一個函數。這個後面緊跟着就會講解。
// Create a copy of filtered_param with splits added where necessary.
NetParameter param;
InsertSplits(filtered_param, ¶m);
然後跟着的是 InsertSplits
這個函數,這個函數是幹嘛的呢?
因爲有的層可能會分裂,下面會有兩條路可以走,比方說有兩個top,那麼這個時候就需要分割出來了。
// For each layer, set up its input and output
bottom_vecs_.resize(param.layer_size());
top_vecs_.resize(param.layer_size());
bottom_id_vecs_.resize(param.layer_size());
param_id_vecs_.resize(param.layer_size());
top_id_vecs_.resize(param.layer_size());
bottom_need_backward_.resize(param.layer_size());
接下來的這個是根據layer的大小給這些vector分配空間
if (!param.layer(layer_id).has_phase()) {
param.mutable_layer(layer_id)->set_phase(phase_);
}
給每個layer分配 phase
,決定它是 train
還是 test
layers_.push_back(LayerRegistry<Dtype>::CreateLayer(layer_param));
layer_names_.push_back(layer_param.name());
LOG_IF(INFO, Caffe::root_solver())
<< "Creating Layer " << layer_param.name();
bool need_backward = false;
// Figure out this layer's input and output
for (int bottom_id = 0; bottom_id < layer_param.bottom_size();
++bottom_id) {
const int blob_id = AppendBottom(param, layer_id, bottom_id,
&available_blobs, &blob_name_to_idx);
// If a blob needs backward, this layer should provide it.
need_backward |= blob_need_backward_[blob_id];
}
然後開始把每個layer鏈接起來,因爲它是自 top
向 bottom
鏈接的,所以這裏是 AppendBottom
,他的 bottom
添加了對應的blob,也就是相當於一個鏈表一樣的結構,每個 bottom
添加了後面一個 bottom
的id。
int num_top = layer_param.top_size();
for (int top_id = 0; top_id < num_top; ++top_id) {
AppendTop(param, layer_id, top_id, &available_blobs, &blob_name_to_idx);
// Collect Input layer tops as Net inputs.
if (layer_param.type() == "Input") {
const int blob_id = blobs_.size() - 1;
net_input_blob_indices_.push_back(blob_id);
net_input_blobs_.push_back(blobs_[blob_id].get());
}
}
然後是把 top
對應層的id添加進去,也就是相當於一個雙向的鏈表一樣的。
Layer<Dtype>* layer = layers_[layer_id].get();
if (layer->AutoTopBlobs()) {
const int needed_num_top =
std::max(layer->MinTopBlobs(), layer->ExactNumTopBlobs());
for (; num_top < needed_num_top; ++num_top) {
// Add "anonymous" top blobs -- do not modify available_blobs or
// blob_name_to_idx as we don't want these blobs to be usable as input
// to other layers.
AppendTop(param, layer_id, num_top, NULL, NULL);
}
}
如果你這個層定義了自動top的話,那麼這裏就會把自動top添加進去。
這個自動top blob就是之前我們在layer看到的這麼一個接口。至於每個層具體怎麼實現的其實是在不同層有不同的實現方法的。
// After this layer is connected, set it up.
layers_[layer_id]->SetUp(bottom_vecs_[layer_id], top_vecs_[layer_id]);
全部都添加好了以後就開始走這個層的 setup
函數,這個 setup
我們之前在 layer
裏面講到過,就是那4步,就是。
void SetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
CheckBlobCounts(bottom, top);
LayerSetUp(bottom, top);
Reshape(bottom, top);
SetLossWeights(top);
}
- 檢查blob的count
- 走layer的setup
- 然後reshape它
- 最後設置loss權重
for (int top_id = 0; top_id < top_vecs_[layer_id].size(); ++top_id) {
if (blob_loss_weights_.size() <= top_id_vecs_[layer_id][top_id]) {
blob_loss_weights_.resize(top_id_vecs_[layer_id][top_id] + 1, Dtype(0));
}
blob_loss_weights_[top_id_vecs_[layer_id][top_id]] = layer->loss(top_id);
LOG_IF(INFO, Caffe::root_solver())
<< "Top shape: " << top_vecs_[layer_id][top_id]->shape_string();
if (layer->loss(top_id)) {
LOG_IF(INFO, Caffe::root_solver())
<< " with loss weight " << layer->loss(top_id);
}
memory_used_ += top_vecs_[layer_id][top_id]->count();
}
然後resize一下loss權重的大小,最後還統計了一下會用到多少內存,然後打印出來。
for (int param_id = 0; param_id < num_param_blobs; ++param_id) {
const ParamSpec* param_spec = (param_id < param_size) ?
&layer_param.param(param_id) : &default_param_spec;
const bool param_need_backward = param_spec->lr_mult() != 0;
need_backward |= param_need_backward;
layers_[layer_id]->set_param_propagate_down(param_id,
param_need_backward);
}
接着設置了每一層的 propagate_down
for (int param_id = 0; param_id < num_param_blobs; ++param_id) {
AppendParam(param, layer_id, param_id);
}
再把每一層的參數添加進去。
// Finally, set the backward flag
layer_need_backward_.push_back(need_backward);
if (need_backward) {
for (int top_id = 0; top_id < top_id_vecs_[layer_id].size(); ++top_id) {
blob_need_backward_[top_id_vecs_[layer_id][top_id]] = true;
}
}
最後設置一下反向傳播的狀態,如果參數裏指定了這一層有反向傳播那麼這裏就會設置成 true
這樣我們這個對於layer初始化的過程就完成了
// Go through the net backwards to determine which blobs contribute to the
// loss. We can skip backward computation for blobs that don't contribute
// to the loss.
// Also checks if all bottom blobs don't need backward computation (possible
// because the skip_propagate_down param) and so we can skip backward
// computation for the entire layer
然後他說通過網絡反向傳播去決定哪一個blob貢獻了loss,就可以跳過這些不需要貢獻loss的反傳計算。
然後看看是不是所有的blob都不需要反傳,如果是的話那麼我們就可以直接跳過 propagate_down
這一步了
bool layer_contributes_loss = false;
bool layer_skip_propagate_down = true;
首先默認他沒有貢獻loss的。
if (layers_[layer_id]->loss(top_id) ||
(blobs_under_loss.find(blob_name) != blobs_under_loss.end())) {
layer_contributes_loss = true;
}
如果發現它貢獻了loss就把這個 layer_contributes_loss
設置成 true
if (blobs_skip_backp.find(blob_name) == blobs_skip_backp.end()) {
layer_skip_propagate_down = false;
}
if (layer_contributes_loss && !layer_skip_propagate_down)
break;
如果一直沒有做貢獻的話那麼我們就可以跳過 propagate_down
這一步了。
// Handle force_backward if needed.
if (param.force_backward()) {
for (int layer_id = 0; layer_id < layers_.size(); ++layer_id) {
layer_need_backward_[layer_id] = true;
for (int bottom_id = 0;
bottom_id < bottom_need_backward_[layer_id].size(); ++bottom_id) {
bottom_need_backward_[layer_id][bottom_id] =
bottom_need_backward_[layer_id][bottom_id] ||
layers_[layer_id]->AllowForceBackward(bottom_id);
blob_need_backward_[bottom_id_vecs_[layer_id][bottom_id]] =
blob_need_backward_[bottom_id_vecs_[layer_id][bottom_id]] ||
bottom_need_backward_[layer_id][bottom_id];
}
for (int param_id = 0; param_id < layers_[layer_id]->blobs().size();
++param_id) {
layers_[layer_id]->set_param_propagate_down(param_id, true);
}
}
}
然後這裏處理了一下強制反傳,就是設置一下哪些blob需要強制反傳,哪些不需要。
// In the end, all remaining blobs are considered output blobs.
for (set<string>::iterator it = available_blobs.begin();
it != available_blobs.end(); ++it) {
LOG_IF(INFO, Caffe::root_solver())
<< "This network produces output " << *it;
net_output_blobs_.push_back(blobs_[blob_name_to_idx[*it]].get());
net_output_blob_indices_.push_back(blob_name_to_idx[*it]);
}
然後又弄了一個做 net_output_blobs_
的vector,就相當於把這個網絡串聯起來,類似於一個單鏈表結構。
ShareWeights();
debug_info_ = param.debug_info();
LOG_IF(INFO, Caffe::root_solver()) << "Network initialization done.";
接着從這個參數裏面抄權值,然後設置一下debug的狀態。
最後這個 Init
函數就結束了。
這樣我們構造函數和 Init
函數就結束了。
FilterNet 函數
// Helpers for Init.
/**
* @brief Remove layers that the user specified should be excluded given the current
* phase, level, and stage.
*/
static void FilterNet(const NetParameter& param,
NetParameter* param_filtered);
這個函數是根據 state
初始化所有層的一個函數。state
包含了 phase, level stage
NetState net_state(param.state());
param_filtered->CopyFrom(param);
這裏拷貝了參數到 param_filtered
這個變量裏面
param_filtered->clear_layer();
然後調用 clear_layer
清除所有層,因爲它是剛剛開始初始化時候調用的,就需要把內存裏的一些垃圾信息刪掉。
for (int i = 0; i < param.layer_size(); ++i)
這裏循環遍歷所有層,然後
加載每一層的參數和名字
const LayerParameter& layer_param = param.layer(i);
const string& layer_name = layer_param.name();
檢查每一層的 StateMeetsRule
bool layer_included = (layer_param.include_size() == 0);
for (int j = 0; layer_included && j < layer_param.exclude_size(); ++j) {
if (StateMeetsRule(net_state, layer_param.exclude(j), layer_name)) {
layer_included = false;
}
}
這個StateMeetsRule
屬於級聯網絡才用得到的開關,我們先跳過這一步。
然後開始copy layer
if (layer_included) {
param_filtered->add_layer()->CopyFrom(layer_param);
}
之前不是把layer都clear了嗎,現在就把從param裏讀的參數添加給layer。
Forward 函數
/**
* @brief Run Forward and return the result.
*
*/
const vector<Blob<Dtype>*>& Forward(Dtype* loss = NULL);
這個是 Net
的 Forward
函數,不是 Layer
的Forward
函數,Net
的 Forward
函數是指揮每一個 Layer
往哪個地方去傳。
template <typename Dtype>
const vector<Blob<Dtype>*>& Net<Dtype>::Forward(Dtype* loss) {
if (loss != NULL) {
*loss = ForwardFromTo(0, layers_.size() - 1);
} else {
ForwardFromTo(0, layers_.size() - 1);
}
return net_output_blobs_;
}
這裏判斷了一下需不需要 loss
,如果不需要 loss
就
直接走這個 ForwardFromTo
這個函數
ForwardFromTo 函數
我們可以進來看一下
for (int c = 0; c < before_forward_.size(); ++c) {
before_forward_[c]->run(i);
}
Dtype layer_loss = layers
首先讓 before_forward_
的這些步驟先走,因爲他們的名字就是在前傳之前的步驟,他們走完之後就開始走 layer
的步驟
Dtype layer_loss = layers_[i]->Forward(bottom_vecs_[i], top_vecs_[i]);
loss += layer_loss;
然後 layer
挨個走完 Forward
之後,有個 loss
把每一步額的 loss
加起來。
這個走完之後就是 after_forward
for (int c = 0; c < after_forward_.size(); ++c) {
after_forward_[c]->run(i);
}
也是顧名思義,所以他們在 forward
之後也走了一遍。
最後把總的 loss
返回就可以了。
return loss;
因爲這個是net的 Forward
函數,他就是一個大的框架,起的是串聯的作用,所以比較簡單。
ForwardPrefilled 函數
他的註釋上寫了,使用 Forward
代替
/// @brief DEPRECATED; use Forward() instead.
const vector<Blob<Dtype>*>& ForwardPrefilled(Dtype* loss = NULL) {
LOG_EVERY_N(WARNING, 1000) << "DEPRECATED: ForwardPrefilled() "
<< "will be removed in a future version. Use Forward().";
return Forward(loss);
}
而且他的實現也就是直接調用的 Forward
函數,所以這個函數實際上是一個比較老的函數,他只是留下了一個接口而已,以後都不會再用到了。
ForwardFromTo 函數
/**
* The From and To variants of Forward and Backward operate on the
* (topological) ordering by which the net is specified. For general DAG
* networks, note that (1) computing from one layer to another might entail
* extra computation on unrelated branches, and (2) computation starting in
* the middle may be incorrect if all of the layers of a fan-in are not
* included.
*/
Dtype ForwardFromTo(int start, int end);
這個函數註釋上就寫的很明白了,就是指定從某一層到某一層的一個函數,與之類似的還有 ForwardFrom
ForwardTo
。都只是參數不同而已。
Forward 函數
這裏是另外的一個 Forward
函數
/// @brief DEPRECATED; set input blobs then use Forward() instead.
const vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom,
Dtype* loss = NULL);
可以看到,他把 loss
放在了參數裏面,這個也是一個比較老的實現方法了,現在也都是用之前哪個 Forward
函數來實現了。
template <typename Dtype>
const vector<Blob<Dtype>*>& Net<Dtype>::Forward(
const vector<Blob<Dtype>*> & bottom, Dtype* loss) {
LOG_EVERY_N(WARNING, 1000) << "DEPRECATED: Forward(bottom, loss) "
<< "will be removed in a future version. Use Forward(loss).";
// Copy bottom to net bottoms
for (int i = 0; i < bottom.size(); ++i) {
net_input_blobs_[i]->CopyFrom(*bottom[i]);
}
return Forward(loss);
}
以前是會把 bottom
都傳進來的,而現在 bottom
是在初始化的時候就加載了,所以不用傳了。但是保留了原來的接口,所以主頁裏還是先複製一下它傳進來的 bottom
,然後再 Forward
ClearParamDiffs 函數
/**
* @brief Zeroes out the diffs of all net parameters.
* Should be run before Backward.
*/
void ClearParamDiffs();
他說了,這裏是把所有的參數都清成0,這個必須得在反傳之前運行。
Backward 函數
反傳函數其實和前傳是一樣的,因爲這個是net的反傳,它只是一個架構的作用
BackwardFromTo(layers_.size() - 1, 0);
可以看到,它第一步就是調用這個 BackwardFromTo
函數,實際上和前傳是一樣的。
if (debug_info_) {
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 << ")";
}
}
然後下面這些都是需要debug的一些參數,比方說各個data,各個diff,還有第二範式等等。
BackwardFromTo 函數
template <typename Dtype>
void Net<Dtype>::BackwardFromTo(int start, int end) {
CHECK_GE(end, 0);
CHECK_LT(start, layers_.size());
for (int i = start; i >= end; --i) {
for (int c = 0; c < before_backward_.size(); ++c) {
before_backward_[c]->run(i);
}
if (layer_need_backward_[i]) {
layers_[i]->Backward(
top_vecs_[i], bottom_need_backward_[i], bottom_vecs_[i]);
if (debug_info_) { BackwardDebugInfo(i); }
}
for (int c = 0; c < after_backward_.size(); ++c) {
after_backward_[c]->run(i);
}
}
}
這個 BackwardFromTo
函數和前傳是一樣的,設置一個起點,設置一個終點,然後運行 before_backward_
,完了運行 Backward
,最後運行 after_backward_
Reshape 函數
/**
* @brief Reshape all layers from bottom to top.
*
* This is useful to propagate changes to layer sizes without running
* a forward pass, e.g. to compute output feature size.
*/
void Reshape();
這裏是net的Reshape,不是blob的Reshape。這裏說Reshape會改變所有的layer的 top bottom的shape,也就是說你你前傳的時候如果就沒有改變這個layer的大小的話就會調用這個函數。
template <typename Dtype>
void Net<Dtype>::Reshape() {
for (int i = 0; i < layers_.size(); ++i) {
layers_[i]->Reshape(bottom_vecs_[i], top_vecs_[i]);
}
}
可以看到,它的實現非常簡單,就是遍歷了所有layer,每個layer都Reshape了一下。
因爲layer的Reshape是調整了一下top的形狀,然後給他分配緩存空間。
ForwardBackward 函數
這個函數就是 Forward
和 Backward
結合起來
Dtype ForwardBackward() {
Dtype loss;
Forward(&loss);
Backward();
return loss;
}
最後他也會返回一個前傳得到的loss,這一步是實現好了的,可以方便的在訓練時候去調它。
Update 函數
/// @brief Updates the network weights based on the diff values computed.
void Update();
這個 Updata
函數是根據計算的diff來更新網絡的權重
template <typename Dtype>
void Net<Dtype>::Update() {
for (int i = 0; i < learnable_params_.size(); ++i) {
learnable_params_[i]->Update();
}
}
它調用的是 learnable_params_
的 Update
,而這個learnable_params_
的 Update
是 Blob
裏的函數,他就是計算權重的一個函數。
ShareWeights 函數
/**
* @brief Shares weight data of owner blobs with shared blobs.
*
* Note: this is called by Net::Init, and thus should normally not be
* called manually.
*/
void ShareWeights();
這個函數是從參數裏面讀取那些權重,這個函數只在 Net::Init
裏調一次,所以不應該手動去調用它。
ShareTrainedLayersWith 函數
/**
* @brief For an already initialized net, implicitly copies (i.e., using no
* additional memory) the pre-trained layers from another Net.
*/
void ShareTrainedLayersWith(const Net* other);
它的註釋是說從已經初始化了的網絡,隱式的去拷貝,不佔用內存,也就內存映射的意思,其實就是加載一個pretrain的模型。
Layer<Dtype>* source_layer = other->layers()[i].get();
可以看到,它的指針指向了傳進來的那個layer,這個意思就是內存映射的意思。
copy 函數
// For an already initialized net, CopyTrainedLayersFrom() copies the already
// trained layers from another net parameter instance.
/**
* @brief For an already initialized net, copies the pre-trained layers from
* another Net.
*/
void CopyTrainedLayersFrom(const NetParameter& param);
void CopyTrainedLayersFrom(const string& trained_filename);
void CopyTrainedLayersFromBinaryProto(const string& trained_filename);
void CopyTrainedLayersFromHDF5(const string& trained_filename);
這些都是拷貝的函數,就是從文件裏面拷貝一個網絡到對象裏面。
toProto 函數
/// @brief Writes the net to a proto.
void ToProto(NetParameter* param, bool write_diff = false) const;
/// @brief Writes the net to an HDF5 file.
void ToHDF5(const string& filename, bool write_diff = false) const;
這些是把網絡儲存到文件中的兩個函數,一個是proto一個是HDF5格式
剩下的函數都是返回成員變量的一些函數了,比方說返回名字等等。