马尔科夫决策(MDP)是强化学习的基础,在这里,我们首先引入状态价值函数和动作价值函数的概念和关系:
可以很明显的看出来,状态价值函数和动作价值函数只差一个,这个是在状态s下选择每个动作的概率。这是很容易理解的,
看上图,s代表的是状态,a代表执行的动作,r代表通过执行动作获得的回报,p为选择动作的概率, g是执行动作后到下一个状态的概率。如果把圆圈看做 ‘能量’,那么根据全概率公式(类似),s0这个粉圈的 ‘能量’ 是不是就是p1*q(a1)+p2*q(a2)+p3*q(a3)。所以能得出v关于q的关系。
但是,q和v的关系呢?在图中,我们也可以很明显的看到,a1的 ‘能量’ 是不是就是(r1+(s1的‘能量’))*g1 +.....。所以动作价值函数就等于执行动作后到达下一个状态的概率 * (回报+下一个状态的状态价值函数)的总和。 现在看公式是不是就很清晰了。
关于详细计算,可以看这个链接
ps: 我以前也不太理解为什么会有,这个函数的存在,因为我当时接触到的,并且我一直以为的,在执行一个动作之后不就是明确到达下一个状态,后来换了一种思考方式。
在武侠小说中,有人从悬崖往下跳,在悬崖上可以看做s0,往下跳的动作可以看做a0,但是往下跳之后却会有很多的不同状态,比如,掉到半山腰捡到武功秘籍,看做s1,r很大,这是主角。掉到山谷,但是有树接着,看做s2, r一般大,这是比较重要的配角。掉到地上,可以看做s3,r为负,这是炮灰甲乙丙丁。
所以一个动作会有很多不同的状态。
接下来就是关于用代码实现求解状态价值函数:
看sutton书上的例题3.5,
便于计算,我没有用书上的5x5的格子,而是3x3的,A(0,1), A'(2, 1),B去掉。
解法1:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.table import Table
WORLD_SIZE = 3 # 3x3的网格
A_POS = [0, 1] # A的位置
A_PRIME_POS = [2, 1] # A'的位置
DISCOUNT = 0.9 # gamma
ACTIONS = [np.array([0, -1]),
np.array([-1, 0]),
np.array([0, 1]),
np.array([1, 0])]
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:
next_state = state
reward = -1
else:
reward = 0
return next_state, reward
# 添加表格
def draw_image(images):
fig, ax = plt.subplots()
ax.set_axis_off()
tb = Table(ax, bbox=[0, 0, 1, 1])
nrows, ncols = images.shape
height, width = 1.0/ncols, 1.0/nrows
for (i, j), image in np.ndenumerate(images):
if [i, j] == A_POS:
image = str(image) + 'A_POS'
if [i, j] == B_POS:
image = str(image) + 'B_POS'
if [i, j] == A_PRIME_POS:
image = 'A_PRIME_POS'
if [i, j] == B_PRIME_POS:
image = 'B_PRIME_POS'
tb.add_cell(i, j, width, height, text=image, loc='center',
facecolor='white')
for i in range(len(images)):
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 figure3_2():
value = np.zeros((WORLD_SIZE, WORLD_SIZE))
while True:
new_value = np.zeros_like(value)
for i in range(WORLD_SIZE):
for j in range(WORLD_SIZE):
state = [i, j]
for action in ACTIONS:
next_state,reward = step(state, action)
new_value[i, j] += ACTION_PROB * (reward + DISCOUNT*value[next_state[0], next_state[1]])
if np.sum(np.abs(value - new_value)) < 1e-4:
draw_image(np.round(new_value, decimals=2))
plt.show()
break
value = new_value
figure3_2()
最后得出各个状态值为:
解法1是通过迭代遍历,对每个状态的每个动作都计算对应的动作价值函数,然后通过加权,来计算迭代之后的状态价值函数。然后通过一个很小的阈值,来判断状态价值函数是否最后收敛。这种方法比较麻烦,而且最后的收敛情况和阈值有关系。但是适用于状态、动作比较多的情况。
解法2:
解法2需要对状态价值函数做一个小小的变化:
因为是概率完备的,所以他们的和为1。这时候价值状态函数就可以改写为:
再进行变换:
其中是这个状态转移到下一个状态的概率(相当于上面五颜六色那个图的 p1*g1, p1*g2, ............)。
继续变换, 写成矩阵的形式P:
代码如下:
p = [
[0.5,0.25,0,0.25,0,0,0,0,0],
[0,0,0,0,0,0,0,1,0],
[0,0.25,0.5,0,0,0.25,0,0,0],
[0.25,0,0,0.25,0.25,0,0.25,0,0],
[0,0.25,0,0.25,0,0.25,0,0.25,0],
[0,0,0.25,0,0.25,0.25,0,0,0.25],
[0,0,0,0.25,0,0,0.5,0.25,0],
[0,0,0,0,0.25,0,0.25,0.25,0.25],
[0,0,0,0,0,0.25,0,0.25,0.5]
]
p = np.array(p)
r = [-0.5,10,-0.5,-0.25,0,-0.25,-0.5,-0.25,-0.5]
r = np.array(r)
v = np.dot(np.linalg.inv(np.eye(9, 9) - DISCOUNT*p), r)
v
可以看出,这两个解法的状态值的是一样的。但是,解法2只能运用在状态动作较少的环境中,例如5x5的格子,那么它的状态转移矩阵就是25x25的,并不适用。
综上:就是我对马尔科夫决策过程的全部理解了,如果大家有什么疑问,欢迎大家在评论区留言,咱们一起探讨