自然语言处理从入门到应用——自然语言处理的基础任务:中文分词和子词切分

分类目录:《自然语言处理从入门到应用》总目录


自然语言处理的一大特点是任务种类纷繁复杂,有多种划分的方式。从处理顺序的角度,可以分为底层的基础任务以及上层的应用任务。其中,基础任务往往是语言学家根据内省的方式定义的,输出的结果往往作为整个系统的一个环节或者下游任务的额外语言学特征,而并非面向普罗大众。本文和后文介绍几种常见的基础任务,包括词法分析(分词、词性标注)、句法分析和语义分析等。

中文分词

词(Word)是最小的能独立使用的音义结合体,是能够独立运用并能够表达语义或语用内容的最基本单元。在以英语为代表的印欧语系(Indo-European lan-guages)中,词之间通常用分隔符(空格等)区分。但是在以汉语为代表的汉藏语系(Sino-Tibetan languages),以及以阿拉伯语为代表的闪-含语系(Semito-Hamitic languages)中,却不包含明显的词之间的分隔符。因此,为了进行后续的自然语言处理,通常需要首先对不含分隔符的语言进行分词(Word Segmentation)操作。本文以中文分词为例,介绍词的切分问题和最简单的分词算法。

中文分词就是将一串连续的字符构成的句子分割成词语序列,如“我喜欢读书”,分词后的结果为“我 喜欢 读书”。最简单的分词算法叫作正向最大匹配(Forward Maximum Matching,FMM)分词算法,即从前向后扫描句子中的字符串,尽量找到词典中较长的单词作为分词的结果。具体代码如下:

def fmm_word_seg(sentence, lexicon, max_len):
    begin = 0
    end = min(begin + max_len, len(sentence))
    words = []
    while begin < end:
        word = sentence[begin:end]
        if word in lexicon or end - begin == 1:
            words.append(word)
            begin = end
            end = min(begin + max_len, len(sentence))
        else:
            end -= 1
    return words

正向最大匹配分词算法存在的明显缺点是倾向于切分出较长的词,这容易导致错误的切分结果,如“研究生命的起源”,由于“研究生”是词典中的词,所以使用正向最大匹配分词算法的分词结果为“研究生命的起源”,显然分词结果不正确。这种情况一般被称为切分歧义问题,即同一个句子可能存在多种分词结果,一旦分词错误,则会影响对句子的语义理解。正向最大匹配分词算法除了存在切分歧义,对中文词的定义也不明确,如“哈尔滨市”可以是一个词,也可以认为“哈尔滨”是一个词,“市”是一个词。因此,目前存在多种中文分词的规范,根据不同规范又标注了不同的数据集。另外,就是未登录词问题,也就是说有一些词并没有收录在词典中,如新词、命名实体、领域相关词和拼写错误词等。由于语言的动态性,新词语的出现可谓是层出不穷,所以无法将全部的词都及时地收录到词典中,因此,一个好的分词系统必须能够较好地处理未登录词问题。相比于切分歧义问题,在真实应用环境中,由未登录词问题引起的分词错误比例更高。因此,分词任务本身也是一项富有挑战的自然语言处理基础任务,可以使用包括后文介绍的多种机器学习方法加以解决,将在后续相关文章中进行详细的介绍。

子词切分

一般认为,以英语为代表的印欧语系的语言,词语之间通常已有分隔符(空格等)进行切分,无须再进行额外的分词处理。然而,由于这些语言往往具有复杂的词形变化,如果仅以天然的分隔符进行切分,不但会造成一定的数据稀疏问题,还会导致由于词表过大而降低处理速度。如“computer”“computers”“computing”等,虽然它们语义相近,但是被认为是截然不同的单词。传统的处理方法是根据语言学规则,引入词形还原(Lemmatization)或者词干提取(Stemming)等任务,提取出单词的词根,从而在一定程度上克服数据稀疏问题。其中,词形还原指的是将变形的词语转换为原形,如将“computing”还原为“compute”;而词干提取则是将前缀、后缀等去掉,保留词干(Stem),如“computing”的词干为“comput”,可见,词干提取的结果可能不是一个完整的单词。

词形还原或词干提取虽然在一定程度上解决了数据稀疏问题,但是需要人工撰写大量的规则,这种基于规则的方法既不容易扩展到新的领域,也不容易扩展到新的语言上。因此,基于统计的无监督子词(Subword)切分任务应运而生,并在现代的预训练模型中使用。所谓子词切分,就是将一个单词切分为若干连续的片段。目前有多种常用的子词切分算法,它们的方法大同小异,基本的原理都是使用尽量长且频次高的子词对单词进行切分。此处重点介绍常用的字节对编码(Byte Pair Encoding,BPE)算法。首先,BPE通过下面的算法构造子词词表:

构造子词词表
输入:规模生文本语料库;期望的子词词表大小 L L L
输出:子词词表
算法:
( 1 ) 将语料库中每个单词切分成字符作为子词
( 2 ) 用切分的子词构成初始子词词表
( 3 ) while 子词词表小于或等于 L L L do
( 4 ) \quad 在语料库中统计单词内相邻子词对的频次
( 5 ) \quad 选取频次最高的子词对,合并成新的子词
( 6 ) \quad 将新的子词加入子词词表,并将语料库中不再存在的子词从子词词表中删除
( 7 )end while

下面,通过一个例子说明如何构造子词词表。首先,假设语料库中存在下列Python词典中的3个单词以及每个单词对应的频次。其中,每个单词结尾增加了一个</w>字符,并将每个单词切分成独立的字符构成子词。

{'l o v e r </w>':2, 'n e w e s t </w>':6, 'w i d e s t </w>':3}

初始化的子词词表为3个单词包含的全部字符:

{'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd', '</w>'}

然后,统计单词内相邻的两个子词的频次,并选取频次最高的子词对'e''s',合并成新的子词'es'(共出现9次),然后加入子词词表中,并将语料库中不再存在的子词's'从子词词表中删除。此时,语料库以及子词词表变为:

{'l o v e r </w>':2, 'n e w es t </w>':6, 'w i d es t </w>':3}
{'l', 'o', 'w', 'e', 'r', 'n', 'es', 't', 'i', 'd', '</w>'}

然后,合并下一个子词对’es’和’t’,新的语料库和子词词表为:

{'l o v e r </w>':2, 'n e w est </w>':6, 'w i d est </w>':3}
{'l', 'o', 'w', 'e', 'r', 'n', 'est', 'i', 'd', '</w>'}

重复以上过程,直到子词词表大小达到一个期望的词表大小为止。构造好子词词表后,可以采用贪心的方法将一个单词切分成子词序列,即首先将子词词表按照子词的长度由大到小进行排序。然后,从前向后遍历子词词表,依次判断一个子词是否为单词的子串,如果是的话,则将该单词切分,然后继续向后遍历子词词表。如果子词词表全部遍历结束,单词中仍然有子串没有被切分,那么这些子串一定为低频串,则使用统一的标记,如<UNK>进行替换。例如,对一个含有三个单词的句子['the</w>','highest</w>','mountain</w>']进行切分,假设排好序的词表为['errrr</w>','tain</w>','moun','est</w>','high','the</w>','a</w>'],则子词切分的结果为['the</w>','high','est</w>','moun','tain</w>']。此过程也叫作对句子(单词序列)进行编码。

那么,如何对一个编码后的句子进行解码,也就是还原成原始的句子呢?此时,单词结尾字符</w>便发挥作用了。只要将全部子词进行拼接,然后将结尾字符替换为空格,就恰好为原始的句子了。通过以上过程可以发现,BPE算法中的编码步骤需要遍历整个词表,是一个非常耗时的过程。可以通过缓存技术加快编码的速度,即将常见单词对应的编码结果事先存储下来,然后编码时通过查表的方式快速获得编码的结果。对于查不到的单词再实际执行编码算法。由于高频词能够覆盖语言中的大部分单词,因此该方法实际执行编码算法的次数并不多,因此可以极大地提高编码过程的速度。

除了BPE,还有很多其他类似的子词切分方法,如WordPiece、Unigram Language Model(ULM)算法等。其中,WordPiece与BPE算法类似,也是每次从子词词表中选出两个子词进行合并。与BPE的最大区别在于,选择两个子词进行合并的策略不同:BPE选择频次最高的相邻子词合并,而WordPiece选择能够提升语言模型概率最大的相邻子词进行合并。经过公式推导,提升语言模型概率最大的相邻子词具有最大的互信息值,也就是两子词在语言模型上具有较强的关联性,它们经常在语料中以相邻方式同时出现。与WordPiece一样,ULM同样使用语言模型挑选子词。不同之处在于,BPE和WordPiece算法的词表大小都是从小到大变化,属于增量法。而ULM则是减量法,即先初始化一个大词表,根据评估准则不断丢弃词表中的子词,直到满足限定条件。ULM算法考虑了句子的不同分词可能,因而能够输出带概率的多个子词分段。为了更方便地使用上述子词切分算法,Google推出了SentencePiece开源工具包,其中集成了BPE、ULM等子词切分算法,并支持Python、C++编程语言的调用,具有快速、轻量的优点。此外,通过将句子看作Unicode编码序列,从而使其能够处理多种语言。

参考文献:
[1] 车万翔, 崔一鸣, 郭江. 自然语言处理:基于预训练模型的方法[M]. 电子工业出版社, 2021.
[2] 邵浩, 刘一烽. 预训练语言模型[M]. 电子工业出版社, 2021.
[3] 何晗. 自然语言处理入门[M]. 人民邮电出版社, 2019
[4] Sudharsan Ravichandiran. BERT基础教程:Transformer大模型实战[M]. 人民邮电出版社, 2023
[5] 吴茂贵, 王红星. 深入浅出Embedding:原理解析与应用实战[M]. 机械工业出版社, 2021.

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

von Neumann

您的赞赏是我创作最大的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值