☰
Transformer生成式摘要毕设资源包:训练解码与评估避坑
2026/10/24 4:16:16 网站建设 项目流程

简介:面向自然语言处理方向本科毕业设计的实践项目,围绕深度学习文本摘要自动生成任务,基于 Transformer 编码器—解码器架构实现从长文本到精炼摘要的端到端流程,适合具备 Python 与神经网络基础、需要完成选题落地或复现实验的学生与入门研究者。压缩包共 34 个文件、约 360KB,以 18 个 Python 源文件为核心,涵盖模型定义、数据预处理、批处理与束搜索解码等模块,另配 5 个 shell 脚本负责训练、评测与强化学习训练启动,3 个 txt 与 2 个 yml 提供依赖与容器编排配置,并附词表、README 及 LICENSE 等工程化文件。已有 3781 人学习下载。读者可获得分词建表、序列化输入、损失函数与优化器配置、ROUGE 与 BLEU 指标评测的完整链路代码,以及 TensorFlow 或 PyTorch 模型搭建与调试思路,配套 Docker 与 CI 脚本便于快速复现环境,宜作为毕设参考与动手改进的基线工程。

1. 从一份本科毕设资源包说起:文本摘要自动生成到底能跑到什么程度

如果你拿到的是一个以深度学习、自然语言处理、文本摘要、Transformer 为关键词的本科毕设资源包,先别急着找入口脚本硬跑。它更像一条完整流水线:脏数据清洗、长文档截断、编码器-解码器训练、beam search 解码、ROUGE 与人工抽检,缺一段都会让摘要看起来“能出字”却不能用。资源包适合两类人:要复现毕设链路、准备答辩材料的学生,以及想快速搭一个中文生成式摘要基线的新手。它解决的是从零把摘要任务跑通的工程问题,不是直接交付一个生产级摘要服务。

2. Transformer 摘要任务拆解:为什么本科毕设优先选编码器-解码器而不是从零堆层

摘要任务最容易在一开始选错路线。抽取式摘要把原文句子打分后拼起来,事实性稳、训练成本低,但论文里能展开的创新点偏少;生成式摘要让模型自己组织语言,能体现深度学习、自然语言处理、Transformer 的完整训练链路,代价是事实一致性和重复问题更明显。本科毕设资源包通常走生成式路线,因为输入输出是“文档-参考摘要”对,训练目标明确,评估也能落到 ROUGE 和人工抽检上。真正要避免的是从零堆 Transformer 层:数据规模不够时,随机初始化模型很容易训成“会造句但不会摘要”的黑匣子。常见做法是微调一个预训练编码器-解码器模型,把资源包里的训练脚本当作参数实验台,而不是结构发明台。

2.1 生成式摘要和抽取式摘要的边界:先确定标签长什么样

拿到数据后先看标签,不要先看模型。生成式摘要的标签是人工改写或压缩后的摘要文本,模型需要学习“压缩+改写”;抽取式摘要的标签是原文句子索引或句子掩码,模型学的是“选句”。如果资源包里的摘要大多是原文整句拼接,甚至整段出现在文档里,那它更像抽取式数据,硬套生成式训练会出现 ROUGE 虚高、人工读起来像抄首句的情况。下面这张表是我一般用来做初筛的判断依据,先定任务形态,再决定训练脚本怎么改。

判断项生成式摘要抽取式摘要
标签形态参考摘要文本句子索引或句子打分
训练目标条件生成句子分类/排序
事实风险可能改写错数字和主体基本保留原句
数据要求需要成对摘要需要句子级标注或弱标签
本科毕设呈现Transformer 训练链路完整工程轻,创新点要另找

如果资源包的数据是document和summary两个字段,基本可以按生成式摘要处理;如果summary大面积等于原文首句或首段,先不要急着训练,先做 5.2 里的泄漏检查。生成式摘要不是“越像原文越好”,而是“覆盖关键信息且不歪曲事实”。这一点在答辩时很容易被追问,资源包里如果有数据清洗脚本,优先看它怎么处理摘要长度比例和重复样本。

2.2 自注意力、位置编码与解码约束:模型不是黑匣子

Transformer 摘要模型的核心是编码器-解码器。编码器把输入文档的 token 序列变成上下文表示,自注意力让每个 token 看到全句甚至全文范围,位置编码补上顺序信息;解码器带因果掩码,训练时用教师强制把参考摘要右移一位喂进去,推理时从起始 token 开始自回归。编码器和解码器之间还有交叉注意力,解码器每一步都去编码器输出里找相关片段。长文档的瓶颈也在自注意力:序列越长,注意力矩阵越大,显存和计算量涨得很快,所以资源包里通常会有max_input_length这样的截断参数。

训练阶段看loss,推理阶段看解码策略,这两个阶段不能混。max_input_length决定模型能看到多少原文,设得太短会把结论截掉;max_target_length决定摘要上限,设得太短会让标签被截断,模型学不全;num_beams是 beam search 的宽度,4 是常见起点,太大推理慢且收益有限;length_penalty大于 1 鼓励更长输出,小于 1 鼓励更短;no_repeat_ngram_size用来抑制三连重复。注意这些参数不是越激进越好,重复惩罚开太大也会让句子变得别扭。

2.3 资源包里的模型入口:先把配置跑通再改结构

我一般先把模型路径、tokenizer 路径和长度参数集中到一个配置里,不让它们散落在训练和推理脚本中。下面这段配置是资源包里最常见的入口形态,路径用本地目录,避免依赖外部下载;参数先按小显存能跑的起点设置,再根据验证集调整。

from dataclasses import dataclass @dataclass class SummaryConfig: model_path: str = "local/seq2seq-summary" # 换成资源包内的模型目录 tokenizer_path: str = "local/seq2seq-summary" # 通常与模型目录一致 max_input_length: int = 512 # 输入文档截断长度 max_target_length: int = 128 # 参考摘要截断长度 num_beams: int = 4 # 推理 beam 宽度 length_penalty: float = 1.0 # 大于1偏长,小于1偏短 no_repeat_ngram_size: int = 3 # 抑制三连重复 learning_rate: float = 2e-5 # 微调常见起点 batch_size: int = 4 # 单卡批大小 grad_accum: int = 4 # 梯度累积步数

这段配置的逻辑是把“数据长度”和“解码行为”分开管理。model_path和tokenizer_path必须指向同一套词表和模型,否则推理会出现空串或特殊符号;max_input_length=512是本科毕设常见折中,长文档要先做句子筛选再截断;max_target_length=128对中文摘要通常够用,但如果你的参考摘要普遍超过 150 字,标签会被截断,训练 loss 可能正常但生成不完整;learning_rate=2e-5是微调起点,不是固定答案,数据量小的时候可以降到 1e-5;batch_size=4配合grad_accum=4得到有效 batch 16,小显存也能凑出稳定梯度。

提示:先拿 20 条训练样本做一次过拟合测试。模型如果连这 20 条都压不下去,先查 labels 是否被-100屏蔽过多、tokenizer 是否匹配、学习率是否过大,不要急着换模型结构。

3. 数据清洗与样本构造:把长文档压进 512 token 前的四个决策

摘要数据最容易翻车的地方不是模型,而是标签和截断。原始语料里常见空摘要、摘要比原文还长、摘要整段抄原文、同一文档重复出现、训练集和验证集共享同一篇文档。这些问题不清理,训练 loss 会很好看,验证集生成却像随机拼接。资源包如果有数据清洗脚本,先确认它做了四件事:字段统一、长度过滤、摘要重复检查、按文档划分数据集。下面按“先清洗再编码”的顺序走一遍,代码可以直接改成自己的路径。

3.1 从原始语料到 src/tgt 对:字段统一与脏数据过滤

第一轮清洗不要上复杂模型,先用规则把明显脏样本挡掉。中文文本先做空白归一化,去掉全角空格和多余换行;文档太短没有摘要价值,太长会撑爆显存;摘要太短信息不足,太长可能是标签错误;摘要整段出现在原文里,要先标记为疑似抽取式样本。下面的函数按行读取jsonl,每行包含document和summary两个字段,过滤后返回成对样本。

import json import re import hashlib def norm_text(s: str) -> str: s = re.sub(r"\s+", " ", s).strip() s = s.replace("\u3000", " ") return s def doc_key(text: str) -> str: # 取前500字符做近似去重,避免同一文档换标题后重复进入训练集 return hashlib.md5(text[:500].encode("utf-8")).hexdigest() def load_pairs(path, min_doc=50, max_doc=4000, min_sum=8, max_sum=300): seen, pairs = set(), [] with open(path, encoding="utf-8") as f: for line in f: obj = json.loads(line) doc = norm_text(obj.get("document", "")) summ = norm_text(obj.get("summary", "")) if not doc or not summ: continue if not (min_doc <= len(doc) <= max_doc): continue if not (min_sum <= len(summ) <= max_sum): continue if summ in doc and len(summ) > 25: # 先过滤整段抄原文的样本,短摘要可能是正常引用,不急着杀 continue key = doc_key(doc) if key in seen: continue seen.add(key) pairs.append({"document": doc, "summary": summ}) return pairs

这里的min_doc=50和max_doc=4000是字符粗筛,不是 token 限制;最终还要经过 tokenizer 截断。min_sum=8用来挡掉空摘要和单字标签,max_sum=300防止把段落误当摘要。summ in doc只能抓“整段完全出现在原文”的情况,短摘要引用原文是正常的,所以加了长度条件。doc_key取前 500 字符做哈希,是为了在划分数据集前去掉近重复文档,避免同一篇新闻换个标题就同时进训练集和验证集。真正划分时建议按doc_key分组,而不是按行随机切分。

3.2 截断、滑窗与首尾拼接:别把摘要信号截掉

长文档直接截前 512 个 token,最可能丢的是结尾结论;直接截后 512 个 token,又可能丢背景。滑窗把文档切成多段分别编码,效果更稳,但训练和推理成本翻倍,本科毕设阶段不一定划算。我一般用“首句+中间启发式选句+尾句”的拼接策略:新闻和报告的关键信息常在开头和结尾,中间按句子长度、是否含数字、是否含连接词做粗排,再用 tokenizer 精确截断。下面的pack_context是字符级粗筛,目的是把明显无关的长段先压掉。

def split_sentences(text: str): return [s.strip() for s in re.split(r"(?<=[。!?!?])", text) if s.strip()] def pack_context(text: str, max_chars: int = 900, keep_tail: bool = True) -> str: sents = split_sentences(text) if len(sents) <= 3: return text[:max_chars] head = sents[:1] tail = sents[-1:] if keep_tail else [] mid = sents[1:-1] def score(s: str) -> float: # 简易启发式:含数字、长度适中的句子优先,不是模型打分 num_bonus = 1.0 if re.search(r"\d", s) else 0.0 len_bonus = min(len(s), 80) / 80 return num_bonus + len_bonus mid = sorted(mid, key=score, reverse=True) out, used = [], 0 for s in head + mid + tail: if used + len(s) > max_chars and out: continue out.append(s) used += len(s) return "".join(out)

max_chars=900是字符预算,不是 token 预算,中文一个字大约接近一个 token,但不同 tokenizer 会有差异,所以后面还要用 tokenizer 截断。keep_tail=True保留结尾句,适合新闻和报告;如果你的数据是会议记录,结尾可能是寒暄,可以关掉。这个打分函数很土,但好处是可解释:答辩时能说清楚“按数字和长度做启发式选句”,而不是一句“模型自己学”。注意不要用验证集来调max_chars和打分规则,否则评估会偏乐观。

3.3 用 tokenizer 批量编码并检查标签:padding 与 -100 别写反

编码阶段最常见的错误是labels的 padding 没有屏蔽。生成式摘要的标签也要 padding,但 padding 位置不能参与 loss,否则模型会学习“预测 padding”。常见做法是把labels中等于pad_token_id的位置改成-100,损失函数会自动忽略。下面这段编码函数同时处理输入和标签,输入用动态 padding,标签截断到max_out。

def encode_batch(tokenizer, batch, max_in=512, max_out=128): docs = [b["document"] for b in batch] sums = [b["summary"] for b in batch] enc = tokenizer( docs, truncation=True, max_length=max_in, padding=True, return_tensors="pt" ) lab = tokenizer( sums, truncation=True, max_length=max_out, padding=True, return_tensors="pt" ) labels = lab["input_ids"].clone() labels[labels == tokenizer.pad_token_id] = -100 enc["labels"] = labels return enc

max_in=512和max_out=128要和训练配置保持一致,训练和推理的长度不一致会让生成行为漂移。padding=True表示按 batch 内最长样本补齐,比固定长度更省显存。labels先克隆再改,避免污染 tokenizer 返回的原始张量。如果tokenizer.pad_token_id是None,这段代码会出错,加载 tokenizer 后先检查pad_token_id、eos_token_id和模型配置里的decoder_start_token_id。这三个特殊 token 对不上,推理阶段很容易输出空串。

注意:如果资源包里的数据已经切好训练集和验证集,仍然要检查两个集合有没有同一篇文档。按行随机切分在摘要任务里很容易造成泄漏,尤其是同一事件的多篇报道。

4. 训练脚本与参数:学习率、标签平滑、梯度累积怎么配才不翻车

本科毕设训练脚本的目标不是堆大模型,而是在有限显存下稳定收敛。最常见的翻车组合是:batch_size调大导致显存溢出,于是把max_input_length砍到 128,结果模型看不到关键信息;或者学习率设成 1e-3,前几百步 loss 直接飞掉;再或者只看训练 loss,验证集生成已经重复成复读机。比较稳的做法是先用小 batch 加梯度累积凑有效 batch,学习率从 2e-5 附近试,配合 warmup 和梯度裁剪,验证阶段同时看 loss、ROUGE 和重复率。

4.1 训练循环骨架:把 labels、mask 和梯度累积接对

下面的训练骨架用梯度累积模拟大 batch,每个 micro-batch 反传一次,累积到grad_accum步再更新参数。混合精度只在 CUDA 设备上开,CPU 训练直接关掉。clip_grad_norm_放在优化器更新前,防止梯度爆炸。注意最后不足一个累积步的 batch 要手动 flush,否则尾部梯度会丢。

import torch from torch.utils.data import DataLoader from transformers import AutoModelForSeq2SeqLM, get_linear_schedule_with_warmup def make_loader(dataset, tokenizer, batch_size, max_in, max_out): def collate(batch): enc = tokenizer( [x["document"] for x in batch], truncation=True, max_length=max_in, padding=True, return_tensors="pt" ) lab = tokenizer( [x["summary"] for x in batch], truncation=True, max_length=max_out, padding=True, return_tensors="pt" ) labels = lab["input_ids"].clone() labels[labels == tokenizer.pad_token_id] = -100 enc["labels"] = labels return enc return DataLoader(dataset, batch_size=batch_size, shuffle=True, collate_fn=collate) def train_one_epoch(model, loader, optimizer, scheduler, scaler, device, grad_accum=4): model.train() optimizer.zero_grad() for step, batch in enumerate(loader): batch = {k: v.to(device) for k, v in batch.items()} with torch.cuda.amp.autocast(enabled=(device.type == "cuda")): loss = model(**batch).loss / grad_accum scaler.scale(loss).backward() if (step + 1) % grad_accum == 0: scaler.unscale_(optimizer) torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) scaler.step(optimizer) scaler.update() scheduler.step() optimizer.zero_grad()

loss / grad_accum是为了让累积后的梯度量级接近真实大 batch,不是为了让 loss 数值好看。clip_grad_norm_(..., 1.0)是常见起点,太小会欠拟合,太大会失去裁剪意义。scaler负责混合精度的数值稳定,CPU 上需要换成普通反向传播。调度器用线性 warmup 加线性衰减,warmup 比例一般设 0.05 到 0.1,小数据可以取 0.1。实际写训练脚本时记得补一个“最后一个不完整累积步”的 flush 分支,否则每个 epoch 末尾的样本贡献会被吃掉。

4.2 显存与有效 batch:小卡怎么把 batch 凑上去

显存不够时不要第一反应砍模型层数,先看序列长度和 batch。摘要任务的显存大头在长输入,max_input_length从 512 降到 384 往往比把模型缩小更有效,但降太多会丢结论。下面这张表是我一般会先试的参数区间,不是绝对标准,具体还要看数据长度分布和显存余量。

参数常见起点调整方式主要影响
max_input_length512384/256太短丢上下文,太长显存涨
max_target_length12896/160太短标签被截断
batch_size2~8按显存降动态 padding 下仍受最长样本影响
grad_accum4~8补有效 batch有效 batch 变大后学习率可微调
learning_rate1e-5~5e-5先 2e-5太大 loss 崩,太小收敛慢
warmup_ratio0.05~0.1小数据取 0.1稳定初期更新

有效 batch 等于batch_size * grad_accum。比如batch_size=2、grad_accum=8也能凑到 16,但训练时间更长,BatchNorm 之类的层不受影响,因为 Transformer 主要用 LayerNorm。显存排查时先看最长样本长度,再看是否在推理阶段开了梯度。model.generate外面一定要包torch.no_grad(),否则 beam search 会保留计算图,显存直接翻倍。

4.3 早停与 checkpoint 选择:别只看训练 loss

验证阶段至少记录三个数:验证 loss、ROUGE-L、重复率。训练 loss 一直降不代表模型变好,生成式摘要很容易在训练集上记住模板,验证集却重复输出。checkpoint 我一般保留两个:验证 loss 最低的和 ROUGE-L 最高的,答辩时对比展示,能解释“为什么最终选这个”。早停 patience 设 2 到 3 次验证,连续不提升就停,别把训练轮数拉满。

best = {"val_loss": float("inf"), "step": -1} if val_loss < best["val_loss"] - 1e-4: best.update(val_loss=val_loss, step=global_step) # 这里保存 checkpoint 到本地目录

1e-4是防止验证 loss 微小抖动触发保存,具体阈值可以按数据量调。保存 checkpoint 时把 tokenizer 一起存,不然推理阶段还得回去找词表。验证集生成不要用训练时的 teacher forcing,要走model.generate,否则评估的不是真实推理行为。ROUGE 只作为参考,最终交付前还是要人工抽检。

5. 避坑与排查:摘要模型输出重复、偏题、指标虚高的五个现场

摘要项目的坑大多不在模型结构,而在数据和参数。下面五条是我在复现类似资源包时最常见的翻车现场,每条按“现象、原因、解决”拆开。不要指望一次全避开,先把排查顺序固定下来:先看数据泄漏,再看解码参数,再看学习率,最后看显存和特殊 token。

5.1 输出反复重复同一句:先分清解码重复还是训练数据重复

现象:生成结果出现“的的的”“公司公司公司”或者整句循环,换几条输入都一样。原因通常有两类:训练集里短摘要和重复模板太多,模型学到了“重复最安全”;推理时max_length开得太长,no_repeat_ngram_size没开,beam search 在局部最优里打转。解决分两步,先清洗训练集中重复率过高的摘要,再在推理阶段加约束。下面的生成参数是起点,不是越大越好。

ids = model.generate( **inputs, max_length=128, min_length=20, num_beams=4, no_repeat_ngram_size=3, repetition_penalty=1.1, early_stopping=True )

no_repeat_ngram_size=3表示禁止三连 n-gram 重复,中文摘要里通常够用;repetition_penalty=1.1轻微惩罚已出现 token,开太大句子会变别扭;min_length=20防止空短输出;early_stopping=True在 beam 收敛时提前停。训练侧可以统计每条摘要的 4-gram 最大重复次数,重复特别高的样本先降权或剔除。

5.2 ROUGE 很高但人工读起来像抄首句:查数据泄漏和截断偏差

现象:验证集 ROUGE 很高,生成摘要和原文首句几乎一样,人工一看全是原文拼接。原因可能是标签本身是抽取式句子,或者摘要片段完整出现在输入里,模型学会了“复制开头”;也可能是训练集和验证集共享同一篇文档,评估被泄漏抬高。解决先做泄漏检查,再按文档哈希划分数据集,最后看截断策略是不是只保留了开头。

def leakage_rate(pairs): hit = sum(1 for p in pairs if p["summary"].strip() in p["document"]) return hit / max(len(pairs), 1)

这个比例高不代表全部要删,短摘要引用原文是正常的,但如果大量长摘要整段出现在原文里,就要重新构造标签。划分数据集时按doc_key分组,不要按行随机切。截断策略如果只取前 512 token,模型自然学会抄开头,改成首尾拼接后再看人工抽检结果。

5.3 loss 正常但验证集崩:学习率、warmup 和有效 batch 的三角关系

现象:训练 loss 稳定下降,验证 loss 上下震荡,生成结果时好时坏。原因常见是学习率偏大、warmup 不足、有效 batch 太小导致梯度噪声大。解决先把学习率降到 1e-5 或 2e-5,warmup 比例提到 0.1,grad_accum加到 8 看验证 loss 是否平滑。不要一边改学习率一边改模型结构,否则排查不了变量。验证阶段固定 beam 参数,避免解码策略变化干扰判断。

5.4 显存溢出:先量序列长度,再动 batch

现象:训练时报显存不足,或者推理时单条能跑、batch 一开就崩。原因多半是最长样本把动态 padding 撑大,或者generate没包no_grad,beam search 保留了计算图。解决先打印 batch 内最大输入长度,再降max_input_length或 batch;推理阶段明确关闭梯度。torch.cuda.empty_cache()只能回收缓存,不是万能药,根因还是张量太大。

with torch.no_grad(): ids = model.generate(**inputs, num_beams=4, max_length=128)

no_grad必须包住整个生成过程,包括编码器和解码器。如果显存还是紧,先把num_beams从 4 降到 2 做功能验证,再逐步加回去。训练侧优先加grad_accum,不要硬撑大 batch。

5.5 推理输出空串/乱码:tokenizer 和特殊 token 对不上

现象:生成结果为空、满屏特殊符号,或者一上来就结束。原因通常是 tokenizer 和模型不是同一目录、decoder_start_token_id缺失、skip_special_tokens=False,或者输入语言和训练数据不匹配。解决先打印特殊 token,再用同一目录加载 tokenizer 和模型。解码时加skip_special_tokens=True,但不要用它掩盖特殊 token 配置错误。

print(tokenizer.pad_token_id, tokenizer.eos_token_id, tokenizer.unk_token_id) print(model.config.decoder_start_token_id) text = tokenizer.decode(ids[0], skip_special_tokens=True)

pad_token_id为None时训练会出问题,decoder_start_token_id为None时生成会报错。资源包如果换了模型目录,tokenizer 也要一起换,不能只改模型路径。输入语言不一致也会导致乱码,中文摘要任务就用中文数据训练和推理。

6. 推理、ROUGE 评估与交付前抽检:把 beam search 从玄学变成可复现流程

批量推理不要一条条generate,按 batch 跑能省时间,但要把max_length、min_length、num_beams、no_repeat_ngram_size

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询