-
Notifications
You must be signed in to change notification settings - Fork 415
NER 说明文档
冬日新雨 edited this page Oct 25, 2021
·
15 revisions
- 该工具包是针对基于模型的 NER 任务,辅助性工具包,旨在加快模型开发,加速模型的并行预测效率。
- 该工具包中,对 NER 模型的输入、输出数据做了规范范式,数据格式有两种,一种是易于理解的
entity
格式,另外一种是方面模型接收的tag
格式,同时,模型一般接收的数据分为字符(char)级别和词汇(word)级别作为输入 token,样例如下:
>>> # 字 token
>>> text = ['胡', '静', '静', '在', '水', '利', '局', '工', '作', '。']
>>> entity = [{'text': '胡静静', 'offset': [0, 3], 'type': 'Person'},
{'text': '水利局', 'offset': [4, 7], 'type': 'Orgnization'}]
>>> tag = ['B-Person', 'I-Person', 'E-Person', 'O', 'B-Orgnization',
'I-Orgnization', 'E-Orgnization', 'O', 'O', 'O']
>>> # 词 token
>>> text = ['胡静静', '在', '重庆', '水利局', '人事科', '工作', '。']
>>> entity = [{'text': '胡静静', 'offset': [0, 1], 'type': 'Person'},
{'text': '水利局', 'offset': [2, 5], 'type': 'Orgnization'}]
>>> tag = ['S-Person', 'O', 'B-Orgnization', 'I-Orgnization', 'E-Orgnization', 'O', 'O']
- 本工具包中的所有 NER 数据处理均基于以上两种数据格式、两个 token 级别的转换。
- tag 标签的标注标准是 B(Begin)I(Inside)O(Others)E(End)S(Single) 格式。
给定文本以及其 entity,返回其对应的 tag 格式标签。
>>> import jionlp as jio
>>> token_list = '胡静静在水利局工作。' # 字级别
>>> token_list = ['胡', '静', '静', '在', '水',
'利', '局', '工', '作', '。'] # 字或词级别
>>> ner_entities =
[{'text': '胡静静', 'offset': [0, 3], 'type': 'Person'},
{'text': '水利局', 'offset': [4, 7], 'type': 'Orgnization'}]
>>> print(jio.ner.entity2tag(token_list, ner_entities))
# ['B-Person', 'I-Person', 'E-Person', 'O', 'B-Orgnization',
# 'I-Orgnization', 'E-Orgnization', 'O', 'O', 'O']
- 不支持批量处理,仅支持单条数据处理。
给定文本以及其 tag,返回其对应的 entity 格式标签。
>>> import jionlp as jio
>>> token_list = '胡静静在水利局工作。' # 字级别
>>> token_list = ['胡', '静', '静', '在', '水',
'利', '局', '工', '作', '。'] # 字或词级别
>>> tags = ['B-Person', 'I-Person', 'E-Person', 'O', 'B-Orgnization',
'I-Orgnization', 'E-Orgnization', 'O', 'O', 'O']
>>> print(jio.ner.tag2entity(token_list, tags))
# [{'text': '胡静静', 'offset': [0, 3], 'type': 'Person'},
# {'text': '水利局', 'offset': [4, 7], 'type': 'Orgnization'}]]
- 不支持批量处理,仅支持单条数据处理。
- 该步骤在实施时,一般在模型预测(predict、inference)阶段完成后,BIOES 标注标准下,实体的范式为
S|B(nI)E
,因此,必定会存在不满足范式的 tag,造成实体无法识别,因此,函数中提供了参数verbose(bool)
,指示是否打印无法转为 entity 的 tag。
给定字符级别的 token 文本以及其 entity,返回其对应的词汇级别的 entity。
>>> import jionlp as jio
>>> char_token_list = '胡静静喜欢江西红叶建筑公司' # 字级别
>>> char_token_list = [
'胡', '静', '静', '喜', '欢', '江', '西',
'红', '叶', '建', '筑', '公', '司'] # 字或词级别
>>> char_entity_list = [
{'text': '胡静静', 'offset': [0, 3], 'type': 'Person'},
{'text': '江西红叶建筑公司', 'offset': [5, 13], 'type': 'Company'}]
>>> word_token_list = ['胡静静', '喜欢', '江西', '红叶', '建筑', '公司']
>>> print(jio.ner.char2word(char_entity_list, word_token_list))
# [{'text': '胡静静', 'offset': [0, 1], 'type': 'Person'},
# {'text': '江西红叶建筑公司', 'offset': [2, 6], 'type': 'Company'}]
- 不支持批量处理,仅支持单条数据处理。
- 所有转换在 entity 格式基础上。
- 由于分词器的词汇边界可能和标注实体的边界不一致造成偏差,会导致一部分实体无法转换为词级别,称为分词偏差。因此,提供参数
verbose(bool)
打印无法转换的实体。 - 分词偏差,根据经验,jieba 分词器错误率在 4.62%,而 pkuseg 分词器错误率在 3.44%。
给定词汇级别的 token 文本以及其 entity,返回其对应的字符级别的 entity。
>>> import jionlp as jio
>>> word_entity_list = [
{'type': 'Person', 'offset': [0, 1], 'text': '胡静静'},
{'type': 'Company', 'offset': [2, 6], 'text': '江西红叶建筑公司'}]
>>> word_token_list = ['胡静静', '喜欢', '江西', '红叶', '建筑', '公司']
>>> print(jio.ner.word2char(word_entity_list, word_token_list))
# [{'text': '胡静静', 'offset': [0, 3], 'type': 'Person'},
# {'text': '江西红叶建筑公司', 'offset': [5, 13], 'type': 'Company'}]
- 不支持批量处理,仅支持单条数据处理。
- 所有转换在 entity 格式基础上。
给定各个类别的实体数据,采用基于 Trie 树的前向最大匹配方法,匹配找出文本中的实体。
>>> import jionlp as jio
>>> entity_dicts = {
'Person': ['张大山', '岳灵珊', '岳不群'],
'Organization': ['成都市第一人民医院', '四川省水利局']}
>>> lexicon_ner = jio.ner.LexiconNER(entity_dicts)
>>> text = '岳灵珊在四川省水利局上班。'
>>> result = lexicon_ner(text)
>>> print(result)
# [{'type': 'Person', 'text': '岳灵珊', 'offset': [0, 3]},
# {'type': 'Organization', 'text': '四川省水利局', 'offset': [4, 10]}]
- 实体词典如上
entity_dicts
所示。 - 所有匹配找出的实体均为 entity 格式。
- 构建 trie 树时,当同一个实体出现在多个类别中,则打印警告信息,仅默认选取其中一个类别进行构建。
NER 模型在训练好后,并行预测阶段,可以有若干种方法对模型进行加速,提高并行处理能力。具体使用方法如下:
>>> import jionlp as jio
>>> # 1、并用样例:
>>> text_list = [list(line) for line in text_list]
>>> def func(token_lists, para=1):
>>> ... token_lists = [['S-' + chr(ord(token) + para) for token in token_list]
>>> ... for token_list in token_lists]
>>> ... return token_lists
>>> max_sen_len = 70
>>> token_batch_obj = jio.ner.TokenBatchBucket(func, max_sen_len=max_sen_len, batch_size=30)
>>> token_break_obj = jio.ner.TokenBreakLongSentence(token_batch_obj, max_sen_len=max_sen_len)
>>> token_split_obj = jio.ner.TokenSplitSentence(token_break_obj, max_sen_len=max_sen_len, combine_sentences=True)
>>> res = token_split_obj(text_list, para=1) # 补充 func 函数的参数
>>> # 其中,三个工具的 max_sen_len 必须保持一致。
>>> # 2、分用样例:
>>> # 允许 TokenSplitSentence, TokenBreakLongSentence 两者结合
>>> token_break_obj = jio.ner.TokenBreakLongSentence(token_batch_obj, max_sen_len=max_sen_len)
>>> token_split_obj = jio.ner.TokenSplitSentence(token_break_obj, max_sen_len=max_sen_len, combine_sentences=True)
>>> res = token_break_obj(text_list, para=1) # 补充 func 函数的参数
- 原理说明:
- 1、将短句进行拼接,至接近最大序列长度。 一般 NER 模型在输入模型前,须首先进行分句处理。但一般较短的句子,其上下文依赖 少,不利于挖掘上下文信息;另一方面,需要大量的 pad 操作,限制了模型效率。因此 须将较短的句子逐一拼接,至接近模型允许的序列最大长度。该方法主要由 TokenSplitSentence 实现。
- 2、将超长句子进行重叠拆解,并使用规则对其进行合并。 输入的文本有一部分,长度超过模型允许序列最大长度,且无标点符号。这类句子一旦 直接应用模型,一方面造成模型并行性能急剧下降,另一方面其模型效果也会下降。因此 须将超长句子进行重叠拆分,然后再次利用规则合并,达到高速并行的效果。该方法已申 请专利。由 TokenBreakLongSentence 实现。
- 3、将相近长度的句子拼接入一个 batch,提升模型的并行能力。 在 tensorflow 等框架中,动态处理 LSTM 等 RNN 序列,会以最长的序列为基准进行 计算。因此,若句子长度均相近,长句和长句放入一个 batch,短句和短句放入一个 batch,则会减少 pad 数量,提升模型的并行能力。由 TokenBatchBucket 实现。
- 其中,样例中的
func
指,输入模型规定最长序列max_sen_len
的一个batch_size
的句子序列集,输出其对应的 tag 标签。
NER 的标注数据,经过模型训练后,预测得到的实体结果,两者之间存在差异,该方法提供了比较两者之间差异的功能。
>>> import jionlp as jio
>>> text = '张三在西藏拉萨游玩!之后去新疆。'
>>> labeled_entities = [ # 人工标注实体
{'text': '张三', 'offset': [0, 2], 'type': 'Person'},
{'text': '西藏拉萨', 'offset': [3, 7], 'type': 'Location'}]
>>> predicted_entities = [ # 模型预测实体
{'text': '张三在', 'offset': [0, 3], 'type': 'Person'},
{'text': '西藏拉萨', 'offset': [3, 7], 'type': 'Location'},
{'text': '新疆', 'offset': [13, 15], 'type': 'Location'}]
>>> res = jio.ner.entity_compare(
text, labeled_entities, predicted_entities, context_pad=1)
>>> print(res)
# [
# {'context': '张三在西',
# 'labeled_entity': {'text': '张三', 'offset': [0, 2], 'type': 'Person'},
# 'predicted_entity': {'text': '张三在', 'offset': [0, 3], 'type': 'Person'}},
# {'context': '去新疆。',
# 'labeled_entity': None,
# 'predicted_entity': {'text': '新疆', 'offset': [13, 15], 'type': 'Location'}}
# ]
- 针对标注语料的实体,以及模型训练后预测得到的实体,往往存在不一致,找出这些标注不一致的数据,能够有效分析模型的预测能力,找出 bad case。
- 提供了参数
context_pad(int)
,用于给出不一致实体对的上下文信息。 - 输入的文本和实体,必须以字符级别为基准,不可以用词汇级别为基准。
针对 NER 的标注数据,分割其为训练、验证、测试集,并给出各个实体类型的统计信息,计算各子集的分布与全数据集分布的相对熵。
>>> import jionlp as jio
>>> dataset_x = ['马成宇在...',
'金融国力教育公司...',
'延平区人民法院曾经...',
...]
>>> dataset_y = [[{'type': 'Person', 'text': '马成宇', 'offset': (0, 3)}],
[{'type': 'Company', 'text': '国力教育公司', 'offset': (2, 8)}],
[{'type': 'Organization', 'text': '延平区人民法院', 'offset': (0, 7)}],
...]
>>> train_x, train_y, valid_x, valid_y, test_x, test_y, stats = \
... jio.ner.analyse_dataset(dataset_x, dataset_y)
>>> print(stats)
whole dataset:
Company 573 39.68%
Person 495 34.28%
Organization 376 26.04%
total 3,000 100.00%
train dataset: 80.00%
Company 464 40.38%
Person 379 32.99%
Organization 306 26.63%
total 2,400 100.00%
valid dataset: 5.00%
Person 32 47.06%
Company 22 32.35%
Organization 14 20.59%
total 150 100.00%
test dataset: 15.00%
Company 87 38.33%
Person 84 37.00%
Organization 56 24.67%
total 450 100.00%
train KL divergence: 0.000546, info dismatch: 0.03%
valid KL divergence: 0.048423, info dismatch: 3.10%
test KL divergence: 0.002364, info dismatch: 0.15%
- info dismatch 信息,百分比越小,说明数据子集类别分布越合理。
将一个实体识别标注数据集(也包括其它同类型的序列标注任务,如主体抽取、要素抽取等)中的所有实体进行收集。
>>> import json
>>> import jionlp as jio
>>> dataset_y = [[{'type': 'Person', 'text': '马成宇', 'offset': (0, 3)},
{'type': 'Company', 'text': '百度', 'offset': (10, 12)},
{'type': 'Company', 'text': '百度', 'offset': (20, 22)}],
[{'type': 'Company', 'text': '国力教育公司', 'offset': (2, 8)}],
[{'type': 'Organization', 'text': '延平区人民法院', 'offset': (0, 7)},
{'type': 'Company', 'text': '百度', 'offset': (10, 12)},
{'type': 'Company', 'text': '百度', 'offset': (20, 22)}]]
>>> res = jio.ner.collect_dataset_entities(dataset_y)
>>> print(json.dumps(res, ensure_ascii=False, indent=4, separators=(',', ':')))
# {
# "Person":{
# "马成宇":1
# },
# "Company":{
# "百度":4,
# "国力教育公司":1
# },
# "Organization":{
# "延平区人民法院":1
# }
# }
- 其中包括每个类型实体的频次,有可能同一实体,具有不同的类型,如“金华”既存在于人名,又存在于地名类型中。
给定一篇文本,从中抽取出其中的时间实体(不依赖模型)。
>>> import time
>>> import json
>>> import jionlp as jio
>>> text = '''8月临近尾声,中秋、国庆两个假期已在眼前。2021年中秋节是9月21日,星期二。
有不少小伙伴翻看放假安排后,发现中秋节前要"补"假。
记者注意到,根据放假安排,9月18日(星期六)上班,9月19日至21日放假调休,也就是从周日开始放假3天。
由于中秋节后上班不到 10天,又将迎来一个黄金周—国庆长假,因此工作也就"安排"上了。
双节来袭,仍有人要坚守岗位。'''
>>> res = jio.ner.extract_time(text, time_base=time.time() with_parsing=False)
>>> print(json.dumps(res, ensure_ascii=False, indent=4, separators=(',', ':')))
# {'text': '8月', 'offset': [41, 43], 'type': 'time_point'}
# {'text': '中秋', 'offset': [48, 50], 'type': 'time_point'}
# {'text': '国庆', 'offset': [51, 53], 'type': 'time_point'}
# {'text': '2021年中秋节', 'offset': [62, 70], 'type': 'time_point'}
# {'text': '9月21日', 'offset': [71, 76], 'type': 'time_point'}
# {'text': '星期二', 'offset': [77, 80], 'type': 'time_point'}
# {'text': '中秋节前', 'offset': [98, 102], 'type': 'time_span'}
# {'text': '9月18日', 'offset': [136, 141], 'type': 'time_point'}
# {'text': '星期六', 'offset': [142, 145], 'type': 'time_point'}
# {'text': '9月19日至21日', 'offset': [149, 158], 'type': 'time_span'}
- 返回结果与 ner 格式相同,其中,
type
类型指示了该实体的具体类型,包括时间点、时间范围、时间段,时间周期四种类型。 - 参数
time_base
用于时间解析时提供时间基,而with_parsing(bool)
用于指示返回结果是否返回解析信息。
给定一篇文本,从中抽取出其中的货币金额实体(不依赖模型)。
>>> import time
>>> import json
>>> import jionlp as jio
>>> text = '张三赔偿李大花人民币车费601,293.11元,工厂费大约一万二千三百四十五元,利息9佰日元,打印费十块钱。'
>>> res = jio.ner.extract_money(text, with_parsing=False)
>>> print(json.dumps(res, ensure_ascii=False, indent=4, separators=(',', ':')))
# [{'text': '601,293.11元', 'offset': [12, 23], 'type': 'money'},
# {'text': '大约一万二千三百四十五元', 'offset': [27, 39], 'type': 'money'},
# {'text': '9佰日元', 'offset': [42, 46], 'type': 'money'},
# {'text': '人民币十块钱', 'offset': [50, 56], 'type': 'money'}]
- 返回结果与 ner 格式相同,其中,
type
类型固定为money
- 参数
with_parsing(bool)
用于指示返回结果是否返回解析信息。 -
ret_all(bool)
: 某些货币金额表达,在大多数情况下并非表达货币金额,如 “几分” 之于 “他有几分不友善”,默认按绝大概率处理, 即不返回此类伪货币金额表达,该参数默认为 False;若希望返回所有抽取到的货币金额表达,须将该参数置 True。 - 该工具配合
jio.parse_money
parse_money说明使用。