社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  机器学习算法

NMT Tutorial 3扩展a. 深度学习的矩阵微积分基础

ApacheCN_飞龙 • 7 年前 • 634 次点击  

写在前面:矩阵微积分是深度学习的数学基础之一,但是这部分内容在大学计算机系(及相关非数学类专业)本科几乎没有介绍,想要了解全凭自学。我之前看过的比较好的资料有三个:维基百科的Matrix Calculus词条The Matrix CookbookTowser的《机器学习中的矩阵/向量求导》。然而前两个都是英文资料,而且主要重视结论,可以当做字典用,但是看完总有点“知其然不知其所以然”的感觉(维基词条似乎有简单的计算过程介绍,但是还是有点简略);Towser大神的文章写得很不错,但是我数学较差,看到张量相关的部分还是觉得有点脑子转不过来

最近这几天我整理自己的微博收藏时,无意间发现北邮陈光老师(爱可可爱生活@微博)曾经推荐过一篇文章:Terence Parr和Jeremy Howard合作的The Matrix Calculus You Need For Deep Learning,感觉比较符合我的需求(再次证明收藏过的东西如果不回顾就是白收藏),相对Towser的文章更基础一点。这篇博客就是我阅读该论文的一些摘要

预备知识

对于一元函数的导数,存在如下几条规则(以下均认为x是自变量):

  • 常数函数的导数为0:f(x) = c \rightarrow df/dx = 0
  • 常量相乘法则:(cf(x))' = c\frac{df}{dx}
  • 幂函数求导法则:f(x) = x^n \rightarrow \frac{df}{dx} = nx^{n-1}
  • 加法法则:\frac{d}{dx}(f(x) + g(x)) = \frac{df}{dx} + \frac{dg}{dx}
  • 减法法则:\frac{d}{dx}(f(x) - g(x)) = \frac{df}{dx} - \frac{dg}{dx}
  • 乘法法则:\frac{d}{dx}(f(x)\cdot g(x)) = f(x)\cdot \frac{dg}{dx} + \frac{df}{dx}\cdot g(x)
  • 链式法则:\frac{d}{dx}(f(g(x))) = \frac{df(u)}{du}\cdot \frac{du}{dx},若令u=g(x)

对于二元函数,需要引入偏导数的概念。假设函数为f(x,y),求函数对xy的偏导数时,将另一个变量看作是常量(对于多元函数,求对某个变量的偏导数时,将其它所有变量都视为常量)。求得的偏导数可以组合成为梯度,记为 \nabla f(x,y)。即 ∇f(x,y)=[∂f(x,y)∂x∂f(x,y)∂y]

(注意:原文里梯度表示成了一个行向量,并说明他们使用了numerator layout。但是这样会使得标量对向量的微分结果与该向量形状相异,也与主流记法相左。因此本文记录时做了转换,使用主流的denominator layout记法)

矩阵微积分

同一个函数对不同变量求偏导的结果可以组成称为梯度,多个函数对各变量求偏导的结果则可以组合成一个矩阵,称为雅可比矩阵(Jacobian matrix)。例如如果有函数f, g,两者各自的梯度可以组合为 J=[∇Tf(x,y)∇Tg(x,y)]

雅可比矩阵的泛化

可以将多元变量组合成一个向量:f(x_1, x_2, \ldots, x_n) = f(\boldsymbol{x})(本文认为所有向量都是n \times 1维的,即 x=[x1x2⋮xn]

)假设有m个函数分别向量\boldsymbol{x}计算得出一个标量,即 y1=f1(x)y2=f2(x)⋮ym=fm(x) 可以简记为 y=f(x) \boldsymbol{y}\boldsymbol{x}求导的结果就是将每个函数对\boldsymbol{x}的导数堆叠起来得到的雅可比矩阵: ∂y∂x=[∂∂x1f1(x)∂∂x2f1(x)⋯∂∂xnf1(x)∂∂x1f2(x)∂∂x2f2(x)⋯∂∂xnf2(x)⋮⋮⋱⋮∂∂x1fm(x)∂∂x2fm(x)⋯∂∂xnfm(x)]

两向量间逐元素运算的导数

假设\bigcirc是一个对两向量逐元素进行计算的操作符(例如\bigoplus是向量加法,就是两个向量逐元素相加),则对于\boldsymbol{y} = \boldsymbol{f}(\boldsymbol{w}) \bigcirc \boldsymbol{g}(\boldsymbol{x}),假设n = m = |y| = |w| = |x|,可以做如下展开 [y1y2⋮yn]=[f1(w)◯g1(x)f2(w)◯g2(x)⋮fn(w)◯gn(x)]

\boldsymbol{y}分别对\boldsymbol{w}\boldsymbol{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))]

由于\bigcirc是逐元素操作,因此f_i是一个关于w_i的函数, g_i也是一个关于x_i的函数。因此当j \not= i时, \frac{\partial }{\partial w_j}f_i(w_i) = \frac{\partial }{\partial w_j}g_i(x_i) = 0,而且 0 0 = 0,因此上述两个雅可比矩阵都是对角矩阵,可以简记为 ∂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)))

如果只考虑最简单的向量计算,不妨令\boldsymbol{f}(\boldsymbol{w}) = \boldsymbol{w}, \boldsymbol{g}(\boldsymbol{x}) = \boldsymbol{x},因此有 f_i(w_i) = w_i。假设\bigcirc为逐元素相加,那么会有 ∂∂wi(fi(wi)+gi(xi))=1=∂∂x(fi(wi)+gi(xi))

其它减、乘、除同理,可以得到如下式子(由于向量加法+的法则就是 \oplus,因此\oplus简写为+。减法同理) ∂(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的向量,例如计算\boldsymbol{y} = \boldsymbol{x} + z实际上是\boldsymbol{y} = \boldsymbol{f}(\boldsymbol{x}) + \boldsymbol{g}(z) ,其中\boldsymbol{f}(\boldsymbol{x}) = \boldsymbol{x}, \boldsymbol{g}(z) = \boldsymbol{1}z

因此可以得到 ∂∂x(x+z)=I∂∂x(xz)=Iz∂∂z(x+z)=1∂∂z(xz)=x

向量的求和规约操作

求和规约,原文是sum reduce,实际上就是把一个向量的所有元素求和

y = {\rm sum}(\boldsymbol{f}(\boldsymbol{x})) = \sum_{i=1}^n f_i(\boldsymbol{x}),展开可得 ∂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={\rm sum}(\boldsymbol{x})。由之前的讨论,有 f_i(\boldsymbol{x}) = x_i。将定义代入上式,并考虑对i \not= j\frac{\partial }{\partial x_j}x_i = 0,易得 y=sum(x)→∇y=1

链式法则

对复杂表达式(各种表达式组合嵌套得到的表达式),需要使用链式法则求导。本文将链式法则分为了三种

单变量链式法则

其实就是高数里学到的。假设y = f(g(x)),令g(x) = u,则单变量链式法则为 dydx=dydududx

单变量全微分链式法则

在最简单的单变量链式法则里,所有中间变量都是单变量函数。对于y = f(x) = x+x^2这种函数,情况会复杂一些。如果引入中间变量u_1u_2,有 u1(x)=x2u2(x,u1)=x+u1(y=f(x)=u2(x,u1))

如果只使用前面的单变量链式法则,那么du_2/du_1 = 1, du_1/dx = 2x,所以 dy/dx = du_2/dx = du_2/du_1 \cdot du_1/dx = 2x,跟结果(2x+1)对不上。如果因为u_2有两个变量而引入偏导数,那么 ∂u1(x)∂x=2x∂u2(x,u1)∂u1=1 注意此时不能直接说\partial u_2 / \partial x = 1!因为对x求偏导数的时候限定了其它变量不会随着 x的变化而变化,但是u_1实际上还是关于x的函数。因此应该使用单变量全微分链式法则:假设u_1, \ldots, u_n(可能)都与x有关,则 ∂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 注意全微分公式在任何时候都是算偏导数的加和,并不是因为例子里存在求和操作,而是因为它表示的是各个 xy的变化量的加权求和。考虑式子y = f(x) = x \cdot x^2,那么 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 对前面介绍的全微分链式法则,如果引入一个新的变量u_{n+1} = x,则可以得到一个更简洁的公式 ∂f(u1,…,un+1)∂x=n+1∑i=1∂f∂ui∂ui∂x 这看上去很像两个向量的内积: ∂f∂u∂u∂x

向量的链式法则

为了引出向量的链式法则,可以先看一个例子。假设\boldsymbol{y} = \boldsymbol{f}(x),其中 [y1(x)y2(x)]=[f1(x)f2(x)]=[ln(x2)sin(3x)]

引入两个中间变量 g_1g_2,使得\boldsymbol{y} = \boldsymbol{f}(\boldsymbol{g}(x)),其中 [g1(x)g2(x)]=[x23x][f1(g)f2(g)]=[ln(g1)sin(g2)] 那么\partial \boldsymbol{y}/\partial x就可以使用全微分链式法则: ∂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 当x不是标量而是向量\boldsymbol{x}时,同样的法则也成立,这意味着我们可以得到一个向量的链式法则 y=f(g(x))→∂y∂x=∂f∂g∂g∂x 事实上,这个公式可以更加简洁。对很多应用,雅可比矩阵都是对角方阵,此时 f_i是一个只与g_i有关的函数,而g_i是一个只与x_i有关的函数。即 ∂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)

要计算的是\frac{\partial }{\partial \boldsymbol{w}}(\boldsymbol{w} \cdot \boldsymbol{x} + b)\frac{\partial }{\partial b}(\boldsymbol{w} \cdot \boldsymbol{x} + b)。尽管之前没讨论过向量内积对向量的偏导数,但是考虑 w⋅x=n∑i(wixi)=sum(w⊗x) 而之前讨论过{\rm sum}(\boldsymbol{x})\boldsymbol{w} \otimes \boldsymbol{x}的偏导数和向量的链式法则,那么引入中间变量 u=w⊗xy=sum(u) 根据上面的推导,有 ∂u∂w=diag(x)∂y∂u=1 因此 ∂y∂w=∂y∂u∂u∂w=1⋅diag(x)=x 再令y = \boldsymbol{w} \cdot \boldsymbol{x} + b,不难得出 ∂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来表示仿射变换,有 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

神经网络损失函数的梯度

最后考虑一个完整的例子。假设模型的输入是\boldsymbol{X},且 X=[x1,x2,…,xN]T

样本个数为N = |\boldsymbol{X}|,令结果向量为 y=[target(x1),target(x2),…,target(xN)]T 其中每个y_i都是一个标量。假设损失函数使用平方误差函数,那么损失函数C为 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 这里主要说明如何计算权重的梯度。偏置b的计算方法同理。由前面的推导可知 ∂∂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 后面关于梯度下降的讨论略


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/jaIPeGnvmt
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/25675