文章目录
- 1. 前言:当一个任务有多个目标
- 2. 目标导向的强化学习 (GoRL) 简介
- 3. HER算法:化失败为成功的智慧
- 4. 代码实践:用PyTorch实现HER+DDPG
-
- 4.1 自定义环境 (WorldEnv)
- 4.2 智能体与算法 (DDPG)
- 4.3 HER的核心:轨迹经验回放
- 4.4 主流程与训练
- 5. 训练结果与分析
- 6. 总结
1. 前言:当一个任务有多个目标
经典的深度强化学习算法,如 PPO、SAC 等,在各自擅长的任务中都取得了非常好的效果。但它们通常都局限在解决单个任务上,换句话说,训练好的算法,在运行时也只能完成一个特定的任务。
想象一个场景:我们想让一个机械臂能把桌子上的任何一个物体重置到任意一个指定位置。对于传统强化学习而言,如果目标物体的初始位置和目标位置每次都变化,那么这就是一个全新的任务。即便任务的“格式”——抓取并移动——是一样的,但策略本身可能需要重新训练。这显然效率极低。
为了解决这类问题,目标导向的强化学习 (Goal-Oriented Reinforcement Learning, GoRL) 应运而生。它的核心思想是学习一个通用策略,这个策略能够根据给定的目标 (goal) 来执行相应的动作,从而用一个模型解决一系列结构相同但目标不同的复杂任务。
然而,在诸如机械臂抓取等真实场景中,奖励往往是稀疏的。只有当机械臂成功将物体放到指定位置时,才会获得正奖励,否则奖励一直为0或-1。在训练初期,智能体很难通过随机探索完成任务并获得奖励,导致学习效率极低。
为了解决稀疏奖励下的学习难题,OpenAI 在2017年提出了事后经验回放 (Hindsight Experience Replay, HER) 算法。HER 的思想极为巧妙:即使我们没有完成预设的目标,但我们总归是完成了“某个”目标。 通过这种“事后诸葛亮”的方式,将失败的经验转化为成功的学习样本,从而极大地提升了在稀疏奖励环境下的学习效率。
本文将从 HER 的基本概念出发,结合一个完整的 PyTorch 代码实例,带你深入理解 HER 是如何与 DDPG 等经典算法结合,并有效解决目标导向的强化学习问题的。
完整代码:下载链接
2. 目标导向的强化学习 (GoRL) 简介
在目标导向的强化学习中,传统的马尔可夫决策过程 (MDP) 被扩展了。除了状态 S
、动作 A
、转移概率 P
之外,还引入了目标空间 G
。策略 π
不仅依赖于当前状态 s
,还依赖于目标 g
,即 π(a|s, g)
。
奖励函数 r
也与目标相关,记为 r_g
。在本文的设定中,状态 s
包含了智能体自身的信息(例如坐标),而目标 g
则是状态空间中的一个特定子集(例如一个目标坐标)。我们使用一个映射函数 φ
将状态 s
映射到其对应的目标 g
。
在 GoRL 中,一个常见的挑战是稀疏奖励。例如,只有当智能体达到的状态 s'
对应的目标 φ(s')
与我们期望的目标 g
足够接近时,才给予奖励。这可以用以下公式表示:
其中,δ_g
是一个很小的阈值。这意味着,在绝大多数情况下,智能体得到的奖励都是-1,学习信号非常微弱。
3. HER算法:化失败为成功的智慧
HER 的核心思想在于重新利用失败的轨迹。
假设智能体在一次任务(一个 episode)中,目标是 g
,但最终没有达到,整个轨迹获得的奖励都是-1。这条“失败”的轨迹对于学习如何达到目标 g
几乎没有帮助。
但 HER 会这样想:虽然智能体没有达到目标 g
,但它在轨迹的最后达到了某个状态 s_T
。这个状态 s_T
自身就可以被看作是一个目标,我们称之为“事后目标” g' = φ(s_T)
。如果我们把这次任务的目标“篡改”为 g'
,那么这条轨迹就变成了一条成功的轨迹!因为智能体确实达到了 g'
。
通过这种方式,HER 能够从任何轨迹中都提取出有价值的学习信号,将稀疏的奖励变得稠密。
在具体实现时,HER 会从一条完整的轨迹中,随机采样一个时间步 (s_t, a_t, r_t, s_{t+1})
,然后根据一定策略选择一个新的目标 g'
来替换原始目标 g
,并根据新目标重新计算奖励 r'
。
HER 提出了几种选择新目标 g'
的策略,其中最常用也最直观的是 future
策略:在当前时间步 t
之后,从该轨迹中随机选择一个未来状态 s_k (k > t)
,将其对应的 φ(s_k)
作为新的目标 g'
。
这种方法保证了新目标是在当前状态之后可以达到的,使得学习过程更加稳定和有效。
HER 作为一个通用的技巧,可以与任何 off-policy 的强化学习算法(如 DQN, DDPG, SAC)结合。在本文的实践中,我们将它与 DDPG 算法相结合。
4. 代码实践:用PyTorch实现HER+DDPG
接下来,我们通过一个完整的 PyTorch 代码项目来学习 HER 的实现。任务非常直观:在一个二维平面上,智能体需要从原点 (0, 0)
移动到一个随机生成的目标点。
4.1 自定义环境 (WorldEnv)
首先,我们定义一个简单的二维世界环境。
- 状态空间: 4维向量
[agent_x, agent_y, goal_x, goal_y]
。 - 动作空间: 2维向量
[move_x, move_y]
,每个分量的范围是[-1, 1]
。 - 目标: 在每个 episode 开始时,在
[3.5, 4.5] x [3.5, 4.5]
区域内随机生成一个目标点。 - 奖励: 如果智能体与目标的距离小于阈值
0.15
,奖励为0
;否则为-1
。 - 终止条件: 达到目标,或达到最大步数
50
。
# 自定义环境
import numpy as np
import random
from typing import Tupleclass WorldEnv:"""二维世界环境类,用于目标导向的强化学习任务智能体需要从起始位置移动到随机生成的目标位置"""def __init__(self) -> None:"""初始化环境参数"""# 距离阈值,当智能体与目标的距离小于等于此值时认为任务完成 (标量)self.distance_threshold: float = 0.15# 动作边界,限制每个动作分量的取值范围为[-1, 1] (标量)self.action_bound: float = 1.0# 地图边界,智能体活动范围为[0, 5] x [0, 5] (标量)self.map_bound: float = 5.0# 最大步数,防止无限循环 (标量)self.max_steps: int = 50# 当前状态,智能体在二维平面上的坐标 (2维向量)self.state: np.ndarray = None# 目标位置,智能体需要到达的目标坐标 (2维向量)self.goal: np.ndarray = None# 当前步数计数器 (标量)self.count: int = 0def reset(self) -> np.ndarray:"""重置环境到初始状态Returns:np.ndarray: 包含当前状态和目标位置的观测向量 (4维向量: [state_x, state_y, goal_x, goal_y])"""# 在目标区域[3.5, 4.5] x [3.5, 4.5]内随机生成目标位置 (2维向量)goal_x = 4.0 + random.uniform(-0.5, 0.5)goal_y = 4.0 + random.uniform(-0.5, 0.5)self.goal = np.array([goal_x, goal_y])# 设置智能体初始位置为原点 (2维向量)self.state = np.array([0.0, 0.0])# 重置步数计数器 (标量)self.count = 0# 返回包含状态和目标的观测向量 (4维向量)return np.hstack((self.state, self.goal))def step(self, action: np.ndarray) -> Tuple[np.ndarray, float, bool]:"""执行一个动作并返回下一个状态、奖励和是否结束Args:action (np.ndarray): 智能体的动作,包含x和y方向的移动量 (2维向量)Returns:Tuple[np.ndarray, float, bool]: - 下一个观测状态 (4维向量: [state_x, state_y, goal_x, goal_y])- 奖励值 (标量)- 是否结束标志 (布尔值)"""# 将动作限制在有效范围内[-action_bound, action_bound] (2维向量)action = np.clip(action, -self.action_bound, self.action_bound)# 计算执行动作后的新位置,并确保在地图边界内[0, map_bound] (标量)new_x = max(0.0, min(self.map_bound, self.state[0] + action[0]))new_y = max(0.0, min(self.map_bound, self.state[1] + action[1]))# 更新智能体位置 (2维向量)self.state = np.array([new_x, new_y])# 增加步数计数 (标量)self.count += 1# 计算当前位置与目标位置之间的欧几里得距离 (标量)distance = np.sqrt(np.sum(np.square(self.state - self.goal)))# 计算奖励:如果距离大于阈值则给予负奖励-1.0,否则给予0奖励 (标量)reward = -1.0 if distance > self.distance_threshold else 0.0# 判断是否结束:距离足够近或达到最大步数 (布尔值)if distance <= self.distance_threshold or self.count >= self.max_steps:done = Trueelse:done = False# 返回新的观测状态、奖励和结束标志# 观测状态包含当前位置和目标位置 (4维向量)return np.hstack((self.state, self.goal)), reward, done
4.2 智能体与算法 (DDPG)
我们选择 DDPG (深度确定性策略梯度) 作为基础的 off-policy 算法。DDPG 包含一个 Actor (策略网络) 和一个 Critic (Q值网络),非常适合处理连续动作空间问题。
PolicyNet
: Actor 网络,输入状态s
(包含目标g
),输出一个确定性的动作a
。QValueNet
: Critic 网络,输入状态s
和动作a
,输出该状态-动作对的Q值。DDPG
: 算法主类,集成了 Actor 和 Critic,并包含目标网络、优化器、软更新和update
逻辑。这里的实现是标准的 DDPG。
# 要训练的智能体和采用的算法
import torch
import torch.nn.functional as F
import numpy as np
from typing import Dict, Anyclass PolicyNet(torch.nn.Module):"""策略网络(Actor网络)用于输出连续动作空间中的动作值"""def __init__(self, state_dim: int, hidden_dim: int, action_dim: int, action_bound: float) -> None:"""初始化策略网络Args:state_dim (int): 状态空间维度 (标量)hidden_dim (int): 隐藏层神经元数量 (标量)action_dim (int): 动作空间维度 (标量)action_bound (float): 动作边界值,动作取值范围为[-action_bound, action_bound] (标量)"""super(PolicyNet, self).__init__()# 第一个全连接层:状态维度 -> 隐藏层维度self.fc1 = torch.nn.Linear(state_dim, hidden_dim)# 第二个全连接层:隐藏层维度 -> 隐藏层维度self.fc2 = torch.nn.Linear(hidden_dim, hidden_dim)# 输出层:隐藏层维度 -> 动作维度(本环境中动作维度为2)self.fc3 = torch.nn.Linear(hidden_dim, action_dim)# 动作边界,用于将输出限制在有效范围内 (标量)self.action_bound = action_bounddef forward(self, x: torch.Tensor) -> torch.Tensor:"""前向传播计算动作输出Args:x (torch.Tensor): 输入状态 (batch_size, state_dim)Returns:torch.Tensor: 输出动作,范围在[-action_bound, action_bound] (batch_size, action_dim)"""# 通过两个隐藏层,使用ReLU激活函数 (batch_size, hidden_dim)x = F.relu(self.fc2(F.relu(self.fc1(x))))# 输出层使用tanh激活函数,将输出限制在[-1, 1],然后乘以action_bound# 得到范围在[-action_bound, action_bound]的动作 (batch_size, action_dim)return torch.tanh(self.fc3(x)) * self.action_boundclass QValueNet(torch.nn.Module):"""Q值网络(Critic网络)用于评估给定状态和动作的Q值"""def __init__(