三层BP

三层BP

程序参考

理论介绍

定义各参数:

/**
  * input vector.输入向量
  */
 private final double[] input;
 /**
  * hidden layer. 隐含层向量
  */
 private final double[] hidden;
 /**
  * output layer. 输出层
  */
 private final double[] output;
 /**
  * target. 期望值
  */
 private final double[] target; 

 /**
  * delta vector of the hidden layer .隐含层delta
  */
 private final double[] hidDelta;
 /**
  * output layer of the output layer. 输出层delta
  */
 private final double[] optDelta; 

 /**
  * learning rate. 学习率
  */
 private final double eta;
 /**
  * momentum. 动量系数
  */
 private final double momentum; 

 /**
  * weight matrix from input layer to hidden layer.输入层到隐含层权值矩阵
  */
 private final double[][] iptHidWeights;
 /**
  * weight matrix from hidden layer to output layer. 隐含层到输出层权值矩阵
  */
 private final double[][] hidOptWeights; 

 /**
  * previous weight update. 上一个输出层到隐含层权值矩阵
  */
 private final double[][] iptHidPrevUptWeights;
 /**
  * previous weight update. 上一个隐含层到输出层权值矩阵
  */
 private final double[][] hidOptPrevUptWeights; 

 public double optErrSum = 0d; 

 public double hidErrSum = 0d; 

 private final Random random; 

定义构造器,初始参数:

/**
  * Constructor. 构造器
  * 初始各个参数
  *
  * @param inputSize 输入层大小
  * @param hiddenSize  隐含层大小
  * @param outputSize 输出层大小
  * @param eta 学习率
  * @param momentum 动量系数
  * @param epoch
  */
 public BP(int inputSize, int hiddenSize, int outputSize, double eta,
   double momentum) { 

  input = new double[inputSize + 1];
  hidden = new double[hiddenSize + 1];
  output = new double[outputSize + 1];
  target = new double[outputSize + 1]; 

  hidDelta = new double[hiddenSize + 1];
  optDelta = new double[outputSize + 1]; 

  iptHidWeights = new double[inputSize + 1][hiddenSize + 1];
  hidOptWeights = new double[hiddenSize + 1][outputSize + 1];
 // 随机初始化权值
  random = new Random(201605);
  randomizeWeights(iptHidWeights);
  randomizeWeights(hidOptWeights); 

  iptHidPrevUptWeights = new double[inputSize + 1][hiddenSize + 1];
  hidOptPrevUptWeights = new double[hiddenSize + 1][outputSize + 1]; 

  this.eta = eta;
  this.momentum = momentum;
 } 

不同构造器

 /**
  * 构造器,初始化各个参数
  * Constructor with default eta = 0.25 and momentum = 0.3.
  *
  * @param inputSize
  * @param hiddenSize
  * @param outputSize
  * @param epoch
  */
 public BP(int inputSize, int hiddenSize, int outputSize) {
  this(inputSize, hiddenSize, outputSize, 0.25, 0.9);
 } 

随机初始化参数

 /**
  * 随机初始化权重矩阵
  * @param matrix
  */
 private void randomizeWeights(double[][] matrix) {
  for (int i = 0, len = matrix.length; i != len; i++)
   for (int j = 0, len2 = matrix[i].length; j != len2; j++) {
    double real = random.nextDouble();
    matrix[i][j] = random.nextDouble() > 0.5 ? real : -real;
   }
 } 

载入训练样本数据,下面程序将数组后移一位,因为需要增加偏置

 /**
  * Load the training data.
  */
 private void loadInput(double[] inData) {
  if (inData.length != input.length - 1) {
   throw new IllegalArgumentException("Size Do Not Match.");
  }
  System.arraycopy(inData, 0, input, 1, inData.length);
 } 
 /**
  * Load the target data.
  *   *  public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)
    从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
    从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。
    被复制的组件的编号等于 length 参数。
    源数组中位置在 srcPos 到 srcPos+length-1 之间的组件被分别复制到目标数组中的 destPos到 destPos+length-1 位置。
  * @param inData
  * @param arg
  */
 private void loadTarget(double[] arg) {
  if (arg.length != target.length - 1) {
   throw new IllegalArgumentException("Size Do Not Match.");
  }
  System.arraycopy(arg, 0, target, 1, arg.length);
 } 

前馈过程

输入层数据经过权值系数转换,再计算累加和

 /**
  * Forward. 前馈过程,layer = f(W^T.X)
  *
  * @param layer0
  * @param layer1
  * @param weight
  */
 private void forward(double[] layer0, double[] layer1, double[][] weight) {
  // threshold unit.
  layer0[0] = 1.0;
  for (int j = 1, len = layer1.length; j != len; ++j) {
   double sum = 0;
   for (int i = 0, len2 = layer0.length; i != len2; ++i)
    sum += weight[i][j] * layer0[i];
   layer1[j] = sigmoid(sum);
  }
 } 

由于只有三层网络:输入层,隐含层,输出层

有两次向前计算的过程:输入层到隐含层,隐含层到输出层

 /**
  * Forward.两个前馈:输入层到隐含层,隐含层到输出层,这是一个 三层神经网络
  */
 private void forward() {
  forward(input, hidden, iptHidWeights);
  forward(hidden, output, hidOptWeights);
 } 

反馈过程

隐含层到输出层delta计算

 /**
  * Calculate output error.  计算输出层更新delta,下面的optErrSum没有用到
  * o * (1d - o) * (target[idx] - o); 这里定义的激活函数是sigmoid函数f(x) = 1/(1+e^(-x)),其导数是f(x)(1-f(x))
  */
 private void outputErr() {
  double errSum = 0;
  for (int idx = 1, len = optDelta.length; idx != len; ++idx) {
   double o = output[idx];
   optDelta[idx] = derivativeSigmoid(o)* (target[idx] - o);
   errSum += Math.abs(optDelta[idx]);
  }
  optErrSum = errSum;
 } 

输入层到隐含层delta计算

 /**
  * Calculate hidden errors. 计算隐含层更新delta,hidErrSum没有用到
  */
 private void hiddenErr() {
  double errSum = 0;
  for (int j = 1, len = hidDelta.length; j != len; ++j) {
   double o = hidden[j];
   double sum = 0;
   for (int k = 1, len2 = optDelta.length; k != len2; ++k)
    sum += hidOptWeights[j][k] * optDelta[k];
   hidDelta[j] = derivativeSigmoid(o)* sum;
   errSum += Math.abs(hidDelta[j]);
  }
  hidErrSum = errSum;
 } 

说明下,上面的两个ErrSum没有用到,原作者程序里面就有,同时我增加了可以选取tanh作为激活函数

上面的delta更新按照下面的规则

计算两次delta更新,要先计算隐含层到输出层,再计算输入层到隐含层,这是两次反馈的过程

 /**
  * Calculate errors of all layers. 输出层 更新delta 隐含层更新delta
  */
 private void calculateDelta() {
  outputErr();
  hiddenErr();
 } 

delta计算出来后,进行更新权值系数

/**
  * Adjust the weight matrix. 更新weight矩阵,增加了动量系数
  *
  * @param delta
  * @param layer
  * @param weight
  * @param prevWeight
  */
 private void adjustWeight(double[] delta, double[] layer,
   double[][] weight, double[][] prevWeight) { 

  layer[0] = 1;
  for (int i = 1, len = delta.length; i != len; ++i) {
   for (int j = 0, len2 = layer.length; j != len2; ++j) {
    double newVal = momentum * prevWeight[j][i] + eta * delta[i]  * layer[j];
    weight[j][i] += newVal;
    prevWeight[j][i] = newVal;
   }
  }
 } 

 /**
  * Adjust all weight matrices. 输出层到隐含层,输入层到隐含层,两个权值矩阵进行更新
  */
 private void adjustWeight() {
  adjustWeight(optDelta, hidden, hidOptWeights, hidOptPrevUptWeights);
  adjustWeight(hidDelta, input, iptHidWeights, iptHidPrevUptWeights);
 } 

两次计算过程,输入层到隐含层,隐含层到输出层

上面的权值更新利用到了动量系数,但是会发现,动量系数的更新和上面的公式不一样,神经网络与机器学习书上公式推导的时候是上面图片中的规则,而最后伪代码写的是上面代码中方式的规则,这里我不明白为什么。

不同的激活函数

 /**
  * Sigmoid.
  *
  * @param val
  * @return
  */
 private double sigmoid(double val) {
  return 1d / (1d + Math.exp(-val));
 }
 private double tanh(double val) {
     double a = Math.pow(Math.E, val);
     double b = Math.pow(Math.E, -val);
      return (a-b)/(a+b);
 }
 private double derivativeSigmoid(double val){
     double f = sigmoid(val);
     return f*(1-f);
 }
 private double derivativeTanh(double val){
     double f = tanh(val);
     return 1- Math.pow(f,2);
 }

训练模型

 /**
  * 训练模型,注意每次需要一个训练集样本数据进行训练
  * Entry method. The train data should be a one-dim vector.
  *
  * @param trainData
  * @param target
  */
 public void train(double[] trainData, double[] target) {
  loadInput(trainData); // 载入数据
  loadTarget(target);
  forward(); // 前馈
  calculateDelta();// 计算delta值
  adjustWeight(); // 调整权值矩阵
 }

测试数据,只需要一个前馈过程就好了

 /**
  * 测试模型,每次也只能测试一条样本数据
  * Test the BPNN.
  *
  * @param inData
  * @return
  */
 public double[] test(double[] inData) {
  if (inData.length != input.length - 1) {
   throw new IllegalArgumentException("Size Do Not Match.");
  }
  System.arraycopy(inData, 0, input, 1, inData.length);
  forward();
  return getNetworkOutput();
 } 

上面用到的返回输出结果

 /**
  * 返回输出层
  * Return the output layer.
  *
  * @return
  */
 private double[] getNetworkOutput() {
  int len = output.length;
  double[] temp = new double[len - 1];
  for (int i = 1; i != len; i++)
   temp[i - 1] = output[i];
  return temp;
 } 

上面训练模型很差的画了一个图

上面链接中给的程序,判断数是正数还是负数。

定义输入层32个神经元,隐含层15个神经元,输出层4个神经元,随机产生1000个数作为训练集,将每个数转换成二进制形式,对每个数训练200次,然后进行测试。

package bp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random; 

public class Test { 

 /**
  * @param args
  * @throws IOException
  */
 public static void main(String[] args) throws IOException {
  BP bp = new BP(32, 15, 4); 

  Random random = new Random();
  List<Integer> list = new ArrayList<Integer>();
  for (int i = 0; i != 1000; i++) {
   int value = random.nextInt();
   list.add(value);
  } 

  for (int i = 0; i != 200; i++) {
   for (int value : list) {
    double[] real = new double[4];
    if (value >= 0)
     if ((value & 1) == 1)
      real[0] = 1;
     else
      real[1] = 1;
    else if ((value & 1) == 1)
     real[2] = 1;
    else
     real[3] = 1;
    double[] binary = new double[32];
    int index = 31;
    do {
     binary[index--] = (value & 1);
     value >>>= 1;
    } while (value != 0); 

    bp.train(binary, real);
   }
  } 

  System.out.println("训练完毕,下面请输入一个任意数字,神经网络将自动判断它是正数还是复数,奇数还是偶数。"); 

  while (true) {
   byte[] input = new byte[10];
   System.in.read(input);
   Integer value = Integer.parseInt(new String(input).trim());
   int rawVal = value;
   double[] binary = new double[32];
   int index = 31;
   do {
    binary[index--] = (value & 1);
    value >>>= 1;
   } while (value != 0); 

   double[] result = bp.test(binary); 

   double max = -Integer.MIN_VALUE;
   int idx = -1; 

   for (int i = 0; i != result.length; i++) {
    if (result[i] > max) {
     max = result[i];
     idx = i;
    }
   } 

   switch (idx) {
   case 0:
    System.out.format("%d是一个正奇数\n", rawVal);
    break;
   case 1:
    System.out.format("%d是一个正偶数\n", rawVal);
    break;
   case 2:
    System.out.format("%d是一个负奇数\n", rawVal);
    break;
   case 3:
    System.out.format("%d是一个负偶数\n", rawVal);
    break;
   }
  }
 } 

}

上面程序测试效果很好

在神经网络中很常见的学习样例就是异或问题

每个样例迭代120次已经很好了

package bp;

public class Test2 {
    public static void main(String[] args){
        //设置样本数据,对应上面的4个二维坐标数据
        double[][] data = new double[][]{{0,0},{0,1},{1,1},{1,0}};
        //设置目标数据,对应4个坐标数据的分类
        double[][] target = new double[][]{{1,0},{0,1},{0,1},{1,0}};
        // BP(int inputSize, int hiddenSize, int outputSize, double eta, double momentum)
        BP bp = new BP(2,3,2);
        int limit = 120;
        while(limit>0){
            for(int i=0;i<data.length;i++){
                bp.train(data[i],target[i]);
            }
            limit--;
        }
        for(int i=0;i<data.length;i++){
            double[] predict= bp.test(data[i]);
            System.out.println("test:"+i);
            for(int j=0;j<predict.length;j++){
                System.out.println(predict[j]+" "+target[i][j]);
            }
        }
    }
}

输出

test:0
0.9891777576155748 1.0
0.00885171294335521 0.0
test:1
0.00596229519260654 0.0
0.9923139692468484 1.0
test:2
0.006437172245489061 0.0
0.9916683784692554 1.0
test:3
0.9913501883953556 1.0
0.006853150891322226 0.0
时间: 2024-09-20 08:59:41

三层BP的相关文章

模式识别:三层BP神经网络的设计与实现

本文的目的是学习和掌握BP神经网络的原理及其学习算法.在MATLAB平台上编程构造一个3-3-1型的singmoid人工神经网络,并使用随机反向传播算法和成批反向传播算法来训练这个网络,这里设置不同的初始权值,研究算法的学习曲线和训练误差.有了以上的理论基础,最后将构造并训练一个3-3-4型的神经网络来分类4个等概率的三维数据集合. 一.技术论述 1.神经网络简述 神经网络是一种可以适应复杂模型的非常灵活的启发式的统计模式识别技术.而反向传播算法是多层神经网络有监督训练中最简单也最一般的方法之一

三层BP神经网络的python实现

这是一个非常漂亮的三层反向传播神经网络的python实现,下一步我准备试着将其修改为多层BP神经网络. 下面是运行演示函数的截图,你会发现预测的结果很惊人! 提示:运行演示函数的时候,可以尝试改变隐藏层的节点数,看节点数增加了,预测的精度会否提升 1 import math 2 import random 3 import string 4 5 random.seed(0) 6 7 # 生成区间[a, b)内的随机数 8 def rand(a, b): 9 return (b-a)*random

BP神经网络模型与学习算法

一,什么是BP "BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一.BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程.它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小.BP神经网络模型拓扑结构包括输入层(input).隐层(hide layer)和输出层(output

BP神经网络原理及C++实战

前一段时间做了一个数字识别的小系统,基于BP神经网络算法的,用MFC做的交互.在实现过程中也试着去找一些源码,总体上来讲,这些源码的可移植性都不好,多数将交互部分和核心算法代码杂糅在一起,这样不仅代码阅读困难,而且重要的是核心算法不具备可移植性.设计模式,设计模式的重要性啊!于是自己将BP神经网络的核心算法用标准C++实现,这样可移植性就有保证的,然后在核心算法上实现基于不同GUI库的交互(MFC,QT)是能很快的搭建好系统的.下面边介绍BP算法的原理(请看<数字图像处理与机器视觉>非常适合做

BP神经网络及matlab实现

本文主要内容包括: (1) 介绍神经网络基本原理,(2) AForge.NET实现前向神经网络的方法,(3) Matlab实现前向神经网络的方法 . 第0节.引例  本文以Fisher的Iris数据集作为神经网络程序的测试数据集.Iris数据集可以在http://en.wikipedia.org/wiki/Iris_flower_data_set  找到.这里简要介绍一下Iris数据集: 有一批Iris花,已知这批Iris花可分为3个品种,现需要对其进行分类.不同品种的Iris花的花萼长度.花萼

BP神经网络公式推导及实现(MNIST)

BP神经网络的基础介绍见:http://blog.csdn.net/fengbingchun/article/details/50274471,这里主要以公式推导为主. BP神经网络又称为误差反向传播网络,其结构如下图.这种网络实质是一种前向无反馈网络,具有结构清晰.易实现.计算功能强大等特点. BP神经网络有一个输入层,一个输出层,一个或多个隐含层.每一层上包含了若干个节点,每个节点代表一个神经元,同一层上各节点之间无任何耦合连接关系,层间各神经元之间实现全连接,即后一层(如输入层)的每一个神

简单易学的机器学习算法——神经网络之BP神经网络

一.BP神经网络的概念 BP神经网络是一种多层的前馈神经网络,其基本的特点是:信号是前向传播的,而误差是反向传播的.详细来说.对于例如以下的仅仅含一个隐层的神经网络模型: (三层BP神经网络模型) BP神经网络的过程主要分为两个阶段.第一阶段是信号的前向传播,从输入层经过隐含层.最后到达输出层:第二阶段是误差的反向传播,从输出层到隐含层.最后到输入层,依次调节隐含层到输出层的权重和偏置,输入层到隐含层的权重和偏置. 二.BP神经网络的流程 在知道了BP神经网络的特点后,我们须要根据信号的前向传播

转载——关于bp神经网络

一.BP神经网络的概念 BP神经网络是一种多层的前馈神经网络,其主要的特点是:信号是前向传播的,而误差是反向传播的.具体来说,对于如下的只含一个隐层的神经网络模型: (三层BP神经网络模型) BP神经网络的过程主要分为两个阶段,第一阶段是信号的前向传播,从输入层经过隐含层,最后到达输出层:第二阶段是误差的反向传播,从输出层到隐含层,最后到输入层,依次调节隐含层到输出层的权重和偏置,输入层到隐含层的权重和偏置. 二.BP神经网络的流程 在知道了BP神经网络的特点后,我们需要依据信号的前向传播和误差

基于BP神经网络的手写数字识别

一.BP神经网络原理及结构 本部分先介绍神经网络基本单元神经元的结构和作用,再主要介绍BP神经网络的结构和原理. 1.神经元 神经元作为神经网络的基本单元,对于外界输入具有简单的反应能力,在数学上表征为简单的函数映射.如下图是一个神经元的基本结构,  神经元结构 图中是神经元的输入,是神经元输入的权重,是神经元的激活函数,y是神经元的输出,其函数映射关系为 激活函数来描述层与层输出之间的关系,从而模拟各层神经元之间的交互反应.激活函数必须满足处处可导的条件.常用的神经元函数有四种,分别是线性函数