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

目录
  • 一、神经网络的构建
  • 二、系统架构
    • 服务器
    • 客户端
    • 采用MVC架构

一、神经网络的构建

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

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

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

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

  • 一般取(输入+输出)/2
  • 隐层一般小于输入层

3)(输入层+1)/2

  • log(输入层)
  • log(输入层)+10

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

(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神经网络的类(自己写的,有错误请指出):

/**
 * BP神经网络类
 * 使用了附加动量法进行优化
 * 主要使用方法:
 *     初始化:   BP bp = new BP(new int[]{int,int*n,int})  //第一个int表示输入层,中间n个int表示隐藏层,最后一个int表示输出层
 *     训练: bp.train(double[],double[])               //第一个double[]表示输入,第二个double[]表示期望输出
 *     测试       int result = bp.test(double[])            //参数表示输入,返回值表示输出层最大权值
 *     另有设置学习率和动量参数方法
 */
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架构

  • Model(模型)表示应用程序核心。
  • View(视图)显示数据。
  • Controller(控制器)处理输入。

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

以下是系统架构:

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

(0)

相关推荐

  • Java使用Tesseract-Ocr识别数字

    前言 Tesseract-Ocr是我在编写爬虫项目中,用来识别图片(不是验证码)的本地解决方案(因为客户不想使用API识别,太贵),识别率目前达到了100%,可以说是相当了得,当然了,这取决于使用的traineddata. 简介 Tesseract最初是在1985年至1994年间在Hewlett-Packard Laboratories Bristol和Greeley Colorado的Hewlett-Packard Co开发的,1996年进行了一些更改,移植到Windows,并且随着C++在1

  • java 百度手写文字识别接口配置代码

    代码如下所示: package org.fh.util; import org.json.JSONObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.List; import java.util.Map; /** * 说明:获取文字识别token类 * 作者:

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

    目录 一.神经网络的构建 二.系统架构 服务器 客户端 采用MVC架构 一.神经网络的构建 (1):构建神经网络层次结构 由训练集数据可知,手写输入的数据维数为784维,而对应的输出结果为分别为0-9的10个数字,所以根据训练集的数据可知,在构建的神经网络的输入层的神经元的节点个数为784个,而对应的输出层的神经元个数为10个.隐层可选择单层或多层. (2):确定隐层中的神经元的个数 因为对于隐层的神经元个数的确定目前还没有什么比较完美的解决方案,所以对此经过自己查阅书籍和上网查阅资料,有以下的

  • pytorch教程实现mnist手写数字识别代码示例

    目录 1.构建网络 2.编写训练代码 3.编写测试代码 4.指导程序train和test 5.完整代码 1.构建网络 nn.Moudle是pytorch官方指定的编写Net模块,在init函数中添加需要使用的层,在foeword中定义网络流向. 下面详细解释各层: conv1层:输入channel = 1 ,输出chanael = 10,滤波器5*5 maxpooling = 2*2 conv2层:输入channel = 10 ,输出chanael = 20,滤波器5*5, dropout ma

  • PyTorch CNN实战之MNIST手写数字识别示例

    简介 卷积神经网络(Convolutional Neural Network, CNN)是深度学习技术中极具代表的网络结构之一,在图像处理领域取得了很大的成功,在国际标准的ImageNet数据集上,许多成功的模型都是基于CNN的. 卷积神经网络CNN的结构一般包含这几个层: 输入层:用于数据的输入 卷积层:使用卷积核进行特征提取和特征映射 激励层:由于卷积也是一种线性运算,因此需要增加非线性映射 池化层:进行下采样,对特征图稀疏处理,减少数据运算量. 全连接层:通常在CNN的尾部进行重新拟合,减

  • Tensorflow训练MNIST手写数字识别模型

    本文实例为大家分享了Tensorflow训练MNIST手写数字识别模型的具体代码,供大家参考,具体内容如下 import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data INPUT_NODE = 784 # 输入层节点=图片像素=28x28=784 OUTPUT_NODE = 10 # 输出层节点数=图片类别数目 LAYER1_NODE = 500 # 隐藏层节点数,只有一个隐藏层 BATCH

  • 如何将tensorflow训练好的模型移植到Android (MNIST手写数字识别)

    [尊重原创,转载请注明出处]https://blog.csdn.net/guyuealian/article/details/79672257 项目Github下载地址:https://github.com/PanJinquan/Mnist-tensorFlow-AndroidDemo 本博客将以最简单的方式,利用TensorFlow实现了MNIST手写数字识别,并将Python TensoFlow训练好的模型移植到Android手机上运行.网上也有很多移植教程,大部分是在Ubuntu(Linu

  • Python实战小项目之Mnist手写数字识别

    目录 程序流程分析图: 传播过程: 代码展示: 创建环境 准备数据集 下载数据集 下载测试集 绘制图像 搭建神经网络 训练模型 测试模型 保存训练模型 运行结果展示: 程序流程分析图: 传播过程: 代码展示: 创建环境 使用<pip install+包名>来下载torch,torchvision包 准备数据集 设置一次训练所选取的样本数Batch_Sized的值为512,训练此时Epochs的值为8 BATCH_SIZE = 512 EPOCHS = 8 device = torch.devi

  • Python实战之MNIST手写数字识别详解

    目录 数据集介绍 1.数据预处理 2.网络搭建 3.网络配置 关于优化器 关于损失函数 关于指标 4.网络训练与测试 5.绘制loss和accuracy随着epochs的变化图 6.完整代码 数据集介绍 MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度手写数字图片,且内置于keras.本文采用Tensorflow下Keras(Keras中文文档)神经网络API进行网络搭建. 开始之前,先回忆下机器学习

  • 纯numpy卷积神经网络实现手写数字识别的实践

    前面讲解了使用纯numpy实现数值微分和误差反向传播法的手写数字识别,这两种网络都是使用全连接层的结构.全连接层存在什么问题呢?那就是数据的形状被“忽视”了.比如,输入数据是图像时,图像通常是高.长.通道方向上的3维形状.但是,向全连接层输入时,需要将3维数据拉平为1维数据.实际上,前面提到的使用了MNIST数据集的例子中,输入图像就是1通道.高28像素.长28像素的(1, 28, 28)形状,但却被排成1列,以784个数据的形式输入到最开始的Affine层. 图像是3维形状,这个形状中应该含有

  • Python使用gluon/mxnet模块实现的mnist手写数字识别功能完整示例

    本文实例讲述了Python使用gluon/mxnet模块实现的mnist手写数字识别功能.分享给大家供大家参考,具体如下: import gluonbook as gb from mxnet import autograd,nd,init,gluon from mxnet.gluon import loss as gloss,data as gdata,nn,utils as gutils import mxnet as mx net = nn.Sequential() with net.nam

  • Python tensorflow实现mnist手写数字识别示例【非卷积与卷积实现】

    本文实例讲述了Python tensorflow实现mnist手写数字识别.分享给大家供大家参考,具体如下: 非卷积实现 import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data data_path = 'F:\CNN\data\mnist' mnist_data = input_data.read_data_sets(data_path,one_hot=True) #offline da

随机推荐