Vol. 08 · ML & Decision Systems
Statistics for Evaluating LLM Systems
Basic concepts of statistics as applied to LLM experiments: quality metrics, variance and confidence intervals, hypothesis testing, A/B tests, LLM-as-judge, evaluation pitfalls and a practical checklist for a valid experiment.
LLM Quality Metrics
Log-loss (cross-entropy) measures how confidently the model predicts the correct token. Perplexity is simply an exponent of log-loss: “how many variants the model loses on average.” PPL=10 means the model “oscillates between 10 equally probable tokens” at each step.
Source-complete noteswhen to use
when to use
- comparison of pretrain quality of models
- assessment of quality degradation during quantization
- PPL on the domain ≠ PPL on your task
- does not reflect Useful framing for downstream tasks
For classification tasks (IntentRouter, sentiment, RAG relevance), accuracy often deceives when classes are imbalanced. F1 - harmonic mean Precision and Recall - fairer for rare classes.
Source-complete notesformulas and error matrix
texPredicted + Predicted −
Actual + TP FN
Actual − FP TN
Accuracy = (TP+TN) / (TP+TN+FP+FN)
→ cheats: 99% TN → accuracy=99% effortless
Precision = TP / (TP+FP) ← of all the “+”, how many “+” are true?
Recall = TP / (TP+FN) ← of all the real “+”s, how many did you find?
F1 = 2 · (P R) / (P+R) ← harmonic mean
Macro F1: average F1 for all classes (imbalance not taken into account)
Weighted F1: class frequency weightedBLEU and ROUGE measure the n-gram intersection between a generation and a reference. Historically the standard for translation and summarization, but poorly correlated with human assessment of the quality of free text.
Source-complete notesmechanics · modern practice
textBLEU (precision-oriented):
BLEU-N = intersection of N-grams(gen, ref) / len(gen)
↑ penalizes for output that is too short (BP)
↑ usually average BLEU-1..4
ROUGE-N (recall-oriented):
ROUGE-N = intersection of N-grams(gen, ref) / len(ref)
ROUGE-L: longest common subsequence
Example:
ref: "the cat was sitting on the rug"
gen: "the rug is occupied by a cat"
BLEU-1 = 0.5 (2 words out of 3 are in ref)
but the meaning is correct → BLEU does not see this
BERTScore: cosine proximity of embeddings
↑ better for semantic qualitymodern practice
- BLEU/ROUGE - only as baseline
- BERTScore - if you need automation
- LLM-as-judge - for free generation
The RAG pipeline consists of two stages - retrieval and generation - and both need to be measured separately. The final quality of the answer depends on both, but different types of errors require different corrections.
Source-complete notesRAGAS and components
sqlRetrieval:
Hit Rate@k - relevant doc in the top-k?
MRR@k - position of the first relevant
Precision@k - share of relevant ones in the top k
Generation (RAGAS):
Faithfulness - Is the answer based on context?
Answer Relevancy - does the answer answer the question?
Context Precision - does the context contain the answer?
Context Recall - has all the required context been found?
Faithfulness Formula (LLM-judge):
score = (#statements from context) / (#all statements)
→ catches hallucinationsScatter, Variance and Confidence Intervals
One assessment of the LLM system does not mean anything. What matters is the spread—how unstable the results are. The variance σ² and standard deviation σ measure this spread around the mean.
Source-complete notesformulas · interpretation
tex# n runs of the experiment, xᵢ is the result of the i-th
Sample mean:
x̄ = (1/n) · Σᵢ xᵢ
Sample variance (unbiased):
s² = 1/(n−1) Σᵢ (xᵢ − x̄)²
↑ divide by n-1, not n (Bessel correction)
Standard Deviation:
s = √s² ← same units as xᵢ
Example: accuracy on 10 test sets:
[0.82, 0.79, 0.84, 0.81, 0.83,
0.80, 0.85, 0.78, 0.82, 0.81]
x = 0.815
s = 0.021 ← spread ≈ 2%interpretation
- s small → results are stable
- s large → more runs needed
- one point instead of distribution = error
The average is also a random variable. Standard error (SE) measures how closely the estimated mean reflects the “true.” A confidence interval (CI) gives a range within which there is a 95% probability that the true value of a metric lies.
Source-complete notesformulas
texStandard error of the mean:
SE = s/√n
↑ decreases with increasing sample
↑ for n=100: SE is 10 times less than n=1
95% Confidence Interval (normal):
CI = x̄ ± 1.96 SE = x̄ ± 1.96 s/√n
Example:
accuracy x̄ = 0.815, s = 0.021, n = 100
SE = 0.021 / √100 = 0.0021
95% CI = [0.811, 0.819]
n = 10 → SE = 0.021/√10 = 0.0066
95% CI = [0.802, 0.828] ← wide interval!
Conclusion: n=10 is a bad score. Need ≥ 50-100.The formula 1.96·SE works under normal distribution. LLM metrics (F1, BLEU, win-rate) are often not normal. Bootstrap is a universal method: we sample with return and build an empirical distribution.
Source-complete notesbootstrap CI algorithm · when to use
sqlresults = [0.82, 0.79, 0.84, 0.81, …] # n=100
bootstrap_means = []
for _ in range(10_000):
sample = resample(results, n=100) # return
bootstrap_means.append(mean(sample))
#95% CI = 2.5% and 97.5% percentiles
ci_low = percentile(bootstrap_means, 2.5)
ci_high = percentile(bootstrap_means, 97.5)
print(f"95% CI: [{ci_low:.3f}, {ci_high:.3f}]")
from scipy.stats import bootstrap
res = bootstrap((data,), np.mean, n_resamples=10000)when to use
- the metric is not normally distributed
- small sample (n < 30)
- complex metrics (BLEU, F1, win-rate)
- gold standard for LLM experiments
LLM adds its own layer of randomness on top of the normal sample variance. Before calculating the metric, you need to know what noise sources there are and which one dominates.
Testing Statistical Hypotheses
Hypothesis testing is a formal procedure for deciding whether the improvement is real or random. The p-value is the most commonly misinterpreted value in science. Let's figure out exactly what it means and what it doesn't.
Source-complete notestypical thresholds α
typical thresholds α
- p < 0.05 - standard (5% probability of false alarm)
- p < 0.01 - strict
- p < 0.1 - soft (research)
- p by itself without effect size is an incomplete picture
The t-test tests whether the observed difference in means could have occurred by chance? Paired t-test - for the same sets of questions (A and B were assessed on the same data). Independent - for different sets.
Source-complete notespaired t-test (recommended for LLM)
sql# Both prompts were run on the same 100 questions
scores_A = [0.8, 0.6, 1.0, …] # 100 values
scores_B = [0.9, 0.7, 1.0, …] # 100 values
diff = scores_B - scores_A # pairwise differences
# H₀: mean(diff) = 0
t = mean(diff) / (std(diff) / √n)
# compare with t-distribution (n-1 degrees of freedom)
from scipy.stats import ttest_rel
t_stat, p_value = ttest_rel(scores_B, scores_A)
Why paired is better than independent:
removes variance from question difficulty
→ one complex issue affects both systems
→ difference is cleanerWhen an LLM-judge or person gives marks 1–5, the data is ordinal, not interval. The t-test assumes normality - this is violated for scores. The Wilcoxon test is a nonparametric analogue of the paired t-test and makes no assumptions about distribution.
Source-complete notesalgorithm and application
sql# Ratings of two systems (1–5) using 50 examples:
grades_A = [3, 4, 3, 5, 2, …]
grades_B = [4, 4, 4, 5, 3, …]
from scipy.stats import wilcoxon
stat, p_value = wilcoxon(grades_B, grades_A,
alternative='greater')
Works through difference ranks:
diff_i = B_i - A_i
→ rank |diff_i|
→ compare the sums of ranks “+” and “−”
When to use:
✓ ratings 1-5, 1-10 (LLM-judge, human eval)
✓ ordinal metrics (pass@k ranks)
✓ small samples (n < 30)If you compare 10 prompts in pairs, that’s 45 tests. With α=0.05 and 45 tests, ~2 false significant results are expected simply by chance. The Bonferroni correction adjusts the significance threshold.
Source-complete notesproblem and solution · rule
sqlFamily-wise error rate (FWER):
P(at least 1 false alarm from m tests)
= 1 − (1−α)^m
m=45, α=0.05 → 1−(0.95)^45 = 0.90 = 90%!
Bonferroni correction:
α* = α / m = 0.05 / 45 = 0.0011
→ use this threshold for each test
Benjamini-Hochberg correction (FDR):
less strict, controls the rate of false discoveries
→ recommended for large numbers of comparisons
from statsmodels.stats.multitest import multipletests
reject, p_adj, _, _ = multipletests(
p_values, method='bonferroni' # or 'fdr_bh'
)rule
- compared 10 prompts, took the best one without correction → error
- Bonferroni - strictly, few comparisons (<20)
- BH / FDR - flexible, many comparisons
A/B Test of LLM systems
p-value says “whether the difference is due to chance”, but nothing about its magnitude. With large n, even a difference of 0.1% will be statistically significant. Effect size is a separate value that answers the question “how big is the effect?”
Source-complete notespractice
practice
- d > 0.5 → worth implementing
- d = 0.2–0.5 → depends on the cost of change
- d < 0.2 → most likely not worth it
- for win-rate: OR / relative lift as effect size
Power (test power) - the probability of detecting a real effect if there is one. Power = 1 − β, where β is the probability of missing an improvement (type II error). Standard: power ≥ 0.80. Power analysis answers the question “how many examples are needed” before the experiment?
Source-complete notesformula and example
sqlFour related parameters (know 3 → find 4th):
α (significance level, usually 0.05)
power (1−β, typically 0.80)
d (expected effect size)
n (← what we are looking for)
Minimum n for paired t-test:
from statsmodels.stats.power import TTestPower
analysis = TTestPower()
n = analysis.solve_power(
effect_size=0.3, # expected Cohen's d
alpha=0.05,
power=0.80
)
# → n ≈ 90 examples per group
Empirical guidelines for LLM:
d=0.5 → n ≥ 34 (large expected effect)
d=0.3 → n ≥ 90
d=0.2 → n ≥ 200 (small effect - you need a lot)Most errors in LLM experiments are not mathematical, but design ones: the test set is chosen incorrectly, systems are compared on different data, or the metric does not correspond to the task.
Source-complete notesdesign checklist
sql① Formulate a hypothesis BEFORE the experiment
"Prompt B gives higher F1 on task X"
→ not “let’s see what’s best”
② Fix the metric BEFORE the run
→ you cannot select a metric after, based on the best result
③ Use the SAME test set for A and B
→ paired test, removes variance from the difficulty of questions
④ The test set should not overlap with the dev set
→ otherwise you optimize for a test without knowledge
⑤ Run each system k≥3 times (at T>0)
→ average across runs, estimate σ as a function of temperature
⑥ Calculate n using power analysis IN ADVANCE
→ don't stop when p < 0.05 “for the first time”LLM-specific assessment
LLM-as-judge is itself a stochastic process with variance. Before trusting his assessments, you need to measure the consistency of the judge with yourself and with people. Only then can the results be interpreted as a metric.
Source-complete notespractice
practice
- validate judge on ~100 human-labeled examples
- average 3+ runs of judge at T>0
- set judge T=0 for reproducibility
- one run judge = not a metric, but a random point
Win-rate (the percentage of victories of A over B in pairwise comparisons) is an intuitive metric. But how do you know if the difference 55% vs 45% is significant? This is a problem for a fraction test - the binomial test or chi-square.
Source-complete noteswin-rate significance test
sql# 100 comparisons A vs B
wins_B = 58 # B won 58 times
n = 100 # total comparisons
# H₀: p(B wins) = 0.5 (equal systems)
from scipy.stats import binomtest
result = binomtest(wins_B, n, p=0.5,
alternative='greater')
print(result.pvalue) # → 0.044 → significant!
# CI for win-rate (Wilson interval):
from statsmodels.stats.proportion import proportion_confint
ci = proportion_confint(wins_B, n, method='wilson')
# → (0.483, 0.671)
Minimum significant difference (α=0.05):
n=50 → win-rate > 64% significant
n=100 → win-rate > 59% significant
n=200 → win-rate > 56% significantWhen comparing more than two systems, pairwise win-rates are not transitive. Elo-rating (from chess) solves this problem: each win/loss updates the rating taking into account the expected result. Used in Chatbot Arena (LMSYS).
Source-complete notesElo mechanics
text# Expected probability of A winning over B:
E_A = 1 / (1 + 10^((R_B − R_A) / 400))
# Rating update after A vs B match:
# S_A = 1 (win), 0.5 (draw), 0 (loss)
R_A_new = R_A + K (S_A − E_A)
#K = learning factor (usually 32)
Initial rating: 1000 for everyone
Difference 400 → expected ~10% wins for the weakest
Bootstrap CI for Elo (Chatbot Arena approach):
→ resample paired matches 1000 times
→ recalculate Elo for each bootstrap
→ 95% CI by percentilesAn average score across multiple tasks/benchmarks masks uneven quality. A model can perform well on 9 out of 10 problems and fail on the 10th - the average score still looks good.
Pitfalls in Evaluating LLM Systems
The three most dangerous pitfalls when evaluating LLM: test data leaked into training, the metric no longer reflects real quality, or the model is “tailored” for a benchmark. All three make the comparison meaningless.
LLM-judge systematically distorts scores in favor of the first or second answer (positional bias) and in favor of longer answers (verbosity bias) - regardless of the quality of the content.
Source-complete notestypes of displacements and protection
sqlPositional bias:
judge([A, B]) → A wins 65% of the time
judge([B, A]) → B wins 62% of the time
→ the first one wins both times!
Defense: Always run in both orders
Result: win only if you win in BOTH runs
or take the average if there is a draw
Verbosity bias:
judge prefers a longer answer
even if it's less accurate
Defense: explicitly in the judge prompt: “length is not a criterion”
Defense: normalize the score by the length of the answer
Self-enhancement bias:
Claude judge overestimates Claude answers
GPT-4 judge overestimates GPT-4 responses
Protection: use judge from another providerA difference in the formulation of a prompt by ±10% of the metric is a common phenomenon. This means that “comparing systems” without prompt control is actually “comparing prompts.” The result depends on who wrote the better prompt for their system.
Source-complete noteshow to control
textAntipattern:
System A: Prompt v7 (carefully written)
System B: prompt v1 (draft)
→ you compare the quality of prompting, not systems
That's right - fix the prompt template:
one template for both systems
vary ONLY the parameter being studied
Or - multi-prompt evaluation:
k=5 prompt options for each system
estimate = mean ± std for all options
→ you evaluate robustness, not a point result
Sensitivity measurement:
CV = std/mean (coefficient of variation)
CV > 0.1 → system is unstable to formulationPractice: Checklist and Rules
Go through this list before and after launch. If at least one point is missed, the conclusions are questionable.
Quick cheat sheet: which statistical method to use in typical LLM scenarios.
Source-complete notestable · minimum stack
| Task | Method | Python |
|---|---|---|
| Compare 2 prompts | paired t-test | ttest_rel(a, b) |
| Ratings 1–5 from judge | Wilcoxon | wilcoxon(a, b) |
| Win-rate significance | Binomial test | binomtest(w, n) |
| CI for any metric | Bootstrap | bootstrap((x,), np.mean) |
| Compare N systems | Elo + bootstrap CI | elo_mle() + resample |
| N prompts → choosing the best | + Bonferroni | multipletests(p_vals) |
| Judge vs human agreement | Spearman ρ / Cohen κ | spearmanr / cohen_kappa |
| How many examples do you need? | Power analysis | TTestPower().solve_power |
minimum stack
- scipy.stats
- statsmodels
- numpy (bootstrap)
- ragas (for RAG metrics)
No dead end
Keep moving through the map.
Continue in sequence, switch to a related guide, or return to the seven-track learning map.