Embedding 模型部署及效果評測

寫在前面

最近大模型發展迅速,與之對應的向量化需求也被帶動起來了,由此社區也衍生出很多模型,本文選幾款,簡單做下評測。

前置概念

爲方便讀者,先簡單介紹幾個概念。

概念1:Vector Embedding

也即向量化嵌入,舉個例子:

想象一下,你是一位市場研究員,職責是分析消費者的購買行爲,併爲你的客戶提供針對性的營銷策略。在你的數據庫中,有成千上萬的消費者交易記錄,每條記錄都包含了消費者的個人信息、購買的商品、購買的時間和地點等信息。

在沒有Vector Embedding的情況下,如果你想找出哪些消費者可能對新產品感興趣,你可能需要手動查看每條交易記錄,然後根據消費者的購買歷史和商品的特點來進行判斷。然而,這種方法可能忽略了消費者的其他重要特徵,如他們的收入水平、興趣愛好、生活方式等,導致分析的結果不夠準確。

現在引入Vector Embedding,此時你就可以將每位消費者的個人信息和購買歷史轉化爲一個多維的“消費者畫像向量”。這個向量不僅包括了消費者的基本信息和購買歷史,還包含了他們的收入水平、興趣愛好、生活方式等各個方面的元素。換句話說,這些信息能反映出消費者的複雜特徵。例如,一位經常購買高端護膚品的消費者畫像向量,大概率與追求高品質生活的向量相近,而一位經常購買戶外運動裝備的消費者畫像向量,大概率與追求健康生活方式和熱愛自然的向量相近。此時,若想找出哪些消費者可能對新產品感興趣,只需要計算出新產品的向量,並與大量消費者畫像的向量進行對比,即可快速篩選出潛在的目標客戶(目標客戶的向量相似度高)。這個過程不再需要逐個查看消費者的個人信息和交易記錄,大大簡化了數據的處理過程。

更深入的概念可閱讀附錄[1]進行學習。

概念2:文檔切分中的Chunk和Overlap

在處理較長文檔或文本時,將其分割成若干小塊,每塊稱爲一個Chunk。在這個特定的切分策略中,每個Chunk由最多N個Token組成。Token通常指文本中的單詞或符號,取決於具體語境。而Overlap是相鄰Chunk之間共有的Token數。舉個例子:

每Chunk 200 Token,Overlap 20。在這個例子中,每個Chunk由最多200個Token組成。Overlap爲20,意味着相鄰的Chunks會有20個Token是重複的,從而確保文本的連貫性。例如,如果某個文本段落共有230個Token,它將被分成兩個Chunks:第一個Chunk將有200個Token,第二個Chunk將有30個Token(因爲230-200=30),並且這兩個Chunks之間將有20個Token重疊。

這種切分方式有助於確保在將長文檔送入諸如大型語言模型進行Embedding或處理時,能夠保持文本的語義連貫性,同時又能滿足模型處理長度有限的輸入的要求。切分文檔時考慮Chunk的大小和Overlap的數量,對於提高模型處理效率和文本的語義理解都是十分重要的。

 

模型調研

與大模型類似,Embedding也是使用模型來實現的,只不過Embedding模型更爲輕量。一般都在2G以內。

經調研(附錄[6~10]),發現以下模型對中文的支持效果較好,且已經開源方便本地私有化部署:

可以看得出m3模型的優勢是支持多語言,並且字符數擴展到了8192,這意味着BGE-M3能夠高效地處理長篇幅的文檔,滿足對於長文檔檢索的需求。

以上幾類模型的鏈接請參考附錄[2~5],下文針對這幾種模型進行效果評測。

模型部署

爲了部署Embedding模型,我們需要引入對應的工具庫,目前主要有幾類:

  1. Sentence-Transformers: Sentence-Transformers庫是基於HuggingFace的Transformers庫構建的,它專門設計用於生成句子級別的嵌入。它引入了一些特定的模型和池化技術,使得生成的嵌入能夠更好地捕捉句子的語義信息。Sentence-Transformers庫特別適合於需要計算句子相似度、進行語義搜索和挖掘同義詞等任務。

  2. HuggingFace Transformers: HuggingFace的Transformers庫是一個廣泛使用的NLP庫,它提供了多種預訓練模型,如BERT、GPT-2、RoBERTa等。這些模型可以應用於各種NLP任務,如文本分類、命名實體識別、問答系統等。Transformers庫支持多種編程語言,並且支持模型的微調和自定義模型的創建。雖然Transformers庫的功能強大,但它主要關注於模型的使用,而不是直接提供句子級別的嵌入。

  3. Langchain集成的HuggingFaceBgeEmbeddings。與3一樣。

  4. FlagEmbedding: 這是一個相對較新的庫,其核心在於能夠將任意文本映射到低維稠密向量空間,以便於後續的檢索、分類、聚類或語義匹配等任務。FlagEmbedding的一大特色是它可以支持爲大模型調用外部知識,這意味着它不僅可以處理純文本數據,還能整合其他類型的信息源,如知識圖譜等,以提供更豐富的語義表示。

總的來說,FlagEmbedding強調的是稠密向量的生成和外部知識的融合;HuggingFace Transformers提供了一個廣泛的預訓練模型集合,適用於多種NLP任務;而Sentence-Transformers則專注於生成高質量的句子嵌入,適合那些需要深入理解句子語義的應用場景。

 

結合上述說明,以及翻閱網上各類文章,發現使用 Sentence-Transformers 居多,因此本文選用它。

安裝 sentence-transformers(在Linux機器安裝吧,Windows機器各種報錯):

pip install -U sentence-transformers

基於Sentence-Transformers的向量化方法:

from sentence_transformers import SentenceTransformer
sentences_1 = ["樣例數據-1", "樣例數據-2"]
sentences_2 = ["樣例數據-3", "樣例數據-4"]
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
embeddings_1 = model.encode(sentences_1, normalize_embeddings=True)
embeddings_2 = model.encode(sentences_2, normalize_embeddings=True)
similarity = embeddings_1 @ embeddings_2.T
print(similarity)

上面的例子演示了句子的向量化過程。額外解釋一下倒數第二行:

embeddings_1 @ embeddings_2.T 是 Python 中的一種矩陣乘法運算。這裏使用了 @ 符號來表示矩陣的點積(dot product)操作。具體來說:

  • embeddings_1 是一個二維數組(或稱爲矩陣),其中每一行代表一個句子在向量空間中的嵌入表示。

  • embeddings_2 也是一個二維數組,其結構與 embeddings_1 相同,但包含不同的句子的嵌入表示。

  • embeddings_2.Tembeddings_2 的轉置,這意味着將它的行列互換。這樣,原來的每行變成了每列,原來的每列變成了每行。

當執行 embeddings_1 @ embeddings_2.T 時,Python 會計算兩個矩陣的點積。結果是一個新的二維數組,其中的每個元素是 embeddings_1 中的一行和 embeddings_2.T 中對應列的乘積之和。在這個上下文中,它實際上是在計算兩組句子嵌入之間的相似度矩陣。

例如,如果 embeddings_1 和 embeddings_2.T 分別是以下的矩陣:

embeddings_1: [ [e11, e12, e13], [e21, e22, e23] ]
embeddings_2.T: [ [f11, f21], [f12, f22], [f13, f23] ]

那麼點積的結果將是:

similarity: [ [e11*f11 + e12*f12 + e13*f13, e11*f21 + e12*f22 + e13*f23],
              [e21*f11 + e22*f12 + e23*f13, e21*f21 + e22*f22 + e23*f23] ]

這個結果矩陣中的每個元素代表了原始句子對之間的某種形式的相似度分數。

 

有了工具集後,把模型文件下載到本地並加載即可。

評測方法

到網上隨便找一篇文章:

據傳,菜煎餅起源於13世紀中期,當時明軍與元軍在嶧州展開激戰,當地人民死傷慘重。後來,從山西洪洞一帶移民至此的民衆,僅靠官府發放的半斤糧食無法充飢,便將五穀摻水,用石磨研磨成漿糊,放在鐵板上,用竹片攤成“薄紙”,並大量包裝蔬菜、野菜、草根和樹葉,以此充飢。

菜煎餅是山東魯南地區的一種大衆食品,製作原料主要有面粉、雜糧、雞蛋等,老少兼宜,俗稱“中國熱狗”,流行於棗莊、濟寧、臨沂、徐州等魯南地,後傳布周圍省市。上個世紀七十年代,棗莊農村的生活還是很匱乏的,老百姓的主食以煎餅爲主,煎餅的主要原料是地瓜幹,條件好一點的可稍放點小麥,剛烙煎餅時鏊子涼,需把鏊子燒熱擦些油才容易把煎餅從鏊子上揭下來,這樣烙出的煎餅就很厚,稍等一會兒,煎餅涼了又板又硬,很難下嚥。因此我們棗莊人把烙煎餅時前幾張和後幾張煎餅稱爲滑鏊子煎餅或滑塌子。這樣的煎餅很難下嚥,但丟了又可惜,精明的母親們就將大白菜,土豆絲,粉條,豆腐切碎加點豬油,放上辣椒麪,花椒麪和鹽,做成了所謂的菜煎餅,這樣一來不但滑鏊子煎餅解決了,並且做出的煎餅還特別好喫,這樣一傳十、十傳百,於是菜煎餅就在農村各家各戶傳開了!

八十年代末期,農村土地實行了聯產承包責任制已有多年,農民在農忙季節忙耕種,農閒時便有了剩餘時間,有的農村婦女就到街上擺地攤賣菜煎餅掙點零花錢。一輛三輪車,一盤小餅鍪,一個蜂窩爐,一個切菜板,幾樣時令蔬菜,食客現場點菜,業主現場烙制,簡簡單單的營生,成爲棗莊街頭一道風景。許許多多的農村人多了一個貼補家用的掙錢機會,人們生活也多了一道風味小喫。到了九十年代末期,就連一些男人也走上了街頭賣起了菜煎餅。

1993年5月,山東省勞動廳在棗莊舉辦特級廚師培訓班,聘請江蘇省淮安商業技工學校一行6人赴棗莊講學,這六人當中有校領導、高級講師、特級廚師,途中經臺兒莊區招待所午餐,席上菜餚豐盛,但惟有“菜煎餅”被其六人齊呼:“天下第一美食”。

山東菜煎餅如何做呢?首先要熱鍋,放油(油要多),下豆腐中火翻炒至金黃,放入之前切好的粉條,繼續翻炒幾分鐘,加入適量的鹽。再放入切好的韭菜,翻炒幾下攪勻即可(千萬不可炒過了,韭菜要生生的),撒味精出鍋。將煎餅攤開,用勺子舀上適量的韭菜餡兒,用勺背整勻。好了之後,可以將兩邊向中間摺疊形成長方形,一張煎餅就做好了。

按照前文的樣例,寫一段腳本進行評測(爲方便演示,簡單地根據段落進行拆分):

import sys
import torch
from sentence_transformers import SentenceTransformer

# 加載預訓練的句子嵌入模型
model = SentenceTransformer(sys.argv[1])
# 定義句子列表
sentences_1 = ["據傳,菜煎餅起源於13世紀中期,當時明軍與元軍在嶧州展開激戰,當地人民死傷慘重。後來,從山西洪洞一帶移民至此的民衆,僅靠官府發放的半斤糧食無法充飢,便將五穀摻水,用石磨研磨成漿糊,放在鐵板上,用竹片攤成“薄紙”,並大量包裝蔬菜、野菜、草根和樹葉,以此充飢。"]
sentences_2 = ["菜煎餅是山東魯南地區的一種大衆食品,製作原料主要有面粉、雜糧、雞蛋等,老少兼宜,俗稱“中國熱狗”,流行於棗莊、濟寧、臨沂、徐州等魯南地,後傳布周圍省市。上個世紀七十年代,棗莊農村的生活還是很匱乏的,老百姓的主食以煎餅爲主,煎餅的主要原料是地瓜幹,條件好一點的可稍放點小麥,剛烙煎餅時鏊子涼,需把鏊子燒熱擦些油才容易把煎餅從鏊子上揭下來,這樣烙出的煎餅就很厚,稍等一會兒,煎餅涼了又板又硬,很難下嚥。因此我們棗莊人把烙煎餅時前幾張和後幾張煎餅稱爲滑鏊子煎餅或滑塌子。這樣的煎餅很難下嚥,但丟了又可惜,精明的母親們就將大白菜,土豆絲,粉條,豆腐切碎加點豬油,放上辣椒麪,花椒麪和鹽,做成了所謂的菜煎餅,這樣一來不但滑鏊子煎餅解決了,並且做出的煎餅還特別好喫,這樣一傳十、十傳百,於是菜煎餅就在農村各家各戶傳開了!"]
sentences_3 = ["八十年代末期,農村土地實行了聯產承包責任制已有多年,農民在農忙季節忙耕種,農閒時便有了剩餘時間,有的農村婦女就到街上擺地攤賣菜煎餅掙點零花錢。一輛三輪車,一盤小餅鍪,一個蜂窩爐,一個切菜板,幾樣時令蔬菜,食客現場點菜,業主現場烙制,簡簡單單的營生,成爲棗莊街頭一道風景。許許多多的農村人多了一個貼補家用的掙錢機會,人們生活也多了一道風味小喫。到了九十年代末期,就連一些男人也走上了街頭賣起了菜煎餅。"]
sentences_4 = ["1993年5月,山東省勞動廳在棗莊舉辦特級廚師培訓班,聘請江蘇省淮安商業技工學校一行6人赴棗莊講學,這六人當中有校領導、高級講師、特級廚師,途中經臺兒莊區招待所午餐,席上菜餚豐盛,但惟有“菜煎餅”被其六人齊呼:“天下第一美食”。"]
sentences_5 = ["山東菜煎餅如何做呢?首先要熱鍋,放油(油要多),下豆腐中火翻炒至金黃,放入之前切好的粉條,繼續翻炒幾分鐘,加入適量的鹽。再放入切好的韭菜,翻炒幾下攪勻即可(千萬不可炒過了,韭菜要生生的),撒味精出鍋。將煎餅攤開,用勺子舀上適量的韭菜餡兒,用勺背整勻。好了之後,可以將兩邊向中間摺疊形成長方形,一張煎餅就做好了。"]
# 獲取句子的嵌入向量表示、
sentences_embeddings_1 = torch.from_numpy(model.encode(sentences_1, normalize_embeddings=True))
sentences_embeddings_2 = torch.from_numpy(model.encode(sentences_2, normalize_embeddings=True))
sentences_embeddings_3 = torch.from_numpy(model.encode(sentences_3, normalize_embeddings=True))
sentences_embeddings_4 = torch.from_numpy(model.encode(sentences_4, normalize_embeddings=True))
sentences_embeddings_5 = torch.from_numpy(model.encode(sentences_5, normalize_embeddings=True))
# 合併所有的句子嵌入表示
all_sentences_embeddings = torch.cat([sentences_embeddings_1, sentences_embeddings_2, sentences_embeddings_3, sentences_embeddings_4, sentences_embeddings_5], dim=0)

# 定義查詢句子
queries_1 = ["菜煎餅的製作原料有哪些?"]
queries_2 = ["菜煎餅的組成是什麼?"]
queries_3 = ["做菜煎餅需要什麼?"]
# 獲取查詢句子的嵌入向量表示
queries_embeddings_1 = torch.from_numpy(model.encode(queries_1, normalize_embeddings=True))
queries_embeddings_2 = torch.from_numpy(model.encode(queries_2, normalize_embeddings=True))
queries_embeddings_3 = torch.from_numpy(model.encode(queries_3, normalize_embeddings=True))

# 計算查詢句子與所有句子的相似度
similarity_queries_1_sentences = queries_embeddings_1 @ all_sentences_embeddings.T
similarity_queries_2_sentences = queries_embeddings_2 @ all_sentences_embeddings.T
similarity_queries_3_sentences = queries_embeddings_3 @ all_sentences_embeddings.T

# 打印numpy size
print("sentences_vector dimension:", sentences_embeddings_1.size())
print("sentences_vector dimension:", queries_embeddings_1.size())
# 打印相似度結果(幾個問題都是在問製作原料,從字面來看,我們預期三個查詢與sentences_2 和 sentences_5 的相似度較高)
print("Query 1 Similarity:", similarity_queries_1_sentences)
print("Query 2 Similarity:", similarity_queries_2_sentences)
print("Query 3 Similarity:", similarity_queries_3_sentences)

執行時,把模型本地路徑作爲第一個參數傳入即可,如:

time python test.py ./bge-large-zh-v1.5
time python test.py ./bge-m3
time python test.py ./m3e-base
time python test.py ./tao-8k

其中的time是爲了測試程序運行耗時。

評測結果

作者在本機CPU執行,機器配置爲:4核CPU(i7)、16GB內存。

評測結果如下:

看起來,bge-m3效果最好,tao-8k也不錯。但需要注意的是,這兩個模型執行耗時也最高。

總結

本文選用常見的幾類中文友好的開源Embedding模型進行了簡單效果評測,發現bge-m3和tao-8k的效果不錯。有條件的讀者可以將其部署在GPU上進行評測,應該會更快。另外,也可以使用更爲全面的數據集進行評估,以得出更爲權威的結論。

在實際的生產環境中,還要進行壓力測試,以評估文檔向量化的性能。

最後,歡迎關注微信公衆號xiaoxi666,一起交流一起玩兒~

附錄

[1] 向量數據庫: https://guangzhengli.com/blog/zh/vector-database/

[2] bge-large-zh-v1.5: https://hf-mirror.com/BAAI/bge-large-zh-v1.5

[3] bge-m3: https://hf-mirror.com/BAAI/bge-m3

[4] m3e-base: https://hf-mirror.com/moka-ai/m3e-base

[5] tao-8k: https://hf-mirror.com/amu/tao-8k

[6] 智源開源最強語義向量模型BGE: https://zhuanlan.zhihu.com/p/648448793

[7] 新一代通用向量模型BGE-M3: https://zhuanlan.zhihu.com/p/680537154

[8] 實戰對比OpenAI、BGE-Large以及阿里Embedding模型效果: https://zhuanlan.zhihu.com/p/658775304​

[9] Embedding模型的選擇: https://zhuanlan.zhihu.com/p/673483110

[10] 中文Embedding模型優劣數據評測: https://zhuanlan.zhihu.com/p/679166797

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章