1. 从“无模型”三个字说起:为什么它是强化学习入门的必经之路
刚接触强化学习那会儿,我对“Model free”这个说法一直有点似懂非懂。字面翻译是“无模型”,可强化学习本身不就是在训练一个模型吗?怎么又“无模型”了?后来踩过几次坑、跑废过几个实验之后才慢慢想明白:这里的“模型”指的压根不是我们要训练的策略网络,而是环境本身的动力学模型——也就是“做了动作A之后,状态会怎么变、奖励会拿多少”这套转移规律。
把这个概念理清楚,很多困惑就迎刃而解了。所谓Model free方法,就是我们在训练智能体的时候,不去显式地学习或已知环境的转移概率和奖励函数,而是让智能体直接跟环境交互,从一条条真实的经验轨迹里学“什么状态下该做什么动作”。与之相对的Model based方法,则会先想办法把环境“建模”出来,再基于这个模型做规划。
为什么说Model free是入门的必经之路?原因很实在。第一,绝大多数真实场景里,你根本拿不到精确的环境模型。比如让机械臂抓取一个形状不规则的物体,接触力、摩擦、形变这些东西的精确物理方程极其复杂,与其费劲去建模,不如让智能体自己多试几次。第二,Model free的方法逻辑更直接,代码实现相对简单,特别适合用来建立对强化学习核心概念(状态、动作、回报、价值函数)的直觉。第三,现在工业界大量落地的方案,比如游戏AI、推荐系统里的策略优化,主力仍然是Model free这一套。
这篇文章我打算把Model free这条线从头到尾捋一遍。不是那种教科书式的罗列,而是按照我自己学习和实践的顺序,把价值函数方法、策略梯度方法、以及两者融合的Actor-Critic框架这几大块讲透。中间会穿插大量我踩过的坑、参数调优的经验,以及那些文档里不会写但实际很关键的小细节。如果你刚开始学强化学习,或者已经看过一些资料但脑子里还是一团浆糊,那这篇应该能帮你把脉络理清楚。
2. 价值函数方法:从Q表到DQN的进化逻辑
2.1 蒙特卡洛与时序差分:两种“算账”方式的本质区别
要理解价值函数方法,得先搞清楚一个核心问题:我们怎么估计某个状态到底“值多少钱”。这里的“值钱”就是价值函数V(s)或者动作价值函数Q(s,a),代表从当前状态出发,未来能拿到的累计回报期望。
最朴素的想法是蒙特卡洛(Monte Carlo,简称MC)。它的逻辑特别像记账:我完整地跑完一整局,看看从状态s开始到最后总共拿了多少回报,然后把这个数字当作V(s)的一个样本,多跑几局取平均。这个方法的好处是无偏——只要样本够多,估计就会收敛到真实值。但问题也很明显:必须等到一局结束才能更新,如果是那种没有终点的连续任务,直接就歇菜了。而且整局回报的方差很大,因为中间任何一步的随机性都会累积到最后的总回报里。
时序差分(Temporal Difference,简称TD)就聪明多了。它不需要等一局结束,走一步就能更新。核心思想是自举(bootstrapping):用下一步的估计值来更新当前步的估计值。公式写出来就是:
V(s) ← V(s) + α [r + γV(s') - V(s)]这个r + γV(s')叫TD目标,r + γV(s') - V(s)叫TD误差。直观理解就是:我本来以为从s出发能拿V(s),结果实际走了一步拿到奖励r,到了s',而s'那边告诉我还能拿V(s'),那我修正一下对s的估计。
我刚开始学的时候总觉得TD有点“自己骗自己”——用估计值去更新估计值,能靠谱吗?后来想通了:这其实是一种用偏差换方差的权衡。TD估计是有偏的(因为依赖当前的估计),但方差比MC小得多,而且能在线学习、单步更新,实用性吊打MC。实际项目里,除非是那种回合制特别短、回报方差很小的场景,否则基本都是TD系的方法唱主角。
这里有个经验之谈:TD(0)、TD(λ)这些参数里的λ,本质上是在MC和TD之间做插值。λ=0就是纯单步TD,λ=1就退化成MC。实践中λ取0.9左右往往是个不错的折中,但具体还得看任务。我做过一个迷宫导航的实验,λ=0.95比λ=0收敛快将近一倍,但λ再往上加,方差带来的抖动就开始拖后腿了。
2.2 Q-Learning与SARSA:一个字母之差,行为天差地别
说到价值函数方法,绕不开两个经典算法:Q-Learning和SARSA。它们长得几乎一样,就差在更新目标上,但学出来的策略风格完全不同。
Q-Learning的更新目标是:
Q(s,a) ← Q(s,a) + α [r + γ max_a' Q(s',a') - Q(s,a)]注意那个max。它假设下一步我会选当前估计下最优的动作。这意味着Q-Learning学的是最优策略的价值,不管实际执行时用的是什么策略(比如ε-greedy探索)。这种“离线策略”(off-policy)的特性让它很灵活,可以从历史数据里学,也可以边探索边学最优。
SARSA的更新目标是:
Q(s,a) ← Q(s,a) + α [r + γ Q(s',a') - Q(s,a)]这里用的是实际下一步会执行的动作a',而不是max。所以SARSA学的是当前执行策略的价值,是“在线策略”(on-policy)的。
这个区别在什么场景下会要命?经典例子是悬崖行走(Cliff Walking)。悬崖边上有一条最优路径,但一旦掉下去就吃大惩罚。Q-Learning因为总是按max来更新,它会认为悬崖边的状态价值很高(因为最优策略下确实能安全通过),于是学出来的策略会贴着悬崖走。而SARSA考虑到探索时可能随机乱走掉下悬崖,所以它会学得保守一些,离悬崖远一点。
我实际跑过这个实验,Q-Learning收敛后的路径确实更短但更“危险”,SARSA的路径绕一点但更稳。如果你的应用场景里探索带来的代价很高(比如真实机器人、医疗决策),SARSA这种保守风格往往更合适;如果是仿真环境里追求最优性能,Q-Learning更直接。
2.3 DQN:让Q-Learning能处理图像输入的三个关键设计
Q-Learning用表格存Q值,状态一多就爆炸。当状态是图像这种高维连续输入时,表格法彻底没戏。DQN(Deep Q-Network)的思路很自然:用神经网络来拟合Q(s,a),输入状态,输出每个动作的Q值。
但直接把Q-Learning和神经网络拼一起会出大问题。我最早自己试着搭的时候,损失曲线跟心电图似的,根本不收敛。后来才明白,DQN能work靠的是三个关键设计:
第一个是经验回放(Experience Replay)。把智能体跟环境交互得到的(s, a, r, s')存进一个缓冲区,训练时从里面随机采样batch。这么做解决了两个问题:一是打破了样本之间的时间相关性(连续的经验高度相关,直接拿来训练会让网络在局部数据上过拟合);二是提高了样本利用率,一条经验可以被反复使用。
第二个是目标网络(Target Network)。如果更新时用的TD目标里的Q(s',a')也是用当前正在训练的网络算的,那目标本身在动,训练就像追着自己的尾巴跑。DQN的做法是搞一个结构相同但参数更新滞后的目标网络,每隔C步把主网络的参数复制过去。这样一段时间内目标相对稳定,训练就稳多了。
第三个是奖励裁剪(Reward Clipping)。DQN那篇经典工作里把奖励统一裁剪到[-1, 1]区间。这招在当时是为了让不同游戏用同一套超参数,但副作用是丢失了奖励的绝对大小信息。现在很多实现里已经不强制裁剪了,但如果你发现Q值爆炸,裁剪一下往往能救急。
这里分享一个我踩过的坑:经验回放缓冲区的大小很关键。太小了,样本多样性不够,容易过拟合最近的经验;太大了,旧的经验可能跟当前策略差太远,反而拖累学习。Atari游戏上常用的100万条对大多数任务来说太大了,我做过的一些连续控制任务,10万条左右效果就不错。这个值得你根据自己的任务多试几组。
3. 策略梯度:直接对策略“动刀”的思路
3.1 为什么有了价值方法还需要策略梯度
价值函数方法有个隐含的假设:策略是从价值函数里“导出”的,比如ε-greedy或者直接取argmax。这在离散动作空间里没问题,但遇到连续动作就尴尬了——你没法对连续动作求argmax,总不能把动作空间离散化吧,维度一高就组合爆炸。
策略梯度方法换了个思路:直接参数化策略π(a|s; θ),然后想办法调整θ让期望回报变大。这样一来,连续动作空间天然支持(网络直接输出动作分布的参数,比如高斯分布的均值和方差),而且策略可以是随机的,这在需要探索或者博弈场景里很重要。
从数学上看,策略梯度的核心是策略梯度定理:
∇J(θ) = E[∇log π(a|s; θ) * Q(s,a)]这个公式的直观理解是:如果某个动作带来的回报高,就增大它被选中的概率;回报低就减小。∇log π给出了“怎么调参数能让这个动作概率变大”的方向,Q值则告诉我们这个动作到底好不好。
我第一次看到这个公式的时候觉得挺神奇,后来用“似然比”的角度理解就顺了。它本质上是一种得分函数估计器,用采样来估计梯度的期望,不需要知道环境模型,也不需要可微的环境,通用性极强。
3.2 REINFORCE的方差问题与基线技巧
最基础的策略梯度算法是REINFORCE,它用整条轨迹的回报G_t来估计Q(s,a)。实现简单到令人发指:跑完一局,算每一步的回报,然后按∇log π * G_t做梯度上升。
但REINFORCE有个致命问题:方差巨大。因为G_t是整条轨迹的累计回报,中间任何随机性都会影响它。我最早跑CartPole的时候,REINFORCE要几千局才能稳定,而且不同随机种子跑出来的曲线能差出好几倍。
解决方差问题的第一个利器是引入基线(Baseline)。把更新量从∇log π * G_t改成∇log π * (G_t - b(s)),其中b(s)是只依赖状态不依赖动作的基线。数学上可以证明这不会引入偏差,但能显著降低方差。最常用的基线就是价值函数V(s),这样G_t - V(s)就变成了优势函数A(s,a)的估计,衡量的是“这个动作比平均水平好多少”。
这个技巧的效果立竿见影。我在同一个任务上对比过,加基线之后收敛速度大概提升了3到5倍,而且曲线平滑多了。基线本质上是在做“相对评价”而不是“绝对评价”,这跟人类学习的直觉也一致——我们更关注“比预期好还是差”,而不是绝对分数。
另一个降低方差的技巧是奖励折扣与归一化。把回报做标准化(减均值除标准差)在实践中几乎总是有益的,尤其是奖励尺度差异大的任务。还有因果性技巧:t时刻的更新只应该用t时刻之后的回报,因为之前的奖励跟当前动作无关。这个在实现时体现为对回报做反向累积,而不是简单求和。
3.3 从REINFORCE到PPO:重要性采样与信任域
REINFORCE是离线策略的吗?严格说它是在线策略的——每次更新后策略变了,旧数据就不能用了,得重新采样。这导致样本效率极低。要复用旧数据,就得用重要性采样:
E_{π_θ}[f] = E_{π_old}[ (π_θ/π_old) * f ]但重要性采样的方差会随着新旧策略差异变大而爆炸。TRPO(Trust Region Policy Optimization)的思路是:限制新旧策略之间的KL散度,在这个约束下最大化改进。它理论上漂亮,但实现复杂(要算Fisher信息矩阵、做共轭梯度),调起来也麻烦。
PPO(Proximal Policy Optimization)是TRPO的实用简化版,现在几乎是策略梯度方法的默认选择。它的核心是裁剪的重要性采样比:
L = min( ratio * A, clip(ratio, 1-ε, 1+ε) * A )当ratio偏离1太多时,梯度就被截断了,相当于软性地限制策略更新幅度。这个设计简单、稳定、效果好,我在好几个项目里用PPO都是“开箱即用”的感觉,超参数不敏感,ε取0.1到0.2之间基本都行。
不过PPO也不是万能的。它属于在线策略方法,样本效率仍然不如离线的价值方法。而且它对优势估计的质量比较敏感,实践中一般用GAE(Generalized Advantage Estimation)来算优势,λ取0.95左右。如果你的任务采样成本很高(比如真实机器人),PPO可能不是最优选择;但如果是仿真环境,PPO的稳定性和易用性很难被替代。
4. Actor-Critic:把价值方法和策略梯度拧成一股绳
4.1 AC框架的分工逻辑:演员与评论家
Actor-Critic(AC)框架的思路特别符合直觉:Actor(演员)负责选动作,Critic(评论家)负责评价动作好不好。Actor就是策略网络π(a|s; θ),Critic就是价值网络V(s; w)或Q(s,a; w)。Actor根据Critic的评价来调整策略,Critic则根据实际回报来修正自己的评价。
这个分工解决了纯策略梯度方差大的问题——Critic提供的优势估计比整条轨迹的回报稳定得多。同时也解决了纯价值方法难以处理连续动作的问题——Actor直接输出动作分布。
我第一次把AC跑通的时候,感觉像是找到了强化学习的“标准答案”。但很快发现,AC的训练稳定性是个大问题。Actor和Critic同时在学,Critic估计不准的时候Actor会被带偏,Actor变了之后Critic又得重新适应,两者容易陷入恶性循环。
4.2 A2C与A3C:同步与异步的工程取舍
A2C(Advantage Actor-Critic)是AC的一个直接改进:用优势函数A(s,a) = Q(s,a) - V(s)来指导Actor更新,同时用n步回报来平衡偏差和方差。实现上通常是同步并行——开多个环境同时采样,凑够一个batch一起更新。
A3C(Asynchronous Advantage Actor-Critic)则是异步并行——多个线程各自跟自己的环境交互,各自算梯度,异步地更新全局参数。这个设计当年很惊艳,因为异步带来的探索多样性让训练更稳,而且不需要经验回放(各线程的经验天然去相关)。
但实际用下来,A3C的异步更新会带来梯度陈旧的问题——某个线程算梯度的时候用的是旧参数,等它更新的时候全局参数已经变了好几轮。A2C虽然看起来“笨”一点(同步等待),但梯度总是基于最新参数,反而更稳。现在很多实现里A2C的性价比其实高于A3C,尤其是配合GPU并行环境的时候。
我自己的经验是:如果环境本身很快(比如简单的控制任务),A2C同步并行的效率更高;如果环境很慢或者异构(比如不同难度的任务混在一起),A3C的异步特性可能更有优势。但总体来说,现在新项目我基本都直接从PPO起步,它本质上是A2C加上信任域约束的进化版。
4.3 GAE:优势估计的偏差-方差调节旋钮
GAE(Generalized Advantage Estimation)是AC框架里绕不开的一个技术点。它用指数加权的方式把不同步数的TD误差组合起来:
A_t^GAE(γ,λ) = Σ (γλ)^l * δ_{t+l}其中δ是TD误差。λ=0时退化成单步TD误差(低方差高偏差),λ=1时接近蒙特卡洛回报(高方差低偏差)。λ这个参数就是调节偏差-方差权衡的旋钮。
实践中λ取0.92到0.98之间最常见,0.95是个万能起点。我做过一个对比实验,在连续控制任务上,λ=0.95比λ=0.9和λ=0.99的样本效率都高,收敛也更稳。但这个值跟任务的时间跨度有关——如果任务里关键决策的间隔很长,λ可能需要调大一点,让优势估计能看到更远的未来。
GAE的实现有个容易出错的细节:反向累积。因为A_t依赖A_{t+1},必须从轨迹末尾往前算。我最早写的时候正着算,结果优势全是错的,训练完全不收敛,排查了半天才发现是这个低级错误。这个坑值得每个刚上手的人注意。
5. 实操中的那些坑:从理论到跑通的最后一公里
5.1 超参数敏感度:学习率、折扣因子与网络结构
强化学习对超参数极其敏感,这是它跟监督学习最大的区别之一。监督学习里学习率差个几倍可能只是收敛快慢的问题,强化学习里学习率大一点直接导致策略崩溃。
学习率方面,策略网络和价值网络的学习率往往需要分开设。我的经验是策略网络的学习率通常要比价值网络小,因为策略更新太激进容易把好不容易学到的行为毁掉。常见配置是策略网络3e-4,价值网络1e-3。但这只是起点,具体任务得调。
折扣因子γ决定了智能体看多远。γ=0.99意味着大约100步的视野,γ=0.999就是1000步。如果任务需要长期规划(比如迷宫寻路),γ得设大;如果任务短平快,γ小一点反而学得快。有个经验公式是γ ≈ 1 - 1/T,T是任务的典型步数。但这不是铁律,还是得试。
网络结构上,策略网络和价值网络可以共享底层特征提取层,也可以完全分开。共享的好处是特征复用、参数少、学得快;坏处是两个目标可能冲突,互相干扰。我一般先试共享,如果训练不稳再改成分开。对于图像输入,CNN是标配;对于低维状态,两三层全连接(每层64到256个单元)通常就够了。别一上来就堆很深的网络,强化学习里网络越大越难训。
5.2 奖励设计与归一化:智能体到底在学什么
奖励函数是强化学习里最“玄学”的部分。理论上只要奖励设计得合理,任何方法都能学到东西;但实践中奖励设计不好,再好的算法也白搭。
最常见的坑是稀疏奖励——只有任务完成才给奖励,中间过程全是0。这种情况下智能体很难学到东西,因为随机探索几乎不可能碰巧完成任务。解决办法有几种:一是奖励塑形,在中间过程给一些引导性的小奖励;二是课程学习,从简单任务开始逐步加难度;三是事后经验回放,把失败轨迹重新标注成“如果目标是实际到达的状态,那这条轨迹就是成功的”。
奖励归一化也很关键。如果奖励尺度差异很大(比如有的步骤奖励是0.1,有的是100),价值网络很难拟合。我一般会维护一个奖励的滑动均值和标准差,做在线归一化。这招在连续控制任务里几乎是必备的。
还有个容易忽略的点是奖励的时间尺度。如果每一步都给正奖励,智能体可能学会“磨洋工”——故意拖长回合来累积奖励。这时候要么给每步加个小的负奖励(时间惩罚),要么用折扣因子让远期奖励贬值。这个坑我在一个导航任务里踩过,智能体学会了在原地转圈,因为转圈也能拿奖励。
5.3 训练不收敛的排查清单
训练不收敛是强化学习日常,我总结了一套排查顺序,基本能覆盖八成问题:
第一步,看数据。把智能体的轨迹打印出来,看看它到底在干什么。很多时候问题一目了然——比如动作全是同一个值(策略塌缩了),或者状态归一化没做导致输入尺度爆炸。
第二步,看价值估计。把V(s)的预测值和实际回报画在一起。如果两者差很远,说明Critic没学好,这时候Actor的更新就是基于错误信息的。可以先冻结Actor,只训Critic,等Critic靠谱了再放开。
第三步,看梯度。梯度爆炸或消失是常见问题。梯度裁剪(clip grad norm)几乎是标配,一般裁到0.5到1.0之间。如果梯度一直很小,可能是网络太深或者激活函数选得不好。
第四步,看超参数。学习率、batch size、GAE的λ、PPO的clip范围,这些都得试。我一般会做小规模的网格搜索,每个配置跑几个随机种子,看均值和方差。
第五步,看随机种子。强化学习的随机性极大,不同种子跑出来的结果可能天差地别。永远不要用单个种子的结果下结论,至少跑3到5个种子取平均。我见过太多“调了半天发现只是种子运气好”的情况。
5.4 评估与复现:别被单次结果骗了
最后说说评估。强化学习的评估跟监督学习很不一样,因为策略在训练过程中不断变化,而且环境本身有随机性。
评估策略时要用确定性策略(比如取动作分布的均值或argmax),而不是训练时的随机策略。否则评估结果会被探索噪声干扰。同时要在独立的环境实例上评估,不能拿训练用的环境来测,否则就是自己考自己。
复现性方面,固定随机种子是基础,但还不够。深度学习框架的某些操作(比如GPU上的某些卷积)本身有非确定性,要完全复现得开deterministic模式,但这会拖慢训练。我的建议是:开发阶段固定种子方便调试,最终报告结果时跑多个种子给出统计。
还有个实用技巧是定期保存检查点,并且记录每个检查点的评估性能。强化学习经常出现“中期表现最好,后期反而退化”的情况(过拟合或者策略塌缩)。有了检查点,你至少能回滚到最好的版本,而不是眼睁睁看着模型越训越差。
6. 写在最后:Model free的边界与我的使用心得
Model free方法撑起了强化学习落地的大半壁江山,但它不是没有边界。样本效率低是它最大的软肋——DQN在Atari上要玩几千万帧才能达到人类水平,而人类可能几十分钟就学会了。这个差距在真实物理系统里是致命的,因为采样成本太高。
所以现在一个明显的趋势是Model free和Model based的融合。比如用Model free方法学一个策略,同时用学到的环境模型来做规划或者生成虚拟经验,两者互相促进。还有离线强化学习,试图从固定数据集里学策略,不跟环境交互,这对那些交互成本高或者危险的场景特别有价值。
但不管技术怎么演进,Model free这套东西都是基础。价值函数、策略梯度、Actor-Critic这些概念,以及它们背后的偏差-方差权衡、探索与利用的平衡,是理解一切强化学习方法的钥匙。我自己的体会是:把Model free的几大经典方法亲手实现一遍,比看十篇综述都管用。实现的过程中你会被迫面对每一个细节,而这些细节恰恰是论文里一笔带过、但实际决定成败的东西。
如果让我给刚入门的人一个建议,那就是:从Q-Learning表格法开始,跑通一个简单环境;然后上DQN,理解经验回放和目标网络;接着写REINFORCE,感受策略梯度的方差问题;最后用PPO收尾,体会现代算法的稳定性。这条路走下来,你对强化学习的理解会扎实得多。至于那些更 fancy 的方法,等你把基础打牢了,看起来都会觉得顺理成章。