预训练永不止步,游戏问答语言模型实操
?PaperWeekly 原創 ·?作者|程任清、劉世興
單位|騰訊游戲知幾AI團隊
研究方向|自然語言處理
簡介
深度學習時代,為了充分訓練深層模型參數并防止過擬合,通常需要更多標注數據喂養,在 NLP 領域,標注數據更是一個昂貴資源。在這種背景下,預訓練應運而生,該技術可以從大量無標注數據中進行預訓使許多 NLP 任務獲得顯著的性能提升,大批的預訓練模型也相繼開源了。
但是由于場景的偏差,通用的預訓練模型通常無法在垂直領域取得理想的效果,在我們的實際業務中同樣也遇到了這個問題, 為了能進一步提升業務下游任務的性能,將大量無標簽領域數據價值發揮到最大,我們嘗試在游戲問答場景下自研預訓練模型 [1],主要遇到的挑戰是如何在預訓時引入更多的知識。
本文將介紹我們在這方面所做的系列工作,通過這篇文章你將了解到如何快速上手模型預訓練,以及如何結合業務知識進一步提升垂直預訓的效果。
(我們所有的實驗都是在騰訊云智能鈦上進行的,感謝為我們提供算力資源!)
我們的模型已開源:
https://share.weiyun.com/S0CeWKGM?(UER版)
https://share.weiyun.com/qqJaHcGZ(huggingface版)
基于UER-PY——中文預訓練初探
UER-PY 是公司內開源項目 BERT-PyTorch 對外開放的版本,提供了對 BERT [2] 等一系列預訓練模型進行了精準的復現,包括預處理、預訓練、下游任務微調等步驟,并增加了多項預訓練相關的功能和優化。至于我們為什么選用這個框架,一是該項目對中文的支持及一些下游評測的腳本確實挺好用的,二是目前項目在不斷更新優化中,遇到問題能得到及時的解決。
上面便是 UER-PY 的簡介,感興趣的同學可以參考 UER-py:
https://github.com/dbiir/UER-py
本小節主要講述基于 UER-PY 進行 MLM 預訓和詞粒度預訓練的實操。
2.1 MLM預訓練
基于 UER-PY 進行預訓練,主要步驟如下:
步驟 1:準備訓練數據(這里需要注意:語料的格式是一行一個句子,不同文檔使用空行分隔,前提是 target 是 bert(包括 mlm 和 nsp,如果 targe 只有 mlm 的話,不需要空行分隔,當時踩了這個坑)
步驟 2:對語料進行預處理
步驟 3:進行預訓
具體腳本都可以在官網上找到,我們簡單的用自己的業務數據跑了下,并進行了評測。
主要在 CLUE 和游戲領域分類任務上進行了評測。UER-PY 提供了一些挺好用的腳本,方便評測,可以參考 Competition solutions:
https://github.com/dbiir/UER-py/wiki/Competition-solutions
查看其 loss,取的是 100000 steps 的結果。
各任務評測結果:
從上述結果可以看出,使用領域語料進行預訓練對于通用的評測任務影響較小,而在業務任務上性能得到提升。除了字粒度,我們也嘗試了詞粒度的預訓,以比較哪種粒度更適合我們的場景。
2.2 詞粒度預訓練
當我們想要獲得特定領域的預訓練模型,如果很多詞匯不在提供的 vocab.txt 中,這是我們就想根據自己的領域語料來構建自己的詞典進行 word-base 預訓練。
本節將主要講述基于游戲領域的語料進行 word-base 的預訓練(同樣還是在 UER-PY 框架上,并在 Wikizh(word-based)+BertEncoder+BertTarget 基礎上進行增量訓練,該模型可在 Modelzoo 下載)。
https://github.com/dbiir/UER-py/wiki/Modelzoo
首先利用 jieba 分詞進行切詞,并在 jieba 自定義詞典里加入領域實體詞,生成得到分詞后的訓練數據(all_qa_log.txt),通過腳本構建詞典:
#構建領域詞典 python3?scripts/build_vocab.py?--corpus_path?../data/all_qa_log.txt?\--vocab_path?../data/zhiji_word_pre/zhiji_word_vocab.txt?\--workers_num?10?\--min_count?30?\?--tokenizer?space???可以調整 min_count 來調整詞典大小,min_count 表示一個詞在語料中重復出現的次數。我們構建后的詞典大小為 137011, 原本詞典大小為 80000。
重構詞典后,需要相應的調整預訓練模型,可通過以下腳本實現:
python3?scripts/dynamic_vocab_adapter.py?--old_model_path?../data/wiki_word_model/wiki_bert_word_model.bin?\--old_vocab_path?../data/wiki_word_model/wiki_word_vocab.txt?\--new_vocab_path?../data/zhiji_word_pre/zhiji_word_vocab.txt?\--new_model_path?../data/zhiji_word_pre/zhiji_bert_word_model.bin這里遇到一個問題:就是詞典明明是 137011 大小,調整后的模型 embedded 層詞典大小對應是 137010,后查看原因是:構建的詞典中有""和" ",uer/utils.vocab.py 中讀取代碼會將這兩個詞弄成一個。
#將下行 w?=?line.strip().split()[0]?if?line.strip()?else?"?" #替換為 w?=?line.strip("\n")?if?line.strip("\n")?else?"?"后面便可以準備訓練數據和訓練啦,腳本可以參考官網,對應的 target 為 mlm,tokenizer 為 space。
通過評測,發現 word-base 預訓練效果不太好,猜測可能是由于 OOV 導致的,后來統計了下語料 oov 的詞占比為 2.43%,還是挺大的。這里優化的方案是利用詞字混合粒度,具體做法如下:
1. 對語料 data.txt(每行一個文本)進行分詞 data_cut.txt(每行對應該文本的分詞結果,以空格連接);
2. 對 data_cut.txt 進行 space 分詞,統計詞頻,將詞頻大于 50 的放入詞典 vocab_a;
3. 對 data_cut.txt 不在 vocab_a 的詞進行字粒度分詞,構建新的結果文件 data_cut_cw.txt;
4. 對 data_cut_cw.txt 進行 space 分詞,統計詞頻,將詞頻大于 50 的詞(102846)和 google 的中文詞典中的 9614 詞(刪除了帶##的詞)和 104 個保留詞放入詞典 vocab_b(112564)。
通過上面的做法,新構建的詞典 oov 占比為 0.03%,解決了 OOV 的問題,然后重新預訓后發現效果還是不太好,猜想原因應該還是由于詞粒度學習的不夠充分,因此后面我們的嘗試基本還是基于字粒度展開。
有興趣的同學可以看看《Is Word Segmentation Necessary for Deep Learning of Chinese Representations》[3] 這篇論文,探討了中文分詞是否必要。
結合領域信息的中文預訓練
3.1 全詞掩碼預訓練
Whole Word Masking(wwm),暫翻譯為全詞 Mask 或整詞 Mask,是發布的一項 BERT 的升級版本,主要更改了原預訓練階段的訓練樣本生成策略。
簡單來說,原有基于 WordPiece 的分詞方式會把一個完整的詞切分成若干個子詞,在生成訓練樣本時,這些被分開的子詞會隨機被 mask。在整詞 Mask 中,如果一個完整的詞的部分 WordPiece 子詞被 mask,則同屬該詞的其他部分也會被 mask,即全詞 Mask。
同理,由于官方發布的 BERT-base, Chinese 中,中文是以字為粒度進行切分,沒有考慮到傳統 NLP 中的中文分詞(CWS)。將全詞 Mask 的方法應用在了中文中,使用了中文維基百科(包括簡體和繁體)進行訓練,利用分詞工具對組成同一個詞的漢字全部進行 Mask [4]。
以上是 wwm 的簡介,更多信息可以參考:
https://github.com/ymcui/Chinese-BERT-wwm?
或《Pre-Training with Whole Word Masking for Chinese BERT》:
https://arxiv.org/abs/1906.08101
本節主要講述基于游戲語料,利用 huggingface/transformers 在騰訊云智能鈦上進行 wwm 預訓練實戰。(為什么不基于 UER-PY?因為目前它還沒不支持 wwm,huggingface 也是最新版本的才支持的,使用過程中也有一些坑,后面會列出來)
3.1.1 準備訓練數據
使用分詞工具準備 reference file,為什么要這步?直接看下官方的說明
For?Chinese?models,?we?need?to?generate?a?reference?files,?because?it's?tokenized?at?the?character?level. **Q?:**?Why?a?reference?file? **A?:**?Suppose?we?have?a?Chinese?sentence?like:?`我喜歡你`?The?original?Chinese-BERT?will?tokenize?it?as`['我','喜','歡','你']`?(character?level).?But?`喜歡`?is?a?whole?word.?For?whole?word?masking?proxy,?we?need?a?result?like?`['我','喜','##歡','你']`,?so?we?need?a?reference?file?to?tell?the?model?which?position?of?the?BERT?original?tokenshould?be?added?`##`.第一步先準備環境,分別是分詞功能和 transformers。
分詞工具安裝有兩個問題需要注意 python>3.6以及 3.0=<transformers<=3.4.0,親測是 python 確實需要 >3.6,而 transformers 不一定。我們使用的 python=3.7.2 和 transformers=3.5.0。
第二步,使用 transformers 源碼中的 examples/contrib/run_chinese_ref.py 腳本生成 reference file。
```bash export?TRAIN_FILE=/path/to/dataset/train.txt export?tokens_RESOURCE=/path/to/tokens/tokenizer export?BERT_RESOURCE=/path/to/bert/tokenizer export?SAVE_PATH=/path/to/data/ref.txt python?examples/contrib/run_chinese_ref.py?\--file_name=path_to_train_or_eval_file?\--tokens=path_to_tokens_tokenizer?\--bert=path_to_bert_tokenizer?\--save_path=path_to_reference_file分詞組件模型可以通過官網下載,另外通過添加自定義詞典加入領域實體(我的最大前向分詞窗口為 12)。
生成結果為:
#訓練數據樣例 我喜歡你 興高采烈表情怎么購買 我需要一張國慶投票券 #對應生成的reference?file [3] [2,?3,?4,?5,?6,?8,?10] [3,?7,?8,?9,?10]解釋生成的 reference file:
#以?興高采烈表情怎么購買?為例 分詞后:['興高采烈表情',?'怎么',?'購買'] bert分詞后:['[CLS]',?'興',?'高',?'采',?'烈',?'表',?'情',?'怎',?'么',?'購',?'買',?'[SEP]'] reference?file對應的為:[2,?3,?4,?5,?6,?8,?10]?(每個值為對應的bert分詞結果的位置索引,并需要和前字進行合并的) 結合bert分詞和reference?file可以生成:['[CLS]',?'興',?'##高',?'##采',?'##烈',?'##表',?'##情',?'怎',?'##么',?'購',?'##買',?'[SEP]']3.1.2 進行 wwm 訓練
訓練的時候,我們用的 transformers 是 3.5.1,腳本用的是該版本的 examples/language-modeling/run_mlm.py。訓練參數如下:
data_dir="/home/tione/notebook/data/qa_log_data/" train_data=$data_dir"qa_log_train.json" valid_data=$data_dir"qa_log_val.json" pretrain_model="/home/tione/notebook/data/chinese-bert-wwm-ext" python?-m?torch.distributed.launch?--nproc_per_node?8?run_mlm_wwm.py?\--model_name_or_path?$pretrain_model?\--model_type?bert?\--train_file?$train_data?\--do_train?\--do_eval?\--eval_steps?10000?\--validation_file?$valid_data\--max_seq_length?64?\--pad_to_max_length?\--num_train_epochs?5.0?\--per_device_train_batch_size?128?\--gradient_accumulation_steps?16?\--save_steps?5000?\--preprocessing_num_workers?10?\--learning_rate?5e-5?\--output_dir?./output_qalog?\--overwrite_output_dir對大語料抽樣統計了語料中文本長度分布如下圖:
我們的訓練數據還是短文本居多的,所以最后將 max_seq_length 設為了 64。
模型是在 chinese-bert-wwm-ext 基礎上進行增量訓練的,chinese-bert-wwm-ext 可以在 huggingface/models 下載:
https://huggingface.co/models
機智如你是不是發現訓練的時候并沒有用 reference file 啊。是的,因為我們碰到了坑。
3.1.2.1 踩坑:transformers 新版本數據讀寫依賴了 datasets,這個庫帶來了什么問題?
這個庫依賴的 python 版本 >python3.6,而智能鈦預裝的環境 python 版本都是 python3.6 的。很簡單的想法就是再裝一個環境唄。沒錯,我們也是這樣想的,然后裝著裝著智能鈦實例就崩掉了。(內心 yy:前期一直不知道原因,智能鈦一旦崩掉可能會由于資源售罄而無法重啟,此時心里是慌得一批,我們的數據啊我們的代碼啊)。找到原因是智能鈦根目錄非常小,只有 50G,而 anaconda 默認的安裝路徑就是在根目錄。此時裝新環境需要指定目錄到 notebook 目錄(一般都有 1T)。
然后就可以安裝各種你需要的庫了。
這個庫需要連接外網來獲取數據讀取腳本(會出現 ConnectionError)。解決的方法是去 datasets 的 git 上下載我們需要的腳本到本地,比如我們需要 text.py 的腳本(即一行一行讀取訓練數據),在下方鏈接下載 text.py,并修改 run_mlm_wwm.py 代碼。
https://github.com/huggingface/datasets/tree/1.1.2/datasets/text
#將 datasets?=?load_dataset(extension,?data_files=data_files)#修改為: datasets?=?load_dataset("../test.py",data_files=data_files)3.1.2.2 踩坑:訓練數據太大(數據千萬級),程序容易被 kill,可以通過下面方法去避免。
一個是在跑 run_mlm_wwm.py 會先將訓練數據進行 tokenizer,這個過程會生成很多的cache文件,而這個 cache 文件默認生成的路徑是 /home/tione/.cache/huggingface/datasets,這個 /home 是不是很熟悉,就是上面提高的只有 50G 的根目錄,所以這一步智能鈦也很容易崩,所以需要指定 cache 路徑。
這一步會生成 cache 文件如下,之后再跑就會優先 load cache 文件,避免重復處理:
在使用 reference file 的時候很容易被 kill 掉。調試的時候會卡到第 5 行,然后就被 kill 了。
我們的處理方法是:直接跳過這一步,構建訓練數據的時候直接將文本及其對應的 ref 關聯好,然后用 Datasets 讀入。這也就是為什么上面的訓練腳本沒有用 reference file。
import?json with?open("qa_log_data/qa_log_train.json","w")?as?fout:with?open("qa_log_data/qa_log_train.txt","r")?as?f1:with?open("qa_log_data/ref_train.txt","r")?as?f2:for?x,y?in?zip(f1.readlines(),f2.readlines()):y?=?json.loads(y.strip("\n"))out_dict?=?{"text":x.strip("\n"),"chinese_ref":y}out_dict?=?json.dumps(out_dict)fout.write(out_dict+"\n")由于訓練數據是 json 了,所以上面的 text.py 對應的替換成 json.py(也是在 datasets 的 git 上可以下載到)
3.1.3 wwm評測
在領域分類數據集(這里的測試集和 4.2 的測試集,屬于不同時期準備的測試集)上對比的 mlm 模型和 wwm 模型的效果,實驗結果如下:
從實驗結果來看,wwm 的效果在中文上的表現確實是優于 mlm 預訓的結果。
3.2 多任務預訓練
本節主要講述我們領域預訓時構建的任務以及對應的實驗結果。
3.2.1 任務描述
原始的 WWM 考慮了整詞的信息,并且我們通過引入實體詞典引入領域實體信息(wwm-ent)。在這基礎上我們另外引入了其他信息,我們主要通過下列任務來嘗試優化預訓練效果:
句子順序預測(SOP)
ALBERT [5] 指出 BERT 的 NSP 任務其實相當于主題預測和句子的連貫性預測,而隨機選擇的“下一句”很容易根據主題信息作出正確預測,導致 NSP 任務太簡單而效果不明顯。
ALBERT 對此提出了“Sentence Order Prediction(SOP)”任務,把相鄰的兩個文本片段交換順序作為負例,讓模型預測文本片段的順序,相比 NSP 效果要好。我們的訓練數據以短文本為主,缺少文檔數據。我們嘗試借鑒 SOP 任務,將一個句子分為前后兩個部分,正序作為正例,逆序作為負例,預測輸入的句子是否是亂序。
替換詞檢測(RTD)
MLM 任務隨機掩蓋了部分輸入,導致訓練階段和預測階段輸入不一致。于是,我們加入另一個替換詞檢測訓練任務(Replaced Token Detection, RTD)。這個任務是由 ELECTRA [6] 提出的,把輸入的部分字用另一個生成器模型的 MLM 預測替換,令判別器模型訓練時預測輸入的每個字是否是替換過的詞。
這個任務相比 MLM 還有一個優點,訓練時所有輸入字都參與了 loss 的計算,提高了訓練效率。和 ELECTRA 不同的是,我們的生成器模型和判別器模型是同一個模型,先進行 MLM 任務的相關計算,得到預測詞,然后再用來構造 RTD 任務的輸入,讓模型預測字是否是替換過。
詞語混排(wop)
在文本中,結構知識往往以詞語或者句子的順序來表達,如 sop 通過句子信息來學習到句子結構信息,詞語重排序和句子重排目標一致,都是還原原文,迫使模型學習到詞法依賴關系。我們實驗里主要針對實體詞進行了詞語的混排預測。
句子對相似度預測(psp)
我們通過句子相似度來引入語義知識,但是一般很難有大量的相似句子的標注語料,考慮我們問答數據知識點的形式,通過語義召回構建了大量正樣本和負樣本用于學習。
3.2.2 效果對比及分析
下表為我們所做的加入其他任務進行預訓的實驗結果對比。
結果1:直接利用開源模型進行下游任務評測的結果;
結果2:利用領域數據在開源模型上進行增加訓練,效果得到的提升,進一步驗證了Don't Stop Pretraining [1];
結果3:我們加入替換詞檢測任務,實驗結果里,加入 RTD 任務的 bert-wwm-rtd 相比 bert-wwm 在領域分類和問答任務任務有了進一步提升;
結果4:加入實體信息的 WWM 相對于沒有加入前效果沒有達到預期,一個可能的原因是我們加入的實體是通過挖掘得到的,沒有經過人工,依賴實體挖掘的效果;
結果5:加入這種類 SOP 任務的方法(bert-wwm-sop)在下游任務上進行評測,效果均略有下降,這個結果和 ALBERT 的實驗結果類似,SOP 任務對多句子輸入的任務效果更好,我們短文本單句不太適合這類任務;
結果6:我們嘗試 wwm、詞語混排和句子相似度預測任務進行多任務學習,學習的方式是通過 batch 來循環多任務學習,即 batch1 學習 task1,batch2 學習 task2,batch3 學習 task3,batch4 再學習 task1 的方式避免出現知識遺忘,通過這種方式,效果相比 bert-wwm 在領域分類和問答任務任務有了進一步提升。
總結
本文介紹了基于 UER-py 和騰訊云智能鈦如何訓練垂直領域預訓練語言模型。通過實驗多種訓練任務和技巧,我們得到了一個在通用領域可用且在游戲問答領域有明顯優勢的語言模型,我們將模型開源以豐富中文 NLP 社區資源。
我們實踐過程中有如下一些發現:
1. 引入詞法知識:wwm 在中文上較傳統的 mlm 有明顯的優勢,另外通過? word seg [7,8](類似 pos seg,這里的 word seg:0 代表一個詞的開始,1 代表結束)融入詞信息的方式并不適合。
2. 引入結構知識:我們通過替換詞檢測(rtd)和詞語混排(wop)任務能很好的引入結構化的知識,而句子順序預測任務(sop)并不適合我們問答的場景。
3. 引入領域知識:一方面利用問答天然簇形式的數據,構建句子對引入語義知識(psp);另一方面利用實體詞 mask 引入更多的詞法信息(wwm-ent),相反的直接通過添加 ner 任務引入實體信息效果則不理想。
我們認為,將領域知識嵌入到語言模型中,以及借助語言模型獲取領域知識,這兩個方向都很重要且相輔相成,在未來一定會取得較大突破。希望感興趣的同學與我們交流探討一起合作,更希望志同道合的同學加入我們。
參考文獻
[1] Gururangan S, Marasovi? A, Swayamdipta S, et al. Don't Stop Pretraining: Adapt Language Models to Domains and Tasks[J]. arXiv preprint arXiv:2004.10964, 2020.
[2] Devlin J, Chang M W, Lee K, et al. Bert: Pre-training of deep bidirectional transformers for language understanding[J]. arXiv preprint arXiv:1810.04805, 2018.
[3] Li X, Meng Y, Sun X, et al. Is word segmentation necessary for deep learning of Chinese representations?[J]. arXiv preprint arXiv:1905.05526, 2019.
[4] Cui Y, Che W, Liu T, et al. Pre-training with whole word masking for chinese bert[J]. arXiv preprint arXiv:1906.08101, 2019.
[5] Lan Z, Chen M, Goodman S, et al. A Lite BERT for Self-supervised Learning of Language Representations[J]. arXiv preprint arXiv:1909.11942, 2019.
[6] Clark K, Luong M T, Le Q V, et al. Electra: Pre-training text encoders as discriminators rather than generators[J]. arXiv preprint arXiv:2003.10555, 2020.
[7] Li X, Yan H, Qiu X, et al. Flat: Chinese ner using flat-lattice transformer[J]. arXiv preprint arXiv:2004.11795, 2020.
[8] Liu W, Zhou P, Zhao Z, et al. K-bert: Enabling language representation with knowledge graph[C]//Proceedings of the AAAI Conference on Artificial Intelligence. 2020, 34(03): 2901-2908.
更多閱讀
#投 稿?通 道#
?讓你的論文被更多人看到?
如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。
總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。?
PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得或技術干貨。我們的目的只有一個,讓知識真正流動起來。
?????來稿標準:
? 稿件確系個人原創作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)?
? 如果文章并非首發,請在投稿時提醒并附上所有已發布鏈接?
? PaperWeekly 默認每篇文章都是首發,均會添加“原創”標志
?????投稿郵箱:
? 投稿郵箱:hr@paperweekly.site?
? 所有文章配圖,請單獨在附件中發送?
? 請留下即時聯系方式(微信或手機),以便我們在編輯發布時和作者溝通
????
現在,在「知乎」也能找到我們了
進入知乎首頁搜索「PaperWeekly」
點擊「關注」訂閱我們的專欄吧
關于PaperWeekly
PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。
總結
以上是生活随笔為你收集整理的预训练永不止步,游戏问答语言模型实操的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 支付宝未入账什么意思
- 下一篇: 科创板是t十0交易吗