多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Python神经网络倒立摆控制:从仿真到实物部署

Python神经网络倒立摆控制:从仿真到实物部署 简介这份资源面向控制理论、机器学习初学者及希望将神经网络落地到实际控制问题的开发者围绕小车倒立摆这一经典不稳定系统提供基于Python与神经网络的平衡控制实现方案。压缩包内共1个文件为单个py脚本整体约2KB属于轻量级代码示例便于直接阅读与二次修改。脚本中应涵盖系统模型定义、神经网络结构搭建、训练流程与主循环等关键环节读者可借此理解如何用传感器状态作为输入、以小车驱动力作为输出训练网络学习控制策略并结合仿真环境验证效果。目前已有553人学习下载说明该案例在控制与机器学习交叉领域具有一定参考价值。通过研读代码读者能掌握倒立摆动态特性建模、网络架构与超参数调整、损失函数与优化器选择等实践要点为后续迁移到真实硬件或更复杂控制任务打下基础。1. 倒立摆控制为什么让神经网络成了刚需小车倒立摆是控制领域最经典的验证平台没有之一。一根摆杆铰接在小车上小车左右移动摆杆要立住同时小车不能跑出轨道边界。传统做法是LQR或者PID串级线性化点附近效果很好但摆杆一旦偏角超过十几度线性模型失效控制器直接翻车。更麻烦的是摩擦、皮带间隙、电机死区这些非线性因素用固定参数根本兜不住。神经网络在这里的价值不是赶时髦而是它天然适合拟合非线性映射。用Python搭一个前馈神经网络输入是小车的位移、速度、摆杆角度、角速度四个状态量输出是电机的PWM或者力指令训练数据来自传统控制器的成功轨迹或者仿真环境里的随机探索。训练完之后网络能在更大范围内保持稳定甚至对参数摄动有一定鲁棒性。这套方案适合谁有Python基础、想从仿真过渡到实物控制的学生和工程师。不需要你精通深度学习框架但得会装numpy、会调参、能看懂状态反馈的基本逻辑。下面从环境搭建到实物部署把这条路走通。2. 用Python搭一个能跑的前馈神经网络控制器2.1 为什么选前馈网络而不是LSTM或CNN倒立摆的状态量只有四个是典型的低维连续控制问题。前馈神经网络FNN足够表达从状态到控制量的非线性映射训练快、推理延迟低在树莓派或者STM32上都能跑。LSTM适合时序依赖强的场景但倒立摆的马尔可夫性很好当前状态就包含了决策所需的全部信息加循环结构反而增加训练难度和推理延迟。CNN更不用考虑输入不是图像一维卷积在这里没有优势。网络结构我一般用4-32-32-1两个隐藏层激活函数用tanh而不是ReLU。原因在于控制量需要正负对称输出tanh的值域是[-1,1]天然匹配归一化后的力指令而且tanh在零点附近光滑梯度不会像ReLU那样在负半轴直接截断。输出层不加激活函数直接线性输出训练时用MSE损失。2.2 仿真环境搭建与数据采集没有实物之前先在Python里把仿真跑通。用欧拉法或者四阶龙格库塔积分倒立摆动力学方程步长1ms。下面是最小可运行的仿真代码import numpy as np # 物理参数 M 0.5 # 小车质量 kg m 0.2 # 摆杆质量 kg l 0.3 # 摆杆半长 m g 9.81 # 重力加速度 dt 0.001 # 仿真步长 def dynamics(state, force): x, dx, theta, dtheta state sin_t, cos_t np.sin(theta), np.cos(theta) # 倒立摆非线性动力学方程 temp (force m * l * dtheta**2 * sin_t) / (M m) ddtheta (g * sin_t - cos_t * temp) / (l * (4/3 - m * cos_t**2 / (M m))) ddx temp - m * l * ddtheta * cos_t / (M m) return np.array([dx, ddx, dtheta, ddtheta]) def step(state, force): # 四阶龙格库塔积分 k1 dynamics(state, force) k2 dynamics(state 0.5*dt*k1, force) k3 dynamics(state 0.5*dt*k2, force) k4 dynamics(state dt*k3, force) return state dt/6 * (k1 2*k2 2*k3 k4)这段代码定义了倒立摆的连续动力学dynamics返回状态导数step做一步积分。参数M、m、l要和你的实物一致否则训练出来的网络搬到实物上会因为模型失配而振荡。数据采集用PD控制器先跑出稳定轨迹把(state, force)对存下来再加一些随机扰动让状态覆盖更广。每个episode跑10秒采集1000个episode大概10万条样本。2.3 网络训练与验证训练代码用numpy手写反向传播不依赖PyTorch方便后续移植到嵌入式端import numpy as np class FNN: def __init__(self, sizes[4, 32, 32, 1]): self.W [np.random.randn(sizes[i1], sizes[i]) * 0.1 for i in range(len(sizes)-1)] self.b [np.zeros((sizes[i1], 1)) for i in range(len(sizes)-1)] def forward(self, x): self.a [x.reshape(-1, 1)] for i in range(len(self.W)-1): z self.W[i] self.a[-1] self.b[i] self.a.append(np.tanh(z)) z self.W[-1] self.a[-1] self.b[-1] self.a.append(z) # 输出层线性 return z.flatten() def train(self, X, Y, epochs500, lr0.01): for ep in range(epochs): loss 0 for xi, yi in zip(X, Y): out self.forward(xi) err out - yi loss err**2 # 反向传播 delta err.reshape(-1, 1) for i in range(len(self.W)-1, -1, -1): grad_W delta self.a[i].T grad_b delta if i 0: delta (self.W[i].T delta) * (1 - self.a[i]**2) self.W[i] - lr * grad_W self.b[i] - lr * grad_b if ep % 50 0: print(fepoch {ep}, loss {loss/len(X):.6f})forward里tanh的导数用1 - a**2计算这是tanh激活的经典性质。训练时学习率0.01500轮足够收敛。验证方法在仿真里从不同初始角度±0.3rad和初始速度出发看网络能否在2秒内把摆杆拉回竖直。如果失败优先检查数据分布是否覆盖了这些初始条件而不是急着加层。提示训练数据里的力指令要归一化到[-1,1]推理时再乘以实际最大推力。不归一化的话输出层梯度会爆炸。3. 从仿真到实物部署时最容易翻车的四个环节3.1 状态观测的噪声与滤波仿真里状态是干净的实物上编码器有量化噪声陀螺仪有零漂。直接把原始值喂给网络输出会抖得像筛糠。常见做法是加一阶低通滤波截止频率设在30Hz左右。代码就三行alpha 0.3 # 滤波系数越小越平滑但延迟越大 x_filt alpha * x_raw (1 - alpha) * x_filt_prev角度和角速度都要滤但角速度滤波系数可以稍大因为微分噪声更严重。注意滤波会引入相位滞后如果摆杆振荡频率接近截止频率系统可能失稳。我一般先用示波器看原始信号频谱再定截止频率。3.2 控制周期与推理延迟神经网络推理在树莓派4上大概0.5ms在STM32F103上跑定点化版本要3ms。控制周期必须大于推理时间加传感器读取时间。我一般把周期定在5ms留足余量。如果周期抖动超过20%网络输出的等效控制量会失真表现为小车一顿一顿的。解决办法是用定时器中断触发控制循环而不是while循环里延时。3.3 输出限幅与积分饱和网络输出是连续的但电机PWM有上下限。直接截断会导致积分饱和摆杆往一边倒的时候控制器拼命输出但被限幅等摆杆回来时输出又来不及撤。正确做法是在训练数据里就把力指令限制在物理极限内推理时再做一个软限幅def soft_limit(u, umax): return umax * np.tanh(u / umax)这样输出永远不会超过umax而且在大误差时梯度依然存在不会像硬截断那样梯度为零。3.4 模型失配的在线补偿仿真用的质量和长度是标称值实物上摆杆质量分布不均匀等效长度有偏差。网络在仿真里训练得再好搬到实物上也可能因为模型失配而振荡。我一般加一个简单的自适应项用PD控制器做底层稳定网络输出作为前馈补偿两者叠加。这样即使网络输出有偏差PD也能兜住。等网络在线微调收敛后再逐步减小PD增益。4. 避坑与排查倒立摆神经网络控制的五个血泪教训现象一仿真里稳如泰山实物上摆杆高频抖动。原因仿真步长1ms实物控制周期5ms离散化误差被网络放大了。解决把仿真步长改成和控制周期一致重新训练。或者用零阶保持器离散化动力学方程。现象二网络输出恒定值摆杆直接倒。原因训练数据里力指令的方差太小网络学到了均值。解决检查数据采集时的激励信号确保力指令覆盖[-umax, umax]的80%以上。可以加白噪声激励。现象三摆杆能立住但小车一直往一个方向跑。原因训练数据里小车位移没有回归到零网络没学会位置控制。解决在损失函数里加位移惩罚项或者用LQR生成的数据做示范LQR本身就会把小车拉回中心。现象四换了块电机网络就失效了。原因电机死区和摩擦特性变了网络没有在线适应能力。解决加一个在线学习率极小的微调层只更新最后一层权重学习率设0.0001防止破坏已学到的特征。现象五训练loss降到很低但验证效果差。原因过拟合。10万条样本对4-32-32-1的网络来说偏少。解决加L2正则化或者把数据增强——对状态量加高斯噪声再喂给网络让网络学会抗噪。5. 进阶技巧用在线微调让网络适应不同摆杆训练好的网络是离线产物但实物系统会磨损、会换电池、会变温度。我习惯在部署后加一个在线微调环节只更新输出层权重学习率设得很小用PD控制器的输出作为监督信号。这样网络会慢慢逼近当前系统的真实逆模型同时PD保证稳定性。具体做法每控制一步计算PD输出u_pd用(u_pd - u_nn)的平方作为损失只对最后一层做梯度下降。学习率0.0001每100步更新一次。跑10分钟之后网络输出和PD输出的差距会缩小到5%以内这时候可以把PD增益降到原来的30%让网络主导控制。验证方法很简单用手轻推摆杆看它回正的速度和超调量。如果回正时间比纯PD短且超调小于5度说明在线微调生效了。如果振荡加剧说明学习率太大或者PD监督信号本身有问题。我自己的习惯是每次换摆杆或者换场地先跑5分钟在线微调再正式测试。这个习惯帮我省了很多次重新训练的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表