一、链式法则简化的原则
注意:
(1)本文将求导的链式法则和深度学习中梯度回传看作是一个概念;
(2)本文链式法则的原则首先建立在基本算术算子:加法、减法、乘法、除法、幂运算和简单函数基础之上;
- 复杂运算的简化;定义基本算子为加、减、乘、除、幂以及其他[简单函数](如sin,tan……)(https://zhuanlan.zhihu.com/p/63265208);假如一个运算包含多个算子,那么精简的原则就是怎么把复杂的多算子运算,转换为单一的算子的一次运算,或者其他的一元简单函数,这种精简包含两个特征
(1)多算子简化为单一算子或者单一的简单函数;
(2)单一算子或简单函数必须仅运行1次;
(3)如果简化为加、减、乘、除,简化后的自变量最多两个,至少1个;
(4)如果简化为一个简单函数,则必须要求该简单函数为一元函数;
注意:二元/一元加减乘除幂构成的运算,其实也是简单函数,这里单独把加减乘除运算拉出来,是因为他们构成的简单函数有可能是二元的,即有两个输入;
案例1
f ( w , x ) = 1 1 + e − ( w 0 x 0 + w 1 x 1 + w 2 ) f(w,x)=\frac{1}{1+e^{-(w_0x_0+w_1x_1+w_2)}} f(w,x)=1+e−(w0x0+w1x1+w2)1
表1上述运算式出现的基础算子个数
加 | 减 | 乘 | 除 | 幂 |
---|---|---|---|---|
3 | 1 | 3 | 1 | 1 |
运算中,包含加、减、乘、除、幂,按原则,需要把运算降为一种算子的运算,那么究竟怎么精简:原则:每一步都是将复杂运算精简为一种算子或简单函数,按照这个原则,一直进行下去,直到不能再简化。
(1)如果简化为除法运算,即需要把分母看作一个整体,即:
{
f
(
u
)
=
1
u
u
=
1
+
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
(1)
\begin{cases} f(u)=\frac{1}{u} \tag{1} \\ u= 1+e^{-(w_0x_0+w_1x_1+w_2)} \\ \end{cases}
{f(u)=u1u=1+e−(w0x0+w1x1+w2)(1)
此时只保留了一种运算,且有一个变元;如果将整体的幂运算简化为一个变元呢:
{
f
(
u
)
=
1
u
+
1
u
=
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
(2)
\begin{cases} f(u)=\frac{1}{u+1} \tag{2} \\ u= e^{-(w_0x_0+w_1x_1+w_2)} \\ \end{cases}
{f(u)=u+11u=e−(w0x0+w1x1+w2)(2)
从公式(2)可以看出,简化后的运算,除了包含加法外,还包含了除法,因此,不是最佳简化;
其他简化,也都不能实现方程(1)的结果,即最终只有一个算子;
案例2
f
=
x
y
x
⋅
e
y
+
1
f=\frac{xy}{x·e^y+1}
f=x⋅ey+1xy
如果仅保留一个算子,要实现最佳简化,很明显也是保留除法算子,因此,简化为:
{
f
(
u
,
v
)
=
u
v
u
=
x
y
v
=
x
⋅
e
y
+
1
\begin{cases} f(u,v)=\frac{u}{v}\\ u=xy\\ v=x·e^y+1 \end{cases}
⎩⎪⎨⎪⎧f(u,v)=vuu=xyv=x⋅ey+1
二、最佳简化是否唯一
最佳简化不唯一,因为加法运算存在加法结合率,即不同的结合方式,具有相同的计算结果,但是,不同的结合方式,是不同的简化方法;
f
=
x
y
+
x
⋅
e
y
+
x
y
(3)
f={xy}+{x·e^y} + \frac{x}{y} \tag{3}
f=xy+x⋅ey+yx(3)
公式(3)可以简化为两个变元的加法,即:
{
f
(
u
,
v
)
=
u
+
v
u
=
x
y
v
=
x
⋅
e
y
+
x
y
\begin{cases} f(u,v)=u+v\\ u=xy\\ v=x·e^y+\frac{x}{y} \end{cases}
⎩⎪⎨⎪⎧f(u,v)=u+vu=xyv=x⋅ey+yx
但是,根据加法结合率,也可以把前两项进行结合,构建如下的链式计算:
{
f
(
u
,
v
)
=
u
+
v
u
=
x
y
+
x
⋅
e
y
v
=
x
y
\begin{cases} f(u,v)=u+v\\ u=xy+x·e^y\\ v=\frac{x}{y} \end{cases}
⎩⎪⎨⎪⎧f(u,v)=u+vu=xy+x⋅eyv=yx
这两种分解,从算法的角度,无任何差异,所以,链式简化并不唯一;
三、梯度backward与链式法则求导
有了上述链式法则后,下一步要做的就是递归的进行简化,比如,公式(3)中的
u
u
u、
v
v
v,再继续对其进行链式法则进行简化,原则仍然是简化为一个算子,这其实也是梯度backward的思想来源,即把一个复杂的运算式的求导,转换为一系列的仅一个算子的求导,根据链式法则,复杂运算式的求导等于这一系列简单算子的导数之乘,所谓的梯度回传,就是梯度的计算有一个先后顺序,这个顺序就是我们简化复杂式的顺序;
案例一,简化为基本算子的情况,以除法为例:
f
(
w
0
,
w
1
,
x
)
=
1
1
+
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
f(w_0,w_1,x)=\frac{1}{1+e^{-(w_0x_0+w_1x_1+w_2)}}
f(w0,w1,x)=1+e−(w0x0+w1x1+w2)1
第一步简化:引入变元u,简化为一个除法运算
{
f
(
u
)
=
1
u
u
=
1
+
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
\begin{cases} f(u)=\frac{1}{u}\\ u= 1+e^{-(w_0x_0+w_1x_1+w_2)} \\ \end{cases}
{f(u)=u1u=1+e−(w0x0+w1x1+w2)
第二步简化:对
u
u
u进行简化,引入v,把
u
u
u简化为一个加法算子
{
f
(
u
)
=
1
u
u
=
1
+
v
v
=
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
\begin{cases} f(u)=\frac{1}{u} \\ u= 1+v \qquad\\ v = e^{-(w_0x_0+w_1x_1+w_2)} \end{cases}
⎩⎪⎨⎪⎧f(u)=u1u=1+vv=e−(w0x0+w1x1+w2)
第三步简化:对
v
v
v进行简化,引入
x
x
x,把
v
v
v简化为一个乘幂运算
{
f
(
u
)
=
1
u
u
=
1
+
v
v
=
e
x
x
=
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
\begin{cases} f(u)=\frac{1}{u} \\ u= 1+v \qquad\\ v = e^{x}\\ x=-(w_0x_0+w_1x_1+w_2) \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧f(u)=u1u=1+vv=exx=−(w0x0+w1x1+w2)
第四步简化:对
x
x
x进行简化,引入
y
y
y,把
x
x
x简化为一个减法运算
{
f
(
u
)
=
1
u
u
=
1
+
v
v
=
e
x
x
=
0
−
y
y
=
w
0
x
0
+
w
1
x
1
+
w
2
\begin{cases} f(u)=\frac{1}{u} \\ u= 1+v \qquad\\ v = e^{x}\\ x=0-y\\ y = w_0x_0+w_1x_1+w_2 \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧f(u)=u1u=1+vv=exx=0−yy=w0x0+w1x1+w2
第五步简化:对
y
y
y进行简化,引入
z
z
z,把
y
y
y简化为一个加法运算
{
f
(
u
)
=
1
u
u
=
1
+
v
v
=
e
x
x
=
0
−
y
y
=
z
+
w
2
z
=
w
0
x
0
+
w
1
x
1
\begin{cases} f(u)=\frac{1}{u} \\ u= 1+v \qquad\\ v = e^{x}\\ x=0-y\\ y =z+w_2\\ z=w_0x_0+w_1x_1 \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎧f(u)=u1u=1+vv=exx=0−yy=z+w2z=w0x0+w1x1
第六步简化:对
z
z
z进行简化,引入
m
m
m,
n
n
n把
z
z
z简化为一个加法运算
{
f
(
u
)
=
1
u
u
(
v
)
=
1
+
v
v
(
x
)
=
e
x
x
(
y
)
=
0
−
y
y
(
z
)
=
z
+
w
2
z
(
m
,
n
)
=
m
+
n
m
(
w
0
,
x
0
)
=
w
0
x
0
n
(
w
1
,
x
1
)
=
w
1
x
1
\begin{cases} f(u)=\frac{1}{u} \\ u(v)= 1+v \qquad\\ v(x) = e^{x}\\ x(y)=0-y\\ y(z) =z+w_2\\ z(m,n)=m+n\\ m(w_0,x_0)=w_0x_0\\ n(w_1,x_1)=w_1x_1 \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧f(u)=u1u(v)=1+vv(x)=exx(y)=0−yy(z)=z+w2z(m,n)=m+nm(w0,x0)=w0x0n(w1,x1)=w1x1
因为简化的变元
m
m
m,
n
n
n只有一个基础运算算子,所以,链式分解到此结束。
如果对
f
(
w
,
x
)
f(w,x)
f(w,x)求导,则根据链式法则,对每一步简化的运算进行求导,然后进行乘积运算;假设求
f
f
f关于
w
0
w_0
w0的偏导,即:
∂
f
∂
w
0
\frac{ \partial f}{ \partial w_0}
∂w0∂f
根据链式法则:
∂
f
(
w
0
,
w
1
,
x
)
∂
w
0
=
∂
f
(
u
)
∂
u
⋅
∂
u
(
v
)
∂
v
⋅
∂
v
(
x
)
∂
x
⋅
∂
x
(
y
)
∂
y
⋅
∂
y
(
z
)
∂
z
⋅
[
∂
z
∂
m
⋅
∂
m
(
w
0
,
x
0
)
∂
w
0
+
∂
z
∂
n
⋅
∂
n
(
w
1
,
x
1
)
∂
w
0
]
\frac{ \partial f(w_0,w_1,x)}{ \partial w_0}=\frac{ \partial f(u)}{ \partial u} · \frac{ \partial u(v)}{ \partial v} · \frac{ \partial v(x)}{ \partial x} · \frac{ \partial x(y)}{ \partial y} · \frac{ \partial y(z)}{ \partial z} · [\frac{ \partial z}{ \partial m} · \frac{ \partial m(w_0,x_0)}{ \partial w_0} + \frac{ \partial z}{ \partial n} · \frac{ \partial n(w_1,x_1)}{ \partial w_0}]
∂w0∂f(w0,w1,x)=∂u∂f(u)⋅∂v∂u(v)⋅∂x∂v(x)⋅∂y∂x(y)⋅∂z∂y(z)⋅[∂m∂z⋅∂w0∂m(w0,x0)+∂n∂z⋅∂w0∂n(w1,x1)]
由于
∂
n
(
w
1
,
x
1
)
∂
w
0
=
0
\frac{ \partial n(w_1,x_1)}{ \partial w_0}=0
∂w0∂n(w1,x1)=0,因此上述式子可以简化为:
∂
f
(
w
0
,
w
1
,
x
)
∂
w
0
=
∂
f
(
u
)
∂
u
⋅
∂
u
(
v
)
∂
v
⋅
∂
v
(
x
)
∂
x
⋅
∂
x
(
y
)
∂
y
⋅
∂
y
(
z
)
∂
z
⋅
∂
z
∂
m
⋅
∂
m
(
w
0
,
x
0
)
∂
w
0
(4)
\frac{ \partial f(w_0,w_1,x)}{ \partial w_0}=\frac{ \partial f(u)}{ \partial u} · \frac{ \partial u(v)}{ \partial v} · \frac{ \partial v(x)}{ \partial x} · \frac{ \partial x(y)}{ \partial y} · \frac{ \partial y(z)}{ \partial z} · \frac{ \partial z}{ \partial m} · \frac{ \partial m(w_0,x_0)}{ \partial w_0} \tag{4}
∂w0∂f(w0,w1,x)=∂u∂f(u)⋅∂v∂u(v)⋅∂x∂v(x)⋅∂y∂x(y)⋅∂z∂y(z)⋅∂m∂z⋅∂w0∂m(w0,x0)(4)
通过上面的案例,可以看出一个复杂运算的求导可以按照链式法则,转换为一系列基础算子构成的运算的求导,且各个子运算相互独立,可以并行的进行计算,提升了梯度计算的效率;
下面将用数据验证上述链式法则:
{
∂
f
(
u
)
∂
u
=
1
−
u
2
∂
u
(
v
)
∂
v
=
1
∂
v
(
x
)
∂
x
=
e
x
∂
x
(
y
)
∂
y
=
−
1
∂
y
(
z
)
∂
z
=
1
∂
z
∂
m
=
1
∂
m
(
w
0
,
x
0
)
∂
w
0
=
x
0
\begin{cases} \frac{ \partial f(u)}{ \partial u} =\frac{1}{-u^2} \\ \frac{ \partial u(v)}{ \partial v}=1\\ \frac{ \partial v(x)}{ \partial x} =e^x\\ \frac{ \partial x(y)}{ \partial y} =-1\\ \frac{ \partial y(z)}{ \partial z}=1 \\ \frac{ \partial z}{ \partial m}=1 \\ \frac{ \partial m(w_0,x_0)}{ \partial w_0}=x_0 \end{cases}
⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧∂u∂f(u)=−u21∂v∂u(v)=1∂x∂v(x)=ex∂y∂x(y)=−1∂z∂y(z)=1∂m∂z=1∂w0∂m(w0,x0)=x0
∂
f
(
w
0
,
w
1
,
x
)
∂
w
0
=
1
−
u
2
⋅
1
⋅
e
x
⋅
(
−
1
)
⋅
1
⋅
1
⋅
x
0
=
x
0
e
x
u
2
(5)
\frac{ \partial f(w_0,w_1,x)}{ \partial w_0}=\frac{1}{-u^2}·1·e^x·(-1)·1·1·x_0\\ =\frac{x_0e^x}{u^2} \tag{5}
∂w0∂f(w0,w1,x)=−u21⋅1⋅ex⋅(−1)⋅1⋅1⋅x0=u2x0ex(5)
因为$u=1+e^{-(w_0x_0+w_1x_1+w_2)} $,带入公式(5)中,得到:
∂
f
(
w
0
,
w
1
,
x
)
∂
w
0
=
1
−
u
2
⋅
1
⋅
e
x
⋅
(
−
1
)
⋅
1
⋅
1
⋅
x
0
=
x
0
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
(
1
+
e
−
(
w
0
x
0
+
w
1
x
1
+
w
2
)
)
2
(5)
\frac{ \partial f(w_0,w_1,x)}{ \partial w_0}=\frac{1}{-u^2}·1·e^x·(-1)·1·1·x_0\\ =\frac{x_0e^{-(w_0x_0+w_1x_1+w_2)}}{(1+e^{-(w_0x_0+w_1x_1+w_2)})^2} \tag{5}
∂w0∂f(w0,w1,x)=−u21⋅1⋅ex⋅(−1)⋅1⋅1⋅x0=(1+e−(w0x0+w1x1+w2))2x0e−(w0x0+w1x1+w2)(5)
下面来验证,假设
x
0
=
1
,
x
1
=
3
,
w
0
=
4
,
w
1
=
1
,
w
2
=
3
x_0=1,x_1=3,w_0=4,w_1=1,w_2=3
x0=1,x1=3,w0=4,w1=1,w2=3,求
∂
f
(
w
0
,
w
1
,
x
)
∂
w
0
\frac{ \partial f(w_0,w_1,x)}{ \partial w_0}
∂w0∂f(w0,w1,x)的值。下面用百度的paddle.autograd.backward函数来对比手动推导的导函数值和调用函数计算的导函数值。
import numpy as np
import paddle
from paddle import to_tensor as TT
x_0 = 1
x_1 = 3
w_0 = 4
w_1 = 1
w_2 = 3
x_0 = TT(x_0, dtype='float32', stop_gradient=True)
x_1 = TT(x_1, dtype='float32')
w_0 = TT(w_0, dtype='float32', stop_gradient=False)
w_1 = TT(w_1, dtype='float32')
w_2 = TT(w_2, dtype='float32')
# 导函数
f_ = ( x_0*paddle.exp(-(w_0*x_0+w_1*x_1+w_2)) )/(1+paddle.exp(-(w_0*x_0+w_1*x_1+w_2)))**2
print(f"手动计算的导函数值:{f_.numpy()[0]}")
# 原函数
f = 1/(1+paddle.exp(-(w_0*x_0+w_1*x_1+w_2)))
paddle.autograd.backward(f, TT(1, dtype='float32'))
print(f"程序计算的导函数值:{w_0.grad.numpy()[0]}")
输出为:
手动计算的导函数值:4.539580913842656e-05
程序计算的导函数值:4.539580550044775e-05
从对比结果来看,除了一些截断误差外,二者一致。
案例二,简化为简单函数的情况,以tanh函数为例:
f
(
x
,
y
,
z
)
=
t
a
n
h
(
x
y
+
z
2
)
f(x,y,z)=tanh(xy+z^2)
f(x,y,z)=tanh(xy+z2)
第一步,简化为仅有一个自变量的简单函数,引入
u
u
u:
{
f
(
u
)
=
t
a
n
h
(
u
)
u
(
x
,
y
,
z
)
=
x
y
+
z
2
\begin{cases} f(u)=tanh(u) \\ u(x,y,z)=xy+z^2 \\ \end{cases}
{f(u)=tanh(u)u(x,y,z)=xy+z2
第二步,简化u,引入
w
w
w,
v
v
v,把u简化为两个自变量的和:
{
f
(
u
)
=
t
a
n
h
(
u
)
u
(
w
,
v
)
=
v
+
w
w
(
x
,
y
)
=
x
y
v
(
z
)
=
z
2
\begin{cases} f(u)=tanh(u) \\ u(w,v)=v+w \\ w(x,y)=xy\\ v(z)=z^2 \end{cases}
⎩⎪⎪⎪⎨⎪⎪⎪⎧f(u)=tanh(u)u(w,v)=v+ww(x,y)=xyv(z)=z2
简化后,
w
w
w已经是一个基本算子的一次运算,因此,
w
w
w无法再简化;
v
v
v是一个一元简单函数了,也无法再简化,因此,简化结束;下面以求
∂
f
(
x
,
y
,
z
)
∂
x
\frac{ \partial f(x,y,z)}{ \partial x}
∂x∂f(x,y,z)为例,来验证正确性:
{
∂
f
(
x
,
y
,
z
)
∂
x
=
∂
f
(
u
)
∂
u
⋅
∂
u
(
w
,
v
)
∂
w
⋅
∂
w
(
x
,
y
)
∂
x
=
{
1
−
2
[
t
a
n
h
(
u
)
]
2
}
y
=
{
1
−
2
[
t
a
n
h
(
x
y
+
z
2
)
]
2
}
y
\begin{cases} \frac{ \partial f(x,y,z)}{ \partial x}= \frac{ \partial f(u)}{ \partial u}· \frac{ \partial u(w,v)}{ \partial w}· \frac{ \partial w(x,y)}{ \partial x}\\ = \{1 - 2[tanh(u)]^2\}y\\ =\{1 - 2[tanh(xy+z^2)]^2\}y \end{cases}
⎩⎪⎨⎪⎧∂x∂f(x,y,z)=∂u∂f(u)⋅∂w∂u(w,v)⋅∂x∂w(x,y)={1−2[tanh(u)]2}y={1−2[tanh(xy+z2)]2}y
假设
x
=
0.1
,
y
=
0.2
,
z
=
0.3
x=0.1,y=0.2,z=0.3
x=0.1,y=0.2,z=0.3,求导函数的值:
import sys,os,math,time
from numpy import *
import numpy as np
import paddle
from paddle import to_tensor as TT
x=0.1
y=0.2
z=0.3
x = TT(x, dtype='float32', stop_gradient=False)
y = TT(y, dtype='float32')
z = TT(z, dtype='float32', stop_gradient=True)
# 导函数
u = x*y + z**2
f_ = y * ( 1-paddle.tanh(u)**2)
print(f"手动计算的导函数值:{f_.numpy()[0]}")
# 原函数
f = paddle.tanh(x*y+z**2)
paddle.autograd.backward(f, TT(1, dtype='float32'))
print(f"程序计算的导函数值:{x.grad.numpy()[0]}")
运算结果为:
手动计算的导函数值:0.197599396109581
程序计算的导函数值:0.197599396109581