Model2Vec:從任何 Sentence Transformer 中蒸餾出小而快的模型
又名:如何讓 sentence transformer 速度提升 500 倍,體積縮小 15 倍
(大)語言模型已成為特徵提取的事實標準。雖然這些模型在大量任務上展現了最先進的效能,但它們也帶來了沉重的資源需求:巨大的能源消耗、計算需求和更長的處理時間。儘管有很多方法可以使現有的(Sentence)Transformer 變得更快,例如量化或專用核心,但它們仍然相對較慢,尤其是在 CPU 上。如果你需要更快的速度,並且正在開發一個有時間限制的產品(例如搜尋引擎),或者可用資源非常少,該怎麼辦?
這正是 Model2Vec 的用武之地——它提供靜態嵌入,對硬體和生態友好,同時保持強大的效能。
在這篇部落格中,我們將討論 Model2Vec 是什麼,它是如何工作的,如何使用它,以及它的效能。
目錄
什麼是 Model2Vec?
Model2Vec 是一種從任何 Sentence Transformer 中蒸餾出小型、快速、高效能靜態模型的技術。從宏觀上看,它的工作原理是:將一個詞彙表透過 sentence transformer 模型,然後使用 PCA 降低所得嵌入的維度,最後使用 zipf 權重對嵌入進行加權。這個過程不需要資料集,只需要一個模型(以及可選的詞彙表)。在推理時,我們只需取句子中出現的所有詞元嵌入的平均值。因此,Model2Vec 模型是完全非上下文相關的。雖然這聽起來像是一個很大的缺點,但我們將展示,考慮到它的小巧和快速,它的效能仍然相當不錯。
以上內容可能聽起來有些複雜,讓我們來詳細解釋一下。
Transformer 和嵌入
在 sentence transformer 的編碼步驟中,一個字串首先被切分成子詞(subword)詞元。這些詞元的嵌入被輸入到模型中,模型將它們上下文關聯起來以建立高質量的句子表示。在輸出端,你輸入的詞元數量與輸出的嵌入數量相同,所以如果你的輸入句子包含 10 個詞元,你也會得到 10 個輸出詞元。然後,這些詞元透過一個池化機制(pooling mechanism)轉換成一個句子表示,這個機制可以是一個簡單的平均值,也可以是一個特殊的池化模組。
回到 Model2Vec:這個專案最初是作為 sentence transformer 的一種快取機制。因為 transformer 的詞彙表通常只有大約 32k 個詞元,像 astoundingly
這樣的詞會被切分成四個唯一的詞元:'as', '##tou', '##nding', '##ly'
,這意味著每次這個詞出現時,我們都會重新計算這四個詞元之間的注意力。但這個詞的意義可能根本沒有歧義!
然而,當我們開始實現這個想法時,我們注意到其實根本不需要快取任何單詞,只需使用單個詞元的輸出表示就可以獲得良好的句子表示。這正是 Model2Vec 的基本工作模式:對於 sentence transformer 詞彙表中的每個 32k 輸入詞元,我們進行一次前向傳播,然後儲存生成的嵌入。對於一個新句子,我們只需取我們計算出的詞元嵌入的平均值。
請注意,model2vec 模型的輸出詞元表示是非上下文相關的。與普通的 transformer 模型不同,它無法在不同上下文中為同一個詞元賦予不同的含義。雖然這看起來是一個巨大的缺點,但我們認為實際的上下文為模型提供了足夠的消歧潛力。
除了這個技巧,我們還發現需要另外兩個技巧才能獲得最佳效能。
PCA
我們使用主成分分析(PCA)來降低生成的詞元空間的維度。通常,使用 PCA 會導致效能下降,因為你會丟棄資訊。然而,在我們的案例中,降低維度實際上顯著提高了效能。我們認為這是因為 PCA 也對生成的空間進行了歸一化,即消除了原始向量空間中的偏差,從而使從向量中學習變得更容易。
Zipf
由於我們對空間中的詞元取簡單的平均值,因此正確地加權向量非常重要。通常,sentence transformer 會根據上下文為我們正確地加權所有詞元,但我們不再擁有這種便利。直觀上,我們希望使用類似逆文件頻率(IDF)的方法來降低非常頻繁或不重要詞語的權重。但我們無法訪問可以計算文件頻率的語料庫。
為了克服這個問題,我們選擇使用語言科學中一個眾所周知的原理,即在一個按頻率排序的列表中,列表中專案的頻率遵循冪律分佈。這被稱為齊夫定律(Zipf's law)。因此,如果我們假設一個詞彙表是按頻率排序的,我們就可以準確地降低非常頻繁專案的權重,而無需訪問實際頻率。由於分詞器詞彙表是按頻率排序的,我們已經有了一個排序列表,所以這個最佳化可以無需任何額外工作就應用。
用法
Model2Vec 庫有兩種主要的使用模式:**蒸餾** 和 **推理**。在蒸餾模式下,您可以使用任何 Sentence Transformer(以及可選的您自己的詞彙表)來蒸餾自己的模型。在推理模式下,您可以使用蒸餾後的模型(或使用我們預先蒸餾的模型)以極高的速度為您的文字資料生成嵌入。
有三種方法可以蒸餾一個模型
- **輸出(Output)**:行為非常像一個真正的 sentence transformer,即它使用一個子詞分詞器,並簡單地對其詞彙表中的所有詞元進行編碼。這種方法建立速度非常快(在 CPU 上 30 秒),體積非常小(float32 格式下 30 MB),但在某些任務上效能可能稍差。
- **詞彙表(單詞) (Vocab (word))**:在此模式下,您可以傳入自己的詞彙表來建立表示。這使您能夠為您擁有的任何領域內資料建立良好的表示,並且可以作為 GloVe 或 word2vec 的直接替代品。
- **詞彙表(子詞) (Vocab (subword))**:在此模式下,您可以傳入自己的詞彙表,但它也使用子詞詞彙表來建立表示。這使您能夠為您擁有的任何領域內資料建立良好的表示。
請注意,儘管基於詞彙表的模型在 RAM 方面更大,但所有模型的速度都是一樣的,因為我們的模型與詞彙表大小無關。
Model2Vec 嵌入可用於各種應用,例如文字分類、聚類、構建搜尋引擎或 RAG 系統。它們特別適合需要快速、輕量級嵌入且資源需求低的應用。
正如我們接下來將展示的,Model2Vec 非常易於使用。它既可以作為獨立包使用,也可以直接在 Sentence Transformers 中使用。這意味著您可以輕鬆地將其整合到任何支援 Sentence Transformers 的流程中(例如 LangChain 和 LlamaIndex)。您還可以直接使用 Sentence Transformers 訓練 model2vec 模型,保持快速的推理速度,同時直接為您的用例進行最佳化。
如何使用 Model2Vec
安裝
Model2Vec 可以使用 pip 安裝
pip install model2vec
用法
推理
開始使用 Model2Vec 最簡單的方法是從我們的 HuggingFace hub 下載我們的旗艦模型之一。這些模型是預訓練好的,可以直接使用。以下程式碼片段展示瞭如何載入模型並生成嵌入。
from model2vec import StaticModel
# Load a model from the HuggingFace hub (in this case the M2V_base_output model)
model_name = "minishlab/M2V_base_output"
model = StaticModel.from_pretrained(model_name)
# Make embeddings
embeddings = model.encode(["It's dangerous to go alone!", "It's a secret to everybody."])
或者蒸餾您自己的模型並直接使用它們
from model2vec import distill
# Choose a Sentence Transformer model
base_model_name = "BAAI/bge-base-en-v1.5"
# Distill an output model with the chosen dimensions
model = distill(model_name=base_model_name, pca_dims=256)
# Make embeddings
embeddings = model.encode(["supervillain Ganondorf has invaded Hyrule!"])
print(model.tokenizer.encode("supervillain Ganondorf has invaded Hyrule!", add_special_tokens=False).tokens)
# ['super', '##vill', '##ain', 'gan', '##ond', '##orf', 'has', 'invaded', 'h', '##yr', '##ule', '!']
# It looks like we split Ganondorf and Hyrule up into many subtokens
# To solve this, we can add these words to our vocabulary.
vocabulary = ["supervillain", "ganondorf", "hyrule"]
# Distill the model with the custom vocabulary.
model = distill(model_name=base_model_name, vocabulary=vocabulary, pca_dims=256)
print(model.tokenizer.encode("supervillain Ganondorf has invaded Hyrule!", add_special_tokens=False).tokens)
# ['supervillain', 'ganondorf', 'has', 'invaded', 'hyrule', '!']
# Much better.
Model2Vec 也直接在 Sentence Transformers 中得到支援。要在 Sentence Transformers 中使用 Model2Vec,您可以使用 `from_model2vec` 初始化一個 `StaticEmbedding` 類。要在 Sentence Transformers 中直接進行蒸餾,可以使用 `from_distillation` 初始化 `StaticEmbedding` 類。
from sentence_transformers import SentenceTransformer
from sentence_transformers.models import StaticEmbedding
# Initialize a StaticEmbedding module using a pre-trained model
static_embedding = StaticEmbedding.from_model2vec("minishlab/M2V_base_output")
model = SentenceTransformer(modules=[static_embedding])
embeddings = model.encode(["It's dangerous to go alone!", "It's a secret to everybody."])
# Or distill your own directly without leaving sentence-transformers
static_embedding = StaticEmbedding.from_distillation("BAAI/bge-base-en-v1.5", device="cpu", pca_dims=256)
model = SentenceTransformer(modules=[static_embedding])
embeddings = model.encode(["It's dangerous to go alone!", "It's a secret to everybody."])
結果
我們在大量任務和資料集上評估了 Model2Vec。Model2Vec 在 MTEB 以及兩個額外任務上進行了評估:PEARL(一個短語表示任務)和 WordSim(一個詞語相似度任務集合)。結果如下表所示。
模型 | 平均 (全部) | 平均 (MTEB) | 分類 | 聚類 | 對偶分類 | 排序 | 檢索 | STS | 摘要 | Pearl | WordSim |
---|---|---|---|---|---|---|---|---|---|---|---|
all-MiniLM-L6-v2 | 56.08 | 56.09 | 62.62 | 41.94 | 82.37 | 58.04 | 41.95 | 78.90 | 30.81 | 60.83 | 49.91 |
M2V_base_glove_subword | 49.06 | 46.69 | 61.27 | 30.03 | 74.71 | 49.15 | 27.16 | 69.09 | 30.08 | 56.82 | 57.99 |
M2V_base_glove | 48.58 | 47.60 | 61.35 | 30.52 | 75.34 | 48.50 | 29.26 | 70.31 | 31.50 | 50.28 | 54.29 |
M2V_base_output | 46.79 | 45.34 | 61.25 | 25.58 | 74.90 | 47.63 | 26.14 | 68.58 | 29.20 | 54.02 | 49.18 |
GloVe_300d | 42.84 | 42.36 | 57.31 | 27.66 | 72.48 | 43.30 | 22.78 | 61.90 | 28.81 | 45.65 | 43.05 |
BPEmb_50k_300d | 39.34 | 37.78 | 55.76 | 23.35 | 57.86 | 43.21 | 17.50 | 55.10 | 29.74 | 47.56 | 41.28 |
可以看出,Model2Vec 在所有任務上都顯著優於 GloVe 和 BPEmb,甚至在某些任務上超過了速度慢得多的 MiniLM 模型。
此外,我們還在一些不在 MTEB 中的分類資料集上評估了 Model2Vec。我們還用這些資料集來測試模型的速度。結果如下表所示。
模型 | 平均分 | SST2 | IMDB | TREC | AG News |
---|---|---|---|---|---|
bge-base-en-v1.5 | 90.00 | 91.54 | 91.88 | 85.16 | 91.45 |
all-MiniLM-L6-v2 | 84.10 | 83.95 | 81.36 | 81.31 | 89.77 |
M2V_base_output | 82.23 | 80.92 | 84.56 | 75.27 | 88.17 |
M2V_base_glove_subword | 81.95 | 82.84 | 85.96 | 70.51 | 88.49 |
BPEmb_50k_300d | 81.15 | 80.42 | 84.04 | 71.25 | 88.92 |
M2V_base_glove | 80.76 | 83.07 | 85.24 | 66.12 | 88.61 |
GloVe_300d | 77.77 | 81.68 | 84.00 | 55.67 | 89.71 |
再次,Model2Vec 在所有任務上都優於 GloVe 和 BPEmb,甚至表現出與 MiniLM 相似的效能。
下圖顯示了每秒處理的句子數與平均分類得分之間的關係。圓圈的大小對應於模型中的引數數量(越大表示引數越多)。該圖表明,Model2Vec 模型比其他模型快得多,同時在分類效能方面仍能與 all-MiniLM-L6-v2 模型相媲美。
所有分類資料集的平均準確率與每秒處理句子數的對比圖。圓圈大小表示模型大小。
消融研究
為了更好地理解影響 Model2Vec 效能的因素,我們進行了一系列全面的消融研究,涵蓋了模型架構和預處理方法的各個方面。在這些研究中,我們檢驗了 PCA、Zipf 加權以及使用 Sentence Transformers 與常規 transformer 模型等關鍵元素的影響。我們還比較了輸入嵌入與輸出嵌入的效能,因為似乎這些也應該表現良好。結果如下表所示。
模型 | 平均 (全部) | 平均 (MTEB) | 分類 | 聚類 | 對偶分類 | 排序 | 檢索 | STS | 摘要 | Pearl | WordSim |
---|---|---|---|---|---|---|---|---|---|---|---|
M2V_base_output | 46.79 | 45.34 | 61.25 | 25.58 | 74.9 | 47.63 | 26.14 | 68.58 | 29.2 | 54.02 | 49.18 |
M2V_base_output_nopca | 44.04 | 42.31 | 61.42 | 20.15 | 68.21 | 44.67 | 25.25 | 61.87 | 29.85 | 51.02 | 48.96 |
M2V_base_output_nozipf | 43.61 | 41.52 | 60.44 | 21.62 | 72.15 | 45.57 | 20.35 | 62.71 | 30.66 | 52.28 | 49.17 |
M2V_base_input_nozipf_nopca | 40.97 | 39.55 | 54.16 | 18.62 | 68.3 | 43.65 | 23.63 | 59.38 | 32.04 | 50.19 | 40.52 |
M2V_base_output_nozipf_nopca | 40.8 | 38.44 | 59.78 | 19.31 | 62.39 | 42.26 | 19.01 | 55.16 | 30 | 49.09 | 48.97 |
M2V_base_input | 40.74 | 39.93 | 60.35 | 22.66 | 59.63 | 43.02 | 25.47 | 50.05 | 29.35 | 50.61 | 34.47 |
M2V_bert_output_nozipf_nopca | 35.54 | 34.82 | 55.69 | 15.42 | 58.68 | 39.87 | 12.92 | 55.24 | 30.15 | 46.9 | 26.72 |
這些結果中有四個主要發現
- 非 Sentence Transformer 模型效果不佳。這可以透過比較 `M2V_bert_output_nozipf_nopca`(使用BERT,一個非 Sentence Transformer)和 `M2V_base_output_nozipf_nopca`(使用BGE-base,一個 Sentence Transformer)看出。使用 Sentence Transformer 帶來了約 5.2% 的效能提升。
- PCA 對效能至關重要。這可以透過比較 `M2V_base_output_nozipf_nopca` 和 `M2V_base_output_nozipf` 看出,效能提升了約 2.8%。此外,PCA 在*所有*任務上都提高了效能。
- Zipf 加權對效能至關重要。這可以透過比較 `M2V_base_output_nozipf_nopca` 和 `M2V_base_output_nopca` 看出,效能提升了約 3.1%。
- 輸出嵌入優於輸入嵌入。這可以透過比較 `M2V_base_input` 和 `M2V_base_output` 看出,效能提升了約 6.1%。請注意,輸入嵌入在某些任務上確實表現良好。我們推測這是因為輸入嵌入本身是歸一化的。
結論
感謝您閱讀我們關於 Model2Vec 的部落格文章!我們希望您覺得它資訊豐富且有用。如果您有任何問題或意見,請隨時與我們聯絡。我們仍在積極開發該專案,並且已經計劃了許多新功能,敬請期待。
- 💻 程式碼倉庫
- 🤗 HuggingFace 組織
- 🤗 HuggingFace 模型
- 👥 領英
- 📚 教程
引用
@software{minishlab2024word2vec,
authors = {Stephan Tulkens, Thomas van Dongen},
title = {Model2Vec: Turn any Sentence Transformer into a Small Fast Model},
year = {2024},
url = {https://github.com/MinishLab/model2vec},
}
致謝
我們要感謝 Tom Aarsen 將 Model2Vec 整合到 Sentence Transformers 中,並幫助我們進行 HuggingFace 整合,以及他對專案的總體反饋。