强化学习——PyTorch 实现 Advantage Actor-Critic (A2C)

前言

  本博客的理论知识来自王树森老师《深度强化学习》,这本书写得简直太好了,强烈推荐,只是现在还在校对没出版,可能有些小瑕疵,但并不影响阅读和学习。

Advantage Actor-Critic (A2C)

  本次的 A2C 的原理我们从带基线的策略梯度开始,在对带基线的策略梯度做蒙特卡洛近似,得到策略梯度的一个无偏估计:
g ( s , a , ; θ ) = [ Q π ( s , a ) − V π ( s ) ⋅ ∇ ln ⁡ π ( a ∣ s ; θ ) ] (1) \boldsymbol{g}(s,a,;\boldsymbol{\theta}) = \left[ Q_\pi(s, a) - V_\pi (s) \cdot \nabla \ln \pi(a|s;\theta)\right] \tag1 g(s,a,;θ)=[Qπ(s,a)Vπ(s)lnπ(as;θ)](1)
公式中的 Q π − V π Q_\pi - V_\pi QπVπ 被称作优势函数 (Advantage Function)。因此,基于上面公式得到的 Actor-Critic 方法被称为 Advantage Actor-Critic,缩写 A2C。A2C 属于 Actor-Critic 方法。有一个策略网络 π ( a ∣ s ; θ ) \pi(a|s;\theta) π(as;θ),相当于演员,用于控制智能体运动。还有一个价值网络 v ( s ; w ) v(s; w) v(s;w),相当于评委,他的评分可以帮助策略网络(演员)改进技术。
强化学习——PyTorch 实现 Advantage Actor-Critic (A2C)_第1张图片

算法推导

  训练价值网络: 训练价值网络 v ( s ; w ) v(s; w) v(s;w) 的算法是从贝尔曼公式来的:
V π ( s t ) = E A t ∼ π ( ⋅ ∣ s t ; θ ) [ E S t + 1 ∼ p ( ⋅ ∣ s t , A t ) [ R t + γ ⋅ V π ( S t + 1 ) ] ] V_\pi(s_t) = \mathbb{E}_{A_t∼\pi(\cdot|s_t;\theta)}[\mathbb{E}_{S_{t+1}∼p(\cdot|s_t, A_t)}[R_t + \gamma \cdot V_\pi(S_{t+1})]] Vπ(st)=EAtπ(st;θ)[ESt+1p(st,At)[Rt+γVπ(St+1)]]
对贝尔曼方程左右两边做近似:

  • 方程左边的 V π ( s t ) V_π(s_t) Vπ(st) 可以近似成 v ( s t ; w ) v(s_t; w) v(st;w) v ( s t ; w ) v(s_t; w) v(st;w) 是价值网络在 t t t 时刻对 V π ( s t ) V_π(s_t) Vπ(st) 做出的估计;
  • 方程右边的期望是关于当前时刻动作 A t A_t At 与下一时刻状态 S t + 1 S_{t+1} St+1 求的。给定当前状态 s t s_t st,智能体执行动作 a t a_t at,环境会给出奖励 r t r_t rt 和新的状态 s t + 1 s_{t+1} st+1。用观测到的 r t r_t rt s t + 1 s_{t+1} st+1 对期望做蒙特卡洛近似,得到:
    r t + γ ⋅ V π ( s t + 1 ) (2) r_t + \gamma \cdot V_\pi(s_{t + 1}) \tag 2 rt+γVπ(st+1)(2)
  • 公式 (1) 中的 V π ( s t + 1 ) V_\pi(s_{t + 1}) Vπ(st+1) 近似成 v ( s t + 1 ; w ) v(s _ {t + 1};\boldsymbol{w}) v(st+1;w),得到:
    y t ^ : = r t + γ ⋅ v ( s t + 1 ; w ) \hat{y_t} := r_t + \gamma \cdot v(s_{t + 1};\boldsymbol{w}) yt^:=rt+γv(st+1;w)
    把它称作 TD 目标。它是价值网络在 t + 1 t + 1 t+1 时刻对 V π ( s t ) V_π(s_t) Vπ(st) 做出的估计。

v ( s t ; w ) v(s_t; w) v(st;w) y ^ t \hat y_t y^t 都是对动作价值 V π ( s t ) Vπ_(s_t) Vπ(st) 的估计。由于 y ^ t \hat y_t y^t 部分基于真实观测到的奖励 r t r_t rt,我们认为 y ^ t \hat y_t y^t v ( s t ; w ) v(s_t;\boldsymbol{w}) v(st;w) 更可靠。所以把 y ^ t \hat y_t y^t 固定住,更新 w \boldsymbol{w} w,使得 v ( s t ; w ) v(s_t;\boldsymbol{w}) v(st;w) 更接近 y ^ t \hat y_t y^t。具体一点,定义损失函数:
L ( w ) : = 1 2 [ v ( s t ; w ) − y ^ t ] 2 L(\boldsymbol{w}) := \frac{1}{2}[v(s_t;\boldsymbol{w}) - \hat y_t]^2 L(w):=21[v(st;w)y^t]2
v ^ t : = v ( s t ; w ) \hat v_t := v(s_t;\boldsymbol{w}) v^t:=v(st;w),损失函数的梯度为:
∇ w L ( w ) = ( v ^ t − y ^ t ) ⏟ T D  误差  δ t ⋅ ∇ w v ( s t ; w ) \nabla_{\boldsymbol{w}}L(\boldsymbol{w}) = \underbrace{(\hat v_t - \hat y_t)}_{TD\ 误差\ \delta_t} \cdot \nabla_{\boldsymbol{w}}v(s_t;\boldsymbol{w}) wL(w)=TD 误差 δt (v^ty^t)wv(st;w)
定义 TD 误差为 δ : = v ^ t − y ^ t \delta := \hat v_t - \hat y_t δ:=v^ty^t。做一轮梯度下降更新 w \boldsymbol{w} w
w ← w − α ⋅ δ t ⋅ ∇ w v ( s t ; w ) \boldsymbol{w} \leftarrow \boldsymbol{w}- \alpha \cdot \delta_t \cdot \nabla_{\boldsymbol{w}}v(s_t;\boldsymbol{w}) wwαδtwv(st;w)
这样可以让价值网络的预测 v ( s t ; w ) v(s_t;\boldsymbol{w}) v(st;w) 更接近 y ^ t \hat y_t y^t

  训练策略网络:A2C 从公式 (1) 出发,对 g ( s , a , ; θ ) \boldsymbol{g}(s,a,;\boldsymbol{\theta}) g(s,a,;θ) 做近似,记作 g ~ \boldsymbol{\tilde{g}} g~,然后用 g ~ \boldsymbol{\tilde{g}} g~ 更新策略网络参数 θ \boldsymbol{\theta} θ。下面我们做数学推导。回忆一下贝尔曼公式:
Q π ( s t , a t ) = E S t + 1 ∼ p ( ⋅ ∣ s t , A t ) [ R t + γ ⋅ V π ( S t + 1 ) ] Q_\pi(s_t ,a_t) = \mathbb{E}_{S_{t+1}∼p(\cdot|s_t, A_t)}[R_t + \gamma \cdot V_\pi(S_{t+1})] Qπ(st,at)=ESt+1p(st,At)[Rt+γVπ(St+1)]
把近似策略梯度 g ( s , a , ; θ ) \boldsymbol{g}(s,a,;\boldsymbol{\theta}) g(s,a,;θ) 中的 Q π ( s t , a t ) Q_π(s_t, a_t) Qπ(st,at) 替换成上面的期望,得到:
g ( s , a , ; θ ) = [ Q π ( s t , a t ) − V π ( s t ) ] ⋅ ∇ θ ln ⁡ π ( a t ∣ s t ; θ ) = [ E S t + 1 [ R t + γ ⋅ V π ( S t + 1 ) ] − V π ( S t ) ] ⋅ ∇ θ ln ⁡ π ( a t ∣ s t ; θ ) \begin{aligned} \boldsymbol{g}(s,a,;\boldsymbol{\theta}) &= [Q_\pi(s_t, a_t) - V_\pi(s_t)] \cdot \nabla_{\boldsymbol{\theta}}\ln \pi(a_t|s_t;\boldsymbol{\theta}) \\ &=\left[ \mathbb{E}_{S_{t+1}}[R_t + \gamma \cdot V_\pi(S_{t+1})] - V_\pi(S_t)\right]\cdot \nabla_{\boldsymbol{\theta}}\ln \pi(a_t|s_t;\boldsymbol{\theta}) \end{aligned} g(s,a,;θ)=[Qπ(st,at)Vπ(st)]θlnπ(atst;θ)=[ESt+1[Rt+γVπ(St+1)]Vπ(St)]θlnπ(atst;θ)
当智能体执行动作 a t a_t at 之后,环境给出新的状态 s t + 1 s_{t+1} st+1 和奖励 r t r_t rt;利用 s t + 1 s_{t+1} st+1 r t r_t rt 对上面的期望做蒙特卡洛近似,得到:
g ( s , a , ; θ ) ≈ [ r t + γ ⋅ V π ( s t + 1 ) − V π ( s t ) ] ⋅ ∇ ln ⁡ π ( a ∣ s ; θ ) \boldsymbol{g}(s,a,;\boldsymbol{\theta}) \approx [ r_t + \gamma \cdot V_\pi(s_{t+1}) - V_\pi(s_t)] \cdot \nabla \ln \pi(a|s;\theta) g(s,a,;θ)[rt+γVπ(st+1)Vπ(st)]lnπ(as;θ)
进一步把状态价值函数 V π ( s ) V_π(s) Vπ(s) 替换成价值网络 v ( s ; w ) v(s; \boldsymbol{w}) v(s;w),得到:
g ~ ( s , a , ; θ ) : = [ r t + γ ⋅ v ( s ; w ) ⏟ T D  目标  y ^ t − v ( s ; w ) ⋅ ∇ ln ⁡ π ( a ∣ s ; θ ) \boldsymbol{\tilde g}(s,a,;\boldsymbol{\theta}):=[ \underbrace{r_t + \gamma \cdot v(s; \boldsymbol{w})}_{TD\ 目标 \ \hat y_t} - v(s; \boldsymbol{w}) \cdot \nabla \ln \pi(a|s;\theta) g~(s,a,;θ):=[TD 目标 y^t rt+γv(s;w)v(s;w)lnπ(as;θ)
前面定义了 TD 目标和 TD 误差, y t ^ : = r t + γ ⋅ v ( s t + 1 ; w ) \hat{y_t} := r_t + \gamma \cdot v(s_{t+1}; \boldsymbol{w}) yt^:=rt+γv(st+1;w) δ t : = v ( s t ; w ) − y ^ t \delta_t := v(s_t;\boldsymbol{w}) - \hat y_t δt:=v(st;w)y^t。因此,可以将 g ( s , a , ; θ ) : = − δ t ⋅ ∇ ln ⁡ π ( a ∣ s ; θ ) \boldsymbol{g}(s,a,;\boldsymbol{\theta}) :=- \delta _t \cdot \nabla \ln \pi(a|s;\theta) g(s,a,;θ):=δtlnπ(as;θ)
g ~ \boldsymbol{\tilde{g}} g~ g \boldsymbol{{g}} g 的近似,所以也是策略梯度 ∇ θ J ( θ ) \nabla_{\boldsymbol{\theta}}J(\boldsymbol{\theta}) θJ(θ) 的近似。用 g ~ \boldsymbol{\tilde{g}} g~更新策略网络参数 θ \boldsymbol{\theta} θ
θ ← θ + β ⋅ g ( s , a , ; θ ) \boldsymbol{\theta} \leftarrow \boldsymbol{\theta} + \beta \cdot \boldsymbol{g}(s,a,;\boldsymbol{\theta}) θθ+βg(s,a,;θ)
这样可以让目标函数 J ( θ ) J(\boldsymbol{\theta}) J(θ) 变大。

训练流程

  下面概括 A2C 训练流程。设当前策略网络参数是 θ n o w \boldsymbol{\theta_{now}} θnow,价值网络参数是 w n o w \boldsymbol{w_{now}} wnow。执行下面的步骤,将参数更新成 θ n e w \boldsymbol{\theta_{new}} θnew w n e w \boldsymbol{w_{new}} wnew

  1. 观测到当前状态 s t s_t st,根据策略网络做决策: a t ∼ π ( ⋅ ∣ s t ; θ n o w ) a_t ∼ π(· | s_t; \boldsymbol{\theta_{now}}) atπ(st;θnow),并让智能体执行动作 a t a_t at
  2. 从环境中观测到奖励 r t r_t rt 和新的状态 s t + 1 s_{t+1} st+1
  3. 让价值网络打分: v ^ t = v ( s t ; w n o w ) \hat v_t = v(s_t;\boldsymbol{w_{now}}) v^t=v(st;wnow) v ^ t + 1 = v ( s s + 1 ; w n o w ) \hat v_{t+1} = v(s_{s + 1};\boldsymbol{w_{now}}) v^t+1=v(ss+1;wnow)
  4. 计算 TD 目标和 TD 误差: y t ^ = r t + γ ⋅ v ^ t + 1 \hat{y_t} = r_t + \gamma \cdot \hat v_{t+1} yt^=rt+γv^t+1 δ t = v ^ t − y ^ t \delta_t = \hat v_{t} - \hat y_t δt=v^ty^t
  5. 更新价值网络: w n e w ← w n o w − α ⋅ δ t ⋅ ∇ w v ( s t ; w n o w ) \boldsymbol{w_{new}} \leftarrow \boldsymbol{w_{now}} - \alpha \cdot \delta_t \cdot \nabla_{\boldsymbol{w}}v(s_t;\boldsymbol{w_{now}}) wnewwnowαδtwv(st;wnow)
  6. 更新策略网络: θ n e w ← θ n o w − β ⋅ δ t ⋅ ∇ θ ln ⁡ π ( a t ∣ s t ; θ n o w ) \boldsymbol{\theta_{new}} \leftarrow \boldsymbol{\theta_{now}}- \beta \cdot \delta_t \cdot \nabla_{\boldsymbol{\theta}}\ln \pi(a_t|s_t;\boldsymbol{\theta_{now}}) θnewθnowβδtθlnπ(atst;θnow).

用目标网络改进训练

  上述训练价值网络的算法存在自举——即用价值网络自己的估值 v ^ t + 1 \hat v_{t + 1} v^t+1 去更新价值网络自己。为了缓解自举造成的偏差,可以使用目标网络 (Target Network) 计算 TD 目标。把目标网络记作 v ( s ; w − ) v(s; \boldsymbol{w^{-}}) v(s;w),它的结构与价值网络的结构相同,但是参数不同。使用目标网络计算 TD 目标,那么 A2C 的训练就变成了:

  1. 观测到当前状态 s t s_t st,根据策略网络做决策: a t ∼ π ( ⋅ ∣ s t ; θ n o w ) a_t ∼ π(· | s_t; \boldsymbol{\theta_{now}}) atπ(st;θnow),并让智能体执行动作 a t a_t at
  2. 从环境中观测到奖励 r t r_t rt 和新的状态 s t + 1 s_{t+1} st+1
  3. 让价值网络给 s t s_t st 打分: v ^ t = v ( s t ; w n o w ) \hat v_t = v(s_t;\boldsymbol{w_{now}}) v^t=v(st;wnow)
  4. 让目标网络给 s t + 1 s_{t+1} st+1 打分: v ^ t + 1 = v ( s s + 1 ; w n o w − ) \hat v_{t+1} = v(s_{s + 1};\boldsymbol{w^-_{now}}) v^t+1=v(ss+1;wnow)
  5. 计算 TD 目标和 TD 误差: y t − ^ = r t + γ ⋅ v ^ t + 1 − \hat{y^-_t} = r_t + \gamma \cdot \hat v^-_{t+1} yt^=rt+γv^t+1 δ t = v ^ t − y ^ t − \delta_t = \hat v_{t} - \hat y^-_t δt=v^ty^t
  6. 更新价值网络: w n e w ← w n o w − α ⋅ δ t ⋅ ∇ w v ( s t ; w n o w ) \boldsymbol{w_{new}} \leftarrow \boldsymbol{w_{now}} - \alpha \cdot \delta_t \cdot \nabla_{\boldsymbol{w}}v(s_t;\boldsymbol{w_{now}}) wnewwnowαδtwv(st;wnow)
  7. 更新策略网络: θ n e w ← θ n o w − β ⋅ δ t ⋅ ∇ θ ln ⁡ π ( a t ∣ s t ; θ n o w ) \boldsymbol{\theta_{new}} \leftarrow \boldsymbol{\theta_{now}}- \beta \cdot \delta_t \cdot \nabla_{\boldsymbol{\theta}}\ln \pi(a_t|s_t;\boldsymbol{\theta_{now}}) θnewθnowβδtθlnπ(atst;θnow).
  8. τ ∈ ( 0 , 1 ) \tau \in (0,1) τ(0,1) 是需要手动调的参数,做加权平均更新目标网络的参数: w n e w − ← τ ⋅ w n e w + ( 1 − τ ) ⋅ w n o w − \boldsymbol{w^-_{new}} \leftarrow \tau \cdot \boldsymbol{w_{new}} + (1 - \tau )\cdot \boldsymbol{w^-_{now}} wnewτwnew+(1τ)wnow.

PyTorch 实现

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import gym
import numpy as np

LR_ACTOR = 0.01     # 策略网络的学习率
LR_CRITIC = 0.001   # 价值网络的学习率
GAMMA = 0.9         # 奖励的折扣因子
EPSILON = 0.9       # ϵ-greedy 策略的概率
TARGET_REPLACE_ITER = 100                 # 目标网络更新的频率
env = gym.make('CartPole-v0')             # 加载游戏环境
N_ACTIONS = env.action_space.n            # 动作数
N_SPACES = env.observation_space.shape[0] # 状态数量
env = env.unwrapped

# 网络参数初始化,采用均值为 0,方差为 0.1 的高斯分布
def init_weights(m) :
    if isinstance(m, nn.Linear) :
        nn.init.normal_(m.weight, mean = 0, std = 0.1)

# 策略网络
class Actor(nn.Module) :
    def __init__(self):
        super(Actor, self).__init__()
        self.net = nn.Sequential(
            nn.Linear(N_SPACES, 50),
            nn.ReLU(),
            nn.Linear(50, N_ACTIONS) # 输出为各个动作的概率,维度为 3
        )

    def forward(self, s):
        output = self.net(s)
        output = F.softmax(output, dim = -1) # 概率归一化
        return output

# 价值网络
class Critic(nn.Module) :
    def __init__(self):
        super(Critic, self).__init__()
        self.net = nn.Sequential(
            nn.Linear(N_SPACES, 20),
            nn.ReLU(),
            nn.Linear(20, 1) # 输出值是对当前状态的打分,维度为 1
        )

    def forward(self, s):
        output = self.net(s)
        return output

# A2C 的主体函数
class A2C :
    def __init__(self):
        # 初始化策略网络,价值网络和目标网络。价值网络和目标网络使用同一个网络
        self.actor_net, self.critic_net, self.target_net = Actor().apply(init_weights), Critic().apply(init_weights), Critic().apply(init_weights)
        self.learn_step_counter = 0 # 学习步数
        self.optimizer_actor = optim.Adam(self.actor_net.parameters(), lr = LR_ACTOR)    # 策略网络优化器
        self.optimizer_critic = optim.Adam(self.critic_net.parameters(), lr = LR_CRITIC) # 价值网络优化器
        self.criterion_critic = nn.MSELoss() # 价值网络损失函数

    def choose_action(self, s):
        s = torch.unsqueeze(torch.FloatTensor(s), dim = 0) # 增加维度
        if np.random.uniform() < EPSILON :                 # ϵ-greedy 策略对动作进行采取
            action_value = self.actor_net(s)
            action = torch.max(action_value, dim = 1)[1].item()
        else :
            action = np.random.randint(0, N_ACTIONS)

        return action

    def learn(self, s, a, r, s_):
        if self.learn_step_counter % TARGET_REPLACE_ITER == 0 :          # 更新目标网络
            self.target_net.load_state_dict(self.critic_net.state_dict())

        self.learn_step_counter += 1

        s = torch.FloatTensor(s)
        s_ = torch.FloatTensor(s_)

        q_actor = self.actor_net(s)               # 策略网络
        q_critic = self.critic_net(s)             # 价值对当前状态进行打分
        q_next = self.target_net(s_).detach()     # 目标网络对下一个状态进行打分
        q_target = r + GAMMA * q_next             # 更新 TD 目标
        td_error = (q_critic - q_target).detach() # TD 误差

        # 更新价值网络
        loss_critic = self.criterion_critic(q_critic, q_target)
        self.optimizer_critic.zero_grad()
        loss_critic.backward()
        self.optimizer_critic.step()

        # 更新策略网络
        log_q_actor = torch.log(q_actor)
        actor_loss = log_q_actor[a] * td_error
        self.optimizer_actor.zero_grad()
        actor_loss.backward()
        self.optimizer_actor.step()

a2c = A2C()

for epoch in range(10000) :
    s = env.reset()
    ep_r = 0
    while True :
        env.render()
        a = a2c.choose_action(s)       # 选择动作

        s_, r, done, info = env.step(a)# 执行动作

        x, x_dot, theta, theta_dot = s_
        # 修改奖励,为了更快收敛
        r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
        r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
        r = r1 + r2

        ep_r += r

        # 学习
        a2c.learn(s, a, r, s_)

        if done :
            break

        s = s_
    print(f'Ep: {epoch} | Ep_r: {round(ep_r, 2)}')

你可能感兴趣的