選擇最適合數(shù)據(jù)的嵌入模型:OpenAI 和開源多語言嵌入的對比測試
OpenAI最近發(fā)布了他們的新一代嵌入模型embedding v3,他們將其描述為性能最好的嵌入模型,具有更高的多語言性能。這些模型分為兩類:較小的稱為text- embeddings -3-small,較大且功能更強大的稱為text- embeddings -3-large。
這些模型的設計和訓練方式的信息披露得很少,模型只能通過付費API訪問。所以就出現(xiàn)了很多開源的嵌入模型但是這些開源的模型與OpenAI閉源模型相比如何呢?
本文將這些新模型與開源模型的性能進行實證比較。我們將創(chuàng)建一個數(shù)據(jù)檢索工作流,在這個工作流中,必須根據(jù)用戶查詢找到語料庫中最相關的文檔。
我們的語料庫是歐洲人工智能法案,該法案目前處于驗證的最后階段。這個語料庫除了是世界上第一個關于人工智能的法律框架外,還有一個重要的特點就是它有24種語言版本。這樣我們可以比較不同語系的數(shù)據(jù)檢索的準確性。
我們將從多語言文本語料庫生成自定義合成問題/答案數(shù)據(jù)集,在此自定義數(shù)據(jù)集上比較OpenAI和最先進的開源嵌入模型的準確性。最后會提供完整的代碼,因為本文所采用的方法可以適用于其他數(shù)據(jù)語料庫。
生成自定義Q/ A數(shù)據(jù)集
讓我們首先從生成自定義數(shù)據(jù)的問答(Q/ A)數(shù)據(jù)集開始,生成自定義數(shù)據(jù)集的好處可以通過確保數(shù)據(jù)集不是嵌入模型訓練的一部分來避免偏差,這可能發(fā)生在MTEB等參考基準上。并且我們可以將評估調(diào)整為特定的數(shù)據(jù)語料庫,這可能與檢索增強應用程序(RAG)等情況相關。
我們將使用Llama Index在其文檔中建議的簡單流程。語料庫首先被分成塊。然后對于每個分塊,通過大型語言模型(large language model, LLM)生成一組合成問題,使答案位于相應的分塊中:
使用Llama Index之類的LLM數(shù)據(jù)框架實現(xiàn)此策略非常簡單,如下面的代碼所示。
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."
"""
這個提示可以生成關于文檔塊的問題,要為每個數(shù)據(jù)塊生成的問題數(shù)量作為參數(shù)“num_questions_per_chunk”傳遞,我們將其設置為2。然后可以通過調(diào)用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來完成這項任務,結(jié)果對象' qa_dataset '包含問題和答案(塊)對。作為生成問題的示例,以下是前兩個問題的結(jié)果(其中“答案”是文本的第一部分):
- 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?
- 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嵌入模型
評估函數(shù)也是遵循Llama Index文檔:首先所有答案(文檔塊)的嵌入都存儲在VectorStoreIndex中,以便有效檢索。然后評估函數(shù)循環(huán)遍歷所有查詢,檢索前k個最相似的文檔,并根據(jù)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 '參數(shù)傳遞給評估函數(shù),對于OpenAI模型,該參數(shù)是一個用模型名稱和模型維度初始化的OpenAIEmbedding對象。
from llama_index.embeddings.openai import OpenAIEmbedding
embed_model = OpenAIEmbedding(model=model_spec['model_name'],
dimensinotallow=model_spec['dimensions'])
dimensions參數(shù)可以縮短嵌入(即從序列的末尾刪除一些數(shù)字),而不會失去嵌入的概念表示屬性。OpenAI在他們的公告中建議,在MTEB基準測試中,嵌入可以縮短到256大小,同時仍然優(yōu)于未縮短的text-embedding-ada-002嵌入(大小為1536)。
我們在四種不同的嵌入模型上運行評估函數(shù):
兩個版本的text-embedding-3-large:一個具有最低可能維度(256),另一個具有最高可能維度(3072)。它們被稱為“OAI-large-256”和“OAI-large-3072”。
OAI-small:text-embedding-3-small,維數(shù)為1536。
OAI-ada-002:傳統(tǒng)的文本嵌入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'],
dimensinotallow=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精度如下:
嵌入尺寸越大,性能越好。
開源嵌入模型
圍繞嵌入的開源研究也是非?;钴S的,Hugging Face 的 MTEB leaderboard會經(jīng)常發(fā)布最新的嵌入模型。
為了在本文中進行比較,我們選擇了一組最近發(fā)表的四個嵌入模型(2024)。選擇的標準是他們在MTEB排行榜上的平均得分和他們處理多語言數(shù)據(jù)的能力。所選模型的主要特性摘要如下。
e5-mistral-7b-instruct:微軟的這個E5嵌入模型是從Mistral-7B-v0.1初始化的,并在多語言混合數(shù)據(jù)集上進行微調(diào)。模型在MTEB排行榜上表現(xiàn)最好,但也是迄今為止最大的(14GB)。
multilingual-e5-large-instruct(ML-E5-large):微軟的另一個E5模型,可以更好地處理多語言數(shù)據(jù)。它從xlm-roberta-large初始化,并在多語言數(shù)據(jù)集的混合上進行訓練。它比E5-Mistral小得多(10倍),上下文大小也小得多(514)。
BGE-M3:該模型由北京人工智能研究院設計,是他們最先進的多語言數(shù)據(jù)嵌入模型,支持100多種工作語言。截至2024年2月22日,它還沒有進入MTEB排行榜。
nomic-embed-text-v1 (Nomic- embed):該模型由Nomic設計,其性能優(yōu)于OpenAI Ada-002和text-embedding-3-small,而且大小僅為0.55GB。該模型是第一個完全可復制和可審計的(開放數(shù)據(jù)和開源訓練代碼)的模型。
用于評估這些開源模型的代碼類似于用于OpenAI模型的代碼。主要的變化在于模型參數(shù):
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"])
結(jié)果如下:
BGE-M3的表現(xiàn)最好,其次是ML-E5-Large、E5-mistral-7b和Nomic-Embed。BGE-M3模型尚未在MTEB排行榜上進行基準測試,我們的結(jié)果表明它可能比其他模型排名更高。雖然BGE-M3針對多語言數(shù)據(jù)進行了優(yōu)化,但它在英語方面的表現(xiàn)也比其他模型更好。
因為式開源模型所以一般都需要本地運行,所以我們還特意記錄了每個嵌入模型的處理時間。
E5-mistral-7b比其他模型大10倍以上,所以最慢是很正常的
總結(jié)
我們把所有的結(jié)果做一個匯總
采用開源模型獲得了最好的性能,BGE-M3模型表現(xiàn)最佳。該模型具有與OpenAI模型相同的上下文長度(8K),大小為2.2GB。
OpenAI的large(3072)、small 和ada模型的性能非常相似。減小large的嵌入尺寸(256)會導致性能下降,并且沒有像OpenAI說的那樣比ada更好。
幾乎所有型號(ML-E5-large除外)在英語上都表現(xiàn)最好。在捷克語和匈牙利語等語言中,表現(xiàn)存在顯著差異,這可能是因為訓練的數(shù)據(jù)比較少。
我們應該付費訂閱OpenAI,還是托管一個開源嵌入模型?
OpenAI最近的價格調(diào)整使得他們的API變得更加實惠,現(xiàn)在每百萬令牌的成本為0.13美元。如果每月處理一百萬個查詢(假設每個查詢涉及大約1K令牌),沒那么成本約為130美元。所以可以根據(jù)實際需要計算來選擇是否托管開源嵌入模型。
當然成本效益并不是唯一的考慮因素??赡苓€需要考慮延遲、隱私和對數(shù)據(jù)處理工作流的控制等其他因素。開源模型提供了完全數(shù)據(jù)控制的優(yōu)勢,增強了隱私性和定制性。
說到延遲,OpenAI的API也存在延遲問題,有時會導致響應時間延長,所有有時候OpenAI的API不一定是最快的選擇。
總之,在開源模型和像OpenAI這樣的專有解決方案之間做出選擇并不是一個簡單的答案。開源嵌入提供了一個非常好的可選項,它將性能與對數(shù)據(jù)的更好控制結(jié)合在一起。而OpenAI的產(chǎn)品可能仍然會吸引那些優(yōu)先考慮便利性的人,特別是如果隱私問題是次要的。
本文代碼:https://github.com/Yannael/multilingual-embeddings