1. 从一篇论文标题说起:为什么异构图表征适合做抽取式摘要
第一次看到“Heterogeneous Graph Neural Networks for Extractive Document Summarization”这个标题时,我的直觉是:终于有人把文档摘要这件事从“序列标注”往“图结构建模”上推了一步。抽取式摘要的本质,是从原文里挑出若干句子拼成摘要,听起来简单,但真正做过的人都知道,难点从来不在“挑”,而在“判断哪句该挑”。
传统的抽取式方法大致经历了几代演进。最早是基于规则和统计特征,比如句子位置、长度、是否包含关键词、和标题的相似度等,靠人工设计特征喂给分类器。后来深度学习上来,Seq2Seq、BERT句分类、句子对打分这些方案陆续出现,效果确实比统计特征好了一大截。但它们有一个共同的隐含假设:文档是一个线性序列,句子与句子之间的关系主要通过位置邻近或注意力机制隐式捕捉。
问题在于,真实文档并不是一条直线。一个段落里的句子可能和三个段落之外的某个句子构成论证关系;一个术语的定义句可能和它在文末的总结句形成呼应;不同章节之间可能存在对照、因果、例证等复杂关系。这些关系如果只用序列模型去学,要么被距离稀释,要么被注意力机制的噪声淹没。
异构图的思路正好切中这个痛点。所谓“异构”,是指图中存在多种类型的节点和多种类型的边。放到摘要任务里,节点可以是句子、词、主题、段落标记等,边可以是“句子-词共现”“句子-句子相似”“词-词共现”“句子-主题归属”等。不同类型的节点和边携带不同的语义信息,GNN在消息传递时能分别处理这些异质关系,从而让每个句子的表示不仅包含自身内容,还融合了它在整个文档关系网络中的位置和角色。
这个标题里的“Extractive Document Summarization”明确了任务边界:不生成新句子,只做选择。这意味着模型最终要对每个句子输出一个分数或标签,决定是否入选摘要。而异构图神经网络在这里扮演的角色,是一个更强的句子编码器和关系推理器。它解决的核心问题是:如何让句子表示同时编码局部上下文、全局主题结构和跨句语义关联。
适合读这篇内容的人,我认为有三类。第一类是做NLP应用开发的同学,想了解图神经网络在文本任务里的落地方式;第二类是对摘要系统有实际需求的产品或工程人员,比如做新闻聚合、报告生成、会议纪要提炼;第三类是对异构图神经网络感兴趣,但还没找到合适文本场景练手的学习者。下面我会从设计思路、核心细节、实操流程和踩坑经验四个层面,把这个方案拆开讲清楚。
2. 整体设计思路:异构图怎么建、GNN怎么跑、摘要怎么选
2.1 为什么是异构图,而不是同构图或序列模型
同构图里所有节点和边都是同一类型,比如只把句子当节点、句子相似度当边。这种做法能捕捉句子间关系,但丢失了词级别的信息,也无法区分“相似”和“因果”这类不同性质的边。序列模型则完全依赖位置顺序,对长距离依赖的处理能力有限。
异构图的好处在于,它允许我们同时建模多种语义单元和多种关系。举个生活化的类比:一个班级里,学生是节点,但学生之间有关系(同桌、同宿舍、同社团),学生和老师之间也有关系(授课、指导),学生和课程之间还有关系(选修、必修)。如果只建“学生-学生”图,你只能看到社交结构;加上“学生-老师”“学生-课程”之后,你才能理解一个学生的完整画像。摘要任务同理,句子不是孤立的,它和词、主题、其他句子都有联系,异构图能把这些联系分门别类地保留下来。
2.2 节点类型与边类型的选型逻辑
在抽取式摘要的异构图里,我通常会设计以下几类节点:
- 句子节点:这是最终要被打分的对象,也是图中最重要的节点类型。
- 词节点:通常取文档中的关键词或高频实词,用来建立句子之间的语义桥梁。
- 主题节点:通过主题模型或聚类得到的文档主题,帮助句子在全局层面找到归属。
- 文档节点(可选):如果做多文档摘要,可以加入文档节点来区分来源。
边类型则包括:
- 句子-词边:句子包含某个词,权重可以用TF-IDF或词频。
- 句子-句子边:基于余弦相似度、共享词比例或位置邻近度构建。
- 词-词边:基于共现窗口或语义相似度。
- 句子-主题边:句子属于某个主题的概率。
- 词-主题边:词对主题的贡献度。
这样设计的理由是:句子-词边提供内容层面的 grounding,句子-句子边提供结构层面的关联,主题节点提供全局语义锚点。三类信息互补,缺一不可。如果只保留句子-句子边,模型容易在局部相似句之间打转;如果只保留句子-词边,又缺乏句子间的直接比较。
2.3 GNN消息传递在摘要任务中的具体含义
GNN的核心操作是消息传递:每个节点从邻居节点收集信息,更新自己的表示。在异构图里,不同类型的边对应不同的消息传递函数。比如句子节点从词节点收集信息时,用的是“内容包含”关系的权重;从其他句子节点收集信息时,用的是“语义相似”关系的权重;从主题节点收集信息时,用的是“主题归属”关系的权重。
经过多层消息传递后,每个句子节点的表示就融合了:它自己说了什么(词)、它和哪些句子像(句子-句子)、它属于哪个主题(主题)。最终用一个分类器或回归器对这个表示打分,分数高的句子入选摘要。
这里有一个关键设计选择:是让所有边类型共享同一个GNN层,还是每种边类型单独一层再融合?实践中,分开处理再融合的效果通常更好,因为不同关系的语义空间差异很大,强行共享参数会导致信息混淆。但分开处理会增加参数量和计算量,需要根据数据规模做权衡。
2.4 与预训练语言模型的结合方式
现在做摘要,几乎不可能绕开预训练语言模型。BERT、RoBERTa这类模型在句子编码上已经很强,异构图GNN的价值在于它能在预训练表示之上,进一步注入文档结构信息。常见的结合方式有两种:
第一种是“先编码后建图”:用BERT对每个句子单独编码,得到句向量,然后用这些句向量构建句子-句子边,同时用BERT的token表示构建词节点,再跑GNN。这种方式计算效率高,因为BERT只需要跑一遍。
第二种是“联合训练”:把GNN和BERT端到端一起训练,让GNN的梯度回传到BERT。这种方式理论上效果更好,但显存和训练时间成本都高很多,小数据集上容易过拟合。
我个人的经验是,如果数据量在几万条以下,先用第一种方式做原型验证,等流程跑通、效果有 baseline 之后,再考虑联合训练。不要一上来就端到端,否则调试成本会让你怀疑人生。
3. 核心细节解析:从图构建到训练策略的实操要点
3.1 句子节点表示的初始化方法
句子节点的初始表示质量,直接决定GNN能不能学好。常见做法有三种:
- 平均词向量:把句子中所有词的预训练词向量取平均。简单快速,但对长句和复杂句的表示能力有限。
- BERT句向量:用BERT的[CLS] token表示或对token表示做池化。效果好,但计算成本高。
- TF-IDF加权词向量:用TF-IDF权重对词向量加权平均,能突出关键词的作用。
我通常会用BERT句向量作为主方案,同时保留TF-IDF加权词向量作为轻量级备选。如果GPU资源紧张,或者文档数量很大,TF-IDF加权词向量配合GNN也能拿到不错的效果。实测下来,在CNN/DailyMail这类新闻摘要数据集上,BERT句向量比TF-IDF加权词向量在ROUGE-1上大概高2到3个点,但训练时间多出三到四倍。
3.2 边权重的计算与归一化
边权重决定了消息传递时邻居信息的重要程度。句子-词边的权重可以用TF-IDF值,句子-句子边的权重可以用余弦相似度,词-词边的权重可以用PMI(点互信息)。这里有一个容易忽略的细节:不同边类型的权重尺度可能差异很大,比如TF-IDF值可能在0到10之间,余弦相似度在0到1之间。如果不做归一化,GNN在聚合时会被大尺度的边主导。
我的做法是,对每种边类型的权重分别做min-max归一化或softmax归一化,让它们落在同一量级。另外,对于句子-句子边,我通常会设置一个相似度阈值(比如0.3),低于阈值的边直接丢弃,避免图中出现大量弱连接噪声。这个阈值需要根据具体数据集调,新闻类文档句子间相似度普遍偏低,阈值可以设0.2左右;学术论文句子间相似度偏高,阈值可以设0.4以上。
3.3 异构图神经网络层的设计选择
异构图GNN的实现方式主要有两类:基于元路径的方法和基于关系感知的方法。元路径方法需要预先定义“句子-词-句子”“句子-主题-句子”这样的路径,然后沿路径做聚合。关系感知方法则直接对每种边类型分别做聚合,再融合。
在摘要任务里,我更倾向于关系感知方法,因为元路径的定义依赖领域知识,换一个数据集可能就要重新设计路径。关系感知方法更通用,扩展性更好。具体实现时,可以用RGCN(关系图卷积网络)或HAN(异构图注意力网络)的思路。RGCN对每种关系用独立的权重矩阵,HAN则用注意力机制自动学习不同关系的重要性。
如果计算资源允许,HAN通常效果更好,因为它能自适应地调整不同边类型的贡献。但HAN的注意力计算复杂度较高,在大图上容易成为瓶颈。RGCN实现简单,训练稳定,适合作为第一版方案。
3.4 句子打分与摘要选择策略
GNN输出每个句子的表示后,需要一个打分模块。最简单的是接一个线性层加sigmoid,输出0到1之间的分数,然后用阈值或Top-K选择句子。但这样做有一个问题:摘要选择是一个组合优化问题,单独给每个句子打分,可能会选出冗余度很高的句子组合。
更合理的做法是在打分之后加入冗余度惩罚。比如用MMR(最大边际相关性)策略:每次选择分数最高且与已选句子相似度最低的句子。或者训练一个句子对分类器,判断两个句子是否应该同时入选。我在实际项目里发现,GNN打分加MMR重排的组合,比纯Top-K在ROUGE-L上能提升1到2个点,而且人工看摘要的连贯性也更好。
另外,摘要长度控制也很关键。通常用预算约束:摘要总词数不超过某个阈值(比如100词),在这个约束下最大化总分数。这可以用贪心算法近似求解,也可以用动态规划做精确求解。贪心算法速度快,效果通常够用。
4. 实操过程:从数据预处理到模型评估的完整链路
4.1 数据预处理与图构建流程
假设我们拿到的是一批新闻文档,每篇文档有若干句子和对应的参考摘要。第一步是分句和分词。分句用NLTK或spaCy的句子分割器,分词用预训练模型对应的tokenizer。这里要注意,分句质量直接影响后续图构建,如果分句把两个完整句子切成一个,句子节点的表示就会混入噪声。
第二步是构建词表。我通常只保留文档中出现频率在前30%的实词,去掉停用词和标点。词表太大会导致词节点过多,图变得稀疏且计算量大;词表太小又会丢失关键信息。一个经验值是每篇文档保留50到100个词节点。
第三步是构建边。句子-词边根据词是否出现在句子中建立,权重用TF-IDF。句子-句子边用句向量的余弦相似度,超过阈值的建立边。词-词边用共现窗口(比如窗口大小为5)内的PMI值。主题节点可以用LDA或BERTopic对文档聚类得到,句子-主题边用句子属于该主题的概率。
第四步是把所有节点和边组装成异构图。这里推荐用DGL或PyG这样的图深度学习框架,它们对异构图的支持比较完善。DGL的heterograph接口可以方便地定义多种节点和边类型,PyG的HeteroData也类似。
4.2 模型搭建与关键参数设置
模型主体由三部分组成:句子编码器、异构图GNN、句子打分器。句子编码器用预训练BERT,输出每个句子的向量表示。GNN部分我用两层RGCN,每层对每种边类型做一次图卷积,然后把不同边类型的结果拼接或相加。隐藏层维度设256,dropout设0.3。打分器是一个两层MLP,输出标量分数。
训练时用二元交叉熵损失,标签是句子是否在参考摘要中。这里有一个类别不平衡问题:参考摘要通常只包含原文10%到20%的句子,正负样本比例悬殊。我的做法是对正样本做加权,权重设为负样本数除以正样本数,或者用focal loss。实测下来,加权交叉熵比focal loss更稳定,调参也更简单。
优化器用AdamW,学习率设2e-5(如果联合训练BERT)或1e-3(如果只训练GNN)。batch size根据显存调整,通常设4到8篇文档。训练轮数设10到20轮,用验证集ROUGE分数做早停。
4.3 训练过程中的监控与调优
训练时我重点监控三个指标:训练损失、验证集ROUGE-1/2/L、以及摘要平均长度。如果训练损失下降但验证ROUGE不升,说明过拟合,需要增加dropout或减少GNN层数。如果验证ROUGE波动很大,可能是学习率太高或batch size太小。
还有一个容易被忽略的指标是图稀疏度。如果图中边太少,GNN退化成MLP;如果边太多,消息传递会被噪声淹没。我通常会在训练前统计一下平均每个句子节点的邻居数,控制在10到30之间比较合理。如果邻居数太少,可以降低相似度阈值或增加词节点;如果太多,可以提高阈值或减少词节点。
4.4 评估方法与结果解读
抽取式摘要的标准评估指标是ROUGE。ROUGE-1看unigram重叠,ROUGE-2看bigram重叠,ROUGE-L看最长公共子序列。这三个指标各有侧重,ROUGE-1反映内容覆盖度,ROUGE-2反映流畅度,ROUGE-L反映句子级结构。
解读结果时要注意,ROUGE分数高不代表摘要一定好。有时候模型选出的句子和参考摘要词重叠高,但顺序混乱或逻辑跳跃,人工读起来并不连贯。所以我通常会在ROUGE之外,做一次人工抽查,看摘要是否覆盖了原文的主要事件、是否有多余的重复信息、句子之间的衔接是否自然。
另外,和baseline对比时,要确保用的是同一个预处理流程和同一个评估脚本。我见过不少项目,自己实现的ROUGE和官方脚本差好几个点,最后发现是分词或大小写处理不一致。建议直接用rouge-score或pyrouge这样的标准库,不要自己手写。
5. 常见问题与排查技巧实录
5.1 图构建阶段的典型问题
问题一:句子-句子边太多,图变得稠密。原因通常是相似度阈值设得太低,或者句向量质量差导致所有句子都相似。排查方法是画一下句子相似度的分布直方图,如果大部分值集中在0.5以上,说明句向量区分度不够。解决办法是换更好的句编码器,或者改用TF-IDF余弦相似度。
问题二:词节点太多,显存爆炸。如果每篇文档保留几百个词节点,异构图会非常大。解决办法是限制词表大小,只保留TF-IDF最高的前N个词,或者用文档频率过滤掉太罕见的词。
问题三:主题节点质量差。LDA在短文本上效果通常不好,BERTopic更稳但计算慢。如果主题节点质量差,可以考虑去掉主题节点,只用句子和词两类节点,效果未必差很多。
5.2 训练阶段的典型问题
问题一:损失不下降。先检查图构建是否正确,比如边权重是否全为零、节点特征是否全为相同值。然后检查学习率是否太大或太小。如果用的是RGCN,检查关系权重矩阵是否初始化正常。
问题二:验证ROUGE先升后降。典型过拟合。增加dropout、减少GNN层数、加L2正则、或者冻结BERT只训练GNN。如果数据量很小,可以考虑用数据增强,比如同义词替换或句子顺序打乱。
问题三:不同随机种子结果差异大。说明模型不稳定。可以尝试固定随机种子、增加batch size、或者用模型集成(跑多个种子取平均)。我在实际项目里通常跑3到5个种子,取验证集上最好的模型做最终评估。
5.3 推理阶段的典型问题
问题一:摘要句子顺序混乱。GNN打分不包含位置信息,选出的句子可能按分数排序而不是按原文顺序。解决办法是在输出时按原文位置重新排序,或者在打分时加入位置特征。
问题二:摘要冗余度高。多个句子表达相同意思。用MMR重排,或者在训练时加入冗余度惩罚项。我通常用MMR,简单有效,不需要改模型结构。
问题三:长文档推理慢。如果文档有几百个句子,异构图会很大,GNN推理时间线性增长。可以考虑对句子做预筛选,先用一个轻量级模型筛掉明显不重要的句子,再对剩余句子建图跑GNN。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 损失不下降 | 图构建错误、学习率不当 | 检查边权重和节点特征 | 修正图构建、调学习率 |
| 验证ROUGE下降 | 过拟合 | 对比训练和验证损失 | 加正则、减层数、冻结BERT |
| 摘要冗余高 | 缺少冗余惩罚 | 人工检查摘要句子相似度 | 加MMR重排或冗余损失 |
| 推理速度慢 | 图太大 | 统计节点和边数量 | 预筛选句子、减小词表 |
| 结果不稳定 | 随机性大 | 多跑几个种子 | 固定种子、模型集成 |
6. 一些实操心得和后续扩展方向
这个方案我在几个内部项目里跑过,效果最好的一次是在一篇长报告摘要任务上,ROUGE-1比BERT句分类baseline高了4个点,ROUGE-L高了3个点。但也不是没有代价,训练时间大概是baseline的三倍,推理时间大概是两倍。所以如果对延迟要求很高,或者数据量特别大,需要做取舍。
有一个小技巧我觉得挺有用:在构建句子-句子边时,除了语义相似度,还可以加入位置邻近度作为边权重的一部分。具体来说,两个句子在原文中距离越近,边权重越高。这样GNN在聚合时会偏向于邻近句子,符合摘要任务中局部连贯性的需求。实测下来,这个改动在ROUGE-1上能提升0.5到1个点,而且实现成本很低。
后续如果想继续扩展,有几个方向可以考虑。一是加入篇章结构信息,比如用句子在段落中的位置、段落类型(引言、方法、结论)作为节点特征。二是尝试动态图构建,让模型自己学习边权重而不是用预定义相似度。三是把抽取式和生成式结合,先用异构图GNN选出关键句子,再用生成模型做改写和压缩,这样既能保证内容覆盖,又能提升流畅度。
最后分享一个调试经验:异构图GNN的代码复杂度比普通序列模型高不少,出bug的时候很难定位。我的做法是先用一个极小的数据集(比如5篇文档)跑通全流程,确认图构建、消息传递、打分、评估每个环节都正确,再扩展到全量数据。这个习惯帮我省了很多时间,推荐你也试试。