--- license: mit language: - en tags: - neural-network - self-training - anomaly-detection - patent - comfyui --- # STANNO — Self-Training Artificial Neural Network Object A neural network that trains another neural network. No backpropagation. Directly modifies weights. ## What is this? STANNO is a modern, open-source implementation of the **Artificial Neurogenesis Network** concept from US Patent 5,852,815 (Thaler, 1998). Instead of using backpropagation to update weights, one network (the **TrainerNet**) computes weight updates for another network (the **TraineeNet**) by analyzing its internal state. Think of it as: you have a student network that learns, and a teacher network that decides how the student's weights should change — without autodiff, without gradients. Multiple STANNOs can be **cascaded** into pipelines where the output of one feeds the next, trained end-to-end with gradient flow across stage boundaries. A **DSANNO** (Data Scanning variant) wraps any trained STANNO and scans large datasets to find rows that match its learned representation — the inverse of anomaly detection. ### Patent & Attribution This codebase is an implementation of the architecture described in **US Patent 5,852,815** (*Artificial Neurogenesis Network*) filed by Stephen L. Thaler. The original patent has expired (US utility patents run 20 years from filing date). This open-source implementation builds upon the original design with modern extensions: **CascadeSTANNO** (multi-stage gradient flow), **DSANNO** (data scanning), and integration with contemporary frameworks (PyTorch, ComfyUI). We acknowledge and attribute all core concepts to Thaler's patent. See [Patents & Reference](#papers--reference) below for full citation details. ## ⚠️ Before you start **STANNO is specialized**, not a general-purpose neural network. It's designed for: - Anomaly detection ✓ - Online learning ✓ - Interpretability ✓ It's **not** for: - Regression (use PyTorch/TensorFlow instead) - Image generation alone (use with ComfyUI + SD 1.5) - High-accuracy function fitting See [STANNO_IS_NOT.md](STANNO_IS_NOT.md) for details. ## Why would I use this? - **Direct weight modification**: The trainer has explicit control over what happens to each synapse. Useful for interpretability, debugging, or unconventional training schemes. - **Meta-learning friendly**: The trainer itself can be learned (via evolution or other methods). Different tasks can teach the trainer how to train. - **Composable**: Three trainer implementations (Fixed, LocalRule, Evolutionary) let you pick the right tool. - **Cascadable**: Chain multiple STANNOs into encoder-decoder pipelines or progressive compression networks. Freeze individual stages, adapt others — all in the same object. - **Data scanning**: DSANNO turns any trained STANNO into a semantic scanner. Find the rows in a large dataset that most closely match the network's learned distribution, with auto-calibrated thresholds and top-k retrieval. - **ComfyUI integration**: Nine custom nodes for image generation workflows. - **Works with LLMs**: Filter or augment LLM inputs/outputs using STANNO's anomaly detection. ## Install ```bash pip install git+https://github.com/nitroxido/stanno.git ``` Or clone and install locally: ```bash git clone https://github.com/nitroxido/stanno.git cd stanno pip install -e . ``` For ComfyUI, the nodes auto-install via ComfyUI-Manager, or manually: ```bash cd ComfyUI/custom_nodes git clone https://github.com/nitroxido/comfyui-stanno.git cd comfyui-stanno pip install -r requirements.txt ``` ## Quick Start ### Train on sin(x) ```python import numpy as np from stanno import STANNO from stanno.config.schema import STANNOConfig # Config config = STANNOConfig( layers=[1, 32, 1], trainer_type="fixed", learning_rate=0.005, ) # Data x = np.linspace(0, 1, 512, dtype=np.float32).reshape(-1, 1) y = np.sin(2 * np.pi * x).astype(np.float32) # Train stanno = STANNO(config) stanno.fit(x, y, epochs=500, batch_size=64) # Predict y_pred = stanno.predict(np.array([[0.25]], dtype=np.float32)) print(f"sin(0.25) ≈ {y_pred[0, 0]:.3f}") # ≈ 0.587 ``` ### Load from config file ```bash python -m stanno train --config examples/sin_regression.json python -m stanno predict --config examples/sin_regression.json --input 0.25 python -m stanno dream --config examples/sin_regression.json ``` ### Anomaly filtering (pre-filter for LLM) ```python from stanno import STANNO from stanno.config.schema import FilterConfig from stanno.integration.filter import STANNOFilter from stanno.integration.llm_client import LLMClient # Train STANNO on normal embeddings stanno = STANNO(...) stanno.fit(normal_embeddings, normal_embeddings, epochs=100) # Set up filter filter_config = FilterConfig(anomaly_threshold=0.7, block_above_threshold=True) llm = LLMClient(llm_config) filt = STANNOFilter(stanno, filter_config, llm) # Score incoming prompt score, meta = filt.score(embedding) if not meta["blocked"]: response = filt.filter_and_send(messages, embedding) ``` ### Online learning ```python from stanno.integration.continual import ContinualSTANNO cont = ContinualSTANNO(stanno) # One sample at a time for x_i, y_i in stream: loss = cont.observe(x_i, y_i) print(f"Step {cont.steps}: loss={loss:.4f}") # Check held-out test set test_loss = cont.test_loss(x_test, y_test) print(f"Test loss: {test_loss:.4f}") ``` ### Cascading (encoder → decoder pipeline) ```python from stanno import STANNO, STANNOConfig, CascadeSTANNO # Two-stage autoencoder: compress 768-D embeddings to 64-D enc = STANNO(STANNOConfig(layers=[768, 256, 64], learning_rate=0.05)) dec = STANNO(STANNOConfig(layers=[64, 256, 768], learning_rate=0.05)) ae = CascadeSTANNO([enc, dec]) ae.fit(embeddings, embeddings, epochs=200, batch_size=32) # Get compressed representation codes = ae.intermediate_output(embeddings, stage=0) # (N, 64) # Freeze encoder, continue training decoder ae.freeze(0) ae.fit(embeddings, embeddings, epochs=100) # only decoder updates ``` CLI equivalent: ```bash python -m stanno cascade --config examples/cascade_autoencoder.json ``` ### Data scanning (DSANNO) ```python from stanno import STANNO, STANNOConfig, DSANNO # Train on known-good data detector = STANNO(STANNOConfig(layers=[64, 128, 64], learning_rate=0.05)) detector.fit(normal_data, normal_data, epochs=200) scanner = DSANNO(detector, mode="reconstruction") # Auto-calibrate threshold from training distribution threshold = scanner.calibrate_threshold(normal_data, percentile=95) # Scan a large dataset — returns matching rows result = scanner.scan(large_dataset, threshold=threshold) matching_rows = large_dataset[result.matched_indices()] # Or just get the top-k best matches indices, scores, _ = scanner.top_k(large_dataset, k=20) ``` CLI equivalent: ```bash python -m stanno scan --model model.stanno.pkl --data corpus.npy --top-k 20 python -m stanno scan --model model.stanno.pkl --data corpus.npy --threshold 0.05 ``` ## The three trainers | Trainer | How it works | Use case | |---------|-------------|----------| | **Fixed** | 4-module patent-faithful design. No learning, deterministic. | Baseline, reproducibility | | **LocalRule** | Shared MLP learns per-synapse update rules. Can meta-train. | Adaptive training, interpretability | | **Evolutionary** | ES-based. Evolves per-layer learning rates. No autodiff. | Exploration, unconventional problems | ## ComfyUI nodes Nine nodes in the **STANNO** category: | Node | What it does | |------|--------------| | **STANNOLoad** | Create or load a STANNO model | | **STANNOTrainImages** | Train as autoencoder on image batch | | **STANNOScoreImages** | Filter images by reconstruction error | | **STANNODreamCond** | Modify CLIP conditioning with dream mode | | **STANNODynamicLoRA** | Apply dream output as LoRA patches | | **STANNOCompositeCheck** | Route images to whichever of two STANNOs matches best | | **STANNOScan** | DSANNO scanner: auto-calibrated threshold + top-k image retrieval | | **STANNOCascadeLoad** | Create or load a multi-stage CascadeSTANNO | | **STANNOCascadeTrainImages** | Train a cascade end-to-end on an image batch | See [comfyui-stanno-integration.md](./comfyui-stanno-integration.md) for workflows and examples. ## Architecture ``` stanno/ ├── config/ # Dataclasses for all configuration ├── core/ │ ├── backend.py # NumPy & PyTorch backend abstraction │ ├── trainer.py # AbstractTrainerNet base class + cascade API │ ├── trainee.py # TraineeNet (the student network) │ └── stanno.py # STANNO orchestrator ├── trainers/ │ ├── fixed.py # 4-module patent design (cascade-aware) │ ├── local_rule.py # Learned per-synapse rules │ └── evolutionary.py # ES-based adaptation ├── data/ # Loaders for CSV, JSON, NumPy, builtin datasets ├── integration/ │ ├── llm_client.py # OpenAI-compatible HTTP client (Ollama, etc.) │ ├── filter.py # STANNOFilter for anomaly detection │ ├── continual.py # ContinualSTANNO for online learning │ ├── cascade.py # CascadeSTANNO — multi-stage chained networks │ └── dsanno.py # DSANNO — data scanning and semantic retrieval └── cli.py # Command-line interface ``` ### CLI subcommands | Command | What it does | |---------|--------------| | `stanno train` | Train a single STANNO from a JSON config | | `stanno predict` | Run one prediction | | `stanno dream` | Generate a sequence via dream mode | | `stanno evaluate` | Compute MSE/MAE on a dataset | | `stanno filter` | Run anomaly filter on a file of prompts | | `stanno cascade` | Train a CascadeSTANNO from a JSON config | | `stanno scan` | Scan a `.npy` dataset with a trained STANNO | ## Configuration All settings in JSON. Example: ```json { "stanno": { "layers": [1, 32, 1], "trainer_type": "fixed", "learning_rate": 0.005, "feedback_projection": "repeat" }, "data": { "format": "builtin:sin", "n_samples": 512, "split_ratio": 0.8 }, "fit": { "epochs": 500, "batch_size": 64, "log_every": 50 } } ``` See [examples/](./examples/) for more. ## Testing ```bash python -c " import numpy as np from stanno import STANNO from stanno.config.schema import STANNOConfig for trainer_type in ['fixed', 'local_rule', 'evolutionary']: cfg = STANNOConfig(layers=[1, 32, 1], trainer_type=trainer_type) stanno = STANNO(cfg) x = np.linspace(0, 1, 100, dtype='f').reshape(-1, 1) y = np.sin(2*np.pi*x).astype('f') stanno.fit(x, y, epochs=50, batch_size=16) pred = stanno.predict(x[:5]) mse = np.mean((pred - y[:5])**2) print(f'{trainer_type:15s} MSE={mse:.5f}') " ``` Expected output: ``` fixed MSE=0.24653 local_rule MSE=0.01234 evolutionary MSE=0.35421 ``` ## Papers & Reference - **Original Patent**: Thaler, S. L. (1998). *Artificial neurogenesis network*. US Patent 5,852,815. - **Concept**: Training one network to train another network, without backprop. - **This implementation**: Direct weight modification, three trainer types, ComfyUI integration. ## License MIT ## Contributing Bug reports, feature requests, and pull requests welcome. Start with an issue describing what you want to do. ## Contact nitroxido https://github.com/nitroxido https://x.com/CompotaMission ## ComfyUI Integration See [comfyui-stanno](https://huggingface.co/oldman-dev/comfyui-stanno) for ComfyUI custom nodes.