news 2026/10/23 5:21:36

从Q-learning到DQN:深度强化学习的核心原理与实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从Q-learning到DQN:深度强化学习的核心原理与实践

1. 从一个求最大的游戏说起:DRL到底在学什么

第一次接触深度强化学习的人,往往会被一大堆概念砸晕。策略、状态空间、奖励函数、价值迭代、策略梯度、经验回放……每个术语看起来都对,串在一起又不知道它们在解决什么问题。我一开始也是这样,直到我把整个 DRL 想明白成一件事,才豁然开朗:它本质上就是在学一个"怎么选才能让长期回报最大"的决策函数。

你看,强化学习的套路很简单。一个智能体(agent)在某个状态(state)下,做出一个动作(action),环境返回一个奖励(reward),然后进入下一个状态。循环往复。这不是什么新鲜玩意儿,股票交易、游戏对战、机器人控制、推荐系统,本质上都是这么个闭环。真正麻烦的是:动作的后果往往不是立即兑现的,而是延迟显现的。你今天吃多了,明天不会立刻胖,但三个月后你会看到后果。智能体最大的难题就是在这种延迟回报下,搞清楚"到底哪一步走对了"。

这时候就需要值函数登场了。值函数回答的问题非常朴素:站在当前这个位置,我到底有多赚?如果把强化学习比作爬山,策略是"往哪走"的指南针,那值函数就是一张"海拔图"——告诉你每个山头有多高。有了这张海拔图,指南针才能规划出最优路线。这也是为什么我想先写清楚值函数、贝尔曼方程和达尔文进化式的 DQN,它们共同构成了 DRL 的地基。

这篇文章面向两类人。一类是刚接触强化学习、打算从零搭建认知框架的新手;另一类是已经跑过一些代码,但面对"为什么 DQN 要搞目标网络""为什么要经验回放"这类问题依然说不清原理的开发朋友。我会尽量用贴近实际的语言把概念讲透,配合最小可运行的代码示例,让你对从 Q-learning 到 DQN 的这条演进线形成完整直觉。

2. 值函数与贝尔曼方程:强化学习的数学引擎

2.1 状态值函数和动作值函数,到底在算哪笔账

值函数分两种。第一种叫状态值函数,记作 V(s),它衡量的是"从状态 s 出发,按照某个策略走下去,预期能拿到的累计奖励总和"。注意关键词:按照某个策略。同一个状态,换一个策略,值就不同。你站在山顶,如果策略是"直接跳下去",那值可能很低;如果策略是"沿缆车下山",值就高很多。

第二种叫动作值函数,记作 Q(s, a),它衡量的是"在状态 s 下先执行动作 a,之后按照策略继续走,预期能拿到的累计奖励总和"。它比 V(s) 多了一层"动作的条件",所以更像一张"状态-动作对照表",这也是 Q-learning 里那个 Q 的由来。

这两个函数的核心差别,用一句话总结:V 问的是"这里好不好",Q 问的是"在这里做某件事好不好"。做决策的时候,光知道 V 还不够,因为你在状态 s 下至少得选一个动作才能前进;只有 Q 能直接告诉你每个动作的收益预期,你挑 Q 值最大的那个动作执行就行。这也是为什么在控制任务中,Q 函数更常用。

2.2 贝尔曼方程:把一个长期问题拆成递归问题

现在问题来了:怎么算这些值函数?

如果从头计算"从这个状态走到终点的累计奖励",你得遍历所有可能的轨迹,这在稍微复杂一点的环境里就是天文数字。贝尔曼方程的精妙之处在于,它把"长期累计"拆成了"即时奖励 + 下一步的期望值",硬生生把一个无穷长的链条变成了一步的递归:

V(s) = R(s) + γ * V(s')

用大白话讲:当前状态的价值 = 当下拿到的奖励 + 折扣后的下一状态价值。为什么能这么拆?因为"从当前状态出发的长期回报"和"下一步状态的长期回报"在本质上是同一类问题,只是状态变了。这种自我指涉的递归形式,是强化学习所有算法的理论根基。

Q 函数也有同样的递归形式:

Q(s, a) = R(s, a) + γ * max(Q(s', a'))

这个式子更关键,它引入了 max 操作。什么意思呢?智能体在下一步会做一个动作,我们假设它会选未来收益最大的那个动作,这就是最优价值函数的定义。有了这个递归式,理论上我们就能从终点逐步往回推,算出每个状态的价值,这就是所谓的动态规划思想。但在实际情况中,环境的状态转移概率我们往往不知道,而且状态空间太大,根本没法从终点往回推,于是才演化出了一套靠采样驱动的学习方法。

2.3 折扣因子 γ:为什么未来的钱不如现在的钱值钱

贝尔曼方程里的 γ(gamma)是个 0 到 1 之间的数,叫折扣因子。它的作用很多人理解成"让未来收益打个折",但这个折扣不是随便拍的,它背后有几个实打实的考虑。

第一是规避不确定性。现实里你的模型是近似提供的 PPT,未来的状态和奖励都有噪声,距离越远,预测越不可靠,所以遥远的收益不应该和眼前的收益一样被信任。第二是数学上的完备性。如果任务没有明确的终点(这种任务叫持续任务),累计奖励可能无限增长,没法收敛;有了 0<γ<1 的折扣,无穷级数就能收敛到一个有限值。第三是行为上的引导。γ 越接近 1,智能体越"高瞻远瞩";γ 越小,它越"急功近利"。

在实际项目里,γ 怎么选?游戏类任务通常取 0.95~0.99,因为游戏往往有长达几百步的时序依赖。如果是推荐系统这种即时反馈强的场景,0.9 就够用了。记住一句话:γ 是你对"远见"的定价,不同的定价会直接改变控制策略的行为风格。

3. 从 Q-learning 到 DQN:函数近似的关键一跃

3.1 表格法的天花板:状态一多,表就炸了

理论讲完了,落到实现上。经典的 Q-learning 把 Q 函数存成一张表,行是状态,列是动作,更新公式长这样:

Q(s, a) ← Q(s, a) + α * (R(s, a) + γ * max(Q(s', a')) - Q(s, a))

新Q = 旧Q + 学习率 × (目标值 - 旧Q)

括号里那一坨叫什么?叫时序差分误差(TD error)。它衡量的是"当前估计"和"实际看到的奖励 + 下一步最优估计"之间的差距,然后沿着差距的方向修正一点。这是一个非常漂亮的增量式算法,不需要环境模型,只需要不断采样试错,就能逐渐逼近正确的 Q 表。

但问题出在"表"字上。表格能覆盖的状态-动作对是有限的。一个国际象棋棋盘,状态数大约是 10 的四十多次方;一个视频游戏,屏幕像素组合出来的状态更是天文数字。你想把这么多状态全部列成一张表,内存直接报警,而且大部分状态你可能永远都访问不到。这就是表格法最致命的短板:它不懂泛化。

3.2 函数近似:用参数化模型代替查表

既然查不了表,那就换个思路——用一组参数来"拟合"Q 函数。这就是函数近似:把 Q(s, a) 从一张表,替换成 Q(s, a; θ),其中 θ 是一组可学习的参数。线性回归是一种简单的近似,神经网络是更强大的近似器。输入是状态,输出是每个动作的 Q 值估计,参数用梯度下降来更新。

这种做法的本质是什么?是压缩和泛化。表里存的是每一个具体状态的独立估值,参数化模型存的是"状态的规律"。打个比方:表格法像你背联系人电话本,每加一个号码你记一行;函数近似像你记住了"区号+号码"的规律,陌生号码你也能猜个八九不离十。这就是深度强化学习相对传统强化学习最大的突破——把"记住"变成了"理解"。

但函数近似也带来代价:非稳定性。表格法更新一个格子不影响其他格子;参数模型更新一组参数,可能同时影响所有状态的输出。这就像你调一个旋钮,整个房间的音量都在变,很可能调完一个地方,另一个本来好的地方反而坏了。

3.3 DQN 的三员大将:回放、固定目标、硬编码

2015 年 DQN(Deep Q-Network,深度 Q 网络)在游戏任务上惊艳登场,核心思想就是"用神经网络做 Q 函数的近似器 + 经验回放 + 目标网络",这三板斧每一刀都切在函数近似不稳定性的要害上。

第一板斧叫经验回放(Experience Replay)。把交互产生的样本(当前状态、动作、奖励、下一状态)存进一个循环缓冲区,训练时随机抽一批小批量来更新网络。这解决了什么问题?两个。一是打破样本间的相关性。强化学习的样本是时序串行的,前后高度相关,如果按顺序更新,网络会被"牵着走";随机抽取能打乱时序,让样本独立同分布。二是样本复用。一条经验能被反复学习,数据利用率大幅提升。这也是为什么同样的模型,配上回放缓冲区,训练速度能快好几倍。

第二板斧叫目标网络(Target Network)。我们算 TD 目标时,用的是 R + γ * max(Q(s', a')),如果这个目标本身就是当前的 Q 网络算出来的,那么它每一轮都在变,相当于你在追赶一个不断移动的箭靶,网络很难收敛。DQN 的做法是维护一份"冷冻副本"——目标网络,它的参数每隔若干步才从主网络同步一次。这样在一段时间内,TD 目标是相对稳定的,主网络的更新就有了明确的"方向感"。

第三板斧是把回报压缩到固定区间。原版 DQN 把奖励 clip 到 [-1, 1],看似简单粗暴,其实非常聪明。因为不同游戏的奖励尺度千差万别,有的奖励是几百,有的只有零点几,如果不用统一的尺度,梯度大小就没法控制,训练极不稳定。压缩之后,不管游戏多复杂,误差信号的大小始终有界,算法稳定性大幅提升。你在自己项目里复现 DQN 时,即使不完整复刻这一步,也要考虑对奖励做归一化。

4. 动手实现一个最小 DQN:把纸面公式跑起来

理论说了这么多,得写点能跑的东西。下面我用一个极简的 DQN 实现,把手上的概念串起来。注意,这不是一个工程级的完整框架,而是一个便于你观察核心机制的教学实例。

4.1 环境与网络定义

我选一个很简单的离散控制环境:状态是低维浮点向量,动作是离散的(只能往上或往下)。你手头没有对应环境也没关系,核心代码逻辑是通用的,能直接迁移到任何离散动作空间的任务。

import random import numpy as np import torch import torch.nn as nn import torch.optim as optim from collections import deque class QNetwork(nn.Module): def __init__(self, state_dim, action_dim, hidden_dim=32): 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)

注意这里的输出层:输出维度等于动作数量。神经网络一次性给出所有动作的 Q 值,而不是输入一个动作输出一个值。这比逐动作预测高效得多,而且在选择最优动作时,直接取输出向量的最大值索引就行,省掉了遍历动作的循环。

4.2 经验回放缓冲区的实现

经验回放本身不难,难在理解它为什么要这样设计。缓冲区就是一个 deque,设置了最大容量,满了就会挤出旧经验。这本身是个先进先出的队列,但采样时我们完全随机抽取,跟顺序无关。

class ReplayBuffer: def __init__(self, capacity=10000): 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) state, action, reward, next_state, done = zip(*batch) return ( torch.FloatTensor(state), torch.LongTensor(action).unsqueeze(1), torch.FloatTensor(reward).unsqueeze(1), torch.FloatTensor(next_state), torch.FloatTensor(done).unsqueeze(1) ) def __len__(self): return len(self.buffer)

我特别说一下done这个字段。很多人在写 DQN 时会忽略它,导致训练出来的智能体表现诡异。想想看:如果下一步是终止状态,那它后面就没有任何未来回报了,TD 目标应该是 R 本身,而不是 R + γ * max(Q(s', a'))。如果不特殊处理终止状态,网络就会"幻想"出一个不存在的未来收益。这个小细节直接影响训练质量,务必在计算目标时乘上 (1 - done):

target = R + γ * (1 - done) * max_Q_next

4.3 训练主循环:探索、采样、更新

接下来是核心的训练循环。我加了三段跟前面理论一一对应的代码逻辑:选取动作时用 ε-贪心策略平衡探索与利用;训练时从回放缓冲区随机采样;周期性地把参数拷贝到目标网络。

def select_action(q_net, state, epsilon): if random.random() < epsilon: return random.randrange(action_dim) with torch.no_grad(): state_tensor = torch.FloatTensor(state).unsqueeze(0) q_vals = q_net(state_tensor) return q_vals.argmax().item() def soft_update target def update(q_net, target_net, optimizer, batch): state, action, reward, next_state, done = batch q_value = q_net(state).gather(1, action) # 当前估计 with torch.no_grad(): max_q_next = target_net(next_state).max(1, keepdim=True).values target = reward + 0.99 * (1 - done) * max_q_next loss = nn.MSELoss()(q_value, target) optimizer.zero_grad() loss.backward() optimizer.step() # target 网络同步 def update_target(q_net, target_net): target_net.load_state_dict(q_net.state_dict())

这里最关键的一行是gather(1, action)。它做的事情是:从 Q 网络的输出里,把里面对应动作的那个 Q 值提取出来。比如网络输出了 [1.2, 0.8],动作是 0,那就取 1.2;动作是 1,就取 0.8。这一步把"网络对状态的完整预测"和"实际执行的动作"对应起来,不难,但很容易写错。写错的话,梯度就不对了,训练出来的网络会在后台半死不活地乱飘。

4.4 训练循环里的那几个超参数怎么定

主循环的逻辑不复杂,但超参数很多时候决定了训练成败。我把我常用的初始配置放出来,作为一个安全的起点:

超参数推荐值理由
学习率 lr1e-3过大容易震荡,过小收敛太慢
折扣因子 γ0.99任务长度几百步时,长期回报占比合适
ε 初始值1.0初期完全随机探索
ε 衰减率0.995(每步)探索率逐步降低,让智能体逐渐"收心"
ε 最小值0.01保留一点随机性,防止彻底保守
回放缓冲区大小10000需要足够多样化的样本,又不能太老
批次大小32兼顾梯度稳定性和内存开销
目标网络更新频率每 500 步太频繁等于没有目标网络,太慢可能导致目标过时

ε 衰减我特别提一句。你可以把 ε 理解为智能体的"好奇心",初始时好奇心拉满,什么动作都试;随着学习推进,好奇心逐渐收敛,行为越来越依赖已有的 Q 估计。但别把它衰减到 0,留一个 0.01 的尾巴,防止智能体永远走老路、失去发现新机会的可能。这种"留尾巴"的思想在强化学习里随处可见,本质是防止过早收敛到次优解。

4.5 一个训练里常被忽略的动作:目标网络参数延迟同步

我见过很多新手把目标网络当成"每隔 N 步直接把主网络参数复制过去",这样没问题。但还有个更平滑的变种叫软更新(soft update):

θ_target ← τ * θ_main + (1 - τ) * θ_target

也就是每次训练一小步时,目标网络的参数朝主网络的方向挪一小点。τ 通常取 0.005。硬更新简单粗暴,软更新更平滑,训练曲线一般也更稳。我个人在项目里偏好在训练早期用硬更新(每 1000 步一次),让目标网络快速跟上;训练后期切到软更新,减缓震荡。当然你也可以一刀切只用软更新,它同样能跑得很好,只是调参时注意 τ 别太大,否则目标网络跟着主网络一起飘,问题就回来了。

5. 常见问题与排查技巧实录

这一段是实操经验的精华所在,都是我踩过的坑、或者帮别人调代码时反复见到的问题。先看两张最能说明问题的表,后面我挑关键的展开讲。

5.1 训练不收敛的四张"体检报告单"

现象可能原因排查方向
Q 值一路飙升,几万步后变成天文数字奖励未归一化 / 目标网络未固定 / 学习率过大检查奖励尺度,检查目标网络同步
策略学了也白学,永远在一个动作附近打转探索率衰减过快 / 奖励太稀疏调低 ε 衰减,增加奖励塑形
训练前几百步涨得很快,之后突然崩盘缓冲区里混入了过旧且冲突的经验检查回放缓冲区大小,清空后重训
表现平平,换了随机种子结果差很多网络初始化影响 / 超参数鲁棒性差多跑几个种子,找方差大的超参

5.2 梯度方向是对的,为什么不收敛?

这是 DQN 新手最大的困惑:公式都写对了,损失也在下降,但智能体水平就是上不去。这个现象的根源往往不在单个更新步骤,而在目标的不一致性。想想看,DQN 的训练目标不是固定的标签,而是由当前策略产生的新样本。策略变了,样本分布就变了,网络学到的"预期"也随之变化。这形成了一个闭环,环里任何一环不稳定,整个训练就会来回震荡。

解决这个问题,最直接的手段就是前面反复提到的经验回放和目标网络。如果这两样都做了还是不稳定,我的经验是:先检查奖励归一化和梯度裁剪。torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)这一行代码能避免极端梯度带来的参数爆炸,便宜又好用。

5.3 探索和利用的平衡怎么掌握?

探索多了,智能体像个无头苍蝇,浪费大量时间在无效动作上;探索少了,它又容易困在局部最优里出不来。我常用的做法是动态调节 ε:训练初期设 1.0,大量随机探索;等每一局的平均回报开始上升时,再加快 ε 的衰减。

但比 ε 衰减更重要的,是奖励函数的设计。我强烈建议你在动手调参之前,先花时间审视你的奖励函数。奖励过于稀疏,智能体可能训练几万步都没见过一次正奖励,梯度信号直接消失;奖励过于密集,又可能诱导它走捷径。强化学习领域有一句老话:"Reward is what you get, not what you want."你设计的奖励,必须精确反映"你希望智能体达成的最终行为",而不是"你猜测能引导它达成目标的行为"。这个差别,调试之后你会体会很深。

5.4 亲测有效的调参顺序

遇到训练效果差,不要一股脑把所有超参数都试一遍,那样你根本没法归结出哪个参数起的作用。我推荐这个顺序:

先固定网络结构(两层隐藏层、每层 32 到 64 个神经元就够用),把奖励归一化做好,确认目标网络参数能正常同步。然后调 γ 和学习率,一个控制长远眼光,一个控制更新步幅,这两个定下来之后,整个训练的大框架就稳了。最后才去细化 ε 的衰减策略和回放缓冲区的大小。缓冲区太小,样本多样性不够;太大,老样本和新策略完全不匹配,一样是噪声。一般情况下,缓冲区容量设成单局步数的 10 到 50 倍,是一个经验法则。

提示:如果你训练到一半发现 Q 值和平均回报一起持续上升,但测试时策略却很差,先别急着怀疑网络结构,去检查一下测试环境里有没有引入训练时没有的随机因素。评估环境的不一致,比模型本身的 bug 更容易掩蔽问题。

6. 走上 DRL 这条路,我踩过最值的一跤

从 Q-learning 到 DQN 的演进,说到底是"从查表到理解规律"的演进。Q-learning 用一张表记住状态的价值,DQN 用一组参数泛化状态的价值。理解了这一跃,后面所有的进阶算法——Double DQN 缓解 Q 值过估计,Dueling DQN 拆分状态价值和动作优势,优先经验回放重新分配样本权重——全都是围绕"如何让函数近似更准、更稳"展开的修补。

我最后的体会是:强化学习跟监督学习最大的不同,在于它没有一个固定不变的"正确答案"。你训练一个图像分类器,标签是死的;你训练一个智能体,目标是自己定义出来的,数据是自己跑出来的。所以在 DRL 里,调试的耐心和系统思维比数学直觉更稀缺。我见过不少人在第一步就卡住,不是因为他们不懂贝尔曼方程,而是忘了"DQN 的原理拆开看只有三步:采样、回放、更新"。

如果你手头已经有了一个 DQN 能稳定跑通的小任务,下一步我建议你做一个实验:删掉经验回放,看看训练曲线会发生什么变化;再把目标网络去掉,看看 Q 值会不会越涨越离谱。亲手触碰到那股"崩坏"的滋味,你对这三个组件背后的动机,会比看任何教程都理解得更深刻。这就是 DRL 这条路上的第一道分水岭,迈过去之后,后面的 Double DQN、PPO、SAC 都会亲切得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 5:21:34

C++单元测试实践指南:框架选型与设计模式

1. 为什么C项目需要单元测试在C项目开发中&#xff0c;单元测试常常被忽视&#xff0c;但它却是保证代码质量最有效的手段之一。我经历过多个大型C项目&#xff0c;发现没有良好单元测试的项目后期维护成本往往是指数级增长的。一个典型的例子是&#xff1a;某次修改看似简单的…

作者头像 李华
网站建设 2026/10/23 5:21:07

红外视频瞳孔检测精度从10%到99.8%:Hough-Contour算法实战

简介&#xff1a;这份资源面向图像处理与计算机视觉领域的研究人员和工程师&#xff0c;针对红外视频中瞳孔直径检测精度不足的问题&#xff0c;给出了一套结合Hough变换与轮廓匹配的精确检测算法方案。算法先对每帧图像灰度化并滤波去噪&#xff0c;再用改进的Hough梯度法初步…

作者头像 李华
网站建设 2026/10/23 5:21:06

基于LSTM的员工离职率预测:HR数据分析实战全流程

做HR数据分析的朋友&#xff0c;几乎都遇到过这样一个问题&#xff1a;业务方跑过来问“你能不能预测一下&#xff0c;哪些员工最近有离职倾向&#xff1f;”这种需求我刚接到的时候第一反应是拿逻辑回归直接开干&#xff0c;但试过之后发现效果一般。真正让我把预测准确率拉起…

作者头像 李华
网站建设 2026/10/23 5:19:36

知识蒸馏实战:从温度系数到软标签,把大模型凝练成小模型

1. 项目概述1.1 知识蒸馏到底是什么做模型部署的同行&#xff0c;对知识蒸馏&#xff08;Knowledge Distillation&#xff09;这个名字应该不陌生。很多人第一次接触它&#xff0c;脑海里冒出来的画面是“大模型当老师&#xff0c;小模型当学生”&#xff0c;这个比喻虽然通俗&…

作者头像 李华
网站建设 2026/10/23 5:19:03

机器视觉工程落地实战:从成像选型到缺陷检测的完整指南

机器视觉这几年是真的火&#xff0c;但也是真的被误解得厉害。很多刚接触工程应用的开发者觉得“机器视觉不就是调用个算法库、跑个模型嘛”&#xff0c;可真到了产线上&#xff0c;早就做过演示方案的团队照样会翻车。我见过不少号称“识别精度98%”的项目&#xff0c;落地半年…

作者头像 李华
网站建设 2026/10/23 5:17:58

微型注释与自我监督学习在医学影像中的临床落地

简介&#xff1a;本资源为MICCAI 2024国际会议论文集第XI卷正式出版物&#xff08;Springer LNCS 15011&#xff09;&#xff0c;聚焦医学图像计算与计算机辅助干预前沿突破&#xff0c;面向医学影像研究人员、AI医疗算法工程师及跨学科临床技术团队。内容系统呈现自我监督学习…

作者头像 李华