线性代数 (Linear Algebra)
线性代数是数学的一个分支,它涉及向量空间和线性映射。许多问题都可以通过线性代数来解决,在机器学习中尤其如此。
标量
标量就是一个单独的数,通常用希腊小写字母表示。在 Pytorch 中我们使用一个元素的张量来表示标量。
import torch
x = torch.tensor([3.0])
y = torch.tensor([2.0])
x + y, x * y, x / y, x ** y
# (tensor([5.]), tensor([6.]), tensor([1.5000]), tensor([9.]))
向量
你可以将向量视为标量值组成的列表。我们称之为向量。我们用粗体表示向量,例如 和 。在 Pytorch 中,我们表示向量为张量。
x = torch.arange(4)
x
# tensor([0, 1, 2, 3])
向量的存在有非常重要的意义,例如我们正在训练一个模型来预测贷款违约的风险。对于每个贷款申请人,我们可以用一个向量来表示他们的年龄、收入、负债等级等特征。
在数学中,向量写为:
我们使用张量的索引来访问向量的任何元素。
x[3]
# tensor(3)
向量的长度通常称为向量的 维度(dimension)。使用 len(x)
来访问向量的长度。
矩阵
矩阵是一个二维数组,其中的每个元素都是一个标量。我们通常使用大写字母来表示矩阵,例如 A。
在数学中,一个矩阵形如:
使用 Pytorch 表示矩阵:
A = torch.arange(20).reshape(5, 4)
A
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])
我们可以通过行和列的索引来访问矩阵的标量元素。
A[2, 3]
# tensor(11)
矩阵的 转置(transposition)是将行和列互换。我们用 来表示矩阵 A 的转置。
A = torch.arange(20).reshape(5, 4)
A.T
# tensor([[ 0, 4, 8, 12, 16],
# [ 1, 5, 9, 13, 17],
# [ 2, 6, 10, 14, 18],
# [ 3, 7, 11, 15, 19]])
矩阵有重要的作用,例如我们可以用矩阵来表示一个数据集,其中每一行表示一个样本,每一列表示一个特征。
张量
在代数上的张量是向量和矩阵的推广。张量是一个数组,其中的元素可以是标量、向量、矩阵等。我们用粗体的大写字母来表示张量,例如 X。
我们可以理解向量是一阶张量,矩阵是二阶张量。
当我们开始处理图像时,张量显得尤为重要。图像通常由三个维度组成:高度、宽度和通道。而视频还有一个时间维度。
X = torch.arange(24).reshape(2, 3, 4)
X
# tensor([[[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]],
# [[12, 13, 14, 15],
# [16, 17, 18, 19],
# [20, 21, 22, 23]]]) # 这是一个三维张量
张量的加法
两个相同形状的张量相加的结果是两个张量相应位置的元素相加。
X = torch.tensor([1.0, 2, 4, 8])
Y = torch.tensor([2, 2, 2, 2])
X + Y
# tensor([ 3., 4., 6., 10.])
与标量相乘
张量与标量相乘的结果是张量的每个元素与标量相乘。
a = 2
X * a
# tensor([ 2., 4., 8., 16.])
按元素乘法
两个矩阵按元素相乘被称为 哈达玛积(Hadamard product),数学表示为 。两个矩阵按元素乘法的数学表示如下:
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
A
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
B = A.clone() # 通过分配新内存,将A的一个副本分配给B
A*B
# tensor([[ 0., 1., 4., 9.],
# [ 16., 25., 36., 49.],
# [ 64., 81., 100., 121.],
# [144., 169., 196., 225.],
# [256., 289., 324., 361.]])
求和
对张量,我们可以计算 求和(sum)。
x = torch.arange(4, dtype=torch.float32)
x, x.sum()
# (tensor([0., 1., 2., 3.]), tensor(6.))
对于一维的张量数学上记为 。
对于矩阵,
A.shape, A.sum()
# (torch.Size([5, 4]), tensor(190.))
数学上记为 。
对于三维的张量,数学上记为 。依此类推。
降维
在计算求和时,我们可以通过指定 axis
参数来指定沿着哪个轴进行求和。
A
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
A_sum_axis0 = A.sum(axis=0) # 0+4+8+12+16, 1+5+9+13+17, 2+6+10+14+18, 3+7+11+15+19
A_sum_axis0, A_sum_axis0.shape
# (tensor([40., 45., 50., 55.]), torch.Size([4]))
A_sum_axis1 = A.sum(axis=1) # 0+1+2+3, 4+5+6+7, 8+9+10+11, 12+13+14+15, 16+17+18+19
A_sum_axis1, A_sum_axis1.shape
# (tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
平均值
我们可以计算张量的 平均值(mean)。
A.mean(), A.sum() / A.numel() # A.numel()是A的元素总数
# (tensor(9.5000), tensor(9.5000))
非降维求和
但是,有时我们想保持轴数不变,而只是通过求和降低维度。例如,给定矩阵 A
,我们可以对所有元素求和,得到只有一个元素的标量。
sum_A = A.sum(axis=1, keepdims=True) # 0+1+2+3, 4+5+6+7, 8+9+10+11, 12+13+14+15, 16+17+18+19
sum_A
# tensor([[ 6.],
# [22.],
# [38.],
# [54.],
# [70.]])
点积
给定两个向量 和 ,它们的 点积(dot product)是相同位置的按元素乘积的和:
y = torch.ones(4, dtype=torch.float32)
x, y, torch.dot(x, y) # 0*1 + 1*1 + 2*1 + 3*1
# (tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
矩阵-向量积
现在我们知道如何计算点积,我们可以计算矩阵-向量积。如果我们有一个矩阵 和一个向量 ,我们可以将矩阵 视为一组向量,每个向量是矩阵的一行。
A
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
x
# tensor([0., 1., 2., 3.])
A.shape, x.shape, torch.mv(A, x) # 0*0 + 1*1 + 2*2 + 3*3, 4*0 + 5*1 + 6*2 + 7*3, 8*0 + 9*1 + 10*2 + 11*3, 12*0 + 13*1 + 14*2 + 15*3, 16*0 + 17*1 + 18*2 + 19*3
# (torch.Size([5, 4]), torch.Size([4]), tensor([ 14., 38., 62., 86., 110.]))
矩阵-矩阵乘法
假设有两个的矩阵 A 和 B,它们的形状分别为 n×k 和 k×m。它们的矩阵乘法是一个 n×m 的矩阵 C。
要得到结果,最简单的方式是考虑 A 的行向量和 B 的列向量。
因此,C :
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = torch.ones(4, 3, dtype=torch.float32)
C = torch.mm(A, B)
C
# tensor([[ 6., 6., 6.],
# [22., 22., 22.],
# [38., 38., 38.],
# [54., 54., 54.],
# [70., 70., 70.]])
运算过程可以表示为:
C[0, 0] = torch.dot(A[0], B[:, 0]) # [0, 1, 2, 3] * [1, 1, 1] = 0*1 + 1*1 + 2*1 + 3*1
C[0, 1] = torch.dot(A[0], B[:, 1]) # [0, 1, 2, 3] * [1, 1, 1] = 0*1 + 1*1 + 2*1 + 3*1
C[0, 2] = torch.dot(A[0], B[:, 2]) # [0, 1, 2, 3] * [1, 1, 1] = 0*1 + 1*1 + 2*1 + 3*1
C[1, 0] = torch.dot(A[1], B[:, 0]) # [4, 5, 6, 7] * [1, 1, 1] = 4*1 + 5*1 + 6*1 + 7*1
C[1, 1] = torch.dot(A[1], B[:, 1]) # [4, 5, 6, 7] * [1, 1, 1] = 4*1 + 5*1 + 6*1 + 7*1
C[1, 2] = torch.dot(A[1], B[:, 2]) # [4, 5, 6, 7] * [1, 1, 1] = 4*1 + 5*1 + 6*1 + 7*1
C[2, 0] = torch.dot(A[2], B[:, 0]) # [8, 9, 10, 11] * [1, 1, 1] = 8*1 + 9*1 + 10*1 + 11*1
C[2, 1] = torch.dot(A[2], B[:, 1]) # [8, 9, 10, 11] * [1, 1, 1] = 8*1 + 9*1 + 10*1 + 11*1
C[2, 2] = torch.dot(A[2], B[:, 2]) # [8, 9, 10, 11] * [1, 1, 1] = 8*1 + 9*1 + 10*1 + 11*1
C[3, 0] = torch.dot(A[3], B[:, 0]) # [12, 13, 14, 15] * [1, 1, 1] = 12*1 + 13*1 + 14*1 + 15*1
...
范数
在数学中,范数是将向量映射到非负值的函数。直观地说,一个向量的范数告诉我们一个向量有多大。
在 Pytorch 中,我们可以使用 torch.norm
来计算范数。
u = torch.tensor([3.0, -4.0])
torch.norm(u)
# tensor(5.)
范数的一些重要性质:
-
对于所有标量 ,向量 ,有 。
-
对于任意两个向量 和 ,有 。
-
对于所有向量 ,有 。并且 当且仅当 。
L2 范数是向量元素平方和的平方根:
torch.sqrt(torch.sum(u ** 2))
# tensor(5.)
L1 范数是向量元素的绝对值之和:
torch.abs(u).sum()
# tensor(7.)
L_p 范数是向量元素的绝对值的 p 次方和的 1/p 次方:
总结
到目前为止,本章介绍了深度学习中经常使用的线性代数基本知识,包括标量、向量、矩阵和张量。我们可以对它们进行加法、标量乘法、按元素乘法、矩阵-向量积和矩阵-矩阵乘法。这些基本操作在后续章节中会经常用到。
如需扩展更多的线性代数知识,可以参考 线性代数 (opens in a new tab)。