第1章:迷宫里的智能体:第一次‘试错’学习
想象一下,你突然置身于一个陌生的网格迷宫中,四周是高耸的墙壁,前方有三条岔路。你不知道哪条通向宝藏,哪条是死胡同。你只知道,每走一步都可能离目标更近,也可能彻底迷失。你会怎么选择?是凭直觉随便挑一条路,还是试图寻找规律?
这个看似简单的迷宫问题,恰恰是人工智能领域一个核心挑战的缩影:如何让一个“智能体”在未知环境中,通过试错来学习最优决策?这就是强化学习(Reinforcement Learning)要解决的根本问题。
在本章,我们将彻底抛弃复杂的数学公式,从一个最基础的网格迷宫游戏开始,亲手构建一个“随机漫步”的智能体。你将直观理解强化学习的四大基石——环境、状态、动作、奖励——并亲眼见证一个一无所知的智能体,如何通过纯粹的随机尝试,慢慢摸索出通往宝藏的道路。
我们的目标很纯粹:建立最原始、最纯粹的强化学习直觉。准备好开始这场迷宫探险了吗?
迷宫环境:试炼场的构建
让我们先设计一个具体的迷宫。假设我们有一个 5×5 的网格,坐标从 (0,0) 到 (4,4),其中 (0,0) 是左上角起点,(4,4) 是右下角宝藏位置。迷宫中散布着几面无法穿越的墙壁。我们约定:智能体每次可以向上、下、左、右四个方向移动一格,但不能走出网格边界,也不能穿过墙壁。如果试图穿墙,则会留在原地。
这个迷宫的具体布局如下表所示(S=起点,G=宝藏,W=墙壁,·=可通行空格):
| | 0 | 1 | 2 | 3 | 4 | |---|---|---|---|---|---| | 0 | S | · | W | · | · | | 1 | · | W | · | W | · | | 2 | · | · | · | · | · | | 3 | · | W | · | W | · | | 4 | · | · | W | · | G |
从表格中可以清晰地看到:起点在左上角 (0,0),宝藏藏在右下角 (4,4)。中间有三道墙壁分别位于 (0,2)、(1,1)、(1,3)、(3,1)、(3,3)、(4,2)。注意,这个迷宫设计得相对简单,但已经足够展示核心概念。实际上,强化学习中的环境可以复杂得多——可以是电子游戏屏幕、机器人传感器读数、股票市场行情等等。但无论环境多么复杂,其本质都可以抽象为类似这样的状态转移系统。
在强化学习术语中,这个迷宫就是环境(Environment)。环境是智能体交互的一切外部条件,它定义了所有可能的规则:哪些状态可达、哪些动作允许、动作会导致什么状态变化、以及如何给予奖励。环境对智能体来说是一个黑盒——智能体只能通过执行动作并观察结果来逐步了解它。
为了在代码中实现这个环境,我们可以创建一个 `Maze` 类。这个类需要维护当前状态(智能体的位置),并提供两个核心方法:`reset()` 将环境重置到起点,`step(action)` 执行一个动作并返回新状态、奖励和是否结束的标志。
```python class Maze: def __init__(self):
0表示可通行,1表示墙壁
self.grid = [ [0, 0, 1, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 1, 0, 0] ] self.start = (0, 0) self.goal = (4, 4) self.state = self.start self.actions = ['up', 'down', 'left', 'right']
def reset(self): """重置环境到起点,返回初始状态""" self.state = self.start return self.state
def step(self, action): """执行动作,返回(新状态, 奖励, 是否结束)""" x, y = self.state
根据动作计算候选新位置
if action == 'up': next_x, next_y = max(0, x-1), y elif action == 'down': next_x, next_y = min(4, x+1), y elif action == 'left': next_x, next_y = x, max(0, y-1) elif action == 'right': next_x, next_y = x, min(4, y+1)
检查是否撞墙
if self.grid[next_x][next_y] == 1: reward = -1 next_state = self.state # 撞墙则状态不变 done = False else: next_state = (next_x, next_y) self.state = next_state if next_state == self.goal: reward = 10 # 找到宝藏 done = True else: reward = 0 # 普通移动 done = False return next_state, reward, done ```
这个 `Maze` 类 encapsulates(封装)了迷宫的所有规则。当智能体调用 `step` 时,环境会根据当前状态和动作,计算出结果。智能体不需要知道迷宫的具体布局,它只能通过尝试来学习。这正是强化学习与监督学习的关键区别:没有预先标注的“正确答案”,只有稀疏的奖励信号。
在智能体与环境的交互中,还有一个重要的循环模式。智能体观察当前状态,选择动作,执行后环境返回奖励和新状态,然后智能体再基于新状态选择下一个动作,如此反复。这个循环可以形象地表示为:
```mermaid graph LR A[智能体<br>观察状态] -->|选择动作| B[环境<br>执行动作] B -->|返回奖励<br>和新状态| A ```
这个简单的循环是强化学习问题的核心架构。所有复杂的算法,从 Q-learning 到深度强化学习,都是在这个循环基础上添加不同的决策机制和学习方法。
强化学习的四大基石:从迷宫看本质
现在,让我们正式定义强化学习问题的四个基本要素:环境、状态、动作、奖励。这四个概念构成了任何强化学习问题的“最小集合”,理解它们是入门的关键。
环境:迷宫的一切规则
环境是智能体存在和交互的外部世界。在迷宫例子中,环境就是那个 5×5 的网格布局及其所有规则:墙壁位置、起点终点、移动约束、奖励定义。环境决定了问题的物理边界和可能性空间。
从智能体的视角看,环境是一个不透明的黑箱。智能体只能通过动作和观察结果来间接感知环境。这种设定模拟了现实世界中许多问题:一个机器人不知道房间的精确三维地图,只能通过传感器和移动来探索;一个交易算法不知道市场的未来走势,只能通过下单和观察价格变化来学习。
环境可以是离散的(如网格迷宫,状态有限且明确)或连续的(如机器人关节角度,状态是实数向量)。它可以是确定性的(同一动作在同一状态总导致相同结果)或随机的(同一动作可能产生不同结果,比如有滑动的冰面)。我们的迷宫是离散且确定性的,这使学习更容易,但核心原理相通。
状态:智能体此刻的位置
状态(State) 是对环境在某一时刻的完整描述。在迷宫中,状态就是智能体当前所在的网格坐标,例如 (2,3)。状态是智能体做决策的依据——它基于当前状态选择动作。
一个关键概念是状态表示(State Representation)。在简单迷宫中,状态就是坐标,是一一对应的。但在复杂问题中,状态可能是高维的:一幅游戏屏幕的像素数组、一组传感器读数、一段文本。如何有效地表示状态,直接影响学习效率。有时,环境可能只提供部分可观测(Partially Observable)的信息,比如智能体只能看到周围几格,而非整个迷宫。这更接近现实,也是更难的挑战。
在我们的随机漫步智能体中,状态将直接作为输入(尽管随机策略实际上不使用它,但更聪明的策略会)。
动作:智能体的移动选择
动作(Action) 是智能体在某一状态下可以执行的操作。在迷宫中,动作集合 A = {上, 下, 左, 右}。动作可以是离散的(有限集合,如四个方向)或连续的(如方向盘转角 -45° 到 +45°)。
动作的选择是智能体策略(Policy) 的核心体现。策略可以看作是状态到动作的映射:π(s) → a。最简单的策略是随机策略,即完全随机选择动作。更优的策略会根据状态选择更有希望的动作,比如在靠近终点时多向终点方向移动。
动作可能受到约束:有些动作在某些状态下无效(如试图从边界向外移动)。我们的 `Maze.step` 方法中处理了这些约束:如果动作导致撞墙或出界,智能体留在原地并受到惩罚。
奖励:宝藏的诱惑与墙壁的惩罚
奖励(Reward) 是环境对智能体动作的即时数值反馈。它告诉智能体“刚才那个动作好不好”。奖励是强化学习中最核心的驱动信号,智能体的所有学习目标都是最大化长期累积奖励。
在我们的迷宫中,我们定义了奖励函数 R(s, a, s'):
- 如果新状态是宝藏:奖励 +10,且回合结束
- 如果动作导致撞墙:奖励 -1,状态不变
- 其他正常移动:奖励 0
这个设计有几个要点: 1. 稀疏性:正奖励只在到达宝藏时出现,其他大部分动作 reward=0。这模拟了现实问题中“成功”信号稀少的特点。 2. 塑形:撞墙给予轻微负奖励,这有助于智能体更快学会避开墙壁。如果撞墙无惩罚,智能体可能花更多时间尝试穿墙。 3. 目标导向:+10 的奖励明确指示了最终目标。智能体会逐渐学会,只有最终到达宝藏才能获得大奖励,因此要避免无谓的撞墙(-1)消耗。
奖励设计是强化学习艺术性的体现。设计不当会导致智能体学习到错误行为(例如,如果撞墙奖励为 +1,智能体会乐于撞墙)。好的奖励函数应精确反映任务的真实目标。
随机漫步智能体:最朴素的试错者
理解了四大基石,现在让我们构建一个最简单的智能体:随机漫步者(Random Walker)。这个智能体没有任何学习能力,在每个状态下完全随机选择一个动作(上下左右等概率)。它就像闭着眼睛在迷宫里乱撞的人。
智能体实现
随机智能体的代码极其简单:
```python import random
class RandomAgent: def __init__(self, actions): self.actions = actions
def choose_action(self, state): """完全随机选择动作,忽略状态""" return random.choice(self.actions) ```
注意 `choose_action` 方法接收 `state` 参数,但随机智能体完全无视它。这体现了“随机”的本质:决策与状态无关。在更高级的智能体中,这个函数会根据状态计算动作的概率分布。
运行一个回合
一个“回合(Episode)”指从开始到结束的完整序列:智能体从起点出发,一步步移动,直到到达宝藏(成功)或达到最大步数(失败)。我们用 `run_episode` 函数模拟一个回合:
```python def run_episode(env, agent, max_steps=100): state = env.reset() total_reward = 0 steps = 0 path = [state] # 记录路径用于分析
for _ in range(max_steps): action = agent.choose_action(state) next_state, reward, done = env.step(action) total_reward += reward steps += 1 path.append(next_state) state = next_state if done: break
return total_reward, steps, path ```
这个函数返回三个值:总奖励、总步数、以及路径列表。路径记录可以帮助我们可视化智能体的行为。
批量模拟与统计分析
单个回合的结果具有随机性。为了了解随机策略的整体表现,我们需要运行大量回合(比如 100 次)并统计:
```python env = Maze() agent = RandomAgent(env.actions)
num_episodes = 100 results = []
for i in range(num_episodes): reward, steps, path = run_episode(env, agent) results.append((reward, steps, path))
分析结果
success_count = sum(1 for r, s, p in results if r >= 10) # 总奖励>=10说明到达了宝藏 avg_steps_success = sum(s for r, s, p in results if r >= 10) / max(1, success_count) avg_steps_all = sum(s for r, s, p in results) / num_episodes
print(f"在 {num_episodes} 次模拟中:") print(f"成功找到宝藏的次数:{success_count}(成功率 {success_count/num_episodes:.1%})") print(f"成功时的平均步数:{avg_steps_success:.1f}") print(f"所有回合的平均步数:{avg_steps_all:.1f}") ```
运行这段代码,你会得到类似这样的输出(由于随机性,每次运行略有不同):
``` 在 100 次模拟中: 成功找到宝藏的次数:3(成功率 3.0%) 成功时的平均步数:18.7 所有回合的平均步数:96.2 ```
这意味着什么?在 100 次尝试中,随机智能体只有 3 次成功到达宝藏,成功率仅 3%。而成功时的平均步数约为 19 步,但所有回合的平均步数高达 96 步——因为大多数失败回合会一直随机走到最大步数(100 步)才结束。智能体几乎是在“熬时间”,而不是高效寻找。
观察一次成功路径
让我们具体看一次成功路径。假设某次模拟中,智能体在 18 步内找到了宝藏。路径可能如下(从 (0,0) 开始):
``` (0,0) → (0,1) → (1,1) 撞墙!停留 (1,1) → (1,0) → (2,0) → (2,1) → (2,2) → (1,2) → (1,3) 撞墙!停留 (1,3) → (2,3) → (3,3) 撞墙!停留 (3,3) → (3,2) → (4,2) 撞墙!停留 (4,2) → (4,1) → (4,0) → (3,0) → (3,1) 撞墙!停留 (3,1) → (3,2) → (3,3) 撞墙!停留 (3,3) → (2,3) → (2,4) → (3,4) → (4,4) 宝藏! ```
可以看到,路径充满了无效移动(撞墙)和 backtracking(走回头路)。智能体完全不知道方向,只是碰运气。这次成功很大程度上归功
这次成功很大程度上归功于运气——智能体在有限的100步内,恰好随机游走出了一条连通路径。但更值得关注的是那些失败的尝试:在100次模拟中,97次智能体在耗尽步数前始终未能找到宝藏。观察这些失败路径,你会发现它们惊人地相似:智能体在起点附近反复打转,频繁撞墙,偶尔深入迷宫腹地却又迷路折返,最终在步数用尽时绝望地停在某个偏僻角落。随机策略没有记忆,没有方向,每一次选择都像是一次独立的抛硬币。
这种“纯随机”策略虽然概念简单,但它揭示了一个深刻问题:如果智能体不能从经验中学习,那么交互就只是无意义的消耗。强化学习的核心魅力,正在于让智能体从这些“试错”经历中提取知识,逐步改善决策。现在,让我们给这个随机漫步者一颗“大脑”——一个能记住、能分析、能改进的简单学习机制。
从“无记忆”到“有经验”:计数智能体的诞生
随机智能体的决策函数 `choose_action(state)` 完全忽略状态,这显然浪费了宝贵信息。一个最朴素的改进思路是:记录每个动作在特定状态下的“历史表现”,并倾向于选择那些历史上表现更好的动作。
但如何定义“表现更好”呢?在迷宫中,最直接的指标是:某个动作在某个状态下,最终导向成功的次数。我们设计一个“经验计数表”,它是一个字典或数组,键是(状态,动作)对,值是该组合下成功到达终点的次数。
计数智能体的实现
让我们实现一个 `CountingAgent`:
```python class CountingAgent: def __init__(self, actions): self.actions = actions