:詞義消歧、語義角色標注與相似度計算)
最近在梳理NLP知識體系的時候正好推進到第5個專題語義學Semantics。如果說分詞、詞性、句法分析解決的是“句子長什么樣”那么語義學解決的就是“這句話到底什么意思”。這個專題內(nèi)容不算多但卻是從“能跑通流程”邁向“能做對任務”的分水嶺。無論你是剛開始接觸NLP的初學者還是正在做檢索、問答、對話系統(tǒng)這類落地項目的開發(fā)者把這一節(jié)吃透后續(xù)看預訓練模型、大語言模型的很多行為都會通透很多。這個單元很適合邊學邊動手不需要特別大的算力主要是吃透三類問題詞義消歧、語義相似度、語義角色標注。下面我按自己實際學習和做項目時的思路來拆把核心原理講清楚也把我在實驗里踩過的坑和調(diào)試經(jīng)驗一并寫出來。1. 語義模塊的整體設計與思路拆解1.1 為什么說語義是NLP的分水嶺我們先看一個老生常談但特別直觀的例子“蘋果熟了”。如果沒有語境這句話至少有三種理解水果成熟了、某科技公司的產(chǎn)品迭代成熟了、電視劇里的人物劇情到位了。分詞和句法分析根本分不出這三種差異它們只保證句子結構合法不會告訴你要選哪個意思。語義學的任務就是在這些候選意義之間做選擇并把“誰對誰做了什么”這種關系抽出來。從實際產(chǎn)品角度看語義能力幾乎決定了所有下游任務的上限。搜索引擎要理解“怎么燉排骨”和“排骨燉多久”是不是同一個問題客服機器人要區(qū)分“我要退款”和“退款多久到賬”的意圖差異內(nèi)容審核要判斷“打死我也不說”和“打死了人也不說”的嚴重程度完全不一樣。如果只停留在詞匹配層面這些場景全都會翻車。所以很多團隊會把“第四層語義能力”當成一個獨立模塊來建設而不是讓每個業(yè)務各自去湊。1.2 三種主流的語義研究視角做語義時會有三種完全不同的切入角度它們對后續(xù)技術選型影響很大。第一種是真值條件視角簡單說就是“句子在什么條件下為真”。比如“小明在圖書館”和“圖書館里有小明”邏輯上表達的是同一件事的兩個側(cè)面。研究這類問題通常需要謂詞邏輯、量詞、事件結構非常嚴謹?shù)涞焦こ汤锿林匾驗檎鎸嵱脩粽f話充滿了省略和歧義。第二種是認知視角探索人腦怎么表征“蘋果”這個概念。這里涉及概念網(wǎng)絡、原型理論、常識推理。工程上很難直接落地但很多詞匯知識資源比如wordnet這類詞網(wǎng)的構建思路都受了它的影響。第三種是分布視角也是現(xiàn)在統(tǒng)治工業(yè)界的一種觀點一個詞的意義由它經(jīng)常共現(xiàn)的語境決定。你不需要給“蘋果”寫定義只需要統(tǒng)計它在大量文本里和哪些詞一起出現(xiàn)。這一條路線直接催生了詞向量、語言模型、預訓練模型這一整個技術家族。我在單元設計里會把三種視角都點到但工程實操會主要圍繞認知視角的詞匯資源以及分布視角的向量化方法展開。1.3 本單元的學習路線設計如果把語義學拆成一門可操作的技術課我建議按下述三段走每段都能獨立形成能力。第一段是詞義級別的功課拿到一個多義詞根據(jù)上下文選出正確義項這叫詞義消歧。第二段是關系級別的功課識別句子里的“誰對誰做了什么”這叫語義角色標注。第三段是度量級別的功課給兩個詞或兩個句子算一個分數(shù)表示它們含義有多接近這叫語義相似度。這三段之間有明確的遞進關系詞義消歧是基礎角色標注依賴詞義的準確選擇相似度計算又可以反過來驗證前兩者的效果。我在給團隊做內(nèi)部分享時會把這三個能力畫成一條流水線有點像先看懂單詞再看懂動作最后給整句打分。2. 核心細節(jié)解析與實操要點2.1 詞義消歧一詞多義是第一道坎詞義消歧WSD的原理并不復雜一個詞有多個義項我們要根據(jù)上下文中其他詞的提示選出最合適的一個。早期經(jīng)典算法是Lesk算法核心思想很直白把目標詞的上下文詞和每個義項的釋義詞放在一起數(shù)重疊重疊越多的義項越可能是正確含義。比如“蘋果”有兩個義項一個和水果相關釋義里有“果實、果樹、甜、脆”這類詞一個和科技產(chǎn)品相關釋義里有“手機、電腦、操作系統(tǒng)”這類詞。如果輸入句子是“蘋果在秋季大量上市”上下文里出現(xiàn)“秋季、上市”這些詞顯然更貼近第一個義項的釋義環(huán)境于是消歧結果就是水果義項。這個算法放在今天看并不高級但它的可解釋性極強出問題了能直接看出是哪個詞撞了。實際工程里現(xiàn)在更多用統(tǒng)計和神經(jīng)網(wǎng)絡方法比如基于詞向量聚類的消歧先給多義詞的每個義項準備若干例句用句向量做聚類中心判斷新例句和哪個中心更近。但無論用什么方法數(shù)據(jù)都繞不開義項劃分這件事。中文尤其麻煩“蘋果”這種名詞還算好辦像“打”這種動詞手里能列出二三十個義項標注一套完整的義項語料本身就是工作量巨大的活。我給一個實操建議不要一上來就想Cover全部義項先按業(yè)務場景把高頻義項標注到覆蓋度80%以上其他低頻義項直接走默認第一義項兜底。這樣投入產(chǎn)出比最高。后面再遇到錯誤樣本補充標注就行。2.2 語義角色標注把句子轉(zhuǎn)成動作片腳本語義角色標注解決的是“誰、對誰、做了什么事、什么時候、在哪、用什么方式”的問題。以句子“他昨天用錘子敲碎了窗戶”為例標注結果是這樣一組角色施事者A0他動作V敲碎受事者A1窗戶時間AM-TMP昨天工具AM-MNR錘子為什么這個結構這么重要因為一旦抽出來你就可以直接回答“誰敲碎了窗戶”“他敲碎了什么”“什么時候發(fā)生的”。問答系統(tǒng)、信息抽取、事件抽取全都建立在這樣的結構之上。很多知識圖譜構建項目卡點往往不在實體識別而在“實體之間的關系”抽不準。而語義角色標注恰恰訓練的就是這種“動賓關系、施受關系”的感知能力。中文做SRL時有個額外難點動詞經(jīng)常不顯式出現(xiàn)比如說“他蘋果”意思是“他買了蘋果”還是“他吃了蘋果”完全靠語境補。這種省略動詞的情況規(guī)則系統(tǒng)幾乎無解統(tǒng)計模型能學個大概但漏標率仍然偏高。我的經(jīng)驗是做SRL之前先做好論元邊界切分寧可先把“誰、什么”切成塊再去判斷塊之間的關系也不要從頭到尾走端到端黑盒。分步走錯誤好定位。2.3 語義相似度從詞到句的度量問題相似度計算可以分三級來看。詞級相似度是最好做的常見思路是路徑相似度和信息相似度。路徑相似度利用詞匯概念網(wǎng)絡中的父子關系層次兩個詞在概念樹上離得越近越相似“轎車”和“汽車”關系近所以相似度高“轎車”和“香蕉”關系遠相似度自然低。信息相似度則會考慮一個詞在語料里的罕見程度越罕見的詞越能區(qū)分概念權重也越高這也符合直觀感受都比較常見的高頻詞關系未必緊密。句子級相似度就難一些。簡單做法是取句子中所有詞的向量做平均得到一個句向量然后算余弦相似度。但這種方式對詞序不敏感“貓追狗”和“狗追貓”會得到幾乎一樣的向量。更進階一點的做法是給詞按TF-IDF加權再平均能緩解一些高頻虛詞的干擾但仍解決不了詞序問題。再往后才輪到各類句編碼模型可解釋性就差很多了。這里要給個很重要的提醒語義相似度沒有“唯一的標準答案”。同一對句子在搜索場景里“相關”的判斷標準是主題一致在問答場景里“相似”的標準是語義蘊含在去重場景里“相似”的標準是句子結構幾乎相同。所以做相似度前一定要先定義清楚業(yè)務上說的“相似”具體是什么否則模型很難取舍。2.4 數(shù)據(jù)與工具的準備要點在做這套語義分析之前要先把數(shù)據(jù)和工具備齊?;A的NLP工具鏈至少要覆蓋中文分詞、詞性標注、依存句法分析這幾項。語義層的實驗通常建議準備三類資源詞匯語義資源包含詞語之間上下位關系、同義關系、反義關系的詞典類資源用途是詞義消歧和路徑相似度。語義角色標注語料帶有人工標注的“動詞-論元”關系數(shù)據(jù)規(guī)模不需要太大幾千句就可以開始實驗。預訓練詞向量或語言模型用于計算分布相似度如果資源緊張用公開的中文詞向量也能完成大部分實驗。工具鏈的選擇我的偏好是快速原型階段用輕量的開源NLP工具包跑通流程后再考慮性能優(yōu)化和模型替換。很多人一上來就調(diào)大模型API最后發(fā)現(xiàn)連基礎的切分都出了問題反而不容易定位。3. 實操過程與核心環(huán)節(jié)實現(xiàn)3.1 用簡化版Lesk算法實現(xiàn)詞義消歧我們先做一個迷你版詞義消歧用來理解Lesk算法的核心邏輯。為方便演示我用一個簡化的詞義表示結構每個義項只有ID、名稱和釋義關鍵詞。實際使用中你可以從更成熟的“詞網(wǎng)”資源里讀取這些數(shù)據(jù)。def lesk_simplified(context_words, senses, window2): def score(sense, ctx): overlap 0 for i, token in enumerate(ctx): if token in sense[gloss]: # 加一點位置權重離目標詞越近的詞權重越高 dist_factor max(0, 1 - abs(i - window) / (window 1)) overlap 1 * dist_factor return overlap best_score -1 best_sense None for sense in senses: s score(sense, context_words) if s best_score: best_score s best_sense sense return best_sense這里的context_words是目標詞前后若干個詞組成的列表window是我們設定的窗口半徑。位置權重的設計是我自己加的經(jīng)驗項離目標詞越近的上下文詞對義項選擇的貢獻越明顯。比如句子“我在菜市場看到蘋果在賣”上下文里“菜市場、賣”都靠近目標詞而“看到”這類詞因為常見即使和某個義項有重疊也會因為距離較遠而壓低貢獻。用上面這個函數(shù)實測幾個句子你會發(fā)現(xiàn)只要窗口大小在3以內(nèi)效果都還行一旦窗口拉到整句低頻噪音詞就會把分數(shù)帶偏。所以這個算法最核心的調(diào)參點不是算法本身而是“給多大窗口”和“過濾多少停用詞”。詞義消歧在學術上很難但在工程上做成“業(yè)務夠用”相對容易。關鍵是不要貪心一次解決所有詞先把多義詞的清單收斂到業(yè)務高頻的幾十個然后逐個優(yōu)化。3.2 計算語義相似度手動算一個例子看一個具體的相似度計算過程同時對比兩種方法。先看最簡單直接的路徑相似度在一個常識概念樹里節(jié)點代表一個概念概念邊代表上下位關系。“汽車”的父節(jié)點是“機動車”“轎車”的父節(jié)點也是“機動車”“香蕉”的父節(jié)點是“水果”。那么從“轎車”到“汽車”的距離是2步相似度可以設定為距離越短得分越高。一個常用的公式是[ sim \frac{1}{1 dist} ]所以“轎車”和“汽車”的sim是0.33而“轎車”和“香蕉”的距離可能超過10sim接近0.09。這個分數(shù)表面上看起來很小但如果把它映射到排序場景里它的作用主要是區(qū)分相對遠近而不是給出一個絕對的“多相似”。原理本身不復雜真正麻煩的是維護一棵與時俱進的概念樹新詞“固態(tài)電池”“鈉離子電池”這類名詞需要持續(xù)插入否則相似度計算就會漏。另一種更貼合工業(yè)界的方法是向量余弦相似度。假設我們用某個公開詞向量庫取“轎車”向量v1和“汽車”向量v2那相似度就是import numpy as np def cosine_sim(vec_a, vec_b): dot np.dot(vec_a, vec_b) norm_a np.linalg.norm(vec_a) norm_b np.linalg.norm(vec_b) return dot / (norm_a * norm_b 1e-8) v_car np.array([0.2, 0.3, -0.1]) v_sedan np.array([0.21, 0.28, -0.08]) v_banana np.array([-0.2, 0.1, 0.4]) print(cosine_sim(v_sedan, v_car)) # 接近0.95 print(cosine_sim(v_sedan, v_banana)) # 可能是負數(shù)或者0.1以下這里用三個手工假設的向量演示計算過程實際訓練好的詞向量維度通常在100到300維但余弦計算就是這樣一個公式?jīng)]有任何黑魔法。一個實用的經(jīng)驗是計算相似度之前先對向量做中心化處理即將每個維度的均值減去。中心化能消除不同向量之間的系統(tǒng)性偏移實驗結果通常比直接算余弦更穩(wěn)定因為純余弦只關心方向不關心模長而模長有時也包含重要信息。句子級相似度我建議從簡單到復雜試起。先取每個詞的向量算平均用這個平均向量代表句子如果效果不滿意再加TF-IDF權重還不行再考慮用真正的句編碼模型。每一次升級都要用同一組測試集做對比才能確認升級到底有沒有用。切忌一上來就追求最復雜的模型否則最后連什么模塊產(chǎn)生了什么效果都說不清。3.3 語義角色標注把規(guī)則跑起來語義角色標注最輕量的落地方式是規(guī)則加詞典。我們先用一個小系統(tǒng)理解標簽體系的含義再看哪些環(huán)節(jié)可以用統(tǒng)計模型替換。def simple_srl(sentence, verb): # 這是一個極度簡化的模板只用來理解語義角色抽取的基本思路 tokens sentence.split() agent_idx None patient_idx None for i, tok in enumerate(tokens): if tok in {他, 她, 某人, 公司, 團隊}: if i tokens.index(verb): agent_idx i if i tokens.index(verb) and tok not in {把, 被, 了}: if patient_idx is None: patient_idx i result {} if agent_idx is not None: result[A0] tokens[agent_idx] result[V] verb if patient_idx is not None: result[A1] tokens[patient_idx] return result print(simple_srl(他敲碎了窗戶, 敲碎))上面的代碼非常簡單很多句子會標錯但它的價值在于給你建立清晰的標簽直覺A0一般在動詞前A1一般在動詞后被字句和把字句需要特殊處理?!鞍选弊志淅铩八汛皯羟盟榱恕薄按皯簟彪m然在動詞前但它是動作的承受者不是施事者所以A0應該是“他”A1是“窗戶”。這個規(guī)則如果不單獨寫初學者很容易被詞序干擾。真實項目里用規(guī)則做SRL只能覆蓋一小部分常見句式更常見的做法是收集一批已標注的SRL語料訓練一個序列標注模型。序列標注會把每個詞貼上BIO標簽比如B-A0代表某個論元開始I-A1代表論元繼續(xù)。訓練這種模型和命名實體識別非常像輸入是詞向量加詞性特征輸出是每個詞的標簽序列。這個過程中需要人工調(diào)整的地方主要是標簽體系是只有A0/A1還是需要細分成工具、地點、時間等附加角色。附加角色一多樣本量需求就成倍上升。我個人的建議是如果業(yè)務只需要做知識抽取A0、V、A1三個角色基本夠用如果要做深層的問答類似“他是用什么敲碎的”才需要考慮AM-MNR、AM-TMP這類附加角色。先定義最小可用的角色集合別為了學術上的完整而把問題無謂放大這是我踩過坑后總結出來的。3.4 評估語義模塊做得好不好語義模塊不像分詞有明確的準確率標準因為它涉及“語義對不對”的主觀判斷。所以工程上一定要建立一套自己的驗收指標我常用三個一個是消歧準確率。方法很簡單準備幾百條人工標注好的多義詞句子跑一遍消歧流程算出正確比例。注意采樣要按業(yè)務場景分布來不要均勻抽樣否則高頻詞占大頭評估結果虛高。一個是相似度與人工打分的相關性。準備若干對句子讓兩個人分別打0到5的相似度分和模型輸出的分數(shù)做排序相關性計算。相關度高說明模型產(chǎn)出的排序基本符合人的直覺這是檢索場景里最看重的指標。還有一個是端到端任務提升。比如你做一個FAQ問答系統(tǒng)把相似度模塊從基礎的詞重疊換成語義度量后看最終問題召回率提高多少。這是最實用的驗收方式因為最終的指標才是業(yè)務方關心的。這三個指標前兩個在開發(fā)期用最后一個在灰度階段看。如果端到端指標沒提升哪怕前兩個指標很好看也要回頭檢查是不是相似度模塊的使用方式出了問題。我遇到過一種情況模型分數(shù)排序是對的但產(chǎn)品側(cè)只拿了一個絕對的相似度閾值去過濾由于分數(shù)整體偏高導致誤召回了一堆無關內(nèi)容。這種事情不對比端到端指標根本發(fā)現(xiàn)不了。4. 常見問題與排查技巧實錄4.1 詞典資源里查不到詞的義項怎么辦這是最常遇到的情況。詞網(wǎng)這類人工構建的資源更新滯后網(wǎng)絡新詞、專業(yè)術語覆蓋不全消歧時經(jīng)常找不到對應義項。我的處理思路是分層兜底先查基礎詞網(wǎng)資源命中不到再看領域詞表領域詞表也沒有就返回高頻義項。這樣雖然對單條樣本可能判斷不夠精準但系統(tǒng)不會整體崩掉。實際操作時可以把“找不到義項”這件事單獨打日志每天統(tǒng)計頻率。如果某個詞反復查不到就啟動人工補充流程把詞條加進領域詞典并標注好義項提示詞。這種滾動更新的節(jié)奏比一次性追求完整覆蓋穩(wěn)健得多。4.2 詞向量在不同批次訓練后不穩(wěn)定用相同語料訓練兩次詞向量得到的向量空間可能差異很大原因是隨機初始化和迭代順序的影響。這會給相似度模塊帶來一個隱患模型參數(shù)更新后向量空間發(fā)生變化導致線上相似度分數(shù)波動。解決辦法是固定隨機種子以及固定訓練數(shù)據(jù)的迭代順序。如果這些做了還是不穩(wěn)定另一個方案是引入一個線性對齊以舊版向量為基準對新版向量做正交變換最小化兩個集合中重疊詞的向量差異。對齊之后的向量帶上線分數(shù)漂移會顯著下降。這套方法在向量模型升級時非常實用。4.3 余弦相似度給反義詞打了高分“好”和“壞”在詞向量空間里往往很近因為它們的語法功能和語境高度相似。這在語義相似度場景里會帶來一種尷尬你問“這個產(chǎn)品好不好”系統(tǒng)把“這個產(chǎn)品很壞”也當成高度相關。這是分布語義的固有偏差解決起來不能完全靠向量本身。我的經(jīng)驗是疊加一個情感極性維度。具體做法是給每個詞打一個情感分在計算相似度時如果兩詞的極性方向不同就在相似度分數(shù)上乘以一個懲罰系數(shù)。這樣“好”和“壞”的向量距離近但因為極性相反最終排序時不至于被誤當成同義。這是規(guī)則和模型結合的典型案例。4.4 標注語料太少模型訓練不動語義角色標注這種任務沒有幾千條高質(zhì)量標注句子模型很難收斂。如果預算和人力有限我建議用遠程監(jiān)督的思路先從已有的結構化知識庫或業(yè)務數(shù)據(jù)庫里拿到“主語-謂語-賓語”三元組再回填到文本中找出對應的句子片段自動產(chǎn)生一批弱標注數(shù)據(jù)。這批數(shù)據(jù)噪音大但作為預訓練集非常合適。拿到弱標注模型后再讓人工只修正模型置信度低的樣本形成快速迭代閉環(huán)。整個過程中保證“修正后的數(shù)據(jù)被重新加入訓練集”這一條很重要否則標注資源等于只修了一半。實際操作里弱標注加上少量人工修正通常能把F1從40多分提到75分以上在這個基礎上再想往上走就得仔細分析錯誤類型而不是無腦加數(shù)據(jù)。4.5 生產(chǎn)環(huán)境里向量檢索性能差語義相似度檢索要遍歷幾十萬個向量做點積當請求量上來后純計算耗時和延遲會讓人頭疼。常規(guī)優(yōu)化手段是建向量索引先做粗篩再做精排。粗篩用近似最近鄰算法犧牲少量精度換取高召回候選集精排階段再用準確的余弦相似度在候選集內(nèi)排序。這樣可以把全量檢索的計算量降下一兩個數(shù)量級。還有更簡單的緩存方案相同或高度相似的查詢在短期內(nèi)重復率高尤其在企業(yè)客服這類場景中。對輸入句子先算一個短指紋如果相似度超過某個閾值直接返回緩存結果。我見過一個問答系統(tǒng)引入緩存后后端壓力降低了40%。所以性能優(yōu)化不要只看索引也得看業(yè)務流量特征。5. 把語義能力落到項目里的工具選型建議5.1 數(shù)據(jù)量決定方法選擇我經(jīng)常被問到一個問題做語義相似度到底用規(guī)則、經(jīng)典模型還是預訓練大模型這個問題不能脫離數(shù)據(jù)量單獨回答。這里給一個參考閾值當你的標注樣本只有幾十到幾百條時用規(guī)則配合詞表比訓練任何模型都強。因為模型在樣本不夠時會嚴重過擬合上線后碰到稍有變化的句式就會崩。當樣本量達到幾千條時可以考慮訓練輕量的分類或匹配模型此時詞向量特征加上簡單的神經(jīng)網(wǎng)絡往往效果穩(wěn)定。當樣本量達到幾萬條以上且你有多卡訓練能力時再考慮微調(diào)預訓練語言模型這樣才能物盡其用。這個經(jīng)驗很適合中小團隊。很多人一上來就微調(diào)大模型結果算力消耗大、迭代速度慢最后效果和簡單的詞向量模型不相上下就是因為數(shù)據(jù)量不夠支撐復雜模型的學習。模型復雜度不是越高越好要和手里的彈藥匹配。5.2 語義服務和業(yè)務解耦的經(jīng)驗項目進入后期語義能力最好沉淀成獨立服務而不是每個業(yè)務各寫一套。獨立服務的好處是所有場景共用同一套詞義資源、向量庫和標注體系出了問題只需在一處修復所有調(diào)用方同時受益。設計接口時建議把輸入定義為純文本輸出定義為結構化結果比如“消歧結果列表”“語義角色集合”“相似度分數(shù)”。讓上游業(yè)務只關心自己需要的字段不要暴露過多的內(nèi)部細節(jié)。語義服務內(nèi)部怎么升級、換模型只要輸出schema不變業(yè)務方就感知不到。這個解耦的收益需要等系統(tǒng)做過一次大升級后才能真正體會到。5.3 預訓練語言模型時代還需要學經(jīng)典語義嗎經(jīng)常有人問現(xiàn)在大模型這么強大還需要理解詞義消歧、語義角色標注這些基礎概念嗎我的回答始終是需要而且理解了這些概念會幫助你更好地用大模型。大模型本質(zhì)上是分布語義的一個超級大的實現(xiàn)它的很多“聰明”之處來自深度學習語言模型但它的缺陷也繼承了分布語義的先天不足。比如它對反義詞的區(qū)分、對罕見義項的識別、對復雜邏輯關系的推理仍然是吃力不討好的。當你清楚這些經(jīng)典模塊擅長什么、不擅長什么你就能在大模型輸出里敏銳識別出錯誤信號知道該去追問還是該換一種方式提問。反過來如果你完全不懂底下這些原理你看到大模型一本正經(jīng)地胡說八道時根本不知道應該怎么定位問題。我在每次做項目總結時都會把經(jīng)典語義模塊比作一套“基本裝備”詞義消歧是盾過濾歧義語義角色標注是矛刺穿表層句子結構相似度計算是尺度量遠近。這三樣裝備不一定每個項目都同時用上但你手里得常備著因為隨時會用到。最后再分享一個小技巧做語義類項目時千萬別只盯著單個指標。我習慣每周固定抽幾十條線上真實請求把模型的中間輸出打印出來逐條看尤其關注那些“模型打了高分但用戶明顯不滿意”的樣本。這些樣本往往能暴露出評測集里覆蓋不到的問題比如標的義項不符合業(yè)務習慣或者角色標注把領域術語拆碎了。先修這類問題模型效果才會有質(zhì)的提升。