BP神经网络的数学原理及其算法实现

转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/44514073
上一篇文章介绍了KNN分类器,当时说了其分类效果不是很出色但是比较稳定,本文后面将利用BP网络同样对Iris数据进行分类。


什么是BP网络

BP神经网络,BP即Back Propagation的缩写,也就是反向传播的意思,顾名思义,将什么反向传播?文中将会解答。不仅如此,关于隐层的含义文中也会给出个人的理解。最后会用Java实现的BP分类器作为其应用以加深印象。
  很多初学者刚接触神经网络的时候都会到网上找相关的介绍,看了很多数学原理之后还是云里雾里,然后会琢磨到底这个有什么用?怎么用?于是又到网上找别人写的代码,下下来之后看一眼发现代码写的很糟糕,根本就理不清,怎么看也看不懂,于是就放弃了。作为过来人,本人之前在网上也看过很多关于BP网络的介绍,也下载了别人实现的代码下来研究,原理都一样,但是至今为止没有看到过能令人满意的代码实现。于是就有了这篇文章,不仅有原理也有代码,对节点的高度抽象会让代码更有可读性。


CSDN博客编辑器终于可以编写数学公式了!第一次使用Markdown编辑器,感觉爽歪歪,latex数学公式虽然写起来麻烦,不过很灵活,排版也漂亮~在这里贴一个Markdown输入数学公式的教程http://ttang.name/2014/05/04/markdown-and-mathjax/很全的说!


BP网络的数学原理

  下面将介绍BP网络的数学原理,相比起SVD的算法推导,这个简直就是小菜一碟,不就是梯度吗求个导就完事了。首先来看看BP网络长什么样,这就是它的样子:
  这里写图片描述
为了简单起见,这里只介绍只有一个隐层的BP网络,多个隐层的也是一样的原理。这个网络的工作原理应该很清楚了,首先,一组输入 x1x2xm 来到输入层,然后通过与隐层的连接权重产生一组数据 s1s2sn 作为隐层的输入,然后通过隐层节点的 θ() 激活函数后变为 θ(sj) 其中 sj 表示隐层的第 j 个节点产生的输出,这些输出将通过隐层与输出层的连接权重产生输出层的输入,这里输出层的处理过程和隐层是一样的,最后会在输出层产生输出y¯j,这里 j 是指输出层第j个节点的输出。这只是前向传播的过程,很简单吧?在这里,先解释一下隐层的含义,可以看到,隐层连接着输入和输出层,它到底是什么?它就是特征空间,隐层节点的个数就是特征空间的维数,或者说这组数据有多少个特征。而输入层到隐层的连接权重则将输入的原始数据投影到特征空间,比如 sj 就表示这组数据在特征空间中第 j 个特征方向的投影大小,或者说这组数据有多少份量的j特征。而隐层到输出层的连接权重表示这些特征是如何影响输出结果的,比如某一特征对某个输出影响比较大,那么连接它们的权重就会比较大。关于隐层的含义就解释这么多,至于多个隐层的,可以理解为特征的特征
  前面提到激活函数 θ() ,一般使用S形函数(即sigmoid函数),比如可以使用log-sigmoid: θ(s)=11+es
log-sigmoid
或者tan-sigmoid: θ(s)=eseses+es
tan-sigmoid
  前面说了,既然在输出层产生输出了,那总得看下输出结果对不对吧或者距离预期的结果有多大出入吧?现在就来分析一下什么东西在影响输出。显然,输入的数据是已知的,变量只有那些个连接权重了,那这些连接权重如何影响输出呢?现在假设输入层第i个节点到隐层第j个节点的连接权重发生了一个很小的变化 Δwij ,那么这个 Δwij 将会对 sj 产生影响,导致 sj 也出现一个变化 Δsj ,然后产生 Δθ(sj) ,然后传到各个输出层,最后在所有输出层都产生一个误差 Δe 。所以说,权重的调整将会使得输出结果产生变化,那么如何使这些输出结果往正确方向变化呢?这就是接下来的任务:如何调整权重。对于给定的训练样本,其正确的结果已经知道,那么由输入经过网络的输出和正确的结果比较将会有一个误差,如果能把这个误差将到最小,那么就是输出结果靠近了正确结果,就可以说网络可以对样本进行正确分类了。怎样使得误差最小呢?首先,把误差表达式写出来,为了使函数连续可导,这里最小化均方根差,定义损失函数如下:
  

L(e)=12SSE=12j=0ke2j=12j=0k(y¯jyj)2

  用什么方法最小化 L ?跟SVD算法一样,用随机梯度下降。也就是对每个训练样本都使权重往其负梯度方向变化。现在的任务就是求L对连接权重 w 的梯度。
  用w1ij表示输入层第 i 个节点到隐层第j个节点的连接权重, w2ij 表示隐层第 i 个节点到输出层第j个节点的连接权重, s1j 表示隐层第 j 个节点的输入,s2j表示输出层第 j 个几点的输入,区别在右上角标,1表示第一层连接权重,2表示第二层连接权重。那么有
  
Lw1ij=Ls1js1jw1ij

由于
s1j=i=1mxiw1ij

所以
s1jw1ij=xi
代入前面式子可得
 
Lw1ij=xiLs1j

接下来只需求出 Ls1j 即可。
由于 s1j 对所有输出层都有影响,所以
Ls1j=i=1kLs2is2is1j

由于
s2i=j=0nθ(s1j)w2ji
所以
   
s2is1j=s2iθ(s1j)θ(s1j)s1j=w2jiθ(s1j)

代入前面的式子可得
   
Ls1j=i=1kLs2iw2jiθ(s1j)=θ(s1j)i=1kLs2iw2ji

现在记
δli=Lsli
则隐层 δ
δ1j=θ(s1j)i=1kδ2iw2ji

输出层 δ
δ2i=Ls2i=kj=012(y¯jyj)2s2i=(y¯iyi)y¯is2i=eiy¯is2i=eiθ(s2i)

到这一步,可以看到是什么反向传播了吧?没错,就是误差 e
反向传播过程是这样的:输出层每个节点都会得到一个误差e,把 e 作为输出层反向输入,这时候就像是输出层当输入层一样把误差往回传播,先得到输出层δ,然后将输出层 δ 根据连接权重往隐层传输,即前面的式子:
δ1j=θ(s1j)i=1kδ2iw2ji

 现在再来看第一层权重的梯度:
 
Lw1ij=xiδ1j

 第二层权重梯度:
 
Lw2ij=Ls2js2jw2ij=δ2jθ(s1i)

 可以看到一个规律: 每个权重的梯度都等于与其相连的前一层节点的输出(即 xi θ(s1i) )乘以与其相连的后一层的反向传播的输出(即 δ1j δ2j 。如果看不明白原理的话记住这句话即可!
 这样反向传播得到所有的 δ 以后,就可以更新权重了。更直观的BP神经网络的工作过程总结如下:
  BP神经网络的工作过程
上图中每一个节点的输出都和权重矩阵中同一列(行)的元素相乘,然后同一行(列)累加作为下一层对应节点的输入。
 为了代码实现的可读性,对节点进行抽象如下:
  单个神经元内部示意图
 这样的话,很多步骤都在节点内部进行了。
 当 θ(s)=11+es 时,
 
θ(s)=θ(s)(1θ(s))=SOut(1SOut)

 当 θ(s)=eseses+es 时,
 
θ(s)=1θ(s)2=1S2Out

BP网络原理部分就到这,接下来要根据上图中的神经元模型用代码实现BP网络,然后对Iris数据集进行分类。完整的代码见github: https://github.com/jingchenUSTC/ANN

BP网络算法实现

首先,单个神经元封装代码如下:

//NetworkNode.java
package com.jingchen.ann;

public class NetworkNode
{
    public static final int TYPE_INPUT = 0;
    public static final int TYPE_HIDDEN = 1;
    public static final int TYPE_OUTPUT = 2;

    private int type;

    public void setType(int type)
    {
        this.type = type;
    }

    // 节点前向输入输出值
    private float mForwardInputValue;
    private float mForwardOutputValue;

    // 节点反向输入输出值
    private float mBackwardInputValue;
    private float mBackwardOutputValue;

    public NetworkNode()
    {
    }

    public NetworkNode(int type)
    {
        this.type = type;
    }

    /**
     * sigmoid函数,这里用tan-sigmoid,经测试其效果比log-sigmoid好!
     * 
     * @param in
     * @return
     */
    private float forwardSigmoid(float in)
    {
        switch (type)
        {
        case TYPE_INPUT:
            return in;
        case TYPE_HIDDEN:
        case TYPE_OUTPUT:
            return tanhS(in);
        }
        return 0;
    }

    /**
     * log-sigmoid函数
     * 
     * @param in
     * @return
     */
    private float logS(float in)
    {
        return (float) (1 / (1 + Math.exp(-in)));
    }

    /**
     * log-sigmoid函数的导数
     * 
     * @param in
     * @return
     */
    private float logSDerivative(float in)
    {
        return mForwardOutputValue * (1 - mForwardOutputValue) * in;
    }

    /**
     * tan-sigmoid函数
     * 
     * @param in
     * @return
     */
    private float tanhS(float in)
    {
        return (float) ((Math.exp(in) - Math.exp(-in)) / (Math.exp(in) + Math
                .exp(-in)));
    }

    /**
     * tan-sigmoid函数的导数
     * 
     * @param in
     * @return
     */
    private float tanhSDerivative(float in)
    {
        return (float) ((1 - Math.pow(mForwardOutputValue, 2)) * in);
    }

    /**
     * 误差反向传播时,激活函数的导数
     * 
     * @param in
     * @return
     */
    private float backwardPropagate(float in)
    {
        switch (type)
        {
        case TYPE_INPUT:
            return in;
        case TYPE_HIDDEN:
        case TYPE_OUTPUT:
            return tanhSDerivative(in);
        }
        return 0;
    }

    public float getForwardInputValue()
    {
        return mForwardInputValue;
    }

    public void setForwardInputValue(float mInputValue)
    {
        this.mForwardInputValue = mInputValue;
        setForwardOutputValue(mInputValue);
    }

    public float getForwardOutputValue()
    {
        return mForwardOutputValue;
    }

    private void setForwardOutputValue(float mInputValue)
    {
        this.mForwardOutputValue = forwardSigmoid(mInputValue);
    }

    public float getBackwardInputValue()
    {
        return mBackwardInputValue;
    }

    public void setBackwardInputValue(float mBackwardInputValue)
    {
        this.mBackwardInputValue = mBackwardInputValue;
        setBackwardOutputValue(mBackwardInputValue);
    }

    public float getBackwardOutputValue()
    {
        return mBackwardOutputValue;
    }

    private void setBackwardOutputValue(float input)
    {
        this.mBackwardOutputValue = backwardPropagate(input);
    }

}

然后就是整个神经网络类:

//AnnClassifier.java
package com.jingchen.ann;

import java.util.ArrayList;
import java.util.List;

/**
 * 人工神经网络分类器
 * 
 * @author chenjing
 * 
 */
public class AnnClassifier
{
    private int mInputCount;
    private int mHiddenCount;
    private int mOutputCount;

    private List<NetworkNode> mInputNodes;
    private List<NetworkNode> mHiddenNodes;
    private List<NetworkNode> mOutputNodes;

    private float[][] mInputHiddenWeight;
    private float[][] mHiddenOutputWeight;

    private List<DataNode> trainNodes;

    public void setTrainNodes(List<DataNode> trainNodes)
    {
        this.trainNodes = trainNodes;
    }

    public AnnClassifier(int inputCount, int hiddenCount, int outputCount)
    {
        trainNodes = new ArrayList<DataNode>();
        mInputCount = inputCount;
        mHiddenCount = hiddenCount;
        mOutputCount = outputCount;
        mInputNodes = new ArrayList<NetworkNode>();
        mHiddenNodes = new ArrayList<NetworkNode>();
        mOutputNodes = new ArrayList<NetworkNode>();
        mInputHiddenWeight = new float[inputCount][hiddenCount];
        mHiddenOutputWeight = new float[mHiddenCount][mOutputCount];
    }

    /**
     * 更新权重,每个权重的梯度都等于与其相连的前一层节点的输出乘以与其相连的后一层的反向传播的输出
     */
    private void updateWeights(float eta)
    {
        //更新输入层到隐层的权重矩阵
        for (int i = 0; i < mInputCount; i++)
            for (int j = 0; j < mHiddenCount; j++)
                mInputHiddenWeight[i][j] -= eta
                        * mInputNodes.get(i).getForwardOutputValue()
                        * mHiddenNodes.get(j).getBackwardOutputValue();
        //更新隐层到输出层的权重矩阵
        for (int i = 0; i < mHiddenCount; i++)
            for (int j = 0; j < mOutputCount; j++)
                mHiddenOutputWeight[i][j] -= eta
                        * mHiddenNodes.get(i).getForwardOutputValue()
                        * mOutputNodes.get(j).getBackwardOutputValue();
    }

    /**
     * 前向传播
     */
    private void forward(List<Float> list)
    {
        // 输入层
        for (int k = 0; k < list.size(); k++)
            mInputNodes.get(k).setForwardInputValue(list.get(k));
        // 隐层
        for (int j = 0; j < mHiddenCount; j++)
        {
            float temp = 0;
            for (int k = 0; k < mInputCount; k++)
                temp += mInputHiddenWeight[k][j]
                        * mInputNodes.get(k).getForwardOutputValue();
            mHiddenNodes.get(j).setForwardInputValue(temp);
        }
        // 输出层
        for (int j = 0; j < mOutputCount; j++)
        {
            float temp = 0;
            for (int k = 0; k < mHiddenCount; k++)
                temp += mHiddenOutputWeight[k][j]
                        * mHiddenNodes.get(k).getForwardOutputValue();
            mOutputNodes.get(j).setForwardInputValue(temp);
        }
    }

    /**
     * 反向传播
     */
    private void backward(int type)
    {
        // 输出层
        for (int j = 0; j < mOutputCount; j++)
        {
            //输出层计算误差把误差反向传播,这里-1代表不属于,1代表属于
            float result = -1;
            if (j == type)
                result = 1;
            mOutputNodes.get(j).setBackwardInputValue(
                    mOutputNodes.get(j).getForwardOutputValue() - result);
        }
        // 隐层
        for (int j = 0; j < mHiddenCount; j++)
        {
            float temp = 0;
            for (int k = 0; k < mOutputCount; k++)
                temp += mHiddenOutputWeight[j][k]
                        * mOutputNodes.get(k).getBackwardOutputValue();
        }
    }

    public void train(float eta, int n)
    {
        reset();
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < trainNodes.size(); j++)
            {
                forward(trainNodes.get(j).getAttribList());
                backward(trainNodes.get(j).getType());
                updateWeights(eta);
            }

        }
    }

    /**
     * 初始化
     */
    private void reset()
    {
        mInputNodes.clear();
        mHiddenNodes.clear();
        mOutputNodes.clear();
        for (int i = 0; i < mInputCount; i++)
            mInputNodes.add(new NetworkNode(NetworkNode.TYPE_INPUT));
        for (int i = 0; i < mHiddenCount; i++)
            mHiddenNodes.add(new NetworkNode(NetworkNode.TYPE_HIDDEN));
        for (int i = 0; i < mOutputCount; i++)
            mOutputNodes.add(new NetworkNode(NetworkNode.TYPE_OUTPUT));
        for (int i = 0; i < mInputCount; i++)
            for (int j = 0; j < mHiddenCount; j++)
                mInputHiddenWeight[i][j] = (float) (Math.random() * 0.1);
        for (int i = 0; i < mHiddenCount; i++)
            for (int j = 0; j < mOutputCount; j++)
                mHiddenOutputWeight[i][j] = (float) (Math.random() * 0.1);
    }

    public int test(DataNode dn)
    {
        forward(dn.getAttribList());
        float result = 2;
        int type = 0;
        //取最接近1的
        for (int i = 0; i < mOutputCount; i++)
            if ((1 - mOutputNodes.get(i).getForwardOutputValue()) < result)
            {
                result = 1 - mOutputNodes.get(i).getForwardOutputValue();
                type = i;
            }
        return type;
    }
}

Iris数据有三种类别,所以输出层会有三个节点,每个节点代表一种类别,节点输出1(具体根据所用激活函数的上界)则表示属于该类,输出-1(具体根据所用激活函数的下界)则表示不属于该类。
  完整的代码已共享到github,地址:https://github.com/jingchenUSTC/ANN。用BP网络对Iris数据进行分类的准确率接近100%!

  • 108
    点赞
  • 454
    收藏
    觉得还不错? 一键收藏
  • 44
    评论
### 回答1: 遗传算法(Genetic Algorithm)和反向传播神经网络(Backpropagation Neural Network, BP神经网络)是两种常用的机器学习算法,可以在Python中实现用于回归问题的解决。 遗传算法是一种模拟自然选择和遗传机制的优化算法,通过将个体的特征编码成染色体,利用选择、交叉和变异操作来不断演化种群,以寻找最优解。在回归问题中,可以将染色体编码成神经网络的权重和偏置值,演化的目标是找到使得神经网络拟合出最佳回归模型的最优权重。通过适应度函数评估每个个体的拟合程度,选择适应度高的个体进行繁殖,然后进行交叉和变异操作,不断演化种群,最终找到最优解。 BP神经网络是一种常用的有监督学习算法,通过前向传播和反向传播的方式进行训练。在回归问题中,BP神经网络可以通过多个输入节点、隐藏层和输出节点的组合,以拟合输入和输出之间的复杂非线性关系。在Python中,可以使用一些常用的神经网络库(如PyTorch、TensorFlow等)来搭建和训练BP神经网络。通过将输入数据喂给网络,计算输出,并与真实输出进行比较,用误差来更新网络的权重和偏置值,不断迭代训练,直到达到收敛或设定的停止条件。 在结合遗传算法BP神经网络进行回归问题的求解时,可以利用遗传算法的全局搜索能力来搜索神经网络的初始权重和偏置值,然后再使用BP神经网络进行细致的优化和训练,提高回归模型的拟合精度和泛化能力。具体实现上,可以先利用遗传算法生成初始种群,然后使用BP神经网络对每个个体进行评估,并计算适应度值。根据适应度值选择优秀的个体进行遗传操作,如选择、交叉和变异。迭代演化后,得到最优个体对应的权重和偏置值,进而得到最优的回归模型。整个过程可以使用Python编程语言进行实现。 ### 回答2: 遗传算法是一种基于自然选择和遗传机制原理的优化算法,它模拟了生物进化的过程,通过对候选解进行进化和选择,以求得问题的最优解。遗传算法可以用于优化神经网络中的参数值,如权重和阈值。 BP神经网络是一种常用的人工神经网络模型,它可以通过反向传播算法来训练网络,实现对数据的拟合和预测。BP神经网络通常用于解决分类和回归问题,其中回归问题是通过利用已知数据来预测连续型变量的值。 在使用遗传算法优化BP神经网络回归模型时,首先需要定义适应度函数,即衡量神经网络预测结果与实际结果之间的差异程度。然后,通过遗传算法的选择、交叉和变异操作对神经网络的参数进行优化,以求得最优的网络结构和参数配置。 使用Python编程语言可以方便地实现遗传算法BP神经网络的结合。Python提供了强大的数学计算库如NumPy和SciPy,可以用于BP神经网络的训练和优化;同时,PyGAD(Python Genetic Algorithm Library)等相关库可以用于实现遗传算法的各种操作,如选择、交叉和变异。 在实际应用中,可以将问题转化为回归问题,通过建立BP神经网络回归模型,并结合遗传算法进行参数优化,进而得到更准确的预测结果。通过Python编程,我们可以灵活地调整遗传算法BP神经网络的参数,以适应不同的问题需求,并获得更好的回归预测效果。 ### 回答3: 遗传算法(Genetic Algorithm)是一种模拟生物进化过程的优化算法。它通过使用种群、代际交叉和变异等操作,以逐步优化问题的解。在遗传算法中,首先需要定义优化目标和适应度函数,然后生成初始的随机种群,通过评估每个个体的适应度来选取更好的个体进行进一步繁衍,直到达到停止准则为止。 BP神经网络(Back Propagation Neural Network)是一种前向反馈的人工神经网络模型,是一种常见的分类和回归算法BP神经网络通过学习训练数据集,调整网络连接权重来建立输入与输出之间的映射关系。它通过正向传播计算输出,并通过反向传播根据误差调整每个神经元的权重,从而优化网络的性能。 在进行回归任务时,可以将遗传算法BP神经网络相结合,使用遗传算法来选择出更好的初始权重,然后再使用BP神经网络进行训练和优化。具体操作可以分为以下几个步骤: 1. 定义适应度函数:将BP神经网络预测结果与实际标签之间的误差作为适应度函数,衡量每个个体的优劣。 2. 初始化种群:随机生成一定数量的BP神经网络的初始权重,并将它们作为初始的种群。 3. 迭代进化:通过计算每个个体的适应度,逐代进行选择、交叉和变异操作,生成新的种群。 4. 终止准则:设置终止条件,如达到最大迭代次数或目标误差小于某个阈值。 5. 使用最优个体:在遗传算法收敛后,选择适应度最高的个体作为BP神经网络的初始权重,并使用该权重对数据进行训练和预测。 在Python中,可以使用遗传算法BP神经网络的相关库来实现上述步骤,如使用DEAP库实现遗传算法部分,使用Scikit-learn库实现BP神经网络部分。通过对训练集的迭代和优化,以达到更好的回归预测效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值