Skip to content

Reinforcement learning for locomotion ​

How a walking controller can be learned by trial and error in simulation: Markov decision processes, returns and value functions, the policy-gradient theorem, GAE, PPO, and the exact observation, action and reward used for Strido, plus how domain randomisation narrows the sim-to-real gap.

In Strido

The environment is StridoEnv in packages/strido-sim/src/strido_sim/rl/env.py (Gymnasium + MuJoCo); training is rl/train.py (stable-baselines3 PPO on CPU). Shared constants (CONTROL_DT, ACTION_SCALE, COMMAND_RANGES) live in packages/strido-core/src/strido/policy.py, and the robot runs the exported network in ros2/strido_control/strido_control/policy_node.py. Workflow: Simulation and RL and docs/simulation.md.

The Markov decision process ​

An MDP is a tuple (S,A,P,r,γ): states, actions, transition probabilities P(s′∣s,a), reward r(s,a) and discount γ∈[0,1). At each step the agent observes st, samples at∼πθ(a∣st) from a policy with parameters θ (a neural network), and the environment returns rt and st+1. Markov means st+1 depends only on (st,at), not on earlier history. A real robot only sees part of the state (a POMDP); in practice we add enough history (here, the previous action and a gait clock) to make the observation nearly Markov.

Returns and discounting ​

The return from time t is the discounted sum of future rewards:

Gt=∑k=0∞γkrt+k

Discounting keeps the sum finite and expresses a preference for sooner reward. The effective horizon is about 1/(1−γ) steps. Worked example: Strido trains with γ=0.99 (train.py) at a policy rate of 50Hz (Δt=0.02s), so the horizon is about 100 steps =2s, i.e. five gait cycles of 0.4s: long enough to value not falling over, short enough to learn quickly.

The objective is the expected return J(θ)=Eτ∼πθ[G0] over trajectories τ.

Value functions ​

Vπ(s)=Eπ[Gt∣st=s],Qπ(s,a)=Eπ[Gt∣st=s,at=a]

They satisfy the Bellman equation

Vπ(s)=Ea∼π,s′∼P[r(s,a)+γVπ(s′)]

The advantage Aπ(s,a)=Qπ(s,a)−Vπ(s) measures how much better action a is than the policy's average in state s.

The policy-gradient theorem ​

We want ∇θJ without differentiating through the (unknown, non-differentiable) physics. The likelihood-ratio trick gives

∇θJ(θ)=Eπθ[∑t∇θlog⁡πθ(at∣st)Aπ(st,at)]

In words: increase the log-probability of actions that turned out better than average, decrease it for worse ones. Only samples from the simulator and the gradient of the network's own log-probability are needed. Using the advantage (rather than Gt) subtracts a baseline V(s), which leaves the gradient unbiased but greatly reduces its variance.

Generalised advantage estimation (GAE) ​

The advantage is estimated from a learned critic Vϕ. The one-step TD error is

δt=rt+γVϕ(st+1)−Vϕ(st)

GAE takes an exponentially weighted sum of these:

A^t=∑l=0∞(γλ)lδt+l

λ=0 gives the low-variance but biased one-step estimate; λ=1 gives the unbiased but high-variance Monte Carlo return minus V. Strido uses λ=0.95 (gae_lambda in train.py). On termination (a fall) the bootstrap Vϕ(st+1) is zero; on time-limit truncation it is kept, since the robot could have continued.

PPO: proximal policy optimisation ​

Plain policy gradient takes one step per batch, and a step that is too large can wreck the policy. PPO reuses each batch for several epochs while preventing the new policy from drifting far from the one that collected the data. With the probability ratio

ρt(θ)=πθ(at∣st)πθold(at∣st)

the clipped surrogate objective is

LCLIP(θ)=Et[min(ρtA^t,clip(ρt,1−ϵ,1+ϵ)A^t)]

For a good action (A^>0) the gain stops once ρ>1+ϵ; for a bad one (A^<0) once ρ<1−ϵ. The min makes it a pessimistic bound, so there is no incentive to move further. The full loss adds a critic regression term and, optionally, an entropy bonus that rewards keeping the policy random enough to explore:

L(θ,ϕ)=−LCLIP(θ)+c1Et[(Vϕ(st)−G^t)2]−c2Et[H(πθ(⋅∣st))]

For continuous actions the policy is a diagonal Gaussian πθ(a∣s)=N(μθ(s),diag(σ2)); at deployment the mean μθ(s) is used.

Strido's settings (train.py): ϵ=0.2, γ=0.99, λ=0.95, learning rate 3×10−4, 1024 steps per environment per rollout, minibatch 2048, 5 epochs per rollout, c2=0 (ent_coef=0.0: exploration comes only from the Gaussian's own spread, log_std_init=-1.0), 2×107 total steps by default. Observations and rewards are normalised with running statistics (VecNormalize, observations clipped at ±10); the normaliser's mean and variance are exported with the weights so the robot applies the same scaling.

Worked example. Suppose an action had A^t=0.5 and after an update ρt=1.35. Unclipped the term would be 0.675; clipped it is 1.2×0.5=0.6, and the gradient through this sample is zero, so PPO stops pushing that action's probability up.

The Strido environment ​

Timing. Policy at 50Hz (CONTROL_DT = 0.02), physics at 500Hz (10 substeps). Episodes last at most 1000 steps (20s). Servos are modelled as MJCF position actuators with a 3.43Nm torque limit (SPT5435LV stall torque at 6V).

Observation (23 values, OBSERVATION_SIZE):

ot=[ωb,gb,c,at−1,sin⁡φt,cos⁡φt]∈R3+3+3+12+2
  • ωb: body angular velocity from the IMU gyro;
  • gb=R⊤(0,0,−1)⊤: the unit gravity direction in the base frame, from the IMU quaternion (encodes roll and pitch, not yaw);
  • c=(vx,vy,ωz): the velocity command, sampled per episode from vx∈[−0.15,0.30]m/s, vy∈[−0.10,0.10]m/s, ωz∈[−0.6,0.6]rad/s, and zero with probability 0.2;
  • at−1: the previous action;
  • a gait clock φt=2πt/T with T=0.4s (GAIT_PERIOD_S), matching the scripted trot.

Every input is available on the real robot from /imu/data and /cmd_vel; there is no base velocity or joint feedback (the servos have none).

Action. at∈[−1,1]12 are offsets from the stand pose q0:

qttarget=clamp(slew(q0+0.3at))

ACTION_SCALE =0.3rad; slew_limit caps joint speed at 6rad/s (the firmware's limit), and clamp_targets applies the joint and knee-crank limits in the same order as the firmware, so the policy never learns to rely on motions the robot would refuse.

Reward. With base-frame linear velocity vb, angular velocity ωb, actuator forces τ and base height z:

rt=1.0exp⁡(−‖vbxy−cxy‖20.01)+0.8exp⁡(−(ωb,z−cω)20.05)−2.0vb,z2−0.05‖ωbxy‖2−0.01‖at−at−1‖2−0.002‖τ‖2−2.0max(0,0.15−z)

The two exponential terms reward tracking the command (at most 1.8 per step); the penalties discourage bouncing, wobbling, jerky actions, high torque (heat, battery) and crouching below 0.15m. The code multiplies by CONTROL_DT * 50, which equals 1 at 50Hz.

Termination. The episode ends with a −2 penalty when the base drops below 0.09m or the body tilts so that −gb,z<0.6 (about 53∘ roll or pitch).

Worked example. Commanded vx=0.20m/s, achieved 0.15m/s: the tracking term is exp⁡(−0.052/0.01)=exp⁡(−0.25)≈0.78. The width 0.01 means errors around 0.1m/s already cost most of the reward (exp⁡(−1)≈0.37).

Domain randomisation and the sim-to-real gap ​

A policy that exploits simulator quirks fails on hardware: the sim-to-real gap. Sources include unmodelled servo dynamics and backlash, friction, mass and inertia errors, sensor noise and latency. Domain randomisation trains on a distribution of simulators so the real robot is, hopefully, just one more sample:

maxθEξ∼p(ξ)[Jξ(θ)]

where ξ are physics parameters. StridoEnv._randomise samples per episode:

ParameterRange
servo stiffness kp×U(0.85,1.15)
base mass×U(0.90,1.15)
floor and foot friction×U(0.6,1.2)
gyro noiseN(0,0.052) per step
gravity-vector noiseN(0,0.022) per step
initial joint anglesq0+N(0,0.022)

Because the policy cannot identify which ξ it is in from a single observation, it learns behaviour that is robust across all of them. Other tools against the gap (not all used here): actuator models fitted to measured servo responses, observation latency, push perturbations, and terrain curricula.

Check yourself ​

  1. What is the effective horizon in seconds for γ=0.99 at 50Hz, and what if the policy ran at 100Hz with the same γ?

    Answer

    About 100 steps =2s; at 100Hz it halves to 1s, so γ should rise to about 0.995 to keep the same horizon.

  2. Why does subtracting V(s) in the policy gradient not bias it?

    Answer

    Ea∼π[∇θlog⁡πθ(a∣s)]=∇θ∑aπθ(a∣s)=∇θ1=0, so any state-only baseline has zero expected contribution.

  3. With ϵ=0.2 and A^t<0, for which ρt does the PPO term stop giving a gradient?

    Answer

    For ρt<0.8: the action's probability has already been reduced enough.

  4. Why does the observation contain the gravity direction rather than Euler angles including yaw?

    Answer

    Walking does not depend on heading, and yaw from a magnetometer-less IMU drifts; the gravity vector is drift-free and continuous.

  5. Name two sim-to-real effects not covered by Strido's randomisation.

    Answer

    For example sensor and actuation latency, servo backlash or deadband, external pushes, uneven terrain.

Further reading ​

  • R. S. Sutton and A. G. Barto, Reinforcement Learning: An Introduction, 2nd ed., MIT Press, 2018.
  • J. Schulman, F. Wolski, P. Dhariwal, A. Radford and O. Klimov, "Proximal Policy Optimization Algorithms", arXiv:1707.06347, 2017.
  • J. Schulman, P. Moritz, S. Levine, M. Jordan and P. Abbeel, "High-Dimensional Continuous Control Using Generalized Advantage Estimation", ICLR, 2016.
  • J. Tobin et al., "Domain Randomization for Transferring Deep Neural Networks from Simulation to the Real World", IROS, 2017.
  • J. Hwangbo et al., "Learning agile and dynamic motor skills for legged robots", Science Robotics, 2019.

MIT licence. Mechanical design derived from Yertle by Jerome Graves. CAD, simulation and native tests are not hardware evidence — see Verification.