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