AbstractIntegratedModule 0.3.9__tar.gz → 0.4.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.
- abstractintegratedmodule-0.4.1/AbstractIntegratedModule.cp313-win_amd64.pyd +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.cpython-310-aarch64-linux-gnu.so +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.cpython-312-x86_64-linux-gnu.so +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1/AbstractIntegratedModule.egg-info}/PKG-INFO +3 -3
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.py +406 -252
- {abstractintegratedmodule-0.3.9/AbstractIntegratedModule.egg-info → abstractintegratedmodule-0.4.1}/PKG-INFO +3 -3
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/README.md +2 -2
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/setup.py +1 -1
- abstractintegratedmodule-0.3.9/AbstractIntegratedModule.cp313-win_amd64.pyd +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/SOURCES.txt +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/dependency_links.txt +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/requires.txt +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/top_level.txt +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/MANIFEST.in +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/pyproject.toml +0 -0
- {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/setup.cfg +0 -0
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AbstractIntegratedModule
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Framework for Advanced Integrated Non-LLM AI Module library - Backend Framework for Non-LLM AI Agent Framework
|
|
5
5
|
Author: Micro-Novelty
|
|
6
6
|
Author-email: hernikpuspita5@gmail.com
|
|
@@ -42,7 +42,7 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
|
|
|
42
42
|
#### Note: The README here you are reading is a direct copy from my README Repository, to download the necessary files, you can visit my Repository with the provided link above.
|
|
43
43
|
|
|
44
44
|
### Library Short Description:
|
|
45
|
-
- Development Stage: Beta, 0.
|
|
45
|
+
- Development Stage: Beta, 0.4.1.
|
|
46
46
|
- Maintainer: Micro-Novelty.
|
|
47
47
|
- library Source-Code is Open-sourced on github.
|
|
48
48
|
- Purpose: Specifically Designed for providing Non-LLM AI Agent Framework for edge Devices, Optimized for ARM64 architecture.
|
|
@@ -62,7 +62,7 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
|
|
|
62
62
|
- Proven Works on ARM64 Environment, Training and Prediction works efficient on Docker ARM64 environment with QEMU, good parallelizing behavior is guaranteed.
|
|
63
63
|
- P2P Works efficiently in ARM64 Docker + QEMU, No conflicting socket and all prediction works efficiently.
|
|
64
64
|
- AWE setup Proven Efficient on Hard-uncontrolled dataset such as Activity Recognition from the given Database.
|
|
65
|
-
|
|
65
|
+
- LSTM is Optimized efficiently for scarce data with AWE method.
|
|
66
66
|
-----
|
|
67
67
|
|
|
68
68
|
<img width="1280" height="600" alt="WhatsApp Image 2026-05-27 at 07 16 32" src="https://github.com/user-attachments/assets/4b58a556-45a3-419b-96fd-9c1b76cac574" />
|
{abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.py
RENAMED
|
@@ -295,26 +295,25 @@ class Singleton(metaclass=SingletonMeta):
|
|
|
295
295
|
# allowing it to better process data with varying geometric complexity, and providing a more stable training process in scarce data environment.
|
|
296
296
|
# It can be used as a general weight initialization and shaping method for various models, especially in scenarios where data geometry is complex and data is scarce.
|
|
297
297
|
|
|
298
|
-
|
|
299
298
|
class GeometricWeightShaping:
|
|
300
299
|
def __init__(self, input_size, output_size):
|
|
301
300
|
self.input_size = input_size
|
|
302
301
|
self.output_size = output_size
|
|
303
|
-
|
|
302
|
+
|
|
304
303
|
|
|
305
304
|
def eigenvalue_encoder(self, x):
|
|
306
305
|
eps = 1e-5
|
|
307
306
|
raw_X = np.asarray(x)
|
|
307
|
+
AME = self.AME_Encoder(raw_X)
|
|
308
|
+
AMR = 1.0 / (1.0 + np.exp(-AME)) + eps
|
|
309
|
+
mag = np.mean(np.linalg.norm(raw_X, axis=-1))
|
|
310
|
+
|
|
308
311
|
if raw_X.ndim > 2:
|
|
309
312
|
raw_X = raw_X.reshape(raw_X.shape[0], -1)
|
|
310
313
|
|
|
311
|
-
mag = np.mean(np.linalg.norm(raw_X, axis=-1))
|
|
312
|
-
if np.isnan(mag) or np.isinf(mag):
|
|
313
|
-
mag = self.AME_Encoder(raw_X)
|
|
314
|
-
|
|
315
314
|
anisotropy = self.anisotropy_measurement(raw_X)
|
|
316
315
|
|
|
317
|
-
structured_noise = np.random.uniform(
|
|
316
|
+
structured_noise = np.random.uniform(0, mag, size=raw_X.shape)
|
|
318
317
|
X = np.vstack((raw_X, structured_noise))
|
|
319
318
|
if X.ndim == 2 and X.shape[1] == 1:
|
|
320
319
|
X = np.hstack((raw_X, structured_noise))
|
|
@@ -322,22 +321,25 @@ class GeometricWeightShaping:
|
|
|
322
321
|
cov = np.cov(X, rowvar=False)
|
|
323
322
|
eigenvalues, eigenvectors = np.linalg.eigh(cov)
|
|
324
323
|
idx = np.argsort(eigenvalues)[::-1]
|
|
325
|
-
|
|
326
324
|
eigenvalues = eigenvalues[idx]
|
|
327
|
-
energy = np.cumsum(eigenvalues) / np.sum(eigenvalues)
|
|
328
|
-
k = np.searchsorted(energy, 0.90) + 1
|
|
329
325
|
|
|
330
|
-
|
|
331
|
-
|
|
326
|
+
energy = np.cumsum(eigenvalues) / np.sum(eigenvalues)
|
|
327
|
+
energy_sigmoid_growth = 1.0 / (1.0 + np.exp(-energy))
|
|
328
|
+
energy_consistency = np.std(energy_sigmoid_growth)
|
|
329
|
+
k = np.searchsorted(energy, 0.90) + 1 # +1 converts 0-based index to count
|
|
332
330
|
|
|
333
331
|
trA = k / (1.0 - anisotropy) + eps
|
|
334
|
-
trB = (1/2 +
|
|
335
|
-
trC = (1/6 +
|
|
332
|
+
trB = (1/2 + energy_consistency) / (1.0 + trA**2)
|
|
333
|
+
trC = (1/6 + AMR) / (1.0 - trB**2) + eps
|
|
336
334
|
|
|
337
335
|
if np.isnan(trC) or np.isinf(trC):
|
|
338
|
-
trC = anisotropy * (
|
|
336
|
+
trC = anisotropy * (trB**2 - 1.0) + eps
|
|
337
|
+
if np.isnan(trC) or np.isinf(trC):
|
|
338
|
+
trC = (1.0 - AMR)
|
|
339
339
|
|
|
340
|
-
|
|
340
|
+
min_val = min(trC, 0)
|
|
341
|
+
max_val = max(trC, 0)
|
|
342
|
+
floating_point = np.random.uniform(min_val, max_val, size=X.shape)
|
|
341
343
|
return k, floating_point, structured_noise
|
|
342
344
|
|
|
343
345
|
|
|
@@ -407,23 +409,25 @@ class GeometricWeightShaping:
|
|
|
407
409
|
k, floating_point, structured_noise = self.eigenvalue_encoder(x)
|
|
408
410
|
AME = self.AME_Encoder(x)
|
|
409
411
|
AMR = 1.0 / (1.0 + np.exp(-AME)) # abstract modelling rate
|
|
410
|
-
|
|
411
412
|
|
|
412
413
|
spectral_similarity = self.spectral_similarity(x, floating_point, structured_noise)
|
|
413
414
|
|
|
414
415
|
AEL = (0.3 + spectral_similarity + eps) * anisotropy
|
|
415
416
|
scaled_anisotropy = anisotropy / (anisotropy + 1.0)
|
|
417
|
+
|
|
418
|
+
abstraction_efficiency = (1.0 + AEL) * (1.0 - AMR)
|
|
416
419
|
|
|
417
|
-
|
|
418
|
-
if np.isnan(
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
floating_context = rng.uniform(1e-10, efficient_distributed_energy, size=(input_size, output_size))
|
|
422
|
-
self.floating_context = floating_context
|
|
420
|
+
abstraction_efficiency = k + AEL * (1.0 - AMR)
|
|
421
|
+
if np.isnan(abstraction_efficiency) or np.isinf(abstraction_efficiency):
|
|
422
|
+
abstraction_efficiency = (1 - AMR) + eps
|
|
423
423
|
|
|
424
|
-
|
|
424
|
+
abstract_context = rng.uniform(0, abstraction_efficiency, size=(input_size, output_size))
|
|
425
|
+
return abstract_context
|
|
425
426
|
|
|
426
427
|
def weight_shaping(self, x, type=None):
|
|
428
|
+
if np.isnan(x).any() or np.isinf(x).any():
|
|
429
|
+
x = np.nan_to_num(x, nan=0.0, posinf=1e99, neginf=-1e99)
|
|
430
|
+
|
|
427
431
|
if isinstance(x, list):
|
|
428
432
|
x = np.asarray(x)
|
|
429
433
|
|
|
@@ -433,11 +437,12 @@ class GeometricWeightShaping:
|
|
|
433
437
|
if np.std(x) == 0:
|
|
434
438
|
x = np.random.uniform(0, 1, size=x.shape)
|
|
435
439
|
|
|
436
|
-
|
|
437
|
-
if np.isnan(
|
|
438
|
-
|
|
440
|
+
abstract_context = self.abstract_weight_shaping(x)
|
|
441
|
+
if np.isnan(abstract_context).any() or not np.isfinite(abstract_context).any():
|
|
442
|
+
abstract_context = np.ones_like(x)
|
|
443
|
+
|
|
444
|
+
return abstract_context
|
|
439
445
|
|
|
440
|
-
return floating_context
|
|
441
446
|
|
|
442
447
|
|
|
443
448
|
# ________ UTILITY functions for activations and losses, can be used across different models and architectures _________
|
|
@@ -1437,96 +1442,101 @@ class LSTMCell:
|
|
|
1437
1442
|
def _g(self, v): return v[2*self.hidden_size:3*self.hidden_size]
|
|
1438
1443
|
def _o(self, v): return v[3*self.hidden_size:]
|
|
1439
1444
|
|
|
1440
|
-
|
|
1441
|
-
# ── forward ──────────────────────────────
|
|
1445
|
+
# _________ forward method for cell class _____________
|
|
1442
1446
|
def forward(self, x_seq: np.ndarray, h0=None, c0=None):
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
returns: hs (T, hidden), cs (T, hidden), cache (for BPTT)
|
|
1446
|
-
"""
|
|
1447
|
-
T = x_seq.shape[0]
|
|
1448
|
-
H = self.hidden_size
|
|
1447
|
+
T = x_seq.shape[0]
|
|
1448
|
+
H = self.hidden_size
|
|
1449
1449
|
expected_input = self.input_size
|
|
1450
1450
|
|
|
1451
1451
|
h = np.zeros(H) if h0 is None else h0.copy()
|
|
1452
1452
|
c = np.zeros(H) if c0 is None else c0.copy()
|
|
1453
1453
|
|
|
1454
|
-
hs
|
|
1455
|
-
|
|
1454
|
+
hs = np.zeros((T, H))
|
|
1455
|
+
cs = np.zeros((T, H))
|
|
1456
|
+
cache = []
|
|
1457
|
+
|
|
1458
|
+
# preallocate xh buffer once
|
|
1459
|
+
xh = np.empty(expected_input + H)
|
|
1456
1460
|
|
|
1457
1461
|
for t in range(T):
|
|
1458
|
-
x
|
|
1462
|
+
x = x_seq[t]
|
|
1459
1463
|
if x.ndim == 0:
|
|
1460
|
-
x = x.reshape(1)
|
|
1464
|
+
x = x.reshape(1)
|
|
1461
1465
|
if x.shape[0] < expected_input:
|
|
1462
|
-
x = np.pad(x, (0, expected_input - x.shape[0]))
|
|
1466
|
+
x = np.pad(x, (0, expected_input - x.shape[0]))
|
|
1463
1467
|
elif x.shape[0] > expected_input:
|
|
1464
|
-
x = x[:expected_input]
|
|
1465
|
-
|
|
1466
|
-
xh = np.concatenate([x, h]) # (input+hidden,)
|
|
1468
|
+
x = x[:expected_input]
|
|
1467
1469
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
g = np.tanh(self._g(z)) # candidate
|
|
1472
|
-
o = sigmoid(self._o(z)) # output
|
|
1470
|
+
# write into buffer, no allocation
|
|
1471
|
+
xh[:expected_input] = x
|
|
1472
|
+
xh[expected_input:] = h
|
|
1473
1473
|
|
|
1474
|
-
|
|
1474
|
+
z = self.W @ xh + self.b
|
|
1475
|
+
H1, H2, H3 = H, H * 2, H * 3
|
|
1476
|
+
|
|
1477
|
+
# direct slices, no method calls
|
|
1478
|
+
f = sigmoid(z[:H1])
|
|
1479
|
+
i = sigmoid(z[H1:H2])
|
|
1480
|
+
g = np.tanh(z[H2:H3])
|
|
1481
|
+
o = sigmoid(z[H3:])
|
|
1482
|
+
|
|
1483
|
+
c_new = f * c + i * g
|
|
1475
1484
|
tanh_c = np.tanh(c_new)
|
|
1476
|
-
h_new
|
|
1485
|
+
h_new = o * tanh_c
|
|
1477
1486
|
|
|
1478
|
-
|
|
1487
|
+
# store copies — h/c will be overwritten next iteration
|
|
1488
|
+
cache.append((x.copy(), h.copy(), c.copy(),
|
|
1489
|
+
f, i, g, o, c_new, tanh_c, xh.copy()))
|
|
1479
1490
|
h, c = h_new, c_new
|
|
1480
|
-
hs[t]
|
|
1491
|
+
hs[t] = h
|
|
1492
|
+
cs[t] = c
|
|
1481
1493
|
|
|
1482
1494
|
return hs, cs, cache
|
|
1483
1495
|
|
|
1484
|
-
#
|
|
1485
|
-
def backward(self, dhs: np.ndarray, cache,
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
"""
|
|
1490
|
-
T = len(cache)
|
|
1496
|
+
# ________ backward method for Cell class __________
|
|
1497
|
+
def backward(self, dhs: np.ndarray, cache,
|
|
1498
|
+
dh_next=None, dc_next=None, T_limit=None):
|
|
1499
|
+
# T_limit avoids slicing cache list externally
|
|
1500
|
+
T = T_limit if T_limit is not None else len(cache)
|
|
1491
1501
|
H = self.hidden_size
|
|
1492
1502
|
|
|
1493
|
-
dW
|
|
1494
|
-
db
|
|
1495
|
-
dh
|
|
1496
|
-
dc
|
|
1503
|
+
dW = np.zeros_like(self.W)
|
|
1504
|
+
db = np.zeros_like(self.b)
|
|
1505
|
+
dh = np.zeros(H) if dh_next is None else dh_next.copy()
|
|
1506
|
+
dc = np.zeros(H) if dc_next is None else dc_next.copy()
|
|
1497
1507
|
dx_seq = np.zeros((T, self.input_size))
|
|
1498
1508
|
|
|
1509
|
+
# preallocate dz buffer once
|
|
1510
|
+
dz = np.empty(4 * H)
|
|
1511
|
+
H1, H2, H3 = H, H * 2, H * 3
|
|
1512
|
+
|
|
1499
1513
|
for t in reversed(range(T)):
|
|
1500
1514
|
x, h_prev, c_prev, f, i, g, o, c_new, tanh_c, xh = cache[t]
|
|
1501
1515
|
|
|
1502
|
-
dh_total = dhs[t] + dh
|
|
1516
|
+
dh_total = dhs[t] + dh
|
|
1503
1517
|
|
|
1504
|
-
# output gate
|
|
1505
1518
|
do = dh_total * tanh_c
|
|
1506
1519
|
dtanhc = dh_total * o
|
|
1507
|
-
|
|
1508
|
-
# cell state
|
|
1509
1520
|
dc_new = dtanhc * tanh_deriv(tanh_c) + dc
|
|
1510
1521
|
|
|
1511
|
-
# gates
|
|
1512
1522
|
df = dc_new * c_prev
|
|
1513
1523
|
di = dc_new * g
|
|
1514
1524
|
dg = dc_new * i
|
|
1515
|
-
dc = dc_new * f
|
|
1516
|
-
|
|
1517
|
-
# pre-activation gradients
|
|
1518
|
-
df_pre = df * sigmoid_deriv(f)
|
|
1519
|
-
di_pre = di * sigmoid_deriv(i)
|
|
1520
|
-
dg_pre = dg * tanh_deriv(g)
|
|
1521
|
-
do_pre = do * sigmoid_deriv(o)
|
|
1525
|
+
dc = dc_new * f
|
|
1522
1526
|
|
|
1523
|
-
|
|
1527
|
+
# write into preallocated dz buffer
|
|
1528
|
+
dz[:H1] = df * sigmoid_deriv(f)
|
|
1529
|
+
dz[H1:H2] = di * sigmoid_deriv(i)
|
|
1530
|
+
dz[H2:H3] = dg * tanh_deriv(g)
|
|
1531
|
+
dz[H3:] = do * sigmoid_deriv(o)
|
|
1524
1532
|
|
|
1525
|
-
|
|
1533
|
+
# nplace accumulation, no intermediate allocation
|
|
1534
|
+
dW += np.outer(dz, xh) # unavoidable alloc but outer is C-level
|
|
1526
1535
|
db += dz
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1536
|
+
|
|
1537
|
+
dxh = self.W.T @ dz
|
|
1538
|
+
dx_seq[t] = dxh[:self.input_size]
|
|
1539
|
+
dh = dxh[self.input_size:]
|
|
1530
1540
|
|
|
1531
1541
|
return {"dW": dW, "db": db}, dx_seq, dh, dc
|
|
1532
1542
|
|
|
@@ -1534,83 +1544,77 @@ class LSTMCell:
|
|
|
1534
1544
|
# ─────────────────────────────────────────────
|
|
1535
1545
|
# LSTM Network (cell + linear output head)
|
|
1536
1546
|
# ─────────────────────────────────────────────
|
|
1537
|
-
|
|
1538
1547
|
class LSTMNetwork:
|
|
1539
|
-
"""
|
|
1540
|
-
One LSTM layer + a linear output projection.
|
|
1541
|
-
input_size → hidden_size → output_size
|
|
1542
|
-
"""
|
|
1543
|
-
|
|
1544
1548
|
def __init__(self, pipeline, input_size, hidden_size, output_size, seed=0):
|
|
1545
|
-
self.cell
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
self.
|
|
1549
|
-
self.
|
|
1550
|
-
self.
|
|
1551
|
-
|
|
1552
|
-
|
|
1549
|
+
self.cell = LSTMCell(input_size, hidden_size, seed)
|
|
1550
|
+
self.weight_shaper = GeometricWeightShaping(output_size, hidden_size)
|
|
1551
|
+
self.Wy = None
|
|
1552
|
+
self.by = np.zeros(output_size)
|
|
1553
|
+
self.pipeline = pipeline
|
|
1554
|
+
self._trained = False
|
|
1555
|
+
|
|
1556
|
+
# forward method to calculate proper weight for prediction and training.
|
|
1553
1557
|
def forward(self, x_seq):
|
|
1558
|
+
# also Wy init only here, removed from train_step
|
|
1554
1559
|
if self.Wy is None:
|
|
1555
1560
|
self.Wy = self.weight_shaper.weight_shaping(x_seq)
|
|
1556
|
-
|
|
1557
1561
|
hs, cs, cache = self.cell.forward(x_seq)
|
|
1558
|
-
|
|
1559
|
-
preds = hs @ self.Wy.T + self.by # (T, output_size)
|
|
1562
|
+
preds = hs @ self.Wy.T + self.by # (T, output_size)
|
|
1560
1563
|
return preds, hs, cs, cache
|
|
1561
1564
|
|
|
1565
|
+
# calculate loss of MSE (Mean squared error.)
|
|
1562
1566
|
def loss_mse(self, preds, targets, AMR):
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
preds = preds[:targets.shape[0], :targets.shape[1]]
|
|
1570
|
-
|
|
1567
|
+
# proper and correct shape alignment
|
|
1568
|
+
min_T = min(preds.shape[0], targets.shape[0])
|
|
1569
|
+
min_F = min(preds.shape[1], targets.shape[1])
|
|
1570
|
+
preds = preds[:min_T, :min_F]
|
|
1571
|
+
targets = targets[:min_T, :min_F]
|
|
1572
|
+
|
|
1571
1573
|
diff = preds - targets
|
|
1572
|
-
|
|
1574
|
+
loss = (1.0 - AMR) * np.mean(diff ** 2)
|
|
1575
|
+
dloss = diff / (min_T * min_F) # FIX 3 — normalize by full element count
|
|
1576
|
+
return loss, dloss
|
|
1573
1577
|
|
|
1578
|
+
# backward method for the network to calculate proper weights with cell backward
|
|
1574
1579
|
def backward(self, dpreds, hs, cache):
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
hs = hs[:min_T, :] # (min_T, hidden)
|
|
1580
|
+
min_T = min(dpreds.shape[0], hs.shape[0])
|
|
1581
|
+
dpreds = dpreds[:min_T]
|
|
1582
|
+
hs = hs[:min_T]
|
|
1579
1583
|
|
|
1580
|
-
dWy = dpreds.T @ hs
|
|
1584
|
+
dWy = dpreds.T @ hs
|
|
1581
1585
|
dby = dpreds.sum(axis=0)
|
|
1582
|
-
dhs = dpreds @ self.Wy
|
|
1586
|
+
dhs = dpreds @ self.Wy
|
|
1583
1587
|
|
|
1584
|
-
|
|
1588
|
+
# pass min_T directly, need to avoid creating a sliced list
|
|
1589
|
+
cell_grads, dx, _, _ = self.cell.backward(dhs, cache, T_limit=min_T)
|
|
1585
1590
|
return cell_grads, {"dWy": dWy, "dby": dby}, dx
|
|
1586
1591
|
|
|
1592
|
+
# update ensured proper gradient clipping
|
|
1587
1593
|
def update(self, cell_grads, out_grads, lr=1e-3, clip=5.0):
|
|
1588
|
-
"""SGD with gradient clipping."""
|
|
1589
1594
|
def clip_and_step(param, grad):
|
|
1590
|
-
|
|
1595
|
+
np.clip(grad, -clip, clip, out=grad)
|
|
1591
1596
|
param -= lr * grad
|
|
1592
1597
|
|
|
1593
|
-
clip_and_step(self.cell.W,
|
|
1594
|
-
clip_and_step(self.cell.b,
|
|
1595
|
-
clip_and_step(self.Wy,
|
|
1596
|
-
clip_and_step(self.by,
|
|
1598
|
+
clip_and_step(self.cell.W, cell_grads["dW"])
|
|
1599
|
+
clip_and_step(self.cell.b, cell_grads["db"])
|
|
1600
|
+
clip_and_step(self.Wy, out_grads["dWy"])
|
|
1601
|
+
clip_and_step(self.by, out_grads["dby"])
|
|
1597
1602
|
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1603
|
+
# train step for each LSTM fitting method
|
|
1604
|
+
def train_step(self, x_seq, targets, lr=1e-3, AMR=None):
|
|
1605
|
+
# accept precomputed AMR
|
|
1606
|
+
if AMR is None:
|
|
1607
|
+
AME = self.pipeline.AME_Encoder(x_seq)
|
|
1608
|
+
AMR = 1.0 / (1.0 + np.exp(-AME))
|
|
1601
1609
|
|
|
1602
1610
|
preds, hs, cs, cache = self.forward(x_seq)
|
|
1603
|
-
|
|
1604
|
-
AME = self.pipeline.AME_Encoder(x_seq)
|
|
1605
|
-
AMR = 1.0 / (1.0 + np.exp(-AME))
|
|
1606
|
-
|
|
1607
|
-
loss, dloss = self.loss_mse(preds, targets, AMR)
|
|
1611
|
+
loss, dloss = self.loss_mse(preds, targets, AMR)
|
|
1608
1612
|
cell_grads, out_grads, _ = self.backward(dloss, hs, cache)
|
|
1609
1613
|
self.update(cell_grads, out_grads, lr)
|
|
1610
|
-
|
|
1611
1614
|
return loss, preds
|
|
1612
1615
|
|
|
1613
1616
|
|
|
1617
|
+
|
|
1614
1618
|
# ─────────────────────────────────────────────
|
|
1615
1619
|
# LSTM Engine
|
|
1616
1620
|
# ─────────────────────────────────────────────
|
|
@@ -1649,148 +1653,265 @@ class LSTMEngine:
|
|
|
1649
1653
|
self.residual_mean = None
|
|
1650
1654
|
|
|
1651
1655
|
# ── calibrate on validation set ──────────
|
|
1652
|
-
def
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1656
|
+
def calibrate_residual(self, X_val, Y_val):
|
|
1657
|
+
if len(X_val) == 0:
|
|
1658
|
+
self.residual_mean = 0.0
|
|
1659
|
+
self.residual_std = 1.0
|
|
1660
|
+
return
|
|
1661
|
+
|
|
1662
|
+
confidence_errors = []
|
|
1663
|
+
|
|
1658
1664
|
for j in range(len(X_val)):
|
|
1659
1665
|
preds, _, _, _ = self.model.forward(X_val[j])
|
|
1660
|
-
err = (preds[:, 0] - Y_val[j, :, 0])
|
|
1661
|
-
residuals.extend(err.tolist())
|
|
1662
1666
|
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1667
|
+
pred_vals = preds[:, 0] if preds.ndim > 1 else preds
|
|
1668
|
+
|
|
1669
|
+
# get true class
|
|
1670
|
+
true_vals = Y_val[j, :, 0] if Y_val[j].ndim > 1 else Y_val[j]
|
|
1671
|
+
min_len = min(len(pred_vals), len(true_vals))
|
|
1672
|
+
|
|
1673
|
+
# sigmoid to get predicted probability
|
|
1674
|
+
pred_prob = 1.0 / (1.0 + np.exp(-pred_vals[:min_len]))
|
|
1675
|
+
true_bin = true_vals[:min_len].astype(float)
|
|
1676
|
+
|
|
1677
|
+
# confidence error — how wrong was the predicted probability
|
|
1678
|
+
# correct prediction → low error
|
|
1679
|
+
# wrong prediction → high error
|
|
1680
|
+
err = np.abs(pred_prob - true_bin)
|
|
1681
|
+
confidence_errors.extend(err.tolist())
|
|
1682
|
+
|
|
1683
|
+
errors = np.array(confidence_errors)
|
|
1684
|
+
|
|
1685
|
+
# IQR outlier removal
|
|
1686
|
+
q25, q75 = np.percentile(errors, [25, 75])
|
|
1687
|
+
iqr = q75 - q25
|
|
1688
|
+
mask = (errors >= q25 - 1.5 * iqr) & (errors <= q75 + 1.5 * iqr)
|
|
1689
|
+
clean = errors[mask] if mask.sum() > 0 else errors
|
|
1690
|
+
|
|
1691
|
+
self.residual_mean = float(clean.mean())
|
|
1692
|
+
self.residual_std = float(max(clean.std(), 1e-6))
|
|
1693
|
+
|
|
1694
|
+
# floor std on small n — can't trust calibration with few samples
|
|
1695
|
+
if len(X_val) < 20:
|
|
1696
|
+
self.residual_std = max(self.residual_std, 0.1)
|
|
1697
|
+
print(f'[!] Small val set ({len(X_val)} samples) — flooring σ to 0.1')
|
|
1698
|
+
|
|
1699
|
+
self.calibration_coverage = float(mask.mean())
|
|
1700
|
+
self.n_calibration_samples = len(X_val)
|
|
1701
|
+
|
|
1666
1702
|
print(f"[=] Calibrated: residual μ={self.residual_mean:.4f} "
|
|
1667
|
-
|
|
1703
|
+
f"σ={self.residual_std:.4f} "
|
|
1704
|
+
f"coverage={self.calibration_coverage:.1%} "
|
|
1705
|
+
f"n={self.n_calibration_samples}")
|
|
1706
|
+
|
|
1668
1707
|
|
|
1669
1708
|
# ── MC dropout forward ────────────────────
|
|
1670
|
-
def _mc_forward(self, x_seq: np.ndarray) ->
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
between every timestep, scaled to preserve expected value.
|
|
1674
|
-
"""
|
|
1675
|
-
T = x_seq.shape[0]
|
|
1676
|
-
H = self.model.cell.hidden_size
|
|
1709
|
+
def _mc_forward(self, x_seq: np.ndarray) -> np.ndarray:
|
|
1710
|
+
T = x_seq.shape[0]
|
|
1711
|
+
H = self.model.cell.hidden_size
|
|
1677
1712
|
expected_input = self.model.cell.input_size
|
|
1678
|
-
p
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1713
|
+
p = self.dropout
|
|
1714
|
+
cell = self.model.cell
|
|
1715
|
+
W = cell.W
|
|
1716
|
+
b = cell.b
|
|
1717
|
+
H1, H2, H3 = H, H * 2, H * 3 # slice boundaries precomputed
|
|
1718
|
+
|
|
1719
|
+
# FIX 1 — Wy check once before loop, not T times inside
|
|
1720
|
+
if self.model.Wy is None:
|
|
1721
|
+
self.model.Wy = self.model.weight_shaper.weight_shaping(x_seq)
|
|
1722
|
+
Wy = self.model.Wy
|
|
1723
|
+
by = self.model.by
|
|
1724
|
+
|
|
1725
|
+
h = np.zeros(H)
|
|
1726
|
+
c = np.zeros(H)
|
|
1727
|
+
xh = np.empty(expected_input + H) # preallocate concat buffer
|
|
1728
|
+
preds = np.empty(T) # preallocate output
|
|
1729
|
+
|
|
1730
|
+
# precompute dropout scale factor
|
|
1731
|
+
inv_keep = 1.0 / (1.0 - p)
|
|
1685
1732
|
|
|
1686
1733
|
for t in range(T):
|
|
1687
|
-
x
|
|
1734
|
+
x = x_seq[t]
|
|
1735
|
+
|
|
1736
|
+
# shape alignment
|
|
1688
1737
|
if x.ndim == 0:
|
|
1689
|
-
x = x.reshape(1)
|
|
1738
|
+
x = x.reshape(1)
|
|
1690
1739
|
if x.shape[0] < expected_input:
|
|
1691
|
-
x = np.pad(x, (0, expected_input - x.shape[0]))
|
|
1740
|
+
x = np.pad(x, (0, expected_input - x.shape[0]))
|
|
1692
1741
|
elif x.shape[0] > expected_input:
|
|
1693
|
-
x = x[:expected_input]
|
|
1694
|
-
xh = np.concatenate([x, h])
|
|
1695
|
-
z = cell.W @ xh + cell.b
|
|
1742
|
+
x = x[:expected_input]
|
|
1696
1743
|
|
|
1697
|
-
#
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1744
|
+
# need to write into preallocated buffer instead of np.concatenate
|
|
1745
|
+
xh[:expected_input] = x
|
|
1746
|
+
xh[expected_input:] = h
|
|
1747
|
+
|
|
1748
|
+
z = W @ xh + b # (4H,)
|
|
1749
|
+
|
|
1750
|
+
# direct slices instead of method calls
|
|
1751
|
+
f = sigmoid(z[:H1])
|
|
1752
|
+
i = sigmoid(z[H1:H2])
|
|
1753
|
+
g = np.tanh(z[H2:H3])
|
|
1754
|
+
o = sigmoid(z[H3:])
|
|
1702
1755
|
|
|
1703
1756
|
c = f * c + i * g
|
|
1704
1757
|
tanh_c = np.tanh(c)
|
|
1705
1758
|
h = o * tanh_c
|
|
1706
1759
|
|
|
1707
|
-
#
|
|
1708
|
-
mask = (np.random.rand(H) > p)
|
|
1709
|
-
h
|
|
1710
|
-
# cell state c is untouched —
|
|
1711
|
-
# preserves long-term memory
|
|
1712
|
-
if self.model.Wy is None:
|
|
1760
|
+
# FIX 4 — precomputed inv_keep, inplace mask application
|
|
1761
|
+
mask = (np.random.rand(H) > p) * inv_keep
|
|
1762
|
+
h *= mask
|
|
1713
1763
|
|
|
1714
|
-
|
|
1764
|
+
preds[t] = (h @ Wy.T + by)[0]
|
|
1715
1765
|
|
|
1716
|
-
|
|
1717
|
-
preds.append(pred[0])
|
|
1766
|
+
return preds # (T,)
|
|
1718
1767
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
# ── gate uncertainty ──────────────────────
|
|
1722
|
-
def _gate_uncertainty(self, x_seq: np.ndarray, AMR: float) -> Any:
|
|
1768
|
+
# ── gate uncertainty for LSTM prediction──────────────────────
|
|
1769
|
+
def _gate_uncertainty(self, x_seq: np.ndarray, AMR: float) -> np.ndarray:
|
|
1723
1770
|
"""
|
|
1724
1771
|
Structural uncertainty from gate activations.
|
|
1725
|
-
|
|
1726
|
-
High uncertainty when:
|
|
1727
|
-
forget gate (f) is LOW → model is erasing memory
|
|
1728
|
-
input gate (i) is HIGH → model is overwriting with new info
|
|
1729
|
-
→ transition moment, inherently harder to predict
|
|
1730
|
-
|
|
1731
|
-
Returns per-timestep uncertainty in [0, 1].
|
|
1772
|
+
Vectorized — no Python loop over timesteps.
|
|
1732
1773
|
"""
|
|
1733
1774
|
_, _, cache = self.model.cell.forward(x_seq)
|
|
1734
|
-
gate_uncertainty = []
|
|
1735
|
-
for entry in cache:
|
|
1736
|
-
_, _, _, f, i, g, o, _, _, _ = entry
|
|
1737
|
-
# mean forget across hidden dims — low = erasing
|
|
1738
|
-
forget_instability = 1.0 - f.mean()
|
|
1739
|
-
# mean input — high = overwriting
|
|
1740
|
-
input_activity = i.mean()
|
|
1741
|
-
# combined: both high = maximally uncertain transition
|
|
1742
|
-
u = AMR * forget_instability + AMR * input_activity
|
|
1743
|
-
gate_uncertainty.append(u)
|
|
1744
1775
|
|
|
1745
|
-
|
|
1776
|
+
# cache[t] = (x, h_prev, c_prev, f, i, g, o, c_new, tanh_c, xh)
|
|
1777
|
+
# need to extract f and i directly as stacked arrays — shape (T, H)
|
|
1778
|
+
T = len(cache)
|
|
1779
|
+
|
|
1780
|
+
if T == 0:
|
|
1781
|
+
return np.array([0.0])
|
|
1782
|
+
|
|
1783
|
+
# vectorized extraction — one pass instead of T unpacks
|
|
1784
|
+
f_all = np.empty((T, cache[0][3].shape[0])) # (T, H)
|
|
1785
|
+
i_all = np.empty((T, cache[0][4].shape[0])) # (T, H)
|
|
1786
|
+
|
|
1787
|
+
for t, entry in enumerate(cache):
|
|
1788
|
+
f_all[t] = entry[3] # forget gate
|
|
1789
|
+
i_all[t] = entry[4] # input gate
|
|
1790
|
+
|
|
1791
|
+
# vectorized computation — no per-timestep Python arithmetic
|
|
1792
|
+
forget_instability = 1.0 - f_all.mean(axis=1) # (T,)
|
|
1793
|
+
input_activity = i_all.mean(axis=1) # (T,)
|
|
1794
|
+
|
|
1795
|
+
# precompute scalar factor once
|
|
1796
|
+
scale = 1.0 - AMR
|
|
1797
|
+
gate_uncertainty = scale * (forget_instability + input_activity)
|
|
1746
1798
|
|
|
1799
|
+
return np.clip(gate_uncertainty, 0.0, 1.0)
|
|
1747
1800
|
|
|
1748
|
-
# empirical quantiles from actual residuals
|
|
1801
|
+
# empirical quantiles from actual residuals
|
|
1749
1802
|
def calibrate(self, X_val, Y_val):
|
|
1750
|
-
|
|
1803
|
+
if len(X_val) == 0:
|
|
1804
|
+
self.residual_mean = 0.0
|
|
1805
|
+
self.residual_std = 1.0
|
|
1806
|
+
self.quantiles = {}
|
|
1807
|
+
return
|
|
1808
|
+
|
|
1809
|
+
all_errors = []
|
|
1751
1810
|
for j in range(len(X_val)):
|
|
1752
|
-
preds, _, _, _ = self.model.forward(X_val[j])
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
self.
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1811
|
+
preds, _, _, _ = self.model.forward(X_val[j])
|
|
1812
|
+
y = Y_val[j]
|
|
1813
|
+
pred_vals = preds[:, 0] if preds.ndim > 1 else preds
|
|
1814
|
+
true_vals = y[:, 0] if y.ndim > 1 else y
|
|
1815
|
+
min_T = min(len(pred_vals), len(true_vals))
|
|
1816
|
+
all_errors.append(pred_vals[:min_T] - true_vals[:min_T])
|
|
1817
|
+
|
|
1818
|
+
residuals = np.concatenate(all_errors)
|
|
1819
|
+
n = len(residuals)
|
|
1820
|
+
|
|
1821
|
+
self.residual_mean = float(residuals.mean())
|
|
1822
|
+
self.residual_std = float(max(residuals.std(), 1e-6))
|
|
1823
|
+
|
|
1824
|
+
# adapt confidence levels to sample size
|
|
1825
|
+
# small n → only compute what's statistically supportable
|
|
1826
|
+
if n < 20:
|
|
1827
|
+
# only 90% interval is reliable — use 10th/90th percentile
|
|
1828
|
+
# wide enough to be honest about uncertainty
|
|
1829
|
+
levels = [10.0, 90.0]
|
|
1830
|
+
p = np.percentile(residuals, levels)
|
|
1831
|
+
self.quantiles = {
|
|
1832
|
+
0.90: (float(p[0]), float(p[1])),
|
|
1833
|
+
0.95: (float(p[0]), float(p[1])), # same as 90% — honest, not fake precision
|
|
1834
|
+
0.99: (float(p[0]), float(p[1])),
|
|
1835
|
+
}
|
|
1836
|
+
print(f'[!] n={n} too small for tail quantiles — '
|
|
1837
|
+
f'using 10th/90th for all intervals')
|
|
1838
|
+
|
|
1839
|
+
elif n < 50:
|
|
1840
|
+
# 90% and 95% supportable, 99% not reliable
|
|
1841
|
+
levels = [2.5, 5.0, 95.0, 97.5]
|
|
1842
|
+
p = np.percentile(residuals, levels)
|
|
1843
|
+
self.quantiles = {
|
|
1844
|
+
0.90: (float(p[1]), float(p[2])),
|
|
1845
|
+
0.95: (float(p[0]), float(p[3])),
|
|
1846
|
+
0.99: (float(p[0]), float(p[3])), # same as 95% — honest
|
|
1847
|
+
}
|
|
1848
|
+
print(f'[!] n={n} insufficient for 99% interval — '
|
|
1849
|
+
f'using 95% as proxy')
|
|
1850
|
+
|
|
1851
|
+
else:
|
|
1852
|
+
# full precision justified
|
|
1853
|
+
levels = [0.5, 2.5, 5.0, 95.0, 97.5, 99.5]
|
|
1854
|
+
p = np.percentile(residuals, levels)
|
|
1855
|
+
self.quantiles = {
|
|
1856
|
+
0.90: (float(p[2]), float(p[3])),
|
|
1857
|
+
0.95: (float(p[1]), float(p[4])),
|
|
1858
|
+
0.99: (float(p[0]), float(p[5])),
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
# floor std on small n
|
|
1862
|
+
if n < 20:
|
|
1863
|
+
self.residual_std = max(self.residual_std, 0.1)
|
|
1864
|
+
|
|
1865
|
+
print(f"[=] Calibrated: μ={self.residual_mean:.4f} "
|
|
1866
|
+
f"σ={self.residual_std:.4f} "
|
|
1867
|
+
f"n={n} "
|
|
1868
|
+
f"90%=[{self.quantiles[0.90][0]:.4f}, {self.quantiles[0.90][1]:.4f}]")
|
|
1770
1869
|
|
|
1771
1870
|
# interval to calculate prediction interval from MC mean + empirical quantiles
|
|
1772
1871
|
def _interval(self, mc_mean, confidence_level):
|
|
1872
|
+
# flatten mc_mean safely — handles scalar, 0-d array, or 1-d array
|
|
1873
|
+
mc_scalar = float(np.asarray(mc_mean).flat[0])
|
|
1874
|
+
|
|
1875
|
+
if confidence_level not in self.quantiles:
|
|
1876
|
+
available = sorted(self.quantiles.keys())
|
|
1877
|
+
if not available:
|
|
1878
|
+
return mc_scalar - self.residual_std, mc_scalar + self.residual_std
|
|
1879
|
+
confidence_level = min(available, key=lambda k: abs(k - confidence_level))
|
|
1880
|
+
print(f'[!] Confidence level not found, using closest: {confidence_level}')
|
|
1881
|
+
|
|
1773
1882
|
lo_bias, hi_bias = self.quantiles[confidence_level]
|
|
1774
|
-
return mc_mean + lo_bias, mc_mean + hi_bias
|
|
1775
1883
|
|
|
1884
|
+
lo = mc_scalar + float(np.asarray(lo_bias).flat[0])
|
|
1885
|
+
hi = mc_scalar + float(np.asarray(hi_bias).flat[0])
|
|
1886
|
+
|
|
1887
|
+
if lo > hi:
|
|
1888
|
+
lo, hi = hi, lo
|
|
1889
|
+
|
|
1890
|
+
return lo, hi
|
|
1776
1891
|
|
|
1777
1892
|
# MC sample counting for label confidence (last timestep)
|
|
1778
1893
|
def _label_confidence_empirical(self, mc_samples_last, label_bins):
|
|
1779
1894
|
"""
|
|
1780
1895
|
mc_samples_last : (n_samples,) — raw MC draws at last timestep
|
|
1781
1896
|
label_bins : {"Good": (0, 35), "Moderate": (35, 75), ...}
|
|
1782
|
-
|
|
1783
|
-
No distribution assumption — just count what fraction
|
|
1784
|
-
of actual MC samples land in each bin.
|
|
1785
1897
|
"""
|
|
1786
|
-
label_conf = {}
|
|
1787
1898
|
n = len(mc_samples_last)
|
|
1899
|
+
if n == 0:
|
|
1900
|
+
return {name: 0.0 for name in label_bins}
|
|
1901
|
+
|
|
1902
|
+
names = list(label_bins.keys())
|
|
1903
|
+
bounds = np.array(list(label_bins.values())) # (n_bins, 2)
|
|
1788
1904
|
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1905
|
+
# vectorized — broadcast (n_samples,) against (n_bins, 2)
|
|
1906
|
+
# samples shape: (1, n_samples), bounds shape: (n_bins, 1)
|
|
1907
|
+
samples = mc_samples_last[np.newaxis, :] # (1, n_samples)
|
|
1908
|
+
lo = bounds[:, 0, np.newaxis] # (n_bins, 1)
|
|
1909
|
+
hi = bounds[:, 1, np.newaxis] # (n_bins, 1)
|
|
1792
1910
|
|
|
1793
|
-
|
|
1911
|
+
hits = ((samples >= lo) & (samples < hi)).sum(axis=1) # (n_bins,)
|
|
1912
|
+
probs = hits / n # (n_bins,)
|
|
1913
|
+
|
|
1914
|
+
return dict(zip(names, probs.tolist()))
|
|
1794
1915
|
|
|
1795
1916
|
# LSTM training loop with confidence layers integrated into the loss and validation monitoring.
|
|
1796
1917
|
def fit_stm(self, X, Y, epochs=50, hidden=32, lr=5e-3, seq_len=20, print_every=5):
|
|
@@ -1800,7 +1921,7 @@ class LSTMEngine:
|
|
|
1800
1921
|
AME = self.pipeline.AME_Encoder(X)
|
|
1801
1922
|
AMR = 1.0 / (1.0 + np.exp(-AME))
|
|
1802
1923
|
|
|
1803
|
-
n_train = int(AMR * len(X))
|
|
1924
|
+
n_train = int((1.0 - AMR) * len(X))
|
|
1804
1925
|
X_tr, Y_tr = X[:n_train], Y[:n_train]
|
|
1805
1926
|
X_te, Y_te = X[n_train:], Y[n_train:]
|
|
1806
1927
|
|
|
@@ -1810,7 +1931,7 @@ class LSTMEngine:
|
|
|
1810
1931
|
np.random.shuffle(idx)
|
|
1811
1932
|
epoch_loss = 0.0
|
|
1812
1933
|
for j in idx:
|
|
1813
|
-
loss, _ = model.train_step(X_tr[j], Y_tr[j], lr=lr)
|
|
1934
|
+
loss, _ = model.train_step(X_tr[j], Y_tr[j], lr=lr, AMR=AMR)
|
|
1814
1935
|
epoch_loss += loss
|
|
1815
1936
|
epoch_loss /= n_train
|
|
1816
1937
|
|
|
@@ -1837,7 +1958,10 @@ class LSTMEngine:
|
|
|
1837
1958
|
|
|
1838
1959
|
print("[=] Training complete!")
|
|
1839
1960
|
print(f"[=] Final val loss: {val_loss:.6f}")
|
|
1961
|
+
print('===== CALIBRATION METHOD =====')
|
|
1962
|
+
self.calibrate_residual(X_te, Y_te)
|
|
1840
1963
|
|
|
1964
|
+
# get optimal lstm samples amount for the model to process
|
|
1841
1965
|
def lstm_optimal_samples(self, engine, x_seq, tolerance=0.005, max_n=500):
|
|
1842
1966
|
"""
|
|
1843
1967
|
Run increasing n_samples until std estimate stabilizes.
|
|
@@ -1858,7 +1982,7 @@ class LSTMEngine:
|
|
|
1858
1982
|
prev_std = current_std
|
|
1859
1983
|
return max_n
|
|
1860
1984
|
|
|
1861
|
-
|
|
1985
|
+
# derive local bins for flexibility in scarce dataset
|
|
1862
1986
|
def derive_bins_from_data(self, y_values, n_bins=4, labels=None):
|
|
1863
1987
|
"""
|
|
1864
1988
|
Use percentiles of actual data to set boundaries.
|
|
@@ -1911,7 +2035,7 @@ class LSTMEngine:
|
|
|
1911
2035
|
return bins
|
|
1912
2036
|
|
|
1913
2037
|
|
|
1914
|
-
# ── main predict ─────────────────────────
|
|
2038
|
+
# ── main predict function for the whole network ─────────────────────────
|
|
1915
2039
|
def predict(self, x_seq: np.ndarray,
|
|
1916
2040
|
label_bins: dict = None,
|
|
1917
2041
|
confidence_level: float = 0.90) -> Any:
|
|
@@ -1937,9 +2061,6 @@ class LSTMEngine:
|
|
|
1937
2061
|
label_confidence: {label: probability} if label_bins given
|
|
1938
2062
|
overall : single scalar confidence for last timestep
|
|
1939
2063
|
"""
|
|
1940
|
-
assert self.residual_std is not None, \
|
|
1941
|
-
"Call calibrate(X_val, Y_val) before predict()"
|
|
1942
|
-
|
|
1943
2064
|
# ── point prediction ──────────────────
|
|
1944
2065
|
preds_clean, _, _, _ = self.model.forward(x_seq)
|
|
1945
2066
|
AME = self.pipeline.AME_Encoder(x_seq) # geometric complexity scalar
|
|
@@ -1965,7 +2086,11 @@ class LSTMEngine:
|
|
|
1965
2086
|
|
|
1966
2087
|
# ── prediction interval ───────────────
|
|
1967
2088
|
total_std = np.sqrt(mc_std**2 + self.residual_std**2)
|
|
1968
|
-
|
|
2089
|
+
# compute interval for every timestep — always returns (T,) arrays
|
|
2090
|
+
interval_low = np.empty(len(mc_mean))
|
|
2091
|
+
interval_high = np.empty(len(mc_mean))
|
|
2092
|
+
for t in range(len(mc_mean)):
|
|
2093
|
+
interval_low[t], interval_high[t] = self._interval(mc_mean[t], confidence_level)
|
|
1969
2094
|
|
|
1970
2095
|
# ── label confidence (last timestep) ──
|
|
1971
2096
|
label_conf = None
|
|
@@ -1980,7 +2105,7 @@ class LSTMEngine:
|
|
|
1980
2105
|
# ── overall scalar confidence ─────────
|
|
1981
2106
|
# weighted combination of MC confidence and gate stability
|
|
1982
2107
|
gate_stability = 1.0 - gate_unc[-1] # high = stable
|
|
1983
|
-
overall = AMR * mc_confidence[-1] + \
|
|
2108
|
+
overall = (1.0 - AMR) * mc_confidence[-1] + \
|
|
1984
2109
|
self.pipeline.confidence_threshold * gate_stability
|
|
1985
2110
|
|
|
1986
2111
|
return {
|
|
@@ -1989,16 +2114,15 @@ class LSTMEngine:
|
|
|
1989
2114
|
"mc_std" : mc_std,
|
|
1990
2115
|
"mc_confidence" : mc_confidence,
|
|
1991
2116
|
"gate_uncertainty": gate_unc,
|
|
1992
|
-
"interval_low" :
|
|
1993
|
-
"interval_high" :
|
|
2117
|
+
"interval_low" : interval_low,
|
|
2118
|
+
"interval_high" : interval_high,
|
|
1994
2119
|
"label_confidence": label_conf,
|
|
1995
2120
|
"overall" : overall,
|
|
1996
2121
|
}
|
|
1997
2122
|
|
|
1998
2123
|
# ─────────────────────────────────────────────
|
|
1999
|
-
# Architecture summary helper
|
|
2124
|
+
# Architecture summary helper to visualize results
|
|
2000
2125
|
# ─────────────────────────────────────────────
|
|
2001
|
-
|
|
2002
2126
|
def architectural_summary(self, model: LSTMNetwork):
|
|
2003
2127
|
H = model.cell.hidden_size
|
|
2004
2128
|
I = model.cell.input_size
|
|
@@ -7511,7 +7635,11 @@ class IntegratedPipeline:
|
|
|
7511
7635
|
elif isinstance(a, np.ndarray) and np.issubdtype(a.dtype, np.character):
|
|
7512
7636
|
# catches arrays filled with string text
|
|
7513
7637
|
clean_str = ' '.join(a.astype(str).flatten()).replace('[', '').replace(']', '')
|
|
7514
|
-
|
|
7638
|
+
try:
|
|
7639
|
+
a = np.fromstring(clean_str, sep=' ')
|
|
7640
|
+
except:
|
|
7641
|
+
clean_string = clean_str.strip(",")
|
|
7642
|
+
a = np.fromstring(clean_string, sep=' ')
|
|
7515
7643
|
else:
|
|
7516
7644
|
# Ensure standard float array if it was integers or objects
|
|
7517
7645
|
a = np.asarray(a, dtype=float)
|
|
@@ -8354,18 +8482,18 @@ class IntegratedPipeline:
|
|
|
8354
8482
|
|
|
8355
8483
|
if choose_method == 'Y':
|
|
8356
8484
|
self.autonomous = True
|
|
8357
|
-
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method='dynamic')
|
|
8485
|
+
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method='dynamic', embedded=True)
|
|
8358
8486
|
|
|
8359
8487
|
else:
|
|
8360
8488
|
method = input('|| Choose one method (ex: dynamic): ')
|
|
8361
8489
|
if method:
|
|
8362
|
-
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method=method)
|
|
8490
|
+
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method=method, embedded=True)
|
|
8363
8491
|
else:
|
|
8364
8492
|
print('|| Invalid Method.. returning to dynamic prediction..')
|
|
8365
|
-
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method='dynamic')
|
|
8493
|
+
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method='dynamic', embedded=True)
|
|
8366
8494
|
else:
|
|
8367
8495
|
print('[+] Autonomous dynamic prediction: ')
|
|
8368
|
-
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method='dynamic')
|
|
8496
|
+
probs, details = self.ensemble.predict_ensemble(input_ids, X, y, method='dynamic', embedded=True)
|
|
8369
8497
|
|
|
8370
8498
|
self.modular_prediction_saving(input_ids, X, probs)
|
|
8371
8499
|
print('🚀 Memory Added!')
|
|
@@ -11768,9 +11896,15 @@ class PipelinePredictionManager:
|
|
|
11768
11896
|
if sequence_ids is not None:
|
|
11769
11897
|
print("\n[🔍] Using sequence encoding for transformer input due to low anisotropy.")
|
|
11770
11898
|
input_ids = sequence_ids.copy()
|
|
11899
|
+
|
|
11771
11900
|
target_probs = self.pipeline.predict_proba(input_ids, X, type='Hybrid', embedded=True)
|
|
11772
11901
|
target_probs = target_probs[:mlp_probs.shape[0], :mlp_probs.shape[1]]
|
|
11773
|
-
target_pred_indices = np.argmax(target_probs, axis=1)
|
|
11902
|
+
target_pred_indices = np.argmax(target_probs, axis=1)
|
|
11903
|
+
|
|
11904
|
+
if self.pipeline.cache and 'label_bins' in self.pipeline.cache:
|
|
11905
|
+
print('[=] label_bins cache found!')
|
|
11906
|
+
label_bins = self.pipeline.cache['label_bins']
|
|
11907
|
+
lstm_probs, _ = self.pipeline.ensemble._get_lstm_probs(input_ids, X, label_bins=label_bins)
|
|
11774
11908
|
|
|
11775
11909
|
results = []
|
|
11776
11910
|
attention_data = [] if return_attention else None
|
|
@@ -11811,20 +11945,32 @@ class PipelinePredictionManager:
|
|
|
11811
11945
|
else:
|
|
11812
11946
|
trans_confidence = trans_probs_i[trans_class_idx]
|
|
11813
11947
|
|
|
11948
|
+
if lstm_probs is not None:
|
|
11949
|
+
lstm_probs_i = lstm_probs[i]
|
|
11950
|
+
lstm_class_idx = np.argmax(lstm_probs_i)
|
|
11951
|
+
if isinstance(lstm_probs_i, float):
|
|
11952
|
+
lstm_confidence = target_confidence
|
|
11953
|
+
else:
|
|
11954
|
+
lstm_confidence = lstm_probs_i[lstm_class_idx]
|
|
11955
|
+
|
|
11814
11956
|
trans_label = reverse_map.get(trans_class_idx, f"unknown_{trans_class_idx}")
|
|
11815
11957
|
|
|
11816
11958
|
calibration = self.pipeline._calibrate_probs(target_probs, target_pred_indices, attn_weights, input_ids)
|
|
11817
11959
|
# Blend predictions (MLP decides class, transformer calibrates confidence)
|
|
11818
11960
|
mlp_weight = mlp_confidence / (target_confidence + trans_confidence + eps)
|
|
11819
11961
|
trans_weight = trans_confidence / (target_confidence + trans_confidence + eps)
|
|
11962
|
+
if lstm_confidence is not None:
|
|
11963
|
+
lstm_weight = lstm_confidence / (target_confidence + lstm_confidence + eps)
|
|
11820
11964
|
|
|
11821
11965
|
calibration_weighting = calibration[target_class_idx] if target_class_idx < len(calibration) else 0.0
|
|
11822
11966
|
|
|
11823
11967
|
# Weighted blend: calibration_weighting * calibrated + (1-weight) * mlp
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11968
|
+
if lstm_weight is not None:
|
|
11969
|
+
final_probs = mlp_weight * target_probs[i][:len(calibration)] + trans_weight * calibration[i][:len(calibration)] + lstm_weight * calibration[i][:len(calibration)]
|
|
11970
|
+
else:
|
|
11971
|
+
final_probs = mlp_weight * target_probs[i][:len(calibration)] + trans_weight * calibration[i][:len(calibration)]
|
|
11827
11972
|
|
|
11973
|
+
final_class_idx = target_class_idx
|
|
11828
11974
|
try:
|
|
11829
11975
|
final_confidence = final_probs[final_class_idx]
|
|
11830
11976
|
except IndexError:
|
|
@@ -11940,7 +12086,7 @@ class PipelinePredictionManager:
|
|
|
11940
12086
|
|
|
11941
12087
|
elif not results[0].get('models_agree', True) and confidence > self.pipeline.confidence_threshold:
|
|
11942
12088
|
if final_confidence is not None and confidence < self.pipeline.confidence_threshold:
|
|
11943
|
-
print("\n[⚠️]
|
|
12089
|
+
print("\n[⚠️] Low confidence detected, but both models don't agree. Using calibrated probabilities for final decision to ensure robustness.")
|
|
11944
12090
|
final_probs = self.pipeline.hybrid_prediction(rules, input_ids, dataset)
|
|
11945
12091
|
final_idx = final_probs[0].argmax()
|
|
11946
12092
|
original_idx = final_idx
|
|
@@ -11949,6 +12095,16 @@ class PipelinePredictionManager:
|
|
|
11949
12095
|
final_idx = int(np.argmax(final_probs[:len(reverse_map)-1]))
|
|
11950
12096
|
print(f"[⚠️] Clamping {final_idx} → {final_idx}")
|
|
11951
12097
|
final_idx = int(final_idx)
|
|
12098
|
+
else:
|
|
12099
|
+
print('[🎯] Stable confidence established, But both Models doesnt Agree, Re-evaluating...')
|
|
12100
|
+
final_probs = self.pipeline.hybrid_prediction(rules, input_ids, dataset)
|
|
12101
|
+
final_idx = final_probs[0].argmax()
|
|
12102
|
+
original_idx = final_idx
|
|
12103
|
+
|
|
12104
|
+
if final_idx > len(reverse_map):
|
|
12105
|
+
final_idx = int(np.argmax(final_probs[:len(reverse_map)-1]))
|
|
12106
|
+
print(f"[⚠️] Clamping {final_idx} → {final_idx}")
|
|
12107
|
+
final_idx = int(final_idx)
|
|
11952
12108
|
|
|
11953
12109
|
chosen_label = reverse_map.get(final_idx, f"unknown_{final_idx}")
|
|
11954
12110
|
try:
|
|
@@ -13898,14 +14054,12 @@ async def example_async_with_result_queue(pipeline, test_titles, label_map, rule
|
|
|
13898
14054
|
# Example using the proper result queue
|
|
13899
14055
|
|
|
13900
14056
|
agent = CohesiveAgentDeployment(
|
|
13901
|
-
pipeline=pipeline,
|
|
13902
14057
|
memory_name="test_agent",
|
|
13903
14058
|
filename=filename,
|
|
13904
14059
|
target_title=title_name,
|
|
13905
14060
|
label_name=label_name,
|
|
13906
14061
|
security_level="DEVELOPMENT",
|
|
13907
|
-
enable_peers=False
|
|
13908
|
-
peer_discovery_port=5558
|
|
14062
|
+
enable_peers=False
|
|
13909
14063
|
)
|
|
13910
14064
|
|
|
13911
14065
|
await agent.start()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AbstractIntegratedModule
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Framework for Advanced Integrated Non-LLM AI Module library - Backend Framework for Non-LLM AI Agent Framework
|
|
5
5
|
Author: Micro-Novelty
|
|
6
6
|
Author-email: hernikpuspita5@gmail.com
|
|
@@ -42,7 +42,7 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
|
|
|
42
42
|
#### Note: The README here you are reading is a direct copy from my README Repository, to download the necessary files, you can visit my Repository with the provided link above.
|
|
43
43
|
|
|
44
44
|
### Library Short Description:
|
|
45
|
-
- Development Stage: Beta, 0.
|
|
45
|
+
- Development Stage: Beta, 0.4.1.
|
|
46
46
|
- Maintainer: Micro-Novelty.
|
|
47
47
|
- library Source-Code is Open-sourced on github.
|
|
48
48
|
- Purpose: Specifically Designed for providing Non-LLM AI Agent Framework for edge Devices, Optimized for ARM64 architecture.
|
|
@@ -62,7 +62,7 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
|
|
|
62
62
|
- Proven Works on ARM64 Environment, Training and Prediction works efficient on Docker ARM64 environment with QEMU, good parallelizing behavior is guaranteed.
|
|
63
63
|
- P2P Works efficiently in ARM64 Docker + QEMU, No conflicting socket and all prediction works efficiently.
|
|
64
64
|
- AWE setup Proven Efficient on Hard-uncontrolled dataset such as Activity Recognition from the given Database.
|
|
65
|
-
|
|
65
|
+
- LSTM is Optimized efficiently for scarce data with AWE method.
|
|
66
66
|
-----
|
|
67
67
|
|
|
68
68
|
<img width="1280" height="600" alt="WhatsApp Image 2026-05-27 at 07 16 32" src="https://github.com/user-attachments/assets/4b58a556-45a3-419b-96fd-9c1b76cac574" />
|
|
@@ -10,7 +10,7 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
|
|
|
10
10
|
#### Note: The README here you are reading is a direct copy from my README Repository, to download the necessary files, you can visit my Repository with the provided link above.
|
|
11
11
|
|
|
12
12
|
### Library Short Description:
|
|
13
|
-
- Development Stage: Beta, 0.
|
|
13
|
+
- Development Stage: Beta, 0.4.1.
|
|
14
14
|
- Maintainer: Micro-Novelty.
|
|
15
15
|
- library Source-Code is Open-sourced on github.
|
|
16
16
|
- Purpose: Specifically Designed for providing Non-LLM AI Agent Framework for edge Devices, Optimized for ARM64 architecture.
|
|
@@ -30,7 +30,7 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
|
|
|
30
30
|
- Proven Works on ARM64 Environment, Training and Prediction works efficient on Docker ARM64 environment with QEMU, good parallelizing behavior is guaranteed.
|
|
31
31
|
- P2P Works efficiently in ARM64 Docker + QEMU, No conflicting socket and all prediction works efficiently.
|
|
32
32
|
- AWE setup Proven Efficient on Hard-uncontrolled dataset such as Activity Recognition from the given Database.
|
|
33
|
-
|
|
33
|
+
- LSTM is Optimized efficiently for scarce data with AWE method.
|
|
34
34
|
-----
|
|
35
35
|
|
|
36
36
|
<img width="1280" height="600" alt="WhatsApp Image 2026-05-27 at 07 16 32" src="https://github.com/user-attachments/assets/4b58a556-45a3-419b-96fd-9c1b76cac574" />
|
|
@@ -8,7 +8,7 @@ class BinaryDistribution(Distribution):
|
|
|
8
8
|
|
|
9
9
|
setup(
|
|
10
10
|
name="AbstractIntegratedModule",
|
|
11
|
-
version="0.
|
|
11
|
+
version="0.4.1",
|
|
12
12
|
description="Framework for Advanced Integrated Non-LLM AI Module library - Backend Framework for Non-LLM AI Agent Framework",
|
|
13
13
|
long_description=open("README.md", encoding="utf-8").read(),
|
|
14
14
|
long_description_content_type="text/markdown",
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|