动手写一个Caffe层:矩阵相乘Matmul
来源:互联网 发布:如何让mac休眠不断网 编辑:程序博客网 时间:2024/06/17 19:17
动手写一个Caffe层:矩阵相乘Matmul
- 背景
- 实现
- 前向传播实现
- 后向传播实现
- backward推导
- 小结
背景
最近在研究chainer网络的caffe实现,顺便也体验一下caffe。对于caffe训练过程的基本认识为搭积木,按顺序写好net.prototxt即可。但是有些时候会遇到没有想要的那块儿积木,这个时候就得自己造一造。
我期望的目的是实现一个
实现
关于实现自定义层,可以参考官方文档,下面我也将对照官方文档进行步骤说明。在这之前,也可以参考L2正则化层的实现,这个就讲得更实例一些。
官方文档看着挺长,实际总结一下就如下几步:
- 实现your_layer.hpp
- layer type 定义
- layer blob 定义
- 前向/后向接口定义
- 实现your_layer.cpp
- LayerSetup实现
- Reshape实现
- 前向传播Forward_cpu实现
- 后向传播Backward_cpu实现
- 写对应的GPU实现版本(可选)
- 定义相应的layer_param(可选)
- 实例化your_layer
- 写对应testbench(可选)
实现your_layer.hpp
那么就顺着说吧,先在include/caffe/layers/your_layer.hpp
目录下新建一个hpp文件,里面实现type()虚函数,和控制Blob个数的虚函数,Blob个数可以是动态的,也可以是写死的。
//blob numbervirtual inline int ExactNumBottomBlobs() const { return 1; }virtual inline int ExactNumTopBlobs() const { return 1; }//typevirtual inline const char* type() const { return "Matmul"; }
其中ExactNumBottomBlobs()
和ExactNumTopBlobs()
控制我的bottom Blob和top Blob个数为1,type()
返回我的layer名字Matlul。
当然另外一种偷懒的办法,可以把/caffe/layers/inner_product_layer.hpp
复制过来,然后改改名字,删掉不用执行的接口,比如为了简便这个Matmul层就直接CPU实现啦,与GPU相关的一律咔掉。
实现your_layer.cpp
这是整个自定义layer的重点。
首先LayerSetup方法我就把它当成每个layer的初始化后进行的参数赋值(虽然基类Layer的构造函数并没有参数赋值的功能),一些会用到的参数都在这里初始化,比如:
M_ = bottom[0]->num(); //rows of AN_ = bottom[0]->num(); //cols of BK_ = bottom[0]->channels(); //rows of A = cols of BW_ = sqrt(K_);//for value check//LOG(INFO) << "M:" << M_ <<" K_:" << K_ << " W_:" << W_ ;
其中M_,N_,K_的取值,是为了下面调用caffe_cblas内建函数caffe_cpu_gemm
做矩阵乘时的接口所需要的,这也是参考InnerProduct_layer的写法。
Reshape方法,在这里的作用是定义输出的top Blob的size,如果不规定好,程序不知道输出的Blob具有什么size,也就无法进行下去了。
int myints[] = {M_, M_};vector<int> top_shape (myints, myints + sizeof(myints) / sizeof(int) );top[0]->Reshape(top_shape); //reshape top
其中最主要的就是讲top的size规定好,实现的代码我写得不优雅,功能倒是可以实现。
Forward_cpu的函数主要负责当前layer前向传播时输出数据的计算,由于矩阵相乘在InnerProduct_layer中也有用到,而且这是就是最基本的运算,所以可以调用内建函数实现
const Dtype* bottom_data = bottom[0]->cpu_data();Dtype* top_data = top[0]->mutable_cpu_data();const Dtype* weight = bottom[0]->cpu_data();caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, N_, K_, (Dtype)1., bottom_data, weight, (Dtype)0., top_data);
只想说明一下mutable前缀的变量是可以修改的,即我们输出的top数据要修改,所以改变的数据都是mutable部分。关于caffe_cpu_gemm
的详细信息,可以参考一下官方文档或者seven_first的中文解析。
Backward_cpu函数用于计算当前layer后向传播时对bottom层偏导数的计算,对于当前这个矩阵相乘的layer,同样可以用一次caffe_cpu_gemm
实现,具体代码段如下,backward的推导见下一节。
const Dtype* top_diff = top[0]->cpu_diff(); Dtype* bottom_diff = bottom[0]->mutable_cpu_diff();const Dtype* weight = bottom[0]->cpu_data();caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1., top_diff, weight, (Dtype)0., bottom_diff);
注册及编译
代码部分写完了,要想你的protext文件中能够调用自定义的层,需要对你的层进行“注册”,让它可以实例化。主要的步骤就是在your_layer.cpp文件的末尾,添加如下两行:
INSTANTIATE_CLASS(MatmulLayer);REGISTER_LAYER_CLASS(Matmul);
如果要添加别的名字的layer,就更换一下即可。这样就可以在你的prototxt里面通过type = "Matmul"
来调用Matmul层了。
最后,记得把你的caffe重新编译一下,才可以使你的改动生效哦。
backward推导
这一部分主要是推导一下矩阵相乘的偏导数,用于Backward计算过程中输出给bottom_cpu_diff()
。用到的也是高数中基本的矩阵求导和链式法则。
条件:
其中X作为bottom_blob,经过caffe的
Reshape_layer
从NxCxHxW维变换为Cx(HxW=k)维。其中元素对矩阵求偏导的结果
目标:
将链式求导法则
一开始看不出规律,我就先计算了
与
将公式(5)代入公式(7)有:
所以可以得到
其实从矩阵的size也可以验证,公式所述的方式是对应正确的。ok,既然Backward也是一个矩阵相乘的方式,在上一节中的后向传播也可以沿用
caffe_cpu_gemm
进行计算。小结
1.主要就是介绍了一个简单地caffe自定义layer的实现,由于做的过程中基本都是调用CBLAS的内建函数,写起来并不复杂,主要是公式推导以及自定义layer的过程走通了,希望对大家有帮助。
2.在做实验的时候,可以通过在Forward_cpu函数中调用LOG(INFO) << "value is" <<val;
的方式,打印log帮助你调试。
- 动手写一个Caffe层:矩阵相乘Matmul
- 动手写一个"liveReload"
- 动手写一个轮播
- opencv 矩阵相乘, matlab矩阵相乘,以及自己写的矩阵相乘的时间比较
- matmul
- 动手写一个用户注册协议倒计时
- Tomcat中动手写一个servlet
- 动手写一个Remoting测试工具
- 准备动手写一个博客网站了
- 第一个任务--动手写一个网页
- 动手写一个STM8的轻量级bootloader
- caffe 添加一个新层
- caffe添加一个新层
- caffe: 新建一个loss层
- 动手写Jquery之--自动关闭的层
- 矩阵相乘
- 矩阵相乘
- 矩阵相乘
- centos7下使用yum安装mysql
- WdatePicker显示时分秒
- Java this 引用逃逸
- C/C++ struct 区别
- Jackson学习一之对象与JSON互相转化
- 动手写一个Caffe层:矩阵相乘Matmul
- Java 8十个lambda表达式案例
- IT-rsync--使用记录
- 8.2 喀迈拉(no.41~no.44)
- python网络服务wsgi及相关
- 【JZOJ 4709】 Matrix
- Android中Activity之间参数传递
- Spring与JMX集成
- Linux Shell case语句