神经网络模型搭建及Java实例

首先什么是人工神经网络?简单来说就是将单个感知器作为一个神经网络节点,然后用此类节点组成一个层次网络结构,我们称此网络即为人工神经网络(本人自己的理解)。当网络的层次大于等于3层(输入层+隐藏层(大于等于1)+输出层)时,我们称之为多层人工神经网络。

1、神经单元的选择

  那么我们应该使用什么样的感知器来作为神经网络节点呢?在上一篇文章我们介绍过感知器算法,但是直接使用的话会存在以下问题:

  1)感知器训练法则中的输出

  由于sign函数时非连续函数,这使得它不可微,因而不能使用上面的梯度下降算法来最小化损失函数。

  2)增量法则中的输出为;

  每个输出都是输入的线性组合,这样当多个线性单元连接在一起后最终也只能得到输入的线性组合,这和只有一个感知器单元节点没有很大不同。 

  为了解决上面存在的问题,一方面,我们不能直接使用线性组合的方式直接输出,需要在输出的时候添加一个处理函数;另一方面,添加的处理函数一定要是可微的,这样我们才能使用梯度下降算法。

  满足上面条件的函数非常的多,但是最经典的莫过于sigmoid函数,又称Logistic函数,此函数能够将内的任意数压缩到(0,1)之间,因此这个函数又称为挤压函数。为了将此函数的输入更加规范化,我们在输入的线性组合中添加一个阀值,使得输入的线性组合以0为分界点。

  sigmoid函数:

  其函数曲线如图1.1所示。

神经网络模型搭建及Java实例_第1张图片

图1.1 sigmoid函数曲线[2]

  此函数有个重要特性就是他的导数:

 

  有了此特性在计算它的梯度下降时就简便了很多。

  另外还有双曲函数tanh也可以用来替代sigmoid函数,二者的曲线图比较类似。

 

2、反向传播算法又称BP算法(Back Propagation)

         现在,我们可以用上面介绍的使用sigmoid函数的感知器来搭建一个多层神经网络,为简单起见,此处我们使用三层网络来分析。假设网络拓补如图2.1所示。

神经网络模型搭建及Java实例_第2张图片

图2.1 BP网络拓补结构[3]

  网络的运行流程为:当输入一个样例后,获得该样例的特征向量,再根据权向量得到感知器的输入值,然后使用sigmoid函数计算出每个感知器的输出,再将此输出作为下一层感知器的输入,依次类推,直到输出层。

  那么如何确定每个感知器的权向量呢?这时我们需要使用反向传播算法来逐步进行优化。在正式介绍反向传播算法之前,我们先继续进行分析。

  在上一篇介绍感知器的文章中,为了得到权向量,我们通过最小化损失函数来不断调整权向量。此方法也适用于此处求解权向量,首先我们需要定义损失函数,由于网络的输出层有多个输出结点,我们需要将输出层每个输出结点的差值平方求和。于是得到每一个训练样例的损失函数为:(前面加个0.5方便后面求导使用)

  在多层的神经网络中,误差曲面可能有多个局部极小值,这意味着使用梯度下降算法找到的可能是局部极小值,而不是全局最小值。

  现在我们有了损失函数,这时可以根据损失函数来调整输出结点中的输入权向量,这类似感知器中的随机梯度下降算法,然后从后向前逐层调整权重,这就是反向传播算法的思想。

 

具有两层sigmoid单元的前馈网络的反向传播算法:

1)将网络中的所有权值随机初始化。

2)对每一个训练样例,执行如下操作:

  A)根据实例的输入,从前向后依次计算,得到输出层每个单元的输出。然后从输出层开始反向计算每一层的每个单元的误差项。

  B)对于输出层的每个单元k,计算它的误差项:

  C)对于网络中每个隐藏单元h,计算它的误差项:

  D)更新每个权值:

神经网络模型搭建及Java实例_第3张图片

符号说明:

xji:结点i到结点j的输入,wji表示对应的权值。

outputs:表示输出层结点集合。

整个算法与delta法则的随机梯度下降算法类似,算法分析如下:

  1)权值的更新方面,和delta法则类似,主要依靠学习速率,该权值对应的输入,以及单元的误差项。

  2)对输出层单元,它的误差项是(t-o)乘以sigmoid函数的导数ok(1-ok),这与delta法则的误差项有所不同,delta法则的误差项为(t-o)。

  3)对于隐藏层单元,因为缺少直接的目标值来计算隐藏单元的误差,因此需要以间接的方式来计算隐藏层的误差项对受隐藏单元h影响的每一个单元的误差进行加权求和,每个误差权值为wkh, wkh就是隐藏单元h到输出单元k的权值。

 

3、反向传播算法的推导

  算法的推导过程主要是利用梯度下降算法最小化损失函数的过程,现在损失函数为:

  对于网络中的每个权值wji,计算其导数:

  1)若j是网络的输出层单元

  对netj的求导:

  其中:

  

  

  所以有:

  为了使表达式简洁,我们使用:

  权值的改变朝着损失函数的负梯度方向,于是有权值改变量:

神经网络模型搭建及Java实例_第4张图片

 

2)若j是网络中的隐藏单元

  由于隐藏单元中w的值通过下一层来间接影响输入,故使用逐层剥离的方式来进行求导:

神经网络模型搭建及Java实例_第5张图片

  因为:

  所以:

  同样,我们使用:

  所以权值变化量:

 

4、算法的改进

  反向传播算法的应用非常的广泛,为了满足各种不同的需求,产生了很多不同的变体,下面介绍两种变体:

  1)增加冲量项

  此方法主要是修改权值更新法则。他的主要思想在于让第n次迭代时的权值的更新部分依赖于第n-1次的权值。

  其中0<=a<1:称为冲量的系数。加入冲量项在一定程度上起到加大搜索步长的效果,从而能更快的进行收敛。另一方面,由于多层网络易导致损失函数收敛到局部极小值,但通过冲量项在某种程度上可以越过某些狭窄的局部极小值,达到更小的地方。

  2)学习任意的深度的无环网络

  在上述介绍的反向传播算法实际只有三层,即只有一层隐藏层的情况,要是有很多隐藏层应当如何进行处理?

  现假设神经网络共有m+2层,即有m层的隐藏层。这时,只需要变化一个地方即可得到具有m个隐藏层的反向传播算法。第k层的单元r的误差 的值由更深的第k+1层的误差项计算得到:


      下边用Java进行神经网络的实例搭建。

BPNN类,实现神经网络的主要算法。

package bpnn;

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

/**
 * BP neural network core code and prediction processing code
 * @author Pumpkin
 * @since 2018/04/02
 * @version 1.0
 */
public class BPNN {

    
    // private static int LAYER = 3; // Three-layer neural network
    private static final int NodeNum = 10; // The maximum number of nodes per layer
    private static final int ADJUST = 5; // Hidden layer node adjustment constant
    private static final int MaxTrain = 2000; // Maximum training times
    private static final double ACCU = 0.015; // Allowable error for each iteration
    private double ETA_W = 0.5; // Weight learning efficiency
    private double ETA_T = 0.5; // Threshold learning efficiency
    private double accu;

    // 附加动量项
    //private static final double ETA_A = 0.3; // 动量常数0.1
    //private double[][] in_hd_last; // 上一次的权值调整量
    //private double[][] hd_out_last;

    private int in_num; // The number of input layer nodes
    private int hd_num; // The number of hidden layer nodes
    private int out_num; // The number of output layer nodes

    private ArrayList> list = new ArrayList<>(); // Input and output data

    private double[][] in_hd_weight; // BP network in-hidden synaptic weight
    private double[][] hd_out_weight; // BP network hidden_out synaptic weight
    private double[] in_hd_th; // BP network in-hidden threshold
    private double[] hd_out_th; // BP network hidden-out threshold

    private double[][] out; // The output value of each neuron converted by the sigmoid function, the input layer is the original value
    private double[][] delta; // Delta learning rules

    /** Obtaining the largest number of neurons in the third layer of the network
     * @return  **/
    public int GetMaxNum() {
        return Math.max(Math.max(in_num, hd_num), out_num);
    }

    // Setting the weight learning rate
    public void SetEtaW() {
        ETA_W = 0.5;
    }

    // Set threshold learning rate
    public void SetEtaT() {
        ETA_T = 0.5;
    }

    // BP neural network training
    public void Train(int in_number, int out_number,
            ArrayList> arraylist) throws IOException {
        list = arraylist;
        in_num = in_number;
        out_num = out_number;

        GetNums(in_num, out_num); // Get the number of nodes in the input layer, hidden layer, and output layer
        InitNetWork(); // Initialize network weights and thresholds

        int datanum = list.size(); // Number of training data 
        int createsize = GetMaxNum(); // Compare to create an array that stores the output data for each layer
        out = new double[3][createsize];

        for (int iter = 0; iter < MaxTrain; iter++) {
            for (int cnd = 0; cnd < datanum; cnd++) {
                // 第一层输入节点赋值

                for (int i = 0; i < in_num; i++) {
                    out[0][i] = list.get(cnd).get(i); // 为输入层节点赋值,其输入与输出相同
                }
                Forward(); // 前向传播
                Backward(cnd); // 误差反向传播

            }
            System.out.println("This is the " + (iter + 1)
                    + " th trainning NetWork !");
            accu = GetAccu();
            System.out.println("All Samples Accuracy is " + accu);
            if (accu < ACCU)
                break;

        }

    }

    // 获取输入层、隐层、输出层的节点数,in_number、out_number分别为输入层节点数和输出层节点数
    public void GetNums(int in_number, int out_number) {
        in_num = in_number;
        out_num = out_number;
        hd_num = (int) Math.sqrt(in_num + out_num) + ADJUST;
        if (hd_num > NodeNum)
            hd_num = NodeNum; // 隐层节点数不能大于最大节点数
    }

    // 初始化网络的权值和阈值
    public void InitNetWork() {
        // 初始化上一次权值量,范围为-0.5-0.5之间
        //in_hd_last = new double[in_num][hd_num];
        //hd_out_last = new double[hd_num][out_num];

        in_hd_weight = new double[in_num][hd_num];
        for (int i = 0; i < in_num; i++)
            for (int j = 0; j < hd_num; j++) {
                int flag = 1; // 符号标志位(-1或者1)
                if ((new Random().nextInt(2)) == 1)
                    flag = 1;
                else
                    flag = -1;
                in_hd_weight[i][j] = (new Random().nextDouble() / 2) * flag; // 初始化in-hidden的权值
                //in_hd_last[i][j] = 0;
            }

        hd_out_weight = new double[hd_num][out_num];
        for (int i = 0; i < hd_num; i++)
            for (int j = 0; j < out_num; j++) {
                int flag = 1; // 符号标志位(-1或者1)
                if ((new Random().nextInt(2)) == 1)
                    flag = 1;
                else
                    flag = -1;
                hd_out_weight[i][j] = (new Random().nextDouble() / 2) * flag; // 初始化hidden-out的权值
                //hd_out_last[i][j] = 0;
            }

        // 阈值均初始化为0
        in_hd_th = new double[hd_num];
        for (int k = 0; k < hd_num; k++)
            in_hd_th[k] = 0;

        hd_out_th = new double[out_num];
        for (int k = 0; k < out_num; k++)
            hd_out_th[k] = 0;

    }

    // 计算单个样本的误差
    public double GetError(int cnd) {
        double ans = 0;
        for (int i = 0; i < out_num; i++)
            ans += 0.5 * (out[2][i] - list.get(cnd).get(in_num + i))
                    * (out[2][i] - list.get(cnd).get(in_num + i));
        return ans;
    }

    // 计算所有样本的平均精度
    public double GetAccu() {
        double ans = 0;
        int num = list.size();
        for (int i = 0; i < num; i++) {
            int m = in_num;
            for (int j = 0; j < m; j++)
                out[0][j] = list.get(i).get(j);
            Forward();
            int n = out_num;
            for (int k = 0; k < n; k++)
                ans += 0.5 * (list.get(i).get(in_num + k) - out[2][k])
                        * (list.get(i).get(in_num + k) - out[2][k]);
        }
        return ans / num;
    }

    // 前向传播
    public void Forward() {
        // 计算隐层节点的输出值
        for (int j = 0; j < hd_num; j++) {
            double v = 0;
            for (int i = 0; i < in_num; i++)
                v += in_hd_weight[i][j] * out[0][i];
            v += in_hd_th[j];
            out[1][j] = Sigmoid(v);
        }
        // 计算输出层节点的输出值
        for (int j = 0; j < out_num; j++) {
            double v = 0;
            for (int i = 0; i < hd_num; i++)
                v += hd_out_weight[i][j] * out[1][i];
            v += hd_out_th[j];
            out[2][j] = Sigmoid(v);
        }
    }

    // 误差反向传播
    public void Backward(int cnd) {
        CalcDelta(cnd); // 计算权值调整量
        UpdateNetWork(); // 更新BP神经网络的权值和阈值
    }

    // 计算delta调整量
    public void CalcDelta(int cnd) {

        int createsize = GetMaxNum(); // 比较创建数组
        delta = new double[3][createsize];
        // 计算输出层的delta值
        for (int i = 0; i < out_num; i++) {
            delta[2][i] = (list.get(cnd).get(in_num + i) - out[2][i])
                    * SigmoidDerivative(out[2][i]);
        }

        // 计算隐层的delta值
        for (int i = 0; i < hd_num; i++) {
            double t = 0;
            for (int j = 0; j < out_num; j++)
                t += hd_out_weight[i][j] * delta[2][j];
            delta[1][i] = t * SigmoidDerivative(out[1][i]);
        }
    }

    // 更新BP神经网络的权值和阈值
    public void UpdateNetWork() {

        // 隐含层和输出层之间权值和阀值调整
        for (int i = 0; i < hd_num; i++) {
            for (int j = 0; j < out_num; j++) {
                hd_out_weight[i][j] += ETA_W * delta[2][j] * out[1][i]; // 未加权值动量项
                /* 动量项
                 * hd_out_weight[i][j] += (ETA_A * hd_out_last[i][j] + ETA_W
                 * delta[2][j] * out[1][i]); hd_out_last[i][j] = ETA_A *
                 * hd_out_last[i][j] + ETA_W delta[2][j] * out[1][i];
                 */
            }

        }
        for (int i = 0; i < out_num; i++)
            hd_out_th[i] += ETA_T * delta[2][i];

        // 输入层和隐含层之间权值和阀值调整
        for (int i = 0; i < in_num; i++) {
            for (int j = 0; j < hd_num; j++) {
                in_hd_weight[i][j] += ETA_W * delta[1][j] * out[0][i]; // 未加权值动量项
                /* 动量项
                 * in_hd_weight[i][j] += (ETA_A * in_hd_last[i][j] + ETA_W
                 * delta[1][j] * out[0][i]); in_hd_last[i][j] = ETA_A *
                 * in_hd_last[i][j] + ETA_W delta[1][j] * out[0][i];
                 */
            }
        }
        for (int i = 0; i < hd_num; i++)
            in_hd_th[i] += ETA_T * delta[1][i];
    }

    // 符号函数sign
    public int Sign(double x) {
        if (x > 0)
            return 1;
        else if (x < 0)
            return -1;
        else
            return 0;
    }

    // 返回最大值
    public double Maximum(double x, double y) {
        if (x >= y)
            return x;
        else
            return y;
    }

    // 返回最小值
    public double Minimum(double x, double y) {
        if (x <= y)
            return x;
        else
            return y;
    }

    // log-sigmoid函数
    public double Sigmoid(double x) {
        return (double) (1 / (1 + Math.exp(-x)));
    }

    // log-sigmoid函数的倒数
    public double SigmoidDerivative(double y) {
        return (double) (y * (1 - y));
    }

    // tan-sigmoid函数
    public double TSigmoid(double x) {
        return (double) ((1 - Math.exp(-x)) / (1 + Math.exp(-x)));
    }

    // tan-sigmoid函数的倒数
    public double TSigmoidDerivative(double y) {
        return (double) (1 - (y * y));
    }

    // 分类预测函数
    public ArrayList> ForeCast(
            ArrayList> arraylist) {

        ArrayList> alloutlist = new ArrayList<>();
        ArrayList outlist = new ArrayList<>();
        int datanum = arraylist.size();
        for (int cnd = 0; cnd < datanum; cnd++) {
            for (int i = 0; i < in_num; i++)
                out[0][i] = arraylist.get(cnd).get(i); // 为输入节点赋值
            Forward();
            for (int i = 0; i < out_num; i++) {
                if (out[2][i] > 0 && out[2][i] < 0.5)
                    out[2][i] = 0;
                else if (out[2][i] > 0.5 && out[2][i] < 1) {
                    out[2][i] = 1;
                }
                outlist.add(out[2][i]);
            }
            alloutlist.add(outlist);
            outlist = new ArrayList<>();
            outlist.clear();
        }
        return alloutlist;
    }
    
}

DataUtil类主要用于进行数据的预处理以及训练和测试数据。

package bpnn;

/**
 * Data processing class to process training data and test data
 * @author Pumpkin
 * @
 */
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;

class DataUtil {

    private final ArrayList> alllist = new ArrayList<>(); // 存放所有数据
    private final ArrayList outlist = new ArrayList<>(); // 存放输出数据,索引对应每个everylist的输出
    private final ArrayList checklist = new ArrayList<>();  //存放测试集的真实输出字符串
    private int in_num = 0;
    private int out_num = 0; // 输入输出数据的个数
    private int type_num = 0; // 输出的类型数量
    private double[][] nom_data; //归一化输入数据中的最大值和最小值/*
    private int in_data_num = 0; //提前获得输入数据的个数*/

    // 获取输出类型的个数
    public int GetTypeNum() {
        return type_num;
    }

    // 设置输出类型的个数
    public void SetTypeNum(int type_num) {
        this.type_num = type_num;
    }

    // 获取输入数据的个数
    public int GetInNum() {
        return in_num;
    }

    // 获取输出数据的个数
    public int GetOutNum() {
        return out_num;
    }

    // 获取所有数据的数组
    public ArrayList> GetList() {
        return alllist;
    }

    // 获取输出为字符串形式的数据
    public ArrayList GetOutList() {
        return outlist;
    }

    // 获取输出为字符串形式的数据
    public ArrayList GetCheckList() {
        return checklist;
    }

    //返回归一化数据所需最大最小值
    public double[][] GetMaxMin(){

        return nom_data;
    }

    // 读取文件初始化数据
    public void ReadFile(String filepath, String sep, int flag)
            throws Exception {

        ArrayList everylist = new ArrayList<>(); // 存放每一组输入输出数据
        int readflag = flag; // flag=0,train;flag=1,test
        String encoding = "utf-8";
        File file = new File(filepath);
        if (file.isFile() && file.exists()) { // 判断文件是否存在
            InputStreamReader read = new InputStreamReader(new FileInputStream(
                    file), encoding);// 考虑到编码格式
            try (BufferedReader bufferedReader = new BufferedReader(read)) {
                String lineTxt = null;
                while ((lineTxt = bufferedReader.readLine()) != null) {
                    int in_number = 0;
                    String splits[] = lineTxt.split(sep); // 按','截取字符串
                    if (readflag == 0) {
                        for (int i = 0; i < splits.length; i++)
                            try {
                                everylist.add(Normalize(Double.valueOf(splits[i]),nom_data[i][0],nom_data[i][1]));
                                in_number++;
                            } catch (NumberFormatException e) {
                                if (!outlist.contains(splits[i]))
                                    outlist.add(splits[i]); // 存放字符串形式的输出数据
                                for (int k = 0; k < type_num; k++) {
                                    everylist.add(0.0);
                                }
                            //everylist.set(in_number + outlist.indexOf(splits[i]),1.0);
                            }
                    } else if (readflag == 1) {
                        for (int i = 0; i < splits.length; i++)
                            try {
                                everylist.add(Normalize(Double.valueOf(splits[i]),nom_data[i][0],nom_data[i][1]));
                                in_number++;
                            } catch (NumberFormatException e) {
                                checklist.add(splits[i]); // 存放字符串形式的输出数据
                            }
                    }
                    alllist.add(everylist); // 存放所有数据
                    in_num = in_number;
                    out_num = type_num;
                    everylist = new ArrayList<>();
                    everylist.clear();
                    
                }
            }
        }
    }

    //向文件写入分类结果
    public void WriteFile(String filepath, ArrayList> list, int in_number,  ArrayList resultlist) throws IOException{
        File file = new File(filepath);
        FileWriter fw = null;
        BufferedWriter writer = null;
        try {
            fw = new FileWriter(file);
            writer = new BufferedWriter(fw);
            for(int i=0;inom_data[i][0])
                                nom_data[i][0]=Double.valueOf(splits[i]);
                            if(Double.valueOf(splits[i])

Test类,进行数据的读取和训练结果并显示。

package bpnn;

import java.util.ArrayList;

/**
 *
 * @author Pumpkin
 */


public class Test {
    public static void main(String args[]) throws Exception {

        ArrayList> alllist = new ArrayList<>(); // 存放所有数据
        ArrayList outlist = new ArrayList<>(); // 存放分类的字符串
        int in_num = 0;
        int out_num = 0; // 输入输出数据的个数

        DataUtil dataUtil = new DataUtil(); // 初始化数据

        dataUtil.NormalizeData("C:\\Users\\童\\Desktop\\BPNN\\data\\train.txt");

        dataUtil.SetTypeNum(3); // 设置输出类型的数量
        dataUtil.ReadFile("C:\\Users\\童\\Desktop\\BPNN\\data\\train.txt", ",", 0);

        in_num = dataUtil.GetInNum(); // 获得输入数据的个数
        out_num = dataUtil.GetOutNum(); // 获得输出数据的个数(个数代表类型个数)
        alllist = dataUtil.GetList(); // 获得初始化后的数据

        outlist = dataUtil.GetOutList();
        System.out.print("Classification type:");
        for(int i =0 ;i> testList = new ArrayList<>();
        ArrayList> resultList = new ArrayList<>();
        ArrayList normallist = new ArrayList<>(); // 存放测试集标准的输出字符串
        ArrayList resultlist = new ArrayList<>(); // 存放测试集计算后的输出字符串

        double right = 0; // 分类正确的数量
        int type_num = 0; // 类型的数量
        double all_num = 0; //测试集的数量
        type_num = outlist.size();

        testList = testUtil.GetList(); // 获取测试数据
        normallist = testUtil.GetCheckList(); 

        int errorcount=0; // 分类错误的数量
        resultList = bpnn.ForeCast(testList); // 测试
        all_num=resultList.size();
        for (int i = 0; i < all_num; i++) {
            String checkString = "unknow";
            for (int j = 0; j < type_num-1; j++) {
                if(resultList.get(i).get(j)==1.0){
                    checkString = outlist.get(j);
                    resultlist.add(checkString);
                }
                /*else{
                    resultlist.add(checkString);
                }*/
            }
            /*
            if(checkString.equals("unknow"))
                errorcount++;
            */
            if(checkString.equals(normallist.get(i)))
                right++;
        }
        testUtil.WriteFile("C:\\Users\\童\\Desktop\\BPNN\\data\\result.txt",testList,in_num,resultlist);

        System.out.println("The number of test sets:"+ (new Double(all_num)).intValue());
        System.out.println("Classification correct quantity:"+(new Double(right)).intValue());
        System.out.println("The correct classification rate of the algorithm:"+right/all_num);

        System.out.println("Classification results are stored in:C:\\Users\\童\\Desktop\\BPNN\\data\\result.txt");      
    }
}

至此,简单的神经网络搭建已基本完成,开始你的表演吧。




你可能感兴趣的:(Java,神经网络)