1.背景

NLP中一个最基本任务就是分词,当我们分词完成之后怎么来评判分词结果的好坏呢?换句话来说就是我该如何对分词结果打分?这个分数怎么算法,依照的标准是什么?例如:


原句子:武汉市长江大桥

分词一:武汉 市长 江大桥

分词二: 武汉市 长江大桥


对于分词一和分词二的打分应该是多少呢?为了搞清楚这个问题,我们先来学习(回顾)一些机器学习中的常见分类评估标准。

2.机器学习中的分类评估

2.1 准确率

准确率(accuracy),是一个用来衡量分类器预测结果与真实结果差异的一个指标,越接近于1说明分类结果越准确。举个例子,比如现在有一个猫狗图片分类器对100张图片进行分类,分类结果显示有38张图片是猫,62张图片是狗,经与真实标签对比后发现,38张猫的图片中有20张是分类正确的,62张狗的图片中有57张是分类正确的,那么准确率是多少呢?显然就应该是 ( 20 + 57 ) / 100 = 0.77 (20+57)/100=0.77 (20+57)/100=0.77,即分对的数量除以总的数量就这么简单,这也是准确率好理解的原因之一。

同时可以发现,对于这100张图片来说,其真实标签为狗75张,猫25张。那么现在问题就来了,假如某分类器使坏,对于输入的任何让本其结果都输出为狗,那么从整个结果来看就是狗的照片全都分对,猫的照片全都分错,准去率为 75 / 100 = 0.75 75/100=0.75 75/100=0.75。可是仔细想想这有意义吗?若是猫狗照片数量为10和90,你啥也不做,就这么一搞准确率就到 0.9 0.9 0.9了,岂不无法无天?那我们应该这么避免这种情况呢?那就要轮到精确率和召回率登场了。

2.2 精确率、召回率与F-score

什么精确率(precision)与召回率(recall)呢?我们先用一个矩阵来把上面猫狗分类的结果给展示出来:

NLP中文分词的评估指标_召回率

怎么去读这张表呢?首先从这张表可知:数据集中有25张猫的图片,75张狗的图片;同时对于左图20这个值,其含义是将猫预测为猫的数量为20,同理5表示将猫预测为狗的数量,18表示将狗预测成猫的数量,57表示将狗预测为狗的数量。衍生开就是,如右图所示:


  • True Positive(TP):表示将正样本预测为正样本,即预测正确;
  • False Positive(FP):表示将负样本预测为正样本,即预测错误;
  • False Negative(FN):表示将正样本预测为负样本,即预测错误;
  • True Negative(TN):表示将负样本预测为负样本,即预测正确;

图p0140这个矩阵就称为混淆矩阵(confusion matrix),同时我们可以得到如下计算公式:

A c c u r c a y = T P + T N T P + F P + F N + T N P r e c i s i o n = T P T P + F P R e c a l l = T P T P + F N F − s c o r e = ( 1 + β 2 ) P r e c i s i o n ⋅ R e c a l l β 2 ⋅ P r e c i s i o n + R e c a l l \begin{aligned} Accurcay&=\frac{TP+TN}{TP+FP+FN+TN}\\[3ex] Precision&=\frac{TP}{TP+FP}\\[3ex] Recall&=\frac{TP}{TP+FN}\\[3ex] F-score&=(1+\beta^2)\frac{Precision\cdot Recall}{\beta^2\cdot Precision+Recall} \end{aligned} AccurcayPrecisionRecallFscore=TP+FP+FN+TNTP+TN=TP+FPTP=TP+FNTP=(1+β2)β2Precision+RecallPrecisionRecall

注:当 F − s c o r e F-score Fscore β = 1 \beta=1 β=1时称为 F 1 F_1 F1

可以看到,精确率计算的是预测对的正样本在整个预测为正样本中的比重,而召回率计算的是预测对的正样本在整个真实正样本中的比重。因此一般来说,召回率越高也就意味着这个模型找寻正样本的能力越强。但值得注意的是,通常在实际任务中,并不明确哪一类是正样本哪一类又是负样本,所以每个类别,都可以计算其各项指标:

对于猫来说:

P r e c i s i o n = 20 20 + 18 = 0.53 R e c a l l = 20 20 + 5 = 0.8 F 1 = 2 × 0.53 × 0.8 0.53 + 0.8 = 0.63 \begin{aligned} Precision&=\frac{20}{20+18}=0.53\\[1ex] Recall &= \frac{20}{20+5}=0.8\\[1ex] F_1&=\frac{2\times 0.53\times 0.8}{0.53+0.8}=0.63 \end{aligned} PrecisionRecallF1=20+1820=0.53=20+520=0.8=0.53+0.82×0.53×0.8=0.63

对于狗来说:

P r e c i s i o n = 57 57 + 5 = 0.92 R e c a l l = 57 57 + 18 = 0.76 F 1 = 2 × 0.92 × 0.76 0.92 + 0.76 = 0.83 \begin{aligned} Precision&=\frac{57}{57+5}=0.92\\[1ex] Recall &= \frac{57}{57+18}=0.76\\[1ex] F_1&=\frac{2\times 0.92\times 0.76}{0.92+0.76}=0.83 \end{aligned} PrecisionRecallF1=57+557=0.92=57+1857=0.76=0.92+0.762×0.92×0.76=0.83

对于上面的计算过程,也可以通过​​sklearn​​中的包来完成。

from sklearn.metrics import classification_report,confusion_matrix
y = [0]*25 + [1]*75
y_pre =[0]*20+[1]*5+[0]*18+[1]*57
print(confusion_matrix(y,y_pre))
print(classification_report(y,y_pre,target_names=['cat','dog']))


>>
[[20 5]
[18 57]]
precision recall f1-score support

cat 0.53 0.80 0.63 25
dog 0.92 0.76 0.83 75

此时,通过这三个指标,我们再来对比下面的极端情况:

NLP中文分词的评估指标_混淆矩阵_02

对于猫来说:精确率、召回率、F1分别为:0,0,0

对于狗来说:精确率、召回率、F1分别为:0.75,1,0.86

只用准确率:准确率为0.75

还有一个问题就是,既然有了精确率和召回率那还搞了F1干啥?这当然也是为了避免一些极端情况。还是以上面的数据为例,试想一下假如某个分类器说对于猫的识别,它的召回率能做到1,那么这算是厉害还是不厉害呢?可能厉害也可能不厉害,厉害就是当猫狗所有类别都分类正确的情况下,但这是及其困难;还有一种作弊的方式就是将所有的样本都预测成猫,那么这样将会得到如下混淆矩阵:

NLP中文分词的评估指标_分词器_03

此时对于猫来说,其精确率、召回率、F1分别为:0.25,1,0.4

总结就是,通过引入精确率,召回率能够明显的解决只用准确率的不足之处,同时加入F-score能够解决召回率和精确率的不足之处。

3.NLP中的精确率、召回率和F-score

前面说到的分类中的评估标准,但是在分词中标准答案和分词结果数不一定相等,因此要做一个思维转换。对于长为 n n n的字符串,分词结果是一系列单词。设每个单词按照其在文中的起止位置可记作区间 [ i , j ] [i,j] [i,j],其中 i ≤ i ≤ j ≤ n i\leq i \leq j\leq n iijn。那么标准答案所有区间构成集合 A A A作为正类,其它情况作为负类。同时,即分词结果所有单词构成的区间集合为 B B B。那么:

T P ∪ F N = A T P ∪ F P = B A ∩ B = T P \begin{aligned} TP\cup FN &= A\\ TP\cup FP&=B\\ A\cap B&=TP \end{aligned} TPFNTPFPAB=A=B=TP

因此相应的计算公式如下:

P r e c i s i o n = ∣ A ∩ B ∣ ∣ B ∣ ,    R e c a l l = ∣ A ∩ B ∣ ∣ A ∣ Precision=\frac{|A\cap B|}{|B|},\;Recall=\frac{|A\cap B|}{|A|} Precision=BAB,Recall=AAB

这样说可能有点晦涩,如下表所示:

分词结果

分词区间

标准答案

[‘武汉市’,‘长江大桥’]

[1,2,3],[4,5,6,7]

A

分词结果1

[‘武汉’,‘市长’,‘江大桥’]

[1,2],[3,4],[5,6,7]

B

重合部分

A∩B

分词结果2

[‘武汉市’,‘长江大桥’]

[1,2,3],[4,5,6,7]

B’

重合部分

[‘武汉市’,‘长江大桥’]

[1,2,3],[4,5,6,7]

A∩B’

可以发现,重合部分就是正确部分;因此,对于分词结果1来说,精确率和召回率均为0,因为没有重合部分。而对于分词结果2来说都为1。下面再来看个例子:

分词结果

分词区间

标准答案

[‘结婚’,‘的’,‘和’,‘尚未’,‘结婚’ ,‘的’]

[1,2],[3,3],[4,4],[5,6],[7,8],[9,9]

A

分词结果

[‘结婚’,‘的’,‘和尚’,‘未结婚’ ,‘的’]

[1,2],[3,3],[4,5],[6,7,8],[9,9]

B

重合部分

[‘结婚’,‘的’,‘的’]

[1,2],[3,3],[9,9]

A∩B

此时的精确率为: 3 / 5 = 0.6 3/5=0.6 3/5=0.6,召回率为: 3 / 6 = 0.5 3/6=0.5 3/6=0.5

4. OOV Recall 与 IV Recall

OOV指的是“未登录词”(Out Of Vocabulary)的简称,也就是新词,已知词典中不存在的词。出现OOV的原因一方面可能确实是因为产生了有意义的新词而词典并没有收录;另一方面可能就是因为分词器产生的错误无意义的分词结果,这当然也不会出现在字典中。IV指的是“登陆词”(In Vocabulary),也就是已经存在字典中的词。而OOV Recall和IV Recall 分别指的就是OOV的召回率和IV的召回率。为了说明这两个召回率的具体含义,请先耐心看下面的详细例子:

标准分词 A:[‘结婚’,’ 的’,’ 和’,’ 尚未’,’ 结婚 ‘,‘的’,’ 都’,’ 应该’,’ 好好’,’ 考虑’,’ 一下’,’ 人生’,’ 大事’]

标准区间 A:[1,2],[3,3],[4,4],[5,6],[7,8],[9,9],[10,10],[11,12],[13,14],[15,16],[17,18],[19,20],[21,22]

分词结果 B:[‘结婚’,’ 的’,‘和尚’,‘未结婚 ‘,‘的 ‘,‘都’,’ 应该’,’ 好好考虑’,’ 一下’,’ 人生大事’]

分词区间 B:[1,2],[3,3],[4,5],[6,7,8],[9,9],[10,10],[11,12],[13,14,15,16],[17,18],[19,20,21,22]

重复词语 A∩B:[‘结婚’,’ 的’,’ 的’,’ 都’,’ 应该’,’ 一下’]

重复区间 A∩B:[1,2], [3,3], [9,9],[10,10],[11,12],[17,18]

词典:[‘结婚’, ‘尚未’, ‘的’, ‘和’, ‘青年’, ‘都’, ‘应该’, ‘好好考虑’, ‘自己’, ‘人生’, ‘大事’]

R e c a l l = 6 10 = 0.6 P r e c i s i o n = 6 13 = 0.4615 F 1 = 2 × 0.6 × 0.4615 0.6 + 0.4615 = 0.5217 O O V R e c a l l = #重复词区间未在词典中出现的词 #标准分词中未在词典中出现的词 = 1 3 = 0.3333 I V R e c a l l = #重复词区间在词典中出现的词 #标准分词中在词典中出现的词 = 5 10 = 0.5 \begin{aligned} Recall & = \frac{6}{10} = 0.6\\[1ex]Precision & = \frac{6}{13}=0.4615\\[1ex] F1 & = \frac{2\times 0.6\times 0.4615}{0.6+0.4615} = 0.5217\\[2ex] OOV Recall & = \frac{\text{\#重复词区间未在词典中出现的词}}{\text{\#标准分词中未在词典中出现的词}}=\frac{1}{3}=0.3333\\[2ex] IV Recall &=\frac{\text{\#重复词区间在词典中出现的词}}{\text{\#标准分词中在词典中出现的词}}=\frac{5}{10}=0.5 \end{aligned} RecallPrecisionF1OOVRecallIVRecall=106=0.6=136=0.4615=0.6+0.46152×0.6×0.4615=0.5217=#标准分词中未在词典中出现的词#重复词区间未在词典中出现的词=31=0.3333=#标准分词中在词典中出现的词#重复词区间在词典中出现的词=105=0.5

前面三项指标同第3节中的一样,不在赘述。从上面的计算过程可以看到,OOV召回率等于重复词区间未在词典中出现的词除以标准分词中未在词典中出现的词。需要注意的是重复词区间未在词典中出现的词就意味着未在字典中出现的新词是有意义的,只是字典没有收录而已;同理标准分词中未在词典中出现的词就更是如此。同时也可以将两者分别称为重复词区间有意义的新词所有有意义的新词。有意义的新词越多也就表示你用来分词的字典收录越不全(可能也会因为词语的颗粒度大小造成),而OOV recall越低也就意味着词典分词器对有意义新词的发现或者说查找能力越低。

O O V R e c a l l = #重复词区间未在词典中出现的词 #标准分词中未在词典中出现的词 = #重复词区间有意义的新词 #所有有意义的新词 \begin{aligned}OOV Recall & = \frac{\text{\#重复词区间未在词典中出现的词}}{\text{\#标准分词中未在词典中出现的词}}=\frac{\text{\#重复词区间有意义的新词}}{\text{\#所有有意义的新词}}\end{aligned} OOVRecall=#标准分词中未在词典中出现的词#重复词区间未在词典中出现的词=#所有有意义的新词#重复词区间有意义的新词

同理,从IV 召回率的计算公式可以发现重复词区间在词典中出现的词指的就是分词得到的正确部分(即正样本);标准分词中在词典中出现的词指的就是所有正样本。因此,IV 召回率就可以来衡量词典中的词被正确找回的概率。如果IV召回率低,就说明字典分词器连词典中的词汇都无法百分之百的发现或者找回,说明其消歧能力不好。例如“商品,和服,服务”三个词都在词典中,词典分词依然可能分布对句子”商品和服务“。

import re

def to_region(segmentation: str) -> list:
"""
将分词结果转换为区间
:param segmentation: 商品 和 服务
:return: [(0, 2), (2, 3), (3, 5)]
"""
region = []
start = 0
for word in re.compile("\\s+").split(segmentation.strip()):
end = start + len(word)
region.append((start, end))
start = end
return region


def prf(gold: str, pred: str, dic) -> tuple:
"""
计算P、R、F1
:param gold: 标准答案文件,比如“商品 和 服务”
:param pred: 分词结果文件,比如“商品 和服 务”
:param dic: 词典
:return: (P, R, F1, OOV_R, IV_R)
"""
A_size, B_size, A_cap_B_size, OOV, IV, OOV_R, IV_R = 0, 0, 0, 0, 0, 0, 0
A, B = set(to_region(gold)), set(to_region(pred))
A_size += len(A)
B_size += len(B)
A_cap_B_size += len(A & B)
text = re.sub("\\s+", "", gold)

for (start, end) in A:
word = text[start: end]
if word in dic:
IV += 1
else:
OOV += 1

for (start, end) in A & B:
word = text[start: end]
if word in dic:
IV_R += 1
else:
OOV_R += 1
p, r = A_cap_B_size / B_size * 100, A_cap_B_size / A_size * 100
return p, r, 2 * p * r / (p + r), OOV_R / OOV * 100, IV_R / IV * 100


if __name__ == '__main__':
dic = ['结婚', '尚未', '的', '和', '青年', '都', '应该', '好好考虑', '自己', '人生', '大事']
gold = '结婚 的 和 尚未 结婚 的 都 应该 好好 考虑 一下 人生 大事'
pred = '结婚 的 和尚 未结婚 的 都 应该 好好考虑 一下 人生大事'
print("Precision:%.2f Recall:%.2f F1:%.2f OOV-R:%.2f IV-R:%.2f" % prf(gold, pred, dic))

5. 总结

此处一共介绍了精确率、召回率、F-score、OOV召回率、IV召回率,其中前面三种指标可以用来衡量任意一种分词器分词结果的好坏;而后两种指标则是用来衡量基于词典分词模型好坏的一个评估指标。同时,一定需要明白的是:精确率计算的是预测对的正样本数占整个预测为正样本数的比重,而召回率计算的是预测对的正样本占整个真实正样本的比重,而F-score则是对两者的一个调和平均。

参考:

  • 《自然语言处理入门》,何晗

NLP中文分词的评估指标_分词器_04