OpenAI 最近发布了新一代嵌入模型,名为 embedding v3,他们将其描述为性能最强的嵌入模型,具有更高的多语言性能。这些模型分为两类:一类较小,称为 text-embedding-3-small;另一类较大,功能更强大,称为 text-embedding-3-large。
关于这些模型的设计和训练方法,披露的信息很少。与之前发布的嵌入模型(2022 年 12 月发布的 ada-002 模型类)一样,OpenAI 再次选择了闭源方法,即只能通过付费 API 访问模型。
但是,模型的性能是否好到值得付费呢?
本文的目的是通过实证比较这些新模型与开源模型的性能。我们将以数据检索工作流程为基础,根据用户查询,找到语料库中最相关的文档。
我们的语料库将是《欧洲人工智能法案》,目前正处于最后的验证阶段。该语料库除了是全球首个人工智能法律框架外,还有一个有趣的特点,那就是它有 24 种语言版本。这使得比较不同语系数据检索的准确性成为可能。
本文将介绍以下两个主要步骤:
生成自定义 Q/A 数据集
首先,让我们在自定义数据上生成一个问答(Q/A)数据集,用于评估不同嵌入模型的性能。生成自定义 Q/A 数据集有两个好处。首先,它可以确保数据集没有参与嵌入模型的训练,从而避免出现偏差,而在 MTEB 等参考基准上可能会出现这种情况。其次,它允许根据特定的数据语料库进行评估,例如在检索增强应用(RAG)的情况下,这可能是相关的。
我们将遵循 Llama Index 在其文档中建议的简单流程。首先将语料库分割成一组块。然后,通过大语言模型(LLM)为每个语块生成一组合成问题,使答案位于相应的语块中。整个过程如下图所示:
使用 LLM 数据框架(如 Llama Index)可以直接实施这一策略。语料库的加载和文本的分割可以通过高级函数方便地完成,如下代码所示。
from llama_index.readers.web import SimpleWebPageReader
from llama_index.core.node_parser import SentenceSplitter
language = "EN"
url_doc = "https://eur-lex.europa.eu/legal-content/"+language+"/TXT/HTML/?uri=CELEX:52021PC0206"
documents = SimpleWebPageReader(html_to_text=True).load_data([url_doc])
parser = SentenceSplitter(chunk_size=1000)
nodes = parser.get_nodes_from_documents(documents, show_progress=True)
在本示例中,语料库是欧盟人工智能法的英文版,使用此官方 URL 直接从网上获取。我们使用的是 2021 年 4 月的草案版本,因为最终版本尚未适用于所有欧洲语言。在该版本中,可以用欧盟其他 23 种官方语言中的任何一种替换 URL 中的英语,以检索不同语言的文本(BG 表示保加利亚语、ES 表示西班牙语、CS 表示捷克语等)。
我们使用 SentenceSplitter 对象将文档分割成 1000 个词块。对于英语而言,这将产生约 100 个语块。
然后将每个块作为以下提示的上下文提供:
prompts={}
prompts["EN"] = """\"EN"] = """\
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 Teacher/ 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. Restrict the questions to the context information provided."
"""
该提示的目的是生成与文档分块相关的问题,就像老师在准备即将到来的测验一样。我们将参数 "num_questions_per_chunk "设为两个,以此来传递每个文档块要生成的问题数量。然后,可以调用 Llama 索引库中的 generate_qa_embedding_pairs 生成问题:
from llama_index.llms import OpenAI
from llama_index.legacy.finetuning import generate_qa_embedding_pairs
qa_dataset = generate_qa_embedding_pairs(
llm=OpenAI(model="gpt-3.5-turbo-0125",additional_kwargs={'seed':42}),
nodes=nodes,
qa_generate_prompt_tmpl = prompts[language],
num_questions_per_chunk=2
)
我们依靠 OpenAI 的 GPT-3.5-turbo-0125 模式来完成这项任务,根据 OpenAI 的说法,该模式是该系列的旗舰模式,支持 16K 上下文窗口,并针对对话进行了优化 。
生成的对象 "qa_dataset "包含问题和答案(块)对。以生成的问题为例,下面是前两个问题("答案 "是第一个文本块)的结果:
1) 根据解释性备忘录,制定人工智能统一规则的条例(人工智能法)提案的主要目标是什么?
2) 如背景信息所述,关于人工智能的条例提案如何旨在解决与使用人工智能相关的风险,同时促进人工智能在欧盟的应用?
语块和问题的数量取决于语言,英语约为 100 个语块和 200 个问题,匈牙利语为 200 个语块和 400 个问题。
评估 OpenAI 嵌入模型
我们的评估功能遵循 Llama Index 文档,包括两个主要步骤。首先,将所有答案(文档块)的嵌入模型存储在 VectorStoreIndex 中,以便高效检索。然后,评估函数在所有查询中循环,检索前 k 个最相似的文档,并以 MRR(平均互易等级)评估检索的准确性。
def evaluate(dataset, embed_model, insert_batch_size=1000, top_k=5):(dataset, embed_model, insert_batch_size=1000, top_k=5):
# Get corpus, queries, and relevant documents from the qa_dataset object
corpus = dataset.corpus
queries = dataset.queries
relevant_docs = dataset.relevant_docs
# Create TextNode objects for each document in the corpus and create a VectorStoreIndex to efficiently store and retrieve embeddings
nodes = [TextNode(id_=id_, text=text) for id_, text in corpus.items()]
index = VectorStoreIndex(
nodes, embed_model=embed_model, insert_batch_size=insert_batch_size
)
retriever = index.as_retriever(similarity_top_k=top_k)
# Prepare to collect evaluation results
eval_results = []
# Iterate over each query in the dataset to evaluate retrieval performance
for query_id, query in tqdm(queries.items()):
# Retrieve the top_k most similar documents for the current query and extract the IDs of the retrieved documents
retrieved_nodes = retriever.retrieve(query)
retrieved_ids = [node.node.node_id for node in retrieved_nodes]
# Check if the expected document was among the retrieved documents
expected_id = relevant_docs[query_id][0]
is_hit = expected_id in retrieved_ids # assume 1 relevant doc per query
# Calculate the Mean Reciprocal Rank (MRR) and append to results
if is_hit:
rank = retrieved_ids.index(expected_id) + 1
mrr = 1 / rank
else:
mrr = 0
eval_results.append(mrr)
# Return the average MRR across all queries as the final evaluation metric
return np.average(eval_results)
嵌入模型通过 "embed_model "参数传递给评估函数,对于 OpenAI 模型来说,"embed_model "参数是一个用模型名称和模型维度初始化的 OpenAIEmbedding 对象。
from llama_index.embeddings.openai import OpenAIEmbeddingimport OpenAIEmbedding
embed_model = OpenAIEmbedding(model=model_spec['model_name'],
dimensions=model_spec['dimensions'])
维度 API 参数可以缩短嵌入(即从序列末尾移除一些数字),而不会使嵌入失去其概念表示特性。例如,OpenAI 在其公告中指出,在 MTEB 基准测试中,嵌入式可以缩短到 256 大小,但仍然优于未缩短的text-embedding-ada-0021536 的嵌入。
我们在四种不同的 OpenAI 嵌入模型上运行了评估功能:
每个模型都在四种不同的语言上进行了评估: 英语 (EN)、法语 (FR)、捷克语 (CS) 和匈牙利语 (HU),分别涵盖日耳曼语、罗曼语、斯拉夫语和乌拉尔语的例子。
embeddings_model_spec = {
}
embeddings_model_spec['OAI-Large-256']={'model_name':'text-embedding-3-large','dimensions':256}'OAI-Large-256']={'model_name':'text-embedding-3-large','dimensions':256}
embeddings_model_spec['OAI-Large-3072']={'model_name':'text-embedding-3-large','dimensions':3072}
embeddings_model_spec['OAI-Small']={'model_name':'text-embedding-3-small','dimensions':1536}
embeddings_model_spec['OAI-ada-002']={'model_name':'text-embedding-ada-002','dimensions':None}
results = []
languages = ["EN", "FR", "CS", "HU"]
# Loop through all languages
for language in languages:
# Load dataset
file_name=language+"_dataset.json"
qa_dataset = EmbeddingQAFinetuneDataset.from_json(file_name)
# Loop through all models
for model_name, model_spec in embeddings_model_spec.items():
# Get model
embed_model = OpenAIEmbedding(model=model_spec['model_name'],
dimensions=model_spec['dimensions'])
# Assess embedding score (in terms of MRR)
score = evaluate(qa_dataset, embed_model)
results.append([language, model_name, score])
df_results = pd.DataFrame(results, columns = ["Language" ,"Embedding model", "MRR"])
由此得出的 MRR 精确度报告如下:
不出所料,对于大型模型来说,3072 的较大嵌入尺寸会带来更好的性能。不过,与小型模型和传统 Ada 模型相比,大型模型的嵌入尺寸要小于我们的预期。为了进行比较,我们还在下文中报告了 OpenAI 模型在 MTEB 基准测试中的表现。
值得注意的是,在我们的评估中,大型模型、小型模型和 Ada 模型之间的性能差异远没有 MTEB 基准那么明显,这反映了一个事实,即在大型基准中观察到的平均性能并不一定反映在定制数据集上获得的性能。
开源嵌入模型评估
为了在本文中进行比较,我们选择了一组最近(2024 年)发布的四个嵌入模型。选择的标准是它们在 MTEB 排行榜上的平均得分及其处理多语言数据的能力。所选模型的主要特点概述如下。
评估这些开源模型的代码与用于 OpenAI 模型的代码相似。主要变化在于模型规格,其中必须指定诸如最大上下文长度和池类型等额外细节。然后,我们分别对四种语言中的每种模型进行评估:
embeddings_model_spec = {
}
embeddings_model_spec['E5-mistral-7b']={'model_name':'intfloat/e5-mistral-7b-instruct','max_length':32768, 'pooling_type':'last_token', 'E5-mistral-7b']={'model_name':'intfloat/e5-mistral-7b-instruct','max_length':32768, 'pooling_type':'last_token',
'normalize': True, 'batch_size':1, 'kwargs': {'load_in_4bit':True, 'bnb_4bit_compute_dtype':torch.float16}}
embeddings_model_spec['ML-E5-large']={'model_name':'intfloat/multilingual-e5-large','max_length':512, 'pooling_type':'mean',
'normalize': True, 'batch_size':1, 'kwargs': {'device_map': 'cuda', 'torch_dtype':torch.float16}}
embeddings_model_spec['BGE-M3']={'model_name':'BAAI/bge-m3','max_length':8192, 'pooling_type':'cls',
'normalize': True, 'batch_size':1, 'kwargs': {'device_map': 'cuda', 'torch_dtype':torch.float16}}
embeddings_model_spec['Nomic-Embed']={'model_name':'nomic-ai/nomic-embed-text-v1','max_length':8192, 'pooling_type':'mean',
'normalize': True, 'batch_size':1, 'kwargs': {'device_map': 'cuda', 'trust_remote_code' : True}}
results = []
languages = ["EN", "FR", "CS", "HU"]
# Loop through all models
for model_name, model_spec in embeddings_model_spec.items():
print("Processing model : "+str(model_spec))
# Get model
tokenizer = AutoTokenizer.from_pretrained(model_spec['model_name'])
embed_model = AutoModel.from_pretrained(model_spec['model_name'], **model_spec['kwargs'])
if model_name=="Nomic-Embed":
embed_model.to('cuda')
# Loop through all languages
for language in languages:
# Load dataset
file_name=language+"_dataset.json"
qa_dataset = EmbeddingQAFinetuneDataset.from_json(file_name)
start_time_assessment=time.time()
# Assess embedding score (in terms of hit rate at k=5)
score = evaluate(qa_dataset, tokenizer, embed_model, model_spec['normalize'], model_spec['max_length'], model_spec['pooling_type'])
# Get duration of score assessment
duration_assessment = time.time()-start_time_assessment
results.append([language, model_name, score, duration_assessment])
df_results = pd.DataFrame(results, columns = ["Language" ,"Embedding model", "MRR", "Duration"])
由此得出的 MRR 精确度报告如下。
结果表明,BGE-M3 的性能最好,其次是 ML-E5-Large、E5-mistral-7b 和 Nomic-Embed。BGE-M3 模型尚未在 MTEB 排行榜上进行基准测试,我们的结果表明,它的排名可能会高于其他模型。值得注意的是,虽然 BGE-M3 针对多语言数据进行了优化,但它在英语方面的表现也优于其他模型。
我们还在下面报告了每种嵌入模型的处理时间。
E5-mistral-7b 比其他型号大 10 多倍,毫无疑问是迄今为止速度最慢的型号。
结论
让我们把八个测试型号的性能并列在一张图中。
从这些结果中可以看出:
因此,你应该选择付费订阅 OpenAI 还是托管开源嵌入模型?