Skip to content

Linear Transformations(线性变换)

每次 matrix 乘法都是一次线性变换——一种在保持线性性的同时对 vector 进行重塑、旋转或投影的函数。本节涵盖旋转、反射、缩放、剪切、投影、映射的 kernel 与像,以及神经网络 layer 如何将这些变换链式组合。

  • 线性变换(或线性映射)是一种接受一个 vector 并产生另一个 vector 的函数,同时保持加法和缩放。若 \(T\) 是线性的,则:

    • \(T(\mathbf{u} + \mathbf{v}) = T(\mathbf{u}) + T(\mathbf{v})\)
    • \(T(c\mathbf{u}) = cT(\mathbf{u})\)
  • 每个线性变换都可以表示为与某个 matrix 的乘法。Matrix 就是变换。当你将一个 vector 乘以一个 matrix 时,你就在对其应用一次线性变换。

  • \(2 \times 2\) 的 matrix 看作一台机器:输入二维 vector,输出新的二维 vector。Matrix 的各列告诉你标准 basis vector \(\hat{\mathbf{i}}\)\(\hat{\mathbf{j}}\) 在变换后落在哪里。其余一切都由线性性推导而来。

Matrix 的各列展示了 basis vector 的落点

  • 例如,若
\[ 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 列)。其他所有 vector 都是这两个的组合,其输出自然随之确定。

  • 将两个 matrix 相乘可以理解为依次应用两个变换。若 \(B\) 对 vector 进行一次变换,\(A\) 再对结果进行变换,则 \(AB\) 将两步合为一步。在游戏引擎中,先旋转角色再向前移动,与先移动再旋转的结果不同——这正是 matrix 乘法不满足交换律的原因。

  • 旋转使 vector 旋转某个角度 \(\theta\),而不改变其长度。Vector 大小不变,只是指向新方向。

旋转保持长度但改变方向

  • 二维旋转 matrix 为:
\[ R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \]
  • \(\theta = 90°\) 时:
\[ R = \begin{bmatrix} 0 & -1 \\ 1 & 0 \end{bmatrix} \]

因此 \([1, 0]^T\) 变为 \([0, 1]^T\)。原来向右的 vector 现在向上。旋转 matrix 是正交的,行列式始终为 1。当你在手机上旋转照片时,这个 matrix 正被应用到每个像素坐标上。

  • 在三维中,每个轴都有对应的旋转 matrix。机械臂绕特定轴旋转每个关节,每个关节就是一个旋转 matrix。绕 z 轴的旋转看起来就像嵌入三维的二维旋转:
\[ R_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix} \]
  • 缩放沿各轴独立地拉伸或压缩 vector:
\[ S(s_x, s_y) = \begin{bmatrix} s_x & 0 \\ 0 & s_y \end{bmatrix} \]

缩放按不同因子拉伸各轴

  • \(S(2, 1.5)\) 将 x 分量加倍,y 分量乘以 1.5。沿某轴缩放 \(-1\) 会翻转该分量。对角 matrix 始终是缩放变换。当你将图像缩小到 50% 时,你在对每个像素坐标应用 \(S(0.5, 0.5)\)

  • 反射沿某轴或某条线翻转 vector,如镜像。沿 x 轴反射保持 x 分量,取反 y 分量:

\[ \text{Ref}_x = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix} \]

沿 x 轴的反射翻转 y 分量

  • 例如,\([3, 2]^T\) 变为 \([3, -2]^T\)。当手机将自拍水平翻转使文字正确显示时,它正在应用一个反射 matrix。沿直线 \(y = x\) 反射则交换两个分量:
\[ \text{Ref}_{y=x} = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix} \]
  • 反射 matrix 的行列式为 \(-1\),确认了它翻转了方向。

  • 旋转和反射都是刚性变换:它们保持距离和角度。表示它们的 matrix 是正交 matrix——这正是正交 matrix 的行列式总为 \(+1\)(旋转)或 \(-1\)(反射)的原因。

  • 剪切沿一个轴按另一个轴的比例偏斜 vector。水平剪切因子为 \(k\)

\[ \text{Sh}_x(k) = \begin{bmatrix} 1 & k \\ 0 & 1 \end{bmatrix} \]

剪切使顶部横向滑动,底部保持固定

  • 每个点沿水平方向移动 \(k\) 乘以其高度的距离。当 \(k = 0.5\) 时,高度为 2 的点向右移动 1 个单位。底行保持不动,顶行滑动。斜体文字就是这样工作的:直立字母被剪切,从而向右倾斜。

  • 上述所有变换(旋转、缩放、反射、剪切)都是线性变换。它们保持原点不动,并保持直线不弯曲。但平移(将所有点移动固定距离)呢?

  • 平移不是线性变换,因为它移动了原点。若将每个点向右移动 3,零 vector 就变成了 \([3, 0]^T\),打破了线性性。为了处理平移,我们使用仿射变换(affine transformation),它将线性变换与平移结合:

\[\mathbf{y} = A\mathbf{x} + \mathbf{t}\]
  • 为了将其表示为单次 matrix 乘法,我们使用齐次坐标:给每个 vector 添加一个额外的 1,并使用 \((n+1) \times (n+1)\) 的 matrix:
\[ \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} \]
  • 仿射变换保持直线和平行性,但不一定保持角度或长度。视频游戏中的每个物体都使用仿射变换来定位:旋转、缩放,然后放置在正确的位置——一切都编码在一个 matrix 中。

  • 退化变换(奇异 matrix)将空间压缩到更低维度。

  • 例如,matrix

\[ \begin{bmatrix} 1 & 2 \\ 2 & 4 \end{bmatrix} \]

将每个二维 vector 映射到一条直线上,因为两列指向同一方向。行列式为零,信息丢失,变换无法撤销。

  • 将彩色图像(每像素 3 个值:红、绿、蓝)转换为灰度图(每像素 1 个值)就是一种退化变换:颜色信息永久丢失。

  • 在 ML 中,线性变换是神经网络的核心——数据被表示为 matrix(一叠表示对象特征的 vector,如人、飞机、文本、图像……任何事物!)

  • 每个 layer 应用一次 matrix 乘法(线性变换),具体细节会在其他章节介绍——我们需要先说明如何组织这些数据,并建立神经网络的正确动机。

  • 然而,当今最常用的技术几乎完全是将数据通过一系列线性变换,我们称这种架构为 Transformer

  • Gemini、ChatGPT、Claude、Qwen、DeepSeek 以及当今世界表现最好的 AI,都是 Transformer!

编程练习(使用 CoLab 或 notebook)

  1. 将旋转 matrix 应用于一个 vector,绘制原始 vector 和旋转后的 vector。尝试不同角度。

    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='原始')
    plt.quiver(0, 0, v_rot[0], v_rot[1], angles='xy', scale_units='xy', scale=1, color='blue', label='旋转后')
    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. 将剪切变换应用于构成正方形的一组点,可视化变形后的形状。

    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='原始')
    plt.plot(sheared[0], sheared[1], 'b-o', label='剪切后')
    plt.grid(True); plt.legend(); plt.gca().set_aspect('equal')
    plt.show()