learn-data-centric-ai-with-phoebe / Session 2 of 6
Learn Data-Centric AI with Phoebe · Session 2 of 6

When labellers disagree

Before any row can be called wrong, somebody has to say what right is. Give the same image to two careful people and they will sometimes hand back two different labels, not because either is careless, but because the instruction they were given leaves room. Tonight we measure that room: raw agreement, agreement beyond chance, where it concentrates by class, and the uncomfortable fact that two labellers who agree can be wrong together. Then we compare the two ways out, paying for more labels per row or rewriting the rule, on 800 real digits with three constructed labellers.

🟠 Advanced ML engineers, labelling leads, data scientists Python, numpy 45 min
0-3 · Welcome 3-20 · Consistency, agreement, consensus 20-42 · Build-along: three labellers, one vote 42-45 · Q&A
Part 0

Where we start

Session 1 froze a model and a test set and planted declared noise in 800 training labels. That noise came from one imaginary labeller. Real labelling teams have several people, a written guideline, and a queue of rows somebody argued about. Tonight the question moves one step upstream of "which rows are wrong": how consistent is the labelling in the first place, and when two people disagree, is the fix another person or a better rule?

🎫 Clear all three quiz questions to stamp your passport Live = presented in the 45 minutes Self-study = the full depth, after class ★ Build-along = run the code yourself

★ What you walk out with tonight. Ng's two-labeller protocol, Cohen's kappa computed by hand, a per-class agreement table that points at the rule to rewrite, the proof that agreement is not correctness (670 rows agreed, 68 wrong together), and a costed comparison of majority vote against a guideline rewrite on the same frozen model.

Part 1 · covers label consistency

Two careful people, two answers

5 min live

Ng's 2021 talk spends several slides on one idea: much of what looks like label noise is not error at all, it is inconsistency. Three transcriptions of the same audio clip read "Um, today's weather", "Um... today's weather" and "Today's weather". None of them is wrong. The instruction never said what to do with a hesitation. A model trained on all three learns that the answer is a coin toss, and no amount of model work removes a coin toss that lives in the labels.

ONE IMAGE, TWO CAREFUL LABELLERS, TWO ANSWERS, ONE RULE TO FIX row 28, real true digit 7 Labeller A constructed, rule v1 Labeller B constructed, rule v1 1 7 diff 1 or 7? Guideline v2 1/7: a 7 has a top bar, a 1 is a single stroke relabel that class pair relabel the disputed classes, measure again Ng's protocol: two independent labellers on a sample, measure where they disagree, revise the rule, repeat.
🔍 Click to zoom - the disagreement is logged against the guideline, not against a person; the image is real, the labellers are constructed
LiveThe protocol, in Ng's own words2 min▶

Ng's slide on making data quality systematic is three sentences long, and every word is doing work: "Ask two independent labelers to label a sample of images. Measure consistency between labelers to discover where they disagree. For classes where the labelers disagree, revise the labeling instructions until they become consistent."

Notice what it does not say. It does not say "find the bad labeller" or "take the majority". The unit of repair is the instruction, and the unit of measurement is the class. A sample is enough, because the goal is to find which parts of the rule are ambiguous, not to relabel the whole set twice. The iguana example on the same deck shows how little it takes: the instruction "Use bounding boxes to indicate the position of iguanas" produces boxes that differ from labeller to labeller, and every one of them follows the instruction.

LiveInconsistent is not the same as wrong2 min▶

An error is a label that contradicts the rule: a 3 labelled 8 when the guideline is clear that it is a 3. An inconsistency is two labels that each satisfy the rule, because the rule does not decide the case. You cannot call "Um... today's weather" wrong until someone has written down whether hesitations are transcribed and how. Before that, it is one of several defensible answers.

This matters for what you do next. Errors are fixed row by row, and session 3 builds a detector that finds them. Inconsistencies are fixed once, in the guideline, and then every affected row is relabelled against the new rule. Running an error detector on an inconsistent dataset flags rows whose label is "wrong" only relative to the model's guess at what the rule should have been.

Self-studyWhy this is a spec problem, not a people problem3 min▶

When two labellers disagree the tempting move is to rank them, retrain the weaker one or replace them. That treats the symptom. If two careful people read the same instruction and diverge, the instruction admits both readings, and a third careful person will diverge too, in a direction you cannot predict. Replacing people does not remove the ambiguity; it only changes which reading wins this month.

Treat the guideline the way an engineering team treats an API contract. It has a version number. Every class has a definition, a list of edge cases with a ruling for each, and examples of both sides of every boundary. A disagreement is a bug report against the contract. The fix is a new version, and rows labelled under the old version are marked so they can be rechecked. Session 6 writes that version into the data-edit spec, so the guideline outlives the team that wrote it.

People problems do exist. The careless labeller C in tonight's build-along is one. But you can only tell a careless person from an ambiguous rule once the rule is clear enough that careful people agree on it, which is why the rule comes first. The disputed rows themselves usually come out of error analysis, which ML Strategy p3 ↗ covers.

Real world

Steel class 23 of 39. Ng's steel inspection project has 39 defect classes, and the slide he uses for label consistency picks one: "Class 23: Foreign particle defect". It is one class in 39, and the slide uses it to show labels for the same kind of defect coming back inconsistent. The point of picking one class is the point of the protocol: the repair is not a better detector for class 23 but a sharper definition of what class 23 is, followed by relabelling against it.

Part 2 · covers measuring agreement

Agreement beyond percent the same

6 min live

The first number anyone computes is raw agreement: the share of rows where the two labels match. It is the right place to start and the wrong place to stop, because two people guessing at random would still agree some of the time. Cohen's kappa, from 1960, subtracts that chance agreement out: kappa = (p_o - p_e) / (1 - p_e), where p_o is the observed agreement and p_e is the agreement you would expect if each labeller kept their own label frequencies but assigned them independently of the image. Kappa of 1 is perfect agreement, 0 is no better than chance.

B AGREES WITH A, BY A'S LABEL constructed labellers on the real 800 images · rust hatching = a look-alike digit the guideline leaves open 0 1 2 3 4 5 6 7 8 9 100% 71% · 1 vs 7 100% 71% · 3 vs 8 67% · 4 vs 9 100% 100% 76% · 7 vs 1 77% · 8 vs 3 72% · 9 vs 4 A and B agree on 670 rows 68 wrong together same rule, same slips share of A's label that B matched, 0 to 100 percent Kappa for the pair is 0.819 on 83.8% raw agreement; every disagreement sits in the six rust digits.
🔍 Click to zoom - the per-class table tells you which rule to rewrite; the box on the right is why a high kappa still is not a quality score

Tonight's numbers, from the build-along on the real 800 training images with constructed labellers. A is session 1's declared plant. B works from the same guideline and makes the same look-alike confusions, drawn independently. C is careless: a fifth of C's labels are replaced by a random digit.

PairRaw agreement p_oChance p_eCohen's kappa
A and B (same guideline, same confusions)83.8%10.1%0.819
A and C (C is careless)67.6%10.0%0.640

With ten nearly balanced classes, chance agreement is only about 10 percent, so kappa sits close to raw agreement. That is not always true, and the self-study card below says when it breaks.

LiveWhy kappa is not a quality score3 min▶

Look at the pair with the higher kappa. A and B agree on 670 of the 800 rows, and 68 of those 670 are wrong together: both labellers wrote the same wrong digit. That is not bad luck. They share a guideline, so they share its blind spots; a 7 without a clear top bar looks like a 1 to both of them for the same reason. Kappa compares the labellers with each other and never with the truth, so a shared confusion raises it.

So kappa measures consistency, and consistency is the thing Ng's protocol asks for, which makes it the right tool for finding ambiguous classes. It is the wrong tool for certifying a dataset. A high kappa on a class tells you the rule is being read the same way; it does not tell you the rule is right. For that you need either a ground truth sample checked by someone who knows the domain, or a detector that looks at the images, which is session 3.

The per-digit table points at where to start. Every disagreement between A and B falls on one of the three look-alike pairs:

A's label0123456789
B agrees100%71%100%71%67%100%100%76%77%72%
Self-studyKappa's own known limits3 min▶

Three limits are standard in the measurement literature, and each one shows up in real labelling projects.

Prevalence. Chance agreement p_e is computed from each labeller's label frequencies. When one class dominates, say most rows are "no defect", both labellers use that label most of the time, so p_e is high, and even a high raw agreement can produce a modest kappa. The same labelling quality gives different kappas on datasets with different class balance, so a kappa from one project does not transfer to another.

Few classes. With two classes, chance agreement is 50 percent when both labellers use the two labels equally often, and higher when one label dominates, so the gap between raw agreement and kappa is large. With ten balanced classes, as tonight, it is small. Compare kappas only between tasks with the same number of classes and similar balance.

It is one number for all classes. A kappa of 0.8 can hide one class at perfect agreement and another near chance. Always report the per-class table alongside it. Cohen's kappa is also defined for exactly two labellers; for more, extensions such as Fleiss' kappa exist, with the same caveats. Published verbal scales ("substantial", "almost perfect") are conventions, not measurements, so set your own threshold per class from what a disagreement costs.

Real world

Systematic, not random. When Northcutt, Athalye and Mueller had crowd workers check the label errors in ten benchmark test sets, they found the errors were "primarily systematic mislabeling, not just random noise". Systematic errors are exactly the kind two labellers share, which is why a benchmark can have good inter-annotator agreement and still carry thousands of wrong labels.

Part 3 · covers consensus labelling

Three labels, one answer

6 min live

When each row carries more than one label, something has to turn them into one. The default is majority vote, and the MIT data-centric AI notes on dataset creation name its two flaws in two lines: "Resolving ties is ambiguous in majority vote", and "A bad annotator and good annotator have equal impact". Everything more sophisticated exists to fix one of those two sentences.

THREE WAYS OUT OF A DISAGREEMENT, AND WHAT EACH COSTS PER ROW 800 rows, labeller A 133 wrong labels model: 457 of 500 More labellers: A, B, C vote cost: 3 labels on every row, 2,400 in all Better guideline: the 1/7 rule cost: one rewrite, recheck 1s and 7s Adjudicate the disagreements cost: an expert on the 130 A-B splits 483 of 500 82 wrong labels left 472 of 500 48 rows fixed, constructed not computed misses the 68 wrong together The vote scores highest but pays three labels on every row; the rule fixes one pair for one rewrite.
🔍 Click to zoom - a vote buys accuracy by the row, a rule buys it by the class; adjudication only reaches rows where people already disagree
LiveMajority vote, and the two sentences that break it2 min▶

Tonight's three constructed labellers vote on each of the 800 rows. The majority label is right on 89.8 percent of them, and it leaves 82 wrong labels against 133 for A alone. Trained on the vote, the frozen model scores 483 of 500, against 457 on A's labels. That is a real gain, and it costs three labels on every row.

Both flaws show up in the same run. On 25 rows all three labellers wrote a different digit, a three-way split with no majority; the build-along falls back to A's label and says so, which is a choice, not a rule. And C, who guesses on a fifth of rows, gets exactly the same vote as A and B. Where A and B share a confusion, C's occasional correct answer is outvoted two to one, which is how the 68 rows wrong together survive the vote.

LiveWeighting annotators: Dawid-Skene and CROWDLAB2 min▶

The MIT notes describe the step beyond the vote. First, estimate each annotator's quality as their agreement with the consensus, then let better annotators count for more. The classic model for this is Dawid-Skene, which learns a full confusion matrix per annotator: not just "C is wrong 20 percent of the time" but "B writes 1 when the truth is 7". The notes also give its limit: it cannot give a reliable consensus for rows labelled by only one annotator, which in practice is most rows of most datasets.

CROWDLAB closes that gap by adding a model. It combines any classifier's predicted probabilities with the annotations, so a singly labelled row still gets a second opinion from the model, and multiply labelled rows get the annotators weighted by how reliable they have proved. Neither is computed here; both are named so you recognise them when a vendor quotes them. Note what neither can do: if every annotator shares the same confusion, weighting them differently does not recover the truth.

Self-studyRewrite the rule, or add a labeller?3 min▶

The build-along runs a constructed experiment: suppose a revised rule, "a 7 has a top bar, a 1 is a single stroke", ends the 1 and 7 confusion and nothing else. It fixes 48 rows of A's labels, and the frozen model goes from 457 to 472. The vote got to 483, higher, but it paid for two more complete passes over all 800 rows. The rule fixed one of three confused pairs with one rewrite and a recheck of the 1s and 7s; rules for 3/8 and 4/9 are the obvious next edits. The effect of the rule is constructed, the images and the model scores are real.

Sheng, Provost and Ipeirotis (2008), "Get Another Label?", study when repeated labelling improves label quality and model quality. As reported from the paper's abstract, the answer is that it pays under conditions rather than always; read the paper before budgeting a second or third pass. The order that follows from tonight is Ng's: measure agreement on a sample, rewrite the rule where it breaks, and pay for extra labels only on the classes that stay inconsistent after the rewrite.

Real world

Five workers, three must agree. The labelerrors.com study used consensus as a verification step rather than a labelling step. Each item flagged by the algorithm was shown to five Mechanical Turk workers, and it counted as a label error if fewer than 3 of the 5 agreed with the given label. Consensus was spent only on the rows a detector had already flagged, which is where it buys the most, and the authors still note the corrected labels "are not 100% perfect".

What to ask for, if you lead the team. Before paying for a second labelling pass, ask for two things: the agreement matrix per class from two independent labellers on a sample, and the version number of the guideline they worked from. If the disagreement sits in a few classes, the next spend is a rule rewrite for those classes, not another pass over every row.

Build-along

Three labellers, one vote, one better rule

★ 22 min · everyone builds

Six steps in numpy on the same 800 real training images as session 1. The three labellers are constructed and declared, so every accuracy figure can be checked against the true digit. Every number printed below is what the code produces.

FOUR REAL ROWS OF THE 800, THREE CONSTRUCTED LABELLERS, ONE VOTE real image true A B C vote what happened row 5 7 1 1 7 1 A and B wrong together; C was right and got outvoted row 25 1 7 7 1 7 the same confusion the other way; shared confusions survive the vote row 24 4 9 4 4 4 the vote fixes A B and C each got it right on their own row 28 7 1 7 6 1 three-way split, no majority; falls back to A, which is wrong Over all 800: the vote is 89.8% correct, 82 wrong against 133 for A alone, with 25 three-way splits.
🔍 Click to zoom - rows 5, 25, 24 and 28 are real images drawn from their own 64 pixels; the labels are the ones s2.py prints for them

Three labellers on the same 800 images. This continues from session 1's steps 1 to 4, which define X, y, train, test, PLANT, centre, g_train and score; run those first. Labeller A is session 1's declared plant. B reads the same guideline and makes the same look-alike confusions, drawn with its own seed. C is careless: a fifth of C's labels are replaced by a random digit. All three are constructed; the images are real.

★ Step 1 · three labellerslab_A = g_train rng_B = np.random.default_rng(2) def plant_with(r, ids): given = y[ids].copy() for (a, b), rate in PLANT.items(): cand = np.array([i for i, t in enumerate(ids) if y[t] == a]) m = int(round(rate * len(cand))) look = [((X[ids[i]] - centre[b]) ** 2).sum() - ((X[ids[i]] - centre[a]) ** 2).sum() for i in cand] top = cand[np.argsort(look, kind="stable")][: int(np.ceil(2 * m))] given[r.choice(top, m, replace=False)] = b return given lab_B = plant_with(rng_B, train) rng_C = np.random.default_rng(3) lab_C = y[train].copy() careless = rng_C.random(800) < 0.20 lab_C[careless] = rng_C.integers(0, 10, careless.sum()) truth = y[train] for name, L in (("A", lab_A), ("B", lab_B), ("C", lab_C)): print(name, "accuracy against the true digit:", f"{100*(L == truth).mean():.1f}%") # A accuracy against the true digit: 83.4% # B accuracy against the true digit: 83.4% # C accuracy against the true digit: 80.5%

Raw agreement, chance agreement and kappa. Chance agreement is the sum over digits of the product of the two labellers' label frequencies: how often they would match if each kept their own habits but ignored the image. Kappa is the share of the remaining room that they actually closed.

★ Step 2 · agreement and kappadef kappa(p, q): po = (p == q).mean() pe = sum((p == k).mean() * (q == k).mean() for k in range(10)) return po, pe, (po - pe) / (1 - pe) po, pe, k = kappa(lab_A, lab_B) print(f"A-B agreement {100*po:.1f}%, chance {100*pe:.1f}%, kappa {k:.3f}") po, pe, k = kappa(lab_A, lab_C) print(f"A-C agreement {100*po:.1f}%, chance {100*pe:.1f}%, kappa {k:.3f}") # A-B agreement 83.8%, chance 10.1%, kappa 0.819 # A-C agreement 67.6%, chance 10.0%, kappa 0.640

Find where they disagree, class by class. Group the rows by A's label and ask how often B wrote the same thing. This is the table Ng's protocol runs on: the classes below 100 percent are the ones whose rule needs rewriting.

★ Step 3 · per-digit agreementfor d in range(10): m = lab_A == d print(d, f"{100*(lab_B[m] == d).mean():.0f}%", end=" ") print() # 0 100% 1 71% 2 100% 3 71% 4 67% 5 100% 6 100% 7 76% 8 77% 9 72%

Check agreement against the truth. This step is only possible because the labellers are constructed and the true digits are known. On your own data you would need a checked sample. Count the rows where A and B agree, and how many of those they got wrong together.

★ Step 4 · agreement is not correctnessboth = lab_A == lab_B print("A and B agree on", int(both.sum()), "rows; of those,", int((both & (lab_A != truth)).sum()), "are wrong together") # A and B agree on 670 rows; of those, 68 are wrong together

Take the majority vote, and handle the ties out loud. Where all three labellers wrote a different digit there is no majority. The code falls back to A and counts how often that happens, so the choice is visible in the output instead of buried in an argmax.

★ Step 5 · majority votevotes = np.stack([lab_A, lab_B, lab_C], axis=1) maj, ties = [], 0 for row in votes: c = np.bincount(row, minlength=10) if c.max() == 1: ties += 1 maj.append(row[0]) # no majority: fall back to A, and say so else: maj.append(int(c.argmax())) maj = np.array(maj) print("majority vote accuracy:", f"{100*(maj == truth).mean():.1f}%", "| three-way splits:", ties) print("majority vote wrong labels:", int((maj != truth).sum()), "| A alone:", int((lab_A != truth).sum())) # majority vote accuracy: 89.8% | three-way splits: 25 # majority vote wrong labels: 82 | A alone: 133

Rewrite the rule for the worst pair, then score everything on the frozen model. The rewrite is a constructed effect: we suppose the new rule "a 7 has a top bar, a 1 is a single stroke" ends the 1 and 7 confusion and touches nothing else. Then the same frozen kNN from session 1 is trained on three label sets and scored on the same 500 untouched test digits.

★ Step 6 · the guideline rewrite and the model scoresfixed = lab_A.copy() pair = ((truth == 1) & (lab_A == 7)) | ((truth == 7) & (lab_A == 1)) fixed[pair] = truth[pair] print("1/7 rows fixed by the new rule:", int(pair.sum())) print("model on A:", score(X[train], lab_A), "| on A with the 1/7 rule:", score(X[train], fixed), "| on majority vote:", score(X[train], maj), "of 500") # 1/7 rows fixed by the new rule: 48 # model on A: 457 | on A with the 1/7 rule: 472 | on majority vote: 483 of 500
Same frozen model, same 500 test digitsLabels per rowWrong training labelsCorrect
Labeller A alone (the declared plant)1133457
A with the 1/7 rule rewritten (constructed effect)1, plus a recheck of one pair85472
Majority vote of A, B and C382483

Read the middle row as a first step, not a final answer. One rule for one of three confused pairs bought 15 test digits for a fraction of the labelling cost the vote needed for 26. The same rewrite for 3/8 and 4/9 is the next edit, and the vote still carries the 68 rows A and B got wrong together, which only a rule change or a detector can reach. On one seed a difference of a digit or two is noise, one test digit being 0.2 points; the gaps here are large enough to read, but not to rank the two routes to the decimal.

Homework

Before session 3

30 min
  1. Compute kappa for two real people. Take 50 rows of your own data, have two colleagues label them independently from the current guideline, and run step 2's kappa function on their two columns. Write down raw agreement, chance agreement and kappa.
  2. Find the class with the lowest agreement using step 3, and write its rule: a definition, the edge cases with a ruling for each, and one example on each side of every boundary it shares with a neighbour class.
  3. Price the two routes. Estimate the cost of a third label on every row against the cost of the rule rewrite plus a recheck of the affected classes. Which would you fund first, and what agreement number would make you change your mind?
  4. Bring your team's flagged rows list. Session 3 finds wrong labels with a detector; a list of rows your team already suspects is the best way to see what it catches and what it misses.
Check yourself

Three questions

Clear all three and session 2 gets stamped in your passport.

1 · Labellers A and B have a Cohen's kappa of 0.819, yet 68 of the 670 rows they agree on are wrong together. What does kappa measure?

Kappa is a consistency measure, which is exactly what Ng's protocol needs for finding ambiguous classes. It cannot certify correctness: two labellers trained on the same guideline share its blind spots, and their shared wrong labels count as agreement.

2 · Three labellers vote, and one of them guesses at random on a fifth of the rows. Why do methods such as Dawid-Skene and CROWDLAB weight annotators instead of counting votes?

The MIT notes name both flaws: ties are ambiguous and every annotator counts the same. Tonight's vote had 25 three-way splits and fell back to A on each. Weighting fixes the equal-impact flaw, but no weighting recovers the truth when every annotator shares the same confusion.

3 · Your two labellers agree above 95 percent on every class except two look-alike classes, where they sit near 70 percent. Following Ng's protocol, what is the next move?

Ng's slide: "For classes where the labelers disagree, revise the labeling instructions until they become consistent." Disagreement concentrated in a few classes is a spec problem. Tonight the rule rewrite for one pair bought 457 to 472 with one rewrite; the vote bought 483 but cost three labels on every row.

Sources covered

What this session teaches, and where it came from

✓Ng, "MLOps: From Model-centric to Data-centric AI" (2021)The speech transcription and iguana examples, steel class 23 of 39, and the two-labeller protocol, read from the slide deck
✓MIT, Introduction to Data-Centric AI, dataset creation and curationMajority vote and its two flaws, annotator quality as agreement with consensus, Dawid-Skene and CROWDLAB, read from the lecture notes
✓Northcutt, Athalye, Mueller (2021), label errors in test setsFive MTurk workers per flagged item, an error if fewer than 3 of 5 agree, and errors that are mostly systematic, read from the NeurIPS paper
✓Cohen (1960), a coefficient of agreement for nominal scalesThe standard kappa formula, (p_o - p_e) / (1 - p_e), computed by hand in step 2
◐Sheng, Provost, Ipeirotis (2008), "Get Another Label?"Reported: repeated labelling pays under conditions; known from the abstract, not read in full
◐The three labellers A, B and CConstructed on the real 800 UCI digit images; every agreement, vote and model score computed by s2.py

Every fact on this page, and its verification tier, is recorded in the course's source map.

Session 2 cheat sheet · pin this

The protocolTwo independent labellers on a sample, measure where they disagree, revise the rule for those classes.
Kappa(p_o - p_e) / (1 - p_e). Report it with the per-class table, never alone.
Agreement is not correctness670 agreed, 68 wrong together. A shared guideline shares its confusions.
Majority vote's two flawsTies have no principled answer; a bad and a good annotator count the same.
The 3-of-5 ruleFive workers per flagged item; an error if fewer than 3 agree with the given label.
Rule before peopleRewrite the guideline for the disputed classes before paying for another labelling pass.