NER 实战
本节定位
前两节已经把:
- 序列标注任务
- BiLSTM + CRF 的核心思想
讲清楚了。
这一节我们把它放回项目里,做一个更像真实业务的练习:
从简历文本里抽取姓名、学校和技能。
这类任务非常适合练 NER,因为它同时包含:
- 明确 span
- 明确类型
- 很多边界细节
学习目标
- 学会定义一个最小 NER 项目边 界
- 学会从 token 标签恢复实体
- 学会做实体级别错误分析
- 通过可运行示例建立信息抽取项目骨架
先建立一张地图
NER 实战更适合按“标签 -> 实体 -> 评估 -> 迭代”的顺序理解:
所以这节真正想解决的是:
- NER 项目为什么不只是“标签预测”
- 为什么实体恢复和错误分析才更像真实项目
一、项目问题先要定义清楚
1.1 场景
输入:
- 一段简历或候选人简介文本
输出:
- 姓名
- 学校
- 技能
1.2 为什么这比“随便抽点实体”更适合练手?
因为它边界清楚:
- 类别数不多
- 实体类型明确
- 结果很容易做业务解释
1.3 第一个关键点不是模型,而是标签体系
例如:
张三->B-NAME清华大学->B-SCHOOL I-SCHOOL ...Python->B-SKILL
这一步一旦含糊,后面模型和评估都会一起乱。
1.4 一个更适合新人的总类比
你可以把 NER 想成:
- 在一段文字里拿荧光笔圈重点信息
难点不只是“圈出来”,而是:
- 从哪里开始圈
- 到哪里结束
- 这一段到底属于哪一类
这样理解后,为什么 NER 特别容易卡在边界问题,就会自然很多。
二、先做一个可运行标注与解码闭环
下面这个示例会做三件事:
- 准备一个小型样本
- 把 BIO 标签解码成实体
- 做简单的预测对比与错误分析
samples = [
{
"tokens": ["张三", "毕业于", "清华大学", ",", "熟悉", "Python", "和", "PyTorch"],
"gold_tags": ["B-NAME", "O", "B-SCHOOL", "O", "O", "B-SKILL", "O", "B-SKILL"],
"pred_tags": ["B-NAME", "O", "B-SCHOOL", "O", "O", "B-SKILL", "O", "B-SKILL"],
},
{
"tokens": ["李四", "来自", "北京大学", ",", "掌握", "Java"],
"gold_tags": ["B-NAME", "O", "B-SCHOOL", "O", "O", "B-SKILL"],
"pred_tags": ["B-NAME", "O", "O", "O", "O", "B-SKILL"],
},
]
def decode_entities(tokens, tags):
entities = []
current_tokens = []
current_type = None
for token, tag in zip(tokens, tags):
if tag == "O":
if current_tokens:
entities.append(("".join(current_tokens), current_type))
current_tokens = []
current_type = None
continue
prefix, entity_type = tag.split("-", 1)
if prefix == "B":
if current_tokens:
entities.append(("".join(current_tokens), current_type))
current_tokens = [token]
current_type = entity_type
elif prefix == "I" and current_type == entity_type:
current_tokens.append(token)
else:
if current_tokens:
entities.append(("".join(current_tokens), current_type))
current_tokens = [token]
current_type = entity_type
if current_tokens:
entities.append(("".join(current_tokens), current_type))
return entities
for sample in samples:
gold_entities = decode_entities(sample["tokens"], sample["gold_tags"])
pred_entities = decode_entities(sample["tokens"], sample["pred_tags"])
print("tokens:", sample["tokens"])
print("gold :", gold_entities)
print("pred :", pred_entities)
print("miss :", [x for x in gold_entities if x not in pred_entities])
print()