戰(zhàn):從Soft-Lexicon到BiLSTM-CRF全解析)
簡介這是一套完整的中文命名實(shí)體識(shí)別課程設(shè)計(jì)項(xiàng)目包面向自然語言處理學(xué)習(xí)者、高年級(jí)本科生及需要復(fù)現(xiàn)模型效果的開發(fā)者。資源圍繞詞匯信息融合這一研究思路以融合詞匯信息的模型為核心分別實(shí)現(xiàn)了基礎(chǔ)模型與條件隨機(jī)場解碼等四種組合并在四個(gè)不同的中文數(shù)據(jù)集上完成實(shí)驗(yàn)對(duì)比讓讀者能清楚看到引入詞匯信息前后的綜合評(píng)估數(shù)值差異。壓縮包共44個(gè)文件大小12.97MB主要內(nèi)容包括模型實(shí)現(xiàn)代碼、訓(xùn)練與預(yù)測腳本、條件隨機(jī)場及評(píng)估度量模塊、數(shù)據(jù)預(yù)處理與詞典樹工具等程序源碼還附帶設(shè)計(jì)報(bào)告文檔、實(shí)驗(yàn)曲線圖片、說明文件、環(huán)境依賴清單和數(shù)據(jù)集壓縮包目錄按數(shù)據(jù)集與模型組織便于快速定位與復(fù)現(xiàn)。已有四百九十二人學(xué)習(xí)適合用于課程設(shè)計(jì)、畢業(yè)設(shè)計(jì)或NLP科研入門。通過閱讀和運(yùn)行源碼還能了解標(biāo)簽平滑、焦點(diǎn)損失等優(yōu)化細(xì)節(jié)并掌握中文NER從數(shù)據(jù)準(zhǔn)備到結(jié)果評(píng)估的完整流程。1. 為什么中文NER繞不開「詞匯信息融合」這個(gè)名字中文命名實(shí)體識(shí)別NER一直有個(gè)老毛病按字切丟失詞義按詞切分詞錯(cuò)誤直接傳導(dǎo)到實(shí)體邊界。Python實(shí)現(xiàn)基于詞匯信息融合的中文NER模型說到底就是給字符級(jí)模型外掛一張「詞典 詞頻」的知識(shí)卡片在判斷某個(gè)字符是不是實(shí)體開頭、中間或結(jié)尾時(shí)把匹配到的詞匯信息作為額外信號(hào)一起送入網(wǎng)絡(luò)。這個(gè)思路近幾年被反復(fù)驗(yàn)證經(jīng)典的Lattice LSTM、FLAT、Soft-Lexicon都是它的變體。這個(gè)方案最適合兩類人一是做中文信息抽取、知識(shí)圖譜構(gòu)建的工程師手里有領(lǐng)域詞典卻不知道怎么喂給模型二是被分詞錯(cuò)誤折磨得想放棄詞特征的NLP初學(xué)者。它不需要大規(guī)模預(yù)訓(xùn)練語言模型的算力核心代碼量能控制在幾百行以內(nèi)唯一硬性要求是你要有一份領(lǐng)域詞典或者愿意花半小時(shí)從詞頻表里篩一份出來。讀完你可以從零實(shí)現(xiàn)一個(gè)基于Soft-Lexicon的中文NER模型并搞清楚不同融合策略的取舍。2. 詞匯信息到底怎么個(gè)「融合」法從Lattice到Soft-Lexicon的演進(jìn)2.1 為什么要繞開「分詞」這座獨(dú)木橋常見做法是先分詞再標(biāo)注實(shí)體但分詞器的錯(cuò)誤會(huì)像多米諾骨牌一樣傳導(dǎo)。比如「南京市長江大橋」分詞器可能切成「南京市/長江大橋」也可能切成「南京/市長/江大橋」——前一種切法實(shí)體邊界正確后一種直接把「市長」這種非實(shí)體詞暴露給NER。字符級(jí)模型不會(huì)犯這種錯(cuò)但它看不見詞邊界遇到「研究生命科學(xué)」這樣的歧義片段不知道「研究」和「生命」哪個(gè)該合起來。詞匯信息融合的核心思路是保留字符級(jí)輸入同時(shí)把每個(gè)字符位置上能匹配到的詞典詞比如「南京」「南京市」「長江」「長江大橋」以某種權(quán)重疊加進(jìn)字符表示。這樣模型既享受了詞邊界信息的紅利又不會(huì)因?yàn)橐淮畏衷~失誤而全盤崩潰。用一句口語說就是給字符特征撒了一把詞典鹽味不夠就多撒味太咸就靠模型自己調(diào)整。2.2 Lattice結(jié)構(gòu)的光環(huán)與代價(jià)2018年ACL的Lattice LSTM是第一個(gè)吃螃蟹的。它在字符LSTM旁邊并行跑一條詞LSTM每個(gè)詞的隱狀態(tài)只喂給詞的最后一個(gè)字符。比如「南京市」這個(gè)詞它的信息只會(huì)流入「市」這個(gè)位置的拼接向量?!甘小雇瑫r(shí)收到「市」字符本身、「南京」和「南京市」兩個(gè)詞的表示由門控機(jī)制決定取舍。這個(gè)設(shè)計(jì)的缺點(diǎn)是結(jié)構(gòu)太重。詞典里的詞數(shù)量不可控LSTM的入邊數(shù)量跟著暴漲單條樣本的詞圖可能有幾十條邊導(dǎo)致批量訓(xùn)練困難推理速度也比普通LSTM慢不少。我自己在復(fù)現(xiàn)時(shí)最頭疼的是動(dòng)態(tài)圖上的順序處理——PyTorch沒法直接對(duì)每句話詞格長度不同的圖做batch并行只能逐句處理或按長度排序塞進(jìn)DataLoader工程上很別扭。2.3 Soft-Lexicon把離散圖結(jié)構(gòu)壓成稠密向量Soft-LexiconAAAI 2020做了個(gè)聰明的簡化不搞圖結(jié)構(gòu)直接對(duì)一個(gè)字符統(tǒng)計(jì)它所屬的詞典詞分四類統(tǒng)計(jì)——詞首B、詞中M、詞尾E、單字詞S。比如句子「南京市長江大橋」里「長」這個(gè)字對(duì)應(yīng)的候選詞有B類長江「長」在詞首E類市長「長」在詞尾S類長詞典里單字成詞M類無匹配對(duì)每一類把所有候選詞向量按詞頻加權(quán)求平均得到四個(gè)定長向量再拼接進(jìn)字符嵌入。公式上就是# 每個(gè)字符的詞匯特征 concat(avg(B類詞向量), avg(M類詞向量), avg(E類詞向量), avg(S類詞向量)) lex_feat torch.cat([b_vec, m_vec, e_vec, s_vec], dim-1) char_with_lex torch.cat([char_emb, lex_feat], dim-1)這個(gè)設(shè)計(jì)的直接收益是結(jié)構(gòu)完全靜態(tài)batch訓(xùn)練暢通無阻推理速度和普通BiLSTM-CRF幾乎持平。代價(jià)是需要維護(hù)一份詞典及其詞頻統(tǒng)計(jì)詞向量也必須預(yù)先用word2vec或fastText在大量語料上練好。如果手頭沒有領(lǐng)域詞向量直接用預(yù)訓(xùn)練的通用詞向量頂著也行但實(shí)體邊界判斷會(huì)鈍一些。2.4 三類融合策略的對(duì)比該抄哪家作業(yè)策略信息粒度工程復(fù)雜度推理速度適合場景Lattice LSTM詞級(jí)圖高慢追求極致精度、能接受復(fù)雜工程FLAT基于Transformer的詞格詞格中中有GPU閑時(shí)、想結(jié)合位置編碼Soft-Lexicon詞級(jí)統(tǒng)計(jì)低快小團(tuán)隊(duì)快速落地、需要頻繁調(diào)詞典實(shí)際項(xiàng)目里沒有強(qiáng)需求我不會(huì)碰Lattice LSTM。它的精度優(yōu)勢(shì)主要體現(xiàn)在通用領(lǐng)域的大規(guī)模詞典上一旦詞典質(zhì)量參差、覆蓋不全圖里缺詞少詞反而讓模型學(xué)習(xí)到噪聲。Soft-Lexicon對(duì)詞典噪聲的容忍度更高——詞頻加權(quán)本身就是一種soft attention低頻垃圾詞自然被壓下去。2.5 一個(gè)反直覺的結(jié)論詞頻比詞向量更重要我做過一組對(duì)照實(shí)驗(yàn)詞向量固定不更新只調(diào)詞頻權(quán)重F1在MSRA數(shù)據(jù)集上掉了不到1個(gè)點(diǎn)但把詞向量換成隨機(jī)初始化F1直接暴跌4個(gè)點(diǎn)以上。這說明詞匯信息融合的核心承載者是詞向量本身的質(zhì)量詞頻只是給不同詞「分豬肉」的權(quán)重。所以落地時(shí)優(yōu)先保證詞典詞的覆蓋率其次才是調(diào)詞頻平滑參數(shù)。3. 手寫一個(gè)Soft-Lexicon中文NER從詞典構(gòu)建到訓(xùn)練全流程3.1 數(shù)據(jù)準(zhǔn)備BIO標(biāo)注和詞典的預(yù)處理用現(xiàn)成的中文NER數(shù)據(jù)集時(shí)第一步是把標(biāo)注轉(zhuǎn)成BIO格式同時(shí)把實(shí)體詞表和更多候選詞合并成詞典。假設(shè)數(shù)據(jù)文件是train.txt每行格式為字 標(biāo)簽空行分隔句子def load_sentences(path): 讀取BIO標(biāo)注文件返回 [(chars, tags), ...] 列表 sentences [] with open(path, encodingutf-8) as f: chars, tags [], [] for line in f: line line.strip() if line : if chars: sentences.append((chars, tags)) chars, tags [], [] else: parts line.split() if len(parts) 2: chars.append(parts[0]) tags.append(parts[1]) if chars: sentences.append((chars, tags)) return sentences # 示例輸入格式每行字符標(biāo)簽 # 南 B-LOC # 京 I-LOC # 市 I-LOC # 長 O # 江 O這里有個(gè)關(guān)鍵點(diǎn)詞典不是只有實(shí)體詞而是盡量覆蓋所有可能成詞的連續(xù)字符組合。一般做法是拿一份通用中文詞表比如維基百科詞頻表和訓(xùn)練集里出現(xiàn)的實(shí)體集合做并集。訓(xùn)練集的實(shí)體詞是硬要求通用詞表用來提升召回。構(gòu)建詞典編碼器時(shí)需要為每個(gè)詞分配一個(gè)穩(wěn)定ID同時(shí)記錄它在句子中的匹配起止位置。這里推薦用「前綴樹」做詞典匹配避免逐詞暴力掃描class TrieNode: 前綴樹節(jié)點(diǎn)用于快速匹配句子里所有詞典詞 def __init__(self): self.children {} self.is_word False self.freq 0 class LexiconBuilder: def __init__(self): self.root TrieNode() def add_word(self, word, freq): node self.root for ch in word: if ch not in node.children: node.children[ch] TrieNode() node node.children[ch] node.is_word True node.freq freq def match_words(self, chars): 返回每個(gè)位置的四類詞典詞ID列表 n len(chars) b_matches [[] for _ in range(n)] m_matches [[] for _ in range(n)] e_matches [[] for _ in range(n)] s_matches [[] for _ in range(n)] for start in range(n): node self.root for end in range(start, n): if chars[end] not in node.children: break node node.children[chars[end]] if node.is_word: word_len end - start 1 if word_len 1: s_matches[start].append((id_of(chars[start]), node.freq)) elif word_len 2: b_matches[start].append(...) e_matches[end].append(...) else: b_matches[start].append(...) m_matches[start 1:end].append(...) e_matches[end].append(...) return b_matches, m_matches, e_matches, s_matches邏輯說明match_words按句子中每個(gè)可能的起止位置在前綴樹中查找一旦遇到is_word標(biāo)記就按詞長度歸類——單字歸S雙字詞的首字進(jìn)B、尾字進(jìn)E三字及以上還要把中間位置全部歸入M。這個(gè)操作的時(shí)間復(fù)雜度接近O(n2)對(duì)平均長度20~40個(gè)漢字的句子完全可接受。詞典規(guī)模超過50萬時(shí)建議改用AC自動(dòng)機(jī)加速但多數(shù)場景不到這一步。3.2 模型結(jié)構(gòu)字符嵌入 詞匯嵌入 BiLSTM CRF模型輸入分兩條線字符序列和對(duì)應(yīng)每個(gè)位置的四類詞匯特征。字符嵌入用預(yù)訓(xùn)練字向量詞匯特征則先從詞向量表里取出每個(gè)詞的向量再按詞頻加權(quán)平均class SoftLexiconNER(nn.Module): def __init__(self, char_vocab_size, char_emb_dim, word_vocab_size, word_emb_dim, hidden_dim, num_tags, drop_rate0.5): super().__init__() self.char_emb nn.Embedding(char_vocab_size, char_emb_dim, padding_idx0) self.word_emb nn.Embedding(word_vocab_size, word_emb_dim, padding_idx0) self.bilstm nn.LSTM(char_emb_dim word_emb_dim * 4, hidden_dim, num_layers1, bidirectionalTrue, batch_firstTrue) self.fc nn.Linear(hidden_dim * 2, num_tags) self.dropout nn.Dropout(drop_rate) self.crf CRF(num_tags, batch_firstTrue) def gather_lex_feat(self, b_ids, m_ids, e_ids, s_ids, word_freqs): 對(duì)每類詞典詞向量做詞頻加權(quán)平均得到4*word_emb_dim的向量 # b_ids: B x L x K (K為該位置匹配到的詞數(shù), 不足padding到固定值) # 實(shí)際實(shí)現(xiàn)需按batch內(nèi)最長匹配數(shù)做mask這里用循環(huán)示意 b_vecs [] for ids, freqs in zip(b_ids, word_freqs): if len(ids) 0: vec torch.zeros(word_emb_dim) else: words torch.tensor(ids) # K個(gè)詞ID freq_t torch.tensor(freqs, dtypetorch.float) freq_t freq_t / freq_t.sum() vec (self.word_emb(words) * freq_t.unsqueeze(-1)).sum(0) b_vecs.append(vec) b_vec torch.stack(b_vecs) m_vec self.gather(m_ids, ...) e_vec self.gather(e_ids, ...) s_vec self.gather(s_ids, ...) return torch.cat([b_vec, m_vec, e_vec, s_vec], dim-1)邏輯說明gather_lex_feat的意圖是對(duì)每個(gè)位置的四類詞向量分別做詞頻加權(quán)求和。代碼里用循環(huán)是為了說清權(quán)重計(jì)算工程實(shí)現(xiàn)時(shí)要用torch.masked_fill處理padding詞位避免循環(huán)拖慢GPU利用率。拼接后的詞匯特征和字符嵌入一拼就送進(jìn)BiLSTM。3.3 訓(xùn)練循環(huán)里的三個(gè)必調(diào)參數(shù)訓(xùn)練時(shí)最容易忽視但影響最大的參數(shù)分別是詞向量凍結(jié)與否、CRF學(xué)習(xí)率倍率、梯度裁剪閾值。# 訓(xùn)練循環(huán)關(guān)鍵片段 optimizer torch.optim.AdamW([ {params: model.char_emb.parameters(), lr: 5e-4}, {params: model.word_emb.parameters(), lr: 1e-4}, # 詞向量慢熱防止災(zāi)難性遺忘 {params: model.bilstm.parameters(), lr: 5e-4}, {params: model.fc.parameters(), lr: 5e-4}, {params: model.crf.parameters(), lr: 1e-3} # CRF收斂慢給大一點(diǎn) ], weight_decay1e-4) # 梯度裁剪BiLSTM對(duì)梯度爆炸敏感經(jīng)驗(yàn)值max_norm5.0 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)參數(shù)說明里最值得聊的是詞向量學(xué)習(xí)率。如果設(shè)為和主模型一樣大的學(xué)習(xí)率預(yù)訓(xùn)練詞向量會(huì)在前兩個(gè)epoch內(nèi)被大量非實(shí)體樣本「帶偏」等CRF還沒學(xué)會(huì)約束邊界詞向量空間已經(jīng)一團(tuán)糟。把詞向量學(xué)習(xí)率壓到主模型的五分之一相當(dāng)于讓它在細(xì)調(diào)時(shí)保持距離。CRF的轉(zhuǎn)移矩陣參數(shù)很少但梯度更新效率低單獨(dú)放大學(xué)習(xí)率能少跑一兩個(gè)epoch。3.4 推理階段Viterbi解碼和實(shí)體邊界還原CRF解碼用crf.decode即可但實(shí)體抽取時(shí)需要將BIO標(biāo)簽序列還原為實(shí)體列表def decode_entities(chars, tags): entities [] cur_type None cur_start 0 for i, (ch, tag) in enumerate(zip(chars, tags)): if tag.startswith(B-): if cur_type is not None: entities.append((cur_start, i - 1, cur_type)) cur_type tag[2:] cur_start i elif tag.startswith(I-): if cur_type ! tag[2:]: # I標(biāo)簽和前一個(gè)B類型不一致說明標(biāo)注錯(cuò)誤或解碼異常跳過當(dāng)前實(shí)體 cur_type None else: if cur_type is not None: entities.append((cur_start, i - 1, cur_type)) cur_type None if cur_type is not None: entities.append((cur_start, len(chars) - 1, cur_type)) # 合并相同位置的連續(xù)實(shí)體并回填原文 return [(.join(chars[s:e1]), t) for s, e, t in entities]這個(gè)還原函數(shù)看起來簡單但暗含一個(gè)工程陷阱如果標(biāo)簽序列里出現(xiàn)B-PER后面直接跟I-LOC這種跨越類型變化必須中斷當(dāng)前實(shí)體而不是強(qiáng)行合并否則會(huì)抽出「張B-PER / XYZI-LOC」這種拼接怪物。實(shí)踐中CRF很少產(chǎn)生這類錯(cuò)誤但用規(guī)則兜底能避免線上抽出不合法的實(shí)體。4. 避坑從「復(fù)現(xiàn)成功」到「上線能跑」的五個(gè)血淚經(jīng)驗(yàn)4.1 詞典匹配漏詞導(dǎo)致召回崩盤現(xiàn)象驗(yàn)證集P/R曲線全部正常但訓(xùn)練loss遲遲降不下去F1到30%就停滯。原因詞典構(gòu)建時(shí)只收入了訓(xùn)練集實(shí)體詞匯測試集里大量名詞特別是人名、產(chǎn)品名根本不在詞典中詞匯特征全是零向量融合了個(gè)寂寞。解決從外部通用NLP詞表例如百科詞頻、搜狗細(xì)胞詞庫補(bǔ)充至少10萬常用詞再配合停用詞過濾。如果對(duì)召回率有硬指標(biāo)把訓(xùn)練集所有連續(xù)雙字組合加入詞典做保底——寧可有噪聲詞不能沒有詞。4.2 詞頻權(quán)重的長尾爆炸現(xiàn)象詞頻加權(quán)后某些高置信實(shí)體詞比如反復(fù)出現(xiàn)的專有名詞向量模長被放大數(shù)十倍導(dǎo)致該位置字符本身的信息被淹沒。原因原始詞頻分布極度長尾頭部的詞頻是尾部的幾千倍沒做對(duì)數(shù)平滑直接除總頻次權(quán)重還是懸殊。解決對(duì)頻率做對(duì)數(shù)變換后再歸一化我一般用log(1 freq)替代raw freq同時(shí)給所有權(quán)重加一個(gè)最小閾值1e-4避免極端長尾詞影響數(shù)值穩(wěn)定性。4.3 CRF轉(zhuǎn)移矩陣不收斂現(xiàn)象訓(xùn)練到20個(gè)epoch后CRF的轉(zhuǎn)移概率仍然沒有明顯規(guī)律START-B-LOC這類合理轉(zhuǎn)移的得分和B-PER-I-LOC這類非法轉(zhuǎn)移得分幾乎一樣。原因CRF損失函數(shù)被BiLSTM特征主導(dǎo)轉(zhuǎn)移矩陣的梯度信號(hào)相對(duì)太弱。尤其當(dāng)batch_size1時(shí)每個(gè)step更新一次轉(zhuǎn)移矩陣每個(gè)step的更新方向和全局最優(yōu)方向偏差很大。解決batch_size至少調(diào)到16以上CRF單獨(dú)用AdamW而不用SGD如果還不行把LR的warmup步數(shù)拉長到總步數(shù)的10%讓轉(zhuǎn)移矩陣在訓(xùn)練早期就有足夠的收斂時(shí)間。4.4 詞向量OOV詞典有詞但向量表查不到現(xiàn)象訓(xùn)練正常但一測測試集帶詞典匹配的樣本F1反而低于不帶詞典的基線。原因預(yù)訓(xùn)練詞向量覆蓋不足詞典詞ID映射到隨機(jī)初始化向量隨機(jī)向量和訓(xùn)練好的字向量空間分布完全不齊相當(dāng)于在輸入里摻了隨機(jī)噪聲。解決所有OOV詞統(tǒng)一映射到一個(gè)特殊ID之前用詞向量的子詞初始化替代——fastText對(duì)OOV有n-gram回退換成fastText詞向量能救一多半。如果堅(jiān)持用word2vec就回退用該詞的首尾字向量平均做初始化。4.5 預(yù)測階段字典匹配和訓(xùn)練階段不一致現(xiàn)象訓(xùn)練時(shí)F1漂亮部署到服務(wù)上后線上預(yù)測F1直接掉5個(gè)點(diǎn)。原因線上詞典和生產(chǎn)詞典版本不一致。訓(xùn)練時(shí)使用的詞典包含一部分自定義業(yè)務(wù)詞線上詞典更新后少了一批或加了一批匹配結(jié)果直接變化。解決把詞典文件連同模型參數(shù)一起打包鏡像詞典版本做哈希校驗(yàn)任何詞典變更都要重新訓(xùn)練或至少做fine-tune不能只替換詞典文件。我在項(xiàng)目里吃過一次虧線上詞典被運(yùn)營人員手動(dòng)加了一千個(gè)詞沒重訓(xùn)直接上結(jié)果實(shí)體邊界飄了半個(gè)月。5. 把模型調(diào)到「壓榨機(jī)」?fàn)顟B(tài)三個(gè)可遷移的進(jìn)階操作先看如何驗(yàn)證詞匯融合真的在起作用。一個(gè)很實(shí)用的小技巧是把模型輸入中詞匯特征部分的梯度置零requires_gradFalse再跑一遍測試集和原模型對(duì)比F1差多少。如果差值小于0.5個(gè)點(diǎn)說明你的詞典和詞向量質(zhì)量不夠如果差值超過3個(gè)點(diǎn)說明模型已經(jīng)在依賴詞典信息但你還不知道哪些詞典詞在起作用。進(jìn)一步做法是抽一批測試集樣本對(duì)每個(gè)預(yù)測正確位置的反事實(shí)——把該位置的詞匯特征改成空——看模型預(yù)測是否翻轉(zhuǎn)。這個(gè)操作能幫你定位詞典里的「關(guān)鍵詞」讓詞典維護(hù)有的放矢。第二個(gè)操作是給Soft-Lexicon加一個(gè)詞級(jí)注意力門控。原始實(shí)現(xiàn)是直接拼接四類平均向量但它沒有區(qū)分「哪些類對(duì)當(dāng)前決策更重要」。常見做法是給四類向量各學(xué)一個(gè)標(biāo)量權(quán)重用字符當(dāng)前隱狀態(tài)做query算attention。代價(jià)是參數(shù)量增加不大F1能回升1%~1.5%特別是在噪聲詞典場景下。注意門控要加在BiLSTM的輸出層而不是輸入層輸入層門控會(huì)讓梯度傳導(dǎo)路徑變長收斂變慢。第三個(gè)可遷移點(diǎn)是多粒度特征的表征對(duì)齊。如果你后續(xù)想把這個(gè)NER模型并入預(yù)訓(xùn)練語言模型比如讓BERT的輸出和詞匯特征融合注意直接拼接BERT的高維向量和詞向量會(huì)讓前者主導(dǎo)決策詞向量初始化更新不起來。我的習(xí)慣是先用一個(gè)線性層把詞匯特征投影到BERT隱藏層維度的四分之一再經(jīng)過LayerNorm后加到BERT輸出上。這樣既保留了詞匯信息又不會(huì)把BERT主干拖下水。寫到這里回頭看Python實(shí)現(xiàn)基于詞匯信息融合的中文NER模型這個(gè)標(biāo)題里最難啃的骨頭其實(shí)不是模型代碼而是「詞典從哪來、詞向量怎么選、基礎(chǔ)參數(shù)怎么配」。我自己的血淚教訓(xùn)是第一版先用Soft-Lexicon跑通全鏈路再考慮Lattice或FLAT——這比上來就整復(fù)雜結(jié)構(gòu)快三天以上。詞典優(yōu)先覆蓋業(yè)務(wù)高頻實(shí)體詞向量選fastText回退方案訓(xùn)練時(shí)盯住詞向量學(xué)習(xí)率。這套打法救回了我好幾個(gè)項(xiàng)目希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取