强化学习Actor-Critic 算法

第 10 章 Actor-Critic 算法

10.1简介

本书之前的章节讲解了基于值函数的方法(DQN)和基于策略的方法(REINFORCE),其中基于值函数的学习方法只学习一个价值函数,而基于策略的方法只学习一个策略函数。那么,一个很自然的问题是,有没有什么方法既学习价值函数,又学习策略函数呢?答案是Actor-Critic是囊括一系列算法的整体架构,目前很多高效的前沿算法都属于Actor-Critic算法,本章接下来将会介绍一种最简单的Actor-Critic算法。需要明确的是,Actor-Critic算法本质上是基于策略的算法,因为这一系列算法的目标都是优化一个带参数的策略,只是会额外学习价值函数,从而帮助策略函数更好地学习。

10.2 Actor-Critic

回顾一下,再REINFORCE算法中,目标函数的梯度中有一项轨迹回报,用于指导策略的更新。REINFORCE算法用蒙特卡洛方法来估计 Q ( s , a ) Q(s,a) Q(s,a),能不能考虑拟合一个值函数来指导策略进行学习呢?这是Actor-Critic算法所做的。在策略梯度中,可以把梯度写成下面这个更加一般的形式:

g = E [ Σ t T ψ t ∇ θ log ⁡ π θ ( a t ∣ s t ) ] g=\mathbb{E}[\varSigma _{t}^{T}\psi _t\nabla _{\theta}\log \pi _{\theta}(a_t|s_t)] g=E[ΣtTψtθlogπθ(atst)]

其中, ψ t \psi _t ψt可以有很多种形式:

  1. Σ T t ′ = 0 γ t ′ r t ′ \underset{t'=0}{\overset{T}{\varSigma }} \gamma ^{t'}r_{t'} t=0ΣTγtrt:轨迹的总回报
  2. Σ T t ′ = t γ t ′ − t r t ′ \underset{t'=t}{\overset{T}{\varSigma }} \gamma ^{t'-t}r_{t'} t=tΣTγttrt:动作 a t a_t at之后的回报
  3. Σ T t ′ = t γ t ′ − t r t ′ − b ( s t ) \underset{t'=t}{\overset{T}{\varSigma }} \gamma ^{t'-t}r_{t'}-b(s_t) t=tΣTγttrtb(st):基准线版本的改进
  4. Q π θ ( s t , a t ) Q^{\pi_\theta}(s_t,a_t) Qπθ(st,at):动作价值函数
  5. A π θ ( s t , a t ) A^{\pi_\theta}(s_t,a_t) Aπθ(st,at):优势函数
  6. r t + γ V π θ ( s t + 1 ) − V π θ ( s t ) r_t+\gamma V^{\pi_\theta}(s_{t+1})-V^{\pi_\theta}(s_t) rt+γVπθ(st+1)Vπθ(st):时序差分残差

9.5节提到REINFORCE通过蒙特卡洛采样的方法对策略梯度的估计是无偏的,但是方差非常大。我们可以用形式(3)引入基线函数(baseline function) b ( s t ) b(s_t) b(st)来减小方差。此外,我们也可以采用Actor-Critic算法估计一个动作价值函数Q,代替蒙特卡洛采样得到的回报,这便是形式(4)。这个时候,我们可以把状态价值函数V作为基线,从Q函数减去这个V函数则得到了A函数,我们称之为优势函数(advantage function),这便是形式(5)。更进一步,我们可以利用 Q = r + γ V Q=r+\gamma V Q=r+γV等式得到形式(6)。

本章着重介绍形式(6),即通过时序差分残差 ψ t = r t + γ V π θ ( s t + 1 ) − V π θ ( s t ) \psi _t=r_t+\gamma V^{\pi_\theta}(s_{t+1})-V^{\pi_\theta}(s_t) ψt=rt+γVπθ(st+1)Vπθ(st)来指导策略梯度进行学习。事实上,用Q值或者V值本质上也使用奖励来进行指导,但是用神经网络进行估计的方法可以减少方差、提高鲁棒性。除此之外,REINFORCE算法基于蒙特卡洛采样,只能在序列结束后进行更新,这同时也要求任务具有限的步数,而Actor-Critic算法则可以在每一步之后都进行更新,并且不对任务的步数做限制。

我们将Actor-Critic分为两个部分:Actor(策略网络)和Critic(价值网络),如图10-1所示。

  • Actor要做的是与环境交互,并在Critic价值函数的指导下用策略梯度学习一个更好的策略。
  • Critic要做的是通过Actor与环境交互收集的数据学习一个价值函数,这个价值函数会用于判断在当前状态什么动作是好的,什么动作是不好的,进而帮助Actor进行策略更新

(Critic更像是一个裁判,Actor更像是一个运动员,这个算法学习的好需要裁判和运动员的水平都很高)

在这里插入图片描述

Actor的更新采用的策略梯度的原则,那Critic是怎么更新的?

将Critic价值网络表示为 V ω V_\omega Vω,参数是 ω \omega ω。于是,可以采用时序差分残差的学习方式,对于单个数据定义如下价值函数的损失函数:

L ( ω ) = 1 2 ( r + γ V ω ( s t + 1 ) − V ω ( s t ) ) 2 \mathbb{L}\left( \omega \right) =\frac{1}{2}(r+\gamma V_{\omega}(s_{t+1})-V_\omega(s_t))^2 L(ω)=21(r+γVω(st+1)Vω(st))2

与DQN中的一样,我们采取类似于目标网络的方法,将上式中 r + γ V ω ( s t + 1 ) r+\gamma V_{\omega}(s_{t+1}) r+γVω(st+1)做为时序差分的目标,不会产生梯度来更新价值函数。因此,价值函数的梯度为:

∇ ω L ( ω ) = − ( r + γ V ω ( s t + 1 ) − V ω ( s t ) ) ∇ ω V ω ( s t ) \nabla _{\omega}\mathbb{L}\left( \omega \right) =-\left( r+\gamma V_{\omega}(s_{t+1})-V_\omega(s_t) \right) \nabla _{\omega}V_{\omega}\left( s_t \right) ωL(ω)=(r+γVω(st+1)Vω(st))ωVω(st)

然后使用梯度下降方法来更新Critic价值网络参数即可。

Actor-Critic算法的具体流程如下:

  • 初始化策略网络参数 θ \theta θ,价值网络参数 ω \omega ω
  • for序列 e = 1 − − > E e=1-->E e=1>E do
  • ​ 用当前策略 π θ \pi_\theta πθ采样轨迹 { s 1 , a 1 , r 1 , s 2 , a 2 , r 2 , . . . } \left\{s_1,a_1,r_1,s_2,a_2,r_2,...\right\} {s1,a1,r1,s2,a2,r2,...}
  • ​ 为每一步数据计算: δ t = r + γ V ω ( s t + 1 ) − V ω ( s t ) \delta _t=r+\gamma V_{\omega}(s_{t+1})-V_\omega(s_t) δt=r+γVω(st+1)Vω(st)
  • ​ 更新价值参数 ω = ω + α ω Σ t δ t ∇ ω V ω ( s t ) \omega=\omega+\alpha_\omega\varSigma _t\delta _t\nabla _{\omega}V_{\omega}\left( s_t \right) ω=ω+αωΣtδtωVω(st)
  • ​ 更新策略参数 θ = θ + α θ Σ t δ t ∇ ω l o g π θ ( s t ∣ s t ) \theta=\theta+\alpha_\theta\varSigma _t\delta _t\nabla _{\omega}log\pi_\theta(s_t|s_t) θ=θ+αθΣtδtωlogπθ(stst)
  • end for

以上就是Actor-Critic算法的全部流程

10.3 代码实践

import gym
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
import rl_utils

# 策略网络 输入是某个状态  输出是该状态下的动作概率分布
class PolicyNet(torch.nn.Module):
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(PolicyNet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, action_dim)

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


# 价值网络 输入是某个状态 输出是状态的价值
class ValueNet(torch.nn.Module):
    def __init__(self, state_dim, hidden_dim):
        super(ValueNet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, 1)

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


# 定义ActorCritic算法 主要包含采取动作 和更新网络参数两个函数
class ActorCritic:
    def __init__(self, state_dim, hidden_dim, action_dim, actor_lr, critic_lr, gamma, device):
        # 策略网络
        self.actor = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
        self.critic = ValueNet(state_dim, hidden_dim).to(device)  # 价值网络
        # 策略网络优化器
        self.actor_optimizer = torch.optim.Adam(self.actor.parameters(), lr=actor_lr)
        self.critic_optimizer = torch.optim.Adam(self.critic.parameters(), lr=critic_lr)  # 价值网络优化器
        self.gamma = gamma
        self.device = device

    def take_action(self, state):
        state = torch.tensor([state], dtype=torch.float).to(self.device)
        probs = self.actor(state)  # 根据策略网络 得到的动作概率分布
        # print(probs)
        action_dist = torch.distributions.Categorical(probs)
        # print(action_dist)
        action = action_dist.sample()  # 采样动作的索引值
        # print(action)
        return action.item()  # item是为了让结果更加精准   但是因为action中的内容是0或者1所以  没有item结果是一样的
    # tensor([[0.5170, 0.4830]], device='cuda:0', grad_fn=<SoftmaxBackward0>)
    # Categorical(probs: torch.Size([1, 2]))
    # tensor([0], device='cuda:0')
    # 该值是这个函数的返回值0

    def update(self, transition_dict):
        states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
        rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
        next_states = torch.tensor(transition_dict['next_states'], dtype=torch.float).to(self.device)
        dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)

        # 时序差分目标
        td_target = rewards + self.gamma * self.critic(next_states) * (1 - dones)
        td_delta = td_target - self.critic(states)  # 时序差分误差
        log_probs = torch.log(self.actor(states).gather(1, actions))
        actor_loss = torch.mean(-log_probs * td_delta.detach())
        # 均方误差损失函数
        critic_loss = torch.mean(F.mse_loss(self.critic(states), td_target.detach()))  # 求均值
        self.actor_optimizer.zero_grad()
        self.critic_optimizer.zero_grad()
        actor_loss.backward()  # 计算策略网络的梯度
        critic_loss.backward()  # 计算价值网络的梯度
        self.actor_optimizer.step()  # 更新策略网络的参数
        self.critic_optimizer.step()  # 更新价值网络的参数

actor_lr = 1e-3
critic_lr = 1e-2
num_episodes = 1000
hidden_dim = 128
gamma = 0.98
device = torch.device("cuda") if torch.cuda.is_available() else torch.device(
    "cpu")

env_name = 'CartPole-v0'
env = gym.make(env_name)
env.seed(0)
torch.manual_seed(0)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = ActorCritic(state_dim, hidden_dim, action_dim, actor_lr, critic_lr, gamma, device)

return_list = rl_utils.train_on_policy_agent(env, agent, num_episodes)


episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Actor-Critic on {}'.format(env_name))
plt.show()

mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('Actor-Critic on {}'.format(env_name))
plt.show()

Iteration 0:   0%|          | 0/100 [00:00<?, ?it/s]/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:15: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at  ../torch/csrc/utils/tensor_new.cpp:201.)
  from ipykernel import kernelapp as app
Iteration 0: 100%|██████████| 100/100 [00:00<00:00, 101.75it/s, episode=100, return=21.100]
Iteration 1: 100%|██████████| 100/100 [00:01<00:00, 58.71it/s, episode=200, return=72.800]
Iteration 2: 100%|██████████| 100/100 [00:05<00:00, 19.73it/s, episode=300, return=109.300]
Iteration 3: 100%|██████████| 100/100 [00:05<00:00, 17.30it/s, episode=400, return=163.000]
Iteration 4: 100%|██████████| 100/100 [00:06<00:00, 16.27it/s, episode=500, return=193.600]
Iteration 5: 100%|██████████| 100/100 [00:06<00:00, 15.90it/s, episode=600, return=195.900]
Iteration 6: 100%|██████████| 100/100 [00:06<00:00, 15.80it/s, episode=700, return=199.100]
Iteration 7: 100%|██████████| 100/100 [00:06<00:00, 15.72it/s, episode=800, return=186.900]
Iteration 8: 100%|██████████| 100/100 [00:06<00:00, 15.94it/s, episode=900, return=200.000]
Iteration 9: 100%|██████████| 100/100 [00:06<00:00, 15.45it/s, episode=1000, return=200.000]

在这里插入图片描述

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值