来源:市场资讯
(来源:DeepHub IMBA)
这篇文章的目标是要做的是一个自定义的 8×8 grid world。agent 从左上角出发,绕开墙壁,找到右下角的 goal。
agent 用 Q-Learning 训练,纯靠试错学习,没有任何的硬编码路径,也没有地图。
![]()
读到这个项目主要包括一下三个目标
一个从零写起的自定义 Gymnasium 环境
一个训练好的 Q-Learning agent,100% 到达 goal
三张可视化:训练曲线、学到的 policy,以及和随机 agent 的并排对比
项目结构
gridworld/├── grid_env.py # 自定义 Gymnasium 环境├── agent.py # Q-Learning agent├── train.py # 训练循环 + 图表├── visualize.py # 动画运行 + 对比└── requirements.txt
环境
Gymnasium 里一切都围绕一个类:gym.Env。继承它实现几个方法,这样创造的环境就和任意 RL 算法兼容。
class GridWorldEnv(gym.Env):def __init__(self, render_mode=None):self.grid_size = 8self.max_steps = 200self.action_space = spaces.Discrete(4) # up, right, down, leftself.observation_space = spaces.Discrete(64) # 8x8 = 64 statesself.walls = {(1,1),(1,2),(1,3),(2,5),(3,5),(4,5),(5,2),(5,3),(5,4),(6,6)}self.start = (0, 0)self.goal = (7, 7)state space 就是 agent 在 grid 上的位置,被压平成 0 到 63 之间的一个整数。action space 有 4 个离散动作 —— up、right、down、left。
环境的逻辑全在step()方法。每次 agent 做出一个动作,step() 都会跑一遍:
def step(self, action):moves = {0: (-1,0), 1: (0,1), 2: (1,0), 3: (0,-1)}dr, dc = moves[action]nr, nc = r + dr, c + dc# Stay in place if wall or boundaryif 0 <= nr < self.grid_size and (nr, nc) not in self.walls:self.agent_pos = [nr, nc]terminated = (tuple(self.agent_pos) == self.goal)if terminated:reward = 1.0else:dist = abs(self.agent_pos[0] - 7) + abs(self.agent_pos[1] - 7)reward = -0.01 - 0.001 * dist两点要说明,一是 agent 撞墙不会崩只是停在原地,这是处理边界比较简单且实用的写法。二是 reward 由两部分组成:-0.01 的步长惩罚用来鼓励效率;一个基于距离的小惩罚,在训练早期把 agent 往正确方向轻轻推一下。+1.0 的大奖励只在到达 goal 那一刻给出。
这就是 reward shaping 加一些中间信号来引导学习,但不改变最优 policy。
agent.py
整个项目的核心是 Q-Learning 更新规则:
Q(s,a) ← Q(s,a) + α * [r + γ * max_a' Q(s',a') - Q(s,a)]
代码如下:
def update(self, state, action, reward, next_state, done):best_next = 0.0 if done else np.max(self.Q[next_state])td_target = reward + self.gamma * best_nexttd_error = td_target - self.Q[state, action]self.Q[state, action] += self.alpha * td_error
Q-table 是一个形状 (64, 4) 的 NumPy 数组:每个 state 一行,每个 action 一列。从全零开始每一步之后就更新一次。
action 选择用 epsilon-greedy:
def select_action(self, state):if np.random.rand() < self.epsilon:return np.random.randint(self.n_actions) # explorereturn np.argmax(self.Q[state]) # exploit
训练初期 epsilon 接近 1.0,agent 几乎在乱走。随着训练推进epsilon 衰减到 0.05,agent 越来越多地利用已经学到的东西。这种探索-利用的权衡是 RL 的基本问题。
训练
训练循环就是本系列第 1 部分写过的那个 agent-environment 循环,落到代码就是:
for ep in range(1, n_episodes + 1):obs, _ = env.reset()done = Falsewhile not done:action = agent.select_action(obs)next_obs, reward, terminated, truncated, _ = env.step(action)agent.update(obs, action, reward, next_obs, terminated or truncated)obs = next_obsagent.decay_epsilon()
重置环境,循环到 done,挑动作,走一步,更新 Q-table,重复。
运行:
python train.py
训练 2000 个 episode 大约 10 秒。输出长这样:
Episode Reward Steps Epsilon Success200 -0.113 59.8 0.367 92.5%400 0.704 18.3 0.135 100.0%600 0.754 15.4 0.050 100.0%2000 0.764 14.9 0.050 100.0%
到第 400 个 episode,agent 已经 100% 能到达 goal,用大约 15 步完成。
训练曲线如下
![]()
训练结束后 train.py 会生成三张图,保存为 training_curves.png:
Episode reward —— 从低且嘈杂开始,随着 agent 学习上升并稳定下来
Steps per episode —— 随着 agent 找到更短路径而急剧下降
Success rate —— 达到 100% 并保持
reward 图早期那段噪声是 agent 在探索,乱走且经常失败,正常现象。
可视化学到的 policy
![]()
训练完成后可以把 greedy policy 提出来 —— 每个 state 上的最佳 action —— 画成一张箭头图,保存为 policy_values.png。
左图是 value function V(s) 的热力图。靠近 goal 的 state 是绿色(高 value),远离或被墙挡住的是红色(低 value)。可以看到 value 从 goal 一路向回传播 —— Bellman 方程在实践中就是这个样子。
右图把 policy 画成 grid 上的箭头。每个非墙、非 goal 的格子上标出 agent 会朝哪个方向走。箭头连成一条从 start 到 goal 的连贯路径,绕开每一堵墙。
def plot_policy_and_values(agent):V = np.max(agent.Q, axis=1).reshape(grid, grid) # V(s) = max_a Q(s,a)policy = np.argmax(agent.Q, axis=1).reshape(grid, grid)arrows = {0: '↑', 1: '→', 2: '↓', 3: '←'}随机 vs 已训练
![]()
整个项目里最有说服力的结果来自对比:
Random TrainedAvg steps 182.6 14.0Success rate 23.5% 100.0%Best steps 31 14============
随机 agent 在 grid 上漫无目的地走,200 步限制内只有 23% 的概率能找到 goal,找到时平均要 182 步。已训练的 agent 每次都直接到 goal14 步搞定。
强化学习的全部意义就在这里 —— 用一个能在所有 state 间泛化的 policy,替换掉随机试错。
跑可视化脚本还会生成 agent_run.gif,实时展示 agent 在 grid 上导航,身后还会画出路径轨迹:
python visualize.py
总结
Q-Learning 是 off-policy。 agent 可以用一个随机 policy 探索,仍然能学到最优 policy。原因在于更新里那个 max_a' Q(s', a'):它总是朝最优可能动作更新,与实际采取的动作无关。
Q-table 就是 policy。训练完之后环境就不再需要了。agent 学到的一切都在 Q-table 里。用 agent.save() 存起来,下次用 agent.load() 加载。
reward shaping 有用。去掉距离惩罚,agent 也能学会,只是慢。整形之后的 reward 在训练早期给了一个关于方向的提示,当 goal 离 start 较远、reward 又是稀疏的(只有成功才给 +1),这种提示能省掉大量从 goal 反向传播到 start 的 episode。
agent-environment 循环是通用的。从表格 Q-Learning 到 PPO 再到 RLHF,每一种 RL 算法跑的都是同一个基本循环。变化的只是 policy 怎么表示,以及更新规则怎么写。
代码
https://github.com/ES7/Reinforcement-Learning-Projects/tree/main
by Ebad Sayed
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.