写在前面:矩阵微积分是深度学习的数学基础之一,但是这部分内容在大学计算机系(及相关非数学类专业)本科几乎没有介绍,想要了解全凭自学。我之前看过的比较好的资料有三个:维基百科的Matrix Calculus词条、The Matrix Cookbook和
Towser的《机器学习中的矩阵/向量求导》。然而前两个都是英文资料,而且主要重视结论,可以当做字典用,但是看完总有点“知其然不知其所以然”的感觉(维基词条似乎有简单的计算过程介绍,但是还是有点简略);Towser大神的文章写得很不错,但是我数学较差,看到张量相关的部分还是觉得有点脑子转不过来
最近这几天我整理自己的微博收藏时,无意间发现北邮陈光老师(爱可可爱生活@微博)曾经推荐过一篇文章:Terence Parr和Jeremy Howard合作的The Matrix Calculus You Need For Deep Learning,感觉比较符合我的需求(再次证明收藏过的东西如果不回顾就是白收藏),相对Towser的文章更基础一点。这篇博客就是我阅读该论文的一些摘要
预备知识
对于一元函数的导数,存在如下几条规则(以下均认为是自变量):
- 常数函数的导数为0:
- 常量相乘法则:
- 幂函数求导法则:
- 加法法则:
- 减法法则:
- 乘法法则:
- 链式法则:,若令
对于二元函数,需要引入偏导数的概念。假设函数为,求函数对或的偏导数时,将另一个变量看作是常量(对于多元函数,求对某个变量的偏导数时,将其它所有变量都视为常量)。求得的偏导数可以组合成为梯度,记为
。即 ∇f(x,y)=[∂f(x,y)∂x∂f(x,y)∂y]
(
注意:原文里梯度表示成了一个行向量,并说明他们使用了numerator layout。但是这样会使得标量对向量的微分结果与该向量形状相异,也与主流记法相左。因此本文记录时做了转换,使用主流的denominator layout记法)
矩阵微积分
同一个函数对不同变量求偏导的结果可以组成称为梯度,多个函数对各变量求偏导的结果则可以组合成一个矩阵,称为雅可比矩阵(Jacobian matrix)。例如如果有函数,两者各自的梯度可以组合为 J=[∇Tf(x,y)∇Tg(x,y)]
雅可比矩阵的泛化
可以将多元变量组合成一个向量:(本文认为所有向量都是维的,即 x=[x1x2⋮xn]
)假设有
个函数分别向量
计算得出一个标量,即 y1=f1(x)y2=f2(x)⋮ym=fm(x)
可以简记为 y=f(x)
对
求导的结果就是将每个函数对
的导数堆叠起来得到的雅可比矩阵: ∂y∂x=[∂∂x1f1(x)∂∂x2f1(x)⋯∂∂xnf1(x)∂∂x1f2(x)∂∂x2f2(x)⋯∂∂xnf2(x)⋮⋮⋱⋮∂∂x1fm(x)∂∂x2fm(x)⋯∂∂xnfm(x)]
两向量间逐元素运算的导数
假设是一个对两向量逐元素进行计算的操作符(例如是向量加法,就是两个向量逐元素相加),则对于,假设,可以做如下展开
[y1y2⋮yn]=[f1(w)◯g1(x)f2(w)◯g2(x)⋮fn(w)◯gn(x)]
分别对
和
求导可以得到两个方阵 Jw=∂y∂w=[∂∂w1(f1(w)◯g1(x))∂∂w2(f1(w)◯g1(x))⋯∂∂wn(f1(w)◯g1(x))∂∂w1(f2(w)◯g2(x))∂∂w2(f2(w)◯g2(x))⋯∂∂wn(f2(w)◯g2(x))⋮⋮⋱⋮∂∂w1(fn(w)◯gn(x))∂∂w2(fn(w)◯gn(x))⋯∂∂wn(fn(w)◯gn(x))]
Jx=∂y∂x=[∂∂x1(f1(w)◯g1(x))∂∂x2(f1(w)◯g1(x))⋯∂∂xn(f1(w)◯g1(x))∂∂x1(f2(w)◯g2(x))∂∂x2(f2(w)◯g2(x))⋯∂∂xn(f2(w)◯g2(x))⋮⋮⋱⋮∂∂x1(fn(w)◯gn(x))∂∂x2(fn(w)◯gn(x))⋯∂∂xn(fn(w)◯gn(x))]
由于是逐元素操作,因此是一个关于的函数,
也是一个关于的函数。因此当时,
,而且
,因此上述两个雅可比矩阵都是对角矩阵,可以简记为 ∂y∂w=diag(∂∂w1(f1(w1)◯g1(x1)),∂∂w2(f2(w2)◯g2(x2)),…,∂∂wn(fn(wn)◯gn(xn)))
∂y∂x=diag(∂∂x1(f1(x1)◯g1(x1)),∂∂x2(f2(x2)◯g2(x2)),…,∂∂xn(fn(xn)◯gn(xn)))
如果只考虑最简单的向量计算,不妨令,因此有
。假设为逐元素相加,那么会有 ∂∂wi(fi(wi)+gi(xi))=1=∂∂x(fi(wi)+gi(xi))
其它减、乘、除同理,可以得到如下式子(由于向量加法
的法则就是
,因此
简写为
。减法同理) ∂(w+x)∂w=I=∂(w+x)∂x∂(w−x)∂w=I∂(w−x)∂x=−I∂(w⊗x)∂w=diag(x)∂(w⊗x)∂x=diag(w)∂(w⊘x)∂w=diag(⋯1xi⋯)∂(w⊘x)∂x=diag(⋯−wix2i⋯)
向量与标量运算的导数
向量与标量之间的运算,可以把标量扩展成一个同维度的向量,然后对两个向量做逐元素的运算。标量的扩展一般是将其乘以一个全为1的向量,例如计算实际上是
,其中
因此可以得到 ∂∂x(x+z)=I∂∂x(xz)=Iz∂∂z(x+z)=1∂∂z(xz)=x
向量的求和规约操作
求和规约,原文是sum reduce,实际上就是把一个向量的所有元素求和
令,展开可得
∂y∂x=[∂y∂x1∂y∂x2⋯∂y∂xn]T=[∂∂x1∑ifi(x)∂∂x2∑ifi(x)⋯∂∂xn∑ifi(x)]T=[∑i∂fi(x)∂x1∑i∂fi(x)∂x2⋯∑i∂fi(x)∂xn]T
讨论一个最简单的情况,就是
。由之前的讨论,有
。将定义代入上式,并考虑对
有
,易得 y=sum(x)→∇y=1
链式法则
对复杂表达式(各种表达式组合嵌套得到的表达式),需要使用链式法则求导。本文将链式法则分为了三种
单变量链式法则
其实就是高数里学到的。假设,令,则单变量链式法则为 dydx=dydududx
单变量全微分链式法则
在最简单的单变量链式法则里,所有中间变量都是单变量函数。对于这种函数,情况会复杂一些。如果引入中间变量和 ,有 u1(x)=x2u2(x,u1)=x+u1(y=f(x)=u2(x,u1))
如果只使用前面的单变量链式法则,那么
,所以
,跟结果(
)对不上。如果因为
有两个变量而引入偏导数,那么 ∂u1(x)∂x=2x∂u2(x,u1)∂u1=1 注意此时不能直接说
!因为对
求偏导数的时候限定了其它变量不会随着
的变化而变化,但是
实际上还是关于
的函数。因此应该使用单变量全微分链式法则:假设
(可能)都与
有关,则 ∂f(x,u1,…,un)∂x=∂f∂x+n∑i=1∂f∂ui∂ui∂x 将上面的例子代入,可知 ∂f(x,u1)∂x=∂f∂x+∂f∂u1∂u1∂x=1+2x 注意全微分公式在任何时候都是算偏导数的加和,并不是因为例子里存在求和操作,而是因为它表示的是各个
对
的变化量的加权求和。考虑式子
,那么 u1(x)=x2u2(x,u1)=xu1∂u1∂x=2x∂u2∂x=u1∂u2∂u1=x 使用全微分公式,可知 dydx=∂u2∂x+∂u2∂u1∂u1∂x=u1+x⋅2x=x2+2x2=3x2
对前面介绍的全微分链式法则,如果引入一个新的变量
,则可以得到一个更简洁的公式 ∂f(u1,…,un+1)∂x=n+1∑i=1∂f∂ui∂ui∂x 这看上去很像两个向量的内积: ∂f∂u∂u∂x
向量的链式法则
为了引出向量的链式法则,可以先看一个例子。假设,其中 [y1(x)y2(x)]=[f1(x)f2(x)]=[ln(x2)sin(3x)]
引入两个中间变量
和
,使得
,其中 [g1(x)g2(x)]=[x23x][f1(g)f2(g)]=[ln(g1)sin(g2)] 那么
就可以使用全微分链式法则:
∂y∂x=[∂f1(g)∂x∂f2(g)∂x]=[∂f1∂g1∂g1∂x+∂f1∂g2∂g2∂x∂f2∂g1∂g1∂x+∂f2∂g2∂g2∂x] 这个向量可以写成矩阵与向量相乘的形式 [∂f1∂g1∂g1∂x+∂f1∂g2∂g2∂x∂f2∂g1∂g1∂x+∂f2∂g2∂g2∂x]=[∂f1∂g1∂f1∂g2∂f2∂g1∂f2∂g2][∂g1∂x∂g2∂x] 可以看到这个矩阵是雅可比矩阵,向量同理,即 [∂f1∂g1∂f1∂g2∂f2∂g1∂f2∂g2][∂g1∂x∂g2∂x]=∂f∂g∂g∂x
当
不是标量而是向量
时,同样的法则也成立,这意味着我们可以得到一个向量的链式法则 y=f(g(x))→∂y∂x=∂f∂g∂g∂x 事实上,这个公式可以更加简洁。对很多应用,雅可比矩阵都是对角方阵,此时
是一个只与
有关的函数,而
是一个只与
有关的函数。即 ∂f∂g=diag(∂fi∂gi)∂g∂x=diag(∂gi∂xi) 因此在这种情况下,向量的链式法则可以简化为 ∂∂xf(g(x))=diag(∂fi∂gi)diag(∂gi∂xi)=diag(∂fi∂gi∂gi∂xi)
激活函数的梯度
假设网络是全连接前馈神经网络(即这里不考虑卷积、RNN等),激活函数为ReLU,那么有 activation(x)=max(0,w⋅x+b)
要计算的是
和
。尽管之前没讨论过向量内积对向量的偏导数,但是考虑 w⋅x=n∑i(wixi)=sum(w⊗x) 而之前讨论过
和
的偏导数和向量的链式法则,那么引入中间变量 u=w⊗xy=sum(u) 根据上面的推导,有 ∂u∂w=diag(x)∂y∂u=1 因此 ∂y∂w=∂y∂u∂u∂w=1⋅diag(x)=x
再令
,不难得出 ∂y∂w=x, ∂y∂b=1 激活函数是一个分段函数,且在0点不连续。分段求导可得
∂∂zmax(0,z)={0z≤0dzdz=1z>0 因此 ∂∂xmax(0,x)=[∂∂x1max(0,x1)∂∂x2max(0,x2)⋮∂∂xnmax(0,xn)] 综合起来,对激活函数,引入中间变量
来表示仿射变换,有 z(w,b,x)=w⋅x+bactivation(z)=max(0,z) 通过链式法则 ∂activation∂w=∂activation∂z∂z∂w
代入前面的推导,有 ∂activation∂w={0∂z∂w=0z≤01∂z∂w=xz>0∂activation∂b={0z≤01z>0
神经网络损失函数的梯度
最后考虑一个完整的例子。假设模型的输入是,且 X=[x1,x2,…,xN]T
样本个数为
,令结果向量为
y=[target(x1),target(x2),…,target(xN)]T 其中每个
都是一个标量。假设损失函数使用平方误差函数,那么损失函数
为 C(w,b,X,y)=1NN∑i=1(yi−activation(xi))2=1NN∑i=1(yi−max(0,w⋅xi+b))2
引入中间变量,有 u(w,b,x)=max(0,w⋅x+b)v(y,u)=y−uC(v)=1NN∑i=1v2 这里主要说明如何计算权重的梯度。偏置
的计算方法同理。由前面的推导可知 ∂∂wu(w,b,x)={0w⋅x+b≤0xw⋅x+b>0 而 ∂v(y,u)∂w=∂∂w(y−u)=0−∂u∂w=−∂u∂w={0w⋅x+b≤0−xw⋅x+b>0
因此,整个梯度的计算过程为 ∂C(v)∂w=∂∂w1NN∑i=1v2=1NN∑i=1∂v2∂v∂v∂w=1NN∑i=12v∂v∂w 中间展开过程略,最后可得 ∂C(v)∂w={0w⋅xi+b≤02N∑Ni=1(w⋅xi+b−yi)xiw⋅xi+b>0 后面关于梯度下降的讨论略