ARTICLE DETAIL

资讯详情

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

强化学习算法入门:从Q-Learning到DQN与PPO一次讲透

强化学习算法入门:从Q-Learning到DQN与PPO一次讲透 今天是“更弱智的算法学习”系列更新到 day35主题还是算法但我今天决定把火力集中在“强化学习算法”上。后台搜索词里“dqn算法matlab”“ppo算法matlab”“深度强化学习算法”“强化学习q算法”这几天刷屏式地涨评论区也一堆人问面试到底要不要复习强化学习前面三十多天我把排序、枚举、剪枝、KMP、匈牙利这些经典货都盘了一遍今天轮到强化学习这个让很多人“看了三天还在起点”的硬骨头。这篇笔记会写得比标题还弱智先讲明白它在干什么再手把手写一个能跑的Q-Learning最后把DQN、PPO、MADDPG、MAPPO这些名词一次捋顺。1. 为什么 day35 我决定把强化学习彻底盘明白1.1 从“算法工程师面试”这个热搜词说起先别急着打开 LeetCode 刷题。我翻了后台近一周的算法类搜索记录排在最前面的除了“暴力枚举算法”“排序算法”“kmp算法”这些老朋友还有一大串强化学习相关词“dqn算法matlab”“ppo算法matlab”“maddpg算法”“mappo算法”“深度强化学习算法”。“算法工程师面试”这个词也被顶得很高说明很多人正在为面试抱佛脚。这里面藏着一个很有意思的信号大家既知道强化学习是热点又不知道从哪儿下手。知乎上关于“强化学习怎么入门”的帖子收藏量动辄几万但真正能把 Q-Learning 手推一遍的人少之又少。更尴尬的是很多人背了 DQN 和 PPO 的名词解释面试官一问“为什么 DQN 需要目标网络”当场就卡住。Day35 这篇笔记就是冲着这个痛点来的把强化学习当成一个普通算法去理解而不是当成黑魔法。我今天想做到三件事让零基础的人知道强化学习解决什么问题让有一点基础的人能写出一个不收敛都难的 Q-Learning 小例子让准备面试的人能接住关于 DQN 和 PPO 的追问。1.2 强化学习和我们熟悉的排序、枚举、剪枝到底哪里不一样老读者知道这个系列前三十多天基本都在啃“有一个输入给你一个输出”的算法冒泡排序就是比较交换暴力枚举就是循环套循环剪枝就是提前砍掉不可能的分支。这类问题的共同点是答案的正确性有明确标准规则是静态的。强化学习完全不是这个路子。它没有标准答案只有一条一条的反馈信号。这就像教一只小狗捡球你不能直接告诉它“把嘴张开、咬住球、跑回来”这三步分别该怎么做你只能在它做对的时候给一颗零食做错的时候不给。小狗通过不断试错慢慢学会一套策略。在强化学习里这个“小狗”叫 Agent智能体它待的世界叫 Environment环境它每做一个动作都会得到一个 Reward奖励它用来决定“在什么状态下做什么动作”的那套规则叫 Policy策略。算法工程师要做的不是直接写死规则而是写一个让 Agent 自己通过试错找出最优策略的“学习程序”。这种“用反馈驱动学习”的范式和传统算法从思维方式上就是两码事。维度传统算法排序、枚举、KMP强化学习算法Q-Learning、DQN、PPO输入输出输入确定输出要求正确没有标准答案只有奖励信号数据来源给定样本或直接遍历Agent 与环境的交互轨迹关键操作比较、交换、循环、剪枝试错、探索、利用、奖励累加最优解判断结果与目标比对累计奖励最大化典型场景刷题、竞赛、数据结构游戏AI、机器人控制、推荐系统这么一对比就清楚了刷 LeetCode 练的是“在明确规则下把解算出来”而强化学习练的是“在不确定的交互中把策略找出来”。两者都叫算法但底层哲学完全不同。2. 核心细节Q-Learning 和 DQN 到底在算什么东西2.1 先用一个更弱智的比喻小老鼠走迷宫为了避免一上来就被公式劝退我先用一只虚拟小老鼠讲一遍强化学习的核心故事。假设迷宫有 5 个格子老鼠从格子 1 出发想走到格子 5。格子 3 里有陷阱踩到就扣分格子 5 里有奶酪踩到就加分。老鼠每走一步可以向左或向右。它不知道路在哪只能瞎试。如果我用最“弱智”的方式教它我会准备一张表格行是格子编号列是“向左/向右”两个动作表格里填的是“这个状态下做这个动作大概值多少分”。这个表格就是 Q 表。老鼠每走一步都会看一眼 Q 表做一个动作收到一个奖励然后更新 Q 表里对应的格子。一开始表里全是 0它只能乱走走了几步发现格子 3 是陷阱就试着把“走向格子 3 的动作”分数调低某一次踩到格子 5 吃了奶酪就把“走向格子 5 的动作”分数调高。走几百上千次之后Q 表里的分数会慢慢稳定老鼠只要每次挑分数最高的动作走就能吃到奶酪。这就是 Q-Learning 的全部直觉。它不聪明但特别好使是几乎所有现代强化学习算法的地基。2.2 Q 表更新公式拆解为什么是 r γ·max Q(s,a)老鼠的故事讲完公式绕不开了。Q-Learning 的核心更新公式长这样[ Q(s,a) \leftarrow Q(s,a) \alpha \left[ r \gamma \cdot \max_{a} Q(s,a) - Q(s,a) \right] ]看不懂很正常我一行一行拆(Q(s,a))当前 Q 表里“在状态 s 做动作 a”的估计值。你可以把它理解为“这个动作的长期价值”。(r)这一步实际拿到的即时奖励。踩陷阱就是负分吃奶酪就是正分。(s)做完动作后到达的新状态。(\max_{a} Q(s,a))在新状态 s 下Q 表里所有动作里分数最高的那个。它代表“未来最乐观的收益”。(\gamma)折扣因子0 到 1 之间。它用来告诉算法未来的奖励值多少钱。(\gamma 0.9) 就说明未来奖励打九折(\gamma) 越接近 1Agent 越有远见。(\alpha)学习率决定新信息覆盖旧信息的程度。整个公式翻译成人话就是新的估计值 旧估计值 学习率 ×即时奖励 折扣后的未来最佳收益 - 旧估计值。括号里那一坨 (r \gamma \max Q(s,a)) 被称为目标值它用“这次实际拿到的奖励 未来预期的最大值”来修正旧的估计。反复迭代后Q 值会收敛到真实的最优动作价值。为什么不能只看即时奖励因为很多任务要牺牲眼前利益换取长期收益。下棋时送掉一个卒是为了赢棋投资里现在亏一点是为了以后赚更多。如果你只按即时奖励做决策Agent 就变成了近视眼永远学不会长线操作。折扣因子 (\gamma) 就是专门用来调“眼光有多远”的旋钮。2.3 DQN 要解决的两个坑经验回放、目标网络Q-Learning 在格子迷宫这种状态少、动作少的问题上非常好用但一旦遇到 Atari 游戏那种屏幕像素有上百万种组合的环境Q 表就彻底炸了根本不可能把每种状态都列成一行。深度强化学习算法里的 DQNDeep Q-Network给出的方案是用神经网络替代 Q 表输入状态输出每个动作的 Q 值估计。但把 Q-Learning 直接套上神经网络会翻两个大车。第一个车是“样本相关性”。强化学习产生的数据是连续轨迹相邻几步的状态高度相似如果你按顺序拿这些数据去训练网络模型会疯狂震荡今天学会明天忘光。第二个车是“目标不稳定”。公式里的目标值 (r \gamma \max Q(s,a)) 用的还是网络自己算出来的 Q 值相当于拿自己的输出当标准答案边追边跑很容易原地打转。DQN 的解法非常工程化。针对样本相关性它搞出一个“经验回放”机制把 Agent 每一步的状态、动作、奖励、新状态存进一个缓冲池训练时随机抽样打乱顺序。这样每次梯度更新都像在重新温习不同时期的经历相关性被打破数据利用率也提高。针对目标不稳定它维护一个“目标网络”每隔一定步数才把主网络的参数复制过去平时目标网络冻结不动用来稳定地计算目标值。等于把标准答案固定下来让主网络追着一个固定的靶子练练完一个阶段再更新靶子。我当年第一次看 DQN 论文时也觉得这两个技巧是“工程补丁”后来自己复现才意识到没有经验回放和目标网络的 DQN训练曲线就是一条心电图根本收敛不了。这两个补丁不是可选项是保命项。2.4 从 Q-Learning 到 DQN再到 PPO策略梯度时代Q-Learning 和 DQN 属于“基于价值”的方法先把每个动作值多少钱估准再挑最值的动作执行。但这种思路有个天花板动作空间连续时没法用。机器人要输出“关节转几度”不可能穷举所有角度算 Q 值。于是有了“基于策略”的方法。思路反过来不估价值直接用一个带参数的神经网络表示策略把“状态”映射成“动作概率分布”然后通过梯度上升让给高奖励的动作增大概率。这类方法里最出圈的就是 PPOProximal Policy Optimization。PPO 的核心是“信任区域”。它每次更新参数时会限制新策略和旧策略之间的差距不能太大防止一次更新过猛把策略推崩。论文里用了一个截断函数如果新旧策略比值太大就把收益剪掉尽量稳健地更新。这个设计让 PPO 在训练稳定性和效果之间取得了很好的平衡所以现在工业界聊强化学习十个里有八个在聊 PPO。再看热词里那两个看着吓人的词MADDPG 和 MAPPO。它们都是多智能体强化学习算法。MADDPG 是 actor-critic 框架在多智能体场景下的扩展每个智能体有自己的 actor 和 critic但 critic 能看到所有智能体的动作相当于每个智能体有一个“上帝视角”的教练训练时会引用全局信息评估自己的动作。MAPPO 就更直接把单智能体的 PPO 改造成多智能体并行版本所有智能体共享一套策略参数去训练在很多协作任务里对比 MADDPG 效果更稳。面试官如果问这两个其实想听的就是“你把 single-agent 的方法怎么扩展到 multi-agent”。3. 手把手实操过程从零写一个 Q-Learning再把它改成 DQN3.1 场景选择一个 5x5 格子有宝藏也有陷阱纸上谈兵没意思我直接从 day35 的训练记录里翻出一个最简单又能看效果的环境5x5 的网格世界Agent 从左上角出发目标在右下角中间随机放两个陷阱和一个加分宝藏。网格世界的好处是状态空间小只有 25 个位置每个位置可选动作只有 4 个上、下、左、右Q 表就是 25 行 4 列的矩阵调试起来非常直观。我之前用这个环境给没接触过强化学习的朋友讲半小时就能讲完整个流程比一上来就打超级玛丽舒服得多。奖励设计是这样每走一步扣 0.1 分逼 Agent 学会找最短路径而不是原地绕圈踩到陷阱扣 5 分并立刻回合终止踩到宝藏加 3 分到达终点加 10 分并回合终止。注意这里没有死板的“罚死你”扣分是为了引导行为这也是强化学习调参里面最需要手感的地方。环境逻辑不复杂我用 Python 写了一个很直白的类。为了省篇幅状态直接用坐标 ((x, y)) 表示动作用 0、1、2、3 分别代表上下左右。越界动作直接返回原地并给一个小的负奖励这样 Agent 会自然学会不走墙。3.2 参数计算与选择学习率、折扣因子、epsilon 初始化和退火写 Q 表之前先把四个关键参数定下来并说清楚为什么这么选学习率 alpha 取 0.1。太小学得慢太大会震荡0.1 在格子世界这种小规模任务里是比较通用的起点。折扣因子 gamma 取 0.9。这个环境最长路径也就是十来步gamma0.9 时长远收益还能传回来不会让 Agent 只顾眼前。epsilon 从 0.9 开始每回合乘以 0.995 退火最低不低于 0.01。前期高 epsilon 让 Agent 大量探索后期低 epsilon 让 Agent 更多利用已经学会的策略。没有退火就 90% 的时间在乱走收敛会慢很多。回合数设 1000 回。网格世界很小1000 回足够 Q 表收敛每次训练不到一秒非常适合用来调试。DQN 版本的参数会稍有变化经验池容量 10000每次训练抽样 batch size 32目标网络每 100 步同步一次优化器用 Adam学习率 0.001。网络结构用两层全连接输入层 25 个神经元把格子 one-hot 成向量中间 64 个 ReLU输出层 4 个神经元对应四个动作的 Q 值。这里要强调一个最容易踩的坑gamma 设到 0.99 不是每个问题都好。之前我调试一个链条更长的任务时gamma 调高确实让 Agent 更有远见但在这种有陷阱的小地图里高 gamma 反而会让它为了追远处的宝藏而忽略脚下的陷阱导致训练曲线来回波动。参数不是越大越好要跟任务尺度匹配。3.3 核心代码实现Q-Learning 和 DQN 的骨架代码下面是我实际跑通的 Q-Learning 核心循环去掉了环境类只保留关键逻辑import numpy as np # 初始化 Q 表25 个位置 × 4 个动作 q_table np.zeros((5, 5, 4)) alpha 0.1 gamma 0.9 epsilon 0.9 min_epsilon 0.01 epsilon_decay 0.995 def choose_action(state): if np.random.random() epsilon: return np.random.randint(4) return np.argmax(q_table[state]) def update_q_table(state, action, reward, next_state): best_next np.max(q_table[next_state]) target reward gamma * best_next q_table[state][action] alpha * (target - q_table[state][action]) for episode in range(1000): state (0, 0) total_reward 0 while True: action choose_action(state) next_state, reward, done step(state, action) update_q_table(state, action, reward, next_state) state next_state total_reward reward if done: break epsilon max(min_epsilon, epsilon * epsilon_decay)DQN 版本难啃一点但也只是把 Q 表换成了网络加回放池import torch import torch.nn as nn import random from collections import deque class DQN(nn.Module): def __init__(self): super().__init__() self.net nn.Sequential( nn.Linear(25, 64), nn.ReLU(), nn.Linear(64, 4) ) def forward(self, x): return self.net(x) q_net DQN() target_net DQN() target_net.load_state_dict(q_net.state_dict()) optimizer torch.optim.Adam(q_net.parameters(), lr0.001) replay_buffer deque(maxlen10000) for episode in range(2000): state (0, 0) total_reward 0 while True: state_t torch.tensor(flatten(state), dtypetorch.float32) if random.random() epsilon: action random.randint(0, 3) else: action q_net(state_t).argmax().item() next_state, reward, done step(state, action) replay_buffer.append((state, action, reward, next_state, done)) if len(replay_buffer) 128: batch random.sample(replay_buffer, 32) # 计算 target / 优化步骤省略 state next_state total_reward reward if done: break if episode % 100 0: target_net.load_state_dict(q_net.state_dict())代码只是骨架但逻辑是完整的。新手从 Q 表版本先跑通再往上加经验回放和目标网络能非常清楚地感受到每一步改动带来的稳定性提升。3.4 训练效果观察Q 表可视化与曲线解读训练结束后收获最大的不是那个“能到达终点”的结果而是观察 Q 表和奖励曲线的变化过程。前 50 回合累计奖励基本是负数因为 Agent 完全在乱撞经常掉进陷阱。原因很简单epsilon 还在 0.9 左右大部分动作都是随机走踩陷阱概率自然高。到 200 回合左右曲线开始拉平Agent 偶尔能找到一条到终点的路但路径还很绕。到了 600 回合以后累计奖励逐渐逼近最大值说明 epsilon 降到 0.3 以下Agent 更多利用 Q 表里的高价值动作路径越来越短。把 Q 表渲染成热力图也很有感觉。初始时所有格子颜色一致训练中期陷阱附近的格子的 Q 值明显变暗靠近终点的格子开始发亮。训练完成后从起点开始沿着每个状态的最大 Q 值对应的动作走路径是一条干净利落的“上-右-上-右-下-下-右”最短路线。这一瞬间你会觉得前面学的公式全部串起来了。我建议所有入门者都亲手把 Q 表打印出来看看。那是强化学习最直观的“脑内状态图”你能看到 Agent 是在哪里“学聪明”的也能看出哪些地方一直学不好很可能就是奖励设计出了问题。4. day35 踩坑实录强化学习最容易翻车的四个地方4.1 训练不收敛先检查奖励尺度和 epsilon 退火速度最常见的翻车现场是“训练了三千回合奖励曲线还在原地波动”。很多人第一反应是改网络结构其实八成问题出在奖励设计和探索策略上。奖励数值特别容易失控。比如把“踩陷阱扣 5 分”改成“扣 50 分”Agent 会因为单步奖励过于巨大在陷阱附近变得畏畏缩缩宁可绕远路也不靠近甚至卡在起点不动。反过来如果奖励只有 ±0.1信号又太弱学几百回合也看不出来变化。调参顺序应该是先观察奖励的量级是否与任务步数匹配再考虑网络和超参。epsilon 退火过快也会导致不收敛。如果你把 0.995 的衰减系数改成 0.9epsilon 从 0.9 跌到 0.1 只需要 20 回合Agent 在还没摸清环境的情况下就过早进入“贪心”阶段永远困在局部最优里。退火太慢也不行3000 回合后还在 60% 概率乱走收敛速度同样感人。我现在的习惯是把退火曲线画出来让 epsilon 在训练总回合数的前 60% 从高位线性衰减到 0.1后面保持小值微调。4.2 DQN 训练震荡目标网络没有及时冻结DQN 领域最经典的坑就是忘掉冻结目标网络或者同步间隔设置得太短。目标网络的作用是给 Q 值的更新提供一个稳定的靶子。如果每 10 步就同步一次目标值也在快速变化主网络刚追上新目标目标又跑了训练自然震荡。在我的 5x5 环境里100 步同步一次就很稳到了复杂环境可以试试 1000 步甚至 2000 步同步一次。排查方法也很简单训练时把每 100 回合的损失值loss单独记录下来。如果 loss 曲线一直上下乱跳而 reward 曲线也在拉锯优先检查同步步数如果 loss 平稳但 reward 不涨大概率是奖励设计的问题。把两个指标分开看能省下大量瞎猜的时间。4.3 探索与利用的平衡别让你的 Agent 是个“复读机”还有一个更隐蔽的问题Q 表已经完全收敛到一个局部最优但 Agent 的表现依然很差。原因在于探索不够。当 epsilon 降到 0.01 以后Agent 几乎完全按照 Q 表的最大值行动如果 Q 表早期错误地把某个次优方向的分数拱高了它就会一直走那条路再也没有机会试别的路。这就好比一个人考试只做自己会的题永远不会发现自己还有别的解法。解决办法不止一个。最简单的就是提高 epsilon 下限比如不让它低于 0.05留下一点随机探索的空间。更高级一点的做法是乐观初始化把 Q 表初始值设成正数而非 0鼓励 Agent 觉得每条路都有希望从而更愿意探索。我调试时经常用后者效果立竿见影。4.4 面试官最爱的追问DQN 和 PPO 的区别、MADDPG 和 MAPPO 的取舍因为后台“算法工程师面试”和“maddpg算法”“mappo算法”热度很高我把面试里最常见的强化学问答做成了速查表方便 day35 的读者直接背熟问题核心答案要点DQN 和 PPO 的区别DQN 是基于价值的先估 Q 值再选动作PPO 是基于策略的直接优化策略网络DQN 为什么不稳定样本相关性强、目标值在变需要经验回放 目标网络动作空间连续时选什么策略梯度类算法比如 PPO、SAC、DDPGMADDPG 和 MAPPO 怎么选都用 actor-critic 思路扩展多智能体MADDPG 有全局 critic 指导协作性更强MAPPO 实现简单、并行高效适合大规模多智能体场景折扣因子 gamma 的作用控制未来奖励的权重gamma 越大越有远见但收敛越慢面试官听你说完这些并不会惊叹你背了多少公式而是在意你有没有真正上手调过参数。所以实操部分不是凑字数而是你面试时能讲出来的“真实手感”。5. 把后台热词串一遍day35 顺带复盘其它必刷算法5.1 暴力枚举、剪枝和分治在强化学习里的影子不用觉得今天专攻强化学习前面的“暴力枚举算法”“剪枝算法”就白学了。其实它们在强化学习里都有对应物。暴力枚举的本质是遍历所有可能性。Q-Learning 在状态空间特别小的时候工作方式就是“动态规划式的暴力枚举”反复试每条路径最终把所有状态动作对的价值都估一遍。只不过真实环境状态太多我们才需要神经网络来泛化。剪枝算法更像强化学习里的“提前终止”。在网格世界里踩到陷阱立刻结束回合把从陷阱出发的所有后续路径全部剪掉这正是剪枝思想在试错学习中的体现。分治算法也能看到影子很多复杂任务会拆成子任务先学基础技能再组合。所以我一直不建议把算法分成“刷题算法”和“工业界算法”两个世界。底层思维是相通的枚举是思路剪枝是效率迭代是学习组合是架构。5.2 那些常驻热榜的排序与枚举为什么 day35 还要再提“冒泡排序算法c”“堆排序算法”“归并排序算法”“数据结构排序算法”“java 蓝桥杯算法题目”“leecode必刷基础算法题”这些词这几天又上来了。有个现象很有意思不管我写多深的多智能体强化学习后台搜索量前十永远有基础排序。这说明算法学习的真实生态是分层的。一部分人在卷强化学习和语义分割另一部分人刚学完 C 语法正对着冒泡排序和堆排序发愁。我在 day35 里特意提它们是想给刚开始的同学一句定心话排序算法背十遍不如手写一遍手写十遍不如改错一遍。把冒泡排序的两个嵌套循环画成交换图把归并排序的递归树画出来比看十篇教程都管用。你甚至可以拿排序算法去理解今天的强化学习归并排序是分治堆排序是贪心的变体快速排序的 benchmark 思想就像 epsilon 退火里的“先用宽泛策略探索再缩小范围精调”。知识点之间永远能互相映射。5.3 算法工程师面试的真相不止考手撕代码后台“算法工程师面试”这个词单独出现的频次很高很多人以为算法面试就是手撕排序和并查集。真实情况不是这样。如今算法岗位的分工已经非常细化。做搜索推荐的会聊随机森林回归、特征提取算法做机器人的会聊 PID、粒子群算法、DWA 路径规划做视觉的会聊语义分割、DBnet 算法数学原理做量化或控制系统的会接触 MPPT 这类工程算法。这些都是“算法”但跟 LeetCode 上的题目完全不是一个物种。我的建议是简历上写了什么项目就把那个项目背后真正用到的算法原理吃透。面试官问你随机森林回归不是在考察你能背多少篇论文而是想确认你对树模型、bagging、特征重要性的理解是否经得起追问。今天这篇强化学习笔记也一样如果你将来面试的岗位写了强化学习能亲手跑通一个小规模 Q-Learning比背十篇 survey 都有说服力。这个系列写到 day35我自己最大的变化是心态算法别再分“难的”和“简单的”只看你手上有没有一个能跑起来的例子。Q-Learning 是我见过的最适合当“第一个例子”的强化学习算法代码短、直观、可调试。如果你今天看完也想上手记住我最后给你的小技巧先在一个 3x3 或者 5x5 的简化环境里把训练逻辑调通再加奖励复杂度。环境越小你越能看清楚算法每一步到底在做什么。下一次 day36我准备把 PPO 用同样的“更弱智”方式拆一遍到时候我们接着拿策略梯度开刀。
返回列表