You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

425 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#define INNODE 2 // 输入层神经元个数
#define HIDENODE 10 // 隐藏层神经元个数
#define OUTNODE 1 // 输出层神经元个数
/**
* 步长(学习率)
*/
double StudyRate = 1.6;
/**
* 允许最大误差
*/
double threshold = 1e-4;
/**
* 最大迭代次数
*/
int mostTimes = 1e6;
/**
* 训练集大小
*/
int trainSize = 0;
/**
* 测试集大小
*/
int testSize = 0;
/**
* 样本
*/
typedef struct Sample{
double out[30][OUTNODE]; // 输出
double in[30][INNODE]; // 输入
}Sample;
/**
* 神经元结点
*/
typedef struct Node{
double value; // 当前神经元结点输出的值
double bias; // 当前神经元结点偏偏置值
double bias_delta; // 当前神经元结点偏置值的修正值
double *weight; // 当前神经元结点向下一层结点传播的权值
double *weight_delta; // 当前神经元结点向下一层结点传播的权值的修正值
}Node;
/**
* 输入层
*/
Node inputLayer[INNODE];
/**
* 隐藏层
*/
Node hideLayer[HIDENODE];
/**
* 输出层
*/
Node outLayer[OUTNODE];
double Max(double a, double b){
return a > b ? a : b;
}
/**
* 激活函数sigmoid
* @param x 输入值
* @return 输出值
*/
double sigmoid(double x){
double y;//请补全sigmod函数的计算结果
y=1/(exp(-x)+1);
return y;
}
/**
* 读取训练集
* @param filename 文件名
* @return 训练集
*/
Sample * getTrainData(const char * filename){
Sample * result = (Sample*)malloc(sizeof (Sample));
FILE * file = fopen(filename, "r");
if(file != NULL){
int count = 0;
while (fscanf(file, "%lf %lf %lf", &result->in[count][0], &result->in[count][1], &result->out[count][0]) != EOF){
++count;
}
trainSize = count;
printf("%s The file has been successfully read!\n", filename);
fclose(file);
return result;
} else{
return result;
fclose(file);
printf("%s Encountered an error while opening the file!\n\a", filename);
return NULL;
}
}
/**
* 读取测试集
* @param filename 文件名
* @return 测试集
*/
Sample * getTestData(const char * filename){
Sample* result=(Sample*)malloc(sizeof(Sample));/*在内存中分配足够的空间来存储一个Sample结构并将指向该内存块的指针存储在result变量中*/
FILE * file = fopen(filename, "r");//打开文件
if(file != NULL){
int count=0;
while(fscanf(file,"%lf %lf",&result->in[count][0],&result->in[count][1])!=EOF)
{
++count;
}
testSize=count;
printf("%s The file has been successfully read!\n", filename);
fclose(file);
return result;
}
//将最终的count的值存储在名为testSize的全局变量中以便后续使用
//返回result
else{
fclose(file);
printf("%s Encountered an error while opening the file!\n\a", "TextData.txt");
return NULL;
}
}
/**
* 打印样本
* @param data 要打印的样本
* @param size 样本大小
*/
void printData(Sample * data, int size){
if(data==NULL)
printf("Sample is empty!");
else{
int i;
for(i=0;i<testSize;i++)
{
printf("%4.3lf %4.3lf %4.3f",data->in[i][0],data->in[i][1],data->out[i][0]);
printf("\n");
}
}
}
/**
* 初始化函数
*/
void init(){
// 设置时间戳为生成随机序列的种子
srand(time(NULL));
// 输入层的初始化
for (int i = 0; i < INNODE; ++i) {
inputLayer[i].weight = (double *)malloc(sizeof (double ) * HIDENODE);//开辟了一个数组
inputLayer[i].weight_delta = (double *) malloc(sizeof (double ) * HIDENODE);
inputLayer[i].bias = 0.0;
inputLayer[i].bias_delta = 0.0;
}
// 输出层权值初始化
for (int i = 0; i < INNODE; ++i) {
for (int j = 0; j < HIDENODE; ++j) {
inputLayer[i].weight[j] = rand() % 10000 / (double )10000 * 2 - 1.0;
inputLayer[i].weight_delta[j] = 0.0;
}
}
// 初始化隐藏层结点
for (int i = 0; i < HIDENODE; ++i) {
/*为隐藏层节点 i 分配了一个 double 类型的数组,用于存储该节点向下一层节点传播的权重。
使用malloc 函数在堆内存中分配足够的空间,以存储 OUTNODE double 类型的权重值。
*/
hideLayer[i].weight =(double*)malloc(sizeof(double)*OUTNODE);
/*为隐藏层节点 i 分配了一个用于存储权重修正值的数组。这个数组将在神经网络的训练过程中用于存储权重的更新值。
使用malloc 函数在堆内存中分配足够的空间,以存储 OUTNODE double 类型的权重值。
*/
hideLayer[i].weight_delta =(double*)malloc(sizeof(double)*OUTNODE);
/*为隐藏层节点 i 初始化了一个随机的偏置值。
这个值通常是一个在 -1.0 1.0 之间的随机数,用于调整该节点的激活函数的阈值。
*/
hideLayer[i].bias = rand()%10000/(double)10000*2-1.0;
/*初始化了隐藏层节点 i 的偏置值修正值,初始值为0.0。*/
hideLayer[i].bias_delta = 0.0;
}
// 初始化隐藏层权值
for (int i = 0; i < HIDENODE; ++i) {
for (int j = 0; j < OUTNODE; ++j) {
hideLayer[i].weight[j] = rand() % 10000 / (double )10000 * 2 - 1.0;
hideLayer[i].weight_delta[j] = 0.0;
}
}
for (int i = 0; i < OUTNODE; ++i) {
outLayer[i].bias = rand() % 10000 / (double )10000 * 2 - 1.0;
outLayer[i].bias_delta = 0.0;
}
}
/**
* 重置修正值
*/
void resetDelta(){
for (int i = 0; i < INNODE; ++i) {
for (int j = 0; j < HIDENODE; ++j) {
inputLayer[i].weight_delta[j] = 0.0;
}
}
for (int i = 0; i < HIDENODE; ++i) {
hideLayer[i].bias_delta = 0.0;
for (int j = 0; j < OUTNODE; ++j) {
hideLayer[i].weight_delta[j] = 0.0;
}
}
for (int i = 0; i < OUTNODE; ++i) {
outLayer[i].bias_delta = 0.0;
}
}
int main() {
// 初始化
init();
// 获取训练集
Sample * trainSample = getTrainData("TrainData.txt");
printf("\n");
printData(trainSample, trainSize);
for (int trainTime = 0; trainTime < mostTimes; ++trainTime) {
// 重置梯度信息
resetDelta();
// 当前训练最大误差
double error_max = 0.0;
// 开始训练(累计bp
for (int currentTrainSample_Pos = 0; currentTrainSample_Pos < trainSize; ++currentTrainSample_Pos) {
// 输入自变量
for (int inputLayer_Pos = 0; inputLayer_Pos < INNODE; ++inputLayer_Pos) {
inputLayer[inputLayer_Pos].value = trainSample->in[currentTrainSample_Pos][inputLayer_Pos];
}
/** ----- 开始正向传播 ----- */
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
double sum = 0.0;
for (int inputLayer_Pos = 0; inputLayer_Pos < INNODE; ++inputLayer_Pos) {
sum += inputLayer[inputLayer_Pos].value * inputLayer[inputLayer_Pos].weight[hideLayer_Pos];
}
sum -= hideLayer[hideLayer_Pos].bias;
hideLayer[hideLayer_Pos].value = sigmoid(sum);
}
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE ; ++outLayer_Pos) {
double sum = 0.0;
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
sum+=hideLayer[hideLayer_Pos].value*hideLayer[hideLayer_Pos].weight[outLayer_Pos];/*计算每一个隐藏层节点的value和权值的乘积,相加得到sum
*/
}
/*更新sum,使sum减去偏置值;
*/
sum-=outLayer[outLayer_Pos].bias; /*利用sigmod函数对得到的sum进行激活,把激活后的结果赋值给对应的输出层节点value(outLayer[outLayer_Pos].value)。 */
outLayer[outLayer_Pos].value=sigmoid(sum);
}
/** ----- 计算误差 ----- */
double error = 0.0;
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
double temp = fabs(outLayer[outLayer_Pos].value - trainSample->out[currentTrainSample_Pos][outLayer_Pos]);
// 损失函数
error += temp * temp / 2.0;
}
error_max = Max(error_max, error);
/** ----- 反向传播 ----- */
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
double bias_delta = -(trainSample->out[currentTrainSample_Pos][outLayer_Pos] - outLayer[outLayer_Pos].value)
* outLayer[outLayer_Pos].value * (1.0 - outLayer[outLayer_Pos].value);
outLayer[outLayer_Pos].bias_delta += bias_delta;
}
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
double weight_delta = (trainSample->out[currentTrainSample_Pos][outLayer_Pos] - outLayer[outLayer_Pos].value)
* outLayer[outLayer_Pos].value * (1.0 - outLayer[outLayer_Pos].value)
* hideLayer[hideLayer_Pos].value;
hideLayer[hideLayer_Pos].weight_delta[outLayer_Pos] += weight_delta;
}
}
//
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
double sum = 0.0;
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
sum += -(trainSample->out[currentTrainSample_Pos][outLayer_Pos] - outLayer[outLayer_Pos].value)
* outLayer[outLayer_Pos].value * (1.0 - outLayer[outLayer_Pos].value)
* hideLayer[hideLayer_Pos].weight[outLayer_Pos];
}
hideLayer[hideLayer_Pos].bias_delta += sum * hideLayer[hideLayer_Pos].value * (1.0 - hideLayer[hideLayer_Pos].value);
}
for (int inputLayer_Pos = 0; inputLayer_Pos < INNODE; ++inputLayer_Pos) {
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
double sum = 0.0;
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
sum += (trainSample->out[currentTrainSample_Pos][outLayer_Pos] - outLayer[outLayer_Pos].value)
* outLayer[outLayer_Pos].value * (1.0 - outLayer[outLayer_Pos].value)
* hideLayer[hideLayer_Pos].weight[outLayer_Pos];
}
inputLayer[inputLayer_Pos].weight_delta[hideLayer_Pos] += sum * hideLayer[hideLayer_Pos].value * (1.0 - hideLayer[hideLayer_Pos].value)
* inputLayer[inputLayer_Pos].value;
}
}
}
// 判断误差是否达到允许误差范围
if(error_max < threshold){
printf("\a Training completed!Total training count:%d, maximum error is:%f\n", trainTime + 1, error_max);
break;
}
// 误差无法接受,开始修正
for (int inputLayer_Pos = 0; inputLayer_Pos < INNODE; ++inputLayer_Pos) {
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
inputLayer[inputLayer_Pos].weight[hideLayer_Pos] += StudyRate
* inputLayer[inputLayer_Pos].weight_delta[hideLayer_Pos] /
(double) trainSize;
}
}
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
hideLayer[hideLayer_Pos].bias += StudyRate
* hideLayer[hideLayer_Pos].bias_delta / (double )trainSize;
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
hideLayer[hideLayer_Pos].weight[outLayer_Pos] += StudyRate
* hideLayer[hideLayer_Pos].weight_delta[outLayer_Pos] / (double )trainSize;
}
}
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
outLayer[outLayer_Pos].bias += StudyRate
* outLayer[outLayer_Pos].bias_delta / (double )trainSize;
}
}
// 训练完成,读取测试集
Sample * testSample = getTestData("TestData.txt");
printf("The predicted results are as follows:\n");
for (int currentTestSample_Pos = 0; currentTestSample_Pos < testSize; ++currentTestSample_Pos) {
for (int inputLayer_Pos = 0; inputLayer_Pos < INNODE; ++inputLayer_Pos) {
inputLayer[inputLayer_Pos].value = testSample->in[currentTestSample_Pos][inputLayer_Pos];
}
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
double sum = 0.0;
for (int inputLayer_Pos = 0; inputLayer_Pos < INNODE; ++inputLayer_Pos) {
sum += inputLayer[inputLayer_Pos].value * inputLayer[inputLayer_Pos].weight[hideLayer_Pos];
}
sum -= hideLayer[hideLayer_Pos].bias;
hideLayer[hideLayer_Pos].value = sigmoid(sum);
}
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
double sum = 0.0;
for (int hideLayer_Pos = 0; hideLayer_Pos < HIDENODE; ++hideLayer_Pos) {
sum += hideLayer[hideLayer_Pos].value * hideLayer[hideLayer_Pos].weight[outLayer_Pos];
}
sum -= outLayer[outLayer_Pos].bias;
outLayer[outLayer_Pos].value = sigmoid(sum);
}
for (int outLayer_Pos = 0; outLayer_Pos < OUTNODE; ++outLayer_Pos) {
testSample->out[currentTestSample_Pos][outLayer_Pos] = outLayer[outLayer_Pos].value;
}
}
printData(testSample, testSize);
return 0;
}
步骤6
文字大致描述bp神经网络
通过一些训练数据包含输入的信息和应该输出的信息让bp神经网络能够在输入测试数据后在没有输出范例的情况下按要求输出数据。该训练过程为先将输入层、隐藏层、输出层的各个数值——值权重、偏置值等初始化将输入数据原封不动传递给输入层神经元每个输入层神经元对于将值按照一定权重传递给隐藏层神经元。每个隐藏层神经元将所接受到的值收集起来减去事先初始化好的偏置值后将剩下的值通过sigmod函数映射到【0,1】之间将映射结果作为自身的值。每一个隐藏层神经元将自身的值按照一定权重传递给输出层神经元输出层神经元将所接收到的值收集起来减去自身偏置值后将剩下的值通过sigmod函数映射到【0,1】之间作为自身的值即预期输出的值。通过分析该输出与应该输出的数据之间的误差反向传播将误差分摊给各个神经元——调整其权重已达到下一次输入数据后缩小误差的目的。一组训练数据训练完后倘若最大误差不符合要求则将进行下一次训练。通过反复训练将最大误差缩小到要求范围之内即达成训练学习要求。
实验习题1
适当提高学习率可以加快更新权重值的速度,从而加快训练速度,缩短得到输出数据的时间。
实验习题2
适当增加隐藏层神经元个数可以有效减小误差。
实验习题3
不同的激活函数直接影响了神经网络的拟合能力和收敛速度。