AbstractIntegratedModule 0.7.6__tar.gz → 0.7.7__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 (23) hide show
  1. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractIntegratedModule.egg-info/PKG-INFO +7 -5
  2. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractIntegratedModule.py +321 -104
  3. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractOptimizedModules.c +352 -301
  4. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/PKG-INFO +7 -5
  5. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/README.md +6 -4
  6. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/setup.py +1 -1
  7. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractIntegratedModule.egg-info/SOURCES.txt +0 -0
  8. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractIntegratedModule.egg-info/dependency_links.txt +0 -0
  9. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractIntegratedModule.egg-info/requires.txt +0 -0
  10. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractIntegratedModule.egg-info/top_level.txt +0 -0
  11. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/AbstractOptimizedModules.pyx +0 -0
  12. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/MANIFEST.in +0 -0
  13. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/Cargo.toml +0 -0
  14. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/pyproject.toml +0 -0
  15. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/src/lib.rs +0 -0
  16. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/target/debug/build/libsqlite3-sys-ed07b882cd2aa5e2/out/bindgen.rs +0 -0
  17. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/target/debug/build/serde_core-ebc15f2e9cad7f5f/out/private.rs +0 -0
  18. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/target/debug/build/target-lexicon-08527f45de28143d/out/host.rs +0 -0
  19. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/target/release/build/libsqlite3-sys-bf0400df4523274c/out/bindgen.rs +0 -0
  20. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/target/release/build/serde_core-5cdb76131825e4af/out/private.rs +0 -0
  21. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/abstract_model_storage/target/release/build/target-lexicon-43eb95a0588bf457/out/host.rs +0 -0
  22. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/pyproject.toml +0 -0
  23. {abstractintegratedmodule-0.7.6 → abstractintegratedmodule-0.7.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AbstractIntegratedModule
3
- Version: 0.7.6
3
+ Version: 0.7.7
4
4
  Summary: Library for Advanced Integrated Non-LLM AI Models - Optimized Backend Framework For Non-LLM AI Agent
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: 0.7.6 Official Release.
45
+ - Development Stage: 0.7.7 Official Release.
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.
@@ -66,10 +66,12 @@ https://github.com/Micro-Novelty/IntegratedPipeline-Specialized-Non-LLM-AI-Agent
66
66
  - Robust Advanced prediction capabilities proven effective on ARM64 Using MLP + LSTM Architectures.
67
67
  - Transformer Modules Optimized using Cython, to reduce Memory overhead and Reduce CPU Usage, With Reduced Training Time.
68
68
  - Changelog:
69
- - v0.7.6:
69
+ - v0.7.7:
70
70
  - [=] New features:
71
- - Adding Optimization and refinements for parsing JSON in Rust.
72
- - Ensure robustness in calculating Samples similarity to prevent misleading answers in different environment.
71
+ - Fixed shape handling fragility in batching predictions.
72
+ - Ensuring proper number of classes during batcthing predictions to prevent shape error.
73
+ - Fixed text encoder and encode function that can cause token mismatch.
74
+
73
75
 
74
76
  -----
75
77
 
@@ -8057,7 +8057,11 @@ class IntegratedPipeline:
8057
8057
  self._agent_mode = os.environ.get('AGENT_MODE', 'single')
8058
8058
  self._agent_port = int(os.environ.get('AGENT_PORT', 5555))
8059
8059
  self._use_async = os.environ.get('USE_ASYNC_QUEUE', 'true').lower() == 'true'
8060
-
8060
+
8061
+ # Special token indices — reserve before any real words
8062
+ self._PAD_IDX = 0
8063
+ self._UNK_IDX = 1
8064
+ self._SPECIAL_TOKENS = {'[PAD]': self._PAD_IDX, '[UNK]': self._UNK_IDX}
8061
8065
 
8062
8066
  print(f'[= PORT =] IntegratedPipeline initialized on port {self._agent_port}')
8063
8067
 
@@ -8087,9 +8091,12 @@ class IntegratedPipeline:
8087
8091
  self.autonomous = False
8088
8092
  self.show_explainability_details = True
8089
8093
  self.froze_learning = False
8094
+ self._cache_save_count = None
8095
+ self._prob_save_count = None
8090
8096
 
8091
8097
  self.temperature = 1.0
8092
8098
  self.transformer_lr = 0.1
8099
+ self.max_seq_len = 16
8093
8100
 
8094
8101
  self.memory_name = memory_name
8095
8102
 
@@ -8280,7 +8287,7 @@ class IntegratedPipeline:
8280
8287
  f'only supports {num_classes} — expanding to {unique_in_batch}')
8281
8288
  num_classes = unique_in_batch
8282
8289
  elif unique_in_batch < num_classes:
8283
- # batch just doesn't happen to contain all classes — this is FINE,
8290
+ # batch just doesn't happen to contain all classes.
8284
8291
  # use the model's full num_classes so onehot stays the right width
8285
8292
  print(f'[=] Batch only contains {unique_in_batch}/{num_classes} classes '
8286
8293
  f'— using full model class count for onehot width')
@@ -8352,40 +8359,115 @@ class IntegratedPipeline:
8352
8359
  return layer1, layer2
8353
8360
 
8354
8361
 
8362
+
8355
8363
  def text_encoder(self, texts):
8364
+ """
8365
+ Build vocabulary from texts incrementally.
8366
+ Reserves 0=[PAD], 1=[UNK].
8367
+ """
8356
8368
  vocab = self.vocab
8357
- idx = 0
8369
+
8370
+ if not vocab:
8371
+ vocab.update(self._SPECIAL_TOKENS)
8372
+
8373
+ # track next free index from actual max value in vocab,
8374
+ # not len() which breaks if any entries were ever removed
8375
+ idx = max(vocab.values()) + 1 if vocab else len(self._SPECIAL_TOKENS)
8376
+
8358
8377
  for item in texts:
8359
- texts = item[0] if isinstance(item, tuple) else item
8360
- for word in texts.split():
8378
+ text = item[0] if isinstance(item, tuple) else item
8379
+ if not isinstance(text, str) or not text.strip():
8380
+ continue # skip None/empty safely
8381
+
8382
+ for word in text.lower().split():
8361
8383
  if word not in vocab:
8362
8384
  vocab[word] = idx
8363
8385
  idx += 1
8364
8386
 
8365
8387
 
8366
- def encode(self, sentence, vocab, max_len=6):
8367
- tokens = sentence.split()
8368
- ids = [vocab.get(w, 0) for w in tokens]
8369
- while len(ids) < max_len:
8370
- ids.append(0)
8371
-
8372
- return ids[:max_len]
8388
+ def encode(self, sentence, vocab, max_len=None):
8389
+ """
8390
+ Encode sentence to token ids.
8391
+ Unknown words → [UNK] (1). Shorter sequences → [PAD] (0) padded.
8392
+ """
8393
+ # guard against None/empty input
8394
+ if not isinstance(sentence, str) or not sentence.strip():
8395
+ pad_len = max_len or 6
8396
+ return [self._PAD_IDX] * pad_len
8397
+
8398
+ # dynamic max_len from vocab's seen sentence lengths
8399
+ # if not explicitly provided, derive from pipeline config or vocab size
8400
+ if max_len is None:
8401
+ max_len = getattr(self, 'max_seq_len', 16) # configurable, default 16 not 6
8402
+
8403
+ tokens = sentence.lower().split()
8404
+ ids = [vocab.get(w, self._UNK_IDX) for w in tokens]
8405
+
8406
+ # informative truncation warning with actual token count
8407
+ if len(tokens) > max_len:
8408
+ print(f'[!] Truncated "{sentence[:40]}..." '
8409
+ f'({len(tokens)} tokens → {max_len})')
8373
8410
 
8411
+ # pad or truncate to exact max_len
8412
+ ids = ids[:max_len]
8413
+ ids.extend([self._PAD_IDX] * (max_len - len(ids)))
8374
8414
 
8415
+ return ids
8375
8416
 
8376
8417
  def input_encoding(self, datasets):
8377
- texts = [d[0] for d in datasets]
8418
+ texts = [d[0] for d in datasets]
8378
8419
  intents = [d[1] for d in datasets]
8379
- intent_to_id = {intent:i for i, intent in enumerate(sorted(set(intents)))}
8380
- num_classes = self._get_num_classes()
8420
+
8421
+ intent_to_id = {intent: i for i, intent in enumerate(sorted(set(intents)))}
8422
+ batch_classes = len(intent_to_id)
8423
+
8424
+ model_classes = self._get_num_classes()
8425
+
8426
+ if model_classes is None:
8427
+ num_classes = batch_classes
8428
+ print(f'[=] No existing model — using batch class count: {num_classes}')
8429
+
8430
+ elif batch_classes > model_classes:
8431
+ print(f'[⚠️] input_encoding: batch has {batch_classes} classes '
8432
+ f'but model only supports {model_classes} — '
8433
+ f'expanding num_classes to {batch_classes}')
8434
+ num_classes = batch_classes
8435
+
8436
+ elif batch_classes < model_classes:
8437
+ # batch is a SUBSET of known classes
8438
+ print(f'[=] input_encoding: batch has {batch_classes}/{model_classes} '
8439
+ f'classes — using full model class count')
8440
+ num_classes = model_classes
8441
+
8442
+ # remap intent_to_id to GLOBAL indices from the stored label_map
8443
+ # so index 2 in this batch actually means class 2 globally,
8444
+ if hasattr(self, 'label_map') and self.label_map:
8445
+ remapped = {}
8446
+ for intent in intent_to_id:
8447
+ if intent in self.label_map:
8448
+ remapped[intent] = self.label_map[intent]
8449
+ else:
8450
+ # genuinely new intent not in global map — append at end
8451
+ remapped[intent] = max(self.label_map.values()) + 1
8452
+ print(f'[⚠️] Unknown intent "{intent}" not in label_map — '
8453
+ f'assigned index {remapped[intent]}')
8454
+ intent_to_id = remapped
8455
+ else:
8456
+ num_classes = model_classes
8457
+
8381
8458
  labels = [intent_to_id[i] for i in intents]
8382
8459
 
8383
- reverse_map = {}
8384
- for i in range(len(intents)):
8385
- reverse_map[i] = intents[i]
8460
+ # validate all labels are in range
8461
+ max_label = max(labels) if labels else 0
8462
+ if max_label >= num_classes:
8463
+ print(f'[⚠️] Max label index {max_label} >= num_classes {num_classes} '
8464
+ f'— expanding num_classes to {max_label + 1}')
8465
+ num_classes = max_label + 1
8386
8466
 
8387
- self.texts = texts
8388
- self.intents = intents
8467
+ reverse_map = {i: intent for intent, i in intent_to_id.items()}
8468
+
8469
+ self.texts = texts
8470
+ self.intents = intents
8389
8471
  self.reverse_map = reverse_map
8390
8472
 
8391
8473
  self.model2 = Transformer(
@@ -8393,17 +8475,21 @@ class IntegratedPipeline:
8393
8475
  d_model=32,
8394
8476
  n_heads=4,
8395
8477
  num_classes=num_classes
8396
- )
8478
+ )
8397
8479
 
8480
+ # safe y_true construction with explicit bounds guard per row
8398
8481
  y_true = np.zeros((len(labels), num_classes))
8399
- for i,l in enumerate(labels):
8400
- y_true[i,l] = 1
8401
-
8402
- input_ids_list = []
8482
+ for i, l in enumerate(labels):
8483
+ if 0 <= l < num_classes:
8484
+ y_true[i, l] = 1.0
8485
+ else:
8486
+ print(f'[⚠️] Label {l} out of range for num_classes={num_classes} '
8487
+ f'at sample {i} (intent="{intents[i]}") — row left as zeros')
8403
8488
 
8404
- input_ids_list = []
8405
- for text in texts:
8406
- input_ids_list.append(np.array(self.encode(text, self.vocab)))
8489
+ input_ids_list = [
8490
+ np.array(self.encode(text, self.vocab))
8491
+ for text in texts
8492
+ ]
8407
8493
 
8408
8494
  return input_ids_list, y_true
8409
8495
 
@@ -9553,107 +9639,212 @@ class IntegratedPipeline:
9553
9639
 
9554
9640
  def _batch_hybrid_prediction(self, batch_input_ids, batch_X_raw, y_true):
9555
9641
  print('[+] Initiating hybrid prediction batching...')
9556
- idx_total = 0
9642
+
9643
+ num_classes = self._get_num_classes()
9644
+ if num_classes is None:
9645
+ print('[⚠️] num_classes unavailable — cannot safely batch predict')
9646
+ return np.zeros((len(batch_input_ids), 1))
9647
+
9648
+ zero_row = np.zeros(num_classes) # reusable fallback shape
9557
9649
  batch_probs = self._batch_model_memory_gate(batch_input_ids, batch_X_raw)
9558
-
9650
+ fresh_probs = None # FIX 1 — explicit init, no UnboundLocalError
9651
+
9559
9652
  needs_prediction = [i for i, p in enumerate(batch_probs) if p is None]
9560
-
9653
+
9561
9654
  if needs_prediction:
9562
9655
  fresh_input_ids = batch_input_ids[needs_prediction]
9563
- fresh_X_raw = batch_X_raw[needs_prediction]
9564
-
9565
- # Batch ensemble
9656
+ fresh_X_raw = batch_X_raw[needs_prediction]
9657
+
9658
+ # slice y_true to match the fresh subset only
9659
+ if y_true is not None and hasattr(y_true, '__len__'):
9660
+ fresh_y_true = y_true[needs_prediction] \
9661
+ if len(y_true) == len(batch_input_ids) \
9662
+ else np.zeros((len(needs_prediction), num_classes))
9663
+ else:
9664
+ fresh_y_true = np.zeros((len(needs_prediction), num_classes))
9665
+
9566
9666
  fresh_probs, details = self.ensemble.predict_ensemble(
9567
- fresh_input_ids, fresh_X_raw, y_true, method='dynamic', embedded=False
9667
+ fresh_input_ids, fresh_X_raw, fresh_y_true,
9668
+ method='dynamic', embedded=False
9568
9669
  )
9569
-
9670
+
9671
+ # validate fresh_probs shape before assignment
9672
+ fresh_probs = np.asarray(fresh_probs)
9673
+ if fresh_probs.ndim == 1:
9674
+ fresh_probs = fresh_probs[np.newaxis, :]
9675
+
9570
9676
  for i, idx in enumerate(needs_prediction):
9571
- batch_probs[idx] = fresh_probs[i]
9572
- idx_total += 1
9573
- if idx_total < 2:
9677
+ row = fresh_probs[i]
9678
+
9679
+ # align to expected num_classes if shape drifted
9680
+ if row.shape[0] != num_classes:
9681
+ aligned = np.zeros(num_classes)
9682
+ min_len = min(row.shape[0], num_classes)
9683
+ aligned[:min_len] = row[:min_len]
9684
+ row = aligned
9685
+
9686
+ batch_probs[idx] = row
9687
+
9688
+ # instance-level counter
9689
+ if getattr(self, '_cache_save_count', 0) < 2:
9574
9690
  self.modular_prediction_saving(
9575
- fresh_input_ids[i:i+1],
9576
- fresh_X_raw[i:i+1],
9577
- fresh_probs[i:i+1]
9691
+ fresh_input_ids[i:i+1],
9692
+ fresh_X_raw[i:i+1],
9693
+ row[np.newaxis, :]
9578
9694
  )
9695
+ self._cache_save_count = getattr(self, '_cache_save_count', 0) + 1
9579
9696
 
9697
+ # build valid_probs with guaranteed consistent shape
9580
9698
  valid_probs = []
9581
- for p in batch_probs:
9699
+ for i, p in enumerate(batch_probs):
9582
9700
  if p is None:
9583
- # Use zeros as fallback for None values
9584
- valid_probs.append(np.zeros_like(fresh_probs[0]))
9701
+ # zero_row always defined, no UnboundLocalError
9702
+ valid_probs.append(zero_row.copy())
9585
9703
  elif isinstance(p, list):
9586
- valid_probs.append(np.array(p))
9587
- else:
9704
+ arr = np.array(p, dtype=np.float64)
9705
+ if arr.shape[0] != num_classes:
9706
+ aligned = np.zeros(num_classes)
9707
+ aligned[:min(arr.shape[0], num_classes)] = arr[:num_classes]
9708
+ arr = aligned
9709
+ valid_probs.append(arr)
9710
+ elif isinstance(p, np.ndarray):
9711
+ if p.shape[0] != num_classes:
9712
+ aligned = np.zeros(num_classes)
9713
+ aligned[:min(p.shape[0], num_classes)] = p[:num_classes]
9714
+ p = aligned
9588
9715
  valid_probs.append(p)
9589
-
9716
+ else:
9717
+ print(f'[⚠️] Unexpected type in batch_probs[{i}]: {type(p)} — using zeros')
9718
+ valid_probs.append(zero_row.copy())
9719
+
9720
+ # single explicit conversion with clear error, no nested bare except
9721
+ if not valid_probs:
9722
+ print('[⚠️] No valid probabilities collected — returning zeros')
9723
+ return np.zeros((len(batch_input_ids), num_classes))
9724
+
9590
9725
  try:
9591
- try:
9592
- return np.array(valid_probs)
9593
- except:
9594
- return valid_probs
9595
- except Exception as e:
9596
- print(f'[-] Error converting batch probabilities to array: {e}')
9726
+ result = np.stack(valid_probs) # stack guarantees shape (N, num_classes)
9727
+ # np.array() on ragged list gives object array
9728
+ # np.stack() fails fast if shapes disagree
9729
+ return result
9730
+ except ValueError as e:
9731
+ print(f'[⚠️] Stack failed (shape mismatch): {e}'
9732
+ f'shapes: {[p.shape for p in valid_probs]}')
9733
+ # last resort return
9597
9734
  return valid_probs
9598
-
9735
+
9599
9736
 
9600
9737
  def _batch_predict_proba(self, batch_input_ids, batch_X, type='Hybrid'):
9601
9738
  batch_size = len(batch_input_ids)
9602
- idx_total = 0
9603
9739
 
9604
- # Batch memory gate
9740
+ output_memory = self._batch_model_memory_gate(batch_input_ids, batch_X)
9741
+ num_classes = self._get_num_classes()
9742
+ if num_classes is None:
9743
+ print('[⚠️] num_classes unavailable in prediction batching function!')
9744
+ return np.zeros((batch_size, 1))
9745
+
9746
+ zero_row = np.zeros(num_classes)
9605
9747
  batch_probs = [None] * batch_size
9748
+ fresh_probs = None # explicit init, no UnboundLocalError
9749
+
9750
+
9606
9751
  for i in range(batch_size):
9607
9752
  probs = self.model_probability_gate(
9608
- batch_input_ids[i:i+1],
9753
+ batch_input_ids[i:i+1],
9609
9754
  batch_X[i:i+1]
9610
9755
  )
9611
9756
  if probs is not None:
9612
9757
  batch_probs[i] = probs[0]
9613
-
9614
- # Find which need fresh prediction
9758
+
9759
+
9615
9760
  needs_prediction = [i for i, p in enumerate(batch_probs) if p is None]
9616
-
9761
+
9617
9762
  if needs_prediction:
9618
9763
  fresh_input_ids = batch_input_ids[needs_prediction]
9619
- fresh_X = batch_X[needs_prediction]
9620
-
9621
- # Batch transformer and MLP
9764
+ fresh_X = batch_X[needs_prediction]
9765
+
9622
9766
  transformer_pred, fresh_probs, attn_weights = self.model2.predict(fresh_input_ids)
9623
9767
  mlp_pred = self.mlp.forward(fresh_X)
9624
-
9625
- mlp_pred_indices = np.argmax(mlp_pred, axis=1)
9626
- trans_pred_indices = np.argmax(fresh_probs, axis=1)
9627
-
9628
- # Calibrate batch
9768
+
9769
+ # coerce indices to int
9770
+ mlp_pred_indices = np.argmax(mlp_pred, axis=1).astype(int)
9771
+ trans_pred_indices = np.argmax(fresh_probs, axis=1).astype(int)
9772
+
9629
9773
  for i, idx in enumerate(needs_prediction):
9630
- idx_total += 1
9631
- if mlp_pred_indices[i] != trans_pred_indices[i]:
9774
+
9775
+ # validate indices in range before use
9776
+ mlp_cls = int(mlp_pred_indices[i])
9777
+ trans_cls = int(trans_pred_indices[i])
9778
+
9779
+ mlp_cls = mlp_cls if 0 <= mlp_cls < num_classes else 0
9780
+ trans_cls = trans_cls if 0 <= trans_cls < num_classes else 0
9781
+
9782
+ if mlp_cls != trans_cls:
9632
9783
  calibrated = self._calibrate_probs(
9633
- fresh_probs[i:i+1],
9634
- [mlp_pred_indices[i]],
9784
+ fresh_probs[i:i+1],
9785
+ [mlp_cls],
9635
9786
  attn_weights[i:i+1] if attn_weights is not None else None,
9636
9787
  fresh_input_ids[i:i+1]
9637
9788
  )
9638
- batch_probs[idx] = calibrated[0]
9789
+ # validate calibrated output shape
9790
+ row = np.asarray(calibrated[0])
9791
+ if row.shape[0] != num_classes:
9792
+ aligned = np.zeros(num_classes)
9793
+ aligned[:min(row.shape[0], num_classes)] = row[:num_classes]
9794
+ row = aligned
9795
+ batch_probs[idx] = row
9796
+
9639
9797
  else:
9640
- # Models agree
9641
- probs_i = fresh_probs[i].copy()
9642
- target = mlp_pred_indices[i]
9643
- probs_i[target] = min(probs_i[target] * 1.2, 0.95)
9644
- probs_i /= probs_i.sum()
9798
+ # models agree — boost confidence on agreed class
9799
+ probs_i = fresh_probs[i].copy()
9800
+ probs_i[trans_cls] = min(probs_i[trans_cls] * 1.2, 0.95)
9801
+ row_sum = probs_i.sum()
9802
+ probs_i /= row_sum if row_sum > 1e-8 else 1.0
9645
9803
  batch_probs[idx] = probs_i
9646
-
9647
- # Save to memory
9648
- if idx_total < 2:
9804
+
9805
+ # instance-level save counter, not local idx_total
9806
+ if getattr(self, '_prob_save_count', 0) < 2:
9649
9807
  self.modular_probability_saving(
9650
- fresh_input_ids[i:i+1],
9651
- fresh_X[i:i+1],
9808
+ fresh_input_ids[i:i+1],
9809
+ fresh_X[i:i+1],
9652
9810
  np.array([batch_probs[idx]])
9653
9811
  )
9654
-
9655
- return np.array(batch_probs)
9812
+ self._prob_save_count = getattr(self, '_prob_save_count', 0) + 1
9656
9813
 
9814
+ else:
9815
+ raise Warning('[!] Data is None before batching!')
9816
+
9817
+ # safe final assembly with consistent shape
9818
+ valid_probs = []
9819
+ for i, p in enumerate(batch_probs):
9820
+ if p is None:
9821
+ valid_probs.append(zero_row.copy())
9822
+ elif isinstance(p, list):
9823
+ arr = np.array(p, dtype=np.float64)
9824
+ if arr.shape[0] != num_classes:
9825
+ aligned = np.zeros(num_classes)
9826
+ aligned[:min(arr.shape[0], num_classes)] = arr[:num_classes]
9827
+ arr = aligned
9828
+ valid_probs.append(arr)
9829
+ elif isinstance(p, np.ndarray):
9830
+ if p.shape[0] != num_classes:
9831
+ aligned = np.zeros(num_classes)
9832
+ aligned[:min(p.shape[0], num_classes)] = p[:num_classes]
9833
+ p = aligned
9834
+ valid_probs.append(p)
9835
+ else:
9836
+ if output_memory is not None:
9837
+ print('[=] Unexpected Sample type in batch probability, Using previous memory to fill gaps in Samples Ambiguity')
9838
+ valid_probs.append(output_memory)
9839
+ else:
9840
+ print(f'[⚠️] Unexpected Sample type in batch_probs[{i}]: {type(p)} — using zeros to fill in valid probability')
9841
+ valid_probs.append(zero_row.copy())
9842
+
9843
+ try:
9844
+ return np.stack(valid_probs)
9845
+ except ValueError as e:
9846
+ print(f'[⚠️] Stack failed: {e} — shapes: {[p.shape for p in valid_probs]}')
9847
+ return valid_probs
9657
9848
 
9658
9849
 
9659
9850
  def hybrid_prediction(self, rules, input_ids, dataset):
@@ -10370,6 +10561,23 @@ class IntegratedPipeline:
10370
10561
 
10371
10562
  return X, Y
10372
10563
 
10564
+ def _sync_vocab_to_embedding(self):
10565
+ """Extend token_embedding rows if vocab has grown since model creation.
10566
+ Preserves all existing trained weights — only appends new rows.
10567
+ """
10568
+ if self.model2 is None:
10569
+ return
10570
+ current_emb_size = self.model2.token_embedding.shape[0]
10571
+ current_vocab_size = len(self.vocab)
10572
+ if current_vocab_size > current_emb_size:
10573
+ d_model = self.model2.d_model
10574
+ n_new = current_vocab_size - current_emb_size
10575
+ new_rows = np.random.randn(n_new, d_model) * 0.02
10576
+ self.model2.token_embedding = np.vstack([
10577
+ self.model2.token_embedding, new_rows
10578
+ ])
10579
+ print(f'[=] token embedding grown: {current_emb_size} → {current_vocab_size}')
10580
+
10373
10581
 
10374
10582
  def _extract_cache_samples_for_lstm(self, target_count):
10375
10583
  """
@@ -10531,25 +10739,34 @@ class IntegratedPipeline:
10531
10739
 
10532
10740
  def sequence_encoding(self, datasets, max_len=32):
10533
10741
  input_sequences = []
10534
- for item in datasets:
10535
- if not self.model2:
10536
- intents = [d[1] for d in datasets]
10537
- intent_to_id = {intent:i for i, intent in enumerate(sorted(set(intents)))}
10538
- num_classes = self._get_num_classes()
10539
- self.model2 = Transformer(
10540
- vocab_size=len(self.vocab),
10541
- d_model=32,
10542
- n_heads=4,
10543
- num_classes=num_classes
10544
- )
10545
10742
 
10546
- text = item[0] if isinstance(item, tuple) else item
10547
- token_ids = self.encode(text, self.vocab, max_len=max_len)
10548
- token_embs = self.model2.token_embedding[token_ids] # (max_len, d_model)
10549
- pos_embs = self.model2.pos_embedding[:max_len] # (max_len, d_model)
10550
- sequence_input = token_embs + pos_embs
10551
- input_sequences.append(sequence_input)
10552
- return np.stack(input_sequences) # shape: (batch, max_len, d_model)
10743
+ if datasets:
10744
+ for item in datasets:
10745
+ if not self.model2:
10746
+ intents = [d[1] for d in datasets]
10747
+ intent_to_id = {intent:i for i, intent in enumerate(sorted(set(intents)))}
10748
+ num_classes = self._get_num_classes()
10749
+ self.model2 = Transformer(
10750
+ vocab_size=len(self.vocab),
10751
+ d_model=32,
10752
+ n_heads=4,
10753
+ num_classes=num_classes
10754
+ )
10755
+
10756
+ self._sync_vocab_to_embedding()
10757
+ text = item[0] if isinstance(item, tuple) else item
10758
+
10759
+ token_ids = self.encode(text, self.vocab, max_len=max_len)
10760
+ token_embs = self.model2.token_embedding[token_ids] # (max_len, d_model)
10761
+ pos_embs = self.model2.pos_embedding[:max_len] # (max_len, d_model)
10762
+
10763
+ sequence_input = token_embs + pos_embs
10764
+ input_sequences.append(sequence_input)
10765
+ return np.stack(input_sequences) # shape: (batch, max_len, d_model)
10766
+ else:
10767
+ raise Warning('[!] Dataset is None! make sure you provide a dataset or create it Automatically!')
10768
+
10769
+ return None
10553
10770
 
10554
10771
  def transformer_pooled_features(self, sequence_inputs):
10555
10772
  # mean/max/std pooling over sequence dimension