1.4 像素级访问与遍历策略
1.4.1 类型安全访问方法 1.4.1.1 —— 单路径推理:子目标分解、顺序执行、级联规划
核心问题:当智能体面对一个遥远的目标时,它该如何把"从纽约走到洛杉矶"这样的宏大任务,拆解成"先走到街口 → 再走到公交站 → 再…"这样一步步可执行的子目标?更重要的是,这些子目标之间如何像多米诺骨牌一样级联推进,而不是各自为政?
目录
- 宏观视角:为什么我们需要"单路径推理"?
- 中观视角:三层架构——从目标到子目标到动作
- 微观视角:子目标如何"级联"——HIQL 的洞察
- 类型安全访问:子目标的表示与可达性边界
- 顺序执行的力学:从向量到矩阵
- 训练闭环:这到底意味着什么?
- 延伸阅读
1. 宏观视角:为什么我们需要"单路径推理"?
别急,我们先从一个具体的场景聊起。
想象你正在玩一个迷宫游戏。终点在地图的另一端,中间隔着十几个房间,奖励极其稀疏——只有到达终点才给 1 分,其他时候都是 0。传统的强化学习(比如 flat Q-learning)会怎么做?它会在迷宫里随机乱撞,期望某天"恰好"摸到终点。听起来就很绝望对吧?
这就是长程稀疏奖励问题(Long-horizon Sparse Reward Problem)。在学术文献中,这被形式化为一个 Goal-Conditioned MDP:
M = <G, S, A, R, T, H>
其中 G 是目标集合,S 是状态空间,A 是动作空间,R 是奖励函数,T 是转移概率,H 是时间跨度。
当 H 很大(比如需要 1000 步才能到达目标),且 R 只在 s = g 时才给奖励时,智能体几乎学不到任何有效信号。
“单路径推理”(Single-Path Reasoning) 就是我们解决这个问题的核心思路:
不直接学"如何从起点到终点",而是学"如何从当前状态到下一个可达的子目标,然后从那个子目标再到下一个…",像接力赛一样,一棒接一棒。
听起来抽象对吧?如果把整个任务画成一张地图,单路径推理就像是在地图上画一条折线——每个转折点就是一个子目标,而智能体只需要关注"从当前位置到下一个转折点"这一段直线距离。
图 1:传统 flat RL vs 单路径级联推理。左侧是"一步登天"的绝望探索,右侧是把长程任务拆解为多个短程子任务的接力赛。
2. 中观视角:三层架构——从目标到子目标到动作
现在我们已经了解了"为什么要拆解",接下来看看"怎么拆解"。
在继续之前,我需要先介绍一个你已经熟悉的概念作为锚点:RNN 的层级结构。在 RNN 中,我们有输入层 → 隐藏层 → 输出层,信息逐层流动。单路径推理的层级结构与之神似,但流动的不是特征,而是目标。
2.1 三层架构的全景图
图 2:单路径级联规划的三层架构。高层负责"看地图定方向"(生成子目标),低层负责"脚踏实地走路"(执行动作),环境反馈触发重新规划。
这个架构在文献中有多种实现,但核心思想一致。根据 Hierarchical Implicit Q-Learning (HIQL) 的框架,我们可以把它抽象为三个核心组件:
| 组件 | 职责 | 类比 |
|---|---|---|
| 价值函数 Vθ(s, g) | 评估"从状态 s 到目标 g 有多好" | GPS 导航的距离估算 |
| 高层策略 πh | 选择下一个子目标 gt+k | 看地图决定下一个路口 |
| 低层策略 πl | 执行原始动作到达子目标 | 实际走路/开车 |
2.2 关键洞察:为什么分层后信号更清晰?
这里有一个反直觉的点。你可能会想:“分成两层不是更复杂了吗?”
别急,让我们看看 HIQL 的核心洞察。假设价值函数有一定的噪声(这在离线 RL 中很常见)。在 flat 方法中,低层策略需要从噪声中分辨出"哪个动作能让我最终到达遥远的目标"。但由于目标太远,不同动作之间的价值差异被噪声淹没了——这就是信噪比问题(Signal-to-Noise Ratio Problem)。
但如果我们分层:
- 高层策略只需要比较"哪个子目标让我离最终目标更近"——子目标之间的价值差异通常很大,信噪比高。
- 低层策略只需要比较"哪个动作让我离附近的子目标更近"——距离短,价值差异相对明显,信噪比也高。
图 3:分层架构如何提升信噪比。左侧 flat 策略中,遥远目标导致动作间价值差异微小;右侧分层后,高层比较子目标(差异大),低层比较动作到附近子目标(差异也大)。
3. 微观视角:子目标如何"级联"——HIQL 的洞察
现在我们已经了解了宏观架构,接下来要深入最微妙的环节:子目标之间的级联关系。
3.1 级联的本质:一个子目标是下一个的"起点"
“级联”(Cascade)这个词听起来很高级,但其实概念很简单。想象你正在爬楼梯:
第 n 级台阶,既是到达第 n+1 级台阶的起点,也是到达第 n 级台阶的终点。
在单路径推理中,这个关系被严格编码。根据 HIQL 的数学形式,高层策略生成子目标:
g<sub>t+k</sub> ~ π<sup>h</sup>(s<sub>t+k</sub> | s<sub>t</sub>, g)
而低层策略的目标就是到达这个子目标:
a<sub>t</sub> ~ π<sup>l</sup>(a<sub>t</sub> | s<sub>t</sub>, φ(s<sub>t+k</sub>))
注意这里的级联关系:当低层策略成功到达 gt+k 后,环境状态变为 st+k = gt+k。此时高层策略再次启动,以 st+k 为新的"当前状态",生成下一个子目标 gt+2k。
这就像接力赛中,前一棒的终点就是后一棒的起点。
图 4:级联执行的时间线。每个高层决策对应 k 个低层动作。当低层成功到达子目标后,高层以新状态为起点重新规划。
3.2 Toy Example:一个极简的级联规划
让我们用一个玩具例子来建立直觉。假设状态空间是一维的:s ∈ [0, 10],目标 g = 10,当前 s0 = 0。每步可以移动 ±1。
Flat 方法:策略需要直接学 “π(a|s, g=10)”。当 s=0 时,它需要判断 a=+1 和 a=-1 哪个最终能到 10。但由于距离太远,价值差异很小,学习很困难。
级联方法:设 k=3(每 3 步重新规划一次)。
- 第 1 轮:s0=0,高层生成子目标 g3=3。低层执行 3 步 +1 动作,到达 s3=3。
- 第 2 轮:s3=3,高层生成子目标 g6=6。低层执行 3 步 +1 动作,到达 s6=6。
- 第 3 轮:s6=6,高层生成子目标 g9=9。低层执行 3 步 +1 动作,到达 s9=9。
- 第 4 轮:s9=9,高层生成子目标 g=10。低层执行 1 步 +1 动作,到达终点。
import numpy as np
class CascadePlanner:
"""
单路径级联规划的极简实现
状态空间: 1D 连续空间 [0, 10]
动作: {-1, +1}
"""
def __init__(self, k=3, epsilon=0.5):
self.k = k # 高层规划间隔
self.epsilon = epsilon # 子目标达成阈值
self.goal = 10 # 最终目标
def high_level_policy(self, s_t, g):
"""
高层策略: 生成 k 步后的子目标
简单启发式: 向目标方向走 k 步
"""
direction = np.sign(g - s_t)
subgoal = s_t + direction * self.k
# 裁剪到合理范围
subgoal = np.clip(subgoal, 0, g)
return subgoal
def low_level_policy(self, s_t, subgoal):
"""
低层策略: 向子目标移动
"""
if abs(subgoal - s_t) < self.epsilon:
return 0 # 已到达
return np.sign(subgoal - s_t)
def execute(self, s0):
"""
级联执行完整轨迹
"""
s = s0
trajectory = [s]
high_level_steps = 0
subgoal = None
while abs(s - self.goal) > self.epsilon:
# 高层规划: 每 k 步或到达子目标后重新规划
if high_level_steps % self.k == 0 or (subgoal is not None and abs(s - subgoal) < self.epsilon):
subgoal = self.high_level_policy(s, self.goal)
print(f" [高层] 状态 {s:.1f} -> 子目标 {subgoal:.1f}")
# 低层执行
action = self.low_level_policy(s, subgoal)
s = np.clip(s + action, 0, 10)
trajectory.append(s)
high_level_steps += 1
if len(trajectory) > 100: # 安全上限
break
print(f" [完成] 到达目标! 总步数: {len(trajectory)-1}")
return trajectory
# 运行示例
print("=" * 50)
print("Toy Example: 1D 级联规划")
print("=" * 50)
planner = CascadePlanner(k=3)
traj = planner.execute(s0=0.0)
print(f"轨迹: {[f'{x:.1f}' for x in traj]}")
输出:
==================================================
Toy Example: 1D 级联规划
==================================================
[高层] 状态 0.0 -> 子目标 3.0
[高层] 状态 3.0 -> 子目标 6.0
[高层] 状态 6.0 -> 子目标 9.0
[高层] 状态 9.0 -> 子目标 10.0
[完成] 到达目标! 总步数: 10
轨迹: ['0.0', '1.0', '2.0', '3.0', '4.0', '5.0', '6.0', '7.0', '8.0', '9.0', '10.0']
看,这就是级联的本质:每个子目标既是当前段的终点,也是下一段的起点。不需要一次性看到终点,只需要看到"下一个路口"。
4. 类型安全访问:子目标的表示与可达性边界
现在我们已经了解了级联的直觉,接下来要处理一个棘手的问题:子目标到底该怎么表示?
4.1 子目标的表示困境
在高层策略中,子目标 gt+k 理论上可以是任何状态。但如果状态空间是高维的(比如图像观测),让神经网络直接输出一个 64×64×3 的图像作为子目标,这几乎是不可能的——生成模型会崩溃。
HIQL 的解决方案非常优雅:不直接生成子目标状态,而是生成子目标的"表示"(Representation)。
具体来说,价值函数被参数化为:
V<sub>θ</sub>(s, g) = V<sub>θ</sub>(s, φ<sub>θ</sub>(g))
其中 φθ(g) 是从价值函数中间层提取的目标表示。高层策略只需要输出这个紧凑的表示向量 zt+k = φ(st+k),而不是完整的状态。
图 5:HIQL 的表示学习方案。左侧直接生成高维状态不可行;右侧通过价值函数提取紧凑表示,高层策略只需输出低维向量。
4.2 可达性边界:子目标不能太"远"
还有一个关键问题:高层策略生成的子目标,低层策略一定能到达吗?
根据 HAC (Hierarchical Actor-Critic) 和 SSE (Strict Subgoal Execution) 的研究,如果子目标超出了低层策略的"能力半径",整个级联就会断裂。
图 6:可达性边界(Policy Competence Radius)。子目标必须在低层策略的能力范围内,否则级联断裂。
SSE 框架专门解决了这个问题,引入了前沿经验回放(Frontier Experience Replay)来区分可达和不可达的子目标,确保高层策略只选择"在能力范围内"的子目标。
import torch
import torch.nn as nn
class GoalRepresentationEncoder(nn.Module):
"""
HIQL 风格的目标表示编码器
从价值函数中间层提取紧凑表示
"""
def __init__(self, state_dim, repr_dim=256):
super().__init__()
# 状态编码器
self.state_encoder = nn.Sequential(
nn.Linear(state_dim, 512),
nn.ReLU(),
nn.Linear(512, repr_dim)
)
# 目标编码器 (与状态共享结构,但独立参数)
self.goal_encoder = nn.Sequential(
nn.Linear(state_dim, 512),
nn.ReLU(),
nn.Linear(512, repr_dim)
)
# 价值函数头
self.value_head = nn.Sequential(
nn.Linear(repr_dim * 2, 256),
nn.ReLU(),
nn.Linear(256, 1)
)
def encode_goal(self, g):
"""编码目标为紧凑表示 φ(g)"""
return self.goal_encoder(g)
def encode_state(self, s):
"""编码状态"""
return self.state_encoder(s)
def forward(self, s, g):
"""
计算价值 V(s, φ(g))
"""
z_s = self.encode_state(s)
z_g = self.encode_goal(g)
# 拼接状态和目标的表示
combined = torch.cat([z_s, z_g], dim=-1)
value = self.value_head(combined)
return value, z_g
class HighLevelPolicy(nn.Module):
"""
高层策略: 生成子目标表示
"""
def __init__(self, state_dim, repr_dim, k_steps=3):
super().__init__()
self.k = k_steps
self.net = nn.Sequential(
nn.Linear(state_dim + repr_dim, 512), # 输入: 状态 + 最终目标表示
nn.ReLU(),
nn.Linear(512, repr_dim) # 输出: 子目标表示 (不是完整状态!)
)
def forward(self, s, goal_repr):
"""
生成 k 步后的子目标表示
s: 当前状态
goal_repr: 最终目标的表示 φ(g)
"""
x = torch.cat([s, goal_repr], dim=-1)
subgoal_repr = self.net(x)
return subgoal_repr
class LowLevelPolicy(nn.Module):
"""
低层策略: 执行动作到达子目标
"""
def __init__(self, state_dim, repr_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim + repr_dim, 512),
nn.ReLU(),
nn.Linear(512, action_dim),
nn.Tanh() # 连续动作空间
)
def forward(self, s, subgoal_repr):
"""
根据当前状态和子目标表示生成动作
"""
x = torch.cat([s, subgoal_repr], dim=-1)
action = self.net(x)
return action
# 测试表示维度
print("=" * 50)
print("类型安全访问: 表示维度测试")
print("=" * 50)
state_dim = 100
repr_dim = 256
action_dim = 4
batch_size = 32
# 模拟数据
s = torch.randn(batch_size, state_dim)
g = torch.randn(batch_size, state_dim)
# 价值函数
value_net = GoalRepresentationEncoder(state_dim, repr_dim)
value, z_g = value_net(s, g)
print(f"状态维度: {state_dim} -> 目标表示维度: {repr_dim}")
print(f"价值函数输出形状: {value.shape}")
print(f"目标表示形状: {z_g.shape}")
# 高层策略
high_policy = HighLevelPolicy(state_dim, repr_dim, k_steps=3)
subgoal_repr = high_policy(s, z_g)
print(f"高层策略输出 (子目标表示): {subgoal_repr.shape}")
# 低层策略
low_policy = LowLevelPolicy(state_dim, repr_dim, action_dim)
action = low_policy(s, subgoal_repr)
print(f"低层策略输出 (动作): {action.shape}")
print(f"动作范围: [{action.min():.2f}, {action.max():.2f}]")
5. 顺序执行的力学:从向量到矩阵
现在我们已经了解了单个时间步的运作,接下来看看多个时间步如何顺序执行,以及如何从向量级实现推广到矩阵级实现。
5.1 单步的力学:优势加权回归
在继续之前,我们需要理解策略是如何从价值函数"提取"出来的。
根据 HIQL 和 AWR (Advantage-Weighted Regression),高层和低层策略都通过优势加权回归训练:
L<sub>π<sup>h</sup></sub> = E[ exp(β · A<sup>h</sup>(s<sub>t</sub>, s<sub>t+k</sub>, g)) · ||π<sup>h</sup>(s<sub>t</sub>, g) - s<sub>t+k</sub>||<sup>2</sup> ]
其中优势函数定义为:
A<sup>h</sup>(s<sub>t</sub>, s<sub>t+k</sub>, g) = V(s<sub>t+k</sub>, g) - V(s<sub>t</sub>, g)
这个公式的直觉是:如果某个子目标 st+k 能让价值大幅提升(优势高),我们就让高层策略更倾向于选择它。β 是温度参数,控制"只选最好的"还是"也给次优的一些机会"。
图 7:优势加权回归的直觉。价值提升越大的子目标,获得越高的训练权重。
5.2 从向量到矩阵:批量级联执行
在实际训练中,我们不会一次处理一个样本,而是批量处理。这就涉及到从向量级到矩阵级的推广。
import torch
import torch.nn.functional as F
class BatchCascadeExecutor:
"""
矩阵级批量级联执行器
同时处理多个 episode 的级联规划
"""
def __init__(self, value_net, high_policy, low_policy, k=3, horizon=100):
self.value_net = value_net
self.high_policy = high_policy
self.low_policy = low_policy
self.k = k
self.horizon = horizon
def compute_advantages(self, states, goals, subgoals):
"""
批量计算优势函数
states: [B, T, state_dim]
goals: [B, goal_dim]
subgoals: [B, T, state_dim]
返回: A^h(s_t, s_{t+k}, g) = V(s_{t+k}, g) - V(s_t, g)
"""
B, T, _ = states.shape
# 扩展目标以匹配时间维度
goals_expanded = goals.unsqueeze(1).expand(B, T, -1) # [B, T, goal_dim]
# 计算 V(s_t, g) 对所有时间步
states_flat = states.reshape(B * T, -1)
goals_flat = goals_expanded.reshape(B * T, -1)
with torch.no_grad():
values_current, _ = self.value_net(states_flat, goals_flat)
values_current = values_current.reshape(B, T)
# 计算 V(s_{t+k}, g)
# 对于最后 k 步,使用最后一个状态
subgoals_shifted = torch.cat([
subgoals[:, self.k:],
subgoals[:, -self.k:].repeat(1, 1, 1) # 填充
], dim=1)
subgoals_flat = subgoals_shifted.reshape(B * T, -1)
with torch.no_grad():
values_subgoal, _ = self.value_net(subgoals_flat, goals_flat)
values_subgoal = values_subgoal.reshape(B, T)
# 优势 = 未来价值 - 当前价值
advantages = values_subgoal - values_current # [B, T]
return advantages
def high_level_advantage_weighted_loss(self, states, goals, subgoals, beta=1.0):
"""
高层策略的优势加权回归损失
"""
B, T, state_dim = states.shape
# 获取目标表示
_, goal_repr = self.value_net(states[:, 0], goals) # [B, repr_dim]
goal_repr_expanded = goal_repr.unsqueeze(1).expand(B, T, -1) # [B, T, repr_dim]
# 高层策略生成子目标表示
states_flat = states.reshape(B * T, state_dim)
goal_repr_flat = goal_repr_expanded.reshape(B * T, -1)
predicted_subgoal_repr = self.high_policy(states_flat, goal_repr_flat)
predicted_subgoal_repr = predicted_subgoal_repr.reshape(B, T, -1)
# 获取真实子目标的表示
subgoals_flat = subgoals.reshape(B * T, state_dim)
with torch.no_grad():
true_subgoal_repr, _ = self.value_net(subgoals_flat, subgoals_flat)
true_subgoal_repr = true_subgoal_repr.reshape(B, T, -1)
# 计算优势作为权重
advantages = self.compute_advantages(states, goals, subgoals) # [B, T]
weights = torch.exp(beta * advantages).detach() # 停止梯度
# 加权 MSE 损失
mse = torch.mean((predicted_subgoal_repr - true_subgoal_repr) ** 2, dim=-1) # [B, T]
weighted_loss = torch.mean(weights * mse)
return weighted_loss, advantages.mean().item()
def low_level_advantage_weighted_loss(self, states, subgoals, actions, beta=1.0):
"""
低层策略的优势加权回归损失
"""
B, T, state_dim = states.shape
_, action_dim = actions.shape[1:]
# 获取子目标表示
subgoals_flat = subgoals.reshape(B * T, state_dim)
with torch.no_grad():
subgoal_repr, _ = self.value_net(subgoals_flat, subgoals_flat)
subgoal_repr = subgoal_repr.reshape(B, T, -1)
# 低层策略生成动作
states_flat = states.reshape(B * T, state_dim)
subgoal_repr_flat = subgoal_repr.reshape(B * T, -1)
predicted_actions = self.low_policy(states_flat, subgoal_repr_flat)
predicted_actions = predicted_actions.reshape(B, T, action_dim)
# 计算低层优势: V(s_{t+1}, subgoal) - V(s_t, subgoal)
next_states = torch.cat([states[:, 1:], states[:, -1:]], dim=1)
states_flat = states.reshape(B * T, state_dim)
next_states_flat = next_states.reshape(B * T, state_dim)
subgoals_flat_expanded = subgoals.reshape(B * T, state_dim)
with torch.no_grad():
v_current, _ = self.value_net(states_flat, subgoals_flat_expanded)
v_next, _ = self.value_net(next_states_flat, subgoals_flat_expanded)
v_current = v_current.reshape(B, T)
v_next = v_next.reshape(B, T)
advantages = v_next - v_current
weights = torch.exp(beta * advantages).detach()
# 加权 MSE
mse = torch.mean((predicted_actions - actions) ** 2, dim=-1)
weighted_loss = torch.mean(weights * mse)
return weighted_loss
# 测试批量级联执行
print("=" * 50)
print("矩阵级实现: 批量级联执行")
print("=" * 50)
B, T, state_dim, repr_dim, action_dim = 4, 50, 100, 256, 4
# 初始化网络
value_net = GoalRepresentationEncoder(state_dim, repr_dim)
high_policy = HighLevelPolicy(state_dim, repr_dim, k=3)
low_policy = LowLevelPolicy(state_dim, repr_dim, action_dim)
# 模拟批量轨迹数据
states = torch.randn(B, T, state_dim)
goals = torch.randn(B, state_dim)
subgoals = torch.randn(B, T, state_dim)
actions = torch.randn(B, T, action_dim)
# 级联执行器
executor = BatchCascadeExecutor(value_net, high_policy, low_policy, k=3)
# 计算高层损失
high_loss, avg_adv = executor.high_level_advantage_weighted_loss(states, goals, subgoals)
print(f"高层策略加权损失: {high_loss.item():.4f}")
print(f"平均高层优势: {avg_adv:.4f}")
# 计算低层损失
low_loss = executor.low_level_advantage_weighted_loss(states, subgoals, actions)
print(f"低层策略加权损失: {low_loss.item():.4f}")
print("\n维度检查:")
print(f" 批量大小 B: {B}")
print(f" 时间跨度 T: {T}")
print(f" 状态维度: {state_dim}")
print(f" 表示维度: {repr_dim}")
print(f" 动作维度: {action_dim}")
print(f" 高层输出: [B, T, repr_dim] = [{B}, {T}, {repr_dim}]")
print(f" 低层输出: [B, T, action_dim] = [{B}, {T}, {action_dim}]")
5.3 级联的矩阵视图
如果把整个级联过程画成一个矩阵,每一列是一个时间步,每一行是一个层级:
图 8:级联的矩阵视图。高层策略每隔 k 步激活一次(生成子目标),低层策略每步都激活(执行动作)。状态流是连续的,但高层决策是离散的。
6. 训练闭环:这到底意味着什么?
现在我们已经了解了原理,让我们把视角拉回训练过程,看看这一切在实际训练/使用中意味着什么。
6.1 非平稳性问题:当低层策略"进化"时
在层级强化学习中,有一个臭名昭著的问题:非平稳性(Non-stationarity)。
想象你正在教一个机器人走路:
- 第一周,你(高层)告诉它"先走到门口"。它(低层)连站都站不稳,根本到不了门口。
- 第二周,低层学会了走路,能轻松到达门口了。
- 但此时你(高层)还在用第一周的策略——“走到门口很难,我得选个更近的子目标”。
问题出在哪?高层策略的学习假设(“门口很难到达”)已经过时了,因为低层能力已经进化。这就是"移动靶标"问题。
图 9:非平稳性问题及解决方案。当低层能力进化时,高层策略必须同步更新,否则会陷入次优。
HAC 和后续工作通过后见经验回放(Hindsight Experience Replay)缓解这个问题:即使低层没到达预定子目标,也把"实际到达的状态"重新标记为子目标,让高层从"失败"中学习。
6.2 训练流程的完整代码
import torch
import torch.optim as optim
from collections import deque
class HierarchicalTrainer:
"""
完整的层级训练流程
包含价值函数、高层策略、低层策略的联合训练
"""
def __init__(self, state_dim, repr_dim, action_dim, k=3,
lr=3e-4, beta=1.0, gamma=0.99, tau=0.005):
self.k = k
self.beta = beta
self.gamma = gamma
self.tau = tau # 目标网络软更新系数
# 网络
self.value_net = GoalRepresentationEncoder(state_dim, repr_dim)
self.value_target = GoalRepresentationEncoder(state_dim, repr_dim)
self.value_target.load_state_dict(self.value_net.state_dict())
self.high_policy = HighLevelPolicy(state_dim, repr_dim, k)
self.low_policy = LowLevelPolicy(state_dim, repr_dim, action_dim)
# 优化器
self.v_optimizer = optim.Adam(self.value_net.parameters(), lr=lr)
self.h_optimizer = optim.Adam(self.high_policy.parameters(), lr=lr)
self.l_optimizer = optim.Adam(self.low_policy.parameters(), lr=lr)
# 经验回放
self.replay_buffer = deque(maxlen=100000)
def store_transition(self, s, a, s_next, g, reward, done):
"""存储转移样本"""
self.replay_buffer.append((s, a, s_next, g, reward, done))
def train_value_function(self, batch_size=256):
"""
训练目标条件价值函数 (IQL 风格)
使用 expectile 回归处理分布偏移
"""
if len(self.replay_buffer) < batch_size:
return 0.0
# 采样
indices = torch.randint(0, len(self.replay_buffer), (batch_size,))
batch = [self.replay_buffer[i] for i in indices]
states = torch.stack([torch.tensor(b[0], dtype=torch.float32) for b in batch])
actions = torch.stack([torch.tensor(b[1], dtype=torch.float32) for b in batch])
next_states = torch.stack([torch.tensor(b[2], dtype=torch.float32) for b in batch])
goals = torch.stack([torch.tensor(b[3], dtype=torch.float32) for b in batch])
rewards = torch.tensor([b[4] for b in batch], dtype=torch.float32).unsqueeze(1)
dones = torch.tensor([b[5] for b in batch], dtype=torch.float32).unsqueeze(1)
# 当前价值
v_current, _ = self.value_net(states, goals)
# 目标价值 (使用目标网络)
with torch.no_grad():
v_next, _ = self.value_target(next_states, goals)
v_target = rewards + self.gamma * (1 - dones) * v_next
# Expectile 损失 (κ = 0.7, 更关注高估)
kappa = 0.7
diff = v_target - v_current
weight = torch.where(diff > 0, kappa, 1 - kappa)
value_loss = (weight * (diff ** 2)).mean()
# 优化
self.v_optimizer.zero_grad()
value_loss.backward()
torch.nn.utils.clip_grad_norm_(self.value_net.parameters(), 1.0)
self.v_optimizer.step()
# 软更新目标网络
self.soft_update_target()
return value_loss.item()
def train_policies(self, batch_size=256):
"""
联合训练高层和低层策略
"""
if len(self.replay_buffer) < batch_size:
return 0.0, 0.0
# 采样长程轨迹片段
indices = torch.randint(0, len(self.replay_buffer) - self.k, (batch_size,))
states = []
subgoals = []
actions_list = []
goals = []
for i in indices:
# 获取当前状态和目标
s_t = self.replay_buffer[i][0]
g = self.replay_buffer[i][3]
# k 步后的状态作为子目标
s_tk = self.replay_buffer[i + self.k][2]
# 收集中间动作
acts = [self.replay_buffer[i + j][1] for j in range(self.k)]
states.append(s_t)
subgoals.append(s_tk)
actions_list.append(acts[0]) # 简化: 用第一步动作
goals.append(g)
states = torch.stack([torch.tensor(s, dtype=torch.float32) for s in states])
subgoals = torch.stack([torch.tensor(s, dtype=torch.float32) for s in subgoals])
actions = torch.stack([torch.tensor(a, dtype=torch.float32) for a in actions_list])
goals = torch.stack([torch.tensor(g, dtype=torch.float32) for g in goals])
# 获取目标表示
with torch.no_grad():
_, goal_repr = self.value_net(states, goals)
_, subgoal_repr_true = self.value_net(subgoals, subgoals)
# 训练高层策略
pred_subgoal_repr = self.high_policy(states, goal_repr)
with torch.no_grad():
v_current, _ = self.value_net(states, goals)
v_subgoal, _ = self.value_net(subgoals, goals)
advantages = v_subgoal - v_current
weights = torch.exp(self.beta * advantages).squeeze()
high_loss = (weights * torch.mean((pred_subgoal_repr - subgoal_repr_true) ** 2, dim=1)).mean()
self.h_optimizer.zero_grad()
high_loss.backward()
torch.nn.utils.clip_grad_norm_(self.high_policy.parameters(), 1.0)
self.h_optimizer.step()
# 训练低层策略
pred_actions = self.low_policy(states, subgoal_repr_true.detach())
with torch.no_grad():
# 低层优势: 向子目标靠近
v_s, _ = self.value_net(states, subgoals)
# 简化: 用动作与专家动作的 MSE
low_weights = torch.ones(batch_size) # 简化权重
low_loss = torch.mean((pred_actions - actions) ** 2)
self.l_optimizer.zero_grad()
low_loss.backward()
torch.nn.utils.clip_grad_norm_(self.low_policy.parameters(), 1.0)
self.l_optimizer.step()
return high_loss.item(), low_loss.item()
def soft_update_target(self):
"""软更新目标价值网络"""
for param, target_param in zip(self.value_net.parameters(),
self.value_target.parameters()):
target_param.data.copy_(self.tau * param.data + (1 - self.tau) * target_param.data)
def train_step(self):
"""单步训练"""
v_loss = self.train_value_function()
h_loss, l_loss = self.train_policies()
return v_loss, h_loss, l_loss
# 演示训练流程
print("=" * 50)
print("训练闭环: 完整训练流程")
print("=" * 50)
trainer = HierarchicalTrainer(
state_dim=100,
repr_dim=256,
action_dim=4,
k=3
)
# 模拟填充回放缓冲区
print("填充经验回放缓冲区...")
for i in range(1000):
s = torch.randn(100).numpy()
a = torch.randn(4).numpy()
s_next = s + np.random.randn(100) * 0.1 # 小步转移
g = torch.randn(100).numpy()
r = 1.0 if np.linalg.norm(s_next - g) < 0.5 else 0.0
done = r > 0.5
trainer.store_transition(s, a, s_next, g, r, done)
print(f"缓冲区大小: {len(trainer.replay_buffer)}")
# 训练几步
print("\n执行训练步骤:")
for step in range(5):
v_loss, h_loss, l_loss = trainer.train_step()
print(f" Step {step+1}: V-loss={v_loss:.4f}, H-loss={h_loss:.4f}, L-loss={l_loss:.4f}")
print("\n训练完成。注意:")
print(" - 价值函数使用 expectile 回归处理离线数据偏移")
print(" - 高层策略通过优势加权学习子目标生成")
print(" - 低层策略学习到达子目标的控制策略")
print(" - 目标网络软更新保证训练稳定性")
6.3 级联规划在实际使用中的意义
图 10:从训练到推理的完整闭环。训练阶段从离线数据学习价值函数和策略;推理阶段通过级联子目标逐步逼近最终目标。
7. 延伸阅读
如果你读到这里,应该已经对单路径级联规划有了从直觉到实现的完整理解。以下是一些值得深入阅读的文献,按主题分类:
核心论文
-
HIQL (Hierarchical Implicit Q-Learning) —— Park et al., 2023. 从单一价值函数提取两层策略的开创性工作。本文的核心架构参考。
-
HAC (Hierarchical Actor-Critic) —— Levy et al., 2019. 使用 hindsight experience replay 解决非平稳性问题的经典方法。
-
SSE (Strict Subgoal Execution) —— Han et al., 2025. 最新的子目标可达性保证框架,引入 Frontier Experience Replay。
-
HECRL (Hierarchical Entity-Centric RL) —— Haramati et al., 2026. 将子目标分解与因子化结构结合,处理多实体环境。
-
CoGHP (Chain-of-Goals Hierarchical Policy) —— 2025. 将层级控制重新表述为自回归序列生成,使用 MLP-Mixer 架构。
关键概念索引
| 概念 | 解释 | 首次出现 |
|---|---|---|
| Goal-Conditioned MDP | 目标条件马尔可夫决策过程,智能体接收额外目标输入 | 第 1 节 |
| Signal-to-Noise Ratio | 信噪比问题,长程目标导致动作价值差异被噪声淹没 | 第 2.2 节 |
| Policy Competence Radius | 策略能力半径,低层策略能可靠到达的范围 | 第 4.2 节 |
| Advantage-Weighted Regression | 优势加权回归,从价值函数提取策略的方法 | 第 5.1 节 |
| Non-stationarity | 非平稳性,低层策略进化导致高层策略假设过时 | 第 6.1 节 |
| Hindsight Experience Replay | 后见经验回放,将失败轨迹重新标记为成功 | 第 6.1 节 |
下一步探索方向
- 多路径规划:本文讨论的是单路径(一条子目标链),实际应用中可能需要维护多条候选路径(如 graph-based planning)。
- 自适应 k 值:本文假设 k 固定,但更好的方法可能根据任务难度动态调整规划间隔。
- 扩散模型用于子目标生成:如 ESD 和 HECRL 所示,扩散模型可以生成更高质量的子目标分布。
写在最后:单路径级联规划的本质,是把"不可能完成的任务"变成"一系列可完成的小任务"。就像爬山时不需要一直盯着山顶,只需要看"下一个平台"在哪里。这种"近视但务实"的策略,恰恰是智能体在长程稀疏奖励世界中生存的关键。
本文参考了以下国外前沿文献:HIQL (Park et al., 2023), HAC (Levy et al., 2019), SSE (Han et al., 2025), HECRL (Haramati et al., 2026), CoGHP (2025), ESD, PIP, HIFQL, Probabilistic Subgoal Representations, 以及 Multi-Agent Subgoal HRL。
59



被折叠的 条评论
为什么被折叠?



