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