简介:这套基于MATLAB 2021a的深度强化学习仿真资源,以小球弹射控制系统为研究场景,完整展示了DDPG与TD3两种连续动作空间算法的实现与对比。压缩包共十八个文件,包含十一份.m源码,分别负责网络结构定义、智能体训练、环境模拟与结果可视化;四张.png图片清晰呈现了两种算法在七状态和八状态环境下的奖励变化曲线;两份.mat数据文件保存了训练完成的智能体参数;另有一份txt文档探讨了FPGA与MATLAB接口部署问题;整个资源包仅二点零三兆字节,结构紧凑,便于快速查阅。目前已有1468人学习下载,适合强化学习初学者、控制仿真方向的学生以及需要选型算法的工程师。通过对照源码与仿真图,可以深入理解Actor-Critic框架、经验回放、目标网络等DDPG核心机制,并结合TD3的双Q学习、延迟更新和噪声抖动,掌握抑制价值过估计的改进思路;不同状态数量下的测试结果也为评估算法收敛速度和稳定性提供了直观依据,是一份兼具教学与工程参考价值的完整案例。
1. 小球弹射控制为什么值得交给DDPG和TD3做对比
小球弹射控制系统听起来像一个物理实验台,但把它当成一个连续动作控制问题来看,它恰好是深度强化学习算法对比的绝佳载体。系统本身一句话就能说清:一个小球在水平轨道上,弹射器给它一个初速度,球在阻尼作用下滑行,控制目标是让它准确停在目标位置。传统PID最怕这种带延迟和不确定性的对象,而DDPG和TD3正好是深度强化学习里处理连续动作空间的两大基线算法。用MATLAB 2021a自带的强化学习工具箱实现这套环境,跑完对比实验,你能直观看到经验回放、目标网络、策略平滑这些机制在同一个物理环境中各自贡献了多少。这件事适合两类人:一是要做算法对比实验的学生,二是想评估DRL在自己控制任务上是否可行的工程师,MATLAB版本不需要搭PyTorch或TensorFlow环境,几分钟就能把基线跑起来。
2. 环境与MDP建模:小球弹射系统的状态、动作和奖励设计
2.1 状态空间和动作空间:一次弹射对应一个决策
把小球弹射问题写成马尔可夫决策过程,第一步是定义状态、动作和奖励。这里有一个值得先想清楚的设计决策:一次完整的弹射过程算一个回合,还是把弹射后的滑行过程拆成多个控制步?
常见做法是拆成多个控制步。弹射器给出初速度后,小球在轨道上滑行,每一步控制器都可以施加一个辅助力来修正速度,而不是发射之后完全不管。这样问题就从“单步参数优化”变成了“序列决策”,深度强化学习才有用武之地。如果只让算法一次性输出弹射初速度,然后等小球滑到终点看误差,那本质上是参数搜索,不是强化学习。
所以我把状态定义成三维:
% 状态向量:位置x,速度v,目标位置x_target % 全部用连续值,范围经过归一化 obsInfo = rlNumericSpec([3 1], ... 'LowerLimit', [-2 -2 -2], ... 'UpperLimit', [2 2 2]); obsInfo.Name = 'ballState';动作则定义为弹射时刻的初速度,以及后续每一步可以施加的修正加速度,这样动作空间是一维连续量:
% 动作:施加在小球上的加速度修正量,范围[-1, 1] actInfo = rlNumericSpec([1 1], ... 'LowerLimit', -1, ... 'UpperLimit', 1); actInfo.Name = 'controlAccel';归一化是这一步必须做的事。MATLAB的强化学习工具箱要求预先声明每个状态和动作的上下界,这个上下界会直接影响探索噪声的尺度。如果动作范围写得比物理上能接受的大很多,训练早期随机策略会把小球推出轨道边界,环境返回的终止条件让算法学到一堆无效经验,后面再想纠正就难了。
2.2 奖励函数设计:稀疏奖励还是密集奖励
奖励函数是强化学习里最影响收敛速度的设计决策。我第一版用的是稀疏奖励:小球停在目标位置附近就给+1,否则给0。结果跑了300个回合,平均回报纹丝不动。原因很好理解,在连续动作空间里,随机策略能精确停在目标位置的概率几乎为零,agent从头到尾收不到一次正向反馈,策略梯度无从更新。
后来换成密集奖励,把每一步的欧氏距离和速度作为惩罚:
function [reward, isDone] = computeReward(x, v, x_target, step) distance = abs(x - x_target); reward = -distance - 0.1 * abs(v) - 0.01 * step; if distance < 0.01 reward = reward + 10; % 到达目标位置的额外奖励 isDone = true; elseif abs(x) > 2 reward = reward - 5; % 越界惩罚 isDone = true; else isDone = false; end end这段代码里有三个值得注意的参数。第一个是距离惩罚系数,它决定了agent有多大的动力去靠近目标;第二步数惩罚系数0.01,作用是鼓励agent少走弯路,但这东西不能设太大,否则agent会为了早结束而选择直接越界;第三是越界惩罚-5,必须比一回合正常累积的负回报更重,才能让agent真正回避越界。
还有一个容易被忽略的细节:距离阈值0.01对应的是物理单位还是归一化单位,这直接决定任务难度。
2.3 环境动力学方程与离散化:欧拉积分还是ode45
小球在水平轨道上的动力学用一阶阻尼模型就够用了。质量m,阻尼系数b,外力u,运动方程为:
m * dv/dt = -b * v + u dx/dt = v
MATLAB里实现环境有两种路径,一是自己写离散欧拉积分,二是用ode45解微分方程。对于这种简单线性系统,固定步长欧拉积分完全够用,而且更可控。用ode45虽然精度高,但每一步的仿真时间不固定,强化学习的环境步进需要稳定间隔,否则连续状态下训练出来的策略在部署时会出现时序偏差。
function [nextObs, reward, isDone, loggedSignals] = envStep(action, obs) % 固定步长欧拉积分 m = 1.0; % 小球质量 b = 0.5; % 阻尼系数 dt = 0.05; % 控制步长 x = obs(1); v = obs(2); x_target = obs(3); % 动力学更新 a = (-b * v + action) / m; v = v + a * dt; x = x + v * dt; % 计算奖励与终止条件 [reward, isDone] = computeReward(x, v, x_target, 0); nextObs = [x; v; x_target]; loggedSignals = struct('reward', reward); enddt取0.05秒意味着一个2秒的回合有40个控制步,这个粒度刚好能让DDPG这样的actor-critic算法在1000个回合内看到足够多的时间差分信号。步长太大会导致动力学失真,步长太小则单回合步数太多,训练效率骤降。后面在避坑章节里我会展开讲这个参数的敏感性。
3. DDPG与TD3的差异:为什么TD3要改这三点
3.1 从DQN到DDPG:连续动作空间是硬需求
小球弹射系统的动作是连续的加速度值,DQN这种基于动作价值表的算法天然不适用。DQN输出的是每个离散动作的Q值,动作空间稍大一点就会指数爆炸。DDPG把策略网络从Q-learning里解耦出来,引入了确定性策略梯度的思路:actor网络直接输出连续动作,critic网络评估这个动作的Q值。
DDPG的核心机制是经验回放加目标网络。经验回放把环境交互得到的四元组(s, a, r, s')存进缓冲区,训练时随机采样,打破样本之间的时间相关性。目标网络则用软更新的方式缓慢跟踪在线网络,让TD误差的计算目标不至于剧烈变化。这两点是DDPG能在连续控制任务上站稳脚跟的基础。
实现DDPG时有个容易踩的坑:actor和critic的网络结构如果设得太深,训练初期收敛极慢。小球弹射这种低维状态问题,两层全连接网络各128个神经元已经足够,没必要堆到三四层。
3.2 TD3的三个关键改进
TD3论文里说得很直白,它是在DDPG基础上修了三个问题。第一个是clipped double Q-learning,用两个critic网络分别估算Q值,取较小值作为TD目标,抑制Q值过估计。DDPG的monotonic overestimation会让critic对很多不该做的动作给出虚高的评价,策略跟着跑偏。
第二个改进是延迟更新actor网络。actor的更新频率比critic低,让critic先稳定下来再更新策略。实践中常见设置是critic每步更新,actor每两步更新一次。第三个改进是目标策略平滑,在计算TD目标时给目标动作加上一个小噪声,相当于对Q函数做正则化,避免策略利用Q函数的尖锐峰值。
这三个改动对小球弹射系统的影响可以直观感受到。DDPG学到的策略往往动作噪声大,速度曲线抖动明显;TD3学到的策略更平滑,收敛后速度曲线接近理论最优。不过TD3的超参数比DDPG多,调起来也更麻烦。
3.3 关键超参数对照表:从这些值起步
做对比实验最重要的原则是两个算法站在同一起跑线上。我常用的做法是共享经验回放缓冲区大小、批大小、学习率、折扣因子这些公共超参数,只保留算法特有的设置差异。下面这张表是经过多轮调试后比较稳的一组起始值:
| 超参数 | DDPG | TD3 | 说明 |
|---|---|---|---|
| Actor学习率 | 1e-3 | 1e-3 | 太小收敛慢,太大训练震荡 |
| Critic学习率 | 1e-3 | 1e-3 | 两个算法保持一致 |
| 批大小 | 128 | 128 | 影响样本利用率 |
| 折扣因子 | 0.99 | 0.99 | 衡量未来奖励的权重 |
| 经验池容量 | 1e6 | 1e6 | 超过这个值后旧样本被覆盖 |
| 软更新系数τ | 0.005 | 0.005 | 目标网络更新速率 |
| Actor延迟更新频率 | 1 | 2 | TD3每两步更新一次actor |
| 目标策略平滑噪声 | 无 | N(0, 0.2) | TD3独有,裁剪到±0.5 |
| 动作探索噪声 | N(0, 0.1) | N(0, 0.1) | 训练时叠加在动作上 |
超参数表的实际意义在于:如果两个算法用了完全不同的学习率,收敛速度的对比就不公平。我在做对比实验时严格固定公共参数,改一个变量就只改一个变量,这样最终曲线差异才能归因到算法机制本身。
4. MATLAB 2021a实现:环境接口与agent创建代码
4.1 自定义环境接口:rlFunctionEnv绑定reset和step
MATLAB 2021a的强化学习工具箱提供了多种环境接口方式。最简单的是用rlFunctionEnv,把reset和step函数绑定进去,不需要写完整的类定义。这一步特别适合快速验证算法,环境逻辑全部写在函数里,修改起来直观。
% 环境初始化函数 function initialState = envReset() x0 = 0; % 小球起点 v0 = 0; % 初始速度为零 x_target = 1.0; % 目标位置 initialState = [x0; v0; x_target]; end % 绑定到rlFunctionEnv env = rlFunctionEnv(obsInfo, actInfo, @envStep, @envReset);envReset负责每个回合开始时返回初始状态。这里有个设计细节:目标位置x_target在重置时可以随机化,让agent学会对不同目标距离都适用。如果固定目标位置,agent可能学到的是针对这个特定点的记忆,而不是通用的控制策略。我在实验中有一半回合随机化目标位置,另一半固定,这样既保证任务难度可控,又迫使策略具备泛化能力。
rlFunctionEnv接受的状态和动作规格必须和前面定义的obsInfo、actInfo一致,维度不对会在训练开始时报错。这个错误很常见,调试时要先检查规格定义。
4.2 创建DDPG与TD3 agent:actor和critic网络结构
接下来是创建agent网络。MATLAB用网络层数组定义actor和critic,最终通过rlDDPGAgent和rlTD3Agent封装。这里有一个MATLAB特有的注意事项:actor的输出层必须用tanh激活函数,把动作限制在[-1, 1]范围内,再通过动作规格的缩放映射到实际范围。如果输出层用了purelin,动作值可能超出声明范围,训练过程直接崩溃。
% 定义actor网络 actorNet = [ featureInputLayer(3, 'Name', 'obs') fullyConnectedLayer(128, 'Name', 'fc1') reluLayer('Name', 'relu1') fullyConnectedLayer(128, 'Name', 'fc2') reluLayer('Name', 'relu2') fullyConnectedLayer(1, 'Name', 'action') tanhLayer('Name', 'tanh') % 输出限制在[-1,1] ]; actor = rlDeterministicActor(actorNet, obsInfo, actInfo); % 定义critic网络,输入是状态和动作的连接 criticNet = [ featureInputLayer(3, 'Name', 'obs') fullyConnectedLayer(128, 'Name', 'fc1') reluLayer('Name', 'relu1') concatenationLayer(1, 2, 'Name', 'concat') % 合并动作输入 fullyConnectedLayer(128, 'Name', 'fc2') reluLayer('Name', 'relu2') fullyConnectedLayer(1, 'Name', 'qvalue') ]; critic = rlQValueFunction(criticNet, obsInfo, actInfo);critic网络的结构里使用concatenationLayer把状态特征和动作特征合并,这是MATLAB强化学习工具箱的标准做法。需要注意的是动作输入分支没有经过任何非线性变换,直接与状态特征拼接,这样Q函数的梯度能直接回传到actor网络,不会被中间层扭曲。
TD3需要两个critic网络,MATLAB的rlTD3Agent构造函数内部会自动复制一份critic结构,不需要手动创建两个。但这样也带来一个问题:两个critic网络如果初始化为完全相同的参数,训练初期两条Q曲线完全重合,clipped double Q的效果会被削弱。工具箱在创建时会做一次参数扰动,所以不用太担心。
% 创建DDPG agent ddpgOpts = rlDDPGAgentOptions; ddpgOpts.TargetSmoothFactor = 0.005; ddpgOpts.ExperienceBufferLength = 1e6; ddpgOpts.MiniBatchSize = 128; ddpgAgent = rlDDPGAgent(actor, critic, ddpgOpts); % 创建TD3 agent td3Opts = rlTD3AgentOptions; td3Opts.TargetSmoothFactor = 0.005; td3Opts.ExperienceBufferLength = 1e6; td3Opts.MiniBatchSize = 128; td3Opts.TargetPolicySmoothNoiseStd = 0.2; td3Opts.TargetPolicySmoothNoiseBound = 0.5; td3Opts.PolicyUpdateFrequency = 2; td3Agent = rlTD3Agent(actor, critic, td3Opts);rlTD3AgentOptions里四个参数是TD3性能的关键。TargetPolicySmoothNoiseStd是目标策略平滑噪声的标准差,太大让目标Q值失真,太小失去正则化效果;TargetPolicySmoothNoiseBound是噪声裁剪边界;PolicyUpdateFrequency控制actor延迟更新的步数。这些参数和PyTorch版本的TD3代码一样,改一个都会明显影响收敛曲线,建议每轮只动一个参数。
4.3 训练循环配置:收敛判断与可视化
训练配置决定了实验何时停止、如何判断收敛。MATLAB的rlTrainingOptions提供了丰富的停止条件设置。我一般用AverageReward作为主停止条件,同时设置最大回合数兜底,防止策略确实无法收敛时训练一直跑下去。
trainOpts = rlTrainingOptions; trainOpts.MaxEpisodes = 1000; trainOpts.MaxStepsPerEpisode = 80; trainOpts.ScoreAverageWindowLength = 20; trainOpts.StopTrainingCriteria = 'AverageReward'; trainOpts.StopTrainingValue = -5; trainOpts.Verbose = true; trainOpts.Plots = 'training-progress'; % 训练 trainingStats = train(td3Agent, env, trainOpts);ScoreAverageWindowLength设20,表示最近20个回合的平均回报超过StopTrainingValue就停止训练。这个窗口太短,个别高回报回合会误报收敛;太长则需要跑很多回合才能触发停止。MaxStepsPerEpisode设80对应每个回合4秒仿真时长,小球从起点滑到目标位置并停稳,正常情况下40步内就能完成,预留80步是给探索阶段留余量。
train函数的输出trainingStats里记录了每个回合的回报、平均回报和采样步数。保存这个结构体,后续画对比图要用。我见过很多人直接在命令行里跑训练,关掉窗口数据就丢了,必须显式保存。
5. DDPG与TD3调试避坑指南:五个常见的翻车现场
5.1 动作范围与物理边界不匹配,训练发散
现象是训练早期奖励曲线剧烈震荡,偶尔出现巨大的负回报,然后策略彻底崩溃。罪魁祸首通常是动作范围声明得比物理系统能承受的范围大。比如把action的UpperLimit设为10,但模型里小球加速度超过2就会飞出轨道的边界。随机探索时动作值一直在2以上徘徊,每次都以越界终止,agent无论如何调整策略都得不到有效反馈。
解决方法是先跑一个开环脚本,随机采样动作,统计状态变量实际能达到的范围,再根据这个范围去声明观测和动作的上下界。这一步看起来不起眼,但能省掉一整天的无效训练。经验法则是动作范围设置为物理极限的70%,留出探索空间同时保证大部分随机动作不越界。
5.2 奖励尺度太大导致Q值爆炸
奖励函数里到达目标给+10,这个值看起来合理,但如果训练早期偶尔蒙中一次目标,TD误差会被放大到很大值,Q网络的参数更新步长瞬间过大,损失直接跳到NaN。我遇到过训练到第200回合时critic loss从个位数跳到几千,之后曲线一直有毛刺。
解决方法是把奖励整体缩放,让单步奖励保持在[-1, 1]区间。距离惩罚除以一个归一化系数,到达奖励降为+2。另一个选择是使用reward normalization层,但MATLAB 2021a的rlQValueFunction里没有内置这个功能,手动缩放更直接。调奖励函数的原则是:先让早期偶然的成功不至于引发Q值振荡,再逐步提高末态奖励权重。
5.3 TD3默认参数跑出来还不如DDPG
TD3有四个独有超参数,如果直接照搬论文里的值,在小球弹射这种简单任务上经常表现不佳。TargetPolicySmoothNoiseStd设0.2在复杂机械臂任务上没问题,但在这个一维控制任务上,0.2的噪声相对于动作范围已经是20%的干扰,导致目标Q值过于模糊。
调整方法分两步。先把PolicyUpdateFrequency从2改为4,让actor更新更慢,critic有更多时间收敛;再把平滑噪声标准差从0.2降到0.1,噪声裁剪边界设为0.3。这两个改动会让TD3收敛曲线追平DDPG,再往后td3的优势才逐渐浮现。如果一上来就拿着默认参数,很容易得出“TD3不如DDPG”的错误结论。
5.4 控制步长不当导致环境不稳定
dt=0.05可以从一开始就稳定运行,但如果你为了加快仿真把dt改成0.2,会出现一个问题:每一步的位移变化太大,小球可能在一个控制步里从目标位置左侧直接飞到右侧,distance从正变成负,密集奖励完全失效。更严重的是,过大步长让离散化误差累积,系统的阻尼效应被扭曲,agent学到的策略在更细的步长下部署时完全失效。
判断步长是否合适的标准:在零输入的情况下,小球从初速度1.0自然滑行到停止,至少要经过20个控制步。只有在这个前提下,强化学习的时间差分信号才足够细粒度,QCritic能学到真实的Q函数形状。
5.5 探索噪声过大或过小
DDPG和TD3在训练时都需要给动作叠加高斯噪声做探索。噪声方差设0.1比较中庸。我做过一组测试:噪声为0.5时,训练前期几乎无法收敛,因为动作被噪声淹没,策略的确定性部分完全学不到东西;噪声为0.001时,前100回合收敛慢,但后期策略更精确。用小方差噪声后,如果发现agent陷入了局部最优,可以手动提高噪声方差做退火,先探索再收敛。TD3的exploration noise和target policy smooth noise是两回事,这个需要分清楚。调的时候,先固定target smoothing,单独调exploration noise,两个维度同时动很难定位问题。
6. 对比实验怎么设计:收敛曲线、鲁棒性与参数灵敏度
6.1 同一起跑线:共享环境与随机种子
算法对比要让人信服,关键是控制变量。环境、观测规格、动作规格、最大训练回合数、经验池容量这些公共配置必须完全一致。另一个容易忽略的干扰项是随机种子,MATLAB的rng函数控制全局随机数生成,不设种子的话,两次训练之间的经验采样和网络初始化都不一样,对比曲线没有说服力。我在每次训练前执行rng(42),保证结果可复现。
同时需要做多组实验取平均。单次训练曲线噪声太大,两个算法的差异可能淹没在随机性里。我自己会把每种算法跑5次不同种子,保存每次的avgReward向量,最后画mean和std band。这个做法虽然多花时间,但图表呈现出的差异才是真实可信的。
6.2 对比结果怎么解读:三个维度
收敛速度看曲线首次达到停止阈值的回合数。下图是我跑的一组代表性结果(说明性描述,非真实数据):DDPG通常在第280回合首次触达阈值,TD3在第240回合左右触达。TD3的曲线更平滑,DDPG的曲线后期仍有小幅震荡,原因是DDPG的Q值过估计让策略偶尔偏离最优方向。
最终策略质量用收敛后的平均单回合回报衡量。TD3因为有目标策略平滑机制,收敛后动作抖动小,速度曲线更平滑;DDPG收敛后仍然有轻微抖动。用小球的终点误差来衡量,TD3稳态误差能到0.005以内,DDPG在0.01左右。
鲁棒性测试的方法是把目标位置随机化,训练时只在0.5到1.5之间随机,测试时外推到2.0的位置。这类外推测试能暴露agent是否学会了通用控制规律还是仅仅背下了训练分布。TD3在这个任务上外推表现明显更好,这跟策略平滑机制有一定关系——Q函数被正则化得更平滑,决策边界不会随着状态改变而剧烈变化。
6.3 训练完成后的三个验证动作
训练结束不等于实验结束,我在提交结果前会做三个验证。第一是sim函数回放策略,看实际控制曲线和状态轨迹,确认没有明显的抖动和越界;第二是保存agent到MAT文件,确认加载后能复现同样的行为;第三是做一次参数灵敏度扫描,把学习率从1e-3改到3e-3和1e-4,观察两个算法是否表现出相同的趋势。这一步能帮你回答“这个结论是不是只在特定超参数下成立”的关键问题。
整个实验做下来,我最深的体会是:DDPG和TD3在小球弹射这类简单连续控制任务上,差距远没有论文里展示的那么大,但TD3的鲁棒性优势是真实存在的,尤其当目标位置分布变化时。而DDPG的价值在于结构简单、参数少,适合作为baseline快速验证环境建模是否正确。这也是这套对比实验的真正目的——先确认环境没有bug,再评判算法优劣。
如果你是刚开始做这个方向,建议先从DDPG跑通全流程,再把TD3加进去。按本文的配置和避坑清单一步步来,半天时间出第一版对比结果不成问题。希望帮到你。
本文还有配套的精品资源,点击获取