一、RAG介绍
RAG的全称是Retrieval-Augmented Generation,即检索增强生成。RAG技术的出现其实是早于大语言模型的,只不过目前随着大语言模型的火热,它也得到了更广泛的应用。
其实大家作为用户能明显地感受到大语言模型有三个问题。第一个是偏见,因为训练的时候我们使用的语料是海量的,其中不可避免地有一些有害语料,比如说一些关于种族歧视、暴力或者不合适的言论。大语言模型本身没有办法判断这些语料的好坏,就会学习这些内容,所以输出的时候就会带有偏见。
第二个问题是幻觉,这个大家可能比较熟悉,大语言模型本质上是一种概率预测模型,它预测下一个字是什么,并不是进行理解和思考。那么当你问一些看似真实的问题时,它就会胡编乱造,所以到目前为止,它不能当作一个搜索引擎来使用,存在信息可信度的问题。
还有一个非常常见的问题是时效性,也就是过时的问题。大语言模型训练的语料有截止时间,问它一些最新的情况,它就会不了解。比如问一下ChatGPT关于杭州亚运会的情况,它就会说不知道。
可以看到这三个问题其实是非常严重的,如果不解决,对于大语言模型真正的广泛应用会造成很大的限制。目前大家比较通用的一个方法是用RAG,也就是检索增强生成,我们来看一下什么是检索增强生成。
说直白一点,其实它就是外挂一个知识库,让大语言模型回答问题的时候,先去检索这个外挂的知识库,从中抽取相关的知识输入进自己的prompt里,然后再回答用户的问题。它包括,首先是外挂数据库的数据提取,然后把它做一个embedding,接着创建索引,让大语言模型能检索到它。在提问的时候去检索相关的知识,把这个知识连同提问一起让大语言模型归纳生成,再生成一个答案。
二、RAG的5种文档切分策略
知识库文档涉及到切分问题。我们知道大模型上下文token数量是有一定限制的。对于一个企业级知识库,把一整个知识直接作为上下文喂给大模型是不现实的。而且这里面还涉及一个精确度问题,想象一下,如果我们把整个使用手册作为一个整体来检索,那么即使找到了相关内容,也可能会返回大量无关的信息,通过将文档分成较小的块,我们可以更精确地定位到用户需要的信息。常见的文档切分有以下5种方式。
1、固定大小分块:按预定义的字符、单词或标记数量将文本分成统一段,建议块间有重叠。优点是实现容易、利于批处理;缺点是易打断句子,使重要信息分散。
实现原理:基于滑动窗口算法,设置固定字符数(推荐512-1024字符)和重叠率(15-20%
适用场景:技术文档、日志文件等结构化文本
2、语义分块:根据句子、段落等有意义单位细分文档,通过余弦相似度判断片段是否形成块。能保持语言自然流畅和完整想法,提高检索准确性,但确定相似度阈值较难。
技术要点:
-
使用Sentence-BERT计算句子嵌入相似度
-
动态调整余弦相似度阈值(推荐0.75-0.85)
-
基于TextTiling算法实现段落分割
3、递归分块:先依固有分隔符分块,若块大小超限制则进一步拆分。可保持语言流畅,但实施和计算较复杂。
分层策略:
(1)一级分割符:章节标题(#、##等)
(2)二级分割符:段落分隔符(\n\n)
(3)三级分割符:句子结束符(。?!)
4、基于文档结构的分块:利用文档标题、章节等结构定义块边界,能保持结构完整性,但依赖清晰的文档结构,块长度可能不一致,超出模型令牌限制时可递归拆分合并。
特征提取:
-
Markdown/HTML标题层级
-
LaTeX公式环境(equation、align等)
-
代码块(```包裹内容)
5、基于LLM的分块:提示LLM生成语义孤立且有意义的块,语义准确性高,但计算要求高,受LLM上下文窗口限制。
Prompt设计:
text
你是一个专业文档处理引擎,请将以下文本分割为语义完整的段落:
要求:
1. 每个段落包含完整论点
2. 保留关键数据表格
3. 最大长度不超过1024token
文本:{input_text}
三、向量化
针对大型知识库数据集,直接匹配数据集文本不可避免会遇到性能瓶颈,而且文本匹配也伴随着语义鸿沟(比如"苹果"可以是水果或者是公司)。而向量化可以解决这些问题。
所谓向量化,简单地说就是将文本转换为向量,便于计算机理解和处理。通过文本embedding模型,把文档切分后的每个Chunk转换为向量表示,存储为索引。在检索时,针对用户Query提取Query embedding,与索引中的Chunk embedding比对,找到最相似的向量,提取对应Chunk,进而为大模型提供相关知识,提升生成内容的质量和准确性 。
通常RAG架构图如下:
用户提问 → 向量化 → 向量数据库检索 → 重排序 → 拼接Prompt → 大模型生成 → 返回答案
│ │ │
知识库分块 → 向量化 → 存入数据库
关键步骤如下:
步骤1:准备知识库并向量化
python
from sentence_transformers import SentenceTransformer
import faiss
import numpy as np
# 假设知识库中有以下文档(实际需从PDF/数据库等加载)
documents = [
"苹果是一种水果,富含维生素C。",
"苹果公司是美国的一家科技企业,生产iPhone。",
"华为是全球领先的通信设备供应商。",
"维生素C有助于提高免疫力。"
]
1. 加载模型(中文推荐使用 bge 或 paraphrase-multilingual)
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# 2. 将文档转为向量
doc_embeddings = model.encode(documents)
# 3. 创建向量数据库(FAISS)
dimension = doc_embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(doc_embeddings.astype('float32')) # FAISS需要float32类型
步骤2:用户提问并检索相关文档
python
# 用户问题
query = "苹果有什么健康益处?"
# 将问题向量化
query_embedding = model.encode([query])
# 检索最相似的3个文档
k = 3
distances, indices = index.search(query_embedding.astype('float32'), k)
# 输出检索结果
print("最相关的文档索引:", indices[0])
for idx in indices[0]:
print(f"文档内容: {documents[idx]}")
输出结果:
makefile
最相关的文档索引: [0 3 1]
文档内容: 苹果是一种水果,富含维生素C。
文档内容: 维生素C有助于提高免疫力。
文档内容: 苹果公司是美国的一家科技企业,生产iPhone。 # 这里出现歧义!
步骤3:优化检索结果(解决歧义)
问题:检索结果包含"苹果公司"这类无关内容。
解决方案:
-
优化分块策略:将文档按语义分块(如每段一个块)。
-
元数据过滤:添加文档来源标签(如"水果" vs "公司")。
-
重排序:用交叉编码器(Cross-Encoder)对Top结果精排。
步骤4:将检索结果输入大模型生成答案
python
from openai import OpenAI
# 拼接检索到的文档作为上下文
context = "\n".join([documents[idx] for idx in final_results[:2]]) # 取前2个相关文档
# 调用GPT生成答案
client = OpenAI(api_key="your-api-key")
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "基于以下上下文回答问题,如果无法回答请说'我不知道'"},
{"role": "user", "content": f"问题:{query}\n上下文:{context}"}
]
)
print("最终答案:", response.choices[0].message.content)
输出示例:
苹果富含维生素C,有助于提高免疫力,是一种健康的水果。
四、结语
RAG对于大模型是非常重要的一项技术。它的好处显而易见,首先是长尾知识,在一些特定领域,知识非常复杂,不包含在我们原来的训练知识里,比如说特定的医学知识,那么只要确保我们外挂的这个数据库里面有这些长尾知识就能被检索到。
第二个好处是可以保证数据的安全性,因为对于企业来讲,把自己的商业机密上传到大语言模型是一件令人担心且不可行的事情,如果通过一个外挂的数据库,每次让大语言模型检索,那么数据的安全性会得到很大保证。
第三个好处是可以解决数据及时性,因为我们的知识库可以实时更新。
第四个好处是来源性和可解释性也会大大增强,因为我们可以看到它引用了哪些知识,参考了知识库的哪几条去进行索引,这个是可以导出的。
基于以上优势,RAG 技术对于大模型在企业的大规模应用至关重要。
最后引用智谱清言的一张图概括一下RAG的全貌: