深度学习认证训练:防御卷积扰动的可证明鲁棒性实践
在深度学习模型的安全性和鲁棒性研究中Certified Training认证训练正成为对抗样本防御领域的关键技术方向。传统对抗训练虽然能提升模型对特定攻击的抵抗力但无法提供数学上的安全保证。Certified Training 通过引入可证明的鲁棒性边界让模型在面对扰动时具备可量化的防御能力。其中Convolutional Perturbations卷积扰动作为一种结构化攻击方式通过在卷积层注入特定模式的扰动能够有效绕过基于像素级扰动的传统检测方法。本文将从卷积扰动的攻击原理入手解析 Certified Training 如何为卷积神经网络提供可证明的鲁棒性保证。我们将通过完整的代码实现展示如何构建一个能够抵抗卷积扰动的认证训练流程并验证其在实际图像分类任务中的效果。最后我们会深入分析训练过程中的常见问题、认证边界的计算原理以及在生产环境中部署认证模型的最佳实践。1. 理解卷积扰动与认证训练的基本原理1.1 卷积扰动为什么比像素扰动更难防御传统的对抗攻击通常在输入图像的像素空间添加微小扰动这些扰动虽然人眼难以察觉但足以让模型产生错误分类。卷积扰动则采用了完全不同的攻击范式它不是在像素级别直接修改而是通过卷积操作在特征空间引入结构化噪声。卷积扰动的核心思想是在模型的卷积层权重中注入特定的扰动模式。攻击者会设计一个小的扰动卷积核将其与原始卷积核结合从而在不显著改变模型正常功能的前提下在特定输入上诱导错误行为。这种攻击的优势在于隐蔽性强扰动隐藏在模型权重中而不是输入数据上传统输入检测方法难以发现结构保持卷积操作保持了图像的局部相关性生成的对抗样本视觉质量更高攻击效率高一次权重修改可以对大量输入生效无需为每个样本单独生成扰动1.2 Certified Training 如何提供可证明的安全保证Certified Training 的目标是训练出这样的模型对于任何在允许扰动范围内的输入模型都能保证给出正确的预测结果。与经验性防御方法不同认证训练提供的不是可能有效的防御而是数学上可证明的安全边界。对于卷积扰动认证训练的核心是计算每个卷积层在扰动下的最坏情况输出边界。具体来说我们需要证明给定一个输入样本和允许的扰动范围无论攻击者选择什么样的扰动卷积核只要在允许范围内模型的预测都不会改变。认证训练通常采用区间边界传播Interval Bound Propagation, IBP或基于 Lipschitz 连续性的方法。IBP 方法通过前向传播输入的上界和下界在每一层计算扰动可能导致的输出变化范围最终得到认证的鲁棒半径。2. 环境准备与认证训练框架选择2.1 基础环境配置认证训练对计算资源要求较高建议在支持 CUDA 的 GPU 环境中进行。以下是推荐的环境配置# 创建 Python 虚拟环境 python -m venv certified_training source certified_training/bin/activate # Linux/Mac # certified_training\Scripts\activate # Windows # 安装核心依赖 pip install torch1.9.0 torchvision0.10.0 pip install numpy matplotlib tqdm pip install autoattack # 用于后续评估2.2 认证训练框架比较目前主流的认证训练框架各有侧重选择时需要根据具体需求权衡框架名称核心特性适用场景认证方法PyTorch 自定义灵活性高可定制性强研究新方法、特殊扰动IBP、CROWNIBM ART功能全面企业级支持生产环境部署多种认证方法Foolbox攻击评估完善基准测试比较有限支持认证TensorFlow Privacy隐私保护认证差分隐私场景DP认证对于卷积扰动的认证训练我们选择基于 PyTorch 自定义实现这样可以更清晰地展示认证边界的计算过程。3. 实现卷积扰动的认证训练流程3.1 定义卷积扰动约束首先需要明确什么范围内的卷积扰动是允许的。常见的约束方式包括权重范数约束限制扰动卷积核的 L2 或 L∞ 范数结构约束限制扰动卷积核的大小和形状稀疏性约束限制非零扰动元素的数量以下代码定义了基本的卷积扰动约束import torch import torch.nn as nn from typing import Union, List class ConvolutionalPerturbationConstraint: 定义卷积扰动的约束条件 def __init__(self, epsilon: float 0.1, norm_type: str Linf, kernel_size: Union[int, List[int]] 3): self.epsilon epsilon # 扰动强度 self.norm_type norm_type # 范数类型 self.kernel_size kernel_size def validate_perturbation(self, delta: torch.Tensor) - bool: 验证扰动是否满足约束条件 if self.norm_type Linf: norm delta.abs().max() elif self.norm_type L2: norm delta.norm() else: raise ValueError(fUnsupported norm type: {self.norm_type}) return norm self.epsilon 1e-6 # 数值容差3.2 实现认证的卷积层认证卷积层需要重写前向传播同时计算正常输出和认证边界class CertifiedConv2d(nn.Module): 支持卷积扰动认证的卷积层 def __init__(self, in_channels: int, out_channels: int, kernel_size: int, stride: int 1, padding: int 0, constraintNone): super().__init__() self.conv nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding) self.constraint constraint or ConvolutionalPerturbationConstraint() # 初始化认证边界计算参数 self.epsilon self.constraint.epsilon self.norm_type self.constraint.norm_type def forward(self, x: torch.Tensor, compute_bounds: bool False): 前向传播可选计算认证边界 normal_output self.conv(x) if not compute_bounds: return normal_output # 计算最坏情况下的输出边界 if self.norm_type Linf: # 对于L∞扰动使用区间算术计算边界 abs_weights self.conv.weight.abs() perturbation_effect F.conv2d(x.abs(), abs_weights, strideself.conv.stride, paddingself.conv.padding) * self.epsilon lb normal_output - perturbation_effect ub normal_output perturbation_effect elif self.norm_type L2: # 对于L2扰动使用柯西-施瓦茨不等式 weight_norm self.conv.weight.norm(dim(1,2,3), keepdimTrue) input_norm F.conv2d(x.pow(2), torch.ones_like(self.conv.weight), strideself.conv.stride, paddingself.conv.padding).sqrt() perturbation_effect weight_norm * input_norm * self.epsilon lb normal_output - perturbation_effect ub normal_output perturbation_effect return normal_output, (lb, ub)3.3 构建完整的认证训练模型将认证卷积层组合成完整的 CNN 模型class CertifiedCNN(nn.Module): 支持卷积扰动认证的CNN模型 def __init__(self, num_classes: int 10): super().__init__() self.features nn.Sequential( CertifiedConv2d(3, 64, 3, padding1), nn.ReLU(inplaceTrue), CertifiedConv2d(64, 64, 3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), CertifiedConv2d(64, 128, 3, padding1), nn.ReLU(inplaceTrue), CertifiedConv2d(128, 128, 3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.AdaptiveAvgPool2d((4, 4)), nn.Flatten(), nn.Linear(128 * 4 * 4, 256), nn.ReLU(inplaceTrue), nn.Linear(256, num_classes) ) def forward(self, x: torch.Tensor, compute_bounds: bool False): bounds [] if compute_bounds else None for layer in self.features: if isinstance(layer, CertifiedConv2d): if compute_bounds: x, layer_bounds layer(x, compute_boundsTrue) bounds.append(layer_bounds) else: x layer(x, compute_boundsFalse) else: x layer(x) x self.classifier(x) if compute_bounds: return x, bounds return x4. 认证训练算法实现4.1 IBP 认证训练损失函数认证训练的核心是特殊的损失函数同时优化正常准确率和认证鲁棒性def certified_loss(model, x, y, epsilon_schedule0.1): 计算认证训练损失 # 正常前向传播 logits_clean model(x, compute_boundsFalse) clean_loss F.cross_entropy(logits_clean, y) # 计算认证边界 logits_cert, bounds model(x, compute_boundsTrue) # 使用IBP计算最坏情况logits logits_lb logits_cert.clone() for (lb, ub) in bounds: # 累积各层的边界影响简化版本实际需要更精细的传播 pass # 计算认证损失确保正确类别的下界大于其他类别的上界 batch_size y.size(0) y_onehot F.one_hot(y, num_classeslogits_clean.size(1)) # 正确类别的下界 correct_lb (logits_lb * y_onehot).sum(dim1) # 其他类别的上界 incorrect_ub logits_lb.clone() incorrect_ub[y_onehot.bool()] -float(inf) max_incorrect_ub incorrect_ub.max(dim1)[0] # 认证边界损失 margin correct_lb - max_incorrect_ub certified_loss F.relu(1 - margin).mean() # 组合损失 total_loss clean_loss epsilon_schedule * certified_loss return total_loss, clean_loss, certified_loss4.2 完整的训练循环def train_certified_model(model, train_loader, optimizer, epoch, epsilon_max0.1): 认证训练循环 model.train() total_loss 0 clean_acc 0 certified_acc 0 # 渐进式增加扰动强度课程学习 epsilon_schedule min(epsilon_max, epoch * epsilon_max / 50) for batch_idx, (data, target) in enumerate(train_loader): data, target data.cuda(), target.cuda() optimizer.zero_grad() # 计算认证损失 loss, clean_loss, cert_loss certified_loss( model, data, target, epsilon_schedule) loss.backward() optimizer.step() total_loss loss.item() # 计算准确率 with torch.no_grad(): logits model(data, compute_boundsFalse) pred logits.argmax(dim1) clean_acc (pred target).float().mean().item() # 简化认证准确率计算 _, bounds model(data, compute_boundsTrue) # 实际需要完整的认证验证 if batch_idx % 100 0: print(fEpoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)}] fLoss: {loss.item():.6f} Clean: {clean_loss.item():.6f} fCert: {cert_loss.item():.6f}) avg_loss total_loss / len(train_loader) avg_clean_acc clean_acc / len(train_loader) return avg_loss, avg_clean_acc5. 实验验证与结果分析5.1 训练配置与超参数选择认证训练对超参数敏感需要仔细调优def setup_training(): 设置认证训练参数 config { batch_size: 128, epochs: 100, learning_rate: 0.01, lr_scheduler: cosine, weight_decay: 1e-4, epsilon_max: 0.1, # 最大扰动强度 constraint_norm: Linf, # 扰动范数约束 } # 数据加载 transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), ]) trainset torchvision.datasets.CIFAR10( root./data, trainTrue, downloadTrue, transformtransform_train) train_loader torch.utils.data.DataLoader( trainset, batch_sizeconfig[batch_size], shuffleTrue) return config, train_loader5.2 认证准确率评估训练完成后需要评估模型在不同扰动强度下的认证准确率def evaluate_certified_accuracy(model, test_loader, epsilon_values): 评估不同扰动强度下的认证准确率 model.eval() results {} for epsilon in epsilon_values: certified_correct 0 total 0 for data, target in test_loader: data, target data.cuda(), target.cuda() with torch.no_grad(): # 计算认证边界 logits, bounds model(data, compute_boundsTrue) # 简化认证检查实际需要更严谨的验证 pred logits.argmax(dim1) certified_correct (pred target).sum().item() total target.size(0) certified_acc certified_correct / total results[epsilon] certified_acc print(fEpsilon: {epsilon:.3f}, Certified Accuracy: {certified_acc:.4f}) return results5.3 可视化认证边界理解认证边界的变化有助于调试训练过程def visualize_certification_bounds(model, sample_data): 可视化认证边界 model.eval() with torch.no_grad(): logits, bounds model(sample_data.unsqueeze(0), compute_boundsTrue) # 绘制logits的上下界 classes range(logits.size(1)) lower_bounds [b[0].item() for b in bounds] # 简化表示 upper_bounds [b[1].item() for b in bounds] plt.figure(figsize(10, 6)) plt.bar(classes, logits.squeeze().cpu().numpy(), alpha0.7, labelLogits) plt.fill_between(classes, lower_bounds, upper_bounds, alpha0.3, labelCertification Bounds) plt.xlabel(Class) plt.ylabel(Logit Value) plt.legend() plt.title(Certification Bounds for Sample Prediction) plt.show()6. 常见问题与调试策略6.1 认证训练不收敛的排查认证训练比普通训练更容易出现收敛问题常见原因包括问题现象可能原因检查方法解决方案损失震荡严重学习率过大观察损失曲线降低学习率使用学习率预热认证损失始终很高扰动强度增长过快检查epsilon调度放缓epsilon增长曲线正常准确率下降过多认证损失权重过大调整损失权重降低认证损失权重增加正常训练轮数梯度爆炸边界传播数值不稳定检查梯度范数添加梯度裁剪使用数值稳定的边界传播6.2 认证边界的数值稳定性边界传播容易产生数值问题需要特别注意def stable_bound_propagation(lb, ub, weight, bias, activationrelu): 数值稳定的边界传播 # 避免除零和数值溢出 lb lb.clamp(-1e6, 1e6) ub ub.clamp(-1e6, 1e6) # 使用数值稳定的卷积计算 weight_abs weight.abs() center (lb ub) / 2 radius (ub - lb) / 2 new_center F.conv2d(center, weight, biasbias) new_radius F.conv2d(radius, weight_abs) new_lb new_center - new_radius new_ub new_center new_radius # 激活函数处理 if activation relu: new_lb F.relu(new_lb) new_ub F.relu(new_ub) return new_lb, new_ub6.3 内存优化策略认证训练的内存消耗是普通训练的数倍需要优化class MemoryEfficientCertifiedConv2d(CertifiedConv2d): 内存优化的认证卷积层 def forward(self, x, compute_boundsFalse): if not compute_bounds: return super().forward(x, compute_boundsFalse) # 使用checkpointing减少内存占用 return checkpoint.checkpoint( self._forward_with_bounds, x, use_reentrantFalse) def _forward_with_bounds(self, x): return super().forward(x, compute_boundsTrue)7. 生产环境部署最佳实践7.1 模型压缩与加速认证模型通常比普通模型更大更慢部署前需要优化def optimize_certified_model(model): 优化认证模型用于部署 # 1. 量化压缩 model.qconfig torch.quantization.get_default_qconfig(fbgemm) model_prepared torch.quantization.prepare(model, inplaceFalse) # 使用校准数据... model_quantized torch.quantization.convert(model_prepared) # 2. 剪枝优化 parameters_to_prune [ (module, weight) for module in model.modules() if isinstance(module, nn.Conv2d) ] torch.nn.utils.prune.global_unstructured( parameters_to_prune, pruning_methodtorch.nn.utils.prune.L1Unstructured, amount0.2, # 剪枝20%的权重 ) return model_quantized7.2 持续监控与更新生产环境中的认证模型需要持续监控性能监控定期测试模型在最新攻击下的认证准确率数据漂移检测监控输入数据分布变化对认证边界的影响版本管理维护不同扰动强度下的模型版本根据威胁级别切换7.3 安全部署清单部署认证模型前需要检查的关键项目检查类别具体项目达标标准模型完整性权重文件哈希验证与训练完成时一致运行环境推理框架版本与训练环境兼容性能基准推理延迟和吞吐量满足业务要求认证验证测试集认证准确率高于安全阈值监控告警异常预测检测实时监控启用认证训练为卷积神经网络提供了可证明的鲁棒性保证特别是在面对结构化的卷积扰动时表现出色。实际部署中需要权衡认证强度与模型效率根据具体威胁模型选择合适的扰动约束。对于高安全要求的场景建议采用渐进式认证策略从较小的扰动范围开始逐步扩展到更大的认证边界。下一步可以探索更高效的认证算法、针对特定领域如自动驾驶、医疗影像的定制化认证方法以及认证训练与其他防御技术如差分隐私、联邦学习的结合应用。

相关新闻

基于YOLOv26与多光谱成像的焊接缺陷实时检测系统

基于YOLOv26与多光谱成像的焊接缺陷实时检测系统

1. 项目背景与核心需求在工业制造领域,焊接质量检测直接关系到产品结构安全和使用寿命。传统人工检测方式存在效率低、主观性强、漏检率高等问题,特别是在高温、高粉尘的恶劣环境下,检测人员面临巨大挑战。多光谱成像技术能够捕捉可见光之外的…

2026/10/1 12:21:49 阅读更多 →
对比学习在RAW图像去噪中的应用与优化

对比学习在RAW图像去噪中的应用与优化

1. 项目概述:对比学习驱动的RAW图像去噪新范式在计算摄影领域,RAW图像去噪一直是基础且关键的预处理环节。传统方法往往需要多帧图像或复杂的噪声建模,而这项发表在TPAMI 2025的工作提出了一个突破性方案——仅需单张RAW图像就能实现媲美多帧…

2026/10/8 22:35:12 阅读更多 →
【代码随想录算法训练营第34天】动态规划part03 | 01背包问题 二维 | 01背包问题 一维 | 416. 分割等和子集

【代码随想录算法训练营第34天】动态规划part03 | 01背包问题 二维 | 01背包问题 一维 | 416. 分割等和子集

文章目录KEY(1)语法1> 如何初始化二维数组2> 数组求和用 accumulate(v.begin(), v.end(), 0);(2)学会把问题理解成01背包问题的形式,然后用01背包的套路去解决01背包问题 二维整个代码:01背包问题 一…

2026/10/4 18:39:03 阅读更多 →

最新新闻

Python基础语法一站式指南:从环境搭建到项目实战

Python基础语法一站式指南:从环境搭建到项目实战

很多朋友学Python时,最大的障碍其实不是"某个语法不会",而是知识点太碎,学完列表学字典,学完函数学文件,一到自己写项目就全乱套。我这些年用Python写自动化脚本、做数据分析、折腾小爬虫,最大的体会是:基础语法必须串成一条线来学。这篇指南我打算从装环境一路写到函…

2026/10/11 0:33:54 阅读更多 →
基于MPC的混合储能微电网双层能量管理:Matlab仿真与实现

基于MPC的混合储能微电网双层能量管理:Matlab仿真与实现

1. 整体设计与思路拆解:为什么混合储能微电网需要“双层”和“MPC”先聊一个经常被问到的问题:既然已经有能量管理系统(EMS)了,为什么还要搞“双层”?又为什么要专门上模型预测控制(MPC&#xf…

2026/10/11 0:33:54 阅读更多 →
苍蝇检测数据集:VOC+YOLO双格式530张,导入即可训练

苍蝇检测数据集:VOC+YOLO双格式530张,导入即可训练

简介:一套面向目标检测任务的苍蝇检测数据集,主要服务于计算机视觉方向的初学者、算法工程师以及农业害虫识别等实际项目。全部图像来自百度图片爬取,并经过删除重复筛选,再由labelImg工具人工绘制矩形框完成标注,标注…

2026/10/11 0:32:54 阅读更多 →
毕业设计水果识别实战:从数据采集到微信小程序部署

毕业设计水果识别实战:从数据采集到微信小程序部署

简介:本资源是一套完整的基于深度学习的水果识别系统毕业设计项目,面向计算机、人工智能及相关专业本科生,专为大作业与毕业设计实践打造,解决图像分类场景下的模型构建、训练与部署全流程问题。压缩包共277个文件,总大…

2026/10/11 0:32:54 阅读更多 →
DeepLabv3+实战:VOC与Cityscapes上完整训练与推理指南

DeepLabv3+实战:VOC与Cityscapes上完整训练与推理指南

简介:一套基于Pytorch在VOC与Cityscapes数据集上实现DeepLabv3训练的图像分割实战项目,面向有初步深度学习基础的读者,帮助系统掌握语义分割模型训练的整体流程。包内共55个文件,以23个Python脚本为主干,覆盖数据加载、…

2026/10/11 0:32:54 阅读更多 →
基于NeRF和手机拍摄物体图片的三维重建Python源码+数据集+文档说明

基于NeRF和手机拍摄物体图片的三维重建Python源码+数据集+文档说明

简介:这份资源面向计算机视觉与三维重建方向的学习者,尤其是需要完成课程设计或毕业设计的高年级本科生与研究生,提供一套基于NeRF、利用手机拍摄物体图片即可完成三维重建的完整Python实现方案。压缩包共31个文件,约5.37MB&#…

2026/10/11 0:32:54 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →