AbstractIntegratedModule 1.1.4__tar.gz → 1.1.6__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-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractIntegratedModule.egg-info/PKG-INFO +1 -1
  2. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractIntegratedModule.py +228 -77
  3. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractOptimizedModules.c +1173 -1164
  4. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractOptimizedModules.pyx +6 -4
  5. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/PKG-INFO +1 -1
  6. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/README.md +79 -11
  7. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/pyproject.toml +1 -1
  8. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/setup.py +1 -1
  9. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractIntegratedModule.egg-info/SOURCES.txt +0 -0
  10. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractIntegratedModule.egg-info/dependency_links.txt +0 -0
  11. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractIntegratedModule.egg-info/requires.txt +0 -0
  12. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/AbstractIntegratedModule.egg-info/top_level.txt +0 -0
  13. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/MANIFEST.in +0 -0
  14. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/Cargo.toml +0 -0
  15. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/pyproject.toml +0 -0
  16. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/src/lib.rs +0 -0
  17. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/target/debug/build/libsqlite3-sys-ed07b882cd2aa5e2/out/bindgen.rs +0 -0
  18. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/target/debug/build/serde_core-ebc15f2e9cad7f5f/out/private.rs +0 -0
  19. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/target/debug/build/target-lexicon-08527f45de28143d/out/host.rs +0 -0
  20. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/target/release/build/libsqlite3-sys-bf0400df4523274c/out/bindgen.rs +0 -0
  21. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/target/release/build/serde_core-5cdb76131825e4af/out/private.rs +0 -0
  22. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/abstract_model_storage/target/release/build/target-lexicon-43eb95a0588bf457/out/host.rs +0 -0
  23. {abstractintegratedmodule-1.1.4 → abstractintegratedmodule-1.1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AbstractIntegratedModule
3
- Version: 1.1.4
3
+ Version: 1.1.6
4
4
  Summary: Integrated Pipeline with Specialized Non-LLM AI Agent Framework for ARM64 architecture
5
5
  Author: Micro-Novelty
6
6
  Author-email: Micro-Novelty <hernikpuspita5@gmail.com>
@@ -2981,7 +2981,7 @@ class LSTMEngine:
2981
2981
  boundaries = np.percentile(non_dominant, percentiles)
2982
2982
 
2983
2983
  if labels is None:
2984
- labels = ["Base"] + [f"Level_{i+1}" for i in range(n_bins-1)]
2984
+ labels = ["Base"] + [f"Level_{i+1}" for i in range(n_bins-1)]
2985
2985
 
2986
2986
  base_lo = float(value_range[0]) if value_range else -1e-6
2987
2987
  bins = {"Base": (base_lo, float(boundaries[0]))}
@@ -3584,6 +3584,7 @@ class WeightedEnsemblePredictor:
3584
3584
  def _dynamic_weighted_ensemble(self, trans_probs, mlp_probs, attn_weights,
3585
3585
  input_ids, lstm_probs=None, lstm_weight_hint=0.0):
3586
3586
  # normalize all inputs to guaranteed 2D float64
3587
+ mean_pred_counts = np.mean(self.pipeline.model3.pred_counts)
3587
3588
  try:
3588
3589
  trans_probs = np.asarray(trans_probs, dtype=np.float64)
3589
3590
  mlp_probs = np.asarray(mlp_probs, dtype=np.float64)
@@ -3635,7 +3636,8 @@ class WeightedEnsemblePredictor:
3635
3636
  np.ascontiguousarray(lstm_probs),
3636
3637
  np.ascontiguousarray(lstm_weight_hints),
3637
3638
  float(self.pipeline.confidence_threshold),
3638
- has_lstm
3639
+ has_lstm,
3640
+ mean_pred_counts
3639
3641
  )
3640
3642
  except Exception as e:
3641
3643
  print(f'[=] Error in optimized dynamic weighted ensemble: {e}, using regular dynamic ensemble method.')
@@ -3666,7 +3668,7 @@ class WeightedEnsemblePredictor:
3666
3668
  mlp_cf = 1.0 / (1.0 + mlp_entropy)
3667
3669
 
3668
3670
  tw = trans_cf * (1.0 + agreement) / 2.0
3669
- mw = mlp_cf * (1.0 + agreement) / 2.0
3671
+ mw = mlp_cf * mean_pred_counts * (1.0 + agreement) / 2.0
3670
3672
 
3671
3673
  if has_lstm:
3672
3674
  lstm_row = np.zeros(n_classes)
@@ -3693,6 +3695,7 @@ class WeightedEnsemblePredictor:
3693
3695
 
3694
3696
 
3695
3697
  def _attention_weighted_ensemble(self, trans_probs, mlp_probs, attn_weights):
3698
+ mean_pred_counts = np.mean(self.pipeline.model3.pred_counts)
3696
3699
  if attn_weights is None:
3697
3700
  return (trans_probs + mlp_probs) / 2
3698
3701
 
@@ -3730,12 +3733,12 @@ class WeightedEnsemblePredictor:
3730
3733
  trans_trust = attn_limit * (1.0 - anisotropy)
3731
3734
 
3732
3735
  # MLP gets the rest
3733
- mlp_trust = 1.0 - trans_trust
3736
+ mlp_trust = (1.0 - trans_trust) * mean_pred_counts
3734
3737
 
3735
3738
  try:
3736
- ensemble[i] = trans_trust * trans_row + mlp_trust * mlp_row
3739
+ ensemble[i] = trans_trust * trans_row + mlp_trust * mlp_row
3737
3740
  except:
3738
- ensemble = trans_trust * trans_row + mlp_trust * mlp_row
3741
+ ensemble = trans_trust * trans_row + mlp_trust * mlp_row
3739
3742
 
3740
3743
  return ensemble
3741
3744
 
@@ -4003,6 +4006,7 @@ class ExplainabilityModule:
4003
4006
  def data_preparation(self, titles, labels):
4004
4007
  datasets = []
4005
4008
  raw = []
4009
+
4006
4010
  for title in titles:
4007
4011
  tupled_title = (str(title))
4008
4012
  datasets.append(tupled_title)
@@ -4025,6 +4029,7 @@ class ExplainabilityModule:
4025
4029
  filled = int(value * max_width)
4026
4030
  return '█' * filled + '░' * (max_width - filled)
4027
4031
 
4032
+
4028
4033
  def _learn_from_feedback(self, text, correct_label, wrong_result, batch_size=2):
4029
4034
  eps = 1e-5
4030
4035
  print(f"\n[📚] Learning: '{text}' → {correct_label}...")
@@ -4205,7 +4210,7 @@ class ExplainabilityModule:
4205
4210
  if choice.lower() == 'skip':
4206
4211
  return None
4207
4212
  elif choice.lower() == 'explain':
4208
- print(explanation)
4213
+ print(f"Explanation: {explanation}")
4209
4214
  return self._ask_for_feedback(text, result, explanation)
4210
4215
  elif choice == '4':
4211
4216
  history = self.get_decision_history(limit=10)
@@ -4253,11 +4258,11 @@ class ExplainabilityModule:
4253
4258
  if zero_ratio > threshold:
4254
4259
  print(f'[!] {len(zero_rows)} zero rows ({zero_ratio:.0%}), refitting on current batch')
4255
4260
  if isinstance(texts, str):
4256
- self.tfidf.fit(texts)
4257
- X_features = self.tfidf.transform(texts).toarray()
4261
+ self.tfidf.fit([texts])
4262
+ X_features = self.tfidf.transform([texts]).toarray()
4258
4263
  elif isinstance(texts[0], str):
4259
- self.tfidf.fit(texts[0])
4260
- X_features = self.tfidf.transform(texts[0]).toarray()
4264
+ self.tfidf.fit([texts[0]])
4265
+ X_features = self.tfidf.transform([texts[0]]).toarray()
4261
4266
  else:
4262
4267
  X_features = X_features
4263
4268
 
@@ -4480,10 +4485,10 @@ class ExplainabilityModule:
4480
4485
 
4481
4486
  if mlp_conf > trans_conf:
4482
4487
  final_pred = mlp_pred
4483
- final_conf = mlp_conf * (1.0 - trans_conf) * (1.0 - np.mean(AAT)) + eps
4488
+ consensus_conf = mlp_conf * (1.0 - trans_conf) * (1.0 - np.mean(AAT)) + eps
4484
4489
  else:
4485
4490
  final_pred = trans_pred
4486
- final_conf = trans_conf * (1.0 - mlp_conf) * np.mean(AAT) + eps
4491
+ consensus_conf = trans_conf * (1.0 - mlp_conf) * np.mean(AAT) + eps
4487
4492
 
4488
4493
  print('='*50)
4489
4494
  print('===== ABSTRACTION LAYER ======')
@@ -4493,10 +4498,12 @@ class ExplainabilityModule:
4493
4498
  print(f'[= ABSTRACTION =] Sigmoid growth of Attention weight consistency: {np.std(sigmoid_growth)}')
4494
4499
  print('[=] Note: Very little Consistency meaning Transformer attention quality is Healthy and focused')
4495
4500
 
4496
- if isinstance(final_conf, np.ndarray):
4497
- final_conf = 1.0 / (1.0 + np.exp(-final_conf))
4501
+ if isinstance(consensus_conf, np.ndarray):
4502
+ consensus_conf = 1.0 / (1.0 + np.exp(-final_conf))
4498
4503
  # Apply a sigmoid transformation to ensure the confidence is between 0 and 1
4499
4504
 
4505
+ # averaged all confidences to get the final confidence.
4506
+ final_conf = mlp_conf + trans_conf + final_conf / 3
4500
4507
  if np.isnan(final_conf).any() or np.isinf(final_conf).any():
4501
4508
  final_conf = self.pipeline.confidence_threshold
4502
4509
 
@@ -9582,6 +9589,7 @@ class IntegratedPipeline:
9582
9589
  self.max_size = 500
9583
9590
  self.error_decay = 0.85
9584
9591
  self.performance_result = 1.0
9592
+ self.max_ram_allowed = 150
9585
9593
 
9586
9594
  # === TRAINING SETUP ===
9587
9595
  self.mlp_training_epochs = 1000
@@ -9872,6 +9880,7 @@ class IntegratedPipeline:
9872
9880
  f"Unexpected memory type: {type(memory).__name__}",
9873
9881
  "Clear and reinitialize memory")
9874
9882
 
9883
+
9875
9884
  def k_fold_cross_validate(self, X, y, input_dim, n_classes, k=5, seed=42,
9876
9885
  epochs=None, lr=None, log_to_diagnostics=True):
9877
9886
  """
@@ -12165,11 +12174,11 @@ class IntegratedPipeline:
12165
12174
  if zero_ratio > threshold:
12166
12175
  print(f'[!] {len(zero_rows)} zero rows ({zero_ratio:.0%}), refitting on current batch')
12167
12176
  if isinstance(texts, str):
12168
- self.tfidf.fit(texts)
12169
- X_features = self.tfidf.transform(texts).toarray()
12177
+ self.tfidf.fit([texts])
12178
+ X_features = self.tfidf.transform([texts]).toarray()
12170
12179
  elif isinstance(texts[0], str):
12171
- self.tfidf.fit(texts[0])
12172
- X_features = self.tfidf.transform(texts[0]).toarray()
12180
+ self.tfidf.fit([texts[0]])
12181
+ X_features = self.tfidf.transform([texts[0]]).toarray()
12173
12182
  else:
12174
12183
  X_features = X_features
12175
12184
 
@@ -12415,33 +12424,6 @@ class IntegratedPipeline:
12415
12424
  pass
12416
12425
 
12417
12426
 
12418
- def utility_MLP_set(self, X, y):
12419
- if not self.autonomous:
12420
- try:
12421
- joblib.dump(self.mlp, 'analyzer_model.pkl')
12422
- joblib.dump(self.model2, 'analyzer_agent.pkl')
12423
- print("🎉 Model trained and saved!")
12424
- except Exception as e:
12425
- print(f'|| Failed to joblib dump file! : {e}, User Manual filepath suggestion needed...')
12426
-
12427
- permission = input('|| Insert Filepath? [Y/N]: ')
12428
- if permission == 'Y':
12429
- suggested_path = input('|| Filepath suggestion: ')
12430
- if suggested_path:
12431
- self.safe_pickle_save_with_feedback(self.mlp, suggested_path)
12432
- self.safe_pickle_save_with_feedback(self.model2, suggested_path)
12433
- print(" || Model saved!")
12434
- else:
12435
- print('|| Failed to dump Your model! ')
12436
- pass
12437
- else:
12438
- print('|| Failed to dump Your model! ')
12439
- pass
12440
-
12441
- else:
12442
- pass
12443
-
12444
-
12445
12427
  def auto_generate_labels_from_texts(self, rules, texts):
12446
12428
  y_raw = []
12447
12429
  self.rules = rules
@@ -13062,7 +13044,7 @@ class IntegratedPipeline:
13062
13044
  is in that sample's OWN true class — used as a genuine continuous
13063
13045
  LSTM training target instead of the raw class index.
13064
13046
 
13065
- X : hybrid_X after shape_adaptation — same format self.mlp
13047
+ X : hybrid_X after shape_adaptation — same format self.mlp
13066
13048
  (self.model3) and self.model3.train() already consume.
13067
13049
  Y : raw class indices, shape (n_samples,) or (n_samples, 1)
13068
13050
  input_ids : sequence_inputs — same format self.model2 (Transformer)
@@ -13070,7 +13052,7 @@ class IntegratedPipeline:
13070
13052
  confidence is used if not provided or Transformer
13071
13053
  is unavailable.
13072
13054
 
13073
- Returns : (n_samples,) float64 array, each value in (eps, 1.0]
13055
+ Returns : (n_samples,) float64 array, each value in (eps, 1.0]
13074
13056
  — never exactly 0, to avoid degenerate regression
13075
13057
  targets downstream (consistent with the eps-floor
13076
13058
  pattern used elsewhere in this codebase).
@@ -13147,8 +13129,9 @@ class IntegratedPipeline:
13147
13129
  sample_confs.append(float(mlp_probs[i, true_class]))
13148
13130
  else:
13149
13131
  print(f'[⚠️] Sample {i}: true_class={true_class} out of '
13150
- f'range for MLP output width {n_cls} — skipping '
13132
+ f'range for MLP output width {n_cls} — skipping Most '
13151
13133
  f'MLP contribution for this sample')
13134
+ sample_confs.append(float(mlp_probs[i, n_cls - 1]))
13152
13135
 
13153
13136
  if trans_probs is not None:
13154
13137
  n_cls = trans_probs.shape[1]
@@ -13157,7 +13140,8 @@ class IntegratedPipeline:
13157
13140
  else:
13158
13141
  print(f'[⚠️] Sample {i}: true_class={true_class} out of '
13159
13142
  f'range for Transformer output width {n_cls} — '
13160
- f'skipping Transformer contribution for this sample')
13143
+ f'skipping Most Transformer contribution for this sample')
13144
+ sample_confs.append(float(trans_probs[i, n_cls - 1]))
13161
13145
 
13162
13146
  if sample_confs:
13163
13147
  confidences[i] = max(np.mean(sample_confs), eps)
@@ -13318,6 +13302,7 @@ class IntegratedPipeline:
13318
13302
  raise Warning('[!] Dataset is None or empty! Make sure you provide a dataset or create it automatically.')
13319
13303
 
13320
13304
  if not self.model2:
13305
+ print(datasets)
13321
13306
  intents = [d[1] for d in datasets]
13322
13307
  intent_to_id = {intent: i for i, intent in enumerate(sorted(set(intents)))}
13323
13308
  num_classes = self._get_num_classes(label_map=label_map)
@@ -13457,7 +13442,7 @@ class IntegratedPipeline:
13457
13442
  sequence_inputs = np.where(flat_mask, sequence_inputs + noise, sequence_inputs)
13458
13443
 
13459
13444
  # ---- 6. Hard safety net: guarantee finite output no matter what happened above ----
13460
- if not np.isfinite(sequence_inputs).all():
13445
+ if not np.isfinite(sequence_inputs).all() or not np.isfinite(sequence_inputs.std(axis=-1)).all():
13461
13446
  n_bad = (~np.isfinite(sequence_inputs)).sum()
13462
13447
  print(f"[!] _features_to_sequence: FINAL sanitize caught {n_bad} non-finite values "
13463
13448
  f"(this should be rare — investigate if it triggers often)")
@@ -13469,6 +13454,7 @@ class IntegratedPipeline:
13469
13454
 
13470
13455
  return sequence_inputs
13471
13456
 
13457
+
13472
13458
  def _sanitize_string_chars(self, x):
13473
13459
  if isinstance(x, (str, np.str_)):
13474
13460
  clean_str = str(x).replace('[', '').replace(']', '').replace('...', '').strip()
@@ -13534,11 +13520,63 @@ class IntegratedPipeline:
13534
13520
 
13535
13521
  return False
13536
13522
 
13523
+ def _should_train_transformer(self, sequence_inputs, X_raw,
13524
+ min_seq_len=3, min_anisotropy=0.35,
13525
+ min_samples=10, ram_headroom_mb=80):
13526
+ """
13527
+ Decides whether Transformer training is worth its RAM/compute cost
13528
+ for THIS data, reusing existing AME/anisotropy signals rather than
13529
+ adding new expensive computation. Built specifically for
13530
+ memory-constrained edge deployment (e.g. Raspberry Pi Zero).
13531
+
13532
+ Returns (should_train: bool, reason: str) so callers can log why should_train.
13533
+
13534
+ """
13535
+ sequence_inputs = np.asarray(sequence_inputs)
13536
+ n_samples = sequence_inputs.shape[0] if sequence_inputs.ndim > 0 else 0
13537
+ T = sequence_inputs.shape[1] if sequence_inputs.ndim > 1 else 1
13538
+
13539
+ # ── Check 1: sample count ────────────────────────────────
13540
+ if n_samples < min_samples:
13541
+ return False, (f'only {n_samples} samples (need >= {min_samples}) — '
13542
+ f'too little data to justify Transformer parameter count')
13543
+
13544
+ # ── Check 2: sequence length ─────────────────────────────
13545
+ if T < min_seq_len:
13546
+ return False, (f'sequence length T={T} (need >= {min_seq_len}) — '
13547
+ f'attention has too few positions to model '
13548
+ f'meaningful relationships')
13549
+
13550
+ # ── Check 3: anisotropy ──
13551
+ # ── zero new computation cost ──
13552
+ anisotropy = self.anisotropy_measurement(sequence_inputs)
13553
+ if anisotropy < min_anisotropy:
13554
+ return False, (f'anisotropy={anisotropy:.4f} (need >= {min_anisotropy}) — '
13555
+ f'data lacks directional/sequential structure; '
13556
+ f'MLP alone is the appropriate model for this '
13557
+ f'geometry (same finding as make_moons earlier)')
13558
+
13559
+ # ── Check 4: RAM headrooms, optional, graceful if unavailable ──
13560
+ if ram_headroom_mb is not None:
13561
+ try:
13562
+ available_mb = psutil.virtual_memory().available / (1024 * 1024)
13563
+ if available_mb < ram_headroom_mb:
13564
+ return False, (f'only {available_mb:.0f}MB RAM available '
13565
+ f'(need >= {ram_headroom_mb}MB headroom) — '
13566
+ f'skipping Transformer to protect device stability')
13567
+ except ImportError:
13568
+ pass # psutil not installed — skip memory check silently,
13569
+
13570
+
13571
+ return True, (f'n={n_samples}, T={T}, anisotropy={anisotropy:.4f} — '
13572
+ f'sufficient structure and data to justify Transformer training')
13537
13573
 
13538
13574
  def transformer_utilities(self, X_provided= None, X_raw=None, y_true=None, rules=None,
13539
13575
  datasets=None, label_map=None, batch_size=2, min_signal=1e-3,
13540
- max_samples_for_focused_fit=500):
13576
+ max_samples_for_focused_fit=500, max_ram_used=80):
13541
13577
  unsuitable = False
13578
+ max_ram_used = self.max_ram_allowed if max_ram_used is None else max_ram_used
13579
+
13542
13580
  if X_provided is not None:
13543
13581
  X_raw = X_provided
13544
13582
 
@@ -13717,13 +13755,14 @@ class IntegratedPipeline:
13717
13755
  n_heads=self.transformer_heads,
13718
13756
  num_classes=n_classes
13719
13757
  )
13720
-
13721
- if self.use_transformer:
13758
+
13759
+ should_train_transformer, reason = self._should_train_transformer(sequence_inputs, X_raw, min_seq_len=3, min_anisotropy=0.35, min_samples=10, ram_headroom_mb=max_ram_used)
13760
+ if self.use_transformer and should_train_transformer:
13761
+ print(f'[=] Transformer Training allowed, Reason: {reason}')
13722
13762
  self.model2.train(sequence_inputs, y_true, epochs=self.transformer_training_epochs, mode=mode, lr=lr, embedded=True, batch_size=batch_size)
13723
13763
 
13724
13764
  X = self.shape_adaptation(hybrid_X, input_dim)
13725
13765
 
13726
-
13727
13766
  self.initialize_model_(X, input_dim, n_classes)
13728
13767
  if not unsuitable:
13729
13768
  self.model3.train(X, y, epochs=self.mlp_training_epochs, lr=self.mlp_lr, max_samples_for_focused_fit=max_samples_for_focused_fit)
@@ -13794,8 +13833,7 @@ class IntegratedPipeline:
13794
13833
  print(f"\n🚀 Separate Modular MLP Pipeline:")
13795
13834
  print(f" Samples: {len(self.X)}")
13796
13835
 
13797
- y_true = self.initialize_model_encoding(self.X, y_raw)
13798
- self.utility_MLP_set(self.X, y_true)
13836
+ y_true = self.initialize_model_encoding(self.X, y_raw)
13799
13837
  print('✅ Done Training MLP Model! ')
13800
13838
 
13801
13839
 
@@ -14505,6 +14543,7 @@ class AsyncResultQueue:
14505
14543
  logger.debug(f"[=] Submitted request {request_id}: {texts}")
14506
14544
  return request_id
14507
14545
 
14546
+
14508
14547
  async def wait_for_result(self, request_id: str, timeout: int = 30) -> Dict:
14509
14548
  """
14510
14549
  Wait for a specific request to complete.
@@ -16369,7 +16408,6 @@ class PipelineAsyncManager:
16369
16408
  class PipelinePredictionManager:
16370
16409
  def __init__(self, pipeline, label_csv='labels.csv', target_title='title', label='label'):
16371
16410
  self.pipeline = pipeline
16372
-
16373
16411
  try:
16374
16412
  print("📖 Loading labels from text file...")
16375
16413
  self.titles, self.y_raw, self.label_map = self.load_labels_from_csv(label_csv, target_title, label)
@@ -16490,8 +16528,10 @@ class PipelinePredictionManager:
16490
16528
  X_gen = None
16491
16529
  use_embedded = False
16492
16530
  attn_weights = None
16531
+
16493
16532
  trans_probs = None
16494
16533
  mlp_probs = None
16534
+ target_probs = None
16495
16535
 
16496
16536
  print(f"\n[🚀] Regular Prediction Initiated...")
16497
16537
  self.pipeline.titles = titles
@@ -16508,6 +16548,30 @@ class PipelinePredictionManager:
16508
16548
  _, y, _, _ = self.pipeline.mlp_training_features(rules, dataset)
16509
16549
  else:
16510
16550
  dataset, _ = self.pipeline.data_preparation(titles, label_map)
16551
+
16552
+ if X is not None or y is not None and isinstance(X, (np.ndarray, list)) and isinstance(y, (np.ndarray, list)) and len(X) > 0 and len(y) > 0:
16553
+ X_train, X_val, y_train, y_val = self._prepare_train_val_split(
16554
+ X, y, min_val_per_class=5, min_frac=0.1, max_frac=0.3
16555
+ )
16556
+
16557
+ onehot_validation = self.pipeline._validate_onehot(y_train)
16558
+ if onehot_validation:
16559
+ if num_classes != len(label_map):
16560
+ num_classes = len(label_map)
16561
+ if num_classes > np.max(y_train):
16562
+ y_train = np.eye(num_classes)[np.asarray(y_train)]
16563
+ y_val = np.eye(num_classes)[np.asarray(y_val)]
16564
+ else:
16565
+ print('[⚠️] Warning: Y onehot encoding fails, Returning y samples as is, This may cause Exploding Gradient in MLP Training!')
16566
+ y_train = y_train
16567
+ y_val = y_val
16568
+
16569
+ X_mean = X_train.mean(axis=0)
16570
+ X_std = X_train.std(axis=0) + 1e-8
16571
+
16572
+ X_train = (X_train - X_mean) / X_std
16573
+ X = (X_val - X_mean) / X_std
16574
+ y = y_val.copy()
16511
16575
 
16512
16576
  if X_gen is not None:
16513
16577
  self.pipeline.transformer_utilities(X_provided=X, X_raw=X_gen, y_true=y, rules=rules, datasets=dataset, label_map=label_map, batch_size=batch_size)
@@ -16860,6 +16924,7 @@ class PipelinePredictionManager:
16860
16924
  self.pipeline.titles = titles
16861
16925
  self.pipeline.labels = label_map
16862
16926
 
16927
+ num_classes = self.pipeline._get_num_classes(label_map=label_map)
16863
16928
  try:
16864
16929
 
16865
16930
  if titles is not None and rules is not None:
@@ -16871,10 +16936,34 @@ class PipelinePredictionManager:
16871
16936
  else:
16872
16937
  datasets, _ = self.pipeline.data_preparation(titles, label_map)
16873
16938
 
16939
+ if X is not None or y is not None and len(X) > 0 and len(y) > 0:
16940
+ X_train, X_val, y_train, y_val = self._prepare_train_val_split(
16941
+ X, y, min_val_per_class=5, min_frac=0.1, max_frac=0.3
16942
+ )
16943
+
16944
+ onehot_validation = self.pipeline._validate_onehot(y_train)
16945
+ if onehot_validation:
16946
+ if num_classes != len(label_map):
16947
+ num_classes = len(label_map)
16948
+ if num_classes > np.max(y_train):
16949
+ y_train = np.eye(num_classes)[np.asarray(y_train)]
16950
+ y_val = np.eye(num_classes)[np.asarray(y_val)]
16951
+ else:
16952
+ print('[⚠️] Warning: Y onehot encoding fails, Returning y samples as is, This may cause Exploding Gradient in MLP Training!')
16953
+ y_train = y_train
16954
+ y_val = y_val
16955
+
16956
+ X_mean = X_train.mean(axis=0)
16957
+ X_std = X_train.std(axis=0) + 1e-8
16958
+
16959
+ X_train = (X_train - X_mean) / X_std
16960
+ X = (X_val - X_mean) / X_std
16961
+ y = y_val.copy()
16962
+
16874
16963
  if X_gen is not None:
16875
- self.pipeline.transformer_utilities(X_provided=X, X_raw=X_gen, y_true=y, rules=rules, datasets=dataset, label_map=label_map, batch_size=batch_size)
16964
+ self.pipeline.transformer_utilities(X_provided=X_train, X_raw=X_gen, y_true=y_train, rules=rules, datasets=dataset, label_map=label_map, batch_size=batch_size)
16876
16965
  else:
16877
- self.pipeline.transformer_utilities(X_provided=X, X_raw=X, y_true=y, rules=rules, datasets=dataset, label_map=label_map, batch_size=batch_size)
16966
+ self.pipeline.transformer_utilities(X_provided=X_train, X_raw=X, y_true=y_train, rules=rules, datasets=dataset, label_map=label_map, batch_size=batch_size)
16878
16967
 
16879
16968
  reverse_map = {v: k for k, v in label_map.items()}
16880
16969
 
@@ -17395,7 +17484,7 @@ class PipelinePredictionManager:
17395
17484
  signals.append(f'AME={AME:.3f}')
17396
17485
 
17397
17486
  if error_counts is not None and len(error_counts) > 0:
17398
- # FIX — max, not mean: catches localized single-class failure
17487
+ # max: catches localized single-class failure
17399
17488
  # that a flat average would dilute away
17400
17489
  worst_class_idx = int(np.argmax(error_counts))
17401
17490
  max_error = float(error_counts[worst_class_idx])
@@ -17410,6 +17499,51 @@ class PipelinePredictionManager:
17410
17499
 
17411
17500
  return need_ensemble
17412
17501
 
17502
+ def _check_for_transformer_sequences(self, sequence_inputs, min_seq_len=3,
17503
+ min_samples=10, min_AME=0.5, min_anisotropy=0.5):
17504
+ score = 0
17505
+ sequence_inputs = np.asarray(sequence_inputs)
17506
+ n_samples = sequence_inputs.shape[0] if sequence_inputs.ndim > 0 else 0
17507
+ T = sequence_inputs.shape[1] if sequence_inputs.ndim > 1 else 1
17508
+
17509
+ # ── Check 1: sample count ────────────────────────────────
17510
+ if n_samples < min_samples:
17511
+ score += 1
17512
+ print(f'[=] only {n_samples} samples (need >= {min_samples}) — '
17513
+ f'too little data to justify Transformer parameter count')
17514
+
17515
+ # ── Check 2: sequence length ─────────────────────────────
17516
+ if T < min_seq_len:
17517
+ score += 1
17518
+ print(f'[=] sequence length T={T} (need >= {min_seq_len}) — '
17519
+ f'attention has too few positions to model '
17520
+ f'meaningful relationships')
17521
+
17522
+ # ── Check 3: anisotropy ──
17523
+ # ── zero new computation cost ──
17524
+ anisotropy = self.pipeline.anisotropy_measurement(sequence_inputs)
17525
+ if anisotropy < min_anisotropy:
17526
+ score += 1
17527
+ print(f'[=] anisotropy={anisotropy:.4f} (need >= {min_anisotropy}) — '
17528
+ f'data lacks directional/sequential structure; '
17529
+ f'MLP alone is the appropriate model for this '
17530
+ f'geometry.')
17531
+
17532
+
17533
+ AME = self.pipeline.AME_Encoder(sequence_inputs)
17534
+ if AME > min_AME:
17535
+ score += 1
17536
+ print(f'[=] Result of Abstract Modelling Error is High, Complexity in samples is High.')
17537
+
17538
+ error_counts = np.mean(self.pipeline.model3.error_counts)
17539
+ if error_counts > 0.5:
17540
+ score += 1
17541
+ print('[=] MLP Experienced lots of Errors, Considering to use Transformer Prediction.')
17542
+
17543
+ propriate_single_transformer = score == 5
17544
+ print(f'[+] Transformer Prediction allowed: {propriate_single_transformer}')
17545
+ return propriate_single_transformer
17546
+
17413
17547
  def advanced_prediction_method(self, titles=None, label_map=None, rules=None,
17414
17548
  X=None, y=None,
17415
17549
  show_proba=False, top_k=3,
@@ -17434,11 +17568,14 @@ class PipelinePredictionManager:
17434
17568
  AME = None
17435
17569
  use_embedded = False
17436
17570
  dataset = None
17571
+ target_probs = None
17437
17572
 
17438
17573
  X_gen = None
17439
17574
  sec_chosen_label = None
17575
+
17440
17576
  sec_confidence = 0.0
17441
17577
  final_confidence = 0.0
17578
+ min_signal = 1e-3
17442
17579
 
17443
17580
  correct = 0
17444
17581
  sec_correct= 0
@@ -17530,9 +17667,18 @@ class PipelinePredictionManager:
17530
17667
 
17531
17668
  if dataset is not None:
17532
17669
  input_ids, _ = self.pipeline.input_encoding(dataset)
17670
+ elif dataset is not None and X_train is not None:
17671
+ input_ids, _ = self.pipeline.input_encoding(dataset)
17672
+ row_sums = input_ids.sum(axis=1)
17673
+ weak_rows = np.where(row_sums < min_signal)[0]
17674
+ weak_ratio = len(weak_rows) / len(input_ids)
17675
+ if weak_ratio > 0.3:
17676
+ print(f'[=] Zero rows abundant in Input indices: {weak_ratio:.1%}, using input indices from X samples...')
17677
+ input_ids = self.pipeline._features_to_sequence(X_train)
17533
17678
  else:
17534
17679
  input_ids = self.pipeline._features_to_sequence(X_train)
17535
17680
 
17681
+
17536
17682
  if len(input_ids) == 0:
17537
17683
  print('[⚠️] input_encoding produced no samples — skipping '
17538
17684
  'transformer training/prediction for this call and transformer results will be Replaced by MLP or LSTM.')
@@ -17661,6 +17807,7 @@ class PipelinePredictionManager:
17661
17807
  return result, cached['prediction'], cached['confidence']
17662
17808
  else:
17663
17809
  print(f'[!] Similarity: {cached['similarity']} is low, Cannot pick label due to low certainty, Initiating advanced prediction...')
17810
+ target_probs = mlp_probs.copy()
17664
17811
  else:
17665
17812
  print('[=] No verified output from cache available that matched samples, starting advanced prediction...')
17666
17813
  if self.pipeline.use_transformer:
@@ -17669,7 +17816,7 @@ class PipelinePredictionManager:
17669
17816
 
17670
17817
  target_probs = self.pipeline.predict_proba(input_ids, X, type='Hybrid', embedded=use_embedded)
17671
17818
  else:
17672
- target_probs = mlp_probs
17819
+ target_probs = mlp_probs.copy()
17673
17820
 
17674
17821
  target_probs = target_probs[:mlp_probs.shape[0], :mlp_probs.shape[1]]
17675
17822
  target_probs = self.pipeline.model3.continuous_predictive_correction(self, target_probs, mlp_pred_indices)
@@ -17923,12 +18070,20 @@ class PipelinePredictionManager:
17923
18070
 
17924
18071
  else:
17925
18072
  print("[=] Initiating Continuous sample prediction without Titles.")
17926
- n_samples = mlp_probs.shape[0]
18073
+
18074
+ transformer_takes = self._check_for_transformer_sequences(input_ids, min_seq_len=3,
18075
+ min_samples=10, min_AME=0.5, min_anisotropy=0.5)
18076
+ if not transformer_takes:
18077
+ n_samples = mlp_probs.shape[0]
18078
+ chosen_probs = mlp_probs.copy()
18079
+ else:
18080
+ n_samples = trans_probs.shape[0]
18081
+ chosen_probs = trans_probs.copy()
17927
18082
 
17928
18083
  lstm_pred_indices = np.argmax(lstm_probs, axis=1) if lstm_probs is not None else None
17929
18084
  for i in range(n_samples):
17930
18085
  outcome = self._compute_sample_prediction(
17931
- i, mlp_probs, target_probs, target_pred_indices,
18086
+ i, chosen_probs, target_probs, target_pred_indices,
17932
18087
  trans_probs=trans_probs, lstm_probs=lstm_probs,
17933
18088
  lstm_pred_indices=lstm_pred_indices,
17934
18089
  attn_weights=attn_weights, input_ids=input_ids,
@@ -18009,14 +18164,6 @@ class PipelinePredictionManager:
18009
18164
  confidence = (confidence + trans_confidence / 2) + 1e-5
18010
18165
  print(f'[=] Calibrating confidence around: {confidence:.1%}')
18011
18166
 
18012
- if isinstance(chosen_label, str) and chosen_label.startswith("unknown") or float(confidence) < self.pipeline.confidence_threshold:
18013
- if chosen_label is None or chosen_label.startswith('unknown'):
18014
- chosen_label = 'Unknown'
18015
- confidence = 1.0 - confidence #Invert confidence for unknown class
18016
- print(f"\n[⚠️] Final prediction is {chosen_label} with uncertain confidence: {confidence:.1%}. Consider more consistent data for the model to learn from.")
18017
- else:
18018
- print(f"\n[🎯] Predicted label: {chosen_label} || With Certain Confidence: {confidence:.1%}")
18019
-
18020
18167
  payload = {
18021
18168
  'X_samples': X,
18022
18169
  'input_ids': input_ids
@@ -18025,7 +18172,14 @@ class PipelinePredictionManager:
18025
18172
  if titles is not None and len(titles) > 0:
18026
18173
  correct, sec_correct = self.display_hybrid_results(payload, final_class_idx, results, top_k, verbose=True)
18027
18174
 
18028
-
18175
+ if isinstance(chosen_label, str) and chosen_label.startswith("unknown") or float(confidence) < self.pipeline.confidence_threshold:
18176
+ if chosen_label is None or chosen_label.startswith('unknown'):
18177
+ chosen_label = 'Unknown'
18178
+ confidence = 1.0 - confidence #Invert confidence for unknown class
18179
+ print(f"\n[⚠️] Final prediction is {chosen_label} with uncertain confidence: {confidence:.1%}. Consider more consistent data for the model to learn from.")
18180
+ else:
18181
+ print(f"\n[🎯] Predicted label: {chosen_label} || With Certain Confidence: {confidence:.1%}")
18182
+
18029
18183
  return results, chosen_label, confidence
18030
18184
 
18031
18185
  else:
@@ -18312,7 +18466,7 @@ class PipelinePredictionManager:
18312
18466
  print(f'[⚡] Final Prediction: {chosen_label} with confidence: {confidence:.1%}')
18313
18467
  chosen_label = chosen_label
18314
18468
 
18315
-
18469
+ self.pipeline.evaluate_mlp_performance(X, y, self.label_map)
18316
18470
  # delete pipelines cache
18317
18471
  print('[🔍] Pipelines Cache Cleaned!')
18318
18472
  self.pipeline.cache.clear()
@@ -18374,10 +18528,7 @@ class PipelinePredictionManager:
18374
18528
 
18375
18529
  except Exception as e:
18376
18530
  print(f'[!] Cant check and calibrate probs based on penalty due to: {e}')
18377
- traceback.print_exc()
18378
18531
 
18379
-
18380
-
18381
18532
  return final_probs
18382
18533
 
18383
18534