机器学习里的向量:语义如何变成数字

· 约 7 分钟读完

写完《大模型如何理解你的话并生成回复》之后,发现里面有个词被反复使用却从没解释过:向量。token 要变成 4096 维向量,注意力是向量之间的点积,输出还是向量乘矩阵——整篇文章建立在”读者知道向量是什么”的前提上。

这一篇就来补这块地基:向量到底是什么,怎么算出来,算出来之后能用来干什么。看完再回去读那篇推理流程,里面所有矩阵运算都会有直觉支撑。

向量就是一组有序的数字

中学课本里的向量是”既有大小又有方向的量”,画成一个箭头。平面上的 (3, 4) 表示从原点出发,向右 3、向上 4,长度是 5(勾股定理),方向斜向右上。

这个定义换个角度看更有用:向量是一个事物在若干个维度上的取值清单。维度可以是任何可量化的属性。比如用三个维度描述水果——甜度、酸度、脆度,每项 0 到 1 打分:

苹果 = [0.7, 0.3, 0.8]
梨   = [0.8, 0.2, 0.7]
柠檬 = [0.1, 0.9, 0.4]

苹果和梨的数字接近,柠檬和它们差得远。把这三个向量画进三维空间,苹果和梨是两个挨着的点,柠檬孤悬在另一头。“语义相近”就这样变成了”空间距离近”——这是全文最核心的一句话。

机器学习里的向量就是这个思路,只有两点不同:维度从 3 个变成几百到几千个;打分的不是人,而是训练过程。维度多到无法想象也没关系——三维空间里”距离""夹角”的计算公式,换到 4096 维照样成立,只是画不出来了。

词怎么变成向量

最朴素的编码方式叫 one-hot:词表有 10 万个词,每个词就是一个 10 万维向量,属于自己的那一位是 1,其余全是 0。

猫 = [0, 0, 1, 0, 0, ..., 0]
狗 = [0, 0, 0, 0, 1, ..., 0]

这个方案有两个致命问题。一是维度等于词表大小,巨大且稀疏;二是更糟的——任意两个词的向量都互相垂直,距离全部相等。“猫”到”狗”的距离和”猫”到”微积分”的距离一样远,语义关系完全丢失。

嵌入(embedding)解决的就是这个问题:把每个词压成一个几百到几千维的稠密向量,每一维都是小数。关键在于这些小数不是人填的,而是训练出来的。

训练的理论根基是分布假说:一个词的含义由它的上下文决定。“猫”和”狗”经常出现在相似的语境里——“喂___”、“___掉毛”、“带___去宠物医院”——所以它们应该拿到相近的向量。

word2vec(2013 年)把这个假说变成了可执行的训练任务:让一个小神经网络做填空题,给定上下文预测中间的词(或者反过来)。网络为了把题做对,必须给上下文相似的词分配相似的向量。训练完成后,把网络的权重矩阵拿出来,每一行就是一个词的向量。词向量是做预测任务的副产品,没有人定义过任何一个维度的含义。

大模型里的嵌入矩阵同理:它是模型参数的一部分,跟其他所有参数一起在”预测下一个 token”的训练中被反复调整。上一篇说的”token ID 去嵌入矩阵查表取出 4096 维向量”,查的就是这张训练出来的表。

怎么计算两个向量有多像

有了向量,下一步是给”近”下一个可计算的定义。常用的度量有三种。

点积(dot product):对应位置相乘再求和。

a = [1, 2],  b = [2, 3]
a · b = 1×2 + 2×3 = 8

两个向量方向越一致、数值越大,点积越大。问题是它受长度影响——把 b 的每个数都乘 10,点积也跟着乘 10,但语义并没有变。

欧氏距离:两个点之间的直线距离,勾股定理推广到 N 维,值越小越相似。同样受向量长度影响。

余弦相似度:只看两个向量的夹角,不管长度。算法是点积除以两个向量长度的乘积,取值范围 -1 到 1:方向完全相同是 1,互相垂直(不相关)是 0,方向完全相反是 -1。

import numpy as np

def cos_sim(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

a = np.array([1, 2])
b = np.array([2, 3])
print(cos_sim(a, b))  # 0.992,方向几乎一致

文本场景基本默认用余弦相似度,因为关心的是”语义方向”而不是数值大小。实践中还有个常见技巧:先把所有向量归一化成长度 1,之后点积就直接等于余弦相似度,检索时省一步除法——很多 embedding 模型输出的向量已经预先归一化好了。

度量取值范围受长度影响典型用途
点积无界归一化后的快速检索、注意力打分
欧氏距离≥ 0聚类、图像特征比对
余弦相似度-1 ~ 1不受文本语义相似度(默认选它)

向量运算里藏着语义关系

词向量时代有一个著名的发现:向量的加减法对应语义关系。

国王 - 男人 + 女人 ≈ 女王
北京 - 中国 + 法国 ≈ 巴黎

「国王 - 男人」这个差向量,可以理解成”王位”这个抽象概念所指的方向,把它加到「女人」上,自然落到「女王」附近。同理「北京 - 中国」近似一个”首都关系”向量,加到哪个国家上就指向哪国首都。

这说明训练并没有只学会”谁和谁像”,还把性别、国籍、首都这类抽象关系编码成了空间中方向大致一致的位移。这种线性结构在当年的 word2vec 上非常惊艳;在现代大模型的高维空间里仍部分成立,不过实际应用已经很少依赖这种算术游戏——它的价值更多是帮人建立直觉:语义被几何化了,连”关系”都是有方向的

从词向量到句向量

word2vec 是一个词一个向量,而实际需求往往是给一句话、一段文档算向量。干这个活的叫 embedding 模型(文本嵌入模型),它和生成式大模型是两类东西:生成模型吃一段话、吐下一个 token;embedding 模型吃一段话、吐一个固定长度的向量(常见 384 到 3072 维),代表整段话的语义。

它的威力在于跨越字面:

docs = ["路由器怎么重置", "猫粮品牌推荐", "WiFi 总是断连的排查步骤"]
doc_vecs = [embed(d) for d in docs]

query = "家里网络一直掉线"
scores = [cos_sim(embed(query), v) for v in doc_vecs]
print(scores)  # [0.61, 0.07, 0.86]

查询和第三篇文档没有任何一个词重合——“网络掉线”对”WiFi 断连”——相似度却遥遥领先。关键字搜索在这种场景下直接失效,向量检索能命中,因为比的是语义不是字面。这种检索方式叫语义搜索(semantic search)。

向量的实际用途

RAG(检索增强生成) 是目前最大的应用场景。流程是:预先把知识库文档切块,每块算一个向量存起来;用户提问时把问题也算成向量,找出最相近的几块文本,连同问题一起塞进大模型的 prompt。大模型能回答”公司内部规章”或”昨天刚发生的事”,靠的不是参数里的记忆,而是检索喂进来的上下文。

推荐系统:给用户和物品分别算向量(用户向量可以由其历史行为对应的物品向量聚合而来),推荐就是”找离用户向量最近的物品”。

去重与聚类:相似新闻聚合、问答社区检测重复提问,本质都是找向量距离小于阈值的样本对。

数据量大了之后:向量数据库与近似搜索。 一万条向量暴力遍历毫秒级就算完;上亿条时每次查询全量算一遍余弦就太慢了。向量数据库(pgvector、Milvus、Qdrant 等)用 HNSW 之类的近似最近邻(ANN)索引解决这个问题:放弃”百分百找到最近的”,换取千倍的查询速度——召回率 98% 但快三个数量级,绝大多数场景都划算。

回到大模型:向量的三次出场

铺垫完这些,再回看上一篇的推理流程,向量在三个关键位置出场,而且玩法是同一套。

第一次:嵌入层。 token ID 查嵌入矩阵变成 4096 维向量——就是本文第二节讲的 embedding,只是它作为大模型的第一层,跟模型其余部分一起训练,而不是单独的产物。

第二次:注意力打分。 每个 token 的 Q 向量和所有 token 的 K 向量做点积,决定”我该关注谁”。现在可以看清这个动作的本质了:点积就是相似度。“怎么样”的 Q 和”天气”的 K 点积大,意味着两者在当前语境里高度相关,于是”天气”的信息被大权重融合进来。注意力机制就是每个 token 对所有 token 发起的一场相似度检索。

第三次:输出头。 最后一层的隐藏向量乘以输出矩阵得到 10 万个 logits。把这步展开看:输出矩阵的每一列对应词表里一个 token,所谓”乘矩阵”实际是拿隐藏向量和每个候选 token 的向量逐一做点积,得分最高的就是和当前语境语义最契合的下一个词。“预测下一个 token”,最后一步也是一次相似度排序。

查表、相似度打分、相似度排序——整个推理流程从头到尾只在重复一件事:用几何距离表达语义远近。

几个容易踩的认知误区

单个维度没有含义。 水果例子里第 1 维是甜度,但真实 embedding 的第 1024 维不代表任何可命名的属性。语义分布在所有维度的组合上,试图解读单个维度基本是徒劳。

相似不等于同义。 分布假说有个副作用:“涨价”和”降价”的上下文极其相似,向量往往也很近。向量距离分得清”相关不相关”,不一定分得清”立场相反”。做情感、立场类判断时不能只靠它。

不同模型的向量不通用。 模型 A 和模型 B 各自训练,各有各的语义空间,向量之间没有任何对应关系。混在一个库里检索得到的是乱数;换 embedding 模型意味着整个知识库全量重算向量。

维度不是越高越好。 维度翻倍,存储和每次检索的计算量跟着翻倍,检索效果未必提升。常见的 768 / 1024 / 1536 维对多数场景已经够用。

RAG 切块大小直接影响检索质量。 一个向量只能表达”一团语义”。整篇万字长文压进一个向量,各主题互相平均,结果是什么都沾一点、什么都不突出,谁来查都排不进前几名。所以 RAG 要先切块——常见几百 token 一块,让每个向量只负责一小段集中的语义。