从零开始编写深度学习库(五)PoolingLayer 网络层CPU编写
记录:编写卷积层和池化层,比较需要注意的细节就是边界问题,还有另外一个就是重叠池化的情况,这两个小细节比较重要,边界问题pad在反向求导的时候,由于tensorflow是没有计算的,另外一个比较烦人的是Eigen::Tensor的rowmajor、和colmajor问题,也很烦人。为了跟tensorflow做比较,一些边界处理上的细节,需要特别注意。
一、c++、maxpooling、avgpooling
#pragma once #include "config.h" #include <vector> enum PoolingMethod { max, avg }; class CPoolingLayer { public: CPoolingLayer(std::vector<int>pooling_shape,PoolingMethod pooling_method, int padding = 0) { m_hksize = pooling_shape[0]; m_wksize = pooling_shape[1]; m_hstride = pooling_shape[2]; m_wstride = pooling_shape[3]; m_padding = padding; m_pooling_method = pooling_method; }; ~CPoolingLayer() { }; //返回(bottom[0],bottom[1]/hstride*bottom[2]/wstride,hsize,wsize,bottom[3]) void CPoolingLayer::extract_image_patches(const Tensor4xf &bottom, Tensor5xf &patches) { //这个Eigen tensor类的extract_image_patches函数,由于有数据存储列排列、行排列两种不同的模式。 //在下面函数中,如果是采用rowmajor,下面的调用方式才是正确的 //不能采用bottom.extract_image_patches( m_hksize,m_wksize, m_hstride,m_wstride, 1, 1); //switch (m_padding_method) //{ //case valid: patches = bottom.extract_image_patches(m_hksize, m_wksize, m_hstride, m_wstride, 1, 1, Eigen::PADDING_VALID); //break; //case same: //patches = bottom.extract_image_patches(m_hksize, m_wksize, m_hstride, m_wstride, 1, 1, //Eigen::PADDING_SAME ); //break; //default: //break; //} } //根据stride、size等计算输出数据的维度形状 Eigen::DSizes<int, 4> CPoolingLayer::get_top_shape(const Tensor4xf&bottom) { Eigen::DSizes<int, 4>top_shape; top_shape[0] = bottom.dimension(0); //switch (m_padding_method) //{ //case valid: top_shape[1] = Eigen::divup(float(bottom.dimension(1) - m_hksize + 1), float(m_hstride)); top_shape[2] = Eigen::divup(float(bottom.dimension(2) - m_wksize + 1), float(m_wstride)); //break; //case same: //top_shape[1] = Eigen::divup(float(bottom.dimension(1)), float(m_hstride)); //top_shape[2] = Eigen::divup(float(bottom.dimension(2)), float(m_wstride)); //break; //default: //break; //} top_shape[3] = bottom.dimension(3); return top_shape; } //需要特别注意这边的均值池化,与tesorflow,在same模式下处理方式不同,tensorflow的在计算池化的时候, //不管有没有padding,padding值在计算池化操作都被忽略 // bottom(batch_size, input_height, input_width, input_channel); void CPoolingLayer::forward(const Tensor4xf&bottom,Tensor4xf&top, const Eigen::ThreadPoolDevice &device) { Tensor4xf pad_bottom; CBaseFunction::padding_forward(bottom, m_padding, m_padding, pad_bottom); Eigen::array<int, 2> reduction_dims{1,2};//第二维、第三维的大小等于(hksize、wksize) Eigen::DSizes<int, 4>post_reduce_dims=get_top_shape(pad_bottom); Tensor5xf patches; //(bottom[0], hsize, wsize,bottom[1] / hstride*bottom[2] / wstride, bottom[3]) extract_image_patches(pad_bottom, patches); Tensor3xf pooling(post_reduce_dims[0],post_reduce_dims[1]*post_reduce_dims[2],post_reduce_dims[3]); switch (m_pooling_method) { case avg: pooling.device(device) = patches.mean(reduction_dims);//对reduction_dims内对应的维度索引进行统计,比如统计第3、2 break; case max: pooling.device(device) = patches.maximum(reduction_dims);//最大池化 break; default: break; } top=pooling.reshape(post_reduce_dims); } //本函数主要用于索引解码,从一维索引到获取多维下标值。主要原因在于:max std::vector<int> CPoolingLayer::decode_index(std::vector<int>dim,int index) { std::vector<int>result; for (int i=0;i<5;i++) { int accu = 1; for (int j=5-1;j>i;j--) { accu *= dim[j]; } result.push_back(std::floor(index / accu)); index = index%accu; } return result; } //主要是重叠池化的时候,反向求导的时候是微分值累加。 void CPoolingLayer::maxpooling_backward(const Tensor4xf&bottom,const Tensor4xf&dtop,Tensor4xf&dbottom) { Tensor4xf pad_bottom; CBaseFunction::padding_forward(bottom, m_padding, m_padding, pad_bottom); Tensor5xf patches; extract_image_patches(pad_bottom, patches); Tensor4xf dpad_bottom(pad_bottom.dimension(0), pad_bottom.dimension(1), pad_bottom.dimension(2), pad_bottom.dimension(3)); dpad_bottom.setZero(); Eigen::DSizes<int, 4>post_reduce_dims = get_top_shape(pad_bottom); Eigen::array<Eigen::DenseIndex, 2> reduce_dims{ 1,2 }; auto index_tuples = patches.index_tuples(); Eigen::Tensor<Eigen::Tuple<Eigen::DenseIndex, float>, 3, Eigen::internal::traits<Tensor5xf>::Layout> reduced_by_dims; reduced_by_dims = index_tuples.reduce(reduce_dims, Eigen::internal::ArgMaxTupleReducer<Eigen::Tuple<Eigen::DenseIndex, float> >()); int batch = dtop.dimension(0); int height = dtop.dimension(1); int widht = dtop.dimension(2); int channel = dtop.dimension(3); bool isColMajor = (Eigen::internal::traits<Tensor4xf>::Layout ==Eigen::ColMajor); for (int b= 0; b < batch; b++) { for (int h = 0; h< height; h++) { for (int w = 0; w < widht; w++) { for (int c = 0; c <channel ; c++) { const auto &dmax_element = dtop(b, h, w, c); int max_inpatch_height; int max_inpatch_width; if (isColMajor) {//如果是列主元存储,那么维度的序号刚好相反,由(b,h,w,c)变成(c,w,h,b) const Eigen::Tuple<Eigen::DenseIndex, float>&v = reduced_by_dims(c*widht*height*batch + w*height*batch + h*batch + b); int index_in_patch = v.first % (m_wksize*m_hksize);//最大值在每个块中的索引 max_inpatch_height = index_in_patch%m_hksize; max_inpatch_width = index_in_patch / m_hksize; } else{ const Eigen::Tuple<Eigen::DenseIndex, float>&v = reduced_by_dims(b*height*widht*channel + h*widht*channel + w*channel + c); int index_in_patch = v.first % (m_wksize*m_hksize);//最大值在每个块中的索引 max_inpatch_height = index_in_patch/m_wksize; max_inpatch_width = index_in_patch % m_wksize; } int patch_height = h*m_hstride + max_inpatch_height; int patch_width = w*m_wstride + max_inpatch_width; dpad_bottom(b, patch_height, patch_width, c) += dmax_element; /*if (patch_height < dbottom.dimension(1) && patch_width < dbottom.dimension(2)) { dbottom(b, patch_height, patch_width, c) += dmax_element; } else { std::cout << "out of range" << std::endl; }*/ } } } } CBaseFunction::padding_backward(dpad_bottom, m_padding, m_padding, dbottom); } //均值池化,也可以看成是卷积 void CPoolingLayer::avgpooling_backward(const Tensor4xf&dtop, Tensor4xf&dbottom) { Tensor4xf mean_coffe = dtop*(1.f / (m_wksize*m_hksize));//均值池化反向求导要除以均值系数 for (int b=0;b<mean_coffe.dimension(0);b++) { for (int h=0;h<mean_coffe.dimension(1);h++) { for (int w=0;w<mean_coffe.dimension(2);w++) { for (int c=0;c<mean_coffe.dimension(3);c++) { const auto &mean_element= mean_coffe(b, h, w, c); for (int kh=0;kh<m_hksize;kh++) { for (int kw=0;kw<m_wksize;kw++) { int patch_height = h*m_hstride + kh - m_padding; int patch_width = w*m_wstride + kw - m_padding; if (patch_height>=0 &&patch_width>=0&&patch_width<dbottom.dimension(2)&&patch_height<dbottom.dimension(1)) { dbottom(b, patch_height, patch_width,c) += mean_element; } } } } } } } //CBaseFunction::padding_backward(dpad_bottom, m_padding_method, m_padding_method, dbottom); } void CPoolingLayer::backward(const Tensor4xf&bottom,const Tensor4xf&dtop, Tensor4xf&dbottom, const Eigen::ThreadPoolDevice &device) { dbottom.setZero(); //计算第2、3维的降维 switch (m_pooling_method) { case max: maxpooling_backward(bottom, dtop, dbottom); break; case avg: avgpooling_backward(dtop, dbottom); break; default: break; } } private: int m_hksize;//池化块的长宽 int m_wksize; int m_hstride;//池化步长 int m_wstride; int m_padding;//边界处理方法 PoolingMethod m_pooling_method;//池化方法:均值池化、最大池化等 }; class CPoolingLayer_test { public: static void CPoolingLayer_test::test() { Eigen::ThreadPool *tp = new Eigen::ThreadPool(8); Eigen::ThreadPoolDevice device(tp, 8); int batch_size = 1; int input_channel =1; int input_height =5; int input_width =5; int kenel_height = 3; int kenel_widht = 2; int khstride =2; int kwstride = 3; int pad = 0; Tensor4xf bottom(batch_size, input_height, input_width, input_channel); int count = 0; for (int i=0;i<batch_size;i++) { for (int j=0;j<input_height;j++) { for (int k=0;k<input_width;k++) { for (int h=0;h<input_channel;h++) { bottom(i, j, k, h) = 0.1f*count; count++; } } } } Tensor1xf label_1d(batch_size); for (int i = 0; i < batch_size; i++) { label_1d(i) = i; } //第一层:pooling层 CPoolingLayer layer({kenel_height,kenel_widht,khstride,kwstride },PoolingMethod::max,pad); Tensor4xf top; layer.forward(bottom, top,device); Tensor2xf top_flatten; CBaseFunction::flatten(top, top_flatten); //第二层:sotfmax网络层 Tensor2xf one_hot; CBaseFunction::onehot(label_1d, top_flatten.dimension(1), one_hot); Tensor2xf dtop_flatten(top_flatten); float loss = CBaseFunction::softmax_with_loss(top_flatten, one_hot, dtop_flatten, device); Tensor4xf dtop; CBaseFunction::reshape_like(dtop_flatten, top, dtop); Tensor4xf dbottom(bottom); layer.backward(bottom, dtop,dbottom,device); //Tensor4rf dbottom_swap = dbottom.swap_layout(); std::cout << "***************forward************" << std::endl; //CBaseFunction::print_shape(one_hot); CBaseFunction::print_shape(dbottom); CBaseFunction::print_element(dbottom); //std::cout << "bottom" << bottom<< std::endl; //std::cout << "top" << top << std::endl; //std::cout << "dbottom" << dbottom << std::endl; std::cout << "loss" << loss << std::endl; //std::cout << "dbottom" << dbottom << std::endl; //std::cout << "dtop" << top << std::endl; } };
二、tensorflow 验证结果:
import tensorflow as tf batch_size = 1 input_channel = 1 input_height =5 input_width = 5 kenel_height =3 kenel_widht =2 khstride =2 kwstride=3 pad=0 bottom=tf.constant([i*0.1 for i in range(batch_size*input_channel*input_height*input_width)],shape=(batch_size,input_height,input_width,input_channel),dtype=tf.float32) pool1=tf.nn.max_pool(tf.pad(bottom,[[0,0],[pad,pad],[pad,pad],[0,0]]),[1,kenel_height,kenel_widht,1],strides=[1,khstride,kwstride,1],padding='VALID') pool_flatten=tf.reshape(pool1,[batch_size,-1]) label=tf.constant([i for i in range(batch_size)]) one_hot=tf.one_hot(label,pool_flatten.get_shape().as_list()[1]) predicts=tf.nn.softmax(pool_flatten) loss =-tf.reduce_mean(one_hot * tf.log(predicts)) #打印相关变量,梯度等,验证是否与c++结果相同 dbottom,dpool1=tf.gradients(loss,[bottom,pool1]) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print (sess.run([dbottom])) print (sess.run(loss)) #print ('dbottom_data',dbottom_data)
从零开始编写深度学习库(五)PoolingLayer 网络层CPU编写的更多相关文章
- 从零开始编写深度学习库(五)Eigen Tensor学习笔记2.0
1.extract_image_patches函数的使用: 假设Eigen::Tensor形状为(3,8,8,9),现在要对第二维.第三维根据size大小为(2,2),stride=(2,2),那么如 ...
- Kelp.Net是一个用c#编写的深度学习库
Kelp.Net是一个用c#编写的深度学习库 基于C#的机器学习--c# .NET中直观的深度学习 在本章中,将会学到: l 如何使用Kelp.Net来执行自己的测试 l 如何编写测试 l ...
- 30个深度学习库:按Python、C++、Java、JavaScript、R等10种语言分类
30个深度学习库:按Python.C++.Java.JavaScript.R等10种语言分类 包括 Python.C++.Java.JavaScript.R.Haskell等在内的一系列编程语言的深度 ...
- Python机器学习库和深度学习库总结
我们在Github上的贡献者和提交者之中检查了用Python语言进行机器学习的开源项目,并挑选出最受欢迎和最活跃的项目. 1. Scikit-learn(重点推荐) www.github.com/sc ...
- python之感知器-从零开始学深度学习
感知器-从零开始学深度学习 未来将是人工智能和大数据的时代,是各行各业使用人工智能在云上处理大数据的时代,深度学习将是新时代的一大利器,在此我将从零开始记录深度学习的学习历程. 我希望在学习过程中做到 ...
- 人工智能不过尔尔,基于Python3深度学习库Keras/TensorFlow打造属于自己的聊天机器人(ChatRobot)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_178 聊天机器人(ChatRobot)的概念我们并不陌生,也许你曾经在百无聊赖之下和Siri打情骂俏过,亦或是闲暇之余与小爱同学谈 ...
- 深度学习库 SynapseML for .NET 发布0.1 版本
2021年11月 微软开源一款简单的.多语言的.大规模并行的机器学习库 SynapseML(以前称为 MMLSpark),以帮助开发人员简化机器学习管道的创建.具体参见[1]微软深度学习库 Synap ...
- 64位Win7下安装并配置Python3的深度学习库:Theano
注:本文全原创,作者:Noah Zhang (http://www.cnblogs.com/noahzn/) 这两天在安装Python的深度学习库:Theano.尝试了好多遍,CMake.MinGW ...
- MXNet深度学习库简介
MXNet深度学习库简介 摘要: MXNet是一个深度学习库, 支持C++, Python, R, Scala, Julia, Matlab以及JavaScript等语言; 支持命令和符号编程; 可以 ...
随机推荐
- 20135320赵瀚青LINUX第十八章读书笔记
概述:调试工作艰难是内核级开发区别于用户级开发的一个显著特点 18.1准备开始 内核调试往往是一个令人挠头不已的漫长过程.幸运的是,在这些费劲的问题中也有不少比较简单而且容易消灭的小bug,运气好你可 ...
- kafka运行错误:提示找不到或者无法加载主类错误解决方法
kafaka版本:kafka_2.11-1.1.0原因有2个:1 目录不能有空格 D:\Soft\kafka_2.11-1.1.0 , 放在Program Files目录中一直有问题2 修改D ...
- 从零开始玩转JMX(三)——Model MBean
Model MBean 相对于Standard MBean,Model MBean更加灵活.如果我们不能修改已有的Java类,那么使用Model MBean是不错的选择. Model MBean也是一 ...
- git gc内存错误的解决方案
Auto packing the repository for optimum performance. You may alsorun "git gc" manually. Se ...
- SSH免密码登录Linux
如果两台linux之间交互频繁,但是每次交互如果都需要输入密码,就会很麻烦,通过配置SSH就可以解决这一问题 下面就说下配置流程(下面流程在不同机器上全部操作一边) 1)cd ~到这个目录中 2)ss ...
- 推荐一个JavaScript触发器插件,可通过指定频次、指定时间内触发指定的处理函数
推荐一个JavaScript触发器插件js-trigger js-trigger是一个JavaScript触发器插件,可通过指定频次.指定时间内触发指定的处理函数 https://tanwei-cc. ...
- AVL树 - 学习笔记
2017-08-29 14:35:55 writer:pprp AVL树就是带有平衡条件的二叉查找树.每个节点的左子树和右子树高度相差最多为1的二叉查找树 空树的高度定为-1 对树的修正称为旋转 对内 ...
- PHP数据库链接类(PDO+Access)
PHP PDO Access链接 class DbHelpClass { private $conn; private $qxId; private $ret; function __construc ...
- 关于IIS权限问题(Selenium WebDriver调用出错记录)
本地VS调试过程中用Selenium WebDriver打开FF浏览器可以正常工作,项目部署至IIS后请求调用浏览器一直提示超时,异常如下: 因为本地调试可以成功,首先排除组件版本问题和浏览器兼容问题 ...
- 常见ADB命令
常见ADB命令 比如说知道了push和pull操作,就可以实现一个简单的手机助手. 如果有多台设备,操作的时候要指定设备. -s加设备名称