📖 什么是关系抽取
关系抽取(Relation Extraction)是在已识别实体的基础上,判断两个实体之间存在何种语义关系,并将其转化为三元组的过程。如果说NER是"找到实体",关系抽取就是"理解实体间的关联"。
🎯 关系抽取的核心任务
- 关系分类:给定两个实体,判断它们的关系类型
- 关系三元组抽取:从文本中同时识别实体和关系
- 远程监督:利用已有知识库自动标注训练数据
📐 关系抽取方法
| 方法 | 原理 | 优点 | 缺点 |
| 基于模板 | 人工定义关系表达模式 | 高精度 | 低召回、需人工维护 |
| 远程监督 | 用已有KB对齐文本自动标注 | 大规模 | 噪声标签 |
| 监督学习 | BiLSTM/PCNN等深度模型 | 高F1 | 需要标注数据 |
| 联合抽取 | 同时建模实体和关系 | 避免错误传播 | 模型复杂 |
💻 Python实现:基于模板的关系抽取
import re
from typing import List, Tuple, Dict
class PatternRelationExtractor:
"""基于模板的关系抽取器"""
def __init__(self):
self.patterns = []
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 = []
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课成就解锁
关系抽取工程师
🔗 模板抽取
🌳 依存句法
🌐 开放域
📊 远程监督