• 强化学习——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),相当于评委,他的评分可以帮助策略网络(演员)改进技术。
    在这里插入图片描述

    算法推导

      训练价值网络: 训练价值网络 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 ; θ )

    g(s,a,;θ)=[Qπ(st,at)Vπ(st)]θlnπ(at|st;θ)=[ESt+1[Rt+γVπ(St+1)]Vπ(St)]θlnπ(at|st;θ)" role="presentation" style="position: relative;">g(s,a,;θ)=[Qπ(st,at)Vπ(st)]θlnπ(at|st;θ)=[ESt+1[Rt+γVπ(St+1)]Vπ(St)]θlnπ(at|st;θ)
    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)}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
  • 相关阅读:
    Python复习笔记5——常用模块
    Java线程同步的四种方式详解(建议收藏)
    Jupyter部署和使用教程
    将关系模式R分解2NF后续(8.1)
    Google IO 2023推出Android Studio官方AI工具Studio Bot
    The Sandbox Alpha 第三季排行榜公布
    HarmonyOS应用开发:鸿蒙自定义组件slot插槽,体现的更强大!
    二叉树——遍历:按层次非递归遍历、先序非递归、先中后序递归遍历二叉树的链表结构【C语言,数据结构】(内含源代码)
    投资理财知识分享:100个金融知识专业术语
    【sql】sql知识汇总
  • 原文地址:https://blog.csdn.net/weixin_53598445/article/details/125995336