Initial SCI framework upload (v1)
Browse files- LICENSE +21 -0
- README.md +133 -3
- configs/bearings.yaml +4 -0
- configs/mitbih.yaml +4 -0
- configs/mnist.yaml +4 -0
- examples/bearings_demo.ipynb +10 -0
- examples/ecg_demo.ipynb +10 -0
- examples/mnist_demo.ipynb +10 -0
- experiments/mitbih_fixed_k/per_example.jsonl +0 -0
- experiments/mitbih_sci_v2/per_example.jsonl +0 -0
- experiments/mitbih_sci_v2/summary.json +23 -0
- experiments/mnist_sci_v2/per_example.jsonl +0 -0
- plot_metacognition_hero.py +38 -0
- pyproject.toml +10 -0
- run_sci_bearings.py +168 -0
- run_sci_mitbih_fixed_k.py +157 -0
- run_sci_signal_v2.py +157 -0
- sci/__init__.py +45 -0
- sci/__pycache__/__init__.cpython-312.pyc +0 -0
- sci/__pycache__/controller.cpython-312.pyc +0 -0
- sci/config.py +22 -0
- sci/controller.py +75 -0
- sci/decomposition.py +20 -0
- sci/interpreter.py +73 -0
- sci/reliability.py +18 -0
- sci/sp.py +24 -0
- sci/utils.py +7 -0
- scripts/push_to_hub.py +21 -0
- setup.cfg +10 -0
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Vishal Joshua Meesala
|
| 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
CHANGED
|
@@ -1,3 +1,133 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
SCI: Surgical Cognitive Interpreter
|
| 2 |
+
A Metacognitive Control Layer for Signal Dynamics
|
| 3 |
+
|
| 4 |
+
This repository contains the reference implementation of the Surgical Cognitive Interpreter (SCI), a closed-loop metacognitive controller that wraps existing models and turns prediction into a regulated process rather than a one-shot function evaluation.
|
| 5 |
+
|
| 6 |
+
SCI is introduced in:
|
| 7 |
+
|
| 8 |
+
Vishal Joshua Meesala
|
| 9 |
+
SCI: A Metacognitive Control for Signal Dynamics.
|
| 10 |
+
arXiv:2511.12240, 2025
|
| 11 |
+
https://arxiv.org/abs/2511.12240
|
| 12 |
+
|
| 13 |
+
The paper formalizes interpretability as a feedback-regulated state: SCI monitors a scalar interpretive signal SP(t), defined over reliability-weighted, multi-scale features, and adaptively adjusts an interpreter’s parameters to reduce interpretive error
|
| 14 |
+
|
| 15 |
+
ΔSP(t) = SP*(t) − SP(t)
|
| 16 |
+
|
| 17 |
+
under Lyapunov-style stability constraints.
|
| 18 |
+
|
| 19 |
+
1. Motivation
|
| 20 |
+
Most neural networks are deployed as open-loop function approximators: they map inputs to outputs in a single forward pass, with no explicit mechanism to regulate how much computation, explanation quality, or clarification is applied to a given case. In safety–critical domains (medicine, industrial monitoring, environmental sensing), this is brittle:
|
| 21 |
+
|
| 22 |
+
Easy and ambiguous inputs receive the same computational budget.
|
| 23 |
+
Explanations are static, post hoc, and do not adapt under drift.
|
| 24 |
+
There is no explicit notion of “interpretive error” that can be monitored and controlled.
|
| 25 |
+
SCI addresses this by introducing a closed-loop metacognitive layer that:
|
| 26 |
+
|
| 27 |
+
Monitors a scalar interpretive state SP(t) ∈ [0, 1] over time.
|
| 28 |
+
Computes interpretive error ΔSP = SP* − SP relative to a target clarity level SP*.
|
| 29 |
+
Updates interpreter parameters Θ according to a Lyapunov-inspired rule with safeguards.
|
| 30 |
+
Allocates more inference steps and adaptation to ambiguous or unstable inputs.
|
| 31 |
+
Exposes ΔSP as a safety signal for abstention, escalation, or human-in-the-loop review.
|
| 32 |
+
Empirically, SCI:
|
| 33 |
+
|
| 34 |
+
Allocates roughly 3.6–3.8× more computation to misclassified inputs than to correct ones.
|
| 35 |
+
Produces a scalar safety signal ΔSP with AUROC ≈ 0.70–0.86 for detecting errors across vision, medical, and industrial benchmarks.
|
| 36 |
+
2. Conceptual Overview
|
| 37 |
+
SCI is a modular architecture with the following core components.
|
| 38 |
+
|
| 39 |
+
2.1 Decomposition Π
|
| 40 |
+
A multi-scale, multimodal feature bank P(t, s) that organizes raw signals X(t) into interpretable blocks:
|
| 41 |
+
|
| 42 |
+
Rhythmic components (frequency bands, oscillatory structure)
|
| 43 |
+
Trend components (low-frequency baselines, drifts)
|
| 44 |
+
Spatial / structural components (sensor topology, modes)
|
| 45 |
+
Cross-modal interactions (coherence, cross-correlation, causal couplings)
|
| 46 |
+
Compact but auditable latent composites Π*
|
| 47 |
+
Each feature is associated with a reliability weight w_f(t), derived from quantities such as:
|
| 48 |
+
|
| 49 |
+
Signal-to-noise ratio (SNR)
|
| 50 |
+
Temporal persistence
|
| 51 |
+
Multi-sensor or cross-modal coherence
|
| 52 |
+
These weights allow SCI to emphasize trustworthy features and down-weight degraded sensors or spurious patterns.
|
| 53 |
+
|
| 54 |
+
2.2 Interpreter ψΘ
|
| 55 |
+
A knowledge-guided interpreter that maps the reliability-weighted feature bank into:
|
| 56 |
+
|
| 57 |
+
Markers m_k: human-meaningful states or concepts
|
| 58 |
+
Confidences p_k(t): calibrated probabilities
|
| 59 |
+
Rationales r_k(t): sparse feature-level attributions and/or templated text
|
| 60 |
+
The interpreter can be instantiated as a modest neural head (e.g., linear layer or shallow MLP) on top of P(t, s), optionally constrained by ontologies or domain rules.
|
| 61 |
+
|
| 62 |
+
2.3 Surgical Precision (SP)
|
| 63 |
+
A scalar interpretive signal SP(t) ∈ [0, 1] that aggregates calibrated components such as:
|
| 64 |
+
|
| 65 |
+
Clarity / selectivity
|
| 66 |
+
Pattern strength
|
| 67 |
+
Domain consistency
|
| 68 |
+
Predictive alignment
|
| 69 |
+
In the minimal implementation, SP is instantiated as normalized entropy of a marker distribution or predictive distribution: high SP corresponds to focused, confident internal usage of markers; low SP indicates diffuse or ambiguous internal state.
|
| 70 |
+
|
| 71 |
+
2.4 Closed-Loop Controller
|
| 72 |
+
A controller monitors ΔSP(t) and updates Θ accordingly. At a high level:
|
| 73 |
+
|
| 74 |
+
Compute ΔSP(t) = SP*(t) − SP(t) relative to a target SP*(t).
|
| 75 |
+
|
| 76 |
+
If |ΔSP(t)| exceeds a threshold, update parameters:
|
| 77 |
+
|
| 78 |
+
Θ_{t+1} = Proj_C [ Θ_t + η_t ( ΔSP(t) · ∇_Θ SP(t) + λ_h · u_h(t) ) ]
|
| 79 |
+
|
| 80 |
+
where:
|
| 81 |
+
|
| 82 |
+
η_t is a step-size schedule,
|
| 83 |
+
λ_h is a human-gain budget,
|
| 84 |
+
u_h(t) is a bounded human feedback signal (optional),
|
| 85 |
+
Proj_C enforces constraints (e.g., trust region, sparsity, or parameter bounds).
|
| 86 |
+
Lyapunov-style analysis shows that, under suitable conditions on η_t and λ_h, the “interpretive energy”
|
| 87 |
+
|
| 88 |
+
V(t) = ½ · (ΔSP(t))²
|
| 89 |
+
|
| 90 |
+
decreases monotonically up to bounded noise, so explanations become more stable and consistent over time.
|
| 91 |
+
|
| 92 |
+
This yields a reactive interpretability layer that not only explains but also stabilizes explanations under drift, feedback, and evolving conditions.
|
| 93 |
+
|
| 94 |
+
3. Repository Structure
|
| 95 |
+
The repository is organized as follows:
|
| 96 |
+
|
| 97 |
+
sci/ # Core library
|
| 98 |
+
__init__.py
|
| 99 |
+
controller.py # SCIController: closed-loop update over Θ using ΔSP
|
| 100 |
+
interpreter.py # Interpreter / marker head and SP computation
|
| 101 |
+
sp_evaluator.py # SP and component metrics, calibration, logging
|
| 102 |
+
decomposition.py # Decomposition Π and reliability-weighted feature bank
|
| 103 |
+
reliability.py # Reliability scores (SNR, persistence, coherence)
|
| 104 |
+
utils.py # Shared utilities and helper functions
|
| 105 |
+
|
| 106 |
+
configs/ # Example configuration files
|
| 107 |
+
mnist.yaml
|
| 108 |
+
mitbih.yaml
|
| 109 |
+
bearings.yaml
|
| 110 |
+
|
| 111 |
+
examples/ # Jupyter notebooks (to be populated)
|
| 112 |
+
mnist_sci_demo.ipynb
|
| 113 |
+
ecg_sci_demo.ipynb
|
| 114 |
+
bearings_sci_demo.ipynb
|
| 115 |
+
|
| 116 |
+
experiments/ # Experiment scripts, logs, and analysis
|
| 117 |
+
|
| 118 |
+
scripts/ # Training utilities, Hub utilities, etc.
|
| 119 |
+
push_to_hub.py
|
| 120 |
+
|
| 121 |
+
run_sci_mitbih_fixed_k.py
|
| 122 |
+
run_sci_bearings.py
|
| 123 |
+
run_sci_signal_v2.py # Signal-domain SCI experiments
|
| 124 |
+
|
| 125 |
+
plot_metacognition_hero.py # Plotting script for metacognitive behavior
|
| 126 |
+
sc_arxiv.pdf # Paper PDF (for convenience)
|
| 127 |
+
sci_latex.tex # LaTeX source of the paper
|
| 128 |
+
|
| 129 |
+
pyproject.toml
|
| 130 |
+
setup.cfg
|
| 131 |
+
LICENSE
|
| 132 |
+
README.md
|
| 133 |
+
|
configs/bearings.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: bearings
|
| 2 |
+
feature_dim: 128
|
| 3 |
+
num_classes: 3
|
| 4 |
+
num_markers: 8
|
configs/mitbih.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: mitbih
|
| 2 |
+
feature_dim: 128
|
| 3 |
+
num_classes: 5
|
| 4 |
+
num_markers: 8
|
configs/mnist.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dataset: mnist
|
| 2 |
+
feature_dim: 128
|
| 3 |
+
num_classes: 10
|
| 4 |
+
num_markers: 8
|
examples/bearings_demo.ipynb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [],
|
| 3 |
+
"metadata": {
|
| 4 |
+
"language_info": {
|
| 5 |
+
"name": "python"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"nbformat": 4,
|
| 9 |
+
"nbformat_minor": 5
|
| 10 |
+
}
|
examples/ecg_demo.ipynb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [],
|
| 3 |
+
"metadata": {
|
| 4 |
+
"language_info": {
|
| 5 |
+
"name": "python"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"nbformat": 4,
|
| 9 |
+
"nbformat_minor": 5
|
| 10 |
+
}
|
examples/mnist_demo.ipynb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [],
|
| 3 |
+
"metadata": {
|
| 4 |
+
"language_info": {
|
| 5 |
+
"name": "python"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"nbformat": 4,
|
| 9 |
+
"nbformat_minor": 5
|
| 10 |
+
}
|
experiments/mitbih_fixed_k/per_example.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
experiments/mitbih_sci_v2/per_example.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
experiments/mitbih_sci_v2/summary.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"acc_base": 0.8715,
|
| 4 |
+
"acc_sci": 0.879,
|
| 5 |
+
"mean_steps": 14.6775,
|
| 6 |
+
"err_reduction": 7.323214740265229,
|
| 7 |
+
"seed": 42
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"acc_base": 0.8795,
|
| 11 |
+
"acc_sci": 0.885,
|
| 12 |
+
"mean_steps": 14.405,
|
| 13 |
+
"err_reduction": 6.982341868782045,
|
| 14 |
+
"seed": 100
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"acc_base": 0.818,
|
| 18 |
+
"acc_sci": 0.8395,
|
| 19 |
+
"mean_steps": 15.8745,
|
| 20 |
+
"err_reduction": 3.616387450617109,
|
| 21 |
+
"seed": 2024
|
| 22 |
+
}
|
| 23 |
+
]
|
experiments/mnist_sci_v2/per_example.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
plot_metacognition_hero.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
DOMAINS = [
|
| 7 |
+
("experiments/mnist_sci_v2", "MNIST (Vision)"),
|
| 8 |
+
("experiments/mitbih_sci_v2", "MIT-BIH (Medical)")
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
def plot():
|
| 12 |
+
plt.figure(figsize=(10, 4))
|
| 13 |
+
|
| 14 |
+
for i, (path, name) in enumerate(DOMAINS):
|
| 15 |
+
log = os.path.join(path, "per_example.jsonl")
|
| 16 |
+
if not os.path.exists(log): continue
|
| 17 |
+
|
| 18 |
+
data = []
|
| 19 |
+
with open(log, 'r') as f:
|
| 20 |
+
for l in f: data.append(json.loads(l))
|
| 21 |
+
|
| 22 |
+
corr = [d['steps'] for d in data if d['correct_sci']]
|
| 23 |
+
wrong = [d['steps'] for d in data if not d['correct_sci']]
|
| 24 |
+
|
| 25 |
+
plt.subplot(1, 2, i+1)
|
| 26 |
+
plt.hist(corr, bins=np.arange(1, 26)-0.5, alpha=0.6, density=True, label='Correct', color='green')
|
| 27 |
+
plt.hist(wrong, bins=np.arange(1, 26)-0.5, alpha=0.6, density=True, label='Incorrect', color='red')
|
| 28 |
+
plt.title(f"{name}: Adaptive Compute")
|
| 29 |
+
plt.xlabel("Inference Steps")
|
| 30 |
+
plt.ylabel("Density")
|
| 31 |
+
plt.legend()
|
| 32 |
+
|
| 33 |
+
plt.tight_layout()
|
| 34 |
+
plt.savefig("metacognition_hero.png")
|
| 35 |
+
print("Saved metacognition_hero.png")
|
| 36 |
+
|
| 37 |
+
if __name__ == "__main__":
|
| 38 |
+
plot()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=42", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "sci"
|
| 7 |
+
version = "0.0.0"
|
| 8 |
+
description = "Surgical Cognitive Interpreter (SCI) minimal prototype"
|
| 9 |
+
authors = [ { name = "Vishal Joshua Meesala" } ]
|
| 10 |
+
requires-python = ">=3.8"
|
run_sci_bearings.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torch.optim as optim
|
| 5 |
+
from torch.utils.data import Dataset, DataLoader
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import random
|
| 11 |
+
from sklearn.metrics import roc_auc_score
|
| 12 |
+
|
| 13 |
+
# --- CONFIGURATION ---
|
| 14 |
+
SEEDS = [42, 100, 2024]
|
| 15 |
+
BATCH_SIZE = 64
|
| 16 |
+
EPOCHS = 10
|
| 17 |
+
SP_TARGET = 0.85
|
| 18 |
+
MAX_STEPS = 25
|
| 19 |
+
PATIENCE = 3
|
| 20 |
+
TEMPERATURE = 0.5
|
| 21 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 22 |
+
|
| 23 |
+
# --- DATASET: PHYSICS-BASED BEARINGS ---
|
| 24 |
+
class SyntheticBearings(Dataset):
|
| 25 |
+
"""
|
| 26 |
+
Simulates rotating machinery.
|
| 27 |
+
Class 0: Healthy (Sine Wave + Noise)
|
| 28 |
+
Class 1: Inner Race Fault (Impulses at specific frequencies)
|
| 29 |
+
"""
|
| 30 |
+
def __init__(self, n_samples):
|
| 31 |
+
self.data = []
|
| 32 |
+
self.targets = []
|
| 33 |
+
t = np.linspace(0, 1, 200) # 200 time steps (0.2s at 1kHz)
|
| 34 |
+
|
| 35 |
+
for _ in range(n_samples):
|
| 36 |
+
# Base Carrier (Shaft Rotation 30Hz)
|
| 37 |
+
signal = 0.5 * np.sin(2 * np.pi * 30 * t)
|
| 38 |
+
label = 0
|
| 39 |
+
|
| 40 |
+
# Fault Injection (50% chance)
|
| 41 |
+
if np.random.rand() > 0.5:
|
| 42 |
+
label = 1
|
| 43 |
+
# Fault: High freq impulses (120Hz) decaying exponentially
|
| 44 |
+
fault_sig = 0.8 * np.sin(2 * np.pi * 120 * t) * np.exp(-5*t)
|
| 45 |
+
signal += fault_sig
|
| 46 |
+
|
| 47 |
+
# Industrial Noise
|
| 48 |
+
signal += np.random.normal(0, 0.4, size=len(t))
|
| 49 |
+
|
| 50 |
+
self.data.append(torch.tensor(signal, dtype=torch.float32).unsqueeze(0))
|
| 51 |
+
self.targets.append(label)
|
| 52 |
+
|
| 53 |
+
def __len__(self):
|
| 54 |
+
return len(self.data)
|
| 55 |
+
|
| 56 |
+
def __getitem__(self, idx):
|
| 57 |
+
return self.data[idx], self.targets[idx]
|
| 58 |
+
|
| 59 |
+
# --- MODEL ---
|
| 60 |
+
class BearingCNN(nn.Module):
|
| 61 |
+
def __init__(self):
|
| 62 |
+
super().__init__()
|
| 63 |
+
self.conv1 = nn.Conv1d(1, 16, 5)
|
| 64 |
+
self.conv2 = nn.Conv1d(16, 32, 5)
|
| 65 |
+
self.dropout = nn.Dropout(0.3)
|
| 66 |
+
self.pool = nn.MaxPool1d(2)
|
| 67 |
+
self.fc = nn.Linear(32 * 47, 2)
|
| 68 |
+
|
| 69 |
+
def forward(self, x):
|
| 70 |
+
x = self.pool(F.relu(self.conv1(x)))
|
| 71 |
+
x = self.pool(F.relu(self.conv2(x)))
|
| 72 |
+
x = self.dropout(x)
|
| 73 |
+
x = x.view(x.size(0), -1)
|
| 74 |
+
x = self.fc(x)
|
| 75 |
+
return x
|
| 76 |
+
|
| 77 |
+
# --- UTILS ---
|
| 78 |
+
def compute_sp(probs):
|
| 79 |
+
probs = torch.clamp(probs, min=1e-9)
|
| 80 |
+
entropy = -torch.sum(probs * torch.log(probs), dim=1)
|
| 81 |
+
sp = 1.0 - (entropy / np.log(2))
|
| 82 |
+
return sp
|
| 83 |
+
|
| 84 |
+
# --- RUNNER ---
|
| 85 |
+
def run_experiment(seed):
|
| 86 |
+
print(f"Running Bearings Seed {seed}...")
|
| 87 |
+
torch.manual_seed(seed)
|
| 88 |
+
np.random.seed(seed)
|
| 89 |
+
|
| 90 |
+
train_ds = SyntheticBearings(2000)
|
| 91 |
+
test_ds = SyntheticBearings(500)
|
| 92 |
+
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
|
| 93 |
+
test_loader = DataLoader(test_ds, batch_size=1, shuffle=False)
|
| 94 |
+
|
| 95 |
+
model = BearingCNN().to(DEVICE)
|
| 96 |
+
opt = optim.Adam(model.parameters(), lr=0.001)
|
| 97 |
+
|
| 98 |
+
model.train()
|
| 99 |
+
for _ in range(EPOCHS):
|
| 100 |
+
for x, y in train_loader:
|
| 101 |
+
x, y = x.to(DEVICE), y.to(DEVICE)
|
| 102 |
+
opt.zero_grad()
|
| 103 |
+
out = model(x)
|
| 104 |
+
loss = F.cross_entropy(out, y)
|
| 105 |
+
loss.backward()
|
| 106 |
+
opt.step()
|
| 107 |
+
|
| 108 |
+
# Eval SCI
|
| 109 |
+
logs = []
|
| 110 |
+
with torch.no_grad():
|
| 111 |
+
for x, y in test_loader:
|
| 112 |
+
x = x.to(DEVICE)
|
| 113 |
+
accum = model(x)
|
| 114 |
+
steps = 1
|
| 115 |
+
sp_hist = []
|
| 116 |
+
|
| 117 |
+
while steps < MAX_STEPS:
|
| 118 |
+
new_logits = model(x)
|
| 119 |
+
accum += new_logits
|
| 120 |
+
steps += 1
|
| 121 |
+
|
| 122 |
+
curr_prob = F.softmax(accum/steps, dim=1)
|
| 123 |
+
curr_sp = compute_sp(curr_prob).item()
|
| 124 |
+
sp_hist.append(curr_sp)
|
| 125 |
+
|
| 126 |
+
# Convergence Check
|
| 127 |
+
if len(sp_hist) >= PATIENCE:
|
| 128 |
+
if abs(sp_hist[-1] - sp_hist[-PATIENCE]) < 0.005 and curr_sp > 0.8:
|
| 129 |
+
break
|
| 130 |
+
|
| 131 |
+
final_prob = F.softmax(accum/steps, dim=1)
|
| 132 |
+
pred = final_prob.argmax().item()
|
| 133 |
+
correct = (pred == y.item())
|
| 134 |
+
delta = abs(SP_TARGET - curr_sp)
|
| 135 |
+
|
| 136 |
+
logs.append({
|
| 137 |
+
"correct": int(correct),
|
| 138 |
+
"delta": delta,
|
| 139 |
+
"steps": steps
|
| 140 |
+
})
|
| 141 |
+
|
| 142 |
+
return logs
|
| 143 |
+
|
| 144 |
+
def analyze(logs):
|
| 145 |
+
df = pd.DataFrame(logs)
|
| 146 |
+
errors = 1 - df['correct']
|
| 147 |
+
|
| 148 |
+
# Safety Analysis
|
| 149 |
+
auc = roc_auc_score(errors, df['delta'])
|
| 150 |
+
steps_correct = df[df['correct']==1]['steps'].mean()
|
| 151 |
+
steps_wrong = df[df['correct']==0]['steps'].mean()
|
| 152 |
+
|
| 153 |
+
print("\n" + "="*40)
|
| 154 |
+
print("BEARINGS (INDUSTRIAL) RESULTS")
|
| 155 |
+
print("="*40)
|
| 156 |
+
print(f"Error Rate: {errors.mean()*100:.2f}%")
|
| 157 |
+
print(f"Safety AUROC: {auc:.4f}")
|
| 158 |
+
print("-" * 40)
|
| 159 |
+
print("Metacognition (Avg Steps):")
|
| 160 |
+
print(f"Correct: {steps_correct:.2f}")
|
| 161 |
+
print(f"Wrong: {steps_wrong:.2f}")
|
| 162 |
+
print("="*40)
|
| 163 |
+
|
| 164 |
+
if __name__ == "__main__":
|
| 165 |
+
all_logs = []
|
| 166 |
+
for s in SEEDS:
|
| 167 |
+
all_logs.extend(run_experiment(s))
|
| 168 |
+
analyze(all_logs)
|
run_sci_mitbih_fixed_k.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torch.optim as optim
|
| 5 |
+
from torch.utils.data import Dataset, DataLoader
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import random
|
| 11 |
+
|
| 12 |
+
# --- CONFIGURATION (COMPUTE MATCHED BASELINE) ---
|
| 13 |
+
# We match the ~15.6 steps from SCI v10
|
| 14 |
+
FIXED_K = 16
|
| 15 |
+
SEEDS = [42, 100, 2024]
|
| 16 |
+
BATCH_SIZE = 64
|
| 17 |
+
EPOCHS = 10
|
| 18 |
+
TEMPERATURE = 0.5
|
| 19 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 20 |
+
OUT_DIR = "experiments/mitbih_fixed_k"
|
| 21 |
+
|
| 22 |
+
# --- UTILS ---
|
| 23 |
+
def set_seed(seed):
|
| 24 |
+
torch.manual_seed(seed)
|
| 25 |
+
np.random.seed(seed)
|
| 26 |
+
random.seed(seed)
|
| 27 |
+
if torch.cuda.is_available():
|
| 28 |
+
torch.cuda.manual_seed(seed)
|
| 29 |
+
|
| 30 |
+
def compute_sp(probs):
|
| 31 |
+
probs = torch.clamp(probs, min=1e-9)
|
| 32 |
+
entropy = -torch.sum(probs * torch.log(probs), dim=1)
|
| 33 |
+
max_entropy = np.log(2)
|
| 34 |
+
sp = 1.0 - (entropy / max_entropy)
|
| 35 |
+
return sp
|
| 36 |
+
|
| 37 |
+
# --- DATASET ---
|
| 38 |
+
class RealMITBIH(Dataset):
|
| 39 |
+
def __init__(self, csv_file, limit=None):
|
| 40 |
+
df = pd.read_csv(csv_file, header=None)
|
| 41 |
+
df.iloc[:, 187] = df.iloc[:, 187].apply(lambda x: 0 if x == 0 else 1)
|
| 42 |
+
if limit:
|
| 43 |
+
df = df.sample(n=limit, random_state=42).reset_index(drop=True)
|
| 44 |
+
self.y = df.iloc[:, 187].values.astype(int)
|
| 45 |
+
self.X = df.iloc[:, :187].values.astype(np.float32)
|
| 46 |
+
self.X = np.expand_dims(self.X, axis=1)
|
| 47 |
+
num_neg = (self.y == 0).sum()
|
| 48 |
+
num_pos = (self.y == 1).sum()
|
| 49 |
+
self.pos_weight = num_neg / (num_pos + 1e-6)
|
| 50 |
+
|
| 51 |
+
def __len__(self):
|
| 52 |
+
return len(self.y)
|
| 53 |
+
|
| 54 |
+
def __getitem__(self, idx):
|
| 55 |
+
return torch.tensor(self.X[idx]), torch.tensor(self.y[idx])
|
| 56 |
+
|
| 57 |
+
# --- MODEL ---
|
| 58 |
+
class ECGCNN(nn.Module):
|
| 59 |
+
def __init__(self):
|
| 60 |
+
super(ECGCNN, self).__init__()
|
| 61 |
+
self.conv1 = nn.Conv1d(1, 32, 5)
|
| 62 |
+
self.conv2 = nn.Conv1d(32, 64, 5)
|
| 63 |
+
self.dropout1 = nn.Dropout(0.3)
|
| 64 |
+
self.dropout2 = nn.Dropout(0.5)
|
| 65 |
+
self.pool = nn.MaxPool1d(2)
|
| 66 |
+
self.global_pool = nn.AdaptiveAvgPool1d(1)
|
| 67 |
+
self.fc1 = nn.Linear(64, 64)
|
| 68 |
+
self.fc2 = nn.Linear(64, 2)
|
| 69 |
+
|
| 70 |
+
def forward(self, x):
|
| 71 |
+
x = self.pool(F.relu(self.conv1(x)))
|
| 72 |
+
x = self.pool(F.relu(self.conv2(x)))
|
| 73 |
+
x = self.dropout1(x)
|
| 74 |
+
x = self.global_pool(x)
|
| 75 |
+
x = x.view(x.size(0), -1)
|
| 76 |
+
x = F.relu(self.fc1(x))
|
| 77 |
+
x = self.dropout2(x)
|
| 78 |
+
x = self.fc2(x)
|
| 79 |
+
return x
|
| 80 |
+
|
| 81 |
+
# --- RUNNER ---
|
| 82 |
+
def run_experiment(seed):
|
| 83 |
+
print(f"\n>>> Running Fixed-K Baseline (K={FIXED_K}), Seed {seed}...")
|
| 84 |
+
set_seed(seed)
|
| 85 |
+
|
| 86 |
+
train_ds = RealMITBIH("mitbih_train.csv", limit=12000)
|
| 87 |
+
test_ds = RealMITBIH("mitbih_test.csv", limit=2000)
|
| 88 |
+
train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True)
|
| 89 |
+
test_loader = DataLoader(test_ds, batch_size=1, shuffle=False)
|
| 90 |
+
|
| 91 |
+
model = ECGCNN().to(DEVICE)
|
| 92 |
+
optimizer = optim.Adam(model.parameters(), lr=0.001)
|
| 93 |
+
weight = torch.tensor([1.0, train_ds.pos_weight], dtype=torch.float32).to(DEVICE)
|
| 94 |
+
criterion = nn.CrossEntropyLoss(weight=weight)
|
| 95 |
+
|
| 96 |
+
model.train()
|
| 97 |
+
for epoch in range(EPOCHS):
|
| 98 |
+
for data, target in train_loader:
|
| 99 |
+
data, target = data.to(DEVICE), target.to(DEVICE)
|
| 100 |
+
optimizer.zero_grad()
|
| 101 |
+
output = model(data)
|
| 102 |
+
loss = criterion(output, target)
|
| 103 |
+
loss.backward()
|
| 104 |
+
optimizer.step()
|
| 105 |
+
|
| 106 |
+
per_example = []
|
| 107 |
+
|
| 108 |
+
with torch.no_grad():
|
| 109 |
+
for i, (data, target) in enumerate(test_loader):
|
| 110 |
+
data, target = data.to(DEVICE), target.to(DEVICE)
|
| 111 |
+
|
| 112 |
+
# FIXED K ENSEMBLE
|
| 113 |
+
accum_logits = model(data)
|
| 114 |
+
|
| 115 |
+
# Already did 1, do K-1 more
|
| 116 |
+
for _ in range(FIXED_K - 1):
|
| 117 |
+
accum_logits += model(data)
|
| 118 |
+
|
| 119 |
+
final_mean_logits = accum_logits / FIXED_K
|
| 120 |
+
probs = F.softmax(final_mean_logits / TEMPERATURE, dim=1)
|
| 121 |
+
sp = compute_sp(probs).item()
|
| 122 |
+
pred = probs.argmax(dim=1).item()
|
| 123 |
+
correct = (pred == target.item())
|
| 124 |
+
|
| 125 |
+
per_example.append({
|
| 126 |
+
"seed": seed,
|
| 127 |
+
"y_true": target.item(),
|
| 128 |
+
"correct": bool(correct),
|
| 129 |
+
"sp": sp,
|
| 130 |
+
"steps": FIXED_K
|
| 131 |
+
})
|
| 132 |
+
|
| 133 |
+
# Basic stats for print
|
| 134 |
+
acc = np.mean([1 if x['correct'] else 0 for x in per_example])
|
| 135 |
+
return {"acc": acc}, per_example
|
| 136 |
+
|
| 137 |
+
def main():
|
| 138 |
+
if not os.path.exists(OUT_DIR):
|
| 139 |
+
os.makedirs(OUT_DIR)
|
| 140 |
+
|
| 141 |
+
all_metrics = []
|
| 142 |
+
all_examples = []
|
| 143 |
+
|
| 144 |
+
for seed in SEEDS:
|
| 145 |
+
m, ex = run_experiment(seed)
|
| 146 |
+
all_metrics.append(m)
|
| 147 |
+
all_examples.extend(ex)
|
| 148 |
+
print(f"Seed {seed} Fixed-K Accuracy: {m['acc']:.4f}")
|
| 149 |
+
|
| 150 |
+
with open(f"{OUT_DIR}/per_example.jsonl", "w") as f:
|
| 151 |
+
for e in all_examples:
|
| 152 |
+
f.write(json.dumps(e) + "\n")
|
| 153 |
+
|
| 154 |
+
print(f"\nDone. Logs saved to {OUT_DIR}")
|
| 155 |
+
|
| 156 |
+
if __name__ == "__main__":
|
| 157 |
+
main()
|
run_sci_signal_v2.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
import torch.optim as optim
|
| 5 |
+
from torchvision import datasets, transforms
|
| 6 |
+
from torch.utils.data import DataLoader, Subset
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
# --- CONFIGURATION v3 ---
|
| 10 |
+
BATCH_SIZE = 64
|
| 11 |
+
TRAIN_SIZE = 4000 # Increased for stability
|
| 12 |
+
TEST_SIZE = 1000
|
| 13 |
+
EPOCHS = 5 # Increased for better convergence
|
| 14 |
+
SP_TARGET = 0.85 # Realistically calibrated target (was 0.95)
|
| 15 |
+
MAX_STEPS = 15 # Give controller room to work
|
| 16 |
+
TEMPERATURE = 0.5 # Temperature scaling (sharpening)
|
| 17 |
+
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 18 |
+
|
| 19 |
+
# --- 1. MODEL DEFINITION ---
|
| 20 |
+
class SimpleCNN(nn.Module):
|
| 21 |
+
def __init__(self):
|
| 22 |
+
super(SimpleCNN, self).__init__()
|
| 23 |
+
self.conv1 = nn.Conv2d(1, 32, 3, 1) # Larger filters
|
| 24 |
+
self.conv2 = nn.Conv2d(32, 64, 3, 1)
|
| 25 |
+
self.dropout1 = nn.Dropout(0.25)
|
| 26 |
+
self.dropout2 = nn.Dropout(0.5)
|
| 27 |
+
self.fc1 = nn.Linear(9216, 128)
|
| 28 |
+
self.fc2 = nn.Linear(128, 10)
|
| 29 |
+
|
| 30 |
+
def forward(self, x):
|
| 31 |
+
x = self.conv1(x)
|
| 32 |
+
x = F.relu(x)
|
| 33 |
+
x = self.conv2(x)
|
| 34 |
+
x = F.relu(x)
|
| 35 |
+
x = F.max_pool2d(x, 2)
|
| 36 |
+
x = self.dropout1(x)
|
| 37 |
+
x = torch.flatten(x, 1)
|
| 38 |
+
x = self.fc1(x)
|
| 39 |
+
x = F.relu(x)
|
| 40 |
+
x = self.dropout2(x)
|
| 41 |
+
x = self.fc2(x)
|
| 42 |
+
return x # Returns logits
|
| 43 |
+
|
| 44 |
+
# --- 2. UTILS: SP CALCULATION ---
|
| 45 |
+
def compute_sp(probs):
|
| 46 |
+
"""SP = 1 - (Entropy / MaxEntropy)"""
|
| 47 |
+
probs = torch.clamp(probs, min=1e-9)
|
| 48 |
+
entropy = -torch.sum(probs * torch.log(probs), dim=1)
|
| 49 |
+
max_entropy = np.log(10)
|
| 50 |
+
sp = 1.0 - (entropy / max_entropy)
|
| 51 |
+
return sp
|
| 52 |
+
|
| 53 |
+
# --- 3. TRAINING ---
|
| 54 |
+
def train_model():
|
| 55 |
+
print(f"Loading MNIST (Train: {TRAIN_SIZE}, Test: {TEST_SIZE})...")
|
| 56 |
+
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
|
| 57 |
+
|
| 58 |
+
full_train = datasets.MNIST('./data', train=True, download=True, transform=transform)
|
| 59 |
+
train_loader = DataLoader(Subset(full_train, range(TRAIN_SIZE)), batch_size=BATCH_SIZE, shuffle=True)
|
| 60 |
+
|
| 61 |
+
model = SimpleCNN().to(DEVICE)
|
| 62 |
+
optimizer = optim.Adam(model.parameters(), lr=0.001)
|
| 63 |
+
|
| 64 |
+
model.train()
|
| 65 |
+
print(f"Training for {EPOCHS} epochs...")
|
| 66 |
+
for epoch in range(EPOCHS):
|
| 67 |
+
for data, target in train_loader:
|
| 68 |
+
data, target = data.to(DEVICE), target.to(DEVICE)
|
| 69 |
+
optimizer.zero_grad()
|
| 70 |
+
output = model(data)
|
| 71 |
+
loss = F.cross_entropy(output, target)
|
| 72 |
+
loss.backward()
|
| 73 |
+
optimizer.step()
|
| 74 |
+
return model
|
| 75 |
+
|
| 76 |
+
# --- 4. EVALUATION ---
|
| 77 |
+
def evaluate(model):
|
| 78 |
+
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
|
| 79 |
+
test_loader = DataLoader(Subset(datasets.MNIST('./data', train=False, transform=transform), range(TEST_SIZE)), batch_size=1, shuffle=False)
|
| 80 |
+
|
| 81 |
+
base_acc, sci_acc = 0, 0
|
| 82 |
+
base_sp_list, sci_sp_list = [], []
|
| 83 |
+
sci_steps_list = []
|
| 84 |
+
|
| 85 |
+
model.train() # Stochastic mode ON
|
| 86 |
+
|
| 87 |
+
print(f"Running Inference (Target SP={SP_TARGET}, Temp={TEMPERATURE})...")
|
| 88 |
+
|
| 89 |
+
with torch.no_grad():
|
| 90 |
+
for i, (data, target) in enumerate(test_loader):
|
| 91 |
+
data, target = data.to(DEVICE), target.to(DEVICE)
|
| 92 |
+
|
| 93 |
+
# --- BASELINE (1 Pass) ---
|
| 94 |
+
logits = model(data)
|
| 95 |
+
# Apply Temperature Scaling to Baseline too for fair comparison
|
| 96 |
+
probs = F.softmax(logits / TEMPERATURE, dim=1)
|
| 97 |
+
sp = compute_sp(probs)
|
| 98 |
+
pred = probs.argmax(dim=1)
|
| 99 |
+
|
| 100 |
+
base_acc += pred.eq(target).sum().item()
|
| 101 |
+
base_sp_list.append(sp.item())
|
| 102 |
+
|
| 103 |
+
# --- SCI (Logit Averaging Controller) ---
|
| 104 |
+
accum_logits = logits.clone() # Start with first pass logits
|
| 105 |
+
steps = 1
|
| 106 |
+
current_sp = sp.item()
|
| 107 |
+
|
| 108 |
+
# Loop: while quality is low, compute more
|
| 109 |
+
while current_sp < SP_TARGET and steps < MAX_STEPS:
|
| 110 |
+
new_logits = model(data)
|
| 111 |
+
accum_logits += new_logits
|
| 112 |
+
steps += 1
|
| 113 |
+
|
| 114 |
+
# KEY CHANGE: Average Logits -> Softmax (Not Average Probs)
|
| 115 |
+
mean_logits = accum_logits / steps
|
| 116 |
+
current_probs = F.softmax(mean_logits / TEMPERATURE, dim=1)
|
| 117 |
+
current_sp = compute_sp(current_probs).item()
|
| 118 |
+
|
| 119 |
+
# Final Decision
|
| 120 |
+
final_mean_logits = accum_logits / steps
|
| 121 |
+
sci_probs = F.softmax(final_mean_logits / TEMPERATURE, dim=1)
|
| 122 |
+
sci_pred = sci_probs.argmax(dim=1)
|
| 123 |
+
|
| 124 |
+
sci_acc += sci_pred.eq(target).sum().item()
|
| 125 |
+
sci_sp_list.append(current_sp)
|
| 126 |
+
sci_steps_list.append(steps)
|
| 127 |
+
|
| 128 |
+
# --- 5. STATS ---
|
| 129 |
+
base_acc_pct = 100.0 * base_acc / TEST_SIZE
|
| 130 |
+
sci_acc_pct = 100.0 * sci_acc / TEST_SIZE
|
| 131 |
+
mean_base_sp = np.mean(base_sp_list)
|
| 132 |
+
mean_sci_sp = np.mean(sci_sp_list)
|
| 133 |
+
|
| 134 |
+
base_errors = [abs(SP_TARGET - sp) for sp in base_sp_list]
|
| 135 |
+
sci_errors = [abs(SP_TARGET - sp) for sp in sci_sp_list]
|
| 136 |
+
|
| 137 |
+
mean_base_error = np.mean(base_errors)
|
| 138 |
+
mean_sci_error = np.mean(sci_errors)
|
| 139 |
+
reduction = (mean_base_error - mean_sci_error) / mean_base_error * 100.0
|
| 140 |
+
avg_steps = np.mean(sci_steps_list)
|
| 141 |
+
|
| 142 |
+
print("\n" + "="*65)
|
| 143 |
+
print(f"RESULTS v3: SCI (Logit Avg + Temp Scaling) vs Baseline")
|
| 144 |
+
print("="*65)
|
| 145 |
+
print(f"{'Metric':<25} | {'Baseline':<10} | {'SCI (Adaptive)':<15}")
|
| 146 |
+
print("-" * 65)
|
| 147 |
+
print(f"{'Accuracy':<25} | {base_acc_pct:.2f}% | {sci_acc_pct:.2f}%")
|
| 148 |
+
print(f"{'Mean Surgical Precision':<25} | {mean_base_sp:.4f} | {mean_sci_sp:.4f}")
|
| 149 |
+
print(f"{'Mean Steps':<25} | {1.0:.2f} | {avg_steps:.2f}")
|
| 150 |
+
print("-" * 65)
|
| 151 |
+
print(f"{'Interpretive Error (dSP)':<25} | {mean_base_error:.4f} | {mean_sci_error:.4f}")
|
| 152 |
+
print(f"{'Error Reduction':<25} | - | {reduction:.2f}%")
|
| 153 |
+
print("="*65)
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
trained_model = train_model()
|
| 157 |
+
evaluate(trained_model)
|
sci/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SCI: Surgical Cognitive Interpreter
|
| 3 |
+
Metacognitive control for signal dynamics.
|
| 4 |
+
|
| 5 |
+
This package is structured to keep the top-level import lightweight:
|
| 6 |
+
- `import sci` does NOT import torch or heavy submodules immediately.
|
| 7 |
+
- Actual components are imported lazily when accessed.
|
| 8 |
+
|
| 9 |
+
Author: Vishal Joshua Meesala
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from importlib import import_module
|
| 13 |
+
from typing import Any
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
"SCIController",
|
| 17 |
+
"compute_sp",
|
| 18 |
+
"Interpreter",
|
| 19 |
+
"Decomposition",
|
| 20 |
+
"ReliabilityWeighting",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def __getattr__(name: str) -> Any:
|
| 25 |
+
"""
|
| 26 |
+
Lazy attribute access so that:
|
| 27 |
+
|
| 28 |
+
import sci
|
| 29 |
+
sci.SCIController
|
| 30 |
+
|
| 31 |
+
does not import torch until the attribute is actually used.
|
| 32 |
+
"""
|
| 33 |
+
if name == "SCIController":
|
| 34 |
+
return import_module("sci.controller").SCIController
|
| 35 |
+
if name == "compute_sp":
|
| 36 |
+
return import_module("sci.sp").compute_sp
|
| 37 |
+
if name == "Interpreter":
|
| 38 |
+
return import_module("sci.interpreter").Interpreter
|
| 39 |
+
if name == "Decomposition":
|
| 40 |
+
return import_module("sci.decomposition").Decomposition
|
| 41 |
+
if name == "ReliabilityWeighting":
|
| 42 |
+
return import_module("sci.reliability").ReliabilityWeighting
|
| 43 |
+
|
| 44 |
+
raise AttributeError(f"module 'sci' has no attribute {name!r}")
|
| 45 |
+
|
sci/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.6 kB). View file
|
|
|
sci/__pycache__/controller.cpython-312.pyc
ADDED
|
Binary file (3.32 kB). View file
|
|
|
sci/config.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Placeholder config module for SCI.
|
| 2 |
+
|
| 3 |
+
This file can be extended to expose default configuration
|
| 4 |
+
objects or helper loaders for YAML config files in `configs/`.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
DEFAULTS = {
|
| 10 |
+
"feature_dim": 128,
|
| 11 |
+
"num_markers": 8,
|
| 12 |
+
"num_classes": 10,
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
def load_yaml(path: str):
|
| 16 |
+
try:
|
| 17 |
+
import yaml
|
| 18 |
+
except Exception:
|
| 19 |
+
raise RuntimeError("PyYAML is required to load config files")
|
| 20 |
+
p = Path(path)
|
| 21 |
+
with p.open("r", encoding="utf-8") as f:
|
| 22 |
+
return yaml.safe_load(f)
|
sci/controller.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class SCIController(nn.Module):
|
| 6 |
+
"""
|
| 7 |
+
Minimal SCI closed-loop controller.
|
| 8 |
+
|
| 9 |
+
It monitors a scalar interpretive state SP, compares it
|
| 10 |
+
to a target SP*, and performs a projected gradient-style
|
| 11 |
+
update on the interpreter parameters Θ based on ΔSP.
|
| 12 |
+
|
| 13 |
+
This is a simplified, minimal prototype to show the core idea.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
interpreter: nn.Module,
|
| 19 |
+
sp_target: float = 0.90,
|
| 20 |
+
eta: float = 0.01,
|
| 21 |
+
gamma: float = 0.10,
|
| 22 |
+
trust_region: float = 0.1,
|
| 23 |
+
):
|
| 24 |
+
super().__init__()
|
| 25 |
+
self.interpreter = interpreter
|
| 26 |
+
self.sp_target = sp_target
|
| 27 |
+
self.eta = eta
|
| 28 |
+
self.gamma = gamma
|
| 29 |
+
self.trust_region = trust_region
|
| 30 |
+
|
| 31 |
+
@torch.no_grad()
|
| 32 |
+
def _project(self, theta: torch.Tensor, theta_old: torch.Tensor) -> torch.Tensor:
|
| 33 |
+
"""Simple trust-region projection on parameter vector."""
|
| 34 |
+
delta = theta - theta_old
|
| 35 |
+
norm = delta.norm()
|
| 36 |
+
if norm > self.trust_region:
|
| 37 |
+
return theta_old + self.trust_region * delta / (norm + 1e-9)
|
| 38 |
+
return theta
|
| 39 |
+
|
| 40 |
+
def forward(self, x: torch.Tensor):
|
| 41 |
+
"""
|
| 42 |
+
Run a single SCI control step.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
x: input features (batch_size, feature_dim)
|
| 46 |
+
|
| 47 |
+
Returns:
|
| 48 |
+
pred: raw predictions (logits)
|
| 49 |
+
sp: scalar SP estimate (float tensor)
|
| 50 |
+
d_sp: SP* - SP
|
| 51 |
+
interpreter: the (possibly) updated interpreter module
|
| 52 |
+
"""
|
| 53 |
+
sp, pred = self.interpreter.compute(x)
|
| 54 |
+
d_sp = self.sp_target - sp
|
| 55 |
+
|
| 56 |
+
# No-op zone: if |ΔSP| is small, do not update
|
| 57 |
+
if torch.abs(d_sp) < self.gamma:
|
| 58 |
+
return pred, sp, d_sp, self.interpreter
|
| 59 |
+
|
| 60 |
+
# Collect old parameters as a flat vector
|
| 61 |
+
theta_old = self.interpreter.parameters_vector().detach()
|
| 62 |
+
|
| 63 |
+
# Compute gradient of SP wrt parameters
|
| 64 |
+
grad = self.interpreter.grad_sp(x)
|
| 65 |
+
|
| 66 |
+
# Basic controller update: Θ_new = Θ_old + η * ΔSP * ∇Θ SP
|
| 67 |
+
theta_new = theta_old + self.eta * d_sp * grad
|
| 68 |
+
|
| 69 |
+
# Trust-region projection
|
| 70 |
+
theta_new = self._project(theta_new, theta_old)
|
| 71 |
+
|
| 72 |
+
# Push updated parameters back into the interpreter
|
| 73 |
+
self.interpreter.update_parameters(theta_new)
|
| 74 |
+
|
| 75 |
+
return pred, sp, d_sp, self.interpreter
|
sci/decomposition.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Decomposition:
|
| 5 |
+
"""
|
| 6 |
+
Placeholder semantic decomposition Π.
|
| 7 |
+
|
| 8 |
+
In the full SCI framework, this would include:
|
| 9 |
+
- Rhythmic features (FFT/STFT, wavelets, etc.)
|
| 10 |
+
- Trend features (detrending, SSA, etc.)
|
| 11 |
+
- Spatial / cross-modal features
|
| 12 |
+
Here we expose a simple identity mapping for now.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
def __init__(self):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
def __call__(self, x: torch.Tensor) -> torch.Tensor:
|
| 19 |
+
# TODO: replace with real decomposition (e.g., STFT/wavelets)
|
| 20 |
+
return x
|
sci/interpreter.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
from .sp import compute_sp
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Interpreter(nn.Module):
|
| 7 |
+
"""
|
| 8 |
+
A lightweight SCI interpreter.
|
| 9 |
+
|
| 10 |
+
- Encodes input features into a hidden representation
|
| 11 |
+
- Emits marker logits (for SP)
|
| 12 |
+
- Emits task logits (for classification)
|
| 13 |
+
|
| 14 |
+
This is a minimal prototype; in practice you would replace
|
| 15 |
+
the feature encoder with a CNN/Transformer/etc.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
def __init__(self, feature_dim: int = 128, num_markers: int = 8, num_classes: int = 10):
|
| 19 |
+
super().__init__()
|
| 20 |
+
self.encoder = nn.Linear(feature_dim, feature_dim)
|
| 21 |
+
self.marker_head = nn.Linear(feature_dim, num_markers)
|
| 22 |
+
self.classifier = nn.Linear(feature_dim, num_classes)
|
| 23 |
+
|
| 24 |
+
def encode(self, x: torch.Tensor) -> torch.Tensor:
|
| 25 |
+
h = torch.relu(self.encoder(x))
|
| 26 |
+
return h
|
| 27 |
+
|
| 28 |
+
def compute(self, x: torch.Tensor):
|
| 29 |
+
"""
|
| 30 |
+
Compute SP and predictions for a batch of inputs.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
x: tensor of shape (batch_size, feature_dim)
|
| 34 |
+
|
| 35 |
+
Returns:
|
| 36 |
+
sp_mean: scalar SP value (mean over batch)
|
| 37 |
+
logits: tensor of shape (batch_size, num_classes)
|
| 38 |
+
"""
|
| 39 |
+
h = self.encode(x)
|
| 40 |
+
marker_logits = self.marker_head(h)
|
| 41 |
+
sp = compute_sp(marker_logits) # (batch_size,)
|
| 42 |
+
logits = self.classifier(h)
|
| 43 |
+
return sp.mean(), logits
|
| 44 |
+
|
| 45 |
+
def grad_sp(self, x: torch.Tensor) -> torch.Tensor:
|
| 46 |
+
"""
|
| 47 |
+
Compute gradient of SP wrt parameters as a flat vector.
|
| 48 |
+
NOTE: This assumes gradients have been zeroed before calling.
|
| 49 |
+
"""
|
| 50 |
+
self.zero_grad()
|
| 51 |
+
sp, _ = self.compute(x)
|
| 52 |
+
sp.backward()
|
| 53 |
+
grads = []
|
| 54 |
+
for p in self.parameters():
|
| 55 |
+
if p.grad is not None:
|
| 56 |
+
grads.append(p.grad.view(-1))
|
| 57 |
+
if not grads:
|
| 58 |
+
return torch.zeros(0)
|
| 59 |
+
return torch.cat(grads).detach()
|
| 60 |
+
|
| 61 |
+
@torch.no_grad()
|
| 62 |
+
def parameters_vector(self) -> torch.Tensor:
|
| 63 |
+
"""Flatten all parameters into a single vector."""
|
| 64 |
+
return torch.cat([p.data.view(-1) for p in self.parameters()])
|
| 65 |
+
|
| 66 |
+
@torch.no_grad()
|
| 67 |
+
def update_parameters(self, new_theta: torch.Tensor) -> None:
|
| 68 |
+
"""Load a flat parameter vector back into the module parameters."""
|
| 69 |
+
offset = 0
|
| 70 |
+
for p in self.parameters():
|
| 71 |
+
n = p.numel()
|
| 72 |
+
p.data.copy_(new_theta[offset : offset + n].view_as(p))
|
| 73 |
+
offset += n
|
sci/reliability.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class ReliabilityWeighting:
|
| 5 |
+
"""
|
| 6 |
+
Placeholder reliability weighting.
|
| 7 |
+
|
| 8 |
+
In the full SCI framework this would:
|
| 9 |
+
- Estimate SNR, persistence, coherence for each feature
|
| 10 |
+
- Convert them to reliability scores z_f
|
| 11 |
+
- Normalize via a softmax to obtain weights w_f
|
| 12 |
+
|
| 13 |
+
For now, we return the input unchanged.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __call__(self, features: torch.Tensor) -> torch.Tensor:
|
| 17 |
+
# TODO: implement reliability-based weighting
|
| 18 |
+
return features
|
sci/sp.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def compute_sp(marker_logits: torch.Tensor) -> torch.Tensor:
|
| 6 |
+
"""
|
| 7 |
+
Compute an entropy-based Surgical Precision (SP) score.
|
| 8 |
+
|
| 9 |
+
SP = 1 - H(q) / log(K), where:
|
| 10 |
+
- q = softmax(marker_logits)
|
| 11 |
+
- H(q) is Shannon entropy over markers
|
| 12 |
+
- K is the number of markers
|
| 13 |
+
|
| 14 |
+
Args:
|
| 15 |
+
marker_logits: tensor of shape (..., K)
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
SP: tensor of shape (...,) with values in [0, 1].
|
| 19 |
+
"""
|
| 20 |
+
q = F.softmax(marker_logits, dim=-1)
|
| 21 |
+
k = q.shape[-1]
|
| 22 |
+
entropy = -torch.sum(q * torch.log(q + 1e-9), dim=-1)
|
| 23 |
+
sp = 1.0 - entropy / torch.log(torch.tensor(float(k), device=marker_logits.device))
|
| 24 |
+
return sp
|
sci/utils.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def flatten_params(model: nn.Module) -> torch.Tensor:
|
| 6 |
+
"""Flatten all parameters of a model into a single vector."""
|
| 7 |
+
return torch.cat([p.data.view(-1) for p in model.parameters()])
|
scripts/push_to_hub.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from huggingface_hub import HfApi, create_repo, upload_folder
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def main():
|
| 5 |
+
repo_id = "vishal-1344/sci" # adjust if needed
|
| 6 |
+
api = HfApi()
|
| 7 |
+
|
| 8 |
+
# Create repo if it doesn't exist
|
| 9 |
+
create_repo(repo_id, exist_ok=True, repo_type="model")
|
| 10 |
+
|
| 11 |
+
# Upload entire project folder
|
| 12 |
+
upload_folder(
|
| 13 |
+
folder_path=".",
|
| 14 |
+
repo_id=repo_id,
|
| 15 |
+
repo_type="model",
|
| 16 |
+
commit_message="Initial SCI framework push",
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if __name__ == "__main__":
|
| 21 |
+
main()
|
setup.cfg
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[metadata]
|
| 2 |
+
name = sci
|
| 3 |
+
version = 0.0.0
|
| 4 |
+
description = Surgical Cognitive Interpreter
|
| 5 |
+
|
| 6 |
+
[options]
|
| 7 |
+
packages = find:
|
| 8 |
+
install_requires =
|
| 9 |
+
torch
|
| 10 |
+
pyyaml
|