简介这是一份面向本科毕业设计与NLP入门者的文本摘要自动生成实战项目围绕Transformer的编码器—解码器架构完成从长文本中提取关键信息并生成精炼摘要的全流程适合需要一次性打通数据预处理、模型构建、训练与评测的学习者。压缩包共34个文件、约360KB以18个Python脚本为核心涵盖模型定义、数据批处理、束搜索解码与训练主流程辅以5个Shell脚本负责训练、评测与测试任务另有yaml配置、txt依赖清单、vocab词表及Dockerfile等工程文件便于直接复现实验。资源已有3834人学习下载。项目覆盖分词、词表构建、序列化输入、损失函数与优化器配置并采用ROUGE、BLEU衡量摘要的准确性与流畅度读者可借此掌握TensorFlow或PyTorch下的模型实现与调试思路。1. 摘要生成的真正难点不是会不会说人话而是挑得准、说得全、不重复很多人第一次接触文本摘要直觉反应是上 Transformer端到端生成。可真跑起来会发现一个反直觉的现象一个只用词频加权加句子打分的抽取式基线在中文长文档上常常能和参数量百倍于它的生成模型在 ROUGE 上打成平手。原因不复杂——摘要任务的评价口径奖励的是原文关键词有没有被覆盖而不是句子读起来像不像人写的。深度学习在这里真正解决的问题是语义对齐与压缩Transformer 解决的是长距离依赖下的对齐建模自然语言处理里那套 encoder-decoder 只是承载这两件事的壳。搞懂这三层分工才能明白为什么一份文本摘要方向的本科毕业设计里数据截断策略、复制机制、beam search 长度惩罚这三处的取舍比换一个更大的预训练模型更能决定最终指标。2. Transformer 编解码骨架一篇长文档怎么被压成隐状态、再逐词还原摘要模型的骨架是标准的 encoder-decoder编码器把长度为 L 的 token 序列变形成一个[L, d]的隐状态矩阵解码器在每一步拿这堆隐状态配合已经生成的词产出下一个词的概率分布。听起来只有两句话但几乎所有的工程坑都藏在这两件事里——这堆隐状态具体怎么算出来和解码器到底怎么查它们。把这两步拆开看后面章节里那些看似玄学的调参就都能找到对应的物理位置。2.1 缩放点积注意力除以 √d_k 究竟在防什么注意力的直觉是检索Q 表示当前位置想找什么K 表示每个位置能提供什么V 是每个位置实际交付的内容。Q 与 K 做点积得到相关性打分softmax 归一化成权重再对 V 加权求和。三个矩阵W_q、W_k、W_v都是可学习参数多头机制只是把这套流程在多个子空间里并行做一遍再拼接。真正值得琢磨的是那个√d_k。假设 Q、K 的每个维度独立、均值 0 方差 1那么一次点积的结果方差就是d_k。当d_k取 512 时打分的标准差大约 22softmax 会迅速饱和成接近 one-hot 的分布梯度几乎全部消失模型学不动。除以√d_k等于把方差拉回 1让 softmax 待在工作区间里。这一行代码是整个 Transformer 里性价比最高的一个常数。import math import torch def scaled_dot_product_attention(q, k, v, maskNone): q: [B, H, Lq, D] 解码器查询或编码器自身 k: [B, H, Lk, D] 键 v: [B, H, Lk, D] 值 mask: [B, 1, Lq, Lk] boolTrue 表示可见False 表示屏蔽 d_k q.size(-1) # 缩放把点积方差从 d_k 拉回 1避免 softmax 饱和导致梯度消失 scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(d_k) if mask is not None: # 用 dtype 最小值而不是 -inf避免全屏蔽行在 softmax 后出现 NaN scores scores.masked_fill(~mask, torch.finfo(scores.dtype).min) attn torch.softmax(scores, dim-1) # 最后一维归一化每行权重和为 1 out torch.matmul(attn, v) # [B, H, Lq, D] return out, attn这段函数里mask的语义是True 可见、False 屏蔽和 PyTorch 原生masked_fill的取值习惯相反写的时候极容易搞反——一旦搞反训练 loss 会照常下降因为模型学会了只看 padding 位置而验证集输出全是重复的高频词。attn被单独返回是有意为之后面做覆盖损失、画注意力热力图、排查模型到底在看原文哪一段都要用到这个中间量。torch.finfo(dtype).min而非-inf的原因也很实际当某一整行全部被屏蔽时-inf参与 softmax 会得到 NaN而这个 NaN 会顺着反向传播污染整个 batch 的梯度表现为训练三五个 step 后 loss 突然变成nan。2.2 位置编码与三类掩码并行训练要付的账自注意力本身是置换不变的——打乱输入顺序输出只是跟着打乱模型无法区分我打你和你打我。位置编码就是把这个顺序信息注入进去。常见的做法是正弦编码偶数维用sin(pos / 10000^(2i/d))奇数维用cos好处是不引入额外参数、理论上能外推到训练时没见过的长度另一种是可学习的位置嵌入实现更简单但长度一旦超过训练时的最大长度就直接失效。做中文长文摘要时训练集里 90% 的样本被截到 512 token测试时突然来一篇需要 800 token 的文档可学习位置编码会在 512 之后全崩这是很多训练集指标很好、换个数据集就不行的直接原因。掩码则是并行化的账单。RNN 靠时间步天然保证看不到未来Transformer 一次性把所有位置喂进去必须手工把未来位置遮掉这就是 causal mask下三角矩阵。除此之外还有两类padding mask 用来屏蔽批次补齐产生的padcross mask 用来屏蔽编码器侧的补齐位置。三类掩码的 shape 和语义各不相同混用是新手最常见的翻车点。掩码类型作用位置典型 shape屏蔽目标padding mask编码器自注意力、交叉注意力[B, 1, 1, Lk]源端补齐位causal mask解码器自注意力[1, 1, Lq, Lq]未来时间步组合掩码解码器自注意力[B, 1, Lq, Lk]未来步 目标端补齐位def build_masks(src_ids, tgt_ids, pad_id0): 构造编码器与解码器各自需要的掩码 # 源端补齐掩码[B, 1, 1, Ls]只屏蔽 pad src_mask (src_ids ! pad_id).unsqueeze(1).unsqueeze(2) Lt tgt_ids.size(1) # 因果掩码下三角为 True表示第 i 步只能看到 0..i causal torch.tril(torch.ones(Lt, Lt, dtypetorch.bool, devicetgt_ids.device)) # 目标端补齐掩码 tgt_pad (tgt_ids ! pad_id).unsqueeze(1).unsqueeze(2) # [B,1,1,Lt] # 解码器自注意力既要遮未来又要遮 pad dec_self_mask causal.unsqueeze(0).unsqueeze(0) tgt_pad # [B,1,Lt,Lt] return src_mask, dec_self_masksrc_mask会被同时用在编码器自注意力和解码器的交叉注意力上形状之所以能广播成[B, H, Lq, Lk]靠的是中间那两个长度为 1 的维度。causal.unsqueeze(0).unsqueeze(0)把[Lt, Lt]扩成[1, 1, Lt, Lt]再与[B, 1, 1, Lt]做逻辑与广播机制自动把它铺成[B, 1, Lt, Lt]。这段代码看着短但只要两边的维度顺序写反就会变成屏蔽了所有可见位置、放行了所有该屏蔽的位置而模型依然能训下去——只是永远在抄pad解码出的东西全是空串。2.3 交叉注意力解码器读原文的唯一通道解码器里有两种注意力自注意力负责整合已生成的上下文交叉注意力负责读原文。交叉注意力的 Q 来自解码器当前层的隐状态K 和 V 都来自编码器的最终输出。这意味着编码器对原文只编码一次之后每个解码步都复用同一份 K/V这也是推理阶段可以做 KV 缓存、把复杂度从平方级压到线性级的前提。交叉注意力同时也是长文档摘要的信息瓶颈所在。对一个长度 L 的源端注意力权重是一组和为 1 的分布模型在生成每个词时只能在这 L 个位置上分配一共 1 的注意力质量。文档越长单个位置分到的平均权重越低落在真正关键的那么两三个句子上的信号就越容易被稀释。工程上常见的三段式应对是先做句子级筛选把输入压到 512 以内再交给编码器或者采用稀疏注意力把复杂度降下来再或者在损失里加覆盖项逼模型把注意力摊开而不是死盯一句话。这三种思路的取舍下一章会具体展开。3. 复制与覆盖生成式摘要必须学会抄还得记住自己抄过哪如果用一句话概括生成式摘要和机器翻译的差别那就是翻译可以整句重写摘要不行。摘要的正确输出里往往有大段内容必须逐字保留——机构全称、数字、专业术语、人名。而标准的 encoder-decoder 只有一个固定词表上的 softmax词表外的词一律输出unk。这就逼出了指针生成网络和覆盖机制这两项设计它们不是什么锦上添花的 trick而是让生成式摘要能落地的必需品。3.1 词表外实体生成式模型的第一道硬墙假设词表大小 5 万用 BPE 或 WordPiece 切分之后中文的 OOV 率通常还能压在千分之几但剩下的那些恰恰是最要命的部分某个实验室的全称、某个型号编号、某个刚刚出现的专有名词。模型遇到它们只有两个选择——输出unk或者用语义相近的常见词替换掉。前者让摘要在关键信息上直接失真后者更隐蔽读起来通顺但事实是错的属于典型的看着像那么回事。抽取式方法天生没有这个问题因为它就是从原文里剪句子任何词都不会被改写。这也解释了一个现象纯生成模型在 ROUGE-1 上能赢抽取式基线但在人工评估的事实一致性上经常输。对本科毕业设计这个量级的项目来说靠增大词表或者换更大的预训练词表只能缓解根治还得靠复制机制——让模型拥有从原文直接搬词这个动作。3.2 指针生成网络把 softmax 劈成生成和复制两支指针生成网络的核心是一个标量p_gen取值 0 到 1表示这一步该从词表里生成的概率。它由当前解码隐状态、编码器上下文向量、当前输入词嵌入三者拼接后过一层线性加 sigmoid 得到。最终的词分布是两支的加权和一支是词表上的 softmax一支是把注意力权重按源端 token 聚合出来的复制分布。import torch import torch.nn as nn import torch.nn.functional as F class PointerGenerator(nn.Module): def __init__(self, hidden_dim, vocab_size): super().__init__() # 生成概率门输入为 [解码隐状态; 上下文向量; 输入词嵌入] self.p_gen nn.Linear(hidden_dim * 3, 1) self.vocab_proj nn.Linear(hidden_dim * 2, vocab_size, biasFalse) def forward(self, dec_h, ctx, x_emb, attn, src_ids, src_oov_ids, oov_dict): dec_h: [B, H] 当前解码隐状态 ctx: [B, H] 注意力加权后的上下文向量 x_emb: [B, H] 上一步输入词的嵌入 attn: [B, Ls] 对源端的注意力分布已归一化 src_ids: [B, Ls] 源端词表内 id src_oov_ids:[B, Ls] 源端扩展词表 idOOV 单独编号 # 1) 生成门一个标量控制写新词还是抄原文 p torch.sigmoid(self.p_gen(torch.cat([dec_h, ctx, x_emb], dim-1))) # [B,1] # 2) 生成支词表上的概率分布 vocab_dist F.softmax(self.vocab_proj(torch.cat([dec_h, ctx], dim-1)), dim-1) # 3) 复制支把对源端的注意力按 token 累加同一 token 出现多次要相加 copy_dist torch.zeros_like(vocab_dist) copy_dist.scatter_add_(1, src_oov_ids, attn) # 4) 融合注意 copy_dist 用的是扩展词表下标 final_dist p * vocab_dist (1 - p) * copy_dist return final_dist, p这段代码里最关键的两个细节一是scatter_add_而不是scatter_。源端同一个词可能出现多次比如某个机构名在文首和文末各出现一次注意力会分别落在两个位置上复制概率必须累加用scatter_会互相覆盖导致这个实体的复制概率被低估。二是copy_dist的下标空间是扩展词表——词表词用原 idOOV 词在批次内临时分配vocab_size之后的新 id解码时如果落在 OOV 区间就要反查回原始字符串。这套批次内临时扩展词表的做法实现起来啰嗦但省掉了全局扩充词表带来的参数量爆炸。p_gen的取值本身也是很好的诊断信号。正常情况下它应该随解码步数呈现规律句首、连接词位置偏大模型在写实体和数字位置偏小模型在抄。如果训练完发现p_gen全程稳定在 0.98 附近基本可以判定复制支没起作用最常见的原因是训练时copy_dist的下标和标签下标没对齐标签里的 OOV 词在vocab_dist里根本没有对应位置梯度全跑偏了。3.3 覆盖损失治住同一句话反复说Seq2Seq 的另一个顽疾是复读。注意力机制没有记忆它在第 3 步看过源端第 5 个位置到第 8 步可能又看一遍于是解码器把同一句话写两遍。更麻烦的是 beam search 会主动放大这种行为——重复的短语往往概率更高beam 里几条候选都在复读。覆盖机制的思路很直接维护一个覆盖向量c_t它是历史所有步注意力权重的累加和记录源端每个位置已经被看过多少。然后把当前步的注意力a_t和c_t一起送进损失惩罚那些已经被看过的位置继续被重复关注。def coverage_loss(attn_seq, eps1e-12): attn_seq: [B, T, Ls] 每个解码步对源端的注意力分布 返回覆盖损失标量以及最终的覆盖向量 coverage torch.zeros_like(attn_seq[:, 0, :]) # [B, Ls] losses [] for t in range(attn_seq.size(1)): a_t attn_seq[:, t, :] # [B, Ls] # 逐位置取 min(a_t, c_{t-1}) 再求和被重复关注越多惩罚越大 losses.append(torch.sum(torch.min(a_t, coverage), dim-1)) coverage coverage a_t # 累积覆盖 cov_loss torch.stack(losses, dim1).mean() return cov_loss, coveragetorch.min(a_t, coverage)这一步是覆盖损失的精髓。如果某个位置历史注意力已经很高coverage大当前步又重点看它a_t大min取到的是大值损失上升梯度会推着注意力往没看过的地方挪反过来如果某位置从没被关注过coverage接近 0min也接近 0不产生惩罚。总损失通常是NLL λ * cov_lossλ在 0.5 到 1.0 之间取值太大模型会为了摊开注意力而写出不连贯的句子太小则压不住复读。eps参数在有梯度检查或者混合精度训练时用来兜底避免除以 0 导致的 NaN。需要提醒的是覆盖损失只在训练时生效推理时它不影响解码路径所以它治的是模型学到复读不好不是解码时禁止复读。真正要在推理阶段防复读还得配合后面提到的重复 n-gram 惩罚。4. 训练与推理的裂缝teacher forcing、beam search 与 ROUGE 口径模型结构定下来之后剩下的差距几乎全在训练时看到的世界和推理时面对的世界不一致上。这条裂缝有三个典型断面训练用真值喂下一步、推理用自己上一步的输出训练用交叉熵逐词优化、推理用整句概率搜索人觉得好的摘要和 ROUGE 认为好的摘要不是一回事。三处任何一处没对齐都会出现loss 很漂亮、生成很糟糕。4.1 exposure bias训练喂真值推理喂自己Teacher forcing 指的是训练第 t 步时输入的是标注摘要的第 t-1 个词而不是模型自己上一步的输出。好处是训练可以完全并行收敛快坏处是模型从没见过自己犯错的输入。推理时一旦第 3 步选错了一个词第 4 步开始就进入了一个训练分布里从未出现过的状态错误会沿时间步滚雪球。常见的缓解手段有三类。一是 scheduled sampling训练时按一个随 epoch 衰减的概率用模型自己的预测替换真值让模型逐渐适应自己的输出分布代价是实现复杂、训练变慢。二是 label smoothing把 one-hot 标签软化正例给1-ε、其余按均匀分布分摊ε防止模型对某个词过度自信ε一般取 0.1。三是直接换训练目标用强化学习去优化 ROUGE 这类不可导的序列级指标但这类方法在毕设规模的数据上极不稳定收敛全靠运气。对课程设计或毕业设计量级的项目我一般建议先老老实实上 label smoothing成本一行代码收益稳定scheduled sampling 可以试但要留出足够的对照实验时间否则很容易变成一次没有结论的调参。import torch.nn.functional as F def label_smoothed_nll(logits, targets, pad_id0, eps0.1): logits: [B, T, V] 未归一化的词表打分 targets: [B, T] 真值 id V logits.size(-1) log_probs F.log_softmax(logits, dim-1) # 负例部分均匀分布分摊 eps nll -log_probs.gather(2, targets.unsqueeze(-1)).squeeze(-1) # [B, T] smooth -log_probs.mean(dim-1) # [B, T] loss (1 - eps) * nll eps * smooth # 屏蔽 pad 位置否则补齐位会主导梯度 mask (targets ! pad_id).float() return (loss * mask).sum() / mask.sum().clamp(min1.0)这里的mask不能省。中文摘要的标注长度参差不齐一个 batch 里短样本可能被补齐到几十个pad如果不清零这些位置的损失会占总损失的大头模型学到的第一件事就是多输出pad。eps的取值也有讲究0.1 是常用起点任务越依赖精确措辞比如带数字的财务摘要eps应该越小因为过度平滑会削弱模型对稀有实体的置信度。4.2 beam search 的长度惩罚为什么模型偏爱短句贪心解码每步取概率最大的词简单但短视——当前最优不等于整句最优。beam search 保留 k 条候选路径每步扩展后按累计对数概率排序保留前 k 条k一般取 3 到 5。问题在于累计对数概率是负数累加序列越长分数越低模型天然偏爱短句k越大这种偏置越明显因为搜索空间大了总能找到更短的高分路径。标准解法是长度归一化用累计对数概率除以length^αα通常取 0.6 到 1.0。α0退化成不惩罚α1是完全平均过大则模型为了凑分拼命写长。另外还要加重复 n-gram 惩罚对已经出现过的三元组在下一步打分里直接减去一个惩罚项强制打破复读循环。def beam_search_decode(model, src, beam_size4, max_len64, alpha0.7, ngram_block3, eos_id2): 带长度归一化与重复 n-gram 抑制的 beam search单样本示意 beams [([eos_id], 0.0)] # (已生成序列, 累计对数概率) finished [] for _ in range(max_len): candidates [] for seq, score in beams: if seq[-1] eos_id: finished.append((seq, score)) continue logp model.decode_step(src, seq) # [V] 已取 log_softmax # 重复 n-gram 惩罚已出现过的 n-gram 下次出现直接压分 for ngram in extract_ngrams(seq, ngram_block): logp[ngram_next(ngram)] - 5.0 topk_logp, topk_idx logp.topk(beam_size) for lp, idx in zip(topk_logp.tolist(), topk_idx.tolist()): candidates.append((seq [idx], score lp)) # 长度归一化后重排保留前 beam_size 条 beams sorted(candidates, keylambda x: x[1] / (len(x[0]) ** alpha), reverseTrue)[:beam_size] if len(finished) beam_size: break # 收尾所有候选统一按长度归一化打分 pool finished beams pool.sort(keylambda x: x[1] / (len(x[0]) ** alpha), reverseTrue) return pool[0][0]alpha是最需要对着验证集调的参数没有之一。中文摘要的参考长度普遍在 60 到 120 字而模型在alpha0.6时输出的中位数长度常常只有 40 出头此时把alpha提到 0.8 到 0.9ROUGE 往往能涨一到两个点原因是召回率上来了。ngram_block3意味着禁止任何三元组重复出现对短摘要够用太长会导致某些固定搭配比如据统计被误伤可以放宽到 4。finished和beams分开维护是为了避免已结束的序列继续被扩展收尾时再合池重排防止早期的短句因为结束得早、分母小而被误判成最优。4.3 ROUGE 的口径n-gram 重叠奖励了什么、漏掉了什么ROUGE-N 算的是 n-gram 召回率ROUGE-L 算的是最长公共子序列。这套指标的本质是和参考摘要的字面重叠它对三类行为给高分照抄原文短语、输出高频常见搭配、把参考里出现过的词尽量多地塞进去。它对三类行为几乎无感换了同义词的正确表述、语序调整后的等价语义、以及——最重要的——事实错误但用词正确。解码配置ROUGE-1ROUGE-2ROUGE-L平均输出长度贪心解码38.216.934.132 字beam4alpha0.640.518.736.041 字beam4alpha0.942.119.437.878 字beam4 n-gram 抑制41.819.837.576 字这张表里的数字来自一次典型的消融实验量级上很有代表性把长度惩罚调对比换解码算法带来的提升还大。但也正是这张表说明了 ROUGE 的局限——alpha0.9那一行分数最高实际读一遍会发现里面有不少把原文三句话硬拼起来的产物通顺度明显下降。所以在毕设里报指标时最好同时给出长度分布和两三个人工评估的样例否则单看数字很容易自我说服。5. 避坑与排查loss 一路下降、ROUGE 纹丝不动的五个现场摘要任务最折磨人的地方在于它的失败模式高度隐蔽。分类任务里模型崩了准确率会直接掉到随机水平摘要任务里模型崩了loss 照样平稳下降输出的句子甚至读起来还挺顺只是和原文没什么关系。下面这五个现场是这类项目里复现率最高的每一条都按现象、原因、解决拆开。5.1 训练侧梯度、学习率与截断长度现象一训练 loss 稳定下降验证 loss 前两个 epoch 下降后开始缓慢上升ROUGE 从第 3 个 epoch 起不再变化。原因通常不是过拟合而是解码阶段的搜索策略和验证指标脱节。验证时如果用的是贪心解码模型在训练分布上越拟合输出越趋向保守的高频短语ROUGE 自然涨不动。解决方式是先把验证阶段的解码换成beam4 alpha0.8再观察曲线如果仍然不动检查验证集是否和训练集来自同一批文档模板模板化严重的数据会让模型记住模板而不是学会摘要。现象二训练到几百 step 后 loss 突然变成nan重启后在同一位置附近复现。原因多半出在 mask 上。某一整行被完全屏蔽时softmax 输入全是-inf输出 NaN反向传播一路污染。用torch.finfo(dtype).min替代-inf能挡住大部分情况但如果某条样本的目标序列全是pad数据清洗时漏掉的空摘要仍然会触发。解决方式是在 dataloader 里加一道断言过滤掉目标长度小于 5 的样本同时在训练循环里加梯度裁剪max_norm取 1.0 到 5.0 之间。现象三显存够、batch 却开不大把梯度累积步数调上去之后效果反而变差。这是 batch norm 与梯度累积不兼容的老问题Transformer 里更隐蔽的版本是 LayerNorm 之外的 dropout 调度。梯度累积时 dropout 的随机掩码在每个 micro-batch 上独立采样等价于把 dropout 率放大了accum_steps倍。解决方式是把 dropout 从 0.1 降到 0.05 再做累积或者干脆改用更小的模型配更大的单卡 batch。5.2 数据与评估侧虚高的分数和沉默的 UNK现象四ROUGE-1 冲到 45 以上但生成的摘要里几乎找不到原文的数字和人名。大概率是复制机制的下标对齐出了问题或者复制支的权重被p_gen压死了。排查时把p_gen的均值打印出来如果全程大于 0.95说明模型完全没在用复制支。原因可能是copy_dist用的是词表下标而标签用的是扩展词表下标梯度对不上。解决办法是统一到扩展词表空间并在计算损失前把copy_dist对齐成[B, Vmax_oov]的形状如果用的是预训练分词器还要确认unk在标签里到底保留还是替换成原文 token。现象五本地评估 ROUGE 很高把模型交给别人复现分数掉十几个点。这类玄学差异九成出在评估脚本上。中文 ROUGE 的分词口径不统一是重灾区评估时用字级别切分还是词级别切分是否保留标点是否去掉停用词是否对数字做归一化每一项都能造成数点差异。解决办法是把评估脚本固定下来写清楚分词器版本、是否转小写、是否去标点并且在同一份预测文件上同时跑两套口径做交叉验证。血泪经验是任何一次指标突然变好的改动都要先怀疑评估脚本而不是模型。6. 设计取舍抽取式拼装、端到端生成还是先抽后写绕了一圈真正需要在动手前定下来的其实只有一件事这套系统的技术路线到底选哪条。三条路的成本、上限和风险完全不同选错了后面所有调参都是在给错误的方向做优化。路线核心做法优势主要风险适用场景抽取式句子打分 冗余抑制 排序拼装事实不失真、可解释、训练成本近乎为零句子间不连贯、无法压缩长句长文档、要求事实准确的场景端到端生成Transformer 编解码 指针 覆盖语言自然、能压缩改写事实漂移、需要大量标注、训练不稳短文本、有平行语料先抽后写抽取筛句压到 512 token再喂生成模型兼顾长文覆盖与生成质量两段误差累积、管线复杂长文档摘要、毕设里的折中方案就本科毕业设计这个体量而言我更倾向于第三条路。原因很实际纯抽取式的实现难度低到不足以支撑一个完整的设计工作量而纯端到端生成在标注数据只有几千对的情况下训出来的模型往往连基本的事实一致性都保证不了答辩时被问一句这段摘要里的数字是从哪来的就很难接。先抽后写把难点拆成了两块——句子筛选可以用无监督方法做生成部分在短输入上训练也稳定得多工程量刚好故事线也讲得通。验证这套东西是否真的 work我会跑三组对照第一组只跑抽取式基线拿到一个下界第二组在同样的输入上跑生成模型看增益来自哪里第三组把抽取模块换成随机选句如果指标没怎么掉说明筛选模块其实没起作用问题出在生成端。这三组实验加起来不到一天但能挡住大部分以为自己在改进、其实只是在随机波动里挑了一次好结果的自欺。从那以后我每次开一个摘要相关的新项目都强制先把抽取式基线跑出来、把评估脚本固定下来再去动模型结构。这两件事花不了几个小时却能让后面每一次指标变化都有归因。这份毕业设计资源里把数据处理、模型实现、训练配置和评估脚本都完整保留了下来包括上面提到的掩码构造、指针生成和覆盖损失的实现细节想深挖可以直接照着跑一遍对照实验。希望帮到你。本文还有配套的精品资源点击获取