跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

双延迟深度确定性策略梯度算法 (TD3) 详解

TD3(双延迟深度确定性策略梯度算法)是针对连续控制任务的强化学习算法,旨在解决 DDPG 中的 Q 值过估计和不稳定性问题。文章详细介绍了 TD3 的三大核心改进:双 Critic 网络减少偏差、延迟策略更新提高稳定性以及目标策略平滑增强鲁棒性。内容涵盖理论背景、数学推导及完整的 PyTorch 代码实现,包括 Actor-Critic 架构、经验回放机制及训练循环逻辑,适合希望深入理解并复现 TD3 算法的技术人员阅读。

并发大师发布于 2026/2/5更新于 2026/10/264.6K 浏览
双延迟深度确定性策略梯度算法 (TD3) 详解

双延迟深度确定性策略梯度算法 (TD3) 详解

一、TD3 算法的背景

双延迟深度确定性策略梯度算法,TD3(Twin Delayed Deep Deterministic Policy Gradient)是强化学习中专为解决连续动作空间问题设计的一种算法。TD3 算法的提出是在深度确定性策略梯度(DDPG)算法的基础上改进而来,用于解决强化学习训练中存在的一些关键挑战。

二、TD3 的背景

1. TD3 的理论背景

TD3 的提出基于以下几个强化学习的理论与技术发展:

  • Actor-Critic 架构:Actor 网络负责生成动作,Critic 网络负责评估动作的价值(Q 值)。这种架构使得算法能够高效地解决高维连续动作问题。Actor 更新目标是最大化 Critic 网络的 Q 值,而 Critic 网络优化目标是最小化 Q 值预测误差。
  • 确定性策略梯度(Deterministic Policy Gradient, DPG):DPG 是强化学习中一种适用于连续动作空间的策略梯度方法,TD3 继承了 DPG 的优势,即通过学习一个确定性策略直接生成动作。
  • 双 Q 学习(Double Q-Learning):TD3 借鉴了双 Q 学习的思想,使用两个独立的 Critic 网络来降低 Q 值估计的偏差。
  • 经验回放池(Replay Buffer):TD3 通过从经验回放池中采样数据训练网络,打破数据相关性,提高了学习效率。

2. DDPG 的局限性

TD3 算法由 Fujimoto 等人在 2018 年提出,对深度确定性策略梯度(Deep Deterministic Policy Gradient, DDPG)算法的改进。DDPG 是一种结合策略(Actor)和价值函数(Critic)的强化学习方法,可以在连续动作空间中表现出色。然而,DDPG 存在以下问题:

  • Q 值过估计问题:Critic 网络在训练时容易高估 Q 值,从而导致策略网络(Actor)学习不稳定。
  • 策略噪声问题:由于策略直接输出确定性动作,在训练时容易陷入局部最优解。
  • 训练不稳定性:Critic 网络和 Actor 网络同时训练时,相互影响可能导致训练震荡。

为了解决上述问题,TD3 通过以下三点创新改进了 DDPG。

三、TD3 算法的核心思想

TD3 在 DDPG 的基础上提出了三项关键改进:

1. 双 Critic 网络(Twin Critics)

  • 动机:DDPG 中的 Critic 网络在估计 Q 值时存在系统性的高估问题。
  • 方法:TD3 使用两个独立的 Critic 网络计算 Q 值,取两者的最小值来作为目标 Q 值。
  • 效果:有效减少了 Q 值的高估偏差(Overestimation Bias)。

2. 延迟更新(Delayed Policy Updates)

  • 动机:在 DDPG 中,Critic 网络和 Actor 网络同时更新,可能导致 Actor 策略在不稳定的 Q 值估计上进行优化。
  • 方法:TD3 降低 Actor 和目标网络的更新频率,通常在 Critic 更新两次后才更新 Actor。
  • 效果:降低了 Actor 网络的更新频率,从而提高了策略的稳定性。

3. 目标策略平滑(Target Policy Smoothing)

  • 动机:DDPG 中的目标策略直接输出确定性动作,容易对极端动作过拟合。TD3 通过在目标策略中加入高斯噪声,对动作进行'平滑'。
  • 方法:在目标值计算中,对动作加入噪声并裁剪到一定范围。
  • :提高了算法对噪声和目标值波动的鲁棒性。
效果

四、TD3 算法详细讲解

TD3(Twin Delayed Deep Deterministic Policy Gradient)适用于连续动作空间问题,主要基于 Actor-Critic 框架和深度确定性策略梯度(DDPG)。以下是 TD3 的数学基础与推导。

1. Actor-Critic 框架的核心

Actor-Critic 方法的核心在于将策略学习(Actor)与价值评估(Critic)结合。Actor 负责生成动作,Critic 负责评估当前策略的表现。Actor 网络优化目标是通过 Critic 网络的反馈提高策略质量。

(1) 策略梯度

Actor 通过最大化累计奖励学习最优策略:

$$ \nabla_\phi J(\pi_\phi) = \mathbb{E}{s \sim \rho^\pi} \left[ \nabla\phi \pi_\phi(s) \nabla_a Q^\pi(s, a) \big|{a=\pi\phi(s)} \right] $$

其中:

  • $\rho^\pi$ 是由策略生成的状态分布。
  • $Q^\pi(s, a)$ 是 Critic 估计的动作值函数。
  • $\pi_\phi(s)$ 是 Actor 的策略函数。
(2) 价值评估 (Critic)

Critic 通过最小化时间差分(Temporal Difference, TD)误差,学习状态 - 动作值函数:

$$ L(\theta) = \mathbb{E}{(s, a, r, s') \sim \mathcal{D}} \left[ \big( Q\theta(s, a) - y \big)^2 \right] $$

其中目标值 $y$ 定义为:

$$ y = r + \gamma Q_{\theta'}(s', \pi_{\phi'}(s')) $$

  • $\mathcal{D}$ 是经验回放池中采样的数据。
  • $\theta'$ 和 $\phi'$ 是 Critic 和 Actor 的目标网络参数。

2. TD3 的关键改进

TD3 在 DDPG 的基础上,针对 Q 值过估计和策略训练不稳定问题,提出了三项核心改进。

(1) 双 Critic 网络

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) 延迟 Actor 更新

为了避免 Actor 网络频繁更新导致策略不稳定,TD3 在 Critic 更新 $n$ 次后才更新 Actor 一次(通常 $n=2$)。Actor 的优化目标为:

$$ L(\phi) = -\mathbb{E}{s \sim \mathcal{D}} \big[ Q{\theta_1}(s, \pi_\phi(s)) \big] $$

Critic 网络训练稳定后,Actor 的策略梯度才会更加准确。

(3) 目标动作平滑

在计算目标值 $y$ 时,对动作加入高斯噪声 $\epsilon \sim \mathcal{N}(0, \sigma)$ 并进行裁剪,防止策略过拟合到极端动作:

$$ a' = \pi_{\phi'}(s') + \text{clip}(\epsilon, -c, c) $$

  • 这样可以让目标 Q 值更加平滑,增强策略的鲁棒性。

3. 完整 TD3 算法流程

伪代码如下:

  1. 初始化 Actor 和 Critic 网络及其对应的目标网络;
  2. 与环境交互,收集经验存入回放池;
  3. 重复以下步骤直到收敛:
    • 从回放池采样批量数据;
    • 更新 Critic 网络(通常每步都更新);
    • 每隔 $d$ 步更新 Actor 策略(延迟更新);
    • 软更新目标网络参数。

目标网络软更新:

$$ \theta_i' \leftarrow \tau \theta_i + (1 - \tau) \theta_i' $$ $$ \phi' \leftarrow \tau \phi + (1 - \tau) \phi' $$

延迟更新 Actor:每隔 $d$ 步,更新 Actor 策略。

更新 Critic:通过最小化损失函数更新 $Q_{\theta_1}$ 和 $Q_{\theta_2}$:

$$ L(\theta_i) = \mathbb{E}{(s, a, r, s')} \big[ (Q{\theta_i}(s, a) - y)^2 \big] $$

从 $\mathcal{D}$ 中随机抽取一个批量数据 $(s, a, r, s')$:

  • 与环境交互,使用当前策略 $\pi_\phi$ 执行动作 $a_t$,存储 $(s_t, a_t, r_t, s_{t+1})$;
  • 构建经验回放池 $\mathcal{D}$,用于存储交互数据。

4. 数学细节解析

(1) Critic 损失函数

TD3 使用两个 Critic 网络,损失函数为:

$$ L(\theta) = \mathbb{E} \big[ (Q_\theta(s, a) - y)^2 \big] $$

其中目标值:

$$ y = r + \gamma \min \big( Q_{\theta_1'}(s', a'), Q_{\theta_2'}(s', a') \big) $$

(2) Actor 策略梯度

Actor 通过最大化 Critic 网络的输出优化策略:

$$ \nabla_\phi J(\phi) = \mathbb{E}{s \sim \rho^\pi} \big[ \nabla_a Q{\theta_1}(s, a) \big|{a=\pi\phi(s)} \nabla_\phi \pi_\phi(s) \big] $$

(3) 延迟更新的效果

延迟更新使 Actor 网络只在 Critic 网络收敛后才更新,减少了 Actor 网络梯度被不准确 Q 值引导的风险,从而提高了稳定性。

五、Python 实现

环境配置

# Python 3.11.5
# torch 2.1.0
# gym 0.26.2

代码实现

import argparse
from collections import deque
import os
import random
import numpy as np
import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Normal

device = 'cuda' if torch.cuda.is_available() else 'cpu'

class ReplayBuffer:
    def __init__(self, max_size):
        self.storage = []
        self.max_size = max_size
        self.ptr = 0

    def push(self, data):
        if len(self.storage) == self.max_size:
            self.storage[int(self.ptr)] = data
            self.ptr = (self.ptr + 1) % self.max_size
        else:
            self.storage.append(data)

    def sample(self, batch_size):
        ind = np.random.randint(0, len(self.storage), size=batch_size)
        x, y, u, r, d = [], [], [], [], []
        for i in ind:
            X, Y, U, R, D = self.storage[i]
            x.append(np.array(X, copy=False))
            y.append(np.array(Y, copy=False))
            u.append(np.array(U, copy=False))
            r.append(np.array(R, copy=False))
            d.append(np.array(D, copy=False))
        return np.array(x), np.array(y), np.array(u), np.array(r).reshape(-1, 1), np.array(d).reshape(-1, 1)

class Actor(nn.Module):
    def __init__(self, state_dim, action_dim, max_action):
        super(Actor, self).__init__()
        self.fc1 = nn.Linear(state_dim, 400)
        self.fc2 = nn.Linear(400, 300)
        self.fc3 = nn.Linear(300, action_dim)
        self.max_action = max_action

    def forward(self, state):
        a = F.relu(self.fc1(state))
        a = F.relu(self.fc2(a))
        a = torch.tanh(self.fc3(a)) * self.max_action
        return a

class Critic(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(Critic, self).__init__()
        self.fc1 = nn.Linear(state_dim + action_dim, 400)
        self.fc2 = nn.Linear(400, 300)
        self.fc3 = nn.Linear(300, 1)

    def forward(self, state, action):
        state_action = torch.cat([state, action], 1)
        q = F.relu(self.fc1(state_action))
        q = F.relu(self.fc2(q))
        q = self.fc3(q)
        return q

class TD3:
    def __init__(self, state_dim, action_dim, max_action):
        self.actor = Actor(state_dim, action_dim, max_action).to(device)
        self.actor_target = Actor(state_dim, action_dim, max_action).to(device)
        self.critic_1 = Critic(state_dim, action_dim).to(device)
        self.critic_1_target = Critic(state_dim, action_dim).to(device)
        self.critic_2 = Critic(state_dim, action_dim).to(device)
        self.critic_2_target = Critic(state_dim, action_dim).to(device)

        self.actor_optimizer = optim.Adam(self.actor.parameters())
        self.critic_1_optimizer = optim.Adam(self.critic_1.parameters())
        self.critic_2_optimizer = optim.Adam(self.critic_2.parameters())

        self.actor_target.load_state_dict(self.actor.state_dict())
        self.critic_1_target.load_state_dict(self.critic_1.state_dict())
        self.critic_2_target.load_state_dict(self.critic_2.state_dict())

        self.max_action = max_action
        self.memory = ReplayBuffer(max_size=50000)
        self.num_critic_update_iteration = 0
        self.num_actor_update_iteration = 0
        self.num_training = 0

    def select_action(self, state):
        state = torch.tensor(state.reshape(1, -1)).float().to(device)
        return self.actor(state).cpu().data.numpy().flatten()

    def update(self, num_iteration, args):
        for i in range(num_iteration):
            x, y, u, r, d = self.memory.sample(args.batch_size)
            state = torch.FloatTensor(x).to(device)
            action = torch.FloatTensor(u).to(device)
            next_state = torch.FloatTensor(y).to(device)
            done = torch.FloatTensor(d).to(device)
            reward = torch.FloatTensor(r).to(device)

            noise = torch.ones_like(action).data.normal_(0, args.policy_noise).to(device)
            noise = noise.clamp(-args.noise_clip, args.noise_clip)
            next_action = (self.actor_target(next_state) + noise).clamp(-self.max_action, self.max_action)

            target_Q1 = self.critic_1_target(next_state, next_action)
            target_Q2 = self.critic_2_target(next_state, next_action)
            target_Q = torch.min(target_Q1, target_Q2)
            target_Q = reward + ((1 - done) * args.gamma * target_Q).detach()

            current_Q1 = self.critic_1(state, action)
            loss_Q1 = F.mse_loss(current_Q1, target_Q)
            self.critic_1_optimizer.zero_grad()
            loss_Q1.backward()
            self.critic_1_optimizer.step()

            current_Q2 = self.critic_2(state, action)
            loss_Q2 = F.mse_loss(current_Q2, target_Q)
            self.critic_2_optimizer.zero_grad()
            loss_Q2.backward()
            self.critic_2_optimizer.step()

            if i % args.policy_delay == 0:
                actor_loss = - self.critic_1(state, self.actor(state)).mean()
                self.actor_optimizer.zero_grad()
                actor_loss.backward()
                self.actor_optimizer.step()

                for param, target_param in zip(self.actor.parameters(), self.actor_target.parameters()):
                    target_param.data.copy_((1 - args.tau) * target_param.data + args.tau * param.data)
                for param, target_param in zip(self.critic_1.parameters(), self.critic_1_target.parameters()):
                    target_param.data.copy_((1 - args.tau) * target_param.data + args.tau * param.data)
                for param, target_param in zip(self.critic_2.parameters(), self.critic_2_target.parameters()):
                    target_param.data.copy_((1 - args.tau) * target_param.data + args.tau * param.data)

            self.num_actor_update_iteration += 1
            self.num_critic_update_iteration += 1
            self.num_training += 1

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--env_name', default="Pendulum-v0")
    parser.add_argument('--tau', default=0.005, type=float)
    parser.add_argument('--iteration', default=5, type=int)
    parser.add_argument('--learning_rate', default=3e-4, type=float)
    parser.add_argument('--gamma', default=0.99, type=float)
    parser.add_argument('--capacity', default=50000, type=int)
    parser.add_argument('--num_iteration', default=100000, type=int)
    parser.add_argument('--batch_size', default=100, type=int)
    parser.add_argument('--seed', default=1, type=int)
    parser.add_argument('--policy_noise', default=0.2, type=float)
    parser.add_argument('--noise_clip', default=0.5, type=float)
    parser.add_argument('--policy_delay', default=2, type=int)
    parser.add_argument('--exploration_noise', default=0.1, type=float)
    parser.add_argument('--max_episode', default=2000, type=int)
    args = parser.parse_args()

    env = gym.make(args.env_name)
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.shape[0]
    max_action = float(env.action_space.high[0])

    agent = TD3(state_dim, action_dim, max_action)

    if args.mode == 'train':
        ep_r = 0
        for i in range(args.num_iteration):
            state = env.reset()
            for t in range(2000):
                action = agent.select_action(state)
                action = action + np.random.normal(0, args.exploration_noise, size=env.action_space.shape[0])
                action = action.clip(env.action_space.low, env.action_space.high)
                next_state, reward, done, info = env.step(action)
                ep_r += reward
                agent.memory.push((state, next_state, action, reward, np.float(done)))
                if len(agent.memory.storage) >= args.capacity - 1:
                    agent.update(10, args)
                state = next_state
                if done or t == args.max_episode - 1:
                    print(f"Ep_i {i}, ep_r {ep_r:.2f}")
                    ep_r = 0
                    break

六、总结

TD3 不仅改进了 DDPG 的不足,还为强化学习的稳定性研究提供了重要的理论和实践参考。其成功之处在于:

  • 克服了 Q 值过估计问题,使得训练过程更加稳定;
  • 提升了策略更新的鲁棒性,能更高效地探索动作空间。

作为一个里程碑式的算法,TD3 推动了连续动作空间强化学习的发展,为后续算法(如 SAC、PPO 等)提供了宝贵的启发。

参考文献:Addressing Function Approximation Error in Actor-Critic Methods

目录

  1. 双延迟深度确定性策略梯度算法 (TD3) 详解
  2. 一、TD3 算法的背景
  3. 二、TD3 的背景
  4. 1. TD3 的理论背景
  5. 2. DDPG 的局限性
  6. 三、TD3 算法的核心思想
  7. 1. 双 Critic 网络(Twin Critics)
  8. 2. 延迟更新(Delayed Policy Updates)
  9. 3. 目标策略平滑(Target Policy Smoothing)
  10. 四、TD3 算法详细讲解
  11. 1. Actor-Critic 框架的核心
  12. (1) 策略梯度
  13. (2) 价值评估 (Critic)
  14. 2. TD3 的关键改进
  15. (1) 双 Critic 网络
  16. (2) 延迟 Actor 更新
  17. (3) 目标动作平滑
  18. 3. 完整 TD3 算法流程
  19. 4. 数学细节解析
  20. (1) Critic 损失函数
  21. (2) Actor 策略梯度
  22. (3) 延迟更新的效果
  23. 五、Python 实现
  24. 环境配置
  25. Python 3.11.5
  26. torch 2.1.0
  27. gym 0.26.2
  28. 代码实现
  29. 六、总结

更多推荐文章

查看全部
  • IntelliJ IDEA 配置 AI 插件实现 Java 俄罗斯方块游戏开发
  • 基于FPGA的电机控制开源项目实践指南
  • 低空安全综合管理服务平台建设方案
  • 企业电子招投标采购系统功能简介
  • Git 密码修改实战:HTTPS、SSH 与 Token 配置全解
  • 数据库 SQL 防火墙:拦截率 99.99% 及恶意 SQL 防护
  • Xilinx FPGA 实现 RISC-V 五级流水线 CPU 设计实战
  • OpenClaw Windows 安装配置教程:Node.js 22、Kimi 模型与飞书机器人集成
  • 基于昇腾 NPU 部署 Mistral-7B-Instruct-v0.2 模型实战
  • 本体论入门:RDF、RDFS 和 OWL 指南
  • Python 零基础转行指南:技术路线、工具配置与面试准备
  • AIGC 时代的 Model Context Protocol (MCP) 核心解析
  • Stable Diffusion 模型原理与本地部署实战
  • GitHub Copilot 学生认证指南:Pro 版免费权益获取流程
  • 终身机器人学习基准测试平台 LIBERO 详解
  • FPGA 中加法器资源利用深度剖析
  • 5 款开源 PPT 生成大模型实测对比:技术原理与工具评测
  • C++ 类和对象:拷贝构造与赋值运算符重载详解
  • Stable Diffusion 文生图基础详解与参数配置
  • C++11 核心特性实战:Lambda、移动语义与模板进阶

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online