Caffe 激励层(Activation)分析

Caffe_Activation

通常来讲,激励层的输入输出尺寸一致,为非线性函数,完成非线性映射,从而可以拟合更为复杂的函数表达式激励层都派生于NeuronLayer: class XXXlayer : public NeuronLayerhtml

1.基本函数

激励层的基本函数较为简单,主要包含构造函数和前向、后向函数markdown

 explicit XXXLayer(const LayerParameter& param) :NeuronLayer<Dtype>(param){} virtual inline const char* type() const { return "layerNane"; } virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom); virtual void Backward_gpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);explicit XXXLayer(const LayerParameter& param) :NeuronLayer<Dtype>(param){} virtual inline const char* type() const { return "layerNane"; } virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom); virtual void Backward_gpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

2.经常使用激励函数

(1) Relu/PRelu Rectufied Linear Units

ReLU的函数表达式为\(f(x) = x*(x>0) + negative\_slope*x*(x <= 0)\) 具体实现以下网络

 //forward_cpu template <typename Dtype> void ReLULayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>& top){ // 根据bottom求解top const Dtype* bottom_data = bottom[0]->cpu_data();//const 不可修饰 Dtype* top_data = top[0]->mutable_cpu_data();//可修饰 const int count = bottom[0]->count();//由于count_一致,也可用top Dtype negative_slope = this->layer_param_.relu_param().negative_slope(); for (size_t i = 0; i < count; i++) { top_data[i] = bottom_data[i]*(bottom_data[i] > 0) + negative_slope*bottom_data[i]*(bottom_data[i] <= 0); } } //Backward_cpu // 导数形式 f'(x) = 1 x>0 ; negative_slope*x x<0 template <typename Dtype> void ReLULayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom){ const Dtype* top_diff = top[0].cpu_diff();//top diff const Dtype* bottom_data = bottom[0].cpu_data();//用以判断x是否大于0 Dtype* bottom_diff = bottom[0].cpu_diff();//bottom diff const int count = bottom[0].count(); for (size_t i = 0; i < count; i++) { bottom_diff[i] = top_diff[i]*(bottom_data[i] > 0) +negative_slope*(bottom_data[i] <= 0); } } // Relu 函数形式简单,导函数简单,能有效的解决梯度弥散问题,可是当x小于0时,易碎 // 可是网络多为多神经元,因此实际应用中不会影响到网络的正常训练。//forward_cpu template <typename Dtype> void ReLULayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>& top){ // 根据bottom求解top const Dtype* bottom_data = bottom[0]->cpu_data();//const 不可修饰 Dtype* top_data = top[0]->mutable_cpu_data();//可修饰 const int count = bottom[0]->count();//由于count_一致,也可用top Dtype negative_slope = this->layer_param_.relu_param().negative_slope(); for (size_t i = 0; i < count; i++) { top_data[i] = bottom_data[i]*(bottom_data[i] > 0) + negative_slope*bottom_data[i]*(bottom_data[i] <= 0); } } //Backward_cpu // 导数形式 f'(x) = 1 x>0 ; negative_slope*x x<0 template <typename Dtype> void ReLULayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,const vector<Blob<Dtype>*>& bottom){ const Dtype* top_diff = top[0].cpu_diff();//top diff const Dtype* bottom_data = bottom[0].cpu_data();//用以判断x是否大于0 Dtype* bottom_diff = bottom[0].cpu_diff();//bottom diff const int count = bottom[0].count(); for (size_t i = 0; i < count; i++) { bottom_diff[i] = top_diff[i]*(bottom_data[i] > 0) +negative_slope*(bottom_data[i] <= 0); } } // Relu 函数形式简单,导函数简单,能有效的解决梯度弥散问题,可是当x小于0时,易碎 // 可是网络多为多神经元,因此实际应用中不会影响到网络的正常训练。
(2) Sigmoid (S曲线)

Sigmoid函数表达式为\(f(x) = 1./(1+exp(-x))\);值域0-1,常做为BP神经网络的激活函数
因为输出为0-1,也做为logistic回归分析的几率输出函数。具体实现以下;函数

 //定义一个sigmoid函数方便计算 template <typename Dtype> inline Dtype sigmoid(Dtype x){ return 1./(1.+exp(-x)); } //前向 直接带入sigmoid函数便可 template <typename Dtype> void SigmoidLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>& top){ const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data();//须要计算 const int count = bottom[0]->count();//N*C*H*W; for (size_t i = 0; i < count; i++) { top_data[i] = sigmoid(bottom_data[i]); } } //Backward_cpu 因为f'(x) = f(x)*(1-f(x)),因此须要top_data // bottom_diff = top_diff*f'(bottom_data) = top_diff*top_data*(1-top_data) template <typename Dtype> void SigmoidLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,vector<Blob<Dtype>*>& bottom){ const Dtype* top_diff = top[0]->cpu_diff(); const Dtype* top_data = top[0]->cpu_data(); Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); //须要计算 const int count = bottom[0]->count(); for (size_t i = 0; i < count; i++) { //top_data[i] == sigmoid(bottom_data[i]); bottom_diff[i] = top_diff[i]*top_data[i]*(1.-top_data[i]); } } // Sigmoid函数能够做为二分类的几率输出,也能够做为激活函数完成非线性映射,可是网络 // 增长时,容易出现梯度弥散问题,目前在CNN中基本不使用 //定义一个sigmoid函数方便计算 template <typename Dtype> inline Dtype sigmoid(Dtype x){ return 1./(1.+exp(-x)); } //前向 直接带入sigmoid函数便可 template <typename Dtype> void SigmoidLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>& top){ const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data();//须要计算 const int count = bottom[0]->count();//N*C*H*W; for (size_t i = 0; i < count; i++) { top_data[i] = sigmoid(bottom_data[i]); } } //Backward_cpu 因为f'(x) = f(x)*(1-f(x)),因此须要top_data // bottom_diff = top_diff*f'(bottom_data) = top_diff*top_data*(1-top_data) template <typename Dtype> void SigmoidLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,vector<Blob<Dtype>*>& bottom){ const Dtype* top_diff = top[0]->cpu_diff(); const Dtype* top_data = top[0]->cpu_data(); Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); //须要计算 const int count = bottom[0]->count(); for (size_t i = 0; i < count; i++) { //top_data[i] == sigmoid(bottom_data[i]); bottom_diff[i] = top_diff[i]*top_data[i]*(1.-top_data[i]); } } // Sigmoid函数能够做为二分类的几率输出,也能够做为激活函数完成非线性映射,可是网络 // 增长时,容易出现梯度弥散问题,目前在CNN中基本不使用

(3)TanH,双正切函数

TanH函数的表达式为 \(\frac{(1.-exp(-2x))}{(1.+exp(-2x))}\);值域0-1,与sigmoid函数有相同的问题,
可是TanH在RNN中使用较为普遍,理由参考,具体实现以下所示。post

 //定义一个tanH的函数表达式,实际已经封装 inline Dtype TanH(Dtype x){ return (1.-exp(-2*x))/(1.+exp(-2*x)); } //Forward_cpu template <typename Dtype> void TanHLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>& top){ const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); const int count = bottom[0]->count(); for (size_t i = 0; i < count; i++) { top[i] = TanH(bottom_data[i]); } } //Backward_cpu f'(x) = 1-f(x)*f(x); // bottom_diff = top_diff(1-top_data*top_data); template <typename Dtype> void TanHLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,vector<Blob<Dtype>*>& bottom){ const Dtype* top_diff = top[0]->cpu_diff(); const Dtype* top_data = top[0]->cpu_data(); Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); //须要计算 const int count = bottom[0]->count(); for (size_t i = 0; i < count; i++) { //top_data[i] == TanH(bottom_data[i]); bottom_diff[i] = top_diff[i]*(1.-top_data[i]*top_data[i]); } }//定义一个tanH的函数表达式,实际已经封装 inline Dtype TanH(Dtype x){ return (1.-exp(-2*x))/(1.+exp(-2*x)); } //Forward_cpu template <typename Dtype> void TanHLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, vector<Blob<Dtype>*>& top){ const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); const int count = bottom[0]->count(); for (size_t i = 0; i < count; i++) { top[i] = TanH(bottom_data[i]); } } //Backward_cpu f'(x) = 1-f(x)*f(x); // bottom_diff = top_diff(1-top_data*top_data); template <typename Dtype> void TanHLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down,vector<Blob<Dtype>*>& bottom){ const Dtype* top_diff = top[0]->cpu_diff(); const Dtype* top_data = top[0]->cpu_data(); Dtype* bottom_diff = bottom[0]->mutable_cpu_diff(); //须要计算 const int count = bottom[0]->count(); for (size_t i = 0; i < count; i++) { //top_data[i] == TanH(bottom_data[i]); bottom_diff[i] = top_diff[i]*(1.-top_data[i]*top_data[i]); } }
其余的激励函数就不在枚举,能够查看具体的caffe源码,实现大体相同

3.说明

(1) 梯度弥散和梯度爆炸

网络方向传播时,loss通过激励函数会有\(loss*\partial{f(x)}\),而如sigmoid的函数,
max(\(\partial{f(x)}\))只有1/4所以深层网络传播时loss愈来愈小,则出现前层网络未完整学习然后层网络学习饱和的现象学习

(2) Caffe激励层的构建

如上述的代码所示,激励层主要完成forward和Bacward的函数实现便可,由构建的函数表达式推导出它的导函数形式,弄懂bottom_data,top_data,bottom_diff,top_diff便可ui

转载于:https://www.cnblogs.com/LaplaceAkuir/p/7702322.htmlthis