首页 / 资讯中心 / 文章详情

从Q-Learning到DQN:用神经网络突破表格法的高维困境

从Q-Learning到DQN:用神经网络突破表格法的高维困境 ★ FEATURED ARTICLE
之前我们聊 Q-Learning 时大部分代码还停留在“查表”阶段把状态离散成一个个格子然后用一张二维表记录每个状态-动作的价值。这个思路在小型迷宫、FrozenLake 这类环境里足够用但一旦遇到 CartPole、Atari 游戏甚至连续控制任务表格就会瞬间失效。第 49 篇我们来解决这个核心瓶颈把 Q-Learning 中的 Q 表换成神经网络这就是 Deep Q-NetworkDQN。本文会从 Q-Learning 的更新公式重新推导 DQN 的损失函数与训练流程并给出 Q-Learning 基线、DQN 完整可运行的 CartPole 实战代码最后整理训练不收敛、Q 值爆炸等高频问题的排查清单。适合已经理解 MDP、价值迭代和时序差分但还没有系统跑通过 DQN 的读者。1. 从 Q-Learning 到 DQN为什么需要深度网络1.1 Q-Learning 解决的是什么问题Q-Learning 是强化学习中经典的无模型Model-Free、**异策略Off-Policy**算法。所谓无模型是指它不需要知道环境的转移概率 P(s|s,a) 和奖励函数 R(s,a)直接通过与环境的交互样本来学习所谓异策略是指它用 ε-greedy 等探索策略采集数据却在更新时使用 max 算子去逼近最优策略的价值行为策略和目标策略可以是不同的。表格版 Q-Learning 的核心操作只有两件事初始化一张 Q 表表中每个格子 Q(s,a) 代表“在状态 s 下执行动作 a 的长期回报期望”。每采集一条经验 (s,a,r,s)用下面的公式更新 Q 表Q(s,a) ← Q(s,a) α [ r γ * max_{a} Q(s,a) - Q(s,a) ]其中 r γ * max_{a} Q(s,a) 叫做TD Target它和当前 Q(s,a) 的差值叫做TD Error。Q-Learning 的收敛性建立在 Bellman 最优方程之上Q*(s,a) r γ * max_{a} Q*(s,a)只要每个状态-动作对都持续被访问并且学习率 α 满足随机近似条件Q 表最终会收敛到最优 Q 值。这个数学结论很漂亮但它有一个致命的隐性前提——状态的个数必须是有限的、可枚举的。1.2 表格方法的瓶颈拿 CartPole 举例。环境返回的观测 state 是一个 4 维连续向量[小车位置, 小车速度, 杆角度, 杆角速度]这四个值都是浮点数。要用表格法我们必须手动把它们切成若干区间。比如每个维度切成 12 个区间那么表格规模就是12^4 × 2 41472 个格子听上去还能接受但我们立刻会遇到两个问题离散化粒度不好把握。切得太粗相邻的不同状态被当成同一个状态策略粗糙切得太细格子数量指数爆炸每个格子被访问到的次数急剧减少训练数据稀疏收敛变慢。泛化能力为零。Q 表只能记录“见过”的状态一个从未访问过的状态没有任何价值信息。状态一多这条路径就走死了。再往后推一步如果观测是图像比如 Atari 游戏的一帧画面是 210×160×3 的像素矩阵把所有像素组合枚举出来是一个天文数字。连续控制任务机械臂、无人机的状态本质上是连续向量理论上表格根本不可行。所以用函数逼近代替查表是自然的发展方向。1.3 DQN 的核心思想DQN 的思路一句话就能说清楚用一个神经网络 Q(s,a;θ) 去近似真实的最优 Q 函数 Q(s,a)*。网络输入是状态 s输出是每个候选动作的 Q 值。以 CartPole 为例输入维度是 4输出维度是 2网络中间用若干隐藏层拟合 Q 函数。这样有三个好处不需要手动离散化状态连续状态直接进网络。网络参数是共享的相似的状态会被分到相近的 Q 值天然具备泛化能力。训练变成了常见的梯度下降问题可以复用深度学习生态里成熟的优化器、正则化手段。但直接把 Q-Learning 的更新公式套到神经网络上会面临一个麻烦TD Target 中也包含同一个网络自身的输出优化目标一直在移动训练极不稳定。2013 年 Mnih 等人提出的 DQN 在 Nature 上发表时最重要的工程贡献就是两个稳定化技巧——经验回放Experience Replay和目标网络Target Network。接下来的章节我们先把原理逐层剥开再进入代码实战。2. 环境准备与版本说明本文的实战代码依赖以下环境操作系统Windows / Linux / macOS 均可。Python 版本3.10 或更高。PyTorch2.x 稳定版。Gymnasium建议使用 0.29 或更新的稳定版gymnasium是旧版gym的社区继承版本API 有差异下面代码均基于新 API。NumPy用于 Q-Learning 基线的数组操作。安装命令pip install torch gymnasium numpy版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果网络环境安装较慢可以自行配置 PyTorch 的国内镜像源。项目结构如下rl-dqn-tutorial/ ├── src/ │ ├── q_learning_baseline.py # 表格 Q-Learning 基线 │ ├── dqn_agent.py # DQN 网络、回放缓冲区和 Agent │ └── train_dqn.py # DQN 训练主循环 └── requirements.txt3. 核心算法原理拆解3.1 再看一遍 Q-Learning 更新公式为了让后面 DQN 的损失函数看起来不突兀我们先把 Q-Learning 公式拆成“预测”和“目标”两部分预测 Q(s, a) 目标 r γ * max_{a} Q(s, a) 更新 Q(s, a) α * (目标 - 预测)Q-Learning 拿当前 Q 值做预测拿“即时奖励 下一状态最优动作的折扣 Q 值”做目标两者做差得到 TD Error然后用学习率 α 把 Q 值往目标方向推一小步。这个过程中有两点值得注意max 算子导致了异策略特性。更新时选择下一个动作用的是贪婪策略而不是采集数据时使用的 ε-greedy 策略。这样一来即使行为策略一直在探索更新过程仍然在向最优策略的价值逼近。自举Bootstrapping特性。目标是基于 Q(s,a) 估计出来的不是真实回报的累计。自举让算法效率高、方差大也是后续稳定性问题的根源。3.2 从 Q 表到 Q 网络Q 表本质上是把状态离散化后存储的一张查找表。换成神经网络后我们不再显式存储每个状态的 Q 值而是用一组参数 θ 来表示 Q 函数Q(s, a; θ) ≈ Q*(s, a)网络输出层的维度等于动作数。CartPole 动作空间只有“向左”、“向右”两个动作所以输出层是 2 维如果动作空间有 18 个离散动作输出层就是 18 维。用神经网络逼近 Q 函数后Q-Learning 的“查表并直接修改格子数值”就变成了“前向传播得到预测、计算梯度、反向传播更新参数”。公式对应关系如下对比维度表格 Q-LearningDQNQ 值存储方式Q 表直接查表神经网络前向传播计算适用状态空间低维离散状态高维连续状态、图像更新方式查表后直接赋值梯度下降最小化损失样本利用率每条经验用一次即丢弃经验回放反复采样使用泛化能力几乎为零参数共享具备泛化能力3.3 经验回放Experience Replay如果按照原始 Q-Learning 的模式每走一步就立刻用这条经验更新网络会遇到两个问题样本高度相关。强化学习的相邻状态之间存在强相关性连续几条经验往往指向同一个方向梯度更新会产生较大振荡。样本利用率低。一条经验用过一次就扔掉了而深度网络训练偏偏非常“吃数据”。经验回放的做法是维护一个固定容量比如 20000的缓冲区把每条经验 (s, a, r, s, done) 存进去。训练时从缓冲区中随机均匀采样一个小批量batch来计算损失。随机采样打破了样本之间的时间相关性让梯度估计更稳定同时同一条经验可以被多次复用相当于提高了数据效率。这里还要强调一个前提DQN 是异策略算法经验回放才能成立。因为更新时使用的是 max 算子计算出的目标值和采集这条经验时的行为策略无关。假如是 SARSA 这类同策略算法旧策略采集的经验就不能随便回放了。3.4 目标网络Target Network光有经验回放还不够DQN 的损失函数定义如下L(θ) E[ ( Q(s, a; θ) - (r γ * max_{a} Q(s, a; θ)) )² ]注意等式右侧的 TD Target 中下一个状态的 Q 值也使用了当前要更新的参数 θ。这意味着优化目标每更新一次参数就跟着变一次训练目标始终在移动。在监督学习中标签是固定的在这里标签由模型自己生成梯度的方向会变得不可控很容易发散。DQN 的解决办法是额外维护一个参数为 θ⁻ 的目标网络专门用来计算 TD Target目标 r γ * max_{a} Q(s, a; θ⁻)训练过程中目标网络的参数不参与梯度更新而是每隔固定步数比如每 100 步或 1000 步从策略网络复制一次θ⁻ ← θ这样在一个同步周期内TD Target 是相对固定的训练就从“追一个移动靶”变成了“追一个每隔一段时间才移动一次的靶”稳定性大幅提升。后续实践中还常使用软更新每一步都把目标网络向策略网络靠近一点θ⁻ ← τ * θ (1 - τ) * θ⁻其中 τ 通常取 0.005软更新比硬更新更平滑在 DDPG、SAC 等算法中也是标准做法。3.5 DQN 完整算法流程把以上模块串起来DQN 的训练流程如下1. 初始化策略网络 Q(s,a;θ)初始化目标网络 Q(s,a;θ⁻)令 θ⁻ θ 2. 初始化空的经验回放缓冲区 D容量为 C 3. 循环执行每个 episode a. 获取初始状态 s b. 循环执行每一步 - 用 ε-greedy 策略选择动作 a - 执行 a得到 r 和 s - 将 (s, a, r, s, done) 存入 D - 若 D 中样本数量足够随机采样一个 batch - 计算损失 L(θ)用梯度下降更新 θ - 每隔 C 步同步 θ⁻ ← θ - s ← s如果 done 则结束当前 episode c. 衰减 ε下一节就按照这个流程分别实现表格 Q-Learning 基线和 DQN并放到 CartPole-v1 环境中对比。4. 完整实战Q-Learning 与 DQN 对比实验4.1 实验环境与项目结构CartPole-v1 是一个经典的平衡控制任务小车在轨道上左右移动杆子初始竖立目标是在 500 步内不让杆子倒下。观测是 4 维连续状态动作是 2 个离散动作每坚持一步奖励 1当杆子倾斜角度超过阈值或小车越界时 episode 提前终止。我们先用表格 Q-Learning 配合状态离散化跑一个基线再用 DQN 跑同一个任务观察两者在训练曲线和最终表现上的差异。4.2 表格 Q-Learning 基线文件路径src/q_learning_baseline.py# 文件路径src/q_learning_baseline.py import gymnasium as gym import numpy as np def build_bins(n_bins12): 根据 CartPole 状态的物理范围构造离散化区间 return [ np.linspace(-2.4, 2.4, n_bins), # 小车位置 x np.linspace(-3.0, 3.0, n_bins), # 小车速度 x_dot np.linspace(-0.5, 0.5, n_bins), # 杆角度 theta np.linspace(-3.0, 3.0, n_bins) # 杆角速度 theta_dot ] def discretize_state(state, bins): 将连续的 4 维状态映射为离散索引元组 return tuple(np.digitize(state[i], bins[i]) for i in range(len(state))) def train_q_learning(episodes1500, alpha0.1, gamma0.99, epsilon1.0, epsilon_min0.01, epsilon_decay0.995, n_bins12): env gym.make(CartPole-v1) bins build_bins(n_bins) # 每个维度离散化后索引范围是 0 ~ n_bins所以每个维度长度是 n_bins 1 q_table_shape [n_bins 1] * 4 [env.action_space.n] q_table np.zeros(q_table_shape) rewards_history [] for episode in range(episodes): state, _ env.reset() state_idx discretize_state(state, bins) total_reward 0 done False while not done: # epsilon-greedy 动作选择 if np.random.random() epsilon: action env.action_space.sample() else: action np.argmax(q_table[state_idx]) next_state, reward, terminated, truncated, _ env.step(action) done terminated or truncated next_idx discretize_state(next_state, bins) # Q-Learning 核心更新 if terminated: # 真正终止后续没有回报不继续自举 td_target reward else: td_target reward gamma * np.max(q_table[next_idx]) td_error td_target - q_table[state_idx (action,)] q_table[state_idx (action,)] alpha * td_error state_idx next_idx total_reward reward epsilon max(epsilon_min, epsilon * epsilon_decay) rewards_history.append(total_reward) if (episode 1) % 100 0: avg np.mean(rewards_history[-100:]) print(f[Q-Learning] Episode {episode 1}, fAverage Reward(100): {avg:.2f}, Epsilon: {epsilon:.3f}) env.close() return q_table, rewards_history if __name__ __main__: train_q_learning()运行命令python src/q_learning_baseline.py这段代码里有两个容易踩坑的细节np.digitize返回的索引范围是 0 到 n_bins因此表格每个维度的长度必须是 n_bins 1否则索引会越界。terminated和truncated要分开理解。CartPole 里terminatedTrue意味着杆子倒了状态之后没有未来回报而truncatedTrue只是达到了 500 步上限此时环境本身没有“死亡”理论上还可以继续自举。代码里对terminated单独处理可以避免把“真正结束”和“超时截断”混为一谈后造成的价值低估。4.3 DQN 网络与经验回放实现文件路径src/dqn_agent.py# 文件路径src/dqn_agent.py import random from collections import deque import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim class DQNNetwork(nn.Module): 三层全连接网络输入状态输出每个动作的 Q 值 def __init__(self, state_dim, action_dim, hidden_dim128): super().__init__() self.net 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.net(x) class ReplayBuffer: 经验回放缓冲区容量满时自动丢弃最旧样本 def __init__(self, capacity20000): self.buffer deque(maxlencapacity) def push(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) states torch.FloatTensor([b[0] for b in batch]) actions torch.LongTensor([b[1] for b in batch]) rewards torch.FloatTensor([b[2] for b in batch]) next_states torch.FloatTensor([b[3] for b in batch]) dones torch.FloatTensor([b[4] for b in batch]) return states, actions, rewards, next_states, dones def __len__(self): return len(self.buffer)ReplayBuffer使用deque(maxlencapacity)实现容量满时自动淘汰最老的样本保证缓冲区里始终是“最新的一批经验”。4.4 DQN Agent 与训练主循环继续在src/dqn_agent.py中补充DQNAgent# 文件路径src/dqn_agent.py追加以下代码 class DQNAgent: def __init__(self, state_dim, action_dim, lr1e-3, gamma0.99, epsilon_start1.0, epsilon_end0.01, epsilon_decay0.995, capacity20000, batch_size64, target_update100): self.action_dim action_dim self.gamma gamma self.batch_size batch_size self.target_update target_update self.policy_net DQNNetwork(state_dim, action_dim) self.target_net DQNNetwork(state_dim, action_dim) self.target_net.load_state_dict(self.policy_net.state_dict()) self.target_net.eval() self.optimizer optim.Adam(self.policy_net.parameters(), lrlr) self.buffer ReplayBuffer(capacity) self.epsilon epsilon_start self.epsilon_end epsilon_end self.epsilon_decay epsilon_decay self.learn_step 0 def choose_action(self, state): epsilon-greedy 动作选择 if random.random() self.epsilon: return random.randrange(self.action_dim) with torch.no_grad(): state_tensor torch.FloatTensor(state).unsqueeze(0) q_values self.policy_net(state_tensor) return q_values.argmax().item() def update(self): 从经验回放中采样并更新一次网络参数 if len(self.buffer) self.batch_size: return None states, actions, rewards, next_states, dones self.buffer.sample( self.batch_size ) # 策略网络预测取出 batch 中每个样本对应动作的 Q 值 current_q self.policy_net(states).gather( 1, actions.unsqueeze(1) ).squeeze(1) # 目标网络计算r gamma * max_a Q_target(s, a) # no_grad 保证梯度不会流向目标网络 with torch.no_grad(): next_q self.target_net(next_states).max(1)[0] target_q rewards self.gamma * next_q * (1 - dones) loss F.mse_loss(current_q, target_q) self.optimizer.zero_grad() loss.backward() self.optimizer.step() self.learn_step 1 # 硬更新每隔 target_update 步同步一次目标网络 if self.learn_step % self.target_update 0: self.target_net.load_state_dict(self.policy_net.state_dict()) return loss.item() def decay_epsilon(self): self.epsilon max(self.epsilon_end, self.epsilon * self.epsilon_decay)有几处实现细节需要重点说明gather(1, actions.unsqueeze(1))的作用是从网络输出中取出每个样本实际执行动作对应的 Q 值。actions形状是(batch_size,)unsqueeze(1)后变成(batch_size, 1)正好匹配 Q 值矩阵索引。计算 target 时包裹了torch.no_grad()。如果这里忘记隔离梯度目标网络的参数也会被反向传播更新目标网络就失去了意义。dones参与 target 计算时乘了(1 - dones)。当done为 1 时下一状态没有未来回报目标退化为r。训练主循环放在src/train_dqn.py# 文件路径src/train_dqn.py import gymnasium as gym from dqn_agent import DQNAgent def train(episodes600, seed42): env gym.make(CartPole-v1) state_dim env.observation_space.shape[0] action_dim env.action_space.n agent DQNAgent(state_dim, action_dim, lr1e-3, gamma0.99, capacity20000, batch_size64, target_update100) rewards_history [] for episode in range(episodes): state, _ env.reset(seedseed episode) total_reward 0 done False while not done: action agent.choose_action(state) next_state, reward, terminated, truncated, _ env.step(action) done terminated or truncated # 保存经验 agent.buffer.push(state, action, reward, next_state, done) loss agent.update() state next_state total_reward reward agent.decay_epsilon() rewards_history.append(total_reward) if (episode 1) % 50 0: avg sum(rewards_history[-50:]) / 50 print(f[DQN] Episode {episode 1}, fAverage Reward(50): {avg:.2f}, fEpsilon: {agent.epsilon:.4f}, Loss: {loss:.4f}) env.close() return rewards_history if __name__ __main__: train()运行命令python src/train_dqn.py4.5 结果与对比说明我使用这套超参数运行时典型表现如下表格 Q-Learning前 300 个 episode 基本在随机探索平均奖励只有 20~40随着 ε 衰减逐渐能学到一些有效策略最终在 100~250 之间波动。对离散化 bin 数量非常敏感改成 6 个 bin 可能学得很差改成 30 个 bin 又会因为样本稀疏而变慢。DQN早期同样因为探索不足表现一般但通常在 100~300 个 episode 之间出现明显的“学习跳跃”之后平均奖励很快接近 500。损失值在训练初期会比较高后期逐渐下降并伴随波动这是正常的。需要强调具体数值会受随机种子、网络初始化、超参数影响不能把上面的数字当成固定结论。**真正重要的不是“DQN 一定比 Q-Learning 好”这个结论而是表格方法在状态维度增长后会指数级失效而 DQN 通过神经网络逼近和两个稳定性技巧把 Q-Learning 从低维离散场景扩展到了高维连续场景。5. 常见问题与排查思路下面整理 DQN 训练中最常遇到的几类问题按“现象 → 原因 → 排查方向”给出解决思路。问题现象常见原因解决思路训练很久 reward 一直很低ε 衰减过快或过慢、探索不足调整epsilon_decay让探索期足够长损失值震荡剧烈或快速上升学习率过大、奖励尺度太大降低lr对 reward 做 clip 或归一化网络输出 Q 值虚高max 算子对 Q 值存在高估改用 Double DQN或增大目标网络更新频率性能在学习后突然崩溃回放缓冲区里旧策略样本污染减小 buffer 容量或尽早调整 ε 策略评估时表现远差于训练时评估时没有关闭 ε 探索评估阶段将epsilon设为 0训练曲线锯齿感极强batch 大小太小、样本相关性强增大batch_size检查是否打乱样本顺序5.1 训练不收敛、reward 长期低位徘徊这种问题多数出在探索策略上。如果epsilon_decay太小ε 衰减太慢智能体会一直随机乱走后期仍然浪费大量探索如果衰减太快训练还没积累足够样本就进入纯贪婪模式策略容易困在局部。排查步骤打印每个 episode 的 ε 值确认它是否在合理区间递减。打印回放缓冲区中的样本数量确认len(buffer)是否一直小于batch_size导致网络根本没怎么训练。观察前 100 个 episode 的平均奖励是否比随机策略高。CartPole 随机策略约 20~30如果连这个基准都不到大概率是环境 reset 或 reward 处理错了。5.2 损失值不降反升、Q 值爆炸DQN 的损失值本身并不是单调递减的因为 TD Target 由目标网络产生目标网络会周期性变化。但如果损失值出现明显发散通常有两个原因学习率太大参数更新步长过头网络在最优解附近振荡甚至漂移。奖励值域过大。如果 reward 动辄上千TD Target 就会很大梯度也同步变大。对应的处理方式# 在 update 中限制梯度范数防止梯度爆炸 torch.nn.utils.clip_grad_norm_(self.policy_net.parameters(), max_norm10.0)同时对输入状态做归一化也很重要。CartPole 状态范围不算离谱但如果是像素输入一定要先除以 255 或做标准化。5.3 terminated 与 truncated 混淆导致价值低估现在的 Gymnasium API 把 episode 结束分成两类terminatedTrue环境真正进入终止状态比如杆子倒了后面没有 reward。truncatedTrue只是到达步数上限被截断环境本身还能继续。如果代码里把两者合并成done terminated or truncated同时在计算 target 时统一使用(1 - done)那么当截断发生时下一状态本应继续自举却被强制当成终止会系统性低估状态价值。CartPole 中截断只发生在 500 步上限影响较小但在很多长程任务里这个问题会被放大。更严谨的做法是分开存储# 存储时只记录 terminatedtruncated 不切断自举 agent.buffer.push(state, action, reward, next_state, float(terminated))同时循环结束条件仍是terminated or truncated这属于容易被人忽视但值得注意的细节。6. 最佳实践与工程建议6.1 超参数配置经验以下是一组在 CartPole 这类中小型离散动作任务上通用的起点配置实际使用时按项目调整超参数建议范围调整方向learning rate1e-4 ~ 1e-3不收敛时调小训练太慢可适当调大gamma0.95 ~ 0.99任务越长gamma 应越接近 1epsilon_decay0.99 ~ 0.999任务越复杂探索期应该越长buffer capacity10000 ~ 100000样本多样性不足时增大batch size32 ~ 256梯度不稳时增大target_update100 ~ 1000 步更新太频繁会失去稳定作用6.2 训练稳定性与可复现工程实战中除了算法本身训练过程的稳定性往往决定项目能否交付固定随机种子。在代码开头设置random.seed(0)、np.random.seed(0)、torch.manual_seed(0)并在env.reset(seed...)中传种子。虽然不能完全消除随机性但能让结果可控。使用滑动平均评估曲线。单次 episode 的 reward 波动极大建议每 N 个 episode 计算一次滑动平均再决定是否继续训练。定期保存模型 checkpoint。深度强化学习经常出现“学会了又崩掉”的现象保存历史最好模型比保存最后一个模型更稳妥。评估与训练分离。评估时关闭 ε 探索多次随机种子运行取平均值避免单次运行结果误导判断。6.3 DQN 的经典改进方向如果你的任务里 DQN 已经能跑通下一步可以按顺序尝试以下改进Double DQN把 target 计算中的 max 改成a argmax Q(s; θ)再用Q(s; θ⁻)取值缓解 Q 值高估问题代码改动量很小。Dueling DQN把网络输出拆成状态价值 V(s) 和动作优势 A(s,a)在动作差距较小的情况下更容易区分好动作。Prioritized Experience Replay不再均匀采样经验而是按 TD Error 大小给样本加权让“更值得学”的样本获得更高采样概率。Noisy DQN用噪声层代替 ε-greedy 探索让探索强度随训练自动调整。Rainbow把上述改进与多步 TD、分布 RL 等整合到一起是 DQN 方向的集大成者。7. 总结与下一步学习路线本文从 Q-Learning 的表格更新公式重新出发推导了 DQN 的损失函数解释了为什么朴素地把 Q 表换成神经网络会导致训练不稳定以及经验回放和目标网络分别解决了什么问题。实战部分给出了两个可运行的 CartPole 实验表格 Q-Learning 使用状态离散化建立 Q 表DQN 用三层全连接网络配合 20000 容量经验回放和 100 步目标网络更新完成训练。通过对比可以直观感受到状态空间一旦连续化、高维化表格方法就会迅速走到极限而 DQN 把问题转化成了标准的“监督回归 经验复用”让深度强化学习真正具备落地能力。接下来建议按顺序做三件事把本文的train_dqn.py换个环境跑一遍比如LunarLander-v2体会动作空间更大时 DQN 的表现。花半小时给 DQN 加 Double DQN 和 Dueling 结构观察 Q 值高估现象是否缓解。如果想继续深入下一步就是策略梯度方向从 REINFORCE 开始理解“直接优化策略”的思路再过渡到 PPO、SAC 等现代主流算法。DQN 是深度强化学习里承上启下的关键一环它承接了表格方法的全部理论根基又开启了深度网络在控制、博弈、调度等场景的大门。建议读者把本文代码亲自动手运行一遍改几个超参数观察曲线变化这比只看推导收获要大得多。
阅读完成 · 觉得有帮助?
咨询建站