如何用Python实现DQN算法?DQN强化学习实战教程

Python DQN (Deep Q-Network) 完整实现指南

核心概念回顾

DQN 是结合深度神经网络与Q-Learning的强化学习算法,关键创新点:

  • 经验回放 (Experience Replay):打破数据相关性
  • 目标网络 (Target Network):稳定训练目标
  • ε-greedy 策略:探索与利用平衡

环境准备

pip install gymnasium numpy torch matplotlib

注意:Gym 已升级为 gymnasium,以下代码基于 gymnasium。

深度强化学习 DQN 纯白板逐行代码Python实现
加载中
深度强化学习 DQN 纯白板逐行代码Python实现

完整代码实现

1 构建 DQN 智能体

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import random
from collections import deque
import gymnasium as gym
class DQN(nn.Module):
    """深度Q网络"""
    def __init__(self, state_dim, action_dim, hidden_dim=128):
        super(DQN, self).__init__()
        self.network = nn.Sequential(
            nn.Linear(state_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.ReLU(),
            nn.Linear(hidden_dim, action_dim)
        )
    def forward(self, x):
        return self.network(x)
class DQNAgent:
    """DQN 智能体"""
    def __init__(
        self,
        state_dim: int,
        action_dim: int,
        lr: float = 1e-3,
        gamma: float = 0.99,
        epsilon_start: float = 1.0,
        epsilon_end: float = 0.01,
        epsilon_decay: float = 0.995,
        batch_size: int = 64,
        memory_size: int = 10000,
        target_update_freq: int = 100,
        device: str = "cpu"
    ):
        self.state_dim = state_dim
        self.action_dim = action_dim
        self.gamma = gamma
        self.batch_size = batch_size
        self.target_update_freq = target_update_freq
        self.device = torch.device(device)
        # ε-greedy 参数
        self.epsilon = epsilon_start
        self.epsilon_end = epsilon_end
        self.epsilon_decay = epsilon_decay
        # 网络
        self.q_network = DQN(state_dim, action_dim).to(self.device)
        self.target_network = DQN(state_dim, action_dim).to(self.device)
        self.target_network.load_state_dict(self.q_network.state_dict())
        self.target_network.eval()
        # 优化器 & 损失函数
        self.optimizer = optim.Adam(self.q_network.parameters(), lr=lr)
        self.loss_fn = nn.MSELoss()
        # 经验回放缓冲区
        self.memory = deque(maxlen=memory_size)
    def select_action(self, state: np.ndarray) -> int:
        """ε-greedy 动作选择"""
        if random.random() < self.epsilon:
            return random.randint(0, self.action_dim - 1)
        else:
            state_tensor = torch.FloatTensor(state).unsqueeze(0).to(self.device)
            with torch.no_grad():
                q_values = self.q_network(state_tensor)
            return q_values.argmax(dim=1).item()
 

如何用Python实现DQN算法?DQN强化学习实战教程

def store_transition(self, state, action, reward, next_state, done): """存储经验""" self.memory.append((state, action, reward, next_state, done)) def train_step(self): """单步训练""" if len(self.memory) < self.batch_size: return None # 随机采样 batch batch = random.sample(self.memory, self.batch_size) states, actions, rewards, next_states, dones = zip(batch) states = torch.FloatTensor(np.array(states)).to(self.device) actions = torch.LongTensor(actions).unsqueeze(1).to(self.device) rewards = torch.FloatTensor(rewards).unsqueeze(1).to(self.device) next_states = torch.FloatTensor(np.array(next_states)).to(self.device) dones = torch.FloatTensor(dones).unsqueeze(1).to(self.device) # 当前 Q 值: Q(s, a) current_q = self.q_network(states).gather(1, actions) # 目标 Q 值: r + γ max_a' Q_target(s', a') with torch.no_grad(): next_q = self.target_network(next_states).max(dim=1, keepdim=True)[0] target_q = rewards + self.gamma next_q (1 - dones) # 计算损失并反向传播 loss = self.loss_fn(current_q, target_q) self.optimizer.zero_grad() loss.backward() self.optimizer.step() # 衰减 ε self.epsilon = max(self.epsilon_end, self.epsilon self.epsilon_decay) return loss.item() def update_target_network(self): """更新目标网络""" self.target_network.load_state_dict(self.q_network.state_dict()) def save(self, path: str): torch.save(self.q_network.state_dict(), path) def load(self, path: str): self.q_network.load_state_dict(torch.load(path, map_location=self.device))

2 训练循环

def train_dqn(
    env_name: str = "CartPole-v1",
    max_episodes: int = 500,
    max_steps_per_episode: int = 1000,
    render: bool = False,
    save_path: str = "dqn_cartpole.pth"
):
    """训练 DQN"""
    # 创建环境
    env = gym.make(env_name)
    state_dim = env.observation_space.shape[0]
    action_dim = env.action_space.n
    print(f"Environment: {env_name}")
    print(f"State dim: {state_dim}, Action dim: {action_dim}")
    # 创建智能体
    agent = DQNAgent(
        state_dim=state_dim,
        action_dim=action_dim,
        lr=1e-3,
        gamma=0.99,
        epsilon_start=1.0,
        epsilon_end=0.01,
        epsilon_decay=0.995,
        batch_size=64,
        memory_size=10000,
        target_update_freq=100
    )
    # 记录
    episode_rewards = []
    losses = []
    for episode in range(1, max_episodes + 1):
        state, _ = env.reset()
        total_reward = 0
        episode_loss = []
        for step in range(max_steps_per_episode):
            if render:
                env.render()
            action = agent.select_action(state)
            next_state, reward, terminated, truncated, _ = env.step(action)
            done = ter

如何用Python实现DQN算法?DQN强化学习实战教程

minated or truncated agent.store_transition(state, action, reward, next_state, done) loss = agent.train_step() if loss is not None: episode_loss.append(loss) total_reward += reward state = next_state if done: break # 定期更新目标网络 if episode % agent.target_update_freq == 0: agent.update_target_network() avg_loss = np.mean(episode_loss) if episode_loss else 0 episode_rewards.append(total_reward) losses.append(avg_loss) if episode % 10 == 0: avg_reward = np.mean(episode_rewards[-10:]) print( f"Episode {episode:4d} | " f"Reward: {total_reward:6.1f} | " f"Avg(10): {avg_reward:6.1f} | " f"Loss: {avg_loss:.4f} | " f"ε: {agent.epsilon:.4f}" ) # 保存模型 agent.save(save_path) print(f"n模型已保存至: {save_path}") return episode_rewards, losses # 启动训练 if __name__ == "__main__": rewards, losses = train_dqn( env_name="CartPole-v1", max_episodes=500, render=False )

3 可视化训练曲线

import matplotlib.pyplot as plt
def plot_training(rewards, losses):
    """绘制训练曲线"""
    fig, axes = plt.subplots(1, 2, figsize=(14, 5))
    # 奖励曲线(滑动平均)
    window = 10
    smooth_rewards = np.convolve(rewards, np.ones(window)/window, mode='valid')
    axes[0].plot(smooth_rewards, linewidth=2)
    axes[0].set_title("Smoothed Episode Reward")
    axes[0].set_xlabel("Episode")
    axes[0].set_ylabel("Reward")
    axes[0].grid(True)
    # 损失曲线
    axes[1].plot(losses, linewidth=2, color='red')
    axes[1].set_title("Training Loss")
    axes[1].set_xlabel("Episode")
    axes[1].set_ylabel("Loss")
    axes[1].grid(True)
    plt.tight_layout()
    plt.savefig("dqn_training.png", dpi=150)
    plt.show()
# plot_training(rewards, losses)

4 测试/评估

def evaluate_agent(agent: DQNAgent, env_name: str = "CartPole-v1", n_episodes: int = 10, render: bool = False):
    """评估智能体"""
    env = gym.make(env_name)
    rewards = []
    for ep in range(n_episodes):
        state, _ = env.reset()
        total_reward = 0
        done = False
        while not done:
            if render:
                env.render()
            action = agent.select_action(state)  # ε≈0,几乎总是贪心
            next_state, reward, terminated, truncated, _ = env.step(action)
            done = terminated or truncated
            total_reward += reward
            state = next_state
        rewards.append(total_reward)
    avg_reward = np.mean(rewards)
    print(f"评估结果 ({n_episodes} episodes): 平均奖励 = {avg_reward:.2f}")
    return rewards
# 使用示例
if __name__ == "__main__":
    # 加载已训练模型
    agent = DQNAgent(state_dim=4, action_dim=2)
    agent.load("dqn_cartpole.pth")
    agent.epsilon = 0.0  # 纯贪心策略评估
    evaluate_agent(agent, render=True, n_episodes=5)

如何用Python实现DQN算法?DQN强化学习实战教程


关键超参数说明

参数 推荐值 说明
lr 1e-3 ~ 1e-4 学习率
gamma 99 折扣因子
epsilon_start 0 初始探索率
epsilon_end 01~0.05 最小探索率
epsilon_decay 995~0.999 探索率衰减
batch_size 32~128 批量大小
memory_size 10000~100000 回放缓冲区大小
target_update_freq 100~1000 目标网络更新频率

进阶改进方向

┌─────────────────────────────────────────────┐
│           DQN 改进版本                       │
├─────────────────────────────────────────────┤
│ 1. Double DQN       → 解决 Q 值过估计       │
│ 2. Dueling DQN      → 分离状态价值与优势函数 │
│ 3. Prioritized Replay → 优先采样重要经验     │
│ 4. Noisy Networks    → 参数化噪声替代 ε-greedy│
│ 5. Rainbow DQN       → 上述所有改进的集合     │
└─────────────────────────────────────────────┘

Double DQN 修改示例(替换 train_step 中的目标计算):

# 当前网络选择最佳动作
next_actions = self.q_network(next_states).max(dim=1, keepdim=True)[1]
# 目标网络评估该动作的 Q 值
next_q = self.target_network(next_states).gather(1, next_actions)
target_q = rewards + self.gamma  next_q  (1 - dones)

常见问题排查

问题 可能原因 解决方案
奖励不收敛 ε 衰减太快/太慢 调整 epsilon_decay
损失震荡 学习率过大 降低 lr,使用 Adam 默认
训练不稳定 目标网络更新太频繁 增大 target_update_freq
无法解决复杂环境 网络容量不足 增加隐藏层/神经元,或改用 DDPG/PPO

如需针对特定环境(如 Atari、LunarLander)或实现 Double/Dueling DQN,可以告诉我具体需求!

首发原创文章,作者:王坚‌,如若转载,请注明出处:https://idctop.com/article/485072.html

赞 (0)
企业为何要做GEO2026最新?GEO优化对GEO提升有多大
上一篇 2026年7月12日 05:00
Excel怎么筛选结尾的数据?excel筛选以什么结尾
下一篇 2026年7月12日 05:01

相关推荐

  • 高端流媒体服务器价格多少?高配流媒体服务器报价贵吗

    2026年高端流媒体服务器价格通常在8万至50万元不等,具体取决于并发算力、存储架构及硬件编解码方案,企业需根据实际业务并发量与延迟要求进行精准选型,2026高端流媒体服务器价格核心构成硬件底座:算力与转码决定基础成本高端流媒体服务器的核心壁垒在于实时转码与高并发分发能力,根据2026年主流硬件架构,成本分布如……

    2026年4月29日
    8500
  • 服务器弹性ip有什么用?弹性ip怎么配置才正确

    服务器弹性IP是保障业务连续性与架构高可用性的核心网络资源,其核心价值在于解耦IP地址与物理设备的强绑定关系,实现IP资源的灵活流转与即时生效,在复杂的网络架构中,弹性IP不仅是一个静态的公网地址,更是实现故障秒级切换、负载均衡调整以及混合云组网的关键抓手,对于追求高稳定性的企业级应用而言,掌握弹性IP的运作机……

    2026年3月25日
    11100
  • 服务器搭建sip环境windows,Windows服务器怎么搭建SIP环境?

    在Windows服务器上搭建SIP环境,核心在于选择轻量级开源软交换平台(如Asterisk或Kamailio)并正确配置网络防火墙与路由策略,以构建稳定、低延迟的VoIP通信基石,Windows环境虽非传统SIP服务器的首选操作系统,但通过优化系统内核参数与严格的端口管理,完全可以满足中小型企业内部通信及测试……

    2026年3月9日
    13500
  • Python getoption怎么用,有哪些方法?

    Python getoption(通常指getopt模块)是解析命令行参数的基础工具,但复杂项目建议使用argparse实现更健壮的功能, 如果你刚开始接触Python命令行工具,getopt用起来特别直接,但参数一多就容易乱,下面我会从用法、对比到实战,把getoption这件事讲透,Python getop……

    2026年7月21日
    800
  • 个人博客怎么选虚拟主机?个人博客虚拟主机推荐

    个人博客选择虚拟主机的核心在于平衡性能、稳定性与成本,建议优先选择国内备案且提供SSD硬盘的主机,以确保访问速度和合规性,搭建个人博客就像在数字世界安家,虚拟主机就是那块地基,地基打得不牢,房子盖得再漂亮也会摇晃,很多新手博主在起步阶段,往往被琳琅满目的主机套餐迷花眼,最后要么因为服务器卡顿被访客劝退,要么因为……

    2026年6月12日
    3210
  • 你真的知道服务器镜像有什么用吗,如何配置?

    服务器镜像有什么用?服务器镜像是服务器操作系统、配置、数据的一整块快照,它的核心价值在于快速复制、恢复和迁移环境,让运维工作从“装系统”变成“一键部署”,服务器镜像的核心作用服务器镜像到底解决什么问题?我们拆开来看,快速部署新服务器当你需要上线新业务,或者扩容现有架构,传统做法是申请服务器、安装操作系统、配置网……

    2026年7月30日
    1600
  • 你知道目前主流web服务器有哪些吗,哪个更适合生产环境?

    目前主流Web服务器以Nginx、Apache、Tomcat和IIS四分天下,其中Nginx凭借高并发与低资源占用成为市场占有率第一的常客,Apache依然在传统运维场景中保有大量存量,Tomcat专注Java应用,IIS则绑定Windows生态,第一梯队:Nginx与Apache的攻守之势Nginx凭什么稳坐……

    2026年9月15日
    200
  • 服务器硬盘和存储硬盘区别在哪?2026选购指南全解析

    服务器硬盘和存储硬盘的核心区别在于其设计目标、性能特征、可靠性等级以及应用场景,服务器硬盘专为满足数据中心和企业级应用对高性能、高可靠性、7×24小时不间断运行及处理海量并发请求的严苛需求而设计;而存储硬盘(通常指消费级或桌面级硬盘/NAS专用硬盘)则更侧重于成本效益、大容量存储和相对温和的工作负载环境,其可靠……

    2026年2月6日
    11800
  • 服务器瓶颈有哪些方面

    服务器瓶颈通常集中在CPU、内存、磁盘I/O、网络带宽、系统配置与架构设计六个方面,其中磁盘I/O和网络延迟在多数场景下是最大痛点,下面从现象、判断方法到解决路径逐一拆解,帮你少走弯路,CPU瓶颈:计算资源亮起“过劳”红灯CPU是服务器的“大脑”,任何请求都要靠它调度,CPU瓶颈发生时,最直观的感受是系统响应变……

    2026年8月27日
    500
  • 个人域名注册需要实名制吗?域名实名认证流程及所需资料

    个人域名注册必须完成实名认证,这是国家法律法规的强制要求,未实名域名将无法解析访问,建议立即通过注册商后台提交身份证信息进行核验,在2026年的互联网环境下,拥有个人域名不再仅仅是技术爱好者的炫技,而是构建个人品牌、保护知识产权以及实现独立数据资产化的基础手段,许多用户在购买域名后,常常困惑于为何网站打不开,或……

    服务器运维 2026年6月9日
    4400

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注