跳转至

第 15 章:Embedding、向量数据库与语义检索

1. 本章要解决的问题

前面几章,我们基本都在讨论模型本身:

  • 第 12 章讲的是怎样把预训练模型做成会按指令回答的助手
  • 第 13 章讲的是怎样更省资源地做微调
  • 第 14 章讲的是怎样让模型在多个可行回答里更贴近人类偏好

但当你真的开始做应用,很快会碰到另一类完全不同的问题:

模型本身再强,也不等于它天然知道你手里的私有知识。

比如你想做一个课程资料问答助手、公司内部知识库助手,或者一个能回答你项目文档细节的聊天系统,用户一开口问的往往不是:

请解释什么是 Transformer。

而是:

我们组上周那版实验报告里,为什么最终没有采用混合检索方案?

这时候即使底座模型很强,也可能出现几个典型问题:

  • 它不知道这份文档压根存在
  • 它知道相关概念,但不知道你这份资料里的具体结论
  • 它会用训练时学到的常识“脑补”答案,产生幻觉
  • 它回答得很流畅,但无法给出依据

于是问题就从“模型会不会回答”,变成了:

怎样把外部知识表示成模型可用的检索对象,并在用户提问时快速找回最相关内容。

这正是本章要解决的事。

从全书结构上看,这一章有三个作用:

  • 它标志着我们从“训练模型”正式走向“构建应用”
  • 它先把 RAG 里最关键的检索基础讲清楚,避免下一章一上来就把流程混在一起
  • 它也是很多大模型岗位面试里的高频主题,因为你很少需要从零训练一个大模型,但经常需要把 embedding、检索、向量库接进业务系统

如果说前几章回答的是:

怎样把一个模型训练得更像一个可用助手。

那么这一章要回答的就是:

当助手需要访问训练语料之外的新知识时,我们怎样把文本映射成向量、建立检索系统,并在海量候选内容里找回语义上最相关的信息。

2. 你学完后应该会什么

  • 能解释什么是 embedding,以及它为什么能支持语义相似度计算
  • 能区分 sparse retrieval、dense retrieval、hybrid retrieval 的核心差异
  • 能理解 cosine similarity、dot product、L2 distance 在向量检索里的直觉
  • 能描述一个最小可用的语义检索流水线:切块、编码、建库、召回、重排
  • 能说清 chunking、metadata、top-k、reranking 分别在系统里扮演什么角色
  • 能理解为什么需要向量数据库,以及它解决的主要是“规模化检索”问题
  • 能自然衔接到下一章的 RAG 系统设计

3. 为什么关键词搜索不够了

先从最直观的问题开始。

很多同学第一次做“文档问答”时,会本能地想:

能不能先用关键词匹配?

当然可以,而且在很多场景下它依然有效。
但只靠关键词搜索,你很快会碰到几个明显限制。

3.1 词面匹配不等于语义匹配

假设知识库里有一句话:

Transformer 通过自注意力机制建模全局依赖,因此训练时具有更强的并行性。

用户的问题是:

为什么 Transformer 比 RNN 更适合并行训练?

这两个文本表达的是同一件事,但用词并不完全一致:

  • 文档里写的是“自注意力”“全局依赖”“并行性”
  • 用户问的是“适合并行训练”

如果系统只看关键词重合,可能就会漏掉这段本来很相关的内容。

3.2 现实问题里同义表达很多

在实际业务中,同一个意思常常有很多说法:

  • “报销” 和 “费用申请”
  • “离职” 和 “解除劳动关系”
  • “embedding” 和 “向量表示”
  • “召回” 和 “retrieve relevant chunks”

如果系统只能按字面匹配,它就会非常依赖你是否刚好说中了文档里的原词。

3.3 大模型应用更关心“相关内容”,不是“同词内容”

RAG 或知识问答的目标,通常不是找出“和问题长得最像”的句子,而是找出:

最有助于回答这个问题的证据。

而“有助于回答”往往是语义层面的,不只是词面层面的。

这就是 embedding 出场的原因。

4. Embedding 到底是什么

最朴素地说:

embedding 就是把离散对象映射成连续向量表示。

这里的对象可以是:

  • 一个词
  • 一个句子
  • 一段文本
  • 一篇文档
  • 一张图片
  • 甚至用户、商品、代码片段

在本章里,我们主要讨论文本 embedding。

4.1 为什么要把文本变成向量

因为计算机更擅长在向量空间里做运算。

一旦文本被编码成固定维度的向量,我们就可以:

  • 计算两个文本有多相似
  • 在海量文本里找到“最近邻”
  • 做聚类、分类、去重、推荐
  • 把用户问题和文档片段放到同一个语义空间里比较距离

所以 embedding 的核心价值不是“把文本变成数字”这么简单,而是:

让语义上的接近,可以被几何空间中的接近近似表达。

4.2 一个直觉类比

你可以把 embedding 空间想成一张高维地图。

  • 语义相近的文本,位置更接近
  • 语义差异大的文本,位置更远
  • 某些方向可能对应“主题变化”“语气变化”“任务类型变化”

当然,真实向量空间远比地图复杂,也不一定容易可视化。
但这个“语义映射到几何距离”的直觉非常重要。

4.3 Embedding 模型在学什么

embedding 模型并不是简单统计词频,而是在训练过程中学会:

让相关文本的表示更接近,不相关文本的表示更远。

不同 embedding 模型的训练目标可能不同,例如:

  • 对比学习:拉近正样本,拉远负样本
  • 双塔检索训练:让 query 和相关 document 更接近
  • 句向量训练:让语义相似句子更接近

所以 embedding 向量里的信息,不只是“出现了哪些词”,还包含一定程度的语义结构。

5. 相似度是怎么计算的

文本变成向量之后,下一步就是比较“谁更接近谁”。

最常见的相似度或距离度量有三类。

5.1 Cosine Similarity

最常见的是 cosine similarity,定义为:

cos(x, y) = (x · y) / (||x|| ||y||)

它关心的重点是:

两个向量方向是否接近。

直觉上,如果两个文本表达的语义方向相似,即使向量长度不同,cosine similarity 也可能很高。

它非常常见,因为很多时候我们更关心“语义方向像不像”,而不是绝对长度。

5.2 Dot Product

dot product 也很常见:

x · y

如果向量已经做过归一化,那么 dot product 和 cosine similarity 在排序上会非常接近。
但如果没有归一化,dot product 还会受向量模长影响。

这在工程里很重要,因为:

  • 有的模型默认输出已归一化向量
  • 有的向量库底层直接优化 inner product 检索
  • 有的系统会把 cosine 检索转成 normalized dot product 来做

5.3 L2 Distance

另一种常见做法是欧氏距离:

||x - y||_2

距离越小,表示越接近。

它也能用于近邻搜索,但在文本语义检索里,大家更常提的是 cosine similarity 或 inner product。

5.4 先记住工程结论

入门阶段不用过早纠结哪一个“理论上绝对最好”,更重要的是先记住:

  • embedding 把文本放进向量空间
  • 检索就是在这个空间里找最近的候选
  • 你的相似度定义,会直接影响召回结果

6. Sparse Retrieval、Dense Retrieval 与 Hybrid Retrieval

理解现代检索系统,最重要的一组对比就是这三个词。

6.1 Sparse Retrieval:基于词项的检索

sparse retrieval 典型代表是 TF-IDF、BM25。

它的核心思想是:

把文本表示成“哪些词出现了、出现了多少、这些词稀不稀有”。

优点:

  • 可解释性强
  • 对精确关键词命中很有效
  • 不需要训练 embedding 模型也能工作
  • 对专有名词、编号、特定术语常常很强

缺点:

  • 词面不一致时容易漏召回
  • 难以处理同义改写、语义泛化
  • 对自然语言问法变化更敏感

6.2 Dense Retrieval:基于向量的检索

dense retrieval 的核心思想是:

把 query 和文档都编码成稠密向量,再按向量相似度检索。

优点:

  • 能捕捉一定语义相似性
  • 对改写、近义表达更鲁棒
  • 更适合自然语言问答和知识检索

缺点:

  • 对精确字符串、版本号、产品型号不一定稳定
  • embedding 模型质量会直接影响检索效果
  • 大规模检索需要 ANN 索引和向量库支持

6.3 Hybrid Retrieval:把两者结合

现实系统里,很多团队最后都会走向 hybrid retrieval,也就是:

既看关键词信号,也看语义向量信号。

因为两类方法各有偏好:

  • sparse 擅长精确命中
  • dense 擅长语义泛化

把它们结合起来,通常能减少各自的短板。

例如一个问题里既有非常关键的产品编号,又有自然语言描述时,hybrid 常常比单一路线更稳。

7. 一个最小语义检索系统长什么样

把概念放到工程流程里,一般最小可用系统会经过下面几步:

  1. 准备原始文档
  2. 把文档切成适合检索的 chunk
  3. 用 embedding 模型把每个 chunk 编码成向量
  4. 把向量和 metadata 存进向量数据库
  5. 用户提问时,把 query 也编码成向量
  6. 在向量库里做近邻搜索,取回 top-k 候选
  7. 必要时做 reranking,再把结果交给后续生成模块

注意这里最容易被忽略的一点:

语义检索不是只有“算 embedding”这一步,前后的数据组织同样决定效果。

8. Chunking:为什么不能整篇文档直接塞进去

很多人第一次做知识库时会问:

为什么不把一整篇文档编码成一个向量?

因为这样通常太粗了。

8.1 文档太长,语义会被平均掉

一篇长文档里可能同时包含:

  • 背景介绍
  • 方法说明
  • 结果分析
  • 风险提示
  • 附录

如果整个文档只对应一个向量,那么用户只问其中一个细节时,这个大向量往往不够精确。

8.2 检索的目标通常是找证据片段

RAG 里真正要喂给模型的,通常不是“整个知识库里最相关的一整篇文档”,而是:

最能支撑当前问题回答的那几段内容。

所以我们通常会先切 chunk。

8.3 Chunk 太小也不行

但 chunk 也不是越小越好。太小会带来几个问题:

  • 上下文不完整
  • 语义边界被切碎
  • 召回到的内容不够回答问题
  • 后续生成时缺证据链

所以 chunking 的本质,是在两件事之间找平衡:

  • 检索粒度够细
  • 每个片段又保留足够语义上下文

8.4 常见 chunking 策略

常见做法包括:

  • 按固定 token 长度切分
  • 按段落、标题、小节结构切分
  • 设置 overlap,让相邻 chunk 共享一部分内容
  • 对表格、代码、问答对做特殊切分

工程里通常没有一刀切的最佳答案。
文档类型不同,最优 chunking 策略也会不同。

9. Metadata:为什么检索不只是向量

一个成熟的检索系统里,存的通常不只是:

  • chunk 文本
  • embedding 向量

还会带上 metadata,例如:

  • 文档标题
  • 来源链接
  • 作者
  • 时间
  • 章节名
  • 权限标签
  • 业务域标签

这些信息有几个关键作用。

9.1 用于过滤

例如你可以要求只在:

  • 某个项目空间内搜索
  • 最近三个月文档里搜索
  • 用户有权限访问的资料里搜索

9.2 用于排序增强

有些场景下,除了语义相似度,你还希望考虑:

  • 文档新鲜度
  • 来源可信度
  • 是否来自官方知识源

9.3 用于答案可追溯

如果你想让系统回答时附出处,那 metadata 几乎是必须的。
否则模型即使答对了,也很难告诉用户“这段信息来自哪里”。

10. 为什么需要向量数据库

如果文档量很小,你当然可以把所有向量放进内存,逐个算相似度。
这叫 brute-force 检索。

但当数据量大起来,问题就来了:

  • 向量数量很多
  • 每个向量维度不低
  • 用户查询要低延迟返回
  • 还要支持增量写入、过滤、删除、权限控制

这时就需要向量数据库或向量检索引擎。

10.1 它解决的核心不是“存”,而是“快而稳地找”

向量数据库最核心的价值不是把向量放进去,而是:

在大规模数据上高效做相似向量搜索。

这通常会依赖 ANN,也就是 approximate nearest neighbor,近似最近邻搜索。

它的思想是:

不必每次都和全量向量精确比较,只要用更快的方法找到足够好的近邻。

这样就能在召回质量和查询延迟之间做工程折中。

10.2 常见能力

一个向量数据库通常会提供:

  • 向量写入与更新
  • top-k 相似检索
  • metadata filter
  • 删除与重建索引
  • 持久化和服务化接口

10.3 常见工具

学习和项目里常会看到这些名字:

  • FAISS:非常经典的向量检索库,更偏底层
  • Chroma:上手简单,适合原型和教学
  • Milvus:更偏独立向量数据库
  • Weaviate、Qdrant、pgvector:也都很常见

入门时不用执着于“谁最好”,更重要的是理解:

无论底层工具是谁,系统都在做同一件事:把 query 向量和文档向量匹配起来,并在规模化条件下尽量又快又准。

11. Reranking:为什么召回之后还要再排一次

很多初学者以为检索系统做到 top-k 召回就结束了。
其实在较完整的系统里,常常还会有 reranking。

11.1 召回阶段更像“先别漏掉”

第一阶段检索,目标通常是:

先从大库里找出一小批大概率相关的候选。

这时更看重召回率,不希望真正相关内容被漏掉。

11.2 重排阶段更像“把最该看的放前面”

有了几十条候选之后,我们可以用更贵但更准的方法重新排序,比如:

  • cross-encoder reranker
  • 更强的相关性打分模型
  • 基于业务规则的重排

因为这时候候选集合已经小很多,所以你可以接受更复杂的打分成本。

11.3 为什么这一步很重要

因为 dense retrieval 常常能把“差不多相关”的内容一起召回来,但:

  • 哪段最适合直接回答当前问题
  • 哪段证据最完整
  • 哪段最应该排在第 1 位

往往还需要更细致的比较。

所以一个常见的工业级思路是:

先粗召回,再精重排。

12. 一个简单例子:从问题到检索结果

假设你的知识库里有三段文本:

Chunk A: Transformer 使用 self-attention 建模 token 之间关系。
Chunk B: RNN 依赖时间步递归,因此训练时并行性较弱。
Chunk C: Transformer 去掉了顺序递归结构,因此在训练阶段更容易并行计算。

用户问题是:

为什么 Transformer 比 RNN 更适合并行训练?

一个比较合理的检索过程会是:

  1. 把用户问题编码成 query embedding
  2. 把三个 chunk 的 embedding 和 query 比较相似度
  3. 发现 Chunk C 最相关,Chunk B 次相关,Chunk A 也有帮助
  4. 返回 C + B (+ A) 作为候选证据

这里你会发现:

  • Chunk C 直接回答“为什么更容易并行”
  • Chunk B 提供了和 RNN 的对照依据
  • Chunk A 补充了机制背景

这也说明检索不一定只找“唯一答案块”,很多时候它找的是:

一组能共同支撑回答的问题相关证据。

13. 做语义检索时最常见的坑

这一节很适合面试,也很适合做项目时提前避坑。

13.1 Embedding 模型和任务不匹配

不是所有 embedding 模型都适合同一种任务。

例如有的更偏通用语义匹配,有的更偏搜索,有的更适合中英混合,有的更偏代码。
如果模型选型不对,后面再怎么调 top-k 都可能救不回来。

13.2 Chunk 切分不合理

太大,召回不准。
太小,信息不全。
不做 overlap,语义边界容易断裂。
只按字数硬切,可能把表格、代码、标题结构切坏。

13.3 只看检索分数,不做人类抽样检查

很多系统离线分数看起来还行,但抽样一看会发现:

  • 召回的段落表面相关,实际没法回答问题
  • 高分 chunk 有重复内容
  • 真正关键段落排位太后

所以检索系统一定要结合人工 case review。

13.4 过度迷信向量检索

有些场景下,关键词本来就很关键,比如:

  • 产品编号
  • 法条编号
  • API 名称
  • 精确字段名

这时如果只做 dense retrieval,效果可能反而不如 BM25 或 hybrid。

13.5 忽略数据治理

检索系统效果差,很多时候不是模型不行,而是数据本身有问题,例如:

  • 文档版本混乱
  • 重复内容太多
  • 过期资料没有清理
  • 权限标签缺失

应用工程里一个很现实的结论是:

垃圾进,垃圾出。

14. 评估一个检索系统,应该看什么

如果你做的是生成模型,大家常问 loss、BLEU、ROUGE、准确率。
但做 retrieval,更常见的是另一套指标思路。

14.1 先看“有没有把相关内容找回来”

例如:

  • Recall@k:前 k 个结果里有没有覆盖到相关文档
  • Hit@k:前 k 个里是否至少命中一个正确项

这类指标关注的是:

该找回来的,有没有被找回来。

14.2 再看“排得好不好”

例如:

  • MRR
  • NDCG

它们关注的是:

相关结果是不是排得足够靠前。

因为在真实系统里,后续模型能看到的上下文窗口有限,排位靠后的 chunk 很可能根本用不上。

14.3 最后还要看端到端效果

检索做得好,不等于最终问答体验就一定好。
因为后面还会受这些环节影响:

  • prompt 拼接
  • 上下文截断
  • 生成模型能力
  • 引用格式
  • 拒答策略

所以检索评估最好分层看:

  • 检索层指标
  • RAG 端到端问答指标
  • 人工体验评估

15. 和下一章 RAG 的关系

到了这里,你应该已经能看出:

RAG 并不是“突然多了一个神奇模块”,而是在 embedding 检索的基础上,把检索结果喂给生成模型。

更具体地说:

  • 第十五章解决的是“怎么找资料”
  • 第十六章解决的是“找回来的资料,怎么组织进 prompt,并生成可信回答”

所以如果你对 RAG 的理解总是糊成一团,通常不是因为 RAG 太复杂,而是因为你还没有把它拆成两层:

  1. retrieval:从知识库中找相关证据
  2. generation:基于证据生成回答

本章讲清楚的,就是第一层。

16. 本章小结

这一章最核心的主线可以压缩成下面几句话:

  • embedding 的作用,是把文本映射到可以比较语义距离的向量空间
  • 语义检索的目标,不是找字面最像的文本,而是找最相关的证据
  • dense retrieval 能补足关键词搜索的语义短板,但 sparse retrieval 依然重要
  • 一个可用系统通常不止有 embedding,还包括 chunking、metadata、top-k 检索和 reranking
  • 向量数据库解决的重点,是规模化条件下高效做近邻搜索
  • 这些能力合在一起,才构成下一章 RAG 的检索基础

如果你把前面的训练章节看作“如何把模型练好”,那么从这一章开始,我们正式进入另一条主线:

如何把模型接进真实世界的数据与系统。

而这,正是大模型应用工程最常见、也最有岗位价值的一段路。

17. 面试高频题

17.1 什么是 embedding?它和 hidden state 有什么关系?

可以先用应用视角回答:

embedding 是把文本映射成向量表示,使得相似文本在向量空间中更接近。
而 hidden state 更像模型内部中间表示,不一定直接拿来做检索。
实际工程里说 embedding,通常指经过专门训练并可直接用于相似度搜索的向量。

17.2 BM25 和 dense retrieval 有什么区别?

BM25 更依赖词项匹配和词频统计,擅长精确关键词检索。
dense retrieval 依赖 embedding 的语义表示,擅长处理改写和近义表达。
很多真实系统会做 hybrid,把两者结合。

17.3 为什么 RAG 里要切 chunk?

因为整篇文档粒度太粗,难以精确定位证据;但太小又会丢上下文。
切 chunk 是为了在“检索精度”和“语义完整性”之间做平衡。

17.4 向量数据库和传统数据库有什么不同?

传统数据库更擅长精确过滤、结构化查询、事务等;向量数据库更擅长基于向量相似度做近邻搜索。
在 RAG 系统里,两者常常是互补关系,而不是谁替代谁。

17.5 为什么召回之后还要 rerank?

因为第一阶段通常更强调别漏掉相关候选,排序未必最优。
rerank 可以在小候选集上用更贵但更准的方法,把最该看的证据排到前面。

18. 动手建议

如果你准备把这一章变成求职项目,最推荐的最小实践路线是:

  1. 找一份结构比较清晰的小型文档集,例如课程讲义、博客文章或项目文档
  2. 自己实现一个 chunking 脚本,并记录不同 chunk 大小的效果差异
  3. 用一个 embedding 模型把 chunk 建成向量索引
  4. 对比 BM25、dense retrieval、hybrid retrieval 的检索结果
  5. 手动构造 20 到 50 个问题,做一次简单的 Recall@k 抽样评估
  6. 在下一章把它接成一个完整的 RAG Demo

这样做的价值很高,因为你最终展示给面试官的就不再是:

“我会调一个 RAG 框架。”

而是:

“我知道检索系统为什么会好,也知道它为什么会坏。”

这两者的含金量差别非常大。