从 if 判断到 BM25,搞懂 RAG 中的“稀疏向量”

在学习 RAG(检索增强生成)的时候,大家一上来接触的通常都是大名鼎鼎的 Embedding(稠密向量,Dense Vector)。但随着深入接触 混合检索(Hybrid Search),另一个概念便无法绕开——稀疏向量(Sparse Vector)

以前我总觉得,关键词检索不就是用 for 循环遍历每一个文本块(Chunk),然后用 if 关键词 in text 做字面匹配吗?为什么它也能被称为“向量”?

今天这篇笔记,就彻底拆解一下稀疏向量的底层逻辑,以及它在 LangChain 和谷歌搜索中是如何优雅运转的。

💡 为什么关键词匹配变成了“向量”?

如果只有一两篇文章,用 if 判断确实最直观。但如果面对海量文本(比如几百万个 Chunk),让 CPU 去做纯字符串的 for 循环遍历,效率会低到令人发指。

为了能用数学矩阵的方式进行超大规模的计算加速,科学家们把“关键词匹配”套上了一层数学马甲,将其变成了向量。

向量化的三步走策略:

  1. 建立全局词表:把系统里所有文档去重后,编上号做成一个巨大的词表。假设词表里一共有 30,000 个不重复的词。
  2. 用数字代替 if:每篇文档(Chunk)都用一个长度为 30,000 的数字数组(也就是向量)来表示。如果词表里第 5 个位置的词在文档里出现了,就把该位置的值设为 1(或其权重),没出现就设为 0。
  3. 为什么叫“稀疏”?:一句话或一个短 Chunk 通常只有几十个词。也就是说,在这 30,000 个位置里,只有那几十个位置是有数值的,其余 29,000+ 个位置全部是 0

结论:因为向量中 $0$ 的数量远远大于非 $0$ 的数量,所以被称为稀疏向量。它本质上就是“关键词匹配”的数学表达。

🛠️ 以 LangChain + BM25 为例:长文本的检索流程

当我们用 LangChain 框架,把一篇 8000 字的长文章喂给 BM25Retriever 时,底层代码到底是怎么跑的?

第一阶段:构建本地“账本”

  1. 文本切分 (Chunking):利用 TextSplitter 把 8000 字切成(例如)16 个小 Chunk。
  2. 分词与统计:在本地用分词工具(如 jieba)把所有 Chunk 读一遍,构建出这篇长文章的全局词表。
  3. 计算 BM25 权重:16 个 Chunk 各自生成对应的稀疏向量。在 BM25 算法中,向量里的非 0 元素不是简单的出现次数,而是经过了两个核心逻辑优化的“魔改权重”:
    • 词频饱和度(TF Saturation):一个词出现 20 次和 21 次,重要性几乎无异,权重增长有“天花板”,防止刷分。
    • 篇章长度惩罚:短文本里出现关键词,比长篇大论里偶尔提到一次更聚焦,短文本会获得更高的权重。

第二阶段:在线检索(Query 匹配)

当你输入问题:“什么是遥感图像的稀疏表示?”

  1. Query 向量化:Query 同样被分词,并根据全局词表转成一个稀疏向量。
  2. 矩阵内积(Dot Product):计算机拿着 Query 的向量去和那 16 个 Chunk 的稀疏向量做点乘。如果完全没重合,结果就是 0;如果关键词重合得多,得分就高。
  3. Top-K 排序:整个过程完全在本地 CPU 和内存中完成,通过高效的矩阵乘法瞬间算出 16 个 Chunk 的得分,并把最高分的文本块捞出来喂给大模型。

🔍 现代 RAG 的天平:稀疏 vs 稠密

为什么有了能理解语义的稠密向量(Dense),我们依然离不开稀疏向量(Sparse)?

因为稀疏向量拥有独特的“精准匹配”绝活:

  • 抓取专有名词与型号:稠密向量擅长模糊理解,但容易把 iPhone 13iPhone 14 搞混。稀疏向量则会死死咬住字面,实现精准拦截。
  • 应对未登录词(OOV):面对新网络梗、特定的代码报错、行业内生僻的专业术语,稀疏向量只要“看字对眼”就能稳定发挥。

在现代 RAG(包括谷歌搜索的底层)中,最强的玩法通常是混合检索(Hybrid Search)

把稠密向量的“情商”(懂语义)和稀疏向量的“智商”(精准对齐)结合起来,才是 RAG 走向工业落地的标准姿势。


本站由 楠瓜 使用 Stellar 1.33.1 主题创建。
风起于青萍之末,浪成于微澜之间。