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 | CTB、PKU、863 |
命名实体识别 | 教程 | 教程 | 教程 | ner | PKU、MSRA、OntoNotes |
依存句法分析 | 教程 | 教程 | 教程 | dep | SD、UD、PMT |
成分句法分析 | 教程 | 教程 | 教程 | con | Chinese Tree Bank |
语义依存分析 | 教程 | 教程 | 教程 | sdp | CSDP |
语义角色标注 | 教程 | 教程 | 教程 | srl | Chinese Proposition Bank |
抽象意义表示 | 教程 | 暂无 | 教程 | amr | CAMR |
另外,通过print(hanlp.pretrained.mtl.ALL)
可以直接打印所有的模型名称,并且附有模型文件下载链接。
我们选择上面的一种模型
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…
也可以自己下载文件到指定目录解压,然后从指定目录加载,但是要注意解压的文件夹名称要和压缩文件名称一致,例如
hanlp.load(
save_dir=(
'./data/hanlp/mtl/'
'close_tok_pos_ner_srl_dep_sdp_con_electra_base_20210111_124519'),
device=0
)
下载好之后,可以查看该模型支持哪些任务:
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
为细分。 。 '/'前面是任务,后面是标注标准
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*"可以得到两种分词结果。
以下是粗粒度的分词结果:
['本', '院', '定于', '2022', '年', '6', '月', '1', '日', '上午', '09', '时', '00', '分', '在', '普洱市中级人民法院第三法庭', '公开', '开庭', '审理', '原告', '中国音像著作权集体管理协会', '与', '被告', '普洱帝都娱乐有限公司', '著作权', '权', '属', '、', '侵权', '纠纷', '一', '案', '。']
在应用于特定领域时,一般我们都会有一些领域词,而hanlp这种通用的模型没办法提取出领域词,我们希望可以添加这样一个词表,可以让hanlp在分词时,将这些词作为一个分词结果。
我们可以通过这种方式自定义词典
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
会在统计模型的分词结果上执行最长匹配并合并匹配到的词条。一般情况下,推荐使用该模式,使用方式如下
tok.dict_combine = {'市中级人民法院', '开庭审理'}
# 分词结果
['本院', '定于', '2022年', '6月', '1', '日', '上午', '09', '时', '00', '分', '在', '普洱市', '中级', '人民', '法院', '第三', '法庭', '公开', '开庭审理', '原告', '中国', '音像', '著作权', '集体', '管理', '协会', '与', '被告', '普洱', '帝都', '娱乐', '有限', '公司', '著作权', '权属', '、', '侵权', '纠纷', '一', '案', '。']
合并模型添加的词并不一定总能分词成功,因为还是以统计为主,比如这里市中级人民法院就没有分词成功。
另外如果你的自定义词典中的词含有空格、制表符等,可以通过tuple的形式添加
tok.dict_combine = {('iPad', 'Pro')}
如果想要获取分词在原文本的位置信息,可以这样配置
tok.config.output_spans = True
返回格式为三元组(单词,单词的起始下标,单词的终止下标),下标以字符级别计量。
接下来我们用它来对一段文本进行实体抽取(实体抽取任务中包含分词)
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*”。
接下来我们再做一个测试:
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]
]
}
像自定义分词一样,很多时候特定领域有自己的实体,也同样可以通过添加自定义词典的形式来提高抽取效果。
这里分为白名单词典和强制词典,与分词的合并模型和强制模式类似。
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
BIOES
比如这里把原告和被告添加为ROLE实体
ner = self.HanLP['ner/msra']
ner.dict_tags = {('审理', '原告'): ('O', 'S-ROLE'),
('与', '被告'): ('O', 'S-ROLE'),
('1', '1日', '日'): ('I', 'S-TIME', 'I')
}
但是想把1日
添加为一个时间实体没有成功,有知道如何添加的可以留言~
另外还可以添加黑名单词典,黑名单中的词语绝对不会被当做命名实体,比如这里1被识别为实体,将其从实体中移除:
def add_black_list(self):
ner = self.HanLP['ner/msra']
ner.dict_blacklist = {'1'}