1. 关系抽取入门从SPO三元组说起第一次接触关系抽取这个概念时我盯着SPO三元组这个术语发呆了半天。后来在实际项目中才明白原来这就是把句子拆解成谁-做了什么-对谁的结构。比如马云创立了阿里巴巴这句话拆解后就是马云创立阿里巴巴这个三元组。这种结构化表示特别有用。我在电商平台做用户评论分析时就靠它快速提取出用户A-投诉-物流延迟、商品B-存在-质量问题这样的关键信息。相比原始文本三元组能让计算机直接理解语义关系这对后续的舆情分析、知识图谱构建都是基础性工作。关系抽取通常跟在命名实体识别NER之后进行。就像玩拼图先找到所有碎片实体再确定碎片之间的连接方式关系。这里有个容易踩坑的地方实体识别错了关系抽取肯定跑偏。我有次处理医疗文本把糖尿病患者的胰岛素剂量中的糖尿病患者错误识别为两个实体导致后续关系抽取完全乱套。2. 经典方法演进从规则到深度学习2.1 基于规则的方法简单但有效刚入行时我觉得写规则太低级总想直接用深度学习。直到接手了一个金融合同解析项目才发现基于规则的方法在特定场景下真香。我们针对借贷合同设计了这样的规则模板如果出现借款方和贷款方实体 且中间包含向...借款模式 则提取关系为(借款方, 借贷, 贷款方)这种方法的优势是精准可控我在银行项目里用规则模板准确率能达到92%以上。但缺点也很明显——新领域要重写规则有次遇到网络小说中的奇葩表达借了马爸爸的花呗规则就失效了。2.2 监督学习分类器实战当数据量足够时监督学习会更灵活。我最常用的pipeline模式是这样的# 特征工程示例 def extract_features(entity1, entity2, text): features { between_words: text[entity1.end:entity2.start], before_entity1: text[:entity1.start].split()[-3:], entity_types: (entity1.type, entity2.type) } return features # 使用sklearn训练分类器 from sklearn.ensemble import RandomForestClassifier clf RandomForestClassifier() clf.fit(train_features, train_labels)在电商评论分析中我用这种方法实现了用户-评价-商品的关系分类。但要注意特征设计——有次忘了考虑否定词不喜欢被错误分类成正向关系闹了笑话。2.3 半监督学习小数据的大智慧当标注数据有限时远程监督Distant Supervision是个不错的选择。我曾在上市公司公告分析中这样操作从已有知识图谱获取(公司, 收购, 公司)样本在公告文本中自动标注包含这些公司对的句子训练分类器但这里有个大坑不是所有共现的实体都存在关系。有次系统误把腾讯和阿里都在杭州设分公司标注成了竞争关系后来加了注意力机制才解决。3. 工业级实现基于LTP的实战代码3.1 环境搭建避坑指南第一次用LTP时我在安装上就栽了跟头。这里分享几个实用技巧# 推荐使用清华镜像源 pip install pyltp -i https://pypi.tuna.tsinghua.edu.cn/simple # 模型文件要放在指定路径 LTP_DIR ./ltp_data_v3.4.0 # 模型目录结构要保持完整Windows用户特别注意如果遇到dll加载错误可能是VC运行库缺失安装VS2015的VC_redist就能解决。3.2 核心代码逐行解析关系抽取的核心流程我封装成了这样class TripleExtractor: def __init__(self): self.parser LtpParser() # 初始化LTP分析器 def ruler1(self, words, postags, roles_dict, role_index): # 基于语义角色的抽取规则 v words[role_index] if A0 in roles_dict[role_index] and A1 in roles_dict[role_index]: s extract_entity(roles_dict[role_index][A0]) o extract_entity(roles_dict[role_index][A1]) return [s, v, o] if s and o else None def ruler2(self, words, postags, child_dict_list): # 基于依存分析的备用规则 svos [] for i, postag in enumerate(postags): if postag v: # 找到动词 child_dict child_dict_list[i] if SBV in child_dict and VOB in child_dict: s words[child_dict[SBV][0]] o words[child_dict[VOB][0]] svos.append([s, words[i], o]) return svos实际运行时会先尝试语义角色标注ruler1失败再回退到依存分析ruler2。这种混合策略在我处理的政务文档中准确率能达到85%左右。3.3 性能优化技巧处理长文档时原始LTP可能会很慢。我总结了几点优化经验批量处理句子而非单句对的、了等停用词提前过滤缓存模型实例避免重复加载# 批量处理示例 def batch_parse(parser, sentences): results [] for batch in chunked(sentences, 100): # 每批100句 results.extend([parser.parse(s) for s in batch]) return results4. 工业实践中的挑战与对策4.1 领域适应难题在医疗领域迁移时我们的模型遇到了严重水土不服。比如注射胰岛素被错误拆分成医生注射胰岛素和患者接受注射。后来通过领域自适应解决了收集少量医疗文本标注数据在基础模型上fine-tune添加医疗实体识别模块4.2 嵌套关系处理马云辞去阿里巴巴董事局主席职务这种嵌套关系特别棘手。我们最终采用span-level标注加上层叠式预测先识别马云和阿里巴巴再识别董事局主席最后建立马云-辞去-董事局主席和董事局主席-属于-阿里巴巴4.3 实时性要求在舆情监控场景中我们对速度要求极高。最终方案是# 预加载模型 parser LtpParser() # 异步处理 async def process_text(text): loop asyncio.get_event_loop() return await loop.run_in_executor(None, parser.parse, text)配合消息队列我们的系统现在能实时处理上万条/分钟的新闻数据。