记录:编写卷积层和池化层,比较需要注意的细节就是边界问题,还有另外一个就是重叠池化的情况,这两个小细节比较重要,边界问题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编写的更多相关文章

  1. 从零开始编写深度学习库(五)Eigen Tensor学习笔记2.0

    1.extract_image_patches函数的使用: 假设Eigen::Tensor形状为(3,8,8,9),现在要对第二维.第三维根据size大小为(2,2),stride=(2,2),那么如 ...

  2. Kelp.Net是一个用c#编写的深度学习库

    Kelp.Net是一个用c#编写的深度学习库 基于C#的机器学习--c# .NET中直观的深度学习   在本章中,将会学到: l  如何使用Kelp.Net来执行自己的测试 l  如何编写测试 l  ...

  3. 30个深度学习库:按Python、C++、Java、JavaScript、R等10种语言分类

    30个深度学习库:按Python.C++.Java.JavaScript.R等10种语言分类 包括 Python.C++.Java.JavaScript.R.Haskell等在内的一系列编程语言的深度 ...

  4. Python机器学习库和深度学习库总结

    我们在Github上的贡献者和提交者之中检查了用Python语言进行机器学习的开源项目,并挑选出最受欢迎和最活跃的项目. 1. Scikit-learn(重点推荐) www.github.com/sc ...

  5. python之感知器-从零开始学深度学习

    感知器-从零开始学深度学习 未来将是人工智能和大数据的时代,是各行各业使用人工智能在云上处理大数据的时代,深度学习将是新时代的一大利器,在此我将从零开始记录深度学习的学习历程. 我希望在学习过程中做到 ...

  6. 人工智能不过尔尔,基于Python3深度学习库Keras/TensorFlow打造属于自己的聊天机器人(ChatRobot)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_178 聊天机器人(ChatRobot)的概念我们并不陌生,也许你曾经在百无聊赖之下和Siri打情骂俏过,亦或是闲暇之余与小爱同学谈 ...

  7. 深度学习库 SynapseML for .NET 发布0.1 版本

    2021年11月 微软开源一款简单的.多语言的.大规模并行的机器学习库 SynapseML(以前称为 MMLSpark),以帮助开发人员简化机器学习管道的创建.具体参见[1]微软深度学习库 Synap ...

  8. 64位Win7下安装并配置Python3的深度学习库:Theano

    注:本文全原创,作者:Noah Zhang  (http://www.cnblogs.com/noahzn/) 这两天在安装Python的深度学习库:Theano.尝试了好多遍,CMake.MinGW ...

  9. MXNet深度学习库简介

    MXNet深度学习库简介 摘要: MXNet是一个深度学习库, 支持C++, Python, R, Scala, Julia, Matlab以及JavaScript等语言; 支持命令和符号编程; 可以 ...

随机推荐

  1. Jquery10 高级事件

    学习要点: 1.模拟操作 2.命名空间 3.事件委托 4.on.off 和 one jQuery 不但封装了大量常用的事件处理,还提供了不少高级事件方便开发者使用.比如模拟用户触发事件.事件委托事件. ...

  2. MySQL优化具体

    1. 查询与索引优化分析 在优化MySQL时,通常需要对数据库进行分析,常见的分析手段有慢查询日志,profiling分析,EXPLAIN分析查询,以及show命令查询系统状态及系统变量,通过定位分析 ...

  3. Spring笔记2——Spring中Bean的装配

    1.引言 Spring中,对象无需自己负责查找或创建与其关联的其他对象,而是由容器负责把需要相互协作的对象引用赋予各个对象.创建应用对象之间的协作关系的行为通常称为装配(Wiring),这也是依赖注入 ...

  4. 读取Jar中的json文件

    现在操作json的jar 都是用的fastjson, 如果需要读取的json文件不在jar包里面,则可以这样获取到: String path = this.getClass().getClassLoa ...

  5. Nginx解决post请求405问题

    和工商银行的一个合作项目,对方客户端需要请求我们的一个静态页面,但是客户端发送过来的请求方法用的post,日志显示405错误(请求方法错误),正常一个静态页面直接用get请求就可以了,工行那边说写死了 ...

  6. linux下安装sphinx

    1.下载sphinx源码包 上面截图的这个网址   复制链接地址   在putty终端使用:wget http://sphinxsearch.com/files/sphinx-2.3.1-beta.t ...

  7. java的时间获取

    System类代表系统,系统级的很多属性和控制方法都放置在该类的内部.该类位于java.lang包. currentTimeMillis方法 public static long currentTim ...

  8. Restore IP Addresses,将字符串转换成ip地址

    问题描述: Given a string containing only digits, restore it by returning all possible valid IP address c ...

  9. 机器学习笔记—EM 算法

    EM 算法所面对的问题跟之前的不一样,要复杂一些. EM 算法所用的概率模型,既含有观测变量,又含有隐变量.如果概率模型的变量都是观测变量,那么给定数据,可以直接用极大似然估计法,或贝叶斯估计法来估计 ...

  10. Hibernate与 MyBatis的比较(转,留作以后细细钻研)

    最近做了一个Hibernate与MyBatis的对比总结,希望大家指出不对之处. 第一章     Hibernate与MyBatis Hibernate 是当前最流行的O/R mapping框架,它出 ...