Skip to content

Commit e00489e

Browse files
committed
NCE Fixing / Hyperparameter tests
1 parent f414c26 commit e00489e

40 files changed

Lines changed: 4009 additions & 193 deletions

README.md

Lines changed: 211 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,87 @@
11
# NEAT
22

33
NEAT, 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
2467
pip install neat-optim
2568
```
2669

27-
Keras integration:
70+
Install Keras integration:
2871

2972
```bash
3073
pip install "neat-optim[keras]" tensorflow
3174
```
3275

33-
Developer environment:
76+
Install a local development environment:
3477

3578
```bash
3679
python -m venv .venv
3780
source .venv/bin/activate
3881
pip 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
4587
pip 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
6596
import 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+
77116
model.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
87129
import numpy as np
@@ -92,57 +134,154 @@ from neat_optim.state import ArrayState
92134
param = np.array([1.0, -2.0], dtype=np.float32)
93135
grad = np.array([0.5, -0.25], dtype=np.float32)
94136
state = 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

97144
result = neat_step(param, grad, state, config)
98145
print(result.param)
146+
print(result.state.momentum)
99147
print(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
141239
benchmarks/ Reproducible benchmark entrypoints
142-
examples/ Minimal usage examples
240+
examples/ Minimal runnable examples
143241
docs/ 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

148287
Useful commands:
@@ -152,19 +291,29 @@ ruff check .
152291
ruff format .
153292
pytest
154293
python -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

Comments
 (0)