ES-HyperNEAT in 3D: experiments
Code behind the two-part series 3D creatures need 3D brains
and A gradient test for ES-HyperNEAT’s quadtree.
Pure Python and NumPy, no GPU, no autodiff library: the CPPN’s backward pass
is written out by hand in eshn.py.
| File | What it is |
|---|---|
eshn.py |
CPPN (forward and input-gradient), the 2^n-tree substrate search with both split tests, network assembly, the evolution strategy |
tasks.py |
The pursuit task in 2, 3 and 4 dimensions, and its substrate layouts |
worlds.py |
The retina and docking tasks, and one registry of every task and layout |
handset.py |
The hand-set locality rule on every layout, with the grid search behind Part 1’s table |
run_pursuit.py |
Train one arm for one seed |
scaling.py |
Search cost against substrate dimension, and how often the two split tests disagree |
run_all.py |
The whole protocol, resumable, any OS |
summarise.py |
Turns results/ into assets/es-hyperneat/results.json for the post |
export_swimmers.py |
Exports a few evolved CPPNs (checked against their recorded scores) for the swimming-cell figures |
The sampling and gradient arms differ only in complexity() in eshn.py.
Protocol
Fixed before any sampling-vs-gradient comparison was looked at:
- pursuit: 120 generations of 32 genomes (16 antithetic pairs), held-out score on 16 fixed chases every 5 generations
- 3D and 2D (map projection) substrates, sampling and gradient tests: seeds 0-7
- 2D “drop z” and 2D ring substrates, sampling only: seeds 0-3
- added after review, sampling test, seeds 0-7:
- neuron budget: 3D and 2D map with exactly 75 or 480 hidden neurons (
--budget: grow the full tree, keep the cells where the weights vary most)
- neuron budget: 3D and 2D map with exactly 75 or 480 hidden neurons (
- other worlds, gradient test, seeds 0-7 (
worlds.py):- a flat creature (
pursuit2: 2D matched, 3D with a meaningless extra axis, 1D angle line) - a 4D creature (
pursuit4: 4D matched, 3D map, 3D drop w) - a 3D retina (
retina3: tell a 2x2x2 cube from a 2x2x1 pancake in a 4x4x4 volume; 3D, 2D slices side by side, 2D drop z) - 6-DOF docking (
docking6: reach a dock and match its tumble; 6D with moving and turning at right angles, 3D side by side, 3D nested shells, two 2D maps side by side)
The retina and docking tasks are ports of experiments from an earlier draft of the posts, rebuilt on this code with seeds and saved results.
- a flat creature (
python run_all.py --groups budget,worlds --no-scaling runs only the added sets.
- scaling: 10 random CPPNs for each substrate dimension n = 2..7, tree depth 2 (plus depth 3-4 for n = 2, 3)
Running it on Windows
Needs Python 3.10 or newer and git. In PowerShell:
git fetch origin
git checkout es-hyperneat-experiments
cd experiments\es-hyperneat
py -m venv .venv
.\.venv\Scripts\Activate.ps1 # if blocked: Set-ExecutionPolicy -Scope Process Bypass
python -m pip install -r requirements.txt
python run_all.py --smoke # ~1 minute: checks everything runs, writes to results_smoke\
python run_all.py # the real thing
run_all.py uses every core but one and prints a line as each run finishes.
Keep the laptop on mains power and stop it sleeping while it runs
(powercfg /change standby-timeout-ac 0). If anything interrupts it, run the
same command again: finished runs are kept and skipped.
When it finishes it writes assets\es-hyperneat\results.json. Commit the
results and push:
cd ..\..
git add experiments/es-hyperneat/results assets/es-hyperneat/results.json
git commit -m "ES-HyperNEAT experiment results"
git push -u origin es-hyperneat-experiments
Simplifications relative to ES-HyperNEAT as published
All of these apply identically to every arm:
- The CPPN has a fixed shape (two hidden layers of sin, Gaussian and tanh units, plus a linear skip path) and is trained with an OpenAI-style evolution strategy, not evolved with NEAT.
- One tree per network, with complexity averaged over every input and output neuron, rather than one tree per input neuron.
- One hidden layer; band pruning is replaced by keeping leaves whose own complexity exceeds a lower threshold.
- Two changes were needed before anything learned at all (see the post): 1/sqrt(fan-in) scaling in the substrate network, and shifting the CPPN’s output bias so its average weight starts at zero.