迷宫中的探索者——强化学习初体验
在一个随机生成的迷宫中,我们的智能体“小探”正面临着寻找宝藏的挑战。它没有地图,没有先验知识,只有一个简单的目标:在有限的步数内,找到那枚闪闪发光的宝藏。每一次移动,都可能撞上墙壁、踏入陷阱,也可能离目标更近一步。它是如何学会在迷宫中高效穿行的?这背后,正是强化学习(Reinforcement Learning, RL)的核心思想——一个智能体通过与环境互动,从试错中学习最优策略的框架。本章,你将跟随“小探”的脚步,踏出强化学习探索的第一步,理解状态、动作、奖励、策略等基石概念,并亲手用代码实现一个解决微型迷宫问题的Q-Learning算法。
一、 故事启程:小探的第一次迷宫探险
想象一下,你被送入一个完全陌生的2x2网格迷宫(如图1-1所示)。每个格子是一个“状态”,你站在起点(左上角,状态S₀)。你的动作只有四个:上、下、左、右。但迷宫有墙壁,有些动作会把你撞回原地。你的终极目标,是到达右下角的宝藏格(状态S₃),那里有丰厚的奖励。而某些格子是陷阱(比如左下角,状态S₂),踩上去会受到惩罚。每一步移动都会消耗少量能量(一个小的负奖励),鼓励你尽快找到宝藏。
```mermaid graph TD subgraph 迷宫布局 (2x2 Grid) S0[起点<br/>状态 S₀] -->|右移| S1[安全格<br/>状态 S₁] S0 -->|下移| S2[陷阱<br/>状态 S₂] S1 -->|下移| S3[宝藏<br/>状态 S₃] S2 -->|右移| S3 end
style S0 fill:#e1f5fe style S3 fill:#fff3e0 style S2 fill:#ffebee ```
图1-1:一个简单的2x2迷宫布局示例 *注:S₀为起点,S₃为宝藏(目标),S₂为陷阱。箭头表示可行的移动方向。*
“小探”的初始状态是空白的。它不知道哪个方向通向宝藏,哪个方向是死胡同。它的“大脑”里,只有对当前状态(我在哪?)和可能动作(我能去哪?)的感知,以及对行动结果的即时反馈(得到奖励还是惩罚?)。它开始随机游走:向右,撞墙(无奖励,状态不变);向下,掉入陷阱(大负奖励,状态重置);再次尝试,向右,安全(小负奖励,消耗步数);向下,终于抵达宝藏(大正奖励!任务完成)。
这个过程,就是强化学习最原始的形态。智能体(Agent) “小探”在环境(Environment) 这个迷宫中,根据当前状态(State) 选择一个动作(Action) ,环境随之转移到新的状态,并给予一个奖励(Reward) 。智能体的目标,是学习一个策略(Policy) ——一个从状态到动作的映射——使得长期累积的奖励最大化。这个累积奖励,称为回报(Return) ,通常用折扣未来奖励之和来表示:$G_t = R_{t+1} + \gamma R_{t+2} + \gamma^2 R_{t+3} + ...$,其中 $\gamma$ 是折扣因子(0 ≤ γ ≤ 1),它权衡了当前奖励与未来奖励的重要性。
在这个故事中,我们无意中触及了强化学习的核心要素,即马尔可夫决策过程(Markov Decision Process, MDP)的要素:状态空间S、动作空间A、状态转移概率P、奖励函数R、折扣因子γ。对于我们的2x2迷宫,状态空间S = {S₀, S₁, S₂, S₃},动作空间A = {上, 下, 左, 右}。状态转移是确定性的(给定状态和动作,下一个状态唯一确定),奖励函数是我们预先定义的(例如,到达S₃得+10,踏入S₂得-10,每步得-1,其他得0)。
二、 核心概念解析:从故事到框架
将“小探”的故事抽象化,我们得到强化学习的基本交互循环(图1-2)。这个循环是理解所有RL算法的起点。
```mermaid sequenceDiagram participant Agent as 智能体 (Agent) participant Env as 环境 (Environment)
loop 每个时间步 t Agent->>Env: 观察状态 s_t Agent->>Env: 选择动作 a_t Env->>Agent: 执行动作,获得奖励 r_{t+1},转移到新状态 s_{t+1} end ```
图1-2:强化学习标准交互循环 *智能体观察环境状态,选择动作,执行后获得奖励和新状态。此循环持续进行。*
下面我们逐一解析这些概念,并建立与故事的对应关系:
1. 状态 (State, s):对环境的描述。在迷宫中,状态就是智能体当前所在的格子坐标(如 (0,0) 表示S₀)。一个“好的”状态表示应该包含做出最优决策所需的所有历史信息。在我们的确定性迷宫中,当前位置足以决定最优动作,满足马尔可夫性质:未来状态只依赖于当前状态和动作,与过去无关。 2. 动作 (Action, a):智能体可以执行的操作。在迷宫中,是四个方向的移动。动作空间可以是离散的(如上下左右)或连续的(如机器人关节的角度)。 3. 奖励 (Reward, r):环境对智能体动作的即时数值反馈。它是指导学习的“方向盘”。我们的设计:宝藏=+10,陷阱=-10,每步移动=-1(鼓励效率),撞墙=0(或-1,视设计而定)。奖励函数的设计是RL问题定义的关键,它编码了我们的终极目标。 4. 策略 (Policy, π):智能体的行为准则,定义了在给定状态下选择动作的概率。可以是确定性策略 π(s) = a(在状态s总是选动作a),也可以是随机性策略 π(a|s)(在状态s选择动作a的概率)。小探从随机策略开始,最终学习到的应该是一个在S₀向右,在S₁向下的确定性策略。 5. 价值 (Value):对未来回报的期望。这是评估状态或状态-动作对“好坏”的尺度。
- **状态价值函数** $V^\pi(s)$:从状态s开始,遵循策略π,未来累积折扣回报的期望。它回答“这个状态有多好?”
- **动作价值函数** $Q^\pi(s, a)$:在状态s下先执行动作a,然后遵循策略π,未来累积折扣回报的期望。它回答“在这个状态下,执行这个动作有多好?”
在Q-Learning等算法中,我们直接学习最优动作价值函数 $Q^*(s, a)$,它代表了在状态s下执行动作a,并 thereafter 采取最优动作所能获得的最大期望回报。一旦 $Q^*$ 被准确估计,最优策略就是 $\pi^*(s) = \arg\max_a Q^*(s, a)$。 6. 折扣因子 (Discount Factor, γ):0 ≤ γ ≤ 1。它使得未来奖励的现值降低。γ接近1,智能体更重视长期回报(有远见);γ接近0,智能体只图眼前利益(短视)。在我们的快速寻宝任务中,γ可以设为0.9或0.99。
对比传统编程与强化学习(表1-1)有助于理解范式差异:
| 维度 | 传统编程 | 强化学习 | | :--- | :--- | :--- | | 输入 | 明确的规则与数据 | 环境交互(状态、动作、奖励) | | 输出 | 确定的程序/答案 | 学习到的策略(行为准则) | | 核心 | 人设计算法,计算机执行 | 计算机通过试错自主学习策略 | | 知识来源 | 程序员预先编码 | 环境奖励信号引导 | | 适应性 | 规则固定,环境变化需重写 | 策略可随环境动态调整 |
表1-1:传统编程与强化学习范式对比
三、 初识Q-Learning:让“小探”学会思考
现在,我们要为“小探”安装一个“大脑”——Q-Learning算法。这是一种无模型(Model-free)、离策略(Off-policy) 的RL算法。无模型意味着它不需要知道环境的状态转移概率P(s'|s,a)和奖励函数R(s,a,s');离策略意味着它学习的是最优策略 $\pi^*$,但行为策略(用于探索)可以是别的(如ε-greedy)。
Q-Learning的核心是维护一个Q表(Q-Table) ,一个|S|×|A|的表格,存储每个状态-动作对的估计动作价值 $Q(s, a)$。初始时,Q表所有值设为0(或小随机数)。智能体通过以下步骤更新Q值:
1. 观察当前状态 $s_t$。 2. 选择一个动作 $a_t$(通常使用ε-greedy:以概率ε随机探索,以概率1-ε选择当前Q值最大的动作)。 3. 执行 $a_t$,环境返回奖励 $r_{t+1}$ 和新状态 $s_{t+1}$。 4. 更新Q值: $$Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha \left[ r_{t+1} + \gamma \max_{a} Q(s_{t+1}, a) - Q(s_t, a_t) \right]$$ 其中:
- $\alpha$ 是**学习率**(0 < α ≤ 1),控制更新步长。
- $r_{t+1} + \gamma \max_{a} Q(s_{t+1}, a)$ 称为**时序差分目标(TD Target)** ,是“估计”的 $s_t, a_t$ 的价值。
- 括号内部分是**时序差分误差(TD Error)** $\delta_t$,是目标与当前估计的差距,驱动学习。
5. 令 $s_t \leftarrow s_{t+1}$,重复直到终止状态。
这个更新公式的直观含义是:用“即时奖励+未来最大估计价值”来修正当前Q值的估计。它 bootstraps(自举)——用现有的Q估计来更新Q估计,无需等待回合结束。
四、 代码实现:从零构建2x2迷宫求解器
下面是一个完整的Python实现,使用`numpy`库构建Q表并模拟迷宫交互。代码被设计为清晰、可运行、并包含详细注释,便于青少年理解每一步。
```python import numpy as np import random
class MazeEnv: """定义2x2迷宫环境""" def __init__(self):
状态空间: 0: (0,0) S0起点, 1: (0,1) S1, 2: (1,0) S2陷阱, 3: (1,1) S3宝藏
self.state_space = [0, 1, 2, 3]
动作空间: 0:上, 1:下, 2:左, 3:右
self.action_space = [0, 1, 2, 3]
状态转移字典: {当前状态: {动作: (下一状态, 奖励, 是否终止)}}
self.transitions = { 0: {0: (0, -1, False), 1: (2, -10, True), 2: (0, -1, False), 3: (1, -1, False)}, # S0: 下=陷阱,右=S1 1: {0: (0, -1, False), 1: (3, 10, True), 2: (0, -1, False), 3: (1, -1, False)}, # S1: 下=宝藏 2: {0: (0, -1, False), 1: (2, -1, False), 2: (2, -1, False), 3: (3, 10, True)}, # S2: 右=宝藏 3: {0: (3, 0, True), 1: (3, 0, True), 2: (3, 0, True), 3: (3, 0, True)} # S3: 终止状态 } self.current_state = 0 self.done = False
def reset(self): """重置环境到初始状态""" self.current_state = 0 self.done = False return self.current_state
def step(self, action): """执行动作,返回(新状态, 奖励, 是否终止)""" next_state, reward, terminated = self.transitions[self.current_state][action] self.current_state = next_state self.done = terminated return next_state, reward, self.done
def render(self): """简单打印迷宫状态(可选)""" grid = np.array([['S0', 'S1'], ['S2', 'S3']]) print(f"当前状态: {self.current_state} (对应格子: {grid.flat[self.current_state]})")
def q_learning(env, episodes=1000, alpha=0.1, gamma=0.9, epsilon=0.1): """Q-Learning算法主函数""" n_states = len(env.state_space) n_actions = len(env.action_space)
初始化Q表,全零
Q = np.zeros((n_states, n_actions))
for episode in range(episodes): state = env.reset() done = False
while not done:
ε-greedy动作选择
if random.uniform(0, 1) < epsilon: action = random.choice(env.action_space) # 探索 else: action = np.argmax(Q[state]) # 利用:选择Q值最大的动作
next_state, reward, done = env.step(action)
Q-Learning更新公式
old_value = Q[state, action] next_max = np.max(Q[next_state]) new_value = old_value + alpha * (reward + gamma * next_max - old_value) Q[state, action] = new_value
state = next_state
(可选) 打印训练进度
if (episode + 1) % 100 == 0: print(f"Episode {episode+1}/{episodes} 完成")
return Q
def test_policy(Q): """测试学习到的策略""" env = MazeEnv() state = env.reset() path = [state] total_reward = 0 steps = 0 print("=== 测试学习到的策略 ===") env.render()
while steps < 10: # 防止无限循环 action = np.argmax(Q[state]) state, reward, done = env.step(action) path.append(state) total_reward += reward steps += 1 print(f"步骤{steps}: 执行动作{action} -> 新状态{state}, 奖励{reward}") env.render() if done: break
print(f"路径: {path}") print(f"总奖励: {total_reward}") return path, total_reward
主程序
if __name__ == "__main__":
1. 创建环境
env = MazeEnv() print("迷宫环境已创建。状态空间: 4个,动作空间: 4个。")
2. 训练Q表
print("\n开始训练Q-Learning算法...") trained_Q = q_learning(env, episodes=1000, alpha=0.1, gamma=0.9, epsilon=0.1)
3. 打印学到的Q表
print("\n=== 训练后的Q表 ===") print("行: 状态(S0-S3), 列: 动作(上,下,左,右)") print(np.round(trained_Q, 2))
4. 提取并展示最优策略
print("\n=== 最优策略 (每个状态选择Q值最大的动作) ===") policy = {} for s in env.state_space: best_action = np.argmax(