分层强化学习实战指南从Option框架到子任务发现的决策地图如果你正在为强化学习项目中的长期稀疏奖励问题而头疼看着智能体在复杂环境中像无头苍蝇一样乱撞那么分层强化学习HRL很可能就是你寻找的那把钥匙。它不是什么遥不可及的学术概念而是一种能切实将庞大任务拆解、让智能体学会“分步骤思考”的工程范式。无论是让机器人完成从走到跑再到取物的连贯动作还是让游戏AI学会探索、收集、战斗的系列操作HRL都提供了一种结构化的解决方案。本文面向的是已经熟悉基础RL概念并希望将理论落地到实际项目的开发者。我们将绕过繁琐的公式推导直击核心面对一个具体问题你该选择基于Option的框架还是转向子任务发现的方法我们将通过GridWorld和MuJoCo的实战代码片段为你绘制清晰的决策地图。1. 核心范式之争Option框架与子任务发现在深入代码之前我们必须厘清分层强化学习中两大主流范式的根本区别。这并非孰优孰劣的问题而是适用场景与先验知识要求的差异。Option框架更像是一种“自上而下”的设计哲学。它要求我们在算法运行之前就凭借对任务的理解人工定义好一系列子任务称为Options。每个Option是一个三元组I, π, βI (Initiation Set): Option可以被启动的状态集合。π (Policy): Option内部执行的策略。β (Termination Condition): Option终止的概率函数。它的优势在于结构清晰、可解释性强。上层策略Meta-Policy只需在多个预定义的Option之间做选择每个Option负责执行一段时间的底层动作。例如在机器人控制中我们可以预先定义“走到A点”、“拿起物体”、“放下物体”等Options。上层策略学习如何组合这些基本技能来完成“将物体从A搬运到B”的复合任务。# Option框架的简化伪代码结构 class Option: def __init__(self, initiation_set, policy, termination_condition): self.I initiation_set # 可启动的状态条件 self.pi policy # Option内部策略一个神经网络或查表 self.beta termination_condition # 终止函数 def run(self, state): 执行该Option直到终止 trajectory [] while not self.beta(state): action self.pi(state) state, reward, done, _ env.step(action) trajectory.append((state, action, reward)) return state, trajectory # 上层策略学习选择哪个Option class MetaPolicy: def select_option(self, state, available_options): # 基于状态评估每个Option的长期价值选择最优 values [critic(state, option) for option in available_options] return available_options[np.argmax(values)]注意Option框架的成功高度依赖于人工设计的子任务是否合理。如果Option划分不当例如粒度过粗或过细反而会限制智能体的学习能力。相比之下子任务发现走的是“自下而上”的路径。我们并不预先告诉智能体“子任务是什么”而是让它通过与环境交互自动地发现状态空间中反复出现的模式、瓶颈状态或是有用的技能片段。这类方法的核心思想通常是最大化某种信息论目标例如技能与所产生的轨迹之间的互信息Mutual Information从而驱使智能体学到多样且有用的技能。两者的关键抉择点可以总结为下表特性维度Option框架子任务发现先验知识需求高需人工定义子任务低可完全从数据中学习可解释性强每个Option有明确语义弱发现的技能可能难以用语言描述灵活性较低一旦定义难以在线扩展高可动态发现新技能典型算法Option-Critic, MAXQDIAYN, VALOR, SeCTAR适用场景任务结构清晰领域知识丰富环境复杂任务结构未知或难以定义如果你的任务像组装家具一样步骤明确先装A再拧B最后固定C那么Option框架是你的首选。如果你的任务更像教一个婴儿探索世界你并不知道他会先学会爬、走还是跑那么子任务发现更能带来惊喜。2. Option-Critic实战在GridWorld中学会“宏观决策”让我们在一个经典的GridWorld环境中实现Option-Critic算法。这个环境是一个网格世界智能体需要从起点(S)走到目标(G)中间有障碍物(W)。我们将设计两个Option一个倾向于横向移动一个倾向于纵向移动让上层策略学会在何时选择“横着走”还是“竖着走”。首先我们定义环境和一个简单的神经网络策略。import torch import torch.nn as nn import torch.optim as optim import numpy as np class GridWorld: def __init__(self, size5): self.size size self.start (0, 0) self.goal (size-1, size-1) self.obstacles [(1,1), (2,3), (3,1)] self.state self.start self.actions [0, 1, 2, 3] # 上下左右 def reset(self): self.state self.start return self.state def step(self, action): x, y self.state if action 0: # 上 x max(x-1, 0) elif action 1: # 下 x min(x1, self.size-1) elif action 2: # 左 y max(y-1, 0) elif action 3: # 右 y min(y1, self.size-1) next_state (x, y) if next_state in self.obstacles: next_state self.state # 撞墙留在原地 self.state next_state done (next_state self.goal) reward 10.0 if done else -0.1 # 稀疏奖励步数惩罚 return next_state, reward, done, {} # 定义Option策略网络和终止函数网络 class OptionPolicy(nn.Module): def __init__(self, input_dim, output_dim, num_options): super().__init__() self.shared nn.Linear(input_dim, 32) self.option_policies nn.ModuleList([nn.Linear(32, output_dim) for _ in range(num_options)]) self.terminations nn.ModuleList([nn.Linear(32, 1) for _ in range(num_options)]) def forward(self, state, option): x torch.relu(self.shared(state)) policy_logits self.option_policies[option](x) termination_prob torch.sigmoid(self.terminations[option](x)) return policy_logits, termination_prob接下来是Option-Critic算法的核心训练循环。算法会同时更新上层策略选择Option、每个Option的内部策略以及Option的终止函数。def train_option_critic(env, num_options2, episodes1000): input_dim 2 # (x, y)坐标 output_dim len(env.actions) model OptionPolicy(input_dim, output_dim, num_options) optimizer optim.Adam(model.parameters(), lr0.001) for episode in range(episodes): state env.reset() state_tensor torch.FloatTensor(state) option np.random.choice(num_options) # 初始随机选择Option done False total_reward 0 while not done: # 1. 根据当前状态和Option得到动作概率和终止概率 policy_logits, term_prob model(state_tensor, option) action_dist torch.distributions.Categorical(logitspolicy_logits) action action_dist.sample() # 2. 执行动作 next_state, reward, done, _ env.step(action.item()) total_reward reward next_state_tensor torch.FloatTensor(next_state) # 3. 计算TD误差和梯度此处为简化版省略critic网络 # ... (实际实现需包含Q值计算和策略梯度更新) # 4. 决定是否终止当前Option if torch.rand(1) term_prob.item() or done: option np.random.choice(num_options) # 终止后重新选择 state_tensor next_state_tensor if episode % 100 0: print(fEpisode {episode}, Total Reward: {total_reward:.2f})在这个简单示例中智能体通过学习会让“横向移动”Option在需要左右调整位置时激活而“纵向移动”Option则在需要上下移动时激活。上层策略学会了在合适的时机切换Option从而更高效地到达目标。关键点在于Option的终止条件β也是可学习的。智能体会发现在拐角处终止当前Option并重新选择往往能获得更好的长期回报。3. 子任务发现实战用DIAYN在MuJoCo中发掘“隐藏技能”当环境变得高维连续如MuJoCo的机器人仿真环境预先定义有意义的Option变得极其困难。这时子任务发现方法大放异彩。我们以DIAYNDiversity is All You Need为例展示如何让一个HalfCheetah猎豹机器人完全通过无监督学习自主发现奔跑、转弯、跳跃等基础技能。DIAYN的核心思想非常巧妙它假设存在一个隐变量z代表技能并最大化技能z与执行该技能所产生的状态s之间的互信息。直观上这迫使不同的z对应截然不同的状态分布从而学到不同的技能。其优化目标可分解为多样性奖励一个判别器q(z|s)试图根据状态猜测技能z。策略π(a|s,z)的目标是让判别器更容易猜对即最大化log q(z|s)。熵正则项鼓励策略在不同状态下动作分布具有高熵促进探索。import gym import torch import torch.nn as nn import torch.optim as optim import numpy as np from torch.distributions import Normal # 定义技能判别器 class SkillDiscriminator(nn.Module): def __init__(self, state_dim, skill_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, skill_dim) ) def forward(self, state): logits self.net(state) return torch.distributions.Categorical(logitslogits) # 输出技能类别的分布 # 定义基于技能的策略网络 class SkillPolicy(nn.Module): def __init__(self, state_dim, action_dim, skill_dim): super().__init__() self.skill_embedding nn.Embedding(skill_dim, 16) self.shared_net nn.Sequential( nn.Linear(state_dim 16, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), ) self.mean_layer nn.Linear(256, action_dim) self.log_std_layer nn.Parameter(torch.zeros(1, action_dim)) def forward(self, state, skill): skill_emb self.skill_embedding(skill) x torch.cat([state, skill_emb], dim-1) x self.shared_net(x) mean self.mean_layer(x) log_std self.log_std_layer.expand_as(mean) std torch.exp(log_std) return Normal(mean, std) def train_diayn(env_nameHalfCheetah-v4, num_skills10, epochs1000): env gym.make(env_name) state_dim env.observation_space.shape[0] action_dim env.action_space.shape[0] policy SkillPolicy(state_dim, action_dim, num_skills) discriminator SkillDiscriminator(state_dim, num_skills) policy_optimizer optim.Adam(policy.parameters(), lr3e-4) disc_optimizer optim.Adam(discriminator.parameters(), lr3e-4) for epoch in range(epochs): # 为每个技能收集轨迹 for skill in range(num_skills): state env.reset() episode_states [] for t in range(1000): # 最大步数 state_tensor torch.FloatTensor(state).unsqueeze(0) skill_tensor torch.tensor([skill]) # 策略根据状态和技能输出动作分布 action_dist policy(state_tensor, skill_tensor) action action_dist.sample() next_state, reward, done, _ env.step(action.squeeze(0).detach().numpy()) episode_states.append(state) state next_state if done: break # 转换状态数据 states torch.FloatTensor(np.array(episode_states)) skills torch.tensor([skill] * len(states)) # 更新判别器最大化 log q(z|s) disc_dist discriminator(states) disc_loss -disc_dist.log_prob(skills).mean() # 交叉熵损失 disc_optimizer.zero_grad() disc_loss.backward() disc_optimizer.step() # 更新策略最大化 [log q(z|s) 熵正则项] # 重新采样动作以计算梯度 action_dist policy(states, skills) actions action_dist.rsample() # 使用重参数化技巧 # 计算多样性奖励判别器给出的log概率 with torch.no_grad(): disc_dist_new discriminator(states) diversity_reward disc_dist_new.log_prob(skills) # 计算策略的熵 entropy action_dist.entropy().mean(dim-1) # 策略损失是负的奖励 熵 policy_loss -(diversity_reward 0.1 * entropy).mean() policy_optimizer.zero_grad() policy_loss.backward() policy_optimizer.step() if epoch % 100 0: print(fEpoch {epoch}, Disc Loss: {disc_loss.item():.3f}, Policy Loss: {policy_loss.item():.3f})训练结束后你可以通过固定不同的技能编号z从0到9来查看智能体学到了什么。你可能会发现某个z对应着向前奔跑另一个对应着向左转还有一个可能对应着某种跳跃的步态。这一切都是在没有给出任何任务目标如向前跑的情况下纯粹通过“让行为多样化”这个无监督目标学到的。这些技能之后可以作为高层策略的“基础动作库”用于快速学习下游任务例如“跑到某个特定位置”。4. 决策框架何时用Option何时用子任务发现经过前面的原理剖析和实战演练我们现在可以系统地回答这个核心问题。选择哪种范式取决于你面临的问题特性、资源约束和最终目标。坚定选择Option框架的场景任务结构先验明确你非常清楚完成目标需要哪些步骤。例如在工业流程控制中“打开阀门A”、“加热到X度”、“注入物料B”这些步骤是工程师已知的。对可解释性和安全性要求极高在自动驾驶、医疗机器人等领域我们需要确切知道智能体在每一层做出了什么决策。Option框架中每个子任务都有清晰的定义和边界便于调试和验证。需要快速原型验证当你想验证分层思想是否对你的任务有效时手动设计几个有代表性的Option是最快的方式。这能帮你快速建立性能基线。计算资源有限子任务发现方法尤其是基于互信息的通常需要大量的无监督预训练和更复杂的模型如判别器。Option框架的训练相对更直接计算开销更小。转向子任务发现方法的场景任务结构未知或极其复杂你面对的是一个全新的、黑盒的环境比如一个新的视频游戏或一个复杂的物理仿真环境你无法预知哪些子技能是有效的。追求终极灵活性与泛化能力你希望智能体能够自主发现一个丰富的、可重用的技能库。这些技能可能超出你的想象为解决未来未知任务提供基础。这在终身学习Lifelong Learning和元学习Meta-Learning场景中至关重要。拥有海量的无监督交互数据你可以在任务无关的环境中进行大规模预训练例如让机器人在空房间里随意活动。子任务发现方法能充分利用这些数据挖掘出环境本身所蕴含的“物理常识”技能。下游任务多样你需要一个通用的“技能底座”来支持多个不同的高层任务。例如一个机器人技能库可以同时用于“搬运物品”、“清理房间”和“接待客人”等任务。混合策略与进阶思路在实际项目中黑白分明的选择很少见更多是灰度决策。一个成熟的HRL系统往往是混合体两阶段学习首先使用子任务发现方法如DIAYN在目标环境中进行无监督预训练挖掘出一组基础技能。然后将这些技能“封装”成Options再使用Option框架如Option-Critic来学习组合这些技能完成特定任务。这结合了子任务发现的自动化优势和Option框架的结构化学习效率。分层中的分层对于极度复杂的任务可以设计多级分层。最底层是原子动作中间层是由子任务发现产生的技能如“移动”、“转向”最上层则是人工定义的、具有语义的Option如“导航到房间A”。上层提供任务导向下层提供灵活的执行能力。我曾在一个人形机器人平衡行走的项目中就采用了这种混合策略。我们先让机器人在模拟中无监督地学习各种腿部摆动模式子任务发现得到了几十种保持平衡的微技能。然后我们将这些技能作为底层Option上层策略只需要学习在身体重心发生偏移时调用哪个微技能组合来恢复平衡。这比直接从零开始学习平衡策略要快得多也稳定得多。最终没有银弹。Option框架提供了可控性和可解释性子任务发现提供了自动化和泛化潜力。你的选择应该基于对项目需求最坦诚的评估是更需要一个精准的工具还是一个充满可能性的工具箱理解这两把利刃的刃口朝向才能在你的强化学习征途中披荆斩棘。