跳转到主要内容

category

选择最适合您的数据的模型

OpenAI最近发布了他们的新一代嵌入模型,称为嵌入v3,他们将其描述为性能最高的嵌入模型,具有更高的多语言性能。这些模型分为两类:一类较小,称为text-embedding-3-small,另一类较大,功能更强大,称为text-embedding-3-large。

关于这些模型的设计和训练方式,披露的信息很少。作为他们之前发布的嵌入模型(2022年12月,ada-002模型类),OpenAI再次选择了一种封闭源方法,其中模型只能通过付费的API访问。

但是,这些表演是否如此之好,以至于值得付出代价?

这篇文章的动机是将这些新模型的性能与开源模型的性能进行实证比较。我们将依赖于数据检索工作流,在该工作流中,必须在给定用户查询的情况下找到语料库中最相关的文档。

我们的语料库将是《欧洲人工智能法案》,该法案目前正处于最后的验证阶段。这个语料库的一个有趣的特点是,除了是世界上第一个人工智能的法律框架外,它还有24种语言。这使得可以比较不同语言族的数据检索准确性。

该职位将经历以下两个主要步骤:

  • 从多语言文本语料库生成自定义合成问答数据集
  • 在这个自定义数据集上比较OpenAI和最先进的开源嵌入模型的准确性。


在这个Github存储库中提供了重现本文中呈现的结果的代码和数据。请注意,以《欧盟人工智能法案》为例,本文所遵循的方法可以适用于其他数据语料库。

生成自定义问答数据集


让我们首先从生成自定义数据的问答数据集(Q/a)开始,该数据集将用于评估不同嵌入模型的性能。生成自定义问答数据集的好处有两个。首先,它通过确保数据集没有成为嵌入模型训练的一部分来避免偏差,这可能发生在参考基准(如MTEB)上。其次,它允许根据特定的数据语料库进行评估,例如,在检索增强应用程序(RAG)的情况下,这可能是相关的。

我们将遵循Llama Index在其文档中建议的简单流程。语料库首先被分成一组组块。然后,对于每个块,通过大型语言模型(LLM)生成一组合成问题,使得答案位于相应的块中。该过程如下所示:

Generating a question/answer dataset for your data, methodology from Llama Index

使用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月的草案版本,因为最终版本尚未适用于所有欧洲语言。在这个版本中,URL中的英语可以替换为其他23种欧盟官方语言中的任何一种,以检索不同语言的文本(保加利亚语为BG,西班牙语为ES,捷克语为CS,等等)。


下载24种欧盟官方语言的《欧盟人工智能法案》链接(来自欧盟官方网站)


我们使用PensioneSplitter对象将文档拆分为1000个令牌的块。对于英语来说,这会产生大约100个块。

然后将每个块作为上下文提供给以下提示(Llama索引库中建议的默认提示):

prompts={}
prompts["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上下文窗口,并针对对话框进行了优化(https://platform.OpenAI.com/docs/models/GPT-3-5-turbo)。

生成的objet“qa_dataset”包含问题和答案(组块)对。作为生成问题的示例,以下是前两个问题的结果(“答案”是第一块文本):

1) 根据解释性备忘录,制定人工智能统一规则的法规(《人工智能法》)提案的主要目标是什么?
2) 如上下文信息所述,人工智能条例的提案如何旨在解决与人工智能使用相关的风险,同时促进欧盟对人工智能的吸收?

语块和问题的数量取决于语言,从英语的大约100个语块和200个问题到匈牙利语的200个语块或400个问题。

OpenAI嵌入模型的评估


我们的评估功能遵循Llama指数文档,包括两个主要步骤。首先,所有答案(文档块)的嵌入都存储在VectorStoreIndex中,以实现高效检索。然后,评估函数在所有查询上循环,检索前k个最相似的文档,并根据MRR(平均倒数排名)评估检索的准确性。

def evaluate(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_mode”参数传递给评估函数,对于OpenAI模型,该参数是用模型名称和模型维度初始化的OpenAIEmbedding对象。

from llama_index.embeddings.openai import OpenAIEmbedding

embed_model = OpenAIEmbedding(model=model_spec['model_name'],
dimensions=model_spec['dimensions'])


维度API参数可以缩短嵌入(即从序列的末尾移除一些数字),而不会丢失嵌入的概念表示属性。例如,OpenAI在其公告中建议,在MTEB基准上,嵌入可以缩短到256的大小,同时仍然优于1536大小的未排序文本嵌入-ad-002嵌入。

我们在四个不同的OpenAI嵌入模型上运行了评估函数:

  • text-embedding-3-大的两个版本:一个具有尽可能低的维度(256),另一个具有最高的维度(3072)。这些被称为“OAI-large256”和“OAI-Large3072”。
  • OAI small:文本嵌入-3-小型嵌入模型,尺寸为1536。
  • OAI-ada-002:传统的text-embedding-ada-002型号,尺寸为1536。

每个模型在四种不同的语言上进行了评估:英语(EN)、法语(FR)、捷克语(CS)和匈牙利语(HU),分别涵盖了日耳曼语、罗曼斯语、斯拉夫语和乌拉尔语。

embeddings_model_spec = {
}

embeddings_model_spec['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的结果准确性报告如下:

OpenAI模型的性能摘要


正如预期的那样,对于大模型,3072的较大嵌入尺寸可以观察到更好的性能。然而,与小型和遗留的Ada模型相比,大型模型比我们预期的要小。为了进行比较,我们还在下面报告了OpenAI模型在MTEB基准上获得的性能。

OpenAI嵌入模型的性能,如其官方公告所述
值得注意的是,在我们的评估中,大、小和Ada模型之间的性能差异远不如MTEB基准中明显,这反映了一个事实,即在大型基准中观察到的平均性能并不一定反映在自定义数据集上获得的性能。

开源嵌入模型的评估


围绕嵌入的开源研究相当活跃,并且定期发布新的模型。关于最新发布的模型,一个保持更新的好地方是拥抱脸😊 MTEB排行榜。

为了在本文中进行比较,我们选择了最近发表的一组四个嵌入模型(2024)。选择的标准是他们在MTEB排行榜上的平均得分以及他们处理多语言数据的能力。所选模型的主要特征概述如下。

  Embedding model Embedding size Context size Size (GB) MTEB Rank (Feb 24) Release date
  e5-mistral-7b-instruct 4096 32768 14 4 04/01/2024
  multilingual-e5-large-instruct 1024 514 1.12 10 08/02/2024
  BGE-M3 1024 8192 2.27 NA 29/01/2024
  nomic-embed-text-v1 768 8192 0.55 22 10/02/2024

view rawembeddings_models.csv hosted with ❤ by GitHub

  • E5-Mistral-7B-结构(E5-Mistral-7B):微软的这个E5嵌入模型是从Mistral-7B-v0.1初始化的,并在多语言数据集的混合上进行了微调。该型号在MTEB排行榜上表现最好,但也是迄今为止最大的型号(14GB)。
  • 多语言e5-大型结构(ML-e5-larg):微软的另一个e5模型,旨在更好地处理多语言数据。它是从xlm-roberta-large初始化的,并在多语言数据集的混合上进行训练。它比E5 Mistral小得多(10倍),但上下文大小也低得多(514)。
  • BGE-M3:该模型由北京人工智能研究院设计,是他们最先进的多语言数据嵌入模型,支持100多种工作语言。截至2024年2月22日,尚未在MTEB排行榜上进行基准测试。
  • nomic-embed-text-v1(nomic-embed):该模型由nomic设计,声称比OpenAI Ada-002和text-embedding-3-small性能更好,但尺寸仅为0.55GB。有趣的是,该模型是第一个完全可复制和可审计的模型(开放数据和开源训练代码)。

用于评估这些开源模型的代码与用于OpenAI模型的代码相似。主要的变化在于模型规范,其中必须指定额外的细节,如最大上下文长度和池类型。然后,我们为四种语言中的每一种评估每个模型:

embeddings_model_spec = {
}

embeddings_model_spec['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多倍,是迄今为止速度最慢的型号。


结论


让我们把八个测试模型的性能放在一个图中。

八款测试车型的性能总结
这些结果的主要观察结果如下:

  • 开源模型获得了最佳性能。北京人工智能研究院开发的BGE-M3模型脱颖而出。该模型的上下文长度与OpenAI模型(8K)相同,大小为2.2GB。
  • OpenAI范围内的一致性。大型(3072)、小型和传统OpenAI模型的性能非常相似。然而,减小大模型(256)的嵌入尺寸导致了性能的下降。
  • 语言敏感性。几乎所有车型(ML-E5-large除外)的英语表现都最好。捷克语和匈牙利语等语言的表现有显著差异。

因此,你应该订阅付费的OpenAI,还是托管开源嵌入模型?

OpenAI最近的价格修订使其API的访问变得更加实惠,目前成本为每百万代币0.13美元。因此,每月处理一百万个查询(假设每个查询涉及大约1K个代币)的成本约为130美元。因此,根据您的使用情况,租用和维护自己的嵌入服务器可能不划算。

然而,成本效益并不是唯一的考虑因素。可能还需要考虑其他因素,如延迟、隐私和对数据处理工作流的控制。开源模型提供了完全数据控制、增强隐私和自定义的优势。另一方面,OpenAI的API存在延迟问题,有时会导致响应时间延长。

总之,在开源模型和OpenAI等专有解决方案之间的选择并不能给出一个简单的答案。开源嵌入提供了一个引人注目的选择,将性能与对数据的更大控制相结合。相反,OpenAI的产品可能仍然会吸引那些优先考虑便利性的人,尤其是在隐私问题是次要的情况下。

有用的链接

Notes:

文章链接