一、 RAG流程
- 数据入库:读取本地数据并切成小块,并把这些小块经过编码embedding后,存储在一个向量数据库中(下图1——6步);
- 相关性检索:用户提出问题,问题经过编码,再在向量数据库中做相似性检索,获取与问题相关的信息块context,并通过重排序算法,输出最相关的N个context(下图7——10步);
- 问题输出:相关段落context + 问题组合形成prompt输入大模型中,大模型输出一个答案或采取一个行动(下图11——15步)
1.1、RAG的本质
RAG流程是为了弥补大语言模型在垂直领域下知识的不足。相比于Agent流程而言,整个过程相对稳定,RAG流程中的大模型发挥的空间较少;
- RAG流程核心:数据入库 + 相关性检索。
- 主要难点在:知识管理(非结构化加载器做文件解析 + 数据如何切片)、知识检索、知识重排序
1.2、RAG代码实现
from sentence_transformers import SentenceTransformer, util
from transformers import BartForConditionalGeneration, BartTokenizer
import torch
# # [1]:初始化检索模型 (Sentence-BERT)
retrieval_model = SentenceTransformer('sentence-transformers/all-mpnet-base-v2')
# # [2]:文档向量化
documents = [
"Machine learning is a field of artificial intelligence that uses statistical techniques.",
"Deep learning is a subset of machine learning that uses neural networks.",
"Natural language processing is a field of AI focused on the interaction between computers and humans.",
"RAG stands for Retrieval-Augmented Generation, a framework that combines document retrieval and generation.",
]
document_embeddings = retrieval_model.encode(documents, convert_to_tensor=True)
# # [3]:初始化生成模型 (BART)
tokenizer = BartTokenizer.from_pretrained('facebook/bart-large-cnn')
generator_model = BartForConditionalGeneration.from_pretrained('facebook/bart-large-cnn')
def retrieve_documents(query, top_k=2):
"""
查询数据,召回相关的文档
:param query:问题
:param top_k:最相关的K个答案进行召回
:return:
"""
# # 计算查询 query 与所有文档的相似性分数(使用余弦相似度),并返回最相关的top_k个结果
query_embedding = retrieval_model.encode(query, convert_to_tensor=True)
cos_scores = util.pytorch_cos_sim(query_embedding, document_embeddings)[0]
top_results = torch.topk(cos_scores, k=top_k)
relevant_docs = [documents[idx] for idx in top_results.indices]
return relevant_docs
def generate_answer(query, relevant_docs):
"""
基于召回文档生成最后答案
:param query: 问题
:param relevant_docs: 召回的最相关的 top_k 个文档
:return:
"""
input_text = query + " " + " ".join(relevant_docs)
inputs = tokenizer(input_text, return_tensors="pt", max_length=512, truncation=True)
# # query + relevant_docs作为新输入,用于生成新结果
summary_ids = generator_model.generate(inputs['input_ids'], max_length=150, num_beams=4, early_stopping=True)
answer = tokenizer.decode(summary_ids[0], skip_special_tokens=True)
return answer
query = "What is RAG in AI?"
# # 召回与查询相关的文档
relevant_docs = retrieve_documents(query)
print("Retrieved Documents:", relevant_docs)
# # 基于召回的文档生成答案
answer = generate_answer(query, relevant_docs)
print("Generated Answer:", answer)
二、RAG常见问题
- 文档解析:本地数据可能来源于PDF、PPT、Excel,甚至是图片、表格、流程图,不同的文档的读取和解析本身就是个比较难的问题;
- 数据切片:如何把文档分割成合理长度的chunks,这个需要根据项目做不同的数据工程;
- 用户问题:用户提的问题,是直接用于信息检索,还是需要做进一步处理(比如做问题扩充、问题改写、问题凝练等);
- 问题检索:使用更新的检索算法;
- re-rank重排序:检索相关context只是粗排序(召回),再添加re-rank做精细排序;
- response检测:内容审核、敏感性分析、其他必要问题纠察;
- 效果评估方案:相当于是改进方向,做好评估才能使准确率逐步提升;
三、 RAG改进方向
主要为了提升检索的准确率,提高回复的质量。
3.1、检索前
- 增强数据颗粒度:校准知识库数据,在确保准确性的前提下,使内容变得简洁、准确、无冗余;
- 调整数据切片长度:找到合适的切片长度,使每个文本块chunks保存的知识互相独立,没有信息交叉;
- 添加元数据:比如使用【日期、价格】等元数据加强敏感数据的检索,增强相关性;
- 假设性问题:为每个chunks创建假设性问题来解决文档间的不一致问题;
- 动态更新知识库:涉及时效性问题时,比如金融、新闻等领域,可以通过API动态更新数据库,增强知识库的时效性;
- 模型微调:利用特定领域的语料来微调Embedding模型 + 基座模型,将特定知识嵌入到模型中
3.2、检索中
- 知识库分类:将相近知识存入同一个知识库,检索过程中先对问题进行分类,再在对应知识库中查找相关数据;
- 多轮检索:针对复杂问题,通过多轮检索逐步聚焦目标文档。第一次检索后,可以将初步结果再次作为输入,进行二次筛选,找到更加精确的信息;
- 多模态输入:结合文本、图片、视频等多模态信息,可以更全面地为问题提供背景支持,提升回答的精度;
- 动态文档扩展:对检索到的文档进行扩展,如使用知识图谱、外部API或其他数据库进行补充,从而丰富大模型的上下文信息;
- 多模型集成:引入多个模型的回答,然后通过加权、打分等方法选出最佳答案。或者让不同模型分别进行回答,后续结合打分机制选取最优解;
- 改进检索算法:设计更为复杂的模块对召回的结果进行精细化排序,提高召回质量,如基于Dense Passage Retrieval (DPR) 或者使用语义搜索技术(如FAISS)来代替传统的BM25检索方法;
3.3、检索后
- response检测:检查生成内容是否符合逻辑或事实,过滤掉明显错误的回答,做内容审核、敏感性分析等;
- 不确定检测:通过设定阈值来识别并提示用户答案的不确定性,或者引导进一步的问题澄清;
- 提示词精炼:压缩无关上下文,突出关键段落,减少总体长度;
- 选用更好的模型:提高知识处理能力,增加输出长度;
3.4、改进检索算法:DPR实现
不同的检索模型有不同的召回性能。选择更好的检索模型可以显著提高召回准确率。
Dense Retrieval(稠密检索):相比于传统的基于词频的检索模型(如TF-IDF或BM25),稠密向量检索模型可以捕捉语义信息,尤其在长查询或含有复杂句子时表现更好。常见的稠密检索模型包括:
- DPR(Dense Passage Retrieval):基于双塔模型(query encoder和document encoder)将查询和文档嵌入到同一个向量空间,计算其余弦相似度来进行召回。
- Sentence-BERT:基于BERT的句子级向量模型,能够在语义层面上更好地理解查询和文档。
Hybrid Retrieval(混合检索):结合稠密检索和稀疏检索。可以同时使用BM25和DPR的结果,将二者结合进行召回。
from transformers import DPRQuestionEncoder, DPRContextEncoder, DPRQuestionEncoderTokenizer, DPRContextEncoderTokenizer
import torch
# # [1]:初始化 DPR 模型和 Tokenizer
question_encoder = DPRQuestionEncoder.from_pretrained("facebook/dpr-question_encoder-single-nq-base")
context_encoder = DPRContextEncoder.from_pretrained("facebook/dpr-ctx_encoder-single-nq-base")
question_tokenizer = DPRQuestionEncoderTokenizer.from_pretrained("facebook/dpr-question_encoder-single-nq-base")
context_tokenizer = DPRContextEncoderTokenizer.from_pretrained("facebook/dpr-ctx_encoder-single-nq-base")
# # [2]:文档向量化
documents = [
"Machine learning is a field of artificial intelligence that uses statistical techniques.",
"Deep learning is a subset of machine learning that uses neural networks.",
"Natural language processing is a field of AI focused on the interaction between computers and humans.",
"RAG stands for Retrieval-Augmented Generation, a framework that combines document retrieval and generation.",
]
# # 将文档库编码为稠密向量
context_embeddings = []
for doc in documents:
inputs = context_tokenizer(doc, return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
embedding = context_encoder(**inputs).pooler_output
context_embeddings.append(embedding)
context_embeddings = torch.cat(context_embeddings, dim=0)
# # [3]:编码查询为稠密向量
query = "What is RAG in AI?"
query_inputs = question_tokenizer(query, return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
query_embedding = question_encoder(**query_inputs).pooler_output
# # [4]:计算查询与文档的相似度
scores = torch.matmul(query_embedding, context_embeddings.T)
top_k = torch.topk(scores, k=2)
# # [5]:打印最相关的文档
relevant_docs = [documents[i] for i in top_k.indices[0]]
print("Top relevant documents:", relevant_docs)
四、非文本内容处理
4.1、图片
- 可以使用视觉模型(如 CLIP、ViT 等)将图像编码为向量,类似于文本的向量化。在检索阶段,通过将文本查询与图像向量进行相似度计算,实现图像的检索;
- OCR 处理图片中的文本:对于包含文本的图片(如流程图、图表等),可以使用 OCR(Optical Character Recognition,光学字符识别)技术提取图片中的文字,将其作为文本信息参与检索和生成流程;
4.2、表格
- 表格转为结构化数据:表格转化为JSON、CSV 等,然后通过匹配查询与表格中的字段,实现基于表格数据的检索和回答;
- 表格语义化:可以使用专门的表格理解模型(如 TAPAS)将表格转化为语义信息,使模型能够根据查询直接在表格中查找相关信息;
4.3、流程图
- 结构化理解:对于流程图,可以使用图像处理技术或者专门的流程图解析工具(如 Graphviz)将流程图结构化表示,将其转化为流程节点、关系的语义表示。例如,使用图卷积网络(GCN) 或其他图算法对流程图进行语义理解;
- 语义转换:将流程图中的结构转化为自然语言描述,供 RAG 模型使用,通过图像处理技术将流程图的各个元素(如节点和连线)提取出来,并转换为带有语义的描述。比如“从 A 节点经过 B 节点,最后到达 C 节点”可以转化为“流程从 A 开始,经过 B 后到达 C”
4.4、多模态融合
- 多模态模型:如果希望同时处理文本、图像、表格等多种模态,可以使用多模态模型,如 CLIP(处理图像和文本)或 VisualBERT(结合图像和文本进行推理)。
- 多模态融合检索与生成:通过将不同模态的输入(图像、表格、文本等)编码为统一的向量空间,能够实现多模态信息的融合。RAG 的查询阶段可以同时检索文本、表格和图像,生成阶段则利用不同模态的信息来生成准确的答案
五、数据切分
数据切分方式会直接影响检索模型的召回率和生成模型的准确性,因此需要在数据切分时仔细设计。
以下是常见的 RAG 数据切分方法及技巧:
5.1、按固定长度切分
- 方法:将文本按固定长度(如 100-200 字或词)切分为相同大小的段落。
- 优点:实现简单,可以保证每个段落的长度一致,利于后续处理。
- 缺点:固定长度切分可能会破坏上下文连贯性,导致信息片段不完整,影响检索效果。
- 适用场景:适用于信息密度较高、内容结构清晰的文档。
5.2、按语义边界切分
- 方法:利用NLP技术,根据语义信息将文档按句子、段落或小标题等语义边界切分。
- 优点:可以保留语义连贯性,避免语义割裂,提升检索和生成的准确性。
- 缺点:由于段落长度不定,有些段落可能过长或过短,影响生成效果。
- 适用场景:适合内容结构较复杂、语义关系较强的文本(如技术文档、研究报告等)。
5.3、基于滑动窗口切分
- 方法:在每次切分时,选择一定长度的文本(如 100 字)作为“主内容”,然后向前或向后滑动一定的重叠窗口(如 50 字)切分下一个片段。
- 优点:通过滑动窗口可以部分保留上下文信息,确保信息的完整性,减少因切分带来的信息丢失。
- 缺点:会产生重复内容,导致数据量增加。
- 适用场景:适合在高上下文依赖、需要保持连贯性的场景下使用。
5.4、按主题或逻辑结构切分
- 方法:利用主题分割技术,如LDA(Latent Dirichlet Allocation)或文本聚类算法,按逻辑或主题将文本分成不同主题的片段。
- 优点:能确保每个段落包含相对独立的主题信息,便于检索系统按主题查找。
- 缺点:实现较为复杂,对数据的处理和分析能力要求较高。
- 适用场景:适用于主题清晰、内容较长的文档(如论文、新闻文章等)。
5.5、智能摘要切分
- 方法:通过抽取摘要或关键句子生成更精炼的段落,将冗长文本缩短到关键内容。
- 优点:减少了不相关的信息冗余,提高检索和生成的精度。
- 缺点:存在信息丢失的风险,可能导致部分重要细节被忽略。
- 适用场景:适合数据冗长、信息密集的场景,或对信息精度要求较高的应用。
5.6、数据切分后的注意事项
- 段落长度控制:保证每个段落的长度在生成模型的上下文窗口内。过长的段落会被截断,而过短的段落可能会丢失上下文信息。
- 去重和过滤:切分后应清理重复数据,并过滤掉无意义的文本(如格式标记等)以提高数据质量。
- 嵌入生成与检索:每个段落切分后,应生成独立的向量嵌入,保证在检索时可以精确找到匹配的片段,提高生成模型的响应准确性。
六、向量检索:近似近邻搜索算法(ANN)
对于向量检索,可以使用HNSW、Faiss、ScaNN等高效的近似近邻算法构建索引,以加速向量的相似性搜索。尤其在大规模数据场景中,ANN显著提升检索速度。
6.1、HNSW
HNSW(Hierarchical Navigable Small World)一种基于图结构的近似最近邻算法,它利用小世界图(Small World Graph)实现快速近邻搜索,可以在海量数据中快速找到与查询向量相似的向量。
- 特点:HNSW通过分层图结构加速搜索过程,高层图结构包含稀疏节点来快速缩小搜索范围,逐层向下到最底层的密集图,从而找到最近邻。
- 适用领域:适用于推荐系统、图像检索、自然语言处理等需要高精度的场景,由于其良好的并行性和高检索精度,也适用于低延迟要求的在线服务,如实时推荐和检索。
优点:
- 精度高:相比其他近似最近邻算法,HNSW的检索精度较高,接近于暴力搜索;
- 可并行:支持多线程并行化,在硬件支持良好的情况下,查询速度可以显著提升;
- 快速收敛:通过层次结构和小世界特性,可在较少迭代中收敛到结果;
缺点:
- 内存占用大:HNSW需要为每个点存储多个邻居节点信息,在大规模数据下内存消耗较高。
- 构建时间较长:由于构建图结构需要计算节点间的相似性,初始索引构建较慢,适合在索引更新不频繁的场景
HNSW的主要特点说明:
层次化图结构:HNSW索引使用多层的分层图结构,每一层都包含数据的子集。顶层包含较少的点,而底层包含所有数据点。顶层的点之间的连接较少,有利于快速粗略定位,而底层连接密集,便于细致搜索。小世界属性:HNSW索引构建了一个具有小世界网络性质的图,意味着在图中任意两点间的最短路径长度较小。通过在搜索时遍历小世界图结构,可以有效缩短查找路径,提升搜索速度。Navigable Navigable:HNSW允许快速从任意点导航到目标点。其索引构建过程会在图中加入跳跃连接,这些跳跃连接可以跨越较远的距离,避免深层遍历带来的高复杂度,使得搜索速度更快。近似搜索:HNSW并非每次都返回最准确的最近邻点,而是以较高概率返回相似点,这样能大幅降低计算复杂度。同时,用户可以通过调节参数来平衡精度与速度。
HNSW的应用广泛,包括搜索引擎、推荐系统、图像检索、自然语言处理等场景。因为HNSW在搜索效率和内存消耗之间提供了良好的平衡,因此在许多大规模数据中,HNSW是流行的近似最近邻搜索方案。
6.2、Faiss
Faiss是Facebook AI Research开发的开源库,专为高维向量的近似最近邻搜索设计,它支持多种近似算法,包括基于聚类的Product Quantization(PQ)、IVF-PQ、HNSW等,并且针对不同的硬件平台进行了优化(如GPU)。
适用领域:
- 适用于大规模高维向量检索,特别是在图像检索、文本检索和推荐系统中。
- 由于Faiss对GPU有良好支持,非常适合在GPU服务器上进行高维向量检索任务。
优点:
- 高效性:提供多种索引结构(如IVF、PQ等),用户可以选择适合自己需求的索引结构以平衡精度和效率。
- 硬件支持:优化了GPU计算能力,可利用GPU大规模并行处理能力处理高维数据,极大地提升了速度。
- 灵活性:支持不同算法组合,并可以针对不同的数据集和硬件进行参数调整。
缺点:
- 算法复杂性:由于Faiss提供的算法选择较多,初学者可能需要一定时间熟悉和选择适合的配置。
- 构建开销:对于大规模数据构建索引可能需要较长时间,同时GPU内存占用较大。
6.3、ScaNN(Scalable Nearest Neighbors)
ScaNN是Google提出的高效最近邻搜索算法,针对内积和L2距离进行了优化。它通过使用近似量化和树结构实现高效的近邻搜索,并结合了基于分区和精细化搜索的技术。
适用领域:
- 非常适合海量数据的快速检索需求,特别是对内存和计算资源有限的环境。
- 可用于图像特征检索、语义搜索、推荐系统,尤其是内积相似性检索需求较多的场景。
优点:
- 高效率:对内积和L2距离进行了特定优化,因此在这两种相似性度量上表现优异。
- 资源友好:设计时考虑了内存使用,适合在计算资源受限的情况下应用。
- 易用性:提供简单的API接口,容易上手。
缺点:
- 适用范围有限:虽然在内积和L2距离方面优化明显,但在其他度量下的性能可能不如Faiss等多功能库。
- 精度略低:由于ScaNN主要关注于大规模数据的搜索速度,在某些场景下精度可能不如HNSW
6.4、总结
算法 | 优势 | 缺点 | 适用场景 |
---|---|---|---|
HNSW | 高精度、并行性强 | 内存占用大、构建时间较长 | 实时推荐、图像和文本检索 |
Faiss | 高效、支持GPU、多种算法 | 配置复杂、GPU内存开销大 | 大规模高维数据、图像检索 |
ScaNN | 高效率、内存友好 | 精度略低、适用范围有限 | 海量数据快速检索、推荐系统 |
- HNSW适合精度要求较高的在线应用;
- Faiss适合高维大数据集和GPU服务器;
- ScaNN则适合对资源消耗敏感的大规模应用。
七、重排序Re-rank
结果重排(Re-ranking)是将初步检索到的候选结果按相关性重新排序,以提升检索结果的精准性和用户体验。
- 基于模型的重排:通过深度学习模型(如BERT、T5等)进行排序,以提高语义相关性。可以使用Pointwise、Pairwise或Listwise排序模型,在重新排序时考虑文档与查询的语义匹配。
- 混合排序策略:将文本相似度、点击率、用户反馈等多种信号融合在一起,构建加权排序策略。权重可以基于业务场景和用户行为数据不断调整,确保最相关的结果排名靠前。
- 用户行为反馈:使用历史点击数据、停留时间等用户行为数据进行排序优化。系统可以优先展示用户点击率较高的结果,提高用户满意度。
- 上下文信息和个性化排序:通过分析用户的历史行为、偏好、位置等上下文信息,对结果进行个性化排序。例如,对于同样的“手机推荐”查询,用户A和用户B可能得到不同品牌或价格段的手机推荐结果。