news 2026/10/20 13:19:28

BART中文摘要实战:从数据准备到推理优化的全流程指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BART中文摘要实战:从数据准备到推理优化的全流程指南

中文自动摘要这个任务,说简单也简单,说坑也多。简单在于,如果你只是拿个预训练模型跑个demo,几行代码就能出结果;坑多在于,当你真正想把它用到业务里,会发现生成的结果要么重复啰嗦,要么漏掉关键信息,要么干脆把原文抄了一遍。我用BART做中文摘要有一段时间了,从最初的直接调用预训练权重,到后来自己整理数据做微调,中间踩了不少坑,也积累了一些实操经验。这篇文章就把整个流程拆开揉碎讲清楚,从模型选型、数据准备、训练配置到推理优化,每一步都给出可复现的方案和参数依据。适合有一定PyTorch基础、想快速上手中文摘要的开发者,也适合已经在做摘要但效果不理想的同学参考。

1. 中文摘要任务的整体设计与模型选型

1.1 为什么选BART而不是T5或GPT

做中文摘要,模型架构的选择直接决定了后续的训练成本和生成质量。目前主流方案有三类:以BART为代表的去噪自编码器、以T5为代表的文本到文本统一框架、以及以GPT为代表的纯解码器架构。我最初也纠结过选哪个,后来通过对比实验发现,BART在摘要任务上的表现确实更稳。

原因在于BART的预训练方式。它采用“破坏文本再重建”的策略,具体来说是把原文用多种方式打乱(比如随机遮盖、句子重排、文本填充),然后让模型学会恢复原始文本。这种训练目标天然适合摘要任务,因为摘要本质上就是从长文本中提取核心信息并重新组织,和BART的预训练目标高度一致。T5虽然也是编码器-解码器结构,但它的预训练任务更偏向多任务混合,在中文摘要上的收敛速度往往不如BART。GPT系列是纯解码器,生成流畅度好,但容易偏离原文事实,做摘要时幻觉问题比较明显。

具体到中文场景,我推荐使用fnlp/bart-base-chinese这个预训练权重。它是基于中文语料训练的BART模型,词表覆盖了常见汉字和词组,编码器6层、解码器6层,隐藏层维度768,注意力头数12,总参数量约1.4亿。这个规模在单张消费级显卡上就能微调,推理速度也够用。如果你追求更好的效果,可以上bart-large-chinese,参数量翻倍到4亿左右,但显存需求会明显增加。

提示:不要直接用英文BART权重做中文摘要,词表不匹配会导致大量未知token,生成结果基本不可用。

1.2 摘要任务的核心难点与应对思路

中文摘要的难点和英文不太一样。英文有空格分词,中文需要先做分词或依赖模型自身的tokenizer。BART的中文tokenizer是基于BPE的,能较好地处理未登录词,但在处理数字、专有名词时仍可能出现切分错误。我遇到过把“2023年”切成“202”“3”“年”的情况,虽然不影响理解,但生成时容易丢字。

另一个难点是长文本处理。BART的位置编码最大长度通常是1024,但很多新闻或报告远超这个长度。直接截断会丢失关键信息,我的做法是先用TextRank或Lead-3提取关键句,把原文压缩到800字以内再送入模型。这样既保留了核心信息,又避免了截断带来的信息损失。

还有一个容易被忽视的问题是重复生成。BART在解码时如果没加约束,容易反复输出同一句话。解决办法是在生成时启用no_repeat_ngram_size参数,通常设为3,即禁止3-gram重复。同时配合repetition_penalty设为1.2左右,能有效抑制重复。

1.3 整体流程设计

整个中文摘要系统的流程可以拆成四步:数据准备、模型微调、推理生成、后处理。数据准备阶段需要收集原文-摘要对,清洗格式,划分训练集和验证集。模型微调阶段加载预训练权重,设置训练参数,监控损失和ROUGE指标。推理生成阶段用beam search或采样策略生成摘要,控制长度和重复。后处理阶段做去重、截断、标点修正。

这个流程看起来线性,但实际操作中需要反复迭代。比如训练时发现验证集ROUGE不涨,可能要回头检查数据质量;推理时发现生成结果太短,可能要调整min_length和max_length的比例。我建议先把流程跑通,再逐步调优每个环节。

2. 数据准备与预处理的关键细节

2.1 中文摘要数据集的获取与清洗

公开的中文摘要数据集不多,常用的有LCSTS(中文微博摘要)、NLPCC摘要任务数据、以及一些新闻摘要数据集。LCSTS有约240万条微博原文-摘要对,但质量参差不齐,很多摘要就是原文的前几个字。NLPCC的数据质量更高,但规模较小,约5万条。我通常把LCSTS做预训练,再用NLPCC做精调。

清洗数据时要注意几个点。第一,过滤掉摘要长度小于10个字的样本,这类摘要信息量太少,训练时容易让模型学会“偷懒”。第二,过滤掉原文和摘要完全相同的样本,这是数据标注错误。第三,检查特殊字符,比如HTML标签、乱码、多余空格,用正则统一清理。第四,对原文做长度过滤,保留50到1000字之间的样本,太短的没必要摘要,太长的截断或分段处理。

import re def clean_text(text): text = re.sub(r'<[^>]+>', '', text) # 去HTML标签 text = re.sub(r'\s+', ' ', text) # 合并空白 text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9,。!?、;:""''()]', '', text) return text.strip() def filter_sample(example): src = clean_text(example['text']) tgt = clean_text(example['summary']) if len(tgt) < 10 or len(src) < 50: return False if src == tgt: return False return True

2.2 数据格式转换与Tokenization

BART的输入需要转成token id序列。用HuggingFace的BartTokenizer时,注意设置max_source_length和max_target_length。我通常把原文截断到512个token,摘要截断到128个token。这个比例是根据中文摘要的统计规律定的:摘要长度一般是原文的10%到20%,512的20%约100,留些余量到128。

Tokenization时有个细节:BART的tokenizer默认会在序列末尾加</s>,这是解码器的起始符。编码器输入不需要这个,所以要对原文和摘要分别处理。原文用tokenizer(text, max_length=512, truncation=True, padding='max_length'),摘要用tokenizer(summary, max_length=128, truncation=True, padding='max_length')。注意摘要的labels要把padding位置的token id设为-100,这样损失计算时会忽略这些位置。

from transformers import BartTokenizer tokenizer = BartTokenizer.from_pretrained('fnlp/bart-base-chinese') def preprocess(example): inputs = tokenizer( example['text'], max_length=512, truncation=True, padding='max_length', return_tensors='pt' ) with tokenizer.as_target_tokenizer(): labels = tokenizer( example['summary'], max_length=128, truncation=True, padding='max_length', return_tensors='pt' ) labels['input_ids'][labels['input_ids'] == tokenizer.pad_token_id] = -100 return { 'input_ids': inputs['input_ids'].squeeze(), 'attention_mask': inputs['attention_mask'].squeeze(), 'labels': labels['input_ids'].squeeze() }

2.3 数据划分与类别平衡

训练集、验证集、测试集的比例我一般按8:1:1划分。如果数据量超过10万,可以按98:1:1,验证集不用太大,几千条足够监控过拟合。划分时要保证随机性,用train_test_split设置random_state固定种子,方便复现。

类别平衡方面,中文摘要数据往往存在主题分布不均的问题,比如娱乐新闻远多于科技新闻。如果业务场景对某些领域有偏好,可以在训练时对样本加权,或者对少数领域做数据增强。我试过用同义词替换和句子重排做增强,但效果有限,不如直接多收集一些目标领域的数据。

注意:验证集和测试集必须来自同一分布,否则ROUGE指标会虚高。我见过有人用微博数据训练,用新闻数据测试,结果ROUGE-1只有0.2出头,完全没法用。

3. 模型微调与训练配置实战

3.1 训练环境与超参数设置

微调BART-base-chinese,单张12GB显存的显卡就够。batch size设8,梯度累积步数设4,等效batch size为32。学习率用3e-5,这是BART微调的常用值,太大容易震荡,太小收敛慢。优化器选AdamW,权重衰减0.01。学习率调度用线性预热,预热步数占总步数的10%。

训练轮数一般3到5轮就够了。我试过训10轮,第4轮之后验证集ROUGE就开始下降,明显过拟合。早停策略设patience=2,即验证集指标连续2轮不提升就停止。模型保存时只保留验证集ROUGE最高的checkpoint,不要保存最后一轮的,因为最后一轮往往过拟合。

from transformers import BartForConditionalGeneration, AdamW, get_linear_schedule_with_warmup model = BartForConditionalGeneration.from_pretrained('fnlp/bart-base-chinese') optimizer = AdamW(model.parameters(), lr=3e-5, weight_decay=0.01) total_steps = len(train_loader) * 5 // 4 # 梯度累积 scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(0.1 * total_steps), num_training_steps=total_steps )

3.2 训练过程中的监控与调优

训练时要监控三个指标:训练损失、验证损失、验证集ROUGE。训练损失下降但验证损失上升,说明过拟合,要早停或加正则。验证损失下降但ROUGE不涨,可能是生成策略有问题,比如beam search的beam数太小。

我习惯每500步评估一次,用generate方法生成验证集的摘要,计算ROUGE-1、ROUGE-2、ROUGE-L。ROUGE-1反映词汇重叠,ROUGE-2反映短语连贯性,ROUGE-L反映句子结构。中文摘要通常ROUGE-1在0.4左右,ROUGE-2在0.2左右,ROUGE-L在0.35左右算不错。如果ROUGE-1超过0.5,要警惕是不是数据泄漏了。

生成时用num_beams=4,length_penalty=2.0,no_repeat_ngram_size=3。length_penalty大于1会鼓励生成更长的摘要,中文摘要通常需要这个,因为模型倾向于生成短句。early_stopping=True让beam search在找到完整候选后提前结束,加速推理。

3.3 显存优化与混合精度训练

如果显存不够,可以开启混合精度训练。用torch.cuda.amp的autocast和GradScaler,显存占用能降低30%左右,训练速度提升20%。注意混合精度下损失缩放要正确,GradScaler会自动处理。

from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() for batch in train_loader: optimizer.zero_grad() with autocast(): outputs = model(**batch) loss = outputs.loss scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() scheduler.step()

如果还是不够,可以减小batch size到4,梯度累积步数加到8。或者用gradient_checkpointing,用时间换空间,显存能再降40%,但训练速度会慢一些。我一般优先用混合精度,实在不行再上gradient checkpointing。

提示:混合精度训练时,labels里的-100不会被自动转换,需要确保它们保持为整数类型,否则损失计算会出错。

4. 推理生成与后处理优化

4.1 生成策略的选择与参数调优

推理阶段的核心是生成策略。贪心搜索速度快但容易重复,beam search质量好但慢。我通常用beam search,beam数设4到6。beam数太大收益递减,8以上基本没提升,反而显存翻倍。

关键参数有三个:max_length、min_length、length_penalty。max_length设128,min_length设30,防止生成过短。length_penalty设2.0,鼓励生成更完整的摘要。no_repeat_ngram_size设3,禁止3-gram重复。repetition_penalty设1.2,进一步抑制重复。

def generate_summary(text): inputs = tokenizer(text, max_length=512, truncation=True, return_tensors='pt') with torch.no_grad(): summary_ids = model.generate( inputs['input_ids'].to(device), attention_mask=inputs['attention_mask'].to(device), max_length=128, min_length=30, num_beams=4, length_penalty=2.0, no_repeat_ngram_size=3, repetition_penalty=1.2, early_stopping=True ) return tokenizer.decode(summary_ids[0], skip_special_tokens=True)

4.2 后处理:去重、截断与标点修正

模型生成的摘要常有重复词和多余标点。我写了一个后处理函数,先用正则去掉连续重复的短语,再修正常见标点错误,比如连续逗号、句末缺句号。如果摘要超过指定长度,按句号截断,保证语义完整。

def postprocess(summary): summary = re.sub(r'(.{2,}?)\1+', r'\1', summary) # 去连续重复 summary = re.sub(r'[,,]{2,}', ',', summary) # 合并逗号 summary = re.sub(r'[。.]{2,}', '。', summary) # 合并句号 if not summary.endswith(('。', '!', '?')): summary += '。' return summary

4.3 长文本摘要的分段与融合

超过512字的原文,我采用分段摘要再融合的策略。先用TextRank提取关键句,把原文压缩到800字以内,再按512字切分,每段生成摘要,最后把各段摘要拼接,再用一次BART做摘要融合。这样能处理任意长度的文本,但计算量会翻倍。

TextRank的实现可以用jieba.analyse.textrank,提取前20个关键句。注意关键句要按原文顺序排列,不能打乱,否则摘要逻辑会乱。融合阶段把各段摘要拼成一段,再送入模型生成最终摘要,max_length设150,min_length设50。

注意:分段摘要时,段与段之间要有重叠,通常重叠50字,避免关键信息被切断。重叠部分在融合时会自然去重。

5. 常见问题排查与避坑经验

5.1 生成结果重复或循环

这是最常见的问题。原因通常是解码时没有加重复惩罚,或者训练数据里有大量重复样本。解决办法:训练时过滤重复样本,推理时加no_repeat_ngram_size=3和repetition_penalty=1.2。如果还不行,试试num_beams=1用贪心搜索,有时beam search反而会陷入循环。

我遇到过一次,模型反复输出“的的的的”,查了半天发现是tokenizer的pad_token和eos_token设成了同一个,导致解码时无法正确终止。检查tokenizer配置,确保pad_token_id、eos_token_id、decoder_start_token_id互不相同。

5.2 ROUGE指标不涨或下降

训练几轮后ROUGE不涨,先检查数据质量。用pandas抽样看几条原文和摘要,确认摘要不是原文的截断。再检查tokenization,看原文和摘要的token长度分布,如果摘要平均长度只有20个token,说明摘要太短,模型学不到东西。

如果数据没问题,调学习率。3e-5不行就试1e-5,再不行试5e-5。学习率太大导致震荡,太小导致欠拟合。还可以试不同的length_penalty,1.0到2.5之间调,看哪个ROUGE最高。

5.3 显存溢出与训练中断

显存溢出通常发生在batch size太大或序列太长时。解决办法:减小batch size,缩短max_source_length,开启混合精度,用gradient checkpointing。如果训练中途中断,检查是不是某个batch的序列特别长,可以用DataLoader的collate_fn做动态padding,按batch内最长序列padding,而不是全局padding到512。

def collate_fn(batch): input_ids = [item['input_ids'] for item in batch] labels = [item['labels'] for item in batch] input_ids = torch.nn.utils.rnn.pad_sequence(input_ids, batch_first=True, padding_value=tokenizer.pad_token_id) labels = torch.nn.utils.rnn.pad_sequence(labels, batch_first=True, padding_value=-100) attention_mask = (input_ids != tokenizer.pad_token_id).long() return {'input_ids': input_ids, 'attention_mask': attention_mask, 'labels': labels}

5.4 常见问题速查表

问题现象可能原因解决办法
生成结果重复无重复惩罚加no_repeat_ngram_size=3和repetition_penalty=1.2
ROUGE不涨学习率不当试1e-5、3e-5、5e-5
显存溢出batch太大减小batch,开混合精度
摘要太短min_length太小设min_length=30
摘要偏离原文幻觉降低temperature,用beam search
训练不收敛数据噪声清洗数据,过滤短摘要
推理速度慢beam数太大减到4,或改用贪心
标点混乱后处理缺失加正则修正

6. 效果评估与迭代方向

6.1 自动评估与人工评估结合

ROUGE是自动评估的标配,但它只看词汇重叠,不看语义。我通常ROUGE和人工评估一起做。人工评估抽100条,从流畅性、信息量、事实一致性三个维度打分,每个维度1到5分。流畅性看句子通顺与否,信息量看是否覆盖原文要点,事实一致性看有没有编造内容。

如果ROUGE高但人工评分低,说明模型在“抄”原文,没有真正理解。这时候要检查训练数据,看摘要是不是原文的简单截断。如果ROUGE低但人工评分高,说明模型生成的摘要用词不同但语义正确,这种情况可以接受,甚至更好。

6.2 领域适配与持续学习

通用摘要模型在特定领域往往表现不佳。比如医疗摘要需要保留专业术语,法律摘要需要精确引用条款。我的做法是在通用模型基础上,用领域数据做继续预训练,然后再微调摘要任务。领域数据不需要标注摘要,只要原文就行,用BART的去噪目标做自监督训练。

持续学习方面,如果业务数据不断更新,可以定期用新数据微调模型,但要注意灾难性遗忘。我通常保留10%的通用数据混在领域数据里一起训练,这样模型不会忘记通用能力。

6.3 模型压缩与部署优化

如果要在边缘设备部署,模型压缩是必须的。我试过用torch.quantization做动态量化,模型大小从500MB降到150MB,推理速度提升2倍,ROUGE只降了0.02。还可以用ONNX导出,再用ONNX Runtime推理,速度比PyTorch快30%左右。

import torch.quantization model.eval() quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, dtype=torch.qint8 ) torch.save(quantized_model.state_dict(), 'bart_quantized.pt')

部署时用FastAPI包一个HTTP接口,输入原文,输出摘要。注意加超时和限流,防止长文本把服务拖垮。我一般设超时10秒,超过就返回“摘要生成超时,请缩短输入”。

提示:量化后的模型在CPU上推理更快,但GPU上可能没优势,因为GPU对int8的支持不如CPU成熟。部署前先测一下目标硬件的实际速度。

7. 实操心得与经验总结

7.1 数据质量决定上限

我做过对比实验,同样的模型和参数,用清洗过的数据比用原始数据,ROUGE-1能差0.05以上。数据清洗的投入绝对值得。具体来说,过滤短摘要、去重、修正标点、统一全半角,这些操作花不了多少时间,但效果立竿见影。

还有一个经验:摘要长度分布要均匀。如果训练数据里摘要都是20字左右,模型就只会生成20字的摘要。我通常把摘要按长度分桶,每个桶的样本数差不多,这样模型能学会生成不同长度的摘要。

7.2 训练参数不要照搬

网上的教程往往给一套参数就完事,但实际数据不同,最优参数也不同。学习率、batch size、beam数这些都要根据验证集表现调。我一般先跑一个小规模实验,用1万条数据训2轮,快速试几组参数,找到大致范围后再全量训练。

length_penalty这个参数特别敏感。中文摘要通常需要1.5到2.5之间,英文摘要1.0到1.5就够了。我试过设3.0,结果生成了一堆废话,把原文的细节都抄进去了。

7.3 推理速度与质量的权衡

beam search的beam数从4加到8,ROUGE可能只涨0.01,但推理时间翻倍。如果业务对延迟敏感,beam数设2或3就够了,或者用贪心搜索加重复惩罚。我实测下来,beam=3在质量和速度之间平衡最好。

还有一个技巧:用torch.no_grad()包裹推理代码,能省不少显存。如果批量推理,把多条原文拼成一个batch,比逐条推理快很多。但要注意padding,不同长度的原文padding到同一长度会浪费计算,用动态padding按batch内最长序列padding。

7.4 后处理不能省

模型生成的摘要直接输出往往不能看,标点混乱、重复词、缺句号都是常事。后处理函数虽然简单,但能显著提升可读性。我建议把后处理做成可配置的,不同业务场景用不同的规则。比如新闻摘要需要保留数字和日期,社交摘要可以去掉表情符号。

最后分享一个小技巧:如果生成的摘要开头总是“本文介绍了”或“据悉”,可以在后处理里用正则去掉这些模板化开头。这些词往往是训练数据里的高频模式,模型学会了偷懒。去掉之后摘要更简洁,信息密度更高。

这个方案后续还可以扩展的方向包括:多文档摘要(把多篇相关文章合成一篇摘要)、可控摘要(指定摘要长度或关键词)、跨语言摘要(中文原文生成英文摘要)。每个方向都有不同的技术挑战,但核心流程和BART的使用方法是相通的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 13:19:06

Linux常用命令实战指南:从文件操作到排查技巧的完整梳理

刚接触 Linux 的时候&#xff0c;我也找过一份所谓“Linux 常见命令集合”&#xff0c;复制到笔记里当作字典来背。结果可想而知&#xff1a;背了三天&#xff0c;连文件权限数字对应的含义都记不牢。后来在实际项目里摸爬滚打&#xff0c;才慢慢明白&#xff0c;命令不是背出来…

作者头像 李华
网站建设 2026/10/20 13:17:29

Leaflet离线地图方案:内网部署、瓦片下载与坐标系转换实践

简介&#xff1a;一套面向Web前端与GIS开发者的LeafLet离线地图示例合集&#xff0c;定位是解决离线场景下地图开发缺少可直接运行参考的问题。内容覆盖地图初始化、图层切换与管理、Marker标记与Popup弹窗、事件监听、GeoJSON几何绘制、离线瓦片加载与缓存等主干功能&#xff…

作者头像 李华
网站建设 2026/10/20 13:16:53

SSH升级加固实战:基于RPM打包的OpenSSH与OpenSSL升级脚本解析

简介&#xff1a;面向RockyLinux9.5服务器运维与安全管理人员&#xff0c;这套升级加固包用于将SSH服务升级至10.0p1版本、将OpenSSL库升级至3.5.0版本&#xff0c;并完成基线安全加固&#xff0c;适用于SSH高危漏洞应急修复、等保合规整改、内网安全加固或版本迭代升级等运维任…

作者头像 李华
网站建设 2026/10/20 13:16:47

Gompertz各向异性扩散图像去噪Matlab实现与调参技巧

简介&#xff1a;基于Gompertz分布函数的各向异性扩散滤波Matlab代码包&#xff0c;专为图像去噪与边缘保持场景设计&#xff0c;适用于计算机、电子信息工程、数学等专业学生的课程设计、期末大作业和毕业设计。包内共15个文件&#xff0c;含2个m格式主程序、7个tif与6个png格…

作者头像 李华
网站建设 2026/10/20 13:15:15

上下文模式实战:显式上下文管理提升代码可维护性

1. 从“上下文模式”说起&#xff1a;一个被低估的工程概念第一次看到“context-mode”这个词&#xff0c;很多人会下意识觉得它是个抽象到没法落地的概念。但如果你写过稍微复杂一点的系统&#xff0c;或者维护过一个跑了半年以上的项目&#xff0c;你就会发现&#xff1a;上下…

作者头像 李华
网站建设 2026/10/20 13:13:55

在 Switch 大气层装 wiliwili 的完整指南:B站客户端从安装到看片

在 Switch 大气层装 wiliwili 的完整指南&#xff1a;B站客户端从安装到看片 【免费下载链接】wiliwili 第三方B站客户端&#xff0c;目前可以运行在PC全平台、PSVita、PS4 、Xbox 和 Nintendo Switch上 项目地址: https://gitcode.com/GitHub_Trending/wi/wiliwili 想把…

作者头像 李华