详解神经网络的前向传播和反向传播(从头推导)

详解神经网络的前向传播和反向传播(从头推导)详解神经网络的前向传播和反向传播本篇博客是对MichaelNielsen所著的《NeuralNetworkandDeepLearning》第2章内容的解读,有兴趣的朋友可以直接阅读原文NeuralNetworkandDeepLearning。  对神经网络有些了解的人可能都知道,神经网络其实就是一个输入XXX到输出YYY的映射函数:f(X)=Yf(X)=Yf(X)=Y,函…

大家好,又见面了,我是你们的朋友全栈君。

详解神经网络的前向传播和反向传播

本篇博客是对Michael Nielsen所著的《Neural Network and Deep Learning》第2章内容的解读,有兴趣的朋友可以直接阅读原文Neural Network and Deep Learning

  对神经网络有些了解的人可能都知道,神经网络其实就是一个输入 X X 到输出
Y


Y

的映射函数: f(X)=Y f ( X ) = Y ,函数的系数就是我们所要训练的网络参数 W W ,只要函数系数确定下来,对于任何输入
xi



x


i


我们就能得到一个与之对应的输出 yi y i ,至于 yi y i 是否符合我们预期,这就属于如何提高模型性能方面的问题了,本文不做讨论。

  那么问题来了,现在我们手中只有训练集的输入 X X 和输出
Y


Y

,我们应该如何调整网络参数 W W 使网络实际的输出
f(X)=Y^


f


(


X


)


=




Y


^



与训练集的 Y Y 尽可能接近?

  在开始正式讲解之前,让我们先对反向传播过程有一个直观上的印象。反向传播算法的核心是代价函数
C


C

对网络中参数(各层的权重 w w 和偏置
b


b

)的偏导表达式 Cw ∂ C ∂ w Cb ∂ C ∂ b 。这些表达式描述了代价函数值 C C 随权重
w


w

或偏置 b b 变化而变化的程度。到这里,BP算法的思路就很容易理解了:如果当前代价函数值距离预期值较远,那么我们通过调整
w


w

b b 的值使新的代价函数值更接近预期值(和预期值相差越大,则
w


w

b b 调整的幅度就越大)。一直重复该过程,直到最终的代价函数值在误差范围内,则算法停止。

  BP算法可以告诉我们神经网络在每次迭代中,网络的参数是如何变化的,理解这个过程对于我们分析网络性能或优化过程是非常有帮助的,所以还是尽可能搞透这个点。我也是之前大致看过,然后发现看一些进阶知识还是需要BP的推导过程作为支撑,所以才重新整理出这么一篇博客。

前向传播过程

  在开始反向传播之前,先提一下前向传播过程,即网络如何根据输入
X


X

得到输出 Y Y 的。这个很容易理解,粗略看一下即可,这里主要是为了统一后面的符号表达。

详解神经网络的前向传播和反向传播(从头推导)
详解神经网络的前向传播和反向传播(从头推导)


wljk



w



j


k




l



为第 l1 l − 1 层第 k k 个神经元到第
l


l

层第 j j 个神经元的权重,
blj



b


j


l


为第 l l 层第
j


j

个神经元的偏置, alj a j l 为第 l l 层第
j


j

个神经元的激活值(激活函数的输出)。不难看出, alj a j l 的值取决于上一层神经元的激活:

alj=σ(kwljkal1k+blj)(1) (1) a j l = σ ( ∑ k w j k l a k l − 1 + b j l )

将上式重写为矩阵形式:

al=σ(wlal1+bl)(2) (2) a l = σ ( w l a l − 1 + b l )

为了方便表示,记

zl=wlal1+bl z l = w l a l − 1 + b l
为每一层的权重输入,

(2) ( 2 )
式则变为

al=σ(zl) a l = σ ( z l )


  利用

(2) ( 2 )
式一层层计算网络的激活值,最终能够根据输入

X X
得到相应的输出


Y^




Y


^




反向传播过程

  反向传播过程中要计算 Cw ∂ C ∂ w Cb ∂ C ∂ b ,我们先对代价函数做两个假设,以二次损失函数为例:

C=12nxy(x)aL(x)2(3) (3) C = 1 2 n ∑ x ‖ y ( x ) − a L ( x ) ‖ 2

其中

n n
为训练样本


x


x


的总数,

y=y(x) y = y ( x )
为期望的输出,即ground truth,

L L
为网络的层数,


aL(x)



a


L



(


x


)


为网络的输出向量。


假设1:总的代价函数可以表示为单个样本的代价函数之和的平均:

C=1nxCx  Cx=12yaL2(4) (4) C = 1 n ∑ x C x     C x = 1 2 ‖ y − a L ‖ 2



  这个假设的意义在于,因为反向传播过程中我们只能计算单个训练样本的

Cxw ∂ C x ∂ w


Cxb ∂ C x ∂ b
,在这个假设下,我们可以通过计算所有样本的平均来得到总体的

Cw ∂ C ∂ w


Cb ∂ C ∂ b



假设2:代价函数可以表达为网络输出的函数

costC=C(aL) c o s t C = C ( a L )
,比如单个样本

x x
的二次代价函数可以写为:


Cx=12yaL2=12j(yjaLj)2(5)





(5)





C


x



=



1


2






y






a


L







2



=



1


2







j




(



y


j







a


j


L




)


2






反向传播的四个基本方程

  权重 w w 和偏置
b


b

的改变如何影响代价函数 C C 是理解反向传播的关键。最终,这意味着我们需要计算出每个
Cwljk








C









w



j


k



l




Cblj ∂ C ∂ b j l ,在讨论基本方程之前,我们引入误差 δ δ 的概念, δlj δ j l 表示第 l l 层第
j


j

个单元的误差。关于误差的理解,《Neural Network and Deep Learning》书中给了一个比较形象的例子。

详解神经网络的前向传播和反向传播(从头推导)

  如上图所示,假设有个小恶魔在第 l l 层第
j


j

个单元捣蛋,他让这个神经元的权重输出变化了 Δzlj Δ z j l ,那么这个神经元的激活输出为 σ(zlj+Δzlj) σ ( z j l + Δ z j l ) ,然后这个误差向后逐层传播下去,导致最终的代价函数变化了 CzljΔzlj ∂ C ∂ z j l Δ z j l 。现在这个小恶魔改过自新,它想帮助我们尽可能减小代价函数的值(使网络输出更符合预期)。假设 Czlj ∂ C ∂ z j l 一开始是个很大的正值或者负值,小恶魔通过选择一个和 Czlj ∂ C ∂ z j l 方向相反的 Δzlj Δ z j l 使代价函数更小(这就是我们熟知的梯度下降法)。随着迭代的进行, Czlj ∂ C ∂ z j l 会逐渐趋向于0,那么 Δzlj Δ z j l 对于代价函数的改进效果就微乎其微了,这时小恶魔就一脸骄傲的告诉你:“俺已经找到了最优解了(局部最优)”。这启发我们可以用 Czlj ∂ C ∂ z j l 来衡量神经元的误差:

δlj=Czlj δ j l = ∂ C ∂ z j l

下面就来看看四个基本方程是怎么来的。

  


1. 输出层的误差方程


δLj=CzLj=CaLjaLjzLj=CaLjσ(zLj)(BP1) (BP1) δ j L = ∂ C ∂ z j L = ∂ C ∂ a j L ∂ a j L ∂ z j L = ∂ C ∂ a j L σ ′ ( z j L )

如果上面的东西你看明白了,这个方程应该不难理解,等式右边第一项

CaLj ∂ C ∂ a j L
衡量了代价函数随网络最终输出的变化快慢,而第二项

σ(zLj) σ ′ ( z j L )
则衡量了激活函数输出随

zLj z j L
的变化快慢。当激活函数饱和,即

σ(zLj)0 σ ′ ( z j L ) ≈ 0
时,无论

CaLj ∂ C ∂ a j L
多大,最终

δLj0 δ j L ≈ 0
,输出神经元进入饱和区,停止学习。

  (BP1)方程中两项都很容易计算,如果代价函数为二次代价函数

C=12j(yjaLj)2 C = 1 2 ∑ j ( y j − a j L ) 2
,则

CaLj=aLjyj ∂ C ∂ a j L = a j L − y j
,同理,对激活函数

σ(z) σ ( z )


zLj z j L
的偏导即可求得

σ(zLj) σ ′ ( z j L )
。将(BP1)重写为矩阵形式:

δL=aCσ(zL)(BP1a) (BP1a) δ L = ∇ a C ⊙ σ ′ ( z L )




为Hadamard积,即矩阵的点积。


2. 误差传递方程


δl=((wl+1)Tδl+1)σ(zl)(BP2) (BP2) δ l = ( ( w l + 1 ) T δ l + 1 ) ⊙ σ ′ ( z l )

这个方程说明我们可以通过第

l+1 l + 1
层的误差

δl+1 δ l + 1
计算第

l l
层的误差


δl



δ



l




,结合(BP1)和(BP2)两个方程,我们现在可以计算网络中任意一层的误差了,先计算

δL δ L
,然后计算

δL1 δ L − 1


δL2 δ L − 2
,…,直到输入层。

证明过程如下:

δlj=Czlj=kCzl+1kzl+1kzlj=kδl+1kzl+1kzlj δ j l = ∂ C ∂ z j l = ∑ k ∂ C ∂ z k l + 1 ∂ z k l + 1 ∂ z j l = ∑ k δ k l + 1 ∂ z k l + 1 ∂ z j l

因为

zl+1k=jwl+1kjalj+bl+1k=jwl+1kjσ(zlj)+bl+1k z k l + 1 = ∑ j w k j l + 1 a j l + b k l + 1 = ∑ j w k j l + 1 σ ( z j l ) + b k l + 1
,所以

zl+1kzlj=wl+1kjσ(zlj) ∂ z k l + 1 ∂ z j l = w k j l + 1 σ ′ ( z j l )
,因此可以得到(BP2),

δlj=kwl+1kjδl+1kσ(zlj) δ j l = ∑ k w k j l + 1 δ k l + 1 σ ′ ( z j l )




3. 代价函数对偏置的改变率


Cblj=Czljzljblj=Czlj=δlj(BP3) (BP3) ∂ C ∂ b j l = ∂ C ∂ z j l ∂ z j l ∂ b j l = ∂ C ∂ z j l = δ j l

这里因为

zlj=kwljkal1k+blj z j l = ∑ k w j k l a k l − 1 + b j l
所以

zLjbLj=1 ∂ z j L ∂ b j L = 1



4. 代价函数对权重的改变率


Cwljk=CzljzLjwljk=Czljal1k=al1kδlj(BP4) (BP4) ∂ C ∂ w j k l = ∂ C ∂ z j l ∂ z j L ∂ w j k l = ∂ C ∂ z j l a k l − 1 = a k l − 1 δ j l

可以简写为

Cw=ainδout(6) (6) ∂ C ∂ w = a i n δ o u t

,不难发现,当上一层激活输出接近0的时候,无论返回的误差有多大,

Cw ∂ C ∂ w
的改变都很小,这也就解释了为什么神经元饱和不利于训练。

  从上面的推导我们不难发现,当输入神经元没有被激活,或者输出神经元处于饱和状态,权重和偏置会学习的非常慢,这不是我们想要的效果。这也说明了为什么我们平时总是说激活函数的选择非常重要。

  当我计算得到 Cwljk ∂ C ∂ w j k l Cblj ∂ C ∂ b j l 后,就能愉悦地使用梯度下降法对参数进行一轮轮更新了,直到最后模型收敛。

反向传播为什么快

  回答这个问题前,我们先看一下普通方法怎么求梯度。以计算权重为例,我们将代价函数看成是权重的函数 C=C(w) C = C ( w ) ,假设现在网络中有100万个参数,我们可以利用微分的定义式来计算代价函数对其中某个权重 wj w j 的偏导:

CwjC(w+εej)C(w)ε(7) (7) ∂ C ∂ w j ≈ C ( w + ε e j → ) − C ( w ) ε

然后我们算一下,为了计算

Cwj ∂ C ∂ w j
,我们需要从头到尾完整进行一次前向传播才能得到最终

C(w+εej) C ( w + ε e j → )
的值,要计算100万个参数的偏导就需要前向传播100万次,而且这还只是一次迭代,想想是不是特别可怕?

  再反观反向传播算法,如方程(BP4)所示,我们只要知道

al1k a k l − 1


δlj δ j l
就能计算出偏导

Cwljk ∂ C ∂ w j k l
。激活函数值

al1k a k l − 1
在一次前向传播后就能全部得到,然后利用(BP1)和(PB2)可以计算出

δlj δ j l
,反向传播和前向传播计算量相当,所以总共只需2次前向传播的计算量就能计算出所有的

Cwljk ∂ C ∂ w j k l
。这比使用微分定义式求偏导的计算量少了不止一点半点,简直是质的飞跃。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/131491.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • windows的安装_kafka windows安装

    windows的安装_kafka windows安装一、安装JAVAJDK1、下载安装包http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html注意:根据32/64位操作系统下载对应的安装包2、添加系统变量:JAVA_HOME=C:\ProgramFiles(x86)\Java\jdk1.8.0_144二、安装Zo…

  • 使用哈夫曼树实现文本编码、解码

    使用哈夫曼树实现文本编码、解码使用二叉树存储结构的链表,进行构造二叉树,对指定字符串编码解码

  • Redis布隆过滤器原理及应用场景「建议收藏」

    Redis布隆过滤器原理及应用场景「建议收藏」1、布隆过滤器是什么?(判断某个key一定不存在)本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构特点是高效地插入和查询,可以用来告诉你“某样东西一定不存在或者可能存在”。相比于传统的List、Set、Map等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。使用:1.布隆过滤器在NoSQL数据库领域中应用的非常广泛2….

  • linux 安装Jenkins和配置

    linux 安装Jenkins和配置简要介绍Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。语言:Java一句话描述:持续集成工具建议的版本建议使用版本为“jenkins-2.164.x”。环境清单:CentOS7.6配置安装环境安装OpenJDK。 要求Java的OpenJDK为“1.8.0”以上,可以通过以下命令安装…

    2022年10月28日
  • matlab循环读取txt文件「建议收藏」

    matlab循环读取txt文件「建议收藏」一般情况下,假如我要读取一个名为a.txt的文件,只需要利用下面的语句:a=load(‘a.txt’);现在假如我需要循环读取saif_1.txt,saif_2.txt,,,一直到saif_10.txt,他们都是10*1的矩阵,对他们进行转置操作后,再合并到一个文件中,可以利用下面的语句:forN=1:10a=load([‘saif_’,num2str(N),’.txt’]);……

  • 进制转换器

    进制转换器

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号