本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:强化学习作为人工智能的重要分支,致力于让智能体通过与环境交互学习最优策略以最大化长期奖励。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 几乎成了事实标准 。为什么?

因为它天生具备三大优势:

  1. 动态计算图(Define-by-Run)
  2. 灵活的自动求导机制(autograd)
  3. 直观的模块化网络构建方式(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)
        )

🎯 三大优势:

  1. 打破时间相关性 → 数据更符合i.i.d.
  2. 提高样本利用率 → 每条经验可用多次
  3. 平滑策略变化 → 避免因突变引发震荡
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值会有两个致命问题:

  1. 训练不稳定 :目标值随网络更新不断变化 → “移动靶问题”
  2. 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提供的灵活性与强大生态,正是这场革命背后的隐形推手。

希望这篇长达万字的干货能帮你打通任督二脉!如果你觉得有用,别忘了点赞收藏~🌟

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:强化学习作为人工智能的重要分支,致力于让智能体通过与环境交互学习最优策略以最大化长期奖励。PyTorch凭借其动态计算图和强大的自动求导机制,成为实现强化学习算法的首选框架。本项目“Python-PyTorch实现多种增强学习算法”系统性地展示了如何使用PyTorch构建和训练主流RL算法,涵盖Q-Learning、SARSA、DQN、Policy Gradient(如A2C、PPO)以及适用于连续动作空间的DDPG等。结合OpenAI Gym环境进行实验,项目包含完整的模型定义、训练流程、经验回放机制与性能评估方法,帮助开发者掌握从理论到代码落地的全过程,适用于游戏AI、机器人控制、自动驾驶等前沿领域的研究与应用。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐