简介:面向人工智能和机器学习学习者的MATLAB深度强化学习案例程序,涵盖DQN、PPO、DDPG、TD3等主流算法。包内21个文件,包含7张示意图、7个XML配置、5个MathML公式和2个Rels关系文件,共1.18MB,便于快速获取与本地实验。内容分为环境构建、算法训练、算法分析三部分:既有预设或自定义环境的搭建方式,也有参数调整与模型训练流程,还提供学习曲线、平均奖励等指标的可视化分析方法。通过对这些模块的逐项拆解,读者可以掌握强化学习智能体与交互环境的实现细节,理解状态空间处理、奖励函数设计以及策略优化思路。目前已有754人学习,尤其适合刚接触DRL的MATLAB用户作为入门到进阶的实操参考。借助该案例可完成从实验配置到结果评估的完整闭环,提升在人工智能与机器学习领域的建模和调试能力。
1. 为什么拖到MATLAB里做深度强化学习
深度强化学习的项目最常见的内耗不在算法本身,而在“环境怎么建模、训练数据落在哪、策略怎么交还给控制链路”。如果目标场景是机械臂、过程控制、能源调度这类物理对象,MATLAB 的强化学习工具箱把环境定义、Agent 构建、训练监控和 Simulink 部署收敛在同一套流程里,调试成本会比 Python 侧低不少。案例包里这份 DRL 教程正是按这个思路组织的:先自定义环境,再切 DQN、PPO、DDPG、TD3 四类算法,最后落回控制任务的闭环验证。适合已经跑过强化学习入门例子、现在想把 DRL 用到实物或仿真控制项目里的工程师;即使你对算法细节不熟,也能先按文件把训练流程跑通,再反推超参含义。
2. MATLAB强化学习环境构建与奖励函数设计
2.1 环境接口:reset与step是DRL的契约
强化学习工具箱并不关心你的被控对象是机械臂还是热力系统,它只认一个环境对象,而这个对象的两个方法就是整个训练循环的契约:reset负责把环境恢复到初始状态并返回观测,step接收动作、推进环境动力学、返回新观测、奖励和终止标志。定义一个自定义环境,通常从rl.env.MATLABEnvironment继承,然后实现这两个方法,初始代码可以踩的坑非常集中:
classdef SimpleNavEnv < rl.env.MATLABEnvironment % 自定义环境:状态为二维坐标,动作为离散的“左/停/右” properties State double = [0; 0]; % 当前观测 Reward double = 0; IsDone logical = false; end methods function env = SimpleNavEnv() % 定义观测维度和动作空间,Agent 创建时会依据这两项做检查 obsInfo = rlNumericSpec([2 1]); actInfo = rlFiniteSetSpec([-1 0 1]); env = env@rl.env.MATLABEnvironment(obsInfo, actInfo); end function [obs, reward, isdone] = reset(env) % 每回合从随机位置出发 env.State = [0.1; 0.9]; obs = env.State; reward = 0; isdone = false; end function [obs, reward, isdone] = step(env, action) % 这里是控对象的动态方程,实际项目替换成你的被控模型 env.State = env.State + [0; double(action)]; % 为一个靠近目标位置设计的密集奖励 dist = abs(env.State(2) - 0.5); reward = -dist; isdone = dist < 0.02; obs = env.State; env.Reward = reward; env.IsDone = isdone; end end end这个类里最关键的两个点:obsInfo和actInfo必须在构造函数里先定义,因为后续所有 Agent 创建、网络输入层推断、训练记录都要读取这两个对象;step里返回的三个值顺序不能写反,工具箱默认第一个是观测、第二个是即时奖励、第三个是否终止。很多人一开始会漏掉IsDone状态更新,导致训练永远走不完一集,从训练曲线上看就是回合长度恒等于MaxStepsPerEpisode。
2.2 奖励函数构造:从稀疏到密集
案例环境通常给你一个能跑的奖励公式,但换到自己的任务后,奖励设计往往是第一道坎。常见做法是先给稀疏奖励,比如只判断“最终是否到达目标点”,跑几百集看不到任何反馈;更建议先用密度奖励让 Agent 尽早学到梯度方向,再逐步稀疏化。上一个例子里reward = -dist就是在引导智能体往目标靠,绝对值小、量纲与状态一致,不会让 Q 值爆炸。需要注意四个容易出错的地方:第一,奖励量纲和网络输出层不匹配,比如 state 的范围是 0~1,奖励却给到几百,Critic 的输出会一直跟不上下游;第二,在isdone里同时放“成功”和“失败”条件,结果失败样本也被当成终止状态,值函数会被污染;第三,奖励函数里写了if分支但没覆盖所有动作,导致某些分支返回空值;第四,reward出现NaN时训练会静默发散,建议在step末尾加一句assert(~isnan(reward), 'reward is NaN')来快速定位。
2.3 案例包里的环境与文件对应关系
解压案例包后,第一眼会看到matlab、media、metadata、mathml这几个目录。简单对一下位置就知道资源是怎么组织的:
| 路径 | 作用 | 实操时重点看 |
|---|---|---|
matlab/ | 存放 .m 源码和可能的 .mlx 实时脚本 | Agent 构建、训练入口文件都在这里 |
media/ | 图像、结果截图、过程可视化 | 用来对照训练曲线是否复现成功 |
metadata/ | 案例说明、依赖项信息 | 确认当前 MATLAB 版本需要的工具箱 |
mathml/ | 数学公式的标记描述 | 论文写作或公式推导时参考,不影响运行 |
document.xml | 主流程文档的 XML 导出 | 用于找回案例整体的操作顺序 |
这里要提醒:不要把metadata当成训练结果目录,它不产生任何模型文件。训练过程中真正需要关注的是你指定的保存目录,比如agents/,里面按训练轮次生成的.mat文件才是 Agent 本体。
3. DQN与PPO在案例包中的复现和训练超参调整
3.1 DQN代理创建:输入维度、网络末端与回放缓冲区
DQN 适合离散动作空间,像前面的[-1 0 1]三动作导航问题就非常典型。创建 Agent 时,需要先构造一个接受状态输入、输出每个离散动作 Q 值的网络。以状态维度 4、动作数量 3 为例:
obsInfo = rlNumericSpec([4 1]); actInfo = rlFiniteSetSpec([-1 0 1]); qNetwork = [ featureInputLayer(4, 'Normalization', 'none', 'Name', 'state') fullyConnectedLayer(24, 'Name', 'fc1') reluLayer('Name', 'relu1') fullyConnectedLayer(24, 'Name', 'fc2') reluLayer('Name', 'relu2') fullyConnectedLayer(numel(actInfo.Elements), 'Name', 'output')]; critic = rlQValueRepresentation(qNetwork, obsInfo, actInfo, ... 'ObservationInputNames', 'state'); dqnOpts = rlDQNAgentOptions(... 'MiniBatchSize', 64, ... 'ExperienceBufferLength', 100000, ... 'TargetSmoothFactor', 1e-3, ... 'DiscountFactor', 0.99); agent = rlDQNAgent(critic, dqnOpts);rlQValueRepresentation的作用是告诉工具箱“网络的输入是 state、输出是各动作的 Q 值”,这一步别省略。TargetSmoothFactor控制目标网络的软更新速度,取 1e-3 意味着每步训练只向当前网络靠近千分之一,能有效减缓 DQN 常见的 Q 值震荡;如果你发现训练前期损失值上下跳动,优先调大这个值而不是盲目改学习率。ExperienceBufferLength是经验回放池的上限,池子越大越能减少样本相关性,但占用内存也线性增长,一般 10 万到 50 万之间比较常见。
3.2 PPO代理创建:Actor-Critic与剪辑目标
PPO 是 Policy Gradient 家族里稳定性最好的算法之一,在新版工具箱里创建 PPO Agent 的常规写法是把 Actor 和 Critic 两个网络分别包装成表示对象:
% 以连续状态、离散3动作为例,创建随机策略与价值网络 [actorRep, criticRep] = buildPPONetworks(obsInfo, actInfo); % ↑ 这里引用了案例包内 buildPPONetworks.m 辅助函数 % 内部会对离散动作末端加 softmax,连续动作末端则输出均值和方差 ppoOpts = rlPPOAgentOptions(... 'ExperienceHorizon', 2048, ... 'ClipFactor', 0.2, ... 'EntropyLossWeight', 0.001, ... 'MiniBatchSize', 64, ... 'NumEpoch', 3); agent = rlPPOAgent(actorRep, criticRep, ppoOpts);ExperienceHorizon可以理解为 PPO 收集一条更新轨迹所用的步数,相当于把很多样本攒起来算一个优势估计,数值太小策略容易抖动,太大会让旧策略与当前策略偏差变大,2048 是个保守起点。ClipFactor是裁剪系数,0.2 是论文默认值,控制策略更新的最大幅度;如果训练曲线出现台阶式下跌,试着把它降到 0.1,代价是收敛变慢。EntropyLossWeight是熵正则权重,调大可以增强探索,但设到 0.01 以上经常能看到奖励不涨。
3.3 训练选项参数表与训练过程观测
训练选项集中在rlTrainingOptions,它决定了“跑多久、什么时候停、什么时候存模型”。
| 参数 | 含义 | 经验取值/调整方向 |
|---|---|---|
MaxEpisodes | 最大回合数 | 先设 500,观察曲线再决定是否加 |
MaxStepsPerEpisode | 单回合最大步数 | 与环境最大时间相关,不宜过大 |
StopTrainingCriteria | 提前停止条件 | 常用AverageReward |
StopTrainingValue | 提前停止阈值 | 取你期望的平均奖励 |
SaveAgentCriteria | 保存 Agent 的条件 | 改成EpisodeReward就能按回合奖赏保存 |
SaveAgentDirectory | 模型保存目录 | 用fullfile(pwd,'agents')更稳 |
训练入口代码通常长这样:
trainOpts = rlTrainingOptions(... 'MaxEpisodes', 2000, ... 'MaxStepsPerEpisode', 300, ... 'StopTrainingCriteria', 'AverageReward', ... 'StopTrainingValue', 30, ... 'SaveAgentCriteria', 'EpisodeReward', ... 'SaveAgentValue', 25, ... 'SaveAgentDirectory', fullfile(pwd, 'agents'), ... 'Plots', 'training-progress'); trainStats = train(agent, env, trainOpts);训练开始后弹出的training-progress窗口里,绿色方块是每回合总奖励,深色曲线是滑动平均。这个图不是给你看个热闹的:如果滑动平均长期不增长,先检查奖励里是否全是负值,看EpisodeReward的最小值,如果一直在 -300 附近打转,说明 Agent 完全没有接收到有效梯度信号,优先怀疑奖励函数而不是网络结构。
4. 连续动作空间的DDPG与TD3实现要点
4.1 从离散到连续的动作输出
前面 DQN 和 PPO 的例子用的是rlFiniteSetSpec,也就是几个离散档位。但机械臂关节力矩、风机桨距角、车辆油门这些场景,动作本身是连续值。连续动作空间下不能再用“对每个动作算 Q 值”的思路,DDPG 改为 Actor 网络直接输出连续动作,Critic 网络则接收“状态 + 动作”来评估该动作的好坏。案例包里的连续控制任务一般会给你两个辅助函数:buildActorNet和buildCriticNet。Actor 的最后一层通常是tanhLayer,把输出压缩到 [-1,1],再用rlContinuousDeterministicActor包装成一个确定性策略表示。
4.2 DDPG代理创建与探索噪声
DDPG 在训练初期靠给动作叠加随机噪声来探索。创建代理时,NoiseOptions里的Variance决定初始噪声幅度,VarianceDecayRate控制噪声衰减速度。这两个值直接影响探索与利用的平衡,值得单独拿出来看:
[actorRep, criticRep] = buildDDPGNetworks(obsInfo, actInfo); % buildDDPGNetworks 内部会把 Actor 输出层限定为连续值 ddpgOpts = rlDDPGAgentOptions(... 'MiniBatchSize', 64, ... 'ExperienceBufferLength', 1e6, ... 'TargetSmoothFactor', 1e-3, ... 'NoiseOptions', struct(... 'Variance', 0.2, ... 'VarianceDecayRate', 1e-5)); agent = rlDDPGAgent(actorRep, criticRep, ddpgOpts);Variance设成 0.2 表示初始动作会在策略输出上叠加约 ±20% 的随机扰动,这样 Agent 前期会四处乱试;VarianceDecayRate设成 1e-5,噪声会随训练步数缓慢消失,后期逐渐收敛到确定性策略。如果在连续控制任务里看到训练前期奖励涨得飞快、后面又断崖下跌,基本可以判定噪声衰减太快,导致后期彻底丧失探索能力,合理做法是调低VarianceDecayRate一个数量级。TargetSmoothFactor的作用和 DQN 里的目标网络一致,连续控制里建议从 1e-3 起步,不要直接按默认值跑。
4.3 TD3的延迟更新与目标平滑正则
TD3 是对 DDPG 的高方差问题的修正,核心差异有三个:双 Q 网络取最小值、Actor 延迟更新、目标策略加平滑噪声。在 MATLAB 里创建 TD3 Agent 的代码与 DDPG 结构非常接近:
[actorRep, criticRep] = buildTD3Networks(obsInfo, actInfo); td3Opts = rlTD3AgentOptions(... 'MiniBatchSize', 100, ... 'ExperienceBufferLength', 1e6, ... 'TargetPolicySmoothVariance', 0.2, ... 'PolicyUpdateFrequency', 2); agent = rlTD3Agent(actorRep, criticRep, td3Opts);TargetPolicySmoothVariance是在目标动作上叠加的平滑噪声方差,它让 Q 值对动作的小扰动不敏感,从而抑制值函数过估计。PolicyUpdateFrequency表示 Actor 每隔多少步更新一次,设为 2 意味着 Critic 更新两次才动一次 Actor,这是 TD3“延迟更新”的直接体现。调参经验是:如果 DDPG 的 Q 值震荡到训练曲线无法稳定,换 TD3 通常会好一截,但 TD3 对MiniBatchSize更敏感,尽量不低于 64。
4.4 连续控制任务训练的常见崩溃模式
连续控制任务的失败模式比离散动作更难观察。第一种是 Actor 输出饱和,动作一直被tanh压在 ±1 附近,奖励也停在固定值,这时要检查网络初始化与奖励量纲。第二种是 Critic 发散,表现为训练后半段平均奖励骤降为极大的负数,通常是TargetSmoothFactor太小或奖励中有异常大值。第三种是噪声方差归零太早,Agent 陷入局部最优,表现是训练曲线走平后无论怎么加训练时间都不再提升,此时重新设置NoiseOptions再续训往往比重头训练更有效。
5. 学习曲线分析、模型保存与断点续训
5.1 训练记录的提取与复盘
train返回的trainStats是一个结构体,记录了每一回合的EpisodeReward、EpisodeSteps、AverageReward等字段。很多人关掉绘图窗口就没什么数据可看了,其实可以手动取出来做进一步分析:
episodeRewards = trainStats.EpisodeReward; avgWindow = 50; smoothRewards = movmean(episodeRewards, avgWindow); figure; plot(episodeRewards, 'Color', [0.8 0.8 0.8], 'LineWidth', 0.5); hold on; plot(smoothRewards, 'b-', 'LineWidth', 2); xlabel('Episode'); ylabel('Reward'); legend('Raw', 'Moving Average', 'Location', 'best');滑动平均窗口取 50 或者 100,主要看单回合奖励的噪声水平。噪声大就加大窗口,否则平滑线会掩盖真实的趋势。复盘时盯着smoothRewards的斜率变化:如果连续 200 回合没有明显上升趋势,不一定是算法不行,先去看EpisodeSteps是否一直在上限附近徘徊,如果是,说明 Agent 没有学会提前终止无效轨迹,这时往奖励函数里加“每步小惩罚”比增加训练回合更有效。
5.2 模型保存、加载与断点续训
长训练任务最怕跑到一半停电或参数崩坏。工具箱支持的模型保存有两种触发方式:按训练条件自动保存,以及手动保存当前 Agent。自动保存在第 3 章的rlTrainingOptions里已经配置过。手动保存用于你已经判断当前策略不错、想停下来调整奖励函数再接着练的场景:
% 训练结束后保存完整 Agent 对象 save(fullfile(pwd, 'agents', 'finalAgent.mat'), 'trainedAgent'); % 新会话中恢复 s = load(fullfile(pwd, 'agents', 'finalAgent.mat')); trainedAgent = s.trainedAgent;续训时要特别区分“接着训练”和“从头训练”。直接调用train(trainedAgent, env, newOpts)会沿用原来的网络权重和经验回放缓冲区,适合在原有策略基础上微调;如果你想清掉旧经验而保留网络结构,需要重新创建 Agent。一个常见错误是保存时只存网络对象而不存 Agent,导致续训时/mdlInfo.TrainingOptions等内部状态全部丢失,训练精度对不上前面的曲线。因此保存对象时,只存trainedAgent这个完整变量即可,不要试图把actor、critic拆开存。
5.3 诊断案例:平均奖励在平台上不动怎么办
最典型的场景是:跑了 300 回合,平均奖励稳定在 -150,不上不下。按优先级检查三件事。第一,奖励是否过稀疏,把step里的奖励改为“当前距离到目标距离的差”,也就是每一步因靠近而获得的正向差值;第二,探索噪声是否饱和或消失,离散动作检查EpsilonGreedyExploration的衰减率,连续动作检查NoiseOptions的VarianceDecayRate;第三,网络容量不足,把两层 24 神经元扩成两层 64 神经元再跑 100 回合,如果曲线有变化说明是表达能力问题。记得每改一次只动一个变量,否则无法定位是谁起的作用。
6. 从Agent到Simulink:策略评估与部署验证
6.1 在原始环境中做闭环sim评估
训练和评估不要共用同一段代码路径。正式评估时关掉探索噪声,用确定性策略连续跑多个回合,看最终奖励分布。MATLAB 中可以用rlSimulationOptions控制评估的回合数和步数:
simOpts = rlSimulationOptions('NumSimulations', 10, 'MaxSteps', 500); totalRewards = zeros(simOpts.NumSimulations, 1); for i = 1:simOpts.NumSimulations exp = sim(env, agent, simOpts); totalRewards(i) = sum(exp.Reward); end fprintf('Mean reward: %.2f, Std: %.2f\n', ... mean(totalRewards), std(totalRewards));评估时看的不只是平均奖励,更要看标准差。如果 10 次评估中有 1 次严重偏离,说明策略还存在某些未覆盖的状态区域。标准差超过平均值的 20% 时,回去补充这些失败案例到经验池再续训通常是最快的修法。
6.2 Simulink中的智能体模块与代码生成技巧
部署到 Simulink 时,常见的做法是先用rlSimulinkEnv把你原来的 MATLAB 环境替换为 Simulink 模型,从模型里替换出一个 Agent 模块,并确保该模块的观测输入和动作输出维度与训练时一致。Simulink 环境下做验证最大的价值是能无缝对接已有的被控对象模型,不需要把微分方程从 Simulink 再翻译回 MATLAB 的step函数。
代码生成前有两件事值得做:在模型配置里把求解器固定为定步长,否则生成的代码在不同机器上的运行行为会有差异;同时把训练时加的噪声模块从路径中删除,否则生成代码里会残留随机数生成逻辑,导致部署版本和训练版本策略不一致。最后用codegen生成 C 代码时,要避免在评估函数里引入plot、figure等可视化指令,MATLAB Coder 要求目标函数全部为可编译代码,把训练诊断用的可视化单独放到一个脚本里与部署入口分离,这一步做完,策略从训练环境到实际控制链路的迁移才算真正闭合。
本文还有配套的精品资源,点击获取