Notes
深度学习
线性代数

线性代数 (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.]))

向量

你可以将向量视为标量值组成的列表。我们称之为向量。我们用粗体表示向量,例如 u\mathbf{u}v\mathbf{v}。在 Pytorch 中,我们表示向量为张量。

x = torch.arange(4)
x
# tensor([0, 1, 2, 3])

向量的存在有非常重要的意义,例如我们正在训练一个模型来预测贷款违约的风险。对于每个贷款申请人,我们可以用一个向量来表示他们的年龄、收入、负债等级等特征。

在数学中,向量写为:

x=[x1x2xn]\mathbf{x} = \begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{bmatrix}

我们使用张量的索引来访问向量的任何元素。

x[3]
# tensor(3)

向量的长度通常称为向量的 维度(dimension)。使用 len(x) 来访问向量的长度。

矩阵

矩阵是一个二维数组,其中的每个元素都是一个标量。我们通常使用大写字母来表示矩阵,例如 A

在数学中,一个矩阵形如:

A=[a1,1a1,2a1,na2,1a2,2a2,nam,1am,2am,n]\mathbf{A} = \begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1} & a_{m,2} & \cdots & a_{m,n} \end{bmatrix}

使用 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\mathbf{A}^\top 来表示矩阵 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),数学表示为 \odot。两个矩阵按元素乘法的数学表示如下:

AB=[a1,1b1,1a1,2b1,2a1,nb1,na2,1b2,1a2,2b2,2a2,nb2,nam,1bm,1am,2bm,2am,nbm,n]\mathbf{A} \odot \mathbf{B} = \begin{bmatrix} a_{1,1} b_{1,1} & a_{1,2} b_{1,2} & \cdots & a_{1,n} b_{1,n} \\ a_{2,1} b_{2,1} & a_{2,2} b_{2,2} & \cdots & a_{2,n} b_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m,1} b_{m,1} & a_{m,2} b_{m,2} & \cdots & a_{m,n} b_{m,n} \end{bmatrix}

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.))

对于一维的张量数学上记为 i=1nxi\sum_{i=1}^{n} x_i

对于矩阵,

A.shape, A.sum()
# (torch.Size([5, 4]), tensor(190.))

数学上记为 i=1mj=1nai,j\sum_{i=1}^{m} \sum_{j=1}^{n} a_{i,j}

对于三维的张量,数学上记为 i=1mj=1nk=1pai,j,k\sum_{i=1}^{m} \sum_{j=1}^{n} \sum_{k=1}^{p} a_{i,j,k}。依此类推。

降维

在计算求和时,我们可以通过指定 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.]])

点积

给定两个向量 x\mathbf{x}y\mathbf{y},它们的 点积(dot product)是相同位置的按元素乘积的和:

xy=i=1nxiyi\mathbf{x} \cdot \mathbf{y} = \sum_{i=1}^{n} x_i y_i

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\mathbf{A} 和一个向量 x\mathbf{x},我们可以将矩阵 A\mathbf{A} 视为一组向量,每个向量是矩阵的一行。

Ax=[a1Txa2TxamTx]\mathbf{A} \mathbf{x} = \begin{bmatrix} \mathbf{a}^{T}_1 \mathbf{x} \\ \mathbf{a}^{T}_2 \mathbf{x} \\ \vdots \\ \mathbf{a}^{T}_m \mathbf{x} \end{bmatrix}

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.]))

矩阵-矩阵乘法

假设有两个的矩阵 AB,它们的形状分别为 n×k 和 k×m。它们的矩阵乘法是一个 n×m 的矩阵 C

A=[a1,1a1,2a1,ka2,1a2,2a2,kan,1an,2an,k]\mathbf{A} = \begin{bmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,k} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,k} \\ \vdots & \vdots & \ddots & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,k} \end{bmatrix}

 B=[b1,1b1,2b1,mb2,1b2,2b2,mbk,1bk,2bk,m] \mathbf{B} = \begin{bmatrix} b_{1,1} & b_{1,2} & \cdots & b_{1,m} \\ b_{2,1} & b_{2,2} & \cdots & b_{2,m} \\ \vdots & \vdots & \ddots & \vdots \\ b_{k,1} & b_{k,2} & \cdots & b_{k,m} \end{bmatrix}

C=AB\mathbf{C} = \mathbf{A} \mathbf{B}

要得到结果,最简单的方式是考虑 A 的行向量和 B 的列向量。

A=[a1Ta2TanT]\mathbf{A} = \begin{bmatrix} \mathbf{a}^{T}_1 \\ \mathbf{a}^{T}_2 \\ \vdots \\ \mathbf{a}^{T}_n \end{bmatrix}

B=[b1b2bm]\mathbf{B} = \begin{bmatrix} \mathbf{b}_1 & \mathbf{b}_2 & \cdots & \mathbf{b}_m \end{bmatrix}

因此,C :

C=[a1Tb1a1Tb2a1Tbma2Tb1a2Tb2a2TbmanTb1anTb2anTbm]\mathbf{C} = \begin{bmatrix} \mathbf{a}^{T}_1 \mathbf{b}_1 & \mathbf{a}^{T}_1 \mathbf{b}_2 & \cdots & \mathbf{a}^{T}_1 \mathbf{b}_m \\ \mathbf{a}^{T}_2 \mathbf{b}_1 & \mathbf{a}^{T}_2 \mathbf{b}_2 & \cdots & \mathbf{a}^{T}_2 \mathbf{b}_m \\ \vdots & \vdots & \ddots & \vdots \\ \mathbf{a}^{T}_n \mathbf{b}_1 & \mathbf{a}^{T}_n \mathbf{b}_2 & \cdots & \mathbf{a}^{T}_n \mathbf{b}_m \end{bmatrix}

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.]])

运算过程可以表示为:

[012345678910111213141516171819][111111111111]=[666222222383838545454707070]\begin{bmatrix} 0 & 1 & 2 & 3 \\ 4 & 5 & 6 & 7 \\ 8 & 9 & 10 & 11 \\ 12 & 13 & 14 & 15 \\ 16 & 17 & 18 & 19 \end{bmatrix} \begin{bmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{bmatrix} = \begin{bmatrix} 6 & 6 & 6 \\ 22 & 22 & 22 \\ 38 & 38 & 38 \\ 54 & 54 & 54 \\ 70 & 70 & 70 \end{bmatrix}

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.)

范数的一些重要性质:

  1. 对于所有标量 aa,向量 u\mathbf{u},有 au=au\lVert a \mathbf{u} \rVert = |a| \lVert \mathbf{u} \rVert

  2. 对于任意两个向量 u\mathbf{u}v\mathbf{v},有 u+vu+v\lVert \mathbf{u} + \mathbf{v} \rVert \leq \lVert \mathbf{u} \rVert + \lVert \mathbf{v} \rVert

  3. 对于所有向量 u\mathbf{u},有 u0\lVert \mathbf{u} \rVert \geq 0。并且 u=0\lVert \mathbf{u} \rVert = 0 当且仅当 u=0\mathbf{u} = 0

L2 范数是向量元素平方和的平方根:

u2=i=1nui2\lVert \mathbf{u} \rVert_2 = \sqrt{\sum_{i=1}^{n} u_i^2}

torch.sqrt(torch.sum(u ** 2))
# tensor(5.)

L1 范数是向量元素的绝对值之和:

u1=i=1nui\lVert \mathbf{u} \rVert_1 = \sum_{i=1}^{n} |u_i|

torch.abs(u).sum()
# tensor(7.)

L_p 范数是向量元素的绝对值的 p 次方和的 1/p 次方:

up=(i=1nuip)1/p\lVert \mathbf{u} \rVert_p = \left( \sum_{i=1}^{n} |u_i|^p \right)^{1/p}

总结

到目前为止,本章介绍了深度学习中经常使用的线性代数基本知识,包括标量、向量、矩阵和张量。我们可以对它们进行加法、标量乘法、按元素乘法、矩阵-向量积和矩阵-矩阵乘法。这些基本操作在后续章节中会经常用到。

如需扩展更多的线性代数知识,可以参考 线性代数 (opens in a new tab)