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.
Files changed (16) hide show
  1. abstractintegratedmodule-0.4.1/AbstractIntegratedModule.cp313-win_amd64.pyd +0 -0
  2. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.cpython-310-aarch64-linux-gnu.so +0 -0
  3. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.cpython-312-x86_64-linux-gnu.so +0 -0
  4. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1/AbstractIntegratedModule.egg-info}/PKG-INFO +3 -3
  5. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.py +406 -252
  6. {abstractintegratedmodule-0.3.9/AbstractIntegratedModule.egg-info → abstractintegratedmodule-0.4.1}/PKG-INFO +3 -3
  7. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/README.md +2 -2
  8. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/setup.py +1 -1
  9. abstractintegratedmodule-0.3.9/AbstractIntegratedModule.cp313-win_amd64.pyd +0 -0
  10. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/SOURCES.txt +0 -0
  11. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/dependency_links.txt +0 -0
  12. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/requires.txt +0 -0
  13. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/AbstractIntegratedModule.egg-info/top_level.txt +0 -0
  14. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/MANIFEST.in +0 -0
  15. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/pyproject.toml +0 -0
  16. {abstractintegratedmodule-0.3.9 → abstractintegratedmodule-0.4.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AbstractIntegratedModule
3
- Version: 0.3.9
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.3.9.
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" />
@@ -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
- self.floating_context = None
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(eps, mag, size=raw_X.shape)
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
- K_G = 1.0 / (1.0 + k)
331
- mag_G = 1.0 / (1.0 + K_G)
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 + mag_G) / (1.0 + trA**2)
335
- trC = (1/6 + K_G) / (trB**2 - 1.0)
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 * (1.0 - np.std(x)) + eps
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
- floating_point = np.random.uniform(1e-10, trC, size=X.shape)
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
- efficient_distributed_energy = k + AEL * (1.0 - AMR)
418
- if np.isnan(efficient_distributed_energy) or np.isinf(efficient_distributed_energy):
419
- efficient_distributed_energy = (1 - AMR) + eps
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
- return floating_context
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
- floating_context = self.abstract_weight_shaping(x)
437
- if np.isnan(floating_context).any() or not np.isfinite(floating_context).any():
438
- floating_context = np.ones_like(x)
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
- x_seq : (T, input_size)
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, cs = np.zeros((T, H)), np.zeros((T, H))
1455
- cache = [] # store everything needed for backward
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 = x_seq[t]
1462
+ x = x_seq[t]
1459
1463
  if x.ndim == 0:
1460
- x = x.reshape(1) # fix zero-dimensional
1464
+ x = x.reshape(1)
1461
1465
  if x.shape[0] < expected_input:
1462
- x = np.pad(x, (0, expected_input - x.shape[0])) # pad if too small
1466
+ x = np.pad(x, (0, expected_input - x.shape[0]))
1463
1467
  elif x.shape[0] > expected_input:
1464
- x = x[:expected_input] # truncate if too large
1465
-
1466
- xh = np.concatenate([x, h]) # (input+hidden,)
1468
+ x = x[:expected_input]
1467
1469
 
1468
- z = self.W @ xh + self.b # (4H,)
1469
- f = sigmoid(self._f(z)) # forget
1470
- i = sigmoid(self._i(z)) # input
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
- c_new = f * c + i * g
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 = o * tanh_c
1485
+ h_new = o * tanh_c
1477
1486
 
1478
- cache.append((x, h, c, f, i, g, o, c_new, tanh_c, xh))
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], cs[t] = h, c
1491
+ hs[t] = h
1492
+ cs[t] = c
1481
1493
 
1482
1494
  return hs, cs, cache
1483
1495
 
1484
- # ── backward (BPTT) ──────────────────────
1485
- def backward(self, dhs: np.ndarray, cache, dh_next=None, dc_next=None):
1486
- """
1487
- dhs : (T, hidden) — gradient of loss w.r.t. each hidden state
1488
- Returns: gradients dict + dx_seq
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 = np.zeros_like(self.W)
1494
- db = np.zeros_like(self.b)
1495
- dh = np.zeros(H) if dh_next is None else dh_next.copy()
1496
- dc = np.zeros(H) if dc_next is None else dc_next.copy()
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 # gradient from loss + recurrence
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 # flows back to previous cell state
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
- dz = np.concatenate([df_pre, di_pre, dg_pre, do_pre])
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
- dW += np.outer(dz, xh)
1533
+ # nplace accumulation, no intermediate allocation
1534
+ dW += np.outer(dz, xh) # unavoidable alloc but outer is C-level
1526
1535
  db += dz
1527
- dxh = self.W.T @ dz
1528
- dx_seq[t] = dxh[:self.input_size]
1529
- dh = dxh[self.input_size:]
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 = LSTMCell(input_size, hidden_size, seed)
1546
- H = hidden_size
1547
-
1548
- self.weight_shaper = GeometricWeightShaping(output_size, H)
1549
- self.Wy = None
1550
- self.by = np.zeros(output_size)
1551
- self.pipeline = pipeline
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
- # linear projection at every timestep
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
- if preds.shape != targets.shape:
1564
- if preds.shape[0] > targets.shape[0]:
1565
- targets = targets[:preds.shape[0], :]
1566
- if targets.shape[0] > preds.shape[0]:
1567
- preds = preds[:targets.shape[0], :]
1568
- else:
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
- return AMR * np.mean(diff ** 2), diff / len(diff)
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
- # gradient through linear head
1576
- min_T = min(dpreds.shape[0], hs.shape[0])
1577
- dpreds = dpreds[:min_T, :] # (min_T, out)
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 # (out, hidden)
1584
+ dWy = dpreds.T @ hs
1581
1585
  dby = dpreds.sum(axis=0)
1582
- dhs = dpreds @ self.Wy # (T, hidden)
1586
+ dhs = dpreds @ self.Wy
1583
1587
 
1584
- cell_grads, dx, _, _ = self.cell.backward(dhs, cache[:min_T])
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
- grad = np.clip(grad, -clip, clip)
1595
+ np.clip(grad, -clip, clip, out=grad)
1591
1596
  param -= lr * grad
1592
1597
 
1593
- clip_and_step(self.cell.W, cell_grads["dW"])
1594
- clip_and_step(self.cell.b, cell_grads["db"])
1595
- clip_and_step(self.Wy, out_grads["dWy"])
1596
- clip_and_step(self.by, out_grads["dby"])
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
- def train_step(self, x_seq, targets, lr=1e-3):
1599
- if self.Wy is None:
1600
- self.Wy = self.weight_shaper.weight_shaping(x_seq)
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 calibrate(self, X_val, Y_val):
1653
- """
1654
- Collect residuals on clean (no-dropout) val predictions.
1655
- Must be called before predict().
1656
- """
1657
- residuals = []
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
- residuals = np.array(residuals)
1664
- self.residual_mean = residuals.mean()
1665
- self.residual_std = residuals.std()
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
- f"[=] σ={self.residual_std:.4f}")
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) -> Any:
1671
- """
1672
- One stochastic forward pass — dropout applied to h
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 = self.dropout
1679
-
1680
- h = np.zeros(H)
1681
- c = np.zeros(H)
1682
- preds = []
1683
-
1684
- cell = self.model.cell
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 = x_seq[t]
1734
+ x = x_seq[t]
1735
+
1736
+ # shape alignment
1688
1737
  if x.ndim == 0:
1689
- x = x.reshape(1) # fix zero-dimensional
1738
+ x = x.reshape(1)
1690
1739
  if x.shape[0] < expected_input:
1691
- x = np.pad(x, (0, expected_input - x.shape[0])) # pad if too small
1740
+ x = np.pad(x, (0, expected_input - x.shape[0]))
1692
1741
  elif x.shape[0] > expected_input:
1693
- x = x[:expected_input] # truncate if too large
1694
- xh = np.concatenate([x, h])
1695
- z = cell.W @ xh + cell.b
1742
+ x = x[:expected_input]
1696
1743
 
1697
- # gate activations
1698
- f = sigmoid(cell._f(z))
1699
- i = sigmoid(cell._i(z))
1700
- g = np.tanh(cell._g(z))
1701
- o = sigmoid(cell._o(z))
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
- # ── dropout on h, inverted scaling ──
1708
- mask = (np.random.rand(H) > p).astype(float) / (1.0 - p)
1709
- h = h * mask # perturb hidden state only
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
- self.model.Wy = self.model.weight_shaper.weight_shaping(x_seq)
1764
+ preds[t] = (h @ Wy.T + by)[0]
1715
1765
 
1716
- pred = h @ self.model.Wy.T + self.model.by
1717
- preds.append(pred[0])
1766
+ return preds # (T,)
1718
1767
 
1719
- return np.array(preds) # (T,)
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
- return np.array(gate_uncertainty) # (T,)
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
- residuals = []
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
- min_T = min(preds.shape[0], Y_val[j].shape[0])
1754
- err = preds[:min_T, 0] - Y_val[j, :min_T, 0]
1755
- residuals.extend(err.tolist())
1756
-
1757
- residuals = np.array(residuals)
1758
- self.residual_std = residuals.std()
1759
- self.residual_mean = residuals.mean()
1760
-
1761
- # store empirical quantiles instead of assuming normality
1762
- self.quantiles = {
1763
- 0.90: (np.percentile(residuals, 5),
1764
- np.percentile(residuals, 95)),
1765
- 0.95: (np.percentile(residuals, 2.5),
1766
- np.percentile(residuals, 97.5)),
1767
- 0.99: (np.percentile(residuals, 0.5),
1768
- np.percentile(residuals, 99.5)),
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
- for name, (lo, hi) in label_bins.items():
1790
- hits = ((mc_samples_last >= lo) & (mc_samples_last < hi)).sum()
1791
- label_conf[name] = hits / n
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
- return label_conf
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
- low, high = self._interval(mc_mean, confidence_level)
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" : low,
1993
- "interval_high" : 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
- a = np.fromstring(clean_str, sep=' ')
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
- final_probs = mlp_weight * target_probs[i][:len(calibration)] + trans_weight * calibration[i][:len(calibration)]
11825
-
11826
- final_class_idx = target_class_idx
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[⚠️] High confidence detected, but both models don't agree. Using calibrated probabilities for final decision to ensure robustness.")
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.9
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.3.9.
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.3.9.
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.3.9",
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",