深度学习 大模型学习-RAG技术全景解析
引言:大模型的局限与RAG的诞生在当今的AI时代,大语言模型(LLM)如GPT、LLaMA等展现出了惊人的能力,但它们并非完美无缺。知识截止日期 、幻觉问题 以及缺乏实时信息 是三大核心痛点。例如,一个在2023年训练的模型无法回答"2024年美国总统大选的结果"。为了弥补这一鸿沟,检索增强生成(Retrieval-Augmented Generation,RAG) 技术应运而生。RAG的核心思想是:在模型生成回答之前,先从一个外部知识库中检索相关文档,然后将这些文档作为上下文与用户问题一起输入给LLM,从而生成更准确、更可信的答案 。本文将带你从零开始,逐步深入RAG技术的各个环节,并配有可运行的代码示例。## 一、基础概念:RAG的工作流程一个典型的RAG系统包含三个核心阶段:1. 索引(Indexing) :将外部文档(如PDF、网页)切分成小块(Chunks),并将每个块转换为向量(Embedding),存入向量数据库。2. 检索(Retrieval) :将用户问题也转换为向量,在向量数据库中搜索最相似的文档块。3. 生成(Generation) :将检索到的文档块与原始问题拼接成提示词(Prompt),输入给LLM生成最终答案。### 1.1 为什么需要向量检索?传统的关键词搜索(如BM25)只能匹配字面相同的内容,而向量搜索利用深度学习模型(如OpenAI的text-embedding-ada-002)将文本映射到语义空间,即使"猫"和"猫咪"字面不同,但语义相近,也能被检索到。## 二、环境准备与基础工具在开始写代码之前,我们需要安装必要的库。这里我们使用轻量级的langchain框架和开源的chromadb作为向量数据库。bashpip install langchain chromadb openai tiktoken注意:你需要一个OpenAI API密钥来调用嵌入模型和LLM。如果你没有,可以替换为Hugging Face的开源模型(如all-MiniLM-L6-v2)。## 三、代码示例1:构建本地RAG系统(基础版)这个示例将演示如何从一个简单的文本文件构建RAG系统。我们首先准备一个虚构的文档内容。python# 导入必要的库from langchain.document_loaders import TextLoaderfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain.embeddings import OpenAIEmbeddingsfrom langchain.vectorstores import Chromafrom langchain.llms import OpenAIfrom langchain.chains import RetrievalQA# 1. 加载文档(这里我们使用一个字符串模拟外部知识)# 在实际场景中,你可以加载PDF、网页等text_content = """深度学习是机器学习的一个分支,它使用多层神经网络来学习数据的表示。Transformer是一种基于自注意力机制的神经网络架构,它彻底改变了自然语言处理领域。RAG(检索增强生成)是一种结合检索和生成的方法,能有效减少大模型的幻觉问题。"""# 将字符串保存为临时文件with open("temp_knowledge.txt", "w", encoding="utf-8") as f: f.write(text_content)# 2. 加载并分割文档loader = TextLoader("temp_knowledge.txt", encoding="utf-8")documents = loader.load()# 文本分割器:设置块大小为100个字符,重叠20个字符text_splitter = RecursiveCharacterTextSplitter( chunk_size=100, chunk_overlap=20, length_function=len,)chunks = text_splitter.split_documents(documents)print(f"文档被分割为 {len(chunks)} 个块")# 3. 创建向量数据库# 使用OpenAI的嵌入模型,将文本转换为向量embeddings = OpenAIEmbeddings()# 将块存入Chroma数据库(内存模式)vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" # 持久化存储)# 4. 创建检索问答链# 使用gpt-3.5-turbo作为生成模型llm = OpenAI(temperature=0, model_name="gpt-3.5-turbo")qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", # 将检索到的文档全部塞入提示词 retriever=vectorstore.as_retriever(search_kwargs={"k": 2}) # 检索前2个最相似的块)# 5. 提问测试question = "什么是RAG?"response = qa_chain.run(question)print(f"问题:{question}")print(f"回答:{response}")运行结果示例: 文档被分割为 3 个块问题:什么是RAG?回答:RAG(检索增强生成)是一种结合检索和生成的方法,能有效减少大模型的幻觉问题。代码解释: - RecursiveCharacterTextSplitter:智能地按照段落、句子、单词来分割文本,避免在中间切断语义。- Chroma:一个轻量级的向量数据库,支持内存和持久化两种模式。- RetrievalQA:Langchain提供的高级封装,自动完成检索和生成两步。## 四、进阶技术:优化检索质量基础版解决了"有没有"的问题,但在实际项目中,我们需要考虑检索精度 和生成质量 。以下是几个关键优化点。### 4.1 混合检索(Hybrid Search)纯向量搜索在处理精确匹配(如产品编号、人名)时可能表现不佳。混合检索结合了关键词搜索(BM25) 和向量搜索 ,取长补短。python# 导入BM25检索器from langchain.retrievers import BM25Retriever, EnsembleRetriever# 假设我们已经有了chunks列表(Document对象列表)# 创建BM25检索器bm25_retriever = BM25Retriever.from_documents(chunks)bm25_retriever.k = 2# 创建向量检索器vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 2})# 创建集成检索器(混合检索)ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.5, 0.5] # 两个检索器的权重)# 测试检索结果query = "什么是Transformer?"results = ensemble_retriever.get_relevant_documents(query)print("检索到的文档:")for i, doc in enumerate(results): print(f"{i+1}. {doc.page_content}")关键点: - EnsembleRetriever:可以组合任意数量的检索器,并赋予不同权重。- 权重调整:如果业务中精确匹配更重要,可以调高BM25的权重;如果语义理解更重要,则调高向量检索的权重。### 4.2 重排序(Re-ranking)检索阶段返回的Top-K结果中,可能存在一些噪声(不相关但向量距离近的文档)。重排序使用一个更强大的交叉编码器(Cross-Encoder) 对结果重新打分,提升准确性。python# 安装重排序依赖# pip install sentence-transformersfrom sentence_transformers import CrossEncoder# 初始化交叉编码器模型(轻量级)reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')# 假设检索到了5个文档(经过混合检索后)documents = [ "Transformer是一种基于自注意力机制的神经网络架构。", "RAG是一种结合检索和生成的方法。", "深度学习使用多层神经网络来学习数据表示。", "自注意力机制允许模型关注输入序列的不同位置。", "卷积神经网络主要用于图像处理。"]query = "Transformer的核心机制是什么?"# 构建查询-文档对pairs = [[query, doc] for doc in documents]# 计算相似度分数scores = reranker.predict(pairs)print("重排序分数:", scores)# 按分数降序排列,取前3个top_indices = scores.argsort()[-3:][::-1]print("最相关的文档:")for idx in top_indices: print(f" - {documents[idx]} (分数: {scores[idx]:.3f})")为什么需要重排序? - 向量搜索使用的双编码器(Bi-Encoder)将问题和文档分别编码成两个向量,计算效率高但精度有限。- 交叉编码器将问题和文档拼接后一起编码,能捕捉更细微的交互关系,但计算成本高。因此通常只对Top-10或Top-20结果进行重排序。## 五、高级应用:多模态RAG与流式输出### 5.1 多模态RAG现代RAG系统可以处理包含图片、表格的文档。例如,使用Unstructured库解析PDF中的表格数据。这里给出一个概念性的伪代码框架:python# 多模态RAG示例(概念性)from langchain.document_loaders import UnstructuredPDFLoaderfrom langchain.embeddings import OpenAIEmbeddingsfrom langchain.vectorstores import Chroma# 加载包含图片和表格的PDFloader = UnstructuredPDFLoader("report.pdf", mode="elements")docs = loader.load() # 自动提取文本、表格、图片描述# 对每个元素单独处理vectorstore = Chroma.from_documents(docs, OpenAIEmbeddings())# 用户提问时,检索器会返回文本和表格描述query = "去年公司营收是多少?"retrieved = vectorstore.similarity_search(query)# 将检索到的表格数据作为上下文输入LLMprint(retrieved[0].page_content) # 可能包含表格的Markdown表示### 5.2 流式输出(Streaming)为了提升用户体验,生成阶段可以支持流式输出,让用户看到逐字生成的过程。pythonfrom langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandlerfrom langchain.llms import OpenAI# 启用流式回调llm = OpenAI( temperature=0, streaming=True, callbacks=[StreamingStdOutCallbackHandler()])# 使用同样的RAG链,但这次输出会逐字打印qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever())# 提问,观察输出qa_chain.run("请详细解释Transformer的自注意力机制。")## 六、总结:RAG的未来与挑战RAG技术为大模型插上了"实时知识"的翅膀,但它并非万能。从本文的实践可以看出:1. 基础实现很简单 :只需几十行代码就能搭建一个可用的RAG系统。2. 优化是艺术 :混合检索、重排序、分块策略(如动态分块)等细节决定了最终效果。3. 未来方向 : - 代理式RAG(Agentic RAG) :让LLM自主决定何时检索、检索什么、如何利用结果。 - 多步推理RAG :对于复杂问题,系统可能需要多次检索和推理(如"思维链"+RAG)。 - 评估体系 :如何自动评估RAG系统的检索召回率和生成忠实度,仍是活跃的研究领域。最后一点建议 :在构建生产级RAG系统时,请务必关注数据安全 (不要将敏感数据暴露给外部API)、延迟优化 (使用缓存、异步处理)和成本控制(嵌入模型和LLM的调用费用)。RAG不是银弹,但它是当前解决大模型"知识短板"最实用的方案之一。