文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java实现BP神经网络MNIST手写数字识别的示例详解

2023-01-31 15:00

关注

一、神经网络的构建

(1):构建神经网络层次结构

由训练集数据可知,手写输入的数据维数为784维,而对应的输出结果为分别为0-9的10个数字,所以根据训练集的数据可知,在构建的神经网络的输入层的神经元的节点个数为784个,而对应的输出层的神经元个数为10个。隐层可选择单层或多层。

(2):确定隐层中的神经元的个数

因为对于隐层的神经元个数的确定目前还没有什么比较完美的解决方案,所以对此经过自己查阅书籍和上网查阅资料,有以下的几种经验方式来确定隐层的神经元的个数,方式分别如下所示:

3)(输入层+1)/2

实验得到以第五种的方式得到的测试结果相对较高。

(3):设置神经元的激活函数

在《机器学习》的书中介绍了两种比较常用的函数,分别是阶跃函数和Sigmoid函数。最后自己采用了后者函数。

(4):初始化输入层和隐层之间神经元间的权值信息

采用的是使用简单的随机数分配的方法,并且两层之间的神经元权值是通过二维数组进行保留,数组的索引就代表着两层对应的神经元的索引信息

(5):初始化隐层和输出层之间神经元间的权值信息

采用的是使用简单的随机数分配的方法,并且两层之间的神经元权值是通过二维数组进行保留,数组的索引就代表着两层对应的神经元的索引信息

(6):读取CSV测试集表格信息,并加载到程序用数据保存,其中将每个维数的数据都换成了0和1的二进制数进行处理。

(7):读取CSV测试集结果表格信息,并加载到程序用数据保存

(8):计算输入层与隐层中隐层神经元的阈值

这里主要是采用了下面的方法:

Sum=sum+weight[i][j] * layer0[i];

参数的含义:将每个输入层中的神经元与神经元的权值信息weight[i][j]乘以对应的输入层神经元的阈值累加,然后再调用激活函数得到对应的隐层神经元的阈值。

(9):计算隐层与输出层中输出层的神经元的阈值

方法和上面的类似,只是相对应的把权值信息进行了修改即可。

(10):计算误差逆传播(输出层的逆误差)

采用书上P103页的方法(西瓜书)

(11):计算误差传播(隐层的逆误差)

采用书上P103页的方法(西瓜书)

(12):更新各层神经元之间的权值信息

double newVal = momentum * prevWeight[j][i] + eta * delta[i] * layer[j];

参数:其中设置momentum 为0.9,设置eta 为0.25,prevWeight[j][i]表示神经元之间的权值,layer[j]和delta[i]表示两层不同神经元的阈值。

(13):循环迭代训练5次

(14):输入测试集数据

(15):输出测试集预测结果和实际结果进行比较,得到精确度

此处放一个多隐层BP神经网络的类(自己写的,有错误请指出):


import java.util.Random;
 
public class BP {
    
    private final double[][] layers;//输入层、隐含层、输出层
    private final double[][] deltas;//每层误差
    private final double[][][] weights;//权值
    private final double[][][] prevUptWeights;//更新之前的权值信息
    private final double[] target;   //预测的输出内容
    
    private double eta;        //学习率
    private double momentum;    //动量参数
    
    private final Random random;  //主要是对权值采取的是随机产生的方法
    
    //初始化
    public BP(int[] size, double eta, double momentum) {
       int len = size.length;
       //初始化每层
       layers = new double[len][];
       for(int i = 0; i<len; i++) {
           layers[i] = new double[size[i] + 1];
       }
       //初始化预测输出
        target = new double[size[len - 1] + 1];
        
       //初始化隐藏层和输出层的误差
       deltas = new double[len - 1][];
       for(int i = 0; i < (len - 1); i++) {
           deltas[i] = new double[size[i + 1] + 1];
       }
       
       //使每次产生的随机数都是第一次的分配,这是有参数和没参数的区别
        random = new Random(100000);
       //初始化权值
       weights = new double[len - 1][][];
       for(int i = 0; i < (len - 1); i++) {
           weights[i] = new double[size[i] + 1][size[i + 1] + 1];
       }
       randomizeWeights(weights);
       
       //初始化更新前的权值
       prevUptWeights = new double[len - 1][][];
       for(int i = 0; i < (len - 1); i++) {
           prevUptWeights[i] = new double[size[i] + 1][size[i + 1] + 1];
       }
       
        this.eta = eta;             //学习率
        this.momentum = momentum;   //动态量
    }
    
    //随机产生神经元之间的权值信息  
    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++) {
               for(int k = 0, len3 = matrix[i][j].length; k != len3; k++) {
                   double real = random.nextDouble();    //随机分配着产生0-1之间的值  
                   matrix[i][j][k] = random.nextDouble() > 0.5 ? real : -real;
               }
            }
        }
    }
    
    //初始化输入层,隐含层,和输出层  
    public BP(int[] size) {
        this(size, 0.25, 0.9);
    }
    
    //训练数据
    public void train(double[] trainData, double[] target) {
       loadValue(trainData,layers[0]);       //加载输入的数据
       loadValue(target,this.target);         //加载输出的结果数据
        forward();                  //向前计算神经元权值(先算输入到隐含层的,然后再算隐含到输出层的权值)
        calculateDelta();           //计算误差逆传播值 
        adjustWeight();             //调整更新神经元的权值
    }
 
    //加载数据
    private void loadValue(double[] value,double [] layer) {
        if (value.length != layer.length - 1)
            throw new IllegalArgumentException("Size Do Not Match.");
        System.arraycopy(value, 0, layer, 1, value.length);  //调用系统复制数组的方法(存放输入的训练数据)
    }
    
    //向前计算(先算输入到隐含层的,然后再算隐含到输出层的权值)
    private void forward() {
       //计算隐含层到输出层的权值
       for(int i = 0; i < (layers.length - 1); i++) {
           forward(layers[i], layers[i+1], weights[i]);
       } 
    }
    
    //计算每一层的误差(因为在BP中,要达到使误差最小)(就是逆传播算法,书上有P101)
    private void calculateDelta() {
        outputErr(deltas[deltas.length-1],layers[layers.length - 1],target);   //计算输出层的误差(因为要反过来算,所以先算输出层的)
        
        for(int i = (layers.length - 1); i > 1; i--) {
            hiddenErr(deltas[i - 2],layers[i - 1],deltas[i - 1],weights[i - 1]);   //计算隐含层的误差
        }
    }
    
     //更新每层中的神经元的权值信息
    private void adjustWeight() {
       for(int i = (layers.length - 1); i > 0; i--) {
            adjustWeight(deltas[i - 1], layers[i - 1], weights[i - 1], prevUptWeights[i - 1]);
       }
    }
    
    //向前计算各个神经元的权值(layer0:某层的数据,layer1:下一层的内容,weight:某层到下一层的神经元的权值)
    private void forward(double[] layer0, double[] layer1, double[][] weight) {
        layer0[0] = 1.0;//给偏置神经元赋值为1(实际上添加了layer1层每个神经元的阙值)简直漂亮!!!
        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);  //调用神经元的激活函数来得到结果(结果肯定是在0-1之间的)
        }
    }
    
    //计算输出层的误差(delte:误差,output:输出,target:预测输出)
    private void outputErr(double[] delte, double[] output,double[] target) {
        for (int idx = 1, len = delte.length; idx != len; ++idx) {
            double o = output[idx];
            delte[idx] = o * (1d - o) * (target[idx] - o);
        }
    }
    
    //计算隐含层的误差(delta:本层误差,layer:本层,delta1:下一层误差,weights:权值)
    private void hiddenErr(double[] delta, double[] layer, double[] delta1, double[][] weights) {
        for (int j = 1, len = delta.length; j != len; ++j) {
            double o = layer[j];  //神经元权值
            double sum = 0;
            for (int k = 1, len2 = delta1.length; k != len2; ++k)  //由输出层来反向计算
                sum += weights[j][k] * delta1[k];
            delta[j] = o * (1d - o) * sum;
        }
    }
    
    //更新每层中的神经元的权值信息(这也就是不断的训练过程)
    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;  //保存这一次得到的权值,方便下一次进行更新
            }
        }
    }
    
    //我这里用的是sigmoid激活函数,当然也可以用阶跃函数,看自己选择吧 
    private double sigmoid(double val) {
        return 1d / (1d + Math.exp(-val));
    }
    
    //测试神经网络
    public int test(double[] inData) {
        if (inData.length != layers[0].length - 1)
            throw new IllegalArgumentException("Size Do Not Match.");
        System.arraycopy(inData, 0, layers[0], 1, inData.length);
        forward();
        return getNetworkOutput();
    }
    
    //返回最后的输出层的结果
    private int getNetworkOutput() {
        int len = layers[layers.length - 1].length;
        double[] temp = new double[len - 1];
        for (int i = 1; i != len; i++)
            temp[i - 1] = layers[layers.length - 1][i];
        //获得最大权值下标
        double max = temp[0];
        int idx = -1;
        for (int i = 0; i <temp.length; i++) {
            if (temp[i] >= max) {
                max = temp[i];
                idx = i;
            }
        }
        return idx;
    }
    
    //设置学习率
    public void setEta(double eta) {
       this.eta = eta;
    }
    
    //设置动量参数
    public void setMomentum(double momentum){
       this.momentum = momentum;
    }
}

二、系统架构

由于BP神经网络训练过程时间较长,所以采用客户端服务器(C/S)的形式,在服务器进行训练,在客户端直接进行识别,使用套接字进行通讯。

服务器

客户端

采用MVC架构

MNIST数字集经过整理存储在CSV文件中。

以下是系统架构:

到此这篇关于Java实现BP神经网络MNIST手写数字识别的示例详解的文章就介绍到这了,更多相关Java手写数字识别内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯