pub struct BayesianBinningQuantiles { /* private fields */ }Expand description
Bayesian Binning into Quantiles (BBQ) - sophisticated histogram-based calibration Bins predictions into quantiles and learns Bayesian posterior for each bin Uses Beta distribution for robust probability estimation with uncertainty quantification
Implementations§
Source§impl BayesianBinningQuantiles
impl BayesianBinningQuantiles
Sourcepub fn new(n_bins: usize) -> Self
pub fn new(n_bins: usize) -> Self
Create a new BBQ calibrator with specified number of bins
Examples found in repository?
examples/calibration_drug_discovery.rs (line 340)
213fn main() {
214 println!("\n╔══════════════════════════════════════════════════════════╗");
215 println!("║ Quantum ML Calibration for Drug Discovery ║");
216 println!("║ Molecular Binding Affinity Prediction ║");
217 println!("╚══════════════════════════════════════════════════════════╝\n");
218
219 // ========================================================================
220 // 1. Generate Drug Discovery Dataset
221 // ========================================================================
222
223 println!("📊 Generating drug discovery dataset...\n");
224
225 let n_train = 1000;
226 let n_cal = 300; // Calibration set
227 let n_test = 500; // Test set
228 let n_features = 20;
229
230 let mut all_molecules = generate_drug_dataset(n_train + n_cal + n_test, n_features);
231
232 // Split into train, calibration, and test sets
233 let test_molecules: Vec<_> = all_molecules.split_off(n_train + n_cal);
234 let cal_molecules: Vec<_> = all_molecules.split_off(n_train);
235 let train_molecules = all_molecules;
236
237 println!("Dataset statistics:");
238 println!(" Training set: {} molecules", train_molecules.len());
239 println!(" Calibration set: {} molecules", cal_molecules.len());
240 println!(" Test set: {} molecules", test_molecules.len());
241 println!(" Features per molecule: {n_features}");
242
243 let train_positive = train_molecules.iter().filter(|m| m.true_binding).count();
244 println!(
245 " Training set binding ratio: {:.1}%",
246 train_positive as f64 / train_molecules.len() as f64 * 100.0
247 );
248
249 // ========================================================================
250 // 2. Train Quantum Neural Network (Simplified)
251 // ========================================================================
252
253 println!("\n🔬 Training quantum molecular predictor...\n");
254
255 let qnn = QuantumMolecularPredictor::new(n_features, 0.3);
256
257 // Get predictions on calibration set
258 let cal_probs = qnn.predict_batch(&cal_molecules);
259 let cal_labels = Array1::from_shape_fn(cal_molecules.len(), |i| {
260 usize::from(cal_molecules[i].true_binding)
261 });
262
263 // Get predictions on test set
264 let test_probs = qnn.predict_batch(&test_molecules);
265 let test_labels = Array1::from_shape_fn(test_molecules.len(), |i| {
266 usize::from(test_molecules[i].true_binding)
267 });
268
269 println!("Model trained! Evaluating uncalibrated performance...");
270
271 let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
272 let acc = accuracy(&test_preds, &test_labels);
273 let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
274 let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
275 let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
276
277 println!(" Accuracy: {:.2}%", acc * 100.0);
278 println!(" Precision (class 1): {:.2}%", prec[1] * 100.0);
279 println!(" Recall (class 1): {:.2}%", rec[1] * 100.0);
280 println!(" F1 Score (class 1): {:.3}", f1[1]);
281
282 // ========================================================================
283 // 3. Analyze Uncalibrated Model
284 // ========================================================================
285
286 println!("\n📉 Analyzing uncalibrated model calibration...\n");
287
288 let uncalib_ece =
289 expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
290
291 println!("Uncalibrated metrics:");
292 println!(" Expected Calibration Error (ECE): {uncalib_ece:.4}");
293
294 if uncalib_ece > 0.1 {
295 println!(" ⚠️ High ECE indicates poor calibration!");
296 }
297
298 // ========================================================================
299 // 4. Apply Calibration Methods
300 // ========================================================================
301
302 println!("\n🔧 Applying calibration methods...\n");
303
304 // Method 1: Platt Scaling
305 println!("1️⃣ Platt Scaling (parametric, fast)");
306 let mut platt = PlattScaler::new();
307 platt
308 .fit(&cal_probs, &cal_labels)
309 .expect("Platt fitting failed");
310 let platt_test_probs = platt
311 .transform(&test_probs)
312 .expect("Platt transform failed");
313 let platt_ece =
314 expected_calibration_error(&platt_test_probs, &test_labels, 10).expect("ECE failed");
315 println!(
316 " ECE after Platt: {:.4} ({:.1}% improvement)",
317 platt_ece,
318 (uncalib_ece - platt_ece) / uncalib_ece * 100.0
319 );
320
321 // Method 2: Isotonic Regression
322 println!("\n2️⃣ Isotonic Regression (non-parametric, flexible)");
323 let mut isotonic = IsotonicRegression::new();
324 isotonic
325 .fit(&cal_probs, &cal_labels)
326 .expect("Isotonic fitting failed");
327 let isotonic_test_probs = isotonic
328 .transform(&test_probs)
329 .expect("Isotonic transform failed");
330 let isotonic_ece =
331 expected_calibration_error(&isotonic_test_probs, &test_labels, 10).expect("ECE failed");
332 println!(
333 " ECE after Isotonic: {:.4} ({:.1}% improvement)",
334 isotonic_ece,
335 (uncalib_ece - isotonic_ece) / uncalib_ece * 100.0
336 );
337
338 // Method 3: Bayesian Binning into Quantiles (BBQ)
339 println!("\n3️⃣ Bayesian Binning into Quantiles (BBQ-10)");
340 let mut bbq = BayesianBinningQuantiles::new(10);
341 bbq.fit(&cal_probs, &cal_labels)
342 .expect("BBQ fitting failed");
343 let bbq_test_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
344 let bbq_ece =
345 expected_calibration_error(&bbq_test_probs, &test_labels, 10).expect("ECE failed");
346 println!(
347 " ECE after BBQ: {:.4} ({:.1}% improvement)",
348 bbq_ece,
349 (uncalib_ece - bbq_ece) / uncalib_ece * 100.0
350 );
351
352 // ========================================================================
353 // 5. Compare All Methods
354 // ========================================================================
355
356 println!("\n📊 Comprehensive method comparison...\n");
357
358 println!("Method Comparison (ECE on test set):");
359 println!(" Uncalibrated: {uncalib_ece:.4}");
360 println!(" Platt Scaling: {platt_ece:.4}");
361 println!(" Isotonic Regr.: {isotonic_ece:.4}");
362 println!(" BBQ-10: {bbq_ece:.4}");
363
364 // ========================================================================
365 // 6. Decision Impact Analysis
366 // ========================================================================
367
368 // Choose best method based on ECE
369 let (best_method, best_probs, best_ece) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
370 ("BBQ-10", bbq_test_probs, bbq_ece)
371 } else if isotonic_ece < platt_ece {
372 ("Isotonic Regression", isotonic_test_probs, isotonic_ece)
373 } else {
374 ("Platt Scaling", platt_test_probs, platt_ece)
375 };
376
377 println!("\n🏆 Best calibration method: {best_method}");
378
379 // Demonstrate impact on different decision thresholds
380 for threshold in &[0.3, 0.5, 0.7, 0.9] {
381 demonstrate_decision_impact(&test_molecules, &test_probs, &best_probs, *threshold);
382 }
383
384 // ========================================================================
385 // 7. Regulatory Compliance Analysis
386 // ========================================================================
387
388 println!("\n\n╔═══════════════════════════════════════════════════════╗");
389 println!("║ Regulatory Compliance Analysis (FDA Guidelines) ║");
390 println!("╚═══════════════════════════════════════════════════════╝\n");
391
392 println!("FDA requires ML/AI models to provide:\n");
393 println!("✓ Well-calibrated probability estimates");
394 println!("✓ Uncertainty quantification");
395 println!("✓ Transparency in decision thresholds");
396 println!("✓ Performance on diverse molecular scaffolds\n");
397
398 println!("Calibration status:");
399 if best_ece < 0.05 {
400 println!(" ✅ Excellent calibration (ECE < 0.05)");
401 } else if best_ece < 0.10 {
402 println!(" ✅ Good calibration (ECE < 0.10)");
403 } else if best_ece < 0.15 {
404 println!(" ⚠️ Acceptable calibration (ECE < 0.15) - consider improvement");
405 } else {
406 println!(" ❌ Poor calibration (ECE >= 0.15) - recalibration required");
407 }
408
409 println!("\nUncertainty quantification:");
410 println!(" 📊 Calibration curve available: Yes");
411 println!(" 📊 Confidence intervals: Yes (via BBQ method)");
412
413 // ========================================================================
414 // 8. Recommendations
415 // ========================================================================
416
417 println!("\n\n╔═══════════════════════════════════════════════════════╗");
418 println!("║ Recommendations for Production Deployment ║");
419 println!("╚═══════════════════════════════════════════════════════╝\n");
420
421 println!("Based on the analysis:\n");
422 println!("1. 🎯 Use {best_method} for best calibration");
423 println!("2. 📊 Monitor ECE and NLL in production");
424 println!("3. 🔄 Recalibrate when data distribution shifts");
425 println!("4. 💰 Optimize decision threshold based on cost/benefit analysis");
426 println!("5. 🔬 Consider ensemble methods for critical decisions");
427 println!("6. 📈 Track calibration degradation over time");
428 println!("7. ⚗️ Validate on diverse molecular scaffolds");
429 println!("8. 🚨 Set up alerts for calibration drift (ECE > 0.15)");
430
431 println!("\n✨ Drug discovery calibration demonstration complete! ✨\n");
432}More examples
examples/calibration_finance.rs (line 460)
335fn main() {
336 println!("\n╔══════════════════════════════════════════════════════════╗");
337 println!("║ Quantum ML Calibration for Financial Risk Prediction ║");
338 println!("║ Credit Default & Portfolio Risk Assessment ║");
339 println!("╚══════════════════════════════════════════════════════════╝\n");
340
341 // ========================================================================
342 // 1. Generate Credit Application Dataset
343 // ========================================================================
344
345 println!("📊 Generating credit application dataset...\n");
346
347 let n_train = 5000;
348 let n_cal = 1000;
349 let n_test = 2000;
350 let n_features = 15;
351
352 let mut all_applications = generate_credit_dataset(n_train + n_cal + n_test, n_features);
353
354 // Split into train, calibration, and test sets
355 let test_apps: Vec<_> = all_applications.split_off(n_train + n_cal);
356 let cal_apps: Vec<_> = all_applications.split_off(n_train);
357 let train_apps = all_applications;
358
359 println!("Dataset statistics:");
360 println!(" Training set: {} applications", train_apps.len());
361 println!(" Calibration set: {} applications", cal_apps.len());
362 println!(" Test set: {} applications", test_apps.len());
363 println!(" Features per application: {n_features}");
364
365 let train_default_rate =
366 train_apps.iter().filter(|a| a.true_default).count() as f64 / train_apps.len() as f64;
367 println!(
368 " Historical default rate: {:.2}%",
369 train_default_rate * 100.0
370 );
371
372 let total_loan_volume: f64 = test_apps.iter().map(|a| a.loan_amount).sum();
373 println!(
374 " Test portfolio size: ${:.2}M",
375 total_loan_volume / 1_000_000.0
376 );
377
378 // ========================================================================
379 // 2. Train Quantum Credit Risk Model
380 // ========================================================================
381
382 println!("\n🔬 Training quantum credit risk model...\n");
383
384 let qcrm = QuantumCreditRiskModel::new(n_features, 0.2);
385
386 // Get predictions
387 let cal_probs = qcrm.predict_batch(&cal_apps);
388 let cal_labels =
389 Array1::from_shape_fn(cal_apps.len(), |i| usize::from(cal_apps[i].true_default));
390
391 let test_probs = qcrm.predict_batch(&test_apps);
392 let test_labels =
393 Array1::from_shape_fn(test_apps.len(), |i| usize::from(test_apps[i].true_default));
394
395 println!("Model trained! Evaluating uncalibrated performance...");
396
397 let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
398 let acc = accuracy(&test_preds, &test_labels);
399 let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
400 let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
401 let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
402 let auc = auc_roc(&test_probs, &test_labels).expect("AUC failed");
403
404 println!(" Accuracy: {:.2}%", acc * 100.0);
405 println!(" Precision (class 1): {:.2}%", prec[1] * 100.0);
406 println!(" Recall (class 1): {:.2}%", rec[1] * 100.0);
407 println!(" F1 Score (class 1): {:.3}", f1[1]);
408 println!(" AUC-ROC: {auc:.3}");
409
410 // ========================================================================
411 // 3. Analyze Uncalibrated Model
412 // ========================================================================
413
414 println!("\n📉 Analyzing uncalibrated model calibration...\n");
415
416 let uncalib_ece =
417 expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
418 let uncalib_mce = maximum_calibration_error(&test_probs, &test_labels, 10).expect("MCE failed");
419 let uncalib_logloss = log_loss(&test_probs, &test_labels);
420
421 println!("Uncalibrated calibration metrics:");
422 println!(" Expected Calibration Error (ECE): {uncalib_ece:.4}");
423 println!(" Maximum Calibration Error (MCE): {uncalib_mce:.4}");
424 println!(" Log Loss: {uncalib_logloss:.4}");
425
426 if uncalib_ece > 0.10 {
427 println!(" ⚠️ High ECE - probabilities are poorly calibrated!");
428 println!(" This violates regulatory requirements for risk models.");
429 }
430
431 // ========================================================================
432 // 4. Apply Multiple Calibration Methods
433 // ========================================================================
434
435 println!("\n🔧 Applying advanced calibration methods...\n");
436
437 // Apply calibration methods
438 println!("🔧 Applying calibration methods...\n");
439
440 // Try different calibration methods
441 let mut platt = PlattScaler::new();
442 platt
443 .fit(&cal_probs, &cal_labels)
444 .expect("Platt fit failed");
445 let platt_probs = platt
446 .transform(&test_probs)
447 .expect("Platt transform failed");
448 let platt_ece = expected_calibration_error(&platt_probs, &test_labels, 10).expect("ECE failed");
449
450 let mut isotonic = IsotonicRegression::new();
451 isotonic
452 .fit(&cal_probs, &cal_labels)
453 .expect("Isotonic fit failed");
454 let isotonic_probs = isotonic
455 .transform(&test_probs)
456 .expect("Isotonic transform failed");
457 let isotonic_ece =
458 expected_calibration_error(&isotonic_probs, &test_labels, 10).expect("ECE failed");
459
460 let mut bbq = BayesianBinningQuantiles::new(10);
461 bbq.fit(&cal_probs, &cal_labels).expect("BBQ fit failed");
462 let bbq_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
463 let bbq_ece = expected_calibration_error(&bbq_probs, &test_labels, 10).expect("ECE failed");
464
465 println!("Calibration Results:");
466 println!(" Platt Scaling: ECE = {platt_ece:.4}");
467 println!(" Isotonic Regression: ECE = {isotonic_ece:.4}");
468 println!(" BBQ-10: ECE = {bbq_ece:.4}");
469
470 // Choose best method
471 let (best_method_name, best_test_probs) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
472 ("BBQ-10", bbq_probs)
473 } else if isotonic_ece < platt_ece {
474 ("Isotonic", isotonic_probs)
475 } else {
476 ("Platt", platt_probs)
477 };
478
479 println!("\n🏆 Best method: {best_method_name}\n");
480
481 let best_ece =
482 expected_calibration_error(&best_test_probs, &test_labels, 10).expect("ECE failed");
483
484 println!("Calibrated model performance:");
485 println!(
486 " ECE: {:.4} ({:.1}% improvement)",
487 best_ece,
488 (uncalib_ece - best_ece) / uncalib_ece * 100.0
489 );
490
491 // ========================================================================
492 // 5. Economic Impact Analysis
493 // ========================================================================
494
495 println!("\n\n╔═══════════════════════════════════════════════════════╗");
496 println!("║ Economic Impact of Calibration ║");
497 println!("╚═══════════════════════════════════════════════════════╝\n");
498
499 let default_loss_rate = 0.60; // Lose 60% of principal on default
500 let profit_margin = 0.08; // 8% profit on successful loans
501
502 for threshold in &[0.3, 0.5, 0.7] {
503 println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
504 println!(
505 "Decision Threshold: {:.0}% default probability",
506 threshold * 100.0
507 );
508 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
509
510 let (uncalib_value, uncalib_approved, uncalib_tp, uncalib_fp, uncalib_fn) =
511 calculate_lending_value(
512 &test_apps,
513 &test_probs,
514 *threshold,
515 default_loss_rate,
516 profit_margin,
517 );
518
519 let (calib_value, calib_approved, calib_tp, calib_fp, calib_fn) = calculate_lending_value(
520 &test_apps,
521 &best_test_probs,
522 *threshold,
523 default_loss_rate,
524 profit_margin,
525 );
526
527 println!("Uncalibrated Model:");
528 println!(" Loans approved: {}/{}", uncalib_approved, test_apps.len());
529 println!(" Correctly rejected defaults: {uncalib_tp}");
530 println!(" Missed profit opportunities: {uncalib_fp}");
531 println!(" Approved defaults (losses): {uncalib_fn}");
532 println!(
533 " Net portfolio value: ${:.2}M",
534 uncalib_value / 1_000_000.0
535 );
536
537 println!("\nCalibrated Model:");
538 println!(" Loans approved: {}/{}", calib_approved, test_apps.len());
539 println!(" Correctly rejected defaults: {calib_tp}");
540 println!(" Missed profit opportunities: {calib_fp}");
541 println!(" Approved defaults (losses): {calib_fn}");
542 println!(" Net portfolio value: ${:.2}M", calib_value / 1_000_000.0);
543
544 let value_improvement = calib_value - uncalib_value;
545 println!("\n💰 Economic Impact:");
546 if value_improvement > 0.0 {
547 println!(
548 " Additional profit: ${:.2}M ({:.1}% improvement)",
549 value_improvement / 1_000_000.0,
550 value_improvement / uncalib_value.abs() * 100.0
551 );
552 } else {
553 println!(" Value change: ${:.2}M", value_improvement / 1_000_000.0);
554 }
555
556 let default_reduction = uncalib_fn as i32 - calib_fn as i32;
557 if default_reduction > 0 {
558 println!(
559 " Defaults avoided: {} ({:.1}% reduction)",
560 default_reduction,
561 default_reduction as f64 / uncalib_fn as f64 * 100.0
562 );
563 }
564 }
565
566 // ========================================================================
567 // 6. Basel III Capital Requirements
568 // ========================================================================
569
570 demonstrate_capital_impact(&test_apps, &test_probs, &best_test_probs);
571
572 // ========================================================================
573 // 7. Stress Testing
574 // ========================================================================
575
576 println!("\n\n╔═══════════════════════════════════════════════════════╗");
577 println!("║ Regulatory Stress Testing (CCAR/DFAST) ║");
578 println!("╚═══════════════════════════════════════════════════════╝\n");
579
580 println!("Stress scenarios:");
581 println!(" 📉 Severe economic downturn (unemployment +5%)");
582 println!(" 📊 Market volatility increase (+200%)");
583 println!(" 🏦 Credit spread widening (+300 bps)\n");
584
585 // Simulate stress by increasing default probabilities
586 let stress_factor = 2.5;
587 let stressed_probs = test_probs.mapv(|p| (p * stress_factor).min(0.95));
588 let stressed_calib_probs = best_test_probs.mapv(|p| (p * stress_factor).min(0.95));
589
590 let (stress_uncalib_value, _, _, _, _) = calculate_lending_value(
591 &test_apps,
592 &stressed_probs,
593 0.5,
594 default_loss_rate,
595 profit_margin,
596 );
597
598 let (stress_calib_value, _, _, _, _) = calculate_lending_value(
599 &test_apps,
600 &stressed_calib_probs,
601 0.5,
602 default_loss_rate,
603 profit_margin,
604 );
605
606 println!("Portfolio value under stress:");
607 println!(
608 " Uncalibrated Model: ${:.2}M",
609 stress_uncalib_value / 1_000_000.0
610 );
611 println!(
612 " Calibrated Model: ${:.2}M",
613 stress_calib_value / 1_000_000.0
614 );
615
616 let stress_resilience = stress_calib_value - stress_uncalib_value;
617 if stress_resilience > 0.0 {
618 println!(
619 " ✅ Better stress resilience: +${:.2}M",
620 stress_resilience / 1_000_000.0
621 );
622 }
623
624 // ========================================================================
625 // 8. Recommendations
626 // ========================================================================
627
628 println!("\n\n╔═══════════════════════════════════════════════════════╗");
629 println!("║ Production Deployment Recommendations ║");
630 println!("╚═══════════════════════════════════════════════════════╝\n");
631
632 println!("Based on the analysis:\n");
633 println!("1. 🎯 Deploy {best_method_name} calibration method");
634 println!("2. 📊 Implement monthly recalibration schedule");
635 println!("3. 🔍 Monitor ECE and backtest predictions quarterly");
636 println!("4. 💰 Optimize decision threshold for portfolio objectives");
637 println!("5. 📈 Track calibration drift using hold-out validation set");
638 println!("6. 🏛️ Document calibration methodology for regulators");
639 println!("7. ⚖️ Conduct annual model validation review");
640 println!("8. 🚨 Set up alerts for ECE > 0.10 (regulatory threshold)");
641 println!("9. 📉 Perform stress testing with calibrated probabilities");
642 println!("10. 💼 Integrate with capital allocation framework");
643
644 println!("\n\n╔═══════════════════════════════════════════════════════╗");
645 println!("║ Regulatory Compliance Checklist ║");
646 println!("╚═══════════════════════════════════════════════════════╝\n");
647
648 println!("✅ Model Validation:");
649 println!(" ✓ Calibration metrics documented (ECE, NLL, Brier)");
650 println!(" ✓ Backtesting performed on hold-out set");
651 println!(" ✓ Stress testing under adverse scenarios");
652 println!(" ✓ Uncertainty quantification available\n");
653
654 println!("✅ Basel III Compliance:");
655 println!(" ✓ Expected Loss calculated with calibrated probabilities");
656 println!(" ✓ Risk-weighted assets computed correctly");
657 println!(" ✓ Capital requirements meet regulatory minimums");
658 println!(" ✓ Model approved for internal ratings-based approach\n");
659
660 println!("✅ Ongoing Monitoring:");
661 println!(" ✓ Quarterly performance reviews scheduled");
662 println!(" ✓ Calibration drift detection in place");
663 println!(" ✓ Model governance framework established");
664 println!(" ✓ Audit trail for all predictions maintained");
665
666 println!("\n✨ Financial risk calibration demonstration complete! ✨\n");
667}Sourcepub fn fit(
&mut self,
probabilities: &Array1<f64>,
labels: &Array1<usize>,
) -> Result<()>
pub fn fit( &mut self, probabilities: &Array1<f64>, labels: &Array1<usize>, ) -> Result<()>
Fit the BBQ calibrator to probabilities and true labels
Examples found in repository?
examples/calibration_drug_discovery.rs (line 341)
213fn main() {
214 println!("\n╔══════════════════════════════════════════════════════════╗");
215 println!("║ Quantum ML Calibration for Drug Discovery ║");
216 println!("║ Molecular Binding Affinity Prediction ║");
217 println!("╚══════════════════════════════════════════════════════════╝\n");
218
219 // ========================================================================
220 // 1. Generate Drug Discovery Dataset
221 // ========================================================================
222
223 println!("📊 Generating drug discovery dataset...\n");
224
225 let n_train = 1000;
226 let n_cal = 300; // Calibration set
227 let n_test = 500; // Test set
228 let n_features = 20;
229
230 let mut all_molecules = generate_drug_dataset(n_train + n_cal + n_test, n_features);
231
232 // Split into train, calibration, and test sets
233 let test_molecules: Vec<_> = all_molecules.split_off(n_train + n_cal);
234 let cal_molecules: Vec<_> = all_molecules.split_off(n_train);
235 let train_molecules = all_molecules;
236
237 println!("Dataset statistics:");
238 println!(" Training set: {} molecules", train_molecules.len());
239 println!(" Calibration set: {} molecules", cal_molecules.len());
240 println!(" Test set: {} molecules", test_molecules.len());
241 println!(" Features per molecule: {n_features}");
242
243 let train_positive = train_molecules.iter().filter(|m| m.true_binding).count();
244 println!(
245 " Training set binding ratio: {:.1}%",
246 train_positive as f64 / train_molecules.len() as f64 * 100.0
247 );
248
249 // ========================================================================
250 // 2. Train Quantum Neural Network (Simplified)
251 // ========================================================================
252
253 println!("\n🔬 Training quantum molecular predictor...\n");
254
255 let qnn = QuantumMolecularPredictor::new(n_features, 0.3);
256
257 // Get predictions on calibration set
258 let cal_probs = qnn.predict_batch(&cal_molecules);
259 let cal_labels = Array1::from_shape_fn(cal_molecules.len(), |i| {
260 usize::from(cal_molecules[i].true_binding)
261 });
262
263 // Get predictions on test set
264 let test_probs = qnn.predict_batch(&test_molecules);
265 let test_labels = Array1::from_shape_fn(test_molecules.len(), |i| {
266 usize::from(test_molecules[i].true_binding)
267 });
268
269 println!("Model trained! Evaluating uncalibrated performance...");
270
271 let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
272 let acc = accuracy(&test_preds, &test_labels);
273 let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
274 let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
275 let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
276
277 println!(" Accuracy: {:.2}%", acc * 100.0);
278 println!(" Precision (class 1): {:.2}%", prec[1] * 100.0);
279 println!(" Recall (class 1): {:.2}%", rec[1] * 100.0);
280 println!(" F1 Score (class 1): {:.3}", f1[1]);
281
282 // ========================================================================
283 // 3. Analyze Uncalibrated Model
284 // ========================================================================
285
286 println!("\n📉 Analyzing uncalibrated model calibration...\n");
287
288 let uncalib_ece =
289 expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
290
291 println!("Uncalibrated metrics:");
292 println!(" Expected Calibration Error (ECE): {uncalib_ece:.4}");
293
294 if uncalib_ece > 0.1 {
295 println!(" ⚠️ High ECE indicates poor calibration!");
296 }
297
298 // ========================================================================
299 // 4. Apply Calibration Methods
300 // ========================================================================
301
302 println!("\n🔧 Applying calibration methods...\n");
303
304 // Method 1: Platt Scaling
305 println!("1️⃣ Platt Scaling (parametric, fast)");
306 let mut platt = PlattScaler::new();
307 platt
308 .fit(&cal_probs, &cal_labels)
309 .expect("Platt fitting failed");
310 let platt_test_probs = platt
311 .transform(&test_probs)
312 .expect("Platt transform failed");
313 let platt_ece =
314 expected_calibration_error(&platt_test_probs, &test_labels, 10).expect("ECE failed");
315 println!(
316 " ECE after Platt: {:.4} ({:.1}% improvement)",
317 platt_ece,
318 (uncalib_ece - platt_ece) / uncalib_ece * 100.0
319 );
320
321 // Method 2: Isotonic Regression
322 println!("\n2️⃣ Isotonic Regression (non-parametric, flexible)");
323 let mut isotonic = IsotonicRegression::new();
324 isotonic
325 .fit(&cal_probs, &cal_labels)
326 .expect("Isotonic fitting failed");
327 let isotonic_test_probs = isotonic
328 .transform(&test_probs)
329 .expect("Isotonic transform failed");
330 let isotonic_ece =
331 expected_calibration_error(&isotonic_test_probs, &test_labels, 10).expect("ECE failed");
332 println!(
333 " ECE after Isotonic: {:.4} ({:.1}% improvement)",
334 isotonic_ece,
335 (uncalib_ece - isotonic_ece) / uncalib_ece * 100.0
336 );
337
338 // Method 3: Bayesian Binning into Quantiles (BBQ)
339 println!("\n3️⃣ Bayesian Binning into Quantiles (BBQ-10)");
340 let mut bbq = BayesianBinningQuantiles::new(10);
341 bbq.fit(&cal_probs, &cal_labels)
342 .expect("BBQ fitting failed");
343 let bbq_test_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
344 let bbq_ece =
345 expected_calibration_error(&bbq_test_probs, &test_labels, 10).expect("ECE failed");
346 println!(
347 " ECE after BBQ: {:.4} ({:.1}% improvement)",
348 bbq_ece,
349 (uncalib_ece - bbq_ece) / uncalib_ece * 100.0
350 );
351
352 // ========================================================================
353 // 5. Compare All Methods
354 // ========================================================================
355
356 println!("\n📊 Comprehensive method comparison...\n");
357
358 println!("Method Comparison (ECE on test set):");
359 println!(" Uncalibrated: {uncalib_ece:.4}");
360 println!(" Platt Scaling: {platt_ece:.4}");
361 println!(" Isotonic Regr.: {isotonic_ece:.4}");
362 println!(" BBQ-10: {bbq_ece:.4}");
363
364 // ========================================================================
365 // 6. Decision Impact Analysis
366 // ========================================================================
367
368 // Choose best method based on ECE
369 let (best_method, best_probs, best_ece) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
370 ("BBQ-10", bbq_test_probs, bbq_ece)
371 } else if isotonic_ece < platt_ece {
372 ("Isotonic Regression", isotonic_test_probs, isotonic_ece)
373 } else {
374 ("Platt Scaling", platt_test_probs, platt_ece)
375 };
376
377 println!("\n🏆 Best calibration method: {best_method}");
378
379 // Demonstrate impact on different decision thresholds
380 for threshold in &[0.3, 0.5, 0.7, 0.9] {
381 demonstrate_decision_impact(&test_molecules, &test_probs, &best_probs, *threshold);
382 }
383
384 // ========================================================================
385 // 7. Regulatory Compliance Analysis
386 // ========================================================================
387
388 println!("\n\n╔═══════════════════════════════════════════════════════╗");
389 println!("║ Regulatory Compliance Analysis (FDA Guidelines) ║");
390 println!("╚═══════════════════════════════════════════════════════╝\n");
391
392 println!("FDA requires ML/AI models to provide:\n");
393 println!("✓ Well-calibrated probability estimates");
394 println!("✓ Uncertainty quantification");
395 println!("✓ Transparency in decision thresholds");
396 println!("✓ Performance on diverse molecular scaffolds\n");
397
398 println!("Calibration status:");
399 if best_ece < 0.05 {
400 println!(" ✅ Excellent calibration (ECE < 0.05)");
401 } else if best_ece < 0.10 {
402 println!(" ✅ Good calibration (ECE < 0.10)");
403 } else if best_ece < 0.15 {
404 println!(" ⚠️ Acceptable calibration (ECE < 0.15) - consider improvement");
405 } else {
406 println!(" ❌ Poor calibration (ECE >= 0.15) - recalibration required");
407 }
408
409 println!("\nUncertainty quantification:");
410 println!(" 📊 Calibration curve available: Yes");
411 println!(" 📊 Confidence intervals: Yes (via BBQ method)");
412
413 // ========================================================================
414 // 8. Recommendations
415 // ========================================================================
416
417 println!("\n\n╔═══════════════════════════════════════════════════════╗");
418 println!("║ Recommendations for Production Deployment ║");
419 println!("╚═══════════════════════════════════════════════════════╝\n");
420
421 println!("Based on the analysis:\n");
422 println!("1. 🎯 Use {best_method} for best calibration");
423 println!("2. 📊 Monitor ECE and NLL in production");
424 println!("3. 🔄 Recalibrate when data distribution shifts");
425 println!("4. 💰 Optimize decision threshold based on cost/benefit analysis");
426 println!("5. 🔬 Consider ensemble methods for critical decisions");
427 println!("6. 📈 Track calibration degradation over time");
428 println!("7. ⚗️ Validate on diverse molecular scaffolds");
429 println!("8. 🚨 Set up alerts for calibration drift (ECE > 0.15)");
430
431 println!("\n✨ Drug discovery calibration demonstration complete! ✨\n");
432}More examples
examples/calibration_finance.rs (line 461)
335fn main() {
336 println!("\n╔══════════════════════════════════════════════════════════╗");
337 println!("║ Quantum ML Calibration for Financial Risk Prediction ║");
338 println!("║ Credit Default & Portfolio Risk Assessment ║");
339 println!("╚══════════════════════════════════════════════════════════╝\n");
340
341 // ========================================================================
342 // 1. Generate Credit Application Dataset
343 // ========================================================================
344
345 println!("📊 Generating credit application dataset...\n");
346
347 let n_train = 5000;
348 let n_cal = 1000;
349 let n_test = 2000;
350 let n_features = 15;
351
352 let mut all_applications = generate_credit_dataset(n_train + n_cal + n_test, n_features);
353
354 // Split into train, calibration, and test sets
355 let test_apps: Vec<_> = all_applications.split_off(n_train + n_cal);
356 let cal_apps: Vec<_> = all_applications.split_off(n_train);
357 let train_apps = all_applications;
358
359 println!("Dataset statistics:");
360 println!(" Training set: {} applications", train_apps.len());
361 println!(" Calibration set: {} applications", cal_apps.len());
362 println!(" Test set: {} applications", test_apps.len());
363 println!(" Features per application: {n_features}");
364
365 let train_default_rate =
366 train_apps.iter().filter(|a| a.true_default).count() as f64 / train_apps.len() as f64;
367 println!(
368 " Historical default rate: {:.2}%",
369 train_default_rate * 100.0
370 );
371
372 let total_loan_volume: f64 = test_apps.iter().map(|a| a.loan_amount).sum();
373 println!(
374 " Test portfolio size: ${:.2}M",
375 total_loan_volume / 1_000_000.0
376 );
377
378 // ========================================================================
379 // 2. Train Quantum Credit Risk Model
380 // ========================================================================
381
382 println!("\n🔬 Training quantum credit risk model...\n");
383
384 let qcrm = QuantumCreditRiskModel::new(n_features, 0.2);
385
386 // Get predictions
387 let cal_probs = qcrm.predict_batch(&cal_apps);
388 let cal_labels =
389 Array1::from_shape_fn(cal_apps.len(), |i| usize::from(cal_apps[i].true_default));
390
391 let test_probs = qcrm.predict_batch(&test_apps);
392 let test_labels =
393 Array1::from_shape_fn(test_apps.len(), |i| usize::from(test_apps[i].true_default));
394
395 println!("Model trained! Evaluating uncalibrated performance...");
396
397 let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
398 let acc = accuracy(&test_preds, &test_labels);
399 let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
400 let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
401 let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
402 let auc = auc_roc(&test_probs, &test_labels).expect("AUC failed");
403
404 println!(" Accuracy: {:.2}%", acc * 100.0);
405 println!(" Precision (class 1): {:.2}%", prec[1] * 100.0);
406 println!(" Recall (class 1): {:.2}%", rec[1] * 100.0);
407 println!(" F1 Score (class 1): {:.3}", f1[1]);
408 println!(" AUC-ROC: {auc:.3}");
409
410 // ========================================================================
411 // 3. Analyze Uncalibrated Model
412 // ========================================================================
413
414 println!("\n📉 Analyzing uncalibrated model calibration...\n");
415
416 let uncalib_ece =
417 expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
418 let uncalib_mce = maximum_calibration_error(&test_probs, &test_labels, 10).expect("MCE failed");
419 let uncalib_logloss = log_loss(&test_probs, &test_labels);
420
421 println!("Uncalibrated calibration metrics:");
422 println!(" Expected Calibration Error (ECE): {uncalib_ece:.4}");
423 println!(" Maximum Calibration Error (MCE): {uncalib_mce:.4}");
424 println!(" Log Loss: {uncalib_logloss:.4}");
425
426 if uncalib_ece > 0.10 {
427 println!(" ⚠️ High ECE - probabilities are poorly calibrated!");
428 println!(" This violates regulatory requirements for risk models.");
429 }
430
431 // ========================================================================
432 // 4. Apply Multiple Calibration Methods
433 // ========================================================================
434
435 println!("\n🔧 Applying advanced calibration methods...\n");
436
437 // Apply calibration methods
438 println!("🔧 Applying calibration methods...\n");
439
440 // Try different calibration methods
441 let mut platt = PlattScaler::new();
442 platt
443 .fit(&cal_probs, &cal_labels)
444 .expect("Platt fit failed");
445 let platt_probs = platt
446 .transform(&test_probs)
447 .expect("Platt transform failed");
448 let platt_ece = expected_calibration_error(&platt_probs, &test_labels, 10).expect("ECE failed");
449
450 let mut isotonic = IsotonicRegression::new();
451 isotonic
452 .fit(&cal_probs, &cal_labels)
453 .expect("Isotonic fit failed");
454 let isotonic_probs = isotonic
455 .transform(&test_probs)
456 .expect("Isotonic transform failed");
457 let isotonic_ece =
458 expected_calibration_error(&isotonic_probs, &test_labels, 10).expect("ECE failed");
459
460 let mut bbq = BayesianBinningQuantiles::new(10);
461 bbq.fit(&cal_probs, &cal_labels).expect("BBQ fit failed");
462 let bbq_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
463 let bbq_ece = expected_calibration_error(&bbq_probs, &test_labels, 10).expect("ECE failed");
464
465 println!("Calibration Results:");
466 println!(" Platt Scaling: ECE = {platt_ece:.4}");
467 println!(" Isotonic Regression: ECE = {isotonic_ece:.4}");
468 println!(" BBQ-10: ECE = {bbq_ece:.4}");
469
470 // Choose best method
471 let (best_method_name, best_test_probs) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
472 ("BBQ-10", bbq_probs)
473 } else if isotonic_ece < platt_ece {
474 ("Isotonic", isotonic_probs)
475 } else {
476 ("Platt", platt_probs)
477 };
478
479 println!("\n🏆 Best method: {best_method_name}\n");
480
481 let best_ece =
482 expected_calibration_error(&best_test_probs, &test_labels, 10).expect("ECE failed");
483
484 println!("Calibrated model performance:");
485 println!(
486 " ECE: {:.4} ({:.1}% improvement)",
487 best_ece,
488 (uncalib_ece - best_ece) / uncalib_ece * 100.0
489 );
490
491 // ========================================================================
492 // 5. Economic Impact Analysis
493 // ========================================================================
494
495 println!("\n\n╔═══════════════════════════════════════════════════════╗");
496 println!("║ Economic Impact of Calibration ║");
497 println!("╚═══════════════════════════════════════════════════════╝\n");
498
499 let default_loss_rate = 0.60; // Lose 60% of principal on default
500 let profit_margin = 0.08; // 8% profit on successful loans
501
502 for threshold in &[0.3, 0.5, 0.7] {
503 println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
504 println!(
505 "Decision Threshold: {:.0}% default probability",
506 threshold * 100.0
507 );
508 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
509
510 let (uncalib_value, uncalib_approved, uncalib_tp, uncalib_fp, uncalib_fn) =
511 calculate_lending_value(
512 &test_apps,
513 &test_probs,
514 *threshold,
515 default_loss_rate,
516 profit_margin,
517 );
518
519 let (calib_value, calib_approved, calib_tp, calib_fp, calib_fn) = calculate_lending_value(
520 &test_apps,
521 &best_test_probs,
522 *threshold,
523 default_loss_rate,
524 profit_margin,
525 );
526
527 println!("Uncalibrated Model:");
528 println!(" Loans approved: {}/{}", uncalib_approved, test_apps.len());
529 println!(" Correctly rejected defaults: {uncalib_tp}");
530 println!(" Missed profit opportunities: {uncalib_fp}");
531 println!(" Approved defaults (losses): {uncalib_fn}");
532 println!(
533 " Net portfolio value: ${:.2}M",
534 uncalib_value / 1_000_000.0
535 );
536
537 println!("\nCalibrated Model:");
538 println!(" Loans approved: {}/{}", calib_approved, test_apps.len());
539 println!(" Correctly rejected defaults: {calib_tp}");
540 println!(" Missed profit opportunities: {calib_fp}");
541 println!(" Approved defaults (losses): {calib_fn}");
542 println!(" Net portfolio value: ${:.2}M", calib_value / 1_000_000.0);
543
544 let value_improvement = calib_value - uncalib_value;
545 println!("\n💰 Economic Impact:");
546 if value_improvement > 0.0 {
547 println!(
548 " Additional profit: ${:.2}M ({:.1}% improvement)",
549 value_improvement / 1_000_000.0,
550 value_improvement / uncalib_value.abs() * 100.0
551 );
552 } else {
553 println!(" Value change: ${:.2}M", value_improvement / 1_000_000.0);
554 }
555
556 let default_reduction = uncalib_fn as i32 - calib_fn as i32;
557 if default_reduction > 0 {
558 println!(
559 " Defaults avoided: {} ({:.1}% reduction)",
560 default_reduction,
561 default_reduction as f64 / uncalib_fn as f64 * 100.0
562 );
563 }
564 }
565
566 // ========================================================================
567 // 6. Basel III Capital Requirements
568 // ========================================================================
569
570 demonstrate_capital_impact(&test_apps, &test_probs, &best_test_probs);
571
572 // ========================================================================
573 // 7. Stress Testing
574 // ========================================================================
575
576 println!("\n\n╔═══════════════════════════════════════════════════════╗");
577 println!("║ Regulatory Stress Testing (CCAR/DFAST) ║");
578 println!("╚═══════════════════════════════════════════════════════╝\n");
579
580 println!("Stress scenarios:");
581 println!(" 📉 Severe economic downturn (unemployment +5%)");
582 println!(" 📊 Market volatility increase (+200%)");
583 println!(" 🏦 Credit spread widening (+300 bps)\n");
584
585 // Simulate stress by increasing default probabilities
586 let stress_factor = 2.5;
587 let stressed_probs = test_probs.mapv(|p| (p * stress_factor).min(0.95));
588 let stressed_calib_probs = best_test_probs.mapv(|p| (p * stress_factor).min(0.95));
589
590 let (stress_uncalib_value, _, _, _, _) = calculate_lending_value(
591 &test_apps,
592 &stressed_probs,
593 0.5,
594 default_loss_rate,
595 profit_margin,
596 );
597
598 let (stress_calib_value, _, _, _, _) = calculate_lending_value(
599 &test_apps,
600 &stressed_calib_probs,
601 0.5,
602 default_loss_rate,
603 profit_margin,
604 );
605
606 println!("Portfolio value under stress:");
607 println!(
608 " Uncalibrated Model: ${:.2}M",
609 stress_uncalib_value / 1_000_000.0
610 );
611 println!(
612 " Calibrated Model: ${:.2}M",
613 stress_calib_value / 1_000_000.0
614 );
615
616 let stress_resilience = stress_calib_value - stress_uncalib_value;
617 if stress_resilience > 0.0 {
618 println!(
619 " ✅ Better stress resilience: +${:.2}M",
620 stress_resilience / 1_000_000.0
621 );
622 }
623
624 // ========================================================================
625 // 8. Recommendations
626 // ========================================================================
627
628 println!("\n\n╔═══════════════════════════════════════════════════════╗");
629 println!("║ Production Deployment Recommendations ║");
630 println!("╚═══════════════════════════════════════════════════════╝\n");
631
632 println!("Based on the analysis:\n");
633 println!("1. 🎯 Deploy {best_method_name} calibration method");
634 println!("2. 📊 Implement monthly recalibration schedule");
635 println!("3. 🔍 Monitor ECE and backtest predictions quarterly");
636 println!("4. 💰 Optimize decision threshold for portfolio objectives");
637 println!("5. 📈 Track calibration drift using hold-out validation set");
638 println!("6. 🏛️ Document calibration methodology for regulators");
639 println!("7. ⚖️ Conduct annual model validation review");
640 println!("8. 🚨 Set up alerts for ECE > 0.10 (regulatory threshold)");
641 println!("9. 📉 Perform stress testing with calibrated probabilities");
642 println!("10. 💼 Integrate with capital allocation framework");
643
644 println!("\n\n╔═══════════════════════════════════════════════════════╗");
645 println!("║ Regulatory Compliance Checklist ║");
646 println!("╚═══════════════════════════════════════════════════════╝\n");
647
648 println!("✅ Model Validation:");
649 println!(" ✓ Calibration metrics documented (ECE, NLL, Brier)");
650 println!(" ✓ Backtesting performed on hold-out set");
651 println!(" ✓ Stress testing under adverse scenarios");
652 println!(" ✓ Uncertainty quantification available\n");
653
654 println!("✅ Basel III Compliance:");
655 println!(" ✓ Expected Loss calculated with calibrated probabilities");
656 println!(" ✓ Risk-weighted assets computed correctly");
657 println!(" ✓ Capital requirements meet regulatory minimums");
658 println!(" ✓ Model approved for internal ratings-based approach\n");
659
660 println!("✅ Ongoing Monitoring:");
661 println!(" ✓ Quarterly performance reviews scheduled");
662 println!(" ✓ Calibration drift detection in place");
663 println!(" ✓ Model governance framework established");
664 println!(" ✓ Audit trail for all predictions maintained");
665
666 println!("\n✨ Financial risk calibration demonstration complete! ✨\n");
667}Sourcepub fn transform(&self, probabilities: &Array1<f64>) -> Result<Array1<f64>>
pub fn transform(&self, probabilities: &Array1<f64>) -> Result<Array1<f64>>
Transform probabilities to calibrated probabilities using BBQ
Examples found in repository?
examples/calibration_drug_discovery.rs (line 343)
213fn main() {
214 println!("\n╔══════════════════════════════════════════════════════════╗");
215 println!("║ Quantum ML Calibration for Drug Discovery ║");
216 println!("║ Molecular Binding Affinity Prediction ║");
217 println!("╚══════════════════════════════════════════════════════════╝\n");
218
219 // ========================================================================
220 // 1. Generate Drug Discovery Dataset
221 // ========================================================================
222
223 println!("📊 Generating drug discovery dataset...\n");
224
225 let n_train = 1000;
226 let n_cal = 300; // Calibration set
227 let n_test = 500; // Test set
228 let n_features = 20;
229
230 let mut all_molecules = generate_drug_dataset(n_train + n_cal + n_test, n_features);
231
232 // Split into train, calibration, and test sets
233 let test_molecules: Vec<_> = all_molecules.split_off(n_train + n_cal);
234 let cal_molecules: Vec<_> = all_molecules.split_off(n_train);
235 let train_molecules = all_molecules;
236
237 println!("Dataset statistics:");
238 println!(" Training set: {} molecules", train_molecules.len());
239 println!(" Calibration set: {} molecules", cal_molecules.len());
240 println!(" Test set: {} molecules", test_molecules.len());
241 println!(" Features per molecule: {n_features}");
242
243 let train_positive = train_molecules.iter().filter(|m| m.true_binding).count();
244 println!(
245 " Training set binding ratio: {:.1}%",
246 train_positive as f64 / train_molecules.len() as f64 * 100.0
247 );
248
249 // ========================================================================
250 // 2. Train Quantum Neural Network (Simplified)
251 // ========================================================================
252
253 println!("\n🔬 Training quantum molecular predictor...\n");
254
255 let qnn = QuantumMolecularPredictor::new(n_features, 0.3);
256
257 // Get predictions on calibration set
258 let cal_probs = qnn.predict_batch(&cal_molecules);
259 let cal_labels = Array1::from_shape_fn(cal_molecules.len(), |i| {
260 usize::from(cal_molecules[i].true_binding)
261 });
262
263 // Get predictions on test set
264 let test_probs = qnn.predict_batch(&test_molecules);
265 let test_labels = Array1::from_shape_fn(test_molecules.len(), |i| {
266 usize::from(test_molecules[i].true_binding)
267 });
268
269 println!("Model trained! Evaluating uncalibrated performance...");
270
271 let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
272 let acc = accuracy(&test_preds, &test_labels);
273 let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
274 let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
275 let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
276
277 println!(" Accuracy: {:.2}%", acc * 100.0);
278 println!(" Precision (class 1): {:.2}%", prec[1] * 100.0);
279 println!(" Recall (class 1): {:.2}%", rec[1] * 100.0);
280 println!(" F1 Score (class 1): {:.3}", f1[1]);
281
282 // ========================================================================
283 // 3. Analyze Uncalibrated Model
284 // ========================================================================
285
286 println!("\n📉 Analyzing uncalibrated model calibration...\n");
287
288 let uncalib_ece =
289 expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
290
291 println!("Uncalibrated metrics:");
292 println!(" Expected Calibration Error (ECE): {uncalib_ece:.4}");
293
294 if uncalib_ece > 0.1 {
295 println!(" ⚠️ High ECE indicates poor calibration!");
296 }
297
298 // ========================================================================
299 // 4. Apply Calibration Methods
300 // ========================================================================
301
302 println!("\n🔧 Applying calibration methods...\n");
303
304 // Method 1: Platt Scaling
305 println!("1️⃣ Platt Scaling (parametric, fast)");
306 let mut platt = PlattScaler::new();
307 platt
308 .fit(&cal_probs, &cal_labels)
309 .expect("Platt fitting failed");
310 let platt_test_probs = platt
311 .transform(&test_probs)
312 .expect("Platt transform failed");
313 let platt_ece =
314 expected_calibration_error(&platt_test_probs, &test_labels, 10).expect("ECE failed");
315 println!(
316 " ECE after Platt: {:.4} ({:.1}% improvement)",
317 platt_ece,
318 (uncalib_ece - platt_ece) / uncalib_ece * 100.0
319 );
320
321 // Method 2: Isotonic Regression
322 println!("\n2️⃣ Isotonic Regression (non-parametric, flexible)");
323 let mut isotonic = IsotonicRegression::new();
324 isotonic
325 .fit(&cal_probs, &cal_labels)
326 .expect("Isotonic fitting failed");
327 let isotonic_test_probs = isotonic
328 .transform(&test_probs)
329 .expect("Isotonic transform failed");
330 let isotonic_ece =
331 expected_calibration_error(&isotonic_test_probs, &test_labels, 10).expect("ECE failed");
332 println!(
333 " ECE after Isotonic: {:.4} ({:.1}% improvement)",
334 isotonic_ece,
335 (uncalib_ece - isotonic_ece) / uncalib_ece * 100.0
336 );
337
338 // Method 3: Bayesian Binning into Quantiles (BBQ)
339 println!("\n3️⃣ Bayesian Binning into Quantiles (BBQ-10)");
340 let mut bbq = BayesianBinningQuantiles::new(10);
341 bbq.fit(&cal_probs, &cal_labels)
342 .expect("BBQ fitting failed");
343 let bbq_test_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
344 let bbq_ece =
345 expected_calibration_error(&bbq_test_probs, &test_labels, 10).expect("ECE failed");
346 println!(
347 " ECE after BBQ: {:.4} ({:.1}% improvement)",
348 bbq_ece,
349 (uncalib_ece - bbq_ece) / uncalib_ece * 100.0
350 );
351
352 // ========================================================================
353 // 5. Compare All Methods
354 // ========================================================================
355
356 println!("\n📊 Comprehensive method comparison...\n");
357
358 println!("Method Comparison (ECE on test set):");
359 println!(" Uncalibrated: {uncalib_ece:.4}");
360 println!(" Platt Scaling: {platt_ece:.4}");
361 println!(" Isotonic Regr.: {isotonic_ece:.4}");
362 println!(" BBQ-10: {bbq_ece:.4}");
363
364 // ========================================================================
365 // 6. Decision Impact Analysis
366 // ========================================================================
367
368 // Choose best method based on ECE
369 let (best_method, best_probs, best_ece) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
370 ("BBQ-10", bbq_test_probs, bbq_ece)
371 } else if isotonic_ece < platt_ece {
372 ("Isotonic Regression", isotonic_test_probs, isotonic_ece)
373 } else {
374 ("Platt Scaling", platt_test_probs, platt_ece)
375 };
376
377 println!("\n🏆 Best calibration method: {best_method}");
378
379 // Demonstrate impact on different decision thresholds
380 for threshold in &[0.3, 0.5, 0.7, 0.9] {
381 demonstrate_decision_impact(&test_molecules, &test_probs, &best_probs, *threshold);
382 }
383
384 // ========================================================================
385 // 7. Regulatory Compliance Analysis
386 // ========================================================================
387
388 println!("\n\n╔═══════════════════════════════════════════════════════╗");
389 println!("║ Regulatory Compliance Analysis (FDA Guidelines) ║");
390 println!("╚═══════════════════════════════════════════════════════╝\n");
391
392 println!("FDA requires ML/AI models to provide:\n");
393 println!("✓ Well-calibrated probability estimates");
394 println!("✓ Uncertainty quantification");
395 println!("✓ Transparency in decision thresholds");
396 println!("✓ Performance on diverse molecular scaffolds\n");
397
398 println!("Calibration status:");
399 if best_ece < 0.05 {
400 println!(" ✅ Excellent calibration (ECE < 0.05)");
401 } else if best_ece < 0.10 {
402 println!(" ✅ Good calibration (ECE < 0.10)");
403 } else if best_ece < 0.15 {
404 println!(" ⚠️ Acceptable calibration (ECE < 0.15) - consider improvement");
405 } else {
406 println!(" ❌ Poor calibration (ECE >= 0.15) - recalibration required");
407 }
408
409 println!("\nUncertainty quantification:");
410 println!(" 📊 Calibration curve available: Yes");
411 println!(" 📊 Confidence intervals: Yes (via BBQ method)");
412
413 // ========================================================================
414 // 8. Recommendations
415 // ========================================================================
416
417 println!("\n\n╔═══════════════════════════════════════════════════════╗");
418 println!("║ Recommendations for Production Deployment ║");
419 println!("╚═══════════════════════════════════════════════════════╝\n");
420
421 println!("Based on the analysis:\n");
422 println!("1. 🎯 Use {best_method} for best calibration");
423 println!("2. 📊 Monitor ECE and NLL in production");
424 println!("3. 🔄 Recalibrate when data distribution shifts");
425 println!("4. 💰 Optimize decision threshold based on cost/benefit analysis");
426 println!("5. 🔬 Consider ensemble methods for critical decisions");
427 println!("6. 📈 Track calibration degradation over time");
428 println!("7. ⚗️ Validate on diverse molecular scaffolds");
429 println!("8. 🚨 Set up alerts for calibration drift (ECE > 0.15)");
430
431 println!("\n✨ Drug discovery calibration demonstration complete! ✨\n");
432}More examples
examples/calibration_finance.rs (line 462)
335fn main() {
336 println!("\n╔══════════════════════════════════════════════════════════╗");
337 println!("║ Quantum ML Calibration for Financial Risk Prediction ║");
338 println!("║ Credit Default & Portfolio Risk Assessment ║");
339 println!("╚══════════════════════════════════════════════════════════╝\n");
340
341 // ========================================================================
342 // 1. Generate Credit Application Dataset
343 // ========================================================================
344
345 println!("📊 Generating credit application dataset...\n");
346
347 let n_train = 5000;
348 let n_cal = 1000;
349 let n_test = 2000;
350 let n_features = 15;
351
352 let mut all_applications = generate_credit_dataset(n_train + n_cal + n_test, n_features);
353
354 // Split into train, calibration, and test sets
355 let test_apps: Vec<_> = all_applications.split_off(n_train + n_cal);
356 let cal_apps: Vec<_> = all_applications.split_off(n_train);
357 let train_apps = all_applications;
358
359 println!("Dataset statistics:");
360 println!(" Training set: {} applications", train_apps.len());
361 println!(" Calibration set: {} applications", cal_apps.len());
362 println!(" Test set: {} applications", test_apps.len());
363 println!(" Features per application: {n_features}");
364
365 let train_default_rate =
366 train_apps.iter().filter(|a| a.true_default).count() as f64 / train_apps.len() as f64;
367 println!(
368 " Historical default rate: {:.2}%",
369 train_default_rate * 100.0
370 );
371
372 let total_loan_volume: f64 = test_apps.iter().map(|a| a.loan_amount).sum();
373 println!(
374 " Test portfolio size: ${:.2}M",
375 total_loan_volume / 1_000_000.0
376 );
377
378 // ========================================================================
379 // 2. Train Quantum Credit Risk Model
380 // ========================================================================
381
382 println!("\n🔬 Training quantum credit risk model...\n");
383
384 let qcrm = QuantumCreditRiskModel::new(n_features, 0.2);
385
386 // Get predictions
387 let cal_probs = qcrm.predict_batch(&cal_apps);
388 let cal_labels =
389 Array1::from_shape_fn(cal_apps.len(), |i| usize::from(cal_apps[i].true_default));
390
391 let test_probs = qcrm.predict_batch(&test_apps);
392 let test_labels =
393 Array1::from_shape_fn(test_apps.len(), |i| usize::from(test_apps[i].true_default));
394
395 println!("Model trained! Evaluating uncalibrated performance...");
396
397 let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
398 let acc = accuracy(&test_preds, &test_labels);
399 let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
400 let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
401 let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
402 let auc = auc_roc(&test_probs, &test_labels).expect("AUC failed");
403
404 println!(" Accuracy: {:.2}%", acc * 100.0);
405 println!(" Precision (class 1): {:.2}%", prec[1] * 100.0);
406 println!(" Recall (class 1): {:.2}%", rec[1] * 100.0);
407 println!(" F1 Score (class 1): {:.3}", f1[1]);
408 println!(" AUC-ROC: {auc:.3}");
409
410 // ========================================================================
411 // 3. Analyze Uncalibrated Model
412 // ========================================================================
413
414 println!("\n📉 Analyzing uncalibrated model calibration...\n");
415
416 let uncalib_ece =
417 expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
418 let uncalib_mce = maximum_calibration_error(&test_probs, &test_labels, 10).expect("MCE failed");
419 let uncalib_logloss = log_loss(&test_probs, &test_labels);
420
421 println!("Uncalibrated calibration metrics:");
422 println!(" Expected Calibration Error (ECE): {uncalib_ece:.4}");
423 println!(" Maximum Calibration Error (MCE): {uncalib_mce:.4}");
424 println!(" Log Loss: {uncalib_logloss:.4}");
425
426 if uncalib_ece > 0.10 {
427 println!(" ⚠️ High ECE - probabilities are poorly calibrated!");
428 println!(" This violates regulatory requirements for risk models.");
429 }
430
431 // ========================================================================
432 // 4. Apply Multiple Calibration Methods
433 // ========================================================================
434
435 println!("\n🔧 Applying advanced calibration methods...\n");
436
437 // Apply calibration methods
438 println!("🔧 Applying calibration methods...\n");
439
440 // Try different calibration methods
441 let mut platt = PlattScaler::new();
442 platt
443 .fit(&cal_probs, &cal_labels)
444 .expect("Platt fit failed");
445 let platt_probs = platt
446 .transform(&test_probs)
447 .expect("Platt transform failed");
448 let platt_ece = expected_calibration_error(&platt_probs, &test_labels, 10).expect("ECE failed");
449
450 let mut isotonic = IsotonicRegression::new();
451 isotonic
452 .fit(&cal_probs, &cal_labels)
453 .expect("Isotonic fit failed");
454 let isotonic_probs = isotonic
455 .transform(&test_probs)
456 .expect("Isotonic transform failed");
457 let isotonic_ece =
458 expected_calibration_error(&isotonic_probs, &test_labels, 10).expect("ECE failed");
459
460 let mut bbq = BayesianBinningQuantiles::new(10);
461 bbq.fit(&cal_probs, &cal_labels).expect("BBQ fit failed");
462 let bbq_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
463 let bbq_ece = expected_calibration_error(&bbq_probs, &test_labels, 10).expect("ECE failed");
464
465 println!("Calibration Results:");
466 println!(" Platt Scaling: ECE = {platt_ece:.4}");
467 println!(" Isotonic Regression: ECE = {isotonic_ece:.4}");
468 println!(" BBQ-10: ECE = {bbq_ece:.4}");
469
470 // Choose best method
471 let (best_method_name, best_test_probs) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
472 ("BBQ-10", bbq_probs)
473 } else if isotonic_ece < platt_ece {
474 ("Isotonic", isotonic_probs)
475 } else {
476 ("Platt", platt_probs)
477 };
478
479 println!("\n🏆 Best method: {best_method_name}\n");
480
481 let best_ece =
482 expected_calibration_error(&best_test_probs, &test_labels, 10).expect("ECE failed");
483
484 println!("Calibrated model performance:");
485 println!(
486 " ECE: {:.4} ({:.1}% improvement)",
487 best_ece,
488 (uncalib_ece - best_ece) / uncalib_ece * 100.0
489 );
490
491 // ========================================================================
492 // 5. Economic Impact Analysis
493 // ========================================================================
494
495 println!("\n\n╔═══════════════════════════════════════════════════════╗");
496 println!("║ Economic Impact of Calibration ║");
497 println!("╚═══════════════════════════════════════════════════════╝\n");
498
499 let default_loss_rate = 0.60; // Lose 60% of principal on default
500 let profit_margin = 0.08; // 8% profit on successful loans
501
502 for threshold in &[0.3, 0.5, 0.7] {
503 println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
504 println!(
505 "Decision Threshold: {:.0}% default probability",
506 threshold * 100.0
507 );
508 println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
509
510 let (uncalib_value, uncalib_approved, uncalib_tp, uncalib_fp, uncalib_fn) =
511 calculate_lending_value(
512 &test_apps,
513 &test_probs,
514 *threshold,
515 default_loss_rate,
516 profit_margin,
517 );
518
519 let (calib_value, calib_approved, calib_tp, calib_fp, calib_fn) = calculate_lending_value(
520 &test_apps,
521 &best_test_probs,
522 *threshold,
523 default_loss_rate,
524 profit_margin,
525 );
526
527 println!("Uncalibrated Model:");
528 println!(" Loans approved: {}/{}", uncalib_approved, test_apps.len());
529 println!(" Correctly rejected defaults: {uncalib_tp}");
530 println!(" Missed profit opportunities: {uncalib_fp}");
531 println!(" Approved defaults (losses): {uncalib_fn}");
532 println!(
533 " Net portfolio value: ${:.2}M",
534 uncalib_value / 1_000_000.0
535 );
536
537 println!("\nCalibrated Model:");
538 println!(" Loans approved: {}/{}", calib_approved, test_apps.len());
539 println!(" Correctly rejected defaults: {calib_tp}");
540 println!(" Missed profit opportunities: {calib_fp}");
541 println!(" Approved defaults (losses): {calib_fn}");
542 println!(" Net portfolio value: ${:.2}M", calib_value / 1_000_000.0);
543
544 let value_improvement = calib_value - uncalib_value;
545 println!("\n💰 Economic Impact:");
546 if value_improvement > 0.0 {
547 println!(
548 " Additional profit: ${:.2}M ({:.1}% improvement)",
549 value_improvement / 1_000_000.0,
550 value_improvement / uncalib_value.abs() * 100.0
551 );
552 } else {
553 println!(" Value change: ${:.2}M", value_improvement / 1_000_000.0);
554 }
555
556 let default_reduction = uncalib_fn as i32 - calib_fn as i32;
557 if default_reduction > 0 {
558 println!(
559 " Defaults avoided: {} ({:.1}% reduction)",
560 default_reduction,
561 default_reduction as f64 / uncalib_fn as f64 * 100.0
562 );
563 }
564 }
565
566 // ========================================================================
567 // 6. Basel III Capital Requirements
568 // ========================================================================
569
570 demonstrate_capital_impact(&test_apps, &test_probs, &best_test_probs);
571
572 // ========================================================================
573 // 7. Stress Testing
574 // ========================================================================
575
576 println!("\n\n╔═══════════════════════════════════════════════════════╗");
577 println!("║ Regulatory Stress Testing (CCAR/DFAST) ║");
578 println!("╚═══════════════════════════════════════════════════════╝\n");
579
580 println!("Stress scenarios:");
581 println!(" 📉 Severe economic downturn (unemployment +5%)");
582 println!(" 📊 Market volatility increase (+200%)");
583 println!(" 🏦 Credit spread widening (+300 bps)\n");
584
585 // Simulate stress by increasing default probabilities
586 let stress_factor = 2.5;
587 let stressed_probs = test_probs.mapv(|p| (p * stress_factor).min(0.95));
588 let stressed_calib_probs = best_test_probs.mapv(|p| (p * stress_factor).min(0.95));
589
590 let (stress_uncalib_value, _, _, _, _) = calculate_lending_value(
591 &test_apps,
592 &stressed_probs,
593 0.5,
594 default_loss_rate,
595 profit_margin,
596 );
597
598 let (stress_calib_value, _, _, _, _) = calculate_lending_value(
599 &test_apps,
600 &stressed_calib_probs,
601 0.5,
602 default_loss_rate,
603 profit_margin,
604 );
605
606 println!("Portfolio value under stress:");
607 println!(
608 " Uncalibrated Model: ${:.2}M",
609 stress_uncalib_value / 1_000_000.0
610 );
611 println!(
612 " Calibrated Model: ${:.2}M",
613 stress_calib_value / 1_000_000.0
614 );
615
616 let stress_resilience = stress_calib_value - stress_uncalib_value;
617 if stress_resilience > 0.0 {
618 println!(
619 " ✅ Better stress resilience: +${:.2}M",
620 stress_resilience / 1_000_000.0
621 );
622 }
623
624 // ========================================================================
625 // 8. Recommendations
626 // ========================================================================
627
628 println!("\n\n╔═══════════════════════════════════════════════════════╗");
629 println!("║ Production Deployment Recommendations ║");
630 println!("╚═══════════════════════════════════════════════════════╝\n");
631
632 println!("Based on the analysis:\n");
633 println!("1. 🎯 Deploy {best_method_name} calibration method");
634 println!("2. 📊 Implement monthly recalibration schedule");
635 println!("3. 🔍 Monitor ECE and backtest predictions quarterly");
636 println!("4. 💰 Optimize decision threshold for portfolio objectives");
637 println!("5. 📈 Track calibration drift using hold-out validation set");
638 println!("6. 🏛️ Document calibration methodology for regulators");
639 println!("7. ⚖️ Conduct annual model validation review");
640 println!("8. 🚨 Set up alerts for ECE > 0.10 (regulatory threshold)");
641 println!("9. 📉 Perform stress testing with calibrated probabilities");
642 println!("10. 💼 Integrate with capital allocation framework");
643
644 println!("\n\n╔═══════════════════════════════════════════════════════╗");
645 println!("║ Regulatory Compliance Checklist ║");
646 println!("╚═══════════════════════════════════════════════════════╝\n");
647
648 println!("✅ Model Validation:");
649 println!(" ✓ Calibration metrics documented (ECE, NLL, Brier)");
650 println!(" ✓ Backtesting performed on hold-out set");
651 println!(" ✓ Stress testing under adverse scenarios");
652 println!(" ✓ Uncertainty quantification available\n");
653
654 println!("✅ Basel III Compliance:");
655 println!(" ✓ Expected Loss calculated with calibrated probabilities");
656 println!(" ✓ Risk-weighted assets computed correctly");
657 println!(" ✓ Capital requirements meet regulatory minimums");
658 println!(" ✓ Model approved for internal ratings-based approach\n");
659
660 println!("✅ Ongoing Monitoring:");
661 println!(" ✓ Quarterly performance reviews scheduled");
662 println!(" ✓ Calibration drift detection in place");
663 println!(" ✓ Model governance framework established");
664 println!(" ✓ Audit trail for all predictions maintained");
665
666 println!("\n✨ Financial risk calibration demonstration complete! ✨\n");
667}Sourcepub fn fit_transform(
&mut self,
probabilities: &Array1<f64>,
labels: &Array1<usize>,
) -> Result<Array1<f64>>
pub fn fit_transform( &mut self, probabilities: &Array1<f64>, labels: &Array1<usize>, ) -> Result<Array1<f64>>
Fit and transform in one step
Trait Implementations§
Source§impl Clone for BayesianBinningQuantiles
impl Clone for BayesianBinningQuantiles
Source§fn clone(&self) -> BayesianBinningQuantiles
fn clone(&self) -> BayesianBinningQuantiles
Returns a duplicate of the value. Read more
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
Performs copy-assignment from
source. Read moreSource§impl Debug for BayesianBinningQuantiles
impl Debug for BayesianBinningQuantiles
Auto Trait Implementations§
impl Freeze for BayesianBinningQuantiles
impl RefUnwindSafe for BayesianBinningQuantiles
impl Send for BayesianBinningQuantiles
impl Sync for BayesianBinningQuantiles
impl Unpin for BayesianBinningQuantiles
impl UnwindSafe for BayesianBinningQuantiles
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
Converts
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
Converts
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
The inverse inclusion map: attempts to construct
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
Checks if
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
Use with care! Same as
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
The inclusion map: converts
self to the equivalent element of its superset.