ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

强化学习入门:贝尔曼方程、DQN与经验回放实战拆解

强化学习入门:贝尔曼方程、DQN与经验回放实战拆解 强化学习这四个字这几年被提得太频繁了从下棋到机械臂抓取从推荐排序到水下航行器控制到处都能看到它的影子。但真正把 Course3-Week3 这一周的内容从头到尾啃一遍你会发现很多网上讲得玄乎的东西内核其实就几个概念反复转回报、折扣因子、策略、状态动作价值函数、贝尔曼方程再加上一个把表格换成神经网络的工程技巧。这一篇我想干的事很简单把这一周课程里的核心知识点拆开配上我自己跑代码时踩过的坑讲清楚强化学习到底在算什么、为什么这么算、以及落到 Python 里该怎么写。适合刚入门想建立框架的朋友也适合学过一遍但总觉得“懂了又没完全懂”的朋友拿来对照。全程不堆公式吓人能上代码的地方就上代码能上表格的地方就上表格。1. 强化学习到底在解决一个什么问题1.1 从一个火星车例子说清楚 RL 的基本框架课程里用一个火星车的例子开场我觉得这个例子比任何定义都好用。想象有一台火星车停在一条线上线上有若干个位置车可以往左走也可以往右走。某些位置埋着有价值的矿石走到那里就能拿到对应的奖励然后这一轮任务结束。车不知道地图它只能通过不断尝试慢慢摸清楚“在哪个位置、往哪边走、最后能拿到多少好处”。这个场景几乎包含了强化学习的全部要素。车当前所在的位置叫状态state往左或往右这个决定叫动作action走到矿石位置拿到的分数叫奖励reward。车每一次决策后环境会把它带到新的状态并给它一个即时反馈。整套“状态、动作、奖励、下一状态”的循环就是强化学习和环境交互的基本节奏。和我们熟悉的监督学习相比最大的差别在于没有标准答案。监督学习里每张图片都标好了“这是猫”模型只要逼近标签就行。而强化学习里没人告诉你“在位置 4 应该往左走”你只知道最后拿到的总分。这个延迟奖励的特性是强化学习所有难点和魅力的根源。你要在几十步甚至几百步之后才知道当初那个决定对不对这种“credit assignment信用分配”问题正是价值函数要解决的核心。我个人的体会是刚开始学的时候别急着上神经网络先把这种“小格子、有限状态”的问题在纸上或者用几行 Python 跑通把回报算明白、把策略迭代看明白后面换深度网络时才不会一头雾水。1.2 监督学习、无监督学习和强化学习的分工很多朋友一上来就纠结“强化学习是不是比深度学习更高级”这其实是个伪命题。它们解决的是不同层次的问题我习惯用一张表来对照维度监督学习无监督学习强化学习输入特征 标签只有特征状态 奖励反馈每一步都有正确答案无反馈延迟、稀疏、可能有噪声目标拟合映射找结构最大化长期累积回报数据来源静态数据集静态数据集与环境交互产生典型任务分类、回归聚类、降维控制、决策、博弈关键在于数据是不是自己产生的。监督学习的数据集是别人给你的而强化学习的数据是智能体自己“玩”出来的你选的动作会改变你接下来看到的数据分布。这就带来一个很微妙的后果如果你一开始策略很差收集到的数据也差模型就容易越学越偏。这也是后面要讲经验回放、探索策略的原因。把这层关系理顺你再看“深度强化学习”这个词就不会发怵了——它无非是“用深度网络去拟合强化学习里的某个函数”两套东西是组合关系不是替代关系。1.3 这一周课程在整个学习路径里的位置Course3-Week3 属于整门课的收尾部分前面你已经有了回归、分类、神经网络的基础这一周才正式把“决策”这条线补上。我建议的学习顺序是先理解回报和折扣因子这类“算账”的概念再理解策略和价值函数这类“表示”的概念最后才是 DQN 这类“用网络去逼近”的工程手段。顺序反了就容易出现“代码能跑但不知道在优化什么”的尴尬。这一周内容看着不多但它其实是整门课里概念密度最高的一周几乎每一段都对应一个独立的知识点。我的建议是每读完一小节就停下来自己复述一遍比如“折扣因子到底在调节什么”能用自己的话说清楚才算真的过了。2. 回报、折扣因子与策略强化学习的三块基石2.1 回报与折扣因子到底在算一笔什么账强化学习里智能体追求的不是某一步的即时奖励而是从当前时刻开始的累积回报return。定义很朴素把未来每一步拿到的奖励加起来但越靠后的奖励要打个折。这个折扣的系数就是折扣因子 γgamma通常取 0 到 1 之间比如 0.9、0.99。为什么一定要打折不能直接全加起来我总结下来有三个现实理由。第一未来的不确定性越远的事情越难预测用一个小于 1 的系数衰减能体现这种不确定性。第二数学上的收敛性如果每一步都有正奖励且无限进行下去不打折的总和会发散到无穷大优化就没法做了。第三行为偏好打折其实是在告诉智能体“能早拿的奖励尽量早拿”这符合很多实际控制任务的需求。拿火星车例子算一下你就明白了。假设折扣因子 0.9车在某个位置面临往左还是往右的选择往左走三步能到达一个奖励为 100 的矿石往右走两步能到达一个奖励为 40 的矿石。往左的回报约等于 100 乘以 0.9 的三次方大概是 72.9往右的回报约等于 40 乘以 0.9 的平方大概是 32.4。所以哪怕左边的矿石更远靠折扣一算仍然是往左更划算。这就是折扣因子在“远近”和“大小”之间做权衡的方式。注意折扣因子越小智能体越短视越接近 1它越有耐心。同一个任务γ 从 0.9 调到 0.99学出来的策略可能完全不同。2.2 策略把状态映射到动作的那张表策略policy是强化学习里真正要学的东西。它的定义简单到一句话给定一个状态输出该采取什么动作。用符号写就是 π(s) a。如果状态是离散的、数量有限策略可以就是一张查找表每个状态对应一个动作。如果状态是连续的高维向量比如机械臂的关节角度、摄像头的像素策略就得用一个函数来近似通常是神经网络。这里有个容易被忽略的点策略和价值函数是两个东西。策略是“怎么做”价值函数是“这么做值多少”。很多算法是先把价值函数学准再从价值函数里提取出策略比如后面要讲的 Q-learning也有算法直接学策略跳过价值函数比如策略梯度那一类。这一周的重点偏向前者也就是先把“值多少钱”算清楚动作自然就选出来了。我在实际项目里发现把这两个概念分开想调试时思路会清晰很多。当模型表现不好时先问自己是价值估计不准还是我根据价值选动作的方式有问题这两个方向排查的手段完全不一样。2.3 马尔可夫决策过程用一句话概括核心假设强化学习的问题通常被形式化成马尔可夫决策过程MDP。这个名字听着唬人核心假设其实只有一条当前状态包含了做决策所需的全部信息未来只取决于现在和更早的历史无关。这就是所谓的“马尔可夫性”。为什么这个假设重要因为它让我们可以只盯着当前状态来估值不用把整条历史轨迹都记下来。假设不成立的时候比如你只看到一帧画面不知道球的速度方向我们通常的做法是把最近几帧拼起来当作状态人为地“造”出马尔可夫性。一个完整的 MDP 由状态集合、动作集合、状态转移概率、奖励函数、折扣因子这几样东西组成。落到代码里如果你做的是“有模型”的方法就需要显式地知道状态转移概率如果做的是“无模型”的方法比如 Q-learning那只需要能跟环境交互、拿到“下一状态和奖励”就行。大部分实际问题都属于后者因为你根本拿不到精确的转移概率。我把这三块基石的关系理成一句话策略决定怎么走回报和折扣因子定义什么算走得好MDP 规定了你在什么样的世界里走。这三者搭起来剩下的就都是算法层面的工程活了。3. 状态动作价值函数与贝尔曼方程强化学习的心脏3.1 状态动作价值函数 Q(s,a) 到底是什么上一节说策略是 π(s)a但你有没有想过凭什么在状态 s 就选这个 a要回答这个问题我们需要一个能对“某个状态下做某个动作”打分的东西这就是状态动作价值函数记作 Q(s,a)。它的含义是在状态 s 采取动作 a并且之后都按最优方式行动能拿到的期望累积回报。注意这里有两个关键限定词。一是“之后都按最优方式行动”也就是说 Q 值衡量的是这一步动作的潜力而不是随便乱走的结果。二是“期望”因为环境可能有随机性同样的动作不一定每次都给一样的结果所以取的是平均意义下的回报。这里顺带把一个常被问到的概念讲透状态价值函数 V(s) 的作用是什么。V(s) 衡量的是“我就待在状态 s按当前策略走下去能拿多少”。它和 Q(s,a) 的关系非常直接——V(s) 等于在这个状态下对所有可能动作的 Q(s,a) 按当前策略的概率加权求和。如果你用的是贪心策略那 V(s) 就等于 max 的 Q(s,a)。那为什么实际算法里更常用 Q 而不是 V因为光有 V 选不出动作。V 只告诉你这个状态好不好却没告诉你是哪个动作让它变好的。而 Q 每个动作一个值直接比较大小就能选出最优动作这对无模型的算法太方便了。这是我看课程时印象最深的一个设计动机。3.2 贝尔曼方程把“未来”折叠进“现在”Q 函数的定义里含有“未来所有步的回报之和”看起来要算到天荒地老。贝尔曼方程的漂亮之处就是把这个无限求和递归地折叠成一步Q(s,a) R(s) γ · max Q(s, a)用人话翻译一遍在状态 s 做动作 a 的价值等于当下拿到的奖励加上折扣后的、下一状态 s 里最优动作的价值。你看无穷远的未来被“下一状态的最优价值”这一项代表了因为它本身又递归地包含了再下一步。这个等式是几乎所有价值类强化学习算法的地基。它的直觉其实很符合生活一件事值不值得做等于立刻得到的好处加上它把你带到的那个新处境本身的价值。我常常拿它类比“下棋”一步棋的价值等于吃子得分加上走完之后局面的优劣评估。贝尔曼方程给了我们一个迭代求解的思路先随便初始化所有 Q 值然后反复用这个等式去更新直到数值不再大幅变化就收敛到了真实 Q 值。这就是所谓的价值迭代。理解了这一步后面 DQN 里那个“目标值 奖励 γ·max Q(下一状态)”的损失函数就完全不用死记硬背了它就是贝尔曼方程的工程版本。3.3 用一个手算的例子把贝尔曼方程走一遍光看公式容易飘我们拿一个三状态的小例子走一遍。假设有状态 A、B、C从 A 出发有两个动作去 B 或去 C。去 B 的即时奖励是 0到了 B 之后还能继续拿奖励去 C 的即时奖励是 5但 C 是终点到了就结束。折扣因子取 0.5。再假设 B 状态下最优的 Q 值是 10。那么从 A 去 B 的 Q 值等于 0 加上 0.5 乘以 10等于 5。从 A 去 C 的 Q 值等于 5 加上 0.5 乘以 0终点没有后续等于 5。两个动作打平。如果我们把折扣因子改成 0.9去 B 的 Q 值变成 0 加 0.9 乘 10 等于 9此时去 B 明显更好。这个手算过程揭示了一个非常重要的现象折扣因子会改变最优动作的选择。所以调 γ 绝不是随便填个 0.99 就完事它直接决定了智能体是“稳吃眼前小利”还是“冒险博取长远大利”。我在做控制类任务时通常先从 0.9 起步观察策略是否过于短视再逐步上调。再补一个实操经验手算这种小例子对理解收敛现象特别有用。你可以自己写个几行的循环把 Q 值反复套用贝尔曼方程更新然后把每一轮的数值打印出来看着它从乱七八糟慢慢稳定到某个值那种“哦原来收敛是这样”的感觉比看十页推导都管用。4. 从表格到神经网络Deep Q-Learning 实战4.1 为什么状态一多表格法就撑不住了前面几节说的都可以用一张表来存 Q 值状态做行、动作做列。状态少的时候完全没问题火星车就六个格子随便存。但真实问题的状态是连续的高维向量机械臂的状态可能包含七八个关节角度和角速度游戏画面更是几十万维的像素。这时候表格的规模会爆炸而且很多状态压根没被访问过表里全是空的。解决办法和图像识别里用神经网络代替查表是同一个思路不去记每个状态的 Q 值而是学一个函数 Q(s,a; θ)输入状态和动作输出一个 Q 值θ 是网络参数。这样即使遇到训练时没见过的新状态网络也能根据相似性给出合理估计。这就是深度 Q 网络DQN的核心动机。不过在工程细节上把 Q 学习直接套上神经网络会非常不稳定甚至发散。课程里专门花篇幅讲了两个关键的稳定化技巧下面分别说。4.2 目标网络与经验回放两个救命的工程技巧第一个技巧是目标网络target network。回忆贝尔曼方程构造的损失我们让网络的输出 Q(s,a) 去逼近“即时奖励 γ·max Q(s,a)”。问题是等式右边也用到了同一个 Q这就好比你在追一个自己也在动的靶子训练很容易震荡。做法是再复制一个结构相同、参数暂时冻结的网络专门用来算右边的目标值。主网络每更新若干步才把参数“软更新”或者定期硬拷贝给目标网络。这样右边的靶子在短时间内是固定的训练就稳了。所谓软更新是每次只挪一小步比如新目标网络参数等于 0.995 乘旧参数加 0.005 乘主网络参数让目标缓慢跟随主网络避免突变。第二个技巧是经验回放experience replay。智能体在环境里连续跑产生的样本之间高度相关而神经网络的训练假设样本独立同分布。直接拿连续样本来训练网络会顺着一个方向连续更新容易过拟合最近的经历。做法是准备一个回放缓冲区把每一步的“状态、动作、奖励、下一状态”存进去训练时从里面随机抽一小批。这样既打破了样本相关性又能让同一份数据被反复利用样本效率大幅提升。我在实际项目里缓冲区的容量一般设到几万到几十万条太小了容易遗忘早期经验太大了又会拖慢单步训练速度需要一个权衡。4.3 探索与利用epsilon-greedy 的取舍还有一个绕不开的问题智能体是根据当前 Q 值选动作的可如果一开始 Q 值都是垃圾它就会一直选那个“碰巧看起来最好”的动作永远不去探索其他可能最后困在局部最优里。这就是探索与利用exploration vs exploitation的矛盾。最常用的解法是epsilon-greedy 策略以 ε 的概率随机选一个动作去探索以 1-ε 的概率选当前 Q 值最大的动作去利用。ε 通常从一个较大的值比如 1.0纯随机开始随着训练推进逐渐衰减到一个很小的值比如 0.05 或 0.1。前期多探索把地图摸清后期多利用把已经学到的好策略稳定下来。我踩过的一个坑是 ε 衰减得太快。刚跑几百步就把它降到 0.01结果智能体还没探索明白就“锁定”了一条烂路后面怎么训都上不去。经验是让 ε 的衰减和总训练步数挂钩保证探索阶段有足够长的持续时间也可以设一个下限别让它降到 0保留一点点随机性应对环境的微妙变化。5. 一个可复现的 DQN 代码骨架5.1 网络结构与关键超参数理论说再多不如直接看代码怎么搭。下面是一个简化的 DQN 骨架用 PyTorch 写的结构上做了精简方便你理解主流程真正跑任务时再按需加深加宽。import random import numpy as np import torch import torch.nn as nn import torch.optim as optim from collections import deque class QNet(nn.Module): def __init__(self, state_dim, action_dim, hidden128): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) ) def forward(self, x): return self.net(x) class ReplayBuffer: def __init__(self, capacity100000): self.buffer deque(maxlencapacity) def push(self, s, a, r, s_next, done): self.buffer.append((s, a, r, s_next, done)) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) s, a, r, s_next, done zip(*batch) return (np.array(s), np.array(a), np.array(r), np.array(s_next), np.array(done)) def __len__(self): return len(self.buffer)网络部分我没用卷积是因为大多数入门任务的输入本身就是低维向量。真到了玩像素游戏那种场合前面再加几层卷积把图像压成特征向量思路是一样的。隐藏层 128 是一个起步值任务简单可以缩到 64复杂就往 256 加。超参数方面我一般这么起步折扣因子 0.99学习率 1e-3 到 5e-4 之间回放缓冲区容量 10 万每步之后从缓冲区抽 64 或 128 条训练目标网络每几百步同步一次。这些数不是金科玉律但作为起点很稳能省掉很多盲调的时间。5.2 训练循环与损失函数训练循环是整个算法的主干贴出来对照着看会清楚很多def train(env, num_episodes1000, gamma0.99, lr1e-3, batch_size64, buffer_size100000, eps_start1.0, eps_end0.05, eps_decay0.995, target_update10): state_dim env.observation_space.shape[0] action_dim env.action_space.n policy_net QNet(state_dim, action_dim) target_net QNet(state_dim, action_dim) target_net.load_state_dict(policy_net.state_dict()) target_net.eval() optimizer optim.Adam(policy_net.parameters(), lrlr) buffer ReplayBuffer(buffer_size) eps eps_start for episode in range(num_episodes): s, _ env.reset() done False while not done: if random.random() eps: a env.action_space.sample() else: with torch.no_grad(): q policy_net(torch.tensor(s, dtypetorch.float32)) a int(q.argmax().item()) s_next, r, terminated, truncated, _ env.step(a) done terminated or truncated buffer.push(s, a, r, s_next, done) s s_next if len(buffer) batch_size: bs, ba, br, bsn, bd buffer.sample(batch_size) bs torch.tensor(bs, dtypetorch.float32) ba torch.tensor(ba, dtypetorch.long).unsqueeze(1) br torch.tensor(br, dtypetorch.float32).unsqueeze(1) bsn torch.tensor(bsn, dtypetorch.float32) bd torch.tensor(bd, dtypetorch.float32).unsqueeze(1) q_values policy_net(bs).gather(1, ba) with torch.no_grad(): next_q target_net(bsn).max(1, keepdimTrue)[0] target br gamma * next_q * (1 - bd) loss nn.functional.mse_loss(q_values, target) optimizer.zero_grad() loss.backward() optimizer.step() eps max(eps_end, eps * eps_decay) if episode % target_update 0: target_net.load_state_dict(policy_net.state_dict())这段代码里有几个地方值得单独拎出来讲。一是 (1 - bd) 这一项它保证当这一步是终止状态时目标值只等于即时奖励不再往后叠加未来价值否则会错误地把“下一局的开始”当成当前局面的延续。二是用 target_net 算 next_q这就是前面说的目标网络稳定化。三是 gather 操作它的作用是只取实际执行的那个动作对应的 Q 值其他动作的值不参与这次损失这是 Q 学习的标准做法。我第一次写这些代码时最常犯的错就是忘了乘 (1 - bd)导致智能体在任务结束后还傻乎乎地估值训练目标总是偏高表现就是怎么都学不好。这种 bug 不报错但会让结果诡异排查起来很费劲。5.3 训练过程中的观察与判断代码能跑不等于学得对你得会看训练曲线。我通常盯三个指标每回合的总回报、损失值、以及回合长度。健康的情况是总回报整体上升、损失先降后在一个区间波动、回合长度按照任务性质变化比如控制任务希望它越走越稳游戏任务希望它存活越久。如果回报一直贴着地板不动先别急着调网络回头看看奖励设计和 ε 衰减。如果是损失剧烈震荡八成是学习率太高或者目标网络同步太频繁。我总结了一个粗线条的排查顺序奖励信号是否合理 → 探索是否充分 → 目标网络是否稳定 → 网络容量和学习率。按这个顺序查基本能定位到八成的问题。提示训练几百回合没起色很正常DQN 的方差本来就大。建议同一套配置至少跑三次取平均别被单次运气好的结果骗了。6. 常见问题与排查技巧实录6.1 训练不收敛、震荡、卡在低分的排查这一节基本是我自己踩坑的合集。先上一张速查表出问题时对着看现象可能原因排查/解决方向回报长期不上升奖励太稀疏、探索不足检查奖励设计调大初始 ε 或放缓衰减损失剧烈震荡学习率过高、目标网络更新太频繁降学习率拉长目标同步间隔或用软更新回报先升后崩灾难性遗忘、经验回放比例失衡增大缓冲区检查样本采样是否偏最新Q 值爆炸到很大缺少终止处理、折扣因子接近 1确认终止状态不叠加未来值检查 γ策略来回抖动ε 太低导致探索不足、环境噪声大保留 eps 下限平滑决策或多次评估一个特别隐蔽的坑是奖励尺度。如果你一个任务里奖励有的给 1有的给 1000网络就会被大数值的奖励牵着走忽略掉那些小但有意义的信号。稳妥的做法是做奖励归一化或者把奖励都缩放到一个相近的量级。我做过一个任务就是因为在某个少见状态下给了个异常大的奖励结果智能体一心想着复现那个场景其他有用的行为全被压下去了。另一个经验是先跑通一个极简环境。在 CartPole 这种小任务上调通确认你的代码框架、损失、目标网络都没问题再搬到复杂任务上。直接在复杂环境里 debug你会分不清到底是框架有 bug 还是任务本身难那才叫折磨。6.2 奖励设计一个被严重低估的环节强化学习里有一句话叫“你奖励什么就会得到什么”这话一点不夸张。奖励设计得好任务轻松学设计得烂智能体总能找到你想不到的歪路。我见过智能体为了刷分宁可停在原地不停触发小奖励也不去完成真正的目标俗称“奖励黑客”。设计奖励我的几条原则让最终目标有明确的正奖励中间步骤可以给稀疏的引导奖励但别让引导奖励盖过最终目标惩罚要克制惩罚太狠会让智能体干脆不动因为不动就不会被罚考虑时间尺度如果任务本身奖励很稀疏可以加一点基于进展的塑形奖励但要小心别把最优解带偏。说实话奖励设计更像是一门手艺而不是科学得多试。我的笨办法是每次只改一个地方跑一小批实验记录对比慢慢就能养出对“什么奖励会引出什么行为”的直觉。6.3 一句话讲清离线强化学习为什么火进阶方向里这几年讨论度很高的是离线强化学习代表算法之一是IQLImplicit Q-Learning。它解决的是一个很现实的痛点在很多领域医疗、自动驾驶你没法让智能体随便去环境里试错风险太大你只有一批历史数据想知道在这批数据上能学到多好的策略。难点在于分布偏移——数据里出现过的好动作和你策略想选的动作可能对不上直接用普通的 Q 学习会高估那些没见过的动作越学越离谱。IQL 的巧思在于用“期望回归”去估计数据分布内的最优价值而不用去查询数据分布外的动作从而绕开了这个高估问题。理解它的前提还是前面那套 Q 函数和贝尔曼方程只不过在“能从数据里学多少”这件事上做了严格约束。想深入这一块先把这一周的表格方法吃透再看 IQL 会觉得顺很多。7. 强化学习还能往哪些方向延展7.1 机械臂控制与经典控制的结合一个很落地的方向是机械臂控制。传统机械臂大多用 PID 那套经典控制方法调参靠经验遇到负载变化、摩擦干扰时需要重新调。有研究把 Q-learning 这类自适应方法和 PID 结合让控制器能根据实时反馈自动调整参数在水下航行器AUV这类环境多变的场景里表现更稳。思路上智能体学的不是直接输出力矩而是学“在当前状态下 PID 参数该往哪个方向调”把强化学习当成了上层调参器。这种“经典控制打底 强化学习微调”的混合方案我觉得比纯端到端更靠谱也更容易在实际工程里落地。对刚入门的朋友我建议拿一个带连续控制的仿真环境练手先把离散动作的 DQN 跑熟再去看需要处理连续动作的算法会顺很多。7.2 从图强化学习到多智能体与联邦训练随着任务越来越复杂出现了一批有意思的延展方向。图强化学习是把状态或智能体之间的关系用图结构表示再配合图神经网络去抽取关系特征适合交通路网、社交网络这类天然带结构的问题。深度强化学习则是前面一直在讲的主线用深网络拟合价值或策略。联邦式的多智能体训练关注的是多个智能体在各自本地数据上学习、又需要协作的场景核心难点在于如何在不集中数据的前提下让整体策略收敛。这些方向的共同点是它们都没有绕开这一周的基础概念价值函数、贝尔曼方程、探索与利用全都还在。区别只在于“状态怎么表示”“多个智能体怎么协调”这些工程层面的问题。所以别急着追新名词地基打牢了上层建筑换得再快你也接得住。8. 我个人的一些收尾体会把这一周啃下来我觉得强化学习最反直觉、也最需要反复咀嚼的就是那个延迟奖励带来的信用分配问题。监督学习每步都有答案你错了立刻知道强化学习里一个糟糕的决定可能几十步之后才显出后果而一个局部的坏结果背后未必是坏决定。理解了这点你就能明白为什么价值函数这么重要——它本质上是在给每个“状态动作对”一个长远视角的评分把模糊的、延迟的反馈拆解到每一步上。再一个体会是工程细节的重要性远超标配的公式。目标网络、经验回放、epsilon 衰减、(1-done) 的处理这些看起来琐碎的技巧恰恰是决定你能不能跑出结果的关键。公式告诉你要往哪走而这些技巧决定你能不能走到。我经常跟人开玩笑说写 DQN 就像熬汤原料就那么几样但火候和放料的顺序才见功夫。如果让我给正在学这一块的朋友一条建议那就是别贪多把一个小环境从随机乱撞训到稳定拿高分比把十个环境的示例代码都跑一遍收获大得多。你在那个从零到一的过程中踩的坑、调的参、看的曲线才是真正长在你身上的东西。等哪天你看着回报曲线从一条平线慢慢抬头、又稳定在高位你会真切地感受到强化学习是怎么回事——那种感觉比任何定义都实在。
返回列表