【教程地址】https://github.com/datawhalechina/joyrl-book
【强化学习库JoyRL】https://github.com/datawhalechina/joyrl/tree/main
策略梯度算法是一类通过优化策略本身来解决强化学习问题的方法。这与传统的基于价值的方法(如Q-learning)不同,后者通过学习每个状态或动作的价值来间接地优化策略。策略梯度方法直接通过参数化策略并优化这些参数来提高期望收益。
1.基于价值算法的缺点
- 无法表示连续动作。只能处理离散动作空间的问题,无法表示连续动作空间的问题。
- 高方差。基于价值的方法通常都是通过采样的方式来估计价值函数,这样会导致估计的方差很高,从而影响算法的收敛性。
- 探索与利用的平衡问题。很多问题的最优策略是随机策略,即需要以不同的概率选择不同的动作。而基于价值算法在实现时通常选择贪心的确定性策略。
2.策略梯度算法的关键要素
2.1.策略表示:
策略函数:表示在给定状态s下选择动作a的概率,参数化由θ表示。
任意轨迹其产生的概率 :
2.2.目标函数J(θ):
策略的价值期望公式:
定义为策略πθ下的期望回报。目标是调整策略参数θ以最大化这个函数。
2.3.策略梯度:
提供了计算目标函数J(θ)的梯度的方法,形式通常为期望形式:
其中R_t代表时间t以后获得的回报。这个表达式说明了策略梯度是对数策略的梯度与从该时间步开始的回报的乘积的期望。第一行就是目标函数的表达形式,到第二行就是全期望展开式,到第三行就是利用了积分的梯度性质,即梯度可以放到积分号的里面也就是被积函数中,第四行到最后就是对数微分技巧。
3.策略梯度算法的核心思想
- 基于价值的算法是通过学习价值函数来指导策略的,而基于策略的算法则是对策略进行优化,并且通过计算轨迹的价值期望来指导策略的更新。策略梯度方法的核心是直接对策略进行参数化,并利用收集到的经验(通过与环境的交互)来优化策略参数。这种直接方法允许模型学习随机策略,这在很多复杂的环境中是必要的。
4.REINFORCE算法
REINFORCE算法由Williams于1992年提出,是强化学习领域的基础算法之一。它是策略梯度的蒙特卡洛变体。REINFORCE的核心思想是使用完整的剧情来更新策略,即它等到剧情结束时才更新策略。这样,它不需要依赖于自助法来估算回报。
该算法通过最大化预期回报来更新策略参数。具体来说,它计算预期回报关于策略参数的梯度,然后将参数沿梯度的方向移动。策略通常使用动作偏好的softmax进行参数化。每一步的更新与所采取动作的对数概率的梯度和所获得的回报的乘积成正比。这个乘积确保了更高回报的动作变得更有可能,而较低回报的动作变得不太可能。
4.1模型设计
import torch
import torch.nn as nn
import torch.nn.functional as F
class PGNet(nn.Module):
def __init__(self, input_dim, output_dim, hidden_dim=128):
""" 初始化策略梯度网络(PGNet),它是一个全连接的前馈神经网络。
参数:
input_dim (int): 输入层的维度,即环境状态的维度。
output_dim (int): 输出层的维度,即可能的动作数量。
hidden_dim (int, optional): 隐藏层的维度,默认值为128。
"""
super(PGNet, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim) # 定义输入层到隐藏层的全连接层。
self.fc2 = nn.Linear(hidden_dim, hidden_dim) # 定义隐藏层到隐藏层的全连接层。
self.fc3 = nn.Linear(hidden_dim, output_dim) # 定义隐藏层到输出层的全连接层。
def forward(self, x):
""" 定义网络的前向传播路径。
参数:
x (Tensor): 输入到网络的数据,即环境的状态。
返回:
Tensor: 经过网络处理后的输出,即每个动作的概率。
"""
x = F.relu(self.fc1(x)) # 通过第一个全连接层,然后应用ReLU激活函数。
x = F.relu(self.fc2(x)) # 通过第二个全连接层,然后应用ReLU激活函数。
x = torch.sigmoid(self.fc3(x)) # 通过第三个全连接层,然后应用Sigmoid激活函数以获取每个动作的概率。
return x
4.2更新函数
import torch
from torch.distributions import Bernoulli
from torch.autograd import Variable
import numpy as np
class PolicyGradient:
def __init__(self, model, memory, cfg):
""" 初始化策略梯度类。
参数:
model: PyTorch模型,用作策略网络,即用于预测给定状态下各动作的概率。
memory: 用于存储游戏中的状态、动作和回报,帮助算法在每一步学习。
cfg: 包含配置参数的字典,如折扣因子gamma、设备device和学习率lr。
"""
self.gamma = cfg['gamma'] # 折扣因子,用于计算未来奖励的当前价值。
self.device = torch.device(cfg['device']) # 定义训练所用的设备,例如"cpu"或"cuda:0"。
self.memory = memory # 经验回放池,用于存储和随机获取游戏过程中的转换。
self.policy_net = model.to(self.device) # 将策略网络模型加载到指定的设备上。
# 定义优化器,这里使用RMSprop,也可以根据需要使用其他优化器如Adam等。
self.optimizer = torch.optim.RMSprop(self.policy_net.parameters(), lr=cfg['lr'])
def sample_action(self, state):
""" 根据当前状态和策略网络采样动作。
参数:
state: 当前环境状态。
返回: 根据策略网络和当前状态采样得到的动作。
"""
state = torch.from_numpy(state).float() # 将numpy的状态向量转换为PyTorch张量。
state = Variable(state) # 将状态张量封装为Variable,这在PyTorch中用于自动求导。
probs = self.policy_net(state) # 通过策略网络获取给定状态下每个动作的概率。
m = Bernoulli(probs) # 根据动作概率创建一个伯努利分布,用于模拟二元动作的随机过程。
action = m.sample() # 从伯努利分布中采样动作。
action = action.data.numpy().astype(int)[0] # 将动作转换为numpy数组,并获取具体的动作值。
return action
# predict_action方法与sample_action在功能上是相同的,用于预测动作。
def update(self):
""" 根据收集的经验更新策略网络。
"""
# 从记忆库中获取状态、动作和回报的批次。
state_pool, action_pool, reward_pool = self.memory.sample()
state_pool, action_pool, reward_pool = list(state_pool), list(action_pool), list(reward_pool)
# 初始化并计算折扣回报,这是一种将未来奖励的价值转换为当前价值的方法。
running_add = 0
for i in reversed(range(len(reward_pool))):
if reward_pool[i] == 0:
running_add = 0
else:
running_add = running_add * self.gamma + reward_pool[i]
reward_pool[i] = running_add
# 标准化回报,通过从每个回报中减去平均回报并除以标准差来减少回报之间的方差,增强训练的稳定性。
reward_mean = np.mean(reward_pool)
reward_std = np.std(reward_pool)
for i in range(len(reward_pool)):
reward_pool[i] = (reward_pool[i] - reward_mean) / reward_std
# 开始一轮梯度下降。
self.optimizer.zero_grad()
# 对于经验池中的每个转换,计算损失并进行反向传播。
for i in range(len(reward_pool)):
state = state_pool[i]
action = Variable(torch.FloatTensor([action_pool[i]]))
reward = reward_pool[i]
state = Variable(torch.from_numpy(state).float())
probs = self.policy_net(state)
m = Bernoulli(probs)
# 计算策略梯度损失,即负的对数概率乘以回报。
loss = -m.log_prob(action) * reward
loss.backward()
self.optimizer.step() # 根据收集的梯度更新策略网络的参数。
self.memory.clear() # 清空记忆库,为收集新的游戏经验做准备。
5.策略梯度推导进阶
具有非周期性和状态连通性马氏链一定存在一个平稳分布:
平稳状态下:
进阶版的策略梯度推导考虑了策略梯度算法的一些潜在问题,尤其是在面对高方差和评估策略价值时的不稳定性。这部分内容提出了一种更加稳健的策略评估方法,即使用优势函数(Advantage)代替简单的累积回报。优势函数的核心思想是减少策略评估的方差,使得梯度估计更加稳定。它通常定义为某状态下某动作的Q值与该状态下策略的平均Q值之差。这样,即使回报的绝对值变化很大,只要相对于平均值的变化不大,策略的更新就可以保持稳定。
6.策略函数设计
在强化学习中,策略函数的设计是核心组成部分之一。对于离散动作空间,策略函数通常输出一个概率分布,指明在给定状态下选择各个可能动作的概率。设计时通常使用神经网络,其中输入层接收状态信息,输出层输出每个动作的概率。为了将神经网络的输出转换为概率分布,通常会在最后一层使用Softmax函数,它可以将一组值映射为概率分布。Softmax函数的输出保证了所有可能动作的概率之和为1,并且每个动作的概率都是非负的,从而满足概率分布的基本要求。这种设计使得策略函数能够在每个状态下根据当前学到的知识给出每个动作的选择概率。
对于连续动作空间,通常策略对应的动作可以从高斯分布。,只需要在模型最后一层输出两个值,一个是均值,一个是方差,然后再用这两个值来构建一个高斯分布,然后采样即可。