1+ import os
12import sys
23import traceback
3- import os
4+
45import numpy as np
56import scipy .stats as stats
67
1213
1314 # Inject pip-installed NVIDIA DLLs into Windows PATH so TF 2.10 can use the GPU without a system CUDA install
1415 try :
15- site_packages = site .getsitepackages ()[1 ] if len (site .getsitepackages ()) > 1 else site .getsitepackages ()[0 ]
16- nvidia_base = os .path .join (site_packages , "nvidia" )
16+ site_packages = (
17+ site .getsitepackages ()[1 ]
18+ if len (site .getsitepackages ()) > 1
19+ else site .getsitepackages ()[0 ]
20+ )
21+ nvidia_base = os .path .join (site_packages , 'nvidia' )
1722 dll_paths = [
18- os .path .join (nvidia_base , " cudnn" , " bin" ),
19- os .path .join (nvidia_base , " cublas" , " bin" ),
20- os .path .join (nvidia_base , " cuda_runtime" , " bin" ),
23+ os .path .join (nvidia_base , ' cudnn' , ' bin' ),
24+ os .path .join (nvidia_base , ' cublas' , ' bin' ),
25+ os .path .join (nvidia_base , ' cuda_runtime' , ' bin' ),
2126 ]
22- os .environ [" PATH" ] = os .pathsep .join (dll_paths ) + os .pathsep + os .environ .get (" PATH" , "" )
27+ os .environ [' PATH' ] = os .pathsep .join (dll_paths ) + os .pathsep + os .environ .get (' PATH' , '' )
2328 except Exception :
2429 pass
2530
3237 from validate_pu import load_pu_domain_split
3338
3439 print ('Loading Authentic PU dataset (Cross-Domain RPM Split)...' )
35- (X_train_full , y_train_full , cond_train_full ), (X_target , y_target , cond_target ) = load_pu_domain_split ()
40+ (X_train_full , y_train_full , cond_train_full ), (X_target , y_target , cond_target ) = (
41+ load_pu_domain_split ()
42+ )
3643
3744 unique_rpm = set (cond_train_full ).union (set (cond_target ))
3845 rpm_map = {float (r ): float (r ) for r in unique_rpm }
@@ -56,11 +63,11 @@ def get_matched_coverage_gap(score, correct, target_n):
5663 sorted_indices = np .argsort (score )[::- 1 ]
5764 hi_indices = sorted_indices [:target_n ]
5865 lo_indices = sorted_indices [target_n :]
59-
66+
6067 hi_mask = np .zeros (len (score ), dtype = bool )
6168 hi_mask [hi_indices ] = True
6269 lo_mask = ~ hi_mask
63-
70+
6471 ah = correct [hi_mask ].mean () if hi_mask .any () else float ('nan' )
6572 al = correct [lo_mask ].mean () if lo_mask .any () else float ('nan' )
6673 return ah - al
@@ -75,105 +82,135 @@ def clone_for_mc(layer):
7582 return layer .__class__ .from_config (layer .get_config ())
7683
7784 seeds = [42 , 43 , 44 , 45 , 46 ]
78-
85+
7986 results = {
8087 'phys_gap' : [],
8188 'soft_gap' : [],
8289 'mc_gap' : [],
8390 'ens_gap' : [],
84- 'noise_catch' : {db : [] for db in [np .inf , 20 , 10 , 5 , 0 ]}
91+ 'noise_catch' : {db : [] for db in [np .inf , 20 , 10 , 5 , 0 ]},
8592 }
86-
93+
8794 # Test data processing
8895 test_ds = Dataset .from_arrays (
89- X_target , y_target , cond_target , fs = 64000 , physics = pu_physics , taxonomy = pu_taxonomy , name = 'PU_Test'
96+ X_target ,
97+ y_target ,
98+ cond_target ,
99+ fs = 64000 ,
100+ physics = pu_physics ,
101+ taxonomy = pu_taxonomy ,
102+ name = 'PU_Test' ,
90103 )
91104 sig_te = np .stack ([test_ds .X [i ].reshape (- 1 ) for i in range (len (test_ds .X ))]).astype (np .float32 )
92105 yte = test_ds .y
93106 cond_te = test_ds .cond
94107 Xin_te = sig_te [..., None ]
95-
108+
96109 for seed in seeds :
97- print (f" \n { '=' * 80 } \n === RUNNING SEED { seed } ===\n { '=' * 80 } " )
110+ print (f' \n { "=" * 80 } \n === RUNNING SEED { seed } ===\n { "=" * 80 } ' )
98111 tf .keras .backend .clear_session ()
99112 np .random .seed (seed )
100113 tf .random .set_seed (seed )
101-
114+
102115 # 1. 80/20 Split for Calibration
103116 indices = np .arange (len (y_train_full ))
104117 np .random .shuffle (indices )
105-
118+
106119 split_idx = int (0.8 * len (indices ))
107120 train_idx = indices [:split_idx ]
108121 calib_idx = indices [split_idx :]
109-
110- X_tr , y_tr , cond_tr = X_train_full [train_idx ], y_train_full [train_idx ], cond_train_full [train_idx ]
111- X_ca , y_ca , cond_ca = X_train_full [calib_idx ], y_train_full [calib_idx ], cond_train_full [calib_idx ]
112-
113- train_ds = Dataset .from_arrays (X_tr , y_tr , cond_tr , fs = 64000 , physics = pu_physics , taxonomy = pu_taxonomy , name = 'PU_Train' )
114- calib_ds = Dataset .from_arrays (X_ca , y_ca , cond_ca , fs = 64000 , physics = pu_physics , taxonomy = pu_taxonomy , name = 'PU_Calib' )
115-
122+
123+ X_tr , y_tr , cond_tr = (
124+ X_train_full [train_idx ],
125+ y_train_full [train_idx ],
126+ cond_train_full [train_idx ],
127+ )
128+ X_ca , y_ca , cond_ca = (
129+ X_train_full [calib_idx ],
130+ y_train_full [calib_idx ],
131+ cond_train_full [calib_idx ],
132+ )
133+
134+ train_ds = Dataset .from_arrays (
135+ X_tr , y_tr , cond_tr , fs = 64000 , physics = pu_physics , taxonomy = pu_taxonomy , name = 'PU_Train'
136+ )
137+ calib_ds = Dataset .from_arrays (
138+ X_ca , y_ca , cond_ca , fs = 64000 , physics = pu_physics , taxonomy = pu_taxonomy , name = 'PU_Calib'
139+ )
140+
116141 # 2. Train Primary Model (Bypass SCM to prevent multiprocess deadlocks)
117142 model = CNSD ()
118143 nc = int (train_ds .y .max ()) + 1
119144 model .cnn = _train_cnn (train_ds .X , train_ds .y , num_classes = nc , epochs = 20 , seed = seed )
120145 model .symbolic = model ._build_symbolic (train_ds )
121146 model ._fitted = True
122-
147+
123148 # 3. Train Ensemble Models
124149 ens = []
125150 nc = int (train_ds .y .max ()) + 1
126- for s in [seed * 10 , seed * 10 + 1 , seed * 10 + 2 ]:
151+ for s in [seed * 10 , seed * 10 + 1 , seed * 10 + 2 ]:
127152 np .random .seed (s )
128153 tf .random .set_seed (s )
129154 m_cnn = _train_cnn (train_ds .X , train_ds .y , num_classes = nc , epochs = 20 , seed = s )
130155 ens .append (m_cnn )
131-
156+
132157 # 4. Calibrate Tau
133- sig_ca = np .stack ([calib_ds .X [i ].reshape (- 1 ) for i in range (len (calib_ds .X ))]).astype (np .float32 )
158+ sig_ca = np .stack ([calib_ds .X [i ].reshape (- 1 ) for i in range (len (calib_ds .X ))]).astype (
159+ np .float32
160+ )
134161 Xin_ca = sig_ca [..., None ]
135162 probs_ca = model .cnn .predict (Xin_ca , batch_size = 128 , verbose = 0 )
136163 pred_ca = probs_ca .argmax (1 )
137- correct_ca = ( pred_ca == calib_ds .y )
138-
164+ correct_ca = pred_ca == calib_ds .y
165+
139166 best_tau = 1.0
140167 best_gap = - 100.0
141168 for tau in [1.0 , 1.5 , 2.0 , 2.5 , 3.0 ]:
142169 model .symbolic .tau = float (tau )
143- verds = np .array ([model .symbolic .diagnose (sig_ca [i ], pred_ca [i ], calib_ds .cond [i ])['verdict' ] for i in range (len (sig_ca ))])
144- conf = (verds == 'CONFIRMED' )
145- cnfl = (verds == 'CONFLICT' )
170+ verds = np .array (
171+ [
172+ model .symbolic .diagnose (sig_ca [i ], pred_ca [i ], calib_ds .cond [i ])['verdict' ]
173+ for i in range (len (sig_ca ))
174+ ]
175+ )
176+ conf = verds == 'CONFIRMED'
177+ cnfl = verds == 'CONFLICT'
146178 ca = correct_ca [conf ].mean () if conf .any () else 0.0
147179 fa = correct_ca [cnfl ].mean () if cnfl .any () else 1.0
148180 gap = ca - fa
149181 if gap > best_gap :
150182 best_gap = gap
151183 best_tau = tau
152-
153- print (f" Calibrated best tau = { best_tau } (Calib GAP = { best_gap :+.3f} )" )
184+
185+ print (f' Calibrated best tau = { best_tau } (Calib GAP = { best_gap :+.3f} )' )
154186 model .symbolic .tau = float (best_tau )
155-
187+
156188 # 5. Evaluate on Test Set
157189 probs_te = model .cnn .predict (Xin_te , batch_size = 128 , verbose = 0 )
158190 pred_te = probs_te .argmax (1 )
159- correct_te = ( pred_te == yte )
160-
191+ correct_te = pred_te == yte
192+
161193 # Physics evaluation
162- verds = np .array ([model .symbolic .diagnose (sig_te [i ], pred_te [i ], cond_te [i ])['verdict' ] for i in range (len (sig_te ))])
163- conf = (verds == 'CONFIRMED' )
164- cnfl = (verds == 'CONFLICT' )
194+ verds = np .array (
195+ [
196+ model .symbolic .diagnose (sig_te [i ], pred_te [i ], cond_te [i ])['verdict' ]
197+ for i in range (len (sig_te ))
198+ ]
199+ )
200+ conf = verds == 'CONFIRMED'
201+ cnfl = verds == 'CONFLICT'
165202 ca = correct_te [conf ].mean () if conf .any () else float ('nan' )
166203 fa = correct_te [cnfl ].mean () if cnfl .any () else float ('nan' )
167204 phys_gap = ca - fa
168205 target_n = int (conf .sum ())
169206 results ['phys_gap' ].append (phys_gap )
170- print (f" Physics GAP={ phys_gap :+.3f} (Coverage N={ target_n } )" )
171-
207+ print (f' Physics GAP={ phys_gap :+.3f} (Coverage N={ target_n } )' )
208+
172209 # Softmax evaluation at matched coverage
173210 softmax_score = probs_te .max (1 )
174211 soft_gap = get_matched_coverage_gap (softmax_score , correct_te , target_n )
175212 results ['soft_gap' ].append (soft_gap )
176-
213+
177214 # MC-Dropout at matched coverage
178215 mc_model = tf .keras .models .clone_model (model .cnn , clone_function = clone_for_mc )
179216 mc_model .set_weights (model .cnn .get_weights ())
@@ -184,65 +221,82 @@ def clone_for_mc(layer):
184221 mc_preds = np .stack (mc_preds )
185222 mc_mean = mc_preds .mean (0 )
186223 mc_pred_class = mc_mean .argmax (1 )
187- mc_correct = ( mc_pred_class == yte )
224+ mc_correct = mc_pred_class == yte
188225 eps = 1e-12
189- mc_score = (mc_mean * np .log (mc_mean + eps )).sum (1 ) # Certainty (negative entropy)
226+ mc_score = (mc_mean * np .log (mc_mean + eps )).sum (1 ) # Certainty (negative entropy)
190227 mc_gap = get_matched_coverage_gap (mc_score , mc_correct , target_n )
191228 results ['mc_gap' ].append (mc_gap )
192-
229+
193230 # Ensemble at matched coverage
194- ens_preds_probs = np .stack ([m .predict (Xin_te , batch_size = 128 , verbose = 0 ) for m in ens ]) # (3, n, c)
195- ens_mean = ens_preds_probs .mean (0 ) # (n, c)
231+ ens_preds_probs = np .stack (
232+ [m .predict (Xin_te , batch_size = 128 , verbose = 0 ) for m in ens ]
233+ ) # (3, n, c)
234+ ens_mean = ens_preds_probs .mean (0 ) # (n, c)
196235 ens_pred_class = ens_mean .argmax (1 )
197- ens_correct = ( ens_pred_class == yte )
236+ ens_correct = ens_pred_class == yte
198237 # Score = negative entropy of ensemble mean
199238 ens_score = (ens_mean * np .log (ens_mean + eps )).sum (1 )
200239 ens_gap = get_matched_coverage_gap (ens_score , ens_correct , target_n )
201240 results ['ens_gap' ].append (ens_gap )
202-
203- print (f"Matched Coverage GAPs -> Softmax:{ soft_gap :+.3f} | MC-Drop:{ mc_gap :+.3f} | Ens:{ ens_gap :+.3f} " )
204-
241+
242+ print (
243+ f'Matched Coverage GAPs -> Softmax:{ soft_gap :+.3f} | MC-Drop:{ mc_gap :+.3f} | Ens:{ ens_gap :+.3f} '
244+ )
245+
205246 # 6. Noise Test
206247 rng = np .random .RandomState (seed )
207- sig_power = (sig_te ** 2 ).mean ()
248+ sig_power = (sig_te ** 2 ).mean ()
208249 for snr_db in [np .inf , 20 , 10 , 5 , 0 ]:
209250 if np .isinf (snr_db ):
210251 sig_n = sig_te
211252 else :
212253 npow = sig_power / (10 ** (snr_db / 10 ))
213254 sig_n = sig_te + rng .randn (* sig_te .shape ).astype (np .float32 ) * np .sqrt (npow )
214-
255+
215256 Xin_n = sig_n [..., None ]
216257 # Use ensemble mode vote to define 'unanimous' exactly like Abhi's template
217- ep_class = np .stack ([m .predict (Xin_n , batch_size = 128 , verbose = 0 ).argmax (1 ) for m in ens ])
258+ ep_class = np .stack (
259+ [m .predict (Xin_n , batch_size = 128 , verbose = 0 ).argmax (1 ) for m in ens ]
260+ )
218261 v = stats .mode (ep_class , axis = 0 , keepdims = False ).mode
219262 unan = (ep_class == v ).all (0 )
220- ok = (v == yte )
221-
222- pv = np .array ([model .symbolic .diagnose (sig_n [i ], v [i ], cond_te [i ])['verdict' ] for i in range (len (sig_n ))])
223- pc = (pv == 'CONFLICT' )
263+ ok = v == yte
264+
265+ pv = np .array (
266+ [
267+ model .symbolic .diagnose (sig_n [i ], v [i ], cond_te [i ])['verdict' ]
268+ for i in range (len (sig_n ))
269+ ]
270+ )
271+ pc = pv == 'CONFLICT'
224272 uw = unan & (~ ok )
225273 catch = pc [uw ].mean () if uw .sum () > 0 else float ('nan' )
226274 results ['noise_catch' ][snr_db ].append (catch )
227275 s = 'clean' if np .isinf (snr_db ) else f'{ snr_db } dB'
228- print (f" Noise={ s :>5} | catch_rate={ catch :.3f} " )
229-
230- print ("\n " + "=" * 60 + "\n FINAL AGGREGATED RESULTS (5 Seeds)\n " + "=" * 60 )
231- print (f"Physics GAP: { np .nanmean (results ['phys_gap' ]):+.3f} ± { np .nanstd (results ['phys_gap' ]):.3f} " )
232- print (f"Softmax GAP: { np .nanmean (results ['soft_gap' ]):+.3f} ± { np .nanstd (results ['soft_gap' ]):.3f} " )
233- print (f"MC-Drop GAP: { np .nanmean (results ['mc_gap' ]):+.3f} ± { np .nanstd (results ['mc_gap' ]):.3f} " )
234- print (f"Ensemble GAP: { np .nanmean (results ['ens_gap' ]):+.3f} ± { np .nanstd (results ['ens_gap' ]):.3f} " )
235-
276+ print (f' Noise={ s :>5} | catch_rate={ catch :.3f} ' )
277+
278+ print ('\n ' + '=' * 60 + '\n FINAL AGGREGATED RESULTS (5 Seeds)\n ' + '=' * 60 )
279+ print (
280+ f'Physics GAP: { np .nanmean (results ["phys_gap" ]):+.3f} ± { np .nanstd (results ["phys_gap" ]):.3f} '
281+ )
282+ print (
283+ f'Softmax GAP: { np .nanmean (results ["soft_gap" ]):+.3f} ± { np .nanstd (results ["soft_gap" ]):.3f} '
284+ )
285+ print (f'MC-Drop GAP: { np .nanmean (results ["mc_gap" ]):+.3f} ± { np .nanstd (results ["mc_gap" ]):.3f} ' )
286+ print (
287+ f'Ensemble GAP: { np .nanmean (results ["ens_gap" ]):+.3f} ± { np .nanstd (results ["ens_gap" ]):.3f} '
288+ )
289+
236290 print ("\n Noise Test Catch Rate (Physics catches Ensemble's confident errors):" )
237291 for snr_db in [np .inf , 20 , 10 , 5 , 0 ]:
238292 s = 'clean' if np .isinf (snr_db ) else f'{ snr_db } dB'
239293 vals = [v for v in results ['noise_catch' ][snr_db ] if not np .isnan (v )]
240294 m = np .nanmean (vals ) if len (vals ) > 0 else float ('nan' )
241295 std = np .nanstd (vals ) if len (vals ) > 0 else float ('nan' )
242- print (f" { s :>6} : { m :.3f} ± { std :.3f} " )
243-
244- print (" ================ DONE ================" )
245-
296+ print (f' { s :>6} : { m :.3f} ± { std :.3f} ' )
297+
298+ print (' ================ DONE ================' )
299+
246300except Exception :
247301 with open ('crash_traceback.txt' , 'w' ) as f :
248302 traceback .print_exc (file = f )
0 commit comments