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][2]
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 type | Count | Probability |
|---|---|---|
| Abusive | 100 | 0.01 |
| Clean | 9,900 | 0.99 |
| Total | 10,000 | 1.00 |
An abusive signup is one of 100 rows in this population, so its probability is a fraction:
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 , is the set of possible outcomes of our random pick: all 10,000 rows. An event is a subset of those outcomes. Let be the 100 abusive rows and the rows the detector flags. Probability assigns each event a value from 0 to 1; the whole sample space has probability 1.[3]
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:
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:
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 . Before running the snippet, predict its last two lines from the prior and Bernoulli variance.
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-121signups: 10,000
2prior = E[Y]: 0.0100
3Var[Y]: 0.0099Evidence 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 behavior | Probability |
|---|---|---|
| Abusive | flags it | 0.95 |
| Clean | falsely flags it | 0.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:
| Notation | Plain English |
|---|---|
| If a signup is abusive, how often does the detector flag it? | |
| 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 . The product question after a flag is . 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:
Clean signups:
Now put the flagged signups into one pile.
| Source of flagged signup | Count |
|---|---|
| abusive and flagged | 95 |
| clean and flagged | 495 |
| all flagged signups | 590 |
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:
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.
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)1true flags: 95
2false flags: 495
3all flags: 590
4P(abuse | flagged): 0.161Why 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.
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.021abusive 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 , first keep only flagged rows. The denominator isn't all 10,000 signups; it's the 590 signups that survived the filter.
| Probability | World you count inside | Numerator |
|---|---|---|
| all 10,000 signups | 100 abusive signups | |
| 590 flagged signups | 95 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.

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:
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:
These views are linked by one identity. When , conditional probability is the joint probability divided by the probability of the world you conditioned on:
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.
Check it against the counts: , 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 and with :
For this example:
| Symbol | Meaning | Value |
|---|---|---|
| signup is abusive | ||
| detector flagged the signup | ||
| abuse base rate | 0.01 | |
| true-positive rate | 0.95 | |
| false-positive rate | 0.05 |
When is the observed evidence, is its likelihood under hypothesis . 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 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:
Before substituting, predict what belongs in the denominator: only true flags, or every flag? Both source piles belong there:
With that denominator in hand, compute the posterior:
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][2]
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 . Here describes the flagged queue, not all signups.
Try . The probability of this ordered sequence is . Holding the labels fixed and varying gives the likelihood:
| Candidate rate | Likelihood |
|---|---|
| 0.20 | 0.032 |
| 0.50 | 0.125 |
| 0.80 | 0.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 : the maximum occurs at the observed fraction, . The table also doesn't say there's a 12.8 percent chance that . 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 , seeing leaves the probability of unchanged if the events are independent:
Start with a broken abuse detector:
| If the signup is... | Broken detector flags it |
|---|---|
| Abusive | 20 percent |
| Clean | 20 percent |
Out of 10,000 signups, this detector produces:
| Source of flagged signup | Count |
|---|---|
| abusive and flagged | 20 |
| clean and flagged | 1,980 |
| all flagged signups | 2,000 |
The flagged pile is still 1 percent abusive:
Independence holds here because equals . 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.
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-121prior: 0.010
2posterior: 0.010
3update: +0.000A 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 property | Value |
|---|---|
| true-positive rate | 0.95 |
| false-positive rate | 0.05 |
Change only the population. Before reading the posterior column, predict which world makes a flag most trustworthy.
| Abuse base rate | Posterior after flag | What changed? |
|---|---|---|
| 1 percent | about 16 percent | clean signups dominate the flagged pile |
| 10 percent | about 68 percent | true flags become a much larger share |
| 50 percent | about 95 percent | both 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.
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()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 0The 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 policy | ||
|---|---|---|
| broad review | 0.95 | 0.05 |
| stricter review | 0.80 | 0.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 .
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.
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)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 0The 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 isn't enough to establish calibrated risk.[4]
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.
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")1average predicted risk: 80%
2observed abuse rate: 50%
3empirical gap: 30 percentage pointsSix 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][2]
| Distribution | Quantity it describes | Possible values | Typical ML example |
|---|---|---|---|
| Bernoulli | one yes/no outcome | 0 or 1 | one signup is abusive |
| Categorical | one choice among several labels | one of K classes | allow, review, or block |
| Binomial | number of successes in n independent Bernoulli trials with the same p | 0 through n | abusive signups in a fixed batch |
| Poisson | event count in a fixed interval under independent arrivals at a constant rate | 0, 1, 2, ... | abuse alerts in one minute |
| Normal (Gaussian) | continuous measurement around a mean | any real value | measurement 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]
For a Poisson count, the mean and variance both equal , the expected number of arrivals in the chosen interval. At an average of two alerts per minute, a five-minute count has , provided the process assumptions hold. Bursty, coordinated abuse may violate those assumptions.[6]
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]
Predict the ordering before running the snippet: the observed token with probability 0.01 should incur the largest loss.
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}")1target probability=0.90 loss=0.105
2target probability=0.50 loss=0.693
3target probability=0.01 loss=4.605Lower 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.
Here, is the token at position , and means multiply the terms for positions 2 through . 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.

For three token probabilities, . 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:
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)}")1raw product in float: 0.0
2log probability: -921.0
3finite in log space: TrueThe mathematical probability is , 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]
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.
| Symptom | Mistake | Better move |
|---|---|---|
| "The flag means 95 percent abusive." | reversed the conditional probabilities | write both questions in plain English |
| Posterior feels too high | ignored rare base rate | start from counts before formulas |
| Denominator is all signups | forgot conditioning | denominator should be the evidence pile |
| One threshold used everywhere | ignored population shift | recompute base rates per product slice |
| Model confidence treated as truth | event never named | define the event and compare to labels |
| Code returns a number for impossible evidence | divided by zero-probability evidence | reject undefined cases loudly |
A score of 0.80 is used as 80 percent risk without checking labels | calibration was assumed | bucket predictions and compare score with observed rate |
A long sequence gets probability 0.0 in code | small probabilities were multiplied directly | sum 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.
- A signup isn't flagged by the original detector. How many abusive and clean signups are in that group? Compute its abuse probability.
- 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?
- 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.
- 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.
- Compute
-log(0.80)and-log(0.10). Then callmath.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 item | Answer |
|---|---|
| Not flagged | 5 abusive and 9,405 clean signups. Risk is , not zero. |
| Improved detector | 95 true flags plus 99 false flags gives 194 reviews and abuse. Recall stayed fixed; strict review in the earlier table lowered it to 80 percent. |
| Capacity and missed abuse | Broad: 590 reviews, 5 misses, over capacity. Strict: 179 reviews, 20 misses, fits. Improved: 194 reviews, 5 misses, fits. |
| Prior estimate | The 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 probabilities | Losses 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.