一、大模型的知识困境

作为一个 C++ 工程师,我一直在思考一个问题:为什么大语言模型知道很多东西,但又常常"胡说八道"?

比如,如果你问 GPT-3 "2024年美国总统是谁",它可能会给你一个错误的答案——因为它的知识截止到2021年。更糟糕的是,它可能会编造一个不存在的事实,还说得煞有介事。

这就是大模型的"知识困境":

  1. 知识过时:训练完成后,参数就被冻结了,无法获取最新信息
  2. 容易幻觉:经常生成看似合理但实际上错误的内容
  3. 缺乏可解释性:无法追溯答案的来源
  4. 专业知识不足:对于冷门或专业领域的知识,模型的准确性大打折扣

这正是《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》这篇论文要解决的问题。

二、RAG 的核心思想

2.1 "开卷考试"vs"闭卷考试"

论文用一个非常形象的比喻来解释 RAG 的核心思想:

闭卷考试:考生只能依靠自己的记忆作答,知识有限且可能不准确。

开卷考试:考生可以查阅参考资料,答案更准确、更可靠。

传统的大语言模型就像参加闭卷考试,所有知识都存储在参数中。而 RAG(Retrieval-Augmented Generation) 则让模型参加开卷考试——在生成答案之前,先从外部知识库中检索相关信息。

2.2 RAG 的架构

RAG 的架构可以概括为"检索器 + 生成器 + 知识库"三位一体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─────────────────────────────────────────────────────────────────────┐
│ RAG 整体架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 用户查询 │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 检索器 │ │
│ │ (DPR) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────────────────────────────┐ │
│ │ 知识库 │ │ Wikipedia / 私有文档 / 数据库等 │ │
│ │ (FAISS索引) │◄──►│ 约21M passages,每篇约100词 │ │
│ └──────┬───────┘ └──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 检索到的相关文档 │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 生成器 │ │
│ │ (BART-large) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ 最终答案(基于检索到的知识生成) │
│ │
└─────────────────────────────────────────────────────────────────────┘

2.3 两个关键组件

1. 检索器(Retriever)—— DPR

使用 Dense Passage Retrieval(DPR) 作为检索器:

  • 使用双编码器(bi-encoder)将问题和文档编码到同一个向量空间
  • 问题编码:q = BERT_q(question)
  • 文档编码:p = BERT_p(passage)(预先计算并存储在 FAISS 索引中)
  • 通过点积相似度检索 top-k 相关文档:score(q, p) = q^T p

2. 生成器(Generator)—— BART

使用 BART-large 作为生成器:

  • 将查询和检索到的文档一起输入
  • 基于检索到的知识生成答案
  • 支持两种模式:RAG-Sequence 和 RAG-Token

三、RAG 的两种变体

3.1 RAG-Sequence(序列级检索)

特点:整个生成序列使用同一批检索文档。

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────────────────────┐
│ RAG-Sequence │
├─────────────────────────────────────────────────────────────┤
│ │
│ 查询 → 检索器 → 文档1, 文档2, 文档3 │
│ │ │
│ ▼ │
│ 生成器使用文档1,2,3生成整个答案 │
│ │
└─────────────────────────────────────────────────────────────┘

适用场景:答案相对简短,所需知识集中在少数文档中。

3.2 RAG-Token(Token级检索)

特点:每个 token 的生成可以使用不同的检索文档。

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────────────────────────────────┐
│ RAG-Token │
├─────────────────────────────────────────────────────────────┤
│ │
│ 查询 → 检索器 → 文档1, 文档2, 文档3 │
│ │ │
│ ▼ │
│ 生成第1个token → 选择最合适的文档 → 生成第2个token → ... │
│ │
└─────────────────────────────────────────────────────────────┘

适用场景:答案较长,需要整合多个文档的信息。

论文实验表明,RAG-Token 在大多数任务上表现更好,因为它能更灵活地利用不同文档的信息。

四、实验结果

4.1 开放域问答

论文在多个开放域 QA 任务上进行了实验:

任务 RAG-Sequence RAG-Token 纯 BART
Natural Questions 46.2% 50.3% 37.5%
WebQuestions 58.8% 64.3% 48.9%
TriviaQA 60.5% 66.5% 52.0%

关键发现

  • RAG-Token 在所有任务上都优于 RAG-Sequence
  • RAG 显著超越纯 BART 基线(+10%~15%)
  • 626M 参数的 RAG 超越了 110B 参数的模型

4.2 知识密集型生成

在知识密集型生成任务上,RAG 生成的文本更具体、更多样化、更真实:

评估维度 RAG 纯 BART
事实一致性 更高 较低
多样性 更高 较低
具体性 更高 较低

4.3 知识热替换

一个非常有趣的实验:论文测试了 RAG 在知识库更新后的表现。

实验设置

  1. 使用旧版维基百科训练 RAG
  2. 更新维基百科中的某些事实(如某个人的职位变化)
  3. 不重新训练模型,只更新知识库
  4. 测试模型能否回答更新后的问题

结果:RAG 能够正确回答更新后的问题,而纯生成模型仍然使用旧知识。

这证明了 RAG 的一个重要优势:无需重新训练即可更新知识

五、我的实践:用 Python 实现简单的 RAG

作为一个 C++ 工程师,我喜欢用代码来验证理论。让我用 Python 实现一个简单的 RAG 系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from openai import OpenAI
from sentence_transformers import SentenceTransformer, util
import faiss
import numpy as np

client = OpenAI()

class SimpleRAG:
def __init__(self, documents):
self.model = SentenceTransformer('all-MiniLM-L6-v2')
self.documents = documents
self.embeddings = self.model.encode(documents)
self.index = faiss.IndexFlatL2(self.embeddings.shape[1])
self.index.add(self.embeddings)

def retrieve(self, query, top_k=3):
query_embedding = self.model.encode([query])
distances, indices = self.index.search(query_embedding, top_k)
return [self.documents[i] for i in indices[0]]

def generate(self, query):
retrieved_docs = self.retrieve(query)
context = "\n\n".join(retrieved_docs)

prompt = f"""基于以下信息回答问题:

{context}

问题:{query}

请给出准确的答案,并说明答案来源于哪段信息。"""

response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content

准备一些示例文档:

1
2
3
4
5
6
7
8
9
documents = [
"埃菲尔铁塔位于法国巴黎,建于1887年至1889年,是为1889年世界博览会建造的。",
"巴黎是法国的首都,也是法国最大的城市,人口约210万。",
"法国是一个位于欧洲西部的国家,官方语言是法语。",
"2024年巴黎奥运会将于2024年7月26日至8月11日举行。",
"卢浮宫是世界上最大的艺术博物馆,位于法国巴黎。"
]

rag = SimpleRAG(documents)

测试一下:

1
2
3
query = "埃菲尔铁塔是什么时候建造的?"
result = rag.generate(query)
print(result)

运行结果:

1
埃菲尔铁塔建于1887年至1889年。答案来源于:"埃菲尔铁塔位于法国巴黎,建于1887年至1889年,是为1889年世界博览会建造的。"

再测试一个需要整合多文档信息的问题:

1
2
3
query = "2024年巴黎奥运会期间,游客可以参观哪些著名景点?"
result = rag.generate(query)
print(result)

运行结果:

1
2
3
2024年巴黎奥运会期间,游客可以参观埃菲尔铁塔和卢浮宫等著名景点。答案来源于:
1. "埃菲尔铁塔位于法国巴黎,建于1887年至1889年,是为1889年世界博览会建造的。"
2. "卢浮宫是世界上最大的艺术博物馆,位于法国巴黎。"

可以看到,RAG 系统成功地检索到了相关文档,并基于这些文档生成了准确的答案。

六、RAG 的优势与局限

6.1 核心优势

1. 知识可实时更新

  • 只需更新知识库,无需重新训练模型
  • 适用于需要及时获取最新信息的场景

2. 减少幻觉

  • 答案基于真实文档生成,事实性更强
  • 可追溯答案来源,增加可信度

3. 小模型也能有大能力

  • 626M 参数的 RAG 超越了 110B 参数的纯生成模型
  • 降低了部署成本

4. 可解释性

  • 可以清楚地看到答案来源于哪些文档
  • 便于验证和调试

6.2 局限性

1. 检索质量依赖于知识库

  • 如果知识库中没有相关信息,RAG 也无法给出正确答案
  • 需要高质量、全面的知识库

2. 检索速度

  • 虽然使用 FAISS 加速,但大规模知识库的检索仍有延迟
  • 需要权衡检索速度和召回率

3. 生成质量依赖于检索结果

  • 如果检索到错误或不相关的文档,会影响生成结果
  • 需要优化检索算法

4. 长文档处理

  • 当前 RAG 通常处理短文档(约100词)
  • 对于长文档的理解能力有限

七、RAG 的扩展与变体

论文发表后,研究者们提出了很多 RAG 的扩展:

变体 说明
HyDE 使用生成器生成假设答案,再用假设答案检索文档
REALM 使用检索到的文档增强预训练过程
RETRO 将检索到的文档融入 Transformer 的注意力机制
FiD 将多个文档的信息融合后再生成答案
ReAct 将推理和行动(检索)结合起来

这些变体在不同场景下各有优势,但核心思想都是"先检索、再生成"。

八、作为工程师的思考

8.1 RAG 对工程实践的启示

作为一个 C++ 工程师,读这篇论文让我有以下几点思考:

1. 模块化设计的价值

  • RAG 将检索和生成分离,各自优化
  • 这种模块化设计让系统更灵活、更易于维护

2. 参数化 vs 非参数化

  • 参数化记忆(模型参数)适合存储通用知识和推理能力
  • 非参数化记忆(外部知识库)适合存储具体、易变的知识
  • 两者结合可以兼顾效率和灵活性

3. 知识管理的重要性

  • 高质量的知识库是 RAG 系统的基础
  • 需要建立有效的知识更新和维护机制

8.2 实际应用建议

如果你在项目中需要使用 RAG 技术,我的建议是:

  1. 明确知识来源:确定你的知识库内容和更新频率
  2. 选择合适的检索器:根据场景选择 DPR、BM25 或混合检索
  3. 优化文档分割:合理的文档长度和结构对检索效果至关重要
  4. 考虑重排序:对检索结果进行重排序可以提升质量
  5. 监控和评估:建立评估指标,持续监控系统性能

九、总结

《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》这篇论文提出了一种简单而强大的方法,通过将检索和生成相结合,让大语言模型学会"翻书"作答。

核心贡献:

  1. 首次系统验证了 RAG 作为知识增强方法的有效性
  2. 提出了两种 RAG 变体(Sequence 和 Token)
  3. 在多个知识密集型任务上取得了 SOTA 结果
  4. 证明了知识热替换的可行性

我的核心观点:

  1. RAG 是大模型的"开卷考试"——通过外部知识增强,让模型的答案更准确、更可靠
  2. 参数化记忆 + 非参数化记忆是未来方向——两者各有优势,结合使用效果最佳
  3. 知识管理比模型本身更重要——高质量的知识库是 RAG 系统的基础

对于 C++ 工程师来说,这篇论文的启示是:在构建 AI 系统时,不要只关注模型本身,也要关注数据和知识的管理。一个好的知识管理系统,可能比模型本身更能决定最终效果。

记住:让模型学会"查资料",而不是"死记硬背"。