PPO(Proximal Policy Optimization,近端策略优化)是一种经典的强化学习算法,通过截断概率比来限制策略更新的幅度,从而保证更新的稳定性,广泛应用于连续动作空间任务。GRPO(Group Relative Policy Optimization,群组相对策略优化)是由DeepSeek提出的一种强化学习算法,它通过引入更灵活的策略更新方式和目标函数,能够适应多种强化学习问题,尤其在复杂环境和多目标优化场景中表现出色。两者都致力于提高策略学习的效率和效果,但PPO更注重稳定性,GRPO更强调灵活性和通用性。
8.1 PPO算法的核心知识和应用
PPO由OpenAI于2017年提出,是一种强化学习算法,用于训练能够执行连续动作的智能体,以最大化累积奖励。
8.1.1 PPO的推出背景
PPO的推出背景是为了解决传统策略梯度方法和TRPO的局限性,通过简化算法实现、提高数据效率和保持训练稳定性,为强化学习的广泛应用奠定了坚实的基础。
1. 传统策略梯度方法的局限性
早期的强化学习算法在实际应用中面临诸多挑战,例如训练不稳定、样本效率低、难以扩展到复杂环境等,具体说明如下所示。
- 训练不稳定:传统的策略梯度方法(如REINFORCE算法)直接优化策略的参数,但更新过程中容易导致策略的剧烈变化,从而使得训练过程不稳定。
- 样本效率低:这些方法通常需要大量的样本才能收敛,因为每次策略更新后,旧的样本数据可能不再适用,导致数据利用率低。
- 更新步长难以控制:策略梯度方法中,学习率的选择非常关键。如果学习率过大,可能导致策略更新过度,训练发散;如果学习率过小,则收敛速度过慢。
2. TRPO的局限性
TRPO(Trust Region Policy Optimization,信任区域策略优化)是为了解决传统策略梯度方法的不稳定性而提出的。TRPO通过限制策略更新的幅度,确保每次更新都在“信任区域”内,从而保证策略更新的稳定性。然而,TRPO也存在一些问题:
- 实现复杂:TRPO的实现需要计算复杂的二阶导数(如共轭梯度法),这使得算法的实现和调试变得非常困难。
- 计算成本高:TRPO的每次更新都需要进行复杂的优化计算,导致计算成本较高,难以在大规模问题中应用。
3. PPO的提出动机
PPO(Proximal Policy Optimization,近端策略优化)正是为了解决上述问题而提出的。PPO的主要目标是:
- 简化TRPO的复杂性:通过引入一种更简单的方式来限制策略更新的幅度,避免使用复杂的二阶导数计算。
- 提高数据效率:允许使用相同的数据进行多次更新,从而提高样本的利用效率。
- 保持训练稳定性:通过限制策略更新的幅度,避免过大的策略变化,从而保证训练过程的稳定性。
4. PPO的核心思想
PPO的核心思想是通过裁剪的概率比来限制策略更新的幅度。具体来说,PPO定义了一个目标函数,其中引入了一个“裁剪项”(clipping term),确保策略更新不会偏离当前策略太远。这种裁剪机制既保证了策略更新的稳定性,又避免了复杂的二阶导数计算。
5. PPO的优势
- 简单易实现:PPO的实现相对简单,不需要复杂的二阶导数计算,因此更容易实现和调试。
- 高效稳定:PPO在保持训练稳定性的同时,提高了数据的利用效率,能够更快地收敛。
- 适用性强:PPO适用于多种强化学习任务,包括连续动作空间和离散动作空间的问题。
6. PPO的广泛影响
PPO的提出极大地推动了强化学习的发展,它不仅在学术界得到了广泛的应用和研究,还在工业界得到了大量的实践。例如,在机器人控制、游戏AI、自动驾驶等领域,PPO都被证明是一种非常有效的强化学习算法。
总之,PPO的推出背景是为了解决传统策略梯度方法和TRPO的局限性,通过简化算法实现、提高数据效率和保持训练稳定性,为强化学习的广泛应用奠定了坚实的基础。
8.1.2 PPO算法的基本思想
PPO(Proximal Policy Optimization,近端策略优化)的核心思想是通过限制策略更新的幅度,确保更新过程的稳定性,同时简化算法实现并提高样本效率。
1. 策略更新的稳定性
PPO的核心目标是确保策略更新的稳定性。在传统策略梯度方法中,策略更新可能会导致策略的剧烈变化,从而使得训练过程不稳定。PPO通过引入裁剪的概率比(clipping)来限制策略更新的幅度。具体来说,PPO定义了一个目标函数,其中引入了一个“裁剪项”,确保策略更新不会偏离当前策略太远。
2. 裁剪的概率比(Clipping)
PPO的目标函数基于策略梯度方法的期望奖励目标,但引入了一个裁剪机制。假设当前策略为πθ,更新后的策略为 πθ′,则概率比 r(θ) 定义为:
PPO的目标函数为:
其中:
- At
是优势函数(Advantage Function),表示在状态st
下采取行动at
的期望奖励;
- ϵ 是裁剪参数,通常取值为0.1或0.2。裁剪机制能够确保概率比 r(θ) 在 1−ϵ 和 1+ϵ 之间,从而限制策略更新的幅度。
3. 计算策略梯度
通过下面公式计算策略梯度:
在这里,是策略的梯度,表示策略在当前状态下采取动作a的对数概率的梯度。
4. 优化策略
在计算出策略梯度后,PPO通过梯度上升方法更新策略参数θ:
其中,α是学习率,控制更新的步长。
5. 目标函数的改进
除了裁剪的概率比,PPO还引入了一个额外的目标函数,称为“目标函数的截断版本”(Clipped Surrogate Objective Function)。这个目标函数结合了裁剪的概率比和原始的概率比,进一步提高了算法的稳定性。具体来说,PPO的目标函数可以表示为:
其中:
是值函数(Value Function)的损失,用于优化状态价值函数;
- S(θ) 是策略的熵,用于增加策略的探索性;
和
是超参数,用于平衡不同部分的权重。
6. 数据的多次利用
PPO允许使用相同的数据进行多次更新,从而提高样本的利用效率。在每次策略更新时,PPO会从经验回放缓存(Experience Replay Buffer)中采样一批数据,并使用这些数据进行多次策略更新。这种机制类似于深度学习中的小批量梯度下降(Mini-batch Gradient Descent),能够有效提高样本的利用效率,减少样本浪费。
7. 简化实现
PPO的另一个重要特点是简化了TRPO的复杂性。TRPO需要计算复杂的二阶导数(如共轭梯度法),这使得算法的实现和调试变得非常困难。而PPO通过裁剪的概率比和目标函数的改进,避免了复杂的二阶导数计算,使得算法的实现更加简单高效。
总之,PPO算法的基本思想是通过裁剪的概率比限制策略更新的幅度,确保训练过程的稳定性;同时,通过允许数据的多次利用,提高样本效率;并通过简化目标函数和实现过程,降低算法的复杂性。这些改进使得PPO在实际应用中表现出色,成为强化学习领域中一种广泛使用的算法。
8.1.3 综合实战:使用PPO算法解决CartPole平衡问题
例如在下面的实例中实现了一个基于PPO(近端策略优化)算法的强化学习训练程序,用于解决Gym环境中的CartPole平衡问题。在实例中定义了策略网络和价值网络,通过采样动作、计算折扣回报和优势估计,利用裁剪的概率比来优化策略,同时更新价值网络以更准确地估计状态价值。训练过程中,智能体与环境交互,收集数据并周期性地更新策略,最终通过可视化展示训练好的策略在环境中的表现。
实例8-1:使用PPO算法解决CartPole平衡问题(源码路径:codes\8\ppo_cartpole.py)
实例文件ppo_cartpole.py的具体实现代码如下所示。
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.distributions import Categorical
import gym
import matplotlib.pyplot as plt
from collections import deque
# 设置随机种子以保证结果可复现
torch.manual_seed(0)
np.random.seed(0)
class PolicyNetwork(nn.Module):
def __init__(self, state_dim, action_dim, hidden_dim=128):
super(PolicyNetwork, self).__init__()
self.fc1 = nn.Linear(state_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, action_dim)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return F.softmax(x, dim=-1)
class ValueNetwork(nn.Module):
def __init__(self, state_dim, hidden_dim=128):
super(ValueNetwork, self).__init__()
self.fc1 = nn.Linear(state_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, 1)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)
def select_action(policy, state):
# 确保状态是有效的 numpy 数组
if isinstance(state, tuple):
state = state[0] # 处理可能包含额外信息的元组
if isinstance(state, list):
state = np.array(state)
# 处理可能的空状态或异常状态
if state.size == 0:
print("Warning: Empty state received. Using zeros as fallback.")
state = np.zeros(4) # CartPole 环境状态维度为 4
elif state.shape != (4,):
print(f"Warning: State shape {state.shape} is not (4,). Attempting to reshape.")
state = state.reshape(4)
state = torch.FloatTensor(state)
# 确保状态维度正确(添加批次维度)
if state.dim() == 1:
state = state.unsqueeze(0)
with torch.no_grad():
probs = policy(state)
dist = Categorical(probs)
action = dist.sample()
log_prob = dist.log_prob(action)
return action.item(), log_prob
def update(ppo_optimizer, policy, value, states, actions, log_probs_old, rewards, dones, gamma=0.99, eps_clip=0.2):
# 计算折扣回报
returns = []
R = 0
for r, done in zip(reversed(rewards), reversed(dones)):
if done:
R = 0
R = r + gamma * R
returns.insert(0, R)
returns = torch.FloatTensor(returns)
# 标准化回报
returns = (returns - returns.mean()) / (returns.std() + 1e-8)
# 转换数据为张量
states = torch.FloatTensor(states)
actions = torch.LongTensor(actions)
log_probs_old = torch.FloatTensor(log_probs_old)
# 获取当前策略下的动作概率和状态价值
probs = policy(states)
dist = Categorical(probs)
log_probs = dist.log_prob(actions)
values = value(states).squeeze()
# 计算优势估计
advantages = returns - values.detach()
# 计算策略损失
ratio = torch.exp(log_probs - log_probs_old)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - eps_clip, 1 + eps_clip) * advantages
policy_loss = -torch.min(surr1, surr2).mean()
# 计算价值损失
value_loss = F.mse_loss(values, returns)
# 总损失
loss = policy_loss + 0.5 * value_loss
# 优化
ppo_optimizer.zero_grad()
loss.backward()
ppo_optimizer.step()
return loss.item()
def train():
# 创建环境
env = gym.make('CartPole-v1')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
# 初始化网络
policy = PolicyNetwork(state_dim, action_dim)
value = ValueNetwork(state_dim)
# 优化器
optimizer = optim.Adam(list(policy.parameters()) + list(value.parameters()), lr=3e-4)
# 训练参数
max_episodes = 300
max_steps = 500
update_interval = 20
# 记录奖励
episode_rewards = []
avg_rewards = []
for episode in range(max_episodes):
# 获取初始状态 - 处理可能的 Gym API 变化
state_info = env.reset()
state = state_info[0] if isinstance(state_info, tuple) else state_info
episode_reward = 0
states, actions, log_probs, rewards, dones = [], [], [], [], []
for step in range(max_steps):
# 选择动作
action, log_prob = select_action(policy, state)
# 执行动作 - 处理可能的 Gym API 变化
step_info = env.step(action)
next_state, reward, terminated, truncated, _ = step_info if len(step_info) == 5 else (*step_info[:3], False, {})
done = terminated or truncated
# 修改奖励以加速训练
reward = reward if not done else -10
# 存储转换
states.append(state)
actions.append(action)
log_probs.append(log_prob)
rewards.append(reward)
dones.append(done)
state = next_state
episode_reward += reward
if done:
break
# 记录奖励
episode_rewards.append(episode_reward)
avg_reward = np.mean(episode_rewards[-100:])
avg_rewards.append(avg_reward)
# 打印进度
print(f"Episode {episode+1}/{max_episodes}, Reward: {episode_reward}, Avg Reward: {avg_reward:.2f}")
# 更新策略
if (episode + 1) % update_interval == 0:
loss = update(optimizer, policy, value, states, actions, log_probs, rewards, dones)
print(f"Update at episode {episode+1}, Loss: {loss:.4f}")
# 检查是否解决
if avg_reward > 490:
print(f"Environment solved in {episode+1} episodes!")
break
env.close()
# 可视化训练结果
plt.figure(figsize=(10, 5))
plt.plot(episode_rewards, label='Episode Rewards')
plt.plot(avg_rewards, label='Average Rewards', linewidth=2)
plt.xlabel('Episode')
plt.ylabel('Reward')
plt.title('PPO Training Performance on CartPole')
plt.legend()
plt.grid(True)
plt.show()
return policy, env
def visualize_policy(policy, env, episodes=5):
for episode in range(episodes):
# 获取初始状态 - 处理可能的 Gym API 变化
state_info = env.reset()
state = state_info[0] if isinstance(state_info, tuple) else state_info
done = False
total_reward = 0
while not done:
env.render()
action, _ = select_action(policy, state)
# 执行动作 - 处理可能的 Gym API 变化
step_info = env.step(action)
next_state, reward, terminated, truncated, _ = step_info if len(step_info) == 5 else (*step_info[:3], False, {})
done = terminated or truncated
state = next_state
total_reward += reward
print(f"Visualization Episode {episode+1}, Total Reward: {total_reward}")
env.close()
if __name__ == "__main__":
trained_policy, env = train()
visualize_policy(trained_policy, env)
上述代码的实现流程如下所示。
(1)初始化环境和网络
创建Gym环境(CartPole-v1),初始化策略网络(PolicyNetwork)和价值网络(ValueNetwork),分别用于输出动作的概率分布和估计状态的价值。
设置优化器,用于更新策略网络和价值网络的参数。
(2)训练过程
与环境交互:在每个episode中,智能体从初始状态开始,根据当前策略网络选择动作并与环境交互,收集状态、动作、奖励、对数概率等信息。
奖励修改:为了加速训练,对奖励进行调整,例如在智能体失败时给予较大的负奖励。
数据存储:将每个时间步的状态、动作、对数概率、奖励和终止标志存储到缓冲区中。
(3)策略更新:每隔一定间隔(update_interval),使用收集到的数据进行策略更新,具体流程如下所示。
- 计算折扣回报:从后向前计算每个时间步的折扣回报。
- 标准化回报:对回报进行标准化处理,以提高训练稳定性。
- 计算优势估计:通过回报减去状态价值来估计优势函数。
- 计算策略损失和价值损失:利用裁剪的概率比计算策略损失,同时计算价值网络的均方误差损失。
- 优化网络:通过反向传播更新策略网络和价值网络的参数。
(4)记录和可视化:在每个episode中记录奖励,并计算最近100个episode的平均奖励。使用Matplotlib绘制奖励曲线,展示训练过程中的性能变化。
(5)策略可视化:在训练完成后,使用训练好的策略在环境中运行多个episode,可视化展示智能体的行为表现,观察其是否能够成功完成任务。如图8-1所示。
图8-1 训练过程中的奖励曲线图