强化学习极简实战:从手写环境到 Gymnasium 策略迭代 (Policy Iteration)
2026/9/6 4:38:09 网站建设 项目流程

强化学习极简实战:从手写环境到 Gymnasium 策略迭代 (Policy Iteration)

导读:强化学习初学者往往容易被两件事卡住:一是面对 Gym 框架时不知道它底层是怎么和数学公式关联的;二是不知道策略评估和策略提升这两套循环到底在算什么。
本教程以经典的冰湖环境 (FrozenLake-4x4)为例,带你彻底穿透 Gymnasium 封装的本质,手把手掌握基于模型的动态规划核心算法——策略迭代(Policy Iteration)


🎬 最终实机运行效果

经过策略迭代求解后,智能体大脑中形成最优策略矩阵,在冰湖地图上自动避开所有陷阱并以最短路径(6步)直达终点:


目录

  • 一、环境建模的两种范式:手写 move() vs Gymnasium 的 env.P
  • 二、策略迭代核心算法(数学推导与代码实现)
    • 1. 广义策略迭代 (GPI) 双循环闭环
    • 2. 策略评估 (Policy Evaluation) 与贝尔曼期望方程
    • 3. 策略提升 (Policy Improvement) 与均分概率
  • 三、从“离线算脑”到“实机表演”
    • 1. 训练结果收敛可视化
    • 2. 深度理解加权随机抽样 np.random.choice
  • 四、框架进阶:从 Jupyter 历史包袱到 Gymnasium 原生极简渲染
  • 五、完整工程代码结构与运行指南

一、环境建模的两种范式:手写 move() vs Gymnasium 的 env.P

强化学习解决的是马尔可夫决策过程 (MDP),其数学五元组为( S , A , P , R , γ ) (S, A, P, R, \gamma)(S,A,P,R,γ)
对于 4×4 的格子世界,状态S ∈ { 0 , … , 15 } S \in \{0, \dots, 15\}S{0,,15},动作A ∈ { 0 : 左 , 1 : 下 , 2 : 右 , 3 : 上 } A \in \{0:\text{左}, 1:\text{下}, 2:\text{右}, 3:\text{上}\}A{0:,1:,2:,3:}

1. 传统手写环境的思路:单步动态推算

如果我们不借助任何框架,纯手写一个物理规则函数,通常会写一个move(row, col, action)

defmove(row,col,action):# 如果已经掉入陷阱或到达终点,无法再行动ifget_state(row,col)in['trap','terminal']:returnrow,col,0# 根据动作改变坐标ifaction==0:row-=1# 上ifaction==1:row+=1# 下ifaction==2:col-=1# 左ifaction==3:col+=1# 右# 不允许走出地图外面 (边界撞墙截断)row=max(0,min(3,row))col=max(0,min(3,col))# 奖励机制:每走一步扣1分,掉入陷阱扣100分reward=-1ifget_state(row,col)=='trap':reward=-100returnrow,col,reward

2. Gymnasium 的封装思想:全局离线转移查找表 (env.P)

Gymnasium 通过env.unwrapped.P直接向算法暴露了环境的动力学模型(Transition Dynamics)
它不是在运行时一步一步去move,而是在初始化时,就已经把 16 个格子 × 4 种动作可能产生的全部情况都预先计算好,保存为一个嵌套字典:

env.P[state][action]->[(prop,next_state,reward,over),...]

每个元组包含 4 个元素:

  • prop(Probability):状态转移概率P ( s ′ ∣ s , a ) P(s' \mid s, a)P(ss,a)。当设置is_slippery=False时,不打滑,概率为1.0 1.01.0;若打滑,则目标方向及垂直两侧各为1 3 \frac{1}{3}31
  • next_state:执行动作后的下一个状态编号 (0 ∼ 15 0 \sim 15015)。
  • reward:到达下一个状态拿到的即时奖励。
  • over(terminated):是否进入终止状态(掉进冰洞H或到达终点G)。

💡 核心认知差异:为什么 Gymnasium 不需要每步扣分?

  • 手写move的逻辑:每走一步reward = -1,通过负反馈逼迫算法快点到达终点;
  • Gymnasium 的逻辑:普通格子和陷阱reward = 0.0,终点reward = 1.0
    • 为什么智能体不会在原地兜圈?关键在于折扣因子γ = 0.9 \gamma = 0.9γ=0.9!第 1 步到终点拿1.0 1.01.0分,第 2 步才到就变成1.0 × 0.9 = 0.9 1.0 \times 0.9 = 0.91.0×0.9=0.9分,第 3 步就是0.81 0.810.81分……折扣机制天然惩罚了多余步数,驱动算法自动寻找最短路径。

二、策略迭代核心算法(数学推导与代码实现)

1. 广义策略迭代 (GPI) 双循环闭环

策略迭代由策略评估策略提升交替执行,直至收敛:

计算得到当前真实价值 V(s)

贪婪更新策略 π

初始策略 π₀ (各动作概率0.25)

策略评估 Policy Evaluation

策略提升 Policy Improvement

策略是否稳定?

最优策略 π* 与最优价值 V*


2. 策略评估 (Policy Evaluation) 与贝尔曼期望方程

数学原理:在当前策略π \piπ下,计算每个状态s ss的长期折现价值V ( s ) V(s)V(s)

V k + 1 ( s ) = ∑ a ∈ A π ( a ∣ s ) ∑ s ′ P ( s ′ ∣ s , a ) [ R ( s , a , s ′ ) + γ V k ( s ′ ) ] V_{k+1}(s) = \sum_{a \in A} \pi(a \mid s) \sum_{s'} P(s' \mid s, a) \left[ R(s, a, s') + \gamma V_k(s') \right]Vk+1(s)=aAπ(as)sP(ss,a)[R(s,a,s)+γVk(s)]

其中动作价值函数(Q函数)为:

Q ( s , a ) = ∑ s ′ P ( s ′ ∣ s , a ) [ R ( s , a , s ′ ) + γ V ( s ′ ) ] Q(s, a) = \sum_{s'} P(s' \mid s, a) \left[ R(s, a, s') + \gamma V(s') \right]Q(s,a)=sP(ss,a)[R(s,a,s)+γV(s)]

代码实现 (common.py):
# 计算在状态 state 执行 action 时的动作价值 Q(s, a)defget_qsa(env,values,state,action):value=0.0# 遍历该动作可能产生的所有结果forprop,next_state,reward,overinenv.P[state][action]:# 贝尔曼折现项:gamma = 0.9next_value=values[next_state]*0.9# 如果掉进陷阱或到达终点,终止状态未来没有后续收益ifover:next_value=0.0# Q(s, a) = 即时奖励 + 衰减的未来价值next_value+=reward next_value*=prop value+=next_valuereturnvalue# 全图状态价值更新defget_values(env,values,pi,algorithm):new_values=np.zeros([16])forstateinrange(16):action_value=np.zeros(4)foractioninrange(4):action_value[action]=get_qsa(env,values,state,action)ifalgorithm=='策略迭代':# 按策略概率加权求期望:V(s) = sum(pi(a|s) * Q(s, a))new_values[state]=(action_value*pi[state]).sum()elifalgorithm=='价值迭代':# 直接贪婪取最大值:V(s) = max_a Q(s, a)new_values[state]=action_value.max()returnnew_values

3. 策略提升 (Policy Improvement) 与均分概率

数学原理:基于评估出来的价值函数,贪婪更新策略:

π ′ ( s ) = arg ⁡ max ⁡ a Q ( s , a ) \pi'(s) = \arg\max_{a} Q(s, a)π(s)=argamaxQ(s,a)

如果存在多个动作并列第一,则让这些动作均分概率

defget_pi(env,values):new_pi=np.zeros([16,4])forstateinrange(16):action_value=np.zeros(4)foractioninrange(4):action_value[action]=get_qsa(env,values,state,action)# 统计达到最大分数的动作数量count=(action_value==action_value.max()).sum()# 让并列第一的最优动作均分概率foractioninrange(4):ifaction_value[action]==action_value.max():new_pi[state,action]=1/countelse:new_pi[state,action]=0.0returnnew_pi

三、从“离线算脑”到“实机表演”

1. 训练结果收敛可视化

训练代码只需一个清晰的交替循环:

for_inrange(10):for_inrange(100):values=get_values(env,values,pi,algorithm)pi=get_pi(env,values)

训练后得到的最优状态价值V ( s ) V(s)V(s)

[[0.5905 0.6561 0.729 0.6561] [0.6561 0.0000 0.8100 0.0000] [0.7290 0.8100 0.9000 0.0000] [0.0000 0.9000 1.0000 0.0000]]
  • 陷阱H全为0.0
  • 终点G(右下角)为1.0
  • 距离终点越近,数值越高(以0.9 0.90.9逐步衰减)。

最优动作策略地图:

↓ → ↓ ← ↓ ○ ↓ ○ → ↓ ↓ ○ ○ → → ❤️

2. 深度理解加权随机抽样np.random.choice

在实机表演中,执行动作的代码为:

action=np.random.choice(np.arange(4),size=1,p=pi[index])[0]
  • 当只有一个第一名时:该动作概率为1.0 1.01.0,加权抽样等同于100 % 100\%100%必然触发,决定性执行;
  • 当存在多个并列第一时(如起点[0.0, 0.5, 0.5, 0.0],向下和向右同样优秀):
    • 若使用np.argmax(),会死板地永远只选排在前面的“下”;
    • 使用np.random.choice,智能体会各以50 % 50\%50%的概率灵活选择两条最优路径!

四、框架进阶:从 Jupyter 历史包袱到 Gymnasium 原生极简渲染

很多学习者容易困惑:为什么视频里用了SDL_VIDEODRIVER='dummy',还用matplotlibIPython.display搞得极为繁琐?

  • 原因:Jupyter Notebook 在浏览器中运行,无法弹出操作系统的原生 GUI 窗口,所以被迫把画面转成图片数组并不断清屏重绘;
  • 现代化本地写法:Gymnasium 内置 Pygame 桌面渲染,在本地开发只需设置render_mode='human',在循环中显式调用env.render(),即可实现极高帧率、零闪烁的原生游戏窗口渲染!

五、完整工程代码结构与运行指南

1. 文件结构

强化学习/ ├── common.py # Qsa计算、策略评估、策略提升公共算法库 ├── main.py # 主入口:环境创建、模型训练、原生GUI实机表演 ├── frozenlake.gif # 实机运行录屏动画 └── README.md # 完整技术教程文档

2. 快速运行

# 激活虚拟环境.\.venv\Scripts\activate.bat# 启动训练与 100 遍动画表演python main.py

终端将输出最优价值矩阵与箭头策略地图,随后桌面将弹出 Pygame 窗口,丝滑自动表演智能体 100 次通关全过程!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询