1 说明
本文是学习Dive into Deep Learning中相应内容做出的总结和一些实现代码,原文链接:矩阵计算。
2 求导
学习PyTorch的自动求导之前首先需要知道求导的过程。
注意:可能不同的教材关于对于向量和矩阵求导有着不同的定义,本文关于向量或者矩阵求导后会进行一次转置操作。但在PyTorch中不会进行转置,所以代码的求导后的形状可能与手动进行推导的形状不一样。我也是跑完代码才发现代码似乎并没有进行转置的操作。
2.1 标量求导
首先介绍标量求导,标量求导可以分为标量关于向量求导和标量关于矩阵求导。
2.1.1 标量关于向量求导
关于向量求导意味着向量的每一维都是一个自变量。
2.1.1.1 标量关于一维向量(标量)求导
标量关于标量的求导也是最基础的求导操作。该操作即为微积分中对某一个函数求偏导数的操作。
例如存在函数:
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.]))
2.1.1.2 标量关于n维列向量求导
标量关于 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在对行向量求导的时候并没有将结果变为一个列向量,这也是和李沐老师讲的不一样的地方。
2.1.1.3 标量关于n维行向量求导
求导过程与关于 n n n维列向量求导如出一辙,只是最终的结果是一个 n n n维的列向量而不再是一个 n n n维的行向量。
这里的关于行向量求导实际和上面关于列向量求导的代码是一样的,故不再给出。
2.1.2 标量关于矩阵求导
标量关于矩阵求导,则意味着矩阵的每一个元素都是一个变量,与标量关于向量求导类似,依然是一次对每一个变量求偏导,按照对应关系得到一个矩阵;同样的,需要将这个矩阵进行一个转置操作。
例如有函数关系式
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.]])
从结果可以看到这里的矩阵也没有进行转置的操作。
2.2 向量求导
向量求导意味着向量的每一维都是一个函数表达式。
注意:在PyTorch中只有标量才能进行求导操作,所以向量求导部分不在给出代码实现。
2.2.1 向量关于标量求导
向量关于标量求导只需要将每一维都关于指定变量求导组成一个新的向量即可,这就转换成了多次标量对标量求导。
例如有一个向量
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]
(结果向量的形状与原向量形状相同)。
2.2.2 向量关于向量求导
向量关于向量求导一共分为四种情况,只对第一种情况进行详细说明,其余三种情况可以类似推出。
2.2.2.1 行向量关于行向量求导
首先给出两个行向量
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(x