Skip to main content

Nous: A Neuro-Symbolic Library for Interpretable AI

Project description

Nous: A Neuro‑Symbolic Library for Interpretable AI

PyPI version License: MIT Python ≥3.8 PyTorch ≥2.1 GitHub Repo

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.

🚀 Key Features

  • Human‑Readable Explanations. Get clear "IF-THEN" rules that explain predictions
  • Differentiable Rule Learning. Train symbolic rules with gradient-based optimization
  • Faithful Interpretability. Honest leave‑one‑out, counterfactuals, and minimal sufficient explanations
  • Zero‑Dependency Inference. Export to pure NumPy for production deployment
  • Prototype‑Based Reasoning. Classification by similarity to learned prototypes
  • Advanced Optimizers. Specialized training for sparse, gated models

📦 Installation

Stable release (PyPI)

pip install nous

Development version (GitHub)

pip install "nous[dev,examples] @ git+https://github.com/EmotionEngineer/nous@main"

🎯 Quick Start

Training a Classification Model

from nous import NousNet
import torch

# Initialize model
model = NousNet(
    input_dim=10,
    num_outputs=3,
    task_type="classification",
    num_facts=32,
    rules_per_layer=(16, 8),
    rule_selection_method="soft_fact",
    use_prototypes=True
)

# Sample data
X = torch.randn(1000, 10)
y = torch.randint(0, 3, (1000,))

# Training
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3)
criterion = torch.nn.CrossEntropyLoss()

for epoch in range(100):
    optimizer.zero_grad()
    outputs = model(X)
    loss = criterion(outputs, y)
    loss.backward()
    optimizer.step()

Generating Explanations

from nous import generate_enhanced_explanation

# Explain a prediction
x_sample = X[0].numpy()
explanation = generate_enhanced_explanation(
    model, x_sample, y_true=int(y[0].item()),
    feature_names=[f"f{i}" for i in range(10)],
    class_names=["A", "B", "C"]
)

print(explanation)

Export for Production

from nous.export import export_numpy_inference, load_numpy_module

# Export to pure NumPy
export_numpy_inference(model, "nous_infer.py")

# Load and use in any environment
infer = load_numpy_module("nous_infer.py")
probs = infer.predict(X.numpy()[:5])

🏗️ Core Architecture

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#e8f4f8','primaryTextColor':'#1a1a1a','primaryBorderColor':'#2c5f7c','lineColor':'#4a90a4','secondaryColor':'#fef5e7','tertiaryColor':'#f0f8ff','noteTextColor':'#1a1a1a','noteBkgColor':'#fffacd','textColor':'#1a1a1a'}}}%%

graph TB
    %% Input Layer
    INPUT["<b>📥 Input Layer</b><br/>x ∈ ℝᴰ<br/><i>Raw Features</i>"]:::inputStyle
    
    %% Preprocessing
    CALIB["<b>📊 Feature Calibrators</b><br/>Monotonic splines<br/>Feature scaling & normalization<br/><i>Optional preprocessing</i>"]:::preprocessStyle
    
    %% Beta Facts
    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
    
    %% Rule Layer 1
    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
    
    GATE1["<b>🚪 Gating 1</b><br/>Soft top-k selection<br/>Budget masking<br/><i>Sparsity control</i>"]:::gateStyle
    
    AGG1["<b>∑ Aggregation 1</b><br/>Weighted sum<br/>Residual connections"]:::aggStyle
    
    %% Rule Layer 2
    RULE2["<b>⚙️ Rule Layer 2</b><br/>Higher-order combinations<br/>━━━━━━━━━━━━━━<br/>Rules over rules<br/><i>R₂ meta-rules</i>"]:::ruleStyle
    
    GATE2["<b>🚪 Gating 2</b><br/>Hierarchical pruning<br/>Confidence weighting"]:::gateStyle
    
    AGG2["<b>∑ Aggregation 2</b><br/>Final rule scores<br/>Symbolic → numeric"]:::aggStyle
    
    %% Output Heads
    HEAD_LINEAR["<b>📐 Linear Head</b><br/>W·r + b<br/><i>Regression output</i>"]:::headStyle
    
    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
    
    %% Output
    OUTPUT["<b>📤 Predictions</b><br/>ŷ ∈ ℝᴷ<br/>━━━━━━━━━━━━━━<br/>• Logits (classification)<br/>• Values (regression)<br/>+ Rule activations<br/>+ Explanation data"]:::outputStyle
    
    %% Explanation Module
    EXPLAIN["<b>💡 Explanation Engine</b><br/>━━━━━━━━━━━━━━<br/>• IF-THEN rules<br/>• Counterfactuals<br/>• Feature importance<br/>• Minimal sufficient sets<br/>• Global rule ranking"]:::explainStyle
    
    %% Connections
    INPUT --> CALIB
    CALIB --> BETA
    BETA --> RULE1
    RULE1 --> GATE1
    GATE1 --> AGG1
    AGG1 --> RULE2
    RULE2 --> GATE2
    GATE2 --> AGG2
    
    AGG2 --> HEAD_LINEAR
    AGG2 --> HEAD_PROTO
    
    HEAD_LINEAR --> OUTPUT
    HEAD_PROTO --> OUTPUT
    
    OUTPUT -.->|"Rule traces"| EXPLAIN
    RULE1 -.->|"Layer 1 rules"| EXPLAIN
    RULE2 -.->|"Layer 2 rules"| EXPLAIN
    BETA -.->|"Fact activations"| EXPLAIN
    
    %% Subgraphs
    subgraph SYMBOLIC ["<b>🧠 Symbolic Core</b>"]
        BETA
        RULE1
        RULE2
    end
    
    subgraph CONTROL ["<b>🎛️ Neural Control</b>"]
        GATE1
        GATE2
        AGG1
        AGG2
    end
    
    subgraph HEADS ["<b>🎯 Task Heads</b>"]
        HEAD_LINEAR
        HEAD_PROTO
    end
    
    %% Gradient Flow Annotation
    GRAD["<b>⚡ Gradient Flow</b><br/>End-to-end differentiable<br/>Backprop through rules"]:::gradStyle
    OUTPUT -.->|"∇Loss"| GRAD
    GRAD -.->|"∂L/∂β, ∂L/∂W"| BETA
    
    %% Styling
    classDef inputStyle fill:#e3f2fd,stroke:#1976d2,stroke-width:3px,color:#0d47a1,font-weight:bold
    classDef preprocessStyle fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#4a148c
    classDef factStyle fill:#fff3e0,stroke:#e65100,stroke-width:3px,color:#bf360c,font-weight:bold
    classDef ruleStyle fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,color:#1b5e20,font-weight:bold
    classDef gateStyle fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#880e4f
    classDef aggStyle fill:#e0f2f1,stroke:#00695c,stroke-width:2px,color:#004d40
    classDef headStyle fill:#f1f8e9,stroke:#558b2f,stroke-width:3px,color:#33691e,font-weight:bold
    classDef outputStyle fill:#e8eaf6,stroke:#283593,stroke-width:4px,color:#1a237e,font-weight:bold
    classDef explainStyle fill:#fffde7,stroke:#f9a825,stroke-width:3px,color:#f57f17,font-weight:bold
    classDef gradStyle fill:#fce4ec,stroke:#ad1457,stroke-width:2px,color:#880e4f,font-style:italic
    
    style SYMBOLIC fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px,stroke-dasharray: 5 5
    style CONTROL fill:#fff3e0,stroke:#ef6c00,stroke-width:2px,stroke-dasharray: 5 5
    style HEADS fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,stroke-dasharray: 5 5

Key Components

  • β‑Facts. Differentiable, bounded atoms defined as: βᵢ(x) = σ(kᵢ · (Lᵢx − Rᵢx − θᵢ))^νᵢ where k controls sharpness, ν controls shape, and (L, R, θ) parameterize linear predicates

  • Rule Layers. Combinators over β‑facts using AND/OR/k‑of‑n/NOT with multiple selection modes

  • Differentiable Gaters. Soft top‑k or budgeted masking over rules

  • Prototype Head. Classification by similarity to learned, L2‑normalized prototypes

📊 Performance Benchmarks

Dataset Metric Nous XGBoost EBM MLP KAN
HELOC (classification) AUC 0.7922 ± 0.0037 0.7965 ± 0.0071 0.8001 ± 0.0065 0.7910 ± 0.0045 0.7964 ± 0.0060
Accuracy 0.7199 ± 0.0063 0.7239 ± 0.0089 0.7279 ± 0.0083 0.7218 ± 0.0063 0.7252 ± 0.0073
California Housing (regression) RMSE ↓ 0.5157 ± 0.0117 0.4441 ± 0.0117 0.5500 ± 0.0131 0.5231 ± 0.0072 0.5510 ± 0.0046
R² ↑ 0.8001 ± 0.0091 0.8517 ± 0.0090 0.7726 ± 0.0107 0.7944 ± 0.0027 0.7719 ± 0.0038

Note: Nous provides state‑of‑the‑art interpretability with competitive accuracy, trading minimal performance gaps for full symbolic transparency.

🔍 Advanced Features

Minimal Sufficient Explanations

from nous.explain import minimal_sufficient_explanation

mse = minimal_sufficient_explanation(model, x_sample)
print(f"Minimal rules needed: {mse['rules_used']}")

Counterfactual Suggestions

from nous.explain import suggest_rule_counterfactuals

cf = suggest_rule_counterfactuals(model, x_sample, target_class=1)
print(f"Change {cf['feature']} from {cf['current']} to {cf['target']}")

Global Rule Analysis

from nous.explain import global_rulebook

rules = global_rulebook(model, X.numpy())
print(f"Top global rule: {rules[0]['description']}")

🗂️ Project Structure

nous/
├── model.py              # Main NousNet class
├── facts.py              # β-facts and calibrators
├── rules/                # Rule layers and gaters
├── prototypes.py         # Prototype-based head
├── explain/              # Interpretation tools
├── export/               # NumPy export
├── training/             # Training utilities
├── optim/                # Specialized optimizers
└── examples/             # Usage examples

🤝 Contributing

We welcome contributions! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/amazing-feature)
  3. Add tests and documentation
  4. Open a pull request

Bug reports, documentation improvements, and use‑case suggestions are appreciated.

📄 License

MIT License. See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nous-0.5.1.tar.gz (55.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nous-0.5.1-py3-none-any.whl (67.3 kB view details)

Uploaded Python 3

File details

Details for the file nous-0.5.1.tar.gz.

File metadata

  • Download URL: nous-0.5.1.tar.gz
  • Upload date:
  • Size: 55.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for nous-0.5.1.tar.gz
Algorithm Hash digest
SHA256 bf6f63996b03548ba1500bf9fe679aabeff2e4d96cf2a61d461163290d725563
MD5 ae20a0f0896f96a1807c335e28b1f60f
BLAKE2b-256 c3c23986cd00fbdf571d95a7ed61fde751dd4399b4c8bb13e99748b71127a873

See more details on using hashes here.

File details

Details for the file nous-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: nous-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 67.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for nous-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 510bf63a89309d82bd2e298ec439000872f8e15a068a0d91cdbd9e7e80591ae6
MD5 8a0eb5f9fb2849e6f1365071cfce550e
BLAKE2b-256 bb4b5979ac5ebace39d185d85bc35b5cf479bd3686ca5af1a2fc78a17f7e0e20

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page