基于ResNet优化模型的阿尔茨海默症识别课程设计实战
简介这份资源是深度学习课程设计的完整实践项目围绕基于ResNet优化模型的阿尔茨海默症识别展开面向希望入门医学影像分析的小白学习者也可作为进阶者的课程设计、大作业或毕设参考。项目提供2D与3D两条技术路线2D方法将79张不同位置的切片转为79通道图像输入ResNet503D方法采用三维卷积直接处理体数据并引入MedicalNet医疗影像预训练模型便于对比不同建模思路的识别效果。资源包共13个文件包含6个py脚本、3个ipynb笔记本、2份pdf论文资料、1个csv预测结果和1个md说明压缩包约2.06MB脚本与笔记本均可直接运行方便读者边调试边理解网络结构。目前已有90人学习适合作为医学影像分类任务的入门跳板帮助读者掌握数据加载、模型搭建、预训练迁移与结果分析等关键环节。1. 阿尔茨海默症识别从 ResNet 到优化模型的课程设计落地路径阿尔茨海默症Alzheimers Disease, AD的早期识别在医学影像领域一直是个硬骨头。结构核磁共振sMRI能清晰反映海马体萎缩、脑室扩大等结构性变化但人眼判读依赖放射科医生的经验不同中心、不同年资的医生给出的 MCI 转化风险评估可能相差很大。深度学习课程设计选这个题目好处是数据公开、任务定义清晰、评价指标成熟坏处是——如果只拿一个 torchvision 的 resnet18 直接套上去准确率往往卡在 70% 上下连自己都说服不了。这篇笔记围绕「基于 ResNet 优化模型的阿尔茨海默症识别」这个课程设计题目把数据准备、ResNet 选型、优化点设计、训练调参和排错串成一条能复现的路径。适合正在做深度学习课程设计、想拿 ADNI 或 OASIS 数据集跑出一个像样结果的同学也适合已经跑通 baseline、想搞清楚「优化模型到底优化在哪」的熟手。2. 数据与任务定义ADNI/OASIS 的标签体系与切片策略2.1 先搞清楚三分类还是二分类AD 识别最常见的任务设定有两类。第一类是二分类AD vs 认知正常CN这个任务相对简单baseline 就能到 85% 以上课程设计如果只做这个创新点很难写。第二类是三分类AD vs 轻度认知障碍MCI vs CN难点全在 MCI——它和 CN 的脑结构差异细微和 AD 又有重叠模型很容易把 MCI 判成 CN导致 MCI 召回率偏低。我一般建议课程设计直接做三分类然后在报告里单独分析 MCI 的混淆矩阵这样优化点才有落脚处。标签来源上ADNI 自带诊断标签DXSUM 表里的 DIAGNOSISOASIS 则提供 CDR 评分通常按 CDR0 为 CN、CDR0.5 为 MCI、CDR1 为 AD 来映射。这里有个坑同一个受试者多次随访的标签可能变化如果按图像切分而不按受试者切分训练集和验证集会泄漏指标虚高。正确做法是按 subject ID 做 GroupShuffleSplit。2.2 切片方向与预处理为什么用轴状位而不是矢状位sMRI 是三维体数据直接上 3D CNN 显存吃不消课程设计阶段更常见的是取二维切片。轴状位axial切片在海马体层面能同时看到左右海马信息密度高矢状位sagittal虽然能看到海马长轴但单张切片左右不对称模型学起来更吃力。常见做法是取轴状位中间若干层比如从第 60 层到第 120 层每隔 5 层取一张每个受试者得到 12 张左右切片。预处理流程我一般固定成四步N4 偏置场校正用 ANTs 或 SimpleITK、配准到 MNI 模板、颅骨剥离、灰度归一化到 [0,1]。如果课程设计时间紧可以跳过配准但偏置场校正和颅骨剥离建议保留否则同一台机器不同扫描的亮度差异会让模型学偏。import numpy as np import nibabel as nib from sklearn.model_selection import GroupShuffleSplit def load_slices(path, z_start60, z_end120, step5): 读取3D sMRI按轴状位抽层返回切片数组和对应层号 img nib.load(path).get_fdata() # 灰度归一化到[0,1]避免不同扫描亮度差异 img (img - img.min()) / (img.max() - img.min() 1e-8) slices [] for z in range(z_start, z_end, step): slices.append(img[:, :, z]) return np.stack(slices, axis0) # shape: (N, H, W) # 按受试者切分防止同一人多次随访泄漏 subjects np.array([...]) # 每个样本对应的subject ID labels np.array([...]) # 0CN, 1MCI, 2AD gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, val_idx next(gss.split(np.zeros(len(labels)), labels, groupssubjects))这段代码的关键在GroupShuffleSplit的groups参数它保证同一个受试者的所有切片只出现在训练集或验证集之一。z_start、z_end、step三个参数控制切片数量和覆盖范围如果显存紧张可以把 step 调到 8 或 10但别低于 5否则相邻切片几乎一样信息冗余。归一化那行加了1e-8是防止全黑切片除零。2.3 数据增强别把海马体翻转没了医学影像增强和自然图像不一样。水平翻转在自然图像里没问题但在脑影像里会改变左右半脑的解剖语义海马体左右不对称是 AD 的重要特征翻转等于把这个信号抹掉。我一般只用小角度旋转±10°、平移±5%、缩放±5%和轻微高斯噪声。颜色抖动、Cutout 这类增强在灰度 MRI 上意义不大反而可能遮住关键区域。3. ResNet 选型与优化点设计从 baseline 到改进模型3.1 为什么是 ResNet 而不是 VGG 或纯 CNNResNet 的核心是残差连接它让梯度能绕过若干卷积层直接回传缓解了深层网络的退化问题。在 AD 识别任务上海马体萎缩是弥散性变化需要较大的感受野浅层 CNN 感受野不够VGG 堆到 16 层以上又容易梯度消失。ResNet18 或 ResNet34 在参数量和感受野之间比较平衡课程设计阶段用 ResNet18 做 baseline 最稳妥。如果要用预训练权重注意 ImageNet 是 RGB 三通道而 MRI 是单通道常见做法是把第一层卷积的权重在通道维度求平均再复制成单通道或者直接改成nn.Conv2d(1, 64, ...)并随机初始化第一层。3.2 优化点一把 SE 注意力嵌进残差块ResNet 的残差块对所有通道一视同仁但 AD 切片里海马体区域和背景的贡献显然不同。SENet 的 squeeze-and-excitation 模块能自适应学习通道权重嵌进 BasicBlock 后模型会更关注有判别力的通道。实现上就是在残差相加之前对out做一次全局平均池化、两层全连接、Sigmoid再乘回out。import torch import torch.nn as nn class SEBasicBlock(nn.Module): def __init__(self, inplanes, planes, stride1, reduction16): super().__init__() self.conv1 nn.Conv2d(inplanes, planes, 3, stride, 1, biasFalse) self.bn1 nn.BatchNorm2d(planes) self.conv2 nn.Conv2d(planes, planes, 3, 1, 1, biasFalse) self.bn2 nn.BatchNorm2d(planes) self.relu nn.ReLU(inplaceTrue) # SE模块通道注意力 self.se nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(planes, planes // reduction, 1), nn.ReLU(inplaceTrue), nn.Conv2d(planes // reduction, planes, 1), nn.Sigmoid() ) self.downsample None if stride ! 1 or inplanes ! planes: self.downsample nn.Sequential( nn.Conv2d(inplanes, planes, 1, stride, biasFalse), nn.BatchNorm2d(planes) ) def forward(self, x): identity x out self.relu(self.bn1(self.conv1(x))) out self.bn2(self.conv2(out)) out out * self.se(out) # 通道加权 if self.downsample is not None: identity self.downsample(x) return self.relu(out identity)reduction16是 SE 模块的压缩比通道数少的时候可以调到 8否则中间层太窄。AdaptiveAvgPool2d(1)把每个通道压成一个标量两层 1x1 卷积相当于全连接Sigmoid 保证权重在 0 到 1 之间。注意 SE 模块放在残差相加之前这样注意力只调制主分支恒等映射保持干净。3.3 优化点二多切片特征融合单张切片信息有限同一个受试者的多张切片其实可以融合。常见做法有两种一是把多张切片堆成多通道输入比如 12 张切片当成 12 通道第一层卷积改成nn.Conv2d(12, 64, ...)二是每张切片单独过 CNN再对特征做池化或注意力融合。第一种实现简单但通道间没有显式交互第二种更灵活但代码量大。课程设计里我一般推荐第一种把in_channels改成切片数即可改动最小效果通常能涨 2 到 3 个百分点。3.4 优化点三损失函数加类别权重和 L2 正则三分类里 MCI 样本通常最少交叉熵会被 CN 和 AD 主导。给nn.CrossEntropyLoss传weight参数按类别频率的倒数设置能让模型更关注 MCI。另外优化器里加 weight_decay比如Adam(model.parameters(), lr1e-4, weight_decay1e-4)这就是 L2 正则的常见实现方式能压住过拟合。如果验证集准确率震荡大可以把 weight_decay 调到 5e-4 试试。from collections import Counter counts Counter(labels[train_idx]) total sum(counts.values()) # 类别权重 总数 / (类别数 * 该类样本数) weights torch.tensor([total / (len(counts) * counts[i]) for i in range(3)], dtypetorch.float32) criterion nn.CrossEntropyLoss(weightweights) optimizer torch.optim.Adam(model.parameters(), lr1e-4, weight_decay1e-4)weights的计算保证少数类获得更大梯度。注意权重别设得太极端如果 MCI 只有 CN 的十分之一权重差十倍以上会让训练不稳定可以先开方缓和一下。4. 训练流程与参数配置能复现的最小闭环4.1 训练循环与学习率调度课程设计的训练循环不需要太复杂但有几个点必须做对每个 epoch 打乱训练集、验证集不增强、保存验证集最优模型而不是最后一个 epoch。学习率调度用余弦退火或 StepLR 都行我一般用CosineAnnealingLR初始 lr 设 1e-4T_max 设总 epoch 数。from torch.optim.lr_scheduler import CosineAnnealingLR device torch.device(cuda if torch.cuda.is_available() else cpu) model SEBasicBlock # 此处指代完整网络实际需组装 model model.to(device) scheduler CosineAnnealingLR(optimizer, T_max50) best_acc 0.0 for epoch in range(50): model.train() for imgs, labs in train_loader: imgs, labs imgs.to(device), labs.to(device) optimizer.zero_grad() loss criterion(model(imgs), labs) loss.backward() optimizer.step() scheduler.step() # 验证 model.eval() correct total 0 with torch.no_grad(): for imgs, labs in val_loader: imgs, labs imgs.to(device), labs.to(device) pred model(imgs).argmax(dim1) correct (pred labs).sum().item() total labs.size(0) acc correct / total if acc best_acc: best_acc acc torch.save(model.state_dict(), best_ad_resnet.pth)T_max50要和总 epoch 数一致否则余弦周期走不完。保存最优模型而不是最后一个是因为验证集准确率通常在 30 到 40 epoch 之间达到峰值之后可能过拟合。model.eval()和torch.no_grad()在验证阶段必须加否则 BatchNorm 的 running mean 会被验证集更新结果不可信。4.2 关键参数表参数推荐值说明输入尺寸224x224与 ImageNet 预训练一致便于迁移batch size16 或 32显存 8G 以下用 16初始学习率1e-4预训练模型微调不宜过大weight_decay1e-4L2 正则强度过拟合时调到 5e-4epoch50配合余弦退火优化器Adam课程设计够用SGD 调起来更麻烦切片数12z 从 60 到 120step54.3 评价指标别只看准确率三分类任务里准确率会被多数类拉高必须同时看混淆矩阵、MCI 的召回率和 F1。我一般会在报告里放一张混淆矩阵热力图重点看 MCI 有多少被误判成 CN。如果 MCI 召回率低于 50%说明模型没学到 MCI 的细微特征可以考虑加大 MCI 的类别权重或者单独对海马体区域做裁剪再训练。5. 避坑与排查课程设计里最容易翻车的五个点5.1 现象验证集准确率比训练集还高原因数据泄漏。同一受试者的多次随访切片被分到了训练集和验证集模型在验证集上见到了「熟人」。解决用GroupShuffleSplit按 subject ID 切分切分前先确认每个受试者的所有切片都归到同一侧。5.2 现象loss 变成 NaN原因学习率太大或者输入没归一化导致梯度爆炸。解决先把 lr 降到 1e-5 跑几个 epoch 看 loss 是否正常下降检查输入像素值是否在 [0,1] 或标准化到均值 0 方差 1如果用了预训练权重确认第一层单通道改造后没有引入异常值。5.3 现象MCI 召回率极低几乎全判成 CN原因类别不平衡加上 MCI 和 CN 特征差异小。解决给 CrossEntropyLoss 传类别权重对 MCI 样本做重采样或者把三分类改成层次分类先分 AD vs 非 AD再在非 AD 里分 MCI vs CN。5.4 现象训练时显存溢出原因batch size 太大或者输入切片数太多。解决把 batch size 降到 8 或 16把切片数从 12 降到 8如果还不行把输入尺寸从 224 降到 128但注意预训练权重的位置编码会失配需要重新微调。5.5 现象换了随机种子结果波动很大原因数据量小初始化敏感。解决固定所有随机种子torch、numpy、random跑 3 到 5 个种子取平均报告里写均值±标准差。课程设计里如果只跑一次就下结论答辩时容易被问住。6. 进阶技巧用 Grad-CAM 验证模型到底在看哪里模型准确率上去了但你怎么知道它看的是海马体而不是扫描伪影Grad-CAM 能生成热力图把模型关注区域叠加回原切片。具体做法是取最后一个卷积层的输出和对应梯度做全局平均池化得到通道权重再加权求和、ReLU、上采样到原图尺寸。import cv2 def grad_cam(model, img_tensor, target_layer, class_idxNone): img_tensor: (1, C, H, W)target_layer: 模型中的卷积层 features [] grads [] def forward_hook(module, inp, out): features.append(out) def backward_hook(module, grad_in, grad_out): grads.append(grad_out[0]) handle_f target_layer.register_forward_hook(forward_hook) handle_b target_layer.register_backward_hook(backward_hook) output model(img_tensor) if class_idx is None: class_idx output.argmax(dim1).item() model.zero_grad() output[0, class_idx].backward() fmap features[0].detach().cpu().numpy()[0] # (C, h, w) grad grads[0].detach().cpu().numpy()[0] # (C, h, w) weights grad.mean(axis(1, 2)) # (C,) cam np.tensordot(weights, fmap, axes([0], [0])) cam np.maximum(cam, 0) cam cv2.resize(cam, (img_tensor.shape[3], img_tensor.shape[2])) cam (cam - cam.min()) / (cam.max() - cam.min() 1e-8) handle_f.remove() handle_b.remove() return camtarget_layer一般选最后一个残差块的conv2那里感受野最大语义信息最强。weights是每个通道的梯度均值代表该通道对目标类别的重要性。np.maximum(cam, 0)是 ReLU只保留正贡献区域。生成的 cam 可以叠加到原切片上如果高亮区域集中在海马体附近说明模型学到了有解剖意义的特征如果高亮在颅骨边缘或背景说明模型可能走了捷径需要检查预处理是否到位。我自己的习惯是每跑完一个模型至少抽 10 个验证样本看 Grad-CAM如果超过 3 个样本的高亮区域明显偏离海马体这个模型即使准确率高我也不会写进报告。课程设计答辩时一张能解释的热力图比多两个百分点的准确率更有说服力。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

525张鸡目标检测数据集:VOC与YOLO格式解析及训练实践

525张鸡目标检测数据集:VOC与YOLO格式解析及训练实践

简介:一份面向目标检测与计算机视觉学习者的鸡只图像标注数据集,包含525张真实场景JPEG图片及其对应的Pascal VOC和YOLO格式标注文件,类别涵盖Chicken,共607个矩形标注框。数据集使用LabelImg工具以统一规则绘制,适合用…

2026/10/11 14:28:28 阅读更多 →
SEED脑电情绪识别:从.mat读取到CNN建模的完整预处理链

SEED脑电情绪识别:从.mat读取到CNN建模的完整预处理链

简介:本资源是一套基于SEED公开数据集的EEG情绪识别完整实验代码与结果记录包,面向脑机接口、生物信号处理及机器学习方向的本科生、研究生与科研初学者,用于开展情绪状态分类建模与算法对比研究。压缩包共18个文件,含4个核心Pyth…

2026/10/11 15:03:14 阅读更多 →
终端编码工具内核拆解:双会话内核与事件溯源架构设计

终端编码工具内核拆解:双会话内核与事件溯源架构设计

1. 项目缘起:为什么要啃下这个终端编码工具的内核第一次接触这个工具是在一个内部技术分享会上,某位开发者演示了如何在终端里用自然语言直接驱动代码修改,整个过程没有离开命令行,也没有打开任何图形界面。当时我的第一反应是“这…

2026/10/11 13:59:22 阅读更多 →

最新新闻

德思特 GNSS 模拟器技术参数详解:700+通道、1000Hz 迭代率、可模拟1200颗卫星的全星座仿真方案

德思特 GNSS 模拟器技术参数详解:700+通道、1000Hz 迭代率、可模拟1200颗卫星的全星座仿真方案

在高阶自动驾驶 HiL 闭环、低空无人系统及高动态 PNT(定位、导航、定时)测试中,传统户外路测往往受环境干扰大且场景难以 100% 复现。针对工程选型关注的核心参数与信号支持能力,德思特 GNSS 模拟器基于 Skydel 引擎与 SDA 软件定…

2026/10/11 22:52:37 阅读更多 →
vnpy量化实战:多因子选股+LightGBM动态仓位优化闭环

vnpy量化实战:多因子选股+LightGBM动态仓位优化闭环

简介:本资源是一套基于vn.py框架深度二次开发的量化投资实践项目,面向金融工程开发者、量化交易学习者及AI金融交叉领域从业者,解决选股自动化、策略回测工程化与机器学习模型集成等核心问题。压缩包共1656个文件,体量59.07MB&…

2026/10/11 22:52:37 阅读更多 →
vllm-metal 加载 GGUF 量化模型完整指南:Mac 本地部署 LLM 的省钱秘籍

vllm-metal 加载 GGUF 量化模型完整指南:Mac 本地部署 LLM 的省钱秘籍

【免费下载链接】vllm-metal Community maintained hardware plugin for vLLM on Apple Silicon 项目地址: https://gitcode.com/gh_mirrors/vl/vllm-metal 点击查看 免费下载 vllm-metal 是一个社区维护的硬件插件,让 vLLM 能够运行在 Apple Silicon&a…

2026/10/11 22:52:37 阅读更多 →
家电维修预约欧米到家|博世洗衣机维修预约|附近师傅上门检修|欧米到家报修热线

家电维修预约欧米到家|博世洗衣机维修预约|附近师傅上门检修|欧米到家报修热线

前言🌆 国内住宅业态丰富,各地老城老旧管网老化、水质杂质多,城市高层住宅水压波动频繁,全国大部分地区属于湿润气候,梅雨季、多雨季节潮湿多雨、空气湿度极高,冬夏温差大,差异化的居家工况让洗…

2026/10/11 22:52:37 阅读更多 →
构网型储能变流器参数整定:虚拟惯量、阻尼与下垂系数实战解析

构网型储能变流器参数整定:虚拟惯量、阻尼与下垂系数实战解析

最近在调试一个构网型储能样机,100kW 的柜子在离网工况下带 RLC 负载,光是 J 和 D 两个参数就调了两个晚上。功率波形要么像水面波纹一样持续荡漾,要么频率响应慢到让人怀疑控制器死机。后来我才意识到,构网型变流器能不能真正工程…

2026/10/11 22:52:37 阅读更多 →
MySQL子查询完全指南:分类、执行流程、性能优化与常见坑

MySQL子查询完全指南:分类、执行流程、性能优化与常见坑

子查询在MySQL里被很多人当成"会用但说不清"的技术点。SQL子查询用得好,能把复杂统计拆成清晰的嵌套逻辑;用不好,一条慢查询直接拖垮业务接口。这篇文章我把子查询从分类、执行流程到性能优化、报错排查完整过一遍,所有…

2026/10/11 22:51:36 阅读更多 →

日新闻

流感时间序列预测实战: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/11 10:45:37 阅读更多 →
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/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练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/11 14:36:54 阅读更多 →