1. 简单表达式和理解梯度
函数变量在某个点周围的极小区域内变化,而导数就是变量变化导致的函数在该方向上的变化率。要注意的是,在这里已经指出了是在“极小区域”内,所以函数在以某一点的数值只能用来估计所对应的自变量发生微小变化时函数值的变化情况;当自变量的变化值很大时是无法用导数进行估计的。
函数关于每个变量的导数指明了整个表达式对于该变量的敏感程度。指的是自变量变化单位长度的时候,因变量变化的长度是自变量的多少倍。
对加法操作求导:
这就是说,无论其值如何, x,y 的导数均为1。这是有道理的,因为无论增加 x,y 中任一个的值,函数f的值都会增加,并且增加的变化率独立于 x,y 的具体值(情况和乘法操作不同)。
取最大值操作也是常常使用的:
上式是说,如果该变量比另一个变量大,那么梯度是1,反之为0。例如,若
x=4,y=2
,那么max是4,所以函数对于
y
就不敏感。也就是说,在
2. 使用链式法则计算复合表达式
现在考虑更复杂的包含多个函数的复合函数,比如
f(x,y,z)=(x+y)z
。虽然这个表达足够简单,可以直接微分,但是在此使用一种有助于读者直观理解反向传播的方法。将公式分成两部分:
q=x+y
和
f=qz
。在前面已经介绍过如何对这分开的两个公式进行计算,因为
f
是
# 设置输入值
x = -2; y = 5; z = -4
# 进行前向传播
q = x + y # q becomes 3
f = q * z # f becomes -12
# 进行反向传播:
# 首先回传到 f = q * z
dfdz = q # df/dz = q, 所以关于z的梯度是3
dfdq = z # df/dq = z, 所以关于q的梯度是-4
# 现在回传到q = x + y
dfdx = 1.0 * dfdq # dq/dx = 1. 这里的乘法是因为链式法则
dfdy = 1.0 * dfdq # dq/dy = 1
最后得到变量的梯度
[dfdx,dfdy,dfdz]
,它们告诉我们函数f对于变量
[x,y,z]
的敏感程度。这是一个最简单的反向传播。一般会使用一个更简洁的表达符号,这样就不用写
df
了。这就是说,用
dq
来代替
dfdq
,且总是假设梯度是关于最终输出的。
反向传播可以看做是门单元之间在通过梯度信号相互通信,只要让它们的输入沿着梯度方向变化,无论它们自己的输出值在何种程度上升或降低,都是为了让整个网络的输出值更高。
3. 反向传播实践:分段计算
假设有如下函数:
首先要说的是,这个函数完全没用,读者是不会用到它来进行梯度计算的,这里只是用来作为实践反向传播的一个例子,需要强调的是,如果对x或y进行微分运算,运算结束后会得到一个巨大而复杂的表达式。然而做如此复杂的运算实际上并无必要,因为我们不需要一个明确的函数来计算梯度,只需知道如何使用反向传播计算梯度即可。下面是构建前向传播的代码模式:
x = 3 # 例子数值
y = -4
# 前向传播
sigy = 1.0 / (1 + math.exp(-y)) # 分子中的sigmoi #(1)
num = x + sigy # 分子 #(2)
sigx = 1.0 / (1 + math.exp(-x)) # 分母中的sigmoid #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # 分母 #(6)
invden = 1.0 / den #(7)
f = num * invden # 搞定! #(8)
到了表达式的最后,就完成了前向传播。注意在构建代码s时创建了多个中间变量,每个都是比较简单的表达式,它们计算局部梯度的方法是已知的。这样计算反向传播就简单了:我们对前向传播时产生每个变量 (sigy,num,sigx,xpy,xpysqr,den,invden) 进行回传。我们会有同样数量的变量,但是都以 d 开头,用来存储对应变量的梯度。注意在反向传播的每一小块中都将包含了表达式的局部梯度,然后根据使用链式法则乘以上游梯度。对于每行代码,我们将指明其对应的是前向传播的哪部分。
# 回传 f = num * invden
dnum = invden # 分子的梯度 #(8)
dinvden = num #(8)
# 回传 invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# 回传 den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# 回传 xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# 回传 xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# 回传 sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)
# 回传 num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# 回传 sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
其中需要注意的是,在上文中已经提到用
对前向传播变量进行缓存:在计算反向传播时,前向传播过程中得到的一些中间变量非常有用。在实际操作中,最好代码实现对于这些中间变量的缓存,这样在反向传播的时候也能用上它们。如果这样做过于困难,也可以(但是浪费计算资源)重新计算它们。
在不同分支的梯度要相加:如果变量x,y在前向传播的表达式中出现多次,那么进行反向传播的时候就要非常小心,使用+=而不是=来累计这些变量的梯度(不然就会造成覆写)。这是遵循了在微积分中的多元链式法则,该法则指出如果变量在线路中分支走向不同的部分,那么梯度在回传的时候,就应该进行累加。
4. 回传流中的模式
反向传播中的梯度可以被很直观地解释。例如神经网络中最常用的加法、乘法和取最大值这三个门单元,它们在反向传播过程中的行为都有非常简单的解释。先看下面这个例子:
一个展示反向传播的例子。加法操作将梯度相等地分发给它的输入。取最大操作将梯度路由给更大的输入。乘法门拿取输入激活数据,对它们进行交换,然后乘以梯度。
加法门单元把输出的梯度相等地分发给它所有的输入,这一行为与输入值在前向传播时的值无关。这是因为加法操作的局部梯度都是简单的+1,所以所有输入的梯度实际上就等于输出的梯度,因为乘以1.0保持不变。上例中,加法门把梯度2.00不变且相等地路由给了两个输入。
取最大值门单元对梯度做路由。和加法门不同,取最大值门将梯度转给其中一个输入,这个输入是在前向传播中值最大的那个输入。这是因为在取最大值门中,最高值的局部梯度是1.0,其余的是0。上例中,取最大值门将梯度2.00转给了z变量,因为z的值比w高,于是w的梯度保持为0。
乘法门单元相对不容易解释。它的局部梯度就是输入值,但是是相互交换之后的,然后根据链式法则乘以输出值的梯度。上例中,x的梯度是-4.00x2.00=-8.00。
非直观影响及其结果。注意一种比较特殊的情况,如果乘法门单元的其中一个输入非常小,而另一个输入非常大,那么乘法门的操作将会不是那么直观:它将会把大的梯度分配给小的输入,把小的梯度分配给大的输入。在线性分类器中,权重和输入是进行点积w^Tx_i,这说明输入数据的大小对于权重梯度的大小有影响。例如,在计算过程中对所有输入数据样本x_i乘以1000,那么权重的梯度将会增大1000倍,这样就必须降低学习率来弥补。这就是为什么数据预处理关系重大,它即使只是有微小变化,也会产生巨大影响。对于梯度在计算线路中是如何流动的有一个直观的理解,可以帮助读者调试网络。
本文所介绍内容相对基础简单,更多相关细节可以参考博客《三、梯度下降与反向传播(含过程推导及证明)》

本文详细介绍了反向传播的概念,从简单表达式和梯度理解开始,到使用链式法则计算复合表达式,再到反向传播的实际应用,包括分段计算和处理分支情况。文章通过实例解释了加法、乘法和取最大值门单元在反向传播中的行为,强调了数据预处理对梯度计算的影响,帮助理解神经网络中梯度如何流动。

9062

被折叠的 条评论
为什么被折叠?



