ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于MATLAB/Simulink的Actor-Critic四水箱液位控制实现

基于MATLAB/Simulink的Actor-Critic四水箱液位控制实现 简介针对四水库系统的强化学习控制任务该资源以参与者-批评者Actor-Critic算法为核心提供了一套完整的MATLAB/Simulink实现方案。源码将Simulink仿真模型与独立MATLAB脚本相结合覆盖状态定义、动作选择、奖励计算、网络训练等关键环节参数化编程让水位、流量、学习率等参数均可灵活修改注释详细清晰便于二次开发与算法对比。压缩包共7个文件主要包括.m主程序、.slx仿真模型、.slxc缓存、.mat案例数据及PDF说明文档整体大小仅2.26MB轻量易用。附赠的案例数据可直接运行无需额外配置即可复现四水库协调控制过程直观展示Actor-Critic方法如何通过与环境的交互逐步改善控制策略。资源面向计算机、电子信息工程、数学等专业学生适用于课程设计、期末大作业和毕业设计等场景。目前已有41人学习下载适合需要快速上手强化学习控制应用的研究者参考。1. 从四水箱控制看 Actor-Critic 的落地方式提到强化学习在过程控制里的应用很多人先想到仿真推车或机器人其实水箱液位控制才是入门最快的场景系统非线性可控状态量容易测量奖励函数也直观。这个代码包把 Actor-Critic参与者-批评者算法用 MATLAB 实现接到一个四水箱 Simulink 模型上。状态是四个水箱的液位动作是两个泵的流量指令奖励按照液位与目标值的误差构造。整套代码在 MATLAB 2014 到 2024a 上都能运行脚本负责算法slx 负责被控对象。适合做课程设计、期末大作业也适合工作后想评估强化学习控制方式与传统 PID 差距的工程师。解压后没有复杂的配置改参数集中在脚本前部所以也适合第一次接触策略梯度的人进行代码级学习。2. Actor-Critic 控制四水箱系统的原理与状态-动作设计2.1 四水箱系统的状态空间与控制目标四水箱系统是一个典型的 MIMO 过程控制对象。两个水泵分别向上层水箱注水水箱之间通过连接管道互相流通四个液位都受两个泵流量的联合影响。控制目标是把四个液位稳定在设定的参考值附近同时尽量避免大超调和频繁的阀门动作。与单水箱不同四水箱的耦合很强一个泵的流量会同时改变多个水箱的液位所以反馈控制必须基于全部四个状态。在这个代码包里状态被定义为连续实数向量动作也是连续量。连续动作意味着 Actor 网络不能简单选择离散动作而是输出高斯分布的均值有时还包括标准差。训练时从分布中采样得到实际动作测试时通常直接取均值这样可以保证控制信号的连续性。% 状态归一化降低量纲影响 h_norm h ./ h_range; % 动作归一化到 [-1, 1]适配 tanh 输出 u_norm (u - u_min) ./ (u_max - u_min) * 2 - 1;归一化处理有两个作用。第一网络权重的初始范围通常在 -1 到 1 之间如果输入液位是几十厘米不归一化会让第一层加权和很快饱和第二当量程改变时只需要修改归一化系数不需要重训网络。脚本开头的参数区就是管理这些系数的这也就是摘要里强调的参数化编程。2.1.1 为什么用四状态而不是两个状态如果只取两个关键水箱的状态Critic 网络对价值函数的估计会丢失耦合信息。比如上方水箱液位变化会改变对下方水箱的入口压力人为忽略这个关系策略就无法判断当前偏差是由哪个泵引起的。代码采用完整的四维状态用维度增加换取控制精度。代价是网络输入层节点增加训练所需样本量会有所上升但四节点在这个问题里仍然极其轻量。2.2 Actor 网络与 Critic 网络的职责划分Actor-Critic 的核心思想是把策略梯度与价值函数结合起来。Actor 学习策略负责给出动作Critic 学习价值函数负责评价当前状态的好坏。两者共享状态输入但更新的损失函数不同。这里常用两个两层全连接网络中间用 ReLU 激活输出层根据动作范围接 tanh。% Actor 网络输入为四维状态输出为二维连续动作均值 actor_layers [ featureInputLayer(4, Normalization, none) fullyConnectedLayer(16) reluLayer fullyConnectedLayer(16) reluLayer fullyConnectedLayer(2) tanhLayer ]; % Critic 网络输入为四维状态输出为状态的价值 critic_layers [ featureInputLayer(4) fullyConnectedLayer(32) reluLayer fullyConnectedLayer(16) reluLayer fullyConnectedLayer(1) ];这里 Actor 输出层只画了均值实际采样时还要补一个表示探索程度的标准差。代码包里通常用一个可变的sigma参数来简化实现。固定标准差比较容易调试但训练结束后策略里会残留固定噪声导致控制动作抖动。建议在训练后期把sigma逐步衰减到执行器分辨率的水平。2.2.1 Critic 网络结构是否需要更深四水箱系统的价值函数形态并不复杂两个隐含层足够。但需要注意的是Critic 的宽度一般比 Actor 更宽因为价值函数的性质通常比策略更平滑需要更多神经元来拟合边缘状态。如果 Critic 过窄TD 误差的方差会增大过宽则容易过拟合到早期样本导致训练过程中价值估计漂移。常见做法是 Critic 的第一个全连接层是 Actor 的两倍宽。2.3 奖励函数与 discounted return 的设定奖励函数是强化学习目标的唯一表达直接影响策略最终行为。在水箱控制问题里常用负的误差平方和来刻画惩罚。平方项对大偏差更敏感所以算法在初始阶段会优先处理最大误差但接近稳态时平方项数值变小梯度也变小可能出现收敛变慢。另一种是绝对误差梯度更平稳但很难零稳态误差。% 单步奖励负的权重平方误差 r - (w1 * (h(1) - h1_ref)^2 w2 * (h(2) - h2_ref)^2 ... w3 * (h(3) - h3_ref)^2 w4 * (h(4) - h4_ref)^2);四个权重w1到w4决定了算法对不同水箱的关注度。如果某个水箱经常超调就适当调大它的权重。代码包里没有固定权重需要根据仿真结果反复试验。另一个做法是把动作幅度也放进奖励避免执行机构饱和但四水箱问题中通常不需要因为动作本身有归一化限幅。折扣因子 gamma 出现在 Critic 的 bootstrapping 计算里它决定了未来奖励的折算系数。gamma 接近 1 时Critic 会重点估计长期回报策略更保守不容易短视gamma 过小则会优先优化短期误差。水箱系统时间常数大通常取 0.95 到 0.99 之间。如果你发现策略对设定值变化的响应太慢可以试着把 gamma 从 0.99 降到 0.96。2.4 参数初始化与超参数表网络权重的初始化方式对训练稳定性影响很大。MATLAB 深度学习层默认使用类型相关的初始化函数但手写梯度更新时需要自己对权重矩阵做缩放。常见做法是用randn乘以一个小数比如 0.1防止初始分数过大导致所有 tanh 单元饱和。参数常用值对训练的影响Actor 学习率 alpha_a0.0001 ~ 0.001过大策略发散过小收敛慢Critic 学习率 alpha_v0.001 ~ 0.01通常比 Actor 快 3 倍以上折扣因子 gamma0.95 ~ 0.99接近 1 更重视长期回报轨迹长度100 ~ 500 步决定单次更新的回报范围探索标准差 sigma0.05 ~ 0.2探索程度后期需衰减这些数值并不是代码包里的原始配置而是这类连续控制问题的通用起点。值得注意的是改变 gamma 后奖励曲线的绝对均值也会变化所以对比不同 gamma 的效果时应该看误差面积或稳定时间而不是直接比较平均奖励。3. 运行与复现AtorCritic4states.m 与 Simulink 模型配合3.1 文件结构和版本兼容性说明解压后你会看到modelo_simulink.slx、AtorCritic4states.m、TG2.pdf以及一个slprj目录。.slx是当前 MATLAB 版本的 Simulink 模型.m是算法入口脚本PDF 是说明文档slprj是 Simulink 的中间工作目录。slprj里保存的是模型编译信息删除后会自动重新生成所以不影响工程运行。文件路径不要带中文且最好放在 MATLAB 用户目录下避免权限问题。由于slx是压缩格式的模型文件版本兼容性很关键。MATLAB 2019a 及以上可以打开 2014 保存的模型但高版本保存后低版本无法打开。如果你只有 MATLAB 2014建议先把modelo_simulink.slx转存为.mdl格式作为备份或者按照 PDF 里的系统方程手动搭建。四水箱的数学模型并不复杂手动搭建的过程还能帮你更清楚状态量顺序。3.2 从脚本启动训练的完整步骤打开 MATLAB把当前文件夹切换到解压目录确认工作区干净然后直接运行脚本。下面是加载模型和调用仿真的一般写法。% 指定模型名 model modelo_simulink; % 仅加载模型不打开编辑器窗口 load_system(model); % 设置仿真结束时间 sim_time 500; % 运行仿真返回状态轨迹 [t, x] sim(model, sim_time); % 检查状态输出 disp(size(x)); plot(t, x);load_system的作用是把模型读入内存但不显示界面适合在训练循环里反复调用避免编辑器开销。sim(model, sim_time)的第二个参数直接指定仿真时长。如果模型内部有离散采样步长sim会自动与模型的求解器配置一致。注意第一次调用sim时模型需要编译时间较长后续调用会快很多这是正常现象。3.2.1 高版本返回的 SimulationOutput 结构MATLAB 2019a 之后sim的默认返回值变成Simulink.SimulationOutput对象上面那种多输出写法会报错。此时需要改成out sim(model, sim_time); t out.tout; x out.states; % 需要模型设置了状态输出如果不确定字段名用fieldnames(out)查看对象包含哪些字段。常见的问题是out.states为空因为模型没有开启状态保存。解决方法是打开模型在模型设置中的 Data Import/Export 面板勾选 “Output states”或者直接把四个液位信号连到 To Workspace 模块。这类问题在课程设计中遇到频率极高脚本本身没毛病但环境配置对不上。3.2.2 运行报错的常见检查点如果运行脚本提示未定义函数或变量先检查当前路径是否真的包含所有文件。MATLAB 的路径搜索顺序是当前文件夹、MATLAB 路径、内置函数。如果脚本里调用了trained_params.mat但这个文件还没有生成同样会报错。另一个高发问题是脚本名与函数名相同MATLAB 会优先执行脚本导致函数递归调用。建议把主脚本命名成main_train_tanks.m把算法函数放单独的.m文件里避免混淆。3.3 在 Simulink 中观察水位与执行器输出训练结束并把网络权重保存成结构体之后可以在 Simulink 模型里添加一个 MATLAB Function 模块把训练得到的策略网络嵌入为控制器。这样模型就是完整的闭环环境由模型提供控制器由自定义函数提供。function u policy_controller(h1, h2, h3, h4) % 离散化后的策略网络前向计算 p coder.load(trained_params.mat); % 归一化状态 state [(h1 - p.h1_ref) / p.h_range(1), ... (h2 - p.h2_ref) / p.h_range(2), ... (h3 - p.h3_ref) / p.h_range(3), ... (h4 - p.h4_ref) / p.h_range(4)]; % 两层全连接 tanh hidden max(0, p.W1 * state p.b1); u tanh(p.W2 * hidden p.b2); end上面的coder.load能把训练好的权重从.mat文件加载到 Simulink 的 MATLAB Function 模块中不会出现在代码生成时无法解析的机器学习对象。这里的关键是状态顺序要与训练时一致否则控制动作会匹配到错误的水箱。在实际项目里我通常会在训练脚本最后强制打印一次状态列名和顺序降低部署时的误配概率。3.4 参数修改后模型同步的坑如果脚本里的目标水位改了Simulink 模型里的参考信号也要同步。避免这个问题的常见做法是把参考水位定义成工作区变量模型里的 Constant 模块直接引用变量名。% 在模型外用结构体定义参考值 param.h_ref [12; 10; 8; 9]; param.h_range [20; 20; 20; 20]; % 写入工作区 assignin(base, param, param);这样训练和模型用的是同一个变量不会再出现脚本里改了一处、模型里另一处的错误。需要注意的是assignin只是写入基础工作区如果 Simulink 在快速加速模式下可能需要在模型配置里设置为普通加速或把它作为模型参数传入。更严谨的方式是定义Simulink.SimulationInput对象但四水箱这种简单模型用assignin已经够用。4. 核心代码逐段拆解与训练调参4.1 策略梯度与 TD 误差的更新逻辑AtorCritic4states.m的训练循环通常包含四个部分与环境交互、计算单步奖励、估计 Critic 价值、更新两个网络。这里最核心的公式是 TD 误差 delta它同时驱动两个网络的更新。% 计算 TD 误差 delta r gamma * V(s_next) - V(s); % 更新 Critic 网络参数 theta_v theta_v alpha_v * delta * dV_dtheta_v; % 更新 Actor 网络参数连续高斯策略 grad_log_pi (u - mu(s)) / sigma^2; theta_a theta_a alpha_a * delta * grad_log_pi * dmu_dtheta_a;这里的delta是实际奖励与 Critic 预测之间的偏差。如果delta为正说明当前动作的效果好于预期Actor 应该增加这个动作的概率delta为负则减少。Critic 的更新方向则是让V(s)更接近r gamma*V(s_next)。注意grad_log_pi这一行是针对高斯分布的简化导数不是一般的梯度网络公式它只是(u - mean)/variance。4.1.1 MATLAB 版本差异与梯度计算代码包里如果用深度学习工具箱的dlgradient就不需要手动推导dV_dtheta_v。但如果像早期版本那样手写两层网络就需要自己计算反向传播。常见的错误是忘记对输出层 tanh 求导导致 Actor 的梯度符号错乱。对 tanh 的导数是1 - y^2这里的y是 tanh 层的输出。如果你在 MATLAB 2014 运行脚本时跳出梯度相关的矩阵维度错误优先检查这句导数。4.2 学习率、折扣因子与 rollout 长度的实际影响rollout 长度指每次收集多少步经验后做一次参数更新。在 Simulink 里sim可以一次返回一条完整轨迹但把整条轨迹用于单次更新相邻样本相关性太强容易造成参数震荡。常见做法是把长轨迹切成长度较小的段每段计算折扣回报后更新。调参场景现象幅度建议Actor 学习率太大奖励曲线出现剧烈抖动降低到原来的 1/3gamma 太大对设定值变化响应慢从 0.99 降到 0.95rollout 太短梯度方向噪声大从 50 步加长到 150 步sigma 不衰减稳态水位波动大每 1000 轮乘以 0.95如果你在仿真中看到水位曲线接近目标值后仍然不规则振荡一个常见原因就是探索标准差过大。四水箱系统的执行机构精度如果按 0.1 算sigma 衰减到 0.1 以下才能让控制信号稳定。代码包里若没有自动衰减逻辑可以在训练循环最后加一行乘法。4.2.1 一个可复现的调参实验如果想快速验证参数对训练的影响可以固定模型、固定随机种子只改动alpha_a。跑三次分别设置 0.0001、0.001 和 0.01。记录每个参数下的前 200 轮平均奖励。你会看到小学习率那条曲线上升缓慢但不震荡大学习率那条曲线可能在前 50 轮就冲到很高的奖励随后崩下来。这就是典型的学习率过大特征。通过这个实验可以确定 Actor 学习率的合理数量级之后再微调 Critic 学习率。4.3 判断训练是否收敛的指标只看奖励曲线上升并不能说明控制器已经可用因为探索噪声会掩盖真实性能。最好在训练结束后固定随机种子关闭 sigma让策略以纯贪婪方式跑一次完整仿真。% 关闭探索噪声 sigma 0; % 固定初始状态和随机种子 rng(42); out sim(model, 1000); % 计算稳态误差 h_end out.states(end, :); ess max(abs(h_end - h_ref)); disp([稳态误差: , num2str(ess)]);固定随机种子是训练后评估的常见标准做法它保证每次测试的扰动序列相同不同训练轮次的对比才有效。如果最终稳态误差只比执行器分辨率高一点说明训练合格如果误差很大检查奖励函数里是否平衡了各个权重。另外要画水位时间曲线观察是否存在周期振荡振荡通常反映出 sigma 过大或 Critic 发散。5. 一个进阶技巧把单条轨迹改成经验池批量更新5.1 经验池替换单轨迹更新的最小实现四水箱系统非线性不强单条轨迹训练也可以收敛。但如果你想在 Simulink 模型里加入扰动或者改造成用水需求波动场景单步sim的交互方式会让样本相关性非常高训练容易陷入局部动作。常见做法是引入一个长度为 N 的经验池把每一步的状态、动作、奖励、下一状态存成一行然后随机采样一个小批量更新两个网络。% 经验池一行 exp [state, u, r, next_state]; % 存到池中 replay_buffer [replay_buffer; exp]; if size(replay_buffer, 1) buffer_size replay_buffer(1:end - buffer_size, :) []; end % 随机批量采样 idx randi(size(replay_buffer, 1), batch_size, 1); batch replay_buffer(idx, :);注意批量更新时Critic 的目标值用r gamma * V(s_next)计算但由于s_next来自历史样本对应的时间步并不连续所以不能直接用序列的累计回报必须逐样本算。这个改动很小但能显著降低连续样本的相关性。另外经验池里的奖励要按当前模型输出重新计算如果中途改变了奖励函数旧样本会污染训练通常建议清空池子再继续。对于四水箱这个量级经验池大小 1000 到 2000 就足够了不需要像图像任务那样开到几万。批量大小 32 或 64 都能跑但每回合更新次数不要超过采样次数否则也会震荡。代码包里没有现成的经验池实现你可以把它加到AtorCritic4states.m的循环体里放在delta计算之前把原来的单步更新换成批量采样即可。这样改完之后你会发现水位曲线的振荡明显变小尤其是把仿真步长调小时原本训练出的策略可能会有高频抖动批量更新能平滑掉一部分这类噪声。你把这行经验池逻辑插进去后再对比一下水位曲线的振荡幅度会看到明显变化。本文还有配套的精品资源点击获取
返回列表