namespace AIStudio.Assistants.ModelComparison; /// /// How often a model won the runs which were decided, together with how sure that share is. /// /// /// Only decided runs count: a tie says that neither model is better, not that both are. The share comes with the /// Wilson score interval at 95 %, which stays meaningful for a handful of runs and for shares near 0 or 1, unlike the /// textbook interval. Only when the whole interval lies above one half is the model clearly better, and only when it /// lies below one half clearly worse; everything else is not yet clear. /// /// The interval treats every run as an independent test. The runs of one batch ask the same question, though, so they /// are less independent than that, and the interval is narrower than it should be. That is why the statistics count /// runs, but show how many comparisons they come from as well. /// public sealed record ModelComparisonWinShare { /// /// The quantile of the standard normal distribution for a two-sided interval of 95 %. /// private const double Z_95 = 1.959964; /// /// How many decided runs the model won. /// public required int Wins { get; init; } /// /// How many decided runs the model lost. /// public required int Losses { get; init; } /// /// How many runs were decided. /// public int Decided => this.Wins + this.Losses; /// /// The share of the decided runs which the model won, or null when no run was decided. /// public double? Share { get; private init; } /// /// The lower end of the interval, or null when no run was decided. /// public double? LowerBound { get; private init; } /// /// The upper end of the interval, or null when no run was decided. /// public double? UpperBound { get; private init; } /// /// Whether the decided runs say which model is better. /// public ModelComparisonSignificance Significance { get; private init; } /// /// Computes the share and its interval. /// /// How many decided runs the model won. /// How many decided runs the model lost. /// The share. public static ModelComparisonWinShare Of(int wins, int losses) { ArgumentOutOfRangeException.ThrowIfNegative(wins); ArgumentOutOfRangeException.ThrowIfNegative(losses); var decided = wins + losses; if (decided == 0) return new() { Wins = 0, Losses = 0, Significance = ModelComparisonSignificance.NOT_YET_CLEAR }; var share = (double)wins / decided; var zSquared = Z_95 * Z_95; var denominator = 1 + zSquared / decided; var center = share + zSquared / (2.0 * decided); var margin = Z_95 * Math.Sqrt(share * (1 - share) / decided + zSquared / (4.0 * decided * decided)); var lower = (center - margin) / denominator; var upper = (center + margin) / denominator; return new() { Wins = wins, Losses = losses, Share = share, LowerBound = lower, UpperBound = upper, Significance = lower > 0.5 ? ModelComparisonSignificance.CLEARLY_BETTER : upper < 0.5 ? ModelComparisonSignificance.CLEARLY_WORSE : ModelComparisonSignificance.NOT_YET_CLEAR, }; } }