以前老是在看前沿文章,真正落实到工业级任务仍是须要实打实的硬核基础,我司选用了HANLP做为分词组件,在使用的过程当中才感觉到本身基础的薄弱,决定最近好好把分词的底层算法梳理一下。算法
NLP的底层任务由易到难大体能够分为词法分析、句法分析和语义分析。分词是词法分析(还包括词性标注和命名实体识别)中最基本的任务,能够说既简单又复杂。说简单是由于分词的算法研究已经很成熟了,大部分的准确率均可以达到95%以上,说复杂是由于剩下的5%很难有突破,主要由于三点:数组
然而,在真实的应用中每每会由于以上的难点形成分词效果欠佳,进而影响以后的任务。对于追求算法表现的童鞋来讲,不只要会调分词包,也要对这些基础技术有必定的了解,在作真正的工业级应用时有能力对分词器进行调整。这篇文章不是着重介绍某个SOTA成果,而是对经常使用的分词算法(不只是机器学习或神经网络,还包括动态规划等)以及其核心思想进行介绍。网络
我认为分词算法根据其核心思想主要分为两种,第一种是基于字典的分词,先把句子按照字典切分红词,再寻找词的最佳组合方式;第二种是基于字的分词,即由字构词,先把句子分红一个个字,再将字组合成词,寻找最优的切分策略,同时也能够转化成序列标注问题。归根结底,上述两种方法均可以归结为在图或者几率图上寻找最短路径的问题。接下来将以“他说的确实在理”这句话为例,讲解各个不一样的分词算法核心思想。数据结构
最大匹配分词寻找最优组合的方式是将匹配到的最长词组合在一块儿。主要的思路是先将词典构形成一棵Trie树,也称为字典树,以下图:并发
Trie树由词的公共前缀构成节点,下降了存储空间的同时提高查找效率。最大匹配分词将句子与Trie树进行匹配,在匹配到根结点时从新由下一个字开始进行查找。好比正向(从左至右)匹配“他说的确实在理”,得出的结果为“他说/的确/实在/理”。若是进行反向最大匹配,则为“他/说/的/确实/在理”。app
可见,词典分词虽然能够在O(n)时间对句子进行分词,可是效果不好,在实际状况中基本不使用此种方法。机器学习
最短路径分词算法首先将一句话中的全部词匹配出来,构成词图(有向无环图DAG),以后寻找从起始点到终点的最短路径做为最佳组合方式,引用《统计天然语言处理》中的图:ide
图中每条边的权重都为1。函数
在求解DAG图的最短路径问题时,老是要利用到一种性质:即两点之间的最短路径也包含了路径上其余顶点间的最短路径。好比S->A->B->E为S到E到最短路径,那S->A->B必定是S到B到最短路径,不然会存在一点C使得d(S->C->B)<d(S->A->B),那S到E的最短路径也会变为S->C->B->E,这就与假设矛盾了。利用上述的最优子结构性质,能够利用贪心算法或动态规划两种求解算法:高并发
基于Dijkstra算法求解最短路径。该算法适用于全部带权有向图,求解源节点到其余全部节点的最短路径,并能够求得全局最优解。Dijkstra本质为贪心算法,在每一步走到当前路径最短的节点,递推地更新原节点到其余节点的距离。但应用于分词问题时,因为词图是带权有向无环图,没法求得全局最优解。不过针对当前问题,Dijkstra算法的计算结果为:“他/说/的/确实/在理“。可见最短路径分词算法能够知足部分分词要求。
2. N-最短路径分词算法
N-最短路径分词是对Dijkstra算法的扩展,在每一步保存最短的N条路径,并记录这些路径上当前节点的前驱,在最后求得最优解时回溯获得最短路径。该方法的准确率优于Dijkstra算法,但在时间和空间复杂度上都更大。
在前文的词图中,边的权重都为1。而现实中却不同,经常使用词的出现频率/几率确定比罕见词要大。所以能够将求解词图最短路径的问题转化为求解最大几率路径的问题,即分词结果为“最有可能的词的组合“。计算词出现的几率,仅有词典是不够的,还须要有充足的语料。所以分词任务已经从单纯的“算法”上升到了“建模”,即利用统计学方法结合大数据挖掘,对“语言”进行建模。
语言模型的目的是构建一句话出现的几率p(s),根据条件几率公式咱们知道:
而要真正计算“他说的确实在理”出现的几率,就必须计算出上述全部形如 n=1,...,6 的几率,计算量太过庞大,所以咱们近似地认为:
其中 ,
为字或单词。咱们将上述模型成为二元语言模型(2-gram model)。相似的,若是只对词频进行统计,则为一元语言模型。因为计算量的限制,在实际应用中n通常取3。
咱们将基于词的语言模型所统计出的几率分布应用到词图中,能够获得词的几率图:
对该词图用2.1.2中的算法求解最大几率的路径,便可获得分词结果。
与基于词典的分词不一样的是,基于字的分词事先不对句子进行词的匹配,而是将分词当作序列标注问题,把一个字标记成B(Begin), I(Inside), O(Outside), E(End), S(Single)。所以也能够当作是每一个字的分类问题,输入为每一个字及其先后字所构成的特征,输出为分类标记。对于分类问题,能够用统计机器学习或神经网络的方法求解。
统计机器学习方法经过一系列算法对问题进行抽象,进而获得模型,再用获得的模型去解决类似的问题。也能够将模型当作一个函数,输入X,获得f(X)=Y。另外,机器学习中通常将模型分为两类:生成式模型和判别式模型,二者的本质区别在于X和Y的生成关系。生成式模型以“输出Y按照必定的规律生成输入X”为假设对P(X,Y)联合几率进行建模;判别式模型认为Y由X决定,直接对后验几率P(Y|X)进行建模。二者各有利弊,生成模型对变量的关系描述更加清晰,而判别式模型容易创建和学习。下面对几种序列标注方法作简要介绍。
生成式模型主要有n-gram模型、HMM隐马尔可夫模型、朴素贝叶斯分类等。在分词中应用比较多的是n-gram模型和HMM模型。若是将2.1.3中的节点由词改为字,则可基于字的n-gram模型进行分词,不过这种方法的效果没有基于词的效果要好。
HMM模型是经常使用的分词模型,基于Python的jieba分词器和基于Java的HanLP分词器都使用了HMM。要注意的是,该模型建立的几率图与上文中的DAG图并不一样,由于节点具备观测几率,因此不能再用上文中的算法求解,而应该使用Viterbi算法求解最大几率的路径。
判别式模型主要有感知机、SVM支持向量机、CRF条件随机场、最大熵模型等。在分词中经常使用的有感知机模型和CRF模型:
感知机是一种简单的二分类线性模型,经过构造超平面,将特征空间(输入空间)中的样本分为正负两类。经过组合,感知机也能够处理多分类问题。但因为每次迭代都会更新模型的全部权重,被误分类的样本会形成很大影响,所以采用平均的方法,在处理完一部分样本后对更新的权重进行平均。
2. CRF分词算法
CRF能够说是目前最经常使用的分词、词性标注和实体识别算法,它对未登录词有很好的识别能力,但开销较大。
目前对于序列标注任务,公认效果最好的模型是BiLSTM+CRF。结构如图:
利用双向循环神经网络BiLSTM,相比于上述其它模型,能够更好的编码当前字等上下文信息,并在最终增长CRF层,核心是用Viterbi算法进行解码,以获得全局最优解,避免B,S,E这种标记结果的出现。
前文主要讲了分词任务中所用到的算法和模型,但在实际的工业级应用中,仅仅有算法是不够的,还须要高效的数据结构进行辅助。
中文有7000多个经常使用字,56000多个经常使用词,要将这些数据加载到内存虽然容易,但进行高并发毫秒级运算是困难的,这就须要设计巧妙的数据结构和存储方式。前文提到的Trie树只能够在O(n)时间完成单模式匹配,识别出“的确”后到达Trie树对也节点,句子指针接着指向“实”,再识别“实在”,而没法识别“确实”这个词。若是要在O(n)时间完成多模式匹配,构建词图,就须要用到Aho-Corasick算法将模式串预处理为有限状态自动机,如模式串是he/she/his/hers,文本为“ushers”。构建的自动机如图:
这样,在第一次到叶节点5时,下一步的匹配能够直接从节点2开始,一次遍历就能够识别出全部的模式串。
对于数据结构的存储,通常能够用链表或者数组,二者在查找、插入和删除操做的复杂度上各有千秋。在基于Java的高性能分词器HanLP中,做者使用双数组完成了Trie树和自动机的存储。
3.2 词图
图做为一种常见的数据结构,其存储方式通常有两种:
邻接矩阵用数组下标表明节点,值表明边的权重,即d[i][j]=v表明节点i和节点j间的边权重为v。以下图:
用矩阵存储图的空间复杂度较高,在存储稀疏图时不建议使用。
2. 邻接表
邻接表对图中的每一个节点创建一个单链表,对于稀疏图能够极大地节省存储空间。第i个单链表中的节点表示依附于顶点i的边,以下图:
在实际应用中,尤为是用Viterbi算法求解最优路径时,因为是按照广度优先的策略对图进行遍历,最好是使用邻接表对图进行存储,便于访问某个节点下的全部节点。
分词做为NLP底层任务之一,既简单又重要,不少时候上层算法的错误都是由分词结果致使的。所以,对于底层实现的算法工程师,不只须要深刻理解分词算法,更须要懂得如何高效地实现。而对于上层应用的算法工程师,在实际分词时,须要根据业务场景有选择地应用上述算法,好比在搜索引擎对大规模网页进行内容解析时,对分词对速度要求大于精度,而在智能问答中因为句子较短,对分词的精度要求大于速度。