学习强化学习时,很多初学者会陷入公式和理论的泥沼。贝尔曼方程、价值函数、策略评估...这些概念看似抽象难懂,但其实它们都可以通过代码变得直观。就像学习游泳不能只靠看书,理解MDP最好的方式就是亲手实现一个。
Python作为数据科学和机器学习领域的主流语言,配合NumPy等科学计算库,能让我们高效地构建和实验MDP环境。通过代码,那些数学符号会突然变得鲜活起来——p(s',r|s,a)不再是一组字母,而是环境对智能体动作的真实反馈。
动手实践的价值:
让我们从一个经典的4x4网格世界开始。这个环境中:
python复制import numpy as np
class GridWorld:
def __init__(self, size=4):
self.size = size
self.actions = ['up', 'down', 'left', 'right']
self.goal = (3, 3) # 右下角为目标
self.trap = (1, 1) # 设置一个陷阱位置
# 定义状态转移概率:80%按指令移动,20%随机其他方向
self.transition_probs = {
'up': {'up':0.8, 'left':0.1, 'right':0.1},
'down': {'down':0.8, 'left':0.1, 'right':0.1},
'left': {'left':0.8, 'up':0.1, 'down':0.1},
'right': {'right':0.8, 'up':0.1, 'down':0.1}
}
状态转移是MDP的核心。我们需要处理边界情况,并按照定义的概率分布决定实际移动方向。
python复制 def step(self, state, action):
x, y = state
# 如果已经在终止状态,不再变化
if state == self.goal or state == self.trap:
return state, 0, True
# 根据概率分布决定实际动作
actual_action = np.random.choice(
list(self.transition_probs[action].keys()),
p=list(self.transition_probs[action].values())
)
# 执行移动
if actual_action == 'up':
x = max(x-1, 0)
elif actual_action == 'down':
x = min(x+1, self.size-1)
elif actual_action == 'left':
y = max(y-1, 0)
elif actual_action == 'right':
y = min(y+1, self.size-1)
new_state = (x, y)
# 计算奖励
if new_state == self.goal:
reward = 1
done = True
elif new_state == self.trap:
reward = -1
done = True
else:
reward = -0.04 # 每步小惩罚鼓励尽快到达目标
done = False
return new_state, reward, done
状态价值函数v(s)表示从状态s开始,按照策略π执行的期望回报。它满足贝尔曼方程:
v(s) = Σ π(a|s) * Σ p(s',r|s,a)[r + γv(s')]
这个递归关系是策略评估的基础。
python复制def policy_evaluation(policy, env, gamma=0.9, theta=1e-6):
# 初始化价值函数
V = np.zeros((env.size, env.size))
while True:
delta = 0
# 遍历所有状态
for i in range(env.size):
for j in range(env.size):
state = (i, j)
old_value = V[i][j]
# 如果是终止状态,价值为0
if state == env.goal or state == env.trap:
V[i][j] = 0
continue
new_value = 0
# 对每个可能的动作
for action in env.actions:
# 获取转移概率和奖励
prob = policy[state][action]
next_state, reward, _ = env.step(state, action)
x, y = next_state
# 累加贝尔曼方程各项
new_value += prob * (reward + gamma * V[x][y])
V[i][j] = new_value
delta = max(delta, abs(old_value - V[i][j]))
# 检查收敛
if delta < theta:
break
return V
让我们定义一个简单的均匀随机策略(每个动作概率相等),然后评估其价值函数:
python复制# 创建均匀随机策略
uniform_policy = {}
for i in range(4):
for j in range(4):
uniform_policy[(i,j)] = {'up':0.25, 'down':0.25, 'left':0.25, 'right':0.25}
# 评估策略
env = GridWorld()
V = policy_evaluation(uniform_policy, env)
# 打印结果
print("状态价值函数:")
print(np.round(V, 2))
典型输出可能如下:
code复制[[ 0.13 0.07 0.03 -0.01]
[ 0.16 -1. 0.07 -0.04]
[ 0.19 0.12 0.16 0.11]
[ 0.24 0.19 0.32 0. ]]
注意:由于状态转移的随机性,每次运行结果可能略有不同。陷阱位置(1,1)的价值明显低于周围状态,而靠近目标的状态价值较高。
折扣因子γ决定了未来奖励的现值。让我们比较不同γ值下的价值函数:
| γ值 | 特点 | 对策略的影响 |
|---|---|---|
| 0.0 | 只考虑即时奖励 | 极度短视,可能无法到达远距离目标 |
| 0.9 | 平衡当前和未来奖励 | 能够规划多步路径 |
| 1.0 | 平等对待所有未来奖励 | 在持续任务中可能导致无限值 |
python复制# 比较不同gamma值
gammas = [0.0, 0.5, 0.9, 0.99]
results = {}
for gamma in gammas:
V = policy_evaluation(uniform_policy, env, gamma=gamma)
results[f"γ={gamma}"] = np.round(V, 2)
我们之前设置了80%概率执行指令动作,20%随机其他方向。这种不确定性对策略有重要影响:
python复制# 测试确定性转移(100%执行指令)的情况
deterministic_transitions = {
'up': {'up':1.0},
'down': {'down':1.0},
'left': {'left':1.0},
'right': {'right':1.0}
}
env_det = GridWorld()
env_det.transition_probs = deterministic_transitions
V_det = policy_evaluation(uniform_policy, env_det)
print("\n确定性转移下的价值函数:")
print(np.round(V_det, 2))
关键发现:随机性转移使得价值函数更"平滑",因为状态间的联系更紧密;确定性转移则可能产生更极端的价值差异。
让我们增强网格世界,加入不可通过的障碍物:
python复制class AdvancedGridWorld(GridWorld):
def __init__(self):
super().__init__()
self.obstacles = [(1,3), (2,1), (3,1)] # 障碍物位置
def step(self, state, action):
x, y = state
if state in self.obstacles: # 不应该到达障碍物
raise ValueError("Agent cannot be on obstacle!")
if state == self.goal or state == self.trap:
return state, 0, True
actual_action = np.random.choice(
list(self.transition_probs[action].keys()),
p=list(self.transition_probs[action].values())
)
new_x, new_y = x, y
if actual_action == 'up':
new_x = max(x-1, 0)
elif actual_action == 'down':
new_x = min(x+1, self.size-1)
elif actual_action == 'left':
new_y = max(y-1, 0)
elif actual_action == 'right':
new_y = min(y+1, self.size-1)
# 检查新位置是否是障碍物
if (new_x, new_y) in self.obstacles:
new_x, new_y = x, y # 撞墙,保持原位
new_state = (new_x, new_y)
if new_state == self.goal:
reward = 1
done = True
elif new_state == self.trap:
reward = -1
done = True
else:
reward = -0.04
done = False
return new_state, reward, done
python复制# 评估复杂环境
adv_env = AdvancedGridWorld()
V_adv = policy_evaluation(uniform_policy, adv_env)
print("\n复杂环境的价值函数:")
print(np.round(V_adv, 2))
典型输出可能显示障碍物周围的价值变化,反映了绕路的必要性。
当你的MDP行为不符合预期时,检查以下方面:
对于大型状态空间:
python复制def async_policy_evaluation(policy, env, gamma=0.9, theta=1e-6):
V = np.zeros((env.size, env.size))
states = [(i,j) for i in range(env.size) for j in range(env.size)]
while True:
delta = 0
np.random.shuffle(states) # 随机状态顺序
for state in states:
i, j = state
old_value = V[i][j]
if state == env.goal or state == env.trap:
V[i][j] = 0
continue
new_value = 0
for action in env.actions:
prob = policy[state][action]
next_state, reward, _ = env.step(state, action)
x, y = next_state
new_value += prob * (reward + gamma * V[x][y])
V[i][j] = new_value
delta = max(delta, abs(old_value - V[i][j]))
if delta < theta:
break
return V
虽然网格世界简单,但它包含了MDP的所有关键要素。将这些概念应用到实际问题时:
为了与主流强化学习工具兼容,我们可以实现OpenAI Gym接口:
python复制import gym
from gym import spaces
class GymGridWorld(gym.Env):
def __init__(self):
self.gridworld = GridWorld()
self.action_space = spaces.Discrete(4) # 0=up, 1=down, 2=left, 3=right
self.observation_space = spaces.Tuple((
spaces.Discrete(4), # x坐标
spaces.Discrete(4) # y坐标
))
self.state = (0, 0) # 初始状态
def reset(self):
self.state = (0, 0)
return self.state
def step(self, action):
action_map = ['up', 'down', 'left', 'right']
state, reward, done = self.gridworld.step(self.state, action_map[action])
self.state = state
return state, reward, done, {} # 最后一个空字典是info
这样,我们的环境就可以与大多数RL算法库兼容了。
让我们看一个更实际的例子——库存管理MDP:
python复制class InventoryMDP:
def __init__(self, max_inventory=10, max_order=5):
self.max_inventory = max_inventory
self.max_order = max_order
# 状态:(库存量, 已下单但未到货量)
# 动作:每次下单数量
def step(self, state, order):
inventory, on_order = state
order = min(order, self.max_order) # 不能超过最大订单量
# 随机需求
demand = np.random.randint(0, 5)
# 新库存计算
new_inventory = max(inventory - demand, 0)
sales = inventory - new_inventory
# 到货(上期订单)
new_inventory += on_order
new_inventory = min(new_inventory, self.max_inventory)
# 新状态
new_state = (new_inventory, order)
# 奖励:销售额 - 存储成本 - 订单成本
reward = sales * 10 - new_inventory * 1 - order * 2
return new_state, reward, False # 持续任务
这个例子展示了如何将MDP框架应用于商业决策问题。通过调整奖励函数中的成本系数,可以研究不同策略对库存管理的影响。