# 目标导向强化学习与 HER

> 来源：https://dqn.cn/advanced/goal-conditioned ｜ 作者：DQN.CN 编辑部 ｜ 发布：2026-10-30 ｜ 更新：2026-10-30 ｜ 许可：CC BY-NC-SA 4.0（文字）/ MIT（代码）

> **要点速览**
>
> - 目标导向强化学习让策略和价值函数额外以“目标 g”为输入：π(a|s,g)、Q(s,a,g)，一个网络学会到达许多不同的目标。
> - 在“到达才给奖励”的稀疏奖励任务中，随机探索几乎拿不到成功信号，价值函数长期没有可学的东西。
> - HER（后见经验回放）把一条失败轨迹中实际到达的状态当作“假装的目标”重新计算奖励，使每条轨迹都能产生成功样本；它只适用于异策略算法，且需要奖励能按任意目标重新计算。
> - 本站在自建二维到达任务上实测（3 个种子）：DDPG+HER 在第 100 回合测试成功率即为 1.00；不加 HER 的 DDPG 在第 100 回合为 0.02–0.04，到第 400–600 回合才达到 1.00。

很多任务的目标不是固定的：机械臂要把物体放到桌面上**任意指定**的位置，导航机器人要去地图上**任意一个**目的地。为每个目标训练一个策略显然不现实。**目标导向强化学习**（goal-conditioned RL）把目标 $g$ 作为额外输入，让一个策略 $\pi(a \mid s, g)$ 学会到达许多目标。这类任务最自然的奖励是“到了给 0，没到给 −1”，但这种**稀疏奖励**让探索极其困难。**后见经验回放**（Hindsight Experience Replay，HER）用一个简单的数据重标注技巧解决了这个问题。

## 目标条件的 MDP

在普通 MDP 的基础上增加目标空间 $\mathcal{G}$：

- 每个回合开始时采样一个目标 $g \sim p(g)$，回合中保持不变；
- 奖励依赖目标：$r(s, a, s', g)$，常见的稀疏形式为
$$
r(s', g) = \begin{cases} 0, & \|\phi(s') - g\| < \epsilon \\ -1, & \text{否则} \end{cases}
$$
  其中 $\phi(s)$ 把状态映射到“已到达的目标”（例如机械臂末端或物体的位置）；
- 策略与价值函数都以目标为输入：$\pi(a \mid s, g)$、$Q(s, a, g)$。

把 $(s, g)$ 拼接起来当作“增广状态”，它仍然是一个普通 MDP，所以 DQN、DDPG、SAC 等算法都可以直接用。Schaul 等（2015）把这种以目标为输入的价值函数称为**通用价值函数逼近器**（UVFA）。目标导向的好处是**泛化**：网络在训练过的目标之间共享结构，对没见过的目标也能给出合理的动作。

## 稀疏奖励下的困境

稀疏奖励最大的问题是：**在第一次成功之前，所有奖励都是 −1**。对价值函数来说，所有状态—动作看起来都一样坏，TD 目标中没有任何能区分好坏的信号，策略梯度也几乎为零。智能体只能靠随机探索“撞上”目标。目标越小、状态空间维度越高，撞上的概率就越低——在机械臂任务中可能几乎为零。

## HER：从失败中学习

HER（Andrychowicz 等，2017）的观察是：**一条没能到达目标 $g$ 的轨迹，确实到达了某些别的地方**。如果当初的目标恰好就是那些地方，这条轨迹就是一次成功示范。

做法：把轨迹 $(s_0, a_0, s_1, \dots, s_T)$ 存入回放缓冲区时，除了用原目标 $g$ 存一份，再用若干个**替代目标** $g'$ 各存一份，并**按 $g'$ 重新计算奖励** $r(s_{t+1}, g')$。替代目标的常见选法：

| 策略 | 替代目标 $g'$ |
| --- | --- |
| final | 本条轨迹最后到达的状态 $\phi(s_T)$ |
| future | 对每个 $t$，从 $t$ 之后到达过的状态中随机取 $k$ 个 |
| episode | 从本条轨迹的任意时刻随机取 $k$ 个 |
| random | 从整个缓冲区随机取 $k$ 个已到达状态 |

原论文中 future（$k = 4$ 或 8）通常最好。这样每条轨迹都会产生一些奖励为 0 的“成功”转移，价值函数立刻有了可学的信号；网络在“容易的目标”上学到的结构，又会泛化到真正想要的目标上，相当于自动生成了一个由易到难的课程。

HER 有两个使用前提：

1. **奖励能按任意目标重新计算**：需要知道 $r(s', g)$ 的函数形式（例如基于距离的阈值判断），而不仅是环境返回的一个数；
2. **算法是异策略的**：替代目标下的转移不是当前策略在该目标下会产生的数据，只有异策略算法（DQN、DDPG、TD3、SAC 等）才能直接学习。

## 实验：二维到达任务

为了能在 CPU 上几分钟内跑完，我们自己写了一个最小的目标导向环境 `PointGoalEnv`：

- 平面 $[-1, 1]^2$，智能体每回合从原点出发，每步移动 $0.1 \cdot a$，$a \in [-1, 1]^2$；
- 目标在正方形内随机采样，且离原点至少 0.5；回合固定 50 步；
- 稀疏奖励：离目标距离小于 0.1 时为 0，否则 −1；**成功**的标准是第 50 步结束时位于目标 0.1 范围内。

算法用 DDPG（每回合后更新 40 次、批量 256、$\gamma = 0.98$、软更新 $\tau = 0.05$），把 TD 目标裁剪到理论范围 $[-1/(1-\gamma), 0]$；探索用高斯噪声 0.2 加 20% 的完全随机动作。HER 使用 future 策略、$k = 4$。每 100 回合用 50 个测试目标评估一次确定性策略的成功率，3 个随机种子。

```python
"""目标导向强化学习：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)
```
实际运行输出：

```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.00
```

结果解读：

- **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 值**：奖励 $\in \{-1, 0\}$ 时 $Q \in [-1/(1-\gamma), 0]$，把 TD 目标裁剪到这个区间能防止早期的价值发散。
- **动作惩罚与随机探索**：小的 $\|a\|^2$ 惩罚防止 tanh 输出饱和；一部分完全随机动作保证在策略已经较确定时仍有探索。
- **替代目标的比例**：future、$k = 4$ 意味着缓冲区中约 80% 的转移使用替代目标。比例太高时，原目标的数据占比太低，也可能拖慢学习。
- **观测与目标归一化**：在高维机械臂任务中，对状态和目标做运行均值方差归一化非常重要；本例的坐标本身已在 $[-1, 1]$，因此省略。

## 相关方法

- **课程学习与目标生成**：如 Goal GAN、自动选择“难度适中”的目标进行训练；
- **基于 HER 的扩展**：如对替代目标做优先级采样、把 HER 与模型或规划结合；
- **离线目标导向学习**：在无奖励的数据集上用重标注学习目标条件策略，与 [离线强化学习](https://dqn.cn/advanced/offline-rl) 和 [模仿学习](https://dqn.cn/advanced/imitation-learning) 关系密切；
- **语言或图像目标**：把目标从坐标推广为一句指令或一张图片，是机器人基础模型中常见的设定。

## 小结

| 问题 | 做法 |
| --- | --- |
| 一个策略到达多个目标 | 策略与价值以目标为输入：$\pi(a \mid s, g)$、$Q(s, a, g)$（UVFA） |
| 稀疏奖励下几乎没有成功信号 | HER：用实际到达的状态作为替代目标并重算奖励 |
| 替代目标怎么选 | 常用 future 策略，$k \approx 4$ |
| 适用前提 | 奖励可按目标重算；异策略算法 |

## 练习题

1. 把示例中的 future 策略改成 final 策略，比较两者达到 1.00 成功率所需的回合数。
2. 把成功阈值从 0.1 缩小到 0.05、或把环境扩展到三维，观察不加 HER 的 DDPG 是否还能学会。
3. 为什么替代目标下的转移不能直接用于 PPO 这样的同策略算法？如果一定要用，需要做什么修正？
4. 把 $k$ 分别设为 1、4、8，记录学习速度，并解释“原目标数据比例”对学习的影响。
5. 在示例中把稀疏奖励换成稠密奖励 $r = -\|s' - g\|$，不使用 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*.

## 下一步

上一节：[SAC](https://dqn.cn/advanced/sac)　｜　继续阅读：[基于模型的强化学习](https://dqn.cn/advanced/model-based)——另一条提高样本效率的路线：学习环境模型并用它规划。

## 常见问题速查（FAQ）

**问：HER 为什么只能和异策略算法一起用？**

答：重标注后的转移 (s, a, g') 中的动作是在追求原目标 g 时选的，而不是当前策略在目标 g' 下会选的动作。只有像 DQN、DDPG、TD3、SAC 这类可以用任意行为策略产生的数据来学习的异策略算法，才能直接使用这些数据。

**问：HER 的 future 策略和 final 策略有什么区别？**

答：final 策略只用轨迹最后到达的状态作为替代目标；future 策略对每个时刻 t，从 t 之后实际到达的状态中随机取 k 个作为替代目标。future 产生的替代目标更多样，原论文中 k=4 左右效果最好，本站代码也用 future、k=4。

**问：为什么不直接设计稠密奖励（比如负距离）？**

答：稠密奖励需要人工设计，容易引入意想不到的捷径或局部最优；在很多任务（如机械臂堆积木）里很难写出既正确又好学的稠密奖励。HER 让二值的“成功/失败”奖励也能高效学习，减少了奖励工程。

**问：本站实验中不加 HER 的 DDPG 为什么最后也学会了？**

答：这个二维任务很小，随机动作偶尔也能到达目标（完全随机策略回合结束时在目标附近的比例约 0.3%），积累足够多成功样本后 DDPG 也能学会。HER 的价值在于学得快得多：第 100 回合就达到 1.00 成功率。任务维度更高、目标更难碰到时，差距会更大。

## 如何引用本文

DQN.CN 编辑部. 目标导向强化学习与 HER[EB/OL]. DQN.CN, 2026-10-30（更新于 2026-10-30）. https://dqn.cn/advanced/goal-conditioned.
