learn-ml-epistemology-with-phoebe / Practitioner session 1 of 10
Learn ML Epistemology with Phoebe · Practitioner session 1 of 10

The assumption under the split

One line of code hands you a number that everyone downstream treats as the truth about the model. The number is real, and it is an estimate of exactly one thing: how the model behaves on data drawn the same way as the data it was scored on. Tonight is about what that sentence buys you, the three shapes of data where it is a lie about the deployment you actually face, and why Thornbury's held-out 0.949 could never have warned anybody about the 0.765 that followed.

🔴 Hardest Anyone who writes train_test_split Anyone who signs off on the number it produces 45 min
0-3 · Welcome 3-18 · What the split estimates 18-32 · Three shapes that break it 32-45 · Build-along and Q&A
Part 0

How the practitioner track works

Ten sessions, 45 minutes each, laptop open. Tonight is the foundation: what a held-out estimate is an estimate of. Session 2 builds the Thornbury shortcut and measures how hard a model leans on it. Session 3 trains twenty models that differ only by their random seed. Session 4 designs the stress set. Sessions 5 and 6 measure what selecting on a test set costs and how big a test set has to be before that cost can even be measured. Sessions 7 to 10 move outward to benchmarks, leakage across a whole literature, a finding that did not survive one more data draw, and the bench you run before you sign anything. The worked example throughout is Thornbury, a fictional hospital group predicting which patients deteriorate within 24 hours, where every true relationship is written into the generator and can therefore be checked.

Live = written together in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. A precise statement of what a held-out number estimates and what it is silent about, so you can say it out loud in a review without hedging. The three data shapes where a random split describes a world nobody will deploy into, each with the split that fixes it. A Thornbury generator running on your own machine, with the ward shortcut written in at 90 percent agreement, ready for the measurement in session 2. And the reason the rest of the track exists: the held-out set and the training set come from the same world, so a held-out score cannot see a shortcut at all.

LiveWhat a held-out estimate is unbiased for5 min▶

Hold out a random slice of your rows, train on the rest, score on the slice. What you have built is an unbiased estimate of the model's performance on the distribution the slice was drawn from. That is a real guarantee and it is worth having. It is also the whole guarantee. The estimate is not slightly informative about other distributions, or weakly informative, or informative on average: it is silent about them, because nothing in the procedure ever touched them.

The assumption doing the work has a name. The rows are treated as independent and identically distributed: each one an independent draw from one fixed distribution, so a random half of them stands in for the rest. Both halves of that phrase are claims about how the data came to exist. Independent says one row does not carry information about another. Identically distributed says the world did not move while you were collecting. Neither is something the split function can check, and neither one is repaired by drawing the slice more carefully.

Real world

Thornbury's training rows and held-out rows both come from the collection year. In that year the ward a patient was on agreed with the outcome about 90 percent of the time, so the ward was just as useful on the held-out rows as it was in training. The held-out estimate came back at 0.949 and it was correct. After the wards were reorganised the agreement fell to about 50 percent and the same model scored 0.765. Both numbers are honest measurements of different distributions.

Self-studyThe words that will keep coming back4 min▶
  • Independent and identically distributed. Each row an independent draw from one fixed distribution. The assumption a random split rests on, and a statement about the world rather than about the code.
  • Held-out set. Rows kept out of training and used to estimate behaviour on unseen data from the same distribution.
  • Group. The unit that repeats across rows: a patient, a session, a device, an institution. If rows share a group, they are not independent draws.
  • Group-aware split. A split where every row belonging to one group lands on the same side, so the score answers a question about a new group.
  • Time-based split. Train on the earlier period, score on the later one, so the model is never shown the future of the period it is judged on.
  • Stress set. Rows chosen to break one named assumption, scored alongside the held-out set rather than instead of it. Session 4.
  • Shortcut. A feature that predicts the outcome without causing it, whose usefulness depends on an arrangement that can change. Session 2 builds one and measures the model's reliance on it.
  • Accuracy and the rest of the metric family. Defined, computed and monitored in learn-model-evaluation-with-phoebe; here a metric is only ever a claim whose warrant is in question.
Part 1 · covers what the split estimates

One world, two draws, and a number that only speaks for that world

12 min live

The picture that makes the rest of the track obvious is the smallest one. A random split does not create two worlds. It takes one world and draws from it twice. Everything the held-out score can tell you is a statement about that single world, including every regularity in it that nobody intended to rely on.

A random split draws twice from one world. The score speaks for that world. The collection year Training rows 4,000 patients Held-out rows 2,000 patients Same draw, same world, so the estimate is unbiased here. Thornbury reads 0.949. The ward agrees with the outcome 90 percent of the time on both sides. Any other world, including the one you deploy into Deployment year 2,000 patients Never drawn from, never scored on, never estimated. Thornbury reads 0.765. The wards are reorganised and the agreement falls to 50 percent. Independent and identically distributed is the assumption carrying the number across. Independent: one row tells you nothing about another. Identically distributed: the world held still while you collected. Both are claims about how the data was produced. Neither is a property of the code, and neither is checked by the split.
🔍 Click to zoom - the split never crosses the dashed line, so the number never speaks for the right-hand side.
LiveWhy a held-out score cannot see a shortcut4 min▶

This is the sentence the whole track hangs on, and it is short. A shortcut is a feature that predicts the outcome without causing it. In the collection year, Thornbury's ward agrees with the outcome about 90 percent of the time. That agreement is a property of the world the data was collected from, which means it is a property of the training rows and of the held-out rows in equal measure. A model that leans on the ward scores well on both. A model that ignores it scores worse on both, and would be rejected in review for being worse.

So the held-out set cannot distinguish the two kinds of learning, not because it is too small and not because it was built carelessly, but because it was drawn from the same place. No amount of additional held-out data changes this. The only measurements that can separate them are measurements that do something the split does not do: intervene on a feature and watch the predictions move, which is session 2, or score the model on data drawn from somewhere else, which is session 4.

LivePredicts and causes are different words, and only one of them is being used here3 min▶

Keep the language plain. The vitals drift and the lab trend at Thornbury are written into the generator as things that actually bring the outcome about. The ward is written in as something that merely agrees with the outcome, because in the collection year the high-acuity ward received most of the patients who went on to deteriorate. Both kinds of feature raise a held-out score. Only one of them keeps working when the arrangement behind the agreement is changed by somebody in facilities management.

That distinction is all that is needed tonight, and the formal machinery for reasoning about it, identification and what you may and may not adjust for, belongs to learn-causal-inference-with-phoebe. What is needed here is the habit of asking which kind a feature is, and the knowledge that the held-out score will never answer.

Self-studyUnbiased is not the same as trustworthy3 min▶

Unbiasedness is a statement about repetition: run the whole procedure many times on fresh draws from the same distribution and the estimates centre on the true value for that distribution. It says nothing about how far any single estimate sits from the truth, which is variance, and nothing about whether the distribution is the one you care about, which is validity. A large held-out set buys the first. Nothing you can do inside the collection year buys the second.

The practical reading is that precision and relevance are bought in different shops. When somebody proposes quadrupling the held-out set to increase confidence before deployment, they are buying precision on a quantity nobody disputed.

Part 2 · covers the three shapes that break it

Three shapes of data where a random split is a lie about deployment

12 min live

The independence claim and the identical-distribution claim fail in recognisable ways, and each failure has a standard repair that costs a different line of code. None of the three is a bug in the split function. Each is a mismatch between the question the split answers and the question deployment will ask.

Each shape breaks a different half of the assumption, and each has its own repair. The data shape What a random split does to it The repair Grouped rows rows repeat a unit The same patient, session, device or institution lands on both sides, so the model is scored on rows it has already half seen. A group-aware split Every row from one group goes to one side of the split. Time ordered rows have a clock Training rows arrive from after the period the model is scored on, so it was shown the future of the window it is being judged on. A time-based split Train on the earlier period, score on the later one. A moving population Both sides come from the collection window, so neither can show what happens when the people, the process or the arrangement move. A stress set Rows chosen to break one named assumption. Session 4. Thornbury is the fourth case, and the quietest one. Rows are independent, the clock is fine, nobody moved yet, and the shortcut works on both sides of an honest split.
🔍 Click to zoom - three named shapes with three named repairs, and a fourth case no split can reach.
LiveGrouped rows: the score answers a question nobody asked4 min▶

Rows are rarely the unit anybody cares about. Admissions belong to patients, clicks belong to sessions, readings belong to devices, cases belong to institutions. When a random split scatters the rows of one group across both sides, the held-out score is still an unbiased estimate, but of a strange quantity: performance on a new row from a group the model has already met. If deployment means meeting a new patient, that quantity is not the one you wanted, and it is reliably the friendlier of the two.

The repair is a group-aware split, and the only hard part is naming the group honestly. It is usually the unit that will be new at deployment time. Write it down before the split, because after the split it is invisible: the two index sets look identical in shape and completely different in meaning.

LiveTime-ordered rows: the model saw the future of the week it is graded on4 min▶

When rows carry a clock, a random split takes some rows from after the scored period and puts them in training. The model then has information about how the period turned out while being graded on it. The estimate that comes back is unbiased for a world in which you get to know the future, which is not a world anybody deploys into.

The repair is a time-based split: train on the earlier period, score on the later one, and accept a smaller and noisier number in exchange for one that means something. This is a different failure from a field that could not have been known when the prediction was made, which is leakage; the mechanics of that, how a post-outcome field reaches a feature list and how a noisy one survives review, belong to learn-model-risk-with-phoebe p3.

Self-studyA moving population, and why a stress set is not just more data3 min▶

The third shape is the one Thornbury eventually meets. Collection happens in one period; deployment happens in another; between them the people change, or the process that produces the rows changes, or an arrangement that several features quietly depended on is reorganised. Nothing inside the collection window can measure this, because every row you could add is drawn from the window.

A stress set is data chosen to break one named assumption: a later period, a different site, a rearranged version of the world. It is scored alongside the held-out set, never instead of it, and it answers a narrow question rather than a general one. Designing one, and being precise about what it can and cannot establish, is session 4.

Part 3 · build-along

Build the Thornbury world, then split it two ways

13 min build-along

Laptop open, numpy and scikit-learn, nothing else. Two blocks. The first writes the Thornbury generator, with both honest signals and the ward shortcut, so that from tonight onward every claim in the track can be checked against a world whose truth you typed yourself. The second splits the same frame twice and shows that the two results are different objects answering different questions.

Write the generator. Two standard-normal signals, a logit built from them, a Bernoulli outcome, and a ward that agrees with the outcome with probability p and is a coin flip otherwise. The shortcut strength is a parameter, which is what lets the same function produce the collection year and the deployment year.

Make three frames. Training and held-out at an agreement of 0.90, shifted at 0.50. Keep the seeds different so the three are genuinely separate draws.

Check the shortcut is there. Before trusting anything downstream, confirm the ward matches the outcome about 90 percent of the time in the first two frames and about 50 percent in the third. A generator you have not checked is a claim, not a fixture.

Add a group column. Give the frame two admissions per patient so the grouped shape is real rather than described.

Split it twice. Once at random, once group-aware, and count how many patients appear on both sides of each. The random split leaks patients across the boundary; the group split cannot.

Say what each score would mean. Out loud, before session 2 trains anything on either of them. One estimates performance on a new admission from a known patient. The other estimates performance on a new patient. Only one of those is deployment.

★ Block 1 · the Thornbury generator import numpy as np def make_data(n, seed, shortcut_strength): """Thornbury: does this patient deteriorate within 24 hours? Two honest signals cause the outcome: a vitals drift and a lab trend. One shortcut, the ward, agrees with the outcome without causing it. """ rng = np.random.default_rng(seed) vitals = rng.standard_normal(n) lab = rng.standard_normal(n) z = -0.35 + 1.25 * vitals + 1.05 * lab p = 1.0 / (1.0 + np.exp(-z)) y = (rng.random(n) < p).astype(int) agrees = rng.random(n) < shortcut_strength coin = (rng.random(n) < 0.5).astype(int) ward = np.where(agrees, y, coin) X = np.column_stack([vitals, lab, ward]) return X, y # the collection year: the ward agrees with the outcome 0.90 of the time X_train, y_train = make_data(4000, 101, 0.90) X_held, y_held = make_data(2000, 102, 0.90) # the deployment year: the wards are reorganised and agreement falls to 0.50 X_shift, y_shift = make_data(2000, 103, 0.50) for name, X, y in [("train", X_train, y_train), ("held ", X_held, y_held), ("shift", X_shift, y_shift)]: print(name, "ward agrees with the outcome:", round(float(np.mean(X[:, 2] == y)), 3))
★ Block 2 · two splits, two different objects import numpy as np from sklearn.model_selection import train_test_split, GroupShuffleSplit X, y = make_data(4000, 101, 0.90) # make the grouped shape real: two admissions per patient patient = np.repeat(np.arange(len(y) // 2), 2) rows = np.arange(len(y)) # a random split: the row is the unit i_tr, i_te = train_test_split(rows, test_size=0.25, random_state=0, stratify=y) shared_random = len(set(patient[i_tr]).intersection(patient[i_te])) # a group-aware split: the patient is the unit gss = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=0) g_tr, g_te = next(gss.split(X, y, groups=patient)) shared_group = len(set(patient[g_tr]).intersection(patient[g_te])) print("patients on both sides, random split:", shared_random) print("patients on both sides, group split :", shared_group) # The two held-out sets have the same shape and answer different questions. # Random: how does this do on a new admission from a patient it has met. # Group : how does this do on a patient it has never met. # Neither one can see the ward shortcut, because both are drawn from the # collection year, where the ward agrees with the outcome 0.90 of the time.

The check to keep. After any split, print how many units of the thing that will be new at deployment appear on both sides. If the answer is not zero, the score you are about to compute is an estimate of something easier than the job. This is two lines and it has caught more inflated numbers than any review meeting.

Check yourself

Three questions

Thornbury's held-out estimate is 0.949 and it is unbiased. Unbiased for what?

The guarantee is tied to the distribution that produced the rows. Nothing in the procedure ever touched the deployment year, so the estimate is not weakly informative about it; it is silent. Thornbury's 0.765 is an honest measurement of a different distribution, not a correction to the first one.

Your rows are admissions and most patients have several. You split at random and score 0.96. What is that number an estimate of?

The estimate is still unbiased, just for the wrong quantity. Rows from the same patient are not independent draws, so the held-out rows carry information the model already absorbed. A group-aware split changes the question back to the one deployment asks, and typically lowers the number.

Why could no amount of extra held-out data at Thornbury have revealed the ward shortcut?

Held-out rows come from the same world as training rows, so the shortcut is just as useful on both. A model that ignored the ward would have scored worse and been rejected for it. Seeing the shortcut needs a different kind of measurement: intervene on the feature, or score somewhere else. The ward is also not a leak, since it is genuinely known before the outcome is.

Homework

Before session 2

30 min

Keep tonight's generator in a file you can import, because every session from here uses it. Then take one model your team already runs and answer two questions in writing. First: what is the unit that will be new at deployment, and does the split respect it? Second: name the one feature somebody on the team would call the most predictive, and say whether it causes the outcome or merely agrees with it, and what arrangement that agreement depends on. Bring the feature name to session 2, because the measurement built there applies to it directly.

Sources covered

What tonight maps to

✓ What a held-out estimate is unbiased for Unbiased for the distribution the split was drawn from, silent about any other. Independent and identically distributed as an assumption about the world, not a property of the code.
✓ Three shapes where a random split lies about deployment Grouped rows, time-ordered rows, and a moving population, with the group-aware split, the time-based split and the stress set as the three repairs.
✓ The Thornbury world, built Two honest signals, one shortcut, agreement at 90 percent in the collection year and 50 percent after the wards are reorganised. Held-out 0.949 against shifted 0.765.
✓ Why a held-out score cannot see a shortcut The held-out set and the training set come from the same world, so the shortcut works on both and a model that ignores it scores worse.
◐ Measuring reliance on the shortcut Session 2 flips the ward on every patient and counts the predictions that change.
◐ Twenty seeds, one pipeline Session 3. Models that are indistinguishable where you looked, and distinguishable where you did not.
◐ Designing the stress set Session 4. What a stress test can and cannot establish.
· Leak mechanics inside one validation Not covered by design. learn-model-risk-with-phoebe p3 owns timing, how a post-outcome field reaches a feature list, and why a noisy leak survives review.
· Causal machinery behind predicts against causes Not covered by design. learn-causal-inference-with-phoebe owns identification and what may be adjusted for.
· Metric definitions and drift detection Not covered by design. learn-model-evaluation-with-phoebe defines every metric and owns monitoring.

Session 1 cheat sheet

Unbiased, and only thereA held-out score estimates performance on the distribution the split was drawn from. Silent about all others.
The assumption has a nameIndependent and identically distributed. A claim about how the data was produced, not something the code checks.
Grouped rowsSame patient, session, device or institution on both sides. Fix with a group-aware split.
Time-ordered rowsA random split shows the model the future of the week it is graded on. Fix with a time-based split.
A moving populationNo row from the collection window can measure it. Fix with a stress set, scored alongside not instead.
One world, two drawsA split does not create two worlds. Every regularity in the one world is in both halves.
0.949 then 0.765Ward agreement 90 percent in the collection year, 50 percent after the reorganisation, no retraining.
The two-line checkCount the deployment-new units appearing on both sides of the split. Anything but zero softens the score.