主题
目标导向强化学习与 HER
很多任务的目标不是固定的:机械臂要把物体放到桌面上任意指定的位置,导航机器人要去地图上任意一个目的地。为每个目标训练一个策略显然不现实。目标导向强化学习(goal-conditioned RL)把目标
目标条件的 MDP
在普通 MDP 的基础上增加目标空间
- 每个回合开始时采样一个目标
,回合中保持不变; - 奖励依赖目标:
,常见的稀疏形式为
其中
- 策略与价值函数都以目标为输入:
、 。
把
稀疏奖励下的困境
稀疏奖励最大的问题是:在第一次成功之前,所有奖励都是 −1。对价值函数来说,所有状态—动作看起来都一样坏,TD 目标中没有任何能区分好坏的信号,策略梯度也几乎为零。智能体只能靠随机探索“撞上”目标。目标越小、状态空间维度越高,撞上的概率就越低——在机械臂任务中可能几乎为零。
HER:从失败中学习
HER(Andrychowicz 等,2017)的观察是:一条没能到达目标
做法:把轨迹
| 策略 | 替代目标 |
|---|---|
| final | 本条轨迹最后到达的状态 |
| future | 对每个 |
| episode | 从本条轨迹的任意时刻随机取 |
| random | 从整个缓冲区随机取 |
原论文中 future(
HER 有两个使用前提:
- 奖励能按任意目标重新计算:需要知道
的函数形式(例如基于距离的阈值判断),而不仅是环境返回的一个数; - 算法是异策略的:替代目标下的转移不是当前策略在该目标下会产生的数据,只有异策略算法(DQN、DDPG、TD3、SAC 等)才能直接学习。
实验:二维到达任务
为了能在 CPU 上几分钟内跑完,我们自己写了一个最小的目标导向环境 PointGoalEnv:
- 平面
,智能体每回合从原点出发,每步移动 , ; - 目标在正方形内随机采样,且离原点至少 0.5;回合固定 50 步;
- 稀疏奖励:离目标距离小于 0.1 时为 0,否则 −1;成功的标准是第 50 步结束时位于目标 0.1 范围内。
算法用 DDPG(每回合后更新 40 次、批量 256、
py
"""目标导向强化学习:DDPG 与 DDPG + HER(后见经验回放),在自建的二维“到达目标点”环境上对比。
环境是稀疏奖励:没到达目标时每步 -1,到达(距离 < 0.1)时为 0。
用法:python her_point_goal.py # 默认 3 个种子 × 2 种方法
DQN_QUICK=1 python her_point_goal.py # 缩短训练,仅用于检查代码能否运行"""
import copy
import os
import numpy as np
import torch
import torch.nn as nn
QUICK = os.environ.get("DQN_QUICK") == "1"
torch.set_num_threads(1)
class PointGoalEnv:
"""二维平面 [-1, 1]²。智能体从原点出发,每步移动 0.1·a(a ∈ [-1, 1]²),
目标点在正方形内随机采样且离原点至少 0.5。一个回合固定 50 步,不提前终止。"""
T, STEP, TOL = 50, 0.1, 0.1
def __init__(self, seed):
self.rng = np.random.default_rng(seed)
def reset(self):
self.pos = np.zeros(2)
while True:
self.goal = self.rng.uniform(-1, 1, 2)
if np.linalg.norm(self.goal) >= 0.5:
break
return self.pos.copy(), self.goal.copy()
def step(self, a):
self.pos = np.clip(self.pos + self.STEP * np.clip(a, -1, 1), -1, 1)
return self.pos.copy()
@classmethod
def reward(cls, achieved, goal):
"""奖励只取决于“到达的位置”和“目标”,因此可以对任意目标重新计算——这正是 HER 需要的性质。"""
return -(np.linalg.norm(achieved - goal, axis=-1) >= cls.TOL).astype(np.float32)
def mlp(i, o, out=None):
return nn.Sequential(nn.Linear(i, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, o),
*([out] if out else []))
def train(use_her, seed, episodes, updates=40, batch=256, gamma=0.98, tau=0.05, k_future=4):
rng = np.random.default_rng(seed); torch.manual_seed(seed)
env, test_env = PointGoalEnv(seed), PointGoalEnv(10_000 + seed)
actor, critic = mlp(4, 2, nn.Tanh()), mlp(6, 1)
actor_t, critic_t = copy.deepcopy(actor), copy.deepcopy(critic)
opt_a = torch.optim.Adam(actor.parameters(), lr=1e-3)
opt_c = torch.optim.Adam(critic.parameters(), lr=1e-3)
cap = episodes * env.T * (1 + k_future)
buf = {k: torch.zeros(cap, d) for k, d in (("s", 2), ("g", 2), ("a", 2), ("r", 1), ("s2", 2))}
n = 0
clip_q = -1.0 / (1.0 - gamma) # 奖励 ∈ {-1, 0},所以 Q ∈ [-1/(1-γ), 0]
def act(s, g, noise):
with torch.no_grad():
a = actor(torch.as_tensor(np.concatenate([s, g]), dtype=torch.float32)).numpy()
if noise:
a = np.clip(a + 0.2 * rng.standard_normal(2), -1, 1)
if rng.random() < 0.2: # 20% 概率完全随机,保证探索
a = rng.uniform(-1, 1, 2)
return a
def evaluate(n=50):
ok = 0
for _ in range(n):
s, g = test_env.reset()
for _ in range(env.T):
s = test_env.step(act(s, g, noise=False))
ok += np.linalg.norm(s - g) < env.TOL
return ok / n
log = []
for ep in range(1, episodes + 1):
s, g = env.reset(); S, A = [s], []
for _ in range(env.T):
a = act(s, g, noise=True); s = env.step(a); S.append(s); A.append(a)
S, A = np.array(S), np.array(A)
# 原始目标的转移
goals = [np.repeat(g[None], env.T, 0)]
idx = [np.arange(env.T)]
if use_her:
# “future” 策略:对每个时刻 t,再从 t 之后实际到达的位置中随机取 k 个当作“假装的目标”
for _ in range(k_future):
fut = rng.integers(np.arange(env.T) + 1, env.T + 1)
goals.append(S[fut]); idx.append(np.arange(env.T))
for gs, ix in zip(goals, idx):
new = {"s": S[ix], "a": A[ix], "s2": S[ix + 1], "g": gs,
"r": PointGoalEnv.reward(S[ix + 1], gs)[:, None]}
for k, v in new.items():
buf[k][n:n + len(ix)] = torch.as_tensor(v, dtype=torch.float32)
n += len(ix)
for _ in range(updates):
j = torch.as_tensor(rng.integers(0, n, batch))
s_, g_, a_, r_, s2_ = (buf[k][j] for k in ("s", "g", "a", "r", "s2"))
with torch.no_grad():
a2 = actor_t(torch.cat([s2_, g_], 1))
y = (r_ + gamma * critic_t(torch.cat([s2_, g_, a2], 1))).clamp(clip_q, 0.0)
loss_c = ((critic(torch.cat([s_, g_, a_], 1)) - y) ** 2).mean()
opt_c.zero_grad(); loss_c.backward(); opt_c.step()
pi = actor(torch.cat([s_, g_], 1))
loss_a = -critic(torch.cat([s_, g_, pi], 1)).mean() + (pi ** 2).mean() # 小的动作惩罚,防止饱和
opt_a.zero_grad(); loss_a.backward(); opt_a.step()
for net, tgt in ((actor, actor_t), (critic, critic_t)):
for p, pt in zip(net.parameters(), tgt.parameters()):
pt.data.mul_(1 - tau).add_(tau * p.data)
if ep % (50 if QUICK else 100) == 0:
log.append((ep, evaluate()))
return log
if __name__ == "__main__":
episodes = 100 if QUICK else 600
seeds = [0] if QUICK else [0, 1, 2]
# 参照:完全随机的动作能到达目标的比例
env = PointGoalEnv(123); rng = np.random.default_rng(123); hit = 0
for _ in range(1000):
s, g = env.reset()
for _ in range(env.T):
s = env.step(rng.uniform(-1, 1, 2))
hit += np.linalg.norm(s - g) < env.TOL
print(f"随机策略:回合结束时位于目标附近的比例 {hit / 1000:.3f}")
for use_her in (False, True):
name = "DDPG + HER" if use_her else "DDPG "
for seed in seeds:
log = train(use_her, seed, episodes)
print(f"{name} 种子{seed} 测试成功率:" + " ".join(f"{ep}:{sr:.2f}" for ep, sr in log), flush=True)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
实际运行输出:
text
随机策略:回合结束时位于目标附近的比例 0.003
DDPG 种子0 测试成功率:100:0.02 200:0.04 300:0.44 400:1.00 500:1.00 600:1.00
DDPG 种子1 测试成功率:100:0.02 200:0.02 300:0.14 400:0.06 500:0.46 600:1.00
DDPG 种子2 测试成功率:100:0.04 200:0.02 300:0.72 400:1.00 500:1.00 600:1.00
DDPG + HER 种子0 测试成功率:100:1.00 200:1.00 300:1.00 400:1.00 500:1.00 600:1.00
DDPG + HER 种子1 测试成功率:100:1.00 200:1.00 300:1.00 400:1.00 500:1.00 600:1.00
DDPG + HER 种子2 测试成功率:100:1.00 200:1.00 300:1.00 400:1.00 500:1.00 600:1.001
2
3
4
5
6
7
2
3
4
5
6
7
结果解读:
- HER 让学习快了数倍:3 个种子在第一次评估(第 100 回合)时成功率都已是 1.00,此后保持不变。
- 不加 HER 的 DDPG 也能学会,但慢得多且不稳定:第 100–200 回合成功率只有 0.02–0.04,与随机水平相当;之后靠偶然碰到目标积累的成功样本逐渐学会,分别在第 400、600、400 回合达到 1.00。种子 1 在第 300→400 回合还出现了回落(0.14→0.06)。
- 要如实看待这个结论的适用范围:这个任务只有二维,完全随机的动作也有约 0.3% 的回合能停在目标附近,所以普通 DDPG 最终也能学会。在 Fetch 机械臂推、滑、抓取放置等高维任务中,原论文报告不加 HER 的 DDPG 基本无法学会,差距远大于这里。
实现要点
- 奖励函数写成可以批量计算的纯函数:示例中的
PointGoalEnv.reward(achieved, goal)只依赖到达位置和目标,因此可以对任意替代目标重算。Gymnasium-Robotics 的目标导向环境用compute_reward(achieved_goal, desired_goal, info)提供同样的能力,观测是包含observation、achieved_goal、desired_goal三个键的字典。 - 裁剪目标 Q 值:奖励
时 ,把 TD 目标裁剪到这个区间能防止早期的价值发散。 - 动作惩罚与随机探索:小的
惩罚防止 tanh 输出饱和;一部分完全随机动作保证在策略已经较确定时仍有探索。 - 替代目标的比例:future、
意味着缓冲区中约 80% 的转移使用替代目标。比例太高时,原目标的数据占比太低,也可能拖慢学习。 - 观测与目标归一化:在高维机械臂任务中,对状态和目标做运行均值方差归一化非常重要;本例的坐标本身已在
,因此省略。
相关方法
- 课程学习与目标生成:如 Goal GAN、自动选择“难度适中”的目标进行训练;
- 基于 HER 的扩展:如对替代目标做优先级采样、把 HER 与模型或规划结合;
- 离线目标导向学习:在无奖励的数据集上用重标注学习目标条件策略,与 离线强化学习 和 模仿学习 关系密切;
- 语言或图像目标:把目标从坐标推广为一句指令或一张图片,是机器人基础模型中常见的设定。
小结
| 问题 | 做法 |
|---|---|
| 一个策略到达多个目标 | 策略与价值以目标为输入: |
| 稀疏奖励下几乎没有成功信号 | HER:用实际到达的状态作为替代目标并重算奖励 |
| 替代目标怎么选 | 常用 future 策略, |
| 适用前提 | 奖励可按目标重算;异策略算法 |
练习题
- 把示例中的 future 策略改成 final 策略,比较两者达到 1.00 成功率所需的回合数。
- 把成功阈值从 0.1 缩小到 0.05、或把环境扩展到三维,观察不加 HER 的 DDPG 是否还能学会。
- 为什么替代目标下的转移不能直接用于 PPO 这样的同策略算法?如果一定要用,需要做什么修正?
- 把
分别设为 1、4、8,记录学习速度,并解释“原目标数据比例”对学习的影响。 - 在示例中把稀疏奖励换成稠密奖励
,不使用 HER,比较学习速度和最终成功率。
参考文献
- Kaelbling, L. P. (1993). Learning to achieve goals. IJCAI.
- Schaul, T., Horgan, D., Gregor, K., & Silver, D. (2015). Universal value function approximators. ICML.
- Andrychowicz, M., Wolski, F., Ray, A., Schneider, J., Fong, R., Welinder, P., McGrew, B., Tobin, J., Abbeel, P., & Zaremba, W. (2017). Hindsight experience replay. NeurIPS.
- Plappert, M., et al. (2018). Multi-goal reinforcement learning: Challenging robotics environments and request for research. arXiv:1802.09464.
- Florensa, C., Held, D., Geng, X., & Abbeel, P. (2018). Automatic goal generation for reinforcement learning agents. ICML.