1、On-policy v.s. Off-policy
- On-policy: 学习的智能体和与环境交互的智能体是同一个
- Off-policy: 学习的智能体和与环境交互的智能体不是同一个
大白话就是,今天要学习的智能体它是一边跟环境互动,一边学习,叫On-policy。如果它是在旁边通过看别人玩来学习的话,叫Off-policy
策略梯度算法是On-policy的做法,因为actor去收集数据,然后在自己再学习
∇
R
ˉ
θ
=
E
τ
∽
p
θ
(
τ
)
[
R
(
τ
)
∇
l
o
g
p
θ
(
τ
)
]
(1)
\nabla \bar R_\theta=E_{\tau \backsim p_\theta(\tau)} [R(\tau) \nabla logp_\theta(\tau)] \tag{1}
∇Rˉθ=Eτ∽pθ(τ)[R(τ)∇logpθ(τ)](1)
以上的式子中的期望是对现在的actor
θ
\theta
θ与环境互动所采样出来的轨迹做期望,所以当用这个数据来做更新actor的参数
θ
\theta
θ后变成了
θ
′
\theta^\prime
θ′,那么之前用
θ
\theta
θ采样得到的数据就不能用了,所以PG是会用很多时间来采样数据,每次更新完参数后,就只能用一次,然后再采样来更新。
所以我们想把它变成off-policy,现在用另外一个actor
π
θ
′
\pi_{\theta^\prime}
πθ′去跟环境做互动,用它采样到的数据来训练actor
θ
\theta
θ,那么我们就可以用很多次
π
θ
′
\pi_{\theta^\prime}
πθ′采样到的数据。
2、重要性采样
我们现在有一个函数
f
(
x
)
f(x)
f(x),要计算从分布
p
(
x
)
p(x)
p(x)中采样到x,然后再把x代入到
f
(
x
)
f(x)
f(x)中来计算
f
(
x
)
f(x)
f(x)的期望值(前提是我们无法求
f
(
x
)
f(x)
f(x)的积分),即计算
E
x
∽
p
[
f
(
x
)
]
≈
1
N
∑
i
=
1
N
f
(
x
i
)
(2)
E_{x\backsim p} [f(x)] \approx \frac 1N \sum_{i=1}^N f(x^i) \tag{2}
Ex∽p[f(x)]≈N1i=1∑Nf(xi)(2)
上式表示从
p
(
x
)
p(x)
p(x)中采样N个
x
x
x再代入
f
(
x
)
f(x)
f(x)中,然后计算这N个
f
(
x
i
)
f(x^i)
f(xi)的平均值。
\\[1pt]
现在有个问题是,我们无法从
p
(
x
)
p(x)
p(x)中采样数据(我们假定不能嘛),但我们可以从另外的一个分布
q
(
x
)
q(x)
q(x)中采样数据,注意不能从
q
(
x
)
q(x)
q(x)中采样数据直接代入式(2)中,因为从
p
(
x
)
p(x)
p(x)中采样的数据才能使用公式(2)。这时候我们需要做一个修正,我们知道理论的期望值可以通过以下式子计算:
E
x
∽
p
[
f
(
x
)
]
=
∫
f
(
x
)
p
(
x
)
d
x
=
∫
f
(
x
)
p
(
x
)
q
(
x
)
q
(
x
)
d
x
=
E
x
∽
q
[
f
(
x
)
p
(
x
)
q
(
x
)
]
(3)
E_{x\backsim p} [f(x)]=\int f(x)p(x)dx=\int f(x) \frac{p(x)}{q(x)}q(x)dx = E_{x\backsim q} [f(x) \frac{p(x)}{q(x)}]\tag{3}
Ex∽p[f(x)]=∫f(x)p(x)dx=∫f(x)q(x)p(x)q(x)dx=Ex∽q[f(x)q(x)p(x)](3)
其中
E
x
∽
q
[
f
(
x
)
p
(
x
)
q
(
x
)
]
E_{x\backsim q} [f(x) \frac{p(x)}{q(x)}]
Ex∽q[f(x)q(x)p(x)]表示从q(x)中采样再代入
f
(
x
)
p
(
x
)
q
(
x
)
f(x) \frac{p(x)}{q(x)}
f(x)q(x)p(x)中计算期望值,然后就算出来了式子(3)中第一项的期望值了。
\\[1pt]
1)重要性采样的问题
理论上我们可以把q(x)换成任意的q(x)来算期望,但是在实作上p和q还是不能相差太大,从下图中我们知道期望值是一样的,但他们的方差不一样。
下面是一个简单的例子来进一步说明p和q相差太大会出现的问题,其中p(x)的分布为蓝色曲线,当从p(x)中采样来计算期望,我们发现期望
E
x
∽
p
[
f
(
x
)
]
E_{x\backsim p} [f(x)]
Ex∽p[f(x)]是一个负值,因为从p的分布来看左边数据采样的几率很高,所有左边数据占多数,那么带入f(x)后算出来的均值为负值的可能性非常大。另一方面,q(x)的分布式绿色的线,右边数据被采样到的可能性很大,因此算出来的期望值
E
x
∽
q
[
f
(
x
)
p
(
x
)
q
(
x
)
]
E_{x\backsim q} [f(x) \frac{p(x)}{q(x)}]
Ex∽q[f(x)q(x)p(x)]是正的可能性较大,但这是我们采样次数不够多的时候会出现。当我们采样次数较多的时候,我们采样到了左边的一个点,这个时候
p
q
\frac pq
qp会很大,那么再乘以一个f(x)后,期望值就会变成负数,这时候和等式的左边相等,但这种情况方式的前提是要采样足够多的次数。
2、使用重要性采样将On-policy转换成Off-policy
最初的On-policy的梯度计算公式为:
∇
R
ˉ
θ
=
E
τ
∽
p
θ
(
τ
)
[
R
(
τ
)
∇
l
o
g
p
θ
(
τ
)
]
(4)
\nabla \bar R_\theta=E_{\tau \backsim p_\theta(\tau)} [R(\tau) \nabla logp_\theta(\tau)] \tag{4}
∇Rˉθ=Eτ∽pθ(τ)[R(τ)∇logpθ(τ)](4)
式(4)表示使用
π
θ
\pi_\theta
πθ去跟环境互动来收集数据,然后来计算(4)中方括号那一项。现在不用
π
θ
\pi_\theta
πθ去跟环境互动,我们假定有另外一个
π
θ
′
\pi_{\theta^\prime}
πθ′去跟环境互动来训练
θ
\theta
θ,那么就转换成了Off-policy,即:
∇
R
ˉ
θ
=
E
τ
∽
p
θ
′
(
τ
)
[
p
θ
(
τ
)
q
θ
(
τ
)
R
(
τ
)
∇
l
o
g
p
θ
(
τ
)
]
(5)
\nabla \bar R_\theta=E_{\tau \backsim p_{\theta^\prime}(\tau)} \left[\frac{p_\theta(\tau)}{q_\theta(\tau)} R(\tau) \nabla logp_\theta(\tau) \right] \tag{5}
∇Rˉθ=Eτ∽pθ′(τ)[qθ(τ)pθ(τ)R(τ)∇logpθ(τ)](5)
式(5)表示数据
τ
\tau
τ从
θ
′
\theta^\prime
θ′中采样出来的,这就是使用重要性采样将On-policy转换成Off-policy。这种情况下,我们就从
θ
′
\theta^\prime
θ′里面采集数据,因为收集到的数据与
θ
\theta
θ无关,因此可以用该数据训练
θ
\theta
θ多次。
实际上,在策略梯度算法中做梯度更新的时候,并不使用整条轨迹来更新,而是使用一个状态动作对
(
s
t
,
a
t
)
(s_t,a_t)
(st,at),会分开来计算一条轨迹的所有转移,更新公式如下:
∇
R
ˉ
θ
=
E
(
s
t
,
a
t
)
∽
π
θ
[
A
θ
(
s
t
,
a
t
)
∇
l
o
g
p
θ
(
a
t
n
∣
s
t
n
)
]
(6)
\nabla \bar R_\theta=E_{(s_t,a_t) \backsim \pi_\theta} [A^\theta(s_t,a_t) \nabla logp_\theta(a_t^n|s_t^n)] \tag{6}
∇Rˉθ=E(st,at)∽πθ[Aθ(st,at)∇logpθ(atn∣stn)](6)
上式表示,我们从
θ
\theta
θ采样到
(
s
t
,
a
t
)
(s_t,a_t)
(st,at),再计算它的advantage function
A
θ
(
s
t
,
a
t
)
=
∑
t
′
=
t
T
n
γ
t
′
−
t
r
t
′
n
−
b
A^\theta(s_t,a_t)=\sum_{t^\prime=t}^{T^n}\gamma^{t^\prime-t } r_{t^\prime}^n -b
Aθ(st,at)=∑t′=tTnγt′−trt′n−b,这个函数是估测出来的,它表明在
s
t
s_t
st处采取
a
t
a_t
at是好的还是不好的。现在我们要把式子(6)转换成off-policy,即更新公式变为:
∇
R
ˉ
θ
=
E
(
s
t
,
a
t
)
∽
π
θ
′
[
p
θ
(
s
t
,
a
t
)
p
θ
′
(
s
t
,
a
t
)
A
θ
′
(
s
t
,
a
t
)
∇
l
o
g
p
θ
(
a
t
n
∣
s
t
n
)
]
(7)
\nabla \bar R_\theta=E_{(s_t,a_t) \backsim \pi_{\theta^\prime}} [\frac{p_\theta(s_t,a_t)}{p_{\theta^\prime}(s_t,a_t)} A^{\theta^\prime} (s_t,a_t) \nabla logp_\theta(a_t^n|s_t^n)] \tag{7}
∇Rˉθ=E(st,at)∽πθ′[pθ′(st,at)pθ(st,at)Aθ′(st,at)∇logpθ(atn∣stn)](7)
式子(7)中的
(
s
t
,
a
t
)
(s_t,a_t)
(st,at)是使用另外一个actor
θ
′
\theta^\prime
θ′跟环境互动所采样到的数据,而这里的重要性权重是
p
θ
(
s
t
,
a
t
)
p
θ
′
(
s
t
,
a
t
)
\frac{p_\theta(s_t,a_t)}{p_{\theta^\prime}(s_t,a_t)}
pθ′(st,at)pθ(st,at)。另外一个需注意的是,在on-policy中的advantage function是
A
θ
(
s
t
,
a
t
)
A^\theta(s_t,a_t)
Aθ(st,at),表示我们用
θ
\theta
θ采样到的数据来计算,但在off-policy中我们修改成
A
θ
′
(
s
t
,
a
t
)
A^{\theta^\prime} (s_t,a_t)
Aθ′(st,at),表示从
θ
′
\theta^\prime
θ′中采样到的数据来计算。因为
p
θ
(
s
t
,
a
t
)
=
p
θ
(
a
t
∣
s
t
)
p
θ
(
s
t
)
p_\theta(s_t,a_t)=p_\theta(a_t|s_t)p_\theta(s_t)
pθ(st,at)=pθ(at∣st)pθ(st),因此式子(7)变为了下图中的表达式。注意
s
t
s_t
st出现的概率跟
θ
和
θ
′
\theta 和\theta^\prime
θ和θ′无关因此二者概率可以消掉,其实他们的概率较难算出来,这样也省了不少的麻烦。
下图中最后一个等式是如何从上一个式子得到的呢,因为我们有
∇
p
θ
(
a
t
∣
s
t
)
=
p
θ
(
a
t
∣
s
t
)
∇
l
o
g
p
θ
(
a
t
∣
s
t
)
\nabla p_\theta(a_t|s_t)=p_\theta(a_t|s_t) \nabla logp_\theta(a_t|s_t)
∇pθ(at∣st)=pθ(at∣st)∇logpθ(at∣st)。所以我们得到:
J
θ
′
(
θ
)
=
E
(
s
t
,
a
t
)
∽
π
θ
′
[
p
θ
(
s
t
∣
a
t
)
p
θ
′
(
s
t
∣
a
t
)
A
θ
′
(
s
t
,
a
t
)
]
(8)
J^{\theta^\prime}(\theta)=E_{(s_t,a_t) \backsim \pi_{\theta^\prime}} [\frac{p_\theta(s_t|a_t)}{p_{\theta^\prime}(s_t|a_t)} A^{\theta^\prime} (s_t,a_t)] \tag{8}
Jθ′(θ)=E(st,at)∽πθ′[pθ′(st∣at)pθ(st∣at)Aθ′(st,at)](8)
J
θ
′
(
θ
)
J^{\theta^\prime}(\theta)
Jθ′(θ)中的
θ
\theta
θ是我们要去优化的参数,而用
θ
′
\theta^\prime
θ′去跟环境互动来收集数据
(
s
t
,
a
t
)
(s_t,a_t)
(st,at),然后计算
A
θ
′
(
s
t
,
a
t
)
A^{\theta^\prime} (s_t,a_t)
Aθ′(st,at)
3、PPO/TRPO
在第二部分我们说到,在使用重要性采样的时候,p和q不能相差太多,否则会出现问题,那么怎么解决这个问题呢,这就是PPO在做的事情,它的更新公式如下:
J
P
P
O
θ
′
(
θ
)
=
J
θ
′
(
θ
)
−
β
K
L
(
θ
,
θ
′
)
(9)
J_{PPO}^{\theta^\prime} (\theta)=J^{\theta^\prime}(\theta)-\beta KL(\theta, \theta^\prime) \tag{9}
JPPOθ′(θ)=Jθ′(θ)−βKL(θ,θ′)(9)
上式表明在
J
θ
′
(
θ
)
J^{\theta^\prime}(\theta)
Jθ′(θ)上减去
β
K
L
(
θ
,
θ
′
)
\beta KL(\theta, \theta^\prime)
βKL(θ,θ′),它表示
θ
和
θ
′
\theta 和\theta^\prime
θ和θ′有多像,我们希望学习到的
θ
\theta
θ和
θ
′
\theta^\prime
θ′越像越好
PPO这样的想法是来自于TRPO,在TRPO中
K
L
(
θ
,
θ
′
)
KL(\theta, \theta^\prime)
KL(θ,θ′)放的位置不一样,没有把它放在等式中,而是作为一个另外的约束,但这样的方法要处理这个约束就比较麻烦,PPO直接放在公式里面就不用考虑这个问题。
PPO的流程如下图,首先初始化参数
θ
0
\theta^0
θ0,在每一次的迭代中使用
θ
k
\theta^k
θk(k表示迭代的次数)来与环境互动来收集转移数据,并且计算每个转移的
A
θ
k
(
s
t
,
a
t
)
A^{\theta^k} (s_t,a_t)
Aθk(st,at),最后优化
J
P
P
O
(
θ
)
J_{PPO}(\theta)
JPPO(θ)。
另外我们还可以动态调整参数
β
\beta
β,先设置一个可接受的最大值
K
L
m
a
x
KL_{max}
KLmax,如果更新完参数后发现
K
L
(
θ
,
θ
k
)
KL(\theta,\theta^k)
KL(θ,θk)大于这个最大值,说明减去那一项没有发挥作用,那就增加
β
\beta
β。
下面是PPO2的更新方式,可看出,我们不需要计算
K
L
(
θ
,
θ
k
)
KL(\theta,\theta^k)
KL(θ,θk)
J
P
P
O
2
θ
k
(
θ
)
≈
∑
(
s
t
,
a
t
)
m
i
n
(
p
θ
(
s
t
∣
a
t
)
p
θ
k
(
s
t
∣
a
t
)
A
θ
k
(
s
t
,
a
t
)
,
c
l
i
p
(
p
θ
(
s
t
∣
a
t
)
p
θ
k
(
s
t
∣
a
t
)
,
1
−
ϵ
,
1
+
ϵ
)
A
θ
k
(
s
t
,
a
t
)
)
J_{PPO2}^{\theta^k} (\theta) \approx \sum_{(s_t,a_t)} min \left( \frac{p_\theta(s_t|a_t)}{p_{\theta^k}(s_t|a_t) } A^{\theta^k} (s_t,a_t), clip \left( \frac{p_\theta(s_t|a_t)}{p_{\theta^k}(s_t|a_t) } ,1-\epsilon, 1+\epsilon \right) A^{\theta^k} (s_t,a_t) \right)
JPPO2θk(θ)≈(st,at)∑min(pθk(st∣at)pθ(st∣at)Aθk(st,at),clip(pθk(st∣at)pθ(st∣at),1−ϵ,1+ϵ)Aθk(st,at))
3、PPO代码分析
for ep in range(EP_MAX):
s = env.reset()
buffer_s, buffer_a, buffer_r = [], [], []
ep_r = 0
t0 = time.time()
for t in range(EP_LEN):
a = ppo.choose_action(s) #1
s_, r, done, _ = env.step(a)
buffer_s.append(s)
buffer_a.append(a)
buffer_r.append((r + 8) / 8) # 对奖励进行归一化。有时候会挺有用的。所以我们说说,奖励是个主观的东西。
s = s_
ep_r += r
# N步更新的方法,每BATCH步了就可以进行一次更新
if (t + 1) % BATCH == 0 or t == EP_LEN - 1:
v_s_ = ppo.get_v(s_) #2
# 和PG一样,向后回溯计算。
discounted_r = []
for r in buffer_r[::-1]:
v_s_ = r + GAMMA * v_s_
discounted_r.append(v_s_)
discounted_r.reverse()
# 所以这里的br并不是每个状态的reward,而是通过回溯计算的V值
bs, ba, br = np.vstack(buffer_s), np.vstack(buffer_a), np.array(discounted_r)[:, np.newaxis]
buffer_s, buffer_a, buffer_r = [], [], []
ppo.update(bs, ba, br)
上面是PPO的总体更新流程:
1) 选择动作
Actor网络是我们要学习的网络(对应 θ \theta θ),该网络输出正太分布的均值mu和方差sigma,然后从该分布采样出连续动作a
def choose_action(self, s):
s = s[np.newaxis, :].astype(np.float32)
mu, sigma = self.actor(s) # 通过actor计算出分布的mu和sigma
pi = tfp.distributions.Normal(mu, sigma) # 用mu和sigma构建正态分布
a = tf.squeeze(pi.sample(1), axis=0)[0] # 根据概率分布随机出动作
return np.clip(a, -2, 2)
2) 回溯计算每个时间步的累积折扣奖励,分为三步:
注意最后一个时间步的价值用Critic来评估,计算出来的值相当于是每个状态的真实的价值。
- #1 使用critic网络来评估最后一个时间步处状态s_的价值v_s_
- #2 反向计算每个时间步的累积折扣奖励
- #3 把列表反向
v_s_ = ppo.get_v(s_) #1 # 计算n步中最后一个state的v_s_
# 和PG一样,向后回溯计算。
discounted_r = []
for r in buffer_r[::-1]: #2
v_s_ = r + GAMMA * v_s_
discounted_r.append(v_s_)
discounted_r.reverse() #3
举例说明,假如现在的数据集为 { ( s 1 , a 1 , r 1 ) , ( s 2 , a 2 , r 2 ) , ( s 3 , a 3 , r 3 ) } \{(s_1,a_1, r_1), (s_2,a_2, r_2), (s_3,a_3, r_3)\} {(s1,a1,r1),(s2,a2,r2),(s3,a3,r3)},反向每个时间步t=1,…,3的累积奖励 G t G_t Gt为:
- t=3时, G 3 = r 3 + γ V ( s 4 ) G_3=r_3+\gamma V(s_4) G3=r3+γV(s4)
- t=2时, G 2 = r 2 + γ G 3 = r 2 + γ ( r 3 + γ V ( s 4 ) ) = r 2 + γ r 3 + γ 2 V ( s 4 ) G_2=r_2+\gamma G_3 = r_2+\gamma(r_3+\gamma V(s_4))=r_2+\gamma r_3+\gamma^2V(s_4) G2=r2+γG3=r2+γ(r3+γV(s4))=r2+γr3+γ2V(s4)
- t=1时,
G
1
=
r
1
+
γ
G
2
=
r
1
+
γ
r
2
+
γ
2
r
3
+
γ
3
V
(
s
4
)
G_1 = r_1+ \gamma G_2 =r_1+\gamma r_2+\gamma^2 r_3 +\gamma^3 V(s_4)
G1=r1+γG2=r1+γr2+γ2r3+γ3V(s4)
其中V()是上述代码中的critic网络。
3) 计算每个时间步的Advantage A θ ′ ( s t , a t ) A^{\theta^\prime} (s_t,a_t) Aθ′(st,at)
3.1广义优势估计
一般而言,策略梯度算法的梯度估计都遵循如下形式:
∇
θ
J
(
θ
)
=
E
π
θ
[
Ψ
t
∇
θ
l
o
g
π
θ
(
a
t
∣
s
t
)
]
\nabla_\theta J(\theta) = \mathbb E_{\pi_\theta} \left[ \Psi_t \nabla_\theta {\rm log} \pi_\theta(a_t|s_t) \right]
∇θJ(θ)=Eπθ[Ψt∇θlogπθ(at∣st)]
Ψ
t
=
A
^
t
=
δ
t
+
(
γ
λ
)
δ
t
+
1
+
⋯
+
(
γ
λ
)
T
−
t
+
1
δ
T
−
1
δ
t
=
r
t
+
γ
V
(
s
t
+
1
)
−
V
(
s
t
)
\Psi_t = \widehat{A}_t = \delta_t + (\gamma \lambda) \delta_{t+1} +\cdots+(\gamma \lambda)^{T-t+1} \delta_{T-1} \\ \delta_t = r_t+\gamma V(s_{t+1}) - V(s_t)
Ψt=A
t=δt+(γλ)δt+1+⋯+(γλ)T−t+1δT−1δt=rt+γV(st+1)−V(st)
然后定义广义优势函数估计器(Generalized Advantage Estimator,以下简称GAE):
A
^
G
A
E
(
γ
,
λ
)
=
∑
k
=
0
T
(
γ
λ
)
k
δ
t
+
k
=
∑
k
=
0
T
(
γ
λ
)
k
(
r
t
+
k
+
γ
V
(
s
t
+
k
+
1
)
−
V
(
s
t
+
k
)
)
\widehat{A}^{GAE(\gamma, \lambda)} = \sum_{k=0}^T (\gamma \lambda)^k \delta_{t+k} = \sum_{k=0}^T (\gamma \lambda)^k( r_{t+k}+\gamma V(s_{t+k+1}) - V(s_{t+k}))
A
GAE(γ,λ)=k=0∑T(γλ)kδt+k=k=0∑T(γλ)k(rt+k+γV(st+k+1)−V(st+k))
当 λ = 1 \lambda=1 λ=1时,就是本代码所求出的优势函数值。接着2)中的例子:
- t=3时, A ^ 3 = δ 3 = r 3 + γ V ( s 4 ) − V ( s 3 ) \widehat{A}_3=\delta_3=r_3+\gamma V(s_{4}) - V(s_3) A 3=δ3=r3+γV(s4)−V(s3)
- t=2时, A ^ 2 = δ 2 + γ δ 3 = [ r 2 + γ V ( s 3 ) − V ( s 2 ) ] + γ [ r 3 + γ V ( s 4 ) − V ( s 3 ) ] = [ r 2 + γ r 3 + γ 2 V ( s 4 ) ] − V ( s 2 ) \widehat{A}_2=\delta_2 + \gamma \delta_3 = [r_2+\gamma V(s_{3})-V(s_2)] + \gamma[r_3+\gamma V(s_{4}) - V(s_3)] = [r_2 + \gamma r_3+\gamma^2 V(s_4)] -V(s_2) A 2=δ2+γδ3=[r2+γV(s3)−V(s2)]+γ[r3+γV(s4)−V(s3)]=[r2+γr3+γ2V(s4)]−V(s2)
- t=1时,
A ^ 1 = δ 1 + γ δ 2 + γ 2 δ 3 = [ r 1 + γ V ( s 2 ) − V ( s 1 ) ] + γ [ r 2 + γ V ( s 3 ) − V ( s 2 ) ] + γ 2 [ r 3 + γ V ( s 4 ) − V ( s 3 ) ] = [ r 1 + γ r 2 + γ 2 r 3 + γ 3 V ( s 4 ) ] − V ( s 1 ) \begin{aligned} \widehat{A}_1 &=\delta_1+\gamma \delta_2 +\gamma^2 \delta_3 \\ & =[ r_1+\gamma V(s_{2}) - V(s_1)] + \gamma [r_2+\gamma V(s_{3})-V(s_2)] +\gamma^2 [r_3+\gamma V(s_{4}) - V(s_3)]\\ & =[r_1 + \gamma r_2 + \gamma^2 r_3+\gamma^3 V(s_4)] -V(s_1) \end{aligned} A 1=δ1+γδ2+γ2δ3=[r1+γV(s2)−V(s1)]+γ[r2+γV(s3)−V(s2)]+γ2[r3+γV(s4)−V(s3)]=[r1+γr2+γ2r3+γ3V(s4)]−V(s1)
下列代码中函数cal_adv()的参数tfs表示状态集,tfdc_r是2)中计算出来的累积折扣奖励(每个状态的真实的价值),我们用
self.critic(tfs)来评估(即预测)每个状态的价值,他们的差值就是Advantage。
def cal_adv(self, tfs, tfdc_r):
'''
计算advantage,也就是td-error
'''
tfdc_r = np.array(tfdc_r, dtype=np.float32)
advantage = tfdc_r - self.critic(tfs) # advantage = r - gamma * V(s_)
return advantage.numpy()
4) 更新Actor网络,也就是说更新 θ \theta θ
更新为式子(10),在上一部分,我们已经算出来了
A
θ
′
(
s
t
,
a
t
)
A^{\theta^\prime} (s_t,a_t)
Aθ′(st,at),其中
θ
′
\theta^\prime
θ′在我们的代码中为self.actor_old,即为收集数据的
θ
′
\theta^\prime
θ′
J
θ
′
(
θ
)
=
E
(
s
t
,
a
t
)
∽
π
θ
′
[
p
θ
(
s
t
∣
a
t
)
p
θ
′
(
s
t
∣
a
t
)
A
θ
′
(
s
t
,
a
t
)
]
(10)
J^{\theta^\prime}(\theta)=E_{(s_t,a_t) \backsim \pi_{\theta^\prime}} [\frac{p_\theta(s_t|a_t)}{p_{\theta^\prime}(s_t|a_t)} A^{\theta^\prime} (s_t,a_t)] \tag{10}
Jθ′(θ)=E(st,at)∽πθ′[pθ′(st∣at)pθ(st∣at)Aθ′(st,at)](10)
- #1 - #4: 首先分别使用actor和actor_old来生成两个分布,即pi和oldpi。
- #5: 然后使用这两个分布来计算每个转移样本下动作的概率,并计算出 p θ ( s t ∣ a t ) p θ ′ ( s t ∣ a t ) \frac{p_\theta(s_t|a_t)}{p_{\theta^\prime}(s_t|a_t)} pθ′(st∣at)pθ(st∣at)
- #6: 比例和advantage相乘
- #7: 计算loss
def a_train(self, tfs, tfa, tfadv):
'''
更新策略网络(policy network)
'''
# 输入时s,a,td-error。这个和AC是类似的。
tfs = np.array(tfs, np.float32) #state
tfa = np.array(tfa, np.float32) #action
tfadv = np.array(tfadv, np.float32) #td-error
with tf.GradientTape() as tape:
# 【敲黑板】这里是重点!!!!
# 我们需要从两个不同网络,构建两个正态分布pi,oldpi。
mu, sigma = self.actor(tfs) #1
pi = tfp.distributions.Normal(mu, sigma)#2
mu_old, sigma_old = self.actor_old(tfs) #3
oldpi = tfp.distributions.Normal(mu_old, sigma_old) #4
# ratio = tf.exp(pi.log_prob(self.tfa) - oldpi.log_prob(self.tfa))
# 在新旧两个分布下,同样输出a的概率的比值
# 除以(oldpi.prob(tfa) + EPS),其实就是做了import-sampling。怎么解释这里好呢
# 本来我们是可以直接用pi.prob(tfa)去跟新的,但为了能够更新多次,我们需要除以(oldpi.prob(tfa) + EPS)。
# 在AC或者PG,我们是以1,0作为更新目标,缩小动作概率到1or0的差距
# 而PPO可以想作是,以oldpi.prob(tfa)出发,不断远离(增大or缩小)的过程。
ratio = pi.prob(tfa) / (oldpi.prob(tfa) + EPS) #5
# 这个的意义和带参数更新是一样的。
surr = ratio * tfadv #6
# 我们还不能让两个分布差异太大。
# PPO1
if METHOD['name'] == 'kl_pen':
tflam = METHOD['lam']
kl = tfp.distributions.kl_divergence(oldpi, pi)
kl_mean = tf.reduce_mean(kl)
aloss = -(tf.reduce_mean(surr - tflam * kl)) #7
# PPO2:
# 很直接,就是直接进行截断。
else: # clipping method, find this is better
aloss = -tf.reduce_mean(
tf.minimum(ratio * tfadv, #surr
tf.clip_by_value(ratio, 1. - METHOD['epsilon'], 1. + METHOD['epsilon']) * tfadv)
)
a_gard = tape.gradient(aloss, self.actor.trainable_weights)
self.actor_opt.apply_gradients(zip(a_gard, self.actor.trainable_weights))
if METHOD['name'] == 'kl_pen':
return kl_mean
- #1:for循环首次更新actor时, 它与actor_old是同一个网络,但当使用同一组数据第二次更新actor时,由于actor之前已被更新过,所以这时的actor是新的了,而数据还是最初由actor_old产生的。因此同一组数据可以更新actor多次,这就是ppo的核心所在。
else:
for _ in range(A_UPDATE_STEPS): #1
self.a_train(s, a, adv)
# 更新 critic
for _ in range(C_UPDATE_STEPS):
self.c_train(r, s)
5) 更新Critic网络
使用advantage来更新critic网络:
A
θ
′
(
s
t
,
a
t
)
=
G
t
−
V
(
s
t
)
A^{\theta^\prime} (s_t,a_t) = G_t - V(s_t)
Aθ′(st,at)=Gt−V(st)
def c_train(self, tfdc_r, s):
'''
更新Critic网络
'''
tfdc_r = np.array(tfdc_r, dtype=np.float32) #tfdc_r可以理解为PG中就是G,通过回溯计算。只不过这PPO用TD而已。
with tf.GradientTape() as tape:
v = self.critic(s)
advantage = tfdc_r - v # 就是我们说的td-error
closs = tf.reduce_mean(tf.square(advantage))
grad = tape.gradient(closs, self.critic.trainable_weights)
self.critic_opt.apply_gradients(zip(grad, self.critic.trainable_weights))