简介:这是一份基于强化学习与自适应控制理论学习和策略更新源码包,面向机器人控制、自动化与人工智能方向的研究者和工程师,用于在未知或变化环境中训练机器人自主调整行为策略。压缩包共127个文件,以106个MATLAB脚本为主体,覆盖学习算法、经验回放、仿真启停等关键模块,另有jpg、png、gif图片、fig模型图以及说明文档,方便查看训练状态、理解模块结构,整体大小336KB,体量轻、便于使用。目前已有853人浏览学习。项目将经验回放与在线学习机制融入Simulink仿真环境,结合自适应控制思想帮助机器人应对环境不确定性;整体代码结构划分清晰,启动与清理脚本齐备,随附的图片可辅助核对算法流程与状态变化,适合二次开发、教学演示或课程设计参考。
1. 基于强化学习的自适应机器人控制算法到底在解决什么难题
机械臂抓一个 20kg 的工件和抓一个空抓手,动态特性差了好几倍。传统控制器的做法是提前把模型算准,负载一变就得重新调 PID 增益或重新辨识参数;而“基于强化学习的自适应机器人控制算法”把这件事变成了训练问题——让控制策略在奖励函数和域随机化的作用下,自己学会对不同负载、摩擦甚至模型误差输出合适的力矩。它适合那些模型不精确、环境会变、又不愿意每个工况都重新写控制器的场景,比如协作机械臂末端负载变化、移动机器人车轮打滑、管道机器人通过变径管段时的驱动力再分配。
但这里要先泼一盆冷水:它不是什么魔法。训练时间不短,奖励函数要反复调,仿真里学好的策略搬到真实机械臂上还得再做一轮安全验证。这篇文章我把在 MATLAB/Simulink 里从零搭一条训练链路的完整步骤拆开,从环境模型怎么建、状态与奖励怎么设计,到 DDPG/TD3/SAC 怎么选、部署时有哪些坑,尽量写到你能照着复现的程度。
2. 搭建最小可训环境:MATLAB工具箱、Simulink模型与一次能跑通的训练链路
自适应控制依赖环境的动态模型,强化学习训练更需要一个“能被打断、能重置、能注入随机参数”的仿真环境。这套东西在 Simulink 里实现是最顺手的,因为模型本身就是 Simulink 的,Agent 通过一个 RL Agent 块挂在模型里,状态和动作通过端口交互。
2.1 工具箱与版本核对:别让版本差异浪费一整天
先把依赖说清楚。你要跑通这一篇文章里的流程,至少需要这些组件:MATLAB 本体、Simulink、Deep Learning Toolbox、Reinforcement Learning Toolbox。如果想做后续的实时验证和代码生成,再往上加 Simulink Coder 和 Simulink Desktop Real-Time;如果只想验证算法研究结论,前面四个足够。
版本上我吃过亏。Reinforcement Learning Toolbox 从 R2019a 之后才比较稳定地支持 rlNumericSpec、rlSimulinkEnv 这一套 API,R2022a 之后 SAC、TD3 的实现才比较完整。你如果拿到一个“算法实现.zip”,第一步一定是在 MATLAB 里看版本,再用ver确认工具箱有没有装齐,而不是双击模型就点运行。常见做法是先把命令行里这几句跑一遍:
ver('reinforcement') ver('simulink') ver('deep')这三行的作用是确认核心工具箱是否可用。如果有 1 个工具箱缺失,后面的 train 函数会直接报 undefined function,而且报错信息经常不直接提示缺工具箱,而是说“无法解析函数或变量”,很容易误判成模型错误。这里面最容易漏的是 Deep Learning Toolbox,RL Toolbox 的 actor 和 critic 都建立在深度神经网络上,深度学习工具箱没了它连网络创建都过不去。
2.2 在 Simulink 里搭出机器人被控对象的三种模型层次
训练用模型可以分成三个递进层次:单关节刚性模型、双连杆机械臂模型、带柔性关节和接触力的高保真模型。做算法验证我建议从单关节开始,不是说高保真模型不好,而是强化学习训练第一轮实验大概率要改环境接口,模型越复杂,排查链路越长。
单关节模型的运动方程是典型的二阶系统:
J * theta_ddot = tau - b * theta_dot - m * g * l * sin(theta)在 Simulink 里搭这个模型只需要四个基础模块:两个 Integrator 串联得到角速度和角度,一个 Gain 放惯量倒数,一个 Sin 模块算重力项。我用普通仿真(Normal Mode)时会把求解器设为固定步长离散,步长 0.01s。模型层放一个 Constant 表示负载质量 m,这样后面 ResetFcn 可以随机改它。
双连杆机械臂模型则要加上科里奥利力和耦合惯量项,运动方程会变成 M(q)q_ddot + C(q, q_dot)q_dot + G(q) = tau。这一层适合验证算法在高耦合非线性下的表现,但训练时间大约是单关节模型的 3 到 5 倍。如果目标是产品落地前验证自适应控制方案,我一般会在单关节模型上把 Agent、奖励、观测设计全部调通,再整体平移到双连杆模型上重新训练。
| 模型层次 | Simulink 建模要点 | 适合验证什么 | 训练成本 |
|---|---|---|---|
| 单关节刚性 | 两个积分器 + 重力项 + 摩擦项 | 算法链路、负载自适应 | 低 |
| 双连杆平面臂 | M(q)/C(q)/G(q) 矩阵运算 | 耦合非线性、轨迹跟踪 | 中 |
| 带接触力机械臂 | Simscape Multibody 刚体建模 | 柔顺控制、打磨/装配类任务 | 高 |
2.3 用 rlSimulinkEnv 把 Agent 接到模型:环境创建代码与参数说明
模型搭好后,接下来要做的不是直接放点训练,而是先创建强化学习环境对象。这一步相当于告诉 RL Toolbox:“Simulink 模型里哪个块是 Agent、状态有几维、动作有几维、范围多大。”
% 定义观测空间:6 维,分别对应角度、角速度、角度误差、角速度误差、目标角度、负载估计 obsInfo = rlNumericSpec([6 1], ... 'LowerLimit', -inf * ones(6,1), ... 'UpperLimit', inf * ones(6,1)); obsInfo.Name = 'joint_obs'; % 定义动作空间:1 维连续力矩,单位 N*m actInfo = rlNumericSpec([1 1], ... 'LowerLimit', -20, ... 'UpperLimit', 20); actInfo.Name = 'joint_tau'; % 关联 Simulink 模型与环境 env = rlSimulinkEnv('rl_single_joint', 'rl_single_joint/RL Agent', obsInfo, actInfo); % 每次 episode 开始前重置模型参数 env.ResetFcn = @(in) localResetFcn(in);这里重点说三个参数。第一,obsInfo 的维度一旦定了,Simulink 模型里 RL Agent 块的输入端口宽度就必须是 6,输出端口宽度是 1,两边不匹配会在仿真开头报维度错。第二,actInfo 的上限不是越大越好,力矩上限和机械臂物理约束要匹配,设成 20 意味着策略最大输出 20N·m,超过真实关节可承受范围训练出的策略部署时必炸。第三,env.ResetFcn 是自适应能力的关键——它允许每次 episode 开始时随机改变模型里的质量、摩擦、初始角度。
function in = localResetFcn(in) blk = 'rl_single_joint/mass'; in = setBlockParameter(in, blk, 'Value', num2str(0.5 + 2 * rand)); blk0 = 'rl_single_joint/theta0'; in = setBlockParameter(in, blk0, 'Value', num2str(-pi + 2 * pi * rand)); end这个 ResetFcn 体现了强化学习和传统自适应控制打法上最大的差异:传统方法靠在线辨识去估计质量参数,RL 方法靠训练时把质量放在随机范围里反复采样,让策略见过足够多“负载不同但目标相同”的局面,从而学会在控制过程中自动补偿。你这套方案里如果没有 ResetFcn,策略学到的只是在固定质量下的一个普通反馈控制器,根本谈不上自适应。
2.4 第一次训练选项:先定义“什么算学会”
环境创建完,就可以先放一个最简单的 Agent 上去跑。这里我用 DDPG 做首次链路验证,因为它结构最简单,出了问题好查。
initOpts = rlOptimizerOptions('LearnRate', 1e-3, 'GradientThreshold', 1); agentOpts = rlDDPGAgentOptions(... 'SampleTime', 0.01, ... 'MiniBatchSize', 128, ... 'ExperienceBufferLength', 1e6, ... 'AgentOptimizerOptions', initOpts); agent = rlDDPGAgent(obsInfo, actInfo, agentOpts); trainOpts = rlTrainingOptions(... 'MaxEpisodes', 500, ... 'MaxStepsPerEpisode', 300, ... 'ScoreAveragingWindowLength', 10, ... 'StopTrainingCriteria', 'AverageReward', ... 'StopTrainingValue', -2, ... 'Plots', 'training-progress', ... 'Verbose', true); stats = train(agent, env, trainOpts);几个参数的取舍值得单独说。SampleTime 也就是 RL Agent 的采样周期,Simulink 里被控对象仿真步长如果是 0.01s,SampleTime 通常也设 0.01;如果对象的动力学变化更快(比如关节刚度很大),采样周期要缩到 0.001~0.005。StopTrainingCriteria 是训练提前结束的条件,不要一开始就把数值设太理想,先看一眼训练曲线的走势再修正。我一般第一次训练用 500 个 episode,看 AverageReward 是否还在上涨,如果 500 个 episode 曲线还没有平稳迹象,就加到 1500 再跑一次。
提示:如果遇到“Error: Invalid input signal type”这类报错,先检查动力学模型输出是不是 double 类型、有没有把 boolean 信号传进 RL Agent,这类问题比算法本身更频繁。
3. 状态、动作与奖励设计:自适应能力的来源全在这三个定义里
很多人以为强化学习最难的是算法选型,实际在机器人控制场景里,状态、动作、奖励设计占到整个调试工作量的七成。策略本质上是在拟合“观测到状态-输出动作”的映射,状态里没有的信息,它无论如何也学不出来;奖励函数里表达不清楚的目标,它就会想办法钻空子。
3.1 状态空间设计:把负载信息显式送进网络
单关节模型里最基本的状态是角度和角速度。如果只看这两个量,策略最多能学成一个固定参数下的反馈控制器。要让控制具备自适应能力,状态里必须包含能反映系统变化的信息,常见做法有三个方向。
第一个方向是把目标信号做进状态:角度误差和角速度误差同时进网络,这比只给绝对角度好训练,因为策略不需要对每个目标角度单独记一套映射。第二个方向是把负载估计或与负载相关的信息送进去,可以是外力矩估计、电流信号折算的负载质量,也可以是摩擦系数的在线估计值。第三个方向是“把因果信息直接铺开”,把目标角度、当前角度、当前角速度、误差、误差变化率、负载估计六维打包成一个向量,这有点类似于因果强化学习 CRL 的思路——不要让网络从相关关系里自己反推因果,直接把原因变量摆在输入里,学起来快得多。
我验证过的状态向量是:
obs = [theta; theta_dot; theta - theta_ref; theta_dot - theta_ref_dot; theta_ref; m_hat]theta_ref 是目标轨迹位置,m_hat 是负载质量或它的粗估计值。仿真里可以直接把真实质量接进去,真机部署时如果估计不准,训练时要把 m_hat 叠加噪声。这里有一个很多人会忽略的细节:状态量的物理单位差别很大,角度是零点几弧度,角速度可能是几,负载质量可能是几十,原始值直接喂网络会让梯度被大数值量纲主导,必须在模型出口做归一化。
3.2 动作空间设计:输出力矩还是输出目标角速度
连续动作空间的配置直接决定控制器的底层行为。两种常用方案差异很大。
第一种方案,动作是关节力矩,RL Agent 直接取代整个控制回路。优点是没有中间层,策略自由度最大,缺点也很明显:策略学到的力矩可能有高频抖动,部署到真实关节上冲击很大。第二种方案,动作是目标角速度或目标角度,下层再挂一个 PD 控制器去跟踪,RL 只负责生成参考轨迹。这种分层结构的优点是动作平滑性好、安全性高,代价是控制性能受下层 PD 影响,RL 的自适应能力被部分遮盖了。
我一般在 Simulink 验证阶段用第一种方案,因为要看算法本身能不能处理模型不确定性;真机部署前切到第二种,在 RL Agent 块后面串联一个饱和限制和一个低通滤波。即使这样做会损失一点精度,也值得,因为真实执行器受不了训练策略里那种剧烈抖动力矩。
% 在 Simulink 的 RL Agent 块之后加一个饱和模块 % Saturation 上限: 15 N*m,下限: -15 N*m % 然后再串联一个 Transfer Fcn: 1/(0.02s+1) 做力矩平滑这个串联结构看起来打破了“强化学习直接输出最终指令”的纯粹性,但它换来的是部署安全性。饱和限制同时也保护了训练过程,如果力矩输出超过物理极限,仿真模型会出现积分发散,奖励曲线瞬间崩掉,而且这种崩溃看起来很像算法不收敛,实际是环境数值爆了。
3.3 奖励函数设计:量纲配平与四类常见“走捷径”行为
自适应控制的奖励函数至少包含三项:跟踪误差、控制能量、控制变化率。跟踪误差用二次型让策略对偏差敏感;控制能量项防止策略用大额力矩硬顶;控制变化率项抑制抖动。一个可以稳定跑通的奖励函数示例像这样:
function reward = tracking_reward(theta, theta_dot, theta_ref, tau, tau_prev) err = theta - theta_ref; err_dot = theta_dot - theta_ref_dot; reward = -(err^2 + 0.1 * err_dot^2 + 0.001 * tau^2 + 0.005 * (tau - tau_prev)^2); end这个函数写成 Simulink 里的 MATLAB Function 块,输入直接接状态和动作信号。参数配比的经验是:先让误差项的量级在 0.01~1 之间,能量项和抖动项分别压到误差项的 1/10 和 1/100 左右。如果力矩值本身就在 10N·m 量级,0.001 的系数乘出来是 0.1,和误差项同一量级,这时候策略就会在“减少误差”和“节省力矩”之间找到平衡,而不是被能量项主导到不敢出力。
奖励设计里常见的“走捷径”现象有三个。第一个是策略把关节停在目标角度附近靠重力抵消误差,而不是真正跟踪轨迹,表现为误差不大但速度始终为零,解决办法是在误差项之外再加一个与速度相关的跟踪项。第二个是策略学会在每个 episode 开始时故意让初始状态更有利,如果 ResetFcn 在 episode 开始后才改变目标位置,策略可能通过“先不动、等目标过来”的方式刷奖励,解决办法是确保目标角度在 episode 开始时已固定。第三个是奖励项之间互相打架,能量项过大会导致策略“躺平”,干脆输出零力矩,奖励虽低但稳定,训练曲线直接收敛到一个次优值。
3.4 域随机化:自适应训练的临门一脚
域随机化是自适应控制里最值得投入时间的设计技巧。它的思想非常简单:训练时不只用一组固定的物理参数,而是在每个 episode 开始前随机采样。上一章的 ResetFcn 已经把负载质量随机到 0.5~2.5kg,这里可以再做三件事:随机化摩擦系数、随机化初始角度、随机化目标轨迹幅值。
function in = localResetFcn(in) blk = 'rl_single_joint/friction'; in = setBlockParameter(in, blk, 'Value', num2str(0.05 + 0.3 * rand)); blk_init = 'rl_single_joint/theta0'; in = setBlockParameter(in, blk_init, 'Value', num2str(-1.2 + 2.4 * rand)); blk_target = 'rl_single_joint/theta_ref'; in = setBlockParameter(in, blk_target, 'Value', num2str(0.6 + 1.2 * rand)); end这里的参数要说明白:随机范围不是越大越好。范围太大,训练难度剧增,可能 5000 个 episode 都学不到一个能用的策略;范围太小,测试时超出训练分布,策略立刻失效。我通常的做法是“先扫后扩”:先用小范围验证链路能收敛,再把范围逐步扩大到想覆盖的工况。这也能从训练曲线看出来——如果平均奖励曲线的波动幅度突然变大,说明随机范围已经超出了当前网络容量的学习上限。
提示:域随机化后的训练收敛时间通常是固定参数训练的 2 到 4 倍。如果你在论文里对比算法效果,这个成本必须提前算进实验周期,否则很容易在半途就开始怀疑算法实现有问题,然后白调两周参数。
4. 算法选型与超参数配置:DDPG、TD3、SAC该怎么选
状态动作奖励都定好以后,下一步是选深度强化学习算法。很多初学者的习惯是最新什么火用什么,但机器人连续控制场景里,合适的意思是“能让训练在可接受时间内收敛,且部署策略不会乱抖”。DDPG 是链路验证的首选,TD3 是默认主力,SAC 是在探索性任务上的加强选项。
4.1 为什么连续动作控制不能用 DQN
DQN 的核心动作选择是 argmax 一个 Q 表或 Q 网络输出,它天然适合离散动作空间,比如向左、向右、抓取、放下的逻辑决策。机器人关节力矩是连续变量,如果把力矩离散成 200 个档位,Q 网络的输出头会变成 200 维,每个动作都要采样估计,样本利用率极低;更麻烦的是,离散化会让控制动作出现台阶式跳变,机械臂的加速度曲线会非常不平滑。
这就引出了 actor-critic 架构的必要性:actor 网络直接输连续动作向量,critic 网络评估这个动作的 Q 值,两者交替训练。MATLAB RL Toolbox 里的 DDPG、TD3、SAC 都是这类结构。DDPG 是最简单的一种,适合作为第一版跑通链路,但训练稳定性不如 TD3;TD3 在 DDPG 上加了三个改动,是机器人连续控制里的稳妥选择;SAC 用最大熵框架让策略保持随机性,探索能力强,但训练更慢,超参更敏感。
4.2 TD3 的三个改进点与对应参数配置
TD3 对 DDPG 的改进是有明确工程对应关系的。第一,它用了两个 critic 网络,Q 值取两者最小值,压制 Q 值高估问题,这在机器人场景里表现为训练后期策略不会突然震荡。第二,策略网络采用延迟更新,actor 更新频率低于 critic,实践中用 TargetUpdateFrequency 控制。第三,目标策略平滑,在计算目标 Q 值时给动作叠加一个小噪声,防止策略对尖锐的 Q 函数过度拟合,这也是部署后策略输出平滑度较好的原因。
agentOpts = rlTD3AgentOptions; agentOpts.SampleTime = 0.01; agentOpts.TargetUpdateFrequency = 2; agentOpts.MiniBatchSize = 256; agentOpts.ExplorationModel.StandardDeviation = 0.1; agentOpts.TargetPolicySmoothModel.StandardDeviation = 0.1; agentOpts.TargetPolicySmoothModel.Variance = 0.005; agentOpts.ActorOptimizerOptions.LearnRate = 1e-4; agentOpts.CriticOptimizerOptions.LearnRate = 1e-3; agentOpts.CriticOptimizerOptions.GradientThreshold = 1;这些参数我解释一下。TargetUpdateFrequency 设置为 2 表示 critic 每更新 2 次,actor 才更新 1 次,延迟更新有效避免了 actor 被还没稳定的 critic 带偏。ExplorationModel 的 StandardDeviation 是训练期间叠加在动作上的探索噪声标准差,0.1N·m 的噪声对力矩控制来说已经不小,噪声太大会让机械臂模型剧烈抖动。TargetPolicySmoothModel 的两个参数是目标策略平滑项,Variance 控制噪声方差,StandardDeviation 控制加到目标动作上的扰动上限。
注意:不同 MATLAB 版本里 rlTD3AgentOptions 的属性名略有差异。建议训练前先运行
get(agentOpts)查看当前版本支持的字段,如果属性名对不上,以get输出的为准来改,而不是硬套网上旧教程的写法。
4.3 SAC 的熵权重与训练稳定性权衡
SAC 相比 TD3 的最大区别是引入了熵正则项,策略在训练时不仅最大化累积奖励,还最大化动作分布的熵。通俗地说,它不会很快锁定到一个确定性策略,而是保持一定随机性继续探索。对自适应控制来说,这种探索能力有助于找到更鲁棒的策略,尤其当负载变化范围很大时,SAC 学到的策略面更宽。
但代价也很直接。SAC 需要训练一个额外的熵权重系数,Soft Actor-Critic 里这个系数可以自动调节,MATLAB 里对应 EntropyWeightOptions。如果熵权重初始值设得太大,策略会长时间停留在随机探索状态,训练曲线涨得很慢;设得太小,SAC 就退化成接近 TD3 的行为。机器人控制任务里我一般用 TD3 先找到一套可行的状态动作设计,再用 SAC 做第二轮“探索增强”训练,这样能最大程度减少 SAC 调参带来的时间损失。
| 指标 | DDPG | TD3 | SAC |
|---|---|---|---|
| 训练稳定 | 中 | 高 | 中高 |
| 样本效率 | 中 | 中高 | 低 |
| 探索能力 | 低 | 中 | 高 |
| 超参数量 | 少 | 中 | 多 |
| 适合场景 | 链路验证 | 默认主力 | 大范围随机化/难探索任务 |
4.4 超参配置的一组保守起点
给一组可以直接用的起步参数,前提是模型步长 0.01s、单关节模型、负载随机范围 0.5~2.5kg。MiniBatchSize 用 128 或 256,太小的 batch 让梯度估计噪声变大;ExperienceBufferLength 设成 1e6,确保经验池能覆盖足够多“不同负载+不同初始角度”的组合,如果经验池太小,域随机化的效果会被严重稀释。Actor 学习率 1e-4,Critic 学习率 1e-3,Critic 学习率比 Actor 高一个数量级是常规做法。
MaxEpisodes 和 MaxStepsPerEpisode 的关系需要一起考虑。如果每个 episode 是 3 秒仿真、步长 0.01s,MaxStepsPerEpisode 就是 300;如果目标轨迹变长,这个值要同步加大,否则策略还没走完一个完整轨迹就被强制结束,它永远学不会长时间跟踪。我习惯先跑 300 个 episode 观察 reward 曲线的波动范围,再决定是加 episode 数还是改奖励系数,而不是一口气跑到 5000 回合才发现奖励设计有问题。
5. 避坑清单:五次训练翻车之后整理出的排障顺序
这段算是我在机器人强化学习控制项目里被耽误最久的五个问题。每个问题都是真真切切发生过的,现象、根因、解决方式一条条对清楚,能帮你省下至少一周的排查时间。
5.1 仿真步长与 RL 采样时间不匹配:曲线在涨,控制器其实没学到东西
现象:训练曲线一路上涨,看起来学得不错,但把训练好的策略拿去做验证仿真,发现输出轨迹完全跟不上目标,甚至在目标点附近来回振荡。更迷惑的是,把奖励曲线和动作曲线放在一起看,动作输出的变化非常缓慢,像是在“慢动作”控制。
原因:Simulink 求解器固定步长设了 0.0001s,而 RL Agent 的 SampleTime 设成了 0.1s。策略每个控制周期之间相隔 1000 个仿真步,它看到的状态是 0.1s 前的旧状态,动作输出后系统早就跑远了。训练曲线上涨只是因为策略学会了“在旧状态上做平均补偿”,并没有学到真正的动态响应。
解决:把 Simulink 求解器设为固定步长离散,步长 0.01s,同时把 rlTD3AgentOptions 里的 SampleTime 也设为 0.01,两者严格一致。如果系统动态太快必须用更小步长,就把 Simulink 步长设 0.001s,RL SampleTime 设为 0.01s,此时需要在 Agent 输入端加一个 Rate Transition 模块明确信号语义,否则 Simulink 会报采样率不匹配的警告。
5.2 奖励项量纲差太大:误差项被力矩惩罚项淹没
现象:训练刚开始几轮,奖励就掉到 -10 以下,之后一直徘徊不上来。看训练统计里每一步的平均动作,输出几乎为零,策略“罢工”。
原因:奖励函数里误差项是 (theta - theta_ref)^2,量级约为 0.01,而力矩惩罚项 1.0 * tau^2 在力矩输出 3N·m 时就产生 9 的负奖励。误差项提供的学习信号完全被能量项淹没,策略快速发现“最安全的行为是零输出”。
解决:把每一项都做量纲分析后重新配权。我的配法是先记录一个随机策略下的平均误差、平均力矩、平均力矩变化率,然后让三项的权重分别乘以这些平均值后量级相近。比如随机策略下平均误差平方是 0.05,平均力矩平方是 25,那么能量项系数就设成 0.002 左右,保证三项对奖励的贡献在同一水平线上。
% 先跑 50 个 episode 收集量级信息 % 记录 avg_err_sq, avg_tau_sq, avg_dtau_sq % 然后设置权重,使三者贡献接近 w_err = 1.0; w_tau = w_err * avg_err_sq / avg_tau_sq; w_dtau = w_err * avg_err_sq / avg_dtau_sq;5.3 观测不归一化:换一个初始位置,策略立刻翻车
现象:训练时目标角度固定在 0.5rad 附近,模型测试时改成 1.2rad,策略输出的力矩明显偏小,跟踪动作变形。把状态值打印出来发现,角度 1.2 和训练时见过的 0.5 差距太大,actor 网络对这个输入范围内的处理完全是外推。
原因:神经网络对输入数值范围很敏感。角度、角速度、负载质量这几个观测的物理单位不同,数值范围差异又大,如果不归一化,网络在训练时只见过角速度 0~2、角度 -1~1 这个范围,测试时一旦超出就失去插值能力。
解决:在 Simulink 模型里对观测信号做归一化。角度除以 pi,角速度除以最大角速度,误差除以允许最大误差,负载质量除以它的量程上限。让所有观测落在 [-1, 1] 之间。同时 ResetFcn 里初始角度随机范围要覆盖测试可能出现的范围,不能只在 0.5rad 附近随机。
% 在 MATLAB Function 块里对 obs 做归一化 function obs_norm = normalize_obs(theta, theta_dot, err, m_hat) obs_norm = zeros(6, 1); obs_norm(1) = theta / pi; obs_norm(2) = theta_dot / 5.0; obs_norm(3) = err / pi; obs_norm(4) = err_dot / 5.0; obs_norm(5) = theta_ref / pi; obs_norm(6) = m_hat / 5.0; end5.4 观测向量维度错位:Simulink 里“看不见”的 [1x6] 和 [6x1]
现象:训练点击开始后立刻报错,错误信息大致是“Error in port widths or dimensions. Output port 1 of ‘model/MATLAB Function’ is a [1 6] signal.”,但看 Simulink 模型里端口标注,明明显示连接是正确的。
原因:MATLAB Function 块里如果写obs = [theta; theta_dot; ...]生成的是 6x1 列向量,但如果某个信号本身是从 Bus 或 Mux 里解出来的行向量,最终拼接结果可能变成 1x6。RL Agent 块要求观测输入是列向量,维度严格匹配,Simulink 的端口宽度检查不像普通信号线那样给出直观提示,经常会漏过去。
解决:在所有产出观测向量的 MATLAB Function 块末尾加一行obs = obs(:);强制转为列向量,同时在 RL Agent 块的输入端口右键“Display Signal Dimensions”,把信号维度显示出来核对。动作输出端也是一样,如果动作向量经过 Mux 或 Selector 再进执行器,用tau = tau(:)保证形状正确。
5.5 训练噪声没有递减:验证时策略看起来“没学会”
现象:训练曲线已经收敛,AverageReward 保持稳定,但同样一个 Agent 拿到独立验证环境里跑,输出动作不停左右跳变,关节力矩曲线像是白噪声。录像下来看,机械臂在原地颤抖,根本没法用。
原因:训练时叠加的探索噪声在验证阶段仍然存在。DDPG/TD3 在 Simulink 部署时,如果直接复用训练 Agent 对象,它可能继续从 Explorer 中采样随机动作,而不是使用确定性策略。MATLAB 的 getPolicy 函数可以抽出确定性策略,这行代码很多人会漏掉。
解决:从训练好的 agent 里抽取确定性策略再部署验证。
policy = getPolicy(agent); env_deploy = rlSimulinkEnv('rl_single_joint_deploy', 'rl_single_joint_deploy/RL Agent', obsInfo, actInfo); % 在 Simulink 里把 RL Agent 块的 Agent 属性替换为 policy注意:如果在真实机器人或者高保真模型上验证,部署策略前还要对动作输出做限幅,并且准备一个急停逻辑。训练时策略可以探索边界动作,验证时你不希望它真的把关节打到机械限位。
6. 训练完怎么验证自适应能力:外部模式测试、负载扫描与导出习惯
训练收敛了不等于项目结束。自适应控制算法最关键的验收环节是测试“当系统参数在训练范围内变化时,策略是否仍然能保持性能”。我一般用三个递进步骤:负载扫描、Simulink 外部模式实时仿真、最后才是生成代码部署到物理机器人。
6.1 用负载和摩擦系数扫描测试自适应边界
我习惯在训练结束后写一个负载扫描脚本,把质量从训练范围的最小值扫到最大值,每个质量点跑一次 Simulink 仿真,记录稳态跟踪误差。
loads = [0.5 1.0 1.5 2.0 2.5]; for i = 1:numel(loads) set_param('rl_single_joint/mass', 'Value', num2str(loads(i))); simOut = sim('rl_single_joint', 'StopTime', '10'); y = simOut.yout; % 记录跟踪误差信号 err_signal = y.getElement('track_err').Values.Data; steady_err(i) = max(abs(err_signal(end-100:end))); end plot(loads, steady_err, 'o-');这组代码的作用是量化策略的自适应边界。如果稳态误差在负载变化时基本保持在一个量级,说明策略确实学到了自适应补偿;如果误差随负载增大明显上升,说明训练时的负载随机范围还不够覆盖,或者观测里的负载信息没有被网络充分利用。这个表格可以作为验收标准:
| 测试点 | 负载 | 摩擦系数 | 验收标准 |
|---|---|---|---|
| 边界低值 | 0.5kg | 0.05 | 稳态误差 < 0.02rad |
| 典型中值 | 1.5kg | 0.15 | 稳态误差 < 0.02rad |
| 边界高值 | 2.5kg | 0.30 | 稳态误差 < 0.03rad |
| 训练外推 | 3.0kg | 0.40 | 策略不崩溃,允许误差放宽 |
训练外推那一行特别说明一下:如果测试负载超出训练时的随机范围,策略性能下降是正常的,关键是看它是否“有界”。只要不出现发散震荡,说明策略对未见过参数仍有一定泛化能力,这已经是自适应控制可以接受的边界。
6.2 用 Simulink 外部模式跑实时仿真
普通仿真验证的是数学模型闭环,外部模式则能让模型跑在实时内核上,执行步长更稳定,也更接近部署环境。Simulink 工具栏把仿真模式从 Normal 切到 External,选择 Desktop Real-Time 目标,设置固定步长和硬件执行参数,然后重新下载模型运行。
外部模式在我工作里的实际用途不是代替真机,而是验证“策略计算时间是否满足控制周期”。强化学习策略是一个神经网络,前向推理时间会和网络层数、输入维度相关。如果计算时间超过 10ms 的控制周期,策略在普通仿真里照样能跑,但真实控制器上就会丢步。外部模式下打开信号监视,看关节力矩的刷新率是否和控制周期一致,这一步建议至少跑 10 分钟连续仿真,中间手动改变模型里的 Stiffness 参数观察响应,确认稳定。
6.3 生成代码与部署线路选择
如果你要部署到真实机器人,常见的路线是把训练好的策略导出成 Simulink 模型或 MATLAB Function,再通过 Simulink Coder 生成 C/C++ 代码。生成的代码只包含神经网络的前向推理和必要的信号处理,不依赖完整的 MATLAB 运行环境,可以集成到 ROS2 机器人开发框架里作为一个控制器节点:订阅关节状态话题,推理输出力矩话题。
如果你的真实机器人不能提供足够的安全保护,强烈建议先用离线强化学习路线再过渡。离线强化学习里 IQL 这类方法可以只利用仿真生成的固定数据集训练策略,避免在线强化学习在真机上探索时可能造成的危险动作。这个做法虽然不是必须的,但对我这种要对手腕和关节负责的工程师来说,是值得多花两周时间的安全投入。
我的一个习惯是,训练结束当天一定会做三件事:保存训练曲线图、保存带完整参数注释的 agent 创建脚本、保存 ResetFcn 里所有随机范围的记录。以前我嫌麻烦省略过,半个月后回看实验记录,想不起来某个奖励系数是怎么配出来的,那感觉比训练不收敛还糟糕。还有一个习惯是,任何 Agent 部署到外部模式之前,先在仿真里把动作信号的频率谱画出来,如果高频分量过多,就先加一层平滑滤波再考虑生成代码。这套流程走下来,训练出来的策略至少能保证在仿真里是可信的,希望对你在自己的机械臂项目上少踩几次坑。
本文还有配套的精品资源,点击获取