DQN:深度 Q 学习,让 AI 自己学会走迷宫!
每一轮训练(Episode)就像一次“刷副本”,智能体一边探索环境、一边更新自己的大脑。DQN 就像给 Q-learning 装上了“大脑”,不再手动查表,而是通过神经网络自己学会如何评估每一个动作的价值。ε 一开始设得大一点,然后逐渐降低,比如从 1 降到 0.01,逐步让智能体从“瞎玩”变成“聪明玩”。训练时随机从这些“回忆”里抽样,打乱顺序训练,避免连续样本带来的偏差。每次训练就是在更新这个
🎯 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 的步骤
对每一局游戏或一段路径规划过程:
-
重置环境,得到起始状态
state
-
循环直到终止(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 曲线显示训练趋势
更多推荐
所有评论(0)