Evolutionary Methods and Neuroevolution
Instead of computing gradients, keep a population of policies, score each one, and breed the winners with small random tweaks until behavior improves.
Why Does This Exist?
Gradients need a slope to follow, and in reinforcement learning that slope often does not exist. The reward can stay flat for hundreds of moves and then pay out once, so every small nudge of the weights returns zero signal. It can be deceptive, where the nearby improvement points away from the real goal. Or the policy itself can be something you cannot differentiate at all: a discrete rule set, a compiled simulator, a physical robot.
Policy gradient methods estimate a slope from episode returns, and deep reinforcement learning adds neural networks that learn features from raw input. Both still assume that nudging parameters toward higher return gives a usable signal. When the reward stays flat for long stretches, that nudge carries no information and learning stalls. That flat stretch is the same problem behind exploration versus exploitation: you must try far-apart behaviors before you can refine any of them.
Evolution takes a different deal. Skip derivatives entirely. Keep a population of full policies, run each one, score it by episode return, keep the winners, mix them, add random tweaks, and repeat. It works with any reward function and any policy form because it only looks at scores, never at slopes.
This page teaches the shared loop: selection, crossover, and mutation. It also sketches three specialized children so you can tell them apart: Evolution Strategies, which samples Gaussian noise around a mean policy; CMA-ES, which adapts the shape of that sampling distribution as it learns; and NEAT, which grows network weights and network topology together starting from small nets.
The one idea you need first: fitness means episode return here. One number per policy, higher is better: total reward from a full run. Everything else is machinery for producing policies with bigger numbers.
Think of It Like This
Saving seeds from your best tomato plants
You plant four tomato rows in spring. At harvest you taste each row and weigh the yield. You save seeds only from the two tastiest, most productive rows. Next spring you plant those saved seeds, cross two of the best plants to mix traits, and accept that a little natural variation shows up on its own. You also replant your single best row unchanged, in case the new mixes flop.
The map is direct. The four rows are the population. Taste plus yield is fitness, the episode return. Saving two rows is truncation selection. Mixing two plants is crossover. Natural variation is mutation. Replanting the best row unchanged is elitism.
Where it stops: your garden gives one harvest a year with weather noise you cannot control. Here you choose the fitness function and the tweak size, usually a Gaussian spread you shrink over time, and you test every candidate from the same start, which no garden lets you do.
How It Actually Works
Call the policy weights : one flat vector holding every number the policy needs, whether that is four scalars or four million network weights. Call fitness : the score after running the policy, usually total episode return. Call the population size, here . Call the survivors you breed from, here . Call the mutation spread, here , the standard deviation of the Gaussian noise you add.
One generation runs five steps:
- Start with weight vectors.
- Run each one for one or more full episodes and record .
- Select the top scorers and drop the rest.
- Vary the survivors by crossover, often a plain average, plus mutation, usually fresh draws from added to each weight.
- Replace the old population with the children, keep the single best parent unchanged, and repeat.
No gradient appears anywhere. The only signal is the ranking of scores.
Three flavors you will meet
Evolution Strategies keeps a mean vector and samples children as , with drawn from a standard Gaussian. It then moves toward the noise directions that scored well. Shared random seeds let thousands of workers generate the same perturbations without passing big weight vectors around, which is why OpenAI's 2017 work made this form popular for RL.
CMA-ES keeps the sampling idea but also learns the covariance of the noise, so sampling becomes . When one direction keeps paying off, the distribution stretches along it. That helps on stretched, ill-conditioned problems where a round ball wastes samples, at the price of more bookkeeping per step.
NEAT evolves the network itself, not just its weights. It starts from small nets with no hidden units, adds nodes and links by mutation, tags every new gene with a historical marking so two nets can cross over sensibly, and groups similar topologies into species that share fitness, so a promising new shape survives long enough for its weights to catch up.
Worked example: selecting from four policies
Four one-weight policies with measured returns:
- ,
- ,
- ,
- ,
Total fitness is , so mean fitness is . Ranked best to worst: at 35, at 22, at 15, at 8. Truncation with keeps and ; the cut sits at 22 and everything below it is gone.
Crossover averages the two parents:
Now apply fixed mutation draws for teaching, all consistent with :
- Crossover child:
- Copy of best parent:
- Copy of second parent:
With elitism the next generation is (elite copy of ), (crossover plus mutation), (best parent mutated), (second parent mutated). All four cluster near the two winners, 0.3 and 0.9, which is exactly what one step should do. None of the children has a score yet: the next step must run four fresh episodes, which is why the diagram closes the loop and flags the cost at the bottom.
Code
A pure-Python transcription of the worked example. It ranks the population, breeds the top two, and prints the next generation.
weights = [-0.4, 0.3, 0.9, 1.6]fitness = [8.0, 22.0, 35.0, 15.0]
mean_fitness = sum(fitness) / len(fitness)ranked = sorted(zip(weights, fitness), key=lambda pair: pair[1], reverse=True)best, second = ranked[0][0], ranked[1][0]child_mean = (best + second) / 2.0
next_gen = [best, round(child_mean + 0.12, 2), round(best - 0.07, 2), round(second + 0.03, 2)]
print(mean_fitness) # -> 20.0print((best, second)) # -> (0.9, 0.3)print(child_mean) # -> 0.6print(next_gen) # -> [0.9, 0.72, 0.83, 0.33]Watch Out For
Each step burns full episodes
The symptom is slow wall-clock progress. This page used 4 episodes for one update; real runs use hundreds or thousands of policies per generation, and each one needs full episodes before the update happens. If your simulator is slow or each episode runs minutes on hardware, one generation can take all day.
The fix is to match the tool to the reward. When rewards are dense and the policy is differentiable, prefer policy gradient methods, where each episode gives gradient information for every step. When you must evolve, parallelize rollouts across workers, share random seeds instead of weight vectors, and start with small populations before scaling up.
Everyone crowds the first winner
The symptom is a population that looks done after a few rounds but scores poorly. All weights sit near 0.9 in the example, variation shrinks toward zero, and no child ever tries far regions again. Greedy top-k selection plus tiny causes this fast.
The fix is to protect diversity on purpose. Raise early and shrink it later, use tournament selection instead of always taking the top two, keep an elite of one rather than half the population, and restart or inject fresh random members when the spread of fitness stays near zero for several rounds.
The Quick Version
- Neuroevolution scores whole policies and breeds winners, so it works without differentiable losses or slope signals.
- One generation selects the top scorers, mixes parents by averaging, then adds Gaussian noise with spread .
- The four-policy example scores 8, 22, 35 and 15 for mean 20.0 and keeps the policies at 0.9 and 0.3.
- Crossover mean 0.6 plus fixed tweaks builds the next generation at 0.90, 0.72, 0.83 and 0.33 before rescoring.
- Evolution fits sparse, deceptive, or non-differentiable rewards; gradients fit dense rewards with cheap differentiable policies.