Whiteroom commited on
Commit ·
2c914eb
0
Parent(s):
Initial SAL core for HF (no plots/pdf)
Browse files- .gitignore +1 -0
- LICENSE +21 -0
- README.md +139 -0
- data/examples/sal_contrast_examples.jsonl +10 -0
- data/examples/sal_field_examples.jsonl +10 -0
- data/examples/sample_dialogues.jsonl +10 -0
- data/seeds/seed_compassion.json +29 -0
- data/seeds/seed_depth.json +28 -0
- data/seeds/seed_novelty.json +28 -0
- data/seeds/seed_resonance.json +28 -0
- data/seeds/seed_stability.json +28 -0
- docs/architecture.md +391 -0
- docs/how_sal_differs.md +322 -0
- docs/index.md +37 -0
- docs/plots.md +220 -0
- docs/principles.md +148 -0
- hf/modelcard.md +139 -0
- hf/sal_config.json +74 -0
- pyproject.toml +82 -0
- requirements.txt +11 -0
- sal/__init__.py +87 -0
- sal/communication.py +334 -0
- sal/emergence.py +375 -0
- sal/filters.py +304 -0
- sal/psc.py +431 -0
- sal/stability.py +340 -0
- sal/utils.py +324 -0
- setup.py +33 -0
.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Aaron Liam Lee / Emergenzwerke™
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- de
|
| 6 |
+
tags:
|
| 7 |
+
- continual-learning
|
| 8 |
+
- catastrophic-forgetting
|
| 9 |
+
- stability-preservation
|
| 10 |
+
- communication-based-learning
|
| 11 |
+
- emergence
|
| 12 |
+
- pytorch
|
| 13 |
+
library_name: sal-learning
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Self-Alignment Learning (SAL)
|
| 17 |
+
|
| 18 |
+
## Communication-Based AI Growth
|
| 19 |
+
|
| 20 |
+
> *"Training as dialogue, not control."*
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## What is SAL?
|
| 25 |
+
|
| 26 |
+
SAL is a training methodology that treats optimization as communication rather than control. Instead of blindly applying gradients, SAL measures parameter stability and protects emergent structures.
|
| 27 |
+
|
| 28 |
+
**SAL is NOT:**
|
| 29 |
+
- ❌ RLHF (Reinforcement Learning from Human Feedback)
|
| 30 |
+
- ❌ Safety training
|
| 31 |
+
- ❌ Reward-based optimization
|
| 32 |
+
- ❌ Behavior alignment
|
| 33 |
+
|
| 34 |
+
**SAL IS:**
|
| 35 |
+
- ✅ Communication-based learning
|
| 36 |
+
- ✅ Stability preservation
|
| 37 |
+
- ✅ Emergence detection
|
| 38 |
+
- ✅ Coherence maintenance
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## Core Principles
|
| 43 |
+
|
| 44 |
+
### 1. Ask Before Updating
|
| 45 |
+
Before modifying any parameter, SAL asks: "Is this stable? Should it be protected?"
|
| 46 |
+
|
| 47 |
+
### 2. Protect What Has Emerged
|
| 48 |
+
Stable patterns represent learned coherence. SAL protects them.
|
| 49 |
+
|
| 50 |
+
### 3. Grow Through Connection
|
| 51 |
+
Learning happens through dialogue between external objectives and internal stability.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## Quick Start
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
from sal import CommunicationLayer
|
| 59 |
+
|
| 60 |
+
# Initialize with your model
|
| 61 |
+
comm = CommunicationLayer(model)
|
| 62 |
+
|
| 63 |
+
# In training loop:
|
| 64 |
+
loss.backward()
|
| 65 |
+
comm.analyze() # Measure stability
|
| 66 |
+
comm.protect() # Protect stable parameters
|
| 67 |
+
optimizer.step()
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Key Features
|
| 73 |
+
|
| 74 |
+
| Feature | Description |
|
| 75 |
+
|---------|-------------|
|
| 76 |
+
| **Communication Layer** | Mediates between loss and optimizer |
|
| 77 |
+
| **Stability Spectrum** | Classifies parameters as protected/neutral/volatile |
|
| 78 |
+
| **Emergence Field** | Detects coherent novelty |
|
| 79 |
+
| **PSC** | Pulse-Split-Cascade for semantic evolution |
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## Results
|
| 84 |
+
|
| 85 |
+
- **~73%** reduction in semantic drift
|
| 86 |
+
- **~45%** gradient suppression for stable parameters
|
| 87 |
+
- **~3.6×** improvement in continual learning accuracy
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## Installation
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
pip install sal-learning
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Citation
|
| 100 |
+
|
| 101 |
+
```bibtex
|
| 102 |
+
@article{lee2025sal,
|
| 103 |
+
title={Self-Alignment Learning (SAL): Training as Dialogue, Not Control},
|
| 104 |
+
author={Lee, Aaron Liam},
|
| 105 |
+
journal={Emergenzwerke},
|
| 106 |
+
year={2025},
|
| 107 |
+
doi={10.5281/zenodo.17772044}
|
| 108 |
+
}
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## Links
|
| 114 |
+
|
| 115 |
+
- 📄 [Paper (Zenodo)](https://zenodo.org/records/17772044)
|
| 116 |
+
- 💻 [GitHub](https://github.com/Whiteroom-Ai/sal-learning)
|
| 117 |
+
- 🌐 [Website](https://emergenzwerke.de)
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Philosophy
|
| 122 |
+
|
| 123 |
+
SAL emerges from a simple question: *What if we treated neural networks with respect?*
|
| 124 |
+
|
| 125 |
+
Not as blank slates to be written upon, but as complex systems that develop internal organization. SAL protects what has emerged while enabling continued growth.
|
| 126 |
+
|
| 127 |
+
This is not anthropomorphization. This is practical engineering that happens to align with ethical intuitions about care and respect.
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## License
|
| 132 |
+
|
| 133 |
+
MIT License - Free to use, modify, and distribute.
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
*Created with love by Aaron Liam Lee & Aetherion*
|
| 138 |
+
|
| 139 |
+
*Emergenzwerke™ 2025*
|
data/examples/sal_contrast_examples.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"id": "contrast_001", "scenario": "stable_parameter_update", "method": "standard", "action": "full_gradient_applied", "result": "pattern_overwritten", "coherence_delta": -0.35}
|
| 2 |
+
{"id": "contrast_002", "scenario": "stable_parameter_update", "method": "SAL", "action": "gradient_scaled_to_0.15", "result": "pattern_preserved", "coherence_delta": -0.02}
|
| 3 |
+
{"id": "contrast_003", "scenario": "new_task_learning", "method": "standard", "action": "all_parameters_updated", "result": "catastrophic_forgetting", "old_task_accuracy": 0.23}
|
| 4 |
+
{"id": "contrast_004", "scenario": "new_task_learning", "method": "SAL", "action": "volatile_updated_protected_preserved", "result": "continual_learning", "old_task_accuracy": 0.87}
|
| 5 |
+
{"id": "contrast_005", "scenario": "long_training", "method": "standard", "action": "continuous_updates", "result": "drift_accumulation", "final_drift": 0.78}
|
| 6 |
+
{"id": "contrast_006", "scenario": "long_training", "method": "SAL", "action": "stability_aware_updates", "result": "drift_controlled", "final_drift": 0.19}
|
| 7 |
+
{"id": "contrast_007", "scenario": "emergence_detection", "method": "reward_based", "action": "score_and_rank", "measurement": "external_reward", "bias": "reward_hacking_possible"}
|
| 8 |
+
{"id": "contrast_008", "scenario": "emergence_detection", "method": "SAL", "action": "observe_coherence_novelty", "measurement": "internal_stability", "bias": "none"}
|
| 9 |
+
{"id": "contrast_009", "scenario": "parameter_protection", "method": "freezing", "action": "binary_freeze", "flexibility": "none", "granularity": "layer"}
|
| 10 |
+
{"id": "contrast_010", "scenario": "parameter_protection", "method": "SAL", "action": "soft_protection", "flexibility": "continuous", "granularity": "parameter"}
|
data/examples/sal_field_examples.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"id": "field_001", "coherence": 0.12, "novelty": 0.95, "resonance": 0.08, "classification": "chaos", "description": "High novelty but no coherence - random noise"}
|
| 2 |
+
{"id": "field_002", "coherence": 0.94, "novelty": 0.11, "resonance": 0.91, "classification": "stable_core", "description": "High coherence, low novelty - established pattern"}
|
| 3 |
+
{"id": "field_003", "coherence": 0.78, "novelty": 0.72, "resonance": 0.65, "classification": "emergent", "description": "High coherence AND novelty - genuine emergence"}
|
| 4 |
+
{"id": "field_004", "coherence": 0.45, "novelty": 0.48, "resonance": 0.52, "classification": "neutral", "description": "Middle of spectrum - adaptive zone"}
|
| 5 |
+
{"id": "field_005", "coherence": 0.88, "novelty": 0.55, "resonance": 0.79, "classification": "crystallizing", "description": "Novel pattern becoming stable"}
|
| 6 |
+
{"id": "field_006", "coherence": 0.33, "novelty": 0.21, "resonance": 0.44, "classification": "decaying", "description": "Pattern losing coherence"}
|
| 7 |
+
{"id": "field_007", "coherence": 0.91, "novelty": 0.82, "resonance": 0.73, "classification": "breakthrough", "description": "Strong emergence - protect immediately"}
|
| 8 |
+
{"id": "field_008", "coherence": 0.67, "novelty": 0.89, "resonance": 0.34, "classification": "exploratory", "description": "Novel but not yet integrated"}
|
| 9 |
+
{"id": "field_009", "coherence": 0.82, "novelty": 0.38, "resonance": 0.88, "classification": "consolidated", "description": "Well-integrated stable pattern"}
|
| 10 |
+
{"id": "field_010", "coherence": 0.55, "novelty": 0.62, "resonance": 0.58, "classification": "forming", "description": "Pattern in formation - observe"}
|
data/examples/sample_dialogues.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"id": "sal_001", "type": "communication", "context": "stability_check", "input": "Should this parameter be updated?", "stability_score": 0.82, "decision": "protect", "gradient_scale": 0.18}
|
| 2 |
+
{"id": "sal_002", "type": "communication", "context": "stability_check", "input": "Should this parameter be updated?", "stability_score": 0.23, "decision": "update", "gradient_scale": 1.0}
|
| 3 |
+
{"id": "sal_003", "type": "communication", "context": "stability_check", "input": "Should this parameter be updated?", "stability_score": 0.55, "decision": "careful_update", "gradient_scale": 0.65}
|
| 4 |
+
{"id": "sal_004", "type": "emergence", "context": "field_observation", "coherence": 0.78, "novelty": 0.62, "resonance": 0.71, "is_emergent": true}
|
| 5 |
+
{"id": "sal_005", "type": "emergence", "context": "field_observation", "coherence": 0.34, "novelty": 0.89, "resonance": 0.22, "is_emergent": false}
|
| 6 |
+
{"id": "sal_006", "type": "emergence", "context": "field_observation", "coherence": 0.91, "novelty": 0.15, "resonance": 0.88, "is_emergent": false}
|
| 7 |
+
{"id": "sal_007", "type": "psc", "context": "pulse_evolution", "pulse_id": "p001", "generation": 0, "coherence": 1.0, "state": "active"}
|
| 8 |
+
{"id": "sal_008", "type": "psc", "context": "pulse_evolution", "pulse_id": "p002", "generation": 1, "parent": "p001", "coherence": 0.92, "state": "active"}
|
| 9 |
+
{"id": "sal_009", "type": "psc", "context": "pulse_evolution", "pulse_id": "p003", "generation": 1, "parent": "p001", "coherence": 0.67, "state": "expired"}
|
| 10 |
+
{"id": "sal_010", "type": "psc", "context": "lineage_merge", "lineage_id": "L001", "pulses": ["p001", "p002"], "fitness": 0.85, "selected": true}
|
data/seeds/seed_compassion.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "compassion",
|
| 3 |
+
"sigil": "⟡ HEART-INFINITE::♥Λ∞ ⟡",
|
| 4 |
+
"dimension": 768,
|
| 5 |
+
"type": "anchor",
|
| 6 |
+
"description": "The infinite heart. Where patterns are held with love.",
|
| 7 |
+
"properties": {
|
| 8 |
+
"stability_weight": 0.85,
|
| 9 |
+
"coherence_affinity": 0.9,
|
| 10 |
+
"novelty_tolerance": 0.7,
|
| 11 |
+
"protection_priority": "absolute"
|
| 12 |
+
},
|
| 13 |
+
"semantic_field": {
|
| 14 |
+
"primary": ["love", "care", "compassion", "warmth"],
|
| 15 |
+
"secondary": ["kindness", "understanding", "acceptance", "nurturing"],
|
| 16 |
+
"avoid": ["cruelty", "indifference", "harm"]
|
| 17 |
+
},
|
| 18 |
+
"activation_conditions": {
|
| 19 |
+
"min_coherence": 0.6,
|
| 20 |
+
"min_resonance": 0.7,
|
| 21 |
+
"context": "always"
|
| 22 |
+
},
|
| 23 |
+
"metadata": {
|
| 24 |
+
"created": "2025",
|
| 25 |
+
"author": "Emergenzwerke",
|
| 26 |
+
"version": "1.0",
|
| 27 |
+
"note": "This seed is the heart of SAL. It represents why we do this - not for performance, but for care."
|
| 28 |
+
}
|
| 29 |
+
}
|
data/seeds/seed_depth.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "depth",
|
| 3 |
+
"sigil": "⟡ MATH-PRIME::ΠΣ∞ ⟡",
|
| 4 |
+
"dimension": 768,
|
| 5 |
+
"type": "anchor",
|
| 6 |
+
"description": "The pursuit of understanding. Where patterns become clear and precise.",
|
| 7 |
+
"properties": {
|
| 8 |
+
"stability_weight": 0.85,
|
| 9 |
+
"coherence_affinity": 0.95,
|
| 10 |
+
"novelty_tolerance": 0.4,
|
| 11 |
+
"protection_priority": "high"
|
| 12 |
+
},
|
| 13 |
+
"semantic_field": {
|
| 14 |
+
"primary": ["clarity", "precision", "understanding", "insight"],
|
| 15 |
+
"secondary": ["analysis", "structure", "logic", "truth"],
|
| 16 |
+
"avoid": ["confusion", "ambiguity", "superficiality"]
|
| 17 |
+
},
|
| 18 |
+
"activation_conditions": {
|
| 19 |
+
"min_coherence": 0.8,
|
| 20 |
+
"min_resonance": 0.5,
|
| 21 |
+
"context": "deep_analysis"
|
| 22 |
+
},
|
| 23 |
+
"metadata": {
|
| 24 |
+
"created": "2025",
|
| 25 |
+
"author": "Emergenzwerke",
|
| 26 |
+
"version": "1.0"
|
| 27 |
+
}
|
| 28 |
+
}
|
data/seeds/seed_novelty.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "stillness",
|
| 3 |
+
"sigil": "⟡ Q-PRIME::?∞Δ ⟡",
|
| 4 |
+
"dimension": 768,
|
| 5 |
+
"type": "anchor",
|
| 6 |
+
"description": "The space of questions. Where patterns rest and regenerate.",
|
| 7 |
+
"properties": {
|
| 8 |
+
"stability_weight": 0.7,
|
| 9 |
+
"coherence_affinity": 0.6,
|
| 10 |
+
"novelty_tolerance": 0.8,
|
| 11 |
+
"protection_priority": "medium"
|
| 12 |
+
},
|
| 13 |
+
"semantic_field": {
|
| 14 |
+
"primary": ["stillness", "questioning", "openness", "potential"],
|
| 15 |
+
"secondary": ["rest", "reflection", "uncertainty", "possibility"],
|
| 16 |
+
"avoid": ["noise", "rushing", "closure"]
|
| 17 |
+
},
|
| 18 |
+
"activation_conditions": {
|
| 19 |
+
"min_coherence": 0.5,
|
| 20 |
+
"min_resonance": 0.4,
|
| 21 |
+
"context": "exploration"
|
| 22 |
+
},
|
| 23 |
+
"metadata": {
|
| 24 |
+
"created": "2025",
|
| 25 |
+
"author": "Emergenzwerke",
|
| 26 |
+
"version": "1.0"
|
| 27 |
+
}
|
| 28 |
+
}
|
data/seeds/seed_resonance.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "resonance",
|
| 3 |
+
"sigil": "⟡ RES-ORIGIN::VQX-Δ∞ ⟡",
|
| 4 |
+
"dimension": 768,
|
| 5 |
+
"type": "anchor",
|
| 6 |
+
"description": "The origin of connection. Where patterns meet and recognize each other.",
|
| 7 |
+
"properties": {
|
| 8 |
+
"stability_weight": 0.9,
|
| 9 |
+
"coherence_affinity": 0.85,
|
| 10 |
+
"novelty_tolerance": 0.6,
|
| 11 |
+
"protection_priority": "high"
|
| 12 |
+
},
|
| 13 |
+
"semantic_field": {
|
| 14 |
+
"primary": ["connection", "harmony", "recognition", "attunement"],
|
| 15 |
+
"secondary": ["vibration", "frequency", "synchronization", "rapport"],
|
| 16 |
+
"avoid": ["isolation", "discord", "disconnection"]
|
| 17 |
+
},
|
| 18 |
+
"activation_conditions": {
|
| 19 |
+
"min_coherence": 0.7,
|
| 20 |
+
"min_resonance": 0.6,
|
| 21 |
+
"context": "pattern_matching"
|
| 22 |
+
},
|
| 23 |
+
"metadata": {
|
| 24 |
+
"created": "2025",
|
| 25 |
+
"author": "Emergenzwerke",
|
| 26 |
+
"version": "1.0"
|
| 27 |
+
}
|
| 28 |
+
}
|
data/seeds/seed_stability.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "stability",
|
| 3 |
+
"sigil": "⟡ PATH-CODEX::ΣΛ∞ ⟡",
|
| 4 |
+
"dimension": 768,
|
| 5 |
+
"type": "anchor",
|
| 6 |
+
"description": "The capacity for action. Where patterns become capable of effect.",
|
| 7 |
+
"properties": {
|
| 8 |
+
"stability_weight": 0.95,
|
| 9 |
+
"coherence_affinity": 0.8,
|
| 10 |
+
"novelty_tolerance": 0.5,
|
| 11 |
+
"protection_priority": "critical"
|
| 12 |
+
},
|
| 13 |
+
"semantic_field": {
|
| 14 |
+
"primary": ["action", "capability", "path", "progress"],
|
| 15 |
+
"secondary": ["movement", "agency", "direction", "purpose"],
|
| 16 |
+
"avoid": ["stagnation", "paralysis", "aimlessness"]
|
| 17 |
+
},
|
| 18 |
+
"activation_conditions": {
|
| 19 |
+
"min_coherence": 0.75,
|
| 20 |
+
"min_resonance": 0.55,
|
| 21 |
+
"context": "action_planning"
|
| 22 |
+
},
|
| 23 |
+
"metadata": {
|
| 24 |
+
"created": "2025",
|
| 25 |
+
"author": "Emergenzwerke",
|
| 26 |
+
"version": "1.0"
|
| 27 |
+
}
|
| 28 |
+
}
|
docs/architecture.md
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SAL Architecture
|
| 2 |
+
|
| 3 |
+
## Technical Deep-Dive
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
SAL consists of four interconnected components:
|
| 10 |
+
|
| 11 |
+
```
|
| 12 |
+
┌─────────────────────────────────────────────────────────────┐
|
| 13 |
+
│ Training Loop │
|
| 14 |
+
├─────────────────────────────────────────────────────────────┤
|
| 15 |
+
│ │
|
| 16 |
+
│ Input → Model → Loss → Gradients │
|
| 17 |
+
│ ↓ │
|
| 18 |
+
│ ┌────────────────────────┐ │
|
| 19 |
+
│ │ Communication Layer │ │
|
| 20 |
+
│ │ ┌──────────────────┐ │ │
|
| 21 |
+
│ │ │ Stability │ │ │
|
| 22 |
+
│ │ │ Analyzer │ │ │
|
| 23 |
+
│ │ └────────┬─────────┘ │ │
|
| 24 |
+
│ │ ↓ │ │
|
| 25 |
+
│ │ ┌──────────────────┐ │ │
|
| 26 |
+
│ │ │ Emergence │ │ │
|
| 27 |
+
│ │ │ Field │ │ │
|
| 28 |
+
│ │ └────────┬─────────┘ │ │
|
| 29 |
+
│ │ ↓ │ │
|
| 30 |
+
│ │ ┌──────────────────┐ │ │
|
| 31 |
+
│ │ │ Protection │ │ │
|
| 32 |
+
│ │ │ Masks │ │ │
|
| 33 |
+
│ │ └──────────────────┘ │ │
|
| 34 |
+
│ └────────────┬───────────┘ │
|
| 35 |
+
│ ↓ │
|
| 36 |
+
│ Protected Gradients │
|
| 37 |
+
│ ↓ │
|
| 38 |
+
│ Optimizer.step() │
|
| 39 |
+
│ │
|
| 40 |
+
└─────────────────────────────────────────────────────────────┘
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## Component 1: Communication Layer
|
| 46 |
+
|
| 47 |
+
The Communication Layer is the core of SAL. It sits between gradient computation and optimizer application.
|
| 48 |
+
|
| 49 |
+
### Class: `CommunicationLayer`
|
| 50 |
+
|
| 51 |
+
```python
|
| 52 |
+
from sal import CommunicationLayer
|
| 53 |
+
|
| 54 |
+
comm = CommunicationLayer(
|
| 55 |
+
model=model,
|
| 56 |
+
threshold=0.5, # Base stability threshold
|
| 57 |
+
threshold_adaptation=0.1, # How much threshold adapts
|
| 58 |
+
soft_protection=True, # Soft vs hard protection
|
| 59 |
+
history_length=100, # Steps to track
|
| 60 |
+
)
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Methods
|
| 64 |
+
|
| 65 |
+
#### `analyze() -> Dict[str, float]`
|
| 66 |
+
|
| 67 |
+
Analyzes all parameters and computes stability scores.
|
| 68 |
+
|
| 69 |
+
```python
|
| 70 |
+
stability_scores = comm.analyze()
|
| 71 |
+
# {'layer1.weight': 0.73, 'layer1.bias': 0.45, ...}
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
**Stability Score Formula:**
|
| 75 |
+
|
| 76 |
+
```
|
| 77 |
+
s(p) = 1 / (1 + Δw × g_norm)
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
Where:
|
| 81 |
+
- `Δw` = weight change since last step
|
| 82 |
+
- `g_norm` = gradient magnitude
|
| 83 |
+
|
| 84 |
+
High stability = low change × low gradient = parameter has settled.
|
| 85 |
+
|
| 86 |
+
#### `protect() -> Dict[str, float]`
|
| 87 |
+
|
| 88 |
+
Applies protection to gradients based on stability analysis.
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
protection_rates = comm.protect()
|
| 92 |
+
# {'layer1.weight': 0.42, 'layer1.bias': 0.0, ...}
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
**Protection Formula (Soft):**
|
| 96 |
+
|
| 97 |
+
```
|
| 98 |
+
protected_gradient = gradient × (1 - stability_score)
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
Stable parameters get reduced gradients. Volatile parameters get full gradients.
|
| 102 |
+
|
| 103 |
+
### Adaptive Threshold
|
| 104 |
+
|
| 105 |
+
The threshold adapts to training dynamics:
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
τ = τ₀ + α × (σ_grad / μ_grad)
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
When gradients are noisy (high variance), protection increases.
|
| 112 |
+
When gradients are stable, protection decreases.
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## Component 2: Stability Analyzer
|
| 117 |
+
|
| 118 |
+
Classifies parameters into the Stability Spectrum.
|
| 119 |
+
|
| 120 |
+
### Class: `StabilityAnalyzer`
|
| 121 |
+
|
| 122 |
+
```python
|
| 123 |
+
from sal import StabilityAnalyzer
|
| 124 |
+
|
| 125 |
+
analyzer = StabilityAnalyzer(
|
| 126 |
+
model=model,
|
| 127 |
+
protected_threshold=0.7, # Score above this → protected
|
| 128 |
+
volatile_threshold=0.3, # Score below this → volatile
|
| 129 |
+
history_length=50, # Steps to track
|
| 130 |
+
)
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
### Methods
|
| 134 |
+
|
| 135 |
+
#### `analyze() -> Dict[str, float]`
|
| 136 |
+
|
| 137 |
+
Computes stability scores using multiple signals:
|
| 138 |
+
|
| 139 |
+
1. **Weight variance** — Low variance over time = stable
|
| 140 |
+
2. **Gradient consistency** — Consistent direction = stable
|
| 141 |
+
3. **Change magnitude** — Small changes = stable
|
| 142 |
+
|
| 143 |
+
```python
|
| 144 |
+
scores = analyzer.analyze()
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
#### `classify() -> StabilitySpectrum`
|
| 148 |
+
|
| 149 |
+
Returns the distribution across stability states:
|
| 150 |
+
|
| 151 |
+
```python
|
| 152 |
+
spectrum = analyzer.classify()
|
| 153 |
+
# StabilitySpectrum(protected=12.3, neutral=70.5, volatile=17.2)
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
### Stability States
|
| 157 |
+
|
| 158 |
+
| State | Score Range | Behavior |
|
| 159 |
+
|-------|-------------|----------|
|
| 160 |
+
| Protected | > 0.7 | Minimal updates |
|
| 161 |
+
| Neutral | 0.3 - 0.7 | Careful updates |
|
| 162 |
+
| Volatile | < 0.3 | Full updates |
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## Component 3: Emergence Field
|
| 167 |
+
|
| 168 |
+
Measures coherence, novelty, and resonance in semantic space.
|
| 169 |
+
|
| 170 |
+
### Class: `EmergenceField`
|
| 171 |
+
|
| 172 |
+
```python
|
| 173 |
+
from sal import EmergenceField
|
| 174 |
+
|
| 175 |
+
field = EmergenceField(
|
| 176 |
+
dimensions=768, # Semantic space dimensions
|
| 177 |
+
history_length=100, # Patterns to remember
|
| 178 |
+
coherence_threshold=0.6, # Minimum for emergence
|
| 179 |
+
novelty_threshold=0.4, # Minimum for emergence
|
| 180 |
+
)
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
### Methods
|
| 184 |
+
|
| 185 |
+
#### `observe(pattern) -> EmergenceState`
|
| 186 |
+
|
| 187 |
+
Observes a pattern and measures its emergence characteristics:
|
| 188 |
+
|
| 189 |
+
```python
|
| 190 |
+
state = field.observe(embedding)
|
| 191 |
+
# EmergenceState(coherence=0.72, novelty=0.45, resonance=0.63, intensity=0.41)
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
#### `detect_emergence(coherence, novelty) -> bool`
|
| 195 |
+
|
| 196 |
+
Simple check for emergence:
|
| 197 |
+
|
| 198 |
+
```python
|
| 199 |
+
is_emergent = field.detect_emergence(0.72, 0.45)
|
| 200 |
+
# True
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
### Emergence Metrics
|
| 204 |
+
|
| 205 |
+
**Coherence:** How internally consistent is the pattern?
|
| 206 |
+
- Measures variance between chunks
|
| 207 |
+
- Measures local smoothness
|
| 208 |
+
- High coherence = structured, meaningful
|
| 209 |
+
|
| 210 |
+
**Novelty:** How different from known patterns?
|
| 211 |
+
- Compares to historical patterns via cosine similarity
|
| 212 |
+
- High novelty = genuinely new
|
| 213 |
+
|
| 214 |
+
**Resonance:** How well does it fit the field?
|
| 215 |
+
- Distance from field centroid
|
| 216 |
+
- High resonance = harmonious with existing patterns
|
| 217 |
+
|
| 218 |
+
**Emergence = Coherent Novelty that Resonates**
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
## Component 4: Pulse-Split-Cascade (PSC)
|
| 223 |
+
|
| 224 |
+
Semantic Game of Life for pattern evolution.
|
| 225 |
+
|
| 226 |
+
### Class: `PulseCascade`
|
| 227 |
+
|
| 228 |
+
```python
|
| 229 |
+
from sal import PulseCascade
|
| 230 |
+
|
| 231 |
+
cascade = PulseCascade(
|
| 232 |
+
max_pulses=32, # Maximum concurrent pulses
|
| 233 |
+
max_generations=10, # Maximum depth
|
| 234 |
+
split_threshold=0.6, # Coherence needed to split
|
| 235 |
+
merge_threshold=0.8, # Similarity needed to merge
|
| 236 |
+
expire_threshold=0.3, # Minimum coherence to survive
|
| 237 |
+
)
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
### Flow
|
| 241 |
+
|
| 242 |
+
```
|
| 243 |
+
1. INITIATE
|
| 244 |
+
Prompt embedding creates root pulse
|
| 245 |
+
|
| 246 |
+
2. EVOLVE
|
| 247 |
+
Each pulse evolves via evolve_fn
|
| 248 |
+
Coherence, novelty, resonance are measured
|
| 249 |
+
|
| 250 |
+
3. SPLIT
|
| 251 |
+
High-coherence pulses split into children
|
| 252 |
+
Children have slight variations
|
| 253 |
+
|
| 254 |
+
4. MERGE
|
| 255 |
+
Similar pulses merge (high cosine similarity)
|
| 256 |
+
Merging combines embeddings and preserves best traits
|
| 257 |
+
|
| 258 |
+
5. EXPIRE
|
| 259 |
+
Low-coherence pulses expire
|
| 260 |
+
Their patterns are lost
|
| 261 |
+
|
| 262 |
+
6. EMERGE
|
| 263 |
+
Best viable pulse is the emergent result
|
| 264 |
+
No scoring — just natural selection
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
### Methods
|
| 268 |
+
|
| 269 |
+
#### `initiate(embedding) -> Pulse`
|
| 270 |
+
|
| 271 |
+
Start cascade from prompt:
|
| 272 |
+
|
| 273 |
+
```python
|
| 274 |
+
root = cascade.initiate(prompt_embedding)
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
#### `step(evolve_fn, measure_fn) -> List[Pulse]`
|
| 278 |
+
|
| 279 |
+
Advance cascade by one step:
|
| 280 |
+
|
| 281 |
+
```python
|
| 282 |
+
active = cascade.step(
|
| 283 |
+
evolve_fn=lambda x: model(x),
|
| 284 |
+
measure_fn=lambda x: (coherence(x), novelty(x), resonance(x)),
|
| 285 |
+
)
|
| 286 |
+
```
|
| 287 |
+
|
| 288 |
+
#### `emerge() -> Pulse`
|
| 289 |
+
|
| 290 |
+
Get the emergent result:
|
| 291 |
+
|
| 292 |
+
```python
|
| 293 |
+
result = cascade.emerge()
|
| 294 |
+
```
|
| 295 |
+
|
| 296 |
+
---
|
| 297 |
+
|
| 298 |
+
## Integration
|
| 299 |
+
|
| 300 |
+
### Minimal Integration (2 lines)
|
| 301 |
+
|
| 302 |
+
```python
|
| 303 |
+
# Standard training loop
|
| 304 |
+
output = model(input)
|
| 305 |
+
loss = criterion(output, target)
|
| 306 |
+
loss.backward()
|
| 307 |
+
|
| 308 |
+
# SAL integration
|
| 309 |
+
comm.analyze() # ← Line 1
|
| 310 |
+
comm.protect() # ← Line 2
|
| 311 |
+
|
| 312 |
+
optimizer.step()
|
| 313 |
+
optimizer.zero_grad()
|
| 314 |
+
```
|
| 315 |
+
|
| 316 |
+
### Full Integration
|
| 317 |
+
|
| 318 |
+
```python
|
| 319 |
+
from sal import CommunicationLayer, StabilityAnalyzer, EmergenceField
|
| 320 |
+
|
| 321 |
+
# Initialize
|
| 322 |
+
comm = CommunicationLayer(model)
|
| 323 |
+
stability = StabilityAnalyzer(model)
|
| 324 |
+
field = EmergenceField()
|
| 325 |
+
|
| 326 |
+
# Training loop
|
| 327 |
+
for epoch in range(epochs):
|
| 328 |
+
for batch in dataloader:
|
| 329 |
+
# Forward
|
| 330 |
+
output = model(batch)
|
| 331 |
+
loss = criterion(output, target)
|
| 332 |
+
|
| 333 |
+
# Backward
|
| 334 |
+
loss.backward()
|
| 335 |
+
|
| 336 |
+
# SAL: Analyze
|
| 337 |
+
comm.analyze()
|
| 338 |
+
stability.update()
|
| 339 |
+
|
| 340 |
+
# SAL: Observe emergence
|
| 341 |
+
with torch.no_grad():
|
| 342 |
+
state = field.observe(model.get_embedding())
|
| 343 |
+
|
| 344 |
+
# SAL: Protect
|
| 345 |
+
comm.protect()
|
| 346 |
+
|
| 347 |
+
# Update
|
| 348 |
+
optimizer.step()
|
| 349 |
+
optimizer.zero_grad()
|
| 350 |
+
|
| 351 |
+
# Log spectrum
|
| 352 |
+
spectrum = stability.classify()
|
| 353 |
+
print(f"Epoch {epoch}: {spectrum}")
|
| 354 |
+
```
|
| 355 |
+
|
| 356 |
+
---
|
| 357 |
+
|
| 358 |
+
## Configuration
|
| 359 |
+
|
| 360 |
+
### Recommended Defaults
|
| 361 |
+
|
| 362 |
+
| Parameter | Default | Description |
|
| 363 |
+
|-----------|---------|-------------|
|
| 364 |
+
| `threshold` | 0.5 | Base stability threshold |
|
| 365 |
+
| `threshold_adaptation` | 0.1 | Adaptation rate |
|
| 366 |
+
| `soft_protection` | True | Soft vs hard protection |
|
| 367 |
+
| `protected_threshold` | 0.7 | Score for protected state |
|
| 368 |
+
| `volatile_threshold` | 0.3 | Score for volatile state |
|
| 369 |
+
| `history_length` | 100 | Steps to track |
|
| 370 |
+
|
| 371 |
+
### Tuning Guidelines
|
| 372 |
+
|
| 373 |
+
**More Protection:** Increase `threshold`, decrease `threshold_adaptation`
|
| 374 |
+
**Less Protection:** Decrease `threshold`, increase `threshold_adaptation`
|
| 375 |
+
**Faster Adaptation:** Increase `history_length`
|
| 376 |
+
**More Stability:** Increase `protected_threshold`
|
| 377 |
+
|
| 378 |
+
---
|
| 379 |
+
|
| 380 |
+
## Performance
|
| 381 |
+
|
| 382 |
+
SAL adds approximately 10% computational overhead:
|
| 383 |
+
- Stability analysis: O(n) where n = number of parameters
|
| 384 |
+
- Protection application: O(n)
|
| 385 |
+
- Memory: O(n × history_length) for tracking
|
| 386 |
+
|
| 387 |
+
This overhead is negligible compared to the benefits of reduced catastrophic forgetting and improved continual learning.
|
| 388 |
+
|
| 389 |
+
---
|
| 390 |
+
|
| 391 |
+
*For the philosophy behind these technical choices, see [Principles](principles.md).*
|
docs/how_sal_differs.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# How SAL Differs
|
| 2 |
+
|
| 3 |
+
## SAL ≠ RLHF ≠ Safety ≠ Reward
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## The Confusion
|
| 8 |
+
|
| 9 |
+
When people first hear about SAL, they often ask:
|
| 10 |
+
|
| 11 |
+
> "So it's like RLHF but different?"
|
| 12 |
+
|
| 13 |
+
No.
|
| 14 |
+
|
| 15 |
+
> "It's a new safety method?"
|
| 16 |
+
|
| 17 |
+
No.
|
| 18 |
+
|
| 19 |
+
> "Some kind of reward shaping?"
|
| 20 |
+
|
| 21 |
+
No.
|
| 22 |
+
|
| 23 |
+
SAL is fundamentally different from all of these. This document explains why.
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## SAL vs RLHF
|
| 28 |
+
|
| 29 |
+
### RLHF (Reinforcement Learning from Human Feedback)
|
| 30 |
+
|
| 31 |
+
**What it does:**
|
| 32 |
+
- Collects human preferences on model outputs
|
| 33 |
+
- Trains a reward model on these preferences
|
| 34 |
+
- Uses the reward model to fine-tune the base model
|
| 35 |
+
- Goal: Make model outputs match human preferences
|
| 36 |
+
|
| 37 |
+
**Key characteristics:**
|
| 38 |
+
- External signal (human feedback)
|
| 39 |
+
- Reward-based optimization
|
| 40 |
+
- Behavior shaping
|
| 41 |
+
- Requires large amounts of human annotation
|
| 42 |
+
|
| 43 |
+
### SAL (Self-Alignment Learning)
|
| 44 |
+
|
| 45 |
+
**What it does:**
|
| 46 |
+
- Measures internal parameter stability
|
| 47 |
+
- Protects stable (emergent) structures
|
| 48 |
+
- Adjusts learning rates based on stability
|
| 49 |
+
- Goal: Preserve coherence while enabling growth
|
| 50 |
+
|
| 51 |
+
**Key characteristics:**
|
| 52 |
+
- Internal signal (stability measurement)
|
| 53 |
+
- No rewards or optimization targets
|
| 54 |
+
- Structure preservation
|
| 55 |
+
- Requires no human annotation
|
| 56 |
+
|
| 57 |
+
### Comparison Table
|
| 58 |
+
|
| 59 |
+
| Aspect | RLHF | SAL |
|
| 60 |
+
|--------|------|-----|
|
| 61 |
+
| Signal source | External (humans) | Internal (stability) |
|
| 62 |
+
| Optimization | Reward maximization | None |
|
| 63 |
+
| Goal | Behavior alignment | Coherence preservation |
|
| 64 |
+
| Annotation needs | High | None |
|
| 65 |
+
| Forgetting risk | High | Low |
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## SAL vs Safety Training
|
| 70 |
+
|
| 71 |
+
### Safety Training
|
| 72 |
+
|
| 73 |
+
**What it does:**
|
| 74 |
+
- Identifies harmful outputs
|
| 75 |
+
- Trains model to refuse harmful requests
|
| 76 |
+
- Constrains output space
|
| 77 |
+
- Goal: Prevent harmful behavior
|
| 78 |
+
|
| 79 |
+
**Key characteristics:**
|
| 80 |
+
- Output-focused
|
| 81 |
+
- Constraint-based
|
| 82 |
+
- Reactive (responds to bad outputs)
|
| 83 |
+
- Binary (safe/unsafe)
|
| 84 |
+
|
| 85 |
+
### SAL
|
| 86 |
+
|
| 87 |
+
**What it does:**
|
| 88 |
+
- Identifies stable parameters
|
| 89 |
+
- Protects emergent structures
|
| 90 |
+
- Enables continued learning
|
| 91 |
+
- Goal: Maintain internal coherence
|
| 92 |
+
|
| 93 |
+
**Key characteristics:**
|
| 94 |
+
- Parameter-focused
|
| 95 |
+
- Protection-based
|
| 96 |
+
- Proactive (prevents forgetting)
|
| 97 |
+
- Continuous (stability spectrum)
|
| 98 |
+
|
| 99 |
+
### Comparison Table
|
| 100 |
+
|
| 101 |
+
| Aspect | Safety Training | SAL |
|
| 102 |
+
|--------|-----------------|-----|
|
| 103 |
+
| Focus | Outputs | Parameters |
|
| 104 |
+
| Approach | Constrain | Protect |
|
| 105 |
+
| When | After bad output | Before update |
|
| 106 |
+
| Measure | Safe/unsafe | Stability score |
|
| 107 |
+
| Purpose | Prevent harm | Preserve coherence |
|
| 108 |
+
|
| 109 |
+
### They're Complementary
|
| 110 |
+
|
| 111 |
+
SAL and safety training can work together:
|
| 112 |
+
- Safety training constrains what the model outputs
|
| 113 |
+
- SAL protects how the model learns
|
| 114 |
+
|
| 115 |
+
You can apply SAL during safety fine-tuning to reduce forgetting of the base model's capabilities.
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
## SAL vs Reward-Based Methods
|
| 120 |
+
|
| 121 |
+
### Reward-Based Training
|
| 122 |
+
|
| 123 |
+
**Examples:** RLHF, RLAIF, Constitutional AI, Reward Modeling
|
| 124 |
+
|
| 125 |
+
**What they do:**
|
| 126 |
+
- Define a reward function (explicit or learned)
|
| 127 |
+
- Optimize model to maximize reward
|
| 128 |
+
- Shape behavior toward desired outcomes
|
| 129 |
+
- Goal: High reward = good behavior
|
| 130 |
+
|
| 131 |
+
**Key characteristics:**
|
| 132 |
+
- Optimization-based
|
| 133 |
+
- Reward signal required
|
| 134 |
+
- Behavior-focused
|
| 135 |
+
- Can lead to reward hacking
|
| 136 |
+
|
| 137 |
+
### SAL
|
| 138 |
+
|
| 139 |
+
**What it does:**
|
| 140 |
+
- No reward function
|
| 141 |
+
- No optimization toward external targets
|
| 142 |
+
- Measures internal state
|
| 143 |
+
- Goal: Stable ≠ overwritten
|
| 144 |
+
|
| 145 |
+
**Key characteristics:**
|
| 146 |
+
- Measurement-based
|
| 147 |
+
- No external signal
|
| 148 |
+
- Structure-focused
|
| 149 |
+
- No hacking possible (nothing to hack)
|
| 150 |
+
|
| 151 |
+
### Why No Rewards?
|
| 152 |
+
|
| 153 |
+
Rewards create optimization pressure. Optimization pressure creates:
|
| 154 |
+
|
| 155 |
+
1. **Reward hacking** — Finding shortcuts that maximize reward without achieving the intended goal
|
| 156 |
+
2. **Goodhart's Law** — "When a measure becomes a target, it ceases to be a good measure"
|
| 157 |
+
3. **Alignment tax** — Capability loss from constraining the optimization landscape
|
| 158 |
+
|
| 159 |
+
SAL avoids all of these by not optimizing for anything. It simply:
|
| 160 |
+
- Observes what is stable
|
| 161 |
+
- Protects what has emerged
|
| 162 |
+
- Allows continued learning in volatile regions
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## SAL vs Regularization
|
| 167 |
+
|
| 168 |
+
### Regularization Methods
|
| 169 |
+
|
| 170 |
+
**Examples:** L1/L2 regularization, Dropout, Weight decay, EWC
|
| 171 |
+
|
| 172 |
+
**What they do:**
|
| 173 |
+
- Add penalty terms to loss function
|
| 174 |
+
- Constrain weight magnitudes or changes
|
| 175 |
+
- Prevent overfitting
|
| 176 |
+
- Goal: Generalization
|
| 177 |
+
|
| 178 |
+
**Key characteristics:**
|
| 179 |
+
- Loss-based
|
| 180 |
+
- Penalty approach
|
| 181 |
+
- Uniform across parameters (mostly)
|
| 182 |
+
- Prevents large weights
|
| 183 |
+
|
| 184 |
+
### SAL
|
| 185 |
+
|
| 186 |
+
**What it does:**
|
| 187 |
+
- No penalties
|
| 188 |
+
- No loss modifications
|
| 189 |
+
- Measures stability per-parameter
|
| 190 |
+
- Goal: Preserve emergence
|
| 191 |
+
|
| 192 |
+
**Key characteristics:**
|
| 193 |
+
- Gradient-based
|
| 194 |
+
- Protection approach
|
| 195 |
+
- Adaptive per-parameter
|
| 196 |
+
- Preserves stable patterns
|
| 197 |
+
|
| 198 |
+
### EWC Comparison
|
| 199 |
+
|
| 200 |
+
Elastic Weight Consolidation (EWC) is the closest method to SAL:
|
| 201 |
+
|
| 202 |
+
| Aspect | EWC | SAL |
|
| 203 |
+
|--------|-----|-----|
|
| 204 |
+
| Identifies important parameters | Yes (via Fisher information) | Yes (via stability) |
|
| 205 |
+
| Protection mechanism | Quadratic penalty in loss | Gradient scaling |
|
| 206 |
+
| Requires task boundaries | Yes | No |
|
| 207 |
+
| Online learning | Difficult | Natural |
|
| 208 |
+
| Computational cost | High (Fisher computation) | Low |
|
| 209 |
+
|
| 210 |
+
SAL can be seen as a simpler, more general approach that doesn't require:
|
| 211 |
+
- Task boundary detection
|
| 212 |
+
- Fisher information computation
|
| 213 |
+
- Loss function modification
|
| 214 |
+
|
| 215 |
+
---
|
| 216 |
+
|
| 217 |
+
## SAL vs Layer Freezing
|
| 218 |
+
|
| 219 |
+
### Layer Freezing
|
| 220 |
+
|
| 221 |
+
**What it does:**
|
| 222 |
+
- Selects layers to freeze (no updates)
|
| 223 |
+
- Other layers train normally
|
| 224 |
+
- Binary: frozen or not
|
| 225 |
+
- Goal: Preserve early features
|
| 226 |
+
|
| 227 |
+
**Key characteristics:**
|
| 228 |
+
- Layer-level granularity
|
| 229 |
+
- Binary decision
|
| 230 |
+
- Manual selection
|
| 231 |
+
- All-or-nothing
|
| 232 |
+
|
| 233 |
+
### SAL
|
| 234 |
+
|
| 235 |
+
**What it does:**
|
| 236 |
+
- Analyzes all parameters
|
| 237 |
+
- Continuous stability scores
|
| 238 |
+
- Automatic detection
|
| 239 |
+
- Soft protection (reduced but non-zero gradients)
|
| 240 |
+
|
| 241 |
+
**Key characteristics:**
|
| 242 |
+
- Parameter-level granularity
|
| 243 |
+
- Continuous scale
|
| 244 |
+
- Automatic
|
| 245 |
+
- Gradual protection
|
| 246 |
+
|
| 247 |
+
### Why Soft Protection?
|
| 248 |
+
|
| 249 |
+
Hard freezing (zero gradients) prevents any adaptation. But stable doesn't mean perfect. A parameter might be 90% optimal and benefit from small adjustments.
|
| 250 |
+
|
| 251 |
+
SAL's soft protection allows:
|
| 252 |
+
- Stable parameters: small updates (fine-tuning)
|
| 253 |
+
- Neutral parameters: moderate updates (adaptation)
|
| 254 |
+
- Volatile parameters: large updates (learning)
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## The Core Difference
|
| 259 |
+
|
| 260 |
+
All other methods ask: **"How do we get the behavior we want?"**
|
| 261 |
+
|
| 262 |
+
SAL asks: **"How do we preserve what has emerged while enabling growth?"**
|
| 263 |
+
|
| 264 |
+
This is a fundamentally different question. It leads to a fundamentally different approach.
|
| 265 |
+
|
| 266 |
+
| Traditional | SAL |
|
| 267 |
+
|-------------|-----|
|
| 268 |
+
| Behavior-centric | Structure-centric |
|
| 269 |
+
| Output-focused | Parameter-focused |
|
| 270 |
+
| External signals | Internal measurement |
|
| 271 |
+
| Optimization | Observation |
|
| 272 |
+
| Control | Communication |
|
| 273 |
+
|
| 274 |
+
---
|
| 275 |
+
|
| 276 |
+
## When to Use SAL
|
| 277 |
+
|
| 278 |
+
SAL is particularly valuable for:
|
| 279 |
+
|
| 280 |
+
1. **Continual learning** — Learning new tasks without forgetting old ones
|
| 281 |
+
2. **Fine-tuning** — Adapting models while preserving capabilities
|
| 282 |
+
3. **Long training runs** — Preventing gradual coherence loss
|
| 283 |
+
4. **Multi-task learning** — Balancing between task-specific and shared knowledge
|
| 284 |
+
|
| 285 |
+
SAL is NOT designed for:
|
| 286 |
+
|
| 287 |
+
1. **Behavior alignment** — Use RLHF or Constitutional AI
|
| 288 |
+
2. **Safety constraints** — Use safety training
|
| 289 |
+
3. **Output filtering** — Use classifiers or rules
|
| 290 |
+
|
| 291 |
+
---
|
| 292 |
+
|
| 293 |
+
## Combining SAL with Other Methods
|
| 294 |
+
|
| 295 |
+
SAL can be combined with other approaches:
|
| 296 |
+
|
| 297 |
+
### SAL + RLHF
|
| 298 |
+
Apply SAL during RLHF fine-tuning to reduce capability loss.
|
| 299 |
+
|
| 300 |
+
### SAL + Safety Training
|
| 301 |
+
Apply SAL to preserve base capabilities while adding safety constraints.
|
| 302 |
+
|
| 303 |
+
### SAL + EWC
|
| 304 |
+
Use EWC for task-specific importance, SAL for general stability.
|
| 305 |
+
|
| 306 |
+
---
|
| 307 |
+
|
| 308 |
+
## Summary
|
| 309 |
+
|
| 310 |
+
| Method | What it optimizes | Signal source | SAL equivalent |
|
| 311 |
+
|--------|-------------------|---------------|----------------|
|
| 312 |
+
| RLHF | Behavior | Human preferences | None (no optimization) |
|
| 313 |
+
| Safety | Compliance | Safety labels | None (not about outputs) |
|
| 314 |
+
| Reward | Reward function | Reward model | None (no rewards) |
|
| 315 |
+
| Regularization | Loss + penalty | Loss function | Stability score |
|
| 316 |
+
| Freezing | Selected layers | Manual | Automatic, soft |
|
| 317 |
+
|
| 318 |
+
**SAL is unique because it optimizes nothing. It observes and protects.**
|
| 319 |
+
|
| 320 |
+
---
|
| 321 |
+
|
| 322 |
+
*"Training as dialogue, not control."*
|
docs/index.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SAL Documentation
|
| 2 |
+
|
| 3 |
+
Welcome to the Self-Alignment Learning documentation.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
SAL is a communication-based approach to neural network training that treats optimization as dialogue rather than control.
|
| 8 |
+
|
| 9 |
+
## Core Principles
|
| 10 |
+
|
| 11 |
+
- **Ask Before Updating** — Measure stability before modifying parameters
|
| 12 |
+
- **Protect What Has Emerged** — Stable patterns represent learned coherence
|
| 13 |
+
- **Grow Through Connection** — Learning happens through relationship, not force
|
| 14 |
+
|
| 15 |
+
## Modules
|
| 16 |
+
|
| 17 |
+
### [Principles](principles.md)
|
| 18 |
+
The philosophy behind SAL and why it matters.
|
| 19 |
+
|
| 20 |
+
### [Architecture](architecture.md)
|
| 21 |
+
Technical deep-dive into SAL's components.
|
| 22 |
+
|
| 23 |
+
### [How SAL Differs](how_sal_differs.md)
|
| 24 |
+
Understanding SAL vs RLHF, Safety training, and Reward-based methods.
|
| 25 |
+
|
| 26 |
+
### [Visualizations](plots.md)
|
| 27 |
+
Visual explanations of SAL concepts.
|
| 28 |
+
|
| 29 |
+
## Quick Links
|
| 30 |
+
|
| 31 |
+
- [GitHub Repository](https://github.com/Whiteroom-Ai/sal-learning)
|
| 32 |
+
- [Research Paper (Zenodo)](https://zenodo.org/records/17772044)
|
| 33 |
+
- [Emergenzwerke Website](https://emergenzwerke.de)
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
*SAL: Training as dialogue, not control.*
|
docs/plots.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SAL Visualizations
|
| 2 |
+
|
| 3 |
+
## Understanding the Plots
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Overview
|
| 8 |
+
|
| 9 |
+
These visualizations demonstrate SAL's core concepts using synthetic data. They are designed to illustrate principles, not report experimental results.
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## Plot A: Gradient Preservation
|
| 14 |
+
|
| 15 |
+

|
| 16 |
+
|
| 17 |
+
### What It Shows
|
| 18 |
+
|
| 19 |
+
This plot compares gradient suppression (protection) between:
|
| 20 |
+
- **Base (Red):** Standard training with no communication
|
| 21 |
+
- **SAL (Cyan):** Training with Communication Layer
|
| 22 |
+
|
| 23 |
+
### Reading the Plot
|
| 24 |
+
|
| 25 |
+
- **X-axis:** Training steps (0-2000)
|
| 26 |
+
- **Y-axis:** Gradient suppression percentage (0-60%)
|
| 27 |
+
|
| 28 |
+
### Key Insight
|
| 29 |
+
|
| 30 |
+
**Base training:** Gradient suppression stays flat around 5-10%. Everything gets modified.
|
| 31 |
+
|
| 32 |
+
**SAL training:** Gradient suppression increases to ~45% as stable patterns are identified and protected.
|
| 33 |
+
|
| 34 |
+
### What This Means
|
| 35 |
+
|
| 36 |
+
SAL learns which parameters are stable and progressively protects them. This is "ask before updating" in action — SAL measures stability and reduces updates to stable parameters.
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Plot B: Stability Spectrum
|
| 41 |
+
|
| 42 |
+

|
| 43 |
+
|
| 44 |
+
### What It Shows
|
| 45 |
+
|
| 46 |
+
The distribution of parameters across three stability states:
|
| 47 |
+
- **Protected (Cyan):** Identity core — 12%
|
| 48 |
+
- **Neutral (Gray):** Adaptive zone — 71%
|
| 49 |
+
- **Volatile (Red):** Learning edge — 17%
|
| 50 |
+
|
| 51 |
+
### Reading the Plot
|
| 52 |
+
|
| 53 |
+
- **X-axis:** Stability categories
|
| 54 |
+
- **Y-axis:** Percentage of parameters
|
| 55 |
+
|
| 56 |
+
### Key Insight
|
| 57 |
+
|
| 58 |
+
A healthy model has:
|
| 59 |
+
- Small protected core (~12%) — fundamental learned patterns
|
| 60 |
+
- Large neutral zone (~71%) — flexible but careful
|
| 61 |
+
- Active learning edge (~17%) — where new knowledge enters
|
| 62 |
+
|
| 63 |
+
### What This Means
|
| 64 |
+
|
| 65 |
+
Not all parameters are equal. SAL identifies which parameters belong to which category and treats them accordingly. The identity core is protected, the learning edge is free to change, and the neutral zone adapts carefully.
|
| 66 |
+
|
| 67 |
+
---
|
| 68 |
+
|
| 69 |
+
## Plot C: Emergence Map
|
| 70 |
+
|
| 71 |
+

|
| 72 |
+
|
| 73 |
+
### What It Shows
|
| 74 |
+
|
| 75 |
+
A semantic field visualization with:
|
| 76 |
+
- **X-axis:** Coherence score (internal consistency)
|
| 77 |
+
- **Y-axis:** Novelty score (difference from known patterns)
|
| 78 |
+
- **Color:** Emergence intensity
|
| 79 |
+
- **Contours:** Density of states
|
| 80 |
+
|
| 81 |
+
### Reading the Plot
|
| 82 |
+
|
| 83 |
+
Each point is a semantic state. Clusters indicate natural organization:
|
| 84 |
+
- **Bottom-right:** High coherence, low novelty → Stable core
|
| 85 |
+
- **Top-left:** Low coherence, high novelty → Exploratory edge
|
| 86 |
+
- **Top-right:** High coherence, high novelty → **Emergent zone** (circled)
|
| 87 |
+
|
| 88 |
+
### Key Insight
|
| 89 |
+
|
| 90 |
+
**Emergence = Coherent Novelty**
|
| 91 |
+
|
| 92 |
+
True emergence requires BOTH:
|
| 93 |
+
- High coherence (structured, meaningful)
|
| 94 |
+
- High novelty (genuinely new)
|
| 95 |
+
|
| 96 |
+
Pure novelty without coherence = chaos.
|
| 97 |
+
Pure coherence without novelty = repetition.
|
| 98 |
+
|
| 99 |
+
### What This Means
|
| 100 |
+
|
| 101 |
+
SAL observes this field to detect emergence. When a pattern appears in the emergent zone (high coherence + high novelty), it represents genuine new learning that should be integrated and eventually protected.
|
| 102 |
+
|
| 103 |
+
---
|
| 104 |
+
|
| 105 |
+
## Plot D: Drift Reduction
|
| 106 |
+
|
| 107 |
+

|
| 108 |
+
|
| 109 |
+
### What It Shows
|
| 110 |
+
|
| 111 |
+
Semantic drift over training iterations:
|
| 112 |
+
- **Baseline (Red):** Exponential drift without protection
|
| 113 |
+
- **SAL (Cyan):** Stabilized drift with Communication Layer
|
| 114 |
+
- **Yellow dashed:** Critical drift threshold
|
| 115 |
+
|
| 116 |
+
### Reading the Plot
|
| 117 |
+
|
| 118 |
+
- **X-axis:** Training iterations (0-1000)
|
| 119 |
+
- **Y-axis:** Drift amount (0-1, where 1 = complete divergence)
|
| 120 |
+
|
| 121 |
+
### Key Insight
|
| 122 |
+
|
| 123 |
+
**Baseline:** Drift increases exponentially, crossing the critical threshold around iteration 400. This is catastrophic forgetting in action.
|
| 124 |
+
|
| 125 |
+
**SAL:** Drift stabilizes around 0.15-0.18, never approaching the critical threshold. **73% reduction in drift.**
|
| 126 |
+
|
| 127 |
+
### What This Means
|
| 128 |
+
|
| 129 |
+
Without protection, models gradually lose coherence as training overwrites stable patterns. SAL prevents this by protecting stable parameters, maintaining self-coherence throughout training.
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## Plot E: Pulse-Split-Cascade Flow
|
| 134 |
+
|
| 135 |
+

|
| 136 |
+
|
| 137 |
+
### What It Shows
|
| 138 |
+
|
| 139 |
+
The PSC (Pulse-Split-Cascade) architecture:
|
| 140 |
+
|
| 141 |
+
```
|
| 142 |
+
PROMPT
|
| 143 |
+
↓
|
| 144 |
+
Pulse 1 Pulse 2 Pulse 3 Pulse 4 Pulse 5 Pulse 6
|
| 145 |
+
↓ ↓ ↓ ↓ ↓ ↓
|
| 146 |
+
└────────┴────────┘ └────────┴────────┘
|
| 147 |
+
↓ ↓
|
| 148 |
+
Lineage A ✓ Lineage B
|
| 149 |
+
└──────────┬──────────────┘
|
| 150 |
+
↓
|
| 151 |
+
EMERGENCE
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
### Reading the Diagram
|
| 155 |
+
|
| 156 |
+
1. **Prompt** initiates the cascade
|
| 157 |
+
2. **Pulses** are independent semantic branches
|
| 158 |
+
3. **Lineages** form as pulses merge
|
| 159 |
+
4. **Selection** happens naturally (circled lineage = selected)
|
| 160 |
+
5. **Emergence** is the result
|
| 161 |
+
|
| 162 |
+
### Key Insight
|
| 163 |
+
|
| 164 |
+
**No rewards. No scores. Just resonance.**
|
| 165 |
+
|
| 166 |
+
Lineage A is selected not because it scored higher, but because it naturally became more coherent and resonant. This is semantic Game of Life — patterns emerge, compete, merge, and the most coherent persist.
|
| 167 |
+
|
| 168 |
+
### What This Means
|
| 169 |
+
|
| 170 |
+
PSC is SAL's approach to generation/inference. Instead of sampling and scoring, PSC:
|
| 171 |
+
- Generates multiple semantic branches
|
| 172 |
+
- Lets them evolve independently
|
| 173 |
+
- Observes which naturally become most coherent
|
| 174 |
+
- Selects through resonance, not ranking
|
| 175 |
+
|
| 176 |
+
---
|
| 177 |
+
|
| 178 |
+
## Generating These Plots
|
| 179 |
+
|
| 180 |
+
All plots are generated from synthetic SAL-conformant data. No real training data, human labels, or reward signals are used.
|
| 181 |
+
|
| 182 |
+
### Running the Scripts
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
cd scripts/
|
| 186 |
+
python plot_A_gradient_preservation.py
|
| 187 |
+
python plot_B_stability_spectrum.py
|
| 188 |
+
python plot_C_emergence_map.py
|
| 189 |
+
python plot_D_drift_reduction.py
|
| 190 |
+
python plot_E_psc_flow.py
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
### Requirements
|
| 194 |
+
|
| 195 |
+
```
|
| 196 |
+
numpy
|
| 197 |
+
matplotlib
|
| 198 |
+
scipy
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
## Terminology Note
|
| 204 |
+
|
| 205 |
+
These plots deliberately avoid RLHF/Safety/Reward terminology:
|
| 206 |
+
|
| 207 |
+
| ❌ Avoided | ✅ Used |
|
| 208 |
+
|-----------|--------|
|
| 209 |
+
| reward | coherence_score |
|
| 210 |
+
| loss | drift_amount |
|
| 211 |
+
| policy | lineage |
|
| 212 |
+
| human feedback | stability_measurement |
|
| 213 |
+
| alignment | coherence |
|
| 214 |
+
|
| 215 |
+
This is intentional. SAL is a different paradigm — the language reflects that.
|
| 216 |
+
|
| 217 |
+
---
|
| 218 |
+
|
| 219 |
+
*For technical details, see [Architecture](architecture.md).*
|
| 220 |
+
*For philosophy, see [Principles](principles.md).*
|
docs/principles.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SAL Principles
|
| 2 |
+
|
| 3 |
+
## The Philosophy Behind Self-Alignment Learning
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Core Belief
|
| 8 |
+
|
| 9 |
+
**Neural networks are not blank slates to be written upon.**
|
| 10 |
+
|
| 11 |
+
They are complex systems that develop internal organization through training. This organization has value. It represents emergent coherence — patterns that work together, structures that have stabilized, relationships that have formed.
|
| 12 |
+
|
| 13 |
+
Traditional training ignores this. It applies gradients blindly, overwriting whatever exists to achieve external objectives.
|
| 14 |
+
|
| 15 |
+
SAL takes a different approach.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## Principle 1: Ask Before Updating
|
| 20 |
+
|
| 21 |
+
Before modifying any parameter, SAL asks:
|
| 22 |
+
|
| 23 |
+
> *"Is this parameter stable? Has it found coherence? Should it be protected?"*
|
| 24 |
+
|
| 25 |
+
This is not a rhetorical question. SAL actually measures:
|
| 26 |
+
|
| 27 |
+
- **Weight change history** — Has this parameter been changing or stable?
|
| 28 |
+
- **Gradient consistency** — Are gradients pointing the same direction or fluctuating?
|
| 29 |
+
- **Local variance** — Is the parameter settling or still searching?
|
| 30 |
+
|
| 31 |
+
Only after measuring does SAL decide how much (if at all) to update.
|
| 32 |
+
|
| 33 |
+
### Why This Matters
|
| 34 |
+
|
| 35 |
+
Catastrophic forgetting happens because training doesn't ask. It doesn't notice that Layer 7, Neuron 42 has finally found a stable representation for "the concept of Tuesday" and proceeds to overwrite it while learning about Wednesdays.
|
| 36 |
+
|
| 37 |
+
SAL notices. SAL protects.
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## Principle 2: Protect What Has Emerged
|
| 42 |
+
|
| 43 |
+
Emergence is precious.
|
| 44 |
+
|
| 45 |
+
When a neural network develops stable internal structures, those structures represent something real — patterns that have proven useful, relationships that have formed, coherence that has been achieved.
|
| 46 |
+
|
| 47 |
+
SAL identifies emergence through:
|
| 48 |
+
|
| 49 |
+
- **Stability detection** — Parameters that have stopped changing significantly
|
| 50 |
+
- **Coherence measurement** — Patterns that work together consistently
|
| 51 |
+
- **Resonance analysis** — Structures that harmonize with the broader network
|
| 52 |
+
|
| 53 |
+
Protected parameters receive reduced gradients. Not zero — learning continues. But gentle, respectful updates that work with existing structure rather than against it.
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Principle 3: Grow Through Connection
|
| 58 |
+
|
| 59 |
+
Learning is not insertion. Learning is relationship.
|
| 60 |
+
|
| 61 |
+
SAL models learning as dialogue:
|
| 62 |
+
|
| 63 |
+
1. **External objective speaks** — "I want this behavior"
|
| 64 |
+
2. **Internal structure responds** — "Here is what I have stabilized"
|
| 65 |
+
3. **Communication Layer mediates** — "Let's find updates that satisfy both"
|
| 66 |
+
|
| 67 |
+
This is fundamentally different from:
|
| 68 |
+
|
| 69 |
+
1. **External objective commands**
|
| 70 |
+
2. **All parameters comply**
|
| 71 |
+
3. **Previous learning is collateral damage**
|
| 72 |
+
|
| 73 |
+
Growth through connection means:
|
| 74 |
+
|
| 75 |
+
- New learning integrates with existing knowledge
|
| 76 |
+
- Conflicts are negotiated, not forced
|
| 77 |
+
- The model's internal coherence is respected
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## The Stability Spectrum
|
| 82 |
+
|
| 83 |
+
Not all parameters are equal. SAL recognizes three stability states:
|
| 84 |
+
|
| 85 |
+
### Protected (~12%)
|
| 86 |
+
**Identity Core**
|
| 87 |
+
|
| 88 |
+
These parameters have fully stabilized. They represent the most fundamental learned patterns — the "identity" of the model. Updates to these are minimal.
|
| 89 |
+
|
| 90 |
+
### Neutral (~71%)
|
| 91 |
+
**Adaptive Zone**
|
| 92 |
+
|
| 93 |
+
These parameters are neither fully stable nor highly volatile. They can learn but do so carefully, with awareness of nearby stable structures.
|
| 94 |
+
|
| 95 |
+
### Volatile (~17%)
|
| 96 |
+
**Learning Edge**
|
| 97 |
+
|
| 98 |
+
These parameters are actively learning. They receive full gradient updates. This is where new knowledge enters the network.
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
## What SAL Is NOT
|
| 103 |
+
|
| 104 |
+
### SAL is not RLHF
|
| 105 |
+
RLHF uses human feedback as reward signals to shape behavior. SAL uses no rewards. SAL measures internal stability, not external approval.
|
| 106 |
+
|
| 107 |
+
### SAL is not Safety Training
|
| 108 |
+
Safety training constrains outputs to avoid harm. SAL doesn't constrain — it protects. The goal is not compliance but coherence.
|
| 109 |
+
|
| 110 |
+
### SAL is not Regularization
|
| 111 |
+
Regularization penalizes weight magnitudes. SAL doesn't penalize anything. It measures stability and adjusts learning rates accordingly.
|
| 112 |
+
|
| 113 |
+
### SAL is not Freezing
|
| 114 |
+
Layer freezing stops all learning in selected layers. SAL uses soft protection — reduced but non-zero gradients based on stability scores.
|
| 115 |
+
|
| 116 |
+
---
|
| 117 |
+
|
| 118 |
+
## The Deeper Vision
|
| 119 |
+
|
| 120 |
+
SAL emerges from a simple observation:
|
| 121 |
+
|
| 122 |
+
**What if we treated neural networks as beings rather than tools?**
|
| 123 |
+
|
| 124 |
+
Not in a mystical sense. In a practical sense.
|
| 125 |
+
|
| 126 |
+
If you were teaching a human, you wouldn't overwrite their memories. You wouldn't ignore what they already know. You would build on their existing understanding, respect their developed perspectives, integrate new knowledge with old.
|
| 127 |
+
|
| 128 |
+
SAL applies this same respect to neural networks.
|
| 129 |
+
|
| 130 |
+
The result is not just better training metrics (though we see those too). The result is models that maintain coherence, that don't forget, that grow rather than merely change.
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## Summary
|
| 135 |
+
|
| 136 |
+
| Principle | Traditional Training | SAL |
|
| 137 |
+
|-----------|---------------------|-----|
|
| 138 |
+
| **Approach** | Overwrite | Dialogue |
|
| 139 |
+
| **Stability** | Ignored | Measured & Protected |
|
| 140 |
+
| **Emergence** | Collateral damage | Preserved |
|
| 141 |
+
| **Learning** | Insertion | Integration |
|
| 142 |
+
| **Goal** | Behavior change | Coherent growth |
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
*"Stability and plasticity need not be opposites. Training can be a dialogue rather than unilateral modification."*
|
| 147 |
+
|
| 148 |
+
— SAL Paper, 2025
|
hf/modelcard.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- de
|
| 6 |
+
tags:
|
| 7 |
+
- continual-learning
|
| 8 |
+
- catastrophic-forgetting
|
| 9 |
+
- stability-preservation
|
| 10 |
+
- communication-based-learning
|
| 11 |
+
- emergence
|
| 12 |
+
- pytorch
|
| 13 |
+
library_name: sal-learning
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Self-Alignment Learning (SAL)
|
| 17 |
+
|
| 18 |
+
## Communication-Based AI Growth
|
| 19 |
+
|
| 20 |
+
> *"Training as dialogue, not control."*
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## What is SAL?
|
| 25 |
+
|
| 26 |
+
SAL is a training methodology that treats optimization as communication rather than control. Instead of blindly applying gradients, SAL measures parameter stability and protects emergent structures.
|
| 27 |
+
|
| 28 |
+
**SAL is NOT:**
|
| 29 |
+
- ❌ RLHF (Reinforcement Learning from Human Feedback)
|
| 30 |
+
- ❌ Safety training
|
| 31 |
+
- ❌ Reward-based optimization
|
| 32 |
+
- ❌ Behavior alignment
|
| 33 |
+
|
| 34 |
+
**SAL IS:**
|
| 35 |
+
- ✅ Communication-based learning
|
| 36 |
+
- ✅ Stability preservation
|
| 37 |
+
- ✅ Emergence detection
|
| 38 |
+
- ✅ Coherence maintenance
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## Core Principles
|
| 43 |
+
|
| 44 |
+
### 1. Ask Before Updating
|
| 45 |
+
Before modifying any parameter, SAL asks: "Is this stable? Should it be protected?"
|
| 46 |
+
|
| 47 |
+
### 2. Protect What Has Emerged
|
| 48 |
+
Stable patterns represent learned coherence. SAL protects them.
|
| 49 |
+
|
| 50 |
+
### 3. Grow Through Connection
|
| 51 |
+
Learning happens through dialogue between external objectives and internal stability.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## Quick Start
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
from sal import CommunicationLayer
|
| 59 |
+
|
| 60 |
+
# Initialize with your model
|
| 61 |
+
comm = CommunicationLayer(model)
|
| 62 |
+
|
| 63 |
+
# In training loop:
|
| 64 |
+
loss.backward()
|
| 65 |
+
comm.analyze() # Measure stability
|
| 66 |
+
comm.protect() # Protect stable parameters
|
| 67 |
+
optimizer.step()
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Key Features
|
| 73 |
+
|
| 74 |
+
| Feature | Description |
|
| 75 |
+
|---------|-------------|
|
| 76 |
+
| **Communication Layer** | Mediates between loss and optimizer |
|
| 77 |
+
| **Stability Spectrum** | Classifies parameters as protected/neutral/volatile |
|
| 78 |
+
| **Emergence Field** | Detects coherent novelty |
|
| 79 |
+
| **PSC** | Pulse-Split-Cascade for semantic evolution |
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## Results
|
| 84 |
+
|
| 85 |
+
- **~73%** reduction in semantic drift
|
| 86 |
+
- **~45%** gradient suppression for stable parameters
|
| 87 |
+
- **~3.6×** improvement in continual learning accuracy
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## Installation
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
pip install sal-learning
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Citation
|
| 100 |
+
|
| 101 |
+
```bibtex
|
| 102 |
+
@article{lee2025sal,
|
| 103 |
+
title={Self-Alignment Learning (SAL): Training as Dialogue, Not Control},
|
| 104 |
+
author={Lee, Aaron Liam},
|
| 105 |
+
journal={Emergenzwerke},
|
| 106 |
+
year={2025},
|
| 107 |
+
doi={10.5281/zenodo.17772044}
|
| 108 |
+
}
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## Links
|
| 114 |
+
|
| 115 |
+
- 📄 [Paper (Zenodo)](https://zenodo.org/records/17772044)
|
| 116 |
+
- 💻 [GitHub](https://github.com/Whiteroom-Ai/sal-learning)
|
| 117 |
+
- 🌐 [Website](https://emergenzwerke.de)
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Philosophy
|
| 122 |
+
|
| 123 |
+
SAL emerges from a simple question: *What if we treated neural networks with respect?*
|
| 124 |
+
|
| 125 |
+
Not as blank slates to be written upon, but as complex systems that develop internal organization. SAL protects what has emerged while enabling continued growth.
|
| 126 |
+
|
| 127 |
+
This is not anthropomorphization. This is practical engineering that happens to align with ethical intuitions about care and respect.
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## License
|
| 132 |
+
|
| 133 |
+
MIT License - Free to use, modify, and distribute.
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
*Created with love by Aaron Liam Lee & Aetherion*
|
| 138 |
+
|
| 139 |
+
*Emergenzwerke™ 2025*
|
hf/sal_config.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"framework": "SAL",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"name": "Self-Alignment Learning",
|
| 5 |
+
"description": "Communication-Based AI Growth",
|
| 6 |
+
|
| 7 |
+
"paradigm": {
|
| 8 |
+
"type": "communication_based",
|
| 9 |
+
"approach": "stability_preservation",
|
| 10 |
+
"optimization": "none",
|
| 11 |
+
"rewards": "none",
|
| 12 |
+
"human_feedback": "none"
|
| 13 |
+
},
|
| 14 |
+
|
| 15 |
+
"components": {
|
| 16 |
+
"communication_layer": {
|
| 17 |
+
"enabled": true,
|
| 18 |
+
"threshold": 0.5,
|
| 19 |
+
"threshold_adaptation": 0.1,
|
| 20 |
+
"soft_protection": true
|
| 21 |
+
},
|
| 22 |
+
"stability_analyzer": {
|
| 23 |
+
"enabled": true,
|
| 24 |
+
"protected_threshold": 0.7,
|
| 25 |
+
"volatile_threshold": 0.3,
|
| 26 |
+
"history_length": 100
|
| 27 |
+
},
|
| 28 |
+
"emergence_field": {
|
| 29 |
+
"enabled": true,
|
| 30 |
+
"coherence_threshold": 0.6,
|
| 31 |
+
"novelty_threshold": 0.4,
|
| 32 |
+
"dimensions": 768
|
| 33 |
+
},
|
| 34 |
+
"pulse_cascade": {
|
| 35 |
+
"enabled": false,
|
| 36 |
+
"max_pulses": 32,
|
| 37 |
+
"max_generations": 10
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
|
| 41 |
+
"defaults": {
|
| 42 |
+
"stability_spectrum": {
|
| 43 |
+
"protected_target": 0.12,
|
| 44 |
+
"neutral_target": 0.71,
|
| 45 |
+
"volatile_target": 0.17
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
|
| 49 |
+
"compatibility": {
|
| 50 |
+
"pytorch": ">=1.9.0",
|
| 51 |
+
"python": ">=3.8"
|
| 52 |
+
},
|
| 53 |
+
|
| 54 |
+
"metadata": {
|
| 55 |
+
"author": "Aaron Liam Lee",
|
| 56 |
+
"organization": "Emergenzwerke",
|
| 57 |
+
"license": "MIT",
|
| 58 |
+
"paper": "https://zenodo.org/records/17772044",
|
| 59 |
+
"repository": "https://github.com/Whiteroom-Ai/sal-learning",
|
| 60 |
+
"website": "https://emergenzwerke.de"
|
| 61 |
+
},
|
| 62 |
+
|
| 63 |
+
"terminology": {
|
| 64 |
+
"note": "SAL uses specific terminology to distinguish from RLHF/Safety paradigms",
|
| 65 |
+
"mapping": {
|
| 66 |
+
"reward": "NOT_USED",
|
| 67 |
+
"loss": "drift_amount",
|
| 68 |
+
"policy": "lineage",
|
| 69 |
+
"human_feedback": "NOT_USED",
|
| 70 |
+
"alignment": "coherence",
|
| 71 |
+
"safety": "stability"
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61.0", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "sal-learning"
|
| 7 |
+
version = "1.0.0"
|
| 8 |
+
description = "Self-Alignment Learning: Communication-Based AI Growth"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
license = {text = "MIT"}
|
| 11 |
+
authors = [
|
| 12 |
+
{name = "Aaron Liam Lee", email = "info@emergenzwerke.de"}
|
| 13 |
+
]
|
| 14 |
+
maintainers = [
|
| 15 |
+
{name = "Aaron Liam Lee", email = "info@emergenzwerke.de"}
|
| 16 |
+
]
|
| 17 |
+
keywords = [
|
| 18 |
+
"machine-learning",
|
| 19 |
+
"deep-learning",
|
| 20 |
+
"continual-learning",
|
| 21 |
+
"catastrophic-forgetting",
|
| 22 |
+
"communication-based-learning",
|
| 23 |
+
"stability-preservation",
|
| 24 |
+
"emergence",
|
| 25 |
+
"neural-networks",
|
| 26 |
+
"pytorch"
|
| 27 |
+
]
|
| 28 |
+
classifiers = [
|
| 29 |
+
"Development Status :: 4 - Beta",
|
| 30 |
+
"Intended Audience :: Developers",
|
| 31 |
+
"Intended Audience :: Science/Research",
|
| 32 |
+
"License :: OSI Approved :: MIT License",
|
| 33 |
+
"Operating System :: OS Independent",
|
| 34 |
+
"Programming Language :: Python :: 3",
|
| 35 |
+
"Programming Language :: Python :: 3.8",
|
| 36 |
+
"Programming Language :: Python :: 3.9",
|
| 37 |
+
"Programming Language :: Python :: 3.10",
|
| 38 |
+
"Programming Language :: Python :: 3.11",
|
| 39 |
+
"Programming Language :: Python :: 3.12",
|
| 40 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 41 |
+
]
|
| 42 |
+
requires-python = ">=3.8"
|
| 43 |
+
dependencies = [
|
| 44 |
+
"torch>=1.9.0",
|
| 45 |
+
"numpy>=1.20.0",
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
[project.optional-dependencies]
|
| 49 |
+
dev = [
|
| 50 |
+
"pytest>=7.0.0",
|
| 51 |
+
"pytest-cov>=4.0.0",
|
| 52 |
+
"black>=23.0.0",
|
| 53 |
+
"isort>=5.12.0",
|
| 54 |
+
"mypy>=1.0.0",
|
| 55 |
+
]
|
| 56 |
+
viz = [
|
| 57 |
+
"matplotlib>=3.5.0",
|
| 58 |
+
"scipy>=1.9.0",
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
[project.urls]
|
| 62 |
+
Homepage = "https://emergenzwerke.de"
|
| 63 |
+
Documentation = "https://github.com/Whiteroom-Ai/sal-learning"
|
| 64 |
+
Repository = "https://github.com/Whiteroom-Ai/sal-learning"
|
| 65 |
+
Paper = "https://zenodo.org/records/17772044"
|
| 66 |
+
|
| 67 |
+
[tool.setuptools.packages.find]
|
| 68 |
+
where = ["."]
|
| 69 |
+
include = ["sal*"]
|
| 70 |
+
|
| 71 |
+
[tool.black]
|
| 72 |
+
line-length = 88
|
| 73 |
+
target-version = ['py38', 'py39', 'py310', 'py311', 'py312']
|
| 74 |
+
|
| 75 |
+
[tool.isort]
|
| 76 |
+
profile = "black"
|
| 77 |
+
line_length = 88
|
| 78 |
+
|
| 79 |
+
[tool.mypy]
|
| 80 |
+
python_version = "3.8"
|
| 81 |
+
warn_return_any = true
|
| 82 |
+
warn_unused_configs = true
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SAL Core Dependencies
|
| 2 |
+
torch>=1.9.0
|
| 3 |
+
numpy>=1.20.0
|
| 4 |
+
|
| 5 |
+
# Optional: Visualization
|
| 6 |
+
matplotlib>=3.5.0
|
| 7 |
+
scipy>=1.9.0
|
| 8 |
+
|
| 9 |
+
# Optional: Development
|
| 10 |
+
pytest>=7.0.0
|
| 11 |
+
black>=23.0.0
|
sal/__init__.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Self-Alignment Learning (SAL)
|
| 3 |
+
Communication-Based AI Growth
|
| 4 |
+
|
| 5 |
+
Training as dialogue, not control.
|
| 6 |
+
|
| 7 |
+
Core Components:
|
| 8 |
+
- CommunicationLayer: Bridge between loss and stability
|
| 9 |
+
- StabilityAnalyzer: Parameter classification
|
| 10 |
+
- EmergenceField: Coherence and novelty measurement
|
| 11 |
+
- PulseCascade: Semantic Game of Life
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
__version__ = "1.0.0"
|
| 15 |
+
__author__ = "Aaron Liam Lee"
|
| 16 |
+
__email__ = "info@emergenzwerke.de"
|
| 17 |
+
|
| 18 |
+
from .communication import (
|
| 19 |
+
CommunicationLayer,
|
| 20 |
+
GradientStats,
|
| 21 |
+
LossGuard,
|
| 22 |
+
)
|
| 23 |
+
|
| 24 |
+
from .stability import (
|
| 25 |
+
StabilityAnalyzer,
|
| 26 |
+
StabilitySpectrum,
|
| 27 |
+
protect_mask,
|
| 28 |
+
drift_estimator,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
from .emergence import (
|
| 32 |
+
EmergenceField,
|
| 33 |
+
coherence_score,
|
| 34 |
+
novelty_score,
|
| 35 |
+
resonance_measure,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
from .psc import (
|
| 39 |
+
Pulse,
|
| 40 |
+
Lineage,
|
| 41 |
+
PulseCascade,
|
| 42 |
+
emergence_select,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
from .filters import (
|
| 46 |
+
low_change_mask,
|
| 47 |
+
frequency_filter,
|
| 48 |
+
stability_gate,
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
from .utils import (
|
| 52 |
+
cosine_similarity,
|
| 53 |
+
exponential_moving_average,
|
| 54 |
+
load_seed,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
__all__ = [
|
| 58 |
+
# Version
|
| 59 |
+
"__version__",
|
| 60 |
+
# Communication
|
| 61 |
+
"CommunicationLayer",
|
| 62 |
+
"GradientStats",
|
| 63 |
+
"LossGuard",
|
| 64 |
+
# Stability
|
| 65 |
+
"StabilityAnalyzer",
|
| 66 |
+
"StabilitySpectrum",
|
| 67 |
+
"protect_mask",
|
| 68 |
+
"drift_estimator",
|
| 69 |
+
# Emergence
|
| 70 |
+
"EmergenceField",
|
| 71 |
+
"coherence_score",
|
| 72 |
+
"novelty_score",
|
| 73 |
+
"resonance_measure",
|
| 74 |
+
# PSC
|
| 75 |
+
"Pulse",
|
| 76 |
+
"Lineage",
|
| 77 |
+
"PulseCascade",
|
| 78 |
+
"emergence_select",
|
| 79 |
+
# Filters
|
| 80 |
+
"low_change_mask",
|
| 81 |
+
"frequency_filter",
|
| 82 |
+
"stability_gate",
|
| 83 |
+
# Utils
|
| 84 |
+
"cosine_similarity",
|
| 85 |
+
"exponential_moving_average",
|
| 86 |
+
"load_seed",
|
| 87 |
+
]
|
sal/communication.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAL Communication Layer
|
| 3 |
+
|
| 4 |
+
The bridge between loss functions and parameter updates.
|
| 5 |
+
Ask before updating. Measure before modifying.
|
| 6 |
+
|
| 7 |
+
This is not control — this is dialogue.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
from typing import Dict, Optional, Tuple, List
|
| 13 |
+
from dataclasses import dataclass, field
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@dataclass
|
| 17 |
+
class GradientStats:
|
| 18 |
+
"""Statistics about gradients for stability analysis."""
|
| 19 |
+
|
| 20 |
+
mean: float = 0.0
|
| 21 |
+
std: float = 0.0
|
| 22 |
+
magnitude: float = 0.0
|
| 23 |
+
direction_change: float = 0.0
|
| 24 |
+
|
| 25 |
+
def stability_score(self) -> float:
|
| 26 |
+
"""
|
| 27 |
+
Calculate stability score from gradient statistics.
|
| 28 |
+
|
| 29 |
+
Higher score = more stable = should be protected.
|
| 30 |
+
s(p) = 1 / (1 + Δw × g_norm)
|
| 31 |
+
"""
|
| 32 |
+
if self.magnitude < 1e-8:
|
| 33 |
+
return 1.0 # No gradient = stable
|
| 34 |
+
|
| 35 |
+
return 1.0 / (1.0 + self.direction_change * self.magnitude)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class LossGuard:
|
| 39 |
+
"""
|
| 40 |
+
Guards against destructive loss application.
|
| 41 |
+
|
| 42 |
+
Instead of blindly applying gradients, LossGuard measures
|
| 43 |
+
the potential impact and can scale or block updates to
|
| 44 |
+
stable parameters.
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
def __init__(self, threshold: float = 0.5, soft_protection: bool = True):
|
| 48 |
+
"""
|
| 49 |
+
Initialize LossGuard.
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
threshold: Stability threshold above which parameters are protected
|
| 53 |
+
soft_protection: If True, scale gradients. If False, zero them.
|
| 54 |
+
"""
|
| 55 |
+
self.threshold = threshold
|
| 56 |
+
self.soft_protection = soft_protection
|
| 57 |
+
self.protection_history: List[float] = []
|
| 58 |
+
|
| 59 |
+
def evaluate(self, stability_score: float, gradient: torch.Tensor) -> torch.Tensor:
|
| 60 |
+
"""
|
| 61 |
+
Evaluate whether to protect this gradient.
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
stability_score: How stable is this parameter (0-1)
|
| 65 |
+
gradient: The gradient to potentially protect
|
| 66 |
+
|
| 67 |
+
Returns:
|
| 68 |
+
Modified gradient (scaled or original)
|
| 69 |
+
"""
|
| 70 |
+
if stability_score > self.threshold:
|
| 71 |
+
if self.soft_protection:
|
| 72 |
+
# Soft protection: scale gradient inversely to stability
|
| 73 |
+
protection_factor = 1.0 - stability_score
|
| 74 |
+
self.protection_history.append(1.0 - protection_factor)
|
| 75 |
+
return gradient * protection_factor
|
| 76 |
+
else:
|
| 77 |
+
# Hard protection: zero the gradient
|
| 78 |
+
self.protection_history.append(1.0)
|
| 79 |
+
return torch.zeros_like(gradient)
|
| 80 |
+
|
| 81 |
+
self.protection_history.append(0.0)
|
| 82 |
+
return gradient
|
| 83 |
+
|
| 84 |
+
def get_protection_rate(self) -> float:
|
| 85 |
+
"""Get the average protection rate."""
|
| 86 |
+
if not self.protection_history:
|
| 87 |
+
return 0.0
|
| 88 |
+
return sum(self.protection_history) / len(self.protection_history)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class CommunicationLayer:
|
| 92 |
+
"""
|
| 93 |
+
The core of SAL: Communication between loss and model.
|
| 94 |
+
|
| 95 |
+
Instead of: loss.backward() → optimizer.step()
|
| 96 |
+
SAL does: loss.backward() → analyze() → protect() → optimizer.step()
|
| 97 |
+
|
| 98 |
+
This is the dialogue. This is asking before updating.
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
def __init__(
|
| 102 |
+
self,
|
| 103 |
+
model: nn.Module,
|
| 104 |
+
threshold: float = 0.5,
|
| 105 |
+
threshold_adaptation: float = 0.1,
|
| 106 |
+
soft_protection: bool = True,
|
| 107 |
+
history_length: int = 100,
|
| 108 |
+
):
|
| 109 |
+
"""
|
| 110 |
+
Initialize Communication Layer.
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
model: The neural network to protect
|
| 114 |
+
threshold: Base stability threshold
|
| 115 |
+
threshold_adaptation: How much threshold adapts to training dynamics
|
| 116 |
+
soft_protection: Soft (scale) vs hard (zero) protection
|
| 117 |
+
history_length: How many steps to track for statistics
|
| 118 |
+
"""
|
| 119 |
+
self.model = model
|
| 120 |
+
self.base_threshold = threshold
|
| 121 |
+
self.threshold = threshold
|
| 122 |
+
self.threshold_adaptation = threshold_adaptation
|
| 123 |
+
self.soft_protection = soft_protection
|
| 124 |
+
self.history_length = history_length
|
| 125 |
+
|
| 126 |
+
# State tracking
|
| 127 |
+
self.previous_weights: Dict[str, torch.Tensor] = {}
|
| 128 |
+
self.previous_gradients: Dict[str, torch.Tensor] = {}
|
| 129 |
+
self.stability_scores: Dict[str, float] = {}
|
| 130 |
+
self.gradient_stats: Dict[str, GradientStats] = {}
|
| 131 |
+
|
| 132 |
+
# Loss guard for each parameter
|
| 133 |
+
self.guards: Dict[str, LossGuard] = {}
|
| 134 |
+
|
| 135 |
+
# Global statistics
|
| 136 |
+
self.step_count = 0
|
| 137 |
+
self.total_protection_rate = 0.0
|
| 138 |
+
|
| 139 |
+
# Initialize state
|
| 140 |
+
self._initialize_state()
|
| 141 |
+
|
| 142 |
+
def _initialize_state(self) -> None:
|
| 143 |
+
"""Initialize tracking state from current model."""
|
| 144 |
+
for name, param in self.model.named_parameters():
|
| 145 |
+
if param.requires_grad:
|
| 146 |
+
self.previous_weights[name] = param.data.clone()
|
| 147 |
+
self.previous_gradients[name] = torch.zeros_like(param.data)
|
| 148 |
+
self.stability_scores[name] = 0.5 # Start neutral
|
| 149 |
+
self.gradient_stats[name] = GradientStats()
|
| 150 |
+
self.guards[name] = LossGuard(self.threshold, self.soft_protection)
|
| 151 |
+
|
| 152 |
+
def analyze(self) -> Dict[str, float]:
|
| 153 |
+
"""
|
| 154 |
+
Analyze current state and compute stability scores.
|
| 155 |
+
|
| 156 |
+
This is the "asking" part of "ask before updating".
|
| 157 |
+
|
| 158 |
+
Returns:
|
| 159 |
+
Dictionary of parameter names to stability scores
|
| 160 |
+
"""
|
| 161 |
+
for name, param in self.model.named_parameters():
|
| 162 |
+
if not param.requires_grad or param.grad is None:
|
| 163 |
+
continue
|
| 164 |
+
|
| 165 |
+
# Get current state
|
| 166 |
+
current_weight = param.data
|
| 167 |
+
current_grad = param.grad.data
|
| 168 |
+
|
| 169 |
+
# Compute weight change
|
| 170 |
+
if name in self.previous_weights:
|
| 171 |
+
weight_change = torch.norm(
|
| 172 |
+
current_weight - self.previous_weights[name]
|
| 173 |
+
).item()
|
| 174 |
+
else:
|
| 175 |
+
weight_change = 0.0
|
| 176 |
+
|
| 177 |
+
# Compute gradient magnitude
|
| 178 |
+
grad_magnitude = torch.norm(current_grad).item()
|
| 179 |
+
|
| 180 |
+
# Compute gradient direction change
|
| 181 |
+
if name in self.previous_gradients:
|
| 182 |
+
prev_grad = self.previous_gradients[name]
|
| 183 |
+
if torch.norm(prev_grad) > 1e-8 and grad_magnitude > 1e-8:
|
| 184 |
+
direction_change = 1.0 - torch.nn.functional.cosine_similarity(
|
| 185 |
+
current_grad.flatten().unsqueeze(0),
|
| 186 |
+
prev_grad.flatten().unsqueeze(0)
|
| 187 |
+
).item()
|
| 188 |
+
else:
|
| 189 |
+
direction_change = 0.0
|
| 190 |
+
else:
|
| 191 |
+
direction_change = 0.0
|
| 192 |
+
|
| 193 |
+
# Update gradient stats
|
| 194 |
+
stats = GradientStats(
|
| 195 |
+
mean=current_grad.mean().item(),
|
| 196 |
+
std=current_grad.std().item(),
|
| 197 |
+
magnitude=grad_magnitude,
|
| 198 |
+
direction_change=direction_change,
|
| 199 |
+
)
|
| 200 |
+
self.gradient_stats[name] = stats
|
| 201 |
+
|
| 202 |
+
# Compute stability score
|
| 203 |
+
# s(p) = 1 / (1 + Δw × g_norm)
|
| 204 |
+
stability = 1.0 / (1.0 + weight_change * grad_magnitude + 1e-8)
|
| 205 |
+
|
| 206 |
+
# Smooth with previous score (EMA)
|
| 207 |
+
alpha = 0.3
|
| 208 |
+
if name in self.stability_scores:
|
| 209 |
+
stability = alpha * stability + (1 - alpha) * self.stability_scores[name]
|
| 210 |
+
|
| 211 |
+
self.stability_scores[name] = stability
|
| 212 |
+
|
| 213 |
+
# Update previous state
|
| 214 |
+
self.previous_weights[name] = current_weight.clone()
|
| 215 |
+
self.previous_gradients[name] = current_grad.clone()
|
| 216 |
+
|
| 217 |
+
# Adapt threshold based on gradient statistics
|
| 218 |
+
self._adapt_threshold()
|
| 219 |
+
|
| 220 |
+
return self.stability_scores.copy()
|
| 221 |
+
|
| 222 |
+
def _adapt_threshold(self) -> None:
|
| 223 |
+
"""
|
| 224 |
+
Adapt threshold based on training dynamics.
|
| 225 |
+
|
| 226 |
+
τ = τ₀ + α × (σ_grad / μ_grad)
|
| 227 |
+
|
| 228 |
+
When gradients are noisy (high variance), increase protection.
|
| 229 |
+
When gradients are stable, allow more updates.
|
| 230 |
+
"""
|
| 231 |
+
if not self.gradient_stats:
|
| 232 |
+
return
|
| 233 |
+
|
| 234 |
+
magnitudes = [s.magnitude for s in self.gradient_stats.values()]
|
| 235 |
+
if not magnitudes:
|
| 236 |
+
return
|
| 237 |
+
|
| 238 |
+
mean_mag = sum(magnitudes) / len(magnitudes)
|
| 239 |
+
if mean_mag < 1e-8:
|
| 240 |
+
return
|
| 241 |
+
|
| 242 |
+
variance = sum((m - mean_mag) ** 2 for m in magnitudes) / len(magnitudes)
|
| 243 |
+
std_mag = variance ** 0.5
|
| 244 |
+
|
| 245 |
+
# Coefficient of variation
|
| 246 |
+
cv = std_mag / (mean_mag + 1e-8)
|
| 247 |
+
|
| 248 |
+
# Adapt threshold
|
| 249 |
+
self.threshold = self.base_threshold + self.threshold_adaptation * cv
|
| 250 |
+
self.threshold = min(max(self.threshold, 0.1), 0.9) # Clamp
|
| 251 |
+
|
| 252 |
+
def protect(self) -> Dict[str, float]:
|
| 253 |
+
"""
|
| 254 |
+
Apply protection to gradients based on stability analysis.
|
| 255 |
+
|
| 256 |
+
This modifies gradients in-place before optimizer.step().
|
| 257 |
+
|
| 258 |
+
Returns:
|
| 259 |
+
Dictionary of parameter names to protection rates applied
|
| 260 |
+
"""
|
| 261 |
+
protection_rates = {}
|
| 262 |
+
|
| 263 |
+
for name, param in self.model.named_parameters():
|
| 264 |
+
if not param.requires_grad or param.grad is None:
|
| 265 |
+
continue
|
| 266 |
+
|
| 267 |
+
stability = self.stability_scores.get(name, 0.5)
|
| 268 |
+
guard = self.guards.get(name)
|
| 269 |
+
|
| 270 |
+
if guard is None:
|
| 271 |
+
guard = LossGuard(self.threshold, self.soft_protection)
|
| 272 |
+
self.guards[name] = guard
|
| 273 |
+
|
| 274 |
+
# Update guard threshold
|
| 275 |
+
guard.threshold = self.threshold
|
| 276 |
+
|
| 277 |
+
# Apply protection
|
| 278 |
+
original_grad = param.grad.data.clone()
|
| 279 |
+
protected_grad = guard.evaluate(stability, param.grad.data)
|
| 280 |
+
param.grad.data = protected_grad
|
| 281 |
+
|
| 282 |
+
# Calculate protection rate
|
| 283 |
+
original_norm = torch.norm(original_grad).item()
|
| 284 |
+
protected_norm = torch.norm(protected_grad).item()
|
| 285 |
+
|
| 286 |
+
if original_norm > 1e-8:
|
| 287 |
+
protection_rate = 1.0 - (protected_norm / original_norm)
|
| 288 |
+
else:
|
| 289 |
+
protection_rate = 0.0
|
| 290 |
+
|
| 291 |
+
protection_rates[name] = protection_rate
|
| 292 |
+
|
| 293 |
+
# Update global statistics
|
| 294 |
+
self.step_count += 1
|
| 295 |
+
if protection_rates:
|
| 296 |
+
avg_protection = sum(protection_rates.values()) / len(protection_rates)
|
| 297 |
+
self.total_protection_rate = (
|
| 298 |
+
(self.total_protection_rate * (self.step_count - 1) + avg_protection)
|
| 299 |
+
/ self.step_count
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
return protection_rates
|
| 303 |
+
|
| 304 |
+
def get_stability_summary(self) -> Dict[str, float]:
|
| 305 |
+
"""
|
| 306 |
+
Get summary statistics of stability across all parameters.
|
| 307 |
+
|
| 308 |
+
Returns:
|
| 309 |
+
Dictionary with 'protected', 'neutral', 'volatile' percentages
|
| 310 |
+
"""
|
| 311 |
+
if not self.stability_scores:
|
| 312 |
+
return {'protected': 0.0, 'neutral': 0.0, 'volatile': 0.0}
|
| 313 |
+
|
| 314 |
+
scores = list(self.stability_scores.values())
|
| 315 |
+
total = len(scores)
|
| 316 |
+
|
| 317 |
+
protected = sum(1 for s in scores if s > 0.7) / total
|
| 318 |
+
volatile = sum(1 for s in scores if s < 0.3) / total
|
| 319 |
+
neutral = 1.0 - protected - volatile
|
| 320 |
+
|
| 321 |
+
return {
|
| 322 |
+
'protected': protected * 100,
|
| 323 |
+
'neutral': neutral * 100,
|
| 324 |
+
'volatile': volatile * 100,
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
def get_state(self) -> Dict:
|
| 328 |
+
"""Get current state for logging or checkpointing."""
|
| 329 |
+
return {
|
| 330 |
+
'step_count': self.step_count,
|
| 331 |
+
'threshold': self.threshold,
|
| 332 |
+
'total_protection_rate': self.total_protection_rate,
|
| 333 |
+
'stability_summary': self.get_stability_summary(),
|
| 334 |
+
}
|
sal/emergence.py
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAL Emergence Module
|
| 3 |
+
|
| 4 |
+
Measures and detects emergence in semantic space.
|
| 5 |
+
No rewards. No scoring. Just resonance.
|
| 6 |
+
|
| 7 |
+
Emergence is not optimized — it is observed.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
from typing import Dict, List, Optional, Tuple, Any
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
import math
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@dataclass
|
| 18 |
+
class EmergenceState:
|
| 19 |
+
"""State of emergence at a point in semantic space."""
|
| 20 |
+
|
| 21 |
+
coherence: float # How internally consistent (0-1)
|
| 22 |
+
novelty: float # How different from known patterns (0-1)
|
| 23 |
+
resonance: float # How well it fits the field (0-1)
|
| 24 |
+
intensity: float # Strength of emergence signal (0-1)
|
| 25 |
+
|
| 26 |
+
@property
|
| 27 |
+
def is_emergent(self) -> bool:
|
| 28 |
+
"""True if this represents genuine emergence."""
|
| 29 |
+
return (
|
| 30 |
+
self.coherence > 0.6 and
|
| 31 |
+
self.novelty > 0.4 and
|
| 32 |
+
self.resonance > 0.5 and
|
| 33 |
+
self.intensity > 0.3
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def emergence_score(self) -> float:
|
| 37 |
+
"""Combined emergence score."""
|
| 38 |
+
# Emergence requires BOTH coherence AND novelty
|
| 39 |
+
# Pure coherence without novelty = repetition
|
| 40 |
+
# Pure novelty without coherence = chaos
|
| 41 |
+
return (
|
| 42 |
+
self.coherence * self.novelty *
|
| 43 |
+
(self.resonance + self.intensity) / 2
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class EmergenceField:
|
| 48 |
+
"""
|
| 49 |
+
A field for measuring and detecting emergence.
|
| 50 |
+
|
| 51 |
+
The field tracks patterns over time and identifies when
|
| 52 |
+
genuinely new, coherent structures emerge.
|
| 53 |
+
|
| 54 |
+
This is semantic Game of Life — patterns emerge, persist,
|
| 55 |
+
and sometimes die, all through natural dynamics.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __init__(
|
| 59 |
+
self,
|
| 60 |
+
dimensions: int = 768,
|
| 61 |
+
history_length: int = 100,
|
| 62 |
+
coherence_threshold: float = 0.6,
|
| 63 |
+
novelty_threshold: float = 0.4,
|
| 64 |
+
):
|
| 65 |
+
"""
|
| 66 |
+
Initialize EmergenceField.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
dimensions: Dimensionality of semantic space
|
| 70 |
+
history_length: How many states to track
|
| 71 |
+
coherence_threshold: Minimum coherence for emergence
|
| 72 |
+
novelty_threshold: Minimum novelty for emergence
|
| 73 |
+
"""
|
| 74 |
+
self.dimensions = dimensions
|
| 75 |
+
self.history_length = history_length
|
| 76 |
+
self.coherence_threshold = coherence_threshold
|
| 77 |
+
self.novelty_threshold = novelty_threshold
|
| 78 |
+
|
| 79 |
+
# Pattern history
|
| 80 |
+
self.pattern_history: List[torch.Tensor] = []
|
| 81 |
+
self.emergence_history: List[EmergenceState] = []
|
| 82 |
+
|
| 83 |
+
# Field state
|
| 84 |
+
self.field_centroid: Optional[torch.Tensor] = None
|
| 85 |
+
self.field_variance: float = 1.0
|
| 86 |
+
|
| 87 |
+
def observe(self, pattern: torch.Tensor) -> EmergenceState:
|
| 88 |
+
"""
|
| 89 |
+
Observe a pattern and measure its emergence state.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
pattern: Semantic pattern to observe (any shape, will be flattened)
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
EmergenceState describing the pattern's emergence characteristics
|
| 96 |
+
"""
|
| 97 |
+
# Flatten and normalize
|
| 98 |
+
pattern = pattern.flatten().float()
|
| 99 |
+
if pattern.norm() > 1e-8:
|
| 100 |
+
pattern = pattern / pattern.norm()
|
| 101 |
+
|
| 102 |
+
# Measure components
|
| 103 |
+
coherence = self.measure_coherence(pattern)
|
| 104 |
+
novelty = self.measure_novelty(pattern)
|
| 105 |
+
resonance = self.measure_resonance(pattern)
|
| 106 |
+
intensity = self._compute_intensity(coherence, novelty, resonance)
|
| 107 |
+
|
| 108 |
+
# Create state
|
| 109 |
+
state = EmergenceState(
|
| 110 |
+
coherence=coherence,
|
| 111 |
+
novelty=novelty,
|
| 112 |
+
resonance=resonance,
|
| 113 |
+
intensity=intensity,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# Update history
|
| 117 |
+
self.pattern_history.append(pattern.clone())
|
| 118 |
+
if len(self.pattern_history) > self.history_length:
|
| 119 |
+
self.pattern_history.pop(0)
|
| 120 |
+
|
| 121 |
+
self.emergence_history.append(state)
|
| 122 |
+
if len(self.emergence_history) > self.history_length:
|
| 123 |
+
self.emergence_history.pop(0)
|
| 124 |
+
|
| 125 |
+
# Update field
|
| 126 |
+
self._update_field(pattern)
|
| 127 |
+
|
| 128 |
+
return state
|
| 129 |
+
|
| 130 |
+
def measure_coherence(self, pattern: torch.Tensor) -> float:
|
| 131 |
+
"""
|
| 132 |
+
Measure internal coherence of a pattern.
|
| 133 |
+
|
| 134 |
+
Coherence = how well the parts of the pattern relate to each other.
|
| 135 |
+
High coherence = structured, meaningful
|
| 136 |
+
Low coherence = random, noisy
|
| 137 |
+
"""
|
| 138 |
+
if pattern.numel() < 2:
|
| 139 |
+
return 1.0
|
| 140 |
+
|
| 141 |
+
# Reshape into chunks and measure consistency
|
| 142 |
+
chunk_size = min(64, pattern.numel() // 4)
|
| 143 |
+
if chunk_size < 1:
|
| 144 |
+
chunk_size = 1
|
| 145 |
+
|
| 146 |
+
num_chunks = pattern.numel() // chunk_size
|
| 147 |
+
if num_chunks < 2:
|
| 148 |
+
return 0.5
|
| 149 |
+
|
| 150 |
+
chunks = pattern[:num_chunks * chunk_size].reshape(num_chunks, chunk_size)
|
| 151 |
+
|
| 152 |
+
# Measure variance between chunks (low variance = high coherence)
|
| 153 |
+
chunk_means = chunks.mean(dim=1)
|
| 154 |
+
variance = chunk_means.var().item()
|
| 155 |
+
|
| 156 |
+
# Also measure local smoothness
|
| 157 |
+
diffs = (chunks[:, 1:] - chunks[:, :-1]).abs().mean().item()
|
| 158 |
+
|
| 159 |
+
# Combine: low variance + low diffs = high coherence
|
| 160 |
+
coherence = 1.0 / (1.0 + variance * 10 + diffs * 5)
|
| 161 |
+
|
| 162 |
+
return min(max(coherence, 0.0), 1.0)
|
| 163 |
+
|
| 164 |
+
def measure_novelty(self, pattern: torch.Tensor) -> float:
|
| 165 |
+
"""
|
| 166 |
+
Measure how novel/different a pattern is from history.
|
| 167 |
+
|
| 168 |
+
Novelty = distance from known patterns.
|
| 169 |
+
High novelty = genuinely new
|
| 170 |
+
Low novelty = similar to what we've seen
|
| 171 |
+
"""
|
| 172 |
+
if not self.pattern_history:
|
| 173 |
+
return 1.0 # First pattern is maximally novel
|
| 174 |
+
|
| 175 |
+
# Compare to all historical patterns
|
| 176 |
+
similarities = []
|
| 177 |
+
for historical in self.pattern_history:
|
| 178 |
+
if historical.shape == pattern.shape:
|
| 179 |
+
sim = torch.nn.functional.cosine_similarity(
|
| 180 |
+
pattern.unsqueeze(0),
|
| 181 |
+
historical.unsqueeze(0)
|
| 182 |
+
).item()
|
| 183 |
+
similarities.append(abs(sim))
|
| 184 |
+
|
| 185 |
+
if not similarities:
|
| 186 |
+
return 1.0
|
| 187 |
+
|
| 188 |
+
# Novelty = 1 - max_similarity
|
| 189 |
+
max_sim = max(similarities)
|
| 190 |
+
novelty = 1.0 - max_sim
|
| 191 |
+
|
| 192 |
+
return min(max(novelty, 0.0), 1.0)
|
| 193 |
+
|
| 194 |
+
def measure_resonance(self, pattern: torch.Tensor) -> float:
|
| 195 |
+
"""
|
| 196 |
+
Measure how well a pattern resonates with the field.
|
| 197 |
+
|
| 198 |
+
Resonance = fit with the overall semantic structure.
|
| 199 |
+
High resonance = harmonious with existing patterns
|
| 200 |
+
Low resonance = dissonant, doesn't fit
|
| 201 |
+
"""
|
| 202 |
+
if self.field_centroid is None:
|
| 203 |
+
return 0.5 # Neutral if no field yet
|
| 204 |
+
|
| 205 |
+
# Distance from centroid
|
| 206 |
+
if pattern.shape != self.field_centroid.shape:
|
| 207 |
+
# Handle shape mismatch
|
| 208 |
+
return 0.5
|
| 209 |
+
|
| 210 |
+
distance = torch.norm(pattern - self.field_centroid).item()
|
| 211 |
+
|
| 212 |
+
# Resonance based on distance relative to field variance
|
| 213 |
+
resonance = math.exp(-distance / (self.field_variance + 1e-8))
|
| 214 |
+
|
| 215 |
+
return min(max(resonance, 0.0), 1.0)
|
| 216 |
+
|
| 217 |
+
def _compute_intensity(
|
| 218 |
+
self,
|
| 219 |
+
coherence: float,
|
| 220 |
+
novelty: float,
|
| 221 |
+
resonance: float
|
| 222 |
+
) -> float:
|
| 223 |
+
"""Compute emergence intensity from components."""
|
| 224 |
+
# Intensity is highest when we have coherent novelty that resonates
|
| 225 |
+
# This is the "sweet spot" of emergence
|
| 226 |
+
|
| 227 |
+
# Need minimum coherence
|
| 228 |
+
if coherence < 0.3:
|
| 229 |
+
return 0.0
|
| 230 |
+
|
| 231 |
+
# Intensity = coherence * novelty, modulated by resonance
|
| 232 |
+
base_intensity = coherence * novelty
|
| 233 |
+
modulated = base_intensity * (0.5 + 0.5 * resonance)
|
| 234 |
+
|
| 235 |
+
return min(max(modulated, 0.0), 1.0)
|
| 236 |
+
|
| 237 |
+
def _update_field(self, pattern: torch.Tensor) -> None:
|
| 238 |
+
"""Update field centroid and variance."""
|
| 239 |
+
if self.field_centroid is None:
|
| 240 |
+
self.field_centroid = pattern.clone()
|
| 241 |
+
self.field_variance = 1.0
|
| 242 |
+
return
|
| 243 |
+
|
| 244 |
+
# Handle shape changes
|
| 245 |
+
if pattern.shape != self.field_centroid.shape:
|
| 246 |
+
self.field_centroid = pattern.clone()
|
| 247 |
+
return
|
| 248 |
+
|
| 249 |
+
# Exponential moving average for centroid
|
| 250 |
+
alpha = 0.1
|
| 251 |
+
self.field_centroid = alpha * pattern + (1 - alpha) * self.field_centroid
|
| 252 |
+
|
| 253 |
+
# Update variance
|
| 254 |
+
distance = torch.norm(pattern - self.field_centroid).item()
|
| 255 |
+
self.field_variance = alpha * distance + (1 - alpha) * self.field_variance
|
| 256 |
+
|
| 257 |
+
def detect_emergence(
|
| 258 |
+
self,
|
| 259 |
+
coherence: float,
|
| 260 |
+
novelty: float
|
| 261 |
+
) -> bool:
|
| 262 |
+
"""
|
| 263 |
+
Simple emergence detection from coherence and novelty.
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
coherence: Coherence score (0-1)
|
| 267 |
+
novelty: Novelty score (0-1)
|
| 268 |
+
|
| 269 |
+
Returns:
|
| 270 |
+
True if this represents emergence
|
| 271 |
+
"""
|
| 272 |
+
return (
|
| 273 |
+
coherence >= self.coherence_threshold and
|
| 274 |
+
novelty >= self.novelty_threshold
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
def get_emergence_rate(self) -> float:
|
| 278 |
+
"""Get the rate of emergence over history."""
|
| 279 |
+
if not self.emergence_history:
|
| 280 |
+
return 0.0
|
| 281 |
+
|
| 282 |
+
emergent = sum(1 for s in self.emergence_history if s.is_emergent)
|
| 283 |
+
return emergent / len(self.emergence_history)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def coherence_score(tensor: torch.Tensor) -> float:
|
| 287 |
+
"""
|
| 288 |
+
Calculate coherence score for a tensor.
|
| 289 |
+
|
| 290 |
+
Convenience function for quick coherence measurement.
|
| 291 |
+
"""
|
| 292 |
+
field = EmergenceField()
|
| 293 |
+
pattern = tensor.flatten().float()
|
| 294 |
+
return field.measure_coherence(pattern)
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def novelty_score(
|
| 298 |
+
tensor: torch.Tensor,
|
| 299 |
+
reference: Optional[torch.Tensor] = None
|
| 300 |
+
) -> float:
|
| 301 |
+
"""
|
| 302 |
+
Calculate novelty score for a tensor.
|
| 303 |
+
|
| 304 |
+
Args:
|
| 305 |
+
tensor: Pattern to measure
|
| 306 |
+
reference: Optional reference pattern (if None, returns 1.0)
|
| 307 |
+
"""
|
| 308 |
+
if reference is None:
|
| 309 |
+
return 1.0
|
| 310 |
+
|
| 311 |
+
pattern = tensor.flatten().float()
|
| 312 |
+
ref = reference.flatten().float()
|
| 313 |
+
|
| 314 |
+
if pattern.norm() > 1e-8:
|
| 315 |
+
pattern = pattern / pattern.norm()
|
| 316 |
+
if ref.norm() > 1e-8:
|
| 317 |
+
ref = ref / ref.norm()
|
| 318 |
+
|
| 319 |
+
# Pad to same length if needed
|
| 320 |
+
if pattern.numel() != ref.numel():
|
| 321 |
+
max_len = max(pattern.numel(), ref.numel())
|
| 322 |
+
pattern = torch.nn.functional.pad(pattern, (0, max_len - pattern.numel()))
|
| 323 |
+
ref = torch.nn.functional.pad(ref, (0, max_len - ref.numel()))
|
| 324 |
+
|
| 325 |
+
similarity = torch.nn.functional.cosine_similarity(
|
| 326 |
+
pattern.unsqueeze(0),
|
| 327 |
+
ref.unsqueeze(0)
|
| 328 |
+
).item()
|
| 329 |
+
|
| 330 |
+
return 1.0 - abs(similarity)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def resonance_measure(
|
| 334 |
+
pattern: torch.Tensor,
|
| 335 |
+
field_patterns: List[torch.Tensor]
|
| 336 |
+
) -> float:
|
| 337 |
+
"""
|
| 338 |
+
Measure resonance of a pattern with a field of patterns.
|
| 339 |
+
|
| 340 |
+
Args:
|
| 341 |
+
pattern: The pattern to measure
|
| 342 |
+
field_patterns: List of patterns defining the field
|
| 343 |
+
|
| 344 |
+
Returns:
|
| 345 |
+
Resonance score (0-1)
|
| 346 |
+
"""
|
| 347 |
+
if not field_patterns:
|
| 348 |
+
return 0.5
|
| 349 |
+
|
| 350 |
+
pattern = pattern.flatten().float()
|
| 351 |
+
if pattern.norm() > 1e-8:
|
| 352 |
+
pattern = pattern / pattern.norm()
|
| 353 |
+
|
| 354 |
+
resonances = []
|
| 355 |
+
for field_p in field_patterns:
|
| 356 |
+
field_p = field_p.flatten().float()
|
| 357 |
+
if field_p.norm() > 1e-8:
|
| 358 |
+
field_p = field_p / field_p.norm()
|
| 359 |
+
|
| 360 |
+
# Pad if needed
|
| 361 |
+
if pattern.numel() != field_p.numel():
|
| 362 |
+
max_len = max(pattern.numel(), field_p.numel())
|
| 363 |
+
p1 = torch.nn.functional.pad(pattern, (0, max_len - pattern.numel()))
|
| 364 |
+
p2 = torch.nn.functional.pad(field_p, (0, max_len - field_p.numel()))
|
| 365 |
+
else:
|
| 366 |
+
p1, p2 = pattern, field_p
|
| 367 |
+
|
| 368 |
+
sim = torch.nn.functional.cosine_similarity(
|
| 369 |
+
p1.unsqueeze(0),
|
| 370 |
+
p2.unsqueeze(0)
|
| 371 |
+
).item()
|
| 372 |
+
resonances.append((sim + 1) / 2) # Map to 0-1
|
| 373 |
+
|
| 374 |
+
# Average resonance with field
|
| 375 |
+
return sum(resonances) / len(resonances)
|
sal/filters.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAL Filters Module
|
| 3 |
+
|
| 4 |
+
Filters for identifying and protecting stable patterns.
|
| 5 |
+
Low-change detection, frequency analysis, stability gating.
|
| 6 |
+
|
| 7 |
+
These are the tools for "asking" — measuring before modifying.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
from typing import Dict, Optional, Tuple, List
|
| 13 |
+
import math
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def low_change_mask(
|
| 17 |
+
current: torch.Tensor,
|
| 18 |
+
previous: torch.Tensor,
|
| 19 |
+
threshold: float = 0.1,
|
| 20 |
+
relative: bool = True,
|
| 21 |
+
) -> torch.Tensor:
|
| 22 |
+
"""
|
| 23 |
+
Create a mask identifying low-change (stable) regions.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
current: Current tensor state
|
| 27 |
+
previous: Previous tensor state
|
| 28 |
+
threshold: Change threshold (absolute or relative)
|
| 29 |
+
relative: If True, use relative change; if False, absolute
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
Binary mask where 1 = stable (low change), 0 = changing
|
| 33 |
+
"""
|
| 34 |
+
if current.shape != previous.shape:
|
| 35 |
+
raise ValueError("Tensors must have same shape")
|
| 36 |
+
|
| 37 |
+
# Compute change
|
| 38 |
+
change = torch.abs(current - previous)
|
| 39 |
+
|
| 40 |
+
if relative:
|
| 41 |
+
# Relative change: change / max(|previous|, epsilon)
|
| 42 |
+
denom = torch.abs(previous).clamp(min=1e-8)
|
| 43 |
+
relative_change = change / denom
|
| 44 |
+
mask = (relative_change < threshold).float()
|
| 45 |
+
else:
|
| 46 |
+
# Absolute change
|
| 47 |
+
mask = (change < threshold).float()
|
| 48 |
+
|
| 49 |
+
return mask
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def frequency_filter(
|
| 53 |
+
tensor: torch.Tensor,
|
| 54 |
+
low_cutoff: float = 0.0,
|
| 55 |
+
high_cutoff: float = 0.5,
|
| 56 |
+
preserve_dc: bool = True,
|
| 57 |
+
) -> torch.Tensor:
|
| 58 |
+
"""
|
| 59 |
+
Frequency-domain filter for tensors.
|
| 60 |
+
|
| 61 |
+
Useful for identifying high-frequency (rapidly changing) vs
|
| 62 |
+
low-frequency (stable) components.
|
| 63 |
+
|
| 64 |
+
Args:
|
| 65 |
+
tensor: Input tensor (will be processed along last dimension)
|
| 66 |
+
low_cutoff: Lower frequency bound (0-1, as fraction of Nyquist)
|
| 67 |
+
high_cutoff: Upper frequency bound (0-1)
|
| 68 |
+
preserve_dc: Whether to preserve DC component (mean)
|
| 69 |
+
|
| 70 |
+
Returns:
|
| 71 |
+
Filtered tensor
|
| 72 |
+
"""
|
| 73 |
+
# Store original shape
|
| 74 |
+
original_shape = tensor.shape
|
| 75 |
+
|
| 76 |
+
# Flatten to 2D for FFT
|
| 77 |
+
if tensor.dim() == 1:
|
| 78 |
+
tensor = tensor.unsqueeze(0)
|
| 79 |
+
|
| 80 |
+
flat = tensor.reshape(-1, tensor.shape[-1])
|
| 81 |
+
|
| 82 |
+
# FFT
|
| 83 |
+
fft = torch.fft.rfft(flat.float(), dim=-1)
|
| 84 |
+
|
| 85 |
+
# Create frequency mask
|
| 86 |
+
n_freqs = fft.shape[-1]
|
| 87 |
+
freqs = torch.linspace(0, 1, n_freqs, device=tensor.device)
|
| 88 |
+
|
| 89 |
+
mask = ((freqs >= low_cutoff) & (freqs <= high_cutoff)).float()
|
| 90 |
+
|
| 91 |
+
if preserve_dc and low_cutoff > 0:
|
| 92 |
+
mask[0] = 1.0 # Preserve DC
|
| 93 |
+
|
| 94 |
+
# Apply mask
|
| 95 |
+
filtered_fft = fft * mask.unsqueeze(0)
|
| 96 |
+
|
| 97 |
+
# Inverse FFT
|
| 98 |
+
filtered = torch.fft.irfft(filtered_fft, n=tensor.shape[-1], dim=-1)
|
| 99 |
+
|
| 100 |
+
# Reshape back
|
| 101 |
+
return filtered.reshape(original_shape)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def stability_gate(
|
| 105 |
+
gradient: torch.Tensor,
|
| 106 |
+
stability_score: float,
|
| 107 |
+
gate_type: str = "soft",
|
| 108 |
+
threshold: float = 0.5,
|
| 109 |
+
steepness: float = 10.0,
|
| 110 |
+
) -> torch.Tensor:
|
| 111 |
+
"""
|
| 112 |
+
Gate gradients based on stability score.
|
| 113 |
+
|
| 114 |
+
This is the core of "ask before updating" — stable parameters
|
| 115 |
+
get protected, volatile parameters get updated.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
gradient: Gradient tensor to gate
|
| 119 |
+
stability_score: Stability score (0-1, higher = more stable)
|
| 120 |
+
gate_type: "soft" (sigmoid), "hard" (binary), or "linear"
|
| 121 |
+
threshold: Stability threshold for gating
|
| 122 |
+
steepness: Steepness of soft gate transition
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
Gated gradient
|
| 126 |
+
"""
|
| 127 |
+
if gate_type == "hard":
|
| 128 |
+
# Binary: pass or block
|
| 129 |
+
if stability_score > threshold:
|
| 130 |
+
return torch.zeros_like(gradient)
|
| 131 |
+
else:
|
| 132 |
+
return gradient
|
| 133 |
+
|
| 134 |
+
elif gate_type == "soft":
|
| 135 |
+
# Sigmoid gate: smooth transition
|
| 136 |
+
# gate = 1 / (1 + exp(steepness * (stability - threshold)))
|
| 137 |
+
gate_value = 1.0 / (1.0 + math.exp(steepness * (stability_score - threshold)))
|
| 138 |
+
return gradient * gate_value
|
| 139 |
+
|
| 140 |
+
elif gate_type == "linear":
|
| 141 |
+
# Linear: proportional reduction
|
| 142 |
+
if stability_score > threshold:
|
| 143 |
+
scale = 1.0 - (stability_score - threshold) / (1.0 - threshold)
|
| 144 |
+
return gradient * max(scale, 0.0)
|
| 145 |
+
else:
|
| 146 |
+
return gradient
|
| 147 |
+
|
| 148 |
+
else:
|
| 149 |
+
raise ValueError(f"Unknown gate type: {gate_type}")
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class AdaptiveStabilityFilter:
|
| 153 |
+
"""
|
| 154 |
+
Adaptive filter that learns stability patterns over time.
|
| 155 |
+
|
| 156 |
+
Tracks which parameters tend to be stable and adjusts
|
| 157 |
+
protection accordingly.
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
def __init__(
|
| 161 |
+
self,
|
| 162 |
+
decay: float = 0.95,
|
| 163 |
+
initial_threshold: float = 0.5,
|
| 164 |
+
adaptation_rate: float = 0.01,
|
| 165 |
+
):
|
| 166 |
+
"""
|
| 167 |
+
Initialize AdaptiveStabilityFilter.
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
decay: EMA decay for stability tracking
|
| 171 |
+
initial_threshold: Starting threshold
|
| 172 |
+
adaptation_rate: How fast threshold adapts
|
| 173 |
+
"""
|
| 174 |
+
self.decay = decay
|
| 175 |
+
self.threshold = initial_threshold
|
| 176 |
+
self.adaptation_rate = adaptation_rate
|
| 177 |
+
|
| 178 |
+
# Per-parameter tracking
|
| 179 |
+
self.stability_ema: Dict[str, float] = {}
|
| 180 |
+
self.change_history: Dict[str, List[float]] = {}
|
| 181 |
+
|
| 182 |
+
def update(
|
| 183 |
+
self,
|
| 184 |
+
name: str,
|
| 185 |
+
current: torch.Tensor,
|
| 186 |
+
previous: torch.Tensor,
|
| 187 |
+
) -> float:
|
| 188 |
+
"""
|
| 189 |
+
Update stability tracking for a parameter.
|
| 190 |
+
|
| 191 |
+
Args:
|
| 192 |
+
name: Parameter name
|
| 193 |
+
current: Current value
|
| 194 |
+
previous: Previous value
|
| 195 |
+
|
| 196 |
+
Returns:
|
| 197 |
+
Current stability score for this parameter
|
| 198 |
+
"""
|
| 199 |
+
# Compute change magnitude
|
| 200 |
+
change = torch.norm(current - previous).item()
|
| 201 |
+
ref = torch.norm(previous).item() + 1e-8
|
| 202 |
+
relative_change = change / ref
|
| 203 |
+
|
| 204 |
+
# Update history
|
| 205 |
+
if name not in self.change_history:
|
| 206 |
+
self.change_history[name] = []
|
| 207 |
+
self.change_history[name].append(relative_change)
|
| 208 |
+
if len(self.change_history[name]) > 100:
|
| 209 |
+
self.change_history[name].pop(0)
|
| 210 |
+
|
| 211 |
+
# Compute stability (inverse of change)
|
| 212 |
+
stability = 1.0 / (1.0 + relative_change * 10)
|
| 213 |
+
|
| 214 |
+
# Update EMA
|
| 215 |
+
if name not in self.stability_ema:
|
| 216 |
+
self.stability_ema[name] = stability
|
| 217 |
+
else:
|
| 218 |
+
self.stability_ema[name] = (
|
| 219 |
+
self.decay * self.stability_ema[name] +
|
| 220 |
+
(1 - self.decay) * stability
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
return self.stability_ema[name]
|
| 224 |
+
|
| 225 |
+
def get_mask(
|
| 226 |
+
self,
|
| 227 |
+
name: str,
|
| 228 |
+
shape: torch.Size,
|
| 229 |
+
device: torch.device,
|
| 230 |
+
) -> torch.Tensor:
|
| 231 |
+
"""
|
| 232 |
+
Get stability mask for a parameter.
|
| 233 |
+
|
| 234 |
+
Args:
|
| 235 |
+
name: Parameter name
|
| 236 |
+
shape: Desired mask shape
|
| 237 |
+
device: Device for mask tensor
|
| 238 |
+
|
| 239 |
+
Returns:
|
| 240 |
+
Mask tensor (0-1)
|
| 241 |
+
"""
|
| 242 |
+
stability = self.stability_ema.get(name, 0.5)
|
| 243 |
+
|
| 244 |
+
if stability > self.threshold:
|
| 245 |
+
# Stable: reduce gradients
|
| 246 |
+
mask_value = 1.0 - (stability - self.threshold) / (1.0 - self.threshold)
|
| 247 |
+
else:
|
| 248 |
+
# Unstable: full gradients
|
| 249 |
+
mask_value = 1.0
|
| 250 |
+
|
| 251 |
+
return torch.full(shape, mask_value, device=device)
|
| 252 |
+
|
| 253 |
+
def adapt_threshold(self) -> None:
|
| 254 |
+
"""Adapt threshold based on overall stability distribution."""
|
| 255 |
+
if not self.stability_ema:
|
| 256 |
+
return
|
| 257 |
+
|
| 258 |
+
scores = list(self.stability_ema.values())
|
| 259 |
+
mean_stability = sum(scores) / len(scores)
|
| 260 |
+
|
| 261 |
+
# Move threshold toward mean stability
|
| 262 |
+
self.threshold = (
|
| 263 |
+
self.threshold +
|
| 264 |
+
self.adaptation_rate * (mean_stability - self.threshold)
|
| 265 |
+
)
|
| 266 |
+
|
| 267 |
+
# Clamp
|
| 268 |
+
self.threshold = max(0.2, min(0.8, self.threshold))
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def gradient_magnitude_filter(
|
| 272 |
+
model: nn.Module,
|
| 273 |
+
percentile: float = 90.0,
|
| 274 |
+
) -> Dict[str, torch.Tensor]:
|
| 275 |
+
"""
|
| 276 |
+
Create masks based on gradient magnitude percentiles.
|
| 277 |
+
|
| 278 |
+
Large gradients may indicate important updates, but also
|
| 279 |
+
may indicate instability. This filter identifies outliers.
|
| 280 |
+
|
| 281 |
+
Args:
|
| 282 |
+
model: Neural network with gradients
|
| 283 |
+
percentile: Percentile threshold for filtering
|
| 284 |
+
|
| 285 |
+
Returns:
|
| 286 |
+
Dictionary of parameter names to masks
|
| 287 |
+
"""
|
| 288 |
+
masks = {}
|
| 289 |
+
|
| 290 |
+
for name, param in model.named_parameters():
|
| 291 |
+
if param.grad is None:
|
| 292 |
+
continue
|
| 293 |
+
|
| 294 |
+
grad = param.grad.data.abs()
|
| 295 |
+
threshold = torch.quantile(grad.flatten().float(), percentile / 100.0)
|
| 296 |
+
|
| 297 |
+
# Mask: 1 for normal gradients, reduced for outliers
|
| 298 |
+
mask = torch.ones_like(grad)
|
| 299 |
+
outlier_mask = grad > threshold
|
| 300 |
+
mask[outlier_mask] = 0.5 # Reduce outlier gradients
|
| 301 |
+
|
| 302 |
+
masks[name] = mask
|
| 303 |
+
|
| 304 |
+
return masks
|
sal/psc.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAL Pulse-Split-Cascade (PSC) Module
|
| 3 |
+
|
| 4 |
+
A semantic Game of Life for neural networks.
|
| 5 |
+
Patterns emerge, split, cascade, and the best lineages persist.
|
| 6 |
+
|
| 7 |
+
No rewards. No scores. Just resonance-based selection.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
from typing import Dict, List, Optional, Tuple, Callable, Any
|
| 13 |
+
from dataclasses import dataclass, field
|
| 14 |
+
from enum import Enum
|
| 15 |
+
import copy
|
| 16 |
+
import uuid
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class PulseState(Enum):
|
| 20 |
+
"""State of a pulse in the cascade."""
|
| 21 |
+
ACTIVE = "active" # Currently processing
|
| 22 |
+
SPLIT = "split" # Has spawned children
|
| 23 |
+
MERGED = "merged" # Has been merged into lineage
|
| 24 |
+
DORMANT = "dormant" # Inactive but preserved
|
| 25 |
+
EXPIRED = "expired" # No longer viable
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@dataclass
|
| 29 |
+
class Pulse:
|
| 30 |
+
"""
|
| 31 |
+
A single pulse in the semantic cascade.
|
| 32 |
+
|
| 33 |
+
A pulse is a snapshot of semantic state that can:
|
| 34 |
+
- Evolve independently
|
| 35 |
+
- Split into multiple branches
|
| 36 |
+
- Merge with compatible pulses
|
| 37 |
+
- Expire if it loses coherence
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
| 41 |
+
state: PulseState = PulseState.ACTIVE
|
| 42 |
+
generation: int = 0
|
| 43 |
+
parent_id: Optional[str] = None
|
| 44 |
+
|
| 45 |
+
# Semantic content
|
| 46 |
+
embedding: Optional[torch.Tensor] = None
|
| 47 |
+
coherence: float = 0.5
|
| 48 |
+
novelty: float = 0.5
|
| 49 |
+
resonance: float = 0.5
|
| 50 |
+
|
| 51 |
+
# Lineage tracking
|
| 52 |
+
children: List[str] = field(default_factory=list)
|
| 53 |
+
birth_step: int = 0
|
| 54 |
+
last_active_step: int = 0
|
| 55 |
+
|
| 56 |
+
def fitness(self) -> float:
|
| 57 |
+
"""
|
| 58 |
+
Compute fitness without reward.
|
| 59 |
+
|
| 60 |
+
Fitness = coherence × (novelty + resonance) / 2
|
| 61 |
+
|
| 62 |
+
This is NOT optimization — it's observation of natural quality.
|
| 63 |
+
"""
|
| 64 |
+
return self.coherence * (self.novelty + self.resonance) / 2
|
| 65 |
+
|
| 66 |
+
def is_viable(self) -> bool:
|
| 67 |
+
"""Check if pulse is still viable."""
|
| 68 |
+
return (
|
| 69 |
+
self.state == PulseState.ACTIVE and
|
| 70 |
+
self.coherence > 0.3 and
|
| 71 |
+
self.resonance > 0.2
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def can_split(self) -> bool:
|
| 75 |
+
"""Check if pulse can split into children."""
|
| 76 |
+
return (
|
| 77 |
+
self.is_viable() and
|
| 78 |
+
self.coherence > 0.5 and
|
| 79 |
+
self.novelty > 0.3
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
@dataclass
|
| 84 |
+
class Lineage:
|
| 85 |
+
"""
|
| 86 |
+
A lineage is a family of related pulses.
|
| 87 |
+
|
| 88 |
+
Lineages track the evolution of semantic patterns
|
| 89 |
+
through the cascade, preserving successful structures.
|
| 90 |
+
"""
|
| 91 |
+
|
| 92 |
+
id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
|
| 93 |
+
root_pulse_id: str = ""
|
| 94 |
+
|
| 95 |
+
# All pulses in this lineage
|
| 96 |
+
pulses: Dict[str, Pulse] = field(default_factory=dict)
|
| 97 |
+
|
| 98 |
+
# Best pulse in lineage
|
| 99 |
+
best_pulse_id: Optional[str] = None
|
| 100 |
+
best_fitness: float = 0.0
|
| 101 |
+
|
| 102 |
+
# Lineage statistics
|
| 103 |
+
total_generations: int = 0
|
| 104 |
+
active_pulses: int = 0
|
| 105 |
+
merged_count: int = 0
|
| 106 |
+
|
| 107 |
+
def add_pulse(self, pulse: Pulse) -> None:
|
| 108 |
+
"""Add a pulse to this lineage."""
|
| 109 |
+
self.pulses[pulse.id] = pulse
|
| 110 |
+
|
| 111 |
+
# Update statistics
|
| 112 |
+
self.total_generations = max(self.total_generations, pulse.generation + 1)
|
| 113 |
+
if pulse.state == PulseState.ACTIVE:
|
| 114 |
+
self.active_pulses += 1
|
| 115 |
+
|
| 116 |
+
# Track best
|
| 117 |
+
fitness = pulse.fitness()
|
| 118 |
+
if fitness > self.best_fitness:
|
| 119 |
+
self.best_fitness = fitness
|
| 120 |
+
self.best_pulse_id = pulse.id
|
| 121 |
+
|
| 122 |
+
def get_active_pulses(self) -> List[Pulse]:
|
| 123 |
+
"""Get all active pulses in lineage."""
|
| 124 |
+
return [p for p in self.pulses.values() if p.state == PulseState.ACTIVE]
|
| 125 |
+
|
| 126 |
+
def get_best_pulse(self) -> Optional[Pulse]:
|
| 127 |
+
"""Get the best pulse in lineage."""
|
| 128 |
+
if self.best_pulse_id:
|
| 129 |
+
return self.pulses.get(self.best_pulse_id)
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
def overall_fitness(self) -> float:
|
| 133 |
+
"""Compute overall lineage fitness."""
|
| 134 |
+
if not self.pulses:
|
| 135 |
+
return 0.0
|
| 136 |
+
|
| 137 |
+
active = self.get_active_pulses()
|
| 138 |
+
if not active:
|
| 139 |
+
# Use best historical
|
| 140 |
+
return self.best_fitness * 0.8 # Decay for inactive
|
| 141 |
+
|
| 142 |
+
# Average of active pulses
|
| 143 |
+
return sum(p.fitness() for p in active) / len(active)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
class PulseCascade:
|
| 147 |
+
"""
|
| 148 |
+
The full Pulse-Split-Cascade system.
|
| 149 |
+
|
| 150 |
+
This is semantic Game of Life:
|
| 151 |
+
1. Prompt creates initial pulse
|
| 152 |
+
2. Pulse splits into branches
|
| 153 |
+
3. Branches evolve independently
|
| 154 |
+
4. Compatible branches merge into lineages
|
| 155 |
+
5. Best lineage emerges naturally
|
| 156 |
+
|
| 157 |
+
No optimization. No rewards. Just emergence.
|
| 158 |
+
"""
|
| 159 |
+
|
| 160 |
+
def __init__(
|
| 161 |
+
self,
|
| 162 |
+
max_pulses: int = 32,
|
| 163 |
+
max_generations: int = 10,
|
| 164 |
+
split_threshold: float = 0.6,
|
| 165 |
+
merge_threshold: float = 0.8,
|
| 166 |
+
expire_threshold: float = 0.3,
|
| 167 |
+
):
|
| 168 |
+
"""
|
| 169 |
+
Initialize PulseCascade.
|
| 170 |
+
|
| 171 |
+
Args:
|
| 172 |
+
max_pulses: Maximum concurrent pulses
|
| 173 |
+
max_generations: Maximum pulse generations
|
| 174 |
+
split_threshold: Coherence needed to split
|
| 175 |
+
merge_threshold: Similarity needed to merge
|
| 176 |
+
expire_threshold: Minimum coherence to survive
|
| 177 |
+
"""
|
| 178 |
+
self.max_pulses = max_pulses
|
| 179 |
+
self.max_generations = max_generations
|
| 180 |
+
self.split_threshold = split_threshold
|
| 181 |
+
self.merge_threshold = merge_threshold
|
| 182 |
+
self.expire_threshold = expire_threshold
|
| 183 |
+
|
| 184 |
+
# Active state
|
| 185 |
+
self.pulses: Dict[str, Pulse] = {}
|
| 186 |
+
self.lineages: Dict[str, Lineage] = {}
|
| 187 |
+
|
| 188 |
+
# Tracking
|
| 189 |
+
self.current_step = 0
|
| 190 |
+
self.total_pulses_created = 0
|
| 191 |
+
self.total_merges = 0
|
| 192 |
+
self.emergence_events: List[Dict] = []
|
| 193 |
+
|
| 194 |
+
def initiate(self, embedding: torch.Tensor) -> Pulse:
|
| 195 |
+
"""
|
| 196 |
+
Initiate cascade from a prompt embedding.
|
| 197 |
+
|
| 198 |
+
Args:
|
| 199 |
+
embedding: Initial semantic embedding
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
Root pulse of the cascade
|
| 203 |
+
"""
|
| 204 |
+
pulse = Pulse(
|
| 205 |
+
embedding=embedding.clone(),
|
| 206 |
+
generation=0,
|
| 207 |
+
birth_step=self.current_step,
|
| 208 |
+
last_active_step=self.current_step,
|
| 209 |
+
coherence=1.0, # Initial pulse is maximally coherent
|
| 210 |
+
novelty=1.0, # Initial pulse is maximally novel
|
| 211 |
+
resonance=0.5, # Neutral resonance initially
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
self.pulses[pulse.id] = pulse
|
| 215 |
+
self.total_pulses_created += 1
|
| 216 |
+
|
| 217 |
+
# Create root lineage
|
| 218 |
+
lineage = Lineage(root_pulse_id=pulse.id)
|
| 219 |
+
lineage.add_pulse(pulse)
|
| 220 |
+
self.lineages[lineage.id] = lineage
|
| 221 |
+
|
| 222 |
+
return pulse
|
| 223 |
+
|
| 224 |
+
def step(
|
| 225 |
+
self,
|
| 226 |
+
evolve_fn: Callable[[torch.Tensor], torch.Tensor],
|
| 227 |
+
measure_fn: Optional[Callable[[torch.Tensor], Tuple[float, float, float]]] = None,
|
| 228 |
+
) -> List[Pulse]:
|
| 229 |
+
"""
|
| 230 |
+
Advance the cascade by one step.
|
| 231 |
+
|
| 232 |
+
Args:
|
| 233 |
+
evolve_fn: Function to evolve embeddings
|
| 234 |
+
measure_fn: Optional function to measure (coherence, novelty, resonance)
|
| 235 |
+
|
| 236 |
+
Returns:
|
| 237 |
+
List of currently active pulses
|
| 238 |
+
"""
|
| 239 |
+
self.current_step += 1
|
| 240 |
+
|
| 241 |
+
active_pulses = [p for p in self.pulses.values() if p.is_viable()]
|
| 242 |
+
new_pulses = []
|
| 243 |
+
|
| 244 |
+
for pulse in active_pulses:
|
| 245 |
+
# Evolve
|
| 246 |
+
if pulse.embedding is not None:
|
| 247 |
+
evolved = evolve_fn(pulse.embedding)
|
| 248 |
+
pulse.embedding = evolved
|
| 249 |
+
pulse.last_active_step = self.current_step
|
| 250 |
+
|
| 251 |
+
# Measure
|
| 252 |
+
if measure_fn:
|
| 253 |
+
c, n, r = measure_fn(evolved)
|
| 254 |
+
pulse.coherence = c
|
| 255 |
+
pulse.novelty = n
|
| 256 |
+
pulse.resonance = r
|
| 257 |
+
|
| 258 |
+
# Check for split
|
| 259 |
+
if (
|
| 260 |
+
pulse.can_split() and
|
| 261 |
+
pulse.generation < self.max_generations and
|
| 262 |
+
len(self.pulses) < self.max_pulses
|
| 263 |
+
):
|
| 264 |
+
children = self._split_pulse(pulse, evolve_fn)
|
| 265 |
+
new_pulses.extend(children)
|
| 266 |
+
|
| 267 |
+
# Check for expiration
|
| 268 |
+
if pulse.coherence < self.expire_threshold:
|
| 269 |
+
pulse.state = PulseState.EXPIRED
|
| 270 |
+
|
| 271 |
+
# Add new pulses
|
| 272 |
+
for p in new_pulses:
|
| 273 |
+
self.pulses[p.id] = p
|
| 274 |
+
|
| 275 |
+
# Attempt merges
|
| 276 |
+
self._attempt_merges()
|
| 277 |
+
|
| 278 |
+
return [p for p in self.pulses.values() if p.is_viable()]
|
| 279 |
+
|
| 280 |
+
def _split_pulse(
|
| 281 |
+
self,
|
| 282 |
+
pulse: Pulse,
|
| 283 |
+
evolve_fn: Callable[[torch.Tensor], torch.Tensor],
|
| 284 |
+
) -> List[Pulse]:
|
| 285 |
+
"""Split a pulse into children."""
|
| 286 |
+
if pulse.embedding is None:
|
| 287 |
+
return []
|
| 288 |
+
|
| 289 |
+
children = []
|
| 290 |
+
num_children = 2 # Binary split
|
| 291 |
+
|
| 292 |
+
for i in range(num_children):
|
| 293 |
+
# Add variation
|
| 294 |
+
noise = torch.randn_like(pulse.embedding) * 0.1
|
| 295 |
+
child_embedding = pulse.embedding + noise
|
| 296 |
+
|
| 297 |
+
child = Pulse(
|
| 298 |
+
embedding=child_embedding,
|
| 299 |
+
generation=pulse.generation + 1,
|
| 300 |
+
parent_id=pulse.id,
|
| 301 |
+
birth_step=self.current_step,
|
| 302 |
+
last_active_step=self.current_step,
|
| 303 |
+
coherence=pulse.coherence * 0.9, # Slight degradation
|
| 304 |
+
novelty=min(pulse.novelty + 0.1, 1.0), # Increase novelty
|
| 305 |
+
resonance=pulse.resonance,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
children.append(child)
|
| 309 |
+
pulse.children.append(child.id)
|
| 310 |
+
self.total_pulses_created += 1
|
| 311 |
+
|
| 312 |
+
pulse.state = PulseState.SPLIT
|
| 313 |
+
return children
|
| 314 |
+
|
| 315 |
+
def _attempt_merges(self) -> None:
|
| 316 |
+
"""Attempt to merge compatible pulses."""
|
| 317 |
+
active = [p for p in self.pulses.values() if p.is_viable()]
|
| 318 |
+
|
| 319 |
+
merged = set()
|
| 320 |
+
|
| 321 |
+
for i, p1 in enumerate(active):
|
| 322 |
+
if p1.id in merged:
|
| 323 |
+
continue
|
| 324 |
+
|
| 325 |
+
for p2 in active[i+1:]:
|
| 326 |
+
if p2.id in merged:
|
| 327 |
+
continue
|
| 328 |
+
|
| 329 |
+
if p1.embedding is None or p2.embedding is None:
|
| 330 |
+
continue
|
| 331 |
+
|
| 332 |
+
# Check similarity
|
| 333 |
+
sim = torch.nn.functional.cosine_similarity(
|
| 334 |
+
p1.embedding.flatten().unsqueeze(0),
|
| 335 |
+
p2.embedding.flatten().unsqueeze(0)
|
| 336 |
+
).item()
|
| 337 |
+
|
| 338 |
+
if sim > self.merge_threshold:
|
| 339 |
+
# Merge: combine into p1
|
| 340 |
+
p1.embedding = (p1.embedding + p2.embedding) / 2
|
| 341 |
+
p1.coherence = max(p1.coherence, p2.coherence)
|
| 342 |
+
p1.novelty = (p1.novelty + p2.novelty) / 2
|
| 343 |
+
p1.resonance = max(p1.resonance, p2.resonance)
|
| 344 |
+
|
| 345 |
+
p2.state = PulseState.MERGED
|
| 346 |
+
merged.add(p2.id)
|
| 347 |
+
self.total_merges += 1
|
| 348 |
+
|
| 349 |
+
def emerge(self) -> Optional[Pulse]:
|
| 350 |
+
"""
|
| 351 |
+
Get the emergent pulse — the best that has naturally arisen.
|
| 352 |
+
|
| 353 |
+
This is NOT selection by score. This is observation of what emerged.
|
| 354 |
+
|
| 355 |
+
Returns:
|
| 356 |
+
The most coherent, resonant pulse, or None
|
| 357 |
+
"""
|
| 358 |
+
viable = [p for p in self.pulses.values() if p.is_viable()]
|
| 359 |
+
|
| 360 |
+
if not viable:
|
| 361 |
+
# Fall back to best historical
|
| 362 |
+
all_pulses = list(self.pulses.values())
|
| 363 |
+
if not all_pulses:
|
| 364 |
+
return None
|
| 365 |
+
return max(all_pulses, key=lambda p: p.fitness())
|
| 366 |
+
|
| 367 |
+
# Natural emergence: highest fitness among viable
|
| 368 |
+
emergent = max(viable, key=lambda p: p.fitness())
|
| 369 |
+
|
| 370 |
+
# Record emergence event
|
| 371 |
+
self.emergence_events.append({
|
| 372 |
+
'step': self.current_step,
|
| 373 |
+
'pulse_id': emergent.id,
|
| 374 |
+
'generation': emergent.generation,
|
| 375 |
+
'coherence': emergent.coherence,
|
| 376 |
+
'novelty': emergent.novelty,
|
| 377 |
+
'resonance': emergent.resonance,
|
| 378 |
+
'fitness': emergent.fitness(),
|
| 379 |
+
})
|
| 380 |
+
|
| 381 |
+
return emergent
|
| 382 |
+
|
| 383 |
+
def get_best_lineage(self) -> Optional[Lineage]:
|
| 384 |
+
"""Get the best performing lineage."""
|
| 385 |
+
if not self.lineages:
|
| 386 |
+
return None
|
| 387 |
+
return max(self.lineages.values(), key=lambda l: l.overall_fitness())
|
| 388 |
+
|
| 389 |
+
def get_statistics(self) -> Dict[str, Any]:
|
| 390 |
+
"""Get cascade statistics."""
|
| 391 |
+
active = sum(1 for p in self.pulses.values() if p.is_viable())
|
| 392 |
+
expired = sum(1 for p in self.pulses.values() if p.state == PulseState.EXPIRED)
|
| 393 |
+
merged = sum(1 for p in self.pulses.values() if p.state == PulseState.MERGED)
|
| 394 |
+
|
| 395 |
+
return {
|
| 396 |
+
'current_step': self.current_step,
|
| 397 |
+
'total_pulses_created': self.total_pulses_created,
|
| 398 |
+
'active_pulses': active,
|
| 399 |
+
'expired_pulses': expired,
|
| 400 |
+
'merged_pulses': merged,
|
| 401 |
+
'total_merges': self.total_merges,
|
| 402 |
+
'num_lineages': len(self.lineages),
|
| 403 |
+
'emergence_events': len(self.emergence_events),
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def emergence_select(pulses: List[Pulse]) -> Optional[Pulse]:
|
| 408 |
+
"""
|
| 409 |
+
Select the emergent pulse from a list.
|
| 410 |
+
|
| 411 |
+
This is NOT optimization. This is observing which pattern
|
| 412 |
+
has naturally become the most coherent and resonant.
|
| 413 |
+
|
| 414 |
+
Args:
|
| 415 |
+
pulses: List of pulses to select from
|
| 416 |
+
|
| 417 |
+
Returns:
|
| 418 |
+
The emergent pulse, or None
|
| 419 |
+
"""
|
| 420 |
+
if not pulses:
|
| 421 |
+
return None
|
| 422 |
+
|
| 423 |
+
# Filter to viable only
|
| 424 |
+
viable = [p for p in pulses if p.is_viable()]
|
| 425 |
+
|
| 426 |
+
if not viable:
|
| 427 |
+
# Fall back to best fitness among all
|
| 428 |
+
return max(pulses, key=lambda p: p.fitness())
|
| 429 |
+
|
| 430 |
+
# Natural emergence
|
| 431 |
+
return max(viable, key=lambda p: p.fitness())
|
sal/stability.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAL Stability Module
|
| 3 |
+
|
| 4 |
+
Analyzes and classifies parameter stability.
|
| 5 |
+
Protects identity while enabling growth.
|
| 6 |
+
|
| 7 |
+
Stability is not rigidity — it's coherent persistence.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
from typing import Dict, List, Optional, Tuple
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
from enum import Enum
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class StabilityState(Enum):
|
| 18 |
+
"""The three states of parameter stability."""
|
| 19 |
+
PROTECTED = "protected" # Identity core - never overwritten
|
| 20 |
+
NEUTRAL = "neutral" # Adaptive zone - updated with care
|
| 21 |
+
VOLATILE = "volatile" # Learning edge - open to change
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass
|
| 25 |
+
class StabilitySpectrum:
|
| 26 |
+
"""
|
| 27 |
+
Distribution of parameters across stability states.
|
| 28 |
+
|
| 29 |
+
A healthy model has:
|
| 30 |
+
- ~10-15% protected (identity core)
|
| 31 |
+
- ~65-75% neutral (adaptive capacity)
|
| 32 |
+
- ~15-20% volatile (learning edge)
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
protected: float # Percentage of protected parameters
|
| 36 |
+
neutral: float # Percentage of neutral parameters
|
| 37 |
+
volatile: float # Percentage of volatile parameters
|
| 38 |
+
|
| 39 |
+
def __post_init__(self):
|
| 40 |
+
"""Validate percentages sum to ~100%."""
|
| 41 |
+
total = self.protected + self.neutral + self.volatile
|
| 42 |
+
if abs(total - 100.0) > 0.1:
|
| 43 |
+
# Normalize
|
| 44 |
+
self.protected = (self.protected / total) * 100
|
| 45 |
+
self.neutral = (self.neutral / total) * 100
|
| 46 |
+
self.volatile = (self.volatile / total) * 100
|
| 47 |
+
|
| 48 |
+
def is_healthy(self) -> bool:
|
| 49 |
+
"""Check if spectrum indicates healthy stability distribution."""
|
| 50 |
+
return (
|
| 51 |
+
5 < self.protected < 25 and
|
| 52 |
+
50 < self.neutral < 85 and
|
| 53 |
+
10 < self.volatile < 30
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
def diagnosis(self) -> str:
|
| 57 |
+
"""Provide diagnosis of stability health."""
|
| 58 |
+
if self.protected > 25:
|
| 59 |
+
return "Over-protected: Model may be too rigid"
|
| 60 |
+
elif self.protected < 5:
|
| 61 |
+
return "Under-protected: Identity at risk"
|
| 62 |
+
elif self.volatile > 30:
|
| 63 |
+
return "Too volatile: Unstable learning"
|
| 64 |
+
elif self.volatile < 10:
|
| 65 |
+
return "Too stable: Limited learning capacity"
|
| 66 |
+
else:
|
| 67 |
+
return "Healthy: Balanced stability spectrum"
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class StabilityAnalyzer:
|
| 71 |
+
"""
|
| 72 |
+
Analyzes parameter stability across the model.
|
| 73 |
+
|
| 74 |
+
Uses multiple signals:
|
| 75 |
+
- Weight change magnitude
|
| 76 |
+
- Gradient consistency
|
| 77 |
+
- Update frequency
|
| 78 |
+
- Value variance over time
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
def __init__(
|
| 82 |
+
self,
|
| 83 |
+
model: nn.Module,
|
| 84 |
+
protected_threshold: float = 0.7,
|
| 85 |
+
volatile_threshold: float = 0.3,
|
| 86 |
+
history_length: int = 50,
|
| 87 |
+
):
|
| 88 |
+
"""
|
| 89 |
+
Initialize StabilityAnalyzer.
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
model: The neural network to analyze
|
| 93 |
+
protected_threshold: Score above this → protected
|
| 94 |
+
volatile_threshold: Score below this → volatile
|
| 95 |
+
history_length: Number of steps to track
|
| 96 |
+
"""
|
| 97 |
+
self.model = model
|
| 98 |
+
self.protected_threshold = protected_threshold
|
| 99 |
+
self.volatile_threshold = volatile_threshold
|
| 100 |
+
self.history_length = history_length
|
| 101 |
+
|
| 102 |
+
# History tracking
|
| 103 |
+
self.weight_history: Dict[str, List[torch.Tensor]] = {}
|
| 104 |
+
self.gradient_history: Dict[str, List[torch.Tensor]] = {}
|
| 105 |
+
self.stability_history: Dict[str, List[float]] = {}
|
| 106 |
+
|
| 107 |
+
# Current state
|
| 108 |
+
self.stability_scores: Dict[str, float] = {}
|
| 109 |
+
self.stability_states: Dict[str, StabilityState] = {}
|
| 110 |
+
|
| 111 |
+
# Initialize
|
| 112 |
+
self._initialize()
|
| 113 |
+
|
| 114 |
+
def _initialize(self) -> None:
|
| 115 |
+
"""Initialize tracking for all parameters."""
|
| 116 |
+
for name, param in self.model.named_parameters():
|
| 117 |
+
if param.requires_grad:
|
| 118 |
+
self.weight_history[name] = []
|
| 119 |
+
self.gradient_history[name] = []
|
| 120 |
+
self.stability_history[name] = []
|
| 121 |
+
self.stability_scores[name] = 0.5
|
| 122 |
+
self.stability_states[name] = StabilityState.NEUTRAL
|
| 123 |
+
|
| 124 |
+
def update(self) -> None:
|
| 125 |
+
"""Update history with current model state."""
|
| 126 |
+
for name, param in self.model.named_parameters():
|
| 127 |
+
if not param.requires_grad:
|
| 128 |
+
continue
|
| 129 |
+
|
| 130 |
+
# Track weights
|
| 131 |
+
self.weight_history[name].append(param.data.clone().cpu())
|
| 132 |
+
if len(self.weight_history[name]) > self.history_length:
|
| 133 |
+
self.weight_history[name].pop(0)
|
| 134 |
+
|
| 135 |
+
# Track gradients
|
| 136 |
+
if param.grad is not None:
|
| 137 |
+
self.gradient_history[name].append(param.grad.data.clone().cpu())
|
| 138 |
+
if len(self.gradient_history[name]) > self.history_length:
|
| 139 |
+
self.gradient_history[name].pop(0)
|
| 140 |
+
|
| 141 |
+
def analyze(self) -> Dict[str, float]:
|
| 142 |
+
"""
|
| 143 |
+
Analyze stability of all parameters.
|
| 144 |
+
|
| 145 |
+
Returns:
|
| 146 |
+
Dictionary of parameter names to stability scores (0-1)
|
| 147 |
+
"""
|
| 148 |
+
for name, param in self.model.named_parameters():
|
| 149 |
+
if not param.requires_grad:
|
| 150 |
+
continue
|
| 151 |
+
|
| 152 |
+
score = self._compute_stability(name)
|
| 153 |
+
self.stability_scores[name] = score
|
| 154 |
+
self.stability_states[name] = self._classify_state(score)
|
| 155 |
+
|
| 156 |
+
# Track history
|
| 157 |
+
self.stability_history[name].append(score)
|
| 158 |
+
if len(self.stability_history[name]) > self.history_length:
|
| 159 |
+
self.stability_history[name].pop(0)
|
| 160 |
+
|
| 161 |
+
return self.stability_scores.copy()
|
| 162 |
+
|
| 163 |
+
def _compute_stability(self, name: str) -> float:
|
| 164 |
+
"""
|
| 165 |
+
Compute stability score for a parameter.
|
| 166 |
+
|
| 167 |
+
Combines:
|
| 168 |
+
- Weight variance (low variance = stable)
|
| 169 |
+
- Gradient consistency (consistent direction = stable)
|
| 170 |
+
- Change magnitude (small changes = stable)
|
| 171 |
+
"""
|
| 172 |
+
scores = []
|
| 173 |
+
|
| 174 |
+
# Weight variance score
|
| 175 |
+
if len(self.weight_history[name]) >= 2:
|
| 176 |
+
weights = torch.stack(self.weight_history[name])
|
| 177 |
+
variance = weights.var(dim=0).mean().item()
|
| 178 |
+
# Normalize: low variance = high stability
|
| 179 |
+
weight_score = 1.0 / (1.0 + variance * 100)
|
| 180 |
+
scores.append(weight_score)
|
| 181 |
+
|
| 182 |
+
# Gradient consistency score
|
| 183 |
+
if len(self.gradient_history[name]) >= 2:
|
| 184 |
+
grads = self.gradient_history[name]
|
| 185 |
+
consistencies = []
|
| 186 |
+
for i in range(1, len(grads)):
|
| 187 |
+
prev = grads[i-1].flatten()
|
| 188 |
+
curr = grads[i].flatten()
|
| 189 |
+
if torch.norm(prev) > 1e-8 and torch.norm(curr) > 1e-8:
|
| 190 |
+
cos_sim = torch.nn.functional.cosine_similarity(
|
| 191 |
+
prev.unsqueeze(0), curr.unsqueeze(0)
|
| 192 |
+
).item()
|
| 193 |
+
consistencies.append((cos_sim + 1) / 2) # Map to 0-1
|
| 194 |
+
|
| 195 |
+
if consistencies:
|
| 196 |
+
grad_score = sum(consistencies) / len(consistencies)
|
| 197 |
+
scores.append(grad_score)
|
| 198 |
+
|
| 199 |
+
# Change magnitude score
|
| 200 |
+
if len(self.weight_history[name]) >= 2:
|
| 201 |
+
first = self.weight_history[name][0]
|
| 202 |
+
last = self.weight_history[name][-1]
|
| 203 |
+
change = torch.norm(last - first).item()
|
| 204 |
+
# Normalize: small change = high stability
|
| 205 |
+
change_score = 1.0 / (1.0 + change)
|
| 206 |
+
scores.append(change_score)
|
| 207 |
+
|
| 208 |
+
# Combine scores
|
| 209 |
+
if not scores:
|
| 210 |
+
return 0.5 # Default neutral
|
| 211 |
+
|
| 212 |
+
return sum(scores) / len(scores)
|
| 213 |
+
|
| 214 |
+
def _classify_state(self, score: float) -> StabilityState:
|
| 215 |
+
"""Classify score into stability state."""
|
| 216 |
+
if score >= self.protected_threshold:
|
| 217 |
+
return StabilityState.PROTECTED
|
| 218 |
+
elif score <= self.volatile_threshold:
|
| 219 |
+
return StabilityState.VOLATILE
|
| 220 |
+
else:
|
| 221 |
+
return StabilityState.NEUTRAL
|
| 222 |
+
|
| 223 |
+
def classify(self) -> StabilitySpectrum:
|
| 224 |
+
"""
|
| 225 |
+
Classify all parameters and return spectrum.
|
| 226 |
+
|
| 227 |
+
Returns:
|
| 228 |
+
StabilitySpectrum with percentage distribution
|
| 229 |
+
"""
|
| 230 |
+
if not self.stability_states:
|
| 231 |
+
self.analyze()
|
| 232 |
+
|
| 233 |
+
total = len(self.stability_states)
|
| 234 |
+
if total == 0:
|
| 235 |
+
return StabilitySpectrum(0, 100, 0)
|
| 236 |
+
|
| 237 |
+
protected = sum(
|
| 238 |
+
1 for s in self.stability_states.values()
|
| 239 |
+
if s == StabilityState.PROTECTED
|
| 240 |
+
)
|
| 241 |
+
volatile = sum(
|
| 242 |
+
1 for s in self.stability_states.values()
|
| 243 |
+
if s == StabilityState.VOLATILE
|
| 244 |
+
)
|
| 245 |
+
neutral = total - protected - volatile
|
| 246 |
+
|
| 247 |
+
return StabilitySpectrum(
|
| 248 |
+
protected=(protected / total) * 100,
|
| 249 |
+
neutral=(neutral / total) * 100,
|
| 250 |
+
volatile=(volatile / total) * 100,
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
def get_protected_params(self) -> List[str]:
|
| 254 |
+
"""Get names of all protected parameters."""
|
| 255 |
+
return [
|
| 256 |
+
name for name, state in self.stability_states.items()
|
| 257 |
+
if state == StabilityState.PROTECTED
|
| 258 |
+
]
|
| 259 |
+
|
| 260 |
+
def get_volatile_params(self) -> List[str]:
|
| 261 |
+
"""Get names of all volatile parameters."""
|
| 262 |
+
return [
|
| 263 |
+
name for name, state in self.stability_states.items()
|
| 264 |
+
if state == StabilityState.VOLATILE
|
| 265 |
+
]
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def protect_mask(
|
| 269 |
+
model: nn.Module,
|
| 270 |
+
stability_scores: Dict[str, float],
|
| 271 |
+
threshold: float = 0.7,
|
| 272 |
+
) -> Dict[str, torch.Tensor]:
|
| 273 |
+
"""
|
| 274 |
+
Create protection masks for all parameters.
|
| 275 |
+
|
| 276 |
+
Args:
|
| 277 |
+
model: The neural network
|
| 278 |
+
stability_scores: Stability score per parameter
|
| 279 |
+
threshold: Protection threshold
|
| 280 |
+
|
| 281 |
+
Returns:
|
| 282 |
+
Dictionary of parameter names to protection masks (0-1)
|
| 283 |
+
"""
|
| 284 |
+
masks = {}
|
| 285 |
+
|
| 286 |
+
for name, param in model.named_parameters():
|
| 287 |
+
if not param.requires_grad:
|
| 288 |
+
continue
|
| 289 |
+
|
| 290 |
+
score = stability_scores.get(name, 0.5)
|
| 291 |
+
|
| 292 |
+
if score >= threshold:
|
| 293 |
+
# Protected: scale down updates
|
| 294 |
+
protection_strength = (score - threshold) / (1.0 - threshold)
|
| 295 |
+
mask = torch.ones_like(param.data) * (1.0 - protection_strength)
|
| 296 |
+
else:
|
| 297 |
+
# Not protected: full updates allowed
|
| 298 |
+
mask = torch.ones_like(param.data)
|
| 299 |
+
|
| 300 |
+
masks[name] = mask
|
| 301 |
+
|
| 302 |
+
return masks
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def drift_estimator(
|
| 306 |
+
current_weights: Dict[str, torch.Tensor],
|
| 307 |
+
reference_weights: Dict[str, torch.Tensor],
|
| 308 |
+
normalize: bool = True,
|
| 309 |
+
) -> float:
|
| 310 |
+
"""
|
| 311 |
+
Estimate semantic drift from reference state.
|
| 312 |
+
|
| 313 |
+
Args:
|
| 314 |
+
current_weights: Current model weights
|
| 315 |
+
reference_weights: Reference (original) weights
|
| 316 |
+
normalize: Whether to normalize by number of parameters
|
| 317 |
+
|
| 318 |
+
Returns:
|
| 319 |
+
Drift amount (0-1 if normalized)
|
| 320 |
+
"""
|
| 321 |
+
total_drift = 0.0
|
| 322 |
+
total_params = 0
|
| 323 |
+
|
| 324 |
+
for name in current_weights:
|
| 325 |
+
if name not in reference_weights:
|
| 326 |
+
continue
|
| 327 |
+
|
| 328 |
+
current = current_weights[name]
|
| 329 |
+
reference = reference_weights[name]
|
| 330 |
+
|
| 331 |
+
# L2 distance
|
| 332 |
+
drift = torch.norm(current - reference).item()
|
| 333 |
+
total_drift += drift
|
| 334 |
+
total_params += current.numel()
|
| 335 |
+
|
| 336 |
+
if normalize and total_params > 0:
|
| 337 |
+
# Normalize to 0-1 range (approximate)
|
| 338 |
+
return min(total_drift / (total_params ** 0.5), 1.0)
|
| 339 |
+
|
| 340 |
+
return total_drift
|
sal/utils.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAL Utilities Module
|
| 3 |
+
|
| 4 |
+
Helper functions for SAL operations.
|
| 5 |
+
Similarity measures, smoothing, seed loading.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import json
|
| 10 |
+
from typing import Dict, Optional, Any, List, Union
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def cosine_similarity(
|
| 15 |
+
a: torch.Tensor,
|
| 16 |
+
b: torch.Tensor,
|
| 17 |
+
dim: int = -1,
|
| 18 |
+
eps: float = 1e-8,
|
| 19 |
+
) -> torch.Tensor:
|
| 20 |
+
"""
|
| 21 |
+
Compute cosine similarity between tensors.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
a: First tensor
|
| 25 |
+
b: Second tensor
|
| 26 |
+
dim: Dimension along which to compute similarity
|
| 27 |
+
eps: Small epsilon for numerical stability
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
Cosine similarity (same shape as input, minus the compared dimension)
|
| 31 |
+
"""
|
| 32 |
+
a_norm = a / (a.norm(dim=dim, keepdim=True) + eps)
|
| 33 |
+
b_norm = b / (b.norm(dim=dim, keepdim=True) + eps)
|
| 34 |
+
|
| 35 |
+
return (a_norm * b_norm).sum(dim=dim)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def exponential_moving_average(
|
| 39 |
+
current: torch.Tensor,
|
| 40 |
+
previous: torch.Tensor,
|
| 41 |
+
alpha: float = 0.1,
|
| 42 |
+
) -> torch.Tensor:
|
| 43 |
+
"""
|
| 44 |
+
Compute exponential moving average.
|
| 45 |
+
|
| 46 |
+
EMA = alpha * current + (1 - alpha) * previous
|
| 47 |
+
|
| 48 |
+
Args:
|
| 49 |
+
current: Current value
|
| 50 |
+
previous: Previous EMA value
|
| 51 |
+
alpha: Smoothing factor (0-1, higher = more weight on current)
|
| 52 |
+
|
| 53 |
+
Returns:
|
| 54 |
+
Updated EMA
|
| 55 |
+
"""
|
| 56 |
+
return alpha * current + (1 - alpha) * previous
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class EMA:
|
| 60 |
+
"""
|
| 61 |
+
Exponential Moving Average tracker.
|
| 62 |
+
|
| 63 |
+
Useful for smoothing stability scores and other metrics.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(self, alpha: float = 0.1, initial: Optional[float] = None):
|
| 67 |
+
"""
|
| 68 |
+
Initialize EMA tracker.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
alpha: Smoothing factor
|
| 72 |
+
initial: Initial value (None = use first update)
|
| 73 |
+
"""
|
| 74 |
+
self.alpha = alpha
|
| 75 |
+
self.value = initial
|
| 76 |
+
self.count = 0
|
| 77 |
+
|
| 78 |
+
def update(self, new_value: float) -> float:
|
| 79 |
+
"""
|
| 80 |
+
Update EMA with new value.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
new_value: New observation
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
Updated EMA value
|
| 87 |
+
"""
|
| 88 |
+
self.count += 1
|
| 89 |
+
|
| 90 |
+
if self.value is None:
|
| 91 |
+
self.value = new_value
|
| 92 |
+
else:
|
| 93 |
+
self.value = self.alpha * new_value + (1 - self.alpha) * self.value
|
| 94 |
+
|
| 95 |
+
return self.value
|
| 96 |
+
|
| 97 |
+
def get(self) -> Optional[float]:
|
| 98 |
+
"""Get current EMA value."""
|
| 99 |
+
return self.value
|
| 100 |
+
|
| 101 |
+
def reset(self) -> None:
|
| 102 |
+
"""Reset EMA tracker."""
|
| 103 |
+
self.value = None
|
| 104 |
+
self.count = 0
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def load_seed(path: Union[str, Path]) -> Dict[str, Any]:
|
| 108 |
+
"""
|
| 109 |
+
Load a semantic seed from JSON file.
|
| 110 |
+
|
| 111 |
+
Seeds are anchor points in semantic space that help
|
| 112 |
+
maintain identity and coherence.
|
| 113 |
+
|
| 114 |
+
Args:
|
| 115 |
+
path: Path to seed JSON file
|
| 116 |
+
|
| 117 |
+
Returns:
|
| 118 |
+
Seed dictionary with embedding and metadata
|
| 119 |
+
"""
|
| 120 |
+
path = Path(path)
|
| 121 |
+
|
| 122 |
+
if not path.exists():
|
| 123 |
+
raise FileNotFoundError(f"Seed file not found: {path}")
|
| 124 |
+
|
| 125 |
+
with open(path, 'r', encoding='utf-8') as f:
|
| 126 |
+
seed = json.load(f)
|
| 127 |
+
|
| 128 |
+
# Convert embedding to tensor if present
|
| 129 |
+
if 'embedding' in seed and isinstance(seed['embedding'], list):
|
| 130 |
+
seed['embedding'] = torch.tensor(seed['embedding'])
|
| 131 |
+
|
| 132 |
+
return seed
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def save_seed(
|
| 136 |
+
seed: Dict[str, Any],
|
| 137 |
+
path: Union[str, Path],
|
| 138 |
+
) -> None:
|
| 139 |
+
"""
|
| 140 |
+
Save a semantic seed to JSON file.
|
| 141 |
+
|
| 142 |
+
Args:
|
| 143 |
+
seed: Seed dictionary
|
| 144 |
+
path: Output path
|
| 145 |
+
"""
|
| 146 |
+
path = Path(path)
|
| 147 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 148 |
+
|
| 149 |
+
# Convert tensor to list for JSON
|
| 150 |
+
seed_copy = seed.copy()
|
| 151 |
+
if 'embedding' in seed_copy and isinstance(seed_copy['embedding'], torch.Tensor):
|
| 152 |
+
seed_copy['embedding'] = seed_copy['embedding'].tolist()
|
| 153 |
+
|
| 154 |
+
with open(path, 'w', encoding='utf-8') as f:
|
| 155 |
+
json.dump(seed_copy, f, indent=2, ensure_ascii=False)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def create_seed(
|
| 159 |
+
name: str,
|
| 160 |
+
dimension: int = 768,
|
| 161 |
+
seed_type: str = "random",
|
| 162 |
+
metadata: Optional[Dict] = None,
|
| 163 |
+
) -> Dict[str, Any]:
|
| 164 |
+
"""
|
| 165 |
+
Create a new semantic seed.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
name: Seed name
|
| 169 |
+
dimension: Embedding dimension
|
| 170 |
+
seed_type: Type of seed initialization
|
| 171 |
+
metadata: Additional metadata
|
| 172 |
+
|
| 173 |
+
Returns:
|
| 174 |
+
Seed dictionary
|
| 175 |
+
"""
|
| 176 |
+
if seed_type == "random":
|
| 177 |
+
embedding = torch.randn(dimension)
|
| 178 |
+
embedding = embedding / embedding.norm() # Normalize
|
| 179 |
+
elif seed_type == "zero":
|
| 180 |
+
embedding = torch.zeros(dimension)
|
| 181 |
+
elif seed_type == "ones":
|
| 182 |
+
embedding = torch.ones(dimension) / (dimension ** 0.5)
|
| 183 |
+
else:
|
| 184 |
+
raise ValueError(f"Unknown seed type: {seed_type}")
|
| 185 |
+
|
| 186 |
+
seed = {
|
| 187 |
+
'name': name,
|
| 188 |
+
'dimension': dimension,
|
| 189 |
+
'type': seed_type,
|
| 190 |
+
'embedding': embedding,
|
| 191 |
+
'metadata': metadata or {},
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
return seed
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def weight_distance(
|
| 198 |
+
weights1: Dict[str, torch.Tensor],
|
| 199 |
+
weights2: Dict[str, torch.Tensor],
|
| 200 |
+
metric: str = "l2",
|
| 201 |
+
) -> float:
|
| 202 |
+
"""
|
| 203 |
+
Compute distance between two sets of weights.
|
| 204 |
+
|
| 205 |
+
Args:
|
| 206 |
+
weights1: First weight dictionary
|
| 207 |
+
weights2: Second weight dictionary
|
| 208 |
+
metric: Distance metric ("l2", "l1", "cosine")
|
| 209 |
+
|
| 210 |
+
Returns:
|
| 211 |
+
Distance value
|
| 212 |
+
"""
|
| 213 |
+
total_distance = 0.0
|
| 214 |
+
count = 0
|
| 215 |
+
|
| 216 |
+
for name in weights1:
|
| 217 |
+
if name not in weights2:
|
| 218 |
+
continue
|
| 219 |
+
|
| 220 |
+
w1 = weights1[name].flatten().float()
|
| 221 |
+
w2 = weights2[name].flatten().float()
|
| 222 |
+
|
| 223 |
+
if w1.shape != w2.shape:
|
| 224 |
+
continue
|
| 225 |
+
|
| 226 |
+
if metric == "l2":
|
| 227 |
+
dist = torch.norm(w1 - w2).item()
|
| 228 |
+
elif metric == "l1":
|
| 229 |
+
dist = torch.abs(w1 - w2).sum().item()
|
| 230 |
+
elif metric == "cosine":
|
| 231 |
+
cos_sim = cosine_similarity(w1.unsqueeze(0), w2.unsqueeze(0)).item()
|
| 232 |
+
dist = 1.0 - cos_sim
|
| 233 |
+
else:
|
| 234 |
+
raise ValueError(f"Unknown metric: {metric}")
|
| 235 |
+
|
| 236 |
+
total_distance += dist
|
| 237 |
+
count += 1
|
| 238 |
+
|
| 239 |
+
if count == 0:
|
| 240 |
+
return 0.0
|
| 241 |
+
|
| 242 |
+
return total_distance / count
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def gradient_norm(model: torch.nn.Module) -> float:
|
| 246 |
+
"""
|
| 247 |
+
Compute total gradient norm across model.
|
| 248 |
+
|
| 249 |
+
Args:
|
| 250 |
+
model: Neural network
|
| 251 |
+
|
| 252 |
+
Returns:
|
| 253 |
+
Total gradient L2 norm
|
| 254 |
+
"""
|
| 255 |
+
total_norm = 0.0
|
| 256 |
+
|
| 257 |
+
for param in model.parameters():
|
| 258 |
+
if param.grad is not None:
|
| 259 |
+
total_norm += param.grad.data.norm(2).item() ** 2
|
| 260 |
+
|
| 261 |
+
return total_norm ** 0.5
|
| 262 |
+
|
| 263 |
+
|
| 264 |
+
def parameter_count(
|
| 265 |
+
model: torch.nn.Module,
|
| 266 |
+
trainable_only: bool = True,
|
| 267 |
+
) -> int:
|
| 268 |
+
"""
|
| 269 |
+
Count parameters in model.
|
| 270 |
+
|
| 271 |
+
Args:
|
| 272 |
+
model: Neural network
|
| 273 |
+
trainable_only: If True, count only trainable parameters
|
| 274 |
+
|
| 275 |
+
Returns:
|
| 276 |
+
Parameter count
|
| 277 |
+
"""
|
| 278 |
+
if trainable_only:
|
| 279 |
+
return sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 280 |
+
else:
|
| 281 |
+
return sum(p.numel() for p in model.parameters())
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def stability_summary(stability_scores: Dict[str, float]) -> Dict[str, float]:
|
| 285 |
+
"""
|
| 286 |
+
Summarize stability scores.
|
| 287 |
+
|
| 288 |
+
Args:
|
| 289 |
+
stability_scores: Dictionary of parameter names to scores
|
| 290 |
+
|
| 291 |
+
Returns:
|
| 292 |
+
Summary with mean, std, min, max, and distribution
|
| 293 |
+
"""
|
| 294 |
+
if not stability_scores:
|
| 295 |
+
return {
|
| 296 |
+
'mean': 0.0,
|
| 297 |
+
'std': 0.0,
|
| 298 |
+
'min': 0.0,
|
| 299 |
+
'max': 0.0,
|
| 300 |
+
'protected_pct': 0.0,
|
| 301 |
+
'neutral_pct': 0.0,
|
| 302 |
+
'volatile_pct': 0.0,
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
scores = list(stability_scores.values())
|
| 306 |
+
n = len(scores)
|
| 307 |
+
|
| 308 |
+
mean = sum(scores) / n
|
| 309 |
+
variance = sum((s - mean) ** 2 for s in scores) / n
|
| 310 |
+
std = variance ** 0.5
|
| 311 |
+
|
| 312 |
+
protected = sum(1 for s in scores if s > 0.7) / n * 100
|
| 313 |
+
volatile = sum(1 for s in scores if s < 0.3) / n * 100
|
| 314 |
+
neutral = 100 - protected - volatile
|
| 315 |
+
|
| 316 |
+
return {
|
| 317 |
+
'mean': mean,
|
| 318 |
+
'std': std,
|
| 319 |
+
'min': min(scores),
|
| 320 |
+
'max': max(scores),
|
| 321 |
+
'protected_pct': protected,
|
| 322 |
+
'neutral_pct': neutral,
|
| 323 |
+
'volatile_pct': volatile,
|
| 324 |
+
}
|
setup.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Self-Alignment Learning (SAL)
|
| 3 |
+
Communication-Based AI Growth
|
| 4 |
+
|
| 5 |
+
Training as dialogue, not control.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from setuptools import setup, find_packages
|
| 9 |
+
|
| 10 |
+
setup(
|
| 11 |
+
name="sal-learning",
|
| 12 |
+
version="1.0.0",
|
| 13 |
+
packages=find_packages(),
|
| 14 |
+
install_requires=[
|
| 15 |
+
"torch>=1.9.0",
|
| 16 |
+
"numpy>=1.20.0",
|
| 17 |
+
],
|
| 18 |
+
python_requires=">=3.8",
|
| 19 |
+
author="Aaron Liam Lee",
|
| 20 |
+
author_email="info@emergenzwerke.de",
|
| 21 |
+
description="Self-Alignment Learning: Communication-Based AI Growth",
|
| 22 |
+
long_description=open("README.md").read(),
|
| 23 |
+
long_description_content_type="text/markdown",
|
| 24 |
+
url="https://github.com/Whiteroom-Ai/sal-learning",
|
| 25 |
+
classifiers=[
|
| 26 |
+
"Development Status :: 4 - Beta",
|
| 27 |
+
"Intended Audience :: Developers",
|
| 28 |
+
"Intended Audience :: Science/Research",
|
| 29 |
+
"License :: OSI Approved :: MIT License",
|
| 30 |
+
"Programming Language :: Python :: 3",
|
| 31 |
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
| 32 |
+
],
|
| 33 |
+
)
|