0%

spacy+doccano 中文NLP流程

spacy+doccano 中文NLP流程

使用工具介绍

spacy是一个工业级python自然语言处理包,支持自然语言文本分析、命名实体识别、词性标注、依存句法分析等功能。spacy2.0之后通过引入结巴分词,添加了对中文NLP的支持,不过在使用spacy进行中文自然语言处理时有许多需要注意的地方。

doccano是一个在GitHub上开源的可视化实体标注工具,支持自定义实体标签,文本实体标注,导出标注数据为jsonl(JSON Lines文件,结构化数据,用于管道文件传输)。在中文NPL处理中doccano主要作用是提供中文NER训练参数。


doccano使用过程

在doccano上对语句进行命名实体标签标注

  • 自定义实体标签
  • 选择实体设定实体标签

将doccano上数据导出为json数据

  • export data为json格式
1
2
{"id": 1, "text": "The Hitchhiker's Guide to the Galaxy (sometimes referred to as HG2G, HHGTTGor H2G2) is a comedy science fiction series created by Douglas Adams. Originally a radio comedy broadcast on BBC Radio 4 in 1978, it was later adapted to other formats, including stage shows, novels, comic books, a 1981 TV series, a 1984 video game, and 2005 feature film.", "annotations": [], "meta": {}, "annotation_approver": null}

解析json数据为TRAIN_DATA 数据格式

  • 解析json格式数据
  • 抽取数据中的labels

在现有模型中训练TRAIN_DATA,添加实体类别,更新模型

  • 在模型中添加实体label
  • 循环训练model

对模型测试,查看模型训练效果,对于当前模型未能准确识别的实体,在doccano上更新标注

  • 与训练语句相同句法语句使用模型划分出实体label和实体名称
  • 对照找出当前模型划分错误之处
  • 在doccano调整训练语句

spacy的使用及安装过程

spacy在centos7环境下的安装

在使用pip安装spacy时,因为面对的是中文环境,spacy2.0以后通过引入jieba分词添加了对中文的支持,但是还是有较多的坑。

如果要使用中文模型,目前的安装环境是python3.6+spacy2.0.1,如果安装了最高版本的spacy2.1,在使用中文模型时会出现python re正则包出错的情况。

中文模型的下载地址:https://github.com/howl-anderson/Chinese_models_for_SpaCy/releases

  • 在使用中文模型时需要控制 msgpack-numpy==0.4.4.2 不然会出现 (TypeError:encoding)编码的错误

安装好spacy和下载好中文模型之后,就是在spacy中配置中文环境,如何配置中文环境参考https://www.jianshu.com/p/0dab70cb540e

配置好中文环境后,由于spacy使用的jieba分词模式是最普通的模式,遇到行业内专业词汇很难准确分词,这就需要我们使用jieba的自定义词典分词功能。

  • 使用自定义分词字典

先自定义一个TXT文档,用于标注自定义词块、词性、词频

1
2
3
4
5
6
7
8
9
拿铁 3 i
摩卡 3 i
全自动咖啡机 3 i
半自动咖啡机 3 i
美式(传统滴滤式)咖啡机 100 i
意式咖啡机 19 i
胶囊咖啡机 3 i
月房租 4 i
卡布奇洛 4 i

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import jieba
import jieba.posseg as pseg
import jieba.analyse as anls

doc = """我在江北新区研创园喝咖啡,喝的是卡布奇洛"""
doc01 = """有一台全自动咖啡机出现故障了"""
doc02 = """江北新区研创园的月房租是10000人民币"""
jieba.load_userdict("demo.txt")

# 全模式
seg_list = jieba.cut(doc)
seg_list01 = jieba.cut(doc01)
seg_list02 = jieba.cut(doc02)
print(type(seg_list))
print("/".join(seg_list))
print("/".join(seg_list01))
print("/".join(seg_list02))

效果如下:

1
2
3
我/在/江北/新区/研创园/喝咖啡/,/喝/的/是/卡布奇洛
有/一台/全自动咖啡机/出现/故障/了
江北/新区/研创园/的/月房租/是/10000/人民币
  • 因为当前目标是在文档中找出指定实体类型的实体,如找出实体类型为”法定代表人”的所有实体,在spacy中现有的实体类型并不能满足需求,所以这里需要使用命名实体标注和NER(命名实体识别),自定义实体标注的工作由doccano完成,在获取到训练数据后,需要将其训练至spacy的中文模型中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""
训练一个新的命名实体识别
"""
from __future__ import unicode_literals, print_function
# from nlp_app.spacy_demo.data_conversion import conversion
import plac
import random
from pathlib import Path
import spacy
from spacy.util import minibatch, compounding
import json

# 解析json数据
def conversion(path):
doc = open(path, "r")
train_data = []
try:
# all_text = doc.read()
lines = doc.readlines()
if len(lines) != 1:
for all_text in lines:
print(type(json.loads(all_text)))
all_text = json.loads(all_text)
text_content = all_text["text"]
text_entities = all_text["labels"]
entity_list = []
for entity in text_entities:
entity = tuple(entity)
entity_list.append(entity)
data = (text_content, {"entities": entity_list})
train_data.append(data)
finally:
doc.close()
return train_data


TRAIN_DATA = conversion("/home/hadoop/Documents/train_data.json1")
# 获取TRAIN_DATA 中的label
label_list = []
for data in TRAIN_DATA:
if len(data) == 2:
entities = data[1]["entities"]
for entity in entities:
label_list.append(entity[2])


@plac.annotations(
model=("Model name. Defaults to blank 'en' model.", "option", "m", str),
output_dir=("Optional output directory", "option", "o", Path),
n_iter=("Number of training iterations", "option", "n", int),
)
def main(model="zh_core_2.0.3", output_dir="/usr/local/lib/python3.6/dist-packages/spacy/data/zh_core_2.0.3",
n_iter=100):
random.seed(0)
if model is not None:
nlp = spacy.load(model)
print("Loaded model '%s'" % model)
else:
nlp = spacy.blank("en") # 选择语言创建模型
print("Created blank 'en' model")
if "ner" not in nlp.pipe_names:
ner = nlp.create_pipe("ner")
nlp.add_pipe(ner)
else:
ner = nlp.get_pipe("ner")
# 命名实体识别添加标签
for label in label_list:
ner.add_label(label)

move_names = list(ner.move_names)
other_pipes = [pipe for pipe in nlp.pipe_names if pipe != "ner"]
with nlp.disable_pipes(*other_pipes): # 训练模型
sizes = compounding(1.0, 4.0, 1.001)
for itn in range(n_iter):
random.shuffle(TRAIN_DATA)
batches = minibatch(TRAIN_DATA, size=sizes)
losses = {} # 训练损失
for batch in batches:
texts, annotations = zip(*batch)
nlp.update(texts, annotations, drop=0.35, losses=losses)
print("Losses", losses)
test_text = TRAIN_DATA[0][0]
# doc = nlp(test_text)
print("Entities in '%s'" % test_text)
# for ent in doc.ents:
# print(ent.label_, ent.text)

# 保存模型至路径
if output_dir is not None:
output_dir = Path(output_dir)
if not output_dir.exists():
output_dir.mkdir()
# nlp.meta["name"] = new_model_name
nlp.to_disk(output_dir)
print("Saved model to", output_dir)

# 测试模型效果
print("Loading from", output_dir)
nlp2 = spacy.load(output_dir)
assert nlp2.get_pipe("ner").move_names == move_names
doc2 = nlp2(test_text)
for ent in doc2.ents:
print(ent.label_, ent.text)


if __name__ == "__main__":
plac.call(main)

通过不断强化训练,将自定义实体与文本间句法关系写入中文模型中。在模型训练中有一个值得重视的问题就是深度学习的灾难性遗忘问题,而这个问题在中文模型中尤为明显,模型训练好之后,可以对与训练数据类似句法模板的文档进行命名实体识别,而且准确率较高,但是一旦在该模型基础上再一次训练数据,那么之前所训练的内容将被遗忘,也就是说所有模型训练都是一次性的,不可重复。