一、先搞明白你的场景到底在“玩什么”

很多朋友一上来就问我:“强化学习该用哪个算法?”其实这个问题就像问“该买什么车”一样,不先搞清楚你是跑山路还是走城市,选出来的东西大概率会翻车。强化学习算法的选型,第一件事不是看算法,而是把你的应用场景拆开看清楚。场景里最关键的三个问题,决定了你后面一大半的选型方向。

1.1 动作是连续的还是离散的

动作空间是“离散”还是“连续”,这是最粗的一条分界线。离散动作就像是遥控器上的按钮,只有上、下、左、右那么几个选项,比如围棋落子、游戏按键。连续动作则像方向盘和油门,理论上可以打出任意角度、任意力度,比如机器人关节控制、自动驾驶转向。

如果你连这个问题都没确认,后面的算法选择就无从说起。DQN这类基于价值的方法天生擅长处理离散动作,而像SAC、TD3这类基于策略梯度的方法则在连续动作上大放异彩。当然也有例外,但绝大多数场景都符合这个规律。

1.2 奖励是稀疏还是密集

奖励信号就像游戏里的分数提示。你每一步都能看到分数变化,那就是密集奖励;如果只有走完全程才知道输赢,那就是稀疏奖励。比如训练机器人拿杯子,如果每靠近杯子一点就给个“小奖励”,那是密集奖励;如果只等杯子被拿起来才给奖励,那是稀疏奖励。

稀疏奖励对算法要求极高,因为算法很难从“做了一堆事却什么反馈都没有”的状态里学到东西。这时候需要引入奖励塑形、课程学习等技巧,或者选用更适合探索的算法。而密集奖励相对友好,P贪心探索多一点就能跑起来。

1.3 环境是稳定还是随机

你的环境是不是每次都一样?比如下围棋,规则固定,你走一步它就回一步;比如玩俄罗斯方块,方块总是随机掉落。环境随机性越大,算法需要学到的“抗干扰”能力就越强。如果环境里有大量随机因素,你还得考虑使用可以建模不确定性的方法,或者干脆多跑几次实验,取平均效果来评估。

二、常见强化学习算法都是什么脾气

当你把场景的底细摸清楚后,再来看算法,就会觉得它们不再是一个个黑盒子,而是有自己性格的工具。下面我把几个主流算法用大白话给你捋一捋。

2.1 DQN:适合离散动作的“老实人”

DQN的全称是Deep Q-Network。它的核心思路是学一个“状态-动作”的价值表,然后每次选价值最高的动作。它特别像“菜市场比价”:先记住每种菜在哪个摊位最便宜,下次直接去最便宜的摊位买。

DQN的优点是在离散动作场景里非常稳定,实现也不复杂。缺点是无法直接处理连续动作,因为你没法对无限多个动作逐一比价。另外,DQN的Q值估计容易被高估,所以后来又有了Double DQN、Dueling DQN等改进版本。如果你要做一个“选按钮”类的小游戏AI,DQN基本就是入门首选。

2.2 Policy Gradient:适合连续动作的“直性子”

策略梯度家族的思想是:不求算价值,直接学一个策略,也就是“看到什么状态就输出什么动作”。它像是一个凭直觉办事的人,不纠结每个动作的得分,只按概率采样。

这种方法的优点是能处理连续动作,而且策略本身就是随机的,天然适合需要随机探索的场景。缺点是方差大,训练起来像过山车,经常一会儿进步一会儿倒退。为了稳住它,后面才出现了PPO等改进。

2.3 PPO:稳定可靠的大众选择

PPO(Proximal Policy Optimization)是目前工业界用得最多的算法之一。它相当于给策略梯度加了一道“保险丝”,每次更新不能偏离旧策略太远,防止一不留神就把好策略给改坏了。因为这个特性,PPO在多种环境下都表现得很稳定,所以它也成了很多大厂做推荐系统、控制策略时的“默认按钮”。PPO的核心技术是裁剪目标函数,配合GAE做优势估计,简单说就是既能让策略更新,又不会步子迈太大。

PPO的适用场景很广:离散、连续都能做,只要你不太追求极致的样本效率,选它基本不会翻大车。但PPO的样本效率相对较低,如果你的真实环境跑一次实验很贵,那就要慎重。

2.4 DDPG与TD3:连续控制的硬汉

DDPG(Deep Deterministic Policy Gradient)是“确定性策略梯度”的深度版本,意思是它输出的动作是确定的,不加随机。它用一个演员网络输出动作,用一个评论家网络来评估这个动作好不好。这样做的优点是连续控制比较精准,但缺点是训练不稳定,经常因为过度估计Q值而崩掉。

TD3是对DDPG的“补丁”,用了双Q网络、目标策略平滑等一堆技巧,把DDPG的毛病修了个七七八八。如果你要做机械臂控制、无人机悬停这类连续控制任务,TD3往往比DDPG更可靠。

2.5 SAC:随机策略里的多面手

SAC(Soft Actor-Critic)是目前很多连续控制任务上的“优等生”。它和DDPG最大的不同是:SAC让策略保持随机性,同时把“熵”(也就是随机程度)当成奖励的一部分,鼓励智能体多探索。你把它想象成一个“爱尝鲜”的人,不总走老路,但又能不断发现新路。SAC的样本效率比PPO高,稳定性也不差,所以如果你有足够的算力,连续动作场景可以优先试SAC。

三、场景与算法的匹配评估方法

知道了场景和算法的性格,接下来就得有套“相亲”方法,让两边能坐下来好好匹配。匹配不是一拍脑袋,而是有几个维度可以具体打分。

3.1 先画一张决策流程表

我在正式动手写代码前,会先画一张简单的表,把场景的关键属性列出来。你可以参考下面这个格式:

场景属性 你的场景取值 影响哪些算法选择
动作空间 离散 / 连续 DQN vs SAC/PPO
奖励密度 密集 / 稀疏 需要加奖励塑形或换算法
环境随机性 低 / 中 / 高 影响评估次数与稳定性要求
样本获取成本 便宜 / 昂贵 决定能否用PPO这种低效算法
实时性要求 高 / 低 决定模型能否庞大

比如,你的场景是“用AI控制游戏角色出招”,动作离散、奖励每一帧都有、环境固定,那么DQN就是第一选择。如果换成“让机械臂把螺丝拧进去”,动作连续、奖励可能在拧紧瞬间才给,那就得考虑SAC或TD3,甚至要额外设计奖励函数。

3.2 从动作空间开始排除

动作空间是最硬的筛选条件。如果你的动作是离散的,那可以先排除DDPG、TD3、SAC等以连续动作为主的算法,因为它们要么不能做,要么做起来很别扭。反过来,如果你的动作是连续的,DQN基本就不能用了,只能从PPO、SAC、TD3、DDPG里挑。

3.3 看奖励信号是否“密”

奖励信号密度决定你可能需要做多少额外工作。如果是稀疏奖励,SAC自带的探索机制通常比DDPG更合适,因为它的随机策略会不断试错。PPO也能用,但需要更长的训练时间。如果是密集奖励,DQN或者PPO就很省心。

另外一个思路是:如果奖励特别稀疏,你可以先考虑改环境,比如给每步一个小的距离奖励,比纠结算法更有效。

3.4 对比样本效率与训练成本

样本效率是指“每用一条数据,能学多少东西”。SAC和TD3属于高样本效率的算法,适合那种跑一步很慢的真实物理环境。PPO样本效率低,但它实现简单、超参数少,适合在模拟器里疯跑。如果你的环境是纯软件仿真,跑一千万步不要钱,那PPO是可以的;如果你的环境是机器人本体,每跑一步都会有机械磨损,那就尽量选SAC这类高效率算法。

四、一个完整的示例:用Python对比离散和连续场景

下面我用一个统一的Python示例来演示,怎么根据场景选算法。这里我会用到 gymnasium 环境库和 stable-baselines3 这个算法库。请确保你的环境里已经装好了这些库,如果没有,可以使用 pip install gymnasium stable-baselines3 来安装。注意,这个示例统一使用Python技术栈,下面的代码都在Python环境中运行。

4.1 准备环境

我们引入两个经典环境:CartPole-v1(离散动作)和 Pendulum-v1(连续动作)。CartPole的任务是左右移动小车保持杆子竖直,动作只有向左和向右。Pendulum的任务是摆动机器人的关节让钟摆立起来,动作是施加的力矩,取值范围在-2到2之间,是连续的。

# 技术栈:Python + gymnasium + stable-baselines3
import gymnasium as gym
import numpy as np
from stable_baselines3 import DQN, SAC
from stable_baselines3.common.evaluation import evaluate_policy

# ---------------- 离散场景:CartPole ----------------
def train_dqn_on_cartpole():
    """
    用DQN训练CartPole。
    CartPole的动作空间是离散的,只有0和1两个动作。
    """
    env = gym.make("CartPole-v1")
    # 创建DQN模型,MlpPolicy表示用多层感知机作为策略网络
    model = DQN(
        "MlpPolicy",
        env,
        learning_rate=1e-3,
        buffer_size=50000,          # 经验回放缓冲区大小
        learning_starts=1000,      # 先随机探索1000步再开始学习
        batch_size=32,
        tau=1.0,                   # 目标网络软更新系数
        gamma=0.99,                # 奖励折扣率
        train_freq=4,              # 每4步训练一次
        target_update_interval=1000, # 目标网络更新间隔
        seed=42,
        verbose=0
    )
    # 训练总共1万步
    model.learn(total_timesteps=10000)
    # 用确定性策略评估表现
    mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=10)
    print(f"DQN on CartPole mean reward: {mean_reward:.2f}")
    env.close()

# ---------------- 连续场景:Pendulum ----------------
def train_sac_on_pendulum():
    """
    用SAC训练Pendulum。
    Pendulum的动作空间是连续的一维力矩,范围在[-2, 2]。
    """
    env = gym.make("Pendulum-v1")
    # 创建SAC模型,注意动作空间是连续的
    model = SAC(
        "MlpPolicy",
        env,
        learning_rate=3e-4,
        buffer_size=100000,
        learning_starts=1000,
        batch_size=128,
        tau=0.005,
        gamma=0.98,
        train_freq=1,
        gradient_steps=1,
        seed=42,
        verbose=0
    )
    # 训练2万步
    model.learn(total_timesteps=20000)
    # SAC返回的奖励通常是负值,因为Pendulum的reward是距离的负值
    mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=10)
    print(f"SAC on Pendulum mean reward: {mean_reward:.2f}")
    env.close()

# 如果直接运行本脚本,两个训练任务会依次执行
if __name__ == "__main__":
    print("开始训练离散场景的DQN...")
    train_dqn_on_cartpole()
    print("开始训练连续场景的SAC...")
    train_sac_on_pendulum()

上面这段代码的注释已经写得很清楚。你可能会注意到,DQN和SAC的学习率、缓冲区大小都不太一样。这并不是我随便写的,而是根据这两个算法各自常用的超参范围来选的。DQN对学习率比较敏感,SAC则习惯用稍低的学习率并配合较大的缓冲区。

4.2 运行结果怎么看

运行完上面的代码,你会看到类似下面的输出:

# 示例输出
开始训练离散场景的DQN...
DQN on CartPole mean reward: 28.70
开始训练连续场景的SAC...
SAC on Pendulum mean reward: -322.74

CartPole的最高分是200,如果你只训练1万步,DQN可能只学到四五十步的水平。这说明训练步数不够,或者超参数需要调整。Pendulum的奖励最大值是0,也就是完全立住。得到“-322”说明它已经能晃起来但还没完全立住。不过没关系,重点是教会你“离散用DQN,连续用SAC”的对应关系,以及怎么在代码层面做区分。

当你实际把DQN用在连续动作环境上时,会直接报错,因为 stable-baselines3 会检查动作空间类型。反过来,如果你把SAC用在离散动作上,同样也会报错。这其实就是框架在帮你做第一道选型过滤。

4.3 如果还想换算法,怎么改代码

假设你觉得Pendulum表现不够好,想换成TD3试试。你只需要把模型名从SAC换成TD3,同时注意TD3的超参略有不同。下面是一个简单的对照示例:

# 技术栈:Python + gymnasium + stable-baselines3
from stable_baselines3 import TD3

# 把SAC替换为TD3,其余结构几乎一样
env = gym.make("Pendulum-v1")
model = TD3(
    "MlpPolicy",
    env,
    learning_rate=1e-3,
    buffer_size=100000,
    learning_starts=1000,
    batch_size=128,
    tau=0.005,
    gamma=0.98,
    train_freq=1,
    gradient_steps=1,
    seed=42,
    verbose=0
)
model.learn(total_timesteps=20000)

TD3和SAC在超参数上有些细微差别,比如学习率一般设成1e-3比3e-4要好使一点。但如果你不想深究,先用同等条件跑一遍对比。这就是选型评估的常态:先用默认超参把几个能用的算法各跑一遍,看哪个效果最好,再重点调优。

五、注意事项和常见坑

选型和训练过程中,我见过太多人踩坑。下面这几个坑是高频雷区,你可得小心。

5.1 别只看论文分数

很多算法论文里给的曲线都是一万分调参后的结果,直接拿默认参数跑未必那么神。论文里的实验环境通常只有几个经典游戏,和你的业务场景可能差了十万八千里。正确做法是把自己的场景当第一标准,做一组“算法All vs All”的对比试验,再选最优。

5.2 随机种子和复现问题

强化学习训练非常受随机种子影响,同一套代码换个随机数种子,最终效果可能差出一倍。所以在做算法对比时,一定要在多个随机种子下分别跑,然后取平均和方差。否则你选出来的算法可能只是“运气好”。

下面是一个用多个种子跑实验的示例:

# 技术栈:Python + gymnasium + stable-baselines3
import gymnasium as gym
from stable_baselines3 import PPO

def test_with_seeds(seed_list):
    results = []
    for seed in seed_list:
        env = gym.make("CartPole-v1")
        model = PPO("MlpPolicy", env, seed=seed, verbose=0)
        model.learn(total_timesteps=5000)
        mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=5)
        results.append(mean_reward)
        env.close()
    # 计算平均和标准差
    arr = np.array(results)
    print(f"每个种子的奖励: {arr}")
    print(f"平均奖励: {arr.mean():.2f},标准差: {arr.std():.2f}")

# 使用5个不同的种子
test_with_seeds([10, 20, 30, 40, 50])

你会发现,标准差非常大的时候,说明算法在该场景下不稳定,这时候就算平均分再高,也要警惕。

5.3 调参的时间和耐心

很多算法对超参数很敏感,比如DQN的学习率、SAC的熵系数、PPO的裁剪范围。你不可能指望一次就把参数调好。我的建议是:先固定预算,用网格搜索或随机搜索跑几十个超参组合,再从中挑出表现最好的那一组。但要注意,超参搜索本身也很贵,所以可以先用小步数粗筛,再用好参数跑长训练。

5.4 早停和评估的坑

训练过程中,模型有可能先变好又变坏,这叫“灾难性遗忘”。你不能只看最后一次保存的模型,而应该定期保存检查点,然后对每个检查点都去做评估。最优解往往出现在训练中期,而不是训练结束。

# 技术栈:Python + gymnasium + stable-baselines3
# 在训练过程中定期保存模型
for step in range(10):
    model.learn(total_timesteps=5000, reset_num_timesteps=False)
    mean_reward, _ = evaluate_policy(model, env, n_eval_episodes=5)
    print(f"Step {step+1}, reward {mean_reward:.2f}")
    # 如果奖励达到阈值就提前终止
    if mean_reward > 150:
        model.save("best_model")
        break

这样你能看到整个训练曲线,避免因为最后几步效果变差而误杀一个好模型。

六、文章总结

强化学习算法选型,本质上是一个“场景属性”和“算法性格”的匹配过程。动作空间是最硬的门槛,离散动作找DQN,连续动作找SAC、PPO或TD3。奖励密度决定你需不需要额外设计奖励函数,样本获取成本决定你能不能承受低效率算法。不要迷信论文里的SOTA,所有算法都要在你自己场景的多随机种子实验里见真章。

真正靠谱的决策流程是:先确认场景的动作空间、奖励密度、环境随机性和样本成本,然后列出一两个候选算法,接着用统一的超参配置跑一个短时间的小实验,看趋势后扩大训练步数,最后在多个随机种子下做完整评估。这样走到最后,你选出的算法不仅效果好,而且稳健性也有保障。希望这篇博客能帮你少走些弯路,让每一次选型都有据可依,而不是靠抛硬币决定。