情感分析:用BRNN精準(zhǔn)定位政務(wù)評(píng)論中的具體問題)
簡介面向政務(wù)APP評(píng)論挖掘的技術(shù)文檔提出基于雙向循環(huán)神經(jīng)網(wǎng)絡(luò)BRNN的端到端方面級(jí)情感分析方法E2E-ALSA將方面實(shí)體抽取與情感分類聯(lián)合建模規(guī)避傳統(tǒng)情感詞典與人工規(guī)則覆蓋局限性可服務(wù)于移動(dòng)政務(wù)治理、用戶行為分析等場景。文檔共1個(gè)docx文件壓縮包約556KB內(nèi)容涵蓋研究背景、ALSA方法對(duì)比、BRNN模型設(shè)計(jì)及應(yīng)用思路適合自然語言處理方向的研究生、算法工程師及政務(wù)信息化從業(yè)者參考。目前已有118人學(xué)習(xí)。通過該文檔可快速掌握端到端方面級(jí)情感分析的建模要點(diǎn)理解政務(wù)APP在線評(píng)論細(xì)粒度情感識(shí)別流程為改進(jìn)評(píng)價(jià)指標(biāo)、減少強(qiáng)制推廣等非理性現(xiàn)象提供技術(shù)參考。文中對(duì)傳統(tǒng)、流水線與端到端三類ALSA方法的流程對(duì)比亦有說明有助于讀者理解方法演進(jìn)與選型思路。1. 這個(gè)標(biāo)題到底在解決什么政務(wù)評(píng)論的“好評(píng)差評(píng)”和“罵的是什么”差著一個(gè)端到端政務(wù)APP的評(píng)論區(qū)大概是NLP里最不像電商評(píng)論的數(shù)據(jù)短、口語化、方言夾雜大量具體業(yè)務(wù)詞“補(bǔ)貼咋還不下來”“老年客戶登錄太費(fèi)勁了”“窗口電話打不通”這類句子用整句情感分類只能說一句“這是負(fù)面”但真正該派單給哪個(gè)部門——社保處、技術(shù)部、窗口服務(wù)——完全沒有定位能力。標(biāo)題里“方面級(jí)情感分析”就是干這個(gè)的不只要判斷正負(fù)還要把評(píng)論里的具體方面詞補(bǔ)貼進(jìn)度、登錄流程、電話接通和它對(duì)應(yīng)的情感極性一起抽出來。而“端到端”指的是不再走“先抽方面詞再判斷極性”的流水線結(jié)構(gòu)讓模型一批訓(xùn)練只靠“評(píng)論文本標(biāo)簽序列”直接把“哪里什么情緒”同時(shí)輸出。BRNN雙向循環(huán)神經(jīng)網(wǎng)絡(luò)在里面承擔(dān)的角色是編碼器每個(gè)詞都能同時(shí)看到左邊的上下文和右邊的上下文“補(bǔ)貼不下來”這種觀點(diǎn)詞和“補(bǔ)貼”這個(gè)實(shí)體經(jīng)常隔著好幾個(gè)字雙向循環(huán)正好能把相隔的詞在隱藏狀態(tài)里拉近。這套方案適合誰我理解是兩類人一類是政務(wù)APP或者公共服務(wù)產(chǎn)品的NLP工程師手頭有一堆評(píng)論數(shù)據(jù)但根本沒法依賴外部情感詞典因?yàn)樵~典里不會(huì)有“老年客戶”和“窗口沒人”另一類是做文本挖掘外包項(xiàng)目的人甲方要的不是“準(zhǔn)確率93%”的匯報(bào)而是“評(píng)論里被罵得最多的功能模塊TOP10”這種能直接發(fā)工單結(jié)項(xiàng)的結(jié)果。先說一個(gè)反直覺的觀察這類任務(wù)在測(cè)試集上跑出0.9的F1并不難難的是拿模型去掃真實(shí)評(píng)論流出來的方面詞一堆“這個(gè)”“那里”這種無意義的代詞極性還偏偏標(biāo)對(duì)了導(dǎo)致業(yè)務(wù)部門看報(bào)告一頭霧水。后面會(huì)專門講這個(gè)問題出在哪以及怎么用端到端的標(biāo)簽設(shè)計(jì)把它壓下去。接下來的內(nèi)容全部以復(fù)現(xiàn)一條可用的“評(píng)論-方面-極性”生產(chǎn)管線為目標(biāo)從標(biāo)注方案、模型結(jié)構(gòu)、訓(xùn)練參數(shù)一路寫到上線后怎么驗(yàn)證。2. BRNN做方面級(jí)情感分析為什么雙向循環(huán)比“看完再回頭”更適合評(píng)論2.1 評(píng)論里觀點(diǎn)詞和方面詞的位置關(guān)系決定了“從左到右”不夠用方面級(jí)情感分析任務(wù)里模型拿到一句話需要同時(shí)解兩個(gè)子問題找到“方面詞”的邊界并對(duì)每個(gè)方面詞給出極性。評(píng)論數(shù)據(jù)里一個(gè)特別常見的現(xiàn)象是觀點(diǎn)詞經(jīng)常出現(xiàn)在方面詞的前面比如“真的好慢這個(gè)件”“太差了你們的登錄”這時(shí)候如果編碼器只按從左到右的順序讀一遍模型在讀到“好慢”的時(shí)候根本不知道后面會(huì)跟一個(gè)“登錄”更談不上把它們的表示對(duì)齊。BRNN的核心做法就是每個(gè)時(shí)間步同時(shí)做一次正向讀入和一次反向讀入最后把兩個(gè)方向的隱藏狀態(tài)拼接起來作為這個(gè)詞的上下文表示。這樣反向讀入的那一路在“真的好慢這個(gè)件”這個(gè)句子里已經(jīng)提前把“件”和“這個(gè)”的信息帶給“慢”這個(gè)字注意力機(jī)制再從拼接后的狀態(tài)里決定“登錄”和“慢”哪個(gè)才值得對(duì)齊。其實(shí)從計(jì)算角度看雙向循環(huán)相比后來的雙向Transformer并沒有結(jié)構(gòu)優(yōu)勢(shì)但在這種長度一般在50個(gè)字符以內(nèi)、句內(nèi)依賴緊密的政務(wù)評(píng)論場景BRNN有兩個(gè)實(shí)打?qū)嵉暮锰幰皇菂?shù)量小幾千條標(biāo)注數(shù)據(jù)就能訓(xùn)得動(dòng)不需要預(yù)訓(xùn)練語言模型動(dòng)輒上億的參數(shù)量往小數(shù)據(jù)集上硬套二是對(duì)錯(cuò)別字的容忍度比注意力機(jī)制好政務(wù)評(píng)論里“身辦”“補(bǔ)帖”“都不了”這種錯(cuò)誤反向掃描時(shí)能借助后面的字形信息兜底。當(dāng)前端到端的思想在NLP里被熱炒從智駕端到端到各種端到端模型本質(zhì)都是“少拆中間步驟”放到這個(gè)任務(wù)上就是不要顯式先跑一個(gè)NER再跑一個(gè)分類器而是直接把“評(píng)論序列→標(biāo)簽序列”的映射一步學(xué)出來。2.2 端到端和流水線方案的選型不是越先進(jìn)越好而是看錯(cuò)誤怎么傳導(dǎo)早期做方面級(jí)情感分析最常見的做法是流水線第一步用NER抽方面詞第二步抽出來的每個(gè)方面詞周圍的文本喂給情感分類器判斷正負(fù)。每步都有自己的標(biāo)注模板和模型好處是每一步可以單獨(dú)調(diào)優(yōu)但坑在錯(cuò)誤傳導(dǎo)第一步把“補(bǔ)貼審批”抽成“補(bǔ)貼”和“審批”兩個(gè)詞第二步又分別給那兩個(gè)詞各判一個(gè)極性最后出來一個(gè)“補(bǔ)貼正向?qū)徟?fù)向”這種結(jié)果在業(yè)務(wù)上根本沒法讀。端到端方案直接把標(biāo)簽序列定義成“B正面、I正面、B負(fù)面、I負(fù)面、O”一個(gè)解碼器同時(shí)輸出邊界和極性錯(cuò)誤不會(huì)從“抽詞”傳導(dǎo)到“判極性”因?yàn)樗鼔焊鶝]有兩個(gè)獨(dú)立階段。但要注意端到端不等于模型自己什么都能學(xué)會(huì)。政務(wù)場景里“補(bǔ)貼”往往承載負(fù)面情緒因?yàn)樵u(píng)論區(qū)里提到補(bǔ)貼基本都是在催極少夸——這種情況模型會(huì)學(xué)到“補(bǔ)貼”出現(xiàn)就等于負(fù)面在樣本不平衡時(shí)尤其危險(xiǎn)。所以選型時(shí)我會(huì)先問一個(gè)問題線上遇到“補(bǔ)貼發(fā)放及時(shí)了”這種少見但確實(shí)存在的正面樣本模型有沒有能力糾偏如果數(shù)據(jù)里正面樣本比例低于5%建議在損失函數(shù)里加類別權(quán)重或者把這部分樣本單獨(dú)抽樣湊到10%以上再進(jìn)訓(xùn)練集這個(gè)我在第四章展開。相比之下流水線方案在每一步可以單獨(dú)重采樣端到端方案必須以整體標(biāo)簽序列為單位處理類別平衡這是做數(shù)據(jù)準(zhǔn)備時(shí)比較容易忽略的一點(diǎn)。2.3 端到端方面級(jí)情感分析的標(biāo)簽體系BIEO序列標(biāo)注是落地首選端到端方案絕大多數(shù)情況下長成“序列標(biāo)注”的樣子。把一條評(píng)論和同長度的標(biāo)簽序列對(duì)齊常用標(biāo)簽有三套BIO、BIES、BIEO。B代表方面詞開頭I代表中間或后續(xù)O代表非方面詞而E代表方面詞最后一個(gè)字S代表單個(gè)字自成方面詞。我的建議是政務(wù)評(píng)論場景直接用四標(biāo)簽BIEO——B、I、E、O就夠了不需要S是因?yàn)檎?wù)評(píng)論里的方面詞極少是單字“補(bǔ)貼”算兩個(gè)字“登錄”兩個(gè)字而單字方面詞可以用B直接帶過后面再看CRF層的viterbi解碼怎么修正。表格對(duì)比如下標(biāo)簽體系標(biāo)簽數(shù)邊界恢復(fù)能力適合場景BIO3類×極性子類需要后處理合并連續(xù)標(biāo)簽粗粒度場景不追求精確方面詞BIEO4類×極性子類強(qiáng)末尾邊界顯式給出端到端ABSA本文采用BIES5類×極性子類最強(qiáng)單字單獨(dú)標(biāo)記長文檔、方面詞長度差異大時(shí)極性落在每個(gè)標(biāo)簽上即B_pos、I_pos、E_pos、B_neg、I_neg、E_neg、O一共七種輸出類別。為什么不拆成兩層LSTM一層判邊界一層判極性這種“級(jí)聯(lián)式”端到端也有論文支持但實(shí)現(xiàn)復(fù)雜而且第二層的錯(cuò)誤來源包含第一層的預(yù)測(cè)序列訓(xùn)練時(shí)一旦邊界錯(cuò)了極性層看到的輸入就跟推理時(shí)不一致容易累積漂移。單層7類別標(biāo)注是最保守、最好復(fù)現(xiàn)、也不容易死鎖的工程方案如果需要細(xì)粒度到“服務(wù)態(tài)度”和“辦事效率”兩個(gè)子方面再在標(biāo)簽上擴(kuò)展成“方面類別極性”的復(fù)合標(biāo)簽也不遲。3. 政務(wù)APP評(píng)論數(shù)據(jù)的四個(gè)特點(diǎn)與標(biāo)注方案落地的完整步驟3.1 先認(rèn)識(shí)數(shù)據(jù)再談模型短句占比高、業(yè)務(wù)名詞集中、情緒內(nèi)斂、錯(cuò)別字常態(tài)化政務(wù)APP評(píng)論和電商評(píng)論差別非常大。電商評(píng)論里“質(zhì)量太差了客服還不管”這種句子方面詞“質(zhì)量”和“客服”都是通用詞可以靠預(yù)訓(xùn)練模型里的先驗(yàn)知識(shí)。政務(wù)評(píng)論則充滿“一網(wǎng)通辦”“掌上辦”“老年專窗”“異地就醫(yī)備案”這類業(yè)務(wù)名詞預(yù)訓(xùn)練語言模型大概率沒有在等價(jià)語義上見過這些詞等于把通用知識(shí)這個(gè)buff直接沒收。另一個(gè)顯著特點(diǎn)是情緒往往是含蓄的“弄了半天還是沒有”“本來以為可以結(jié)果不行”這種組合沒有明顯的負(fù)向詞但從兩個(gè)分句的關(guān)系可以推斷負(fù)面。這種委婉表達(dá)對(duì)基于情感詞典或關(guān)鍵詞匹配的方案來說是災(zāi)難對(duì)端到端學(xué)習(xí)來說反而是好事只要標(biāo)注夠一致模型能學(xué)到否定和轉(zhuǎn)折之間的隱含模式。錯(cuò)別字和方言口語是第三、第四個(gè)特點(diǎn)。政務(wù)APP的老年用戶占比高拼音輸入錯(cuò)誤率遠(yuǎn)超電商用戶“年審”寫成“念審”“預(yù)約”寫成“育約”很常見。處理錯(cuò)別字的底線做到兩件事一是模型輸入不要用按字級(jí)別的預(yù)訓(xùn)練BERT分詞器直接用字作為基本輸入單元這樣“念審”和“年審”在字級(jí)別上仍然共享“審”這個(gè)字的表示不至于整詞OOV直接掉線二是詞向量初始化如果不用預(yù)訓(xùn)練就自己在大規(guī)模未標(biāo)注評(píng)論上用word2vec的CBOW刷一遍能讓錯(cuò)別字在向量空間里靠近正確詞。3.2 數(shù)據(jù)標(biāo)注方案JSONL格式、BIEO標(biāo)簽和字段設(shè)計(jì)進(jìn)入標(biāo)注環(huán)節(jié)前最優(yōu)做法是先用規(guī)則快速預(yù)標(biāo)注一批再讓人工修改而不是讓人從頭寫。一個(gè)實(shí)用樣本格式是每一行一條JSON包含text、id和label三個(gè)字段其中l(wèi)abel是和text中的字符一一對(duì)應(yīng)的標(biāo)簽數(shù)組。以下是一個(gè)jsonl格式的樣本示例{id: 20230501-001, text: 補(bǔ)貼審批也太慢了, label: [B_neg, I_neg, E_neg, O, O, O, O, O, O]}文本按字符切分得到“補(bǔ)”“貼”“審”“批”“也”“太”“慢”“了”八個(gè)字前三個(gè)字“補(bǔ)貼審”并不構(gòu)成完整的方面詞——真正要標(biāo)記方面詞是“補(bǔ)貼審批”四個(gè)字標(biāo)簽序列里B_neg在“補(bǔ)”上I_neg在“貼”和“審”上E_neg落在“批”上后面“也太慢了”全部是O。這里有個(gè)關(guān)鍵約定不做“觀點(diǎn)詞”標(biāo)注只標(biāo)“方面詞極性”觀點(diǎn)詞的作用由模型通過注意力機(jī)制去學(xué)這樣標(biāo)注成本降低三分之一任務(wù)難度也回歸ABSA的本來定義。翻車點(diǎn)提醒政務(wù)場景里“服務(wù)”這個(gè)詞經(jīng)常出現(xiàn)在“政務(wù)服務(wù)”和“服務(wù)態(tài)度很好”兩種語境里前者是泛指后者是具體方面。標(biāo)注規(guī)范里要明確只有可以被后續(xù)后續(xù)處置的“部門/功能/物料”才是方面詞“政務(wù)服務(wù)”“政務(wù)APP”這種泛稱一律標(biāo)O否則模型學(xué)到的是標(biāo)簽噪聲。一句話同時(shí)出現(xiàn)兩個(gè)功能模塊時(shí)比如“預(yù)約成了但定位老是歪”應(yīng)該標(biāo)出“預(yù)約”和“定位”兩個(gè)獨(dú)立的方面詞各自帶極性。3.3 弱標(biāo)注規(guī)則輔助用關(guān)鍵詞字典預(yù)生成候選標(biāo)簽人工只做修正為了提升標(biāo)注效率可以先寫一個(gè)基于規(guī)則和關(guān)鍵詞匹配的預(yù)標(biāo)注腳本生成候選標(biāo)簽再由標(biāo)注人員做批量檢查和修改。腳本邏輯很簡單讀jsonl文件對(duì)每條text查找一個(gè)關(guān)鍵詞字典比如“補(bǔ)貼”“審批”“登錄”“閃退”對(duì)應(yīng)預(yù)設(shè)方面詞字典再配合否定詞和積極/消極詞表來推斷極性。下面是實(shí)用實(shí)現(xiàn)思路import json import re ASPECT_KEYWORDS [補(bǔ)貼, 審批, 登錄, 定位, 預(yù)約, 閃退, 卡頓, 電話, 窗口] POS_WORDS [好, 快, 方便, 順利, 滿意] NEG_WORDS [慢, 難, 煩, 卡, 閃退, 失敗, 不行, 無人, 沒] def weak_label(text): labels [O] * len(text) for k in ASPECT_KEYWORDS: start text.find(k) if start -1: continue end start len(k) for i in range(start, end): if i start: labels[i] B_neg # 默認(rèn)按負(fù)面預(yù)標(biāo)人工修正 elif i end - 1: labels[i] E_neg else: labels[i] I_neg # 如果句中有正向詞人工會(huì)在檢查時(shí)調(diào)整為B_pos return labels def convert_line(line): obj json.loads(line) labels weak_label(obj[text]) return {id: obj[id], text: obj[text], label: labels}這里默認(rèn)把命中的方面詞預(yù)標(biāo)成負(fù)面而不是先掃描情感詞原因在于政務(wù)場景的真實(shí)分布里方面詞與負(fù)面同時(shí)出現(xiàn)的比例遠(yuǎn)高于與正面同時(shí)出現(xiàn)的比例預(yù)標(biāo)負(fù)面能減少人工修正的擊鍵次數(shù)。人工檢查時(shí)如果發(fā)現(xiàn)“補(bǔ)貼審批快”這種句子把B_neg改成B_pos后續(xù)I和E同步改成I_pos和E_pos。注意這個(gè)弱規(guī)則腳本只用于“標(biāo)注輔助”不是線上推理模型千萬不能用規(guī)則結(jié)果直接評(píng)估模型效果。標(biāo)注完成后的質(zhì)檢環(huán)節(jié)我會(huì)要求一致性檢查同一批數(shù)據(jù)讓兩個(gè)人各標(biāo)一遍按字符級(jí)別的標(biāo)簽序列算一致性Kappa系數(shù)政務(wù)場景目標(biāo)是0.8以上如果達(dá)不到就回到標(biāo)注規(guī)范里查定義矛盾點(diǎn)多半出在“泛稱不標(biāo)”和“復(fù)合詞拆不拆”這兩條規(guī)則上。4. 端到端訓(xùn)練一個(gè)最小可用的BRNN方面級(jí)情感分類模型4.1 模型結(jié)構(gòu)設(shè)計(jì)Embedding層雙向RNN編碼器CRF解碼一個(gè)都不能少整個(gè)模型的輸入是一條評(píng)論字符串輸出是等長的標(biāo)簽序列。先說結(jié)構(gòu)再給代碼。輸入經(jīng)過一個(gè)字符Embedding層把每個(gè)字映射成一個(gè)300維的向量然后把向量序列喂給一個(gè)雙向的GRUBRNN用GRU比LSTM在短文本上更穩(wěn)參數(shù)少一截容易收斂雙向GRU每個(gè)時(shí)間步把正向和反向兩個(gè)隱藏狀態(tài)拼接得到該字符的上下文表示。這個(gè)表示再經(jīng)過一個(gè)全連接層輸出到七個(gè)類別B_pos、I_pos、E_pos、B_neg、I_neg、E_neg、O的發(fā)射分?jǐn)?shù)最后接一個(gè)條件隨機(jī)場層在校驗(yàn)標(biāo)簽轉(zhuǎn)移合法性的基礎(chǔ)上輸出最優(yōu)序列例如B_neg后面不能直接跟O必須跟I_neg或E_neg。這套結(jié)構(gòu)在學(xué)術(shù)界有個(gè)常用稱呼“BiGRU-CRF”是比“BiLSTM-CRF”更輕的變體在短文本任務(wù)上效果幾乎持平但收斂速度更快。CRF層在這里不是錦上添花它保證輸出標(biāo)簽的序列合法性。沒有CRF時(shí)模型可能輸出“B_neg O E_neg”這種在標(biāo)簽界面上無法解析成有始有終方面詞的結(jié)果有了CRF轉(zhuǎn)移矩陣會(huì)被約束成“只有合法轉(zhuǎn)移的分?jǐn)?shù)高”解碼階段用維特比算法找出整體最優(yōu)路徑。下面是訓(xùn)練腳本里定義網(wǎng)絡(luò)結(jié)構(gòu)的關(guān)鍵部分import torch import torch.nn as nn from torchcrf import CRF class BRNN_ABSA(nn.Module): def __init__(self, vocab_size, emb_size300, hidden_size256, num_labels7): super().__init__() self.embed nn.Embedding(vocab_size, emb_size, padding_idx0) self.gru nn.GRU(emb_size, hidden_size // 2, batch_firstTrue, bidirectionalTrue) self.fc nn.Linear(hidden_size, num_labels) self.crf CRF(num_labels, batch_firstTrue) def forward(self, x, mask, labelsNone): emb self.embed(x) h, _ self.gru(emb) emissions self.fc(h) if labels is not None: return -self.crf(emissions, labels, maskmask) # 負(fù)對(duì)數(shù)似然損失 return self.crf.decode(emissions, maskmask) # 推理時(shí)viterbi解碼這里hidden_size選256雙向GRU把隱藏維切成兩半每向128維拼接后還是256維這樣全連接層的輸入維度跟隱含層輸出自然對(duì)齊。batch_firstTrue表示張量形狀為(句子數(shù), 最大句長, 特征數(shù))避免transpose操作帶來的隱性bug。7個(gè)標(biāo)簽的索引順序需要在訓(xùn)練前固定建議按這個(gè)順序排列[O, B_pos, I_pos, E_pos, B_neg, I_neg, E_neg]CRF的轉(zhuǎn)移矩陣會(huì)自己學(xué)習(xí)哪些轉(zhuǎn)移合法不合法轉(zhuǎn)移的分?jǐn)?shù)會(huì)被壓低而不是直接置零這一點(diǎn)和硬規(guī)則Mask有區(qū)別Beam上沒必要做額外約束。損失函數(shù)直接利用CRF的負(fù)對(duì)數(shù)似然字符串越長CRF得分越低所以mask的作用邊界必須正確填充部分pad位置標(biāo)簽全是O同時(shí)mask把它們掩蓋掉防止CRF統(tǒng)計(jì)到無意義的pad轉(zhuǎn)移。4.2 訓(xùn)練參數(shù)和優(yōu)化器選擇小學(xué)習(xí)率、梯度裁剪、驗(yàn)證集優(yōu)先政務(wù)評(píng)論數(shù)據(jù)集規(guī)模一般不大幾千條到幾萬條之間。針對(duì)這種規(guī)模我通常建議直接用AdamW優(yōu)化器學(xué)習(xí)率取2e-3級(jí)別配合線性warmup前10%的batch做warmup和梯度裁剪。梯度裁剪是BRNN這類循環(huán)網(wǎng)絡(luò)的重中之重原因是雙向GRU在反向傳播時(shí)梯度沿時(shí)間步流動(dòng)容易出現(xiàn)爆炸特別在句長參差不齊的batch里長句的梯度活活把短句的normalization沖掉。clip_norm設(shè)為5.0即可。另一個(gè)關(guān)鍵參數(shù)是batch size一般取32到64之間如果句子被pad到同樣長度很多算力浪費(fèi)在O標(biāo)簽到pad字符的過渡區(qū)域可以按長度排序分桶然后再做mini-batch實(shí)際能省將近一半的訓(xùn)練時(shí)間。代碼里需要特別小心的是dataloader的默認(rèn)行為它按索引直接取樣本不保證一個(gè)batch里的句子長度接近。自定義一個(gè)簡單的bucket sampler給訓(xùn)練數(shù)據(jù)排序每輪訓(xùn)練前shuffle但保持桶內(nèi)順序即可。訓(xùn)練輪數(shù)epoch設(shè)在30左右早停機(jī)制用驗(yàn)證集F1驗(yàn)證集不要和測(cè)試集混用。端到端模型還有一個(gè)常見隱性病訓(xùn)練集規(guī)模小時(shí)標(biāo)簽分布嚴(yán)重偏向O導(dǎo)致模型幾乎把所有token都預(yù)測(cè)成O方面詞全漏。解決辦法是計(jì)算各類別權(quán)重將loss按類別權(quán)重乘一遍或者把負(fù)樣本采樣下溢——這句話最實(shí)操的意思就是把O類樣本的占比壓到序列長度的70%以內(nèi)比如優(yōu)先保留含方面詞的樣本讓不含任何方面詞的純?cè)肼曉u(píng)論占比小于總數(shù)據(jù)量的30%。4.3 用驗(yàn)證集決定訓(xùn)練停點(diǎn)為什么不看loss要看F1訓(xùn)練日志里最常見的誤導(dǎo)是loss還在下降但驗(yàn)證集F1已經(jīng)不再漲甚至掉了。這是因?yàn)镃RF層的負(fù)對(duì)數(shù)似然在擬合訓(xùn)練樣本的具體轉(zhuǎn)移特征而驗(yàn)證集的方面詞邊界分布和訓(xùn)練集并不完全一致訓(xùn)練到后期模型在死記轉(zhuǎn)移規(guī)則而不是泛化。我的習(xí)慣是每個(gè)epoch結(jié)束跑一次驗(yàn)證集計(jì)算三個(gè)指標(biāo)方面詞級(jí)別的精確率、召回率、F1——方面詞級(jí)別的意思是把一個(gè)完整方面詞當(dāng)成一個(gè)樣本單元比對(duì)而不是按字統(tǒng)計(jì)這樣才貼合業(yè)務(wù)口徑。按字統(tǒng)計(jì)的準(zhǔn)確率很容易到98%但那是被O標(biāo)簽稀釋的水分。方面詞級(jí)別的F1低于0.75時(shí)模型基本不能直接給業(yè)務(wù)部門用需要回到數(shù)據(jù)或模型結(jié)構(gòu)層面找原因。驗(yàn)證時(shí)的解碼輸出是一組標(biāo)簽序列解析成方面詞需要做一步后處理從左到右掃描標(biāo)簽遇到B開頭的標(biāo)簽啟動(dòng)一個(gè)方面詞候選遇到I繼續(xù)拼接到候選尾遇到E停止并把候選詞與其極性一起存下來若遇到O時(shí)上一個(gè)候選還沒遇到E說明是個(gè)殘次序列直接丟棄該候選。丟棄這些殘次候選有助于防止模型自造方面詞也方便在測(cè)試集上分析錯(cuò)誤模式是邊界錯(cuò)誤還是極性錯(cuò)誤。下面的代碼展示了批次推理和結(jié)果解析邏輯def decode_to_aspects(text, labels, id2tag): aspects [] cur None cur_polarity None for ch, tag_id in zip(text, labels): tag id2tag[tag_id] if tag.startswith(B): cur, cur_polarity ch, tag.split(_)[1] elif tag.startswith(I) and cur is not None: cur ch elif tag.startswith(E) and cur is not None: cur ch aspects.append((cur, cur_polarity)) cur None else: cur None if cur is not None: # 出現(xiàn)E缺失直接丟棄 pass return aspects這里id2tag是把數(shù)字索引映射回標(biāo)簽名的字典例如“2→B_pos”。輸出一個(gè)列表每個(gè)元素是(方面詞, 極性)。后處理時(shí)不保留殘次候選這樣統(tǒng)計(jì)F1的分子更干凈也更貼近業(yè)務(wù)需要。測(cè)試集上輸出這個(gè)結(jié)構(gòu)后可以做各種維度報(bào)告比如按極性正誤分列、按方面詞詞頻排序生成“高頻負(fù)面方面詞TOP10”這就是前面說的能直接發(fā)工單的結(jié)果形態(tài)。5. 政務(wù)評(píng)論端到端方面級(jí)情感分析的5個(gè)必踩坑與排查方案5.1 標(biāo)注的坑“服務(wù)”到底是方面詞還是泛稱標(biāo)準(zhǔn)不統(tǒng)一現(xiàn)象訓(xùn)練集F1很高線上抽取的方面詞列表里卻出現(xiàn)“政務(wù)服務(wù)”“公共服務(wù)”這類沒有處置對(duì)象的詞組。原因標(biāo)注規(guī)范對(duì)“泛稱”的定義沒有落到具體詞例上不同標(biāo)注員各按各的手感隨意標(biāo)。解決在標(biāo)注規(guī)范里明確幾類不能標(biāo)為方面詞的情況類的統(tǒng)稱服務(wù)、辦事、系統(tǒng)、平臺(tái)、狀態(tài)詞方便、好用、復(fù)雜、否定結(jié)構(gòu)里的否定詞本身。同時(shí)準(zhǔn)備一份“禁用方面詞清單”清單里的詞即使出現(xiàn)在負(fù)面語境里也一律標(biāo)O線上推理時(shí)再用同一個(gè)清單過濾一遍輸出結(jié)果雙保險(xiǎn)。5.2 沒有企業(yè)級(jí)詞表時(shí)錯(cuò)別字把整個(gè)模型帶偏現(xiàn)象模型頻繁把“念審”識(shí)別成方面詞“念審”且極性負(fù)面而正確的“年審”沒有被識(shí)別出來。原因字向量里“念”和“年”的距離太遠(yuǎn)模型沒有語言先驗(yàn)把它們拉近。解決在自己的未標(biāo)注評(píng)論語料上用gensim的word2vec跑二十輪CBOW訓(xùn)練字向量字符級(jí)別的共現(xiàn)信息會(huì)把經(jīng)常出現(xiàn)在相同上下文的“念”“年”擠到相鄰位置這一步成本極低但受益明顯。如果還不行就把高頻錯(cuò)別字對(duì)做成一個(gè)映射表在預(yù)處理階段直接做標(biāo)準(zhǔn)形替換。5.3 數(shù)據(jù)太少的場景l(fā)oss飄紅不下降現(xiàn)象用BiGRU-CRF在只有800條人工標(biāo)注數(shù)據(jù)上訓(xùn)練驗(yàn)證F1始終在0.2徘徊loss幾乎不降。原因BRNN的參數(shù)量相對(duì)于數(shù)據(jù)量仍然偏大雙層正向反向疊加后更難擬合。解決先把模型縮減到隱藏維128Embedding降到100維然后看訓(xùn)練集里的標(biāo)簽分布如果負(fù)面樣本占絕對(duì)主導(dǎo)可以考慮把O標(biāo)簽之外的正負(fù)樣本過采樣。網(wǎng)絡(luò)結(jié)構(gòu)上還有一個(gè)省參數(shù)的做法是共享雙向GRU的權(quán)重兩個(gè)方向用同一組參數(shù)但輸入順序反轉(zhuǎn)參數(shù)量直接減半數(shù)據(jù)不足時(shí)比滿血版穩(wěn)得多。5.4 “服務(wù)器繁忙”這類噪聲評(píng)論把負(fù)面極性傳染給“服務(wù)器”現(xiàn)象線上報(bào)告里排名最高的負(fù)面方面詞是“系統(tǒng)”“服務(wù)器”但業(yè)務(wù)部門反饋沒人投訴過服務(wù)器而是在自動(dòng)彈窗提示“服務(wù)器繁忙請(qǐng)稍后再試”后抱怨登錄失敗。原因模型把彈窗里的提示文本本身當(dāng)成用戶輸入的評(píng)論彈窗里的“服務(wù)器”“繁忙”被標(biāo)注成負(fù)面方面詞而這根本不是用戶手動(dòng)輸入的。解決預(yù)處理階段加兩條硬規(guī)則過濾掉長度小于5字符的評(píng)論過濾掉內(nèi)容完全由彈窗提示、站內(nèi)公告、自動(dòng)回復(fù)組成的文本。更穩(wěn)妥的方案是在數(shù)據(jù)采集時(shí)只保留用戶在輸入框里手動(dòng)提交的內(nèi)容排除系統(tǒng)自動(dòng)截?cái)嗟漠惓N谋尽?.5 不要為了省事先去掉CRF層邊界亂飛的根因就在這現(xiàn)象去掉CRF后測(cè)試集F1從0.82降到0.76且輸出里頻繁出現(xiàn)“B_neg O E_neg”這類無法收斂的序列。原因沒有CRF約束每個(gè)token的分類是獨(dú)立的模型沒有學(xué)標(biāo)簽之間的轉(zhuǎn)移關(guān)系。解決把CRF層加回來同時(shí)檢查batch里的mask是否跟標(biāo)簽一致。很多資料里說“CRF是可選優(yōu)化”但在端到端ABSA任務(wù)里它不是優(yōu)化而是標(biāo)配因?yàn)檩敵霰仨毥Y(jié)構(gòu)化成“有始有終”的方面詞序列才可交割。檢查mask和標(biāo)簽同步的代碼其實(shí)就一步把mask張量和標(biāo)簽中O的位置對(duì)齊兩個(gè)張量必須嚴(yán)格匹配否則CRF會(huì)偷偷給pad位置分配極性標(biāo)簽而不報(bào)錯(cuò)。6. 把模型交給業(yè)務(wù)之前注意力排查、固定短語過濾和驗(yàn)證集復(fù)盤方法模型訓(xùn)完F1達(dá)標(biāo)并不是交付的終點(diǎn)。政務(wù)評(píng)論這個(gè)場景有個(gè)特殊點(diǎn)業(yè)務(wù)方拿著模型結(jié)果去寫報(bào)告他們看不懂注意力機(jī)制更看不懂發(fā)射分?jǐn)?shù)他們要的是抽查時(shí)能還原出“為什么這條評(píng)論被歸為負(fù)面”。這時(shí)候有兩個(gè)手段最管用一是把每個(gè)方面詞的注意力權(quán)重最大的23個(gè)上下文詞挑出來生成一個(gè)“證據(jù)詞”字段隨同報(bào)告一起展示二是把高頻出現(xiàn)的固定說法整理成短語清單比如“怎么還不好”“又崩了”“太慢了”這些作為規(guī)則輔助補(bǔ)充到報(bào)告里給業(yè)務(wù)方的直覺判斷打底。如果一條評(píng)論被模型標(biāo)成負(fù)面但證據(jù)詞是“挺好”說明邊界標(biāo)簽或極性標(biāo)簽標(biāo)注有錯(cuò)誤需要回溯到訓(xùn)練集而不是急著調(diào)模型。驗(yàn)證集復(fù)盤還有個(gè)很有效的習(xí)慣按方面詞出現(xiàn)的頻次分組看F1。政務(wù)評(píng)論里低頻方面詞“跨省結(jié)算”“異地備案”的F1往往比高頻的“登錄”“閃退”低一大截原因不是模型學(xué)不會(huì)低頻詞而是訓(xùn)練數(shù)據(jù)里低頻詞的樣本太少CRF學(xué)到的是“任何詞只要在負(fù)面句子中出現(xiàn)就有概率成為方面詞”這種模糊規(guī)則。應(yīng)對(duì)辦法是給低頻方面詞句子做SMOTE類別的過采樣或者直接用正則把這些固定搭配在預(yù)處理時(shí)合并成不可拆分的詞根比如“異地就醫(yī)備案”整體作為一個(gè)輸入單元。這個(gè)操作在推理時(shí)同樣生效讓邊界預(yù)測(cè)少一個(gè)不穩(wěn)定因素。最后說說我自己的血淚經(jīng)驗(yàn)第一版方案在測(cè)試集F1達(dá)到0.85給業(yè)務(wù)方做現(xiàn)場演示時(shí)輸入一條真實(shí)評(píng)論“弄了半天卡死了還要重新填”模型輸出了“卡死”負(fù)面看起來沒問題但業(yè)務(wù)方隨手又補(bǔ)了“錢什么時(shí)候到賬”這句話模型完全沒輸出“錢”這個(gè)方面詞。原因很直接訓(xùn)練集里“錢”這個(gè)詞出現(xiàn)太少且沒跟“到賬”組合出現(xiàn)。后來我把語料里所有“錢”“到賬”“入賬”“打款”統(tǒng)一歸一化成“MONEY”占位符再進(jìn)模型這類泛金融詞的召回率從0.5直接拉到0.8。中文NLP里做歸一化常被認(rèn)為會(huì)丟失語義但在這個(gè)場景里業(yè)務(wù)方要的“錢什么時(shí)候到賬”本身就是一個(gè)高頻核心意圖歸一化是讓低頻語義聚焦的正確做法。希望這個(gè)思路幫你在類似政務(wù)文本的ABSA項(xiàng)目里少走一段彎路。本文還有配套的精品資源點(diǎn)擊獲取