LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 196 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/9
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningBayesian Inference from EvidenceStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/15
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageStatistical Learning and GeneralizationConformal Prediction and UncertaintyClustering and PCAProbabilistic Generative ModelsCore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingCausal Inference and CounterfactualsPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/25
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernancePrivacy-Preserving Machine LearningData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/17
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningTraining Run OperationsReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/31
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceAccelerator Architecture Field GuideCorrect GPU Profiling and BenchmarkingGPU Kernel Performance Engineering LabGPU Parallel Primitives LabGPU Compilers and Kernel LanguagesGEMM From Naive CUDA to Tensor CoresLow-Precision GPU ProgrammingGPU Runtime Execution LabAI-Generated Kernel EngineeringModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingGPU Failure OperationsDistributed Inference Data PlaneServing Benchmark EngineeringA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning Agent System Design
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnMath & StatisticsProbability for Machine Learning
📊EasyEvaluation & Benchmarks

Probability for Machine Learning

Use one API abuse-risk detector to learn events, random variables, distributions, conditional probability, independence, Bayes rule, and base-rate mistakes.

20 min read
Learning path
Step 14 of 196 in the full curriculum
Adam, Momentum, SchedulersBayesian Inference from Evidence

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

An abuse detector sends an API signup to manual review. On labeled signup data, it catches 95 percent of abusive signups. That number sounds reassuring until a reviewer asks a different question: when a signup is flagged, how often is it actually abusive?

If abuse is rare, even a modest false-alarm rate can fill the review queue with clean signups. That queue determines whether reviewers can keep up, so confusing detector recall with queue risk can turn into a costly operating decision.

Tracking that queue directly from raw counts gives you an intuitive anchor before touching any mathematical symbols. Once the mechanics of false alarms click, the definitions of sample spaces, random variables, conditional probability, and Bayes' theorem feel completely natural.[1]Reference 1Machine Learning: A Probabilistic Perspective.https://probml.github.io/pml-book/book0.html[2]Reference 2Pattern Recognition and Machine Learning.https://www.microsoft.com/en-us/research/publication/pattern-recognition-machine-learning/

Start with the population

Suppose a fully labeled reference population contains 10,000 signups from one API signup surface. These are illustrative counts, not measured abuse rates. Pick one row uniformly at random, so every signup is equally likely to be chosen.

Signup typeCountProbability
Abusive1000.01
Clean9,9000.99
Total10,0001.00

An abusive signup is one of 100 rows in this population, so its probability is a fraction:

P(abusive)=10010000=0.01P(\text{abusive}) = \frac{100}{10000} = 0.01P(abusive)=10000100​=0.01

Read the result in plain language: before the detector says anything, 1 percent of signups are abusive.

That starting probability is the prior: the abuse rate before learning whether this signup was flagged. The posterior will be the probability after we observe that evidence.

The sample space, written Ω\OmegaΩ, is the set of possible outcomes of our random pick: all 10,000 rows. An event is a subset of those outcomes. Let AAA be the 100 abusive rows and BBB the rows the detector flags. Probability assigns each event a value from 0 to 1; the whole sample space has probability 1.[3]Reference 3Deep Learning.https://www.deeplearningbook.org/

For this example, frequencies are exact probabilities for a random pick from the reference population. Using them for future traffic adds an assumption: that traffic is sufficiently similar. Estimate its prior from a representative labeled sample, not just reviewed tickets. A queue selected by the detector overrepresents flagged signups and won't generally reveal the abuse rate among all signups.

Now reduce each selected signup to one bit: let Y = 1 when it's abusive and Y = 0 when it's clean. This is a random variable, a number whose value depends on which case you picked.

Its expectation is the probability-weighted average of its possible values. A 1 contributes with probability 0.01 and a 0 with probability 0.99:

E[Y]=1×0.01+0×0.99=0.01E[Y] = 1 \times 0.01 + 0 \times 0.99 = 0.01E[Y]=1×0.01+0×0.99=0.01

That 0.01 is the base rate written as an average instead of a fraction. A 0/1 variable like this is a Bernoulli variable, and its expectation is the probability of seeing the 1 outcome.

The average doesn't tell you what one pick looks like. Variance measures the average squared distance from the expectation, Var[Y] = E[(Y - E[Y])^2].

Here the 100 ones sit 0.99 above the mean, and the 9,900 zeros sit 0.01 below it. Weight their squared distances by their frequencies:

Var[Y]=0.01(1−0.01)2+0.99(0−0.01)2=0.0099\text{Var}[Y] = 0.01(1 - 0.01)^2 + 0.99(0 - 0.01)^2 = 0.0099Var[Y]=0.01(1−0.01)2+0.99(0−0.01)2=0.0099

For any Bernoulli variable with success probability p, this simplifies to p(1 - p). Here, variance describes the spread of individual 0/1 outcomes around the 1 percent average.

It isn't the same as uncertainty in an estimated rate or variation across product slices and repeated training runs. Later statistics chapters separate estimation noise from true distribution shifts.

The rows make this concrete: encode labels as 0s and 1s, and their mean must equal P(abusive)P(\text{abusive})P(abusive). Before running the snippet, predict its last two lines from the prior and Bernoulli variance.

indicator-mean-is-probability.py
1abuse_indicator = [1] * 100 + [0] * 9_900 2 3prior = sum(abuse_indicator) / len(abuse_indicator) 4variance = sum((value - prior) ** 2 for value in abuse_indicator) / len(abuse_indicator) 5 6print(f"signups: {len(abuse_indicator):,}") 7print(f"prior = E[Y]: {prior:.4f}") 8print(f"Var[Y]: {variance:.4f}") 9 10assert prior == 0.01 11assert abs(variance - prior * (1 - prior)) < 1e-12
Indicator summary
1signups: 10,000 2prior = E[Y]: 0.0100 3Var[Y]: 0.0099

Evidence changes the question

Now let the detector provide evidence. Before looking at the posterior, predict what each rate is counting: the abusive pile, or the clean pile?

If the signup is...Detector behaviorProbability
Abusiveflags it0.95
Cleanfalsely flags it0.05

These are class-conditional rates. The true-positive rate, also called recall, asks: among abusive signups, what fraction are flagged?

The false-positive rate asks the parallel question for clean signups: what fraction are flagged anyway?

Once a flag arrives, product has to ask a different question:

Given that this signup was flagged, how likely is it abusive?

Notation makes the two questions visible:

NotationPlain English
P(flagged∣abusive)P(\text{flagged} \mid \text{abusive})P(flagged∣abusive)If a signup is abusive, how often does the detector flag it?
P(abusive∣flagged)P(\text{abusive} \mid \text{flagged})P(abusive∣flagged)If a signup is flagged, how often is it abusive?

Those lines aren't interchangeable. The first counts inside the abusive pile; the second counts inside the flagged pile.

Read the vertical bar as "given." What appears to its right determines which rows you count inside.

The detector catches 95 percent of abusive signups. Does that mean a flagged signup is 95 percent likely to be abusive?

Answer

No. "Catches 95 percent of abusive signups" is P(flagged∣abusive)P(\text{flagged} \mid \text{abusive})P(flagged∣abusive). The product question after a flag is P(abusive∣flagged)P(\text{abusive} \mid \text{flagged})P(abusive∣flagged). Those conditionals count inside different piles.

Count the flagged pile

Hold off on Bayes rule. Build the review queue directly from the two base piles.

Abusive signups:

100×0.95=95100 \times 0.95 = 95100×0.95=95

Clean signups:

9900×0.05=4959900 \times 0.05 = 4959900×0.05=495

Now put the flagged signups into one pile.

Source of flagged signupCount
abusive and flagged95
clean and flagged495
all flagged signups590

Pause before dividing: which source should dominate the flagged queue? Five percent sounds small, but it acts on 9,900 clean signups. That produces more rows than 95 percent of the 100 abusive signups.

Now divide the abusive rows by the full flagged pile:

P(abusive∣flagged)=95590≈0.161P(\text{abusive} \mid \text{flagged}) = \frac{95}{590} \approx 0.161P(abusive∣flagged)=59095​≈0.161

A flagged signup is about 16 percent likely to be abusive, not 95 percent likely. Recall measured the detector inside abusive rows; posterior risk measures the queue after the detector filters everyone.

Make the accounting executable. This small program prints both source piles before it prints the posterior, so each denominator stays visible.

count-the-flagged-pile.py
1total_signups = 10_000 2abuse_signups = 100 3clean_signups = total_signups - abuse_signups 4true_positive_rate = 0.95 5false_positive_rate = 0.05 6 7true_flags = round(abuse_signups * true_positive_rate) 8false_flags = round(clean_signups * false_positive_rate) 9flagged_signups = true_flags + false_flags 10posterior = true_flags / flagged_signups 11 12print(f"true flags: {true_flags}") 13print(f"false flags: {false_flags}") 14print(f"all flags: {flagged_signups}") 15print(f"P(abuse | flagged): {posterior:.3f}") 16 17assert (true_flags, false_flags, flagged_signups) == (95, 495, 590)
Flagged-pile counts
1true flags: 95 2false flags: 495 3all flags: 590 4P(abuse | flagged): 0.161

Why does a 5 percent false-positive rate create 495 false alarms but the 95 percent true-positive rate creates only 95 true flags?

Answer

The rates apply to different base piles. Five percent applies to 9,900 clean signups, giving 495 false alarms. Ninety-five percent applies to only 100 abusive signups, giving 95 true flags. Base rates decide which pile dominates.

The table fixes both the population and its detector outcomes, so its posterior is exact. If 0.95 and 0.05 instead describe chances for new signups, the realized counts will vary. The table's counts then describe expectations, not promises.

Simulate 100,000 independent new signups to see that variation. rng.random() draws a number between 0 and 1, so comparing it with 0.01 produces abuse about 1 percent of the time. A second draw applies the appropriate flag probability. The fixed seed makes this run reproducible.

simulate-flagged-queue.py
1import random 2 3rng = random.Random(12) 4total_signups = 100_000 5abusive_count = 0 6true_flags = 0 7all_flags = 0 8 9for _ in range(total_signups): 10 abusive = rng.random() < 0.01 11 flag_probability = 0.95 if abusive else 0.05 12 flagged = rng.random() < flag_probability 13 if abusive: 14 abusive_count += 1 15 if flagged: 16 all_flags += 1 17 if abusive: 18 true_flags += 1 19 20simulated_posterior = true_flags / all_flags 21 22print(f"abusive signups: {abusive_count}") 23print(f"true flags: {true_flags}") 24print(f"all flags: {all_flags}") 25print(f"simulated posterior: {simulated_posterior:.1%}") 26print(f"counted posterior: {95 / 590:.1%}") 27 28assert abs(simulated_posterior - 95 / 590) < 0.02
Monte Carlo posterior
1abusive signups: 983 2true flags: 933 3all flags: 5823 4simulated posterior: 16.0% 5counted posterior: 16.1%

Conditioning means narrowing the world

Conditional probability is a filter followed by a count. It changes which rows are eligible before you divide.

For P(abusive∣flagged)P(\text{abusive} \mid \text{flagged})P(abusive∣flagged), first keep only flagged rows. The denominator isn't all 10,000 signups; it's the 590 signups that survived the filter.

ProbabilityWorld you count insideNumerator
P(abusive)P(\text{abusive})P(abusive)all 10,000 signups100 abusive signups
P(abusive∣flagged)P(\text{abusive} \mid \text{flagged})P(abusive∣flagged)590 flagged signups95 abusive flagged signups

Ask "among which cases?" before you divide. Conditioning throws away the 9,410 passed signups. Those 95 abusive flags sit inside 590 flagged rows, not inside 10,000 signups.

Bayes theorem area breakdown: out of 10,000 signups, the 1 percent base rate yields 95 true abuse flags and 495 false alarms. Conditioning on a flag narrows the denominator to 590, making the posterior 16.1 percent.
Bayes' theorem area breakdown: the 590 flagged signups form the new denominator. A 5 percent false-alarm rate over 9,900 clean signups produces 495 false flags, overwhelming the 95 true flags from the 1 percent abuse base rate.

Joint and marginal probability

Once the filter is visible, name the other views of the same rows. A joint probability is the chance that two events happen together. In this population, 95 signups are both abusive and flagged, so:

P(abusive and flagged)=9510000=0.0095P(\text{abusive and flagged}) = \frac{95}{10000} = 0.0095P(abusive and flagged)=1000095​=0.0095

A marginal probability is the chance of one event without splitting by the other. To get the marginal flag rate, add every row that can produce a flag:

P(flagged)=95+49510000=59010000=0.059P(\text{flagged}) = \frac{95 + 495}{10000} = \frac{590}{10000} = 0.059P(flagged)=1000095+495​=10000590​=0.059

These views are linked by one identity. When P(B)>0P(B) > 0P(B)>0, conditional probability is the joint probability divided by the probability of the world you conditioned on:

P(A∣B)=P(A and B)P(B)P(A \mid B) = \frac{P(A \text{ and } B)}{P(B)}P(A∣B)=P(B)P(A and B)​

Read in the other direction, the same identity gives the multiplication rule: a joint probability is a conditional probability times the probability of its conditioning event.

P(A and B)=P(A∣B) P(B)=P(B∣A) P(A)P(A \text{ and } B) = P(A \mid B)\,P(B) = P(B \mid A)\,P(A)P(A and B)=P(A∣B)P(B)=P(B∣A)P(A)

Check it against the counts: P(abusive∣flagged)=0.0095/0.059≈0.161P(\text{abusive} \mid \text{flagged}) = 0.0095 / 0.059 \approx 0.161P(abusive∣flagged)=0.0095/0.059≈0.161, the same posterior as before.

Bayes rule after the counts

The count is clear now, so give it a compact name. Bayes' theorem, also called Bayes rule, is the formula version of the flagged-pile count.

For events AAA and BBB with P(B)>0P(B) > 0P(B)>0:

P(A∣B)=P(B∣A)P(A)P(B)P(A \mid B) = \frac{P(B \mid A)P(A)}{P(B)}P(A∣B)=P(B)P(B∣A)P(A)​

For this example:

SymbolMeaningValue
AAAsignup is abusive
BBBdetector flagged the signup
P(A)P(A)P(A)abuse base rate0.01
P(B∣A)P(B \mid A)P(B∣A)true-positive rate0.95
P(B∣not A)P(B \mid \text{not }A)P(B∣not A)false-positive rate0.05

When BBB is the observed evidence, P(B∣A)P(B \mid A)P(B∣A) is its likelihood under hypothesis AAA. It asks how compatible a flag would be with the hypothesis that the signup is abusive. Bayes rule combines that compatibility with the prior to obtain the posterior.

The denominator P(B)P(B)P(B) is the marginal probability of a flag: how often a flag happens at all. It must include both source piles, weighted by how common abusive and clean signups are:

P(B)=P(B∣A)P(A)+P(B∣not A)P(not A)P(B) = P(B \mid A)P(A) + P(B \mid \text{not }A)P(\text{not }A)P(B)=P(B∣A)P(A)+P(B∣not A)P(not A)

Before substituting, predict what belongs in the denominator: only true flags, or every flag? Both source piles belong there:

P(B)=0.95×0.01+0.05×0.99=0.059P(B) = 0.95 \times 0.01 + 0.05 \times 0.99 = 0.059P(B)=0.95×0.01+0.05×0.99=0.059

With that denominator in hand, compute the posterior:

P(A∣B)=0.95×0.010.059≈0.161P(A \mid B) = \frac{0.95 \times 0.01}{0.059} \approx 0.161P(A∣B)=0.0590.95×0.01​≈0.161

Same answer as the count table. Bayes rule is the compact form of the same accounting: track where the flagged signups came from before you divide.

Likelihood compares explanations for observed labels

Probability starts with a candidate rate and asks what outcomes it could produce.

Likelihood starts with labels already observed and compares candidate rates as explanations for those labels. The expression can look identical because the quantity being varied is different.[1]Reference 1Machine Learning: A Probabilistic Perspective.https://probml.github.io/pml-book/book0.html[2]Reference 2Pattern Recognition and Machine Learning.https://www.microsoft.com/en-us/research/publication/pattern-recognition-machine-learning/

Suppose reviewers label three flagged signups [abuse, clean, abuse]. Assume the labels are independent, so knowing one label doesn't change the probabilities for the others, and all share candidate abuse rate ppp. Here ppp describes the flagged queue, not all signups.

Try p=0.80p = 0.80p=0.80. The probability of this ordered sequence is 0.80×0.20×0.80=0.1280.80 \times 0.20 \times 0.80 = 0.1280.80×0.20×0.80=0.128. Holding the labels fixed and varying ppp gives the likelihood:

L(p)=p×(1−p)×p=p2(1−p)L(p) = p \times (1 - p) \times p = p^2(1-p)L(p)=p×(1−p)×p=p2(1−p)
Candidate rate pppLikelihood L(p)L(p)L(p)
0.200.032
0.500.125
0.800.128

Of these three candidates, 0.80 has the largest likelihood, just ahead of 0.50. It isn't the best value over every possible ppp: the maximum occurs at the observed fraction, p=2/3p = 2/3p=2/3. The table also doesn't say there's a 12.8 percent chance that p=0.80p = 0.80p=0.80. Likelihood isn't a probability distribution over candidate rates.

With many labels, multiplying likelihood terms or summing their log values leads to maximum-likelihood training, which Statistics and Uncertainty names explicitly.

Independence means no update

If a flag appears at the same rate in abusive and clean signups, predict whether it should update your belief. It shouldn't: equal rates carry no information about the event.

When P(B)>0P(B) > 0P(B)>0, seeing BBB leaves the probability of AAA unchanged if the events are independent:

P(A∣B)=P(A)P(A \mid B) = P(A)P(A∣B)=P(A)

Start with a broken abuse detector:

If the signup is...Broken detector flags it
Abusive20 percent
Clean20 percent

Out of 10,000 signups, this detector produces:

Source of flagged signupCount
abusive and flagged20
clean and flagged1,980
all flagged signups2,000

The flagged pile is still 1 percent abusive:

202000=0.01\frac{20}{2000} = 0.01200020​=0.01

Independence holds here because P(A and B)=20/10,000=0.002P(A \text{ and } B) = 20 / 10{,}000 = 0.002P(A and B)=20/10,000=0.002 equals P(A)P(B)=0.01×0.20P(A)P(B) = 0.01 \times 0.20P(A)P(B)=0.01×0.20. Independent events can still happen together; mutually exclusive events can't.

This detector creates review work without changing the queue's abuse rate.

Make that prediction executable: when both classes are flagged at 20 percent, expect the posterior to equal the prior and the update to be zero.

independence-means-no-update.py
1def posterior_if_flagged(prior, true_positive_rate, false_positive_rate): 2 true_flags = true_positive_rate * prior 3 false_flags = false_positive_rate * (1 - prior) 4 return true_flags / (true_flags + false_flags) 5 6prior = 0.01 7posterior = posterior_if_flagged(prior, 0.20, 0.20) 8 9print(f"prior: {prior:.3f}") 10print(f"posterior: {posterior:.3f}") 11print(f"update: {posterior - prior:+.3f}") 12 13assert abs(posterior - prior) < 1e-12
Independent evidence
1prior: 0.010 2posterior: 0.010 3update: +0.000

A detector flags 20 percent of abusive signups and 20 percent of clean signups. What should happen to your belief after seeing a flag?

Answer

It should stay at the prior. The flag appears at the same rate for both groups, so it doesn't distinguish abuse from clean signups. The evidence is independent of the event.

Same detector, different world

Freeze detector performance first:

Detector propertyValue
true-positive rate0.95
false-positive rate0.05

Change only the population. Before reading the posterior column, predict which world makes a flag most trustworthy.

Abuse base ratePosterior after flagWhat changed?
1 percentabout 16 percentclean signups dominate the flagged pile
10 percentabout 68 percenttrue flags become a much larger share
50 percentabout 95 percentboth classes are equally common before evidence

Detector performance stayed fixed; the traffic mix changed. The same detector produces a 16 percent posterior in a 1 percent-abuse world and a 95 percent posterior when abuse is already half the traffic.

That shift can happen across traffic sources or time periods. Holding both detector rates fixed isolates the effect of the base rate; real traffic shifts can change all three rates. Remeasure them before carrying an old queue policy forward.

The detector's true-positive and false-positive rates stay fixed, but abuse base rate rises from 1 percent to 10 percent. Why does the posterior after a flag rise?

Answer

The true-flag pile gets much larger because abusive signups are more common. The false-positive rate didn't change, but it now applies to a smaller clean share of the population. More of the flagged pile is made of true abuse.

A posterior function that matches the table

Turn that accounting into a reusable function. Its flow follows the table: validate each probability, form the true-flag and false-flag masses, then divide true flags by all flags.

Before running the sweep, predict its shape. The detector stays fixed while the prior moves from 1 percent to 50 percent, so the posterior should rise.

Put this in probability_demo.py; it rejects invalid probabilities and zero-probability evidence so undefined decisions surface early.

probability_demo.py
1def check_probability(x, name): 2 if not 0 <= x <= 1: 3 raise ValueError(f"{name} must be between 0 and 1") 4 5def flagged_posterior(prior, true_positive, false_positive): 6 check_probability(prior, "prior") 7 check_probability(true_positive, "true_positive") 8 check_probability(false_positive, "false_positive") 9 10 true_flags = true_positive * prior 11 false_flags = false_positive * (1 - prior) 12 all_flags = true_flags + false_flags 13 14 if all_flags == 0: 15 raise ValueError("evidence probability must be greater than 0") 16 17 return true_flags / all_flags 18 19def main(): 20 priors = [0.01, 0.10, 0.50] 21 22 for prior in priors: 23 posterior = flagged_posterior(prior, 0.95, 0.05) 24 print(prior, round(posterior, 3)) 25 26 try: 27 flagged_posterior(1.4, 0.95, 0.05) 28 except ValueError as error: 29 print(error) 30 31 try: 32 flagged_posterior(0.01, 0.0, 0.0) 33 except ValueError as error: 34 print(error) 35 36if __name__ == "__main__": 37 main()
Posterior sweep
10.01 0.161 20.1 0.679 30.5 0.95 4prior must be between 0 and 1 5evidence probability must be greater than 0

The invalid prior is rejected instead of silently clipped. The final call represents a detector that never flags anyone. Conditioning on its flag is undefined because the denominator is zero; returning the prior would invent an answer.

A threshold changes two probabilities at once

A detector can emit a score rather than just a flag. A threshold turns that score into review evidence: flag when the score exceeds the threshold. On the same scored dataset, raising the threshold can only remove signups from the queue. Recall and false-positive rate can't increase, but precision needn't improve at every threshold.

Use this illustrative measurement table for the same 1 percent abuse population. In a real system, measure both rates on labeled data rather than treating them as permanent detector properties.

Threshold policyP(flagged∣abuse)P(\text{flagged} \mid \text{abuse})P(flagged∣abuse)P(flagged∣clean)P(\text{flagged} \mid \text{clean})P(flagged∣clean)
broad review0.950.05
stricter review0.800.01

At 10,000 signups, broad review catches 95 abusive signups and falsely flags 495 clean ones. Strict review catches 80 and falsely flags 99, producing a queue of 179. Its abuse fraction is 80/179≈44.7%80/179 \approx 44.7\%80/179≈44.7%.

Run both policies through the same accounting. Review rate is the fraction of all signups flagged; precision is the fraction of that queue that is abusive. This helper assumes its three inputs are valid probabilities.

threshold-tradeoff.py
1def review_metrics(prior, recall, false_positive_rate): 2 true_flags = recall * prior 3 false_flags = false_positive_rate * (1 - prior) 4 review_rate = true_flags + false_flags 5 if review_rate == 0: 6 raise ValueError("review rate must be greater than 0") 7 precision = true_flags / review_rate 8 return review_rate, precision 9 10prior = 0.01 11policies = [ 12 ("broad review", 0.95, 0.05), 13 ("stricter review", 0.80, 0.01), 14] 15 16for name, recall, false_positive_rate in policies: 17 review_rate, precision = review_metrics(prior, recall, false_positive_rate) 18 print( 19 f"{name:16} review={review_rate:6.2%} " 20 f"abuse_in_queue={precision:6.2%} recall={recall:6.2%}" 21 ) 22 23try: 24 review_metrics(prior, recall=0.0, false_positive_rate=0.0) 25except ValueError as error: 26 print("empty queue:", error)
Threshold tradeoff
1broad review review= 5.90% abuse_in_queue=16.10% recall=95.00% 2stricter review review= 1.79% abuse_in_queue=44.69% recall=80.00% 3empty queue: review rate must be greater than 0

The stricter policy sends fewer signups to review and raises the abuse fraction in that queue, which is precision. It catches fewer abusive signups, so recall falls.

That is the precision-recall tradeoff shown by the output.

Probability exposes the options; product cost and safety policy choose between them. A reviewer team may value queue cleanliness, coverage, or both.

A threshold can also send no signups to review. Then queue precision is undefined because no flagged pile exists, so code should reject or explicitly represent the empty queue instead of dividing by zero.

A score isn't automatically a probability

A model may emit a score such as 0.80 instead of a thresholded flag. Before treating it as risk, ask: among similarly scored signups, what fraction is actually abusive?

A model is calibrated for this event and population when cases assigned a given probability have a matching event frequency. For scores near 0.80, we'd expect an abuse fraction near 80 percent across sufficiently many comparable signups. This is a group-level property, not a guarantee about any one signup.

Guo et al. found that then-standard image classifiers could achieve high classification accuracy while their confidence estimates were poorly calibrated.

Measure the match; a number in [0,1][0, 1][0,1] isn't enough to establish calibrated risk.[4]Reference 4On Calibration of Modern Neural Networkshttps://arxiv.org/abs/1706.04599

Inspect one small score bucket. Its six labels contain three abusive signups. Compare that observed fraction with the average score, then express the difference in percentage points.

check-one-score-bucket.py
1predicted_risk = [0.80, 0.82, 0.78, 0.81, 0.79, 0.80] 2observed_abuse = [1, 0, 1, 0, 1, 0] 3 4advertised_risk = sum(predicted_risk) / len(predicted_risk) 5observed_rate = sum(observed_abuse) / len(observed_abuse) 6gap = advertised_risk - observed_rate 7 8print(f"average predicted risk: {advertised_risk:.0%}") 9print(f"observed abuse rate: {observed_rate:.0%}") 10print(f"empirical gap: {100 * gap:.0f} percentage points")
Calibration bucket
1average predicted risk: 80% 2observed abuse rate: 50% 3empirical gap: 30 percentage points

Six signups can't establish how a deployed model is calibrated. They do show what to measure: predicted risk against observed abuse in a comparable bucket.

Statistics and Uncertainty teaches how much evidence you need before trusting the measured gap.

Other quantities need other distributions

The abuse label was a Bernoulli 0/1 because one signup either is or isn't abusive. Other ML quantities can take different values, so they need different sets of possible values.

A probability distribution describes how probability is spread over those values; choosing one is a modeling assumption, not a fact that the data proves.[1]Reference 1Machine Learning: A Probabilistic Perspective.https://probml.github.io/pml-book/book0.html[2]Reference 2Pattern Recognition and Machine Learning.https://www.microsoft.com/en-us/research/publication/pattern-recognition-machine-learning/

DistributionQuantity it describesPossible valuesTypical ML example
Bernoullione yes/no outcome0 or 1one signup is abusive
Categoricalone choice among several labelsone of K classesallow, review, or block
Binomialnumber of successes in n independent Bernoulli trials with the same p0 through nabusive signups in a fixed batch
Poissonevent count in a fixed interval under independent arrivals at a constant rate0, 1, 2, ...abuse alerts in one minute
Normal (Gaussian)continuous measurement around a meanany real valuemeasurement error around a target

Use the possible values as a first sanity check.

For Bernoulli, E[Y] = p and Var[Y] = p(1 - p). Categorical class probabilities are nonnegative and sum to one.

For a Binomial count, the mean and variance are np and np(1 - p). For 100 independent signups with p = 0.01, the expected abuse count is 1 and its variance is 0.99. "Expected count 1" doesn't mean every batch contains exactly one abusive signup.[5]Reference 5NIST/SEMATECH e-Handbook: Binomial Distributionhttps://www.itl.nist.gov/div898/handbook/eda/section3/eda366i.htm

For a Poisson count, the mean and variance both equal λ\lambdaλ, the expected number of arrivals in the chosen interval. At an average of two alerts per minute, a five-minute count has λ=10\lambda = 10λ=10, provided the process assumptions hold. Bursty, coordinated abuse may violate those assumptions.[6]Reference 6NIST/SEMATECH e-Handbook: Poisson Distributionhttps://www.itl.nist.gov/div898/handbook/eda/section3/eda366j.htm

A Normal variable isn't bounded to [0, 1], so it isn't a default model for probabilities. Distributions and Sampling later lets you simulate these families and test whether their assumptions fit observed data.

Modeling bridge: The same choice appears in language models. Next-token prediction is categorical: one token out of a vocabulary. Training then cares about the probability assigned to the observed token.

A token model multiplies probabilities

For one observed target token, training rewards probability assigned to that token.

The observed token is the target class. A one-hot target assigns 1 to that class and 0 to every other vocabulary entry. Its cross-entropy contribution is negative log probability, -log(p), where p is the model's probability for the target. Python's math.log uses the natural logarithm, so these losses are measured in nats.[3]Reference 3Deep Learning.https://www.deeplearningbook.org/

Predict the ordering before running the snippet: the observed token with probability 0.01 should incur the largest loss.

negative-log-probability.py
1import math 2 3for target_probability in [0.90, 0.50, 0.01]: 4 loss = -math.log(target_probability) 5 print(f"target probability={target_probability:>4.2f} loss={loss:>5.3f}")
Token surprise
1target probability=0.90 loss=0.105 2target probability=0.50 loss=0.693 3target probability=0.01 loss=4.605

Lower probability costs more because the observed token was more surprising under the model.

For a sequence, extend the same idea one token at a time. The model's joint probability follows the chain rule: multiply the conditional probability of each next token given its preceding context.

P(t1,t2,…,tn)=P(t1)∏i=2nP(ti∣t1,…,ti−1)P(t_1, t_2, \ldots, t_n) = P(t_1) \prod_{i=2}^{n} P(t_i \mid t_1, \ldots, t_{i-1})P(t1​,t2​,…,tn​)=P(t1​)i=2∏n​P(ti​∣t1​,…,ti−1​)

Here, tit_iti​ is the token at position iii, and ∏\prod∏ means multiply the terms for positions 2 through nnn. Each term conditions on the preceding tokens; the chain rule doesn't assume tokens are independent. If there's a prompt, every term also conditions on that prompt.

Chain rule of probability for autoregressive language models: next-token factorization decomposes sequence joint probability into sequential conditional probabilities, transitioning from multiplicative underflow to additive log-likelihood for stable cross-entropy training.
The chain rule of probability factorizes joint sequence likelihood into sequential next-token conditionals. Converting conditional probabilities to log space turns multiplication into numerical stable addition for training loss.

For three token probabilities, 0.5×0.2×0.1=0.010.5 \times 0.2 \times 0.1 = 0.010.5×0.2×0.1=0.01. Their log probabilities add to the log of that product. With long sequences, summing logs also avoids underflow from multiplying tiny numbers.

Make the numeric failure visible by multiplying 200 token probabilities in Python:

keep-sequence-probabilities-in-log-space.py
1import math 2 3token_probability = 0.01 4token_count = 200 5 6raw_product = token_probability ** token_count 7log_probability = token_count * math.log(token_probability) 8 9print(f"raw product in float: {raw_product}") 10print(f"log probability: {log_probability:.1f}") 11print(f"finite in log space: {math.isfinite(log_probability)}")
Log-space sequence probability
1raw product in float: 0.0 2log probability: -921.0 3finite in log space: True

The mathematical probability is 10−40010^{-400}10−400, not zero. It's too small to represent as a nonzero value in Python's usual binary64 floating-point format, so this calculation underflows to 0.0.[7]Reference 7Floating-Point Arithmetic: Issues and Limitationshttps://docs.python.org/3/tutorial/floatingpoint.html

Keep the finite log probability instead. Converting it back with math.exp would underflow again. A genuinely zero token probability is different: its mathematical log is negative infinity, and math.log(0) raises ValueError. Later language-modeling chapters use log-space calculations for cross-entropy and perplexity.

Common mistakes

Most probability bugs begin before the formula. Someone changed the event, population, or denominator without saying so.

SymptomMistakeBetter move
"The flag means 95 percent abusive."reversed the conditional probabilitieswrite both questions in plain English
Posterior feels too highignored rare base ratestart from counts before formulas
Denominator is all signupsforgot conditioningdenominator should be the evidence pile
One threshold used everywhereignored population shiftrecompute base rates per product slice
Model confidence treated as truthevent never nameddefine the event and compare to labels
Code returns a number for impossible evidencedivided by zero-probability evidencereject undefined cases loudly
A score of 0.80 is used as 80 percent risk without checking labelscalibration was assumedbucket predictions and compare score with observed rate
A long sequence gets probability 0.0 in codesmall probabilities were multiplied directlysum log probabilities instead

When a dashboard number looks implausible, ask one question:

Among which cases am I counting?

Once that answer is explicit, the denominator usually stops being mysterious.

Try it yourself

Use the same 10,000-signup population, with 100 abusive signups. Try the counts first, then use code to check your arithmetic.

  1. A signup isn't flagged by the original detector. How many abusive and clean signups are in that group? Compute its abuse probability.
  2. An improved detector keeps 95 percent recall but lowers the false-positive rate to 1 percent. Compute its review count and posterior. Why isn't this the same scenario as raising the original detector's threshold?
  3. Reviewers can handle 200 of these 10,000 signups. Compare broad review, strict review, and the improved detector. Which queues fit, and how many abusive signups does each miss? Treat these as the worked counts, not a guarantee of tomorrow's workload.
  4. Someone proposes using the review queue's abuse rate as next month's prior for all signups. Explain the selection error and suggest a better sampling plan.
  5. Compute -log(0.80) and -log(0.10). Then call math.log(0). Explain how a genuinely zero probability differs from a nonzero sequence probability that underflows.

Solution checks

Compare your denominators as well as your answers.

Practice itemAnswer
Not flagged5 abusive and 9,405 clean signups. Risk is 5/9410≈0.053%5/9410 \approx 0.053\%5/9410≈0.053%, not zero.
Improved detector95 true flags plus 99 false flags gives 194 reviews and 95/194≈49.0%95/194 \approx 49.0\%95/194≈49.0% abuse. Recall stayed fixed; strict review in the earlier table lowered it to 80 percent.
Capacity and missed abuseBroad: 590 reviews, 5 misses, over capacity. Strict: 179 reviews, 20 misses, fits. Improved: 194 reviews, 5 misses, fits.
Prior estimateThe queue conditions on a flag. Sample and label signups independently of that flag, including unflagged cases, to estimate the overall base rate. Check whether the sample represents next month's traffic.
Token probabilitiesLosses are about 0.223 and 2.303 nats. math.log(0) raises ValueError; a true zero has no finite log probability. Underflow can hide a nonzero mathematical probability whose log remains finite.

A complete probability claim

For this reference population, a complete claim is:

Of the 590 signups flagged by broad review, 95 are abusive: 16.1 percent. Strict review raises that fraction to 44.7 percent, but misses 20 of the 100 abusive signups instead of 5.

The probabilities describe two queues. They don't choose a policy for you. That decision also needs review capacity, the cost of false alarms, and the harm of missed abuse.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.In 10,000 signups, 100 are abusive. A detector flags 95% of abusive signups and 5% of clean signups. What is P(abusive | flagged)?

Correct answer: About 16.1%, using 95 true flags out of 590 total flags.

The detector produces 95 true flags and 0.05 times 9,900, or 495, false flags. Conditioning on a flag narrows the denominator to 590 flagged signups, so the posterior is 95 / 590, about 16.1%. The 95% rate answers the reversed question P(flagged | abusive).

2.A detector flags 20% of abusive signups and 20% of clean signups. If the abuse prior is 1%, what is P(abusive | flagged)?

Correct answer: 1%, because equal flag rates leave the prior unchanged.

The flag is equally likely in both classes, so it doesn't distinguish abuse from clean signups. In 10,000 signups, 20 of 100 abuse cases and 1,980 of 9,900 clean cases are flagged. The resulting posterior is 20 / 2,000, or 1%, exactly the prior.

3.At a 1% abuse prior, a broad threshold has 95% recall and a 5% false-positive rate. A strict threshold has 80% recall and a 1% false-positive rate. What changes?

Correct answer: Broad catches more abuse; strict sends a smaller, cleaner queue.

Broad review retains 95% recall, while strict review retains only 80%, so broad review catches more abuse. The lower false-positive rate under strict review reduces review volume and raises the abuse fraction in the queue from about 16.1% to about 44.7%.

4.A model multiplies 200 token probabilities of 0.01, and the floating-point product is 0.0 while the summed log probability is finite. What should the evaluator do?

Correct answer: Keep the log probability; the raw product underflowed.

Every token probability is nonzero, so the mathematical sequence probability is also nonzero. The direct product is merely smaller than the floating-point format can represent. Summing log probabilities implements the same chain-rule product without losing the value to underflow.

5.Six signups have scores averaging 80%, but only 3 of the 6 are abusive. What does this bucket check show?

Correct answer: Predicted risk is 80% versus 50% observed, a 30-point gap.

Calibration compares predicted probabilities with observed outcome rates among similarly scored cases. This bucket advertises about 80% risk but has a 50% observed abuse rate. Six cases are too few to establish deployment-wide calibration, but the mismatch is the empirical quantity that calibration analysis must investigate.

6.Three reviewed signups have labels [abuse, clean, abuse]. Which candidate rate has the largest likelihood L(p) = p^2(1 - p)?

Correct answer: p = 0.80, with likelihood 0.128.

For [1, 0, 1], L(p) = p times (1 - p) times p. The three values are 0.128 at p = 0.80, 0.125 at p = 0.50, and 0.032 at p = 0.20. Likelihood compares candidate explanations for fixed labels; it isn't a probability distribution over p.

7.For the abuse indicator Y with p = 0.01, which statement is correct?

Correct answer: Y is Bernoulli with mean 0.01 and variance 0.0099; it can take only 0 or 1.

A single 0/1 outcome is Bernoulli. Its expectation is p = 0.01 and its variance is p(1 - p) = 0.0099. Counting successes across n independent Bernoulli trials with the same p gives a Binomial random variable.

8.Reviewers label only flagged signups and find 16.1% abuse. Can that fraction estimate the abuse prior among all signups?

Correct answer: No, because selection by a flag changes the population being measured.

The reviewed fraction estimates P(abuse | flagged), not P(abuse). More labels from the same selected queue don't remove that selection bias. A representative sample of all signups, including unflagged cases, supports an estimate of the overall prior.

9.A flagged-posterior function is called with (prior=1.4, true_positive=0.95, false_positive=0.05) and with (prior=0.01, true_positive=0, false_positive=0). What should its contract require?

Correct answer: Reject both: prior 1.4 is invalid, and zero flag probability makes the posterior undefined.

The first call violates the requirement that every probability lie between 0 and 1. In the second call, no signup can be flagged, so P(flagged) is zero and P(abusive | flagged) is undefined. The function should reject both cases rather than clip an input or invent a posterior.

10.An observed target token has probability 0.10 in one prediction and 0.80 in another. Which prediction has the larger negative log probability?

Correct answer: The 0.10 prediction; its loss is about 2.303 versus 0.223.

For an observed target, the contribution is -log(p). Because the logarithm increases with p, negating it makes the loss larger when p is smaller. Assigning only 0.10 probability to what occurred is therefore more surprising than assigning 0.80.

10 questions remaining.

Next Step
Continue to Bayesian Inference from Evidence

You can now count the flagged pile and explain why its 16.1 percent abuse rate differs from detector recall. Bayesian inference turns that same prior, likelihood, and evidence into a reusable update rule for uncertain model and product decisions.

PreviousAdam, Momentum, Schedulers
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Machine Learning: A Probabilistic Perspective.

Murphy, K. P. · 2012

https://probml.github.io/pml-book/book0.html

Pattern Recognition and Machine Learning.

Bishop, C. M. · 2006

https://www.microsoft.com/en-us/research/publication/pattern-recognition-machine-learning/

Deep Learning.

Goodfellow, I., Bengio, Y., Courville, A. · 2016

https://www.deeplearningbook.org/

On Calibration of Modern Neural Networks

Guo, C., Pleiss, G., Sun, Y., & Weinberger, K. Q. · 2017

https://arxiv.org/abs/1706.04599

NIST/SEMATECH e-Handbook: Binomial Distribution

NIST/SEMATECH · 2012

https://www.itl.nist.gov/div898/handbook/eda/section3/eda366i.htm

NIST/SEMATECH e-Handbook: Poisson Distribution

NIST/SEMATECH · 2012

https://www.itl.nist.gov/div898/handbook/eda/section3/eda366j.htm

Floating-Point Arithmetic: Issues and Limitations

Python Software Foundation · 2026

https://docs.python.org/3/tutorial/floatingpoint.html

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.