背景与原理:

BP神经网络通常指基于误差反向传播算法的多层神经网络,BP算法由信号的前向传播和反向传播两个过程组成,在前向传播的过程中,输入从输入层进入网络,经过隐含层逐层传递到达输出层输出,如果输出结果与预期不符那么转至误差反向传播过程,否则结束学习过程。在反向传播过程中,误差会基于梯度下降原理分配给各层神经元,修正各个神经元的权值。

考虑一个经典的分类问题,假设我们有一组数据形如$(x_{1},...,x_{n},y)$,其中$y$为这个数据所属的类别,不妨设有$k$个取值$(C_{1},...,C_{k})$,那么重复之前的描述:我们实际上要求的是给定了一个输入$X$,求这个输入属于某一类的概率,也就是说我们可以理解为输入一个$n$维向量,希望预测一个$k$维向量,每一维表示这个输入属于这一类的概率,而最后我们的预测结果就是概率最大的那一个维度。

考虑一个基础的逻辑回归,我们构造了一个线性函数$z=w^{T}x+b$,然后令$g(z)=\dfrac{1}{1+e^{-z}}$,这样$g(z)$就可以用来表示一个0-1二分类问题中属于类别1的概率。

而这个函数的意义,一方面在于将$R$光滑对称地映射到了$(0,1)$区间能有效地表示概率,另一方面,linear的东西始终只是linear的,当然我们可以引入多项式特征解决一定的问题,但这样我们要面对的问题就是——究竟引入多少才算合适?而这个sigmoid函数能够把linear的东西变成non-linear的,这就是一个进步。

而如果从神经网络的生物学背景来看,我们会看到一个神经元一般有两个状态,即静息状态和兴奋状态,而我们的人工神经网络从背景上来说是对人类神经系统行为的一种模拟,因此一个取值在$[0,1]$之间的函数可以看做神经元状态的一个反映——0代表静息状态,1代表兴奋状态,所以在神经网络中,类似于sigmoid这样的函数是很常用的,它们被称作激活函数。

那么说了这么多,神经网络究竟是在干什么呢?

从一定的意义上来说,神经网络实际上实现的是函数的不断复合——虽然即使引入了一个sigmoid函数,linear的东西可能还是无法摆脱linear,那我们就再设法复合上一层,这样它就没有那么linear了,以此类推,当我们复合次数足够多的时候,我们得到的最终结果将能够拟合出这个“客观正确”的预测模型。

这个过程是怎么实现的呢?

首先神经网络被分为三个部分:输入层、隐藏层和输出层,每层有若干个节点,每个节点称为一个神经元,输入层有$n$个节点,对应于输入特征的维度,输出层有$k$个节点,对应于要预测的东西的维度。

而我们首先从单隐藏层的结构开始:假设我们只有一个隐藏层,这个隐藏层上有$n_{1}$个节点,那么第$i$个节点要处理的输入上一层的所有输入,对于这第一个隐藏层就是$x_{1},...,x_{n}$,而后计算出$z_{i}=w_{i}^{T}x+b$(即输入的一个线性函数),然后用自己的激活函数处理这个信号并发出,即第$i$个节点的输出是$f_{i}(z_{i})$

那么这个过程实际上就是对人类神经元活动的一种模拟:神经元接受上一个神经元发来的信号——神经元处理信号产生一个神经冲动传递给下一个神经元

那么我们假设只有一个隐藏层,那么这个隐藏层的输出就是$f_{1}(z_{1}),...,f_{n_{1}}(z_{n_{1}})$,而下一个就到了输出层,即输出层的第$j$个神经元输出的就是$f_{d}(\omega_{j}^{T}f(z)+\beta_{j})$(无需在意符号,只是为了区分各个层的参数)

那么非常自然地,如果我们有很多个隐藏层,对于第二个隐藏层而言,其接受的输入就是第一个隐藏层的输出,以此类推,每个神经元还是按照自己的参数将上一层的输出线性组合后扔进自己的激活函数里输出给下一层,直到到达输出层为止。

那么实际上,对于一个全连接神经网络,假设上一层有$n$个神经元,下一层有$m$个神经元,上一层第$j$个神经元的输出是$a_{j}$,那么下一层第$i$个神经元的输入是$\sum_{j=1}^{n}w_{ij}a_{j}+b_{i}$

那么如果设下一层是第$d$层,上一层是第$d-1$层,这样下一层的输入是上一层的输出的一个线性组合,记为$z_{d}$,我们有:

$z_{d}=\begin{pmatrix} z_{1} \\ z_{2} \\ ... \\ z_{m} \end{pmatrix}=\begin{pmatrix} \sum_{j=1}^{n}w_{1j}a_{j}+b_{1} \\ \sum_{j=1}^{n}w_{2j}a_{j}+b_{2} \\ ... \\ \sum_{j=1}^{n}w_{mj}a_{j}+b_{m} \end{pmatrix}=\begin{pmatrix} w_{11} & w_{12} & ... & w_{1n} \\ w_{21} & w_{22} &.. & w_{2n}\\...\\w_{m1} & w_{m2} &...& w_{mn}\end{pmatrix} \begin{pmatrix} a_{1}\\a_{2}\\...\\a_{n} \end{pmatrix} + \begin{pmatrix} b_{1}\\b_{2} \\...\\b_{m}\end{pmatrix}$

这样我们设$W_{d}=\begin{pmatrix} w_{11} & w_{12} & ... & w_{1n} \\ w_{21} & w_{22} &.. & w_{2n}\\...\\w_{m1} & w_{m2} &...& w_{mn}\end{pmatrix}$,而$a_{d-1}=\begin{pmatrix} a_{1}\\a_{2}\\...\\a_{n} \end{pmatrix}$,$b_{d}=\begin{pmatrix} b_{1}\\b_{2} \\...\\b_{m}\end{pmatrix}$

这样我们有$z_{d}=W_{d}a_{d}+b_{d}$,而第$d$层的输出,也即第$d+1$层的输入就是$a_{d}=f_{d}(z_{d})$

这样的就构成了一个神经网络,如果每一个神经元的激活函数选的足够好,参数也训练的足够好,再忽略可能的过拟合问题,那么这个神经网络的表现应该是很不错的。

但是...等等,这样的神经网络要怎么训练啊!

这个问题是巨大的——假设我们的神经网络每层有10个神经元,而我们一共有10个隐藏层,这样就需要100个神经元,而每个神经元要对前一层10个神经元的输出做一个线性组合,这就需要11个参数,也就是说这样一个“简陋”的神经网络我们就要训练足足1100个参数(粗略估计,并不严格)!

那么如果没有一个合适的方法,训练神经网络的开销将是灾难性的。

幸运的是,回忆一开始的梯度下降方法,我们知道如果我们希望让一个函数尽快到达最低点,我们只需按照一定的步长沿下降最快的方向前进即可,那么在这里是不是也可以引入类似的方法呢?

在神经网络中我们需要的参数主要有$w,b$,那么我们定义一个损失函数$J(w,b)$,那如果我们能求出一个梯度$\dfrac{\partial J}{\partial w_{i}}$,那我们当然可以按照梯度下降的方法更新$\hat{w_{i}}=w_{i}-\alpha \dfrac{\partial J}{\partial w_{i}}$

但是很遗憾的是,由于函数复合的复杂性,我们想要计算出一个损失函数对某个具体的$w_{i}$的偏导数是相当困难的,因为这个$w_{i}$可能前面复合了一堆东西,后面又复合上了一堆东西,想要直接计算出这个偏导数是另一场灾难。

幸运的是,我们可以从另一个角度来思考这个问题:我们从输出层向前来考虑,假设输出为一个列向量$a_{D}$,而这个$a_{D}$满足$a_{D}=f_{D}(w_{D}a_{D-1}+b_{D})$,那么我们可以很自然地看到:

$\dfrac{\partial J}{\partial w_{D}}=\dfrac{\partial J}{\partial z_{D}} \dfrac{\partial z_{D}}{\partial w_{D}}=\dfrac{\partial J}{\partial z_{D}} a_{D-1}$

$\dfrac{\partial J}{\partial b_{D}}=\dfrac{\partial J}{\partial z_{D}} \dfrac{\partial z_{D}}{\partial b_{D}}=\dfrac{\partial J}{\partial z_{D}} $

而$a_{D-1}$是已知,我们实际要求出:

$\dfrac{\partial J}{\partial z_{D}}$

当然,这个是不难求解的,求解的结果与损失函数有关,我们不妨设这个东西是$\delta_{D}=\dfrac{\partial J}{\partial z_{D}}$

那我们再往前求,如果我们想求出$\dfrac{\partial J}{\partial w_{d}}$,那么由复合函数求导法则,我们有:

$\dfrac{\partial J}{\partial w_{d}}=\dfrac{\partial J}{\partial z_{D}}\dfrac{\partial z_{D}}{\partial z_{D-1}}...\dfrac{\partial z_{d+1}}{\partial z_{d}}\dfrac{\partial z_{d}}{\partial w_{d}}$

而我们知道$\dfrac{\partial z_{d}}{\partial w_{d}}=a_{d-1}$,因此我们有:

$\dfrac{\partial J}{\partial w_{d}}=\dfrac{\partial J}{\partial z_{D}}\dfrac{\partial z_{D}}{\partial z_{D-1}}...\dfrac{\partial z_{d+1}}{\partial z_{d}}a_{d-1}$

这样我们设$\delta_{d}=\dfrac{\partial J}{\partial z_{d}}$,那么我们始终有:

$\dfrac{\partial J}{\partial w_{d}}=\delta_{d} a_{d-1}$

而$\delta_{d}=\dfrac{\partial J}{\partial z_{D}}\dfrac{\partial z_{D}}{\partial z_{D-1}}...\dfrac{\partial z_{d+1}}{\partial z_{d}}$,那么我们有递推:

$\delta_{d}=\delta_{d+1}\dfrac{\partial z_{d+1}}{\partial z_{d}}$

而我们需要算出的是$\dfrac{\partial z_{d+1}}{\partial z_{d}}$

而我们知道$z_{d+1}=w_{d+1}f(z_{d})+b_{d+1}$,这样我们有:

$\dfrac{\partial z_{d+1}}{\partial z_{d}}=\dfrac{\partial z_{d+1}}{\partial f_{d}(z_{d})}\dfrac{\partial f_{d}(z_{d})}{\partial z_{d}}$

因此我们最后得到:

$\dfrac{\partial z_{d+1}}{\partial z_{d}}=w_{d+1}f_{d}^{'}(z_{d})$

因此我们得知:当前层的梯度应该等于回传回当前层的梯度($\delta_{d+1}$)乘以当前层的导数再乘以上一层的输入。

那么我们总结一下BP神经网络的流程:

首先要确定输入、输出和损失函数,然后选取神经网络的结构(层数、每层的神经元个数、每层的激活函数、学习率),然后按照上述过程训练至收敛就好。

代码实现:

import numpy as np
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
import matplotlib.pyplot as plt
import pylab as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
import tensorflow as tf
from sklearn.neural_network import MLPClassifier X,y=datasets.make_classification(n_samples=1000,n_features=20,n_informative=2,n_redundant=2)
X_train,X_test,Y_train,Y_test=train_test_split(X,y,test_size=0.25) lr=LogisticRegression()
svc=LinearSVC(C=1.0)
rfc=RandomForestClassifier(n_estimators=100)#森林中树的个数 lr=lr.fit(X_train,Y_train)
score1=lr.score(X_test,Y_test)
print(score1) svc=svc.fit(X_train,Y_train)
score2=svc.score(X_test,Y_test)
print(score2) rfc=rfc.fit(X_train,Y_train)
score3=rfc.score(X_test,Y_test)
print(score3) gnb=GaussianNB()
gnb=gnb.fit(X_train,Y_train)
score4=gnb.score(X_test,Y_test)
print(score4) sc=[]
siz=[]
i=5
while i<=100:
clf = MLPClassifier(solver='lbfgs', alpha=1e-5,activation='logistic',
hidden_layer_sizes=(i,i), random_state=1,max_iter=500000)
clf.fit(X_train, Y_train)
sc.append(clf.score(X_test,Y_test))
siz.append(i)
i+=5
plt.plot(siz,sc,c='r')
plt.show()

这段代码使用sklearn自带的简易神经网络分类器来训练一个模型,其中solver这个参数指定了梯度下降的方式,alpha指定了正则化参数,activation指定了激活函数,hidden_layer_sizes是一个元组,每一项对应于一个隐藏层中神经元的个数,max_iter决定了神经网络最大的迭代次数,超过这个次数的话会返回一个错误

这是这个两个隐藏层的神经网络在这组数据上的表现随每层神经元个数变化的图像

import numpy as np
from sklearn import datasets
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import LinearSVC
import matplotlib.pyplot as plt
import pylab as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
from sklearn.neural_network import MLPClassifier X,y=datasets.make_classification(n_samples=1000,n_features=20,n_informative=2,n_redundant=2)
X_train,X_test,Y_train,Y_test=train_test_split(X,y,test_size=0.25) Y_train=np.array(Y_train).reshape(750,1)
Y_test=np.array(Y_test).reshape(250,1) def add_layer(input, in_size, out_size, activation_function=None):
w = tf.Variable(tf.random_normal([in_size, out_size]))
b = tf.Variable(tf.zeros([1, out_size]) + 0.1)
Z = tf.matmul(input, w) + b
if activation_function == None:
output = Z
else:
output = activation_function(Z)
return output xs = tf.compat.v1.placeholder(tf.float32, [None, 20])
ys = tf.compat.v1.placeholder(tf.float32, [None, 1]) hidden_layer1 = add_layer(xs, 20, 10, activation_function=tf.nn.relu)
hidden_layer2 = add_layer(hidden_layer1,10,10,activation_function=tf.nn.relu) prediction = add_layer(hidden_layer2, 10, 1, activation_function=None) loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction), reduction_indices=[1])) train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss) init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for i in range(1000):
sess.run(train_step, feed_dict={xs: X_train, ys: Y_train})
if i % 100 == 0:
print(sess.run(loss, feed_dict={xs: X_test, ys: Y_test})) sess.close()

这是用tensorflow搭建神经网络的一个方法,首先我们需要自定义一个函数叫做add_layer,这个函数输入的参数是而上一层是谁,上一层的神经元个数,本层神经元个数,和本层的激活函数

而每层的初始值可以设成随机值,这里的$w$设计成了一个矩阵,因为对于上一层的第$i$个神经元和本层的第$j$个神经元而言对应的$w$值其实就是$w_{ij}$,而如果我们把一整层看做一个向量的话那么两层之间的线性变换过程实际就是一个矩阵乘法,最后根据激活函数进行下激活就完成了这一层的任务。

而初始我们需要定义两个占位符xs和ys表示输入和输出,第一维是None,第二维表示维度

接下来我们可以定义一些隐藏层和一个输出层,以及一个损失函数,这里按照参考格式写就好。

而反向传播的过程不需要我们来写,TensorFlow提供一个train的包,可以决定按什么方法进行优化,而参数是学习率,后面的minimize是我们要最小化的那个损失函数。

最后我们用tensorflow里的一个叫Session的对象run这个神经网络,就可以实现训练的功能了。

小结与提高:

反向传播神经网络是最简单的神经网络,在很多任务上表现优越,但其也有很多问题,比如模型的可解释性很差——事实上整个神经网络虽然原理清晰,但真实过程就像一个黑盒,比如上面我们会看到模型的表现随神经元个数在震荡,这样的现象并不容易找到一个简单的解释。同时由于超参数很多,如何选取就成了一个大问题,同时由于模型的复杂性导致很容易出现过拟合的现象,同时模型的复杂还会带来训练成本的大幅上升,这些都是神经网络所要面对的大问题。

python机器学习——BP(反向传播)神经网络算法的更多相关文章

  1. 【神经网络】BP反向传播神经网络

    BP算法细节 参数说明:假设有n层.J表示代价函数,和上面的E是同样的意思,只不过用不同的字母写而已. 分析:要想知道第l层的第i个结点的残差,必须知道层已经计算出来了残差,你只要把后面一层的每个结点 ...

  2. 【深度学习】BP反向传播算法Python简单实现

    转载:火烫火烫的 个人觉得BP反向传播是深度学习的一个基础,所以很有必要把反向传播算法好好学一下 得益于一步一步弄懂反向传播的例子这篇文章,给出一个例子来说明反向传播 不过是英文的,如果你感觉不好阅读 ...

  3. Python机器学习笔记:异常点检测算法——LOF(Local Outiler Factor)

    完整代码及其数据,请移步小编的GitHub 传送门:请点击我 如果点击有误:https://github.com/LeBron-Jian/MachineLearningNote 在数据挖掘方面,经常需 ...

  4. 机器学习 —— 基础整理(七)前馈神经网络的BP反向传播算法步骤整理

    这里把按 [1] 推导的BP算法(Backpropagation)步骤整理一下.突然想整理这个的原因是知乎上看到了一个帅呆了的求矩阵微分的方法(也就是 [2]),不得不感叹作者的功力.[1] 中直接使 ...

  5. 神经网络与机器学习 笔记—反向传播算法(BP)

    先看下面信号流图,L=2和M0=M1=M2=M3=3的情况,上面是前向通过,下面部分是反向通过. 1.初始化.假设没有先验知识可用,可以以一个一致分布来随机的挑选突触权值和阈值,这个分布选择为均值等于 ...

  6. 【机器学习】反向传播算法 BP

    知识回顾 1:首先引入一些便于稍后讨论的新标记方法: 假设神经网络的训练样本有m个,每个包含一组输入x和一组输出信号y,L表示神经网络的层数,S表示每层输入的神经元的个数,SL代表最后一层中处理的单元 ...

  7. ML(5)——神经网络2(BP反向传播)

    上一章的神经网络实际上是前馈神经网络(feedforward neural network),也叫多层感知机(multilayer perceptron,MLP).具体来说,每层神经元与下一层神经元全 ...

  8. 【原】Coursera—Andrew Ng机器学习—编程作业 Programming Exercise 4—反向传播神经网络

    课程笔记 Coursera—Andrew Ng机器学习—课程笔记 Lecture 9_Neural Networks learning 作业说明 Exercise 4,Week 5,实现反向传播 ba ...

  9. 100天搞定机器学习|day38 反向传播算法推导

    往期回顾 100天搞定机器学习|(Day1-36) 100天搞定机器学习|Day37无公式理解反向传播算法之精髓 上集我们学习了反向传播算法的原理,今天我们深入讲解其中的微积分理论,展示在机器学习中, ...

  10. 手写BP(反向传播)算法

    BP算法为深度学习中参数更新的重要角色,一般基于loss对参数的偏导进行更新. 一些根据均方误差,每层默认激活函数sigmoid(不同激活函数,则更新公式不一样) 假设网络如图所示: 则更新公式为: ...

随机推荐

  1. Java基础学习:5、递归

    1.递归:就是方法自己调用自己. public class Test01 { public void test(int n) { if (n > 2) { test(n -1); } Syste ...

  2. 小程序微信支付完整demo,包含退款

    最近刚完成一个商场小程序(http://market.zhenzikj.com/detail/121.html), 使用到了微信支付功能,其中遇到了很多的抗,所以,我把支付这块摘出来,以免大家少走弯路 ...

  3. 排查前端接受后端的map产生的字段错误

    报错内容 [Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingExce ...

  4. System.Diagnostics.Process.Start(); 用法详解

    来源:https://news.68idc.cn/buildlang/ask/20150104156981.html 实例代码:http://www.cppcns.com/ruanjian/cshar ...

  5. thirty-one

    动态组件 动态切换组件的显示和隐藏 如何实现动态组件的渲染 vue提供了有一个内置的<component>组件,专门用来实现动态组件的渲染.示例代码如下: 使用keep-alive保持状态 ...

  6. MySQL 的limit

    题目 limit:从倒数第二行开始,往前查找一行

  7. DevExpress 动态换肤

    我们都知道Devexpress内置了很多themes,那要怎么在使用时动态更改呢. 下面是方法以: 1.如果你们已经有主题了,那就在XAML中删除类似下下面的语句. dx:ThemeManager.T ...

  8. 编译内核出现错误cc1: error: code model kernel does not support PIC mode

    删除该模块目录下的 .cache.mk 文件就好了,重新 make 即可

  9. SparkRDD所有算子操作,建议全部手敲一遍

    说明: 1.以下方法全部来自这个RDD.scala,可以自己看源码 2.使用$SPARK_HOME/bin/spark-shell运行代码 3.注释部分是运行结果 //org.apache.spark ...

  10. import cv2时出现ImportError: DLL load fail:找不到指定模块