使用HanLP进行分词和实体抽取

使用HanLP进行分词和实体抽取

HanLP Github地址:https://github.com/hankcs/HanLP

HanLP文档地址:https://hanlp.hankcs.com/docs/api/hanlp/pretrained/index.html

多任务模型

首先我们来了解下HanLP有哪些预训练模型,其分为单任务模型和多任务模型,多任务模型就是可以同时执行多个任务,其模型的位置都在hanlp.pretrained.mtl这个包下,根据其文档说明

hanlp.pretrained.mtl.CLOSE_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_BASE_ZH

Electra(Clark et al.2020)在近源中文语料库上训练的联合tok、pos、ner、srl、dep、sdp和con模型的基础版本

hanlp.pretrained.mtl.CLOSE_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_SMALL_ZH

Electra(Clark et al.2020)在近源中文语料库上训练的联合tok、pos、ner、srl、dep、sdp和con模型的迷你版本

hanlp.pretrained.mtl.CLOSE_TOK_POS_NER_SRL_DEP_SDP_CON_ERNIE_GRAM_ZH

ERNIE(Xiao et al.2021)在近源汉语语料库上训练的联合tok、pos、ner、srl、dep、sdp和con模型的基础版本。

hanlp.pretrained.mtl.NPCMJ_UD_KYOTO_TOK_POS_CON_BERT_BASE_CHAR_JA

BERT(Devlin et al.2019)在NPCMJ/UD/Kyoto语料库上训练基本字符编码器,解码器包括tok、pos、ner、dep、con、srl。

hanlp.pretrained.mtl.OPEN_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_BASE_ZH

Electra(Clark et al.2020)在开源中文语料库上训练的联合tok、pos、ner、srl、dep、sdp和con模型的基础版本

hanlp.pretrained.mtl.OPEN_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_SMALL_ZH

Electra(Clark et al.2020)在开源中文语料库上训练的联合tok、pos、ner、srl、dep、sdp和con模型的迷你版本

hanlp.pretrained.mtl.UD_ONTONOTES_TOK_POS_LEM_FEA_NER_SRL_DEP_SDP_CON_XLMR_BASE

XLM-R(Conneau et al.2020)联合tok、pos、lem、fea、ner、srl、dep、sdp和con模型的基础版本,在UD和OntoNotes5语料库上进行训练。

hanlp.pretrained.mtl.UD_ONTONOTES_TOK_POS_LEM_FEA_NER_SRL_DEP_SDP_CON_MT5_SMALL

mT5(Xue et al.2021)联合tok、pos、lem、fea、ner、srl、dep、sdp和con模型的迷你版本,在UD和OntoNotes5语料库上进行训练。

然后根据github上的readme可以了解到这些简写的任务含义以及标注标准。

功能 RESTful 多任务 单任务 模型 标注标准
分词 教程 教程 教程 tok 粗分/细分
词性标注 教程 教程 教程 pos CTBPKU863
命名实体识别 教程 教程 教程 ner PKUMSRAOntoNotes
依存句法分析 教程 教程 教程 dep SDUDPMT
成分句法分析 教程 教程 教程 con Chinese Tree Bank
语义依存分析 教程 教程 教程 sdp CSDP
语义角色标注 教程 教程 教程 srl Chinese Proposition Bank
抽象意义表示 教程 暂无 教程 amr CAMR

另外,通过print(hanlp.pretrained.mtl.ALL)

可以直接打印所有的模型名称,并且附有模型文件下载链接。

模型加载和使用

我们选择上面的一种模型

1
2
3
4
5
6
import hanlp

HanLP = hanlp.load(
hanlp.pretrained.mtl.CLOSE_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_BASE_ZH,
devive=0 # 多个GPU时,可以用该参数指定
)

第一次运行时,会自动下载模型文件,下载地址和存储路径会在控制台显示,存储路径一般在C盘:

C:\Users\username\AppData\Roaming\hanlp……

也可以自己下载文件到指定目录解压,然后从指定目录加载,但是要注意解压的文件夹名称要和压缩文件名称一致,例如

1
2
3
4
5
6
hanlp.load(
save_dir=(
'./data/hanlp/mtl/'
'close_tok_pos_ner_srl_dep_sdp_con_electra_base_20210111_124519'),
device=0
)

下载好之后,可以查看该模型支持哪些任务:

1
2
3
4
tasks = list(HanLP.tasks.keys())
print(tasks)

['con', 'dep', 'ner/msra', 'ner/ontonotes', 'ner/pku', 'pos/863', 'pos/ctb', 'pos/pku', 'sdp', 'srl', 'tok/coarse', 'tok/fine']

tok/fine: tok是分词, coarse为粗分,fine为细分。 。 ‘/‘前面是任务,后面是标注标准

分词和自定义词典

分词测试

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
import hanlp


class HanLPModel:
def __init__(self):
self.HanLP = hanlp.load(
save_dir=(
'./data/hanlp/mtl/'
'close_tok_pos_ner_srl_dep_sdp_con_electra_base_20210111_124519'),
devive=0
)

@staticmethod
def show_all_models():
print(hanlp.pretrained.mtl.ALL)

def show_tasks(self):
tasks = list(self.HanLP.tasks.keys())
print(tasks)

def tokenizer(self, data):
data = data[:512]
result_document = self.HanLP(data, tasks="tok")
return result_document["tok/fine"]

content = """本院定于2022年6月1日 上午09时00分在普洱市中级人民法院第三法庭公开开庭审理原告中国音像著作权集体管理协会与被告普洱帝都娱乐有限公司著作权权属、侵权纠纷一案。"""

print(hanlp_model.tokenizer(content))

['本院', '定于', '2022年', '6月', '1', '日', '上午', '09', '时', '00', '分', '在', '普洱市', '中级', '人民', '法院', '第三', '法庭', '公开', '开庭', '审理', '原告', '中国', '音像', '著作权', '集体', '管理', '协会', '与', '被告', '普洱', '帝都', '娱乐', '有限', '公司', '著作权', '权属', '、', '侵权', '纠纷', '一', '案', '。']

这里设置tasks=”tok”默认是细粒度的分词,如果设置tasks=”tok/coarse”,可以得到粗粒度的分词结果,设置tasks=”tok*”可以得到两种分词结果。

以下是粗粒度的分词结果:

1
['本', '院', '定于', '2022', '年', '6', '月', '1', '日', '上午', '09', '时', '00', '分', '在', '普洱市中级人民法院第三法庭', '公开', '开庭', '审理', '原告', '中国音像著作权集体管理协会', '与', '被告', '普洱帝都娱乐有限公司', '著作权', '权', '属', '、', '侵权', '纠纷', '一', '案', '。']

自定义词典

在应用于特定领域时,一般我们都会有一些领域词,而hanlp这种通用的模型没办法提取出领域词,我们希望可以添加这样一个词表,可以让hanlp在分词时,将这些词作为一个分词结果。

我们可以通过这种方式自定义词典

1
2
3
4
5
6
7
8
9
10
def tokenizer(self, data):
data = data[:512]
tok = self.HanLP['tok/fine']
# 强制模型
tok.dict_force = {'中级人民法院', '开庭审理'}
result_document = self.HanLP(data, tasks="tok")
return result_document["tok/fine"]

# 分词结果
['本院', '定于', '2022年', '6月', '1', '日', '上午', '09', '时', '00', '分', '在', '普洱市', '中级人民法院', '第三', '法庭', '公开', '开庭审理', '原告', '中国', '音像', '著作权', '集体', '管理', '协会', '与', '被告', '普洱', '帝都', '娱乐', '有限', '公司', '著作权', '权属', '、', '侵权', '纠纷', '一', '案', '。']

强制模式优先输出正向最长匹配到的自定义词条,与大众的朴素认知不同,词典优先级最高未必是好事,极有可能匹配到不该分出来的自定义词语,导致歧义。

另外还有一种合并模式,合并模型优先级低于统计模型,即dict_combine会在统计模型的分词结果上执行最长匹配并合并匹配到的词条。一般情况下,推荐使用该模式,使用方式如下

1
2
3
4
tok.dict_combine = {'市中级人民法院', '开庭审理'}

# 分词结果
['本院', '定于', '2022年', '6月', '1', '日', '上午', '09', '时', '00', '分', '在', '普洱市', '中级', '人民', '法院', '第三', '法庭', '公开', '开庭审理', '原告', '中国', '音像', '著作权', '集体', '管理', '协会', '与', '被告', '普洱', '帝都', '娱乐', '有限', '公司', '著作权', '权属', '、', '侵权', '纠纷', '一', '案', '。']

合并模型添加的词并不一定总能分词成功,因为还是以统计为主,比如这里市中级人民法院就没有分词成功。

另外如果你的自定义词典中的词含有空格、制表符等,可以通过tuple的形式添加

tok.dict_combine = {('iPad', 'Pro')}

如果想要获取分词在原文本的位置信息,可以这样配置

tok.config.output_spans = True

返回格式为三元组(单词,单词的起始下标,单词的终止下标),下标以字符级别计量。

实体抽取和自定义实体词典

接下来我们用它来对一段文本进行实体抽取(实体抽取任务中包含分词)

实体抽取测试

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
class HanLPModel:

def extract_ner(self, data):
data = data[:512]
results_document = self.HanLP(data, tasks="ner")
tok_fine = results_document["tok/fine"]
ner_msra = results_document["ner/msra"]
return tok_fine, ner_msra


hanlp_model = HanLPModel()


content = """
国家卫生健康委新闻发言人、宣传司副司长米锋在会上表示,我国现有本土确诊病例和无症状感染者连续27天下降,但又有新的本土聚集性疫情发生,疫情防控形势依然严峻复杂。

“近期,全国疫情整体呈现稳定下降态势。”雷正龙在发布会上介绍,近一周以来,全国每天新增本土感染者已经降至1200例以下,波及范围进一步缩小。北京聚集性疫情和零星散发病例交织,局部地区和重点人群仍有感染传播风险。

当前,上海疫情继续整体向好,新增报告感染人数持续下降,已连续8天每天新增低于1000例,但是防反弹压力仍然较大,个别点位和社区风险仍有波动,疫情防控成果仍需进一步巩固。

此外,四川广安邻水疫情处于波动下降期,疫情传播风险较前期有所降低。天津、吉林近期有聚集性疫情发生,需加快检测和风险点位排查。河南、安徽、江西、辽宁等地疫情已得到有效遏制,疫情形势趋于平稳。

据雷正龙介绍,截至2022年5月22日,31个省(自治区、直辖市)和新疆生产建设兵团累计报告接种新冠病毒疫苗337109.6万剂次。"""
token_res, ner_res = hanlp_model.extract_ner(content)
print(token_res)
print(ner_res)

# 分词结果
['国家', '卫生', '健康委', '新闻', '发言人', '、', '宣传司', '副', '司长', '米锋', '在', '会上', '表示', ',', '我国', '现有', '本土', '确诊', '病例', '和', '无症状', '感染者', '连续', '27', '天', '下降', ',', '但', '又', '有', '新', '的', '本土', '聚集性', '疫情', '发生', ',', '疫情', '防控', '形势', '依然', '严峻', '复杂', '。', '“', '近期', ',', '全国', '疫情', '整体', '呈现', '稳定', '下降', '态势', '。', '”', '雷正龙', '在', '发布会', '上', '介绍', ',', '近', '一', '周', '以来', ',', '全国', '每天', '新增', '本土', '感染者', '已经', '降', '至', '1200', '例', '以下', ',', '波及', '范围', '进一步', '缩小', '。', '北京', '聚集性', '疫情', '和', '零星', '散发', '病例', '交织', ',', '局部', '地区', '和', '重点', '人群', '仍', '有', '感染', '传播', '风险', '。', '当前', ',', '上海', '疫情', '继续', '整体', '向', '好', ',', '新增', '报告', '感染', '人数', '持续', '下降', ',', '已', '连续', '8', '天', '每天', '新增', '低于', '1000', '例', ',', '但是', '防', '反弹', '压力', '仍然', '较', '大', ',', '个别', '点位', '和', '社区', '风险', '仍', '有', '波动', ',', '疫情', '防控', '成果', '仍', '需', '进一步', '巩固', '。', '此外', ',', '四川', '广安', '邻水', '疫情', '处于', '波动', '下降期', ',', '疫情', '传播', '风险', '较', '前期', '有所', '降低', '。', '天津', '、', '吉林', '近期', '有', '聚集性', '疫情', '发生', ',', '需', '加快', '检测', '和', '风险', '点位', '排查', '。', '河南', '、', '安徽', '、', '江西', '、', '辽宁', '等', '地', '疫情', '已', '得到', '有效', '遏制', ',', '疫情', '形势', '趋于', '平稳', '。', '据', '雷正龙', '介绍', ',', '截至', '2022年', '5月', '22日', ',', '31', '个', '省', '(', '自治区', '、', '直辖市', ')', '和', '新疆', '生产', '建设', '兵团', '累计', '报告', '接种', '新冠', '病毒', '疫苗', '337109.6万', '剂次', '。']


# 实体抽取结果
[
('国家卫生健康委', 'ORGANIZATION', 0, 3),
('宣传司', 'ORGANIZATION', 6, 7),
('米锋', 'PERSON', 9, 10),
('雷正龙', 'PERSON', 56, 57),
('1200', 'INTEGER', 75, 76),
('北京', 'LOCATION', 84, 85),
('上海', 'LOCATION', 106, 107),
('1000', 'INTEGER', 127, 128),
('四川', 'LOCATION', 157, 158),
('广安', 'LOCATION', 158, 159),
('天津', 'LOCATION', 173, 174),
('吉林', 'LOCATION', 175, 176),
('河南', 'LOCATION', 190, 191),
('安徽', 'LOCATION', 192, 193),
('江西', 'LOCATION', 194, 195),
('辽宁', 'LOCATION', 196, 197),
('雷正龙', 'PERSON', 211, 212),
('2022年', 'DATE', 215, 216),
('5月', 'DATE', 216, 217),
('22日', 'DATE', 217, 218),
('新疆生产建设兵团', 'ORGANIZATION', 228, 232),
('新冠', 'ORGANIZATION', 235, 236),
('337109.6万', 'DECIMAL', 238, 239)
]

这里返回的实体抽取结果,每个四元组表示[命名实体, 类型标签, 起始下标, 终止下标],下标指的是命名实体在单词数组中的下标,单词数组默认为第一个以tok开头的数组

这里执行ner抽取任务时,设置tasks=”ner”,默认是MSRA标准,如果想要执行特定标注的ner任务,可以这样调用:tasks=”ner/pku”,同时执行所有标准的ner任务:tasks=”ner*”。

接下来我们再做一个测试:

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
content = """本院定于2022年6月1日上午09时00分在普洱市中级人民法院第三法庭公开开庭审理原告中国音像著作权集体管理协会与被告普洱帝都娱乐有限公司著作权权属、侵权纠纷一案。"""

results_document = self.HanLP(content, tasks="ner*")
print(results_document)

{
# 分词结果上面已经有了,就省略了
"ner/msra": [
["2022年", "DATE", 2, 3],
["6月", "DATE", 3, 4],
["1", "LOCATION", 4, 5],
["日", "DATE", 5, 6],
["上午", "TIME", 6, 7],
["分", "TIME", 10, 11],
["普洱市中级人民法院第三法庭", "ORGANIZATION", 12, 18],
["中国音像著作权集体管理协会", "ORGANIZATION", 22, 28],
["普洱帝都娱乐有限公司", "ORGANIZATION", 30, 35]
],
"ner/pku": [
["普洱市中级人民法院", "nt", 12, 16],
["第三法庭", "nt", 16, 18],
["中国音像著作权集体管理协会", "nt", 22, 28],
["普洱帝都娱乐有限公司", "nt", 30, 35]
],
"ner/ontonotes": [
["2022年6月1日上午09时00分", "TIME", 2, 11],
["普洱市中级人民法院第三法庭", "ORG", 12, 18],
["中国音像著作权集体管理协会", "ORG", 22, 28],
["普洱帝都娱乐有限公司", "ORG", 30, 35]
]
}

自定义实体词典

像自定义分词一样,很多时候特定领域有自己的实体,也同样可以通过添加自定义词典的形式来提高抽取效果。

这里分为白名单词典和强制词典,与分词的合并模型和强制模式类似。

1
2
3
4
5
def add_white_list(self):
ner = self.HanLP['ner/msra']
ner.dict_whitelist = {'原告': 'ROLE', '被告': 'ROLE', '著作权权属侵权纠纷': 'REASON', '普洱市': 'LOCATION', '中级人民法院': 'ORGANIZATION', '第三法庭': 'LOCATION', '院定': 'LOCATION'}

[('2022年', 'DATE', 2, 3), ('6月', 'DATE', 3, 4), ('1', 'LOCATION', 4, 5), ('日', 'DATE', 5, 6), ('上午', 'TIME', 6, 7), ('分', 'TIME', 10, 11), ('普洱市', 'ORGANIZATION', 12, 13), ('中级人民法院', 'ORGANIZATION', 13, 16), ('第三法庭', 'LOCATION', 16, 18), ('原告', 'ROLE', 21, 22), ('中国音像著作权集体管理协会', 'ORGANIZATION', 22, 28), ('被告', 'ROLE', 29, 30), ('普洱帝都娱乐有限公司', 'ORGANIZATION', 30, 35)]

白名单词典通过ner.dict_whitelist添加,这里添加的实体除了最后一个”院定”的实体,基本在抽取结果中都成功抽取了,这里的”院定”其实就是做测试用的,肯定不是实体,也表示白名单词典并不一定会被输出,优先级低于统计。

强制词典的添加比较麻烦,需要了解标注规则。

BIO

  • B stands for ‘beginning‘ (signifies beginning of an Named Entity, i.e. NE)
  • I stands for ‘inside‘ (signifies that the word is inside an NE)
  • O stands for ‘outside‘ (signifies that the word is just a regular word outside of an NE)

BIOES

  • B stands for ‘beginning‘ (signifies beginning of an NE)
  • I stands for ‘inside‘ (signifies that the word is inside an NE)
  • O stands for ‘outside‘ (signifies that the word is just a regular word outside of an NE)
  • E stands for ‘end‘ (signifies that the word is the end of an NE)
  • S stands for ‘singleton‘(signifies that the single word is an NE )

比如这里把原告和被告添加为ROLE实体

1
2
3
4
5
ner = self.HanLP['ner/msra']
ner.dict_tags = {('审理', '原告'): ('O', 'S-ROLE'),
('与', '被告'): ('O', 'S-ROLE'),
('1', '1日', '日'): ('I', 'S-TIME', 'I')
}

但是想把1日添加为一个时间实体没有成功,有知道如何添加的可以留言~

另外还可以添加黑名单词典,黑名单中的词语绝对不会被当做命名实体,比如这里1被识别为实体,将其从实体中移除:

1
2
3
def add_black_list(self):
ner = self.HanLP['ner/msra']
ner.dict_blacklist = {'1'}