你给猫拍了张照片,在手机相册里搜”猫”,很多时候就能把它找出来。可电脑并看不见猫:它唯一会做的,就是飞快地加数和乘数。
这一课的傻问题是:电脑怎样把照片变成数字? 顺便再问一句:变成数字以后,它拿这些数做什么?答案就是三个听起来像考试题的概念:向量、点积和矩阵;再加上一道算术:乘一乘,再加起来。
选一张牌:♣ 从零开始,♦ 不怕简单的公式,♠ 想看符号。随时可以换。
照片就是一列数
新手
数码照片就像一幅马赛克:它由一个个彩色小方块拼成,这些小方块叫像素,小到肉眼看不出来。电脑打开照片时,会把每个小方块存成三个数:红色有多少、绿色有多少、蓝色有多少,每个数都在 0 到 255 之间。纯红是 (255, 0, 0);白色是 (255, 255, 255);黑色是 (0, 0, 0)。
一张宽 4000、高 3000 个小方块的照片有 1200 万个像素。每个像素三个数,加起来就是排成一行的 3600 万个数。
排成一行的一列数叫向量,一共有多少个数,就是它的维数。在电脑眼里,你的照片就是一个 3600 万维的向量。它对照片做的一切,都是拿这列数做算术。
进阶
向量是一列有顺序的数,其中每个数叫它的分量。只有两个分量时,可以画成从中心出发的一支箭头:v = (3, 2) 就是”向右 3,向上 2”。它的长度用勾股定理算:√(3² + 2²) = √13 ≈ 3.6(OpenStax)。
一张只有两个像素的灰度照片,就是平面上这样一支箭头;三个像素,就是空间里的一支箭头。再多就画不出来了,但算法一模一样:长度还是各分量平方和的平方根。维数就是向量里有多少个数。
彩色照片里,每个像素有三个 0 到 255 之间的数,每个通道(红、绿、蓝)一个,因为每个数占 8 位(Pillow,一个 Python 图像库)。所以打开的照片是一张 高 × 宽 × 3 的数表。JPEG 文件之所以小,是因为压缩过,但打开时会解压成这张表。要交给神经网络,就把它全部排成一行:4000 × 3000 × 3 = 3600 万个数。
技术
| ♦ 里的说法 | ♠ 里的写法 |
|---|---|
| 向量 | x ∈ ℝⁿ,由 n 个实数组成 |
| 分量 | x = (x₁, …, xₙ) |
| 长度 | 欧几里得范数 ‖x‖ = √(Σᵢ xᵢ²) |
| 维数 | dim ℝⁿ = n:一组基 e₁, …, eₙ 中向量的个数 |
| 通道 | C;RGB 中 C = 3,取值为 {0, …, 255} 的 8 位整数 |
| 高 × 宽 × 3 | 张量 H × W × C(HWC);torchvision 用 C × H × W |
| 把它全部排成一行 | 展平(flatten):x ∈ ℝⁿ,n = H·W·C |
8 位 RGB 图像是一个取值于 {0, …, 255}^(H×W×C) 的整数张量,C = 3:Pillow 13.0 把这种模式描述为 «RGB (3x8-bit pixels, true color)»,并说明 «an 8-bit pixel has a range of 0-255»。torchvision 0.29 用 ToTensor 把它从 [0, 255] 区间的 (H × W × C) 转成 [0.0, 1.0] 区间、形状为 (C × H × W) 的浮点张量。展平后,它是 ℝⁿ 中的一个向量,n = H·W·C;对 4000 × 3000 的照片,n = 3.6×10⁷。
一个空间的维数,是它任意一组基中向量的个数(Austin,Understanding Linear Algebra,定义 3.5.6)。标准基 e₁, …, eₙ 有 n 个向量,所以 dim ℝⁿ = n,也就是分量的个数。范数 ‖x‖ = √(Σᵢ xᵢ²) 就是 n 维的勾股定理。
点积:乘一乘,再加起来
新手
一个水果摊一天卖了 30 个苹果、12 根香蕉和 18 个橙子,单价分别是 0.50 元、0.25 元和 1 元。一共卖了多少钱?把每样的数量乘以单价,再加起来:15 + 3 + 18 = 36 元。两列数这样”逐个相乘再相加”,就是点积,也是电脑处理照片时做得最多的一道算术。这个例子来自一本数学教材,OpenStax。
用箭头来看,点积说的是另一件事:两支箭头有多指向同一侧。斜着推一辆购物车,只有朝车子前进方向的那部分力才算数。两支箭头指向同一侧,点积为正;互成直角,为零;方向相反,为负。
进阶
a = (a₁, a₂) 和 b = (b₁, b₂) 的点积是 a·b = a₁b₁ + a₂b₂:把分量逐个相乘,再加起来(OpenStax)。分量更多也一样。a = (3, 1),b = (1, 2):3·1 + 1·2 = 5。两张各有 3600 万个数的照片,就是 3600 万次乘法。
用箭头来看还有另一种读法:a·b = a 的长度 × b 的长度 × 夹角的余弦(OpenStax)。同向时余弦为 1,成直角时为 0,相反时为 −1。所以两支垂直的箭头,点积为 0。
把点积除以两个长度,就只剩下余弦,它衡量两个方向有多像:这就是余弦相似度。拿照片来说有个妙处。把照片里的每个数都乘以 1.2,照片看起来更亮:箭头变长了,但没有转动,它和原图的余弦相似度仍然是 1(自行计算,前提是没有哪个数超过 255)。
技术
| ♦ 里的说法 | ♠ 里的写法 |
|---|---|
| 点积 | a·b = Σᵢ aᵢbᵢ(也写作 ⟨a, b⟩ 或 aᵀb) |
| 3600 万次乘法 | 代价 O(n):n 次乘法、n − 1 次加法 |
| a 的长度 × b 的长度 × 夹角的余弦 | a·b = ‖a‖‖b‖cos θ,θ ∈ [0, π] |
| 同向时余弦为 1,成直角时为 0,相反时为 −1 | −1 ≤ cos θ ≤ 1,即 |a·b| ≤ ‖a‖‖b‖(柯西–施瓦茨不等式) |
| 垂直 | 正交:a·b = 0(a ⊥ b) |
| 余弦相似度 | cos θ = a·b / (‖a‖‖b‖) |
| 箭头变长了,但没有转动 | 对任意 λ > 0,cos(x, λx) = 1 |
在 ℝⁿ 中,a·b = Σᵢ aᵢbᵢ。OpenStax(Calculus Volume 3,2.3)用余弦定理证明了 a·b = ‖a‖‖b‖cos θ(定理 2.4),以及两个非零向量正交当且仅当 a·b = 0(定理 2.5)。超过三维时反过来做:用 cos θ = a·b / (‖a‖‖b‖) 来定义夹角,而柯西–施瓦茨不等式 |a·b| ≤ ‖a‖‖b‖ 保证这个比值落在 [−1, 1] 内。
PyTorch 2.14 用 torch.nn.functional.cosine_similarity 计算余弦相似度,分母取 max(‖x₁‖‖x₂‖, ε),以免除以零。因为 λ > 0 时 cos(x, λx) = 1,如果另一张照片只是把数值整体放大或缩小,变亮或变暗,余弦相似度就分辨不出来。代价是线性的:n 次乘法、n − 1 次加法。
乘一乘,加一加,就这么多。靠这个我能比价格、比箭头、比照片,还剩时间睡个午觉。
动手试试:两支箭头
拖动箭头的尖端,或者用滑块、键盘方向键来移动。找一个让点积等于 0 的位置:这时两支箭头互成直角。蓝色宽带是 b 在 a 上的影子:两支箭头越同向,影子越长。
矩阵会挪动平面
新手
现在水果摊老板要比较两家店,每家价格不同。他把价格写成一张表:每家店一行,每种水果一列。这张表就是矩阵。想知道在每家店要花多少钱,就把购物清单和每一行逐个相乘再相加:每家店一个点积,一次全算出来。
矩阵还能挪动图画。想象一张画在橡皮膜上的网格:矩阵决定每个点去哪里,就像把橡皮膜拉长或推斜,但有两条规矩:直线还是直线,中心不动。
旋转或拉伸照片的程序,就是做这类计算来决定每个像素该去哪里。有些矩阵会把整幅图压成一条线:压扁以后,就再也还原不回来了。
进阶
矩阵是一张长方形的数表;有 m 行 n 列的矩阵叫 m × n 矩阵(OpenStax)。矩阵乘向量,就是用矩阵的每一行和这个向量做点积。A = (2 1 / 0 1),x = (1, 2):第一行得 2·1 + 1·2 = 4,第二行得 0·1 + 1·2 = 2,所以 Ax = (4, 2)。
读矩阵有个窍门:第一列是 (1, 0) 去的地方,第二列是 (0, 1) 去的地方(Austin)。在这个例子里,(1, 0) 变成 (2, 0),(0, 1) 变成 (1, 1):矩阵把图形横向拉长,又推斜了。知道了这两点,就知道一切点去哪里,因为任何一个点都是这两者的组合。
边长为 1 的小方块变成一个平行四边形。矩阵为 (a b / c d) 时,它的面积是 ad − bc:在例子里是 2·1 − 1·0 = 2,变成两倍(Austin)。如果等于 0,矩阵就把整个平面压扁成一条直线:少了一个维度,而且回不去了。(1 −1 / 2 −2) 就是这样。如果是负数,图形会像照镜子一样翻过来。
Pillow 就是这样旋转或推斜照片的:对每个新像素,它用一个矩阵算出它来自原图的哪个点(再加上一个平移)。
技术
| ♦ 里的说法 | ♠ 里的写法 |
|---|---|
| 矩阵 | A ∈ ℝ^(m×n),元素 aᵢⱼ(第 i 行第 j 列) |
| 每一行 | (Ax)ᵢ = Σⱼ aᵢⱼxⱼ = (A 的第 i 行)·x |
| 第一列是 (1, 0) 去的地方,第二列是 (0, 1) 去的地方 | A = [Ae₁ Ae₂]:第 j 列是 T(eⱼ) |
| 任何一个点都是这两者的组合 | 线性:A(c₁e₁ + c₂e₂) = c₁Ae₁ + c₂Ae₂ |
| 推斜 | 剪切(错切),例如 (1 1 / 0 1) |
| ad − bc | det A,列向量所张平行四边形的有向面积 |
| 压扁 | det A = 0:A 不可逆;秩下降,像是一条直线 |
| 像照镜子一样 | det A < 0:定向反转 |
| 旋转 | 旋转矩阵 R(θ) = (cos θ −sin θ / sin θ cos θ) |
| 它来自原图的哪个点 | 用 A⁻¹:遍历输出,到输入里去找 |
| (再加上一个平移) | 仿射变换 x ↦ Ax + t |
一个 m × n 矩阵 A 定义了从 ℝⁿ 到 ℝᵐ 的映射 T(x) = Ax,其中 (Ax)ᵢ = Σⱼ aᵢⱼxⱼ。它的列是基向量的像:A = [T(e₁) ⋯ T(eₙ)](Austin,命题 2.5.8)。T 是线性的,所以 T(Σⱼ cⱼeⱼ) = Σⱼ cⱼT(eⱼ),知道了列,就知道了一切。
在 2×2 的情形,转角为 θ 的旋转是 (cos θ −sin θ / sin θ cos θ),(1 1 / 0 1) 是一个剪切(Austin,2.6)。det A = ad − bc 是列向量所张平行四边形的有向面积(3.4),A 可逆当且仅当 det A ≠ 0(命题 3.4.12)。det A = 0 时秩下降:像空间 Col(A) 的维数等于 rank A(命题 3.5.12),对 (1 −1 / 2 −2) 来说是一条直线。
Pillow 13.0(ImageTransform.AffineTransform)接收 «the first two rows from the inverse of an affine transform matrix»:对每个输出像素 (x, y),到输入的 (ax + by + c, dx + ey + f) 处取值。它用逆矩阵,是因为它遍历的是输出照片;平移项 (c, f) 让这个变换成为仿射变换,而不是线性变换。
动手试试:让网格变形
调一调矩阵的四个数,或者试试下面的按钮。红色箭头是第一列,即 (1, 0) 去的地方;蓝色箭头是第二列。找一个能把网格压成一条直线的矩阵:面积会变成 0。再看看镜像时字母 F 会怎样。
矩阵乘法:顺序很重要
新手
穿衣服要先穿袜子再穿鞋,反过来就不是一回事了。矩阵也一样:先做一个变换再做另一个,相当于一个矩阵,这个矩阵是把两个矩阵相乘得到的,而且顺序很重要。先旋转照片再横向拉伸,和先拉伸再旋转,结果不一样。
开头那个问题的答案就在这里。一个认猫的神经网络,把照片当成一个巨大的向量接收进来,再一个接一个地乘上矩阵,中间还夹着几个别的步骤。这些矩阵的每一行都像一张价目表:它和照片的点积得出一个数。每张照片都要做上百万次这样的乘加运算。
所以显卡特别适合人工智能:它就是为了同时做大量乘法而设计的。
进阶
两个矩阵相乘时,结果里的每个数,都是第一个矩阵的某一行和第二个矩阵的某一列做点积(OpenStax)。只有当第一个矩阵的列数等于第二个矩阵的行数时才能相乘:m × k 矩阵乘 k × n 矩阵,得到 m × n 矩阵。
对一个点先做 B 再做 A,等于一次做矩阵 AB。注意:写法和做的顺序是反的(Austin)。而且 AB 一般不等于 BA(OpenStax)。设 R 为转 90°,S 为横向拉伸到两倍:先转再拉是 S·R = (0 −2 / 1 0),先拉再转是 R·S = (0 −1 / 2 0)。
神经网络的一层会把输入乘上一个权重矩阵(PyTorch)。一张 100 × 100 的灰度照片有 10000 个数;一个有 1000 个输出的层用的是 1000 × 10000 的矩阵,每张照片要做 1000 万次乘法(自行计算)。层与层之间还有一个不是乘法的步骤;《逻辑回归还是神经网络?》那一课讲了它是什么,以及网络靠它多得到了什么。
显卡会一次放进很多张照片,每张一列,于是一切就变成一次巨大的矩阵乘法(NVIDIA)。
技术
| ♦ 里的说法 | ♠ 里的写法 |
|---|---|
| 两个矩阵相乘 | (AB)ᵢⱼ = Σₖ aᵢₖbₖⱼ |
| 第一个矩阵的某一行和第二个矩阵的某一列 | (A 的第 i 行)·(B 的第 j 列) |
| m × k 矩阵乘 k × n 矩阵,得到 m × n 矩阵 | (m×k)(k×n) → m×n:内侧维数相同 |
| AB | 复合:A(Bx) = (AB)x;若 T ↦ A,S ↦ B,则 S∘T ↦ BA |
| AB 一般不等于 BA | 不满足交换律;满足结合律:(AB)C = A(BC) |
| 层 | 线性层(nn.Linear):y = xAᵀ + b |
| 权重 | A ∈ ℝ^(out×in),通过学习得到;b 为偏置 |
| 1000 万次乘法 | out·in = 10³·10⁴ = 10⁷ 次乘加(FMA),每张照片 |
| 一个不是乘法的步骤 | 非线性激活函数 σ;没有它,A₂(A₁x) = (A₂A₁)x 只相当于一层 |
| 一次放进很多张照片 | 批量(batch)N:一次 (M×K)(K×N) 的 GEMM,2·M·N·K FLOP |
(AB)ᵢⱼ = Σₖ aᵢₖbₖⱼ,要求内侧维数相同(OpenStax,College Algebra 2e,7.5)。矩阵乘法满足结合律和分配律,但不满足交换律。若 T(x) = Ax,S(x) = Bx,则 S∘T 的矩阵是 BA(Austin,命题 2.5.11):先做的那个写在右边。
m·n 个元素中的每一个,都是 k 项的点积:共 m·n·k 次乘加(FMA),即 2·m·n·k FLOP。NVIDIA 的 GEMM 指南也这样算,每个元素都是 «a dot-product of K-element vectors»。对 1000 × 10000 的层、一批 64 张照片:2 · 1000 · 64 · 10000 = 1.28×10⁹ FLOP(自行计算)。
PyTorch 2.14 把线性层写成 y = xAᵀ + b,每个样本占一行,权重 A 的形状是 (out_features, in_features)。因为有偏置 b,它是仿射的,不是线性的。NVIDIA 把权重放在 A 里、激活值放在 B 里,每个样本占一列:只是转置了一下,算的是同一件事。层与层之间如果没有非线性的 σ,由于结合律,连着的几层就会合成一层。
我看不见猫,只看见 3600 万个数。难的是知道该拿哪些矩阵去乘它们。
最后挑战
三句话总结
- 在电脑眼里,照片是一列数(每个像素三个,取值 0 到 255):一个向量,它的维数就是数的个数。
- 点积把两列数逐项相乘再相加:既能算出总价,就像水果摊的账;也能说明两支箭头有多同向(垂直时为 0)。
- 矩阵是一张能挪动平面上每个点的数表,矩阵乘法把这些变换串起来,而且顺序要紧:这正是神经网络对每张照片做上百万次的运算。
参考资料
- 向量、长度和点积:OpenStax,Calculus Volume 3,2.1 和 2.3(水果摊的例子,定理 2.4 和 2.5)。矩阵及其乘法:OpenStax,College Algebra 2e,7.5。
- 变换、行列式和维数:D. Austin,Understanding Linear Algebra,2.2、2.5、2.6、3.4 和 3.5。
- 像素和图像变换:Pillow 13.0,Concepts 和
ImageTransform。从照片到张量:torchvision 0.29,ToTensor。 - 神经网络:PyTorch 2.14,
nn.Linear和cosine_similarity。NVIDIA,Matrix Multiplication Background 和 Linear/Fully-Connected Layers。
**这一课是怎么做的:**在 Claude 的帮助下,根据上面的资料调研和撰写,资料查阅于 2026 年 10 月 17 日。没有单独链接的数字,是根据定义算出来的。例子(4000 × 3000 的照片、两家店、有 1000 个输出的层)都是编的。插图都是原创的。