实现spaCy实体标注模型

命名实体识别是指对现实世界中某个对象的名称的识别。与词性标注一样,是自然语言处理的技术基础之一。它的作用主要是通过模型识别出文本中需要的实体,也可以推导出实体之间的关系(实体消歧)。
本文介绍的是运用Python从头训练一个spaCy模型来识别中标公告中中标公司的名字,现通过爬虫爬取了大约200篇中标公告(爬取过程省略),利用人工对其中的150篇训练集公告进行标注中标公司,使用spaCy训练一个实体抽取模型并进行本地保存,再调取训练好的模型对剩余的50篇公告进行测试,检验该模型对中标公司提取的准确率。

1、获取数据和数据清洗

首先,需要对爬取下来的中标公告文件数据进行清洗处理,分别对其进行去重和删除网络格式(比如&nbsp),清洗前后对比如下:
数据清洗对比

2、标注实体

对于清洗后的数据集,需要把标注后的结果以下例格式进行储存(即文本+实体标注的索引+实体标注类别标签):

TRAIN_DATA = [
    ('Who is Shaka Khan?', {
        'entities': [(7, 17, 'PERSON')]  
        ###实体标注的索引从0开始17是最后一字符的索引+1
    }),
    ('I like London and Berlin.', {
        'entities': [(7, 13, 'LOC'), (18, 24, 'LOC')]
    })
]

也就是说,需要找出需要被标注实体的开始索引和结束索引。由于有204篇公告,每篇公告都都需要人工标注,鉴于数据量和寻找索引工作量都很大,所以通过编写Python程序且小组分工后每位成员人工标注中标公司名称,并把结果储存成上述格式。标注代码如下:

import pandas as pd
import re

def find_company_name(name,text):
    if re.search(name,text):
        tup = list(re.search(name,text).span())
        tup.append("LOC")
        tup=tuple(tup)
        return (text,{'entities':tup})
    return False
from IPython.display import clear_output as clear

textall= []
data5 = pd.read_csv("home_work_clear.txt",encoding="utf-8",sep="\n",header=None)
start=int(input("请输入第几行开始"))
end=int(input("请输入第几行结束"))

for line in data5[0][start:end]:
    clear()#清除输出
    print(line)
    while True:
        panduan="not break"
        company_name = input("请输入公司名字")
        if find_company_name(company_name,line):
            print(find_company_name(company_name,line))
            textall.append(find_company_name(company_name,line))
            break
        elif company_name == "break":
            panduan='break'
            break
        else:
            print("输入公司名字可能有误,重新输入")
        print
    if panduan=="break":
        break

f = open("result%d-%d.txt" % (start,end),"w+",encoding="gbk")
f.write(str(textall))
f.close()

(1)首先输入需要标注的开始位置和结束位置:

输入标注范围

(2)然后输入每份公告的中标公司名称:

输入中标公司名称

(3)最后,把标注后的结果保存到txt中:

标注结果
上图表示文本中索引121-133为中标公司。

3、划分训练集和测试集

经标注后,一共有204个数据集,其中设定训练集150篇公告,测试集为54篇公告:
数据集总数

4、spaCy模型训练

对于处理好的训练集,输入到spaCy模型中进行训练,并对训练后的模型进行保存,代码如下:

def main(model=None, output_dir = None, n_iter=100):   ##参数意义,model:是否存在现有的模型,output_dir:模型存储位置,n_iter迭代次数
    """Load the model, set up the pipeline and train the entity recognizer."""
    if model is not None:
        nlp = spacy.load(model)  # load existing spaCy model  ###这里的作用是对现有的模型进行优化  *非常重要
        print("Loaded model '%s'" % model)
    else:
        nlp = spacy.blank('zh')  # create blank Language class
        print("Created blank 'zh' model")

        if 'ner' not in nlp.pipe_names:
        ner = nlp.create_pipe('ner')
        nlp.add_pipe('ner', last=True)
    # otherwise, get it so we can add labels
    else:
        ner = nlp.get_pipe('ner')

    # add labels
    for _, annotations in TRAIN_DATA:      ##添加train data的标签
        for ent in annotations.get('entities'):
            ner.add_label(ent[2])

    other_pipes = [pipe for pipe in nlp.pipe_names if pipe != 'ner']
    with nlp.disable_pipes(*other_pipes):  # 仅训练我们标注的标签,假如没有则会对所有的标签训练,
                                           #建议不要对下载的spacy的模型进行训练可能导致下载的语言模型出错,训练一个空白语言模型就好
        optimizer = nlp.begin_training()   ##模型初始化
        for itn in range(n_iter):
            random.shuffle(TRAIN_DATA)     ##训练数据每次迭代打乱顺序
            losses = {}                    ##定义损失函数
            for text, annotations in TRAIN_DATA:
                example = Example.from_dict(nlp.make_doc(text), annotations)    ##对数据进行整理成新模型需要的数据
                print("example:",example)
                nlp.update(
                    [example],  # batch of annotations
                    drop=0.5,  # dropout - make it harder to memorise data
                    sgd=optimizer,  # 更新权重
                    losses=losses)
            print(losses)

    # 保存模型
    if output_dir is not None:
        output_dir = Path(output_dir)
        if not output_dir.exists():
            output_dir.mkdir()
        nlp.to_disk(output_dir)
        print("Saved model to", output_dir)

对上述训练过程进行计时,由于代码运行时间过久,测试得当训练5个数据集需要花费差不多1分钟的时间:
训练5个数据集花费时间
通过检查发现代码中的迭代次数为100,为了提高速度把100改为了50,训练150个训练集,花费时间为(约47分钟):
迭代50次的花费时间

并把训练好的模型保存到本地,方便后续测试时可以直接加载已训练好的模型得出预测结果。

5、测试集测试模型

训练完spaCy模型后,导入测试数据进行测试,代码如下:

import spacy

def load_model_test(path,text):
    nlp = spacy.load(path)
    print("Loading from", path)
    doc = nlp(text)
    for i in doc.ents:
        print(i.text,i.label_)

训练结果如下:
测试集预测结果
根据上图测试结果来看,总体预测结果良好,能准确找出中标公司名称。但是,也存在少许预测失败的数据,说明该模型还不是非常精确,后续可以在迭代次数和训练数据量两方面进行改进。