nous 0.2.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. nous-0.5.1/PKG-INFO +336 -0
  2. nous-0.5.1/README.md +275 -0
  3. {nous-0.2.0 → nous-0.5.1}/nous/__init__.py +23 -4
  4. {nous-0.2.0 → nous-0.5.1}/nous/explain/loo.py +16 -2
  5. {nous-0.2.0 → nous-0.5.1}/nous/explain/traces.py +11 -0
  6. {nous-0.2.0 → nous-0.5.1}/nous/export/__init__.py +1 -1
  7. {nous-0.2.0 → nous-0.5.1}/nous/export/numpy_infer.py +211 -35
  8. {nous-0.2.0 → nous-0.5.1}/nous/facts.py +50 -0
  9. {nous-0.2.0 → nous-0.5.1}/nous/model.py +44 -16
  10. nous-0.5.1/nous/optim/__init__.py +13 -0
  11. nous-0.5.1/nous/optim/base.py +47 -0
  12. nous-0.5.1/nous/optim/beta_ng.py +214 -0
  13. nous-0.5.1/nous/optim/extragrad_trust.py +175 -0
  14. nous-0.5.1/nous/optim/factory.py +77 -0
  15. nous-0.5.1/nous/optim/mirror_radam.py +190 -0
  16. nous-0.5.1/nous/optim/sam_gates.py +229 -0
  17. nous-0.5.1/nous/rules/__init__.py +30 -0
  18. nous-0.5.1/nous/rules/gaters.py +212 -0
  19. nous-0.5.1/nous/rules/soft_fact.py +132 -0
  20. nous-0.5.1/nous/utils/__init__.py +4 -0
  21. nous-0.5.1/nous/utils/calibrators.py +31 -0
  22. nous-0.5.1/nous/version.py +1 -0
  23. nous-0.5.1/nous.egg-info/PKG-INFO +336 -0
  24. {nous-0.2.0 → nous-0.5.1}/nous.egg-info/SOURCES.txt +15 -1
  25. {nous-0.2.0 → nous-0.5.1}/pyproject.toml +3 -3
  26. {nous-0.2.0 → nous-0.5.1}/setup.cfg +4 -4
  27. nous-0.5.1/tests/test_export_numpy_gaters.py +41 -0
  28. nous-0.5.1/tests/test_facts.py +26 -0
  29. {nous-0.2.0 → nous-0.5.1}/tests/test_model_forward.py +13 -1
  30. nous-0.5.1/tests/test_optim.py +180 -0
  31. nous-0.5.1/tests/test_rule_gaters.py +67 -0
  32. {nous-0.2.0 → nous-0.5.1}/tests/test_rules.py +12 -2
  33. nous-0.5.1/tests/test_utils.py +8 -0
  34. nous-0.2.0/PKG-INFO +0 -150
  35. nous-0.2.0/README.md +0 -89
  36. nous-0.2.0/nous/rules/__init__.py +0 -11
  37. nous-0.2.0/nous/utils/__init__.py +0 -3
  38. nous-0.2.0/nous/version.py +0 -1
  39. nous-0.2.0/nous.egg-info/PKG-INFO +0 -150
  40. nous-0.2.0/tests/test_facts.py +0 -16
  41. {nous-0.2.0 → nous-0.5.1}/LICENSE +0 -0
  42. {nous-0.2.0 → nous-0.5.1}/nous/data/__init__.py +0 -0
  43. {nous-0.2.0 → nous-0.5.1}/nous/data/california.py +0 -0
  44. {nous-0.2.0 → nous-0.5.1}/nous/data/wine.py +0 -0
  45. {nous-0.2.0 → nous-0.5.1}/nous/explain/__init__.py +0 -0
  46. {nous-0.2.0 → nous-0.5.1}/nous/explain/aggregator.py +0 -0
  47. {nous-0.2.0 → nous-0.5.1}/nous/explain/cf.py +0 -0
  48. {nous-0.2.0 → nous-0.5.1}/nous/explain/facts_desc.py +0 -0
  49. {nous-0.2.0 → nous-0.5.1}/nous/explain/fidelity.py +0 -0
  50. {nous-0.2.0 → nous-0.5.1}/nous/explain/generate.py +0 -0
  51. {nous-0.2.0 → nous-0.5.1}/nous/explain/global_book.py +0 -0
  52. {nous-0.2.0 → nous-0.5.1}/nous/explain/mse.py +0 -0
  53. {nous-0.2.0 → nous-0.5.1}/nous/explain/pruning.py +0 -0
  54. {nous-0.2.0 → nous-0.5.1}/nous/explain/stability.py +0 -0
  55. {nous-0.2.0 → nous-0.5.1}/nous/explain/utils.py +0 -0
  56. {nous-0.2.0 → nous-0.5.1}/nous/prototypes.py +0 -0
  57. {nous-0.2.0 → nous-0.5.1}/nous/rules/blocks.py +0 -0
  58. {nous-0.2.0 → nous-0.5.1}/nous/rules/fixed.py +0 -0
  59. {nous-0.2.0 → nous-0.5.1}/nous/rules/softmax.py +0 -0
  60. {nous-0.2.0 → nous-0.5.1}/nous/rules/sparse.py +0 -0
  61. {nous-0.2.0 → nous-0.5.1}/nous/training/__init__.py +0 -0
  62. {nous-0.2.0 → nous-0.5.1}/nous/training/evaluation.py +0 -0
  63. {nous-0.2.0 → nous-0.5.1}/nous/training/schedulers.py +0 -0
  64. {nous-0.2.0 → nous-0.5.1}/nous/training/train.py +0 -0
  65. {nous-0.2.0 → nous-0.5.1}/nous/types.py +0 -0
  66. {nous-0.2.0 → nous-0.5.1}/nous/utils/metrics.py +0 -0
  67. {nous-0.2.0 → nous-0.5.1}/nous/utils/seed.py +0 -0
  68. {nous-0.2.0 → nous-0.5.1}/nous.egg-info/dependency_links.txt +0 -0
  69. {nous-0.2.0 → nous-0.5.1}/nous.egg-info/requires.txt +0 -0
  70. {nous-0.2.0 → nous-0.5.1}/nous.egg-info/top_level.txt +0 -0
  71. {nous-0.2.0 → nous-0.5.1}/tests/test_explain_loo.py +0 -0
  72. {nous-0.2.0 → nous-0.5.1}/tests/test_export_numpy.py +0 -0
  73. {nous-0.2.0 → nous-0.5.1}/tests/test_forward_explain.py +0 -0
  74. {nous-0.2.0 → nous-0.5.1}/tests/test_prototypes.py +0 -0
nous-0.5.1/PKG-INFO ADDED
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.4
2
+ Name: nous
3
+ Version: 0.5.1
4
+ Summary: Nous: A Neuro-Symbolic Library for Interpretable AI
5
+ Author-email: Islam Tlupov <tlupovislam@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Islam Tlupov
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/EmotionEngineer/nous
29
+ Classifier: Development Status :: 4 - Beta
30
+ Classifier: Intended Audience :: Developers
31
+ Classifier: Intended Audience :: Science/Research
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3 :: Only
35
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
36
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
37
+ Classifier: Typing :: Typed
38
+ Requires-Python: >=3.8
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Requires-Dist: torch>=2.1
42
+ Requires-Dist: numpy>=1.22
43
+ Requires-Dist: pandas>=1.5
44
+ Requires-Dist: scikit-learn>=1.2
45
+ Provides-Extra: dev
46
+ Requires-Dist: pytest>=7.0; extra == "dev"
47
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
48
+ Requires-Dist: mypy>=1.5; extra == "dev"
49
+ Requires-Dist: ruff>=0.5; extra == "dev"
50
+ Requires-Dist: black>=23.0; extra == "dev"
51
+ Requires-Dist: matplotlib>=3.6; extra == "dev"
52
+ Requires-Dist: seaborn>=0.12; extra == "dev"
53
+ Requires-Dist: tqdm>=4.65; extra == "dev"
54
+ Requires-Dist: ucimlrepo>=0.0.5; extra == "dev"
55
+ Provides-Extra: examples
56
+ Requires-Dist: matplotlib>=3.6; extra == "examples"
57
+ Requires-Dist: seaborn>=0.12; extra == "examples"
58
+ Requires-Dist: tqdm>=4.65; extra == "examples"
59
+ Requires-Dist: ucimlrepo>=0.0.5; extra == "examples"
60
+ Dynamic: license-file
61
+
62
+ # Nous: A Neuro‑Symbolic Library for Interpretable AI
63
+
64
+ [![PyPI version](https://img.shields.io/pypi/v/nous.svg)](https://pypi.org/project/nous/)
65
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
66
+ [![Python ≥3.8](https://img.shields.io/badge/Python-3.8%2B-green)](https://www.python.org/)
67
+ [![PyTorch ≥2.1](https://img.shields.io/badge/PyTorch-2.1%2B-orange)](https://pytorch.org/)
68
+ [![GitHub Repo](https://img.shields.io/badge/GitHub-Repository-808080?logo=github)](https://github.com/EmotionEngineer/nous)
69
+
70
+ **Nous** (Greek: νοῦς, "mind") is a neuro‑symbolic deep learning library for building interpretable, causally transparent, and high‑performance models for classification and regression. It combines differentiable β‑facts with rule aggregation layers to produce human‑readable decision logic while retaining the benefits of gradient‑based optimization.
71
+
72
+ ## 🚀 Key Features
73
+
74
+ - **Human‑Readable Explanations**. Get clear "IF-THEN" rules that explain predictions
75
+ - **Differentiable Rule Learning**. Train symbolic rules with gradient-based optimization
76
+ - **Faithful Interpretability**. Honest leave‑one‑out, counterfactuals, and minimal sufficient explanations
77
+ - **Zero‑Dependency Inference**. Export to pure NumPy for production deployment
78
+ - **Prototype‑Based Reasoning**. Classification by similarity to learned prototypes
79
+ - **Advanced Optimizers**. Specialized training for sparse, gated models
80
+
81
+ ## 📦 Installation
82
+
83
+ **Stable release (PyPI)**
84
+ ```bash
85
+ pip install nous
86
+ ```
87
+
88
+ **Development version (GitHub)**
89
+ ```bash
90
+ pip install "nous[dev,examples] @ git+https://github.com/EmotionEngineer/nous@main"
91
+ ```
92
+
93
+ ## 🎯 Quick Start
94
+
95
+ ### Training a Classification Model
96
+
97
+ ```python
98
+ from nous import NousNet
99
+ import torch
100
+
101
+ # Initialize model
102
+ model = NousNet(
103
+ input_dim=10,
104
+ num_outputs=3,
105
+ task_type="classification",
106
+ num_facts=32,
107
+ rules_per_layer=(16, 8),
108
+ rule_selection_method="soft_fact",
109
+ use_prototypes=True
110
+ )
111
+
112
+ # Sample data
113
+ X = torch.randn(1000, 10)
114
+ y = torch.randint(0, 3, (1000,))
115
+
116
+ # Training
117
+ optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
118
+ criterion = torch.nn.CrossEntropyLoss()
119
+
120
+ for epoch in range(100):
121
+ optimizer.zero_grad()
122
+ outputs = model(X)
123
+ loss = criterion(outputs, y)
124
+ loss.backward()
125
+ optimizer.step()
126
+ ```
127
+
128
+ ### Generating Explanations
129
+
130
+ ```python
131
+ from nous import generate_enhanced_explanation
132
+
133
+ # Explain a prediction
134
+ x_sample = X[0].numpy()
135
+ explanation = generate_enhanced_explanation(
136
+ model, x_sample, y_true=int(y[0].item()),
137
+ feature_names=[f"f{i}" for i in range(10)],
138
+ class_names=["A", "B", "C"]
139
+ )
140
+
141
+ print(explanation)
142
+ ```
143
+
144
+ ### Export for Production
145
+
146
+ ```python
147
+ from nous.export import export_numpy_inference, load_numpy_module
148
+
149
+ # Export to pure NumPy
150
+ export_numpy_inference(model, "nous_infer.py")
151
+
152
+ # Load and use in any environment
153
+ infer = load_numpy_module("nous_infer.py")
154
+ probs = infer.predict(X.numpy()[:5])
155
+ ```
156
+
157
+ ## 🏗️ Core Architecture
158
+
159
+ ```mermaid
160
+ %%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#e8f4f8','primaryTextColor':'#1a1a1a','primaryBorderColor':'#2c5f7c','lineColor':'#4a90a4','secondaryColor':'#fef5e7','tertiaryColor':'#f0f8ff','noteTextColor':'#1a1a1a','noteBkgColor':'#fffacd','textColor':'#1a1a1a'}}}%%
161
+
162
+ graph TB
163
+ %% Input Layer
164
+ INPUT["<b>📥 Input Layer</b><br/>x ∈ ℝᴰ<br/><i>Raw Features</i>"]:::inputStyle
165
+
166
+ %% Preprocessing
167
+ CALIB["<b>📊 Feature Calibrators</b><br/>Monotonic splines<br/>Feature scaling & normalization<br/><i>Optional preprocessing</i>"]:::preprocessStyle
168
+
169
+ %% Beta Facts
170
+ BETA["<b>🔷 Beta-Fact Layer</b><br/>βᵢ(x) = σ(kᵢ·(Lᵢx − Rᵢx − θᵢ))^νᵢ<br/>━━━━━━━━━━━━━━<br/>• k: sharpness parameter<br/>• ν: shape exponent<br/>• L,R: feature projections<br/>• θ: threshold bias<br/><i>N differentiable atoms ∈ [0,1]</i>"]:::factStyle
171
+
172
+ %% Rule Layer 1
173
+ RULE1["<b>⚙️ Rule Layer 1</b><br/>Combinator Logic<br/>━━━━━━━━━━━━━━<br/>• AND: ∏ᵢ βᵢ<br/>• OR: 1−∏ᵢ(1−βᵢ)<br/>• k-of-n: soft threshold<br/>• NOT: 1−β<br/><i>R₁ learned rules</i>"]:::ruleStyle
174
+
175
+ GATE1["<b>🚪 Gating 1</b><br/>Soft top-k selection<br/>Budget masking<br/><i>Sparsity control</i>"]:::gateStyle
176
+
177
+ AGG1["<b>∑ Aggregation 1</b><br/>Weighted sum<br/>Residual connections"]:::aggStyle
178
+
179
+ %% Rule Layer 2
180
+ RULE2["<b>⚙️ Rule Layer 2</b><br/>Higher-order combinations<br/>━━━━━━━━━━━━━━<br/>Rules over rules<br/><i>R₂ meta-rules</i>"]:::ruleStyle
181
+
182
+ GATE2["<b>🚪 Gating 2</b><br/>Hierarchical pruning<br/>Confidence weighting"]:::gateStyle
183
+
184
+ AGG2["<b>∑ Aggregation 2</b><br/>Final rule scores<br/>Symbolic → numeric"]:::aggStyle
185
+
186
+ %% Output Heads
187
+ HEAD_LINEAR["<b>📐 Linear Head</b><br/>W·r + b<br/><i>Regression output</i>"]:::headStyle
188
+
189
+ HEAD_PROTO["<b>🎯 Prototype Head</b><br/>Similarity to prototypes<br/>━━━━━━━━━━━━━━<br/>d(r, pₖ) = ||r − pₖ||₂<br/>L2 normalization<br/><i>Classification via distance</i>"]:::headStyle
190
+
191
+ %% Output
192
+ OUTPUT["<b>📤 Predictions</b><br/>ŷ ∈ ℝᴷ<br/>━━━━━━━━━━━━━━<br/>• Logits (classification)<br/>• Values (regression)<br/>+ Rule activations<br/>+ Explanation data"]:::outputStyle
193
+
194
+ %% Explanation Module
195
+ EXPLAIN["<b>💡 Explanation Engine</b><br/>━━━━━━━━━━━━━━<br/>• IF-THEN rules<br/>• Counterfactuals<br/>• Feature importance<br/>• Minimal sufficient sets<br/>• Global rule ranking"]:::explainStyle
196
+
197
+ %% Connections
198
+ INPUT --> CALIB
199
+ CALIB --> BETA
200
+ BETA --> RULE1
201
+ RULE1 --> GATE1
202
+ GATE1 --> AGG1
203
+ AGG1 --> RULE2
204
+ RULE2 --> GATE2
205
+ GATE2 --> AGG2
206
+
207
+ AGG2 --> HEAD_LINEAR
208
+ AGG2 --> HEAD_PROTO
209
+
210
+ HEAD_LINEAR --> OUTPUT
211
+ HEAD_PROTO --> OUTPUT
212
+
213
+ OUTPUT -.->|"Rule traces"| EXPLAIN
214
+ RULE1 -.->|"Layer 1 rules"| EXPLAIN
215
+ RULE2 -.->|"Layer 2 rules"| EXPLAIN
216
+ BETA -.->|"Fact activations"| EXPLAIN
217
+
218
+ %% Subgraphs
219
+ subgraph SYMBOLIC ["<b>🧠 Symbolic Core</b>"]
220
+ BETA
221
+ RULE1
222
+ RULE2
223
+ end
224
+
225
+ subgraph CONTROL ["<b>🎛️ Neural Control</b>"]
226
+ GATE1
227
+ GATE2
228
+ AGG1
229
+ AGG2
230
+ end
231
+
232
+ subgraph HEADS ["<b>🎯 Task Heads</b>"]
233
+ HEAD_LINEAR
234
+ HEAD_PROTO
235
+ end
236
+
237
+ %% Gradient Flow Annotation
238
+ GRAD["<b>⚡ Gradient Flow</b><br/>End-to-end differentiable<br/>Backprop through rules"]:::gradStyle
239
+ OUTPUT -.->|"∇Loss"| GRAD
240
+ GRAD -.->|"∂L/∂β, ∂L/∂W"| BETA
241
+
242
+ %% Styling
243
+ classDef inputStyle fill:#e3f2fd,stroke:#1976d2,stroke-width:3px,color:#0d47a1,font-weight:bold
244
+ classDef preprocessStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c
245
+ classDef factStyle fill:#fff3e0,stroke:#e65100,stroke-width:3px,color:#bf360c,font-weight:bold
246
+ classDef ruleStyle fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,color:#1b5e20,font-weight:bold
247
+ classDef gateStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#880e4f
248
+ classDef aggStyle fill:#e0f2f1,stroke:#00695c,stroke-width:2px,color:#004d40
249
+ classDef headStyle fill:#f1f8e9,stroke:#558b2f,stroke-width:3px,color:#33691e,font-weight:bold
250
+ classDef outputStyle fill:#e8eaf6,stroke:#283593,stroke-width:4px,color:#1a237e,font-weight:bold
251
+ classDef explainStyle fill:#fffde7,stroke:#f9a825,stroke-width:3px,color:#f57f17,font-weight:bold
252
+ classDef gradStyle fill:#fce4ec,stroke:#ad1457,stroke-width:2px,color:#880e4f,font-style:italic
253
+
254
+ style SYMBOLIC fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,stroke-dasharray: 5 5
255
+ style CONTROL fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,stroke-dasharray: 5 5
256
+ style HEADS fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,stroke-dasharray: 5 5
257
+ ```
258
+
259
+ ### Key Components
260
+
261
+ - **β‑Facts**. Differentiable, bounded atoms defined as:
262
+ `βᵢ(x) = σ(kᵢ · (Lᵢx − Rᵢx − θᵢ))^νᵢ`
263
+ where `k` controls sharpness, `ν` controls shape, and `(L, R, θ)` parameterize linear predicates
264
+
265
+ - **Rule Layers**. Combinators over β‑facts using AND/OR/k‑of‑n/NOT with multiple selection modes
266
+
267
+ - **Differentiable Gaters**. Soft top‑k or budgeted masking over rules
268
+
269
+ - **Prototype Head**. Classification by similarity to learned, L2‑normalized prototypes
270
+
271
+ ## 📊 Performance Benchmarks
272
+
273
+ | Dataset | Metric | **Nous** | **XGBoost** | **EBM** | **MLP** | **KAN** |
274
+ |---------|--------|----------|-------------|---------|---------|---------|
275
+ | **HELOC** (classification) | AUC | 0.7922 ± 0.0037 | 0.7965 ± 0.0071 | 0.8001 ± 0.0065 | 0.7910 ± 0.0045 | 0.7964 ± 0.0060 |
276
+ | | Accuracy | 0.7199 ± 0.0063 | 0.7239 ± 0.0089 | 0.7279 ± 0.0083 | 0.7218 ± 0.0063 | 0.7252 ± 0.0073 |
277
+ | **California Housing** (regression) | RMSE ↓ | 0.5157 ± 0.0117 | 0.4441 ± 0.0117 | 0.5500 ± 0.0131 | 0.5231 ± 0.0072 | 0.5510 ± 0.0046 |
278
+ | | R² ↑ | 0.8001 ± 0.0091 | 0.8517 ± 0.0090 | 0.7726 ± 0.0107 | 0.7944 ± 0.0027 | 0.7719 ± 0.0038 |
279
+
280
+ *Note: Nous provides state‑of‑the‑art interpretability with competitive accuracy, trading minimal performance gaps for full symbolic transparency.*
281
+
282
+ ## 🔍 Advanced Features
283
+
284
+ ### Minimal Sufficient Explanations
285
+ ```python
286
+ from nous.explain import minimal_sufficient_explanation
287
+
288
+ mse = minimal_sufficient_explanation(model, x_sample)
289
+ print(f"Minimal rules needed: {mse['rules_used']}")
290
+ ```
291
+
292
+ ### Counterfactual Suggestions
293
+ ```python
294
+ from nous.explain import suggest_rule_counterfactuals
295
+
296
+ cf = suggest_rule_counterfactuals(model, x_sample, target_class=1)
297
+ print(f"Change {cf['feature']} from {cf['current']} to {cf['target']}")
298
+ ```
299
+
300
+ ### Global Rule Analysis
301
+ ```python
302
+ from nous.explain import global_rulebook
303
+
304
+ rules = global_rulebook(model, X.numpy())
305
+ print(f"Top global rule: {rules[0]['description']}")
306
+ ```
307
+
308
+ ## 🗂️ Project Structure
309
+
310
+ ```
311
+ nous/
312
+ ├── model.py # Main NousNet class
313
+ ├── facts.py # β-facts and calibrators
314
+ ├── rules/ # Rule layers and gaters
315
+ ├── prototypes.py # Prototype-based head
316
+ ├── explain/ # Interpretation tools
317
+ ├── export/ # NumPy export
318
+ ├── training/ # Training utilities
319
+ ├── optim/ # Specialized optimizers
320
+ └── examples/ # Usage examples
321
+ ```
322
+
323
+ ## 🤝 Contributing
324
+
325
+ We welcome contributions! Please:
326
+
327
+ 1. Fork the repository
328
+ 2. Create a feature branch (`git checkout -b feat/amazing-feature`)
329
+ 3. Add tests and documentation
330
+ 4. Open a pull request
331
+
332
+ Bug reports, documentation improvements, and use‑case suggestions are appreciated.
333
+
334
+ ## 📄 License
335
+
336
+ MIT License. See [LICENSE](https://github.com/EmotionEngineer/nous/blob/main/LICENSE) for details.
nous-0.5.1/README.md ADDED
@@ -0,0 +1,275 @@
1
+ # Nous: A Neuro‑Symbolic Library for Interpretable AI
2
+
3
+ [![PyPI version](https://img.shields.io/pypi/v/nous.svg)](https://pypi.org/project/nous/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Python ≥3.8](https://img.shields.io/badge/Python-3.8%2B-green)](https://www.python.org/)
6
+ [![PyTorch ≥2.1](https://img.shields.io/badge/PyTorch-2.1%2B-orange)](https://pytorch.org/)
7
+ [![GitHub Repo](https://img.shields.io/badge/GitHub-Repository-808080?logo=github)](https://github.com/EmotionEngineer/nous)
8
+
9
+ **Nous** (Greek: νοῦς, "mind") is a neuro‑symbolic deep learning library for building interpretable, causally transparent, and high‑performance models for classification and regression. It combines differentiable β‑facts with rule aggregation layers to produce human‑readable decision logic while retaining the benefits of gradient‑based optimization.
10
+
11
+ ## 🚀 Key Features
12
+
13
+ - **Human‑Readable Explanations**. Get clear "IF-THEN" rules that explain predictions
14
+ - **Differentiable Rule Learning**. Train symbolic rules with gradient-based optimization
15
+ - **Faithful Interpretability**. Honest leave‑one‑out, counterfactuals, and minimal sufficient explanations
16
+ - **Zero‑Dependency Inference**. Export to pure NumPy for production deployment
17
+ - **Prototype‑Based Reasoning**. Classification by similarity to learned prototypes
18
+ - **Advanced Optimizers**. Specialized training for sparse, gated models
19
+
20
+ ## 📦 Installation
21
+
22
+ **Stable release (PyPI)**
23
+ ```bash
24
+ pip install nous
25
+ ```
26
+
27
+ **Development version (GitHub)**
28
+ ```bash
29
+ pip install "nous[dev,examples] @ git+https://github.com/EmotionEngineer/nous@main"
30
+ ```
31
+
32
+ ## 🎯 Quick Start
33
+
34
+ ### Training a Classification Model
35
+
36
+ ```python
37
+ from nous import NousNet
38
+ import torch
39
+
40
+ # Initialize model
41
+ model = NousNet(
42
+ input_dim=10,
43
+ num_outputs=3,
44
+ task_type="classification",
45
+ num_facts=32,
46
+ rules_per_layer=(16, 8),
47
+ rule_selection_method="soft_fact",
48
+ use_prototypes=True
49
+ )
50
+
51
+ # Sample data
52
+ X = torch.randn(1000, 10)
53
+ y = torch.randint(0, 3, (1000,))
54
+
55
+ # Training
56
+ optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
57
+ criterion = torch.nn.CrossEntropyLoss()
58
+
59
+ for epoch in range(100):
60
+ optimizer.zero_grad()
61
+ outputs = model(X)
62
+ loss = criterion(outputs, y)
63
+ loss.backward()
64
+ optimizer.step()
65
+ ```
66
+
67
+ ### Generating Explanations
68
+
69
+ ```python
70
+ from nous import generate_enhanced_explanation
71
+
72
+ # Explain a prediction
73
+ x_sample = X[0].numpy()
74
+ explanation = generate_enhanced_explanation(
75
+ model, x_sample, y_true=int(y[0].item()),
76
+ feature_names=[f"f{i}" for i in range(10)],
77
+ class_names=["A", "B", "C"]
78
+ )
79
+
80
+ print(explanation)
81
+ ```
82
+
83
+ ### Export for Production
84
+
85
+ ```python
86
+ from nous.export import export_numpy_inference, load_numpy_module
87
+
88
+ # Export to pure NumPy
89
+ export_numpy_inference(model, "nous_infer.py")
90
+
91
+ # Load and use in any environment
92
+ infer = load_numpy_module("nous_infer.py")
93
+ probs = infer.predict(X.numpy()[:5])
94
+ ```
95
+
96
+ ## 🏗️ Core Architecture
97
+
98
+ ```mermaid
99
+ %%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#e8f4f8','primaryTextColor':'#1a1a1a','primaryBorderColor':'#2c5f7c','lineColor':'#4a90a4','secondaryColor':'#fef5e7','tertiaryColor':'#f0f8ff','noteTextColor':'#1a1a1a','noteBkgColor':'#fffacd','textColor':'#1a1a1a'}}}%%
100
+
101
+ graph TB
102
+ %% Input Layer
103
+ INPUT["<b>📥 Input Layer</b><br/>x ∈ ℝᴰ<br/><i>Raw Features</i>"]:::inputStyle
104
+
105
+ %% Preprocessing
106
+ CALIB["<b>📊 Feature Calibrators</b><br/>Monotonic splines<br/>Feature scaling & normalization<br/><i>Optional preprocessing</i>"]:::preprocessStyle
107
+
108
+ %% Beta Facts
109
+ BETA["<b>🔷 Beta-Fact Layer</b><br/>βᵢ(x) = σ(kᵢ·(Lᵢx − Rᵢx − θᵢ))^νᵢ<br/>━━━━━━━━━━━━━━<br/>• k: sharpness parameter<br/>• ν: shape exponent<br/>• L,R: feature projections<br/>• θ: threshold bias<br/><i>N differentiable atoms ∈ [0,1]</i>"]:::factStyle
110
+
111
+ %% Rule Layer 1
112
+ RULE1["<b>⚙️ Rule Layer 1</b><br/>Combinator Logic<br/>━━━━━━━━━━━━━━<br/>• AND: ∏ᵢ βᵢ<br/>• OR: 1−∏ᵢ(1−βᵢ)<br/>• k-of-n: soft threshold<br/>• NOT: 1−β<br/><i>R₁ learned rules</i>"]:::ruleStyle
113
+
114
+ GATE1["<b>🚪 Gating 1</b><br/>Soft top-k selection<br/>Budget masking<br/><i>Sparsity control</i>"]:::gateStyle
115
+
116
+ AGG1["<b>∑ Aggregation 1</b><br/>Weighted sum<br/>Residual connections"]:::aggStyle
117
+
118
+ %% Rule Layer 2
119
+ RULE2["<b>⚙️ Rule Layer 2</b><br/>Higher-order combinations<br/>━━━━━━━━━━━━━━<br/>Rules over rules<br/><i>R₂ meta-rules</i>"]:::ruleStyle
120
+
121
+ GATE2["<b>🚪 Gating 2</b><br/>Hierarchical pruning<br/>Confidence weighting"]:::gateStyle
122
+
123
+ AGG2["<b>∑ Aggregation 2</b><br/>Final rule scores<br/>Symbolic → numeric"]:::aggStyle
124
+
125
+ %% Output Heads
126
+ HEAD_LINEAR["<b>📐 Linear Head</b><br/>W·r + b<br/><i>Regression output</i>"]:::headStyle
127
+
128
+ HEAD_PROTO["<b>🎯 Prototype Head</b><br/>Similarity to prototypes<br/>━━━━━━━━━━━━━━<br/>d(r, pₖ) = ||r − pₖ||₂<br/>L2 normalization<br/><i>Classification via distance</i>"]:::headStyle
129
+
130
+ %% Output
131
+ OUTPUT["<b>📤 Predictions</b><br/>ŷ ∈ ℝᴷ<br/>━━━━━━━━━━━━━━<br/>• Logits (classification)<br/>• Values (regression)<br/>+ Rule activations<br/>+ Explanation data"]:::outputStyle
132
+
133
+ %% Explanation Module
134
+ EXPLAIN["<b>💡 Explanation Engine</b><br/>━━━━━━━━━━━━━━<br/>• IF-THEN rules<br/>• Counterfactuals<br/>• Feature importance<br/>• Minimal sufficient sets<br/>• Global rule ranking"]:::explainStyle
135
+
136
+ %% Connections
137
+ INPUT --> CALIB
138
+ CALIB --> BETA
139
+ BETA --> RULE1
140
+ RULE1 --> GATE1
141
+ GATE1 --> AGG1
142
+ AGG1 --> RULE2
143
+ RULE2 --> GATE2
144
+ GATE2 --> AGG2
145
+
146
+ AGG2 --> HEAD_LINEAR
147
+ AGG2 --> HEAD_PROTO
148
+
149
+ HEAD_LINEAR --> OUTPUT
150
+ HEAD_PROTO --> OUTPUT
151
+
152
+ OUTPUT -.->|"Rule traces"| EXPLAIN
153
+ RULE1 -.->|"Layer 1 rules"| EXPLAIN
154
+ RULE2 -.->|"Layer 2 rules"| EXPLAIN
155
+ BETA -.->|"Fact activations"| EXPLAIN
156
+
157
+ %% Subgraphs
158
+ subgraph SYMBOLIC ["<b>🧠 Symbolic Core</b>"]
159
+ BETA
160
+ RULE1
161
+ RULE2
162
+ end
163
+
164
+ subgraph CONTROL ["<b>🎛️ Neural Control</b>"]
165
+ GATE1
166
+ GATE2
167
+ AGG1
168
+ AGG2
169
+ end
170
+
171
+ subgraph HEADS ["<b>🎯 Task Heads</b>"]
172
+ HEAD_LINEAR
173
+ HEAD_PROTO
174
+ end
175
+
176
+ %% Gradient Flow Annotation
177
+ GRAD["<b>⚡ Gradient Flow</b><br/>End-to-end differentiable<br/>Backprop through rules"]:::gradStyle
178
+ OUTPUT -.->|"∇Loss"| GRAD
179
+ GRAD -.->|"∂L/∂β, ∂L/∂W"| BETA
180
+
181
+ %% Styling
182
+ classDef inputStyle fill:#e3f2fd,stroke:#1976d2,stroke-width:3px,color:#0d47a1,font-weight:bold
183
+ classDef preprocessStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c
184
+ classDef factStyle fill:#fff3e0,stroke:#e65100,stroke-width:3px,color:#bf360c,font-weight:bold
185
+ classDef ruleStyle fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,color:#1b5e20,font-weight:bold
186
+ classDef gateStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#880e4f
187
+ classDef aggStyle fill:#e0f2f1,stroke:#00695c,stroke-width:2px,color:#004d40
188
+ classDef headStyle fill:#f1f8e9,stroke:#558b2f,stroke-width:3px,color:#33691e,font-weight:bold
189
+ classDef outputStyle fill:#e8eaf6,stroke:#283593,stroke-width:4px,color:#1a237e,font-weight:bold
190
+ classDef explainStyle fill:#fffde7,stroke:#f9a825,stroke-width:3px,color:#f57f17,font-weight:bold
191
+ classDef gradStyle fill:#fce4ec,stroke:#ad1457,stroke-width:2px,color:#880e4f,font-style:italic
192
+
193
+ style SYMBOLIC fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,stroke-dasharray: 5 5
194
+ style CONTROL fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,stroke-dasharray: 5 5
195
+ style HEADS fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,stroke-dasharray: 5 5
196
+ ```
197
+
198
+ ### Key Components
199
+
200
+ - **β‑Facts**. Differentiable, bounded atoms defined as:
201
+ `βᵢ(x) = σ(kᵢ · (Lᵢx − Rᵢx − θᵢ))^νᵢ`
202
+ where `k` controls sharpness, `ν` controls shape, and `(L, R, θ)` parameterize linear predicates
203
+
204
+ - **Rule Layers**. Combinators over β‑facts using AND/OR/k‑of‑n/NOT with multiple selection modes
205
+
206
+ - **Differentiable Gaters**. Soft top‑k or budgeted masking over rules
207
+
208
+ - **Prototype Head**. Classification by similarity to learned, L2‑normalized prototypes
209
+
210
+ ## 📊 Performance Benchmarks
211
+
212
+ | Dataset | Metric | **Nous** | **XGBoost** | **EBM** | **MLP** | **KAN** |
213
+ |---------|--------|----------|-------------|---------|---------|---------|
214
+ | **HELOC** (classification) | AUC | 0.7922 ± 0.0037 | 0.7965 ± 0.0071 | 0.8001 ± 0.0065 | 0.7910 ± 0.0045 | 0.7964 ± 0.0060 |
215
+ | | Accuracy | 0.7199 ± 0.0063 | 0.7239 ± 0.0089 | 0.7279 ± 0.0083 | 0.7218 ± 0.0063 | 0.7252 ± 0.0073 |
216
+ | **California Housing** (regression) | RMSE ↓ | 0.5157 ± 0.0117 | 0.4441 ± 0.0117 | 0.5500 ± 0.0131 | 0.5231 ± 0.0072 | 0.5510 ± 0.0046 |
217
+ | | R² ↑ | 0.8001 ± 0.0091 | 0.8517 ± 0.0090 | 0.7726 ± 0.0107 | 0.7944 ± 0.0027 | 0.7719 ± 0.0038 |
218
+
219
+ *Note: Nous provides state‑of‑the‑art interpretability with competitive accuracy, trading minimal performance gaps for full symbolic transparency.*
220
+
221
+ ## 🔍 Advanced Features
222
+
223
+ ### Minimal Sufficient Explanations
224
+ ```python
225
+ from nous.explain import minimal_sufficient_explanation
226
+
227
+ mse = minimal_sufficient_explanation(model, x_sample)
228
+ print(f"Minimal rules needed: {mse['rules_used']}")
229
+ ```
230
+
231
+ ### Counterfactual Suggestions
232
+ ```python
233
+ from nous.explain import suggest_rule_counterfactuals
234
+
235
+ cf = suggest_rule_counterfactuals(model, x_sample, target_class=1)
236
+ print(f"Change {cf['feature']} from {cf['current']} to {cf['target']}")
237
+ ```
238
+
239
+ ### Global Rule Analysis
240
+ ```python
241
+ from nous.explain import global_rulebook
242
+
243
+ rules = global_rulebook(model, X.numpy())
244
+ print(f"Top global rule: {rules[0]['description']}")
245
+ ```
246
+
247
+ ## 🗂️ Project Structure
248
+
249
+ ```
250
+ nous/
251
+ ├── model.py # Main NousNet class
252
+ ├── facts.py # β-facts and calibrators
253
+ ├── rules/ # Rule layers and gaters
254
+ ├── prototypes.py # Prototype-based head
255
+ ├── explain/ # Interpretation tools
256
+ ├── export/ # NumPy export
257
+ ├── training/ # Training utilities
258
+ ├── optim/ # Specialized optimizers
259
+ └── examples/ # Usage examples
260
+ ```
261
+
262
+ ## 🤝 Contributing
263
+
264
+ We welcome contributions! Please:
265
+
266
+ 1. Fork the repository
267
+ 2. Create a feature branch (`git checkout -b feat/amazing-feature`)
268
+ 3. Add tests and documentation
269
+ 4. Open a pull request
270
+
271
+ Bug reports, documentation improvements, and use‑case suggestions are appreciated.
272
+
273
+ ## 📄 License
274
+
275
+ MIT License. See [LICENSE](https://github.com/EmotionEngineer/nous/blob/main/LICENSE) for details.
@@ -1,8 +1,17 @@
1
1
  from .version import __version__
2
2
  from .model import NousNet
3
- from .facts import BetaFactLayer, PiecewiseLinearCalibrator
3
+ from .facts import BetaFactLayer, PiecewiseLinearCalibrator, PiecewiseLinearCalibratorQuantile
4
4
  from .prototypes import ScaledPrototypeLayer
5
- from .rules import FixedPairRuleLayer, SoftmaxRuleLayer, SparseRuleLayer, SimpleNousBlock
5
+ from .rules import FixedPairRuleLayer, SoftmaxRuleLayer, SparseRuleLayer, SoftFactRuleLayer, SimpleNousBlock
6
+ from .rules import (
7
+ BaseRuleGater,
8
+ GateSoftRank,
9
+ GateLogisticThresholdExactK,
10
+ GateCappedSimplex,
11
+ GateSparsemaxK,
12
+ GateHardConcreteBudget,
13
+ make_rule_gater,
14
+ )
6
15
 
7
16
  # Explainability (core API)
8
17
  from .explain import (
@@ -51,7 +60,7 @@ from .training import (
51
60
  from .data import get_wine_data, get_california_housing_data
52
61
 
53
62
  # Utilities
54
- from .utils import set_global_seed
63
+ from .utils import set_global_seed, make_quantile_calibrators
55
64
 
56
65
  __all__ = [
57
66
  "__version__",
@@ -63,7 +72,16 @@ __all__ = [
63
72
  "FixedPairRuleLayer",
64
73
  "SoftmaxRuleLayer",
65
74
  "SparseRuleLayer",
75
+ "SoftFactRuleLayer",
66
76
  "SimpleNousBlock",
77
+ # Differentiable rule gaters
78
+ "BaseRuleGater",
79
+ "GateSoftRank",
80
+ "GateLogisticThresholdExactK",
81
+ "GateCappedSimplex",
82
+ "GateSparsemaxK",
83
+ "GateHardConcreteBudget",
84
+ "make_rule_gater",
67
85
  # Explainability (core)
68
86
  "rule_impact_df",
69
87
  "minimal_sufficient_explanation",
@@ -100,4 +118,5 @@ __all__ = [
100
118
  "get_california_housing_data",
101
119
  # Utilities
102
120
  "set_global_seed",
103
- ]
121
+ "make_quantile_calibrators",
122
+ ]