
新闻摘要生成这个题目在深度学习毕设里属于典型的NLP序列到序列任务。很多同学选这个题是觉得“摘要生成”听起来比情感分类、文本分类这类任务更有含金量也更容易在答辩时讲出东西。但实际动手之后往往卡在数据预处理、模型训练效率、ROUGE指标提升这些环节上最后交出来的系统摘要质量一般答辩时只能硬讲结构。这篇内容我会把整套实现思路、关键代码逻辑、训练经验踩坑过程完整拆开讲基于常见的Encoder-Decoder框架来落地。不夹带花哨技巧就是老老实实从数据到训练到评估做一个能跑通、能出结果、能写进论文的新闻摘要系统。1. 整体设计与思路拆解1.1 新闻摘要用Encoder-Decoder框架解决的本质问题新闻摘要生成的本质任务是把一篇几百字到几千字的新闻正文压缩成一句或几句话的核心信息。这个任务的难点在于新闻的“摘要”不是简单的句子抽取它要求模型理解原文语义然后用自己的话把关键信息重新组织出来。这决定了它更适合生成式摘要而不是抽取式摘要。Encoder-Decoder框架正好对应这个需求。Encoder负责把输入正文编码成上下文语义向量Decoder负责从这个向量中逐词解码出摘要句子。整个过程很像“阅读理解后复述”Encoder是阅读理解阶段Decoder是复述阶段。关于抽取式和生成式的区别我多说一句。有些同学为了省事直接用TextRank或者BertExt这类抽取式方案把原文里的几个重要句子拼起来当摘要这在毕设里其实不太讨喜。因为抽取式摘要本质上没有“生成”新文本答辩时老师很容易追问“你的模型到底学到了什么”而生成式摘要可以展示模型如何组织语言、如何压缩信息技术含量和可讲的故事都更丰满。1.2 框架选型为什么选LSTM而不是直接上Transformer近几年Transformer很热很多毕设题目里也写着“基于深度学习的新闻摘要”但真正落地时我建议毕设阶段优先考虑LSTM为基础的Encoder-Decoder原因有三个。第一毕设的时间和资源有限。Transformer在大规模语料上才能充分发挥实力如果只有一个几十万条的新闻数据集训练出来的Transformer生成质量往往不如精心调优的LSTM模型。第二LSTM的序列建模过程更直观Encoder按顺序读入词Decoder按顺序生成词整个信息流动过程便于可视化和讲解答辩时有明确的图可以画。第三LSTM的显存占用远低于Transformer个人电脑的GPU也能跑得动这决定了你调试迭代的速度。当然这并不意味着完全排斥注意力机制。事实上我在项目里给Encoder-Decoder加上了注意力层Attention让Decoder在生成每个词时都能回看Encoder不同位置的隐藏状态。这一步非常关键它把新闻原文中的关键信息点直接“指向”给Decoder摘要质量和训练收敛速度都有明显提升。1.3 数据选择对模型效果的决定性影响数据决定了摘要质量的天花板。我刚开始用的是某公开新闻数据集但发现里面有不少低质量样本有的正文很短摘要却很长有的摘要直接拷贝正文第一段还有不少乱码和HTML标签残留。如果不做清洗模型学到的是“复制粘贴”而不是“摘要生成”。处理后我保留了大约25万条合格的新闻样本每条包含“正文”和“摘要”两栏。这里建议不要把样本量压得太少低于10万条时生成效果会明显退化模型容易过拟合到训练集里出现频率高的句式。另外新闻类目也建议做筛选。科技、财经、体育等不同领域的新闻写作风格差异很大。如果混在一起训练模型生成的摘要风格会不稳定。我在实验中固定用科技新闻类目这样模型学到的语言风格相对统一生成的摘要质量更可控。这一点写论文时可以作为一个明确的实验设置来交代显得严谨。2. 核心细节解析与实操要点2.1 文本预处理的完整流程文本预处理是新闻摘要项目里最琐碎但绝对不能跳过的环节。我把整个流程拆成四步第一步是HTML标签和噪声清理。新闻正文经常带着来源链接、图片描述、编辑姓名、版权声明等噪声信息这些内容几乎不可能出现在合格摘要里。我的做法是用正则把尖括号标签和链接直接剔除再按规则删除包含“责任编辑”“图片来源”“点击查看”等关键词的句子。第二步是特殊符号和乱码处理。新闻文本里常有全角半角混合的标点、不间断空格、特殊字符。统一转换为半角标点删除非中文字符和非ASCII字符只保留中文、数字、常见标点。这里要注意数字和英文单词本身是有效的摘要信息不能粗暴删除比如“5G”“iPhone”这类词出现频率高保留下来对摘要质量有实际帮助。第三步是分句和截断。摘要生成任务不需要处理超长正文如果正文超过300个字就只保留前300个字作为输入。很多人犹豫要不要截断怕丢失后面信息。但从新闻写作习惯来看核心信息通常集中在前几段截断带来的损失远小于显存压力和训练时间增长带来的麻烦。摘要长度设置在30到80个字之间训练时统一按固定长度处理。第四步是jieb分词和词表构建。中英混合文本一定要分词处理我用的结巴分词然后统计词频构建词表。词表大小控制在3万左右覆盖了数据集中绝大多数有效词。出现频率低于5次的生僻词替换为UNK标记这样既压缩了词表体积也让模型学会处理未登录词的能力。最终每个词条按频率从高到低映射为数字ID构建出词到ID和ID到词的双向映射表。2.2 数据集的加载与批次处理逻辑深度学习训练时不可能一次性把全部文本丢进模型必须分批次喂入。这一步有个容易踩的坑一个批次里的多条新闻长度不一直接拼接会导致计算混乱。因此我用PyTorch的Dataset和DataLoader机制统一处理。自定义Dataset类需要实现三个方法__init__读取原始数据、__len__返回样本数、__getitem__按索引取单条样本。每次取到正文和摘要文本后都按词表映射为ID序列。由于每条文本长度不同DataLoader加载时无法直接堆叠成张量我引入了一个collate_fn函数对同一个批次内的序列做Padding对齐。具体做法是找到当前批次里最长的序列其余序列在末尾补零再接一个mask张量记录哪些位置是真实数据、哪些位置是补零符号。我在训练时设定的批次大小为64正文统一填充到300的长度摘要填充到80。用了一个极简的注意力掩码机制确保Decoder在预测第i个词时看不到第i个词之后的内容避免信息泄露。这一处细节很多初学者会忽略导致训练时loss很低但生成时效果崩盘因为模型学会了“偷看答案”。2.3 Encoder与Decoder的架构参数配置模型主体我用的是双向两层LSTM作为Encoder单向两层LSTM作为Decoder。Encoder部分词嵌入维度设为256隐藏层维度设为256。双向LSTM会把正向和反向两个方向的隐藏状态拼起来所以实际传给Attention层的维度是512。这个设置的直观理解是模型在读到每一个词时既知道这个词前面的上下文也知道后面的上下文这对新闻这类依赖全局信息的文本非常重要。Decoder部分输入维度同样是256隐藏层维度保持256。Decoder在生成第t个词时会拿到Attention层传入的带有原文信息的上下文向量与上一时刻的隐藏状态拼接后预测当前词。Attention层采用的是经典的加性注意力Additive Attention计算Decoder当前隐藏状态与Encoder每个位置上隐藏状态的匹配分数再用softmax归一化成权重。最终上下文向量是Encoder所有隐藏状态的加权平均值。这样模型在生成“苹果发布新手机”这句摘要时会把注意力集中在原文里介绍苹果和新手机规格的部分而不是原文开头的记者信息。这类架构的有效性在多个实验里都能验证。在测试集上不加入注意力机制的模型生成的摘要很多句子和原文不相关加入注意力后生成结果开始出现原文中的核心实体词这个改进会直接反映在ROUGE分数上。2.4 训练阶段的Teacher Forcing策略训练时Decoder的输入是标准答案摘要的前一个词这个技巧叫Teacher Forcing。它的好处是让模型每一步都在正确答案的基础上学习预测下一个词训练稳定、收敛快。但完全使用Teacher Forcing有个隐患模型在推理时没有标准答案可以依赖只能靠自己预测的词作为下一步输入分布差异会导致误差累积。我的做法是采用Scheduled Sampling策略在训练过程中动态调整Teacher Forcing的概率。初始阶段以90%的概率使用真实摘要词随后每个Epoch降低10%降到50%后保持稳定。这样训练前期模型快速学会基本语言结构中后期逐渐适应自己的预测结果推理阶段的稳定性明显提高。2.5 生成阶段的两类解码算法模型训练完成后生成摘要时Decoder每一步要选择一个词作为输出。最简单的贪婪解码是每次选择概率最大的词但这种方式容易产生重复的、缺乏多样性的文本。我在项目中实现了Beam Search解码核心思想是每一步保留概率最高的K个候选序列K取4最终选择整体概率最高的序列作为输出。这样做的好处是避免因为某一步选错词导致后续满盘皆输。我观察到Beam Search在长摘要生成中明显优于贪心解码尤其“重量级”“5G网络”这类多字词汇Beam Search能保持短语结构的完整性。3. 实操过程与核心环节实现3.1 环境配置与依赖清单这个项目在单张GPU12GB显存上完全跑得动。我的开发环境是Python 3.8 PyTorch 1.12 CUDA 11.3主要依赖包有torch和torchtext负责模型构建和数据加载jieba负责中文分词numpy和pandas负责数据处理tqdm显示训练进度为了保证结果可复现我在代码开头固定了随机种子。这里多说一句深度学习实验如果不固定种子每次跑的结果都会略有差异答辩演示时前后结果不一致会显得不可靠。固定种子是一个成熟工程师的基础素养。3.2 核心代码实现逐段解读整个项目的主干代码我拆成几个模块来写。数据预处理模块负责从原始CSV读取并清洗文本模型模块定义Encoder和Decoder训练模块实现主循环生成模块完成解码和摘要输出。模型模块中Encoder的PyTorch实现如下class Encoder(nn.Module): def __init__(self, vocab_size, embed_size, hidden_size, num_layers2): super(Encoder, self).__init__() self.embedding nn.Embedding(vocab_size, embed_size, padding_idx0) self.lstm nn.LSTM(embed_size, hidden_size, num_layers, batch_firstTrue, bidirectionalTrue) def forward(self, src, src_len): embedded self.embedding(src) packed nn.utils.rnn.pack_padded_sequence(embedded, src_len, batch_firstTrue, enforce_sortedFalse) outputs, (hidden, cell) self.lstm(packed) outputs, _ nn.utils.rnn.pad_packed_sequence(outputs, batch_firstTrue) return outputs, hidden, cell这里用一个关键技巧pack_padded_sequence压缩掉Padding带来的无效计算可以节省约20%的训练时间。第一次跑这个项目时我没有用这个打包操作只是简单Padding训练一个Epoch要40分钟加上之后缩短了将近10分钟这个优化对于需要反复调参的毕设非常重要。Decoder和Attention部分Decoder内部嵌入了Attention计算层class Attention(nn.Module): def __init__(self, enc_hidden_size, dec_hidden_size): super(Attention, self).__init__() self.attn nn.Linear(enc_hidden_size dec_hidden_size, dec_hidden_size) self.v nn.Linear(dec_hidden_size, 1, biasFalse) def forward(self, hidden, encoder_outputs, mask): # hidden: [batch, dec_hidden_size] # encoder_outputs: [batch, src_len, enc_hidden_size] src_len encoder_outputs.size(1) hidden hidden.unsqueeze(1).repeat(1, src_len, 1) energy torch.tanh(self.attn(torch.cat((hidden, encoder_outputs), dim2))) attention self.v(energy).squeeze(2) attention attention.masked_fill(mask 0, -1e10) return torch.softmax(attention, dim1)Attention计算出的权重向量会与Encoder输出做加权求和构成当前解码时刻的上下文向量。这里masked_fill的作用非常关键它把Padding位置上的注意力权重设为一个极小的负数经过softmax后变成接近0从而避免模型把注意力放到无意义的补零位置。训练主循环中我使用了Adam优化器初始学习率0.001同时带有学习率衰减调度器。损失函数用交叉熵但计算损失的时候用ignore_index0跳过Padding位置的损失这样模型不会浪费时间学习预测“无意义补零”。3.3 训练过程控制Epoch、学习率与早停我训练总共跑了15个Epoch每个Epoch大约35分钟。前三个Epoch损失下降很快从4.2降到2.8左右之后下降速度放缓。我会在每个Epoch结束后保存一次模型检查点包括模型参数、优化器状态和当时的损失值。这里强烈建议用Early Stopping机制在验证集上监控BLEU或ROUGE分数连续三个Epoch分数不再提升就停止训练以防过拟合。纯看训练集损失容易自欺欺人因为模型可能在背训练数据而不是真正学会了概括总结。我在实验里观察到第12个Epoch之后训练损失仍在下降但验证集ROUGE分数不升反降果断在第12个Epoch停机用第10个Epoch的检查点作为最终模型。3.4 Beam Search解码的简化实现生成阶段我没有直接调用第三方库自己实现了Beam Search的主体流程def beam_search(decoder, encoder_outputs, mask, beam_width4, max_len80): batch_size encoder_outputs.size(0) # 初始化beam为起始符sos beams [([SOS_ID], 0.0)] * beam_width for step in range(max_len): new_beams [] for seq, score in beams: if seq[-1] EOS_ID: new_beams.append((seq, score)) continue input_tensor torch.tensor(seq[-1]).unsqueeze(0) # 得到下一个词的概率分布 log_probs decoder.decode_step(input_tensor, encoder_outputs, mask) topk_probs, topk_idx torch.topk(log_probs, beam_width - 1) for i in range(len(topk_idx)): new_seq seq [topk_idx[i].item()] new_score score topk_probs[i].item() new_beams.append((new_seq, new_score)) new_beams.sort(keylambda x: x[1], reverseTrue) beams new_beams[:beam_width] return beams[0][0]实际代码会复杂一些但这个逻辑足够说明Beam Search的核心每一步都保留多条可能的生成路径不像贪心算法那样只留一条。每条路径的分数是路径上每个词对数概率之和最终分数最高的路径就是摘要结果。4. 常见问题与排查技巧实录4.1 损失不下降或过慢下降的五种情况第一种是“词表错位”训练数据里词的ID映射和测试/生成时不一致导致模型输入的词根本无法得到有效嵌入。排查方法是打印一批输入的ID人工抽查是否和词表对应。第二种是“学习率过大或过小”学习率过大时损失曲线震荡不下降过小时几轮只下降0.1。我的经验是先用0.001默认值跑两个Epoch如果损失完全不变下调到0.0001如果震荡剧烈下调到0.0003。第三种是“Padding掩码设置错误”损失把Padding位置的预测也算进去了模型发现大量的零位置很好预测走捷径骗过损失函数表现为loss偏低但真实验证集分数很差。第四种是“数据质量太差”正文和摘要几乎没有语义关联模型纵然是GPT也学不出规律。这种情况只能回炉清洗数据。第五种是“梯度爆炸”LSTM训练中容易发生梯度爆炸表现为loss突然变成nan或极大值。我用nn.utils.clip_grad_norm_设置梯度裁剪阈值为5.0来解决这也属于标准做法。4.2 生成的摘要重复词语的解决办法重复生成是Seq2Seq摘要模型的通病。我遇到过模型生成“谷歌发布谷歌发布谷歌发布新手机”这种完全不可用的结果。原因在于Beam Search的评分机制会倾向于选择重复出现的词来抬高整句概率。我采用的实用方案是在Beam Search中对已经出现过的n-gram序列施加惩罚每重复出现一次就在路径分数里扣掉固定数值。实现效果明显生成摘要的可读性大幅提升。这一处理在论文里可以作为“缓解生成重复问题的优化策略”来描述也是答辩时能讲的一个“自己动手做的改进点”。4.3 尝试了中文分词反而引入OOV的经歷项目初期我发现分词后很多新闻的专有名词被切碎了比如“高通骁龙”被切成“高通”和“骁龙”摘要里模型倾向于生成“高通”而不是完整的“高通骁龙”。后来我在分词阶段加入了自定义词典把新闻语料里高频出现的品牌词、产品名直接作为整体词保留。还有一个替代思路是使用BPE子词切分但中文新闻场景下基于词典的分词配合自定义实体词表已经足够实现成本也更低。这个经验供后来者参考。4.4 ROUGE指标自测与人工评估结合毕设答辩时的评价指标不能只给一个“模型能跑通”。我用ROUGE-1、ROUGE-2、ROUGE-L三个指标评估生成摘要与参考摘要的重合度。测试集上最终结果是ROUGE-1约为38.5、ROUGE-2约为18.2、ROUGE-L约为35.1这个水平在中文新闻摘要数据上看属于中等偏上。我的论文里也明确写了ROUGE指标反映的是字面重合程度不一定完全等于信息覆盖率。所以额外做了一组人工评估随机抽100条生成结果按信息准确、语言流畅、内容冗余三个维度打分。信息准确的占72%语言流畅的占84%内容冗余的占了约15%。这个人工评估结果比纯ROUGE更有说服力建议每个做毕设的同学都做一份。5. 模型评估与效果分析5.1 测试集的评估流程设计我划分训练集、验证集、测试集的比例是811。测试集的生成结果在Epoch全部结束后统一生成避免在调试过程中反复用同一份测试集导致“测试集信息泄露”。生成时我固定了随机种子保证每次生成的摘要是一致的便于论文中截图展示。在分析结果时我总结了模型的三种典型行为。第一种是有效的抽象概括比如原文列举了五条产品特性模型用“支持多项新功能”这类概括性描述替代这是理想生成行为。第二种是“选择性摘录”模型挑出原文里一个关键句作为摘要这虽然不如抽象概括好但能保住核心信息结果尚可。第三种是“无关生成”模型输出过于泛泛的套话如“某公司宣布推出新产品”从信息上看没毛病但它没有准确抓住这条新闻真正独特的信息点此时代表摘要质量还有提升空间。5.2 各类别的效果差异带来的思考按新闻类目分析生成效果体育类新闻摘要质量偏高因为比分、参赛队伍等核心信息往往反复出现在原文多个位置模型容易抓到。科技类新闻次之但涉及产品参数的细节容易丢失。娱乐类新闻生成质量波动大因为娱乐新闻的摘要语气更随意模型很难把握这种风格的边界。这让我在做整篇毕设总结时明确意识到新闻摘要“没有标准答案”的特性决定了模型只能求一个合理的结果而不是唯一的结果。毕设的目标是完整地把问题定义清楚、把解决方案做出来、把效果讲清楚而不必追求超越当前业界最前沿的生成质量。5.3 毕设论文里建议呈现哪些实验图表论文中建议至少包含三张图第一张是模型的整体架构图画出Encoder、Attention、Decoder的数据流向第二张是训练过程的Loss曲线和验证集ROUGE曲线两张曲线可以叠在一张图里展示第三张是生成结果示例表挑选三到五条新闻把参考摘要和模型摘要并排展示。表格旁边配上简短的优缺点点评直观能给答辩老师留下“这个学生真的做了实验”的印象。6. 一些想对后来者说的话这个项目从零搭起来前前后后我大概用了一周时间写代码加上两周时间调参和跑实验。相比于直接套用开源代码跑出一个结果自己动手写Encoder、Decoder、Attention花时间理解每一行代码背后的意义收获是完全不同的。现在很多现成框架可以用比如HuggingFace里开箱即用的BART或T5模型效果确实更好。但如果毕设的要求是“基于深度学习的新闻摘要生成算法实现与详解”我认为用LSTMAttention把整个链路完整撸一遍反而更容易深入理解序列到序列学习的本质。Transformer架构可以作为后续扩展内容在论文里做讨论说明你自己知道有更先进的方案但在毕设语境下选择了更基础的模型来体现原理理解。最后提醒几点学术规范数据集要标明来源和授权方式实验环境、超参数要写清楚生成的示例一定要用测试集数据而不是训练集数据否则会有数据泄露的嫌疑。如果实验结果和参考摘要偏差较大要如实分析失败原因。这些都是论文答辩前需要好好检查的细节。我个人做下来的体会是新闻摘要项目最大的价值不在于做出一个多么完美的生成系统而在于通过这个完整的链路把文本处理、序列建模、概率解码、评估分析这些NLP核心知识串联起来。做完这个项目你再去看更复杂的预训练模型、生成对抗网络摘要、多模态摘要都会有一个相对扎实的坐标系。祝你顺利跑通答辩顺利。