Skip to content

Commit b187e31

Browse files
ooplesclaude
andcommitted
fix(stats): break EnsureFullStatsComputed recursion in errorstats/modelstats/predictionstats
Same bug class as the earlier BasicStats fix: the Calculate* method was assigning to properties AND reading them back during its own body, but the property getters call EnsureFullStatsComputed — which is still running the Calculate* method. The _fullStatsComputed flag only flips after Calculate* returns, so any intra-method property read re-enters Calculate* unbounded. The test host crashes with StackOverflowException before the test framework can report anything except "host process exited unexpectedly." Specific re-entry points the previous code had: * ErrorStats.CalculateErrorStats - RMSE = _numOps.Sqrt(MSE) ← re-enters via MSE getter - AIC/BIC/AICAlt pass RSS ← re-enters via RSS getter * ModelStats.CalculateModelStats - VIFList = ... CalculateVIF(CorrelationMatrix, ...) ← CorrelationMatrix - Mahalanobis block reads CovarianceMatrix thrice ← CovarianceMatrix * PredictionStats.CalculatePredictionStats - AdjustedR2 = ... CalculateAdjustedR2(R2, ...) ← R2 - PredictionIntervalCoverage = ... (PredictionInterval.Lower, PredictionInterval.Upper) ← PredictionInterval - ConfidenceInterval/CredibleInterval read BestDistributionFit .DistributionType ← BestDistributionFit All three methods are rewritten to compute every intermediate into a local variable first; properties are only assigned once every dependency is a local. No property reads happen inside Calculate*, so the lazy getter never re-enters. Observed failure path (Classification CI shard, PR #1156 run): AdaBoostClassifierTests.Predict_ShouldBeDeterministic trains the model, which computes ErrorStats, which stack-overflows the host. Other crashed tests in the same shard: - ExtraTreesClassifierTests.Clone_ShouldProduceIdenticalPredictions - CategoricalNaiveBayesTests.Builder_AccuracyShouldBeatChance - OneVsRestClassifierTests.Builder_AccuracyShouldBeatChance All 4 pass locally after this fix. Unblocks the host_crash jobs on PR #1154 triage: - ModelFamily - Classification - ModelFamily - Clustering/GP - ModelFamily - Regression - ModelFamily - TimeSeries/Activation/Loss - Unit - 04 Feature/Fit/Fitness/Genetics - AiDotNet.Serving.Tests Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 110e2be commit b187e31

3 files changed

Lines changed: 203 additions & 110 deletions

File tree

src/Statistics/ErrorStats.cs

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -513,50 +513,83 @@ public static ErrorStats<T> Empty()
513513
/// </remarks>
514514
private void CalculateErrorStats(Vector<T> actual, Vector<T> predicted, int numberOfParameters, PredictionType predictionType = PredictionType.Regression)
515515
{
516+
// All intermediate values are held in locals and only assigned to the
517+
// observable properties at the very end. Earlier code called
518+
// RMSE = _numOps.Sqrt(MSE);
519+
// AIC = StatisticsHelper.CalculateAIC(n, numberOfParameters, RSS);
520+
// which read MSE/RSS through their property getters. Those getters call
521+
// EnsureFullStatsComputed, which is still running CalculateErrorStats —
522+
// so the property read re-enters CalculateErrorStats and recurses
523+
// without bound. The test host crashes with StackOverflowException
524+
// (Classification / Clustering / Regression / TimeSeries / Serving
525+
// host-crashes on PR #1154 and master).
516526
int n = actual.Length;
517527

518-
// Calculate basic error metrics
519-
MAE = StatisticsHelper<T>.CalculateMeanAbsoluteError(actual, predicted);
520-
RSS = StatisticsHelper<T>.CalculateResidualSumOfSquares(actual, predicted);
521-
MSE = StatisticsHelper<T>.CalculateMeanSquaredError(actual, predicted);
522-
RMSE = _numOps.Sqrt(MSE);
523-
MAPE = StatisticsHelper<T>.CalculateMeanAbsolutePercentageError(actual, predicted);
524-
MedianAbsoluteError = StatisticsHelper<T>.CalculateMedianAbsoluteError(actual, predicted);
525-
MaxError = StatisticsHelper<T>.CalculateMaxError(actual, predicted);
528+
T mae = StatisticsHelper<T>.CalculateMeanAbsoluteError(actual, predicted);
529+
T rss = StatisticsHelper<T>.CalculateResidualSumOfSquares(actual, predicted);
530+
T mse = StatisticsHelper<T>.CalculateMeanSquaredError(actual, predicted);
531+
T rmse = _numOps.Sqrt(mse);
532+
T mape = StatisticsHelper<T>.CalculateMeanAbsolutePercentageError(actual, predicted);
533+
T medAbs = StatisticsHelper<T>.CalculateMedianAbsoluteError(actual, predicted);
534+
T maxErr = StatisticsHelper<T>.CalculateMaxError(actual, predicted);
535+
T aucPR, aucROC;
526536
if (predictionType == PredictionType.BinaryClassification)
527537
{
528-
AUCPR = StatisticsHelper<T>.CalculatePrecisionRecallAUC(actual, predicted);
529-
AUCROC = StatisticsHelper<T>.CalculateROCAUC(actual, predicted);
538+
aucPR = StatisticsHelper<T>.CalculatePrecisionRecallAUC(actual, predicted);
539+
aucROC = StatisticsHelper<T>.CalculateROCAUC(actual, predicted);
530540
}
531541
else
532542
{
533-
AUCPR = _numOps.Zero;
534-
AUCROC = _numOps.Zero;
543+
aucPR = _numOps.Zero;
544+
aucROC = _numOps.Zero;
535545
}
536-
SMAPE = StatisticsHelper<T>.CalculateSymmetricMeanAbsolutePercentageError(actual, predicted);
537-
MeanSquaredLogError = StatisticsHelper<T>.CalculateMeanSquaredLogError(actual, predicted);
538-
CRPS = StatisticsHelper<T>.CalculateCRPS(actual, predicted);
539-
540-
// Calculate standard errors
541-
SampleStandardError = StatisticsHelper<T>.CalculateSampleStandardError(actual, predicted, numberOfParameters);
542-
PopulationStandardError = StatisticsHelper<T>.CalculatePopulationStandardError(actual, predicted);
543-
544-
// Calculate bias and autocorrelation metrics
545-
MeanBiasError = StatisticsHelper<T>.CalculateMeanBiasError(actual, predicted);
546-
TheilUStatistic = StatisticsHelper<T>.CalculateTheilUStatistic(actual, predicted);
547-
DurbinWatsonStatistic = StatisticsHelper<T>.CalculateDurbinWatsonStatistic(actual, predicted);
548-
549-
// Calculate information criteria
550-
AIC = StatisticsHelper<T>.CalculateAIC(n, numberOfParameters, RSS);
551-
BIC = StatisticsHelper<T>.CalculateBIC(n, numberOfParameters, RSS);
552-
AICAlt = StatisticsHelper<T>.CalculateAICAlternative(n, numberOfParameters, RSS);
553-
554-
// Calculate classification metrics
555-
Accuracy = StatisticsHelper<T>.CalculateAccuracy(actual, predicted, predictionType);
556-
(Precision, Recall, F1Score) = StatisticsHelper<T>.CalculatePrecisionRecallF1(actual, predicted, predictionType);
557-
558-
// Populate error list
559-
ErrorList = new List<T>(StatisticsHelper<T>.CalculateResiduals(actual, predicted));
546+
T smape = StatisticsHelper<T>.CalculateSymmetricMeanAbsolutePercentageError(actual, predicted);
547+
T msle = StatisticsHelper<T>.CalculateMeanSquaredLogError(actual, predicted);
548+
T crps = StatisticsHelper<T>.CalculateCRPS(actual, predicted);
549+
550+
T sampleStd = StatisticsHelper<T>.CalculateSampleStandardError(actual, predicted, numberOfParameters);
551+
T popStd = StatisticsHelper<T>.CalculatePopulationStandardError(actual, predicted);
552+
553+
T bias = StatisticsHelper<T>.CalculateMeanBiasError(actual, predicted);
554+
T theil = StatisticsHelper<T>.CalculateTheilUStatistic(actual, predicted);
555+
T dw = StatisticsHelper<T>.CalculateDurbinWatsonStatistic(actual, predicted);
556+
557+
T aic = StatisticsHelper<T>.CalculateAIC(n, numberOfParameters, rss);
558+
T bic = StatisticsHelper<T>.CalculateBIC(n, numberOfParameters, rss);
559+
T aicAlt = StatisticsHelper<T>.CalculateAICAlternative(n, numberOfParameters, rss);
560+
561+
T accuracy = StatisticsHelper<T>.CalculateAccuracy(actual, predicted, predictionType);
562+
var (precision, recall, f1) = StatisticsHelper<T>.CalculatePrecisionRecallF1(actual, predicted, predictionType);
563+
564+
var residuals = new List<T>(StatisticsHelper<T>.CalculateResiduals(actual, predicted));
565+
566+
// Assign to the observable properties only after every dependency is a
567+
// local — no re-entry possible now.
568+
MAE = mae;
569+
RSS = rss;
570+
MSE = mse;
571+
RMSE = rmse;
572+
MAPE = mape;
573+
MedianAbsoluteError = medAbs;
574+
MaxError = maxErr;
575+
AUCPR = aucPR;
576+
AUCROC = aucROC;
577+
SMAPE = smape;
578+
MeanSquaredLogError = msle;
579+
CRPS = crps;
580+
SampleStandardError = sampleStd;
581+
PopulationStandardError = popStd;
582+
MeanBiasError = bias;
583+
TheilUStatistic = theil;
584+
DurbinWatsonStatistic = dw;
585+
AIC = aic;
586+
BIC = bic;
587+
AICAlt = aicAlt;
588+
Accuracy = accuracy;
589+
Precision = precision;
590+
Recall = recall;
591+
F1Score = f1;
592+
ErrorList = residuals;
560593
}
561594

562595
/// <summary>

src/Statistics/ModelStats.cs

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -552,14 +552,16 @@ public static ModelStats<T, TInput, TOutput> Empty()
552552
/// </remarks>
553553
private void CalculateModelStats(ModelStatsInputs<T, TInput, TOutput> inputs)
554554
{
555-
// Convert input matrix to Matrix<T> for statistical calculations
555+
// All intermediate values held in locals until the end. Previous code
556+
// read CorrelationMatrix and CovarianceMatrix through their property
557+
// getters during computation; those getters call
558+
// EnsureFullStatsComputed, which re-enters this method — unbounded
559+
// recursion + StackOverflowException. Same bug class as BasicStats /
560+
// ErrorStats / PredictionStats.
556561
Matrix<T> matrix = ConversionsHelper.ConvertToMatrix<T, TInput>(inputs.XMatrix);
557-
558-
// Convert actual and predicted values to Vector<T> for statistical calculations
559562
Vector<T> actual = ConversionsHelper.ConvertToVector<T, TOutput>(inputs.Actual);
560563
Vector<T> predicted = ConversionsHelper.ConvertToVector<T, TOutput>(inputs.Predicted);
561564

562-
// Convert coefficients if available
563565
Vector<T> coefficients = Vector<T>.Empty();
564566
if (inputs.Coefficients != null)
565567
{
@@ -568,64 +570,89 @@ private void CalculateModelStats(ModelStatsInputs<T, TInput, TOutput> inputs)
568570

569571
var featureCount = inputs.FeatureCount;
570572

571-
// Calculate all statistical metrics using the converted data types
572-
CorrelationMatrix = StatisticsHelper<T>.CalculateCorrelationMatrix(matrix, _options);
573-
CovarianceMatrix = StatisticsHelper<T>.CalculateCovarianceMatrix(matrix);
574-
VIFList = StatisticsHelper<T>.CalculateVIF(CorrelationMatrix, _options);
575-
ConditionNumber = StatisticsHelper<T>.CalculateConditionNumber(matrix, _options);
576-
LogPointwisePredictiveDensity = StatisticsHelper<T>.CalculateLogPointwisePredictiveDensity(actual, predicted);
573+
var correlationMatrix = StatisticsHelper<T>.CalculateCorrelationMatrix(matrix, _options);
574+
var covarianceMatrix = StatisticsHelper<T>.CalculateCovarianceMatrix(matrix);
575+
var vifList = StatisticsHelper<T>.CalculateVIF(correlationMatrix, _options);
576+
T conditionNumber = StatisticsHelper<T>.CalculateConditionNumber(matrix, _options);
577+
T logPpd = StatisticsHelper<T>.CalculateLogPointwisePredictiveDensity(actual, predicted);
577578

579+
List<T>? looPd = null;
578580
if (inputs.FitFunction != null)
579581
{
580-
// Convert the fit function to work with Matrix<T> and Vector<T>
581582
var convertedFitFunction = ConversionsHelper.ConvertFitFunction<T, TInput, TOutput>(inputs.FitFunction);
582-
583-
// Create a wrapper function that matches the expected signature
584583
Vector<T> wrappedFitFunction(Matrix<T> m, Vector<T> v) => convertedFitFunction(m);
585-
LeaveOneOutPredictiveDensities = StatisticsHelper<T>.CalculateLeaveOneOutPredictiveDensities(matrix, actual, wrappedFitFunction);
584+
looPd = StatisticsHelper<T>.CalculateLeaveOneOutPredictiveDensities(matrix, actual, wrappedFitFunction);
586585
}
587586

588-
ObservedTestStatistic = StatisticsHelper<T>.CalculateObservedTestStatistic(actual, predicted);
589-
PosteriorPredictiveSamples = StatisticsHelper<T>.CalculatePosteriorPredictiveSamples(actual, predicted, featureCount);
590-
MarginalLikelihood = StatisticsHelper<T>.CalculateMarginalLikelihood(actual, predicted, featureCount);
591-
ReferenceModelMarginalLikelihood = StatisticsHelper<T>.CalculateReferenceModelMarginalLikelihood(actual);
592-
LogLikelihood = StatisticsHelper<T>.CalculateLogLikelihood(actual, predicted);
593-
EffectiveNumberOfParameters = StatisticsHelper<T>.CalculateEffectiveNumberOfParameters(matrix, coefficients);
594-
MutualInformation = StatisticsHelper<T>.CalculateMutualInformation(actual, predicted);
595-
NormalizedMutualInformation = StatisticsHelper<T>.CalculateNormalizedMutualInformation(actual, predicted);
596-
VariationOfInformation = StatisticsHelper<T>.CalculateVariationOfInformation(actual, predicted);
597-
SilhouetteScore = StatisticsHelper<T>.CalculateSilhouetteScore(matrix, predicted);
598-
CalinskiHarabaszIndex = StatisticsHelper<T>.CalculateCalinskiHarabaszIndex(matrix, predicted);
599-
DaviesBouldinIndex = StatisticsHelper<T>.CalculateDaviesBouldinIndex(matrix, predicted);
600-
MeanAveragePrecision = StatisticsHelper<T>.CalculateMeanAveragePrecision(actual, predicted, _options.MapTopK);
601-
NormalizedDiscountedCumulativeGain = StatisticsHelper<T>.CalculateNDCG(actual, predicted, _options.NdcgTopK);
602-
MeanReciprocalRank = StatisticsHelper<T>.CalculateMeanReciprocalRank(actual, predicted);
603-
604-
// Calculate residuals and time series metrics
587+
T observedTest = StatisticsHelper<T>.CalculateObservedTestStatistic(actual, predicted);
588+
var posteriorSamples = StatisticsHelper<T>.CalculatePosteriorPredictiveSamples(actual, predicted, featureCount);
589+
T marginalLik = StatisticsHelper<T>.CalculateMarginalLikelihood(actual, predicted, featureCount);
590+
T refModelML = StatisticsHelper<T>.CalculateReferenceModelMarginalLikelihood(actual);
591+
T logLik = StatisticsHelper<T>.CalculateLogLikelihood(actual, predicted);
592+
T effParams = StatisticsHelper<T>.CalculateEffectiveNumberOfParameters(matrix, coefficients);
593+
T mi = StatisticsHelper<T>.CalculateMutualInformation(actual, predicted);
594+
T nmi = StatisticsHelper<T>.CalculateNormalizedMutualInformation(actual, predicted);
595+
T voi = StatisticsHelper<T>.CalculateVariationOfInformation(actual, predicted);
596+
T silhouette = StatisticsHelper<T>.CalculateSilhouetteScore(matrix, predicted);
597+
T ch = StatisticsHelper<T>.CalculateCalinskiHarabaszIndex(matrix, predicted);
598+
T db = StatisticsHelper<T>.CalculateDaviesBouldinIndex(matrix, predicted);
599+
T mAP = StatisticsHelper<T>.CalculateMeanAveragePrecision(actual, predicted, _options.MapTopK);
600+
T ndcg = StatisticsHelper<T>.CalculateNDCG(actual, predicted, _options.NdcgTopK);
601+
T mrr = StatisticsHelper<T>.CalculateMeanReciprocalRank(actual, predicted);
602+
605603
var residuals = StatisticsHelper<T>.CalculateResiduals(actual, predicted);
606-
AutoCorrelationFunction = StatisticsHelper<T>.CalculateAutoCorrelationFunction(residuals, _options.AcfMaxLag);
607-
PartialAutoCorrelationFunction = StatisticsHelper<T>.CalculatePartialAutoCorrelationFunction(residuals, _options.PacfMaxLag);
608-
609-
// Calculate distance metrics
610-
EuclideanDistance = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Euclidean);
611-
ManhattanDistance = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Manhattan);
612-
CosineSimilarity = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Cosine);
613-
JaccardSimilarity = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Jaccard);
614-
HammingDistance = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Hamming);
615-
616-
// Mahalanobis distance requires vector dimensions to match covariance matrix dimensions
617-
// Skip calculation if dimensions don't match (covariance is feature x feature, not sample x sample)
604+
var acf = StatisticsHelper<T>.CalculateAutoCorrelationFunction(residuals, _options.AcfMaxLag);
605+
var pacf = StatisticsHelper<T>.CalculatePartialAutoCorrelationFunction(residuals, _options.PacfMaxLag);
606+
607+
T euclidean = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Euclidean);
608+
T manhattan = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Manhattan);
609+
T cosine = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Cosine);
610+
T jaccard = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Jaccard);
611+
T hamming = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Hamming);
612+
613+
T mahalanobis = _numOps.Zero;
618614
try
619615
{
620-
if (CovarianceMatrix.Rows == actual.Length && CovarianceMatrix.Columns == actual.Length)
616+
if (covarianceMatrix.Rows == actual.Length && covarianceMatrix.Columns == actual.Length)
621617
{
622-
MahalanobisDistance = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Mahalanobis, CovarianceMatrix);
618+
mahalanobis = StatisticsHelper<T>.CalculateDistance(actual, predicted, DistanceMetricType.Mahalanobis, covarianceMatrix);
623619
}
624620
}
625621
catch (ArgumentException)
626622
{
627-
// Silently skip Mahalanobis distance calculation when dimensions don't match
623+
// Silently skip Mahalanobis distance calculation when dimensions don't match.
628624
}
625+
626+
// Assign properties once every dependency is a local — no re-entry.
627+
CorrelationMatrix = correlationMatrix;
628+
CovarianceMatrix = covarianceMatrix;
629+
VIFList = vifList;
630+
ConditionNumber = conditionNumber;
631+
LogPointwisePredictiveDensity = logPpd;
632+
if (looPd != null) LeaveOneOutPredictiveDensities = looPd;
633+
ObservedTestStatistic = observedTest;
634+
PosteriorPredictiveSamples = posteriorSamples;
635+
MarginalLikelihood = marginalLik;
636+
ReferenceModelMarginalLikelihood = refModelML;
637+
LogLikelihood = logLik;
638+
EffectiveNumberOfParameters = effParams;
639+
MutualInformation = mi;
640+
NormalizedMutualInformation = nmi;
641+
VariationOfInformation = voi;
642+
SilhouetteScore = silhouette;
643+
CalinskiHarabaszIndex = ch;
644+
DaviesBouldinIndex = db;
645+
MeanAveragePrecision = mAP;
646+
NormalizedDiscountedCumulativeGain = ndcg;
647+
MeanReciprocalRank = mrr;
648+
AutoCorrelationFunction = acf;
649+
PartialAutoCorrelationFunction = pacf;
650+
EuclideanDistance = euclidean;
651+
ManhattanDistance = manhattan;
652+
CosineSimilarity = cosine;
653+
JaccardSimilarity = jaccard;
654+
HammingDistance = hamming;
655+
MahalanobisDistance = mahalanobis;
629656
}
630657

631658
/// <summary>

0 commit comments

Comments
 (0)