在构建一个成功的检索增强生成(RAG, Retrieval-Augmented Generation)应用时,不应仅专注于选择哪个RAG框架。关键在于深入理解RAG的工作原理,并且掌握不同RAG框架的优势和劣势,以便能够根据特定业务需求来灵活地选择和组合这些框架中的最佳实践。 单纯依赖大规模语言模型(LLM)可能会导致以下问题:

  • 产生误导性的信息(“幻觉”)

  • 训练数据随时间变得过时

  • 在处理特定知识时效率较低

  • 缺乏专业领域的深度见解

  • 推理能力有限 为了解决上述挑战,RAG技术成为了大模型时代的一个重要趋势。它通过结合从广泛的专业文档数据库中检索到的相关信息与语言模型的生成能力,从而显著提高了答案的准确性和相关性。 RAG的技术架构可以分为三个主要部分:

  1. 知识库构建
  • 涉及到收集、整理并存储大量的专业文档或资料,以备后续检索之用。
  1. 知识检索
  • 使用高效的搜索算法和技术,快速定位与用户查询最相关的文档片段。
  1. 知识问答
  • 利用检索到的信息以及预训练的语言模型,生成针对具体问题的答案。 通过这样的流程,RAG不仅克服了单独使用LLM时可能出现的问题,还增强了对特定领域知识的理解和回答质量。

RAG技术路线图

在构建知识库的过程中,RAG(Retrieval-Augmented Generation)技术路线图包括四个关键步骤:文件预处理、文件切分、向量化和构建索引。以下是对这些步骤的详细说明:

文件预处理

输入:原始文件,如PDF、Word等非结构化数据。 输出:纯文本。 技术手段:使用PDF、Word解析器,以及表格转文本、图片OCR等技术。 在实际应用中,用户希望通过上传私有领域的非结构化文档来进行大模型知识问答。这些文档中可能包含复杂的知识,如PDF中的图片、表格和公式等,需要被解析。 对于文件预处理,推荐使用ragflow中的deepdoc模块。deepdoc模块包含视觉处理和解析器两部分,支持OCR、布局识别、TSR等功能。下面是一个版面分析的示例。

文件切分

向量化

构建索引


文件预处理后,由于大模型输入token的限制(因为不可能一次性将所有文件,一次输入大模型),需要对文本分段。最简单的方法,一般采用识别字符的方式分段。仅识别字符切分段落会存在一个问题,即切分出来的段落大小长度不在同一长度范围。我们一般采用langchain中的RecursiveCharacterTextSplitter方法,尽量将所有段落(然后是句子,然后是单词)放在一起,因为这些文本在语义上似乎是最相关的文本片段。使用示例如下:

1
from langchain_text_splitters import RecursiveCharacterTextSplitter # Load example document with open("state_of_the_union.txt") as f: state_of_the_union = f.read() text_splitter = RecursiveCharacterTextSplitter( # Set a really small chunk size, just to show. chunk_size=100, chunk_overlap=20, length_function=len, is_separator_regex=False, ) texts = text_splitter.create_documents([state_of_the_union]) print(texts[0]) print(texts[1])

除了识别以上方法外,在langchain中还提供了大量文件切分的方法。比如HTMLHeaderTextSplitter,可以提取网页url中的html,并切分。

1
url = "https://plato.stanford.edu/entries/goedel/" headers_to_split_on = [ ("h1", "Header 1"), ("h2", "Header 2"), ("h3", "Header 3"), ("h4", "Header 4"), ] html_splitter = HTMLHeaderTextSplitter(headers_to_split_on) # for local file use html_splitter.split_text_from_file(<path_to_file>) html_header_splits = html_splitter.split_text_from_url(url) chunk_size=100, chunk_overlap=20, length_function=len, is_separator_regex=False, ) texts = text_splitter.create_documents([state_of_the_union]) print(texts[0]) print(texts[1])

在处理大模型Rag时,除了需要关注基础的模型构建和训练外,还需要注意一些细节调整,如文件切分和数据处理。这些细节的处理将大大影响最终Rag系统的质量。目前,知乎知学堂推出了一门程序员的AI大模型进阶之旅公开课,详细介绍了Rag常用的技术,包括段落切分算法的设计、embedding算法的选择以及如何搭建自己的Rag应用等。如果你对大模型有一定的了解,这个课程一定会对你有所帮助。

向量化

向量化是将文本、图像、音频和视频等转化为向量矩阵的过程,使它们变成计算机可以理解的格式。这个过程通常涉及以下步骤:

  1. 输入: 分段后的文本
  2. 输出: 向量化后的向量组
  3. 技术手段: 通过远程调用智谱、OpenAI的embedding模型进行向量化处理。
  4. 输出结果展示: 显示向量化后的向量组,以便于进一步分析和处理。 注意:在进行向量化处理时,选择合适的embedding模型和技术手段是非常重要的,这将直接影响到后续模型的性能和效果。

除了远程调用embedding模型外,还有许多开源可私有化部署的embedding模型可供选择。比如:

  • paddlenlp

个人使用比较好用的是rocketqa-zh-base-query-encoder算法,代码示例如下:

1
>>> from paddlenlp import Taskflow >>> import paddle.nn.functional as F >>> text_encoder = Taskflow("feature_extraction", model='rocketqa-zh-base-query-encoder') >>> text_embeds = text_encoder(['春天适合种什么花?','谁有狂三这张高清的?']) >>> text_features1 = text_embeds["features"] >>> text_features1 Tensor(shape=[2, 768], dtype=float32, place=Place(gpu:0), stop_gradient=True, [[ 0.27640465, -0.13405125, 0.00612330, ..., -0.15600294, -0.18932408, -0.03029604], [-0.12041329, -0.07424965, 0.07895312, ..., -0.17068857, 0.04485796, -0.18887770]]) >>> text_embeds = text_encoder('春天适合种什么菜?') >>> text_features2 = text_embeds["features"] >>> text_features2 Tensor(shape=[1, 768], dtype=float32, place=Place(gpu:0), stop_gradient=True, [[ 0.32578075, -0.02398480, -0.18929179, -0.18639392, -0.04062131, ...... >>> probs = F.cosine_similarity(text_features1, text_features2) >>> probs Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True, [0.86455142, 0.41222256])

paddlenlp支持的embedding算法如下所示:


在处理自然语言处理任务时,我们经常需要将文本数据转换为向量形式,以便进行高效的相似性比较和检索。以下是构建索引和使用向量数据库的步骤,以提高匹配效率和准确性。

1. 文本向量化

首先,我们需要将文本段落转换为向量形式。这可以通过使用预训练的语言模型来实现,例如智源(BAAI)的bge-large-zh模型。向量化是将文本转换为机器可理解的数值表示的关键步骤。

2. 构建索引

向量化后的文本需要构建索引,以便快速进行向量匹配。虽然可以通过简单的for循环逐个匹配向量,但这种方法在大规模数据集上效率极低。因此,构建索引是提高检索速度的重要步骤。

3. 使用向量数据库

在实际的生产环境中,我们通常会使用向量数据库来存储和管理向量化的数据。市场上有多种成熟的向量数据库可供选择,其中最常用的是Elasticsearch和Milvus。这些数据库能够有效地处理大规模的向量数据,并支持快速的相似性搜索。

4. 集成向量数据库

在实际应用中,我们可以通过使用特定的框架和库来集成向量数据库。例如,LangChain提供了与Elasticsearch的集成,使得我们可以方便地利用Elasticsearch的强大功能来进行向量检索。

5. 注意事项

在选择向量数据库和构建索引时,需要考虑到数据的规模、查询的频率以及系统的可扩展性等因素。正确的选择和配置可以显著提高系统的性能和用户体验。

1
from langchain import ElasticVectorSearch from langchain_community.embeddings import OpenAIEmbeddings embedding = OpenAIEmbeddings() elastic_host = "http://127.0.0.1" elasticsearch_url = f"https://username:password@{elastic_host}:9200" elastic_vector_search = ElasticVectorSearch( elasticsearch_url=elasticsearch_url, index_name="test_index", embedding=embedding ) ElasticVectorSearch.from_documents(batch_docs, embedding, elasticsearch_url=elasticsearch_url, index_name="test_index")

向量检索

向量检索是信息检索的一种方法,它包括两个主要步骤:

  1. 向量化
  • 用户提出的问题需要转换成一个向量表示。这个过程依赖于一个预训练的模型,该模型能够将文本映射到多维空间中的向量。
  1. 知识库检索
  • 在构建好的知识库中查找与用户问题最相似的信息。知识库本身也由一系列已经向量化的条目组成。

向量化一致性

为了确保检索的有效性,用户问题和知识库条目的向量化必须使用相同的模型。这意味着如果在创建知识库时使用的是一种特定的词嵌入或语言模型(例如BERT, Word2Vec等),那么处理用户查询时也需要应用相同的模型来生成对应的向量。

相似度匹配

一旦用户的查询被转换为向量,系统就会计算这个向量与知识库中所有条目的向量之间的相似度。通常我们会设定一个超参数k,用来指定返回最相关结果的数量。k值是由用户根据具体应用场景确定的。

计算相似度

常用的相似度计算方法有欧氏距离、曼哈顿距离以及余弦相似度等。在这里我们选择余弦相似度作为衡量标准,因为它能很好地反映向量方向上的相似程度,而不仅仅是它们之间的绝对距离。

余弦相似度公式

余弦相似度是通过计算两向量夹角的余弦值来判断它们之间相似程度的方法。其公式如下:

其中A和B代表两个非零向量。当这两个向量越接近平行时,它们的余弦相似度就越接近1;反之,则越接近0。

余弦相似度公式

代码实现如下:

1
def cosine_similarity(cls, vector1: List[float], vector2: List[float]) -> float: """ calculate cosine similarity between two vectors """ dot_product = np.dot(vector1, vector2) magnitude = np.linalg.norm(vector1) * np.linalg.norm(vector2) if not magnitude: return 0 return dot_product / magnitude

在实际应用中,为了提高信息检索的准确性和效率,我们通常会采用多路召回融合重排序策略。#### 多路召回

  • 定义:多路召回是指利用多种不同的策略、特征或简易模型来分别召回候选集的一部分。

  • 目的:通过增加候选对象的数量,提高命中与问题相关文档的可能性。

  • 效果:虽然可以提升召回的全面性,但也会带来候选对象过多的问题,这可能会影响召回的准确率。

融合重排序(Rerank)

  • 必要性:当多路召回导致候选对象数量庞大时,需要对这些候选进行重新评估和排序。

  • 功能:重排序模型负责评估上下文的相关性,并将最有可能提供准确答案的上下文优先级提高。

  • 优势:通过重排序,可以让大语言模型(LLM)在生成回答时参考更相关的上下文,从而增强回复的质量和准确性。

  • 现有解决方案:目前市面上较为优秀的重排序模型包括商业服务Cohere以及开源选项如智源发布的bge-reranker-base和bge-reranker-large。

  • 推荐使用:实践中推荐采用bge-reranker-large作为重排序工具。

知识问答流程

1. 提示工程模板设计

  • 输入:用户提出的问题及从知识库中提取出的k个最相关段落。

  • 输出:根据提示工程模板,将用户的提问与选定的知识库段落整合成一个完整的上下文环境,供后续处理使用。

  • 作用:构建一个结构化的查询框架,使得LLM能够基于提供的上下文给出更加精准的答案。

1
RAG_PROMPT_TEMPALTE="""使用以上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。 问题: {question} 可参考的上下文: ··· {context} ··· 如果给定的上下文无法让你做出回答,请回答数据库中没有这个内容,你不知道。 有用的回答:"""

rag(Retrieval-Augmented Generation)是一种结合了信息检索和生成模型的技术,用于提高文本生成的质量。其工作原理主要分为以下几个步骤:

  1. 用户提问:用户提出一个问题或请求。

  2. 上下文检索:基于用户的提问,系统从预定义的知识库中检索出与问题相关的top k个段落作为背景资料。

  3. 调用大模型LLM:利用大型语言模型(如Qwen2-7B-Instruct系列),将用户的原始问题与上一步骤中找到的相关段落一同输入给模型。

  • 选择合适的大模型:在个人体验过程中,发现参数量较大的模型通常表现更好,特别是Qwen2-7B-Instruct系列模型,在处理rag任务时表现出色。
  1. 生成答案:大模型根据提供的信息综合生成一个既准确又连贯的答案。

rag子模块的选择建议

  • 知识库构建:确保知识库覆盖广泛且更新及时,这直接影响到检索结果的相关性和准确性。

  • 检索算法:采用高效的检索算法来快速定位最相关的内容;可以考虑使用向量化搜索技术以提升效率。

  • 大模型的选择:选择合适的大型语言模型对于生成高质量回答至关重要;除了性能外,还应考虑成本、易用性等因素。

  • 持续优化:通过收集反馈不断调整各个组件的设置,比如改善检索策略、微调模型等,从而达到更好的用户体验。 遵循上述指导原则可以帮助你更好地实现并优化你的rag系统。