ARTICLE DETAIL

资讯详情

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

强化学习入门:从Q-learning到PPO的四类核心算法解析

强化学习入门:从Q-learning到PPO的四类核心算法解析 1. 从零搭建强化学习知识体系为什么我劝你先搞懂这四类算法强化学习这个方向我断断续续折腾了快三年。最开始是被AlphaGo的新闻带进来的觉得让程序自己学会打游戏这件事太酷了。结果一头扎进来才发现这东西的门槛不在代码量而在脑子里那套思维方式——你得习惯“没有标准答案只有试错反馈”的设定。这篇笔记是我自己从Q-learning一路踩坑到PPO之后回头梳理的一份算法地图适合刚入门、被各种名词绕晕的朋友也适合已经跑过几个demo但说不清算法之间关系的同学。先说清楚这篇笔记能给你什么。我不会只丢公式也不会只贴代码。我会把Q-learning、DQN、REINFORCE、PPO这四类最常被拿来当起点的算法按“它们各自解决什么问题、为什么长成这样、实际写的时候哪里最容易翻车”这条线串起来。你看完之后应该能做到两件事第一拿到一个新任务时能判断它大概属于哪类问题、该从哪个算法切入第二跑代码时遇到不收敛、奖励不涨、方差爆炸这些情况知道往哪个方向排查。强化学习最反直觉的地方在于它不像监督学习那样有一份“正确答案”喂给你。你只有一个环境一个动作空间以及一个奖励信号。智能体做对了给分做错了扣分但没人告诉它“正确动作是什么”。这就导致两个核心难题信用分配到底是哪一步导致了最后的奖励和探索与利用的平衡一直用已知的好动作还是试试没走过的路。后面所有算法本质上都在用不同方式回答这两个问题。我见过太多人一上来就啃PPO的论文被clip、GAE、advantage这些词劝退。其实如果先把Q-learning这条线走通再回头看策略梯度很多概念会自然落地。所以这篇笔记的顺序是先讲基于价值的方法Q-learning、DQN再讲基于策略的方法REINFORCE最后讲两者融合的PPO。每一类我都会给出最小可运行的代码骨架和参数选择的经验值。2. 基于价值的算法Q-learning与DQN的核心逻辑2.1 Q-learning为什么用一张表就能学会走迷宫Q-learning是我建议所有人接触的第一个强化学习算法没有之一。原因很简单它把“学什么”这件事具象化成了一张表格。假设你有一个网格世界状态就是格子的坐标动作就是上下左右。Q表的大小是状态数 × 动作数每个格子里的数值代表“在这个状态下做这个动作长期来看能拿多少分”。它的更新公式长这样Q[s, a] Q[s, a] alpha * (reward gamma * max(Q[s_next, :]) - Q[s, a])我第一次看到这个公式的时候觉得那个max特别别扭。后来想明白了它假设智能体下一步会选当前认为最好的动作然后用那个动作的价值来修正当前的价值。这就是时序差分的思想——不需要等到游戏结束才知道总分走一步就能根据下一步的估计来更新。参数上alpha是学习率我一般从0.1开始试gamma是折扣因子0.9到0.99之间比较常见。gamma越接近1智能体越“有远见”但也越难训练因为远处的奖励传回来要经过很多步。这里有个新手特别容易踩的坑探索率epsilon的衰减策略。如果你一开始就设成0.01智能体几乎只走已知路径很可能永远发现不了更优解。我的习惯是epsilon从1.0开始按epsilon max(epsilon * 0.995, 0.01)这样衰减保证前期充分探索后期稳定利用。注意Q-learning是off-policy的意思是它学习的策略和实际执行的策略可以不一样。实际执行时用epsilon-greedy探索但更新时用的是max也就是假设下一步选最优动作。这个细节在面试里经常被问到。2.2 DQN当状态多到表格装不下时怎么办Q-learning的致命伤是状态空间必须离散且有限。一旦你的输入是游戏画面比如Atari的210×160像素状态数量是天文数字表格根本存不下。DQN的思路很直接用神经网络来拟合Q值输入状态输出每个动作的Q值。但直接把Q-learning搬到神经网络上会出问题。我最早自己试着改的时候loss死活不下降后来才知道有两个关键设计不能省第一个是经验回放。智能体把每一步的(s, a, r, s_next, done)存进一个缓冲区训练时随机采样一批。这样做的好处是打破了数据之间的时间相关性。你想如果连续拿十步高度相关的数据去训练网络会在这十步上过拟合然后下一步环境一变就崩了。第二个是目标网络。DQN用两个网络在线网络负责选动作目标网络负责算目标Q值。目标网络的参数每隔N步才从在线网络复制一次。如果不这样做目标值会随着在线网络更新而不断移动就像你追一个自己也在跑的目标训练极不稳定。# 目标Q值的计算 with torch.no_grad(): next_q target_net(next_state).max(dim1)[0] target_q reward gamma * next_q * (1 - done)done这个标志位很重要它表示游戏是否结束。如果结束了就没有“下一步”的Q值目标Q就是当前奖励。DQN的参数经验回放缓冲区大小一般10万到100万batch size用32或64目标网络更新间隔从1000步到10000步不等。学习率我通常用1e-4到1e-3比监督学习小因为强化学习的梯度噪声大得多。2.3 从DQN到Double DQN、Dueling DQN的改进思路DQN本身有个已知缺陷叫Q值高估。因为目标Q值里用了max而max操作会让估计值偏大。打个比方你有十个动作每个的真实价值都是0但估计值有正有负max一取就变成正的了。这个偏差会随着训练累积。Double DQN的修正很巧妙用在线网络选动作用目标网络算那个动作的价值。这样选和算分开高估就被压下来了。代码改动只有一行# Double DQN best_action online_net(next_state).argmax(dim1) next_q target_net(next_state).gather(1, best_action.unsqueeze(1))Dueling DQN则是从网络结构下手把Q值拆成“状态价值V”和“动作优势A”两部分最后合成Q V A - mean(A)。这样网络能学到“这个状态本身好不好”和“这个动作比平均好多少”两个独立信息。在有些动作对结果影响不大的状态下V的估计会更准。我实测下来这两个改进叠加使用在大多数任务上都能带来明显提升而且代码改动量很小性价比很高。3. 基于策略的算法REINFORCE与策略梯度3.1 REINFORCE最朴素的策略梯度长什么样基于价值的方法有个根本限制它只能处理离散动作。如果动作是连续的比如机械臂的关节角度你没法对每个动作算Q值再取max。这时候就需要直接学策略——也就是一个从状态到动作分布的映射。REINFORCE是最基础的策略梯度算法。它的核心思想是让带来高回报的动作概率变大带来低回报的动作概率变小。更新公式是梯度 回报 × log(动作概率)的梯度。我第一次实现REINFORCE的时候发现它跑一个简单的小车爬坡任务都要几千个episode才收敛而且每次结果波动很大。原因在于方差太高。因为回报是整条轨迹的累计值一条轨迹里可能有几十步但只有最后一步的奖励是真正有意义的前面所有步都被同一个回报加权了。改进方向有几个一是加baseline通常用状态价值函数V(s)把回报减去V(s)这样正回报表示“比预期好”负回报表示“比预期差”二是用因果性只让当前步之后的回报影响当前步而不是整条轨迹的回报。# 带baseline的REINFORCE核心 returns [] G 0 for r in reversed(rewards): G r gamma * G returns.insert(0, G) returns torch.tensor(returns) returns (returns - returns.mean()) / (returns.std() 1e-8) loss -(log_probs * returns).mean()那个标准化操作很关键它把回报缩放到均值0方差1训练稳定性会好很多。3.2 策略梯度的方差问题与Actor-Critic的引入REINFORCE的方差问题催生了Actor-Critic结构。Actor是策略网络负责选动作Critic是价值网络负责评估当前状态值多少钱。Actor的更新不再用整条轨迹的回报而是用Critic给出的优势估计。优势函数的定义是A(s, a) Q(s, a) - V(s)意思是“这个动作比平均水平好多少”。如果优势为正说明这个动作值得鼓励为负则应该抑制。这样一来方差比用原始回报小得多。但Critic本身也要学它学得准不准直接影响Actor。我踩过的一个坑是Critic一开始估计得很差导致Actor被带偏整个训练崩掉。解决办法是让Critic先预热一段时间或者用较小的学习率让Critic慢慢跟上。实操心得Actor和Critic的学习率不一定要一样。我通常让Critic的学习率是Actor的2到3倍因为Critic的任务更简单只是回归状态价值学快一点没关系。3.3 连续动作空间下的策略参数化连续动作空间下策略网络通常输出一个高斯分布的均值和标准差。均值决定动作的中心位置标准差决定探索范围。标准差可以设成固定值也可以让网络自己学。让网络学标准差有个好处在不确定的状态下自动增大探索在确定的状态下减小探索。但要注意给标准差加个下界比如std exp(log_std).clamp(0.1, 1.0)否则标准差可能塌缩到0策略失去探索能力。mean net(state) std log_std.exp().expand_as(mean) dist Normal(mean, std) action dist.sample() log_prob dist.log_prob(action).sum(dim-1)这个结构在机械臂控制、无人机姿态控制这类任务里非常常见。我做过一个简单的机械臂抓取仿真用连续动作的Actor-Critic大概500个episode就能稳定抓取比离散化动作空间效果好很多。4. PPO工业界最常用的强化学习算法4.1 PPO到底在解决什么问题PPO全称Proximal Policy Optimization翻译过来叫近端策略优化。它要解决的核心问题是策略更新步子迈太大导致训练崩溃。在普通策略梯度里你算出一个梯度更新参数然后新策略可能和旧策略差别很大。在强化学习里策略变了数据分布就变了之前采的数据可能完全失效。这就像你根据去年的地图导航但今年路全改了。PPO的做法是限制新旧策略之间的差异。它用新旧策略的概率比值ratio new_prob / old_prob然后把这个比值裁剪到[1-epsilon, 1epsilon]之间通常epsilon取0.2。如果比值超出这个范围梯度就被截断不再鼓励继续往那个方向更新。ratio torch.exp(new_log_probs - old_log_probs) surr1 ratio * advantages surr2 torch.clamp(ratio, 1 - clip_eps, 1 clip_eps) * advantages loss -torch.min(surr1, surr2).mean()这个clip操作是PPO的精髓。它保证了每次更新不会太激进同时又不完全阻止更新比TRPO那种硬约束更实用。4.2 PPO的关键参数与调参经验PPO的参数比前面几个算法多我列一下自己常用的配置参数常用值说明clip_epsilon0.2裁剪范围太小更新慢太大不稳定gamma0.99折扣因子长任务可以设0.995lambda0.95GAE参数控制偏差方差权衡learning_rate3e-4比监督学习小一个量级epochs_per_update10同一批数据重复训练轮数batch_size64每次梯度更新的样本数rollout_length2048每次采集多少步数据再更新lambda这个参数值得多说一句。它用在GAE广义优势估计里控制优势估计的偏差和方差。lambda0时只用一步TD误差偏差大方差小lambda1时用整条轨迹回报偏差小方差大。0.95是个经验平衡点。我调PPO最常动的是learning_rate和clip_epsilon。如果训练曲线震荡厉害先把学习率降一半如果学得太慢把clip_epsilon从0.2调到0.3试试。4.3 用PPO训练机械臂抓取任务的完整流程我拿一个简化的机械臂抓取任务举例说明PPO的完整落地流程。环境用PyBullet搭建状态是关节角度和末端位置动作是六个关节的力矩奖励是末端到目标物体的距离负值加上抓取成功的额外奖励。第一步是环境封装。把环境的reset和step包成标准接口确保状态是numpy数组奖励是标量done是布尔值。这一步看起来简单但很多bug都出在这里比如状态维度对不上、奖励没归一化。第二步是网络定义。Actor和Critic可以共享底层特征提取层也可以完全分开。我一般先分开等训练稳定了再尝试共享。Actor输出高斯分布的均值和标准差Critic输出一个标量状态价值。第三步是数据采集。用当前策略跑rollout_length步存下状态、动作、奖励、done、旧策略的log概率。注意这里要存旧策略的log概率因为PPO更新时要用它算ratio。第四步是优势计算。用GAE从后往前算每一步的优势值。这一步容易写错建议对照公式仔细检查。第五步是多轮更新。把采集的数据打乱分成小batch跑epochs_per_update轮。每轮里算ratio、算clip后的损失、反向传播。第六步是日志与评估。每隔几个update跑一次确定性策略不采样直接取均值的评估记录平均回报和成功率。训练时的采样回报波动大确定性评估更能反映真实水平。我跑这个任务大概需要200万步左右达到稳定抓取单卡训练时间在几个小时。如果奖励设计得好收敛会更快。奖励设计是强化学习里最玄学的部分我的一般原则是稠密奖励引导方向稀疏奖励定义目标。比如距离负值是稠密的抓取成功是稀疏的两者加权相加。5. 常见问题与排查技巧实录5.1 训练不收敛的排查清单强化学习最让人崩溃的就是训练不收敛。我整理了一份排查顺序按这个走能解决大部分问题现象可能原因排查方法回报一直不涨奖励设计有问题打印每步奖励看是否有正反馈回报震荡剧烈学习率太大降学习率加梯度裁剪前期涨后期崩过拟合或策略崩溃检查clip参数加熵正则完全随机表现探索不足或网络没更新检查epsilon衰减、梯度是否为0收敛到局部最优探索策略太保守增大熵系数提高初始探索熵正则是个好东西。在损失里加一项-entropy_coef * entropy鼓励策略保持一定的随机性。entropy_coef一般从0.01开始任务越复杂可以设越大。5.2 奖励设计与归一化的实操心得奖励设计我踩过的坑最多。早期做迷宫任务我把奖励设成“到达终点1其他0”结果智能体学会了原地转圈——因为随机走也可能碰巧到终点但概率极低大部分时间回报都是0梯度信号几乎没有。后来改成“每靠近终点一步0.1远离-0.1到达10”学习速度立刻上来了。这就是奖励塑形的作用。但奖励塑形也有风险如果塑形奖励设计不当智能体可能学会刷奖励而不完成真正目标。比如它可能学会在终点附近来回走因为每靠近一步都有分。我的经验是塑形奖励要逐渐衰减最终让原始稀疏奖励占主导。或者用课程学习先从简单目标开始逐步增加难度。奖励归一化也很重要。如果奖励范围是0到1000梯度会很大如果范围是0到0.001梯度几乎为0。我通常用运行均值标准差来归一化奖励或者手动缩放到[-1, 1]区间。5.3 并行环境与训练加速的实用技巧单环境采样太慢是强化学习的另一个痛点。PPO这类on-policy算法需要大量数据单线程跑一天可能才几十万步。解决办法是并行环境。最简单的方式是用gym.vector_env或者自己写多进程。每个进程跑一个环境实例采集完数据后汇总。这样采样速度能提升几倍到几十倍取决于CPU核数。但并行环境有个坑如果环境有随机性不同进程的随机种子要设不同否则采到的数据高度相似。另外并行环境下的batch norm要小心因为不同进程的数据分布可能不一样。我自己的配置是8个并行环境每个环境跑256步一次rollout总共2048步。这个配置在16核机器上跑采样几乎不占时间瓶颈在梯度更新。注意并行环境不是越多越好。环境太多会导致数据相关性下降反而影响学习效率。一般4到16个比较合适。5.4 从仿真到现实的迁移注意事项如果你做的是机器人方向迟早要面对sim-to-real的问题。仿真里训练好的策略搬到真机上可能完全不能用。原因有很多摩擦、延迟、传感器噪声、执行器误差。我在机械臂任务上试过几种迁移方法。最简单的是域随机化训练时随机改变仿真环境的物理参数比如质量、摩擦系数、关节阻尼。这样策略见多识广对参数变化不敏感。另一种是系统辨识先测真机的实际参数把仿真调得尽量接近真机再训练。但真机参数往往测不准所以通常和域随机化结合使用。还有一个细节是观测延迟。仿真里状态是即时的真机上传感器有延迟。训练时可以在观测里加随机延迟让策略适应这种情况。这些方法都不能保证100%成功但能显著提高迁移成功率。我的经验是域随机化范围要覆盖真机可能的参数波动但也不能太大否则策略会学得太保守。6. 算法选型与学习路径建议6.1 什么任务该用什么算法选算法这件事我总结了一个简单的决策流程动作是离散的、状态空间不大先试Q-learning不行再上DQN动作是离散的、状态是图像或高维DQN及其变体动作是连续的策略梯度或PPO任务奖励稀疏、需要大量探索PPO加熵正则或者试试SAC需要离线数据学习CQL、IQL这类离线强化学习算法PPO是我最常用的默认选择因为它在大多数任务上都能跑出不错的结果调参也不算太敏感。如果PPO效果不好我会先检查奖励设计和环境封装而不是急着换算法。6.2 从Q-learning到PPO的学习路线图如果你刚开始学我建议按这个顺序走第一周Q-learning加网格世界。目标是理解时序差分和epsilon-greedy。代码量不到100行但能建立核心直觉。第二周DQN加CartPole。目标是理解经验回放和目标网络。用PyTorch写大概200行。第三周REINFORCE加CartPole。目标是理解策略梯度和方差问题。对比DQN感受两种范式的区别。第四周Actor-Critic加LunarLander。目标是理解优势函数和GAE。第五周PPO加MuJoCo或PyBullet任务。目标是掌握工业界最常用的算法。这个路线走下来大概一个月能对强化学习有比较完整的认识。每步都要自己写代码不要直接调库。调库虽然快但出了问题你不知道怎么排查。6.3 推荐的学习资源与代码库论文方面DQN的Nature论文、PPO的原始论文、GAE的论文这三篇必读。读论文时不要纠结所有数学细节先抓住核心思想和算法流程。代码库我推荐几个OpenAI的Spinning Up代码清晰文档详细适合入门Stable-Baselines3工业级实现适合直接用在项目里CleanRL单文件实现适合想深入理解细节的人。我自己的习惯是先用Stable-Baselines3跑通任务确认环境和奖励没问题再自己手写一遍算法对比两者的训练曲线。如果自己写的版本效果差很多说明某个细节没实现对这时候去翻CleanRL的代码对照。最后分享一个我踩过的最大的坑不要同时改多个东西。强化学习实验的随机性很大如果你同时改了学习率、网络结构和奖励函数结果变好了你也不知道是哪个起的作用。每次只改一个变量跑至少三个随机种子取平均曲线。这样虽然慢但结论可靠。我早期为了快一次改一堆结果浪费了更多时间在无效实验上。
返回列表