
简介针对小车倒立摆平衡控制任务这份MATLAB 2022A仿真资源利用深度强化学习让倒立摆从不稳定状态逐步收敛至平衡并同时实现策略网络与Q网络两种控制方案便于对比算法效果。资源共49个文件以MATLAB脚本为主另含2张示意图片和1段avi操作录像压缩包仅685KB体积小巧目前已有309人学习。压缩包内代码依据仿真流程拆分多个模块包括环境建模、网络初始化、状态判断、动画绘图等并配有详细注释方便阅读与二次开发。附带的操作录像演示了运行环境设置和操作过程能帮助读者避开MATLAB当前文件夹路径等常见坑点快速复现动画效果。对于自动化、机器人等相关专业学生及深度强化学习入门者这份资源既可用于课程设计参考也可作为对比理解策略网络与Q网络学习行为的实践样例。1. 小车倒立摆为什么值得用深度强化学习重做一遍我做过一个用LQR控制的小车倒立摆小角度范围内稳得很好但把初始摆角从5度改成15度再跑一次系统直接发散。原因很明确线性控制器只在小扰动附近有效而大角度下的非线性力矩和速度耦合项全被忽略了。深度强化学习不需要显式推导这些非线性项它靠一次次试探与环境交互直接学出一张从状态到控制力的映射或者一个能评估“状态-动作”好坏的Q函数因此能把稳定域撑得比线性控制器更大。这套方案要解决的事情就是用MATLAB把策略网络和Q网络两条路线都跑通在同一个小车倒立摆环境上做对比并拿到能复现的仿真录像。适合正在做控制算法对比的学生以及需要一个完整深度强化学习仿真demo的工程师。下面先立模型再讲两套网络各自怎么实现、怎么调参、怎么排错。2. 建模与选型小车倒立摆的状态空间和两类网络的分工2.1 小车倒立摆的物理模型与RL接口定义先立模型。小车倒立摆的标准状态是[x, x_dot, theta, theta_dot]分别表示小车水平位移、水平速度、摆杆相对竖直方向的角度、角速度。控制量是施加在小车上的水平力F范围取[-10, 10]N。MATLAB的Reinforcement Learning Toolbox里有一个内置CartPole环境但动作默认只有左右两档不适合做连续控制对比所以我用rlFunctionEnv自定义环境把四阶非线性动力学直接写进step函数里。这样做的好处是策略网络用连续力Q网络用离散力两个agent共用同一套物理模型对比起来公平。环境定义代码如下% 状态定义: [x; dx; theta; dtheta] stateInfo rlNumericSpec([4 1], ... LowerLimit, -inf*ones(4,1), ... UpperLimit, inf*ones(4,1)); stateInfo.Name CartPole States; % 连续动作定义: 水平力, 单位N actContinuous rlNumericSpec([1 1], ... LowerLimit, -10, UpperLimit, 10); actContinuous.Name Horizontal Force; % 离散动作定义: 5个力度档位, 后面DQN用 actDiscrete rlFiniteSetSpec([-10 -5 0 5 10]); actDiscrete.Name Discrete Force; % 生成两个环境, 物理模型相同, 只是动作空间不同 envContinuous rlFunctionEnv(stateInfo, actContinuous, cartpoleStep, cartpoleReset); envDiscrete rlFunctionEnv(stateInfo, actDiscrete, cartpoleStep, cartpoleReset);rlNumericSpec定义连续的状态或动作维度rlFiniteSetSpec定义离散动作集合。这里的物理模型是四阶非线性模型具体运动方程写在cartpoleStep里。两步之间用固定步长dt 0.02对应50Hz控制频率。下面的step函数同时被两个环境调用离散动作传入时还是一个标量力只是取值被限制在档位上。function [nextObs, reward, isDone, loggedSignals] cartpoleStep(obs, action) % 系统参数 g 9.8; % 重力加速度 m 0.2; % 摆杆质量 M 1.0; % 小车质量 l 0.5; % 摆杆质心到转轴长度 dt 0.02; % 控制周期 x obs(1); dx obs(2); theta obs(3); dtheta obs(4); F action(1); s sin(theta); c cos(theta); % 四阶非线性动力学, 由拉格朗日方程推导得到 den M m * s^2; ddx (F m*l*dtheta^2*s m*g*s*c) / den; ddtheta (-(Mm)*g*s - F*c - m*l*s*c*dtheta^2) / (l*den); % 显式欧拉积分 dx dx ddx*dt; dtheta dtheta ddtheta*dt; x x dx*dt; theta theta dtheta*dt; % 奖励: 竖直为正, 角度偏离扣分, 大力动作扣分 reward 1 - 5*theta^2 - 0.01*F^2; nextObs [x; dx; theta; dtheta]; isDone abs(theta) 0.5; % 角度超过约28.6度视为失败 loggedSignals []; end这段代码里有几个关键参数角度惩罚系数5力度惩罚系数0.01失败阈值0.5弧度。显式欧拉在dt0.02下对这个系统足够稳定但如果把控制频率降到20Hz以下也就是dt大于0.05能量漂移会明显变大训练曲线会出现莫名其妙的抖动。需要更高精度时可以把积分段换成四阶Runge-Kutta改动量不大但会让每步仿真时间增加一倍以上。2.2 策略网络与Q网络在控制问题里的本质区别容易混淆的一点是DDPG这类actor-critic算法内部也含有一个Q网络也就是critic但标题里说的“策略网络”指的是当前控制策略的载体即actor“Q网络”指的是用值函数隐式表达策略的DQN。两者的学习目标完全不同。策略网络直接输出动作。以DDPG为例actor网络把四维状态映射成一个连续力critic网络负责给这个动作打分actor再用critic的梯度更新自己。Q网络则输出每个离散动作的期望回报推理时取Q值最大的动作执行策略并不显式存在而是“先算Q值再取最大”的隐式策略。用一张表可以看得很清楚对比维度策略网络DDPG的actorQ网络DQN输出连续力标量每个离散动作的Q值策略形成直接输出动作对Q值取argmax梯度来源dQ/da * dμ/dθTD误差反向传播探索方式高斯噪声或OU噪声ε-greedy目标网络更新软更新每步都混入旧参数硬更新每隔N步直接拷贝从表里能看出DDPG和DQN本质上是两种策略表达方式。DDPG擅长连续力控制但探索噪声和软更新系数很敏感DQN能稳定训练但动作离散化会限制最终控制精度。MATLAB中对两者的实现分别是rlDDPGAgent和rlDQNAgent这个选型决定了后面所有网络结构和训练参数。当状态从一维向量扩展到图结构时策略网络的输入层还要换成图神经网络那是图强化学习与深度强化学习结合的方向倒立摆场景用不到。2.3 两套方案共用的统一参数基线对比实验最怕的是给两个算法不同的预算。所以我先把两个agent里能统一的超参全部对齐折扣因子0.99经验池1e5mini-batch大小128每回合最大步数500最多训练1000回合。参数DDPGDQNMiniBatchSize128128ExperienceBufferLength1e51e5DiscountFactor0.990.99目标网络更新TargetSmoothFactor0.005TargetUpdateFrequency4探索机制NoiseOptions.Variance0.2ε从0.9衰减到0.05这里没有把DDPG的探索噪声设成常见的0.1而是故意给到0.2因为倒立摆的相对稳定区域很小探索不足很容易让actor收敛到“一直往左推”的局部最优。DQN的TargetUpdateFrequency4表示主网络每更新4步才同步一次目标网络这个值太大会让目标值长期偏离太小则失去目标网络的意义。3. 用MATLAB实现策略网络与Q网络的完整训练流程3.1 策略网络DDPG的MATLAB代码与参数说明DDPG的actor网络用featureInputLayer接收四维状态中间两层全连接加ReLU输出层先用tanhLayer把动作压到[-1,1]再用scalingLayer放大到[-10,10]。critic网络是“状态路径动作路径汇合”的经典结构两条路径各自过一层全连接再用additionLayer相加。完整代码如下% 观察和动作定义 obsInfo rlNumericSpec([4 1], LowerLimit, -inf*ones(4,1), UpperLimit, inf*ones(4,1)); actInfo rlNumericSpec([1 1], LowerLimit, -10, UpperLimit, 10); % Actor: 状态 - 连续力 actorNet [ featureInputLayer(4, Normalization, none, Name, state) fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu1) fullyConnectedLayer(64, Name, fc2) reluLayer(Name, relu2) fullyConnectedLayer(1, Name, action) tanhLayer(Name, tanh)]; % 构造scalingLayer并设置缩放系数10 scaleLayer scalingLayer(Name, scale); scaleLayer.Scale 10; % 将scale层接入网络, 使输出范围从[-1,1]变为[-10,10] lgraph layerGraph(actorNet); lgraph addLayers(lgraph, scaleLayer); lgraph connectLayers(lgraph, tanh, scale); actor rlContinuousDeterministicActor(lgraph, obsInfo, actInfo); % Critic: 状态和动作 - Q值 statePath [ featureInputLayer(4, Normalization, none, Name, state) fullyConnectedLayer(64, Name, criticStateFC) reluLayer(Name, criticStateRelu)]; actionPath [ featureInputLayer(1, Normalization, none, Name, action) fullyConnectedLayer(64, Name, criticActionFC) reluLayer(Name, criticActionRelu)]; commonPath [ additionLayer(2, Name, add) reluLayer(Name, criticCommonRelu) fullyConnectedLayer(1, Name, Q)]; criticNet addLayers(layerGraph(statePath), actionPath); criticNet addLayers(criticNet, commonPath); criticNet connectLayers(criticNet, criticStateRelu, add/in1); criticNet connectLayers(criticNet, criticActionRelu, add/in2); critic rlQValueFunction(criticNet, obsInfo, actInfo); % Agent配置 agentOpts rlDDPGAgentOptions(); agentOpts.MiniBatchSize 128; agentOpts.ExperienceBufferLength 1e5; agentOpts.DiscountFactor 0.99; agentOpts.TargetSmoothFactor 0.005; agentOpts.NoiseOptions.Variance 0.2; agent rlDDPGAgent(actor, critic, agentOpts);关键参数说明TargetSmoothFactor0.005表示目标网络每次向主网络移动0.5%这个值越小目标值越稳定但训练变慢NoiseOptions.Variance0.2是探索噪声方差对应作用力上的随机扰动幅度约为0.45N。如果发现训练前期平均回报一直在200以下先把噪声方差调到0.5让agent充分探索等曲线抬头后再降回0.1。scalingLayer在R2021a版本之后才被RL Toolbox原生支持老版本需要在tanh层之后接一个fullyConnectedLayer手动把权重矩阵设成10倍单位阵。3.2 Q网络DQN的MATLAB代码与离散动作设计DQN的离散动作我选了五档力度[-10, -5, 0, 5, 10]。5N的间隔会限制控制的精细度但这正是和连续策略网络对比时最有价值的差异。Q网络输入四维状态输出层5个神经元分别对应五个离散动作的Q值。% 离散动作定义 dqnaction rlFiniteSetSpec([-10 -5 0 5 10]); dqnaction.Name Discrete Force; % Q网络: 状态 - 5个动作的Q值 dqnNet [ featureInputLayer(4, Normalization, none, Name, state) fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu1) fullyConnectedLayer(64, Name, fc2) reluLayer(Name, relu2) fullyConnectedLayer(5, Name, Q)]; % Agent配置 dqnOpts rlDQNAgentOptions(); dqnOpts.MiniBatchSize 128; dqnOpts.ExperienceBufferLength 1e5; dqnOpts.DiscountFactor 0.99; dqnOpts.TargetUpdateFrequency 4; dqnOpts.UseDoubleDQN true; dqnOpts.EpsilonGreedyExploration.Epsilon 0.9; dqnOpts.EpsilonGreedyExploration.EpsilonDecay 0.995; dqnOpts.EpsilonGreedyExploration.EpsilonMin 0.05; agentDQN rlDQNAgent(dqnNet, obsInfo, dqnaction, dqnOpts);这里必须打开UseDoubleDQN。倒立摆的奖励并不稀疏Q值过估计会让agent对某个动作过度自信训练曲线前期向上、后期突然崩掉。Double DQN用主网络选动作、目标网络算Q值正好缓解这个问题。EpsilonDecay0.995的含义是每次采样后ε乘以0.995从0.9衰减到0.05大约需要500多回合给足前期探索时间。如果DQN前100回合平均回报纹丝不动基本可以断定ε衰减太快把EpsilonDecay改成0.998再看。3.3 训练命令与录像保存训练配置里最有用的两个停止条件是AverageReward和EpisodeReward。倒立摆每回合最多500步奖励接近500说明全程未倒所以停止条件设在450比较合理。trainOpts rlTrainingOptions(); trainOpts.MaxEpisodes 1000; trainOpts.MaxStepsPerEpisode 500; trainOpts.ScoreAveragingWindowLength 20; trainOpts.StopTrainingCriteria AverageReward; trainOpts.StopTrainingValue 450; trainOpts.Plots training-progress; % 训练并保存结果 result train(agent, envContinuous, trainOpts); save(ddpg_result.mat, agent, result);训练完成后的录像保存我一般不用sim函数直接录因为sim返回的经验对象不好逐帧画图。正确做法是自己在控制循环里逐帧推进每次drawnow之后用getframe抓帧写成视频。rng(1); % 固定随机种子, 保证每次初始状态一致 obs reset(envContinuous); v VideoWriter(cartpole_ddpg.avi); v.FrameRate 50; % 与dt0.02对应, 一帧一个控制周期 open(v); for t 1:500 action getAction(agent, obs); [obs, reward, isDone, ~] step(envContinuous, action); % 绘制小车和摆杆 clf; plotCartPole(obs); % 自定义绘图函数 drawnow; frame getframe(gcf); writeVideo(v, frame); if isDone break; end end close(v);需要留意的是getAction在旧版本MATLAB里写作agent.getAction(obs)R2023a之后推荐用函数形式。DQN返回的离散动作数值会直接落到档位上不用自己把Q值下标再转换回力度但录像里如果想同时显示五个动作的Q值需要在step循环里手动调用evaluateQ或getValue。4. 策略网络和Q网络训练不稳定时的四个排查切入点4.1 先查奖励函数再查超参数如果两条路线都训不上去90%是奖励函数出了问题。“每步存活1”的奖励太稀疏agent学会了左右晃动保持不倒却没人逼它把角度压向0。更有效的做法是每步都惩罚角度偏差和力度reward 1 - 5*theta^2 - 0.01*F^2;系数5表示偏离1弧度扣5分0.01是力度惩罚专门抑制高频抖动。注意检查顺序第一步先把角度项单独拿出来训练确认能稳再加力度惩罚项如果训练变差就降低惩罚系数改成0.001。奖励的量级决定了Q值的量级DDPG对奖励缩放尤其敏感如果改写奖励后训练曲线形状完全变了说明需要同步调整critic的学习率。4.2 DDPG的探索噪声与目标网络系数DDPG的探索全靠NoiseOptions.Variance。我的习惯是前200回合设0.5让agent大范围试探当平均回报超过350时把噪声降到0.05让动作稳定下来。如果一开始就小噪声actor会过早收敛到某个次优动作附近。TargetSmoothFactor也不是越大越好0.005在倒立摆上表现稳定但如果你发现目标Q值曲线抖动剧烈可以改成0.001再跑。改完噪声和软更新系数后观察critic loss如果loss降不下去优先怀疑奖励量级而不是网络深度。4.3 DQN的过估计现象DQN最常见的问题是训练曲线有明显的“假抬头”前200回合涨得很漂亮后面突然塌掉。这是典型的Q值过估计。打开UseDoubleDQN之后曲线毛刺会变多但整体上升更稳。另一个角度是检查EpsilonGreedyExploration.EpsilonMin如果最终ε还停在0.1以上说明探索和利用的切换没有完成训练后期还在大量随机试错把已经学好的Q值又冲散了。把EpsilonMin设到0.05并确认ε真实衰减到了这个值通常能解决DQN后期掉点的问题。4.4 从训练曲线判断两套网络的差异同一环境、同一奖励下DQN的前100回合平均回报通常涨得更快因为离散动作空间只有5个Q值学习比连续动作容易但最终稳定时长往往不如DDPG因为5N的力度档位不足以精细修正小角度偏差。DDPG前期曲线震荡明显噪声没调好时甚至会出现连续200回合平均回报低于50的情况但只要熬过临界点曲线会突然拉高这是策略梯度方法的典型“拐点”。看到拐点不要急着停训练继续跑到平均回报连续100回合不再上升再保存agent。5. 验证与对比录像里的策略网络与Q网络边界5.1 用三组初始状态确认收敛质量训练结束后的验证分三组零初始偏差、初始角度0.15弧度、初始角速度0.2 rad/s。第三组最能看出两种算法的鲁棒性边界。initStates [ 0 0 0 0; % 零偏差 0 0 0.15 0; % 初始角度约8.6度 0 0 0 0.2]; % 初始角速度扰动 for k 1:3 obs initStates(k, :); stepCount 0; while stepCount 500 act getAction(agent, obs); [obs, ~, isDone, ~] step(envContinuous, act); stepCount stepCount 1; if isDone, break; end end fprintf(case %d, stable steps %d\n, k, stepCount); end这里用fprintf打印稳定步数比直接看录像更客观。以文中参数在相同环境下跑DDPG三组通常都能撑满500步DQN在第三组往往会在300到400步之间倒下这不是基准测试而是两套策略表达方式带来的典型差异。5.2 录像与角度曲线对齐检查录像时把每个时刻的角度和力度同时画在figure的subplot里小车动画放在上半部分角度和力度曲线放在下半部分。这样录像回放时能直接看到DDPG的力度曲线是平滑的连续线DQN的力度曲线是五条横线间跳变。这个细节几乎是两种网络在效果上最直观的差异比看奖励曲线更有说服力。VideoWriter的帧率必须和控制周期匹配dt0.02对应50fps如果嫌文件太大可以把控制周期改成0.05并把FrameRate降到20但欧拉积分的精度会下降验证结果会和训练时有偏差。5.3 对比表与录像导出的完整流程对比维度DDPG策略网络DQNQ网络收敛速度慢约300回合后明显抬头快约150回合已有稳定表现最终稳定步数500步以上小扰动下500步大扰动下约300步动作平滑性连续平滑五档跳变抗初始扰动强中等参数敏感点探索噪声和软更新系数ε衰减速度和Double DQN开关导出录像时我会把文件名分开命名例如cartpole_ddpg.avi和cartpole_dqn.avi并在figure标题里写清算法名避免答辩时混淆。每次录制前固定rng(1)确保两段录像的初始扰动完全一致对比才可信。5.4 一个实用的小技巧对比实验最容易出现的问题是录像看不出区别。建议把画布固定成宽800、高400并用三个子图分别画小车位置、摆杆角度和实时力度。这样录制出来的视频一眼就能看到两种算法在力度输出上的本质差异策略网络输出的是连续变化的力Q网络输出的是阶梯状跳变的力。录制完成后用mmwrite或MATLAB自带的VideoReader逐帧核对关键时间点确认第100帧和第200帧的角度位置对得上再发给别人看。本文还有配套的精品资源点击获取