🏷️ 第6课:命名实体识别

知识图谱的入口——从文本中识别命名实体

📖 什么是命名实体识别

命名实体识别(Named Entity Recognition, NER)是从非结构化文本中识别出具有特定意义的实体,并分类到预定义类别(如人名、地名、组织名等)的技术。NER是知识图谱构建的第一步——没有实体,就没有知识图谱。

🎯 常见实体类型

类别英文示例
人名PER鲁迅、毛泽东、爱因斯坦
地名LOC北京、浙江省、太平洋
组织ORG北京大学、联合国、阿里巴巴
时间TIME2024年3月、春秋时期
数字/金额NUM300万、3.14
作品WORK《呐喊》、蒙娜丽莎
事件EVENT五四运动、二战

📐 NER方法演进

规则/词典 HMM/CRF BiLSTM-CRF BERT-NER 大模型NER

💻 Python实现:基于规则的NER

import re from collections import defaultdict class RuleBasedNER: """基于规则和词典的命名实体识别器""" def __init__(self): self.dictionaries = defaultdict(set) # {entity_type: {entities}} self.patterns = [] # [(regex, entity_type)] self.prefixes = defaultdict(list) # {entity_type: [prefixes]} def add_dictionary(self, entity_type, entities): """添加实体词典""" for e in entities: self.dictionaries[entity_type].add(e) def add_pattern(self, regex, entity_type): ">>>添加正则模式""" self.patterns.append((re.compile(regex), entity_type)) def add_prefix(self, entity_type, prefixes): """添加实体前缀(如'省'、'市')""" self.prefixes[entity_type].extend(prefixes) def recognize(self, text): """识别文本中的命名实体,返回 [(实体, 类型, 起始位置, 结束位置)]""" results = [] # 1. 词典匹配(最长匹配优先) for entity_type, entities in self.dictionaries.items(): for entity in sorted(entities, key=len, reverse=True): start = 0 while True: idx = text.find(entity, start) if idx == -1: break # 检查是否已被更长的实体覆盖 if not any(s <= idx < e or s < idx + len(entity) <= e for _, _, s, e in results): results.append((entity, entity_type, idx, idx + len(entity))) start = idx + 1 # 2. 正则模式匹配 for pattern, entity_type in self.patterns: for match in pattern.finditer(text): entity = match.group() if not any(s <= match.start() < e or s < match.end() <= e for _, _, s, e in results): results.append((entity, entity_type, match.start(), match.end())) # 3. 前缀模式匹配 for entity_type, prefixes in self.prefixes.items(): for prefix in prefixes: pattern = re.compile(r'[一-鿿]{1,4}' + re.escape(prefix)) for match in pattern.finditer(text): entity = match.group() if not any(s <= match.start() < e or s < match.end() <= e for _, _, s, e in results): results.append((entity, entity_type, match.start(), match.end())) results.sort(key=lambda x: x[2]) return results def format_results(self, text, results): """格式化输出识别结果""" lines = [] for entity, etype, start, end in results: lines.append(f" [{etype}] {entity} (位置: {start}-{end})") return " ".join(lines) # ========== 构建中文NER系统 ========== ner = RuleBasedNER() # 词典 ner.add_dictionary("PER", ["鲁迅", "老舍", "徐志摩", "巴金", "茅盾", "毛泽东"]) ner.add_dictionary("LOC", ["绍兴", "北京", "上海", "海宁", "浙江", "浙江省", "中国"]) ner.add_dictionary("ORG", ["北京大学", "清华大学", "浙江大学", "中华文学社"]) ner.add_dictionary("WORK", ["呐喊", "彷徨", "骆驼祥子", "再别康桥", "家", "子夜"]) # 正则模式 ner.add_pattern(r'\d{4}年', "TIME") ner.add_pattern(r'\d{1,2}月\d{1,2}日', "TIME") ner.add_pattern(r'《[^》]+》', "WORK") # 前缀模式 ner.add_prefix("LOC", ["省", "市", "区", "县", "镇"]) ner.add_prefix("ORG", ["大学", "学院", "公司", "集团"]) # 测试 test_texts = [ "鲁迅出生于浙江省绍兴,1923年出版了《呐喊》。", "老舍在北京大学任教,创作了骆驼祥子和茶馆。", "徐志摩是海宁人,1931年11月19日因飞机失事遇难。", "巴金原名李尧棠,出生于四川省成都市。", ] for text in test_texts: results = ner.recognize(text) print(f"原文: {text}") print(ner.format_results(text, results)) print()
原文: 鲁迅出生于浙江省绍兴,1923年出版了《呐喊》。 [PER] 鲁迅 (位置: 0-2) [LOC] 浙江省 (位置: 4-7) [LOC] 绍兴 (位置: 7-9) [TIME] 1923年 (位置: 10-15) [WORK] 《呐喊》 (位置: 18-22) 原文: 老舍在北京大学任教,创作了骆驼祥子和茶馆。 [PER] 老舍 (位置: 0-2) [ORG] 北京大学 (位置: 3-7) 原文: 徐志摩是海宁人,1931年11月19日因飞机失事遇难。 [PER] 徐志摩 (位置: 0-3) [LOC] 海宁 (位置: 4-6) [TIME] 1931年 (位置: 8-13) [TIME] 11月19日 (位置: 13-18) 原文: 巴金原名李尧棠,出生于四川省成都市。 [PER] 巴金 (位置: 0-2) [LOC] 四川省 (位置: 10-13) [LOC] 成都市 (位置: 13-16)

🧠 基于序列标注的NER

BIO标注方案是NER最常用的序列标注方法:

标签含义示例
B-PER人名起始鲁/B-PER
I-PER人名内部迅/I-PER
B-LOC地名起始绍/B-LOC
I-LOC地名内部兴/I-LOC
B-ORG组织起始北/B-ORG
O非实体出/O 生/O
class BIOTagger: """BIO序列标注与解码""" def encode(self, text, entities): """将文本和实体标注转换为BIO标签序列""" tags = ["O"] * len(text) for entity, etype, start, end in entities: tags[start] = f"B-{etype}" for i in range(start + 1, end): tags[i] = f"I-{etype}" return list(zip(text, tags)) def decode(self, tagged_sequence): """从BIO标签序列解码出实体""" entities = [] current_entity = "" current_type = None start = None for i, (char, tag) in enumerate(tagged_sequence): if tag.startswith("B-"): if current_entity: entities.append((current_entity, current_type, start, i)) current_type = tag[2:] current_entity = char start = i elif tag.startswith("I-") and current_type == tag[2]: current_entity += char else: if current_entity: entities.append((current_entity, current_type, start, i)) current_entity = "" current_type = None start = None if current_entity: entities.append((current_entity, current_type, start, len(tagged_sequence))) return entities # BIO标注演示 tagger = BIOTagger() text = "鲁迅出生于绍兴" entities = [("鲁迅", "PER", 0, 2), ("绍兴", "LOC", 4, 6)] tagged = tagger.encode(text, entities) print("=== BIO标注 ===") for char, tag in tagged: print(f" {char} → {tag}") # 解码验证 decoded = tagger.decode(tagged) print(" === BIO解码 ===") for entity, etype, s, e in decoded: print(f" [{etype}] {entity} ({s}-{e})")
=== BIO标注 === 鲁 → B-PER 迅 → I-PER 出 → O 生 → O 于 → O 绍 → B-LOC 兴 → I-LOC === BIO解码 === [PER] 鲁迅 (0-2) [LOC] 绍兴 (4-6)

📊 NER评估指标

def evaluate_ner(gold, pred): """评估NER结果 Args: gold: 标注正确的实体集合 {(entity, type, start, end)} pred: 预测的实体集合 """ gold_set = set((e, t, s, en) for e, t, s, en in gold) pred_set = set((e, t, s, en) for e, t, s, en in pred) tp = len(gold_set & pred_set) fp = len(pred_set - gold_set) fn = len(gold_set - pred_set) precision = tp / (tp + fp) if tp + fp > 0 else 0 recall = tp / (tp + fn) if tp + fn > 0 else 0 f1 = 2 * precision * recall / (precision + recall) if precision + recall > 0 else 0 return {"Precision": f"{precision:.4f}", "Recall": f"{recall:.4f}", "F1": f"{f1:.4f}"} # 评估示例 gold_entities = [("鲁迅", "PER", 0, 2), ("绍兴", "LOC", 4, 6), ("北京大学", "ORG", 8, 12)] pred_entities = [("鲁迅", "PER", 0, 2), ("绍兴", "LOC", 4, 6), ("北京", "LOC", 8, 10)] print("=== NER评估 ===") metrics = evaluate_ner(gold_entities, pred_entities) for k, v in metrics.items(): print(f" {k}: {v}")
=== NER评估 === Precision: 0.6667 Recall: 0.6667 F1: 0.6667

📝 实战练习

练习1:扩展NER系统

添加产品名、职位等新实体类型的词典和规则,测试识别效果。

练习2:实现简单的CRF特征

为每个字符提取词性、是否数字、是否大写等特征,用于CRF训练。

练习3:评估改进

添加更多词典条目,对比改进前后的P/R/F1。

🏷️

🏆 第6课成就解锁

实体识别工程师

🏷️ 词典NER
📋 BIO标注
📊 P/R/F1
🔍 正则模式