使用C++编写卷积神经网络(一)

鉴于用python写神经网络、卷积神经网络的话,不利于框架直接用numpy等来完成,速度实在是慢的吓人,所以改用c++来写。c++的话什么操作基本都要自己定义。由于卷积神经网络主要针对图像类,故最好定义一个适用于图像类的矩阵类,我这边使用了网上一个开源的用c++写好的卷积神经网络,使用了里面定义好的矩阵类,如下。这个类个人觉得非常好用,故写成博客,收藏一波,方便以后使用时候查找。

首先导入需要的模块:

#include <math.h>
#include <string.h>
#include <string>
#include <cstdlib>
#include <random>
#include <algorithm> 

然后定义一个矩阵类,主要用于图像处理方面:
里面包括了矩阵的各种属性,宽、高、通道数,大小,元素个数等,以及对矩阵的各种操作,包括padding、翻转等,还有矩阵之间的各种运算,+、-、*、+=等。。。

namespace mojo
{

enum pad_type { zero = 0, edge = 1, median_edge = 2 };

// matrix class ---------------------------------------------------
// should use opencv if available
//

class matrix
{
	int _size;
	int _capacity;
	float *_x_mem;
	void delete_x() { delete[] _x_mem; x = NULL;  _x_mem = NULL; }
	// 4 extra for alignment and 4 for 3 padding for SSE
	//float *new_x(const int size) { _x_mem = new float[size + 4+3];  x = (float *)(((uintptr_t)_x_mem + 16) & ~(uintptr_t)0x0F); return x; }
	// avx mem aligment
	float *new_x(const int size) { _x_mem = new float[size + 8 + 7];  x = (float *)(((uintptr_t)_x_mem + 32) & ~(uintptr_t)0x1F); return x; }
public:
	std::string _name;
	int cols, rows, chans;
	int chan_stride;
	int chan_aligned;
	float *x;
	// size must be divisible by 8 for AVX
	virtual int calc_chan_stride(int w, int h)
	{
		if (chan_aligned)
		{
			int s = w*h;
			const int remainder = s % 8;
			if (remainder > 0) s += 8 - remainder;
			return s;
		}
		else return w*h;
	}

	matrix( ): cols(0), rows(0), chans(0), _size(0), _capacity(0), chan_stride(0), x(NULL), chan_aligned(0)/*, empty_chan(NULL)*/{}


	matrix( int _w, int _h, int _c=1, const float *data=NULL, int align_chan=0): cols(_w), rows(_h), chans(_c)
	{
		chan_aligned = align_chan;
		chan_stride = calc_chan_stride(cols, rows);
		_size= chan_stride*chans; _capacity=_size; x = new_x(_size);
		if(data!=NULL) memcpy(x,data,_size*sizeof(float));
	}

	// copy constructor - deep copy
	matrix( const matrix &m) : cols(m.cols), rows(m.rows), chan_aligned(m.chan_aligned), chans(m.chans), chan_stride(m.chan_stride), _size(m._size), _capacity(m._size)   {x = new_x(_size); memcpy(x,m.x,sizeof(float)*_size); /*empty_chan = new unsigned char[chans]; memcpy(empty_chan, m.empty_chan, chans);*/} // { v=m.v; x=(float*)v.data();}
	// copy and pad constructor
	matrix( const matrix &m, int pad_cols, int pad_rows, mojo::pad_type padding= mojo::zero, int threads=1) : cols(m.cols), rows(m.rows), chans(m.chans), chan_aligned(m.chan_aligned), chan_stride(m.chan_stride), _size(m._size), _capacity(m._size)
	{
		x = new_x(_size); memcpy(x, m.x, sizeof(float)*_size);
		*this = pad(pad_cols, pad_rows, padding, threads);
	}

	~matrix() { if (x) delete_x(); }
	
	matrix get_chans(int start_channel, int num_chans=1) const
	{
		return matrix(cols,rows,num_chans,&x[start_channel*chan_stride]);
	}


	// if edge_pad==0, then the padded area is just 0. 
	// if edge_pad==1 it fills with edge pixel colors
	// if edge_pad==2 it fills with median edge pixel color
	matrix pad(int dx, int dy, mojo::pad_type edge_pad = mojo::zero, int threads=1) const
	{
		return pad(dx, dy, dx, dy, edge_pad, threads);
	}
	matrix pad(int dx, int dy, int dx_right, int dy_bottom, mojo::pad_type edge_pad = mojo::zero, int threads=1) const
	{
		matrix v(cols+dx+dx_right,rows+dy+dy_bottom,chans);//,NULL,this->chan_aligned);
		v.fill(0);
	
		//float *new_x = new float[chans*w*h]; 
#pragma omp parallel for num_threads(threads)
		for(int k=0; k<chans; k++)
		{
			const int v_chan_offset=k*v.chan_stride;
			const int chan_offset=k*chan_stride;
			// find median color of perimeter
			float median = 0.f;
			if (edge_pad == mojo::median_edge)
			{
				int perimeter = 2 * (cols + rows - 2);
				std::vector<float> d(perimeter);
				for (int i = 0; i < cols; i++)
				{
					d[i] = x[i+ chan_offset]; d[i + cols] = x[i + cols*(rows - 1)+ chan_offset];
				}
				for (int i = 1; i < (rows - 1); i++)
				{
					d[i + cols * 2] = x[cols*i+ chan_offset];
					// file from back so i dont need to cal index
					d[perimeter - i] = x[cols - 1 + cols*i+ chan_offset];
				}

				std::nth_element(d.begin(), d.begin() + perimeter / 2, d.end());
				median = d[perimeter / 2];
				//for (int i = 0; i < v.rows*v.cols; i++) v.x[v_chan_offset + i] = solid_fill;
			}

			for(int j=0; j<rows; j++)
			{
				memcpy(&v.x[dx+(j+dy)*v.cols+v_chan_offset], &x[j*cols+chan_offset], sizeof(float)*cols);
				if(edge_pad== mojo::edge)
				{
					// do left/right side
					for(int i=0; i<dx; i++) v.x[i+(j+dy)*v.cols+v_chan_offset]=x[0+j*cols+chan_offset];
					for (int i = 0; i<dx_right; i++) v.x[i + dx + cols + (j + dy)*v.cols + v_chan_offset] = x[(cols - 1) + j*cols + chan_offset];
				}
				else if (edge_pad == mojo::median_edge)
				{
					for (int i = 0; i < dx; i++) v.x[i + (j + dy)*v.cols + v_chan_offset] = median;
					for (int i = 0; i < dx_right; i++) v.x[i + dx + cols + (j + dy)*v.cols + v_chan_offset] = median;
				}
			}
			// top bottom pad
			if(edge_pad== mojo::edge)
			{
				for(int j=0; j<dy; j++)	memcpy(&v.x[(j)*v.cols+v_chan_offset],&v.x[(dy)*v.cols+v_chan_offset], sizeof(float)*v.cols);
				for (int j = 0; j<dy_bottom; j++) memcpy(&v.x[(j + dy + rows)*v.cols + v_chan_offset], &v.x[(rows - 1 + dy)*v.cols + v_chan_offset], sizeof(float)*v.cols);
			}
			if (edge_pad == mojo::median_edge)
			{
				for (int j = 0; j<dy; j++)	
					for (int i = 0; i<v.cols; i++) 
						v.x[i + j*v.cols + v_chan_offset] = median;
				for (int j = 0; j<dy_bottom; j++) 
					for (int i = 0; i<v.cols; i++) 
						v.x[i + (j + dy + rows)*v.cols + v_chan_offset] = median;
			}
		}

		return v;
	}

	matrix crop(int dx, int dy, int w, int h, int threads=1) const
	{
		matrix v(w,h,chans);

#pragma omp parallel for num_threads(threads)
		for(int k=0; k<chans; k++)
		{
			for(int j=0; j<h; j++)
			{
				memcpy(&v.x[j*w+k*v.chan_stride], &x[dx+(j+dy)*cols+k*chan_stride], sizeof(float)*w);
			}
		}

		return v;
	}

	mojo::matrix shift(int dx, int dy, mojo::pad_type edge_pad=mojo::zero)
	{
		int orig_cols=cols;
		int orig_rows=rows;
		int off_x=abs(dx);
		int off_y=abs(dy);

		mojo::matrix shifted= pad(off_x, off_y, edge_pad);

		return shifted.crop(off_x-dx, off_y-dy,orig_cols,orig_rows);
	}

	mojo::matrix flip_cols()
	{
		mojo::matrix v(cols,rows,chans);
		for(int k=0; k<chans; k++)
			for(int j=0; j<rows; j++)
				for(int i=0; i<cols; i++)
					v.x[i+j*cols+k*chan_stride]=x[(cols-i-1)+j*cols+k*chan_stride];

		return v;
	}
	mojo::matrix flip_rows()
	{
		mojo::matrix v(cols, rows, chans);
		
		for (int k = 0; k<chans; k++)
			for (int j = 0; j<rows; j++)
				memcpy(&v.x[(rows-1-j)*cols + k*chan_stride],&x[j*cols + k*chan_stride], cols*sizeof(float));

		return v;
	}

	void clip(float min, float max)
	{
		int s = chan_stride*chans;
		for (int i = 0; i < s; i++)
		{
			if (x[i] < min) x[i] = min;
			if (x[i] > max) x[i]=max;
		}
	}


	void min_max(float *min, float *max, int *min_i=NULL, int *max_i=NULL)
	{
		int s = rows*cols;
		int mini = 0;
		int maxi = 0; 
		for (int c = 0; c < chans; c++)
		{
			const int t = chan_stride*c;
			for (int i = t; i < t+s; i++)
		{
			if (x[i] < x[mini]) mini = i;
			if (x[i] > x[maxi]) maxi = i;
		}
		}
		*min = x[mini];
		*max = x[maxi];
		if (min_i) *min_i = mini;
		if (max_i) *max_i = maxi;
	}

	float mean()
	{
		const int s = rows*cols;
		int cnt = 0;// channel*s;
		float average = 0;
		for (int c = 0; c < chans; c++)
		{
			const int t = chan_stride*c;
			for (int i = 0; i < s; i++)
				average += x[i + t];
		}
		average = average / (float)(s*chans);
		return average;
	}
	float remove_mean(int channel)
	{
		int s = rows*cols;
		int offset = channel*chan_stride;
		float average=0;
		for(int i=0; i<s; i++) average+=x[i+offset];		
		average= average/(float)s;
		for(int i=0; i<s; i++) x[i+offset]-=average;		
		return average;
	}

	float remove_mean()
	{
		float m=mean();
		int s = chan_stride*chans;
		//int offset = channel*s;
		for(int i=0; i<s; i++) x[i]-=m;		
		return m;
	}
	void fill(float val) { for(int i=0; i<_size; i++) x[i]=val; 
	}
	void fill_random_uniform(float range)
	{
		std::mt19937 gen(0);
		std::uniform_real_distribution<float> dst(-range, range);
		for (int i = 0; i<_size; i++) x[i] = dst(gen);
	}
	void fill_random_normal(float std)
	{
		std::mt19937 gen(0);
		std::normal_distribution<float> dst(0, std);
		for (int i = 0; i<_size; i++) x[i] = dst(gen);
	}


	// deep copy
	inline matrix& operator =(const matrix &m)
	{
		resize(m.cols, m.rows, m.chans, m.chan_aligned);
		memcpy(x,m.x,sizeof(float)*_size);
//		memcpy(empty_chan, m.empty_chan, chans);
		return *this;
	}

	int  size() const {return _size;} 
	
	void resize(int _w, int _h, int _c, int align_chans=0) { 
		chan_aligned = align_chans;
		int new_stride = calc_chan_stride(_w,_h);
		int s = new_stride*_c;
		if(s>_capacity) 
		{ 
			if(_capacity>0) delete_x(); _size = s; _capacity=_size; x = new_x(_size);
		}
		cols = _w; rows = _h; chans = _c; _size = s; chan_stride = new_stride; 
	} 
	
	// dot vector to 2d mat
	inline matrix dot_1dx2d(const matrix &m_2d) const
	{
		mojo::matrix v(m_2d.rows, 1, 1);
		for(int j=0; j<m_2d.rows; j++)	v.x[j]=dot(x,&m_2d.x[j*m_2d.cols],_size);
		return v;
	}

	// +=
	inline matrix& operator+=(const matrix &m2){
	  for(int i = 0; i < _size; i++) x[i] += m2.x[i];
	  return *this;
	}
	// -=
	inline matrix& operator-=(const matrix &m2) {
		for (int i = 0; i < _size; i++) x[i] -= m2.x[i];
		return *this;
	}
	// *= float
	inline matrix operator *=(const float v) {
		for (int i = 0; i < _size; i++) x[i] = x[i] * v;
		return *this;
	}


	inline matrix operator *=(const matrix &v) {
		for (int i = 0; i < _size; i++) x[i] = x[i] * v.x[i];
		return *this;
	}
	inline matrix operator *(const matrix &v) {
		matrix T(cols, rows, chans);
		for (int i = 0; i < _size; i++) T.x[i] = x[i] * v.x[i];
		return T;
	}
	// * float
	inline matrix operator *(const float v) {
		matrix T(cols, rows, chans);
		for (int i = 0; i < _size; i++) T.x[i] = x[i] * v;
		return T;
	}

	// + float
	inline matrix operator +(const float v) {
		matrix T(cols, rows, chans);
		for (int i = 0; i < _size; i++) T.x[i] = x[i] + v;
		return T;
	}

	// +
	inline matrix operator +(matrix m2)
	{
		matrix T(cols,rows,chans);
		for(int i = 0; i < _size; i++) T.x[i] = x[i] + m2.x[i]; 
		return T;
	}

	// -
	inline matrix operator -(matrix m2)
	{
		matrix T(cols, rows, chans);
		for (int i = 0; i < _size; i++) T.x[i] = x[i] - m2.x[i];
		return T;
	}


};



}// namespace

  • 7
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 搭建卷积神经网络需要以下步骤: 1. 定义输入数据:卷积神经网络的输入通常是图像数据。 2. 初始化权重:权重是网络的核心部分,需要随机初始化。 3. 卷积层:卷积层通过卷积核对输入数据进行处理,生成卷积层的特征图。 4. 池化层:池化层通过池化操作对卷积层特征图进行下采样。 5. 全连接层:全连接层把池化层的输出压缩为一个向量,并对其进行处理。 6. 输出层:输出层通过softmax函数对全连接层的结果进行分。 7. 定义损失函数:损失函数用于评估网络的性能,常用的损失函数包括交叉熵损失。 8. 优化器:优化器用于更新网络的参数,常用的优化器包括SGD和Adam。 9. 训练网络:通过不断地训练网络,可以使得网络在训练数据上的性能越来越好。 这些步骤可以使用C语言实现。如果您还不熟悉C语言,建议先学习一些C语言的基础知识,然 ### 回答2: 要用C语言来搭建卷积神经网络,需要遵循一些步骤和原则。 首先,我们需要定义卷积神经网络的结构和层。在C语言中,可以使用结构体来定义一个层和网络的结构。每个层通常包含输入、权重、偏置、激活函数等组成部分。 接下来,我们需要定义函数来执行卷积操作和池化操作。卷积操作需要在输入数据和权重之间进行计算,并使用激活函数对计算结果进行处理。池化操作则是在卷积后对输出进行降采样。这些函数需要按照卷积和池化的步骤来进行编码。 然后,我们需要实现前向传播和反向传播算法来训练卷积神经网络。前向传播算法用于计算预测结果,并将其与真实标签进行比较来计算损失。反向传播算法用于根据损失来更新权重和偏置,以优化网络的性能。 此外,我们还需要实现一些辅助函数,如初始化权重和偏置、导入和导出数据、计算预测精度等。这些辅助函数将帮助我们更好地搭建和测试卷积神经网络。 最后,我们需要使用训练数据来训练网络,并使用测试数据来评估网络的性能。通过多次迭代训练和优化,可以提高网络的准确率和泛化能力。 总之,建立一个使用C语言实现卷积神经网络需要定义网络结构、编写卷积和池化函数、实现前向和反向传播算法、编写辅助函数,并使用训练和测试数据进行训练和评估。 ### 回答3: 卷积神经网络(Convolutional Neural Network,CNN)是一种经典的神经网络结构,特别适用于图像和语音处理任务。下面我将介绍使用C语言搭建卷积神经网络的基本步骤: 1. 定义网络结构:首先,我们需要定义卷积神经网络的结构,包括网络的层数、每层的神经元数量、卷积核的大小等。可以使用结构体或数组来表示网络结构。 2. 初始化权重:使用随机数或者预训练好的权重来初始化网络中的权重参数。可以使用数组或矩阵来表示网络中的权重。 3. 前向传播:对于给定的输入数据,通过卷积计算、池化等操作依次进行前向传播,得到网络的输出。卷积操作可以使用嵌套循环实现,池化操作可以使用最大值或平均值等方式。 4. 激活函数:将前向传播得到的输出通过激活函数进行非线性变换。常用的激活函数包括ReLU、sigmoid和tanh等。 5. 损失函数:根据网络的输出与真实标签之间的差异计算损失函数,常用的损失函数包括均方误差和交叉熵等。 6. 反向传播:通过计算损失函数对网络中的权重参数求导,然后根据梯度下降算法更新网络中的权重参数。可以使用链式法则计算梯度,通过嵌套循环实现权重的更新。 7. 训练网络:使用训练数据对网络进行训练,通过反复进行前向传播和反向传播来逐渐优化网络的权重参数,使得网络输出与真实标签更加接近。 8. 测试网络:使用测试数据对训练好的网络进行测试,计算网络的准确率或其他性能指标。 总之,通过定义网络结构、初始化权重、前向传播、激活函数、损失函数、反向传播、训练网络和测试网络等步骤,可以用C语言搭建卷积神经网络,并应用于图像和语音处理等任务中。需要注意的是,C语言相比其他高级语言(如Python)的代码实现会复杂一些,但通过良好的设计和代码编写,同样可以实现高效的卷积神经网络

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值