http://heshenghuan.github.io/2015/12/21/%E5%9F%BA%E4%BA%8E%E6%84%9F%E7%9F%A5%E5%99%A8%E7%9A%84%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8D%E7%AE%97%E6%B3%95/
基于字标注的方法的实际上是构词方法,即把分词过程视为字在一串字的序列中的标注问题。由于每个字在构造成词的时候,都有一个确定的位置。也即对于词中的一个字来说,它只能是词首字、词中字、词尾字或单字词一个身份。
以常用的4-tag标注系统为例,假如规定每个字最多有四个构词位置,即:
这里的 {B,M,E,S} {B,M,E,S}就是4-tag标注系统中的四个位置标注。
那么对于任意一个已经过分词的句子,我们都可以用这4个标注组成的序列,表示原来的分词结果。例如:
分词结果:我/爱/北京/天安门/。/
字标注形式:我/S 爱/S 北/B 京/E 天/B 安/M 门/E 。/S
需要指出的是,这里的”字”不只限于汉字,它可以是文本中出现的任何一个字符。因为在真实中文语料中,不可避免地会包含一些数量的非汉字字符,这里所说的”字”也包括外文字母、阿拉伯数字和标点符号等字符。所有这些字符都是构词的基本单元。
基于字标注的方法,把分词从原本的切分问题转化成一个序列标注问题。对于一个含有n个字符的句子 cn1=c1c2…cn c1n=c1c2…cn,可以用下面的公式表示分词原理:
其中, tk tk表示第k个字的标注,即 tk∈{B,M,E,S} tk∈{B,M,E,S}。而 ck+2k−2 ck−2k+2表示取词窗口的大小为5。
而把分词过程视为字的标注问题的一个重要优势就是,它能够平等的对待词表词(In-Vocabulary)和未登录词(Out-of-Vocabulary)。
这样,文本中的词表词和未登录词都是用统一的字标注过程来实现的。在学习架构上,既可以不必要专门强调词表词信息,也不用专门设计特定的未登录词识别模块。
机器学习的模型算法可以专注地从文本语料中学习规律,仔细想想这其实更类似于人类早期学习语言的过程。基于字标注的方法也是目前最主流的中文分词方法。
感知器算法是一个可以解决二分类问题的线性分类模型,其模型对于我这样一个初学者来说都是很容易就可以理解的。基础的二分类感知器这里不再多做介绍,我们把目光转向分词算法所需的多类感知器算法身上。
多类感知器是感知器算法用于解决多类分类问题时的一个扩展,它的主要思想是:用多个感知器去进行多类分类,但每个感知器只将一类目标视为正例,而其他的目标均视为负例。
如上图,假设现有一个c类感知器,可用于c个类别的分类。 Xi Xi(i=1,2,…,N)是一个样本,感知器有4个权重向量 θj θj(j=1,2,…,c),则样本 Xi Xi的类别决策可由如下公式得到:
而多类感知器的更新规则也与原始感知器稍有不同。首先给出多类感知器的代价函数(Cost function):
参数的更新规则(Parameter update rule):
这里的
而根据 C(k) C(k)和 y(k) y(k)的关系,又可以细化为:
用简单一句话来说就是,对于错分类样本,让错的感知器权重缩小一点,让应该对的感知器权重增大一点。这样“此消彼长”,经过几次迭代后就可以正确分类这一个样本了。
多类感知器已经可以应付多类分类的问题了,那么它和中文分词有什么联系呢?连接中文分词与多类感知器的桥梁,就是基于字标注的分词方法。
应用基于字标注的分词方法,分词由切分问题转化为序列标注问题。每个字都有一个标注,也就是说每个字属于一个类别,那么给出一个字的标注的过程其实是一个分类过程。所以,利用多类感知器我们可以解决这样一个问题。
接下来,将要解决的问题就是如何进行特征表示,将文本语料转化成可用于感知器训练的特征向量。
特征模板是抽取语料特征的模式,是词特征学习的基础。(The feature template is the pattern of feature extraction from corpus, and the basis of corpus lexical feature learning.)
句子中的每一个字都会根据特征模板生成特定的特征,我对这些出现的特征进行统计记录,并生成了特征空间。
我的算法所使用的特征模板如下:
(1) Cn(n=−2,−1,0,1,2) Cn(n=−2,−1,0,1,2)
(2) CnCn+1(n=−2,−1,0,1) CnCn+1(n=−2,−1,0,1)
(3) C−1C1 C−1C1
其中, Cn Cn表示处于第n个位置的字。当n=0时,表示当前字。
仍旧以句子『我爱北京天安门。』为例。假设 C0 C0为北,则按照模板将产生以下特征:
以上三组分别对应特征模板中的(1),(2)和(3),可以看到特征模板(1)抽取出了一元特征(unigram),(2)和(3)抽取出了二元特征(bigram)。这些字以及字和字的二元组合就构成了不同的特征,之后采取一定的特征表示方法,将这些字符信息转化成数字信息构建特征向量就可以制作可供感知器训练使用的数据了。
在这里,我采用的方法简单粗暴,就是按照unigram和bigram分类,分别统计出现过的不同特征,并给每个特征一个序号,这个序号就是特征向量的维度信息。当然,特征表示的方法不唯一。
维特比算法(Viterbi algorithm)是一种动态规划算法。它用于寻找最有可能产生观测事件序列的-维特比路径-隐含状态序列。这本是常出现于隐马尔可夫模型(Hiden Markov Model)中的术语,不过Viterbi算法也常被用于寻找观察结果最有可能解释相关的动态规划算法。
在基于字标注的中文分词中,每个字常常以某个概率被标注为标注集中的某个tag。若以每个字最大概率的标注的序列作为一句话的标注结果时,其结果的准确率和分词的效果往往不好,有时可能还会出现不符合分词规则的结果,比如标注”B”后一个标注为”S”。所以,就需要在以句子为整体,在序列上求得最优的标注结果。为此,我想到了利用Viterbi算法求解这个标注结果。
为了应用Viterbi算法,我们需要一些定义一些模型参数,这里借鉴了隐马尔可夫模型。我们将4-tag标注系统中的标注集 S={B,M,E,S} S={B,M,E,S}当作隐含状态集,而可观察状态的序列就是未分词的句子。
假定从语料中统计得到,初始状态i的概率为 πi πi,从状态i转移转移到状态j的转移概率为 αi,j αi,j(这两部分的概率矩阵是通过对训练语料的标注统计得到的。)。
现有一句中文语句 C1:N=c1c2…cN C1:N=c1c2…cN作为观察状态序列,产生此观察状态序列的最有可能的隐含状态序列(标注序列) T1:N=t1t2…tN T1:N=t1t2…tN可由以下递推关系给出:
定义 δi(n) δi(n)为部分最大似然概率,即序号从1~n的字符子串的最大似然值,目表是求解出 T1:N=argmaxT1:NP(C1:N|T1:N) T1:N=argmaxT1:NP(C1:N|T1:N), δi(t) δi(t)满足:
(1) δi(1)=P(c1|t1=i)⋅πi δi(1)=P(c1|t1=i)⋅πi
(2) δi(n)=P(cn|tn=i)⋅maxj∈S(δj(n−1)αj,i) δi(n)=P(cn|tn=i)⋅maxj∈S(δj(n−1)αj,i)
其中 n≤N n≤N, δi(n) δi(n)是以i状态为结尾的前t个观测结果最有可能对应的隐含状态的序列的概率。通过保存向后指针记录上面(2)式中用到的状态j可以获得维特比路径。令 Ptr(n,i)=argmaxj∈S(δj(n−1)αj,i) Ptr(n,i)=argmaxj∈S(δj(n−1)αj,i),表示n时刻的状态i是由n-1时刻哪个状态转移而来的,可得到:
tN=argmaxj∈S(δj(n−1)αj,i) tN=argmaxj∈S(δj(n−1)αj,i)
tn−1=Ptr(n,tn) tn−1=Ptr(n,tn)
这里我们使用arg max的标准定义。算法的时间复杂度为 O(N×|S|2) O(N×|S|2)。
由上面的这些公式可以看到,目前不能从语料中通过统计得到的就是 P(cn|tn=i) P(cn|tn=i),这其实是HMM中的发射概率。在我的算法中,这部分概率是由多类感知器给出的:多类感知器对当前字进行分类,输出它分别属于四个类别的概率。利用这个概率完成上述的计算。
如上图中,图中箭头所示路径代表的序列”S\S\B\E\B\M\E\S”为该句最优的标注结果。
例句『我爱北京天安门。』对应的可能标注序列为 4|C|=48 4|C|=48个,其中还包括不符合分词规范的可能序列。使用暴力方法去求得最优标注序列实在是难为计算机了,为了求解出该序列,就需要使用维特比算法了。
至此,一个基于感知器算法的中文分词算法的基本原理和关键算法已经讲述完毕。整个分词算法的基本思路与基于HMM或CRF的分词算法大体类似,只是在发射概率的计算上使用了多类感知器的分类结果的概率输出,而不是HMM给出。
在模型复杂度上,多类感知器比HMM或CRF简单不少,实现难度也低了不少。最后,我们使用SIGHAN2005(icwb2-data)的语料数据进行了分词实验。作为对比,我们使用同样的特征模板和语料,用CRF++工具训练了基于CRF的中文分词器(这部分的具体操作方法请看:中文分词入门之字标注法4)。以下是两个分词器的效果对比:
表1 PKU语料库上性能指标比较
PKU | CWSP | CRF |
---|---|---|
Recall | 0.923 | 0.924 |
Precision | 0.938 | 0.939 |
F1-Measure | 0.930 | 0.931 |
OOV-Recall | 0.578 | 0.595 |
IV-Recall | 0.944 | 0.944 |
表2 CITYU语料库上性能指标比较
CITYU | CWSP | CRF |
---|---|---|
Recall | 0.937 | 0.941 |
Precision | 0.933 | 0.945 |
F1-Measure | 0.935 | 0.943 |
OOV-Recall | 0.648 | 0.685 |
IV-Recall | 0.960 | 0.961 |
表3 MSR语料库上性能指标比较
MSR | CWSP | CRF |
---|---|---|
Recall | 0.954 | 0.963 |
Precision | 0.965 | 0.966 |
F1-Measure | 0.960 | 0.965 |
OOV-Recall | 0.687 | 0.698 |
IV-Recall | 0.961 | 0.970 |
表4 AS语料库上性能指标比较
AS | CWSP | CRF |
---|---|---|
Recall | 0.950 | 0.956 |
Precision | 0.934 | 0.944 |
F1-Measure | 0.942 | 0.950 |
OOV-Recall | 0.634 | 0.680 |
IV-Recall | 0.964 | 0.968 |
表格中,CWSP表示基于感知器的中文分词算法,CRF表示使用CRF++工具训练的分词算法。可以看到,基于感知器的中文分词算法的performance与基于更复杂模型CRF的分词算法相比相差不到1个百分点,是可以接受的结果。
上面讲到,使用基本的特征模板我们可以得到与基于CRF++工具训练的分词模型差距不大的分词效果。所使用的特征模板为:
(1) Cn(n=−2,−1,0,1,2) Cn(n=−2,−1,0,1,2)
(2) CnCn+1(n=−2,−1,0,1) CnCn+1(n=−2,−1,0,1)
(3) C−1C1 C−1C1
我称这个特征模板为10-feat,在此基础上添加字典信息和字符类别信息特征:
(1) Cn(n=−2,−1,0,1,2) Cn(n=−2,−1,0,1,2)
(2) CnCn+1(n=−2,−1,0,1) CnCn+1(n=−2,−1,0,1)
(3) C−1C1 C−1C1
(4) MWL0,t0 MWL0,t0
(5) Cnt0(n=−1,0,1) Cnt0(n=−1,0,1)
(6) T(C−2)T(C−1)T(C0)T(C1)T(C2) T(C−2)T(C−1)T(C0)T(C1)T(C2)
其中(4)、(5)是字典信息特征, MWL0 MWL0指当前字在字典中匹配的最长词的长度, t0 t0表示当前字在字典最长词中对应的标注;(6)是字符类别信息, T(Cn) T(Cn)得到当前字符的类别,一共有五个类别,“1”类为中文数字类,“2”类为日期类(“日”,“月”,“年”),“3”类为英文字母类,“4”类为中国人名常用姓类,“5”类为其他字符。
使用此特征模板得到的最新分词结果如下:
表5 PKU语料库上性能指标比较
PKU | +both | +type | +dict | 10-feat |
---|---|---|---|---|
Recall | 0.915 | 0.917 | 0.920 | 0.923 |
Precision | 0.942 | 0.944 | 0.937 | 0.938 |
F1-Measure | 0.929 | 0.930 | 0.928 | 0.931 |
OOV-Recall | 0.582 | 0.599 | 0.575 | 0.578 |
IV-Recall | 0.936 | 0.936 | 0.941 | 0.944 |
表6 CITYU语料库上性能指标比较
CITYU | +both | +type | +dict | 10-feat |
---|---|---|---|---|
Recall | 0.947 | 0.947 | 0.938 | 0.937 |
Precision | 0.946 | 0.942 | 0.936 | 0.933 |
F1-Measure | 0.946 | 0.944 | 0.937 | 0.935 |
OOV-Recall | 0.695 | 0.686 | 0.664 | 0.648 |
IV-Recall | 0.967 | 0.968 | 0.961 | 0.960 |
表7 MSR语料库上性能指标比较
MSR | +both | +type | +dict | 10-feat |
---|---|---|---|---|
Recall | 0.954 | 0.961 | 0.954 | 0.954 |
Precision | 0.963 | 0.962 | 0.966 | 0.965 |
F1-Measure | 0.959 | 0.961 | 0.960 | 0.960 |
OOV-Recall | 0.693 | 0.680 | 0.698 | 0.687 |
IV-Recall | 0.961 | 0.968 | 0.961 | 0.961 |
表8 AS语料库上性能指标比较
AS | +both | +type | +dict | 10-feat |
---|---|---|---|---|
Recall | 0.954 | 0.949 | 0.949 | 0.950 |
Precision | 0.944 | 0.931 | 0.935 | 0.934 |
F1-Measure | 0.949 | 0.940 | 0.942 | 0.942 |
OOV-Recall | 0.635 | 0.620 | 0.628 | 0.634 |
IV-Recall | 0.968 | 0.963 | 0.964 | 0.964 |
最新版本的感知器分词算法中,使用了新的记录方式表示特征。但所使用的特征模板不变,仍旧是:
(1) Cn(n=−2,−1,0,1,2) Cn(n=−2,−1,0,1,2)
(2) CnCn+1(n=−2,−1,0,1) CnCn+1(n=−2,−1,0,1)
(3) C−1C1 C−1C1
(4) MWL0,t0 MWL0,t0
(5) Cnt0(n=−1,0,1) Cnt0(n=−1,0,1)
(6) T(C−2)T(C−1)T(C0)T(C1)T(C2) T(C−2)T(C−1)T(C0)T(C1)T(C2)
其中,特征(1)是一元特征(unigram),特征(2)是二元特征(bigram),特征(3)是简化表述的三元特征(trigram)。三元特征的信息对于分词的特征表示有着十分重要的作用,然而其特征数目巨大。
以往的特征表示方式,是将特征(2)(3)记录在一个记录表内,统一分配特征序号。而按照这种方式,结合特征向量的生成方式得到的向量空间十分庞大(unigram*5+bigram*5+dict_feat*4+5^5),而导致特征维度急剧膨胀的原因是trigram的数目巨大且因与bigram数目相加乘以5。
于是,为了解决这个问题,我将trigram特征独立出来,使用单独的变量存储,使得特征空间的维度大幅缩小。并且,感知器分词算法的性能没有受到很大影响,模型训练速度也有所提升,模型文件大小也缩小了不少。
如下表,给出了改进前后的特征空间大小对比,其中old表示原有的特征表示方式,new表示上诉新的特征表示方式:
表9 各语料库上不同特征表示方式特征维度对比
|
PKU | CITYU | MSR | AS |
---|---|---|---|---|
old | 3040610 | 3978515 | 4565345 | 7165700 |
new | 1679392 | 2205392 | 2551039 | 4154112 |
可以看到,特征空间维度的缩减十分明显(44%左右)。下面给出使用新的特征表示方式后,感知器分词算法在四个语料库上的performance:
表10 PKU语料库上性能指标比较
PKU | CWSP(tri) | CWSP |
---|---|---|
Recall | 0.907 | 0.915 |
Precision | 0.940 | 0.942 |
F1-Measure | 0.923 | 0.929 |
OOV-Recall | 0.552 | 0.582 |
IV-Recall | 0.928 | 0.936 |
表11 CITYU语料库上性能指标比较
CITYU | CWSP(tri) | CWSP |
---|---|---|
Recall | 0.945 | 0.947 |
Precision | 0.941 | 0.946 |
F1-Measure | 0.943 | 0.946 |
OOV-Recall | 0.674 | 0.695 |
IV-Recall | 0.967 | 0.967 |
表12 MSR语料库上性能指标比较
MSR | CWSP(tri) | CWSP |
---|---|---|
Recall | 0.951 | 0.954 |
Precision | 0.962 | 0.963 |
F1-Measure | 0.956 | 0.959 |
OOV-Recall | 0.664 | 0.693 |
IV-Recall | 0.959 | 0.961 |
表13 AS语料库上性能指标比较
AS | CWSP(tri) | CWSP |
---|---|---|
Recall | 0.951 | 0.954 |
Precision | 0.941 | 0.944 |
F1-Measure | 0.946 | 0.949 |
OOV-Recall | 0.652 | 0.635 |
IV-Recall | 0.964 | 0.968 |
可以看到在特征空间维度缩减近一半的情况下,F1-Measure平均降低0.4%,在总体上看是可以接受的。但是具体看到OOV-Recall指标和IV-Recall指标,这两项指标的下降有些过于明显。所以该种改进方法是否可取,需要看具体使用环境而定。
在研究这个算法的初始阶段,我使用Python编写了一个基础的算法模型,用于验证和实验。Python版本的感知器分词程序(以下简称CWSP-py)在一定程度上证明了基于感知器的分词算法的可行性,但又有着一些设计和实现上的不足,如速度较慢、句子边界处理不完善,特征表功能不完整等等。
在意识到使用Python编写的感知器分词程序的不足之后,我着手使用C++编写了一个更快、语料处理规则更完善的感知器分词算法(以下简称CWSP-cpp)。CWSP-cpp与CWSP-py的不同之处主要有以下几个部分:
接下来,我将分别对这三个不同进行详细的解释。
在最后一版CWSP-py中,所使用的特征模板为:
(1) Cn(n=−2,−1,0,1,2) Cn(n=−2,−1,0,1,2)
(2) CnCn+1(n=−2,−1,0,1) CnCn+1(n=−2,−1,0,1)
(3) C−1C1 C−1C1
(4) MWL0,t0 MWL0,t0
(5) Cnt0(n=−1,0,1) Cnt0(n=−1,0,1)
(6) T(C−2)T(C−1)T(C0)T(C1)T(C2) T(C−2)T(C−1)T(C0)T(C1)T(C2)
其中,特征(1)~(3)是从语料中直接抽取得到的字特征;特征(4)(5)是结合字典信息产生的特征;特征(6)中的 T(Cn) T(Cn)是一个将字符映射为类别号的函数,共包含5个类别,分别是:日期(Dates)、汉字数字(Nums)、英文字母(Letters)、中国人名常用字(Names)及其他(Others)。
而在CWSP-cpp中,我讲以上特征模板扩展为:
(0) P(C0) P(C0)
(1) Cn(n=−2,−1,0,1,2) Cn(n=−2,−1,0,1,2)
(2) CnCn+1(n=−2,−1,0,1) CnCn+1(n=−2,−1,0,1)
(3) C−1C1 C−1C1
(4) MWL0,t0 MWL0,t0
(5) Cnt0(n=−1,0,1) Cnt0(n=−1,0,1)
(6) T(C−1)T(C0)T(C1) T(C−1)T(C0)T(C1)
(7) N(C−1)N(C0)N(C1) N(C−1)N(C0)N(C1)
(8) F(C−1)F(C0)F(C1) F(C−1)F(C0)F(C1)
特征(0)是标点特征,特征(1)~(5)与CWSP-py的特征模板中对应项的意义相同,特征(6)是类型特征,特征(7)是中国人名特征,特征(8)是外国人名特征。
表14 字符类别号
Code | Type |
---|---|
0 | ANum |
1 | CNum1 |
2 | CNum2 |
3 | EngLetter |
4 | Date |
5 | Others |
特征(6)中, T(Cn) T(Cn)是一个将字映射为类别号的函数,现在共有6个类别,分别是:阿拉伯数字(ANum)、中文数字1(CNum1)、中文数字2(CNum2)、英文字母(EngLetter)、日期(Date)及其他(Others)。
表15 中国人名用字类别号
Code | Type |
---|---|
0 | Frequency Surname |
1 | Common Surname |
2 | Given Name |
3 | both 0+2 |
4 | both 2+3 |
5 | Others |
特征(7)中, N(Cn) N(Cn)是一个将字映射为中国人名用字类别的函数,现在共有6个类别,分别是:0.常见姓(Frequency Surname)、1.普通姓(Common Surname)、2.人名用字(Given Name)、3.both 0+2、4.both 2+3 及 5.其他(Others)。
表16 外国人名用字类别号
Code | Type |
---|---|
0 | Noncommmon FNameChar |
1 | Commmon FNameChar |
特征(8)中, F(Cn) F(Cn)是一个将字映射为中国人名用字类别的函数,现在共有2个类别,分别是:非外国人名常用字(Noncommon FNameChar)及外国人名常用字(Common FNameChar)。
在基于字标注的分词方法中,特征的产生是基于一个滑动窗口的。这个窗口有一个固定的长度,一般为奇数。本算法中使用的窗口长度为5,即对于窗口中心的字来说,取其前二及后二字与其组合形成特征。
由于窗口的长度是固定的,所以在处理句子边界(句子第一个字或最后一个字)处的字符时,就会有窗口中字符不满的情况发生。为了避免这种情况的发生而导致特征抽取失败,一般的做法是在句子首位处添加特殊字符,之后窗口的起始位置还是在原来句子的第一个字符处。
例如,对于句子“我爱北京天安门。”,窗口中心位于“我”、“爱”、“门”和“。”处时,都会发生窗口不满的情况。这时,我们就在句子的首尾各添加两个特殊符号,如在CWSP-py中是用字符“#”,句子发生变化:
我爱北京天安门。 ——> #/#/我/爱/北/京/天/安/门/。/#/#
句中用“/”分隔开的每个字,在算法中都是一个独立的单位。窗口开始的位置仍旧在“我”字处。
与此不同的是,在CWSP-cpp中,句子首尾添加的特殊符号被扩展成了一个集合 {B0,B1,E0,E1} {B0,B1,E0,E1},从而改变了处理过后的句子:
我爱北京天安门。 ——> B1 B1/ B0 B0/我/爱/北/京/天/安/门/。/ E0 E0/ E1 E1
在中文分词领域,有一个问题始终困扰着人们,那就是未登录词(out-of-vocabulary)识别。未登录词之所以难以识别,是因为它从未在训练语料中出现过,自然有关它的特征不会被模型发现收集并用于训练。
但当其出现在训练语料中时,算法仍会按照规则抽取特征,此时抽取的出的特征模型从未见过,自然在模型的特征表中不会有它的记录。这就会导致严重的错误发生:因为没有特征记录,于是无法生成特征向量,导致模型识别能力的下降。我称这一现象为特征丢失。
例如,假设现有一个未登录词“天安门”,窗口中心位于“安”,由于特征表中没有关于这些字的特征,所以特征“天安”、“安门”、“天门”都无法找到对应的特征号,从而丢失了这一部分的数据,抽取出的特征可能只包含了一元特征和一些类别信息特征。
对于一个训练好的模型,并不能随意将减到的未登录特征添加至记录表,因为这可能会导致特征空间的变化,从而导致模型的失效。于是为了防止特征丢失,我们在原始的特征表中预留了一个位置给没见过的特征通用,记为“Unknow”特征。注意,这并不是解决OOV的方案,只是一个小技巧,防止了特征丢失,保证了特征向量的内容数量。
这一个小技巧在CWSP-py中是没有使用的,所以我见过一个未登录词中某个字的特征只剩下了类别特征而其余都丢失了。在CWSP-cpp中,我使用自定义的特征表结构,实现了对于“Unknow”特征的支持。
在完成了CWSP-cpp的编写之后,我对其在Bakeoff-05标准数据集上进行了封闭分词实验,并将结果与目前state-of-the-art的算法进行了对比,结果如下:
表17 CWSP-cpp与state-of-the-art算法结果对比
Models | PKU | MSR | CITYU | AS |
---|---|---|---|---|
(Tseng et al., 2005) | 95.0 | 96.4 | 94.3 | 94.7 |
(Zhao et al., 2006) | — | 96.1 | 97.7 | — |
(Zhang and Clark, 2007) | 95.1 | 97.2 | — | — |
(Zhang et al., 2013) | 96.1 | 97.4 | — | — |
(Chen et al., 2015) | 96.5 | 97.4 | — | — |
(Li et al., 2015) | 97.7 | 98.1 | 96.9 | 96.2 |
(Wang et al., 2009) | 95.2 | 97.1 | 94.4 | 95.1 |
(Wang et al., 2010) | 94.2 | 97.2 | 95.6 | — |
CWSP-py | 94.4 | 95.6 | 94.3 | 94.6 |
CWSP-cpp | 94.6 | 96.9 | 95.2 | 95.3 |