计算机不懂「猫」,也不懂「国王」——它只认识数字。 这一课,我们从零开始发明「向量」:现代 AI 描述万物的通用语言。 你会亲手算出「国王 − 男人 + 女人 = 女王」, 再学会向量的加减乘除、点积、余弦相似度和归一化——「像不像」的完整尺子, 以及检索系统的起手式。 最后认识大模型世界的集装箱——张量。全程只需要加减乘除。
假设你在写一个程序,希望它能回答:「猫、老虎、仓鼠——这三种动物,哪两个更像?」
你脱口而出:猫和老虎。理由也许是「都是猫科」「都毛茸茸」「都会扑猎物」。 但问题来了:计算机不懂「猫科」,不懂「毛茸茸」,它只认识数字。你没法把一只猫塞进 CPU——你只能给它数字。
能偷的最大懒,是只用一个数字:给每种动物打一个「体型分」,从 0 到 1。 仓鼠 0.04,猫 0.25,狗 0.45,鳄鱼 0.50,狼 0.72,老虎 0.90。 它们全部住进一根数轴:
神奇的事情发生了:「像不像」突然变成了可以计算的东西。两个数离得越近,就越「像」。整个判断只需要一次减法。
但等一下——按这个算法,猫和仓鼠(差 0.21)比猫和老虎(差 0.65)更像? 好像哪里不对,但先别急着推翻它。这个方案至少做对了一件大事:它把「比较两个事物」变成了「比较两个数字」。这个思想我们要保留。真正的问题出在——维度不够。
再看一眼数轴:狗 0.45,鳄鱼 0.50,只差 0.05——全场最小。
在「一个数字」的世界里,狗和鳄鱼是天造地设的一对——只因为它们体重差不多。 把一只动物压缩成一个数字时,性格、习性、危险程度……全被扔掉了。
给每只动物两个数 (体型, 凶猛):第一个当横坐标,第二个当纵坐标。 它们就从一根线,搬到了一张平面地图上。
横差记作 a,纵差记作 b,距离 = √(a² + b²)——读作「根号下 a 方加 b 方」。 拿狗和鳄鱼练手:
√(开根号)就是找一个「自己乘自己等于它」的数:因为 0.7 × 0.7 = 0.49,所以 √0.49 = 0.7。 这个套路有个大名:勾股定理。三维?三项平方相加。一万维?一万项相加—— 公式一个模子,数学从不挑食。
现在,请注意你刚刚做了什么——你把一只动物写成了一组有顺序的数字:
狗 = [ 0.45 , 0.20 ] // 第 1 个数是体型,第 2 个数是凶猛
这组有顺序的数字,数学上就叫向量(vector)。数字的个数叫维度(dimension)——我们刚造的是 2 维向量。 同一个向量有三副面孔,说的是同一件事:
[0.45, 0.20],两个数,仅此而已。动物能变成向量,那「词」呢?别忘了我们的目的地是大语言模型——它的原料是词。 照样打分,两个维度:性别(男 0 → 女 1)、皇家气质(平民 0 → 皇家 1):
看清楚刚才发生的事,一共只有三步:
向量加减从来不是玄学:减去「男人」,是把男性方向抵消掉;加上「女人」,是把女性方向搬进来。语义的每一步移动,背后都是数字的加减。
2013 年,Google 的 Mikolov 团队在 word2vec 模型里发现: 用真实语料训练出的词向量空间中,king − man + woman 的最近邻真的是 queen。 没有任何人手工打分——所有数字都是模型从海量文本里自己学出来的。
那「自己学」到底是怎么操作的?机制朴素得惊人:模型不停地猜下一个词, 猜错了就回头把每个数字朝「猜对的方向」拧一丁点。拧上几百万次,数字自己排好了队。 这一「拧」就是训练——拧多大、往哪拧,我们留到第 10 课「损失函数与梯度」亲手算。
真实空间也不是 2 维,而是 300 维起步;GPT-3 里每个词是 12288 维。 维度画不出来,但公式和我们今天用的一模一样。
加减说完了,向量还剩乘和除。但这里得先澄清一件事:「向量乘法」不是两个向量相乘,而是「一个数乘一个向量」——数学上叫数乘。
规则简单到没有惊喜:2 × [0.45, 0.20] = [0.90, 0.40]——每个坐标都乘 2。 字面意思是「这只动物的每个特征都翻倍」。但翻倍之后,箭头会往哪儿跑?
所以「数乘」的语义特别好记:k 就是放大器的档位。 k = 2 是「特征加倍」,k = 0.5 是「特征减半」,k = −1 是「彻底反过来」(箭头掉头指向反面)。 方向不动的原因朴素得可爱:横竖都被乘了同一个数,纵比横的比值自然没变。
[0.90, 0.20] ÷ 2 和 [0.90, 0.20] × 0.5 是同一件事, 结果都是 [0.45, 0.10]。向量世界里没有真正的「除法」,只有「乘以倒数」。
那为什么还要给除法留个位置?因为它有一个了不起的用途:求平均。
算出来的 [0.58, 0.58] 正好落在猫和老虎的正中点——一只体型中等、凶猛中等的动物。 这就是「平均向量」:加法把大家合到一起,除法把结果拉回中间。 大模型训练时有成千上万条梯度要取平均,用的就是这个动作。
[0.45, 0.20] 和 [0.90, 0.40]。谁更长?它们方向一样吗?第 4 站我们说「像不像就是距离」。这句话一路用到现在都很好使,但它有一个致命的盲区。
想象你在给文章打分:A 文里「科技」出现 1 次。现在把 A 文原样复制一遍、词频全部翻倍,得到 C 文。C 文和 A 文分明是同一种文章——可按距离算,它们的差距却被翻了一倍。 长度一掺进来,「像不像」就被污染了。
我们需要的是一把只看方向、不看长度的尺子。它的起点叫点积: 两个向量逐项相乘、再全部加起来,把两个向量压成一个数。
点积有三种读法,记住任意一种都够用:
a·b = a₁b₁ + a₂b₂ + a₃b₃ + …——逐项相乘再全部相加,一万维也是这个套路。a·b = |a| × |b| × cos(夹角);图里那条绿色影子就是 |b| × cos。上面第二条读法凭什么等于第一条?答案就一个字:拆。 第 6 站那台放大器(k × a,把 a 放大 k 倍)在这里派上了用场—— 只不过档位不是人挑的,是 b 指定的:把 b 拆成「顺着 a 的一截」和「垂直于 a 的一截」,档位就现形了。
换任何一对 a、b,这六步一步都不用改。二维能把图画出来,三维、一万维画不出来, 但「横着的档位归 0、顺着的那截报出 |a| × 影子长、影子长就是 |b| cos θ」这三件事在多少维都成立。 不信就回到上面的演示:把 b 转到跟 a 垂直,绿色影子会缩成一个点——那正是「档位 0」,点积同时掉到 0.00。
但点积也有毛病,就是你刚才在演示里亲手碰到的那个:把 b 拉长到 1.5 倍,方向一点没变,点积却跟着变成 1.5 倍。它到底还是被长度污染了。最小修补是什么?把两个长度都除掉:
单看点积,它被长度污染,看着有点鸡肋。但在真实系统里,点积几乎总是「算余弦」的第一步—— 而且工程师会偷一个更大的懒:与其每比一次都除一遍,不如提前把向量的长度都改成 1。
这个动作叫归一化(normalize):把向量除以它自己的长度,v̂ = v ÷ |v|。 长度变成 1 之后,a·b 里的 |a|、|b| 都是 1,分母压根不用写——归一化之后,点积直接就是余弦相似度。
于是整条「找相似」的流水线短得惊人:库里的向量提前归一化好 → 查询也归一化 →一次点积就拿到全部相似度分数 → 排序,取前 k 个(top-k)的分数和 ID → 拿着 ID 回词表取词。搜索引擎、推荐、RAG 检索,骨架上都是这四步。
名字里为什么带个「余弦」?就藏在这个除法的结果里——下一站把它摊开看; 而「把长度拧成 1」这个偷懒动作,第 9 站我们亲手做一遍,再把它变成一条打分流水线。
上一站最后那一下除法,凭什么能把两个长度消得干干净净?
就凭第 7 站那条放大器公式可以反过来用:a·b = |a| × |b| × cos θ。 把它原样代进分子:
分子分母里的 |a| × |b| 一模一样,约掉之后什么都不剩——余弦相似度不是什么新发明,它算出来的就是 cos θ 本身。
那何必绕这一圈写成除法?因为真实场景里你手上只有两个向量的坐标,不知道夹角是几度; 而除法用坐标就能算,算完顺手把 cos θ 免费送给你。 名字里的「余弦」两个字,就是这么来的。
那 θ 到底是什么?它是两支箭头之间的夹角,只有两种量法:
np.arccos()、torch.acos() 吐出来的都是弧度,180° 就是 π 弧度。注意 cos θ 是个标量小数:没有方向、没有维度,就是一个小数。 两个 12288 维向量比出来的相似度,也还是这么一个小数。 它凭什么是那个数?下面这个三角形会告诉你。
和距离不一样:余弦永远是一个 −1 到 1 之间的小数,不管你在 2 维还是 12288 维。 这就是为什么今天几乎所有「找相似」的系统——搜索引擎、推荐、RAG 检索—— 报出来的相似度都是一个小数:那不是别的,就是余弦。
第 7 站最后那个除法「余弦 = 点积 ÷ (|a| × |b|)」有个小麻烦:每比一次,都要除一次。 工程上没人这么干——大家换了个更省事的做法:提前把向量的长度都改成 1。
这个动作叫归一化(normalize),做法只有一步除法:v̂ = v ÷ |v|—— 把向量的每个坐标,都除以它自己的长度。看个例子:
方向一点没动:两个坐标除以的是同一个数,纵比横还是 0.40 ÷ 0.30 = 1.33。 变的只有长度——从 0.50 变成 1.00。这其实还是第 6 站那台放大器:乘以一个小于 1 的数,箭头沿原路缩短,只不过这里的档位 k 换成了 1 ÷ |v|。
归一化之后,所有向量都站在同一个圆上。这个半径 1 的圆叫单位圆—— 圆上的点再多,长度也全是 1,于是「谁和谁像」只剩下一件事:角度。
回头看点积:a·b = |a| × |b| × cos θ。既然 |a| = |b| = 1, 公式当场塌缩成 a·b = cos θ——归一化之后,点积就是余弦相似度,一个除号都不用写。第 7 站那一步除法没有消失,它只是被提前做掉了。
「归一化 + 一次点积」这套组合,在工程上有个固定的用法。四步走完,就是一次检索:
为什么非要绕 ID 这一道?因为模型从头到尾只跟数字打交道:算相似度算出的是分数,排序排出来的是 ID,最后一步才把 ID 翻译回人能看懂的字。下一课那本词表,就是「ID ↔ 字」的对照册。
两维也有装不下的时候:「王子」和「国王」怎么区分?都挺皇家、都男性——他们差在年龄。 那就加第三根轴。
想再区分「古代的国王 / 现代的国王」?加第四维「时代」。四维的空间人类画不出来——没关系,数学完全不在乎:距离公式照样一项项加,「谁和谁更像」照样比。 12288 维也一样。在 GPT-3 眼里,每个词是一张 12288 个数字的照片, 没有人说得清第 7042 维是什么意思——但每一维,都可能是一个我们叫不出名字的「角度」。
一个数,是标量;一排数,是向量;一张数表,是矩阵; 一摞表、一箱表……统统叫张量(tensor)。 它是大模型世界里的集装箱:所有输入、所有中间结果、所有参数,都以张量的形状流动。
真实的模型里还有一层最关键的「摞」:一句话要被切成若干个 token, 每个 token 查出一支向量,这些向量按顺序摞成一张表——这才是模型眼中的「一句话」。 一次训练同时喂 32 句话,表再摞成一摞。下一课我们就去造这张表的入口:词表与分词。
| 它是什么(Qwen2.5-7B) | 形状 | 维数 |
|---|---|---|
| 学习率 | 标量 | 0 |
| 一个 token 的词向量 | [3584] | 1 |
| 嵌入矩阵(躺在 safetensors 文件里) | [151936, 3584] ≈ 5.4 亿个数 | 2 |
| 一句话 10 个 token 的向量序列 | [10, 3584] | 2 |
| 一个批次:32 句话 | [32, 10, 3584] | 3 |
| 32 句 × 28 个注意力头的「谁关注谁」表 | [32, 28, 10, 10] | 4 |
上面那张表里最大的形状就是嵌入矩阵。把整个模型的参数量从头加一遍你会发现:「7B」不是厂家规定的,就是加出来的——而且每一步都只是乘法:
三个面试常问的观察就藏在这几行里:参数的大头不在注意力、在 FFN(一层里 87%);K、V 可以做得比 Q、O 小得多(28 个头共用 4 组,省掉 6/7 的 K、V 参数);嵌入矩阵和输出层能不能共用一份参数,直接差出 5.45 亿。
那要多大显存才装得下?除了参数量,还得知道每个参数在显存里占几个字节:
Kaggle 给的是 16GB,装不下这个 76 亿。所以我们要训的第一个模型得自己造小一点: 层数砍下来、宽度收窄、词表缩到只剩算术要用的那几个字—— 参数量一下子小了几千倍,小到一张免费卡就能装下。但张量的形状规则、注意力的算法、训练的动作,一个都没变。这就是我们敢在免费 GPU 上从零手写一个 Transformer 的底气。
(具体小到多少、显存占多少,等第 14 课真的把那个模型造出来、跑起来,我们再把它自己的数字算给你看。)
一个 7B(刚才加出来的 76 亿参数)的大模型,本质就是一堆形状各异的张量, 整整齐齐躺在 .safetensors 文件里——那不是什么神秘格式, 就是「带形状标签的大数字数组」。第 4 课我们会真的打开一个模型文件,把嵌入矩阵挖出来给你看。
向量,是给万物拍的数字照片:一组有顺序的数字捕捉特征, 「像不像」第一次变成可以计算的距离。 加减是搬运特征,数乘是调强度,点积探测方向,再除以长度就得到余弦——一副只认方向、不认长短的尺子; 而把长度统一拧成 1(归一化)之后,一次点积就是余弦——检索系统靠它给全库打分, 取 top-k 的分数和 ID,再拿 ID 回词表取词。 词也有向量,语义于是变成几何——国王 − 男人 + 女人 = 女王不是魔法,是箭头的搬运。 而张量,是数字的集装箱:大模型里流动的一切——词、句、注意力、76 亿参数——全是张量。
[0.45, 0.20];它同时是数列、点、箭头,数字的个数就是维度。a·b = a₁b₁ + a₂b₂ + …,同向为正、垂直为 0、反向为负;再除以 |a||b| 得到余弦——约掉长度之后,它算出来的就是 cos θ 本身,永远在 −1 到 1 之间,回答「像不像」。v̂ = v ÷ |v|——每个坐标除以自己的长度,长度变成 1、方向一点不动。归一化之后 a·b = cos θ,点积直接就是余弦;检索的四步(归一化入库 → 一次点积打分 → 取 top-k 的分数和 ID → 用 ID 回词表取词)全靠它。我们已经会用向量描述「猫」和「国王」了——可那些数字是我们手工打的。 大模型面对的是几百万条句子,谁来打分?下一课,我们自己造一套中文算术语料, 亲手切出词表、编好号,再让模型把每个字的向量从随机数里「跑」出来。