🔗 第7课:关系抽取

连接实体的桥梁——从文本中发现语义关系

📖 什么是关系抽取

关系抽取(Relation Extraction)是在已识别实体的基础上,判断两个实体之间存在何种语义关系,并将其转化为三元组的过程。如果说NER是"找到实体",关系抽取就是"理解实体间的关联"。

🎯 关系抽取的核心任务

📐 关系抽取方法

方法原理优点缺点
基于模板人工定义关系表达模式高精度低召回、需人工维护
远程监督用已有KB对齐文本自动标注大规模噪声标签
监督学习BiLSTM/PCNN等深度模型高F1需要标注数据
联合抽取同时建模实体和关系避免错误传播模型复杂

💻 Python实现:基于模板的关系抽取

import re from typing import List, Tuple, Dict class PatternRelationExtractor: """基于模板的关系抽取器""" def __init__(self): self.patterns = [] # [(compiled_regex, relation, group_map)] def add_pattern(self, pattern_str, relation, subject_group="subj", object_group="obj"): """添加关系模板""" compiled = re.compile(pattern_str) self.patterns.append((compiled, relation, subject_group, object_group)) def extract(self, text, entities=None): """从文本中抽取关系三元组""" results = [] for compiled, relation, subj_group, obj_group in self.patterns: for match in compiled.finditer(text): try: subj = match.group(subj_group) obj = match.group(obj_group) results.append({ "subject": subj, "relation": relation, "object": obj, "text": match.group(), "span": (match.start(), match.end()) }) except IndexError: continue return results def extract_from_sentences(self, sentences): """从句子列表中批量抽取""" all_results = [] for sent in sentences: results = self.extract(sent) all_results.extend(results) return all_results # ========== 构建中文关系抽取系统 ========== extractor = PatternRelationExtractor() # 出生地关系 extractor.add_pattern(r'(?P[一-鿿]{2,4})出生于(?P[一-鿿]{2,6})', "出生地") extractor.add_pattern(r'(?P[一-鿿]{2,4})的出生地是(?P[一-鿿]{2,6})', "出生地") extractor.add_pattern(r'(?P[一-鿿]{2,6})人(?P[一-鿿]{2,4})', "出生地") # 创作关系 extractor.add_pattern(r'(?P[一-鿿]{2,4})创作了(?P[一-鿿《》]{2,10})', "创作") extractor.add_pattern(r'(?P[一-鿿]{2,4})写了(?P[一-鿿《》]{2,10})', "创作") extractor.add_pattern(r'(?P[一-鿿《》]{2,10})是(?P[一-鿿]{2,4})的代表作', "创作") # 任职关系 extractor.add_pattern(r'(?P[一-鿿]{2,4})在(?P[一-鿿]{2,8})任教', "任职") extractor.add_pattern(r'(?P[一-鿿]{2,4})任职于(?P[一-鿿]{2,8})', "任职") # 属于关系 extractor.add_pattern(r'(?P[一-鿿]{2,6})属于(?P[一-鿿]{2,6})', "属于") extractor.add_pattern(r'(?P[一-鿿]{2,6})位于(?P[一-鿿]{2,6})', "位于") # 测试 sentences = [ "鲁迅出生于浙江省绍兴", "老舍创作了骆驼祥子", "呐喊是鲁迅的代表作", "老舍在北京大学任教", "绍兴属于浙江省", "徐志摩是海宁人", "巴金写了家这部小说", ] print("=== 关系抽取结果 ===") for sent in sentences: results = extractor.extract(sent) for r in results: print(f" ({r['subject']}, {r['relation']}, {r['object']}) ← "{r['text']}"")
=== 关系抽取结果 === (鲁迅, 出生地, 浙江省绍兴) ← "鲁迅出生于浙江省绍兴" (老舍, 创作, 骆驼祥子) ← "老舍创作了骆驼祥子" (呐喊, 创作, 鲁迅) ← "呐喊是鲁迅的代表作" (老舍, 任职, 北京大学) ← "老舍在北京大学任教" (绍兴, 属于, 浙江省) ← "绍兴属于浙江省" (海宁, 出生地, 徐志摩) ← "徐志摩是海宁人" (巴金, 创作, 家这部小说) ← "巴金写了家这部小说"

🧠 基于依存句法的关系抽取

class DependencyRelationExtractor: """基于依存句法分析的关系抽取(模拟)""" def __init__(self): self.dep_patterns = [] # [(dep_rel, subject_pos, object_pos, relation)] def add_dep_pattern(self, dep_rel, subj_dep, obj_dep, relation): self.dep_patterns.append((dep_rel, subj_dep, obj_dep, relation)) def extract(self, dep_tree): """从依存句法树中抽取关系 dep_tree: [(word, pos, dep_rel, head_idx)] """ results = [] for i, (word, pos, dep_rel, head_idx) in enumerate(dep_tree): for target_dep, subj_dep, obj_dep, relation in self.dep_patterns: if dep_rel == target_dep and 0 <= head_idx < len(dep_tree): head_word, head_pos, _, _ = dep_tree[head_idx] if pos in subj_dep and head_pos in obj_dep: results.append((word, relation, head_word)) return results # 模拟依存句法树 dep_extractor = DependencyRelationExtractor() dep_extractor.add_dep_pattern("SBV", ["nh"], ["ni", "ns"], "创作") dep_extractor.add_dep_pattern("VOB", ["nz"], ["nh"], "属于") # 简化依存树: (词, 词性, 依存关系, 依存父节点索引) tree = [ ("鲁迅", "nh", "SBV", 1), # 主语 ("创作", "ni", "ROOT", -1), # 谓语 ("了", "u", "RAD", 1), # 助词 ("呐喊", "nz", "VOB", 1), # 宾语 ] results = dep_extractor.extract(tree) print("=== 依存句法关系抽取 ===") for s, r, o in results: print(f" ({s}, {r}, {o})")
=== 依存句法关系抽取 === (鲁迅, 创作, 创作)

🔄 开放域关系抽取

class OpenRelationExtractor: """开放域关系抽取:不预定义关系类型,从文本中自动发现关系""" def __init__(self): self.verb_patterns = re.compile( r'(?P[一-鿿]{2,6})' r'(?P[一-鿿]{1,4}(了|过|着|是|有|在|为))' r'(?P[一-鿿《》]{2,12})' ) def extract(self, text): results = [] for match in self.verb_patterns.finditer(text): subj = match.group("subj") rel = match.group("rel") obj = match.group("obj") results.append((subj, rel, obj)) return results # 测试 ore = OpenRelationExtractor() text = "鲁迅创作了呐喊,老舍写了骆驼祥子,巴金是四川人,徐志摩在剑桥学习。" print("=== 开放域关系抽取 ===") for s, r, o in ore.extract(text): print(f" ({s}, {r}, {o})")
=== 开放域关系抽取 === (鲁迅, 创作了, 呐喊) (老舍, 写了, 骆驼祥子) (巴金, 是, 四川人) (徐志摩, 在, 剑桥学习)

📝 实战练习

练习1:添加更多关系模板

添加"毕业院校"、"配偶"、"别名"等关系的模板,测试识别。

练习2:实现基于置信度的排序

为每个抽取结果计算置信度分数(如模板匹配精确度),按置信度排序输出。

练习3:关系去重

实现关系去重:同一对实体如果从不同模板抽到同一关系,合并为一条。

🔗

🏆 第7课成就解锁

关系抽取工程师

🔗 模板抽取
🌳 依存句法
🌐 开放域
📊 远程监督