乐趣区

关于人工智能:选择最适合数据的嵌入模型OpenAI-和开源多语言嵌入的对比测试

OpenAI 最近公布了他们的新一代嵌入模型embedding v3,他们将其形容为性能最好的嵌入模型,具备更高的多语言性能。这些模型分为两类: 较小的称为 text- embeddings -3-small,较大且性能更弱小的称为 text- embeddings -3-large。

这些模型的设计和训练形式的信息披露得很少, 模型只能通过付费 API 拜访。所以就呈现了很多开源的嵌入模型然而这些开源的模型与 OpenAI 闭源模型相比如何呢?

本文将这些新模型与开源模型的性能进行实证比拟。咱们将创立一个数据检索工作流,在这个工作流中,必须依据用户查问找到语料库中最相干的文档。

咱们的语料库是欧洲人工智能法案,该法案目前处于验证的最初阶段。这个语料库除了是世界上第一个对于人工智能的法律框架外,还有一个重要的特点就是它有 24 种语言版本。这样咱们能够比拟不同语系的数据检索的准确性。

咱们将从多语言文本语料库生成自定义合成问题 / 答案数据集,在此自定义数据集上比拟 OpenAI 和最先进的开源嵌入模型的准确性。最初会提供残缺的代码,因为本文所采纳的办法能够实用于其余数据语料库。

生成自定义 Q / A 数据集

让咱们首先从生成自定义数据的问答 (Q/ A) 数据集开始,生成自定义数据集的益处能够通过确保数据集不是嵌入模型训练的一部分来防止偏差,这可能产生在 MTEB 等参考基准上。并且咱们能够将评估调整为特定的数据语料库,这可能与检索加强应用程序 (RAG) 等状况相干。

咱们将应用 Llama Index 在其文档中倡议的简略流程。语料库首先被分成块。而后对于每个分块,通过大型语言模型 (large language model, LLM) 生成一组合成问题,使答案位于相应的分块中:

应用 Llama Index 之类的 LLM 数据框架实现此策略非常简单,如上面的代码所示。

 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 间接从 Web 上获取。本文应用 2021 年 4 月的草案版本,因为最终版本尚未实用于所有欧洲语言。所以咱们抉择的这一版能够用其余 23 种欧盟官方语言中的任何一种语言替换 URL 中的 language,检索不同语言的文本(BG 示意保加利亚语,ES 示意西班牙语,CS 示意捷克语,等等)。

应用 SentenceSplitter 对象将文档分成每 1000 个令牌的块。对于英语来说,这会生成大概 100 个块。而后将每个块作为上下文提供给以下提醒(Llama Index 库中倡议的默认提醒):

 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”传递,咱们将其设置为 2。而后能够通过调用 Llama Index 库中的 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 来实现这项工作,后果对象 ’ qa_dataset ‘ 蕴含问题和答案 (块) 对。作为生成问题的示例,以下是前两个问题的后果(其中“答案”是文本的第一局部):

  1. What are the main objectives of the proposal for a Regulation laying down harmonised rules on artificial intelligence (Artificial Intelligence Act) according to the explanatory memorandum?
  2. How does the proposal for a Regulation on artificial intelligence aim to address the risks associated with the use of AI while promoting the uptake of AI in the European Union, as outlined in the context information?

OpenAI 嵌入模型

评估函数也是遵循 Llama Index 文档:首先所有答案 (文档块) 的嵌入都存储在 VectorStoreIndex 中,以便无效检索。而后评估函数循环遍历所有查问,检索前 k 个最类似的文档,并依据 MRR (Mean Reciprocal Rank)评估检索的准确性,代码如下:

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

 from llama_index.embeddings.openai import OpenAIEmbedding
 
 embed_model = OpenAIEmbedding(model=model_spec['model_name'],
                               dimensions=model_spec['dimensions'])

dimensions 参数能够缩短嵌入(即从序列的开端删除一些数字),而不会失去嵌入的概念示意属性。OpenAI 在他们的布告中倡议,在 MTEB 基准测试中,嵌入能够缩短到 256 大小,同时依然优于未缩短的 text-embedding-ada-002 嵌入(大小为 1536)。

咱们在四种不同的嵌入模型上运行评估函数:

两个版本的 text-embedding-3-large: 一个具备最低可能维度(256),另一个具备最高可能维度(3072)。它们被称为“OAI-large-256”和“OAI-large-3072”。

OAI-small:text-embedding-3-small,维数为 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 精度如下:

嵌入尺寸越大,性能越好。

开源嵌入模型

围绕嵌入的开源钻研也是十分沉闷的,Hugging Face 的 MTEB leaderboard 会常常公布最新的嵌入模型。

为了在本文中进行比拟,咱们抉择了一组最近发表的四个嵌入模型(2024)。抉择的规范是他们在 MTEB 排行榜上的均匀得分和他们解决多语言数据的能力。所选模型的次要个性摘要如下。

e5-mistral-7b-instruct: 微软的这个 E5 嵌入模型是从 Mistral-7B-v0.1 初始化的,并在多语言混合数据集上进行微调。模型在 MTEB 排行榜上体现最好,但也是迄今为止最大的(14GB)。

multilingual-e5-large-instruct(ML-E5-large): 微软的另一个 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"])

后果如下:

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 的 large(3072)、small 和 ada 模型的性能十分类似。减小 large 的嵌入尺寸 (256) 会导致性能降落,并且没有像 OpenAI 说的那样比 ada 更好。

简直所有型号 (ML-E5-large 除外) 在英语上都体现最好。在捷克语和匈牙利语等语言中,体现存在显著差别,这可能是因为训练的数据比拟少。

咱们应该付费订阅 OpenAI,还是托管一个开源嵌入模型?

OpenAI 最近的价格调整使得他们的 API 变得更加实惠,当初每百万令牌的老本为 0.13 美元。如果每月解决一百万个查问(假如每个查问波及大概 1K 令牌),没那么老本约为 130 美元。所以能够依据理论须要计算来抉择是否托管开源嵌入模型。

当然老本效益并不是惟一的思考因素。可能还须要思考提早、隐衷和对数据处理工作流的管制等其余因素。开源模型提供了齐全数据管制的劣势,加强了隐衷性和定制性。

说到提早,OpenAI 的 API 也存在提早问题,有时会导致响应工夫缩短,所有有时候 OpenAI 的 API 不肯定是最快的抉择。

总之,在开源模型和像 OpenAI 这样的专有解决方案之间做出抉择并不是一个简略的答案。开源嵌入提供了一个十分好的可选项,它将性能与对数据的更好管制联合在一起。而 OpenAI 的产品可能依然会吸引那些优先思考便利性的人,特地是如果隐衷问题是主要的。

本文代码:https://avoid.overfit.cn/post/722aa2145139453aaf692c147d06b3c8

作者:Yann-Aël Le Borgne

退出移动版