Python+PyTorch深度实践多种强化学习算法项目实战
简介:强化学习作为人工智能的重要分支,致力于让智能体通过与环境交互学习最优策略以最大化长期奖励。PyTorch凭借其动态计算图和强大的自动求导机制,成为实现强化学习算法的首选框架。本项目“Python-PyTorch实现多种增强学习算法”系统性地展示了如何使用PyTorch构建和训练主流RL算法,涵盖Q-Learning、SARSA、DQN、Policy Gradient(如A2C、PPO)以及适用于连续动作空间的DDPG等。结合OpenAI Gym环境进行实验,项目包含完整的模型定义、训练流程、经验回放机制与性能评估方法,帮助开发者掌握从理论到代码落地的全过程,适用于游戏AI、机器人控制、自动驾驶等前沿领域的研究与应用。
强化学习实战全解析:从理论到PyTorch工程落地
你有没有想过,一个AI是怎么学会玩Atari游戏的?它既没有被告知规则,也不能像人类那样“看懂”画面。可就在一次次试错中,它从笨拙地乱按按钮,逐渐成长为能击败专业玩家的高手——这背后,就是 强化学习 (Reinforcement Learning, RL)的魔力!
🎯 今天我们就来一场深度之旅:不光讲清楚RL的核心思想,更要手把手带你用 PyTorch 把这些算法变成可运行的代码!我们会从最基础的Q-Learning开始,一路进阶到PPO、DDPG等现代主流算法,最终构建一个完整可复用的训练框架。
准备好了吗?Let’s go!🚀
🧠 强化学习的本质:智能体如何在未知世界中生存?
想象一下,你被丢进一个迷宫里,四周漆黑一片,只有脚下的地板会告诉你是否踩到了陷阱或宝藏。你看不见地图,不知道出口在哪,但你的目标很明确: 活着走出去,并尽可能多地收集金币 。
这就是强化学习的基本设定:
- 智能体(Agent) 就是你自己;
- 环境(Environment) 是那个迷宫;
- 每走一步,你会观察当前状态(State),比如“前方有墙”、“脚下是红砖”;
- 然后你选择一个动作(Action),比如“向前走”、“左转”;
- 环境反馈给你奖励(Reward):“+1金币!” 或 “-10生命值!!”;
- 接着进入新的状态……如此循环。
🔁 这个过程可以用一句话概括:
智能体通过与环境持续交互,在试错中学习最优策略,以最大化长期累积奖励。
听起来是不是有点像我们的人生?😄
数学建模:马尔可夫决策过程(MDP)
为了把这个问题形式化,研究者们引入了 马尔可夫决策过程(Markov Decision Process, MDP) 。它的核心假设是:
“未来只依赖于现在,而与过去无关。”
换句话说,只要你知道当前的状态,就能预测接下来会发生什么,不需要回忆整个历史路径。这个“无记忆性”大大简化了问题复杂度。
在这个框架下,我们定义几个关键概念:
| 概念 | 含义 | 示例 |
|---|---|---|
| $ s \in S $ | 状态(State) | 角色坐标(3,5),血量60% |
| $ a \in A $ | 动作(Action) | 上/下/左/右移动 |
| $ r \in R $ | 奖励(Reward) | 吃到食物+1,撞墙-5 |
| $ \pi(a|s) $ | 策略(Policy) | 在某个状态下选择动作的概率分布 |
| $ V(s) $ | 价值函数(Value Function) | 当前状态下未来能获得的期望回报 |
| $ Q(s,a) $ | 动作价值函数 | 在状态$s$执行动作$a$后的长期收益 |
其中最重要的是 贝尔曼方程(Bellman Equation) ,它是所有动态规划和时序差分方法的基石:
$$
Q(s,a) = \mathbb{E} \left[ r + \gamma \max_{a’} Q(s’,a’) \right]
$$
这里的 $\gamma \in [0,1]$ 是 折扣因子 ,用来控制智能体对远期奖励的关注程度。如果 $\gamma=0$,那AI就变成了短视鬼,只关心眼前利益;如果接近1,则更愿意为长远目标牺牲短期回报。
🧠 小贴士:你可以把它理解为“耐心系数”。越有耐心的人,越愿意投资未来。
⚖️ 探索 vs 利用:AI也需要“冒险精神”
如果你是一个理性投资者,你会怎么做?
- 一直买那只稳赚不赔的蓝筹股(利用)?
- 还是偶尔尝试几只高风险高回报的新股(探索)?
同样的问题摆在每个强化学习智能体面前。这就是著名的 Exploration vs Exploitation Trade-off 。
❌ 如果只利用已知信息:
- 可能陷入局部最优,错过更好的策略;
- 比如总是在迷宫的一个小区域打转。
❌ 如果过度探索:
- 学习效率极低,迟迟无法收敛;
- 就像一个人永远在尝试新路,却从未真正走出过家门。
所以,我们必须设计一些机制来 动态平衡两者之间的关系 。
常见探索策略一览
| 方法 | 原理 | 适用场景 |
|---|---|---|
| ε-greedy | 以概率$\varepsilon$随机选动作,否则选最大Q值动作 | 简单有效,适合离散动作空间 |
| Boltzmann (Softmax) | 按动作Q值的比例分配选择概率,温度参数调节探索强度 | 更平滑的探索行为 |
| UCB(上置信界) | 结合估计均值与不确定性进行选择,“越不确定越值得探索” | 多臂老虎机类问题 |
| 噪声注入 | 给输出加高斯噪声或OU噪声 | 连续控制任务,如机器人行走 |
举个例子,下面这段代码实现了经典的 ε-greedy 策略:
import torch
import numpy as np
def epsilon_greedy(q_values: torch.Tensor, epsilon: float):
if torch.rand(1).item() < epsilon:
return torch.randint(0, q_values.size(-1), (1,)).item() # 随机探索
else:
return torch.argmax(q_values).item() # 贪心利用
💡 实战建议:通常设置初始 $\varepsilon=1.0$(完全探索),然后随着训练逐步衰减至 0.01~0.1,让AI从“好奇宝宝”成长为“老练专家”。
🔥 PyTorch:为什么它是强化学习的首选框架?
现在市面上深度学习框架不少,TensorFlow、JAX、MindSpore……但说到做RL, PyTorch 几乎成了事实标准 。为什么?
因为它天生具备三大优势:
- 动态计算图(Define-by-Run)
- 灵活的自动求导机制(autograd)
- 直观的模块化网络构建方式(torch.nn)
让我们一个个来看它们是如何赋能强化学习开发的。
🌀 动态计算图:让AI也能“即兴发挥”
传统静态图框架(如早期 TensorFlow)要求你先画好整张计算流程图,再运行。这就像是拍电影必须严格按照剧本走,不能临时改台词。
而 PyTorch 是“边跑边画”,每执行一次前向传播,就实时构建一次计算图。这种 动态图机制 特别适合以下场景:
- 不定长序列(如不同长度的游戏回合)
- 条件分支(if/else 控制流)
- RNN 展开步数可变
- 多智能体异步更新
这意味着你在写代码时可以像普通Python一样自由使用 for , if , while ,而不用担心图结构断裂。
来看看一个简单的 Q-Network 实现:
import torch
import torch.nn as nn
class QNetwork(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.fc = nn.Sequential(
nn.Linear(state_dim, 64),
nn.ReLU(),
nn.Linear(64, 64),
nn.ReLU(),
nn.Linear(64, action_dim)
)
def forward(self, x):
return self.fc(x)
# 使用示例
net = QNetwork(4, 2)
state = torch.randn(1, 4) # 模拟CartPole状态
q_values = net(state) # 前向传播,自动记录计算路径
✅ 此时如果我们调用 loss.backward() ,PyTorch 会自动沿着这条动态生成的图反向传播梯度,无需任何额外配置。
graph TD
A[Input State] --> B[Linear Layer 1]
B --> C[ReLU Activation]
C --> D[Linear Layer 2]
D --> E[ReLU Activation]
E --> F[Output Q-values]
F --> G[Gather Selected Action]
G --> H[Compute Loss (MSE)]
H --> I[Backward Pass]
I --> J[Update Gradients]
J --> K[Optimizer Step]
这张图清晰展示了从输入到参数更新的完整闭环。每一个操作都被追踪并构成梯度流动的基础。
🛠 autograd:反向传播的幕后英雄
PyTorch 的自动微分引擎叫做 torch.autograd ,它是实现梯度下降的核心工具。
它的原理很简单:只要张量设置了 requires_grad=True ,所有对其的操作都会被记录下来,形成一张“tape”式的计算图。
来看一个策略梯度的例子:
import torch.distributions as dist
class GaussianPolicy(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.fc_mean = nn.Linear(state_dim, action_dim)
self.fc_std = nn.Linear(state_dim, action_dim)
def forward(self, state):
mean = torch.tanh(self.fc_mean(state))
std = torch.exp(self.fc_std(state)) + 1e-6 # 保证标准差>0
return mean, std
# 采样动作并计算log概率
policy = GaussianPolicy(4, 1)
state = torch.randn(1, 4, requires_grad=True)
mean, std = policy(state)
dist = dist.Normal(mean, std)
action = dist.sample() # 重参数化采样
log_prob = dist.log_prob(action)
# 构造损失(负期望回报)
advantage = torch.tensor([2.0])
loss = -(log_prob * advantage).sum()
loss.backward() # 自动求导!
🔍 关键点解析:
| 操作 | 是否可导 | 说明 |
|---|---|---|
tanh |
✅ | 内置激活函数,支持梯度回传 |
exp |
✅ | 防止标准差≤0 |
Normal.log_prob |
✅ | 概率库支持自动微分 |
sample() |
✅(通过重参数化) | 如 $N(\mu,\sigma)=\mu+\epsilon\cdot\sigma, \epsilon\sim N(0,1)$ |
⚠️ 注意!以下操作会导致 梯度断裂 ,应避免在前向传播中使用:
- .item() → 把tensor转成Python标量
- .numpy() → 转NumPy数组
- torch.argmax() → 返回index而非soft值
替代方案:
- 用 Gumbel-Softmax 近似 argmax
- 推理阶段再使用 .item()
🧱 torch.nn:快速搭建函数逼近器
当状态空间太大时(比如图像),我们不能再用表格存储Q值了。这时候就需要神经网络作为 函数逼近器(Function Approximator) 来泛化未知状态。
PyTorch 的 torch.nn 提供了极其便捷的模块化接口。
全连接网络(MLP)——适用于低维状态
class MLPQNet(nn.Module):
def __init__(self, input_dim, hidden_dims, n_actions):
super().__init__()
layers = []
prev_dim = input_dim
for h_dim in hidden_dims:
layers.extend([
nn.Linear(prev_dim, h_dim),
nn.ReLU()
])
prev_dim = h_dim
layers.append(nn.Linear(prev_dim, n_actions))
self.network = nn.Sequential(*layers)
def forward(self, x):
return self.network(x)
卷积网络(CNN)——处理图像输入
class CNNQNet(nn.Module):
def __init__(self, in_channels, n_actions):
super().__init__()
self.features = nn.Sequential(
nn.Conv2d(in_channels, 32, kernel_size=8, stride=4),
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=4, stride=2),
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, stride=1),
nn.ReLU()
)
self.classifier = nn.Sequential(
nn.Linear(64 * 7 * 7, 512),
nn.ReLU(),
nn.Linear(512, n_actions)
)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, start_dim=1)
return self.classifier(x)
📊 参数估算:
| 层 | 输入 | 输出 | 参数量 |
|---|---|---|---|
| Conv2d (8×8, s=4) | 4×84×84 | 32×20×20 | ~32K |
| Conv2d (4×4, s=2) | 32×20×20 | 64×9×9 | ~131K |
| Conv2d (3×3, s=1) | 64×9×9 | 64×7×7 | ~368K |
| Linear (3136→512) | 3136 | 512 | ~1.6M |
| Linear (512→n_actions) | 512 | n_actions | ~n×512 |
总计约 210万参数 ,轻松跑在现代GPU上!
graph LR
Input[Image Input 4x84x84] --> Conv1[Conv2d: 32 filters]
Conv1 --> ReLU1[ReLU]
ReLU1 --> Conv2[Conv2d: 64 filters]
Conv2 --> ReLU2[ReLU]
ReLU2 --> Conv3[Conv2d: 64 filters]
Conv3 --> ReLU3[ReLU]
ReLU3 --> Flatten[Flatten to 3136-dim]
Flatten --> FC1[Linear 512]
FC1 --> ReLU4[ReLU]
ReLU4 --> Output[Output Q-values]
🎯 从零实现 Q-Learning:让AI学会平衡杆!
我们现在要动手做一个经典实验:用 Q-Learning 解决 CartPole-v1 任务。
目标很简单:控制一辆小车左右移动,使得顶上的杆子不倒。每坚持一帧得+1分,最长500帧。
第一步:离散化连续状态
原始状态是4维连续变量:
[position, velocity, angle, angular_velocity]
我们要把它“切块”成有限区间。例如:
position_bins = np.linspace(-2.4, 2.4, 6)
velocity_bins = np.linspace(-4.0, 4.0, 6)
angle_bins = np.linspace(-0.2095, 0.2095, 6)
angular_velocity_bins = np.linspace(-4.0, 4.0, 6)
state_bins = [position_bins, velocity_bins, angle_bins, angular_velocity_bins]
这样整个Q表大小为 (6,6,6,6,2) ≈ 2592 个条目,完全可以放进内存。
第二步:编写QTable类
class QTable:
def __init__(self, state_bins, n_actions):
self.state_bins = state_bins
self.n_actions = n_actions
q_shape = [len(bins) for bins in state_bins] + [n_actions]
self.q_values = torch.zeros(q_shape) # 用Tensor代替dict!
def digitize_state(self, state):
indices = [
np.digitize(s, self.state_bins[i]) - 1
for i, s in enumerate(state)
]
for i in range(len(indices)):
indices[i] = np.clip(indices[i], 0, self.q_values.shape[i]-1)
return tuple(indices)
def get_q_value(self, state):
idx = self.digitize_state(state)
return self.q_values[idx]
def update(self, state, action, target, alpha=0.1):
idx = self.digitize_state(state)
old_val = self.q_values[idx][action]
self.q_values[idx][action] += alpha * (target - old_val)
✨ 亮点:我们用了 PyTorch张量 替代传统字典,好处多多:
- 支持批量操作
- 可迁移至GPU
- 易于集成到神经网络训练流程
第三步:完整训练循环
env = gym.make('CartPole-v1')
q_table = QTable(state_bins, env.action_space.n)
for episode in range(5000):
state, _ = env.reset()
eps = max(0.01, 1.0 * 0.995**episode) # ε衰减
total_reward = 0
while True:
action = epsilon_greedy_policy(q_table, state, eps, 2)
next_state, reward, done, truncated, _ = env.step(action)
done = done or truncated
with torch.no_grad():
next_q = q_table.get_q_value(next_state)
max_next_q = torch.max(next_q).item()
target = reward + 0.99 * max_next_q * (not done)
q_table.update(state, action, target)
state = next_state
total_reward += reward
if done:
break
if episode % 500 == 0:
print(f"Episode {episode}, Reward: {total_reward}")
📈 实验结果:大约3000轮后就能稳定拿到满分500分!🎉
flowchart TD
A[原始连续状态] --> B{是否离散化?}
B -- 是 --> C[分箱处理 → 得到索引]
C --> D[多维张量索引访问Q值]
B -- 否 --> E[使用Embedding层映射]
E --> F[输出动作价值向量]
D --> G[选择最大Q值动作]
F --> G
G --> H[执行动作获取奖励]
H --> I[计算TD误差]
I --> J[更新Q表或Embedding参数]
🔄 SARSA vs Q-Learning:激进派 vs 保守派
同样是时序差分算法,SARSA 和 Q-Learning 却有着截然不同的哲学观。
| 算法 | 更新目标 | 策略类型 | 特点 |
|---|---|---|---|
| Q-Learning | $ r + \gamma \max_a Q(s’,a) $ | 离策略(Off-policy) | 激进,追求理论最优 |
| SARSA | $ r + \gamma Q(s’,a’) $ | 在策略(On-policy) | 保守,考虑实际行为 |
📌 举个生动例子:悬崖漫步(Cliff Walking)
S . . . . . . G
. . . . . . . .
. . . . . . . .
C C C C C C C .
- S:起点,G:终点,C:悬崖(大惩罚)
- Q-Learning 会紧贴悬崖走捷径(因为假设总能选最优动作)
- SARSA 因担心ε-greedy导致意外跌落,会选择绕远路
所以如果你的任务对安全性要求极高(比如自动驾驶), SARSA 可能更合适 !
SARSA 训练主循环
def train_sarsa():
env = gym.make('CartPole-v1')
q_table = QTable(state_bins, 2)
gamma, alpha, eps = 0.99, 0.1, 1.0
for episode in range(3000):
state, _ = env.reset()
action = epsilon_greedy_policy(q_table, state, eps, 2)
eps *= 0.995; eps = max(eps, 0.01)
while True:
next_state, reward, done, truncated, _ = env.step(action)
done = done or truncated
next_action = epsilon_greedy_policy(q_table, next_state, eps, 2)
current_idx = q_table.digitize_state(state)
next_idx = q_table.digitize_state(next_state)
target = reward + gamma * q_table.q_values[next_idx][next_action] * (not done)
q_table.q_values[current_idx][action] += alpha * (target - q_table.q_values[current_idx][action])
state, action = next_state, next_action
if done: break
📊 对比实验显示:
| 指标 | Q-Learning | SARSA |
|---|---|---|
| 初期学习速度 | 快 ⚡ | 稍慢 🐢 |
| 最终性能 | 高 🚀 | 略低但更稳 🛡️ |
| 抗噪能力 | 差 ❌ | 强 ✅ |
graph LR
subgraph Learning Behavior
A[Q-Learning] -->|Greedy Target| B((更快收敛))
C[SARSA] -->|Realistic Update| D((更平滑过渡))
end
subgraph Risk Sensitivity
B --> E[易受随机扰动影响]
D --> F[适应策略波动能力强]
end
🧩 经验回放(Experience Replay):打破时间枷锁
传统的Q-Learning按顺序处理数据,带来严重问题:
样本高度相关!
连续几帧的画面几乎一样,破坏了SGD所需的独立同分布(i.i.d.)假设,导致训练不稳定甚至发散。
解决方案: 经验回放(Experience Replay)
思路很简单:把历史经验存起来,然后 随机抽样 进行训练。
FIFO 缓冲区设计
class ReplayBuffer:
def __init__(self, capacity):
self.capacity = capacity
self.buffer = []
self.position = 0
def push(self, state, action, reward, next_state, done):
if len(self.buffer) < self.capacity:
self.buffer.append(None)
self.buffer[self.position] = (
torch.FloatTensor(state),
torch.LongTensor([action]),
torch.FloatTensor([reward]),
torch.FloatTensor(next_state),
torch.FloatTensor([done])
)
self.position = (self.position + 1) % self.capacity
def sample(self, batch_size):
batch = random.sample(self.buffer, batch_size)
state, action, reward, next_state, done = zip(*batch)
return (
torch.stack(state),
torch.cat(action),
torch.cat(reward),
torch.stack(next_state),
torch.cat(done)
)
🎯 三大优势:
- 打破时间相关性 → 数据更符合i.i.d.
- 提高样本利用率 → 每条经验可用多次
- 平滑策略变化 → 避免因突变引发震荡
graph TB
A[Agent与Environment交互] --> B[生成经验(s,a,r,s',a')]
B --> C[存入Replay Buffer]
C --> D{Buffer满?}
D -- 是 --> E[随机采样Batch]
D -- 否 --> F[继续收集]
E --> G[计算TD Loss]
G --> H[反向传播更新Q网络]
H --> I[软更新Target Network]
I --> A
🚀 Deep Q-Network(DQN):开启深度强化学习时代
2015年,DeepMind 发表里程碑论文《Human-level control through deep reinforcement learning》,首次将CNN+DQN应用于Atari游戏,震惊世界。
但直接用神经网络拟合Q值会有两个致命问题:
- 训练不稳定 :目标值随网络更新不断变化 → “移动靶问题”
- Q值过估计 :max操作放大噪声 → 导致策略偏差
为此,作者提出两大核心技术:
🔒 技术一:目标网络(Target Network)
解决“移动靶”问题。
做法:复制一份网络参数 $\theta^-$,固定一段时间,用于计算目标值:
$$
y = r + \gamma \max_{a’} Q(s’,a’; \theta^-)
$$
实现方式有两种:
硬更新(Hard Update)
每隔一定步数同步一次:
def update_target():
target_net.load_state_dict(policy_net.state_dict())
软更新(Soft Update)
每步缓慢追踪:
TAU = 0.001
def soft_update():
for tp, pp in zip(target_net.parameters(), policy_net.parameters()):
tp.data.copy_(TAU * pp.data + (1 - TAU) * tp.data)
推荐后者,更平稳!
🔍 技术二:Double DQN —— 抑制过估计
标准DQN中,总是选取使Q值最大的动作,容易放大正向噪声。
Double DQN 将动作选择与价值评估解耦:
$$
y = r + \gamma \cdot Q_{\text{target}}\left(s’, \arg\max_{a’} Q_{\text{policy}}(s’,a’)\right)
$$
即:用主网络选动作,目标网络估值。
with torch.no_grad():
next_actions = policy_net(next_states).max(1)[1].unsqueeze(1)
max_next_qs = target_net(next_states).gather(1, next_actions).squeeze()
targets = rewards + gamma * max_next_qs * (1 - dones)
🛠 Dueling DQN:分解Q值提升泛化
进一步将Q值拆解为两部分:
$$
Q(s,a) = V(s) + \left(A(s,a) - \frac{1}{|A|}\sum_{a’}A(s,a’)\right)
$$
- $V(s)$:状态价值,不管做什么都有的基础分
- $A(s,a)$:优势函数,衡量某个动作有多好
这样即使某些动作差异不大,也能准确判断整体局势好坏。
class DuelingDQN(nn.Module):
def __init__(self, input_dim, n_actions):
super().__init__()
self.feature = nn.Linear(input_dim, 128)
self.advantage = nn.Linear(128, n_actions)
self.value = nn.Linear(128, 1)
def forward(self, x):
feat = torch.relu(self.feature(x))
val = self.value(feat)
adv = self.advantage(feat)
return val + (adv - adv.mean(dim=1, keepdim=True))
🤖 Actor-Critic 与 A2C:降低方差的艺术
前面的方法要么基于值函数(如DQN),要么纯策略梯度(如REINFORCE)。Actor-Critic 则是两者的结合体:
- Actor :负责执行策略 $\pi(a|s)$
- Critic :评估当前状态价值 $V(s)$,提供指导信号
其核心优势在于: 用Critic输出作为基线,显著降低策略梯度方差 。
A2C 实现详解
class ActorCritic(nn.Module):
def __init__(self, input_dim, n_actions):
super().__init__()
self.shared = nn.Sequential(
nn.Linear(input_dim, 64),
nn.ReLU(),
nn.Linear(64, 64),
nn.ReLU()
)
self.actor = nn.Linear(64, n_actions)
self.critic = nn.Linear(64, 1)
def forward(self, x):
shared_out = self.shared(x)
logits = self.actor(shared_out)
value = self.critic(shared_out)
return logits, value
# 训练逻辑
logits, values = model(states)
probs = torch.softmax(logits, dim=-1)
dist = Categorical(probs)
actions = dist.sample()
log_probs = dist.log_prob(actions)
# 计算优势:A = G - V(s)
returns = compute_nstep_returns(rewards, next_values, dones)
advantages = returns - values.squeeze()
# 策略损失
policy_loss = -(log_probs * advantages.detach()).mean()
# 价值损失
value_loss = nn.MSELoss()(values.squeeze(), returns)
# 总损失
total_loss = policy_loss + 0.5 * value_loss
🎯 A2C 成为后续 IMPALA、PPO 等算法的重要基石。
graph LR
S[State s] --> A1[Actor π(a|s)]
S --> C1[Critic V(s)]
A1 --> A[Action a]
Env[(Environment)] --> R[Reward r]
Env --> S'[Next State s']
S' --> C2[Critic V(s')]
R & C2 --> G[Compute Target G = r + γV(s')]
G & C1 --> Adv[Advantage A = G - V(s)]
Adv & A1 --> L[Loss = -logπ·A + (V-G)²]
L --> Opt[Backward Update]
🛠 PPO:当前最实用的策略优化算法
Proximal Policy Optimization(PPO)由OpenAI提出,因其简单高效、鲁棒性强,已成为工业界主流。
💡 核心思想:限制更新幅度
不像TRPO那样复杂约束,PPO采用 裁剪机制 (Clip)来防止策略突变。
定义概率比:
$$
r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)}
$$
损失函数:
$$
\mathcal{L}^{\text{CLIP}} = \mathbb{E}_t \left[ \min\left( r_t A_t,\ \text{clip}(r_t, 1-\epsilon, 1+\epsilon) A_t \right) \right]
$$
def ppo_loss(ratios, advantages, clip_epsilon=0.2):
surr1 = ratios * advantages
surr2 = torch.clamp(ratios, 1 - clip_epsilon, 1 + clip_epsilon) * advantages
return -torch.min(surr1, surr2).mean()
🔧 超参数调优指南
| 参数 | 推荐值 | 说明 |
|---|---|---|
clip_epsilon |
0.1~0.3 | 越小越保守 |
learning_rate |
3e-4 | Adam常用 |
entropy_coef |
0.01 | 鼓励探索 |
update_epochs |
3~10 | 多轮更新同一batch |
还可以加入KL散度监控,动态调整学习率:
kl_div = kl_divergence(old_dist, new_dist)
if kl_div > 0.02: lr *= 0.5
elif kl_div < 0.005: lr *= 1.1
🎮 DDPG:专治连续动作空间
对于机器人控制、自动驾驶等任务,动作不再是离散的“上下左右”,而是连续的“方向盘转多少度”。
DDPG(Deep Deterministic Policy Gradient)为此而生。
🧩 网络结构
- Actor :确定性策略 $\mu(s;\theta^\mu)$,输出具体动作
- Critic :Q函数 $Q(s,a;\theta^Q)$,评估动作好坏
class DDPGActor(nn.Module):
def __init__(self, state_dim, action_dim, max_action):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 400),
nn.ReLU(),
nn.Linear(400, 300),
nn.ReLU(),
nn.Linear(300, action_dim),
nn.Tanh()
)
self.max_action = max_action
def forward(self, state):
return self.max_action * self.net(state)
🌪 Ornstein-Uhlenbeck 噪声
不同于简单高斯噪声,OU噪声具有时间相关性,更适合物理系统中的持续扰动。
class OUNoise:
def __init__(self, size, mu=0., theta=0.15, sigma=0.2):
self.state = mu * np.ones(size)
self.theta = theta
self.sigma = sigma
def sample(self):
dx = self.theta * (self.mu - self.state) + self.sigma * np.random.randn(*self.state.shape)
self.state += dx
return self.state
配合软更新,形成稳定训练闭环。
graph TB
S --> Actor
Actor --> A
A --> Env
Env --> R & S'
S' --> TargetActor
TargetActor --> A'
S' --> Critic
A' --> Critic
Critic --> Q'
Q' --> Target [Target Q = R + γQ']
S --> CriticOnline
A --> CriticOnline
CriticOnline --> Q
Q & Target --> Loss
Loss --> UpdateCritic
UpdateCritic --> SoftUpdateTarget
🏗️ 工程化封装:打造通用RL训练框架
最后,我们将所有组件整合成一个可复用的系统。
class RLTrainer:
def __init__(self, env_name, agent, config):
self.env = gym.make(env_name)
self.agent = agent
self.config = config
self.episode_rewards = []
self.window = deque(maxlen=100)
def train(self):
for ep in range(self.config.max_episodes):
state, _ = self.env.reset()
total_reward = 0
for step in range(self.config.max_steps):
action = self.agent.act(state)
next_state, reward, done, truncated, _ = self.env.step(action)
terminated = done or truncated
self.agent.store(state, action, reward, next_state, terminated)
self.agent.learn() # 异步更新
state = next_state
total_reward += reward
if terminated: break
self.record_episode(total_reward)
self.maybe_save_model()
self.env.close()
搭配YAML配置文件,实现一键切换算法:
env_name: CartPole-v1
algorithm: PPO
max_episodes: 500
batch_size: 64
lr: 0.001
gamma: 0.99
🎯 总结一句话 :
从Q-Learning到PPO,从理论到代码,我们见证了强化学习如何一步步走向成熟。而PyTorch提供的灵活性与强大生态,正是这场革命背后的隐形推手。
希望这篇长达万字的干货能帮你打通任督二脉!如果你觉得有用,别忘了点赞收藏~🌟
简介:强化学习作为人工智能的重要分支,致力于让智能体通过与环境交互学习最优策略以最大化长期奖励。PyTorch凭借其动态计算图和强大的自动求导机制,成为实现强化学习算法的首选框架。本项目“Python-PyTorch实现多种增强学习算法”系统性地展示了如何使用PyTorch构建和训练主流RL算法,涵盖Q-Learning、SARSA、DQN、Policy Gradient(如A2C、PPO)以及适用于连续动作空间的DDPG等。结合OpenAI Gym环境进行实验,项目包含完整的模型定义、训练流程、经验回放机制与性能评估方法,帮助开发者掌握从理论到代码落地的全过程,适用于游戏AI、机器人控制、自动驾驶等前沿领域的研究与应用。
更多推荐




所有评论(0)