
【BP】c++实现
BP网络c++实现
单隐藏层流程

下面实现一个单隐藏层的简单BP算法,其中输入层、隐藏层、输出层节点个数可以自定义,而前向传播和后向传播是主要过程,下面是按上图进行的公式推导
1. 前向传播
1.1 输入层向隐藏层传播
q=∑ixiwij−βjhj(q)=σ(∑ixiwij−βj)q=\sum_i x_i w_{ij} - \beta_j\\ h_j(q)=\sigma( \sum_i x_i w_{ij} - \beta_j ) q=i∑xiwij−βjhj(q)=σ(i∑xiwij−βj)
其中hjh_jhj为第jjj个隐藏层节点的值,xix_ixi为第iii个输入层节点的值,wijw_{ij}wij为第iii个输入层节点到第jjj个隐藏层节点的权重,βj\beta_jβj为第jjj个隐藏层节点偏置值,σ(x)\sigma(x)σ(x)为Sigmoid激活函数,其公式如下
σ(x)=11+e−x\sigma(x) = \frac{1}{1+e^{-x}} σ(x)=1+e−x1
代码
//计算h_j(q)
int computeH_j(){
for (size_t j = 0; j < HIDENODE; j++) {
double sum = 0;
//计算q
for (size_t i = 0; i < INNODE; i++) {
sum += inputLayer[i]->value * inputLayer[i]->weight[j];
}
sum -= hideLayer[j]->bias;
//调用sigma函数
hideLayer[j]->value = utils::sigmoid(sum);
}
}1.2 隐藏层向输出层传播
yk^=σ(∑jhjvjk−λk)\hat{y_k} = \sigma( \sum_j h_j v_{jk} - \lambda_k ) yk^=σ(j∑hjvjk−λk)
其中yk^\hat{y_k}yk^为第kkk个输出层节点的值(预测值),hjh_jhj为第jjj个隐藏层节点的值,vjkv_{jk}vjk为第jjj个隐藏层节点到第kkk个输出层节点的权重,λk\lambda_kλk为第kkk个输出层节点的偏置值,σ(x)\sigma(x)σ(x)为激活函数。
代码
//计算\hat{y},默认OUTNODE为1,不使用循环也可以
int computeHat_y(){
for (size_t j = 0; j < OUTNODE; j++) {
double sum = 0;
for (size_t i = 0; i < HIDENODE; i++) {
sum += hideLayer[i]->value * hideLayer[i]->weight[j];
}
sum -= outLayer[j]->bias;
outLayer[j]->value = utils::sigmoid(sum);
}
}2. 计算损失函数
损失函数定义如下:
Loss=12∑k(yk−yk^)2Loss = \frac{1}{2}\sum_k ( y_k - \hat{y_k} )^2 Loss=21k∑(yk−yk^)2
其中yky_kyk为第kkk个输出层节点的目标值(真实值),yk^\hat{y_k}yk^为第kkk个输出层节点的值(预测值)。
代码
// 计算误差
double error = 0.f;
for (size_t i = 0; i < OUTNODE; i++) {
double tmp = fabs(outLayer[i]->value - idx.out[i]);
error += tmp * tmp / 2;
}
//用于判断是否停止训练
error_max = max(error_max, error);3. 反向传播
利用梯度下降法进行优化,激活函数使用Sigmoid,公式σ(x)=11+e−x\sigma(x) = \frac{1}{1+e^{-x}}σ(x)=1+e−x13.1 计算Δλk\Delta \lambda_kΔλk(输出层节点偏置值的修正值)
其计算公式如下:
Δλk=∂loss∂λk=η(yi−yi^)yi^(1−yi^)(−1)\Delta\lambda_k=\frac {\partial loss}{\partial \lambda_k}=\eta (y_i-\hat{y_i})\hat{y_i}(1-\hat{y_i})(-1) Δλk=∂λk∂loss=η(yi−yi^)yi^(1−yi^)(−1)
其中η\etaη为学习率,默认η=0.8\eta=0.8η=0.8。
代码
for (size_t i = 0; i < OUTNODE; i++) {
double bias_delta = -(idx.out[i] - outLayer[i]->value) *
outLayer[i]->value * (1.0 - outLayer[i]->value);
outLayer[i]->bias_delta += bias_delta;
}3.2 计算Δvjk\Delta v_{jk}Δvjk(隐藏层节点到输出层节点权重的修正值)
其计算公式如下:
Δvjk=∂loss∂v=η(yk−yk^)yk^(1−yk^)hj\Delta v_{jk} =\frac {\partial loss}{\partial v}= \eta ( y_k - \hat{y_k} ) \hat{y_k} ( 1 - \hat{y_k} ) h_j Δvjk=∂v∂loss=η(yk−yk^)yk^(1−yk^)hj
其中hjh_jhj为第jjj个隐藏层节点的值。
代码
for (size_t i = 0; i < HIDENODE; i++) {
for (size_t j = 0; j < OUTNODE; j++) {
double weight_delta = (idx.out[j] - outLayer[j]->value) *
outLayer[j]->value * (1.0 - outLayer[j]->value) *
hideLayer[i]->value;
hideLayer[i]->weight_delta[j] += weight_delta;
}
}3.3 计算Δβj\Delta \beta_jΔβj(隐藏层节点偏置值的修正值)
其计算公式如下:
Δβj=∂loss∂β=−η∑k(yk−yk^)yk^(1−yk^)vjkhj(1−hj)\Delta \beta_j = \frac {\partial loss}{\partial \beta} =- \eta \sum_k ( y_k - \hat{y_k} ) \hat{y_k} ( 1 - \hat{y_k} ) v_{jk} h_j ( 1 - h_j ) Δβj=∂β∂loss=−ηk∑(yk−yk^)yk^(1−yk^)vjkhj(1−hj)
其中vjkv_{jk}vjk为第jjj个隐藏层节点到第kkk个输出层节点的权重。
代码
for (size_t i = 0; i < HIDENODE; i++) {
double sum = 0;
for (size_t j = 0; j < OUTNODE; j++) {
sum += -(idx.out[j] - outLayer[j]->value) *
outLayer[j]->value * (1.0 - outLayer[j]->value) *
hideLayer[i]->weight[j];
}
hideLayer[i]->bias_delta +=
sum * hideLayer[i]->value * (1.0 - hideLayer[i]->value);
}3.4 计算Δwij\Delta w_{ij}Δwij(输入层节点到隐藏层节点权重的修正值)
其计算公式如下:
Δwij=∂loss∂wij=η∑k(yk−yk^)yk^(1−yk^)vjkhj(1−hj)xi\Delta w_{ij} =\frac {\partial loss}{\partial w_{ij}}= \eta \sum_k ( y_k - \hat{y_k} ) \hat{y_k} ( 1 - \hat{y_k} ) v_{jk} h_j ( 1 - h_j ) x_i Δwij=∂wij∂loss=ηk∑(yk−yk^)yk^(1−yk^)vjkhj(1−hj)xi
其中xix_ixi为第iii个输入层节点的值。
代码
for (size_t i = 0; i < INNODE; i++) {
for (size_t j = 0; j < HIDENODE; j++) {
double sum = 0.f;
for (size_t k = 0; k < OUTNODE; k++) {
sum += (idx.out[k] - outLayer[k]->value) *
outLayer[k]->value * (1.0 - outLayer[k]->value) *
hideLayer[j]->weight[k];
}
inputLayer[i]->weight_delta[j] +=
sum *
hideLayer[j]->value * (1.0 - hideLayer[j]->value) *
inputLayer[i]->value;
}
}4. 训练部分
4.1 初始化函数
- 初始化三层神经网络:输入层(
INNODE个神经元),隐藏层(HIDENODE个神经元),输出层(OUTNODE个神经元)。 - 每个神经元的权重和偏置(
bias)被初始化为随机值
4.2 读取训练数据
- 使用
getData函数从文件中读取训练数据(包含输入和输出)。
4.3 训练过程
- 对于训练数据的每一个样本:
- 正向传播 (
forwardPropagation):输入数据被传递到输入层,然后经过隐藏层,并最终到达输出层。每一层的节点值根据权重和偏置计算,并通过激活函数处理。 - 计算误差:使用均方误差,计算输出层的值与训练数据中的实际输出之间的差异。
- 反向传播 (
backwardPropagation):通过反向传播算法调整网络中每个神经元的权重和偏置的梯度(即delta)。 - 更新权重和偏置 (
revise):根据计算得到的梯度调整每个神经元的权重和偏置。
- 正向传播 (
4.4 停止条件
- 如果达到最大训练次数(
mosttimes)或误差小于设定的阈值(threshold),则停止训练。
代码
// 训练
for (size_t times = 0; times < mosttimes; times++) {
// 重置权重和偏置的delta
reset_delta();
// 计算误差,用于判断是否停止训练
double error_max = 0.f;
// 遍历训练数据
for (auto &idx : train_data) {
// 正向传播
forwardPropagation(idx);
// 计算误差
double error = 0.f;
for (size_t i = 0; i < OUTNODE; i++) {
double tmp = fabs(outLayer[i]->value - idx.out[i]);
error += tmp * tmp / 2;
}
error_max = max(error_max, error);
// 反向传播
backwardPropagation(idx);
}
// 如果误差小于阈值,停止训练
if (error_max < threshold) {
cout << "Success with " << times + 1 << " times training." << endl;
cout << "Maximum error: " << error_max << endl;
break;
}
// 更新权重
revise(train_data);
}5. 预测部分
5.1 读取测试数据
- 使用
getData函数从文件中读取测试数据(仅输入,无输出)。
5.2 预测过程
- 对于测试数据的每一个样本:
- 通过与训练类似的正向传播过程计算输出层的值。
- 输出每个样本的输入和对应的预测输出。
代码
void predict(vector<Sample> test_data){
for (auto &idx : test_data) {
// 设置输入层的值
for (size_t i = 0; i < INNODE; i++) {
inputLayer[i]->value = idx.in[i];
}
// 计算隐藏层的值
for (size_t j = 0; j < HIDENODE; j++) {
double sum = 0;
for (size_t i = 0; i < INNODE; i++) {
sum += inputLayer[i]->value * inputLayer[i]->weight[j];
}
sum -= hideLayer[j]->bias;
hideLayer[j]->value = utils::sigmoid(sum);
}
// 计算输出层的值
for (size_t j = 0; j < OUTNODE; j++) {
double sum = 0;
for (size_t i = 0; i < HIDENODE; i++) {
sum += hideLayer[i]->value * hideLayer[i]->weight[j];
}
sum -= outLayer[j]->bias;
outLayer[j]->value = utils::sigmoid(sum);
idx.out.push_back(outLayer[j]->value);
// 输出结果
for (auto &tmp : idx.in) {
cout << tmp << " ";
}
for (auto &tmp : idx.out) {
cout << tmp << " ";
}
cout << endl;
}
}
}多隐藏层流程
每个隐藏层的输出都依赖于前一层的输出,误差梯度需要逐层反向传播。
1. 前向传播
前向传播是指从输入层到输出层的计算流程,具体过程如下:
- 输入层到第一个隐藏层: 输入层的节点直接与第一个隐藏层的节点相连。对于每个隐藏层节点,计算所有输入层节点的加权和,减去偏置,并通过激活函数(使用sigmoid函数)。
- 隐藏层之间的传播: 每个隐藏层的输出成为下一个隐藏层的输入。对于每个隐藏层节点,计算前一隐藏层所有节点的加权和,减去偏置,并通过激活函数。
- 最后一个隐藏层到输出层: 最后一个隐藏层的输出经过加权和和偏置调整后,通过激活函数形成输出层的值。
代码
void forwardPropagation(const Sample& sample) {
// 设置输入层的值
for (size_t i = 0; i < INNODE; ++i) {
inputLayer[i]->value = sample.in[i];
}
// 逐层计算隐藏层的值
for (size_t l = 0; l < hiddenLayers.size(); ++l) {
for (size_t j = 0; j < hiddenLayers[l].size(); ++j) {
double sum = 0;
for (size_t i = 0; i < (l == 0 ? INNODE : hiddenLayers[l - 1].size()); ++i) {
double inputVal = (l == 0) ? sample.in[i] : hiddenLayers[l - 1][i]->value;
sum += inputVal * hiddenLayers[l][j]->weight[i];
}
sum -= hiddenLayers[l][j]->bias;
hiddenLayers[l][j]->value = utils::sigmoid(sum);
}
}
// 计算输出层的值
for (size_t i = 0; i < OUTNODE; ++i) {
double sum = 0;
for (size_t j = 0; j < hiddenLayers.back().size(); ++j) {
sum += hiddenLayers.back()[j]->value * outLayer[i]->weight[j];
}
sum += outLayer[i]->bias;
outLayer[i]->value = utils::sigmoid(sum);
}
}2. 反向传播
反向传播用于计算误差并更新网络中的权重和偏置,具体过程如下
- 输出层误差和梯度计算: 首先计算输出层节点的误差(实际输出yyy与期望输出y^\hat{y}y^之差)。然后,计算这些误差关于激活函数的导数。
- 隐藏层误差和梯度传播: 误差从输出层反向传播到每个隐藏层。对于每个隐藏层节点,计算其对应的误差,这通常是由后一层的误差和权重导出的。然后,计算误差关于激活函数的导数。
- 权重和偏置更新: 以学习率为标准,利用计算出的梯度更新每层的权重和偏置。
代码
void backwardPropagation(const Sample& sample) {
// 计算输出层的误差...
double error_sum = 0.0; // 初始化平方误差和
// 计算输出层的误差
for (size_t i = 0; i < OUTNODE; ++i) {
double tmp = sample.out[i] - outLayer[i]->value;
error_sum += tmp * tmp /2; // 累加平方误差
}
error_max = max(error_max, error_sum);
// 首先,计算输出层的误差梯度和偏置变化
vector<double> outputGradients(OUTNODE);
for (size_t i = 0; i < OUTNODE; ++i) {
double delta = sample.out[i] - outLayer[i]->value; // 误差
outputGradients[i] = delta * outLayer[i]->value * (1 - outLayer[i]->value); // 梯度
}
// 反向传播到隐藏层
for (int l = hiddenLayers.size() - 1; l >= 0; --l) {
vector<double> hiddenGradients(hiddenLayers[l].size());
for (size_t j = 0; j < hiddenLayers[l].size(); ++j) {
double error = 0.0;
if (l == hiddenLayers.size() - 1) {
// 对于最后一个隐藏层,直接与输出层相关联
for (size_t k = 0; k < OUTNODE; ++k) {
error += outputGradients[k] * hiddenLayers[l][j]->weight[k];
}
} else {
// 对于其他隐藏层,与下一层相关联
for (size_t k = 0; k < hiddenLayers[l + 1].size(); ++k) {
error += hiddenGradients[k] * hiddenLayers[l + 1][k]->weight[j];
}
}
hiddenGradients[j] = error * hiddenLayers[l][j]->value * (1 - hiddenLayers[l][j]->value);
}
// 更新权重和偏置的变化量
for (size_t i = 0; i < hiddenLayers[l].size(); ++i) {
hiddenLayers[l][i]->bias_delta += hiddenGradients[i];
for (size_t k = 0; k < (l == 0 ? INNODE : hiddenLayers[l - 1].size()); ++k) {
double inputVal = (l == 0) ? sample.in[k] : hiddenLayers[l - 1][k]->value;
hiddenLayers[l][i]->weight_delta[k] += hiddenGradients[i] * inputVal;
}
}
}
// 更新输出层权重和偏置的变化量
for (size_t i = 0; i < OUTNODE; ++i) {
outLayer[i]->bias_delta += outputGradients[i];
for (size_t j = 0; j < hiddenLayers.back().size(); ++j) {
outLayer[i]->weight_delta[j] += outputGradients[i] * hiddenLayers.back()[j]->value;
}
}
}数字距离测试
实现功能:输入两个值,若两个值差的绝对值小于0.05,则输出1,否则输出0
初始化:输入层节点为2,隐藏层节点为4,输出层节点为1,学习率0.8,误差阈值10−410^{-4}10−4
train数据集样例,完整用例见traindata.txt
0 0 0
0 1 1
1 0 1
1 1 0
0.8 0.8 0
0.6 0.6 0
0.4 0.4 0
0.2 0.2 0
1.0 0.8 1
1.0 0.2 1
0.89 0.99 1
0.523 0.255 1
0.517 0.878 1
0.737 0.741 0
0.555 0.571 0
0.543 0.176 1
0.286 0.425 1
0.865 0.558 1
0.739 0.294 1
0.297 0.992 1
0.258 0.851 1
0.779 0.769 0
0.825 0.102 1
0.091 0.727 1
0.250 0.147 1
0.376 0.352 0
0.524 0.548 0
0.474 0.452 0
0.626 0.648 0test数据集样例,完整用例见testdata.txt
0.111 0.112
0.001 0.999
0.123 0.345
0.123 0.456
0.123 0.789
0.234 0.567
0.234 0.678
0.387 0.401
0.616 0.717
0.701 0.919
0.701 0.720
0.212 0.313
0.212 0.222实际输出
根据输出结果能看出,正确率为100%(前两个数字差的绝对值小于0.05,输出应该为0)在此简单分类的测试时,BP网络发挥很好

0.111 0.112 0.00887687
0.001 0.999 1
0.123 0.345 1
0.123 0.456 1
0.123 0.789 1
0.234 0.567 1
0.234 0.678 1
0.387 0.401 0.00927063
0.616 0.717 0.999986
0.701 0.919 1
0.701 0.72 0.00891922
0.212 0.313 0.999989
0.212 0.222 0.0109173西瓜数据集测试
从网上取得小量西瓜数据集,来源https://gist.github.com/zhenghaoz/abb86b21797b6274b0fcb1dfc96274b2,根据西瓜的各项指标来判断是否是好瓜,好瓜输出1,否则输出0
需要将输入层节点修改为8,隐藏层节点修改为10
编号,色泽,根蒂,敲声,纹理,脐部,触感,密度,含糖率,好瓜
1,青绿,蜷缩,浊响,清晰,凹陷,硬滑,0.697,0.460,是
2,乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,0.774,0.376,是
3,乌黑,蜷缩,浊响,清晰,凹陷,硬滑,0.634,0.264,是
4,青绿,蜷缩,沉闷,清晰,凹陷,硬滑,0.608,0.318,是
5,浅白,蜷缩,浊响,清晰,凹陷,硬滑,0.556,0.215,是
6,青绿,稍蜷,浊响,清晰,稍凹,软粘,0.403,0.237,是
7,乌黑,稍蜷,浊响,稍糊,稍凹,软粘,0.481,0.149,是
8,乌黑,稍蜷,浊响,清晰,稍凹,硬滑,0.437,0.211,是
9,乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,0.666,0.091,否
10,青绿,硬挺,清脆,清晰,平坦,软粘,0.243,0.267,否
11,浅白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否
12,浅白,蜷缩,浊响,模糊,平坦,软粘,0.343,0.099,否
13,青绿,稍蜷,浊响,稍糊,凹陷,硬滑,0.639,0.161,否
14,浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,0.657,0.198,否
15,乌黑,稍蜷,浊响,清晰,稍凹,软粘,0.360,0.370,否
16,浅白,蜷缩,浊响,模糊,平坦,硬滑,0.593,0.042,否
17,青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,0.719,0.103,否将上述数据简单转换,得到面的训练数据(traindata_g.txt)
1 1 1 1 1 1 0.697 0.460 1
2 1 2 1 1 1 0.774 0.376 1
2 1 1 1 1 1 0.634 0.264 1
1 1 2 1 1 1 0.608 0.318 1
3 1 1 1 1 1 0.556 0.215 1
1 2 1 1 2 2 0.403 0.237 1
2 2 1 2 2 2 0.481 0.149 1
2 2 1 1 2 1 0.437 0.211 1
2 2 2 2 2 1 0.666 0.091 0
1 3 3 1 3 2 0.243 0.267 0
3 3 3 3 3 1 0.245 0.057 0
3 1 1 3 3 2 0.343 0.099 0
1 2 1 2 1 1 0.639 0.161 0
3 2 2 2 1 1 0.657 0.198 0
2 2 1 1 2 2 0.360 0.370 0
3 1 1 3 3 1 0.593 0.042 0
1 1 2 2 2 1 0.719 0.103 0下面是测试数据(testdata_g.txt)
3 1 2 2 2 1 0.227 0.160
3 1 2 2 3 1 0.474 0.176
3 2 3 2 3 2 0.634 0.264
1 3 2 3 3 1 0.408 0.318
1 1 3 2 3 2 0.356 0.215
1 2 3 3 2 2 0.203 0.137
2 3 3 2 2 3 0.281 0.149
2 3 3 3 2 3 0.237 0.111
2 3 3 3 2 1 0.666 0.091
1 1 1 1 1 2 0.443 0.367
1 1 2 1 1 1 0.445 0.357
3 1 1 1 1 1 0.543 0.499
1 2 1 1 1 1 0.639 0.361
3 1 1 2 1 1 0.657 0.398
2 1 1 1 3 1 0.461 0.371
1 2 1 1 1 1 0.593 0.442
1 1 2 1 1 1 0.719 0.303实际输出
最后一项是输出,根据结果看出,输出正确率较高,接近100%
Success with 122614 times training.
Maximum error: 9.99991e-05
3 1 2 2 2 1 0.227 0.16 0.000902219
3 1 2 2 3 1 0.474 0.176 0.00800002
3 2 3 2 3 2 0.634 0.264 0.00187203
1 3 2 3 3 1 0.408 0.318 0.000876587
1 1 3 2 3 2 0.356 0.215 0.00235054
1 2 3 3 2 2 0.203 0.137 0.000933375
2 3 3 2 2 3 0.281 0.149 0.0143263
2 3 3 3 2 3 0.237 0.111 0.000985916
2 3 3 3 2 1 0.666 0.091 0.000914765
//下面理论值为1,输出结果是最后一项
1 1 1 1 1 2 0.443 0.367 0.977018
1 1 2 1 1 1 0.445 0.357 0.960767
3 1 1 1 1 1 0.543 0.499 0.965584
1 2 1 1 1 1 0.639 0.361 0.998939
3 1 1 2 1 1 0.657 0.398 0.917984
2 1 1 1 3 1 0.461 0.371 0.997688
1 2 1 1 1 1 0.593 0.442 0.998693
1 1 2 1 1 1 0.719 0.303 0.998758
完整代码
单隐藏层(main.cpp)
#include <iostream>
#include <cmath>
#include <vector>
#include <fstream>
#include <random>
// 输入层节点数
#define INNODE 2
// 隐含层节点数
#define HIDENODE 4
// 输出层节点数
#define OUTNODE 1
// 学习率
double rate = 0.8;
// 误差阈值
double threshold = 1e-4;
// 最大训练次数
size_t mosttimes = 1e6;
using namespace std;
// 样本包括输入和输出
struct Sample {
vector<double> in, out;
};
// 节点包括值、偏置、权重和权重的delta
struct Node {
double value{}, bias{}, bias_delta{};
vector<double> weight, weight_delta;
};
namespace utils {
// sigmoid函数
inline double sigmoid(double x) {
double res = 1.0 / (1.0 + exp(-x));
return res;
}
// 读取文件数据
vector<double> getFileData(string filename) {
vector<double> res;
ifstream in(filename);
if (in.is_open()) {
while (!in.eof()) {
double buffer;
in >> buffer;
res.push_back(buffer);
}
in.close();
} else {
cout << "Error in reading " << filename << endl;
}
return res;
}
// 读取数据,includeOutData为true时,读取的数据包括输入和输出,否则只读取输入
vector<Sample> getData(string filename, bool includeOutData) {
vector<Sample> res;
vector<double> buffer = getFileData(filename);
size_t stepSize = includeOutData ? (INNODE + OUTNODE) : INNODE;
for (size_t i = 0; i < buffer.size(); i += stepSize) {
Sample tmp;
for (size_t t = 0; t < INNODE; t++) {
tmp.in.push_back(buffer[i + t]);
}
if (includeOutData) {
for (size_t t = 0; t < OUTNODE; t++) {
tmp.out.push_back(buffer[i + INNODE + t]);
}
}
res.push_back(tmp);
}
return res;
}
}
Node *inputLayer[INNODE], *hideLayer[HIDENODE], *outLayer[OUTNODE];
inline void init() {
mt19937 rd;
rd.seed(random_device()());
// 生成-1到1的随机数
uniform_real_distribution<double> distribution(-1, 1);
// 初始化权重和偏置
for (size_t i = 0; i < INNODE; i++) {
inputLayer[i] = new Node();
for (size_t j = 0; j < HIDENODE; j++) {
inputLayer[i]->weight.push_back(distribution(rd));
inputLayer[i]->weight_delta.push_back(0.f);
}
}
// 初始化隐藏层的权重和偏置
for (size_t i = 0; i < HIDENODE; i++) {
hideLayer[i] = new Node();
hideLayer[i]->bias = distribution(rd);
for (size_t j = 0; j < OUTNODE; j++) {
hideLayer[i]->weight.push_back(distribution(rd));
hideLayer[i]->weight_delta.push_back(0.f);
}
}
// 初始化输出层的权重和偏置
for (size_t i = 0; i < OUTNODE; i++) {
outLayer[i] = new Node();
outLayer[i]->bias = distribution(rd);
}
}
// 重置权重和偏置的delta
inline void reset_delta() {
for (size_t i = 0; i < INNODE; i++) {
inputLayer[i]->weight_delta.assign(inputLayer[i]->weight_delta.size(), 0.f);
}
for (size_t i = 0; i < HIDENODE; i++) {
hideLayer[i]->bias_delta = 0.f;
hideLayer[i]->weight_delta.assign(hideLayer[i]->weight_delta.size(), 0.f);
}
for (size_t i = 0; i < OUTNODE; i++) {
outLayer[i]->bias_delta = 0.f;
}
}
void forwardPropagation(const Sample idx) {
// 设置输入层的值
for (size_t i = 0; i < INNODE; i++) {
inputLayer[i]->value = idx.in[i];
}
// 计算隐藏层的值
for (size_t j = 0; j < HIDENODE; j++) {
double sum = 0;
for (size_t i = 0; i < INNODE; i++) {
sum += inputLayer[i]->value * inputLayer[i]->weight[j];
}
sum -= hideLayer[j]->bias;
hideLayer[j]->value = utils::sigmoid(sum);
}
// 计算输出层的值
for (size_t j = 0; j < OUTNODE; j++) {
double sum = 0;
for (size_t i = 0; i < HIDENODE; i++) {
sum += hideLayer[i]->value * hideLayer[i]->weight[j];
}
sum -= outLayer[j]->bias;
outLayer[j]->value = utils::sigmoid(sum);
}
}
void backwardPropagation(const Sample idx){
// 反向传播
// 计算输出层的delta_bias,对应\labmda
for (size_t i = 0; i < OUTNODE; i++) {
double bias_delta = -(idx.out[i] - outLayer[i]->value) *
outLayer[i]->value * (1.0 - outLayer[i]->value);
outLayer[i]->bias_delta += bias_delta;
}
// 计算隐藏层的delta_weight,对应v
for (size_t i = 0; i < HIDENODE; i++) {
for (size_t j = 0; j < OUTNODE; j++) {
double weight_delta = (idx.out[j] - outLayer[j]->value) *
outLayer[j]->value * (1.0 - outLayer[j]->value) *
hideLayer[i]->value;
hideLayer[i]->weight_delta[j] += weight_delta;
}
}
// 计算隐藏层的delta_bias,对应\beta
for (size_t i = 0; i < HIDENODE; i++) {
double sum = 0;
for (size_t j = 0; j < OUTNODE; j++) {
sum += -(idx.out[j] - outLayer[j]->value) *
outLayer[j]->value * (1.0 - outLayer[j]->value) *
hideLayer[i]->weight[j];
}
hideLayer[i]->bias_delta +=
sum * hideLayer[i]->value * (1.0 - hideLayer[i]->value);
}
// 计算输入层的delta_weight,对应w
for (size_t i = 0; i < INNODE; i++) {
for (size_t j = 0; j < HIDENODE; j++) {
double sum = 0.f;
for (size_t k = 0; k < OUTNODE; k++) {
sum += (idx.out[k] - outLayer[k]->value) *
outLayer[k]->value * (1.0 - outLayer[k]->value) *
hideLayer[j]->weight[k];
}
inputLayer[i]->weight_delta[j] +=
sum *
hideLayer[j]->value * (1.0 - hideLayer[j]->value) *
inputLayer[i]->value;
}
}
}
void revise(vector<Sample> train_data){
// 更新权重
auto train_data_size = double(train_data.size());
// 更新输入层的权重
for (size_t i = 0; i < INNODE; i++) {
for (size_t j = 0; j < HIDENODE; j++) {
inputLayer[i]->weight[j] +=
rate * inputLayer[i]->weight_delta[j] / train_data_size;
}
}
// 更新隐藏层的权重
for (size_t i = 0; i < HIDENODE; i++) {
hideLayer[i]->bias +=
rate * hideLayer[i]->bias_delta / train_data_size;
for (size_t j = 0; j < OUTNODE; j++) {
hideLayer[i]->weight[j] +=
rate * hideLayer[i]->weight_delta[j] / train_data_size;
}
}
// 更新输出层的权重
for (size_t i = 0; i < OUTNODE; i++) {
outLayer[i]->bias +=
rate * outLayer[i]->bias_delta / train_data_size;
}
}
void predict(vector<Sample> test_data){
for (auto &idx : test_data) {
// 设置输入层的值
for (size_t i = 0; i < INNODE; i++) {
inputLayer[i]->value = idx.in[i];
}
// 计算隐藏层的值
for (size_t j = 0; j < HIDENODE; j++) {
double sum = 0;
for (size_t i = 0; i < INNODE; i++) {
sum += inputLayer[i]->value * inputLayer[i]->weight[j];
}
sum -= hideLayer[j]->bias;
hideLayer[j]->value = utils::sigmoid(sum);
}
// 计算输出层的值
for (size_t j = 0; j < OUTNODE; j++) {
double sum = 0;
for (size_t i = 0; i < HIDENODE; i++) {
sum += hideLayer[i]->value * hideLayer[i]->weight[j];
}
sum -= outLayer[j]->bias;
outLayer[j]->value = utils::sigmoid(sum);
idx.out.push_back(outLayer[j]->value);
// 输出结果
for (auto &tmp : idx.in) {
cout << tmp << " ";
}
for (auto &tmp : idx.out) {
cout << tmp << " ";
}
cout << endl;
}
}
}
int main(int argc, char *argv[]) {
// 初始化
init();
// 读取训练数据
vector<Sample> train_data = utils::getData("traindata.txt",1);
// 训练
for (size_t times = 0; times < mosttimes; times++) {
// 重置权重和偏置的delta
reset_delta();
// 计算误差,用于判断是否停止训练
double error_max = 0.f;
// 遍历训练数据
for (auto &idx : train_data) {
// 正向传播
forwardPropagation(idx);
// 计算误差
double error = 0.f;
for (size_t i = 0; i < OUTNODE; i++) {
double tmp = fabs(outLayer[i]->value - idx.out[i]);
error += tmp * tmp / 2;
}
error_max = max(error_max, error);
// 反向传播
backwardPropagation(idx);
}
// 如果误差小于阈值,停止训练
if (error_max < threshold) {
cout << "Success with " << times + 1 << " times training." << endl;
cout << "Maximum error: " << error_max << endl;
break;
}
// 更新权重
revise(train_data);
}
// 测试
vector<Sample> test_data = utils::getData("testdata.txt",0);
predict(test_data);
return 0;
}多隐藏层(multi_hidden_layers_version.cpp)
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>
#include <random>
using namespace std;
#define INNODE 2
#define OUTNODE 1
double rate = 0.8;
double threshold = 1e-4;
size_t mosttimes = 5e5;
int numberOfHiddenLayers = 1; // 示例:2个隐藏层
vector<int> hiddenLayerSizes = {4}; // 示例:第一个隐藏层有4个节点,第二个有3个节点
double error_max = 0.f; // 初始化最大误差
struct Sample {
vector<double> in, out;
};
struct Node {
double value{}, bias{}, bias_delta{};
vector<double> weight, weight_delta;
};
Node *inputLayer[INNODE], *outLayer[OUTNODE];
vector<vector<Node*>> hiddenLayers;
namespace utils {
inline double sigmoid(double x) {
double res = 1.0 / (1.0 + exp(-x));
return res;
}
vector<double> getFileData(string filename) {
vector<double> res;
ifstream in(filename);
if (in.is_open()) {
while (!in.eof()) {
double buffer;
in >> buffer;
res.push_back(buffer);
}
in.close();
} else {
cout << "Error in reading " << filename << endl;
}
return res;
}
// 读取数据,includeOutData为true时,读取的数据包括输入和输出,否则只读取输入
vector<Sample> getData(string filename, bool includeOutData) {
vector<Sample> res;
vector<double> buffer = getFileData(filename);
size_t stepSize = includeOutData ? (INNODE + OUTNODE) : INNODE;
for (size_t i = 0; i < buffer.size(); i += stepSize) {
Sample tmp;
for (size_t t = 0; t < INNODE; t++) {
tmp.in.push_back(buffer[i + t]);
}
if (includeOutData) {
for (size_t t = 0; t < OUTNODE; t++) {
tmp.out.push_back(buffer[i + INNODE + t]);
}
}
res.push_back(tmp);
}
return res;
}
}
inline void init() {
mt19937 rd;
rd.seed(random_device()());
uniform_real_distribution<double> distribution(-1, 1);
// 初始化输入层
for (int i = 0; i < INNODE; ++i) {
Node* node = new Node();
for (int j = 0; j < hiddenLayerSizes[0]; ++j) {
node->weight.push_back(distribution(rd)); // 随机初始权重
node->weight_delta.push_back(0.0); // 初始权重变化量设置为0
}
inputLayer[i] = node;
}
// 初始化隐藏层
for (int i = 0; i < numberOfHiddenLayers; ++i) {
vector<Node*> layer;
int nodeCount = hiddenLayerSizes[i];
int prevLayerSize = (i == 0) ? INNODE : hiddenLayerSizes[i - 1]; // 前一层的节点数
for (int j = 0; j < nodeCount; ++j) {
Node* node = new Node();
node->bias = distribution(rd);// 随机初始偏置
for (int k = 0; k < prevLayerSize; ++k) {
node->weight.push_back(distribution(rd)); // 对应前一层的每个节点
node->weight_delta.push_back(0.0);
}
layer.push_back(node);
}
hiddenLayers.push_back(layer);
}
// 初始化输出层
for (int i = 0; i < OUTNODE; ++i) {
Node* node = new Node();
node->bias = distribution(rd); // 随机初始偏置
for (int j = 0; j < hiddenLayerSizes.back(); ++j) {
node->weight.push_back(distribution(rd)); // 随机初始权重
node->weight_delta.push_back(0.0); // 初始权重变化量设置为0
}
outLayer[i] = node;
}
}
void forwardPropagation(const Sample& sample) {
// 设置输入层的值
for (size_t i = 0; i < INNODE; ++i) {
inputLayer[i]->value = sample.in[i];
}
// 逐层计算隐藏层的值
for (size_t l = 0; l < hiddenLayers.size(); ++l) {
for (size_t j = 0; j < hiddenLayers[l].size(); ++j) {
double sum = 0;
for (size_t i = 0; i < (l == 0 ? INNODE : hiddenLayers[l - 1].size()); ++i) {
double inputVal = (l == 0) ? sample.in[i] : hiddenLayers[l - 1][i]->value;
sum += inputVal * hiddenLayers[l][j]->weight[i];
}
sum -= hiddenLayers[l][j]->bias;
hiddenLayers[l][j]->value = utils::sigmoid(sum);
}
}
// 计算输出层的值
for (size_t i = 0; i < OUTNODE; ++i) {
double sum = 0;
for (size_t j = 0; j < hiddenLayers.back().size(); ++j) {
sum += hiddenLayers.back()[j]->value * outLayer[i]->weight[j];
}
sum += outLayer[i]->bias;
outLayer[i]->value = utils::sigmoid(sum);
}
}
void backwardPropagation(const Sample& sample) {
// 计算输出层的误差...
double error_sum = 0.0; // 初始化平方误差和
// 计算输出层的误差
for (size_t i = 0; i < OUTNODE; ++i) {
double tmp = sample.out[i] - outLayer[i]->value;
error_sum += tmp * tmp /2; // 累加平方误差
}
error_max = max(error_max, error_sum);
// 首先,计算输出层的误差梯度和偏置变化
vector<double> outputGradients(OUTNODE);
for (size_t i = 0; i < OUTNODE; ++i) {
double delta = sample.out[i] - outLayer[i]->value; // 误差
outputGradients[i] = delta * outLayer[i]->value * (1 - outLayer[i]->value); // 梯度
}
// 反向传播到隐藏层
for (int l = hiddenLayers.size() - 1; l >= 0; --l) {
vector<double> hiddenGradients(hiddenLayers[l].size());
for (size_t j = 0; j < hiddenLayers[l].size(); ++j) {
double error = 0.0;
if (l == hiddenLayers.size() - 1) {
// 对于最后一个隐藏层,直接与输出层相关联
for (size_t k = 0; k < OUTNODE; ++k) {
error += outputGradients[k] * hiddenLayers[l][j]->weight[k];
}
} else {
// 对于其他隐藏层,与下一层相关联
for (size_t k = 0; k < hiddenLayers[l + 1].size(); ++k) {
error += hiddenGradients[k] * hiddenLayers[l + 1][k]->weight[j];
}
}
hiddenGradients[j] = error * hiddenLayers[l][j]->value * (1 - hiddenLayers[l][j]->value);
}
// 更新权重和偏置的变化量
for (size_t i = 0; i < hiddenLayers[l].size(); ++i) {
hiddenLayers[l][i]->bias_delta += hiddenGradients[i];
for (size_t k = 0; k < (l == 0 ? INNODE : hiddenLayers[l - 1].size()); ++k) {
double inputVal = (l == 0) ? sample.in[k] : hiddenLayers[l - 1][k]->value;
hiddenLayers[l][i]->weight_delta[k] += hiddenGradients[i] * inputVal;
}
}
}
// 更新输出层权重和偏置的变化量
for (size_t i = 0; i < OUTNODE; ++i) {
outLayer[i]->bias_delta += outputGradients[i];
for (size_t j = 0; j < hiddenLayers.back().size(); ++j) {
outLayer[i]->weight_delta[j] += outputGradients[i] * hiddenLayers.back()[j]->value;
}
}
}
inline void updateWeightsAndBiases(const vector<Sample>& data) {
// 使用数据集的大小来计算平均
double train_data_size = static_cast<double>(data.size());
// 更新输入层到第一个隐藏层的权重
for (size_t i = 0; i < INNODE; ++i) {
for (size_t j = 0; j < hiddenLayerSizes[0]; ++j) {
inputLayer[i]->weight[j] -= rate * inputLayer[i]->weight_delta[j] / train_data_size;
inputLayer[i]->weight_delta[j] = 0; // 重置权重变化量
}
}
// 更新隐藏层的权重和偏置
for (size_t l = 0; l < hiddenLayers.size(); ++l) {
for (size_t i = 0; i < hiddenLayers[l].size(); ++i) {
hiddenLayers[l][i]->bias -= rate * hiddenLayers[l][i]->bias_delta / train_data_size;
hiddenLayers[l][i]->bias_delta = 0; // 重置偏置变化量
for (size_t j = 0; j < hiddenLayers[l][i]->weight.size(); ++j) {
hiddenLayers[l][i]->weight[j] -= rate * hiddenLayers[l][i]->weight_delta[j] / train_data_size;
hiddenLayers[l][i]->weight_delta[j] = 0; // 重置权重变化量
}
}
}
// 更新输出层的权重和偏置
for (size_t i = 0; i < OUTNODE; ++i) {
outLayer[i]->bias -= rate * outLayer[i]->bias_delta / train_data_size;
outLayer[i]->bias_delta = 0; // 重置偏置变化量
for (size_t j = 0; j < hiddenLayers.back().size(); ++j) {
outLayer[i]->weight[j] -= rate * outLayer[i]->weight_delta[j] / train_data_size;
outLayer[i]->weight_delta[j] = 0; // 重置权重变化量
}
}
}
inline void reset_delta() {
for (size_t i = 0; i < INNODE; i++) {
inputLayer[i]->weight_delta.assign(::inputLayer[i]->weight_delta.size(), 0.f);
}
for (size_t l = 0; l < hiddenLayers.size(); ++l) {
for (size_t i = 0; i < hiddenLayers[l].size(); ++i) {
hiddenLayers[l][i]->bias_delta = 0; // 重置偏置变化量
for (size_t j = 0; j < hiddenLayers[l][i]->weight.size(); ++j) {
hiddenLayers[l][i]->weight_delta[j] = 0; // 重置权重变化量
}
}
}
for (size_t i = 0; i < OUTNODE; i++) {
outLayer[i]->bias_delta = 0.f;
}
}
int main() {
// 初始化神经网络
init();
cout<<"init finished"<<endl;
// 加载训练数据
vector<Sample> train_data = utils::getData("traindata.txt",1);
cout<<"load data finished"<<endl;
// 训练过程
size_t epoch;
for (epoch = 0; epoch < mosttimes; ++epoch) {
error_max=0.0;
for (const auto& sample : train_data) {
// 重置权重和偏置的delta
reset_delta();
// 正向传播
forwardPropagation(sample);
// 反向传播,并获取最大误差
backwardPropagation(sample);
// 更新权重和偏置
updateWeightsAndBiases(train_data);
}
// 检查是否应该提前停止
if (error_max < threshold && epoch > 100) {
cout << "Success with " << epoch + 1 << " times training." << endl;
cout << "Maximum error: " << error_max << endl;
break;
}
}
if (epoch == mosttimes) {
cout << "Reached maximum iterations. " << endl;
cout << "Maximum error: " << error_max << endl;
}
// 加载测试数据
vector<Sample> test_data = utils::getData("testdata.txt",0);
// 测试过程
for (const auto& sample : test_data) {
// 对每个测试样本进行正向传播以获得预测结果
forwardPropagation({sample}); // 注意:这里我们将单个样本包装成一个向量
// 输出输入值和预测结果
for (auto val : sample.in) {
cout << val << " ";
}
for (size_t i = 0; i < OUTNODE; ++i) {
cout << outLayer[i]->value << " ";
}
cout << endl;
}
return 0;
}- 文章作者:
- Lthero博客
- 版权声明:
- 本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议。转载请注明来自 Lthero博客!



