11# NEAT
22
33NEAT, short for Nash-Equilibrium Adaptive Training, is a Keras-first optimizer
4- library for conflict-aware neural network optimization. It exposes a small,
5- production-oriented public API while keeping the novel optimizer math isolated
6- in a backend-agnostic engine that can be tested, benchmarked, and accelerated
7- independently .
4+ library for conflict-aware neural network optimization. It keeps the optimizer
5+ math explicit and testable in a NumPy reference engine, while exposing a small
6+ public API for real model training and an optional native CPU acceleration
7+ path .
88
9- ## Status
9+ ## What NEAT Is
1010
11- - Primary public API: Keras optimizer subclass
11+ - A Keras optimizer you can use in ` model.compile(...) `
12+ - A reference NumPy engine for deterministic algorithm validation
13+ - A player-aware engine that can treat each example or task gradient as a
14+ player in a custom training loop
15+ - A research-friendly implementation of a Nash-inspired correction term
16+ - A small package with a narrow public surface and explicit math spec
17+
18+ ## What NEAT Is Not
19+
20+ - A neural architecture search library
21+ - A framework for building models for you
22+ - A full Nash-equilibrium solver for general multi-agent games
23+ - A claim of universal superiority over Adam or SGD
24+
25+ The first release uses the previous momentum vector as an opponent proxy and
26+ applies a conflict correction when the current gradient moves against that
27+ signal.
28+
29+ The repository also includes a separate player-aware mode that forms an
30+ opponent proxy from other examples in the batch and can add sparsity or hard
31+ pruning pressure for lighter models.
32+
33+ The standard optimizer now also exposes research knobs for:
34+
35+ - opponent source selection: ` momentum ` , ` previous_gradient ` , or ` gradient_ema `
36+ - correction warmup via ` correction_warmup_steps `
37+ - conflict gating via ` conflict_threshold `
38+ - per-run optimizer diagnostics via ` diagnostic_snapshot() `
39+
40+ ## Current Status
41+
42+ - Public training API: Keras optimizer subclass
1243- Reference core: NumPy
13- - Native acceleration: optional CPU-only C++ extension for the NumPy engine
14- - Tested focus for the first release line: Linux and macOS
15- - Python support target: 3.10 to 3.13
16- - Keras runtime note: you must install and configure a supported Keras backend
17- runtime separately, such as TensorFlow
44+ - Native acceleration: optional CPU-only C++ extension
45+ - Tested platforms: Linux and macOS
46+ - Supported Python versions: 3.10 to 3.13
47+ - Supported Keras setup: install ` keras ` plus a backend runtime such as
48+ TensorFlow
49+
50+ ## Why This Repository Exists
51+
52+ Most optimizer repos mix together framework glue, experimental math, and
53+ performance code. NEAT keeps those concerns separate:
54+
55+ - the update rule is specified and tested independently of Keras
56+ - the Keras adapter stays small and serialization-friendly
57+ - the optional native core is constrained behind the same reference semantics
58+
59+ That structure makes the repo easier to review, benchmark, and evolve without
60+ hiding behavior in framework-specific internals.
1861
1962## Installation
2063
21- Core package:
64+ Install the core package:
2265
2366``` bash
2467pip install neat-optim
2568```
2669
27- Keras integration:
70+ Install Keras integration:
2871
2972``` bash
3073pip install " neat-optim[keras]" tensorflow
3174```
3275
33- Developer environment:
76+ Install a local development environment:
3477
3578``` bash
3679python -m venv .venv
3780source .venv/bin/activate
3881pip install -e " .[dev,keras]"
39- pytest
4082```
4183
42- Core -only validation on Python 3.13:
84+ For core -only validation on Python 3.13:
4385
4486``` bash
4587pip install -e " .[dev]"
@@ -48,18 +90,7 @@ pytest
4890
4991## Quick Start
5092
51- Keras usage:
52-
53- ``` python
54- from neat_optim import NEAT
55-
56- optimizer = NEAT(
57- learning_rate = 3e-4 ,
58- alpha = 0.25 ,
59- beta = 0.9 ,
60- nce_mode = " projection" ,
61- )
62- ```
93+ ### Train a Keras Model
6394
6495``` python
6596import keras
@@ -73,15 +104,26 @@ model = keras.Sequential(
73104 ]
74105)
75106
76- optimizer = NEAT(learning_rate = 1e-3 , alpha = 0.2 , beta = 0.9 )
107+ optimizer = NEAT(
108+ learning_rate = 1e-3 ,
109+ alpha = 0.25 ,
110+ beta = 0.9 ,
111+ nce_mode = " projection" ,
112+ opponent_source = " gradient_ema" ,
113+ correction_warmup_steps = 5 ,
114+ )
115+
77116model.compile(
78117 optimizer = optimizer,
79118 loss = keras.losses.SparseCategoricalCrossentropy(from_logits = True ),
80119 metrics = [" accuracy" ],
81120)
121+
122+ history = model.fit(x_train, y_train, epochs = 5 , verbose = 0 )
123+ print (optimizer.diagnostic_snapshot())
82124```
83125
84- Functional reference engine:
126+ ### Use the Reference Engine Directly
85127
86128``` python
87129import numpy as np
@@ -92,57 +134,154 @@ from neat_optim.state import ArrayState
92134param = np.array([1.0 , - 2.0 ], dtype = np.float32)
93135grad = np.array([0.5 , - 0.25 ], dtype = np.float32)
94136state = ArrayState.zeros_like(param)
95- config = NEATConfig(learning_rate = 0.1 , alpha = 0.25 , beta = 0.9 )
137+ config = NEATConfig(
138+ learning_rate = 0.1 ,
139+ alpha = 0.25 ,
140+ beta = 0.9 ,
141+ nce_mode = " projection" ,
142+ )
96143
97144result = neat_step(param, grad, state, config)
98145print (result.param)
146+ print (result.state.momentum)
99147print (result.metrics)
100148```
101149
102- ## Architecture
150+ ### Treat Each Example as a Player
151+
152+ ``` python
153+ import keras
154+ from neat_optim import PlayerNEATConfig
155+ from neat_optim.training import create_player_states, player_train_step
156+
157+ model = keras.Sequential(
158+ [
159+ keras.layers.Input((32 ,)),
160+ keras.layers.Dense(64 , activation = " relu" ),
161+ keras.layers.Dense(10 ),
162+ ]
163+ )
164+ loss_fn = keras.losses.SparseCategoricalCrossentropy(
165+ from_logits = True ,
166+ reduction = " none" ,
167+ )
168+ states = create_player_states(model)
169+ config = PlayerNEATConfig(
170+ learning_rate = 1e-2 ,
171+ alpha = 0.25 ,
172+ beta = 0.9 ,
173+ sparsity_l1 = 1e-4 ,
174+ prune_threshold = 1e-3 ,
175+ )
176+
177+ result = player_train_step(model, x_batch, y_batch, loss_fn, states, config)
178+ states = result.states
179+ ```
180+
181+ ## Public API
103182
104- - ` src/neat_optim/engine/reference.py ` : canonical NumPy implementation of the
105- NEAT update rule
106- - ` src/neat_optim/engine/native.py ` : optional bridge to the native CPU kernel
107- - ` src/neat_optim/keras_optimizer.py ` : Keras optimizer subclass
108- - ` cpp/neat_core/ ` : pybind11-based native extension for the NumPy engine
183+ The first release intentionally keeps the public API narrow:
109184
110- The first release intentionally keeps the public API small:
185+ - ` neat_optim.NEAT `
186+ - ` neat_optim.NEATConfig `
187+ - ` neat_optim.PlayerNEATConfig `
188+ - ` neat_optim.ArrayState `
189+ - ` neat_optim.engine.functional.neat_step `
190+ - ` neat_optim.engine.multiplayer.neat_player_step `
191+ - ` neat_optim.training.player_train_step `
111192
112- - ` NEAT `
113- - ` NEATConfig `
114- - ` ArrayState `
115- - ` neat_step `
193+ The Keras optimizer also exposes ` diagnostic_snapshot() ` and
194+ ` reset_diagnostics() ` for benchmark and experiment code.
116195
117- ## Math Summary
196+ ## How the Update Works
118197
119- For a parameter tensor ` theta_t ` , gradient ` g_t ` , momentum-like buffer
120- ` m_{t-1} ` , and correction scale ` alpha ` :
198+ For parameter tensor ` theta_t ` , gradient ` g_t ` , opponent proxy ` o_t ` , and
199+ correction scale ` alpha ` :
121200
122201``` text
123- c_t = relu(-cos(g_t, m_{t-1} ))
124- p_t = proj_{m_{t-1} }(g_t)
125- nce_t = -alpha * c_t * p_t
126- u_t = g_t + nce_t
127- m_t = beta * m_{t-1} + (1 - beta) * u_t
128- theta_t+1 = (1 - lr * wd) * theta_t - lr * m_t
202+ conflict_t = relu(-cos(g_t, o_t ))
203+ proj_t = proj_{o_t }(g_t)
204+ nce_t = -alpha * conflict_t * proj_t
205+ u_t = g_t + nce_t
206+ m_t = beta * m_{t-1} + (1 - beta) * u_t
207+ theta_t+1 = (1 - lr * wd) * theta_t - lr * m_t
129208```
130209
131- NEAT uses the previous momentum buffer as the opponent proxy for the first
132- release. The exact specification is versioned in
133- [ ` docs/research/math-spec.md ` ] ( docs/research/math-spec.md ) .
210+ The default ` o_t ` is the previous momentum vector, but the standard optimizer
211+ can also use the previous raw gradient or an exponential moving average of
212+ gradients. The correction can be delayed with a warmup or suppressed unless
213+ the measured conflict clears a threshold.
214+
215+ This is Nash-inspired because the optimizer reacts to directional conflict
216+ between the current gradient and an opponent proxy. The exact behavior is
217+ defined in [ ` docs/research/math-spec.md ` ] ( docs/research/math-spec.md ) .
218+
219+ For explicit player-aware stepping, the opponent proxy is built from other
220+ players in the batch. That mode is documented in
221+ [ ` docs/research/player-aware.md ` ] ( docs/research/player-aware.md ) .
222+
223+ ## Lightweight Models
224+
225+ NEAT can now apply lightweight-model pressure through two optional controls:
226+
227+ - ` sparsity_l1 ` : soft-threshold shrinkage after each step
228+ - ` prune_threshold ` : hard pruning of small-magnitude weights to zero
134229
135- ## Repository Layout
230+ These settings encourage sparse parameters. They do not redesign the model
231+ architecture or automatically remove layers.
232+
233+ ## Repository Structure
136234
137235``` text
138- src/neat_optim/ Python package
139- cpp/neat_core/ Native CPU extension
140- tests/ Unit, regression, integration , and property-style tests
236+ src/neat_optim/ Package source
237+ cpp/neat_core/ Optional native CPU extension
238+ tests/ Unit, regression, property , and integration tests
141239benchmarks/ Reproducible benchmark entrypoints
142- examples/ Minimal usage examples
240+ examples/ Minimal runnable examples
143241docs/ User, research, and contributor documentation
144242```
145243
244+ ## Validation Snapshot
245+
246+ At the time of the current repository hardening pass, the project validates
247+ cleanly with:
248+
249+ - lint checks via ` ruff `
250+ - package build via ` python -m build `
251+ - docs build via ` mkdocs build --strict `
252+ - Keras integration tests
253+ - reference/native parity tests
254+ - a real Keras MLP benchmark against SGD, Adam, and AdamW
255+ - benchmark diagnostics and sweep tooling for NEAT-specific ablations
256+
257+ In a small real supervised-learning experiment on the ` sklearn ` digits dataset,
258+ the reference engine successfully trained a two-layer MLP to ` 0.9194 ` test
259+ accuracy after 10 epochs. That result is useful as a sanity check, not as a
260+ benchmark claim.
261+
262+ On the current 20-epoch Keras digits benchmark, NEAT reaches ` 0.9472 ` mean
263+ test accuracy and trails SGD/Adam baselines. The attached diagnostics show why
264+ that is plausible: the mean correction ratio is only ` 0.00385 ` and the mean
265+ gradient/update alignment is ` 0.99991 ` , so NEAT is behaving very close to its
266+ base update on this task.
267+
268+ To reproduce the benchmark:
269+
270+ ``` bash
271+ python benchmarks/run.py
272+ ```
273+
274+ To run the coarse NEAT sweep:
275+
276+ ``` bash
277+ python benchmarks/sweep_neat.py
278+ ```
279+
280+ In the reproducible Keras benchmark on the same digits family of task, tuned
281+ NEAT reached ` 94.72% ` mean test accuracy across three seeds, versus ` 97.04% `
282+ for SGD with momentum and ` 96.85% ` for Adam and AdamW. The detailed report is
283+ in [ ` docs/research/benchmarks.md ` ] ( docs/research/benchmarks.md ) .
284+
146285## Development
147286
148287Useful commands:
@@ -152,19 +291,29 @@ ruff check .
152291ruff format .
153292pytest
154293python -m build
294+ mkdocs build --strict
155295```
156296
157- ## Open Source Policy
297+ ## Documentation
158298
299+ - Documentation index: [ ` docs/index.md ` ] ( docs/index.md )
300+ - Quickstart: [ ` docs/quickstart.md ` ] ( docs/quickstart.md )
301+ - API reference: [ ` docs/api.md ` ] ( docs/api.md )
302+ - Math spec: [ ` docs/research/math-spec.md ` ] ( docs/research/math-spec.md )
303+ - Player-aware mode: [ ` docs/research/player-aware.md ` ] ( docs/research/player-aware.md )
159304- Contributor guide: [ ` CONTRIBUTING.md ` ] ( CONTRIBUTING.md )
305+
306+ ## Open Source Policy
307+
160308- Code of conduct: [ ` CODE_OF_CONDUCT.md ` ] ( CODE_OF_CONDUCT.md )
161309- Security policy: [ ` SECURITY.md ` ] ( SECURITY.md )
310+ - Contributing guide: [ ` CONTRIBUTING.md ` ] ( CONTRIBUTING.md )
162311
163312## Roadmap
164313
165314- ` 0.1.x ` : stabilize the Keras-first API and NumPy reference engine
166- - ` 0.2.x ` : ship native CPU wheels and benchmark parity
167- - ` 0.3.x ` : add richer Keras backend coverage and framework adapters
315+ - ` 0.2.x ` : expand native CPU support and benchmark coverage
316+ - ` 0.3.x ` : add richer Keras backend coverage and adapter surface
168317
169318## License
170319
0 commit comments