初识BP神经网络
原文地址:http://blog.youkuaiyun.com/hjimce/article/details/45457181
作者:hjimce
一、相关理论
因为BP神经网络的求解是用到了梯度下降法,所以建议学习这个算法前,先推导一下回归拟合过程,把逻辑回归先好好搞懂,那么搞这个BP算法就轻轻松松,其实我的理解BP算法就是进化版的逻辑回归。
下面我主要讲BP算法最简单的版本,没有经过任何进化的版本,这样简单易懂。
BP神经网络模型:
BP神经网络模型拓扑结构包括输入层(input)、隐层(hide layer)和输出层(output layer)。上面是BP神经网络的图,输入X(x1,x2,……xn),输出Y(y1,y2,……ym),这里输出和逻辑回归有点区别,逻辑回归输出一般是一个一维的数值,代表分类概率,也就说是逻辑回归的输出是Y(y1)。而bp神经网络的输出可以是多维的特征,即m>=1。还有BP神经网络多了隐藏层,而且有多个神经元。BP的学习规则是使用梯度下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小,原理基本上跟逻辑回归一样。与逻辑回归一样,bp神经网络每个神经元的映射存在如下关系:
也就是说,对于隐藏层的第i个神经元的计算方法为:
需要注意的是,每个神经元的链接权值w都是不一样的。还记得逻辑回归、线性回归是怎么求解这些参数的吗?通过构建残差函数,然后求解残差函数的偏导数,最后采用梯度下降,更新参数。OK,bp算法也是一样的过程,我们需要先记住一个公式:
假设:
则:
这个公式非常重要,我们后面要经常用到这个公式,因此请先把这个求导公式背下来。我这边只讲大体的思路,公式的推导其实跟逻辑回归、线性回归基本相似,因为推导公式参数很不好写,所以懒得打公式,这个推导过程网上一大堆,算法实现的大体思路是:
(1)初始化参数w,b
(2)根据前向传导公式:
计算每一层每个节点的输出值。
(3)构建残差函数,求解偏导数
残差公式为:
其中,N代表训练数据个数,m代表输出层神经元个数,我们希望使得输出层的所有神经元节点的残差总和最小。当然用上面的公式进行推导,最后得到的是批量梯度下降法的公式。因为批量梯度下降法,对于训练样本比较多的时候,计算量估计会很大,因此这里只讲在线梯度下降法的残差公式:
涉及到最小值问题,这个残差公式跟线性回归的定义基本上一样,因此就要利用梯度下降法,求解未知的参数w,b。因此首先要求解偏导数:
(4)利用梯度下降进行反向传播,更新参数:
上面的算法推导过程懒得写的太详细了,因为打公式很累,所以还是直接看代码吧。
二、算法实现
Alorigthm:
1、训练数据归一化。
数据归一化,就是将数据映射到[0,1]或[-1,1]区间或更小的区间。之所以要归一化,我想在学习其他的机器学习算法的时候,也经常遇到。
(1)首先假设一个输入数据为(x1,x2),其中x1=0~1,而x2=100~1000,这样输入数据的特征单位不一样,如果没有对特征进行归一化处理,那么就相当于权重影响一样,x1对输出的影响非常小,而x2对输出的影响却非常大。另一方面,x2那么大,可能将导致收敛速度非常慢,训练时间长。
(2)从输出数据上归一化来讲,如果输出y=100~1000。由于神经网络输出层的激活函数的值域是有限制的,因此需要将网络训练的目标数据映射到激活函数的值域。例如神经网络的输出层若采用S形激活函数,由于S形函数的值域限制在(0,1),也就是说神经网络的输出只能限制在(0,1),所以训练数据的输出就要归一化到[0,1]区间,若没有对y进行归一化到(0,1)那就惨了,即便是你再怎么训练,也不可能把输出训练到取值为100~1000。因此需要把输出归一化到(0,1)才能进行训练。
代码实现:
快速的归一化算法是线性转换算法。
(1)
y = ( x - min )/( max - min )
其中min为x的最小值,max为x的最大值,输入向量为x,归一化后的输出向量为y 。上式将数据归一化到 [ 0 , 1 ]区间,当激活函数采用S形函数时(值域为(0,1))时这条式子适用。
(2)
y = 2 * ( x - min ) / ( max - min ) - 1
这条公式将数据归一化到 [ -1 , 1 ] 区间。当激活函数采用双极S形函数(值域为(-1,1))时这条式子适用。
这边我采用S型函数作为激活函数,归一化函数代码如下:
#训练数据归一化至0~1
def __normalize__(self,trainx):
minx,maxx=self.__MaxMin__(trainx)
return (trainx-minx)/(maxx-minx)
def __MaxMin__(self,trainX):
n,m=shape(trainX)
minx=zeros((n,1))
maxx=zeros((n,1))
for i in range(n):
minx[i,0]=trainX[i,:].min();
maxx[i,0]=trainX[i,:].max();
return minx,maxx</span>
输入、输出归一化:
self.trainy=self.__normalize__(trainy)#训练输出数据归一化
self.data1=self.__normalize__(trainx)#训练输入数据归一化</span>
2、相关参数初始化。
(1)对于权值参数化初始化需要注意的是不能将所有的参数初始化为0。如果所有参数都用相同的值作为初始值,那么所有隐藏层单元最终会得到与输入值有关的、相同的函数(也就是说,对于所有 ,
都会取相同的值,那么对于任何输入
都会有:
)。随机初始化的目的是使对称失效。
(2)学习率初始化,一般学习率当然是越小越好,只是学习率越小,收敛速度越慢
def __init__(self,trainx,trainy):
self.hidenum=2#隐藏层节点的个数
self.error=1;
self.e=0;
self.learningrata=0.9#默认学习率为0.9
self.trainy=self.__normalize__(trainy)#训练输出数据归一化
self.data1=self.__normalize__(trainx)#训练输入数据归一化
mx,nx=shape(trainx)
#方案1 用0作为初始化参数 测试
self.weight1=zeros((self.hidenum,mx));#默认隐藏层有self.hidenum个神经元,输入层与隐藏层的链接权值
self.b1=zeros((self.hidenum,1));
my,ny=shape(trainy)
self.weight2=zeros((my,self.hidenum))#隐藏层与输出层的链接权值
self.b2=zeros((my,1));
#方案2、采用随机初始化为-1~1之间的数 测试
self.weight1=2*random.random((self.hidenum,mx))-1
self.weight2=2*random.random((my,self.hidenum))-1#隐藏层与输出层的链接权值
3、前向传播
outdata2=self.__ForwardPropagation__(self.data1[:,i],self.weight1,self.b1)#隐藏层节点数值
#输出层 outdatatemp为隐藏层数值
outdata3=self.__ForwardPropagation__(outdata2,self.weight2,self.b2)
前向传播函数:
def __ForwardPropagation__(self,indata,weight,b):
outdata=weight*indata+b
outdata=1./(1+exp(-outdata))
return outdata
4、反向传播,首先反向计算每一层的残差,然后计算每一层的偏导数,最后根据梯度下降法更新参数。
#计算每一层的残差
sigma3=(1-outdata3).transpose()*outdata3*(outdata3-self.trainy[:,i])
sigma2=((1-outdata2).transpose()*outdata2)[0,0]*(self.weight2.transpose()*sigma3)
#计算每一层的偏导数
w_derivative2=sigma3*outdata2.transpose()
b_derivative2=sigma3
w_derivative1=sigma2*self.data1[:,i].transpose()
b_derivative1=sigma2
#梯度下降公式
self.weight2=self.weight2-self.learningrata*w_derivative2
self.b2=self.b2-self.learningrata*b_derivative2
self.weight1=self.weight1-self.learningrata*w_derivative1
self.b1=self.b1-self.learningrata*b_derivative1
三、完整代码
源码的学习主要参考前辈博文:《BP人工神经网络的C++实现》,感谢前辈让我学会了BP神经网络的实现。
贴一下整个类的完整代码:
from numpy import *
class CBpnet:
#第一阶段 数据预处理阶段
#构造函数,输入训练数据trainx,输出数据y
def __init__(self,trainx,trainy):
self.hidenum=2
self.error=1;
self.e=0;
self.learningrata=0.9#默认学习率为0.9
self.trainy=self.__normalize__(trainy)#训练输出数据归一化
self.data1=self.__normalize__(trainx)#训练输入数据归一化
mx,nx=shape(trainx)
#方案1 用0作为初始化参数 测试
self.weight1=zeros((self.hidenum,mx));#默认隐藏层有self.hidenum个神经元,输入层与隐藏层的链接权值
self.b1=zeros((self.hidenum,1));
my,ny=shape(trainy)
self.weight2=zeros((my,self.hidenum))#隐藏层与输出层的链接权值
self.b2=zeros((my,1));
#方案2、采用随机初始化为-1~1之间的数 测试
self.weight1=2*random.random((self.hidenum,mx))-1
self.weight2=2*random.random((my,self.hidenum))-1#隐藏层与输出层的链接权值
#训练数据归一化至0~1
def __normalize__(self,trainx):
minx,maxx=self.__MaxMin__(trainx)
return (trainx-minx)/(maxx-minx)
def __MaxMin__(self,trainX):
n,m=shape(trainX)
minx=zeros((n,1))
maxx=zeros((n,1))
for i in range(n):
minx[i,0]=trainX[i,:].min();
maxx[i,0]=trainX[i,:].max();
return minx,maxx
#第二阶段 数据训练阶段
def Traindata(self):
mx,nx=shape(self.data1)
#随机梯度下降法
for i in range(mx):
#第一步、前向传播
#隐藏层
outdata2=self.__ForwardPropagation__(self.data1[:,i],self.weight1,self.b1)#隐藏层节点数值
#输出层 outdatatemp为隐藏层数值
outdata3=self.__ForwardPropagation__(outdata2,self.weight2,self.b2)
self.e=self.e+(outdata3-self.trainy[:,i]).transpose()*(outdata3-self.trainy[:,i])
self.error=self.e/2.
#计算每一层的残差
sigma3=(1-outdata3).transpose()*outdata3*(outdata3-self.trainy[:,i])
sigma2=((1-outdata2).transpose()*outdata2)[0,0]*(self.weight2.transpose()*sigma3)
#计算每一层的偏导数
w_derivative2=sigma3*outdata2.transpose()
b_derivative2=sigma3
w_derivative1=sigma2*self.data1[:,i].transpose()
b_derivative1=sigma2
#梯度下降公式
self.weight2=self.weight2-self.learningrata*w_derivative2
self.b2=self.b2-self.learningrata*b_derivative2
self.weight1=self.weight1-self.learningrata*w_derivative1
self.b1=self.b1-self.learningrata*b_derivative1
def __ForwardPropagation__(self,indata,weight,b):
outdata=weight*indata+b
outdata=1./(1+exp(-outdata))
return outdata</span>
通过8个3位二进制样本对应一个期望输出,训练BP网络,最后训练好的网络可以将输入的三位二进制数对应输出一位十进制数。
测试代码如下:
trainX=mat([[0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1]]).transpose()
trainY=mat([[0],[0.1429],[0.2857],[0.4286],[0.5714],[0.7143],[0.8571],[1.0000]]).transpose()
st=CBpnet(trainX,trainY)
i=0;
while(i<10000):
st.e=0
st.Traindata()
print i,"\t",st.error
i=i+1
看一下结果:迭代了10000次,误差就非常小了,结果非常OK
参考文献:
1、http://www.cnblogs.com/heaad/archive/2011/03/07/1976443.html
2、http://blog.youkuaiyun.com/luxiaoxun/article/details/7649945
3、http://ufldl.stanford.edu/wiki/index.php/%E5%8F%8D%E5%90%91%E4%BC%A0%E5%AF%BC%E7%AE%97%E6%B3%95
***************作者:hjimce 联系qq:1393852684 更多资源请关注我的博客:http://blog.youkuaiyun.com/hjimce 原创文章,转载请保留本行信息**********************