在构建检索增强生成(RAG)Pipeline时,一个关键组件是Retriever。我们有多种embedding模型可供选择,包括OpenAI、CohereAI和开源sentence transformers。此外,CohereAI和sentence transformers还提供了几个重排序器。
但是,有了所有这些选项,我们如何确定最佳组合以获得一流的检索性能?我们如何知道哪种embedding模型最适合我们的数据?或者哪种重新排序对我们的结果提升最大?
在这篇博客文章中,我们将使用LlamaIndex的Retrieval Evaluation模块来快速确定embedding和重排序模型的最佳组合。让我们一起来了解一下吧!
让我们首先了解Retrieval Evaluation中可用的评估指标!
为了衡量我们的检索系统的有效性,我们选择被广泛接受的两个指标:Hit Rate和 Mean Reciprocal Rank (MRR)。让我们深入研究这些指标,以了解它们的重要性以及它们是如何运作的。
命中率(Hit Rate):
命中率计算在前k个检索到的文档中找到正确答案的查询的百分比。简单地说,这是关于我们的系统在前几次猜测中正确的频率。
平均倒数排名(MRR):
对于每个查询,MRR通过查看排名最高的相关文档的排名来评估系统的准确性。具体来说,它是所有查询中这些排名的倒数的平均值。因此,如果第一个相关文档是最高结果,则倒数为1;如果是第二个,则倒数为1/2,依此类推。
既然我们已经确定了评估指标,现在是时候开始实验了。
!pip install llama-index sentence-transformers cohere anthropic voyageai protobuf pypdf
openai_api_key = 'YOUR OPENAI API KEY'
cohere_api_key = 'YOUR COHEREAI API KEY'
anthropic_api_key = 'YOUR ANTHROPIC API KEY'
openai.api_key = openai_api_key
!wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "llama2.pdf"
让我们加载数据。我们前36页来进行实验,不包括目录、参考文献和附录。
然后将这些数据转换为节点进行解析,这些节点表示我们想要检索的数据块。我们设置chunk_size为512。
documents = SimpleDirectoryReader(input_files=["llama2.pdf"]).load_data()
node_parser = SimpleNodeParser.from_defaults(chunk_size=512)
nodes = node_parser.get_nodes_from_documents(documents)
为了评估,我们创建了一个问题上下文对数据集,该数据集包括一系列问题及其相应的上下文。为了消除embedding(OpenAI/CohereAI)和重排序(CohereAI)评估的偏差,我们使用Anthropic LLM生成问题上下文对。
让我们初始化一个Prompt模板来生成问题上下文对。
# Prompt to generate questions
qa_generate_prompt_tmpl = """\
Context information is below.
---------------------
{context_str}
---------------------
Given the context information and not prior knowledge.
generate only questions based on the below query.
You are a Professor. Your task is to setup \
{num_questions_per_chunk} questions for an upcoming \
quiz/examination. The questions should be diverse in nature \
across the document. The questions should not contain options, not start with Q1/ Q2. \
Restrict the questions to the context information provided.\
"""
llm = Anthropic(api_key=anthropic_api_key)
qa_dataset = generate_question_context_pairs(
nodes, llm=llm, num_questions_per_chunk=2
)
过滤句子的函数,例如——以下是基于所提供上下文的两个问题
# function to clean the dataset
def filter_qa_dataset(qa_dataset):
"""
Filters out queries from the qa_dataset that contain certain phrases and the corresponding
entries in the relevant_docs, and creates a new EmbeddingQAFinetuneDataset object with
the filtered data.
:param qa_dataset: An object that has 'queries', 'corpus', and 'relevant_docs' attributes.
:return: An EmbeddingQAFinetuneDataset object with the filtered queries, corpus and relevant_docs.
"""
# Extract keys from queries and relevant_docs that need to be removed
queries_relevant_docs_keys_to_remove = {
k for k, v in qa_dataset.queries.items()
if 'Here are 2' in v or 'Here are two' in v
}
# Filter queries and relevant_docs using dictionary comprehensions
filtered_queries = {
k: v for k, v in qa_dataset.queries.items()
if k not in queries_relevant_docs_keys_to_remove
}
filtered_relevant_docs = {
k: v for k, v in qa_dataset.relevant_docs.items()
if k not in queries_relevant_docs_keys_to_remove
}
# Create a new instance of EmbeddingQAFinetuneDataset with the filtered data
return EmbeddingQAFinetuneDataset(
queries=filtered_queries,
corpus=qa_dataset.corpus,
relevant_docs=filtered_relevant_docs
)
# filter out pairs with phrases `Here are 2 questions based on provided context`
qa_dataset = filter_qa_dataset(qa_dataset)
为了寻找最优检索器,我们采用embedding模型和重排序器的组合。最初,我们建立了一个基本的VectorIndexRetriever。在检索节点后,我们引入一个重排序器来进一步细化结果。值得注意的是,对于这个特定的实验,我们将similarity_top_k设置为10,并用reranker选择了前5名。但是,您可以根据具体实验的需要随意调整此参数。我们在这里展示了OpenAIEmbedding的代码,其他embedding代码请参阅笔记本(https://colab.research.google.com/drive/1TxDVA__uimVPOJiMEQgP5fwHiqgKqm4-?usp=sharing)。
embed_model = OpenAIEmbedding()
service_context = ServiceContext.from_defaults(llm=None, embed_model = embed_model)
vector_index = VectorStoreIndex(nodes, service_context=service_context)
vector_retriever = VectorIndexRetriever(index=vector_index, similarity_top_k = 10)
class CustomRetriever(BaseRetriever):
"" "Custom retriever that performs both Vector search and Knowledge Graph search"""
def __init__(
self,
vector_retriever: VectorIndexRetriever,
) -> None:
"""Init params."""
self._vector_retriever = vector_retriever
def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
"""Retrieve nodes given query."""
retrieved_nodes = self._vector_retriever.retrieve(query_bundle)
if reranker != 'None':
retrieved_nodes = reranker.postprocess_nodes(retrieved_nodes, query_bundle)
else:
retrieved_nodes = retrieved_nodes[:5]
return retrieved_nodes
async def _aretrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
"""Asynchronously retrieve nodes given query.
Implemented by the user.
"""
return self._retrieve(query_bundle)
async def aretrieve(self, str_or_query_bundle: QueryType) -> List[NodeWithScore]:
if isinstance(str_or_query_bundle, str):
str_or_query_bundle = QueryBundle(str_or_query_bundle)
return await self._aretrieve(str_or_query_bundle)
custom_retriever = CustomRetriever(vector_retriever)
为了评估我们的检索器,我们计算了平均倒数排名(MRR)和命中率指标:
retriever_evaluator = RetrieverEvaluator.from_metric_names(
["mrr", "hit_rate"], retriever=custom_retriever
)
eval_results = await retriever_evaluator.aevaluate_dataset(qa_dataset)
我们对各种嵌入模型和重新排序进行了测试。以下是我们考虑的模型:
重排序模型:
下表显示了基于命中率和平均倒数排名(MRR)指标的评估结果:
embedding性能:
重排序的影响:
重排序的必要性:
总体优势:
总之,为了在命中率和MRR方面实现峰值性能,OpenAI或JinaAI Base嵌入与CohereRerank/bge重ranker大型重ranker的组合脱颖而出。
在这篇博客文章中,我们展示了如何使用各种embedding和重排序来评估和增强检索器的性能。以下是我们的最终结论。
[1] https://blog.llamaindex.ai/boosting-rag-picking-the-best-embedding-reranker-models-42d079022e83
[2] https://colab.research.google.com/drive/1TxDVA__uimVPOJiMEQgP5fwHiqgKqm4-?usp=sharing