资讯详情 基于深度学习的文本摘要生成:从Transformer原理到工程部署实战
📅 2026/10/20 8:52:11
简介这份本科毕业设计资源聚焦深度学习与自然语言处理的交叉应用围绕Transformer模型实现文本摘要自动生成适合具备一定Python与神经网络基础、正在准备毕业设计或希望入门NLP方向的高年级本科生。资源包共34个文件以18个Python脚本为核心涵盖模型定义、数据预处理、训练与评估流程另含5个Shell脚本用于一键训练与测试以及yml、txt、vocab等配置与词表文件整体约360KB结构紧凑、便于快速上手。项目完整呈现了从分词、构建词汇表、序列化输入到编码器-解码器建模、损失函数与优化器配置的全流程并涉及ROUGE、BLEU等摘要评价指标的使用同时提供Dockerfile与docker-compose.yml便于环境复现。目前已有3764人学习下载读者可借此掌握Transformer在文本摘要任务中的落地方法积累深度学习框架实操与调试经验为后续科研或NLP岗位工作打下基础。1. 文本摘要生成实战从一篇长文到三行摘要的完整工程链路做过 NLP 项目的同学大概都有这种体验模型在 notebook 里跑得漂漂亮亮一搬到工程环境就各种翻车。这个本科毕业设计项目——基于深度学习的文本摘要自动生成——恰好是一个能让你把 Transformer 从论文拉到实际推理链路的完整案例。它做的事情很明确输入一段长文本输出一段保留核心信息的短摘要。技术栈覆盖 Python、PyTorch、HuggingFace Transformers模型侧用的是 encoder-decoder 架构的预训练模型做微调。适合谁正在做 NLP 方向毕设的本科生、想入门文本生成任务的开发者、以及需要快速搭一个摘要服务原型的工程同学。下面我从项目结构一路拆到二次开发把这条链路讲透。2. 项目架构拆解数据、模型、训练、推理四层怎么串2.1 目录结构长什么样拿到源码包之后第一件事不是急着跑而是先看清楚目录怎么分的。这类文本摘要项目通常遵循一个比较标准的工程分层我拆过好几个类似的项目结构大同小异text-summarization/ ├── configs/ │ └── config.yaml # 模型路径、训练超参、数据路径 ├── data/ │ ├── raw/ # 原始语料长文本-摘要对 │ └── processed/ # 分词、截断后的训练数据 ├── src/ │ ├── dataset.py # Dataset DataLoader 定义 │ ├── model.py # 模型加载与微调封装 │ ├── train.py # 训练入口 │ ├── predict.py # 推理入口 │ └── utils.py # 通用工具函数 ├── requirements.txt └── README.md这个分层的好处是职责清晰configs管参数data管数据src管逻辑。你改模型不用动数据代码换数据集不用碰训练脚本。很多毕设项目把所有代码塞进一个.py文件里后期改起来就是灾难——这个项目的结构算是比较规范的。2.2 数据预处理从原始文本到模型能吃的张量文本摘要任务的数据格式通常是「原文-摘要」成对出现的。预处理阶段要做的事情包括分词、截断/填充、构造 attention mask、把标签对齐到 decoder 的输入。from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(facebook/bart-base) def preprocess(example, max_input_len512, max_target_len128): # 对原文编码截断到 max_input_len inputs tokenizer( example[article], max_lengthmax_input_len, truncationTrue, paddingmax_length, return_tensorspt ) # 对摘要编码作为 labels targets tokenizer( example[summary], max_lengthmax_target_len, truncationTrue, paddingmax_length, return_tensorspt ) inputs[labels] targets[input_ids] # 把 padding 位置的 label 设为 -100损失计算时忽略 inputs[labels][inputs[labels] tokenizer.pad_token_id] -100 return inputs这里有几个关键点值得展开说。max_input_len决定了模型能「看到」多长的原文BART 类模型的位置编码上限一般是 1024但实际训练时设 512 比较常见显存和效果折中。labels里把 padding token 替换成 -100 是 HuggingFace 的约定——交叉熵损失会自动忽略 -100 的位置这样模型不会去学「预测 padding」这种无意义的任务。如果你忘了这一步loss 曲线会看起来很低但生成质量一塌糊涂这是新手最容易踩的坑之一。2.3 模型封装encoder-decoder 怎么加载和微调项目用的模型架构是 encoder-decoder 范式。encoder 负责理解原文decoder 负责逐 token 生成摘要。代码层面HuggingFace 已经把大部分复杂度封装好了from transformers import AutoModelForSeq2SeqLM class SummaryModel: def __init__(self, model_namefacebook/bart-base): self.model AutoModelForSeq2SeqLM.from_pretrained(model_name) def forward(self, input_ids, attention_mask, labelsNone): outputs self.model( input_idsinput_ids, attention_maskattention_mask, labelslabels ) return outputs.loss, outputs.logits def generate(self, input_ids, attention_mask, max_len128, num_beams4): # 推理阶段用 beam search 提升生成质量 summary_ids self.model.generate( input_idsinput_ids, attention_maskattention_mask, max_lengthmax_len, num_beamsnum_beams, early_stoppingTrue ) return summary_idsAutoModelForSeq2SeqLM会自动加载模型的 encoder 和 decoder 权重同时接上语言模型头LM Head。训练时传入labels返回的outputs.loss就是交叉熵损失直接.backward()就行。推理时用generate()方法num_beams4表示 beam search 的宽度比贪心解码生成的摘要更流畅但速度会慢一些。early_stoppingTrue让所有 beam 都生成出结束符时提前停止省时间。2.4 训练循环损失、优化器、学习率调度训练脚本的核心逻辑不复杂但细节决定成败import torch from torch.optim import AdamW from transformers import get_linear_schedule_with_warmup def train_epoch(model, dataloader, optimizer, scheduler, device): model.train() total_loss 0 for batch in dataloader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) loss, _ model.forward(input_ids, attention_mask, labels) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() scheduler.step() optimizer.zero_grad() total_loss loss.item() return total_loss / len(dataloader)clip_grad_norm_把梯度范数限制在 1.0 以内防止梯度爆炸——文本生成任务里这个问题很常见尤其是训练初期。学习率调度用的是线性 warmup前若干步学习率从 0 线性升到设定值之后再线性衰减。这个策略在 Transformer 系列模型上基本是标配不 warmup 直接上大学习率loss 很容易飞。3. 环境搭建与跑通从零到生成第一条摘要3.1 依赖安装与版本对齐这类项目最怕的就是版本冲突。PyTorch、Transformers、CUDA 三者之间的版本兼容性是个玄学问题。我一般会先看requirements.txt然后按下面的顺序装# 先确认 CUDA 版本 nvidia-smi # 安装对应版本的 PyTorch以 CUDA 11.8 为例 pip install torch2.1.0 torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装 transformers 和数据集工具 pip install transformers4.36.0 datasets2.16.0 pip install pyyaml tqdm tensorboard版本号不要随意升级。transformers4.36 和 4.40 之间有些 API 变了比如generate()的部分参数名调整过。如果你跑的时候报TypeError: generate() got an unexpected keyword argument大概率就是版本对不上。3.2 配置文件怎么改config.yaml是整个项目的控制中心跑之前必须按自己的环境改几个关键字段model: name: facebook/bart-base # 预训练模型名称或本地路径 max_input_len: 512 max_target_len: 128 training: batch_size: 8 # 根据显存调整8G 显存建议 4-8 epochs: 10 learning_rate: 3e-5 warmup_steps: 500 output_dir: ./checkpoints data: train_path: ./data/processed/train.csv val_path: ./data/processed/val.csvbatch_size是最需要根据硬件调的参数。8G 显存跑 BART-base、输入长度 512 的话batch_size 设 4 比较稳设 8 可能会 OOM。learning_rate用 3e-5 是微调 BERT 系模型的经典值BART 也适用。如果你换了更大的模型比如 BART-large学习率要降到 1e-5 左右。3.3 训练启动与日志观察配置改好之后启动训练python src/train.py --config configs/config.yaml训练过程中重点盯两个指标training loss 和 validation loss。正常情况下training loss 会稳步下降validation loss 先降后升——当 validation loss 开始持续上升时说明模型开始过拟合了应该提前停止。如果 training loss 一直不降先检查学习率是不是太小或者数据预处理有没有问题比如 labels 全是 -100。提示训练前先用一小部分数据比如 100 条跑 1 个 epoch确认整条链路通了再上全量数据。这样能省很多等待时间。3.4 推理验证加载 checkpoint 生成摘要训练完成后用predict.py加载最好的 checkpoint 做推理python src/predict.py \ --model_path ./checkpoints/best_model \ --input_text 这里粘贴一段长文本 \ --max_length 128 \ --num_beams 4推理脚本内部会做和训练时一致的分词和截断然后调用model.generate()输出摘要 token最后用 tokenizer 解码成可读文本。如果生成的摘要出现大量重复词或者不连贯通常是训练不充分或者 beam search 参数需要调整。4. 核心模块深挖注意力机制与生成策略的实际影响4.1 注意力机制在摘要任务中到底做了什么Transformer 的核心是自注意力机制。在摘要任务里encoder 的自注意力让每个 token 都能看到原文中所有其他 token从而建立全局语义关联decoder 的交叉注意力则让生成每个摘要 token 时能「回看」原文的不同位置决定当前该关注哪些内容。这也是为什么摘要模型能处理长文本——它不是像 RNN 那样逐词传递信息而是通过注意力权重直接建立远距离依赖。实际效果上注意力机制让模型在生成摘要时能自动学会「哪些原文片段重要、哪些可以忽略」。你可以在推理时把注意力权重可视化出来会看到模型生成某个关键词时注意力集中在原文的对应句子上。4.2 生成策略对比贪心、beam search、采样推理阶段的生成策略直接影响摘要质量。项目默认用的是 beam search但不同策略各有适用场景策略参数设置优点缺点贪心解码num_beams1速度快容易生成重复、短视Beam searchnum_beams4~6质量高、流畅速度慢、可能过于保守采样do_sampleTrue, top_p0.9多样性好可能不连贯对于摘要任务beam search 是默认推荐。num_beams设太大比如 10 以上收益递减而且显存占用线性增长。我一般设 4 或 5质量和速度平衡得比较好。4.3 评估指标ROUGE 怎么算、怎么看文本摘要的自动评估主要看 ROUGE 系列指标。ROUGE-1 衡量 unigram 重叠ROUGE-2 衡量 bigram 重叠ROUGE-L 看最长公共子序列。代码里通常用rouge_score库from rouge_score import rouge_scorer scorer rouge_scorer.RougeScorer([rouge1, rouge2, rougeL], use_stemmerTrue) prediction 模型生成的摘要 reference 人工标注的参考摘要 scores scorer.score(reference, prediction) print(fROUGE-1: {scores[rouge1].fmeasure:.4f}) print(fROUGE-2: {scores[rouge2].fmeasure:.4f}) print(fROUGE-L: {scores[rougeL].fmeasure:.4f})ROUGE-1 到 40 以上算不错ROUGE-2 到 20 左右说明模型能抓住一些短语级别的信息。但要注意ROUGE 高不代表摘要可读——它只看词汇重叠不看语义连贯性。所以最终评估还是要人工看一批生成结果。5. 避坑与排查训练和推理中最容易翻车的几个点5.1 Loss 不下降甚至变 NaN现象训练几个 step 后 loss 变成 NaN或者一直停在某个值不动。原因最常见的是学习率太大导致梯度爆炸其次是数据里有空文本或异常字符导致 tokenizer 输出全 padding。解决先把学习率降到 1e-5 试试确认clip_grad_norm_加上了。然后检查数据过滤掉原文或摘要为空的样本。如果用了混合精度训练fp16某些模型会出现溢出换成 bf16 或者关掉混合精度。5.2 生成的摘要全是重复词现象模型输出类似「的的的的的」或者反复重复同一句话。原因训练不充分、beam search 的no_repeat_ngram_size没设、或者模型在解码时陷入了重复循环。解决在generate()里加上no_repeat_ngram_size3禁止生成重复的 3-gram。同时检查训练 epoch 是否够loss 是否降到了合理范围。如果训练数据里本身就有很多重复模式的摘要模型会学到这种坏习惯。5.3 显存不够用OOM现象训练启动后报CUDA out of memory。原因batch_size 太大、输入序列太长、或者模型参数量超出了显卡承载能力。解决优先降 batch_size从 8 降到 4 再到 2。如果还不行把max_input_len从 512 降到 256。再不行就换更小的预训练模型比如t5-small或bart-base换成distilbart。梯度累积是另一个思路batch_size 设 2累积 4 步再更新等效 batch_size 还是 8。5.4 推理速度慢得离谱现象生成一条摘要要好几秒甚至十几秒。原因beam search 宽度太大、模型在 CPU 上跑、或者没有用torch.no_grad()。解决推理时务必加torch.no_grad()上下文否则 PyTorch 会构建计算图白白浪费显存和时间。num_beams从 4 降到 2 或 1 能显著提速。确认模型和输入都在 GPU 上——有时候忘了.to(device)模型悄悄在 CPU 上跑。5.5 ROUGE 分数虚高但摘要没法看现象ROUGE-1 到了 50 多但生成的摘要读起来前言不搭后语。原因ROUGE 只看 n-gram 重叠模型可能通过复制原文片段来「骗分」。如果参考摘要本身和原文高度重叠模型直接抄原文就能拿高分。解决不要只看 ROUGE人工评估至少 50 条生成结果。另外可以看 ROUGE-2 和 ROUGE-L如果 ROUGE-1 很高但 ROUGE-2 很低说明模型只是在做词汇级别的复制没有真正理解语义。6. 二次开发指南换模型、换数据、加功能的实操思路6.1 换预训练模型从 BART 到 T5 或其他项目默认用的是 BART但换成 T5 或 PEGASUS 也很方便因为 HuggingFace 的AutoModelForSeq2SeqLM接口是统一的。你只需要改config.yaml里的model.namemodel: name: google/t5-small # 换成 T5 # name: google/pegasus-xsum # 换成 PEGASUS但要注意不同模型的 tokenizer 不一样预处理代码里的AutoTokenizer.from_pretrained()会自动跟着模型名走所以不用手动改。唯一需要留意的是 T5 需要在输入前加任务前缀比如summarize: 这是 T5 的训练约定不加的话效果会打折扣。6.2 换数据集适配你自己的摘要数据项目默认用的可能是 CNN/DailyMail 或类似的公开数据集。换成自己的数据时只需要保证 CSV 里有两列article和summary。然后在dataset.py里改一下读取逻辑import pandas as pd from torch.utils.data import Dataset class SummaryDataset(Dataset): def __init__(self, csv_path, tokenizer, max_input_len512, max_target_len128): self.data pd.read_csv(csv_path) self.tokenizer tokenizer self.max_input_len max_input_len self.max_target_len max_target_len def __len__(self): return len(self.data) def __getitem__(self, idx): article str(self.data.iloc[idx][article]) summary str(self.data.iloc[idx][summary]) inputs self.tokenizer( article, max_lengthself.max_input_len, truncationTrue, paddingmax_length, return_tensorspt ) targets self.tokenizer( summary, max_lengthself.max_target_len, truncationTrue, paddingmax_length, return_tensorspt ) inputs[labels] targets[input_ids].squeeze() inputs[labels][inputs[labels] self.tokenizer.pad_token_id] -100 return {k: v.squeeze() for k, v in inputs.items()}如果你的数据里摘要特别短比如标题生成任务把max_target_len降到 32 或 64 就行。如果原文特别长比如论文摘要要么增大max_input_len注意显存要么先做段落切分再分别摘要。6.3 加一个 Web 接口用 FastAPI 包一层毕设答辩的时候如果能现场演示一个 Web 界面效果会好很多。用 FastAPI 包一层推理服务代码量很少from fastapi import FastAPI from pydantic import BaseModel import torch from transformers import AutoTokenizer, AutoModelForSeq2SeqLM app FastAPI() device cuda if torch.cuda.is_available() else cpu tokenizer AutoTokenizer.from_pretrained(./checkpoints/best_model) model AutoModelForSeq2SeqLM.from_pretrained(./checkpoints/best_model).to(device) class SummaryRequest(BaseModel): text: str max_length: int 128 app.post(/summarize) def summarize(req: SummaryRequest): inputs tokenizer( req.text, max_length512, truncationTrue, return_tensorspt ).to(device) with torch.no_grad(): ids model.generate( **inputs, max_lengthreq.max_length, num_beams4, no_repeat_ngram_size3 ) return {summary: tokenizer.decode(ids[0], skip_special_tokensTrue)}启动命令uvicorn main:app --host 0.0.0.0 --port 8000然后 POST 请求/summarize就能拿到摘要。注意模型加载放在全局不要每次请求都重新加载——那样每次都要好几秒。6.4 一个容易忽略的细节解码时的特殊 tokentokenizer.decode()一定要加skip_special_tokensTrue否则输出里会混入s、/s、pad这些特殊 token。我见过有人忘了加这个参数生成的摘要开头全是s排查了半天以为是模型出了问题。另外如果用的是 T5解码后可能还会残留extra_id_0之类的哨兵 token同样需要过滤。从那以后我每次部署推理服务都会先拿一条固定输入跑一遍肉眼确认输出干净、没有特殊 token 残留再交给前端联调。希望帮到你。本文还有配套的精品资源点击获取