矩阵是变换的说明书
如果整本书只允许你带走一章,带这一章。大学教材第一页就把矩阵定义成「m 行 n 列的数字表格」,这个定义没错,却像把汽车定义成「几吨钢铁的堆放方式」——合法,但让你永远学不会开车。矩阵的真实身份是:一个函数,一份变换空间的说明书。它吃进一支向量,吐出另一支向量;而说明书的写法简洁到奢侈——它的每一列,写的就是一支基向量变换后的去向。这一章配了全书的旗舰 Demo:四个滑杆,一整张会变形的坐标纸。读完它,你看到的每一个矩阵都会开始动。
把矩阵当函数看
先立一个视角,后面全章靠它:写下 Ax 的时候,不要读成「矩阵乘向量」,读成「把函数 A 应用到输入 x 上」——就像 f(x)。数学家选择省掉括号只是因为懒,别被记法骗了。矩阵是函数,向量是参数,乘积是返回值。这个函数的输入是空间里的一支箭,输出是另一支箭;由于它对空间里每一支箭都同时生效,更形象的画面是:A 抓住整个空间,拧了一把。所有箭头、所有点、整张坐标纸,连同纸上画的每一幅图,一起被拧。
但矩阵不是任意的函数,它是线性的函数——还记得第 01 章立的两条军规:f(u+v) = f(u)+f(v),f(kv) = k·f(v)。这两条军规在几何上的表现,当时也说过:网格线变换后仍然平行、仍然等距,原点钉在原地。可以旋转、可以拉伸、可以斜着剪切、甚至可以整个压扁,但不许弯曲、不许撕裂、不许平移原点。一会儿 Demo 里你会亲眼确认:无论怎么拖滑杆,变形后的网格永远是「斜着的方格纸」,绝不会变成波浪纹。
随堂判定:谁是线性变换
用两条军规当试金石,快速过一遍常见操作,练出手感:
- 绕原点旋转:✓。两支箭的和旋转后,等于各自旋转再相加(整个平行四边形跟着转);缩放同理。网格转完还是等距平行网格。
- 平移(整体挪 3 格):✗。零向量被挪走了,违反「原点不动」;而且 f(2v) = 2v + 3 ≠ 2·f(v) = 2v + 6。平移很有用,但它是「仿射」不是「线性」——一会儿 Android 类比里会讲图形学怎么用齐次坐标把它「洗白」。
- 逐分量取绝对值:✗。f(−v) = f(v) ≠ −f(v),齐次性阵亡。同理 ReLU、sigmoid 等激活函数全都不是线性的——它们存在的意义恰恰就是打破线性。
- 「取向量的前两个分量」(降维切片):✓。切片对加法数乘都诚实,它就是一个矩形矩阵(单位阵砍掉几行)。
- 求导(彩蛋):✓!导数满足 (f+g)' = f'+g'、(kf)' = kf'——在「函数当向量」的抽象世界里,求导是一个如假包换的线性变换,甚至可以写成(无穷大的)矩阵。第 02 章说抽象定义给你迁移能力,这就是一次兑现。
说明书为什么只需要两列
现在推导全书最漂亮的结论。假设我只告诉你一件事:某个线性变换 A 把 î = (1,0) 送到了 (2, 1),把 ĵ = (0,1) 送到了 (−1, 1)。请问:任意一支箭,比如 v = (3, 2),会被送到哪?
你已经有全部的零件了。第 03 章说过,坐标就是配方:v = 3î + 2ĵ。而线性的两条军规说:先组合再变换 = 先变换再组合。于是:
看清楚发生了什么:配方不变,原料换新。v 的坐标 (3, 2) 原封不动,只是原来的原料 î、ĵ 换成了它们的新去向。这意味着,一个线性变换的全部信息,就是「基向量都去了哪」——在二维,两支箭的去向,四个数,完事。把这两个去向按列竖着排进一个 2×2 的表格:
这就是矩阵。矩阵不是被发明出来的数字容器,它是「基向量去向登记表」的简写。而所谓「矩阵乘向量」的计算规则——你背过的那套「行乘列」——不过是上面那行推导的机械化:
中间那步是灵魂:Ax 就是「以 x 的坐标为配方,组合 A 的各列」。第 03 章代码里那句 M @ coef 的天机,现在正式解密。
旗舰 Demo:亲手拧一把空间
四个滑杆按矩阵的位置摆放:上排是 a、b,下排是 c、d。给你一份实验清单,每条都值得做:
- 单位矩阵:点「单位」复位。a=d=1,b=c=0,两列正好是 î、ĵ 自己——「什么都不做」的变换,数学名字叫 I。
- 缩放:只动 a 和 d(对角线)。a=2 把横向拉宽一倍,d=0.5 把纵向压半。对角矩阵 = 沿轴缩放,最温顺的变换。
- 旋转:点「旋转 45°」。注意读数:两列变成了 (0.71, 0.71) 和 (−0.71, 0.71)——正是 î、ĵ 各自转了 45° 后的坐标。一般地,转 θ 角的矩阵是 [cos θ, −sin θ; sin θ, cos θ],不用背:闭眼想「î 转到哪、ĵ 转到哪」,自己就能写出来。
- 剪切:点「剪切」。î 不动,ĵ 被推斜成 (1, 1),整张网格像一摞被推歪的扑克牌。斜体字就是对字形做剪切。
- 压扁:点「压扁(奇异)」,看着整张平面被拍成一条直线,det 读数掉到 0。二维的信息塌缩进一维,无数个点被压成同一个点——这种「有去无回」的变换叫奇异,第 08、09 章的主角。
做完清单再自由发挥,同时保持一个观察习惯:眼睛盯网格看「变换的气质」,读数盯两列看「说明书的内容」,来回对照。这个来回,就是把矩阵从「数字表格」升级成「会动的函数」的全部训练。
从此你拿到任何一个 2×2 矩阵,都有一套三秒读法:把两列画成两支箭,想象方格纸被拽着让 î、ĵ 落到这两支箭上。[3, 0; 0, 3] 是均匀放大三倍;[0, −1; 1, 0] 是 î 转去 (0,1)、ĵ 转去 (−1,0)——逆时针 90°;[1, 1; 1, 1] 两列重合——空间被压上一条线。矩阵从「16 个字母的公式」变成「两支箭的动作」,这是全书性价比最高的一次思维升级。
变换动不了的东西
知道一个变换「能做什么」之后,同样值得盘点它「保证不破坏什么」——这些不变量是后续章节的锚点:
- 直线还是直线:线性变换把直线映成直线(或者极端情况压成一个点),绝不弯成曲线。所以「线性模型的决策边界是直的」不是比喻,是定理。
- 比例保持:线段的中点变换后还是中点,三等分点还是三等分点。lerp 和变换可以随意交换先后。
- 平行保持:平行的两条线,变换后依旧平行(这就是网格保持平行的原因)。
- 但长度、角度、面积一般不保:剪切会改角度,缩放会改长度,压扁会毁面积。「圆被线性变换映成椭圆」——这个看似随口的事实,是第 17 章 SVD 的几何全景图,先存档。特别地,面积的变化率有专属会计,第 08 章的行列式。
一个你天天在用的矩阵列读法:embedding 查表
「矩阵的列」这个视角,能当场解释一个 AI 工程的日常操作。第 02 章说过,词表里每个词有一支独热向量:32000 维,只有自己编号那一位是 1。现在把词表的 embedding 矩阵 E(4096×32000,每列是一个词的词向量)乘上「猫」的独热向量:
配方里只有一个 1,组合结果就是直接抽出那一列——「矩阵乘独热向量 = 查表取列」。所以框架里的 nn.Embedding 本质是一个「懒得真做乘法」的矩阵乘优化:反正配方里只有一个 1,直接按下标取列就好。一个看似查字典的操作,数学身份是不折不扣的线性变换——这也是第 01 章「一切皆矩阵乘」清单里 embedding 那一项的兑现。
写成代码:让说明书跑起来
import numpy as np R = np.array([[0.0, -1.0], # 逆时针转 90°:两列 = î、ĵ 的去向 [1.0, 0.0]]) v = np.array([3.0, 2.0]) R @ v # array([-2., 3.]) —— (3,2) 转到了左上 # 批量:把 500 个点(每行一个)一次全部旋转 pts = np.random.randn(500, 2) rotated = pts @ R.T # 行向量约定下要乘转置,详见第 19 章 # 验证线性军规:f(u+v) == f(u)+f(v) u, w = np.random.randn(2), np.random.randn(2) np.allclose(R @ (u + w), R @ u + R @ w) # True
第二段的 pts @ R.T 埋着一个真实世界的岔路:数学书把向量竖着写、矩阵在左(Ax);数据代码把样本横着摞(每行一个点),于是矩阵跑到右边还得转置。两种约定并存于世,PyTorch 的 nn.Linear 文档里那句 y = xAᵀ + b 让无数初学者眩晕——记住「以 shape 咬合为准」就能解毒,第 19 章专门开庭审理。
不方的矩阵:维度搬运工
2×2 只是热身。矩阵完全可以不方:一个 m×n 矩阵(m 行 n 列),是一台「从 n 维空间到 m 维空间」的搬运机器:输入必须是 n 维向量(列数 = 输入维度),输出必然是 m 维向量(行数 = 输出维度)。说明书读法不变:n 列 = n 支输入基向量各自的去向,只是去向坐标现在有 m 个分量。
比如 3×2 矩阵把平面「立」进三维空间——平面上的一张画,被贴到三维里某个斜面上;2×3 矩阵把三维空间拍扁到平面——一座雕塑投下的影子。升维不添信息(像只是贴得高级),降维必丢信息(影子撑不起雕塑),这句话在第 12 章「秩」会变成精确的定理。
练一次形状咬合的心算:一支 4096 维向量经过 11008×4096 的矩阵,输出几维?——11008 维(内侧 4096 咬合消掉)。再经过 4096×11008 的矩阵呢?回到 4096 维。两个矩阵的参数量是 4096×11008 + 11008×4096 ≈ 9000 万——单单这一层前馈,一亿参数没了,而 32 层就是 29 亿。「参数都花在哪了」这类账,你现在已经可以自己算了。
打开任何模型代码,nn.Linear(4096, 11008) 这种声明满天飞——它就是一个 11008×4096 的矩阵(加一支偏置向量):把 4096 维的表示搬进 11008 维空间。为什么升维再降维?一种直觉:在更高维的空间里,原本纠缠的概念更容易被摊开、分离(高维「地方大」,第 04 章说过几乎正交的方向管够),处理完再压回原维度。Transformer 前馈层「4096 → 11008 → 4096」的一涨一缩,每秒在全球 GPU 上发生几十亿次。你现在能读懂它的类型签名了:每个 Linear 层,就是一份「基向量去向登记表」,登记内容由训练来填写。
神经网络:一串变换的接力
把镜头拉远看整个网络。数据进来是一支向量(一张图、一句话的 embedding);第一层矩阵把它搬到新空间,非线性掰一下;第二层再搬、再掰……几十层接力之后,原本在输入空间里搅成一团的「猫」和「狗」,被拧到了输出空间里泾渭分明的两侧,一刀就能切开。深度学习的「学习」,就是学出每一层该怎么拧——每份说明书上的数字,由梯度下降一点点刻出来。
有个经典的思想实验能把这幅图景画得更具体。想象平面上两类点缠成一对螺旋:红点绕着蓝点转,任何一条直线都切不开它们——这是「线性不可分」的标准反例。现在让一个小网络来处理:第一层矩阵把平面掰宽、旋斜,ReLU 把负半轴拍平,第二层再掰、再拍……如果你能看见每一层之后的数据(有一些著名的可视化项目做过),会看到两条螺旋像被一双手慢慢解开:卷曲被逐层抻直,红蓝各自聚拢,到最后一层的空间里,它们已经乖乖分居两侧,一条直线(也就是一个线性分类器)轻松了断。「特征空间」这个词,指的就是数据被搬运到的这些中间空间;「表示学习」学的,就是搬运的路线图。
顺便把黑话表升级一下:论文里说「把输入投影到隐空间」「学习一个映射」「线性探针」,现在你都能翻译了——投影 = 乘个矩阵,映射 = 变换,线性探针 = 在某个中间空间里插一个最简单的点积打分器,看看这个空间里想要的信息是不是已经「可分」。词汇唬人,动作全是本章的。
这幅图景也解释了「为什么中间要掰非线性」:第 07 章马上会证明,两个线性变换连着做,合起来还是一个线性变换——纯线性的接力,叠一百层等于一层,白费电。非线性是把「多层」变成「深度」的那道工序。但请注意主次:掰弯只是调味,搬运才是主菜;参数、算力、显存,九成以上花在矩阵上。
你和 android.graphics.Matrix 打了多年交道,现在可以坦白两件事了。第一,它是 3×3 而不是 2×2,因为二维的平移不是线性变换(原点动了!),工程上的解法是齐次坐标:给每个点补一个恒为 1 的第三维,(x, y, 1),平移就伪装成了三维里的剪切——一个纯线性的动作。图形学「用高一维的线性,换低一维的平移」,是终极的换空间解决问题。第二,preScale/postRotate 的 pre/post 之谜,就是下一章矩阵乘法「不可交换 + 从右往左执行」的直接后果——悬念留到第 07 章解。
三个易错点。① 列是去向,不是行:说明书按列读,行的解读(每行是一台点积打分器,第 04 章视角)也对,但「基向量去向」这个几何解读只属于列——两种视角别混着用。② 形状咬合:A 的列数必须等于 x 的维数,(m×n)·(n×1) = (m×1),中间的 n 消掉——记「内侧相等,外侧存活」。③ 数学记法 vs 内存布局:数学上向量是列,NumPy 一维数组既非行也非列,PyTorch 的 Linear 存的是「转置后的权重」……读代码时以 shape 为准,别靠想象,第 19 章有完整对照表。
① 不查资料,写出「逆时针旋转 90°」的矩阵(提示:î 去哪,ĵ 去哪);
② 在 Demo 里配出「水平镜像翻转」,它的 det 是多少,颜色为什么变了?
③ [1, 0; 0, 0] 对空间做了什么?哪些向量被送到了原点?
④ nn.Linear(768, 3) 的矩阵是几行几列?它把什么维度的向量送进什么维度的空间——这种「窄出口」层常出现在网络的哪个位置、为什么?
小结
矩阵是线性函数的说明书,列 = 基向量的去向;Ax = 以 x 的坐标为配方组合 A 的列,行乘列公式只是这句话的机械化;矩阵乘独热向量 = 查表取列,embedding 层因此得名;m×n 矩阵是 n 维到 m 维的搬运工,列数管进、行数管出;直线、平行、比例是变换动不了的不动产,长度、角度、面积则随时被没收;神经网络 = 变换接力 + 非线性调味,每层的 Linear 就是一份待训练的说明书。你现在有了「一个矩阵」的完整直觉,下一章处理「两个矩阵」:先旋转再剪切,合起来是什么?答案会顺手解释你背了多年的那条行乘列公式到底从哪来,以及 preScale 和 postScale 为什么效果不同。