NEAT: Evolving Neural Topologies
NEAT grows neural nets instead of fixing their size first: it starts from the smallest wiring, stamps each change with a number, and breeds topologies by task score.
Why Does This Exist?
Every neural net starts with a guess about size. Too few hidden units and the net cannot represent the task. Too many and you pay in slow evaluation, dead weights, and tuning pain. Backprop cannot fix this because it tunes weights inside fixed wiring. Topology stays what a human drew on day one.
Stanley and Miikkulainen introduced NEAT in 2002 to evolve wiring and weights together. Every run starts from the smallest net that connects inputs to outputs, then adds structure only when the bigger shape earns its place. Search over structures is exploration at the topology level, while the weight tuning it replaces is the gradient machinery of deep reinforcement learning.
This page covers the four moves that make it work: minimal start complexification, historical markings that align crossover, speciation that shelters newcomers, and fitness sharing that stops one species from taking over. For the gradient alternative that keeps topology fixed, read policy gradient next. Fitness below means the task score you define, such as game score or distance walked.
Think of It Like This
The numbered brick bridge contest
You run a bridge contest where every brick carries a factory serial number in order of manufacture.
Each team starts with two bricks, the smallest bridge that stands. Each round a team may add one brick or split one span with a joint, and the new piece takes the next serial. To breed two bridges, you lay both instruction lists side by side and line up matching serials. Same serials swap freely. A serial skipped inside the other list is a disagreement. Serials past the other list maximum are genuine novelties. Young bridges with wild new joints enter a junior league first, so a promising but untuned shape survives long enough to prove itself.
The map is exact. Instruction rows are connection genes. Serials are innovation numbers. Lining up serials is crossover alignment. The junior league is speciation. Scores divided by league size are fitness sharing.
Where it stops: contest fitness is finished bridges, while NEAT fitness is any task score you define, and a poor score breeds poor bridges fast. The picture also hides disabled genes. Old wires switch off but stay in the genome, so they can switch back on later.
How It Actually Works
A NEAT genome is a parts list. Node genes name inputs, outputs, and hidden nodes as they appear. Connection genes wire them. Each connection stores an input node, an output node, a weight, an enabled flag, and an innovation number. The innovation number is a global counter that stamps every structural mutation ever invented, in order. The same shape invented twice in different genomes carries the same number.
Crossover aligns by history
Line up both parents by innovation number. Matching genes share a number and come from either parent at random. Disjoint genes miss inside the other parent range. Excess genes sit past the other parent maximum. Disjoint and excess genes come from the fitter parent. Two nets with different shapes still cross cleanly because shared history decides what corresponds.
Compatibility distance with numbers
Speciation needs a distance. For two genomes, count excess genes , disjoint genes , and mean weight gap over matching genes . Let be the larger gene count. With coefficients , , and :
Here is the compatibility distance, counts excess genes, counts disjoint genes, is the mean absolute weight gap on matching genes, and scales for genome length. Use the paper values , , with threshold . Take .
Pair A versus B has , , :
Since , B joins A species.
Pair A versus C has , , :
Since , C founds a new species.
Sheltering newcomers and sharing fitness
A fresh hidden node starts with random weights, so its first score often trails the tuned small net. Without shelter, selection kills each novelty in one generation. Speciation fixes this by grouping close genomes so newcomers race their own kind while they tune.
Fitness sharing then splits raw score by species size. A genome scoring 40 in a species of 5 gets . Large species pay tax, small promising ones keep room, and cloning alone cannot win.
Growth itself is two mutations. Add link wires two existing nodes. Add node splits one wire: the old gene switches off but stays stored, and two new genes route through a fresh hidden node. Because runs start minimal and each addition must survive speciated contests, nets stay as small as the task allows.
Code
This snippet recomputes both distances and the sharing adjustment from the worked example.
def compatibility_distance(excess: int, disjoint: int, w_bar: float, n: int) -> float: return excess / n + disjoint / n + 0.4 * w_bar
def adjusted_fitness(raw: float, size: int) -> float: return raw / size
print(f"{compatibility_distance(1, 2, 0.5, 12):.4f}") # -> 0.4500print(f"{compatibility_distance(4, 5, 1.4, 12):.4f}") # -> 1.3100print(f"{adjusted_fitness(40.0, 5):.1f}") # -> 8.0Watch Out For
Topology bloat eats the compute budget
NEAT adds structure more eagerly than it removes it. The symptom is a population of sprawling nets whose score flatlines while evaluation time climbs. Each extra wire costs a multiply on every forward pass, and disabled genes still get copied, compared, and crossed over.
Push back with a small fitness penalty per connection, cap the per generation structural rate, and prune runs whose disabled fraction keeps rising. When mean genome length doubles with no score gain over a dozen generations, bloat has outrun selection.
A lazy speciation threshold kills novelty
Threshold decides what counts as a new species, and a poor value fails quietly. Too low and each genome forms a species of one, so sharing divides by 1 and shelter means nothing. Too high and the whole population collapses into one species, where tuned small nets outbreed each newcomer before random weights settle.
Watch species count, not only score. Healthy runs hold several species of mixed size that turn over across generations. When you see one species forever, or fifty species of one, retune until newcomers survive roughly ten to twenty generations before open contest.
The Quick Version
- NEAT evolves wiring and weights together, starting each run from a minimal net with inputs wired straight to outputs.
- Innovation numbers stamp each structural change in order, so crossover lines up genes by shared history.
- Compatibility distance gates species: 0.45 keeps B with A, 1.31 splits C off.
- Speciation shelters fresh shapes from open contest while they tune random weights.
- Fitness sharing divides raw score by species size, so 40 over 5 members scores 8.0.
- Add link and add node grow the net, with old genes switched off but kept, so small nets stay small until complexity pays.