提示工程
在进行提示工程时(如思维方式、多模态、自我完善、查询分解等),我们可以做的事情太多了,因此我们将尝试一些有趣的想法。我们将允许语言学习模型(LLM)忽略任何不相关的内容。这里的目的是展示我们如何迅速从提示工程过渡到评估报告。
# 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)
prompt-ignore-contexts
retrieval score: 0.7288135593220338
quality score: 3.519774011299435
看来这种特定的提示工程努力并没有帮助提高我们系统的质量。如我们之前提到的,我们可以用许多其他方式来进行提示工程,我们鼓励您进行更多探索。重要的是,我们有一种清晰简单的方法来评估我们想要试验的任何东西。然而,我们经验性地发现,改善我们的检索系统和数据飞轮(我们在其中修正我们的文档本身)对我们系统的整体质量有更大的影响。
SYSTEM_CONTENT = "提供上下文回答问题 ,且简明扼要。"
词汇搜索
我们现在将传统的词汇搜索与基于向量嵌入的搜索相结合,该搜索用于在我们的查询和文档块之间寻找精确的令牌匹配。直觉是,词汇搜索可以帮助识别在语义表示可能无法捕捉的精确关键词匹配的块。特别是对于那些在我们的嵌入模型中是超出词汇(因此通过子令牌表示)的令牌。但我们基于嵌入的方法在捕捉隐含意义方面仍然非常有优势,因此我们将结合使用基于向量嵌入的搜索和词汇搜索的几个检索块。
BM25
我们来使用BM25进行词汇搜索,这是一种排名算法,奖励我们的查询和上下文之间独特的Token匹配。
import re
from rank_bm25 import BM25Okapi
# BM25 index
texts = [re.sub(r"[^a-zA-Z0-9]", " ", chunk[1]).lower().split() for chunk in chunks]
lexical_index = BM25Okapi(texts)
类似于我们用来检索相关上下文的`semantic_search`函数,我们可以实现一个搜索函数,利用我们的词汇索引来检索相关上下文。
def lexical_search(index, query, chunks, k):
query_tokens = query.lower().split() # preprocess query
scores = index.get_scores(query_tokens) # get best matching (BM) scores
indices = sorted(range(len(scores)), key=lambda i: -scores[i])[:k] # sort and get top k
lexical_context = [{
"id": chunks[i][0],
"text": chunks[i][1],
"source": chunks[i][2],
"score": scores[i]} for i in indices]
return lexical_context
# Retrieve top-k docs
k = 3
query = "What is the default batch size for map_batches?"
top_docs = lexical_search(lexical_index, query, chunks, k=k)
for item in top_docs:
print (item["source"])
print (item["text"])
print ()
配置批量大小#
默认的批量大小取决于您使用的资源类型。如果您使用的是CPU,那么默认的批量大小是4096。如果您使用的是GPU,则必须明确指定一个批量大小。
语义
在语义方面,将这个与我们现有基于向量嵌入的搜索检索到的资源进行比较,显示这两种方法虽然不同,但都检索到了相关资源。因此,我们将结合这两种方法,并将其输入到我们的LLM的上下文中进行生成。
默认情况下,batch_size为4096,使用“default”。compute — 使用Ray Tasks则为“tasks”(默认),或者使用自动扩展的actor池则为ActorPoolStrategy。 batch_format — 如果是“default”或“numpy”,批处理的格式为Dict[str, numpy.ndarray]。
词汇实验
现在,让我们通过将其添加到我们的`generate.py/QueryAgent`类中来将这一点纳入我们的检索工作流。主要的变化将是包括来自词汇搜索的额外资源:
def QueryAgent():
def __init__(use_lexical_search=True, chunks=[...], **kwargs):
# Lexical search
self.chunks = chunks
self.lexical_index = None
if use_lexical_search:
texts = [re.sub(r"[^a-zA-Z0-9]", " ", chunk[1]).lower().split() for chunk in chunks]
self.lexical_index = BM25Okapi(texts)
def __call__(lexical_search_k=1, **kwargs):
# Add lexical search results
if self.lexical_index:
lexical_context = lexical_search(
index=self.lexical_index, query=query, chunks=self.chunks, k=lexical_search_k)
# Insert after <lexical_search_k> worth of semantic results
context_results[lexical_search_k:lexical_search_k] = lexical_context
运行试验:
lexical_search_k_list = [1, 3, 5]
use_lexical_search = True
for lexical_search_k in lexical_search_k_list:
experiment_name = f"lexical-search-bm25-{lexical_search_k}"
experiment_names.append(experiment_name)
run_experiment(
experiment_name=experiment_name,
use_lexical_search=use_lexical_search,
lexical_search_k=lexical_search_k,
**kwargs)
看来添加词汇搜索的影响并不像我们所希望的那样大,但这只是我们探索的词汇搜索的一个方面(关键词匹配),还有许多其他有用的功能,如过滤、计数等。探索如何将词汇搜索结果与语义搜索结果结合起来也是值得的。
USE_LEXICAL_SEARCH = False
LEXICAL_SEARCH_K = 0