Skip to content
AI360Xpert
Beta

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.

CMA-ES moves its Gaussian mean toward weighted winners and stretches covariance across generations to follow the valley to the optimum.
CMA-ES moves its Gaussian mean toward weighted winners and stretches covariance across generations to follow the valley to the optimum.

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 mm. 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 CC. 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 σ\sigma. 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:

x=m+σC1/2z,z∼N(0,I)x = m + \sigma C^{1/2} z, \quad z \sim \mathcal{N}(0, I)

  • mm is the mean vector, the center of the cloud and the current best guess.
  • σ>0\sigma > 0 is the step size, one global scale factor for every direction.
  • CC is the covariance matrix, a symmetric positive-definite matrix whose eigenvectors set the ellipse axes and whose eigenvalues set their squared lengths.
  • zz is a standard normal draw, reshaped by C1/2C^{1/2} and scaled by σ\sigma into a candidate xx.

Each generation samples λ\lambda candidates, scores each with the fitness function ff (lower is better by convention here), sorts them best first as x1:λ,…,xλ:λx_{1:\lambda}, \dots, x_{\lambda:\lambda}, and keeps the top μ\mu with fixed weights w1≥w2≥⋯≥wμ>0w_1 \ge w_2 \ge \dots \ge w_\mu > 0 summing to 1. Only the ranking matters, never the raw scores.

One mean update, computed by hand

Start at m=[0,0]m = [0, 0] with σ=1\sigma = 1 and C=IC = I (a round cloud). Three samples come back ranked best first:

  • x1=[1.0,0.5]x_1 = [1.0, 0.5], weight w1=0.5w_1 = 0.5
  • x2=[0.2,1.2]x_2 = [0.2, 1.2], weight w2=0.3w_2 = 0.3
  • x3=[−0.8,−0.3]x_3 = [-0.8, -0.3], weight w3=0.2w_3 = 0.2

The new mean is the weighted recombination:

m′=∑i=13wixi:3m' = \sum_{i=1}^{3} w_i x_{i:3}

First coordinate: 0.5×1.0+0.3×0.2+0.2×(−0.8)=0.50+0.06−0.16=0.400.5 \times 1.0 + 0.3 \times 0.2 + 0.2 \times (-0.8) = 0.50 + 0.06 - 0.16 = 0.40.

Second coordinate: 0.5×0.5+0.3×1.2+0.2×(−0.3)=0.25+0.36−0.06=0.550.5 \times 0.5 + 0.3 \times 1.2 + 0.2 \times (-0.3) = 0.25 + 0.36 - 0.06 = 0.55.

So m′=[0.40,0.55]m' = [0.40, 0.55]. 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 μeff=1/(0.52+0.32+0.22)=1/0.38≈2.63\mu_{\text{eff}} = 1 / (0.5^2 + 0.3^2 + 0.2^2) = 1 / 0.38 \approx 2.63, 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: yi=(xi:λ−m)/σy_i = (x_{i:\lambda} - m) / \sigma. The covariance update blends three terms:

C′=(1−c1−cμ)C+c1pcpcT+cμ∑i=1μwiyiyiTC' = (1 - c_1 - c_\mu) C + c_1 p_c p_c^T + c_\mu \sum_{i=1}^{\mu} w_i y_i y_i^T

  • The rank-μ\mu term (rightmost sum) stretches CC along this generation's winning steps. Many winners pointing northeast fattens the ellipse toward the northeast.
  • The rank-one term (pcpcTp_c p_c^T) stretches CC along the evolution path pcp_c, a fading average of recent mean moves: pc′=(1−cc)pc+cc(2−cc)μeff (m′−m)/σp_c' = (1 - c_c) p_c + \sqrt{c_c (2 - c_c) \mu_{\text{eff}}} \, (m' - m) / \sigma. It notices when steps agree across generations even if no single generation looks stretched.
  • c1c_1, cμc_\mu and ccc_c are small learning rates (typical c1≈0.2/n2c_1 \approx 0.2 / n^2 side values from the original papers for dimension nn). Small values keep CC 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 pσp_\sigma tracks consecutive mean moves, normalized so its length under pure random selection averages E∥N(0,I)∥E\lVert \mathcal{N}(0, I) \rVert (about 1.251.25 in two dimensions). The step size follows:

σ′=σexp⁡(cσdσ(∥pσ∥E∥N(0,I)∥−1))\sigma' = \sigma \exp\left(\frac{c_\sigma}{d_\sigma} \left(\frac{\lVert p_\sigma \rVert}{E\lVert \mathcal{N}(0, I) \rVert} - 1\right)\right)

Steps that march one way grow ∥pσ∥\lVert p_\sigma \rVert past the baseline, so σ\sigma grows and the cloud strides. Steps that cancel shrink it, so σ\sigma tightens and the cloud fine-tunes. cσc_\sigma sets how fast σ\sigma reacts and dσd_\sigma damps the change.

Why practitioners trust it: three invariances

  • Translation invariant: only differences x−mx - m enter the updates, so shifting the whole problem changes nothing.
  • Rotation invariant: CC 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 ff gives identical behavior. Rescaling rewards mid-tuning cannot break it.

The price is sample cost: the default population grows like 4+3ln⁡n4 + 3 \ln n with dimension nn, and learning a full n×nn \times n covariance needs on the order of n2n^2 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.0

Watch Out For

Covariance collapse freezes the search

Symptom: fitness plateaus early while σ\sigma and the ellipse axes shrink to near zero, and restarts from the same CC keep landing in the same spot. A common trigger is starting σ\sigma tiny on a flat plain: early winners cluster by chance, CC learns that accidental tight cluster, and the cloud can no longer reach out.

Fix: set the initial σ\sigma 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 10710^7, restart with a fresh covariance.

Step size blows up on noisy fitness

Symptom: on stochastic rollouts (a policy scored over a few random episodes), σ\sigma 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 σ\sigma up, and never tune σ\sigma by hand to compensate: fix the evaluation budget first.

The Quick Version

  • CMA-ES samples candidates from one Gaussian m+σC1/2zm + \sigma C^{1/2} z and moves mm to the weighted average of the top-ranked tries, as in [0.40,0.55][0.40, 0.55] from weights [0.5,0.3,0.2][0.5, 0.3, 0.2].
  • The covariance update pairs a rank-μ\mu term that stretches along this generation's winners with a rank-one term that stretches along the multi-generation evolution path.
  • The step size σ\sigma 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 n2n^2 samples in dimension nn.