上一篇我们搞定了“身体”(Milvus 向量数据库),这一篇我们要赋予系统“灵魂”(语义理解)。
很多开发者有一个误区:认为 RAG 的效果主要取决于大模型(LLM)的智商。其实不然,RAG 的“生死线”在于检索(Retrieval)。 如果你喂给 LLM 的是垃圾文档,在好的LLM 也救不了你。而决定检索质量的核心,就是 Embedding(向量化) 和 Rerank(重排序) 模型。
在上一章中,我们搭建了 Milvus 向量数据库,它就像是一个巨大的、极其高效的图书馆书架。但是,把书放上去只是第一步,最难的是:当用户问“苹果怎么吃”时,你怎么知道要把关于“水果”的书给他,而不是关于“iPhone 手机”的说明书?
这就需要一个能够理解人类语言隐含意义的“翻译官”,将文字转化为计算机能理解的数字(向量)。这个过程叫 Embedding。
目前在中文开源社区,BGE (BAAI General Embedding) 是当之无愧的王者。但它是如何工作的?它和 NLP 的祖师爷 BERT 有什么关系?为什么我们还需要一个叫 Reranker 的东西?
本文将为你揭开这些黑盒。
1. 始祖:BERT 的“双向”革命
要理解 BGE,必须先致敬 BERT。
在 BERT 出现之前,语言模型(如 Word2Vec)理解词汇通常是单向的,或者缺乏上下文。比如“银行”这个词,在“河岸边”和“存钱”这两个场景下意思是完全不同的。
BERT (Bidirectional Encoder Representations from Transformers) 的伟大之处在于它是**双向(Bidirectional)**的。它通过一种叫 Masked Language Model (完形填空) 的方式进行预训练:
“今天天气[MASK]错,适合出去玩。”
BERT 必须同时看左边的“今天天气”和右边的“适合出去玩”,才能猜出 [MASK] 是“不”。这种机制让 BERT 拥有了极强的上下文感知能力。
但在 RAG 中,原始 BERT 有一个致命缺陷:
它不擅长做“语义相似度检索”。如果你直接提取 BERT 的向量(通常取 [CLS] token)来算余弦相似度,你会发现所有句子的向量都挤在一起(各向异性问题),很难区分谁跟谁更像。
2. 进化:BGE 是如何炼成的?
BGE (BAAI General Embedding) 是智源研究院基于 BERT 架构(具体是 RetroMAE 变体)深度改造而成的。它之所以能霸榜 C-MTEB(中文嵌入模型榜单),主要做对了三件事:
A. 预训练的魔法:RetroMAE
BGE 使用了一种更激进的预训练任务。它不仅是简单的“完形填空”,而是故意把句子弄得支离破碎(Mask 掉很大比例),强迫编码器(Encoder)去重构原始语义。这让模型生成的向量具有更强的鲁棒性和语义浓缩能力。
B. 对比学习 (Contrastive Learning)
这是 BGE 变强的核心。原始 BERT 是自己学自己,BGE 则是**“成对”**地学。
- 正例 (Positive): (用户问题, 正确答案) -> 拉近它们的向量距离。
- 负例 (Negative): (用户问题, 错误文档) -> 推远它们的向量距离。
BGE 使用了海量的精选中文语料对进行这种训练,硬生生地把 BERT 这个“通才”训练成了专门判断“这就话和那句话是不是一回事”的“专才”。
C. 指令微调 (Instruction Tuning)
BGE 引入了指令机制。对于查询(Query),它要求你加上前缀:
为这个句子生成表示以用于检索相关文章:这就像告诉模型:“嘿,我现在是在做搜索任务,别光顾着理解字面意思,要去想这句话背后的搜索意图。”
3. 架构之争:双塔 vs 单塔 (Bi-Encoder vs Cross-Encoder)
在 RAG 的实际落地中,我们经常听到 Embedding 和 Reranker。它们其实代表了两种不同的神经网络架构。
Embedding 模型 (Bi-Encoder / 双塔)
这也是 Milvus 配合的模型(如 bge-base-zh)。
- 原理:
- 句子 A 进模型 -> 变向量 A。
- 句子 B 进模型 -> 变向量 B。
- 计算 A 和 B 的距离(点积或余弦)。
- 特点: 句子 A 和 句子 B 在进入模型时是互不见面的,各自独立编码。
- 优势: 极快。因为库里的文档可以提前算好向量存起来。搜索时只需要算一次 Query 的向量,然后做向量运算(Milvus 擅长这个)。
- 劣势: 精度一般。因为丢失了句子间的细微交互信息。
Reranker 模型 (Cross-Encoder / 单塔)
这是 BGE-Reranker 的架构。
- 原理:
- 把 句子 A 和 句子 B 拼起来:
[CLS] 句子A [SEP] 句子B。 - 扔进 BERT 模型里,让 Attention 机制疯狂计算 A 中每个字和 B 中每个字的交互关系。
- 直接输出一个 0~1 之间的分数(相关性)。
- 把 句子 A 和 句子 B 拼起来:
- 特点: 它们是深度交互的。模型能像人类一样仔细比对细节。
- 优势: 极准。能识别出逻辑陷阱、否定词等微小差异。
- 劣势: 极慢。你不能提前算好,必须实时算。如果库里有 100 万条数据,算一次要几小时,根本没法上线。
4. 黄金组合:漏斗式检索 (The Funnel Pipeline)
既然 Embedding 快但不准,Reranker 准但不快,RAG 的最佳实践就是把它们结合起来,形成一个**“漏斗”**:
graph TD
A[海量文档库 (100万+)] -->|1. Embedding (Bi-Encoder)| B(Milvus 向量检索)
B -->|粗排: 选出 Top 100| C[候选集 (100条)]
C -->|2. Reranker (Cross-Encoder)| D(精细重排序)
D -->|精排: 选出 Top 3| E[最终上下文]
E -->|拼接 Prompt| F[大模型 LLM]
- 第一层(粗排): 使用 Milvus + BGE-base-zh。从海量数据中快速捞出最像的 100 条。虽然可能混进去几个不相关的,但保证了速度(毫秒级)。
- 第二层(精排): 使用 BGE-Reranker。对这 100 条进行深度阅读打分,把真正相关的排到最前面,截取 Top 3。
- 结果: 既享受了 Milvus 的速度,又拥有了 Cross-Encoder 的精度。
5. 避坑指南
在使用 BGE 系列模型时,有几个常见的坑需要注意:
- Query 前缀: 使用
bge-base-zh时,仅对 Query(问题) 添加指令前缀为这个句子生成表示以用于检索相关文章:,对于入库的文档(Passage)不要加。 - 距离度量: BGE 官方推荐使用 Inner Product (IP / 点积) 距离,而不是 L2(欧氏距离)。并且建议开启
normalize_embeddings=True。 - 最大长度: BGE 的最大上下文通常是 512 tokens。如果你的文档很长,必须先进行切片(Chunking),否则超出的部分会被截断丢弃。
总结
如果说 Milvus 是 RAG 的“海马体”(记忆),那么 BGE 就是 RAG 的“布罗卡区”(语言处理中枢)。
理解了 Bi-Encoder 和 Cross-Encoder 的区别,你就掌握了提升 RAG 准确率的一把钥匙。但是,光有理论是不够的。
在下一篇文章中,我们将拿出键盘,通过 Python 代码,使用 LangChain + Milvus + BGE + Reranker 亲手搭建这套流水线。