Zum Inhalt springen

Simulation und Reinforcement Learning ​

Ein Simulator lässt den Roboter tausende Male laufen, ohne dass ein Teil bricht; Reinforcement Learning (RL) lässt den Computer in diesem Simulator durch Versuch und Irrtum einen Lauf-Controller finden. Strido verwendet MuJoCo für schnelle Physik und Training, Gazebo zum Testen des kompletten ROS-2-Stacks und PPO, um eine Lauf-Policy zu trainieren, die auf dem Pi mit reinem NumPy läuft.

In Strido

  • MuJoCo-Modell: models/mujoco/strido.xml + scene.xml, von make models aus dem CAD erzeugt.
  • Gazebo-Modell: ros2/strido_description/urdf/strido.urdf.xacro (mit sim:=true), Welt ros2/strido_bringup/worlds/flat.sdf, gestartet über gazebo.launch.py.
  • RL-Code: packages/strido-sim/src/strido_sim/rl/ (env.py, train.py, evaluate.py); Policy-Laufzeit packages/strido-core/src/strido/policy.py.
  • Veröffentlichte Policy: models/policies/ppo-v1/policy.npz (MLP 23 → 128 → 128 → 12).
  • Befehle: make sim-walk, make sim-gazebo, make rl-smoke, make rl-train, make rl-eval (Simulation).

Warum zwei Simulatoren ​

MuJoCo 3Gazebo Harmonic
Läuft alsPython-Prozess (mujoco==3.17.0)Docker-Container mit ROS 2 Jazzy
StärkeSchnelle, deterministische Kontaktphysik; lässt sich aus Python leicht tausende Male pro Sekunde schrittweise ausführenSimuliert Sensoren (IMU, GPU-LiDAR, Kamera) und führt die echten ROS-2-Nodes und Controller aus
Verwendet fürGait-Demo, PPO-Training und -AuswertungEnd-to-End-Test von gait_node / policy_node mit denselben Topics wie am Roboter
Befehlmake sim-walk, make rl-*make sim-gazebo

Eine in MuJoCo trainierte Policy in Gazebo laufen zu lassen, ist eine günstige Sim-to-Sim-Prüfung: Funktioniert sie nur im Simulator, in dem sie trainiert wurde, hat sie dessen Eigenheiten gelernt und wird auch den echten Roboter nicht überstehen.

Die Modelle, aus dem CAD erzeugt ​

strido_cad.models erstellt alle Simulationsmodelle aus der verifizierten CAD-Baugruppe, sodass der simulierte Roboter die echten Gliedlängen, Massen und Gelenkgrenzen hat:

  • 13 Glied-Meshes (ros2/strido_description/meshes/), jeweils auf 25 k Flächen reduziert;
  • Massen aus Teilevolumen und Dichten, Trägheiten aus den Meshes, Kabel nach verlegter Länge: insgesamt 3,07 kg (ros2/strido_description/config/mass_report.yaml);
  • MJCF mit 12 Positionsaktoren (kp 25, kv 0.4, Momentgrenze ±3,432 N·m = Blockiermoment des SPT5435LV bei 6 V), ein IMU-Site mit Quaternion-, Gyro- und Beschleunigungssensoren sowie Fuß-Sites;
  • Physikschritt 0,002 s (500 Hz) mit dem Integrator implicitfast.

Eine Vereinfachung: Das Knie ist als direktes Drehgelenk am Unterschenkel modelliert. Das Viergelenk-Koppelgetriebe wird in Software gelöst (Gait, Firmware), und seine Grenzen werden angewendet, bevor Befehle das Gelenk erreichen (Kinematik).

In Gazebo ergänzt das Xacro mit sim:=true gz_ros2_control mit einer position-Befehlsschnittstelle je Gelenk, eine IMU (200 Hz), ein GPU-LiDAR (360 Messpunkte, 10 Hz, 0,05–12 m, Gaußsches Rauschen σ 0,01 m), eine Kamera (640 × 360, 15 Hz) und einen Ground-Truth-Odometrie-Publisher (/ground_truth/odom, 50 Hz).

MuJoCo: der Gait-Lauf ​

bash
make sim-walk                                                     # 10 s trot at 0.15 m/s → build/sim/walk.mp4
uv run --locked python -m strido_sim.walk --seconds 6 --wz 0.6   # turn
uv run --locked mjpython -m strido_sim.walk --viewer             # interactive (macOS needs mjpython)

Dies führt strido.gait aus, denselben Trot, den der Roboter über ROS 2 ausführt, mit 100 Hz wie der echte Befehlsstrom. Zusätzlich hält es die Richtung mithilfe des Gierwinkels aus dem Simulator, den der Roboter nicht hat. Aufgezeichnetes Ergebnis: 1,10 m in 10 s bei 0,15 m/s, Körperhöhe 0,174 m (Verifikation).

Gazebo: der vollständige ROS-2-Stack ​

bash
make sim-gazebo                               # build image, headless walk test, exit code = pass/fail
make sim-gazebo POLICY=models/policies/ppo-v1/policy.npz    # same test with policy_node

walk_smoke wartet 6 s, bis der Roboter steht, sendet 8 s lang 0,15 m/s auf /cmd_vel und besteht, wenn sich die Basis ≥ 0,4 m vorwärts bewegt hat (Ground Truth), aufrecht geblieben ist (|Roll|, |Pitch| < 0,5 rad, Höhe > 0,10 m) und /joint_states, /imu/data, /scan, /camera/image_raw sowie /ground_truth/odom alle veröffentlicht wurden. Aufgezeichnet: 0,98 m in 8 s mit dem Gait-Node.

Reinforcement Learning in einem Absatz ​

Ein Agent (hier: ein neuronales Netz, die Policy πθ(a∣s) mit Gewichten θ) beobachtet den Zustand st, wählt eine Action at und erhält einen Reward rt. Über Millionen von Versuchen passt er sich an, um den erwarteten diskontierten Ertrag zu maximieren

J(θ)=Eπθ[∑t=0∞γtrt]

wobei der Diskontfaktor γ<1 nahe Rewards stärker gewichtet als ferne. Mit dem γ=0.99 des Repos und 50 Policy-Schritten pro Sekunde zählen Rewards etwa 1/(1−γ)=100 Schritte (2 s) voraus noch. PPO (Proximal Policy Optimisation) ist ein dafür weit verbreiteter Algorithmus: Er sammelt mit der aktuellen Policy einen Batch an Erfahrung und verbessert die Policy dann in kleinen, begrenzten (clipped) Schritten, sodass ein schlechter Batch sie nicht zerstören kann. Er maximiert

LCLIP(θ)=Et[min(ρt(θ)A^t, clip(ρt(θ),1−ϵ,1+ϵ)A^t)],ρt(θ)=πθ(at∣st)πθold(at∣st)

wobei A^t der Advantage ist (um wie viel besser die Action war, als der Critic erwartet hat) und hier ϵ=0.2 gilt: Sobald die neue Policy eine Action um mehr als 20 % wahrscheinlicher oder unwahrscheinlicher macht als die alte, belohnt das Ziel keine weitere Änderung. Das Repo verwendet die PPO-Implementierung von Stable-Baselines3 auf der CPU. Die vollständige Herleitung steht unter Reinforcement Learning.

Die Umgebung, wie implementiert ​

StridoEnv (strido_sim/rl/env.py) ist eine Gymnasium-Umgebung: „mit der befohlenen Geschwindigkeit laufen“.

Observations (23 Zahlen) ​

TeilGrößeQuelle am Roboter
Winkelgeschwindigkeit (Gyro)3IMU (/imu/data)
Schwerkraftrichtung im Körperkoordinatensystem3Orientierungsquaternion der IMU
Befehl vx, vy, ωz3/cmd_vel
Vorherige Action12eigene Ausgabe der Policy
Gangtakt sin, cos (Periode 0,4 s)2ein Timer

Es gibt keine Gelenkwinkel oder -geschwindigkeiten. Die SPT5435LV-Servos melden nichts zurück, eine Policy, die sich auf Gelenkrückmeldung stützt, könnte also nicht auf dem Roboter laufen (ADR-006 in Entscheidungen). Die Observation enthält nur, was der Roboter messen kann oder weiß.

Actions (12 Zahlen) ​

Jede Action liegt in [−1,1]. Das Gelenkziel ist qtarget=qstand+0.3a (rad), danach folgen:

  1. die Anstiegsbegrenzung der Firmware von 6 rad/s (6rad/s×0.02s=0.12rad pro Policy-Schritt),
  2. die Gelenk- und Kniekurbel-Begrenzung, genau wie in der Firmware.

Werden diese im Training modelliert, lernt die Policy mit denselben Grenzen, die der Roboter vorgibt, statt sie erst auf der Hardware zu entdecken.

Timing ​

Policy mit 50 Hz (CONTROL_DT = 0.02 s), Physik mit 500 Hz: 0.02s/0.002s=10 Physikschritte pro Action. Eine Episode dauert höchstens 1000 Schritte (1000×0.02s=20s).

Befehle ​

Jede Episode zieht einen Befehl: 20 % der Episoden stehen still (Nullbefehl); die übrigen ziehen gleichverteilt aus vx −0,15…0,30 m/s, vy −0,10…0,10 m/s, ωz −0,6…0,6 rad/s. policy_node begrenzt /cmd_vel am Roboter auf diese Bereiche, weil die Policy nie etwas außerhalb davon gesehen hat.

Reward ​

Pro Schritt, so skaliert, dass perfektes Folgen höchstens 1.0+0.8=1.8 ergibt:

r=1.0exp(−‖vxy−cxy‖2/0.01)track linear velocity+0.8exp(−(ωz−cωz)2/0.05)track yaw rate−2.0vz2no bouncing−0.05(ωx2+ωy2)no rolling/pitching−0.01‖at−at−1‖2smooth actions−0.002∑jτj2save energy, spare the servos−2.0max(0, 0.15m−h)do not crouch below 0.15m

Dabei sind v und ω die Linear- und Winkelgeschwindigkeit der Basis, c der Befehl, a die Action, τj die Gelenkmomente und h die Höhe der Basis.

Eine Episode endet mit einer Strafe von −2, wenn die Basis unter 0,09 m sinkt oder sich über etwa 53° neigt.

Die exponentiellen „Tracking-Kerne“ liefern einen glatten Reward, der bei perfektem Folgen 1 ist und mit dem Fehler abklingt: Bei der Breite 0,01 senkt ein Geschwindigkeitsfehler von 0.1m/s den Term bereits auf e−1≈0.37. Strafen formen, wie der Roboter läuft. Die Moment- und Action-Rate-Terme sind wichtiger, als sie aussehen: Ein Hobby-Servo, das mit 50 Hz zittern soll, erhitzt sich und verschleißt sein Getriebe.

Domain Randomisation ​

Der echte Roboter wird nicht genau dem Modell entsprechen. Statt darauf zu hoffen, variiert das Training das Modell in jeder Episode, damit die Policy lernt, mit einer Bandbreite zurechtzukommen:

RandomisiertBereich
Servosteifigkeit (kp und der passende Bias)× 0,85 … 1,15
Masse der Basis× 0,90 … 1,15
Kontaktreibung, Boden und Füße gemeinsam× 0,6 … 1,2
IMU-RauschenGyro σ 0,05 rad/s, Schwerkraft σ 0,02
AnfangsgelenkwinkelStehpose + σ 0,02 rad

Boden und Füße werden gemeinsam skaliert, weil MuJoCo die größere Reibung der beiden sich berührenden Geoms verwendet; nur eines zu skalieren, würde oft nichts ändern.

Training, Export und Auswertung ​

bash
uv sync --locked --group rl                   # once: PyTorch, Stable-Baselines3, Gymnasium (opt-in, large)
make rl-smoke                                 # 20 k steps + evaluation: checks the pipeline in seconds
make rl-train                                 # 20 M steps, 8 parallel envs, CPU → build/rl/ppo-v1/
make rl-eval                                  # evaluate models/policies/ppo-v1/policy.npz with a video
uv run --locked --group rl python -m strido_sim.rl.evaluate build/rl/<run>/policy.npz --vx 0.2 --video build/rl/eval.mp4

Wichtige PPO-Einstellungen in train.py: 1024 Schritte je Umgebung je Update, Batch 2048, 5 Epochen, Lernrate 3×10−4, γ=0.99, GAE λ=0.95, Clip ϵ=0.2; Actor [128, 128], Critic [256, 256]; Observations und Rewards normalisiert (VecNormalize, Clip 10). Der aufgezeichnete Lauf ppo-v1 dauerte 1734 s auf einer CPU.

Das Training schreibt policy.zip (Stable-Baselines3), vecnormalize.pkl, progress.csv und policy.npz: die Gewichte und Biases des Actors sowie Mittelwert/Varianz der Observations als einfache Arrays. strido.policy.MlpPolicy implementiert den Vorwärtsdurchlauf in einem Dutzend Zeilen NumPy neu: normalisieren o~=clip((o−μ)/σ2+10−8, −10, 10), then h1=tanh⁡(W1o~+b1), h2=tanh⁡(W2h1+b2), a=clip(W3h2+b3, −1, 1). Derselbe Code läuft in evaluate.py und in policy_node, das Ausgewertete ist also Bit für Bit die Funktion, die der Roboter ausführt, und der Pi benötigt kein PyTorch.

Was ppo-v1 erreicht ​

TestErgebnis
MuJoCo, Befehl vx 0,2 m/s (aktuelle Grenzen)0,215 m/s im Mittel, 2,15 m in 10 s, aufrecht
MuJoCo, v0.1-Durchlaufvx 0,15 → 0,152 m/s; 0 → steht; −0,1 → −0,068 m/s; vy 0,1 → 0,023 m/s (schwach); ωz 0,5 → 232° in 10 s
Gazebo über policy_node (Sim-to-Sim)0,82 m in 8 s bei 0,15 m/s, aufrecht

Seitliches und rückwärtiges Folgen ist schwach: zu erwarten bei einer Policy ohne Gelenkrückmeldung (ADR-006).

Die Sim-to-Real-Lücke ​

Alles oben ist Simulation. Auf einem physischen Strido ist noch nichts gelaufen. Die bekannten Lücken:

  • Servoverhalten. kp 25 / kv 0,4 und null Latenz sind Schätzungen; echte Hobby-Servos haben Totzone, Spiel, Getriebenachgiebigkeit und eine spannungsabhängige Geschwindigkeit.
  • Spannungseinbruch des Akkus. Servogeschwindigkeit und -moment sinken mit der Akkuspannung; das Modell nimmt 6 V an.
  • IMU. Das Training liest ein perfektes Quaternion plus Gaußsches Rauschen; das des Roboters stammt aus einem Mahony-Filter mit eigener Verzögerung und eigenem Bias.
  • Kontakte. Gedruckte Füße auf einem echten Boden, keine gleichmäßige Ebene mit randomisierter Reibung.
  • Nicht modellierte Mechanik. Das Knie-Koppelgetriebe ist in der Simulation ein direktes Gelenk; Drucktoleranzen und Biegung sind nicht modelliert.

Das Vorgehen des Repos für einen ersten Hardwarelauf: in MuJoCo und Gazebo auswerten, mit dem Roboter auf einem Ständer beginnen, /strido/estop griffbereit halten und damit rechnen, mit gemessenem Servoverhalten (kp/kv, Latenz) neu zu trainieren, sobald der Roboter existiert.

bash
ros2 launch strido_bringup robot.launch.py use_policy:=true policy:=/path/policy.npz

WARNING

Eine gelernte Policy kann schnelle, große Bewegungen befehlen, die der programmierte Gait nie ausführen würde. Anstiegsbegrenzung, Gelenkbegrenzungen und Watchdog der Firmware gelten weiterhin, können aber nicht verhindern, dass ein Bein Ihre Hand trifft. Testen Sie zuerst auf einem Ständer (Sicherheit).

Selbsttest ​

  1. Warum beobachtet die Policy keine Gelenkwinkel?
  2. Welche zwei Firmware-Verhalten werden in env.step() nachgebildet, und warum?
  3. Warum werden Boden- und Fußreibung gemeinsam randomisiert?
  4. Warum wird policy.npz exportiert, statt das Stable-Baselines3-Modell auf dem Pi auszuführen?
  5. Was sagt Ihnen das Ausführen der in MuJoCo trainierten Policy in Gazebo?
Antworten
  1. Die SPT5435LV-Servos haben keine Positionsrückmeldung; eine Policy darf nur verwenden, was der echte Roboter misst (IMU, Befehl, ihre eigene vorherige Action, einen Takt).
  2. Die Anstiegsbegrenzung von 6 rad/s und die Gelenk-/Kniekurbel-Begrenzung, damit die Policy unter denselben Einschränkungen trainiert, die der Roboter vorgibt.
  3. MuJoCo verwendet die größere Reibung der beiden sich berührenden Geoms; nur eine zu skalieren, hätte oft keine Wirkung.
  4. Der Roboter bräuchte PyTorch; die NumPy-Laufzeit ist klein, und derselbe Code wird in der Auswertung verwendet, sodass die eingesetzte Funktion genau die ausgewertete ist.
  5. Ob die Policy von MuJoCo-spezifischen Eigenheiten abhängt: Eine Sim-to-Sim-Lücke ist ein Warnzeichen für die Sim-to-Real-Lücke.

Weiterführende Literatur ​

MIT-Lizenz. Mechanik abgeleitet von Yertle (Jerome Graves). CAD, Simulation und native Tests sind kein Hardware-Nachweis – siehe Verifikation.