山野莽夫

  • 归档
    • 随笔
    • 建站资源
    • 分享
    • 代码
  • 地球物理学
    • 专业课
    • 概念解释
  • 计算机
  • 互联网
  • 教程
  • 规划
  • 实验室
    • 珍藏的软件
    • 贴吧云签到
    • A1账号自助申请
山野莽夫
小学生的挣扎的点点滴滴
  1. 首页
  2. 代码
  3. c
  4. 正文

人工智能之BP人工神经网络C语言实现内附实现代码

2020年1月12日 5049点热度 0人点赞 2条评论

人工智能现在还是十分火热。说到人工智能,那就必须提到AlphaGo的事情。这样就可以引出神经网络了。BP神经网络是最简单的也是最早的人工神经网络,这是最基本的网络,以后所有的网络都是以此改进而来。当然学习神经网络要从学习BP的原理学起。

正好有个课程报告,让实现BP人工神经网络。大部分同学们都是使用的Python。但是不知道我怎么产生了可怕的想法,非得要使用C语言搞一波。当然一般情况,网上有的话就不重复造轮子了。但是我百度了很多,都是把网络写死了,根本没有办法扩展。不符合我的需求,于是我就产生了用C语言一个可以拓展的BP神经网络的想法。说干就干,但是现实没有那么好办。我前后写了三版。一开始使用随机梯度下降法,而且没有加上偏置。后来想使用批量梯度下降法,加上偏置。但是批量也挺麻烦,转头一想,为什么不使用小批量梯度下降呢。因为这个工程量差不多。于是最终第三版,写成了一个小批量梯度下降法的BP。

至于原理和公式推导什么的,我就不放了。因为实在太麻烦了。但是如果你不懂原理的话,或者想要推导过程。这儿推荐一个大佬写的文章,非常详细。这个程序也是根据这篇文章编写的。 https://blog.csdn.net/u014303046/article/details/78200010/

然后再说下程序出现的问题吧。

1.我本来想加上批规范化的。就是Batch Normalization。可以把每一层的输出搞到同一个分布。但是技术有限,感觉太难写了,懒得动弹就没写。以后有时间再说吧。

2.程序也添加了relu激活函数,但是效果不好,很多时候无法收敛,根本达不到想象中relu激活函数的优化效果。我暂时没找到原因。有时间再调试吧。

3.样本数大的时候,容易发生梯度消失,误差小到一定程度就不再降低。

4.用了c++的一些语法,比cout,因为写着方便。

5.其他的基本都写到注释里了,不懂再在评论区问吧。

/*
Author:山野莽夫
Web:https://www.shanyemangfu.com
version:3.0
*/
#include <iostream>
#include<math.h>
#include<fstream>
#include<algorithm>
#include<ctime>
#include <iomanip>
using namespace std;
//定义函数
double f(double x, int kind);//激活函数
double df(double x, int kind);//激活函数的导数
void readdata();//读取数据
void initial();//初始化
void save();//保存模型
void load();//加载模型
void normalall();//全部数据归一化
bool dobatchtrain(int traintime, double acc, int& times);//
double batchtrain(); //小批量随机梯度下降,单次更新
void batchpos(int begin, int batchsize);//小批量正向计算
void batchnega(int begin, int batchsize);//小批量反向更新
void btest();//测试
//参数
constexpr int hidenum = 2;//隐藏层层数;
constexpr int num[hidenum + 1 + 1 + 1] = { 5,4,5,5,3 };//各层神经元个数0位置存储最大值
constexpr double e = 2.718281828459;
constexpr int tnum = 150;//训练样本数目
constexpr double tau = 0.001;//学习率
constexpr int trainnum = 90;//训练样本数目
constexpr int batchsize = 10;//批大小,要设置为可以被样本数目整除,虽说可以代码处理,但是懒得处理。
//变量
double x[num[1] + 1][tnum + 1];//输入
double y[num[hidenum + 2] + 1][tnum + 1];//期望输出
double nmx[num[1] + 1][tnum + 1];//归一化后的输入
double nmy[num[hidenum + 2] + 1][tnum + 1];//归一化后的期望输出
double w[hidenum + 2][num[0] + 1][num[0] + 1];//层数/k层顺序/k-1层顺序  |权重
double bb[hidenum + 2 + 1][num[0] + 1];//偏置
double bd[hidenum + 2 + 1][num[0] + 1][batchsize + 1];//误差
double by[num[hidenum + 2] + 1][batchsize + 1];//预测输出值
double bz[hidenum + 2 + 1][num[0] + 1][batchsize + 1];//净输出
double ba[hidenum + 2 + 1][num[0] + 1][batchsize + 1];//输出
int r = 1;//控制样本
ofstream rout("rout.txt");//迭代误差


int main()
{
	//std::cout << "Hello World!\n";
	//ifstream fin("data.txt");
	/*ofstream fout("fout.txt");
	ofstream pout("pout.txt");*/
	int times;//实际训练次数
	int traintime = 50000000;//最大训练次数
	double acc = 0.1;//精度
	readdata();//读取训练集
	initial();//初始化
	normalall();
	int option;
	cout << "请选择操作(输入1|2)" << "1:加载模型并测试\t" << "2:训练新模型" << endl;
	cin >> option;
	if (option==1) {
		load();
		btest();
	}
	else if (option == 2) {
		cout << "请输入Y或者N来选择是否保存模型" << endl;
		char issave;
		cin >> issave;
		if (issave == 'Y')
			cout << "你选择保存模型" << endl;
		if (dobatchtrain(traintime, acc, times))
			cout << "迭代" << times << "次训练成功" << endl;
		else
			cout << times << "训练失败,结果不收敛,请调整参数或者训练集" << endl;
		btest();
		if (issave == 'Y')
			save();
		
	}
	else cout << "输入不正确" << endl;
}
void save() {
	ofstream model("model.txt");
	for (int l = 2; l <= hidenum + 2; l++) {
		//model << l <<"\t"<<b[l]<< endl;//层号、偏置
		model << l << endl;//层号、偏置
		for (int i = 1; i <= num[l]; i++) {
			for (int j = 1; j <= num[l - 1]; j++) {
				model << w[l - 1][i][j] << "\t";//权重
			}
			model << bb[l][i] << endl;
		}
	}
	/*for (int l = 1; l <= hidenum + 1; l++) {
		wout << l << endl;
		for (int i = 1; i <= num[l+1]; i++) {

			for (int j = 1; j <= num[l]; j++) {
				wout << w[l][i][j] << "\t";
			}
			wout << endl;
		}
	}*/

}
void load() {
	ifstream readmodel("model.txt");
	for (int l = 2; l <= hidenum + 2; l++) {
		//readmodel >> l >> b[l] ;//层号、偏置
		readmodel >> l;//层号
		for (int i = 1; i <= num[l]; i++) {
			for (int j = 1; j <= num[l - 1]; j++) {
				readmodel >> w[l - 1][i][j];//权重
			}
			readmodel >> bb[l][i];
		}
	}
}
void readdata() {
	ifstream fin("data.txt");
	ofstream fout("fout.txt");
	int temp;
	/*fin >> temp;
	cout << temp;*/
	for (int i = 1; i <= tnum; i++) {
		for (int j = 1; j <= num[1]; j++) {
			fin >> x[j][i];
			//cout << x[j][i] << "\t";
		}
		//fin >> sy[i];
		for (int j = 1; j <= num[hidenum + 2]; j++) {
			fin >> y[j][i];
		}
	}
	for (int i = 0; i <= num[hidenum + 2]; i++) {
		for (int j = 0; j <= tnum; j++)
			fout << y[i][j] << "\t";
		fout << endl;
	}
	//测试真实值
}
double f(double x, int kind) {
	if (kind == 1) {
		return 1 / (1 + pow(e, -x));
	}
	else if (kind == 2) {
		//return max(0.0, x);
		if (x > 0)
			return x;
		else
			return 0;
	}
	else if (kind == 3) {
		return (pow(e, x) - pow(e, -x)) / (pow(e, x) + pow(e, -x));
	}
	else if (kind == 4) {
		return x;
	}
	else return 1 / (1 + pow(e, -x));
}
double df(double x, int kind) {
	if (kind == 1)
		return f(x, 1) * (1 - f(x, 1));
	else if (kind == 2) {
		if (x > 0)
			return 1;
		else
			return 0;
	}
	else if (kind == 3) {
		return 1 - f(x, 3) * f(x, 3);
	}
	else if (kind == 4) {
		return 1;
	}
	else return f(x, 1) * (1 - f(x, 1));
}
void initial() {//初始化权值和偏置	
	for (int i = 1; i <= hidenum + 2; i++) {
		for (int j = 1; j <= num[0]; j++) {
			bb[i][j] = 0;
		}
	}
	srand(time(NULL));
	for (int l = 2; l <= hidenum + 2; l++) {//生成-1--1随机数
		//cout << l - 1 << endl;
		for (int i = 1; i <= num[l]; i++) {
			for (int j = 1; j <= num[l - 1]; j++) {
				w[l - 1][i][j] = 2.0 * rand() / RAND_MAX - 1;
				//cout << w[l - 1][i][j];
			}
			//cout << endl;
		}
	}
}
void btest() {
	ofstream btout("btout.txt");
	int count = 0;
	for (int i = trainnum + 1; i <= tnum; i += batchsize) {
		batchpos(i, batchsize);
		for (int n = 1; n <= batchsize; n++) {
			for (int j = 1; j <= num[1]; j++) {
				cout << x[j][i + n - 1] << "\t";
				btout << x[j][i + n - 1] << "\t";
			}
			for (int j = 1; j <= num[hidenum + 2]; j++) {
				cout << y[j][i + n - 1] << "\t";
				btout << y[j][i + n - 1] << "\t";
			}			
			for (int j = 1; j <= num[hidenum + 2]; j++) {
				cout << ba[hidenum + 2][j][n] << "\t";
				btout << ba[hidenum + 2][j][n] << "\t";
				double temp = ba[hidenum + 2][j][n];
				if (temp > 0.5) {
					if (y[j][i + n - 1] == 1)
						count++;
				}
			}
			cout << endl;
			btout << endl;
		}

	}
	double accuracy;
	accuracy = double(count) /double(tnum-trainnum);
	cout << "判断正确个数:" << count << "正确率:" << accuracy << endl;
	btout << "判断正确个数:" << count << "正确率:" << accuracy << endl;	
}
void normalall() {
	for (int i = 1; i <= tnum; i++) {
		double max = x[1][i];
		double min = x[1][i];
		for (int j = 2; j <= num[1]; j++) {//寻找最大值和最小值
			if (x[j][i] > max)
				max = x[j][i];
			else if (x[j][i] < min) {
				min = x[j][i];
			}
		}
		//cout << max << "\t" << min<<endl;
		for (int j = 1; j <= num[1]; j++) {
			nmx[j][i] = (x[j][i] - min + 1) / (max - min + 1);
			//cout << nmx[j][i] << "\t";
			//xx[i] = (xx[i] - min + 5) / (max - xx[i] + 5);
		}
		//cout << endl;
	}
	/*for (int i = 1; i <= tnum; i++) {
		for (int j = 1; j <= num[1]; j++) {
			cout << nmx[j][i]<<"\t";
		}
		cout << endl;
	}*/


}
bool dobatchtrain(int traintime, double acc, int& times) {
	double e = 0; int time;
	for (time = 1; time <= traintime; time++) {
		e = batchtrain();
		if (time <= 10000) {
			if (time % 500 == 0)
				printf("%d\t%0.12f\n", time, e);
		}
		else {
			if (time % 5000 == 0)
				printf("%d\t%0.12f\n", time, e);
		}//每500次或者5000次输出一次误差

		if (e <= acc) {
			break;
			printf("%d:\t%.12f\n", time, e);
		}
	}
	if (e <= acc) {//如果误差小于精度,迭代成功,模型收敛
		times = time;//输出迭代次数
		return true;
	}
	else {//到达最大迭代次数仍然发散,迭代失败
		times = time;
		return false;
	}
}
double batchtrain() {
	double e = 0;//误差
	//int r = rand() % 6 + 1;

	//r = r % 6;

	for (int i = r; i <= trainnum; i +=  batchsize) {
		//batchpositive(i);
		batchpos(i, batchsize);
		for (int l = 1; l <= batchsize; l++) {//误差计算
			for (int j = 1; j <= num[hidenum + 2]; j++) {
				double temp = ba[hidenum + 2][j][l] - y[j][i + l - 1];
				e = e + 1.0 / 2 * temp * temp;
			}
		}
		batchnega(i, batchsize);
		//batchnegative(i);
	}
	//r++;

	return e;
}
void batchpos(int begin, int batchsize) {
	for (int n = 1; n <= batchsize; n++) {
		for (int i = 1; i <= num[1]; i++) {//输入层
			bz[1][i][n] = nmx[i][n + begin - 1];
			ba[1][i][n] = bz[1][i][n];
		}
		for (int l = 2; l <= hidenum + 1; l++) {//后续隐藏层
			for (int i = 1; i <= num[l]; i++) {
				//bz[l][i][n] = b[l - 1];
				bz[l][i][n] = bb[l][i];
				for (int j = 1; j <= num[l - 1]; j++) {
					bz[l][i][n] += w[l - 1][i][j] * ba[l - 1][j][n];
				}//净输出
				ba[l][i][n] = f(bz[l][i][n], 3);//输出
			}
		}
		for (int i = 1; i <= num[hidenum + 2]; i++) {//输出层
			bz[hidenum + 2][i][n] = bb[hidenum + 2 ][i];
			for (int j = 1; j <= num[hidenum + 2 - 1]; j++) {
				bz[hidenum + 2][i][n] += w[hidenum + 2 - 1][i][j] * ba[hidenum + 2 - 1][j][n];
			}//净输出
			ba[hidenum + 2][i][n] = f(bz[hidenum + 2][i][n], 3);//输出
		}
	}

}
void batchnega(int begin, int batchsize) {
	for (int n = 1; n <= batchsize; n++) {
		//cout << n << endl;
		for (int i = 1; i <= num[hidenum + 2]; i++) {

			bd[hidenum + 2][i][n] = df(bz[hidenum + 2][i][n], 3) * (ba[hidenum + 2][i][n] - y[i][n + begin - 1]);
			//cout << bd[hidenum + 2][i][n] << "\t";
		}
		//cout << endl;
		for (int l = hidenum + 1; l >= 2; l--) {//隐藏层误差
			for (int i = 1; i <= num[l]; i++) {
				bd[l][i][n] = 0;
				for (int k = 1; k <= num[l + 1]; k++) {
					bd[l][i][n] += bd[l + 1][k][n] * w[l][k][i];
				}
				bd[l][i][n] *= df(bz[l][i][n], 3);
			}
		}

	}
	for (int l = hidenum + 2; l >= 2; l--) {//误差反传
		for (int i = 1; i <= num[l]; i++) {
			for (int j = 1; j < num[l - 1]; j++) {
				double temp = 0;
				for (int n = 1; n <= batchsize; n++) {
					temp = temp + bd[l][i][n] * ba[l - 1][j][n];
				}
				w[l - 1][i][j] -= tau * 1.0 / batchsize * temp;
			}
		}
	}
	double temp = 0.0;
	for (int l = hidenum + 2; l >= 2; l--) {//偏置更新
		//
		for (int i = 1; i <= num[l]; i++) {
			temp = 0.0;
			for (int n = 1; n <= batchsize; n++) {
				temp += bd[l][i][n];
			}
			bb[l][i] -= tau * temp / batchsize;
		}
		//b[l - 1] -= tau * temp / num[l]/batchsize;
	}

}

源程序见: https://github.com/mengxiangke/Artificial-Neural-Network-BP

标签: BP 人工智能 人工神经网络 神经网络
最后更新:2020年2月1日

小菜菜

菜鸟

打赏 点赞
< 上一篇
下一篇 >

文章评论

  • sime

    请问您的误差计算为何使用了误差平方和呢

    2020年4月25日
    回复
    • 小菜菜

      @sime 这个使用的最小二乘法吧,这个损失最简单那。

      2020年4月25日
      回复
  • razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
    取消回复

    此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据。

    标签聚合
    c语言 宝塔面板 onedrive wordpress ppt 虚拟机 模板 地震学程序
    最新 热点 随机
    最新 热点 随机
    Azure Student 微软云 学生订阅 免费12个月用量避坑注意点集合 MP3音频文件格式详细解析 python按固定采样点个数分割wav格式音频 愉快使用谷歌免费人工智能平台colab,训练你的神经网络模型,为你的学术生活添砖加瓦 华为云版轻量应用服务器-云耀云服务器简单体验评测 Cloudflare 免费CDN自定义节点ip之自选cloudflare 高速节点ip工具分享
    谷歌内核浏览器出现"此flash player与你的地区不相容"的解决办法 typecho伪静态规则 地震法向矢量和滑动向量计算 宝塔面板安装wordpress插件库在线安装升级插件出现404 Not Found 404 Not Found nginx 留言板 记一下

    COPYRIGHT © 2021 shanyemangfu.com. ALL RIGHTS RESERVED.

    Theme Kratos Made By Seaton Jiang

    蜀ICP备15031791号-2