跳转至

矩阵运算

一句话总结:矩阵加法、标量乘法、矩阵-向量乘、矩阵乘法、Hadamard 积、外积、Kronecker 积与广播——深度学习每一次前向与反向的计算引擎。

矩阵运算是深度学习的计算引擎。 本节覆盖矩阵加法、标量乘法、矩阵-向量积、矩阵乘法、逐元素运算、Kronecker 积以及广播——每一次前向传播和梯度更新背后的操作.

  • 矩阵可以像向量一样做加法与缩放。

  • 加法要求两个矩阵维度相同,然后逐元素相加:

\[ \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 6 & 8 \\ 10 & 12 \end{bmatrix} \]
  • 标量乘法就是把每个元素乘以那个标量:
\[ 3 \times \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} = \begin{bmatrix} 3 & 6 \\ 9 & 12 \end{bmatrix} \]
  • 对矩阵能做的最简单的事,就是拿它去乘一个向量。 矩阵-向量乘法 (matrix-vector multiplication) \(A\mathbf{x}\) 把 \(A\) 的各列按 \(\mathbf{x}\) 的各分量作为权重线性组合起来:
\[ \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}\):矩阵乘输入向量,再加一个偏置。

  • 更一般的情况就是矩阵乘法 (matrix multiplication). 给定 \(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\) 各列的加权和 (weighted sum),权重来自 \(B\) 对应的那一列。

  • 如果 \(B\) 有一列是 \([2, 3]^T\),那么结果对应那一列就是 \(2 \times (A \text{ 的第 1 列}) + 3 \times (A \text{ 的第 2 列})\).

  • 有个实用的特例:矩阵乘它自己的转置,结果一定是方阵。 \(AA^T\) 是 \(m \times m\), \(A^TA\) 是 \(n \times n\):

\[ \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\):

\[ \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\)),但要求两个矩阵尺寸完全相同。 它在机器学习里被大量用于门控 (gating):用一个取值在 0 和 1 之间的掩码逐元素相乘,就能控制每个元素"通过多少".

  • 两个向量 \(\mathbf{u}\) 和 \(\mathbf{v}\) 的外积 (outer product) 会产生一个矩阵:\(\mathbf{u}\mathbf{v}^T\). 每一项都是 \(\mathbf{u}\) 的一个分量乘 \(\mathbf{v}\) 的一个分量:

\[ \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 外积的和——奇异值分解 (Singular Value Decomposition, SVD) 做的正是这件事 (在分解那一节会讲到).

  • 矩阵乘法计算开销很大。 两个 \(n \times n\) 矩阵相乘要做 \(O(n^3)\) 次运算。 \(1000 \times 1000\) 的矩阵就是十亿次乘法。

  • 当矩阵是稀疏 (sparse) 的 (大部分是零) 时,朴素乘法会浪费时间在乘零上。 压缩稀疏行 (Compressed Sparse Row, CSR) 格式只存非零元素,外加它们的位置信息:

    • 值 (values):按行顺序排列的非零元素
    • 列索引 (column indices):每个值属于哪一列
    • 行偏移 (row offsets):每一行在值列表里的起始位置
  • 举个例子,下面这个矩阵:

\[ 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]. 这样就跳过了所有零,稀疏运算快很多。

  • 矩阵的一大核心用途是求解线性方程组 (systems of linear equations). 方程组 \(A\mathbf{x} = \mathbf{b}\) 问的是:"哪个向量 \(\mathbf{x}\),被 \(A\) 变换之后能得到 \(\mathbf{b}\)?"

  • 举个例子,假设你去买水果。 每个苹果 \(x_1\) 元,每根香蕉 \(x_2\) 元。 你知道 2 个苹果加 1 根香蕉是 $5, 1 个苹果加 3 根香蕉是 $10. 写成矩阵形式:

\[ \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{(row 1)} \qquad \qquad x_1 + 3x_2 = 10 \qquad \text{(row 2)}\]
  • 由第 1 行得 \(x_2 = 5 - 2x_1\). 代入第 2 行:\(x_1 + 3(5 - 2x_1) = 10\),得到 \(x_1 = 1\),再得 \(x_2 = 3\). 苹果 $1, 香蕉 $3.

  • 验证一下——完全对得上:

\[ \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}\). 但直接求逆开销大,数值上也不稳定。 实践里我们用分解代替。

  • 不是每个矩阵都是方阵,也不是每个方阵都可逆。 伪逆 (pseudo-inverse) \(A^+\) 把逆的概念推广到任意矩阵。 它总是存在,并给出"尽可能好"的那个逆:

\[A^+ = (A^TA)^{-1}A^T\]
  • 当 \(A\) 是下三角矩阵时,解 \(L\mathbf{x} = \mathbf{b}\) 用前向代入 (forward substitution) 很简单:先解出 \(x_1\),再用它去求 \(x_2\),一路往下。

  • 当 \(A\) 是上三角矩阵时,解 \(U\mathbf{x} = \mathbf{b}\) 就用回代 (back substitution):先解最后一个变量,再往上推。

  • 这就是为什么把矩阵分解成三角因子 (在分解那一节会看到) 这么有用。 它把一个难题变成两个易题。

Coding Tasks (use CoLab or notebook)

  1. Multiply two matrices and verify the dimensions. Then swap the order and observe that the result changes (or that it fails if dimensions don't match).

    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. Solve a system of linear equations \(A\mathbf{x} = \mathbf{b}\) and verify the solution by multiplying back. Try changing \(\mathbf{b}\) to see how the solution shifts.

    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}")
    


📌 本节要点

  • 加法与标量乘:同尺寸矩阵逐元素相加,标量乘法把每个元素乘以标量。
  • 矩阵-向量乘法: \(A\mathbf{x}\) 把 \(A\) 各列按 \(\mathbf{x}\) 分量线性组合,是神经网络层 \(A\mathbf{x} + \mathbf{b}\) 的核心。
  • 矩阵乘法:内部维度必须相配,结果 \((m \times p)\) 每项是行点积;不满足交换律,但满足结合律与分配律。
  • Hadamard 积:同尺寸矩阵逐元素相乘 \(A \odot B\),交换律成立,ML 里用于门控。
  • 外积: \(\mathbf{u}\mathbf{v}^T\) 产生秩 1 矩阵,是 SVD 分解的基本积木。
  • 计算复杂度:两个 \(n \times n\) 矩阵相乘 \(O(n^3)\),稀疏矩阵用 CSR 格式跳过零元素。
  • 线性方程组: \(A\mathbf{x} = \mathbf{b}\) 求"什么向量经 \(A\) 变换后得到 \(\mathbf{b}\)";直接求逆昂贵且不稳,实际用分解。
  • 伪逆: \(A^+ = (A^TA)^{-1}A^T\) 把逆推广到任意矩阵,总是存在。
  • 三角矩阵求解:下三角用前向代入,上三角用回代——把难题化为两个易题,是矩阵分解威力所在。