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

Slice, augment or collect

Relabelling fixed the rows that were wrong. What is left is a different kind of problem: whole kinds of input the model handles worse than the rest, and a budget line that says "collect more data". Tonight we find the weak slice without looking at the test set, add rows that cover it by encoding an invariance, and then put the collection budget on trial against the cheaper edit of cleaning what you already have. Same frozen model, same locked 500 test digits, and one rule that runs through all three parts: decisions are made on training data, and the test set only ever reports.

🟠 Advanced ML engineers, data scientists, data leads Python, numpy 45 min
0-3 · Welcome 3-18 · Slice, augment, collect or clean 18-40 · Build-along: three routes, one frozen model 40-45 · Q&A
Part 0

Where we start

By the end of session 4 the bench had a clear winner for the first edit: run confident learning out of sample and relabel what it flags, 457 correct test digits up to 478. The labels are now mostly right. The model is still wrong on some digits more than others, and the next request on the table is usually a data-collection budget. Before anyone signs it, three questions: which slice is weak, will a cheap transform cover it, and would the new rows buy more than cleaning the old ones?

🎫 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. A way to find the weakest slice on training data alone (the 9s, at 57 of 71 cross-validated agreement), a label-preservation test for any augmentation, and a measured answer to "collect or clean": on these digits, cleaning the 800 you have scored 478, the same as collecting 497 more and cleaning all of it, while collecting without cleaning scored 454.

Part 1 · covers slicing

Find the weak slice without peeking

5 min live

An aggregate score is an average over slices, and averages hide their worst member. The MIT data-centric evaluation notes define a data slice as "a subset of the dataset that shares a common characteristic" and score it by the average per-example loss on held-out data inside the slice. The catch is which held-out data. If you pick the weak slice by reading test accuracy per digit, the test set has made a decision, and its number no longer measures that decision. So the slice is found on training rows, using the same out-of-fold votes that found the wrong labels in session 3.

THE WEAK SLICE, FOUND ON THE TRAINING ROWS ONLY 4-fold agreement per digit, relabelled 800, sorted weakest first 9 4 8 1 3 7 2 6 0 5 57 of 71, 80.3%, weakest 69 of 81, 85.2% 74 of 86, 86.0% 71 of 81, 87.7% 67 of 74, 90.5% 76 of 84, 90.5% 77 of 79, 97.5% 87 of 88, 98.9% 80 of 80, 100.0% 76 of 76, 100.0% 0 100% OUT-OF-FOLD NEIGHBOURS THAT AGREE WITH THE ROW'S LABEL THE WALL Locked test set 500 digits, 65 of them 9s opened after the choice Evaluation data computes scores, "not for any modeling decisions" MIT data-centric evaluation notes The choice uses only training folds. The test set is read once, after it, in build-along step 3.
🔍 Click to zoom - the 9s are the weakest slice by training evidence alone; the test set sits behind the wall until the edit is chosen
LiveA slice, and the leak of choosing it on test2 min▶

A slice is any subset that shares a characteristic you can name: one digit, one writer, one camera, one country, one customer tier. The MIT notes score it by the average loss of held-out examples inside it, which here becomes the share of each digit's training rows whose four-fold, out-of-sample neighbours agree with the label. The 9s come out at 57 of 71, 80.3 percent, then the 4s at 85.2, the 8s at 86.0 and the 1s at 87.7. Those are exactly the digits the declared plant confused, so the slice table is telling the truth about where the data is still thin or still muddled.

The tempting shortcut is to print per-digit test accuracy and pick the lowest. The MIT rule is blunt: evaluation examples "should only be used for computing evaluation scores, not for any modeling decisions." Choosing which slice to edit is a modelling decision. Make it on test, and the next test score is partly a record of your choice rather than a measurement of it; with 65 test 9s, a handful of lucky images can steer the choice and then flatter it. This is evaluation leakage into a decision, a cousin of the feature leakage in Feature Engineering ↗, not the same thing.

LiveHidden slices: sort by loss, cluster the top2 min▶

Digits are a slice you can name in advance. The ones that hurt in production are usually the ones nobody named: a scanner model, a lighting condition, a dialect. The MIT notes give a procedure for finding them: sort the held-out examples by loss, take the high-loss end, and cluster it. A cluster that keeps appearing is a candidate slice; you then look at its members and give it a name a person can act on.

Two cautions carry over from tonight's setup. The "held-out" examples must be out-of-fold training rows, not the locked test set, for the same reason as above. And a cluster of high-loss rows may be a cluster of wrong labels rather than a thin slice, which is why the relabel pass comes first. Reading a hundred of those rows by hand and tagging each one is error analysis, and its method lives in ML Strategy p3 ↗.

Self-studyMIT's four remedies, and which ones are yours3 min▶

Once a weak slice is found, the MIT notes list four remedies: use a more flexible model; over-sample or up-weight the minority subgroup; "Collect additional data from the subgroup of interest"; and engineer features that help the model tell the slice apart. The first is model-side, and here the model is frozen. The last is feature work, which changes what the model sees rather than which rows it learns from, and it belongs to Feature Engineering ↗.

That leaves the two data-side remedies, and the rest of tonight is about them. Over-sampling a slice with transformed copies is Part 2. Collecting new rows, from the slice or in general, is Part 3. Class imbalance, outliers and distribution shift have their own MIT lectures; they are named here and not taught.

Real world

An aggregate that hid a slice. Buolamwini and Gebru's 2018 "Gender Shades" audit of commercial gender classifiers found darker-skinned females misclassified at error rates of up to 34.7 percent, while the maximum error for lighter-skinned males was 0.8 percent. Each system would have looked fine on one overall accuracy number. The slice only appears when someone decides in advance which characteristics to cut by, and then scores each cut separately.

Part 2 · covers targeted augmentation

Augment for coverage, not for decoration

5 min live

Augmentation is a claim about the world written as code. The MIT human-priors notes call the claim an invariance, "a change to the input data that doesn't change its output", and say augmentation helps most "when collecting more data is hard or expensive or time consuming". Tonight's claim is small and checkable: a handwritten 9 moved one pixel in any direction is still a 9. Four shifted copies of every training 9 add 284 rows and take the frozen model from 478 to 480 correct. The honest reading of that +0.4 comes after the figure.

ONE REAL TRAINING 9, FOUR ONE-PIXEL SHIFTS, ONE LABEL label 9 label 9 label 9 label 9 label 9 training row 51 a true 9, given 9 right 1 px (dx, dy) = (1, 0) left 1 px (dx, dy) = (-1, 0) down 1 px (dx, dy) = (0, 1) up 1 px (dx, dy) = (0, -1) down and up push a row of ink off the grid At 8x8 one pixel is four pixels of the 32x32 scan, a coarse transform. The label must survive it.
🔍 Click to zoom - each copy is drawn from real pixels; the edit is only valid because a 9 moved one pixel is still a 9
LiveWhen augmentation is the right edit2 min▶

Augmentation fixes coverage. It helps when the slice is thin, when its real variation is something a transform can imitate, and when new real rows are slow or expensive to get. It does not fix labels: every copy inherits the label of its source. On this seed the shift of the 9s bought two test digits, 478 to 480, and the 9s themselves went from 56 to 59 correct of 65 when the test set was finally read. The overall total rose by only two while the 9s rose by three, so the other digits lost one between them: an extra slice's worth of rows moves the boundary for its neighbours too.

Then the honest dip. Over the twenty-seed sweep, relabel plus shift averages 94.52 against 94.45 for relabel alone: +0.07 points, about a third of one test digit. By the time the 9s are shifted, the main problem on this bench, wrong labels, has already been dealt with, and a one-pixel shift at 8 by 8 is a four-pixel shift of the original 32 by 32 scan, a coarse move that adds little new coverage. The +0.4 on one seed is two digits; the +0.07 is the claim.

LiveThe label-preservation test2 min▶

Before any transform ships, write the invariance as a sentence and try to break it. "A digit shifted by one pixel keeps its label" holds for every digit here. "A digit flipped left to right keeps its label" fails for 2, 3, 4, 5, 7 and 9. "A digit rotated 180 degrees keeps its label" turns a 6 into a 9. Mixup, in the MIT notes, is the case that proves the rule by breaking it on purpose: it blends two inputs and blends their labels in the same proportion, because the blended image no longer belongs to either class.

The test is one line per transform in the edit log: the invariance claimed, one input where it fails, and whether that input can occur in the slice you are augmenting. If you cannot name a failure case, you have not thought about the transform long enough.

Self-studyShift before cleaning, and every error is copied four times2 min▶

The order of edits matters. Tonight the shift runs on the relabelled 800, where the 9s are mostly real 9s. Run it on the given labels instead and the look-alike 4s that the declared plant labelled 9 would be shifted too, four copies each, every copy carrying the wrong label into a denser patch of the training set. Augmentation multiplies whatever the labels say. Clean first, then augment, and write that order into the spec in session 6.

The same logic applies to generative augmentation. A generator trained on noisy labels produces plausible new rows with the same confusions, only more of them.

Real world

Speech with car noise. Ng's iterative loop uses a speech system that does poorly on audio recorded in cars. His remedy is to "get more of that data via data augmentation, data generation or data collection (change inputs x)", or to "give more consistent definition for labels" if they were ambiguous (change labels y). One way to get more of that data is to mix clean speech with recorded road noise: augmentation with an invariance anyone can state: the words do not change when the engine is running. The transcript label survives the transform, which is exactly the test above.

Part 3 · covers collect or clean, and small data

Collect or clean

5 min live

Ng's slide on small data states a claim with no dataset attached: with 500 examples of which 12 percent are noisy, cleaning the noise and collecting another 500 are "about equally effective", and there is "significant room for improvement in problems with <10,000 examples". Treat it as a hypothesis. The bench can test a version of it, because the 497 pool rows come from the same declared plant: collect them raw, collect and clean them, or leave them and clean the 800 you already have.

SAME FROZEN MODEL, FIVE DATA ROUTES: ROWS USED AGAINST TEST ACCURACY TEST ACCURACY, 500 LOCKED DIGITS 90% 92% 94% 96% 600 800 1,000 1,200 1,400 TRAINING ROWS USED 800, drop 110 475 (95.0%) clean the 800: 478 (95.6%) 1,297, drop 143: 483 (96.6%) collect + clean 478 (95.6%) collect, raw 454 (90.8%) same score, 62% more rows same edit, opposite sign: hurts the 800, helps the 1,297 20 seeds: noisy collect +0.25 points; collect-and-clean 94.60 vs relabel 94.45. One digit = 0.2 points.
🔍 Click to zoom - cleaning the rows you have matched collecting and cleaning 62 percent more; dropping ambiguous rows only paid once there were rows to spare
LiveThe bench's answer, and the sweep's2 min▶

Three routes, printed by the build-along below. Collect the 497 pool rows and use their labels as given: 454 correct, 90.8 percent, below the 457 baseline. Collect them and run confident learning over all 1,297: 478, 95.6 percent. Leave the pool alone and clean the 800: 478, 95.6 percent. On this seed, cleaning the 800 matched collecting 62 percent more rows and cleaning all of them.

The twenty-seed sweep says the same thing with more authority than one seed can. More rows with the same noise average +0.25 points over the baseline, and are worse than it in 8 of the 20 seeds. Collect-and-clean averages 94.60 against 94.45 for relabelling the 800 alone, a gap of under one test digit. Ng's slide said cleaning and doubling were about equally effective; here, with systematic noise, collecting without cleaning bought roughly nothing, and collecting with cleaning bought roughly what cleaning alone did. That is one bench and one kind of noise, not a law. The sweep is what you would run on your own data before quoting either.

LiveSmall data: pruning costs more when data is scarce2 min▶

One more knob, from the session 4 bench: after relabelling, drop the ambiguous rows, the ones where no digit wins a majority of the ten out-of-fold neighbours. On the 800 that drops 110 rows and scores 475, 95.0 percent, worse than the 478 of relabelling alone; the twenty-seed mean agrees, 93.61 against 94.45. On the 1,297 it drops 143 rows and scores 483, 96.6 percent, the best score on the bench. The sweep did not run the 1,297 version, so read that 483 as one seed.

The mechanism is scarcity. Ambiguous rows sit on the boundary between digits, which is exactly where a nearest-neighbour model needs examples. With 800 rows, pruning 110 of them removes evidence the model cannot replace. With 1,297, there are enough clean boundary rows left that removing the muddled ones sharpens the boundary instead of thinning it. Ng's long-tail point is the same rule at scale: big-data problems with a long tail of rare events "are also small data problems" in the tail, and a pruning rule tuned on the head can starve the tail.

Self-studyWhat Ng's claim does and does not say2 min▶

The slide states the 500-example, 12-percent case as reasoning, and the curve on the next slide, clean against noisy labels from 250 to 1,500 examples, names no dataset. So it is a claim to test, not a result to cite. Two things the bench adds: the noise here is systematic, look-alike swaps drawn from the images that most resemble the other digit, which is the kind that more data reinforces rather than averages out; and the collection route only matches cleaning when the collected rows are cleaned too. If your noise is closer to random, new rows may do more than they did here. That is the reason to measure, not to assume.

Real world

A competition that allowed only data. The 2021 Data-Centric AI Competition asked entrants to "improve a dataset given a fixed model", with techniques "such as fixing incorrect labels, adding examples that represent edge cases, apply data augmentation". All three of tonight's routes were on the menu. Submissions also carried a cap on the number of images, reported at 10,000 from participant write-ups rather than read on an official page; under a cap like that, every augmented or added row spends budget that a relabel does not.

What to ask for, if you lead the team. Before approving a collection budget, ask two questions. Where do the new labels come from: the same vendor and the same guideline that produced the current noise? If so, the new rows carry the same confusions, and on this bench they bought roughly nothing. And what is the clean-first number: the frozen model's score after cleaning the rows you already own? If collecting and cleaning does not clearly beat cleaning alone, the budget is buying rows, not accuracy.

Build-along

Three routes, one frozen model

★ 22 min · everyone builds

Five steps in numpy, continuing from session 3's script: the digits, the split, the declared plant, the four-fold votes and the confident-learning relabel sug are all already in memory. Every # output line below is copied from the script's printed output. The test set is touched only by score and, once, in step 3, after the choice has been made.

THREE ROUTES, ONE FROZEN MODEL, ONE LOCKED TEST SET WHERE THE ROWS COME FROM THE EDIT OF 500 71 training 9s weakest by CV agreement 800 + 497 pool rows labels as given 800 + 497 pool rows labels as given four 1-px shifts +284 rows, label 9 no cleaning 1,297 rows, 219 wrong confident learning relabel all 1,297 Frozen model kNN, k = 10 480 96.0% 454 90.8% 478 95.6% Route one starts from the relabelled 800. Relabelling the 800 alone also scores 478 of 500.
🔍 Click to zoom - three routes, one model; the only route that bought nothing new was collecting without cleaning

Rank the digits on training evidence only. Continue from session 3's script, steps 1 to 5, so sug (the confident-learning relabel), folds and knn_votes are in memory. Then re-run the four folds on the relabelled labels and count, per digit, how many rows the out-of-fold neighbours agree with. Ties go to the lower digit, so the choice is deterministic.

★ Step 1 · per-digit agreement, never the test set# continues from session 3, steps 1 to 5 (sug, V, folds, Xtr, knn_votes, confident_joint, score) lab = sug.copy() Vk = np.zeros((800, 10), dtype=int) for f in range(4): hold = folds == f Vk[hold] = knn_votes(Xtr[~hold], lab[~hold], Xtr[hold]) agree = [(int((Vk[lab == d].argmax(1) == d).sum()), int((lab == d).sum())) for d in range(10)] for d, (a, t) in enumerate(agree): print(d, f"{a}/{t}", f"{100*a/t:.1f}%") weakest = min(range(10), key=lambda d: (agree[d][0] / agree[d][1], d)) print("weakest:", weakest) # 0 80/80 100.0% # 1 71/81 87.7% # 2 77/79 97.5% # 3 67/74 90.5% # 4 69/81 85.2% # 5 76/76 100.0% # 6 87/88 98.9% # 7 76/84 90.5% # 8 74/86 86.0% # 9 57/71 80.3% # weakest: 9

Shift every training 9 by one pixel, four ways. Right, left, down and up, with zeros filling the edge that moves in. Every copy keeps the label 9, which is only safe because the invariance holds and because step 1 ran on the relabelled rows.

★ Step 2 · four shifts of the weakest digitdef shift(img, dx, dy): a = img.reshape(8, 8) out = np.zeros_like(a) for r in range(8): for c in range(8): if 0 <= r - dy < 8 and 0 <= c - dx < 8: out[r, c] = a[r - dy, c - dx] return out.reshape(64) src = np.where(lab == weakest)[0] extra = np.array([shift(Xtr[i], dx, dy) for i in src for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1))]) X_aug = np.vstack([Xtr, extra]) g_aug = np.concatenate([lab, np.full(len(extra), weakest)]) print("added:", len(extra), "rows") print("relabel:", score(Xtr, lab), "| relabel + shifts:", score(X_aug, g_aug), "of 500") # added: 284 rows # relabel: 478 | relabel + shifts: 480 of 500

Now, and only now, read the slice on the test set. The choice is made and written down; the test set reports what it did to the 9s. Three more of the 65 test 9s are right. That is three images on one seed: a reading, not a result.

★ Step 3 · the slice on test, after the choicedef per_digit(Xa, ga): p = knn_predict(Xa, ga, X[test]) return [int(((p == d) & (y[test] == d)).sum()) for d in range(10)] print("test 9s:", per_digit(Xtr, lab)[9], "->", per_digit(X_aug, g_aug)[9], "of", int((y[test] == 9).sum())) # test 9s: 56 -> 59 of 65

Collect or clean. Give the 497 pool rows their own four folds, pool them with the 800, and run the same out-of-sample votes and confident joint over all 1,297. Then score three training sets on the frozen model: the collected rows as given, the collected rows cleaned, and the 800 cleaned with the pool left alone.

★ Step 4 · collect raw, collect and clean, clean the 800folds_pool = np.arange(len(pool)) % 4 rng.shuffle(folds_pool) X_all = np.vstack([Xtr, X[pool]]) g_all = np.concatenate([g_train, g_pool]) f_all = np.concatenate([folds, folds_pool]) V_all = np.zeros((len(g_all), 10), dtype=int) for f in range(4): hold = f_all == f V_all[hold] = knn_votes(X_all[~hold], g_all[~hold], X_all[hold]) _, sug_all = confident_joint(V_all, g_all) print("collect, raw:", score(X_all, g_all), "| collect and clean:", score(X_all, sug_all), "| clean the 800 only:", score(Xtr, lab)) # collect, raw: 454 | collect and clean: 478 | clean the 800 only: 478

Drop the ambiguous rows at two sizes. A row is ambiguous when no digit gets more than five of its ten out-of-fold votes. Drop those after relabelling, once on the 800 and once on the 1,297, and compare each with its own relabel-only score from step 4.

★ Step 5 · the same pruning rule, scarce and less scarceamb800 = V.max(1) <= 5 amb_all = V_all.max(1) <= 5 print("800 relabel, drop", int(amb800.sum()), "ambiguous:", score(Xtr[~amb800], lab[~amb800])) print("1297 relabel, drop", int(amb_all.sum()), "ambiguous:", score(X_all[~amb_all], sug_all[~amb_all])) # 800 relabel, drop 110 ambiguous: 475 # 1297 relabel, drop 143 ambiguous: 483
Same frozen model, same 500 test digitsTraining rowsCorrectAccuracy
Relabel the 800 (session 4's first edit)80047895.6%
Relabel, then shift the 9s four ways1,08448096.0%
Collect the 497, labels as given1,29745490.8%
Collect the 497, clean all 1,2971,29747895.6%
Relabel the 800, drop 110 ambiguous69047595.0%
Relabel the 1,297, drop 143 ambiguous1,15448396.6%

Read the table with the sweep beside it. The shift's +0.4 is two test digits on one seed and +0.07 on the twenty-seed mean. The clean-the-800 and collect-and-clean rows tie here and sit 0.15 points apart on the sweep. The one gap on this table that survives any reading is the third row: collecting without cleaning costs you the cleaning you skipped.

Homework

Before session 6

30 min
  1. Find your own weakest slice on training data only. Pick a characteristic you can name (a class, a source, a region), score each slice with out-of-fold predictions on the training set, and write down the weakest before you open any test or validation report.
  2. Write the invariance your augmentation assumes, as one sentence, and one real input where it fails. If you cannot find a failure, try harder: flips, rotations, crops and colour shifts all have one.
  3. Price collecting 500 more rows against cleaning. Put a cost and a lead time on each: new rows from your current labelling source, and a relabel pass over the flagged rows you already have. Then ask which one you have a clean-first number for.
  4. Bring your best knob combination from session 4 to the capstone, with its score and the twenty-seed caution in mind. Session 6 turns it into a spec.
Check yourself

Three questions

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

1 · Why is the weakest digit chosen by cross-validated agreement on the relabelled training set, and not by per-digit test accuracy?

Evaluation examples "should only be used for computing evaluation scores, not for any modeling decisions." Out-of-fold votes on training rows find the 9s at 80.3 percent, and the test set reads the result once, afterwards, as 56 to 59 of 65.

2 · Shifting the 9s bought +0.4 points on the shipped seed but +0.07 on the twenty-seed average. What does that say?

One test digit is 0.2 points, so a one-seed gain of two digits is not a claim. The sweep is. By the time the shift runs, relabelling has removed most of what was hurting the 9s, and a one-pixel move at 8 by 8 is a coarse transform that adds little the neighbours did not already cover.

3 · Dropping ambiguous rows after relabelling takes the 800 from 478 to 475, but the 1,297 up to 483. Why?

Same rule (no digit above five of ten votes), same model, different amounts of data around it. Pruning costs more when data is scarce. The 800 result agrees with the twenty-seed mean, 93.61 against 94.45; the 1,297 result is one seed and should be checked before it goes in a spec.

Sources covered

What this session teaches, and where it came from

✓MIT, Introduction to Data-Centric AI: data-centric evaluation (2024)The slice definition, per-slice held-out loss, hidden slices by loss sort and clustering, the four remedies and the evaluation-data rule, read from the lecture notes
✓MIT, Introduction to Data-Centric AI: encoding human priors (2023)Invariance, when augmentation helps, and Mixup mixing the label too, read from the lecture notes
✓Ng, "MLOps: From Model-centric to Data-centric AI" (2021)The 500-example, 12-percent claim and the <10,000 line, quoted as a claim with no dataset named; change x or change y; the long tail, read from the slide deck
✓Buolamwini, Gebru (2018), Gender Shades, PMLR 81Up to 34.7 percent error for darker-skinned females against a maximum of 0.8 percent for lighter-skinned males, read from the abstract
✓Every bench and build-along numberComputed from the UCI Optical Recognition of Handwritten Digits (Alpaydin and Kaynak, 1998) with a constructed, declared noise plant; the twenty-seed means come from the same reference
◐MIT lectures on outliers, class imbalance and distribution shiftNamed as the neighbours of slicing, not taught

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

Session 5 cheat sheet · pin this

Choose on training, report on testWeak slice by out-of-fold agreement; the test set reads the result once, after.
Hidden slicesSort held-out training rows by loss, cluster the top, name the cluster.
Augmentation = an invarianceWrite it as a sentence, find the input where it fails, keep the label only if it survives.
Clean, then augmentShifting before cleaning copies every wrong label four times.
Collect or cleanRaw collect 454, collect and clean 478, clean the 800 only 478.
Pruning and scarcityDrop ambiguous: 475 on the 800, 483 on the 1,297. Scarce data needs its boundary rows.