
1. 句法信息注入Transformer的動機與整體思路1.1 為什么要在Transformer里塞進句法樹Transformer架構(gòu)從2017年提出之后迅速在自然語言處理的各個任務(wù)上鋪開機器翻譯、文本分類、問答系統(tǒng)、語言模型幾乎每個方向都在用。它的核心是自注意力機制每個詞都能直接和序列中任意位置的其他詞計算關(guān)聯(lián)權(quán)重不受距離限制。這個特性讓它在捕捉長距離依賴上比循環(huán)神經(jīng)網(wǎng)絡(luò)強出一大截訓練時還能并行計算速度優(yōu)勢非常明顯。但用久了就會發(fā)現(xiàn)一個問題自注意力學到的關(guān)聯(lián)模式和人類理解語言時依賴的句法結(jié)構(gòu)并不總是一回事。舉個例子“那只追著貓跑的小狗終于停了下來”這句話里“小狗”是主語“停了下來”是謂語中間隔了很長一段修飾成分。自注意力理論上能通過多層堆疊學到這種主謂關(guān)系但它需要大量數(shù)據(jù)去隱式地擬合而且學到的注意力分布未必和句法樹對齊。換句話說模型有能力學到句法但沒有被明確告知句法是什么。這就引出了一個很自然的研究方向能不能把句法樹的結(jié)構(gòu)信息顯式地注入到Transformer里讓模型不用從零開始摸索句法關(guān)系這個思路在循環(huán)神經(jīng)網(wǎng)絡(luò)時代就有大量工作比如把句法樹的路徑編碼進隱狀態(tài)或者用樹結(jié)構(gòu)約束信息流動。到了Transformer時代問題變成了自注意力本身是一個全連接圖怎么把一個樹結(jié)構(gòu)嵌進去同時不破壞它原有的并行性和表達能力這篇論文給出的方案核心思路是在自注意力的計算過程中引入一個基于句法樹的距離偏置。不是硬性地把注意力限制在樹的邊上而是把樹中兩個詞之間的路徑長度轉(zhuǎn)化成一個可以加到注意力分數(shù)上的偏置項。這樣模型仍然能看到全序列但句法上靠近的詞會獲得額外的注意力傾向。這個設(shè)計的好處是既保留了Transformer的靈活性又給了模型一個句法上的“先驗提示”。1.2 整體方案的設(shè)計哲學這個方案的設(shè)計哲學可以用一句話概括軟約束優(yōu)于硬約束。所謂硬約束就是直接把注意力矩陣中不符合句法樹的位置mask掉強制模型只能沿著樹的邊做注意力。這種做法在依賴句法樹質(zhì)量的任務(wù)上可能有效但一旦句法分析器出錯錯誤會直接傳播到下游而且模型失去了捕捉句法之外關(guān)聯(lián)的能力。軟約束的做法則是在注意力分數(shù)上加一個偏置句法上近的加正偏置遠的加負偏置但所有位置仍然可訪問。模型可以根據(jù)任務(wù)需要自己決定多大程度上依賴這個偏置。如果句法信息有用模型會學會放大這個偏置的影響如果句法信息有噪聲模型也可以學會忽略它。這種設(shè)計在工程上更穩(wěn)健也更符合“讓模型自己學”的深度學習哲學。另一個關(guān)鍵設(shè)計是偏置的計算方式。論文沒有直接用句法樹上的最短路徑長度作為偏置而是把路徑長度映射到一個可學習的嵌入向量。具體來說路徑長度被離散化到若干個桶里每個桶對應(yīng)一個可學習的標量偏置。這樣模型可以自己學習“路徑長度為1時偏置應(yīng)該是多少”“路徑長度為2時又該是多少”而不是人為設(shè)定一個固定的衰減函數(shù)。這個細節(jié)很重要因為不同任務(wù)對句法距離的敏感程度不一樣可學習的偏置讓方案能適應(yīng)不同場景。2. 核心細節(jié)解析與實操要點2.1 句法樹的獲取與預處理要注入句法信息第一步是拿到句法樹。論文里用的是依存句法分析器對每個輸入句子生成一棵依存樹每個詞有一個父節(jié)點和一條依存弧標簽。依存樹的好處是它和詞序列一一對應(yīng)每個詞恰好有一個父節(jié)點根節(jié)點除外路徑長度的計算比較直接。實際操作中句法分析器的選擇會影響最終效果。論文里用的是現(xiàn)成的依存分析器沒有自己訓練。這里有一個經(jīng)驗如果你做的任務(wù)有標注好的句法樹比如某些語言有樹庫那直接用金標準句法樹效果最好。但大多數(shù)下游任務(wù)沒有句法標注只能用自動分析器。自動分析器的準確率通常在90%到95%之間意味著每句話里可能有一兩個詞的父節(jié)點是錯的。軟約束的設(shè)計在這里就體現(xiàn)出優(yōu)勢了個別錯誤不會導致整個注意力矩陣崩掉。預處理階段還需要把句法樹轉(zhuǎn)換成距離矩陣。對于長度為n的句子需要計算一個n乘n的矩陣每個元素是詞i和詞j在句法樹上的最短路徑長度。這個計算可以用樹的最近公共祖先算法來做時間復雜度是O(n^2)對于常規(guī)句子長度幾十個詞完全夠用。如果句子特別長可以考慮用一些近似算法但論文里沒有涉及這個場景。注意句法分析器對分詞很敏感。如果你的任務(wù)是中文分詞邊界和句法分析器的分詞邊界必須一致否則詞對不上距離矩陣就錯了。我試過用不同分詞工具混搭結(jié)果路徑長度全亂模型效果直接掉到基線以下。2.2 距離到偏置的映射機制拿到距離矩陣之后下一步是把距離映射成偏置。論文的做法是設(shè)置一個最大距離閾值比如10超過這個閾值的距離統(tǒng)一當作10處理。然后為0到10這11個距離值各分配一個可學習的標量參數(shù)。這些參數(shù)和模型的其他參數(shù)一起訓練初始化通常是零或者小的隨機值。為什么用桶而不是連續(xù)函數(shù)因為句法距離的分布是長尾的大部分詞對的路徑長度在1到5之間超過10的很少。如果用一個連續(xù)函數(shù)比如指數(shù)衰減你需要調(diào)衰減率這個超參數(shù)調(diào)不好效果就差很多。用桶的話每個距離的偏置獨立學習模型自己決定怎么分配。而且桶的數(shù)量是一個可控的超參數(shù)桶太少表達力不夠桶太多參數(shù)多且容易過擬合。論文里用了10個桶這個數(shù)字在大多數(shù)任務(wù)上夠用。偏置加到注意力分數(shù)上的方式也很關(guān)鍵。標準自注意力的分數(shù)是query和key的點積除以根號d然后softmax。加偏置就是在softmax之前把偏置加到分數(shù)上。這樣偏置會影響softmax的分布正偏置讓對應(yīng)位置的注意力權(quán)重變大負偏置讓它變小。注意這個偏置是加在分數(shù)上的不是乘在權(quán)重上的所以它不會改變注意力的歸一化性質(zhì)。2.3 多頭注意力中的偏置共享Transformer是多頭的每個頭有自己的query、key、value投影矩陣。一個自然的問題是句法偏置是所有頭共享還是每個頭獨立論文里做了實驗發(fā)現(xiàn)共享偏置和獨立偏置效果差不多但共享偏置參數(shù)更少訓練更穩(wěn)定。這個結(jié)論和直覺一致句法信息是句子層面的不應(yīng)該隨頭變化。如果每個頭學一套偏置容易過擬合而且不同頭之間沒有一致性約束可能學出矛盾的偏置。不過這里有一個細節(jié)偏置是加在分數(shù)上的而不同頭的分數(shù)尺度可能不一樣。如果某個頭的點積分數(shù)普遍偏大同樣的偏置對它影響就小反之影響就大。論文里沒有對分數(shù)做額外的歸一化直接加偏置。實測下來只要初始化合理這個問題不嚴重因為層歸一化和殘差連接會穩(wěn)定訓練。實操心得如果你在自己的任務(wù)上發(fā)現(xiàn)加偏置后效果不穩(wěn)定可以試試對偏置做一個縮放比如乘以一個可學習的標量初始化為1。這樣模型可以自己調(diào)整偏置的整體強度。我在一個文本分類任務(wù)上試過加了縮放之后收斂更快最終效果也略好。3. 實操過程與核心環(huán)節(jié)實現(xiàn)3.1 從零實現(xiàn)句法偏置注意力的步驟假設(shè)你已經(jīng)有了一個標準的Transformer編碼器現(xiàn)在要把它改造成帶句法偏置的版本。下面是我實際操作的步驟以PyTorch為例。第一步準備句法距離矩陣。對每個batch里的每個句子用依存分析器生成依存樹然后計算距離矩陣。距離矩陣的形狀是batch_size乘seq_len乘seq_len。注意padding位置的距離要設(shè)成一個特殊值比如-1然后在加偏置時mask掉。第二步定義偏置嵌入層。創(chuàng)建一個Embedding層輸入維度是最大距離加1輸出維度是1。初始化用零。這個Embedding層的參數(shù)就是每個距離對應(yīng)的偏置值。第三步修改自注意力的forward函數(shù)。在計算完query和key的點積之后除以根號d之前或之后都可以把偏置加進去。具體來說對于每個位置對(i,j)查表得到偏置b然后分數(shù)加上b。注意偏置的形狀要broadcast到和分數(shù)矩陣一致。第四步處理padding。padding位置的分數(shù)要設(shè)成負無窮這樣softmax之后權(quán)重為零。這個和標準Transformer一樣但要注意偏置不要加到padding位置上否則可能把負無窮變成有限值。第五步訓練和調(diào)參。偏置嵌入層的學習率可以和主模型一樣也可以稍微大一點因為它的參數(shù)少。我試過用一樣的學習率效果就很好。最大距離閾值這個超參數(shù)論文里用10我試過5和15在大多數(shù)任務(wù)上10是安全的。如果句子平均長度很短比如10個詞以內(nèi)可以降到5。import torch import torch.nn as nn import torch.nn.functional as F class SyntaxBiasedAttention(nn.Module): def __init__(self, d_model, n_head, max_distance10): super().__init__() self.d_model d_model self.n_head n_head self.d_k d_model // n_head self.max_distance max_distance self.q_proj nn.Linear(d_model, d_model) self.k_proj nn.Linear(d_model, d_model) self.v_proj nn.Linear(d_model, d_model) self.out_proj nn.Linear(d_model, d_model) self.distance_bias nn.Embedding(max_distance 1, 1) nn.init.zeros_(self.distance_bias.weight) def forward(self, x, distance_matrix, maskNone): batch_size, seq_len, _ x.shape q self.q_proj(x).view(batch_size, seq_len, self.n_head, self.d_k).transpose(1, 2) k self.k_proj(x).view(batch_size, seq_len, self.n_head, self.d_k).transpose(1, 2) v self.v_proj(x).view(batch_size, seq_len, self.n_head, self.d_k).transpose(1, 2) scores torch.matmul(q, k.transpose(-2, -1)) / (self.d_k ** 0.5) clamped_dist torch.clamp(distance_matrix, 0, self.max_distance) bias self.distance_bias(clamped_dist).squeeze(-1) bias bias.unsqueeze(1) scores scores bias if mask is not None: scores scores.masked_fill(mask 0, float(-inf)) attn F.softmax(scores, dim-1) context torch.matmul(attn, v) context context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) return self.out_proj(context)這段代碼的關(guān)鍵點在于distance_bias的初始化為零這樣訓練初期模型行為和標準Transformer完全一樣然后逐漸學會利用句法偏置。如果初始化成隨機值訓練初期可能會引入噪聲收斂變慢。3.2 距離矩陣的計算實現(xiàn)距離矩陣的計算需要一個函數(shù)輸入是依存樹輸出是每對詞的最短路徑長度。依存樹可以用一個父節(jié)點數(shù)組表示parent[i]是詞i的父節(jié)點索引根節(jié)點的父節(jié)點設(shè)為自己或者-1。計算最短路徑的常用方法是先找到兩個詞的最近公共祖先然后路徑長度等于兩個詞到祖先的深度之和減去兩倍祖先的深度。def compute_distance_matrix(parents, seq_len): depth [0] * seq_len for i in range(seq_len): node i while parents[node] ! node: node parents[node] depth[i] 1 dist [[0] * seq_len for _ in range(seq_len)] for i in range(seq_len): for j in range(seq_len): a, b i, j path_a, path_b [], [] while a ! parents[a]: path_a.append(a) a parents[a] path_a.append(a) while b ! parents[b]: path_b.append(b) b parents[b] path_b.append(b) set_a {node: idx for idx, node in enumerate(path_a)} lca None for idx, node in enumerate(path_b): if node in set_a: lca node break dist[i][j] set_a[lca] path_b.index(lca) return dist這個實現(xiàn)的時間復雜度是O(n^2)乘以每次找LCA的O(n)對于n小于100的句子完全夠用。如果要做batch處理可以把每個句子的距離矩陣算好之后padding到相同長度然后堆疊成tensor。注意依存分析器的輸出格式各不相同有的用0-based索引有的用1-based有的根節(jié)點索引是-1。在寫代碼之前一定要確認清楚否則距離矩陣全錯。我踩過這個坑調(diào)了半天才發(fā)現(xiàn)是索引偏移了一位。3.3 訓練配置與超參數(shù)選擇論文里的實驗設(shè)置是標準的Transformer base配置6層編碼器8個頭d_model是512ffn是2048dropout是0.1。優(yōu)化器用Adam學習率用warmup加逆平方根衰減。這些配置在大多數(shù)任務(wù)上都是安全的起點。句法偏置相關(guān)的超參數(shù)主要有兩個最大距離閾值和偏置的學習率。最大距離閾值論文里用10我建議根據(jù)任務(wù)的平均句子長度來定。如果平均長度是2010是合理的如果平均長度是50可以設(shè)成15或20。偏置的學習率可以和主模型一樣也可以設(shè)成主模型的2倍因為它的參數(shù)少需要更快的學習。我試過2倍學習率收斂稍快但最終效果差不多。還有一個容易忽略的點句法分析器的質(zhì)量。如果分析器的準確率低于90%注入句法信息可能反而有害。這種情況下可以考慮用集成多個分析器的結(jié)果或者降低偏置的初始強度讓模型自己決定用多少。論文里沒有討論分析器質(zhì)量的影響但這是實際部署時必須考慮的。4. 常見問題與排查技巧實錄4.1 加了句法偏置反而掉點怎么辦這是最常見的問題。明明句法信息應(yīng)該有用為什么加了之后效果變差了根據(jù)我的經(jīng)驗原因通常有以下幾個。第一個原因是句法分析器質(zhì)量太差。如果你的任務(wù)領(lǐng)域和句法分析器的訓練領(lǐng)域差距很大比如分析器是在新聞?wù)Z料上訓練的你的任務(wù)是社交媒體文本分析準確率可能掉到80%以下。這種情況下句法偏置引入的噪聲大于信號模型學不到有用的東西。解決辦法是換一個在目標領(lǐng)域上表現(xiàn)更好的分析器或者干脆不用句法偏置等有了高質(zhì)量分析器再說。第二個原因是偏置的初始化太強。如果偏置初始化成非零值訓練初期模型就被迫接受一個可能錯誤的句法先驗導致優(yōu)化困難。論文里初始化成零這是最安全的做法。如果你改了初始化先改回零試試。第三個原因是最大距離閾值設(shè)得太小。如果閾值是5但很多有用的句法關(guān)系路徑長度是6或7這些關(guān)系就被截斷成5偏置無法區(qū)分它們。解決辦法是增大閾值或者用對數(shù)分桶讓長距離的桶更稀疏。第四個原因是任務(wù)本身不依賴句法。有些任務(wù)比如情感分類主要靠關(guān)鍵詞和局部模式句法結(jié)構(gòu)幫助不大。這種情況下加句法偏置可能只是增加了參數(shù)沒有帶來信息增益??梢韵茸鲆粋€消融實驗不加偏置的基線是多少加了之后是多少如果持平或略降說明這個任務(wù)不適合。4.2 訓練不收斂或loss震蕩加了句法偏置之后如果訓練不收斂首先檢查距離矩陣是否正確。一個簡單的驗證方法是打印幾個句子的距離矩陣人工檢查是否合理。比如相鄰詞的路徑長度通常是1或2如果打印出來是0或很大的數(shù)說明計算有問題。另一個常見原因是padding處理不當。如果padding位置的距離沒有mask掉偏置會加到padding位置上softmax之后padding位置可能獲得非零權(quán)重污染上下文表示。檢查mask是否在加偏置之后、softmax之前應(yīng)用。還有一個可能的原因是偏置嵌入層的梯度太大。因為偏置直接加到分數(shù)上對softmax的影響是指數(shù)級的梯度可能很大??梢栽囋噷ζ们度雽拥奶荻茸霾眉艋蛘哂酶〉膶W習率。我遇到過一次loss震蕩把偏置學習率降到主模型的0.5倍就好了。4.3 推理時沒有句法分析器怎么辦訓練的時候有句法分析器推理的時候如果分析器不可用或者太慢怎么辦有幾個方案。方案一是用一個小型快速分析器準確率低一點但速度快。很多依存分析器有蒸餾版本速度是原版的幾倍準確率只降一兩個點。對于推理延遲敏感的場景這個方案很實用。方案二是緩存句法分析結(jié)果。如果推理請求有重復比如同一個句子被多次處理可以緩存距離矩陣。但自然語言任務(wù)里完全重復的句子不多這個方案效果有限。方案三是訓練一個句法距離預測器輸入是詞向量輸出是距離矩陣。這個預測器可以和主模型聯(lián)合訓練推理時只用預測器不需要外部分析器。這個方案比較復雜論文里沒有涉及但我在一個項目里試過效果還可以代價是增加了一些參數(shù)和訓練時間。方案四是直接去掉句法偏置用標準Transformer推理。如果訓練時句法偏置的權(quán)重學得很小去掉之后影響不大。但如果權(quán)重很大去掉之后效果會明顯下降。所以這個方案只適合偏置權(quán)重小的情況。4.4 常見問題速查表問題現(xiàn)象可能原因排查方法解決辦法加了偏置后掉點分析器質(zhì)量差檢查分析器在目標領(lǐng)域的準確率換分析器或不用偏置訓練不收斂距離矩陣錯誤打印距離矩陣人工檢查修正距離計算邏輯loss震蕩偏置梯度太大監(jiān)控偏置層梯度范數(shù)降低偏置學習率或梯度裁剪推理速度慢分析器太慢測分析器延遲用蒸餾版分析器或緩存偏置權(quán)重學不動初始化太小或?qū)W習率太低檢查偏置值變化增大學習率或初始化長句效果差最大距離閾值太小統(tǒng)計長句的距離分布增大閾值或?qū)?shù)分桶避坑技巧在正式訓練之前先做一個小的消融實驗用10%的數(shù)據(jù)訓練標準Transformer和帶偏置的版本對比驗證集效果。如果帶偏置的版本沒有優(yōu)勢先別急著上全量數(shù)據(jù)回頭檢查分析器和距離矩陣。這個習慣幫我省了很多算力。5. 效果分析與擴展思路5.1 論文實驗結(jié)果的解讀論文在機器翻譯和語言模型兩個任務(wù)上做了實驗。機器翻譯用的是標準的數(shù)據(jù)集語言模型用的是維基百科語料。結(jié)果顯示加了句法偏置之后BLEU值和困惑度都有改善。改善幅度不算大機器翻譯上大概0.5到1個BLEU語言模型上困惑度降了幾個點。這個幅度在學術(shù)界算是顯著但在工業(yè)界可能不夠驚艷。值得注意的是論文做了消融實驗把偏置換成固定的距離衰減函數(shù)效果比可學習的偏置差。這說明可學習的偏置確實有用模型學到的偏置模式不是簡單的衰減。論文還可視化了學到的偏置值發(fā)現(xiàn)路徑長度為1時偏置最大然后逐漸減小但路徑長度為0時偏置不是最大的。路徑長度為0就是詞自己自己對自己的注意力本來就應(yīng)該大不需要額外偏置。這個發(fā)現(xiàn)符合直覺。另一個有趣的發(fā)現(xiàn)是句法偏置在低資源場景下幫助更大。論文里做了低資源機器翻譯的實驗訓練數(shù)據(jù)少的時候句法偏置帶來的改善更明顯。這很好理解數(shù)據(jù)多的時候模型自己能從數(shù)據(jù)里學到句法數(shù)據(jù)少的時候顯式的句法先驗更有價值。這個結(jié)論對實際應(yīng)用很有指導意義如果你的任務(wù)標注數(shù)據(jù)少值得試試句法偏置。5.2 可以嘗試的擴展方向第一個擴展方向是語義依存而不是句法依存。句法依存關(guān)注的是主謂賓定狀補這些語法關(guān)系語義依存關(guān)注的是施事受事時間地點這些語義角色。對于很多下游任務(wù)語義信息比句法信息更有用。可以把依存分析器換成語義角色標注器距離矩陣基于語義依存樹計算。這個方向論文里沒有做但邏輯上是通的。第二個擴展方向是多語言場景。不同語言的句法差異很大比如中文沒有形態(tài)變化英文有豐富的形態(tài)。句法偏置在多語言模型里可能幫助更大因為模型需要處理不同語言的句法??梢栽囋囋?multilingual BERT 上加句法偏置看看跨語言遷移效果有沒有提升。第三個擴展方向是結(jié)合其他結(jié)構(gòu)信息。除了句法樹還有篇章結(jié)構(gòu)、共指鏈、實體關(guān)系圖等。這些結(jié)構(gòu)信息都可以轉(zhuǎn)化成距離矩陣或者鄰接矩陣用類似的方式注入Transformer。關(guān)鍵是要設(shè)計好距離的定義和偏置的映射方式。第四個擴展方向是動態(tài)句法。論文里的句法樹是靜態(tài)的每個句子分析一次。但有些任務(wù)里句法結(jié)構(gòu)可能隨上下文變化比如歧義句在不同語境下有不同的句法樹??梢栽囋囉枚鄠€分析器生成多個句法樹然后對偏置做集成。這個方向比較前沿實現(xiàn)復雜度也高。5.3 工程落地的經(jīng)驗總結(jié)在實際工程里落地這個方案有幾個經(jīng)驗值得分享。第一句法分析器的延遲是瓶頸。依存分析器的速度通常比Transformer編碼器慢如果推理延遲要求高分析器可能成為瓶頸。解決辦法是用快速分析器或者把分析器部署成獨立服務(wù)和主模型并行調(diào)用。第二句法偏置的參數(shù)很少對模型大小影響可以忽略。偏置嵌入層只有幾十個參數(shù)相比Transformer的幾百萬參數(shù)微不足道。所以不用擔心增加模型大小。第三句法偏置和其他正則化方法可以疊加。比如dropout、權(quán)重衰減、標簽平滑這些和句法偏置不沖突。我試過同時用dropout和句法偏置效果比單獨用好。第四句法偏置的可解釋性是一個加分項。學到的偏置值可以可視化幫助理解模型在多大程度上依賴句法。如果偏置值都很小說明模型不太需要句法信息如果偏置值很大說明句法信息很關(guān)鍵。這個可解釋性在調(diào)試和匯報時很有用。我個人在實際操作中的體會是句法偏置不是萬能藥它適合那些句法結(jié)構(gòu)對任務(wù)有明確幫助的場景比如機器翻譯、句法分析、語義角色標注。對于情感分類、主題分類這些任務(wù)句法偏置的幫助有限。另外句法分析器的質(zhì)量是天花板分析器不行后面怎么調(diào)都白搭。所以如果你打算用這個方案先把分析器選好、調(diào)好再考慮偏置的設(shè)計。最后分享一個小技巧如果訓練數(shù)據(jù)足夠多可以先在大量無標注數(shù)據(jù)上預訓練一個句法距離預測器然后用它來生成距離矩陣這樣推理時就不依賴外部分析器了整個流程更可控。