30分钟上手策略梯度:从原理到实战案例详解
你还在为强化学习算法复杂而头疼?本文带你30分钟掌握Policy Gradient(策略梯度)的核心原理,从零实现一个简单的强化学习模型。读完本文你将能够:
- 理解策略梯度的基本概念和数学原理
- 掌握REINFORCE算法的实现步骤
- 用PyTorch实现一个简单的策略梯度模型
- 在实际环境中训练和评估策略梯度模型
什么是策略梯度
策略梯度是强化学习中一种直接优化策略的方法,它不需要像Q-learning那样估计价值函数,而是直接通过梯度上升来最大化期望奖励。策略梯度算法的核心思想是:如果一个动作带来了正的奖励,那么我们就增加这个动作被选中的概率;反之,如果一个动作带来了负的奖励,我们就减少这个动作被选中的概率。
策略梯度算法通常用一个参数化的策略函数$\pi_\theta(a|s)$来表示智能体的行为,其中$\theta$是策略的参数,$s$是当前状态,$a$是要执行的动作。策略函数输出的是在状态$s$下采取各个动作的概率分布。
策略梯度的数学原理
策略梯度算法的目标是最大化期望累积奖励: $$\bar{R}\theta = \mathbb{E}{\tau \sim p_\theta(\tau)}[R(\tau)]$$
其中,$\tau$是一个完整的轨迹(trajectory),$R(\tau)$是该轨迹的总奖励。为了最大化这个期望,我们使用梯度上升的方法,计算$\bar{R}_\theta$对参数$\theta$的梯度:
$$\nabla \bar{R}\theta = \mathbb{E}{\tau \sim p_\theta(\tau)}[R(\tau) \nabla \log p_\theta(\tau)]$$
进一步展开,可以得到策略梯度的具体计算公式:
$$\nabla \bar{R}\theta \approx \frac{1}{N} \sum{n=1}^{N} \sum_{t=1}^{T_{n}} R\left(\tau^{n}\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right)$$
这个公式的直观含义是:在每个状态$s_t$下执行动作$a_t$后,如果整个轨迹的总奖励$R(\tau)$是正的,我们就增加在状态$s_t$下执行动作$a_t$的概率;反之则减少这个概率。
REINFORCE算法
REINFORCE算法是最简单也最经典的策略梯度算法,它使用蒙特卡洛方法来估计梯度。REINFORCE算法的步骤如下:
- 利用当前策略$\pi_\theta$采样一条轨迹$\tau = {s_1, a_1, r_1, s_2, a_2, r_2, ..., s_T, a_T, r_T}$
- 计算每个时刻$t$的折扣回报$G_t = \sum_{k=t+1}^T \gamma^{k-t-1} r_k$
- 计算损失函数$L(\theta) = -\sum_{t=1}^T G_t \log \pi_\theta(a_t|s_t)$
- 利用梯度下降更新参数$\theta$
REINFORCE算法的关键在于使用整个轨迹的回报来指导参数更新,这也是它被称为蒙特卡洛策略梯度的原因。
策略梯度模型实现
下面我们用PyTorch实现一个简单的策略梯度模型。首先定义策略网络:
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):
""" 初始化策略网络,为全连接网络
input_dim: 输入的特征数即环境的状态维度
output_dim: 输出的动作维度
"""
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 = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = torch.sigmoid(self.fc3(x))
return x
接下来实现策略梯度算法的主体部分:
import torch
from torch.distributions import Bernoulli
from torch.autograd import Variable
import numpy as np
class PolicyGradient:
def __init__(self, model, memory, cfg):
self.gamma = cfg['gamma']
self.device = torch.device(cfg['device'])
self.memory = memory
self.policy_net = model.to(self.device)
self.optimizer = torch.optim.RMSprop(self.policy_net.parameters(), lr=cfg['lr'])
def sample_action(self, state):
state = torch.from_numpy(state).float()
state = Variable(state)
probs = self.policy_net(state)
m = Bernoulli(probs) # 伯努利分布
action = m.sample()
action = action.data.numpy().astype(int)[0] # 转为标量
return 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()
策略梯度训练技巧
在实际实现策略梯度算法时,有一些技巧可以提高训练效果:
添加基线
策略梯度的一个问题是如果所有的奖励都是正的,那么所有动作的概率都会被增加,这可能导致未被采样到的动作概率下降。为了解决这个问题,我们可以在奖励中减去一个基线$b$:
$$\nabla \bar{R}{\theta} \approx \frac{1}{N} \sum{n=1}^{N} \sum_{t=1}^{T_{n}}\left(R\left(\tau^{n}\right)-b\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right)$$
基线$b$通常设置为平均奖励,这样可以让$R(\tau)-b$有正有负,从而使得好的动作概率增加,差的动作概率减少。
分配合适的分数
另一个改进是给每个动作分配不同的权重,而不是整个轨迹使用相同的奖励。具体来说,我们使用从时刻$t$开始的折扣回报$G_t$来代替整个轨迹的总奖励$R(\tau)$:
$$\nabla \bar{R}{\theta} \approx \frac{1}{N} \sum{n=1}^{N} \sum_{t=1}^{T_{n}}\left(\sum_{t^{\prime}=t}^{T_{n}} \gamma^{t^{\prime}-t} r_{t^{\prime}}^{n}-b\right) \nabla \log p_{\theta}\left(a_{t}^{n} \mid s_{t}^{n}\right)$$
这样可以更精确地评估每个动作的好坏,而不是将整个轨迹的结果平均分配给每个动作。
实战案例:CartPole环境
下面我们在OpenAI Gym的CartPole环境中测试我们实现的策略梯度算法。CartPole环境的目标是控制一个小车,使车上的杆子保持竖直不倒。
首先,我们需要创建一个经验回放缓冲区来存储轨迹数据:
class Memory:
def __init__(self):
self.state_pool = []
self.action_pool = []
self.reward_pool = []
self.pool_size = 0
def push(self, state, action, reward):
self.state_pool.append(state)
self.action_pool.append(action)
self.reward_pool.append(reward)
self.pool_size += 1
def sample(self):
return self.state_pool, self.action_pool, self.reward_pool
def clear(self):
self.state_pool = []
self.action_pool = []
self.reward_pool = []
self.pool_size = 0
然后,我们可以开始训练模型:
import gym
import numpy as np
import torch
# 配置参数
cfg = {
'gamma': 0.99,
'lr': 0.01,
'train_eps': 1000,
'device': 'cpu'
}
# 创建环境
env = gym.make('CartPole-v0')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
# 初始化模型和记忆库
model = PGNet(state_dim, action_dim)
memory = Memory()
agent = PolicyGradient(model, memory, cfg)
# 训练模型
rewards = []
for i_ep in range(cfg['train_eps']):
state = env.reset()
ep_reward = 0
while True:
action = agent.sample_action(state)
next_state, reward, done, _ = env.step(action)
agent.memory.push(state, action, reward)
state = next_state
ep_reward += reward
if done:
agent.update()
rewards.append(ep_reward)
if (i_ep+1) % 100 == 0:
print(f"Episode: {i_ep+1}, Reward: {np.mean(rewards[-100:])}")
break
# 绘制奖励曲线
import matplotlib.pyplot as plt
plt.plot(rewards)
plt.title('Training Reward')
plt.xlabel('Episode')
plt.ylabel('Reward')
plt.show()
在CartPole环境中,策略梯度算法通常可以在1000个episode内收敛,达到200分的最大奖励。
总结
策略梯度是一种直接优化策略的强化学习方法,它通过梯度上升来最大化期望奖励。本文我们介绍了策略梯度的基本原理,实现了REINFORCE算法,并在CartPole环境中进行了测试。策略梯度算法的优点是可以处理连续动作空间,并且具有较好的收敛性;缺点是训练过程中方差较大,通常需要较多的采样数据。
在实际应用中,策略梯度算法有很多改进版本,如A2C、A3C、PPO等,这些算法通过引入价值函数估计或其他技巧来减少方差,提高训练效率。如果你想深入了解策略梯度算法,可以继续学习这些高级变种。
希望本文能够帮助你理解策略梯度算法的核心思想和实现方法。如果你有任何问题或建议,欢迎在评论区留言讨论!
点赞收藏本文,关注作者获取更多强化学习实战教程!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考








