提速不掉点:基于词颗粒度的中文WoBERT
By 苏剑林 | 2020-09-18 | 112042位读者 |当前,大部分中文预训练模型都是以字为基本单位的,也就是说中文语句会被拆分为一个个字。中文也有一些多颗粒度的语言模型,比如创新工场的ZEN和字节跳动的AMBERT,但这类模型的基本单位还是字,只不过想办法融合了词信息。目前以词为单位的中文预训练模型很少,据笔者所了解到就只有腾讯UER开源了一个以词为颗粒度的BERT模型,但实测效果并不好。
那么,纯粹以词为单位的中文预训练模型效果究竟如何呢?有没有它的存在价值呢?最近,我们预训练并开源了以词为单位的中文BERT模型,称之为WoBERT(Word-based BERT,我的BERT!),实验显示基于词的WoBERT在不少任务上有它独特的优势,比如速度明显的提升,同时效果基本不降甚至也有提升。在此对我们的工作做一个总结。
字还是词? #
究竟是“字”好还是“词”好?这是中文NLP一个很让人抓狂的问题,也有一些工作去系统地研究这个问题。比较新的是香侬科技在ACL 2019上发表的《Is Word Segmentation Necessary for Deep Learning of Chinese Representations?》,里边得到了字几乎总是优于词的结论。前面也说了,现在中文预训练模型确实也基本上都是以字为单位的。所以,看上去这个问题已经解决了?就是字更好?
事情远没有这么简单。就拿香侬科技的这篇论文来说,它的实验结果是没有错,但却是没有代表性的。为什么这样说呢?因为它比较的是大家的Embedding层都是随机初始化的情况下的效果,这样一来,对于同样的任务,以词为单位的模型Embedding层参数更多,自然就更容易过拟合,效果容易变差,这不用做实验都能猜个大概。问题是,我们用基于词的模型的时候,通常并不是随机初始化的,往往都是用预训练好的词向量的(下游任务看情况选择是否微调词向量),这才是分词的NLP模型的典型场景,但论文里边却没有比较这个场景,所以论文的结果并没有什么说服力。
事实上,“过拟合”现象具有两面性,我们要防止过拟合,但过拟合也正好说明了模型拥有比较强的拟合能力,而如果我们想办法抑制过拟合,那么就能够在同样复杂度下得到更强的模型,或者在同样效果下得到更低复杂度的模型。而缓解过拟合问题的一个重要手段就是更充分的预训练,所以不引入预训练的比较对以词为单位的模型来说是不公平的,而我们的WoBERT正是证实了以词为单位的预训练模型的可取性。
词的好处 #
一般认为,以字为单位的好处是:
1、参数更少,不容易过拟合;
2、不依赖于分词算法,避免边界切分错误;
3、没那么严重的稀疏性,基本上不会出现未登录词。
至于以词为单位的理由是
1、序列变短,处理速度更快;
2、在文本生成任务上,能缓解Exposure Bias问题;
3、词义的不确定性更低,降低建模复杂度。
对于词的好处,大家可能会有些疑惑。比如第2点,词能缓解Exposure Bias,这是因为理论上来说,序列越短Exposure Bias问题就越不明显(词的模型单步预测出一个$n$字词,相当于字的模型预测了$n$步,这$n$步都递归依赖,所以字的模型Exposure Bias问题更严重)。至于第3点,虽然有多义词的存在,但是多数词的含义还是比较确定的,至少比字义更加明确,这样一来可能只需要一个Embedding层就能把词义建模好,而不是像字模型那样,要通用多层模型才能把字组合成词。
看起来不相伯仲,但事实上以字为单位的好处,并非就是以词为单位的缺点了,只要多一些技巧,以词为单位也能一定程度上避免这几个问题。比如:
1、以词为单位的参数多了,但是可以通过预训练来缓解过拟合,所以这个问题不会很严重;
2、依赖分词算法是个问题,如果我们只保留最常见的一部分词,那么不管哪个分词工具分出来的结果都是差不多的,差异性不大;
3、至于边界切分错误,这个难以避免,但是需要准确的边界的,只是序列标注类任务而已,文本分类、文本生成其实都不需要准确的边界,因此不能就此否定词模型;
4、如果我们把大部分字也加入到词表中,也不会出现未登录词。
所以,其实用词的好处是相当多的,除了需要非常精确边界的序列标注类型的任务外,多数NLP任务以词为单位都不会有什么问题。因此,我们就去做了以词为单位的BERT模型了。
Tokenizer #
往BERT里边加入中文词,首先得让Tokenizer能分出词来。只需要把词加入到字典vocab.txt里边就行了吗?并不是。BERT自带的Tokenizer会强行把中文字符用空格隔开,因此就算你把词加入到字典中,也不会分出中文词来。此外,BERT做英文word piece的分词的时候,使用的是最大匹配法,这对中文分词来说精度也不够。
为了分出词来,我们修改了一下BERT的Tokenizer,加入了一个“前分词(pre_tokenize)”操作,这样我们就可以分出中文词来,具体操作如下:
1、把中文词加入到vocab.txt;
2、输入一个句子$s$,用pre_tokenize先分一次词,得到$[w_1,w_2,\dots,w_l]$;
3、遍历各个$w_i$,如果$w_i$在词表中则保留,否则将$w_i$用BERT自带的tokenize函数再分一次;
4、将每个$w_i$的tokenize结果有序拼接起来,作为最后的tokenize结果。
在bert4keras>=0.8.8版本中,实现上述改动只需要在构建Tokenizer的时候传入一行参数,例如:
tokenizer = Tokenizer(
dict_path,
do_lower_case=True,
pre_tokenize=lambda s: jieba.cut(s, HMM=False)
)
其中pre_tokenize
为外部传入的分词函数,如果不传入则默认为None
。简单起见,WoBERT使用了结巴分词,删除了BERT自带词表的冗余部分(比如带##的中文词),然后加入了20000个额外的中文词(结巴分词自带的词表词频最高的两万个),最终WoBERT的vocab.txt规模是33586。
模型细节 #
目前开源的WoBERT是Base版本,在哈工大开源的RoBERTa-wwm-ext基础上进行继续预训练,预训练任务为MLM。初始化阶段,将每个词用BERT自带的Tokenizer切分为字,然后用字embedding的平均作为词embedding的初始化。
到这里,WoBERT的技术要点基本上都说清楚了,剩下的就是开始训练了。我们用单张24G的RTX训练了100万步(大概训练了10天),序列长度为512,学习率为5e-6,batch_size为16,累积梯度16步,相当于batch_size=256训练了6万步左右。训练语料大概是30多G的通用型语料。训练代码已经在文章开头的链接中开源了。
此外,我们还提供了WoNEZHA,这是基于华为开源的NEZHA进行再预训练的,训练细节跟WoBERT基本一样。NEZHA的模型结构跟BERT相似,不同的是它使用了相对位置编码,而BERT用的是绝对位置编码,因此理论上NEZHA能处理的文本长度是无上限的。这里提供以词为单位的WoNEZHA,就是让大家多一个选择。
模型效果 #
最后,说一下WoBERT的效果。简单来说,在我们的评测里边,WoBERT相比于BERT,在不需要精确边界的NLP任务上基本都没有变差的,有些还会有一定的提升,而速度上则有明显提升,所以一句话就是“提速不掉点”。
比如中文榜单上的两个分类任务:
\begin{array}{c}
\text{文本分类效果对比}\\
{\begin{array}{c|cc}
\hline
& \text{IFLYTEK} & \text{TNEWS} \\
\hline
\text{BERT} & 60.31\% & 56.94\% \\
\text{WoBERT} & \textbf{61.15%} & \textbf{57.05%} \\
\hline
\end{array}}
\end{array}
我们内部还测了不少任务,结果都是类似的,表明这些NLU任务上WoBERT和BERT基本上都差不多的。但是速度上,WoBERT就比BERT有明显优势了,下表是两个模型在处理不同字数的文本时的速度比较:
\begin{array}{c}
\text{速度对比}\\
{\begin{array}{c|ccc}
\hline
& \text{128} & \text{256} & \text{512} \\
\hline
\text{BERT} & \text{1.0x} & \text{1.0x} & \text{1.0x} \\
\text{WoBERT} & \textbf{1.16x} & \textbf{1.22x} & \textbf{1.28x} \\
\hline
\end{array}}
\end{array}
我们还测了WoBERT+UniLM的方式Seq2Seq任务(CSL/LCSTS标题生成),结果是比以字为单位的模型有明显提升:
\begin{array}{c}
\text{CSL摘要生成实验结果}\\
{\begin{array}{c|c|cccc}
\hline
& \text{beam size} & \text{Rouge-L} & \text{Rouge-1} & \text{Rouge-2} & \text{BLEU} \\
\hline
\text{BERT} & 1 & 63.81 & 65.45 & 54.91 & 45.52 \\
\text{WoBERT} & 1 & \textbf{66.38} & \textbf{68.22} & \textbf{57.83} & \textbf{47.76} \\
\hline
\text{BERT} & 2 & 64.44 & 66.09 & 55.75 & 46.39 \\
\text{WoBERT} & 2 & \textbf{66.65} & \textbf{68.68} & \textbf{58.5} & \textbf{48.4} \\
\hline
\text{BERT} & 3 & 64.75 & 66.34 & 56.06 & 46.7 \\
\text{WoBERT} & 3 & \textbf{66.83} & \textbf{68.81} & \textbf{58.67} & \textbf{48.6} \\
\hline
\end{array}}\\
\\
\text{LCSTS摘要生成实验结果}\\
{\begin{array}{c|c|cccc}
\hline
& \text{beam size} & \text{Rouge-L} & \text{Rouge-1} & \text{Rouge-2} & \text{BLEU} \\
\hline
\text{BERT} & 1 & 27.99 & 29.57 & 18.04 & 11.72 \\
\text{WoBERT} & 1 & \textbf{31.51} & \textbf{32.9} & \textbf{21.13} & \textbf{13.74} \\
\hline
\text{BERT} & 2 & 29.2 & 30.7 & 19.17 & 12.64 \\
\text{WoBERT} & 2 & \textbf{31.91} & \textbf{33.35} & \textbf{21.55} & \textbf{14.13} \\
\hline
\text{BERT} & 3 & 29.45 & 30.95 & 19.5 & 12.93 \\
\text{WoBERT} & 3 & \textbf{32.19} & \textbf{33.72} & \textbf{21.81} & \textbf{14.29} \\
\hline
\end{array}}
\end{array}
这说明以词为单位来做文本生成其实是更有优势的。要是生成更长的文本,这个优势还能进一步放大。
当然,我们也不否认,用WoBERT去做NER等序列标注任务时,可能会有明显的掉点,比如做人民日报的NER,掉了3%左右,可能让人意外的是,经过bad case分析,我们发现掉点的原因并不是因为切分错误,而是因为稀疏性(平均来说每个词的样本更少,所以训练得没那么充分)。
不管怎么说,我们把我们的工作开源出来,给大家在使用预训练模型的时候,多一个尝试的选择吧。
文章小结 #
在这篇文章里,我们开源了以词为单位的中文BERT模型(WoBERT),并讨论了以词为单位的优缺点,最后通过实验表明,以词为单位的预训练模型在不少NLP任务(尤其是文本生成)上有它独特的价值,一方面它有速度上的优势,一方面效果上能媲美以字为单位的BERT,欢迎大家测试。
转载到请包括本文地址:https://kexue.fm/archives/7758
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Sep. 18, 2020). 《提速不掉点:基于词颗粒度的中文WoBERT 》[Blog post]. Retrieved from https://kexue.fm/archives/7758
@online{kexuefm-7758,
title={提速不掉点:基于词颗粒度的中文WoBERT},
author={苏剑林},
year={2020},
month={Sep},
url={\url{https://kexue.fm/archives/7758}},
}
September 18th, 2020
苏神,在模型效果那里,对比的模型是哈工大放出的RoBERTa-wwm-ext,还是说用和WoEERT同样的训练语料继续训练的BERT?
对比的是RoBERTa-wwm-ext,在这里 https://github.com/CLUEbenchmark/CLUE 放出的结果。
我猜你是怀疑提升来自额外的预训练而不是词信息,这点怀疑是合理的,但应该不成立。首先已经说了,BERT跟WoBERT在NLU任务上差别不大,因此额外预训练其实没给NLU带来什么影响。而NLG有明显提升,这确实是词带来的,因为我试过在字的模型上继续预训练(包括用seq2seq任务而不是mlm预训练),也没对NLG任务有什么帮助,直到加入词后才有的提升。此外,我们进行的额外预训练相当于batch_size=256训练了6万步,其实这对于预训练来说是很少的步数,起不了太大的作用,在这里仅能起到弥补字与词的gap的作用了。
因此,总的来说,如果效果有什么变化(提升或者下降),应该都跟额外的预训练没直接关系,而是词的引入有直接关系。
September 19th, 2020
师兄好,关于
“它的实验结果是没有错,但却是没有代表性的。为什么这样说呢?因为它比较的是大家的Embedding层都是随机初始化的情况下的效果,这样一来,对于同样的任务,以词为单位的模型Embedding层参数更多,自然就更容易过拟合,效果容易变差,这不用做实验都能猜个大概。问题是,我们用基于词的模型的时候,通常并不是随机初始化的,往往都是用预训练好的词向量的(下游任务看情况选择是否微调词向量),这才是分词的NLP模型的典型场景,但论文里边却没有比较这个场景,所以论文的结果并没有什么说服力”,
我觉得这个论述存在一些问题:
1.“以词为单位的模型Embedding层参数更多,自然就更容易过拟合,效果容易变差”,用预训练好的词向量不是一样参数量更多,更容易过拟合吗?
2. 该文中词向量是随机初始化,但字向量也是随机初始化的,尽管加上一组二者都用预训练好的向量来初始化会更有说服力。但这种比较同样是公平的、值得参考的
我已经说了,词向量的经典用法就是word2vec预训练然后才finetune或者不finetune,既然它没有比较这种用法,那么就是没有代表性的,或者至少说是不全面的。我又不是说它实验是错的,如果你只关心随机初始化的Embedding,那肯定有参考价值呀。
至于“用预训练好的词向量不是一样参数量更多,更容易过拟合”,参数量多少与过拟合没有必然联系,BERT参数量那么大,它效果照样很好,因为它经过了充分的预训练,所以不比较预训练是不充分的。
当然,你说字向量也预训练一下,然后再跟词向量预训练比较,这个思想方向是正确的,但是按照以往的经验,字向量预训练的作用并没有词向量大,所以我估计还是比不过的。我不知道你有没有经历过词向量时代,总之,在很多任务场景里边(比如我司的很多线上模型),“适当的分词+适当的Word2Vec”明显好于基于字的模型。
September 22nd, 2020
請問你們是怎麼分析NER 掉點的問題是出在稀疏性的呢?就是說稀疏跟切分錯誤,兩者你們是如何區分的?
对于NER来说,基于词可能带来的三个问题:稀疏性、切分错误、未登录词。
本文已经说了,加入了多数字之后,未登录词问题基本不存在了,所以只能在稀疏性、切分错误分析。通过样本观察发现,识别错误、识别不出来的,基本上不是因为切分错误,这是“随机挑取+肉眼观察/简单统计”就可以得出的结论,所以只能解释为稀疏性了。
直观理解的话,由于只保留了高频词,所以其实切分错误的概率确实是很小的...
因為你前面提到「不依赖于分词算法,避免边界切分错误」,所以切分錯誤應該是分詞算法引入的,那如果分詞算法把一個專有名詞切錯了,那後面的NER 怎麼可能沒有錯呢?
例如「今日是24節氣中的秋分」,假設分詞算法斷成:「今日/是/24節/氣中/的/秋分」,但我們希望斷成「今日/是/24節氣/中/的/秋分」,然後NER 目標是標出「24節氣」這個詞,那麼前者就不可能標對呀?
還是說,遇到這種情況,用你們的方法,由於詞表中沒有「24節」「氣中」這樣的詞,所以就進一步切割成以字為單位「24 節 氣 中」,然後對每個字做IOB tagging,所以就可以標出「24-B, 節-I, 氣-I, 中-O」?
如果是這樣的話,那麼當詞表中為0個詞,就會變成原始的Bert對吧?而照你的說法原始的Bert做NER效果更好,所以問題是出在加入詞表中的那些詞。因為後來加入的那些詞顯然沒有其組成字出現的那麼頻繁,所以造成了稀疏性的問題,這樣理解對吧?
那如果有一種方法可以將詞跟它的組成字關連起來,例如給每個詞的初始化向量是它的組成字的bert向量取平均,如此這個詞就利用到了字的訊息,解決了稀疏性的問題,這樣理論上就應該至少會跟原始bert一樣好了吧?如果沒有變好,說明可能存在別的問題;如果變得更好,說明使用詞確實有無法替代的優勢。
1、分词把要识别的实体分错的话,NER肯定识别错误,这没有问题;
2、目前观测到的结果是,NER识别错误并不是主要由切分错误引起的;
3、稀疏性的理解没什么问题;
4、每个词向量初始化为字向量的平均,这是一种还凑合的初始化,但没有解决问题。
简单来理解,NER就是要识别每个token的类别,用词的话,平均每个token学习得没那么充分。当然,这也有可能是因为WoBERT是由字模型Finetune过来的的原因,如果完全从零训练一个WoBERT,估计这个问题会有所缓解。
4、每个词向量初始化为字向量的平均,这是一种还凑合的初始化,但没有解决问题。
平均只是舉例,也許可以設計一個方式,讓網路自己學。比方說在訓練階段,遇到詞表中的詞,首先還是切分成字,用bert 輸出字向量,然後再輸入RNN 輸出成一個詞向量;另外一種就是你們的方式,直接讓bert 輸出詞向量。這兩種輸出的詞向量,計算一個MSE loss,加到原本的訓練裡面。
所以若按照你們原本的方式,詞表中的每個詞是隨機初始化的,出現頻率遠比組成字少很多,不能保證fine tune 時一定能學到很好的詞向量表示;但是加上一層RNN之後,fine tune時那些詞可以從其組成字中得到訊息,使得產生的詞向量可能會比隨機初始化更好。然而,由於目標是希望bert 直接輸出詞向量,所以再加上一個MSE loss,讓bert 去學RNN 輸出的詞向量,到預測階段就把RNN的部分拿掉,讓bert直接輸出詞向量。
如此,雖然fine tune時這個詞出現次數不多,但是其組成字已經出現過很多次了,而RNN 充分利用這些組成字的資訊,就相當於這個詞已經看過很多遍了(例如,人類看到某個詞中出現了某個字,大概就可以猜出是甚麼意思,不會因為這個詞沒看過,就對這個詞毫無所知。),所以可以解決詞出現次數不多的問題。
2、目前观测到的结果是,NER识别错误并不是主要由切分错误引起的;
但是仍然有一種可能是,某句話存在多種切分法,而每種切法切出來的詞都是常用詞。
例如:
下雨天留客天留我不留
下雨/天留客/天留/我不留
下雨天/留客天/留我不/留
下雨天/留客/天留我不/留
「下雨、下雨天、留客、留客天、留、不留」這些詞都有可能出現在詞表中,但是若分詞算法選擇了其中一種切法,你們的做法就會直接從詞表裡面抓出對應的詞。但是也許那個詞根本不應該被切出來,它可能是跟著前面的一部分或後面的一部分。但若以字為單位的話,attention 會動態的決定某個字應該跟前面還是後面連在一起,就避免了整體準確率受限於分詞算法的問題。
我認為如果要以詞為單位,又要避免分詞算法出錯造成後續影響,可能還需要某種「重新組詞」的功能,就是在最後的輸出上再重新決定哪些詞需要重分?讓前面錯誤的分詞也能得到修正的結果。
1、感觉上还有因为WoBERT是从以字为单位的BERT中finetune过来的,限制了WoBERT的能力;
2、如果还要想办法实现这个目的,倒不如直接用基于字的模型?其实我更加看好基于词的模型在分类任务和生成任务上的场景,毕竟术业有专攻也没什么不好的。
September 24th, 2020
你好,我想使用您的gitub中的train.py训练我自己的bert模型,我有一套自己的词组和文章作为训练语料,但是训练结果不能保存为bert_config.json,bert_model.ckpt,vocab.txt这种格式,我查看了bert4keras的源码,但是没找到怎么保存
September 24th, 2020
哈哈哈,不好意思打扰了,问题解决了,睡了一觉再醒来幡然醒悟就解决了,MMP
bert_config.json是手工编辑的,vocab.txt可以用bert4keras.tokenizers.save_vocab保存,bert_model.ckpt可以用bert.save_weights_as_checkpoint转换。
September 25th, 2020
Word-based BERT,我的BERT!
哈哈哈哈哈哈哈哈
September 25th, 2020
如果生成的文本含有并列结构,比如一个人的头衔什么的,Exposure Bias的情况还是会经常出现。
September 25th, 2020
你好,想问一下wobert词表里有类似bert的[unused]的词吗?
我去掉了。你要加可以随机加回去,然后修改一下embedding层的权重就行了。
苏老师您好✨,我一直苦苦寻觅Word级别的中文PLM,很感谢您开源了这项工作。在使用WoBERT时,我尝试在vocab.txt词表中添加了“家居”和“时政”两个词表文件中没有的词汇后,使用模型时报了这样的错:
```
CUDA error: device-side assert triggered
```
然后,我又去掉了添加的两个词汇后,代码顺利的运行了。所以想请教一下您,关于“修改一下embedding层的权重”的具体操作是?
谢谢苏老师。
你是要自己重新预训练模型吗?如果是,应该不会有什么错才对;如果不是,谁教你的往训练好的模型词表里边加新词的...
还有,为什么会有pytorch出现?预训练加新词的思路,是我在我的bert4keras里边写好的,跟pytorch没关系。
同问“修改一下embedding层的权重”的具体操作是?
具体操作是:
1、随机初始化一个模型;
2、自行去checkpoint读取预训练权重;
3、将自行读取的权重自行赋值到模型中;
4、在这个过程中,当然是可以自行对读取到的权重进行修改后再赋值。
感谢!!
September 28th, 2020
请问woNEZHA是否有对比测试结果
WoNEZHA的实验没那么丰富,但是已有的一些实验数据来看,WoNEZHA vs NEZHA,跟WoBERT vs BERT,结果是类似的,就没有放出来了。
December 7th, 2020
「目前开源的WoBERT是Base版本,在哈工大开源的RoBERTa-wwm-ext基础上进行继续预训练,预训练任务为MLM。」
苏神,请问一下怎么在一个模型的基础上继续预训练且任务只有 MLM 呢?有什么参考的步骤吗?之前只预训练过 BERT,不过任务还是官方的 MLM + NSP。
我不是开源了训练脚本了吗...
嗯嗯,看到了你发的这个脚本(https://github.com/ZhuiyiTechnology/WoBERT/blob/master/train.py),想问一下对于一个一般的模型(比如 T5),如果想继续用 MLM 任务预训练一下自己的语料的话,还是得重新编写训练脚本吗
弄懂keras,模仿着改吧,不算多复杂的事。
好的,谢谢!