如何用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

相关推荐

  • 服务器控制台怎么看?服务器控制台在哪里打开

    要高效查看服务器控制台,核心在于明确服务器类型(如Windows Server或Linux)并掌握正确的访问路径与权限配置,服务器控制台是服务器管理的“大脑”,通过本地直连、远程桌面(RDP)或SSH客户端即可访问,关键在于建立安全、稳定的连接通道,对于运维人员而言,熟练掌握控制台的查看方法,是保障业务连续性与……

    2026年3月9日
    13200
  • 服务器最大速度是多少?如何测试服务器网速最快?

    服务器最大速度并非单纯由网络带宽决定,而是硬件I/O处理能力、网络传输质量、系统架构优化以及软件配置效率的综合体现,要突破性能瓶颈,不能仅依赖升级带宽,必须构建从存储到传输的全链路高速通道,通过精细化调优实现数据吞吐的极致效率,真正的速度提升,在于消除每一毫秒的延迟,让硬件资源利用率达到理论峰值, 硬件基础:物……

    2026年2月23日
    14700
  • 服务器有自动备份吗,云服务器怎么开启自动备份

    服务器具备自动备份的能力,但这并非默认开启的通用功能,而是需要根据业务需求进行配置和管理的专业服务,绝大多数现代服务器环境,无论是云服务器还是物理服务器,都提供了完善的自动备份机制,关键在于管理员是否正确部署了相应的策略,硬件支持是基础,软件配置是核心,只有通过合理的设置,才能实现数据的定时、自动、安全保存,很……

    2026年2月21日
    13000
  • accept python函数是什么,怎么用?

    accept()是Python socket编程中用于接受客户端连接请求的核心方法,调用后返回新的连接套接字与客户端地址,理解其阻塞机制和并发处理是构建高性能网络服务的基础,理解accept()函数的工作原理阻塞机制与连接队列当服务器套接字进入监听状态后,操作系统会为其维护一个已完成连接队列,队列中存放已完成T……

    2026年7月20日
    700
  • 手机APN服务器地址除了114还有哪些,怎么设置?

    手机APN设置中的“服务器地址”并非固定为114,而是根据运营商、业务类型和网络环境存在多种选择,其中最常用的是10.0.0.172(移动梦网代理)和mmsc.monternet.com(彩信服务器),而114.114.114.114本质是公共DNS,常被误称为APN服务器地址,理解APN服务器地址的真实含义很……

    2026年8月13日
    800
  • mofbus python是什么?mofbus python怎么安装

    Mofbus Python 是一个基于 Python 的轻量级消息总线框架,专为微服务架构设计,通过解耦组件通信、支持异步事件驱动,能显著提升系统开发效率与运行稳定性,在微服务架构日益普及的今天,服务间的通信方式直接决定了系统的健壮性和可维护性,传统的 REST API 调用虽然直观,但在高并发场景下容易成为性……

    2026年7月5日
    16200
  • 个人网站名怎么取才吸睛?个人网站起名技巧

    个人网站不仅是网络名片,更是构建私域流量池、实现品牌资产数字化的核心基础设施,其价值远超简单的信息展示,在移动互联网高度渗透的2026年,依赖第三方平台获取流量的红利期已逐渐消退,算法推荐的不确定性和平台规则的频繁变动,让许多创作者和小型企业主意识到,拥有一个完全自主控制的个人网站,是抵御风险、沉淀用户数据的唯……

    2026年5月26日
    4200
  • 个人云主机有什么用?个人云主机租用费用多少

    个人云主机本质上是一台24小时在线、由你完全掌控的远程计算机,它不仅是搭建个人网站和博客的低成本方案,更是实现数据私有化、运行自动化脚本及开发测试环境的强力工具,很多人听到“云主机”或“云服务器”,第一反应是互联网大厂或者电商企业的专属装备,觉得那玩意儿贵且复杂,随着云计算技术的普及,个人云主机的门槛已经降到了……

    2026年6月17日
    2100
  • 服务器往移动硬盘拷贝数据慢怎么办,如何提高传输速度

    服务器向移动硬盘迁移数据,最核心的原则在于确保传输稳定性与数据完整性,而非单纯追求速度,直接结论是:必须通过合理的硬件选型、正确的文件系统格式化以及科学的传输策略,构建一条从服务器到移动硬盘的高可靠数据链路,任何忽视细节的操作都可能导致数据损坏或传输中断, 硬件接口与物理连接是传输的基石服务器与移动硬盘的物理连……

    2026年3月25日
    11200
  • 学livecode还是python好?,零基础学编程选什么好

    LiveCode与Python集成后,开发者可以在LiveCode的简化开发环境中直接调用Python脚本,从而大幅提升跨平台应用的数据处理能力和生态兼容性,livecode python 是什么:定义与工作原理livecode python 指的是在 LiveCode 开发平台中嵌入 Python 脚本引擎……

    2026年7月18日
    600

发表回复

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