【caffe解读】 caffe从数学公式到代码实现2-基础函数类
文章首發于微信公眾號《與有三學AI》
[caffe解讀] caffe從數學公式到代碼實現2-基礎函數類
接著上一篇,本篇就開始讀layers下面的cpp,先看一下layers下面都有哪些cpp。
absval_layer.cpp
accuracy_layer.cpp
argmax_layer.cpp
base_conv_layer.cpp
base_data_layer.cpp
batch_norm_layer.cpp
batch_reindex_layer.cpp
bias_layer.cpp
bnll_layer.cpp
concat_layer.cpp
contrastive_loss_layer.cpp
conv_layer.cpp
crop_layer.cpp
cudnn_conv_layer.cpp
cudnn_lcn_layer.cpp
cudnn_lrn_layer.cpp
cudnn_pooling_layer.cpp
cudnn_relu_layer.cpp
cudnn_sigmoid_layer.cpp
cudnn_softmax_layer.cpp
cudnn_tanh_layer.cpp
data_layer.cpp
deconv_layer.cpp
dropout_layer.cpp
dummy_data_layer.cpp
eltwise_layer.cpp
elu_layer.cpp
embed_layer.cpp
euclidean_loss_layer.cpp
exp_layer.cpp
filter_layer.cpp
flatten_layer.cpp
hdf5_data_layer.cpp
hdf5_output_layer.cpp
hinge_loss_layer.cpp
im2col_layer.cpp
image_data_layer.cpp
infogain_loss_layer.cpp
inner_product_layer.cpp
input_layer.cpp
log_layer.cpp
loss_layer.cpp
lrn_layer.cpp
lstm_layer.cpp
lstm_unit_layer.cpp
memory_data_layer.cpp
multinomial_logistic_loss_layer.cpp
mvn_layer.cpp
neuron_layer.cpp
parameter_layer.cpp
pooling_layer.cpp
power_layer.cpp
prelu_layer.cpp
recurrent_layer.cpp
reduction_layer.cpp
relu_layer.cpp
reshape_layer.cpp
rnn_layer.cpp
scale_layer.cpp
sigmoid_cross_entropy_loss_layer.cpp
sigmoid_layer.cpp
silence_layer.cpp
slice_layer.cpp
softmax_layer.cpp
softmax_loss_layer.cpp
split_layer.cpp
spp_layer.cpp
tanh_layer.cpp
threshold_layer.cpp
tile_layer.cpp
window_data_layer.cpp
其中,下面這些layer是不需要反向傳播的,大部分都是io類,我們就不講了,自己去看。
threshold_layer.cpp
accuracy_layer.cpp
argmax_layer.cpp
data_layer.cpp
image_data_layer.cpp
input_layer.cpp
window_data_layer.cpp
parameter_layer.cpp
memory_data_layer.cpp
dummy_data_layer.cpp
hdf5_data_layer.cpp
hdf5_output_layer.cpp
neuron_layer.cpp
silence_layer.cpp
reshape_layer.cpp
rnn_layer.cpp
base_data_layer.cpp
剩下的就是要講的,我們先從官方的開始看,后面再看自己寫的以及一些開源的。這些layers大概有這么幾大類,基礎數學函數類,blob?shape操作類,loss類。
本節先看一些基礎函數類的layer,都只有一個輸入,一個輸出。注意其中有一些是容許inplace?的layer,有一些是不容許的。所謂inplace,輸入輸出共用一塊內存,在layer的傳播過程中,直接覆蓋,省內存。Caffe在開源框架中,是比較占內存的了。
?
01 absval_layer.cpp
數學定義:
由于這是第一個例子,我們說的詳細些;
Forward:
template?<typename?Dtype>
void?AbsValLayer<Dtype>::Forward_cpu(
const?vector<Blob<Dtype>*>&
bottom,?const?vector<Blob<Dtype>*>&?top)?{
const?int?count?=?top[0]->count();
Dtype*?top_data?=?top[0]->mutable_cpu_data();
caffe_abs(count,?bottom[0]->cpu_data(),
top_data);
}
其中,count是blob的size,等于N*C*H*W,bottom[0]是輸入x,top[0]是輸出f(x),利用mutable_cpu_data來寫入,cpu_data來讀取。
Backward:
template?<typename?Dtype>
void?AbsValLayer<Dtype>::Backward_cpu(const
vector<Blob<Dtype>*>&?top,
const?vector<bool>&
propagate_down,?const?vector<Blob<Dtype>*>&bottom)?{
const?int?count?=?top[0]->count();
const?Dtype*?top_diff?=?top[0]->cpu_diff();
if?(propagate_down[0])?{
const?Dtype*?bottom_data?=?bottom[0]->cpu_data();
?Dtype*?bottom_diff?=?bottom[0]->mutable_cpu_diff();
?caffe_cpu_sign(count,?bottom_data,?bottom_diff);
?caffe_mul(count,?bottom_diff,?top_diff,
bottom_diff);
}
}
根據梯度下降法和鏈式法則,
一次標準的梯度更新過程如下,wt+1=wt+Δwt,對于sgd算法,其中?wt=?η?gt?,w為參數,t為時序,Δ為更新量,η為學習率,g為梯度
其中梯度g就是
在backward中,我們只需要計算出即可,至于上面的符號,學習率等在其他地方處理,其實就是
其中其實top_diff,就是對應:
const?Dtype*?top_diff?=?top[0]->cpu_diff();
在這里我們知道了,cpu_data就是bottom的data,而cpu_diff就是它存儲的梯度,有疑問可以返回上一篇。
propagate_down是一個參數,用于控制是否容許梯度下傳的,
caffe_cpu_sign(count,?bottom_data,?bottom_diff);實際上就是計算了梯度,
再利用caffe_mul(count,?bottom_diff,?top_diff,bottom_diff);
就OK了
沒有對應的test文件,就不解析了。
?
02 exp_layer.cpp
看下caffe?關于其參數的定義:
數學定義:
//?Message?that?stores?parameters?used?by?ExpLayer
message?ExpParameter?{
//?ExpLayer?computes
outputs?y?=?base?^?(shift?+?scale?*?x),?for?base?>?0.
//?Or?if?base?is?set?to
the?default?(-1),?base?is?set?to?e,
//?so?y?=?exp(shift?+?scale?*?x).
optional?float?base?=?1
[default?=?-1.0];
optional?float?scale?=?2
[default?=?1.0];
optional?float?shift?=?3
[default?=?0.0];
從下面的setuplayer中可以看出,如果base不是-1,則必須是大于0的數,也就是-2,-3等是不支持的。
const?Dtype?base?=?this->layer_param_.exp_param().base();
if?(base?!=?Dtype(-1))?{
?CHECK_GT(base,?0)?<<?"base?must?be?strictlypositive.";
}
當base=-1,也就是默認時f(x)=e^{αx+β},就是我們熟悉的指數函數了
還記得指數函數求導吧;
?
03 log_layer.cpp
數學定義:
同樣base=-1是默認值,否則必須大于0
?
04 power_layer.cpp
數學定義:
梯度也是很簡單的,不過為了提高計算效率,caffe盡可能的復用了中間結果,尤其是在反向傳播的時候,分兩種case,完整的計算大家還是去看代碼,這里粘代碼太難受了。
?
05 tanh_layer.cpp
數學定義:
這一次咱們遇到有test?layer,仔細說說。
在caffe/test目錄下test_tanh_layer.cpp
所謂測試,就是要驗證網絡的正向和反向。
這個文件是這樣測試的:
先定義了個tanh_na?ve函數,然后利用GaussianFille初始化一個bottom,將其通過forward函數,把出來的結果和tanh_na?ve的結果進行比對,完整代碼如下,感受一下:
void?TestForward(Dtype?filler_std)?{
?FillerParameter?filler_param;
?filler_param.set_std(filler_std);
?GaussianFiller<Dtype>?filler(filler_param);
?filler.Fill(this->blob_bottom_);
?LayerParameter?layer_param;
?TanHLayer<Dtype>?layer(layer_param);
?layer.SetUp(this->blob_bottom_vec_,
this->blob_top_vec_);
?layer.Forward(this->blob_bottom_vec_,
this->blob_top_vec_);
//?Now,?check?values
const?Dtype*?bottom_data?=?this->blob_bottom_->cpu_data();
const?Dtype*?top_data?=?this->blob_top_->cpu_data();
const?Dtype?min_precision?=?1e-5;
for?(int?i?=?0;?i?<?this->blob_bottom_->count();
++i)?{
???Dtype?expected_value?=
tanh_naive(bottom_data[i]);
???Dtype?precision?=?std::max(
?????Dtype(std::abs(expected_value?*?Dtype(1e-4))),
min_precision);
???EXPECT_NEAR(expected_value,?top_data[i],
precision);
?}
}
EXPECT_NEAR函數就會檢查梯度是否正確,如果過不了,就得回去看forward函數是否有錯了。
反向驗證:
void?TestBackward(Dtype?filler_std)?{
?FillerParameter?filler_param;
?filler_param.set_std(filler_std);
?GaussianFiller<Dtype>?filler(filler_param);
?filler.Fill(this->blob_bottom_);
?LayerParameter?layer_param;
?TanHLayer<Dtype>?layer(layer_param);
?GradientChecker<Dtype>?checker(1e-2,?1e-2,?1701);
?checker.CheckGradientEltwise(&layer,?this->blob_bottom_vec_,
this->blob_top_vec_);
}
其中GradientChecker(const?Dtype?stepsize,?const?Dtypethreshold,
const?unsigned?int?seed?=?1701,?const?Dtype?kink?=0.,const?Dtype?kink_range?=?-1)
可以設置stepwise和誤差閾值,CheckGradientEltwise是逐個像素檢查。
?
06 sigmoid_layer.cpp
數學定義:
?
07 relu_layer.cpp
數學定義:
其中negative_slope?a默認=0,退化為f(x)=max(x,0)
上面的relu其實包含了我們常說的relu和ReLU和LeakyReLU
?
08 prelu_layer.cpp
與LeakyReLU不同的是,負號部分的參數a是可學習的并不固定。所以,在反向傳播時,該參數需要求導,默認a=0.25。
數學定義
此處的a是個變量。
首先,對x也就是bottom的求導
代碼如下
if?(propagate_down[0])?{
Dtype*?bottom_diff?=?bottom[0]->mutable_cpu_diff();
for?(int?i?=?0;?i?<?count;?++i)?{
int?c?=?(i?/?dim)?%?channels?/?div_factor;
bottom_diff[i]?=?top_diff[i]?*?((bottom_data[i]?>?0)
+?slope_data[c]?*?(bottom_data[i]?<=?0));
}
}
而scale參數的求導,則會稍微復雜些,如下
if?(this->param_propagate_down_[0])?{
?Dtype*?slope_diff?=?this->blobs_[0]->mutable_cpu_diff();
for?(int?i?=?0;?i?<?count;?++i)?{
int?c?=?(i?/?dim)?%?channels?/?div_factor;
???slope_diff[c]?+=?top_diff[i]?*?bottom_data[i]
*?(bottom_data[i]?<=?0);
?}
}
因為對于blob中第i個數據,?當i不等于k時,yi?與xk是沒有關系的,但是a卻與blob中的所有數據有關系。
我們重新表述一下
?
09 elu_layer.cpp
數學定義:
?
10 bnll_layer.cpp
數學定義:
這次就先這樣。
同時,在我的知乎專欄也會開始同步更新這個模塊,歡迎來交流
https://zhuanlan.zhihu.com/c_151876233
注:部分圖片來自網絡
打一個小廣告,我在gitchat開設了一些課程和chat,歡迎交流。
感謝各位看官的耐心閱讀,不足之處希望多多指教。后續內容將會不定期奉上,歡迎大家關注有三公眾號 有三AI!
?
?
總結
以上是生活随笔為你收集整理的【caffe解读】 caffe从数学公式到代码实现2-基础函数类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【caffe解读】 caffe从数学公式
- 下一篇: 【分享预告】细数GAN和图像分类的前世今