🎯 1. 引言:从 Q-learning 到 DQN

“我是谁?我在哪?我该怎么做?这是每一个强化学习智能体在环境中睁眼的第一刻必须思考的问题。”

在强化学习的世界中,Q-learning 曾是最火的“自学派代表”。它的思路很简单直白:

把每一个【状态 + 动作】的组合打个分,叫做 Q 值。以后只做分数最高的那个。

🧠 Q-learning 的核心:Q 表

想象你是个 AI 玩家,在一个 4×4 的冰湖地图上移动。你会建一个表格,像这样:

状态
s0 0 0 0 0
s1 0 0 0 0

每玩一局,你就更新这个表格——用经验让它越来越准确。这就是强化学习的核心理念:Trial and Error(试错学习)


⚠️ Q-learning 的瓶颈:世界太大,表装不下了!

Q-learning 一切都挺好,直到你尝试让它在复杂地图、像素图、甚至玩《超级马里奥》:

  • 状态数量太多(像素级别成千上万)
  • 表格撑不下(维度爆炸)
  • 相似状态没法“类比学习”(泛化能力差)

简单说:你不能指望它记住每一张游戏画面。


💡 这时 DQN 出场了!

“既然表格太大记不下,那干脆交给神经网络吧!”

DQN(Deep Q Network)用神经网络来估算 Q 值函数,即:

Q(s, a) ≈ NN(s)[a]
  • 把原来的表变成函数:状态进去,动作得分出来
  • 通过深度学习训练神经网络,替代 Q 表
  • 从此:不再死记硬背,而是“看图做判断”

🔁 从 Q 表到 Q 函数

Q-learning DQN
显式表格 神经网络函数
每个状态都记 自动泛化
状态必须离散 状态可高维连续

DQN 就像给 Q-learning 装上了“大脑”,不再手动查表,而是通过神经网络自己学会如何评估每一个动作的价值。


🧬 2. DQN 的核心组成结构

DQN 的本质,就是用一个神经网络来逼近 Q 函数,但为了让它真的“稳稳地学”,我们还需要几个关键结构模块,一起来看看 DQN 的“四大天王”:


🧠 1. Q 网络(主网络)

这是 DQN 的“核心大脑”,输入状态 sss,输出每个动作的 Q 值。

Q(s, a) ≈ NeuralNetwork(s)[a]

每次训练就是在更新这个网络的参数,让它更精准地预估出“当前状态下哪个动作更值钱”。


🎯 2. ε-贪婪策略(Epsilon-Greedy)

“是该冒险探索一下,还是安心走老路?”

在学习过程中,我们不能一开始就完全按照当前网络输出去选动作。因为它还不够聪明!

所以我们用 ε-贪婪策略:

  • 以 ε 的概率选择随机动作(探索)
  • 1−ε1 - ε1ε 的概率选择当前 Q 值最大的动作(利用)

ε 一开始设得大一点,然后逐渐降低,比如从 1 降到 0.01,逐步让智能体从“瞎玩”变成“聪明玩”。


🔁 3. 经验回放(Replay Buffer)

“你得复盘过去,才能更好地前进。”

DQN 会把每次经历的状态转移(三元组)保存下来:

(state, action, reward, next_state, done)

训练时随机从这些“回忆”里抽样,打乱顺序训练,避免连续样本带来的偏差。这也让数据复用率更高。


🎯 4. 目标网络(Target Network)

“如果目标一直变,那你永远追不上。”

为了稳定训练,DQN 会维护一个“延迟更新”的副本网络 QtargetQ_{\text{target}}Qtarget,专门用来计算 TD 目标:

TD_target = reward + γ * max(Q_target(next_state))

而这个目标网络是每隔固定步数,从主网络复制参数过去的,不是每次都同步。

这就像你每过几天才跟老板同步一次目标,而不是老板边跑边喊方向。


🏗️ 3. DQN 训练流程简述

DQN 看起来是个深度模型,实际上它的训练过程非常有条理。每一轮训练(Episode)就像一次“刷副本”,智能体一边探索环境、一边更新自己的大脑。以下是标准 DQN 的完整训练流程。


📦 初始化阶段

  • 初始化主 Q 网络 Q(s,a;θ)Q(s, a; \theta)Q(s,a;θ)
  • 初始化目标网络 QtargetQ_{\text{target}}Qtarget,并复制主网络参数
  • 初始化经验回放池 ReplayBuffer
  • 设置初始 ε 值(探索率)

🔁 每一个 Episode 的步骤

对每一局游戏或一段路径规划过程:

  1. 重置环境,得到起始状态 state

  2. 循环直到终止(done)为 True

    • 使用 ε-贪婪策略选择一个动作 action

      • 以 ε 的概率随机动作
      • 否则选择 argmax(Q(state, a))
    • 执行动作,得到 next_state, reward, done

    • 将这一经验 transition = (state, action, reward, next_state, done) 存入回放池

    • 从回放池中 随机采样一个 batch,用于训练主 Q 网络:

      • 对每个样本计算 TD 目标值:

        target = reward + γ * max(Q_target(next_state)) if not done else reward
        
      • 最小化误差损失:

        loss = (target - Q(state)[action]) ** 2
        
    • 执行梯度下降更新主 Q 网络参数

    • 更新当前状态:state ← next_state


🔄 目标网络更新

  • 每隔固定步数(例如 C=1000):

    Q_target ← Q_main
    

🔽 ε 衰减策略

  • 每完成一个 episode,适当降低探索率:

    ε = max(ε_min, ε * decay_rate)
    

    例如从 1 降到 0.1,让智能体从“狂试”逐渐变得“稳准狠”。


✅ 训练完成后

  • 可以将训练好的 Q 网络保存下来
  • 用于测试智能体的实际表现(不再探索,只利用)

📝 流程图

初始化 → 多次 Episode 循环:
  状态 → 选动作 → 得到反馈 → 存经验
              ↓
        抽样经验 → 计算 TD 目标 → 更新网络
              ↓
        每隔 C 步同步目标网络
              ↓
        衰减 ε,继续下一轮探索

✅ 代码实战

环境要求

pip install gym torch numpy matplotlib

📦 文件保存为 dqn_cartpole.py

import gym
import torch
import torch.nn as nn
import torch.optim as optim
import random
import numpy as np
from collections import deque
import matplotlib.pyplot as plt

# ========== 超参数 ==========
EPISODES = 500
BATCH_SIZE = 64
GAMMA = 0.99
LR = 1e-3
EPSILON_START = 1.0
EPSILON_END = 0.01
EPSILON_DECAY = 0.995
TARGET_UPDATE = 10
MEMORY_SIZE = 10000

# ========== Q 网络 ==========
class DQN(nn.Module):
    def __init__(self, state_dim, action_dim):
        super().__init__()
        self.fc = nn.Sequential(
            nn.Linear(state_dim, 128),
            nn.ReLU(),
            nn.Linear(128, action_dim)
        )

    def forward(self, x):
        return self.fc(x)

# ========== 经验回放 ==========
class ReplayBuffer:
    def __init__(self, capacity):
        self.buffer = deque(maxlen=capacity)

    def push(self, *transition):
        self.buffer.append(transition)

    def sample(self, batch_size):
        batch = random.sample(self.buffer, batch_size)
        return zip(*batch)

    def __len__(self):
        return len(self.buffer)

# ========== 训练主流程 ==========
env = gym.make("CartPole-v1")
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n

policy_net = DQN(state_dim, action_dim)
target_net = DQN(state_dim, action_dim)
target_net.load_state_dict(policy_net.state_dict())

optimizer = optim.Adam(policy_net.parameters(), lr=LR)
memory = ReplayBuffer(MEMORY_SIZE)

epsilon = EPSILON_START
rewards_history = []

for episode in range(EPISODES):
    state = env.reset()
    state = torch.tensor(state, dtype=torch.float32)
    total_reward = 0

    for t in range(500):  # 每局最长 500 步
        # ε-贪婪选择动作
        if random.random() < epsilon:
            action = env.action_space.sample()
        else:
            with torch.no_grad():
                q_values = policy_net(state)
                action = torch.argmax(q_values).item()

        # 执行动作
        next_state, reward, done, _ = env.step(action)
        next_state_tensor = torch.tensor(next_state, dtype=torch.float32)
        memory.push(state, action, reward, next_state_tensor, done)

        state = next_state_tensor
        total_reward += reward

        # 训练
        if len(memory) >= BATCH_SIZE:
            states, actions, rewards, next_states, dones = memory.sample(BATCH_SIZE)

            states = torch.stack(states)
            actions = torch.tensor(actions).unsqueeze(1)
            rewards = torch.tensor(rewards, dtype=torch.float32).unsqueeze(1)
            next_states = torch.stack(next_states)
            dones = torch.tensor(dones, dtype=torch.float32).unsqueeze(1)

            q_values = policy_net(states).gather(1, actions)
            with torch.no_grad():
                max_next_q = target_net(next_states).max(1)[0].unsqueeze(1)
                q_targets = rewards + GAMMA * max_next_q * (1 - dones)

            loss = nn.functional.mse_loss(q_values, q_targets)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

        if done:
            break

    # 衰减探索率
    epsilon = max(EPSILON_END, epsilon * EPSILON_DECAY)

    rewards_history.append(total_reward)

    # 更新目标网络
    if episode % TARGET_UPDATE == 0:
        target_net.load_state_dict(policy_net.state_dict())

    print(f"Episode {episode}: Reward = {total_reward}, Epsilon = {epsilon:.3f}")

# ========== 绘制训练结果 ==========
plt.plot(rewards_history)
plt.title("DQN CartPole Training Reward")
plt.xlabel("Episode")
plt.ylabel("Total Reward")
plt.grid()
plt.show()

🎯 效果说明

  • 训练几百轮后,智能体会逐渐学会让小车保持平衡,获得接近 500 分的表现(完美)
  • 输出 reward 曲线显示训练趋势

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐