Xiaowu/20220424 (#726)
* update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * fix * update * update * update * update * update * update * update * update * update * update * update * update * update * Delete mdp-4.png * uopdate * update * update * update * update * update * update * update * Update 4-搜索最优策略.md
@@ -15,7 +15,7 @@ if __name__=="__main__":
|
||||
if (Q[i,j] == 0):
|
||||
Q[i,j]=-10
|
||||
|
||||
chars = [0x2191, 0x2192, 0x2193, 0x2190]
|
||||
chars = [0x2190, 0x2191, 0x2192, 0x2193]
|
||||
for i in range(Q.shape[0]):
|
||||
if np.sum(Q[i,:]) == -40:
|
||||
print("O", end="")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
this is code book for 《强化学习》第二版 Sutton
|
||||
@@ -9,7 +9,7 @@ P = np.array([
|
||||
|
||||
def Check_Convergence(P):
|
||||
P_curr = P.copy()
|
||||
for i in range(100000):
|
||||
for i in range(1000):
|
||||
P_next=np.dot(P,P_curr)
|
||||
print("迭代次数 =",i+1)
|
||||
print(P_next)
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
公司统计了去年全年 100 辆车的 100 天的租车、还车记录,抽样以后发现这 100 辆车在四个店的流动情况是这样的:
|
||||
|
||||
```
|
||||
1) B A A B A B A D
|
||||
2) C B A B A B A D
|
||||
1) B A A B A B A D ......
|
||||
2) C B A B A B A D ......
|
||||
......
|
||||
100) A B A D C C C B
|
||||
100) A B A D C C C B ......
|
||||
```
|
||||
|
||||
*注:上述数据是用 RentCar_0_Data.py 生成的,并保存在 CarData.txt 文本文件中便于阅读。如果再次运行本代码,因为随机序列不同,将会生成不同的数据序列。*
|
||||
@@ -304,7 +304,7 @@ $$
|
||||
|**C**|0.0|0.9|0.1|0.0||1.0|
|
||||
|**D**|0.0|0.3|0.3|0.4||1.0|
|
||||
||||||||
|
||||
|转入总和|0.9|1.5|0.6|1.0|4.0|
|
||||
|转入总和|0.9|1.5|0.6|1.0||4.0|
|
||||
|
||||
数据解读:
|
||||
|
||||
|
||||
@@ -174,6 +174,12 @@ $$
|
||||
- 马尔可夫随机场
|
||||
给随机场定义一种马尔可夫性质,即随机场中每个位置的属性定义是马尔可夫性的,简单理解就是随机场中每个位置的属性只与邻近的位置有关,与其他位置无关。这种方法被应用于图像分割取得较好效果。
|
||||
|
||||
|
||||
由于具有马尔可夫性质,所以马尔可夫过程是一个无记忆的随机过程,由“状态-概率”二元组组成:$<S,P>$。
|
||||
|
||||
- $S$ 为有限的状态空间集,s_i表示时间步i的状态,其中$S=\{s_1,s_2,...s_n\}$。
|
||||
- $P$ 为状态转移矩阵,$P_{ss'}=\mathbb P[S_{t+1}=s'| S_t=s]$。
|
||||
|
||||
### 4 马尔科夫链(Markov Chain)
|
||||
|
||||
是一种最简单的马尔可夫过程(非严格意义上的),专指离散指数集的马尔可夫过程。经典的马尔可夫链主要是研究当前状态和未来状态之间的转移概率,并可以计算出多次试验之后的每个状态的概率分布,从而将看起来毫无规律的一些随机现象变成了整体有序的状态变化。
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
@@ -122,35 +122,51 @@
|
||||
图 2 奖励的两种定义方式
|
||||
</center>
|
||||
|
||||
- 给状态定义奖励 —— **注重结果**
|
||||
- 给状态定义奖励 —— **面向结果**
|
||||
图 2 左图中:
|
||||
- 从状态 $S_a$ 到达 $S_c$ 后,获得奖励 $R_1$。
|
||||
- 从状态 $S_a$ 到达 $S_d$ 后,获得奖励 $R_2$。
|
||||
- 从状态 $S_b$ 到达 $S_d$ 后,获得奖励 $R_2$。
|
||||
|
||||
可以看到,只要到达 $S_d$,就可以获得 $R_2$,**奖励是给与目标状态的,与源状态无关**。比如,走一个迷宫,别人找到了最佳路径用了 1 分钟完成任务,而你走了弯路,3 分钟才完成任务,但是裁判并不会因为你花费了更多的力气而给你更多的奖励,**注重结果**。
|
||||
可以看到,只要到达 $S_d$,就可以获得 $R_2$,**奖励是给与目标状态的,与源状态无关**。比如,走一个迷宫,别人找到了最佳路径用了 1 分钟完成任务,而你走了弯路,3 分钟才完成任务,但是裁判并不会因为你花费了更多的力气而给你更多的奖励,**面向结果**。
|
||||
|
||||
- 给过程定义奖励 —— **注重过程**
|
||||
此时:
|
||||
- 对于 $S_c$ 来说,奖励函数 $R(S_c)=R_1$。
|
||||
- 对于 $S_d$ 来说,奖励函数 $R(S_d)=R_2$。
|
||||
|
||||
|
||||
- 给过程定义奖励 —— **面向过程**
|
||||
图 2 右图中:
|
||||
- 从状态 $S_a$ 到达 $S_c$ 时,获得奖励 $R_1$。
|
||||
- 从状态 $S_a$ 到达 $S_c$ 时,获得奖励 $R_2$。
|
||||
- 从状态 $S_b$ 到达 $S_c$ 时,获得奖励 $R_3$。
|
||||
- 从状态 $S_a$ 到达 $S_d$ 时,获得奖励 $R_2$。
|
||||
- 从状态 $S_b$ 到达 $S_d$ 时,获得奖励 $R_3$。
|
||||
|
||||
注意,这个**奖励与源状态和目标状态的组合有关**。比如,同样是考上清华大学,一个城市里的学生和一个大山里的学生所经历的历程不一样,受教育的环境也不同,应该获得不同的奖励值,**注重过程**。
|
||||
注意,这个**奖励与源状态和目标状态的组合有关**。比如,同样是考上清华大学,一个城市里的学生和一个大山里的学生所经历的历程不一样,受教育的环境也不同,应该获得不同的奖励值,**面向过程**。
|
||||
|
||||
更正式的定义是图 2 右图中的方法,即**注重过程**,用数学语言表述就是 $S \times S' \to R$,意为奖励 $R$ 由 $S,S'$ 共同决定,不同的过程会有不同的奖励。但是在目前阶段为了研究问题方便,我们会使用图 2 左图中的定义,即 $S' \to R$。这两种方式很容易区分,读者只需要看 $R$ 在图中标注的位置就可以了。
|
||||
此时:
|
||||
- 对于 $S_c$ 来说,奖励函数 $R(S_c)=R_1$。
|
||||
- 对于 $S_d$ 来说,由于奖励来自两个过程,没有上下文的话,无法确切知道其奖励函数值底是哪个,所以把奖励函数定义为一个数学期望:$R(S_d)=\mathbb E[R_{t+1}|S_t=s_d]$。
|
||||
|
||||
有没有一种方式可以统一这两种定义方式呢?在后面学习贝尔曼方程时,再来具体解释。在那之前,我们一直会用**注重结果**的方式,因为它对初学者来说比较容易理解,计算也简便。
|
||||
更通用的写法是:
|
||||
|
||||
$$
|
||||
R(s)=\mathbb E[R_{t+1}|S_t=s] \tag{1}
|
||||
$$
|
||||
|
||||
|
||||
更正式的定义是图 2 右图中的方法,即**面向过程**,用数学语言表述就是 $S \times S' \to R$,意为奖励 $R$ 由 $S,S'$ 共同决定,不同的过程会有不同的奖励。但是在目前阶段为了研究问题方便,我们会使用图 2 左图中的定义,即 $S' \to R$。这两种方式很容易区分,读者只需要看 $R$ 在图中标注的位置就可以了。
|
||||
|
||||
有没有一种方式可以统一这两种定义方式呢?在后面学习贝尔曼方程时,再来具体解释。在那之前,我们一直会用**面向结果**的方式,因为它对初学者来说比较容易理解,计算也简便。
|
||||
|
||||
一个完整的奖励过程如图 3 所示。就是在状态转移图中,给每个状态都定义一个奖励值,当到达这个状态时,强化学习过程就会获得相应的奖励值,使得整个过程向着获得最大收益的方向优化和运行。
|
||||
|
||||
<center>
|
||||
<img src="./img/Reward-2.png">
|
||||
|
||||
图 3 状态与奖励
|
||||
图 3 马尔科夫奖励过程
|
||||
</center>
|
||||
|
||||
很明显,这是用**注重结果**的方式来定义奖励。如果是**注重过程**的话,图 3 的 $S_1$ 状态应该没有奖励值,因为看上去它似乎是起始状态,没有任何**过程**可以定义它的奖励。
|
||||
很明显,这是用**面向结果**的方式来定义奖励。如果是**面向过程**的话,图 3 的 $S_1$ 状态应该没有奖励值,因为看上去它是**起始**状态,没有任何**过程**可以定义它的奖励。
|
||||
|
||||
在图 3 中:
|
||||
- 到达$S_1$状态时,会得到$R_2$的奖励;
|
||||
@@ -211,7 +227,7 @@ $$
|
||||
图 5 安全驾驶问题的马尔可夫奖励过程
|
||||
</center>
|
||||
|
||||
显然,这里使用了图 2 中的第一种方式(**注重结果**)来定义奖励,举例来说,无论状态“发生事故”是通过什么路径到达的(可以通过“拨打手机”到达,也可以通过“路口闯灯”到达),都可以得到 -12 的奖励(实际上是惩罚)。
|
||||
显然,这里使用了图 2 中的第一种方式(**面向结果**)来定义奖励,举例来说,无论状态“发生事故”是通过什么路径到达的(可以通过“拨打手机”到达,也可以通过“路口闯灯”到达),都可以得到 -12 的奖励(实际上是惩罚)。
|
||||
|
||||
奖励函数(值)的设计一般是人工设定的,是通过分析目标问题的实际科学意义或者人文意义来决定的。比如,在图 5 中,通过交通规则的学习,给出制定奖励的过程如下:
|
||||
|
||||
@@ -236,7 +252,7 @@ $$
|
||||
|
||||
读者可能会产生怀疑:为什么表 1 中都是同样计算 $G_{S}$ 的回报值,但是有不同的结果?这是因为采样不同,路径不同,造成的回报值不同,这种情况是正常的。
|
||||
|
||||
到目前可以总结出,马尔可夫奖励过程是一个元组的数据序列:$<S,P,R>$,分别表示状态 $S$、转移概率 $P$、奖励 $R$。
|
||||
与马尔可夫过程的二元组 $<S,P>$ 相比,马尔可夫奖励过程是一个元组的数据序列:$<S,P,R>$,分别表示状态 $S$、转移概率 $P$、奖励 $R$。
|
||||
|
||||
### 6 奖励函数的设计
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ def Sampling(dataModel, start_state, episodes, gamma):
|
||||
G_sum = 0 # 定义最终的返回值, G 的平均数
|
||||
# 循环多幕
|
||||
for episode in tqdm.trange(episodes):
|
||||
# 由于使用了注重结果奖励方式,所以起始状态也有奖励,做为 G 的初始值
|
||||
# 由于使用了面向结果奖励方式,所以起始状态也有奖励,做为 G 的初始值
|
||||
G = dataModel.get_reward(start_state)
|
||||
curr_s = start_state # 把给定的起始状态作为当前状态
|
||||
t = 1 # 折扣因子
|
||||
|
||||
@@ -30,7 +30,7 @@ def Sampling_Checkpoint(dataModel, start_state, episodes, gamma, checkpoint):
|
||||
G_sum = 0 # 定义最终的返回值,G 的平均数
|
||||
# 循环多幕
|
||||
for episode in tqdm.trange(episodes):
|
||||
# 由于使用了注重结果奖励方式,所以起始状态也有奖励,做为 G 的初始值
|
||||
# 由于使用了面向结果奖励方式,所以起始状态也有奖励,做为 G 的初始值
|
||||
G = dataModel.get_reward(start_state)
|
||||
curr_s = start_state # 把给定的起始状态作为当前状态
|
||||
t = 1 # 折扣因子
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
图 1 学习问题的状态转移概率图
|
||||
</center>
|
||||
|
||||
需要再次说明的是,在图 1 中,我们使用了**注重结果**的奖励定义方式,直接给每个状态赋值一个奖励,意味只要达到这个状态,就可以立刻获得标注出的奖励值,而不管是从哪条路径达到的。
|
||||
需要再次说明的是,在图 1 中,我们使用了**面向结果**的奖励定义方式,直接给每个状态赋值一个奖励,意味只要达到这个状态,就可以立刻获得标注出的奖励值,而不管是从哪条路径达到的。
|
||||
|
||||
表 1 中列出了状态转移矩阵,与租车问题中的矩阵形式相同。
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
V(s) &= \mathbb E [G_t | S_t = s]
|
||||
v(s) &= \mathbb E [G_t | S_t = s]
|
||||
\\\\
|
||||
&=\mathbb E [ R_{t+1}+\gamma R_{t+2}+\gamma^2 R_{t+3}+ \gamma^3 R_{t+4}+ \cdots]
|
||||
\end{aligned}
|
||||
@@ -96,7 +96,7 @@ $$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
\mathbb E[骰子]&=\sum_{i=1}^6 p_i V_i
|
||||
\mathbb E[骰子]&=\sum_{i=1}^6 p_i v_i
|
||||
\\\\
|
||||
&= \frac{2}{15} \times 1+\frac{2}{15} \times 2+\frac{2}{15} \times 3+\frac{1}{5} \times 4+\frac{1}{5} \times 5+\frac{1}{5} \times 6
|
||||
\\\\
|
||||
@@ -106,7 +106,7 @@ $$
|
||||
|
||||
观察式 2,在定义状态价值函数时,数学期望对于 $G_t$ 没有定义权重或概率,所以每一幕的 $G_t$ 值都是同等价值的,因此,状态价值函数就是很多幕的 $G_t$ 的算术平均值。
|
||||
|
||||
以表 1 中的数据为例:$V(Start)=[6+7+(-7)+(-16)]/4=-2.5$
|
||||
以表 1 中的数据为例:$v(Start)=[6+7+(-7)+(-16)]/4=-2.5$
|
||||
|
||||
但是,只有 4 幕采样并不能准确计算出真正的期望值,因此,我们需要更多的采样。一般情况下,采样的数量级应该是成千上万的,才会得到一个比较稳定的数学期望值。
|
||||
|
||||
@@ -228,6 +228,8 @@ $V_S \leftarrow G_{sum} / Episodes$
|
||||
|
||||
#### 算法说明
|
||||
|
||||
这个算法有一个很神秘的名字,叫做**蒙特卡洛方法**,专门用于采样,然后计算概率或者数学期望。在后面我们还会更深入地学习其理论知识,读者先记住这个名字就可以。
|
||||
|
||||
以表 2 中的第 1 个采样序列为例,说明算法的执行过程。
|
||||
|
||||
<center>
|
||||
@@ -254,7 +256,7 @@ def Sampling(dataModel, start_state, episodes, gamma):
|
||||
G_sum = 0 # 定义最终的返回值,G 的平均数
|
||||
# 循环多幕
|
||||
for episode in tqdm.trange(episodes):
|
||||
# 由于使用了注重结果奖励方式,所以起始状态也有奖励,做为 G 的初始值
|
||||
# 由于使用了面向结果奖励方式,所以起始状态也有奖励,做为 G 的初始值
|
||||
G = dataModel.get_reward(start_state)
|
||||
curr_s = start_state # 把给定的起始状态作为当前状态
|
||||
t = 1 # 折扣因子
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
在醉汉回家的例子中,奖励函数可以有两种设计方式:
|
||||
|
||||
- 对状态奖励
|
||||
- 对过程奖励
|
||||
- 面向结果的奖励
|
||||
- 面向过程的奖励
|
||||
|
||||
通过对这两种方式的比较,读者可以得到一些基本的概念,为后面的贝尔曼方程的学习做好准备。
|
||||
|
||||
@@ -36,7 +36,9 @@ P = np.array(
|
||||
])
|
||||
```
|
||||
|
||||
### 基于状态的奖励方法
|
||||
### 面向结果的奖励方法
|
||||
|
||||
所谓面向结果,就是直接给状态设定奖励,而不管其来龙去脉,是否是起始或终止状态等。
|
||||
|
||||
#### 第一种情况
|
||||
|
||||
@@ -73,7 +75,7 @@ R1 = [0, 0, 0, 0, 0, 1]
|
||||
- 从 Start 状态开始,做 1000 次采样,得到每次采样的 G 值,然后求平均值;
|
||||
- 从 A 出发(假设醉汉目前在 A 的位置),做 1000 次采样,得到每次采样的 G 值,然后求平均值;
|
||||
......
|
||||
- Home 状态属于终止状态,按照定义,**终止状态的价值为 0**,因为没有状态转移发生,也就没有奖励发生。
|
||||
- Home 状态属于终止状态,**按照定义,终止状态的价值为 0**,因为没有状态转移发生,也就没有奖励发生。
|
||||
|
||||
表 1
|
||||
|
||||
@@ -186,11 +188,13 @@ R2 = [-1, -1, -1, -1, -1, 0]
|
||||
|
||||
表 1 中的状态值都是正数,表 2 中都是负数,这合理吗?作为一个价值体系来说,正值和负值其实没有绝对意义,相互之间的差值才能够真正体现状态价值。所以只要越靠近终点的状态价值越高,就可以了。
|
||||
|
||||
所以,大家在阅读强化学习的资料时,通常会看到如情况二这种奖励方式。这也是笔者开始学习时感到不解的地方,经过试验后,相比大家应该明白了。
|
||||
所以,大家在阅读强化学习的资料时,通常会看到如情况二这种奖励方式。这也是笔者开始学习时感到不解的地方,经过试验后,想必大家应该明白了。
|
||||
|
||||
### 基于过程的奖励方法
|
||||
另外,还有一个方法可以避免终止状态不为 0 的问题,就是像安全驾驶问题中那样建模:虽然*安全到家*和*发生事故*已经可以看作是终止状态了,但是可以人为地定义一个独立的*终止*状态,令*安全到家*和*发生事故*都以 100% 的概率转移到该终止状态。
|
||||
|
||||
下面我们研究一下过程奖励方法对状态函数值的影响。
|
||||
### 面向过程的奖励方法
|
||||
|
||||
下面我们研究面向过程奖励方法对状态函数值的影响。
|
||||
|
||||
#### 第一种情况
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
### 1 提出问题
|
||||
|
||||
在解决安全驾驶问题的过程中,我们学习了马尔可夫奖励过程、状态价值函数、蒙特卡洛采样法等强化学习的重要概念。蒙特卡洛法虽然是一种科学的方法,但是需要大量的采样才能得到**比较理想的结果**,并不能说是**准确的结果**。
|
||||
在解决安全驾驶问题的过程中,我们学习了马尔可夫奖励过程、状态价值函数等强化学习的重要概念。其中,计算价值函数需要大量的采样然后求平均值,才能得到**比较理想的结果**,并不能说是**准确的结果**,因为我们无法衡量什么叫做准确。
|
||||
|
||||
蒙特卡洛法是针对**无模型**强化学习问题的,对于**有模型**的问题,我们有什么更好的方法可以解决吗?
|
||||
那么有没有更好的方法呢?
|
||||
|
||||
在本节中,我们以代码的生命周期问题为例,通过描述开发过程(编码、测试、审查、合并等)的状态/过程,来寻找用于分析解决有模型问题的**更好的办法**,而不是一味地使用蒙特卡洛方法依靠大数定理来计算状态价值函数。
|
||||
在本节中,我们以代码的生命周期问题为例,通过描述开发过程(编码、测试、审查、合并等)的状态/过程,来寻找用于分析解决有模型问题的**更好的办法**,而不是一味地使用采样方法依靠大数定理来计算状态价值函数。
|
||||
|
||||
|
||||
### 2 建立模型
|
||||
|
||||
|
||||
图 1 是一个有关代码生命周期的马尔可夫链,也可以叫做状态转移图,并且给每个状态都赋予了一个即时奖励值。在这里使用了**注重结果**的奖励方式,即只给状态定义奖励,在图 1 中用红色文字表示。
|
||||
图 1 是一个有关代码生命周期的马尔可夫链,也可以叫做状态转移图,并且给每个状态都赋予了一个即时奖励值。在这里使用了**面向结果**的奖励方式,即只给状态定义奖励,在图 1 中用红色文字表示。
|
||||
|
||||
奖励值的确定方案有很多,在此我们采用了面向代码质量的指导思想给各个状态定义不同的值,对代码质量有损害的为负值,反之为正值。
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
|
||||
|
||||
|
||||
状态的说明已经奖励函数的定义:
|
||||
状态的说明以及奖励函数的定义:
|
||||
|
||||
- C:编码 Coding
|
||||
|
||||
开发人员写代码的状态,全神贯注,废寝忘食。在此状态下无法确定代码质量,所以 $R=0$。
|
||||
开发人员写代码的状态,全神贯注,废寝忘食。在此状态下无法确定代码质量,所以 $R=0$(这个听上去比较令人心寒,但却是事实)。
|
||||
|
||||
- 有 0.6 的概率产生 bug,需要修复。
|
||||
- 另外 0.4 的概率进入审查状态。
|
||||
@@ -91,5 +91,5 @@
|
||||
- 如果在缺陷状态超过 1 天,则有 0.5 的概率回到正常编码状态;
|
||||
- 如果没超过 1 天,则有 0.7 的概率回到自身。
|
||||
|
||||
需要再次说明的是,在图 1 中,我们使用了**注重结果**的奖励定义方式,直接给每个状态赋值一个奖励,意味只要达到这个状态,就可以立刻获得标注出的奖励值,而不管是从哪条路径达到的。
|
||||
需要再次说明的是,在图 1 中,我们使用了**面向结果**的奖励定义方式,直接给每个状态赋值一个奖励,意味只要达到这个状态,就可以立刻获得标注出的奖励值,而不管是从哪条路径达到的。
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
|
||||
## 贝尔曼方程 Bellman Equation
|
||||
|
||||
这个问题,如果像安全驾驶问题中一样用蒙特卡洛采样方法,仍然可以得到答案。但是在本章,我们会学习一种更科学简便的方法,被称作贝尔曼方程。
|
||||
|
||||
### 对状态价值函数的进一步分解
|
||||
|
||||
前面已经学习了状态价值函数的定义,其公式为:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
V(s) &= \mathbb E [G_t | S_t = s]
|
||||
\\
|
||||
&=\mathbb E [ R_{t+1}+\gamma R_{t+2}+\gamma^2 R_{t+3}+ \gamma^3 R_{t+4}+ \cdots]
|
||||
\end{aligned}
|
||||
v(s) = \mathbb E [G_t | S_t = s]
|
||||
\tag{1}
|
||||
$$
|
||||
|
||||
@@ -18,9 +16,9 @@ $$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
G_t &= R_{t+1}+\gamma R_{t+2}+ \gamma^2 R_{t+3} + \gamma^3 R_{t+4} + \cdots
|
||||
G_t &= R_{t+1}+\gamma R_{t+2}+ \gamma^2 R_{t+3} + \gamma^3 R_{t+4} + \cdots &(2.1)
|
||||
\\
|
||||
G_{t+1} &= R_{t+2}+ \gamma R_{t+3}+ \gamma^2 R_{t+4} + \cdots
|
||||
G_{t+1} &= R_{t+2}+ \gamma R_{t+3}+ \gamma^2 R_{t+4} + \cdots &(2.2)
|
||||
\end{aligned}
|
||||
\tag{2}
|
||||
$$
|
||||
@@ -30,20 +28,22 @@ $$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
V(s)&=\mathbb E [G_t|S_t=s]
|
||||
v(s)&=\mathbb E [G_t|S_t=s]
|
||||
\\
|
||||
&=\mathbb E[R_{t+1}+\gamma R_{t+2}+\gamma^2 R_{t+3}+ \gamma^3 R_{t+4}+\cdots|S_t=s]
|
||||
(带入式2.1)&=\mathbb E[R_{t+1}+\gamma R_{t+2}+\gamma^2 R_{t+3}+ \gamma^3 R_{t+4}+\cdots|S_t=s]
|
||||
\\
|
||||
&=\mathbb E[R_{t+1}+\gamma (R_{t+2}+\gamma R_{t+3}+ \gamma^2 R_{t+4}+\cdots)|S_t=s]
|
||||
\\
|
||||
&=\mathbb E[(R_{t+1}+\gamma G_{t+1})|S_t=s]
|
||||
(带入式2.2)&=\mathbb E[(R_{t+1}+\gamma G_{t+1})|S_t=s]
|
||||
\\
|
||||
&= \underbrace{ \mathbb E[R_{t+1}|S_t=s]}_A + \gamma \underbrace{\mathbb E[G_{t+1}|S_t=s]}_B
|
||||
\end{aligned}
|
||||
\tag{3}
|
||||
$$
|
||||
|
||||
到了这一步,把 $V(s)$ 分成了 $A,B$ 两部分,下面分析一下这两部分如何分别得到。
|
||||
到了这一步,把 $V(s)$ 分成了 $A,B$ 两部分,**$A$ 可以看作是即时奖励,$B$ 可以看作是带折扣的未来奖励**。
|
||||
|
||||
下面分析一下这两部分如何分别得到。
|
||||
|
||||
|
||||
### $A$ 部分:关于 $R_{t+1}$ 的数学期望
|
||||
@@ -61,7 +61,7 @@ $$
|
||||
小写的 $s,s'$,表示具体的**状态实例**,具体到图 2 中:
|
||||
|
||||
$$
|
||||
s = [s_a], \quad s' = [s_b,s_c]
|
||||
s = [s_a], \quad s' = [s_b,s_c] \tag{4}
|
||||
$$
|
||||
|
||||
|
||||
@@ -69,23 +69,31 @@ $$
|
||||
|
||||
$P_{ss'}$ 是状态转移概率的集合,即转移矩阵。在图 2 中从 $s_a$ 出发有两个转移方向,分别是 $s_b,s_c$,对应的概率是 $p_1,p_2$,并且 $p_1+p_2=1$。
|
||||
|
||||
$$P_{ss'}=[p_1,p_2]$$
|
||||
$$
|
||||
P_{ss'}=[p_1,p_2] \tag{5}
|
||||
$$
|
||||
|
||||
实际上 $p_1$ 可以写作 $p_{s_a s_b}$,含义是从 $s_a$ 到 $s_b$ 的转移概率,因为有式 4,所以 $p_{s_a s_b}$ 又可以写作 $p_{ss'}$,这样一来 $p_1,p_2$ 就可以有统一的符号了,这在后面的数学推导中很重要。
|
||||
|
||||
#### 奖励函数定义
|
||||
|
||||
在图 2 中,$R_{t+1}$ 指的是到达状态 $s_a$ 后可以得到的奖励 $R(s_a)$,可以看作是 $s_a$ 的函数。
|
||||
|
||||
在使用**注重过程**的奖励函数定义方式时,从$s_a$ 转移到 $s_b$ **或** $s_c$,分别可以得到 $r_1,r_2$ 的奖励,但不能同时得到。
|
||||
在使用**面向过程**的奖励函数定义方式时,从$s_a$ 转移到 $s_b$ **或** $s_c$,分别可以得到 $r_1,r_2$ 的奖励,但不能同时得到。
|
||||
|
||||
$$
|
||||
R_{ss'}=[r_1,r_2]
|
||||
R_{ss'}=[r_1,r_2] \tag{6}
|
||||
$$
|
||||
|
||||
和 $p_1,p_2$ 一样,$r_1,r_2$ 也可以统一写成 $r_{ss'}$。
|
||||
|
||||
图 2 中从 $s_a$ 出发并不能确定具体转移到哪个状态,所以也无法确定得到的奖励 $R_{t+1}$ 是 $r_1$ 还是 $r_2$。但是幸好有转移概率 $P_{ss'}$ 存在,给了我们一个机会,可以通过数学期望(带权重的平均值)来定义 $R(s_a)$:
|
||||
|
||||
$$R(s_a)=\mathbb E[R_{t+1}|S_t=s_a] = \underbrace {p_1 \cdot r_1}_{S_{t+1}=s_b} + \underbrace{p_2 \cdot r_2}_{S_{t+1}=s_c} = \sum P_{ss'} R_{ss'} \tag{4}$$
|
||||
$$R(s_a)=\mathbb E[R_{t+1}|S_t=s_a] = \underbrace {p_1 \cdot r_1}_{S_{t+1}=s_b} + \underbrace{p_2 \cdot r_2}_{S_{t+1}=s_c}=\sum_{s'} p_{ss'}r_{ss'} = P_{ss'} R_{ss'} \tag{7}$$
|
||||
|
||||
*注:式 4 实际上统一了**注重过程**的奖励方式和**注重结果**的奖励方式,把过程的期望看作是结果。这一点很重要,希望读者牢记,避免在以后的学习中遇到概念上的疑问。*
|
||||
式 7 实际上统一了**面向过程**的奖励方式($r_1,r_2$)和**面向结果**的奖励方式 $R(s_a)$,把过程的期望看作是结果。因为在有的资料中,会直接给出 $R(s_a)$ 的值,但是又不解释它是怎么来的。这一点很重要,希望读者牢记,避免在以后的学习中遇到概念上的疑问。
|
||||
|
||||
式 7 中求和符号下面的 $s'$,表示要遍历 $s'$ 求和,在本例中就是要遍历 [$s_b,s_c$]。
|
||||
|
||||
#### 结论
|
||||
|
||||
@@ -95,13 +103,13 @@ $$
|
||||
\begin{aligned}
|
||||
R(s) & = \mathbb E[R_{t+1}|S_t=s]
|
||||
\\
|
||||
({\footnotesize 根据图2实例化}\ s \to s_a) &= \mathbb E[R_{t+1}|S_t=s_a]
|
||||
(根据图2实例化\ s \to s_a) &= \mathbb E[R_{t+1}|S_t=s_a]
|
||||
\\
|
||||
({\footnotesize 式4})&= p_1 \cdot r_1+p_2 \cdot r_2
|
||||
(带入式7\to)&= p_1 \cdot r_1+p_2 \cdot r_2
|
||||
\\
|
||||
({\footnotesize 抽象化\ p,r}) &=\sum_{s'} P_{ss'} \cdot R_{ss'}
|
||||
(抽象\to) &= \sum_{s'}p_{ss'} r_{ss'}=P_{ss'} R_{ss'}
|
||||
\end{aligned}
|
||||
\tag{5}
|
||||
\tag{8}
|
||||
$$
|
||||
|
||||
### $B$ 部分:关于 $G_{t+1}$ 的数学期望
|
||||
@@ -125,7 +133,7 @@ $$
|
||||
但是,由于 $P_{ss'}$ 的存在,我们仍然可以用数学期望的方式
|
||||
|
||||
$$
|
||||
G_{t+1}=\mathbb E\big[[G_b,G_c]|S_{t+1}=[s_b,s_c]\big]= \underbrace{p_1 \cdot G_b}_{S_{t+1}=s_b} + \underbrace {p_2 \cdot G_c}_{S_{t+1}=s_c} \tag{6}
|
||||
G_{t+1}=\mathbb E\big[[G_b,G_c]|S_{t+1}=[s_b,s_c]\big]= \underbrace{p_1 \cdot G_b}_{S_{t+1}=s_b} + \underbrace {p_2 \cdot G_c}_{S_{t+1}=s_c} \tag{9}
|
||||
$$
|
||||
|
||||
|
||||
@@ -134,57 +142,55 @@ $$
|
||||
首先要注意的一个问题是,$B$ 部分不等于 $V(s')$,因为按式 1 的定义:
|
||||
|
||||
$$
|
||||
V(s) = \mathbb E [G_t | S_t = s]
|
||||
v(s) = \mathbb E [G_t | S_t = s]
|
||||
$$
|
||||
|
||||
则有:
|
||||
|
||||
$$
|
||||
V(s') = \mathbb E [G_{t+1} | S_t = s']\ne \mathbb E[G_{t+1}|S_t=s]
|
||||
v(s') = \mathbb E [G_{t+1} | S_t = s']\ne \mathbb E[G_{t+1}|S_t=s]
|
||||
$$
|
||||
|
||||
具体到图 3 中,状态 $s_a, s_b, s_c$ 的价值函数的实例化表示:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
V(s_a)&=\mathbb E [G_a|S_t=s_a]
|
||||
v(s_a)&=\mathbb E [G_a|S_t=s_a] & \in v(s)
|
||||
\\
|
||||
V(s_b)&=\mathbb E [G_b|S_{t+1}=s_b]
|
||||
v(s_b)&=\mathbb E [G_b|S_{t+1}=s_b] & \in v(s')
|
||||
\\
|
||||
V(s_c)&=\mathbb E [G_c|S_{t+1}=s_c]
|
||||
v(s_c)&=\mathbb E [G_c|S_{t+1}=s_c] & \in v(s')
|
||||
\end{aligned}
|
||||
\tag{7}
|
||||
\tag{10}
|
||||
$$
|
||||
|
||||
不同时刻状态的价值函数的取值范围是:
|
||||
|
||||
$$
|
||||
V(s)=[V(s_a)], \quad V(s')=[V(s_b),V(s_c)]
|
||||
V(s)=[v(s_a)], \quad V(s')=[v(s_b),v(s_c)] \tag{11}
|
||||
$$
|
||||
|
||||
#### 结论
|
||||
|
||||
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
B&=\mathbb E\big[G_{t+1}|S_t=s \big ]
|
||||
\\
|
||||
({\footnotesize 实例化})&=\mathbb E\big[G_{t+1}|S_t=s_a \big ]
|
||||
(实例化\to)&=\mathbb E\big[G_{t+1}|S_t=s_a \big ]
|
||||
\\
|
||||
({\footnotesize 带入式\ 6})&= \mathbb E\big[(p_1 \cdot G_{b}|S_{t+1}=s_b) + (p_2\cdot G_{c}|S_{t+1}=s_c)\big|S_t=s_a]
|
||||
(带入式9\to)&= \mathbb E\big[(p_1 \cdot G_{b}|S_{t+1}=s_b) + (p_2\cdot G_{c}|S_{t+1}=s_c)\big|S_t=s_a]
|
||||
\\
|
||||
({\footnotesize 期望加法变换})&=\mathbb E\big[p_1\cdot G_{b}|S_{t+1}=s_b,S_t=s_a]+\mathbb E[p_2\cdot G_{c}|S_{t+1}=s_c,S_t=s_a\big]
|
||||
(期望求和\to)&=\mathbb E\big[p_1\cdot G_{b}|S_{t+1}=s_b,S_t=s_a]+\mathbb E[p_2\cdot G_{c}|S_{t+1}=s_c,S_t=s_a\big]
|
||||
\\
|
||||
({\footnotesize 马尔可夫性质})&=\mathbb E\big[p_1\cdot G_{b}|S_{t+1}=s_b]+\mathbb E[p_2\cdot G_{c}|S_{t+1}=s_c\big]
|
||||
(马尔可夫性质\to)&=\mathbb E\big[p_1\cdot G_{b}|S_{t+1}=s_b]+\mathbb E[p_2\cdot G_{c}|S_{t+1}=s_c\big]
|
||||
\\
|
||||
({\footnotesize 提出常数}\ p_1,p_2)&=p_1 \cdot \mathbb E[G_{b}|S_{t+1}=s_b]+ p_2 \cdot \mathbb E[G_{c}|S_{t+1}=s_c]
|
||||
(提出常数\ p_1,p_2\to)&=p_1 \cdot \mathbb E[G_{b}|S_{t+1}=s_b]+ p_2 \cdot \mathbb E[G_{c}|S_{t+1}=s_c]
|
||||
\\
|
||||
({\footnotesize 带入式\ 7})&= p_1 \cdot V(s_b) + p_2 \cdot V(s_c)
|
||||
(带入式10\to)&= p_1 \cdot v(s_b) + p_2 \cdot v(s_c)
|
||||
\\
|
||||
({\footnotesize 抽象化\ p,V})&= \sum_{s'} P_{ss'}V(s')
|
||||
(抽象\to)&=\sum_{s'} p_{ss'}v(s')= P_{ss'}V(s')
|
||||
\end{aligned}
|
||||
\tag{8}
|
||||
\tag{12}
|
||||
$$
|
||||
|
||||
|
||||
@@ -196,29 +202,31 @@ $$
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
V(s) &= \mathbb E[R_{t+1}|S_t=s] + \gamma \mathbb E[G_{t+1}|S_t=s]
|
||||
v(s) &= \mathbb E [G_t | S_t = s]
|
||||
\\
|
||||
{\footnotesize (带入式\ 4,8)}&=\sum_{s'} P_{ss'} R_{ss}+ \gamma \sum_{s'} P_{ss'}V(s') & (9.1)
|
||||
&=\mathbb E [R_{t+1}|S_t=s] + \gamma \mathbb E[G_{t+1}|S_t=s]
|
||||
\\
|
||||
&= R(s)+ \gamma \sum_{s'} P_{ss'}V(s') &(9.2)
|
||||
&=\sum_{s'} p_{ss'} r_{ss'}+ \gamma \sum_{s'} p_{ss'}v(s') =\sum_{s'} p_{ss'} [r_{ss'}+\gamma v(s')] &(9.1)
|
||||
\\
|
||||
&=\sum_{s'} P_{ss'} [R_{ss'}+\gamma V(s')] &(9.3)
|
||||
&= P_{ss'} R_{ss'} + \gamma P_{ss'} V(s') &(9.2)
|
||||
\\
|
||||
&= R(s)+ \gamma P_{ss'}V(s') &(9.3)
|
||||
\end{aligned}
|
||||
\tag{9}
|
||||
$$
|
||||
|
||||
- 在**针对状态定义奖励函数**的问题中,使用式 9.2 比较方便,因为 $R(s)$ 是直接定义在状态 $s$ 上。这是 David Silver 课件中的写法。
|
||||
**也就是说,一个状态 $s$ 的价值函数 $v(s)$ 由它的下游状态(可能不止一个)$s'$ 的价值函数 $v(s')$ 和转移概率 $P_{ss'}$ 以及转移过程中的奖励 $R_{ss'}$ 构成。**
|
||||
|
||||
- 在**针对过程定义奖励函数**的问题中,使用式 9.3 比较方便,因为 $R(s,s')$ 是定义在从 $s\to s'$ 的转移过程上。这是 Richard S. Sutton and Andrew G. Barto 书中的写法,但不完全相同,我们后面再讲。
|
||||
- 在**面向结果定义奖励函数**的问题中,使用式 9.3 比较方便,因为 $R(s)$ 是直接定义在状态 $s$ 上。这是 David Silver 课件中的写法。
|
||||
|
||||
- 在**面向过程定义奖励函数**的问题中,使用式 9.1 或 9.2 比较方便,因为 $R_{ss'}$ 是定义在从 $s\to s'$ 的转移过程上。这是 Richard S. Sutton and Andrew G. Barto 书中的写法,但不完全相同,我们后面再讲。
|
||||
|
||||
如果针对图 1,状态 $s_a$ 的价值函数实例计算公式为:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
V(s_a)&=(p_1 \cdot r_1 + p_2 \cdot r_2) + \gamma[p_1 \cdot V(s_b) + p_2 \cdot V(s_c)]
|
||||
v(s_a)&=(p_1 \cdot r_1 + p_2 \cdot r_2) + \gamma[p_1 \cdot v(s_b) + p_2 \cdot v(s_c)]
|
||||
\\
|
||||
&=R(s)+\gamma[p_1 \cdot V(s_b) + p_2 \cdot V(s_c)]
|
||||
&=R(s)+\gamma[p_1 \cdot v(s_b) + p_2 \cdot v(s_c)]
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
也就是说,一个状态 $s$ 的价值函数 $V(s)$ 由它的下游状态(可能不止一个)$s'$ 的价值函数 $V(s')$ 和转移概率 $P_{ss'}$ 以及转移过程中的奖励 $R_{ss'}$ 构成。
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
图 2 中,每个状态下方都用括号表示了该状态的序号,比如 编码 (1) 表示状态 1,价值函数值用 $v_1$ 表示。
|
||||
|
||||
以状态 复审状态 (3) 为例,根据式 9.3,可以得到其价值函数为:
|
||||
以状态“复审状态(3)”为例,根据式 9.3,可以得到其价值函数为:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
v_3&=R(s_3)+\gamma[P_{s_3,s_4} \cdot V(s_4) + P_{s_3,s_5} \cdot V(s_5)]
|
||||
v_3&=R(s_3)+\gamma[p_{s_3,s_4} \cdot v(s_4) + p_{s_3,s_5} \cdot v(s_5)]
|
||||
\\
|
||||
&=3+ (0.2 v_4 + 0.8 v_5),
|
||||
\end{aligned}
|
||||
@@ -74,13 +74,11 @@ $$
|
||||
5v_3-11=0.2(0.9v_3-14)+0.5(0.9v_3+1)+0.3v_3+2
|
||||
$$
|
||||
|
||||
得到:$v_3 \approx 2.629$
|
||||
|
||||
所以,最终的结果为:
|
||||
得到:$v_3 \approx 2.629$,然后再逐一解决其它变量,最终的结果为:
|
||||
|
||||
$$
|
||||
\begin{cases}
|
||||
v_0 \approx -22.634
|
||||
v_0 \approx -21.634
|
||||
\\
|
||||
v_1 \approx -11.634
|
||||
\\
|
||||
@@ -105,14 +103,19 @@ $$
|
||||
图 3 价值函数计算结果
|
||||
</center>
|
||||
|
||||
得到结果后,可以验证一下图 3 中红色线条部分:
|
||||
|
||||
$$
|
||||
2.629 = 3 + 1 \times [0.2 \times 2.145 + 0.8 \times (-1) ]
|
||||
$$
|
||||
|
||||
完全符合价值函数的定义。
|
||||
|
||||
### 给过程定义奖励
|
||||
|
||||
在图 x 中,是通过给状态定义奖励来得到 $R(s)$ 值的,直接使用贝尔曼方程的第 2 种形式即可。
|
||||
|
||||
前面我们学习过,有的问题是注重状态,有的问题是注重过程。如果遇到的问题是给过程定义奖励,应该如何解决?很简单,使用贝尔曼方程的第 1 种形式即可。
|
||||
|
||||
如图 3 所示
|
||||
前面我们学习过,有的问题是注重状态,有的问题是面向过程。如果遇到的问题是给过程定义奖励,应该如何解决?很简单,使用贝尔曼方程的第 1 种形式即可。
|
||||
|
||||
<center>
|
||||
<img src="./img/Code-4.png">
|
||||
@@ -120,7 +123,13 @@ $$
|
||||
图 2
|
||||
</center>
|
||||
|
||||
举例,编码状态 C 的下游状态是 缺陷 B 和 测试 T,转移概率 $p_{ss'}$ 分别是 0.6, 0.4,
|
||||
如图 3,标出了 6 个过程的奖励,其它过程未标出。其中,编码状态 C:
|
||||
|
||||
- 下游状态是 缺陷 B 和 测试 T;
|
||||
- 转移概率 $p_{ss'}$ 分别是:$p_{C,T}=0.4, p_{C,B}=0.6$;
|
||||
- 奖励值 $r_{ss'}$ 分别是:$r_{C,T}=+0.6, p_{C,B}=-0.4$。
|
||||
|
||||
则编码状态 C 的状态奖励值为:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
@@ -132,10 +141,10 @@ R(C) &= p_{C,T} r_{C,T} + p_{C,B} r_{C,B}
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
|
||||
同理,重构状态 F 的状态奖励值为:
|
||||
$$
|
||||
\begin{aligned}
|
||||
R(F) &= p_{F,C} r_{F,C} + p_{F,T} r_{F,T} + p_{F,R} r_{F,R}
|
||||
R(F) &= p_{F,C} \cdot r_{F,C} + p_{F,T} \cdot r_{F,T} + p_{F,R} \cdot r_{F,R}
|
||||
\\
|
||||
&=0.2 \times 0.35 + 0.5 \times 2 + 0.3 \times 1
|
||||
\\
|
||||
@@ -143,4 +152,9 @@ R(F) &= p_{F,C} r_{F,C} + p_{F,T} r_{F,T} + p_{F,R} r_{F,R}
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
然后再列方程组即可的解。
|
||||
然后再列方程组即可得解。
|
||||
|
||||
### 思考与练习
|
||||
|
||||
1. 读者可以自行验证图 3 中的“缺陷”状态的价值函数,以获得深刻理解。
|
||||
2. 读者可以用贝尔曼方程验证醉汉回家简化版中的状态奖励和价值函数的计算结果。
|
||||
|
||||
@@ -170,13 +170,17 @@ V_s - \gamma P_{ss'}V_s &= R_s
|
||||
\\
|
||||
(I-\gamma P_{ss'})V_s&=R_s, &(I \ {\footnotesize 是对角矩阵})
|
||||
\\
|
||||
V_s&=(I-\gamma P_{ss'})^{-1}R_s
|
||||
V_s&=(I-\gamma P_{ss'})^{-1}R_s &(矩阵没有除法,但可以求逆)
|
||||
\end{aligned}
|
||||
\tag{17}
|
||||
$$
|
||||
|
||||
式 16 中,等式右侧的值都是已知的,所以可以解出 $V_s$ 的数学解析解。
|
||||
|
||||
【代码位置:1_CodeLifeCycle_DataModel_P.py】
|
||||
|
||||
根据式 17 中所需元素,需要定义状态转移矩阵和奖励函数向量。
|
||||
|
||||
定义状态转移矩阵:
|
||||
|
||||
```python
|
||||
@@ -201,7 +205,7 @@ P = np.array(
|
||||
Rewards = [-3, 0, +1, +3, +2, -1, 0]
|
||||
```
|
||||
|
||||
代码
|
||||
式 17 的具体实现:
|
||||
|
||||
```python
|
||||
def SolveMatrix(dataModel, gamma):
|
||||
@@ -209,16 +213,14 @@ def SolveMatrix(dataModel, gamma):
|
||||
I = np.eye(dataModel.N) * (1+1e-7)
|
||||
# I = np.eye(dataModel.N) # 非奇异矩阵时使用此行代码以提高计算精度
|
||||
factor = I - gamma * dataModel.P
|
||||
inv_factor = np.linalg.inv(factor)
|
||||
inv_factor = np.linalg.inv(factor) # 求矩阵的逆
|
||||
vs = np.dot(inv_factor, dataModel.R)
|
||||
return vs
|
||||
```
|
||||
在定义状态转移矩阵时,右下角的值,即从 $S_{End} \to S_{End}$ 的转移概率,即可以写成 0.0,也可以写成 1.0,从强化学习的概念角度出发,都没有错。
|
||||
在定义状态转移矩阵时,右下角的值,即从 $S_{End} \to S_{End}$ 的转移概率,即可以写成 0.0,也可以写成 1.0,从强化学习的概念角度出发,都没有错。但是却与代码处理逻辑有关。
|
||||
|
||||
与代码处理逻辑有关。
|
||||
|
||||
写成 0.0 时,np.random.choice(p=) 函数由于概率之和不为 1,所以函数调用出错。
|
||||
写成 1.0 时,由于矩阵的行列式为 0,是个奇异矩阵,不可求逆。此时可以在对角矩阵上增加一个微小的值来解决奇异矩阵不可求逆的问题,但是最终结果会有 1e-7 的误差,可以接受。
|
||||
- 写成 0.0 时,np.random.choice(p=) 函数由于概率之和不为 1,所以函数调用出错。
|
||||
- 写成 1.0 时,由于矩阵的行列式为 0,是个奇异矩阵,不可求逆。此时可以在对角矩阵上增加一个微小的值来解决奇异矩阵不可求逆的问题,但是最终结果会有 1e-7 的误差,可以接受。
|
||||
|
||||
```
|
||||
[-21.63390663 -11.63390663 3.36609337 2.62899263 2.14496314
|
||||
|
||||
@@ -23,7 +23,7 @@ $$
|
||||
|
||||
写成代码也很简单:
|
||||
|
||||
【代码位置:0_Simple_Iteration.py】
|
||||
【代码位置:2_Simple_Iteration.py】
|
||||
|
||||
```Python
|
||||
import math
|
||||
@@ -157,7 +157,7 @@ $$
|
||||
依此类推,读者可以比较 $V_{[0]},V_{[1]},V_{[2]},V_{[3]}$ 的数值,是不是一步步地向着上一小节中用矩阵法得到的结果迈进。
|
||||
|
||||
做为一名伟大的程序员,我们当然要用代码来解决上述的繁复手工计算过程:
|
||||
【代码位置:1_Linear_Equations_Iteration.py】
|
||||
【代码位置:3_Linear_Equations_Iteration.py】
|
||||
```Python
|
||||
# 线性方程组原始迭代法
|
||||
def linear_equations_iteration(dataModel, gamma):
|
||||
@@ -211,7 +211,7 @@ End: 0.0
|
||||
|
||||
对于式 4 来说:$A = \gamma P_{ss'}, x=V(s), B=R(s)$。代码实现如下:
|
||||
|
||||
【代码位置:2_Matrix_Iteration.py】
|
||||
【代码位置:4_Matrix_Iteration.py】
|
||||
|
||||
```Python
|
||||
# 矩阵迭代法
|
||||
@@ -290,7 +290,7 @@ $$
|
||||
|
||||
则式 7 表示的误差 $\varepsilon_{[k]}$ 也趋近于 0,即,$x_{[k]}$ 可以收敛到 $x_{[*]}$。所以,我们来检查一下 $A^k$ 是否趋近于 0 就可以了。
|
||||
|
||||
【代码位置:2_Matrix_Iteration.py】
|
||||
【代码位置:4_Matrix_Iteration.py】
|
||||
|
||||
```Python
|
||||
def check_convergence(dataModel):
|
||||
@@ -330,7 +330,7 @@ $$
|
||||
会如何呢?
|
||||
|
||||
一个不怎么伟大的程序员也可以立刻写出如下代码:
|
||||
【代码位置:3_Linear_Equations_Iteration_SingleArray.py】
|
||||
【代码位置:5_Linear_Equations_Iteration_SingleArray.py】
|
||||
|
||||
```Python
|
||||
# 单数组原始迭代法
|
||||
@@ -382,7 +382,7 @@ End: 0.0
|
||||
但是,有时候由于状态成千上万,没有可能写出状态转移矩阵来,对于人类来说,手工维护一个 $20 \times 20$ 的矩阵,已经是极限了,需要非常小心才能不出错。所以,最佳设计是只需要得到在某个状态下转移到可能达到的下游状态的列表,而不是转移到所有状态(包括不能达到的状态即概率为 0)的矩阵。
|
||||
|
||||
举例来说,在本问题中,状态转移矩阵是这样定义的:
|
||||
【代码位置:CodeLifeCycle_DataModel_P.py】
|
||||
|
||||
```Python
|
||||
# 状态转移概率
|
||||
P = np.array(
|
||||
@@ -402,6 +402,9 @@ P = np.array(
|
||||
[0.6, 0.0, 0.4, 0.0, 0.0, 0.0, 0.0], # Coding
|
||||
```
|
||||
对于这种情况,可以定义另外一种数据结构,来解决稀疏问题:
|
||||
|
||||
【代码位置:6_Bellman_Equation_Iteration_SingleArray.py】
|
||||
|
||||
```Python
|
||||
# 用字典代替状态转移矩阵
|
||||
D = {
|
||||
@@ -417,7 +420,6 @@ D = {
|
||||
用第一行数据举例,它表示:在 Bug 状态下,可以以 0.7 的概率转移到 Bug,以 0.3 的概率转移到 Coding。
|
||||
|
||||
相应地,需要改动数据模型代码:
|
||||
【代码位置:CodeLifeCycle_DataModel_D.py】
|
||||
|
||||
```Python
|
||||
class DataModel(object):
|
||||
@@ -435,7 +437,7 @@ class DataModel(object):
|
||||
|
||||
因此,必须改进原始迭代法中的代码,让它可以适应通用场景。
|
||||
|
||||
#### 单数组就地更新算法
|
||||
单数组就地更新算法
|
||||
|
||||
---
|
||||
|
||||
@@ -452,7 +454,7 @@ class DataModel(object):
|
||||
|
||||
代码如下:
|
||||
|
||||
【代码位置:4_Bellman_Equation_Iteration_SingleArray.py】
|
||||
|
||||
|
||||
```Python
|
||||
# 贝尔曼方程单数组就地更新
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 31 KiB |
@@ -1,5 +1,5 @@
|
||||
import numpy as np
|
||||
import CodeLifeCycle_DataModel_P as dm
|
||||
import CodeLifeCycle_0_DataModel_P as dm
|
||||
|
||||
# 线性方程组原始迭代法
|
||||
def linear_equations_iteration(dataModel, gamma):
|
||||
@@ -1,40 +0,0 @@
|
||||
import numpy as np
|
||||
import CodeLifeCycle_DataModel_D as dm
|
||||
|
||||
|
||||
# 贝尔曼方程单数组就地更新
|
||||
def Bellman_iteration_single_array(dataModel, gamma):
|
||||
print("---单数组就地更新法---")
|
||||
V = np.zeros(dataModel.N)
|
||||
count = 0
|
||||
while (count < 1000): # 1000 是随意指定的一个比较大的数,避免不收敛而导致while无限
|
||||
count += 1
|
||||
V_old = V.copy()
|
||||
# 遍历每一个 state 作为 curr_state
|
||||
for curr_state in dataModel.S:
|
||||
# 得到转移概率
|
||||
list_state_prob = dataModel.get_next(curr_state)
|
||||
# 计算 \sum(P·V)
|
||||
v_sum = 0
|
||||
for next_state, next_prob in list_state_prob:
|
||||
v_sum += next_prob * V[next_state.value]
|
||||
# 计算 V = R + gamma * \sum(P·V)
|
||||
V[curr_state.value] = dataModel.R[curr_state.value] + gamma * v_sum
|
||||
# 检查收敛性
|
||||
if np.allclose(V, V_old):
|
||||
break
|
||||
print("迭代次数 :", count)
|
||||
return V
|
||||
|
||||
def print_V(V):
|
||||
print(V)
|
||||
vv = np.around(V,3)
|
||||
for s in dataModel.S:
|
||||
print(str.format("{0}:\t{1}", s.name, vv[s.value]))
|
||||
|
||||
if __name__=="__main__":
|
||||
dataModel = dm.DataModel()
|
||||
gamma = 1
|
||||
|
||||
V = Bellman_iteration_single_array(dataModel, gamma)
|
||||
print_V(V)
|
||||
@@ -1,5 +1,5 @@
|
||||
import numpy as np
|
||||
import CodeLifeCycle_DataModel_P as dm
|
||||
import CodeLifeCycle_0_DataModel_P as dm
|
||||
|
||||
# 矩阵迭代法
|
||||
def matrix_iteration(dataModel, gamma):
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
from sre_parse import State
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
|
||||
@@ -40,3 +38,41 @@ class DataModel(object):
|
||||
def get_next(self, curr_s):
|
||||
list_state_prob = self.D[curr_s] # 根据当前状态返回可用的下游状态及其概率
|
||||
return list_state_prob
|
||||
|
||||
|
||||
# 贝尔曼方程单数组就地更新
|
||||
def Bellman_iteration_single_array(dataModel, gamma):
|
||||
print("---单数组就地更新法---")
|
||||
V = np.zeros(dataModel.N)
|
||||
count = 0
|
||||
while (count < 1000): # 1000 是随意指定的一个比较大的数,避免不收敛而导致while无限
|
||||
count += 1
|
||||
V_old = V.copy()
|
||||
# 遍历每一个 state 作为 curr_state
|
||||
for curr_state in dataModel.S:
|
||||
# 得到转移概率
|
||||
list_state_prob = dataModel.get_next(curr_state)
|
||||
# 计算 \sum(P·V)
|
||||
v_sum = 0
|
||||
for next_state, next_prob in list_state_prob:
|
||||
v_sum += next_prob * V[next_state.value]
|
||||
# 计算 V = R + gamma * \sum(P·V)
|
||||
V[curr_state.value] = dataModel.R[curr_state.value] + gamma * v_sum
|
||||
# 检查收敛性
|
||||
if np.allclose(V, V_old):
|
||||
break
|
||||
print("迭代次数 :", count)
|
||||
return V
|
||||
|
||||
def print_V(V):
|
||||
print(V)
|
||||
vv = np.around(V,3)
|
||||
for s in dataModel.S:
|
||||
print(str.format("{0}:\t{1}", s.name, vv[s.value]))
|
||||
|
||||
if __name__=="__main__":
|
||||
dataModel = DataModel()
|
||||
gamma = 1
|
||||
|
||||
V = Bellman_iteration_single_array(dataModel, gamma)
|
||||
print_V(V)
|
||||
@@ -0,0 +1,16 @@
|
||||
# 第 8 章
|
||||
|
||||
|
||||
|
||||
我们仍然以前面学过的的状态转移问题来开始本章的学习,通过动手实验来发现这种方法的不足,然后引入新的概念,以便可以解决更复杂的问题。
|
||||
|
||||
|
||||
### 8.1.0 温故知新
|
||||
|
||||
在前面的章节中,我们利用贝尔曼方程,可以解决比较复杂的马尔科夫奖励过程 MRP 的问题。无论是安全驾驶问题,还是软件工程问题,似乎都是在描述一种状态转移的规律,但是这些状态转移是如何发生的呢,我们并没有仔细考察,也就是说我们只看到了现象,不知道原因,所以只能说是一种统计的结果。
|
||||
|
||||
在本章中我们重点学习的是如何
|
||||
|
||||
### 本章目录
|
||||
|
||||
### 参考资料
|
||||
@@ -0,0 +1,196 @@
|
||||
|
||||
## 8.1 射击气球问题
|
||||
|
||||
|
||||
|
||||
### 8.1.1 提出问题
|
||||
|
||||
在游乐场,有一个(用弓箭)射击气球中奖的游戏规则如图 8.1.1 所示,是这样的:
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-1.png">
|
||||
|
||||
图 8.1.1 射击气球游戏
|
||||
</center>
|
||||
|
||||
1. 游客花 4 元钱买 2 支箭,得到两次射击机会;
|
||||
2. 墙上有两个气球,一个蓝色大气球,一个红色小气球,每次射击游客可以任意选择目标;
|
||||
3. 脱靶不得奖;
|
||||
4. 击破蓝色大气球可以得到小奖,价值 1 元;
|
||||
5. 击破红色小气球可以得到大奖,价值 3 元。
|
||||
|
||||
游乐场老板经过几个月的营业后进行了统计,当然老板的目的不是做人工智能学习,而是要看能否多赚钱。结果如图 8.1.2 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-2.png">
|
||||
|
||||
图 8.1.2 游客中奖情况统计
|
||||
</center>
|
||||
|
||||
统计结果说明:
|
||||
|
||||
1. 游客在第一次射击时:
|
||||
|
||||
- 中大奖的概率是 0.06
|
||||
- 中小奖的概率是 0.38
|
||||
- 脱靶的概率是 0.56
|
||||
|
||||
|
||||
2. 第二次射击根据第一次射击的结果而有所不同:
|
||||
|
||||
比如,如果第一次射击中大奖,则:
|
||||
- 第二次射击继续中大奖的概率会是 0.10,比初始的 0.06 提高了一些,因为游客有经验了;
|
||||
- 第二次射击脱靶的概率是 0.40,比初始的 0.56 要低;
|
||||
- 第二次射击中小奖的概率是 0.50,比第一次的 0.38 要高,也是因为有了一定的经验而提高了命中率。
|
||||
|
||||
其它统计数字在图 2 中显示,不再赘述。
|
||||
|
||||
### 8.1.2 错误的 MRP 模型
|
||||
|
||||
老板设计的这个游戏很精明,因为游客会从数字中得到暗示:如果我中一个大奖和一个小奖,正好 4 元钱,抵掉了两支箭的花销。但实际上的中奖概率很低。那么做为一个聪明的游客,你应该如何选择呢?
|
||||
|
||||
在前面的章节中,我们学习过马尔科夫奖励过程的知识,是否可以把图 8.1.2 转换成状态转移矩阵与奖励函数,用来计算“脱靶、小奖、大奖” 三个状态的哪一个的状态价值函数值最大,来判断选择哪种方案呢?
|
||||
|
||||
有些读者前面的学习基础很牢固,于是可以立刻画出如图 8.1.3 的状态转移图和转移概率表。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-3.png">
|
||||
|
||||
图 8.1.3 MRP 模型下的状态转移图
|
||||
</center>
|
||||
|
||||
然后在前面的代码的基础上换一下数据(状态、转移、奖励),即可用矩阵法立刻解出结果。
|
||||
|
||||
我们可以先检查一下这个状态转移矩阵的收敛情况:
|
||||
|
||||
【代码位置】Shoot_0_MPR_Wrong.py
|
||||
|
||||
```
|
||||
折扣 = 1
|
||||
迭代100次, 检查状态转移矩阵是否趋近于 0:
|
||||
[[0. 0.524 0.405 0.071]
|
||||
[0. 0.524 0.405 0.071]
|
||||
[0. 0.524 0.405 0.071]
|
||||
[0. 0.524 0.405 0.071]]
|
||||
------------
|
||||
价值函数:
|
||||
Start: 6174930.04
|
||||
Miss: 6174934.04
|
||||
Small: 6174935.15
|
||||
Grand: 6174937.304
|
||||
```
|
||||
|
||||
当折扣值 $\gamma=1$ 时,这个转移矩阵本身收敛到了 [0, 0.524, 0.405, 0.071],意味着如果游客持续地射击多次,脱靶的可能性最大(0.524),中小奖的可能性为 0.405,中大奖的可能性为 0.071。
|
||||
|
||||
从输出的价值函数值看,值都很大。因为可以在多个状态之间不断地循环,获得很多很多奖励。但是却忽略了一个条件:每个游客只能射箭两次。如果要多次射箭的话,需要停下来买更多的箭。从图 8.1.3 左图来看,“开始”状态只经历了一次,所以这个状态转移图画得不对。
|
||||
|
||||
另外,没有收敛到全 0 状态,说明用迭代法计算状态价值函数是有问题的,会有无限奖励。因此需要折扣值小于 1。
|
||||
|
||||
下面我们看看 $\gamma=0.9$ 时的情况:
|
||||
```
|
||||
折扣 = 0.9
|
||||
迭代100次, 检查状态转移矩阵是否趋近于 0:
|
||||
[[0. 0. 0. 0.]
|
||||
[0. 0. 0. 0.]
|
||||
[0. 0. 0. 0.]
|
||||
[0. 0. 0. 0.]]
|
||||
------------
|
||||
价值函数:
|
||||
Start: 1.501
|
||||
Miss: 5.501
|
||||
Small: 6.599
|
||||
Grand: 8.736
|
||||
------------
|
||||
```
|
||||
这次的转移矩阵趋近于 0 了,说明迭代可以收敛,价值函数基本反映出了各个状态的比较结果。但还是存在前面说过的问题:在后面的状态转移中,没有包括前面的“开始”状态,相当于忘记了最开始要花 4 元钱买箭的问题,这就应了那句话:“只看见贼吃肉,没看见贼挨打”。所以整个的状态价值函数计算模型就是错误的。
|
||||
|
||||
### 8.1.3 正确的 MRP 模型
|
||||
|
||||
这个问题的状态转移图应该如图 8.1.4 所示,是一个有向无环图。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-4.png">
|
||||
|
||||
图 8.1.4 正确的的状态转移图
|
||||
</center>
|
||||
|
||||
图例说明:
|
||||
|
||||
- 圆圈内的文字是状态描述,数值是该状态的序号。
|
||||
- 连线是状态转移方向,数值是状态转移概率。
|
||||
- 红色的 $R$ 是奖励值。
|
||||
- 方框是终止状态 T。
|
||||
|
||||
状态说明:
|
||||
|
||||
1. 虽然状态中有四个“大奖”,四个“小奖”,四个“脱靶”,但是这四个状态不是同一个状态,只是名字一样而已,在图中特意用序号把它们都区分开了。其中:
|
||||
- 0 号为开始状态;
|
||||
- 1~3 号为二级状态;
|
||||
- 4~12 号为三级状态。
|
||||
2. 开始状态需要有 -4 的奖励,因为一开始游客是花 4 元钱买了两支箭。
|
||||
3. 所有的三级状态(序号4~12),最后都接一个终止状态 T,在第二次射击后,都会以 1 的概率达到该状态。
|
||||
4. 奖励是定义在状态上的,面向结果,在绘图时故意让字母 $R$ 与状态区域重合,以强调这一点。
|
||||
|
||||
为什么状态中含有四个“大奖”不能只用一个状态来表示呢?换句话说,为什么图 8.1.3 是错误的呢?
|
||||
|
||||
就好比从一楼上 10 级台阶到二楼,台阶可以定义为 $S_{1,1}$ 到 $S_{1,10}$;而二楼到三楼同样有 10 级台阶,也定义为 $S_{2,1}$ 到 $S_{2,10}$。但所处的楼层不一样,所以它们也是 20 个不同的状态。假设除了楼梯以外,还有一个滑梯,从 $S_{2,1}$ 能直接滑到 $S_{1,1}$,从而造成环,那就是两个状态之间的循环转移,在本例中并不存在。
|
||||
|
||||
### 8.1.4 计算 MRP 模型的状态价值函数
|
||||
|
||||
由于是个有向无环的图,所以可以简单地从后向前计算一下各个状态的价值函数。根据式 xxx 很简单就可以得到(为简化计算过程,本例中折扣 $\gamma=1$):
|
||||
|
||||
首先,终止状态 $v_T = 0$。
|
||||
|
||||
然后以 $v_4$ 为例应用式 ():$v_4 =R(s)+ \gamma P_{ss'}V(s')= 3+1\times1\times0=3$。其它三级状态的计算方法相同,得到三级状态的值后,就可以向上继续计算二级状态的价值函数,最后得到开始状态的价值函数。
|
||||
|
||||
$$
|
||||
\begin{cases}
|
||||
v_T=0 & (终止状态)
|
||||
\\
|
||||
v_4=0, v_5 = 1, v_6 = 3 &(三级状态)
|
||||
\\
|
||||
v_7=0, v_8=1, v_9=3 &(三级状态)
|
||||
\\
|
||||
v_{10}=0, v_{11}=1, v_{12}=3 &(三级状态)
|
||||
\\
|
||||
v_1 = 0 + 0.56v_4+0.38v_5+0.06v_6= 0.56&(二级状态)
|
||||
\\
|
||||
v_2 = 1 + 0.50v_7+0.42v_8+0.08v_9= 1.66&(二级状态)
|
||||
\\
|
||||
v_3 = 3 + 0.40v_{10}+0.50v_{11}+0.10v_{12}= 3.8&(二级状态)
|
||||
\\
|
||||
v_0 = -4 + 0.56v_1+0.38v_2+0.06v_3 = -2.8276 &(开始状态)
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
当然,也可以用矩阵法来解这个线性方程组,得到的结果和手工计算的一致:
|
||||
|
||||
【代码位置】Shoot_1_MPR_Correct.py
|
||||
```
|
||||
v0 = -2.8276
|
||||
v1=0.56, v2=1.66, v3=3.8
|
||||
v4=0.0, v5=1.0, v6=3.0
|
||||
v7=0.0, v8=1.0, v9=3.0
|
||||
v10=0.0, v11=1.0, v12=3.0
|
||||
```
|
||||
|
||||
最终的状态价值函数结果标在图 8.1.5 中,圆圈内的数值是马尔可夫奖励过程的状态价值函数值。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-5.png">
|
||||
|
||||
图 8.1.5 MRP 模型的状态价值函数值
|
||||
</center>
|
||||
|
||||
所以,从统计学的观点看,$s_0$ 的状态函数值 $v_0$ 告诉我们只要游客买了箭,就已经亏了,老板是稳赚不赔的。当然不排除个别游客(射箭能手)连续两次击中大奖。一般人都会高估自己(哈哈,科学的说法是:不能正确地评估自己),一些游客会因为想到最大值为 3+3=6 的奖励而参与,一些游客只是因为没有射过箭而参与。
|
||||
|
||||
那么游客应该怎么选择呢?
|
||||
|
||||
- 虽然 $v_{3}=3.8$ 状态价值最高,但是到达 $s_3$ 只有 0.06 的概率,可以理解为游客的预期收益是:$0.06 \times 3.8 = 0.228$ 元。
|
||||
- $s_1$ 的预期收益是 $0.56 \times 0.56 = 0.3136$ 元。
|
||||
- $s_2$ 的预期收益是 $0.38 \times 1.66 = 0.6308$ 元。
|
||||
|
||||
上面三个状态的预期收入值相加为 1.1724 元,正好等于游客开始花的 4 元再加上 $v_0$ 的值,即 4-2.8276=1.1724,这也是 $v_0$ 的表达式告诉我们的。
|
||||
|
||||
|
||||
所以游客第一次射击时应该选择 $s_2$,即“小奖”状态,然后第二次射击时继续选择“小奖”状态。这样虽然肯定不能赢回开始花的 4 元钱,但是可以做到损失最小(或者收益最大)。
|
||||
@@ -0,0 +1,184 @@
|
||||
|
||||
## 8.2 策略与动作
|
||||
|
||||
### 8.2.1 疑问
|
||||
|
||||
在 8.1 节中,我们经过一番分析,纠正了错误的模型图,得到了一个看似合理的结果。但仔细想一想,其实上面的解题过程是有问题的:
|
||||
|
||||
1. 原问题是“聪明的游客会如何选择?”,但是整个解题思路是根据游乐场老板的统计结果进行的,是从老板的角度出发,而非游客的角度。
|
||||
2. 在一级的三个状态中(大奖,小奖,脱靶),它只代表“结果”,而不代表“选择”,较真儿地说,没有游客会选择“脱靶”状态,因为“脱靶”只是个结果。
|
||||
3. 忽略前面两个疑问,最后得到了 $v_1,v_2,v_3$ 的值,游客会看到 $v_3$ 最大,所以会误导其选择大奖而射击小气球,但是忽略了击中的难度。现实生活中人们往往也有类似的经历,想利益最大化,却忽略了风险。
|
||||
|
||||
实际上,老板并不知道游客选择的是哪个气球,他只看到了最终的结果。游客自己也不会主动说我要射击哪一个气球,避免“打哪儿指哪儿”的尴尬场面出现。
|
||||
|
||||
### 8.2.2 有洞察力的数据
|
||||
|
||||
经过对游客的调查与过程观察,以及与老板的访谈,我们得到了一些更细化的统计数据,如图 8.2.1 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-6.png">
|
||||
|
||||
图 8.2.1 细化的射击数据
|
||||
</center>
|
||||
|
||||
数据说明:
|
||||
|
||||
1. 在开始状态时,游客的**策略**选择可以有两个:
|
||||
- $a_1$ - 选择射击小气球而中大奖,大概有 40% 的人选择,记为 $\pi_1=0.4$。
|
||||
- $a_2$ - 选择射击大气球而中小奖,大概有 60% 的人选择,记为 $\pi_2=0.6$。
|
||||
|
||||
2. 执行**动作** $a_1$,在射击红色小气球时,有可能的**过程**及**结果**是:
|
||||
- 有 0.80 的概率**脱靶**,记为 $p_{11}=0.80$;
|
||||
- 还有 0.05 的概率歪打正着地击中大气球而得**小奖**,记为 $p_{12}=0.05$。
|
||||
- 因为气球比较小,不容易击中,只有 0.15 的概率打中,得**大奖**,记为 $p_{13}=0.15$;
|
||||
|
||||
3. 选择**动作** $a_2$,在射击大气球时,有可能的**过程**及**结果**是:
|
||||
- 有 0.4 的概率**脱靶**,记为 $p_{21}=0.4$;
|
||||
- 因为气球比较大,容易击中,有 0.6 的概率击中,得**小奖**,记为 $p_{22}=0.6$;
|
||||
- 没有任何运气可以击中小气球,记为 $p_{23}=0.0$。
|
||||
- 这种情况在实际解题时可以不必画出来,在这里画出来是为了把细节讲清楚,避免误解。
|
||||
|
||||
请读者注意在 $a_2$ 部分下的 [0.4,0.6] 是状态转移概率,与开始状态 $s$ 下的策略选择概率 [0.4,0.6] 只是在数值上恰巧相同,含义完全不同。
|
||||
|
||||
现在可以统计一下各种中奖的情况:
|
||||
|
||||
- 脱靶的概率是两种情况之和:
|
||||
1. 选择射击红色气球,脱靶, $\pi_1 p_{12}= 0.4 \times 0.8 = 0.32$
|
||||
2. 选择射击蓝色气球,脱靶, $\pi_2 p_{22}= 0.6 \times 0.4 = 0.24$
|
||||
|
||||
二者和为 $0.32+0.24=0.56$
|
||||
|
||||
- 中小奖的概率是两种情况之和:
|
||||
1. 选择射击红色气球,但却误中蓝色气球,$\pi_1 p_{12}= 0.4 \times 0.05 = 0.02$
|
||||
2. 选择射击蓝色气球,击中,$\pi_2 p_{22}= 0.6 \times 0.6 = 0.36$
|
||||
|
||||
二者和为 $0.36+0.02=0.38$
|
||||
|
||||
- 中大奖的概率是两种情况之和:
|
||||
1. 选择射击红色气球,击中, $\pi_1 p_{13}= 0.4 \times 0.15 = 0.06$
|
||||
2. 选择射击蓝色气球,却击中红色气球, $\pi_2 p_{23}= 0.6 \times 0.0 = 0.00$
|
||||
|
||||
二者和为 $0.06+0.00=0.06$
|
||||
|
||||
所以这个结果 $[0.56, 0.48, 0.06]$ 和图 8.1.2 中老板统计的第一次射击的结果是一致的。但是图 8.2.1 中的数据更具有洞察力,可以让我们做到“知其所以然”。
|
||||
|
||||
### 8.2.3 带有动作节点的全新模型
|
||||
|
||||
在图 8.2.1 中,我们给一级和二级状态之间增加了动作节点,并且计算出在经过**策略选择**和**状态转移**两个过程后,得到的中奖数据与老板的统计数据相同。
|
||||
|
||||
需要注意的是,在图 8.1.2 中的二级状态只有 3 个节点(脱靶、小奖、大奖),而图 8.2.1 中的二级状态有 5 个有效(转移概率不为 0)节点。
|
||||
|
||||
在做结果统计的时候,可以把 $a_1,a_2$ 的下游状态中具有相同名称的结果相加(比如两种情况下脱靶的总概率为 0.32+0.24=0.56),但实际上它们还是不同的状态,各自具备不同的价值函数,不能像图 3 那样混在一起。
|
||||
|
||||
所以在做过程分析的时候,这些状态节点中具有相同名称的节点是不能合并在一起的,因为其**来源**不同,必然会造成**去向**也不同。保留来龙去脉的过程,是全新模型的重要特征。
|
||||
|
||||
下面我们需要在二级状态后也增加动作节点,以便可以得到该问题的全模型,如图 8.2.2 所示。
|
||||
|
||||
#### 图例说明
|
||||
|
||||
- 空心方框:状态节点。在后面的模型图中,我们更多地会用空心圆形来表示。在这里是为了可以从形状上与动作节点分开。含状态名称和序号(括号内的数字)。
|
||||
- 实心椭圆:动作节点。含动作名称和序号(括号内的数字,成对出现)。
|
||||
- 蓝色连线:动作选择,含策略概率(蓝色数字)。
|
||||
- 绿色连线:状态转移,含转移概率(绿色数字)。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-7.png">
|
||||
|
||||
图 8.2.2 带有动作节点的全新模型
|
||||
</center>
|
||||
|
||||
#### 模型说明
|
||||
|
||||
- 状态节点
|
||||
|
||||
- 用状态名称和序号加以区分,比如“小奖(2)”和“小奖(5)”是两个不同的状态。
|
||||
- 状态空间的值为 7,一共有 7 个状态。
|
||||
- 状态空间的定义是 $S$,一般说到 $s \in S$,意思是某个状态 $s$ 在 $S$ 集合中。
|
||||
- 状态节点下都有两个动作选择:红色和蓝色。
|
||||
- 最下面的长方形节点是终止状态。
|
||||
|
||||
- 动作节点
|
||||
|
||||
- 用动作名称和序号加以区分,比如“红(2)”和“红(4)”,表明它们是不同的两个动作。
|
||||
- 动作空间的值为 2,因为一共只有两个动作:选择射击红色气球或蓝色气球。
|
||||
- 射击红色气球后,有可能的状态转移有三种:脱靶,小奖,大奖。
|
||||
- 射击蓝色气球后,有可能的状态转移有两种:脱靶,小奖。
|
||||
|
||||
读者可能会有疑问:为什么动作空间不是 12?
|
||||
- 动作空间的定义是 $A(s)$,$a \in A(s)$,举例来说,在“大奖(3)”状态,即 $s=$“大奖(3)” 时,$A(s)$ 集合中只有两个动作,红色或蓝色,而不是 12 个。
|
||||
|
||||
- 策略
|
||||
策略始终保持不变:0.4 的概率选择红色,0.6 的概率选择蓝色。
|
||||
|
||||
- 状态转移
|
||||
根据上游状态节点的不同,下游的转移概率也不尽相同。举例来说:
|
||||
- “红(0)”下面的转移概率是 [0.80, 0.05, 0.15],是初始转移概率。
|
||||
- “红(6)”下面的转移概率是 [0.70, 0.05, 0.25],
|
||||
- 因为在“红(0)”击中红球后,进入“大奖(3)”状态,游客的经验增加,射中红色气球的概率也会增加,从初始的 0.15 升高到 0.25;
|
||||
- 相应地脱靶概率从 0.80 降低为 0.70;
|
||||
- 而误中小奖的概率不变,保持在很小的 0.05 的概率。
|
||||
|
||||
#### 终止节点的模型设计
|
||||
|
||||
在图 8.2.2 中,我们看到了一个比较奇怪的现象:所有的二级动作的下游状态,全都归结于同一个终止节点(6)。为什么这样设计呢?
|
||||
|
||||
其实在这里有两种设计方法,如图 8.2.3 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-8.png">
|
||||
|
||||
图 8.2.3 不同的终止节点设计
|
||||
</center>
|
||||
|
||||
- 左侧
|
||||
|
||||
再多设计出 3 个状态 $s_6,s_7,s_8$,用于承载动作“红(1)”的结果。记住动作“蓝(1)”也会有两个状态,命名为 $s_9,s_{10}$,这样的话,一对动作就会有 5 个下游状态,五对动作一共要增加 25 个下游状态。
|
||||
|
||||
- 右侧
|
||||
|
||||
像目前这样,用终止节点 T 直接接到动作“红(1)”上,只不过这里画了 3 个终止节点,与图 8.2.2 的共用同一个终止节点没有本质区别。
|
||||
|
||||
在这里我们先把问题提出来,等到我们后面学习完动作价值函数的知识后,再给予解答。
|
||||
|
||||
### 8.2.4 验算老板的统计数据
|
||||
|
||||
下面我们验算一下游乐场老板在图 8.1.1 中关于第一次“小奖”后第二次也“小奖”的统计数据是否正确。
|
||||
|
||||
在图 8.2.2 中:
|
||||
|
||||
- 左侧状态“小奖(2)”下面:
|
||||
$$
|
||||
0.4 \times 0.05 + 0.6 \times 0.55 = 0.35
|
||||
$$
|
||||
|
||||
- 右侧“小奖(5)”下面:
|
||||
$$
|
||||
0.4 \times 0.04 + 0.6 \times 0.75 = 0.466
|
||||
$$
|
||||
|
||||
- 状态“开始0”下面,左侧“红(0)”的策略选择概率是 0.4,右侧“蓝(0)”的策略选择概率是 0.6:
|
||||
$$
|
||||
0.4 \times 0.35 + 0.6 \times 0.466 = 0.4196 \approx 0.42
|
||||
$$
|
||||
|
||||
所以最后的结果是 0.42,与图 8.1.1 中老板的统计值一致。只不过老板知其然不知其所以然,只看到了统计结果,没有深入到过程细节中,而这正是我们接下来要学习的问题。
|
||||
|
||||
用代码也可以验证所有情况,输出结果如下:
|
||||
|
||||
【代码位置】Shoot_2_DataModel.py
|
||||
```
|
||||
第一枪脱靶 0.56
|
||||
第二枪脱靶 0.56
|
||||
第二枪小奖 0.38
|
||||
第二枪大奖 0.06
|
||||
第一枪小奖 0.38
|
||||
第二枪脱靶 0.5
|
||||
第二枪小奖 0.42
|
||||
第二枪大奖 0.08
|
||||
第一枪大奖 0.06
|
||||
第二枪脱靶 0.4
|
||||
第二枪小奖 0.5
|
||||
第二枪大奖 0.1
|
||||
```
|
||||
|
||||
有读者会问:如果有了老板的统计结果,能不能反推出这个模型呢?答案是不能。因为可能会有很多种组合都可以满足老板的统计结果,模型不是唯一的。
|
||||
@@ -0,0 +1,286 @@
|
||||
## 8.3 马尔可夫决策过程
|
||||
|
||||
### 8.3.1 强化学习模型
|
||||
|
||||
在引入了策略动作后,强化学习的模型就已经很完备了,如图 8.3.1。
|
||||
|
||||
<center>
|
||||
<img src="./img/RL-basic.png">
|
||||
|
||||
图 8.3.1 强化学习模型
|
||||
</center>
|
||||
|
||||
#### 实体说明
|
||||
|
||||
- 智能体:强化学习的行为主体,比如一个机器人,或者一个程序。
|
||||
- 环境:根据智能体的不同,这个环境的定义也不同。比如智能体是狼,那么环境就是由草原、羊群、其它狼等构成。如果智能体是工业机器人,则环境由生产线、工件、流程等构成。
|
||||
- 状态:智能体所在的与环境相关的状态,可能有很多状态,比如棋类游戏中的每一个盘面。也可能很少的状态,比如扫地机器人一共只有“充电、扫地”两个状态。
|
||||
- 动作:智能体在不同状态下根据策略而采取的动作,其目的是从环境处获得奖励。
|
||||
- 奖励:环境给与智能体的反馈,可能是食物、胜负、荣誉等等虚头八脑的东西,但是一定是可以量化的数值。可以是正数或负数,只与前后奖励值的相互比较的大小有关,与绝对值无关。比如在一个环境中,令 $R_1=1,R_2=2$,和令 $R_1=-1,R_2=1$,是没有区别的,只要 $R_2 \gt R_1$ 即可。
|
||||
|
||||
#### 序号说明
|
||||
|
||||
1. 智能体做策略选择
|
||||
2. 执行动作影响环境
|
||||
3. 环境给与即时奖励
|
||||
4. 环境产生新的状态
|
||||
5. 智能体得到奖励
|
||||
6. 智能体得到新状态
|
||||
|
||||
#### 环境、状态、决策、动作、过程、奖励、结果
|
||||
|
||||
回顾一下人们所了解的所有智能生物的行为,都是根据当前**环境**和自身**状态**情况先做出**决策**,选择**动作**,经历**过程**,然后得到**奖励**与**结果**。
|
||||
- 这里的**决策**(或**策略**),可以是一个函数,直接输出一个选择好的动作值,红色的 $a_1$ 或蓝色的 $a_2$。
|
||||
- 这里的**结果**就是图 8.2.1 中的状态,包括开始、中大奖、中小奖、脱靶,与**马尔可夫过程**以及**马尔可夫奖励过程**中的状态是一个概念。
|
||||
|
||||
|
||||
先有奖励还是先有结果?
|
||||
|
||||
从模型上看,在等待**结果**的**过程**中得到**奖励**,但是**过程**具有不确定性。
|
||||
|
||||
那么**过程**如何理解呢?
|
||||
|
||||
最简单并且容易理解的说法是:人们做一件事,一般都会有两个结果,即成功和失败,你可以努力争取成功,但是并不能完全避免失败。这就可以叫做**转移概率**。只不过你非常努力的时候,转移到成功状态的概率较大,而转移到失败状态的概率较小。
|
||||
|
||||
用本例来说明的话,可以解释为:
|
||||
|
||||
- 游客可能第一次射击,没经验,所以打偏;
|
||||
- 弓箭可能准星不准,游乐场老板也不用心维护;
|
||||
- 气球会在风中有微小摆动;
|
||||
- 周围的环境嘈杂,游客不能击中精力射击;
|
||||
......
|
||||
|
||||
诸如此类的环境因素会造成在选择动作后,却偏离既定目标,得到意想不到的结果。这与**马尔可夫过程**以及**马尔可夫奖励过程**中的状态转移概率不尽相同,而且一般也不用矩阵形式来描述。因为矩阵中的元素所处的行列数值,都代表了状态的序号,而在马尔科夫决策过程部分,没有办法把动作和状态放在一起来形成矩阵。简单地说就是动作把矩阵割裂成了很多小的部分,在每个小部分中没有环状的状态转移存在。
|
||||
|
||||
|
||||
### 8.3.2 马尔可夫决策过程 MDP
|
||||
|
||||
在上一节中,我们在上下游的状态之间引入了策略和动作,即,在马尔可夫奖励过程中引入策略和动作的概念,就形成了一个新的概念:**马尔可夫决策过程**。它是图 8.3.1 所示的强化学习模型的具体体现。
|
||||
|
||||
用一个文字公式来表示 MDP(Markov Decision Process,马尔可夫决策过程):
|
||||
|
||||
$$
|
||||
马尔可夫决策过程 = 马尔可夫奖励过程 + 动作策略
|
||||
$$
|
||||
|
||||
#### 过程描述
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-0.png">
|
||||
|
||||
图 8.3.2 马尔可夫决策过程
|
||||
</center>
|
||||
|
||||
图 8.3.2 是一个马尔可夫过程。可以通过实体与箭头的位置,来准确地理解发生的地点和顺序。其中的下标表示时间序号。比如:
|
||||
|
||||
- 动作 $a$ 是发生在状态 $s$ 的右端点上,有了 $a$ 才会有状态转移。
|
||||
- 在 $s_1$ 执行了 $a_1$ 后,有可能到实线的 $s_2$,也有可能到另外一个虚线的 $s_2$,这不是 $a_1$ 所能控制的,属于状态转移。
|
||||
- 在到达 $s_2$ 的过程中产生 $R_2$。这样定义可以保证唯一性,是为了区别从其它状态到达 $s_2$ 时的奖励。
|
||||
|
||||
在前面的**马尔科夫奖励**过程中,我们学习过组成其过程的是一个四元组数据 $<S,P,R,\gamma>$,分别是状态、转移概率、奖励、折扣。进一步,在**马尔可夫决策**过程中,通过上一小节中引入的新元素“动作”,可以得到一个五元组数据 $ <S,A,P,R,\gamma>$,其中 $A$ 代表动作。
|
||||
|
||||
请读者注意上述五元组数据序列的顺序:
|
||||
1. 必须从一个状态 $S$ 开始;
|
||||
2. 执行一个动作 $A$;
|
||||
3. 会以概率 $P$ 转移到下一个状态 $S'$;
|
||||
4. 在转移过程中得到奖励 $R$;
|
||||
5. 最后计算整个序列的回报时可以有折扣 $\gamma \in [0,1]$。
|
||||
|
||||
表 1 三种过程的简单比较
|
||||
|
||||
|过程名称|组成元素|数据序列|计算|
|
||||
|-|-|-|-|
|
||||
|马尔可夫过程 MP|$<S,P>$|$S_0,S_1,\cdots,S_t$||
|
||||
|马尔可夫奖励过程 MRP|$<S,P,R,\gamma>$|$S_0,R_1,S_1,R_2,\cdots,S_t,R_{t+1}$|$V$|
|
||||
|马尔可夫决策过程 MDP|$<S,A,P,R,\gamma>$|$S_0,A_0,R_1,S_1,A_1,R_2,\cdots,S_t,A_t,R_{t+1}$|$V_\pi,Q_\pi$|
|
||||
|
||||
#### 策略选择模型($S \to \pi \to A$)
|
||||
|
||||
如图 8.3.3 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-state-action.png">
|
||||
|
||||
图 8.3.3 马尔可夫决策过程的策略选择模型
|
||||
</center>
|
||||
|
||||
- 策略 $\pi$
|
||||
|
||||
用 $\pi(a|s)$ 表示在状态 $s$ 下选择动作 $a$。蓝色的弧形虚线表示作用范围。
|
||||
特别地,$\pi(a_1|s)$,表示在 $s$ 状态下,根据策略 $\pi$ 执行动作 $a_1$。其通用定义为:
|
||||
|
||||
$$
|
||||
\pi(a|s) = \mathbb P [A_t=a|S_t=s] \tag{8.3.1}
|
||||
$$
|
||||
|
||||
用文字描述为:在时刻 $t$ 的状态为 $s$ 时选择动作 $a$ 的概率。大写的 $A_t,S_t$ 表示变量或集合,小写的 $a,s$ 表示实例数值。
|
||||
|
||||
在射击气球问题中,动作有两个,射击红色气球和射击蓝色气球。如何选择这两个动作就是策略问题。
|
||||
|
||||
策略可以被描述为一个函数,函数的最终输出是一个确定的动作值,比如 $a_1$ 或 $a_2$。但是函数的内部运算方法可以有两种:
|
||||
|
||||
- **概率选择方法**,具体的说就是用类似下面这个函数:
|
||||
```Python
|
||||
a = np.random.choice([a1,a2], p=[0.4,0.6])
|
||||
```
|
||||
来得到动作值 $a$,概率大的动作理所当然地有较大的概率被选中。
|
||||
|
||||
- **最佳动作方法**,就是用类似下面这个函数:
|
||||
```Python
|
||||
a = np.argmax([p1,p2])
|
||||
```
|
||||
来得到动作值 $a$,概率小的那个动作永远没有被选中的机会。
|
||||
|
||||
策略函数不能输出一个概率让调用者做决定,否则就失去了决策的意义。
|
||||
|
||||
#### 状态转移模型($A \to p \to S'$)
|
||||
|
||||
如图 8.3.4 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-action-state.png">
|
||||
|
||||
图 8.3.4 马尔可夫决策过程的状态转移模型
|
||||
</center>
|
||||
|
||||
- 转移概率 $p$
|
||||
每个动作下面都会存在一个转移概率,绿色的弧形虚线表示作用范围。特别地,如果只有一个下游状态存在,则转移概率值为 1。
|
||||
|
||||
- $p^{a}_{ss_1}$,小写的 $p$ 表示在动作 $a$ 发生后,从状态 $s$ 转移到状态 $s_1$ 的概率的实例,上标 $a$ 要保持和动作符号一致,下标 $ss_1$ 要保持和上下游状态符号一致;
|
||||
|
||||
- $P^{a}_{ss'}$,大写的 $P$ 表示 $a$ 动作的所有下游状态的转移概率的集合(用向量表示),在本例中包括两个元素 $P^{a}_{ss'}=[ p^{a}_{s s_1},p^{a}_{s s_2}]$。其通用表达式为:
|
||||
|
||||
$$
|
||||
P^a_{ss'}=\mathbb P [S_{t+1}=s'|S_t=s, A_t=a] \tag{8.3.2}
|
||||
$$
|
||||
|
||||
用文字描述为:在时刻 $t$ 的状态 $s$ 下采取动作 $a$ 后转移到时刻 $t+1$ 的状态 $s'$ 的概率。大写的符号是变量,小写的符号是实例。
|
||||
|
||||
在实际运算中,转移概率 $p$ 值可以直接作为权重系数与后面的主体(状态价值或奖励)相乘,以表达数学期望。
|
||||
|
||||
#### 完整模型($S \to \pi \to A \to p \to S'$)
|
||||
|
||||
把**策略选择模型**和**状态转移模型**组合起来,绘制出图 8.3.5 的马尔可夫决策过程的完整模型。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-1.png">
|
||||
|
||||
图 8.3.5 马尔可夫决策过程的完整模型
|
||||
</center>
|
||||
|
||||
|
||||
|
||||
图 8.3.5 实际上是图 8.2.1 的一个抽象,去掉了与应用场景相关的表达,但又不是抽象到无法理解,所以这是一个马尔可夫决策过程的**实例化**模型。为什么叫实例化模型呢?因为图中的各个子元素都带有序号,而不是笼统抽象的符号。比如:
|
||||
|
||||
- 状态
|
||||
|
||||
用空心的圆表示,里面标出了状态序号,如 $s_0, s_1$ 等。
|
||||
|
||||
两侧的 $s,s'$ 和 $S_t,S_{t+1}$,是一种概念的两种表达形式,在公式推导中,一般会用 $s,s'$ 表示当前状态和下一个状态的**实例**,用 $S_t,S_{t+1}$ 来强调**时序**关系的和状态**变量**。
|
||||
|
||||
状态一定是源于动作,终于动作,不能两个状态直接连在一起。状态是状态转移的结果。
|
||||
|
||||
- 动作
|
||||
|
||||
用实心的圆表示,里面标出了动作序号,如 $a_1,a_2$ 等。
|
||||
动作一定是源于状态、终于状态,不可能两个动作直接连接在一起。动作是策略选择的结果。
|
||||
|
||||
- 过程
|
||||
|
||||
图 8.3.5 中带有箭头的线都表示过程,有两类:
|
||||
|
||||
- 蓝色实线:表示策略 $\pi$ 的动作选择过程。**这是强化学习的主要学习目标**。
|
||||
|
||||
- 绿色虚线:表示概率 $P$ 的状态转移过程。**这是区别有模型与无模型强化学习问题的主要标志**。有明确的转移概率的,叫做**有模型**,否则叫做**无模型**。
|
||||
|
||||
这里用绿色虚线可以给色弱的朋友们提供更好的帮助,以便可以轻松地与蓝色实线区分开来。
|
||||
|
||||
#### 奖励模型
|
||||
|
||||
在奖励模型中,我们使用了面向过程的奖励方法。与在**马尔可夫奖励过程**中学习过的面向过程的奖励方法相似,区别是:
|
||||
|
||||
- 在前面的**马尔可夫奖励过程**中,定义的是 $R_{ss'}$,即状态到状态的转移过程中产生的奖励。
|
||||
- 在本节的**马尔可夫决策过程**中,定义比较复杂,请看图 8.3.6。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-reward.png">
|
||||
|
||||
图 8.3.6 马尔可夫决策过程的奖励模型
|
||||
</center>
|
||||
|
||||
在图 8.3.6 中,简化了一些符号,比如状态转移概率用 $p_1,\cdots,p_5$ 表示(图 8.3.5 中用 $p^a_{ss'}$ 表示,但含义一致),重点突出了**红色**的奖励机制。
|
||||
|
||||
共有三层元素来描述整体的奖励模型:
|
||||
|
||||
- 底层
|
||||
$r_1,\cdots,r_5$,分别表示从动作发生后,到达下个状态时的即时奖励值。如:射中小气球可以得到 3 分的奖励,脱靶 0 分,射中大气球得 1 分奖励。
|
||||
|
||||
读者在这里可能会有疑问:明明是先射中气球,然后老板才给与奖励,所以红色的 $r$ 应该标注在 $s_1,s_2,\cdots$ 上才对,面向结果。但图中为什么标注在到达 $s_1,s_2,\cdots$ 的过程中呢(面向过程)?
|
||||
|
||||
可以这样解释:
|
||||
|
||||
1. 其实是否能射中气球,在游客扣下扳机后,让箭飞一会儿,在箭飞行的过程中接近尾声的时候就已经确定了,而不是在气球爆裂后才确定(假设箭头接触气球后一定会爆裂)。箭飞行的过程就是状态转移阶段。
|
||||
|
||||
2. 得到老板的奖品,只是老板在履行承诺而已。它是即刻生效的,而不是后期生效的。老板是看到气球爆裂后才决定是否给与奖励(因为人类肉眼无法观察箭的飞行过程),但是这与强化学习问题无关了。当然,如果老板耍赖,你也可以把这种耍赖的概率也计算在状态转移中(比如有 p=0.1 的概率是老板耍赖)。
|
||||
|
||||
|
||||
同一个动作分支的下游过程奖励值可以写成一个奖励向量($n$ 为下游状态的数量):
|
||||
|
||||
$$
|
||||
R^a_{ss'} = [r_1, r_2, \cdots, r_n] \tag{8.3.3}
|
||||
$$
|
||||
|
||||
其中的 $r_1,\cdots,r_n$ 也可以抽象地写成 $r^a_{ss'}$,便于后面用公式抽象表达。
|
||||
|
||||
- 中层
|
||||
$R(s,a)$ 表示在 $s$ 状态时执行动作 $a$ 的奖励函数,简称为**动作奖励函数**,在有的文献中记为 $R^a_s$,其含义一致。笔者认为,如果定义为**函数**,就应该有个形式化的括号来表示。
|
||||
这个奖励函数不是凭空产生的,是根据底层的过程奖励计算得到的,它发生在动作节点上。如果在其它学习资料的案例中发现有这种写法,请读者一定要明白其来源。
|
||||
|
||||
如果 $a_1,a_2$ 表示两个不同动作,则这两个动作的奖励函数分别是:
|
||||
- $R(s,a_1)=p_1 r_1 + p_2 r_2 + p_3 r_3$
|
||||
- $R(s,a_2)=p_4 r_4 + p_5 r_5$
|
||||
|
||||
有通用写法($n$ 为下游状态的数量):
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
R(s,a) &= \mathbb E[R_{t+1}|S_t=s,A_t=a]
|
||||
\\
|
||||
&=\sum^n_{i=1} p_ir_i = \sum_{s' \in S} p^a_{ss'} r^a_{ss'} = P^a_{ss'}R^a_{ss'}
|
||||
\end{aligned}
|
||||
\tag{8.3.4}
|
||||
$$
|
||||
|
||||
以 $a_1$ 为例,在实际的状态转移过程中,只可能有 $r_1,r_2,r_3$ 三者中的之一发生,要看运气(概率),即游客要么中大奖,要么中小奖,要么不中奖,没有其它的组合。但是在模型描述时,只能用这种数学期望来表示。
|
||||
|
||||
|
||||
- 顶层
|
||||
对于状态 $s_0$ 来说,由于不知道下一步将采取什么动作以及转移到哪一个状态,所以,只能把奖励函数定义为一个期望:
|
||||
$$
|
||||
\begin{aligned}
|
||||
R(s) &= \mathbb E[R_{t+1}|S_t=s]
|
||||
\\
|
||||
&=\pi_1 R(s,a_1) + \pi_2 R(s,a_2)
|
||||
\\
|
||||
&=\sum_{a \in A(s)} \pi(a|s)R(s,a)
|
||||
\end{aligned}
|
||||
\tag{8.3.5}
|
||||
$$
|
||||
|
||||
其中 $A$ 是动作集合(或称为动作空间),在本例中只有**射击红色气球**和**射击蓝色气球**两种动作选择。
|
||||
|
||||
注意,这个奖励函数也不是凭空产生的,而是在 $R(s,a)$ 的基础上的定义,相当于是面向状态的奖励。
|
||||
|
||||
如果针对图 8.3.6,把式 (8.3.4),(8.3.5) 串起来,
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
R(s) &= \sum_{a \in A(s)} \pi(a|s) R(s,a)
|
||||
\\
|
||||
&= \sum_{a \in A(s)} \pi(a|s) \sum_{s' \in S} p^a_{ss'} r^a_{ss'}
|
||||
\\
|
||||
&= \pi_1(p_1r_1+p_2r_2+p_3r_3) + \pi_2(p_4r_4+p_5r_5)
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
由于 $r \subset R(s,a) \subset R(s)$,**所以在实际的计算中,只能使用这个奖励模型中的三层的任一层,它们是互斥的,否则就会造成重复计算奖励。**
|
||||
@@ -0,0 +1,118 @@
|
||||
## 8.4 贝尔曼期望方程
|
||||
|
||||
有了模型和奖励,就可以进一步地研究价值问题了。如同马尔可夫奖励过程中的状态价值函数一样,在马尔可夫奖励过程过程中,同样会有价值函数,而且概念进一步地深化和扩展了。
|
||||
|
||||
|
||||
### 8.4.1 奖励过程和决策过程的比较
|
||||
|
||||
我们首先用图 8.4.1 来比较一下两种过程,避免概念混淆。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-3.png">
|
||||
|
||||
图 8.4.1 左侧:马尔可夫奖励过程模型(贝尔曼方程);右侧:马尔可夫决策过程模型(贝尔曼期望方程)。
|
||||
</center>
|
||||
|
||||
表 8.4.1 比较图 8.4.1 中的左右两部分
|
||||
|
||||
||左侧|右侧|
|
||||
|-|-|-|
|
||||
|名称|马尔科夫奖励过程 MRP|马尔可夫决策过程 MDP
|
||||
|模型|两层节点,一层过程|三层节点,两层过程|
|
||||
|上层节点|源状态|源状态|
|
||||
|中层节点|无|动作节点|
|
||||
|下层节点|目标状态|目标状态|
|
||||
|过程|实线箭头为状态转移 $P_{ss'}$ 以及过程奖励 $R_{ss'}$|实线箭头为策略选择$\pi(a \mid s)$,虚线箭头为状态转移$P^a_{ss'}$以及过程奖励$R^a_{ss'}$|
|
||||
|解法|贝尔曼方程|贝尔曼期望方程|
|
||||
|状态价值函数定义| $ v(s)= \mathbb E [G_t \mid S_t=s]$ | $v_\pi(s,a)=\mathbb E[G_t \mid S_t=s]$|
|
||||
|动作价值函数定义|无|$q_\pi(s,a)=\mathbb E[G_t \mid S_t=s,A_t=a]$|
|
||||
|
||||
$v_\pi(s)$ 比 $v(s)$ 多了一个下标 $\pi$,是为了区分二者,没有实际的数学含义。
|
||||
|
||||
在有的资料中,给贝尔曼期望方程的 $\mathbb E$ 写作 $\mathbb E_\pi$,也是这个意思,没有实际的数学含义。
|
||||
|
||||
|
||||
表 8.4.2 虚线框内的部分的局部比较
|
||||
|
||||
||左侧|右侧|
|
||||
|-|-|-|
|
||||
|顶端节点|源状态 $s_0$,需要计算状态价值函数$v(s_0)$|源动作 $a_1$,需要计算动作价值函数$q_{\pi}(s_0,a_1)$|
|
||||
|中间过程|状态转移概率$P_{ss'}$,过程奖励向量$R_{ss'}$|状态转移概率$P_{ss'}^{a_1}$,过程奖励向量$R_{ss'}^{a_1}$|
|
||||
|底端节点|下游状态$s_1,s_2,s_3$,假设已知状态价值函数$v(s')$|下游状态$s_1,s_2,s_3$,假设已知状态价值函数$v_{\pi}(s')$|
|
||||
|
||||
比较图 8.4.1 中左侧和右侧虚线框内的部分,可以说除了符号不同,其它都是相同的,包括位置和含义。
|
||||
|
||||
### 8.4.2 动作价值函数 $q_\pi$
|
||||
|
||||
先回忆一下在马尔可夫奖励过程中学习过的状态价值函数,温故而知新。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
v(s) &= \mathbb E [G_t \mid S_t = s]
|
||||
\\
|
||||
&=\mathbb E [R_{t+1}\mid S_t=s] + \gamma \mathbb E[G_{t+1}\mid S_t=s]
|
||||
\\
|
||||
&=\sum_{s'} p_{ss'} r_{ss'}+ \gamma \sum_{s'} p_{ss'}v(s') =\sum_{s'} p_{ss'} [r_{ss'}+\gamma v(s')]
|
||||
\\
|
||||
&= P_{ss'} R_{ss'} + \gamma P_{ss'} V(s')
|
||||
\\
|
||||
&= R(s)+ \gamma P_{ss'}V(s')
|
||||
\end{aligned}
|
||||
\tag{8.4.1}
|
||||
$$
|
||||
|
||||
观察贝尔曼方程的推导,其本质是:某个状态的价值函数 $v(s)$ 由三部分组成:
|
||||
1. 其下游状态的价值 $v(s')$;
|
||||
2. 转移概率 $p$;
|
||||
2. 转移过程中的奖励 $r$。
|
||||
|
||||
无巧不成书,在下面推导动作价值函数 $q_\pi$ 的公式时,我们也遇到了和式 8.4.1 同样的表达。所以,我们可以大胆地预测,计算动作价值函数 $q_\pi(s,a)$ 的公式与计算状态价值函数 $v(s)$ 的贝尔曼方程完全一致。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
q_\pi(s,a) &= \mathbb E [G_t \mid S_t = s, A_t=a]
|
||||
\\
|
||||
&=\mathbb E [R_{t+1}\mid S_t=s, A_t=a] + \gamma \mathbb E[G_{t+1}\mid S_t=s, A_t=a]
|
||||
\\
|
||||
&=\sum_{s'} p_{ss'}^a r_{ss'}^a+ \gamma \sum_{s'} p_{ss'}^a v_\pi(s') =\sum_{s'} p_{ss'}^a [r_{ss'}^a+\gamma v_\pi(s')] &(1)
|
||||
\\
|
||||
&=P^a_{ss'} R^a_{ss'} + \gamma P^a_{ss'} V_\pi(s')=P^a_{ss'}[R^a_{ss'}+\gamma V_\pi(s')] &(2)
|
||||
\\
|
||||
&= R^a(s)+ \gamma P_{ss'}^a V_\pi(s') &(3)
|
||||
\end{aligned}
|
||||
\tag{8.4.2}
|
||||
$$
|
||||
|
||||
|
||||
比较式 8.4.1 和式 8.4.2,对于本章的动作价值函数来说,除了在条件部分多出来一个 $A_t=a$ 以外,其它的部分完全相同,所以它们的表达式也应该相同,但是含义不同,前者是简单的状态价值函数,后者是有策略下的动作价值函数。
|
||||
|
||||
那么多出来的这个 $A_t=a$ 会造成什么不同吗?答案是不会。因为这个条件相当于在图 8.4.1 中右侧的部分确定了是选择 $a_1$ 还是 $a_2$,正是因为有了这个条件存在,才会让黄色虚线框部分的模型结构高度相似。
|
||||
|
||||
|
||||
### 8.4.3 状态价值函数 $v_\pi$
|
||||
|
||||
函数名称定义为 $v_\pi$ 的原因是为了和马尔可夫过程的状态价值函数 $v$ 区分开来,其定义是:
|
||||
|
||||
$$
|
||||
v_\pi(s) = \mathbb E [G_t \mid S_t=s] \tag{8.4.3}
|
||||
$$
|
||||
|
||||
同前面一样,式 8.4.3 仍然是要求回报 $G_t$ 的数学期望。在图 8.4.1 的右侧,我们考虑以 $v_\pi(s_0)$ 为例推出通用的价值函数公式。
|
||||
|
||||
状态 $s_0$ 并不直接接触到奖励机制,而是通过策略 $\pi$ 与下游的两个动作 $a_1,a_2$ 连接,所以,一旦知道了 $a_1,a_2$ 的动作价值函数 $q_\pi$,那么 $s_0$ 的状态价值函数就可以表示为 $q_\pi$ 的期望了,即:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
v_\pi(s_0) &=\pi_1 q_\pi(s_0,a_1) + \pi_2 q_\pi(s_0,a_2)
|
||||
\\
|
||||
&=\sum_{a \in A(s)} \pi(a \mid s_0) q_\pi(s_0,a)
|
||||
\end{aligned}
|
||||
\tag{8.4.4}
|
||||
$$
|
||||
|
||||
所以,式 8.4.3 可以引申为:
|
||||
|
||||
$$
|
||||
v_\pi(s) = \mathbb E [G_t \mid S_t=s] = \sum_{a \in A(s)} \pi(a|s)q_\pi(s,a)
|
||||
\tag{8.4.5}
|
||||
$$
|
||||
@@ -0,0 +1,204 @@
|
||||
|
||||
## 8.5 实例演算
|
||||
|
||||
下面我们通过对图 8.2.2 的全模型图的实例化计算,来解释贝尔曼期望方程中的动作价值函数即状态价值函数的含义。
|
||||
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-7.png">
|
||||
|
||||
来自图 8.2.2 带有动作节点的全新模型
|
||||
</center>
|
||||
|
||||
|
||||
因为上层节点依赖下层节点,为了简化问题,我们要从下向上反向推演。
|
||||
|
||||
有一些特别的说明:
|
||||
|
||||
- 在本例中我们设 $\gamma=1$ 以简化计算过程。
|
||||
- 空心圆状态序号 $s_n, \ n \in [0,6]$。
|
||||
- 实心圆动作序号 $a_m, \ m \in [0,11]$。
|
||||
|
||||
### 8.5.1 处于第五层的状态价值函数
|
||||
|
||||
我们要说明 $v_\pi(s_6)=v_T=0$。
|
||||
|
||||
在 8.2.3 节中,我们曾经提出了一个疑问,还没有给予解答:即,在第二次射击的动作(如“红(2)”)下面,是要接三个不同的状态呢,还是接一个终止节点?
|
||||
|
||||
我们以动作“红(2)”下面的状态转移为例说明这个问题。
|
||||
|
||||
如图 8.5.1,给出了上下两种终点设计方法。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-end.png">
|
||||
|
||||
图 8.5.1 两种设计终点状态的方法
|
||||
</center>
|
||||
|
||||
#### 第一种方法
|
||||
|
||||
图 8.5.1 的右图,动作“红(2)”直接终点状态 T。这样的话,按照定义,终止状态的价值函数值必有 $v(T)=0$。
|
||||
|
||||
有的读者可能会有疑问:那么 $[0,1,3]$ 的奖励给了谁?会不会就被丢掉了呢?
|
||||
|
||||
|
||||
按式 8.4.2 的 (1) 号子式,我们可以计算一下动作“红(2)”的价值函数。
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
q_\pi(s,a)&=\sum_{s'} p_{ss'}^a [r_{ss'}^a+\gamma v_\pi(s')]
|
||||
\\
|
||||
&=0.8\times(0+v(T))+0.05\times(1+v(T))+0.15\times(3+v(T))
|
||||
\\
|
||||
&=0.5
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
在从动作“红(2)”到状态 $T$ 的转移过程中,得到了奖励 $[0,1,3]$,这个奖励是计算到“红(2)”的动作价值函数上的,所有并不会丢掉。
|
||||
|
||||
#### 第二种方法
|
||||
|
||||
图 8.5.1 中的左图,“红(2)”下面新增加了 3 个状态,以 $s_8$ 为例。
|
||||
|
||||
由于 $s_8$ 在本例中的含义是中大奖,奖励值为 3,所以很容易**误认为** $v_\pi(s_8) = 3$。但正如第一种方法所示,这个奖励计算在动作“红(1)”的动作价值函数上,而不是计算在 $s_8$ 的状态价值函数上。
|
||||
|
||||
如果不认为 $s_8$ 为终止状态,可以在其后增加一个动作,叫做“结束E”,执行此动作的策略为 1,也就是 $s_8$ 只有这一个下游动作,并且这个动作以 1 的概率转移到另外定义的终止状态 T,奖励为 $r_T=0$。
|
||||
|
||||
那么反向计算过程为:
|
||||
1. 终止状态的 $v(T)=0$,这是必须的。
|
||||
2. 按式 8.4.2,$q_\pi(s_8,E)=P^a_{ss'} R^a_{ss'} + \gamma P^a_{ss'} V_\pi(s')=1 \times r_T + 1\times 1 \times v(T)=0$。
|
||||
3. 按式 8.4.5,$v_\pi(s_8) = \sum_a \pi(a|s)q_\pi(s,a)=1 \times q_\pi(s_8,E)=0$
|
||||
|
||||
所以 $v_\pi(s_8)=0$,相当于是终止状态。同理可得 $v_\pi(s_6),v_\pi(s_7)$ 的值都是 0,如此一来,计算“红(2)”的动作价值函数时的结果将与方法一相等。因此,单独增加 3 个新状态 $s_6,s_7,s_8$ 并没有多少帮助,和终止节点的作用一样。
|
||||
|
||||
### 8.5.2 处于第四层的动作价值函数
|
||||
|
||||
即“红(2)”(命名为$a_2$)到“蓝(11)”(命名为$a_{11}$)这 10 个动作。
|
||||
|
||||
由式 8.4.2 可知:$q_\pi(s,a) = P^a_{ss'} R^a_{ss'} + \gamma P^a_{ss'} V_\pi(s')$,这是矩阵形式,便于书写,节省篇幅,读者可以自行用最原始的子式 1 来佐证。
|
||||
|
||||
由于在上面一步中,已经 $v_T=0$,所以式 8.4.2 的后半部分 $V(s')$ 都是 0,因此可以轻易解得这 10 个动作的价值函数为:
|
||||
|
||||
$$
|
||||
\begin{cases}
|
||||
q_\pi(s_1,a_2) = (0.8,0.05,0.15) \cdot (0,1,3)^T=0.5
|
||||
\\
|
||||
q_\pi(s_1,a_3) = (0.4,0.6) \cdot (0,1)^T=0.6
|
||||
\\
|
||||
q_\pi(s_2,a_4) = (0.78,0.05,0.17) \cdot (0,1,3)^T=0.56
|
||||
\\
|
||||
q_\pi(s_2,a_5) = (0.45,0.55) \cdot (0,1)^T=0.55
|
||||
\\
|
||||
q_\pi(s_3,a_6) = (0.70,0.05,0.25) \cdot (0,1,3)^T=0.8
|
||||
\\
|
||||
q_\pi(s_3,a_7) = (0.2,0.8) \cdot (0,1)^T=0.8
|
||||
\\
|
||||
q_\pi(s_4,a_8) = (0.8,0.05,0.15) \cdot (0,1,3)^T=0.5
|
||||
\\
|
||||
q_\pi(s_4,a_{9}) = (0.4,0.6) \cdot (0,1)^T=0.6
|
||||
\\
|
||||
q_\pi(s_5,a_{10}) = (0.74,0.04,0.22) \cdot (0,1,3)^T=0.7
|
||||
\\
|
||||
q_\pi(s_5,a_{11}) = (0.25,0.75) \cdot (0,1)^T=0.75
|
||||
\end{cases}
|
||||
\tag{6}
|
||||
$$
|
||||
|
||||
### 8.5.3 处于第三层的状态价值函数
|
||||
|
||||
即 $s_1$ 到 $s_5$ 这五个状态。
|
||||
|
||||
由式 5 知:$v_\pi(s) = \sum_a \pi(a|s)q_\pi(s,a)$,所以有:
|
||||
|
||||
$$
|
||||
\begin{cases}
|
||||
v_\pi(s_1)=0.4 q_\pi(s_1,a_2)+0.6 q_\pi(s_1,a_3)=0.4\times 0.5+0.6 \times 0.6=0.56
|
||||
\\
|
||||
v_\pi(s_2)=0.4 q_\pi(s_2,a_4)+0.6 q_\pi(s_2,a_5)=0.4\times 0.56+0.6 \times 0.55=0.554
|
||||
\\
|
||||
v_\pi(s_3)=0.4 q_\pi(s_3,a_6)+0.6 q_\pi(s_3,a_7)=0.4\times 0.8+0.6 \times 0.8=0.8
|
||||
\\
|
||||
v_\pi(s_4)=0.4 q_\pi(s_4,a_8)+0.6 q_\pi(s_4,a_{9})=0.4\times 0.5+0.6 \times 0.6=0.56
|
||||
\\
|
||||
v_\pi(s_5)=0.4 q_\pi(s_5,a_{10})+0.6 q_\pi(s_5,a_{11})=0.4\times 0.7+0.6 \times 0.75=0.73
|
||||
\end{cases}
|
||||
\tag{7}
|
||||
$$
|
||||
|
||||
### 8.5.4 处于第二层的动作价值函数
|
||||
|
||||
只有 $a_0$ 和 $a_1$ 两个动作,这次与第四层的情况不一样,处于下游的 $v_\pi$ 都不是 0,所以用式 8.4.2 比较方便:$q_\pi(s,a)=P^a_{ss'}[R^a_{ss'}+\gamma V_\pi(s')]$。
|
||||
|
||||
其中:
|
||||
|
||||
- 对于 $q_\pi(s_0,a_0)$ 来说
|
||||
- $P^a_{ss'}=[0.8,0.05,0.15]$
|
||||
- $R^a_{ss'}=[0,1,3]$
|
||||
- $V_\pi(s')=[v_\pi(s_1),v_\pi(s_2),v_\pi(s_3)]$
|
||||
- 对于 $q_\pi(s_0,a_1)$ 来说
|
||||
- $P^a_{ss'}=[0.4,0.6]$
|
||||
- $R^a_{ss'}=[0,1]$
|
||||
- $V_\pi(s')=[v_\pi(s_4),v_\pi(s_5)]$
|
||||
|
||||
|
||||
$$
|
||||
\begin{cases}
|
||||
q_\pi(s_0,a_0)=(0.8,0.05,0.15) \cdot [(0,1,3)+(0.56,0.554,0.8)]^T=1.0957
|
||||
\\
|
||||
q_\pi(s_0,a_1)=(0.4,0.6) \cdot [(0,1)+(0.56,0.73)]^T=1.262
|
||||
\end{cases}
|
||||
\tag{8}
|
||||
$$
|
||||
|
||||
从式 8 的结果可得,选择射击蓝色气球的动作价值为 1.262,大于射击红色气球的值 1.0957。
|
||||
|
||||
|
||||
### 8.5.5 处于第一层的开始状态价值函数
|
||||
|
||||
$$
|
||||
v_\pi(s_0) = \sum_a \pi(a|s)q_\pi(s,a)=(0.4,0.6) \cdot (1.0957,1.262)^T = 1.19548
|
||||
\tag{9}
|
||||
$$
|
||||
|
||||
计算 $v_\pi$ 时可以用变量相乘再相加,也可以直接变成矩阵运算,原理一样。
|
||||
|
||||
|
||||
到了这里,有些读者可能会产生疑问:为什么在没有引入动作之前,计算的(马尔可夫奖励过程的)开始状态的价值函数为 -2.8276,但是在上面的计算中,马尔可夫奖励过程的状态价值函数为 1.19548 呢?不是说值不一样,而是一正一负!
|
||||
|
||||
原因是我们在本节中的马尔可夫奖励过程中,没有把最开始买子弹的 4 元钱纳入模型中。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-6.png">
|
||||
|
||||
图 8.5.2 设计开始状态
|
||||
</center>
|
||||
|
||||
如果我们像图 6 这样设计模型,那么在“选择”的状态价值是 1.19548,“买箭”的动作价值就等于 $1.19548-4 \approx -2.8$,开始状态的价值也是 -2.8,而且也不会影响后续状态的价值计算。
|
||||
|
||||
最终得到所有的价值函数值,标在图 8.5.3 中。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-result.png">
|
||||
|
||||
图 8.5.3 计算结果
|
||||
</center>
|
||||
|
||||
观察图 8.5.3,读者可以做同层节点的值大小的比较,再结合其实际含义找出原因。比如:
|
||||
|
||||
从动作节点中可以看到:
|
||||
|
||||
- 第一次选择蓝色气球的价值是 1.262,比选择红色气球的动作 1.096 要大,因为从统计上看,蓝色气球更容易打中,且更多的游客选择了它。
|
||||
- 一旦在第一次选择了蓝色,无论结果如何,则后续的过程中再次选择蓝色的动作价值较大。
|
||||
- 第四层最中间的红色(6)和蓝色(7)动作节点的值最高(0.8),因为它是在第一枪是射中了红色气球后,第二枪再选择任何气球,中奖的机会都比较大。
|
||||
- 最低的是第四层中间的红色(2)节点(0.5),因为是第一次脱靶后“贼心不死”,还要继续打红球。
|
||||
|
||||
从状态节点中可以看到:
|
||||
|
||||
- 第五层的状态值$v_T=0$,因为它们属于终止状态,达到这些节点而得到的奖励都归属于它们的上级节点。
|
||||
- 第三层中间的大奖的状态值在本层的五个状态中最高(0.80),原因是它下方的两个动作节点的值(0.8)很高。
|
||||
- 最有趣的是第三层左侧“脱靶”的状态值为 0.56,比“小奖”的状态值 0.55 要大,这是为什么呢?按理说“脱靶”应该更糟糕才对。
|
||||
- 因为它们的上游动作是选择红色气球,射击时很容易脱靶,如果在射击红色气球时却打中了蓝色气球中了小奖,那只能说明歪得离谱(哈哈),在实际中不能给这种“运气”以更高的估值。
|
||||
|
||||
### 思考与练习
|
||||
|
||||
1. 虽然 $v_\pi$ 和 $q_\pi$ 可以相互换算,但是和先有鸡还是先有蛋的问题不一样。那么在一个马尔科夫决策过程中,$v_\pi$ 和 $q_\pi$ 谁先出现呢?谁又是链条的最后一个呢?
|
||||
@@ -0,0 +1,346 @@
|
||||
## 8.6 算法实现
|
||||
|
||||
上一节中利用终止状态价值函数为 0 的定义,以及该问题是一个有向无环图的特点,我们根据贝尔曼期望方程,手工反向演算出了各个状态的价值函数。但正如贝尔曼方程可以用迭代法来实现一样,贝尔曼期望方程也可以用迭代法来实现。
|
||||
|
||||
### 8.6.1 模型部分
|
||||
|
||||
【代码位置】Shoot_2_DataModel.py
|
||||
|
||||
#### 定义状态
|
||||
|
||||
```Python
|
||||
# 状态空间
|
||||
class States(Enum):
|
||||
Start = 0 # 开始
|
||||
S_Red_R0 = 1 # 选择射击红球Red,但是脱靶R=0
|
||||
S_Red_R1 = 2 # 选择射击红球Red,但是误中蓝球R=1小奖
|
||||
S_Red_R3 = 3 # 选择射击红球Red,击中R=3大奖
|
||||
S_Blue_R0 = 4 # 选择射击蓝球Blue,但是脱靶R=0
|
||||
S_Blue_R1 = 5 # 选择射击蓝球Blue,击中R=1小奖
|
||||
T = 6 # 终止
|
||||
```
|
||||
按图 8.2.2,一共 7 个状态,序号和图中一致。
|
||||
|
||||
#### 定义动作
|
||||
```Python
|
||||
# 动作空间
|
||||
class Actions(Enum):
|
||||
Red = 0 # 射击红色小气球
|
||||
Blue = 1 # 射击蓝色大气球
|
||||
```
|
||||
在每个状态上有两个动作。
|
||||
|
||||
#### 定义奖励
|
||||
```Python
|
||||
# 奖励
|
||||
class Rewards(Enum):
|
||||
Zero = 0 # 脱靶,无奖
|
||||
Small = 1 # 小奖,价值 1 元
|
||||
Grand = 3 # 大奖,价值 3 元
|
||||
```
|
||||
|
||||
#### 定义状态转移字典
|
||||
|
||||
```Python
|
||||
P = {
|
||||
# state: {action: [(p, s', r),...]}
|
||||
States.Start.value:{ # S0开始状态(第一轮)
|
||||
Actions.Red.value:[ # 选择射击红球
|
||||
(0.80, States.S_Red_R0.value, Rewards.Zero.value ), # 脱靶的概率:0.80, 转移到状态S1, 0分奖励
|
||||
(0.05, States.S_Red_R1.value, Rewards.Small.value), # 误中蓝球的概率:0.05,转移到状态S2, 1分奖励
|
||||
(0.15, States.S_Red_R3.value, Rewards.Grand.value), # 击中红球的概率:0.15,转移到状态S3, 3分奖励
|
||||
],
|
||||
Actions.Blue.value:[ # 选择射击蓝球
|
||||
(0.40, States.S_Blue_R0.value, Rewards.Zero.value), # 脱靶的概率:0.40, 转移到状态 S4, 0分奖励
|
||||
(0.60, States.S_Blue_R1.value, Rewards.Small.value) # 击中蓝球的概率:0.6, 转移到状态 S5, 1分奖励
|
||||
]
|
||||
},
|
||||
# 以下为第二轮的选择
|
||||
States.S_Red_R0.value:{ # S1状态(第一轮打红球脱靶)
|
||||
... # 省略以节省篇幅
|
||||
},
|
||||
States.S_Red_R1.value:{ # S2状态(第一轮打红球误中蓝球,小奖)
|
||||
... # 省略以节省篇幅
|
||||
},
|
||||
States.S_Red_R3.value:{ # S3状态(第一轮打红球击中,大奖)
|
||||
... # 省略以节省篇幅
|
||||
},
|
||||
States.S_Blue_R0.value:{ # S4状态(第一轮打蓝脱靶)
|
||||
... # 省略以节省篇幅
|
||||
},
|
||||
States.S_Blue_R1.value:{ # S4状态(第一轮打蓝球击中,小奖)
|
||||
... # 省略以节省篇幅
|
||||
},
|
||||
}
|
||||
```
|
||||
这是一个两级的字典,数据结构比较复杂,是为了在后面的计算中,可以用 $O(1)$ 的效率查到所需的数据。
|
||||
|
||||
- 第一级,以状态为 Key,在 Value 中定义了动作空间(在此状态下都有哪些可以选择的动作)。
|
||||
|
||||
比如在 States.Start.value 状态(即 $s_0$)下面,定义了 Actions.Red.value 和 Actions.Blue.value 两个动作,分别表示射击红球和蓝球。
|
||||
|
||||
- 第二级,以动作为 Key,在 Value 中定义了此动作下的转移概率 P、下游状态 S、奖励 R,所以简称为 p_s_r。
|
||||
|
||||
比如在 Actions.Blue.value 动作下面(即图 8.2.2 中的“蓝(1)”),定义了两个下游状态:
|
||||
- 以 0.4 的概率,转移到 $s_4$ 状态,奖励值 0。
|
||||
- 以 0.6 的概率,转移到 $s_5$ 状态,奖励值 1。
|
||||
|
||||
### 8.6.2 迭代算法部分
|
||||
|
||||
---
|
||||
|
||||
定义误差 $\varepsilon$
|
||||
输入策略 $\pi(a|s)$
|
||||
初始化 $V_\pi(s),Q_\pi(s,a) \leftarrow 0$
|
||||
循环:
|
||||
保存备份以检查收敛性 $V_{old}(s) \leftarrow V_\pi(s)$
|
||||
对每一个非终止状态的 $s \in S$:
|
||||
获得动作空间 $A(s)$
|
||||
对于每个动作 $a \in A(s)$:
|
||||
获得转移概率 $p$, 下游状态 $s'$,奖励 $r$
|
||||
计算 $q_\pi(s,a)=\sum_{s'} p \big [r+\gamma v_\pi(s') \big ]$
|
||||
存放到数组 $Q_\pi(s,a) \leftarrow q_\pi(s,a)$
|
||||
计算 $v_\pi(s) = \sum_{a \in A(s)} \pi(a|s)q_\pi(s,a)$
|
||||
存放到数组 $V_\pi(s) \leftarrow v_\pi(s)$
|
||||
检查收敛性, 如果 $\max(|V_{old} - V_\pi|) < \varepsilon$ 则退出循环
|
||||
返回 $V_\pi(s),Q_\pi(s,a)$
|
||||
|
||||
---
|
||||
|
||||
几点说明:
|
||||
|
||||
- 在算法中,用小写的 $v_\pi,q_\pi$ 表示单个的价值函数,用大写的 $V_\pi,Q_\pi$ 表示数组,存放单个的数值。根据算法特点,可以把计算 $q_\pi$ 的函数独立出来,然后再完成计算 $v_\pi$ 的函数,最后再完成迭代算法函数。
|
||||
- 误差 $\varepsilon$,可以设置为 1e-4。
|
||||
- 策略是输入参数,在 8.6.4 节的主控代码中输入,形式为每个状态下的动作的概率。
|
||||
- 理论上 $V_\pi(s)$ 可以初始化为任何值,因为迭代总会收敛。但是考虑到终止状态必须为 0,所以一般会初始化为全 0 的数组。
|
||||
- $Q_\pi(s)$ 是直接填充的,不需要参与迭代,所以可以初始化为任何数值,全 0 也可以。
|
||||
|
||||
#### 计算单个 $q_\pi$
|
||||
|
||||
根据式 8.4.2 实现计算指定 $q_\pi(s,a)$ 的函数:
|
||||
|
||||
$$
|
||||
q_\pi(s,a)=\sum_{s'} p_{ss'}^a \big [r_{ss'}^a+\gamma v_\pi(s') \big ] \tag{由8.4.2}
|
||||
$$
|
||||
|
||||
【代码位置】Algo_PolicyValueFunction.py
|
||||
|
||||
```Python
|
||||
# 式 (8.4.2) 计算 q_pi
|
||||
def q_pi(p_s_r, gamma, V):
|
||||
q = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in p_s_r: # s_next 即 s'
|
||||
# math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q += p * (r + gamma * V[s_next])
|
||||
return q
|
||||
```
|
||||
代码实现很直接,对照着公式做一个循环加法即可。
|
||||
|
||||
需要的输入参数有三个:
|
||||
|
||||
- p_s_r:是转移概率 P、下游状态 S、奖励 R 的缩写,是一种数据结构定义,在前面有说明。
|
||||
- gamma:折扣值。
|
||||
- V:状态价值函数数组。
|
||||
|
||||
返回值:指定状态 s 和动作 a 的动作价值函数 $q_\pi(s,a)$。
|
||||
|
||||
有的读者可能有疑问:这个函数里面没有体现出 s 和 a 呀,在哪里指定的呢?答案是在这个函数的调用者中指定的 (s,a),有上下文关系,而这个函数本身不需要管上下文,只需要用给定的参数计算 q 值即可。
|
||||
|
||||
|
||||
#### 计算单个 $v_\pi$
|
||||
|
||||
根据式 8.4.5 从 $q_\pi$ 计算 $v_\pi$:
|
||||
|
||||
$$
|
||||
v_\pi(s) = \sum_{a \in A(s)} \pi(a|s)q_\pi(s,a)
|
||||
\tag{由8.4.5}
|
||||
$$
|
||||
|
||||
```Python
|
||||
# 式 (8.4.5) 计算 v_pi
|
||||
def v_pi(policy, s, actions, gamma, V, Q):
|
||||
v = 0
|
||||
for a, p_s_r in actions: # 遍历每个动作以计算q值,进而计算v值
|
||||
q = q_pi(p_s_r, gamma, V)
|
||||
# math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
v += policy[s][a] * q
|
||||
# 顺便记录下q(s,a)值,不需要再单独计算一次
|
||||
Q[s,a] = q
|
||||
return v
|
||||
```
|
||||
|
||||
输入参数
|
||||
|
||||
- policy:即式 8.4.5 中的 $\pi(a|s)$,指定状态 s 下的采用动作 a 的概率。这是由调用者(智能体)指定的,不是环境本身的特性,初始化环境时代入。
|
||||
- s:指定状态
|
||||
- actions:指定状态下的动作空间。
|
||||
- gamma:折扣。
|
||||
- V:状态价值函数数组。
|
||||
- Q:动作价值函数数组。
|
||||
|
||||
输出值:指定状态的状态价值函数值 $v_\pi(s)$。
|
||||
|
||||
#### 用迭代法计算 $V_\pi,Q_\pi$
|
||||
|
||||
实现算法 8.6.3。
|
||||
|
||||
```python
|
||||
# 迭代法计算 v_pi
|
||||
def V_in_place_update(env, gamma, iteration):
|
||||
V = np.zeros(env.nS) # 初始化 V(s)
|
||||
Q = np.zeros((env.nS, env.nA)) # 初始化 Q(s,a)
|
||||
count = 0 # 计数器,用于衡量性能和避免无限循环
|
||||
# 迭代
|
||||
while (count < iteration):
|
||||
V_old = V.copy() # 保存上一次的值以便检查收敛性
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
if env.is_end(s): # 终止状态v=0
|
||||
continue
|
||||
actions = env.get_actions(s) # 获得当前状态s下的所有可选动作
|
||||
V[s] = v_pi(env.Policy, s, actions, gamma, V, Q)
|
||||
# 检查收敛性
|
||||
if abs(V-V_old).max() < 1e-4:
|
||||
break
|
||||
count += 1
|
||||
# end while
|
||||
print("迭代次数 = ",count)
|
||||
return V, Q
|
||||
```
|
||||
### 8.6.3 主代码
|
||||
|
||||
```Python
|
||||
import numpy as np
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import Algo_PolicyValueFunction as algo
|
||||
|
||||
if __name__=="__main__":
|
||||
max_iteration = 1000# 最大迭代次数
|
||||
gamma = 1 # 折扣
|
||||
Policy = { # 策略
|
||||
0:[0.4,0.6], # 在状态 0 时,选择射击红球的概率0.4,选择射击蓝球的概率0.6
|
||||
1:[0.4,0.6], # 在状态 1 时,同上
|
||||
2:[0.4,0.6],
|
||||
3:[0.4,0.6],
|
||||
4:[0.4,0.6],
|
||||
5:[0.4,0.6],
|
||||
6:[0.4,0.6] # 可以不定义,因为在终止状态没有动作
|
||||
}
|
||||
env = dataModel.Env(Policy) # 初始化环境,带入策略
|
||||
V, Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 迭代计算V,Q
|
||||
```
|
||||
策略由字典 Policy 指定,Key 是状态值(0-6),Value 是每个状态下选择两个动作的概率,本例统一为 [0.4,0.6]。可以给每个状态都指定不相同的动作选择概率。
|
||||
|
||||
运行结果:
|
||||
```
|
||||
迭代次数 = 2
|
||||
--------------
|
||||
状态函数:S0(Start): 1.19548
|
||||
动作函数:Red:1.0957 Blue:1.262
|
||||
--------------
|
||||
状态函数:S1(S_Red_R0): 0.56
|
||||
动作函数:Red:0.5 Blue:0.6
|
||||
--------------
|
||||
状态函数:S2(S_Red_R1): 0.554
|
||||
动作函数:Red:0.56 Blue:0.55
|
||||
--------------
|
||||
状态函数:S3(S_Red_R3): 0.8
|
||||
动作函数:Red:0.8 Blue:0.8
|
||||
--------------
|
||||
状态函数:S4(S_Blue_R0):0.56
|
||||
动作函数:Red:0.5 Blue:0.6
|
||||
--------------
|
||||
状态函数:S5(S_Blue_R1):0.73
|
||||
动作函数:Red:0.7 Blue:0.75
|
||||
--------------
|
||||
状态函数:S6(T): 0.0
|
||||
动作函数:Red:0.0 Blue:0.0
|
||||
--------------
|
||||
```
|
||||
由于问题简单,所以只迭代了 2 次就收敛了。
|
||||
|
||||
再对比图 8.5.3,结果与手工计算的完全一致,说明算法和代码是可信的。
|
||||
<center>
|
||||
<img src="./img/shoot-result.png">
|
||||
|
||||
由图 8.5.3 计算结果
|
||||
</center>
|
||||
|
||||
### 8.6.4 二级回溯
|
||||
|
||||
如果不想计算 $Q_\pi$,能不能直接计算出来 $V_\pi$ 呢?这一小节解决这个问题。
|
||||
|
||||
从前面的推导中,可以在已知 $q_\pi(s,a)$ 时计算出 $v_\pi(s)$,也可以已知 $v_\pi(s)$ 计算出 $q_\pi(s,a)$,这似乎变成了一个鸡生蛋蛋生鸡的死循环问题。能不能像贝尔曼方程那样,从$v_\pi(s')$ 计算出 $v_\pi(s)$ 来,从$q_\pi(s',a')$ 计算出 $q_\pi(s,a)$ 来呢?这样的话,我们就可以继续利用前面学过的迭代法或者矩阵法来方便地解出状态价值函数和动作价值函数了。
|
||||
|
||||
先绘制出两张二级回溯图,方便公式推导。如图 8.6.1 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/mdp-7.png">
|
||||
|
||||
图 8.6.1 $v_\pi$ 和 $q_\pi$ 的二级回溯图
|
||||
</center>
|
||||
|
||||
#### 获得 $v_\pi$ 的迭代表达式
|
||||
|
||||
先看左图,我们的目的是想从 $v_\pi(s')$ 得到 $v_\pi(s)$,中间隔着一个 $q_\pi(s,a)$。如果把 $q_\pi(s,a)$ 的表达式带入 $v_\pi(s)$ 的表达式,就可以达到消去 $q_\pi(s,a)$ 的目的了。
|
||||
|
||||
|
||||
于是可以把式 8.4.2 的 $q_\pi(s,a)$ 带入式 8.4.5:
|
||||
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
v_\pi(s) &= \sum_a \pi(a|s)q_\pi(s,a) &(式8.4.5)
|
||||
\\
|
||||
(代入式8.4.2替换 q_\pi \to)&=\sum_a \pi(a|s) \Big(\sum_{s'} p_{ss'}^a [r_{ss'}^a+\gamma v_\pi(s')]\Big) &(1)
|
||||
\\
|
||||
(矩阵形式\to)&=\sum_a \pi(a|s) \Big(P^a_{ss'}[R^a_{ss'}+\gamma V_\pi(s')]\Big) &(2)
|
||||
\\
|
||||
(动作奖励函数形式\to)&=\sum_a \pi(a|s) \Big( R^a(s)+ \gamma P_{ss'}^a V_\pi(s') \Big) &(3)
|
||||
\end{aligned}
|
||||
\tag{8.6.1}
|
||||
$$
|
||||
|
||||
式 8.6.1 就是 $v_\pi$ 的迭代表达式。
|
||||
|
||||
#### 获得 $q_\pi$ 的迭代表达式
|
||||
|
||||
再看右图,想用 $q_\pi(s',a')$ 来表示 $q_\pi(s,a)$,需要把$v_\pi(s')$ 消掉。
|
||||
|
||||
从式 8.4.5 把 $s,a$ 换成 $s',a'$,可以得到式 8.6.2:
|
||||
|
||||
$$
|
||||
v_\pi(s') = \sum_{a'} \pi(a'|s')q_\pi(s',a')
|
||||
\tag{8.6.2}
|
||||
$$
|
||||
|
||||
再推导 $q_\pi$ 的表达式:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
q_\pi(s,a)&=\sum_{s'} p_{ss'}^a \big [r_{ss'}^a+\gamma v_\pi(s') \big ] &(由式8.4.2)
|
||||
\\
|
||||
(代入式8.6.2替换v_\pi\to)&=\sum_{s'} p_{ss'}^a \Big ( r_{ss'}^a+\gamma \sum_{a'} [\pi(a'|s')q_\pi(s',a') ] \Big) &(1)
|
||||
\\
|
||||
(动作奖励函数形式 \to)&=R^a(s)+\gamma \sum_{s'} p_{ss'}^a \Big ( \sum_{a'} [\pi(a'|s')q_\pi(s',a')] \Big) &(2)
|
||||
\end{aligned}
|
||||
\tag{8.6.3}
|
||||
$$
|
||||
|
||||
式 8.6.3 就是 $q_\pi$ 的迭代的表达形式。
|
||||
|
||||
#### 速查表
|
||||
|
||||
本节的公式有些多,下面总结一下,便于读者以后速查。
|
||||
|
||||
表 8.6.1 $v_\pi, q_\pi$ 的互换速查表
|
||||
|
||||
|=|$v_\pi$|$q_\pi$|
|
||||
|-|-|-|
|
||||
|$v_\pi$|$v_\pi(s)=\sum_a \pi(a \mid s) \Big(\sum_{s'} p_{ss'}^a [r_{ss'}^a+\gamma v_\pi(s')]\Big)$|$v_\pi(s) = \sum_{a} \pi(a \mid s)q_\pi(s,a)$|
|
||||
|$q_\pi$|$q_\pi(s,a)=\sum_{s'} p_{ss'}^a \big [r_{ss'}^a+\gamma v_\pi(s') \big ]$|$q_\pi(s,a)=\sum_{s'} p_{ss'}^a \Big ( r_{ss'}^a+\gamma \sum_{a'} [\pi(a'\mid s')q_\pi(s',a') ] \Big)$|
|
||||
|
||||
在表 8.6.1 中,我们只给出了公式的原始形式,读者可以在实际用使用矩阵形式或是奖励函数形式。
|
||||
|
||||
讲解二级回溯的方法,是为了和贝尔曼方程的迭代形式有个呼应。但是在实际的代码实现中,笔者建议使用独立计算 $q_\pi,v_\pi$ 的形式,以模块化代码,对于理解概念也有帮助,而不是用二级回溯的方法来实现。
|
||||
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,43 @@
|
||||
import numpy as np
|
||||
|
||||
# 式 (8.4.2) 计算 q_pi
|
||||
def q_pi(p_s_r, gamma, V):
|
||||
q = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in p_s_r:
|
||||
# math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q += p * (r + gamma * V[s_next])
|
||||
return q
|
||||
|
||||
# 式 (8.4.5) 计算 v_pi
|
||||
def v_pi(policy, s, actions, gamma, V, Q):
|
||||
v = 0
|
||||
for a, p_s_r in actions: # 遍历每个动作以计算q值,进而计算v值
|
||||
q = q_pi(p_s_r, gamma, V)
|
||||
# math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
v += policy[s][a] * q
|
||||
# 顺便记录下q(s,a)值,不需要再单独计算一次
|
||||
Q[s,a] = q
|
||||
return v
|
||||
|
||||
# 迭代法计算 v_pi
|
||||
def calculate_Vpi_Qpi(env, gamma, iteration):
|
||||
V = np.zeros(env.nS) # 初始化 V(s)
|
||||
Q = np.zeros((env.nS, env.nA)) # 初始化 Q(s,a)
|
||||
count = 0 # 计数器,用于衡量性能和避免无限循环
|
||||
# 迭代
|
||||
while (count < iteration):
|
||||
V_old = V.copy() # 保存上一次的值以便检查收敛性
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
if env.is_end(s): # 终止状态v=0
|
||||
continue
|
||||
actions = env.get_actions(s) # 获得当前状态s下的所有可选动作
|
||||
V[s] = v_pi(env.Policy, s, actions, gamma, V, Q)
|
||||
# 检查收敛性
|
||||
if abs(V-V_old).max() < 1e-4:
|
||||
break
|
||||
count += 1
|
||||
# end while
|
||||
print("迭代次数 = ",count)
|
||||
return V, Q
|
||||
@@ -0,0 +1,60 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
|
||||
# 奖励
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
R = [-4, 0, 1, 3]
|
||||
|
||||
P = np.array(
|
||||
[ # 0 1 2 3
|
||||
[0.00, 0.56, 0.38, 0.06], # 0
|
||||
[0.00, 0.56, 0.38, 0.06], # 1
|
||||
[0.00, 0.50, 0.42, 0.08], # 2
|
||||
[0.00, 0.40, 0.50, 0.10], # 3
|
||||
]
|
||||
)
|
||||
|
||||
# 状态
|
||||
class States(Enum):
|
||||
Start = 0
|
||||
Miss = 1
|
||||
Small = 2
|
||||
Grand = 3
|
||||
|
||||
class DataModel(object):
|
||||
def __init__(self):
|
||||
self.P = P # 状态转移矩阵
|
||||
self.R = R # 奖励
|
||||
self.S = States # 状态集
|
||||
self.N = len(self.S) # 状态数量
|
||||
|
||||
def SolveMatrix(dataModel, gamma):
|
||||
# 在对角矩阵上增加一个微小的值来解决奇异矩阵不可求逆的问题
|
||||
I = np.eye(dataModel.N) * (1+1e-7)
|
||||
#I = np.eye(dataModel.N)
|
||||
factor = I - gamma * dataModel.P
|
||||
inv_factor = np.linalg.inv(factor)
|
||||
vs = np.dot(inv_factor, dataModel.R)
|
||||
return vs
|
||||
|
||||
def check_convergence(dataModel, gamma=1):
|
||||
print("迭代100次, 检查状态转移矩阵是否趋近于 0: ")
|
||||
P_new = dataModel.P.copy()
|
||||
for i in range(100):
|
||||
P_new = np.dot(dataModel.P, P_new) * gamma
|
||||
print(np.around(P_new, 3))
|
||||
print("------------")
|
||||
|
||||
if __name__=="__main__":
|
||||
dataModel = DataModel()
|
||||
gammas = [1,0.9]
|
||||
for gamma in gammas:
|
||||
print("折扣 =", gamma)
|
||||
check_convergence(dataModel, gamma)
|
||||
V = SolveMatrix(dataModel, gamma)
|
||||
vv = np.around(V,3)
|
||||
print("价值函数:")
|
||||
for s in dataModel.S:
|
||||
print(str.format("{0}:\t{1}", s.name, vv[s.value]))
|
||||
print("------------")
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
|
||||
N_States = 13
|
||||
|
||||
# 奖励
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
R = [-4, 0, 1, 3, 0, 1, 3, 0, 1, 3, 0, 1, 3]
|
||||
|
||||
P = np.array(
|
||||
[ # 0 1 2 3 4 5 6 7 8 9 10 11 12
|
||||
[0.00, 0.56, 0.38, 0.06, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 0
|
||||
[0.00, 0.00, 0.00, 0.00, 0.56, 0.38, 0.06, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 1
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.50, 0.42, 0.08, 0.00, 0.00, 0.00], # 2
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.40, 0.50, 0.10], # 3
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 4
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 5
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 6
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 7
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 8
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 9
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 10
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 11
|
||||
[0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00], # 12
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def SolveMatrix(gamma):
|
||||
# 在对角矩阵上增加一个微小的值来解决奇异矩阵不可求逆的问题
|
||||
#I = np.eye(dataModel.N) * (1+1e-7)
|
||||
I = np.eye(N_States)
|
||||
factor = I - gamma * P
|
||||
inv_factor = np.linalg.inv(factor)
|
||||
vs = np.dot(inv_factor, R)
|
||||
return vs
|
||||
|
||||
if __name__=="__main__":
|
||||
v = SolveMatrix(1.0)
|
||||
print("状态价值函数:")
|
||||
print("v0 =", v[0])
|
||||
print(str.format("v1={0},\tv2={1},\tv3={2}", v[1],v[2],v[3]))
|
||||
print(str.format("v4={0},\t\tv5={1},\t\tv6={2}", v[4],v[5],v[6]))
|
||||
print(str.format("v7={0},\t\tv8={1},\t\tv9={2}", v[7],v[8],v[9]))
|
||||
print(str.format("v10={0},\tv11={1},\tv12={2}", v[10],v[11],v[12]))
|
||||
@@ -0,0 +1,225 @@
|
||||
from enum import Enum
|
||||
|
||||
# 状态空间
|
||||
class States(Enum):
|
||||
Start = 0 # 开始
|
||||
S_Red_R0 = 1 # 选择射击红球Red,但是脱靶R=0
|
||||
S_Red_R1 = 2 # 选择射击红球Red,但是误中蓝球R=1小奖
|
||||
S_Red_R3 = 3 # 选择射击红球Red,击中R=3大奖
|
||||
S_Blue_R0 = 4 # 选择射击蓝球Blue,但是脱靶R=0
|
||||
S_Blue_R1 = 5 # 选择射击蓝球Blue,击中R=1小奖
|
||||
T = 6 # 终止
|
||||
|
||||
# 动作空间
|
||||
class Actions(Enum):
|
||||
Red = 0 # 射击红色小气球
|
||||
Blue = 1 # 射击蓝色大气球
|
||||
|
||||
# 奖励
|
||||
class Rewards(Enum):
|
||||
Zero = 0
|
||||
Small = 1
|
||||
Grand = 3
|
||||
|
||||
P = {
|
||||
# state: {action: [(p, s', r),...]}
|
||||
States.Start.value:{ # 开始状态(第一枪)
|
||||
Actions.Red.value:[ # 选择射击红球
|
||||
(0.80, States.S_Red_R0.value, Rewards.Zero.value ), # 脱靶的概率:0.80, 转移到状态 SR0(s1), 得到0分奖励
|
||||
(0.05, States.S_Red_R1.value, Rewards.Small.value), # 误中蓝球的概率:0.05, 转移到状态 SR1(s2), 得到1分奖励
|
||||
(0.15, States.S_Red_R3.value, Rewards.Grand.value), # 击中红球的概率:0.15, 转移到状态 SR3(s3), 得到3分奖励
|
||||
],
|
||||
Actions.Blue.value:[ # 选择射击蓝球
|
||||
(0.40, States.S_Blue_R0.value, Rewards.Zero.value), # 脱靶的概率:0.40, 转移到状态 SB0(s4), 得到0分奖励
|
||||
(0.60, States.S_Blue_R1.value, Rewards.Small.value) # 击中蓝球的概率:0.6, 转移到状态 SB1(s5), 得到1分奖励
|
||||
]
|
||||
},
|
||||
# 以下为第二枪的选择
|
||||
States.S_Red_R0.value:{ # 第一枪打红球脱靶
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.80, States.T.value, Rewards.Zero.value), # 第二枪脱靶概率
|
||||
(0.05, States.T.value, Rewards.Small.value), # 第二枪误中蓝球的概率
|
||||
(0.15, States.T.value, Rewards.Grand.value), # 第二枪击中红球的概率
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.40, States.T.value, Rewards.Zero.value), # 第二枪脱靶的概率不变
|
||||
(0.60, States.T.value, Rewards.Small.value) # 第二枪击中蓝球概率不变
|
||||
]
|
||||
},
|
||||
States.S_Red_R1.value:{ # 第一枪打红球误中蓝球
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.78, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.05, States.T.value, Rewards.Small.value), # 误中蓝色球
|
||||
(0.17, States.T.value, Rewards.Grand.value), # 击中红球率升高
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.45, States.T.value, Rewards.Zero.value), # 脱靶率升高
|
||||
(0.55, States.T.value, Rewards.Small.value) # 击中蓝球率降低
|
||||
]
|
||||
},
|
||||
States.S_Red_R3.value:{ # 第一枪打红球击中大奖
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.70, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.05, States.T.value, Rewards.Small.value), # 误中蓝色球
|
||||
(0.25, States.T.value, Rewards.Grand.value), # 击中红球率升高
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.20, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.80, States.T.value, Rewards.Small.value) # 击中蓝球率提高
|
||||
]
|
||||
},
|
||||
States.S_Blue_R0.value:{ # 第一枪打蓝脱靶
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.80, States.T.value, Rewards.Zero.value), # 脱靶率不变
|
||||
(0.05, States.T.value, Rewards.Small.value), # 误中蓝球
|
||||
(0.15, States.T.value, Rewards.Grand.value), # 击中红球率不变
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.40, States.T.value, Rewards.Zero.value), # 脱靶概率不变
|
||||
(0.60, States.T.value, Rewards.Small.value) # 击中蓝球率不变
|
||||
]
|
||||
},
|
||||
States.S_Blue_R1.value:{ # 第一枪打蓝球中小奖
|
||||
Actions.Red.value:[ # 选择射击红球
|
||||
(0.74, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.04, States.T.value, Rewards.Small.value), # 误中蓝球
|
||||
(0.22, States.T.value, Rewards.Grand.value), # 击中目标红球率升高
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.25, States.T.value, Rewards.Zero.value), # 脱靶率很低
|
||||
(0.75, States.T.value, Rewards.Small.value) # 击中蓝球率很高
|
||||
]
|
||||
},
|
||||
# States.T.value:{ # 终止
|
||||
# Actions.Red.value:[
|
||||
# (1.0, States.T.value, 0)
|
||||
# ],
|
||||
# Actions.Blue.value:[
|
||||
# (1.0, States.T.value, 0)
|
||||
# ]
|
||||
# }
|
||||
}
|
||||
|
||||
class Env(object):
|
||||
def __init__(self, policy):
|
||||
self.nS = len(States)
|
||||
self.nA = len(Actions)
|
||||
self.S = States
|
||||
self.A = Actions
|
||||
self.P = P
|
||||
self.Policy = policy
|
||||
self.end_states = [States.T.value]
|
||||
|
||||
def get_actions(self, s):
|
||||
actions = self.P[s]
|
||||
return actions.items()
|
||||
|
||||
def is_end(self,s):
|
||||
if s in self.end_states:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
Policy = {
|
||||
0:[0.4,0.6], # 在状态 0 时,选择红球的概率0.4,选择蓝球的概率0.6
|
||||
1:[0.4,0.6], # 在状态 1 时,同上
|
||||
2:[0.4,0.6],
|
||||
3:[0.4,0.6],
|
||||
4:[0.4,0.6],
|
||||
5:[0.4,0.6]
|
||||
}
|
||||
env = Env(Policy)
|
||||
# 统计概率
|
||||
# 第一枪脱靶的概率
|
||||
p0 = Policy[0][0] * P[States.Start.value][Actions.Red.value][0][0] \
|
||||
+ Policy[0][1] * P[States.Start.value][Actions.Blue.value][0][0]
|
||||
print("第一枪脱靶", p0)
|
||||
|
||||
# 第一枪脱靶后第二枪也脱靶
|
||||
p00 = Policy[0][0] * (Policy[States.S_Red_R0.value][0] * P[States.S_Red_R0.value][Actions.Red.value][0][0] \
|
||||
+ Policy[States.S_Red_R0.value][1] * P[States.S_Red_R0.value][Actions.Blue.value][0][0]) \
|
||||
+ Policy[0][1] * (0.4 * P[States.S_Blue_R0.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Blue_R0.value][Actions.Blue.value][0][0])
|
||||
# 第一枪脱靶后第二枪中小奖
|
||||
p01 = Policy[0][0] * (0.4 * P[States.S_Red_R0.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Red_R0.value][Actions.Blue.value][1][0]) \
|
||||
+ Policy[0][1] * (0.4 * P[States.S_Blue_R0.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Blue_R0.value][Actions.Blue.value][1][0])
|
||||
# 第一枪脱靶后第二枪中大奖
|
||||
p02 = Policy[0][0] * (0.4 * P[States.S_Red_R0.value][Actions.Red.value][2][0]) \
|
||||
+ Policy[0][1] * (0.4 * P[States.S_Blue_R0.value][Actions.Red.value][2][0])
|
||||
|
||||
p00 = round(p00,3)
|
||||
p01 = round(p01,3)
|
||||
p02 = round(p02,3)
|
||||
print("\t第二枪脱靶",p00)
|
||||
print("\t第二枪小奖",p01)
|
||||
print("\t第二枪大奖",p02)
|
||||
#assert((p00+p01+p02)==1)
|
||||
|
||||
########################################
|
||||
|
||||
# 第一枪中小奖的概率
|
||||
p1 = Policy[0][0] * P[States.Start.value][Actions.Red.value][1][0] \
|
||||
+ Policy[0][1] * P[States.Start.value][Actions.Blue.value][1][0]
|
||||
print("第一枪小奖", p1)
|
||||
|
||||
# 第一枪小奖后第二枪脱靶
|
||||
p10 = Policy[0][0] * (0.4 * P[States.S_Red_R1.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Red_R1.value][Actions.Blue.value][0][0]) \
|
||||
+ Policy[0][1] * (0.4 * P[States.S_Blue_R1.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Blue_R1.value][Actions.Blue.value][0][0])
|
||||
# 第一枪小奖后第二枪中小奖
|
||||
p11 = Policy[0][0] * (0.4 * P[States.S_Red_R1.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Red_R1.value][Actions.Blue.value][1][0]) \
|
||||
+ Policy[0][1] * (0.4 * P[States.S_Blue_R1.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Blue_R1.value][Actions.Blue.value][1][0])
|
||||
# 第一枪小奖后第二枪中大奖
|
||||
p12 = Policy[0][0] * (0.4 * P[States.S_Red_R1.value][Actions.Red.value][2][0]) \
|
||||
+ Policy[0][1] * (0.4 * P[States.S_Blue_R1.value][Actions.Red.value][2][0])
|
||||
p10 = round(p10,3)
|
||||
p11 = round(p11,3)
|
||||
p12 = round(p12,3)
|
||||
print("\t第二枪脱靶",p10)
|
||||
print("\t第二枪小奖",p11)
|
||||
print("\t第二枪大奖",p12)
|
||||
#assert((p10+p11+p12)==1)
|
||||
|
||||
########################################
|
||||
|
||||
# 第一枪中大奖的概率
|
||||
p2 = Policy[0][0] * P[States.Start.value][Actions.Red.value][2][0]
|
||||
p2 = round(p2,3)
|
||||
print("第一枪大奖",p2)
|
||||
|
||||
# 第一枪大奖后第二枪脱靶
|
||||
p20 = Policy[0][0] * P[States.S_Red_R3.value][Actions.Red.value][0][0] \
|
||||
+ Policy[0][1] * P[States.S_Red_R3.value][Actions.Blue.value][0][0]
|
||||
# 第一枪大奖后第二枪中小奖
|
||||
p21 = Policy[0][0] * P[States.S_Red_R3.value][Actions.Red.value][1][0] \
|
||||
+ Policy[0][1] * P[States.S_Red_R3.value][Actions.Blue.value][1][0]
|
||||
# 第一枪大奖后第二枪中大奖
|
||||
p22 = Policy[0][0] * P[States.S_Red_R3.value][Actions.Red.value][2][0]
|
||||
|
||||
p20 = round(p20,3)
|
||||
p21 = round(p21,3)
|
||||
p22 = round(p22,3)
|
||||
print("\t第二枪脱靶",p20)
|
||||
print("\t第二枪小奖",p21)
|
||||
print("\t第二枪大奖",p22)
|
||||
#assert((p20+p21+p22)==1)
|
||||
#assert((p0+p1+p2)==1)
|
||||
|
||||
assert(p00 == p0)
|
||||
assert(p01 == p1)
|
||||
assert(p02 == p2)
|
||||
assert(p10 <= p0)
|
||||
assert(p11 >= p1)
|
||||
assert(p12 >= p2)
|
||||
assert(p20 <= p0)
|
||||
assert(p21 >= p1)
|
||||
assert(p22 >= p2)
|
||||
assert(p21 >= p11)
|
||||
assert(p21 >= p21)
|
||||
@@ -0,0 +1,25 @@
|
||||
import numpy as np
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import Algo_PolicyValueFunction as algo
|
||||
|
||||
if __name__=="__main__":
|
||||
max_iteration = 1000# 最大迭代次数
|
||||
gamma = 1 # 折扣
|
||||
Policy = { # 策略
|
||||
0:[0.4,0.6], # 在状态 0 时,选择射击红球的概率0.4,选择射击蓝球的概率0.6
|
||||
1:[0.4,0.6], # 在状态 1 时,同上
|
||||
2:[0.4,0.6],
|
||||
3:[0.4,0.6],
|
||||
4:[0.4,0.6],
|
||||
5:[0.4,0.6],
|
||||
6:[0.4,0.6] # 可以不定义,因为在终止状态没有动作
|
||||
}
|
||||
env = dataModel.Env(Policy) # 初始化环境
|
||||
V, Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 迭代计算V,Q
|
||||
V = np.round(V,5)
|
||||
Q = np.round(Q,5)
|
||||
for i,s in enumerate(env.S):
|
||||
print(str.format("状态函数:S{0}({1}):\t{2}", i, s.name, V[s.value]))
|
||||
print(str.format("动作函数:{0}:{1}\t{2}:{3}", env.A.Red.name, Q[s.value,env.A.Red.value], env.A.Blue.name, Q[s.value,env.A.Blue.value]))
|
||||
print("--------------")
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
https://zhuanlan.zhihu.com/p/426285546
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
# 第 9 章
|
||||
|
||||
### 9.1.0 温故知新
|
||||
|
||||
在前面的射击问题中,有以下几个缺陷:
|
||||
|
||||
- 是一个单向选择和转移的过程,最后形成了一张有向无环图。这就决定了我们可以从后向前一步步**回溯**,自下向上逐层计算状态价值函数 $v_\pi(s)$ 和动作价值函数 $q_\pi(s,a)$,直到开始状态。其计算过程是:$v_T \to q_\pi \to v_\pi \to q_\pi \to v_\pi$,如图 8.5.3 所示。
|
||||
|
||||
- 每个分支的细节分析起来比较多,但实际上分支之间的区别只存在于状态转移的过程的具体概率数值上。比如图 8.5.3 中的“红(2)”动作后的状态转移概率是 [0.8,0.05.0.15],而动作“红(4)”的状态转移概率是 [0.78,0.05,0.17]。这种细节只是导致计算结果不同,但概念是相同的。
|
||||
|
||||
- 而另外一个重要的策略选择问题,我们暂时用“红:蓝=0.4:0.6”的固定概率,在两次射击时都是如此。但是在实际问题中,游客各有各的策略,这种统一的设定只是一种统计结果。游乐场老板虽然精明,但是游客们也不是傻子,谁都知道连续两次选择红色气球是有机会得到最高值 6 分的奖励的,只不过是能不能顺利实现的问题。
|
||||
|
||||
所以,本章中我们将会提出一个新的问题需要利用新的知识来解决,其特点正好可以弥补上述缺陷。
|
||||
@@ -0,0 +1,161 @@
|
||||
## 9.1 穿越虫洞问题
|
||||
|
||||
|
||||
### 9.1.1 提出问题
|
||||
|
||||
<center>
|
||||
<img src="./img/ship-1.png">
|
||||
|
||||
图 9.1.1 穿越虫洞问题
|
||||
</center>
|
||||
|
||||
问题描述
|
||||
|
||||
- 在一个 5x5 的宇宙空间中,一艘探索太空的宇宙飞船可以任意向四个方向行驶,策略 $\pi=0.25$。
|
||||
|
||||
- 比如在 $s_{18}$ 处,如果选择向右行驶,将会以 $p=1.0$ 的转移概率达到 19,并得到 0 的奖励。
|
||||
|
||||
- 如果在如 $s_{24}$ 所示的角落处向右行驶或向下行驶,将会碰撞能量屏障使飞船受损,飞船位置不发生改变,得到 -1 的“奖励”,但并非终止状态。其它角落处也是如此,一共有 4 个**角落状态**(序号为:0,4,20,24)。
|
||||
|
||||
- 如果在如 $s_9$ 所示的边界处向右行驶,将会碰撞能量屏障使飞船受损,得到 -1 的“奖励”,飞船位置不发生改变,但并非终止状态,还可以进一步行驶。其它边界处也是如此,一共有 12 个**边界状态**(序号为:1,2,3,5,9,10,14,15,19,21,22,23)。
|
||||
|
||||
- 在 $s_1$ 和 $s_3$ 处有两个**虫洞**:
|
||||
|
||||
- 在 $s_1$ 处进行下一步行驶时,无论任何方向,将无条件地达到 $s_{12}$ 处,并得到 +5 的奖励,但后者并非终止状态。
|
||||
|
||||
- 在 $s_3$ 处进行下一步行驶时,无论任何方向,将无条件地达到 $s_{21}$ 处,并得到 +10 的奖励,但后者并非终止状态。
|
||||
|
||||
所以序号为 1,3 的两个状态可以从边界状态中去掉。
|
||||
|
||||
### 9.1.2 问题特点
|
||||
|
||||
注意几点:
|
||||
|
||||
1. 没有终止状态,也就是说没有分幕,飞船可以一直行驶。
|
||||
|
||||
没有终点状态的话,我们无法确定任意一个状态的价值函数,进而算出其上游的动作价值函数。所幸在 8.6 节中,已经写好了计算 $v_\pi,q_\pi$ 的迭代算法了,直接调用即可,但是需要准备好环境。
|
||||
|
||||
2. 在每个状态(方格)下,可以随机选择 4 个方向中的任意一个移动,即动作空间为 4,动作为“上下左右”,策略概率为 0.25。
|
||||
3. 到达 $s_1,s_3$ 时,不是被立刻吸入虫洞,而是要进行下一步动作时才会时空转移。但是在 $s_1,s_3$ 并没有机会向上行驶而出界。
|
||||
4. 关于边角位置的状态,如图 9.1.2 所示,以状态 $s_2$ 为例:
|
||||
- 如果从该状态以 0.25 的概率选择向上移动,会以概率 1.0 回到 $s_2$,并有 -1 的奖励。所以说,这里面既有策略 $\pi$,又有转移概率 $p$,只不过只有一个下游状态,没有分支。
|
||||
- 如果从 $s_2$ 以 0.25 的概率选择向下移动,会以 1.0 的概率转移到 $s_7$,得到 0 的奖励。
|
||||
<center>
|
||||
<img src="./img/ship-2.png">
|
||||
|
||||
图 9.1.2 边界状态的动作和概率转移
|
||||
</center>
|
||||
|
||||
所以,这个穿越虫洞问题的动作空间是 4,动作发生后的转移概率是 1,即,动作发生后,可以准确地到达目标,不会发生意外。而上一章中的射击气球问题,动作空间是 2,动作发生后的状态转移大于 2(两个动作分别是 2 和 3)。
|
||||
|
||||
最主要的是,读者一定要把这一章的问题与前面章节中学习的马尔科夫奖励过程中的没有动作而直接发生状态转移的情况分开。
|
||||
|
||||
|
||||
### 9.1.3 模型数据定义
|
||||
|
||||
在强化学习中,经常会用图 9.1.1 这种方格(或长方格)来研究各种算法,因为这种方式可以有效地把一些连续问题转变为离散问题,从而使用马尔科夫链来简化并描述问题。所以有必要建立一个通用的模型,用数据驱动的方式来定义模型的各种行为。
|
||||
|
||||
模型定义可以分为四个小部分。
|
||||
|
||||
【代码位置】Wormhole_0_Data.py
|
||||
|
||||
#### 状态部分
|
||||
|
||||
```Python
|
||||
# 状态空间 = 空间宽度 x 空间高度
|
||||
GridWidth, GridHeight = 5, 5 # 可以是长方形,宽高不等
|
||||
# 起点,可以多个
|
||||
StartStates = []
|
||||
# 终点,可以多个
|
||||
EndStates = []
|
||||
```
|
||||
- 空间宽度 GridWidth 和高度 GridHeight
|
||||
|
||||
可以不相等,比如 3 X 4。
|
||||
|
||||
- 起点 StartStates
|
||||
|
||||
有些场景需要定义起点,比如迷宫游戏。
|
||||
|
||||
- 终止状态 EndStates
|
||||
|
||||
有些场景需要定义终止状态,到达此状态后算是分幕结束。
|
||||
|
||||
- 关于序号的约定
|
||||
|
||||
- 可以用从 0 开始的序号,方格的左上角序号为 0,然后向右依次加 1,右下角的序号为 GridWidth*GridHeight-1,比如 3x4-1=11。
|
||||
|
||||
- 也可以用 $(x,y)$ 的方式定义每个状态的位置,方格的左上角坐标为 (0,0),以 3x4 为例,右下角为 (2,3)。
|
||||
|
||||
#### 动作部分
|
||||
```Python
|
||||
# 动作空间
|
||||
LEFT, UP, RIGHT, DOWN = 0, 1, 2, 3
|
||||
Actions = [LEFT, UP, RIGHT, DOWN]
|
||||
# 初始策略
|
||||
Policy = [0.25, 0.25, 0.25, 0.25]
|
||||
# 状态转移概率: [SlipLeft, MoveFront, SlipRight, SlipBack]
|
||||
SlipProbs = [0.0, 1.0, 0.0, 0.0]
|
||||
```
|
||||
- 按中国人的习惯,定义左(LEFT)、上(UP)、右(RIGHT)、下(DOWN)顺时针顺序的四个方向。
|
||||
- 动作空间 Actions
|
||||
|
||||
由这上面四个动作组成。当然,在醉汉回家问题中,只有左、右两个动作。
|
||||
|
||||
- 初始策略 Policy
|
||||
|
||||
在 4 个方向上随机选择,概率为 0.25。
|
||||
|
||||
- 状态转移概率 SlipProbs
|
||||
|
||||
在动作执行后,是否会出现偏差。举例来说,在冰面向前行走,很有可能冰面太滑而造成向左 0.2、向右 0.1、向前 0.7 的状态转移概率,那么该值就可以写成 [0.2, 0.7, 0.1, 0.0]。注意顺序不能乱。
|
||||
|
||||
#### 奖励部分
|
||||
|
||||
```Python
|
||||
# 每走一步的奖励值,可以是0或者-1
|
||||
StepReward = 0
|
||||
# 特殊奖励 from s->s' then get r, 其中 s,s' 为状态序号,不是坐标位置
|
||||
SpecialReward = {
|
||||
(0,0):-1, # s0 -> s0 得到-1奖励
|
||||
(2,2):-1,
|
||||
......
|
||||
(1,12):+5,
|
||||
(3,21):+10
|
||||
}
|
||||
```
|
||||
- 每步奖励 StepReward
|
||||
|
||||
表示每走一步都可以有 -1 的奖励,或者 0。在本例中为 0,表示每次移动不减分。
|
||||
|
||||
- 特殊奖励 SpecialReward
|
||||
|
||||
比如本例中的碰撞边界得 -1,或者穿越虫洞得 +5 或 +10。这个字典不管智能体是如何从 $s$ 到达 $s'$ 的,也就是忽略了中间的动作选择和状态转移两个步骤,只看起始和终止状态。
|
||||
|
||||
以状态 12 为例,在字典中定义了“(1,12):+5”,表示从状态 1 到状态 12 可以得到 +5 的奖励。但是从 7,11,13,17 四个状态也可以到达 12,只能按照 StepReward 的定义获得 0 奖励。
|
||||
|
||||
#### 特殊移动
|
||||
|
||||
```Python
|
||||
# 特殊移动,用于处理类似虫洞场景
|
||||
SpecialMove = {
|
||||
(1,LEFT):12, # 从状态1执行向左的动作会到达状态12
|
||||
(1,UP):12,
|
||||
(1,RIGHT):12,
|
||||
(1,DOWN):12,
|
||||
(3,LEFT):20,
|
||||
(3,UP):21,
|
||||
(3,RIGHT):21,
|
||||
(3,DOWN):21
|
||||
}
|
||||
# 墙
|
||||
Blocks = []
|
||||
```
|
||||
|
||||
- 特殊移动 SpecialMove
|
||||
|
||||
用于处理本例中的虫洞场景,比如“(1,LEFT):12”,表示“从状态 1 执行向左的动作会到达状态 12”。
|
||||
- 墙 Blocks
|
||||
|
||||
用于搭建迷宫类场景。撞墙后一般原地不动。
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
|
||||
## 9.2 随机策略下的结果
|
||||
|
||||
### 9.2.1 通用的模型逻辑
|
||||
|
||||
有了模型的数据定义后,需要把它转换成模型的代码逻辑。为此创建一个 GridWorld 类来读取 9.1.3 中的模型数据定义。需要保证这个类可以处理所有 GridWorld 形式的数据。
|
||||
|
||||
【代码位置】GridWorld_Model.py
|
||||
|
||||
```Python
|
||||
class GridWorld(object):
|
||||
# 生成环境
|
||||
def __init__(self, ...):
|
||||
...
|
||||
# 用于生成“状态->动作->转移->奖励”字典
|
||||
def __init_states(self, Probs, StepReward):
|
||||
...
|
||||
# 用于计算移动后的下一个状态
|
||||
# 左上角为 [0,0], 横向为 x, 纵向为 y
|
||||
def __get_next_state(self, s, x, y, action):
|
||||
...
|
||||
```
|
||||
|
||||
这个类是可以应用到任何 GridWorld(方格世界)类型的强化学习场景中,读者如果希望自己构建更有趣的方格世界,可以只定义数据部分即可。
|
||||
|
||||
此时可以运行 Wormhole_0_Data.py,打印输出“状态->动作->转移->奖励字典”,用于观察我们在该模型中的数据设置是否正确。
|
||||
【代码位置】 Wormhole_0_Data.py
|
||||
```python
|
||||
if __name__=="__main__":
|
||||
env = model.GridWorld(
|
||||
GridWidth, GridHeight, StartStates, EndStates, # 关于状态的参数
|
||||
Actions, Policy, SlipProbs, # 关于动作的参数
|
||||
StepReward, SpecialReward, # 关于奖励的参数
|
||||
SpecialMove, Blocks) # 关于移动的限制
|
||||
model.print_P(env.P_S_R)
|
||||
```
|
||||
|
||||
最后一行代码打印输出“状态->动作->转移->奖励字典”:
|
||||
|
||||
```
|
||||
state = 0
|
||||
action = LEFT
|
||||
[(1.0, 0, -1)]
|
||||
action = UP
|
||||
[(1.0, 0, -1)]
|
||||
action = RIGHT
|
||||
[(1.0, 1, 0)]
|
||||
action = DOWN
|
||||
[(1.0, 5, 0)]
|
||||
state = 1
|
||||
action = LEFT
|
||||
[(1.0, 12, 5)]
|
||||
action = UP
|
||||
[(1.0, 12, 5)]
|
||||
action = RIGHT
|
||||
[(1.0, 12, 5)]
|
||||
action = DOWN
|
||||
[(1.0, 12, 5)]
|
||||
...
|
||||
state = 24
|
||||
...
|
||||
```
|
||||
为了节省篇幅,我们只截取了其中的一部分输出内容。
|
||||
|
||||
- state = 0
|
||||
|
||||
在状态 $s_0$ 时,向左和向上的移动都会出界(然后返回 $s_0$),得 -1 奖励。向右和向下的移动可以以概率 1.0 到达 $s_1,s_5$,0 分奖励。
|
||||
- state = 1
|
||||
|
||||
在状态 $s_1$ 时,任何一个动作都会以 1.0 的概率移动到 $s_{12}$,并得到 5 分奖励。
|
||||
|
||||
- state = 24
|
||||
一共有 $[s_0,\cdots,s_{24}]$ 25 个状态,数据形式上一样,内容不尽相同。
|
||||
|
||||
|
||||
有了这个模型,我们下一步可以继续解决穿越虫洞问题了。
|
||||
|
||||
|
||||
### 9.2.2 过程控制
|
||||
|
||||
在第 8 章的末尾,我们已经利用贝尔曼期望方程实现了迭代算法,**算法实现**代码单独存放在 Algo_PolicyValueFunction.py 中,就是为了可以在任何地方复用。
|
||||
|
||||
在本章中,我们又在前面创建了模型的**数据定义**和通用的**模型逻辑**,再加上上面的**算法实现**,万事俱备了。但是先捋清楚它们之间的调用关系。
|
||||
|
||||
<center>
|
||||
<img src="./img/grid_world_1.png">
|
||||
|
||||
图 9.2.1 模块关系
|
||||
</center>
|
||||
|
||||
如图 9.2.1 所示。
|
||||
|
||||
- 数据定义:Wormhole_0_Data.py,定义穿越虫洞问题的模型。
|
||||
- 模型逻辑:GridWorld_Model.py,接收数据定义,用通用逻辑生成模型。
|
||||
- 算法实现:Algo_PolicyValueFunction.py,接收模型,运行算法。
|
||||
- 过程控制与结果输出:Wormhole_1_VQ_pi.py,控制以上过程,输出结果。
|
||||
|
||||
这种设计的好处就是可以高度复用,比如:
|
||||
|
||||
- 更换数据定义,就可以把穿越虫洞问题变成悬崖行走问题或迷宫问题,但是模型逻辑部分和算法部分不需要改动。
|
||||
|
||||
- 更换算法实现,就可以把计算贝尔曼价值函数问题变成后面要学习的计算贝尔曼最有价值函数问题。
|
||||
|
||||
过程控制代码如下:
|
||||
|
||||
```Python
|
||||
import numpy as np
|
||||
import Wormhole_0_Data as data # 数据定义
|
||||
import GridWorld_Model as model # 模型逻辑
|
||||
import Algo_PolicyValueFunction as algo # 算法实现
|
||||
import DrawQpi as drawQ # 结果输出
|
||||
|
||||
if __name__=="__main__":
|
||||
env = model.GridWorld(
|
||||
# 关于状态的参数
|
||||
data.GridWidth, data.GridHeight, data.StartStates, data.EndStates,
|
||||
# 关于动作的参数
|
||||
data.Actions, data.Policy, data.SlipProbs,
|
||||
# 关于奖励的参数
|
||||
data.StepReward, data.SpecialReward,
|
||||
# 关于移动的限制
|
||||
data.SpecialMove, data.Blocks)
|
||||
|
||||
gamma = 0.9 # 折扣,在本例中用1.0可以收敛,但是用0.9比较保险
|
||||
iteration = 1000 # 算法最大迭代次数
|
||||
V_pi, Q_pi = algo.V_in_place_update(env, gamma, iteration) # 原地更新的迭代算法
|
||||
print("V_pi")
|
||||
V = np.reshape(np.round(V_pi,2), (data.GridWidth, data.GridHeight))
|
||||
print(V)
|
||||
print("Q_pi")
|
||||
print(np.round(Q_pi,2))
|
||||
# 字符图形化显示
|
||||
drawQ.draw(Q_pi, (data.GridWidth, data.GridHeight))
|
||||
```
|
||||
|
||||
在最开始的 import 部分,会把前面所述的“数据定义、模型逻辑、算法实现”三个模块都引入,然后在 main 下面先把数据“喂到”模型中(env=model.GridWorld(data...)),然后再把模型“喂到”算法中(algo.V_in_place_update(env...)),最后格式化输出模型的返回值 $V_\pi,Q_\pi$ 两个数组。
|
||||
|
||||
### 9.2.3 结果输出与分析
|
||||
|
||||
#### 状态价值函数 $v_\pi(s)$ 的结果
|
||||
|
||||
$V_\pi$ 其实是一个一维数组,按状态的顺序 [0,24] 存放着 $v_\pi(s)$ 的值。但是为了和方格世界对应,特地做了 5x5 的 reshape()。
|
||||
|
||||
```
|
||||
迭代次数 = 41
|
||||
V_pi
|
||||
[[ 1.66 5.63 4.52 8.73 3.28]
|
||||
[ 0.64 2.02 2.3 2.99 1.51]
|
||||
[-0.35 0.41 0.7 0.75 0.05]
|
||||
[-1.16 -0.56 -0.34 -0.43 -0.97]
|
||||
[-1.96 -1.41 -1.22 -1.34 -1.85]]
|
||||
```
|
||||
迭代了 41 次收敛,比起在射击问题中只迭代了 2 次即收敛,本例中的情况要复杂很多。
|
||||
|
||||
<center>
|
||||
<img src="./img/ship-3.png">
|
||||
|
||||
图 9.2.2 状态价值函数 $v_\pi(s)$ 的结果
|
||||
</center>
|
||||
|
||||
图 9.2.2 就是手绘的图形化结果,其中有几个相互有关系的格子被标上了不同的颜色,方便我们来验算一下 $v_\pi$ 的计算是否正确。
|
||||
|
||||
根据式 8.6.1:$v_\pi(s)=\sum_a \pi(a \mid s) \Big(\sum_{s'} p_{ss'}^a [r_{ss'}^a+\gamma v_\pi(s')]\Big)$,其中:
|
||||
|
||||
- $\pi=0.25$
|
||||
- $p=1.0$
|
||||
- 而 $r$ 根据情况有所不同,可能是 0, -1, 5, 10 中的一个值。
|
||||
|
||||
下面以蓝色格子为例进行验算:$s=s_3,s'=s_{21},\pi(a|s_3)=0.25,p^a_{3,21}=1,r^a_{3,21}=10,\gamma=0.9$,代入式 8.6.1:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
v_\pi(s_{3})&=\sum_{a \in (L,U,R,D)} \pi(a|s_3) \Big(\sum_{s'=S_{21}} p^a_{3,21} [r^a_{3,21}+0.9 v(s_{21})] \Big)
|
||||
\\
|
||||
&=4 \times 0.25 \times \Big(1\times[10+0.9\times(-1.41)]\Big)
|
||||
\\
|
||||
&\approx 8.73
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
与$v_\pi(s_3)$的值吻合。
|
||||
|
||||
- 上式中外部的求和运算由 $4\times0.25$ 完成,是因为 4 个方向上的策略概率相等,所以简单地乘以 4 即可。
|
||||
- 内部的求和运算,因为转移概率 $p^a_{3,21}=1$,所以只有一项状态转移,不需要求和。
|
||||
|
||||
橙色和绿色的部分的验证由读者在思考与练习中完成。
|
||||
|
||||
再分析一下两个虫洞入口 $s_1,s_3$ 的状态价值函数值:
|
||||
|
||||
$v_\pi(s_3)=8.73$,小于离开此状态的即时奖励($r=10$),而 $v_\pi(s_1)=5.63$,大于离开此状态时的即时奖励($r=5$)。这是为什么呢?
|
||||
|
||||
- 因为 $s_3$ 的状态价值函数由其下游状态 $s_{21}$ 决定,而飞船在 $s_{21}$ 有 0.25 的可能出界而得到负的奖励。如果目标状态是处于角落位置的 $s_20$,那么 $v_\pi(s_3)$ 的值将会更小。
|
||||
|
||||
- 而 $s_1$ 的下游状态 $s_{12}$ 在中心区域,很不容易出界,状态价值为正数,所以 $v_\pi(s_1)$ 的值要大于即时奖励值。
|
||||
|
||||
整个方格世界的状态值分布,上半部分为正数,是因为有个两个虫洞入口的状态值很高;向下逐渐变成负数,是因为靠近边界的地方因为容易出界而得到负的奖励。
|
||||
|
||||
#### 动作价值函数 $q_\pi(s,a)$ 的结果
|
||||
|
||||
|
||||
$Q_\pi$ 是一个二维数组,行序号等于状态的顺序[0,24],列序号代表 4 个动作,每个单元存放的就是在策略 $\pi$ 下(本例中为四个方向随机的 0.25)的 4 个动作的价值函数值,顺序是“左上右下”。
|
||||
|
||||
```
|
||||
Q_pi
|
||||
[[ 0.49 0.49 5.07 0.58]
|
||||
[ 5.63 5.63 5.63 5.63]
|
||||
[ 5.07 3.06 7.86 2.07]
|
||||
......
|
||||
[-1.27 -0.31 -1.2 -2.1 ]
|
||||
[-1.1 -0.38 -1.67 -2.2 ]
|
||||
[-1.2 -0.87 -2.67 -2.67]]
|
||||
```
|
||||
上面的输出为了节省篇幅,省略了中间的一些行。
|
||||
|
||||
以第一行数据(状态 0)为例:[0.49 0.49 5.07 0.58],可以看到最大值是 5.07,对应的动作是“右”,也就是说智能体(飞船)在这个状态下,最佳动作应该向右侧移动。而两个相等的 4.9 表示向左侧或者上方移动,都是出界,会有相等的低价值。
|
||||
|
||||
针对第二行数据,如果使用 np.argmax([5.63, 5.63, 5.63, 5.63]) 函数,只会返回第一个最大值,这不是我们想要的,应该使用下面的代码来获得所有的最佳动作:
|
||||
```python
|
||||
best_actions = np.argwhere(self.policy == np.max(self.policy)) #应该返回 [0,1,2,3]
|
||||
```
|
||||
|
||||
为了方便地看到图形化的输出,笔者特地写了一个简陋的类,用图形化字符展示最大动作值方向,输出如下:
|
||||
|
||||
```
|
||||
+-----+-----+-----+-----+-----+
|
||||
| | ▲ | | ▲ | |
|
||||
| ┼─►|◄─┼─►| ┼─►|◄─┼─►|◄─┼ |
|
||||
| | ▼ | | ▼ | |
|
||||
+-----+-----+-----+-----+-----+
|
||||
| | ▲ | ▲ | ▲ | ▲ |
|
||||
| ┼─►| ┼ | ┼ | ┼ | ┼ |
|
||||
| | | | | |
|
||||
+-----+-----+-----+-----+-----+
|
||||
| ▲ | ▲ | ▲ | ▲ | ▲ |
|
||||
| ┼ | ┼ | ┼ | ┼ | ┼ |
|
||||
| | | | | |
|
||||
+-----+-----+-----+-----+-----+
|
||||
| ▲ | ▲ | ▲ | ▲ | ▲ |
|
||||
| ┼ | ┼ | ┼ | ┼ | ┼ |
|
||||
| | | | | |
|
||||
+-----+-----+-----+-----+-----+
|
||||
| ▲ | ▲ | ▲ | ▲ | ▲ |
|
||||
| ┼ | ┼ | ┼ | ┼ | ┼ |
|
||||
| | | | | |
|
||||
+-----+-----+-----+-----+-----
|
||||
```
|
||||
比如状态 0,最大值是代表“右”的 5.07,所以就在 (0,0) 的格子中显示一个向右的箭头。而状态 1 四个方向的值相等,都是 5.63,就显示 4 个方向的箭头。
|
||||
|
||||
欢迎读者发挥自己的聪明才智来修改这个类,展示更优美的字符图形化界面。
|
||||
|
||||
图 9.2.3 是笔者手绘的动作价值函数结果图,方便读者阅读。
|
||||
|
||||
<center>
|
||||
<img src="./img/ship-4.png">
|
||||
|
||||
图 9.2.3 动作价值函数 $q_\pi(s,a)$ 的结果
|
||||
(左图:四个方向上的数值;右图:最大值代表的动作方向)
|
||||
</center>
|
||||
|
||||
左图显示了每个状态内 4 个方向的动作价值。在一个方格内,4 个数据是有可比性的,不同方格内的数据没有可比性。正负符号也不表示其它意思,只看大小。
|
||||
|
||||
上半部分的动作价值函数值普遍为正数,下半部分普遍为负数,主要是和状态价值函数的分布有关。
|
||||
|
||||
绘制完全部 25 个状态的最佳动作后,我们来一起分析一下。
|
||||
|
||||
先看图 9.2.4。
|
||||
|
||||
<center>
|
||||
<img src="./img/ship-5.png">
|
||||
|
||||
图 9.2.4 有趣的相邻四角对称现象
|
||||
</center>
|
||||
|
||||
我们以红色虚线所示的菱形为例,菱形内部四个角的四个数值相同,都是 0.67,但是它们属于四个不同的状态的动作,这是为什么呢?
|
||||
|
||||
其实这正是动作价值函数的公式所带来的效果。
|
||||
|
||||
$q_\pi$ 值是由其下游状态的 $v_\pi$ 所决定的,对于红色菱形区来说,其中心状态是 $s_{13}$,保留三位小数的 $v_\pi(s_{13})=0.746$,所以 $s_8$ 的向下的动作的价值函数为:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
q_\pi(s_8,a_{Down})&=\sum_{s'} p_{ss'}^a \big [r_{ss'}^a+\gamma v_\pi(s') \big ]
|
||||
\\
|
||||
&=1.0 \times [0 + 0.9\times0.746]
|
||||
\\
|
||||
&=0.6714\approx 0.67
|
||||
\end{aligned}
|
||||
$$
|
||||
|
||||
同理,$q_\pi(s_{12},a_{Right}),q_\pi(s_{14},a_{Left}),q_\pi(s_{18},a_{Up})$ 都是相同的结果。
|
||||
|
||||
黄色菱形也是如此。
|
||||
|
||||
蓝色菱形因为位置靠边,只有三个顶点。在 $s_5$ 向左移动出界会得到 -1 的奖励,因此 $0.58-1=-0.42$,正好是 $q_\pi(s_5,a_{Left})$ 的值。
|
||||
|
||||
绿色菱形的上顶点所代表的动作值,是有穿越虫洞的奖励,所以 $10+0.9\times(-1.41)=8.73$,其中 -1.41 是下游状态 $s_21$ 的状态值。
|
||||
|
||||
下面我们再看看哪些动作是合理的,哪些有疑问的。
|
||||
|
||||
合理的动作:
|
||||
|
||||
- 在状态 1 和 3,任意向四个方向移动,都会无条件穿过虫洞,这个没有问题;
|
||||
- 在状态 2 向右走,因为 $v_3$ 的价值大于 $v_1$ 的价值;
|
||||
- 在状态 0 向右走到达状态 1,虽然 $v_3$ 更好,但是要绕远(0->5->6->7->2->3),得不偿失;
|
||||
|
||||
不合理的动作:
|
||||
|
||||
- 在状态 6,是不是可以向右走以便最终到达状态 3 更好呢?
|
||||
- 在状态 24,如果想用最短路径到达状态 3,应该可以选择向上和向左两个方向,但是在图 9.2.3 中只有一个向上的选择。
|
||||
- 状态 7 和状态 24 情况一样,应该有向上和向右两个可选项,图中只有一个。
|
||||
|
||||
分析至此,虽然图 9.2.3 中的动作选择大方向没错,但是有些细节值得推敲,也许这个策略组合还不是最佳的。
|
||||
|
||||
|
||||
### 思考与练习
|
||||
|
||||
1. 验证图 x 中 橙色 $v_\pi(s_{5})$ 和 绿色 $v_\pi(s_{18})$ 的 $v_\pi$ 值
|
||||
2. 读者可以呼唤两个虫洞中的任意一个的入口和出口位置,看看方格世界的价值函数值的分布会有何变化。
|
||||
3. 如果方格世界中没有虫洞,哪个状态的值会是最高?
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
## 9.3 策略对价值函数的影响
|
||||
|
||||
### 9.3.1 给定策略下的状态函数值
|
||||
|
||||
让我们先暂时从穿越虫洞的问题中穿越回到射击气球的问题,因为还有一个疑问没有解决。
|
||||
|
||||
射击气球问题的初始化环境指定的策略是这样的:
|
||||
|
||||
```python
|
||||
Policy = { # 原始策略
|
||||
0:[0.4,0.6], # 在状态 0 时,选择射击红球的概率0.4,选择射击蓝球的概率0.6
|
||||
1:[0.4,0.6], # 在状态 1 时,同上
|
||||
2:[0.4,0.6],
|
||||
3:[0.4,0.6],
|
||||
4:[0.4,0.6],
|
||||
5:[0.4,0.6],
|
||||
6:[0.4,0.6] # 可以不定义,因为在终止状态没有动作
|
||||
}
|
||||
```
|
||||
|
||||
前面也提到过,这个策略是通过统计游客的行为而得到的。意味着在100个游客中,有40个游客会选择射击红色气球,另外60个游客会选择射击蓝色气球,两轮射击都是如此。用公式表示为:
|
||||
|
||||
$$
|
||||
\pi(a \mid s)=
|
||||
\begin{cases}
|
||||
0.4, & a=射击红球
|
||||
\\
|
||||
0.6, & a=射击蓝球
|
||||
\end{cases}
|
||||
\tag{9.3.1}
|
||||
$$
|
||||
|
||||
|
||||
所以最终的状态与动作价值函数结果如图 8.5.3 所示。
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-result.png">
|
||||
|
||||
由图 8.5.3
|
||||
</center>
|
||||
|
||||
但是,这还没有回答在第 8 章最开始提出的问题:那么做为一个聪明的游客,**你**应该如何选择呢?
|
||||
|
||||
因为一个不怎么“聪明”的游客也会想到:
|
||||
|
||||
1. 应该连续两次选择射击红球才有可能得到 6 元的奖励,大于开始付出的 4 元;
|
||||
2. 如果射中一次红球、一次蓝球,刚好 4 元;
|
||||
3. 如果射中两次蓝球,只有 2 元。
|
||||
|
||||
所以 0.4:0.6 这个数字,只是游乐场老板统计的结果,对于你来说这是最好的策略吗?
|
||||
|
||||
### 9.3.2 尝试不同的策略带来的影响
|
||||
|
||||
为了验证策略对最终结果的影响,我们可以尝试修改一下该策略,然后比较“开始(0)”状态的价值函数的值 $v_\pi(s_0)$,看看谁高谁低。因为对下游状态的所有策略上的修改,最终会反映到开始状态的,所以我们只需要简单地比较开始状态的价值函数值就可以了。
|
||||
|
||||
【代码位置】Shoot_0_Try_Policy.py
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import copy
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import Algo_PolicyValueFunction as algo
|
||||
|
||||
if __name__=="__main__":
|
||||
Policy = { # 原始策略
|
||||
0:[0.4,0.6] # 下同
|
||||
......
|
||||
}
|
||||
gamma = 1
|
||||
max_iteration = 1000
|
||||
env = dataModel.Env(Policy)
|
||||
V,Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration)
|
||||
print("在原始策略下的状态价值函数值")
|
||||
print(np.round(V,5))
|
||||
print("在原始策略下的动作价值函数值")
|
||||
print(Q)
|
||||
......(未完待续)
|
||||
```
|
||||
|
||||
这一部分的输出结果同图 9.3.1 相同,我们只是为了方便比对。
|
||||
|
||||
```
|
||||
在原始策略下的状态价值函数值 V:
|
||||
[1.19548 0.56 0.554 0.8 0.56 0.73 0. ]
|
||||
在原始策略下的动作价值函数值 Q:
|
||||
[[1.0957 1.262 ]
|
||||
[0.5 0.6 ]
|
||||
[0.56 0.55 ]
|
||||
[0.8 0.8 ]
|
||||
[0.5 0.6 ]
|
||||
[0.7 0.75 ]
|
||||
[0. 0. ]]
|
||||
```
|
||||
|
||||
接下来修改策略,再进行价值计算,看看结果如何:
|
||||
|
||||
```python
|
||||
......(接上一段代码)
|
||||
# 新策略
|
||||
test_policy = np.array([
|
||||
[0.2,0.8], # 修改状态 0 的策略
|
||||
[0.5,0.5], # 修改状态 1 的策略
|
||||
[0.3,0.7], # 修改状态 2 的策略
|
||||
[0.1,0.9], # 修改状态 3 的策略
|
||||
[0.3,0.7], # 修改状态 4 的策略
|
||||
[0.6,0.4] # 修改状态 5 的策略
|
||||
])
|
||||
# 每次只修改一个策略,保持其它策略不变,以便观察其影响
|
||||
for i in range(6):
|
||||
print(str.format("修改状态 {0} 的策略:{1}", i, test_policy[i]))
|
||||
new_policy = copy.deepcopy(Policy) # 继承原始策略
|
||||
new_policy[i] = test_policy[i] # 只修改其中一个状态的策略
|
||||
env = dataModel.Env(new_policy) # 输入新策略
|
||||
V,Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 价值计算
|
||||
print(np.round(V,5))
|
||||
```
|
||||
打印输出结果:
|
||||
|
||||
```
|
||||
修改状态 0 的策略:[0.2 0.8]
|
||||
[1.22874 0.56 0.554 0.8 0.56 0.73 0. ]
|
||||
修改状态 1 的策略:[0.5 0.5]
|
||||
[1.19228 0.55 0.554 0.8 0.56 0.73 0. ]
|
||||
修改状态 2 的策略:[0.3 0.7]
|
||||
[1.19546 0.56 0.553 0.8 0.56 0.73 0. ]
|
||||
修改状态 3 的策略:[0.1 0.9]
|
||||
[1.19548 0.56 0.554 0.8 0.56 0.73 0. ]
|
||||
修改状态 4 的策略:[0.3 0.7]
|
||||
[1.19788 0.56 0.554 0.8 0.57 0.73 0. ]
|
||||
修改状态 5 的策略:[0.6 0.4]
|
||||
[1.19188 0.56 0.554 0.8 0.56 0.72 0. ]
|
||||
```
|
||||
|
||||
### 9.3.3 结果分析
|
||||
|
||||
上述结果不方便分析,下面把结果列在表 9.3.1 中,便于对照比较。
|
||||
|
||||
表 9.3.1 修改策略后的状态价值函数
|
||||
|
||||
|策略 $\to$ 价值|$v'_\pi(s_0)$|$v'_\pi(s_1)$|$v'_\pi(s_2)$|$v'_\pi(s_3)$|$v'_\pi(s_4)$|$v'_\pi(s_5)$|$v'_\pi(s_6)$|
|
||||
|-|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
|原始策略[0.4, 0.6]下的$v_\pi(s)$|1.19548 | 0.56 | 0.554 | 0.8 | 0.56 | 0.73 | 0 |
|
||||
|修改 $s_0$ 的策略 $\pi'_0$:[0.2, 0.8]|**1.22874**| 0.56| 0.554| 0.8| 0.56 | 0.73 | 0|
|
||||
|修改 $s_1$ 的策略 $\pi'_1$:[0.7, 0.3]|**1.19228**|**0.55**| 0.554| 0.8| 0.56| 0.73 |0|
|
||||
|修改 $s_2$ 的策略 $\pi'_2$:[0.3, 0.7]|**1.19546**| 0.56 | **0.553**| 0.8| 0.56| 0.73 | 0|
|
||||
|修改 $s_3$ 的策略 $\pi'_3$:[0.1, 0.9]|1.19548| 0.56| 0.554| 0.8 |0.56 | 0.73 | 0|
|
||||
|修改 $s_4$ 的策略 $\pi'_4$:[0.5, 0.5]|**1.19788**|0.56| 0.554|0.8|**0.57**|0.73|0|
|
||||
|修改 $s_5$ 的策略 $\pi'_5$:[0.6, 0.4]|**1.19188**| 0.56|0.554 |0.8 | 0.56 |**0.72** | 0|
|
||||
|
||||
表 9.3.1 中,第一行为原始策略的基准值,便于比较。后面每行中,我们把与基准值相比有变化的值都用黑体标了出来,便于读者可以快速发现差异。
|
||||
|
||||
从$v'_\pi(s_0)$ 的值看,有的子策略提高了该值,比如 $\pi'_0,\pi'_4$;另外一些子策略降低了该值,比如 $\pi'_1,\pi'_2,\pi'_5$;而 $\pi'_3$ 对改值没有影响。所以,有必要搞清楚每个策略的具体影响,才能获得最大利益。
|
||||
|
||||
- 对状态 $s_0$ 的策略修改
|
||||
|
||||
把状态(0) 的策略 $\pi_0$ 从 [0.4,0.6] 修改为 [0.2,0.8] 后,对后续状态的价值函数都没有影响,但是 $v_0$ 的价值函数提高到了 1.22874(基准值为 1.19548)。
|
||||
|
||||
原因是根据式 8.4.5,策略 $\pi$ 相当于在使用 $q_\pi$ 计算 $v_\pi$ 时的权重:
|
||||
|
||||
$$
|
||||
v_\pi(s) = \sum_{a \in A(s)} \pi(a|s)q_\pi(s,a)
|
||||
\tag{由8.4.5}
|
||||
$$
|
||||
|
||||
状态“红(0)”的动作价值函数比“蓝(1)”小:$q_\pi(s_0,a_0)=1.0957,q_\pi(s_0,a_1)=1.262$。而在原始策略 $\pi_0$ 中,$q_\pi(s_0,a_1)$ 占比 0.6,新策略 $\pi'_0$ 中 $q_\pi(s_0,a_1)$ 占比 0.8,所以最后会影响到 $v_\pi(s_0)$ 的价值:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
v_\pi(s_0) &= \pi_0(a_0|s_0)q_\pi(s_0,a_0)+\pi_0(a_1|s_0)q_\pi(s_0,a_1)
|
||||
\\
|
||||
&=0.4\times1.0957+0.6\times1.262 = 1.19548
|
||||
\\
|
||||
v'_\pi(s_0) &= \pi'_0(a_0|s_0)q_\pi(s_0,a_0)+ \pi'_0(a_1|s_0)q_\pi(s_0,a_1)
|
||||
\\
|
||||
&= 0.2 \times 1.0957 + 0.8 \times 1.262 = 1.22874
|
||||
\end{aligned}
|
||||
\tag{9.3.2}
|
||||
$$
|
||||
|
||||
注意,$\pi_0$ 的修改只会影响到状态 $s_0$。
|
||||
|
||||
- 对状态 $s_1$ 的策略修改
|
||||
|
||||
$\pi_1$ 的修改会影响到状态值 $v_\pi(s_1)$,进而会影响到它的上游的 $q_\pi(s_0,a_0)$ 的值,以及再上游的状态 $v_\pi(s_0)$ 的值。
|
||||
|
||||
所以从表 9.3.1 中看,$v_\pi(s_1)=0.55$,低于原始策略的 $0.56$,所以最后 $v'_\pi(s_0)=1.19228$,也低于原始策略的 $1.19548$。
|
||||
|
||||
- 对状态 $s_3$ 的策略修改
|
||||
|
||||
为什么对状态 $s_3$ 的策略 $\pi_3$ 修改没有影响到任何状态值的变化呢?
|
||||
|
||||
因为它的下游动作价值函数 $q_\pi(s_3,a_0)=q_\pi(s_3,a_1)=0.8$,所以无论权重是多少,只要权重之和为 1,结果都是 0.8。
|
||||
|
||||
- 其它状态
|
||||
|
||||
新的策略 $\pi'$ 的数值,有些会提高 $v_\pi(s_0)$ 的值,有些会降低 $v_\pi(s_0)$ 的值,完全依赖于具体的策略数值。比如 $\pi'_1=[0.7, 0.3]$ 降低了 $v_\pi(s_0)$ 的值,改成 $\pi'_1=[0.3, 0.7]$ 就会提高 $v_\pi(s_0)$ 的数值。
|
||||
|
||||
|
||||
### 思考与练习
|
||||
@@ -0,0 +1,253 @@
|
||||
|
||||
## 9.4 搜索最优策略组合
|
||||
|
||||
### 9.4.1 最优策略
|
||||
|
||||
对 $\pi$ 的修改会提高或降低 $v_\pi(s_0)$ 的值,这还只是独立修改一个策略的影响,那么一共有多少种组合可以帮助游客提高收益呢?其最大值又是多少呢?本节的任务就是给每个状态都找到最好的策略,从而使整体状态值达到最佳。
|
||||
|
||||
但是在具体操作中会遇到这样的困难:假设我们知道了一个状态上的最佳策略是 [0.4,0.6],但是不可能把一支箭分成 0.4:0.6,所以你必须二选一,用公式表示为:
|
||||
|
||||
$$
|
||||
\pi(a \mid s)=
|
||||
\begin{cases}
|
||||
1, & if \ a=\argmax A(s)
|
||||
\\
|
||||
0, & 其它动作
|
||||
\end{cases}
|
||||
\tag{9.4.1}
|
||||
$$
|
||||
|
||||
当 $\pi=[0.4,0.6], A(s)=[a_0,a_1]$ 时,式 9.4.1 的结果是:$\pi(a_0|s)=0,\pi(a_1|s)=1$。因为 0.6 > 0.4 且处于数组中第 1 个位置(从 0 开始计算)。
|
||||
|
||||
从另一个角度看,以两个动作为例,假设原始策略为 [0.4, 0.6],如果新策略 [0.2, 0.8] 会提高整体状态函数值的话,那么另一个新策略 [0.1, 0.9] 甚至 [0.0, 1.0] 也一定会更大地提高状态函数值。读者只需要把上一小节的策略改一改就知道了,比如这一行代码:
|
||||
```python
|
||||
[0.2,0.8], # 修改状态 0 的策略,可以尝试 [0.1, 0.9] or [0.0, 1.0]
|
||||
```
|
||||
因此,$\pi(a_0|s)=0,\pi(a_1|s)=1$ 也就成立了。
|
||||
|
||||
当两个概率值相等时,我们可以选择任意一个,总选第一个的话,可能会失去其它机会。
|
||||
|
||||
在图 9.3.1 中,一共有 6 个有效的状态($s_0,\cdots,s_5$) ,在每个状态上的策略有两种动作选择(红球或蓝球),这样一共有 $2\times2\times2\times2\times2\times2=2^6$ 种组合。推广到一般情况:如果状态空间为 $S$,动作空间为 $A$,则策略组合是 $|A|^{|S|}$ 种。
|
||||
|
||||
对于射击气球这个简单的问题来说,因为计算一次 $v_\pi,q_\pi$ 只需要迭代两次,即使用遍历的方法,也很容易快速得到结果。所以,我们先用最笨但是最准确的遍历来得到评估的基准。
|
||||
|
||||
### 9.4.2 策略遍历搜索
|
||||
|
||||
知道了一共有 $2^6=64$ 种组合,用手写出来还是比较累的,但是作为一个合格的程序员,我们可以用代码生成 64 个组合策略,实际上就是用二进制表示的 [0, 63]:
|
||||
```
|
||||
[[0 0 0 0 0 0] # 红 红 红 红 红 红
|
||||
[0 0 0 0 0 1] # 红 红 红 红 红 蓝
|
||||
[0 0 0 0 1 0] # 红 红 红 红 蓝 红
|
||||
......
|
||||
[1 1 1 1 0 1] # 蓝 蓝 蓝 蓝 红 蓝
|
||||
[1 1 1 1 1 0] # 蓝 蓝 蓝 蓝 蓝 红
|
||||
[1 1 1 1 1 1]] # 蓝 蓝 蓝 蓝 蓝 蓝
|
||||
```
|
||||
上面的输出中,0 表示在该状态选择红色球,1 表示在该状态选择蓝色球。
|
||||
|
||||
下面要根据式 9.4.1,把上述的二进制形式翻译一下,变成模型可以认识的策略数据结构:用 onehot(热独)编码来表示选择红球或者蓝球,比如:[1, 0] 表示选择红球,[0, 1] 表示选择蓝球。
|
||||
|
||||
【代码位置】Shoot_3_OptimalSearch.py
|
||||
|
||||
```python
|
||||
# 创建onehot编码形式的policy
|
||||
def create_onehot_policy(actions):
|
||||
policy = {}
|
||||
for s in range(len(actions)): # onehot
|
||||
policy[s] = [1,0] if actions[s]==0 else [0,1]
|
||||
return policy
|
||||
```
|
||||
以**策略组合**(0)为例,输出格式如下:
|
||||
|
||||
```
|
||||
策略组合(0):{0: [1, 0], 1: [1, 0], 2: [1, 0], 3: [1, 0], 4: [1, 0], 5: [1, 0]}
|
||||
```
|
||||
|
||||
注意我们用**策略组合**这个词,表示在 $[s_0,\cdots,s_5]$ 六个状态下的各自的**策略**动作选择,策略组合 0 中的策略是都选红色球。
|
||||
|
||||
好了,准备工作完成,接下来可以把各种策略组合代入环境中,计算价值函数了:
|
||||
|
||||
```python
|
||||
if __name__=="__main__":
|
||||
all_policy_in_binary = create_binary_policy() # 二进制形式
|
||||
print(all_policy_in_binary)
|
||||
# 遍历所有策略组合
|
||||
gamma = 1
|
||||
max_iteration = 1000
|
||||
V_values = []
|
||||
for id, actions in enumerate(all_policy_in_binary):
|
||||
policy = create_onehot_policy(actions) # onehot形式
|
||||
print(str.format("策略组合({0}): {1}", id, policy))
|
||||
env = dataModel.Env(policy) # 创建环境,代入策略组合
|
||||
V, Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 迭代法计算V,Q
|
||||
V_values.append(V) # 保存每个策略组合的价值函数结果,便于比较
|
||||
print("价值函数 :", V)
|
||||
```
|
||||
|
||||
上述代码将会输出所有策略组合及其状态价值函数:
|
||||
|
||||
```python
|
||||
策略组合(0): {0: [1, 0], 1: [1, 0], 2: [1, 0], 3: [1, 0], 4: [1, 0], 5: [1, 0]}
|
||||
价值函数:[1.048 0.5 0.56 0.8 0.5 0.7 0. ]
|
||||
策略组合(1):{0: [1, 0], 1: [1, 0], 2: [1, 0], 3: [1, 0], 4: [1, 0], 5: [0, 1]}
|
||||
价值函数:[1.048 0.5 0.56 0.8 0.5 0.75 0. ]
|
||||
......
|
||||
策略组合 62:{0: [0, 1], 1: [0, 1], 2: [0, 1], 3: [0, 1], 4: [0, 1], 5: [1, 0]}
|
||||
价值函数:[1.26 0.6 0.55 0.8 0.6 0.7 0. ]
|
||||
策略组合 63:{0: [0, 1], 1: [0, 1], 2: [0, 1], 3: [0, 1], 4: [0, 1], 5: [0, 1]}
|
||||
价值函数:[1.29 0.6 0.55 0.8 0.6 0.75 0. ]
|
||||
```
|
||||
|
||||
一共 64 组,为了节省篇幅,中间的部分省略,请读者自己运行代码观察全部结果。
|
||||
|
||||
接下来,要通过比较 $v_\pi(s_0)$ 的大小,来搜索最优策略组合。$v_\pi(s_0)$ 的值就是在上面输出的价值函数列表中的第一个单元的值。
|
||||
|
||||
```python
|
||||
# 搜索最优策略组合
|
||||
def find_best_policy(V_values, all_policy_in_binary):
|
||||
v = np.array(V_values) # 列表变成数组
|
||||
v0_best = np.max(v[:,0]) # 获得所有策略组合中 v(s0) 的最大值
|
||||
print("v(s0)的最优价值函数 :", v0_best)
|
||||
best_ids = np.argwhere(v[:,0] == v0_best) # 拥有最大值的策略组合可能不止一个
|
||||
for id in best_ids:
|
||||
print(str.format("策略组合({0}):{1}", id[0], all_policy_in_binary[id[0]]))
|
||||
print("状态价值函数 :", v[id][0])
|
||||
print("====")
|
||||
```
|
||||
|
||||
上述代码会输出最大值,以及达到该最大值的策略组合序号、策略组合情况(二进制形式),以及对应的状态价值函数:
|
||||
|
||||
```
|
||||
v(s0)的最优价值函数 : 1.29
|
||||
====
|
||||
策略组合(35):[1 0 0 0 1 1] # 二进制形式
|
||||
状态价值函数 : [1.29 0.5 0.56 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(39):[1 0 0 1 1 1]
|
||||
状态价值函数 : [1.29 0.5 0.56 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(43):[1 0 1 0 1 1]
|
||||
状态价值函数 : [1.29 0.5 0.55 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(47):[1 0 1 1 1 1]
|
||||
状态价值函数 : [1.29 0.5 0.55 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(51):[1 1 0 0 1 1]
|
||||
状态价值函数 : [1.29 0.6 0.56 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(55):[1 1 0 1 1 1]
|
||||
状态价值函数 : [1.29 0.6 0.56 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(59):[1 1 1 0 1 1]
|
||||
状态价值函数 : [1.29 0.6 0.55 0.8 0.6 0.75 0. ]
|
||||
====
|
||||
策略组合(63):[1 1 1 1 1 1]
|
||||
状态价值函数 : [1.29 0.6 0.55 0.8 0.6 0.75 0. ]
|
||||
```
|
||||
|
||||
$v_\pi(s_0)$ 的最大值为 1.29,可以达到该值的策略组合有 8 个:[35, 39, 43, 47, 51, 55, 59, 63]。
|
||||
|
||||
### 9.4.3 结果分析
|
||||
|
||||
#### 策略解读
|
||||
|
||||
在总共 64 个策略组合中,有 8 个可以达到最大值。这说明针对本例,最优策略并不是唯一的。在其它强化学习问题中,也是如此:
|
||||
|
||||
1. 可以保证有最优策略;
|
||||
2. 可以保证找到最优策略;
|
||||
3. 不能保证找到所有最优策略。
|
||||
|
||||
观察策略组合序号的话,对数字敏感的读者可能会发现,它是一个等差数列,相邻数字之间相差 4。这个现象很有趣。进一步,我们可以把策略组合的情况(二进制表示形式)做一个反异或(先异或,再求反)操作:
|
||||
|
||||
表 9.4.1 最优策略组合中的动作选择
|
||||
|
||||
|策略组合序号|$\pi(s_0)$|$\pi(s_1)$|$\pi(s_2)$|$\pi(s_3)$|$\pi(s_4)$|$\pi(s_5)$|
|
||||
|:-:|-|-|-|-|-|-|
|
||||
|35|1|0|0|0|1|1|
|
||||
|39|1|0|0|1|1|1|
|
||||
|43|1|0|1|0|1|1|
|
||||
|47|1|0|1|1|1|1|
|
||||
|51|1|1|0|0|1|1|
|
||||
|55|1|1|0|1|1|1|
|
||||
|59|1|1|1|0|1|1|
|
||||
|63|1|1|1|1|1|1|
|
||||
|NOT-XOR|1|0|0|0|1|1|
|
||||
|
||||
从表 9.4.1 中可以看到,上述 8 个策略组合中的共同点是在 $\pi(s_0),\pi(s_4),\pi(s_5)$ 三个策略上都选择 1(蓝色球),而在 $\pi(s_1),\pi(s_2),\pi(s_3)$ 三个策略上选择什么无所谓。
|
||||
|
||||
为什么会是这样呢?我们可以通过输出最优策略组合的状态/动作价值函数来分析。
|
||||
|
||||
```python
|
||||
# 输出最优策略的价值函数
|
||||
best_ids = find_best_policy(V_values, all_policy_in_binary)
|
||||
for id in best_ids:
|
||||
policy = create_onehot_policy(all_policy_in_binary[id[0]]) # onehot形式
|
||||
print(str.format("策略组合({0}): {1}", id[0], policy))
|
||||
env = dataModel.Env(policy) # 创建环境,代入策略组合
|
||||
V, Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 迭代法计算V,Q
|
||||
print("最优状态价值函数 :", V)
|
||||
print("最优动作价值函数 :\n", Q)
|
||||
```
|
||||
|
||||
以策略组合(51)为例,其最优状态价值函数和动作价值函数的结果如下:
|
||||
|
||||
```
|
||||
策略组合(51): {0: [0, 1], 1: [0, 1], 2: [1, 0], 3: [1, 0], 4: [0, 1], 5: [0, 1]}
|
||||
最优状态价值函数 : [1.29 0.6 0.56 0.8 0.6 0.75 0. ]
|
||||
最优动作价值函数 :
|
||||
[[1.128 1.29 ] # [q(s0,a0), q(s0,a1)]
|
||||
[0.5 0.6 ] # [q(s1,a0), q(s1,a1)]
|
||||
[0.56 0.55 ] # [q(s2,a0), q(s2,a1)]
|
||||
[0.8 0.8 ] # [q(s3,a0), q(s3,a1)]
|
||||
[0.5 0.6 ] # [q(s4,a0), q(s4,a1)]
|
||||
[0.7 0.75 ] # [q(s5,a0), q(s5,a1)]
|
||||
[0. 0. ]] # [q(s6,a0), q(s6,a1)]
|
||||
```
|
||||
|
||||
首先看策略组合:
|
||||
|
||||
- 在 $s_0$ 状态,选择射击蓝色气球;
|
||||
|
||||
射击蓝色气球后,会转移到 $s_4,s_5$:
|
||||
|
||||
- 如果状态转移到 $s_4$,继续射击蓝色气球;
|
||||
- 如果状态转移到 $s_5$,也是继续射击蓝色气球。
|
||||
|
||||
虽然从逻辑上说,射击蓝色气球的动作已经决定了不可能再走左半分支(这就是表 9.4.1 反异或的结果的说明),但是该策略还是告诉了我们:
|
||||
|
||||
- 在 $s_1$ 状态,选择蓝色气球;
|
||||
- 在 $s_2$ 状态,选择红色气球;
|
||||
- 在 $s_3$ 状态,选择红色气球。
|
||||
|
||||
#### 价值函数解读
|
||||
|
||||
把上面的价值函数输出中的晦涩的数字标注在图 9.4.1 中,一目了然:
|
||||
|
||||
<center>
|
||||
<img src="./img/shoot-result-search.png">
|
||||
|
||||
图 9.4.1 搜索最优策略的结果
|
||||
</center>
|
||||
|
||||
绿色数字仍然是状态转移概率。其它图例说明如下:
|
||||
|
||||
- 状态价值函数(空心方框)
|
||||
|
||||
- 最优状态价值函数的数值,普遍比图 8.5.3 中在通用策略 [0.4, 0.6] 下的状态价值函数要高。
|
||||
- 但我们只需要关心 $v_\pi(s_0)$ 即可,因为它反应了策略的整体表现。
|
||||
|
||||
- 动作价值函数(实心圆)
|
||||
|
||||
- 最底层的动作价值函数没有变化,因为下面紧接着终止状态,在图中没有画出。
|
||||
- 上层的动作价值函数比通用策略下的高,因为下游的价值函数值提高了。
|
||||
|
||||
- 策略选择(蓝色数字与虚线箭头)
|
||||
|
||||
用 0 表示不选择,1 表示被选择。可以看到:
|
||||
- 在 $s_0,s_4,s_5$ 状态都选择了蓝色球。
|
||||
- 在 $s_1,s_2,s_3$ 状态,由于 8 个最优策略的选择各不相同,所以没有标出来。
|
||||
- 在每个状态下的两个动作价值函数,相对较大的那个 $q_\pi$ 值与上游状态的价值函数 $v_\pi$ 相等,如虚线箭头所示。这并不是巧合,在下一节会讲解其原理。
|
||||
|
||||
### 思考与练习
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
- 找到一个复杂些的好例子
|
||||
- 可以比较policy iteration and value iteration
|
||||
- 可以绘图:结果图、曲线(趋势)图
|
||||
|
||||
- Jack Rent Car
|
||||
- maze https://cs.stanford.edu/people/karpathy/reinforcejs/gridworld_dp.html
|
||||
|
||||
|
||||
|
||||
$$
|
||||
v_*(s)= \max_\pi v_\pi(s)
|
||||
$$
|
||||
|
||||
$$
|
||||
v_*(s)= \max_a q_*(s,a)
|
||||
$$
|
||||
|
||||
|
||||
$$
|
||||
q_*(s,a)= \max_\pi q_\pi(s,a)
|
||||
$$
|
||||
|
||||
$$
|
||||
\pi_*=\argmax_\pi v_\pi(s)
|
||||
$$
|
||||
|
||||
$$
|
||||
v_*(s)= \max_{a} \Big(\sum_{s'} p^a_{ss'} r^a_{ss'} + \gamma \sum_{s'} p^a_{ss'} v_*(s') \Big )
|
||||
$$
|
||||
|
||||
$$
|
||||
q_*(s,a) = \sum_{s'} p^a_{ss'} r^a_{ss'} + \gamma \sum_{s'} p^a_{ss'} v_*(s')
|
||||
$$
|
||||
|
||||
$$
|
||||
q_*(s,a) = \sum_{s'} p^a_{ss'} r^a_{ss'} + \gamma \sum_{s'} p^a_{ss'} \max_{a'} q_*(s',a')
|
||||
$$
|
||||
|
||||
|
||||
比较 贝尔曼方程,贝尔曼期望方程,贝尔曼最优方程
|
||||
|
||||
|
||||
|过程名称|组成元素|数据序列|计算|
|
||||
|-|-|-|-|
|
||||
|马尔可夫过程 MP|$<S,P>$|$S_0,S_1,\cdots,S_t$||
|
||||
|马尔可夫奖励过程 MRP|$<S,P,R,\gamma>$|$S_0,R_1,S_1,R_2,\cdots,S_t,R_{t+1}$|$V$|
|
||||
|马尔可夫决策过程 MDP|$<S,A,P,R,\gamma>$|$S_0,A_0,R_1,S_1,A_1,R_2,\cdots,S_t,A_t,R_{t+1}$|$V_\pi,Q_\pi$|
|
||||
|马尔可夫决策过程 MDP|$<S,A,P,R,\gamma>$|$S_0,A_0,R_1,S_1,A_1,R_2,\cdots,S_t,A_t,R_{t+1}$|$V_*,Q_*$|
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
https://zhuanlan.zhihu.com/p/28498261
|
||||
|
||||
理论证明策略迭代方法的正确
|
||||
@@ -0,0 +1,5 @@
|
||||
https://zhuanlan.zhihu.com/p/28868460
|
||||
|
||||
价值迭代可以从策略迭代推导得到
|
||||
|
||||
动态规划
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
冰面行走问题
|
||||
|
||||
0.7,0.2,0.1
|
||||
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,61 @@
|
||||
import numpy as np
|
||||
import copy
|
||||
|
||||
# 式 (8.4.2) 计算 q
|
||||
def q_pi(p_s_r, gamma, V):
|
||||
q = 0
|
||||
# 遍历每个转移概率,以计算 q
|
||||
for p, s_next, r in p_s_r:
|
||||
# math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q += p * (r + gamma * V[s_next])
|
||||
return q
|
||||
|
||||
# 式 (8.4.5) 计算 v_pi
|
||||
def v_pi(s, actions, gamma, V, Q):
|
||||
list_q = []
|
||||
for a, p_s_r in actions: # 遍历每个动作以计算q值,进而计算v值
|
||||
q = q_pi(p_s_r, gamma, V)
|
||||
list_q.append(q)
|
||||
# 顺便记录下q(s,a)值,不需要再单独计算一次
|
||||
Q[s,a] = q
|
||||
return max(list_q)
|
||||
#V[s] = max(list_q) if len(list_q) > 0 else 0
|
||||
|
||||
def calculate_Vstar(env, gamma, max_iteration):
|
||||
V = np.zeros(env.nS)
|
||||
Q = np.zeros((env.nS, env.nA))
|
||||
count = 0
|
||||
# 迭代
|
||||
while (count < max_iteration):
|
||||
V_old = V.copy()
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
if env.is_end(s): # 终止状态v=0
|
||||
continue
|
||||
# 获得 状态->动作 策略概率
|
||||
actions = env.get_actions(s)
|
||||
V[s] = v_pi(s, actions, gamma, V, Q)
|
||||
# 检查收敛性
|
||||
if abs(V-V_old).max() < 1e-4:
|
||||
break
|
||||
count += 1
|
||||
# end while
|
||||
print("迭代次数 = ",count)
|
||||
return V, Q
|
||||
|
||||
def get_policy(env, V, gamma):
|
||||
policy = np.zeros((env.nS, env.nA))
|
||||
for s in range(env.nS):
|
||||
actions = env.get_actions(s)
|
||||
list_q = []
|
||||
if actions is None:
|
||||
continue
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
q_star = 0
|
||||
for p, s_next, r in next_p_s_r:
|
||||
q_star += p * (r + gamma * V[s_next])
|
||||
list_q.append(q_star)
|
||||
policy[s, np.argmax(list_q)] = 1
|
||||
return policy
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import numpy as np
|
||||
|
||||
# 式 (8.4.2) 计算 q_pi
|
||||
def q_pi(p_s_r, gamma, V):
|
||||
q = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in p_s_r:
|
||||
# math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q += p * (r + gamma * V[s_next])
|
||||
return q
|
||||
|
||||
# 式 (8.4.5) 计算 v_pi
|
||||
def v_pi(policy, s, actions, gamma, V, Q):
|
||||
v = 0
|
||||
for a, p_s_r in actions: # 遍历每个动作以计算q值,进而计算v值
|
||||
q = q_pi(p_s_r, gamma, V)
|
||||
# math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
v += policy[s][a] * q
|
||||
# 顺便记录下q(s,a)值,不需要再单独计算一次
|
||||
Q[s,a] = q
|
||||
return v
|
||||
|
||||
# 迭代法计算 v_pi
|
||||
def calculate_Vpi_Qpi(env, gamma, iteration):
|
||||
V = np.zeros(env.nS) # 初始化 V(s)
|
||||
Q = np.zeros((env.nS, env.nA)) # 初始化 Q(s,a)
|
||||
count = 0 # 计数器,用于衡量性能和避免无限循环
|
||||
# 迭代
|
||||
while (count < iteration):
|
||||
V_old = V.copy() # 保存上一次的值以便检查收敛性
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
if env.is_end(s): # 终止状态v=0
|
||||
continue
|
||||
actions = env.get_actions(s) # 获得当前状态s下的所有可选动作
|
||||
V[s] = v_pi(env.Policy, s, actions, gamma, V, Q)
|
||||
# 检查收敛性
|
||||
if abs(V-V_old).max() < 1e-4:
|
||||
break
|
||||
count += 1
|
||||
# end while
|
||||
#print("迭代次数 = ",count)
|
||||
return V, Q
|
||||
@@ -0,0 +1,76 @@
|
||||
|
||||
import numpy as np
|
||||
|
||||
LEFT = 0x25c4
|
||||
UP = 0x25b2
|
||||
RIGHT = 0x25ba
|
||||
DOWN = 0x25bc
|
||||
SPACE = 0x0020
|
||||
CENTER = 0x253c
|
||||
LINE = 0x2500
|
||||
|
||||
class GridCell(object):
|
||||
def __init__(self, policy):
|
||||
self.space = np.zeros((3,5), dtype=int)
|
||||
self.space.fill(SPACE) # 空格
|
||||
self.space[1,2] = CENTER # 中心
|
||||
self.policy = np.round(policy, 2)
|
||||
|
||||
# policy 是一个1x4的数组或矩阵,如[0.1,0.1,0.4,0.4]
|
||||
# 这种情况要在向上和向右的地方画两个箭头
|
||||
# 01234
|
||||
# 0 ^
|
||||
# 1 o >
|
||||
# 2
|
||||
|
||||
best_actions = np.argwhere(self.policy == np.max(self.policy))
|
||||
pos = best_actions.flatten().tolist()
|
||||
for a in pos:
|
||||
if a == 0: # left
|
||||
self.space[1,0] = LEFT
|
||||
self.space[1,1] = LINE
|
||||
elif a == 1: # up
|
||||
self.space[0,2] = UP
|
||||
elif a == 2: # right
|
||||
self.space[1,3] = LINE
|
||||
self.space[1,4] = RIGHT
|
||||
elif a == 3: # down
|
||||
self.space[2,2] = DOWN
|
||||
|
||||
|
||||
class Grid(object):
|
||||
def __init__(self, q, shape):
|
||||
self.array = np.zeros((shape[0]*3, shape[1]*5), dtype=int)
|
||||
for i in range(len(q)):
|
||||
row = (int)(i / shape[0])
|
||||
col = (int)(i % shape[0])
|
||||
policy = q[i]
|
||||
cell = GridCell(policy)
|
||||
self.array[row*3:row*3+3, col*5:col*5+5] = cell.space
|
||||
|
||||
def draw(q, shape):
|
||||
grid = Grid(q, shape)
|
||||
for j, rows in enumerate(grid.array):
|
||||
if (j % 3 == 0):
|
||||
print("+-----"*shape[1], end="")
|
||||
print("+")
|
||||
print("|", end="")
|
||||
for i,col in enumerate(rows):
|
||||
print(chr(col), end="")
|
||||
if ((i+1) % 5 == 0):
|
||||
print("|", end="")
|
||||
print()
|
||||
print("+-----"*shape[1])
|
||||
|
||||
if __name__=="__main__":
|
||||
q = np.array([
|
||||
[0.49, 0.49, 5.07, 0.58],
|
||||
[5.63, 5.63, 5.63, 5.63],
|
||||
[5.07, 3.06, 7.86 , 2.07],
|
||||
[8.73, 8.73, 8.73 , 8.73]
|
||||
])
|
||||
grid = Grid(q,(2,2))
|
||||
for rows in grid.array:
|
||||
for col in rows:
|
||||
print(chr(col), end="")
|
||||
print()
|
||||
@@ -0,0 +1,258 @@
|
||||
import numpy as np
|
||||
import copy
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
LEFT, UP, RIGHT, DOWN = 0, 1, 2, 3
|
||||
|
||||
class GridWorld(object):
|
||||
# 生成环境
|
||||
def __init__(self,
|
||||
GridWidth, GridHeight, StartStates, EndStates,
|
||||
Actions, Policy, SlipProbs,
|
||||
StepReward, SpecialReward,
|
||||
SpecialMove, Blocks):
|
||||
|
||||
self.Width = GridWidth
|
||||
self.Height = GridHeight
|
||||
self.Actions = Actions
|
||||
self.nS = GridHeight * GridWidth
|
||||
self.nA = len(Actions)
|
||||
self.SpecialReward = SpecialReward
|
||||
self.StartStates = StartStates
|
||||
self.EndStates = EndStates
|
||||
self.SpecialMove = SpecialMove
|
||||
self.Blocks = Blocks
|
||||
self.Policy = self.__init_policy(Policy)
|
||||
self.P_S_R = self.__init_states(SlipProbs, StepReward)
|
||||
|
||||
# 把统一的policy设置复制到每个状态上
|
||||
def __init_policy(self, Policy):
|
||||
PI = {}
|
||||
for s in range(self.nS):
|
||||
PI[s] = Policy
|
||||
return PI
|
||||
|
||||
# 用于生成状态->动作->转移->奖励字典
|
||||
def __init_states(self, Probs, StepReward):
|
||||
P = {}
|
||||
s_id = 0
|
||||
self.Pos2Sid = {}
|
||||
self.Sid2Pos = {}
|
||||
for y in range(self.Height):
|
||||
for x in range(self.Width):
|
||||
self.Pos2Sid[x,y] = s_id
|
||||
self.Sid2Pos[s_id] = [x,y]
|
||||
s_id += 1
|
||||
|
||||
for s, (x,y) in self.Sid2Pos.items():
|
||||
P[s] = {}
|
||||
if (s in self.EndStates):
|
||||
continue
|
||||
for action in self.Actions:
|
||||
list_probs = []
|
||||
for dir, prob in enumerate(Probs):
|
||||
if (prob == 0.0):
|
||||
continue
|
||||
s_next = self.__get_next_state(
|
||||
s, x, y, action + dir - 1) # 处理每一个转移概率,方向逆时针减1
|
||||
reward = StepReward # 通用奖励定义 (-1)
|
||||
if (s, s_next) in self.SpecialReward: # 如果有特殊奖励定义
|
||||
reward = self.SpecialReward[(s, s_next)]
|
||||
list_probs.append((prob, s_next, reward))
|
||||
|
||||
P[s][action] = list_probs
|
||||
return P
|
||||
|
||||
# 用于计算移动后的下一个状态
|
||||
# 左上角为 [0,0], 横向为 x, 纵向为 y
|
||||
def __get_next_state(self, s, x, y, action):
|
||||
action = action % 4 # 避免负数
|
||||
if (s,action) in self.SpecialMove:
|
||||
return self.SpecialMove[(s,action)]
|
||||
|
||||
if (action == UP): # 向上转移
|
||||
if (y != 0): # 不在上方边界处,否则停在原地不动
|
||||
s = s - self.Width
|
||||
elif (action == DOWN): # 向下转移
|
||||
if (y != self.Height-1):# 不在下方边界处,否则停在原地不动
|
||||
s = s + self.Width
|
||||
elif (action == LEFT): # 向左转移
|
||||
if (x != 0): # 不在左侧边界处,否则停在原地不动
|
||||
s = s - 1
|
||||
elif (action == RIGHT): # 向右转移
|
||||
if (x != self.Width-1): # 不在右侧边界处,否则停在原地不动
|
||||
s = s + 1
|
||||
return s
|
||||
|
||||
def is_end(self, s):
|
||||
return (s in self.EndStates)
|
||||
|
||||
def get_actions(self, s):
|
||||
actions = self.P_S_R[s]
|
||||
return actions.items()
|
||||
|
||||
'''
|
||||
# 式 (2.1) 计算 q_pi
|
||||
def q_pi(Psr, gamma, V):
|
||||
q = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in Psr:
|
||||
# math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q += p * (r + gamma * V[s_next])
|
||||
return q
|
||||
|
||||
# 式 (5) 计算 v_pi
|
||||
def v_pi(env: GridWorld, s, gamma, V, Q):
|
||||
actions = env.get_actions(s) # 获得当前状态s下的所有可选动作
|
||||
v = 0
|
||||
for a, p_s_r in actions: # 遍历每个动作以计算q值,进而计算v值
|
||||
q = q_pi(p_s_r, gamma, V)
|
||||
# math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
v += env.Policy[a] * q
|
||||
# 顺便记录下q(s,a)值,不需要再单独计算一次
|
||||
Q[s,a] = q
|
||||
return v
|
||||
|
||||
# 迭代法计算 v_pi
|
||||
def V_in_place_update(env: GridWorld, gamma, iteration):
|
||||
V = np.zeros(env.nS) # 初始化 V(s)
|
||||
Q = np.zeros((env.nS, env.nA)) # 初始化 Q(s,a)
|
||||
count = 0 # 计数器,用于衡量性能和避免无限循环
|
||||
# 迭代
|
||||
while (count < iteration):
|
||||
V_old = V.copy() # 保存上一次的值以便检查收敛性
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
V[s] = v_pi(env, s, gamma, V, Q)
|
||||
# 检查收敛性
|
||||
if abs(V-V_old).max() < 1e-4:
|
||||
break
|
||||
count += 1
|
||||
# end while
|
||||
print("迭代次数 = ",count)
|
||||
return V, Q
|
||||
|
||||
# 双数组迭代
|
||||
def V_pi_2array(env: GridWorld, gamma, iteration):
|
||||
V = np.zeros(env.nS)
|
||||
Q = np.zeros((env.nS, env.nA))
|
||||
|
||||
count = 0
|
||||
# 迭代
|
||||
while (count < iteration):
|
||||
V_old = V.copy()
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
v_pi = 0
|
||||
# 获得 状态->动作 策略概率
|
||||
actions = env.get_actions(s)
|
||||
if actions is not None:
|
||||
# 遍历每个策略概率
|
||||
|
||||
for action, next_p_s_r in actions:
|
||||
# 获得 动作->状态 转移概率
|
||||
q_pi = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in next_p_s_r:
|
||||
# 式2.1 math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q_pi += p * (r + gamma * V_old[s_next])
|
||||
#end for
|
||||
# 式5 math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
Q[s][action] = q_pi
|
||||
v_pi += 0.25 * q_pi
|
||||
# end for
|
||||
V[s] = v_pi
|
||||
#endfor
|
||||
# 检查收敛性
|
||||
if abs(V-V_old).max() < 1e-4:
|
||||
break
|
||||
# 把 V_curr 赋值给 V_next
|
||||
#print(np.reshape(np.round(V,2), (4,4)))
|
||||
count += 1
|
||||
# end while
|
||||
print(count)
|
||||
#print(Q)
|
||||
return V, Q
|
||||
|
||||
|
||||
def V_star(env: GridWorld, gamma, iteration):
|
||||
V_star = np.zeros(env.nS)
|
||||
Q_star = copy.deepcopy(env.Psr) # 拷贝状态转移结构以存储Q(s,a)值
|
||||
count = 0
|
||||
# 迭代
|
||||
while (count < iteration):
|
||||
V_old = V_star.copy()
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
# 获得 状态->动作 策略概率
|
||||
actions = env.get_actions(s)
|
||||
list_q = []
|
||||
if actions is not None:
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
# 获得 动作->状态 转移概率
|
||||
q_star = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in next_p_s_r:
|
||||
# 式2.1 math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q_star += p * (r + gamma * V_star[s_next])
|
||||
#end for
|
||||
# 式5 math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
list_q.append(q_star)
|
||||
Q_star[s][action] = q_star
|
||||
# end for
|
||||
V_star[s] = max(list_q) if len(list_q) > 0 else 0
|
||||
#endfor
|
||||
# 检查收敛性
|
||||
if abs(V_star-V_old).max() < 1e-4:
|
||||
break
|
||||
count += 1
|
||||
# end while
|
||||
print(count)
|
||||
return V_star, Q_star
|
||||
|
||||
def get_policy(env: GridWorld, V, gamma):
|
||||
policy = np.zeros((env.nS, env.nA))
|
||||
for s in range(env.nS):
|
||||
actions = env.get_actions(s)
|
||||
list_q = np.zeros(env.nA)
|
||||
if actions is None:
|
||||
continue
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
q_star = 0
|
||||
for p, s_next, r in next_p_s_r:
|
||||
q_star += p * (r + gamma * V[s_next])
|
||||
list_q[action] = q_star
|
||||
|
||||
policy[s, np.argmax(list_q)] = 1
|
||||
return policy
|
||||
'''
|
||||
action_names = ['LEFT', 'UP', 'RIGHT', 'DOWN']
|
||||
|
||||
def print_P(P):
|
||||
print("状态->动作->转移->奖励 字典:")
|
||||
for s,v in P.items():
|
||||
print("state =",s)
|
||||
for action,v2 in v.items():
|
||||
print("\taction =", action_names[action])
|
||||
print("\t",v2)
|
||||
|
||||
# left, up, right, down
|
||||
chars = [0x2190, 0x2191, 0x2192, 0x2193]
|
||||
# 需要处理多个值相等的情况
|
||||
def print_policy(policy, shape):
|
||||
best_actions = np.argmax(policy, axis=1)
|
||||
for i, action in enumerate(best_actions):
|
||||
print(chr(chars[action]), end="")
|
||||
print(" ", end="")
|
||||
if ((i+1) % shape[0] == 0):
|
||||
print("")
|
||||
|
||||
# 绘图
|
||||
def draw_table(V, shape):
|
||||
tab = plt.table(cellText=V, loc='center', rowHeights=[0.1]*5)
|
||||
tab.scale(1,1)
|
||||
plt.axis('off')
|
||||
plt.show()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import numpy as np
|
||||
import copy
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import Algo_PolicyValueFunction as algo
|
||||
|
||||
if __name__=="__main__":
|
||||
Policy = { # 原始状态
|
||||
0:[0.4,0.6], # 状态0:[动作 0 概率,动作 1 概率]
|
||||
1:[0.4,0.6], # 同上
|
||||
2:[0.4,0.6],
|
||||
3:[0.4,0.6],
|
||||
4:[0.4,0.6],
|
||||
5:[0.4,0.6]
|
||||
}
|
||||
gamma = 1
|
||||
max_iteration = 1000
|
||||
env = dataModel.Env(Policy)
|
||||
V,Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration)
|
||||
print("在原始策略下的状态价值函数值 V:")
|
||||
print(np.round(V,5))
|
||||
print("在原始策略下的动作价值函数值 Q:")
|
||||
print(Q)
|
||||
|
||||
# 新策略
|
||||
test_policy = np.array([
|
||||
[0.2,0.8], # 修改状态 0 的策略
|
||||
[0.5,0.5], # 修改状态 1 的策略
|
||||
[0.3,0.7], # 修改状态 2 的策略
|
||||
[0.1,0.9], # 修改状态 3 的策略
|
||||
[0.3,0.7], # 修改状态 4 的策略
|
||||
[0.6,0.4] # 修改状态 5 的策略
|
||||
])
|
||||
# 每次只修改一个策略,保持其它策略不变,以便观察其影响
|
||||
for i in range(6):
|
||||
print(str.format("修改状态 {0} 的策略:{1}", i, test_policy[i]))
|
||||
new_policy = copy.deepcopy(Policy) # 继承原始策略
|
||||
new_policy[i] = test_policy[i] # 只修改其中一个状态的策略
|
||||
env = dataModel.Env(new_policy)
|
||||
V,Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration)
|
||||
print(np.round(V,5))
|
||||
@@ -0,0 +1,224 @@
|
||||
from enum import Enum
|
||||
|
||||
class States(Enum):
|
||||
Start = 0 # 开始
|
||||
S_Red_R0 = 1 # 脱靶
|
||||
S_Red_R1 = 2 # 小奖
|
||||
S_Red_R3 = 3 # 大奖
|
||||
S_Blue_R0 = 4 # 脱靶
|
||||
S_Blue_R1 = 5 # 小奖
|
||||
T = 6 # 终止
|
||||
|
||||
# 动作空间
|
||||
class Actions(Enum):
|
||||
Red = 0 # 红色小气球,可以中大奖
|
||||
Blue = 1 # 蓝色大气球,可以中小奖
|
||||
|
||||
# 奖励
|
||||
class Rewards(Enum):
|
||||
Zero = 0
|
||||
Small = 1
|
||||
Grand = 3
|
||||
|
||||
P = {
|
||||
# state: {action: [(p, s', r),...]}
|
||||
States.Start.value:{ # 开始状态(第一枪)
|
||||
Actions.Red.value:[ # 选择射击红球
|
||||
(0.80, States.S_Red_R0.value, Rewards.Zero.value ), # 脱靶的概率:0.80, 转移到状态 SR0(s1), 得到0分奖励
|
||||
(0.05, States.S_Red_R1.value, Rewards.Small.value), # 误中蓝球的概率:0.05, 转移到状态 SR1(s2), 得到1分奖励
|
||||
(0.15, States.S_Red_R3.value, Rewards.Grand.value), # 击中红球的概率:0.15, 转移到状态 SR3(s3), 得到3分奖励
|
||||
],
|
||||
Actions.Blue.value:[ # 选择射击蓝球
|
||||
(0.40, States.S_Blue_R0.value, Rewards.Zero.value), # 脱靶的概率:0.40, 转移到状态 SB0(s4), 得到0分奖励
|
||||
(0.60, States.S_Blue_R1.value, Rewards.Small.value) # 击中蓝球的概率:0.6, 转移到状态 SB1(s5), 得到1分奖励
|
||||
]
|
||||
},
|
||||
# 以下为第二枪的选择
|
||||
States.S_Red_R0.value:{ # 第一枪打红球脱靶
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.80, States.T.value, Rewards.Zero.value), # 第二枪脱靶概率
|
||||
(0.05, States.T.value, Rewards.Small.value), # 第二枪误中蓝球的概率
|
||||
(0.15, States.T.value, Rewards.Grand.value), # 第二枪击中红球的概率
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.40, States.T.value, Rewards.Zero.value), # 第二枪脱靶的概率不变
|
||||
(0.60, States.T.value, Rewards.Small.value) # 第二枪击中蓝球概率不变
|
||||
]
|
||||
},
|
||||
States.S_Red_R1.value:{ # 第一枪打红球误中蓝球
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.78, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.05, States.T.value, Rewards.Small.value), # 误中蓝色球
|
||||
(0.17, States.T.value, Rewards.Grand.value), # 击中红球率升高
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.45, States.T.value, Rewards.Zero.value), # 脱靶率升高
|
||||
(0.55, States.T.value, Rewards.Small.value) # 击中蓝球率降低
|
||||
]
|
||||
},
|
||||
States.S_Red_R3.value:{ # 第一枪打红球击中大奖
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.70, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.05, States.T.value, Rewards.Small.value), # 误中蓝色球
|
||||
(0.25, States.T.value, Rewards.Grand.value), # 击中红球率升高
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.20, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.80, States.T.value, Rewards.Small.value) # 击中蓝球率提高
|
||||
]
|
||||
},
|
||||
States.S_Blue_R0.value:{ # 第一枪打蓝脱靶
|
||||
Actions.Red.value:[ # 继续选择射击红球
|
||||
(0.80, States.T.value, Rewards.Zero.value), # 脱靶率不变
|
||||
(0.05, States.T.value, Rewards.Small.value), # 误中蓝球
|
||||
(0.15, States.T.value, Rewards.Grand.value), # 击中红球率不变
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.40, States.T.value, Rewards.Zero.value), # 脱靶概率不变
|
||||
(0.60, States.T.value, Rewards.Small.value) # 击中蓝球率不变
|
||||
]
|
||||
},
|
||||
States.S_Blue_R1.value:{ # 第一枪打蓝球中小奖
|
||||
Actions.Red.value:[ # 选择射击红球
|
||||
(0.74, States.T.value, Rewards.Zero.value), # 脱靶率降低
|
||||
(0.04, States.T.value, Rewards.Small.value), # 误中蓝球
|
||||
(0.22, States.T.value, Rewards.Grand.value), # 击中目标红球率升高
|
||||
],
|
||||
Actions.Blue.value:[ # 第二枪选择射击蓝球
|
||||
(0.25, States.T.value, Rewards.Zero.value), # 脱靶率很低
|
||||
(0.75, States.T.value, Rewards.Small.value) # 击中蓝球率很高
|
||||
]
|
||||
},
|
||||
States.T.value:{ # 终止
|
||||
Actions.Red.value:[
|
||||
(1.0, States.T.value, 0)
|
||||
],
|
||||
Actions.Blue.value:[
|
||||
(1.0, States.T.value, 0)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class Env(object):
|
||||
def __init__(self, policy):
|
||||
self.nS = len(States)
|
||||
self.nA = len(Actions)
|
||||
self.S = States
|
||||
self.A = Actions
|
||||
self.P = P
|
||||
self.Policy = policy
|
||||
self.end_states = [States.T.value]
|
||||
|
||||
def get_actions(self, s):
|
||||
actions = self.P[s]
|
||||
return actions.items()
|
||||
|
||||
def is_end(self,s):
|
||||
if s in self.end_states:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
Policy = {
|
||||
0:[0.4,0.6], # 在状态 0 时,选择红球的概率0.4,选择蓝球的概率0.6
|
||||
1:[0.4,0.6], # 在状态 1 时,同上
|
||||
2:[0.4,0.6],
|
||||
3:[0.4,0.6],
|
||||
4:[0.4,0.6],
|
||||
5:[0.4,0.6]
|
||||
}
|
||||
env = Env(Policy)
|
||||
# 统计概率
|
||||
# 第一枪脱靶的概率
|
||||
p0 = 0.4 * P[States.Start.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.Start.value][Actions.Blue.value][0][0]
|
||||
print("第一枪脱靶", p0)
|
||||
|
||||
# 第一枪脱靶后第二枪也脱靶
|
||||
p00 = 0.4 * (0.4 * P[States.S_Red_R0.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Red_R0.value][Actions.Blue.value][0][0]) \
|
||||
+ 0.6 * (0.4 * P[States.S_Blue_R0.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Blue_R0.value][Actions.Blue.value][0][0])
|
||||
# 第一枪脱靶后第二枪中小奖
|
||||
p01 = 0.4 * (0.4 * P[States.S_Red_R0.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Red_R0.value][Actions.Blue.value][1][0]) \
|
||||
+ 0.6 * (0.4 * P[States.S_Blue_R0.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Blue_R0.value][Actions.Blue.value][1][0])
|
||||
# 第一枪脱靶后第二枪中大奖
|
||||
p02 = 0.4 * (0.4 * P[States.S_Red_R0.value][Actions.Red.value][2][0]) \
|
||||
+ 0.6 * (0.4 * P[States.S_Blue_R0.value][Actions.Red.value][2][0])
|
||||
|
||||
p00 = round(p00,3)
|
||||
p01 = round(p01,3)
|
||||
p02 = round(p02,3)
|
||||
print("\t第二枪脱靶",p00)
|
||||
print("\t第二枪小奖",p01)
|
||||
print("\t第二枪大奖",p02)
|
||||
#assert((p00+p01+p02)==1)
|
||||
|
||||
########################################
|
||||
|
||||
# 第一枪中小奖的概率
|
||||
p1 = 0.4 * P[States.Start.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.Start.value][Actions.Blue.value][1][0]
|
||||
print("第一枪小奖", p1)
|
||||
|
||||
# 第一枪小奖后第二枪脱靶
|
||||
p10 = 0.4 * (0.4 * P[States.S_Red_R1.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Red_R1.value][Actions.Blue.value][0][0]) \
|
||||
+ 0.6 * (0.4 * P[States.S_Blue_R1.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Blue_R1.value][Actions.Blue.value][0][0])
|
||||
# 第一枪小奖后第二枪中小奖
|
||||
p11 = 0.4 * (0.4 * P[States.S_Red_R1.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Red_R1.value][Actions.Blue.value][1][0]) \
|
||||
+ 0.6 * (0.4 * P[States.S_Blue_R1.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Blue_R1.value][Actions.Blue.value][1][0])
|
||||
# 第一枪小奖后第二枪中大奖
|
||||
p12 = 0.4 * (0.4 * P[States.S_Red_R1.value][Actions.Red.value][2][0]) \
|
||||
+ 0.6 * (0.4 * P[States.S_Blue_R1.value][Actions.Red.value][2][0])
|
||||
p10 = round(p10,3)
|
||||
p11 = round(p11,3)
|
||||
p12 = round(p12,3)
|
||||
print("\t第二枪脱靶",p10)
|
||||
print("\t第二枪小奖",p11)
|
||||
print("\t第二枪大奖",p12)
|
||||
#assert((p10+p11+p12)==1)
|
||||
|
||||
########################################
|
||||
|
||||
# 第一枪中大奖的概率
|
||||
p2 = 0.4 * P[States.Start.value][Actions.Red.value][2][0]
|
||||
p2 = round(p2,3)
|
||||
print("第一枪大奖",p2)
|
||||
|
||||
# 第一枪大奖后第二枪脱靶
|
||||
p20 = 0.4 * P[States.S_Red_R3.value][Actions.Red.value][0][0] \
|
||||
+ 0.6 * P[States.S_Red_R3.value][Actions.Blue.value][0][0]
|
||||
# 第一枪大奖后第二枪中小奖
|
||||
p21 = 0.4 * P[States.S_Red_R3.value][Actions.Red.value][1][0] \
|
||||
+ 0.6 * P[States.S_Red_R3.value][Actions.Blue.value][1][0]
|
||||
# 第一枪大奖后第二枪中大奖
|
||||
p22 = 0.4 * P[States.S_Red_R3.value][Actions.Red.value][2][0]
|
||||
|
||||
p20 = round(p20,3)
|
||||
p21 = round(p21,3)
|
||||
p22 = round(p22,3)
|
||||
print("\t第二枪脱靶",p20)
|
||||
print("\t第二枪小奖",p21)
|
||||
print("\t第二枪大奖",p22)
|
||||
#assert((p20+p21+p22)==1)
|
||||
#assert((p0+p1+p2)==1)
|
||||
|
||||
assert(p00 == p0)
|
||||
assert(p01 == p1)
|
||||
assert(p02 == p2)
|
||||
assert(p10 <= p0)
|
||||
assert(p11 >= p1)
|
||||
assert(p12 >= p2)
|
||||
assert(p20 <= p0)
|
||||
assert(p21 >= p1)
|
||||
assert(p22 >= p2)
|
||||
assert(p21 >= p11)
|
||||
assert(p21 >= p21)
|
||||
@@ -0,0 +1,104 @@
|
||||
from email import policy
|
||||
import numpy as np
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import math
|
||||
import Algo_PolicyValueFunction as algo
|
||||
import copy
|
||||
|
||||
# 给指定的policy的前n/2个填0,后n/2个填1
|
||||
def fill_number(policy, pos, n):
|
||||
for i in range(n):
|
||||
if i < n/2:
|
||||
policy[i,pos] = 0
|
||||
else:
|
||||
policy[i,pos] = 1
|
||||
|
||||
|
||||
def create_binary_policy():
|
||||
n = (int)(math.pow(2, 6))
|
||||
policy = np.zeros((n, 6), dtype=np.int32)
|
||||
pos = 0
|
||||
while True:
|
||||
count = (int)(math.pow(2,pos))
|
||||
for i in range(count):
|
||||
start = i * n
|
||||
end = (i+1) * n
|
||||
fill_number(policy[start:end], pos, n)
|
||||
n = (int)(n/2)
|
||||
pos += 1
|
||||
if n == 1:
|
||||
break
|
||||
|
||||
return policy
|
||||
|
||||
|
||||
def test():
|
||||
list2 = []
|
||||
n = 3
|
||||
count = 0
|
||||
while count < n:
|
||||
list0 = copy.deepcopy(list2)
|
||||
list1 = copy.deepcopy(list2)
|
||||
if len(list0) == 0:
|
||||
list0.append(0)
|
||||
list1.append(1)
|
||||
else:
|
||||
for item in list0:
|
||||
item.append(0)
|
||||
for item in list1:
|
||||
item.append(1)
|
||||
list2 = []
|
||||
for item in list0:
|
||||
list2.append([item])
|
||||
for item in list1:
|
||||
list2.append([item])
|
||||
count += 1
|
||||
print(list2)
|
||||
|
||||
# 创建onehot编码形式的policy,如 [[0,1],[1,0],[1,0]...]
|
||||
def create_onehot_policy(actions):
|
||||
policy = {}
|
||||
for s in range(len(actions)): # onehot
|
||||
policy[s] = [1,0] if actions[s]==0 else [0,1]
|
||||
return policy
|
||||
|
||||
# 搜索最优策略组合
|
||||
def find_best_policy(V_values, all_policy_in_binary):
|
||||
v = np.array(V_values) # 列表变成数组
|
||||
v0_best = np.max(v[:,0]) # 获得所有策略组合中 v(s0) 的最大值
|
||||
print("v(s0)的最优价值函数 :", v0_best)
|
||||
best_ids = np.argwhere(v[:,0] == v0_best) # 获得所有的最大值的策略组合序号
|
||||
for id in best_ids:
|
||||
print("====")
|
||||
print(str.format("策略组合({0}):{1}", id[0], all_policy_in_binary[id[0]]))
|
||||
print("状态价值函数 :", v[id][0])
|
||||
return best_ids
|
||||
|
||||
if __name__=="__main__":
|
||||
|
||||
#test()
|
||||
#exit(0)
|
||||
|
||||
all_policy_in_binary = create_binary_policy() # 二进制形式
|
||||
print(all_policy_in_binary)
|
||||
# 遍历所有策略组合
|
||||
gamma = 1
|
||||
max_iteration = 1000
|
||||
V_values = []
|
||||
for id, actions in enumerate(all_policy_in_binary):
|
||||
policy = create_onehot_policy(actions) # onehot形式
|
||||
print(str.format("策略组合({0}): {1}", id, policy))
|
||||
env = dataModel.Env(policy) # 创建环境,代入策略组合
|
||||
V, Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 迭代法计算V,Q
|
||||
V_values.append(V) # 保存每个策略组合的价值函数结果,便于比较
|
||||
print("状态价值函数 :", V)
|
||||
|
||||
# 输出最优策略的价值函数
|
||||
best_ids = find_best_policy(V_values, all_policy_in_binary)
|
||||
for id in best_ids:
|
||||
policy = create_onehot_policy(all_policy_in_binary[id[0]]) # onehot形式
|
||||
print(str.format("策略组合({0}): {1}", id[0], policy))
|
||||
env = dataModel.Env(policy) # 创建环境,代入策略组合
|
||||
V, Q = algo.calculate_Vpi_Qpi(env, gamma, max_iteration) # 迭代法计算V,Q
|
||||
print("最优状态价值函数 :", V)
|
||||
print("最优动作价值函数 :\n", Q)
|
||||
@@ -0,0 +1,18 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import copy
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import Algo_OptimalValueFunction as algo
|
||||
|
||||
if __name__=="__main__":
|
||||
env = dataModel.Env(None)
|
||||
gamma = 1
|
||||
max_iteration = 100
|
||||
V_star, Q_star = algo.calculate_Vstar(env, gamma, max_iteration)
|
||||
print(np.round(V_star,5))
|
||||
policy = algo.get_policy(env, V_star, gamma)
|
||||
print(policy)
|
||||
print(Q_star)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import GridWorld_Model as model
|
||||
|
||||
# 状态空间 = 空间宽度 x 空间高度
|
||||
GridWidth, GridHeight = 5, 5
|
||||
# 起点,可以多个
|
||||
StartStates = []
|
||||
# 终点,可以多个
|
||||
EndStates = []
|
||||
# 动作空间
|
||||
LEFT, UP, RIGHT, DOWN = 0, 1, 2, 3
|
||||
Actions = [LEFT, UP, RIGHT, DOWN]
|
||||
# 初始策略
|
||||
Policy = [0.25, 0.25, 0.25, 0.25]
|
||||
# 转移概率: [SlipLeft, MoveFront, SlipRight, SlipBack]
|
||||
SlipProbs = [0.0, 1.0, 0.0, 0.0]
|
||||
# 每走一步的奖励值,可以是0或者-1
|
||||
StepReward = 0
|
||||
# 特殊奖励 from s->s' then get r, 其中 s,s' 为状态序号,不是坐标位置
|
||||
SpecialReward = {
|
||||
(0,0):-1, # s0 -> s0 得到-1奖励
|
||||
(2,2):-1,
|
||||
(4,4):-1,
|
||||
(5,5):-1,
|
||||
(9,9):-1,
|
||||
(10,10):-1,
|
||||
(14,14):-1,
|
||||
(15,15):-1,
|
||||
(19,19):-1,
|
||||
(20,20):-1,
|
||||
(21,21):-1,
|
||||
(22,22):-1,
|
||||
(23,23):-1,
|
||||
(24,24):-1,
|
||||
(1,12):+5,
|
||||
(3,21):+10
|
||||
}
|
||||
# 特殊移动,用于处理类似虫洞场景
|
||||
SpecialMove = {
|
||||
(1,LEFT):12,
|
||||
(1,UP):12,
|
||||
(1,RIGHT):12,
|
||||
(1,DOWN):12,
|
||||
(3,LEFT):21,
|
||||
(3,UP):21,
|
||||
(3,RIGHT):21,
|
||||
(3,DOWN):21
|
||||
}
|
||||
# 墙
|
||||
Blocks = []
|
||||
|
||||
if __name__=="__main__":
|
||||
env = model.GridWorld(
|
||||
GridWidth, GridHeight, StartStates, EndStates, # 关于状态的参数
|
||||
Actions, Policy, SlipProbs, # 关于动作的参数
|
||||
StepReward, SpecialReward, # 关于奖励的参数
|
||||
SpecialMove, Blocks) # 关于移动的限制
|
||||
model.print_P(env.P_S_R)
|
||||
@@ -0,0 +1,27 @@
|
||||
import numpy as np
|
||||
import Wormhole_0_Data as data # 数据定义
|
||||
import GridWorld_Model as model # 模型逻辑
|
||||
import Algo_PolicyValueFunction as algo # 算法实现
|
||||
import DrawQpi as drawQ # 结果输出
|
||||
|
||||
if __name__=="__main__":
|
||||
env = model.GridWorld(
|
||||
# 关于状态的参数
|
||||
data.GridWidth, data.GridHeight, data.StartStates, data.EndStates,
|
||||
# 关于动作的参数
|
||||
data.Actions, data.Policy, data.SlipProbs,
|
||||
# 关于奖励的参数
|
||||
data.StepReward, data.SpecialReward,
|
||||
# 关于移动的限制
|
||||
data.SpecialMove, data.Blocks)
|
||||
|
||||
gamma = 0.9 # 折扣,在本例中用1.0可以收敛,但是用0.9比较保险
|
||||
iteration = 1000 # 算法最大迭代次数
|
||||
V_pi, Q_pi = algo.calculate_Vpi_Qpi(env, gamma, iteration) # 原地更新的迭代算法
|
||||
print("V_pi")
|
||||
V = np.reshape(np.round(V_pi,3), (data.GridWidth, data.GridHeight))
|
||||
print(V)
|
||||
print("Q_pi")
|
||||
print(np.round(Q_pi,3))
|
||||
# 字符图形化显示
|
||||
drawQ.draw(Q_pi, (data.GridWidth, data.GridHeight))
|
||||
@@ -0,0 +1,179 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import copy
|
||||
|
||||
# 状态空间
|
||||
Num_States = 31
|
||||
|
||||
#class States(Enum):
|
||||
# Start = 0 # 开始
|
||||
# Grand = 1 # 大奖
|
||||
# Miss = 2 # 脱靶
|
||||
# Small = 3 # 小奖
|
||||
|
||||
|
||||
# 动作空间
|
||||
class Actions(Enum):
|
||||
Red = 0 # 红色小气球,可以中大奖
|
||||
Blue = 1 # 蓝色大气球,可以中小奖
|
||||
|
||||
# 奖励
|
||||
class Rewards(Enum):
|
||||
Zero = 0
|
||||
Small = 1
|
||||
Grand = 3
|
||||
|
||||
|
||||
P = {
|
||||
# curr state
|
||||
0:{ # 开始
|
||||
# action:[(p, s', r), (p, s', r)]
|
||||
0:[(0.20, 1, 3), (0.75, 2, 0), (0.05, 3, 1)], # 第一次击中目标红球的概率=0.2
|
||||
1:[(0.40, 4, 0), (0.60, 5, 1)] # 第一次击中目标兰球的概率=0.6
|
||||
},
|
||||
1:{ # 第一次击中目标红球
|
||||
0:[(0.25, 6, 3), (0.70, 7, 0), (0.05, 8, 1)], # 第二次击中红球的概率=0.25,提高了
|
||||
1:[(0.35, 9, 0), (0.65, 10, 1)] # 第二次击中兰球的概率=0.65,提高了
|
||||
},
|
||||
2:{ # 第一次打红球脱靶
|
||||
0:[(0.20, 11, 3), (0.75, 12, 0), (0.05, 13, 1)], # 第二次击中红球的概率=0.20,没变化
|
||||
1:[(0.40, 14, 0), (0.60, 15, 1)] # 第二次击中兰球的概率=0.60,没变化
|
||||
},
|
||||
3:{ # 第一次误中兰球
|
||||
0:[(0.18, 16, 3), (0.77, 17, 0), (0.05, 18, 1)], # 第二次击中红球的概率=0.18,降低了
|
||||
1:[(0.45, 19, 0), (0.55, 20, 1)] # 第二次击中兰球的概率=0.55,降低了
|
||||
},
|
||||
4:{ # 第一次打兰球脱靶
|
||||
0:[(0.20, 21, 3), (0.75, 22, 0), (0.05, 23, 1)], # 第二次击中红球的概率=0.20,没变化
|
||||
1:[(0.35, 24, 0), (0.65, 25, 1)] # 第一次击中兰球的概率=0.65,提高了
|
||||
},
|
||||
5:{ # 第一次击中目标兰球
|
||||
0:[(0.22, 26, 3), (0.73, 27, 0), (0.05, 28, 1)], # 第二次击中红球的概率=0.22,提高了
|
||||
1:[(0.25, 29, 0), (0.75, 30, 1)] # 第二次击中兰球的概率=0.75,提高了
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Env(object):
|
||||
def __init__(self):
|
||||
self.nS = 31
|
||||
self.nA = 2 # red | blue
|
||||
self.P = P
|
||||
self.Policy = np.zeros((6, self.nA))
|
||||
self.Policy[0:6,0] = np.random.randint(0,2,6) # 随机生成 0/1 表示动作序号作为初始策略
|
||||
self.Policy[0:6,1] = 1 - self.Policy[0:6,0]
|
||||
|
||||
def reset(self, from_start = True):
|
||||
if (from_start):
|
||||
return self.States.Start.value
|
||||
else:
|
||||
idx = np.random.choice(self.state_space)
|
||||
return idx
|
||||
|
||||
def get_actions(self, s):
|
||||
if (s < 6):
|
||||
actions = self.P[s]
|
||||
return actions.items()
|
||||
else: # 状态 s6 以下没有后续动作
|
||||
return None
|
||||
|
||||
def get_states(self, a):
|
||||
for s, actions in self.P.items():
|
||||
if actions.__contains__(a):
|
||||
return actions[a]
|
||||
|
||||
def step(self, curr_state, action):
|
||||
probs = self.P[curr_state][action]
|
||||
if (len(probs) == 1):
|
||||
return self.P[curr_state][action][0]
|
||||
else:
|
||||
idx = np.random.choice(3, p=self.transition)
|
||||
return self.P[curr_state][action][idx]
|
||||
|
||||
|
||||
def policy_evaluation(env: Env, gamma):
|
||||
V_curr = [0.0] * env.nS
|
||||
V_next = [0.0] * env.nS
|
||||
Q = copy.deepcopy(env.P) # 拷贝状态转移结构以存储Q(s,a)值
|
||||
count = 0
|
||||
# 迭代
|
||||
while (True):
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
v_pi = 0
|
||||
# 获得 状态->动作 策略概率
|
||||
actions = env.get_actions(s)
|
||||
if actions is None:
|
||||
continue
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
# 获得 动作->状态 转移概率
|
||||
q_pi = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in next_p_s_r:
|
||||
# 式2.1 math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q_pi += p * (r + gamma * V_curr[s_next])
|
||||
#end for
|
||||
# 式5 math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
v_pi += env.Policy[s,action] * q_pi
|
||||
# 顺便把 q 记住,免得以后再算一遍
|
||||
Q[s][action] = q_pi
|
||||
# end for
|
||||
V_next[s] = v_pi
|
||||
#endfor
|
||||
# 检查收敛性
|
||||
if np.allclose(V_next, V_curr):
|
||||
break
|
||||
# 把 V_curr 赋值给 V_next
|
||||
V_curr = V_next.copy()
|
||||
count += 1
|
||||
# end while
|
||||
print("迭代次数 = ",count)
|
||||
#print(Q)
|
||||
return V_next
|
||||
|
||||
def policy_improvement(env:Env, V, gamma):
|
||||
policy = np.zeros((6, env.nA))
|
||||
for s in range(env.nS):
|
||||
actions = env.get_actions(s)
|
||||
list_q = [0] * env.nA
|
||||
if actions is None:
|
||||
continue
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
q = 0
|
||||
for p, s_next, r in next_p_s_r:
|
||||
q += p * (r + gamma * V[s_next])
|
||||
list_q[action] = q
|
||||
best_action = np.argmax(list_q)
|
||||
policy[s, best_action] = 1
|
||||
return policy
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
env = Env()
|
||||
print("初始策略:")
|
||||
print(env.Policy)
|
||||
gamma = 1
|
||||
count = 0
|
||||
while True:
|
||||
count += 1
|
||||
print(count)
|
||||
print("策略评估")
|
||||
V = policy_evaluation(env, gamma)
|
||||
print("策略改进")
|
||||
new_policy = policy_improvement(env, V, gamma)
|
||||
if (new_policy == env.Policy).all():
|
||||
break
|
||||
else:
|
||||
print(new_policy)
|
||||
env.Policy = new_policy.copy()
|
||||
print("最终策略")
|
||||
print(env.Policy)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
@@ -1,56 +0,0 @@
|
||||
from tokenize import Special
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import GridWorldAgent2 as agent2
|
||||
|
||||
# 状态空间(尺寸)S,终点目标T,起点S,障碍B,奖励R,动作空间A,转移概率P
|
||||
|
||||
# 空间宽度
|
||||
GridWidth = 2
|
||||
# 空间高度
|
||||
GridHeight = 2
|
||||
# 起点
|
||||
#Start = [0,0]
|
||||
# 墙
|
||||
#Block = [[1,1]]
|
||||
# end with reward,如果r=0,则StepReward=-1
|
||||
EndStates = [3]
|
||||
|
||||
# 每走一步都-1,如果配置为0,则不减1,而是要在End处得到最终奖励
|
||||
StepReward = 0
|
||||
|
||||
SpecialReward = {
|
||||
(0,2):-1,
|
||||
(1,3):+1,
|
||||
(2,3):+1
|
||||
}
|
||||
|
||||
SpecialMove = {}
|
||||
|
||||
UP, RIGHT, DOWN, LEFT = 0, 1, 2, 3
|
||||
|
||||
# 动作空间
|
||||
Actions = [UP, RIGHT, DOWN, LEFT]
|
||||
|
||||
# 初始策略
|
||||
Pi = [0.25, 0.25, 0.25, 0.25]
|
||||
|
||||
# 转移概率
|
||||
# SlipLeft, MoveFront, SlipRight, SlipBack
|
||||
Probs = [0.0, 1.0, 0.0, 0.0]
|
||||
|
||||
if __name__=="__main__":
|
||||
env = agent2.GridWorld(GridWidth, GridHeight, Actions, SpecialReward, Probs, StepReward, EndStates, SpecialMove, Pi)
|
||||
gamma = 1
|
||||
iteration = 1000
|
||||
V_pi = agent2.V_pi_2array(env, gamma, iteration)
|
||||
print(np.reshape(np.round(V_pi,2), (GridWidth, GridHeight)))
|
||||
|
||||
V_star, Q_star = agent2.V_star(env, gamma)
|
||||
print("V*")
|
||||
print(np.reshape(np.round(V_star,3), (GridWidth,GridHeight)))
|
||||
print("Q*")
|
||||
agent2.print_P(Q_star)
|
||||
|
||||
policy = agent2.get_policy(env, V_star, gamma)
|
||||
agent2.print_policy(policy, (GridWidth, GridHeight))
|
||||
@@ -0,0 +1,41 @@
|
||||
@@ -1,40 +0,0 @@
|
||||
from tokenize import Special
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import GridWorldAgent2 as agent2
|
||||
|
||||
# 状态空间(尺寸)S,终点目标T,起点S,障碍B,奖励R,动作空间A,转移概率P
|
||||
|
||||
# 空间宽度
|
||||
GridWidth = 4
|
||||
# 空间高度
|
||||
GridHeight = 4
|
||||
# 起点
|
||||
#Start = [0,0]
|
||||
# 墙
|
||||
#Block = [[1,1]]
|
||||
# end with reward,如果r=0,则StepReward=-1
|
||||
EndStates = [0, 15]
|
||||
|
||||
# 每走一步都-1,如果配置为0,则不减1,而是要在End处得到最终奖励
|
||||
StepReward = -1
|
||||
|
||||
SpecialReward = {}
|
||||
|
||||
SpecialMove = {}
|
||||
|
||||
UP, RIGHT, DOWN, LEFT = 0, 1, 2, 3
|
||||
|
||||
# 动作空间
|
||||
Actions = [UP, RIGHT, DOWN, LEFT]
|
||||
|
||||
# 转移概率
|
||||
# SlipLeft, MoveFront, SlipRight, SlipBack
|
||||
Probs = [0.0, 1.0, 0.0, 0.0]
|
||||
|
||||
if __name__=="__main__":
|
||||
env = agent2.GridWorld(GridWidth, GridHeight, Actions, SpecialReward, Probs, StepReward, EndStates, SpecialMove)
|
||||
gamma = 1
|
||||
iteration = 1000
|
||||
V_pi = agent2.V_pi_2array(env, gamma, iteration)
|
||||
print(np.reshape(np.round(V_pi,2), (GridWidth, GridHeight)))
|
||||
@@ -0,0 +1,68 @@
|
||||
@@ -1,67 +0,0 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import GridWorldAgent2 as agent2
|
||||
|
||||
# 状态空间(尺寸)S,终点目标T,起点S,障碍B,奖励R,动作空间A,转移概率P
|
||||
|
||||
# 空间宽度
|
||||
GridWidth = 4
|
||||
# 空间高度
|
||||
GridHeight = 4
|
||||
# 起点
|
||||
#Start = [0,0]
|
||||
# 终点
|
||||
#End = [(0,2),()]
|
||||
# 墙
|
||||
#Block = [[1,1]]
|
||||
# end with reward,如果r=0,则StepReward=-1
|
||||
|
||||
SpecialReward = {
|
||||
(1,2):-1,
|
||||
(3,2):-1,
|
||||
(6,2):-1,
|
||||
(4,8):-1,
|
||||
(9,8):-1,
|
||||
(11,8):-1,
|
||||
(6,10):-1,
|
||||
(9,10):-1,
|
||||
(11,10):-1,
|
||||
(14,10):-1,
|
||||
(14,15):+5,
|
||||
(11,15):+5,
|
||||
}
|
||||
|
||||
EndStates = [2,8,10,15]
|
||||
|
||||
UP, RIGHT, DOWN, LEFT = 0, 1, 2, 3
|
||||
|
||||
# 每走一步都-1,如果配置为0,则不减1,而是要在End处得到最终奖励
|
||||
StepReward = 0
|
||||
|
||||
SpecialMove = {}
|
||||
|
||||
# 动作空间
|
||||
Actions = [UP, RIGHT, DOWN, LEFT]
|
||||
|
||||
# 转移概率
|
||||
# SlipLeft, MoveFront, SlipRight, SlipBack
|
||||
Probs = [0.2, 0.7, 0.1, 0.0]
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
env = agent2.GridWorld(GridWidth, GridHeight, Actions, SpecialReward, Probs, StepReward, EndStates, SpecialMove)
|
||||
#agent2.print_P(env.P)
|
||||
gamma = 0.9
|
||||
iteration = 1000
|
||||
print("V_pi")
|
||||
V_pi = agent2.V_pi_2array(env, gamma, iteration)
|
||||
print(np.reshape(np.round(V_pi,3), (GridWidth,GridHeight)))
|
||||
|
||||
V_star, Q_star = agent2.V_star(env, gamma)
|
||||
print("V*")
|
||||
print(np.reshape(np.round(V_star,3), (GridWidth,GridHeight)))
|
||||
print("Q*")
|
||||
agent2.print_P(Q_star)
|
||||
|
||||
policy = agent2.get_policy(env, V_star, gamma)
|
||||
agent2.print_policy(policy, (GridWidth, GridHeight))
|
||||
@@ -0,0 +1,39 @@
|
||||
@@ -1,39 +0,0 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import GridWorldAgent2 as agent2
|
||||
|
||||
# 状态空间(尺寸)S,终点目标T,起点S,障碍B,奖励R,动作空间A,转移概率P
|
||||
|
||||
# 空间宽度
|
||||
GridWidth = 4
|
||||
# 空间高度
|
||||
GridHeight = 4
|
||||
# 起点
|
||||
#Start = [0,0]
|
||||
# 终点
|
||||
EndStates = [15]
|
||||
# 墙
|
||||
#Block = [[1,1]]
|
||||
# 每走一步都-1,如果配置为0,则不减1,而是要在End处得到最终奖励
|
||||
StepReward = -1
|
||||
|
||||
SpecialReward = {}
|
||||
|
||||
SpecialMove = {}
|
||||
|
||||
UP, RIGHT, DOWN, LEFT = 0, 1, 2, 3
|
||||
|
||||
Actions = [UP, RIGHT, DOWN, LEFT]
|
||||
|
||||
# 转移概率
|
||||
# SlipLeft, MoveFront, SlipRight, SlipBack
|
||||
Probs = [0.0, 1.0, 0.0, 0.0]
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
env = agent2.GridWorld(GridWidth, GridHeight, Actions, SpecialReward, Probs, StepReward, EndStates, SpecialMove)
|
||||
gamma = 1
|
||||
iteration = 3
|
||||
V_pi = agent2.V_pi_2array(env, gamma, iteration)
|
||||
print(np.reshape(np.round(V_pi,2), (4,4)))
|
||||
@@ -0,0 +1,87 @@
|
||||
@@ -1,86 +0,0 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import GridWorldAgent2 as agent2
|
||||
|
||||
# 状态空间(尺寸)S,终点目标T,起点S,障碍B,奖励R,动作空间A,转移概率P
|
||||
|
||||
# 空间宽度
|
||||
GridWidth = 5
|
||||
# 空间高度
|
||||
GridHeight = 5
|
||||
# 起点
|
||||
StartStates = []
|
||||
# 终点
|
||||
EndStates = []
|
||||
|
||||
LEFT, UP, RIGHT, DOWN = 0, 1, 2, 3
|
||||
# 动作空间
|
||||
Actions = [LEFT, UP, RIGHT, DOWN]
|
||||
Policy = [0.25, 0.25, 0.25, 0.25]
|
||||
# 转移概率
|
||||
# SlipLeft, MoveFront, SlipRight, SlipBack
|
||||
SlipProbs = [0.0, 1.0, 0.0, 0.0]
|
||||
|
||||
# 每走一步都-1,如果配置为0,则不减1,而是要在End处得到最终奖励
|
||||
StepReward = 0
|
||||
# from s->s', get r
|
||||
# s,s' 为状态序号,不是坐标位置
|
||||
SpecialReward = {
|
||||
(0,0):-1,
|
||||
(1,1):-1,
|
||||
(2,2):-1,
|
||||
(3,3):-1,
|
||||
(4,4):-1,
|
||||
(5,5):-1,
|
||||
(9,9):-1,
|
||||
(10,10):-1,
|
||||
(14,14):-1,
|
||||
(15,15):-1,
|
||||
(19,19):-1,
|
||||
(20,20):-1,
|
||||
(21,21):-1,
|
||||
(22,22):-1,
|
||||
(23,23):-1,
|
||||
(24,24):-1,
|
||||
(1,21):+10,
|
||||
(3,13):+5
|
||||
}
|
||||
|
||||
# 特殊移动,用于处理类似虫洞场景
|
||||
SpecialMove = {
|
||||
(1,LEFT):21,
|
||||
(1,UP):21,
|
||||
(1,RIGHT):21,
|
||||
(1,DOWN):21,
|
||||
(3,LEFT):13,
|
||||
(3,UP):13,
|
||||
(3,RIGHT):13,
|
||||
(3,DOWN):13
|
||||
}
|
||||
Blocks = []
|
||||
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
env = agent2.GridWorld(
|
||||
GridWidth, GridHeight, StartStates, EndStates, # 关于状态的参数
|
||||
Actions, Policy, SlipProbs, # 关于动作的参数
|
||||
StepReward, SpecialReward, # 关于奖励的参数
|
||||
SpecialMove, Blocks) # 关于移动的限制
|
||||
agent2.print_P(env.Psr)
|
||||
gamma = 0.9
|
||||
iteration = 1000
|
||||
V_pi2, Q_pi2 = agent2.V_pi_2array(env, gamma, iteration)
|
||||
V_pi, Q_pi = agent2.V_in_place_update(env, gamma, iteration)
|
||||
assert(np.allclose(V_pi, V_pi2, rtol=1e-2))
|
||||
assert(np.allclose(Q_pi, Q_pi2, rtol=1e-2))
|
||||
print(np.reshape(np.round(V_pi,2), (GridWidth,GridHeight)))
|
||||
|
||||
V_star, Q_star = agent2.V_star(env, gamma, iteration)
|
||||
print("V*")
|
||||
print(np.reshape(np.round(V_star,2), (GridWidth,GridHeight)))
|
||||
print("Q*")
|
||||
agent2.print_P(Q_star)
|
||||
|
||||
policy = agent2.get_policy(env, V_star, gamma)
|
||||
agent2.print_policy(policy, (GridWidth, GridHeight))
|
||||
@@ -0,0 +1,111 @@
|
||||
@@ -1,111 +0,0 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
import copy
|
||||
import Shoot_2_DataModel as dataModel
|
||||
import Algo_OptimalValueFunction as algo
|
||||
|
||||
'''
|
||||
class Env(object):
|
||||
def __init__(self):
|
||||
self.nS = 31
|
||||
self.nA = 2
|
||||
self.P = P
|
||||
self.Policy = {0:0.4, 1:0.6}
|
||||
|
||||
def reset(self, from_start = True):
|
||||
if (from_start):
|
||||
return self.States.Start.value
|
||||
else:
|
||||
idx = np.random.choice(self.state_space)
|
||||
return idx
|
||||
|
||||
def get_actions(self, s):
|
||||
if (s < 6):
|
||||
actions = self.P[s]
|
||||
return actions.items()
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_states(self, a):
|
||||
for s, actions in self.P.items():
|
||||
if actions.__contains__(a):
|
||||
return actions[a]
|
||||
|
||||
def step(self, curr_state, action):
|
||||
probs = self.P[curr_state][action]
|
||||
if (len(probs) == 1):
|
||||
return self.P[curr_state][action][0]
|
||||
else:
|
||||
idx = np.random.choice(3, p=self.transition)
|
||||
return self.P[curr_state][action][idx]
|
||||
|
||||
|
||||
|
||||
def V_star(env: Env, gamma):
|
||||
V_curr = [0.0] * env.nS
|
||||
V_next = [0.0] * env.nS
|
||||
Q_star = copy.deepcopy(env.P) # 拷贝状态转移结构以存储Q(s,a)值
|
||||
count = 0
|
||||
# 迭代
|
||||
while (True):
|
||||
# 遍历所有状态 s
|
||||
for s in range(env.nS):
|
||||
# 获得 状态->动作 策略概率
|
||||
actions = env.get_actions(s)
|
||||
list_q = []
|
||||
if actions is not None:
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
# 获得 动作->状态 转移概率
|
||||
q_star = 0
|
||||
# 遍历每个转移概率,以计算 q_pi
|
||||
for p, s_next, r in next_p_s_r:
|
||||
# 式2.1 math: \sum_{s'} p_{ss'}^a [ r_{ss'}^a + \gamma * v_{\pi}(s')]
|
||||
q_star += p * (r + gamma * V_curr[s_next])
|
||||
#end for
|
||||
# 式5 math: \sum_a \pi(a|s) q_\pi (s,a)
|
||||
list_q.append(q_star)
|
||||
Q_star[s][action] = q_star
|
||||
# end for
|
||||
V_next[s] = max(list_q) if len(list_q) > 0 else 0
|
||||
#endfor
|
||||
# 检查收敛性
|
||||
if np.allclose(V_next, V_curr):
|
||||
break
|
||||
# 把 V_curr 赋值给 V_next
|
||||
V_curr = V_next.copy()
|
||||
count += 1
|
||||
# end while
|
||||
print(count)
|
||||
print(Q_star)
|
||||
return V_next
|
||||
|
||||
def get_policy(env: Env, V, gamma):
|
||||
policy = np.zeros((env.nS, env.nA))
|
||||
for s in range(env.nS):
|
||||
actions = env.get_actions(s)
|
||||
list_q = []
|
||||
if actions is None:
|
||||
continue
|
||||
# 遍历每个策略概率
|
||||
for action, next_p_s_r in actions:
|
||||
q_star = 0
|
||||
for p, s_next, r in next_p_s_r:
|
||||
q_star += p * (r + gamma * V[s_next])
|
||||
list_q.append(q_star)
|
||||
policy[s, np.argmax(list_q)] = 1
|
||||
return policy
|
||||
'''
|
||||
|
||||
if __name__=="__main__":
|
||||
env = dataModel.Env(None)
|
||||
gamma = 1
|
||||
max_iteration = 100
|
||||
V_star, Q_star = algo.calculate_Vstar(env, gamma, max_iteration)
|
||||
print(np.round(V_star,4))
|
||||
policy = algo.get_policy(env, V_star, gamma)
|
||||
print(policy)
|
||||
print(Q_star)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@@ -1,43 +0,0 @@
|
||||
import time
|
||||
|
||||
# 递归法
|
||||
@@ -27,8 +28,7 @@ def f3(n):
|
||||
|
||||
|
||||
if __name__=="__main__":
|
||||
n = 41
|
||||
|
||||
n = 40
|
||||
|
||||
start = time.time()
|
||||
print("递归法结果 =", f1(n))
|
||||
@@ -41,4 +41,4 @@ if __name__=="__main__":
|
||||
|
||||
print("备忘录法结果 =", f3(n))
|
||||
end3 = time.time()
|
||||
print("备忘录法耗时 =", end3-end2)
|
||||
print("备忘录法耗时 =", end3-end2)
|
||||
@@ -1,3 +1,4 @@
|
||||
@@ -1,24 +0,0 @@
|
||||
import numpy as np
|
||||
|
||||
# C:背包容量,N:物品数量,V:物品价值,S:物品体积
|
||||
@@ -16,9 +17,9 @@ def f(Capacity, Number, Value, Size):
|
||||
return dp_Value
|
||||
|
||||
if __name__=="__main__":
|
||||
Capacity = 10
|
||||
Capacity = 5
|
||||
Number = 4
|
||||
Value = [0,2,4,3,7]
|
||||
Size = [0,2,3,5,5]
|
||||
result = f(Capacity,Number,Value,Size)
|
||||
print(result)
|
||||
print(result)
|
||||
@@ -1,3 +1,4 @@
|
||||
@@ -1,152 +0,0 @@
|
||||
|
||||
### 动态规划(迭代法)
|
||||
|
||||
@@ -21,7 +22,7 @@
|
||||
|
||||
### 问题二
|
||||
|
||||
有一个10级的台阶,弟弟每天放学回家都要经过。他有时候一级一级地上台阶,有时候跳两级,有时候一级两级随意上跳。问:他一共有多少种跳法?这次需要输出所有数列(1级时输出1,2级时输出2,3级时输出3,4级时输出5......)。
|
||||
有一个 6 级的台阶,姐姐和弟弟每天放学回家都要经过。弟弟有时候一级一级地上台阶,有时候跳两级,有时候一级两级随意上跳,但是方法每天都不一样,姐姐在旁边帮他记录,保证他不重复。问:弟弟一共需要多少天可以遍历所有跳法?这次需要输出所有数列(1级时输出1,2级时输出2,3级时输出3,4级时输出5......)。
|
||||
|
||||
其实这是一个斐波那契数列的变形题目,可以抽象为:
|
||||
|
||||
@@ -31,27 +32,25 @@ $$
|
||||
|
||||
其解题思路在于把问题逐级分解:
|
||||
|
||||
- 想跳到第 10 级台阶,先要跳到第 9 级或第 8 级台阶;
|
||||
- 想跳到第 6 级台阶,先要跳到第 5 级或第 4 级台阶;
|
||||
又分成两种情况:
|
||||
- 想跳到第 9 级台阶,先要跳到第 8 级或第 7 级台阶;
|
||||
- 想跳到第 5 级台阶,先要跳到第 4 级或第 3 级台阶;
|
||||
双分成两种情况:
|
||||
- 想跳到第 8 级台阶,先要跳到第 7 级或第 6 级台阶;
|
||||
- 想跳到第 4 级台阶,先要跳到第 3 级或第 2 级台阶;
|
||||
叒分成两种情况:
|
||||
- 想跳到第7级台阶,先要跳到第 6 级或第 5 级台阶;
|
||||
- 想跳到第6级台阶,先要跳到第 5 级或第 4 级台阶;
|
||||
- 想跳到第3级台阶,先要跳到第 2 级或第 1 级台阶;
|
||||
- 想跳到第2级台阶,先要跳到第 1 级台阶。
|
||||
......
|
||||
- 想跳到第 8 级台阶,先要跳到第 7 级或第 6 级台阶;
|
||||
- 想跳到第 4 级台阶,先要跳到第 3 级或第 2 级台阶;
|
||||
......
|
||||
|
||||
所以有:
|
||||
|
||||
$$
|
||||
\begin{aligned}
|
||||
f(10)&=f(9)+f(8) & {\footnotesize (到第10级台阶的方法有 9 \to 10 和 8 \to 10两种组合)}
|
||||
f(6)&=f(5)+f(4) & {\footnotesize (到第 6 级台阶的方法有 5 \to 6 和 4 \to 6 两种组合)}
|
||||
\\
|
||||
f(9)&=f(8)+f(7)
|
||||
\\
|
||||
\cdots
|
||||
f(5)&=f(4)+f(3)
|
||||
\\
|
||||
f(3)&=f(2)+f(1)
|
||||
\\
|
||||
@@ -95,18 +94,17 @@ def f3(n):
|
||||
return results
|
||||
```
|
||||
输出结果:
|
||||
|
||||
```
|
||||
递归法结果 = 89
|
||||
递归法耗时 = 0.0009970664978027344
|
||||
迭代法结果 = 89
|
||||
迭代法耗时 = 0.0010001659393310547
|
||||
备忘录法结果 = {1: 1, 2: 2, 3: 3, 4: 5, 5: 8, 6: 13, 7: 21, 8: 34, 9: 55, 10: 89}
|
||||
备忘录法耗时 = 0.0
|
||||
递归法结果 = 13
|
||||
递归法耗时 = 0.0
|
||||
迭代法结果 = 13
|
||||
迭代法耗时 = 0.0009682178497314453
|
||||
备忘录法结果 = {1: 1, 2: 2, 3: 3, 4: 5, 5: 8, 6: 13}
|
||||
备忘录法耗时 = 0.001001119613647461
|
||||
```
|
||||
所以 10 级台阶一共有 89 种跳法。其中,备忘录法可以记录所有的计算结果,当然需要耗费 $O(n)$ 空间。
|
||||
所以 6 级台阶一共有 13 种跳法。其中,备忘录法可以记录所有的计算结果,当然需要耗费 $O(n)$ 空间。
|
||||
|
||||
当计算 $f(40)$ 时,递归法需要花费成指数级增加的时间,在笔者的电脑上约17秒,复杂度为 $O(2^n)$。而迭代法和备忘录法仍然是瞬时完成,复杂度 $O(n)$。
|
||||
如果台阶数增加到 40,当计算 $f(40)$ 时,递归法需要花费成指数级增加的时间,在笔者的电脑上约17秒,复杂度为 $O(2^n)$。而迭代法和备忘录法仍然是瞬时完成,复杂度 $O(n)$。
|
||||
|
||||
```
|
||||
递归法结果 = 165580141
|
||||
@@ -152,4 +150,4 @@ https://cloud.tencent.com/developer/article/1817113
|
||||
|
||||
https://zhuanlan.zhihu.com/p/70689256
|
||||
|
||||
https://blog.csdn.net/weixin_41082481/article/details/115922389
|
||||
https://blog.csdn.net/weixin_41082481/article/details/115922389
|
||||
@@ -18,8 +18,19 @@ draft里面将会以日记形式存放独立的小“故事”,每个“故事
|
||||
问题.md(正文内容)
|
||||
|
||||
|
||||
三门问题
|
||||
租车问题
|
||||
1. 概念
|
||||
- 三门问题
|
||||
|
||||
2. 探索与利用
|
||||
- 多臂赌博机
|
||||
|
||||
3.
|
||||
|
||||
- 租车问题
|
||||
|
||||
|
||||
|
||||
|
||||
醉汉回家问题
|
||||
安全驾驶问题
|
||||
学生学习问题
|
||||
@@ -47,3 +58,9 @@ Gym里面的more
|
||||
从估算状态价值到计算状态价值(贝尔曼方程)
|
||||
从状态价值到动作价值
|
||||
从动作价值到最优动作价值
|
||||
|
||||
|
||||
|
||||
|
||||
书中所有示意图片,都是用 Microsoft PowerPoint 绘制。
|
||||
所有文字、公式、代码,都是用 Microsoft Visual Studio Code 编写。
|
||||