|
| 1 | +/** |
| 2 | + * Monte Carlo Dropout for Uncertainty Estimation |
| 3 | + * |
| 4 | + * Adds MC Dropout to the routing engine to estimate uncertainty |
| 5 | + * and make more reliable routing decisions. |
| 6 | + * |
| 7 | + * Key concepts: |
| 8 | + * - Run N stochastic forward passes with dropout enabled |
| 9 | + * - Compute variance across passes as uncertainty signal |
| 10 | + * - Use uncertainty to trigger fallback or ensemble mode |
| 11 | + */ |
| 12 | + |
| 13 | +import { MIN_CONFIDENCE } from '../routing/jev/jevRouter'; |
| 14 | + |
| 15 | +export interface MCResult { |
| 16 | + mean: number; |
| 17 | + variance: number; |
| 18 | + uncertainty: number; |
| 19 | + predictions: number[]; |
| 20 | + isUncertain: boolean; |
| 21 | +} |
| 22 | + |
| 23 | +export interface DropoutConfig { |
| 24 | + dropoutRate: number; |
| 25 | + nSamples: number; |
| 26 | + uncertaintyThreshold: number; |
| 27 | +} |
| 28 | + |
| 29 | +/** |
| 30 | + * Monte Carlo Dropout Router. |
| 31 | + * |
| 32 | + * Implements MC Dropout for uncertainty-aware routing decisions. |
| 33 | + * During inference, randomly mask a subset of weights and run |
| 34 | + * multiple stochastic forward passes. The variance across passes |
| 35 | + * is used as an uncertainty estimate. |
| 36 | + */ |
| 37 | +export class MCDropoutRouter { |
| 38 | + private dropoutRate: number; |
| 39 | + private nSamples: number; |
| 40 | + private uncertaintyThreshold: number; |
| 41 | + |
| 42 | + constructor(config?: Partial<DropoutConfig>) { |
| 43 | + this.dropoutRate = config?.dropoutRate ?? 0.1; |
| 44 | + this.nSamples = config?.nSamples ?? 10; |
| 45 | + this.uncertaintyThreshold = config?.uncertaintyThreshold ?? 0.2; |
| 46 | + } |
| 47 | + |
| 48 | + /** |
| 49 | + * Run MC Dropout for uncertainty estimation. |
| 50 | + * |
| 51 | + * @param prompt - The task prompt |
| 52 | + * @param weights - Base attention weights |
| 53 | + * @returns MCResult with mean, variance, and uncertainty |
| 54 | + */ |
| 55 | + async routeWithUncertainty( |
| 56 | + prompt: string, |
| 57 | + weights: Record<string, number> |
| 58 | + ): Promise<MCResult> { |
| 59 | + const predictions: number[] = []; |
| 60 | + |
| 61 | + for (let i = 0; i < this.nSamples; i++) { |
| 62 | + const maskedWeights = this.applyDropout(weights); |
| 63 | + const logits = this.computeLogits(prompt, maskedWeights); |
| 64 | + const probs = this.softmax(logits); |
| 65 | + predictions.push(Math.max(...probs)); |
| 66 | + } |
| 67 | + |
| 68 | + const mean = this.mean(predictions); |
| 69 | + const variance = this.variance(predictions); |
| 70 | + const uncertainty = Math.sqrt(variance); |
| 71 | + |
| 72 | + return { |
| 73 | + mean, |
| 74 | + variance, |
| 75 | + uncertainty, |
| 76 | + predictions, |
| 77 | + isUncertain: uncertainty > this.uncertaintyThreshold, |
| 78 | + }; |
| 79 | + } |
| 80 | + |
| 81 | + /** |
| 82 | + * Apply dropout mask to weights. |
| 83 | + * |
| 84 | + * During inference, randomly mask a fraction of weights with |
| 85 | + * probability p, then scale by 1/(1-p) to maintain expected value. |
| 86 | + */ |
| 87 | + private applyDropout(weights: Record<string, number>): Record<string, number> { |
| 88 | + const masked: Record<string, number> = {}; |
| 89 | + |
| 90 | + for (const [key, value] of Object.entries(weights)) { |
| 91 | + if (Math.random() < this.dropoutRate) { |
| 92 | + masked[key] = 0; |
| 93 | + } else { |
| 94 | + masked[key] = value / (1 - this.dropoutRate); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + return masked; |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * Compute logits from prompt and masked weights. |
| 103 | + * |
| 104 | + * In production, this would call the actual OptionAttention engine. |
| 105 | + * For now, returns mock logits based on weights. |
| 106 | + */ |
| 107 | + private computeLogits( |
| 108 | + prompt: string, |
| 109 | + weights: Record<string, number> |
| 110 | + ): number[] { |
| 111 | + // Placeholder: integrate with optionAttention.ts in production |
| 112 | + return Object.keys(weights).map((key) => weights[key] || 0); |
| 113 | + } |
| 114 | + |
| 115 | + /** |
| 116 | + * Convert logits to probability distribution using softmax. |
| 117 | + */ |
| 118 | + private softmax(logits: number[]): number[] { |
| 119 | + const maxLogit = Math.max(...logits); |
| 120 | + const exps = logits.map((x) => Math.exp(x - maxLogit)); |
| 121 | + const sum = exps.reduce((a, b) => a + b, 0); |
| 122 | + return exps.map((x) => x / sum); |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Compute mean of array. |
| 127 | + */ |
| 128 | + private mean(values: number[]): number { |
| 129 | + if (values.length === 0) return 0; |
| 130 | + return values.reduce((a, b) => a + b, 0) / values.length; |
| 131 | + } |
| 132 | + |
| 133 | + /** |
| 134 | + * Compute variance of array. |
| 135 | + */ |
| 136 | + private variance(values: number[]): number { |
| 137 | + if (values.length === 0) return 0; |
| 138 | + const m = this.mean(values); |
| 139 | + return values.reduce((sum, v) => sum + Math.pow(v - m, 2), 0) / values.length; |
| 140 | + } |
| 141 | + |
| 142 | + /** |
| 143 | + * Determine if routing decision is uncertain. |
| 144 | + */ |
| 145 | + isRoutingUncertain(uncertainty: number): boolean { |
| 146 | + return uncertainty > this.uncertaintyThreshold; |
| 147 | + } |
| 148 | + |
| 149 | + /** |
| 150 | + * Get confidence adjusted by uncertainty. |
| 151 | + */ |
| 152 | + getAdjustedConfidence(meanConfidence: number, uncertainty: number): number { |
| 153 | + return Math.max(0, meanConfidence - uncertainty); |
| 154 | + } |
| 155 | +} |
| 156 | + |
| 157 | +export default MCDropoutRouter; |
0 commit comments