从零到一实现神经网络(六):误差反向传播算法更新网络权重

本博客参考书籍:python神经网络编程

误差反向传播原理

下图中,e=[e1,e2,e3,…]表示输出层各神经元的输出误差
在这里插入图片描述

上面的章节我们知道,损失函数的函数值是所有输出神经元的输出值误差另一种表示,它的出现是为了将神经网络的表现量化。又因为输出层某一神经元的输出值是由上一层所有神经元输出值与其对应权重参数乘积的代数和,所以该输出层神经元的误差与上一层所有神经元(对应的权重参数)都有关(也就是说输出和误差是多个节点共同作用的结果),…,这样一层层地向上传播直到输入层,这便是误差能够反向传播进而影响到权重参数的原因

单个神经元上的误差传播

在这里插入图片描述
当某一神经元只有一个前馈信号(前一层只有一个神经元与之相链接)时,显而易见,该神经元所造成的误差全部由其前馈信号造成,误差直接向前传递即可
当有两个前馈信号时,误差如何合理分配
在这里插入图片描述

一种思想是我们按照权重分配,我们认为相对较大的权重对误差的贡献比例也较大,相应的它也应该分的更多的误差
在这里插入图片描述

更多层的误差传播

我们这里使用一个包含一个隐藏层的3层神经网络作为例子
在这里插入图片描述
从上面的图片中我们可以看到每个神经元都能够得到下一层神经元传递过来的误差,其中
隐藏层第一个神经元得到的误差
e h i d d e n 1 = w 1 , 1 w 1 , 1 + w 1 , 2 ∗ e o u t p u t 1 + w 2 , 1 w 2 , 1 + w 2 , 2 ∗ e o u t p u t 2 e_{hidden_1}=\frac{w_{1,1}}{w_{1,1}+w_{1,2}}*e_{output_1}+\frac{w_{2,1}}{w_{2,1}+w_{2,2}}*e_{output_2} ehidden1=w1,1+w1,2w1,1eoutput1+w2,1+w2,2w2,1eoutput2
隐藏层第二个神经元得到的误差
e h i d d e n 2 = w 1 , 2 w 1 , 1 + w 1 , 2 ∗ e o u t p u t 1 + w 2 , 2 w 2 , 1 + w 2 , 2 ∗ e o u t p u t 2 e_{hidden_2}=\frac{w_{1,2}}{w_{1,1}+w_{1,2}}*e_{output_1}+\frac{w_{2,2}}{w_{2,1}+w_{2,2}}*e_{output_2} ehidden2=w1,1+w1,2w1,2eoutput1+w2,1+w2,2w2,2eoutput2

下面我们带入数值计算

在这里插入图片描述

误差计算的矩阵表示

上面我们推导出了隐藏层的两个神经元输出误差的数学表达式,当神经元数量增多时,这个式子将会变得十分繁琐,同时为了方便使用Numpy的广播机制,我们在这里使用矩阵表示

输出层的误差矩阵
e o u t p u t = ( e o u t p u t 1 e o u t p u t 2 ) e_{output}=\begin{pmatrix} e_{output_1}\\ e_{output_2} \end{pmatrix} eoutput=(eoutput1eoutput2)
隐藏层的误差矩阵
e h i d d e n = ( w 1 , 1 w 1 , 1 + w 1 , 2 w 2 , 1 w 2 , 1 + w 2 , 2 w 1 , 2 w 1 , 1 + w 1 , 2 w 2 , 2 w 2 , 1 + w 2 , 2 ) ∗ ( e o u t p u t 1 e o u t p u t 2 ) = ( e h i d d e n 1 e h i d d e n 2 ) e_{hidden}=\begin{pmatrix} \frac{w_{1,1}}{w_{1,1}+w_{1,2}}&\frac{w_{2,1}}{w_{2,1}+w_{2,2}}\\ &\\ \frac{w_{1,2}}{w_{1,1}+w_{1,2}}&\frac{w_{2,2}}{w_{2,1}+w_{2,2}} \end{pmatrix}*\begin{pmatrix}e_{output_1}\\ e_{output_2} \end{pmatrix}=\begin{pmatrix} e_{hidden_{1}}\\e_{hidden_{2}}\end{pmatrix} ehidden=w1,1+w1,2w1,1w1,1+w1,2w1,2w2,1+w2,2w2,1w2,1+w2,2w2,2(eoutput1eoutput2)=(ehidden1ehidden2)

我们看到,上面这个式子还是太复杂了,如何将其简化?
实际上,权重矩阵的每一个元素都带有一个分母,而这个分母的作用是归一化因子,所以如果我们将分母去除,那么我们得到的将会是
e h i d d e n = ( w 1 , 1 w 2 , 1 w 1 , 2 w 2 , 2 ) ∗ ( e o u t p u t 1 e o u t p u t 2 ) = ( e h i d d e n 1 e h i d d e n 2 ) e_{hidden}=\begin{pmatrix} w_{1,1}&w_{2,1}\\ &\\ w_{1,2}&w_{2,2} \end{pmatrix}*\begin{pmatrix}e_{output_1}\\ e_{output_2} \end{pmatrix}=\begin{pmatrix} e_{hidden_{1}}\\e_{hidden_{2}}\end{pmatrix} ehidden=w1,1w1,2w2,1w2,2(eoutput1eoutput2)=(ehidden1ehidden2)

关于这个化简的问题,推导过程笔者正在研究当中,不过在这里笔者还是有一个理解,我们如果将分母去掉,显然,隐藏层获得的误差应该会增大,但是隐藏层误差关于其权重参数的偏导也会成倍增大,梯度也会成倍增大,那么权重参数的更新幅度也会成倍增大,所以从这个角度来看,去掉分母可能并不会影响最终效果

求函数梯度的新方法

在误差反向传播算法中,还是需要求得函数的梯度,不过我们将不再使用数值微分的方法,下面我们从误差函数与权重参数的关系式入手

举一个包含一个隐藏层的3层神经网络作为例子,在该神经网络中中我们使用sigmoid函数作为输出层的映射函数,均方差误差函数作为损失函数
在这里插入图片描述

在输出层中使用sigmoid()函数可能有点少见,实际上这是可以的,该函数的输出范围在(0,1)之间,并且呈递增状,能够很多反映输出层拟输出中包含的特征,另外,该函数求导比较容易,在本实例中表现较好

输出层输出误差 e k e_k ek与权重参数的偏导数表达式推导

根据均方差损失函数 E = 1 2 ∑ n ( t n − o n ) 2 E=\frac{1}{2}\sum_{n}(t_{n}-o_{n})^{2} E=21n(tnon)2,我们可以得到输出层某一神经元输出误差 e k = 1 2 ( t k − o k ) 2 e_k=\frac{1}{2}(t_{k}-o_{k})^{2} ek=21(tkok)2,由于 t k t_k tk是该神经元的理想输出,为一个常数,所以输出误差 e k e_k ek只与实际输出 o k o_k ok有函数关系
在这里插入图片描述

于是,该神经元的输出误差 e k e_{k} ek关于与之相连的权重参数的偏导数如下

∂ e k ∂ w k , j = 1 2 ∂ ∂ w k , j ( t k − o k ) 2 \frac{\partial e_{k}}{\partial w_{k,j}}=\frac{1}{2}\frac{\partial}{\partial w_{k,j}}(t_k-o_k)^{2} wk,jek=21wk,j(tkok)2

根据链式法则
∂ e k ∂ w k , j = ∂ e k ∂ o k ∂ o k ∂ w k \frac{\partial e_{k}}{\partial w_{k,j}}=\frac{\partial e_k}{\partial o_k}\frac{\partial o_k}{\partial w_k} wk,jek=okekwkok
其中
∂ e k ∂ o k = − ( t k − o k ) \frac{\partial e_k}{\partial o_k}=-(t_k-o_k) okek=(tkok)
∂ o k ∂ w k = ∂ ∂ w k , j s i g m o i d ( ∑ j w k , j ∗ o j ) \frac{\partial o_k}{\partial w_k}=\frac{\partial}{\partial w_{k,j}}sigmoid(\sum_{j}w_{k,j}*o_j) wkok=wk,jsigmoid(jwk,joj)
其中
∂ ∂ x s i g m o i d ( x ) = ∂ ∂ x 1 1 + e − x = e − x ( 1 + e − x ) 2 = 1 + ( − 1 ) + e − x ( 1 + e − x ) 2 = 1 1 + e − x ( 1 − 1 1 + e − x ) = s i g m o i d ( x ) ( 1 − s i g m o i d ( x ) ) \begin{aligned} \frac{\partial}{\partial x} sigmoid(x)&=\frac{\partial}{\partial x} \frac{1}{1+e^{-x}} \\ &=\frac{e^{-x}}{(1+e^{-x})^{2}}\\ &=\frac{1+(-1)+e^{-x}}{(1+e^{-x})^{2}}\\&=\frac{1}{1+e^{-x}}(1-\frac{1}{1+e^{-x}})\\&=sigmoid(x)(1-sigmoid(x)) \end{aligned} xsigmoid(x)=x1+ex1=(1+ex)2ex=(1+ex)21+(1)+ex=1+ex1(11+ex1)=sigmoid(x)(1sigmoid(x))

所以
令 t = ∑ j w k , j ∗ o j 令t=\sum_{j}w_{k,j}*o_j t=jwk,joj
则 ∂ o k ∂ w k = ∂ s i g m o i d ( t ) ∂ t ∂ t ∂ w k , j = s i g m o i d ( t ) ( 1 − s i g m o i d ( t ) ∗ ∂ ∂ w k , j ( ∑ j w k , j ∗ o j ) = s i g m o i d ( ∑ j w k , j ∗ o j ) ( 1 − s i g m o i d ( ∑ j w k , j ∗ o j ) ) ∗ o j \begin{aligned} 则 \frac{\partial o_k}{\partial w_k}&=\frac{\partial sigmoid(t)}{\partial t}\frac{\partial t}{\partial w_{k,j}}\\ &=sigmoid(t)(1-sigmoid(t)*\frac{\partial}{\partial w_{k,j}}(\sum_jw_{k,j}*o_j)\\ &=sigmoid(\sum_{j}w_{k,j}*o_j)(1-sigmoid(\sum_{j}w_{k,j}*o_j))*o_j \end{aligned} wkok=tsigmoid(t)wk,jt=sigmoid(t)(1sigmoid(t)wk,j(jwk,joj)=sigmoid(jwk,joj)(1sigmoid(jwk,joj))oj

∂ e k ∂ w k , j = ∂ e k ∂ o k ∂ o k ∂ w k = − ( t k − o k ) ∗ s i g m o i d ( ∑ j w k , j ∗ o j ) ( 1 − s i g m o i d ( ∑ j w k , j ∗ o j ) ) ∗ o j \frac{\partial e_{k}}{\partial w_{k,j}}=\frac{\partial e_k}{\partial o_k}\frac{\partial o_k}{\partial w_k}=-(t_k-o_k)*sigmoid(\sum_{j}w_{k,j}*o_j)(1-sigmoid(\sum_{j}w_{k,j}*o_j))*o_j wk,jek=okekwkok=(tkok)sigmoid(jwk,joj)(1sigmoid(jwk,joj))oj
又因为
o k = s i g m o i d ( ∑ j w k , j ∗ o j ) o_k=sigmoid(\sum_{j}w_{k,j}*o_j) ok=sigmoid(jwk,joj)
所以
∂ e k ∂ w k , j = − e k ∗ o k ( 1 − o k ) ∗ o j T \frac{\partial e_{k}}{\partial w_{k,j}}=-e_k*o_k(1-o_k)*o_j^{T} wk,jek=ekok(1ok)ojT

我们得到了输出层输出误差 e k e_{k} ek关于与之相连的权重参数的偏导数的数学表达式,那么
Δ w k , j = η ∂ e k ∂ w k , j \Delta w_{k,j}=\eta\frac{\partial e_k}{\partial w_{k,j}} Δwk,j=ηwk,jek

隐藏层输出误差 e j e_j ej与权重参数的偏导数表达式推导

同理,我们可以得到隐藏层输出误差 e j e_{j} ej关于与之相连的权重参数的偏导数的数学表达式
∂ e j ∂ w j , i = − e j ∗ o j ( 1 − o j ) ∗ o i T \frac{\partial e_{j}}{\partial w_{j,i}}=-e_j*o_j(1-o_j)*o_i^{T} wj,iej=ejoj(1oj)oiT
Δ w j , i = η ∂ e j ∂ w j , i \Delta w_{j,i}=\eta\frac{\partial e_j}{\partial w_{j,i}} Δwj,i=ηwj,iej

初始权重的优化

由于在输出层我们使用sigmoid()函数作为映射函数,而sigmoid()函数的缺点就是容易导致网络饱和,我们可以将权重设置为[0,1]之间的均匀分布

self.w_i_h=np.random.rand(self.h_node,self.inode)-0.5    # 输入层与隐藏层之间的权重参数
self.w_h_o=np.random.rand(self.o_node,self.h_node)-0.5

实际上根据相关学者对于初始权重参数的设定的研究表明,对于特定形状的网络,特定的激活函数,初始权重参数是固定的。他们得出的结论大致为:一个节点传入链接数量平方根的倒数范围内随机取样,比如输出层每个神经元有3个传入链接,那么隐藏层与输出层之间的初始权重就应该在 ( − 1 3 , 1 3 ) (-\frac{1}{\sqrt{3}},\frac{1}{\sqrt{3}}) (3 1,3 1)之间

self.w_i_h=np.random.normal(0.0,pow(self.h_node,-0.5),(self.h_node,self.i_node))
self.w_h_o=np.random.normal(0.0,pow(self.o_node,-0.5),(self.h_node,self.i_node))

神经网络中的信号传递

线代基础:矩阵相乘条件

为了方便表示众多权重参数以方便计算,我们引入了矩阵
矩阵相乘的条件:左矩阵的列数等于右矩阵行数,如下图
( a 11 a 12 a 21 a 22 a 31 a 32 ) ∗ ( b 11 b 21 ) = ( a 11 b 11 + a 12 b 21 a 21 b 11 + a 22 b 21 a 31 b 11 + a 32 b 21 ) \begin{pmatrix} a_{11}&a_{12}\\ a_{21}&a_{22}\\ a_{31}&a_{32} \end{pmatrix} *\begin{pmatrix} b_{11}\\ b_{21} \end{pmatrix}=\begin{pmatrix} a_{11}b_{11}+a_{12}b_{21}\\ a_{21}b_{11}+a_{22}b_{21}\\ a_{31}b_{11}+a_{32}b_{21} \end{pmatrix} a11a21a31a12a22a32(b11b21)=a11b11+a12b21a21b11+a22b21a31b11+a32b21
相乘前后,矩阵维度变化 :(行,列)
( 3 , 2 ) ∗ ( 2 , 1 ) = ( 3 , 1 ) (3,2)*(2,1)=(3,1) (3,2)(2,1)=(3,1)

结论:相乘后得到的矩阵 行数等于左矩阵,列数等于右矩阵

信号正向传播过程图示

输入层到隐藏层

在这里插入图片描述

输入层的预处理

如我们看到的那样,输入层的每个神经元都是一个输入数据,我们通常使用列表表示一组输入数据: i n p u t s = [ x 1 , x 2 , x 3 , . . . , x n ] inputs=[x_1,x_2,x_3,...,x_n] inputs=[x1,x2,x3,...,xn],这样的数据不满足矩阵相乘的条件

因此我们将上述数组转化为numpy数组表示
i n p u t s = a r r a y ( [ [ x 1 , x 2 , x 3 , . . . . , x n ] ] ) \begin{aligned} inputs=array([[&x_1,\\ &x_2,\\ &x_3,\\ &....,\\ &x_n]]) \end{aligned} inputs=array([[x1,x2,x3,....,xn]])

矩阵表示为
i n p u t s = ( x 1 x 2 x 3 . . . x n ) inputs=\begin{pmatrix} x_1\\ x_2\\ x_3\\ ...\\ x_n \end{pmatrix} inputs=x1x2x3...xn

层/参数矩阵维度符号表示计算过程
输出层实际输出矩阵(onode,1)o_outputsigmoid(o_input)
拟输出矩阵(onode,1)o_inputnp.dot(w_o,h_output)
权重参数(onode,hnode)w_o
隐藏层实际输出矩阵(hnode,1)h_outputsigmoid(h_input)
拟输出矩阵(hnode,1)h_inputnp.dot(w_h,inputs)
权重参数(hnode,inode)w_h
输入层输出矩阵(inode,1)inputs

反向传播过程计算过程图示

层/权重维度符号表示计算过程
输出层(onode,1)o_errortargets-o_output
权重更新(onode,1)*(1,hnode)w_o+=lr * np.dot((o_error* o_output* (1.0 - o_output)), h_output.T)
隐藏层(hnode,1)h_errornp.dot(w_o.T,o_error)
权重更新(hnode,1)*(1,inode)w_h+=lr * np.dot((h_error* h_output* (1.0 - h_output)), inputs.T)

三层神经网络框架

class Threelayer_net:
	def __init__(self,i_node,h_node,o_node,learningrate):	# 设置网络的一些参数
		pass
	def predict(self,input_):	# 使用网络进行预测
		pass
	def train(self,input_,target_):		# 训练网络,更新参数
		pass	

代码的最终实现

class Threelayer_net:
    def __init__(self, inode, hnode, onode, learningrate):
        self.inode = inode
        self.hnode = hnode
        self.onode = onode
        # 权重参数初始化
        self.wh = np.random.normal(0.0, pow(self.inode, -0.5), (self.hnode, self.inode))
        self.wo = np.random.normal(0.0, pow(self.hnode, -0.5), (self.onode, self.hnode))
		# 设置学习率
        self.lr = learningrate
        
    def train(self, input_, target_):	# 训练网络
        inputs = np.array(input_, ndmin=2).T
        targets = np.array(target_, ndmin=2).T
        # 隐藏层神经元
        h_input = np.dot(self.wh, inputs)
        h_output = sigmoid(h_input)
        # 输出层神经元
        o_input = np.dot(self.wo, h_output)
        o_output = sigmoid(o_input)
        # 计算误差并反向传播
        o_error = targets - o_output
        h_error = np.dot(self.wo.T, o_error) 
        # 权重更新,关于这里的权重参数是加减的问题,实际上并没有影响,权重过大会导致输出增大,误差变为负数,这样的结果会反过来使得权重减小
        self.wo += self.lr * np.dot((o_error * o_output * (1.0 - o_output)),h_output.T)
        self.wh+=self.lr*np.dot((h_error*h_output*(1.0-h_output)),inputs.T)
        
        return o_error
        
    def predict(self,input_):	# 预测数据
        inputs=np.array(input_,ndmin=2).T
        
        h_input=np.dot(self.wh,inputs)
        h_output=sigmoid(h_input)
        
        o_input=np.dot(self.wo,h_output)
        o_output=sigmoid(o_input)
        
        return o_output

笔者将模型的训练过程放在了码云,请参考:训练过程源码
另外,本博客参考:《python神经网络编程》作者的github源码

  • 7
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夺笋123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值