记录:编写卷积层和池化层,比较需要注意的细节就是边界问题,还有另外一个就是重叠池化的情况,这两个小细节比较重要,边界问题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. 20135320赵瀚青LINUX第十八章读书笔记

    概述:调试工作艰难是内核级开发区别于用户级开发的一个显著特点 18.1准备开始 内核调试往往是一个令人挠头不已的漫长过程.幸运的是,在这些费劲的问题中也有不少比较简单而且容易消灭的小bug,运气好你可 ...

  2. kafka运行错误:提示找不到或者无法加载主类错误解决方法

    kafaka版本:kafka_2.11-1.1.0原因有2个:1  目录不能有空格   D:\Soft\kafka_2.11-1.1.0 , 放在Program Files目录中一直有问题2  修改D ...

  3. 从零开始玩转JMX(三)——Model MBean

    Model MBean 相对于Standard MBean,Model MBean更加灵活.如果我们不能修改已有的Java类,那么使用Model MBean是不错的选择. Model MBean也是一 ...

  4. git gc内存错误的解决方案

    Auto packing the repository for optimum performance. You may alsorun "git gc" manually. Se ...

  5. SSH免密码登录Linux

    如果两台linux之间交互频繁,但是每次交互如果都需要输入密码,就会很麻烦,通过配置SSH就可以解决这一问题 下面就说下配置流程(下面流程在不同机器上全部操作一边) 1)cd ~到这个目录中 2)ss ...

  6. 推荐一个JavaScript触发器插件,可通过指定频次、指定时间内触发指定的处理函数

    推荐一个JavaScript触发器插件js-trigger js-trigger是一个JavaScript触发器插件,可通过指定频次.指定时间内触发指定的处理函数 https://tanwei-cc. ...

  7. AVL树 - 学习笔记

    2017-08-29 14:35:55 writer:pprp AVL树就是带有平衡条件的二叉查找树.每个节点的左子树和右子树高度相差最多为1的二叉查找树 空树的高度定为-1 对树的修正称为旋转 对内 ...

  8. PHP数据库链接类(PDO+Access)

    PHP PDO Access链接 class DbHelpClass { private $conn; private $qxId; private $ret; function __construc ...

  9. 关于IIS权限问题(Selenium WebDriver调用出错记录)

    本地VS调试过程中用Selenium WebDriver打开FF浏览器可以正常工作,项目部署至IIS后请求调用浏览器一直提示超时,异常如下: 因为本地调试可以成功,首先排除组件版本问题和浏览器兼容问题 ...

  10. 常见ADB命令

    常见ADB命令 比如说知道了push和pull操作,就可以实现一个简单的手机助手. 如果有多台设备,操作的时候要指定设备.  -s加设备名称