欢迎关注WX公众号:【程序员管小亮】
专栏——深度学习入门笔记
声明
1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献。
2)本文仅供学术交流,非商用。所以每一部分具体的参考资料并没有详细对应。如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除。
3)博主才疏学浅,文中如有不当之处,请各位指出,共同进步,谢谢。
4)此属于第一版本,若有错误,还需继续修正与增删。还望大家多多指点。大家都共享一点点,一起为祖国科研的推进添砖加瓦。
深度学习入门笔记(三):求导和计算图
0、写在前面
这一次主要是想对微积分和导数直观理解一下。很多人在想或许自从大学毕以后,再也没有接触微积分。不要担心,为了高效应用神经网络和深度学习,其实 并不需要非常深入理解微积分。
如果你是精通微积分的那一小部分人群,对微积分非常熟悉,可以跳过这个笔记。
1、导数
导数,也叫导函数值,又名微商,是微积分中的重要基础概念,但是其实理解起来并没有那么难。来看一个例子:
一个函数
f
(
a
)
=
3
a
f(a)=3a
f(a)=3a,如图可以看出它是一条直线,这个别说你不会,xD。那么什么是导数,简单理解一下:
看看函数中几个点,假定
a
=
2
a=2
a=2,那么
f
(
a
)
=
3
a
f(a)=3a
f(a)=3a 是
a
a
a 的 3 倍,也就是 3 * 2 = 6
,即若
a
=
2
a=2
a=2,那么函数
f
(
a
)
=
6
f(a)=6
f(a)=6,第一个点就是
(
a
,
f
(
a
)
)
=
(
2
,
6
)
(a,f(a))=(2,6)
(a,f(a))=(2,6)。
如果假定稍微改变一点点
a
a
a 的值,只增加一点,变为 2.001(只增加了 0.001),这时
a
a
a 将向右做微小的移动。0.001 的差别实在是太小了,
1
0
−
3
10^{-3}
10−3 数量级的移动,不能在图中很明显地看出来,这里稍稍夸张了一下,意思到位就ok。现在
f
(
a
)
=
3
a
f(a)=3a
f(a)=3a 等于
a
a
a 的 3 倍是 2.001 * 3 = 6.003
。
请看这个绿色小清新的三角形!!!根据刚才的结果,如果向右移动 0.001,那么
f
(
a
)
f(a)
f(a) 增加 6.003 - 6 = 0.003
,
f
(
a
)
f(a)
f(a) 的值增加 3 倍于右移的
a
a
a,0.003 / 0.001 = 3
,因此我们说函数
f
(
a
)
f(a)
f(a) 在
a
=
2
a=2
a=2点的导数就是在这个点的斜率,而这个点的斜率是 3,那么斜率是什么?
已知一个图如上,斜率 K 计算公式如下:
导数这个概念意味着斜率!!!导数 这个词,听起来就是一个很可怕、很令人惊恐的词,但是 斜率 以一种很友好的方式来描述导数这个概念。所以提到导数,不严格的说,就把它当作函数的斜率就好了。通过一个例子来体会一下斜率的定义,在上图的绿色三角形中,用三角形的高除以三角形的宽,即斜率等于 0.003 / 0.001 = 3
,等于3,或者说导数等于 3。这意味着什么呢?这意味着当你将
a
a
a 右移 0.001时,
f
(
a
)
f(a)
f(a) 的值增加 3 倍水平方向的量。
如果换个数呢?现在假设
a
=
5
a=5
a=5 也是一样的,此时
f
(
a
)
=
3
a
=
15
f(a)=3a=15
f(a)=3a=15。把
a
a
a 右移一个很小的幅度,增加到 5.001,根据
f
(
a
)
=
3
a
f(a) = 3a
f(a)=3a 可以得到 3 * 5.001 = 15.003
。即在
a
=
5
a=5
a=5 时,斜率是 3。这就表示,当变量
a
a
a 的值发生微小改变时,
d
f
(
a
)
d
a
=
3
\frac{df(a)}{da}=3
dadf(a)=3。一个等价的导数表达式还可以这样写
d
d
a
f
(
a
)
\frac{d}{da}f(a)
dadf(a) ,即
f
(
a
)
f(a)
f(a) 放在上面或者放在右边都没有关系,是一样的。
那就是导数的正式定义!!!数学上导数用 f ′ ( a ) = d f ( a ) d a f'(a) = \frac{df(a)}{da} f′(a)=dadf(a) 表示。
导数的一个特性是:这个例子中的这个函数在任何地方的斜率总是等于 3,不管 a = 2 a=2 a=2 或 a = 5 a=5 a=5,这个函数的斜率总等于3,也就是说导数总等于 3。那么所有的函数斜率都是不变的嘛?当然不是,下面这个例子中函数在不同点的斜率是可变的。
2、深入理解导数
下面来看一个更加复杂的例子,有多复杂?在这个例子中,函数在不同点处的斜率是不一样的,别慌,先来举个例子:
这里有一个不一样的函数,
f
(
a
)
=
a
2
f(a)={{\text{a}}^{\text{2}}}
f(a)=a2,直观上看,是个曲线,眉头一皱,感觉事情不太对劲。现在如果假设
a
=
2
a=\text{2}
a=2 的话,那么
f
(
a
)
=
a
2
f(a)={{\text{a}}^{\text{2}}}
f(a)=a2 可以得到
f
(
2
)
=
2
2
=
4
f(2) = 2^2 = 4
f(2)=22=4。还是稍稍往右推进一点点,现在
a
=
2
.
001
a=\text{2}.\text{001}
a=2.001,则
f
(
a
)
≈
4.004
f(a)\approx 4.004
f(a)≈4.004 (为什么要约等于?如果你用计算器算的话,就会发现这个准确的值应该为4.004001,只是为了简便起见,省略了后面的部分)。
还是画图的方法进行理解,得到一个小三角形,如果细心的话你就会发现,这次严格意义上并不是三角形。如果把
a
a
a 往右移动 0.001,那么
f
(
a
)
f(a)
f(a) 将增大四倍,即增大 4.004 - 4 = 0.004
,而 0.004 / 0.001 = 4
。
在微积分中,把这个三角形斜边的斜率,称为 f ( a ) f(a) f(a) 在点 a = 2 a=\text{2} a=2 处的导数(即为 4 );或者写成微积分的正式定义形式,当 a = 2 a=\text{2} a=2 的时候, d d a f ( a ) = 4 \frac{d}{da}f(a)=4 dadf(a)=4。由此可知,函数 f ( a ) = a 2 f(a)={{a}^{{2}}} f(a)=a2,在 a a a 取不同值的时候,它的斜率是不同的,这和上面的例子显然是不同的。
如果你还是不太理解的话,这里有种直观的方法可以解释,就是画图法。为什么一个点的斜率,在不同位置会不同如果?我们可以在曲线上的不同位置,画一些小小的三角形你就会发现,三角形高和宽的比值,即斜率,在曲线上不同的地方是不同的。所以当 a = 2 a=2 a=2 时,斜率为 4;而当 a = 5 a=5 a=5 时,斜率为 10。
如果严谨地说,可以百度导数表。你会发现,函数
f
(
a
)
=
a
2
f(a)={{a}^{{2}}}
f(a)=a2 的斜率(即导数)为
2
a
2a
2a,而函数
f
(
a
)
=
3
a
f(a)=3a
f(a)=3a 的斜率(即导数)为3。
这意味着什么?这么说,如果任意给定一点
a
a
a,稍微将
a
a
a 增大 0.001,两个函数增大的完全不一样。一个是和
a
a
a 有关的,而另一个则是常数。
来小结一下:
-
导数就是斜率,而函数的斜率在不同的点可能是不同的。在 f ( a ) = 3 a f(a)=\text{3}a f(a)=3a 时,在任何点它的斜率都是相同的,均为3。但对 f ( a ) = a 2 f(a)={{\text{a}}^{\text{2}}} f(a)=a2,斜率是变化的,所以它们的导数或者斜率,在曲线上不同的点处是不同的。
-
如果想知道一个函数的导数,可参考导数表,然后应该就能找到这些函数的导数公式,直接带数就完事了。
3、计算图
一个神经网络的计算大体上可以看成是,前向或反向传播组合而成的。只有公式描述,确实有一些晦涩,这个时候我们想到了计算图。计算图是什么?
计算图是一种描述方程的语言,既然是图,则有 节点(变量) 和 边(操作)。
这么说太官方了,来举一个比逻辑回归更加简单的,或者说不那么正式的神经网络的例子。
我们的目的是计算函数 J J J,函数 J J J 的组成是什么呢?是由三个变量 a , b , c a,b,c a,b,c 组成的函数,这个函数是 3(a + bc) \text{3(a}+\text{bc)} 3(a+bc) 。计算这个函数实际上有三个不同的步骤,也就是拆分一下,用复合函数的思想去理解。
首先是计算
b
b
b 乘以
c
c
c,用一个函数
u
=
b
c
{u}={bc}
u=bc 来表示;然后计算另一个函数
v
=
a
+
u
v=a+u
v=a+u;最后输出
J
=
3
v
J=3v
J=3v,这就是要计算的函数
J
J
J。这三步可以画成如下的计算图:
先画三个变量
a
,
b
,
c
a,b,c
a,b,c,第一步就是计算
u
=
b
c
u=bc
u=bc,放个矩形框,它的输入是
b
,
c
b,c
b,c;接着还是放个矩形框,进行第二步
v
=
a
+
u
v=a+u
v=a+u;最后一步还是个矩形框,进行
J
=
3
v
J=3v
J=3v。
举个例子:
a
=
5
,
b
=
3
,
c
=
2
a=5, b=3, c=2
a=5,b=3,c=2 ,
u
=
b
c
u=bc
u=bc 就是 3 * 2 = 6
;而
v
=
a
+
u
v=a+u
v=a+u,就是 5+6=11
;
J
J
J 是 3 倍的
v
v
v ,因此,
J
J
J = 3 × (5 + 3 × 2)
。如果把它算出来,就得到33,实际上就是
J
J
J 的值。
计算图的一个大优势是:当有不同的或者一些特殊的输出变量时,例如上面例子中的 J J J 和逻辑回归中准备优化的代价函数 J J J,用计算图来处理会很方便。从这个小例子中可以看出,通过一个从左向右(蓝色箭头)的过程,可以计算出 J J J 的值。而为了计算导数,从右到左(红色箭头,和蓝色箭头的过程相反)的过程是用于计算导数最自然、最直观的方式。
4、使用计算图求导数
如何利用计算图来计算函数 J J J 的导数呢?
先不急,来看个例子,下面用到的公式:
d J d u = d J d v d v d u \frac{dJ}{du}=\frac{dJ}{dv}\frac{dv}{du} dudJ=dvdJdudv
d J d b = d J d u d u d b \frac{dJ}{db}=\frac{dJ}{du}\frac{du}{db} dbdJ=dudJdbdu
d J d a = d J d u d u d a \frac{dJ}{da}=\frac{dJ}{du}\frac{du}{da} dadJ=dudJdadu
这是一个计算图,记录了整个流程:
假设计算 d J d v \frac{{dJ}}{{dv}} dvdJ,那要怎么算呢?如果你会微积分的话,就好说了,直接求导数没啥好说的;那么不会的话呢,也不用着急!这么看,比如要把这个 v v v 值拿过来,改变一下,那么 J J J 的值会怎么变呢?(是不是用上了上面提到的导数讲解 😃)
首先 J = 3 v J = 3v J=3v, v = 11 v=11 v=11, J = 33 J=33 J=33,这是已知条件。如果让 v v v 增加一点点,比如到11.001,那么 J = 3 v = 33.003 J = 3v = 33.003 J=3v=33.003,这里 v v v 增加了 0.001,而最终结果是 J J J 上升了 0.003,也就是原来的 3 倍,所以 d J d v = 3 \frac{{dJ}}{{dv}}=3 dvdJ=3。
为啥这么说?当然是因为对于任何 v v v 的增量, J J J 都会有 3 倍增量。所以有 J = 3 v J=3v J=3v,推出 d J d v = 3 \frac{{dJ}}{{dv}} =3 dvdJ=3。
吴恩达老师的手稿如下:
看另一个例子, d J d a \frac{{dJ}}{da} dadJ 是多少呢?换句话说,如果提高 a a a 的值, J J J 的数值有什么影响?
变量
a
=
5
a=5
a=5,增加到了 5.001,那么对
v
v
v 的影响就是
a
+
u
a+u
a+u,之前
v
=
11
v=11
v=11,现在变成 5.001 - 5 + 11 = 11.001
,
J
J
J 就变成11.001 * 3 = 33.003
,所以
a
a
a 增加 0.001,
J
J
J 增加 0.003。那么增加
a
a
a,
a
a
a 的改变量会传播到计算图的最右边,所以
J
J
J 最后是 33.003。所以
J
J
J 的增量是 3 乘以
a
a
a 的增量,也就意味着导数是 3,即
d
J
d
a
=
3
\frac{{dJ}}{da}=3
dadJ=3。
吴恩达老师的手稿如下:
要解释清楚这个计算过程,就会牵扯出链式法则,名字虽然挺厉害的,其实很简单。
首先 a a a 增加了, v v v 也会增加; v v v 增加多少呢?这取决于 d v d a \frac{{dv}}{da} dadv;然后 v v v 的增加导致 J J J 也会增加。所以这在微积分里实际上叫链式法则,顾名思义,互相之间被链住了,一个变化都会变化。
那么怎么计算一个链式法则的求导呢?
其实不难,前面给了三个公式,这就是答案。通过分解的方法,把整个链式法则分解为几个小的链子,分别求导再相乘,就解出正确答案了。
通过改变一个变量来看另一个变量的变化关系这种方法,我们得到了 d J d v = 3 \frac{{dJ}}{{dv}} =3 dvdJ=3、 d v d a = 1 \frac{{dv}}{da} =1 dadv=1,所以 d J d a = d J d v d v d a = 3 ∗ 1 = 3 \frac{{dJ}}{da}=\frac{{dJ}}{{dv}} \frac{{dv}}{da}=3 * 1=3 dadJ=dvdJdadv=3∗1=3,即为所求。
下图表示了整个计算过程:
继续计算另一条线的导数,也就是这个 u u u,那么 d J d u \frac{dJ}{du} dudJ 是多少呢?
通过和之前类似的计算,这里简单说一下。从
u
=
6
u=6
u=6 出发,令
u
u
u 增加到 6.001,
v
v
v 之前是 11,现在变成 6.001 - 6 + 11 = 11.001
,
J
J
J 从 33 变成 33 * 3 = 33.003
,所以
d
J
d
u
=
3
\frac{{dJ}}{du}= 3
dudJ=3。
对 u u u 的分析很类似对 a 的分析,为啥这么说呢?实际上还是计算, d J d u = d J d v ⋅ d v d u \frac{{dJ}}{du}=\frac{{dJ}}{dv}\cdot \frac{{dv}}{du} dudJ=dvdJ⋅dudv,又因为有 d J d v = 3 \frac{{dJ}}{dv} =3 dvdJ=3、 d v d u = 1 \frac{{dv}}{du} = 1 dudv=1,最终算出的结果是 3 × 1 = 3 3×1=3 3×1=3,所以可以看出对 u u u 的分析类似对 a 的分析。
吴恩达老师的手稿如下:
现在,来看最后一个例子,那么 d J d b \frac{{dJ}}{db} dbdJ 呢?
事实上,使用微积分链式法则,这也可以写成乘积的形式,就是 d J d b = d J d u ⋅ d u d b \frac{{dJ}}{db}=\frac{{dJ}}{du}\cdot \frac{{du}}{db} dbdJ=dudJ⋅dbdu。
当
b
b
b 增加 0.001 变成 3.001 时,
u
=
b
c
u=bc
u=bc 就变成 3.001 * 2 = 6.002
,
u
u
u 增加了 6.002 - 6 = 0.002
,也就是
b
b
b 的增加量的二倍,所以
d
u
d
b
=
2
\frac{{du}}{db} =2
dbdu=2。那么
d
J
d
u
\frac{{dJ}}{du}
dudJ 是多少呢?在前面我们已经弄清楚了,等于 3,所以这两部分相乘,可得
d
J
d
b
=
6
\frac{{dJ}}{db}= 6
dbdJ=6。
其实还有另一个例子,不过 c c c 和 b b b 是比较相似的。经过计算你会发现 d J d c = d J d u ⋅ d u d c = 3 × 3 \frac{{dJ}}{dc} =\frac{{dJ}}{du}\cdot \frac{{du}}{dc} = 3 \times 3 dcdJ=dudJ⋅dcdu=3×3,这个结果是 9。
吴恩达老师的手稿如下:
所以当计算所有这些导数时,最有效率的办法是从右到左计算,跟着这个红色箭头走,充分利用计算图的优势。特别是当第一次计算对
v
v
v 的导数时,之后在计算对
a
a
a 导数时,然后对
u
u
u 的导数,最后是对
b
b
b 和
c
c
c 的导数。
到这里,计算图求导数就完事了。这是一个计算图,也是一个流程图。是不是简单了好多,尤其是对 b b b 和 c c c 的导数!!!
推荐阅读
- 深度学习入门笔记(一):深度学习引言
- 深度学习入门笔记(二):神经网络基础
- 深度学习入门笔记(三):求导和计算图
- 深度学习入门笔记(四):向量化
- 深度学习入门笔记(五):神经网络的编程基础
- 深度学习入门笔记(六):浅层神经网络
- 深度学习入门笔记(七):深层神经网络
- 深度学习入门笔记(八):深层网络的原理
- 深度学习入门笔记(九):深度学习数据处理
- 深度学习入门笔记(十):正则化
- 深度学习入门笔记(十一):权重初始化
- 深度学习入门笔记(十二):深度学习数据读取
- 深度学习入门笔记(十三):批归一化(Batch Normalization)
- 深度学习入门笔记(十四):Softmax
- 深度学习入门笔记(十五):深度学习框架(TensorFlow和Pytorch之争)
- 深度学习入门笔记(十六):计算机视觉之边缘检测
- 深度学习入门笔记(十七):深度学习的极限在哪?
- 深度学习入门笔记(十八):卷积神经网络(一)
- 深度学习入门笔记(十九):卷积神经网络(二)
- 深度学习入门笔记(二十):经典神经网络(LeNet-5、AlexNet和VGGNet)
参考文章
- 吴恩达——《神经网络和深度学习》视频课程