
这篇文章的目标是要做的是一个自定义的 8×8 grid world。agent 从左上角出发,绕开墙壁,找到右下角的 goal。
agent 用 Q-Learning 训练,纯靠试错学习,没有任何的硬编码路径,也没有地图。

读到这个项目主要包括一下三个目标
gridworld/
├── grid_env.py # 自定义 Gymnasium 环境
├── agent.py # Q-Learning agent
├── train.py # 训练循环 + 图表
├── visualize.py # 动画运行 + 对比
└── requirements.txtGymnasium 里一切都围绕一个类:gym.Env。继承它实现几个方法,这样创造的环境就和任意 RL 算法兼容。
class GridWorldEnv(gym.Env):
def __init__(self, render_mode=None):
self.grid_size = 8
self.max_steps = 200
self.action_space = spaces.Discrete(4) # up, right, down, left
self.observation_space = spaces.Discrete(64) # 8x8 = 64 states
self.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 boundary
if 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.0
else:
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。
整个项目的核心是 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_next
td_error = td_target - self.Q[state, action]
self.Q[state, action] += self.alpha * td_errorQ-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) # explore
return 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 = False
while 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_obs
agent.decay_epsilon()重置环境,循环到 done,挑动作,走一步,更新 Q-table,重复。
运行:
python train.py训练 2000 个 episode 大约 10 秒。输出长这样:
Episode Reward Steps Epsilon Success
--------------------------------------------------
200 -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:
reward 图早期那段噪声是 agent 在探索,乱走且经常失败,正常现象。

训练完成后可以把 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: '←'}
整个项目里最有说服力的结果来自对比:
Random Trained
=============================================
Avg steps 182.6 14.0
Success 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.pyQ-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
喜欢就关注一下吧
本文分享自 DeepHub IMBA 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!