Covariance Matrix Adaptation Evolution Strategy (CMA-ES)
CMA-ES is a gradient-free optimizer that keeps reshaping a Gaussian cloud of candidate solutions, moving it toward the best tries and stretching it along directions that keep paying off.
Why Does This Exist?
Some problems give you no usable gradient. The simulator is not differentiable, the reward arrives as a single score at the end of an episode, or the surface is discontinuous, so stochastic gradient descent in RL has nothing to differentiate. That is exactly where policy search lives: you try whole parameter vectors, score each rollout, and keep what works. Policy gradient methods do this with estimated gradients, but they still assume you can get a reliable gradient signal from those scores.
Plain evolution strategies fill the gap with a fixed sampling cloud: draw candidates around a mean, keep the best, move the mean. That works on round, well-scaled problems and stalls on stretched ones. Picture tuning a robot controller where one gain is a hundred times more sensitive than another, or a narrow curved valley like a Rosenbrock function. A spherical cloud wastes nearly every sample on directions that fail, and a hand-picked step size is either too timid along the valley or too wild across it.
CMA-ES, introduced by Hansen and Ostermeier, removes both hand choices. It learns the full shape of the cloud (the covariance) and its global scale (the step size) from the history of successful steps. This page teaches that adaptation loop: the sampling rule, the weighted mean move, the two-part covariance update, the step-size test, and the invariances you get for free. For the sampling spread question in general, see exploration vs exploitation.
Think of It Like This
The Foggy-Valley Rescue Team
Picture a rescue team hunting for a lost hiker in a fog-filled valley at night. They work from a base camp with a ring of searchers carrying lanterns.
- The base camp is the mean . Each night the camp moves toward the weighted average of the best reports, listening hardest to the searcher with the strongest signal.
- The search pattern is the covariance . When good news keeps coming from the northeast, the leader stretches the next night's pattern into an ellipse pointing northeast instead of a circle.
- The lantern brightness is the step size . When several nights in a row pull the camp the same way, the leader brightens the lanterns and ranges farther. When reports zigzag, the leader dims them and searches tight.
- The logbook holds the evolution paths. It remembers not just last night but the drift over many nights, so one lucky shout does not yank the whole camp sideways.
Where the analogy stops: real searchers talk to each other and cliffs block their paths. CMA-ES candidates are independent draws from a Gaussian with no memory and no obstacles, and the math runs the same on flat ground as in a valley.
How It Actually Works
CMA-ES keeps one search distribution and updates it every generation:
- is the mean vector, the center of the cloud and the current best guess.
- is the step size, one global scale factor for every direction.
- is the covariance matrix, a symmetric positive-definite matrix whose eigenvectors set the ellipse axes and whose eigenvalues set their squared lengths.
- is a standard normal draw, reshaped by and scaled by into a candidate .
Each generation samples candidates, scores each with the fitness function (lower is better by convention here), sorts them best first as , and keeps the top with fixed weights summing to 1. Only the ranking matters, never the raw scores.
One mean update, computed by hand
Start at with and (a round cloud). Three samples come back ranked best first:
- , weight
- , weight
- , weight
The new mean is the weighted recombination:
First coordinate: .
Second coordinate: .
So . The mean steps toward the good half of the cloud but keeps a pull from every keeper, which steadies it against one lucky draw. The effective selection mass here is , so this generation counts as roughly two and a half independent winners.
How the covariance learns direction
Write each keeper as a scaled step from the old mean: . The covariance update blends three terms:
- The rank- term (rightmost sum) stretches along this generation's winning steps. Many winners pointing northeast fattens the ellipse toward the northeast.
- The rank-one term () stretches along the evolution path , a fading average of recent mean moves: . It notices when steps agree across generations even if no single generation looks stretched.
- , and are small learning rates (typical side values from the original papers for dimension ). Small values keep positive-definite and stop one odd generation from collapsing the cloud.
In the diagram, the round Gen 0 ring becomes the tilted Gen 1 ellipse and then the narrow Gen 2 ellipse riding the valley. Same idea, drawn to our numbers for the mean and the samples.
How the step size learns pace
A second path tracks consecutive mean moves, normalized so its length under pure random selection averages (about in two dimensions). The step size follows:
Steps that march one way grow past the baseline, so grows and the cloud strides. Steps that cancel shrink it, so tightens and the cloud fine-tunes. sets how fast reacts and damps the change.
Why practitioners trust it: three invariances
- Translation invariant: only differences enter the updates, so shifting the whole problem changes nothing.
- Rotation invariant: learns orientation, so a rotated valley costs the same as an axis-aligned one. Fixed-shape methods can differ by orders of magnitude here.
- Order invariant: weights depend on ranks, so any strictly increasing transform of gives identical behavior. Rescaling rewards mid-tuning cannot break it.
The price is sample cost: the default population grows like with dimension , and learning a full covariance needs on the order of samples. Past a few hundred dimensions, use a restricted variant (separable or limited-memory CMA-ES) instead.
Code
A minimal weighted-mean step in pure Python, using the worked numbers above:
from typing import List, Tuple
samples: List[Tuple[float, float]] = [(1.0, 0.5), (0.2, 1.2), (-0.8, -0.3)]weights: List[float] = [0.5, 0.3, 0.2]
mean_x = sum(w * x for w, (x, _) in zip(weights, samples))mean_y = sum(w * y for w, (_, y) in zip(weights, samples))mu_eff = 1.0 / sum(w * w for w in weights)
print(f"new mean: [{mean_x:.2f}, {mean_y:.2f}]")print(f"effective winners: {mu_eff:.2f}")print(f"weights sum to: {sum(weights):.1f}")# -> expected output:new mean: [0.40, 0.55]effective winners: 2.63weights sum to: 1.0Watch Out For
Covariance collapse freezes the search
Symptom: fitness plateaus early while and the ellipse axes shrink to near zero, and restarts from the same keep landing in the same spot. A common trigger is starting tiny on a flat plain: early winners cluster by chance, learns that accidental tight cluster, and the cloud can no longer reach out.
Fix: set the initial to span roughly a quarter of your expected search range, and put a floor under it (stop or restart when the smallest axis drops below your tolerance). Watch the axis ratio too: if the longest axis outgrows the shortest by more than about , restart with a fresh covariance.
Step size blows up on noisy fitness
Symptom: on stochastic rollouts (a policy scored over a few random episodes), keeps growing while the true mean barely moves. Noise shuffles the ranking each generation, the path length test reads that churn as signal, and the cloud starts jumping over the good region.
Fix: average each candidate over more episodes, or widen the population so ranking averages the noise out. If scores stay noisy, prefer rank-based methods with explicit noise handling over pushing up, and never tune by hand to compensate: fix the evaluation budget first.
The Quick Version
- CMA-ES samples candidates from one Gaussian and moves to the weighted average of the top-ranked tries, as in from weights .
- The covariance update pairs a rank- term that stretches along this generation's winners with a rank-one term that stretches along the multi-generation evolution path.
- The step size grows when successive mean moves point one way and shrinks when they cancel, judged against the expected random-selection path length.
- Ranking-only selection makes it invariant to any increasing rescaling of fitness, plus translation and rotation invariant by construction.
- It needs no gradients, which suits non-differentiable simulators and episodic policy scores, but full covariance learning costs on the order of samples in dimension .