本文基于d2l项目内容整理,介绍线性代数的基础概念及其在PyTorch中的实现。

1. 标量、向量、矩阵与张量

1.1 标量

只有一个元素的张量是标量 (scalar),用普通小写字母表示,如 $a$、$b$ 和 $c$ 等。用 $\mathbb{R}$ 表示所有连续的实数标量空间,即 $a \in \mathbb{R}$。

1.2 向量

将标量组织成列表,表示的一维张量是向量 (vector),用粗体的小写字母表示,如 $\mathbf{x}$、$\mathbf{y}$ 和 $\mathbf{z}$ 等。每个向量都由 $n$ 个实值标量组成,记作 $\mathbf{x} \in \mathbb{R}^n$。

向量的重要特性:

  • 此时的标量作为向量的元素或分量

  • “列”是向量的默认方向

  • 向量的维度即为向量的长度(张量的维度指的是张量具有的轴数,张量某个轴的维数指的是该轴长度)

1.3 矩阵

从一阶推广到二阶,表示的二维张量是矩阵 (array, matrix),用粗体的大写字母表示,如 $\mathbf{A}$、$\mathbf{B}$ 和 $\mathbf{C}$ 等。每个矩阵都由 $m$ 行和 $n$ 列的实值标量组成,记作 $\mathbf{A} \in \mathbb{R}^{m \times n}$。

矩阵的重要概念:

  • 行列数量相等的矩阵称为方阵

  • 交换矩阵行列的过程称为转置 (transpose),记作 $\mathbf{A}^T$

  • 若转置前后矩阵相等,即 $\mathbf{A} = \mathbf{A}^T$,该矩阵被称为对称矩阵

1.4 张量

进一步推广到更多阶,统称为张量 (tensor),泛指全部的代数对象,用无衬线字体的大写字母表示,如 $\mathsf{X}$、$\mathsf{Y}$ 和 $\mathsf{Z}$ 等。用于构建具有更多轴的数据。


2. 张量的创建

PyTorch 提供了许多创建张量并初始化的方法:

  • torch.zeros():创建指定大小的全零张量
  • torch.ones():创建指定大小的全 1 张量
  • torch.randn():创建指定大小的张量,并用来自标准正态分布的随机数填充
  • torch.arange():创建从起始值到结束值(不含)的等差数列张量
  • torch.eye():创建指定大小的单位矩阵 (identity matrix)
  • torch.linspace():创建从起始值到结束值,以特定步长间距的张量
  • torch.full():创建指定大小,由特定值填充的张量

另外,torch.tensor()函数和torch.Tensor()方法是根据输入创建张量的通用方法:

推荐使用 torch.tensor()

  • 接收多种类型的数据作为输入,并自动推断张量的数据类型
  • 始终以拷贝的形式创建张量,不共享内存
  • 由于数据类型明确、总是执行初始化,而成为更被推荐的选择

谨慎使用 torch.Tensor()

  • 接收torch.Tensor类型的数据作为输入,根据设备选择默认的数据类型(一般是浮点型)
  • 直接使用对现有数据的引用而不拷贝数据,或创建未初始化、具有随机数据的张量

3. 张量的基本运算

3.1 加减除、数乘与乘法

加减除运算相对简单,这里重点介绍矩阵数乘(逐元素分别相乘,Hadamard 积,数学符号用 $\odot$ 表示)和矩阵乘法:

矩阵数乘(Hadamard积)

1
2
3
4
5
a: torch.Tensor = torch.arange(20, dtype=torch.float32).reshape(5, 4)
b: torch.Tensor = torch.arange(20, 40, dtype=torch.float32).reshape(5, 4)

print(f'矩阵数乘:a * b =\n{a * b}')
print(f'矩阵数乘:a.mul(b) =\n{a.mul(b)}')

矩阵乘法

符号@原本用于装饰器的定义,Python 3.5 开始,@亦可用于矩阵的乘法运算符。

1
2
3
print(f'矩阵乘法:a @ b.T =\n{a @ b.T}')
print(f'矩阵乘法:a.matmul(b.T) =\n{a.matmul(b.T)}')
print(f'矩阵乘法:a.mm(b.T) =\n{a.mm(b.T)}')
查看完整代码示例
1
2
3
4
5
6
7
8
9
a: torch.Tensor = torch.arange(20, dtype=torch.float32).reshape(5, 4)
b: torch.Tensor = torch.arange(20, 40, dtype=torch.float32).reshape(5, 4)

print(f'矩阵数乘:a * b =\n{a * b}')
print(f'矩阵数乘:a.mul(b) =\n{a.mul(b)}')

print(f'矩阵乘法:a @ b.T =\n{a @ b.T}')
print(f'矩阵乘法:a.matmul(b.T) =\n{a.matmul(b.T)}')
print(f'矩阵乘法:a.mm(b.T) =\n{a.mm(b.T)}')

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
矩阵数乘:a * b =
tensor([[ 0., 21., 44., 69.],
[ 96., 125., 156., 189.],
[224., 261., 300., 341.],
[384., 429., 476., 525.],
[576., 629., 684., 741.]])
矩阵数乘:a.mul(b) =
tensor([[ 0., 21., 44., 69.],
[ 96., 125., 156., 189.],
[224., 261., 300., 341.],
[384., 429., 476., 525.],
[576., 629., 684., 741.]])
矩阵乘法:a @ b.T =
tensor([[ 134., 158., 182., 206., 230.],
[ 478., 566., 654., 742., 830.],
[ 822., 974., 1126., 1278., 1430.],
[1166., 1382., 1598., 1814., 2030.],
[1510., 1790., 2070., 2350., 2630.]])

PyTorch 中,以下划线_结尾的方法通常是原地操作,将直接修改调用该方法的张量,而不是返回一个新的张量,如add_()mul_()等。

矩阵与向量乘法

1
2
3
4
A = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
x = torch.tensor([7, 8, 9], dtype=torch.float32)

print(f'矩阵 A ({A.shape}) 与向量 x ({x.shape}) 的乘积:{A.mv(x)}')

mv()方法比@运算符、matmul()mm()更专注于矩阵与向量的乘法。

3.2 向量的点积

在实践中,用非负且权重和为 1 的点积进行加权求和;使用范数乘积计算向量的夹角余弦值,评估它们的相似度。

余弦相似度的取值范围为 $[-1, 1]$:

  • 1 表示向量同向
  • -1 表示向量反向
  • 0 表示向量垂直
1
2
3
4
5
6
scores = torch.tensor([72, 80, 95, 29, 100], dtype=torch.float)
weights = torch.tensor([0.2, 0.3, 0.1, 0.2, 0.2], dtype=torch.float) # 权重值均非负,且和为一

score = torch.dot(scores, weights)
print(f'用点积计算加权和:{score:.2f}')
# 输出:用点积计算加权和:73.70
1
2
3
4
5
6
7
x = torch.tensor([1.0, 2.0, 3.0])
y = torch.tensor([4.0, 5.0, 6.0])

cosine_similarity = torch.dot(x, y) / (torch.norm(x) * torch.norm(y))

print(f"两向量的余弦相似度:{cosine_similarity.item():.3f}")
# 输出:两向量的余弦相似度:0.975

3.3 求和、求均值

参数命名差异:

  • 对于轴或维,NumPy 中使用axis,PyTorch 中使用dim
  • 对于维度保持,NumPy 中使用keepdims,PyTorch 中使用keepdim

虽然它们在运行时是兼容的,但为了在 PyCharm 等 IDE 中有更好的代码提示体验,应规范这些参数的使用。

1
2
3
4
5
6
7
8
9
10
x: torch.Tensor = torch.arange(24, dtype=torch.float32).reshape(3, -1)

print(f'x =\n{x}')
print(f'x.numel() = {x.numel()}') # 元素总数
print(f'x.shape = {x.shape}') # 张量形状
print(f'x.sum() = {x.sum()}') # 所有元素求和
print(f'x.sum(dim=0) = {x.sum(dim=0)}') # 按行求和
print(f'x.sum(dim=1) = {x.sum(dim=1)}') # 按列求和
print(f'x.mean() = {x.mean()}') # 所有元素求均值
print(f'x.mean(dim=0) = {x.mean(dim=0)}') # 按行求均值

非降维求和

1
2
3
4
5
6
7
8
9
10
11
sum_default = x.sum(dim=1)
sum_keepdim = x.sum(dim=1, keepdim=True)

print(f'x 的维数:{x.dim()}')
print(f'x.sum(dim=1) = {sum_default}')
print(f'x.sum(dim=1) 的维数:{sum_default.dim()}')

print(f'x.sum(dim=1, keepdim=True) = {sum_keepdim}')
print(f'x.sum(dim=1, keepdim=True) 的维数:{sum_keepdim.dim()}')

print(f'x / sum_keepdim =\n{x / sum_keepdim}')

使用 keepdim=True 可以保持原始张量的维度,便于后续的广播运算。

累计求和

1
print(f'x.cumsum(dim=1) =\n{x.cumsum(dim=1)}')

4. 范数

范数 (norm) 是用于衡量张量大小或长度的量。

4.1 几何角度理解范数

几何视角的范数理解:

在低维空间以内,函数是几何图形的概括,几何图形是函数的形象化。由于高维空间难以想象,于是从函数泛化出映射的概念,实现从一个集合到另一个集合的转换。为了更好地表达(线性)映射关系,以矩阵作为工具表征这些映射。最终,这些集合/向量,通过矩阵的映射关系,得到了另一个集合/向量。在整个过程中,向量的范数表示集合的大小,矩阵的范数是对变化大小的度量。

在用向量抽象真实世界的深度学习中,有许多最优化问题:使相似向量间的距离尽可能最小、使异质向量间的距离尽可能最大。而范数成了重要的指标之一。

4.2 向量范数

  • L0 范数:向量的非零元素个数——稀疏度
  • L1 范数(曼哈顿范数):向量的所有元素绝对值之和
  • L2 范数(欧几里得范数):向量的模,向量元素绝对值平方的和开算术平方根
  • ∞ 范数:向量元素中,绝对值的最大值
  • -∞ 范数:向量元素中,绝对值的最小值
  • p 范数:向量元素中,绝对值的 p 次方和的 1/p 次幂

对于向量 $\mathbf{x} = [x_1, x_2, \ldots, x_n]^T$:

  • L0 范数:$|\mathbf{x}|0 = \sum{i=1}^n \mathbf{1}(x_i \neq 0)$
  • L1 范数:$|\mathbf{x}|1 = \sum{i=1}^n |x_i|$
  • L2 范数:$|\mathbf{x}|2 = \sqrt{\sum{i=1}^n x_i^2}$
  • ∞ 范数:$|\mathbf{x}|_\infty = \max_i |x_i|$
  • p 范数:$|\mathbf{x}|p = \left(\sum{i=1}^n |x_i|^p\right)^{1/p}$

4.3 矩阵范数

L1 范数(列和范数):矩阵中,全部列向量绝对值之和的最大值

L2 范数(谱范数):矩阵的最大奇异值(矩阵的特征值的平方根)

∞ 范数(行和范数):矩阵中,全部行向量绝对值之和的最大值

F 范数(Frobenius 范数):矩阵各元素绝对值平方的和开算数平方根

4.4 重要性质

范数的重要性质:

  • “L”指的是范数满足线性 (linear) 条件
  • $L_n$ 范数亦可记作 $\ell_n$ 范数
  • $L_2$ 范数比 $L_1$ 范数更容易受异常值的影响
  • 对于 $L_2$ 范数,常常简称为 $\ell$ 范数。深度学习更常使用的是 $|\mathbf{x}|$
  • $L_1$ 和 $L_2$ 范数都是 $L_p$ 范数的特例

4.5 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
x = torch.tensor([3.0, -4.0, 2.0])

norm_0 = torch.norm(x, p=0)
norm_1 = torch.norm(x, p=1)
norm_2 = torch.norm(x, p=2)
norm_f = torch.norm(x, p='fro')
norm_inf = torch.norm(x, p=torch.inf)

print(f'向量的 L0 范数: {norm_0.item():.2f}')
print(f'向量的 L1 范数: {norm_1.item():.2f}')
print(f'向量的 L2 范数: {norm_2.item():.2f}')
print(f'向量的 F 范数: {norm_f.item():.2f}')
print(f'向量的无穷范数: {norm_inf.item():.2f}')
查看输出结果
1
2
3
4
5
向量的 L0 范数: 3.00
向量的 L1 范数: 9.00
向量的 L2 范数: 5.39
向量的 F 范数: 5.39
向量的无穷范数: 4.00

总结

本文介绍了线性代数的基础概念,包括标量、向量、矩阵和张量的定义与性质,以及它们在PyTorch中的实现方法。这些概念是深度学习的数学基础,理解它们对于后续学习神经网络和机器学习算法至关重要。