域Embedding訓(xùn)練實(shí)戰(zhàn)與踩坑復(fù)盤)
在垂類模型的數(shù)據(jù)工程里前面幾個(gè)階段我們基本都在跟數(shù)據(jù)和標(biāo)注較勁真正把“語(yǔ)義”這個(gè)事落地還是得到 Embedding 訓(xùn)練這一步。這一階段討論的是怎么把一套領(lǐng)域相關(guān)的文本表示從通用模型手里接過(guò)來(lái)讓它能區(qū)分專業(yè)場(chǎng)景里那些微妙差異我的選擇是用雙編碼器架構(gòu)配合 InfoNCE 損失函數(shù)來(lái)做整套訓(xùn)練閉環(huán)。這篇文章記錄的就是這一個(gè)階段從架構(gòu)選型、數(shù)據(jù)構(gòu)造到實(shí)際訓(xùn)練和踩坑的完整流程主要面向正在做垂類檢索、召回或者語(yǔ)義匹配的工程同學(xué)也適合準(zhǔn)備把對(duì)比學(xué)習(xí)用在自己領(lǐng)域數(shù)據(jù)上的研究者參考。我會(huì)把原理和實(shí)操放在一起講有些結(jié)論看起來(lái)反直覺(jué)但跑完一輪實(shí)驗(yàn)基本都能對(duì)上。1. 為什么垂類模型不能直接吃通用Embedding1.1 通用向量的“領(lǐng)域失明”問(wèn)題先說(shuō)我遇到的一個(gè)真實(shí)場(chǎng)景。早期為了快速出效果我直接拿來(lái)一個(gè)在大規(guī)模通用語(yǔ)料上預(yù)訓(xùn)練好的文本向量模型用來(lái)做某個(gè)細(xì)分行業(yè)的單據(jù)檢索。日常運(yùn)營(yíng)人員的提問(wèn)都是“這個(gè)單子能不能在臺(tái)賬里查到”標(biāo)準(zhǔn)單據(jù)名稱是像“某類設(shè)備維修記錄單”這種規(guī)范文本聽(tīng)起來(lái)好像沒(méi)什么但實(shí)際跑出來(lái)的 top10 檢索結(jié)果非常失敗。失敗不在于模型本身能力差而在于通用模型訓(xùn)練語(yǔ)料里這種垂直場(chǎng)景的數(shù)據(jù)占比太低。模型對(duì)“維修”、“單”這些詞的理解偏向日常生活對(duì)領(lǐng)域內(nèi)的同義改寫、簡(jiǎn)寫和編號(hào)規(guī)則幾乎無(wú)感。同一個(gè)意思用戶輸入和庫(kù)里的正式名稱字面完全不像但語(yǔ)義上應(yīng)該匹配。通用向量的語(yǔ)義空間是按開(kāi)放域問(wèn)題訓(xùn)練出來(lái)的它不知道“質(zhì)保期”在這個(gè)行業(yè)里等同于“三包有效期”這種專有映射。這種問(wèn)題在開(kāi)放域搜索里可能不明顯因?yàn)橥ㄓ谜Z(yǔ)料本身覆蓋了各種問(wèn)法。但一到垂類場(chǎng)景領(lǐng)域詞表是封閉的、句式是固定的、標(biāo)準(zhǔn)答案是明確的通用模型分配在“常見(jiàn)語(yǔ)義”上的容量就對(duì)不上了。所以做垂類模型數(shù)據(jù)工程到最后真正拉開(kāi)差距的不是模型結(jié)構(gòu)多新而是 Embedding 有沒(méi)有被領(lǐng)域數(shù)據(jù)重新校準(zhǔn)過(guò)。1.2 用“語(yǔ)義空間偏移”理解垂類訓(xùn)練的目標(biāo)如果從訓(xùn)練目標(biāo)看垂類 Embedding 要做的事只有一件在這個(gè)封閉領(lǐng)域里重新度量“相似”。通用模型給每個(gè)文本一個(gè)向量這個(gè)向量在通用語(yǔ)義空間里是合理的但在垂類語(yǔ)義空間里不是。比如“卡”這個(gè)詞用在支付行業(yè)和用在機(jī)械行業(yè)代表完全不同的實(shí)體。想讓模型理解這一點(diǎn)只能讓它看足量的領(lǐng)域內(nèi)正負(fù)樣本通過(guò)損失函數(shù)把向量空間里的距離關(guān)系掰過(guò)來(lái)。我把這個(gè)階段看作數(shù)據(jù)工程鏈條中“從量到質(zhì)”的轉(zhuǎn)換前幾個(gè)階段解決的是數(shù)據(jù)有沒(méi)有、干不干凈Embedding 訓(xùn)練解決的是用這些數(shù)據(jù)能不能形成有效的度量。你需要先接受一個(gè)前提通用 Embedding 可以作為初始權(quán)重但絕不應(yīng)該作為終態(tài)。如果一開(kāi)始就抱著“通用模型反正能跑湊合用吧”的心態(tài)后續(xù)所有召回優(yōu)化都是在有瑕疵的地基上打補(bǔ)丁。1.3 先做一輪低成本語(yǔ)義體檢在進(jìn)入訓(xùn)練之前建議先花半天時(shí)間做一次便宜的體檢也是我每個(gè)垂類項(xiàng)目都會(huì)重復(fù)的動(dòng)作準(zhǔn)備大概 50 條領(lǐng)域內(nèi)的真實(shí) query以及一個(gè)對(duì)應(yīng)的候選文檔池用通用 Embedding 做 recall10再把結(jié)果人工瞄一遍。不用做完整評(píng)估只要看 top10 里有多少是“類型對(duì)得上但內(nèi)容不對(duì)”或者“字面相關(guān)但語(yǔ)義不相關(guān)”。我見(jiàn)過(guò)的絕大多數(shù)垂類項(xiàng)目這一步之后都會(huì)得到同一個(gè)結(jié)論通用向量在領(lǐng)域內(nèi)會(huì)把字面相似性和語(yǔ)義相關(guān)性嚴(yán)重混淆。典型的例子是搜索“設(shè)備故障”和標(biāo)準(zhǔn)文檔里“機(jī)器運(yùn)轉(zhuǎn)異常處理規(guī)范”字面完全不重疊但語(yǔ)義應(yīng)該相關(guān)而通用模型往往排序很低。體檢的價(jià)值在于給你一個(gè)量化的基線。后面訓(xùn)練完再測(cè)同一個(gè)體檢集提升多少一目了然。這一階段的所有工作本質(zhì)上都是在跟這個(gè)基線作對(duì)比。2. 雙編碼器架構(gòu)拆解從結(jié)構(gòu)選擇到部署約束2.1 為什么是“兩個(gè)塔”而不是一個(gè)交叉編碼器垂類語(yǔ)義匹配的技術(shù)棧里有兩個(gè)常用套路交叉編碼器和雙編碼器。交叉編碼器把 query 和文檔拼成一個(gè)序列用一個(gè)模型做完整交互效果好但慢得離譜因?yàn)樗枰獮槊總€(gè)候選對(duì)跑一次前向。雙編碼器把 query 和文檔分別編碼成兩個(gè)向量之后只需要算向量相似度可以用近似最近鄰檢索做大規(guī)模召回。我選雙編碼器核心原因是生產(chǎn)環(huán)境約束。線上一個(gè) query 打到幾百上千萬(wàn)的候選庫(kù)如果每條都過(guò)交叉編碼器響應(yīng)時(shí)間直接爆炸。雙塔結(jié)構(gòu)等于把重計(jì)算放在離線階段文檔塔可以提前把庫(kù)里所有文檔變成向量存進(jìn)索引線上只需要編碼一次 query然后做一個(gè)向量檢索。這種“離線重、在線輕”的結(jié)構(gòu)才是支撐高性能召回的唯一現(xiàn)實(shí)選擇。很多剛接觸的人會(huì)覺(jué)得雙塔丟失了交互信息不如交叉編碼器。這個(gè)批評(píng)沒(méi)有錯(cuò)但要看場(chǎng)景。垂類搜索里的候選集往往很大需要的是先快速召回一個(gè)高質(zhì)量的子集語(yǔ)義精排再交給交叉編碼器這類重模型。雙塔負(fù)責(zé)“大海撈針”交叉編碼器負(fù)責(zé)“針尖對(duì)麥芒”兩者是配合關(guān)系不是替代關(guān)系。2.2 雙塔內(nèi)部的三個(gè)關(guān)鍵設(shè)計(jì)點(diǎn)第一個(gè)設(shè)計(jì)點(diǎn)是兩個(gè)塔是否共享參數(shù)。共享參數(shù)的好處是參數(shù)少、訓(xùn)練穩(wěn)定適合 query 和文檔文本分布比較接近的場(chǎng)景比如檢索式問(wèn)答。不共享參數(shù)的好處是每個(gè)塔可以適應(yīng)不同文本風(fēng)格適合 query 普遍短小、文檔普遍長(zhǎng)且結(jié)構(gòu)化的垂類場(chǎng)景。我在大多數(shù)垂類項(xiàng)目里選擇兩個(gè)塔結(jié)構(gòu)相同但權(quán)重獨(dú)立因?yàn)?query 側(cè)的用詞和文檔側(cè)的規(guī)范術(shù)語(yǔ)分布差異太大了。第二個(gè)設(shè)計(jì)點(diǎn)是序列編碼的池化方式。文本經(jīng)過(guò)編碼器后得到一串 token 向量通常有幾種做法把它壓成一個(gè)句子向量。最粗糙的是用 [CLS] 位好一點(diǎn)的是對(duì)所有 token 做均值池化更好一點(diǎn)的是按 token 的權(quán)重加權(quán)求和。垂類文檔往往很長(zhǎng)存在大量與核心語(yǔ)義無(wú)關(guān)的章節(jié)比如模板里的“注意事項(xiàng)”“參考文件”這種噪聲對(duì)均值池化傷害很大。我會(huì)在文檔塔上嘗試加權(quán)池化權(quán)重來(lái)自一個(gè)簡(jiǎn)單訓(xùn)練的領(lǐng)域詞表映射效果往往比默認(rèn)池化高出不少。第三個(gè)設(shè)計(jì)點(diǎn)是向量歸一化。雙塔輸出的向量在算點(diǎn)積之前一定要做 L2 歸一化讓所有向量都落在單位球面上這樣點(diǎn)積等價(jià)于余弦相似度語(yǔ)義可比性才穩(wěn)定。如果不做歸一化向量模長(zhǎng)會(huì)參與相似度計(jì)算導(dǎo)致長(zhǎng)文檔天然占便宜排序結(jié)果會(huì)失真。這個(gè)細(xì)節(jié)看似小但對(duì)后續(xù)難負(fù)樣本挖掘和閾值設(shè)定影響巨大我見(jiàn)過(guò)好幾個(gè)項(xiàng)目因?yàn)槁┑暨@一步調(diào)了很久溫度參數(shù)都調(diào)不對(duì)。2.3 底層模型怎么選不要盲目追求大垂類場(chǎng)景的文本通常有個(gè)特點(diǎn)領(lǐng)域內(nèi)專用詞占比高、句式重復(fù)性強(qiáng)、單條文本長(zhǎng)度波動(dòng)大。底層預(yù)訓(xùn)練模型的選擇上我的建議是先用中等規(guī)模的通用模型初始化再把領(lǐng)域語(yǔ)料繼續(xù)做少量預(yù)訓(xùn)練。這個(gè)操作成本不高但收益穩(wěn)定尤其是那些領(lǐng)域術(shù)語(yǔ)跟通用詞義沖突大的場(chǎng)景比如法律、醫(yī)療、工業(yè)制造。大模型不一定更優(yōu)。模型越大訓(xùn)練和線上推理成本越高而且垂類數(shù)據(jù)量通常有限大模型容易過(guò)擬合到少部分高頻 pattern 上。雙塔訓(xùn)練的核心是讓兩個(gè) encoder 學(xué)到“好匹配”的判別邊界而不是讓它們變得更加博學(xué)。領(lǐng)域知識(shí)由預(yù)訓(xùn)練階段負(fù)責(zé)匹配能力由雙塔訓(xùn)練負(fù)責(zé)這個(gè)分工想清楚之后模型選型就不會(huì)被參數(shù)規(guī)模帶跑。3. InfoNCE 損失函數(shù)一個(gè)公式怎么讓兩個(gè)塔“學(xué)會(huì)對(duì)齊”3.1 從實(shí)例到公式InfoNCE 到底在算什么雙塔架構(gòu)定下來(lái)后核心問(wèn)題就是怎么定義“拉近正樣本、推開(kāi)負(fù)樣本”。我最早用的是三元組損失但很快就換成了 InfoNCE因?yàn)?InfoNCE 在訓(xùn)練穩(wěn)定性、負(fù)樣本利用率和最終效果上都明顯更好。它本質(zhì)上在做一件很簡(jiǎn)單的事給定一個(gè) batch 里的 query把當(dāng)前 batch 里唯一的正樣本從所有樣本中識(shí)別出來(lái)。如果用一句話說(shuō)清楚 InfoNCE 的思路那就是把匹配問(wèn)題變成分類問(wèn)題每個(gè) query 有一個(gè)正確答案其余 batch 內(nèi)的文檔都是錯(cuò)誤答案讓模型學(xué)會(huì)從一堆候選中挑出那個(gè)正確的。寫成公式就是這個(gè)樣子[ L_{InfoNCE} -\log \frac{\exp(\text{sim}(q,p^)/\tau)}{\exp(\text{sim}(q,p^)/\tau) \sum_{i1}^{N-1} \exp(\text{sim}(q,p_i^-)/\tau)} ]其中 sim 是余弦相似度τ 是溫度系數(shù)p^ 是正樣本p_i^- 是負(fù)樣本。這個(gè)公式里面有一個(gè)關(guān)鍵設(shè)計(jì)分母中的負(fù)樣本數(shù)量越多模型需要區(qū)分的類別就越多學(xué)到的語(yǔ)義邊界就越細(xì)。這一特點(diǎn)正好匹配垂類檢索的訴求——因?yàn)檎鎸?shí)召回面對(duì)候選集是百萬(wàn)級(jí)的訓(xùn)練時(shí)區(qū)分幾十個(gè)負(fù)樣本只是最小成本的模擬。建議訓(xùn)練中用大 batch 來(lái)增加負(fù)樣本數(shù)量這也是 InfoNCE 的核心收益之一。batch 里每個(gè) query 對(duì)應(yīng)的正文檔對(duì)其他 query 來(lái)說(shuō)恰好就是負(fù)樣本這一機(jī)制下產(chǎn)生的“batch 內(nèi)負(fù)樣本”數(shù)量等于 batch size 減一完全不需要額外構(gòu)造和存儲(chǔ)訓(xùn)練效率非常高。后續(xù)講數(shù)據(jù)構(gòu)造時(shí)會(huì)再展開(kāi)。3.2 溫度系數(shù) τ 的直覺(jué)理解與調(diào)參影響InfoNCE 公式里的溫度系數(shù) τ 是最容易被忽略、但影響最大的超參數(shù)。它的作用相當(dāng)于給相似度打分做縮放分?jǐn)?shù)除以一個(gè)更小的 τ會(huì)讓相似度分布的對(duì)比度變大相當(dāng)于把模型的注意力集中到那些最難區(qū)分的高相似度負(fù)樣本上τ 太大則會(huì)讓所有分?jǐn)?shù)的差異被抹平模型學(xué)不動(dòng)τ 太小又會(huì)讓模型對(duì)噪聲極度敏感一點(diǎn)擾動(dòng)就會(huì)被放大。我的經(jīng)驗(yàn)是τ 一般初始設(shè)在 0.05 左右再根據(jù)訓(xùn)練集難度微調(diào)。判斷標(biāo)準(zhǔn)很簡(jiǎn)單如果訓(xùn)練一開(kāi)始 loss 掉不下去檢查 τ如果 loss 掉得飛快但評(píng)估指標(biāo)沒(méi)有同步提升很可能 τ 太小模型在死記硬背區(qū)分訓(xùn)練樣本而不是泛化出了語(yǔ)義邊界。比如之前在一個(gè)保險(xiǎn)場(chǎng)景里我把 τ 從 0.05 調(diào)小到 0.02訓(xùn)練 loss 降得明顯更快但 recall5 反而掉了調(diào)回 0.04 效果最好。這里有個(gè)實(shí)操細(xì)節(jié)值得注意如果向量沒(méi)有做 L2 歸一化τ 的實(shí)際效果會(huì)失控。因?yàn)橛嘞蚁嗨贫缺旧淼娜≈捣秶幌拗圃?[-1, 1]除以一個(gè)很小的 τ 前還說(shuō)得過(guò)去如果向量沒(méi)有歸一化點(diǎn)積的范圍可能是 [-10, 10] 甚至更大同一個(gè) τ 就會(huì)產(chǎn)生截然不同的梯度尺度。所以“先歸一化再調(diào)溫度”是雙塔訓(xùn)練里絕對(duì)不能打破的順序。3.3 InfoNCE 與三元組損失的對(duì)比我看到很多項(xiàng)目還在用三元組損失也不是完全不行但有幾個(gè)坑很難繞開(kāi)。首先三元組損失需要顯式構(gòu)造 anchor、positive、negative 三元組負(fù)樣本的難度全部依賴人工挖掘策略而 InfoNCE 天然利用了 batch 內(nèi)全部負(fù)樣本不需要額外設(shè)計(jì)。其次三元組損失只有一個(gè)固定 margin遇到簡(jiǎn)單負(fù)樣本和困難負(fù)樣本距離相差很大時(shí)梯度容易被簡(jiǎn)單樣本主導(dǎo)。InfoNCE 的另一個(gè)優(yōu)勢(shì)是它與 softmax 分類天然同構(gòu)。從公式里可以看出它就是帶溫度的 softmax 交叉熵?fù)p失每個(gè) query 被要求預(yù)測(cè)自己對(duì)應(yīng)的正樣本。這讓整個(gè)訓(xùn)練過(guò)程非常穩(wěn)定因?yàn)樘荻扔忻鞔_的“分類不確定性”作為校準(zhǔn)信號(hào)不會(huì)像 margin loss 那樣經(jīng)常出現(xiàn)負(fù)樣本離得太遠(yuǎn)或太近導(dǎo)致的波動(dòng)。我之前做過(guò)一個(gè)對(duì)比實(shí)驗(yàn)同樣數(shù)據(jù)一份用三元組損失一份用 InfoNCE訓(xùn)練同樣的步數(shù)后者的 recall10 高出 6 個(gè)百分點(diǎn)左右。而且三元組損失需要更多小 trick 才能訓(xùn)起來(lái)比如動(dòng)態(tài)難樣本挖掘、margin 自適應(yīng)InfoNCE 則只需要關(guān)注數(shù)據(jù)和溫度省心太多。除非你有特殊的場(chǎng)景需求否則 InfoNCE 是我在這個(gè)階段的首選。4. 垂類訓(xùn)練數(shù)據(jù)構(gòu)建正樣本、負(fù)樣本與難負(fù)樣本的分寸4.1 正樣本從哪里來(lái)日志、規(guī)則與人工標(biāo)注的配合Embedding 訓(xùn)練需要成對(duì)的 query 和正樣本文檔。在垂類項(xiàng)目里正樣本來(lái)源通常有三種一是用戶檢索點(diǎn)擊日志用戶搜完之后點(diǎn)開(kāi)了哪條文檔這是一個(gè)弱標(biāo)注的正樣本二是基于規(guī)則和領(lǐng)域知識(shí)構(gòu)造的規(guī)范匹配對(duì)比如“報(bào)銷憑證”和“費(fèi)用結(jié)算證明”就是人工規(guī)則認(rèn)定的同義對(duì)三是人工標(biāo)注數(shù)量少但質(zhì)量最高用來(lái)做種子集。我的做法是把這三種來(lái)源結(jié)合先用日志數(shù)據(jù)跑一個(gè)召回模型從日志里取高置信度的點(diǎn)擊對(duì)作為自動(dòng)正樣本再用規(guī)則構(gòu)造一批同義改寫對(duì)補(bǔ)充長(zhǎng)尾表達(dá)最后請(qǐng)領(lǐng)域同學(xué)標(biāo)幾百條最難的語(yǔ)義對(duì)重點(diǎn)關(guān)注那些通用模型容易混淆的邊界情況。日志數(shù)據(jù)噪聲很大用戶點(diǎn)開(kāi)文檔不一定代表內(nèi)容匹配需要用停留時(shí)長(zhǎng)和后續(xù)行為做置信度過(guò)濾否則模型會(huì)被噪聲帶偏。關(guān)于正樣本數(shù)量很多人會(huì)問(wèn)是不是越多越好。我的經(jīng)驗(yàn)是垂類模型對(duì)正樣本的質(zhì)量敏感度遠(yuǎn)高于數(shù)量敏感度。幾百條高質(zhì)量種子對(duì)配合幾千條相對(duì)可信的日志對(duì)就能訓(xùn)練出一個(gè)明顯優(yōu)于通用 Embedding 的模型。如果日志數(shù)據(jù)本身有系統(tǒng)性偏向比如只覆蓋熱門類型冷門類型完全沒(méi)有點(diǎn)擊那再多數(shù)據(jù)也救不了冷門類的召回。4.2 負(fù)樣本的層次化設(shè)計(jì)隨機(jī)負(fù)樣本與難負(fù)樣本負(fù)樣本的重要性經(jīng)常被低估。隨機(jī)負(fù)樣本簡(jiǎn)單粗暴從候選庫(kù)里隨機(jī)取一段文本跟 query 大概率不相關(guān)這保證了模型能區(qū)分基礎(chǔ)的正負(fù)語(yǔ)義。但如果只用隨機(jī)負(fù)樣本模型會(huì)變成“字面匹配器”只要字面重疊高就認(rèn)為相關(guān)這對(duì)垂類搜索是致命的。大部分垂類場(chǎng)景里真正難的是區(qū)分那些看起來(lái)很像但語(yǔ)義不同的樣本。比如“設(shè)備安裝”和“設(shè)備調(diào)試”在同一個(gè)項(xiàng)目流程里可能都出現(xiàn)但用戶搜“安裝”時(shí)“調(diào)試規(guī)范”就不應(yīng)該排前面。這類難負(fù)樣本需要主動(dòng)挖掘用當(dāng)前模型跑一遍訓(xùn)練集把每個(gè) query 召回排名靠前但不相關(guān)的文檔撈出來(lái)作為難負(fù)樣本混入訓(xùn)練數(shù)據(jù)下一輪模型就會(huì)對(duì)這類樣本更敏感。難負(fù)樣本的加入量需要控制分寸。我在實(shí)踐中一般把負(fù)樣本池設(shè)計(jì)成三部分隨機(jī)負(fù)樣本占 60%難負(fù)樣本占 30%領(lǐng)域內(nèi)規(guī)則構(gòu)造的“相似但不同”樣本占 10%。難負(fù)樣本比例如果太高訓(xùn)練初期模型還沒(méi)有區(qū)分能力會(huì)把大量難負(fù)樣本當(dāng)成正樣本的近鄰導(dǎo)致?lián)p失波動(dòng)劇烈。先訓(xùn)幾輪讓模型把基礎(chǔ)語(yǔ)義學(xué)扎實(shí)再逐步提高難負(fù)樣本比例效果會(huì)更穩(wěn)定。4.3 數(shù)據(jù)清洗與去重防止數(shù)據(jù)泄漏和“病態(tài)捷徑”訓(xùn)練數(shù)據(jù)構(gòu)造中還有一個(gè)很容易翻車的點(diǎn)數(shù)據(jù)泄漏。如果同一條文檔既出現(xiàn)在訓(xùn)練集里又出現(xiàn)在測(cè)試集/驗(yàn)證集里召回指標(biāo)會(huì)被嚴(yán)重高估。垂類數(shù)據(jù)總量小這個(gè)問(wèn)題尤其容易出現(xiàn)。我在每個(gè)項(xiàng)目里都會(huì)做嚴(yán)格去重不只是整段去重還要做改寫后去重因?yàn)楹芏辔臋n只是換了標(biāo)題正文基本一致。另一個(gè)容易被忽略的問(wèn)題是“病態(tài)捷徑”。模型很聰明會(huì)去找數(shù)據(jù)里最省力的判別信號(hào)。如果所有正樣本文檔都集中在某種格式比如都帶“規(guī)范”兩個(gè)字模型可能學(xué)會(huì)只看“規(guī)范”這個(gè) token 就判相關(guān)而不是理解真正的語(yǔ)義關(guān)系。這種模型在測(cè)試集上表現(xiàn)尚可一上線遇到格式不同的真實(shí)文檔就崩。避免病態(tài)捷徑的辦法有兩個(gè)一是讓正負(fù)樣本在格式特征上盡量同分布保證表面特征不是有效區(qū)分信號(hào)二是在訓(xùn)練數(shù)據(jù)里加入大量對(duì)抗性噪聲樣本專門把那些“表面很像、語(yǔ)義不同”的例子做成負(fù)樣本逼模型放棄表面匹配策略。這兩個(gè)操作聽(tīng)起來(lái)簡(jiǎn)單但實(shí)戰(zhàn)中需要反復(fù)看 bad case 才能找到模型的“偷懶點(diǎn)”。4.4 數(shù)據(jù)規(guī)模與冷啟動(dòng)確實(shí)沒(méi)有數(shù)據(jù)時(shí)怎么辦垂類項(xiàng)目最尷尬的情況是冷啟動(dòng)連日志都沒(méi)有人工標(biāo)注也沒(méi)預(yù)算。這種情況下我的建議是先做兩件事一是用通用模型對(duì)領(lǐng)域文檔做一次聚類把同一簇內(nèi)但字面差異大的文本對(duì)自動(dòng)挖掘成弱正樣本二是利用領(lǐng)域詞表做同義替換生成合成數(shù)據(jù)。這兩招不完美但能在一天內(nèi)構(gòu)造出初始訓(xùn)練集讓模型先跑起來(lái)。等模型跑出第一版就能用模型自身的召回結(jié)果做第二輪數(shù)據(jù)迭代這就是所謂的“自舉”。先用弱數(shù)據(jù)訓(xùn)一個(gè)粗模型再讓粗模型去挖掘難負(fù)樣本和高置信度正樣本擴(kuò)大訓(xùn)練集。每一輪迭代之后模型對(duì)領(lǐng)域語(yǔ)義的理解都會(huì)加深也會(huì)在下一輪數(shù)據(jù)挖掘中找到更難、更有價(jià)值的樣本。數(shù)據(jù)工程的這四個(gè)階段最后都會(huì)收斂到這個(gè)“模型輔助數(shù)據(jù)生產(chǎn)”的模式上。5. 訓(xùn)練實(shí)操模型配置、批量大小與調(diào)參策略5.1 訓(xùn)練流程的整體設(shè)計(jì)與關(guān)鍵參數(shù)整個(gè)訓(xùn)練流程我會(huì)分成三個(gè)階段領(lǐng)域預(yù)訓(xùn)練、雙塔對(duì)比訓(xùn)練、可選精調(diào)。領(lǐng)域預(yù)訓(xùn)練是在通用模型基礎(chǔ)上繼續(xù)用垂類語(yǔ)料做掩碼語(yǔ)言建模這一步不是必須但對(duì)領(lǐng)域術(shù)語(yǔ)多的場(chǎng)景收益明顯。雙塔對(duì)比訓(xùn)練是主力用前文的數(shù)據(jù)構(gòu)造方案喂給 InfoNCE??蛇x精調(diào)則是在少量人工標(biāo)注數(shù)據(jù)上用更小的學(xué)習(xí)率再走一輪專門修正邊界案例。我用一段偽代碼概括 InfoNCE 訓(xùn)練的核心邏輯方便不熟悉實(shí)現(xiàn)細(xì)節(jié)的讀者理解整個(gè)過(guò)程# 偽代碼InfoNCE 雙塔訓(xùn)練前向 def info_nce_loss(query_emb, doc_emb, tau0.05): # query_emb 和 doc_emb 均已做 L2 歸一化 # 假設(shè) batch 內(nèi)第 i 個(gè) query 的正確答案是第 i 個(gè) doc logits query_emb doc_emb.T / tau # [batch, batch] labels torch.arange(len(query_emb)) # 對(duì)角線為正確匹配 return cross_entropy(logits, labels)這里唯一容易被忽視的是 labels 的構(gòu)造對(duì)角線樣本對(duì)是正樣本同一行其他列都是負(fù)樣本。如果你的數(shù)據(jù) pipeline 把正樣本對(duì)排列打亂了必須維護(hù)一個(gè)索引映射否則 batch 內(nèi)“負(fù)樣本”里混進(jìn)正確答案訓(xùn)練損失就直接崩了。關(guān)鍵超參我整理了一張表這是我在垂類項(xiàng)目里比較通用的一組起點(diǎn)配置參數(shù)推薦范圍說(shuō)明學(xué)習(xí)率1e-5 ~ 3e-5基于預(yù)訓(xùn)練模型微調(diào)不建議太大batch size64 ~ 256越大隱式負(fù)樣本越多但受顯存限制溫度 τ0.03 ~ 0.1先小后大視訓(xùn)練難度微調(diào)難負(fù)樣本比例20% ~ 40%逐步加入不要一開(kāi)始就給滿訓(xùn)練輪數(shù)3 ~ 8垂類數(shù)據(jù)通常不需要訓(xùn)很多輪warmup前 10% step避免學(xué)習(xí)率突變打亂預(yù)訓(xùn)練權(quán)重5.2 大規(guī)模 batch 的實(shí)現(xiàn)技巧與顯存優(yōu)化InfoNCE 非常吃 batch size。batch 越大隱式負(fù)樣本越多訓(xùn)練出的語(yǔ)義邊界越細(xì)。但雙塔結(jié)構(gòu)有一個(gè)實(shí)現(xiàn)優(yōu)勢(shì)query 塔和文檔塔可以分開(kāi)前向不需要同時(shí)把兩側(cè)的長(zhǎng)文本都存進(jìn)顯存顯存峰值比交叉編碼器低得多。我一般用梯度累積來(lái)模擬更大的 batch先把文檔塔的向量存下來(lái)再編碼 query這樣即使單卡也能跑到 128 以上。垂類文檔通常很長(zhǎng)如果原樣截?cái)鄷?huì)損失關(guān)鍵信息。我的做法是先把文檔按語(yǔ)義分段對(duì)每段分別編碼再把所有段的向量做加權(quán)合并。這樣做的原因是文檔里真正跟 query 匹配的核心信息往往只出現(xiàn)在某一段全局均值反而把信號(hào)稀釋了。合并權(quán)重可以通過(guò)一個(gè)可學(xué)習(xí)的注意力層來(lái)獲得訓(xùn)練時(shí)和雙塔一起更新。如果資源允許多卡訓(xùn)練值得投入。雙塔的并行策略很簡(jiǎn)單每張卡處理一個(gè)子 batch前向完成后把所有 doc 向量匯總到一塊做全量 logits 計(jì)算。注意這里的同步通信是必要的因?yàn)?InfoNCE 的負(fù)樣本是跨卡共享的如果只在單卡內(nèi)做負(fù)樣本batch 效應(yīng)會(huì)被打折效果明顯不如全量共享。5.3 評(píng)估與早停不能用 loss 判斷模型好壞訓(xùn)練過(guò)程中最容易犯的錯(cuò)誤是盯著 loss 看。InfoNCE loss 下降不代表檢索效果好因?yàn)樗环从衬P蛥^(qū)分訓(xùn)練集 batch 內(nèi)樣本的能力而檢索效果取決于模型在真實(shí)候選集上的語(yǔ)義排序能力。所以我在訓(xùn)練過(guò)程中每個(gè) epoch 結(jié)束都會(huì)跑一次固定的驗(yàn)證評(píng)估集算 recallk 和 nDCG。垂類場(chǎng)景的離線評(píng)估集設(shè)計(jì)要貼近線上真實(shí)分布query 來(lái)自真實(shí)的用戶日志正樣本來(lái)自人工確認(rèn)負(fù)樣本從全量候選庫(kù)里隨機(jī)抽取。我用 recall5 和 recall20 作為主要指標(biāo)因?yàn)榫€上召回階段只要把正確文檔帶回精排就算完成目標(biāo)。如果 recall5 提升不明顯但 recall20 提升明顯說(shuō)明模型把語(yǔ)義帶到了更前面的位置同樣值得發(fā)布。早停的標(biāo)準(zhǔn)應(yīng)該圍繞評(píng)估集指標(biāo)而不是 loss 變化。我通常設(shè)置 patience 為 3 個(gè) epoch如果評(píng)估 recall20 連續(xù)三輪沒(méi)有上升就停止。垂類數(shù)據(jù)量不大訓(xùn)練通常不會(huì)太長(zhǎng)關(guān)鍵是把握好“繼續(xù)訓(xùn)練會(huì)不會(huì)過(guò)擬合到難負(fù)樣本”這個(gè)臨界點(diǎn)。一旦評(píng)估指標(biāo)下降立刻回滾到最佳 checkpoint不要戀戰(zhàn)。6. 實(shí)戰(zhàn)中踩過(guò)的坑與完整排查鏈路6.1 坑一難負(fù)樣本加太猛訓(xùn)練直接發(fā)散第一次做難負(fù)樣本挖掘時(shí)我犯過(guò)一個(gè)典型錯(cuò)誤把模型自身召回 top20 的不相關(guān)文檔全部塞進(jìn)負(fù)樣本池比例一度拉到 50% 以上。結(jié)果訓(xùn)練 loss 上下亂跳評(píng)估指標(biāo)不升反降。原因現(xiàn)在看很簡(jiǎn)單訓(xùn)練初期模型根本沒(méi)有區(qū)分難負(fù)樣本的能力把大量高相似度負(fù)樣本丟給一個(gè)沒(méi)準(zhǔn)備好的模型等于讓它在黑暗中猜拳梯度信號(hào)互相打架。排查過(guò)程花了很長(zhǎng)時(shí)間因?yàn)槲蚁葢岩墒菍W(xué)習(xí)率問(wèn)題調(diào)了幾輪沒(méi)有改善又懷疑是數(shù)據(jù)量不夠加了數(shù)據(jù)還是不行最后把我把負(fù)樣本比例按輪次動(dòng)態(tài)調(diào)整前兩輪只保留隨機(jī)負(fù)樣本第三輪開(kāi)始才逐漸加入難負(fù)樣本一切才恢復(fù)正常。這個(gè)經(jīng)驗(yàn)后來(lái)固化成我的訓(xùn)練策略難負(fù)樣本是給“已經(jīng)學(xué)會(huì)基礎(chǔ)語(yǔ)義的模型”加量的不是給冷啟動(dòng)模型補(bǔ)課的。6.2 坑二向量沒(méi)有歸一化調(diào)參怎么調(diào)都不對(duì)還有一個(gè)項(xiàng)目A 同學(xué)搭建了一套雙塔訓(xùn)練代碼照著網(wǎng)上的配置跑但無(wú)論怎么調(diào)學(xué)習(xí)率和溫度評(píng)估指標(biāo)都很差。我?guī)兔ε挪闀r(shí)第一眼看的就是向量是否做了歸一化發(fā)現(xiàn)訓(xùn)練代碼把 raw encoder 輸出的向量直接做了點(diǎn)積沒(méi)有 L2 歸一化。這里引發(fā)的問(wèn)題非常隱蔽向量模長(zhǎng)參差不齊長(zhǎng)文檔總是占優(yōu)模型學(xué)會(huì)了“長(zhǎng)度匹配”而不是“語(yǔ)義匹配”。排查鏈路是給幾個(gè)樣本打出來(lái)看相似度矩陣發(fā)現(xiàn)所有相似度都集中在某個(gè)區(qū)間分布完全不像余弦相似度的樣子。把歸一化加上之后同樣的訓(xùn)練配置和超參效果立刻恢復(fù)正常。這個(gè)坑我很確定地寫下來(lái)是想提醒所有做雙塔訓(xùn)練的讀者歸一化必須在 loss 計(jì)算之前一個(gè)都不能漏。它可以放在 encoder 輸出之后作為一個(gè)獨(dú)立的 normalize 層。6.3 坑三數(shù)據(jù)泄漏導(dǎo)致評(píng)估虛高差點(diǎn)帶病上線訓(xùn)練數(shù)據(jù)里有一條很隱蔽的數(shù)據(jù)泄漏路徑評(píng)估集中某些 query 的正樣本文檔在訓(xùn)練集中也以較高相似度的變體出現(xiàn)過(guò)。導(dǎo)致評(píng)估指標(biāo)看起來(lái)非常好但如果把評(píng)估集換成線上真實(shí) query效果就崩了。當(dāng)時(shí)我們甚至差點(diǎn)把那個(gè)模型發(fā)布上線還好在最后的 bad case 分析中發(fā)現(xiàn)模型對(duì)某些文檔標(biāo)題異常偏愛(ài)詞表之外幾乎不看。排查手段是評(píng)估集和訓(xùn)練集做雙向相似度檢索把訓(xùn)練集每條文檔去評(píng)估集里找最相似的樣本如果相似度超過(guò)閾值就檢查是否屬于同一份文檔的變體。這個(gè)操作看起來(lái)耗時(shí)但對(duì)垂類這種小數(shù)據(jù)量環(huán)境非常值得做。從這之后我把“訓(xùn)練-評(píng)估隔離檢查”寫進(jìn)了每一個(gè)數(shù)據(jù)工程的驗(yàn)收清單每次發(fā)版前都必須過(guò)一遍。6.4 坑四溫度 τ 成為“全局開(kāi)關(guān)”一調(diào)就全局生效最后一個(gè)想記錄的坑是 τ 的定位偏差。一開(kāi)始我把它當(dāng)作一個(gè)“能漲點(diǎn)就調(diào)”的參數(shù)每次模型效果不理想就先把 τ 從小到大掃一遍結(jié)果發(fā)現(xiàn)它雖然能帶來(lái)短期提升但無(wú)法彌補(bǔ)數(shù)據(jù)層面的問(wèn)題。后來(lái)我把 τ 當(dāng)成一個(gè)“和作用域綁定的開(kāi)關(guān)”它主要影響難樣本區(qū)分的銳度只有在數(shù)據(jù)質(zhì)量、負(fù)樣本比例、歸一化都正確的前提下調(diào) τ 才有意義。這個(gè)認(rèn)知改變了我整個(gè)調(diào)參流程的順序?,F(xiàn)在遇到效果不好的情況我會(huì)先排查數(shù)據(jù)問(wèn)題再檢查歸一化和訓(xùn)練代碼最后才動(dòng)溫度系數(shù)。垂類模型數(shù)據(jù)工程走到 Embedding 訓(xùn)練階段時(shí)數(shù)據(jù)就是天花板模型和參數(shù)只是負(fù)責(zé)逼近這個(gè)天花板。把順序搞反的人往往在參數(shù)調(diào)節(jié)上花掉大量時(shí)間卻始終突破不了那個(gè)數(shù)據(jù)決定的性能邊界。這一階段的實(shí)戰(zhàn)做下來(lái)我的一個(gè)整體感覺(jué)是雙編碼器加 InfoNCE 這個(gè)組合在垂類場(chǎng)景里足夠穩(wěn)它能幫你從前幾個(gè)階段積累的數(shù)據(jù)里榨出一套真正屬于領(lǐng)域語(yǔ)義的向量表示?;乜凑麄€(gè)訓(xùn)練過(guò)程最有價(jià)值的建議還是那幾句老話先體檢再動(dòng)手先歸一化再調(diào)參先保數(shù)據(jù)隔離再談指標(biāo)。項(xiàng)目推進(jìn)中總會(huì)有各種突發(fā)問(wèn)題但只要每一步的選擇都能說(shuō)清楚為什么遇到問(wèn)題時(shí)排查鏈路就會(huì)清晰很多。技術(shù)上雙塔結(jié)構(gòu)本身并不復(fù)雜真正的復(fù)雜度永遠(yuǎn)在數(shù)據(jù)和細(xì)節(jié)里這也是為什么我把這一階段的筆記重點(diǎn)都放在了踩坑和排錯(cuò)上。