BP神经网络—java实现
神经网络的结构
神经网络的网络结构由输入层,隐含层,输出层组成。隐含层的个数+输出层的个数=神经网络的层数,也就是说神经网络的层数不包括输入层。下面是一个三层的神经网络,包含了两层隐含层,一个输出层。其中第一层隐含层的节点数为3,第二层的节点数为2,输出层的节点数为1;输入层为样本的两个特征X1,X2.
图1 三层神经网络
在神经网络中每一个节点的都与上一层的所有节点相连,称为全连接。神经网络的上一层输出的数据是下一层的输入数据。在图中的神经网络中,原始的输入数据,通过第一层隐含层的计算得出的输出数据,会传到第二层隐含层。而第二层的输出,又会作为输出层的输入数据。
神经网络中的每一层(除了输入层)都是由神经元组成,也称为节点。每一个神经元都相当于一个感知器。如下图:
图2 单个神经元
在神经网络中,每个节点都将计算出特征矩阵X与权值矩阵的加权和,得到净输入e,然后通过激励函数f(e)得到该节点的输出y。在图1中,每条连线都可以看做是一个权值。
在神经网络中,可以添加输出层节点的个数来解决多分类问题。有四个类别需要分类则,则输出层的节点个数可以设为4个节点,每一个节点代表一个类别。
BP神经网络的训练过程
神经网络的训练过程分为两个过程:1、向前传播得到预测数据;2、反向传播更新权重。如下图所示:
图3 神经网络的训练过程
第一步、向前传播得到预测数据:向前传播的过程,即数据从输入层输入,经过隐含层,输出层的计算得到预测值,预测值为输出层的输出结果。网络层的输出即,该层中所有节点(神经元)的输出值的集合。我们以图一的神经网络结构为例,分析向前传播过程。
1.得到隐含层的输出y1,y2,y3:
2.获取到第二层的隐含层输出y4,y5,输入的数据也就是第一层隐含层的输出数据y1,y2,y3。
3、通过输出层,得到最后的预测值y。
第二步、反向传播更新权重:根据样本的真实类标,计算模型预测的结果与真实类标的误差。然后将该误差反向传播到各个隐含层。计算出各层的误差,再根据各层的误差,更新权重。
1.计算输出层的误差:其中z为该样本的类标
2计算第二层隐含层的误差
3.计算第一次隐含层的误差:
4、更新权重:新的权值=原权值+学习速率×该节点的误差×激励函数的导函数的值(f(e)的倒数)×与该节点相连的输入值
4.1更新输入层与第一层隐含层之间的权值:
4.2更新第一层隐含层与第二层隐含层之间的权值
4.3更新第二层隐含层与输出层之间的权值
以上就是反向传播的过程。误差从输出层反向的传到输入层,然后再从输入层向前更新权值。
BP神经网络的设计与实现
(一) BP神经网络的设计
1.设计网络的结构:
本次实验采用java语言实现。设计了包含一个隐含层的神经网络,即一个2层的神经网络。
每层都含有一个一维X特征矩阵即为输入数据,一个二维W权值矩阵,一个一维的误差矩阵error,同时该神经网络中还包含了一个一维的目标矩阵target,记录样本的真实类标。
X特征矩阵:第一层隐含层的X矩阵的长度为输入层输入数据的特征个数+1,隐含层的X矩阵的长度则是上一层的节点的个数+1,X[0]=1。
W权值矩阵:第一维的长度设计为节点(即神经元)的个数,第二维的长度设计为上一层节点的个数+1;W[0][0]为该节点的偏置量
error误差矩阵:数组长度设计为该层的节点个数。
目标矩阵target:输出层的节点个数与其一致。
激活函数:采用sigmoid函数:1/1+e-x
2.神经网络的计算过程
按照以上的设计,向前传播得到下一层的输出结果,如图所示:
求误差过程,如图所示:
反向传播过程,调整权值,如图所示:
(二) BP神经网络的实现
一、向前传播得到预测数据:
1.初始化权值
2.训练数据集:
2.1、导入训练数据集和目标值;
2.2、向前传播得到输出值;
2.2.1、获取隐含层的输出
2.2.2、获取输出层的输出
二、反向传播更新权重
1、获取输出层的误差;
2、获取隐含层的误差;
3、更新隐含层的权值;
4、更新输出层的权值;
三.测试神经网络
3.3 向前传播得到预测值;
代码如下:
1 public class Bp { 2 3 private double[] hide1_x;//// 输入层即第一层隐含层的输入;hide1_x[数据的特征数目+1], hide1_x[0]为1 4 private double[][] hide1_w;// 隐含层权值,hide1_w[本层的节点的数目][数据的特征数目+1];hide_w[0][0]为偏置量 5 private double[] hide1_errors;// 隐含层的误差,hide1_errors[节点个数] 6 7 private double[] out_x;// 输出层的输入值即第二次层隐含层的输出 out_x[上一层的节点数目+1], out_x[0]为1 8 private double[][] out_w;// 输出层的权值 hide1_w[节点的数目][上一层的节点数目+1]// 9 // out_w[0][0]为偏置量 10 private double[] out_errors;// 输出层的误差 hide1_errors[节点个数] 11 12 private double[] target;// 目标值,target[输出层的节点个数] 13 14 private double rate;// 学习速率 15 16 public Bp(int input_node, int hide1_node, int out_node, double rate) { 17 super(); 18 19 // 输入层即第一层隐含层的输入 20 hide1_x = new double[input_node + 1]; 21 22 // 第一层隐含层 23 hide1_w = new double[hide1_node][input_node + 1]; 24 hide1_errors = new double[hide1_node]; 25 26 // 输出层 27 out_x = new double[hide1_node + 1]; 28 out_w = new double[out_node][hide1_node + 1]; 29 out_errors = new double[out_node]; 30 31 target = new double[out_node]; 32 33 // 学习速率 34 this.rate = rate; 35 init_weight();// 1.初始化网络的权值 36 } 37 38 /** 39 * 初始化权值 40 */ 41 public void init_weight() { 42 43 set_weight(hide1_w); 44 set_weight(out_w); 45 } 46 47 /** 48 * 初始化权值 49 * 50 * @param w 51 */ 52 private void set_weight(double[][] w) { 53 for (int i = 0, len = w.length; i != len; i++) 54 for (int j = 0, len2 = w[i].length; j != len2; j++) { 55 w[i][j] = 0; 56 } 57 } 58 59 /** 60 * 获取原始数据 61 * 62 * @param Data 63 * 原始数据矩阵 64 */ 65 private void setHide1_x(double[] Data) { 66 if (Data.length != hide1_x.length - 1) { 67 throw new IllegalArgumentException("数据大小与输出层节点不匹配"); 68 } 69 System.arraycopy(Data, 0, hide1_x, 1, Data.length); 70 hide1_x[0] = 1.0; 71 } 72 73 /** 74 * @param target 75 * the target to set 76 */ 77 private void setTarget(double[] target) { 78 this.target = target; 79 } 80 81 /** 82 * 2.训练数据集 83 * 84 * @param TrainData 85 * 训练数据 86 * @param target 87 * 目标 88 */ 89 public void train(double[] TrainData, double[] target) { 90 // 2.1导入训练数据集和目标值 91 setHide1_x(TrainData); 92 setTarget(target); 93 94 // 2.2:向前传播得到输出值; 95 double[] output = new double[out_w.length + 1]; 96 forword(hide1_x, output); 97 98 // 2.3、方向传播: 99 backpropagation(output); 100 101 } 102 103 /** 104 * 反向传播过程 105 * 106 * @param output 107 * 预测结果 108 */ 109 public void backpropagation(double[] output) { 110 111 // 2.3.1、获取输出层的误差; 112 get_out_error(output, target, out_errors); 113 // 2.3.2、获取隐含层的误差; 114 get_hide_error(out_errors, out_w, out_x, hide1_errors); 115 //// 2.3.3、更新隐含层的权值; 116 update_weight(hide1_errors, hide1_w, hide1_x); 117 // * 2.3.4、更新输出层的权值; 118 update_weight(out_errors, out_w, out_x); 119 } 120 121 /** 122 * 预测 123 * 124 * @param data 125 * 预测数据 126 * @param output 127 * 输出值 128 */ 129 public void predict(double[] data, double[] output) { 130 131 double[] out_y = new double[out_w.length + 1]; 132 setHide1_x(data); 133 forword(hide1_x, out_y); 134 System.arraycopy(out_y, 1, output, 0, output.length); 135 136 } 137 138 139 public void update_weight(double[] err, double[][] w, double[] x) { 140 141 double newweight = 0.0; 142 for (int i = 0; i < w.length; i++) { 143 for (int j = 0; j < w[i].length; j++) { 144 newweight = rate * err[i] * x[j]; 145 w[i][j] = w[i][j] + newweight; 146 } 147 148 } 149 } 150 151 /** 152 * 获取输出层的误差 153 * 154 * @param output 155 * 预测输出值 156 * @param target 157 * 目标值 158 * @param out_error 159 * 输出层的误差 160 */ 161 public void get_out_error(double[] output, double[] target, double[] out_error) { 162 for (int i = 0; i < target.length; i++) { 163 out_error[i] = (target[i] - output[i + 1]) * output[i + 1] * (1d - output[i + 1]); 164 } 165 166 } 167 168 /** 169 * 获取隐含层的误差 170 * 171 * @param NeLaErr 172 * 下一层的误差 173 * @param Nextw 174 * 下一层的权值 175 * @param output 下一层的输入 176 * @param error 177 * 本层误差数组 178 */ 179 public void get_hide_error(double[] NeLaErr, double[][] Nextw, double[] output, double[] error) { 180 181 for (int k = 0; k < error.length; k++) { 182 double sum = 0; 183 for (int j = 0; j < Nextw.length; j++) { 184 sum += Nextw[j][k + 1] * NeLaErr[j]; 185 } 186 error[k] = sum * output[k + 1] * (1d - output[k + 1]); 187 } 188 } 189 190 /** 191 * 向前传播 192 * 193 * @param x 194 * 输入值 195 * @param output 196 * 输出值 197 */ 198 public void forword(double[] x, double[] output) { 199 200 // 2.2.1、获取隐含层的输出 201 get_net_out(x, hide1_w, out_x); 202 // 2.2.2、获取输出层的输出 203 get_net_out(out_x, out_w, output); 204 205 } 206 207 /** 208 * 获取单个节点的输出 209 * 210 * @param x 211 * 输入矩阵 212 * @param w 213 * 权值 214 * @return 输出值 215 */ 216 private double get_node_put(double[] x, double[] w) { 217 double z = 0d; 218 219 for (int i = 0; i < x.length; i++) { 220 z += x[i] * w[i]; 221 } 222 // 2.激励函数 223 return 1d / (1d + Math.exp(-z)); 224 } 225 226 /** 227 * 获取网络层的输出 228 * 229 * @param x 230 * 输入矩阵 231 * @param w 232 * 权值矩阵 233 * @param net_out 234 * 接收网络层的输出数组 235 */ 236 private void get_net_out(double[] x, double[][] w, double[] net_out) { 237 238 net_out[0] = 1d; 239 for (int i = 0; i < w.length; i++) { 240 net_out[i + 1] = get_node_put(x, w[i]); 241 } 242 243 } 244 245 }
(二) BP神经网络的测试
用上面实现的BP神经网络来训练模型,自动判断它是正数还是复数,奇数还是偶数.
1 public class Test { 2 3 /** 4 * @param args 5 * @throws IOException 6 */ 7 public static void main(String[] args) throws IOException { 8 9 10 Bp bp = new Bp(32, 15, 4, 0.05); 11 12 Random random = new Random(); 13 14 List<Integer> list = new ArrayList<Integer>(); 15 for (int i = 0; i != 6000; i++) { 16 int value = random.nextInt(); 17 list.add(value); 18 } 19 20 for (int i = 0; i !=25; i++) { 21 for (int value : list) { 22 double[] real = new double[4]; 23 if (value >= 0) 24 if ((value & 1) == 1) 25 real[0] = 1; 26 else 27 real[1] = 1; 28 else if ((value & 1) == 1) 29 real[2] = 1; 30 else 31 real[3] = 1; 32 33 double[] binary = new double[32]; 34 int index = 31; 35 do { 36 binary[index--] = (value & 1); 37 value >>>= 1; 38 } while (value != 0); 39 40 bp.train(binary, real); 41 42 43 44 } 45 } 46 47 48 49 50 System.out.println("训练完毕,下面请输入一个任意数字,神经网络将自动判断它是正数还是复数,奇数还是偶数。"); 51 52 while (true) { 53 54 byte[] input = new byte[10]; 55 System.in.read(input); 56 Integer value = Integer.parseInt(new String(input).trim()); 57 int rawVal = value; 58 double[] binary = new double[32]; 59 int index = 31; 60 do { 61 binary[index--] = (value & 1); 62 value >>>= 1; 63 } while (value != 0); 64 65 double[] result =new double[4]; 66 bp.predict(binary,result); 67 68 69 double max = -Integer.MIN_VALUE; 70 int idx = -1; 71 72 for (int i = 0; i != result.length; i++) { 73 if (result[i] > max) { 74 max = result[i]; 75 idx = i; 76 } 77 } 78 79 switch (idx) { 80 case 0: 81 System.out.format("%d是一个正奇数\n", rawVal); 82 break; 83 case 1: 84 System.out.format("%d是一个正偶数\n", rawVal); 85 break; 86 case 2: 87 System.out.format("%d是一个负奇数\n", rawVal); 88 break; 89 case 3: 90 System.out.format("%d是一个负偶数\n", rawVal); 91 break; 92 } 93 } 94 } 95 }
在BP神经网络中, 学习速率,训练集,以及训练次数,都会影响到最终模型的泛化能力。因此,在设计模型时,节点的个数,学习速率的大小,以及训练次数都是需要考虑的。