共计 2124 个字符,预计需要花费 6 分钟才能阅读完成。
背景介绍
命名实体识别(NER)是自然语言处理中的基础任务,而高质量标注数据是模型效果的基石。但新手常遇到:

- 实体边界划分不一致(如 ” 北京大学 ” 该标为整体还是分开)
- 标签体系设计不合理导致模型混淆
- 多人协作时标注风格不统一
我曾参与医疗文本的 NER 项目,发现 30% 的 bad case 源于标注问题。下面分享从踩坑中总结的完整方案。
标签体系设计
常见的三种标注方案对比:
-
IOB:最基础格式,B- 表示实体开头,I- 表示实体内部
北京 B-LOC 大学 I-LOC -
IOBES:增加 E(实体结尾)和 S(单字实体)标签
北京 B-LOC 大学 E-LOC -
BIOUL:U 表示单字实体,L 表示实体结尾
推荐选择 IOBES:比 IOB 多了边界信息,比 BIOUL 更易解析。实际项目中标明实体类型和边界更重要。
BRAT 标注实战
-
安装 BRAT(推荐 docker 方式):
docker pull brat docker run -d -p 8001:80 -v /path/to/data:/data brat -
配置标注规范(
annotation.conf):[entities] PER # 人名 ORG # 组织 [relations] [events] [attributes] -
标注示例(保存为
.ann文件):T1 PER 0 3 张三 T2 ORG 8 12 腾讯
数据格式转换
将 BRAT 格式转为 PyTorch 可用的序列标签(完整代码见 GitHub):
from typing import List, Dict
import torch
# 标签映射表示例
LABEL_MAP = {
"O": 0,
"B-PER": 1, "I-PER": 2, "E-PER": 3, "S-PER": 4,
# 其他实体类型...
}
def brat_to_labels(text: str, ann_lines: List[str]) -> List[int]:
"""
将 BRAT 标注转换为 IOBES 标签序列
:param text: 原始文本
:param ann_lines: .ann 文件内容
"""labels = ["O"] * len(text)
for line in ann_lines:
if line.startswith("T"): # 实体标注行
parts = line.split("\t")
entity_info = parts[1].split()
entity_type = entity_info[0]
start, end = int(entity_info[1]), int(entity_info[2])
# 处理实体边界
if end - start == 1:
labels[start] = f"S-{entity_type}"
else:
labels[start] = f"B-{entity_type}"
for i in range(start+1, end-1):
labels[i] = f"I-{entity_type}"
labels[end-1] = f"E-{entity_type}"
return [LABEL_MAP[label] for label in labels]
标注质量检查
开发时用这个脚本快速发现问题:
def check_annotation_consistency(ann_dir: str):
"""检查标注文件中的常见错误"""
for ann_file in Path(ann_dir).glob("*.ann"):
with open(ann_file) as f:
text = f.read()
# 检查重叠标注
entities = parse_entities(text)
for i, e1 in enumerate(entities):
for e2 in entities[i+1:]:
if check_overlap(e1["start"], e1["end"], e2["start"], e2["end"]):
print(f"重叠实体: {e1} 与 {e2}")
避坑指南
实体边界模糊
- 医疗文本中 ”2 型糖尿病 ” 是否包含 ” 型 ”?建议:
- 建立标注规则文档
- 对争议案例投票表决
嵌套实体处理
当出现如 ” 北京大学生 ”(人名 + 地名嵌套):
- 优先标注最外层实体
- 用关系标注标记内部结构
团队协作方案
- 使用 Git 管理标注文件
- 每日合并时运行检查脚本
- 通过
git blame追踪问题来源
进阶优化
数据增强
def replace_entities(text: str, entity_db: Dict[str, List[str]]):
"""用同类型实体替换原实体"""
for ent in extract_entities(text):
if ent["type"] in entity_db:
replacement = random.choice(entity_db[ent["type"]])
text = text[:ent["start"]] + replacement + text[ent["end"]:]
return text
半自动标注流程
- 用现有模型预测未标注数据
- 人工修正预测结果
- 将修正数据加入训练集
思考问题
- 当标注人员对 ” 有限公司 ” 是否属于机构名有分歧时,如何制定可操作的规则?
- 如何量化评估标注质量对最终模型 F1 值的影响?
- 对于专业领域 NER(如医疗),非专业人士标注时如何保证准确性?
希望通过这套流程,能帮你避开我踩过的那些坑。记住:在 NER 任务中,高质量的标注数据比模型结构创新更重要。
正文完
发表至: 自然语言处理
近两天内
