基于 Langchain 的 RAG 架构底层原理
当下,微调(Fine-Tuning)和检索增强生成(Retrieval-Augmented Generation,简称RAG)是大型语言模型(LLM)与专有数据之间融合贯通的最主流的两种方法。微调对数据集和硬件要求高,如果没有足够大的平台很难深入研究,因此本文主要涉及检索增强生成(RAG架构)领域知识。
概念
RAG 是一种使用额外数据增强 LLM 知识的技术,是 2020 年发表的论文 面向知识密集型 NLP 任务的检索增强生成中提出的新思想。LLM 通过外部知识源获取额外信息,从而生成更准确、更符合上下文的答案,并减少错误信息(或称为“幻觉”,即模型对用户意图的误解或在处理特定指示时产生了不准确的推断)。典型的 RAG 应用程序有两个主要组件:
- 索引:用于引入源数据并对其进行索引的管道
- 加载:将数据库中的大段文本读入系统
- 拆分:大块数据不便于搜索,且模型的上下文窗口有限,因此需要拆分数据
- 存储:采用向量数据库和索引存储数据
- 检索和生成:实际的 RAG 链,它在运行时接受用户查询并从索引中检索相关数据,然后将其传递给模型
- 检索:将用户的查询通过嵌入模型转化为向量,以便与向量数据库中的其他上下文信息进行比对。通过这种相似性搜索,可以找到向量数据库中最匹配的前 k 个数据
- 生成:将用户的查询和检索到的额外信息一起嵌入到一个预设的提示模板中,这个经过检索增强的提示内容会被输入到大语言模型 (LLM) 中,以生成所需的输出
从原始数据到响应生成最常见的流程图如下:
流程图2:
RAG 测评指标:
Langchain
LangChain 采用组件化设计的思想,将语言模型开发分为多个子任务:对话历史 Memory、提示工程 Prompt、输出解析 Parase、LLM链 Chain、索引 Indexes、代理 Agents。Langchain 模块化设计的中心思想是提升代码维护、扩展和重用的能力,可以快速开发多轮提示以及解析输出的应用。
此外,Langchain 还提供了对话过程中需要的基本功能,如:文档加载器 UnstructuredBaseLoader、文档分割器 TextSplitter、向量数据库存储和搜索 BaseChatMemory、多种工具类链调用 MapReduceDocumentsChain
还有额外的功能:API 用量记录、数据流返回 acall、缓存 SQLite、支持多种模型接口 OpenAI、向量数据库接口langchain.vectorstores
模型链 Chain
链(Chains)通常将大语言模型(LLM)与其他组件组合在一起来构建更复杂的链。比如,LLMChain 允许我们对创建的 Prompt 使用大模型;
Chain 基类是所有 Chain 对象的起点,处理输入、输出、历史和回调等功能,支持同步和异步调用,内部组件也可以通过回调进行交互;
自定义 Chain 需要继承 Chain 基类,实现 _call/_acall 方法定义调用逻辑;
对话历史 Memory
根据需求可以将历史存储在 SQLite、qdrant等数据库中,下面代码将历史存储在缓存中:
from langchain.memory import ConversationBufferMemory |
提示工程 Prompt
Prompt 很少是写明不变的,通常从多个组件构建而成的。 PromptTemplate 负责构建这个输入。
输出解析 Parase 是提示工程的一种:
get_format_instructions() -> str
:方法,返回一个包含有关如何格式化语言模型输出的字符串,即提示 Prompt。parse(str) -> Any
:方法,接受一个字符串(假定为语言模型的响应)并将其解析为某个结构。parse_with_prompt(str) -> Any
:一个方法,它接受一个字符串(假设是语言模型的响应)和一个提示(假设是生成这样的响应的提示),并将其解析为某种结构。提示在此大多数情况下是为了提供信息以便 OutputParser 重新尝试或以某种方式修复输出。
索引 Indexes
结构化文档,以便 LLM 可以与外部文档交互。 LangChain有许多模块可帮助您加载、结构化、存储和检索文档。
代理 Agents
代理涉及 LLM 做出行动决策(Observation)、执行该行动(Action)、查看一个观察结果(Observation),并重复该过程直到完成。LangChain 提供了一个标准的代理接口,一系列可供选择的代理,以及端到端代理的示例。
- 使用工具并观察其输出
- 生成相应返回给用户
文本召回
RAG 将匹配分为多个阶段,可分为:粗召回、细召回、粗排序、精排序、再排序。该任务包括多个子任务,如文本相似度计算、问答匹配、对话匹配,类似于RAG的文本抽取式阅读理解和多项选择
RAG 采取召回(IF-IDF、BM25)、粗排(双塔 Bert 模型、Sentence-Bert、text2vec、uniem)、精排(单塔 Bert 模型),得到相关的文档输入 LLM 中。
基于词匹配
传统方法将词匹配、词距等分数作为特征,用线性模型或树模型预测相关性,效果远不如深度学习。
将查询文本分词,词在文档 d 中出现的次数越多,则查询文本和文档 d 越相关
IF-IDF
概念
词袋模型:(bag of words)只考虑词频,不考虑词的顺序和上下文
词频 TF:每个词在文档中出现的次数的集合;$\sum_{t\in\mathcal{Q}}\operatorname{tf}_{t,d}$
- 缺陷:文档越长,TF 越大;解决:除以文档长度,归一化;$\sum_{t\in Q}\frac{\mathrm{tf}_{t,d}}{l_d}$
- 缺陷:每个词重要性不同;解决:语义重要性(term weight),在文档中出现的越多,权重越低;
文档频率 DF:词 t 在多少文档中出现过,定义“词”区别文档的能力;
逆文档排序 IDF:衡量一个词在 N 个文档中的重要性;$\mathrm{idf}_t=\mathrm{log}\frac N{\mathrm{df}_t}$
$\mathrm{TFIDF}(\mathcal{Q},d) = \sum_{t\in\mathcal{Q}} \frac{\mathrm{tf}{t,d}}{l{d}} \cdot \mathrm{idf}_{t}.$
其中,查询词q的分词后得到 Q 集合,它与文档 d 的相关性用 TF-IDF 衡量;结果还取决于所采取的分词算法;
BM25
IF-IDF 的变种,k 和 b 是参数(通常设置为 k∈[1.2, 2],b=0.75)
$\sum_{t\in Q}\frac{\mathrm{tf}{t,d}\cdot(k+1)}{\mathrm{tf}{t,d}+k\cdot\left(1-b+b\cdot\frac{l_d}{\mathrm{mean}(l_d)}\right)}\cdot\ln\left(1+\frac{N-\mathrm{df}_t+0.5}{\mathrm{df}_t+0.5}\right)$
基于词距
两个词在文档中出现位置之间,间隔的词越少越可能相关;
简而言之,查询词切分后的 term 在文档中出现的次数越多越好,任意两个词之间的距离越近越好;
eg:OkaTP
基于深度学习
基于交互策略的单塔模型 准确度更高
Bert 输出 similarity,二分类任务(相似/不相似)
**基于向量匹配的双塔模型 **速度更快
Bert 输出 Sentence_Embedding,拟合 cos_Similarity(回归任务)
评价指标
二分类评价指标 AUC
可信度 RAG
当检索返回的结果有错误或信息丢失时,会导致LLM回复出现幻觉。
为解决这个问题的三个指标:可信度(Faithfulness)、答案相关性(Answer Relevance)、上下文相关性(Context Relevance)
Query 预处理
同义Query,
意图识别
召回
索引技术:倒排索引、压缩倒排索
检索模型:BM25、BERT
引入上下文信息,更好地理解用户意图
Langchain-chatchat 源码解读
Q&A
Embedding 模型怎么保持加载的:EmbeddingPool;
metaData 存在哪里:MySQL 数据库;
LLMChain 怎么区分chatprompt和prompt的:ChatOpenAI、OpenAI;
docstore 和 index_to_docstore_id
存在哪里:本地文件 {index_name}.pkl
;