SAGE框架:子目标条件化动作生成在强化学习规划中的应用
在强化学习和机器人控制领域如何让智能体在复杂环境中高效规划并执行动作一直是个核心挑战。传统的规划方法往往面临计算复杂度高或难以处理高维状态空间的困境。近期提出的 SAGESubgoal-Conditioned Action Generation框架通过将子目标条件化动作生成与潜在世界模型规划相结合为解决这一问题提供了新思路。本文将深入解析 SAGE 的核心原理、实现细节及实际应用帮助读者掌握这一前沿技术。1. SAGE 框架概述与核心价值1.1 什么是 SAGESAGE 是一种基于子目标条件化动作生成的规划框架其核心思想是将复杂的长期任务分解为一系列可管理的子目标然后在潜在空间中进行规划并生成具体动作。与传统的端到端学习方法不同SAGE 通过显式地建模子目标与动作之间的关系实现了更高效、更可解释的决策过程。该框架主要由三个关键组件构成潜在世界模型Latent World Model、子目标生成器Subgoal Generator和动作生成器Action Generator。潜在世界模型负责将高维观察映射到低维潜在空间子目标生成器在潜在空间中规划合理的子目标序列动作生成器则根据当前状态和子目标生成具体的控制指令。1.2 解决的核心问题SAGE 主要针对强化学习中的几个经典难题首先是长期信用分配问题即如何将长期回报合理地分配给中间决策步骤其次是探索效率问题在大型状态空间中如何有效探索最后是样本效率问题如何用有限的经验数据学习有效的策略。通过子目标分解SAGE 将复杂的长期任务转化为一系列简单的短期任务每个子目标都对应一个相对简单的控制问题。这种分解不仅降低了学习难度还提高了算法的稳定性和可解释性。1.3 应用场景与优势SAGE 框架特别适用于需要长期规划的任务场景如机器人导航、游戏 AI、自动驾驶等。在这些场景中智能体需要综合考虑多步决策的影响而不仅仅是即时奖励。与传统方法相比SAGE 的优势主要体现在三个方面首先子目标条件化使得动作生成更加有针对性避免了无效探索其次潜在空间规划大大降低了计算复杂度最后模块化设计使得不同组件可以独立改进和调优。2. 技术原理深度解析2.1 潜在世界模型Latent World Model潜在世界模型是 SAGE 框架的基础其作用是将高维的原始观察如图像、传感器数据编码为低维的潜在表示。这种编码不仅压缩了数据维度还提取了与环境动态相关的关键特征。典型的世界模型采用变分自编码器VAE或类似结构包含编码器、动态预测器和解码器。编码器将当前观察映射到潜在状态动态预测器根据当前潜在状态和动作预测下一时刻的潜在状态解码器则从潜在状态重建观察。import torch import torch.nn as nn class LatentWorldModel(nn.Module): def __init__(self, obs_dim, action_dim, latent_dim32): super().__init__() self.encoder nn.Sequential( nn.Linear(obs_dim, 128), nn.ReLU(), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, latent_dim * 2) # 输出均值和方差 ) self.dynamics nn.Sequential( nn.Linear(latent_dim action_dim, 64), nn.ReLU(), nn.Linear(64, latent_dim) ) self.decoder nn.Sequential( nn.Linear(latent_dim, 64), nn.ReLU(), nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, obs_dim) ) def encode(self, obs): h self.encoder(obs) mu, logvar h.chunk(2, dim-1) return mu, logvar def predict(self, z, action): return self.dynamics(torch.cat([z, action], dim-1))2.2 子目标生成与规划子目标生成是 SAGE 的核心创新点。在潜在空间中算法需要生成一系列中间子目标这些子目标应该满足两个条件一是可达性即从当前状态能够通过有限步骤到达二是导向性即子目标序列应该引导智能体向最终目标前进。常用的子目标生成方法包括基于采样的规划如 RRT*、基于优化的方法如模型预测控制 MPC或学习-based 方法。SAGE 通常采用分层规划策略在高层次生成粗粒度的子目标序列在低层次进行细粒度的动作生成。class SubgoalPlanner: def __init__(self, world_model, horizon10): self.world_model world_model self.horizon horizon def plan(self, start_z, goal_z): 在潜在空间中规划子目标序列 subgoals [] current_z start_z # 使用模型预测控制进行规划 for t in range(self.horizon): # 计算向目标方向的前进步骤 direction goal_z - current_z step_size direction / (self.horizon - t) next_subgoal current_z step_size # 验证子目标的可达性 if self._is_reachable(current_z, next_subgoal): subgoals.append(next_subgoal) current_z next_subgoal else: # 如果不可达调整子目标 adjusted self._adjust_subgoal(current_z, goal_z) subgoals.append(adjusted) current_z adjusted return subgoals def _is_reachable(self, from_z, to_z, max_steps5): 检查子目标是否在有限步骤内可达 # 简化的可达性检查实际中需要更复杂的验证 distance torch.norm(to_z - from_z) return distance 2.0 # 阈值可根据具体环境调整2.3 动作生成机制动作生成器接收当前状态和子目标输出具体的控制动作。这个组件通常采用策略网络的形式可以通过强化学习或模仿学习进行训练。关键设计点在于如何平衡子目标导向与即时奖励。过于专注于子目标可能导致忽略环境中的即时机会而过于关注即时奖励又可能偏离长期目标。SAGE 通过设计合适的目标函数来解决这一矛盾。class ActionGenerator(nn.Module): def __init__(self, state_dim, subgoal_dim, action_dim): super().__init__() self.network nn.Sequential( nn.Linear(state_dim subgoal_dim, 128), nn.ReLU(), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, action_dim), nn.Tanh() # 假设动作范围在 [-1, 1] ) def forward(self, state, subgoal): input_tensor torch.cat([state, subgoal], dim-1) return self.network(input_tensor)3. 完整实现与训练流程3.1 环境准备与依赖配置实现 SAGE 框架需要以下环境配置Python 3.8PyTorch 1.9Gym 或类似强化学习环境可选MuJoCo 用于物理仿真依赖安装命令pip install torch1.9.0 gym0.21.0 numpy matplotlib3.2 网络架构整合将各个组件整合为完整的 SAGE 系统class SAGE: def __init__(self, obs_dim, action_dim, latent_dim32, planning_horizon10): self.world_model LatentWorldModel(obs_dim, action_dim, latent_dim) self.planner SubgoalPlanner(self.world_model, planning_horizon) self.action_generator ActionGenerator(latent_dim, latent_dim, action_dim) # 优化器 self.world_optimizer torch.optim.Adam(self.world_model.parameters()) self.action_optimizer torch.optim.Adam(self.action_generator.parameters()) def train_world_model(self, observations, actions): 训练世界模型 self.world_model.train() losses [] for obs, action in zip(observations, actions): # 编码当前状态 mu, logvar self.world_model.encode(obs) z self._reparameterize(mu, logvar) # 预测下一状态 next_z_pred self.world_model.predict(z, action) # 计算重建损失和动态预测损失 recon_loss F.mse_loss(self.world_model.decoder(z), obs) dynamics_loss F.mse_loss(next_z_pred, mu) # 简化损失计算 total_loss recon_loss dynamics_loss losses.append(total_loss) self.world_optimizer.zero_grad() total_loss.backward() self.world_optimizer.step() return torch.stack(losses).mean() def _reparameterize(self, mu, logvar): 重参数化技巧 std torch.exp(0.5 * logvar) eps torch.randn_like(std) return mu eps * std3.3 训练流程设计SAGE 的训练采用分阶段策略世界模型预训练使用收集的环境数据单独训练世界模型确保其能够准确预测环境动态。策略网络训练固定世界模型训练动作生成器以最大化累积奖励。联合微调同时优化世界模型和策略网络进一步提高性能。def train_sage(sage, env, num_episodes1000): 完整的训练循环 for episode in range(num_episodes): obs env.reset() episode_reward 0 trajectory [] for step in range(env.max_steps): # 编码当前观察 with torch.no_grad(): z, _ sage.world_model.encode(torch.FloatTensor(obs)) # 规划子目标简化版实际中需要更复杂的规划 goal_z torch.zeros_like(z) # 假设目标状态 subgoals sage.planner.plan(z, goal_z) current_subgoal subgoals[0] if subgoals else goal_z # 生成动作 action sage.action_generator(z, current_subgoal) action action.detach().numpy() # 执行动作 next_obs, reward, done, _ env.step(action) episode_reward reward # 保存转移数据 trajectory.append((obs, action, reward, next_obs, done)) obs next_obs if done: break # 使用收集的数据更新模型 if len(trajectory) 0: observations, actions, rewards, next_observations, dones zip(*trajectory) sage.train_world_model(observations, actions) print(fEpisode {episode}, Reward: {episode_reward})4. 实战应用迷宫导航任务4.1 任务定义与环境设置以二维迷宫导航为例智能体需要从起点到达目标位置。迷宫包含障碍物智能体只能观测到局部环境信息。import numpy as np class MazeEnv: def __init__(self, size10): self.size size self.obstacles [(2,2), (2,3), (5,5), (5,6)] self.start_pos (0, 0) self.goal_pos (9, 9) self.current_pos self.start_pos self.max_steps 100 def reset(self): self.current_pos self.start_pos return self._get_observation() def _get_observation(self): # 返回当前位置和局部障碍物信息 obs np.zeros((3, 3)) # 3x3 局部视野 center_x, center_y 1, 1 # 观察中心 for dx in [-1, 0, 1]: for dy in [-1, 0, 1]: world_x self.current_pos[0] dx world_y self.current_pos[1] dy if (world_x, world_y) in self.obstacles: obs[center_x dx, center_y dy] 1 # 障碍物 elif (world_x, world_y) self.goal_pos: obs[center_x dx, center_y dy] 2 # 目标 return obs.flatten()4.2 SAGE 在迷宫任务中的配置针对迷宫任务需要调整模型参数和训练策略# 初始化 SAGE 系统 obs_dim 9 # 3x3 局部观察 action_dim 2 # x,y 方向移动 sage_maze SAGE(obs_dim, action_dim, latent_dim16, planning_horizon5) # 训练配置 env MazeEnv() train_sage(sage_maze, env, num_episodes500)4.3 性能评估与结果分析训练完成后评估 SAGE 在迷宫任务中的表现def evaluate_sage(sage, env, num_trials10): successes 0 total_steps 0 for trial in range(num_trials): obs env.reset() steps 0 for step in range(env.max_steps): with torch.no_grad(): z, _ sage.world_model.encode(torch.FloatTensor(obs)) goal_z torch.zeros_like(z) # 简化目标表示 action sage.action_generator(z, goal_z).numpy() obs, reward, done, _ env.step(action) steps 1 if done: successes 1 break total_steps steps success_rate successes / num_trials avg_steps total_steps / num_trials print(f成功率: {success_rate:.2f}, 平均步数: {avg_steps:.1f}) return success_rate, avg_steps5. 常见问题与调试技巧5.1 训练不收敛问题问题现象奖励曲线震荡或持续不上升世界模型预测误差大。可能原因学习率设置不当潜在空间维度不合适批次大小过小梯度爆炸或消失解决方案使用学习率调度器如余弦退火尝试不同的潜在维度通常 16-64增大批次大小但注意内存限制添加梯度裁剪torch.nn.utils.clip_grad_norm_# 改进的优化器配置 def create_optimizers(model, lr1e-3): optimizer torch.optim.Adam(model.parameters(), lrlr) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max100) return optimizer, scheduler5.2 子目标不可达问题问题现象智能体频繁无法到达生成的子目标导致规划失效。可能原因世界模型预测不准确子目标间距过大动作空间限制解决方案加强世界模型训练增加更多样化的数据调整子目标密度确保相邻子目标可达在动作生成器中添加约束class ConstrainedActionGenerator(ActionGenerator): def __init__(self, state_dim, subgoal_dim, action_dim, max_action_norm1.0): super().__init__(state_dim, subgoal_dim, action_dim) self.max_norm max_action_norm def forward(self, state, subgoal): action super().forward(state, subgoal) # 对动作范数进行约束 action_norm torch.norm(action, dim-1, keepdimTrue) action action / torch.maximum(action_norm, torch.tensor(self.max_norm)) return action5.3 内存与计算效率优化问题现象训练速度慢内存占用高。优化策略使用经验回放缓冲区实现批量规划采用分布式训练from collections import deque import random class ReplayBuffer: def __init__(self, capacity10000): self.buffer deque(maxlencapacity) def push(self, transition): self.buffer.append(transition) def sample(self, batch_size): return random.sample(self.buffer, batch_size) def __len__(self): return len(self.buffer)6. 进阶技巧与最佳实践6.1 多尺度子目标规划对于复杂任务可以采用多尺度规划策略。在高层生成宏观子目标在底层生成细粒度动作。class HierarchicalPlanner: def __init__(self, high_level_planner, low_level_planner): self.high_planner high_level_planner self.low_planner low_level_planner def plan(self, start_state, final_goal): # 高层规划生成粗粒度子目标序列 macro_subgoals self.high_planner.plan(start_state, final_goal) detailed_plan [] current_state start_state for macro_goal in macro_subgoals: # 底层规划为每个宏观子目标生成详细动作序列 micro_plan self.low_planner.plan(current_state, macro_goal) detailed_plan.extend(micro_plan) current_state macro_goal # 假设完美执行 return detailed_plan6.2 不确定性感知规划在实际环境中世界模型存在预测不确定性。优秀的规划器应该考虑这种不确定性。class UncertaintyAwarePlanner(SubgoalPlanner): def plan_with_uncertainty(self, start_z, goal_z, uncertainty_threshold0.1): subgoals [] current_z start_z while torch.norm(current_z - goal_z) uncertainty_threshold: # 考虑预测不确定性选择最可靠的子目标 candidate_subgoals self._generate_candidates(current_z, goal_z) best_subgoal self._select_most_reliable(current_z, candidate_subgoals) subgoals.append(best_subgoal) current_z best_subgoal return subgoals def _select_most_reliable(self, from_z, candidates): 选择预测不确定性最小的子目标 uncertainties [] for candidate in candidates: # 估计到达该子目标的不确定性 uncertainty self._estimate_uncertainty(from_z, candidate) uncertainties.append(uncertainty) min_idx torch.argmin(torch.tensor(uncertainties)) return candidates[min_idx]6.3 迁移学习与领域自适应SAGE 框架具有良好的迁移学习能力可以通过以下策略实现特征解耦将环境特定特征与任务相关特征分离渐进式训练从简单任务开始逐步增加难度元学习学习快速适应新环境的能力class TransferSAGE(SAGE): def __init__(self, source_domain_dim, target_domain_dim, shared_latent_dim): # 共享的世界模型核心域特定的编码器 self.shared_world_model LatentWorldModel(shared_latent_dim) self.domain_encoders { source: DomainEncoder(source_domain_dim, shared_latent_dim), target: DomainEncoder(target_domain_dim, shared_latent_dim) } def encode(self, obs, domain): domain_specific self.domain_encoders[domain](obs) return self.shared_world_model.encode(domain_specific)7. 实际工程部署考虑7.1 实时性要求处理在实时控制场景中需要平衡规划质量与计算延迟异步规划在后台线程进行重规划前台执行当前最优计划模型简化部署时使用轻量级网络版本缓存机制复用相似状态的规划结果7.2 安全性与鲁棒性生产环境部署必须考虑安全性动作约束确保生成的动作在物理限制范围内故障检测监控规划与执行的一致性回退策略当规划失败时启用保守策略7.3 监控与调试工具建立完善的监控体系规划质量指标子目标达成率、路径最优性模型健康度预测误差、不确定性估计性能指标推理延迟、内存使用SAGE 框架通过将复杂的决策问题分解为可管理的子任务为强化学习在实际应用中的落地提供了有力工具。掌握这一技术需要深入理解其各个组件的相互作用并在具体任务中仔细调参和验证。

相关新闻

Linear Loops自动化工作流:提升团队开发效率的完整指南

Linear Loops自动化工作流:提升团队开发效率的完整指南

在项目迭代和团队协作中,重复性的任务流转、状态同步和跨工具数据搬运往往消耗大量开发时间。Linear 最新推出的 Loops 功能,正是瞄准了这一痛点,旨在通过自动化工作流简化循环工程操作。本文将完整解析 Loops 的核心概念、适用场景&#xff…

2026/10/16 12:45:55 阅读更多 →
60%的知识库文档从未被检索过——你在用20%的文档回答100%的问题

60%的知识库文档从未被检索过——你在用20%的文档回答100%的问题

核心观点:知识库不是“越多越好”。我去跑了一个查询,盯了半天——六成的文档,过去30天一次都没被搜过。 我去做了件大多数人不会做的事:给知识库里每一条文档,查一下它过去30天被检索过多少次。 1200条FAQ。我按检索…

2026/10/17 21:39:34 阅读更多 →
开源软件商业化:从信任建立到价值变现的完整路径

开源软件商业化:从信任建立到价值变现的完整路径

开源软件到底能不能赚钱?这是很多开发者和创业公司都在思考的问题。最近看到一种观点:"开源首先解决的是信任问题,其次是流量。能不能赚钱取决于这东西有没有价值,是不是开源是其次的。"这句话看似简单,却道…

2026/10/17 8:03:55 阅读更多 →

最新新闻

算法习题答案背后的机制:递归、复杂度与工程避坑

算法习题答案背后的机制:递归、复杂度与工程避坑

简介:《数据结构与算法分析(Java语言描述)》第三版教材的配套习题答案文档,适合正在系统学习数据结构和算法、需要用习题检验自身理解的Java学习者与备考学生。文档覆盖文件处理与递归调用、递归算法设计(如求整数二进…

2026/10/17 21:39:43 阅读更多 →
MySQL学生成绩管理系统数据库设计与建表实战指南

MySQL学生成绩管理系统数据库设计与建表实战指南

简介:一套完整的基于Python Flask与MySQL数据库的学生成绩管理系统课程设计报告,适用于计算机科学与技术、软件工程等专业的学生,也适合自学Web开发并需要完成数据库课程设计任务的人。系统围绕学生成绩管理核心业务展开,覆盖在线…

2026/10/17 21:39:43 阅读更多 →
语音识别中的CTC与RNN-T:端到端对齐与工程选型详解

语音识别中的CTC与RNN-T:端到端对齐与工程选型详解

如果你正好在啃语音识别的经典教材或者课程,大概率会在前几章就碰到 CTC 和 RNN-T 这两个名字。我当年第一次看到这两个缩写,脑子里全是问号:CTC 不是通讯技术么,RNN-T 和 RNN 又差在哪?后来在几个语音项目里真正把这两…

2026/10/17 21:39:43 阅读更多 →
144页Transformer讲义精读指南:从注意力机制到工程实践

144页Transformer讲义精读指南:从注意力机制到工程实践

简介:这份144页PPT配套视频课程,面向希望系统掌握Transformer架构的NLP学习者、算法工程师与高校师生,从序列建模的经典方法讲起,逐步深入到注意力机制、大语言模型与高效Transformer等前沿内容。包内共1个PDF文件,约5…

2026/10/17 21:39:43 阅读更多 →
Kafka接入AI的三种路线:MCP提案、会话记忆与实时上下文

Kafka接入AI的三种路线:MCP提案、会话记忆与实时上下文

1. 从一条业务问题说起:Kafka 和 AI 之间到底缺了什么我大概从 2016 年开始重度使用 Kafka,最开始是帮一家电商公司搭日志管道,后来陆续做过金融风控事件流、IoT 设备数据上报、实时数仓同步,再到近两年开始给大模型应用做数据底座…

2026/10/17 21:39:43 阅读更多 →
Python Flask+MySQL学生成绩管理系统:数据库设计与触发器实践

Python Flask+MySQL学生成绩管理系统:数据库设计与触发器实践

简介:围绕 MySQL 学生成绩管理系统展开的数据库课程设计文档,完整覆盖系统总体设计、需求分析、概要设计、系统实现与测试总结全流程,适合计算机专业学生用于数据库课设、毕业设计或 FlaskMySQL 入门实践。文档以华东交通大学课程设计报告为原…

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

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →