资讯详情

异构图表征重塑抽取式摘要:从序列建模到图神经网络实战

📅 2026/10/16 8:39:44 | 华诺云谱 👁 阅读
异构图表征重塑抽取式摘要:从序列建模到图神经网络实战
1. 为什么异构图表征正在重塑抽取式摘要的技术路线抽取式文档摘要这个任务说白了就是从一篇长文档里挑出若干条最能代表全文主旨的原句拼在一起当作摘要输出。它不像生成式摘要那样需要重新组织语言所以天然具备忠实度高、语法稳定、落地成本低这几个优势。过去几年里这个方向的主流做法基本被预训练语言模型加上句子级打分器统治比如把每句话编码成一个向量再做一个二分类或者排序决定这句话留还是不留。但真正在一线做过长文档摘要的人都知道纯序列建模有个绕不开的短板句子和句子之间的关系并不只是前后相邻这么简单。一篇技术报告里第三段可能在解释第一段提出的概念第七段可能在反驳第五段的结论这种跨段落的语义关联用一条链式的序列结构根本表达不出来。你硬要用位置编码去近似模型学到的也只是距离近的大概率相关而不是语义上真正相关。异构图表征这条路线恰好就是冲着这个痛点来的。它把文档拆成不同粒度的节点——词、句子、甚至主题簇——然后用不同类型的边把它们连起来构成一张异构信息网络。所谓异构指的是节点类型不止一种、边的关系也不止一种。比如词到句的属于关系、句到句的语义相似关系、词到词的共现关系这些关系的物理含义完全不同如果混在一起用同一种方式处理信息就被抹平了。我个人的判断是这个方向之所以值得投入核心在于它把文档结构从一个隐式的、靠位置编码猜测的东西变成了一个显式的、可以人为设计的图。你可以根据任务需要决定要不要加篇章结构边、要不要加指代消解边、要不要加关键词共现边。这种可控性是纯序列模型给不了的。这篇文章我会从设计思路、核心细节、实操落地、问题排查四个层面把这条技术路线拆开讲透。适合已经做过基础摘要任务、想进一步优化长文档场景的从业者也适合刚接触图神经网络、想找一个具体落地场景练手的朋友。读完之后你应该能自己搭出一套可跑的异构图表征摘要流程并且知道每一步为什么这么设计。2. 整体方案设计与思路拆解2.1 从序列建模到图表征的核心动机先说清楚一个前提抽取式摘要的本质是一个句子排序加筛选的问题。给定文档 D {s1, s2, ..., sn}目标是学一个打分函数 f(si, D)输出每句话的得分然后按得分选 top-k 句再按原文顺序还原。序列模型的做法是把 D 当成一个长序列用 Transformer 的自注意力去捕捉句子间关系。问题在于自注意力的复杂度是 O(n²)文档一长计算量爆炸不说注意力还会被稀释——一句话要和几十上百句话算相似度真正重要的那几条关系反而被淹没了。图模型换了个思路我不要求每句话和所有句子都发生关系我只在有明确关系的句子之间连边。这样图的边数远小于 n²计算效率高而且每条边都有语义依据。更关键的是图结构天然支持多跳推理——句子 A 和句子 C 没有直接连边但通过句子 B 可以建立联系这种间接关联在序列模型里很难显式建模。异构性则进一步解决了关系类型不同的问题。同构图上所有边一视同仁但现实中词属于句和句相似于句完全是两码事。异构图表征通过元路径和类型特定的聚合让不同关系走不同的信息通道最后再融合。这就是它比同构图更强的地方。2.2 节点与边的类型设计具体到抽取式摘要我一般会设计三类节点词节点文档中的实词通常过滤掉停用词和低频词保留 TF-IDF 排名前 N 的词。句节点文档中的每一句话这是最终要打分的对象。主题节点可选用聚类或者主题模型得到的粗粒度语义簇用来捕捉全局信息。边的关系类型我常用的有这几种边类型连接对象物理含义构建方式词-句边词节点 ↔ 句节点该词出现在该句中TF-IDF 加权句-句边句节点 ↔ 句节点两句语义相似余弦相似度阈值词-词边词节点 ↔ 词节点两词共现滑动窗口共现句-主题边句节点 ↔ 主题节点句子属于该主题聚类归属这里有个经验边不是越多越好。我早期做过一个实验把所有相似度大于 0.1 的句子对都连上结果图变得极其稠密模型反而学不动因为噪声边太多。后来把阈值提到 0.3 到 0.4 之间并且限制每个句子最多连 5 条边效果明显提升。这个阈值没有绝对标准得根据你的文档长度和句子平均长度去调。2.3 为什么选择元路径聚合而不是简单拼接异构图的核心操作是元路径。所谓元路径就是一条规定了节点类型序列的路径模式。比如句-词-句这条元路径表示两个句子通过共享词建立联系句-句-句表示句子通过相似关系传递。为什么不能把不同关系的邻居向量直接拼起来喂给网络因为拼接会让模型无法区分这个邻居是通过哪种关系来的。元路径聚合的做法是先按元路径分组聚合再跨元路径融合。这样每种关系的贡献是独立计算的最后用注意力加权融合模型能自动学到哪种关系对当前任务更重要。我实测下来在摘要任务上句-词-句和句-句这两条元路径的贡献最大主题相关的元路径在长文档上才有明显增益。短文档少于 10 句加主题节点基本是负收益因为聚类本身就不稳定。3. 核心细节解析与实操要点3.1 图构建阶段的参数选择图构建是整个流程的地基这一步做砸了后面模型再强也救不回来。我按节点类型分别说。词节点的筛选不要用全部词。我的做法是先做词形还原去停用词然后按 TF-IDF 排序取前 300 到 500 个词。文档特别长的话可以放宽到 800。词太少句-词-句的桥接能力不足词太多图会变得又大又稀疏训练慢且容易过拟合。句-句边的阈值句子向量我一般用预训练模型取平均池化或者直接用 Sentence-BERT 类的编码器。相似度用余弦。阈值设定有个技巧不要用固定值用分位数。比如取所有句子对相似度的 85 分位作为阈值这样不管文档内部相似度整体偏高还是偏低都能自适应地保留最相关的那批边。边的权重词-句边用 TF-IDF 值句-句边用相似度值词-词边用共现次数归一化。权重在聚合时作为注意力的一部分不要直接乘到特征上否则数值范围差异太大会导致训练不稳定。注意图构建完成后一定要做一次连通性检查。如果图被切成好几个不连通子图说明阈值太高或者词节点太少需要调整。孤立节点要么删掉要么用最近邻强行连一条边。3.2 节点特征的初始化方式节点特征的质量直接决定模型上限。我的经验是句节点用预训练句向量词节点用预训练词向量主题节点用其成员句向量的均值。句向量这块早期我用 TF-IDF 加权词向量平均效果一般。后来换成预训练编码器的句表示提升非常明显。原因是预训练模型捕捉的语义更丰富而 TF-IDF 平均本质上还是词袋丢失了语序和上下文。词向量用静态的或者上下文化的都行。静态的如 GloVe 类训练快上下文化的如 BERT 类效果好但显存吃紧。如果文档平均长度在 500 词以内我建议直接用上下文化词向量因为词节点数量可控。主题节点如果要用聚类数建议设为文档句子数的平方根左右。比如 50 句话聚 7 个主题比较合适。聚类算法用 K-Means 或者层次聚类都行层次聚类不需要预设簇数但慢一些。3.3 异构聚合层的设计细节聚合层是模型的核心。我一般设计两层类型内聚合和类型间融合。类型内聚合就是对每种边类型分别做一次图注意力聚合。公式上大致是对节点 v遍历它的所有类型为 r 的邻居计算注意力权重加权求和得到该类型下的表示。注意力权重的计算用节点自身表示和邻居表示的拼接过一个单层前馈网络再 softmax。类型间融合就是把上一步得到的多个类型表示用另一层注意力加权融合成一个统一表示。这层注意力的 query 用节点自身的原始表示key 和 value 用各类型表示。这样模型能自动学到对这句话来说词邻居更重要还是句邻居更重要。层数不要堆太多。我试过 4 层效果反而比 2 层差因为过平滑问题——节点表示经过多轮聚合后趋于一致区分度下降。2 到 3 层是比较稳的选择。3.4 句子打分的输出层设计聚合完之后每个句节点得到一个最终表示。输出层我一般用两种方案二分类过一个线性层加 sigmoid输出该句被选中的概率。训练时用交叉熵标签来自参考摘要的句子匹配。排序用 pairwise ranking loss让正样本句的得分高于负样本句。二分类简单直接适合数据量大的场景。排序损失更贴近任务本质但需要构造句对训练成本高。我通常先用二分类跑通再考虑换排序损失做精调。标签构造有个坑参考摘要里的句子和原文句子往往不是完全一致需要做软匹配。我的做法是计算参考摘要句和原文句的 ROUGE 或者语义相似度超过阈值的算正样本。硬匹配会漏掉大量语义相同但表述不同的句子。4. 实操过程与核心环节实现4.1 环境准备与依赖说明这套流程我一般用 Python 生态实现核心依赖如下pip install torch torch-geometric pip install transformers sentence-transformers pip install scikit-learn numpy pandas pip install rouge-score pyrougetorch-geometric是图神经网络的主力库它内置了多种聚合算子省得自己手写消息传递。sentence-transformers用来生成句向量rouge-score用来评估。硬件上如果文档平均长度在 30 句以内单张 8G 显存的卡足够。如果要做长文档100 句以上建议 16G 以上因为图规模和句向量维度都会上去。4.2 数据预处理与图构建代码先做文档切句。中文用标点切分英文用 NLTK 或者 spaCy。切完之后过滤掉过短的句子少于 5 个词和纯符号句。import re from sklearn.feature_extraction.text import TfidfVectorizer def split_sentences(text): # 简单按句末标点切分实际项目建议用 spaCy sents re.split(r(?[。.!?])\s*, text) return [s.strip() for s in sents if len(s.strip()) 5] def build_word_sentence_edges(sents, top_k400): vectorizer TfidfVectorizer(max_featurestop_k, stop_wordsenglish) tfidf vectorizer.fit_transform(sents) vocab vectorizer.get_feature_names_out() edges [] for i, sent_vec in enumerate(tfidf): for j in sent_vec.nonzero()[1]: edges.append((fw_{vocab[j]}, fs_{i}, sent_vec[0, j])) return edges, vocab句-句边用句向量算相似度取分位数阈值import numpy as np from sentence_transformers import SentenceTransformer def build_sentence_edges(sents, quantile0.85, max_degree5): model SentenceTransformer(all-MiniLM-L6-v2) emb model.encode(sents, normalize_embeddingsTrue) sim emb emb.T np.fill_diagonal(sim, 0) threshold np.quantile(sim[sim 0], quantile) edges [] for i in range(len(sents)): neighbors np.argsort(sim[i])[::-1][:max_degree] for j in neighbors: if sim[i, j] threshold: edges.append((fs_{i}, fs_{j}, float(sim[i, j]))) return edges, emb这两段代码跑完你就得到了词-句边和句-句边。把它们合并成一张异构图节点特征按前面说的方式初始化。4.3 模型搭建与训练流程模型主体用torch-geometric的HeteroConv封装。核心思路是给每种边类型配一个GATConv然后自动做类型间融合。import torch import torch.nn.functional as F from torch_geometric.nn import HeteroConv, GATConv, Linear class HeteroSummarizer(torch.nn.Module): def __init__(self, hidden_dim, num_layers2, heads4): super().__init__() self.convs torch.nn.ModuleList() for _ in range(num_layers): conv HeteroConv({ (word, in, sentence): GATConv((-1, -1), hidden_dim, headsheads), (sentence, sim, sentence): GATConv((-1, -1), hidden_dim, headsheads), (sentence, in, word): GATConv((-1, -1), hidden_dim, headsheads), }, aggrsum) self.convs.append(conv) self.classifier Linear(hidden_dim, 1) def forward(self, x_dict, edge_index_dict): for conv in self.convs: x_dict conv(x_dict, edge_index_dict) x_dict {k: F.relu(v) for k, v in x_dict.items()} return self.classifier(x_dict[sentence]).squeeze(-1)训练时损失用带类别权重的交叉熵因为正样本句通常只占 15% 到 25%不加权的话模型会倾向于全判负。pos_weight torch.tensor([neg_count / pos_count]) criterion torch.nn.BCEWithLogitsLoss(pos_weightpos_weight) optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5)训练轮数我一般设 30 到 50 轮早停的 patience 设 5。学习率用余弦退火初始 1e-3最低降到 1e-5。4.4 推理与摘要生成推理阶段模型输出每句话的得分按得分降序选 top-k。k 的确定有两种方式固定比例比如选 20% 的句子或者固定数量比如选 3 句。我一般用固定比例因为不同文档长度差异大。选完之后必须按原文顺序重排否则摘要读起来逻辑是乱的。这一步很多人会忘导致明明选对了句子可读性却很差。def generate_summary(model, graph, sents, ratio0.2): model.eval() with torch.no_grad(): scores torch.sigmoid(model(graph.x_dict, graph.edge_index_dict)) k max(1, int(len(sents) * ratio)) top_idx torch.topk(scores, k).indices.sort().values return [sents[i] for i in top_idx]评估用 ROUGE-1、ROUGE-2、ROUGE-L。我实测下来在长文档上这套异构方案比纯序列基线在 ROUGE-L 上能高出 2 到 4 个点短文档上优势不明显甚至可能持平。5. 常见问题与排查技巧实录5.1 训练不收敛或损失震荡这是最常见的坑。我遇到过几次排查下来基本是三个原因。第一图太稠密。边数超过节点数的 10 倍聚合时信息过载。解决办法是提高相似度阈值或者限制每个节点的最大度数。我一般把句节点的最大出度限制在 5 到 8。第二特征尺度差异大。词向量和句向量如果来自不同模型数值范围可能差一个量级。解决办法是在输入模型前对每类节点特征做一次 LayerNorm 或者标准化。第三学习率太高。图神经网络的梯度比普通 MLP 更容易爆炸学习率建议从 5e-4 起步不要一上来就 1e-3。5.2 模型偏向选择短句或长句这是标签分布导致的。如果参考摘要里短句多模型就学会选短句。解决办法是在损失里加一个长度惩罚项或者对长短句分别做样本加权。我的做法是统计正负样本的平均长度如果差异超过 30%就给长度偏离均值远的样本更高权重。5.3 长文档显存溢出文档超过 100 句时句-句边的数量会急剧上升。解决办法有两个一是用稀疏矩阵存储边二是对句-句边做采样每个节点只保留相似度最高的 3 条边。我一般两个一起用显存能降一半以上。5.4 常见问题速查表问题现象可能原因排查方向解决手段损失不下降学习率过高 / 图过稠密打印梯度范数、统计边数降学习率、提高阈值验证集 ROUGE 波动大正负样本失衡统计正样本比例加 pos_weight推理结果顺序乱忘记重排检查输出逻辑按原文索引排序短文档效果差主题节点噪声对比有无主题节点短文档去掉主题节点显存溢出边数过多统计边节点比边采样 稀疏存储提示每次改动图结构后一定要重新跑一遍连通性检查和边数统计。我踩过的最大坑就是改了阈值忘了重新构图结果模型一直在用旧图训练白白浪费了两天。5.5 几个独家避坑经验第一个经验句向量模型的选择比图结构本身更重要。我做过对比同样的图结构换一个更强的句编码器ROUGE 提升比调图参数还大。所以如果效果不理想先别急着改图先换句向量模型试试。第二个经验元路径不要超过三条。我试过五条元路径模型复杂度上去了效果反而降。原因是每条元路径都需要独立的聚合参数参数一多就过拟合。两到三条是甜点区。第三个经验训练集和测试集的图构建参数要一致。有人训练时用 0.85 分位阈值测试时图小就改成固定 0.5结果分布不一致效果崩了。阈值这种超参一旦定了就全程统一。第四个经验ROUGE 高不代表摘要好读。我见过 ROUGE-L 很高但句子之间毫无连贯性的输出。如果下游要给人看建议在选句之后加一个简单的连贯性重排比如用相邻句相似度做一次局部调整。这套异构图表征的摘要方案我从最初跑通到调到一个能用的状态大概花了三周时间其中一半时间耗在图构建的参数调试上。现在回头看最值得投入的其实是句向量质量和边阈值这两个点其他都是锦上添花。如果你刚开始做建议先用小规模数据把整条链路跑通再逐步加元路径和主题节点不要一上来就堆复杂结构。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑