一、简介

RAG 是 Retrieval-Augmented Generation,中文通常叫 检索增强生成

RAG 可以表示为:

$$\text{Answer}=\operatorname{LLM}\left(\text{Query},\operatorname{Retrieve}(\text{Query},\mathcal{D})\right)$$

其中:Query为用户问题,D为外部文档集合,Retrieve为检索器

RAG 将知识分成两类:

  • 参数化知识(Parametric Memory):存储在模型参数中的知识;
  • 非参数化知识(Non-parametric Memory):存储在文档、数据库、向量库、网页或知识图谱中的知识。

传统大语言模型主要依赖参数化知识:$$P_\theta(y\mid x)$$

RAG 在生成时额外引入外部文档 z:$$P(y\mid x)=\sum_{z\in\mathcal{Z}}P_\eta(z\mid x)P_\theta(y\mid x,z)$$

其中:x为输入问题,z为检索到的文档,y为生成答案

$$P_\eta(z\mid x)$$检索器认为文档 z 与问题 x 相关的概率;

$$P_\theta(y\mid x,z$$:生成器根据问题和文档生成答案的概率。

二、流程

c54baf4b0c218af4f39ff6a259fc02c8.png

1.文档加载

设文档集合为:$$\mathcal{D}={D_1,D_2,\ldots,D_N}$$

每个文档通常包含正文和元数据:$$D_i=(\text{content}_i,\text{metadata}_i)$$

2.文档清洗

常见操作包括:

  • 删除空白和重复内容;
  • 去掉网页导航栏、页眉、页脚;
  • 保留标题结构;
  • 识别代码块、表格和公式;
  • 添加来源、时间和权限信息。

清洗很重要。原始数据很乱时,后面的检索效果通常也不会好。

3.文档切分

文档太长,不能直接全部交给模型,所以需要切成小块:$$D_i\rightarrow{c_{i,1},c_{i,2},\ldots,c_{i,m}}$$

常用参数:

  • chunk_size:每块长度;
  • chunk_overlap:相邻块的重叠长度。

假设文档长度为 T,块长度为 L,重叠长度为 O,块数大约为:$$M\approx\left\lceil\frac{T-L}{L-O}\right\rceil+1$$

块太小,信息容易被切断;块太大,又会混入很多无关内容。

4,文档向量化

Embedding 模型把文本转换为向量:$$\mathbf{v}_i=f(c_i)\in\mathbb{R}^d$$

语义接近的文本,向量通常也比较接近。

5.建立索引

保存文档块、向量和元数据:$$\mathcal{I}={(\mathbf{v}_i,c_i,\text{metadata}_i)}$$

6.查询和检索

用户问题 q 也会被转换为向量:$$\mathbf{q}=f(q)$$

然后计算它和文档向量的相似度,取分数最高的 k 个文档:$$\operatorname{TopK}(q)=\underset{c_i}{\operatorname{arg,topk}};s(\mathbf{q},\mathbf{v}_i)$$

7.重排序

第一次检索主要追求速度,结果不一定非常准确。

因此可以再使用 Reranker 对候选文档重新打分:$$r_i=g(q,c_i)$$

典型流程为:

$$\text{快速召回 Top-20}\rightarrow\text{重排序后取 Top-5}$$

8.生成答案

将检索结果和问题一起交给大模型:$$P=\text{Instruction}\Vert\text{Context}\Vert q$$

最终生成:$$P_\theta(y\mid q,C)=\prod_{t=1}^{T}P_\theta(y_t\mid y_{<t},q,C)$$

三、检索的数学基础

1.TF-IDF

TF-IDF 用于衡量一个词在某篇文档中的重要程度。

词频:$$\operatorname{TF}(t,D)=\frac{f(t,D)}{|D|}$$

逆文档频率:$$\operatorname{IDF}(t)=\log\frac{N+1}{\operatorname{df}(t)+1}+1$$

最终:$$\operatorname{TFIDF}(t,D)=\operatorname{TF}(t,D)\cdot\operatorname{IDF}(t)$$

TF-IDF 适合关键词检索,但不擅长理解同义词。

2.BM25

BM25 是实际搜索系统中很常用的稀疏检索算法:$$\operatorname{BM25}(Q,D) =\sum_{t\in Q}\operatorname{IDF}(t) \frac{f(t,D)(k_1+1)} {f(t,D)+k_1\left(1-b+b\frac{|D|}{\operatorname{avgdl}}\right)}$$

其中:

f(t,D):词 t 在文档中的出现次数;

|D|:文档长度;

avgdl:平均文档长度;

k_1:控制词频影响;

b:控制文档长度影响。

3.余弦相似度

向量检索最常见的相似度是余弦相似度:

$$\operatorname{cos}(\mathbf{a},\mathbf{b}) =\frac{\mathbf{a}\cdot\mathbf{b}} {|\mathbf{a}|2|\mathbf{b}|2}=\frac{\sum{i=1}^{d}a_ib_i} {\sqrt{\sum{i=1}^{d}a_i^2}\sqrt{\sum_{i=1}^{d}b_i^2}}$$

值越接近 1,通常说明两个文本越相似。

如果向量已经归一化:$$\hat{\mathbf{a}}=\frac{\mathbf{a}}{|\mathbf{a}|_2}$$

则内积和余弦相似度相同:$$\hat{\mathbf{a}}^{\mathsf T}\hat{\mathbf{b}} =\operatorname{cos}(\mathbf{a},\mathbf{b})$$

4.双编码器

密集检索通常分别编码问题和文档:$$\mathbf{q}=f_q(q),\qquad\mathbf{d}=f_d(d)$$

相关性为:$$s(q,d)=\mathbf{q}^{\mathsf T}\mathbf{d}$$

文档向量可以提前算好,所以检索速度很快。

5.对比学习

训练检索模型时,希望问题和正确文档接近,和错误文档远离:

$$\mathcal{L} =-\log \frac{\exp(s(q,d^+)/\tau)} {\exp(s(q,d^+)/\tau)+\sum_j\exp(s(q,d_j^-)/\tau)}$$

其中:

  • $$d^+$$正确文档;
  • $$d_j^-$$错误文档;
  • $$\tau$$温度参数。

6.RAG 概率模型

RAG 的基本思想可以表示为:$$P(y\mid x)=\sum_{z\in\mathcal{Z}} P_\eta(z\mid x)P_\theta(y\mid x,z)$$

其中:

  • $$P_\eta(z\mid x)$$检索器认为文档 z 相关的概率;
  • $$P_\theta(y\mid x,z)$$生成器根据文档生成答案的概率。

四、常见检索方法

1.稀疏检索

常见算法:

  • TF-IDF;
  • BM25;
  • 倒排索引。

优点:精确匹配强,适合编号、函数名和关键词。

缺点:不太理解同义词和自然语言改写。

2.密集检索

将查询和文档转换为向量,再做语义匹配。

优点:能找到“意思相近但关键词不同”的内容。

缺点:可能忽略精确编号和特殊符号。

3.混合检索

实际项目中,BM25 和向量检索经常一起使用:$$s_{\text{hybrid}} =\alpha s_{\text{dense}}+(1-\alpha)s_{\text{sparse}}$$

也可以使用 RRF 融合排名:$$\operatorname{RRF}(d) =\sum_{r\in\mathcal{R}}\frac{1}{K+\operatorname{rank}_r(d)}$$

对于安全和代码场景,混合检索通常比单独使用向量检索更可靠。

4.多查询检索

把一个问题改写为多个问题:$$q\rightarrow{q_1,q_2,\ldots,q_m}$$

多个查询分别检索,再合并结果,可以提高召回率

5.父子文档检索

  • 小块用于检索
  • 大块用于提供上下文

设子块 c_{i,j} 属于父文档 P_i:

$c_{i,j}\subset P_i$

命中小块后,返回更完整的父文档:$$\operatorname{Return}(c_{i,j})=P_i$$

这种方式比较适合教材、论文和源码文件。

五、向量索引

1.Flat

直接将查询向量与所有文档向量比较:$$O(Nd)$$

优点是结果精确,缺点是数据量大时速度慢。

适合:

  • 小型知识库;
  • 学习和测试;
  • 作为其他索引的效果基准

2.HNSW

HNSW 使用多层近邻图进行搜索。

常见参数:

  • M:每个节点连接多少邻居;
  • efConstruction:建库时搜索范围;
  • efSearch:查询时搜索范围。

efSearch 越大,结果通常越准,但查询也越慢。

3.IVF

IVF 先把向量聚成多个桶:$$a(\mathbf{v}) =\underset{j}{\operatorname{argmin}} |\mathbf{v}-\boldsymbol{\mu}_j|_2$$

查询时只搜索最接近的几个桶,而不是搜索全部向量。

4.PQ

PQ 将一个长向量拆成多个子向量,再分别压缩:$$\mathbf{v}=[\mathbf{v}^{(1)},\mathbf{v}^{(2)},\ldots,\mathbf{v}^{(m)}]$$

优点是节省内存,缺点是会损失一些精度。