一、為什么要重排序?

       重新排序是檢索過程中的一個步驟,根據(jù)某些標(biāo)準(zhǔn)對最初檢索到的結(jié)果進(jìn)行進(jìn)一步排序、細(xì)化或重新排序,以提高其相關(guān)性或準(zhǔn)確性。

       這里有一個例子:想象一下,你正在搜索有關(guān)企鵝的信息。當(dāng)你輸入搜索查詢時,系統(tǒng)會很快彈出幾篇關(guān)于不同類型企鵝的文章。這些結(jié)果是基于通用相關(guān)性檢索獲得的。

       現(xiàn)在,假設(shè)前幾篇文章是關(guān)于“南極企鵝”的,但你真正想要的是關(guān)于“動物園棲息地的企鵝”的信息,那么就需要對這幾篇文章進(jìn)行重新排序了,比如使用用戶行為、特定關(guān)鍵字或更復(fù)雜的算法來進(jìn)行該操作。這可能會使有關(guān)動物園棲息地的文章在列表中排名更高,降低有關(guān)南極洲的文章的排名。這一步驟確保最相關(guān)或最有用的信息出現(xiàn)在頂部,使您更容易找到您要查找的內(nèi)容。

       本質(zhì)上,重新排序微調(diào)了最初檢索到的結(jié)果,基于特定標(biāo)準(zhǔn)或用戶偏好提供了一組更具針對性和相關(guān)性的文檔或信息。

二、基于嵌入的檢索有什么問題?

使用基于嵌入的檢索有許多優(yōu)點(diǎn):

  1. 使用向量點(diǎn)積快速計(jì)算,并且在查詢推理期間不需要調(diào)用任何模型;
  2. 嵌入可以對文檔和查詢進(jìn)行語義編碼,并可以根據(jù)查詢檢索出高度相關(guān)的文檔。

? ? ? ?然而,盡管有這些優(yōu)點(diǎn),基于嵌入的檢索有時準(zhǔn)確性不高,并返回與查詢相關(guān)的無關(guān)上下文,這會大大降低RAG系統(tǒng)的整體質(zhì)量。

? ? ? ?針對上述問題,可以借鑒推薦系統(tǒng)召回和排序兩個階段來解決。第一階段基于嵌入召回出相關(guān)性最高的top-k個文檔,該階段注重召回而非準(zhǔn)確性。第二階段是對召回的top-k個文檔進(jìn)行重新排序。

三、代碼實(shí)現(xiàn)

模型zephyr-7b-alpha

嵌入模型:hkunlp/instructor-large

3.1 加載數(shù)據(jù)

? ? ? ?LlamaIndex通過數(shù)據(jù)連接器Reader來加載數(shù)據(jù),數(shù)據(jù)連接器可以加載不同數(shù)據(jù)源的數(shù)據(jù),并將數(shù)據(jù)格式化為Document對象,Document對象會存儲文本和對應(yīng)的元數(shù)據(jù)(未來會存儲圖像和音頻)。

PDFReader = download_loader("PDFReader")
loader = PDFReader()
docs = loader.load_data(file=Path("QLoRa.pdf"))

3.2 分塊

? ? ?我們將文本分割成512大小的分塊來創(chuàng)建節(jié)點(diǎn)Node。Node是LlamaIndex中的原子數(shù)據(jù)單元,表示源文檔的“塊”。節(jié)點(diǎn)包含元數(shù)據(jù)以及與其他節(jié)點(diǎn)的關(guān)系信息。

node_parser = SimpleNodeParser.from_defaults(chunk_size=512)
nodes = node_parser.get_nodes_from_documents(docs)

3.3 開源LLM和嵌入

       我們將使用開源大模型zephyr-7b-alpha,并對其進(jìn)行量化,量化后的模型可以運(yùn)行在Colab免費(fèi)的T4 GPU上。

? ? ? ?在本例中,我們將使用hkunlp/instructor-large指令微調(diào)的文本嵌入模型,它可以通過簡單地提供任務(wù)指令來生成針對任何任務(wù)(例如,分類、檢索、聚類、文本評估等)定制的文本嵌入,而無需任何微調(diào)。在MTEB排行榜(https://huggingface.co/spaces/mteb/leaderboard)上排名第14!

from google.colab import userdata

# huggingface and cohere api token
hf_token = userdata.get('hf_token')

quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
)

def messages_to_prompt(messages):
prompt = ""
for message in messages:
if message.role == 'system':
prompt += f"<|system|>\n{message.content}</s>\n"
elif message.role == 'user':
prompt += f"<|user|>\n{message.content}</s>\n"
elif message.role == 'assistant':
prompt += f"<|assistant|>\n{message.content}</s>\n"

# ensure we start with a system prompt, insert blank if needed
if not prompt.startswith("<|system|>\n"):
prompt = "<|system|>\n</s>\n" + prompt

# add final assistant prompt
prompt = prompt + "<|assistant|>\n"

return prompt

# LLM
llm = HuggingFaceLLM(
model_name="HuggingFaceH4/zephyr-7b-alpha",
tokenizer_name="HuggingFaceH4/zephyr-7b-alpha",
query_wrapper_prompt=PromptTemplate("<|system|>\n</s>\n<|user|>\n{query_str}</s>\n<|assistant|>\n"),
context_window=3900,
max_new_tokens=256,
model_kwargs={"quantization_config": quantization_config},
# tokenizer_kwargs={},
generate_kwargs={"temperature": 0.7, "top_k": 50, "top_p": 0.95},
messages_to_prompt=messages_to_prompt,
device_map="auto",
)

# Embedding
embed_model = HuggingFaceInstructEmbeddings(
model_name="hkunlp/instructor-large", model_kwargs={"device": DEVICE}
)

3.4 配置索引和檢索器

      首先,我們將設(shè)置ServiceContext對象,并使用它來構(gòu)造索引和查詢。

索引是一種由Document對象組成的數(shù)據(jù)結(jié)構(gòu),大模型可以使用該索引進(jìn)行查詢相關(guān)上下文。

? ? ? ?VectorStoreIndex是最常見和最易于使用的索引,支持在大規(guī)模數(shù)據(jù)語料庫上進(jìn)行檢索,包括獲取“top-k”個最相似的節(jié)點(diǎn),并將它們傳遞到“響應(yīng)合成”模塊。

# ServiceContext
service_context = ServiceContext.from_defaults(llm=llm,
embed_model=embed_model
)

# index
vector_index = VectorStoreIndex(
nodes, service_context=service_context
)

# configure retriever
retriever = VectorIndexRetriever(
index=vector_index,
similarity_top_k=10,
service_context=service_context)

3.5 初始化重排序器

我們會評估以下3個重新排序器的性能:

# Define all embeddings and rerankers
RERANKERS = {
"WithoutReranker": "None",
"CohereRerank": CohereRerank(api_key=cohere_api_key, top_n=5),
"bge-reranker-base": SentenceTransformerRerank(model="BAAI/bge-reranker-base", top_n=5),
"bge-reranker-large": SentenceTransformerRerank(model="BAAI/bge-reranker-large", top_n=5)
}

3.6 檢索比較

       檢索器定義了在給定查詢時如何有效地從索引中檢索相關(guān)上下文。

       節(jié)點(diǎn)后處理器:節(jié)點(diǎn)后處理器接收一組檢索到的節(jié)點(diǎn),并對它們進(jìn)行轉(zhuǎn)換、過濾或重新排序操作。節(jié)點(diǎn)后處理器通常在節(jié)點(diǎn)檢索步驟之后和響應(yīng)之前應(yīng)用于查詢引擎中。

? ? ? ?讓我們創(chuàng)建一些助手函數(shù)來執(zhí)行我們的任務(wù):

# helper functions

def get_retrieved_nodes(
query_str, reranker
):
query_bundle = QueryBundle(query_str)

retrieved_nodes = retriever.retrieve(query_bundle)

if reranker != "None":
retrieved_nodes = reranker.postprocess_nodes(retrieved_nodes, query_bundle)
else:
retrieved_nodes

return retrieved_nodes

def pretty_print(df):
return display(HTML(df.to_html().replace("\\n", "<br>")))

def visualize_retrieved_nodes(nodes) -> None:
result_dicts = []
for node in nodes:
node = deepcopy(node)
node.node.metadata = None
node_text = node.node.get_text()
node_text = node_text.replace("\n", " ")

result_dict = {"Score": node.score, "Text": node_text}
result_dicts.append(result_dict)

pretty_print(pd.DataFrame(result_dicts))

讓我們可視化查詢的結(jié)果:

query_str = "What are the top features of QLoRA?"

# Loop over rerankers
for rerank_name, reranker in RERANKERS.items():
print(f"Running Evaluation for Reranker: {rerank_name}")

query_bundle = QueryBundle(query_str)

retrieved_nodes = retriever.retrieve(query_bundle)

if reranker != "None":
retrieved_nodes = reranker.postprocess_nodes(retrieved_nodes, query_bundle)
else:
retrieved_nodes

print(f"Visualize Retrieved Nodes for Reranker: {rerank_name}")
visualize_retrieved_nodes(retrieved_nodes)

沒有重新排序–頂部節(jié)點(diǎn)的相似性得分為0.87,這是baseline分?jǐn)?shù)。

CohereRebank——頂部節(jié)點(diǎn)的相似性得分為0.988。

bge reranker base——頂部節(jié)點(diǎn)的相似性得分為0.72。

四、性能評價(jià)

       現(xiàn)在,我們將使用RetrieverEvaluator來評估我們的Retriever的質(zhì)量。

       我們定義了各種評估指標(biāo),如命中率hit-rateMRR,這些指標(biāo)根據(jù)每個特定問題的ground-truth文本來評估檢索結(jié)果的質(zhì)量。為了簡化評估數(shù)據(jù)集的構(gòu)造,我們采用了合成數(shù)據(jù)生成方法。

       MRR( Mean Reciprocal Rank)表示平均倒數(shù)排名,是一個國際上通用的對搜索算法進(jìn)行評價(jià)的機(jī)制,并且只評估排名前10個的排名感知相關(guān)性得分。第一個結(jié)果匹配,分?jǐn)?shù)為1,第二個匹配分?jǐn)?shù)為0.5,第n個匹配分?jǐn)?shù)為1/n,如果沒有匹配的句子分?jǐn)?shù)為0。最終的分?jǐn)?shù)為所有得分之和。

        hit-rate衡量檢索到的結(jié)果中至少包含一個與基本事實(shí)相關(guān)的項(xiàng)目的查詢的比例或百分比。例如,想象一個搜索引擎返回一個文檔列表來響應(yīng)用戶的查詢。這里的基本事實(shí)是指該查詢的已知相關(guān)文檔,通常由人類判斷或標(biāo)記數(shù)據(jù)確定。hit-rate計(jì)算搜索結(jié)果至少包含一個相關(guān)文檔的頻率。

4.1 構(gòu)建(查詢、上下文)對的評估數(shù)據(jù)集

      我們可以手動構(gòu)建問題+Node id的檢索評估數(shù)據(jù)集。Llamaindex通過generate_question_context_pairs函數(shù)在現(xiàn)有文本語料庫上提供合成數(shù)據(jù)集生成。使用大模型根據(jù)每個上下文塊自動生成問題,可以參閱:https://docs.llamaindex.ai/en/stable/module_guides/evaluating/usage_pattern_retrieval.html。

? ? ? ?這里,我們使用Zephr-7B在現(xiàn)有的文本語料庫上構(gòu)建一個簡單的評估數(shù)據(jù)集,返回的結(jié)果是一個EmbeddingQAFinetuneDataset對象(包含queries、relevant_docs和corpus)。

# 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.\
"""

# Evaluator

qa_dataset = generate_question_context_pairs(
nodes, llm=llm, num_questions_per_chunk=2, qa_generate_prompt_tmpl=qa_generate_prompt_tmpl
)
# helper function for displaying results
def display_results(reranker_name, eval_results):
"""Display results from evaluate."""

metric_dicts = []
for eval_result in eval_results:
metric_dict = eval_result.metric_vals_dict
metric_dicts.append(metric_dict)

full_df = pd.DataFrame(metric_dicts)

hit_rate = full_df["hit_rate"].mean()
mrr = full_df["mrr"].mean()

metric_df = pd.DataFrame({"Reranker": [reranker_name], "hit_rate": [hit_rate], "mrr": [mrr]})

return metric_df

?Llamaindex提供了一個函數(shù),用于在批處理模式下對數(shù)據(jù)集運(yùn)行RetrieverEvaluator。

query_str = "What are the top features of QLoRA?"

results_df = pd.DataFrame()
# Loop over rerankers
for rerank_name, reranker in RERANKERS.items():
print(f"Running Evaluation for Reranker: {rerank_name}")

query_bundle = QueryBundle(query_str)

retrieved_nodes = retriever.retrieve(query_bundle)

if reranker != "None":
retrieved_nodes = reranker.postprocess_nodes(retrieved_nodes, query_bundle)
else:
retrieved_nodes

retriever_evaluator = RetrieverEvaluator.from_metric_names(
["mrr", "hit_rate"], retriever=retriever
)

eval_results = await retriever_evaluator.aevaluate_dataset(qa_dataset)

current_df = display_results(rerank_name, eval_results)
results_df = pd.concat([results_df, current_df], ignore_index=True)

4.2 結(jié)果

WithoutReranker:為每個嵌入提供了baseline;

CohereRerank:是SOTA結(jié)果;

bge-reranker-base:結(jié)果比CohereRerank差,也許是因?yàn)樵撃P蛯@種重新排序無效;

bge-reranker-large:結(jié)果比bge-reranker-base差,也許是因?yàn)樵?a href="http://cnzze.cn/wiki/what-is-a-large-model-understand-the-basic-concepts-of-ai/">模型對這種重新排序無效。

     結(jié)果表明重新排序在優(yōu)化檢索過程中的重要性,尤其是CohereRebank模型。

五、結(jié)論

      為搜索選擇適當(dāng)?shù)那度胫陵P(guān)重要;即使是最有效的重新排序也無法彌補(bǔ)較差的基本搜索結(jié)果(比如bge-rerankers)。

? ? ? 最大限度地提高檢索器的性能取決于發(fā)現(xiàn)嵌入和重新排序的最佳組合。這仍然是一個活躍的研究領(lǐng)域,來確定最有效的組合。

本文章轉(zhuǎn)載微信公眾號@ArronAI

上一篇:

開源| 伯克利AI分布式框架Ray,兼容TensorFlow、PyTorch與MXNet

下一篇:

Open-R1 技術(shù)解密:HuggingFace 如何完整復(fù)現(xiàn) DeepSeek 推理模型
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動選型,提升決策效率

查看全部API→
??

熱門場景實(shí)測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個渠道
一鍵對比試用API 限時免費(fèi)

#AI深度推理大模型API

對比大模型API的邏輯推理準(zhǔn)確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費(fèi)