回顾:基于值函数的方法主要是学习价值函数,然后根据价值函数导出一个策略,学习过程中并不存在一个显示的策略,有动态规划算法(DP)、蒙特卡洛方法(MC)、时序差分算法(TD)(SARSA和Q-learning)、DQN及DQN的改进算法等。基于策略函数的方法是直接显式地学习一个目标策略,有策略梯度算法(REINFORCE)。基于值函数和策略函数的方法是价值函数和策略函数均学习,学习到最优策略和最优价值函数,有AC(Actor-Critic)算法、信任区域策略优化算法(TRPO)、PPO算法、深度确定性策略梯度算法(DDPG)以及SAC(Soft Actor-Critic)算法等。也可以把基于值函数和策略函数的方法归到基于策略函数的方法,这篇只讲基于策略函数的强化学习算法。
Value-based:学习价值函数,暗含策略。确定性策略直接从价值函数贪婪的生成:。
Policy-based:没有价值函数,直接策略学习。将策略函数参数化为,其中
是可学习的策略参数,
的输出是动作集合的概率分布。
AC:既学习价值函数也学习策略函数。(归到Policy-based)
策略学习的优点:
a、更好的收敛特性:保证收敛于局部最优(最坏情况)或全局最优(最好情况)
b、策略梯度在高维动作空间更有效
c、策略梯度可以学习随机策略,而价值函数则不能(只能导出确定性策略)
策略学习的缺点:
a、通常会收敛到局部最优
b、评估策略具有高方差
策略学习相对于值函数中的
选取动作有优势:
策略函数选择动作的概率作为被优化的参数的函数会平滑的变化;
值函数中的
选取动作,只要估计的动作价值函数的变化导致了最大动作价值对应的动作发生了变化,则选择某个动作的概率就可能会突然变化很大,即使估计的动作价值函数只发生了任意小的变化。
因此,基于策略的学习比基于动作价值函数的学习有更强的收敛性保证。
目录
1、策略
确定性策略(Deterministic policy):在每个状态只输出一个确定性的动作,即只有该动作的概率为 1,其他动作的概率为 0。连续动作空间可以使用DDPG(策略网络直接输出连续动作的值)。
策略网络输出随机新策略如下:
随机性策略(Stochastic policy):在每个状态输出的是关于动作的概率分布(伯努利分布:如0.4概率往左0.6概率往右、高斯分布:一般用于连续动作空间等),然后根据该分布进行采样就可以得到一个动作。
策略网络输出随机新策略如下:一个是离散动作的网络,一个是连续动作的网络
2、策略梯度
就像基于值函数的方法中的DQN算法用函数(神经网络)来表达值函数,基于策略的方法就是策略参数化,即用函数
来近似。函数近似我们只关注那些可微的算法,即特征的线性组合、神经网络(一般用神经网络)。策略函数
要做的就是输入某个状态
,然后输出一个关于动作的概率分布,训练模型以能够找到最优策略及最优价值函数。
基于策略的方法可以不需要值函数,直接优化策略函数,学出随机性策略。
策略梯度算法是基于策略函数方法的基础。
策略网络模型:
定义目标函数(代价函数):
其中,是初始状态,
代表了从初始状态
开始遵循策略
的期望回报。
求出目标函数对参数
的导数(梯度)
,然后用梯度上升方法最大化目标函数
,得出最优策略。
梯度下降:若要求出目标函数
的最小值,即
。那么就算出目标函数
对参数
的梯度
,然后用
更新参数
梯度上升:若要求出目标函数
的最大值,即
。那么就算出目标函数
对参数
的梯度
,然后用
更新参数
(方式一)
(方式二)
通过参数θ更新来移动分布p(一个策略对应一个分布p),使其未来的策略采集的数据获得较高分数
或
。
符合这个框架的方法都成为策略梯度法。
2.1、策略梯度算法推导一
2.1.1、状态访问分布:
不同策略得到的价值函数不一样,因为不同策略访问到的状态的概率分布不同。
定义MDP的初始状态分布,
表示采取策略
使得智能体在t时刻状态为
的概率,有
,定义一个策略
的状态访问分布(state visitation distribution):
(有限序列就不用加到
了)
假如有三个状态,在每一时刻三个状态的概率和为1,即
。
状态访问分布
代表了在每一时间步状态为
的概率之和,每一时间步的概率前都有一个权重
(权重随时间增加而减小)。
是为了使得所有状态的状态概率分布之和为1,即:
(
)
状态访问分布的性质:
状态的状态访问分布就是
初始状态为
的状态访问分布+
上一时刻任意状态分布跳转到下一时刻状态
。因为上一时刻任意状态分布包含了权重
,所以后一项就不用乘
了。
占用度量:
策略的占用度量(occupancy measure):
占用度量表示动作状态对
被访问到的概率。
定理1:智能体分别以策略
和
和同一个MDP交互得到的占用度量
和
满足:
即策略和占用度量一一对应
定理2:给定一合法占用度量(指的是存在“一个策略使智能体与MDP交互产生的状态动作对被访问到的概率”),可生成该占用度量的唯一策略是:
这个公式是下面推出的:
2.1.2、算法推导:
代价函数:
的求解公式:
的推导过程(要用到状态访问分布和
):
1、首先得到
,状态价值函数
的对参数
求导:
令
,令
为从状态
出发遵循策略
后k步到达状态x的概率(如
),那么:
找到了
与下一状态
的关系,继续:
其实,从这就能看出来梯度
就只和一条序列的每一步的
有关(那些概率就不管了,因为最终是跟环境互动,这是unknown MDP问题的一般做法),
代表只要采样n条序列,然后计算这n条序列的每一条序列的
求和再除以n就能算出梯度。
2、然后,推导
:
令
,代表了从状态
遵循策略
走任意步长到状态
的期望概率。
(
)
tip:
是加了基准线的梯度公式(
可以是任意函数,甚至是一个随机不随动作a变化)。
最后推导出
常用的能够代入数据的公式:
![]()
其中,
代表了状态的概率分布,即每一步是状态s的概率,
代表动作状态对
被访问到的概率。
隐含了从初始状态
到序列终止条件。
,代表了从状态
遵循策略
走任意步长,每一步的
相加的期望。
tips:这里的
是从初始状态
下遵循策略
后每一步
的信息。
---很显然对于一个序列的每个状态采取策略函数输出的动作,当
很大时,策略函数会将这个测流函数采取的动作的概率增加,以达到
。因此,影响梯度值大小的主要是Q值,Q值大的话梯度就大,因此,更新梯度主要是让策略更多的去采样到能够得到较大Q值的动作。
观察梯度公式,是对策略采集的数据求期望,即策略梯度算法为在线策略(on-policy)算法。因此,在计算梯度的时候要用到当前策略
采样得到的数据来计算梯度。
2.3、策略梯度算法推导二
代价函数:
其中,
,
是一个序列的累计奖励,
。
求期望公式:
的求解公式:
的推导过程:
(用到了
)
其中,
(状态转移概率与策略无关,即与
无关)
则
---很显然对于一个序列(每个状态采取策略函数输出的一个动作),当
很大时,策略函数会将这个序列采取的动作的概率增加,以达到
。
梯度公式直觉的:
一、
如果在
序列中,看到状态
后采取动作
,最终得到的
是正的,那么更新参数
就会增加
的概率;
如果在
序列中,看到状态
后采取动作
,最终得到的
是负的,那么更新参数
就会减小
的概率。
如果将
改为
(即在状态
采取动作
后的即时奖励),会一直增加会得到即时奖励的动作的概率,减小“虽然不会得到即时奖励后边能得到更高的总回报的”动作,最终导致一直采取会得到即时奖励的动作。
二、为什么要取log?
取log是让
除以
假设让agent与环境交互多次,某一个状态
在
、
、
、
、
都被看到了
状态
采取动作a、
状态
采取动作b、
状态
采取动作b、
状态
采取动作b、
状态
采取动作b
、
、
、
、
那么,这样更新的话会让策略偏向于在状态
采取出现次数多的动作b(因为更新次数多),但是采取b比采取a得到的总回报小,这样会导致增加动作b的概率。
因此,使用Normalization,
使得出现频率高的的动作除以的
大,出现频率小的的动作除以的
小,这样更新就不会让策略偏向于出现几率大动作。
Tip1、添加基准线
一般回报
是正的。
①在理想情况下,不会有问题:
在某一状态
下有三个动作,采取三个动作得到的总回报都是正的,但是
大小不一样,假如动作a和动作c得到的总回报比动作b的大,那么更新的话动作a和动作c的概率会增大,动作b的概率会减小,保证概率之和为1(离散动作采用softmax,连续动作采样高斯分布)。虽然采取每个动作都会得到正数的梯度,但是最终是梯度比较小的概率减小,梯度比较大的概率增大。
②在实际操作时,有问题:
在某一状态
实际采样时,只采集到动作b和动作c,没有出现动作a,这样更新会导致动作b和动作c的概率增加,动作a的概率减小,保证概率之和为1。
因此,希望
有正有负,做法就是在
基础上减去一个基准线b,即
。b一般是
(b可以是任意函数,甚至是一个随机不随动作a变化),训练时不断地把
的值记录下来,不断计算平均值作为b。
这代表只要
超过b,才将这个
中状态
出现的动作的概率增加;只要
没有超过b,才将这个
中状态
出现的动作的概率减小。就保证了不会导致没有被sample到就减小概率得情况。
梯度公式就是:
令
,
(
)
b一般是
,可以尽可能的减小方差。
因此,添加基准线不影响期望,方差减小。b可以用函数或者神经网络拟合(用
)。
Tip2、Assign Suitable Credit(分配合适的分)
代表了对于同一个序列的状态动作对都要乘上
,这样是不公平的,如果一个序列的总回报很高不代表所有的动作就是好的,一个序列的总回报不高不代表所有的动作就是不好的。
①理想情况下,采样够多的话不是问题,因为对同样一个状态动作对在不同序列得到的
不同,最终会抵消不影响效果。
②实际上采样不够多时,就要给每一个状态动作对分配合理的分数贡献。
因此,对于一个序列,给每一个状态动作对都乘上不同的权重,即这个状态动作对之后的累计奖励(不算这个状态动作对之前的奖励)
,而不是相同的
,这样能真正的反映每个动作是不是好的。
梯度公式就是:
这个梯度公式其实就是策略梯度算法推导一的梯度公式。
“这个公式还可以有这样来得到:
任意时间步的奖励的期望:
(某一时刻的奖励要对之前每一时刻的策略求梯度)
奖励总期望:
”
其实这就是REINFORCE算法。
,一个好的
采用价值函数
,因此
实际上是优势函数
(用神经网络表示是
,V是Q的期望,因此A有正有负),
可以用critic来表示(就是Dueling DQN中的Q-V)。这是后面的AC算法。
tip:这里是
,可以证明每一步的
的方差都是和
(即相关性)有关,相关性越高,方差越小,因此
采用
能尽可能的减小方差,而期望不变。
若只有策略函数,没有价值函数(就是单纯的策略学习),那么
只能通过采集数据根据公式
来计算,这里涉及一个重要性采样的问题(见强化学习2):
如果是同策略
采集的数据来更新,就用公式
或者
,这里
,角标
表示是策略
的数据,
。
如果是异策略
采集的数据,但是更新的是参数
,就要用到重要性采样,
或者
,这里
,角标
表示是策略
的数据,
。
tip:这里近似认为
采集的数据计算的
与
采集数据计算的
一样。
异策略下的目标函数可以写为
或者
。与环境交互的是策略
,更新的是策略
的参数
。
采集数据后计算
,再乘以
。计算梯度的话再乘上
。
这两种推导方式其实是从不同的角度入手的,方式一是每步的入手,方拾二是从整体的
入手。本质上是一样的,只是更新方式不同,其实方式二的Tip2就是方式一。
更新是无偏但是方差大(因为采用了蒙特卡洛方法),减小方差的方法:
a、虽然
方式也采用蒙特卡洛更新Q值,但是相对于来说方差减小了。
b、添加基准线,即方式二的Tip1可以减小方差。
2.4、REINFORCE
计算梯度要用到的值,估算
有很多方式,REINFORCE算法采用了蒙特卡洛方法估算Q值,即:
梯度:
REINFORCE算法流程:
初始化策略参数
(即初始化策略网络
)
![]()
![]()
![]()
:
![]()
![]()
![]()
:(一个序列)
用策略网络
与环境互动采集轨迹:
计算当前序列每个时刻
的回报
对策略网络
更新参数:
![]()
REINFORCE相关代码:
import gym
import torch
import torch.nn.functional as F
import numpy as np
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 REINFORCE:
def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,
device):
self.policy_net = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
self.optimizer = torch.optim.Adam(self.policy_net.parameters(), lr=learning_rate) # 使用Adam优化器
self.gamma = gamma # 折扣因子
self.device = device
def take_action(self, state): # 根据动作概率分布随机采样
state = torch.tensor([state], dtype=torch.float).to(self.device)
probs = self.policy_net(state)
action_dist = torch.distributions.Categorical(probs)
action = action_dist.sample()
return action.item()
def update(self, transition_dict):
reward_list = transition_dict['rewards']
state_list = transition_dict['states']
action_list = transition_dict['actions']
G = 0
self.optimizer.zero_grad()
for i in reversed(range(len(reward_list))): # 从最后一步算起
reward = reward_list[i]
state = torch.tensor([state_list[i]], dtype=torch.float).to(self.device)
action = torch.tensor([action_list[i]]).view(-1, 1).to(self.device)
log_prob = torch.log(self.policy_net(state).gather(1, action))
G = self.gamma * G + reward
loss = -log_prob * G # 每一步的损失函数
loss.backward() # 反向传播计算梯度
self.optimizer.step() # 累计梯度实施梯度下降
learning_rate = 1e-3
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 = REINFORCE(state_dim, hidden_dim, action_dim, learning_rate, gamma, device)
for i in range(episodes):
transition_dict = {'states': [],'actions': [],'next_states': [],'rewards': [],'dones': []}
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
transition_dict['states'].append(state)
transition_dict['actions'].append(action)
transition_dict['next_states'].append(next_state)
transition_dict['dones'].append(done)
state = next_state
agent.update(transition_dict)
定义策略网络:输入状态
,输出该状态下的动作概率分布,用softmax()来实现分布。
采样:通过策略网络的输出(动作概率分布)对离散的动作进行采样。
因为梯度为,则损失函数可以写为
,因此每个序列的损失就是
,每个序列的每一步的损失就是
。而且,因为是梯度上升,在用loss.backward()计算梯度时对loss加个负号,再用torch.optim.Adam()梯度下降更新,这样就是梯度上升的运用了。
可以采用gym的小游戏‘CartPole-v0’环境进行实验操作。
总结:
对比DQN算法,REINFORCE是一个在线策略算法,必须依赖在线采集的数据进行更新,所以要采集很多序列。
REINFORCE算法就是智能体根据当前策略直接和环境交互,通过采样得到的轨迹数据直接计算出策略参数的梯度,进而更新当前策略,使其向最大化策略期望回报的目标靠近。
REINFORCE算法优化的目标(即策略期望回报)正是最终所使用策略的性能,这比基于价值的强化学习算法的优化目标(一般是时序差分误差的最小化)要更加直接。
REINFORCE算法理论上是能保证局部最优的,借助蒙特卡洛方法采样轨迹来估计动作价值,这样是可以得到无偏的梯度。但是,梯度估计的方差很大,可能会造成一定程度上的不稳定。
2.5、带有基准线的REINFORCE
初始化策略参数
(即初始化策略网络
)与状态价值函数
![]()
![]()
![]()
:
![]()
![]()
![]()
:(一个序列)
用策略网络
与环境互动采集轨迹:
计算当前序列每个时刻
的回报
对状态价值网络
更新参数:
或
对策略网络
更新参数:
![]()
3、Actor-Critic
在基于策略学习的基础上加上价值函数的学习,即既学习策略函数(参数
),又价值函数学习
(参数
),这就是Actor-Critic算法,本质上还是基于策略的方法。
带基准线的强化学习算法既学习了策略函数又学习了状态价值函数,但是也不属于一个AC算法,因为带基准线的强化学习算法的状态价值函数仅被用作基准线,而不是一个评判器Critic。实际上是没有被用于自举操作(用后续各个状态的价值估计值来更新当前某个状态的价值估计值),而只是作为正被更新的状态价值的基线。
自举法引入了偏差是很有用的,降低了方差加快了学习。
带基准线的强化学习算法使用的是蒙特卡洛估计加一个基准线,是无偏差的,并且会渐近的收敛至局部最小值,但是学习缓慢产生高方差估计。
使用多步的方法(多步时序差分)可以灵活地选择自举操作的程度。
如策略梯度公式,
是由一个神经网络来表示,而
可以有很多种形式:
a、REINFOCE使用蒙特卡洛方法
估计每一时刻的Q值,对策略梯度的估计是无偏的,但是有很大的方差。
b、用一个轨迹的总回报
来代替轨迹的每一时刻的
。
c、蒙特卡洛方法估计每一时刻的Q值基础上加一个基准线,即
,可以减小方差。
d、用神经网络
计算每一时刻的
。---AC
e、用优势函数
代替
,即在
基础上减去基准
。---两个网络的A2C
f、因为
,可以用时序差分(TD)
来代替
这一项。V是Q的期望,因此A有正有负。---一个网络的A2C算法
a、b、c都是基于蒙特卡洛累积奖励来计算的,c是带有基准线的REINFORCE,d、e、f用神经网络进行估计Q或V也是通过奖励R来更新的,但是神经网络相当于TD知道了下一时刻的估计,因此可以减小方差、提高鲁棒性,牺牲了偏差。
e的方法可以采用Dueling DQN中的网络架构,也可以采用两个网络(Q和V),但是有风险。因此一般采用f的方法
,只用一个网络V。
f方法的价值函数网络采用
就可以了。e、f在神经网络的基础上加上了基准线,可以减小方差。
REINFORCE对比Actor-Critic(更新策略函数方式):
REINFORCE算法基于蒙特卡洛采样,只能在序列结束后进行计算更新(因为要计算累计奖励估计Q值),这同时也要求任务具有有限的步数;
Actor-Critic算法更新策略函数则可以在每一步之后都进行更新,并且不对任务的步数做限制。(类似于TD)虽然公式是
,对每一时刻的梯度求和再更新参数,但是也可以对每一时刻计算梯度直接更新参数再累计参数的更新。
3.1、AC
AC模型:与
AC算法的Actor采用求策略梯度然后梯度上升进行更新参数:
损失函数
每个序列的损失
每个序列的梯度
更新
AC算法的Critic采用求时序差分算法进行更新参数:
损失函数
每个序列的损失
每个序列的梯度
更新
策略网络
(Actor)与环境互动采集数据,然后根据价值网络
算出的值计算策略网络梯度的权重然后更新策略网络
的参数
。这样更新能够让策略网络的策略
学习到价值更高的动作。
价值网络
(Critic)通过策略网络
(Actor)与环境互动采集的数据学习价值函数
。
因此,价值网络
会用于判断状态
下哪个动作是好的,哪个是不好的,帮助策略网络进行更新最优策略。
AC算法流程:
初始化策略参数
、价值函数参数
(即初始化策略网络
和价值函数训练网络
),
用相同的网络参数
,初始化目标网络
![]()
![]()
![]()
:
![]()
![]()
![]()
:(一个序列)
用策略网络
与环境互动采集轨迹:
计算当前序列每个时刻
的
(用来更新
)与
(用来更新
)
对策略网络
更新参数:
(用torch.optim.Adam()梯度下降更新时)
对价值网络
更新参数:
![]()
:
更新目标网络
的参数
![]()
3.2、A2C(Advantage AC)
A2C就是采用优势函数的AC模型,即
一个网络的A2C模型:
A2C算法的Actor采用求策略梯度然后梯度上升进行更新参数:
损失函数
每个序列的损失
每个序列的梯度
更新
A2C算法的Critic采用求时序差分算法进行更新参数:
损失函数
每个序列的损失
每个序列的梯度
更新
策略网络
(Actor)与环境互动采集数据,然后根据价值网络
算出的值计算策略网络梯度的权重然后更新策略网络
的参数
。这样更新能够让策略网络的策略
学习到价值更高的动作。
价值网络
(Critic)通过策略网络
(Actor)与环境互动采集的数据学习价值函数
。
因此,价值网络
会用于判断状态
下哪个动作是好的,哪个是不好的,帮助策略网络进行更新最优策略。
A2C算法流程:
初始化策略参数
、价值函数参数
(即初始化策略网络
和价值函数网络
)
用相同的网络参数
,初始化目标网络
![]()
![]()
![]()
:
![]()
![]()
![]()
:(一个序列)
用策略网络
与环境互动采集轨迹:
计算当前序列每个时刻
的
,即
(用来更新
)
计算当前序列每个时刻
的
(用来更新
)
对策略网络
更新参数:
(用torch.optim.Adam()梯度下降更新时)
对训练价值网络
更新参数:
![]()
:
更新目标网络
的参数
![]()
A2C相关代码:
import gym
import torch
import torch.nn.functional as F
import numpy as np
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)
class A2C:
def __init__(self, state_dim, hidden_dim, action_dim, actor_lr, critic_lr, gamma, target_update, device):
self.actor = PolicyNet(state_dim, hidden_dim, action_dim).to(device) # 策略网络
self.critic = ValueNet(state_dim, hidden_dim).to(device) # 训练价值网络
self.target_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.target_update = target_update # 目标网络更新频率
self.count = 0 # 计数器,记录更新次数
self.device = device
def take_action(self, state):
state = torch.tensor([state], dtype=torch.float).to(self.device)
probs = self.actor(state)
action_dist = torch.distributions.Categorical(probs)
action = action_dist.sample()
return action.item()
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_target2 = rewards + self.gamma * self.target_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_target2.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() # 更新价值网络的参数
if self.count % self.target_update == 0:
self.target_q_net.load_state_dict(self.q_net.state_dict()) # 更新目标网络
self.count += 1
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 = A2C(state_dim, hidden_dim, action_dim, learning_rate, gamma, target_update, device)
for i in range(episodes):
transition_dict = {'states': [],'actions': [],'next_states': [],'rewards': [],'dones': []}
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
transition_dict['states'].append(state)
transition_dict['actions'].append(action)
transition_dict['next_states'].append(next_state)
transition_dict['dones'].append(done)
state = next_state
agent.update(transition_dict)
定义策略网络:输入状态
,输出该状态下的动作概率分布,用softmax()来实现分布。
采样:通过策略网络的输出(动作概率分布)对离散的动作进行采样。
定义价值网络:输入状态
,输出该状态下的状态价值。
可以采用gym的小游戏‘CartPole-v0’环境进行实验操作。
总结:
AC模型比REINFORCE更稳定,减小了方差。
价值模块Critic在策略模块Actor采样的数据中学习状态价值,能够帮助Actor分辨什么是好的动作,什么不是好的动作,进而指导Actor进行策略更新。
随着Actor的训练的进行,其与环境交互所产生的数据分布也发生改变,这需要Critic尽快适应新的数据分布并帮助Actor更好的判别。
A2C比AC多了优势函数
(即添加了基准线),能够减小方差。
3.4、A3C
异步优势演员-评论员算法(A3C)是在A2C算法基础上加上了异步的模式进行学习,由于使用了多个CPU,所以学习速度非常快。
异步优势演员-评论员算法有一个全局网络(包含策略函数网络和价值函数网络
,两个网络可以共用前几层)。假设全局网络的参数是
,然后使用多个进程,每个进程都有1个CPU训练,每一个进程在工作之前都会把全局网络的参数复制过来(复制全局网络),然后与环境互动学习。每个进程与环境互动采集数据计算梯度,将计算的梯度传回全局网络更新参数。
所有的进程都是平行独立训练的,每个进程计算出梯度就要传回去,可能该进程计算的梯度传回去的时候,原来全局网络的参数已经更新了。
这样看异步优势演员-评论员算法属于异策略算法,但是异步优势演员-评论员算法是一种同策略算法,这是由于每个进程的演员和评论员都是基于当前策略与环境互动采集的数据计算梯度的,不存储历史数据计算梯度,主要通过平行探索来保持训练的稳定性。
AC算法总结:
格式基本上是一个价值网络和一个策略网络(根据价值网络的形式不同分为几种不同的AC算法),由价值网络评判动作的好坏。
属于同策略算法,若要使用异策略加上重要性采样。
AC算法为什么是同策略算法?
从梯度公式
可以看出,梯度是“策略网络采样的数据得到的总回报对参数的导数”,
是对策略网络
的分布的采样估计。因此为了满足正确性,每次对参数更新完之后就要重新采样计算梯度。
4、确定性策略梯度算法
4.1、路径衍生策略梯度
路径衍生策略梯度可以看成是DQN解连续动作的方法,也可以看成是一种特别的AC算法。
对DQN的角度来说,解决连续动作向量没有通用的解决方法,因为没办法得到哪一个动作向量可以使得Q值取最大,即
。路径衍生策略梯度就可以解决了,用一个策略网络
(演员Actor)输入状态
,然后解出输出哪一个动作或动作向量可以得到最大的Q值。这是确定性策略。
对AC算法来说,原来的算法Critic没有给Actor足够的信息,只告诉Actor这个状态
下采取动作的价值高不高,这个动作好不好,而没有告诉Actor什么样的动作是好的(就是那个动作的价值最高)。路径衍生策略梯度就可以实现Critic不只是告诉Actor这个动作的价值好不好,还告诉Actor采取什么样的动作才可以得到比较大的价值。
路径衍生策略梯度用于连续动作空间,但是策略输出的是确定性策略
,之前的那些算法都是学习的随机性策略
。
之前的基于策略的算法都属于在线策略学习算法,虽然TRPO和PPO算法的优化目标中包含重要性采样的过程,但其只是用到了上一轮策略的数据(这样的话两个策略之间的分布就不会太大,误差就会小),而不是过去所有策略的数据。
路径衍生策略梯度方法使用确定性策略
,并且可以实现离线策略学习(因此DQN中的目标网络和经验回放,以及Double DQN技巧都是以使用)。
REINFORCE和AC算法都是在线策略学习,因为更新策略函数的参数是要在此策略下的价值作为权重,而不能是其他策略采集数据估计的价值作为本策略更新参数权重价值(虽然可以使用重要性采样,由于我们有假设,如果两个策略分布相差太多会导致误差)。
路径衍生策略梯度算法为什么是异策略算法?
策略网络
建立的是状态
和动作a一一对应关系(不是一个分布),是为了得到
。因此,
不是当前策略采集的数据计算出的价值回报也没关系,也可以更新网络。
目的是尽可能地训练出价值网络,然后将价值网络作为权重更新出状态
和动作a的一一对应关系。
路径衍生策略梯度模型:
路径衍生策略梯度算法就是学习一个函数(评价器输入是
与
,输出是
),然后学习
(演员输入
,目的是输出能够使得
尽可能大的动作,
),这个演员的工作就是解决
问题。
在训练的时候,策略与环境互动采集数据,然后根据数据学习Q网络,估计完Q值以后就把Q网络固定住,然后只去学习
,更新策略函数的参数。即先学Critic再学Actor,能够让演员在给定状态
后采取动作a使得Q函数输出的值越大越好。
路径衍生策略梯度算法流程:
初始化策略参数
、价值函数参数
(即初始化策略网络
和价值函数网络
)
用相同的网络参数
,初始化目标网络
(Actor也需要目标网络因为目标网络也会被用来计算目标值)
用相同的网络参数
,初始化目标网络
初始化
![]()
![]()
![]()
:
![]()
![]()
![]()
:(一个序列)
用策略网络
与环境互动采集轨迹(
):
将四元组
存储到
中
![]()
:
从
中随机采样一个batch(n个
)
对每个数据
用目标网络计算当
,用训练网络计算
最小化损失函数
,更新网络
的参数
计算策略函数的梯度:
对策略网络
更新参数:
(用torch.optim.Adam()梯度下降更新时)
![]()
:
更新目标网络
的参数
更新目标网络
的参数
![]()
对比DQN的改变:
a、在决定状态
执行的动作
不再是
决定,而是训练策略网络
采集;
b、在决定状态
执行的动作
不再是
决定,而是目标策略网络
决定;
c、之前只学习Q网络,现在多了策略网络,学习策略网络是为了最大化价值网络Q,解决
问题;
d、类似Q网络,既有训练网络又有目标网络,策略网络也有训练网络和目标网络。
4.2、DDPG
深度确定性策略梯度(deep deterministic policy gradient),即DDPG。
“深度”:神经网络
“确定性”:确定性的动作
“策略梯度”:策略网络
DDPG和路径衍生策略梯度算法基本一样,只不过DDPG处理的就是确定性连续动作,而且增加了随机噪声来增加探索,使用软更新更新网络参数。
代价函数:
的求解公式:
的推导过程(要用到状态访问分布和
,
是确定性策略):
1、首先得到
,状态价值函数
的对参数
求导:
令
为从状态
出发遵循策略
后k步到达状态x的概率(如
),那么:
找到了
与下一状态
的关系,继续:
2、然后,推导
:
令
,代表了从状态
遵循策略
走任意步长到状态
的期望概率。
(
)
最后推导出
常用的能够代入数据的公式:
以上是在线策略形式的DDPG算法推导(
),如果是离线策略形式的DDPG算法,那么状态
的分布
,其他策略采集的数据代入Q网络计算出梯度权重更新策略网络的参数。
DDPG算法流程:
初始化策略参数
、价值函数参数
(即初始化策略网络
和价值函数网络
)
用相同的网络参数
,初始化目标网络
(Actor也需要目标网络因为目标网络也会被用来计算目标值)
用相同的网络参数
,初始化目标网络
初始化
,随机噪声
![]()
![]()
![]()
:
![]()
![]()
![]()
:(一个序列)
用策略网络
与环境互动采集轨迹(
):
将四元组
存储到
中
![]()
:
从
中随机采样一个batch(n个
)
对每个数据
用目标网络计算当
,用训练网络计算
最小化损失函数
,更新网络
的参数
计算策略函数的梯度:
对策略网络
更新参数:
(用torch.optim.Adam()梯度下降更新时)
更新目标网络
的参数:
更新目标网络
的参数:
![]()
DQN中有
算法探索动作,DDPG在确定性策略(探索有限)基础上加一个噪声
来探索。
在给目标网络更新网络参数时,不用像DQN一样每隔C步更新一次,可以使用软更新
与
来更新。
DDPG相关代码:
import random
import gym
import numpy as np
import torch
from torch import nn
import torch.nn.functional as F
# 经验回放
class ReplayBuffer:
def __init__(self, capacity):
self.buffer = collections.deque(maxlen=capacity)
def add(self, state, action, reward, next_state, done):
self.buffer.append((state, action, reward, next_state, done))
def sample(self, batch_size):
transitions = random.sample(self.buffer, batch_size)
state, action, reward, next_state, done = zip(*transitions)
return np.array(state), action, reward, np.array(next_state), done
def size(self):
return len(self.buffer)
# 策略网络
class PolicyNet(torch.nn.Module):
def __init__(self, state_dim, hidden_dim, action_dim, action_bound):
super(PolicyNet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, action_dim)
self.action_bound = action_bound # action_bound是环境可以接受的动作最大值
def forward(self, x):
x = F.relu(self.fc1(x))
return torch.tanh(self.fc2(x)) * self.action_bound
# 价值网络
class QValueNet(torch.nn.Module):
def __init__(self, state_dim, hidden_dim, action_dim):
super(QValueNet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim + action_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, hidden_dim)
self.fc_out = torch.nn.Linear(hidden_dim, 1)
def forward(self, x, a):
cat = torch.cat([x, a], dim=1) # 拼接状态和动作
x = F.relu(self.fc1(cat))
x = F.relu(self.fc2(x))
return self.fc_out(x)
# DDPG算法
class DDPG:
def __init__(self, state_dim, hidden_dim, action_dim, action_bound, sigma, actor_lr, critic_lr, tau, gamma, device):
self.actor = PolicyNet(state_dim, hidden_dim, action_dim, action_bound).to(device) # 初始化训练策略网络
self.critic = QValueNet(state_dim, hidden_dim, action_dim).to(device) # 初始化训练价值网络
self.target_actor = PolicyNet(state_dim, hidden_dim, action_dim, action_bound).to(device) # 初始化目标策略网络
self.target_critic = QValueNet(state_dim, hidden_dim, action_dim).to(device) # 初始化目标价值网络
self.target_critic.load_state_dict(self.critic.state_dict()) # 目标价值网络参数=训练价值网络参数
self.target_actor.load_state_dict(self.actor.state_dict()) # 目标策略网络参数=训练策略网络参数
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.sigma = sigma # 高斯噪声的标准差,均值直接设为0
self.tau = tau # 目标网络软更新参数
self.action_dim = action_dim
self.device = device
def take_action(self, state):
state = torch.tensor([state], dtype=torch.float).to(self.device)
action = self.actor(state).item()
action = action + self.sigma * np.random.randn(self.action_dim) # 给动作添加高斯噪声
return action
def soft_update(self, net, target_net):
for param_target, param in zip(target_net.parameters(), net.parameters()):
param_target.data.copy_(param_target.data * (1.0 - self.tau) + param.data * self.tau)
def update(self, transition_dict):
states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
actions = torch.tensor(transition_dict['actions'], dtype=torch.float).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)
next_q_values = self.target_critic(next_states, self.target_actor(next_states))
q_targets = rewards + self.gamma * next_q_values * (1 - dones)
critic_loss = torch.mean(F.mse_loss(self.critic(states, actions), q_targets))
self.critic_optimizer.zero_grad()
critic_loss.backward()
self.critic_optimizer.step()
actor_loss = -torch.mean(self.critic(states, self.actor(states)))
self.actor_optimizer.zero_grad()
actor_loss.backward()
self.actor_optimizer.step()
self.soft_update(self.actor, self.target_actor) # 软更新策略网络
self.soft_update(self.critic, self.target_critic) # 软更新价值网络
actor_lr = 3e-4
critic_lr = 3e-3
num_episodes = 200
hidden_dim = 64
gamma = 0.98
tau = 0.005 # 软更新参数
buffer_size = 10000
minimal_size = 1000
batch_size = 64
sigma = 0.01 # 高斯噪声标准差
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
env_name = 'Pendulum-v0'
env = gym.make(env_name)
random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
replay_buffer = ReplayBuffer(buffer_size)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.shape[0]
action_bound = env.action_space.high[0] # 动作最大值
agent = DDPG(state_dim, hidden_dim, action_dim, action_bound, sigma, actor_lr, critic_lr, tau, gamma, device)
for i_episode in range(num_episodes):
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
replay_buffer.add(state, action, reward, next_state, done)
state = next_state
if replay_buffer.size() > minimal_size:
b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
transition_dict = {
'states': b_s,
'actions': b_a,
'next_states': b_ns,
'dones': b_d}
agent.update(transition_dict)
定义策略网络:输入状态
,输出确定性连续动作(利用
计算连续动作,范围是
)。
采样:通过策略网络的输出进行采样。因为是离线策略学习,所以每次采集的数据都放在经验回放
里。策略网络
输出动作时,为了更好地探索,我们向动作中加入随机高斯噪声
。
定义价值网络:输入状态
,与动作
,输出该状态动作对
的状态价值
。
可以采用gym的小游戏‘Pendulum-v0’环境进行实验操作(动作是连续的一维向量)。
总结:基于策略函数的强化学习主要是通过学习策略网络来学习出最优的策略。有学习随机策略的算法,也有学习确定性策略
的算法,有价值函数作为评价器的AC算法。