構建 LLM 應用:句子 Transformer(第三部分)
作者:Vipra Singh
編譯:ronghuaiyang
導讀
通過檢索增強生成(RAG)應用的視角來學習大型語言模型(LLM)。
在前幾篇博文中,我們學習了面向 RAG 的數據準備,這包括數據攝入、數據預處理及分塊。
由於在執行 RAG 期間需要搜索相關的上下文分塊,我們必須將數據從文本格式轉換爲向量嵌入。
因此,我們將探索使用 Sentence Transformers 來轉換文本的最有效方式。
讓我們從一些最常用的嵌入模型開始。
- 嵌入模型
嵌入是一種單詞表示形式(通過數值向量),使得具有相似意義的單詞具有相似的表示。
這些向量可以通過多種機器學習算法和大型文本數據集來學習。詞嵌入的主要作用之一是爲下游任務(如文本分類和信息檢索)提供輸入特徵。
在過去十年中,已經提出了多種詞嵌入方法,以下是其中的一些:
1.1. 獨立於上下文的嵌入
獨立於上下文的嵌入重新定義了詞表示,爲每個詞分配唯一的向量,而不考慮上下文變化。這裏這個簡要的探討側重於同形異義詞消歧方面。
-
獨立於上下文的模型爲每個詞分配獨特的向量,無論上下文如何變化。
-
像 “duck” 這樣的同形異義詞獲得單一的向量,沒有上下文線索的情況下混合了多種含義。
-
這種方法生成了一個全面的詞向量映射,以固定的表示形式捕獲多個意義。
獨立於上下文的嵌入提供了效率,但在理解語言的細微差別時,尤其是在處理同形異義詞時,也帶來了挑戰。這一範式轉變促使我們更仔細地審視自然語言處理中的權衡。
一些常見的基於頻率的獨立於上下文的嵌入方法包括:
- 詞袋模型(Bag of Words, BoW)
詞袋模型會創建一個詞彙表,包含所有句子中最常見的詞彙,然後按照如下方式對句子進行編碼:
Bag of Words
- TF-IDF
TF-IDF 是一種從句子中尋找特徵的簡單技術。在計數特徵中,我們會統計文檔中出現的所有單詞 / 詞組的數量,而 TF-IDF 只針對重要單詞提取特徵。我們是如何做到這一點的呢?想象語料庫中的一個文檔,對於文檔中的任一單詞,我們會考慮兩個方面:
- Term Frequency: 這個詞在文檔中有多重要?
- Inverse Document Frequency: 該詞在整個語料庫中的重要性如何?
TF-IDF
一些常見的基於預測的、與上下文無關的嵌入方法包括:
- Word2Vec: Word2Vec 中的詞嵌入是通過一個兩層的神經網絡學習得到的,在訓練過程中無意間捕獲了語言上下文信息。這些嵌入作爲算法主要目標的副產品出現,展示了這種方法的高效性。Word2Vec 通過兩種不同的模型架構——CBOW 和連續 skip-gram,提供了靈活性。
連續詞袋模型(CBOW):
-
根據周圍上下文單詞的窗口來預測當前單詞。
-
強調了上下文單詞在預測目標單詞時的協同影響。
連續 Skip-Gram:
-
利用當前單詞來預測周圍的上下文單詞窗口。
-
側重於目標詞在生成上下文詞時的預測能力。
CBOW & Skip-gram
Word2Vec 的雙重模型架構提供了捕捉語言細微差別的多功能性,使得實踐者可以根據自然語言處理任務的具體需求,在 CBOW 和連續 skip-gram 之間做出選擇。理解這兩種架構之間的相互作用增強了 Word2Vec 在多種情境下的應用效果。
Word-2-Vec
-
GloVe(全局向量詞表示): GloVe 的優勢在於其在訓練過程中利用了語料庫中彙總的全局詞 - 詞共現統計信息。由此產生的表示不僅封裝了語義關係,還在詞向量空間中揭示了引人注目的線性子結構,加深了對詞嵌入的理解。
-
FastText: 與 GloVe 不同,FastText 採用了一種新穎的方法,將每個詞視爲由字符 n-gram 組成的。這一獨特特性使 FastText 不僅能學習罕見詞彙,還能巧妙處理詞彙表外的詞彙。對字符級嵌入的重視使 FastText 能夠捕捉形態學上的細微差別,提供了對詞彙更全面的表示。
1.2. 上下文依賴型嵌入
上下文依賴型方法根據同一個詞所處的不同上下文環境,學習不同的詞嵌入表示。
基於 RNN 的方法:
-
ELMO(來自語言模型的嵌入):基於具有字符級編碼層和兩個雙向 LSTM 層的神經語言模型,學習上下文化的詞表示。
-
CoVe(上下文化詞向量):利用爲機器翻譯任務訓練的帶有注意力機制的序列到序列模型中的深層 LSTM 編碼器來爲詞向量賦予上下文信息。
基於 Transformer 的方法:
-
BERT(來自變換器的雙向編碼器表示):基於 Transformer 的語言表示模型,使用大量跨領域的語料庫進行訓練。它應用了掩碼語言模型來預測序列中隨機遮蓋的單詞,並結合下一個句子預測任務來學習句子間的關聯。
-
XLM(跨語言語言模型):通過下一個 token 預測、類似 BERT 的掩碼語言模型目標以及翻譯目標,使用 Transformer 進行預訓練。
-
RoBERTa(穩健優化的 BERT 預訓練方法):在 BERT 的基礎上發展而來,調整了關鍵超參數,移除了下一個句子預訓練目標,並使用更大的小批量尺寸和學習率進行訓練。
-
ALBERT(用於語言表示自我監督學習的輕量 BERT):提出參數縮減技術,以減少內存消耗並加快 BERT 的訓練速度。
- BERT =======
BERT(來自變換器的雙向編碼器表示)是谷歌 AI 開發的自然語言處理領域的一大突破,徹底改變了語言模型的格局。這一深入探討將聚焦於其預訓練方法論及其雙向架構的精妙之處。
-
預訓練: BERT 通過兩項無監督任務進行預訓練——掩碼語言建模(Masked Language Modeling, MLM) 和 下一句預測(Next Sentence Prediction, NSP)。在 MLM 任務中,大約隨機遮蓋輸入中 15% 的詞令牌,目標是僅憑上下文預測被遮蓋詞的原始_詞彙 ID_。
-
除了掩碼語言模型外,BERT 還採用 NSP 任務聯合預訓練文本對的表示。許多重要的下游任務,如問答(Question Answering, QA)和自然語言推理(Natural Language Inference, NLI),都是基於理解兩個句子間的關係,這是單純的語言模型所不能直接捕獲的。
-
預訓練數據: 預訓練過程大體遵循現有的語言模型預訓練文獻。預訓練語料庫包括書籍語料庫(8 億詞)和英文維基百科(25 億詞)。
-
雙向性: 與從左到右的語言模型預訓練不同,MLM 目標使得表示能夠融合左右上下文,從而使我們能夠預訓練一個深度的雙向變換器。
BERT 的架構由多層編碼器組成,每層對輸入應用自注意力並通過到下一層。即使是最小的變體 BERT BASE,也擁有 12 層編碼器、帶有 768 隱藏單元的前饋神經網絡塊和 12 個注意力頭。
2.1. 輸入表示
BERT 接收的輸入序列由句子或句子對(例如,對於問答任務中的 <問題,答案>)組成,這些內容整合成一個 token 序列。
在輸入模型之前,使用詞彙量爲 3 萬的 _WordPiece 分詞器_對輸入序列進行預處理。該分詞器通過將單詞拆分成若干個_子詞(token)_來工作。
特殊 token 包括:
-
[CLS] 作爲每個序列的第一個 token 使用。與這個 token 對應的最終隱藏狀態被用作分類任務中的序列整體表示。
-
[SEP] 句子對被打包成單一序列。我們通過兩種方式區分這些句子。首先,我們用一個特殊 token([SEP]) 將它們分開。其次,我們爲每個 token 添加一個學習到的嵌入,以表明它屬於句子 A 還是句子 B。
-
[PAD] 用於表示輸入句子中的填充部分(空 token)。模型期望輸入的是固定長度的句子。因此,根據數據集設定一個最大長度。較短的句子會被填充,較長的句子則被截斷。爲了明確區分真實 token 和 [PAD]token,我們使用了注意力掩碼。
引入了分割嵌入來指示給定 token 屬於第一個句子還是第二個句子。_位置嵌入_表示句子中 token 的位置。與原始 Transformer 不同,BERT 根據絕對序數位置學習位置嵌入,而不是使用三角函數。
對於給定的 token,其輸入表示是通過將相應的 token 嵌入、分割嵌入和位置嵌入相加構建的。
BERT 的輸入表示, 輸入嵌入是 token 嵌入、分割嵌入和位置嵌入的總和。
句子編碼使用 BERT
爲了獲取 token 嵌入,會在嵌入層使用一個嵌入_查找表_(如上圖所示),其中行代表詞彙表中所有可能的 token 的 ID(例如,3 萬個行),列代表 token 嵌入的大小。
2.2. 爲何選用 Sentence BERT(S-BERT)而非 BERT?
到目前爲止一切順利,但在構建句子向量時,這些 Transformer 模型存在一個問題:Transformer 模型使用的是基於詞或 token 級別的嵌入,而非句子級別的嵌入。
在句子 Transformer 出現之前,使用 BERT 計算句子相似度的方法是採用交叉編碼器結構。這意味着我們將兩個句子傳遞給 BERT,並在 BERT 頂部添加一個分類頭,以此輸出相似度分數。
BERT 交叉編碼器架構包括一個消費句子 A 和 B 的 BERT 模型。兩個句子在同一序列中被處理,中間由 [SEP] 令牌隔開。之後連接一個前饋神經網絡分類器,輸出相似度分數。
然而,這種方法的缺點是計算成本高,因爲它需要對每一對句子進行編碼,這在大型語料庫或實時應用中是不切實際的。於是,Sentence BERT(S-BERT)應運而生,它採用了句向量方法,允許獨立地編碼句子,然後在向量空間中直接比較句子,極大地提高了效率。S-BERT 通過微調 BERT 模型,使其在句子對相似度任務上進行訓練,從而學習到有意義的句子級別嵌入。這樣,每個句子都可以單獨編碼,然後通過計算這些固定長度句向量的餘弦相似度等度量來快速估計句子間的相似性,無需像原始 BERT 那樣對每對句子進行聯合編碼。
在左側展示的是孿生(雙編碼器)架構,而右側則是非孿生(交叉編碼器)架構。兩者的主要區別在於:左側架構中,模型同時接受兩個輸入,它們作爲一個整體被處理以生成表示;而右側的交叉編碼器架構中,兩個輸入是並行處理的,但模型的輸出—即相似度評分—是基於兩個輸入聯合處理的結果,因此輸出彼此依賴。簡而言之,孿生架構允許獨立編碼輸入,然後比較它們的編碼,而交叉編碼器則直接基於聯合輸入給出最終的相似度判斷。
左:交叉編碼器,右:雙編碼器
交叉編碼器網絡確實能產生非常精確的相似度分數(比 SBERT 還好),但它不具備擴展性。如果我們想在一個包含 100K 句子的小數據集中進行相似性搜索,就需要完成 100K 次的交叉編碼器推斷計算。
若要對句子進行聚類,我們必須比較數據集中所有的 100K 條句子,這將導致近 5 億次的比較——這顯然是不現實的。
理想情況下,我們需要預先計算句子向量,這些向量可以被存儲並在需要時使用。 如果這些向量表示良好,我們只需計算每對向量之間的餘弦相似度即可。使用原始的 BERT(以及其他 Transformer 模型),我們可以通過平均 BERT 輸出的所有 token 的嵌入值來構建句子嵌入(如果輸入 512 個 token,就會輸出 512 個嵌入)。【方法 1】
或者,我們可以使用第一個 [CLS]token 的輸出(這是一個 BERT 特有的 token,其輸出嵌入用於分類任務)。【方法 2】
利用這兩種方法之一,我們可以獲得句子嵌入,這些嵌入可以被更快地存儲和比較,將搜索時間從 65 小時縮短到大約 5 秒。然而,準確性並不高,甚至不如使用平均化的 GloVe 嵌入(該方法於 2014 年開發)的表現。這就凸顯了 Sentence BERT(S-BERT)的重要性,它通過微調 BERT 來直接優化句子級別的相似度任務,從而在保持高效的同時顯著提升了準確性。
Sentence BERT (Bi-Encoder)
因此,使用 BERT 從 10,000 個句子中找到最相似的句子對需要 65 小時。而使用 SBERT,創建嵌入僅需約 5 秒鐘,並且通過餘弦相似度進行比較僅需約 0.01 秒。
自 SBERT 論文發表以來,基於訓練原始 SBERT 時採用的類似概念,已經構建了許多其他的句子 Transformer 模型。它們都在許多相似和不相似的句子對上進行訓練。
通過使用如 softmax 損失、多負樣本排序損失或均方誤差邊際損失等損失函數,這些模型被優化以對相似句子產生相似的嵌入,而對不相似的句子則產生不同的嵌入。
提取獨立的句子嵌入是 BERT 面臨的主要問題之一。爲了解決這一問題,開發了 SBERT。
- Sentence Transformers ========================
實際上,關於 SBERT 的描述需要一些澄清。SBERT 實際上並沒有完全摒棄分類頭,而是採用了不同的策略來生成句子嵌入。SBERT 的核心改進在於它通過微調 BERT 模型來優化句子對的相似度任務,這通常涉及到使用_孿生網絡_(siamese)架構進行訓練,而非簡單地去掉分類頭或一次處理一個句子。
在孿生網絡架構中,確實有兩個相同的網絡(基於 BERT)並行運行,它們共享相同的權重。這兩個網絡分別接收一對句子作爲輸入,各自產生句法表示,然後通過特定的損失函數(比如上述提到的 softmax 損失、多負樣本排序損失或 MSE 邊際損失)來優化它們的輸出,以確保相似句子的嵌入更加接近,而不相似句子的嵌入則遠離彼此。最後,SBERT 通常使用平均池化(mean pooling)或其他方法(如 [CLS]token 的輸出)來從這些句法表示中提取句子級別的嵌入。
因此,SBERT 的設計旨在克服原始 BERT 在處理句子級別的任務時的侷限性,通過專門針對句子相似度任務的微調,實現了更高效、更準確的句子表示。
在處理句子對(如句子 A 和句子 B)時,SBERT 模型的工作流程如下:首先,SBERT 模型會分別對兩個句子應用 BERT 模型,得到每個句子的 token 嵌入。這些嵌入是由 512 個 768 維向量組成的(這裏糾正一下,BERT 模型的輸出維度通常是 768,而非 512)。之後,爲了從這些豐富的 token 級別信息中提煉出句子級別的表示,我們會使用一個池化函數(如平均池化 mean pooling、最大池化 max pooling 或 [CLS]token 策略等)來壓縮這些數據,將其轉換成單個 768 維的句子向量。這樣,每個句子就被編碼成了一個高維的向量,這些向量可以直接用於計算句子間的相似度,如通過計算餘弦相似度來衡量兩個句子的語義接近程度。
實際上,SBERT 確實基於單個 BERT 模型進行操作。在訓練過程中,儘管我們是依次處理句子 A 和句子 B 作爲一對進行的,但這並不意味着存在兩個物理分離的模型實體,而是同一模型在不同時間點分別處理兩個句子,且該模型的所有參數(權重)在處理這對句子時保持一致。因此,提及 “兩個模型共享相同權重” 是一種簡化的表述方式,幫助理解在處理句子對時,SBERT 確保了模型對句子的編碼方式具有一致性和可比性,實質上是在利用 BERT 框架實現一種孿生網絡(Siamese Network)的訓練策略,從而優化句子間的相似度判斷。
3.1. 孿生 BERT 預訓練
在訓練句子 Transformer 時,存在多種方法。我們將描述最初 SBERT 論文中突出介紹的、基於 _softmax 損失_進行優化的原始過程。
softmax 損失方法採用 “孿生(siamese)” 架構,並在斯坦福自然語言推理(SNLI)和多體裁自然語言推理(MNLI)語料庫上進行微調。
SNLI 包含 57 萬個句子對,而 MNLI 包含 43 萬個。這兩個語料庫中的配對都包括一個前提和一個假設。每對配對被分配以下三個標籤之一:
-
0 — 蘊含,即前提暗示假設。
-
1 — 中立,前提和假設都可能是真的,但它們不一定相關。
-
2 — 矛盾,前提和假設相互矛盾。
基於此數據,我們將句子 A(假設爲前提)輸入到孿生 BERT A 中,將句子 B(假設)輸入到孿生 BERT B 中。
孿生 BERT 輸出經過池化的句子嵌入。SBERT 論文中測試了三種不同的池化方法,分別是_**平均(mean)**_、_**最大(max)**_和基於 ***[CLS]* __標記的池化。對於 NLI 和 STSb 數據集,_平均池化_表現最佳。
現在我們得到了兩個句子嵌入,我們將嵌入 A 稱爲 u,嵌入 B 稱爲 v。下一步是拼接 u 和 v。儘管測試了多種拼接方法,但表現最優的是採用**(u, v, |u-v|)**的操作,即將兩個嵌入自身及其差的絕對值串聯起來。這種方式能夠更好地捕捉句子間的相對關係,有助於模型在區分蘊含、中立和矛盾關係時更爲精確。
計算 | u-v| 是爲了得到兩個向量間逐元素的差異。這個差異與原始的兩個嵌入向量 u 和 v 一起,被輸入到一個具有_三個_輸出的前饋神經網絡(FFNN)中。
這三個輸出對應於我們 NLI 相似度標籤 0(蘊含)、1(中立)和 2(矛盾)。我們需要基於 FFNN 的輸出計算 softmax 概率,這通常在_交叉熵損失函數_的計算過程中完成。softmax 概率與標籤一起,用於依據 _softmax 損失_進行優化。
這些操作在訓練期間應用於兩個句子嵌入,即 u 和 v。需要注意的是,softmax-loss 實際上指的是交叉熵損失(該損失默認包含 softmax 函數)。
這導致對於標註爲 0(相似)的句子,其池化後的句子嵌入變得更加相似;而對於標註爲 2(不相似)的句子,嵌入則變得不那麼相似。
記住我們使用的是_孿生(siamese)_BERT,而非兩個獨立的 BERT(dual BERTs)。這意味着我們並非使用兩個獨立的 BERT 模型,而是用同一個 BERT 模型依次處理句子 A 和句子 B。
這意味着,當我們優化模型權重時,它們會被推向這樣一個方向:在識別到_蘊含_標籤的情況下,模型輸出的向量更加相似;而在識別到_矛盾_標籤時,輸出的向量則更加不相似。
- SBERT 的目標函數 ==============
通過使用這兩個向量 u 和 v,以下是針對不同目標優化的三種方法討論:
4.1. 分類
將這三個向量 u、v 和 | u-v| 拼接起來,乘以一個可訓練的權重矩陣 W,其乘積結果輸入到 softmax 分類器中,該分類器輸出各句子對應不同類別的歸一化概率。使用交叉熵損失函數來更新模型的權重。
SBERT 用於分類目標的架構。參數 n 表示嵌入的維度(對於 BERT 基礎模型,默認爲 768),而 k 表示標籤的數量。
4.2. 迴歸
在這種形式下,獲取向量 u 和 v 後,直接通過選定的相似度指標計算它們之間的相似度得分。預測的相似度得分與真實值進行比較,並使用均方誤差(MSE)損失函數來更新模型。
SBERT 用於迴歸目標的架構。參數 n 表示嵌入的維度(對於 BERT 基礎模型,默認爲 768)。
4.3. 三元組損失
三元組目標引入了三元組損失,該損失基於三個句子計算,通常稱爲_錨點_、_正例_和_負例_。假設_錨點_和_正例_句子彼此非常接近,而_錨點_和_負例_則差異很大。在訓練過程中,模型評估 (錨點,正例) _對相比_ (錨點,負例) 對有多接近。
Triplet SBERT 結構
接下來,讓我們看看如何初始化和使用這些句子 Transformer 模型。
- 上手使用 Sentence Transformers =============================
開始使用句子轉換器最快捷簡便的方式是通過 SBERT 的創建者提供的 sentence-transformers 庫。我們可以通過 pip 命令安裝它。
!pip install sentence-transformers
我們將從原始的 SBERT 模型 bert-base-nli-mean-tokens 開始。首先,我們下載並初始化該模型。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('bert-base-nli-mean-tokens')
model
Output:
SentenceTransformer(
(0): Transformer({'max_seq_length': 128, 'do_lower_case': False}) with Transformer model: BertModel
(1): Pooling({'word_embedding_dimension': 768, 'pooling_mode_cls_token': False, 'pooling_mode_mean_tokens': True, 'pooling_mode_max_tokens': False, 'pooling_mode_mean_sqrt_len_tokens': False})
)
這裏展示的輸出是一個 SentenceTransformer 對象,它包含_三個_主要組件:
-
Transformer 本身,這裏顯示了最大序列長度爲 128 個 token,以及是否將輸入轉換爲小寫(本例中,模型_不_進行小寫轉換)。我們還可以看到模型類別爲 BertModel。
-
**池化(Pooling)**操作,這裏顯示我們正在生成一個 768 維的句子嵌入,採用的是_平均池化(mean pooling)_方法。
一旦我們有了模型,就可以通過使用 encode 方法快速生成句子嵌入。
sentences = [
"the fifty mannequin heads floating in the pool kind of freaked them out",
"she swore she just saw her sushi move",
"he embraced his new life as an eggplant",
"my dentist tells me that chewing bricks is very bad for your teeth",
"the dental specialist recommended an immediate stop to flossing with construction materials"
]
embeddings = model.encode(sentences)
embeddings.shape
Output:
(5, 768)
- 選擇哪種嵌入模型? ============
然而,我們很快會發現,當前多數採用的嵌入模型屬於 Transformer 類別。這些模型由不同的供應商提供,有些是開源的,有些則是專有的,每一款都針對特定目標進行了優化:
-
有些特別適合編碼任務。
-
另一些則專門爲英語設計。
-
還有能出色處理多語言數據集的嵌入模型。
最直接的方法是利用現有的學術基準。但重要的是要意識到,這些基準可能無法全面反映出 AI 應用中檢索系統的真實世界應用場景。
作爲替代方案,你可以嘗試多種嵌入模型,並編制最終的評估表,以確定最適合你特定用例的模型。我強烈建議在此過程中加入重排序器(re-ranker),因爲它能顯著提升檢索器的性能,最終達到最佳效果。
爲了簡化決策過程,Hugging Face 提供了出色的大規模文本嵌入基準(Massive Text Embedding Benchmark, MTEB)排行榜。這個資源全面展示了所有可用嵌入模型及其在不同指標上的得分。
HuggingFace MTEB
如果你選擇第二種方法,這裏有一篇優秀的 Medium 博客文章,展示瞭如何利用 LlamaIndex 中的檢索評估模塊。這個資源能幫助你高效地評估並從初始模型列表中識別出最優的嵌入模型及重排序器組合。通過實踐指南和案例分析,文章指導用戶如何設置實驗、收集性能指標,並最終挑選出最適合特定應用場景的模型配置,從而提升檢索和問答系統的整體效能。
我確信你現在更有信心爲您的 RAG 架構選擇最合適的嵌入及重新排序模型了!
結論
本文綜述了多種用於生成文本向量表示的嵌入模型,涵蓋了從詞袋模型、TF-IDF、Word2Vec、GloVe、FastText 到 ELMO、BERT 等。深入剖析了 BERT 的架構及預訓練方法,介紹了 SBERT 在高效生成句子嵌入方面的應用,並通過 sentence-transformers 庫的實例操作加以說明。結論部分突出了選取合適嵌入模型的挑戰,並推薦利用諸如 Hugging Face 的 “大規模文本嵌入基準(MTEB)排行榜” 等資源進行評估,以便更科學地作出決策。
英文原文:https://medium.com/@vipra_singh/building-llm-applications-sentence-transformers-part-3-a9e2529f99c1
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ksz1nM5A0NmTrj7VHEmbFA