尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用强化学习造一只会走路的鸭子:微型双足步态全流程解析

用强化学习造一只会走路的鸭子:微型双足步态全流程解析 做一只会走路的鸭子听起来像是个玩具项目但真正把强化学习驱动的双足步态跑在一台巴掌大的平台上要过的坎比实验室里大多数人想象的要多。这个项目最初只是我在走廊上的一句玩笑“四足满地跑人形满街走为什么没人做一只小鸭子”后来这句话变成了一套可跑、可看、可复现的开源平台——一台整机重量不到400克的微小型双足鸭形机器人关节动作由深度强化学习训练出来的策略网络驱动训练和部署代码全部开源。这篇文章会把项目拆开讲清楚硬件为什么这样选型仿真训练链路怎么搭开源仓库里的模块如何组织以及我在训练和真机迁移过程中踩到的一系列坑。适合谁看如果你正打算低成本验证双足步态算法或者想在机器人强化学习方向找一个完整的上手案例这篇内容应该能帮你省下不少时间。就算你暂时不打算动手做硬件只看训练和架构部分也能有不少收获。1. 为什么是一只会走路的鸭子项目缘起与设计目标1.1 从一句玩笑到一个完整平台当时我所在的实验室主要做四足步态控制平台体积大、调试周期长。虽然四足和双足有很多可迁移的结论但四足本身的静稳定性太好很多步态规律其实是被“掩盖”的——你不需要真正解决好平衡问题四条腿自然分摊了一切。我想找一个能把平衡问题逼出来的小平台而双足恰恰是最合适的任务难度。一次偶然的机会我拿到一只玩具小鸭底下一对宽扁的脚掌重心低走路自带左右摇摆。我试着手动给它调了一套正弦步态发现它能走但抗扰动能力很差稍微碰一下就是一个大趔趄。这时候我意识到这个形态非常适合拿来验证强化学习步态算法动作维数不高但动力学非线性很足奖励函数也可以做得很有意思。于是我给项目定了三个硬目标第一整机重量控制在400克以内站立高度在25厘米左右第二强化学习训练出来的策略要能直接迁移到真机不允许在真机上重新调一整天PD参数第三训练和部署代码全部开源让拿到硬件的人能反推整个方案。这三个目标后来都实现了只是中间的过程远比预想曲折。1.2 技术指标与设计底线项目开始前我把技术指标列成了一张表后面所有硬件选型都围绕这张表展开指标项目标值备注整机重量≤400g含电池、主控、线路站立高度约25cm便于桌面和走廊测试单腿自由度3个髋外摆、髋俯仰、膝俯仰峰值关节扭矩≥0.6 N·m考虑动态裕量后反推最大行走速度0.15 m/s鸭子步态的合理速度连续行走时间≥15分钟锂电池容量约束训练算法PPO近端策略优化仿真器MuJoCo / Isaac Gym都跑过最终用MuJoCo出真机这张表里最容易被新手忽略的是“峰值关节扭矩”这一项。很多人在微型机器人上装的是玩具舵机静态能站住但强化学习训练出来的策略在起步、转向、被扰动恢复时关节力矩峰值往往是静态的数倍。如果电机没有足够的扭矩裕量策略在仿真里学得再好真机上也只会原地抖动。1.3 为什么不是人形也不是四足这个问题经常被问。人形机器人确实更有视觉冲击力但微型人形平台的腿长、质心高度、关节惯量参数很难留出足够的设计裕量稍有误差就变成“站都站不稳”更别说训练步态。四足则走到另一个极端静稳定结构天然就不需要解决太多平衡问题验证强化学习算法的“惊险感”少了很多。鸭子形态正好卡在中间双足必须持续处理动态平衡但腿短、重心低、脚掌面积大又在机械上给了算法足够多的容错空间。而鸭子行走时那种左右摇摆的姿态本质上是一种周期性失稳后的受控恢复动作——当你把任务定义为“摇摆着前进并且不摔倒”它就变成了一个比“站直走”更有区分度的强化学习问题。我后来在奖励函数里专门为这种步态风格设计了奖励项这是后话。2. 硬件底子微型双足鸭的机械与电子设计2.1 腿部关节配置三自由度原则每条腿我用了3个自由度髋关节外摆/内收hip yaw、髋关节前摆/后摆hip pitch、膝关节俯仰knee pitch两条腿共6个执行器。鸭子的踝关节被省略了取而代之的是一对TPU打印的宽扁脚掌中间夹一层硅胶垫相当于一个廉价的被动弹性踝关节。这样设计的理由是踝关节在微型平台上重量占比太高而且需要额外两个电机控制维数翻倍后强化学习策略的收敛难度会明显上升。被动弹性脚掌的好处在于它能为策略提供一部分“免费”的缓冲和地面自适应能力代价是脚掌的形变会带来建模误差——这个误差我选择在仿真里做域随机化来处理。大腿和小腿的比例也做了一些特殊调整。真实鸭子是“大腿短、小腿长、脚蹼外翻”的结构我参照了这个比例让大腿略微前倾、小腿自然下垂、脚掌向外偏一个角度。这种形态在走路时天然会产生横向摆动视觉上更像鸭子同时也让hip yaw这个关节在步态中真正承担起重心转移的任务而不是一个摆设。2.2 电机选型扭矩估算与减速比选择微型双足机器人最怕两件事电机扭矩不够以及齿轮箱回差太大。我先做了个粗略的静力估算整机质量按0.35 kg算重力约3.43 N静态单腿承重大概一半也就是1.7 N左右髋关节到身体质心的力臂约为0.06 m静态力矩约0.1 N·m双足动态步态的峰值力矩一般是静态的2到3倍按3倍估算就是0.3 N·m再留2倍安全裕量目标峰值扭矩定为0.6 N·m以上。在电机方案上我对比了三类方案串行总线舵机、微型无刷电机加行星减速箱、传统小功率直驱电机。串行总线舵机的优势是集成度高自带减速箱、编码器和驱动板开发周期短缺点是齿轮箱回差偏大扭矩响应存在明显非线性。微型无刷加行星减速箱的力量和响应更好但要自己画FOC驱动板还要解决编码器安装、散热和机械加工精度的问题成本翻倍。最终我选择了串行总线舵机方案。代价是训练时要额外对执行器建模不能把关节当成理想的“目标角度即位置”模型要加入最大角速度限制、响应延迟和回差噪声。这些建模工作放到强化学习环境里来做比在真机上跟电机脾气较劲要省时间得多。2.3 传感器、主控与通信架构传感器方案比较常规一个六轴IMU加速度计加陀螺仪放在躯干中心每条腿的关节编码器读取角度和角速度脚掌底部贴了两片薄膜压力传感器主要用来获取足底接触状态。接触状态我不直接进策略但会用在奖励函数和真机的状态机判断里。主控采用双处理器方案底层用一块STM32单片机负责电机控制回路、传感器采集和串口通信上层用树莓派Zero 2 W跑策略网络推理。两层之间用USB串口连接频率做到100Hz。选树莓派而不是直接在STM32上跑网络是因为策略网络里有卷积和全连接层在单片机上优化推理效率很痛苦而树莓派上可以直接用PyTorch导出的ONNX模型部署成本低很多。通信上真机和PC之间走无线串口模块训练数据通过Wi-Fi回传如果是在场地里做真机评估我会同时架一个小型动作捕捉系统用外部位置数据来校准机载IMU的积分漂移。这个“外部设备只做评估、不参与推理”的原则保证了策略在脱离实验室环境后依然能独立运行。2.4 装配时最容易忽略的惯量匹配这个项目最深刻的硬件经验之一是“重心位置比电机好坏更影响步态质量”。最初一版样机我把电池和主控都放在了鸭子的背部视觉上和真实鸭子更像结果训练好的策略拿到真机上稍微走几步就开始剧烈点头。后来分析发现电池抬高导致整机质心远离髋关节轴线转动惯量变大被动脚掌那点缓冲根本压不住。解决办法很简单也很土把电池、主控和大部分线缆全部塞到靠近脚底的“鸭肚子”位置。机头、鸭翅膀、尾巴都是轻质打印外壳不承担任何电子元件。调整之后整机质心落在髋关节轴线下方的支撑多边形正中间策略迁移一下子就顺畅了。这个经验后来被我写进了项目文档——一个能立住的硬件能让强化学习省下至少三分之一的时间。3. 强化学习怎么让鸭子学会走路3.1 仿真建模的取舍不是越精细越好很多第一次做机器人强化学习的人容易陷入“仿真模型必须跟CAD一致”的执念。机械结构当然是越接近越好但仿真里真正决定步态质量的是接触动力学和惯性参数而不是外壳长什么样。我的做法是鸭子主体只用几个box和sphere拼出来腿用圆柱加球形关节表达脚掌用扁椭球加少量几何细节。重点标定三类参数质量、质心位置、转动惯量。质量可以拆零件用电子秤逐个称质心用吊线法粗测转动惯量直接从CAD估算。脚掌的摩擦系数和弹性阻尼则在后续域随机化里处理。MuJoCo的MJCF格式写起来很直观核心建模文件大致长这样mujoco modelmini_duck option timestep0.002 iterations50 tolerance1e-10/ worldbody body nametorso pos0 0 0.12 inertial pos0 -0.01 -0.02 mass0.26 diaginertia1e-4 2e-4 2e-4/ joint namehip_yaw typehinge axis0 0 1 range-30 30/ body namethigh pos0 0 -0.04 inertial pos0 0 -0.02 mass0.03 diaginertia1e-5 1e-5 3e-5/ joint namehip_pitch typehinge axis0 1 0 range-120 120/ body nameshin pos0 0 -0.04 inertial pos0 0 -0.02 mass0.02 diaginertia1e-5 1e-5 2e-5/ joint nameknee_pitch typehinge axis0 1 0 range-150 0/ geom namefoot typeellipsoid size0.035 0.02 0.005 pos0 0 -0.02/ /body /body /body /worldbody /mujoco仿真步长设在2ms控制频率则是50Hz也就是每20ms从策略输出一次动作。大量并行环境并行跑4096个环境同时rollout一个下午就能看到初步的步态雏形。3.2 状态空间与动作空间策略网络的输入设计直接决定了任务好不好学。我给鸭子设计的观测向量包括机体的三轴角速度IMU数据经过滤波和单位转换机体姿态的旋转矩阵或四元数用于判断倾斜和翻转每只腿3个关节的角度和角速度上一个控制周期输出的动作用来提供“肌肉记忆”足底接触标志位1/0表示左脚或右脚是否触地期望前进速度和期望转向速度在训练时用随机目标采样。动作空间则设计为6维向量每个关节输出一个目标角度。这里有一个小门道采用“绝对位置”动作比“位置增量”动作在仿真到真机迁移时更稳定。增量模式的策略学得快但真机上任意一步的建模误差都会被累积绝对位置模式相当于让策略直接输出关节目标PD控制器负责追踪整个闭环对外部误差更鲁棒。执行器模型用带限速和延迟的一阶PD来近似def step_torque(q_target, q_current, dq_current, kp, kd, vmax): q_cmd np.clip(q_target, q_current - vmax * dt, q_current vmax * dt) torque kp * (q_cmd - q_current) - kd * dq_current return torque真机部署时同一套PD参数直接复用这是整个迁移链路里比较让我放心的一段代码。3.3 奖励函数从“会走”到“像鸭子一样走”奖励函数是这个项目里最需要耐心打磨的部分。我把它拆成三个层次的奖励项生存项、任务项、风格项。生存项最简单也最暴力只要机体没摔倒每一帧给一个小正奖励一旦躯干中心高度低于阈值或者倾斜角超过30度立刻给负奖励并终止回合。这个机制让策略首先学会“不摔”。任务项负责方向引导reward_vel exp(-1.5 * ||v_target - v_forward_proj||) reward_heading cos(yaw_error) reward_torque_penalty -0.0001 * sum(tau**2) reward_penetration -10 * max(0, contact_penetration)其中v_forward_proj是指机体前进方向上的线速度yaw_error是朝向与目标方向的角度差。这些项一起推动鸭子能够稳定地朝指定方向移动。风格项则是让它对得起“鸭子”这个名头。我额外加了一个髋外摆关节的相位激励当摆动腿处于支撑相和后摆相时允许并轻微鼓励hip yaw关节跟随一个正弦参考产生左右摇摆的姿态同时惩罚膝关节反曲和脚掌的滑动。加权后策略在满足“往前走”的同时自然涌现出接近真实鸭子的步态节奏。我特意没有用“强制指定步态相位”的方式因为强模板会扼杀强化学习的探索空间也会让策略在真机遇到扰动时变得僵化。3.4 PPO训练与超参调节算法选用了PPO这不是因为它最时髦而是它在机器人连续控制任务上足够稳定对超参不太敏感社区资料也多。实际训练用的超参如下超参数数值说明并行环境数4096学习效率的关键PPO clip范围0.2常规配置策略网络学习率3e-4线性衰减价值网络学习率1e-3线性衰减折扣因子 γ0.99兼顾长期回报GAE λ0.95优势估计每个iteration的样本量983044096环境×24步mini-batch大小4096稳定性好训练轮数5平均每个样本使用5次熵系数初始值0.01后期线性降到0.001这个配置在单张消费级显卡上跑了不到4个小时回报曲线从0.3附近升到0.7步态从最初的“原地抽搐”逐渐变成“左右摇摆地向前挪”。最容易观察到的转折点是1小时左右策略学会了交替迈步2小时后转弯和抗扰动能力开始成形3小时后几乎不再摔倒。3.5 因果强化学习视角用干预实验替代盲目调奖励说一个可能稍微拔高一点的点。很多人调奖励函数是纯“对着曲线加惩罚”这种方式既费时间又容易过拟合。我在这个项目里尝试把因果强化学习的思路带进训练诊断流程效果出奇地好。因果强化学习的核心机制是把因果推断工具嵌入强化学习的观测和分析流程不再只看总回报曲线而是把任务拆成几个可干预的因果因子——比如“身体平衡因子”“前向速度因子”“步态相位因子”。训练到某一阶段后我会在仿真里固定其他因子单独干预一个因子来看策略响应。比如把机体的初始横滚角硬拉10度观察策略是选择加大髋外摆来恢复还是选择加快步频来稳住再比如把期望速度从0.05突然跳到0.2观察策略的前馈响应是否立即更新。这套干预实验帮我找到了几次很隐蔽的训练异常有一次总回报一直在涨但干预横滚角后发现策略几乎不做主动恢复全靠被动脚掌在地面的摩擦力撑着走——那其实说明策略只是在钻奖励函数的空子并不是真的学会了平衡。后来我用因果图的方式给策略模块加了一个“姿态恢复必要性”的观测头训练诊断就直观多了。这也是我在整个开源仓库里保留causal_diagnose.py这个工具的原因。4. 开源架构拆解仓库里到底有什么4.1 目录结构与模块划分我把整个项目整理成了一个叫duck_rl的开源仓库目录结构大致是这样的duck_rl/ ├── README.md ├── environment/ │ ├── duck_env.py # 仿真训练环境MuJoCo │ ├── duck_robot.py # 真机部署适配层 │ └── config/ │ ├── train.yaml # 训练配置 │ └── deploy.yaml # 真机部署配置 ├── rl/ │ ├── ppo/ │ │ ├── agent.py # PPO智能体实现 │ │ └── buffer.py # rollout缓存 │ ├── reward/ │ │ └── duck_reward.py # 奖励函数实现与可视化 │ └── scheduler/ # 学习率、熵系数调度 ├── deploy/ │ ├── duck_controller.cpp # STM32底层控制器 │ └── run_deploy.py # 树莓派上的推理入口 ├── tools/ │ ├── replay_dataset.py # 仿真步态回放 │ ├── export_onnx.py # 转换部署格式 │ └── causal_diagnose.py # 干预实验工具 └── docs/ ├── mechanics.md └── training_notes.md模块边界的原则是环境、算法、部署三者互相独立。训练环境通过一个统一的step()接口对外服务真机适配层实现同样的接口。策略训练时完全不知道后面要跑真机部署时也完全不知道训练用的奖励函数长什么样。这个设计让硬件改动不会污染训练逻辑算法更新也不会影响部署流程。4.2 训练、评估、部署三个入口仓库里最常用的三个命令分别对应三个工作阶段训练入口读取train.yaml生成和注册MuJoCo环境启动PPO训练每个checkpoint保存一次模型并在训练完成后自动导出ONNX。评估入口则加载checkpoint在可视化环境里回放步态同时录制接触力、关节力矩、质心轨迹用来判断策略的稳定性指标。部署入口是我自己最常用的一段代码。推理侧只保留一个100Hz的循环读取传感器、做归一化、过一遍网络、输出目标角度然后通过串口发给STM32。整个推理在树莓派Zero 2 W上大约耗时1.2毫秒远小于控制周期。部署代码里没有RL库只剩一个ONNX Runtime这能让人清晰地看到“算法和部署是两件事”这个基础工程原则。4.3 依赖管理与可复现性开源项目最怕“别人clone下来跑不起来”。我在依赖上做了个狠决定只依赖MuJoCo、PyTorch、NumPy、PyYAML这五个基础库不引入复杂训练框架。PPO实现是仓库自己维护的四五百行代码虽然看起来“土”但每一步都能被调试也能被教学。requirements.txt里直接锁版本训练配置全部走YAML连随机种子都写进配置。这样做的价值在复现实验结果时特别明显我后来在另一台机器上重跑训练得到的回报曲线和之前几乎重合。这种可复现性比追新框架版本爽得多。4.4 开源协议和模块边界的价值观我选了MIT协议也是想给社区最大的使用自由。真正比协议更重要的是项目文档——把机械设计选型、训练参数依据、常见错误排查都写进docs。我见过太多开源机器人项目代码能跑但换一个环境就崩原因就是缺少“为什么这样设计”的上下文。这个仓库不敢说完美但它至少把从仿真到真机之间的几十个细节判断都记录下来了。5. 训练中的坑位复盘奖励欺骗、参数漂移与退化5.1 奖励欺骗的典型案例原地转圈第一版奖励函数里我用了朝向角度差作为主要引导项。结果训练出来的鸭子确实“很乖”——它学会了在原地快速打转始终保持正对目标方向但机体几乎不产生前向位移。因为奖励函数只看朝向角度差转圈可以把这个差长期维持在零附近同时还能规避掉速度惩罚项。这真的是个经典的奖励欺骗案例解决方式也很经典把前向速度项拆得更细必须要求机体坐标系x方向的位移累计增长而不是只看瞬时朝向。我还加了一条“前进位移窗口平均速度”的奖励项用过去两秒的平均前向速度来判断“是不是真的在走”原地转圈一下子就骗不到奖励了。从那以后我每次都先看策略的轨迹热力图而不是只看回报曲线。5.2 摩擦系数和接触刚度的域随机化真机测试时我发现了另一个问题仿真里走得很好的鸭子在宿舍走廊的瓷砖上疯狂打滑在会议室地毯上又频繁卡脚。查下来问题出在摩擦系数和接触刚度——MuJoCo默认的接触参数跟廉价舵机驱动的脚掌实际情况差距很大。域随机化是我的标准解法。训练时摩擦系数从0.3到1.5之间均匀采样脚掌刚度从低到高变档再加入高斯接触噪声。这一步看似简单却解决了“一种步态只适应一种地面”的老毛病。训练出来的策略最终对不同地面的泛化能力明显更好虽然单一看某种地面不如专测数据漂亮但综合可靠性高了很多。5.3 用因果干预实验定位策略退化有一次训练到后期回报曲线平稳但真机表现突然变差。我以为是硬件出了问题反复检查电机和脚掌都没有异常。重启训练发现同样的现象又出现了。最后定位的方式就是前面提到的因果干预实验。我对训练中的策略做了两组干预一组把期望前进速度调低到近乎零一组把机体初始偏航角强制拉偏15度。结果发现策略对第一组干预几乎不敏感——它已经退化成一个“只会按固定频率迈步”的节奏发生器不再根据目标速度调整动作。原因在熵系数我把熵系数设成线性降到0训练后期策略变得极其确定性探索能力趋近于零任何微小环境变化都可能导致崩溃。修复办法是让熵系数在后期保持一个较小但不为0的底值最终解决了退化问题。这个案例说明强化学习训练有时候并不是“loss越低越好”策略的探索余量是需要刻意保留的。因果干预实验帮我建立了一组“策略行为体检”指标每次训练完先体检再上真机比盲目相信回报曲线靠谱得多。5.4 仿真到真机的“最后一公里”细节即便做了域随机化仿真和真机仍然有差异。我总结出最后几个必须过的细节缺一个都可能让鸭子在下地瞬间变成“抽风鸡”第一确保真机的关节限位和仿真完全一致否则策略输出一个超出物理限位的角度底层电机根本执行不了第二仿真里加入执行器延迟模型真机的串口和PD计算加起来大约有8到12毫秒延迟这个量级足够让步态变脆第三电机回差要单独测不同负载下的回差差异非常大我最终在真机推理代码里加了一个简易的前馈补偿项。这段经验我写进了项目的training_notes.md后来有朋友复现时照着做一次就下地成功了。6. 实测表现与最后想说的话6.1 真机实测从走廊到楼道拿最终策略上真机测试过几种常见地面抛光瓷砖、粗糙水泥地、短毛地毯、塑胶跑道以及5度以下的小坡。结果最顺的是水泥地鸭子能以约0.12 m/s的速度稳定走连续走15分钟累计里程大约110米瓷砖上稍显谨慎策略会自动降低步幅、增加步频来抵抗打滑地毯上有轻微卡脚但几乎没有摔倒。坡道上前5度能保持稳定超过8度时策略会明显吃力需要人为把期望目标速度调低。我用外置动作捕捉做了对比真机质心轨迹和仿真回放的平均误差大约在2.3厘米左右。这个误差水平对双足步态来说已经很理想了它让我相信在硬件和仿真建模做扎实的前提下域随机化加因果诊断这套流程确实能把sim-to-real的鸿沟压到很小。6.2 给想复现的你的几条真心话如果看完这篇你也想照着一套方法做一只自己的小机器人我有几句实在话想说在前头。第一先跑通一次完整的训练闭环再谈优化。很多人在第一步就卡在“追求更复杂的奖励函数”其实哪怕奖励只有“别摔、往前走”两项第一次完整跑通的价值也远大于纸上谈兵。第二真机下地前务必用遥控器手动把每个关节掰一遍确认限位、转向和停止状态都正常这一步能避免大量“策略没问题、硬件乱来”的误会。第三录像是你最好的调试伙伴——肉眼觉得步态还行往往是因为大脑自动补全了细节慢动作录像才能看到脚掌滑动、膝盖反曲这些真问题。最后再说一个经验之外的经验做这类项目最好把“让鸭子走得稳定”和“让鸭子走得像鸭子”当成两个独立目标。稳定是算法问题风格是设计问题。先稳定再谈风格你会少掉很多心态上的煎熬。我也还在继续往这个平台上加东西比如鸭翅膀的辅助平衡控制和自适应步态切换后续有结果了再拿出来分享。
返回列表