CNN压缩:为反向传播添加mask(caffe代码修改)
神经网络压缩的研究近三年十分热门,笔者查阅到相关的两篇博客,博主们非常奉献的提供了源代码,但是发发现在使用gpu训练添加mask的网络上,稍微有些不顺,特此再进行详细说明。
此文是在 基于Caffe的CNN剪枝[1]和 Deep Compression阅读理解及Caffe源码修改[2] 的基础上修改的。
mask的结构?
[1]中使用的blob,存储mask。blob是一块数据块,在初始化时,需要为gpu上的数据块申请一块空间,故有Addmask()函数。AddMask()是blob.hpp中的blob的成员方法,需要在blob.cpp中实现。使用时将Addmask()添加在innerproduct.cpp和base_conv.cpp中,使得网络在setuplayer的过程中,为fc层和conv层多开辟一块存放mask的syncedmemory。blob有一系列需要实现的cpu_data()/mutable_cpu_data()等,初始化中改变mask的值时需要注意使用合理的方式。
InnerProductLayer.cpp
void InnerProductLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
...
this->blobs_[].reset(new Blob<Dtype>(weight_shape));
this->blobs_[]->Addmask();
...}
base_conv.cpp:
template <typename Dtype>
void BaseConvolutionLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
...
this->blobs_[].reset(new Blob<Dtype>(weight_shape));
this->blobs_[]->Addmask();
...}
修改blob.hpp和blob.cpp,添加成员mask_和相关的方法,在[1]文章的评论里作者已给出源代码。
[2]中使用layer结构定义mask,layer是相当于数据的一系列操作,或者说是blob的组合方法。
但是,想要实现在gpu上的操作,数据需要有gpu有关的操作。故此处采用[1]中的方法,将mask_添加到blob class中,实现mask_属性。
mask的初始化?
在Caffe框架下,网络的初始化有两种方式,一种是调用filler,按照模型中定义的初始化方式进行初始化,第二种是从已有的caffemodel或者snapshot中读取相应参数矩阵进行初始化[1]。
1、filler的方法
在程序开始时,网络使用net.cpp中的Init()进行初始化,由输入至输出,依次调用各个层的layersetup,建立网络结构。如下所示是caffe中使用xavier方法进行填充的操作。
virtual void Fill(Blob<Dtype>* blob) {
CHECK(blob->count());
int fan_in = blob->count() / blob->num();
int fan_out = blob->count() / blob->channels();
Dtype n = fan_in; // default to fan_in
if (this->filler_param_.variance_norm() ==
FillerParameter_VarianceNorm_AVERAGE) {
n = (fan_in + fan_out) / Dtype();
} else if (this->filler_param_.variance_norm() ==
FillerParameter_VarianceNorm_FAN_OUT) {
n = fan_out;
}
Dtype scale = sqrt(Dtype() / n);
caffe_rng_uniform<Dtype>(blob->count(), -scale, scale,
blob->mutable_cpu_data());
//Filler<Dtype>:: FillMask(blob);
CHECK_EQ(this->filler_param_.sparse(), -)
<< "Sparsity not supported by this Filler.";
}
filler的作用是,为建立的网络结构产生随机初始化值。
即使是从snapshot或caffemodel中读入数据,也执行随机填充操作。
2、从snapshot或caffemodel中读入数据
tools/caffe.cpp 中的phase:train可以从snapshot或caffemodel中提取参数,进行finetune。phase:test则可以从提取的参数中建立网络,进行预测过程。
这里笔者的网络结构是在pycaffe中进行稀疏化的,因此读入网络的proto文件是一个连接数不变、存在部分连接权值为零的网络。需要在读入参数的同时初始化mask_。因此修改blob.cpp中的fromproto函数:
template <typename Dtype>
void Blob<Dtype>::FromProto(const BlobProto& proto, bool reshape) {
if (reshape) {
vector<int> shape;
if (proto.has_num() || proto.has_channels() ||
proto.has_height() || proto.has_width()) {
// Using deprecated 4D Blob dimensions --
// shape is (num, channels, height, width).
shape.resize();
shape[] = proto.num();
shape[] = proto.channels();
shape[] = proto.height();
shape[] = proto.width();
} else {
shape.resize(proto.shape().dim_size());
for (int i = ; i < proto.shape().dim_size(); ++i) {
shape[i] = proto.shape().dim(i);
}
}
Reshape(shape);
} else {
CHECK(ShapeEquals(proto)) << "shape mismatch (reshape not set)";
}
// copy data
Dtype* data_vec = mutable_cpu_data();
if (proto.double_data_size() > ) {
CHECK_EQ(count_, proto.double_data_size());
for (int i = ; i < count_; ++i) {
data_vec[i] = proto.double_data(i);
}
} else {
CHECK_EQ(count_, proto.data_size());
for (int i = ; i < count_; ++i) {
data_vec[i] = proto.data(i);
}
}
if (proto.double_diff_size() > ) {
CHECK_EQ(count_, proto.double_diff_size());
Dtype* diff_vec = mutable_cpu_diff();
for (int i = ; i < count_; ++i) {
diff_vec[i] = proto.double_diff(i);
}
} else if (proto.diff_size() > ) {
CHECK_EQ(count_, proto.diff_size());
Dtype* diff_vec = mutable_cpu_diff();
for (int i = ; i < count_; ++i) {
diff_vec[i] = proto.diff(i);
}
}
if(shape_.size()==||shape_.size()==){
Dtype* mask_vec = mutable_cpu_data();
CHECK(count_);
for(int i=;i<count_;i++)
mask_vec[i]=data_vec[i]?:;
}
在读入proto文件的同时,如果层的大小是4D——conv层、或2D——fc层时,初始化mask_为data_vec[i]?1:0。当层的大小是1Ds——pool或relu层时,不进行mask的初始化。
反向传播的修改?
1、修改blob的更新方式,添加math_funcion.hpp头文件。
template <typename Dtype>
void Blob<Dtype>::Update() {
// We will perform update based on where the data is located.
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
// perform computation on CPU
caffe_axpy<Dtype>(count_, Dtype(-),
static_cast<const Dtype*>(diff_->cpu_data()),
static_cast<Dtype*>(data_->mutable_cpu_data()));
caffe_mul<Dtype>(count_,
static_cast<const Dtype*>(mask_->cpu_data()),
static_cast<const Dtype*>(data_->cpu_data()),
static_cast<Dtype*>(data_->mutable_cpu_data()));
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
// perform computation on GPU
caffe_gpu_axpy<Dtype>(count_, Dtype(-),
static_cast<const Dtype*>(diff_->gpu_data()),
static_cast<Dtype*>(data_->mutable_gpu_data()));
caffe_gpu_mul<Dtype>(count_,
static_cast<const Dtype*>(mask_->gpu_data()),
static_cast<const Dtype*>(data_->gpu_data()),
static_cast<Dtype*>(data_->mutable_gpu_data()));
#else
NO_GPU;
#endif
break;
default:
LOG(FATAL) << "Syncedmem not initialized.";
}
}
2、为cpu下的计算和gpu下的计算分别添加形如weight[i]*=mask[i];的运算方式。
inner_product_layer.cpp:
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_[]) {
const Dtype* top_diff = top[]->cpu_diff();
const Dtype* bottom_data = bottom[]->cpu_data();
// Gradient with respect to weight
Dtype* weight_diff = this->blobs_[]->mutable_cpu_diff();
vector<int> weight_shape();
if (transpose_) {
weight_shape[] = K_;
weight_shape[] = N_;
} else {
weight_shape[] = N_;
weight_shape[] = K_;
}
int count = weight_shape[]*weight_shape[];
const Dtype* mask = this->blobs_[]->cpu_mask();
for(int j=;j<count;j++)
weight_diff[j]*=mask[j]; if (transpose_) {
caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans,
K_, N_, M_,
(Dtype)., bottom_data, top_diff,
(Dtype)., weight_diff);
} else {
caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans,
N_, K_, M_,
(Dtype)., top_diff, bottom_data,
(Dtype)., weight_diff);
}
}
if (bias_term_ && this->param_propagate_down_[]) {
const Dtype* top_diff = top[]->cpu_diff();
// Gradient with respect to bias
caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)., top_diff,
bias_multiplier_.cpu_data(), (Dtype).,
this->blobs_[]->mutable_cpu_diff());
}
if (propagate_down[]) {
const Dtype* top_diff = top[]->cpu_diff();
// Gradient with respect to bottom data
if (transpose_) {
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans,
M_, K_, N_,
(Dtype)., top_diff, this->blobs_[]->cpu_data(),
(Dtype)., bottom[]->mutable_cpu_diff());
} else {
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans,
M_, K_, N_,
(Dtype)., top_diff, this->blobs_[]->cpu_data(),
(Dtype)., bottom[]->mutable_cpu_diff());
}
}
}
inner_product_layer.cu:
template <typename Dtype>
void InnerProductLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom) {
if (this->param_propagate_down_[]) {
const Dtype* top_diff = top[]->gpu_diff();
const Dtype* bottom_data = bottom[]->gpu_data();
vector<int> weight_shape();
if (transpose_) {
weight_shape[] = K_;
weight_shape[] = N_;
} else {
weight_shape[] = N_;
weight_shape[] = K_;
}
int count = weight_shape[]*weight_shape[];
caffe_gpu_mul<Dtype>(count,static_cast<const Dtype*>(this->blobs_[]->mutable_gpu_diff()),static_cast<const Dtype*>(this->blobs_[]->gpu_mask()),static_cast<Dtype*>(this->blobs_[]->mutable_gpu_diff()));
Dtype* weight_diff = this->blobs_[]->mutable_gpu_diff();
//for(int j=0;j<count;j++)
//weight_diff[j]*=this->masks_[j];
// Gradient with respect to weight
if (transpose_) {
caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans,
K_, N_, M_,
(Dtype)., bottom_data, top_diff,
(Dtype)., weight_diff);
} else {
caffe_gpu_gemm<Dtype>(CblasTrans, CblasNoTrans,
N_, K_, M_,
(Dtype)., top_diff, bottom_data,
(Dtype)., weight_diff);
}
}
if (bias_term_ && this->param_propagate_down_[]) {
const Dtype* top_diff = top[]->gpu_diff();
// Gradient with respect to bias
caffe_gpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)., top_diff,
bias_multiplier_.gpu_data(), (Dtype).,
this->blobs_[]->mutable_gpu_diff());
}
if (propagate_down[]) {
const Dtype* top_diff = top[]->gpu_diff();
// Gradient with respect to bottom data
if (transpose_) {
caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasTrans,
M_, K_, N_,
(Dtype)., top_diff, this->blobs_[]->gpu_data(),
(Dtype)., bottom[]->mutable_gpu_diff());
} else {
caffe_gpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans,
M_, K_, N_,
(Dtype)., top_diff, this->blobs_[]->gpu_data(),
(Dtype)., bottom[]->mutable_gpu_diff());
}
}
}
至此修改完毕。
另外,caffe在新的版本中已添加sparse_参数,参考 https://github.com/BVLC/caffe/pulls?utf8=%E2%9C%93&q=sparse
CNN压缩:为反向传播添加mask(caffe代码修改)的更多相关文章
- 反向传播BackPropagation
http://www.cnblogs.com/charlotte77/p/5629865.html http://www.cnblogs.com/daniel-D/archive/2013/06/03 ...
- BP(back propagation)反向传播
转自:http://www.zhihu.com/question/27239198/answer/89853077 机器学习可以看做是数理统计的一个应用,在数理统计中一个常见的任务就是拟合,也就是给定 ...
- cs231n(三) 误差反向传播
摘要 本节将对反向传播进行直观的理解.反向传播是利用链式法则递归计算表达式的梯度的方法.理解反向传播过程及其精妙之处,对于理解.实现.设计和调试神经网络非常关键.反向求导的核心问题是:给定函数 $f( ...
- CS231n课程笔记翻译5:反向传播笔记
译者注:本文智能单元首发,译自斯坦福CS231n课程笔记Backprop Note,课程教师Andrej Karpathy授权翻译.本篇教程由杜客翻译完成,堃堃和巩子嘉进行校对修改.译文含公式和代码, ...
- 【cs231n】反向传播笔记
前言 首先声明,以下内容绝大部分转自知乎智能单元,他们将官方学习笔记进行了很专业的翻译,在此我会直接copy他们翻译的笔记,有些地方会用红字写自己的笔记,本文只是作为自己的学习笔记.本文内容官网链接: ...
- DL反向传播理解
作者:寒小阳 时间:2015年12月. 出处:http://blog.csdn.net/han_xiaoyang/article/details/50321873 声明:版权所有,转载请联系作者并注明 ...
- 卷积神经网络(CNN)反向传播算法
在卷积神经网络(CNN)前向传播算法中,我们对CNN的前向传播算法做了总结,基于CNN前向传播算法的基础,我们下面就对CNN的反向传播算法做一个总结.在阅读本文前,建议先研究DNN的反向传播算法:深度 ...
- CNN中卷积层 池化层反向传播
参考:https://blog.csdn.net/kyang624823/article/details/78633897 卷积层 池化层反向传播: 1,CNN的前向传播 a)对于卷积层,卷积核与输入 ...
- 《神经网络的梯度推导与代码验证》之CNN的前向传播和反向梯度推导
在FNN(DNN)的前向传播,反向梯度推导以及代码验证中,我们不仅总结了FNN(DNN)这种神经网络结构的前向传播和反向梯度求导公式,还通过tensorflow的自动求微分工具验证了其准确性.在本篇章 ...
随机推荐
- UNIX 系统概述
UNIX体系结构(UNIX Architecture) 调用内核的接口叫做系统调用(system call,图1.1中的阴影部分),普通函数库是建立在系统调用接口的基础之上.应用(applicatio ...
- 多源最短路Floyd 算法————matlab实现
弗洛伊德(Floyd)算法是一种用于寻找给定的加权图中顶点间最短路径的算法.该算法名称以创始人之一.1978年图灵奖获得者.斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名. 基本思想 通过Floyd计 ...
- Java中实现String.padLeft和String.padRight
因为习惯了C#中的padLeft和padRight,接触Java后突然失去这两个功能,觉得别扭,就试着实现了这两个方法. Java中String.format()中带有字符串对齐功能如下: Syste ...
- OC比C中,新增的数据类型
布尔型 BOOL 以及 boolean 1)这两者都是判断类型 2)在C底层这两者都是一个 char类型 占一个字符大小 3)BOOL 的取值为 YES / NO 其中NO =0 YES =1 4)b ...
- ionic之$ionicHistory
$ionicHistory 定义:当用户通过导航栏切换视图页面的时候,ionicHistory起到跟踪视图的作用,类似的浏览器的行为方式,一个ionic应用程序能够保持以前的视图,当前视图,和前视图( ...
- [笔记]我的Linux入门之路 - 05.Eclipse的Python开发环境搭建与Numpy、Scipy库安装
一.Python环境 直接终端查询下python安装没:python --version Python 2.7.12 Ubuntu竟然已经装了Python2.7,那就好说了.不然自己装和装jdk差不多 ...
- JWebFileTrans(JDownload): 一款可以从网络上下载文件的小程序(三),多线程断点下载
一 前言 本篇博客是<JWebFileTrans(JDownload):一款可以从网络上下载文件的小程序>系列博客的第三篇,本篇博客的内容主要是在前两篇的基础上增加多线程的功能.简言之,本 ...
- Socket中的异常和参数设置
1.常见异常 1.java.net.SocketTimeoutException . 这个异 常比较常见,socket 超时.一般有 2 个地方会抛出这个,一个是 connect 的 时 候 , 这 ...
- fopen的使用小记
整理自https://msdn.microsoft.com/zh-cn/library/t3ayayh1(VS.80).aspx errno, _doserrno, _sys_errlist, and ...
- webpack使用
Webpack是一个现代js应用的模块打包机.如果一个文件依赖另一个文件,webpack认为这就存在一个依赖关系.不管另一个文件是什么内容,image,css或js都被当作一个模块.Webpack从e ...