Embedding 是什么?向量相似度怎么计算?
一句话回答
Embedding 是把文本(也可以是图片等)映射成一个固定长度的向量,语义相近的内容在向量空间里距离更近。相似度常用余弦相似度(只看方向)、点积(方向和长度都算)和欧氏距离(看两点之间的距离);向量归一化成单位长度后,三者给出的排序完全一致。它是语义检索、RAG、聚类、去重和推荐的基础。
详细解析
Embedding 模型和生成模型
Embedding 模型读入一段文本,输出一个向量,通常有几百到几千维。比如"怎么退款"和"退货流程说明"的向量很接近,和"今天天气怎么样"的向量离得很远。它一般用对比学习训练:让意思相近的文本对靠近,不相关的文本对远离。
| Embedding 模型 | 生成模型 | |
|---|---|---|
| 输出 | 一个固定长度的向量 | 一段 token 序列 |
| 用途 | 检索、聚类、分类、相似度计算 | 回答、写作、推理 |
| 成本和速度 | 一次前向计算,便宜、快 | 逐个 token 生成,贵、慢 |
| 结构 | 传统上多为编码器结构,近年也有基于大模型改造的 | 多为 Decoder-only |
三种相似度
点积: a · b = Σ aᵢ × bᵢ
余弦相似度:cos(a, b) = (a · b) / (|a| × |b|),范围 -1 到 1,越大越相似
欧氏距离: |a - b| = √Σ(aᵢ - bᵢ)²,越小越相似
- 余弦相似度只看方向,不受向量长度影响,最常用
- 点积同时受方向和长度影响,计算最快
- 欧氏距离衡量空间中两点之间的距离
归一化后的关系:向量长度都是 1 时,点积就等于余弦相似度,欧氏距离的平方等于 2 − 2 × 余弦相似度。三者是单调对应的,检索的排序结果相同。所以很多系统先归一化,再用最快的点积计算。不少 Embedding 接口返回的向量已经归一化,以文档为准。具体用哪种度量,优先按模型文档的推荐,向量库建索引时选的度量也要和它一致。
用途
- 语义检索、RAG:问题和文档都转成向量,找最相近的片段,见 RAG 的完整链路
- 聚类和分类:把向量当作特征,给用户反馈分组、打标签
- 去重:相似度超过阈值的内容视为重复
- 推荐:找和用户看过的内容相似的条目
- 语义缓存:相似的问题直接复用之前的答案,见 语义缓存
选型要考虑什么
- 语言和领域:中文场景选中文语料训练充分的模型。公开榜单(如 MTEB 及其中文版本)只能参考,一定要在自己的数据上评测
- 维度:维度高,表达能力可能更强,但存储和检索成本更高。有的模型训练时专门做过处理,支持指定输出维度,截短后的向量仍然可用(自己截短时要重新归一化);其他模型的向量不能随意截短
- 最大输入长度:超长的文本会被截断或报错,长文档要先切块,见 文档怎么切分
- 使用方式:有的模型要求给查询和文档加不同的前缀或指令,要按模型文档使用
- 成本:按输入 token 计费,通常远低于生成模型。存储量约等于"向量数 × 维度 × 每个数的字节数",假设有 100 万个片段、1024 维、用 float32(4 字节)存储,约 4 GB,向量索引还有额外的开销
代码示例
const dot = (a, b) => a.reduce((sum, x, i) => sum + x * b[i], 0)
const norm = (a) => Math.sqrt(dot(a, a))
const cosine = (a, b) => dot(a, b) / (norm(a) * norm(b))
const euclidean = (a, b) => Math.sqrt(a.reduce((sum, x, i) => sum + (x - b[i]) ** 2, 0))
const normalize = (a) => a.map((x) => x / norm(a))
// 玩具向量,真实的 Embedding 有几百到几千维
const a = [3, 4]
const b = [6, 8] // 和 a 方向相同,长度是 a 的 2 倍
const c = [1, 7]
console.log(cosine(a, b), dot(a, b), euclidean(a, b)) // 1 50 5:方向相同,但点积和距离都受长度影响
// 归一化后:点积 = 余弦相似度,欧氏距离的平方 = 2 - 2 × 余弦相似度
const [na, nc] = [normalize(a), normalize(c)]
console.log(cosine(a, c), dot(na, nc)) // 都约为 0.877
console.log(euclidean(na, nc) ** 2, 2 - 2 * cosine(a, c)) // 都约为 0.246
// 检索:llm.embed 是通用伪接口,各家 SDK 的方法名和返回结构不同
const [query, ...docs] = await llm.embed(['怎么退款', '退货流程说明', '会员积分规则'])
const ranked = docs.map((v, i) => ({ i, score: cosine(query, v) })).sort((x, y) => y.score - x.score)
面试官可能追问
相似度达到多少才算"相关"?阈值怎么定?
没有通用的值。不同模型的分数分布差别很大,有的模型对不相关的文本也会给出不低的余弦相似度。要用自己数据里标注过"相关、不相关"的样本来校准阈值;做检索时更常用"取前 K 个",而不是只依赖绝对阈值。
Embedding 能区分"支持退款"和"不支持退款"吗?
往往区分得不好。两句话大部分字词相同,向量非常接近。Embedding 对否定、数字、专有名词这类细节不敏感,要靠关键词检索和重排序来补充,见 混合检索 和 重排序。
换 Embedding 模型要注意什么?
不同模型的向量空间互不兼容,维度也可能不同,所有文档都要重新向量化,新旧向量不能混用。迁移时可以新旧索引并行,评测通过后再切换,相似度阈值也要重新校准。
文本 Embedding 和大模型内部的 token embedding 是一回事吗?
不是。大模型的第一层把每个 token 映射成一个向量,叫 token embedding,它是模型内部的一部分,每个 token 一个。检索用的文本 Embedding 是一整段文本对应一个向量,由专门训练的模型对整段内容编码得到,目标是让相似度能反映语义。
易错点
- 不同模型生成的向量不能放在一起比较
- 余弦相似度高只说明"话题相近",不代表这段内容能回答问题
- 向量库索引的距离度量要和模型推荐的一致
- 超出模型最大输入长度的部分会被截断,等于没有参与向量化
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。