目录

RAG

https://mp.weixin.qq.com/s/gwEmcxDmc8zGS3uMLXpuyw

Retrieval-Augmented Generation 检索增强生成

深入理解LLM RAG检索生成

https://www.bilibili.com/read/cv29676672/

大语言模型的问题

  • 偏见
  • 幻觉(信息可信度)
  • 过时(时效性)

大语言模型的知识来源

  • 训练的知识
  • 用户的输入
  • 外部索引、知识库

外挂数据

  • 外部向量数据库
  • 外部知识图谱
  • 文档数据
  • WEB数据等

RAG.assets/217c62d3a4bed44cca9113c4f08e0107364471435.png@1256w_760h_!web-article-pic.avif

经过Doc Loader,加载各种数据源的数据,经过embedding向量化后存储进向量数据库。这是Retrieval-augmented基础数据处理器。用户通过 QA向LLM提问,会通过QA问题向向量数据库召回相似度较高的上下文,通过Prompt提示词一起发给LLM,LLM通过问题与上下文一起生成答案返回给用户。

RAG实现

数据加载(Document Loaders)

langchain_community.document_loaders

数据处理(Text Splitters)

数据分割

加载完数据后,我们下一步通常需要将数据进行拆分,尤其是在处理长文本的情况下。如何将文本进行分割处理,听起来很简单,比如我按400个字符,直接切片就好了,但往往这样应用效果不甚理想。

我们通常希望能将将语义相关的文本片段保留在一起。 重点其实就在这个“语义相关”,比如中文,我们希望是句号为分割符,比如一段长代码,我们希望以编程语言特点来分割,比如Python中的def、class

以langchain为例,langchain目前支持HTML、字符、MarkdownHeader和多种代码分割,甚至正在实验中的语义分割。

  1. 按MarkdownHeader分割
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from langchain.text_splitter import MarkdownHeaderTextSplitter

markdown_document = "# Foo\n\n ## Bar\n\nHi this is Jim\n\nHi this is Joe\n\n ### Boo \n\n Hi this is Lance \n\n ## Baz\n\n Hi this is Molly"

headers_to_split_on = [
("#", "Header 1"),
("##", "Header 2"),
("###", "Header 3"),
]

markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_document)
md_header_splits

{'content': 'Hi this is Jim  \nHi this is Joe', 'metadata': {'Header 1': 'Foo', 'Header 2': 'Bar'}}
{'content': 'Hi this is Molly', 'metadata': {'Header 1': 'Foo', 'Header 2': 'Baz'}}
  1. metadata

在进行文本分割的同时,我们还可以给分割的文本添加一下metadata的数据,方便记录该文本段的一些基本信息,如文章来源、作者信息等。 一个是能在进行文本召回时可以作为过滤搜索,另一方面还在作为发给LLM的补充数据,让LLM生成的内容更为丰富

1
2
metadatas = [{"document": 1}, {"document": 2}]
documents = text_splitter.create_documents([state_of_the_union, state_of_the_union], metadatas=metadatas) print(documents[0])

2.2.3 分割参数

在进行文本分割时,我们还需要重点关注两个参数 chunk_size 和 chunk_overlap,这两个参数分别表示分割长度和两段分割文本重合长度。 在实际RAG应用中,chunk_size需要结合向量数据库的来选择合适大小,比如腾讯云的向量数据库,一次只支持单块512token(400左右字符)的大小写入,那chunk_size就应该设设置400多。chunk_overlap的大小建议设置在chunk_size的1/5左右,在召回多段文本时,可以增加数据的丰富度。 实际情况请结合具体项目进行设置和测试验证

2.3 数据向量化 (Text embedding models)

在进行数据分割后,需要对文本数据段进行向量化,目前主流的中文向量化模型有

RAG.assets/e3efe65fec033d2f0afc8b086129f4fd364471435.png@!web-article-pic.avif

使用OpenAI Embeddings向量化处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from langchain_openai import OpenAIEmbeddings

embeddings_model = OpenAIEmbeddings(openai_api_key="...")

embeddings = embeddings_model.embed_documents(
    [
        "Hi there!",
        "Oh, hello!",
        "What's your name?",
        "My friends call me World",
        "Hello World!"
    ]
)
len(embeddings), len(embeddings[0])

(5, 1536)

目前Langchain支持37种embedding model,这些向量化模型核心功能就将文本向量化,提供给向量数据库进行存储

2.4 向量数据库 (Vector stores)

RAG.assets/6283c23d69b57e6c3a07d6318174a45c364471435.png@1256w_522h_!web-article-pic.avif

2.5 数据召回(Retrievers)

在讲解完数据加载、数据处理、数据向量化和向量数据库后,我们开始进入数据召回的环节。数据召回是我们向 LLM提问时,需要根据我们提问的问题向向量数据库召回相关的文档数据,并和问题加载进Prompt发送给LLM。

比如下面这段提示词:

1
2
3
4
5
6
7
template = """
Answer the question based only on the following context:

{context}

Question: {question}
"""

context就是我们召回的上下文

数据召回的方法有许多种,应用在不同应用场景,当前Langchain主流支持的Retrievers有以下8种

RAG.assets/b46260aeff6070de0cc0519e67d8cd50364471435.png@1256w_2174h_!web-article-pic.avif

这里简述一下不同Retrievers的主要应用场景,大家可以具体问题具体分析,再去查阅一下相关文档

  1. Vectorstore 基础的向量召回方法,根据用户问题直接向向量数据库召回数据
  2. ParentDocument 如果你文档数据有许多不同的小块信息,你可以根据问题检索出小块信息,再根据小块信息去索引出原文档或者更大块的数据,将大块数据作为上下文发送给LLM
  3. Multi Vector 如果你有相关问题的数据集,可以为问题和文档分别存储到不同的向量数据库,在检索时可以根据问题检索出合适文档上下文
  4. Self Query 如果你提出的问题可以通过基于元数据(而不是与文本的相似性)来获取文档,可以使用这种Retrievers,利用LLM的能力,自动生成对应的检索方法,来召回数据
  5. Contextual Compression 如果您发现您检索的文档包含太多不相关的信息,并且分散了LLM的注意力,可以利用上下文压缩的方法,将召回的数据利用LLM进行数据处理
  6. Time-Weighted Vectorstore 如果你的文档数据中包含时间相关的数据,可以考虑用此Retriever
  7. Multi-Query Retriever 用户提出的问题很复杂,需要多个不同的信息来回答,可以使用此Retriever,利用LLM生成多个相关的问题,再分别从向量数据库召回数据
  8. Ensemble 如果您有多种检索方法,并希望尝试将它们组合起来,可以使用此Retriever
  9. Long-Context Reorder 当你需要召回多段上下文数据时,但发现LLM并没有根据你的上下文来回答问题时,可以考虑使用 Retriever对你召回的数据进行重新排序,将相似度较高的排在前面,让LLM能更好的利用上下文来回答问题

2.5.1 数据召回算法

在数据召回中,目前业内有两种较为通用的召回算法

  1. 相似度匹配算法(Similarity Search with Euclidean Distance) 这是向量数据库自身具备的特点,通过比较向量之间的距离来判断它们的相似度。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
COLLECTION_NAME = "state_of_the_union_test"

db = PGVector.from_documents(
embedding=embeddings,
documents=docs,
collection_name=COLLECTION_NAME,
connection_string=CONNECTION_STRING,
)
query = "What did the president say about Ketanji Brown Jackson"
docs_with_score = db.similarity_search_with_score(query)

for doc, score in docs_with_score:
print("-" * 80)
print("Score: ", score)
print(doc.page_content)
print("-" * 80)
  1. 最大边界相关算法(Maximal Marginal Relevance) 采用这个算法,会优化召回的数据段之间的相似程度和多样性,对数据重新打分
1
2
3
4
5
6
7
docs_with_score = db.max_marginal_relevance_search_with_score(query)

for doc, score in docs_with_score:
print("-" * 80)
print("Score: ", score)
print(doc.page_content)
print("-" * 80)

算法Python实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def maximal_marginal_relevance(
    query_embedding: np.ndarray,
    embedding_list: list,
    lambda_mult: float = 0.5,
    k: int = 4,
) -> List[int]:
    """Calculate maximal marginal relevance."""
    if min(k, len(embedding_list)) <= 0:
        return []
    if query_embedding.ndim == 1:
        query_embedding = np.expand_dims(query_embedding, axis=0)
    similarity_to_query = cosine_similarity(query_embedding, embedding_list)[0]
    most_similar = int(np.argmax(similarity_to_query))
    idxs = [most_similar]
    selected = np.array([embedding_list[most_similar]])
    while len(idxs) < min(k, len(embedding_list)):
        best_score = -np.inf
        idx_to_add = -1
        similarity_to_selected = cosine_similarity(embedding_list, selected)
        for i, query_score in enumerate(similarity_to_query):
            if i in idxs:
                continue
            redundant_score = max(similarity_to_selected[i])
            equation_score = (
                lambda_mult * query_score - (1 - lambda_mult) * redundant_score
            )
            if equation_score > best_score:
                best_score = equation_score
                idx_to_add = i
        idxs.append(idx_to_add)
        selected = np.append(selected, [embedding_list[idx_to_add]], axis=0)
    return idxs
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
代码功能:

该代码定义了一个名为 maximal_marginal_relevance 的函数,用于实现 MMR 算法。
它接受以下参数:
query_embedding: 查询的嵌入表示(向量形式)。
embedding_list: 待选句子的嵌入表示列表。
lambda_mult: 用于权衡相关性和多样性的参数(0-1 之间)。
k: 要选择的句子数量。
它返回一个整数列表,表示选出的句子索引。
算法步骤:

初始化:

处理边界情况:如果 k 或 embedding_list 长度为 0 或更小,则直接返回空列表。
如果 query_embedding 是 1 维向量,则扩展其维度以适应余弦相似度计算。
计算每个句子与查询的余弦相似度。
选择与查询最相似的句子作为初始句子,并记录其索引。
迭代选择句子:

循环直到选出 k 个句子或所有句子都被考虑:
初始化当前最佳分数为负无穷大。
计算剩余每个句子与已选句子集的最大相似度(代表冗余程度)。
使用 MMR 公式计算每个句子的综合得分: equation_score = lambda_mult * query_score - (1 - lambda_mult) * redundant_score
选择得分最高的句子,并记录其索引。
将选中的句子加入已选集合。
返回结果:

返回所有选中句子的索引列表。

https://www.anyscale.com/blog/a-comprehensive-guide-for-building-rag-based-llm-applications-part-1

构建基于RAG的LLM生产应用程序

加载数据

Ray Dataset

https://docs.ray.io/en/master/ray-overview/index.html

Raydataset 是一个开源的 Python 库,用于构建和管理大型分布式数据集。它基于 Ray 框架,可以利用 Ray 的并行计算能力来加速数据集的加载、处理和查询。

Raydataset 的主要功能包括:

  • 支持多种数据格式,包括 CSV、JSON、TFRecord 等。
  • 支持多种数据加载方式,包括本地加载、远程加载、流式加载等。
  • 支持多种数据处理方式,包括数据过滤、数据转换、数据聚合等。
  • 支持多种数据查询方式,包括随机查询、范围查询、全文搜索等。

Raydataset 可以应用于各种场景,例如:

  • 机器学习:用于训练和评估机器学习模型。
  • 自然语言处理:用于处理和分析自然语言数据。
  • 图像处理:用于处理和分析图像数据。
  • 数据分析:用于分析和挖掘数据。

分块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def chunk_section(section, chunk_size, chunk_overlap):
    text_splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n", " ", ""],
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len)
    chunks = text_splitter.create_documents(
        texts=[sample_section["text"]], 
        metadatas=[{"source": sample_section["source"]}])
    return [{"text": chunk.page_content, "source": chunk.metadata["source"]} for chunk in chunks]

向量化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class EmbedChunks:
    def __init__(self, model_name):
        if model_name == "text-embedding-ada-002":
            self.embedding_model = OpenAIEmbeddings(
                model=model_name,
                openai_api_base=os.environ["OPENAI_API_BASE"],
                openai_api_key=os.environ["OPENAI_API_KEY"])
        else:
            self.embedding_model = HuggingFaceEmbeddings(
                model_name=model_name,
                model_kwargs={"device": "cuda"},
                encode_kwargs={"device": "cuda", "batch_size": 100})

    def __call__(self, batch):
        embeddings = self.embedding_model.embed_documents(batch["text"])
        return {"text": batch["text"], "source": batch["source"], "embeddings": 
embeddings}

embedding_model_name = “thenlper/gte-base”

存储

RAG.assets/image2.pngpgvector

text, source, embedding

问题检索

RAG.assets/image14.png

问题向量化 -> 检索TopK

生成回答

我们使用了0.0的温度来进行可重复的实验,但你应该根据你的使用情况来调整。对于需要始终以事实为基础的用例,我们推荐非常低的温度值,而更多的创造性任务可以从更高的温度中获益。

1
2
3
4
5
6
7
query = "What is the default batch size for map_batches?"
response = generate_response(
    llm="meta-llama/Llama-2-70b-chat-hf"
    temperatune=0. 0,
    stream=True,
    system_content="Answer the query using the context provided. Be succinct.",简明扼要
    user_content=f"query: {query}, context: (context)")

Agent

写到了一个函数里

Evaluation

RAG.assets/llm_evaluations.png

LLM Experiments

RAG.assets/image13.png

Embedding models

[“thenlper/gte-base”, “thenlper/gte-large”, “BAAI/bge-large-en”, “text-embedding-ada-002”]

这是一个有趣的结果,因为当前排行榜上的第一名(BAAI/bge large en)不一定是我们特定任务的最佳人选。在我们的实验中,使用较小的lper/gte-large产生了最好的检索和质量分数。

OSS vs. closed LLMs

1
2
3
4
5
6
7
8
9
llms = ["gpt-3.5-turbo",
        "gpt-4",
        "gpt-4-1106-preview",
        "meta-llama/Llama-2-7b-chat-hf", 
        "meta-llama/Llama-2-13b-chat-hf", 
        "meta-llama/Llama-2-70b-chat-hf",
        "codellama/CodeLlama-34b-Instruct-hf",
        "mistralai/Mistral-7B-Instruct-v0.1",
        "mistralai/Mixtral-8x7B-Instruct-v0.1"]

Note: Some of our LLMs have much larger context lengths, ex. gpt-4 is 8,192 tokens and gpt-3.5-turbo-16k is 16,384 tokens. We could increase the number of chunks that we use for these since we saw that increasing num_chunks continued to improve the retrieval and quality scores. However, we will keep this value fixed for now since the performance started to taper off anyway and so we can compare these performances under the exact same configurations.

RAG.assets/Screenshot_2023-12-17_at_2.41.18_AM.png

注意:我们的一些LLM具有更大的上下文长度,例如gpt-4是8192个令牌,gpt-3.5-turb-16k是16384个令牌。我们可以增加用于这些的块的数量,因为我们看到增加num_chunk可以继续提高检索和质量分数。然而,由于性能开始逐渐下降,我们暂时将保持该值不变,因此我们可以在完全相同的配置下比较这些性能。

Fine-tuning

Prompt engineering

当涉及到设计提示时,我们可以做的太多了(思考的x、多模式、自我精炼、查询分解等),所以我们只尝试一些有趣的想法。我们将允许LLM忽略任何不相关的内容。这里的想法是展示我们从即时工程到评估报告的速度有多快

1
2
3
4
5
6
7
8
9
# Prompt
generation_system_content = "Answer the query using the context provided. Be succinct. Contexts are organized in a list of dictionaries [{'text': <context>}, {'text': <context>}, ...]. Feel free to ignore any contexts in the list that don't seem relevant to the query. "

# Evaluate
experiment_name = "prompt-ignore-contexts"
run_experiment(
    experiment_name=experiment_name,
    generation_system_content=generation_system_content,  # new prompt
    **kwargs)

这种具体的快速工程努力似乎无助于提高我们系统的质量。正如我们前面提到的,我们有太多其他方法可以设计我们的提示,我们鼓励您探索更多。这里重要的是,我们有一种干净简单的方法来评估我们想要实验的任何东西。然而,我们根据经验发现,提高我们的检索系统和数据飞轮(我们修复文档本身的地方)的质量对我们系统的整体质量产生了更大的影响。

Lexical search 词汇搜索

现在,我们将用传统的词汇搜索来补充基于矢量嵌入的搜索,该搜索在查询和文档块之间搜索精确的标记匹配。我们的直觉是,词汇搜索可以帮助识别具有精确关键字匹配的块,而语义表示可能无法捕捉到这些块。特别是对于我们的嵌入模型中词汇表之外的标记(因此通过子标记表示)。但我们基于嵌入的方法对于捕捉隐含含义仍然非常有利,因此我们将结合基于矢量嵌入的搜索和词汇搜索中的几个检索块。

RAG.assets/rag-based-llm-applications-lexical-search.png

BM25

将其与检索到的源与我们现有的基于向量嵌入的搜索进行比较表明,这两种方法虽然不同,但都检索到了相关的源。因此,我们将把这两种方法结合起来,并将其输入到我们的LLM生成的上下文中。

添加词汇搜索似乎没有我们希望的那么有影响力,但这只是我们探索的词汇搜索的一个方面(关键词匹配),但还有许多其他有用的功能,如过滤、计数等。我们还值得探索如何将词汇搜索结果与语义搜索结果相结合。

Reranking

到目前为止,在我们所有的方法中,我们都使用了嵌入模型(+词汇搜索)来识别数据集中前k个相关的块。块的数量(k)一直是一个小数字,因为我们发现添加太多块没有帮助,而且我们的LLM限制了上下文长度。然而,这一切都是在假设前k个检索到的块确实是最相关的块,并且它们的顺序也是正确的。如果增加块的数量没有帮助,因为一些相关的块在有序列表中要低得多。而且,语义表示虽然非常丰富,但并没有针对这一特定任务进行训练。 在本节中,我们实现了重新排序,这样我们就可以使用语义和词汇搜索方法在数据集上撒下更大的网(检索许多块),然后根据用户的查询重新排序。这里的直觉是,我们可以通过特定于用例的排名来解释语义表示中的差距。我们将训练一个监督模型,该模型预测文档的哪一部分与给定用户的查询最相关。我们将使用这个预测来重新排列相关的块,以便将文档中这一部分的块移到列表的顶部。

RAG.assets/rag-based-llm-applications-reranking.png

RAG中文基准 CRUD-RAG

https://arxiv.org/abs/2401.17043

• Chunk size: 64, 128, 256, 512.

• Chunk overlap: 0%, 10%, 30%, 50%, 70%.

• Embedding model: m3e-base, bge-base, stella-base, gte-base.

• Retriever: dense, bm25, hybrid, hybrid+rerank. • Top-k: 2, 4, 6, 8, 10.

• Base LLMs: GPT-3.5, GPT-4, ChatGLM2-6B, Baichuan2-13B, Qwen-7B, Qwen-14B.

4.2 块大小对RAG性能的影响

  • 实验结果表明,在文本续写任务中,较大的块大小可以显著提高整体语义相似性指标(如BLEU、ROUGE、BERTScore)和RAGQuestEval指标,这表明较大的块有助于保持原文的结构,对于创造性任务尤为重要。
  • 在多文档摘要任务中,较大的块大小虽然增加了生成文本的长度,但也可能导致BLEU分数的显著下降,而ROUGE-L和BERTScore保持相对稳定。

4.3 块重叠对RAG性能的影响

  • 在文本续写任务中,随着块重叠的增加,生成文本与参考答案的对齐度(BLEU、ROUGE、BERTScore)显著提高,RAGQuestEval指标也有所改善。
  • 在多文档摘要任务中,所有评估指标都随着块重叠的增加而略有提高,尤其是在召回率方面。

4.4 检索器对RAG性能的影响

  • 文本续写任务中,密集检索器(Dense retriever)优于基于关键词的BM25检索器,因为密集检索器能够捕捉到更多的语义和上下文信息。
  • 在多文档摘要任务中,BM25检索器在整体语义相似性指标上表现更好,但在关键信息的精确度上,密集检索器更优。

检索器

在RAG(Retrieval-Augmented Generation)系统中,检索器(Retriever)是一个关键组件,其主要功能是根据用户的输入(如问题或查询)从外部知识库或文档集合中检索出最相关的信息片段或文档。这些检索到的信息随后被用作大型语言模型(LLM)生成回答的上下文或辅助信息。

检索器的工作原理通常涉及以下几个步骤:

  1. 查询处理:接收用户的输入并将其转换为适合检索的格式,这可能包括分词、去除停用词、词干提取等预处理步骤。
  2. 相似度计算:使用特定的算法(如TF-IDF、BM25、密集向量检索等)来计算用户查询与知识库中每个文档的相似度得分。
  3. 排名和选择:根据相似度得分对文档进行排序,并选择得分最高的前k个文档作为检索结果。这里的k值(Top-k)是一个重要的参数,决定了提供给LLM的上下文信息量。
  4. 上下文生成:将检索到的文档片段或整个文档作为上下文信息,与用户的输入一起输入到LLM中,以生成最终的回答。

检索器在RAG系统中的作用至关重要,因为它直接影响到LLM生成回答的质量和准确性。一个高效的检索器能够快速准确地找到与用户查询最相关的信息,从而提高整个系统的性能。在实际应用中,检索器可能需要根据特定的应用场景进行优化,以确保检索结果的相关性和多样性。

BM25(BM25 Retrieval)

  • BM25是一种基于概率的排名函数,用于信息检索系统中的文档检索。它是由Brett M. Carter和Stephen T. Robertson在1992年提出的,旨在模拟用户在搜索引擎中输入查询时,文档与查询的相关性。
  • BM25考虑了查询词在文档中的频率(TF)以及在整个文档集合中的逆文档频率(IDF),以此来计算查询词与文档之间的相关性得分。
  • BM25的优点是计算简单且高效,但它主要基于关键词的频率,可能无法充分捕捉到文档的语义信息。

Dense(Dense Retrieval)

  • Dense检索器使用深度学习模型(如BERT、GPT等)将文档和查询转换为高维向量表示。然后,通过计算这些向量之间的相似度(如余弦相似度)来确定文档的相关性。
  • Dense检索器能够捕捉到文档的深层语义信息,因为它利用了预训练语言模型对文本的理解能力。这使得Dense检索器在处理需要理解上下文和语义的任务时表现更好。
  • 然而,Dense检索器的计算成本较高,因为它依赖于复杂的神经网络模型。

Hybrid(Hybrid Retrieval)

  • Hybrid检索器结合了BM25和Dense检索器的优点。它首先使用BM25检索器快速筛选出一批候选文档,然后使用Dense检索器对这些文档进行更精细的语义相关性排序。
  • Hybrid检索器旨在平衡检索速度和准确性。通过BM25的快速筛选,可以减少Dense检索器需要处理的文档数量,从而降低整体的计算成本,同时保持较高的检索质量。

4.5 嵌入模型对RAG性能的影响

  • 在创造性任务如文本续写中,不同嵌入模型之间的性能差异较小,但在需要精确定位相关文档的问答任务中,m3e-base模型表现最差。
  • 对于幻觉修改任务,m3e-base模型在所有指标上表现最好,这表明它在纠正文本中的错误方面更有效。

4.6 Top-k值对RAG性能的影响

  • 在文本续写任务中,增加Top-k值可以提高生成文本的多样性和准确性。
  • 在多文档摘要任务中,增加Top-k值会导致更长但质量较低的摘要,而在问答任务中,增加Top-k值可以提高召回率和精确度。

4.7 LLM对RAG性能的影响

  • 在文本续写和多文档摘要任务中,模型参数越大,性能越好,GPT-4模型在所有任务中表现最佳。
  • 在单文档问答任务中,Qwen和Baichuan2模型在某些情况下表现优于GPT系列模型,而在多文档问答任务中,GPT-4模型表现出色。

4.8 优化RAG系统的建议

  • 文章根据实验结果提供了关于如何优化RAG系统性能的建议,包括选择合适的Top-k值、块大小、块重叠、嵌入模型、检索器和LLM。

Table 6. The experimental results for evaluating different embedding models in our benchmark.

RAG.assets/image-20240211184034345.png

RAG.assets/image-20240211184051733.png

网易 QAnything

https://github.com/netease-youdao/QAnything

致力于支持任意格式文件或数据库的本地知识库问答系统,可断网安装使用

目前已支持格式: PDF(pdf)Word(docx)PPT(pptx)XLS(xlsx)Markdown(md)电子邮件(eml)TXT(txt)图片(jpg,jpeg,png)CSV(csv)网页链接(html)

特点

  • 数据安全,支持全程拔网线安装使用。
  • 支持跨语种问答,中英文问答随意切换,无所谓文件是什么语种。
  • 支持海量数据问答,两阶段向量排序,解决了大规模数据检索退化的问题,数据越多,效果越好。
  • 高性能生产级系统,可直接部署企业应用。
  • 易用性,无需繁琐的配置,一键安装部署,拿来就用。
  • 支持选择多知识库问答。

架构

RAG.assets/qanything_arch.png

知识库数据量大的场景下两阶段优势非常明显,如果只用一阶段embedding检索,随着数据量增大会出现检索退化的问题,如下图中绿线所示,二阶段rerank重排后能实现准确率稳定增长,即数据越多,效果越好

RAG.assets/two_stage_retrieval.jpg

一阶段检索(embedding)

模型名称 Retrieval STS PairClassification Classification Reranking Clustering 平均
bge-base-en-v1.5 37.14 55.06 75.45 59.73 43.05 37.74 47.20
bge-base-zh-v1.5 47.60 63.72 77.40 63.38 54.85 32.56 53.60
bge-large-en-v1.5 37.15 54.09 75.00 59.24 42.68 37.32 46.82
bge-large-zh-v1.5 47.54 64.73 79.14 64.19 55.88 33.26 54.21
jina-embeddings-v2-base-en 31.58 54.28 74.84 58.42 41.16 34.67 44.29
m3e-base 46.29 63.93 71.84 64.08 52.38 37.84 53.54
m3e-large 34.85 59.74 67.69 60.07 48.99 31.62 46.78
bce-embedding-base_v1 57.60 65.73 74.96 69.00 57.29 38.95 59.43

二阶段检索(rerank)

模型名称 Reranking 平均
bge-reranker-base 57.78 57.78
bge-reranker-large 59.69 59.69
bce-reranker-base_v1 60.06 60.06

开源版本QAnything的大模型基于通义千问,并在大量专业问答数据集上进行微调;在千问的基础上大大加强了问答的能力。 如果需要商用请遵循千问的license,具体请参阅:通义千问

😄😄😄

RAG.assets/image-20240225141615627.png

Active Retrieval Augmented Generation

arXiv:2305.06983v2 [cs.CL] 22 Oct 2023

提出了两种前瞻性主动检索增强生成(FLARE)方法来实现主动检索增强生成框架。第一种方法在生成答案时使用检索鼓励指令,促使LMs生成检索查询,称为FLAREinstruct。第二种方法直接使用LMs的生成作为搜索查询,称为FLAREdirect,它迭代地生成下一句话以洞察未来的主题,如果存在不确定的标记,则检索相关文档以重新生成下一句话。

表达检索信息需求的一个直接方法是在需要额外信息时生成“[Search(query)]”,例如,“加纳国旗的颜色具有以下含义。红色代表[Search(加纳国旗红色含义)]烈士的鲜血,…”

直接FLARE

由于我们无法对黑盒LMs进行微调,我们发现通过检索指令生成的查询可能不可靠。因此,我们提出了一种更直接的前瞻性主动检索方法,使用下一句话来决定何时以及检索什么信息。

在步骤t时,我们首先生成一个临时的下一句话ˆst = LM([x, y<t])而不基于检索到的文档。然后我们决定是否触发检索并基于ˆst制定查询。如果LM对ˆst有信心,我们接受它而不检索额外信息;如果没有信心,我们使用ˆst来制定搜索查询qt来检索相关文档,然后重新生成下一句话。我们选择句子作为迭代的基础,因为它们作为语义单元既不像短语那样短,也不像段落那样长。然而,我们的方法也可以使用短语或段落作为基础。由于LMs往往具有良好的校准,低概率/置信度通常表明缺乏知识(Jiang et al., 2021; Kadavath et al., 2022; Varshney et al., 2022),我们主动触发检索,如果ˆst中的任何标记的概率低于阈值θ ∈ [0, 1]。θ = 0意味着从不触发检索,而θ = 1则在每个句子都触发检索。

RAG.assets/image-20240211232126525.png

检索的一种方法是直接使用下一句话ˆst作为查询qt。这种方法存在延续错误的风险。例如,如果LM生成了“乔·拜登就读于宾夕法尼亚大学”而不是正确的事实“他就读于特拉华大学”,使用这个错误的句子作为查询可能会检索到误导性的信息。我们提出了两种简单的方法来克服这个问题,

RAG.assets/image-20240211232053676.png

遮蔽句子作为隐式查询。第一种方法是遮蔽ˆst中概率低于阈值β ∈ [0, 1]的低置信度标记,其中β值越高,遮蔽越激进。这消除了句子中的潜在干扰,提高了检索的准确性。

生成问题作为显式查询。另一种方法是生成针对ˆst中低置信度跨度的显式问题。例如,如果LM对“宾夕法尼亚大学”不确定,可以生成一个问题,如“乔·拜登就读于哪所大学?”来帮助检索相关信息。

基于前窗口的方法

前窗口方法在每个l个标记触发一次检索,其中l代表窗口大小。

基于前句子的方法

前句子方法在每个句子触发一次检索,并使用前一个句子作为查询

问题分解方法

问题分解方法手动注释了任务特定的示例,以指导LMs在生成输出时生成分解的子问题。例如,self-ask(Press et al., 2022)这类方法,在示例中手动插入子问题。对于测试案例,检索在模型生成子问题时动态触发。上述方法可以在生成过程中检索额外信息,但它们有明显的缺点:(1)使用之前生成的标记作为查询可能无法反映LMs未来生成的意图。(2)在固定间隔检索信息可能效率低下,因为它可能发生在不适当的点。(3)问题分解方法需要任务特定的提示工程,这限制了它们在新任务中的通用性。

大型语言模型的检索增强生成研究综述

Retrieval-Augmented Generation for Large Language Models: A Survey

[2312.10997] Retrieval-Augmented Generation for Large Language Models: A Survey (arxiv.org)

RAG四个阶段

Technology tree of RAG research development featuring representative works

RAG.assets/image-20240225141355934.png

  • 在其2017年的起源时,与Transformer架构的出现相一致,主要重点是通过预训练模型(PTM)吸收额外的知识来增强语言模型
  • 随后chatGPT的到来标志着RAG轨迹的一个重要时刻,将LLMs推向了前沿。大部分RAG工作集中在推理上,少数致力于微调过程
  • 随着GPT-4的引入,RAG技术领域经历了重大变革。重点发展成为一种混合方法,结合了RAG和微调的优势

定义

典型的RAG应用工作流程

RAG.assets/image-20240225143703627.png

  • 用户询问ChatGPT关于一个最近引起公众广泛讨论的高调事件(例如,OpenAI首席执行官的突然解雇和复职)
  • 作为最著名和广泛使用的LLM,ChatGPT受限于其预训练数据,缺乏对最近事件的了解。RAG通过从外部知识库检索最新的文档摘要来解决这一差距。在这种情况下,它获取了与查询相关的新闻文章。
  • “检索什么”、“何时检索”以及“如何使用检索到的信息”

RAG工作流程包括三个关键步骤。

  • 将语料库分割成离散的块,然后使用编码模型构建向量索引
  • RAG根据查询和索引块的向量相似性识别并检索块。
  • 模型根据从检索到的块中获得的上下文信息综合响应。

RAG框架(RAG Framework)

三类RAG:

Naive RAG

Naive RAG遵循传统的包括索引、检索和生成的过程。它也被称为“检索-阅读”框架

  • 索引:是数据准备中的关键初始步骤,它在离线时进行。它从数据索引开始,原始数据清理和提取,各种文件格式如PDF、HTML、Word和Markdown被转换为标准化的纯文本。为了适应语言模型的上下文限制,这些文本随后被分割成更小、更易管理的块,这个过程被称为块化。这些块随后通过嵌入模型转换为向量表示,该模型在推理效率和模型大小之间取得了平衡。这有助于在检索阶段进行相似性比较。最后,创建一个索引来存储这些文本块及其向量嵌入作为键值对,允许高效和可扩展的搜索能力。
  • 检索:在接收到用户查询后,系统使用与索引阶段相同的编码模型将输入转换为向量表示。然后,它计算查询向量与索引语料库中的向量化块之间的相似度分数。系统优先检索与查询最相似的前K个块。这些块随后被用作解决用户请求的扩展上下文基础。
  • 生成:提出的查询和选定的文档被合成为一个连贯的提示,大型语言模型负责制定响应。模型回答问题的方法可能因任务特定标准而有所不同,允许它要么利用其固有的参数化知识,要么将其响应限制在提供的文档所包含的信息内。在持续的对话中,任何现有的对话历史都可以集成到提示中,使模型能够有效地进行多轮对话互动。

缺陷:

低精度

低召回率

调和写作风格和语调的差异以确保输出的一致性至关重要

存在生成模型过度依赖增强信息的风险,可能导致输出仅重复检索到的内容,而没有提供新的价值或综合信息

Advanced RAG

为了解决Naive RAG的局限性而开发的

它在检索质量、索引挑战、检索过程优化等方面进行了针对性的增强。它引入了滑动窗口、细粒度分割和元数据等技术来优化索引过程,并提出了各种方法来优化检索过程

检索前处理

优化数据索引。增强数据粒度、优化索引结构、添加元数据、对齐优化和混合检索

检索

主要的关注点是通过计算查询和组块之间的相似度来识别合适的上下文

在高级RAG中,嵌入模型存在优化的潜力:

  • 微调嵌入模型:BGE
  • 动态嵌入:同一个单词在不同语境下有不同的Embedding openai的ada

检索后处理过程

将检索与查询合并作为输入

  • 重排序:
  • 简练Prompt:压缩无关语句、突出关键段落、减少整体语境长度

Modular RAG(模块化RAG)

提供了更大的通用性和灵活性,它集成了多种方法来增强功能模块

模块化的RAG范式正日益成为RAG领域的规范,允许跨多个模块的序列化流水线或端到端的训练方法。

三种比较:

RAG.assets/image-20240225152530881.png

新模块

  • Search Module:这些搜索的数据源可以包括搜索引擎、文本数据、表格数据和知识图谱
  • Memory Module:该模块利用LLM的记忆能力来指导检索。该方法涉及识别与当前输入最相似的记忆
  • Fusion:使用LLM将用户查询扩展到多个不同的视角
  • Routing:
  • Predict:它解决了检索内容中常见的冗余和噪声问题
  • Task Adapter:使RAG适应多种下游任务

新格局

优化RAG管道

RAG核心组件的分类

RAG.assets/image-20240227100057746.png

具有不同扩充方面的代表性RAG研究的技术树

RAG.assets/image-20240227100155514.png

RAG生态系统概述

RAG.assets/image-20240227100229488.png

大模型RAG问答下的实用开源文档解析工具总结及技术思考:从文档版本分析到单双栏处理

https://mp.weixin.qq.com/s/Uh862US7NYYfEdhIOqKhVg

FaaF:RAG系统评估的事实函数

FaaF: Facts as a Function for the evaluation of RAG systems

[2403.03888] FaaF: Facts as a Function for the evaluation of RAG systems (arxiv.org)

检索增强生成系统:自动数据集创建、评估和布尔代理设置

Retrieval Augmented Generation Systems: Automatic Dataset Creation, Evaluation and Boolean Agent Setup

[2403.00820] Retrieval Augmented Generation Systems: Automatic Dataset Creation, Evaluation and Boolean Agent Setup (arxiv.org)