本文是学习Dive into Deep Learning中相应内容做出的总结和一些实现代码,原文链接:矩阵计算。
学习PyTorch的自动求导之前首先需要知道求导的过程。
注意:可能不同的教材关于对于向量和矩阵求导有着不同的定义,本文关于向量或者矩阵求导后会进行一次转置操作。但在PyTorch中不会进行转置,所以代码的求导后的形状可能与手动进行推导的形状不一样。我也是跑完代码才发现代码似乎并没有进行转置的操作。
首先介绍标量求导,标量求导可以分为标量关于向量求导和标量关于矩阵求导。
关于向量求导意味着向量的每一维都是一个自变量。
标量关于标量的求导也是最基础的求导操作。该操作即为微积分中对某一个函数求偏导数的操作。
例如存在函数:
f ( x , y ) = x 2 + y 2 f(x, y) = x^2 + y^2 f(x,y)=x2+y2
和一维向量(或标量) x x x,则求 f f f关于 x x x的导数需要将除 x x x以外的变量全部看做常数(即不参与求导运算,此处只有 y y y),则可以知道
d f d x = 2 x 。 \frac{df}{dx} = 2x 。 dxdf=2x。
代码实现如下:
import torch
# 在PyTorch里面实现不能采用公式方法实现
# 而只能求某一点的导数值
# 求x=1的导数值和y=2的导数值
x = torch.tensor([1.], requires_grad = True)
y = torch.tensor([2.], requires_grad = True)
f = x ** 2 + y ** 2
f.backward()
x.grad, y.grad
(tensor([2.]), tensor([4.]))
标量关于 n n n维列向量求导,则意味着列向量的每一维都是一个变量,只需要分别对每一维的标量进行求导(即上述的对标量求导),这样会得到 n n n个导数,接着将这 n n n个导数按照与 n n n维列向量的相互对应关系进行排列,此时得到一个新的 n n n维列向量,对该列向量取一次转置(即变为行向量)得到最终结果。
例如有函数关系式
f ( x , y , z ) = x 2 + y 2 + z 2 f(x, y, z) = x^2 + y^2 + z^2 f(x,y,z)=x2+y2+z2
和 3 3 3维列向量
v ⃗ = [ x , y , z ] T \vec v=[x, y, z]^T v=[x,y,z]T
则
d f d v ⃗ = [ 2 x , 2 y , 2 z ] 。 \frac{df}{d\vec v} = [2x, 2y, 2z]。 dvdf=[2x,2y,2z]。
# 由于列向量无法进行点乘的操作
# 这里使用行向量来模拟
# v为一个三维行向量
# 求[1, 2, 3]处的导数
v = torch.arange(1.0, 4.0)
v.requires_grad_(True)
f = torch.dot(v, v)
f.backward()
v.grad
tensor([2., 4., 6.])
从结果可以看出PyTorch在对行向量求导的时候并没有将结果变为一个列向量,这也是和李沐老师讲的不一样的地方。
求导过程与关于 n n n维列向量求导如出一辙,只是最终的结果是一个 n n n维的列向量而不再是一个 n n n维的行向量。
这里的关于行向量求导实际和上面关于列向量求导的代码是一样的,故不再给出。
标量关于矩阵求导,则意味着矩阵的每一个元素都是一个变量,与标量关于向量求导类似,依然是一次对每一个变量求偏导,按照对应关系得到一个矩阵;同样的,需要将这个矩阵进行一个转置操作。
例如有函数关系式
f ( x 1 , x 2 , x 3 , x 4 ) = x 1 2 + x 2 2 + x 3 2 + x 4 2 f(x_1, x_2, x_3, x_4) = x_1^2 + x_2^2 + x_3^2 + x_4^2 f(x1,x2,x3,x4)=x12+x22+x32+x42
和矩阵
X = ( x 1 x 2 x 3 x 4 ) X= \begin{pmatrix} x_1 & x_2 \\ x_3 & x_4 \end{pmatrix} X=(x1x3x2x4)
则
d f d X = ( 2 x 1 2 x 3 2 x 2 2 x 4 ) 。 \frac{df}{dX} = \begin{pmatrix} 2x_1 & 2x_3 \\ 2x_2 & 2x_4 \end{pmatrix}。 dXdf=(2x12x22x32x4)。
代码实现如下:
# 对应x1 = 1, x2 = 2, x3 = 3, x4 = 4处的导数
X = torch.arange(1.0, 5.0).reshape(2, 2)
X.requires_grad_(True)
f = (X ** 2).sum()
f.backward()
X.grad
tensor([[2., 4.],
[6., 8.]])
从结果可以看到这里的矩阵也没有进行转置的操作。
向量求导意味着向量的每一维都是一个函数表达式。
注意:在PyTorch中只有标量才能进行求导操作,所以向量求导部分不在给出代码实现。
向量关于标量求导只需要将每一维都关于指定变量求导组成一个新的向量即可,这就转换成了多次标量对标量求导。
例如有一个向量
v ⃗ = [ x + y , x 2 + y 2 , x 3 + y 3 ] \vec v = [x + y, x^2 + y^2, x^3 + y^3] v=[x+y,x2+y2,x3+y3]
则
d v ⃗ d x = [ 1 , 2 x , 3 x 2 ] \frac{d \vec v}{dx}=[1, 2x, 3x^2] dxdv=[1,2x,3x2]
(结果向量的形状与原向量形状相同)。
向量关于向量求导一共分为四种情况,只对第一种情况进行详细说明,其余三种情况可以类似推出。
首先给出两个行向量
v 1 ⃗ = [ x + y + z , x 2 + y 2 + z 2 , x 3 + y 3 + z 3 ] , v 2 ⃗ = [ x , y , z ] \vec {v1} = [x + y + z, x^2 + y^2 + z^2, x^3 + y^3 + z^3], \vec {v2} = [x, y, z] v1=[x+y+z,x2+y2+z2,x3+y3+z3],v2=[x,y,z]
如果要计算
d v 1 ⃗ d v 2 ⃗ \frac{d \vec {v1}}{d \vec {v2}} dv2dv1
则可以先将 v 2 ⃗ \vec {v2} v2看成标量, v 1 ⃗ \vec {v1} v1依次对 v 2 ⃗ \vec {v2} v2求导,此时得到
d v 1 ⃗ d v 2 ⃗ = [ d ( x + y + z ) d v 2 ⃗ , d ( x 2 + y 2 + z 2 ) d v 2 ⃗ , d ( x 3 + y 3 + z 3 ) d v 2 ⃗ ] \frac{d \vec {v1}}{d \vec {v2}} = [\frac {d(x+y+z)}{d \vec {v2}}, \frac {d(x^2+y^2+z^2)}{d \vec {v2}}, \frac {d(x^3+y^3+z^3)}{d \vec {v2}}] dv2dv1=[dv2d(x+y+z),dv2d(x2+y2+z2),dv2d(x3+y3+z3)]
此时在对该行向量的中的求导展开(即进行标量关于向量求导的过程(每个标量关于向量求导得到的是一个列向量),此时得到的结果是一个矩阵:
d v 1 ⃗ d v 2 ⃗ = ( 1 2 x 3 x 2 1 2 y 3 y 2 1 2 z 3 z 2 ) 。 \frac{d \vec {v1}}{d \vec {v2}} = \begin{pmatrix} 1 & 2x & 3x^2 \\ 1 & 2y & 3y^2 \\ 1 & 2z & 3z^2 \end{pmatrix}。 dv2dv1=⎝ ⎛1112x2y2z3x23y23z2⎠ ⎞。
将上述的第二个向量变化为列向量
v 3 ⃗ = v 2 ⃗ T = [ x , y , z ] T \vec {v3} = \vec {v2}^T = [x, y, z]^T v3=v2T=[x,y,z]T
采用和上述相同的方法,首先得到:
d v 1 ⃗ d v 3 ⃗ = [ d ( x + y + z ) d v 3 ⃗ , d ( x 2 + y 2 + z 2 ) d v 3 ⃗ , d ( x 3 + y 3 + z 3 ) d v 3 ⃗ ] \frac{d \vec {v1}}{d \vec {v3}} = [\frac {d(x+y+z)}{d \vec {v3}}, \frac {d(x^2+y^2+z^2)}{d \vec {v3}}, \frac {d(x^3+y^3+z^3)}{d \vec {v3}}] dv3dv1=[dv3d(x+y+z),dv3d(x2+y2+z2),dv3d(x3+y3+z3)]
此时将上述向量的每一项进行详细的展开,则可以得到:
d v 1 ⃗ d v 3 ⃗ = [ 1 , 1 , 1 , 2 x , 2 y , 2 y , 3 x 2 , 3 y 2 , 3 z 2 ] 。 \frac{d \vec {v1}}{d \vec {v3}} = [1, 1, 1, 2x, 2y, 2y, 3x^2, 3y^2, 3z^2]。 dv3dv1=[1,1,1,2x,2y,2y,3x2,3y2,3z2]。
同样的我们记
v 4 ⃗ = v 1 ⃗ T = [ x + y + z , x 2 + y 2 + z 2 , x 3 + y 3 + z 3 ] T \vec {v4} = \vec {v1}^T = [x + y + z, x^2 + y^2 + z^2, x^3 + y^3 + z^3]^T v4=v1T=[x+y+z,x2+y2+z2,x3+y3+z3]T
依然采用相同的方法我们可以得到:
d v 4 ⃗ d v 2 ⃗ = [ d ( x + y + z ) d v 2 ⃗ , d ( x 2 + y 2 + z 2 ) d v 2 ⃗ , d ( x 3 + y 3 + z 3 ) d v 2 ⃗ ] T \frac{d \vec {v4}}{d \vec {v2}} = [\frac {d(x+y+z)}{d \vec {v2}}, \frac {d(x^2+y^2+z^2)}{d \vec {v2}}, \frac {d(x^3+y^3+z^3)}{d \vec {v2}}]^T dv2dv4=[dv2d(x+y+z),dv2d(x2+y2+z2),dv2d(x3+y3+z3)]T
此时将上述向量的每一项进行详细的展开,则可以得到:
d v 4 ⃗ d v 2 ⃗ = [ 1 , 1 , 1 , 2 x , 2 y , 2 y , 3 x 2 , 3 y 2 , 3 z 2 ] T 。 \frac{d \vec {v4}}{d \vec {v2}} = [1, 1, 1, 2x, 2y, 2y, 3x^2, 3y^2, 3z^2]^T。 dv2dv4=[1,1,1,2x,2y,2y,3x2,3y2,3z2]T。
同样地,我们有:
d v 4 ⃗ d v 3 ⃗ = [ d ( x + y + z ) d v 3 ⃗ , d ( x 2 + y 2 + z 2 ) d v 3 ⃗ , d ( x 3 + y 3 + z 3 ) d v 3 ⃗ ] T \frac{d \vec {v4}}{d \vec {v3}} = [\frac {d(x+y+z)}{d \vec {v3}}, \frac {d(x^2+y^2+z^2)}{d \vec {v3}}, \frac {d(x^3+y^3+z^3)}{d \vec {v3}}]^T dv3dv4=[dv3d(x+y+z),dv3d(x2+y2+z2),dv3d(x3+y3+z3)]T
进一步的:
d v 4 ⃗ d v 3 ⃗ = ( 1 1 1 2 x 2 y 2 z 3 x 2 3 y 2 3 z 2 ) 。 \frac{d \vec {v4}}{d \vec {v3}} = \begin{pmatrix} 1 & 1 & 1 \\ 2x & 2y & 2z \\ 3x^2 & 3y^2 & 3z^2 \end{pmatrix}。 dv3dv4=⎝ ⎛12x3x212y3y212z3z2⎠ ⎞。
向量关于矩阵求导与向量关于向量求导依然类似,我们依然需要先把向量矩阵看成一个标量转换成向量对标量求导,然后再将得到的向量的每一个元素展开(此时是标量关于矩阵求导)
给出一个例子:
v ⃗ = [ x 2 + y 2 + z 2 + w 2 , x 2 + y 2 + z 2 + w 2 , x 2 + y 2 + z 2 + w 2 , x 2 + y 2 + z 2 + w 2 ] , M = ( x y z w ) \vec {v} = [x^2+y^2+z^2+w^2, x^2+y^2+z^2+w^2, x^2+y^2+z^2+w^2, x^2+y^2+z^2+w^2], M = \begin{pmatrix} x & y z & w \end{pmatrix} v=[x2+y2+z2+w2,x2+y2+z2+w2,x2+y2+z2+w2,x2+y2+z2+w2],M=(xyzw)
首先我们看做是对标量求导,可以得到:
d v d M = [ d ( x 2 + y 2 + z 2 + w 2 ) d M , d ( x 2 + y 2 + z 2 + w 2 ) d M , d ( x 2 + y 2 + z 2 + w 2 ) d M , d ( x 2 + y 2 + z 2 + w 2 ) d M ] \frac {dv}{dM} = [\frac {d(x^2+y^2+z^2+w^2)}{dM}, \frac {d(x^2+y^2+z^2+w^2)}{dM}, \frac {d(x^2+y^2+z^2+w^2)}{dM}, \frac {d(x^2+y^2+z^2+w^2)}{dM}] dMdv=[dMd(x2+y2+z2+w2),dMd(x2+y2+z2+w2),dMd(x2+y2+z2+w2),dMd(x2+y2+z2+w2)]
展开后得到:
d v d M = ( ( 2 x 2 z 2 y 2 w ) ( 2 x 2 z 2 y 2 w ) ( 2 x 2 z 2 y 2 w ) ( 2 x 2 z 2 y 2 w ) ) \frac {dv}{dM} = \begin{pmatrix} \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} \end{pmatrix} dMdv=((2x2y2z2w)(2x2y2z2w)(2x2y2z2w)(2x2y2z2w))
此时变成了一个三维的矩阵。
同样采用上面的例子,只是将向量改为转置,则有:
d v T d M = [ d ( x 2 + y 2 + z 2 + w 2 ) d M , d ( x 2 + y 2 + z 2 + w 2 ) d M , d ( x 2 + y 2 + z 2 + w 2 ) d M , d ( x 2 + y 2 + z 2 + w 2 ) d M ] T = ( ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ) \frac {dv^T}{dM} = [\frac {d(x^2+y^2+z^2+w^2)}{dM}, \frac {d(x^2+y^2+z^2+w^2)}{dM}, \frac {d(x^2+y^2+z^2+w^2)}{dM}, \frac {d(x^2+y^2+z^2+w^2)}{dM}]^T = \begin{pmatrix} \begin{pmatrix} 2x & 2y \\ 2z & 2w \end{pmatrix} \\ \begin{pmatrix} 2x & 2y \\ 2z & 2w \end{pmatrix} \\ \begin{pmatrix} 2x & 2y \\ 2z & 2w \end{pmatrix} \\ \begin{pmatrix} 2x & 2y \\ 2z & 2w \end{pmatrix} \end{pmatrix} dMdvT=[dMd(x2+y2+z2+w2),dMd(x2+y2+z2+w2),dMd(x2+y2+z2+w2),dMd(x2+y2+z2+w2)]T=⎝ ⎛(2x2z2y2w)(2x2z2y2w)(2x2z2y2w)(2x2z2y2w)⎠ ⎞
同样是一个三维的矩阵。
矩阵求导意味着矩阵的每一个元素都是一个函数表达式。
注意:在PyTorch中只有标量才能进行求导操作,所以此处同样不在给出代码实现。
矩阵中的每一个元素关于该表量求导,形状与原矩阵的形状相同。
例如给定一个矩阵:
Y = ( x x 2 x 3 x 4 ) Y = \begin{pmatrix} x & x^2 \\ x^3 & x^4 \end{pmatrix} Y=(xx3x2x4)
则
d Y d x = ( 1 2 x 3 x 2 4 x 3 ) 。 \frac{dY}{dx} = \begin{pmatrix} 1 & 2x \\ 3x^2 & 4x^3 \end{pmatrix}。 dxdY=(13x22x4x3)。
方法类似于向量关于向量求导,先将向量看成一个标量,对标量求导后得到的矩阵中将每个元素具体展开即可,此时展开的过程转换成了标量对向量求导。
下面给出例子:
Y = ( x 2 + y 2 + z 2 + w 2 x 2 + y 2 + z 2 + w 2 x 2 + y 2 + z 2 + w 2 x 2 + y 2 + z 2 + w 2 ) , v ⃗ = [ x , y , z , w ] Y = \begin{pmatrix} x^2 + y^2 + z^2 + w^2 & x^2 + y^2 + z^2 + w^2 \\ x^2 + y^2 + z^2 + w^2 & x^2 + y^2 + z^2 + w^2 \end{pmatrix}, \vec v = [x, y, z, w] Y=(x2+y2+z2+w2x2+y2+z2+w2x2+y2+z2+w2x2+y2+z2+w2),v=[x,y,z,w]
d Y d v ⃗ = ( d ( x 2 + y 2 + z 2 + w 2 ) d v ⃗ d ( x 2 + y 2 + z 2 + w 2 ) d v ⃗ d ( x 2 + y 2 + z 2 + w 2 ) d v ⃗ d ( x 2 + y 2 + z 2 + w 2 ) d v ⃗ ) = ( ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ) \frac {dY}{d \vec {v}} = \begin{pmatrix} \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec v} & \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec v} \\ \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec v} & \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec v} \end{pmatrix} = \begin{pmatrix} \begin{pmatrix} 2x \\ 2y \\ 2z \\ 2w \end{pmatrix} & \begin{pmatrix} 2x \\ 2y \\ 2z \\ 2w \end{pmatrix} \\ \begin{pmatrix} 2x \\ 2y \\ 2z \\ 2w \end{pmatrix} & \begin{pmatrix} 2x \\ 2y \\ 2z \\ 2w \end{pmatrix} \end{pmatrix} dvdY=(dvd(x2+y2+z2+w2)dvd(x2+y2+z2+w2)dvd(x2+y2+z2+w2)dvd(x2+y2+z2+w2))=⎝ ⎛⎝ ⎛2x2y2z2w⎠ ⎞⎝ ⎛2x2y2z2w⎠ ⎞⎝ ⎛2x2y2z2w⎠ ⎞⎝ ⎛2x2y2z2w⎠ ⎞⎠ ⎞
类似地,我们有:
d Y d v T ⃗ = ( d ( x 2 + y 2 + z 2 + w 2 ) d v T ⃗ d ( x 2 + y 2 + z 2 + w 2 ) d v T ⃗ d ( x 2 + y 2 + z 2 + w 2 ) d v T ⃗ d ( x 2 + y 2 + z 2 + w 2 ) d v T ⃗ ) = ( ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ( 2 x 2 y 2 z 2 w ) ) 。 \frac {dY}{d \vec {v^T}} = \begin{pmatrix} \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec {v^T}} & \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec {v^T}} \\ \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec {v^T}} & \frac {d(x^2 + y^2 + z^2 + w^2)}{d \vec {v^T}} \end{pmatrix} = \begin{pmatrix} \begin{pmatrix} 2x & 2y & 2z & 2w \end{pmatrix} & \begin{pmatrix} 2x & 2y & 2z & 2w \end{pmatrix} \\ \begin{pmatrix} 2x & 2y & 2z & 2w \end{pmatrix} & \begin{pmatrix} 2x & 2y & 2z & 2w \end{pmatrix} \end{pmatrix}。 dvTdY=(dvTd(x2+y2+z2+w2)dvTd(x2+y2+z2+w2)dvTd(x2+y2+z2+w2)dvTd(x2+y2+z2+w2))=((2x2y2z2w)(2x2y2z2w)(2x2y2z2w)(2x2y2z2w))。
我们设
M = ( x y z w ) M = \begin{pmatrix} x & y \\ z & w \end{pmatrix} M=(xzyw)
同样地,我们有:
d Y d M = ( d ( x 2 + y 2 + z 2 + w 2 ) d M d ( x 2 + y 2 + z 2 + w 2 ) d M d ( x 2 + y 2 + z 2 + w 2 ) d M d ( x 2 + y 2 + z 2 + w 2 ) d M ) = ( ( 2 x 2 z 2 y 2 w ) ( 2 x 2 z 2 y 2 w ) ( 2 x 2 z 2 y 2 w ) ( 2 x 2 z 2 y 2 w ) ) 。 \frac {dY}{dM} = \begin{pmatrix} \frac {d(x^2 + y^2 + z^2 + w^2)}{dM} & \frac {d(x^2 + y^2 + z^2 + w^2)}{dM} \\ \frac {d(x^2 + y^2 + z^2 + w^2)}{dM} & \frac {d(x^2 + y^2 + z^2 + w^2)}{dM} \end{pmatrix} = \begin{pmatrix} \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} & \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} \\ \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} & \begin{pmatrix} 2x & 2z \\ 2y & 2w \end{pmatrix} \end{pmatrix}。 dMdY=(dMd(x2+y2+z2+w2)dMd(x2+y2+z2+w2)dMd(x2+y2+z2+w2)dMd(x2+y2+z2+w2))=⎝ ⎛(2x2y2z2w)(2x2y2z2w)(2x2y2z2w)(2x2y2z2w)⎠ ⎞。
在上述所有的求导介绍中,比较复杂的是向量关于向量求导,向量关于矩阵求导,矩阵关于向量求导,矩阵关于矩阵求导。这些求导一定要特别注意形状,主要是关于非标量(向量或矩阵)求导,则最终的形状都要进行一次转置操作。