1、Net数据结构
Net是由一系列层组成的有向无环(DAG)计算图,Caffe保留了计算图中所有的中间值以确保前向和反向迭代的准确性。一个典型的Net开始于data layer:从磁盘中加载数据,终止于loss layer:计算分类和重构这些任务的目标函数。Net由一系列层和它们之间的相互连接构成,用的是一种文本建模语言(protobuf)。Net是通过protobuf文件来描述整个Net是怎么由layer组成的。
Caffe中网络的构建与设备无关。网络构建完之后,通过设置Caffe::mode()函数中的Caffe::set_mode()即可实现在CPU或GPU上的运行。CPU与GPU无缝切换并且独立于模型定义。前传(forward)过程为给定的待推断的输入计算输出。在前传过程中,Caffe组合每一层的计算以得到整个模型的计算”函数”。本过程自底向上进行。反传(backward)过程根据损失来计算梯度从而进行学习。在反传过程中,Caffe通过自动求导并反向组合每一层的梯度来计算整个网络的梯度。这就是反传过程的本质。本过程自顶向下进行。反传过程以损失开始,然后根据输出计算梯度。根据链式准则,逐层计算出模型其余部分的梯度。有参数的层,会在反传过程中根据参数计算梯度。只要定义好了模型,Caffe中前传和反传的计算就可以立即进行,Caffe已经准备好了前传和反传的实现方法。
实现方法:
(1)、Net::Forward()和Net::Backward()方法实现网络的前传和反传,而Layer::Forward()和Layer::Backward()计算每一层的前传和后传。
(2)、每一层都有forward_{cpu,gpu}()和backward_{cpu,gpu}方法来适应不同的计算模式。由于条件限制或者为了使用便利,一个层可能仅实现了CPU或者GPU模式。
与大多数的机器学习模型一样,在Caffe中,学习是由一个损失函数驱动的(通常也被称为误差、代价或者目标函数)。一个损失函数通过将参数集(即当前的网络权值)映射到一个可以标识这些参数”不良程度”的标量值来学习目标。因此,学习的目的是找到一个网络权重的集合,使得损失函数最小。
在Caffe中,损失是通过网络的前向计算得到的。每一层由一系列的输入blobs(bottom),然后产生一系列的输出blobs(top)。这些层的某些输出可以用来作为损失函数。典型的一对多分类任务的损失函数是softMaxWithLoss函数。
Loss weights:对于含有多个损失层的网络(例如,一个网络使用一个softMaxWithLoss输入分类并使用EuclideanLoss层进行重构),损失权值可以被用来指定它们之间的相对重要性。
按照惯例,有着Loss后缀的Caffe层对损失函数有贡献,其它层被假定仅仅用于中间计算。然而,通过在层定义中添加一个loss_weight:字段到由该层的top blob,任何层都可以作为一个loss。对于带后缀Loss的层来说,其对于该层的第一个top blob含有一个隐式的loss_weight:1;其它层对应于所有top blob有一个隐式的loss_weight: 0。
然而,任何可以反向传播的层,可允许给予一个非0的loss_weight,例如,如果需要,对网络的某些中间层所产生的激活进行正则化。对于具有相关非0损失的非单输出,损失函数可以通过对所有blob求和来进行简单地计算。
那么,在Caffe中最终的损失函数可以通过对整个网络中所有的权值损失进行求和计算获得。
为了创建一个Caffe模型,需要在一个protobuf(.prototxt)文件中定义模型的结构。在Caffe中,层和相应的参数都定义在caffe.proto文件里。
2、Net的消息格式
// Net参数
message NetParameter {
// 网络名称
optional string name = 1;
// Net的输入blobs,可以有多个Blob
repeated string input = 3;
//输入blobs的shape
repeated BlobShape input_shape = 8;
// 输入blobs的维度,已被废弃,推荐用BlobShape代替
repeated int32 input_dim = 4;
// 是否每一层都需要执行反向操作
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
// Net三种状态:Phase、level、stage
optional NetState state = 6;
// 是否打印Net的前向、反向、更新的结果
optional bool debug_info = 7 [default = false];
//layer参数组成Net的所有层,每个层配置都包括连接属性和行为,由LayerParameter定义
repeated LayerParameter layer = 100;
// 已被废弃,用LayerParameter代替
repeated V1LayerParameter layers = 2;
}
3、Net模版类分析
位置:
include/caffe/net.hpp
src/caffe/net.cpp
3.1、依赖文件
#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"
3.2、Net中的类对象
我们把Blob比做Caffe的砖石,Layer比做Caffe的墙面,那么Net更像是工匠手中的图纸,描述每个墙面要出现的位置,所以Net实现必然有一套用于记录Layer和Blob的数据结构。
vector<shared_ptr<Layer<Dtype> > > layers_;//记录Net Prototxt中出现的每个Layer
vector<string> layer_names_;//记录Net Prototxt中出现的每个Layer的名称
map<string, int> layer_names_index_;//记录Net Prototxt中出现的每个Layer的名称和顺序索引的对应关系
vector<bool> layer_need_backward_; // 指定layers是否需要backward
vector<shared_ptr<Blob<Dtype> > > blobs_; // 存储每一个layer产生的中间结果
vector<string> blob_names_; // blobs名
map<string, int> blob_names_index_; // blobs 索引
vector<bool> blob_need_backward_; // 指定blobs是否需要backward
vector<vector<Blob<Dtype>*> > bottom_vecs_; // 存储每一个layer input bottom blobs 指针
vector<vector<int> > bottom_id_vecs_; // 存储每一个bottom blobs id
vector<vector<bool> > bottom_need_backward_; // 指定bottom blobs是否需要backward
vector<vector<Blob<Dtype>*> > top_vecs_; // 存储每一个layer output top blobs 指针
vector<vector<int> > top_id_vecs_; // 存储每一个layer output top blobs id
vector<Dtype> blob_loss_weights_; // layer 的loss函数值
vector<vector<int> > param_id_vecs_; //
//网络输入/输出Blob的索引
vector<int> net_input_blob_indices_;
vector<int> net_output_blob_indices_;
vector<Blob<Dtype>*> net_input_blobs_;
vector<Blob<Dtype>*> net_output_blobs_;
//网络权值
vector<shared_ptr<Blob<Dtype> > > params_;
//可训练的网络权值
vector<Blob<Dtype>*> learnable_params_;
//从params_到learnable_params_的映射
vector<int> learnable_param_ids_;
//学习速率倍乘因子
vector<float> params_lr_;
vector<bool> has_params_lr_;
//权值衰减因子
vector<float> params_weight_decay_;
vector<bool> has_params_decay_;
//记录网络占用的内存大小
size_t memory_used_;
//是否显示调试信息
bool debug_info_;
//数据并行条件下,根据网络实际有效网络,其他网络间接引用了根网络
const Net* const root_net_;
// 禁止使用Net类的拷贝和赋值操作
DISABLE_COPY_AND_ASSIGN(Net);
3.3、Net中的成员函数
3.3.1、构造函数及初始化函数
// 显示构造函数,内部调用Init函数
explicit Net(const NetParameter& param, const Net* root_net = NULL);
explicit Net(const string& param_file, Phase phase, const Net* root_net = NULL);
// Net初始化:创建blobs和layers以搭建整个网络DAG图,以及调用layer的SetUp函数,初始化时也会做另一些记录,例如确认整个网络结构的正确与否等,另外,初始化期间,Net会打印其初始化日志到INFO信息中
void Init(const NetParameter& param);
3.3.2、前向传播和反向传播的函数
//前向传播的函数,输入Blob已经预先填充
const vector<Blob<Dtype>*>& ForwardPrefilled(Dtype* loss = NULL);
//Net前向传播的几种形式
Dtype ForwardFromTo(int start, int end);
Dtype ForwardFrom(int start);
Dtype ForwardTo(int end);
const vector<Blob<Dtype>*>& Forward(const vector<Blob<Dtype>* > & bottom,Dtype* loss = NULL);
string Forward(const string& input_blob_protos, Dtype* loss = NULL);
// 对Net中的所有diff_数据清零,反向传播之前运行
void ClearParamDiffs();
// 反向传播,以下相关的反向传播函数,内部最终均会调用BackwardFromTo函数
void Backward();
void BackwardFromTo(int start, int end);
void BackwardFrom(int start);
void BackwardTo(int end);
// 前向反向传播,输入bottom,输出loss
Dtype ForwardBackward(const vector<Blob<Dtype>* > & bottom) {
Dtype loss;
Forward(bottom, &loss);
Backward();
return loss;
}
3.3.3、权值和偏置项的操作
// 根据已经由Solver专备好的diff更新Net权值和偏置
void Update();
// 共享权值和偏置数据
void ShareWeights();
// 从另一个Net拷贝train layers
void ShareTrainedLayersWith(const Net* other);
// 从另一个Net拷贝train layers,加载已训练好的模型
void CopyTrainedLayersFrom(const NetParameter& param);
void CopyTrainedLayersFrom(const string trained_filename);
void CopyTrainedLayersFromBinaryProto(const string trained_filename);
void CopyTrainedLayersFromHDF5(const string trained_filename);
// 写Net到NetParameter
void ToProto(NetParameter* param, bool write_diff = false) const;
// 写Net weights到HDF5文件
void ToHDF5(const string& filename, bool write_diff = false) const;
3.3.4、获取各种参数信息
// 获得Net名
inline const string& name() const { return name_; }
// 获得所有layer名
inline const vector<string>& layer_names() const { return layer_names_; }
// 获得blob名
inline const vector<string>& blob_names() const { return blob_names_; }
// 获得blob
inline const vector<shared_ptr<Blob<Dtype> > >& blobs() const { return blobs_; }
// 获得layer
inline const vector<shared_ptr<Layer<Dtype> > >& layers() const { return layers_; }
// 获得Net phase状态:train or test
inline Phase phase() const { return phase_; }
// 获得每一个layer的bottom vector
inline const vector<vector<Blob<Dtype>*> >& bottom_vecs() const { return bottom_vecs_; }
// 获得每一个layer的top vector
inline const vector<vector<Blob<Dtype>*> >& top_vecs() const { return top_vecs_; }
inline const vector<vector<bool> >& bottom_need_backward() const { return bottom_need_backward_; }
inline const vector<Dtype>& blob_loss_weights() const { return blob_loss_weights_; }
inline const vector<bool>& layer_need_backward() const { return layer_need_backward_; }
// 获得各种参数值
inline const vector<shared_ptr<Blob<Dtype> > >& params() const { return params_; }
inline const vector<Blob<Dtype>*>& learnable_params() const { return learnable_params_; }
inline const vector<float>& params_lr() const { return params_lr_; }
inline const vector<bool>& has_params_lr() const { return has_params_lr_; }
inline const vector<float>& params_weight_decay() const { return params_weight_decay_; }
inline const vector<bool>& has_params_decay() const { return has_params_decay_; }
const map<string, int>& param_names_index() const { return param_names_index_; }
inline const vector<int>& param_owners() const { return param_owners_; }
// input blob数目
inline int num_inputs() const { return net_input_blobs_.size(); }
// output blob数目
inline int num_outputs() const { return net_output_blobs_.size(); }
inline const vector<Blob<Dtype>*>& input_blobs() const { return net_input_blobs_; }
inline const vector<Blob<Dtype>*>& output_blobs() const { return net_output_blobs_; }
inline const vector<int>& input_blob_indices() const { return net_input_blob_indices_; }
inline const vector<int>& output_blob_indices() const { return net_output_blob_indices_; }
bool has_blob(const string& blob_name) const;
const shared_ptr<Blob<Dtype> > blob_by_name(const string& blob_name) const;
bool has_layer(const string& layer_name) const;
const shared_ptr<Layer<Dtype> > layer_by_name(const string& layer_name) const;
// 设置是否显示debug info
void set_debug_info(const bool value) { debug_info_ = value; }
// 移除指定的layers
static void FilterNet(const NetParameter& param, NetParameter* param_filtered);
static bool StateMeetsRule(const NetState& state, const NetStateRule& rule, const string& layer_name);