赞
踩
参考《python自然语言处理实战》
命名实体识别(Named Entities Recognition,NER)是自然语言处理中的一个基础任务。主要分为实体类、事件类、数字类三大类和人名、地名、机构名、时间、日期、货币、百分比等小类。NER比较侧重召回率,即需要最大限度的找出给定句子中所有的命名实体。
和分词一样,NER主要方法也分为三类,基于规则的、基于统计的、规则和统计相结合的方法。序列标注方式是目前NER中的主流方式,一般基于隐马尔可夫(HMM)和条件随机场(CRF)。相较于HMM,CRF不只考虑前一个字的特征的影响,也将句子中其它位置字的特征的影响考虑在内,更符合实际情况。
以地名处理为例,NER处理方式可以参考分词的处理方式,即对每个字做状态标注,如果是地名,则对词首、词中、词尾分别标定为B、M、E,单独成词标定为S,非地名词统一标定为O,通过对输入逐字做状态标注,从而实现地名识别。因此其任务可以表述为,对于给定的句子x,输出一个状态序列y,例如输入x为"我来到北京",则输出"OOOBE"、或“OOOBB”等状态序列。
可以看出可能的序列有很多,那么如何选择一个最符合的序列呢?可以借助一些模型比如HMM或CRF来实现。HMM在分词任务中已经学习了,所以这里使用CRF来做地名识别。
对于序列"OOOBE"和“OOOBB”,前者肯定更好,因为两个字都是词首的情况显然不符合实际。假如可以制定一些规则来评判可能的y的好坏,那么形如"BB"、"EE"这些序列肯定是要减分的。
CRF中有特征函数的概念,一个特征就对应一条规则.对于一个给定的序列,用每一个规则对他校验并给分,再把所有规则下的得分相加作为这个序列的总分(由于不同规则的重要成程度不同,因此每条规则都要有自己的权重值,最后求和要进行加权)。对于所有可能的序列,取总分最高者作为输出序列。
上面提到规则的权重值,训练的任务就是拿到这些权重值。
CRF++对CRF模型做了封装,因此可以使用CRF++来做地名的识别。CRF++的使用分为几个步骤:
CRF++要求训练集每行一个token(即每行一个字),句子间用空行隔开,每行的最后一列是标签,前面的列是这一行的特征。然后设计特征模板。特征模板中每一条就是对应于CRF中的特征函数,通过定义这些特征函数来使模型知晓上下文的信息,从而提高模型的准确率。准备好训练集和特征模板后便可以使用cfr_learn命令训练并生成模型,再使用crf_test命令将模型应用于测试集进行测试分析。
书中地名识别使用的是98年人民日报分词数据集,特征只取了单个字。训练完对测试集应用这个模型,得到该模型的查准率,召回率和调和平均值如下(data/testresult.txt是测试集,crf++将模型应用于测试集后,将预测结果放在了最后一列,如下):
迈 O O
向 O O
充 O O
满 O O
希 O O
望 O O
的 O O
新 O O
世 O O
def calculatePRAndF1(): with open("data/testresult.txt", mode="r", encoding="utf8") as f: allLocPre = 0 # 所有被模型识别为地名的数量 locPreCorr = 0 # 被模型识别为地名且正确的数量 allLocReal = 0 # 所有地名的数量 for line in f: line = line.strip() if line == "": continue word, realFlag, preFlag = line.split() # 字, 实际状态标注, 预测状态标注 if preFlag != "O": allLocPre += 1 if realFlag != "O": allLocReal += 1 if realFlag == preFlag: if not realFlag == "O": locPreCorr += 1 precision = locPreCorr * 1.0 / allLocPre # 查准率 recall = locPreCorr * 1.0 / allLocReal # 召回率 f1 = 2 * precision * recall / (precision + recall) # 调和平均 return precision, recall, f1
结果如下:
precision -> 0.9080725791520089
recall -> 0.8419742489270386
f1 -> 0.8737751647960093
这是只把字作为特征的情况下得到的结果,如果再追加一个词性为特征,效果将会提高很多,如下(data/testresultwithflag.txt是增加了词性为特征后得到的模型的测试结果):
def calculatePRAndF1(): with open("data/testresultwithflag.txt", mode="r", encoding="utf8") as f: allLocPre = 0 # 所有被模型识别为地名的数量 locPreCorr = 0 # 被模型识别为地名且正确的数量 allLocReal = 0 # 所有地名的数量 for line in f: line = line.strip() if line == "": continue word, _, realFlag, preFlag = line.split() # 字, 实际状态标注, 预测状态标注 if preFlag != "O": allLocPre += 1 if realFlag != "O": allLocReal += 1 if realFlag == preFlag: if not realFlag == "O": locPreCorr += 1 precision = locPreCorr * 1.0 / allLocPre # 查准率 recall = locPreCorr * 1.0 / allLocReal # 召回率 f1 = 2 * precision * recall / (precision + recall) # 调和平均 return precision, recall, f1
结果如下:
precision -> 0.9910729613733905
recall -> 0.9910729613733905
f1 -> 0.9910729613733905
各个指标都有很大的提升。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。