Caffe Innerproduct.cpp学习
来源:互联网 发布:英国贝德福德大学 知乎 编辑:程序博客网 时间:2024/05/22 06:26
==================================common_layers.hpp=========================================
======================================Innerproductlayer.cpp====================================
template <typename Dtype>class InnerProductLayer : public Layer<Dtype> { public: explicit InnerProductLayer(const LayerParameter& param) : Layer<Dtype>(param) {} virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual void Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top); virtual inline const char* type() const { return "InnerProduct"; } virtual inline int ExactNumBottomBlobs() const { return 1; } virtual inline int ExactNumTopBlobs() const { return 1; } protected: 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); int M_;//表示样本个数 int K_;//表示单个样本特征向量长度 int N_;//表示全连接层输出神经元的个数 bool bias_term_; Blob<Dtype> bias_multiplier_;};
======================================Innerproductlayer.cpp====================================
%%%%%%%%%%%LayerSetUp%%%%%%%%%%%%template <typename Dtype>void InnerProductLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const int num_output = this->layer_param_.inner_product_param().num_output(); bias_term_ = this->layer_param_.inner_product_param().bias_term(); N_ = num_output;//全连接层输出神经元的个数 const int axis = bottom[0]->CanonicalAxisIndex( this->layer_param_.inner_product_param().axis()); // Dimensions starting from "axis" are "flattened" into a single // length K_ vector. For example, if bottom[0]'s shape is (N, C, H, W), // and axis == 1, N inner products with dimension CHW are performed. K_ = bottom[0]->count(axis);//将axis维之后的维度flat // Check if we need to set up the weights //在Caffe.proto里,LayerParameter中有一个repeated blobs field,但是在跟多net的定义文件即prototxt文件里并没有blobs,那么在这里将进行处理————显然,如果this->blobs_.size() > 0那么参数blob就不需要初始化了,skip;反之,则进行初始化 if (this->blobs_.size() > 0) { LOG(INFO) << "Skipping parameter initialization"; } else { if (bias_term_) { this->blobs_.resize(2); } else { this->blobs_.resize(1); } // Intialize the weight vector<int> weight_shape(2); weight_shape[0] = N_; weight_shape[1] = K_; this->blobs_[0].reset(new Blob<Dtype>(weight_shape));//<strong><em>可以认为blobs_[0]的维度为N_*K_,即通常,我们将权值矩阵设为N*K维。可以这么认为,但是在实际上,在C++中数据都是存放在内存中,并没有所谓的矩阵的概念</em></strong> // fill the weights 定义了一个智能指针 shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>( this->layer_param_.inner_product_param().weight_filler())); weight_filler->Fill(this->blobs_[0].get()); // If necessary, intiialize and fill the bias term if (bias_term_) { vector<int> bias_shape(1, N_); this->blobs_[1].reset(new Blob<Dtype>(bias_shape)); shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>( this->layer_param_.inner_product_param().bias_filler())); bias_filler->Fill(this->blobs_[1].get()); } } // parameter initialization //param_propagate_down_是从Layer<Dtype> 继承来的数据成员 this->param_propagate_down_.resize(this->blobs_.size(), true);}
%%%%%%%%%%%%%%%%%%Reshape%%%%%%%%%%%%%%%%%%template <typename Dtype>void InnerProductLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { // Figure out the dimensions const int axis = bottom[0]->CanonicalAxisIndex( this->layer_param_.inner_product_param().axis()); const int new_K = bottom[0]->count(axis); CHECK_EQ(K_, new_K) << "Input size incompatible with inner product parameters."; // The first "axis" dimensions are independent inner products; the total // number of these is M_, the product over these dimensions. M_ = bottom[0]->count(0, axis);//若axis=1,则M_表示bottom[0]里的样本个数 // The top shape will be the bottom shape with the flattened axes dropped, // and replaced by a single axis with dimension num_output (N_). vector<int> top_shape = bottom[0]->shape(); top_shape.resize(axis + 1);<strong><em>//重置top_shape,对于全链接层输出top往往不需要像bottom那样四维(NxCxHxW),所以重置。如果axis=1,那么top就重置为二维的,即一个矩阵。注意vector的resize操作————此种情况下,axis之前的元素保持不变</em></strong> top_shape[axis] = N_;//为矩阵的第二维赋值,即矩阵的列数;矩阵的行数为M_ top[0]->Reshape(top_shape);//top[0]的shape变成了M_x N_ // Set up the bias multiplier if (bias_term_) { vector<int> bias_shape(1, M_); bias_multiplier_.Reshape(bias_shape); caffe_set(M_, Dtype(1), bias_multiplier_.mutable_cpu_data()); }}
%%%%%%%%%%%%%%%%%Forward_cpu%%%%%%%%%%%%%%%%%%template <typename Dtype>void InnerProductLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); const Dtype* weight = this->blobs_[0]->cpu_data(); caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, N_, K_, (Dtype)1., bottom_data, weight, (Dtype)0., top_data); if (bias_term_) { caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, 1, (Dtype)1., bias_multiplier_.cpu_data(), this->blobs_[1]->cpu_data(), (Dtype)1., top_data); }}forward实现的功能就是 y=xw'+bx为输入,维度 MxKy为输出,维度 Nx1w为权重,维度 NxKb为偏置,维度 Nx1具体到代码实现,用的是这个函数caffe_cpu_gemm,具体的函数头为:void caffe_cpu_gemm<float>(const CBLAS_TRANSPOSE TransA, const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K, const float alpha, const float* A, const float* B, const float beta, float* C)整理它的功能其实很直观,即C←α*op(A)×op(B)+β*Cconst CBLAS_TRANSPOSE TransA # A是否转置const CBLAS_TRANSPOSE TransB # B是否转置若TransA = CblasNoTrans, op( A ) = A;若TransA = CblasTrans, op( A ) = A'M N K个人觉得为:const int M <strong>//op()操作后矩阵A的行数,矩阵C的行数 op()操作一般为转置或共额转置</strong>const int N <strong>//op()操作后矩阵B的列数,矩阵C的列数</strong>const int K <strong>//op()操作后矩阵A的列数,矩阵B的行数</strong>则,其中A维度是MxK,B维度是KxN,C维度为MxNlda,ldb,ldc,在BLAS的文档里,这三个参数分别为ABC的行数,但是实际使用发现,在CBLAS里应该是列数,注意是经过op()操作的矩阵ABC的列数<strong>全连接层的forward包括了两步:</strong># 这一步表示 y←wx,或者说是y←xw'caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, N_, K_, (Dtype)1., bottom_data, weight, (Dtype)0., top_data);# 这一步表示 y←y+bcaffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, 1, (Dtype)1., bias_multiplier_.cpu_data(), this->blobs_[1]->cpu_data(), (Dtype)1., top_data);<pre code_snippet_id="1584186" snippet_file_name="blog_20160221_4_1082892" name="code" class="cpp">实际上参与的计算为:(Mx1) x (1xN) = MxN 在C++中,数据都是存储在内存中,并以指针指向,那么为什么一个是<span style="font-family: Arial, Helvetica, sans-serif;">Mx1,一个是1xN, 个人觉得这应该是处于向量相对于矩阵的特殊性,更加自由点——以一维数组和多维数组为例,一维数组的数据在内存中的存储形式比多维数组的要自由点,约束少点,因为多维数组要保持RowMajor</span>
%%%%%%%%%%%%%%%%%%%%%%%%%back_forward%%%%%%%%%%%%%%%%%%%%%%template <typename Dtype>void InnerProductLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { if (this->param_propagate_down_[0]) { const Dtype* top_diff = top[0]->cpu_diff(); const Dtype* bottom_data = bottom[0]->cpu_data(); // Gradient with respect to weight caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1., top_diff, bottom_data, (Dtype)1., this->blobs_[0]->mutable_cpu_diff()); } if (bias_term_ && this->param_propagate_down_[1]) { const Dtype* top_diff = top[0]->cpu_diff(); // Gradient with respect to bias caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff, bias_multiplier_.cpu_data(), (Dtype)1., this->blobs_[1]->mutable_cpu_diff()); } if (propagate_down[0]) { const Dtype* top_diff = top[0]->cpu_diff(); // Gradient with respect to bottom data caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1., top_diff, this->blobs_[0]->cpu_data(), (Dtype)0., bottom[0]->mutable_cpu_diff()); }}参考UFLDL上的公式第一步,更新w,对应代码是:caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1., top_diff, bottom_data, (Dtype)0., this->blobs_[0]->mutable_cpu_diff());对照公式,有:<strong>需要更新的w的梯度的维度是NxK公式中的a^(l)对应的是bottom_data,维度是MxK公式中的\delta_(l+1)对应的是top_diff,维度是MxN</strong>第二步,更新b,对应代码是:caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff, bias_multiplier_.cpu_data(), (Dtype)0., this->blobs_[1]->mutable_cpu_diff());对照公式,有:公式中,<strong>b的梯度的维度应该为Nx1 ; \delta_(l+1)对应的是top_diff,维度是MxN</strong>这里用到了caffe_cpu_gemv,简单来说跟上面的caffe_cpu_gemm类似,不过前者是计算矩阵和向量之间的乘法的(从英文命名可以分辨,v for vector, m for matrix)。函数头:void caffe_cpu_gemv<float>(const CBLAS_TRANSPOSE TransA, const int M, const int N, const float alpha, const float* A, const float* x, const float beta, float* y) # <strong>实现的功能类似 Y←αAX + βY,若需要转置,则Y←αA'X + βY.所以个人认为ablas_sgemv()中参数MN表示的在op()操作之前的时候矩阵的行数和列数,即不管是Y←αAX + βY还是Y←αA'X + βY,都是矩阵A本身的行数和列数,而非其转置。# 其中A的维度为 MxN# X是一个向量,维度为 Mx1# Y是结果 ,也是一个向量,维度为Nx1</strong>const CBLAS_TRANSPOSE TransA # 是否对A进行转置# 下面的参数很直观,不描述了const int Mconst int Nconst float alphaconst float* Aconst float* xconst float betafloat* y绕回到具体的代码实现。。如何更新b?根据公式b的梯度直接就是delta# 所以对应的代码其实就是将top_diff转置后就可以了(忽略乘上bias_multiplier这步)caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff, bias_multiplier_.cpu_data(), (Dtype)0., this->blobs_[1]->mutable_cpu_diff());进行的计算实际为:(MxN)' x (Mx1) = N x 1第三步是计算\delta^(l):<strong>在公式中有一项f’,这里面可以忽略掉最后一项f’。因为在caffe实现中,这是由Relu layer来实现的,这里只需要实现括号里面的累加就好了,这个累加其实可以等价于矩阵乘法:</strong>caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1., top_diff, this->blobs_[0]->cpu_data(), (Dtype)0., (*bottom)[0]->mutable_cpu_diff());<strong># top_diff为\delta^(l+1) 维度 MxN# this->blobs_[0]->cpu_data()为W^(l) 维度 NxK# (*bottom)[0]->mutable_cpu_diff()是要计算的结果,也就是\delta^(l) 维度是MxK#即,当前层的\delta^(l) 维度是MxK,下一层的\delta^(l+1) 维度是MxN</strong>
0 0
- Caffe Innerproduct.cpp学习
- caffe-innerproduct layer
- caffe base_conv_layers.cpp 学习
- caffe Poolinglayer.cpp学习
- Caffe Relu_layer.cpp 学习
- Caffe softmax_layer.cpp学习
- Caffe softmax_loss_layer.cpp 学习
- Caffe源码剖析3-InnerProduct层
- 关于caffe innerproduct层的理解
- caffe源码学习中-tools/caffe.cpp
- Caffe BaseDataLayer.cpp BasePrefetchingDataLayer.cpp DataLayer.cpp 学习
- Caffe net.hpp net.cpp学习
- caffe源码c++学习笔记Classifier.cpp
- 学习笔记: 源码 caffe.cpp 初探
- caffe学习笔记--写一个运行caffe.cpp的makefile
- caffe源码深入学习1:caffe.cpp解析
- caffe源码学习中--src/caffe/solver.cpp
- Caffe Notes: Caffe.cpp
- cookie 和session 的区别详解
- 学习笔记
- 创业平台
- 蓝桥网 算法提高 线段和点
- Redis替代文件存储session
- Caffe Innerproduct.cpp学习
- 西安游记(钟楼 -- 回民街 -- 西安古城墙)
- Java学习之基本类型与字符串之间的转换
- CountDownLatch使用
- 闭包
- 编码/加密——《跟我学Shiro》
- PHP_Memcache函数详解
- OC语言之OC中的私有方法
- github新建SSH key出现github permission denied<publickey>