2. 状态(State)的定义与表示
状态,说白了就是描述智能体当前所处环境的全部有用信息。
我刚入行时总觉得这个概念太虚,直到在项目里吃过亏才真正理解它有多重要。
2.1 什么是状态
在马尔可夫决策过程中,状态 S 是一个集合,里面包含了智能体做决策所需的一切。每次决策前,智能体看一眼当前状态,然后决定下一步动作。
举一个最简单的例子:你玩贪吃蛇,当前蛇头的位置、食物的位置、蛇身的长度——这些就是状态。少了任何一个,决策就会出错。
嗯,这里要注意一个坑:什么叫“完整刻画”?
我曾经在一个机器人导航项目里,只用坐标(x,y)做状态,结果机器人总在同一个地方撞墙——因为我忘了把朝向角度加进去。状态一旦漏掉关键信息,整个策略就废了。
2.2 状态空间的分类
状态空间根据实际情况可以分成两大类,我来跟你聊聊各自的特点。
| 类型 | 特点 | 典型例子 |
|---|---|---|
| 离散状态空间 | 状态数量有限,可枚举 | 网格世界、棋类游戏、电梯楼层 |
| 连续状态空间 | 状态取值连续,不可枚举 | 机器人位置(x,y,θ)、风速、股票价格 |
实际项目中,离散状态处理起来舒服很多,因为我们可以用表格把每个状态的价值存下来。但现实世界大多是连续的——你想想看,机器人位置怎么可能只落在整数格点上?
2.3 状态的表示方法
状态怎么存?取决于你用什么工具。在 Python 里,我习惯这么干:
# 离散状态:直接用元组或整数索引
state_grid = (2, 3) # 网格第2行第3列
state_idx = 23 # 展平后的全局索引
# 连续状态:用 NumPy 数组或 namedtuple
import numpy as np
state_continuous = np.array([1.5, 2.7, 0.3]) # x, y, theta
# 结构化状态:用字典(适合带属性)
state_dict = {'pos': (1.2, 3.4), 'velocity': 0.5, 'is_obstacle': False}
我个人习惯把离散状态映射成整数编号,这样在训练 Q 表时能直接当行索引。连续状态则一律用向量,方便喂给神经网络。
2.4 状态转移:状态如何变化
状态不是静止的。智能体执行一个动作后,环境会进入新状态。这个变化规律叫状态转移函数:
P(s' | s, a) —— 在状态 s 执行动作 a 后,转移到 s' 的概率。
为什么要强调概率?因为环境往往有随机性。比如你让机器人往前走,可能遇到打滑、风阻,最后实际位置跟预期有偏差。
# 简单示例:网格世界的状态转移逻辑
def transition(state, action):
x, y = state
if action == 'up':
next_state = (x, y+1) if y+1 < grid_height else (x, y)
elif action == 'down':
next_state = (x, y-1) if y-1 >= 0 else (x, y)
# ... 其他方向
return next_state
注意这里的确定性转移。如果要做成随机版本,你可以在代码里加一个概率分支——比如 0.8 概率走预设方向,0.2 概率滑到旁边。
2.5 状态空间的结构化表示
下面这张图展示了经典网格世界中的状态空间结构。每个格子是一个状态,智能体通过上下左右移动在状态间跳转。
每个格子就是一个离散状态。智能体从当前格子走到相邻格子,就是一次状态转移。箭头表示了可能的下一个状态——如果在边界上,转移会留在原地。
2.6 实战:状态设计检查清单
我总结了一份清单,每次设计状态时我都会过一遍:
- 完整性——所有会影响决策的因素都包含了吗?
- 马尔可夫性——只看当前状态,能否独力决定未来演化?
- 可观测性——智能体能获取到这些状态信息吗?
- 尺度选择——离散化粒度太粗会丢失信息,太细会让状态空间爆炸。
- 冗余消除——有没有两个状态其实等价?可以合并吗?
举个例子。我做过一个自动驾驶仿真项目,最初把绝对坐标当状态,结果车在相同位置但不同速度下表现完全不同——后来加入了速度分量。说白了,状态里少一个关键变量,模型就学不会。
2.7 总结
状态是 MDP 的起点,也是建模时最容易出错的地方。
- 离散状态用整数索引,连续状态用向量。
- 状态转移函数决定了环境如何响应动作。
- 设计时要兼顾完整性和可行性——信息不能少,但也不能太多。
我的经验是:先从一个简单的离散状态集合开始,跑通流程后再逐步细化。别一开始就想把整个宇宙装进去。