中文分词的古今中外,你想知道的都在这里
一只小狐狸带你解锁NLP/ML/DL秘籍
作者:QvQ,夕小瑶,小鹿鹿鹿
虽然英文中有天然的单词分隔符(空格),但是常有单词与其他标点黏滞的情况,比如"Hey, how are you."中的"Hey"和"you"是需要与身后的标点分隔开的
目录
为什么需要分词?
能不能不分词?
中文分词难在哪?
从古至今的分词算法:词典到预训练
从中到外的分词工具
为什么需要分词?
显然,要解决上述问题的话,分词就是了,分词之后,最小的输入单位就不再是字了,而是词,由于“哈哈”与“哈士奇”是两个不同的词,自然就不会有前面这个问题啦。因此可以认为,分词缓解了“一字多义”的问题。
除此之外,从特征(feature)与NLP任务的角度来说,字相比词来说,是更原始和低级的特征,往往与任务目标的关联比较小;而到了词级别后,往往与任务目标能发生很强的关联。比如对于情感分类任务,“我今天走狗屎运了”这句中的每个字都跟正向情感关系不大,甚至“狗”这个字还往往跟负面情感密切相关,但是“狗屎运”这个词却表达了“幸运”、“开心”、“惊喜”的正向情感,因此,分词可以看作是给模型提供了更high-level、更直接的feature,丢给模型后自然容易获得更佳的表现。
能不能不分词?
然而,从上面这段的描述也能看出,要满足这个条件,是很难得的。这需要训练语料非常丰富,且模型足够大(可以有额外容量来内置一个隐含的分词模型),才有可能获得比“分词器+词级模型“更好的表现。这也是为什么BERT等大型预训练模型往往是字级别的,完全不需要使用分词器。
此外,分词也并不是百利而无一害的,一旦分词器的精度不够高,或者语料本身就噪声很大(错字多、句式杂乱、各种不规范用语),这时强行分词反而容易使得模型更难学习。比如模型终于学会了“哈士奇”这个词,却有人把哈士奇打成了“蛤士奇”,结果分词器没认出来,把它分成了“蛤”、“士”、“奇”这三个字,这样我们这个已经训练好的“word level模型”就看不到“哈士奇”了(毕竟模型训练的时候,“哈士奇”是基本单位)。
中文分词难在哪?
1 歧义问题
2 未登录词问题
3 规范问题
算法篇
1 基于词典
对于中文分词问题,最简单的算法就是基于词典直接进行greedy匹配。 比如,我们可以直接从句子开头的第一个字开始查字典,找出字典中以该字开头的最长的单词,然后就得到了第一个切分好的词。比如这句“夕小瑶正在讲NLP”,查字典发现“夕小瑶”是最长的词了,于是得到
夕小瑶/正在讲NLP
然后从下一个词的开头开始继续匹配字典,发现“正在”就是最长的词了,于是
夕小瑶/正在/讲NLP
依此类推,最终得到
夕小瑶/正在/讲/NLP
这种简单的算法即为前向最大匹配法(FMM)
虽然做法很朴素,但是名字听起来还有点高端╮(╯▽╰)╭
不过,由于中文句子本身具有重要信息后置的特点,从后往前匹配的分词正确率往往要高于从前往后,于是就有了反向进行的“后向最大匹配法(BMM)”。 当然了,无论是FMM还是BMM,都一定存在不少切分错误,因此一种考虑更周到的方法是“双向最大匹配”。 双向最大匹配算法是指对待切分句子分别使用FMM和RMM进行分词,然后对切分结果不重合的歧义句进行进一步的处理。通常可对两种方法得到的词汇数目进行比较,根据数目的相同与否采取相应的措施,以此来降低歧义句的分词错误率.
2 基于统计
2.1 基于语言模型
犯这种错误的根本原因在于,基于词典的方法在切分时是没有考虑词语所在的上下文的,没有从全局出发找最优解。其实上面这个句子无非就是在纠结两种切分方式:
a. 没关系/,/除/夕小瑶/在家/做饭/。b. 没关系/,/除夕/小瑶/在家/做饭/。
我们日常说话中很少会有人说出“没关系/,/除/xxxx/做饭/。”这种句子,而第二个句子出现的频率则会非常高,比如里面的“小瑶”可以替换成“我”、“老王”等。显然给定一个句子,各种切分组合是数量有限的,如果有一个东西可以评估出任何一个组合的存在合理性的分值,那么不就找到了最佳的分词组合嘛!
所以,这种方法的本质就是在各种切词组合中找出那个最合理的组合,这个过程就可以看作在切分词图中找出一条概率最大的路径:
显然,当m取值稍微一大,乘法链的后面几项会变得非常难计算(估计出这几项的概率需要依赖极其庞大的语料才能保证估计误差可接受)。计算困难怎么办?当然是用合理的假设来简化计算,比如我们可以假设当前位置取什么词仅取决于相邻的前面n个位置,即
这种简化的语言模型就称为n-gram语言模型。这样乘法链中的每个乘子都可以在已经完成人工标注的分词语料中计算得到啦。当然了,在实际计算中可能还会引入一些平滑技巧,来弥补分词语料规模有限导致的估计误差,这里就不展开讲啦。
2.2 基于统计机器学习
样本标签
一般用{B:begin, M:middle, E:end, S:single}这4个类别来描述一个分词样本中每个字所属的类别。它们代表的是该字在词语中的位置。其中,B代表该字是词语中的起始字,M代表是词语中的中间字,E代表是词语中的结束字,S则代表是单字成词。
一个样本如下所示:
人/b 们/e 常/s 说/s 生/b 活/e 是/s 一/s 部/s 教/b 科/m 书/e
之后我们就可以直接套用统计机器学习模型来训练出一个分词器啦。统计序列标注模型的代表就是生成式模型的代表——隐马尔可夫模型(HMM),和判别式模型的代表——(线性链)条件随机场(CRF)。已经对这两个模型很熟悉的小伙伴可以跳过。
隐马尔可夫模型(HMM)
HMM模型的详细介绍见
《如果你跟夕小瑶恋爱了... (上)》 / 《下》
举个栗子!
我们的输入是一个句子:
观测序列就是我可以直接看到的序列,也就是“小Q硕士毕业于中国科学院”这个字序列,而状态序列则是不能通过肉眼直接观察到的内在序列,也就是上面这句话所对应的标注结果“BEBEBMEBEBME”,而我们的HMM模型,就可以帮助我们完成从观测序列->状态序列的华丽变身!
用数学抽象表示如下:用
我们的理想输出的是
可是,上面这个式子也太难算了吧!!!为此,聪明的科学家们引入了两个可以简化计算的假说:
观测独立性假设:每个字的输出仅仅与当前字有关,即每个λ的值只依赖于其对应的O值,即
齐次马尔可夫假设:每个输出仅仅与上一个输出有关,即Oi的值只依赖于Oi-1,即
综合上面两个式子,我们得到:
在HMM中,将
最后,求解
条件随机场 (CRF)
HMM隐马模型有一个非常大的缺点,就是其存在输出独立性假设,导致其不能将上下文纳入特征设计,大大限制了特征的可用范围。CRF则没有这个限制,它不对单独的节点进行归一化,而是对所有特征进行全局归一化,进而全局的最优值。因此,在分词问题上,显然作为判别式模型的CRF相比HMM更具优越性。
关于CRF的详细介绍可以看小夕之前写的这篇推文:《逻辑回归到条件随机场》 这里再来简单回顾一下。
HMM模型围绕的是一个关于序列X和Y的联合概率分布𝑃(𝑋,𝑌),而条件随机场则围绕条件概率分布模型𝑃(𝑌|𝑋)展开。
CRF通过定义条件概率P(Y∣X) 来描述模型。
这里即表示如果当前位置的字(观测值)是‘瑶’,且上个位置的字是‘小’,且上个位置的预测标签是类别‘I’,那么该特征函数会输出1,也就是说该特征的特征值为1,其余情况均为0。显然这个特征一旦取值为1,则是一个很强的特征来指示‘瑶’这个字的位置的预测标签为‘E’。 与HMM一样,训练CRF中的参数依然是通过万能的极大似然估计,具体算法形式如梯度下降法、IIS、拟牛顿法等。训练好CRF分词模型后,跟HMM一样, 可以通过Viterbi算法来进行全局的推理,从而得到最优的分词序列。这里同样不展开讲啦。 总结一下,与HMM比,使用CRF进行分词有以下优点:
CRF可以使用输入文本的全局特征,而HMM只能看到输入文本在当前位置的局部特征 CRF是判别式模型,直接对序列标注建模;HMM则引入了不必要的先验信息
3 基于神经网络
3.1 基于(Bi-)LSTM
对LSTM模型还不熟悉的小伙伴见小夕以前写的这篇的《step-by-step to LSTM》,本文对lstm的基本理论不再赘述啦。
如前面语言模型一节中所述,字的上下文信息对于排解切分歧义来说非常重要,能考虑的上下文越长,自然排解歧义的能力就越强。而前面的n-gram语言模型也只能做到考虑一定距离的上下文,那么有没有在理论上能考虑无限长上下文距离的分词模型呢? 答案就是基于LSTM来做。 当然啦,LSTM是有方向的,为了让每个位置的字分类时既能考虑全部历史信息(左边的所有的字),又能考虑全部未来信息(右边所有的字),我们可以使用双向LSTM(Bi-LSTM)来充当序列标注的骨架模型,如图LSTM完成对每个位置的上下文信息的编码后,最终通过softmax分类层完成对每个位置的分类,从而跟HMM和CRF一样完成了基于序列标注的中文分词。
3.2 基于预训练模型+知识蒸馏
最近的一年多的时间里,BERT、ERNIE、XLNet等大型预训练席卷了NLP的绝大部分领域,在分词问题上也有显著的优越性。
工具篇
1 Jieba
说到分词工具第一个想到的肯定是家喻户晓的“结巴”中文分词,主要算法是前面讲到的基于统计的最短路径词图切分,近期还内置了百度飞桨的预训练模型+大规模蒸馏的前沿分词模型。github项目地址:https://github.com/fxsjy/jieba使用示例:
#encoding=utf-8
#Jieba
#pip install jieba
import jieba
sentence = "不会讲课的程序员不是一名好的算法工程师"
tokens = jieba.cut(sentence)
print("jieba: " + " ".join(tokens))
#output
#Building prefix dict from the default dictionary ...
#Loading model from cache /tmp/jieba.cache
#Loading model cost 0.266 seconds.
#Prefix dict has been built successfully.
#jieba: 不会 讲课 的 程序员 不是 一名 好 的 算法 工程
2 THULAC(THU Lexical Analyzer for Chinese)
由清华大学自然语言处理与社会人文计算实验室研制推出的一套中文词法分析工具包,具有中文分词和词性标注功能。该工具所采用的分词模型为结构化感知机。更多算法细节请参考github项目和阅读论文原文。
github项目地址:https://github.com/thunlp/THULAC
论文链接:https://www.mitpressjournals.org/doi/pdf/10.1162/coli.2009.35.4.35403
使用示例:
#THULAC
#pip install thulac
import thulac
sentence = "不会讲课的程序员不是一名好的算法工程师"
thu1 = thulac.thulac(seg_only=True) #只分词
text = thu1.cut(sentence, text=True) #进行一句话分词
print("THULAC: " + text)
#output
#Model loaded succeed
#THULAC: 不 会 讲课 的 程序员 不 是 一 名 好 的 算法 工程师
3 NLPIR-ICTCLAS汉语分词系统
主页:http://ictclas.nlpir.org/github项目地址:https://github.com/tsroten/pynlpir使用示例:
#NLPIR-ICTCLAS
#pip install pynlpir
import pynlpir
sentence = "不会讲课的程序员不是一名好的算法工程师"
pynlpir.open()
tokens = [x[0] for x in pynlpir.segment(sentence)]
print("NLPIR-TCTCLAS: " + " ".join(tokens))
pynlpir.close()
#output
#NLPIR-TCTCLAS: 不 会 讲课 的 程序员 不 是 一 名 好 的 算法 工程
4 LTP
项目主页:https://www.ltp-cloud.com/github项目地址:https://github.com/HIT-SCIR/ltp论文链接:http://jcip.cipsc.org.cn/CN/abstract/abstract1579.shtml使用示例:使用前需下载分词模型(http://ltp.ai/download.html)
#LTP
#pip install pyltp
from pyltp import Segmentor
sentence = "不会讲课的程序员不是一名好的算法工程师"
segmentor = Segmentor()
#segmentor.load("/path/to/your/cws/model")
segmentor.load("ltp_data_v3.4.0/cws.model")
tokens = segmentor.segment(sentence)
print("LTP: " + " ".join(tokens))
segmentor.release()
#output
#LTP: 不 会 讲课 的 程序员 不 是 一 名 好 的 算法 工程
5 HanLP
#HanLP
#v1.x
#pip install pyhanlp
from pyhanlp import *
sentence = "不会讲课的程序员不是一名好的算法工程师"
print(HanLP.segment(sentence))
#HanLP
#v2.0
#pip install hanlp
import hanlp
sentence = "不会讲课的程序员不是一名好的算法工程师"
tokenizer = hanlp.load('PKU_NAME_MERGED_SIX_MONTHS_CONVSEG')
tokens = tokenizer(sentence)
print("hanlp 2.0: " + " ".join(tokens))
#output
#hanlp 2.0: 不 会 讲课 的 程序员 不 是 一 名 好 的 算法 工程
6 Stanford CoreNLP
github项目地址:https://github.com/Lynten/stanford-corenlp论文链接:https://nlp.stanford.edu/pubs/sighan2005.pdf使用示例:需要先从stanford官网下载中文切词模型(https://stanfordnlp.github.io/CoreNLP/)
###stanford CoreNLP
#pip install stanfordcorenlp
from stanfordcorenlp import StanfordCoreNLP
sentence = "不会讲课的程序员不是一名好的算法工程师"
with StanfordCoreNLP(r'stanford-chinese-corenlp-2018-10-05-models', lang='zh') as nlp:
print("stanford: " + " ".join(nlp.word_tokenize(sentence)))
可
能
喜
欢
夕小瑶的卖萌屋
关注&星标小夕,带你解锁AI秘籍
订阅号主页下方「撩一下」有惊喜