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

上一期介绍了用于储存图像的矩阵类,在写卷积神经网络前,先用C++实现一个三层的神经网络。以手写数字识别为例。嗯,非常经典到老掉牙的例子。

首先先定义一些基本的操作的函数:
查看矩阵信息的函数:

void show(matrix &m) { for (int i = 0; i < m.size(); i++)  cout << m.x[i] << endl; cout << "=============" << endl; }
void show_info(matrix &m)
{
	cout << "cols is " << m.cols << endl;
	cout << "rows is " << m.rows << endl;
	cout << "chans is " << m.chans << endl;
}

用于打乱数据集:

vector<int> get_a_sequence(int length)
{
	vector<int> sequence;
	for (int i = 0; i < length; i++) { sequence.push_back(i); }
	return sequence;
}

void dataset_shuffle(vector<vector<float>> &train_images, vector<vector<int>> &Labels, vector<int> &sequence)
{
	random_shuffle(sequence.begin(), sequence.end());
	vector<vector<float>> new_train_images(60000);
	vector<vector<int>> new_train_labels(60000);
	//new_train_images = train_images;
	//new_train_labels = Labels;
	for (int i = 0; i < train_images.size(); i++)
	{
		new_train_images[i] = train_images[i];
		new_train_labels[i] = Labels[i];
	}
	int index = 0;
	for (int i = 0; i < train_images.size(); i++)
	{
		index = sequence[i];
		train_images[i] = new_train_images[index];
		Labels[i] = new_train_labels[index];
	}
}

获得向量的最大值下标,用于测试函数:

inline int Argmax(float *x, int size)
{
	float max = 0;
	int argmax = 0;
	for (int i = 0; i < size; i++)
	{
		if (max <= x[i])
		{
			max = x[i];
			argmax = i;
		}
	}
	return argmax;
}

测试函数:(因为没有封装,所以写成了三层全连接层的形式)

void test(vector<vector<float>> &test_images, vector<int> &test_labels, vector<matrix> &parameter)
{
	cout << "正在测试中。。。" << endl;

	vector <vector<int>> Labels;
	Labels = Onehot_encoding(test_labels, 10);

	vector<float> Img;
	vector<int> Label;

	matrix W1, W2, B1, B2;
	W1 = parameter[0];
	W2 = parameter[1];
	B1 = parameter[2];
	B2 = parameter[3];

	matrix Hide, Hide_s;
	matrix Output, Output_s;

	int correct_num = 0;
	float accuracy_rate;

	for (int k = 0; k < test_images.size(); k++)
	{
		Img = test_images[k];
		Label = Labels[k];
		matrix img(28, 28, 1); vector_to_matrix(img.x, Img);
		matrix label(10, 1, 1); vector_to_matrix(label.x, Label);
		//数据正向传播
		Hide = M_dot_V(W1, img) + B1;
		Hide_s = sigmoid_f(Hide);

		Output = M_dot_V(W2, Hide_s) + B2;
		Output_s = sigmoid_f(Output);

		int L, index;
		L = Argmax(Output_s.x, 10);
		if (L == test_labels[k])  correct_num++;
	}
	accuracy_rate = 1.0f*correct_num / test_images.size();
	cout << "测试准确率为:" << accuracy_rate << endl;
}

数据集读取:写成.h文件

#pragma once


#include <iostream> // cout
#include <sstream>
#include <fstream>
#include <iomanip> //setw
#include <random>
#include <stdio.h>


namespace mnist
{
std::string data_name() {return std::string("MNIST");}

// from tiny_cnn
template<typename T>
T* reverse_endian(T* p) {
	std::reverse(reinterpret_cast<char*>(p), reinterpret_cast<char*>(p) + sizeof(T));
	return p;
}

// from tiny_cnn (kinda)
bool parse_mnist_labels(const std::string& label_file, std::vector<int> *labels) {
	std::ifstream ifs(label_file.c_str(), std::ios::in | std::ios::binary);

	if (ifs.bad() || ifs.fail()) 
	{
		return false;
	}
	int magic_number, num_items;

	ifs.read((char*) &magic_number, 4);
	ifs.read((char*) &num_items, 4);

	reverse_endian(&magic_number);
	reverse_endian(&num_items);

	for (size_t i = 0; i < num_items; i++) {
		unsigned char label;
		ifs.read((char*) &label, 1);
		labels->push_back((int) label);
	}
	return true;
}

// from tiny_cnn
struct mnist_header {
	int magic_number;
	int num_items;
	int num_rows;
	int num_cols;
};

// from tiny_cnn (kinda)
bool parse_mnist_images(const std::string& image_file, 
	std::vector<std::vector<float>> *images,
	float scale_min = -1.0, float scale_max = 1.0,
	int x_padding = 0, int y_padding = 0) 
{
	std::ifstream ifs(image_file.c_str(), std::ios::in | std::ios::binary);

	if (ifs.bad() || ifs.fail())
	{
			return false;
	}
	mnist_header header;

	// read header
	ifs.read((char*) &header.magic_number, 4);
	ifs.read((char*) &header.num_items, 4);
	ifs.read((char*) &header.num_rows, 4);
	ifs.read((char*) &header.num_cols, 4);

	reverse_endian(&header.magic_number);
	reverse_endian(&header.num_items);
	reverse_endian(&header.num_rows);
	reverse_endian(&header.num_cols);

		
	const int width = header.num_cols + 2 * x_padding;
	const int height = header.num_rows + 2 * y_padding;

	// read each image
	for (size_t i = 0; i < header.num_items; i++) 
	{
		std::vector<float> image;
		std::vector<unsigned char> image_vec(header.num_rows * header.num_cols);

		ifs.read((char*) &image_vec[0], header.num_rows * header.num_cols);
		image.resize(width * height, scale_min);
	
		for (size_t y = 0; y < header.num_rows; y++)
		{
			for (size_t x = 0; x < header.num_cols; x++)
				image[width * (y + y_padding) + x + x_padding] = 
					(image_vec[y * header.num_cols + x] / 255.0f) * (scale_max - scale_min) + scale_min;
		}
		
		images->push_back(image);
	}
	return true;
}

// == load data (MNIST-28x28x1 size, no padding, pixel range -1 to 1)
bool parse_test_data(std::string &data_path, std::vector<std::vector<float>> &test_images, std::vector<int> &test_labels, 
	float min_val=-1.f, float max_val=1.f, int padx=0, int pady=0)
{
	if(!parse_mnist_images(data_path+"/t10k-images.idx3-ubyte", &test_images, min_val, max_val, padx, pady)) 
		if (!parse_mnist_images(data_path + "/t10k-images-idx3-ubyte", &test_images, min_val, max_val, padx, pady))
			return false;
	if(!parse_mnist_labels(data_path+"/t10k-labels.idx1-ubyte", &test_labels)) 
		if (!parse_mnist_labels(data_path + "/t10k-labels-idx1-ubyte", &test_labels)) return false;
	return true;
}
bool parse_train_data(std::string &data_path, std::vector<std::vector<float>> &train_images, std::vector<int> &train_labels, 
	float min_val=-1.f, float max_val=1.f, int padx=0, int pady=0)
{
	if(!parse_mnist_images(data_path+"/train-images.idx3-ubyte", &train_images, min_val, max_val, padx, pady))
		if (!parse_mnist_images(data_path + "/train-images-idx3-ubyte", &train_images, min_val, max_val, padx, pady))
			return false;
	if(!parse_mnist_labels(data_path+"/train-labels.idx1-ubyte", &train_labels))
		if (!parse_mnist_labels(data_path + "/train-labels-idx1-ubyte", &train_labels)) return false;
	return true;
}
}

数据集处理函数:

//========================数据集处理=============================================

//对标签数据做one_hot编码
vector<vector<int>> Onehot_encoding(vector<int> &labels, int type_nums)
{
	vector<vector<int>> labels_encoding;
	for (int i = 0; i < labels.size(); i++)
	{
		vector<int> temp(type_nums, 0);
		temp[labels[i]] = 1;
		labels_encoding.push_back(temp);
	}
	return labels_encoding;
}

//把数据集分成几个大小为batch_size的集合,主要用于样本图像
vector<vector<vector<float>>> split_batch_x(vector<vector<float>> &imgs, int batch_size)
{
	vector<vector<vector<float>>> x;
	int _size = imgs.size() / batch_size;
	for (int i = 0; i < _size; i++)
	{
		vector<vector<float>> batch_x;
		for (int j = 0; j < batch_size; j++)
		{
			batch_x.push_back(imgs[j + i * batch_size]);
		}
		x.push_back(batch_x);
	}
	return x;
}

//把数据集分成几个大小为batch_size的集合,主要用于样本标签
vector<vector<vector<int>>> split_batch_y(vector<vector<int>> &labels, int batch_size)
{
	vector<vector<vector<int>>> y;
	int _size = labels.size() / batch_size;
	for (int i = 0; i < _size; i++)
	{
		vector<vector<int>> batch_y;
		for (int j = 0; j < batch_size; j++)
		{
			batch_y.push_back(labels[j + i * batch_size]);
		}
		y.push_back(batch_y);
	}
	return y;
}

激活函数和它的导数,这里选用了sigmoid:


inline matrix sigmoid_f(matrix &m)
{
	matrix out(m.cols, m.rows, m.chans);
	for (int i = 0; i < out.size(); i++) out.x[i] = 1.0f / (1.0f + exp(-(m.x[i])));
	return out;
}


inline matrix sigmoid_df(matrix &m)
{
	matrix out(m.cols, m.rows, m.chans);
	for (int i = 0; i < out.size(); i++) out.x[i] = m.x[i] * (1.f - m.x[i]);
	return out;

然后再加点矩阵类的一些补充运算

//=======================矩阵类的一些补充运算====================================
//将vector转化为matrix
inline void vector_to_matrix(float *m, vector<int> &v)
{
	for (int i = 0; i < v.size(); i++)  m[i] = v[i];
}
inline void vector_to_matrix(float *m, vector<float> &v)
{
	for (int i = 0; i < v.size(); i++)  m[i] = v[i];
}

//矩阵转置
inline matrix Transposition(matrix &m)
{
	int _w = m.cols;
	int _h = m.rows;
	matrix out(_h, _w, 1);
	for (int i = 0; i < _h; i++)
	{
		for (int j = 0; j < _w; j++)
		{
			out.x[j + i * _w] = m.x[i + j * _h];
		}
	}
	return out;
}

//矩阵*向量,结果是一个向量那种,矩阵和向量都是存成一维的形式,matrix类,你懂的
inline matrix M_dot_V(matrix &A, matrix &B)
{
	int _w = A.cols;
	int _h = A.rows;
	matrix out(_w, 1, 1);
	out.fill(0);
	for (int i = 0; i < _w; i++)
	{
		for (int j = 0; j < _h; j++)
		{
			out.x[i] += A.x[j + i * _h] * B.x[j];
		}
	}
	return out;
}

//向量*向量,结果是一个矩阵那种,矩阵和向量都是存成一维的形式,matrix类,你懂的
inline matrix V_dot_V(matrix &A, matrix &B)
{
	int _w = A.size();
	int _h = B.size();
	matrix out(_w, _h, 1);
	for (int i = 0; i < _w; i++)
	{
		for (int j = 0; j < _h; j++)
		{
			out.x[j + i * _h] = A.x[i] * B.x[j];
		}
	}
	return out;
}

别忘了还要导入一些相关的包哦:

include <vector>
include <iostream>
using namespace std;
using namespace 

最后是主函数(记得把手写数字的数据集下载下来,放在对应目录下):

int main()
{
	string data_path = "data/mnist/";

	int batch_size = 10;
	int num_input = 28 * 28 * 1;
	int num_hide = 30;
	int	num_classes = 10;
	float lr = 3; // 学习率
	int epochs = 1;
	//====================================================================
	//读取数据集
	vector<vector<float>> train_images, train_images_copy;
	vector<int> train_labels;
	vector<vector<float>> test_images;
	vector<int> test_labels;

	cout << "读取数据中" << endl;
	if (!parse_test_data(data_path, test_images, test_labels)) { std::cerr << "error: could not parse data.\n"; return 1; }
	if (!parse_train_data(data_path, train_images, train_labels)) { std::cerr << "error: could not parse data.\n"; return 1; }
	cout << "数据读取完成" << endl;

	train_images_copy = train_images;
	//====================================================================
	vector<vector<int>> Labels;
	Labels = Onehot_encoding(train_labels, 10);

	vector<vector<vector<float>>> x;
	vector<vector<float>> batch_x;
	vector<float> Img;
	vector<vector<vector<int>>> y;
	vector<vector<int>> batch_y;
	vector<int> Label;

	vector<int> sequence;
	sequence = get_a_sequence(train_images.size());  //生成一个序列,用来打乱作为索引,用于打乱数据集
	//========================================================================
	//全连接层
	matrix W1(num_hide, num_input, 1);
	matrix B1(num_hide, 1, 1);
	matrix W2(num_classes, num_hide, 1);
	matrix B2(num_classes, 1, 1);

	W1.fill_random_normal(1.f);
	B1.fill_random_normal(1.f);
	W2.fill_random_normal(1.f);
	B2.fill_random_normal(1.f);

	matrix Hide, Hide_s;    //隐藏层
	matrix Output, Output_s;   //输出层
	matrix delta_1, delta_2;   //隐藏层和输出层误差
	matrix W1_T, W2_T;    //矩阵的转置
	matrix W1_t, W2_t, B1_t, B2_t;   //用来存参数的梯度
	//========================================================================
//开始训练
	for (int epoch = 0; epoch < epochs; epoch++)
	{
		dataset_shuffle(train_images, Labels, sequence);  //随机打乱数据集

		x = split_batch_x(train_images, batch_size);
		y = split_batch_y(Labels, batch_size);
		//=====================================================================
		for (int m = 0; m < x.size(); m++)
		{
			batch_x = x[m];
			batch_y = y[m];

			matrix W1_t_sum(W1.cols, W1.rows, W1.chans); W1_t_sum.fill(0);
			matrix W2_t_sum(W2.cols, W2.rows, W2.chans); W2_t_sum.fill(0);
			matrix B1_t_sum(B1.cols, B1.rows, B1.chans); B1_t_sum.fill(0);
			matrix B2_t_sum(B2.cols, B2.rows, B2.chans); B2_t_sum.fill(0);

			for (int n = 0; n < batch_x.size(); n++)
			{
				Img = batch_x[n];
				Label = batch_y[n];
				matrix img(28, 28, 1); vector_to_matrix(img.x, Img);
				matrix label(10, 1, 1); vector_to_matrix(label.x, Label);

				Hide = M_dot_V(W1, img) + B1;
				Hide_s = sigmoid_f(Hide);

				Output = M_dot_V(W2, Hide_s) + B2;
				Output_s = sigmoid_f(Output);

				//=======================================================================
				//误差反向传播
				delta_1 = (Output_s - label)*sigmoid_df(Output_s);   //输出层误差
				W1_T = Transposition(W1);
				W2_T = Transposition(W2);
				delta_2 = M_dot_V(W2_T, delta_1)*sigmoid_df(Hide_s);   //隐藏层误差

				//=======================================================================
				//计算参数梯度
				B2_t = delta_1;
				B1_t = delta_2;
				W2_t = V_dot_V(delta_1, Hide_s);
				W1_t = V_dot_V(delta_2, img);

				B2_t_sum += B2_t;
				B1_t_sum += B1_t;
				W2_t_sum += W2_t;
				W1_t_sum += W1_t;
			}
			//更新参数===============================================================
			W1 = W1 - W1_t_sum * (lr / batch_size);
			W2 = W2 - W2_t_sum * (lr / batch_size);
			B1 = B1 - B1_t_sum * (lr / batch_size);
			B2 = B2 - B2_t_sum * (lr / batch_size);

			if (m % 100 == 0)  cout << "=====================正在训练第" << epoch << "个epoch,第" << m << "个batch=====================" << endl;
		}
	}
	cout << "训练完成" << endl;

	vector<matrix> parameter;
	parameter.push_back(W1);
	parameter.push_back(W2);
	parameter.push_back(B1);
	parameter.push_back(B2);

	test(train_images_copy, train_labels, parameter);
	test(test_images,test_labels,parameter);

	system("pause");
	return 0;
}

最后是实验结果:
在这里插入图片描述
50个epochs 可以达到93%以上的准确率,然后是100个epochs的结果:
在这里插入图片描述
可以看到准确率可以达到94%以上,不过神经网络差不多只能达到这个结果了,要想有更好的结果,就要使用卷积神经网络了,后面有时间再补充上了。

### 回答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)的代码实现会复杂一些,但通过良好的设计和代码编写,同样可以实现高效的卷积神经网络
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值