资讯动态

基于Q-Learning的小车倒立摆平衡控制MATLAB仿真实战

发布时间:2026/10/17 11:26:00 来源:尧图企业网站定制
做控制相关项目的时候很多同学第一次接触强化学习都会拿小车倒立摆来试手。这个对象看起来结构简单但它是典型的不稳定非线性系统PID想调稳都得花不少功夫而Q-Learning这种不依赖精确模型的算法反而能在这个场景里体现出价值。我这段时间正好在MATLAB里把一整套基于Q-Learning的小车倒立摆平衡控制系统性能仿真完整跑通了一遍从建模、状态离散化、奖励设计到训练循环、参数调整、性能评估都踩了一遍所以想把这些过程整理出来分享给正在做控制类课题设计、算法预研或者刚入门强化学习想找一个能跑起来的例子的朋友。先说结论只要状态空间划分合理、奖励函数不过分稀疏纯Q-Learning在MATLAB里训练两万回合以内小车倒立摆就能做到稳定直立平均保持时间可以从最初的零点几秒提升到十秒以上。这里不打算堆一堆数学推导而是按我实操时的顺序来讲怎么搭仿真环境、怎么设参数、怎么判断训练是否收敛、怎么排查发散。文章后面还会给出性能仿真阶段的测试思路以及一次从完全发散到恢复稳定的完整调参记录。1. 项目整体设计与方案选型先想清楚为什么用强化学习1.1 为什么选Q-Learning而不是PID/LQR如果只是想让倒立摆立起来经典控制理论确实更成熟。在平衡点附近把系统线性化之后LQR可以给出很好的反馈增益PID经过仔细整定也能维持直立。但这类方法有一个隐含前提模型工作在平衡点附近。一旦初始角度偏大或者外界干扰让角度超出线性化区间线性控制器的性能会明显下降甚至直接发散。Q-Learning的做法完全不一样。它不需要我们显式推导控制律智能体通过不断试错在离散状态空间里学习“当前状态下做哪个动作能让累计奖励最大”。这样控制系统不再被限制在平衡点附近对非线性状态转移也有天然的适应能力。换句话说传统控制器是在“模型已知的前提下设计最优策略”而Q-Learning是在“模型基本不可知的前提下通过探索找策略”。从项目实现角度讲Q-Learning在MATLAB里落地也足够轻量。它不需要像深度强化学习那样引入神经网络框架一个Q表加一个环境函数就能跑起来训练时间通常只需要几分钟到十几分钟。对于课程设计或个人预研来说这种复杂度是刚刚好的。维度传统PID/LQRQ-Learning模型依赖通常需要线性化模型只需与环境交互参数设计整定KP/KI/KD或Q/R矩阵设计离散状态空间与奖励函数适用范围平衡点附近效果好非线性、大范围状态均可学习代码复杂度低中等训练时间无需训练需要一定回合数与探索可解释性控制律直观策略藏在Q表里需结合实验分析1.2 倒立摆数学模型与仿真环境设计小车倒立摆的物理模型本身并不复杂常见的一阶倒立摆可以这样描述小车质量为M1.0kg摆杆质量为m0.1kg摆杆长度为l0.5m重力加速度取g9.8m/s²。状态变量一共四个小车位置x、摆杆角度θ垂直向上为0、小车速度dx、摆杆角速度dtheta。控制输入是作用在小车上的水平力F。完整推导拉格朗日方程比较啰嗦这里直接给结论形式的状态更新关系。摆杆角加速度可以写成与F、θ、dtheta相关的表达式小车加速度则同时受F和摆杆运动的影响。这样的系统有非线性项比如θ²的角速度耦合、cosθ耦合所以不能用简单的线性状态方程来实现仿真。我的做法是在MATLAB里用固定步长的欧拉积分来推进状态步长取0.02秒。为什么不用RK4因为训练过程需要反复进行环境交互欧拉积分在0.02秒步长下已经能保留足够准确的动态特征而且计算量小很多。如果你觉得不放心可以改成0.01秒步长但训练回合里单回合步数会翻倍整体时间变长收敛趋势不会有多大区别。1.3 控制目标、初始条件与任务边界这个项目的控制目标很明确让摆杆保持竖直同时小车不要冲出允许范围。为了增加一点难度每次训练开始时小车位置固定为0但摆杆角度会在[-0.1, 0.1]弧度之间随机初始化速度都设为0。这样学习到的策略不会只对单一初始条件有效对小的随机扰动也有一定鲁棒性。回合结束条件有两个摆杆角度绝对值超过0.6弧度大约34度说明已经倒伏或者小车位置绝对值超过2.4米说明小车撞到了行程限位。单回合最大步数设置为500步对应10秒仿真时间。如果智能体能让摆杆坚持到500步结束就算这一回合成功。训练停止条件我设为连续20回合都成功或者达到最大训练回合数20000回合。2. Q-Learning关键设计状态、动作、奖励与参数怎么定2.1 状态离散化粒度怎么选才不丢关键信息Q-Learning处理连续状态有一个绕不开的前提必须把连续状态划分成离散格子。状态有四个维度我在项目里每维都划分成12格以下是具体范围小车位置[-2.4, 2.4]12格摆杆角度[-0.6, 0.6]12格小车速度[-3.0, 3.0]12格摆杆角速度[-5.0, 5.0]12格Q表的规模就是12×12×12×12每个状态组合对应3个动作总计大约6.2万条Q值。这个规模在MATLAB里只是一块很小的数组训练速度不会成为瓶颈但已经足够表达倒立摆平衡所需的控制策略。这里有一个很容易踩的坑网格太粗智能体区分不了细小的状态差异控制动作会显得僵直表现为“明明摆杆已经开始往右倒它还在往右推”网格太细Q表虽然表达能力更强但探索难度成倍增加每个格子里的状态都需要多次访问才能学准确训练回合数必须跟着翻倍。我实际测试下来每维10到15格是比较舒服的范围既能学到平滑策略又不会拖慢收敛。2.2 动作集设计离散力三选一动作集我用了三个离散力-10N、0N、10N。为什么不把F设计成连续取值因为Q-Learning本身是离散动作算法每回合只能从有限集合里选一个动作强行连续化会导致无法直接使用max操作。三选一已经能表达“左推、右推、不推”三种必要行为比二选一多了一个“保持不动”的选项这对于倒立摆平衡很重要——当摆杆很接近竖直时最优策略可能就是不施加任何控制力让系统自然稳定。力的大小需要匹配小车质量。小车质量是1kg如果力太小比如±2N在摆杆角度偏大时根本无法把倾倒趋势扳回来训练会一直失败如果力太大比如±100N小车会在几步之内冲到边界位置发散比角度发散还快。我按工程经验取±10N在1kg小车质量下足够快速纠偏又不会让小车行程在短时间内撞到限位。2.3 奖励函数要同时抑制“原地抖”和“往一边跑”奖励函数是强化学习项目里最值得花时间的部分。我在这个项目里用的奖励函数是r cos(θ) - 0.1 × |x| - 0.01 × |dtheta|如果发生终止条件再额外减1。这个设计是逐项考虑的。cos(θ)这一项最核心θ越接近0奖励越接近1否则迅速下降相当于把“摆杆竖直”这一目标放在最高优先级0.1×|x|是位置惩罚防止小车只顾平衡摆杆、不管自己已经冲到边界附近0.01×|dtheta|是角速度惩罚让智能体尽量避免高频抖动如果完全去掉这一项学到的策略往往会看到小车在平衡点附近快速来回震荡摆杆角度虽然在可控范围但控制力曲线非常难看。还有一个很容易犯的错用稀疏奖励也就是“保持直立到最后给1倒下给-1”。这种设计思路听起来合理但实际训练时智能体绝大多数回合都很快倒下奖励几乎全是-1反馈信息太少探索效率非常差。改成密集奖励后智能体每一步都能感受到“角度更接近竖直会让奖励变大”学习速度提升非常明显。如果你的训练对象和这个项目类似可以通过现象反推怎么调权重摆杆能立住但小车一直在朝一个方向跑增大|x|前面的系数摆杆角度稳定但控制力来回切换增大|dtheta|前面的系数摆杆总有一个固定偏差减小cosθ对微小角度的敏感度或者增加一点角度二次惩罚项。2.4 学习率、折扣因子、探索率参数背后的实际影响这几个参数对收敛速度影响很大我直接说最终采用的参数组合参数推荐值影响学习率α0.1后期降到0.02值太大导致Q值震荡太小导致学习过慢折扣因子γ0.95值太接近1会让策略过于看重远期太小会只顾眼前初始探索率ε1.0保证前期充分探索最低探索率ε_min0.05保留少量探索避免Q表固化探索衰减方式线性衰减20000回合内从1降到0.05探索和利用之间逐步平衡学习率α控制的是每一次Q值更新对新信息的采纳程度。0.1起步随着训练推进慢慢降到0.02是因为前期需要快速吸收经验后期Q值已经接近真实值更新幅度太大反而会破坏已经收敛的部分。折扣因子γ0.95表示未来奖励的权重。倒立摆这个任务每步奖励量级在-1到1之间500步回合如果γ太接近1累计奖励在数学上会膨胀Q值更新容易不稳定γ0.95时智能体既要考虑未来几步能不能保持平衡又不会因为远期目标太模糊而忽视眼前状态。探索率ε的选择很关键。我用的是ε-greedy策略初期ε1也就是完全随机动作让智能体尽可能多地见识不同状态随训练回合数线性下降到0.05后期绝大多数动作都选择当前Q值最大的动作只保留少量探索测试。如果ε一直固定为0.1你会发现Q表后期的噪声很大因为已经稳定的策略偶尔会被随机动作打断回报曲线会一直上下波动。3. MATLAB仿真实现与训练流程从环境到Q表收敛3.1 仿真文件组织与数据流MATLAB写强化学习项目我建议把环境、智能体和训练主流程分开不要全塞在一个脚本里。我的文件结构大致是envStep.m环境更新函数输入当前状态和动作输出下一个状态、奖励、是否终止stateToIndex.m连续状态到离散格索引的映射函数trainQLearning.m主训练脚本负责Q表初始化、回合循环、参数更新和日志记录plotResult.m训练后绘图和性能评估包括回报曲线、状态轨迹、控制力曲线这样做的最大好处是以后切换算法非常方便。比如想把Q-Learning换成SARSA或DQN只需要改智能体的学习模块环境部分完全不用动。我调试的时候也方便环境函数有bug时可以先单独测试envStep不需要每次都跑完整训练。3.2 环境函数与状态映射代码envStep是仿真的核心输入当前状态向量s和控制力F通过动力学方程更新状态。核心代码大致是function [sNext, reward, done] envStep(s, F) g 9.8; M 1.0; m 0.1; L 0.5; dt 0.02; x s(1); theta s(2); dx s(3); dtheta s(4); % 摆杆角加速度 thetaAcc (g*sin(theta) - cos(theta)*(F m*L*dtheta^2*sin(theta))/(Mm)) / ... (L*(4/3 - m*cos(theta)^2/(Mm))); % 小车加速度 xAcc (F m*L*(dtheta^2*sin(theta) - thetaAcc*cos(theta))) / (Mm); sNext s dt * [dx; dtheta; xAcc; thetaAcc]; % 终止判断 done abs(sNext(2)) 0.6 || abs(sNext(1)) 2.4; % 奖励函数 reward cos(sNext(2)) - 0.1*abs(sNext(1)) - 0.01*abs(sNext(4)); if done reward reward - 1.0; end end状态映射函数的作用是把连续状态变成Q表索引。注意边界处理如果状态正好等于上限索引必须封顶到最后一格否则会数组越界function idx stateToIndex(s, sMin, sMax, gridN) idx zeros(1,4); for i 1:4 binWidth (sMax(i) - sMin(i)) / gridN(i); idx(i) min(gridN(i), max(1, floor((s(i) - sMin(i)) / binWidth) 1)); end end3.3 Q-Learning训练循环与更新代码训练主循环是整个项目的核心逻辑。每一回合先随机初始化状态然后循环执行“选动作→走一步→更新Q值→进入下一状态”直到回合结束。Q值更新严格遵循标准公式Q(s,a) Q(s,a) α × (r γ × max(Q(s,a)) - Q(s,a))终止状态时max(Q(s,a))这一项要置为0因为结束后没有未来奖励了。训练循环代码大致如下MAX_EPISODES 20000; MAX_STEPS 500; alpha 0.1; gamma 0.95; epsilon 1.0; actions [-10; 0; 10]; gridN [12 12 12 12]; sMin [-2.4, -0.6, -3.0, -5.0]; sMax [2.4, 0.6, 3.0, 5.0]; Q zeros(gridN(1), gridN(2), gridN(3), gridN(4), length(actions)); returns zeros(MAX_EPISODES, 1); for episode 1:MAX_EPISODES theta0 (rand*2 - 1) * 0.1; s [0; theta0; 0; 0]; done false; stepCount 0; episodeReward 0; while ~done stepCount MAX_STEPS idx stateToIndex(s, sMin, sMax, gridN); if rand epsilon aIdx randi(length(actions)); else [~, aIdx] max(Q(idx(1), idx(2), idx(3), idx(4), :)); end F actions(aIdx); [sNext, reward, done] envStep(s, F); idxNext stateToIndex(sNext, sMin, sMax, gridN); qPredict Q(idx(1), idx(2), idx(3), idx(4), aIdx); if done qTarget reward; else qTarget reward gamma * max(Q(idxNext(1), idxNext(2), idxNext(3), idxNext(4), :)); end Q(idx(1), idx(2), idx(3), idx(4), aIdx) qPredict alpha * (qTarget - qPredict); s sNext; stepCount stepCount 1; episodeReward episodeReward reward; end epsilon max(0.05, 1 - 0.95 * episode / MAX_EPISODES); alpha max(0.02, 0.1 - 0.08 * episode / MAX_EPISODES); returns(episode) episodeReward; end这段代码可以直接跑但两个细节需要留意。一是Q表初始化成全零我开始也这么用实际效果OK不过有些人喜欢先给一个小正值初始化鼓励智能体探索更多状态我的经验是只要探索率设计合理全零初始化完全够用。二是回合结束条件中要用“更新后的状态”来判断是否终止不要用旧状态否则智能体会在多走一步之后才收到失败的反馈时序上差了一步训练效率会降低。3.4 训练过程怎么看回报曲线与成功标准训练不是放着等结果就行我习惯每50回合计一次滑动平均回报画出来。前期回报通常是负的因为智能体不知道怎样控制力才能让摆杆保持平衡大部分回合很快倒下中期回报开始上升说明智能体逐渐学到了一部分状态下的正确动作后期回报稳定在高位并且曲线波动很小说明策略基本收敛。单看回报还不够我还会记录每个回合成功与否。如果连续20个回合都成功保持500步就提前终止训练并保存Q表。我实际跑出来的结果是在大约8000回合时回报曲线开始明显抬升到15000到18000回合达到稳定。这是正常速度不要太早放弃。如果你发现训练了5000回合回报还是完全平的、一点上升趋势都没有那大概率不是算法问题而是环境、状态映射或奖励函数设计出了问题下面一节专门讲这类问题。4. 训练调试实战常见发散问题与排查技巧4.1 长时间不收敛、震荡与“原地乱动”的根因很多人在训练强化学习时第一反应是怀疑算法但根据我的经验Q-Learning本身公式很成熟绝大多数“不收敛”都是环境或参数没配对。状态离散化太粗是最隐蔽的原因。出现这种现象时训练初期回报还有一点上升但很快卡死在一个较低水平因为智能体无法区分“摆杆正在以高速远离垂直位置”和“摆杆正在以低速接近垂直位置”这两个状态如果被分到了同一个格子它就只能用过拟合的方式选择动作结果就是控制力反复横跳。奖励函数量纲失衡也很常见。比如位置惩罚系数设得很大智能体就只关心让小车留在中间即使摆杆已经倒到一边也不愿意大力纠正因为冲出边界扣的分更多反之如果角度惩罚太小智能体虽然让摆杆保持竖直但小车一直在轨道上疯狂左右冲直到某个时刻位置超限才结束这本质上是探索不足。还有一种情况叫做“动作盲区”动作力太小无法抵抗重力矩智能体无论选哪个动作结果都是倒下。这时候问题不在强化学习而在物理参数。把力从±5N改成±10N之后训练效果立刻好转。所以遇到发散的回合先检查这个基本项。4.2 从完全发散到稳定平衡的调参记录我自己的调试过程挺有代表意义。第一次跑的时候我用的奖励函数是r cos(θ) - 0.5×|x|想着位置惩罚大一点小车不会乱跑。结果训练到3000回合回报一直稳定在-3左右摆杆基本撑不过30步。后来我检查每个回合结束时的状态发现小车确实很少冲边界但摆杆角度经常在0.3到0.5之间缓慢偏移角度偏离到0.6才触发终止。这说明智能体学会了“只要位置不超限就行”的偷懒策略把奖励函数的逻辑带偏了。第二次我把位置惩罚降到0.1角度项变成主导训练明显好转但又出现了新的问题小车开始反复左右冲从一条轨迹上看位置一直在边界附近反弹。原因是我给的位置惩罚集中在绝对值项上而角度控制变强之后小车需要比较大的位移来维持平衡。后来我在奖励里加入角速度惩罚项才把这种高频抖动压住。整个调参过程下来我的体会是奖励函数的每一项都需要一个明确的行为动机如果发现智能体做出了你不希望看到的“钻空子”行为优先调整奖励权重而不是急着改Q-Learning参数。4.3 常见问题速查表现象可能原因建议处理方式回报长期不上升状态离散化太粗增加网格数或缩小状态边界奖励出现大幅震荡学习率α偏大将α降到0.05以下摆杆能立住但小车乱跑位置惩罚权重低增加控制力高频来回切换缺少角速度惩罚加入0.01×摆杆一直停在某个偏角角度项敏感度不足增大角度误差惩罚比如用θ²每次都在固定状态倒下探索率衰减太快降低衰减速度保证后期仍有随机探索训练后期Q值被随机动作污染ε降到0.05以下后仍有明显波动关闭探索直接贪婪策略测试5. 性能仿真评估与后续扩展学会看指标也留好算法接口5.1 训练完成后的性能仿真怎么测训练收敛之后很多人就停了但一个完整的性能仿真项目不应该终止于训练完成。我用下面这几个方法验证策略的真实表现。先把探索彻底关掉ε直接设为0用纯贪婪策略测试50个随机初始状态统计三个指标成功回合率、平均保持时间、最大可恢复初始角度。我跑出来的常见结果是成功回合率在90%以上平均保持时间超过9秒最大可恢复初始角度大约在0.35弧度左右。这些指标比你单看“训练时最后几回合成功”更有说服力因为随机初始状态不会和训练时的初始分布完全一样。再做一次干扰测试在某个时间点突然给摆杆注入一个角速度扰动相当于模拟外界冲击观察系统能不能在几秒内恢复平衡。恢复时间越短说明策略越鲁棒。这种测试在课程设计评审或项目答辩时很加分因为别人看到的是“不仅训练的时候能立住遇到扰动也能自动恢复”。最后绘图分析。把测试阶段的状态轨迹和控制力曲线画出来重点看两个地方稳态时控制力是否有明显的高频抖动以及摆杆角度是否在零点附近对称波动。如果控制力曲线看起来像锯齿一样即使摆杆没倒也说明策略还有改进空间可以尝试在奖励函数里继续加大角速度惩罚。5.2 从Q-Learning走向DQN这套环境还能怎么复用这个项目最大的复用价值在于MATLAB里这套环境函数完全不需要改动我可以直接把它接给其他强化学习算法。比如把Q表换成一个小型神经网络输入连续状态输出每个动作的Q值就变成DQN。这时候状态离散化函数可以扔掉动作集可以保留三值动作训练框架从查表更新变成神经网络的反向传播。还可以往双倒立摆、倒立摆加随机风干扰、或者小车轨道带斜坡的方向扩展。每次只改一个环节比如改环境方程、改奖励函数、改动作集就能观察算法在不同难度下的表现。这种扩展思路很适合用来作为一个系列实验从简单到复杂一层层验证比单独跑一个固定任务更能说明算法的能力边界。最后说一点体感。这个项目里Q值更新本身只占一小部分代码真正决定成败的是环境建模和奖励设计。你在MATLAB里跑的时候如果发现训练初期一片混乱先别急着怀疑强化学习回过头检查状态映射有没有写错、奖励量纲合不合理、动作力能不能抵抗重力矩。把环境写对了Q-Learning的收敛速度往往比你预期的更快。把这个流程走通之后再去做DQN或者其他连续控制算法你会对每一行代码和数据流都有更清楚的感觉。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑