1. 动态规划算法
算法背景:策略迭代算法是一种典型的动态规划。
- 动态规划应用于马尔可夫决策过程的规划问题而不是学习问题,我们必须对环境是完全已知的,才能做动态规划,也就是要知道状态转移概率和对应的奖励。
- 在白盒环境中,不需要通过智能体和环境的大量交互来学习,可以直接用动态规划求解状态价值函数。但是,现实中的白盒环境很少,这也是动态规划算法的局限之处,我们无法将其运用到很多实际场景中。另外,策略迭代和价值迭代通常只适用于有限马尔可夫决策过程,即状态空间和动作空间是离散且有限的。
- 使用动态规划完成预测问题和控制问题的求解,是解决马尔可夫决策过程预测问题和控制问题的非常有效的方式。
策略迭代算法有两个步骤:即策略评估和策略迭代。本文主要结合代码,详细讲解第一个步骤:策略评估。
2 策略评估公式
当前我们在优化策略 π,在优化过程中得到一个最新的策略。我们先保证这个策略不变,然后估计它的价值,即给定当前的策略函数来估计状态价值函数V。策略评估通过对价值函数的不断迭代,从而估计出每个状态的价值函数,如下式所示。把贝尔曼期望备份转换成动态规划的迭代。
看到这个公式可能会有点懵,其实它的推导也非常简单。首先在马尔克夫决策过程中,给出了Q函数的贝尔曼方程:
然后,我们知道,对策略函数进行求和后,我们就能将Q函数转化为V函数。
将(1)式代入(2)式中便可得:
将(4)式写成迭代形式,便是式(0)啦!
3 策略评估代码
3.1. 背景介绍
网格世界(Grid World) 规则:网格中的每一个小格都对应于环境中的状态. 在一个小格上, 有 4 种可能的动作: 北移, 南移,东移, 西移, 其中各个动作都确定性地使智能体在网格上沿对应的方向移动一格. 如果所采取的动作将令智能体脱离网格, 那么该动作的结果为智能体的位置保持不变, 且造成 −1 的奖赏. 除了上述动作与将智能体移出特殊状态 A 与 B 的动作外, 其他的动作只会造成 0 的奖赏.在状态 A 上, 所有的 4 个动作会产生 +10 的奖赏, 并将智能体带至 A’. 在状态 B 上, 所有的 4个动作会产生 +5 的奖赏, 并将智能体带至 B’.
3.2 代码
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.table import Table
matplotlib.use('Agg')
WORLD_SIZE = 5
A_POS = [0, 1]
A_PRIME_POS = [4, 1]
B_POS = [0, 3]
B_PRIME_POS = [2, 3]
DISCOUNT = 0.9
# left, up, right, down
ACTIONS = [np.array([0, -1]),
np.array([-1, 0]),
np.array([0, 1]),
np.array([1, 0])]
ACTIONS_FIGS = ['←', '↑', '→', '↓']
ACTION_PROB = 0.25
def step(state, action):
if state == A_POS:
return A_PRIME_POS, 10
if state == B_POS:
return B_PRIME_POS, 5
next_state = (np.array(state) + action).tolist()
x, y = next_state
if x < 0 or x >= WORLD_SIZE or y < 0 or y >= WORLD_SIZE:
reward = -1.0
next_state = state # 如果碰壁,则位置不变
else:
reward = 0
return next_state, reward
def draw_image(image):
fig, ax = plt.subplots()
ax.set_axis_off()
tb = Table(ax, bbox=[0, 0, 1, 1])
nrows, ncols = image.shape
width, height = 1.0 / ncols, 1.0 / nrows
# Add cells
for (i, j), val in np.ndenumerate(image):
# add state labels
if [i, j] == A_POS:
val = str(val) + " (A)"
if [i, j] == A_PRIME_POS:
val = str(val) + " (A')"
if [i, j] == B_POS:
val = str(val) + " (B)"
if [i, j] == B_PRIME_POS:
val = str(val) + " (B')"
tb.add_cell(i, j, width, height, text=val,
loc='center', facecolor='white')
# Row and column labels...
for i in range(len(image)):
tb.add_cell(i, -1, width, height, text=i+1, loc='right',
edgecolor='none', facecolor='none')
tb.add_cell(-1, i, width, height/2, text=i+1, loc='center',
edgecolor='none', facecolor='none')
ax.add_table(tb)
def draw_policy(optimal_values):
fig, ax = plt.subplots()
ax.set_axis_off()
tb = Table(ax, bbox=[0, 0, 1, 1])
nrows, ncols = optimal_values.shape
width, height = 1.0 / ncols, 1.0 / nrows
# Add cells
for (i, j), val in np.ndenumerate(optimal_values):
next_vals=[]
for action in ACTIONS:
next_state, _ = step([i, j], action)
next_vals.append(optimal_values[next_state[0],next_state[1]])
best_actions=np.where(next_vals == np.max(next_vals))[0]
val=''
for ba in best_actions:
val+=ACTIONS_FIGS[ba]
# add state labels
if [i, j] == A_POS:
val = str(val) + " (A)"
if [i, j] == A_PRIME_POS:
val = str(val) + " (A')"
if [i, j] == B_POS:
val = str(val) + " (B)"
if [i, j] == B_PRIME_POS:
val = str(val) + " (B')"
tb.add_cell(i, j, width, height, text=val,
loc='center', facecolor='white')
# Row and column labels...
for i in range(len(optimal_values)):
tb.add_cell(i, -1, width, height, text=i+1, loc='right',
edgecolor='none', facecolor='none')
tb.add_cell(-1, i, width, height/2, text=i+1, loc='center',
edgecolor='none', facecolor='none')
ax.add_table(tb)
def figure_3_2():
value = np.zeros((WORLD_SIZE, WORLD_SIZE))
while True:
# keep iteration until convergence
new_value = np.zeros_like(value)
for i in range(WORLD_SIZE):
for j in range(WORLD_SIZE):
for action in ACTIONS:
(next_i, next_j), reward = step([i, j], action)
# bellman equation
new_value[i, j] += ACTION_PROB * (reward + DISCOUNT * value[next_i, next_j])
if np.sum(np.abs(value - new_value)) < 1e-4:
draw_image(np.round(new_value, decimals=2))
plt.savefig('../images/figure_3_2.png')
plt.close()
break
value = new_value
if __name__ == '__main__':
figure_3_2()
3.3 代码解释
代码比较长,但是大部分都是可视化的代码,核心部分其实只有一行。
new_value[i, j] += ACTION_PROB * (reward + DISCOUNT * value[next_i, next_j])
再对比一下式(0),是不是一下子就豁然开朗了呢!注意这是确定性的环境,所以状态概率p恒等于1。
如果还不清楚,那么接着看下去。为了更清楚的看出每个格子中的价值是如何更新的,笔者结合公式,取其中的几个格子进行分析。
3.4 第一次更新
我们对着公式,一步步看价值是怎么更新的。
首先,给定动作以后,状态间的转移是确定的,即=1。比如如果智能体从1号状态出发,向右走,那么它会到达横向的2号格子。很多时候有些环境是概率性的,比如智能体在第 1 号状态,它选择往右走的时候,地板可能是滑的,可能会滑到纵向的2号格子。
对(1,1)格子。
- 向左 = 0.25 * (-1 + 0.9 * 1 * 0) = -0.25。向左走会碰壁,所以reward为-1,且智能体继续待在原地。最后一项为0
- 向上 = 0.25 * (-1 + 0.9 * 1 * 0) = -0.25。向上走也会碰壁,所以reward为-1,且智能体继续待在原地。最后一项为0
- 向右 = 0.25 * (0 + 0.9 * 1 * 0) = 0
- 向下 = 0.25 * (0 + 0.9 * 1 * 0) = 0
- 将这四项相加,便得到了(1,1)格子的价值0.5。
计算(2,2)格子。这时智能体无论做任何动作都会转移到第五行第二列的格子,=1,= V(5,2)=0
- 向左 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
- 向上 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
- 向右 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
- 向下 = 0.25 * (10 + 0.9 * 1 * 0) = 2.5
- 将这四项相加,便得到了(2,2)格子的价值10。
读者可以试着自己计算一下其它格子的价值,然后和图上的结果,进行校对。
3.5 第二次更新
接下来是第二轮的更新。其实初学的时候是很容易算错的,大家可以一起来试试哦!
对(1,1)格子。
- 向左 = 0.25 * (-1 + 0.9 * 1 * -0.5) = -0.3625,
- 向上 = 0.25 * (-1 + 0.9 * 1 * -0.5) = -0.3625,
- 向右 = 0.25 * (0 + 0.9 * 10) = 2.25,
- 向下 = 0.25 * (0 + 0.9 * 1 * -0.25) = -0.05625,
- 相加得1.46875。
计算(2,2)格子。
- 向左 = 0.25 * (10+ 0.9 * 1 * -0.25),
- 向上 = 0.25 * (10 + 0.9 * 1 * -0.25) ,
- 向右 = 0.25 * (10 + 0.9 * 1 * -0.25) ,
- 向下 = 0.25 * (10 + 0.9 * 1 * -0.25) ,
- 相加得9.775。