提到强化学习里的模型无关控制,很多新手第一反应是“无模型”这三个字,听起来像是什么高深莫测的理论。其实恰恰相反,model-free控制是目前落地最多、上手最快、也最容易出效果的一类强化学习方法。它的核心思想一句话就能讲明白:不预测环境,不建动力学方程,直接靠试错反馈来学策略。你要做的不是去计算“如果左转会怎样”,而是真的去左转一次,看结果好坏,然后调整下一次的选择。这种“只认结果、不问原因”的学习方式,在处理那些环境复杂到根本没法建模的任务时,反而成了最大的优势。
这篇文章我会把model-free控制的主要流派拆开讲一遍:蒙特卡洛、时序差分、Q-learning、SARSA,再到策略梯度和Actor-Critic。会讲清楚它们各自解决什么问题、原理上有什么差异、实际写代码时参数怎么调、训练过程中会遇到哪些坑。适合两类人看:一是准备入门强化学习但被各种术语绕晕的初学者,二是在做项目选型、纠结该用哪类算法的开发者。目标只有一个:让你读完这篇,心里有谱,知道该从哪里下手。
1. Model-Free控制的核心思路:不建模,直接试错
1.1 为什么选择Model-Free而不是Model-Based
在强化学习里,环境模型指的是你对任务世界的认知程度:状态怎么迁移、动作会带来什么奖励、下一个状态是什么。如果能完整写出这些规则,那就是model-based路线——你可以用动态规划、策略迭代这类方法,在模型上“想象”出最优策略。问题在于,现实任务里你几乎拿不到完整准确的模型。机器人控制涉及接触摩擦,游戏对局涉及对手行为,推荐系统涉及用户兴趣波动,这些都不可能用公式精确描述。
Model-free控制选择了一条更笨也更稳的路:不试图构建环境模型,直接学习“在什么状态做什么动作”的价值。有种说法我觉得很贴切:model-based像是靠着地图找餐厅,你需要先有一张准确的地图;model-free像是盲吃探店,一家家吃过去,记住哪家好吃,哪家下次绕开。对环境一无所知也能上手,只要你有足够多的试错机会,并且能从反馈中学习。
在实际工程里还有一层现实考虑:model-based方法虽然样本效率高,但模型误差会导致“规划出来的策略一到真实环境就失灵”——这就是所谓的模型偏差问题。而model-free方法虽然需要的样本量大、训练时间长,但学到的策略直接来自真实交互数据,可靠性更高。所以在数据不那么昂贵、环境允许大量试错的场景里,model-free几乎是我的默认选择。
1.2 三类主流方法:价值迭代、策略迭代与两者融合
Model-free控制有三大流派,它们是理解整个领域的骨架。
第一类是基于价值的迭代,典型代表是Q-learning和SARSA。这类方法维护一张“状态-动作价值表”,每一步根据即时奖励和下一状态的价值估计来更新这个表。核心问题是“什么动作值得做”,策略本身是隐式的——训练完成后,直接选取价值最大的动作就行。
第二类是基于策略的迭代,典型代表是REINFORCE和策略梯度方法。它把策略本身参数化,直接用梯度上升去最大化期望回报。这类方法适合连续动作空间,因为价值方法需要遍历所有动作来求最大值,动作一旦连续就没法枚举。
第三类是Actor-Critic架构,把前两者融合起来:Actor通过策略梯度调整动作选择,Critic对当前状态的价值进行评估,给Actor一个更强、更低方差的反馈信号。现在的很多进阶算法,像A3C、PPO、SAC,本质上都是这个套路。
三类方法的本质差异在于“策略从哪里来”:价值法从价值表中隐式提取,策略法直接建模策略分布,Actor-Critic则是两套模型各司其职。它们之间没有绝对的优劣,主要看任务状态空间和动作空间的形态、样本获取成本以及训练稳定性的要求。
1.3 什么时候不能用Model-Free
虽然model-free好用,但也不是万能的。我碰到最多的翻车场景是:样本获取极贵的任务,比如真实机器人训练、医疗推荐、大型工业控制,每试一次都要付出真实代价,这时model-free的“大量试错”前提就不成立。另外,状态空间超高维但要求快速部署的场景,比如深度强化学习直接上手训练,动辄几十万步,如果硬件和训练时间都受限,就很难落地。
倒不是说这类场景不能碰model-free,而是你要有预期:在自己动手写之前,先想清楚三件事——样本是否拿得到?奖励是否能实时返回?训练过程能否容忍大量失败?想清楚了再选算法,不然容易掉进“训练了一周,奖励曲线还是直线”的绝望循环里。
2. 核心算法拆解:从蒙特卡洛到Actor-Critic
2.1 蒙特卡洛方法:用完整回合的经验说话
蒙特卡洛方法是最直观的model-free思路:一个回合跑完,得到完整的轨迹,包括每一步的状态、动作和最终的总回报。然后回头看这整个回合,把总回报分配给轨迹中出现的各个状态,该状态的价值就用多次回合的经验平均值来估计。
它有个关键概念叫first-visit vs every-visit。每次访问某个状态时,只记录第一次出现之后的累计回报叫first-visit,每次出现都记录叫every-visit。理论上两者在大数定理下都会收敛到真实价值函数,但first-visit方法方差小一些、分析也更容易,所以实用中我往往优先用它。在表格型任务里写蒙特卡洛的核心,其实就是一个累加平均的过程,逻辑非常清晰。
蒙特卡洛最大的缺点是必须等整个回合结束才能做一次更新。这意味着回合很长、或者根本没有明确的回合结束边界时,这种方法就不好使了。另一个问题是方差大:一个随机动作可能让总回报剧烈波动,导致价值估计不稳定。这也直接推动了时序差分方法的出现。
2.2 时序差分家族:SARSA和Q-learning的相爱相杀
时序差分方法解决了蒙特卡洛“必须等回合结束”的问题。它的更新只用一步经验:当前状态、当前动作、即时奖励、以及下一状态的价值估计。拿这些信息就能立刻更新,不用等回合结束。这就是常说的“用一步预测结果去更新上一步的估计”,所以它属于bootstrap方法——用估计去更新估计。
在这个框架下,SARSA和Q-learning的区别就在更新公式里的那个关键项:
SARSA是on-policy方法,更新用的是“实际采取的下一步动作”对应的Q值:
[ Q(s,a) \leftarrow Q(s,a) + \alpha [r + \gamma Q(s', a') - Q(s,a)] ]
这里的a'是智能体在s'实际准备执行的动作,由当前的行为策略(比如epsilon-greedy)决定。这条更新路线始终在“走自己选择的路”,对策略的行为风格很敏感。
Q-learning是off-policy方法,更新用的是“下一状态所有动作里的最大Q值”:
[ Q(s,a) \leftarrow Q(s,a) + \alpha [r + \gamma \max_{a'} Q(s', a') - Q(s,a)] ]
它不管智能体下一步实际会不会选择那个最优动作,只管“如果全力以赴最优,价值应该是多少”。说得夸张点,Q-learning在学一条“永远选最优”的路线,哪怕行为策略还在瞎试探。
这两种策略原型在悬崖行走这类任务里差异极其明显。我实测下来:SARSA学习出来的策略更保守,因为它在更新时考虑到了自己“会犯错”的可能,会主动避开悬崖边缘;Q-learning则更激进,学出的路径贴着悬崖走,因为它假设自己每一步都选最优动作。在部署阶段如果行为策略保持epsilon,SARSA的赔率更低。但如果练完就把epsilon降为零,Q-learning往往能拿到更高上限。
2.3 策略梯度方法:不学价值,直接学动作
基于价值的方法在动作空间连续时会崩溃——因为max操作依赖枚举所有动作,连续动作你怎么列举?策略梯度绕开了这个问题:直接把策略建模为一个参数化的概率分布 (\pi_\theta(a|s)),然后通过采样动作与环境交互,用回报的期望来指导参数更新。
最基础的REINFORCE算法更新公式是:
[ \theta \leftarrow \theta + \alpha \sum_{t} G_t \nabla_\theta \log \pi_\theta(a_t|s_t) ]
其中G_t是从t时刻开始的累计回报。这个公式的逻辑可以这样理解:某条轨迹的回报比平均高,就提高这条轨迹里所有动作的概率;比平均低,就压低。但问题是,这个估计量的方差非常大——不同随机种子下的训练曲线可能天差地别。解决方向是引入基线:用当前状态的价值函数作为baseline,把G_t减掉一个期望回报,让“高/低”的判断更精确。这也就自然过渡到了Actor-Critic结构。
Actor-Critic里,Actor是策略网络,负责输出动作分布;Critic是价值网络,负责估计当前状态的价值,作为Actor更新时的基线。因为有了Critic的实时评估,算法不用等完整回合结束,可以走一步更新一步,这大大缩短了反馈回路,也降低了方差。当前工程界主流的PPO、SAC等算法,都是在Actor-Critic骨架上做的改进。
从实践角度选型,我的经验是:
- 离散动作空间、表格型任务:Q-learning最快,代码量和调参成本都最低。
- 连续动作空间、物理控制类任务:优先考虑策略梯度或Actor-Critic。
- 需要稳定策略分布、不想用贪心策略作决策的任务:策略梯度天然适合。
- 样本效率优先、但环境有风险的任务:SARSA保守特性有优势。
3. 实操过程:从环境搭建到参数调优
3.1 环境设计与奖励函数怎么定
实操作的第一步不是选算法,而是定义清楚环境。很多人上来就把经典环境库里的任务跑一遍,以为跑通了就算会了,换到自己的任务就抓瞎。我的建议是:从“手工可验证”的小环境开始验证算法正确性,再迁移到真实任务。
手工环境设计要点有三个: 状态空间要小到你能枚举出来、动作空间要明确、奖励函数要能让智能体通过局部反馈找到方向。我常用一个4×4的网格世界任务做测试,起点在左下角,终点在右上角,每一步奖励为-1,到达终点奖励为10。这个设定足够简单,你可以手动算几步Q值来验证代码逻辑是否正确。
奖励函数是强化学习里最考验经验的环节。核心原则是两条:奖励不能太稀疏,稀疏到智能体找不到任何正向信号;同时不能下错引导,奖励函数给错了方向,智能体就会想着“钻漏洞”。我见过一个任务是让机器人推箱子到指定位置,设计者把“接近箱子”也给了奖励,结果机器人学会了绕着箱子打转——再小的距离变化都给它疯狂拉高奖励。这就是奖励塑形没做好带来的闭环式误学。碰到这种情况,要么把奖励改成稀疏目标,要么用奖励塑形的标准方式重新校核。
3.2 Q-learning核心实现与参数选择
我直接给出一个能跑通的Q-learning实现骨架,这个版本我改写过很多次,用起来很稳。
import numpy as np class QLearningAgent: def __init__(self, state_dim, action_dim, lr=0.1, gamma=0.95, epsilon=1.0, epsilon_min=0.01, epsilon_decay=0.995): self.q_table = np.zeros((state_dim, action_dim)) self.lr = lr # 学习率 self.gamma = gamma # 折扣因子 self.epsilon = epsilon self.epsilon_min = epsilon_min self.epsilon_decay = epsilon_decay def choose_action(self, state): if np.random.random() < self.epsilon: return np.random.randint(self.q_table.shape[1]) return np.argmax(self.q_table[state]) def update(self, state, action, reward, next_state, done): best_next = np.max(self.q_table[next_state]) if not done else 0 td_target = reward + self.gamma * best_next td_error = td_target - self.q_table[state][action] self.q_table[state][action] += self.lr * td_error if done: self.epsilon = max(self.epsilon * self.epsilon_decay, self.epsilon_min) def train(self, env, episodes=500): rewards = [] for ep in range(episodes): state = env.reset() total_reward = 0 while True: action = self.choose_action(state) next_state, reward, done = env.step(action) self.update(state, action, reward, next_state, done) state = next_state total_reward += reward if done: break rewards.append(total_reward) return rewards这段代码里几个参数需要解释清楚。学习率 lr 决定每次更新的步子大小,太小收敛慢,太大后期震荡。我一般取0.1,如果是噪声比较大的环境会降到0.05甚至0.01。折扣因子 gamma 决定了“未来奖励的打折率”,越大越看重长期收益,通常取0.9到0.99之间。这里有个容易被忽略的点:gamma不是随便拍的,如果任务的单步奖励绝对值比较大,gamma太高会让累计回报膨胀,导致训练不稳定。
epsilon的衰减节奏是另一门学问。初始epsilon设为1.0,让智能体前几十步充分探索;然后逐步衰减到0.01,保持一点点随机性避免过早陷入局部最优。这里的decay参数决定了“从探索转向利用”的速度,衰减太快会导致探索不足,衰减太慢则训练后期还在乱试。
3.3 训练流程与收敛判断
训练流程里,我建议把“训练”和“评估”两个环节拆开。训练时用epsilon-greedy做探索,评估时把epsilon直接置零,让智能体纯凭学到的Q表行动。这样每训练若干回合,就暂停一次,统计一下当前策略在评估模式下的平均回报。这个平均回报曲线比训练时的奖励曲线更能反映真实进度,因为训练奖励里混着大量随机探索带来的噪声。
判断收敛看两条曲线:一是评估奖励是否进入平稳区间,二是Q表更新的平均td_error是否逐渐趋近于零。如果td_error还在正负之间大幅跳动,说明还没学好;如果奖励曲线长期不涨,则多半是参数或环境设计有问题,而不是单纯训练步数不够。
一个实用经验是:每次改动代码、算法或者环境设置之后,先用小规模训练检查程序能不能跑通,再加大训练量。我曾经在图方便直接跑5000回合,跑到2500回合发现更新公式里把next_state写成了state,浪费了一个下午。小规模试跑10个回合就能看出这种低级错误。
4. 常见问题与避坑实录
4.1 Q值高估:为什么Q-learning学了“过度自信”
Q-learning的目标公式里有个max操作,这导致了一个著名的理论问题:对下一状态的价值估计本身有误差,max操作会把误差放大。打个比方,如果你对四个动作的真实价值估计分别是1、1、1、1,但估计噪声让它们变成了0.8、1.1、1.3、0.9,那max会选中虚高的1.3,于是更新时就把目标值抬高了。长期下来,Q表会出现系统性高估。
这个问题在表格型任务里影响不大,因为估计方差小,高估不会太离谱。但在深度Q网络里非常严重,会导致策略质量劣化。解决办法有两个方向:一是用Double Q-learning的思路,把“选动作”和“估计动作价值”拆开,分别用两套参数,打破高估的耦合;二是在深度版本里,用双网络结构配合经验回放,这也是经典DQN系列算法的核心改进方向。
我建议在实际项目里先把基础Q-learning跑通,确认自己的更新逻辑没问题,再考虑降低高估的进阶技巧。不要一开始就上重型武器,不然出了问题你都定位不到是哪里错了。
4.2 探索与利用:epsilon衰减的节奏怎么把握
探索不够和探索过度都会让训练失败,这个度其实很难拿捏。我的经验是:如果你发现奖励曲线前期一直很低,甚至没有上升趋势,先用“暴力探索模式”来验证环境本身是不是可解的——把epsilon固定为0.5甚至更高,跑一遍看看奖励有没有明显变化。如果固定高探索都学不出东西,那问题大概率出在奖励函数或状态表示上,而不是探索不足。
反过来,如果你发现奖励上升到一定程度就开始来回波动、并且波动幅度很大,那很可能是epsilon衰减得太快,智能体早早就固化了策略,一旦踩进某个局部最优就跳不出来。这时候可以调低decay值,或者让epsilon每轮先降后升,周期性探索。周期性探索在我实际跑任务时表现不错,尤其适合那些存在多个局部最优解的任务。
另外,epsilon的最小值不要设成0。哪怕是训练完部署上线,也要保留1%到5%的随机性。真实环境是非平稳的,策略如果完全确定,遇到没见过的状态就无可挽回了。
4.3 奖励稀疏怎么办:塑形、课程学习与目标重定义
稀疏奖励是实际项目里最多人问的问题。环境只在任务完成时给一次奖励,其他时候都是0,Q-learning在这种场景下学习效率极低——因为你试了无数次,反馈信号全都是零,完全不知道该往哪个方向调。
我的策略是按优先级尝试三件事。第一是检查问题本身:能不能把奖励密度提高,在不改变最优策略的前提下给中间过程一些引导?比如前面提到的推箱子任务,可以在“箱子推进1厘米”时给一个很小的奖励,但必须确保这个奖励远小于最终完成任务的奖励,防止智能体只去刷中间奖励。第二是课程学习:刚开始用简化的任务形态练,比如缩小地图、减少障碍物、降低目标精度要求,学出基础策略后再逐步增加难度。第三是用别的算法思路,比如在状态表示里加入“距离目标”这类启发式特征,让Q函数更容易学到价值差异,而不是在纯稀疏信号下盲目搜索。
这三步都试过仍不收敛,我就考虑换赛道——用基于策略的方法替代Q-learning。策略梯度方法在稀疏奖励场景下往往比Q-learning更抗造,因为策略分布天然自带探索性,不容易完全卡死。
4.4 训练结果不稳:先定位代码问题,再怀疑算法
训练不稳定时,最忌讳的做法是“觉得算法不行就换一个”。我排过很多次雷,最后发现绝大多数不稳定都来自代码层面的低级问题:更新顺序错了、discount忘记在回合结束置零、状态空间没有正确归一化、经验回放缓冲区里混入了旧策略的过期样本等等。
排查思路我建议按这个顺序来:先打印几个回合的完整交互日志,看每个步骤的状态、动作、奖励、是否终止是否合理。再手动算几步更新,拿笔算一遍公式,和代码输出对一下。然后画Q值曲线,看价值是否单调上升或有规律的波动。最后才动参数。很多调参其实是无效的,因为代码有bug时任何参数组合看起来都是“差一点就成功”。
我自己有个习惯:在环境里故意设置一个已知初始Q值,比如把某个状态动作的Q表初始化为一个奇怪的数字,跑几回合之后检查Q值是怎么变化的,以此验证更新公式正确传播了这个初始误差。这个技巧看起来土,但能快速帮你确认代码逻辑有没有问题。
写在最后的一点体会
Model-free控制这个方向,初学的时候像是在黑盒子里摸黑前进,但随着你亲手写过几套算法、跑过几个环境、踩过几次坑,这些概念会慢慢串成一张清晰的网。我的切身体会是:别急着追最新最复杂的算法,先把Q-learning和SARSA这类基础方法吃透,把更新公式里的每个符号都理解到能手算推演的程度。后面理解策略梯度、Actor-Critic、PPO这些事情,会快很多。
如果这篇文章能给你什么实质建议,我建议你从一个小到能手动验证的网格世界开始,亲手实现一遍Q-learning,加上epsilon衰减,再加上评估模式。跑通之后,再换成连续控制任务,去试试策略梯度或者直接用现成库调包。每一步都记录下来,跑数据、画曲线、留笔记。等你做完了这三四个小项目,model-free控制对你来说就不是“理论概念”而是“工具箱里的日常工具”了。