《白话强化学习与PyTorch》学习笔记---第九章

第九章---PG算法族

  • 9.1 PG
  • 9.2 Actor-Critic
  • 9.3 DDPG

在第八章中的DQN算法族中,都是求一个状态或则一个状态下某个动作的估值为手段的“间接”求解策略,而本章中的策略梯度法(Policy Gradient)手段更为直接,直接让一个模型或则网络学到一个策略,从而可以解决DQN算法族中无法解决的连续控制问题。本书中的9.1~9.3节本小白感觉介绍的不太容易明白,具体理解仍然可以参考之前推荐的刘建平老师的系列博客。下面是我的学习笔记,相关代码已上传到我的GitHub中:

9.1 PG

既然需要求解一个策略,那首先需要将策略用数学语言进行表达。在基于策略的强化学习中,策略 π \pi π可以被描述为一个包含参数 θ \theta θ的函数:
π θ ( s , a ) = P [ a ∣ s , θ ] ≈ π ( a ∣ s ) \pi_\theta (s,a)=P[a|s,\theta]\approx\pi(a|s) πθ(s,a)=P[as,θ]π(as)
可以看出,策略函数 π θ \pi_\theta πθ是一个概率密度函数,得到是在某一个状态下采取某一个动作的概率。那么在应用这个策略时,我们在某一状态选择的就是对应的概率最大的动作或则进行一定的采样探索。因此,如何确定策略函数中的参数 θ \theta θ就是解决问题的关键。因此要寻找一个最优参数,需要设计一个基于参数 θ \theta θ的目标函数 J ( θ ) J(\theta) J(θ),有 J 1 ( θ ) J_1(\theta) J1(θ)(初始状态价值), J a v V ( θ ) J_{avV}(\theta) JavV(θ)(平均价值), J a v R ( θ ) J_{avR}(\theta) JavR(θ)(平均奖励)三种设计策略目标函数的方法(具体公式就不写了),因为我们希望策略目标函数的值越大越好,因此可以使用之前所提到的梯度下降相反的梯度上升的方法来求解最优参数(同样具体推导公式不写了),最终无论采用上述的哪种策略目标函数,其策略梯度都可以写成如下形式:
∇ θ J ( θ ) = E π θ [ ∇ θ l o g π θ ( s , a ) Q π θ ( s , a ) ] \nabla_\theta J(\theta)=\mathbb{E}_{\pi \theta}[\nabla_\theta log\pi_\theta(s,a)Q_{\pi_{\theta}}(s,a)] θJ(θ)=Eπθ[θlogπθ(s,a)Qπθ(s,a)]
参数更新方法为: θ = θ + α ∇ θ l o g π θ ( s t , a t ) Q π θ ( s , a ) \theta = \theta + \alpha \nabla_\theta log_{\pi_\theta} (s_t,a_t)Q_{\pi_{\theta}}(s,a) θ=θ+αθlogπθ(st,at)Qπθ(s,a)
其中 ∇ θ l o g π θ ( s , a ) \nabla_\theta log\pi_\theta(s,a) θlogπθ(s,a)为分值函数(score function)。接下来就是策略函数 π θ ( s , a ) \pi_\theta (s,a) πθ(s,a)的设计,针对离散动作空间和连续动作空间主要分为如下两种:

  1. 离散空间——Softmax策略,其分值函数为: ∇ θ l o g π θ ( s , a ) = ϕ ( s , a ) − E π θ [ ϕ ( s , ⋅ ) ] \nabla_\theta log\pi_\theta(s,a) = \phi(s,a)-\mathbb{E}_{\pi_\theta}[\phi(s,\cdot)] θlogπθ(s,a)=ϕ(s,a)Eπθ[ϕ(s,)]
  2. 连续空间——高斯策略,其分值函数为: ∇ θ l o g π θ ( s , a ) = ( a − μ ( s ) ) ϕ ( s ) σ 2 \nabla_\theta log\pi_\theta(s,a) =\frac{(a-\mu(s))\phi(s)}{\sigma^2} θlogπθ(s,a)=σ2(aμ(s))ϕ(s)

有了策略梯度公式和策略函数后,就可以得到基于策略梯度最基本的算法——蒙特卡罗策略梯度reinforce算法,该算法使用某状态下收获的期望 E [ G t ] \mathbb{E}[G_t] E[Gt],即价值函数 v t ( s ) v_t(s) vt(s)来作为基于策略 π θ \pi_\theta πθ下行为价值 Q π θ ( s , a ) Q_{\pi_{\theta}}(s,a) Qπθ(s,a)的估计值。其算法流程很简单,对于每一个蒙特卡洛序列,计算每个时间位置( t t t)的 E [ G t ] \mathbb{E}[G_t] E[Gt],并且对每个时间位置( t t t)使用梯度上升法更新策略函数中的参数 θ \theta θ。下面是具体的代码实现:
这里使用了Gym中的CartPole-v0游戏环境,该环境只有两个离散动作,因此采用softmax策略作为我们的策略函数,我们的策略模型神经网络来表示,最后一层为softmax层。这样好处就是梯度的更新可以交给神经网络来做,其策略网络如下所示:

class Policy(nn.Module):
    def __init__(self):
        super(Policy, self).__init__()
        self.state_space = state_space
        self.action_space = action_space
        self.fc1 = nn.Linear(self.state_space, 128)
        self.fc2 = nn.Linear(128, self.action_space)

    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = F.softmax(self.fc2(x), dim=-1)
        return x

因此最后输出的结果是所有动作的概率,其和为1。如本例中只有两个动作(0,1),若某一状态输出的x为[0.8,0.2],则表示在该状态下选择动作0的概率大。因此对应的选择动作的代码为:

def choose_action(self,state):
    state = torch.unsqueeze(torch.FloatTensor(state), 0) #状态转换成Tensor
    probs = self.policy(state) #得到每个动作的概率
    c = Categorical(probs)
    action =c.sample() #根据概率随机采样得到一个动作
    return  int(action.data.numpy())

最后进行网络更新的代码为:

def learn(self):
    r = 0
    for i in reversed(range(len(self.reward_pool))):
        r = r*gamma+self.reward_pool[i]
        self.reward_pool[i] = r

    reward_mean = np.mean(self.reward_pool)
    reward_std = np.std(self.reward_pool)
    self.reward_pool = (self.reward_pool - reward_mean) / reward_std

    self.optimizer.zero_grad()
    for i in range(len(self.state_pool)):
        state = torch.unsqueeze(torch.FloatTensor(self.state_pool[i]), 0)
        action = torch.FloatTensor([self.action_pool[i]])
        reward = self.reward_pool[i]
        probs = self.policy(state)
        c =  Categorical(probs)
        loss = -c.log_prob(action) * reward
        print(loss)
        loss.backward()
     self.optimizer.step()

前半段主要是根据每一回合的蒙特卡洛序列存储的reward来计算价值函数(代码中都为self.reward_pool) v t ( s ) = E [ G t ] = E [ r t + 1 + γ r t + 2 + γ 2 r t + 3 + . . . ] v_t(s)=\mathbb{E}[G_t]=\mathbb{E}[r_{t+1}+\gamma r_{t+2} +\gamma^2r_{t+3}+...] vt(s)=E[Gt]=E[rt+1+γrt+2+γ2rt+3+...]后半段主要是进行梯度更新,其中c.log_prob(action)即为公式中的 l o g π θ ( s , a ) log\pi_\theta(s,a) logπθ(s,a),reward即为 G t G_t Gt,加一个负号是因为优化器使用的是梯度下降,而我们需要的是梯度上升。
可以看出上面这个例子是对应于离散空间的,关于连续动作空间需要使用高斯策略函数,作为小白的我编了一个代码来训练Gym中的环境Pendulum-v0,不过分数总是很低,不知是代码问题还是算法本身问题,要是有小伙伴有连续空间效果比较好的代码还请分享我一下(哈哈)

9.2 Actor-Critic

了解了基于策略的强化学习算法基本思路后,再看Actor-Critic算法就轻松的多了。在上面的蒙特卡洛Reinforce算法中,我们用收获的期望 E [ G t ] \mathbb{E}[G_t] E[Gt]代替了基于策略 π θ \pi_\theta πθ下的行为价值 Q π θ ( s , a ) Q_{\pi_{\theta}}(s,a) Qπθ(s,a),这会导致行为有较多的变异性,我们的参数更新的方向很可能不是策略梯度的最优方向。因此,为了解决这一问题,提出了一种策略(Policy Based)和价值(Value Based)相结合算法,即Actor-Critic。
在DP算法中,策略函数就是我们的Actor,负责产生动作进行环境交互,但是没有Critic,我们计算了蒙特卡洛序列的每一状态下收获的期望了代替了Critic的功能,来评价Actor的表现。而现在Critic的行为价值函数也是通过一个网络训练得到的,是基于策略 π θ \pi_\theta πθ的一个近似:
Q w ( s , a ) ≈ Q π θ ( s , a ) Q_w(s,a)\approx Q_{\pi_{\theta}}(s,a) Qw(s,a)Qπθ(s,a)
那么我们策略网络的参数更新是否就直接更改为 θ = θ + α ∇ θ l o g π θ ( s t , a t ) Q π θ ( s , a ) \theta = \theta + \alpha \nabla_\theta log_{\pi_\theta} (s_t,a_t)Q_{\pi_{\theta}}(s,a) θ=θ+αθlogπθ(st,at)Qπθ(s,a)了呢?也不是,我们有很多可以选择的强化学习方法来进行Actor-Critic学习,其常用的是基于状态价值TD误差的Actor-Critic算法,这样Actor的策略函数参数更新法公式是:
θ = θ + α ∇ θ l o g π θ ( s t , a t ) δ ( t ) \theta = \theta + \alpha \nabla_\theta log_{\pi_\theta} (s_t,a_t)\delta(t) θ=θ+αθlogπθ(st,at)δ(t)
其中, δ ( t ) = R t + γ V w ( S t + 1 ) − V w ( S t ) \delta(t) = R_{t} + \gamma V_w(S_{t+1})-V_w(S_t) δ(t)=Rt+γVw(St+1)Vw(St),而状态价值函数 V w ( t ) V_w(t) Vw(t)是通过Critic网络得到的。而Critic网络使用均方差损失函数 ∑ ( R t + γ V w ( S t + 1 ) − V w ( S t ) ) 2 \sum {(R_{t} + \gamma V_w(S_{t+1})-V_w(S_t)})^2 (Rt+γVw(St+1)Vw(St))2来进行参数 w w w的梯度更新。
这样一来就可以进行单步更新,比传统的蒙特卡洛要快,但是因为两个网络的优化相互依赖,因此收敛性较差。将该算法应用到CartPole-v0游戏中时,发现算法不收敛…
因此,所给的代码中仍然使用的时蒙特卡洛方法来进行Actor-Critic学习,实验表明收敛速度很快。此时策略函数 π θ ( s , a ) \pi_\theta (s,a) πθ(s,a)的参数更新方式为:
θ = θ + α ∇ θ l o g π θ ( s t , a t ) ( G t − V w ( s t ) ) \theta = \theta + \alpha \nabla_\theta log_{\pi_\theta} (s_t,a_t) (G_t -V_w (s_t)) θ=θ+αθlogπθ(st,at)(GtVw(st))
Critic网络参数 w w w的均方误差函数为: ∑ ( G t − V w ( s t ) ) 2 \sum(G_t -V_w (s_t))^2 (GtVw(st))2
代码部分和第一节所讲的蒙特卡罗策略梯度reinforce算法类似,只是多了一个Critic网络和网络参数的更新

class Q_net(nn.Module):
    def __init__(self):
        super(Q_net, self).__init__()
        self.fc1 = nn.Linear(state_space,32)
        self.out = nn.Linear(32,1)

    def forward(self,x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.out(x)
        return  x

因为输出的时价值函数,所以输出维度为1。

def learn(self):
    r = 0
    for i in reversed(range(len(self.reward_pool))):
        r = r*gamma+self.reward_pool[i]
        self.reward_pool[i] = r

    self.optimizer_q.zero_grad()
    for i in range(len(self.state_pool)):
        state = torch.unsqueeze(torch.FloatTensor(self.state_pool[i]), 0)
        reward = torch.FloatTensor([self.reward_pool[i]])
        v  = self.Q_net(state)
        loss_q = self.loss_td(reward, v)
        loss_q.backward()
    self.optimizer_q.step()
    self.optimizer_policy.zero_grad()
    for i in range(len(self.state_pool)):
        state = torch.unsqueeze(torch.FloatTensor(self.state_pool[i]), 0)
        action = torch.FloatTensor([self.action_pool[i]])
        reward = self.reward_pool[i] - self.Q_net(state)
        probs = self.policy(state)
        c =  Categorical(probs)
        loss = -c.log_prob(action) * reward
        loss.backward()

    self.optimizer_policy.step()
    self.state_pool = []
    self.action_pool = []
    self.reward_pool = []

网络训练学习的代码主要分为三步:计算 G T G_T GT,更新Critc网络参数,更新Actor网络参数

9.3 DDPG

前面讲了在使用单步跟新的Actor-Critic算法时,存在收敛困难的问题,深度确定性策略梯度(DDPG)算法通过经验回放和双网络的方法进行了算法改进。在9.1中讲到的PG是一个随机策略,因为策略函数 π θ \pi_\theta πθ是一个概率密度函数,因此在动作的选择时基于一个概率分布的,是不确定性的。而DPG(Deterministic Policy Gradient)则跳过了概率分布这一步,我们所训练的策略是直接输出某一状态下的动作,即:
π θ = a \pi_\theta=a πθ=a
且在原论文中证明了确定性策略梯度的存在:
∇ θ μ ( J ) = E s t ∼ ρ β [ ∇ a Q ( s , a ∣ θ Q ) ∣ s = s t , a = μ ( s t ) ∇ θ μ μ ( s ∣ θ μ ) ∣ s = s t ] \nabla_{\theta^\mu}(J)=\mathbb{E}_{s_t\sim\rho^\beta}[\nabla_aQ(s,a| \theta^Q)|_{s=s_t,a=\mu(s_t)}\nabla_{\theta_\mu}\mu(s|\theta^\mu)|_{s=s_t}] θμ(J)=Estρβ[aQ(s,aθQ)s=st,a=μ(st)θμμ(sθμ)s=st]
通过这个式子就可以计算每次优化输出动作的网络 μ θ \mu_\theta μθ的参数 θ \theta θ的更新量。这个看上去很复杂,但实际应用的时候可以简单理解为在某一状态 s t s_t st下Actor网络输出动作 a t = μ ( s ∣ θ μ ) ∣ s = s t a_t=\mu(s|\theta^\mu)|_{s=s_t} at=μ(sθμ)s=st,Critic将对该状态下的动作 a t a_t at的进行评估得到一个动作价值 Q Q Q值,而我们期望动作价值越大越好,因此当我们使用神经网络进行梯度下降更新参数使,可以使用下式进行更新:
J ( θ μ ) = 1 m ∑ j = i m Q ( s i , a i ∣ θ Q ) J(\theta^\mu)=\frac{1}{m}\sum_{j=i}^m Q(s_i,a_i|\theta^Q) J(θμ)=m1j=imQ(si,aiθQ)
其余DPG到DDPG的过程可以类比于DQN到DDQN的过程(详细可见我第八章中的内容),下面是DDPG的算法流程图:

初始化Critic当前网络 Q ( s , a ∣ θ Q ) Q(s,a|\theta^Q) Q(s,aθQ)和Actor当前网络 μ ( s ∣ θ μ ) \mu(s|\theta^\mu) μ(sθμ),网络参数分别为 θ Q \theta^Q θQ θ μ \theta^\mu θμ
初始化目标网络参数 Q ′ Q' Q μ ′ \mu' μ,且参数分别和当前网络参数相等: θ Q ′ ← τ θ Q + ( 1 − τ ) θ Q ′ \theta^{Q'}\leftarrow \tau \theta^Q+(1- \tau)\theta^{Q'} θQτθQ+(1τ)θQ, θ μ ′ + ( 1 − τ ) θ μ ′ \theta^{\mu'}+(1-\tau)\theta^{\mu'} θμ+(1τ)θμ
初始化经验回放池 R R R,初始化随机噪声 N N N(用于动作探索),批量梯度下降的样本数 m m m
for episode=1,M
     获得初始状态 s 1 s_1 s1
      for t = 1,T
          从当前Actor网络中获取动作并加上一个随机噪声: a t = μ ( s t ∣ θ μ ) + N t a_t=\mu(s_t|\theta^\mu)+N_t at=μ(stθμ)+Nt
          执行动作 a t a_t at得到奖励 r t r_t rt和下一状态 s t + 1 s_{t+1} st+1
          将 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1)存进经验回放池中 R R R
           从经验回放池中采集m个样本,将 s i + 1 s_{i+1} si+1输入目标Actor网络,再将其得到的动作和 s i + 1 s_{i+1} si+1输入目标Critic网络得到状态 s i + 1 s_{i+1} si+1下的目标Critic网络输出值,即: Q ′ ( s i + 1 , μ ′ ( s i + 1 ∣ θ μ ′ ) ∣ θ Q ′ ) Q'(s_{i+1},\mu'(s_{i+1}|\theta^{\mu'})|\theta^{Q'}) Q(si+1,μ(si+1θμ)θQ) ,令当前时刻目标 Q Q Q值为 y i = r i + γ Q ′ ( s i + 1 , μ ′ ( s i + 1 ∣ θ μ ′ ) ∣ θ Q ′ ) y_i=r_i+\gamma Q'(s_{i+1},\mu'(s_{i+1}|\theta^{\mu'})|\theta^{Q'}) yi=ri+γQ(si+1,μ(si+1θμ)θQ)
          由当前Critic网络计算 Q Q Q,结合目标Critic网络计算得到的 y i y_i yi更新当前Critic网络参数 θ Q \theta^Q θQ——最小化均方差损失函数: L = 1 N ∑ i ( y i − Q ( s i , a i ∣ θ Q ) ) 2 ) L = \frac{1}{N} \sum_i(y_i-Q(s_i,a_i|\theta^Q))^2) L=N1i(yiQ(si,aiθQ))2)
           更新当前Actor网络参数 J ( θ μ ) = 1 m ∑ j = i m Q ( s i , a i ∣ θ Q ) J(\theta^\mu)=\frac{1}{m}\sum_{j=i}^m Q(s_i,a_i|\theta^Q) J(θμ)=m1j=imQ(si,aiθQ)
          更新目标网络参数 θ Q ′ ← τ θ Q + ( 1 − τ ) θ Q ′ \theta^{Q'}\leftarrow \tau \theta^Q+(1- \tau)\theta^{Q'} θQτθQ+(1τ)θQ, θ μ ′ + ( 1 − τ ) θ μ ′ \theta^{\mu'}+(1-\tau)\theta^{\mu'} θμ+(1τ)θμ

在最后的更新目标网络网络参数时的 τ \tau τ是一个远远小于1的数字,也就是说,更新目标网络参数时并不是一下子粗鲁地把它赋值为当前网络的参数值,而是让它在很大程度上保留原来的值,慢慢的进行更新。在提供的代码中 τ \tau τ为0.01,更新代码如下所示;

        for target_param, param in zip(self.Actor_target.parameters(), self.Actor_eval.parameters()):
        target_param.data.copy_(
            target_param.data * (1.0 - TAU) + param.data * TAU
        )
    for target_param, param in zip(self.Critic_target.parameters(), self.Critic_eval.parameters()):
        target_param.data.copy_(
            target_param.data * (1.0 - TAU) + param.data * TAU
        )

在DDPG算法中使用的是具有连续动作的Pendulum-v0游戏环境。下面分别是Actor和Critic神经网络的搭建代码:

class ANet(nn.Module):   
    def __init__(self,s_dim,a_dim):
        super(ANet,self).__init__()
        self.fc1 = nn.Linear(s_dim,hidden_size)

        self.out = nn.Linear(hidden_size,a_dim)

    def forward(self,x):
        x = self.fc1(x)
        torch.nn.Dropout(0.5)
        x = F.relu(x)
        x = self.out(x)
        x = F.tanh(x)     #输出范围为-1,1
        actions_value = x*2    #更改输出范围print(env.action_space.high)
        return actions_value

class CNet(nn.Module): 
    def __init__(self,s_dim,a_dim):
        super(CNet,self).__init__()
        self.fcs = nn.Linear(s_dim,hidden_size)
        self.fca = nn.Linear(a_dim,hidden_size)
        self.out = nn.Linear(hidden_size,1)

    def forward(self,s,a):
        x = self.fcs(s)
        y = self.fca(a)
        torch.nn.Dropout(0.5)
        net = F.relu(x+y)
        actions_value = self.out(net)
        return actions_value

Actor网络(ANet)的输出即为实际的动作值,可以看出经过tanh(x)激励函数后最终输出的结果为在[-2,2]区间里的连续值。Critic网络(CNet)的输出值即为对在 s s s状态下采取动作 a a a的评价值。因为在训练过程中发现该环境的初始状态总是随机的,训练效果并不是那么的好,于是使用了torch.nn.Dropout(0.5)来缓解过拟合,它的效果简单来说就是在训练的时候会随机忽略一些神经元(如0.5表示忽略了50%的神经元),防止神经网络对部分神经元的过度依赖,导致在某些情况测试效果很好换了一个初始状态测试效果很差的情况出现。但是在测试的时候,我们仍然需要从ANet网络中输出动作,此刻我们不想要丢掉任何神经元,因此需要让ANet进入评估模式:self.Actor_eval.eval()。(默认为训练模式:self.Actor_eval.train())。
其余代码都是在前两节代码的基础上更改的,结合算法流程图应该不难理解。可以说,DDPG是在单进程、单线程领域对于连续控制模型的较为成熟的训练方案,而且思路也并不复杂()
这是在回家的最后一天更新完了第八章,呐祝新年快乐吧(如果有朋友看到了这里),下学期可能更新就很慢了(┭┮﹏┭┮)

你可能感兴趣的:(学习笔记)