简介面向自然语言处理开发者与研究者这份基于Python的BertSum实现围绕BERT微调完成抽取式摘要任务覆盖从文本预处理、预训练模型加载、任务层构建到训练评估与后处理的完整流程。压缩包共36个文件以20个py脚本为主承担数据处理、模型定义和训练评估等核心功能另有7个txt文件对应数据划分映射5个gitignore与2个json辅助环境配置和样本组织README与license便于查阅项目说明整体约14.99MB。项目中可清晰看到bert_data、raw_data、src等模块划分便于对照论文复现实验通过Transformers库加载预训练BERT、在顶部接入编码器-解码器结构并使用ROUGE指标评价生成摘要质量是理解深度文本摘要技术路线的合适实践样本。已有1523人学习浏览适合具备一定Python与深度学习基础、希望将BERT落地到摘要生成任务的读者。1. 用Python微调BERT做提取式摘要论文代码到底在跑什么如果你在学校或者公司接过这样一个任务——把一篇长文档压成三到五句话的摘要——第一次搜到“Python-微调BERT用于提取摘要的论文代码”时大概率以为装个BERT模型就能直接输出摘要。真动手才发现BERT是一个语言模型不是摘要生成器它不会像人一样读完文章后“写”一段话而是先判断哪些句子值得保留再原样抽出来拼成摘要。这个把“抽句子”当作分类任务来微调BERT的思路就是提取式摘要Extractive Summarization论文代码的核心。它能解决的问题很具体新闻稿、论文、裁判文书这种长文本手工写摘要成本太高用微调后的BERT自动挑出关键句准确率能做到接近人工标注水平。这篇笔记适合正在复现论文、做毕业设计或者想用最小代价把摘要功能做进内部系统的人。我会按“任务建模 → 环境跑通 → 数据构造 → 训练调参 → 踩坑记录 → 验证进阶”的顺序把整个方案讲透。2. 提取式摘要的建模思路BERT在这里不是生成器是句子打分器2.1 提取式摘要为什么比生成式摘要更适合BERT微调提取式摘要的本质是“从原文里选句子”而不是“重新组织语言”。这个定位决定了模型结构可以非常朴素把文档切成长度不等的句子逐句判断“这句要不要进摘要”是一个典型的二分类问题。BERT作为预训练语言模型本身具备很强的语义表示能力——CLS向量经过微调后能捕捉句子在整篇文档语境下的重要程度。相比之下生成式摘要需要模型理解全文后逐词生成新句子对算力和数据量的要求高一个量级还要处理重复、幻觉等问题在普通单卡环境下很难跑通。很多论文代码选择提取式路线不是因为生成式效果差而是因为提取式的训练目标和评估指标ROUGE都存在“确定的最优解”复现性更好。我见过不少第一次接触这套代码的人上来就想找“BERT生成摘要”的现成脚本结果发现HuggingFace仓库里全是分类模型权重根本没有摘要专用权重。原因在于BERT原生就不具备从左到右生成文本的注意力掩码结构强行用生成式架构还得改模型头、换训练策略等于把简单问题复杂化了。正确做法是把任务拆成句子切分 → 句子编码 → 二分类打分 → 按分排序截断。这四步里只有“句子编码”和“二分类打分”是BERT负责的其他两步用Python标准库就能做。2.2 论文代码中常见的三种模型结构哪种最适合自己复现看过多篇论文配套代码后会发现提取式摘要的模型结构大致分三类。第一类是Sentence-level分类器最简单把每个句子独立送到BERT里做二分类句子之间互不影响适合数据量小、硬件资源有限的场景。第二类是Document-level编码器把整篇文档所有句子的CLS向量拼起来再接一个BiLSTM或Transformer层让句子之间能“互相看见”。这种结构效果更好但训练时间翻倍显存占用也明显上升。第三类是Token-level指针网络把摘要任务建模成“从原文中按顺序选定句子”解码时用指针指向某一个句子的起点和终点这是很多论文主打SOTA的结构但实现复杂度高新手不建议从这类代码入手。我的建议是如果只是想把“微调BERT做提取摘要”这件事跑通优先选择第一类。理由有四点一是改造空间大后面想要效果提升可以随时在句子编码层加注意力二是训练数据构造简单只需标0/1标签三是单卡RTX 309024GB显存就能训练四是排错容易分模块逐步验证。论文代码里通常把这三类结构分别放在model.py的不同类里复现时先看README要求的数据格式和评估指标再决定保留哪个类、删掉哪个类。以下默认按第一类结构展开这也是90%入门复现代码的落点。2.3 数据从哪来LCSTS与CNN/DailyMail的标签结构差异做提取式摘要微调数据质量直接决定效果。中文场景最常用的论文数据集是LCSTS大连理工发布的短文本摘要数据集每条样本包含原始文本和参考摘要但注意它没有现成的“句子级标签”。要做二分类微调得先把参考摘要和原文句子做对齐用编辑距离或ROUGE-1匹配把每个句子和摘要的相似度算出来超过阈值的句子标为1否则标为0。英文场景则常用CNN/DailyMail它自带每篇文章的人工摘要标注方式类似。两个数据集最大的差异在于文本长度LCSTS每条原文平均100-200字切句后大约5-10句CNN/DailyMail平均800字以上切句后经常超过30句直接导致序列长度和显存策略完全不同。这里必须提醒一个细节LCSTS的短文本摘要很多是“关键词组合”而不是完整句子做对齐时会出现大量句子匹配分数低于阈值的情况最终训练集正负样本比例失衡。我见过有人直接拿LCSTS的摘要原文当标签去和原文句子算ROUGE由于摘要本身是短语拼出来的匹配阈值设0.5会导致正样本只有个位数。常见的解法是降低阈值到0.3同时把“包含摘要中出现名词短语”的句子也标为正样本极端情况下还需要人工修正几十条训练数据。这个预处理步骤在论文代码里通常单独存放在preprocess.py跑通之后第一件事就是打印训练集的正负样本比例低于1:5就要调整标签生成策略。3. 把论文代码跑起来的准备动作环境、权重与第一个最小样例3.1 环境与依赖安装CUDA版本陷阱和Python版本选择不论论文代码是PyTorch还是TensorFlow写的现在主流提取式摘要复现基本都转向PyTorch HuggingFace Transformers。安装环境的顺序比很多人想象的要严格先装CUDA驱动和cuDNN再装PyTorch最后装Transformers和Datasets。反过来的话PyTorch经常会从pip源拉到一个CPU版本训练时才发现跑的不是GPU。用nvidia-smi查看驱动支持的最高CUDA版本然后去PyTorch官网选对应cu版本安装例如CUDA 12.1对应pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121。Python版本我建议3.9或3.10Transformers库在新版本上兼容性最好3.8以下会出现datasets库的map函数类型注解报错。装好依赖后验证环境的代码很短但必须做两件事确认CUDA可用、确认Transformers加载BERT权重不报错。权重下载是第一个容易踩坑的地方——bert-base-chinese和bert-base-uncased两个权重各约400MBHuggingFace官网在国内访问不稳定。论文代码里通常用from_pretrained直接下载如果你发现卡在进度条一动不动需要设置环境变量HF_ENDPOINThttps://hf-mirror.com再用一次。需要说明的是这个操作仅解决HuggingFace模型权重下载问题不涉及任何网络代理方案。3.2 加载BERT权重与Tokenizer的最小可运行代码下面这段代码是论文代码里最基础的启动片段完成“加载模型分词推理”三步验证。它不会训练任何东西只是确认模型和分词器能协同工作。# 导入依赖 from transformers import BertTokenizer, BertForSequenceClassification # 加载BERT分类模型num_labels2表示二分类句子进/不进摘要 tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForSequenceClassification.from_pretrained(bert-base-chinese, num_labels2) # 对“这句话要不要进摘要”做推理 sentence 这是第一篇输入到BERT里的测试句子。 inputs tokenizer(sentence, return_tensorspt, truncationTrue, max_length128) outputs model(**inputs) predictions outputs.logits.argmax(dim-1).item() print(预测标签, predictions) # 0表示不进摘要1表示进摘要这段代码有三个关键点需要理解。第一BertForSequenceClassification在句子开头自动添加[CLS]标记这个位置的最终隐状态会接一个线性分类层输出的logits就是两个类别的得分取argmax得到标签。第二max_length128对中文短文本够用但对CNN/DailyMail这种长句子可能要调到256这个值直接决定显存消耗。第三第一次运行from_pretrained会看到「Downloading」进度条这说明权重正在下载并缓存到~/.cache/huggingface目录第二次运行会直接命中缓存不再联网下载。3.3 论文代码的目录结构与数据入口定位拿到一份真实的论文代码压缩包通常会有train.py、model.py、data_loader.py、utils.py和config.py这几个文件。不要急着运行train.py先把config.py打开找到data_path、model_name、batch_size、max_length、num_epochs这些字段。这一步能省很多时间因为论文代码的默认参数往往是为原作者的GPU准备的直接跑大概率OOM显存不足。接着打开data_loader.py确认它读的是原始文档文件还是已经处理好的特征文件。很多论文代码为了节省重复预处理时间会把分词结果、标签向量提前存成.npy或.pt文件data_loader.py只负责加载。如果你改动了预处理逻辑必须删除这些缓存文件否则训练时用的还是旧数据。最常见的翻车是改了标签生成阈值但没有删缓存训练了一晚上才发现模型学到的还是旧标签分布。最小样例验证到这一步就够了下面进入真正的数据处理和训练环节。4. 构造训练样本与微调参数BERT提取摘要的训练脚本拆解4.1 句子切分与标签对齐先写一个可用的数据预处理脚本要让BERT学会“抽句子”第一步是把文档切句。中文切句比英文麻烦英文按句号切基本准确中文的句号、问号、感叹号都会出现在句子末尾还需处理引号和括号的跨句情况。论文代码里最常用的方案是用re.split()按标点切分再按长度过滤过短的片段。下面展示一个经过实际验证的预处理脚本它输出的是BERT可以直接消费的数据集文件。import re import json from tqdm import tqdm def split_sentences(text): 按中文标点切句子并过滤掉空串和过短片段 parts re.split(r(?[。!?]), text) sentences [] for part in parts: part part.strip() if len(part) 5: # 过滤掉标点残留 continue sentences.append(part) return sentences def build_training_data(src_path, ref_path, out_path, threshold0.3): 把原文和参考摘要对齐生成句子级标签 with open(src_path, r, encodingutf-8) as f_src, \ open(ref_path, r, encodingutf-8) as f_ref, \ open(out_path, w, encodingutf-8) as f_out: for src_line, ref_line in tqdm(zip(f_src, f_ref)): sentences split_sentences(src_line.strip()) ref_text ref_line.strip() for sent in sentences: # 用简单的字符重叠率计算句子与摘要的相似度 overlap len(set(sent) set(ref_text)) / max(len(set(ref_text)), 1) label 1 if overlap threshold else 0 record {sentence: sent, label: label} f_out.write(json.dumps(record, ensure_asciiFalse) \n) # 使用示例 build_training_data(data/src.txt, data/ref.txt, data/train.jsonl, threshold0.3)这个脚本里有三个参数值得推敲。第一threshold0.3是字符重叠率的阈值它决定多少句子被标为正样本。LCSTS数据集实测用0.3能产生大约8%-12%的正样本低于0.2会产生大量低质量正样本噪声高于0.4则正样本过少模型会学成“全预测0”。第二len(part) 5的过滤条件很关键直接去掉标点残留和短词碎片否则模型会看到大量“的”“了”这种无意义“句子”。第三输出用JSONL格式而非CSV原因是句子中包含逗号时CSV会出现列错位JSONL天然规避了转义问题且后续用datasets.load_dataset读取更顺滑。4.2 训练循环与关键参数从HuggingFace Trainer到原生PyTorch数据准备好之后训练环节有两种做法。论文代码里最常见的是用Transformers库的Trainer封装因为它自动处理了梯度累积、学习率调度、评估循环和断点保存。对大模型微调实战来说Trainer是快速验证效果的首选。下面这段代码是我经常用的训练启动脚本骨架改三个参数就能跑通大多数论文的基线模型。from transformers import BertForSequenceClassification, Trainer, TrainingArguments from datasets import load_dataset # 读取上一步生成的JSONL文件 dataset load_dataset(json, data_filesdata/train.jsonl, splittrain) dataset dataset.train_test_split(test_size0.1, seed42) # 定义分词函数 def tokenize_function(examples): return tokenizer( examples[sentence], truncationTrue, max_length128, # 根据文本长度调整长文档可以设256 paddingmax_length, ) tokenized_dataset dataset.map(tokenize_function, batchedTrue) # 训练参数 training_args TrainingArguments( output_dir./checkpoints, evaluation_strategyepoch, save_strategyepoch, learning_rate2e-5, # BERT微调的标准初始学习率 per_device_train_batch_size16, # 24GB显存可设328GB显存建议8 per_device_eval_batch_size32, num_train_epochs3, weight_decay0.01, load_best_model_at_endTrue, logging_dir./logs, ) # 初始化模型 model BertForSequenceClassification.from_pretrained( bert-base-chinese, num_labels2 ) trainer Trainer( modelmodel, argstraining_args, train_datasettokenized_dataset[train], eval_datasettokenized_dataset[test], ) trainer.train()这里有几个参数会影响训练能否收敛。learning_rate2e-5是BERT微调的经典起点调大可能出现loss震荡调小则收敛过慢per_device_train_batch_size受显存限制在8GB显存环境下要降到8同时观察单步耗时——如果单步超过0.5秒说明batch需要继续缩小num_train_epochs3对大多数提取式摘要数据够了跑更多轮次容易过拟合表现是训练集准确率接近100%、验证集ROUGE不再提升。还有一个容易忽略的参数是weight_decay0.01它防止非bias和LayerNorm参数出现极端权重不加这个参数时验证集loss通常会高0.1-0.2个点。4.3 序列长度与句级编码长文档的截断策略决定上限BERT的max_length限制是512个token而CNN/DailyMail的文章经常超过这个长度。论文代码里常见的处理策略有三种第一种是硬截断只取前512 token简单但会丢掉文档后段的关键句第二种是分块编码把长文档切成多段每段分别过BERT再池化效果最好但训练成本翻倍第三种是滑窗重叠相邻窗口重叠50个token能缓解关键句刚好被切在两块之间的问题。做工程落地时我一般优先试“按句编号、句子级滑窗”的方案把所有句子的CLS向量拼成一个序列超过长度时保留开头和结尾的句子因为论文摘要的关键信息通常分布在首尾。如果训练时发现显存不够除了减小batch另一个有效做法是冻结BERT底层。在模型初始化后把前几层的参数requires_grad设为False只训练高层和分类头。这样显存占用下降约30%效果只损失1-2个ROUGE点。对论文复现来说多跑几个基线模型对比效果比追求单个模型最高分更重要所以冻结底层换训练速度通常是划算的。5. 微调BERT做提取摘要的避坑记录五个高频翻车点5.1 数据预处理阶段正样本为0模型永远预测“不进摘要”现象训练几轮后模型对验证集所有句子的预测都是0ROUGE指标静止为0。原因标签对齐逻辑有缺陷。最常见的是threshold设太高或者参考摘要是短语而非句子字符重叠率普遍低于阈值导致正样本占比不足1%。模型看到几乎全是负样本学到的最优策略就是全预测0。解决先统计数据集中标签分布打印正样本条数和比例。正负比至少做到1:10才有训练意义。调低threshold到0.3以下同时检查数据清洗逻辑——参考摘要两端如果有换行符或多余空格会把所有重叠率都拉低。5.2 分词阶段中英文混排文本把BERT整懵现象中文论文摘要里混着英文缩写如BERT、GPU、ROUGETokenizer把英文拆成了未知词[UNK]关键术语直接丢失模型怎么调都学不好。原因bert-base-chinese的分词器是字级分词英文单词会被切碎超过词表范围就变成[UNK]。这是中文预训练模型的通病不是代码问题。解决预处理时对英文片段做保护替换将连续英文子串替换成占位符比如_ENG_等模型输出后再映射回原词。如果数据里英文比例很高直接换用bert-base-multilingual-cased它对英文支持更好代价是模型体积更大、推理稍慢。5.3 训练阶段Loss下降但验证集ROUGE不涨现象训练loss从0.7降到0.3但验证集ROUGE-1只有20左右和随机抽取差不多。原因二分类准确率高不等于摘要质量高因为“是否进摘要”的标签是在数据预处理阶段用启发式规则生成的本身含噪声。模型可能学会了“句子越长越重要”这种捷径而不是真正抓住语义重点。解决先检查训练集里是不是大量长句子被标为正样本。如果是说明标签生成逻辑有偏。改进方法是用ROUGE-L分数替代字符重叠率计算句子和参考摘要的最长公共子序列能更准确地衡量句子级重要性。ROUGE-L的计算直接调rouge_score库不要在预处理里自己实现。5.4 推理阶段输出的摘要句子数量不可控有时只有一句有时贴满全篇现象对一篇30句的文章做预测模型只挑出1句换一篇10句的文章模型又挑出9句摘要长度完全不受控。原因模型是逐句独立打分没有考虑摘要的总长度约束。论文代码里的测试脚本通常只做了“按分数排序”没有做“挑出前N句”的长度限制。解决在推理时设定摘要求句数上限。最稳的方式是按句子的相对位置均匀取样先按分数排序再按排名做非极大值抑制相邻句子的分数如果差距小于0.1就保留排名靠前的一句。这能避免连续段落被重复选出。实际工程中我习惯把摘要长度和文档长度挂钩文档少于10句取3句10-30句取5句30句以上取8句。5.5 显存不足8GB显卡训练BERT直接OOM现象batch_size设为16运行第一个epoch不到一半就报CUDA out of memory。原因BERT模型参数量约1.1亿光是模型权重就占400MB加上梯度、优化器状态和中间激活值8GB显存下16的batch确实会超。论文代码的默认参数通常是在24GB以上显存跑出来的。解决按顺序做三件事——batch_size降到4启用梯度累积gradient_accumulation_steps4在模型初始化后冻结前8层BERT参数。三个手段叠加后显存占用能压到5GB以内训练速度放慢但可以正常跑完。如果还不行换用bert-base-chinese的4层蒸馏版本distilbert-base-chinese效果损失大约3-5个ROUGE点但显存需求直接减半。6. 进阶验证与部署ROUGE评估的正确姿势与ONNX推理加速提取式摘要模型的最终价值体现在效度验证上。论文代码里虽然自带评估脚本但直接跑的人经常犯一个错误把训练时的二分类准确率当作模型效果其实这个指标没有意义。正确的验证方式是用ROUGE-1、ROUGE-2和ROUGE-L三个指标把模型生成的摘要和人工参考摘要对比。ROUGE-1衡量单字/词重叠ROUGE-2衡量相邻词对的重叠ROUGE-L衡量最长公共子序列三者同时看才能判断摘要的忠实度和信息量。代码实现上用rouge_score库可以一行完成关键是保存模型输出摘要时要保留原始句子文本不要把tokenizer解码后的字符串当摘要直接去算ROUGE——解码过程可能会丢掉标点和空格导致分数异常偏低。部署环节如果对推理速度有要求我建议把微调后的模型导出成ONNX格式。BERT分类模型导出后推理速度能提升1.5到2倍显存占用下降20%。导出的方式是用transformers.onnx包的export函数指定featuresequence-classification结束后用onnxruntime加载并替换from_pretrained的调用。注意ONNX模型对动态轴支持有限如果推理时句子长度忽长忽短需要在导出时固定max_length否则会报维度不匹配。我一般固定为128长文档分段处理后再汇总实测在CPU上单条推理也能控制在30毫秒以内。希望这份从数据预处理到训练调参再到部署验证的完整拆解能帮你把论文代码真正跑起来。如果只记一句话我的习惯是拿到任何摘要论文代码先看数据预处理里的标签生成方式再决定要不要信它的实验结果——很多模型效果不佳问题根本不在模型结构而在数据对齐这一步就已经把信息丢掉了。希望帮到你。本文还有配套的精品资源点击获取