代码语言

知识点思维导图

29 个知识节点

Python(25) - 向量与 Embedding

读完后,你应能完成以下任务:

  • 绘制“Python(25) - 向量与 Embedding / 先建立前端锚点:用 CSS 颜色破题”的关键对象与数据流,解释“它就是一个三维向量 [255, 0, 0]。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Python(25) - 向量与 Embedding / 为什么不用关键词匹配?”设计正常与异常输入,验证“"退款"和"拿回钱"对机器是两个完全无关的字符串。”,输出首个偏差位置与回归测试结果。
  • 实现“Python(25) - 向量与 Embedding / 核心干货二:怎么算"两段文字像不像"——余弦相似度”的最小代码或配置,检验“最常用的是余弦相似度(cosine similarity)。”,输出命令、结果与 Diff,并说明不适用边界。

你想做一个「语义搜索」:用户搜"怎么退款",文档里写的是"取消订单后如何拿回钱"——没有一个字相同,传统的关键词匹配(includes / LIKE '%退款%')会一条都搜不到。本篇解决这个问题:怎么让机器理解"意思相近",而不是"字面相同"。答案就是 Embedding——把文字压成一串坐标数字,语义相近的文字坐标也挨得近,检索就变成"找最近的点"。这也是下一篇 RAG(第 26 篇)的地基。

依赖链提醒:阶段五是 23 调用大模型API → 24 函数调用/结构化输出 → 25 本篇 Embedding → 26 RAG → 27 Agent。本篇只讲清 Embedding 的最小可用形态(生成向量 + 算相似度 + 内存里检索),完整的"检索增强生成"流程留到第 26 篇。向量运算的底层全是 NumPy,建议先过第 20 篇。

一、先建立前端锚点:用 CSS 颜色破题

别一上来就背"向量""维度"这些术语。你其实早就用过向量了——CSS 颜色。

rgb(255, 0, 0) 是什么?它就是一个三维向量 [255, 0, 0]。三个数字分别是红、绿、蓝三个"维度"上的坐标。

关键直觉来了:

rgb(255, 0, 0)   纯红
rgb(250, 5, 5)   稍微暗一点的红 —— 数值接近,看起来也接近
rgb(0, 0, 255)   纯蓝     —— 数值差很远,看起来也差很远

颜色看起来像不像,约等于这两组数字离得近不近。 这就是向量的全部精髓——把"东西"变成"一串坐标",然后用"坐标距离"衡量"像不像"。

Embedding 干的是同一件事,只是:

CSS 颜色 Embedding
输入 一个颜色 一段文字
输出 3 个数字 [r, g, b] 一长串数字(OpenAI 是 1536 个)
"近" 代表 颜色看起来像 语义(意思)相近
怎么算近 数值距离 向量距离 / 余弦相似度

所以一句话定义:Embedding 就是把一段文字压成一串坐标(一个高维向量),语义相近的文字,坐标点挨得近。 三维你能想象成空间里的点,1536 维想象不出来没关系,数学公式一模一样。

⚠️ 边界:颜色的三个维度有明确含义(红绿蓝),但 Embedding 那 1536 个维度没有人类能解释的含义——它是模型训练出来的,你别试图去理解"第 7 个维度代表什么"。你只需要相信:模型保证了"意思近 → 坐标近"这个性质。


二、为什么不用关键词匹配?

先看前端老办法的死穴:

// JS:传统关键词匹配
const docs = ["如何取消订单并拿回钱", "配送时间说明", "积分规则"]
const query = "怎么退款"
// 字面包含匹配:一条都搜不到,因为没有"退款"两个字
const hits = docs.filter(d => d.includes("退款"))   // []  ❌

问题就在:includes / 数据库 LIKE 只认字面,不认语义。"退款"和"拿回钱"对机器是两个完全无关的字符串。

Embedding 把每段文字变成向量后,"退款"的向量和"拿回钱"的向量会挨得很近,于是就能搜到。这就是从"字符串匹配"升级到"语义匹配"的跨越。


三、核心干货一:怎么生成 Embedding

Embedding 不是你自己算的,是调模型 API 拿的(和第 23 篇调大模型同一套 SDK)。先装官方库:

pip install openai numpy

对照心智模型:这跟你在前端 fetch 一个 API 拿 JSON 没区别,只是返回的 JSON 里是一长串数字。

⚠️ 边界:embedding 模型 ≠ 聊天模型text-embedding-3-small 只会吐向量、不会对话;gpt-4 只会对话、不给你向量。调错 model 名字会直接报错。

批量更省钱省时——input 直接传列表,一次编码多条:


四、核心干货二:怎么算"两段文字像不像"——余弦相似度

拿到两个向量后,怎么量化它们"近不近"?最常用的是余弦相似度(cosine similarity)

直觉:把向量看成从原点射出的箭头,余弦相似度衡量两支箭头的"方向"有多一致,跟箭头多长无关。

  • 方向几乎相同 → 值接近 1(语义非常像)
  • 方向垂直无关 → 值接近 0(不相关)
  • 方向相反 → 值接近 -1(语义相反)

公式就是第 20 篇结尾那段:点积 / (两个向量各自的长度相乘)。用 NumPy 一行搞定:

⚠️ 易混点:别用"两点直线距离(欧氏距离)"想当然代替余弦相似度。 文本检索里余弦更常用,因为它只看方向、不受向量长度影响。两者结论大多一致,但语义检索的行业默认是余弦。另外注意:余弦相似度越大越相似,而距离是越小越相似,方向相反,排序时别搞反。

JS 里没有内置这些,得自己写循环,能直观看出 NumPy 帮你省了多少:

// JS:纯手写余弦相似度,对比一下啰嗦程度
function cosineSimilarity(a, b) {
  let dot = 0, normA = 0, normB = 0
  for (let i = 0; i < a.length; i++) {   // 1536 次循环,逐个累加
    dot += a[i] * b[i]
    normA += a[i] * a[i]
    normB += b[i] * b[i]
  }
  return dot / (Math.sqrt(normA) * Math.sqrt(normB))
}

五、核心干货三:归一化的小技巧(让点积 = 余弦)

如果你把每个向量先归一化(缩放成长度为 1),那么"余弦相似度"就退化成了简单的"点积"——少算一步除法,批量检索时更快。OpenAI 的 embedding 默认已经是归一化的,但自己处理时知道这点有用:

这一节属于"知道就行"的优化点,新手阶段直接用上面的 cosine_similarity 即可,不影响功能。


六、核心干货四:搭一个最小语义检索(Embedding 的"Hello World")

把前面拼起来,就是一个内存版语义搜索——这就是 RAG 的雏形,也是 Embedding 最小可用形态:

这段就是语义检索的完整骨架,对照前端你会发现结构很眼熟:

阶段 干什么 类比前端
离线建库 把所有文档批量转向量存起来 构建期把数据预处理成索引
在线查询 查询转向量 → 和库里逐个算相似度 → 排序取 top_k 拿到搜索词去索引里 filter + sort

真实项目不会在内存里逐个 for 比较(几万条会很慢),而是用向量数据库(如 Chroma / FAISS / pgvector)做高效近邻检索——这是第 26 篇 RAG 的内容。本篇你先理解"检索 = 在向量堆里找最近的点"这个本质就够了。


七、前端新手最容易踩的坑

  1. 以为 embedding 是关键词提取——不是。它不抽关键词,而是把整段语义压成坐标。两段没有共同字的话也能算出"很像"。

  2. embedding 模型和聊天模型用混——text-embedding-3-small 出向量,gpt-4 出对话,model 名字传错直接报错。

  3. 每次请求都重新编码整个知识库——文档向量应该离线算一次存起来,只有用户的 query 才需要实时编码。否则又慢又烧钱。

  4. 相似度方向搞反——余弦相似度越大越相似(接近 1),排序要 reverse=True;如果你改用"距离"则是越小越相似,两者排序方向相反。

  5. 不同模型的向量混用比较——text-embedding-3-small 的向量和别的模型的向量不在同一个坐标系,不能互相算相似度。整个知识库必须用同一个模型编码。

  6. 维度对不上还硬算——两个向量长度(维度)必须相同才能算相似度。换了模型导致维度变化(比如 1536 → 3072),旧向量全部作废,得重新生成。

  7. 拿 list 直接做数学运算——Python 的 list 不支持 a * b 这种向量化运算(那是 NumPy 的本事,第 20 篇),算相似度前记得 np.array(...)


八、总结

  • 先建立前端锚点:用 CSS 颜色破题:它就是一个三维向量 [255, 0, 0]。
  • 为什么不用关键词匹配?:"退款"和"拿回钱"对机器是两个完全无关的字符串。
  • 核心干货一:怎么生成 Embedding:Embedding 不是你自己算的,是调模型 API 拿的(和第 23 篇调大模型同一套 SDK)。
  • 核心干货二:怎么算"两段文字像不像"——余弦相似度:最常用的是余弦相似度(cosine similarity)。
  • 核心干货四:搭一个最小语义检索(Embedding 的"Hello World"):真实项目不会在内存里逐个 for 比较(几万条会很慢),而是用向量数据库(如 Chroma / FAISS / pgvector)做高效近邻检索——这是第 26 篇 RAG 的内容。
  • 前端新手最容易踩的坑:以为 embedding 是关键词提取——不是。 -> embedding 模型和聊天模型用混——text-embedding-3-small 出向量,gpt-4 出对话,model 名字传错直接报错。 -> 每次请求都重新编码整个知识库——文档向量应该离线算一次存起来,只有用户的 query 才需要实时编码。 -> 相似度方向搞反——余弦相似度越大越相似(接近 1),排序要 reverse=True;

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“向量与 Embedding”中,需要同时满足“先建立前端锚点:用 CSS 颜色破题”与“为什么不用关键词匹配?”。给定正文约束“rgb(255, 0, 0) 是什么?它就是一个三维向量 [255, 0, 0]。”,哪些判断保持了原有处理机制?多选
2“向量与 Embedding”出现偏差:“在“向量与 Embedding / 核心干货一:怎么生成 Embedding”中,即使不满足“Embedding 不是你自己算的,是调模型 API 拿的(和第 23 篇调大模型同一套 SDK)”,结果与副作用仍会保持不变。”已成为实际行为。围绕“核心干货一:怎么生成 Embedding”与“核心干货二:怎么算"两段文字像不像"——余弦相似度”,哪些判断能定位被改变的职责或边界?多选
3评审“向量与 Embedding”方案时,验收条件包含“如果你把每个向量先归一化(缩放成长度为 1),那么"余弦相似度"就退化成了简单的"点积"——少算一步除法,批量检索时更快。”。关于“核心干货三:归一化的小技巧(让点积 = 余弦)”与“核心干货四:搭一个最小语义检索(Embedding 的"Hello World")”的哪些决策符合正文机制?多选