首先来看一下之前提及的贝尔曼期望方程:
V
π
(
s
)
=
∑
a
∈
A
π
(
a
∣
s
)
(
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
π
(
s
′
)
)
V^{\pi}(s)=\sum_{a\in A}\pi(a|s)(r(s,a)+\gamma\sum_{s'\in S}P(s'|s,a)V^{\pi}(s'))
Vπ(s)=a∈A∑π(a∣s)(r(s,a)+γs′∈S∑P(s′∣s,a)Vπ(s′)) 其中
π
(
a
∣
s
)
\pi(a|s)
π(a∣s)由策略决定,
r
(
s
,
a
)
,
P
(
s
′
∣
s
,
a
)
r(s,a),P(s'|s,a)
r(s,a),P(s′∣s,a)由MDP决定。因此上述贝尔曼期望方程可以看作,在策略确定以及MDP确定的情况下,利用状态
s
s
s可转移到的所有周边状态
s
′
s'
s′的价值函数来计算
s
s
s的价值函数。
策略评估是一个循环迭代的过程,第k+1轮中状态
s
s
s的价值函数使用第k轮中与之相关的状态
s
′
s'
s′的价值函数来求解。当迭代轮次最够多时,认为当前价值函数逼近或者就是真正的价值函数。实际应用中策略评估使用贝尔曼期望函数进行迭代使用大量计算资源,无须迭代无限轮次,只需要某次迭代更新的幅度非常小时算法即可停止。
3.2 策略提升
策略评估之后,我们获得了在当前策略下所有状态的价值函数,也就是得到了在当前策略下每个状态出发到达终结状态的期望回报。得到了状态价值函数,也就相当于得到了动作价值函数。我们便可以在当前策略的基础上每个状态都贪心地选择最优的动作
a
a
a以最大化
Q
π
(
s
,
a
)
Q^{\pi}(s,a)
Qπ(s,a),从而实现策略提升。
π
′
(
s
)
=
arg
max
a
Q
π
(
s
,
a
)
=
arg
max
a
{
r
(
s
,
a
)
+
γ
∑
s
′
P
(
s
′
∣
s
,
a
)
V
π
(
s
′
)
}
贪心算法与贪心策略我们需要考虑的问题是,局部最优的累积最终是否达到全局最优。在策略提升中需要考虑的问题是,每个状态都选择动作价值最大的动作得到的新策略
π
′
\pi'
π′是否在每个状态的价值都不低于原策略
π
\pi
π在该状态的价值。证明如下:
V
π
(
s
)
≤
Q
π
(
s
,
π
′
(
s
)
)
=
E
π
′
[
R
t
+
γ
V
π
(
S
t
+
1
)
∣
S
t
=
s
]
≤
E
π
′
[
R
t
+
γ
Q
π
(
S
t
+
1
,
π
′
(
S
t
+
1
)
)
∣
S
t
=
s
]
=
E
π
′
[
R
t
+
γ
R
t
+
1
+
γ
2
V
π
(
S
t
+
2
)
∣
S
t
=
s
]
≤
E
π
′
[
R
t
+
γ
R
t
+
1
+
γ
2
R
t
+
2
+
γ
3
V
π
(
S
t
+
3
)
∣
S
t
=
s
]
.
.
.
≤
E
π
′
[
R
t
+
γ
R
t
+
1
+
γ
2
R
t
+
2
+
γ
3
R
t
+
3
+
.
.
.
∣
S
t
=
s
]
=
V
π
′
(
s
)
import copy
classPolicyIteration:""" 策略迭代算法 """def__init__(self, env, theta, gamma):
self.env = env
self.v =[0]* self.env.ncol * self.env.nrow # 初始化价值为0
self.pi =[[0.25,0.25,0.25,0.25]for i inrange(self.env.ncol * self.env.nrow)]# 初始化为均匀随机策略
self.theta = theta # 策略评估收敛阈值
self.gamma = gamma # 折扣因子defpolicy_evaluation(self):# 策略评估
cnt =1# 计数器while1:
max_diff =0
new_v =[0]* self.env.ncol * self.env.nrow # 定义一个新容器for s inrange(self.env.ncol * self.env.nrow):
qsa_list =[]# 开始计算状态s下的所有Q(s,a)价值for a inrange(4):
qsa =0for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p *(r + self.gamma * self.v[next_state]*(1- done))
qsa_list.append(self.pi[s][a]* qsa)
new_v[s]=sum(qsa_list)# 状态价值函数和动作价值函数之间的关系
max_diff =max(max_diff,abs(new_v[s]- self.v[s]))
self.v = new_v
if max_diff < self.theta:break# 满足收敛条件,退出评估迭代
cnt +=1print("策略评估进行%d轮后完成"% cnt)defpolicy_improvement(self):# 策略提升for s inrange(self.env.nrow * self.env.ncol):
qsa_list =[]for a inrange(4):
qsa =0for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p *(r + self.gamma * self.v[next_state]*(1- done))
qsa_list.append(qsa)
maxq =max(qsa_list)
cntq = qsa_list.count(maxq)# 计算有几个动作得到了最大的Q值# 让这些动作均分概率
self.pi[s]=[1/ cntq if q == maxq else0for q in qsa_list]print("策略提升完成")return self.pi
defpolicy_iteration(self):# 策略迭代while1:
self.policy_evaluation()
old_pi = copy.deepcopy(self.pi)# 将列表进行深拷贝,方便接下来进行比较
new_pi = self.policy_improvement()if old_pi == new_pi:break
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
读者可能会纳闷为啥代码中qsa的计算方法与公式不同?具体来说,公式中状态转移概率在括号内部,代码中状态转移概率在括号外部并且括号内还乘了
(
1
−
d
o
n
e
)
(1-done)
(1−done)。这个问题跟环境的设定有关。本文环境中,并不是在状态
s
s
s下做出动作决策
a
a
a就立即获得当前轮次的奖励,而是在真正迁移到下一状态之后才获得对应奖励,这就解释了为什么转移概率在括号外面。本文还设定转移到终结状态后奖励为0,因此乘以
(
1
−
d
o
n
e
)
(1-done)
(1−done)。
策略提升部分,每次都是以混合策略的形式提升。得到每个状态
s
s
s动作价值函数最大的几个动作,将概率
1
1
1平均分配到上述动作中。
状态价值函数的贝尔曼最优方程如下所示:
V
∗
(
s
)
=
max
a
∈
A
{
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
∗
(
s
′
)
}
V^*(s)=\max_{a\in A}\{r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)V^*(s')\}
V∗(s)=a∈Amax{r(s,a)+γs′∈S∑P(s′∣s,a)V∗(s′)} 将其写成迭代更新的方式如下:
V
k
+
1
(
s
)
=
max
a
∈
A
{
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
k
(
s
′
)
}
V^{k+1}(s)=\max_{a\in A}\{r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)V^k(s')\}
Vk+1(s)=a∈Amax{r(s,a)+γs′∈S∑P(s′∣s,a)Vk(s′)} 迭代更新到
V
k
+
1
V^{k+1}
Vk+1与
V
k
V^k
Vk相同或差距很小时,此时对应着最优状态价值函数
V
∗
(
s
)
V^*(s)
V∗(s),利用最优状态价值函数便可以复原出最优策略。
π
(
s
)
=
arg
max
a
{
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
∗
(
s
′
)
}
\pi(s)=\arg \max_{a}\{r(s,a)+\gamma \sum_{s'\in S}P(s'|s,a)V^*(s')\}
π(s)=argamax{r(s,a)+γs′∈S∑P(s′∣s,a)V∗(s′)}
悬崖漫步环境下的价值迭代代码如下所示:
classValueIteration:""" 价值迭代算法 """def__init__(self, env, theta, gamma):
self.env = env
self.v =[0]* self.env.ncol * self.env.nrow # 初始化价值为0
self.theta = theta # 价值收敛阈值
self.gamma = gamma
# 价值迭代结束后得到的策略
self.pi =[Nonefor i inrange(self.env.ncol * self.env.nrow)]defvalue_iteration(self):
cnt =0while1:
max_diff =0
new_v =[0]* self.env.ncol * self.env.nrow
for s inrange(self.env.ncol * self.env.nrow):
qsa_list =[]# 开始计算状态s下的所有Q(s,a)价值for a inrange(4):
qsa =0for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p *(r + self.gamma * self.v[next_state]*(1- done))
qsa_list.append(qsa)# 这一行和下一行代码是价值迭代和策略迭代的主要区别
new_v[s]=max(qsa_list)
max_diff =max(max_diff,abs(new_v[s]- self.v[s]))
self.v = new_v
if max_diff < self.theta:break# 满足收敛条件,退出评估迭代
cnt +=1print("价值迭代一共进行%d轮"% cnt)
self.get_policy()defget_policy(self):# 根据价值函数导出一个贪婪策略for s inrange(self.env.nrow * self.env.ncol):
qsa_list =[]for a inrange(4):
qsa =0for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p *(r + self.gamma * self.v[next_state]*(1- done))
qsa_list.append(qsa)
maxq =max(qsa_list)
cntq = qsa_list.count(maxq)# 计算有几个动作得到了最大的Q值# 让这些动作均分概率
self.pi[s]=[1/ cntq if q == maxq else0for q in qsa_list]