Proximal Policy Optimization (PPO)

本文详细介绍了强化学习中的PPO算法原理及其实现过程,包括On-policy与Off-policy的区别、重要性采样的应用、PPO算法的具体流程及其实现代码分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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} Exp[f(x)]N1i=1Nf(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} Exp[f(x)]=f(x)p(x)dx=f(x)q(x)p(x)q(x)dx=Exq[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)}] Exq[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)] Exp[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)}] Exq[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θ(atnstn)](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γttrtnb,这个函数是估测出来的,它表明在 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θ(atnstn)](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θ(atst)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θ(atst)=pθ(atst)logpθ(atst)。所以我们得到:
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θ(stat)pθ(stat)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(stat)pθ(stat)Aθk(st,at),clip(pθk(stat)pθ(stat),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πθ(atst)]
Ψ 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++(γλ)Tt+1δT1δ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=0T(γλ)kδt+k=k=0T(γλ)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θ(stat)pθ(stat)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θ(stat)pθ(stat)
  • #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)=GtV(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))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值