马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
改进召回(Retrieval)和引入重排(Reranking)提升RAG架构下的LLM应用结果
原创 ully AI工程化 2023-08-24 21:08
收录于合集
#LLM应用架构3个
#领域技术13个
动手点关注
干货不迷路
如前文LLM应用架构之检索增强(RAG)的缘起与架构介绍,RAG架构很好的解决了当前大模子Prompt learning过程中context window限制等问题,整体架构简明清晰,易于实现,得到了广泛的应用,但实际落地过程中有大量的实际问题必要改进优化。
llamaindex实现下的RAG架构
以RAG召回为例,最原始的做法是通过top-k的方式从向量数据库中检索背景数据然后直接提交给LLM去生成答案,但如许存在检索出来的chunks并不一定完全和上下文相关的问题,末了导致大模子生成的结果质量不佳。
这个问题很大程度上是由于召回相关性不够或者是召回数量太少导致的,从扩大召回这个角度思考,借鉴推荐系统做法,引入粗排或重排的步调来改进结果。其基本思路就是,原有的top-k向量检索召回扩大召回数目,再引入粗排模子,这里的模子可以是策略,轻量级的小模子,或者是LLM,对召回结果联合上下文举行重排,通过如许的改进模式可以有用提升RAG的结果。
下面介绍llamaindex在这方面的一些具体思路和实现。
1)基于LLM的召回或重排
在逻辑概念上,这种方法利用 LLM 来决定哪些文档/文本块与给定查询相关。prompt由一组候选文档构成,这时LLM 的任务是选择相关的文档集,并用内部指标对其相关性举行评分。为了制止由于大文档chunk化带来的内容分裂,在建库阶段也可做了一定优化,利用summary index对大文档举行索引。
基于 LLM 的检索工作原理简图
在LLM开辟中有一个原则就是尽大概的利用大模子的能力,LLM并不但是末了作答,在关键词增强,答案一致性判断等上面都可以利用,在这里就可以利用大模子来判断生成结果最符合的候选问答。怎样做好prompt就是关键,这是llamaindex内置的prompt,可以看到,这里用到了大模子的few-shot能力:
- A list of documents is shown below. Each document has a number next to it along with a summary of the document. A question is also provided.Respond with the numbers of the documents you should consult to answer the question, in order of relevance, as wellas the relevance score. The relevance score is a number from 1–10 based on how relevant you think the document is to the question.Do not include any documents that are not relevant to the question.Example format:Document 1:<summary of document 1>Document 2:<summary of document 2>…Document 10:<summary of document 10>Question: <question>Answer:Doc: 9, Relevance: 7Doc: 3, Relevance: 4Doc: 7, Relevance: 3Let's try this now:{context_str}Question: {query_str}Answer:
复制代码 别的,这一召回过程可以执行多次,形成批次,如许可以更大范围召回相关文档,然后将每个批次从大模子得到的结果打分举行汇总,得到终极的候选文档。llama-index提供了两种形式的抽象:作为独立的检索模块(ListIndexLLMRetriever)或重排模块(LLMRerank)。
- LLM Retriever (ListIndexLLMRetriever)
该模块是通过列表索引定义的,列表索引只是将一组节点存储为一个平面列表。你可以在一组文档上建立列表索引,然后利用 LLM 检索器从索引中检索相关文档。
- from llama_index import GPTListIndexfrom llama_index.indices.list.retrievers import ListIndexLLMRetrieverindex = GPTListIndex.from_documents(documents, service_context=service_context)# high - level APIquery_str = "What did the author do during his time in college?"retriever = index.as_retriever(retriever_mode="llm")nodes = retriever.retrieve(query_str)# lower-level APIretriever = ListIndexLLMRetriever()response_synthesizer = ResponseSynthesizer.from_args()query_engine = RetrieverQueryEngine(retriever=retriever, response_synthesizer=response_synthesizer)response = query_engine.query(query_str)
复制代码 通过这种召回模式来取代传统的向量检索模式,这种实现是相对来讲会比较慢,得当召回文档比较少的情况,但可以省去重排阶段。
它是本方案中典范的一种实现,被定义为 NodePostprocessor 抽象的一部分,用于初始检索通报后的第二阶段处理。后处理器可单独利用,也可作为 RetrieverQueryEngine 调用的一部分利用。在下面的示例中,我们展示了怎样在通过向量索引举行初始检索调用后,将后处理器作为独立模块利用。
- from llama_index.indices.query.schema import QueryBundlequery_bundle = QueryBundle(query_str)# configure retrieverretriever = VectorIndexRetriever(index=index,similarity_top_k=vector_top_k,)retrieved_nodes = retriever.retrieve(query_bundle)# configure rerankerreranker = LLMRerank(choice_batch_size=5, top_n=reranker_top_n, service_context=service_context)retrieved_nodes = reranker.postprocess_nodes(retrieved_nodes, query_bundle)
复制代码 必要说明的是,基于LLM召回或重排存在一些缺陷,首先就是慢,第二就是增加了LLM的调用成本,第三,由于打分是分批举行的,存在着无法全局对齐的问题。
对比演示
下面基于top-k和基于LLM召回做一个例子,基于the Great Gatsby(了不起的盖茨比) 和 the 2021 Lyft SEC 10-k两份数据,只对比召回阶段的结果。
1. The Great Gatsby
在这例子中,将《the Great Gatsby》作为文档对象载入,并在其上建立一个向量索引(块大小设置为 512)。
- # LLM Predictor (gpt-3.5-turbo) + service contextllm_predictor = LLMPredictor(llm=ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo"))service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor, chunk_size_limit=512)# load documentsdocuments = SimpleDirectoryReader('../../../examples/gatsby/data').load_data()index = GPTVectorStoreIndex.from_documents(documents, service_context=service_context)
复制代码 然后,我们定义了一个 get_retrieved_nodes 函数,该函数既可以只对索引举行基于向量检索,也可以举行基于向量检索 + 重排序。
- def get_retrieved_nodes( query_str, vector_top_k=10, reranker_top_n=3, with_reranker=False): query_bundle = QueryBundle(query_str) # configure retriever retriever = VectorIndexRetriever( index=index, similarity_top_k=vector_top_k, ) retrieved_nodes = retriever.retrieve(query_bundle) if with_reranker: # configure reranker reranker = LLMRerank(choice_batch_size=5, top_n=reranker_top_n, service_context=service_context) retrieved_nodes = reranker.postprocess_nodes(retrieved_nodes, query_bundle) return retrieved_nodes
复制代码 然后我们提出一些问题。对于基于原来的向量检索,我们设定 k=3。对于两阶段检索,我们设定向量检索的 k=10 和基于 LLM 的重排序的 n=3。
- 测试问题: ”Who was driving the car that hit Myrtle?”
对于那些不熟悉《the Great Gatsby》的人来说,叙述者厥后从Gatsby那里发现,开车的实在是Daisy,但Gatsby替她背了黑锅。
检索到的top上下文如下图所示。我们可以看到,在基于嵌入的检索中,前两个文本包罗了车祸的语义,但没有提供关于谁是真正责任人的细节。只有第三个文本包罗正确答案。
利用top-k向量检索召回的上下文 (baseline)
相反,两阶段方法只返回一个相关的上下文,并且它包罗正确的答案。
利用向量检索+重排得到的上下文
2. 2021 Lyft SEC 10-K
测试就 2021 年 Lyft SEC 10-K 提出一些问题,特别是关于 COVID-19 的影响和应对措施。Lyft SEC 10-K 长达 238 页,按 ctrl-f 查找 "COVID-19 "可找到 127 条匹配信息。
利用了与上述 Gatsby 示例雷同的设置。主要区别在于,将分块大小设置为 128 而非 512,将 k=5 设置为向量检索基线,将 k=40 和 ranker n=5 设置为向量检索+重排序的组合利用方法。
- 测试问题 :”What initiatives are the company focusing on independently of COVID-19?”
基线的结果如上图所示。可以看到,指数 0、1、3、4 所对应的结果都是直接针对 COVID-19 而采取的措施,尽管该问题是专门针对独立于 COVID-19 大盛行的公司措施。
利用top-k向量检索召回的上下文 (baseline)
在方法 2 中,将前 k 项扩大到 40 项,然后利用 LLM 筛选前 5 项,从而得到更多相关结果。独立公司的办法包罗 “expansion of Light Vehicles” (1), “incremental investments in brand/marketing” (2), international expansion (3), and accounting for misc. risks such as natural disasters and operational risks in terms of financial performance (4)。
利用向量检索+重排得到的上下文
可以看到,基于LLM的召回或重排相对于传统Top-k直接向量检索在结果上有比较大的提升,但同样存在一些问题,必要联合实际的场景来综合选择。
2)基于相对轻量的模子和算法
这种做法是LLM模式的一种简化,接纳BM25, Cohere Rerank等方法对召回结果举行粗排,在结果和性能上取得折中。
利用例子:
- cohere_rerank = CohereRerank(api_key=os.environ["COHERE_API_KEY"], top_n=top_k)reranking_query_engine = index.as_query_engine( similarity_top_k=top_k, node_postprocessors=[cohere_rerank],)
复制代码
3)基于规则
在粗排阶段,也可以借鉴推荐系统里的做法一样,在进入精排模子前,粗排模子也可以用一些规则更换,有时候高质量的规则会有更好的表现。在llamaindex中可以设置后处理器( Postprocessor)。这些后处理器可以在从索引返回结果后修改查询结果。
好比增加一个优先选择近来的文档的策略。可将其定义为FixedRecencyPostprocessor。
- recency_postprocessor = FixedRecencyPostprocessor(service_context=service_context, top_k=1)recency_query_engine = index.as_query_engine( similarity_top_k=top_k, node_postprocessors=[recency_postprocessor],)
复制代码 这里面FixedRecencyPostprocessor可以通过过滤chunknode上metadata里的Date字段来举行排序。
- > Source (Doc id: 24ec05e1-cb35-492e-8741-fdfe2c582e43): date: 2017-01-28 00:00:00
- Under the category:THE WORLDPOST:World Leaders React To The Reality ...
- > Source (Doc id: 098c2482-ce52-4e31-aa1c-825a385b56a1): date: 2015-01-18 00:00:00
- Under the category:POLITICS:The Issue That's Looming Over The Final ...
复制代码 值得说明的是,善于利用metadata对于RAG架构中许多问题都有奇效,后面文章中将介绍Metadata的一些利用案例。
不止云云,在llamaindex中,这些postprocessor是可以联合利用的,形成一些链式规则,好比将刚才的cohere_rerank和recency_postprocessor合并利用,进一步精细化排序。
- query_engine = index.as_query_engine( similarity_top_k=top_k, node_postprocessors=[cohere_rerank, recency_postprocessor],)
复制代码 总结
RAG架构来自于实际问题,而许多问题都是相似的,在结果优化层面,我们可以借鉴一些推荐系统等传统AI系统的优化履历,将其迁徙过来,这对于改进RAG结果有很大的帮助,在后面的文章里,还将继续介绍具体场景的一些利用问题,接待关注。
注:本文部分内容参考于llamaindex和qdrant官方博客。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |