C++元编程——CNN卷积层实现

本文介绍了使用C++元编程技术实现卷积神经网络(CNN)的卷积层。通过引用相关文献,作者展示了如何进行卷积导数计算,并提供了试验代码。虽然部分辅助函数(如矩阵间隔填充和周围扩充)因编译时间问题未采用元编程,但核心的卷积运算和规格计算静态函数已实现。此外,还更新了激活函数模块,添加了计算sigmoid的函数。
摘要由CSDN通过智能技术生成

CNN包括RELU层,池化层,卷积层,全连接层。具体的理论基础参见:CNN理论基础

本次对卷积层进行了实践,其指导文献参见:卷积导数计算

先上试验代码:

#include <stdio.h>
#include <conio.h>
#include <iostream>
#include <vector>


#include "bp.hpp"
#include "activate_function.hpp"
#include "restricked_boltzman_machine.hpp"
#include "dbn.hpp"
#include "convolution_layer.hpp"


int main(int argc, char** argv)
{
#if 1
	using conv_type = conv_layer<7, 7, 3, 3, 2, 2, XavierGaussian>;
	conv_type cl;
	mat<7, 7, double> mt_origin({
		1,2,3,4,5,6,7,
		1,2,3,4,5,6,7,
		1,2,3,4,5,6,7,
		1,2,3,4,5,6,7,
		1,2,3,4,5,6,7,
		1,2,3,4,5,6,7,
		1,2,3,4,5,6,7
		});
	conv_type::ret_type mt_expected({
		1,2,3,
		4,5,6,
		7,8,9
		});
	mt_expected = mt_expected.t();
	for (int i = 0; ; ++i)
	{
		auto mt_cl_forward = (cl.forward(mt_origin));
		auto mt_delta = (mt_cl_forward - mt_expected);
		//mt_delta.print();
		auto mt_cl_backward = cl.backward(mt_delta);
		if (i % 1000 == 0)
		{
			mt_cl_forward.print();
		}
	}
	_getch();
    return 0;
}

结果是一如既往令人满意的,0.001学习率基本算个2w次能得到结果:

 

下面就是具体实现了(convolution.hpp):

#ifndef _CONVOLUTION_LAYER_HPP_
#define _CONVOLUTION_LAYER_HPP_
#include "mat.hpp"
#include "base_function.hpp"

/* 卷积层 */
template<int input_row, int input_col, int tpl_row, int tpl_col, int row_step, int col_step, typename tpl_init_method, typename val_t = double>
struct conv_layer 
{
	using input_type = mat<input_row, input_col, val_t>;
	using ret_type = mat<get_step_inner_size(input_row, tpl_row, row_step), get_step_inner_size(input_col, tpl_col, col_step), val_t>;
	using pad_type = mat<input_row + get_pad_size(input_row, tpl_row, row_step)
		, input_col + get_pad_size(input_col, tpl_col, col_step), val_t>;
	using pad_size = pad_size_t<input_row, input_col, tpl_row, tpl_col, row_step, col_step>;

	mat<tpl_row, tpl_col, val_t> mt_tpl;
	pad_type mt_input;
	ret_type mt_bias;
	double lr;

	conv_layer():lr(0.001)
	{
		weight_initilizer<tpl_init_method>::cal(mt_tpl);
	}

	inline ret_type forward(const input_type& mt)
	{
		mt_input = mt.pad<pad_size::top, pad_size::left, pad_size::right, pad_size::bottom>();
		return inner_conv<row_step, col_step>(mt_input, mt_tpl) + mt_bias;
	}

	inline input_type backward(const ret_type& mt_delta) 
	{
		auto mt_delta_span = mt_delta.span<row_step - 1, col_step - 1>();			// 采用了步长运算,等于有一些没计算,所以反向传播时候的贡献是0
		using ret_pad_type = decltype(mt_delta_span);
		/* 计算反向传播误差 */
		/* 计算返回阵需要pad的大小 */
		constexpr int target_r = tpl_row + pad_type::r - 1;
		constexpr int target_c = tpl_col + pad_type::c - 1;
		constexpr int pad_top = (target_r - ret_pad_type::r) / 2;
		constexpr int pad_left = (target_c - ret_pad_type::c) / 2;
		constexpr int pad_right = (target_c - ret_pad_type::c) - pad_left;
		constexpr int pad_bottom = (target_r - ret_pad_type::r) - pad_top;
		auto mt_delta_span_pad = mt_delta_span.pad<pad_top, pad_left, pad_right, pad_bottom>();
		auto mt_tpl_rot = mt_tpl.rot180();
		auto mt_ret_pad = inner_conv<1, 1>(mt_delta_span_pad, mt_tpl_rot);
		input_type mt_ret;
		mt_ret.assign<-1 * pad_size::top, -1 * pad_size::left>(mt_ret_pad);			// 剪除外边
		/* 计算卷积核更新 */
		auto mt_update = inner_conv<1, 1>(mt_input, mt_delta_span);
		mt_update = mt_update / mt_update.max_abs();
		mt_tpl = mt_tpl - lr * mt_update;				// 更新模板
		mt_bias = mt_bias - lr * mt_delta;
		/* 将模板均值置0,最大波动范围为1 */
		double d_mean = mt_tpl.sum() / (tpl_row * tpl_col);
		mt_tpl = mt_tpl - d_mean;
		mt_tpl = mt_tpl / mt_tpl.max_abs();

		return mt_ret;
	}
};

#endif

矩阵运算增加了一些东西,比如矩阵间隔填充函数span,以及周围扩充函数pad,这些函数没有使用元编程,因为实在是太耗费编译时间了,而且还可能导致编译器内存太大导致编译不起来。mat.hpp代码如下:

#ifndef _MAT_HPP_
#define _MAT_HPP_
#include <climits>

#include <map>
#include <boost/pool/pool.hpp>

template<int i_size, typename val_t>
struct mat_m
{
	static boost::pool<> s_pool;
	val_t* p;
	mat_m() :p(nullptr)
	{
		//p = (val_t*)malloc(sz * sizeof(val_t));
		p = (val_t*)(s_pool.malloc());
		for (int i = 0; i < i_size; ++i)
		{
			p[i] = 0;
		}
	}
	~mat_m()
	{
		if (p)
		{
			//free(p);
			s_pool.free(p);
		}
	}
	val_t& get(const int& len_1d, const int& i_1d_idx, const int& i_2d_idx)
	{
		val_t& ret = p[i_2d_idx + len_1d
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

腾昵猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值