美文网首页
利用crf++进行实体识别

利用crf++进行实体识别

作者: 烛之文 | 来源:发表于2020-08-22 15:14 被阅读0次

    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 为特征模板文件,主要表示上下文的组合特征,详细见下图;
     后面两个分别对应训练的数据集和保存的模型;

    template文件
    若有算法细节不太明白的,建议网上搜搜CRF算法原理的文章看看。

    4. 测试与评估

    当训练好模型后,可以按下面命令进行测试:
    crf_test -m model_dir/crf_model ./data/crf_test.txt >> result.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算法或其他算法原理的分享。






    相关文章

      网友评论

          本文标题:利用crf++进行实体识别

          本文链接:https://www.haomeiwen.com/subject/dagmjktx.html