强化学习笔记
1.马尔可夫决策过程(MDP)
1.马尔可夫性质
当且仅当某时候的状态只取决于上一时刻的状态时,一个随机过程被称为具有马尔可夫性质。
P
(
S
t
+
1
∣
S
t
)
=
P
(
S
t
+
1
∣
S
1
,
.
.
.
,
S
t
)
P(S_{t+1}|S_t)=P(S_{t+1}|S_1,...,S_t)
P(St+1∣St)=P(St+1∣S1,...,St)
2.马尔可夫过程
指具有马尔可夫性质的随机过程被称为马尔可夫过程,通常用元组<S,P>描述。
S表示有限数量的状态集合,终止状态是指它不会再转移到其他状态。
S
=
{
s
1
,
s
2
,
.
.
.
s
n
}
S=\{s_1,s_2,...s_n\}
S={s1,s2,...sn}
P是状态转移矩阵,定义所有状态对之间的转移概率,某个状态到其他状态的概率和必须为1。
P
=
[
P
(
s
1
∣
s
1
)
.
.
.
P
(
s
n
∣
s
1
)
.
.
.
.
.
.
.
.
.
P
(
s
1
∣
s
n
)
.
.
.
P
(
s
n
∣
s
n
)
]
P=\begin{bmatrix} P(s_1|s_1) & ...& P(s_n|s_1) \\ ... & ... &...\\P(s_1|s_n)&...&P(s_n|s_n) \end{bmatrix}
P=
P(s1∣s1)...P(s1∣sn).........P(sn∣s1)...P(sn∣sn)
3.马尔可夫奖励过程(MRP)
再马尔可夫过程的基础上加入奖励函数r和折扣因子γ,就可以得到马尔可夫奖励过程,通常由<S,P,r,γ>构成。
S是有限状态的集合。
P是状态转移矩阵。
r是奖励函数,某个状态s的奖励r(s)指转移到该状态时可以获得的奖励的期望。
γ是折扣因子,取值范围为[0,1)。
(1) 回报:
在马尔可夫奖励过程中,从第t时刻状态St开始,直到终止状态时,所有奖励的衰减之和称为回报。
G
t
=
R
t
+
γ
R
t
+
1
+
r
2
R
t
+
2
+
.
.
.
=
∑
i
=
0
∞
r
k
R
t
+
k
G_t=R_t+γR_{t+1}+r^2R_{t+2}+...=\sum_{i=0}^∞r^kR_{t+k}
Gt=Rt+γRt+1+r2Rt+2+...=i=0∑∞rkRt+k
(2) 价值函数:
在马尔可夫奖励过程中,一个状态的期望回报被称为这个状态的价值。价值函数输入为某个状态,输出为这个状态的价值。
V
(
s
)
=
E
[
G
t
∣
S
t
=
s
]
V
(
s
)
=
r
(
s
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
)
V
(
s
′
)
V(s)=E[G_t|S_t=s]\\ V(s)=r(s)+γ\sum_{s'∈S}P(s'|s)V(s')
V(s)=E[Gt∣St=s]V(s)=r(s)+γs′∈S∑P(s′∣s)V(s′)
(3)贝尔曼方程:
V
=
R
+
γ
P
V
(
I
−
r
P
)
V
=
R
V
=
(
I
−
r
P
)
−
1
R
V=R+γPV\\ (I-rP)V=R\\ V=(I-rP)^{-1}R
V=R+γPV(I−rP)V=RV=(I−rP)−1R
贝尔曼方程的计算复杂度O(n3),求解较大规模的马尔可夫奖励过程的价值函数时,可以使用动作规划(DP)算法,蒙特卡洛方法(MC)和时序差分(TD)。
4.马尔可夫决策过程(MDP)
马尔可夫过程和马尔可夫奖励过程都是自发改变的随机过程,在这基础上加一个外界的“刺激”(智能体的动作)来改变这个随机过程,就有了马尔可夫决策过程,通常由<S,A,P,r,γ>。
S是状态的集合。
A是动作的集合。
γ是折扣因子。
r(s,a)是奖励函数,此时奖励可能同时取决于状态s和动作a。
P(s’|s,a)是状态转移函数,表示在状态s执行动作a之后到达状态s‘的概率。
r’(s)表示在一个MRP状态s的奖励。
r
′
(
s
)
=
∑
a
∈
A
π
(
a
∣
s
)
r
(
s
,
a
)
r'(s)=\sum_{a∈A}\pi(a|s)r(s,a)
r′(s)=a∈A∑π(a∣s)r(s,a)
P’(s’|s)表示在一个MRP的状态从s转移至s’的概率。
P
′
(
s
′
∣
s
)
=
∑
a
∈
A
π
(
a
∣
s
)
P
(
s
′
∣
s
,
a
)
P'(s'|s)=\sum_{a∈A}\pi(a|s)P(s'|s,a)
P′(s′∣s)=a∈A∑π(a∣s)P(s′∣s,a)
(1)策略:
策略Π(a|s)表示输入状态为s的情况下采取动作a的概率。
π
(
a
∣
s
)
=
P
(
A
t
=
a
∣
S
t
=
s
)
\pi(a|s)=P(A_t=a|S_t=s)
π(a∣s)=P(At=a∣St=s)
(2)状态价值函数:
从状态s出发遵循策略Π能获得的期望回报,被称为状态价值函数VΠ(s)。
V
π
(
s
)
=
E
π
[
G
t
∣
S
t
=
s
]
V^\pi(s)=E_\pi[G_t|S_t=s]
Vπ(s)=Eπ[Gt∣St=s]
(3)动作价值函数:
从状态s出发,遵循策略Π并执行动作a得到的期望回报,被称为动作价值函数QΠ(s,a)。
Q
π
(
s
,
a
)
=
E
π
[
G
t
∣
S
t
=
s
,
A
t
=
a
]
Q^\pi(s,a)=E_\pi[G_t|S_t=s,A_t=a]
Qπ(s,a)=Eπ[Gt∣St=s,At=a]
状态s的价值等于在该状态下基于策略Π采取所有动作的概率和相应的价值相乘再求和的结果。
V
π
(
s
)
=
∑
a
∈
A
π
(
a
∣
s
)
Q
π
(
s
,
a
)
V^\pi(s)=\sum_{a∈A}\pi(a|s)Q^\pi(s,a)
Vπ(s)=a∈A∑π(a∣s)Qπ(s,a)
状态s下采取动作a的价值等于即时奖励加上经过衰减的所有可能的下一个状态的状态转移概率和相应的价值乘积。
Q
π
(
s
,
a
)
=
r
(
s
,
a
)
+
r
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
π
(
s
′
)
Q^\pi(s,a)=r(s,a)+r\sum_{s'∈S}P(s'|s,a)V^\pi(s')
Qπ(s,a)=r(s,a)+rs′∈S∑P(s′∣s,a)Vπ(s′)
(4)贝尔曼期望方程:
由上式推导可得
Q
π
=
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
∑
a
′
∈
A
π
(
a
′
∣
s
′
)
Q
π
(
s
′
∣
a
′
)
V
π
(
s
)
=
∑
a
∈
A
π
(
a
∣
s
)
(
r
(
s
,
a
)
+
r
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
π
(
s
′
)
)
Q^\pi=r(s,a)+γ\sum_{s'∈S}P(s'|s,a)\sum_{a'∈A}\pi(a'|s')Q^\pi(s'|a')\\V^\pi(s)=\sum_{a∈A}\pi(a|s)(r(s,a)+r\sum_{s'∈S}P(s'|s,a)V^\pi(s'))
Qπ=r(s,a)+γs′∈S∑P(s′∣s,a)a′∈A∑π(a′∣s′)Qπ(s′∣a′)Vπ(s)=a∈A∑π(a∣s)(r(s,a)+rs′∈S∑P(s′∣s,a)Vπ(s′))
(5)贝尔曼最优方程:
强化学习的目标:通常是找到一个策略,使得智能体从初始状态出能获得最多的期望回报。
最优策略(Π *(s)):在有限状态和动作集合的MDP中,至少存在一个策略比其他所有策略都好或者至少存在一个策略不差于其他所有策略。
最优状态价值函数:
V
∗
(
s
)
=
max
π
V
π
(
s
)
V^*(s)=\max_{\pi}V^\pi(s)
V∗(s)=πmaxVπ(s)
最优动作价值函数:
Q
∗
(
s
,
a
)
=
max
π
Q
π
(
s
,
a
)
Q^*(s,a)=\max_{\pi}Q^\pi(s,a)
Q∗(s,a)=πmaxQπ(s,a)
为了使用最优动作价值函数最大,需要在当前状态动作对(s,a)之后都执行最优策略。
Q
∗
(
s
,
a
)
=
r
(
s
,
a
)
+
r
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
∗
(
s
′
)
V
∗
(
s
′
)
=
max
a
∈
A
Q
∗
(
s
,
a
)
Q^*(s,a)=r(s,a)+r\sum_{s'∈S}P(s'|s,a)V^*(s')\\V^*(s')=\max_{a∈A}Q^*(s,a)
Q∗(s,a)=r(s,a)+rs′∈S∑P(s′∣s,a)V∗(s′)V∗(s′)=a∈AmaxQ∗(s,a)
根据最优状态价值函数和最优动作价值函数的关系,以及贝尔曼方程,得到贝尔曼最优方程。
V
∗
(
s
)
=
max
a
∈
A
{
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
∗
(
s
′
)
}
Q
∗
(
s
,
a
)
=
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
max
a
′
∈
A
Q
∗
(
s
′
,
a
′
)
V^*(s)=\max_{a∈A}\{r(s,a)+γ\sum_{s'∈S}P(s'|s,a)V^*(s')\}\\Q^*(s,a)=r(s,a)+γ\sum_{s'∈S}P(s'|s,a)\max_{a'∈A}Q^*(s',a')
V∗(s)=a∈Amax{r(s,a)+γs′∈S∑P(s′∣s,a)V∗(s′)}Q∗(s,a)=r(s,a)+γs′∈S∑P(s′∣s,a)a′∈AmaxQ∗(s′,a′)
V*(s)与VΠ(s)的区别:前者会按照获取最多期望回报的动作来取值,其他动作则不会赋予概率,类似于贪心算法;后者会根据策略Π对各个动作的概率分配获取的期望回报来求和, 就是求期望。
2.蒙特卡罗(MC)、动态规划(DP)、时间差分(TD)
这些方法用于找到一个策略,使得智能体从初始状态出能获得最多的期望回报。
1.蒙特卡罗(MC)
如图所示,蒙特卡罗需要一个完整的路径。
蒙特卡罗公式的推导:
(1).状态价值的期望回报:当策略在MDP上采样很多条序列,计算从这个状态出发的回报再求其期望。
V
π
=
E
π
[
G
t
∣
S
t
=
s
]
≈
1
N
∑
i
=
1
N
G
t
(
i
)
V^\pi=E_\pi[G_t|S_t=s]\approx \frac{1}{N}\sum_{i=1}^NG_t^{(i)}
Vπ=Eπ[Gt∣St=s]≈N1i=1∑NGt(i)
(2).采用增量式更新来推导。
V
N
π
=
1
N
∑
i
=
1
N
G
t
(
i
)
=
1
N
(
G
t
+
∑
i
=
1
N
−
1
G
t
(
i
)
)
=
1
N
(
G
t
+
(
N
−
1
)
V
N
−
1
π
(
s
)
)
=
V
N
−
1
π
(
s
)
+
1
N
(
G
t
−
V
N
−
1
π
(
s
)
)
(
V
N
−
1
π
(
s
)
表示
V
N
π
的前一步的值,都是表示同一状态
)
V^\pi_N=\frac{1}{N}\sum_{i=1}^NG_t^{(i)}\\=\frac{1}{N}(G_t+\sum_{i=1}^{N-1}G_t^{(i)})\\=\frac{1}{N}(G_t+(N-1)V^\pi_{N-1}(s))\\=V^\pi_{N-1}(s)+\frac{1}{N}(G_t-V^\pi_{N-1}(s))\\(V^\pi_{N-1}(s)表示V^\pi_N的前一步的值,都是表示同一状态)
VNπ=N1i=1∑NGt(i)=N1(Gt+i=1∑N−1Gt(i))=N1(Gt+(N−1)VN−1π(s))=VN−1π(s)+N1(Gt−VN−1π(s))(VN−1π(s)表示VNπ的前一步的值,都是表示同一状态)
(3).蒙特卡罗公式。
1.
收集
e
p
i
s
o
d
e
(
S
1
,
A
1
,
R
1
,
.
.
.
.
S
t
)
2.
状态
S
t
的回报为
G
t
N
(
S
t
)
←
N
(
S
t
)
+
1
V
(
S
t
)
←
V
(
S
t
)
+
1
N
(
S
t
)
(
G
t
−
V
(
S
t
)
)
1.收集episode(S_1,A_1,R_1,....S_t)\\2.状态S_t的回报为G_t\\N(S_t)\gets N(S_t)+1\\V(S_t)\gets V(S_t)+\frac{1}{N(S_t)}(G_t-V(S_t))
1.收集episode(S1,A1,R1,....St)2.状态St的回报为GtN(St)←N(St)+1V(St)←V(St)+N(St)1(Gt−V(St))
(4).部分代码展示
#一条episode例子:
#[('s1', '前往s2', 0, 's2'), ('s2', '前往s3', -2, 's3'), ('s3', '前往s5', 0, 's5')]
#使用蒙特卡洛方法对所有采样序列计算所有状态的价值
def MC(episodes,V,N,gamma):
for episode in episodes:
G=0
for i in range(len(episode)-1,-1,-1):#一个序列从后往前计算
(s,a,r,s_next)=episode[i] #s表示当前状态,s_next表示下一个状态,r表示奖励,a表示动作
G=r+gamma*G
N[s]=N[s]+1
V[s]=V[s]+(G-V[s])/N[s]
2.动态规划(DP)
动态规划:将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到目标问题的解。
基于动态规划的强化学习分为策略迭代(policy iteration)和价值迭代(value iteration)。
(1).策略迭代
策略迭代由两部分组成:策略评估(policy evaluation)和策略提升(policy improvement)。
i.策略评估:根据之前学习的贝尔曼期望方程,可以得到,当知道奖励函数r(s,a)和状态转移函数p(s’|s,a)时,可以根据下一个状态的价值来计算当前状态的价值。因此,可以把下一个可能状态的价值当成一个子问题,把当前状态的价值看作成当前问题。公式如下:
V
k
+
1
(
s
)
=
∑
a
∈
A
π
(
a
∣
s
)
(
r
(
s
,
a
)
+
r
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
k
(
s
′
)
)
V^{k+1}(s)=\sum_{a∈A}\pi(a|s)(r(s,a)+r\sum_{s'∈S}P(s'|s,a)V^k(s'))
Vk+1(s)=a∈A∑π(a∣s)(r(s,a)+rs′∈S∑P(s′∣s,a)Vk(s′))
当子问题和当前问题之间的差值比较小时,则证明迭代已经收敛,可以结束策略评估。
ii.策略提升:可以贪心地在每一个状态选择动作价值最大的动作。
π
′
(
s
)
=
a
r
g
max
a
Q
π
(
s
,
a
)
=
a
r
g
max
a
{
r
(
s
,
a
)
+
γ
∑
s
′
∈
S
P
(
s
′
∣
s
,
a
)
V
π
(
s
′
)
}
\pi'(s)=arg \max_aQ^\pi(s,a)=arg \max_a\{r(s,a)+γ\sum_{s'∈S}P(s'|s,a)V^\pi(s')\}
π′(s)=argamaxQπ(s,a)=argamax{r(s,a)+γs′∈S∑P(s′∣s,a)Vπ(s′)}
策略迭代算法:
部分代码展示:
#策略评估用来根据策略来更新状态价值函数,而策略提升则通过更新的状态价值函数来找到最大的状态选择动作价值,来为最大的分配概率,利用贪心算法的思想,则每次会选择收益最大的一个动作。
class PolicyIteration:
""" 策略迭代算法 """
def __init__(self, env, theta, gamma):
self.env = env
self.v = [0] * self.env.ncol * self.env.nrow # 初始化价值为0
self.pi = [[0.25, 0.25, 0.25, 0.25]
for i in range(self.env.ncol * self.env.nrow)] # 初始化为均匀随机策略
self.theta = theta # 策略评估收敛阈值
self.gamma = gamma # 折扣因子
def policy_evaluation(self): # 策略评估
cnt = 1 # 计数器
while 1:
max_diff = 0
new_v = [0] * self.env.ncol * self.env.nrow
for s in range(self.env.ncol * self.env.nrow):
qsa_list = [] # 开始计算状态s下的所有Q(s,a)价值
for a in range(4):
qsa = 0
for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p * (r + self.gamma * self.v[next_state] *
(1 - done))
# 本章环境比较特殊,奖励和下一个状态有关,所以需要和状态转移概率相乘
qsa_list.append(self.pi[s][a] * qsa)
new_v[s] = sum(qsa_list) # 状态价值函数和动作价值函数之间的关系
max_diff = max(max_diff, abs(new_v[s] - self.v[s]))
self.v = new_v
if max_diff < self.theta: break # 满足收敛条件,退出评估迭代
cnt += 1
print("策略评估进行%d轮后完成" % cnt)
def policy_improvement(self): # 策略提升
for s in range(self.env.nrow * self.env.ncol):
qsa_list = []
for a in range(4):
qsa = 0
for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p * (r + self.gamma * self.v[next_state] *
(1 - done))
qsa_list.append(qsa)
maxq = max(qsa_list)
cntq = qsa_list.count(maxq) # 计算有几个动作得到了最大的Q值
# 让这些动作均分概率
self.pi[s] = [1 / cntq if q == maxq else 0 for q in qsa_list]
print("策略提升完成")
return self.pi
#继续评估新策略,提升策略。。。。。
def policy_iteration(self): # 策略迭代
while 1:
self.policy_evaluation()
old_pi = copy.deepcopy(self.pi) # 将列表进行深拷贝,方便接下来进行比较
new_pi = self.policy_improvement()
if old_pi == new_pi: break
(2).价值迭代
价值迭代直接由贝尔曼最优方程来进行动态规划 ,得到最终的最优状态价值函数。
由于策略迭代中的策略评估需要进行很多论才能收敛得到某一个策略的状态函数,这需要很大的计算量。
部分代码展示:
#与策略迭代算法的区别在于,他在价值迭代部分不会根据策略各个动作的概率来求和得到状态价值,而是直接选择一个最大的状态价值。
class ValueIteration:
""" 价值迭代算法 """
def __init__(self, env, theta, gamma):
self.env = env
self.v = [0] * self.env.ncol * self.env.nrow # 初始化价值为0
self.theta = theta # 价值收敛阈值
self.gamma = gamma
# 价值迭代结束后得到的策略
self.pi = [None for i in range(self.env.ncol * self.env.nrow)]
def value_iteration(self):
cnt = 0
while 1:
max_diff = 0
new_v = [0] * self.env.ncol * self.env.nrow
for s in range(self.env.ncol * self.env.nrow):
qsa_list = [] # 开始计算状态s下的所有Q(s,a)价值
for a in range(4):
qsa = 0
for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p * (r + self.gamma * self.v[next_state] *
(1 - done))
qsa_list.append(qsa) # 这一行和下一行代码是价值迭代和策略迭代的主要区别
new_v[s] = max(qsa_list)
max_diff = max(max_diff, abs(new_v[s] - self.v[s]))
self.v = new_v
if max_diff < self.theta: break # 满足收敛条件,退出评估迭代
cnt += 1
print("价值迭代一共进行%d轮" % cnt)
self.get_policy()
def get_policy(self): # 根据价值函数导出一个贪婪策略
for s in range(self.env.nrow * self.env.ncol):
qsa_list = []
for a in range(4):
qsa = 0
for res in self.env.P[s][a]:
p, next_state, r, done = res
qsa += p * (r + self.gamma * self.v[next_state] * (1 - done))
qsa_list.append(qsa)
maxq = max(qsa_list)
cntq = qsa_list.count(maxq) # 计算有几个动作得到了最大的Q值
# 让这些动作均分概率
self.pi[s] = [1 / cntq if q == maxq else 0 for q in qsa_list]
3.时间差分(TD)
时序差分:用来估计一个策略的价值函数的方法,它结合了蒙特卡罗和动态规划算法的思想。
- 时序差分方法和蒙特卡罗的相似之处在于可以从样本数据中学习,不需要事先知道环境(由他们公式可知,不需要知道P(s’|s,a));
- 和动态规划的相似之处在于可以根据贝尔曼方程的思想,利用后续状态的价值估计来更新当前状态的价值估计。
时序差分(TD):
r
t
+
γ
V
(
s
t
+
1
)
−
V
(
s
t
)
r_t+γV(s_{t+1})-V(s_t)
rt+γV(st+1)−V(st)
4. MC、DP、TD之间的关系。
右下角表示穷举法。
MC与TD的区别:
- TD可以在每一步后在线学习。
MC 必须等到episode结束才能知道return。 - TD 可以从不完整的序列中学习。
MC 只能从完整序列中学习。 - TD 在连续(非终止)环境中工作。
MC 仅适用于 episodic(终止)环境。 - TD利用马尔可夫特性,在马尔可夫环境中更高效。
MC 不利用马尔可夫特性,在非马尔可夫环境中更有效。
3.基于时序差分的强化学习算法
1.Sarsa算法
可以通过时序差分算法来估计动作价值函数Q:
Q
(
s
t
,
a
t
)
←
Q
(
s
t
,
a
t
)
+
α
[
r
t
+
γ
Q
(
s
t
+
1
,
a
t
+
1
)
−
Q
(
s
t
,
a
t
)
]
Q(s_t,a_t)\gets Q(s_t,a_t)+\alpha[r_t+γQ(s_{t+1},a_{t+1})-Q(s_t,a_t)]
Q(st,at)←Q(st,at)+α[rt+γQ(st+1,at+1)−Q(st,at)]
(1) ε-贪婪算法:
每次以概率1-ε选择以往经验中期望奖励估值最大的那根拉杆,以概率ε随机选择一根拉杆。
a
t
=
{
a
r
g
max
a
∈
A
Q
(
a
)
,
采样概率
:
1
−
ϵ
从
A
中随机选择,采样概率
:
ϵ
a_t=\begin{cases} arg\max\limits_{a∈A}Q(a),采样概率:1-\epsilon\\从A中随机选择,采样概率:\epsilon \end{cases}
at={arga∈AmaxQ(a),采样概率:1−ϵ从A中随机选择,采样概率:ϵ
(2) Sarsa算法具体流程:
用贪婪算法根据动作价值选取动作来和环境交互,再根据得到的数据用时序差分算法更新动作价值估计。
关于下面第二个for函数的内容:用于记录一条序列的回报
部分代码展示:
class Sarsa:
""" Sarsa算法 """
def __init__(self, ncol, nrow, epsilon, alpha, gamma, n_action=4):
self.Q_table = np.zeros([nrow * ncol, n_action]) # 初始化Q(s,a)表格
self.n_action = n_action # 动作个数
self.alpha = alpha # 学习率
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-贪婪策略中的参数
def take_action(self, state): # 选取下一步的操作,具体实现为epsilon-贪婪
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state]) #选择state状态下动作价值最大的动作,np.argmax返回的是索引,而np.max返回是是值
return action
def best_action(self, state): # 用于打印策略
Q_max = np.max(self.Q_table[state]) #选择state状态下最大的价值
a = [0 for _ in range(self.n_action)]
for i in range(self.n_action): # 若两个动作的价值一样,都会记录下来
if self.Q_table[state, i] == Q_max:
a[i] = 1
return a
#用时序差分算法来估计动作价值函数Q
def update(self, s0, a0, r, s1, a1):
td_error = r + self.gamma * self.Q_table[s1, a1] - self.Q_table[s0, a0]#时序差分算法
self.Q_table[s0, a0] += self.alpha * td_error #动作价值函数
2.多步Sarsa
由于时序差分只关注一步奖励和一步状态转移,导致局部最优而不是整体最优,所以需要使用n步的奖励,于是就有多步时序差分。
Q
(
s
t
,
a
t
)
←
Q
(
s
t
,
a
t
)
+
α
[
r
t
+
γ
r
t
+
1
+
.
.
.
+
γ
n
Q
(
s
t
+
n
,
a
t
+
n
)
−
Q
(
s
t
,
a
t
)
]
Q(s_t,a_t)\gets Q(s_t,a_t)+\alpha[r_t+γr_{t+1}+...+γ^nQ(s_{t+n},a_{t+n})-Q(s_t,a_t)]
Q(st,at)←Q(st,at)+α[rt+γrt+1+...+γnQ(st+n,at+n)−Q(st,at)]
部分代码展示:
class nstep_Sarsa:
""" n步Sarsa算法 """
def __init__(self, n, ncol, nrow, epsilon, alpha, gamma, n_action=4):
self.Q_table = np.zeros([nrow * ncol, n_action])
self.n_action = n_action
self.alpha = alpha
self.gamma = gamma
self.epsilon = epsilon
self.n = n # 采用n步Sarsa算法
self.state_list = [] # 保存之前的状态
self.action_list = [] # 保存之前的动作
self.reward_list = [] # 保存之前的奖励
def take_action(self, state):
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state])
return action
def best_action(self, state): # 用于打印策略
Q_max = np.max(self.Q_table[state])
a = [0 for _ in range(self.n_action)]
for i in range(self.n_action):
if self.Q_table[state, i] == Q_max:
a[i] = 1
return a
def update(self, s0, a0, r, s1, a1, done):
self.state_list.append(s0)
self.action_list.append(a0)
self.reward_list.append(r)
if len(self.state_list) == self.n: # 若保存的数据可以进行n步更新
G = self.Q_table[s1, a1] # 得到Q(s_{t+n}, a_{t+n})
for i in reversed(range(self.n)):
G = self.gamma * G + self.reward_list[i] # 不断向前计算每一步的回报
# 如果到达终止状态,最后几步虽然长度不够n步,也将其进行更新
if done and i > 0:
s = self.state_list[i]
a = self.action_list[i]
self.Q_table[s, a] += self.alpha * (G - self.Q_table[s, a])
s = self.state_list.pop(0) # 将需要更新的状态动作从列表中删除,下次不必更新
a = self.action_list.pop(0)
self.reward_list.pop(0)
# n步Sarsa的主要更新步骤
self.Q_table[s, a] += self.alpha * (G - self.Q_table[s, a])
if done: # 如果到达终止状态,即将开始下一条序列,则将列表全清空
self.state_list = []
self.action_list = []
self.reward_list = []
3. Q-learning算法
Q ( s t , a t ) ← Q ( s t , a t ) + α [ max a Q ( s t + 1 , a ) − Q ( s t , a t ) ] Q_(s_t,a_t) \gets Q(s_t,a_t)+\alpha[\max_aQ(s_{t+1},a)-Q(s_t,a_t)] Q(st,at)←Q(st,at)+α[amaxQ(st+1,a)−Q(st,at)]
(1) Q-learning算法具体流程:
部分代码展示:
class QLearning:
""" Q-learning算法 """
def __init__(self, ncol, nrow, epsilon, alpha, gamma, n_action=4):
self.Q_table = np.zeros([nrow * ncol, n_action]) # 初始化Q(s,a)表格
self.n_action = n_action # 动作个数
self.alpha = alpha # 学习率
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-贪婪策略中的参数
def take_action(self, state): #选取下一步的操作
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state])
return action
def best_action(self, state): # 用于打印策略
Q_max = np.max(self.Q_table[state])
a = [0 for _ in range(self.n_action)]
for i in range(self.n_action):
if self.Q_table[state, i] == Q_max:
a[i] = 1
return a
def update(self, s0, a0, r, s1):
td_error = r + self.gamma * self.Q_table[s1].max() - self.Q_table[s0, a0]
self.Q_table[s0, a0] += self.alpha * td_error
np.random.seed(0)
(2) 在线策略和离线策略的区别
行为策略:采样数据的策略。
目标策略:利用这些数据来更新的策略。
在线策略:行为策略和目标策略都是同一策略,例:Sarsa。
离线策略:行为策略和目标策略不是同一策略,例:Q-learning。
如下图,Sarsa的行为策略和目标策略都是ε-贪心策略采样,Q-learning的行为策略为ε-贪心策略采样,目标策略为贪心策略采样。
(3) Sarsa 和Q-learning区别
Sarsa获得的期望回报是高于Q-learning,由于Q-learning沿着悬崖边走,会以一定概率探索“掉入悬崖作,而Sarsa相对保守的路线使智能体几乎不可能掉入悬崖。
4. Dyna-Q算法
(1) 直接强化学习和间接强化学习
实际经验:真实的智能体和环境交互得到的数据。
仿真经验:利用实际经验来学习模型(模型学习),然后根据学习模型产生的经验。
直接强化学习:使用实际经验用来学习策略。
间接强化学习:利用仿真经验来辅助策略学习。
直接强化学习是无模型的强化学习,间接强化学习是基于模型的强化学习。
(2) Dyna-Q算法实现流程:
如果N=0时,Dyna-Q会退化为Q-learning算法。
部分代码展示:
import random
class DynaQ:
""" Dyna-Q算法 """
def __init__(self,ncol,nrow,epsilon,alpha,gamma,n_planning,n_action=4):
self.Q_table = np.zeros([nrow * ncol, n_action]) # 初始化Q(s,a)表格
self.n_action = n_action # 动作个数
self.alpha = alpha # 学习率
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-贪婪策略中的参数
self.n_planning = n_planning #执行Q-planning的次数, 对应1次Q-learning
self.model = dict() # 环境模型
def take_action(self, state): # 选取下一步的操作
if np.random.random() < self.epsilon:
action = np.random.randint(self.n_action)
else:
action = np.argmax(self.Q_table[state])
return action
def q_learning(self, s0, a0, r, s1):
td_error = r + self.gamma * self.Q_table[s1].max(
) - self.Q_table[s0, a0]
self.Q_table[s0, a0] += self.alpha * td_error
def update(self, s0, a0, r, s1):
self.q_learning(s0, a0, r, s1)
self.model[(s0, a0)] = r, s1 # 将数据添加到模型中
for _ in range(self.n_planning): # Q-planning循环
# 随机选择曾经遇到过的状态动作对
(s, a), (r, s_) = random.choice(list(self.model.items())) #选择以前存入model的数据
self.q_learning(s, a, r, s_)
5.DQN算法
1.DQN的特点
(1).问题:
前面的Q-learning算法,以矩阵
的方式建立了一张存储每个状态下所有动作的Q值
的表格。但是只有在环境的状态和动作都是离散的
时候才适用。当状态或动作数量非常大的时候以及状态每一个维度的值都是连续的
,这种做事是不适用的。例如:存储一张RGB图像时。
(2).解决办法:
可以使用神经网络来表示函数Q,若动作是连续的,神经网络的输入是状态s和动作a
,然后输出一个标量
,表示采取动作a能获得的价值。若动作是离散的
,只将状态s输入
到神经网络中,使其同时输出每一个动作的Q值
。
(3).经验回放:
DQN是离线策略算法,DQN算法采用经验回放方法,维护一个回放缓冲区
,将每次从环境中采样得到的四元组数据(状态、动作、奖励、下一个状态)
存储到回放缓冲区中,训练Q网络的时候再从回放缓冲区中随机采样若干数据来进行训练。
(4).目标网络:
1
2
[
Q
w
(
s
,
a
)
−
(
r
+
γ
m
a
x
a
′
Q
w
−
(
s
′
,
a
′
)
)
]
2
\frac{1}{2}[Q_w(s,a)-(r+γmax_{a'}Q_{w-}(s',a'))]^2
21[Qw(s,a)−(r+γmaxa′Qw−(s′,a′))]2
DQN利用两套Q网络
来实现训练过程中Q网络的不断更新会导致目标不断发生改变
。其中Qw(s,a)表示训练网络
,Qw-(s,a)表示目标网络
。
为了避免两套网络的参数随时保持一致,目标网络并不会每一步都更新
,但是训练网络会每一步进行更新,而目标网络的参数每隔C步才会与训练网络同步一次。
2.DQN算法实现流程
3.部分代码实现
class ReplayBuffer:
''' 经验回放池 '''
def __init__(self, capacity):
self.buffer = collections.deque(maxlen=capacity) # 队列,先进先出,deque支持双向队列(支持双向插入和删除),可以很好的解决队列和栈的实现。
def add(self, state, action, reward, next_state, done): # 将数据加入buffer
self.buffer.append((state, action, reward, next_state, done))
def sample(self, batch_size): # 从buffer中采样数据,数量为batch_size
transitions = random.sample(self.buffer, batch_size)
state, action, reward, next_state, done = zip(*transitions) #将transitions
return np.array(state), action, reward, np.array(next_state), done
def size(self): # 目前buffer中数据的数量
return len(self.buffer)
#输入状态s,返回所有动作的Q
class Qnet(torch.nn.Module):
''' 只有一层隐藏层的Q网络 '''
def __init__(self, state_dim, hidden_dim, action_dim):
super(Qnet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, action_dim)
def forward(self, x):
x = F.relu(self.fc1(x)) # 隐藏层使用ReLU激活函数
return self.fc2(x)
class DQN:
''' DQN算法 '''
def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
epsilon, target_update, device):
self.action_dim = action_dim
self.q_net = Qnet(state_dim, hidden_dim,
self.action_dim).to(device) # Q网络
# 目标网络
self.target_q_net = Qnet(state_dim, hidden_dim,
self.action_dim).to(device)
# 使用Adam优化器
self.optimizer = torch.optim.Adam(self.q_net.parameters(),
lr=learning_rate)
self.gamma = gamma # 折扣因子
self.epsilon = epsilon # epsilon-贪婪策略
self.target_update = target_update # 目标网络更新频率
self.count = 0 # 计数器,记录更新次数
self.device = device
def take_action(self, state): # epsilon-贪婪策略采取动作
if np.random.random() < self.epsilon:
action = np.random.randint(self.action_dim)
else:
state = torch.tensor([state], dtype=torch.float).to(self.device)
action = self.q_net(state).argmax().item()
return action
def update(self, transition_dict):
states = torch.tensor(transition_dict['states'],
dtype=torch.float).to(self.device)
actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(
self.device)
rewards = torch.tensor(transition_dict['rewards'],
dtype=torch.float).view(-1, 1).to(self.device)
next_states = torch.tensor(transition_dict['next_states'],
dtype=torch.float).to(self.device)
dones = torch.tensor(transition_dict['dones'],
dtype=torch.float).view(-1, 1).to(self.device)
q_values = self.q_net(states).gather(1, actions) # Q值
# 下个状态的最大Q值
max_next_q_values = self.target_q_net(next_states).max(1)[0].view(
-1, 1)
q_targets = rewards + self.gamma * max_next_q_values * (1 - dones
) # TD误差目标
dqn_loss = torch.mean(F.mse_loss(q_values, q_targets)) # 均方误差损失函数
self.optimizer.zero_grad() # PyTorch中默认梯度会累积,这里需要显式将梯度置为0
dqn_loss.backward() # 反向传播更新参数
self.optimizer.step()
if self.count % self.target_update == 0:
self.target_q_net.load_state_dict(
self.q_net.state_dict()) # 更新目标网络
self.count += 1
参考资料:
周博磊强化学习课程
张伟楠等动手学深度学习
强化学习相关代码:https://github.com/boyu-ai/Hands-on-RL