DQN原理与PyTorch实现:从Q-Learning到经验回放+目标网络

很多学习强化学习的读者,在接触 DQN 之前,多半已经能用 Q-Learning 在一个小网格、迷宫或者简单的表格环境里跑通一个 Agent。那一刻你会觉得:强化学习好像也没那么复杂,更新公式背下来,迭代几百轮,Q 表收敛,策略就出来了。

但一旦你把问题换成稍微真实一点的场景——比如输入是图像,或者状态是连续值,Q 表就立刻崩掉了。为什么?因为 Q 表的规模随着状态空间呈指数级增长。国际象棋的状态数已经超过 10 的 40 次方,围棋更是高到无法用表格索引。表格型 Q-Learning 能解决的问题,本质上都停留在“玩具环境”层面。

DQN 解决的就是这个问题。它的核心改动是用神经网络代替 Q 表,但如果你以为 DQN 只是“Q-Learning + 一个深度学习网络”,那就漏掉了最关键的判断:直接从表格更新切到神经网络训练,算法根本不收敛。DQN 真正的贡献,是围绕神经网络训练重新设计了一套稳定机制,包括经验回放(Experience Replay)和目标网络(Target Network)。

这篇文章会从 Q-Learning 的更新公式出发,讲清楚 DQN 到底改了什么、为什么这样改,然后给出一个可复制的 PyTorch 最小实现,在 Gymnasium 的 CartPole 环境上完整跑通训练和验证。看完之后,你就能回答三个问题:DQN 的三个关键机制各自解决什么痛点?训练不收敛时应该先检查哪里?从 DQN 往后,强化学习应该往哪些方向继续深入。

1. 从 Q-Learning 到 DQN:到底改了什么

先回顾表格 Q-Learning 的更新公式:

Q(s, a) <- Q(s, a) + alpha * (r + gamma * max(Q(s', a')) - Q(s, a))

这个公式的意思是:Agent 在状态 s 执行动作 a,环境返回奖励 r 并转移到新状态 s',然后用r + gamma * max(Q(s', a'))作为“真实价值”的估计,去修正原来的 Q(s, a)。这个估计叫时序差分目标(TD Target),更新的幅度由学习率 alpha 控制。

表格方法的逻辑非常清晰:状态 s 和动作 a 作为下标,Q 值直接存在表里。但表格方法有三个硬伤:

  1. 连续状态无法索引。如果状态是温度、位置、速度这种连续值,不可能为每一个小数建一行。
  2. 维度灾难。即使状态离散,只要维度增加,表的大小就会指数膨胀。
  3. 无法泛化。表格里的每个状态都是独立的,学过的状态对没见过的状态没有帮助。

DQN 的思路是把查找表变成函数逼近:用一个带参数 theta 的神经网络表示Q(s, a; theta)。网络输入状态 s,输出所有动作的 Q 值。这样相近的状态会得到相近的输出,天然具备泛化能力。

但问题也随之而来。表格更新一次只影响一个格子;神经网络参数更新一次,会改变所有状态下的输出。这意味着训练信号之间会产生强烈的干扰。如果只是简单套用 Q-Learning 的更新公式,把网络当表用,训练过程会剧烈震荡甚至发散。DQN 能稳定工作的前提,是另外两个设计——经验回放和目标网络——真正解决了样本相关性和目标漂移问题。

2. Q-Learning 复习:先明确一个学习闭环

在进入 DQN 之前,有必要把 Q-Learning 的完整逻辑再过一遍。因为后面所有 DQN 的改进,都是围绕它的损失函数和采样过程展开的。

强化学习问题通常建模为马尔可夫决策过程(MDP),由五元组(S, A, P, R, gamma)描述:

符号含义通俗解释
S状态集合Agent 当前所处的局面
A动作集合Agent 可以选择的行动
P状态转移概率执行动作后环境如何变化
R奖励函数每个状态动作组合获得的即时反馈
gamma折扣因子未来奖励相对当前的价值衰减程度

Agent 的目标是最大化长期累积回报。考虑折扣因子后,从状态 s 出发的累积回报为:

G_t = r_t + gamma * r_{t+1} + gamma^2 * r_{t+2} + ...

动作价值函数 Q(s, a) 表示在状态 s 执行动作 a 后,按照某个策略继续行动所能获得的期望回报。最优动作价值函数 Q* 满足贝尔曼最优方程:

Q*(s, a) = r + gamma * max(Q*(s', a'))

这个方程的本质是:当前状态动作的价值,等于即时奖励加上下一个状态的最优价值。Q-Learning 的更新公式正是对这个方程的迭代逼近。

Q-Learning 是离线策略(Off-Policy)算法,它用行为策略采集数据,却用贪心策略max Q(s', a')更新价值。这样做的优势是可以在探索过程中逐步逼近最优 Q 值,而不需要当前策略已经是最优策略。

整个过程可以用下面这段伪代码概括:

初始化 Q(s, a) 为任意值 重复每个 episode: 初始化状态 s 重复每个 step: 用 epsilon 贪心策略根据当前 Q 选择动作 a 执行 a,获得奖励 r 和新状态 s' 更新 Q(s, a) <- Q(s, a) + alpha * (r + gamma * max Q(s', a') - Q(s, a)) s <- s' 直到 s 为终止状态

看到这里你应该明白,Q-Learning 的更新本身并不复杂。它的局限不在公式,而在“Q 表”这个存储载体。DQN 恰恰是从载体上下手,把表的存储和查找替换成神经网络的参数化和前向计算。

3. DQN 的三个关键机制

DQN 论文(Nature 版本)里最重要的改进有三个:神经网络函数逼近、经验回放、目标网络。下面逐一分析每个机制解决的问题。

3.1 神经网络函数逼近

DQN 的网络结构很直接:输入是状态的特征向量,输出是每个动作的 Q 值。对于 CartPole 这种输入只有 4 个连续值的小环境,两到三层全连接网络就足够;对于 Atari 游戏,输入是 84x84 的灰度图像,则需要卷积神经网络。

用神经网络做 Q 值逼近,最大的好处是泛化。表格方法里,状态 (0.1, 0.2) 和状态 (0.1, 0.3) 是完全没有关联的两个格子;但在神经网络里,它们会映射到相近的特征空间,输出自然接近。这是 DQN 能应对连续和高维状态的根本原因。

代价是训练不稳定。每次梯度下降都在改动网络的整体参数,可能出现“学会一个状态、忘了另一个状态”的灾难性遗忘。所以单靠网络替代 Q 表远远不够。

3.2 经验回放(Experience Replay)

Q-Learning 从环境中按时间顺序采样,连续样本之间存在强烈的相关性。例如在 CartPole 中,第 1 帧和第 2 帧的画面非常相似,如果直接用这些连续样本做梯度更新,梯度方向会偏向最近的经历,导致训练震荡。

经验回放的做法是:把每次交互产生的转移样本(s, a, r, s', done)存进一个固定大小的缓冲区,训练时从中随机采样一个小批量。这带来两个好处:

  1. 打破样本相关性。随机采样让每个 batch 里的样本尽量来自不同的时间点,梯度估计更接近独立同分布。
  2. 提高样本利用率。一条样本可以被多次使用,尤其适合样本采集成本高的真实环境。

经验回放并不复杂,代码上就是一个带有最大容量的队列,满员后自动丢弃旧样本。但它是 DQN 稳定训练的基础。

3.3 目标网络(Target Network)

如果网络同时扮演两个角色——“预测当前 Q 值”和“计算 TD 目标”,就会出现一个问题:每次更新参数后,TD 目标也随之变化。这相当于一个移动靶,模型追着不断漂移的目标训练,很难收敛。

DQN 的解决办法是维护两份网络参数:

  • 策略网络policy_net:负责输出当前动作的 Q 值,也承担梯度更新。
  • 目标网络target_net:负责计算 TD 目标中的max Q(s', a'),参数每隔固定步数才从策略网络复制一次。

训练过程中,策略网络只更新自身参数,目标网络保持相对稳定。这样 TD 目标在一段时间内是固定的,训练变成了一个稳定的回归问题。目标网络不是只要更新越频繁越好,太频繁会重新引入目标漂移,太少则会让目标过时,一般每 50 到 200 步同步一次是比较常用的选择。

把三个机制合起来看,DQN 的每一步更新都遵循这个流程:

从经验缓冲区随机采样一个 batch 用策略网络计算当前状态动作的 Q 值 用目标网络计算下一状态的最大 Q 值,构造 TD 目标 计算两者之间的均方误差或 Huber 损失 反向传播更新策略网络 每隔 N 步,将策略网络参数复制给目标网络

4. 环境准备与依赖安装

动手实现之前,先把运行环境准备好。本文的示例代码基于 Python 和 PyTorch,环境使用 Gymnasium。这里特别提醒一点:

如果你过去习惯使用gym,请注意新版项目已经改名为gymnasium,API 也有一些变化。新版env.step()返回五个值:observation、reward、terminated、truncated、info,其中terminated表示智能体是否因达到终止条件而结束,truncated表示是否因超过步数上限或环境超时而被截断。判断 episode 是否结束,需要把两者取或。

安装依赖只需两条命令:

# 创建虚拟环境(推荐) python -m venv rl_env source rl_env/bin/activate # Windows 下使用 rl_env\Scripts\activate pip install torch gymnasium matplotlib numpy

安装完成后,可以用下面的代码验证环境是否可用:

import gymnasium as gym env = gym.make("CartPole-v1") print("观测空间:", env.observation_space) print("动作空间:", env.action_space)

CartPole-v1 的观测空间是 4 个连续值:小车位置、速度、杆子角度、角速度;动作空间只有两个:向左和向右。目标是通过左右移动小车,让杆子尽量保持垂直。每个 step 存活奖励为 1,episode 默认在 500 步时截断。对于 DQN 入门,这个环境是最合适的试验场。

5. DQN 最小实现:从零跑通 CartPole

下面给出一个完整的 DQN 实现,可以保存为dqn_cartpole.py后直接运行。代码刻意保持结构清晰,没有做过度封装,方便你逐行理解。

5.1 定义 Q 网络

import random from collections import deque import gymnasium as gym import matplotlib.pyplot as plt import numpy as np import torch import torch.nn as nn import torch.optim as optim class DQNNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_size=128): super(DQNNetwork, self).__init__() self.fc1 = nn.Linear(state_dim, hidden_size) self.fc2 = nn.Linear(hidden_size, hidden_size) self.fc3 = nn.Linear(hidden_size, action_dim) def forward(self, x): x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) return self.fc3(x)

这个网络的结构非常简单:输入 4 维状态,经过两个 128 维的隐藏层,输出 2 个动作的 Q 值。隐藏层的激活函数用 ReLU,最后一层不接激活,因为 Q 值理论上可以是任意实数。

5.2 经验回放缓冲区

class ReplayBuffer: def __init__(self, capacity): self.buffer = deque(maxlen=capacity) 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 = np.array([b[0] for b in batch], dtype=np.float32) actions = np.array([b[1] for b in batch], dtype=np.int64) rewards = np.array([b[2] for b in batch], dtype=np.float32) next_states = np.array([b[3] for b in batch], dtype=np.float32) dones = np.array([b[4] for b in batch], dtype=np.float32) return states, actions, rewards, next_states, dones def __len__(self): return len(self.buffer)

这里用 Python 标准库的deque实现固定容量队列,容量满了之后最早的经验会自然丢弃。采样时通过random.sample随机选取一小批样本,这正是“打破相关性”的关键。

5.3 DQN Agent

class DQNAgent: def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99, batch_size=32, buffer_size=10000, target_update_freq=100): 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.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(), lr=lr) self.loss_fn = nn.SmoothL1Loss() # Huber 损失,比 MSE 对异常值更稳健 self.memory = ReplayBuffer(buffer_size) self.total_steps = 0 def choose_action(self, state, epsilon): """使用 epsilon 贪心策略选择动作""" if random.random() < epsilon: return random.randint(0, self.action_dim - 1) with torch.no_grad(): state_tensor = torch.FloatTensor(state).unsqueeze(0) q_values = self.policy_net(state_tensor) return int(q_values.argmax(dim=1).item()) def store_transition(self, state, action, reward, next_state, done): self.memory.push(state, action, reward, next_state, done) def update(self): """从经验池采样一个 batch,执行一次梯度更新""" if len(self.memory) < self.batch_size: return 0.0 states, actions, rewards, next_states, dones = self.memory.sample(self.batch_size) states_t = torch.FloatTensor(states) actions_t = torch.LongTensor(actions).unsqueeze(-1) rewards_t = torch.FloatTensor(rewards).unsqueeze(-1) next_states_t = torch.FloatTensor(next_states) dones_t = torch.FloatTensor(dones).unsqueeze(-1) # 策略网络输出当前状态动作的 Q 值 q_pred = self.policy_net(states_t).gather(1, actions_t) # 用目标网络计算下一状态的最大 Q 值,构造 TD 目标 with torch.no_grad(): q_next = self.target_net(next_states_t).max(dim=1, keepdim=True).values q_target = rewards_t + self.gamma * q_next * (1 - dones_t) # 计算损失并反向传播 loss = self.loss_fn(q_pred, q_target) self.optimizer.zero_grad() loss.backward() # 梯度的值不宜过大,可以视情况引入梯度裁剪 torch.nn.utils.clip_grad_norm_(self.policy_net.parameters(), max_norm=1.0) self.optimizer.step() self.total_steps += 1 if self.total_steps % self.target_update_freq == 0: self.target_net.load_state_dict(self.policy_net.state_dict()) return loss.item()

这里有一个非常值得注意的细节:q_target的计算被放进了torch.no_grad()里。因为目标网络的更新频率较低,我们只需要用它计算目标值,不希望梯度经过它传播。否则梯度会同时更新两个网络,目标漂移问题会再次出现。

损失函数选择了SmoothL1Loss,也就是 Huber 损失。它结合了 MSE 在零点附近的平滑性和 MAE 在大误差时的鲁棒性,当 TD 误差很大时不会产生过大的梯度。

5.4 训练主循环

def train(episodes=500, seed=42): env = gym.make("CartPole-v1") agent = DQNAgent(state_dim=4, action_dim=2) epsilon = 1.0 epsilon_min = 0.01 epsilon_decay = 0.995 reward_history = [] avg_reward_history = [] for episode in range(1, episodes + 1): state, _ = env.reset(seed=seed + episode) total_reward = 0 while True: action = agent.choose_action(state, epsilon) next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated agent.store_transition(state, action, reward, next_state, done) agent.update() state = next_state total_reward += reward if done: break epsilon = max(epsilon_min, epsilon * epsilon_decay) reward_history.append(total_reward) avg_reward = np.mean(reward_history[-50:]) avg_reward_history.append(avg_reward) if episode % 20 == 0: print(f"Episode {episode}, Reward: {total_reward}, " f"Avg(last 50): {avg_reward:.2f}, Epsilon: {epsilon:.3f}") if avg_reward >= 450: print(f"收敛条件达成于第 {episode} 个 episode,平均奖励 {avg_reward:.2f}") break env.close() return reward_history, avg_reward_history

训练过程中使用了 epsilon 贪心策略。刚开始 epsilon 设为 1.0,Agent 几乎完全随机探索;随着训练推进,epsilon 按 0.995 的速率指数衰减,逐渐减少随机动作的比例,让 Agent 更多依赖已经学到的策略。这种方式保证了前期有足够多的探索样本,后期有足够高的利用率。

5.5 运行与绘图

if __name__ == "__main__": rewards, avgs = train(episodes=500) plt.figure(figsize=(10, 5)) plt.plot(rewards, alpha=0.4, label="Episode Reward") plt.plot(avgs, label="Avg Reward (last 50)") plt.xlabel("Episode") plt.ylabel("Reward") plt.legend() plt.title("DQN Training on CartPole-v1") plt.savefig("dqn_cartpole.png", dpi=150) print("训练曲线已保存到 dqn_cartpole.png")

运行方式:

python dqn_cartpole.py

CartPole 的状态向量只有 4 维,网络非常小,CPU 就能完成训练,不需要 GPU。这个示例非常适合用来验证你对 DQN 的理解是否正确。

6. 运行结果与收敛判断

训练开始后,你会在终端看到类似这样的输出(具体数值会因随机种子和采样过程略有不同):

Episode 20, Reward: 22, Avg(last 50): 19.50, Epsilon: 0.905 Episode 40, Reward: 41, Avg(last 50): 24.75, Epsilon: 0.820 Episode 60, Reward: 45, Avg(last 50): 31.20, Epsilon: 0.742 Episode 80, Reward: 67, Avg(last 50): 44.35, Epsilon: 0.671

从训练行为上看,前几十轮 reward 会很低,通常只有 20 到 50,说明 Agent 还在大量随机探索。随着 epsilon 衰减和经验积累,reward 会出现阶段性跳升。一个比较典型的现象是:在第 100 到 200 轮之间,Agent 突然学会稳定控制杆子一段时间,reward 迅速升高到 200 以上,之后可能在 200 到 500 之间波动,最终稳定在 450 以上。

收敛判断标准建议看最近 50 个 episode 的平均奖励。CartPole-v1 的 episode 上限是 500,如果你看到平均奖励持续高于 450,说明 Agent 已经非常稳定,训练可以提前结束。代码中也设置了avg_reward >= 450的提前停止条件。

如果训练结束(跑完 500 个 episode)后平均奖励仍然只有几十,不要急着调神经网络结构,优先检查下面几个环节:

  1. epsilon 衰减是否太快,导致后期探索不足;
  2. 经验回放缓冲区容量是否太小;
  3. 目标网络同步频率是否太慢或太快;
  4. 学习率是否设置过大。

为了更严谨地评估最终策略,建议单独写一个评估函数,只用纯贪心策略去玩游戏,不加入任何随机探索:

def evaluate(agent, episodes=20, seed=0): env = gym.make("CartPole-v1") total = 0.0 for i in range(episodes): state, _ = env.reset(seed=seed + i) done = False while not done: action = agent.choose_action(state, epsilon=0.0) state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated total += reward env.close() return total / episodes

评估时把epsilon固定为 0,Agent 始终选择最大 Q 值对应的动作。这样得到的平均奖励比训练日志中的数值更真实地反映策略水平。

7. DQN 常见问题与排查思路

DQN 在入门阶段最常见的挫折不是看不懂代码,而是“代码能跑,但奖励曲线死活不上涨”。下面整理了一份排查表,建议收藏备用。

问题现象可能原因排查方式解决方案
训练曲线一直贴地,reward 极低epsilon 衰减太快,探索不足打印 epsilon 变化,观察后期是否已小于 0.05调大 epsilon_decay,如 0.998
reward 上不去,且震荡剧烈学习率过大或目标网络同步过快观察 loss 是否忽大忽小降低学习率到 1e-4,调大 target_update_freq
Loss 变成 NaN输入或者梯度爆炸检查 reward 和观测是否有异常值加入梯度裁剪,对观测做归一化
训练前期 reward 能到 500,后期反而下降过拟合或 epsilon 过小导致探索消失绘制最近 50 轮平均曲线引入更大的经验池,或调小学习率
目标网络同步后性能明显变差目标网络同步不频繁检查 total_steps 是否正常累加适当调小 target_update_freq
训练非常慢每个 step 都执行 update,但缓冲区还没满打印 len(memory)正常现象,缓冲区满之前不更新即可

在这些问题里,最常见的原因是“经验池里还没有足够多样本就开始更新”,以及“epsilon 衰减太快导致后期一直在利用”。需要记住一个基本原则:DQN 是离线策略算法,它的样本来自过去任意时刻的策略。经验池里的样本多样性才是训练稳定的基础。

另外,如果你发现代码能跑但是所有动作都选同一个,基本可以确定 Q 网络没有学到有效信息。这时候先不要调网络,把奖励曲线和 epsilon 曲线画出来,看看到底是探索不够还是 TD 目标有问题。

8. DQN 最佳实践与工程建议

跑通一个 CartPole 示例只是第一步。如果把 DQN 用在实际项目中,下面这些工程经验值得提前了解。

8.1 超参数设置的基本盘

DQN 对超参数比较敏感,下面是一组在 CartPole 上常用的默认值,可作为起点:

参数推荐值说明
学习率 lr1e-3 到 1e-4过大容易震荡,过小收敛慢
折扣因子 gamma0.99任务越长,gamma 越接近 1
batch_size32 到 256影响梯度估计的稳定性
经验池容量10000 到 100000与环境复杂度和采样成本成正比
target_update_freq50 到 200更新越快,目标变化越快
epsilon_decay0.99 到 0.998需要保证中期还有一定探索
隐藏层宽度64 到 256CartPole 128 足够,复杂任务需要更大

不要同时调多个超参数。每次只改一个,对比训练曲线,才能判断哪个因素真正影响效果。

8.2 输入归一化

CartPole 的四个观测维度数值范围不同,小车位置在-4.8到4.8之间,角度在约-0.42到0.42之间,角速度范围更大。如果不归一化,网络训练初期容易被数值范围大的维度主导。使用环境的observation_space.high和observation_space.low做归一化是一个稳健的工程习惯。

8.3 梯度裁剪

即使有经验回放和目标网络,遇到极端奖励或离群样本时,梯度依然可能爆炸。在每次loss.backward()之后加一行梯度裁剪是成本最低的稳定手段:

torch.nn.utils.clip_grad_norm_(self.policy_net.parameters(), max_norm=1.0)

这一行代码对于复杂环境尤其重要,建议在写 DQN 时直接加上。

8.4 模型保存与加载

训练完成后,需要把策略网络保存下来,方便后续评估或在真实环境中部署:

torch.save(agent.policy_net.state_dict(), "dqn_cartpole.pth")

需要继续训练时,可以直接加载权重再跑:

agent.policy_net.load_state_dict(torch.load("dqn_cartpole.pth")) agent.target_net.load_state_dict(agent.policy_net.state_dict())

注意加载模型后,目标网络也要同步一次,否则目标网络和策略网络的初始参数不一致,训练一开始就会不稳定。

8.5 固定随机种子

强化学习训练本身有大量随机性,环境、经验采样、网络初始化都会影响结果。为了方便对比实验,固定随机种子几乎是必须的:

import random import numpy as np import torch random.seed(42) np.random.seed(42) torch.manual_seed(42)

在gym.make时,也可以给环境指定 seed。这样出现问题时,可以精确复现。

8.6 评估优先于训练

建议在训练循环之外单独维护一个评估函数,每隔固定轮数执行一次纯贪心评估。训练时的高 reward 可能是因为 epsilon 较高导致的偶然结果,评估曲线才是真实策略水平的体现。把评估曲线和训练曲线画在一张图上,能帮你更早发现问题。

9. DQN 的局限与进一步学习方向

到此为止,你已经理解了 DQN 的核心机制,也亲手实现并运行了一个完整示例。但入门的终点,恰恰是深入理解强化学习的起点。DQN 本身有几个非常明显的局限,知道这些局限,也就知道下一步该学什么。

第一个局限是过估计问题。DQN 在计算 TD 目标时使用max Q(s', a'),而最大值操作会引入正向偏差。即使真实的 Q 值相同,噪声也会让最大值偏高。这会导致 Agent 对某些动作产生过高的估计。解决方案是 Double DQN:用策略网络选择最优动作,再用目标网络计算该动作的 Q 值,从而降低过估计。

第二个局限是样本效率低。虽然经验回放提升了样本利用率,但 DQN 本质上还是需要大量交互。在真实机器人、自动驾驶这类采样成本高的场景里,样本效率直接决定了项目可行性。后续的 Prioritized Experience Replay 通过按 TD 误差大小加权采样,让更“意外”的样本得到更多训练机会,这是提升样本效率的有效方向。

第三个局限是只能处理离散动作。DQN 输出的是一组动作的 Q 值,天然适合离散动作空间。但很多真实问题,如机器人控制、无人机飞行,动作本身是连续值。解决连续控制问题的主流算法是 Actor-Critic 家族的 DDPG、TD3、SAC,以及基于策略梯度的 PPO。学习路径上,建议先吃透 DQN 的价值函数逼近思路,再过渡到 Actor-Critic 架构。

如果按学习进度规划,你的下一步可以是:

  1. 实现 Double DQN,在 CartPole 上对比它与标准 DQN 的差异;
  2. 加入 Prioritized Experience Replay,观察训练速度的变化;
  3. 换一个更复杂的环境,比如 LunarLander-v2,测试代码的通用性;
  4. 学习策略梯度方法,理解价值学习和策略学习两条路线的差别。

真正理解 DQN,不在于背下三个机制的名字,而在于当你面对一条不怎么上涨的训练曲线时,能快速判断出问题出在经验采样、目标计算、还是探索策略。这也是从“会跑代码”走向“会调试算法”的关键一步。建议你先把今天的 CartPole 示例跑熟,再动手修改一个超参数,观察它对收敛速度的影响。有了这个基础,后续的强化学习进阶之路会顺畅很多。

相关新闻

胡广书《数字信号处理》Matlab实战代码包解析

胡广书《数字信号处理》Matlab实战代码包解析

简介&#xff1a;本资源是胡广书《数字信号处理理论、算法与实现》&#xff08;清华大学出版社&#xff0c;2003年&#xff09;配套的完整MATLAB实践支撑包&#xff0c;面向高校电子、通信、自动化等专业本科生及研究生&#xff0c;助力理解经典DSP核心概念与算法实现。压缩包含…

2026/10/20 2:32:33 阅读更多 →
MapReduce Shuffle全流程解析:从环形缓冲到归并调优

MapReduce Shuffle全流程解析:从环形缓冲到归并调优

说起Hadoop里的Shuffle过程&#xff0c;很多人的第一反应是“中间那一段黑盒”。Map输出怎么就到了Reduce输入手里&#xff0c;中间到底经历了什么&#xff0c;为什么会卡&#xff0c;为什么会OOM&#xff0c;为什么调了一堆参数还是慢——这些问题我入行前两年也一直没彻底搞明…

2026/10/20 2:32:33 阅读更多 →
PHP模板注入SSTI实战:Smarty与Twig识别及绕过

PHP模板注入SSTI实战:Smarty与Twig识别及绕过

1. 项目概述&#xff1a;一次真实的PHP模板注入攻防现场还原你有没有遇到过这样的情况&#xff1a;前端页面明明只是展示用户昵称、头像、欢迎语&#xff0c;但后端返回的HTML里却悄悄执行了system(id)&#xff1f;或者调试时随手在模板里写了{{ 7*7 }}&#xff0c;结果页面直接…

2026/10/20 2:32:33 阅读更多 →

最新新闻

投机推理:突破大模型自回归瓶颈的并行验证加速技术

投机推理:突破大模型自回归瓶颈的并行验证加速技术

1. 大模型推理的“车堵一条道”困局老做推理优化的朋友都有过这种体验&#xff1a;GPU 明明算力爆炸强&#xff0c;但每次生成一个 token 都得几毫秒甚至几十毫秒&#xff0c;batch 稍微大一点&#xff0c;延时就像坐火箭一样往上蹿。一开始我们都骂“模型太大了”&#xff0c;…

2026/10/20 7:13:55 阅读更多 →
Spring Boot+微信小程序汽车用品商城开发:从车型适配到支付回调

Spring Boot+微信小程序汽车用品商城开发:从车型适配到支付回调

先说句实在话&#xff1a;汽车用品这个品类&#xff0c;看着和普通电商差不多&#xff0c;但等你真把“Spring Boot 微信小程序”这套销售系统落地时&#xff0c;会发现一堆让人挠头的细节——车型适配怎么做、库存怎么拆、微信支付回调怎么验签、审核类目怎么过。这篇文章不聊…

2026/10/20 7:13:55 阅读更多 →
Delta机器人正逆运动学C#实现:高精度实时解算模块

Delta机器人正逆运动学C#实现:高精度实时解算模块

简介&#xff1a;本资源是一套面向机器人控制开发者与自动化专业学习者的Delta并联机器人运动学算法C#实现工程&#xff0c;聚焦高速拾放场景下的正向/逆向运动学求解这一核心难点。项目以Visual Studio解决方案形式组织&#xff0c;含41个文件&#xff0c;涵盖6个核心C#源码&a…

2026/10/20 7:13:55 阅读更多 →
无语言基础模型如何实现通用视觉异常检测?原理与落地

无语言基础模型如何实现通用视觉异常检测?原理与落地

这两年做计算机视觉落地&#xff0c;特别是工业视觉相关的项目时&#xff0c;我几乎绕不开一个词&#xff1a;异常检测。不管是手机外壳上的划痕、布料上的断纱&#xff0c;还是电路板上的漏焊&#xff0c;本质都是同一件事——在绝大多数正常样本中&#xff0c;把分布外的那一…

2026/10/20 7:13:55 阅读更多 →
Chatterbox TTS实战:开源语音合成全家桶部署与调优指南

Chatterbox TTS实战:开源语音合成全家桶部署与调优指南

去年底我就在关注开源TTS圈子的动向&#xff0c;当时看到Chatterbox TTS这个项目时&#xff0c;第一反应是"又一个语音合成模型"&#xff0c;但真正部署完、跑了几天推理之后&#xff0c;我改变了看法——这玩意儿不是单个模型&#xff0c;而是一整套开箱即用的语音合…

2026/10/20 7:13:55 阅读更多 →
微服务商城对接支付宝支付:独立支付服务与异步回调实战

微服务商城对接支付宝支付:独立支付服务与异步回调实战

1. 支付服务在微服务架构里的定位做电商项目&#xff0c;前面把商品、库存、订单、用户这些服务都搭起来之后&#xff0c;早晚要面对一个绕不开的环节——支付。我这次在基于Vue和SpringCloudAlibaba的微服务商城项目里&#xff0c;把支付单独拆成了一个平台服务&#xff0c;专…

2026/10/20 7:12:54 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →