16 KiB
#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)100002-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: 不同的激活函数直接影响了神经网络的拟合能力和收敛速度。