
简介面向自然语言处理与知识图谱方向的学习者和工程师资源以人物关系抽取为任务采用Pipeline方式将流程拆解为分词、实体识别、关系对抽取与关系分类等环节帮助理解关系抽取任务的完整落地方式。压缩包共14个文件以Python脚本4个py、数据文本4个txt、工程配置4个xml及人物关系表1个xlsx为主整体体积仅96KB轻量且便于快速上手。目前已有152人学习下载。资源内包含按8:1:1划分的训练、验证、测试数据其中训练集用于拟合模型验证集用于调整参数测试集用于最终评估同时提供模型训练和预测脚本可直接运行并观察人物关系抽取效果附带预处理和xlsx读取工具便于整理自有数据或调整实验。测试集上F1约为0.6既可作为基线参考也适合在此基础上尝试数据增强、模型替换等优化策略是实践关系抽取任务的实用样例包。1. 人物关系抽取为什么绕不开Pipeline人物关系抽取是信息抽取里最贴近业务的一类任务从新闻、简历、公告、对话记录里找出“谁和谁是夫妻”“谁任职于哪家公司”“谁起诉了谁”。这类需求看起来只是关系分类落到真实语料上却要先解决实体在哪、实体叫什么、一句话里出现多个人名时该判断哪一对。Pipeline方式把这条链路拆成“先做命名实体识别再对实体对做关系分类”两个模块独立训练、独立替换、独立排错。正因为这种可插拔特性它在工业界的落地频率远高于联合抽取尤其适合增量标注和冷启动场景。这篇内容围绕人物关系抽取的最小可运行链路展开覆盖模块拆分逻辑、数据集构造、模型选型与推理参数以及端到端验证的踩坑点。2. 先拆清任务边界人物关系抽取里的Pipeline链路由哪几段组成2.1 Pipeline与联合抽取的取舍可调试性优先关系抽取在学术界的主流方向是联合抽取用一个模型同时输出实体和关系典型做法包括基于标注策略的CasRel和基于生成的UIE。这类方法在公开榜单上通常分数更高因为实体识别和关系分类共享参数实体边界错误不容易被放大。但工程落地时联合模型的问题也很直接训练数据要求严格对齐、标签体系改起来要重训、推理出问题难以定位是实体模块还是关系模块的责任。Pipeline方式把整个任务切成两段流水线。第一段是命名实体识别产出“人物”和“组织”等实体及其在原文中的位置第二段是关系分类接收实体对和上下文文本输出关系类别。每一段都可以单独拆出来测试和调参哪一段效果不好就替换哪一段。常见做法是第一段用预训练模型微调出的Span或序列标注模型第二段用基于[CLS]的分类模型中间通过规则或窗口策略构造候选实体对。这种拆分在人物关系场景里有额外优势。人物实体在语料里往往有高频率别名、称谓和指代比如“王总”“李太太”“其子”NER模块可以把这些统一归并到规范化姓名后再进入关系分类降低关系模型的输入噪音。联合模型很难做到这种中间层干预。2.2 子任务定义人物实体识别要输出哪些字段人物关系抽取对NER模块的要求与通用NER不同。通用NER只需要标注出Person、Org、Location等类型人物关系场景还关注实体在句子中的序号、左右边界、以及规范化后的映射结果。标注格式我建议使用BIO或BIOES的序列标注每个实体记录起点和终点索引便于后续构造实体对。{text: 张伟聘任李强为公司的技术总监。, entities: [{type: PER, start: 0, end: 2, name: 张伟}, {type: PER, start: 4, end: 6, name: 李强}]}start和end索引是字符级别。序列标注模型输出的标签需要后处理转成上述格式因为模型对每个token输出一个标签序列同一个实体在BIO标注下会包含B-PER和多个I-PER标签必须合并成一段连续文本才能拿到完整实体名。这里有个容易疏忽的细节中文以字为粒度做序列标注时实体长度不受限制英文以子词为粒度时start/end应该基于原始文本字符做映射而不是基于tokenizer后的token索引。2.3 子任务定义人物关系分类的目标与输出关系分类模块的目标是把“实体对上下文”映射到一个预定义的关系集合。人物关系数据集中常见的关系包括父母、配偶、子女、就职公司、教育经历、合作伙伴、竞争对手等。与通用关系抽取不同人物关系分类需要显式处理没关系的实体对也就是负例。一个句子中出现三个人名两两组合就有3个候选对其中只有少数存在目标关系其余全是NA。关系分类模型在Pipeline里通常用单句分类的方式实现。输入文本是“原始句子头实体尾实体”拼接后的序列输出是该候选对的关系类别。类别数量一般在10到50之间取决于业务定义。人物关系场景的关系标签建议控制在20个以内超过这个数量标注一致性和模型可区分度都会明显下降。3. 用最小代码搭出可运行的人物关系抽取Pipeline3.1 模块与框架选型为什么用序列标注加句对分类搭建一个能跑通的人物关系抽取Pipeline不需要复杂架构。常见做法是NER模块用BertForTokenClassification关系分类模块用BertForSequenceClassification。两个模型共享同一个预训练模型底座但独立微调推理时按顺序调用。选择这个组合的理由有三点。一是两个模块都是HuggingFace生态中开箱即用的任务头写代码的复杂度低二是人物关系场景里NER和关系分类的数据规模通常都不大预训练模型微调已经足够三是拆分后可以单独给NER换模型而不影响关系分类。如果人名主要由规则字典覆盖、句式非常固定也可以用字典匹配做第一段直接跳过一个模型。这种简化在人物关系抽取中反而常见因为很多语料里人名靠“先生”“女士”“董事长”等称谓就能识别。但从可回退的角度看序列标注模型更稳妥因为它能处理未见过的名字。3.2 第一步并非训练而是实体对候选构造实际操作过的人会发现Pipeline里最影响最终效果的不是单独某个模型的精度而是从句子文本到实体候选对这一步的构造逻辑。它决定关系分类模块能见到哪些样本也决定推理时的计算量。def build_candidate_pairs(sentence, entities, same_typePER): pairs [] for i in range(len(entities)): head entities[i] for j in range(i 1, len(entities)): tail entities[j] if head[type] ! tail[type]: continue if head[start] tail[start] and head[end] tail[end]: continue if tail[start] head[end]: continue pairs.append({ sentence: sentence, head: head[name], tail: tail[name], head_span: (head[start], head[end]), tail_span: (tail[start], tail[end]), }) return pairs这段代码只构造同类型实体对也就是人物和人物之间的关系。限制同类型是为了控制候选数量因为人物关系抽取里关心的大多是Person与Person之间的关系。如果业务还包含“就职公司”则需要把Person和Org也纳入候选组合此时同类型限制要去掉改成允许跨类型的组合。候选对的生成位置在NER之后、关系分类之前。训练阶段用标注好的关系数据构造正负样本推理阶段对NER模块输出的全部实体做两两组合然后全部送入关系分类模块。这里有一个计算膨胀问题一个含10个人名的实体列表会产生45个候选对关系分类模块要对每一个候选对做一次模型推理。推理时的一个常见优化是按句子批量处理而不是按候选对单个送入模型。3.3 关系分类模块的推理代码与输入拼接关系分类模型接收的输入需要把实体本身和上下文一起编码。使用BertForSequenceClassification时文本模板我一般用输入序列 [CLS] 张伟聘任李强为公司的技术总监 [SEP] 张伟 [SEP] 李强 [SEP]把句子、头实体、尾实体分隔开后输入模型模型通过[CLS]位置的输出做分类。模板并非唯一有些实现直接把实体插入原文并加特殊标记比如在实体两侧加[*]和[/]标记。两种方式都有效区别在于后者把实体标记信息融入token序列模型能感知实体边界前者信息更清晰模板更统一。人物关系抽取中建议使用带实体标记的方式因为人名较短模型容易忽略它的边界信息。from transformers import AutoTokenizer, AutoModelForSequenceClassification tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) model AutoModelForSequenceClassification.from_pretrained(./relation_model) rel2id {NA: 0, 配偶: 1, 就职公司: 2, 父母: 3, 子女: 4} id2rel {v: k for k, v in rel2id.items()} def predict_relation(sentence, head, tail, max_len128): text sentence [SEP] head [SEP] tail inputs tokenizer(text, max_lengthmax_len, truncationTrue, return_tensorspt) logits model(**inputs).logits pred_id logits.argmax(dim-1).item() return id2rel[pred_id]这段推理代码里text拼接直接用[SEP]字符。各家预训练模型对分隔符的处理方式一致所以这个模板可以沿用。max_len设128是人物关系场景的常用值因为多数人物关系出现在一个句子里不需要很长的上下文窗口。如果语料里存在跨句关系则需要把上文或者下文拼接进句子此时max_len建议加到256或512同时用滑动窗口截断。模型输出的关系类别需要后处理映射回人际关系定义。要说明的是人物关系抽取里的NA类占绝大多数推理阈值如果只取argmax会出现所有候选对都有关系的问题后文会单独说明阈值调整。模型训练时则需要保证负例占比不是压倒性的一般控制在3:1到5:1之间否则模型会对NA过拟合。3.4 NER与关系分类之间的错误传导控制Pipeline方式最常被质疑的一点是错误沿流水线传导。NER漏掉实体后续关系分类永远看不到这个候选对NER实体边界多出一个字关系分类的输入就失真。人物关系场景里这个问题的严重程度和人物名表达方式直接相关中文人名多为2到4个字漏边界的影响相对可控英文人名由多个token组成实体边界错误对关系分类的影响更明显。控制错误传导从两个层面入手。第一个层面是给NER模块容错空间推理时把得分阈值调低一些让NER多输出候选实体再在关系分类阶段用分类置信度过滤。这样牺牲少量precision换回recall整体F1往往更好。第二个层面是给关系分类模块加“虚拟实体”特征在拼接输入时不只给实体名还给实体类型标签比如“PER”后缀让模型感知实体类别降低边界偏移对分类的影响。我一般在NER后接一个简单校正规则把连续出现的同名实体合并把包含称谓词的实体截断到纯姓名部分。这些规则放在Pipeline中间层既能提高候选对质量也不会增加计算成本。4. 训练数据构造与参数调整人物关系抽取的工程细节4.1 冷启动没有标注数据时先构造远程标注数据人物关系抽取在全新业务上往往没有现成标注数据。常见做法是用远程监督方式冷启动把人物关系知识库里的实体对与语料文本做字符串匹配能同时匹配到头部和尾部实体的句子自动标注为该关系匹配不到关系的实体对标注为NA。这个方法在OpenIE和远程监督关系抽取研究里被广泛使用在人物关系场景下效果尤其好因为人名具有较高的辨别性不容易和关系词混淆。远程标注数据的噪音来源集中在一点句子中同时出现两个人名不等于两人存在目标关系。比如“张三出席了李四主持的会议”知识库里张三和李四是大学同学远程监督会把这句话标为大学同学关系但句子里根本没有表达这层含义。缓解方法是用触发词过滤。trigger_words {配偶: [妻子, 丈夫, 结婚, 夫妻], 父母: [父亲, 母亲, 爸爸, 妈妈], 就职公司: [任职, 担任, 加入, 工作于]} def weak_label(sentence, head, tail, relation): flags [w for w in trigger_words.get(relation, []) if w in sentence] return len(flags) 0这段过滤逻辑把候选句子里必须包含关系触发词作为硬条件。没有触发词的句子即便实体匹配也标为NA减少假正例对训练集造成的污染。触发词的作用是约束远程监督的召回率宁可少一些训练样本也要保证样本标签的基本正确。4.2 关系分类训练时的类别权重设置远程监督构造的数据天然类别不平衡NA类样本数量通常是正例的10倍以上。直接把全部样本送入训练模型会学到输出NA因为整体loss已经被NA主导。训练时处理不平衡有两个有效手段一是限制训练样本比例二是给loss加权重。实际操作时我常用按比例采样的方式把负例控制在正例的5倍以内再在loss上给正例加1.5到2的权重。权重设置有一个参数顺序问题先做负例下采样再加类别权重。如果只做下采样正例量少模型欠拟合如果只加权重但负例仍然占90%以上权重放大效果不明显。先采样、后加权重的方式在人物关系小数据集上通常能得到更平衡的P/R表现不容易出现“模型什么都说是”或“什么都不说”的两极结果。4.3 训练参数参考表人物关系场景的数据集规模一般在几千到几万条。训练参数按这个规模设定参数推荐值说明batch_size16 或 32显存充足时用32BN在16的Cased模型上表现稳定learning_rate2e-5 到 5e-5微调分类头建议偏小避免破坏预训练参数max_seq_len128单句人物关系足够跨句场景改512num_epochs3 到 5数据量大时3轮远程监督数据也需要不超过5轮warmup_ratio0.1学习率预热比例避免前几步震荡weight_decay0.01正则项防止实体名过拟合lr是微调中最重要的参数。人物关系文本里人名和关系词分布比较集中lr过高会让模型记住人名组合而非关系特征换一组人名效果大幅下降。从2e-5起步如果验证集loss不降降到1e-5再试。warmup的作用是在前几步用较小的学习率收敛到平稳区域对远程监督数据尤其有用因为数据噪音集中在早期阶段。4.4 推理阶段置信度阈值和NA类处理推理阶段关系分类模型输出的logits不能直接当作置信度。正确做法是对logits做softmax后再用阈值判断。import torch import torch.nn.functional as F def predict_with_threshold(sentence, head, tail, threshold0.6): text sentence [SEP] head [SEP] tail inputs tokenizer(text, max_length128, truncationTrue, return_tensorspt) logits model(**inputs).logits probs F.softmax(logits, dim-1) max_prob, pred_id torch.max(probs, dim-1) if max_prob.item() threshold: return NA return id2rel[pred_id.item()]阈值0.6是经验起点。人物关系类别数在10个左右时0.6能过滤掉大部分低置信度的噪声预测。如果业务更关注召回把阈值降到0.4如果更关注precision升到0.75。这个参数值得在验证集上做网格搜索是最简单有效的调优手段。推理性能方面一个常见的优化是batch推理。把句子、头实体、尾实体分别组成batch送入模型而不是for循环逐个推理吞吐量能提升3到5倍。人物关系抽取在新闻语料上做全量抽取时这种优化是必要的。5. 人物关系抽取Pipeline的端到端验证方法与收尾技巧5.1 分阶段评估而不是只盯整体F1端到端验证人物关系抽取Pipeline时不能只看最终的关系F1。Pipeline的最终准确率是NER准确率和关系分类准确率的乘积关系体现NER错一个实体关系分类再好也无法输出正确结果。所以验证时先单独看每段的指标再组合定位问题。分阶段验证我一般分三步走。先取200条句子人工记录NER模块的实体识别是否正确统计实体F1然后固定这200条句子的实体标注为正确值把正确的实体对送入关系分类模型统计关系分类F1最后把NER输出和关系分类串起来统计端到端F1。对比第二步和第三步的差距就能判断瓶颈在哪一段。# 伪代码比较黄金实体与NER预测实体的指标差异 gold_pairs load_gold_pairs(test_data) ner_pairs run_ner_pipeline(test_data) rel_gold run_relation_model(gold_pairs) rel_ner run_relation_model(ner_pairs) fmt EPS: NER配对F1{:.3f}, 黄金实体关系F1{:.3f}, 端到端F1{:.3f} print(fmt.format(entity_pair_f1(ner_pairs, gold_pairs), relation_f1(rel_gold), relation_f1(rel_ner)))验证的核心结论是如果黄金实体关系F1远高于端到端F1说明NER是瓶颈优先补NER数据如果两者接近但都不高说明关系分类模块需要调整数据或参数。这个定位逻辑同样适用于联合抽取模型只是联合模型没有中间接口可以注入黄金实体对比起来更困难。5.2 多义词与别名在人物关系场景里的处理人物关系抽取里经常遇到同一人物在文本中多次以不同方式出现例如“张伟”“张总”“张伟先生”指向同一个人。实体归一化是Pipeline里容易被忽略的模块它放在NER之后、关系分类之前。常见做法是维护一个别名表把称谓和全名映射到同一个规范ID再对实体对做去重。这种去重不仅提升关系分类的准确率还能显著降低候选对数量。推理时如果“张伟”和“张伟先生”同时被识别为两个实体它们之间会产生一个徒劳的分类计算。合并后再构造候选对计算量减少的同时也避免了重复关系被输出。5.3 跨句人物关系抽取的一个实用技巧人物关系抽取的模型大多按单句设计但很多人物关系跨越标点符号例如“张三昨天公布了婚讯。新娘是李四两人相识多年”。单句切分后“婚讯”和“新娘”落在不同句子里单独看任何一句都无法判断关系。对这类文本常见做法是在句子切分时不按句号切整段而是按段落为单位用滑动窗口保留前后两句的上下文。关系分类的输入从单句扩展为“当前句前一句后一句”实体位置标记仍然定位在原文中。这样改动不需要重新训练NER关系分类模型的输入里也只是增加了一部分文本。这个窗口策略适合人物关系抽取的原因在于人物关系在交际语境里经常通过指代表述跨句出现时才需要上下文。“前一句当前句”的组合能覆盖大多数情况保留后一句用于处理倒装和插入语。窗口太大会引入旁人的实体名反而干扰分类。把窗口和置信度阈值叠加使用是人物关系Pipeline落地时收益最明显的两个参数改法。不需要换模型不需要加数据先调这两个点大部分场景的F1能提升一到两个点。本文还有配套的精品资源点击获取