feat: 完整中文翻译 maths-cs-ai-compendium(数学·计算机科学·AI 知识大全)

翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。

第01章 向量 | 第02章 矩阵 | 第03章 微积分
第04章 统计学 | 第05章 概率论 | 第06章 机器学习
第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音
第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络
第13章 计算与操作系统 | 第14章 数据结构与算法
第15章 生产级软件工程 | 第16章 SIMD与GPU编程
第17章 AI推理 | 第18章 ML系统设计
第19章 应用人工智能 | 第20章 前沿人工智能

翻译说明:
- 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留
- mkdocs.yml 配置中文导航 + language: zh
- README.md 已翻译为中文(兼 docs/index.md)
- docs/ 目录包含指向各章文件的 symlink
- 约 29,000 行中文内容,排除 .cache/ 构建缓存
This commit is contained in:
2026-05-03 10:23:20 +08:00
commit 2536c937e3
400 changed files with 49040 additions and 0 deletions
@@ -0,0 +1,166 @@
# 矩阵性质
*矩阵是存储数据集、编码变换和定义每个神经网络层的数据结构。本文涵盖矩阵维度、元素、转置、迹、行列式、逆、秩和零空间,这些是贯穿线性代数和 ML 的基础性质。*
- 核心而言,**矩阵**是按行列排列的数字矩形网格。如果向量是数字的单个列表,那么矩阵就是数字的一张表格。
```math
A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}
```
- 你也可以将矩阵视为向量的堆叠。
- 如果一个人由向量 $[\text{age}, \text{height}, \text{weight}]$ 描述,那么三个人就形成一个矩阵,其中每行是一个人:
```math
\begin{bmatrix} 25 & 170 & 65 \\ 30 & 180 & 80 \\ 22 & 160 & 55 \end{bmatrix}
```
- 这个矩阵有 3 行和 3 列,所以我们称它为 $3 \times 3$ 矩阵。
- 网格中的每个数字称为一个**元素**或**条目**,由其行列标识:$A_{ij}$ 是第 $i$ 行第 $j$ 列的元素。
- 矩阵的**转置**沿其对角线翻转,将行变为列,列变为行。如果 $A$ 是 $m \times n$,那么 $A^T$ 是 $n \times m$。
```math
A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \quad \Rightarrow \quad A^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix}
```
- 矩阵乘以其转置总是得到一个方阵:$AA^T$ 是 $m \times m$$A^TA$ 是 $n \times n$。
- 方阵的**迹**是其对角线元素之和:$\text{tr}(A) = A_{11} + A_{22} + \cdots + A_{nn}$。迹等于特征值之和(我们稍后会看到)。
![迹:对角线元素之和](../images/matrix_trace.svg)
- 对于上面的矩阵,$\text{tr}(A) = 1 + 4 + 9 = 14$。只有高亮的对角线部分重要。
- 如果两个矩阵在不同基下表示相同的线性变换,它们的迹相同。迹是"与基无关的。"
- 矩阵的**秩**是线性无关的行(或等价地,列)的数量。它告诉你矩阵携带了多少"有用信息。"
- 例如,以下矩阵的秩为 2,因为两行之间互不为倍数:
```math
\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}
```
但以下矩阵的秩为 1,因为第二行只是第一行的两倍,所以它没有增加新信息:
```math
\begin{bmatrix} 1 & 2 \\ 2 & 4 \end{bmatrix}
```
- 一个 $5 \times 3$ 矩阵的秩最多为 3。如果某些行只是其他行的缩放或组合版本,秩就会下降。具有最大可能秩的矩阵称为**满秩**。
![秩:独立行张成整个空间 vs. 相关行只张成一个子空间](../images/matrix_rank.svg)
- 方阵可逆(有逆矩阵)当且仅当它是满秩的。
- 秩通过**秩-零化度定理**与**零空间**(矩阵映射到零的向量的集合)相连:$\text{rank}(A) + \text{nullity}(A) = \text{列数 of } A$。矩阵保留的(秩)加上它破坏的(零化度)等于总维度。
- 矩阵的**列空间**是当你将矩阵乘以任意向量时所有可能输出的集合。它由矩阵的列张成。如果矩阵有 3 列但只有 2 列独立,列空间是一个二维平面,而不是整个三维空间。
![列空间:独立列张成一个平面,相关列只张成一条线](../images/column_space.svg)
- **行空间**是同样的概念,但从行的角度来看。秩等于列空间和行空间的维度,所以它们总是一致的。
- 一起来看,列空间告诉你"这个矩阵能产生什么输出?"零空间告诉你"什么输入被映射到零?"这两个空间完整描述了矩阵的功能。
- 方阵的**行列式**是一个标量,捕捉矩阵如何缩放空间。想象一个 $2 \times 2$ 矩阵将一个单位正方形变换成一个平行四边形。行列式就是那个平行四边形的面积(带有符号)。
```math
\det\begin{bmatrix} a & b \\ c & d \end{bmatrix} = ad - bc
```
![行列式:线性变换的面积缩放因子](../images/determinant.svg)
- 例如:
```math
\det\begin{bmatrix} 2 & 1 \\ 0 & 3 \end{bmatrix} = 2 \cdot 3 - 1 \cdot 0 = 6
```
这个变换将单位正方形拉伸成一个面积为 6 的平行四边形。
- 如果行列式为正,变换保持定向(事物不会被"翻转")。如果为负,它翻转定向(像镜面反射)。如果为零,矩阵将空间压缩到更低维度,将平行四边形坍缩成一条线或一个点。
- 行列式为零的矩阵称为**奇异矩阵**。它没有逆矩阵且已永久丢失信息。
- 对于大于 $2 \times 2$ 的矩阵,行列式使用**余子式**和**代数余子式**计算。**余子式** $M_{ij}$ 是通过删除第 $i$ 行和第 $j$ 列得到的较小矩阵的行列式。
![余子式:删除一行一列得到更小的矩阵](../images/cofactor.svg)
- **代数余子式** $C_{ij} = (-1)^{i+j} M_{ij}$ 为每个余子式附加一个符号(像棋盘一样交替:$+, -, +, \ldots$)。整个矩阵的行列式然后沿着任意行或列求和:$\det(A) = \sum_j A_{1j} \cdot C_{1j}$。这称为**代数余子式展开**。
- 方阵 $A$ 的**逆**,记作 $A^{-1}$,是撤销 $A$ 的矩阵:$AA^{-1} = A^{-1}A = I$(单位矩阵)。只有非奇异矩阵才有逆。
- 对于 $2 \times 2$ 矩阵,逆有一个直接公式:
```math
\begin{bmatrix} a & b \\ c & d \end{bmatrix}^{-1} = \frac{1}{ad - bc}\begin{bmatrix} d & -b \\ -c & a \end{bmatrix}
```
注意分母中的行列式,这就是为什么奇异矩阵(行列式为零)没有逆。
- **条件数**衡量矩阵对其输入微小变化的敏感程度。它定义为 $\kappa(A) = \|A\| \cdot \|A^{-1}\|$。
- 接近 1 的条件数意味着矩阵是**良态的**:微小的输入变化产生微小的输出变化。大的条件数意味着它是**病态的**:微小的误差被极大放大。正交矩阵和单位矩阵的条件数为 1,而奇异矩阵的条件数为无穷大。
- 例如,以下矩阵的条件数为 $10^8$。一个方向被正常缩放,而另一个几乎被压缩为零,所以沿该方向的小扰动会被严重扭曲:
```math
\begin{bmatrix} 1 & 0 \\ 0 & 10^{-8} \end{bmatrix}
```
- 就像向量有范数(长度)一样,矩阵也有衡量其"大小"的**范数**。最常见的是**弗罗贝尼乌斯范数**,它将矩阵视为一个长向量并计算其长度:
```math
\|A\|_F = \sqrt{\sum_{i}\sum_{j} A_{ij}^2}
```
- 例如:
```math
\left\|\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix}\right\|_F = \sqrt{1 + 4 + 9 + 16} = \sqrt{30} \approx 5.48
```
- **谱范数** $\|A\|_2$ 是 $A$ 的最大奇异值。它衡量矩阵可以拉伸任何单位向量的最大程度。在 ML 中,矩阵范数用于权重正则化(惩罚大权重)和监控训练稳定性。
- 对称矩阵 $A$ 是**正定的**,如果对每个非零向量 $\mathbf{x}$$\mathbf{x}^T A \mathbf{x} > 0$。这个二次型总是产生正数。
- 例如,以下矩阵是正定的:
```math
A = \begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix}
```
取任意向量,比如 $\mathbf{x} = [1, -1]^T$$\mathbf{x}^T A \mathbf{x} = 2 - 1 - 1 + 3 = 3 > 0$。无论你尝试哪个非零 $\mathbf{x}$,你总是得到正的结果。
- 正定矩阵很重要,因为它们保证优化问题有唯一的最小值。
- 如果条件放宽到 $\mathbf{x}^T A \mathbf{x} \geq 0$(允许为零),矩阵是**半正定**(PSD)。PSD 矩阵经常出现:协方差矩阵、SVM 中的核矩阵以及局部最小值处的 Hessian 矩阵都是 PSD。区别在于 PSD 允许某些方向是"平坦的"(零曲率),而不是严格向上弯曲。
## 编程练习(使用 CoLab 或 notebook
1. 计算矩阵的迹、秩和行列式。尝试使一行成为另一行的倍数,观察秩和行列式如何变化。
```python
import jax.numpy as jnp
A = jnp.array([[1.0, 2.0],
[3.0, 4.0]])
print(f"Trace: {jnp.trace(A)}")
print(f"Rank: {jnp.linalg.matrix_rank(A)}")
print(f"Determinant: {jnp.linalg.det(A):.2f}")
```
2. 计算矩阵的逆,将其乘以原矩阵,验证得到单位矩阵。然后尝试奇异矩阵并观察会发生什么。
```python
import jax.numpy as jnp
A = jnp.array([[1.0, 2.0],
[3.0, 4.0]])
A_inv = jnp.linalg.inv(A)
print(f"A * A_inv:\n{A @ A_inv}")
```
+139
View File
@@ -0,0 +1,139 @@
# 矩阵类型
*特殊的矩阵结构能够解锁计算捷径和数学保证。本文涵盖单位矩阵、对角矩阵、对称矩阵、三角矩阵、正交矩阵、正定矩阵、稀疏矩阵和随机矩阵,这些类型出现在协方差估计、图算法、正则化和马尔可夫链中。*
- 并非所有矩阵都一样。不同的结构赋予矩阵特殊的性质,使它们计算更快、更易于推理,或两者兼得。以下是你最常遇到的类型。
- **方阵**的行数和列数相同($n \times n$)。大多数有趣的性质(行列式、特征值、逆)只适用于方阵。
- **单位矩阵** $I$ 是一个对角线为 1、其余为 0 的方阵。它是"什么都不做"的变换:$AI = IA = A$ 对任何兼容的矩阵 $A$。
```math
I = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}
```
- **零矩阵** $O$ 的所有元素都为零。它将每个向量映射到零向量,破坏所有信息。
- **对角矩阵**除主对角线外全为零。将向量乘以对角矩阵只是独立地缩放每个分量,非常高效。
```math
D = \begin{bmatrix} 3 & 0 \\ 0 & 7 \end{bmatrix}
```
- **对称矩阵**等于其转置:$A = A^T$,意味着 $A_{ij} = A_{ji}$。对称矩阵有一个特殊性质:它们的特征向量总是相互垂直。协方差矩阵总是对称的。
```math
S = \begin{bmatrix} 3 & -1 \\ -1 & 6 \end{bmatrix}
```
- **三角矩阵**在对角线的一侧全为零。**下三角**在上方全为零,**上三角**在下方全为零。它们对于通过前向或回代高效求解方程组至关重要。
```math
L = \begin{bmatrix} 2 & 0 & 0 \\ 1 & 3 & 0 \\ -1 & 2 & 4 \end{bmatrix} \qquad U = \begin{bmatrix} 5 & -1 & 2 \\ 0 & 1 & 3 \\ 0 & 0 & -2 \end{bmatrix}
```
- 三角矩阵的行列式就是其对角线元素的乘积。
- **正交矩阵**具有转置等于逆的性质:$Q^TQ = QQ^T = I$。
- 这意味着你只需转置就能"撤销"变换,计算成本很低。其列是标准正交的(单位长度且相互垂直)。
- **稀疏矩阵**的大多数元素为零,而**稠密矩阵**的大多数元素非零。
![稀疏 vs 稠密:点表示非零元素](../images/sparse_dense.svg)
- 在实践中,许多现实世界的矩阵是极其稀疏的。
- 一个拥有百万用户的社交网络可以表示为一个 $10^6 \times 10^6$ 的矩阵,但每个人只连接到少数其他人,所以几乎所有元素都是零。
![一个小型社交网络及其邻接矩阵:大多数元素为零](../images/social_network_matrix.svg)
- **置换矩阵**是通过重排单位矩阵的行得到的。乘以它会打乱向量的元素。每行每列恰好有一个 1,其余为 0。
- 例如,下面的矩阵将元素 3 移到位置 1,元素 1 移到位置 2,元素 2 移到位置 3:
```math
P = \begin{bmatrix} 0 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix}
```
- **托普利茨矩阵**沿每条对角线(左上到右下)具有相同的值。注意每条对角线是如何恒定的:
```math
T = \begin{bmatrix} a & b & c \\ d & a & b \\ e & d & a \end{bmatrix}
```
- 这种结构出现在信号处理和卷积中,因为将固定滤波器滑过信号等价于乘以托普利茨矩阵。
- **循环矩阵**是一种特殊的托普利茨矩阵,其中每一行是上一行的循环移位。当一行到达末尾时,它会绕回:
```math
C = \begin{bmatrix} 1 & 3 & 2 \\ 2 & 1 & 3 \\ 3 & 2 & 1 \end{bmatrix}
```
- 循环矩阵与离散傅里叶变换(DFT)密切相关,并且是循环卷积如何工作的核心。
- **埃尔米特矩阵**是对称矩阵在复数域中的等价形式:$A = A^\ast$(其中 $A^\ast$ 是共轭转置)。
- 对于实值矩阵,埃尔米特矩阵和对称矩阵是一回事。你会在量子计算和信号处理中遇到它们。
- **酉矩阵**是正交矩阵在复数域中的等价形式:$U^\ast U = UU^\ast = I$。正如正交矩阵在实空间中保持长度,酉矩阵在复空间中保持长度。
- **幂等矩阵**满足 $A^2 = A$。应用变换两次等同于应用一次,这使得它成为一个**投影**。一旦你投影了,再次投影不会改变任何东西。
- **幂零矩阵**满足对某个幂次 $k$ 有 $A^k = O$(零矩阵)。应用变换足够多次后,所有东西都坍缩为零。例如:
```math
\begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix}^2 = \begin{bmatrix} 0 & 0 \\ 0 & 0 \end{bmatrix}
```
- **布尔矩阵**(或二元矩阵)只包含 0 和 1。它表示是/否关系。例如,在一个有 3 个节点的图中,**邻接矩阵**记录哪些节点相连:
```math
B = \begin{bmatrix} 0 & 1 & 1 \\ 1 & 0 & 0 \\ 1 & 0 & 0 \end{bmatrix}
```
- 这里,节点 1 连接到节点 2 和 3,但节点 2 和 3 之间没有连接。
- **范德蒙矩阵**由一组值的连续幂次构成。给定值 $x_1, x_2, x_3$
```math
V = \begin{bmatrix} 1 & x_1 & x_1^2 \\ 1 & x_2 & x_2^2 \\ 1 & x_3 & x_3^2 \end{bmatrix}
```
- 这种结构出现在多项式插值中:找到通过给定点集的唯一多项式。
- **海森堡矩阵**是"几乎"三角的,在第一次次对角线以下全为零:
```math
H = \begin{bmatrix} 4 & 2 & 1 \\ 3 & 5 & -1 \\ 0 & 1 & 6 \end{bmatrix}
```
- 它是有效计算特征值的有用中间形式。先将矩阵化为海森堡形式可以使迭代算法收敛更快。
## 编程练习(使用 CoLab 或 notebook
1. 创建一个正交矩阵(旋转矩阵),乘以其转置,验证得到单位矩阵。尝试不同的角度。
```python
import jax.numpy as jnp
theta = jnp.pi / 4
Q = jnp.array([[jnp.cos(theta), -jnp.sin(theta)],
[jnp.sin(theta), jnp.cos(theta)]])
print(f"Q @ Q.T:\n{Q @ Q.T}")
print(f"Determinant: {jnp.linalg.det(Q):.2f}")
```
2. 创建一个对称矩阵并验证它等于其转置。然后计算其特征值并检查特征向量是否垂直。
```python
import jax.numpy as jnp
S = jnp.array([[4.0, 2.0],
[2.0, 3.0]])
print(f"Symmetric: {jnp.allclose(S, S.T)}")
eigenvalues, eigenvectors = jnp.linalg.eigh(S)
print(f"Eigenvalues: {eigenvalues}")
print(f"Dot product of eigenvectors: {jnp.dot(eigenvectors[:, 0], eigenvectors[:, 1]):.6f}")
```
+146
View File
@@ -0,0 +1,146 @@
# 矩阵运算
*矩阵运算是深度学习的计算引擎。本文涵盖矩阵加法、标量乘法、矩阵-向量积、矩阵乘法、逐元素运算、Kronecker积和广播——支撑每一次前向传播和梯度更新的运算。*
- 矩阵可以像向量一样进行加法和缩放。
- 加法要求两个矩阵维度相同,然后逐元素相加:
```math
\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 6 & 8 \\ 10 & 12 \end{bmatrix}
```
- 标量乘法将每个元素乘以标量:
```math
3 \times \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} = \begin{bmatrix} 3 & 6 \\ 9 & 12 \end{bmatrix}
```
- 矩阵能做的最简单的事情是乘以一个向量。**矩阵-向量乘法** $A\mathbf{x}$ 使用 $\mathbf{x}$ 的分量作为权重来组合 $A$ 的列:
```math
\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \begin{bmatrix} 5 \\ 6 \end{bmatrix} = 5 \begin{bmatrix} 1 \\ 3 \end{bmatrix} + 6 \begin{bmatrix} 2 \\ 4 \end{bmatrix} = \begin{bmatrix} 17 \\ 39 \end{bmatrix}
```
- 这是机器学习中的核心运算。每个神经网络层都计算 $A\mathbf{x} + \mathbf{b}$:矩阵乘以输入向量,再加上偏置。
- 一般情况是**矩阵乘法**。给定 $A$$m \times n$)和 $B$$n \times p$),乘积 $C = AB$ 是一个 $m \times p$ 矩阵,每个元素都是一个点积:
$$C_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj}$$
- 结果中的每个条目都是 $A$ 的一行与 $B$ 的一列的点积。内部维度必须匹配($n$),结果取外部维度($m \times p$)。
- 另一种理解方式:结果的每一列都是 $A$ 的列的**加权和**,其中权重来自 $B$ 的对应列。
- 如果 $B$ 的某一列为 $[2, 3]^T$,则结果列就是 $2 \times (\text{A的第1列}) + 3 \times (\text{A的第2列})$。
- 一个有用的特例:矩阵与其转置相乘总是得到一个方阵。$AA^T$ 是 $m \times m$$A^TA$ 是 $n \times n$
```math
\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix} = \begin{bmatrix} 14 & 32 \\ 32 & 77 \end{bmatrix}
```
- 矩阵乘法有重要的运算规则:
- **不满足交换律**:通常 $AB \neq BA$。顺序很重要。
- **满足结合律**$(AB)C = A(BC)$。你可以任意分组乘法。
- **满足分配律**$A(B + C) = AB + AC$。
- **单位矩阵**$AI = IA = A$。
- **Hadamard积**(逐元素乘积)将两个相同大小的矩阵逐项相乘,记作 $A \odot B$
```math
\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \odot \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 5 & 12 \\ 21 & 32 \end{bmatrix}
```
- 与标准矩阵乘法不同,Hadamard积满足交换律($A \odot B = B \odot A$),且要求两个矩阵维度相同。它在机器学习中广泛用于门控机制:通过与一个取值在0到1之间的掩码逐元素相乘,控制每个条目"通过"多少。
- 两个向量 $\mathbf{u}$ 和 $\mathbf{v}$ 的**外积**产生一个矩阵:$\mathbf{u}\mathbf{v}^T$。每个条目是 $\mathbf{u}$ 的一个元素与 $\mathbf{v}$ 的一个元素的乘积:
```math
\begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} \begin{bmatrix} 4 & 5 \end{bmatrix} = \begin{bmatrix} 4 & 5 \\ 8 & 10 \\ 12 & 15 \end{bmatrix}
```
- 结果总是秩为1,因为每一行都是 $\mathbf{v}^T$ 的缩放版本。任何矩阵都可以写成秩-1外积之和,这正是SVD所做的事情(见分解章节)。
- 矩阵乘法的计算开销很大。两个 $n \times n$ 矩阵相乘需要 $O(n^3)$ 次运算。对于一个 $1000 \times 1000$ 的矩阵,那就是十亿次乘法。
- 当矩阵是**稀疏的**(大部分为零)时,朴素的乘法会浪费时间乘以零。**压缩稀疏行(CSR)**格式只存储非零元素及其位置:
- **值**:按行顺序排列的非零条目
- **列索引**:每个值属于哪一列
- **行偏移**:每一行在值列表中的起始位置
- 例如,矩阵:
```math
A = \begin{bmatrix} 5 & 0 & 0 & 2 \\ 0 & 0 & 3 & 0 \\ 0 & 0 & 0 & -1 \end{bmatrix}
```
- 存储为:values = [5, 2, 3, -1], columns = [0, 3, 2, 3], row offsets = [0, 2, 3, 4]。这跳过了所有零,使稀疏运算快得多。
- 矩阵的一个核心用途是求解**线性方程组**。方程组 $A\mathbf{x} = \mathbf{b}$ 问的是:"什么向量 $\mathbf{x}$ 被 $A$ 变换后,会得到 $\mathbf{b}$"
- 例如,假设你在买水果。苹果每个 $x_1$ 元,香蕉每个 $x_2$ 元。已知2个苹果和1个香蕉共5元,1个苹果和3个香蕉共10元。用矩阵形式表示:
```math
\begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} = \begin{bmatrix} 5 \\ 10 \end{bmatrix}
```
- 矩阵逐行乘以向量(每一行与 $[x_1, x_2]^T$ 点积)得到两个方程:
$$2x_1 + 1x_2 = 5 \qquad \text{(第1行)} \qquad \qquad x_1 + 3x_2 = 10 \qquad \text{(第2行)}$$
- 从第1行得 $x_2 = 5 - 2x_1$。代入第2行:$x_1 + 3(5 - 2x_1) = 10$,解得 $x_1 = 1$,则 $x_2 = 3$。苹果每个1元,香蕉每个3元。
- 验证——结果正确:
```math
\begin{bmatrix} 2 & 1 \\ 1 & 3 \end{bmatrix} \begin{bmatrix} 1 \\ 3 \end{bmatrix} = \begin{bmatrix} 2 + 3 \\ 1 + 9 \end{bmatrix} = \begin{bmatrix} 5 \\ 10 \end{bmatrix}
```
- 如果 $A$ 有逆矩阵,解就是简单的 $\mathbf{x} = A^{-1}\mathbf{b}$。但直接计算逆矩阵代价高昂且数值不稳定。实践中我们使用分解方法。
- 并非所有矩阵都是方阵,也不是所有方阵都可逆。**伪逆** $A^+$ 将逆推广到任意矩阵。它总是存在,并提供"尽可能好的"逆:
$$A^+ = (A^TA)^{-1}A^T$$
- 当 $A$ 是下三角矩阵时,通过**前向代入**求解 $L\mathbf{x} = \mathbf{b}$ 很容易:先解出 $x_1$,然后用它求出 $x_2$,依此类推。
- 当 $A$ 是上三角矩阵时,通过**回代**求解 $U\mathbf{x} = \mathbf{b}$:先解出最后一个变量,然后向上求解。
- 这就是为什么将矩阵分解为三角因子(如分解章节所述)如此有用——它将一个难题转化为两个简单问题。
## 编程练习(使用CoLab或Jupyter Notebook
1. 将两个矩阵相乘并验证维度。然后交换顺序,观察结果如何变化(或者,如果维度不匹配,运算失败)。
```python
import jax.numpy as jnp
A = jnp.array([[1.0, 2.0],
[3.0, 4.0]])
B = jnp.array([[5.0, 6.0],
[7.0, 8.0]])
print(f"A @ B:\n{A @ B}")
print(f"B @ A:\n{B @ A}")
print(f"Equal: {jnp.allclose(A @ B, B @ A)}")
```
2. 求解线性方程组 $A\mathbf{x} = \mathbf{b}$,并通过回代乘法验证解。尝试改变 $\mathbf{b}$,观察解如何变化。
```python
import jax.numpy as jnp
A = jnp.array([[2.0, 1.0],
[5.0, 3.0]])
b = jnp.array([4.0, 7.0])
x = jnp.linalg.solve(A, b)
print(f"Solution x: {x}")
print(f"A @ x: {A @ x}")
```
@@ -0,0 +1,163 @@
# 线性变换
*每个矩阵乘法都是一个线性变换——一个在保持线性性质的同时重塑、旋转或投影向量的函数。本文涵盖旋转、反射、缩放、剪切、投影、映射的核与像,以及神经网络层如何串联这些变换。*
- **线性变换**(或线性映射)是一个接收向量并产生另一个向量的函数,同时保持加法和缩放性质。如果 $T$ 是线性的,则:
- $T(\mathbf{u} + \mathbf{v}) = T(\mathbf{u}) + T(\mathbf{v})$
- $T(c\mathbf{u}) = cT(\mathbf{u})$
- 每个线性变换都可以表示为矩阵乘法。矩阵*就是*变换本身。当你用一个矩阵乘以一个向量时,就是在对它施加一个线性变换。
- 可以把一个 $2 \times 2$ 矩阵想象成一个机器:它接收二维向量,输出新的二维向量。矩阵的列告诉你标准基向量 $\hat{\mathbf{i}}$ 和 $\hat{\mathbf{j}}$ 经过变换后到了哪里。其余一切都由线性性质导出。
![矩阵的列显示了基向量落在何处](../images/basis_transform.svg)
- 例如,如果
```math
A = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}
```
那么 $\hat{\mathbf{i}} = [1, 0]^T$ 落在 $[2, 1]^T$(第1列),$\hat{\mathbf{j}} = [0, 1]^T$ 落在 $[1, 2]^T$(第2列)。其他所有向量都是这两个向量的组合,因此其输出自动遵循。
- 将两个矩阵相乘可以理解为依次施加两个变换。如果 $B$ 将向量从一个空间变换,然后 $A$ 变换结果,那么 $AB$ 按顺序完成这两个操作。在游戏引擎中,先旋转角色再向前移动,与先移动再旋转,结果完全不同——这就是矩阵乘法不满足交换律的原因。
- **旋转**将向量绕一定角度 $\theta$ 转动而不改变其长度。向量大小不变,只是指向新的方向。
![旋转保持长度不变但改变方向](../images/rotation.svg)
- 二维中的旋转矩阵为:
```math
R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}
```
- 当 $\theta = 90°$ 时:
```math
R = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix}
```
因此 $[1, 0]^T$ 变成 $[0, 1]^T$。原来指向右侧的向量现在指向上方。旋转矩阵是正交的,且行列式始终为1。当你在手机上旋转照片时,就是对每个像素坐标应用这个矩阵。
- 在三维中,每个坐标轴都有对应的旋转矩阵。机械臂的每个关节绕特定轴旋转,每个关节就是一个旋转矩阵。绕z轴旋转看起来像是嵌入三维的二维情况:
```math
R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}
```
- **缩放**沿每个坐标轴独立地拉伸或压缩向量:
```math
S(s_x, s_y) = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix}
```
![缩放沿每个轴以不同因子拉伸](../images/scaling.svg)
- $S(2, 1.5)$ 将x分量加倍,y分量乘以1.5。沿某轴缩放 $-1$ 会翻转该分量。对角矩阵总是缩放变换。当你将图片缩小到50%时,就是对每个像素坐标应用 $S(0.5, 0.5)$。
- **反射**像镜子一样将向量翻转到某个轴或直线的另一侧。沿x轴的反射保持x分量不变,取反y分量:
```math
\text{Ref}_x = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}
```
![沿x轴反射翻转y分量](../images/reflection.svg)
- 例如,$[3, 2]^T$ 变成 $[3, -2]^T$。当你的手机水平翻转自拍照使文字正确显示时,就是在应用反射矩阵。沿直线 $y = x$ 的反射交换两个分量:
```math
\text{Ref}_{y=x} = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}
```
- 反射矩阵的行列式为 $-1$,表明它们翻转了方向。
- 旋转和反射都是**刚性变换**:它们保持距离和角度不变。表示这些变换的矩阵是正交矩阵,这就是为什么正交矩阵的行列式总是 $+1$(旋转)或 $-1$(反射)。
- **剪切**沿一个坐标轴按另一坐标轴的比例倾斜向量。水平剪切因子 $k$:
```math
\text{Sh}_x(k) = \begin{bmatrix} 1 & k \\ 0 & 1 \end{bmatrix}
```
![剪切使顶部侧向滑动而底部保持不动](../images/shearing.svg)
- 每个点水平滑动 $k$ 倍于其高度的距离。当 $k = 0.5$ 时,高度为2的点向右移动1。最下面一行保持不动,最上面一行滑动最多。这就是斜体文字的工作原理:正立的字母被剪切,从而向右倾斜。
- 以上所有变换(旋转、缩放、反射、剪切)都是**线性**变换。它们保持原点固定,并保持直线为直线。但**平移**(将所有点按固定量移动)呢?
- 平移*不是*线性变换,因为它移动了原点。如果将每个点向右移动3,零向量会移动到 $[3, 0]^T$,从而破坏了线性性质。为了处理平移,我们使用**仿射变换**,它将线性变换与平移结合起来:
$$\mathbf{y} = A\mathbf{x} + \mathbf{t}$$
- 为了将其表示为单个矩阵乘法,我们使用**齐次坐标**:为每个向量添加一个额外的1,并使用一个 $(n+1) \times (n+1)$ 的矩阵:
```math
\begin{bmatrix} A & \mathbf{t} \\ \mathbf{0}^T & 1 \end{bmatrix} \begin{bmatrix} \mathbf{x} \\ 1 \end{bmatrix} = \begin{bmatrix} A\mathbf{x} + \mathbf{t} \\ 1 \end{bmatrix}
```
- 仿射变换保持直线和平行性,但不一定保持角度或长度。电子游戏中的每个物体都使用仿射变换来定位:旋转它、缩放它,然后放置到正确的位置——所有这些都编码在一个矩阵中。
- **退化变换**(奇异矩阵)将空间坍缩到更低维度。
- 例如,矩阵
```math
\begin{bmatrix} 1 & 2 \\ 2 & 4 \end{bmatrix}
```
将每个二维向量映射到一条直线上,因为两列指向同一方向。行列式为零,信息丢失,且该变换不可逆。
- 将彩色图像(每个像素有3个值:红、绿、蓝)转换为灰度图(每个像素1个值)就是退化变换:颜色信息永久丢失。
- 在机器学习中,线性变换是神经网络的核心。数据被表示为矩阵(向量的堆叠,这些向量代表对象的特征——人、飞机、文本、图像……任何东西!)
- 每一层应用一个矩阵乘法(线性变换),详细内容在其他章节中提供,我们需要解释如何组织这些数据并恰当地引出神经网络。
- 然而,当今最常用的技术几乎完全是将数据通过一系列线性变换传递,我们称之为**Transformer**。
- Gemini、ChatGPT、Claude、Qwen、DeepSeek以及当今世界上性能最好的AI,都是Transformer
## 编程练习(使用CoLab或Jupyter Notebook
1. 对向量应用旋转矩阵,并绘制原始向量和旋转后的向量。尝试不同的角度。
```python
import jax.numpy as jnp
import matplotlib.pyplot as plt
theta = jnp.pi / 3
R = jnp.array([[jnp.cos(theta), -jnp.sin(theta)],
[jnp.sin(theta), jnp.cos(theta)]])
v = jnp.array([1.0, 0.0])
v_rot = R @ v
plt.figure(figsize=(5, 5))
plt.quiver(0, 0, v[0], v[1], angles='xy', scale_units='xy', scale=1, color='red', label='original')
plt.quiver(0, 0, v_rot[0], v_rot[1], angles='xy', scale_units='xy', scale=1, color='blue', label='rotated')
plt.xlim(-1.5, 1.5); plt.ylim(-1.5, 1.5)
plt.grid(True); plt.legend(); plt.gca().set_aspect('equal')
plt.show()
```
2. 对构成正方形的一组点应用剪切变换,并可视化变形后的形状。
```python
import jax.numpy as jnp
import matplotlib.pyplot as plt
square = jnp.array([[0,0],[1,0],[1,1],[0,1],[0,0]]).T
k = 0.5
shear = jnp.array([[1, k],
[0, 1]])
sheared = shear @ square
plt.figure(figsize=(6, 4))
plt.plot(square[0], square[1], 'r-o', label='original')
plt.plot(sheared[0], sheared[1], 'b-o', label='sheared')
plt.grid(True); plt.legend(); plt.gca().set_aspect('equal')
plt.show()
```
+211
View File
@@ -0,0 +1,211 @@
# 矩阵分解
*矩阵分解将复杂矩阵拆分为更简单的因子,用于求解方程组、计算逆矩阵和数据压缩。本文涵盖高斯消元、LU、QR、Cholesky、特征分解和SVD——这些算法是PCA、推荐系统和机器学习数值稳定性的基石。*
- 矩阵分解(或因子分解)将一个矩阵拆分成更容易处理的更简单的部分。可以把它类比为因数分解:$12 = 3 \times 4$ 比单独的12更容易理解。
- 我们分解矩阵是为了更快地求解方程组、稳定地计算逆矩阵、寻找特征值、压缩数据以及理解变换的几何结构。
- 最基本的技术是**高斯消元**(行化简)。思路很简单:给定方程组 $A\mathbf{x} = \mathbf{b}$,使用三种允许的操作简化 $A$,直到答案显而易见。
- 这些操作是:交换两行、将一行乘以非零标量、或将一行的倍数加到另一行上。
- 例如,要消除主元下方的第一列,从下面的行中减去第1行的倍数:
```math
\begin{bmatrix} 2 & 1 & 5 \\ 4 & 3 & 7 \\ 6 & 5 & 9 \end{bmatrix} \xrightarrow{R_2 - 2R_1} \begin{bmatrix} 2 & 1 & 5 \\ 0 & 1 & -3 \\ 6 & 5 & 9 \end{bmatrix} \xrightarrow{R_3 - 3R_1} \begin{bmatrix} 2 & 1 & 5 \\ 0 & 1 & -3 \\ 0 & 2 & -6 \end{bmatrix}
```
- 目标是**行阶梯形(REF)**:每个主元(每行第一个非零条目)下方全为零,且每个主元在其上方主元的右侧。矩阵呈现阶梯形状。
![高斯消元:行操作产生三角形形式,然后从下往上求解](../images/gaussian_elimination.svg)
- 进一步得到**简化行阶梯形(RREF)**,使每个主元为1且是该列中唯一的非零条目。每个矩阵有唯一的RREF。
- 一旦转换为三角形形式,我们通过**回代**求解:最下面一行直接给出最后一个变量,然后向上求解。
- 这是所有其他分解方法所建立的基础,分解的目标就是将矩阵简化为三角形形式,从而可以通过回代求解变量。
- **LU分解**将高斯消元形式化,将方阵分解为 $A = LU$(或通过行交换得到 $A = PLU$),其中 $L$ 是下三角矩阵,$U$ 是上三角矩阵。
![LU分解:将一个困难的矩阵拆分为两个简单的三角矩阵](../images/lu_decomposition.svg)
- 求解 $A\mathbf{x} = \mathbf{b}$:先通过前向代入(从上到下)求解 $L\mathbf{y} = \mathbf{b}$,然后通过回代(从下到上)求解 $U\mathbf{x} = \mathbf{y}$。两次简单的三角求解代替了一次困难的一般求解。
- 相比原始高斯消元的优势在于可复用。一旦得到 $L$ 和 $U$,就可以对许多不同的 $\mathbf{b}$ 向量求解,而无需重新进行分解。
- 如果你需要用1000个不同的右端项求解同一个方程组(这在模拟中很常见),只需分解一次然后重复使用。
- 当矩阵是对称正定矩阵时(如协方差矩阵),我们可以做得更好。
- **Cholesky分解**将其分解为 $A = LL^T$,其中 $L$ 是下三角矩阵。例如:
```math
\begin{bmatrix} 4 & 2 \\ 2 & 5 \end{bmatrix} = \begin{bmatrix} 2 & 0 \\ 1 & 2 \end{bmatrix} \begin{bmatrix} 2 & 1 \\ 0 & 2 \end{bmatrix}
```
- 这大约比LU快两倍,并且保证数值稳定。可以将其视为矩阵的"平方根"。
- 如果分解失败(平方根下出现负值),则该矩阵不是正定的。因此Cholesky分解也可以作为正定性的检验方法。
- 方阵 $A$ 的**特征向量**是特殊方向,该变换在这些方向上只进行拉伸或压缩,而不旋转。**特征值**是缩放因子:
$$A\mathbf{x} = \lambda\mathbf{x}$$
![特征向量保持在同一直线上(仅被缩放),普通向量被旋转](../images/eigenvector.svg)
- 大多数向量在乘以矩阵时方向会改变。但特征向量是特殊的:输出方向与输入方向相同,仅被 $\lambda$ 缩放。如果 $\lambda = 2$,特征向量长度加倍。如果 $\lambda = -1$,它翻转方向。如果 $\lambda = 0$,它被压缩为零。
- 例如,对于:
```math
A = \begin{bmatrix} 3 & 1 \\ 0 & 2 \end{bmatrix}
```
向量 $[1, 0]^T$ 是特征向量,$\lambda = 3$,因为 $A[1, 0]^T = [3, 0]^T = 3[1, 0]^T$。
- 求特征值需要解**特征多项式** $\det(A - \lambda I) = 0$。根即为特征值。然后将每个 $\lambda$ 代回 $(A - \lambda I)\mathbf{x} = \mathbf{0}$ 中,求出对应的特征向量。
- 关键性质:
- $A$ 的迹等于其特征值之和。
- $A$ 的行列式等于其特征值之积。
- 对称矩阵的特征向量互相垂直,特征值为实数。
- 正定矩阵的所有特征值为正。
- 协方差矩阵(我们将在统计学中遇到)总是半正定的。
- 通过特征多项式计算特征值对于大型矩阵来说是不切实际的。相反,使用迭代方法:
- **幂迭代**:反复乘以 $A$ 并归一化。收敛到主特征向量(最大特征值)。简单但只能找到一个特征对。
- **QR算法**:最常用的方法。使用QR分解反复分解和重组矩阵,直到矩阵收敛到三角形形式,对角线上的元素即为所有特征值。
- **反迭代**:寻找最接近给定目标值的特征向量。当你大致知道想要哪个特征值时很有用。
- 对于大型稀疏矩阵,**Arnoldi**和**Lanczos**迭代利用稀疏性提高效率。
- 如果方阵有一组完整的线性无关的特征向量,它可以被**对角化**:$A = PDP^{-1}$,其中 $D$ 是以特征值为对角元的对角矩阵,$P$ 的列是特征向量。
- 这有什么用?对角矩阵非常容易处理。需要计算 $A^{100}$?不用将 $A$ 自乘100次,计算 $PD^{100}P^{-1}$ 即可——而对角矩阵的幂只需独立地对每个对角元求幂。这将一个昂贵的运算变成了廉价运算。
- **特征基**是完全由特征向量构成的基。在这个基下,矩阵变成对角矩阵,变换仅仅是沿每个特征向量方向的独立缩放。这就像是找到了变换的自然坐标系。
- **QR分解**将任意矩阵 $A$ 分解为 $A = QR$,其中 $Q$ 是正交矩阵(其列是标准正交的),$R$ 是上三角矩阵。可以理解为将"方向"信息($Q$)与"缩放和混合"信息($R$)分开。
- **Gram-Schmidt过程**逐列构建 $Q$。取 $A$ 的第一列并归一化。取第二列,减去其在第一列上的投影(使其垂直),再归一化。对每一列重复此过程。结果是一组标准正交向量。
- QR分解是QR算法求特征值背后的引擎。它也直接用于求解最小二乘问题:当 $A\mathbf{x} = \mathbf{b}$ 没有精确解(方程多于未知数)时,QR找到最佳近似解。
- **SVD**(奇异值分解)是最通用、也可以说是最重要的分解。每个矩阵(任意形状、任意秩)都有SVD:$A = U\Sigma V^T$
- $V^T$$n \times n$,正交):旋转输入
- $\Sigma$$m \times n$,对角):沿正交坐标轴缩放(奇异值,非负,递减排列)
- $U$$m \times m$,正交):旋转输出
![SVD:任何变换 = 旋转,然后缩放,再旋转](../images/svd.svg)
- 几何上,SVD表明每个线性变换,无论多么复杂,都只是一个旋转、一个沿坐标轴的拉伸、再一个旋转的组合。一个圆变成了一个椭圆。
- 奇异值($\sigma_1 \geq \sigma_2 \geq \ldots$)揭示了每个方向的"重要性"。大的奇异值对应最重要的方向。$A$ 的秩等于非零奇异值的个数。
- **低秩近似**:只保留最大的 $k$ 个奇异值,将其他置零,就得到了 $A$ 的最佳秩-$k$ 近似。这就是图像压缩的原理:一张 $1000 \times 1000$ 的图像可能只需要 $k = 50$ 个奇异值就能看起来几乎一模一样,压缩了20倍。
- SVD也提供了伪逆:$A^+ = V\Sigma^+U^T$,其中 $\Sigma^+$ 是对非零奇异值取倒数。
- 特征分解只对方阵有效,而SVD对任意矩阵都有效。这是它的关键优势。
- **PCA**(主成分分析)使用特征分解(或SVD)进行降维。
- 想象一个数据集,每个样本有100个特征(堆叠成矩阵的100维向量)。其中许多特征是相关的、冗余的。
- PCA找到数据实际变化的那些方向,让你只保留重要的部分。
![PCA找到数据中方差最大的方向](../images/pca.svg)
- 第一主成分(PC1)是方差最大的方向。
- 第二主成分(PC2)捕获剩余部分的最大方差,且与第一主成分垂直。
- 如果大部分方差只集中在少数几个方向上,你可以将数据投影到这些维度上,丢弃其余部分,损失极小。
- 步骤:
- 标准化数据(减去均值,除以标准差),使所有特征贡献平等
- 计算协方差矩阵
- 求其特征值和特征向量
- 选择 $k$ 个最大特征值对应的特征向量(即主成分)
- 将数据投影到这些主成分上
- 标准化至关重要:如果不做标准化,用公里测量的特征会主导用厘米测量的特征,而不论其实际重要性如何。
- 在实践中,PCA用于可视化(将高维数据投影到2D或3D)、降噪(丢弃主要是噪声的低方差方向),以及通过减少输入特征数量来加速机器学习模型。
- **核PCA**将PCA扩展到非线性关系。它通过核函数将数据映射到更高维空间,在那里结构变得线性,然后应用标准PCA并投影回来。
- **Schur分解**将方阵分解为 $A = QTQ^\ast$,其中 $Q$ 是酉矩阵,$T$ 是上三角矩阵。每个方阵都有Schur分解,即使它不能被对角化。
- **非负矩阵分解(NMF)** 将一个矩阵分解为两个非负矩阵:$A \approx WH$,其中 $W$ 和 $H$ 的所有条目都 $\geq 0$。与可能产生负条目的SVD不同,NMF只做加法,从不做减法。这使得各部分可解释:在主题建模中,$W$ 给出每个文档的主题权重,$H$ 给出每个主题的词权重,全部非负,这与我们对文档"包含多少某个主题"的思考方式相符。
- **谱定理**指出,对称(或Hermitian)矩阵总可以用正交(或酉)矩阵对角化。它们的特征值总是实数,特征向量总是正交的。这是PCA的理论基础。
## 编程练习(使用CoLab或Jupyter Notebook
1. 计算对称矩阵的特征值和特征向量。验证特征向量互相垂直,并从特征分解重建矩阵。
```python
import jax.numpy as jnp
A = jnp.array([[4.0, 2.0],
[2.0, 3.0]])
eigenvalues, eigenvectors = jnp.linalg.eigh(A)
print(f"Eigenvalues: {eigenvalues}")
print(f"Eigenvectors orthogonal: {jnp.dot(eigenvectors[:,0], eigenvectors[:,1]):.6f}")
# Reconstruct: A = P D P^T
D = jnp.diag(eigenvalues)
A_reconstructed = eigenvectors @ D @ eigenvectors.T
print(f"Reconstruction matches: {jnp.allclose(A, A_reconstructed)}")
```
2. 实现幂迭代求最大特征值,以及反迭代求最小特征值。与 `jnp.linalg.eigh` 比较。然后尝试自己实现QR算法。
```python
import jax.numpy as jnp
A = jnp.array([[4.0, 2.0],
[2.0, 3.0]])
# Power iteration: finds the LARGEST eigenvalue
v = jnp.array([1.0, 0.0])
for _ in range(20):
v = A @ v
v = v / jnp.linalg.norm(v)
print(f"Largest eigenvalue: {v @ A @ v:.4f}")
# Inverse iteration: multiply by A^{-1} instead of A, finds the SMALLEST eigenvalue
v = jnp.array([1.0, 0.0])
for _ in range(20):
v = jnp.linalg.solve(A, v)
v = v / jnp.linalg.norm(v)
print(f"Smallest eigenvalue: {1.0 / (v @ jnp.linalg.solve(A, v)):.4f}")
print(f"jnp.linalg.eigh: {jnp.linalg.eigh(A)[0]}")
```
3. 计算矩阵的SVD,然后仅使用前k个奇异值重建矩阵,观察近似质量随k的变化。
```python
import jax.numpy as jnp
A = jnp.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0]])
U, S, Vt = jnp.linalg.svd(A)
for k in [1, 2, 3]:
approx = U[:, :k] @ jnp.diag(S[:k]) @ Vt[:k, :]
error = jnp.linalg.norm(A - approx)
print(f"k={k}, reconstruction error: {error:.4f}")
```