2. 马尔可夫决策过程:状态、动作、奖励、转移概率与折扣因子
马尔可夫决策过程,简称 MDP。说实话,这是整个强化学习的基石。你想想看,不管多复杂的决策问题,最后都能抽象成 MDP 这套框架。我在做机器人路径规划时,一开始总想着直接写规则,结果代码越写越乱。后来老老实实搭了个 MDP 模型,问题一下子就清晰了。
2.1 什么是马尔可夫决策过程
MDP 说白了,就是一个数学框架。它用来描述智能体与环境交互的过程。智能体观察当前状态,做出一个动作,环境反馈一个奖励,然后状态发生转移。就这么简单。
但这里有个关键点——马尔可夫性。什么意思呢?就是未来的状态只取决于当前状态和当前动作,跟过去的历史没关系。嗯,这个假设在实际中不一定完全成立,但大多数情况下够用了。
核心五元组:MDP 可以用 (S, A, P, R, γ) 来表示。
- S:状态空间,所有可能状态的集合
- A:动作空间,所有可能动作的集合
- P:状态转移概率,P(s'|s,a)
- R:奖励函数,R(s,a,s')
- γ:折扣因子,γ ∈ [0,1]
2.2 状态与动作
状态,就是智能体感知到的环境信息。动作,就是智能体可以做的事情。这两个概念看起来简单,但实际设计时坑很多。
我习惯把状态设计成充分统计量。什么意思?就是状态里包含的信息,要能让你做出最优决策。比如下围棋,当前棋盘上的棋子分布就是状态,你不需要知道之前每一步是怎么下的。
动作空间分两种:离散和连续。离散动作空间,比如上下左右四个方向。连续动作空间,比如方向盘转多少度。我个人建议,能用离散就别用连续。连续动作空间处理起来复杂得多,我在自动驾驶项目中吃过这个亏。
我的经验:设计状态时,多问自己一句——这个信息对决策有用吗?如果没用,就别放进去。状态维度太高,学习会非常慢。
2.3 奖励函数
奖励函数,就是告诉智能体什么行为是好的。这是 MDP 里最需要花心思设计的地方。
奖励函数设计有几个原则:
- 稀疏 vs 密集:稀疏奖励只在目标达成时给,比如赢了给+1,输了给-1。密集奖励每一步都给,比如离目标近了给正奖励。密集奖励学得快,但容易让智能体钻空子。
- 延迟奖励:有些动作的后果要很久才显现。比如下棋,一步好棋可能要到几十步后才赢。这时候就需要折扣因子来帮忙。
- 奖励塑形:可以给一些中间奖励来引导学习。但要注意,不要改变最优策略。我曾经在某个项目中,给了一个看似合理的中间奖励,结果智能体学会了原地转圈刷分。
避坑指南:我曾经设计过一个奖励函数,让智能体既要快又要稳。结果它学会了走一步停一步,因为这样既不会太快摔倒,又能拿到时间奖励。后来我改成只奖励最终目标,问题才解决。
2.4 状态转移概率
转移概率 P(s'|s,a),描述的是在状态 s 下执行动作 a,转移到状态 s' 的概率。这个概率反映了环境的随机性。
比如你让机器人往前走一步,它可能因为地面打滑,只走了半步。这个不确定性,就体现在转移概率里。
转移概率有两种情况:
- 已知模型:比如棋盘游戏,规则是确定的,转移概率就是 0 或 1。这种情况可以用动态规划求解。
- 未知模型:大多数实际问题,转移概率是未知的。这时候就需要通过采样来估计,或者用无模型方法。
我个人习惯,先假设模型已知,用动态规划跑一遍,看看最优策略长什么样。然后再切换到未知模型,用强化学习去逼近。这样心里有个底。
2.5 折扣因子
折扣因子 γ,是 MDP 里一个微妙但重要的参数。它决定了智能体有多「短视」。
- γ = 0:只看眼前,只关心即时奖励。
- γ = 1:无限远视,所有未来奖励同等重要。
- γ 在 0 到 1 之间:越靠近 1,越看重长期收益。
为什么需要折扣因子?三个原因:
- 数学上保证收敛:无限时间步的奖励求和可能发散,折扣因子让求和收敛。
- 模拟不确定性:未来的奖励不如现在的可靠,打个折扣更合理。
- 控制策略偏好:调整 γ 可以改变智能体的行为风格。
实际经验:我一般从 γ=0.9 开始调。如果智能体太短视,就增大到 0.95 或 0.99。如果训练不稳定,就减小一些。没有固定值,全靠试。
2.6 核心逻辑流程图
下面这张图,展示了 MDP 的核心交互逻辑。我每次讲课时都会画一遍,因为它太重要了。
你看这个循环:智能体观察状态 s,做出动作 a,环境反馈奖励 r 并转移到新状态 s'。然后重复这个过程。这就是 MDP 的全部。
2.7 一个简单的代码示例
下面我用 Python 实现一个极简的 MDP 环境。这个环境只有两个状态,两个动作。代码很简单,但能帮你理解 MDP 的结构。
import numpy as np
class SimpleMDP:
def __init__(self, gamma=0.9):
self.states = [0, 1] # 两个状态
self.actions = [0, 1] # 两个动作
self.gamma = gamma # 折扣因子
# 转移概率 P(s'|s,a)
# P[state][action][next_state]
self.P = {
0: {0: {0: 0.8, 1: 0.2}, # 状态0,动作0
1: {0: 0.3, 1: 0.7}}, # 状态0,动作1
1: {0: {0: 0.6, 1: 0.4}, # 状态1,动作0
1: {0: 0.1, 1: 0.9}} # 状态1,动作1
}
# 奖励函数 R(s,a,s')
self.R = {
0: {0: {0: 1, 1: -1}, # 状态0,动作0
1: {0: -1, 1: 2}}, # 状态0,动作1
1: {0: {0: 0, 1: 1}, # 状态1,动作0
1: {0: -2, 1: 3}} # 状态1,动作1
}
def step(self, state, action):
# 根据转移概率采样下一个状态
probs = self.P[state][action]
next_state = np.random.choice(
self.states, p=[probs[s] for s in self.states]
)
# 获取奖励
reward = self.R[state][action][next_state]
return next_state, reward
def value_iteration(self, theta=1e-6):
# 值迭代求解最优价值函数
V = np.zeros(len(self.states))
while True:
delta = 0
for s in self.states:
v = V[s]
# Bellman 最优方程
max_value = float('-inf')
for a in self.actions:
value = 0
for s_next in self.states:
prob = self.P[s][a][s_next]
reward = self.R[s][a][s_next]
value += prob * (reward + self.gamma * V[s_next])
max_value = max(max_value, value)
V[s] = max_value
delta = max(delta, abs(v - V[s]))
if delta < theta:
break
return V
# 使用示例
mdp = SimpleMDP(gamma=0.95)
optimal_V = mdp.value_iteration()
print("最优价值函数:", optimal_V)
小提示:代码里的值迭代算法,就是不断用 Bellman 最优方程更新价值函数,直到收敛。这是求解已知 MDP 的标准方法。
2.8 实际应用中的注意事项
最后,分享几个我在项目中积累的经验:
- 状态空间爆炸:状态太多时,转移概率矩阵会变得巨大。这时候需要用函数近似,比如神经网络。
- 奖励设计是艺术:我见过太多项目,算法没问题,但奖励设计不合理导致学不出来。多花时间在奖励设计上,绝对值得。
- 折扣因子不是玄学:它直接影响策略。任务周期短,γ 可以小一些。任务周期长,γ 要大一些。
- 先跑通再优化:我习惯先用小规模问题验证 MDP 模型是否正确,再扩展到大规模。这样能快速发现设计缺陷。
再次提醒:我曾经在一个项目中,把状态设计得过于复杂,包含了大量无关信息。结果算法跑了三天都没收敛。后来精简状态,只保留关键信息,半天就出结果了。少即是多,在 MDP 设计中尤其适用。
好了,MDP 的核心内容就这些。状态、动作、奖励、转移概率、折扣因子,这五个要素构成了强化学习的基石。理解透它们,后面的算法学起来就轻松多了。