
简介一份基于深度学习的文本自动摘要研究方案内容源自《计算机应用》2019年第2期正式论文作者单位为北京电子科技学院与西安电子科技大学。方案针对NLP生成式自动摘要中语义理解不充分、摘要语句不通顺、准确度不高等问题提出了改进的词向量生成技术在Skip-Gram基础上引入词性、词频和逆文本频率三个特征并构建Bi-MulRnn生成式摘要模型融合seq2seq、自编码器、注意力机制、GRU、双向与多层循环神经网络及集束搜索。在大规模中文短文本摘要LCSTS数据集上的实验显示该方案在Rouge评价体系中表现良好能有效提升摘要准确性与语句流畅度。资源为PDF格式共1个文件压缩包大小约1.06MB属于参考文献与专业指导类资料适合从事自然语言处理、文本摘要研究的学生、算法工程师及相关方向科研人员阅读参考用于了解生成式摘要建模思路、关键技术选型及实验评测方法。已有282人学习下载。1. 文本自动摘要方案先算清“压缩”和“重写”这笔账做基于深度学习的文本自动摘要方案时最容易翻车的地方不是模型选型而是没把“压缩”和“重写”这两条路分开。身边不止一个人拿通用大模型跑新闻摘要效果惊艳但换到合同、病历、设备日志后输出不可控最后灰溜溜退回规则。文本自动摘要本质上就两条路抽取式从原文里挑句子拼成摘要生成式用模型重新组织内容。深度学习在两条路上都能做但数据要求、评估方式和踩坑点完全不同。这篇笔记适合正在搭摘要方案、需要给团队一个可复现基线、或者被“ROUGE分数高但没人敢用”折磨的工程师。先定边界再谈模型。2. 抽取式还是生成式方案选型决定后面所有工作量2.1 摘要任务的两类边界什么情况选抽取式抽取式摘要把“摘要”定义成原文句子的子集。优点是忠实度天然有保证因为每个词都来自原文不会出现模型编造事实缺点是句子数量有限无法像人一样跨句合并和抽象。实际工程里抽取式适合处理规范性文本比如会议纪要、合同条款、法律文书这些场景里“摘出关键句”比“重新组织语言”更安全。深度学习在这里的角色常见做法有两种一是给句子打分用BERT/CNN这类模型判断每个句子是否该进摘要二是做序列标注预测每个句子是保留还是删除。前者更通用后者在新闻场景表现好。我一般建议先跑无监督的TextRank基线因为不需要标注数据能快速拿到一个可行结果同时用这个基线的ROUGE分数作为后续深度模型的“下限”——如果深度学习模型连这个下限都比不过就不要上线。深度学习的另一个价值是能融合上下文。单个句子打分容易选出一堆重复信息而基于“句子-文档”的交互编码可以抑制冗余。这个后面会提到。另外提醒一句先搭好深度学习环境配置跑通一个最简单的句子分类模型再去调大模型否则会陷入“环境没配好、代码报错、怀疑业务”的恶性循环。2.2 无监督基线TextRank不依赖标注也能跑通的最小方案TextRank是一种图排序算法把每个句子当节点句子之间的相似度当边迭代计算权重最后取Top-N个句子。它不需要训练只需要分词和相似度计算。这决定了它是所有摘要方案里最适合做“先跑通Pipeline”的选择。import jieba import numpy as np def sentence_similarity(s1, s2): set1 set(jieba.lcut(s1)) set2 set(jieba.lcut(s2)) if not set1 or not set2: return 0.0 return len(set1 set2) / len(set1 | set2) def textrank(sentences, damping0.85, max_iter100, tol1e-6): n len(sentences) sim np.zeros((n, n)) for i in range(n): for j in range(n): if i ! j: sim[i][j] sentence_similarity(sentences[i], sentences[j]) row_sum sim.sum(axis1, keepdimsTrue) row_sum[row_sum 0] 1e-8 M sim / row_sum score np.ones(n) / n for _ in range(max_iter): new_score (1 - damping) / n damping * M.T.dot(score) if np.abs(new_score - score).sum() tol: break score new_score return score这段代码有两个关键点。第一相似度用了分词后的词集合Jaccard对中文文本基本够用如果用词向量效果会好一点但开销大。第二转移矩阵M按行归一化每个句子把自身权重按相似度分给其他句子damping是PageRank的阻尼系数通常取0.85表示从当前节点跳转到任意节点的概率。注意这个相似度矩阵是O(n²)的文档超过200句就会明显变慢所以一般先做句子去重或者用窗口滑动限制连接。拿到score后按句子顺序取前K个句子并按原文顺序输出即可。这个基线不需要任何深度学习环境但也正是因为它没有学习能力遇到“摘要在文档中分布比较均匀”的文本时会拉胯——这时才轮到深度学习模型上场。2.3 把抽取式升级为深度学习模型训练数据从哪来深度学习抽取式模型最简单的形态是对每个句子做二分类预测它是否属于摘要。但需要一个关键问题——没有人工标注正负样本怎么构造常见做法是用ROUGE的Oracle。也就是把原文句子组合起来和参考摘要做ROUGE比较选分数最高的组合作为正样本。这里给一个概念验证代码from itertools import combinations from rouge_score import rouge_scorer def get_oracle_sentences(doc_sentences, ref_summary, top_k3): scorer rouge_scorer.RougeScorer([rouge1], use_stemmerTrue) best_score -1 best_combo [] for k in range(1, top_k 1): for combo in combinations(doc_sentences, k): cand .join(combo) score scorer.score(cand, ref_summary)[rouge1].fmeasure if score best_score: best_score score best_combo combo return best_combo注意这段代码只适合句子数少的文档因为组合数会爆炸。实用做法是贪心先选一个句子使ROUGE最高再从剩下的句子里选一个使组合分数最高迭代直到分数不再提升。用这种Oracle生成句子级别的标签喂给BERT做句子分类就能训练一个有监督抽取模型。这里要强调抽取式深度模型的上限受限于Oracle标签如果Oracle本身选的句子就不够好模型再强也白搭。所以做摘要方案时先看看Oracle的ROUGE分数如果它和人工摘要差距过大说明这个任务用抽取式根本做不好应该转向生成式。这一步是我在多个项目里检验过的判断标准能省掉很多无用功。深度学习算法再复杂也得先回答“任务本身用哪类模型能赢”这个问题。3. 数据准备与评估跑通摘要模型前先解决的两件小事3.1 中文摘要数据清洗以LCSTS为例公开数据集中LCSTS是中文短文本摘要里最常用的一份字段很简单短文本和人工摘要。但原始数据不能直接用里面掺杂着话题标签、用户和URL要清洗。常见做法是正则处理import re def clean_text(text): text re.sub(r#.*?#, , text) # 去掉#话题# text re.sub(r\w[:]?, , text) # 去掉用户 text re.sub(rhttp\S, , text) # 去掉URL text re.sub(r\s, , text) return text.strip()清洗后还要做几件事。第一过滤长度正文短于30字或长于500字的样本会导致训练时padding浪费或者没有足够信息生成摘要。第二过滤摘要和正文重合度过低的样本——生成式摘要允许抽象但完全没重合的样本在初期会干扰模型。第三统一用标点切句而不是用模型切句避免把工程复杂度带到数据清洗阶段。这一步的重要性经常被低估。我见过有团队直接用原始微博数据训练结果模型学会了输出话题标签和表情符号原因是数据噪声被当成了特征。深度学习模型对数据分布极其敏感清洗质量比模型结构更能影响最终效果。所以我的习惯是先把清洗脚本写到数据管线里每次训练前都跑一遍绝不用手动清洗的临时文件。3.2 ROUGE评估为什么它是摘要任务的唯一通用尺子摘要任务不像分类任务有准确率因为“好摘要”没有唯一答案。业界通用做法是用ROUGE它统计生成摘要和参考摘要之间n-gram的重合度。ROUGE-1衡量单字/词重合ROUGE-2衡量二元组ROUGE-L基于最长公共子序列。计算方式很简单from rouge_score import rouge_scorer scorer rouge_scorer.RougeScorer([rouge1, rouge2, rougeL], use_stemmerTrue) scores scorer.score(pred_summary, ref_summary) print(scores[rouge1].fmeasure)use_stemmer会把英文单词还原中文场景一般开着影响不大。ROUGE的问题也很明显它只看词面重合不看语义。生成式摘要只要换一种说法词面重合度就低但内容可能完全正确反过来模型直接复制原文句子ROUGE分数会很高但字数多、信息冗余。所以ROUGE只能当筛选器不能当验收标准。实际项目中我一般用ROUGE做两件事一是比较不同模型时看相对差距比如基线是30新模型是33说明有提升二是配合长度约束比如生成摘要超过128字时ROUGE-L会因截断而虚高。要规避这个问题应该在评测时统一对输出做截断或者把输出长度也记录下来人工抽查时重点关注长摘要。3.3 数据切分与小样本冒烟测试深度学习的训练过程很长如果跑到一半发现数据格式错误或者预处理漏了字段代价很大。所以我的流程是先把完整数据集切成训练/验证/测试比例8:1:1然后只取一小部分跑通全部流程。from datasets import Dataset def split_dataset(dataset, train_ratio0.8, val_ratio0.1): total len(dataset) train_end int(total * train_ratio) val_end int(total * (train_ratio val_ratio)) return dataset.select(range(train_end)), dataset.select(range(train_end, val_end)), dataset.select(range(val_end, total)) # 冒烟测试取200条训练、50条验证确认数据流没问题再上全量 small_train train_dataset.select(range(200)) small_val val_dataset.select(range(50))切分时要注意摘要任务里同一个文档可能有多个参考摘要如果按文档ID去重后再切能避免数据泄漏。这个问题在新闻摘要里经常出现同一篇新闻被不同媒体改写参考摘要也不同但正文其实同源不按文档ID去重会导致验证集分数虚高。小样本冒烟测试的目的不是看模型效果而是确认三件事数据能送进模型、loss能下降、生成结果不是空字符串。这一步能暴露八成环境问题比如CUDA版本不匹配、tokenizer词表异常、label维度错误。等冒烟测试通过再切到全量训练比直接跑一夜然后发现loss为NaN要舒服得多。4. 用深度学习模型训练摘要从Encoder-Decoder到生成参数4.1 生成式摘要的最小原理让模型学会“重写”生成式摘要的核心是序列到序列Seq2Seq结构一个Encoder读入原文一个Decoder逐词生成摘要。早期用LSTM但长文本信息衰减严重后来引入AttentionDecoder在生成每个词时都能“回头看”原文的相关位置。现在主流做法是直接用Transformer把原文和摘要都切成token子词序列Encoder与Decoder不再共享参数。深度学习模型在文本摘要上的优势是可以学习“压缩”的规则哪些词该保留、哪些冗余词该删除、如何把分散的信息合并成一句。但这种能力需要大量“原文-摘要”对。所以生成式模型的样本需求远高于抽取式没有几万条高质量标注数据效果会很不稳定。我见过在几千条样本上硬训Transformer的团队最后生成结果几乎是把原文前几句抄一遍和抽取式没区别。4.2 最小可训练配置用HuggingFace Trainer跑一个生成式模型在深度学习环境配置完成后最快落地生成式摘要的方式是用Transformers库。它提供统一的Tokenizer和Model接口省去手写注意力机制的麻烦。下面是一段最小训练代码不针对特定模型而是给出通用结构from transformers import ( AutoTokenizer, AutoModelForSeq2SeqLM, Seq2SeqTrainingArguments, Seq2SeqTrainer, DataCollatorForSeq2Seq ) tokenizer AutoTokenizer.from_pretrained(your_chinese_summary_model) model AutoModelForSeq2SeqLM.from_pretrained(your_chinese_summary_model) def preprocess(examples): model_inputs tokenizer(examples[text], max_length512, truncationTrue) with tokenizer.as_target_tokenizer(): labels tokenizer(examples[summary], max_length128, truncationTrue) model_inputs[labels] labels[input_ids] return model_inputs training_args Seq2SeqTrainingArguments( output_dir./sum_model, learning_rate3e-5, per_device_train_batch_size8, per_device_eval_batch_size8, predict_with_generateTrue, generation_max_length128, generation_num_beams4, max_steps2000, evaluation_strategysteps, eval_steps200, save_steps200, )这段代码有两点要说明。第一tokenizer和model的路径要替换成实际使用的生成模型但必须保证两者词表一致否则输出会出现大量[UNK]。第二predict_with_generateTrue表示验证阶段用beam search生成结果而不是直接取Decoder输出的最大概率词这样验证的ROUGE才和推理一致。很多项目的评估分数虚高就是因为训练时没开这个开关。4.3 训练与推理的哈姆雷特问题beam search、长度惩罚与重复惩罚生成式摘要的训练通常用teacher forcingDecoder每一步都输入真实摘要的 token模型只预测下一个 token。但推理时没有真实token模型必须吃自己生成的内容。这个差异是摘要模型“训练正常、推理崩”的根源。推理阶段的常用做法是beam search维护K个候选序列每一步扩展后保留概率最高的K条。但这会引入一个副作用——模型为了追求整体概率容易反复生成同一句话。所以我一般会在generate参数里显式抑制重复outputs model.generate( input_idsinput_ids, max_length128, num_beams4, no_repeat_ngram_size3, length_penalty1.0, early_stoppingTrue, )no_repeat_ngram_size3表示生成过程中不允许出现任意连续3个token的重复能有效缓解“车轱辘话”。length_penalty1.0是长度惩罚系数大于1倾向生成长摘要小于1倾向于短摘要如果发现模型总把原文整段抄出来把它调到0.8左右。这些参数需要在验证集上做网格搜索而不是凭感觉固定。5. 避坑文本自动摘要模型的五个常见翻车点5.1 生成摘要反复重复一句话现象模型输出的摘要里连续出现“中国企业中国企业中国企业”或者某个短语循环出现整体看起来像复读机。原因beam search在概率上偏好重复结构而训练数据里某些高频n-gram被过度强化。尤其当文本长度超过训练时的最大长度时模型更容易进入重复循环。解决先在推理阶段加no_repeat_ngram_size3观察重复是否消失。如果训练数据本身重复模式严重就要在数据清洗时把原文中的重复片段去掉或者在训练时使用unlikelihood损失降低重复token的概率。我一般先调推理参数解决不了再动训练目标。5.2 生成摘要比原文还长现象设置max_length128结果模型生成了128个token的摘要但正文才80个token摘要比原文还长。原因训练时的摘要长度分布和推理时的max_length设置不一致。如果训练数据里有很多长摘要模型会倾向生成长内容而长度惩罚设置不当会导致截断在句中被砍断。解决检查训练集摘要长度的分位数把max_length设为90%分位数附近同时把length_penalty适当调低。另外评估时不要把截断后的长摘要直接算ROUGE要记录原始长度防止用长度换取分数。5.3 训练loss一直在降推理结果却是乱码现象训练集和验证集loss都正常下降但模型推理输出里出现大量[UNK]或重复的特殊符号。原因这一步最常见的问题是词表不匹配。训练时用了A模型的tokenizer保存的模型却是从B模型加载的或者as_target_tokenizer没有正确设置导致Decoder端的token id错位。解决重新检查tokenizer和model的路径确认来自同一个预训练目录。再用一句测试文本分别调用tokenizer和model检查input_ids和解码结果能不能对应上。这种问题在深度学习项目里属于“环境配置黑匣子”跑一个小样本推理脚本就能定位。5.4 专有名词和未登录词全部消失现象摘要里本该出现的公司名、人名被替换成通用词或者直接省略。比如“英伟达发布新一代GPU”被生成成“企业发布新产品”。原因生成式模型受限于词表和训练数据低频专名容易在生成时被“记忆”替代。抽取式模型反而不会出这个问题因为它是从原文里选句子。解决如果业务场景中专有名词很多优先考虑抽取式方案。或者用Pointer-Generator思想在Decoder计算一个“生成/复制”开关直接从原文复制token。工程上更简单的做法是生成完成后做一次规则后处理把原文中出现的专有名词与摘要做匹配补回。5.5 ROUGE分数高但人工阅读完全不通过现象验证集ROUGE-2达到25比基线高不少但业务方抽样一看发现摘要要么是信息堆砌要么把最关键的结论漏掉了。原因ROUGE是词面重合模型可能通过复制大量原文句子来刷分而不是真正理解信息层级。这本质上是自动评估指标与真实质量脱节。解决在项目上线前一定要加人工评估环节至少抽样200条从事实一致性、信息完备性、通顺度三个维度打分。如果人工分和ROUGE排名不一致以人工分为准。这个坑我踩过好几次最后形成的习惯是ROUGE只能用来筛选候选模型不能用来验收。6. 进阶用质量三角和坏例迭代把方案推上线人工评估是摘要方案能否上线的最后一道关卡。我常用的评估表只有三个维度第一事实一致性看摘要里是否有原文没有的信息第二信息完备性看核心结论是否被覆盖第三流畅度看句子是否通顺。每个维度1到5分抽样时把生成摘要和原文放在一起让人不用看模型名字直接打分。多做几轮后你会发现分数低的原因高度集中在一两类坏例上。拿到坏例后把它们单独存成一份dev-set每次模型迭代后先跑这份坏例集看有没有解决。比如某次迭代解决了“重复”问题但引入了“事实错误”那下一次训练就要在数据里加入更多事实不一致的负样本或者把训练目标从交叉熵换成对比学习的形式。这种“坏例驱动”的迭代方式比我早期追求评测集平均分要有效得多。还有一个验证技巧把模型生成的摘要反推回文档用“摘要能否作为检索关键词召回原文”来间接判断信息量。如果摘要里的关键实体足够多召回率会高说明摘要没有丢掉核心信息。这个指标虽然不够严谨但实现成本低适合放在监控面板里每天看。我现在的习惯是每一版新模型都做一次人工评估保留所有坏例到固定目录同时记录当时的生成参数和训练日志。等到项目复盘时翻这些记录比翻任何实验总结都直观。深度学习模型的玄学往往就藏在这些细节里希望这些踩坑经验能帮你少走一段弯路。本文还有配套的精品资源点击获取