简介面向需要快速实现中文文本摘要的Python开发者这套代码基于GPT-2模型提供完整可运行的中文摘要生成实现。内容覆盖transformers与torch环境配置、预训练中文模型加载、文本分句编码、generate参数调优以及tokenizer解码等关键环节并额外讨论Finetuning、BERTSUM、Lead-3等优化思路适合NLP入门者或希望在项目中落地摘要能力的技术人员参考。包内共108个文件以105个Python脚本为主辅以2个说明文档和1个模型配置文件压缩包仅458KBpy文件按功能拆分模块边界清晰便于逐个调试与二次开发。资源已有3580人下载学习可直接在本地运行并替换任意Hugging Face中文GPT-2模型权重帮助理解摘要生成全链路也可作为课程设计或工程实践的起步模板。1. 为什么“基于 Python 的 GPT2 中文摘要生成模型代码实现”值得自己跑一遍写摘要这件事很多团队第一反应是调大模型 API但落到具体场景——新闻聚合、客服工单归纳、内部文档缩减——你会发现通用接口要么贵要么格式不合。于是自然想到一个问题用 GPT-2 这种开源模型自己在 Python 里跑一套中文摘要生成到底可不可行答案是可不可行取决于你怎么用。GPT-2 不是专门为摘要设计的但它作为 decoder-only 的语言模型在微调之后做生成式摘要效果能超过不少团队用 BERT 拼接出的“抽取式”方案。这条路适合有 Python 基础、熟悉基础 NLP、想绕过 API 依赖的开发者。下面这套代码和参数基本能让你从一个空白目录跑到能出摘要的检查点。2. 选型与数据准备GPT-2 凭什么能做中文摘要2.1 生成式和抽取式的差别以及为什么这里选生成式文本摘要分两类。抽取式从原文里挑几句话拼成摘要实话说实现简单训练也快但句子级别拼接容易读不通。生成式要模型自己写新句子难度上一个台阶可摘要质量上限高得多。GPT-2 属于生成式阵营它没有 encoder 模块输入直接是 token 序列预测下一个 token。摘要任务在它眼里就是“给定原文 起始提示续写出概括内容”。这个建模方式有个好处模型从头到尾用的是同一套语言建模损失不需要像 BERT 那样为分类头单独加任务层。中文场景下选择 GPT-2 还有个现实理由中文不需要刻意做形态还原词边界本身就是个问题而 GPT-2 用 tokenizer 把文本切成子词天然避开“词表爆炸”。对比 T5 和 BART这两者做摘要效果确实更强但它们属于 encoder-decoder 结构训练开销比 GPT-2 大得多。如果你只有一块消费级显卡GPT-2 的 decoder-only 结构能在可控资源下跑出可用结果。2.2 中文数据清洗三个步骤决定训练成败数据比模型更决定摘要的下限。中文摘要数据集常见的格式是一组 JSON 行每条有source和summary两个字段。清洗时我一般会做三件事。第一去除重定向字符和 HTML 标签中文新闻源里的p标签经常混进来第二统一标点全角半角混合会让 tokenizer 把同一句话切出两种表达第三长度过滤原文超过 512 token 的干脆丢掉摘要短于 10 个字或长于原文一半的也不留。这里有个容易忽略的点GPT-2 的 tokenizer 会把连续数字或英文切成子词中文标点另占 token。所以清洗时把数字统一成半角写法能避免同一个数字被切出多种组合变相减少模型要记忆的无关模式。清洗完的数据集尽量按 95:5 切分训练和验证集验证集要保证抽样不要取最后一段否则摘要主题都集中在某个领域。2.3 构建训练样本从文本到 input_idsGPT-2 训练摘要的基本输入形式是“原文 分隔符 摘要 结束符”。分隔符我在实践里直接用[SEP]在 tokenizer 里注册成特殊 token。代码里最核心的是截断策略。# build_samples.py # 把 (原文, 摘要) 对拼成 GPT-2 接受的文本序列并截断 import json import torch from transformers import GPT2Tokenizer SEP [SEP] def build_samples(data_path, tokenizer, max_src_len512, max_tgt_len128): samples [] with open(data_path, r, encodingutf-8) as f: for line in f: item json.loads(line) src item[source] tgt item[summary] # 拼接后一次性编码避免两次 encode 在边界处产生冗余空格 text src SEP tgt SEP ids tokenizer.encode(text) # 整体超过上限时从尾部截断会丢摘要所以先截原文 if len(ids) max_src_len max_tgt_len: src_ids tokenizer.encode(src, truncationTrue, max_lengthmax_src_len) tgt_ids tokenizer.encode(tgt SEP, truncationTrue, max_lengthmax_tgt_len) ids src_ids tgt_ids samples.append(ids) return samples这段代码先对整个拼接文本做一次编码超长时再分别截断原文和摘要。原因是直接tokenizer.encode(text, max_length...)在超出长度时只保留前max_length个 token摘要作为后半部分基本会被截没。分开截断至少保证摘要尾部最关键的结尾标记还在。注册[SEP]的方法是用这两种 tokenizer 方法之一tokenizer.add_special_tokens({additional_special_tokens: [SEP]})随后一定要重置语言模型的 token 嵌入尺寸否则模型不知道新 token 的嵌入向量。注意截断对象不同训练效果差别很大。把摘要截没等于给模型灌了“原文原文”它会学成复读机这是后面避坑章节里最常看到的病。3. 训练脚本怎么搭加载 GPT-2、写 DataLoader、跑通最小训练循环3.1 用 HuggingFace 加载 GPT-2 与中文分词器加载模型用transformers库里的GPT2LMHeadModel中文预训练权重通常选uer/gpt2-chinese-cluecorpussmall这个权重在中文语料上做过继续预训练直接用它微调摘要任务比随机初始化省一个月时间。加载完成后的第一件大事是设置pad_tokenGPT-2 原始权重里只定义了eos_token没有 pad tokenDataLoader 里做 padding 时会直接报错。# load_model.py # 加载 GPT-2 中文模型并完成必要设置 from transformers import GPT2LMHeadModel, GPT2Tokenizer tokenizer GPT2Tokenizer.from_pretrained(uer/gpt2-chinese-cluecorpussmall) model GPT2LMHeadModel.from_pretrained(uer/gpt2-chinese-cluecorpussmall) # GPT-2 没有 pad_token需要手动用 eos_token 充当 tokenizer.pad_token tokenizer.eos_token model.resize_token_embeddings(len(tokenizer))第二行resize_token_embeddings是把模型的词嵌入矩阵扩大到当前 tokenizer 的词表大小。即便你没有新增任何 token执行这一步也是安全的。真正要注意的是pad_token和eos_token用同一个 token 时标签里如果有 padding 位置模型学习从 padding 位置预测 eos_token训练损失会有轻微污染。后面写训练循环时会把 padding 位置在 loss 计算中屏蔽掉。3.2 最小训练循环loss 要自己盯摘要训练的核心是语言建模损失。因为 GPT-2 是因果语言模型输入input_ids同时也作为标签模型对每个位置预测下一个 token。但这里有个专业细节全序列都参与 loss会让模型把重点放在原文预测上摘要区域的信息反而淹没在长序列里。实践中最常见的做法是只对摘要部分计算 loss。# train_gpt2_summary.py # 训练循环只计算摘要区域 token 的 loss import torch from torch.utils.data import DataLoader from transformers import GPT2LMHeadModel, GPT2Tokenizer, AdamW device torch.device(cuda if torch.cuda.is_available() else cpu) model GPT2LMHeadModel.from_pretrained(uer/gpt2-chinese-cluecorpussmall) tokenizer GPT2Tokenizer.from_pretrained(uer/gpt2-chinese-cluecorpussmall) tokenizer.pad_token tokenizer.eos_token model.to(device) # 假设 batch 里每个样本是 dict: {input_ids: tensor, attention_mask: tensor, summary_start: int} def compute_loss(batch): input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) logits model(input_ids, attention_maskattention_mask).logits shift_logits logits[..., :-1, :].contiguous() shift_labels input_ids[..., 1:].contiguous() loss_fct torch.nn.CrossEntropyLoss(reductionnone) loss loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) loss loss.view(shift_labels.size(0), shift_labels.size(1)) # 摘要开始前的位置 loss 置 0只保留摘要后缀区域 for i in range(loss.size(0)): start_pos batch[summary_start][i] loss[i, :start_pos - 1] 0.0 # padding 为 eos_token 的位置贡献了 eos 预测不影响摘要主体 return loss.sum() / loss.size(0) optimizer AdamW(model.parameters(), lr5e-5) for epoch in range(3): for step, batch in enumerate(dataloader): loss compute_loss(batch) optimizer.zero_grad() loss.backward() optimizer.step() if step % 100 0: print(fepoch {epoch} step {step} loss {loss.item():.4f})这段循环里最关键的是summary_start这个字段。训练前构建样本时每个样本记录[SEP]首次出现的位置也就是摘要开头。计算 loss 时把该位置之前的损失全部清零让模型专注于学习“从[SEP]之后怎么续写摘要”。如果你的 GPU 显存足够大想省事的直接传入labelsinput_ids也能跑但那个 loss 里一半是原文文本预测收敛效果会比较奇怪。嫌循环写得麻烦的人偶尔直接丢官方Trainer但Trainer默认不按区域算 loss自定义 loss 反而隐藏得深调试成本不低。3.3 检查点保存与恢复训练断掉是常态不是意外。我一般每 500 步存一次检查点至少保留模型权重和tokenizer 配置两个目录。恢复训练时从检查点加载权重再把学习率降一半。这里有个很多新手会遇到的坑加载时直接model GPT2LMHeadModel.from_pretrained(checkpoint_dir)却忘记重新注册pad_token结果继续训练时 DataLoader 的 collate 函数因为 pad 行为不一致产生 tensor 尺寸报错。保存和恢复用同一个脚本是最稳的把 tokenizer 设置写进恢复流程里。# 保存目录结构建议 # ./checkpoints/epoch2_step500/ # ├── pytorch_model.bin # ├── config.json # └── tokenizer files恢复训练时resize_token_embeddings这一步不能漏。模型词表和 tokenizer 词表如果尺寸不一致加载权重时尾部向量被随机初始化训练会突然出现非预期的 loss 异常。4. 生成摘要时的参数怎么调beam search、重复惩罚、长度上限4.1 先弄清 beam search 和采样哪个更适合摘要训练完模型生成阶段的参数选择比训练超参更容易直接影响观感。摘要生成有一个隐含要求确定性输出。同一篇新闻业务方不希望每次返回的摘要措辞不同。所以默认用 beam search 而不是temperature采样。beam search 维护num_beams条候选序列每步保留累计概率最高的若干路径最后挑整体概率最高的一条。num_beams1就退化成贪心解码速度快但容易过早进入局部重复。采样虽然能增加多样性更适合写文案、续写故事这类创意场景对摘要来说代价是“不同次调用结果不同”。如果一定要用采样跑摘要把temperature调到 0.7 以下并加top_p0.9能压低随机性。不过我的习惯是能 beam 就 beam没有 param 的随机性好过偶尔蹦出惊艳但常常失稳的输出。4.2 三个必调参数beam width、no_repeat_ngram、repetition_penalty摘要生成过几个参数效果判断非常直接。第一个是num_beams推荐 3 到 5。再大收益有限速度翻倍对中文这种短句密集的语言beam 太宽反而容易摆出低概率的整段模板语。第二个是no_repeat_ngram_size3这是克制复读的关键它禁止生成任意 3-gram 重复两次以上代价是可能把长摘要里合理的同类表述也禁掉所以摘要较长时建议只设到 4。第三个是repetition_penalty1.2它的作用是每生成一个 token 时给历史出现过的 token 的概率打折扣。这两个参数并存的必要性在于一个管 n-gram 硬约束一个管全文重复偏好。实际效果上no_repeat_ngram_size对短摘要立竿见影repetition_penalty对长摘要更平滑。长度控制同样别忽略。max_new_tokens是相对输入长度新增的 token 数比max_length直观。中文摘要平均每个字对应 1 到 1.5 个 token设置 100 到 150 的max_new_tokens通常足够。低于 50 会看到大量后半句没说完就强制截断高于 200 则容易跑题。4.3 一段可以直接拿去用的推理函数把上面的参数整合就是一个标准的摘要生成函数。# inference.py # 加载微调后的模型生成摘要 from transformers import GPT2LMHeadModel, GPT2Tokenizer device torch.device(cuda if torch.cuda.is_available() else cpu) SEP [SEP] model GPT2LMHeadModel.from_pretrained(./gpt2_summary_checkpoint) tokenizer GPT2Tokenizer.from_pretrained(./gpt2_summary_checkpoint) tokenizer.pad_token tokenizer.eos_token model.to(device) def summarize(text, max_new_tokens120, num_beams4): prompt text SEP inputs tokenizer(prompt, return_tensorspt, truncationTrue, max_length512) # 强制不输出 eos_token避免摘要提前结束 forced_ids [] outputs model.generate( inputs[input_ids].to(device), max_new_tokensmax_new_tokens, num_beamsnum_beams, no_repeat_ngram_size3, repetition_penalty1.2, early_stoppingTrue, forced_bos_token_id0, # 用不到但保留占位 ) # 从 [SEP] 之后开始解码去掉原文部分 generated tokenizer.decode(outputs[0], skip_special_tokensTrue) if SEP in generated: return generated.split(SEP)[-1] return generated这里有个细节值得展开直接在outputs上split(SEP)是因为我训练时的分隔符被注册成了特殊 tokenskip_special_tokensTrue会把它们全删掉导致你拿回纯文本后反而找不准摘要起点。所以在split(SEP)[-1]是拿原文摘要这个整体里最后一次出现[SEP]之后的文本这依赖“原文里不出现[SEP]字符串”这个前提。如果你构造训练数据时已经在原文里出现过[SEP]这里就失效了。更可靠的做法是不用skip_special_tokens先按 token id 找到[SEP]的位置再解码只是代码长一点。业务 demo 阶段用 split 足够踩过坑的人自然会改成 token id 定位。5. 训练 GPT2 摘要模型的避坑清单5 个最容易翻车的地方5.1 现象loss 降了但生成的摘要全是原文片段复读原因最隐蔽的一条。loss 从 5.2 降到 1.8看起来训练顺利但生成结果是“引用原文开头几个词”根本看不出概括成分。第一反应怀疑模型容量其实多半是训练数据里原文和摘要重叠度太高比如新闻类数据集里第一句往往是“本报讯 X 报道”而摘要直接改写这句。模型学到了捷径抄第一句就能过训练目标于是生成时复读原文。解决方式有两种。一是在数据清洗阶段删掉“摘要和原文首句字面相似度超过 0.7”的样本二是在 loss 里把原文区域的损失权重压得更低让模型找不到复读的诱导信号。5.2 现象中文被 tokenizer 切成乱码或整句黏成一坨中文预训练权重已内置中文 tokenizer如果换用英文 GPT-2 再加载中文文本会把每个汉字拆成奇怪的字节组合。另一个常见情况是训练时一切正常部署环境里transformers版本升级tokenizer 对空格的处理变化导致同一句话预测的 token 序列不同。解决方式是锁定依赖版本保存模型检查点的时候顺带保存 tokenizer 配置不依赖环境的默认版本行为。我在项目里习惯把所有 tokenizer 文件复制到检查点目录部署时只从检查点目录加载。5.3 现象训练到一半 CUDA OOM进程直接挂掉摘要训练常见的显存瓶颈来自输入长度。batch size 设成 8但输入序列长度全部按 512 处理一张 8G 显存的卡基本撑不住。解决方式不是单纯调小 batch size而是检查你的 DataLoader 是否做了动态 padding按每个 batch 内部最长序列 padding而不是全部 pad 到 512。用collate_fn实现动态 padding 后8G 显存能跑的 batch size 可以翻倍。如果还是不够把max_src_len从 512 降到 384很多新闻摘要任务的原文保留到 384 token 就足够毕竟关键信息通常分布在前半部分。5.4 现象生成的摘要末尾挂着用generate时不传truncation相关参数模型可能在某个 token 位置循环输出eos_token最终代码解码出半截文本加一个多余标记。原因一是训练数据里摘要结尾没有统一加[SEP]模型没学好“结束”的触发模式原因二是推理时no_repeat_ngram_size和长度提升相互冲突模型被迫填满max_new_tokens也不停。解决方式数据清洗保证每条摘要都以[SEP]结尾推理时把forced_eos_token_id设为tokenizer.eos_token_id或将early_stopping打开。如果你的模型在[SEP]之后还继续生成别的内容说明摘要结束符没学会需要回炉检查训练样本。5.5 现象微调后模型通用生成能力明显下降在单个摘要数据集上训练 3 个 epoch 后模型原本会写邮件、续写故事的能力急剧退化。这是典型的灾难性遗忘。解决方式分三个层级最轻的是把学习率降到 2e-5 以下让微调只做小幅修正中间的是在训练数据里混入 10% 通用中文语料随便拿新闻、维基文本做生成式续写样本即可最重的是用 LoRA 之类的参数高效微调方法冻结主模型权重只训练少量低秩矩阵既保留通用能力又完成摘要任务适配。我自己一般从第二个层级起步成本低效果好。6. 摘要质量怎么验证才不靠玄学ROUGE 打分加人工抽检自动评估摘要最常见的是 ROUGE 系列指标ROUGE-L 能同时反映短语和句子级匹配。调用datasets库里的load_metric(rouge)可以快速计算实测对一个 2000 条验证集做打分能帮你发现回归同一套参数改完分数掉了 0.1 立刻警觉。但 ROUGE 有个知名缺陷——它看重字面重合改写能力强的模型可能得分偏低复读原文的模型反而得分偏高。所以你光看指标上升就高兴可能恰恰训出一个复读机。我的验证习惯是两步走。自动打分用来做回归基线人工抽检用来做最后裁判。每次训练完从验证集里随机拿 10 条输入跑一遍生成然后把原文、参考摘要、模型摘要并排打印出来。判断一个标准的尺度很简单摘要能看懂、关键信息不缺失、没有病句和复读就算及格。人工抽检的这个结果不值得记录但很值得在训练参数调整前后对比着看特别是repetition_penalty和no_repeat_ngram_size的调整ROUGE 分数往往不变但观感变化巨大。另一个有效技巧是给模型做“压力测试”特意喂一篇包含长数字、多省份名单、并列结构的新闻原文看摘要能否保持数字准确。摘要生成模型最容易在数字上取近似值系统内部对数字精度要求高的话光靠 ROUGE 根本看不出来。这项踩坑经验是我在协助做证券研报摘要时留下的从那以后所有摘要模型的评估脚本里必然加一条数字一致性计数。最后说一个习惯模型上线前把这些验证片段和生成结果放进一张 Markdown 表格存为某次训练的附属说明。翻车的时候回看历史数据比看tensorboard的曲线能更快定位问题出在数据还是参数。希望这套流程对你的 GPT-2 中文摘要项目有实际帮助。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
APO生产主数据实战:CIF集成、PPM/PDS选型与产能资源建模 1. 生产主数据在 APO 中到底管什么从 ERP 转到 APO 的人,大概都有过这么一段困惑期:ERP 里物料、BOM、工艺路线都齐了,为什么到了 APO 还要再折腾一套“生产主数据”,而且建完还跑不出结果。这个问题我碰到过不止一次,… · 2026/11/2 14:46:04
ECharts柱状图悬停柱子消失:emphasis与blur排查指南 组里一个刚接手可视化模块的同事上周发来一段录屏,画面里的柱状图鼠标一划过去,被悬停的那根柱子还在,其余几根像被人按了隐身键一样直接淡出画布。他反复确认了三遍数据源,甚至把series.data打印到控制台一个一个核对,… · 2026/11/2 14:46:04
Rails项目Paperclip附件处理实战:配置、踩坑与迁移指南 接手一个成年的Rails项目,最头疼的往往不是业务代码,而是那些年迈的第三方依赖。上个月我就是这样,项目里所有用户头像、产品图片上传都在用Paperclip这个Gem处理,文档里的不少写法已经和我熟悉的ActiveStorage完全不同࿰… · 2026/11/2 14:45:02
RAID技术详解:从原理到实战选型指南 1. RAID技术概述与核心价值RAID(Redundant Array of Independent Disks)技术自1988年由加州大学伯克利分校提出以来,已成为存储领域的基石技术。这项技术通过将多个物理磁盘组合成逻辑单元,实现了数据存储的性能提升和可靠性保障的… · 2026/11/3 6:02:12
股票交易算法:动态规划与贪心策略实战 1. 买卖股票的最佳时机问题解析股票交易问题是算法领域的经典题型,也是技术面试中的高频考点。这个问题看似简单,却蕴含着动态规划、贪心算法等核心思想。作为C开发者,掌握这类问题的解法不仅能提升算法能力,更能培养对实际业务场… · 2026/11/3 6:02:12
2nm工艺与200Tbps交换机的网络架构革新 1. 项目背景与核心挑战在数据中心网络架构演进的前沿,2nm制程工艺与200Tbps交换容量的结合正在重新定义核心交换机的性能边界。这个被称为"全尺度参数宇宙构建框架"的项目,本质上是在探索超大规模网络设备的极限设计方法论。我最近参与的一个高… · 2026/11/3 6:02:12
网络操作系统安全知识地图:系统家族、安全机制与访问控制 简介:这份PPT课件面向计算机、网络工程等专业的学生及刚接触系统安全的学习者,系统梳理网络操作系统安全的基础知识框架,帮助读者建立从操作系统类型认知到访问控制机制的整体理解。内容围绕三部分展开:常用网络操作系统简介&… · 2026/11/3 6:02:12
网络安全黄金岗位解析与职业发展指南 1. 网络安全行业全景扫描网络安全早已不是简单的防火墙和杀毒软件,它已经演变成一个覆盖技术、管理、法律等多维度的复合型领域。根据我十多年的行业观察,当前网络安全人才缺口高达300万,而每年相关专业毕业生不足3万,这种供需失衡… · 2026/11/3 6:02:12
Univer:可嵌入的国产文档协同引擎SDK 1. 项目概述:Univer 是什么,它解决的不是“又一个表格组件”,而是协同编辑底层能力的重构Univer 这个名字乍一听像某个开源项目代号,但如果你最近在技术社区、前端团队或企业级办公软件选型讨论中频繁看到它,大概率不是… · 2026/11/3 6:01:12
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46