做机械臂抓取的时候,我遇到过一个非常难受的问题:控制指令里既要选“用夹爪还是吸盘”(离散动作),又要给“夹持角度、力度、接近速度”(连续参数)。离散决策和连续参数天然绑定在一次行动里,分也分不开,用常规强化学习算法怎么训练都别扭。后来查文献才知道,这类问题有个专门的名字,叫混合动作空间(Hybrid Action Space),围绕它有一整套算法体系,其中最具代表性、也最适合作为入门切入点的,就是PDQN和MPDQN这两个。
混合动作空间在真实任务里其实比纯离散或纯连续更常见。游戏里选技能是离散的,释放角度是连续的;自动驾驶里变道决策是离散的,期望轨迹曲率是连续的;推荐系统里内容类型是离散的,展示位次和折扣参数是连续的。这类“先选型、再调参”的决策结构,如果硬套DQN或者DDPG,都会在动作表示阶段就埋下隐患。这篇文章我会从问题定义讲起,逐步拆解PDQN的架构思想,然后重点说明MPDQN针对PDQN过估计问题做的关键改进,最后给出一份可以直接落地的训练细节和选型建议。适合正在做机械臂控制、游戏AI、仿真决策任务的算法工程师,也适合刚入门强化学习、想搞懂混合动作空间如何处理的研究生。公式我会尽量讲成人话,训练中那些论文不写的坑也会一一指出来。
1. 混合动作空间到底难在哪:先认清问题的边界
1.1 三种动作空间与“混合”的真实含义
在强化学习的标准设定里,agent每一步需要输出一个动作a,环境根据当前状态s给出奖励r并转移到s'。动作空间的性质直接决定了你该用哪一类算法。
离散动作空间:动作集合有限,比如迷宫里的上下左右。经典算法是DQN家族(Double DQN、Dueling DQN、Rainbow),它们输出每个离散动作的Q值,执行argmax来完成决策。
连续动作空间:动作是一个多维实数向量,比如关节力矩、油门开度。经典算法是DDPG、TD3、SAC、PPO,它们用策略网络直接输出动作向量。
混合动作空间:动作由一个离散动作d和一个与d绑定的连续参数θ_d共同构成。环境只真正执行“当前选中的那个离散动作及其参数”,其他离散动作即使同样有参数也不会被使用。学术界给这种结构起的名字是参数化动作空间(Parameterised Action Space)。可以理解为:这是一个复合动作集合,每个元素是(d, θ_d)对。决策的关键在于,d和θ_d必须联合优化——你不能先单独决定选哪个离散动作,再单独决定参数,因为“动作价值”本身是被二者共同决定的。
| 动作空间类型 | 示例 | 典型算法 | 输出形式 |
|---|---|---|---|
| 离散 | 上下左右、技能选择 | DQN | 每个动作的Q值 |
| 连续 | 关节力矩、转向角 | DDPG / TD3 / SAC | 确定性向量或高斯分布 |
| 混合 | 技能选择 + 技能参数 | PDQN / MPDQN | 离散Q值 + 参数向量 |
举一个具体例子。在即时战略游戏里,agent发出“释放技能”这个离散动作,还必须给出“朝向哪个位置释放”这个连续参数。同样是释放技能,位置不同,收益可能天差地别。要是把位置也离散成几十个网格点,动作空间立刻爆炸;要是不管离散动作、直接把技能编号作为连续向量的一部分输入,又会让策略网络学到一堆没有语义的跳跃关系。
1.2 为什么不能简单粗暴地离散化或哑变量化
面对混合动作空间,很多人的第一反应是“把它转换成纯离散”或“把它转换成纯连续”。我在做第一个相关项目时也这么干过,结果都翻了车,原因很本质。
第一,连续参数离散化会造成维度灾难。机械臂的抓取位置如果离散成10×10×10的网格,再叠加几个离散动作模式,动作维度瞬间冲到上千。而且离散化本身就引入量化误差,精细控制根本无法实现。反过来,把离散动作用哑变量编码进连续动作空间也有问题,哑变量之间的欧氏距离没有实际语义,策略梯度更新时会被这种不存在的距离带偏,训练出来的策略经常在离散动作边界附近抖动。
第二,分层决策并未解决耦合问题。一种常见的工程做法是上层用DQN选离散动作,下层用DDPG输出连续参数。看起来分工明确,但两个网络相互独立训练时,误差会逐层累积。上层决策网络很难学会“参数可行域”这种信息,下层参数网络又可能在一个有缺陷的离散决策约束下收敛到局部最优。我在实际训练中发现,这种两段式方案往往需要大量人工调参才能勉强不崩,收敛速度也远低于端到端的混合方法。
第三,也是最关键的,这两类动作在价值函数中是非线性的耦合关系。Q(s, d, θ_d)不能简单拆成Q_d(s, d) + Q_θ(s, θ_d)去优化,因为θ对Q的影响强烈依赖于d是否被选中。PDQN和MPDQN这类算法,本质上就是为这种“参数化动作价值”设计更合理的逼近方式。
2. PDQN的设计思路:把Q函数拆成“离散选型 + 连续调参”
2.1 从DQN到参数化动作空间的推理路径
先回顾一下DQN。DQN维持一个Q网络Q(s, d; ω),输出每个离散动作d的Q值,通过学习让Q值逼近贝尔曼最优方程。但它无法泛化到“d还带参数θ_d”的情形,因为参数化动作空间是无限的——参数连续变化,要对无限多的(d, θ_d)做max是不现实的。
一个自然的想法是:能不能用另一个网络来“生成”最有可能的最优参数θ_d(s)?这正是参数化动作空间算法的突破口。在PDQN之前,有些工作尝试将DQN扩展到参数化动作空间,但大多依赖“随机采样参数再评估Q值”这类做法,效率低,效果也一般。PDQN做的核心抽象是:把策略从“在每个状态输出一个动作”提升为“输出两组东西”——对每个离散动作d评估其基础Q值,以及一个能输出最优参数的生成分支。离散分支负责“选哪把枪”,参数分支负责“用多大力、瞄哪里”,两者再通过一个混合Q函数融合在一起,这个混合Q函数才是决策的真正依据。
2.2 PDQN网络结构与动作选择机制
PDQN的典型网络结构可以这样理解。输入是状态特征h(x),经过共享层后分出两个头:
- Q头(离散分支):输出|D|维向量,代表每个离散动作d的基础价值Q_d(x, d)。
- P头(参数分支):输出一个拼接的参数向量θ(x) = [θ_1(x), θ_2(x), ..., θ_|D|(x)],其中每一个θ_d(x)都是环境对应的连续参数。
为了让Q函数能够评估具体的(d, θ_d)组合,PDQN还维护一个混合Critic网络,它联合状态、离散动作的嵌入以及对应的连续参数θ_d,输出最终的Q值。动作执行时,算法要做的事情是:
- 用P头生成全部|D|个候选参数;
- 将每个d与对应θ_d喂入混合Critic,得到Q(x, d, θ_d);
- 选取Q值最大的一组(d*, θ_d*)执行。
动作选择的伪代码大致是这样的:
# 动作选择逻辑 with torch.no_grad(): feature = backbone(state) q_vals = q_head(feature) # shape: (|D|,) params = param_head(feature) # shape: (|D|, param_dim) scores = [] for d_idx in range(num_discrete): p = params[d_idx] # 第d_idx个离散动作对应的参数 q = critic(feature, d_idx, p) # 混合Q值 scores.append(q) d_star = argmax(scores) param_star = params[d_star]从工程角度说,把|D|个候选参数都算一遍并不贵,因为|D|通常在几十以内。真正需要小心的是混合Critic里离散动作索引的编码方式。我试过直接把d作为整数传入,效果和用可学习的embedding层差距很大。建议用embedding层把d映射成稠密向量,再和状态特征、参数拼接到一起,这样Critic能更好地学到“不同离散动作对参数的不同评价方式”。
2.3 两段式损失函数更新逻辑
PDQN在训练时把损失拆成离散和参数两个部分,这是它最容易被误解的地方。
离散部分的更新思路和DQN一致。我们用当前网络选出最优动作,但用目标网络计算目标Q值,TD误差为:
L_Q = E[(r + γ max_{d'} Q(x', d', θ_d'(x')) - Q(x, d, θ_d(x)))²]
这里的关键是:目标值中已经包含了参数分支的输出θ_d'(x'),所以离散分支的学习会隐式地把“参数的最优性”考虑进去。这和普通DQN不同,因为普通DQN的目标值和连续参数无关。
参数部分的更新则借鉴了DDPG的确定性策略梯度思想。我们需要让参数朝“使Q值增大”的方向移动,即求解:
L_P = -Q(x, d*, θ_d*(x))
其中d是当前离散分支的最优动作,θ_d是P头里对应d*的那段参数。反向传播时,梯度穿过混合Critic一直传到P头的参数上,这就是“用Q值梯度指导参数生成网络”的核心机制。
有两点容易踩坑。一是P头的梯度经常被共享层“稀释”:如果共享层的特征表示不够好,P头可能在早期收敛到某个平凡解,比如所有状态都输出同一个参数。建议给P头增加独立的残差连接,或者限制P头梯度的回传范围,让它不完全依赖共享特征。二是两个分支的学习率最好不要一样。实验里Q头用2e-4、P头用1e-4这类配置,往往比统一学习率稳定得多。
3. MPDQN的关键改进:解决PDQN的过估计病根
3.1 PDQN在目标值计算中的耦合缺失
PDQN已经能处理参数化动作空间,但它在训练中有一个隐蔽的偏差——过估计。这个问题和DQN里Q-learning过估计同源,但在PDQN里被放大了,原因在于目标值里的max运算并没有真正在完整的动作空间上取。
看PDQN计算目标值的方式:max_{d'} Q(x', d', θ_d'(x'))。表面上这个max是完整的,但实际计算中,Q(x', d', θ_d')是在混合Critic里,用当前的d'和P头为d'生成的参数θ_d'一起评估的。问题在于:贝尔曼更新期望这个max能覆盖所有可能的(d', θ_d')组合,但PDQN只对“P头当前生成的|D|个参数”做了max。也就是说,它并没有在连续参数空间里真正最大化,只是在一个参数网络输出的有限集合上取max。这会在训练早期引入系统性偏差——因为此时P头还很不准,生成的θ_d'可能远非最优参数,但max依然把Q值算得很高,导致价值被持续高估。
此外,PDQN将Q函数拆成离散基础值和参数评价值相加的方式,在梯度流上也有隐患:离散分支和参数分支共享底层特征,当离散分支对某个d的Q值偏高时,整个特征表示都会朝那个方向偏移,参数分支也会被带偏,进一步加剧过估计。
3.2 Multi-Pass机制如何让Q值评估更准确
MPDQN(Multi-Pass Deep Q-Networks)正是为了解决PDQN的这个问题出现的。它的核心改动并不复杂,但很巧妙:不再把离散分支和参数分支看成两个独立模块,而是把完整的Q(x, d, θ_d)作为统一的评估对象,并在所有离散动作上做多趟前向传播,以此获得更接近真实max的Q值。
具体做法是这样的。MPDQN同样有Q头、P头和混合Critic,但在计算目标值时,它会对每一个离散动作d'都执行一次“参数生成—组合评估”过程,得到|D|个完整的Q值,然后取最大值。关键区别在于:MPDQN在更新P头时,让梯度同时考虑所有离散动作的Q值,而不是只考虑当前最优的d*对应的那一支。也就是说,P头学习的目标是“让所有可能被选中的离散动作都尽量有好的参数”,而不是只服务当前的argmax。
这个多趟计算还有一个额外好处:它抑制了过估计。因为每次取max之前,Q值都被更充分地校准了,即使某个离散动作的参数生成得不好,它的Q值也不会虚高;相反,P头会被迫提升那些被选中概率高但Q值被低估的离散动作的参数质量。一句话概括:PDQN是“只优化被选中的参数”,MPDQN是“优化所有候选参数”。
3.3 两代算法在更新过程中的本质差异
用一个表格来对比两种算法在关键环节的差别:
| 对比项 | PDQN | MPDQN |
|---|---|---|
| 目标值计算 | 用当前最优d对应的θ_d参与目标Q | 对所有d'生成θ_d',逐一评估后取max |
| P头梯度来源 | 只回传当前最优d*对应的Q梯度 | 所有候选离散动作的Q梯度都会影响P头 |
| 对过估计的抑制 | 弱 | 强,更接近真实Q max |
| 计算开销 | 单趟前向 | 多趟前向,约 |
| 离散动作较少时 | 够用 | 更适合,开销可接受 |
举一个直观的例子。假设环境里有三个离散动作A、B、C,真实情况下A最优,但初期P头给A生成的参数很烂,给B生成的参数反而不错。PDQN在更新目标值时,max落在B上,P头会重点优化B的参数,A的参数被冷落,价值网络对A的高估可能长期不纠正。MPDQN则会让A、B、C三个参数同时被检查,如果A的价值被高估,训练信号会明确告诉P头“把A的参数也修一下”,从而加速对A的修正。
这个差异在训练曲线上的表现非常明显。我见过多次类似场景:PDQN在前期有虚高的回报假象,后期突然崩掉;MPDQN的回报曲线更平滑,过估计导致的剧烈波动明显减少。在我做机械臂任务时切换到MPDQN后,训练稳定性提升了一个档次。
4. 训练细节与工程实现,以及绕不开的坑
4.1 探索策略:离散分支与连续分支要分开设计
混合动作空间的探索比纯离散或纯连续都要棘手,因为两个分支的探索语义完全不同。纯DQN里一个ε就能覆盖全部,但在这里,ε-greedy如果用在整个动作上,连续参数的探索会变得非常不稳定。我的做法是:
- 离散分支:用ε-greedy,ε从1.0按指数衰减到0.05左右。这部分和普通DQN一样,负责探索哪个离散动作值得尝试。
- 连续参数分支:用高斯噪声叠加在P头输出上,噪声标准差可以设置成参数范围的一个比例,比如0.1。这部分负责在选中某个离散动作后,探索参数的细微变化是否带来更高回报。
- 如果环境中的参数有物理约束(角度不能超界、力度不能为负),噪声采样之后要做clip或tanh缩放,不要让探索参数跑到物理不可行的区域。
这里有个容易忽略的细节:探索噪声的方差要不要随时间衰减。我试过固定方差,训练后期P头已经收敛到不错的参数,固定噪声反而会让细粒度控制变差;也试过方差衰减到零,结果遇到环境扰动时策略毫无适应性。折中方案是让噪声方差衰减到一个很小的正值,比如0.01,相当于给策略留一点鲁棒性余量。
4.2 稳定性处理:目标网络、经验回放与梯度裁剪
混合动作空间的Q值更新对稳定性比普通DQN更敏感,因为P头和混合Critic耦合深,一旦Q值振荡,参数生成网络会跟着剧烈漂移。以下三个措施我在实验里都验证有效:
- 目标网络软更新:目标网络参数θ^- ← τθ + (1 - τ)θ^-,τ取0.005或0.01。软更新比定期硬拷贝平滑得多,能避免Q值和P头在更新中互相追逐。
- 经验回放池容量:至少100k条轨迹样本。混合动作空间的样本多样性需求更高,池子太小会导致P头反复在几个参数上打转,难以学到泛化的映射。
- 梯度裁剪:P头梯度clip到[-1, 1],混合Critic的梯度clip到[-10, 10]。原因很简单,Q对参数的梯度在早期可能极大,一次更新就可能把参数推出合理区间。
还要注意权重初始化。P头输出层如果初始化太大,早期参数会横冲直撞,训练曲线直接发散。建议把P头输出层的权重初始化到较小的均匀分布范围,比如[-0.003, 0.003],并配合tanh激活避免越界。这个细节看似不起眼,但对收敛速度影响非常大。
4.3 实际训练中的常见不稳定因素
训练混合动作空间模型,我遇到次数最多的三个问题:
第一是P头退化。表现是训练到中期,参数分支基本不再变化,所有状态输出几乎相同的参数。原因是P头梯度被共享层阻隔,或者Q对参数的梯度趋于零。我的解决方式是把P头的梯度回传路径改成独立残差连接,让P头不完全依赖共享特征,同时给Q加一个小的随机噪声,打破梯度为零的僵局。
第二是过估计导致的回报曲线“假高”。如果你发现训练早期回报很高、后面反而下降,基本可以判断是过估计。先用MPDQN替换PDQN试一下,回报曲线通常马上变平滑。如果还不行,可以考虑引入Double DQN的思想,用当前网络选动作、目标网络评估值。
第三是离散分支和参数分支更新步调不一致。如果离散分支收敛太快,P头可能长时间集中优化某一个动作的参数,其他动作参数永远是噪声;如果P头收敛太快,离散分支又容易被一个参数组合带偏。实践中可以定期检查每个离散动作被选择的频率,如果某些动作出现类似“饿死”的情况,手动提高对应离散动作的探索概率,或者使用熵正则惩罚过度集中的离散策略。
5. 应用场景拓展与选型建议
5.1 哪些任务真正需要混合动作空间
混合动作空间存在于很多现实任务中,下面列几个典型:
- 机械臂控制:动作=选择抓取模式(夹取/吸取/下压) + 抓取位置、姿态、力度等连续参数。这是最经典、也最容易上手验证的场景。
- 游戏AI:动作=选择技能/道具 + 释放角度、力度、目标坐标。RTS和MOBA类游戏几乎天生就是混合动作空间。
- 自动驾驶决策规划:动作=变道/直行/停车等离散决策 + 目标加速度、转向角、期望轨迹参数。决策层和规划层的交接本质就在这里。
- 推荐系统:动作=推荐哪种内容/商品类型 + 展示位次、折扣、数量等连续策略参数。
如果你的任务是这类“先选型、再给参数、效果由两者共同决定”的结构,就适合用混合动作空间算法,而不是分开建模或强行离散化。
5.2 PDQN与MPDQN的适用边界
选PDQN还是MPDQN,主要看离散动作数量和训练稳定性需求:
- 如果离散动作很少(比如2-4个),参数维度低,训练资源紧张,PDQN已经完全够用,计算效率还高。
- 如果离散动作较多(比如10个以上),或者你观察到明显的过估计和回报波动,直接上MPDQN。|D|趟前向在几十个离散动作范围内完全可接受。
- 如果你的环境同时包含多个离散动作槽位(比如先选武器再选目标类型,每个槽位都带参数),MPDQN的多趟机制扩展起来更自然,因为它天然地把每个槽位当成完整Q值的一部分。
另外,如果你的任务连续动作空间较大但离散动作较少,也可以考虑混合PPO这类基于策略梯度的方法。PPO在高维连续空间通常更稳,但在动作选择的精确度上不如MPDQN直接。
5.3 后续改进方向与复现路径
PDQN和MPDQN并不是混合动作空间的终点,后续有很多改进方向值得关注:
- 将Double DQN思想引入MPDQN,用当前网络选动作、目标网络评估值,进一步压低过估计。
- 结合PER优先级经验回放,让P头在价值更新时更关注信息量大的样本。
- 把MPDQN的混合Q值框架扩展到最大熵框架,得到能在探索和利用之间更柔性权衡的算法。
如果你打算从零复现,一个可行的路径是:先在最简单的参数化动作环境上跑通PDQN,画出Q值变化曲线,再切到MPDQN感受两者的训练差异。这个过程能让你对混合动作空间的理解深一层,比直接上手复杂任务更省时间。复现时优先复现MPDQN,因为它在稳定性上的收益几乎白捡,唯一的代价就是多几次前向传播的计算量。等跑通之后,你自然会理解为什么混合动作空间会成为控制类强化学习任务里绕不开的一环。