双延迟深度确定性策略梯度算法 (TD3) 详解
双延迟深度确定性策略梯度算法(Twin Delayed Deep Deterministic Policy Gradient, TD3)是强化学习领域针对连续动作空间问题设计的一种重要算法。它由 Fujimoto 等人在 2018 年提出,旨在解决深度确定性策略梯度(DDPG)算法在实际应用中存在的训练不稳定和 Q 值过估计问题。
一、TD3 的背景与动机
DDPG 结合了 Actor-Critic 架构的优势,在连续控制任务中表现优异,但在实际训练中暴露出几个关键缺陷:
- Q 值过估计:Critic 网络容易高估动作价值,导致 Actor 基于错误的反馈更新策略。
- 策略噪声敏感:确定性策略直接输出动作,缺乏探索性,容易陷入局部最优。
- 训练震荡:Actor 和 Critic 同步更新时,相互干扰可能导致性能下降。
TD3 通过三项核心改进有效缓解了上述问题,显著提升了算法的鲁棒性和收敛速度。
二、TD3 的核心思想
1. 双 Critic 网络(Twin Critics)
借鉴 Double Q-Learning 的思想,TD3 维护两个独立的 Critic 网络 $Q_{\theta_1}$ 和 $Q_{\theta_2}$。在计算目标 Q 值时,取两者的最小值:
$$ y = r + \gamma \min \big( Q_{\theta_1'}(s', \pi_{\phi'}(s')), Q_{\theta_2'}(s', \pi_{\phi'}(s')) \big) $$
这种机制能有效抑制 Q 值的高估偏差,防止策略被误导。
2. 延迟更新(Delayed Policy Updates)
为了避免 Actor 在网络尚未稳定时频繁更新,TD3 降低了策略网络的更新频率。通常每更新 Critic 两次,才更新一次 Actor。这确保了 Actor 始终基于相对准确的 Q 值进行优化。
3. 目标策略平滑(Target Policy Smoothing)
在计算目标动作时,向目标策略的输出添加裁剪后的高斯噪声:
$$ a' = \pi_{\phi'}(s') + \text{clip}(\epsilon, -c, c), \quad \epsilon \sim \mathcal{N}(0, \sigma) $$
这一操作相当于对目标 Q 函数进行了正则化,减少了策略对特定状态-动作对的过拟合,增强了泛化能力。
三、数学细节解析
Critic 损失函数
Critic 网络的目标是最小化均方误差(MSE),即预测 Q 值与目标 Q 值之间的差距:
$$ L(\theta_i) = \mathbb{E}{(s, a, r, s')} \left[ \big( Q{\theta_i}(s, a) - y \big)^2 \right] $$
其中 $y$ 为包含双 Critic 最小值和噪声平滑后的目标值。
Actor 策略梯度
Actor 网络通过最大化 Critic 的输出来更新策略参数:
$$ \nabla_\phi J(\phi) = \mathbb{E}{s} \left[ \nabla_a Q{\theta_1}(s, a) \big|{a=\pi\phi(s)} \nabla_\phi \pi_\phi(s) \right] $$
由于采用了确定性策略梯度,可以直接利用链式法则计算梯度。
四、PyTorch 实现详解
以下是一个完整的 TD3 实现示例,基于 OpenAI Gym 环境(如 Pendulum-v0)。代码结构清晰,包含了经验回放、网络定义及训练循环。
1. 环境与配置
import argparse
import gym
import numpy as np
import torch
torch.nn nn
torch.optim optim
torch.distributions Normal
device = torch.cuda.is_available()
parser = argparse.ArgumentParser()
parser.add_argument(, default=)
parser.add_argument(, default=)
parser.add_argument(, default=)
parser.add_argument(, default=)
parser.add_argument(, default=)
parser.add_argument(, default=)
args = parser.parse_args()
env = gym.make(args.env_name)
state_dim = env.observation_space.shape[]
action_dim = env.action_space.shape[]
max_action = (env.action_space.high[])


