has been in production two months. Accuracy is 92.9%.
Then transaction patterns shift quietly.
By the point your dashboard turns red, accuracy has collapsed to 44.6%.
Retraining takes six hours—and wishes labeled data you won’t have until next week.
What do you do in those six hours?
TL;DR
Problem: Model drifts, retraining unavailable
Solution: Self-healing adapter layer
Key idea: Update a small component, not the total model
System behavior:
- Backbone stays frozen
- Adapter updates in real time
- Updates run asynchronously (no downtime)
- Symbolic rules provide weak supervision
- Rollback ensures safety
Result: +27.8% accuracy recovery — with an explicit recall tradeoff explained inside.
This text is a couple of ReflexiveLayer: a small architectural component that sits contained in the network and adjusts to shifted distributions while the backbone stays frozen. The adapter updates in a background thread so inference never stops. Combined with a symbolic rule engine for weak supervision and a model registry for rollback, it recovered 27.8 percentage points of accuracy on this experiment without touching the backbone weights once.
The outcomes are honest: recovery is real but comes with a recall tradeoff that matters in fraud detection. Each are explained in full.
Full code, all 7 versions, production stack, monitoring export, all plots: https://github.com/Emmimal/self-healing-neural-networks/
Why standard approaches fall short here
When a model starts degrading, the everyday playbook is considered one of three things: retrain on fresh labeled data, use an ensemble that features a recently trained model, or roll back to a previous checkpoint.
All standard approaches assume you’ve something it’s possible you’ll not:
- Labeled data
- Time to retrain
- A checkpoint that works on the brand new distribution
Rollback is very misleading.
Rolling back to wash weights on a shifted distribution doesn’t fix the issue—it repeats it.
What I wanted was something that would operate within the gap: no recent labeled data, no downtime, no rollback to a distribution that now not exists. That constraint shaped the architecture.
While this experiment focuses on fraud detection, the identical constraint appears in any production system where retraining is delayed—suggestion engines, risk scoring, anomaly detection, or real-time personalization.
The architecture: one frozen backbone, one trainable adapter
The important thing design alternative is where to place the trainable capability. Fairly than making the entire network adaptable, I isolate adaptation to a single component, the ReflexiveLayer, sandwiched between the frozen backbone and the frozen output head.
Here’s the architecture in a single glance:
class ReflexiveLayer(nn.Module):
def __init__(self, dim):
super().__init__()
self.adapter = nn.Sequential(
nn.Linear(dim, dim), nn.Tanh(),
nn.Linear(dim, dim)
)
self.scale = nn.Parameter(torch.tensor(0.1))
def forward(self, x):
return x + self.scale * self.adapter(x)
The residual connection (x + self.scale * self.adapter(x)) is doing vital work here. The scale parameter starts at 0.1, so the adapter begins as a near-zero perturbation. The backbone signal passes through almost unmodified. As healing accumulates, scale can grow, but the unique backbone output is all the time present within the signal. The adapter can only add correction; it cannot overwrite what the backbone learned.
The adapter cannot overwrite the model—it may possibly only correct it.
The complete model inserts the ReflexiveLayer between the backbone and output head:
class SelfHealingMLP(nn.Module):
def __init__(self, input_dim=10, hidden_dim=64):
super().__init__()
self.backbone = nn.Sequential(
nn.Linear(input_dim, hidden_dim), nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU()
)
self.reflexive = ReflexiveLayer(hidden_dim)
self.output_head = nn.Sequential(
nn.Linear(hidden_dim, 1), nn.Sigmoid()
)
def freeze_for_healing(self):
for p in self.backbone.parameters():
p.requires_grad = False
for p in self.output_head.parameters():
p.requires_grad = False
def unfreeze_all(self):
for p in self.parameters():
p.requires_grad = True
During a heal event, freeze_for_healing() is known as first. Only the ReflexiveLayer receives gradient updates. After healing, unfreeze_all() restores the total parameter graph in case a full retrain is eventually run.
One thing price noting in regards to the parameter counts: the model has 13,250 parameters total, and the ReflexiveLayer holds 8,321 of them (two 64×64 linear layers plus the scalar scale). That’s 62.8% of the overall. The backbone, which maps 10 input features up through 64 hidden units across two layers, holds only 4,864. So the adapter just isn’t “small” in parameter count. It’s architecturally focused: its job is proscribed to remodeling the backbone’s hidden representations, and the residual connection plus frozen backbone ensure it cannot destroy what was learned during training.
The explanation this split matters: catastrophic forgetting (the tendency of neural networks to lose previously learned behavior when updated on recent data) is proscribed since the backbone is all the time frozen during healing. The gradient flow during heal steps only touches the adapter, so the foundational representations cannot degrade no matter what number of heal events occur.
Two signals that determine when to heal
Healing triggered too incessantly wastes compute. Healing triggered too late lets degradation accumulate. The system uses two independent signals.
Signal one: FIDI (Feature-based Input Distribution Inspection)
FIDI monitors the rolling mean of feature V14, the feature the network independently identified as its strongest fraud signal in Neuro-Symbolic AI Experiment. It computes a z-score against calibration statistics from training:
FIDI | μ=-0.363 σ=1.323 threshold=1.0
V14 clean | mean=-0.377 pct<-1.5 = 18.8%
V14 drift | mean=-2.261 pct<-1.5 = 77.4%
When the z-score exceeds 1.0, the incoming data now not looks just like the training distribution. On this experiment the z-score crosses the edge at batch 3 and stays elevated. The drifted V14 distribution has a mean 1.9 standard deviations below calibration, and this drift is applied as a continuing shift for all 25 batches. The system accurately detects it and never returns to HEALTHY.
Signal two: symbolic conflicts
The SymbolicRuleEngine encodes one domain rule: if V14 < -1.5, the transaction is probably going fraud. A conflict occurs when the neural network assigns a low fraud probability (below 0.30) to a transaction the rule flags. When five or more conflicts appear in a batch, a heal is triggered even with no significant z-score.
The 2 signals complement one another. FIDI is sensitive to overall distribution shift in V14’s mean. Conflict counting is sensitive to model-rule disagreement on specific samples and might catch localized degradation that a distribution-level z-score might miss. The dataset has 15.0% fraud (150 fraud transactions within the 1,000-sample test set).

Async healing: weight updates that don't interrupt inference
Probably the most production-critical design decision here is that healing never blocks inference. A background thread processes heal requests from a queue. An RLock (reentrant lock) protects the shared model state.
class AsyncHealingEngine:
def __init__(self, model):
self.model = model
self._lock = threading.RLock()
self._queue = queue.Queue()
self._worker = threading.Thread(
goal=self._heal_worker, daemon=True
)
self._worker.start()
def predict(self, X):
with self._lock: # transient lock, only a forward pass
self.model.eval()
with torch.no_grad():
return self.model(X)
def request_heal(self, X, y, symbolic, batch_idx, fraud_frac=0.0):
self._queue.put({ # non-blocking, returns immediately
"X": X.clone(), "y": y.clone(),
"symbolic": symbolic,
"batch_idx": batch_idx,
"fraud_frac": fraud_frac,
})
request_heal() returns immediately. The inference thread never waits. The heal employee picks up the job, acquires the lock, runs the gradient steps, and releases. The daemon=True flag ensures the background thread exits when the primary process terminates without leaving orphaned threads.
What happens during a heal
The heal combines three loss components into one objective:
total_loss = 0.70 * real_loss + 0.24 * consistency_loss + 0.03 * entropy
(The coefficients come from alpha=0.70 and lambda_lag=0.80, so the consistency term is (1 - 0.70) * 0.80 = 0.24.)
Real data loss (ground truth)
is weighted binary cross-entropy against the incoming batch labels. The fraud weight scales with the observed fraud fraction amongst conflicted samples:
fraud_frac = 0% -> pos_weight = 1.0 (no adjustment)
fraud_frac = 10% -> pos_weight = 2.0
fraud_frac = 20% -> pos_weight = 3.0
fraud_frac >= 30% -> pos_weight = 4.0 (cap)
The condition fraud_frac >= 0.10 acts as a gate: below that, the model adapts symmetrically. On batches where conflicted transactions develop into mostly legitimate, aggressive fraud weighting would push the adapter within the mistaken direction. This gating prevents that.
Consistency loss (symbolic guidance)
is binary cross-entropy against the symbolic rule engine’s predictions. Even without ground-truth labels, the symbolic rule provides a stable weak supervision signal that keeps the adapter aligned with domain knowledge quite than overfitting to whatever pattern happens to dominate the present batch. That is the neuro-symbolic anchor described in Hybrid Neuro-Symbolic Fraud Detection and Neuro-Symbolic AI Experiment.
Entropy minimization (confidence recovery)
(weight 0.03) pushes predictions toward more confident values. Under drift, models often turn into uncertain across many transactions quite than confidently mistaken about specific ones. Call it decision-boundary paralysis. Minimizing entropy counteracts this without dominating the opposite loss terms.
Only five gradient steps are taken per heal. A 100-sample batch just isn't enough data to soundly take large gradient steps. Five steps nudge the adapter toward the brand new distribution without committing to any single batch’s signal.
The shadow model: an honest counterfactual
Any online adaptation system needs a solution to a basic query: is the variation actually helping? To measure this, a frozen copy of the baseline model (the “shadow model”) runs in parallel every batch and never adapts. The lift metric is just:
acc_lift = healed_accuracy - shadow_accuracy
On this experiment, lift is positive on every considered one of the 25 batches, starting from +0.050 to +0.360. The shadow model provides the honest baseline: what you'll get if you happen to did nothing.

Understanding the total results truthfully
The ultimate evaluation runs on the total 1,000-sample drifted test set in spite of everything 25 streaming batches:
Stage Acc Prec Recall F1
------------------------------------------------------------------
Clean Baseline 92.9% 0.784 0.727 0.754
Under Drift, No Healing 44.6% 0.194 0.853 0.316
Shadow, Frozen 44.6% 0.194 0.853 0.316
Production Self-Healed 72.4% 0.224 0.340 0.270
The accuracy recovery is real. The healed model reaches 72.4% on data the baseline collapses on, a 27.8 percentage point improvement over any frozen alternative.
As seen within the production logs, the healed model catches fewer total frauds (Recall 0.34) but stops the ‘false positive explosion’ that happens when a drifted model loses its decision boundary.
However the recall numbers need explanation, because a naive read of this table could be misleading.
What “recall 0.853 at 44.6% accuracy” actually means
The confusion matrix for the no-healing model under drift:
No-Healing: TP=128 TN=318 FP=532 FN=22
Healed: TP=51 TN=673 FP=177 FN=99
The no-healing model catches 128 out of 150 fraud cases (recall 0.853). However it also generates 532 false positives, flagging 532 legitimate transactions as fraud. Accuracy is 44.6% because nearly half the predictions are mistaken. In a payment fraud system, 532 false positives in a 1,000-transaction batch means the model has effectively lost its decision boundary. It's flagging all the pieces suspicious. Operations teams drowning in false alarms is commonly the primary sign that a production model has drifted badly.
The healed model catches 51 out of 150 fraud cases (recall 0.340) while producing only 177 false positives. It misses more fraud, but its predictions are way more reliable.
F1 doesn't capture this tradeoff
F1 treats false positives and false negatives symmetrically. The no-healing model’s F1 is 0.316 and the healed model’s F1 is 0.270. By F1 alone, the no-healing model looks higher. But F1 doesn't account for the price structure of the issue. In most payment fraud systems, the price of a false positive (a blocked legitimate transaction) just isn't zero, and the ratio of cost between false positives and false negatives determines which model behavior is preferable.
If missing a fraud transaction costs $5,000 on average and a false positive costs $15 in customer support and churn risk, the no-healing model’s behavior may be price its 532 false positives to catch more fraud. In case your review queue has a tough capability and a false positive costs closer to $200 in operational overhead, the healed model’s 177 false positives and better accuracy are clearly higher.
The purpose is: this can be a deployment decision, not a model quality decision. The tradeoff exists since the adapter learns that V14’s shifted range isn't any longer a reliable fraud signal in isolation. That's the proper adaptation for the distribution change applied. Whether it serves your specific deployment context requires knowing your cost structure.


Model registry and rollback: the security net
Every heal event creates two snapshots: one before the heal and one after. Post-heal snapshots are tagged and form the pool of rollback candidates. The health monitor tracks a rolling window of F1 scores and compares them to a baseline established at the primary successful heal.
If rolling F1 drops greater than 8 percentage points below that baseline, the rollback engine restores the highest-F1 post-heal snapshot. It targets post-heal snapshots specifically, not the unique clean weights.
This distinction matters. In Neuro-Symbolic Fraud Detection: Catching Concept, the drift monitoring approach demonstrated that rolling back to pre-drift weights on a drifted distribution reproduces the identical failure. The very best available state is whichever post-heal snapshot performed best on the drifted data, not the clean-data baseline.
v21 | batch=10 | acc=0.710 | f1=0.408 | post-heal [BEST]
On this experiment, no rollback was triggered across 25 batches. The rollback_f1_drop threshold is ready conservatively at 0.08 and the heal quality was consistently above it. That's result but not a test of the rollback path. To exercise it deliberately: set rollback_f1_drop = 0.03 and drift_strength = 3.5. The adapter will start receiving conflicting update signals from noisy late batches, F1 will dip below the tightened threshold, and the engine will restore v21. Running this before any production deployment is worth it.


System state over time
The model moves through 4 states during a production run:
HEALTHY: no drift signal, no symbolic conflicts above threshold. No healing occurs.
DRIFTING: FIDI z-score is elevated or conflict count exceeds the minimum. Healing is triggered each batch.
HEALING: the transient state during an energetic heal event. Inference continues on the present weights until the background thread completes and the lock is released.
ROLLED_BACK: healing degraded performance beyond the configured threshold and the registry restored a previous snapshot.
On this experiment, the system is HEALTHY for batches 1 and a couple of, then enters DRIFTING at batch 3 and stays there for the rest of the run. Provided that the synthetic drift is applied as a everlasting constant shift (V14 mean moves by 1.9 standard deviations and stays there), the z-score never returns below the edge. In an actual deployment with gradual or intermittent drift, you'll expect to see more oscillation between states.

Production monitoring export
After every run, the system exports three files to monitoring_export/:
metrics.csv: one row per batch, with accuracy, F1, precision, recall, z-score, conflict count, acc lift vs shadow, and system state. This format imports directly into Grafana as a CSV data source or loads into pandas for ad-hoc evaluation.
events.json: one entry per non-trivial motion (heal triggers, rollbacks). Structured for ELK or any log aggregation system.
threshold_config.json: the present rollback thresholds in a standalone file:
{
"rollback_f1_drop": 0.08,
"rollback_acc_drop": 0.10,
"health_window": 5,
"note": "Edit values and restart to tune risk tolerance"
}
Separating thresholds into their very own file means the operations team can adjust risk tolerance without touching model code. Model owners control architecture and training parameters. Operations controls alerting and rollback thresholds. These are different decisions made by different people on different timescales.

What this approach doesn't solve
It requires not less than one symbolic rule. The consistency loss keeps the adapter from overfitting to noisy batches. Without some type of domain anchor (a rule, a soft label, a teacher model), the heal degrades to fitting the adapter on small samples with only the actual data loss, which produces unstable updates. Should you cannot express even one domain rule, this approach needs a special weak supervision source.
Recovery is bounded by the frozen backbone. The backbone learned representations from clean data. If drift is severe enough that those representations contain no useful signal, the adapter cannot compensate. On this experiment the backbone’s representations remain partially useful because V14 remains to be probably the most informative feature, just shifted in mean. A drift that introduces a wholly recent fraud mechanism the backbone never saw would exhaust what the adapter can fix. This method buys time on gradual distributional shift. It doesn't replace retraining.
The recall tradeoff is real and deployment-specific. The healed model reduces false positives substantially but misses more fraud. It is a consequence of the adapter learning that V14’s recent range isn't any longer a clean fraud signal. Whether that tradeoff is suitable will depend on your cost structure.
The rollback system was not stress-tested on this run. Zero rollbacks in 25 batches means the heal quality stayed above the configured threshold throughout. That just isn't a test of the rollback path. Exercise it explicitly before counting on it in production.
How this suits the series
Hybrid Neuro-Symbolic Fraud Detection embedded analyst-written rules directly into the training loss. The gain over a pure neural baseline was real but smaller than the framing suggested. The symbolic component helps most when training data is noisy or label-sparse.
Neural Network Learned Its Own Fraud Rules reversed the direction: let the gradient discover rules quite than having them provided. The network independently identified V14 as its strongest fraud signal without being told to search for it. That convergence between gradient findings and domain expert knowledge is what makes V14 monitoring meaningful.
Neuro-Symbolic Fraud Detection: Catching Concept Drift Before F1 Drops used learned rule activations as a drift canary, monitoring rule agreement rates to detect distribution shift before model metrics visibly declined. That article left the response query open.
This text is the response. FIDI and symbolic conflict detection trigger healing (developed in Neuro-Symbolic Fraud Detection: Catching Concept Drift Before F1 Drops). The symbolic rule provides the consistency signal during healing (the loss architecture from Hybrid Neuro-Symbolic Fraud Detection and Neural Network Learned Its Own Fraud Rules). The reflexive adapter provides the trainable capability to soak up the shift.
V14 connects all 4 articles. It appeared within the hybrid loss in Hybrid Neuro-Symbolic Fraud Detection. The gradient found it without guidance in Neural Network Learned Its Own Fraud Rules. Its distribution change was the drift canary in Neuro-Symbolic Fraud Detection: Catching Concept Drift Before F1 Drops. Here its shift is the drift being recovered from. In real fraud datasets, a small variety of features carry a lot of the discriminative signal, and people features are also those that change most meaningfully when fraud patterns evolve.
Running it yourself
The complete implementation is a single Python file that uses only a completely synthetic, generic dataset generated on-the-fly contained in the script. No external or real-world datasets are loaded. The generator creates a 10-feature tabular problem with a 15% fraud ratio and applies a controlled mean shift to 1 sensitive feature (called “V14” for continuity across the series) to simulate concept drift.
All code is accessible at: https://github.com/Emmimal/self-healing-neural-networks/
# 1. Be certain that you are in the proper directory
cd production
# 2. Install the required packages (only these three are needed)
pip install torch numpy matplotlib
# 3. Run the script
python self_healing_production_final.py
Expected runtime is under two minutes on CPU. The run generates 8 plots and the three monitoring export files in monitoring_export/.
Key Parameters
| Parameter | Default | Controls |
|---|---|---|
drift_strength |
2.2 | Strength of the simulated drift |
heal_steps |
5 | Gradient steps per healing cycle |
heal_lr |
0.003 | Learning rate for the ReflexiveLayer only |
fidi_threshold |
1.0 | Z-score threshold for drift detection |
rollback_f1_drop |
0.08 | F1 drop that triggers rollback |
conflict_min |
5 | Minimum symbolic conflicts to trigger healing |
To see the rollback system trigger: set rollback_f1_drop = 0.03 and drift_strength = 3.5. The adapter will receive conflicting update signals from noisy late batches, F1 will dip below the tightened threshold, and the rollback engine will restore the perfect post-heal snapshot (batch 10, F1=0.408). Running this deliberately is the proper solution to confirm the security net before trusting it.
Key takeaway: You don’t have to retrain the entire model to survive drift—you wish a controlled place for adaptation.
Summary
A frozen-backbone architecture with a trainable ReflexiveLayer adapter recovered 27.8 percentage points of accuracy under distribution shift, without retraining, without labeled data, and without blocking inference. The recovery comes from three combined mechanisms: the adapter absorbs the distribution shift, the symbolic rule consistency loss keeps the adapter anchored during healing, and the conditional fraud weighting scales the loss to the fraud rate observed in incoming batches.
The tradeoffs are real. Recall drops from 0.853 to 0.340 since the adapter accurately learns that V14’s shifted range isn't any longer a clean fraud signal. Whether that tradeoff is suitable will depend on the price structure of the deployment. For a system where false positive cost is high and review capability is proscribed, the healed model’s behavior is clearly preferable. For a system where missing fraud is catastrophic, the numbers need careful evaluation before deploying this approach.
The rollback and registry infrastructure, the monitoring export, and the tunable thresholds usually are not cosmetic. In a production system affecting real transactions, you wish visibility into model behavior, the flexibility to revert if healing degrades performance, and a clean separation between model tuning and operational threshold tuning. The architecture here tries to offer that infrastructure alongside the core adaptation mechanism.
What the system cannot do: recuperate from drift that makes the backbone’s representations obsolete, operate with none domain rule for weak supervision, or replace a full retrain when fraud patterns change fundamentally. It buys time on gradual distributional shift. For many production fraud systems, gradual shift is the common case.
The query isn't any longer whether models can adapt in real time. It is whether or not we're guiding that adaptation in the proper direction.
Disclosure
This text relies on independent experiments using a fully synthetic dataset generated entirely in code. No real transaction data, no external datasets, no proprietary information, and no confidential data were used at any point.
The synthetic data generator creates an easy 10-feature tabular problem with a 15% fraud ratio and applies a controlled mean shift to 1 feature to simulate concept drift. While the design draws loose inspiration from general statistical patterns commonly observed in public fraud detection benchmarks, no actual data from the ULB Credit Card Fraud dataset (Dal Pozzolo et al., 2015) — or another real dataset — was loaded, copied, or used.
All results are fully reproducible using the one Python file provided within the repository. The views and conclusions expressed listed below are my very own and don't represent any employer or organization.
GitHub: https://github.com/Emmimal/self-healing-neural-networks/
References
[1] Kirkpatrick, J., Pascanu, R., Rabinowitz, N., Veness, J., Desjardins, G., Rusu, A. A., Milan, K., Quan, J., Ramalho, T., Grabska-Barwinska, A., Hassabis, D., Clopath, C., Kumaran, D., and Hadsell, R. (2017). Overcoming catastrophic forgetting in neural networks. , 114(13), 3521-3526. https://doi.org/10.1073/pnas.1611835114
[2] Python Software Foundation. (2024). . Python 3 Documentation. https://docs.python.org/3/library/threading.html
[3] Powers, D. M. W. (2011). Evaluation: From precision, recall and F-measure to ROC, informedness, markedness and correlation. , 2(1), 37-63. https://arxiv.org/abs/2010.16061
[4] Gama, J., Zliobaite, I., Bifet, A., Pechenizkiy, M., and Bouchachia, A. (2014). A survey on concept drift adaptation. , 46(4), Article 44. https://doi.org/10.1145/2523813
[5] Lu, J., Liu, A., Dong, F., Gu, F., Gama, J., and Zhang, G. (2018). Learning under concept drift: A review. , 31(12), 2346-2363. https://doi.org/10.1109/TKDE.2018.2876857
[6] Houlsby, N., Giurgiu, A., Jastrzebski, S., Morrone, B., de Laroussilhe, Q., Gesmundo, A., Attariyan, M., and Gelly, S. (2019). Parameter-efficient transfer learning for NLP. . https://arxiv.org/abs/1902.00751
[7] Paszke, A., Gross, S., Massa, F., Lerer, A., Bradbury, J., Chanan, G., Killeen, T., Lin, Z., Gimelshein, N., Antiga, L., Desmaison, A., Kopf, A., Yang, E., DeVito, Z., Raison, M., Tejani, A., Chilamkurthy, S., Steiner, B., Fang, L., Bai, J., and Chintala, S. (2019). PyTorch: An imperative style, high-performance deep learning library. . https://arxiv.org/abs/1912.01703
