🔄 第9课:共指消解

同一实体的不同面孔——识别文本中的共指关系

📖 什么是共指消解

共指消解(Coreference Resolution)是识别文本中指向同一实体的不同表述,并将它们关联起来的技术。在"鲁迅创作了呐喊。他当时32岁。"中,"他"指代"鲁迅"——这就是共指。

🎯 共指的类型

类型示例说明
代词共指鲁迅→他代词指代实体
名称变体周树人→鲁迅同一实体的不同名称
别名/昵称老舍→舒先生别名、字号等
定指名词鲁迅→那位作家定指描述指代实体
同位语鲁迅,那位伟大的作家同位语修饰

💻 Python实现:共指消解系统

import re from collections import defaultdict class CoreferenceResolver: """共指消解系统""" def __init__(self): self.alias_db = defaultdict(set) # {canonical: {aliases}} self.pronoun_map = {"他": "MASC", "她": "FEM", "它": "NEUT", "其": "ANY"} self.entity_mentions = [] # 提及列表 def add_aliases(self, canonical, aliases): """添加实体别名""" for alias in aliases: self.alias_db[canonical].add(alias) self.alias_db[alias].add(canonical) # 双向 def detect_mentions(self, text, entities): """检测文本中的实体提及""" mentions = [] for entity, etype, start, end in entities: mentions.append({ "text": entity, "type": etype, "start": start, "end": end, "gender": None }) # 检测代词 for match in re.finditer(r'[他她它其]', text): pronoun = match.group() mentions.append({ "text": pronoun, "type": "PRON", "start": match.start(), "end": match.end(), "gender": self.pronoun_map.get(pronoun) }) mentions.sort(key=lambda x: x["start"]) return mentions def resolve(self, mentions, context_entities=None): """执行共指消解,返回共指链""" clusters = [] # [[mention1, mention2, ...], ...] entity_map = {} # {mention_idx: cluster_idx} for i, mention in enumerate(mentions): if mention["type"] == "PRON": # 代词消解:找最近的匹配实体 resolved = False for j in range(i - 1, -1, -1): prev = mentions[j] if prev["type"] != "PRON": # 性别一致性检查 if mention["gender"] == "ANY" or prev.get("gender") is None: if j in entity_map: clusters[entity_map[j]].append(mention) entity_map[i] = entity_map[j] else: clusters.append([prev, mention]) entity_map[j] = len(clusters) - 1 entity_map[i] = entity_map[j] resolved = True break if not resolved: clusters.append([mention]) entity_map[i] = len(clusters) - 1 else: # 命名实体:检查别名数据库 found = False for ci, cluster in enumerate(clusters): for cm in cluster: if cm["type"] != "PRON": # 检查别名关系 if (mention["text"] in self.alias_db and cm["text"] in self.alias_db[mention["text"]]): cluster.append(mention) entity_map[i] = ci found = True break if found: break if not found: clusters.append([mention]) entity_map[i] = len(clusters) - 1 return clusters # ========== 共指消解演示 ========== resolver = CoreferenceResolver() # 添加别名知识 resolver.add_aliases("鲁迅", ["周树人", "鲁迅先生"]) resolver.add_aliases("老舍", ["舒庆春", "舒先生"]) # 模拟NER输出 text1 = "鲁迅创作了呐喊。他当时32岁。周树人也是著名的翻译家。" entities1 = [("鲁迅", "PER", 0, 2), ("呐喊", "WORK", 5, 7), ("周树人", "PER", 18, 21)] mentions1 = resolver.detect_mentions(text1, entities1) print("=== 文本1提及检测 ===") for m in mentions1: print(f" [{m['type']}] {m['text']} ({m['start']}-{m['end']})") clusters1 = resolver.resolve(mentions1) print(" === 共指链 ===") for i, cluster in enumerate(clusters1): names = [m["text"] for m in cluster] print(f" 链{i+1}: {' = '.join(names)}")
=== 文本1提及检测 === [PER] 鲁迅 (0-2) [WORK] 呐喊 (5-7) [PRON] 他 (8-9) [PER] 周树人 (18-21) === 共指链 === 链1: 鲁迅 = 他 链2: 呐喊 链3: 周树人

📊 共指消解评估

def evaluate_coref(gold_clusters, pred_clusters): """MUC评估指标""" def get_links(clusters): links = set() for cluster in clusters: items = frozenset(m["text"] for m in cluster) for a in items: for b in items: if a < b: links.add((a, b)) return links gold_links = get_links(gold_clusters) pred_links = get_links(pred_clusters) tp = len(gold_links & pred_links) fp = len(pred_links - gold_links) fn = len(gold_links - pred_links) p = tp / (tp + fp) if tp + fp > 0 else 0 r = tp / (tp + fn) if tp + fn > 0 else 0 f1 = 2 * p * r / (p + r) if p + r > 0 else 0 return {"P": f"{p:.4f}", "R": f"{r:.4f}", "F1": f"{f1:.4f}"} print("=== 共指消解评估 ===") # 简化评估 gold = [[{"text": "鲁迅"}, {"text": "他"}, {"text": "周树人"}]] pred = [[{"text": "鲁迅"}, {"text": "他"}], [{"text": "周树人"}]] print(evaluate_coref(gold, pred))
=== 共指消解评估 === {'P': '0.5000', 'R': '0.3333', 'F1': '0.4000'}

📝 实战练习

练习1:别名扩展

添加更多别名对(如字号、笔名),验证共指消解效果提升。

练习2:性别一致性

为实体添加性别信息,实现基于性别一致性的代词消解。

练习3:跨句共指

处理跨句子的共指问题:第一句提到"鲁迅",第三句的"他"仍能正确消解。

🔄

🏆 第9课成就解锁

共指消解工程师

🔄 共指检测
🔗 代词消解
📋 别名匹配
📊 MUC评估