一个BP神经网络例子

BP(back propagation)神经网络是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播算法训练的多层前馈神经网络,是应用最广泛的神经网络。
BP算法的基本思想是,学习过程由信号的正向传播与误差的反向传播两个过程组成。即计算误差输出时按从输入到输出的方向进行,而调整权值和阈值则从输出到输入的方向进行。
正向传播时,输入信号通过隐含层作用于输出节点,经过非线性变换,产生输出信号,若实际输出与期望输出不相符,则转入误差的反向传播过程。误差反传是将输出误差通过隐含层向输入层逐层反传,并将误差分摊给各层所有单元,以从各层获得的误差信号作为调整各单元权值的依据。通过调整输入节点与隐层节点的联接强度和隐层节点与输出节点的联接强度以及阈值,使误差沿梯度方向下降,经过反复学习训练,确定与最小误差相对应的网络参数(权值和阈值),训练即告停止。此时经过训练的神经网络即能对类似样本的输入信息,自行处理输出误差最小的经过非线形转换的信息。

详细的误差反向传播的推导建议参考这篇博客:误差反向传播算法推导。

误差虽然是反向传播的,但是更新权重的时候却是正向更新的,细心的小伙伴可以去想想这是为什么。

下面实现一个简单的3层BP网络的例子。
输入、隐藏、输出神经元与偏差单元都是2个,假设输入神经元的输入值分别为0.05与0.1,各神经元之间的初始权重如上图,我们希望最后输出的结果为0.01与0.99,或者输出结果与0.01与0.99相差不大。神经元的激活函数为sigmoid函数。

1//BP神经网络 2#include <iostream> 3#include <string.h> 4#include <time.h> 5#include<math.h> 6using namespace std; 7 8class sigmoidNeuron { //sigmoid神经元 9public: 10 double input; 11 double output; 12 double weight[2]; 13}; 14 15class inputNeuron { //输入神经元与偏差单元 16public: 17 double value; 18 double weight[2]; 19 /*inputNeuron(double val);*/ 20}; 21//inputNeuron::inputNeuron(double val) { 22// value = val; 23//} 24 25double sigmoid(double input) { 26 double output = 0; 27 output = 1 / (1 + exp(-input)); 28 return output; 29} 30 31template <class T> 32int getArrayLen(T& array) 33{ 34 return sizeof(array) / sizeof(array[0]); 35} 36 37void forward(inputNeuron(&input)[2], inputNeuron(&bias)[2], sigmoidNeuron(&hidden)[2], sigmoidNeuron(&output)[2]) { //前向计算时应先对神经元的输入进行初始化,不能直接累加 38 for (int i = 0; i < getArrayLen(hidden); i++) { 39 hidden[i].input = 0; 40 } 41 for (int i = 0; i < getArrayLen(output); i++) { 42 output[i].input = 0; 43 } 44 for (int i = 0; i < getArrayLen(hidden); i++) { 45 for (int j = 0; j < getArrayLen(input); j++) { 46 hidden[i].input += input[j].value * input[j].weight[i]; 47 } 48 hidden[i].input += bias[0].value * bias[0].weight[i]; 49 } 50 for (int i = 0; i < getArrayLen(hidden); i++) { 51 hidden[i].output = sigmoid(hidden[i].input); 52 } 53 54 for (int i = 0; i < getArrayLen(output); i++) { 55 for (int j = 0; j < getArrayLen(hidden); j++) { 56 output[i].input += hidden[j].output * hidden[j].weight[i]; 57 } 58 output[i].input += bias[1].value * bias[1].weight[i]; 59 } 60 for (int i = 0; i < getArrayLen(output); i++) { 61 output[i].output = sigmoid(output[i].input); 62 } 63} 64 65void backPropagation(double (&object)[2], inputNeuron(&input)[2], sigmoidNeuron(&hidden)[2], sigmoidNeuron(&output)[2], double (&learningRate)) { //先更新输入层的权重,再更新隐藏层的权重 66 double partialDer = 0.0; 67 for (int i = 0; i < getArrayLen(input); i++) { 68 for (int j = 0; j < getArrayLen(input[i].weight); j++) { 69 for (int k = 0; k < getArrayLen(output); k++) { 70 partialDer += (object[k] - output[k].output) * output[k].output * (1 - output[k].output) * hidden[j].weight[k] * hidden[j].output * (1 - hidden[j].output) * input[i].value; 71 } 72 input[i].weight[j] += learningRate * partialDer; 73 partialDer = 0.0; 74 } 75 } 76 for (int i = 0; i < getArrayLen(hidden); i++) { 77 for (int j = 0; j < getArrayLen(hidden[i].weight); j++) { 78 hidden[i].weight[j] += learningRate * (object[j] - output[j].output) * output[j].output * (1 - output[j].output) * hidden[i].output; 79 } 80 } 81} 82 83bool calError(double(&objVale)[2], sigmoidNeuron(&output)[2]) { 84 bool judge = true; 85 for (int i = 0; i < getArrayLen(objVale); i++) { 86 if (abs(objVale[i] - output[i].output) > 0.2) { 87 judge = false; 88 } 89 } 90 return judge; 91} 92 93int main() 94{ 95 inputNeuron input[2]; 96 input[0].value = 0.05; 97 input[1].value = 0.1; 98 input[0].weight[0] = 0.15; 99 input[0].weight[1] = 0.25; 100 input[1].weight[0] = 0.20; 101 input[1].weight[1] = 0.30; 102 inputNeuron bias[2]; 103 bias[0].value = bias[1].value = 1; 104 bias[0].weight[0] = 0.35; 105 bias[0].weight[1] = 0.35; 106 bias[1].weight[0] = 0.60; 107 bias[1].weight[1] = 0.60; 108 109 sigmoidNeuron hidden[2]; 110 hidden[0].weight[0] = 0.40; 111 hidden[0].weight[1] = 0.50; 112 hidden[1].weight[0] = 0.45; 113 hidden[1].weight[1] = 0.55; 114 sigmoidNeuron output[2]; 115 116 double object[2] = {0.01,0.99}; 117 118 forward(input, bias, hidden, output); 119 120 int iters = 0; 121 double learningRate = 0.5; 122 while (!calError(object,output)) { 123 backPropagation(object, input, hidden, output, learningRate); 124 forward(input, bias, hidden, output); 125 cout << "此次迭代后,输出为:" << output[0].output << " " << output[1].output; 126 cout << endl; 127 iters += 1; 128 } 129 cout << "算法迭代了" << iters << "次才学会了符合要求的权重。"; 130 cout << "最终输出为:" << output[0].output << " " << output[1].output; 131 return 0; 132} 133 134

这里我设置的误差阈值为0.2,即输出神经元与目标函数值之间的误差在0.2以内就停止训练,最终算法仅迭代了79次就学到了满足要求的权重。
在这里插入图片描述

代码交流 2021