Fork me on GitHub

【干货篇】字节跳动:文本归一化与中文纠错

分享嘉宾:于倬浩,字节跳动抖音算法部门
整理出品:扬奇智能社区

导读: 中文纠错技术是实现中文语句自动检查、自动纠错的一项重要技术,其目的是提高语言正确性的同时减少人工校验成本。纠错模块作为自然语言处理最基础的模块,其重要程度不言而喻。在日常生活中,我们经常会在微信、微博等社交工具或公众号文章中发现许多错别字。我们在几个方面对文本出错概率进行了统计:在新媒体领域中,文本出错概率在2%左右;在语音文本识别ASR/OCR领域中,出错率最高可达10%以上。近期的顶级会议上,也有一些优秀的工作,本次分享会上,也会对其中较为有趣的工作进行覆盖,和大家探讨。

嘉宾介绍: 于倬浩老师,字节跳动抖音算法部门, 就读于中国人民大学,目前在字节跳动抖音算法部门。大学期间获得ACM-ICPC竞赛金牌,曾获得NOI全国信息学奥林匹克竞赛银牌。

图片

中文纠错技术是一项非常基础的NLP技术,实现中文语句自动检查、纠错,由于用户无意或者有意产生的,所以这个问题任务也存在一个对抗的问题,尽管现在已经有很多成熟的方案。这个任务本身的应用场景,除了对文本本身进行纠错,改善文本的质量外,还在一些其他应用场景比如说有搜索引擎或者说ARS和OCR文本的纠错,或者是内容安全相关的风控场景,都有一定需求。因为这也是一个相对来说比较成熟的任务,所以有挺多优秀的开源实现。比如在shibing624/pycorrector这个项目也有超过3000 star,所以很多时候大家都直接拿来用(声明:本次分享不涉及任何公司内部数据,不涉及公司内部业务,纯技术性探讨,主要讲一些近期工作进展)。

图片

任务的定义是我们的输入和输出都是包含错误的一个中文文本序列。可能输出序列和输入序列的长度是一样的,而且是对应位置有一个纠错,而不是有一对一,一对多,或者更加复杂的情况,我们现在这里不涉及,因为更加复杂的情况可能要用其他的办法。我们现在任务的一个严格定义:输入是一段长度为n的一个中文文本序列,输出也是一个长度为n的文本序列,而且整句话大多数的token是不需要修改的,这一点也是需要我们去识别,识别一下哪些token是正确,哪些token是错误的,这些都是任务里需要注意的地方。接下来进入正题。

图片

首先讲一篇ACL 2020的工作,是字节跳动AI Lab李航老师团队的一篇工作。模型大致结构看上图就比较清楚。它是一个分阶段的二阶段的一个网络,第一个模块是一个检测网络,第二个模块是一个还原网络,对于检测网络来说,它其实就是一个双向GRU。检测网络输出,对于每个token是错误和不是错误的一个概率分布,第一阶段的输出会辅助第二阶段做纠正。我们到现在为止,很多已有的工作主要都是一个两阶段的工作,检测和纠错是分开的两个过程。

图片

讲一下具体细节,对于检测网络它就是一个简单的双向GRU。它需要监督的是每个token是正确和错误的概率,还原部分是它把第一部分得出的概率乘自己的Embedding。如果这个token大概率是错误的,就想把它换成一个mask,这样在纠错阶段就可以直接用现有的BERT给它做一个纠错。所以现在两个阶段,其实就是把一个直接给BERT去纠错的东西拆分出来,有利于改善可控性与可解释性。具体纠错的方法是直接拿前一个检测网络的输出乘自己的Embedding,再拿错误的概率乘模型Masked token的Embedding。有一两个极端情况:第一个情况是检测网络认为它是一个完全错误的token,那么在还原网络的输入时,直接就是一个Masked token;如果它是一个检测网络认为是完全正确的token,它在还原网络输入就是它原本的token。最后还有一个残差连接,还原网络后在输出的时候会做一个辅助。

图片

训练的时候也是一个End2End的训练过程,是把两个东西放在一起来训,检测网络的loss,还原网络的loss就都写在这里,第一个是监督一下错误的概率,第二个是监督正确还原的概率,最后的总体loss把两阶段loss乘一个超参数,成一个线性组合。

图片

关于纠错,很重要的是数据怎么做,对于大多数工作而言都是用SIGHAN数据去做的。SIGHAN情况有点复杂,有好几年的SIGHAN,而且它是一个数据规模比较小的测试集,所以可能大家普遍是选用自己的数据集去训,最后在SIGHAN上再做测试,这篇工作的方法是直接拿今日头条里一些相对来说低质量的新闻title,做人工标注,挑出来一些有错误的,并且对它哪些位置是错的或它怎么纠错,做了人工标注。但这样得到的是一个小规模的数据集,总共15000多条,其中1/3文本是有错误的,肯定不够训练,所以又有一个在训练过程中起到辅助作用的数据。他拿了大小为500万的一个文本,来源相同,只不过没有经过人工标注,直接选一些质量相对高一些,接下来直接用一个confusion set做一个随机替换。其中他拿到confusion-set的时候,80%的情况下会用confusion-set里同音字去做替换,20%的情况下,会拿整个字符集里随便挑一个token出来换,这里强行把一个没有问题,或者假设为没有错误的文本去生成有错误的文本,再去训练。最后在训练集上训练完成后,再去SIGHAN上做评测,

图片

当时在检测环节上取得了很不错的效果,比直接用一些基于统计特征,或者说基于规则的方法是要好很多,因为在这篇工作前,大多数的工作都是用非深度,并没有用预训练这种方法,所以算是当时创新点比较多的一篇。

图片

测完这个测试集表现后,还看了下什么情况会导致方案失效,对于一些hard case做了些分析,发现对于67%的错误是需要比较强的推理能力才可以实现。比如说上图case改“高”或者改“心 ”,语法上都是没有问题的,但需要综合整句话的context,结合context才能做出一个正确判断,第二种我理解就是一些领域知识,如上图在“江”的名称或一些专有名称上,如果数据里没有涉及到,那像专有名词就比较难纠正。我理解在很多工程实践上,都是采用一个混合方式,不光是纯粹的一个模型,还有一些其他方式去对需要领域知识做处理。

图片

第二篇是发布在ACL 2021,把纠错做Pre-training的一篇工作。模型结构也是看上图就大概知道,它跟上一篇工作不太一样的地方在于,它只有一部分的网络,并没有单独分出来一个检测和纠正网络,直接End2End。比如现在有一个token错了,需要监督的有两件事,一个是正确的token是谁,比如在上图是“风”,第二个是不仅要监督一个正确的token是谁,还想额外监督一下正确token的发音是什么,比如上图的“凡”。通过一些方法,去融入一些字音和字形上的知识,同时监督最后正确的发音是什么。除此之外,这个模型本身比较简单,但通过Embedding的方式去引入了一些额外的知识,具体方法大概是上图展示的。

图片

具体而言,引入这些额外知识,包含两方面,第一个就是发音的知识,第二个是字形的知识。发音的知识很多时候错误的原因,是我们打错了一个字,或选的是一个音近的字,这种情况下,模型本身的context有拼音相关或者发音相关的知识的话,在直觉上讲是很有利于去做纠错的。同时除了在音近的情况,还有一个形近的情况,这种情况考虑给模型一些额外的关于字形知识,会通过字形的知识去改善我们纠错的效果,所以他考虑通过Embedding的方式去引入额外的一些特征。至于为什么要通过引Embedding的方式,而不是通过其他的一些多模态的方式引入,我理解是比如说一个token,比如说“凡”字不管在序列中什么位置,不管它context是什么样的,它的发音和字形大概都不太会变,这一点是我们需要注意的。但是其实也有情况,比如说多音字,在不同的上下文可能读音不太一样。大致上讲,它是把一些额外的知识通过Embedding的方式去引入。具体做法对于拼音来说,其实用了一个GRU,比如直接把一个通过掉一些那种现有的包,把“凡”这个字转化成拼音,再把拼音通过GRU得到表征。接下来对于字形而言,也是用了比较简单的方法——直接拆分成笔画以后,对于每个笔画用一个GRU去得到它的表征。最后表征结合起来和原来BERT结合起来,就是最后得到他想要的Embedding。

图片

除此之外,其实跟朴素的 Soft-masked BERT没有太大区别。还有不管监督最后正确的字符是什么,其实还监督了正确的字符发音是什么,因为它输入序列里是包含一些发音信息的,但发音信息很多情况下跟错误的肯定是不太一样,或者说可能是相似的。

图片

所以它另外监督了最后正确token的拼音,它最后的训练目标虽然是一个单阶段的网络,但还是有两个训练目标,第一个是他监督的原本的字被正确预测出来的概念,第二个是原本正确的字对应的拼音正确的概率,最后我们需要监督的loss就是把两个loss加起来做一个监督。

图片

除此之外,它在训练数据上比上一种做法更好的地方,在于它在数据生成上也是采用了一些小脆骨,他可能除了用BERT Mask以外,跟刚才很大的一点不一样,是自己去在领域数据上做了Pre-training,而且Pre-training时等于直接在替换token的时候做了一些特殊处理。他是在Wiki的数据集,还有新闻的数据上做的培训。Pre-training以后,在纠错数据上做下游的微调。这份工作其实我觉得它最主要的一点,他考虑到我们最后的目标是改正一些应进和行径的问题,所以说他去引入一些额外的特征,包括数据上做的一些小处理,我觉得也是挺有意思的。

图片

下一篇也是想办法融入一些多模态特征,这也是一篇ACL 2021的工作。但跟刚才不太一样的地方在于,首先分成了多个Feature Extractor,有三个东西,一个发音规律的Extractor,还有一个是对于语义本身的叫Semantic Feature Extractor,第三个是有一个字形的Feature Extractor,接下来做的是类似于多模态里纠正的事情,它把多模态的特征在我们最后的Feature Extractor后,通过某种方式做了一个融合。做融合的方式本身也是挺有意思的,也是他提出了一种叫Adaptive Gating的一个机制。大致而言,跟刚才的主要区别是这篇工作是在文本的Encode之前,把多模态的特征作为Embedding传进去了。上一篇是在做Early Fusion,这篇是在做一个Late Fusion,它是把这些特征在过完各个Encoder以后,再去做融合。

图片



看一些具体细节,首先它是有三个Feature Extractor。

图片

先讲抽Pinyin Feature,考虑的是不直接从拼音的字符序列上去做操作,而是先用一个类似TTS的方法,在中文的数据集上做一个fine-tune,得到一个文本,就直接得到拼音序列与拼音序列的发音。对于数据集而言,我们在数据上做fine-tune,结束我们可以看到这篇其实用的叫Tacotron2的 TTS模型。这里的做法是他们在下游数据上做fine-tune,拿Character Embedding出来。Character Embedding指的是如Input Text就是一个拼音的系列了,对于每个拼音的序列,先通过Embedding look up的方式去得到每个token对应的表征,等于说在另一个数据集上得到了每个拼音序列对应发音的表征,比直接用GRU更加贴近真实发音情况。拿这个表征我们再去作为我们拼音Feature,再去做后面的模态融合。大体而言,用了一个现有的 TTS模型,去生成我们拼音序列的表征。

图片

第二个是Glyph Feature Extractor,使用了一个 VGG19的CV模型,在一个中文图像的数据集上,比如说每一个字,都可以拿到图像,在图像可以去做一个分类任务,跟CV最基础的任务是一样的,是直接在预训练好,在Image Net上预训练好的 VGG19出来,在中文图片的小数据集上再做fine-tune,最后把这些层去掉,把过了maxpool层最后的拿出来,作为我们Glyph Feature 表征,也作为之前提的多模态特征之一。除此之外,就像刚才的 Semantic Feature Extractor,其实它就是一个最基础的BERT。三个 Feature Extractor等于三个模型,再分别做不同的事情,接下来就考虑怎么把三个模态再做一个融合。

图片

传统方法一般在不同的模态特征上,做连接或加法,但这篇提出了Adaptive Gating for feature fusion,上图的FP表示的是 pronunciation或者拼音的表征,FG表示的是Glyph的表征,其实是将除Semantic Feature Extractor以外的两种表征分别去做一个融合,最后再乘上一个线性,变成线性组合,最后得到综合的feature fusion。

图片

训练目标跟之前也大同小异——监督前面Feature Extractor做融合后的表征。在表征上直接再套了一个类似于分类器,最后等于得到了输出训练。简单来说,把这几种模块融合做生成,就可以直接得到想要的序列,按照之前的理解而言,并没有分成检测和纠正两种阶段,如果按照刚才的分法,也是一个单一阶段,等于直接从输入得到了输出,虽然可能有很多不同的Feature Extractor。选择这篇工作也是觉得抽Feature的方式比较有意思。

图片

下一篇是ACL的Findings,模型结构跟我们刚才提到的一样,已经是一个两阶段的网络,有检测和还原的两个模块,乍看上去跟刚才的ACL 2020比较像,也是一个Soft-Masked的过程。它的检测模块做了一个序列标注的任务,还原模块也是跟之前工作一样,直接用错误的概率乘Masked token的表征,再用正确的概率乘原本表征。整体模型结构上不太一样的地方在于它没有用产量连接,看上去有一点不同,但是大体来说其实也是比较一样的。

图片

首先不一样的点在于监督的loss中,纠错模块自带一个惩罚项。还原网络其实它得到了Soft-Masked信息,除了这个信息外,两个模块还是想尽量配合起来。具体是怎么做:直接把第一阶段预测的概率乘第二阶段劳动来开会,如有一些低置信度的一些预测,检测网络输出的概率为0.5,0.5就表示说检测网络并不是很确定说token是错了还是对,属于比较模糊的状态,我们通过加权的方法去减小一点,它在最后检测网络loss里面的影响。所以通过这种带权的方法,把不太确定情况的影响尽量减小一点,对于比较确定的情况,如检测网络认为它就是很错误的一个情况,它其实跟原本就是Soft-Masked的loss是一样的,等于没有惩罚项目,最后把两阶段的loss相乘以后再加起来。

图片

另外不太一样的点,他其实也提出来了一些做数据生成的时候的策略,原本的BERT预训练任务就有各种替换,其实还是就两种:一个是直接拿confusion set做随机替换;第二点不太一样,加了一个noisy拼音的地方,比如上图“的”换成了拼音,就变成除了做最基础的字到字的替换外,还加了拼音到字的替换,也是一个小treap。

图片

Performance是比原本的那个Soft-Masked BERT是要好一点的,可以直接对比出来的,因为两个模型的架构基本是一样。检测网络的Encoder和最开始的Soft-Masked不太一样地方,在于Encoder也是一个BERT,而最开始Soft-Masked那篇的检测网其实就是一个双线GRU,所以其实我理解在检测网络这里换了一个更重的模型,虽然在数据上可能有一些好处,但作为实际工程部署的话,可能还是会带来很多性能损失。SIGHAN在三年中,每年都发布了一个数据集的,也是在每个数据集上分别做测试,一般大家看指标分别看检测和还原准确率,还有召回。

图片

下一篇工作跟上面的也大同小异,是一篇ACL 2021的finding,不太一样的地方是抽特征的方式,这些工作如果有什么非常大的结构区别,其实大家都基于BERT的方式去做,只不过多模态特征是在Late Fusion还是Early Fusion,以及多模态特征怎么抽取。这篇理解可把它当Late Fusion,等于并不是当Embedding在做的。

图片

Semantic Encoder就是最开始一个BERT;Phonetic Encoder转换成拼音序列,再一个单向GRU,经过几层transformer,最后得到一个表征出来;Graphic Encoder跟刚才不太一样,它是用ResNet5,也就是5层的ResNet,是一个轻量级的东西,把不同的字对应字体的图片去作为Graphic Encoder的输入,但大致还是通过不同的多模态的Encoder得到了不同的表征,再去做融合。

图片

上图其实也在Fusion上下了功夫,并不是直接做Fusion。比如发音上相近的这种错误,预期的效果是Phonetic Encoder的表征,会对后面的影响更大一些,他需要把更多的信息留到之后的模态融合以后的表征里面,也是一件很符合直觉的事情。如果在拼音上或者说在发音上有一些错误的话,发音的情况肯定是比字形是更重要。算一下对于每一种模态它的分数,接下来在对之前几种模态得到的表征去做一个加权,加权以后得到最后的表征,再去做后面最后分类或其他事情。

图片

接下来有几个非常重要的地方,首先提到的各种情况都是有一个对齐的假设,输入和输出的序列长度都是一样的,但在实际情况,尤其是在各种业务非对齐的情况就很多了,而且甚至非对齐情况是在占主要情况。比如我们在上图两个例子里,有一对一或者说有一对多或者有多对一的情况,这种架构上来说就不太好做,刚才的一个假设,最后输出那个位置,就跟输入位置相对应的,所以如果我们现在处理非对齐的情况,就会带来很多的问题,这一点我们也有比较多的处理方法。

第一种,把它当成编辑序列的操作序列,直接靠不管是序列标注,还是靠一些其他的生成式的方法,得到需要纠错的编辑序列,如果能直接拿到编辑序列,再用操作序列对原来的文本进行一些增、删、改,就可以做到处理非对齐的情况,比如操作序列就是去删掉这个字,或者说加上一些字,或替换一些字,如果能去生成一个操作序列也是可以的。

第二点,直接用一些生成式的方法,如基于自回归式的模型,也有挺多相关进展。但比较像另外一个语法错误纠正的任务,因为之前提到的都是去纠正一个字形上或者说字音上一些拼写上的错误,但是如果要涉及更加复杂的情况,如语法错误,其实可以考虑采用生成式的模型。我们还可以用一些比较tricky方法,把之前的一些做法强行转化到刚才提到那种做法,比如说把一开始检测模型的一些标注任务搞复杂一些,让它能处理这种非类型情况,这也是有相关工作,但是跟对齐的情况来讲,研究的少一些,相对开源的工作要少一些。

图片

除此之外是一些部署上的问题,比如像两阶段的网络,或者说有一些更加复杂的那种多个Feature Extractor,其实会带来一些推理上的延迟,因为很多时候服务要上线,都是需要去能承受一定的并发,尤其那种多阶段都会直接带来很多推理延迟上的一些开销,所以这也会对实际应用有一些影响,所以又需要考虑一个到底是要用多阶段还是用单阶段的问题。

第二点我们需要用End2End的方法,还是用两个阶段,分别检测还原。如果我们直接用一个End2End的话,就会有一个可解释性或者说可控性的问题,能不能控制一些它不太确定的地方不去改,如果是End2End的话,会比较难,在解释性上和可控性上会有一些问题。另外一个问题——数据稀缺性,因为这个任务,基本上都是在自己去基于Confusion-set的方法去做生成,去生成一波数据,在生成的数据上再训练,因为比较难积累高质量的有标注的数据,而且标注的成本还是高的,所以带来的问题是如果我们基于 computer做替换,会不会让我们模型泛化有一定的影响,可能最后泛化能力会比较差。

图片

我今天主要分享一些开源的工作,总结近两年一些进展主要还是基于一些NN的方法,而且融入了一些多模态,还有模态融合的小tricks,另外我们可能需要根据实际的业务场景去选择数据的生成策略,对下游影响,我觉得是应该能超过多模态特征的这一点影响的。


本文地址:https://www.6aiq.com/article/1653674012600
本文版权归作者和AIQ共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出