NER任务
概念:实体抽取,又称为命名实体识别(named entity recognition,NER),指的是从文本之中抽取出命名性实体,并把这些实体划分到指定的类别。
标注规则
IO | 简单,无边界表示, 会出现无法区分两个连续实体的问题(歧义性) 如 广州市天河区
BIO | 加入了开始边界(减少了实体边界识别的歧义,适合处理多个连续或相邻的实体),性能优秀
BIOES | 精确,性能优秀,灵活性强,复杂度高,标注成本高,计算开销大,存在样本类别不均衡问题
B代表Begin,表示实体名称的开头,I代表Inside,表示实体名称的中间或者末尾字符,E代表End,表示实体名称的结束字符,O代表Outside,表示不是实体名称的字符,S代表Single,表示单字为一个命名实体
文本序列 | IO标注体系 | BIO标注体系 | BIOES标注模式 |
---|---|---|---|
瓦 | I-PER | B-PER | B-PER |
特 | I-PER | I-PER | E-PER |
出 | O | O | O |
生 | O | O | O |
于 | O | O | O |
苏 | I-LOC | B-LOC | B-LOC |
格 | I-LOC | I-LOC | I-LOC |
兰 | I-LOC | I-LOC | E-LOC |
评价规则
Precision | T P T P + F P \frac{TP}{TP + FP} TP+FPTP
Recall | T P T P + F N \frac{TP}{TP + FN} TP+FNTP
F1_Score | 2 ∗ P ∗ R P + R \frac{2*P*R}{P + R} P+R2∗P∗R
Accuracy | T P + F N G T \frac{TP + FN}{GT} GTTP+FN
实现方法
基于规则的方法
实现思路: 词典 + 正则表达式
特点:简单暴力(甚至不需要人工标注数据),扩展性差
基于传统机器学习的方法
实现思路:CRF
特点:特征工程麻烦,速度还行
基于深度学习的方法
实现思路:
- Bi-LSTM with CRF
- Transformer with CRF
- Transformer +Prompt with CRF
特点:不需要特征工程,可拓展性强
实战部分
任务与数据介绍 中文NLP地址要素解析
赛题描述[摘自源网页]
1.赛题背景
地址是日常生活中一种重要的文本信息,诸多场景需要登记地址,如电商购物、外卖配送、人口普查、水电气开户等。常见的地址一般包含以下几类信息:
properties
行政区划信息,如省、市、县、乡镇信息;
路网信息,如路名,路号,道路设施等;
详细地址信息,如POI (兴趣点)、楼栋号、户室号等;
非地址信息,如补充说明,误输入等;
地址要素解析是将地址文本拆分成独立语义的要素,并对这些要素进行类型识别的过程。地址要素解析与地址相关性共同构成了中文地址处理两大核心任务,具有很大的商业价值。目前中文地址领域缺少标准的评测和数据集,这次我们将开放较大规模的标注语料,希望和社区共同推动地址文本处理领域的发展。
2.赛题描述
中文地址要素解析任务的目标即将一条地址分解为上述几个部分的详细标签,如:
markdown
输入:浙江省杭州市余杭区五常街道文一西路969号淘宝城5号楼,放前台
输出:Province=浙江省 city=杭州市 district=余杭区 town=五常街道 road=文一西路road_number=969号 poi=淘宝城 house_number=5号楼 other=,放前台
数据情况(train.conll
, dev.conll
)
markdown
# 样本之间使用\n隔开
上 B-district
城 I-district
区 E-district
望 B-road
江 I-road
东 I-road
路 E-road
0 B-roadno
0 I-roadno
0 I-roadno
号 E-roadno
望 B-poi
江 I-poi
国 I-poi
际 I-poi
中 I-poi
心 E-poi
0 B-houseno
栋 E-houseno
0 B-floorno
楼 E-floorno
数据标注规则 [见前面超链接中的 中文地址要素解析标注规范.pdf]
基于规则匹配的中文地址要素解析
代码转载自赛题提供的baseline
1- 加载数据集
python
def load_conll_file(filename):
"""
读取文件,返回一个生成器,每一句会生成一个字典,字典包含tokens和labels两个key
按空行分割句子,返回包含tokens和labels的字典
:param filename: 文件名
:return: e.g {'tokens': ['浙','江'], 'labels': ['B-LOC','E-LOC']}
"""
with open(filename, 'r', encoding='utf8') as f:
tokens = []
labels = []
for line in f:
if line == '' or line == '\n':
if tokens:
yield {
'tokens': tokens,
'labels': labels
}
tokens = []
labels = []
else:
splits = line.rsplit(' ', 1)
tokens.append(splits[0])
labels.append(splits[-1].rstrip())
if tokens:
yield {
'tokens': tokens,
'labels': labels
}
2- 获取训练集和验证集中的实体要素
python
def get_entities(tokens, labels):
"""
获取实体列表
:param tokens: e.g ['浙','江']
:param labels: e.g ['B-LOC','E-LOC']
:return: e.g
"""
entities = []
cur_entity = {}
for token, label in zip(tokens, labels):
if label[0] in 'OBS' and cur_entity:
entities.append(cur_entity)
cur_entity = {}
if label[0] in 'BS':
cur_entity = {
'text': token,
'type': label[2:]
}
elif label[0] in 'IE':
cur_entity['text'] += token
if cur_entity:
entities.append(cur_entity)
return entities
3 - 定义规则匹配器
python
from spacy.lang.zh import Chinese
class RuleBasedTagger:
def __init__(self, entity_counter, threshold=1):
nlp = Chinese()
ruler = nlp.add_pipe('entity_ruler')
patterns = [{'label': e[0][1], 'pattern': e[0][0]} for e in entity_counter.most_common() if e[1] >= threshold]
with nlp.select_pipes(enable='tagger'):
ruler.add_patterns(patterns)
self.nlp = nlp
def __call__(self, sentence):
doc = self.nlp(sentence)
# (start,end) 包左不包右
return [{
'text': span.text,
'label': span.label_,
'start': span.start,
'end': span.end
} for span in doc.ents]
def format_entities(entities, length):
labels = ['O'] * length
for e in entities:
start, end, label = e['start'], e['end'], e['label']
if start + 1 == end:
labels[start] = 'S-' + label
else:
labels[start] = 'B-' + label
for i in range(start + 1, end - 1):
labels[i] = 'I-' + label
labels[end - 1] = 'E-' + label
return ' '.join(labels)
4 - 主函数
python
if __name__ == '__main__':
# load datasets and count entities
train_dataset = list(load_conll_file('../data/train.conll'))
dev_dataset = list(load_conll_file('../data/dev.conll'))
# print(len(train_dataset))
# print(len(dev_dataset))
from collections import Counter
entity_counter = Counter()
for example in train_dataset + dev_dataset:
entities = get_entities(example['tokens'], example['labels'])
entity_counter.update([(e['text'], e['type']) for e in entities])
# print(entity_counter.most_common())
tagger = RuleBasedTagger(entity_counter)
# print(tagger('浙江杭州阿里'))
# process test file and get predictions
with open('../data/final_test.txt', 'r', encoding='utf8') as fin, \
open('outputs/baseline1.pred.txt', 'w', encoding='utf8') as fout:
for line in fin:
guid, sentence = line.strip().split('\u0001')
predicted_entities = tagger(sentence)
formatted_pred = format_entities(predicted_entities, len(sentence))
print(guid, sentence, formatted_pred, sep='\t', file=fout)