深度学习文本摘要自动生成实战:从TextRank基线到BART微调
2026/9/8 12:07:20 网站建设 项目流程

简介:面向本科毕业设计的深度学习和自然语言处理文本摘要项目,聚焦Transformer模型在自动摘要任务中的落地实现。压缩包共34个文件,含18个Python脚本、5个Shell脚本、3个TXT说明、2个YML配置以及vocab词表、Dockerfile、README等,整体仅360KB,结构紧凑,覆盖数据处理、模型构建、训练、评估、网页演示与容器化部署全流程。其中model.py实现Transformer编码器-解码器结构,train.py与train_rl.sh支持标准训练和强化学习训练,beam_search.py用于解码生成,eval.py结合ROUGE、BLEU等指标评估摘要质量。已有3451人学习下载,适合需要快速搭建文本摘要实验环境的本科生或NLP入门者,可直接参考代码组织方式并在此基础上进行改进与论文写作。 本科毕业设计选这个题目的人不少,但真正能从头把它做好的人不多。基于深度学习的文本摘要自动生成,放在自然语言处理领域里,算是入门门槛和完成度最容易失衡的方向之一:听起来是“让模型总结一篇长文”,做起来却要串起分词、编码、序列生成、评估指标一整条链路。我去年完整做了这个项目,从数据清洗、基线模型一路做到BART微调,这篇文章就把完整流程拆开讲。不管你是正准备开题的本科生,还是刚开始接触NLP的读者,按这套思路走下来,至少能少折腾一个月。

1. 项目定位:文本摘要任务到底在解决什么问题

1.1 摘要任务的两大分支与毕设定位

文本摘要的核心目标,是给定一篇文档,让系统产出一段语义完整、信息密度高的短文本。按实现方式可以分成两大分支:抽取式摘要和生成式摘要。抽取式是从原文里挑出若干关键句子直接拼接,本质上是一种排序+筛选的问题;生成式则是让模型在理解全文后,像人一样重新组织语言,写出一段原文里未必存在的句子。做本科毕设,通常建议两条腿走路:先用传统抽取式方法做基线,再用深度学习做生成式,这样既能把两套方案对比起来分析,工作量和技术深度也撑得起一篇论文。

另外还有一个容易被忽略的分类维度:单文档摘要和多文档摘要。毕设基本都做单文档即可,也就是“一篇文章对应一段摘要”。多文档摘要需要做跨文档的信息融合,对数据规模和模型能力要求高得多,本科阶段不建议碰。语言上尽量选中文,因为中文新闻语料相对好找,也方便在答辩时做效果展示;如果英文能力够硬,CNN/DailyMail数据集也很经典。

1.2 为什么先实现TextRank基线,再上深度学习模型

很多同学一上来就想直接调预训练模型,我建议先停下来。深度学习模型效果再好,如果缺少对比,说服力会大打折扣。TextRank就是最合适的基线:它借鉴了PageRank的思路,把每个句子当成图里的节点,句子之间的相似度作为边权,然后通过迭代计算每个句子的“重要性分数”,最后按分数从高到低抽出若干句子组成摘要。整个算法无监督,不需要训练数据,几十行代码就能实现。

关键点在于,这个基线必须做得认真,不能随便跑个demo糊弄过去。我当时的做法是先用jieba分词,再用BM25或余弦相似度计算句间权重,然后按重要性排序输出TopK句子;为了凑够摘要长度,还做了“去冗余”处理,防止抽出来的几段话讲的是同一个意思。做完之后你会在测试集上拿到一组指标,比如ROUGE-1在0.25左右。后面所有深度学习模型的提升,都要用这组数字来衬托,所以基线的每一分都要抠清楚。

TextRank的局限也很明显:它生成不出原文之外的新词,抽出来的句子衔接生硬,而且对“指代”和“逻辑关系”几乎无感。但这些短板恰恰是深度生成模型的长处,所以这个对比本身就是毕设里一个很好的分析点。

2. 核心模型选型:为什么我最终选了BART

2.1 生成式摘要主流模型横向对比

决定上深度学习后,第一个绕不开的问题就是用哪个模型。当时我列了一张对比表,把主流方案过了一遍。

模型结构类型是否适合摘要生成中文预训练权重实际体验
Seq2Seq + AttentionRNN/LSTM编码解码一般可以作为教学示例,训练慢,长文本信息丢失严重
TransformerEncoder-Decoder适合并行训练,效果好,但需要大量数据和算力
BERTEncoder-only只适合抽取式不适合自由生成,因为结构上没有生成器
GPTDecoder-only可以生成但可控性差相对少开放式生成强,做摘要容易跑题
BARTEncoder-Decoder很合适有成熟权重预训练任务和摘要天然契合
T5/mT5Encoder-Decoder很合适有,但体积大统一文本到文本框架,效果优秀,微调成本略高

我最后选了BART,核心原因有四个。第一,它的Encoder-Decoder结构天生就是“读全文、写摘要”的骨架;第二,预训练目标和摘要任务高度一致,后面细说;第三,HuggingFace上有可以直接用的中文权重fnlp/bart-base-chinese,省去了从零训练的时间;第四,显存要求对本科机器相对友好,base级别模型用一块8G显存的卡就能跑起来。

2.2 BART的预训练原理与中文场景适配

BART是2019年由Facebook提出的预训练模型,它的做法非常有意思:预训练时把正常文本“弄坏”,比如随机遮挡几个词、删掉一段话、把句子顺序打乱,然后让模型把原文还原出来。你可以把它理解成一个“文本修补匠”,它被迫学会了理解上下文和恢复语义。正因为预训练阶段练的是这种“从受损输入还原完整输出”的能力,微调时把它接到摘要任务上就特别自然:输入是完整长文,输出是浓缩摘要,本质上也是一个“变换”任务。

中文场景下,直接用fnlp/bart-base-chinese这个权重即可,它是用中文语料继续预训练过的BART-base。需要注意,BART-base的参数规模大约1.4亿,不是很大,但效果和算力消耗的性价比非常合适本科毕设。如果你设备很好,也可以尝试thu-coai一些更大规模的中文生成模型,但我个人建议先把小模型完整跑通,后面再谈升级。

2.3 动手前必须搞懂的4个基础概念

如果你前期没有系统学过深度学习,直接调模型很容易一头雾水。我建议先把下面这4个概念弄清楚,不要求完全推导公式,但至少得知道它们在做什么。

  • Attention机制:本质是“给每个词分配注意力权重”。做摘要时,模型生成某个词时需要注意原文里的哪些词,这就是Attention在做的事。通俗点说,就像人写概括时,会重点回顾原文里的关键位置。
  • Transformer的Encoder和Decoder:Encoder把输入句子编码成一系列向量,Decoder根据这些向量逐词生成输出。BART就是这个结构的完整实现。很多同学只学过BERT(只有Encoder)或GPT(只有Decoder),遇到BART会有点懵,其实它就是两者结合。
  • Beam Search:生成文本时最简单的策略是贪心搜索,每一步只挑概率最高的那个词,但这样容易陷入局部最优。Beam Search会每一步保留TopK个候选序列,最后挑整体得分最高的那条。K通常取4或5,代表“搜索宽度”。
  • RoUGE指标:这是摘要领域最常用的自动评测方式,通过计算生成摘要和参考摘要之间的词元重合度来打分。后面会专门讲它的用法。

这4个概念在网上都有大量优质讲解,关键是要把它们对应到实际代码里去理解,不是背概念。

3. 数据准备与预处理:影响模型上限的隐形环节

3.1 中文摘要数据集怎么选:LCSTS使用经验

数据决定了模型效果的上限。我当时选择了LCSTS(Large-scale Chinese Short Text Summarization Dataset),这是哈尔滨工业大学开源的一个中文短文本新闻摘要数据集,特点是:输入是一篇短文,输出是一句话标题式的摘要。原始数据规模很大,官方公开版本约240万条,但通常不会全量使用,很多人会按照它附带的质量评分做过滤,然后取出其中质量较高的几十万条来训练。

我实际跑下来的体验是:用小一点的子集,比如评分过滤后剩下30万到40万条,训练速度可控,效果也已经足够支撑毕设分析。LCSTS自带测试集,省去了自己划分的麻烦。如果你想用英文数据,CNN/DailyMail是最常见的选项,大约有31万条新闻-摘要对,处理思路类似,只是分词方式不同。

写代码前一定要先做统计分析,搞清楚文本长度分布。比如LCSTS的正文平均长度可能在100~200字之间,摘要平均在20~40字之间,这个分布直接影响后面max_length的设定。不要想当然设置超参。

3.2 数据清洗与长度过滤流程

数据清洗是整个项目里最繁琐,也最容易被低估的一步。我遇到过的情况包括:正文里夹着HTML标签、特殊表情符号、新闻来源附加信息,还有完全重复的文本对。这些脏数据如果不处理,模型会花不少容量去拟合噪声。

我的清洗流程大致是:

import re def clean_text(text: str) -> str: # 去除HTML标签 text = re.sub(r"<[^>]+>", "", text) # 去除URL text = re.sub(r"http\S+|www\.\S+", "", text) # 压缩连续空白字符 text = re.sub(r"\s+", " ", text).strip() # 过滤掉只剩符号的无效内容 text = re.sub(r"^[\s,。!?、,.!?]*$", "", text) return text

清洗完之后,还需要做长度过滤:正文太短的直接去掉,比如少于20个字符的长文本没有摘要价值;正文过长但超过模型最大输入长度的,可以截断;摘要过长的也要过滤,因为过长摘要容易让训练不稳定。此外,文本去重也很有必要,否则重复样本会让模型偏向某几种句式。

我在预处理时还做了一个小细节:把正文和摘要都做了统一的标点转换,全角转半角、合并重复标点。这个操作能让tokenizer处理得更稳定,生成的摘要标点也会完整一些。

3.3 tokenizer、词表与DataLoader构建细节

一个常见的误区是自己用jieba分词构建词表,然后丢给模型。如果用BERT/BART这类预训练模型,千万不要自己造词表。预训练模型的权重是跟它的tokenizer严格绑定的,你必须使用同一个分词器,否则词表索引对不上,模型会变成随机初始化。

在代码里我直接使用了BartTokenizer.from_pretrained("fnlp/bart-base-chinese")。它是基于BPE(Byte Pair Encoding)的方式对文本切分,中文字符基本是单字级别的处理,不需要额外分词。构建Dataset时,关键是把padding、truncation和labels处理好,下面这段代码可以直接参考:

from torch.utils.data import Dataset class SummaryDataset(Dataset): def __init__(self, texts, summaries, tokenizer, max_src_len=512, max_tgt_len=100): self.texts = texts self.summaries = summaries self.tokenizer = tokenizer self.max_src_len = max_src_len self.max_tgt_len = max_tgt_len def __len__(self): return len(self.texts) def __getitem__(self, idx): src = self.tokenizer( self.texts[idx], max_length=self.max_src_len, truncation=True, padding="max_length", return_tensors="pt", ) tgt = self.tokenizer( self.summaries[idx], max_length=self.max_tgt_len, truncation=True, padding="max_length", return_tensors="pt", ) labels = tgt["input_ids"].clone().squeeze(0) # 将padding部分设为-100,loss计算时会自动忽略 labels[labels == self.tokenizer.pad_token_id] = -100 return { "input_ids": src["input_ids"].squeeze(0), "attention_mask": src["attention_mask"].squeeze(0), "labels": labels, }

这段代码里有几个细节值得说明。padding="max_length"会让batch里每个样本维度一致,省去collate_fn的额外处理;但推理阶段不要这样做,否则会浪费算力。labels里的pad_token_id必须替换成-100,这是HuggingFace里约定俗成的做法,CrossEntropyLoss会忽略label为-100的位置。还有一个容易踩的坑:BART的tokenizer可能会在某些特殊token上产生不一样的索引,建议代码跑起来后先打印几条编码结果检查一眼。

4. 环境配置与训练脚本实操

4.1 环境版本对照与安装避坑

环境配置虽然不涉及算法,但很多项目卡在第一步就是版本对不上。我当时使用的组合是:Python 3.9,PyTorch 2.0,HuggingFace Transformers 4.x,CUDA 11.8。这个组合在Windows 11和Linux服务器上都能比较稳定地运行。

我用的是conda创建虚拟环境,然后执行下面几条核心命令:

conda create -n textsum python=3.9 -y conda activate textsum pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install transformers datasets tqdm rouge-score

这里最容易出问题的是PyTorch和CUDA的匹配。如果你的GPU是NVIDIA 30系之后的中高端卡,CUDA 11.8基本都支持。安装完先做一次测试:

python -c "import torch; print(torch.cuda.is_available(), torch.__version__)"

如果返回True,再导入一次from transformers import BartTokenizer, BartForConditionalGeneration,确保库本身没问题。我有一次在Windows上遇到torch.cuda.is_available()False,最后发现是安装的PyTorch是CPU版本,重新指定CUDA版本安装就好了。如果你机器显存只有4G左右,建议把max_src_len下调到256,或者换用更小的模型;纯CPU训练不太现实,至少需要一块入门级GPU或者使用云GPU平台。

4.2 微调核心代码逐段解读

训练部分的核心代码并不复杂,但有几个环节值得逐段看一下。首先是加载模型和设置训练参数:

from transformers import BartForConditionalGeneration, BartTokenizer, AdamW, get_linear_schedule_with_warmup model_name = "fnlp/bart-base-chinese" tokenizer = BartTokenizer.from_pretrained(model_name) model = BartForConditionalGeneration.from_pretrained(model_name).to(device) batch_size = 16 learning_rate = 3e-5 epochs = 3 warmup_steps = 500

然后是训练循环。这里有个关键点:model的返回值包含了loss,所以我们不需要手动计算交叉熵,直接把outputs.loss拿去反传就可以。

from tqdm import tqdm optimizer = AdamW(model.parameters(), lr=learning_rate) total_steps = len(train_loader) * epochs scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=warmup_steps, num_training_steps=total_steps, ) model.train() for epoch in range(epochs): loop = tqdm(train_loader, desc=f"Epoch {epoch + 1}") for batch in loop: input_ids = batch["input_ids"].to(device) attention_mask = batch["attention_mask"].to(device) labels = batch["labels"].to(device) outputs = model( input_ids=input_ids, attention_mask=attention_mask, labels=labels, ) loss = outputs.loss loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() scheduler.step() optimizer.zero_grad() loop.set_postfix(loss=loss.item())

clip_grad_norm_这行很关键。生成模型在微调初期梯度容易暴涨,梯度裁剪一般取1.0就可以,能有效防止loss突然变成NaN。我最初因为没有加这行,训练到第2个epoch时loss直接跳到几十,整个训练崩掉,浪费了不少时间。

另外,如果想在有限显存下增大batch size,可以加上gradient_accumulation_steps,比如设成2或4,把梯度累加多次再更新一次参数,等效于加大batch。第一个epoch训练完后,一定要先在验证集上看一下效果,不要盲跑完所有epoch。

4.3 训练过程的监控与结果保存

深度学习训练最怕“看起来跑得挺好,结果全错”。我的纠错习惯是:正式训练之前,先用几十条小数据跑两步,确认loss在下降,并且生成结果不是重复乱码;这之后再启动完整训练。这个小实验成本极低,但能过滤掉八成以上的代码错误。

训练过程中,我会把每个epoch的验证集损失和ROUGE分数记录下来。简单做法是每个epoch结束后,加载模型权重,在验证集上做一次推理,计算ROUGE。保存模型时不要只存一个model.pt,最好把tokenizer一起保存:

model.save_pretrained("./best_model") tokenizer.save_pretrained("./best_model")

这样后面加载时只需两行代码就能恢复完整的模型和分词器。我做毕设时每次都把“验证集ROUGE最高的那次”作为最终模型,而不是最后一个epoch的模型,这一点在答辩时也能体现你做实验的严谨性。

5. 评估指标、常见问题与调优实录

5.1 ROUGE指标原理与计算工具

ROUGE是文本摘要最常用的自动评估指标,它的核心思想很简单:比较生成摘要和参考摘要的重合度。ROUGE-1看单字/词的重合,ROUGE-2看连续两个词的重合,ROUGE-L用最长公共子序列来计算相似度。中文语境下,ROUGE指标会基于字级别或切词后的词级别分别计算。

我当时用官方的rouge-score库,代码非常简洁:

from rouge_score import rouge_scorer scorer = rouge_scorer.RougeScorer(["rouge1", "rouge2", "rougeL"], use_stemmer=False) scores = scorer.score(reference_summary, generated_summary) print(scores["rouge1"].fmeasure)

这里有一个很重要的心理预期:中文摘要的ROUGE分数不会很高。LCSTS测试集上,ROUGE-1跑到0.35~0.45、ROUGE-2在0.2左右,已经是相当不错的效果;如果你一开始看到Rouge-1只有0.2,不要慌,先检查是不是生成了大量重复词或者长度差距太大。另外,ROUGE只是一个近似指标,它完全看不出摘要的“语义”是否正确,有时候模型生成了语义正确的摘要,但因为用词不同,分数反而低。所以最终展示时,一定要配合人工看几个案例,或者做一个简单的人工评测表。

5.2 训练中的典型问题与排查方法

我整理了一份自己在整个项目里遇到过的典型问题列表,这些问题在答辩和写论文时都很适合作为“问题分析”章节的素材。

现象可能原因排查与解决办法
loss不下降或直接变NaN学习率过大、梯度爆炸、数据有脏值调小学习率到1e-5,加梯度裁剪,检查清洗后的数据样例
生成结果全是重复片段beam search没有禁用n-gram重复;训练不充分推理时加no_repeat_ngram_size=3,适当增加训练步数
显存不足(OOM)batch size太大或输入过长减小batch size,使用gradient accumulation,或把输入截断到256
验证loss正常但ROUGE低生成长度和参考摘要差异大、解码策略不合适调整生成时的max_length,尝试beam size 5、length_penalty
中文出现乱码或[UNK]tokenizer与权重不匹配确认使用同一预训练模型的tokenizer,检查几条编码结果
模型收敛特别慢训练数据量太少或学习率太低先用小样本过拟合测试,确认梯度没有阻塞,再加大数据量

这中间最隐蔽的问题是“loss在降,生成的句子却越来越短”。后来我发现是decoder侧没有约束生成长度,导致模型学会了提前输出EOS。解决办法是在训练时对标签长度做过滤,推理时设置min_length,比如对LCSTS这种短摘要,设置min_length=10max_length=50,效果立竿见影。

5.3 调优心得与后续扩展方向

做完整个项目,我有一个很深的体会:预训练模型的微调,核心不是把模型调复杂,而是把数据和评测做扎实。学习率不要贪大,3e-5是比较稳妥的起点;epoch数不用太多,BART-base在中文摘要上微调3个epoch左右就基本收敛,再多反而容易过拟合到训练集的句式。每次调整完超参,一定要先在小验证集上做一次快速实验,再决定是否全量训练。

如果你想在这个题目上继续做扩展,有几个方向可以考虑:一是用mT5替换BART,看看更大规模的模型能不能带来稳定提升;二是在解码阶段下功夫,比如用长度惩罚或关键词约束生成,让摘要更可控;三是给自己的流程加上错误分析,把生成结果按“信息缺失”“重复冗余”“语义错误”分类统计,这会让论文的工作量和技术深度都上一个台阶。

最后再说一个我自己常用的技巧:不要只看验证集整体指标,要把生成结果按文本长度分组看。比如把测试集按原文长度分成短、中、长三组,分别计算ROUGE,往往能发现模型在长文本上的短板更明显。这个结论写进论文里,比单纯报一个总分数更有说服力。文本摘要的难点,很多时候并不在模型解读,而在于数据和评估;先把这两块地基打好,深度学习模型的效果反而来得很快。希望这篇完整流程能帮你在开题和答辩的时候少一点焦虑。

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

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

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

立即咨询