利用crf++进行实体识别
1. 前言
crf++是条件随机场(CRF)算法实现的开源工具,因底层是c++语言来实现,算法复现的综合性能很高,在工业界应用很普遍。crf++工具的安装教程可见:https://www.jianshu.com/p/ef8c0fdd2bfb。
条件随机场(算法)是传统机器学习领域做序列标注的经典算法。而如今算法界都被深度学习占领,但我始终觉得传统的算法仍有大的发挥空间,它们是底层的建筑。今天就介绍利用如何利用crf++来进行实体识别,使用的数据集是CLUEbenchmark。
2. 数据集介绍
数据集涵盖10个标签类别,分别为: 地址(address),书名(book),公司(company),游戏(game),政府(government),电影(movie),姓名(name),组织机构(organization),职位(position),景点(scene)
数据集包括一个训练集和一个验证集:
训练集:10748
验证集集:1343
3. 前期准备工作
把下载的CLUEbenchmark数据集分别转换成crf++训练格式文件,标注方案我这里采用BISO。处理代码如下:
import json
import codecs
import re
def generate_train_data(input_file,output_file):
"""
将原始数据集转成训练格式,采用BISO标注方案;
:return:
"""
output=codecs.open(output_file, 'w', encoding='utf-8')
with codecs.open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = json.loads(line.strip())
text = line['text']
label_entities = line.get('label', None)
words = list(text)
labels = ['O'] * len(words)
if label_entities is not None:
for key, value in label_entities.items():
for sub_name, sub_index in value.items():
for start_index, end_index in sub_index:
assert ''.join(words[start_index:end_index+1]) == sub_name
if start_index == end_index :
labels[start_index] = 'S_' + key
else:
labels[start_index] = 'B_' + key
labels[start_index + 1:end_index+1] = ['I_' + key] * (len(sub_name) - 1)
for i ,w in enumerate(words):
output.write(w+'\t'+labels[i]+'\n')
output.write('\n')
output.close()
if __name__=="__main__":
input_file='./data/train.json'
output_file='./data/crf_train.txt'
generate_train_data(input_file,output_file)
转化后格式如下:分两列,第一列是字符,第二例是对应的标签,中间用\t分割。
训练数据集格式
3. 训练
在安装好了crf++工具,准备好了训练数据,就可以按下面命令进行训练:
crf_learn -f 1.0 -c 3.0 template ./data/crf_train.txt ./model_dir/crf_model
其中:
参数 f 为特征出现的次数,取1意味出现1次的特征也参与训练;
参数 c 为拟合参数,值越大表示拟合程度越高;
template 为特征模板文件,主要表示上下文的组合特征,详细见下图;
后面两个分别对应训练的数据集和保存的模型;
若有算法细节不太明白的,建议网上搜搜CRF算法原理的文章看看。
4. 测试与评估
当训练好模型后,可以按下面命令进行测试:
crf_test -m model_dir/crf_model ./data/crf_test.txt >> result.txt
将预测的结果写入result.txt 文中,最后一列为预测列,详细格式如下:
接着我们对预测的结果按各个类别进行评价,评价方法可以使用conlleval.pl文件进行测评,这里我是从githup找了python版的文件,修改下进行测评的。
python conlleval.py result.txt
运行结果如下:
整体效果:
accuracy | precision | recall | F1 |
---|---|---|---|
91.10 | 77.48 | 66.76 | 71.73 |
各个类别效果:
label | precision | recall | F1 |
---|---|---|---|
address | 61.48 | 46.65 | 53.05 |
book | 80.49 | 64.29 | 71.48 |
company | 78.84 | 71.96 | 75.24 |
game | 81.02 | 81.02 | 81.02 |
government | 76.40 | 77.33 | 76.86 |
movie | 72.03 | 68.21 | 70.07 |
name | 81.22 | 68.82 | 74.51 |
organization | 81.93 | 71.66 | 76.45 |
position | 81.94 | 68.13 | 74.40 |
scene | 71.43 | 45.45 | 55.56 |
其中整体F1值达到71.73,各个label中game值最高,然后adress识别效果最差。我们对比下网上公开的训练结果:
对比可以看出来,我们利用crf++训练的效果超过Bi_LSTM+CRF的效果,但是跟其他预训练模型还是有一定差距的。不过我们的模型简单,没那么多参数,工业部署也比较简单。如果在crf的基础上,利用词库或者规则构造一些特征再去训练,这个差距应该会更小。
5. 预测
我们利用crf++中的python包CRFPP调用模型进行实体识别预测:
#@author zzw
import CRFPP
import re
def ner_extract(content):
tagger = CRFPP.Tagger("-m " + './model_dir/crf_model')
tagger.clear()
for word in content:
tagger.add(word)
tagger.parse()
size = tagger.size()
xsize = tagger.xsize()
res=[]
words=""
label = ""
for i in range(0, size):
for j in range(0, xsize):
char = tagger.x(i, j)
tag = tagger.y2(i)
if tag[0] == 'B':
label = re.sub('B_', '', tag)
if words:
res.append((words,label))
words=char
label=""
else:
words+=char
elif tag[0] == 'I':
words+=char
elif tag[0]=='S':
label = re.sub('S_', '', tag)
res.append((char,label))
label=""
else:
continue
if words:
res.append((words,label))
return res
def test():
lines=['根据北京市消防局的说法,此次火灾主要原因是责任单位违规燃放礼花弹,燃放期间民警多次劝阻未果。',
'彭小军认为,国内银行现在走的是台湾的发卡模式,先通过跑马圈地再在圈的地里面选择客户,',
'备受瞩目的动作及冒险类大作《迷失》在其英文版上市之初就受到了全球玩家的大力追捧。']
for line in lines:
res = ner_extract(line)
print('-----------------text---------------')
print(line)
print('-----------------predict-------------')
print(res)
if __name__=="__main__":
test()
预测效果
6. 结语
以上就是利用crf++进行实体识别的整体流程,一些工具细节或算法细节,有兴趣可以深入去了解。后续我会跟进CRF算法或其他算法原理的分享。