2  线性代数

原则:每一个符号都解释,每一步推导都显式写出,几何直觉与代数公式并行。

2.1 数学对象体系

我们先认识三种”数表”对象——标量、向量、矩阵,以及它们的运算;随后抽象出它们共同赖以存在的母体向量空间,再以此为地基依次展开线性映射、行列式、特征值与分解。

\[ \text{标量} \to \text{向量} \to \text{矩阵} \quad \text{维度:} 0,\; 1,\; 2 \]

张量 \(\mathcal{T}\) 是矩阵的高维推广,作为延伸放在 notebook 最后;本笔记主线只到矩阵为止。

2.1.1 标量 Scalar

\[ s \in \mathbb{R} \]

就是一个单独的数。如温度 \(T=3.2\),学习率 \(\eta = 0.001\)

  • 表示:小写斜体 \(s, t, \alpha, \beta\)
  • 所属集合\(\mathbb{R}\)(实数集)

2.1.2 向量 Vector

\[ \mathbf{x} = \begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{bmatrix} \in \mathbb{R}^n \]

\(n\) 个标量排成一列

  • 表示:小写粗体 \(\mathbf{x}, \mathbf{y}, \mathbf{v}\),或箭头 \(\vec{x}\)
  • \(\mathbb{R}^n\) = \(n\) 维实数空间
  • \(x_i\) = 向量的第 \(i\)分量/元素

2.1.2.1 几何直觉

维度 几何意义
\(\mathbb{R}^1\) 数轴上的一个点 \([3]\)
\(\mathbb{R}^2\) 平面上的一个点/箭头 \([3, 4]^T\)
\(\mathbb{R}^3\) 空间中的一个点/箭头 \([1, 2, 3]^T\)
\(\mathbb{R}^{768}\) 768维空间中的一个点 BERT 的词向量

2.1.2.2 行向量 vs 列向量

\[ \text{列向量:} \mathbf{x} = \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} \quad \text{行向量:} \mathbf{x}^T = \begin{bmatrix} x_1 & x_2 & x_3 \end{bmatrix} \]

默认:向量都是列向量。行向量用转置 \(\mathbf{x}^T\) 表示。

2.1.2.3 代码表示:向量的数组表示与打印

练习:声明向量 \(\mathbf{x} = [1.0, 2.0, 3.0]^T\),分别以列格式和行格式打印。

#include <stdio.h>

// 用 C 数组表示向量
// 列向量 x = [1.0, 2.0, 3.0]^T
int main() {
    double x[] = {1.0, 2.0, 3.0};
    int n = sizeof(x) / sizeof(x[0]);

    printf("Vector x (column):\n");
    for (int i = 0; i < n; i++)
        printf("  x[%d] = %.1f\n", i, x[i]);

    printf("\nVector x^T (row):\n  [");
    for (int i = 0; i < n; i++)
        printf("%.1f%s", x[i], i < n-1 ? ", " : "]\n");
    return 0;
}
Vector x (column):
  x[0] = 1.0
  x[1] = 2.0
  x[2] = 3.0

Vector x^T (row):
  [1.0, 2.0, 3.0]

2.1.3 矩阵 Matrix

\[ A = \begin{bmatrix} a_{11} & a_{12} & \cdots & a_{1n} \\ a_{21} & a_{22} & \cdots & a_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ a_{m1} & a_{m2} & \cdots & a_{mn} \end{bmatrix} \in \mathbb{R}^{m \times n} \]

\(m\)\(n\) 列的数表

  • 表示:大写 \(A, B, W\)
  • \(a_{ij}\) = 第 \(i\) 行第 \(j\) 列的元素
  • \(A \in \mathbb{R}^{m \times n}\)\(m\)\(n\) 列的实数矩阵

2.1.3.1 等价理解

视角 理解
列向量的集合 \(A = [\mathbf{a}_1 \vert \mathbf{a}_2 \vert \cdots \vert \mathbf{a}_n]\),每列是一个 \(\mathbb{R}^m\) 中的向量
行向量的集合 \(A\) 每行是一个 \(\mathbb{R}^n\) 中的向量
线性变换(预告) \(A\)\(\mathbb{R}^n\) 中的向量映射成 \(\mathbb{R}^m\) 中的向量(详见”线性变换”节)

2.1.3.2 代码表示:矩阵的行主序存储与打印

练习:编写 print_matrix(name, A, m, n),以行主序一维数组存储并打印 \(2 \times 3\) 矩阵。

#include <stdio.h>

// 打印 m×n 矩阵
void print_matrix(const char* name, double* A, int m, int n) {
    printf("%s (%dx%d):\n", name, m, n);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++)
            printf("%8.2f", A[i * n + j]);
        printf("\n");
    }
}

int main() {
    // A = [[1, 2, 3], [4, 5, 6]]  (2×3)
    double A[] = {1, 2, 3, 4, 5, 6};
    print_matrix("A", A, 2, 3);
    return 0;
}
A (2x3):
    1.00    2.00    3.00
    4.00    5.00    6.00

2.1.4 常用基本矩阵:单位、零、对角

后续几乎每个公式都会用到三种特殊矩阵,先在此一次性定义清楚。

2.1.4.1 单位矩阵 \(I\)

\[ I_n = \begin{bmatrix} 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & 1 \end{bmatrix}, \qquad (I_n)_{ij} = \begin{cases} 1, & i=j \\ 0, & i\ne j \end{cases} \]

对角线全为 1、其余全为 0 的方阵。

  • 它是矩阵世界里的”1”:对任意矩阵 \(A\)\(AI = IA = A\),正如 \(1\cdot a = a\)
  • 逆矩阵的定义就建立在它之上:\(A^{-1}A = AA^{-1} = I\)
  • 特征方程 \((A-\lambda I)\mathbf{v}=\mathbf{0}\)、矩阵指数 \(e^{A}=I+A+\tfrac{A^2}{2!}+\cdots\) 里的 \(I\) 都是它。

2.1.4.2 零矩阵 \(0\)

所有元素都是 0 的矩阵,矩阵世界里的”0”:\(A + 0 = A\)\(A\cdot 0 = 0\)。零向量是它只有一列的特例(向量空间中的零元 \(\mathbf{0}\) 见”向量空间”节);零空间 \(\ker(A)=\{\mathbf{x}\mid A\mathbf{x}=\mathbf{0}\}\) 里的 \(\mathbf{0}\) 也是它。

2.1.4.3 对角矩阵 \(\operatorname{diag}(\cdot)\)

\[ D = \operatorname{diag}(d_1, d_2, \ldots, d_n) = \begin{bmatrix} d_1 & & & \\ & d_2 & & \\ & & \ddots & \\ & & & d_n \end{bmatrix} \]

只有主对角线可能非零、其余全为 0 的方阵(空白处为 0)。

  • 它的作用是”沿坐标轴纯缩放”:用 \(D\) 乘一个向量,就是把各分量分别放大 \(d_1, d_2, \ldots, d_n\) 倍,不混合分量。
  • 后文的特征分解 \(A=Q\Lambda Q^{-1}\) 中的 \(\Lambda\)、SVD 中的 \(\Sigma\) 都是对角阵;它们的对角线正是特征值 / 奇异值——“对角阵 + 换基”是理解一切矩阵分解的统一视角。

2.1.5 张量 Tensor

本节是主线之外的延伸。线性代数第一轮只研究到矩阵(2 阶);张量严格说属于多重线性代数,但深度学习里处处可见,故在此补上。

\[ \mathcal{T} \in \mathbb{R}^{d_1 \times d_2 \times \cdots \times d_k} \]

多维数组,是矩阵的高维推广——把”标量 → 向量 → 矩阵”的维度链条继续往下推:

0阶 标量 \(s\)
1阶 向量 \(\mathbf{x} \in \mathbb{R}^n\)
2阶 矩阵 \(A \in \mathbb{R}^{m \times n}\)
3阶 3D张量 \(\mathcal{T} \in \mathbb{R}^{B \times n \times d}\)(batch × seq_len × dim)
4阶 4D张量 \(\mathcal{T} \in \mathbb{R}^{B \times C \times H \times W}\)(图像 batch)

2.2 核心运算

2.2.1 向量加向量

\[ \mathbf{x} + \mathbf{y} = \begin{bmatrix} x_1 + y_1 \\ x_2 + y_2 \\ \vdots \\ x_n + y_n \end{bmatrix} \]

几何:平行四边形法则 / 三角形法则。

     y
    / 
   /  x+y
  /_____
 x

要求:两个向量维度必须相同。

2.2.1.1 代码表示:向量加向量

练习:实现 vec_add(z, x, y, n),逐元素计算 \(\mathbf{z} = \mathbf{x} + \mathbf{y}\),验证 \([1,2,3] + [4,5,6] = [5,7,9]\)

#include <stdio.h>

// 向量加法: z = x + y
void vec_add(double* z, const double* x, const double* y, int n) {
    for (int i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}

int main() {
    double x[] = {1.0, 2.0, 3.0};
    double y[] = {4.0, 5.0, 6.0};
    double z[3];

    vec_add(z, x, y, 3);

    printf("x      = [%.0f, %.0f, %.0f]\n", x[0], x[1], x[2]);
    printf("y      = [%.0f, %.0f, %.0f]\n", y[0], y[1], y[2]);
    printf("x + y  = [%.0f, %.0f, %.0f]\n", z[0], z[1], z[2]);
    return 0;
}
x      = [1, 2, 3]
y      = [4, 5, 6]
x + y  = [5, 7, 9]

2.2.2 向量乘标量

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

几何

  • \(\alpha \gt 1\):沿原方向拉伸
  • \(0 \lt \alpha \lt 1\):沿原方向缩短
  • \(\alpha \lt 0\):反方向

2.2.2.1 代码表示:标量乘法

练习:实现 scalar_mult(y, alpha, x, n),计算 \(\mathbf{y} = \alpha \mathbf{x}\)。测试 \(\alpha = 2.0\)\(\alpha = -0.5\)

#include <stdio.h>

// 标量乘法: y = alpha * x
void scalar_mult(double* y, double alpha, const double* x, int n) {
    for (int i = 0; i < n; i++)
        y[i] = alpha * x[i];
}

int main() {
    double x[] = {1.0, 2.0, 3.0};
    double y[3];

    scalar_mult(y, 2.0, x, 3);
    printf("x        = [%.0f, %.0f, %.0f]\n", x[0], x[1], x[2]);
    printf("2.0 * x  = [%.0f, %.0f, %.0f]\n", y[0], y[1], y[2]);

    scalar_mult(y, -0.5, x, 3);
    printf("-0.5 * x = [%.1f, %.1f, %.1f]\n", y[0], y[1], y[2]);
    return 0;
}
x        = [1, 2, 3]
2.0 * x  = [2, 4, 6]
-0.5 * x = [-0.5, -1.0, -1.5]

2.2.3 向量内积(点积 / Dot Product)

\[ \mathbf{x} \cdot \mathbf{y} = \mathbf{x}^T \mathbf{y} = \sum_{i=1}^{n} x_i y_i \]

展开写

\[ \begin{bmatrix} x_1 & x_2 & x_3 \end{bmatrix} \begin{bmatrix} y_1 \\ y_2 \\ y_3 \end{bmatrix} = x_1 y_1 + x_2 y_2 + x_3 y_3 \]

行向量 × 列向量 = 标量

几何意义

\[ \mathbf{x} \cdot \mathbf{y} = \|\mathbf{x}\| \|\mathbf{y}\| \cos\theta \]

条件 含义
\(\mathbf{x}^T \mathbf{y} \gt 0\) 夹角 \(\lt 90°\),大致同向
\(\mathbf{x}^T \mathbf{y} = 0\) 正交(垂直)
\(\mathbf{x}^T \mathbf{y} \lt 0\) 夹角 \(\gt 90°\),大致反向

上述 \(\cos\theta\) 在任意维度都成立。任意两个非零向量 \(\mathbf{x}, \mathbf{y} \in \mathbb{R}^n\) 必定张成一个 2 维平面(它们的 span),\(\theta\) 就是该平面内两向量的夹角,定义与 2D/3D 完全一致。\(\cos\theta\) 衡量的始终是”两向量有多对齐”,与空间维度无关。4 维及以上无法直观画出,但数学上是同一个定义。

在深度学习中的角色:Self-Attention 的核心 \(QK^T\) 就是批量内积,衡量每对 token 的相似度!

2.2.3.1 代码表示:点积、夹角与正交

练习:实现 dot(x, y, n)norm2(x, n),计算 \(\mathbf{x} \cdot \mathbf{y}\)\(\cos\theta\)\(\theta\)(度),其中 \(\mathbf{x} = [1,2,3]\)\(\mathbf{y} = [4,5,6]\)。额外验证 \([1,0] \cdot [0,1] = 0\)(正交)。

#include <stdio.h>
#include <math.h>

// 点积: x^T y
double dot(const double* x, const double* y, int n) {
    double s = 0.0;
    for (int i = 0; i < n; i++)
        s += x[i] * y[i];
    return s;
}

// L2 范数
double norm2(const double* x, int n) {
    return sqrt(dot(x, x, n));
}

int main() {
    double x[] = {1.0, 2.0, 3.0};
    double y[] = {4.0, 5.0, 6.0};

    double d = dot(x, y, 3);
    double cos_theta = d / (norm2(x, 3) * norm2(y, 3));
    double theta = acos(cos_theta) * 180.0 / 3.14159265358979323846;

    printf("x · y        = %.2f\n", d);
    printf("||x||        = %.4f\n", norm2(x, 3));
    printf("||y||        = %.4f\n", norm2(y, 3));
    printf("cos(theta)   = %.4f\n", cos_theta);
    printf("theta        = %.2f degrees\n", theta);

    // 正交示例
    double a[] = {1.0, 0.0};
    double b[] = {0.0, 1.0};
    printf("\n[1,0]·[0,1] = %.1f (正交)\n", dot(a, b, 2));
    return 0;
}
x · y        = 32.00
||x||        = 3.7417
||y||        = 8.7750
cos(theta)   = 0.9746
theta        = 12.93 degrees

[1,0]·[0,1] = 0.0 (正交)

2.2.4 矩阵-向量乘法

\[ A\mathbf{x} = \mathbf{y}, \quad A \in \mathbb{R}^{m \times n}, \mathbf{x} \in \mathbb{R}^n, \mathbf{y} \in \mathbb{R}^m \]

以3行2列(m=3,n=2)矩阵与2维向量相乘为例:

\[ \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ a_{31} & a_{32} \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} = \begin{bmatrix} a_{11}x_1 + a_{12}x_2 \\ a_{21}x_1 + a_{22}x_2 \\ a_{31}x_1 + a_{32}x_2 \end{bmatrix} \]

两种理解方式

视角 公式 含义
行视角 \(y_i = \mathbf{r}_i^T \mathbf{x}\) \(y_i\)\(A\) 的第 \(i\) 行与 \(\mathbf{x}\) 的内积
列视角 \(A\mathbf{x} = x_1 \mathbf{a}_1 + x_2 \mathbf{a}_2 + \cdots + x_n \mathbf{a}_n\) 输出是列向量的线性组合

列视角是最深刻的理解:矩阵乘法 = 用输入向量的分量作为权重,对矩阵的列做加权求和。

:全连接层 \(y = Wx\),输出就是 \(W\) 的列向量的线性组合。\(W\) 的每一列可以理解为一个”特征模板”。

2.2.4.1 代码表示:矩阵-向量乘法

练习:实现 mat_vec(y, A, x, m, n) 计算 \(\mathbf{y} = A\mathbf{x}\)。用 \(A_{3\times 2}\)\(\mathbf{x} = [1, -1]^T\) 验证列视角:\(\mathbf{y} = 1 \cdot \mathbf{a}_1 + (-1) \cdot \mathbf{a}_2\)

#include <stdio.h>

// 矩阵-向量乘法: y = A * x
// A: m×n (row-major), x: n×1, y: m×1
void mat_vec(double* y, const double* A, const double* x, int m, int n) {
    for (int i = 0; i < m; i++) {
        y[i] = 0.0;
        for (int j = 0; j < n; j++)
            y[i] += A[i * n + j] * x[j];  // 行视角: y_i = row_i^T * x
    }
}

void print_vec(const char* name, const double* v, int n) {
    printf("%s = [", name);
    for (int i = 0; i < n; i++)
        printf("%.1f%s", v[i], i < n-1 ? ", " : "]\n");
}

int main() {
    // A: 3×2
    double A[] = {1, 2,
                  3, 4,
                  5, 6};
    double x[] = {1.0, -1.0};  // 2×1
    double y[3];               // 3×1

    mat_vec(y, A, x, 3, 2);

    // 列视角验证: y = x[0]*col0 + x[1]*col1
    // col0 = [1,3,5]^T, col1 = [2,4,6]^T
    // y = 1*[1,3,5] + (-1)*[2,4,6] = [-1, -1, -1]
    print_vec("x", x, 2);
    printf("A * x = [%.1f, %.1f, %.1f]\n", y[0], y[1], y[2]);
    printf("\nColumn view: 1*[1,3,5] + (-1)*[2,4,6] = [-1,-1,-1]\n");
    return 0;
}
x = [1.0, -1.0]
A * x = [-1.0, -1.0, -1.0]

Column view: 1*[1,3,5] + (-1)*[2,4,6] = [-1,-1,-1]

2.2.5 矩阵-矩阵乘法

\[ C = AB, \quad A \in \mathbb{R}^{m \times k}, B \in \mathbb{R}^{k \times n}, C \in \mathbb{R}^{m \times n} \]

\[ c_{ij} = \sum_{l=1}^{k} a_{il} b_{lj} \]

四种等价理解

视角 公式 直觉
元素 \(c_{ij} = \mathbf{a}_{i\cdot}^T \mathbf{b}_{\cdot j}\) \(C\) 的每个元素是 \(A\) 的行和 \(B\) 的列的内积
\(\mathbf{c}_j = A \mathbf{b}_j\) \(C\) 的每列是 \(A\)\(B\) 的对应列
\(\mathbf{r}_i^C = \mathbf{r}_i^A B\) \(C\) 的每行是 \(A\) 的对应行乘 \(B\)
外积和 \(C = \sum_{l=1}^k \mathbf{a}_{\cdot l} \mathbf{b}_{l \cdot}\) \(C\)\(k\) 个秩1矩阵之和

外积(outer product):列向量 \(\mathbf{u}\in\mathbb{R}^m\) 乘行向量 \(\mathbf{v}^T\in\mathbb{R}^{1\times n}\),得到 \(m\times n\) 的矩阵 \(\mathbf{u}\mathbf{v}^T\)(与内积 \(\mathbf{u}^T\mathbf{v}=\text{标量}\) 正好相反)。外积一定是秩1矩阵(见”秩”节);上表”外积和”视角即把 \(AB\) 拆成 \(k\) 个外积之和。

关键规则

  • 内维必须匹配:\(A_{m \times k} B_{k \times n}\),中间的 \(k\) 必须相同
  • 一般不满足交换律\(AB \neq BA\)
  • 满足结合律:\((AB)C = A(BC)\)
  • 满足分配律:\(A(B+C) = AB + AC\)

举例

\(A_{2\times 3}\)\(B_{3\times 2}\),内维 \(k=3\),结果 \(2\times 2\)):

\[ \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \begin{bmatrix} 7 & 8 \\ 9 & 10 \\ 11 & 12 \end{bmatrix} = \begin{bmatrix} 1\cdot7+2\cdot9+3\cdot11 & 1\cdot8+2\cdot10+3\cdot12 \\ 4\cdot7+5\cdot9+6\cdot11 & 4\cdot8+5\cdot10+6\cdot12 \end{bmatrix} = \begin{bmatrix} 58 & 64 \\ 139 & 154 \end{bmatrix} \]

反过来

\(B_{3\times 2}\)\(A_{2\times 3}\),内维 \(k=2\),结果 \(3\times 3\)):

\[ \begin{bmatrix} 7 & 8 \\ 9 & 10 \\ 11 & 12 \end{bmatrix} \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} = \begin{bmatrix} 7\cdot1+8\cdot4 & 7\cdot2+8\cdot5 & 7\cdot3+8\cdot6 \\ 9\cdot1+10\cdot4 & 9\cdot2+10\cdot5 & 9\cdot3+10\cdot6 \\ 11\cdot1+12\cdot4 & 11\cdot2+12\cdot5 & 11\cdot3+12\cdot6 \end{bmatrix} = \begin{bmatrix} 39 & 54 & 69 \\ 49 & 68 & 87 \\ 59 & 82 & 105 \end{bmatrix} \]

\(AB\)\(2\times 2\)\(BA\)\(3\times 3\)——形状都不同,更别说数值了,所以 \(AB \neq BA\)

在深度学习中的角色nn.Linear 的前向传播 \(Y = XW^T + b\)

2.2.5.1 代码表示:矩阵-矩阵乘法

练习:实现 mat_mul(C, A, B, m, k, n) 计算 \(C = AB\)。用 \(A_{2\times 3}\)\(B_{3\times 2}\) 验证,再计算 \(BA\) 确认 \(AB \neq BA\)

#include <stdio.h>

// 矩阵乘法: C = A * B
// A: m×k, B: k×n, C: m×n (row-major)
void mat_mul(double* C, const double* A, const double* B, int m, int k, int n) {
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++) {
            C[i * n + j] = 0.0;
            for (int l = 0; l < k; l++)
                C[i * n + j] += A[i * k + l] * B[l * n + j];
        }
}

void print_matrix(const char* name, const double* M, int m, int n) {
    printf("%s (%dx%d):\n", name, m, n);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++)
            printf("%8.2f", M[i * n + j]);
        printf("\n");
    }
}

int main() {
    // A: 2×3, B: 3×2 → C: 2×2
    double A[] = {1, 2, 3,
                  4, 5, 6};
    double B[] = {7,  8,
                  9, 10,
                 11, 12};
    double C[4];

    mat_mul(C, A, B, 2, 3, 2);

    print_matrix("A", A, 2, 3);
    print_matrix("B", B, 3, 2);
    print_matrix("C = A*B", C, 2, 2);

    // 验证 AB != BA (即使维度允许)
    double D[9];  // B(3×2) * A(2×3) → 3×3
    mat_mul(D, B, A, 3, 2, 3);
    print_matrix("B*A (≠ A*B!)", D, 3, 3);
    return 0;
}
A (2x3):
    1.00    2.00    3.00
    4.00    5.00    6.00
B (3x2):
    7.00    8.00
    9.00   10.00
   11.00   12.00
C = A*B (2x2):
   58.00   64.00
  139.00  154.00
B*A (≠ A*B!) (3x3):
   39.00   54.00   69.00
   49.00   68.00   87.00
   59.00   82.00  105.00

2.3 转置与对称性

2.3.1 转置 Transpose

\[ (A^T)_{ij} = A_{ji} \]

行变列,列变行。

关键性质

\[ (AB)^T = B^T A^T \quad \text{(翻转顺序!)} \]

2.3.2 对称矩阵

\[ A = A^T \iff a_{ij} = a_{ji} \]

  • 协方差矩阵 \(\Sigma = X^T X\) 是对称的
  • 对称矩阵的特征值都是实数

为什么 \(\Sigma = X^T X\) 对称? 因为 \((X^T X)^T = X^T (X^T)^T = X^T X\),自己等于自己的转置。

2.3.2.1 代码表示:转置与对称性检验

练习:实现 transpose(B, A, m, n)is_symmetric(A, n)。构造数据矩阵 \(X_{4\times 3}\)(4 个样本,3 个特征),计算协方差矩阵 \(\Sigma = X^T X\),验证其为对称矩阵。

#include <stdio.h>

// 转置: B = A^T
void transpose(double* B, const double* A, int m, int n) {
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            B[j * m + i] = A[i * n + j];
}

// 矩阵乘法: C = A * B
void mat_mul(double* C, const double* A, const double* B, int m, int k, int n) {
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++) {
            C[i * n + j] = 0.0;
            for (int l = 0; l < k; l++)
                C[i * n + j] += A[i * k + l] * B[l * n + j];
        }
}

// 检查是否对称
int is_symmetric(const double* A, int n) {
    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            if (A[i * n + j] != A[j * n + i])
                return 0;
    return 1;
}

void print_matrix(const char* name, const double* M, int m, int n) {
    printf("%s (%dx%d):\n", name, m, n);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++)
            printf("%8.2f", M[i * n + j]);
        printf("\n");
    }
}

int main() {
    // 数据矩阵 X: 4个样本 × 3个特征
    double X[] = {1.0, 2.0, 1.0,
                  2.0, 3.0, 1.0,
                  3.0, 4.0, 2.0,
                  4.0, 5.0, 2.0};

    double Xt[12];  // 3×4
    transpose(Xt, X, 4, 3);

    // 协方差矩阵 Σ = X^T X (3×4 × 4×3 → 3×3)
    double Sigma[9];
    mat_mul(Sigma, Xt, X, 3, 4, 3);

    print_matrix("X", X, 4, 3);
    printf("\n");
    print_matrix("Xt", X, 3, 4);
    printf("\n");
    
    print_matrix("Sigma = X^T X", Sigma, 3, 3);
    printf("\n");

    // 验证: (X^T X)^T = X^T X
    printf("Symmetric? %s\n", is_symmetric(Sigma, 3) ? "Yes" : "No");
    
    return 0;
}
X (4x3):
    1.00    2.00    1.00
    2.00    3.00    1.00
    3.00    4.00    2.00
    4.00    5.00    2.00

Xt (3x4):
    1.00    2.00    1.00    2.00
    3.00    1.00    3.00    4.00
    2.00    4.00    5.00    2.00

Sigma = X^T X (3x3):
   30.00   40.00   17.00
   40.00   54.00   23.00
   17.00   23.00   10.00

Symmetric? Yes

2.4 逆矩阵

\[ A^{-1}A = AA^{-1} = I \]

几何意义\(A^{-1}\)\(A\) 的逆变换——如果 \(A\) 旋转了 \(30°\)\(A^{-1}\) 就旋转 \(-30°\)

2.4.1 什么时候可逆?

\[ A \text{ 可逆} \iff \text{rank}(A) = n \iff \det(A) \neq 0 \iff \ker(A) = \{\mathbf{0}\} \]

只有变换没有”丢失信息”(没有把空间压扁),才能逆转。

2.4.2 关键性质

\[ (AB)^{-1} = B^{-1} A^{-1}, \quad (A^T)^{-1} = (A^{-1})^T \]

2.4.2.1 代码表示:2×2 矩阵求逆

练习:实现 \(2\times 2\) 矩阵求逆 inverse2x2。验证 \(AA^{-1} = I\)。测试 \(\det = 0\) 的不可逆情况。

#include <stdio.h>

// 2×2 矩阵求逆
// det = ad - bc
// A^-1 = (1/det) * [[d, -b], [-c, a]]
int inverse2x2(double* inv, const double* A) {
    double det = A[0] * A[3] - A[1] * A[2];
    if (det == 0.0) return 0;  // 不可逆

    double inv_det = 1.0 / det;
    inv[0] =  A[3] * inv_det;
    inv[1] = -A[1] * inv_det;
    inv[2] = -A[2] * inv_det;
    inv[3] =  A[0] * inv_det;
    return 1;
}

int main() {
    double A[] = {1, 2,
                  3, 4};
    double inv[4];

    printf("A = [[1,2],[3,4]]\n");

    if (inverse2x2(inv, A)) {
        printf("A^-1 = [[%.4f, %.4f],\n", inv[0], inv[1]);
        printf("         [%.4f, %.4f]]\n", inv[2], inv[3]);

        // 验证 A * A^-1 = I
        double I00 = A[0]*inv[0] + A[1]*inv[2];
        double I01 = A[0]*inv[1] + A[1]*inv[3];
        double I10 = A[2]*inv[0] + A[3]*inv[2];
        double I11 = A[2]*inv[1] + A[3]*inv[3];
        printf("\nA * A^-1 = [[%.4f, %.4f],\n", I00, I01);
        printf("             [%.4f, %.4f]]  (≈ I)\n", I10, I11);
    }

    // 不可逆的情况
    double B[] = {1, 2,
                  2, 4};  // det = 0
    printf("\nB = [[1,2],[2,4]], det = %.1f\n", B[0]*B[3] - B[1]*B[2]);
    printf("Invertible? %s\n", inverse2x2(inv, B) ? "Yes" : "No (det=0)");
    return 0;
}
A = [[1,2],[3,4]]
A^-1 = [[-2.0000, 1.0000],
         [1.5000, -0.5000]]

A * A^-1 = [[1.0000, 0.0000],
             [0.0000, 1.0000]]  (≈ I)

B = [[1,2],[2,4]], det = 0.0
Invertible? No (det=0)

2.5 向量空间(线性代数的真正地基)

前面我们把向量看成”一列数”、矩阵看成”一张数表”。但线性代数的真正主角不是数表,而是一个抽象的向量空间(vector space)——前面的 \(\mathbb{R}^n\) 只是它最常见的实例。

2.5.1 公理化定义

定义:设 \(V\) 是非空集合,\(\mathbb{R}\) 是实数域。若 \(V\) 上定义了向量加法 \(\mathbf{x}+\mathbf{y}\in V\)标量乘法 \(\alpha\mathbf{x}\in V\),且对任意 \(\mathbf{x},\mathbf{y},\mathbf{z}\in V\)\(\alpha,\beta\in\mathbb{R}\) 满足以下 8 条公理,则称 \(V\)\(\mathbb{R}\) 上的向量空间,其元素称为向量

加法 4 条

公理 名称 公式
1 交换律 \(\mathbf{x}+\mathbf{y}=\mathbf{y}+\mathbf{x}\)
2 结合律 \((\mathbf{x}+\mathbf{y})+\mathbf{z}=\mathbf{x}+(\mathbf{y}+\mathbf{z})\)
3 零元 存在 \(\mathbf{0}\in V\),使 \(\mathbf{x}+\mathbf{0}=\mathbf{x}\)
4 负元 存在 \(-\mathbf{x}\in V\),使 \(\mathbf{x}+(-\mathbf{x})=\mathbf{0}\)

标量乘 4 条

公理 名称 公式
5 标量对向量加法分配 \(\alpha(\mathbf{x}+\mathbf{y})=\alpha\mathbf{x}+\alpha\mathbf{y}\)
6 向量对标量加法分配 \((\alpha+\beta)\mathbf{x}=\alpha\mathbf{x}+\beta\mathbf{x}\)
7 标量乘结合 \(\alpha(\beta\mathbf{x})=(\alpha\beta)\mathbf{x}\)
8 单位元 \(1\mathbf{x}=\mathbf{x}\)

为什么如此抽象? 因为”向量”远不止数列:多项式、函数、矩阵、概率分布都可以是向量,只要满足这 8 条。抽象出向量空间,一个定理就同时适用于 \(\mathbb{R}^n\)、多项式空间、函数空间……这正是数学系强调公理化的用意。\(\mathbb{R}^n\) 按分量逐项加法与标量乘显然满足 8 条,所以前面所有讨论都合法地”活在”向量空间里。

2.5.2 子空间 Subspace

向量空间 \(V\) 的一个子空间 \(W\subseteq V\),是 \(V\) 中对加法和标量乘都封闭的非空子集——它自己也是向量空间。

判定\(W\) 是子空间 \(\iff\) \(\mathbf{0}\in W\),且对任意 \(\mathbf{x},\mathbf{y}\in W\)\(\alpha\in\mathbb{R}\)\(\mathbf{x}+\mathbf{y}\in W\)\(\alpha\mathbf{x}\in W\)

\(\mathbb{R}^3\) 的子空间 几何
\(\{\mathbf{0}\}\) 原点(零维)
过原点的直线 \(L=\{t\mathbf{v}\}\) 一维
过原点的平面 \(\Pi\) 二维
\(\mathbb{R}^3\) 本身 全空间

“过原点”是关键:不过原点的平面(如 \(z=1\))对加法不封闭(\((0,0,1)+(0,0,1)=(0,0,2)\notin\{z=1\}\)),不是子空间。

伏笔:后面”线性变换”节的\(\ker(A)\)\(\operatorname{Im}(A)\) 都将是子空间——子空间正是理解它们的工具。

2.6 线性组合、线性无关、基

2.6.1 线性组合

给定一组向量 \(\mathbf{v}_1, \mathbf{v}_2, \ldots, \mathbf{v}_k\) 和任意标量 \(\alpha_1, \ldots, \alpha_k\),把它们加权相加得到的

\[ \alpha_1 \mathbf{v}_1 + \alpha_2 \mathbf{v}_2 + \cdots + \alpha_k \mathbf{v}_k \]

就称为这组向量的一个线性组合\(\alpha_i\) 称为系数

直觉:把一组向量当作“原料”,通过缩放和相加,能“够到”哪些点?

线性表示。如果某个向量 \(\mathbf{b}\) 恰好能写成上面这种形式,即存在一组系数使得

\[ \mathbf{b} = \alpha_1 \mathbf{v}_1 + \alpha_2 \mathbf{v}_2 + \cdots + \alpha_k \mathbf{v}_k \]

就称 \(\mathbf{b}\) 可由向量组 \(\{\mathbf{v}_1, \ldots, \mathbf{v}_k\}\) 线性表示

矩阵视角:把向量组按列拼成矩阵 \(V = [\mathbf{v}_1\ \mathbf{v}_2\ \cdots\ \mathbf{v}_k]\),系数写成向量 \(\boldsymbol{\alpha}\),线性表示就变成矩阵–向量乘法

\[ \mathbf{b} = V\,\boldsymbol{\alpha} = \begin{bmatrix} \mathbf{v}_1 & \mathbf{v}_2 & \cdots & \mathbf{v}_k \end{bmatrix} \begin{bmatrix} \alpha_1 \\ \alpha_2 \\ \vdots \\ \alpha_k \end{bmatrix} = \alpha_1 \mathbf{v}_1 + \cdots + \alpha_k \mathbf{v}_k \]

这正是前面“三种视角”里的列视角\(V\boldsymbol{\alpha}\) 就是把 \(V\) 的各列按系数加权后相加。于是“\(\mathbf{b}\) 能否被线性表示”等价于问:方程 \(V\boldsymbol{\alpha} = \mathbf{b}\) 是否有解。

2.6.2 线性张成的空间 Span

固定一组向量,让系数取遍所有实数,所有可能的线性组合构成一个集合,称为这组向量的张成空间

\[ \operatorname{span}(\mathbf{v}_1, \ldots, \mathbf{v}_k) = \{\alpha_1 \mathbf{v}_1 + \cdots + \alpha_k \mathbf{v}_k \mid \alpha_i \in \mathbb{R}\} \]

  • 一个向量的 span → 一条过原点的直线;
  • 两个不共线向量的 span → 一个过原点的平面。

于是:“\(\mathbf{b}\) 能被 \(\{\mathbf{v}_1,\ldots,\mathbf{v}_k\}\) 线性表示” \(\iff\)\(\mathbf{b}\) 落在它们的 span 里”。

2.6.3 线性相关与线性无关

线性相关:如果存在不全为零的系数 \(\alpha_1, \ldots, \alpha_k\),使得

\[ \alpha_1 \mathbf{v}_1 + \alpha_2 \mathbf{v}_2 + \cdots + \alpha_k \mathbf{v}_k = \mathbf{0} \]

就称向量组 \(\{\mathbf{v}_1, \ldots, \mathbf{v}_k\}\) 线性相关。反过来,若上式只有\(\alpha_1 = \cdots = \alpha_k = 0\) 时才成立,则称该组线性无关

矩阵视角:写成 \(V\boldsymbol{\alpha} = \mathbf{0}\),这是一个齐次方程组

\[ \begin{bmatrix} \mathbf{v}_1 & \mathbf{v}_2 & \cdots & \mathbf{v}_k \end{bmatrix} \begin{bmatrix} \alpha_1 \\ \alpha_2 \\ \vdots \\ \alpha_k \end{bmatrix} = \mathbf{0} \tag{1.1} \]

  • 线性相关 \(\iff\) 齐次方程组 \((1.1)\) 存在非零解\(\boldsymbol{\alpha} \neq \mathbf{0}\));
  • 线性无关 \(\iff\) 齐次方程组 \((1.1)\) 只有零解

为什么要区分这两者? 线性相关意味着组内有冗余:必有某个向量能被其余向量线性表示,可以“化”掉。例如 \(\mathbf{v}_3 = 2\mathbf{v}_1 - \mathbf{v}_2\) 时,\(\{\mathbf{v}_1, \mathbf{v}_2, \mathbf{v}_3\}\) 必线性相关。反过来也成立:若 \(\mathbf{b}\) 能由 \(\{\mathbf{v}_1, \ldots, \mathbf{v}_k\}\) 线性表示,则把 \(\mathbf{b}\) 加进该组后,新组必定线性相关。

而线性无关意味着组内每个向量都不可替代——这正是“基”要求“没有冗余”的原因。

2.6.4 基 Basis

把上面两件事合起来,就得到。向量空间的一组同时满足:

  1. 线性无关(没有冗余);
  2. 张成整个空间(够得着所有点)。

\(\mathbb{R}^n\) 最熟悉的一组基是标准基

\[ \mathbf{e}_1 = \begin{bmatrix}1\\0\\0\\ \vdots\end{bmatrix},\quad \ldots,\quad \mathbf{e}_n = \begin{bmatrix}0\\0\\ \vdots\\1\end{bmatrix} \]

但基不唯一:任意 \(n\) 个线性无关的 \(n\) 维向量都构成 \(\mathbb{R}^n\) 的一组基。同一个向量,换一组基,坐标就不同——这正是后面“坐标变换”的出发点。

预告:后续会遇到“基”的两个重要特例——“特征值”节里,某些矩阵有一组由特征向量构成的基;“SVD”节里,任何矩阵都关联到一组正交基(奇异向量)。

2.6.5 代码练习

实现 linear_comb2(y, a1, v1, a2, v2, n):用标准基 \(\{e_1, e_2\}\) 和非标准基 \(\{[1,1],[1,-1]\}\) 分别组合出 \([3, 4]\),并构造一组线性相关的向量加以验证。

#include <stdio.h>

// 线性组合: y = a1*v1 + a2*v2
void linear_comb2(double* y, double a1, const double* v1,
                  double a2, const double* v2, int n) {
    for (int i = 0; i < n; i++)
        y[i] = a1 * v1[i] + a2 * v2[i];
}

int main() {
    // R^2 中的两个向量
    double v1[] = {1.0, 0.0};  // e1
    double v2[] = {0.0, 1.0};  // e2
    double y[2];

    // y = 3*e1 + 4*e2 = [3, 4]
    linear_comb2(y, 3.0, v1, 4.0, v2, 2);
    printf("3*e1 + 4*e2 = [%.0f, %.0f]\n", y[0], y[1]);

    // 用非标准基: v1=[1,1], v2=[1,-1]
    double w1[] = {1.0, 1.0};
    double w2[] = {1.0, -1.0};
    linear_comb2(y, 3.5, w1, -0.5, w2, 2);
    printf("3.5*[1,1] + (-0.5)*[1,-1] = [%.1f, %.1f]\n", y[0], y[1]);

    // 检验线性相关: v3 = 2*v1 - v2 → {v1,v2,v3} 线性相关
    double v3[] = {2.0, -1.0};  // = 2*[1,0] - [0,1]
    linear_comb2(y, 2.0, v1, -1.0, v2, 2);
    printf("2*v1 - v2   = [%.0f, %.0f] = v3\n", y[0], y[1]);
    printf("→ v3 可由 v1,v2 线性表出,{v1,v2,v3} 线性相关\n");
    return 0;
}

2.7 线性变换

矩阵不是数表,矩阵是函数(变换)。

\[ T: \mathbb{R}^n \to \mathbb{R}^m, \quad T(\mathbf{x}) = A\mathbf{x} \]

2.7.1 2D 变换

旋转\(R_\theta = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}\)

拉伸\(S = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix}\)

剪切\(H = \begin{bmatrix} 1 & k \\ 0 & 1 \end{bmatrix}\)

投影(到 x 轴):\(P = \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix}\)

任何线性变换都可以分解为:旋转 → 拉伸 → 旋转(这就是 SVD!)

2.7.2 核与像

概念 定义 含义
核/零空间 \(\ker(A) = \{\mathbf{x} \mid A\mathbf{x} = \mathbf{0}\}\) 被变换”消灭”的输入
像/列空间 \(\text{Im}(A) = \{A\mathbf{x} \mid \mathbf{x} \in \mathbb{R}^n\}\) 变换能”够到”的输出

核与像的维度关系——秩-零度定理 \(\dim(\ker A)+\text{rank}(A)=n\)——见下一节”秩”。

2.7.2.1 代码表示:2D 线性变换

练习:对 \(\mathbf{x} = [1, 0]^T\) 依次施加:旋转 \(90°\)、拉伸 \([2x, 3y]\)、投影到 x 轴。再对 \([1,0]\) 做剪切变换。

#include <stdio.h>
#include <math.h>

void print_vec(const char* name, const double* v, int n) {
    printf("%s = [%.4f, %.4f]\n", name, v[0], v[1]);
}

int main() {
    double x[] = {1.0, 0.0};  // 单位向量 e1

    // 旋转 90°
    double theta = 3.14159265358979323846 / 2;
    double R[4] = {cos(theta), -sin(theta),
                   sin(theta),  cos(theta)};
    double y[2];
    y[0] = R[0]*x[0] + R[1]*x[1];
    y[1] = R[2]*x[0] + R[3]*x[1];
    print_vec("Rotate 90° of [1,0]", y, 2);

    // 拉伸 2x
    double S[4] = {2.0, 0.0, 0.0, 3.0};
    double z[2];
    z[0] = S[0]*y[0] + S[1]*y[1];
    z[1] = S[2]*y[0] + S[3]*y[1];
    print_vec("Then scale [2x,3y]", z, 2);

    // 投影到 x 轴
    double P[4] = {1.0, 0.0, 0.0, 0.0};
    double p[2];
    p[0] = P[0]*z[0] + P[1]*z[1];
    p[1] = P[2]*z[0] + P[3]*z[1];
    print_vec("Then project to x-axis", p, 2);

    // 剪切
    double H[4] = {1.0, 0.5, 0.0, 1.0};
    double h[2];
    h[0] = H[0]*x[0] + H[1]*x[1];
    h[1] = H[2]*x[0] + H[3]*x[1];
    print_vec("Shear [1,0]", h, 2);
    return 0;
}
Rotate 90° of [1,0] = [0.0000, 1.0000]
Then scale [2x,3y] = [0.0000, 3.0000]
Then project to x-axis = [0.0000, 0.0000]
Shear [1,0] = [1.0000, 0.0000]

2.8 秩 Rank

\[ \text{rank}(A) = \text{列向量中线性无关的最大个数} = \text{行向量中线性无关的最大个数} \]

情况 含义
\(\text{rank}(A) = \min(m,n)\) 满秩,信息最丰富
\(\text{rank}(A) \lt \min(m,n)\) 低秩,存在冗余/依赖
\(\text{rank}(A) = 1\) 秩1矩阵,可写为外积 \(A = \mathbf{u}\mathbf{v}^T\)

\(\text{rank}(A) = r\) 意味着 \(A\mathbf{x}\) 的所有输出都挤在一个 \(r\) 维子空间里(即像 \(\text{Im}(A)\) 的维度 \(=r\))。

2.8.1 秩-零度定理(Rank-Nullity)

线性变换 \(A: \mathbb{R}^n \to \mathbb{R}^m\) 把输入空间拆成互不重叠的两部分——被”消灭”的核,与”够得着”的像:

\[ \dim(\ker A) \;+\; \text{rank}(A) \;=\; n \]

含义
\(\dim(\ker A)\)(零度 nullity) 输入中被压成 \(\mathbf{0}\) 的方向数
\(\text{rank}(A)=\dim(\text{Im}\,A)\) 输出空间的维度
两者之和 \(=n\) 输入维度守恒:要么被消灭,要么被保留到输出

这就是把上一节”核与像”串起来的核心等式:\(A\) 可逆 \(\iff \ker A=\{\mathbf{0}\}\iff \text{rank}(A)=n\)(见”逆矩阵”节)。

在深度学习中的角色:LoRA 用低秩矩阵 \(AB\)\(r \ll m,n\))近似大权重矩阵。

2.8.1.1 代码表示:秩的计算(高斯消元法)

练习:通过高斯消元法实现 rank(A, m, n),计算满秩矩阵、低秩矩阵(\(\text{rank}=1\))和外积矩阵 \(\mathbf{u}\mathbf{v}^T\) 的秩。

#include <stdio.h>
#include <math.h>

// 通过高斯消元法(行化简)计算矩阵的秩
// 对 A 的副本操作(不修改原矩阵)
int rank(double* A, int m, int n) {
    // 复制矩阵到临时数组
    double tmp[64];
    for (int i = 0; i < m * n; i++) tmp[i] = A[i];

    int r = 0;
    for (int col = 0; col < n && r < m; col++) {
        // 找主元
        int pivot = -1;
        for (int row = r; row < m; row++) {
            if (fabs(tmp[row * n + col]) > 1e-10) {
                pivot = row;
                break;
            }
        }
        if (pivot < 0) continue;

        // 交换行
        if (pivot != r) {
            for (int j = 0; j < n; j++) {
                double t = tmp[r * n + j];
                tmp[r * n + j] = tmp[pivot * n + j];
                tmp[pivot * n + j] = t;
            }
        }

        // 消元
        for (int row = r + 1; row < m; row++) {
            double factor = tmp[row * n + col] / tmp[r * n + col];
            for (int j = col; j < n; j++)
                tmp[row * n + j] -= factor * tmp[r * n + j];
        }
        r++;
    }
    return r;
}

void print_matrix(const char* name, const double* M, int m, int n) {
    printf("%s (%dx%d):\n", name, m, n);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++)
            printf("%8.2f", M[i * n + j]);
        printf("\n");
    }
}

int main() {
    // 满秩矩阵 (rank = 2)
    double A[] = {1, 2,
                  3, 4};
    print_matrix("A", A, 2, 2);
    printf("rank(A) = %d\n\n", rank(A, 2, 2));

    // 低秩矩阵 (rank = 1): 第2行 = 2 * 第1行
    double B[] = {1, 2,
                  2, 4};
    print_matrix("B", B, 2, 2);
    printf("rank(B) = %d (row2 = 2*row1)\n\n", rank(B, 2, 2));

    // 秩1矩阵 = 外积 uv^T
    double u[] = {1, 2, 3};
    double v[] = {2, 1};
    double C[6];  // 3×2
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 2; j++)
            C[i * 2 + j] = u[i] * v[j];
    print_matrix("C = u*v^T (rank-1)", C, 3, 2);
    printf("rank(C) = %d\n", rank(C, 3, 2));
    return 0;
}
A (2x2):
    1.00    2.00
    3.00    4.00
rank(A) = 2

B (2x2):
    1.00    2.00
    2.00    4.00
rank(B) = 1 (row2 = 2*row1)

C = u*v^T (rank-1) (3x2):
    2.00    1.00
    4.00    2.00
    6.00    3.00
rank(C) = 1

2.9 行列式

2.9.1 2×2 的显式公式

\[ \det\begin{bmatrix} a & b \\ c & d \end{bmatrix} = ad - bc \]

2.9.2 几何意义

\[ |\det(A)| = \text{变换后单位立方体的体积} \]

\(\det(A)\) 含义
\(\gt 0\) 体积放大,方向保持
\(\lt 0\) 体积放大,方向翻转
\(= 0\) 体积坍缩为零 → 空间被压扁 → 不可逆

\[ \det(AB) = \det(A) \cdot \det(B) \]

2.9.2.1 代码表示:行列式计算与性质

练习:实现 det2x2。计算旋转、拉伸、投影矩阵的行列式。验证 \(\det(AB) = \det(A) \cdot \det(B)\)

#include <stdio.h>
#include <math.h>

double det2x2(const double* A) {
    return A[0] * A[3] - A[1] * A[2];
}

int main() {
    // 旋转 45° → det = 1(保体积)
    double theta = 3.14159265358979323846 / 4;
    double R[] = {cos(theta), -sin(theta),
                  sin(theta),  cos(theta)};
    printf("Rotation 45°: det = %.6f (≈1, volume preserved)\n", det2x2(R));

    // 拉伸 → det = 面积缩放
    double S[] = {2.0, 0.0, 0.0, 3.0};
    printf("Scale [2x,3y]: det = %.1f (area ×6)\n", det2x2(S));

    // 投影 → det = 0(面积坍缩)
    double P[] = {1.0, 0.0, 0.0, 0.0};
    printf("Project x:    det = %.1f (collapsed)\n", det2x2(P));

    // det(AB) = det(A)*det(B)
    double A[] = {1, 2, 3, 4};
    double B[] = {5, 6, 7, 8};
    double AB[] = {
        A[0]*B[0]+A[1]*B[2], A[0]*B[1]+A[1]*B[3],
        A[2]*B[0]+A[3]*B[2], A[2]*B[1]+A[3]*B[3]
    };
    printf("\ndet(A)=%.1f, det(B)=%.1f, det(A)*det(B)=%.1f\n",
           det2x2(A), det2x2(B), det2x2(A)*det2x2(B));
    printf("det(AB)=%.1f  → equal!\n", det2x2(AB));
    return 0;
}

2.10 范数 (Norm)

2.10.1 词源与历史:从”木工直角尺”到”长度”

“norm” 的来历。 英文 norm ← 法文 norme ← 拉丁文 norma,原义是木工的直角尺(carpenter’s square),引申为”规则、模型、标准”。一把直角尺的作用,就是给出一个衡量”够不够直、够不够方”的标准——这正是”norm”在数学中的灵魂:一把用来度量某数学量”有多大”的标尺

中文”范数”的”范”。 古汉语里”范(範)“意为模型、模子、典范、法则、度量器具。所以”范数”可直译为”用于度量某个数学量的模型/标尺”——与拉丁词源惊人地呼应。

同一个词,数学里至少有三种含义(前两种最常被混淆):

含义 出处 公式 要点
① 代数/数论范数 Gauss (1832),研究高斯整数 \(a+bi\) \(N(a+bi)=a^2+b^2=\lvert a+bi\rvert^2\) 是长度的平方;满足乘性 \(N(zw)=N(z)N(w)\)不要求三角不等式
② 分析/向量范数 Banach (1922),抽象线性空间 \(\lVert x\rVert\),见下方三公理 要求三角不等式——本节主角
③ 黎曼积分中分割的范数 H. J. S. Smith (1875) \(\lVert P\rVert=\max_i(x_i-x_{i-1})\) 指分割的”细度/网格”(mesh),与向量范数无关,只是同名

关键区分:① 高斯”代数范数”是 \(|z|^2\)(数的平方、乘性),② 分析”范数”才是”长度”(带三角不等式)。我们说深度学习里的 \(\lVert\cdot\rVert\),一律指 ②。

向量范数的双竖线记号。 Erhard Schmidt 在 1908 年论文里,把一个无穷维空间中的向量记为 \(A(x)\),定义其长度为一个正量 \(\lVert A\rVert\)(欧氏范数),并称长度为 1 的向量为 normirt(“已规范化”,normalized)。双竖线 \(\lVert\cdot\rVert\) 大概率是为了与标量的绝对值 \(\lvert\cdot\rvert\) 区分开——一个量标量、一个量向量;不致混淆时也常简写。

矩阵范数。 Householder (1964) 曾指出,范数概念是泛函分析的基础,但直到约 1940 年才频繁出现在数值分析与矩阵理论中(Hotelling 1943、Bowker 1947);Wedderburn (1934) 则把这一概念上溯到 1887 年。


2.10.2 直觉:范数 = 向量的”长度/大小”

范数 \(\lVert\cdot\rVert\) 把任意向量 \(\mathbf{x}\) 映射为一个非负实数 \(\lVert\mathbf{x}\rVert \in \mathbb{R}_{\ge 0}\),回答的唯一问题是”这个向量有多长?“——就像用尺子量一段带方向的箭头:

\[ \lVert\cdot\rVert : V \longrightarrow \mathbb{R}_{\ge 0} \]

但并非任意一个”把向量变成数”的函数都叫范数。下面三条公理才是范数的严格定义

2.10.3 官方定义(公理化)

定义:设 \(V\) 是实数域 \(\mathbb{R}\) 上的向量空间,\(\mathbf{0}\) 为零向量。函数 \(\lVert\cdot\rVert : V \to \mathbb{R}_{\ge 0}\) 称为 \(V\) 上的范数(norm),当且仅当对任意 \(\mathbf{x}, \mathbf{y} \in V\) 与任意标量 \(\alpha \in \mathbb{R}\),以下三条公理同时成立:

公理 名称 公式 直觉
1 正定性 positive definiteness \(\lVert\mathbf{x}\rVert \ge 0\),且 \(\lVert\mathbf{x}\rVert=0 \iff \mathbf{x}=\mathbf{0}\) 长度非负;只有零向量长度为 0
2 绝对齐次性 absolute homogeneity \(\lVert\alpha\mathbf{x}\rVert = \lvert\alpha\rvert\,\lVert\mathbf{x}\rVert\) 向量拉伸 \(k\) 倍,长度也变 \(k\)
3 次可加性 / 三角不等式 subadditivity \(\lVert\mathbf{x}+\mathbf{y}\rVert \le \lVert\mathbf{x}\rVert+\lVert\mathbf{y}\rVert\) 两边之和 \(\ge\) 第三边

三条同时满足,\(\lVert\cdot\rVert\) 才有资格叫”范数”;装备范数的空间 \((V, \lVert\cdot\rVert)\) 称为赋范向量空间(normed vector space)

2.10.3.1 严谨性说明

① 非负性 \(\lVert\mathbf{x}\rVert\ge 0\) 其实可由公理 2、3 推出。\(\alpha=-1\),由齐次性 \(\lVert-\mathbf{x}\rVert=\lVert\mathbf{x}\rVert\),且 \(\lVert\mathbf{0}\rVert=\lVert 0\cdot\mathbf{x}\rVert=0\);对 \(\mathbf{0}=\mathbf{x}+(-\mathbf{x})\) 用三角不等式:

\[ 0=\lVert\mathbf{0}\rVert=\lVert\mathbf{x}+(-\mathbf{x})\rVert\le \lVert\mathbf{x}\rVert+\lVert-\mathbf{x}\rVert=2\lVert\mathbf{x}\rVert \;\Longrightarrow\; \lVert\mathbf{x}\rVert\ge 0 \]

② 但 “\(\lVert\mathbf{x}\rVert=0 \Rightarrow \mathbf{x}=\mathbf{0}\)”(确定性)推不出,必须独立要求。 放宽它,就退化成半范数(seminorm)——允许某个非零向量长度也为 0。

半范数例\(s(\mathbf{x})=|x_1|\)(只看第一个分量)。对 \(\mathbf{x}=[0,5]^T\)\(s(\mathbf{x})=0\)\(\mathbf{x}\ne\mathbf{0}\)\(s\) 不是范数,只是半范数。

③ 反向三角不等式(由公理 2、3 推出):

\[ \big|\,\lVert\mathbf{x}\rVert-\lVert\mathbf{y}\rVert\,\big| \le \lVert\mathbf{x}-\mathbf{y}\rVert \]

刻画”两向量长度之差,不超过它们自身的距离”。

2.10.3.2 反例:为什么这三条是底线

\(\mathbf{x}=[x_1,x_2]^T\),令 \(f(\mathbf{x})=\sqrt{|x_1|}\)。它满足非负,但不满足齐次性

\[ f(2\mathbf{x})=\sqrt{|2x_1|}=\sqrt{2}\,\sqrt{|x_1|}\;\ne\;2\sqrt{|x_1|}=2f(\mathbf{x}) \]

向量拉长 2 倍,“长度”却只增 \(\sqrt{2}\) 倍 → \(f\) 不是范数。

2.10.4 范数诱导距离(度量)

范数天然给出两点间的”距离”:

\[ d(\mathbf{x},\mathbf{y}) = \lVert\mathbf{x}-\mathbf{y}\rVert \]

它满足距离三公理(非负、对称、三角不等式),所以每个赋范空间自动是度量空间——这正是范数既能当”长度”又能当”距离”用的根源。

2.10.5 最常见的一族:Lp 范数

\[ \lVert\mathbf{x}\rVert_p = \left( \sum_{i=1}^n |x_i|^p \right)^{1/p}, \qquad p \ge 1 \]

不同的 \(p\) = 不同的”距离观”:

\(p\) 名称 公式 几何意义
\(p=1\) L1 范数 \(\sum \vert x_i \vert\) 曼哈顿距离
\(p=2\) L2 范数(欧几里得) \(\sqrt{\sum x_i^2}\) 直线距离
\(p=\infty\) L∞ 范数 \(\max_i \vert x_i \vert\) 最大分量

单位”范数球” \(\{\mathbf{x} : \lVert\mathbf{x}\rVert_p \le 1\}\) 的形状直观区分三者:L1 是菱形 ◇,L2 是圆 ⬭,L∞ 是方形 ▢

注意:\(0 < p < 1\)\(\lVert\cdot\rVert_p\) 不满足三角不等式,严格来说不是范数(称为 quasi-norm)。

为什么必须 \(p\ge 1\) 公理 3(三角不等式)此时正是 Minkowski 不等式

\[ \lVert\mathbf{x}+\mathbf{y}\rVert_p \le \lVert\mathbf{x}\rVert_p + \lVert\mathbf{y}\rVert_p \]

它对 \(p\ge 1\) 成立,\(0<p<1\) 时反向。Lp 范数”是不是范数”,就卡在这一条上。

L2 范数简写:

\[ \lVert\mathbf{x}\rVert = \lVert\mathbf{x}\rVert_2 = \sqrt{\mathbf{x}^T \mathbf{x}} \]

2.10.6 有限维空间里,所有范数都”等价”

有限维空间 \(\mathbb{R}^n\) 上,任意两个范数 \(\lVert\cdot\rVert_a,\lVert\cdot\rVert_b\)等价:存在常数 \(0<c\le C\) 使得

\[ c\,\lVert\mathbf{x}\rVert_a \le \lVert\mathbf{x}\rVert_b \le C\,\lVert\mathbf{x}\rVert_a,\qquad \forall \mathbf{x}\in\mathbb{R}^n \]

例如把 L∞ 与 L2 互相夹住:\(\lVert\mathbf{x}\rVert_\infty \le \lVert\mathbf{x}\rVert_2 \le \sqrt{n}\,\lVert\mathbf{x}\rVert_\infty\)

意义:有限维下,收敛、连续、有界、单位球紧致这些拓扑性质与具体选哪个范数无关。所以算法里把 L2 换成 L1,不会改变”序列是否收敛”,但会改变优化几何(稀疏性、条件数)——这是 L1/L2 正则效果不同的根源。注意:无穷维空间里这条结论不成立

2.10.7 矩阵范数

矩阵有两种思路来定”范数”:

① 元素型(把矩阵拍平当向量)—— Frobenius 范数

\[ \lVert A\rVert_F = \sqrt{\sum_{i=1}^m \sum_{j=1}^n a_{ij}^2} = \sqrt{\text{tr}(A^T A)} \]

② 算子型(把矩阵当”放大器”,由向量范数诱导)—— 诱导/算子范数

\[ \lVert A\rVert_p = \sup_{\mathbf{x}\ne\mathbf{0}} \frac{\lVert A\mathbf{x}\rVert_p}{\lVert\mathbf{x}\rVert_p} = \max_{\lVert\mathbf{x}\rVert_p=1}\lVert A\mathbf{x}\rVert_p \]

它衡量”矩阵最多能把向量拉长多少倍”。常见取值:

诱导范数 等价刻画
\(\lVert A\rVert_2\)谱范数 \(=\sigma_{\max}(A)\),最大奇异值 ← 直接连到 SVD 分解
\(\lVert A\rVert_1\) \(=\max_j\)(第 \(j\) 列绝对值之和)
\(\lVert A\rVert_\infty\) \(=\max_i\)(第 \(i\) 行绝对值之和)

两条关键性质: - 次可加乘性 \(\lVert AB\rVert \le \lVert A\rVert\,\lVert B\rVert\)(诱导范数必满足;Frobenius 也满足)。 - 谱半径被范数控制 \(\rho(A)=\max_i|\lambda_i| \le \lVert A\rVert\)(对任意诱导范数)。这是幂迭代、收敛性分析的地基。

2.10.8 对偶范数与 Hölder 不等式

给定范数 \(\lVert\cdot\rVert\),它的对偶范数定义为

\[ \lVert\mathbf{x}\rVert_* = \sup_{\mathbf{y}\ne\mathbf{0}} \frac{\mathbf{x}^T\mathbf{y}}{\lVert\mathbf{y}\rVert} = \max_{\lVert\mathbf{y}\rVert\le 1}\mathbf{x}^T\mathbf{y} \]

Lp 的对偶是 Lq,其中 \(\dfrac{1}{p}+\dfrac{1}{q}=1\)\(p,q\ge 1\),互称共轭指数):\(\lVert\cdot\rVert_p^*=\lVert\cdot\rVert_q\)。特别地 L2 自对偶\(p=q=2\)),L1 的对偶是 L∞。

由此得 Hölder 不等式(对偶范数的本质):

\[ \mathbf{x}^T\mathbf{y} \le \lVert\mathbf{x}\rVert\,\lVert\mathbf{y}\rVert_* \quad\Longleftrightarrow\quad \sum_i |x_i y_i| \le \lVert\mathbf{x}\rVert_p\,\lVert\mathbf{y}\rVert_q \]

\(p=q=2\) 的特例就是 Cauchy–Schwarz 不等式 \(\lvert\mathbf{x}^T\mathbf{y}\rvert \le \lVert\mathbf{x}\rVert_2\lVert\mathbf{y}\rVert_2\)——余弦相似度里”夹角”能有意义的数学保证。

2.10.9 在深度学习中的角色

用途 公式 作用
权重衰减 / L2 正则 \(\tfrac{\lambda}{2}\lVert W\rVert_F^2\) 逼权重变小,防过拟合
L1 正则 \(\lambda\lVert W\rVert_1\) 产生稀疏权重(很多归零,可做特征选择)
梯度裁剪 \(\lVert\nabla\rVert>\) 阈值时缩放 防梯度爆炸
余弦相似度 \(\dfrac{\mathbf{x}^T\mathbf{y}}{\lVert\mathbf{x}\rVert\lVert\mathbf{y}\rVert}\) 衡量方向对齐程度
谱归一化(GAN) 约束 \(\lVert W\rVert_2\le 1\) 限制判别器 Lipschitz 常数,稳定训练

L1 vs L2 正则的几何根源:L1 约束的”球”是菱形 ◇,顶点落在坐标轴上 → 优化易在顶点(稀疏解)相切;L2 约束的”球”是圆 ⬭ → 倾向均匀缩小所有权重。这是”为什么 L1 产生稀疏、L2 不产生”的最干净解释。

2.10.9.1 代码表示:L1/L2/L∞ 范数与 Frobenius 范数

练习:实现 norm_l1norm_l2norm_linffrobenius_norm。计算 \(\mathbf{x} = [3, 4]\) 的三种范数和 \(A_{2\times 3}\) 的 Frobenius 范数。

#include <stdio.h>
#include <math.h>

double dot(const double* x, const double* y, int n) {
    double s = 0.0;
    for (int i = 0; i < n; i++) s += x[i] * y[i];
    return s;
}

double norm_l1(const double* x, int n) {
    double s = 0.0;
    for (int i = 0; i < n; i++) s += fabs(x[i]);
    return s;
}

double norm_l2(const double* x, int n) {
    return sqrt(dot(x, x, n));
}

double norm_linf(const double* x, int n) {
    double mx = 0.0;
    for (int i = 0; i < n; i++)
        if (fabs(x[i]) > mx) mx = fabs(x[i]);
    return mx;
}

double frobenius_norm(const double* A, int m, int n) {
    double s = 0.0;
    for (int i = 0; i < m * n; i++) s += A[i] * A[i];
    return sqrt(s);
}

int main() {
    double x[] = {3.0, 4.0};
    printf("x = [3, 4]\n");
    printf("||x||_1   = %.2f  (Manhattan)\n", norm_l1(x, 2));
    printf("||x||_2   = %.2f  (Euclidean)\n", norm_l2(x, 2));
    printf("||x||_inf = %.2f  (Max)\n", norm_linf(x, 2));

    double A[] = {1, 2, 3, 4, 5, 6};  // 2×3
    printf("\nA = [[1,2,3],[4,5,6]]\n");
    printf("||A||_F   = %.4f\n", frobenius_norm(A, 2, 3));
    return 0;
}
x = [3, 4]
||x||_1   = 7.00  (Manhattan)
||x||_2   = 5.00  (Euclidean)
||x||_inf = 4.00  (Max)

A = [[1,2,3],[4,5,6]]
||A||_F   = 9.5394

2.11 迹 Trace

\[ \text{tr}(A) = \sum_{i=1}^n a_{ii} \]

就是把主对角线上的元素加起来。例:

\[ \text{tr}\begin{bmatrix}2&0\\0&3\end{bmatrix}=2+3=5,\qquad \text{tr}\begin{bmatrix}1&2\\3&4\end{bmatrix}=1+4=5 \]

(只看对角线,非对角元素完全不参与。)

它代表什么? 和行列式一样,迹也是方阵的一个”标量不变量”——把整个矩阵压缩成一个数,衡量线性变换”沿各方向总共拉伸了多少”。下一节将看到,迹等于特征值之和\(\text{tr}(A)=\sum_i\lambda_i\)),而行列式是特征值之:迹管”线性总量”,行列式管”乘性总量”(体积缩放)。这也是上一节 Frobenius 范数 \(\|A\|_F^2=\text{tr}(A^TA)\)(矩阵”总能量”)背后的同一件事。

2.11.1 关键性质

\[ \text{tr}(AB) = \text{tr}(BA) \quad \text{(循环性质:尽管 } AB\ne BA \text{,两者的迹却相等)} \]

2.11.1.1 代码表示:迹的计算与性质

练习:实现 trace(A, n)。验证 \(\text{tr}(AB) = \text{tr}(BA)\)\(\|A\|_F = \sqrt{\text{tr}(A^TA)}\)

#include <stdio.h>
#include <math.h>

double trace(const double* A, int n) {
    double s = 0.0;
    for (int i = 0; i < n; i++)
        s += A[i * n + i];
    return s;
}

int main() {
    double A[] = {2, 1,
                  1, 3};
    printf("A = [[2,1],[1,3]]\n");
    printf("tr(A) = %.1f\n\n", trace(A, 2));

    // 验证循环性质: tr(AB) = tr(BA)
    double B[] = {5, 6, 7, 8};
    double AB[] = {A[0]*B[0]+A[1]*B[2], A[0]*B[1]+A[1]*B[3],
                   A[2]*B[0]+A[3]*B[2], A[2]*B[1]+A[3]*B[3]};
    double BA[] = {B[0]*A[0]+B[1]*A[2], B[0]*A[1]+B[1]*A[3],
                   B[2]*A[0]+B[3]*A[2], B[2]*A[1]+B[3]*A[3]};
    printf("tr(AB) = %.1f, tr(BA) = %.1f  (equal even though AB ≠ BA!)\n",
           trace(AB, 2), trace(BA, 2));

    // Frobenius norm: ||A||_F^2 = tr(A^T A)
    double AtA[] = {A[0]*A[0]+A[2]*A[2], A[0]*A[1]+A[2]*A[3],
                    A[0]*A[1]+A[2]*A[3], A[1]*A[1]+A[3]*A[3]};
    double frob = sqrt(trace(AtA, 2));
    printf("\n||A||_F = sqrt(tr(A^T A)) = %.4f\n", frob);
    return 0;
}
A = [[2,1],[1,3]]
tr(A) = 5.0

tr(AB) = 47.0, tr(BA) = 47.0  (equal even though AB ≠ BA!)

||A||_F = sqrt(tr(A^T A)) = 3.8730

2.12 特征值与特征向量

\[ A\mathbf{v} = \lambda \mathbf{v}, \quad \mathbf{v} \neq \mathbf{0} \]

符号 名称 含义
\(\mathbf{v}\) 特征向量 在变换 \(A\) 下方向不变的向量
\(\lambda\) 特征值 沿 \(\mathbf{v}\) 方向的缩放倍数

2.12.1 求法

\((A - \lambda I)\mathbf{v} = \mathbf{0}\) 有非零解 \(\iff \det(A - \lambda I) = 0\)特征方程

2.12.2 特征分解

\[ A = Q \Lambda Q^{-1}, \quad Q = [\mathbf{v}_1 | \cdots | \mathbf{v}_n], \quad \Lambda = \text{diag}(\lambda_1, \ldots, \lambda_n) \]

名词:可对角化。若 \(A\)\(n\) 个线性无关的特征向量(能拼成可逆的 \(Q\)),就称 \(A\) 可对角化,即可写成 \(A=Q\Lambda Q^{-1}\)。并非所有方阵都可对角化:例如剪切矩阵 \(\begin{bmatrix}1&1\\0&1\end{bmatrix}\) 只有一个独立特征方向,拼不出可逆的 \(Q\)

2.12.3 迹与行列式的特征值刻画

在特征基下 \(A\) 化为对角阵 \(\Lambda=\text{diag}(\lambda_1,\ldots,\lambda_n)\),对角线正是各 \(\lambda_i\)——于是两个看似只是”矩阵算术”的量获得了深刻含义:

\[ \text{tr}(A) = \sum_{i=1}^n \lambda_i \quad \text{(迹 = 特征值之和)}, \qquad \det(A) = \prod_{i=1}^n \lambda_i \quad \text{(行列式 = 特征值之积)} \]

这印证了上一节的说法:迹虽只是对角线之和,却等于特征值之和;行列式同理等于特征值之积。也立刻看出 \(A\) 可逆 \(\iff \det(A)\ne0\iff\) 没有 \(\lambda_i=0\)(没有方向被压成零)。

2.12.4 特征值的物理意义(SSM 中的关键!)

\(\dot{\mathbf{h}} = A\mathbf{h}\) 的解:\(\mathbf{h}(t) = e^{At} \mathbf{h}(0)\)

特征值 \(\lambda_i\) \(e^{\lambda_i t}\) 的行为 系统行为
\(\lambda_i \lt 0\)(实部) \(\to 0\) 衰减(遗忘)
\(\lambda_i = 0\) \(= 1\) 保持(完美记忆)
\(\lambda_i \gt 0\)(实部) \(\to \infty\) 爆炸(不稳定)

HiPPO 初始化让 \(A\) 的特征值分布在负实轴上,使得 SSM 系统能维持多尺度的记忆。

2.12.4.1 代码表示:2×2 对称矩阵特征值

练习:实现 \(2\times 2\) 对称矩阵特征值计算 eigen2x2。验证 \(\text{tr}(A) = \lambda_1 + \lambda_2\)\(\det(A) = \lambda_1 \lambda_2\)

#include <stdio.h>
#include <math.h>

// 2×2 对称矩阵的特征值
// det(A - λI) = 0 → λ² - tr(A)λ + det(A) = 0
void eigen2x2(const double* A, double* lambda1, double* lambda2) {
    double tr = A[0] + A[3];      // trace = a + d
    double det = A[0]*A[3] - A[1]*A[2];  // ad - bc
    double disc = sqrt(tr*tr - 4*det);
    *lambda1 = (tr + disc) / 2.0;
    *lambda2 = (tr - disc) / 2.0;
}

int main() {
    // 对称矩阵
    double A[] = {2, 1,
                  1, 3};
    double l1, l2;
    eigen2x2(A, &l1, &l2);

    printf("A = [[2,1],[1,3]]\n");
    printf("eigenvalues: λ1 = %.4f, λ2 = %.4f\n", l1, l2);

    // 验证: tr = λ1 + λ2, det = λ1 * λ2
    double tr = A[0] + A[3];
    double det = A[0]*A[3] - A[1]*A[2];
    printf("tr(A) = %.1f = λ1+λ2 = %.4f\n", tr, l1+l2);
    printf("det(A) = %.1f = λ1*λ2 = %.4f\n", det, l1*l2);

    // 投影矩阵: P = [[1,0],[0,0]] → λ = 1, 0
    double P[] = {1, 0, 0, 0};
    eigen2x2(P, &l1, &l2);
    printf("\nProjection: λ1 = %.1f (kept), λ2 = %.1f (killed)\n", l1, l2);
    return 0;
}
A = [[2,1],[1,3]]
eigenvalues: λ1 = 3.6180, λ2 = 1.3820
tr(A) = 5.0 = λ1+λ2 = 5.0000
det(A) = 5.0 = λ1*λ2 = 5.0000

Projection: λ1 = 1.0 (kept), λ2 = 0.0 (killed)

2.13 正交矩阵

\[ Q^T Q = QQ^T = I \iff Q^{-1} = Q^T \]

名词:标准正交(orthonormal)。一组向量若两两正交(任意两个内积为 0)且每个长度都为 1,称为标准正交。正交矩阵 \(Q\) 的各列(也是各行)恰好构成 \(\mathbb{R}^n\) 的一组标准正交基。

性质 含义
\(\|Q\mathbf{x}\| = \|\mathbf{x}\|\) 保长度(等距变换)
\((Q\mathbf{x})^T(Q\mathbf{y}) = \mathbf{x}^T\mathbf{y}\) 保内积(保角)
\(\det(Q) = \pm 1\) 保体积
\(Q^T = Q^{-1}\) 逆就是转置,计算极快

几何:正交变换 = 旋转(\(\det = +1\))或旋转+镜像(\(\det = -1\)),不拉伸不压缩

2.13.0.1 代码表示:正交矩阵的性质验证

练习:构造 \(60°\) 旋转矩阵 \(Q\),验证 \(Q^TQ = I\)\(\det(Q) = 1\)\(\|Q\mathbf{x}\| = \|\mathbf{x}\|\)

#include <stdio.h>
#include <math.h>

int main() {
    // 旋转矩阵(正交矩阵)
    double theta = 3.14159265358979323846 / 3;  // 60°
    double Q[] = {cos(theta), -sin(theta),
                  sin(theta),  cos(theta)};

    printf("Rotation matrix Q (60°):\n");
    printf("  [[%.4f, %.4f],\n", Q[0], Q[1]);
    printf("   [%.4f, %.4f]]\n\n", Q[2], Q[3]);

    // Q^T Q = I ?
    double QtQ_00 = Q[0]*Q[0]+Q[2]*Q[2];
    double QtQ_01 = Q[0]*Q[1]+Q[2]*Q[3];
    double QtQ_11 = Q[1]*Q[1]+Q[3]*Q[3];
    printf("Q^T*Q = [[%.4f, %.4f],\n", QtQ_00, QtQ_01);
    printf("          [%.4f, %.4f]]  (≈ I)\n\n", QtQ_01, QtQ_11);

    // det(Q) = 1
    double det = Q[0]*Q[3] - Q[1]*Q[2];
    printf("det(Q) = %.4f\n\n", det);

    // 保长度: ||Qx|| = ||x||
    double x[] = {3.0, 4.0};
    double Qx[] = {Q[0]*x[0]+Q[1]*x[1], Q[2]*x[0]+Q[3]*x[1]};
    double norm_x = sqrt(x[0]*x[0]+x[1]*x[1]);
    double norm_Qx = sqrt(Qx[0]*Qx[0]+Qx[1]*Qx[1]);
    printf("||x||  = %.4f\n", norm_x);
    printf("||Qx|| = %.4f  (preserved!)\n", norm_Qx);
    return 0;
}
Rotation matrix Q (60°):
  [[0.5000, -0.8660],
   [0.8660, 0.5000]]

Q^T*Q = [[1.0000, 0.0000],
          [0.0000, 1.0000]]  (≈ I)

det(Q) = 1.0000

||x||  = 5.0000
||Qx|| = 5.0000  (preserved!)

2.14 谱定理:对称矩阵的正交对角化

一般特征分解 \(A=Q\Lambda Q^{-1}\) 中,特征向量矩阵 \(Q\) 不一定正交\(Q^{-1}\ne Q^T\))。但对实对称矩阵,会发生一件极漂亮的事——特征向量可取成两两正交的。

2.14.1 定理(实对称矩阵的谱定理)

定理:设 \(A\in\mathbb{R}^{n\times n}\) 对称(\(A=A^T\)),则

  1. \(A\) 的特征值全是实数
  2. 存在正交矩阵 \(Q\)\(Q^TQ=I\))和对角阵 \(\Lambda\),使

\[ A = Q\Lambda Q^T, \qquad Q^{-1}=Q^T \]

即:对称矩阵可被正交对角化

2.14.2 几何意义

一般矩阵的作用 = 在一组(可能歪斜的)轴上缩放;对称矩阵的作用 = 在一组相互正交的轴上纯缩放,没有”剪切”成分:

\[ A\mathbf{x} = Q\Lambda Q^T\mathbf{x} = \underbrace{Q}_{\text{旋转到正交特征轴}}\; \underbrace{\Lambda}_{\text{各轴独立缩放}}\; \underbrace{Q^T}_{\text{旋转回去}} \]

2.14.3 与一般特征分解对比

一般矩阵 \(A=Q\Lambda Q^{-1}\) 对称矩阵 \(A=Q\Lambda Q^T\)
特征值 可能是复数 全是实数
特征向量 一般不正交 可取成标准正交
\(Q^{-1}\) 需单独求逆 \(=Q^T\),转置即逆

名词:正定。对称矩阵 \(A\) 称为正定,指对任意 \(\mathbf{x}\ne\mathbf{0}\) 都有 \(\mathbf{x}^T A\mathbf{x}>0\);等价于 \(A\)所有特征值 \(>0\)。(半正定则放宽为 \(\ge0\);协方差矩阵 \(X^TX\) 恒半正定。)

2.14.4 为什么重要

  • 协方差矩阵 \(\Sigma=X^TX\) 对称 ⟹ 可正交对角化 ⟹ PCA 的基石(主成分 = 正交特征向量,方差 = 特征值)。
  • 海森矩阵 \(H=\nabla^2 f\) 对称 ⟹ 其特征值符号刻画极值(正定 ⟹ 局部极小)。
  • 这正是下一节 SVD 对任意矩阵都能给出正交基的根源:\(A^TA\) 恒对称,必有正交特征基。

2.15 SVD 分解

任何矩阵 \(A \in \mathbb{R}^{m \times n}\) 都可以分解为:

\[ A = U \Sigma V^T \]

其中:

  • \(U \in \mathbb{R}^{m \times m}\):正交矩阵(左奇异向量),\(U^TU = I\)
  • \(\Sigma \in \mathbb{R}^{m \times n}\):对角矩阵(奇异值 \(\sigma_1 \geq \sigma_2 \geq \cdots \geq 0\)
  • \(V \in \mathbb{R}^{n \times n}\):正交矩阵(右奇异向量),\(V^TV = I\)

2.15.1 几何意义

\[ A = \underbrace{U}_{\text{旋转}} \underbrace{\Sigma}_{\text{拉伸}} \underbrace{V^T}_{\text{旋转}} \]

任何线性变换 = 旋转 → 拉伸 → 旋转

2.15.2 低秩近似(Eckart-Young 定理)

\[ A_r = \sum_{i=1}^{r} \sigma_i \mathbf{u}_i \mathbf{v}_i^T \]

这是秩为 \(r\) 的矩阵中对 \(A\) 的最佳近似。

特征分解 SVD
适用范围 方阵,且可对角化 任何矩阵
正交性 \(Q\) 一般不正交 \(U, V\) 总是正交的
几何 在一组轴上缩放 旋转+缩放+旋转

2.15.2.1 代码表示:2×2 SVD 分解

练习:实现 \(2\times 2\) SVD 分解。对 \(A = [[3,1],[1,3]]\) 求奇异值,验证 \(A \approx U\Sigma V^T\)\(U^TU = I\)

#include <stdio.h>
#include <math.h>

// 2×2 SVD 分解 (用解析公式)
// A = U * Σ * V^T
void svd_2x2(const double* A, double* U, double* S, double* V) {
    // A^T * A
    double ata[4] = {
        A[0]*A[0]+A[2]*A[2], A[0]*A[1]+A[2]*A[3],
        A[0]*A[1]+A[2]*A[3], A[1]*A[1]+A[3]*A[3]
    };

    // 奇异值: σ² = eigenvalues of A^T*A
    double tr = ata[0] + ata[3];
    double det = ata[0]*ata[3] - ata[1]*ata[2];
    double disc = sqrt(tr*tr/4.0 - det);
    double s1sq = tr/2.0 + disc;
    double s2sq = tr/2.0 - disc;
    S[0] = sqrt(s1sq > 0 ? s1sq : 0);
    S[1] = sqrt(s2sq > 0 ? s2sq : 0);

    // V: eigenvectors of A^T*A
    if (fabs(ata[1]) > 1e-10) {
        double v1n = sqrt(ata[1]*ata[1] + (s1sq - ata[0])*(s1sq - ata[0]));
        V[0] = ata[1] / v1n;
        V[2] = (s1sq - ata[0]) / v1n;
        double v2n = sqrt(ata[1]*ata[1] + (s2sq - ata[0])*(s2sq - ata[0]));
        V[1] = ata[1] / v2n;
        V[3] = (s2sq - ata[0]) / v2n;
    } else {
        V[0] = 1; V[1] = 0;
        V[2] = 0; V[3] = 1;
    }

    // U = A * V * Σ^-1
    if (S[0] > 1e-10) {
        U[0] = (A[0]*V[0] + A[1]*V[2]) / S[0];
        U[2] = (A[2]*V[0] + A[3]*V[2]) / S[0];
    }
    if (S[1] > 1e-10) {
        U[1] = (A[0]*V[1] + A[1]*V[3]) / S[1];
        U[3] = (A[2]*V[1] + A[3]*V[3]) / S[1];
    }
}

int main() {
    double A[] = {3, 1,
                  1, 3};
    double U[4], S[2], V[4];

    svd_2x2(A, U, S, V);

    printf("A = [[3,1],[1,3]]\n\n");
    printf("Singular values: σ1=%.4f, σ2=%.4f\n", S[0], S[1]);

    // 验证: A ≈ U * diag(S) * V^T
    double SVt[4] = {S[0]*V[0], S[0]*V[1], S[1]*V[2], S[1]*V[3]};
    double recon[4] = {
        U[0]*SVt[0]+U[1]*SVt[2], U[0]*SVt[1]+U[1]*SVt[3],
        U[2]*SVt[0]+U[3]*SVt[2], U[2]*SVt[1]+U[3]*SVt[3]
    };
    printf("\nReconstruction A ≈ UΣV^T:\n");
    printf("  [[%.4f, %.4f],\n", recon[0], recon[1]);
    printf("   [%.4f, %.4f]]\n", recon[2], recon[3]);

    // 验证正交性: U^T U ≈ I
    printf("\nU^T*U diagonal: %.4f, %.4f (≈1)\n",
           U[0]*U[0]+U[2]*U[2], U[1]*U[1]+U[3]*U[3]);
    return 0;
}
A = [[3,1],[1,3]]

Singular values: σ1=4.0000, σ2=2.0000

Reconstruction A ≈ UΣV^T:
  [[3.0000, 1.0000],
   [1.0000, 3.0000]]

U^T*U diagonal: 1.0000, 1.0000 (≈1)

2.16 矩阵指数

\[ e^{A} = \sum_{k=0}^{\infty} \frac{A^k}{k!} = I + A + \frac{A^2}{2!} + \frac{A^3}{3!} + \cdots \]

2.16.1 为什么需要矩阵指数?

连续线性系统 \(\dot{\mathbf{h}}(t) = A\mathbf{h}(t)\) 的解为:

\[ \mathbf{h}(t) = e^{At} \mathbf{h}_0 \]

2.16.2 利用特征分解计算

\(A = Q\Lambda Q^{-1}\)

\[ e^{At} = Q e^{\Lambda t} Q^{-1} = Q \begin{bmatrix} e^{\lambda_1 t} & & \\ & \ddots & \\ & & e^{\lambda_n t} \end{bmatrix} Q^{-1} \]

计算矩阵指数 → 转化为计算 \(n\) 个标量指数!

2.16.2.1 代码表示:矩阵指数的幂级数近似

练习:用幂级数实现 mat_exp_2x2(result, A, t, terms),求解 \(\dot{\mathbf{h}} = A\mathbf{h}\)\(A = [[-1,0],[0,-0.1]]\),观察不同特征值的衰减速度。

#include <stdio.h>
#include <math.h>

// 矩阵指数的幂级数近似 (2×2)
// e^(At) ≈ I + At + (At)²/2! + ... + (At)^N/N!
void mat_exp_2x2(double* result, const double* A, double t, int terms) {
    // result = I
    result[0] = 1; result[1] = 0;
    result[2] = 0; result[3] = 1;

    // power = (At)^k / k!
    double power[4];
    double At[4] = {A[0]*t, A[1]*t, A[2]*t, A[3]*t};
    power[0] = 1; power[1] = 0;
    power[2] = 0; power[3] = 1;  // starts at (At)^0 / 0! = I

    for (int k = 1; k <= terms; k++) {
        // power = power * At / k
        double tmp[4];
        tmp[0] = (power[0]*At[0] + power[1]*At[2]) / k;
        tmp[1] = (power[0]*At[1] + power[1]*At[3]) / k;
        tmp[2] = (power[2]*At[0] + power[3]*At[2]) / k;
        tmp[3] = (power[2]*At[1] + power[3]*At[3]) / k;
        power[0] = tmp[0]; power[1] = tmp[1];
        power[2] = tmp[2]; power[3] = tmp[3];

        result[0] += power[0]; result[1] += power[1];
        result[2] += power[2]; result[3] += power[3];
    }
}

int main() {
    // h'(t) = A*h(t), A = [[-1, 0], [0, -0.1]]
    // 解: h(t) = e^(At) * h0
    double A[] = {-1.0,  0.0,
                   0.0, -0.1};
    double h0[] = {1.0, 1.0};

    printf("System: h'(t) = A*h(t), A = [[-1,0],[0,-0.1]]\n");
    printf("Initial: h(0) = [1, 1]\n\n");

    for (double t = 0; t <= 5.0; t += 1.0) {
        double E[4];
        mat_exp_2x2(E, A, t, 20);
        double h1 = E[0]*h0[0] + E[1]*h0[1];
        double h2 = E[2]*h0[0] + E[3]*h0[1];

        // 解析解: h1 = e^(-t), h2 = e^(-0.1t)
        double exact1 = exp(-t);
        double exact2 = exp(-0.1 * t);

        printf("t=%.0f: h = [%.4f, %.4f]  exact = [%.4f, %.4f]\n",
               t, h1, h2, exact1, exact2);
    }
    printf("\nλ=-1 decays fast (short memory), λ=-0.1 decays slow (long memory)\n");
    return 0;
}
System: h'(t) = A*h(t), A = [[-1,0],[0,-0.1]]
Initial: h(0) = [1, 1]

t=0: h = [1.0000, 1.0000]  exact = [1.0000, 1.0000]
t=1: h = [0.3679, 0.9048]  exact = [0.3679, 0.9048]
t=2: h = [0.1353, 0.8187]  exact = [0.1353, 0.8187]
t=3: h = [0.0498, 0.7408]  exact = [0.0498, 0.7408]
t=4: h = [0.0183, 0.6703]  exact = [0.0183, 0.6703]
t=5: h = [0.0067, 0.6065]  exact = [0.0067, 0.6065]

λ=-1 decays fast (short memory), λ=-0.1 decays slow (long memory)

2.17 矩阵求导

2.17.1 标量对向量求导

\[ \frac{\partial f}{\partial \mathbf{x}} = \nabla_{\mathbf{x}} f = \begin{bmatrix} \frac{\partial f}{\partial x_1} \\ \vdots \\ \frac{\partial f}{\partial x_n} \end{bmatrix} \]

2.17.2 常用公式

函数 \(f\) 梯度 \(\frac{\partial f}{\partial \mathbf{x}}\)
\(\mathbf{a}^T \mathbf{x}\) \(\mathbf{a}\)
\(\mathbf{x}^T \mathbf{x}\) \(2\mathbf{x}\)
\(\mathbf{x}^T A \mathbf{x}\) \((A + A^T)\mathbf{x}\)(若 \(A\) 对称:\(2A\mathbf{x}\)

2.17.3 标量对矩阵求导

\[ \frac{\partial f}{\partial W}_{ij} = \frac{\partial f}{\partial w_{ij}} \]

函数 \(f\) 梯度 \(\frac{\partial f}{\partial W}\)
\(f = \text{tr}(AW)\) \(A^T\)
\(f = \mathbf{x}^T W \mathbf{y}\) \(\mathbf{x}\mathbf{y}^T\)
\(f = \|W\mathbf{x} - \mathbf{y}\|^2\) \(2(W\mathbf{x}-\mathbf{y})\mathbf{x}^T\)

2.17.4 链式法则

\(\mathbf{y} = W\mathbf{x}\)

\[ \frac{\partial \mathcal{L}}{\partial W} = \underbrace{\frac{\partial \mathcal{L}}{\partial \mathbf{y}}}_{\text{上游梯度}} \underbrace{\mathbf{x}^T}_{\text{局部梯度}} \]

这就是 PyTorch autograd 在 nn.Linear 中做的事!

2.17.4.1 代码表示:数值梯度验证与梯度下降

练习:实现数值梯度 numerical_grad,与解析梯度 \(\nabla(\mathbf{x}^T\mathbf{x}) = 2\mathbf{x}\) 对比验证。再用梯度下降最小化 \(f(\mathbf{x}) = \mathbf{x}^T\mathbf{x}\)

#include <stdio.h>
#include <math.h>

// 数值梯度验证
// f(x) = x^T x, grad = 2x
double quadratic(const double* x, int n) {
    double s = 0.0;
    for (int i = 0; i < n; i++) s += x[i] * x[i];
    return s;
}

void numerical_grad(double* grad, double (*f)(const double*, int),
                    const double* x, int n, double eps) {
    for (int i = 0; i < n; i++) {
        double x_plus[10], x_minus[10];
        for (int j = 0; j < n; j++) {
            x_plus[j] = x[j];
            x_minus[j] = x[j];
        }
        x_plus[i] += eps;
        x_minus[i] -= eps;
        grad[i] = (f(x_plus, n) - f(x_minus, n)) / (2 * eps);
    }
}

int main() {
    double x[] = {1.0, 2.0, 3.0};
    int n = 3;

    // 解析梯度: ∇(x^T x) = 2x
    printf("f(x) = x^T x\n");
    printf("f([1,2,3]) = %.1f\n\n", quadratic(x, n));

    printf("Analytic grad:  [");
    for (int i = 0; i < n; i++)
        printf("%.1f%s", 2*x[i], i < n-1 ? ", " : "]\n");

    double grad[10];
    numerical_grad(grad, quadratic, x, n, 1e-5);
    printf("Numerical grad: [");
    for (int i = 0; i < n; i++)
        printf("%.4f%s", grad[i], i < n-1 ? ", " : "]\n");

    // 简单梯度下降: min x^T x, x_new = x - lr * grad
    printf("\nGradient descent on x^T x:\n");
    double lr = 0.1;
    double gx[3] = {1.0, 2.0, 3.0};
    for (int step = 0; step <= 5; step++) {
        double fx = quadratic(gx, n);
        printf("  step %d: x=[%.4f, %.4f, %.4f], f=%.6f\n",
               step, gx[0], gx[1], gx[2], fx);
        for (int i = 0; i < n; i++)
            gx[i] -= lr * 2 * gx[i];  // grad = 2x
    }
    return 0;
}
f(x) = x^T x

2.18 总览:概念之间的逻辑链

向量 + 标量乘法 + 加法
        │
        ▼
  线性组合 ──→ 线性无关 ──→ 基 ──→ 维度
        │
        ▼
  矩阵 = 线性变换
        │
        ├──→ 核(被消灭的)+ 像(能到达的)──→ 秩
        │
        ├──→ 行列式(体积缩放)──→ 逆矩阵(可逆 ⟺ det ≠ 0)
        │
        └──→ 特征值/特征向量(不动轴)──→ 特征分解(对称 ⟹ 正交对角化)
                                                │
                            ┌───────────────────┼───────────────────┐
                            ▼                   ▼                   ▼
                       矩阵指数 e^At        正交矩阵                SVD
                            │             (旋转 / 保形)      (旋转+拉伸+旋转)
                            ▼                                    │
                      SSM 连续系统                          低秩近似 ──→ LoRA

  贯穿全程的「度量」:迹 tr(·)  ·  范数 ‖·‖(长度 / 距离 / 正则)

2.19 速查表:符号约定

符号 含义 维度
\(x, \alpha, \lambda\) 标量 \(0\)
\(\mathbf{x}, \mathbf{v}\) 向量(列向量) \(\mathbb{R}^n\)
\(\mathbf{x}^T\) 行向量 \(\mathbb{R}^{1 \times n}\)
\(x_i\) 向量第 \(i\) 个分量 标量
\(A, B, W\) 矩阵 \(\mathbb{R}^{m \times n}\)
\(A^T\) 转置 \(\mathbb{R}^{n \times m}\)
\(A^{-1}\) 逆矩阵 \(\mathbb{R}^{n \times n}\)
\(a_{ij}\) 矩阵第 \(i\) 行第 \(j\) 标量
\(I_n\) \(n\) 阶单位矩阵 \(\mathbb{R}^{n \times n}\)
\(\mathbf{x}^T\mathbf{y}\) 内积 标量
\(\|\mathbf{x}\|\) L2 范数 标量
\(\det(A)\) 行列式 标量
\(\text{tr}(A)\) 标量
\(\lambda\) 特征值 标量
\(\mathbf{v}\) 特征向量 \(\mathbb{R}^n\)
\(\sigma\) 奇异值 标量
\(\text{rank}(A)\) 整数