“Open Relation Extraction: Relational Knowledge Transfer from Supervised Data to Unsupervised Data”这个题目,翻译成人话就是——我手里有一批已经标注好的关系抽取数据,另外还有一堆完全没标注的文本,怎么用前面的数据帮后面的文本做关系抽取,尽量少标注甚至零标注就能达到可用水平。开放关系抽取(Open Relation Extraction)这些年热度一直不低,尤其是当你处理的关系类型不是固定的十来种,而是几十上百种、甚至开放变化的时候,传统“预定义关系+分类”的思路根本扛不住。Relational Knowledge Transfer(关系知识迁移)正好是解决这个痛点的关键路径:把监督数据上的关系分类能力,搬运到完全无监督的目标数据上。这篇文章适合所有正在被关系抽取标注成本折磨的从业者,不管你是刚入门的研究生,还是已经在某个垂直领域做信息抽取的工程师,后面的拆解和踩坑记录都能直接复用。
1. 这个项目到底在解决什么问题
1.1 关系抽取在做什么,卡在哪里
关系抽取的任务很直观:给一段文本,抽取出“实体-关系-实体”这样的三元组。比如“某公司收购了某团队”,我们要得到(某公司,收购,某团队)。“某药物可用于治疗高血压”,要得到(某药物,可用于治疗,高血压)。传统做法是定义一套固定的关系类别,然后用分类器判断两个实体之间的关系属于哪一类。这么做的最大问题在于:现实世界的关系是长尾的、开放的,“收购”可以换个说法,“治疗”在不同语境下也有千百种表达,你定义得再全也跟不上语料的变化。
另一个致命瓶颈是标注成本。关系抽取监督数据非常贵,因为标注员不仅要判断关系类型,还要准确标注实体边界,一条高质量样本的标注耗时可能是普通分类任务的五到十倍。于是大家很自然地想到:如果我已经在通用领域花重金标了一批数据,能不能让这份投入用在另一个完全没有标注数据的领域里?这就是题目里“Supervised Data to Unsupervised Data”的核心诉求。
1.2 开放关系的真正难点
开放关系抽取里的“开放”有两层含义。第一层是关系类别开放:模型需要能够识别出训练时没见过的关系,而不是只能从固定候选集里选一个。第二层是场景开放:源数据(监督数据)和目标数据(无监督数据)往往来自不同的领域,关系分布、文本风格、甚至实体类型都存在明显差异。
举个例子,我曾在某个垂直领域的项目里把通用领域的关系抽取模型直接拿过去用,结果惨不忍睹。通用语料里大量出现的“位于、成立于、是”这类关系,在目标领域里基本不出现,大量出现的是领域特有的关系,比如“获批、进入临床、靶向”。模型完全不认识这些,全部判成无关(NA),整个抽取结果等于白做。这个现象本质上就是领域漂移,而且关系类别分布偏移得比实体识别严重得多,因为实体在语言上相对稳定,而关系表达高度依赖上下文和领域术语。
1.3 知识迁移在这里扮演什么角色
关系知识迁移要解决的就是“如何把源域学到的关系知识用于目标域”的问题。它和传统迁移学习不太一样的地方在于:我们不是简单地做一次模型微调,而是要让模型在目标域上没有标注的情况下,依然能产生可靠的关系预测,并且最好还能逐步优化自身。
这个过程通常包含几个技术组件:一是表示层面的对齐,让模型对领域差异不那么敏感;二是标签层面的桥接,利用源域模型给目标域生成伪标注;三是自适应的拒识机制,知道什么时候该输出关系、什么时候该承认“我不知道”。接下来我会把这几个组件逐个拆开讲。
2. 核心技术方案拆解
2.1 整体思路:先做分类器,再把它“推过”领域边界
我们的出发点是一条被验证过很多次的路线:先在监督数据上训练一个关系分类器,然后用它给无监督目标域数据预测关系,选出置信度高的样本作为伪标注,再把这些伪标注并入训练集继续训练。这个循环就是自训练(Self-training),但直接跑很容易崩,所以整个项目真正的工作量都花在“如何让伪标注更可信”和“如何让分类器跨域更稳定”上。
整体架构可以拆成四个模块:
- 源域关系分类器:预训练语言模型+实体感知的分类头,负责给出关系预测和置信度。
- 目标域伪标注生成模块:对无监督数据做实体抽取、候选关系判断,通过置信度阈值和一致性校验筛选可靠样本。
- 跨域表示适配模块:用对抗训练或对比学习让模型在不同领域间保持表征一致性。
- 自适应拒识模块:处理开放类别和领域无关的NA关系,避免噪声样本污染训练。
这四个模块不是孤立跑的,伪标注生成的质量决定了后面模型能不能学到东西,而跨域适配又反过来提升伪标注的可靠性。它们是互相增益的循环关系。
2.2 数据层面的桥接:从远程监督到伪标注
这里要先提一个老方法:远程监督(Distant Supervision)。核心逻辑是用已有知识库对齐无标注文本,如果一句话里有两个实体恰好对应知识库里的一个关系,就自动把这个句子的关系标成知识库里的关系。这种方法能产生大量“免费”标注,但噪声极大,因为句子完全可能只是在偶然的上下文里提到了这两个实体,并不表达知识库里的关系。所以后来大家做了一系列降噪工作,比如多实例学习、注意力机制、强化学习选择正确句子等。
远程监督给我们的启发是:标签信号不一定要靠人工,只要有一个可信的来源,就能大规模生成标注。在这个项目里,可信来源是源域分类器本身。我们用源域分类器去预测目标域文本的关系,然后保留那些模型“很有把握”的预测作为伪标注。和远程监督不同,这里不是用外部知识库对齐,而是用模型自己的一致性来筛选更可靠的高置信样本。
但是要注意一个问题:分类器的Softmax置信度是会虚高的。预训练模型经过微调后,对训练过的类别往往非常自信,哪怕输入是一段它完全不该有把握的文本。所以不能单纯设一个0.9的阈值就完事,还要配合“Dropout多次预测一致性检查”和“正反向抽取一致性检查”。前者是让模型在不同随机失活状态下预测多次,如果结果不稳定就丢弃;后者是换一种方式验证抽取的完整性。这样筛出来的伪标注才真正能用。
2.3 模型层面的适配:让表达不再“认生”
跨域迁移的另一个关键在于表示层。关系分类器在源域学到的语义空间,在目标域上可能发生偏移。同一个词在不同领域里有不同含义,同一类关系在不同领域的文本表达也完全不同。
最常见的适配手段是对抗式领域适配:在特征提取器之上加一个领域判别器,目标是判断当前特征来自源域还是目标域。特征提取器则要尽力骗过判别器。这个博弈过程会让模型学会去掉领域特有的表达,保留下跨域一致的语义特征。对抗训练(Adversarial Training)效果确实不错,但训练过程很不稳定,而且如果判别器训练过头,会把有用信息也一起抹掉。实践中需要详细地调节判别器和特征提取器的对抗损失权重。
另一种更稳健的做法是对比学习。我们可以构建这样的正样本对:语义上表达同一关系的两个句子,比如“某公司收购某团队”和“某团队被某公司收编”,让模型在表示空间里把它们的距离拉近;负样本对则是不同关系或者无关实体对。目标域没有标注,我们没法直接构造出“同关系”的样本,但可以用伪标注和源域数据混合采样,让模型学会“关系语义一致”的表示。对比学习的优势是训练稳定,而且能天然支持开放关系的扩展性。
2.4 迭代自训练的策略选择
自训练是一个迭代过程,流程看起来不复杂:训练模型->预测伪标注->筛选合并->再训练。但里面有几个策略层面的坑。
迭代次数必须控制。自训练的问题是错误累积,第一轮伪标注里混入的噪声,会成为第二轮训练的“真值”,然后越传越错。我们实验下来,一般两到三轮是上限,再多就可能观察不到收益,有些场景甚至掉点。
筛选标准不能只看置信度。还要考虑类别平衡。如果目标域里大部分实体对都是NA关系,你筛出来的伪标注也会以NA为主,模型会被带偏。这时候可以采用类别感知的阈值:对不同候选关系类别分别设定阈值,确保各个关系都有一定量的样本进入训练集。
伪标签的软硬程度也很讲究。传统做法是把伪标签做成one-hot硬标签,但我们发现用软标签(也就是保留分类器的概率分布)训练学生模型效果更稳定,因为软标签携带了类别之间的相似性信息,模型在迁移过程中不容易把一个错误的高置信预测直接刻进脑子里。
3. 实操过程与核心环节实现
3.1 数据准备:源域和目标域的构建细节
我先讲一个可以直接套用的实验配置。源域数据用的是通用领域的关系抽取语料,包含约20个关系类型,一共三万条标注句子。目标域则是某一个垂直领域的无标注文本,大概五十万句,没有任何关系标注,但我们可以用现成的实体识别模型先抽取出候选实体对。
目标域候选实体对的构建是整个流程的第一步,也是非常容易被忽略的一步。关系抽取的输入是“句子+实体对”,模型判断实体对之间是否存在关系以及属于什么关系。所以你得先有候选实体对,才能送入关系分类器。这里要注意,实体识别模型也是从源域迁移过来的,它同样存在域漂移问题。如果实体识别在目标域上漏了一堆实体,后面的关系抽取等于无米下炊。
我的建议是目标域实体识别单独做一个小规模的标注优化:从目标域里随机挑五千句话做轻量标注,只标实体不标关系。这个成本相对可控,但对整个抽取链条的收益非常明显。很多人一上来就想做完全无监督,结果关系分类器怎么调都不行,最后发现是上游实体识别崩了。这个坑我踩过之后长了记性:关系迁移的前提是实体不漏。
3.2 模型搭建与训练流程
模型主体用的是预训练语言模型,加上一个实体感知的池化层。具体做法是:把头实体和尾实体对应的Token表示取出来,和CLS向量拼接,再过一个分类头得到关系概率分布。实体感知很重要,因为关系判断本质上是在问“这两个实体是什么关系”,把实体边界信息显式传给模型,比只靠CLS向量瞎猜要准得多。
学生模型蒸馏的示意代码如下:
class TeacherStudentRE(nn.Module): def __init__(self, base_model, num_relations): super().__init__() self.encoder = base_model self.classifier = nn.Linear(base_model.config.hidden_size * 3, num_relations) def forward(self, input_ids, head_pos, tail_pos, attention_mask=None): outputs = self.encoder(input_ids, attention_mask=attention_mask) token_reps = outputs.last_hidden_state # 取实体首 token 的表示,和CLS拼接 head_rep = token_reps[torch.arange(token_reps.size(0)), head_pos] tail_rep = token_reps[torch.arange(token_reps.size(0)), tail_pos] cls_rep = token_reps[:, 0, :] sentence_rep = torch.cat([cls_rep, head_rep, tail_rep], dim=-1) logits = self.classifier(sentence_rep) return logits训练过程分阶段进行。第一阶段用源域标注数据训练教师模型,使用标准的交叉熵损失。第二阶段固定教师模型参数,让它在目标域上做预测,通过我们前面说的多轮一致性检查筛选出高置信伪标注。第三阶段用“源域真实标注+目标域伪标注”共同训练学生模型,损失函数里对两类样本分配不同权重,一般伪标注的权重低一些,以降低噪声影响。
具体的超参数配置可以参考:预训练模型用百亿以下参数量的轻量级版本,在消费级GPU上就能跑。Batch size设32,学习率设为2e-5,训练轮数控制在3轮。伪标注筛选的置信度阈值建议先设为0.9,再根据第一轮验证集的精度动态调整,通常降到0.85上下能多召回一些样本而不会显著降低精度。
3.3 评估与结果分析:别只盯着准确率
这个项目的评估和传统关系抽取不太一样,因为目标域没有标注,你怎么知道模型挖出来的关系是不是对的?只能人工抽检。抽检比例不需要太高,随机抽取两百条伪标注结果,人工判断关系预测是否合理,然后算一个抽检准确率。这个指标比训练集上的F1更真实。
我曾经用一条流水线跑过一组值得参考的结果:直接用源域模型在目标域上预测,抽检准确率只有51%,几乎等于瞎猜;加上教师模型的多轮一致性筛选后,准确率能提到68%;再叠加对抗域适配和软标签蒸馏,最后稳定在75%到80%之间。注意这个数字不是单次实验的什么神奇结果,而是多次跑出来的典型水平,但它说明了每个技术栈的增量价值。
还有一个更系统的评估方法:在目标域里手工标注一小批测试集,比如五百条,专门用来评测迁移效果。虽然成本不大,但能给你一个可靠的自检基准。这属于把好钢用在刀刃上,不是偷懒。
4. 常见问题与排查指南
4.1 伪标签噪声太大,越训越差
这是自训练最经典的问题。现象是第一轮模型还能看,第二轮迭代后准确率不但没提升,反而掉了三到五个点。原因就是错误伪标注进入了训练集并不断被强化。
排查流程这样走:先把每一轮筛选出的伪标注做人工抽检,统计噪声比例。如果噪声比例已经超过15%,就要考虑放宽进入条件而不是继续迭代。进一步,我建议对比一下“置信度筛选后的伪标签分布”和“源域训练集的真实分布”,如果某个关系的伪标签数量和源域完全不成比例,就要检查是否受领域偏差干扰。最后,确认你是否用了软标签而不是硬标签。很多硬标签噪声看起来区别不大,但对熵的影响很大,软标签相当于给错误留了修正余地。
4.2 目标域关系类别和源域差异太大
如果目标域里有一半以上的关系是源域里根本没有的,那么无论怎么迁移适配,分类器都不可能凭空学会这些新关系。这时候要做开放关系发现,而不是硬套迁移。
一种思路是引入“未知关系”检测:模型不仅要输出关系概率,还要输出一个置信度,低于某个阈值的归为未知。对未知关系的数据,我们不做伪标注,而是聚类分析,看能不能形成一些一致性较高的候选新类别。这个流程就需要小规模人工介入来给新类别命名。这种情况下,知识传递的目标不再是“准确分类”,而是给人工标注者一个高质量候选池,先圈定目标域里有哪些关系,再标注一份目标域的种子集,作为第二次迁移的监督源。实际情况中,新域的关系发现往往需要人工二次介入,别强求完全自动化。
4.3 置信度阈值到底怎么设
阈值设高了,筛出来的样本太少,数据不够训练;设低了,噪声又按比例增加。这个矛盾伴随整个项目。我的经验是用验证集来标定阈值,而不靠直觉。做法如下:挑选目标域中一千条样本,人工预标注一个低成本的验证集(只需要标关系,不需要标实体),然后画一条“阈值-准确率”曲线。找到准确率开始明显下降的拐点,把阈值定在拐点附近。
另外,不同关系的阈值可以分开设。有些源域关系本身在目标域里很常见,模型对它们的置信度天然偏高;有些稀有关系虽然置信度不高,但你正需要补充这类样本。分开设阈值比一个全局阈值更实用。这个细节在很多开源实现里都看不到,但实际效果影响还挺大。
4.4 模型的崩溃点与失败模式
关系迁移项目里最常见的失败模式是“表面迁移”:模型记住了源域文本的措辞模式,而不是真正学会关系语义。比如源域里“A位于B”出现了很多次,目标域里“B地处A”也表达同样关系,但模型在第二个句子上表现很差。这类问题靠数据量是补不回来的,因为模型只是在硬背短语。
解决方向有两个:一是显式引入句法信息,把序列送入语法依存图编码,让模型关注成分间的结构路径而非表面词汇;二是用对比学习扩增同关系表达的对齐,不需要额外标注就能有用。我建议在标准化迁移流程走通之后,优先投入对比学习部分而不是继续堆标注数据。
5. 实操心得与后续扩展
我个人在这个项目上最大的体会是:“无监督”三个字最容易被误解。它不代表完全不用人工,而是把人工从逐句标注里解放出来,转移到策略设计、过滤规则制定、小规模验证集标注这些更高效的工作上。做过一轮标注的人都知道,三天时间可以标五千条句子的关系,但同样的时间投入在目标域标注上,最终获得的性能提升可能比模型调参还要明显。所以聪明的做法是:用人工精力做设计、定策略,把重复的体力活交给伪标注循环去完成。
还有一条非常实用的经验要在这里分享:在任何一次伪标注筛选迭代中,都要保留一份“绝不进入训练集”的验证样本。这份样本由人工标注,用于每轮迭代后的测试。很多内部系统最终跑崩,不是模型不行,而是缺少一块稳定的试纸。哪怕这块试纸只有三百条,也能让你在迭代早期察觉到模型在走偏,而不是等到上线了才发现整个抽取结果没法用。
至于这个技术后续的扩展方向,结合最近的发展趋势,我觉得有两件事特别值得做。一个是把伪标注和增量学习结合:随着线上语料不断增长,用接近低成本的样本持续刷新分类器,比定期全量重训更实用。另一个是为不同领域分开设置独立的“关系映射表”:每一次迁移不是全校重训,而是多个领域各自维护自己的关系集合,共享底层语义编码器。这个有点像多任务学习的端到端版本,可以让每次迁移成本的边际效应越来越低。
关系知识迁移这条路没有银弹,每个领域、每份数据都有自己的脾气。但把方法论搭对、把评估机制建好,剩下的就是耐心地迭代和调参。希望这篇文章能让你少走几个我走过的弯路,也欢迎你在自己的数据集上按这套流程跑一遍,看看哪里会给你惊喜。