深度强化学习入门:从倒立摆游戏看懂AI如何“自己学会走路”
谷歌AlphaGo击败李世石、OpenAI的Dota2 AI碾压人类冠军、特斯拉自动驾驶不断进化——这些AI奇迹的背后都依赖同一项核心技术强化学习。今天我们不堆砌复杂的数学公式而是通过一个经典的“倒立摆”小游戏从零开始把强化学习的每一个核心概念讲清楚、讲透彻。全文包含完整可运行的Python代码你可以在自己的电脑上亲手训练出一个会自己“顶杆子”的AI。建议收藏慢慢消化。1 强化学习是什么一个“训练小狗”的故事就能讲明白1.1 从狗子学坐下说起你养了一只小土狗。刚到家时它什么都不会乱拉乱尿、扑人、咬拖鞋。你想让它学会“坐下”。你会怎么做当它坐下时你立刻给一块鸡肉干正奖励。当它没坐下时你不给零食甚至轻声说“不”没奖励/负反馈。一开始狗子是乱蒙的。但蒙对几次之后它发现了一个规律“只要我屁股一着地就有肉吃”于是它坐下的次数越来越多。最后不管在客厅、公园还是宠物店只要你喊“坐”它立刻坐得端端正正。这个“训练小狗”的过程就是强化学习的完整写照。1.2 把故事翻译成强化学习的术语狗子学坐下强化学习术语数学符号可选小狗智能体Agent—你家包括你、地板、零食环境Environment—你喊“坐”的那一刻状态Statess小狗做“坐下”或“不坐下”动作Actionaa你给肉干或“啧”一声奖励Rewardrr小狗最后学会的“听见坐就坐下”策略Policy( \pi(as) )一句话定义强化学习强化学习 智能体通过不断与环境交互、试错根据获得的奖励反馈学会“在什么状态下做什么动作”才能最大化累积奖励。1.3 强化学习的核心交互循环强化学习由智能体和环境两部分组成。它们之间的交互可以用下面这个循环来描述每一步称为一个时间步智能体观察当前状态 StSt。智能体根据策略选择一个动作 AtAt。环境执行该动作返回新的状态 St1St1 和一个即时奖励 RtRt。智能体收到奖励更新自己的策略然后进入下一轮。智能体的唯一目标在长期过程中从环境里拿到尽可能多的总奖励。2 强化学习的“Hello World”倒立摆CartPole环境每个领域都有一个最经典的入门例子编程入门print(Hello World)电子入门点亮一颗LED强化学习入门倒立摆CartPole2.1 倒立摆长什么样想象一辆小推车放在一条水平的轨道上。推车上立着一根木杆。你可以控制推车向左或向右移动。目标让木杆保持直立不要倒下。这跟你小时候用手掌顶铅笔是一模一样的道理铅笔往左歪手就往左移往右歪手就往右移。2.2 游戏规则超简单项目具体内容智能体小推车动作空间两个动作0 向左推1 向右推状态空间4个连续数值小车位置、小车速度、杆子角度、杆子角速度奖励规则每存活一步获得1奖励游戏结束条件① 杆子倾斜角度超过12°倒下② 小车偏离中心超过2.4个单位掉轨③ 成功坚持200步满分通关关键洞察这是一个典型的“延迟满足”问题。如果智能体只贪图眼前利益比如总是朝一个方向推短期内可能没问题但长远来看必然倒下。强化学习的精髓就在于平衡“探索”探索未知动作与“利用”使用已知好动作。3 把游戏“翻译”成AI能看懂的数字AI不认识“左”“右”“角度”只认识数字。所以我们必须把一切变成数字。3.1 状态State每一时刻环境会输出一个状态用 ss 表示。倒立摆的状态是一个包含4个浮点数的数组[ 0.02, # 小车位置0是正中间负数是左边正数是右边 0.15, # 小车速度负数是向左移动正数是向右移动 -0.03, # 杆子角度负数是向左歪正数是向右歪弧度 -0.10 ] # 杆子角速度负数是正在向左倒正数是正在向右倒对AI来说整个“世界”就是这4个数字。它看不到杆子只看到 -0.03 这个数字。3.2 动作Action动作 aa 只有两个取值0 # 向左推 1 # 向右推3.3 奖励Reward奖励 rr 就是分数每存活一步r 1。如果游戏结束杆子倒下或小车出界则不再有新的奖励。3.4 策略Policy——智能体的“大脑”策略就是“看到什么状态就做什么动作”的规则用字母 ππ读“派”表示。在数学上策略通常输出的是一个概率分布而不是一个确定的动作。例如π(向左推∣s)0.7,π(向右推∣s)0.3π(向左推∣s)0.7,π(向右推∣s)0.3也就是说在状态 ss 下AI有70%的概率选向左推30%的概率选向右推。然后AI按这个概率随机抽样来决定实际动作。为什么要随机如果永远只选概率最大的动作就可能错过更好的长远策略。就像你天天吃同一家外卖永远不会发现隔壁新开的店更好吃。探索Exploration偶尔尝试概率低的动作。利用Exploitation大多数时候选择概率高的动作。平衡两者是强化学习的核心挑战之一。3.5 策略的数学表达π(a∣s)P(Ata∣Sts)π(a∣s)P(Ata∣Sts)这个式子读作“在状态 ss 的条件下智能体采取动作 aa 的概率”。如果这个 ππ 函数是一个神经网络那么我们就进入了深度强化学习的领域——这也是当今AI界最热门的方向之一。4 第一个可运行的例子随机智能体在让AI学会学习之前我们先写一个完全不学习的智能体不管状态如何随机向左或向右推。它肯定玩不好但可以让我们看清一局游戏的完整流程。4.1 安装依赖首先确保安装了必要的库pip install gym0.25.2 numpy4.2 代码实现import gym import random # 1. 创建倒立摆环境 env gym.make(CartPole-v0) # 2. 随机策略完全不看状态随机返回 0 或 1 def random_policy(state): return random.choice([0, 1]) # 3. 玩一局一个 episode state env.reset() # 重置环境得到初始状态 S0 done False # 游戏是否结束 total_reward 0 while not done: env.render() # 弹窗显示画面 action random_policy(state) # 随机选动作 next_state, reward, done, _ env.step(action) # 执行动作 total_reward reward state next_state print(f游戏结束总共获得奖励: {total_reward}) env.close()运行结果你会看到一个小车在屏幕上左右乱推杆子很快倒下得分大概在10~30之间满分200。这个“傻AI”告诉我们没有学习能力再简单的任务也完不成。5 轨迹和回报怎么定义“玩得好”5.1 轨迹Trajectory一局游戏从开始到结束会产生一串数据τ(S0,A0,R0,S1,A1,R1,S2,A2,R2,… )τ(S0,A0,R0,S1,A1,R1,S2,A2,R2,…)这叫做一条轨迹。由于策略和环境都有随机性从同一个初始状态出发每次运行得到的轨迹都可能不同。5.2 回报Return未来的钱要打折假设一局游戏持续了 TT 步每步奖励都是1简单加起来是 TT 分。但是未来的1分和现在的1分价值一样吗显然不一样——今天给你100块钱和一年后给你100块钱你肯定选今天。在倒立摆中如果杆子马上就要倒了那么“现在立刻救一下”比“等两秒再救”重要得多。因此我们需要给未来的奖励打个折扣。这个折扣率叫做折扣因子γγgamma通常取 0.9 或 0.99。折扣总回报Return的定义GtRtγRt1γ2Rt2γ3Rt3…GtRtγRt1γ2Rt2γ3Rt3…例如γ0.9γ0.9奖励全为1运行4步G010.9×10.81×10.729×13.439G010.9×10.81×10.729×13.439越远的奖励贡献越小。5.3 递推公式非常重要从上面的定义可以推出GtRtγGt1GtRtγGt1这个递推关系使得我们可以从最后一步倒着往前计算回报非常高效。5.4 代码实现计算回报def compute_discounted_return(rewards, gamma0.99): 逆序计算折扣总回报 rewards: 列表例如 [1, 1, 1, 1] G 0 for r in reversed(rewards): # 从最后一步往前遍历 G r gamma * G return G # 示例 rewards [1, 1, 1, 1] total compute_discounted_return(rewards, gamma0.9) print(total) # 输出 3.439这个计算方法在几乎所有强化学习算法中都会用到请务必理解。6 价值函数如何判断一个状态是“好”还是“坏”到目前为止我们只能计算一整局的总回报。但AI在学习过程中需要知道当前这个状态到底值不值得杆子角度 -10°快倒了 → 这个状态很糟糕。杆子角度 0.5°很正 → 这个状态很好。状态价值函数Vπ(s)Vπ(s) 就是用来量化“状态好坏”的。6.1 定义Vπ(s)Eπ[Gt∣Sts]Vπ(s)Eπ[Gt∣Sts]通俗解释从状态 ss 出发按照策略 ππ 一直玩下去平均能拿到的折扣总回报。由于策略和环境都有随机性我们取期望值平均值。下标 ππ 表示这个价值依赖于策略——换一个策略同一个状态的价值也会变。6.2 生活类比V(你当前的职位)V(你当前的职位) 你现在工资 未来升职加薪的期望打折后。如果公司前景好即使当前工资不高价值也可能很高。7 贝尔曼方程强化学习的“心脏”贝尔曼方程是强化学习里最重要的公式没有之一。它表达了当前状态价值与下一状态价值之间的关系。7.1 贝尔曼期望方程最简洁的形式Vπ(s)Eπ[RtγVπ(St1)∣Sts]Vπ(s)Eπ[RtγVπ(St1)∣Sts]用大白话说一个状态的价值 眼下这一步能拿到的奖励的平均值 折扣后的下一个状态的平均价值。7.2 展开形式更具体Vπ(s)∑aπ(a∣s)∑s′p(s′∣s,a)[r(s,a,s′)γVπ(s′)]Vπ(s)a∑π(a∣s)s′∑p(s′∣s,a)[r(s,a,s′)γVπ(s′)]这个式子看起来复杂但意思很直接对所有可能的动作 aa 求和乘以选这个动作的概率。对每个动作考虑所有可能跳转到的下一个状态 s′s′乘以跳转概率。括号里是即时奖励 折扣 × 下一状态的价值。7.3 为什么贝尔曼方程如此重要因为它把当前决策的价值和未来决策的价值联系了起来形成了递归结构。几乎所有强化学习算法Q-learning、DQN、A2C、PPO……都直接或间接地使用贝尔曼方程来更新价值估计或策略。你不需要立刻背下展开形式但一定要记住核心思想现在的好状态 现在的收益 未来的好状态打折。8 马尔可夫决策过程MDP——强化学习的数学框架8.1 什么是MDP马尔可夫决策过程Markov Decision Process是强化学习的标准数学模型。它由以下五个要素组成MDP(S,A,P,R,γ)MDP(S,A,P,R,γ)符号含义倒立摆示例SS状态空间R4R4连续AA动作空间{0, 1}( P(ss,a) )状态转移概率物理引擎决定确定性R(s,a,s′)R(s,a,s′)奖励函数存活时1结束时0γγ折扣因子0.998.2 马尔可夫性质Markov PropertyMDP的核心假设是马尔可夫性质下一个状态只取决于当前状态和当前动作与历史无关。P(St1∣St,At,St−1,At−1,… )P(St1∣St,At)P(St1∣St,At,St−1,At−1,…)P(St1∣St,At)通俗理解“未来只取决于现在与过去无关。”在倒立摆中你只需要知道“当前的位置、速度、角度、角速度”就能决定怎么推不需要知道10步前小车在哪里。这个假设极大地简化了问题否则AI需要记住整个历史计算量会爆炸。8.3 状态转移和奖励函数状态转移概率p(s′∣s,a)p(s′∣s,a) 表示在状态 ss 执行动作 aa 后跳转到 s′s′ 的概率。奖励函数r(s,a,s′)r(s,a,s′) 表示在状态 ss 执行动作 aa 并到达 s′s′ 时获得的即时奖励。在倒立摆中状态转移是确定性的给定当前状态和动作下一状态由物理方程唯一确定所以 p(s′∣s,a)1p(s′∣s,a)1 对某个特定的 s′s′其余为0。但在其他环境如棋类游戏对手下棋有随机性中转移概率是随机的。9 奖励稀疏性当AI永远拿不到分9.1 什么是奖励稀疏倒立摆中每一步都有1奖励这叫稠密奖励。但很多现实问题是稀疏奖励例如让机器人把插头插进插座。只有完全插进去的那一步才给奖励1其他所有步都是0。在这种情况下AI可能永远也得不到任何奖励因为一开始完全乱动永远碰不到“插进去”这个状态。这就是奖励稀疏性问题。9.2 常见的解决方法方法通俗解释奖励塑造设计中间奖励例如“靠近插座给0.1分”“对准插孔给0.5分”。课程学习先让插头离插座很近AI轻松成功学会了再慢慢拉远距离。分层强化学习先学“移动到插座附近”再学“对准插孔”最后学“插入”。好奇心驱动鼓励AI探索未见过的状态即使没有外部奖励也能获得内部奖励。10 动手实现一个会学习的AIREINFORCE算法前面我们写的“随机智能体”不会学习。现在我们用神经网络来写一个能真正学习的AI。算法叫做REINFORCE蒙特卡洛策略梯度是策略梯度方法中最简单的一种。10.1 策略网络Policy Network策略网络就是一个神经网络输入当前状态4个数字。输出两个动作的概率例如 [0.3, 0.7]。网络结构输入层(4) → 隐藏层(128) → 隐藏层(128) → 输出层(2) → Softmax → 概率代码实现import torch import torch.nn as nn import torch.nn.functional as F class PolicyNetwork(nn.Module): def __init__(self, state_dim4, action_dim2, hidden_dim128): super().__init__() self.fc1 nn.Linear(state_dim, hidden_dim) # 4 → 128 self.fc2 nn.Linear(hidden_dim, hidden_dim) # 128 → 128 self.fc3 nn.Linear(hidden_dim, action_dim) # 128 → 2 def forward(self, x): x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) logits self.fc3(x) return F.softmax(logits, dim-1) # 输出概率且和为110.2 REINFORCE的核心思想大白话用当前的策略网络玩一局记录每一步的状态、动作、对数概率、即时奖励。计算每一步的折扣总回报 GtGt即“从这一步开始以后能拿多少总奖励”。调整网络参数如果某一步的回报 GtGt 很高 → 让这一步所选动作的概率变大。如果某一步的回报 GtGt 很低 → 让这一步所选动作的概率变小。一句话打得好就奖励这个动作打得不好就惩罚它。数学上我们最大化期望回报等价于最小化以下损失函数Loss−∑tGtlogπ(at∣st)Loss−t∑Gtlogπ(at∣st)负号是因为PyTorch默认做梯度下降而我们要梯度上升。10.3 完整训练代码可直接复制运行# 1. 导入必要的库 # 使用gymnasium替代已废弃的gym库 import gymnasium as gym import torch import torch.nn as nn # 关键修复解决NameError import torch.optim as optim from torch.distributions import Categorical # 2. 定义策略网络 class PolicyNet(nn.Module): def __init__(self): super().__init__() # 定义网络层4个状态输入 - 128个神经元 - 128个神经元 - 2个动作输出 self.fc1 nn.Linear(4, 128) self.fc2 nn.Linear(128, 128) self.fc3 nn.Linear(128, 2) def forward(self, x): x torch.relu(self.fc1(x)) # 使用ReLU激活函数 x torch.relu(self.fc2(x)) logits self.fc3(x) # 使用Softmax将输出转换为动作的概率分布 return torch.softmax(logits, dim-1) # 3. 数据收集函数 def collect_trajectory(env, policy, gamma0.99): 运行一局游戏收集训练所需的数据 返回动作的对数概率列表 和 每个时间步的折扣回报 state, _ env.reset() # gymnasium的reset返回(state, info) log_probs [] # 存储每个动作的logπ(a|s) rewards [] # 存储每个时间步的即时奖励 done False while not done: # 将numpy数组转换为PyTorch张量并添加batch维度 state_t torch.FloatTensor(state).unsqueeze(0) probs policy(state_t) # 获取动作概率分布 dist Categorical(probs) # 创建分类分布 action dist.sample() # 根据概率采样一个动作 log_probs.append(dist.log_prob(action)) # 记录对数概率 # 执行动作 (gymnasium返回5个值而不是4个) next_state, reward, terminated, truncated, _ env.step(action.item()) done terminated or truncated # 游戏结束条件 rewards.append(reward) state next_state # 逆序计算折扣回报 Gt 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-9) return log_probs, returns # 4. 训练主循环 # 创建环境 (训练时不渲染速度更快) env gym.make(CartPole-v1) # 使用CartPole-v1步数上限为500 policy PolicyNet() # 初始化策略网络 optimizer optim.Adam(policy.parameters(), lr0.01) # 使用Adam优化器 for episode in range(500): # 收集一局游戏的数据 log_probs, returns collect_trajectory(env, policy) # 计算损失函数: L - Σ (Gt * log π) loss -torch.cat([lp * G for lp, G in zip(log_probs, returns)]).sum() # 反向传播更新网络参数 optimizer.zero_grad() loss.backward() optimizer.step() # 每50个回合打印一次训练进度 if (episode 1) % 50 0: print(f回合 {episode1}, 平均回报 {returns.mean().item():.2f}) # 5. 测试训练好的策略 # 创建用于测试的环境并开启渲染模式 test_env gym.make(CartPole-v1, render_modehuman) state, _ test_env.reset() done False total_reward 0 while not done: # 测试时直接选择概率最大的动作不进行采样 with torch.no_grad(): probs policy(torch.FloatTensor(state).unsqueeze(0)) action torch.argmax(probs).item() state, reward, terminated, truncated, _ test_env.step(action) done terminated or truncated total_reward reward print(f测试得分: {total_reward}) test_env.close()运行这段代码你会看到AI的得分从十几分逐渐上升到200分满分。它自己学会了“杆子往左歪就往左推往右歪就往右推”的平衡技巧——没有人教过它物理公式。11 总结一张表记住所有核心概念概念通俗解释数学符号智能体做决策的家伙小推车Agent环境外部世界倒立摆游戏Environment状态当前局势位置、速度等ss动作能做的事左/右aa奖励做得好不好1或0rr策略决策规则状态→动作的概率( \pi(as) )轨迹一局游戏的完整记录ττ回报打折后的总收益GtGt价值函数某个状态的好坏程度Vπ(s)Vπ(s)贝尔曼方程价值函数的自洽关系V(s)E[RγV(s′)]V(s)E[RγV(s′)]马尔可夫性质未来只取决于现在( P(ss,a) )探索 vs 利用贪眼前 vs 冒险试新—12 下一步学什么进阶路线图如果你已经完全掌握了本文的内容恭喜你你已经入门强化学习了接下来可以按以下顺序深入学习Q-learning 和 DQN适合离散动作空间如倒立摆、Atari游戏。DeepMind 打砖块、吃豆人的算法。PPO近端策略优化目前工业界最常用的算法。OpenAI 的 Dota2 AI、ChatGPT 的 RLHF 都在使用。SAC柔性演员-评论家适合连续动作控制如机器人关节、自动驾驶方向盘。多智能体强化学习多个智能体同时学习如足球机器人、自动驾驶车流。无论学习哪个方向都离不开本文讲的基础策略、价值函数、贝尔曼方程、探索与利用。写在最后强化学习是AI领域中最接近人类“试错学习”方式的分支。它不需要人类手把手标注数据只需要告诉AI“什么是对什么是错”然后让它自己去摔跟头、爬起来。最终它能学会我们教不了的东西——就像倒立摆的AI从未学过牛顿力学却自己摸索出了平衡的秘诀。如果你觉得这篇文章对你有帮助请点赞、收藏、转发让更多人看到。有任何问题欢迎在评论区留言我会尽力解答。