@reicek/neataptic-ts 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -143
- package/docs/README.md +58 -143
- package/docs/assets/ascii-maze.bundle.js +1 -1
- package/docs/assets/ascii-maze.bundle.js.map +1 -1
- package/docs/examples/asciiMaze/index.html +2 -2
- package/docs/index.html +58 -141
- package/package.json +1 -1
- package/plans/HyperMorphoNEAT.md +525 -751
- package/plans/Memory_Optimization.md +195 -112
- package/plans/ONNX_EXPORT_PLAN.md +7 -9
- package/test/examples/asciiMaze/index.html +2 -2
package/README.md
CHANGED
|
@@ -103,8 +103,7 @@ import { Neat } from '@reicek/neataptic-ts';
|
|
|
103
103
|
|
|
104
104
|
const neatMo = new Neat(2, 1, fitness, {
|
|
105
105
|
popsize: 60,
|
|
106
|
-
multiObjective: { enabled: true
|
|
107
|
-
novelty: { enabled: true, descriptor: g => [g.nodes.length, g.connections.length], k: 8, blendFactor: 0.25 },
|
|
106
|
+
multiObjective: { enabled: true },
|
|
108
107
|
seed: 7,
|
|
109
108
|
});
|
|
110
109
|
await neatMo.evaluate();
|
|
@@ -191,10 +190,12 @@ Principle: add one lever at a time and measure its *telemetry delta* (front size
|
|
|
191
190
|
Fast iteration recipe:
|
|
192
191
|
|
|
193
192
|
```ts
|
|
193
|
+
import { cpus } from 'node:os';
|
|
194
|
+
|
|
194
195
|
const neat = new Neat(inp, out, fit, {
|
|
195
196
|
popsize: 120,
|
|
196
197
|
fastMode: true,
|
|
197
|
-
threads:
|
|
198
|
+
threads: cpus().length > 1 ? cpus().length - 1 : 1,
|
|
198
199
|
adaptiveMutation: { enabled: true, strategy: 'twoTier' },
|
|
199
200
|
telemetry: { enabled: true, performance: true, complexity: true }
|
|
200
201
|
});
|
|
@@ -221,7 +222,10 @@ Evolve topology first, then fine‑tune weights:
|
|
|
221
222
|
|
|
222
223
|
```ts
|
|
223
224
|
const best = neat.getBest();
|
|
224
|
-
await best?.train(data, {
|
|
225
|
+
await best?.train(data, {
|
|
226
|
+
iterations: 500,
|
|
227
|
+
rate: 0.01,
|
|
228
|
+
optimizer: 'adam',
|
|
225
229
|
gradientClip: { mode: 'norm', maxNorm: 1 },
|
|
226
230
|
movingAverageWindow: 5,
|
|
227
231
|
});
|
|
@@ -238,7 +242,7 @@ await best?.train(data, {
|
|
|
238
242
|
optimizer: 'adamw',
|
|
239
243
|
gradientClip: { mode: 'norm', maxNorm: 1 },
|
|
240
244
|
movingAverageWindow: 7,
|
|
241
|
-
metricsHook: m => console.log(m.iteration, m.error, m.gradNorm)
|
|
245
|
+
metricsHook: m => console.log(m.iteration, m.error, m.gradNorm),
|
|
242
246
|
});
|
|
243
247
|
```
|
|
244
248
|
|
|
@@ -351,7 +355,7 @@ This section summarizes practical knobs to accelerate evolution while preserving
|
|
|
351
355
|
|
|
352
356
|
### Structural Complexity Caching
|
|
353
357
|
|
|
354
|
-
`
|
|
358
|
+
`neat.evolve` caches per-genome complexity metrics (nodes / connections / gates). This avoids recomputing counts each evaluation when unchanged, reducing overhead for large populations or deep architectures.
|
|
355
359
|
|
|
356
360
|
### Profiling
|
|
357
361
|
|
|
@@ -380,8 +384,6 @@ const neat = new Neat(inputs, outputs, fitness, {
|
|
|
380
384
|
popsize: 120,
|
|
381
385
|
threads: 8,
|
|
382
386
|
telemetry: { enabled: true, performance: true },
|
|
383
|
-
telemetrySelect: ['performance', 'complexity'],
|
|
384
|
-
adaptiveMutation: { enabled: true, strategy: 'twoTier' },
|
|
385
387
|
fastMode: true,
|
|
386
388
|
});
|
|
387
389
|
```
|
|
@@ -427,7 +429,7 @@ When enabled BEFORE constructing / mutating a `Network`:
|
|
|
427
429
|
Intended Usage:
|
|
428
430
|
|
|
429
431
|
```ts
|
|
430
|
-
import { config } from 'neataptic';
|
|
432
|
+
import { config, methods } from '@reicek/neataptic-ts';
|
|
431
433
|
config.deterministicChainMode = true; // enable
|
|
432
434
|
const net = new Network(1, 1);
|
|
433
435
|
for (let i = 0; i < 5; i++) net.mutate(methods.mutation.ADD_NODE); // guaranteed 5 hidden chain
|
|
@@ -524,9 +526,7 @@ const neat = new Neat(4, 2, fitnessFn, {
|
|
|
524
526
|
multiObjective: { enabled: true, complexityMetric: 'nodes' },
|
|
525
527
|
});
|
|
526
528
|
// Add structural entropy (maximize)
|
|
527
|
-
neat.registerObjective('entropy', 'max', (g)
|
|
528
|
-
(neat as any)._structuralEntropy(g)
|
|
529
|
-
);
|
|
529
|
+
neat.registerObjective('entropy', 'max', g => (neat as any)._structuralEntropy(g));
|
|
530
530
|
await neat.evaluate();
|
|
531
531
|
await neat.evolve();
|
|
532
532
|
console.log(neat.getObjectives()); // [{key:'fitness',...},{key:'complexity',...},{key:'entropy',...}]
|
|
@@ -583,16 +583,16 @@ Configure an adaptive schedule that expands limits when improvement slope is pos
|
|
|
583
583
|
|
|
584
584
|
```ts
|
|
585
585
|
complexityBudget: {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
586
|
+
enabled: true,
|
|
587
|
+
mode: 'adaptive',
|
|
588
|
+
maxNodesStart: 4,
|
|
589
|
+
maxNodesEnd: 24,
|
|
590
|
+
improvementWindow: 8,
|
|
591
|
+
increaseFactor: 1.15,
|
|
592
|
+
stagnationFactor: 0.93,
|
|
593
|
+
minNodes: 4,
|
|
594
|
+
maxConnsStart: 40,
|
|
595
|
+
maxConnsEnd: 400,
|
|
596
596
|
}
|
|
597
597
|
```
|
|
598
598
|
|
|
@@ -669,11 +669,11 @@ diversityPressure:{ enabled:true, motifSample:25, penaltyStrength:0.05 }
|
|
|
669
669
|
Adaptive novelty threshold targeting an archive insertion rate:
|
|
670
670
|
|
|
671
671
|
```ts
|
|
672
|
-
novelty:{
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
672
|
+
novelty: {
|
|
673
|
+
enabled: true,
|
|
674
|
+
descriptor: g => [g.nodes.length, g.connections.length],
|
|
675
|
+
archiveAddThreshold: 0.5,
|
|
676
|
+
dynamicThreshold: { enabled: true, targetRate: 0.15, adjust: 0.1, min: 0.01, max: 10 },
|
|
677
677
|
}
|
|
678
678
|
```
|
|
679
679
|
|
|
@@ -684,13 +684,13 @@ After each evaluation the threshold is nudged up/down so the fraction of inserte
|
|
|
684
684
|
If you experiment with many custom objectives it is common for some to become constant (providing no ranking discrimination). Enable automatic removal of such stagnant objectives:
|
|
685
685
|
|
|
686
686
|
```ts
|
|
687
|
-
multiObjective:{
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
687
|
+
multiObjective: {
|
|
688
|
+
enabled: true,
|
|
689
|
+
objectives: [
|
|
690
|
+
{ key: 'fitness', direction: 'max', accessor: g => g.score },
|
|
691
|
+
{ key: 'novelty', direction: 'max', accessor: g => (g as any)._novelty },
|
|
692
|
+
],
|
|
693
|
+
pruneInactive: { enabled: true, window: 5, rangeEps: 1e-9, protect: ['fitness'] },
|
|
694
694
|
}
|
|
695
695
|
```
|
|
696
696
|
|
|
@@ -742,13 +742,13 @@ Use these to detect genealogical stagnation (both remaining near zero) vs broad
|
|
|
742
742
|
Apply score adjustments based on lineage structure (depth dispersion) or penalize inbreeding (high ancestor overlap):
|
|
743
743
|
|
|
744
744
|
```ts
|
|
745
|
-
lineagePressure:{
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
745
|
+
lineagePressure: {
|
|
746
|
+
enabled: true,
|
|
747
|
+
mode: 'antiInbreeding', // 'penalizeDeep' | 'rewardShallow' | 'spread' | 'antiInbreeding'
|
|
748
|
+
strength: 0.02, // generic scaling for depth modes
|
|
749
|
+
ancestorWindow: 4, // generations to look back when computing ancestor sets
|
|
750
|
+
inbreedingPenalty: 0.04, // override penalty scaling (defaults to strength*2)
|
|
751
|
+
diversityBonus: 0.02, // bonus scaling for very distinct parent lineages
|
|
752
752
|
}
|
|
753
753
|
```
|
|
754
754
|
|
|
@@ -791,7 +791,7 @@ Operator adaptation vs bandit:
|
|
|
791
791
|
Per-genome mutation rate/amount adapt each generation under strategies (`twoTier`, `exploreLow`, `anneal`). Use:
|
|
792
792
|
|
|
793
793
|
```ts
|
|
794
|
-
adaptiveMutation:{ enabled:true, strategy:'twoTier', sigma:0.08, adaptAmount:true }
|
|
794
|
+
adaptiveMutation: { enabled: true, strategy: 'twoTier', sigma: 0.08, adaptAmount: true }
|
|
795
795
|
```
|
|
796
796
|
|
|
797
797
|
Operator success statistics (bandit + weighting):
|
|
@@ -875,7 +875,7 @@ const csv = neat.exportSpeciesHistoryCSV();
|
|
|
875
875
|
|
|
876
876
|
These APIs are evolving; consult source `src/neat.ts` for full option surfaces while docs finalize.
|
|
877
877
|
|
|
878
|
-
Full option & telemetry reference:
|
|
878
|
+
Full option & telemetry reference: See the generated documentation in the `docs/` directory.
|
|
879
879
|
|
|
880
880
|
# Network Constructor Update
|
|
881
881
|
|
|
@@ -888,6 +888,7 @@ new Network(input: number, output: number, options?: { minHidden?: number })
|
|
|
888
888
|
- `input`: Number of input nodes (required)
|
|
889
889
|
- `output`: Number of output nodes (required)
|
|
890
890
|
- `options.minHidden`: (optional) If set, enforces a minimum number of hidden nodes. If omitted or 0, no minimum is enforced. This allows true 1-1 (input-output only) networks.
|
|
891
|
+
- `options.seed`: (optional) A numeric seed for the random number generator to ensure reproducible initial weights and biases.
|
|
891
892
|
|
|
892
893
|
**Example:**
|
|
893
894
|
|
|
@@ -895,21 +896,25 @@ new Network(input: number, output: number, options?: { minHidden?: number })
|
|
|
895
896
|
// Standard 1-1 network (no hidden nodes)
|
|
896
897
|
const net = new Network(1, 1);
|
|
897
898
|
|
|
898
|
-
// Enforce at least 3 hidden nodes
|
|
899
|
-
const netWithHidden = new Network(2, 1, { minHidden: 3 });
|
|
899
|
+
// Enforce at least 3 hidden nodes and set a seed
|
|
900
|
+
const netWithHidden = new Network(2, 1, { minHidden: 3, seed: 123 });
|
|
900
901
|
```
|
|
901
902
|
|
|
902
|
-
# Neat Evolution
|
|
903
|
+
# Neat Evolution Options
|
|
903
904
|
|
|
904
|
-
The `
|
|
905
|
+
The `Neat` class constructor accepts an options object that includes all `Network` options (`minHidden`, `seed`) plus evolution-specific settings.
|
|
905
906
|
|
|
906
907
|
```ts
|
|
907
|
-
import Neat from '
|
|
908
|
-
const neat = new Neat(2, 1, fitnessFn, {
|
|
908
|
+
import { Neat } from '@reicek/neataptic-ts';
|
|
909
|
+
const neat = new Neat(2, 1, fitnessFn, {
|
|
910
|
+
popsize: 50,
|
|
911
|
+
minHidden: 5, // Passed to Network constructor
|
|
912
|
+
seed: 4242, // Passed to both Neat and Network constructors
|
|
913
|
+
});
|
|
909
914
|
```
|
|
910
915
|
|
|
911
916
|
- All networks created by the evolutionary process will have at least 5 hidden nodes.
|
|
912
|
-
-
|
|
917
|
+
- The `seed` ensures that the entire evolutionary process, including initial population creation, is reproducible.
|
|
913
918
|
|
|
914
919
|
See tests in `test/neat.ts` for usage and verification.
|
|
915
920
|
|
|
@@ -922,7 +927,7 @@ Interoperability layer for exchanging strictly layered MLP (and experimental rec
|
|
|
922
927
|
### Basic Usage
|
|
923
928
|
|
|
924
929
|
```ts
|
|
925
|
-
import { exportToONNX, importFromONNX } from '
|
|
930
|
+
import { exportToONNX, importFromONNX } from '@reicek/neataptic-ts';
|
|
926
931
|
|
|
927
932
|
// Export
|
|
928
933
|
const onnxModel = exportToONNX(network, { includeMetadata: true });
|
|
@@ -1088,7 +1093,7 @@ Notes:
|
|
|
1088
1093
|
### Learning Rate Scheduler Usage
|
|
1089
1094
|
|
|
1090
1095
|
```ts
|
|
1091
|
-
import methods from '
|
|
1096
|
+
import { methods } from '@reicek/neataptic-ts';
|
|
1092
1097
|
const ratePolicy = methods.Rate.cosineAnnealingWarmRestarts(200, 1e-5, 2);
|
|
1093
1098
|
net.train(data, { iterations: 1000, rate: 0.1, ratePolicy });
|
|
1094
1099
|
```
|
|
@@ -1330,8 +1335,7 @@ Optimizer reference (choose based on signal quality & overfitting risk):
|
|
|
1330
1335
|
| adamax | beta1, beta2, eps | Infinity norm (u) instead of v |
|
|
1331
1336
|
| nadam | beta1, beta2, eps | Nesterov variant of Adam |
|
|
1332
1337
|
| radam | beta1, beta2, eps | Rectifies variance early in training |
|
|
1333
|
-
|
|
|
1334
|
-
| adabelief | beta1, beta2, eps | Second moment of (g - m) (gradient surprise) |
|
|
1338
|
+
| AdaBelief | beta1, beta2, eps | Second moment of (g - m) (gradient surprise) |
|
|
1335
1339
|
| lookahead | baseType, la_k, la_alpha | Interpolates toward slow weights every k steps |
|
|
1336
1340
|
|
|
1337
1341
|
General guidance:
|
|
@@ -1658,92 +1662,3 @@ const neat = new Neat(2, 1, fitness, {
|
|
|
1658
1662
|
```
|
|
1659
1663
|
|
|
1660
1664
|
Use when raw search space contains huge numbers of trivial zero-score genomes (e.g. all-linear tiny nets). The filter prevents them from influencing speciation/dominance ordering; they still evolve structurally until criterion passes.
|
|
1661
|
-
|
|
1662
|
-
```
|
|
1663
|
-
|
|
1664
|
-
#### Adaptive Sharing
|
|
1665
|
-
|
|
1666
|
-
If `adaptiveSharing.enabled` the system adjusts `sharingSigma` each generation:
|
|
1667
|
-
|
|
1668
|
-
```
|
|
1669
|
-
|
|
1670
|
-
sigma += step \* sign(fragmentation - target)
|
|
1671
|
-
|
|
1672
|
-
```
|
|
1673
|
-
|
|
1674
|
-
within `[minSigma,maxSigma]`.
|
|
1675
|
-
|
|
1676
|
-
#### Multi-Objective Notes & Strategy {#multi-objective-notes--strategy}
|
|
1677
|
-
|
|
1678
|
-
Implements a simplified NSGA-II style pass: fast non-dominated sort (O(N^2) current implementation) + crowding distance; final ordering uses (rank asc, crowding desc, fitness desc) before truncation. Practical guidance:
|
|
1679
|
-
|
|
1680
|
-
- Start single-objective until baseline performance plateaus, then enable `multiObjective.enabled` with `complexityMetric:'nodes'`.
|
|
1681
|
-
- If early search stagnates due to premature parsimony, delay complexity with `multiObjective.dynamic.addComplexityAt`.
|
|
1682
|
-
- Use `autoEntropy` to seed a third diversity proxy objective only when structural collapse is observed (few species, low ancestor uniqueness).
|
|
1683
|
-
- Monitor front size; if it grows too large relative to population, enable `adaptiveEpsilon` to tighten dominance criteria.
|
|
1684
|
-
|
|
1685
|
-
Planned (future): faster dominance (divide-and-conquer), richer motif diversity pressure, automated compatibility coefficient tuning.
|
|
1686
|
-
|
|
1687
|
-
## ASCII Maze Example: 6‑Input Long-Range Vision (MazeVision)
|
|
1688
|
-
|
|
1689
|
-
The ASCII maze example uses a compact 6‑input perception schema ("MazeVision") with long‑range lookahead via a precomputed distance map. Inputs (order fixed):
|
|
1690
|
-
|
|
1691
|
-
1. compassScalar: Encodes the direction of the globally best next step toward the exit as a discrete scalar in {0,0.25,0.5,0.75} corresponding to N,E,S,W. Uses an extended horizon (H_COMPASS=5000) so it can see deeper than openness ratios.
|
|
1692
|
-
2. openN
|
|
1693
|
-
3. openE
|
|
1694
|
-
4. openS
|
|
1695
|
-
5. openW
|
|
1696
|
-
6. progressDelta: Normalized recent progress signal around 0.5 ( >0.5 improving, <0.5 regressing ).
|
|
1697
|
-
|
|
1698
|
-
### Openness Semantics (openN/E/S/W) {#openness-semantics}
|
|
1699
|
-
|
|
1700
|
-
Each openness value describes the quality of the shortest path to the exit if the agent moves first in that direction, using a bounded lookahead horizon H=1000 over the distance map.
|
|
1701
|
-
|
|
1702
|
-
Value encoding:
|
|
1703
|
-
|
|
1704
|
-
- 1: Direction(s) whose total path length Ldir is minimal among all strictly improving neighbors (ties allowed; multiple 1s possible).
|
|
1705
|
-
- Ratio 0 < Lmin / Ldir < 1: Direction is a valid strictly improving path but longer than the best (Lmin is the shortest improving path cost; Ldir = 1 + distance of neighbor cell). This supplies graded preference rather than binary pruning.
|
|
1706
|
-
- 0: Wall, unreachable cell, dead end, or any non‑improving move (neighbor distance >= current distance) – all treated uniformly.
|
|
1707
|
-
- 0.001: Special back‑only escape marker. When all four openness values would otherwise be 0 but the opposite of the previous successful action is traversable, that single opposite direction is set to 0.001 to indicate a pure retreat (pattern e.g. [0,0,0,0.001]).
|
|
1708
|
-
|
|
1709
|
-
Rules / Notes:
|
|
1710
|
-
|
|
1711
|
-
- Strict improvement filter: Only neighbors whose distanceMap value is strictly less than the current cell distance are considered for 1 or ratio values.
|
|
1712
|
-
- Horizon clipping: Paths with Ldir > H are treated as unreachable (value 0) to bound search cost.
|
|
1713
|
-
- Multiple bests: Corridors that fork into equivalently short routes produce multiple 1s, encouraging neutrality across equally optimal choices.
|
|
1714
|
-
- Backtrack marker is intentionally very small (0.001) so evolution distinguishes "retreat only" states from true walls without overweighting them.
|
|
1715
|
-
- Supervised refinement dataset intentionally contains ONLY deterministic single‑path cases (exactly one openness=1, others 0) for clarity; richer ratio/backtrack patterns appear only in the Lamarckian / evolutionary phase.
|
|
1716
|
-
|
|
1717
|
-
### progressDelta
|
|
1718
|
-
|
|
1719
|
-
Computed from recent distance improvement: delta = prevDistance - currentDistance, clipped to [-2,2], then mapped to [0,1] as 0.5 + delta/4. Values >0.5 mean progress toward exit; <0.5 regression or stalling.
|
|
1720
|
-
|
|
1721
|
-
### Debugging
|
|
1722
|
-
|
|
1723
|
-
Set ASCII_VISION_DEBUG=1 to emit periodic vision lines: current position, compassScalar, input vector, and per‑direction distance/ratio breakdown for auditing mismatches between maze geometry and distance map.
|
|
1724
|
-
|
|
1725
|
-
### Quick Reference
|
|
1726
|
-
|
|
1727
|
-
| Signal | Meaning |
|
|
1728
|
-
| ------ | --------------------------------------------------- |
|
|
1729
|
-
| 1 | Best strictly improving path(s) (minimal Ldir) |
|
|
1730
|
-
| (0,1) | Longer but improving path (ratio Lmin/Ldir) |
|
|
1731
|
-
| 0.001 | Only backtrack available (opposite of prior action) |
|
|
1732
|
-
| 0 | Wall / dead end / non‑improving / unreachable |
|
|
1733
|
-
|
|
1734
|
-
Implementation: `test/examples/asciiMaze/mazeVision.ts` (function `MazeVision.buildInputs6`).
|
|
1735
|
-
|
|
1736
|
-
This design minimizes input size (6 vs earlier large encodings) while preserving directional discrimination and long‑range planning cues, aiding faster evolutionary convergence and avoiding overfitting to local dead‑end noise.
|
|
1737
|
-
|
|
1738
|
-
## Roadmap / Backlog
|
|
1739
|
-
|
|
1740
|
-
Planned or partially designed enhancements not yet merged:
|
|
1741
|
-
|
|
1742
|
-
- Structural motif diversity pressure: penalize over-represented connection patterns (entropy-based sharing) to sustain innovation.
|
|
1743
|
-
- Automated compatibility coefficient tuning: search or adapt excess/disjoint/weight coefficients to stabilize species counts without manual calibration.
|
|
1744
|
-
- Faster Pareto sorting: divide-and-conquer or incremental dominance maintenance to reduce O(N^2) overhead for large populations.
|
|
1745
|
-
- Connection complexity budget (current budget targets nodes only) and dual-objective weighting option.
|
|
1746
|
-
- Diversity-aware parent selection leveraging motif entropy and archive dispersion.
|
|
1747
|
-
- Extended novelty descriptors helper utilities (e.g. built-in graph metrics: depth, feedforwardness, clustering).
|
|
1748
|
-
- Visualization hooks (species lineage graph, archive embedding projection) for diagnostics.
|
|
1749
|
-
```
|