@@ -447,6 +447,14 @@ internal void SetUncertaintyCalibrationArtifacts(UncertaintyCalibrationArtifacts
447447 TemperatureScalingTemperature = artifacts . TemperatureScalingTemperature ;
448448 }
449449
450+ internal void SetDeepEnsembleModels ( List < IFullModel < T , TInput , TOutput > > models )
451+ {
452+ _deepEnsembleModels = models ?? throw new ArgumentNullException ( nameof ( models ) ) ;
453+ }
454+
455+ [ JsonProperty ]
456+ private List < IFullModel < T , TInput , TOutput > > ? _deepEnsembleModels ;
457+
450458 /// <summary>
451459 /// Initializes a new instance of the PredictionModelResult class with default values.
452460 /// </summary>
@@ -562,7 +570,7 @@ public TOutput Predict(TInput newData)
562570 /// </summary>
563571 /// <param name="newData">Input data to predict.</param>
564572 /// <param name="numSamples">Optional number of stochastic samples to draw (overrides configured defaults).</param>
565- /// <returns>A tuple containing the mean prediction and an uncertainty estimate (variance) .</returns>
573+ /// <returns>An uncertainty-augmented prediction result .</returns>
566574 /// <remarks>
567575 /// <para>
568576 /// When uncertainty quantification is enabled, this method performs multiple stochastic forward passes and
@@ -610,6 +618,16 @@ public UncertaintyPredictionResult<T, TOutput> PredictWithUncertainty(TInput new
610618 return PredictWithConformal ( newData , uq , method ) ;
611619 }
612620
621+ if ( method == UncertaintyQuantificationMethod . DeepEnsemble )
622+ {
623+ return PredictWithDeepEnsemble ( newData , uq , method ) ;
624+ }
625+
626+ if ( method == UncertaintyQuantificationMethod . BayesianNeuralNetwork )
627+ {
628+ return PredictWithBayesianNeuralNetwork ( newData , uq , method ) ;
629+ }
630+
613631 var effectiveSamples = numSamples ?? uq . NumSamples ;
614632 if ( effectiveSamples < 1 )
615633 {
@@ -637,7 +655,9 @@ public UncertaintyPredictionResult<T, TOutput> PredictWithUncertainty(TInput new
637655 {
638656 var ( meanTensor , varianceTensor , predictiveEntropy , mutualInformation ) = ComputeMonteCarloMomentsAndMetrics (
639657 normalizedNewData ,
640- effectiveSamples ) ;
658+ effectiveSamples ,
659+ mcDropoutLayers ,
660+ uq . RandomSeed ) ;
641661
642662 var meanOutput = ConvertFromTensor ( meanTensor ) ;
643663 var denormalizedMean = NormalizationInfo . Normalizer . Denormalize ( meanOutput , NormalizationInfo . YParams ) ;
@@ -713,6 +733,162 @@ private UncertaintyPredictionResult<T, TOutput> PredictWithConformal(
713733 classificationSet : classificationSet ) ;
714734 }
715735
736+ private UncertaintyPredictionResult < T , TOutput > PredictWithDeepEnsemble (
737+ TInput newData ,
738+ UncertaintyQuantificationOptions uq ,
739+ UncertaintyQuantificationMethod method )
740+ {
741+ if ( _deepEnsembleModels is not { Count : > 0 } )
742+ {
743+ var deterministic = Predict ( newData ) ;
744+ var fallbackMetrics = CreateDefaultUncertaintyMetrics ( deterministic , mutualInformation : null ) ;
745+ return new UncertaintyPredictionResult < T , TOutput > (
746+ methodUsed : method ,
747+ prediction : deterministic ,
748+ variance : CreateZeroLike ( deterministic ) ,
749+ metrics : fallbackMetrics ) ;
750+ }
751+
752+ var ( normalizedNewData , _) = NormalizationInfo . Normalizer ! . NormalizeInput ( newData ) ;
753+
754+ var numOps = MathHelper . GetNumericOperations < T > ( ) ;
755+ var samples = new List < Tensor < T > > ( _deepEnsembleModels . Count ) ;
756+
757+ for ( int i = 0 ; i < _deepEnsembleModels . Count ; i ++ )
758+ {
759+ var normalizedPrediction = _deepEnsembleModels [ i ] . Predict ( normalizedNewData ) ;
760+ samples . Add ( ConversionsHelper . ConvertToTensor < T > ( normalizedPrediction ! ) . Clone ( ) ) ;
761+ }
762+
763+ var first = samples [ 0 ] ;
764+ var firstVector = first . ToVector ( ) ;
765+ var ( treatAsProbabilities , batch , classes ) = InferProbabilityDistributionLayout ( first , firstVector ) ;
766+
767+ if ( treatAsProbabilities && classes > 1 && HasTemperatureScaling )
768+ {
769+ for ( int i = 0 ; i < samples . Count ; i ++ )
770+ {
771+ samples [ i ] = ApplyTemperatureScalingToProbabilityTensor ( samples [ i ] , TemperatureScalingTemperature , batch , classes ) ;
772+ }
773+ }
774+
775+ var ( meanTensor , varianceTensor ) = ComputeMeanAndVariance ( samples ) ;
776+
777+ var meanOutput = ConvertFromTensor ( meanTensor ) ;
778+ var denormalizedMean = NormalizationInfo . Normalizer ! . Denormalize ( meanOutput , NormalizationInfo . YParams ) ;
779+
780+ if ( uq . DenormalizeUncertainty )
781+ {
782+ varianceTensor = DenormalizeVarianceIfSupported ( varianceTensor , NormalizationInfo . YParams ) ;
783+ }
784+
785+ var varianceOutput = ConvertFromTensor ( varianceTensor ) ;
786+
787+ var predictiveEntropy = CreateZeroVectorTensor ( batch ) ;
788+ var mutualInformation = CreateZeroVectorTensor ( batch ) ;
789+
790+ if ( treatAsProbabilities && classes > 1 )
791+ {
792+ var expectedEntropySum = new Vector < T > ( batch ) ;
793+ foreach ( var sample in samples )
794+ {
795+ var sampleEntropy = ComputePerSampleEntropy ( sample . ToVector ( ) , batch , classes ) ;
796+ for ( int b = 0 ; b < batch ; b ++ )
797+ {
798+ expectedEntropySum [ b ] = numOps . Add ( expectedEntropySum [ b ] , sampleEntropy [ b ] ) ;
799+ }
800+ }
801+
802+ var meanVector = meanTensor . ToVector ( ) ;
803+ var predictiveEntropyVec = ComputePerSampleEntropy ( meanVector , batch , classes ) ;
804+ var expectedEntropyVec = new Vector < T > ( batch ) ;
805+ for ( int b = 0 ; b < batch ; b ++ )
806+ {
807+ expectedEntropyVec [ b ] = numOps . Divide ( expectedEntropySum [ b ] , numOps . FromDouble ( samples . Count ) ) ;
808+ }
809+
810+ var miVec = new Vector < T > ( batch ) ;
811+ for ( int b = 0 ; b < batch ; b ++ )
812+ {
813+ var mi = numOps . Subtract ( predictiveEntropyVec [ b ] , expectedEntropyVec [ b ] ) ;
814+ if ( numOps . LessThan ( mi , numOps . Zero ) )
815+ {
816+ mi = numOps . Zero ;
817+ }
818+ miVec [ b ] = mi ;
819+ }
820+
821+ predictiveEntropy = new Tensor < T > ( [ batch ] , predictiveEntropyVec ) ;
822+ mutualInformation = new Tensor < T > ( [ batch ] , miVec ) ;
823+ }
824+
825+ var metrics = CreateDefaultUncertaintyMetrics ( denormalizedMean , mutualInformation ) ;
826+ metrics [ PredictiveEntropyMetricKey ] = predictiveEntropy ;
827+ metrics [ MutualInformationMetricKey ] = mutualInformation ;
828+
829+ return new UncertaintyPredictionResult < T , TOutput > (
830+ methodUsed : method ,
831+ prediction : denormalizedMean ,
832+ variance : varianceOutput ,
833+ metrics : metrics ) ;
834+ }
835+
836+ private UncertaintyPredictionResult < T , TOutput > PredictWithBayesianNeuralNetwork (
837+ TInput newData ,
838+ UncertaintyQuantificationOptions uq ,
839+ UncertaintyQuantificationMethod method )
840+ {
841+ var estimator = Model as AiDotNet . UncertaintyQuantification . Interfaces . IUncertaintyEstimator < T > ;
842+ if ( estimator == null )
843+ {
844+ var deterministic = Predict ( newData ) ;
845+ var fallbackMetrics = CreateDefaultUncertaintyMetrics ( deterministic , mutualInformation : null ) ;
846+ return new UncertaintyPredictionResult < T , TOutput > (
847+ methodUsed : method ,
848+ prediction : deterministic ,
849+ variance : CreateZeroLike ( deterministic ) ,
850+ metrics : fallbackMetrics ) ;
851+ }
852+
853+ var ( normalizedNewData , _) = NormalizationInfo . Normalizer ! . NormalizeInput ( newData ) ;
854+ if ( normalizedNewData == null )
855+ {
856+ throw new InvalidOperationException ( "Normalizer returned null input." ) ;
857+ }
858+ var inputTensor = ConversionsHelper . ConvertToTensor < T > ( normalizedNewData ) . Clone ( ) ;
859+
860+ var uqResult = estimator . PredictWithUncertainty ( inputTensor ) ;
861+
862+ var meanOutput = ConvertFromTensor ( uqResult . Prediction ) ;
863+ var denormalizedMean = NormalizationInfo . Normalizer ! . Denormalize ( meanOutput , NormalizationInfo . YParams ) ;
864+
865+ var varianceTensor = uqResult . Variance != null
866+ ? uqResult . Variance . Clone ( )
867+ : new Tensor < T > (
868+ uqResult . Prediction . Shape ,
869+ Vector < T > . CreateDefault ( uqResult . Prediction . Length , MathHelper . GetNumericOperations < T > ( ) . Zero ) ) ;
870+ if ( uq . DenormalizeUncertainty )
871+ {
872+ varianceTensor = DenormalizeVarianceIfSupported ( varianceTensor , NormalizationInfo . YParams ) ;
873+ }
874+ var varianceOutput = ConvertFromTensor ( varianceTensor ) ;
875+
876+ var metrics = CreateDefaultUncertaintyMetrics ( denormalizedMean , mutualInformation : null ) ;
877+ if ( uqResult . Metrics != null )
878+ {
879+ foreach ( var kvp in uqResult . Metrics )
880+ {
881+ metrics [ kvp . Key ] = kvp . Value ;
882+ }
883+ }
884+
885+ return new UncertaintyPredictionResult < T , TOutput > (
886+ methodUsed : method ,
887+ prediction : denormalizedMean ,
888+ variance : varianceOutput ,
889+ metrics : metrics ) ;
890+ }
891+
716892 private static ( int batch , int classes ) InferBatchAndClasses ( Tensor < T > probabilities , int configuredClasses )
717893 {
718894 var classes = configuredClasses > 0
@@ -839,13 +1015,22 @@ private static Tensor<T> CreateZeroVectorTensor(int length)
8391015 }
8401016
8411017 private ( Tensor < T > mean , Tensor < T > variance , Tensor < T > predictiveEntropy , Tensor < T > mutualInformation )
842- ComputeMonteCarloMomentsAndMetrics ( TInput normalizedNewData , int numSamples )
1018+ ComputeMonteCarloMomentsAndMetrics (
1019+ TInput normalizedNewData ,
1020+ int numSamples ,
1021+ IReadOnlyList < AiDotNet . UncertaintyQuantification . Layers . MCDropoutLayer < T > > mcDropoutLayers ,
1022+ int ? randomSeed )
8431023 {
8441024 var numOps = MathHelper . GetNumericOperations < T > ( ) ;
8451025
8461026 var samples = new List < Tensor < T > > ( capacity : numSamples ) ;
8471027 for ( int i = 0 ; i < numSamples ; i ++ )
8481028 {
1029+ if ( randomSeed . HasValue )
1030+ {
1031+ ResetMonteCarloDropoutRng ( mcDropoutLayers , randomSeed . Value , i ) ;
1032+ }
1033+
8491034 var normalizedPrediction = Model ! . Predict ( normalizedNewData ) ;
8501035 samples . Add ( ConversionsHelper . ConvertToTensor < T > ( normalizedPrediction ! ) . Clone ( ) ) ;
8511036 }
@@ -905,6 +1090,24 @@ private static Tensor<T> CreateZeroVectorTensor(int length)
9051090 return ( meanTensor , varianceTensor , predictiveEntropy , mutualInformation ) ;
9061091 }
9071092
1093+ private static void ResetMonteCarloDropoutRng (
1094+ IReadOnlyList < AiDotNet . UncertaintyQuantification . Layers . MCDropoutLayer < T > > layers ,
1095+ int baseSeed ,
1096+ int sampleIndex )
1097+ {
1098+ unchecked
1099+ {
1100+ for ( int i = 0 ; i < layers . Count ; i ++ )
1101+ {
1102+ // Mix base seed, sample index, and layer index to get deterministic but distinct streams.
1103+ var mixed = baseSeed ;
1104+ mixed = ( mixed * 1000003 ) ^ ( sampleIndex + 1 ) ;
1105+ mixed = ( mixed * 1009 ) ^ ( i + 1 ) ;
1106+ layers [ i ] . ResetRng ( mixed ) ;
1107+ }
1108+ }
1109+ }
1110+
9081111 private TOutput ApplyTemperatureScalingToOutputProbabilities ( TOutput outputProbabilities , T temperature )
9091112 {
9101113 var tensor = ConversionsHelper . ConvertToTensor < T > ( outputProbabilities ! ) . Clone ( ) ;
0 commit comments