# AI, ML, DL, GenAI, Oh my!
Sean Davis, MD, PhD
October 14, 2026

## What we will cover

- **AI, ML, DL, GenAI:** how the four fit inside each other
- **One model, start to finish:** the eGFR, fitting a model to data,
  adding a predictor
- **The fitting loop**, and two questions that sort any model
- **What goes wrong:** models that are too flexible, neural networks,
  and why a score on cases a model has already seen proves nothing
- **Back to the clinic:** what this changes on the wards

<span class="src">Questions along the way are live: answer on your phone
or laptop. Thursday starts with the chatbot half of this story.</span>

<div class="notes">

Welcome. This session takes about sixty minutes live, or about the same
on your own with the pauses.

Here is the plan. First, four words, AI, machine learning, deep
learning, and generative AI, and how they fit inside each other. Then
one model from start to finish: the eGFR on a chemistry panel, where its
number comes from, how a model is fit to data, and what happens when you
add a predictor. Then the fitting loop that every model comes from, and
two questions that sort any model.

After that, what goes wrong. A model can be too flexible. A neural
network is the same loop with far more dials. And a score on cases a
model has already seen proves nothing. Then back to the clinic, and what
this changes on the wards.

The questions along the way are live: you answer on your phone or
laptop. Thursday starts with the chatbot, which is the same idea with
words.

LIVE: Run sheet, with minutes per block, for about 60 minutes of talk
and 3 for questions. Before class: open this deck once with
?presenter=YOUR-KEY so the live questions open. 0 to 9 (9 min): slides 1
to 5; slide 4 is the setup (target, model, predictors, error, loss),
about a minute and a half, with one click for the loss box; slide 5 is
height and weight, move the two sliders yourself for about two minutes,
press “Show the best fit”, click once to name the fitting loop, then
click again for “What is missing?” and take two or three answers aloud.
9 to 19 (10 min): slides 6 to 11, with the live question on slide 8
(about 45 seconds, then press A); on slide 10 the room has already fit a
line, so move the sliders for only about a minute, then press “Show the
best fit”; on slide 11 ask the room which way the average miss will
move, then turn on “Add sex”. 19 to 35 (16 min): slides 12 to 20; slide
14 one minute, pointing at the padlock; slide 15 two minutes; slides 18
and 19 are live questions too, about 45 seconds each, then press A to
reveal; slide 20 ninety seconds, pointing at the Type column and then at
the last column. 35 to 52 (17 min): slides 21 to 27; on slide 21 move
the slider yourself, about two minutes, and let the room call out where
it goes wrong; slide 22 two minutes; slide 25 three minutes, as a
callback to Tuesday’s talk; slide 27 is the bridge to Thursday, one
minute. Keep about two minutes in this block for questions from the
room. 52 to 60 (8 min): slides 28 to 31; read slide 30 in one minute;
end on the Thursday line on slide 31. 60 to 63: questions, on slide 35,
the last slide (press End from slide 31). Slides 32 and 33 are for
students afterwards; slide 33 has the TensorFlow Playground exercise.
Slide 34 is the reference list.

LIVE: Turn on Zoom live captions and paste the deck link into the chat
at the start. Paragraphs in these notes that start with PAUSE are for
the recorded version; skip them live. ChatGPT Edu and Microsoft Copilot
are this course’s institutional tools; swap in your school’s. Click
outside a slider or button before using the arrow keys.

</div>

## Four words, each inside the one before

<img src="images/ai-nested-circles.svg" class="card"
data-fig-alt="Four nested circles. The largest is artificial intelligence: a computer doing a task that seems to need judgment, such as MYCIN in the 1970s, whose rules were written by hand. Inside it is machine learning: the rules are learned from data, such as the eGFR equation. Inside that is deep learning: machine learning with many layers, a neural network, such as a model that reads chest X-rays. The smallest is generative AI: deep learning that writes new text or makes images, such as a chatbot."
height="560" />

Each is one kind of the one around it. Yesterday we saw how we got here.
Today: how the models inside these circles are made.

<div class="notes">

Yesterday’s talk was the history: how we got from rules written by hand
to the chatbots of today. Here are four words from that history, and how
they fit together. If you missed yesterday, nothing today depends on it.

Artificial intelligence is the largest circle. The sizes of the circles
aren’t meant to show how big each field is. It means a computer doing a
task that seems to need judgment, by any method. MYCIN, from the 1970s,
advised on antibiotics with rules that experts wrote by hand.

Machine learning is inside it. The rules aren’t written by hand. They
are learned from data. The eGFR equation on every chemistry panel is an
example. A statistician would call it a regression. The line between
statistics and machine learning is blurry, and for this course a model
learned from data counts.

Deep learning is inside machine learning. It uses a neural network: many
layers of simple units, each passing numbers to the next. Models that
read images, such as chest X-rays, are usually built this way.

Generative AI is inside deep learning. These models write new text or
make new images. A chatbot is the example you know. So a chatbot is
generative AI, and it’s also deep learning, machine learning, and AI.
The words overlap, but each one means something specific.

Today we start in the second circle, with the eGFR, and reach the third,
deep learning. Thursday we reach the smallest circle, the chatbot. The
same idea, learning from data, is under all of them.

</div>

## Every model has the same setup

<svg class="setup-eq" viewBox="0 20 1200 350" role="img" aria-label="The equation Y sub i equals f of X sub i plus epsilon sub i. Y is the target, the value we want to predict. f is the model, a function. X is the predictors, the inputs. Epsilon is the error, what the model misses.">

<defs><marker id="arr" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0L10,5L0,10z" fill="#7a7064"/></marker></defs>
<g font-family="'Source Serif 4','Georgia',serif" font-style="italic" font-size="120" text-anchor="middle">
<text x="130" y="130" fill="#24211d">Y<tspan font-size="56" dy="22">i</tspan></text>
<text x="300" y="130" fill="#4a443c" font-style="normal">=</text>
<text x="450" y="130" fill="#1f5a61">f</text>
<text x="520" y="130" fill="#4a443c" font-style="normal">(</text>
<text x="620" y="130" fill="#b98a2f">X<tspan font-size="56" dy="22">i</tspan></text>
<text x="725" y="130" fill="#4a443c" font-style="normal">)</text>
<text x="830" y="130" fill="#4a443c" font-style="normal">+</text>
<text x="990" y="130" fill="#a9472a">ε<tspan font-size="56" dy="22">i</tspan></text>
</g>
<g stroke="#7a7064" stroke-width="3" marker-end="url(#arr)" fill="none">
<line x1="130" y1="250" x2="130" y2="165"/>
<line x1="420" y1="250" x2="448" y2="170"/>
<line x1="710" y1="250" x2="635" y2="170"/>
<line x1="990" y1="250" x2="990" y2="165"/> </g>
<g font-family="'Source Sans 3','Helvetica',sans-serif" font-size="28" text-anchor="middle">
<text x="130" y="285" font-weight="700" fill="#24211d">Target</text>
<text x="130" y="320" fill="#4a443c">the value we want</text>
<text x="130" y="352" fill="#4a443c">to predict</text>
<text x="420" y="285" font-weight="700" fill="#1f5a61">Model</text>
<text x="420" y="320" fill="#4a443c">a function: turns the</text>
<text x="420" y="352" fill="#4a443c">inputs into a prediction</text>
<text x="710" y="285" font-weight="700" fill="#b98a2f">Predictors</text>
<text x="710" y="320" fill="#4a443c">the inputs we know,</text>
<text x="710" y="352" fill="#4a443c">for person i</text>
<text x="990" y="285" font-weight="700" fill="#a9472a">Error</text>
<text x="990" y="320" fill="#4a443c">what the model misses:</text>
<text x="990" y="352" fill="#4a443c">target minus prediction</text> </g>
</svg>

<div class="fragment">

<div class="panel">

<span class="ptitle">Loss function</span> One number that adds up the
errors across everyone: the sum of squared errors, or the average miss.
Big errors, big loss. **Fitting** means choosing the model that makes
the loss as small as it can get.

</div>

</div>

<div class="notes">

Every model in this talk has the same setup, and it’s worth one slide.
Person number i has a target, which is the thing we want to predict, and
a set of predictors, which are the things we know about them. The model
is a function: it takes the predictors and returns a prediction. The
prediction is almost never exactly the target. What’s left over is the
error. So the target equals the model’s prediction plus the error. The
target can be a number, like weight, or the chance that a patient falls
in the hospital. It can also be a category: benign or malignant,
pneumonia or not.

The error isn’t only that each person is a little different. It also
holds any error in measuring the target, and in measuring the
predictors. My PhD is in human genetics, and one of our constant
problems was defining the disease. If you can’t define it well, it’s
very hard to find the gene that goes with it. The best models start with
the best measurements.

Those four words, target, model, predictors, error, will come back all
session. The eGFR is a model. A chatbot is a model. The f can be as
simple as a straight line or as complex as a neural network. \[Click\]

How do we choose the model? We need a way to score it. A loss function
adds up the errors across everyone into one number. One common choice
squares each error and adds them up, which is called the residual sum of
squares. Another is the plain average miss. Big errors make a big loss.
Fitting a model means choosing the version of the function that makes
the loss as small as it can be. On the next slide, we do that by hand.

</div>

## Predict weight from height

``` {ojs}
hwData = {
  let s = 20261014;
  const rand = () => { s = (s + 0x6d2b79f5) | 0; let t = Math.imul(s ^ (s >>> 15), 1 | s);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
  const gauss = () => Math.sqrt(-2 * Math.log(1 - rand())) * Math.cos(2 * Math.PI * rand());
  return Array.from({ length: 40 }, () => { const h = Math.min(198, Math.max(150, 171 + 9 * gauss())); return { h, w: -62 + 0.82 * h + 11 * gauss() }; });
}
hwLine = (a, b) => h => a + b * h
hwMiss = (a, b) => hwData.reduce((t, p) => t + Math.abs(hwLine(a, b)(p.h) - p.w), 0) / hwData.length
hwRss = (a, b) => hwData.reduce((t, p) => t + (hwLine(a, b)(p.h) - p.w) ** 2, 0)
// The best fit: ordinary least squares, the line with the smallest sum of squared misses.
hwBest = {
  const n = hwData.length, mh = hwData.reduce((t, p) => t + p.h, 0) / n, mw = hwData.reduce((t, p) => t + p.w, 0) / n;
  const b = hwData.reduce((t, p) => t + (p.h - mh) * (p.w - mw), 0) / hwData.reduce((t, p) => t + (p.h - mh) ** 2, 0);
  return { a: mw - b * mh, b };
}
```

<div class="columns">

<div class="column" width="56%">

<div class="illus-label">

Made-up data for 40 adults.

</div>

``` {ojs}
{
  const W = 640, H = 400, l = 64, r = 14, t = 12, b = 54, x0 = 145, x1 = 205, y0 = 30, y1 = 140;
  const X = h => l + (h - x0) / (x1 - x0) * (W - l - r), Y = w => H - b - (Math.min(Math.max(w, y0), y1) - y0) / (y1 - y0) * (H - b - t);
  const f = hwLine(hwA, hwB);
  return html`<svg class="wchart" viewBox="0 0 ${W} ${H}" role="img" aria-label="Forty made-up adults, height against weight, with a line for intercept ${hwA} and slope ${hwB}. Average miss ${hwMiss(hwA, hwB).toFixed(1)} kilograms.">
    ${[40, 60, 80, 100, 120].map(v => `<line x1="${l}" x2="${W - r}" y1="${Y(v)}" y2="${Y(v)}" stroke="${ink.rule}"/><text x="${l - 8}" y="${Y(v) + 6}" fill="${ink.muted}" font-size="17" text-anchor="end">${v}</text>`).join("")}
    ${[150, 160, 170, 180, 190, 200].map(v => `<text x="${X(v)}" y="${H - b + 24}" fill="${ink.muted}" font-size="17" text-anchor="middle">${v}</text>`).join("")}
    ${hwData.map(p => `<line x1="${X(p.h).toFixed(1)}" x2="${X(p.h).toFixed(1)}" y1="${Y(p.w).toFixed(1)}" y2="${Y(f(p.h)).toFixed(1)}" stroke="${ink.terracotta}" stroke-width="1.5" opacity=".8"/>`).join("")}
    ${hwData.map(p => `<circle cx="${X(p.h).toFixed(1)}" cy="${Y(p.w).toFixed(1)}" r="5" fill="${ink.ink}"/>`).join("")}
    <line x1="${X(x0)}" y1="${Y(f(x0))}" x2="${X(x1)}" y2="${Y(f(x1))}" stroke="${ink.teal}" stroke-width="4"/>
    <text x="${(l + W - r) / 2}" y="${H - 8}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle">height, cm</text>
    <text x="22" y="${(t + H - b) / 2}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle" transform="rotate(-90 22 ${(t + H - b) / 2})">weight, kg</text>
  </svg>`;
}
```

<div class="fragment" data-fragment-index="2">

<span style="font-size:.78em">**What is missing?** What else would help
predict weight? In other words, what additional predictors should we
consider?</span>

</div>

</div>

<div class="column dials" width="44%">

<p class="eq">

weight = <b>a</b> + <b>b</b> × height + error
</p>

``` {ojs}
viewof hwA = rangeInput({ label: "a", min: -150, max: 50, step: 1, value: -20 })
viewof hwB = rangeInput({ label: "b", min: 0, max: 1.6, step: 0.01, value: 0.4 })
```

<span class="defn">**a**: the intercept. **b**: the slope, kg per extra
centimeter.</span>

``` {ojs}
{
  const miss = hwMiss(hwA, hwB), rss = hwRss(hwA, hwB);
  const best = Math.abs(hwA - hwBest.a) < 0.5 && Math.abs(hwB - hwBest.b) < 0.005;
  return html`<div class="readout stack" role="status"><span class="k test">Loss (sum of squared misses): <b>${rss.toFixed(0)}</b></span><span class="k test">Average miss: <b>${miss.toFixed(1)}</b> kg</span></div>
    ${best ? html`<div class="fb ok">The best fit: no line has a smaller loss.</div>` : ""}`;
}
```

``` {ojs}
{
  const button = html`<button class="w-btn" type="button">Show the best fit</button>`;
  button.onclick = () => {
    for (const [view, v] of [[viewof hwA, Math.round(hwBest.a)], [viewof hwB, +hwBest.b.toFixed(2)]]) {
      const input = view.querySelector("input");
      input.value = v;
      input.dispatchEvent(new Event("input", { bubbles: true }));
    }
  };
  return button;
}
```

<div class="fragment" data-fragment-index="1">

<span style="font-size:.78em">What you just did is the **fitting loop**:
guess the dials, score the misses with the loss, adjust, repeat.</span>

</div>

</div>

</div>

<div class="notes">

Here is that setup with the simplest model there is. The target is
weight. The one predictor is height. Each dot is one made-up adult. The
model is a straight line with two numbers in it: a, the intercept, and
b, the slope. Each thin line is that person’s error, how far their
weight is from the line. The loss is the sum of the squared errors, and
the average miss is beside it.

PAUSE: 2 min. Move the two sliders and make the loss as small as you
can. Then press “Show the best fit”.

I could move these sliders all day long, but it turns out we have good
ways of fitting a model. Fitting is what the computer does when you
press that button: it finds the line with the smallest sum of squared
errors. For a straight line, that’s the best you can do. This is called
linear regression, and it’s machine learning in its simplest form.
\[Click\]

What you just did by hand has a name: the fitting loop. Guess the dials,
score the misses with the loss, adjust the dials, and repeat. When to
stop is a question we come back to. For a straight line there’s a
formula that jumps to where the loop would end, which is what the button
uses. Bigger models, like neural networks, really do run the loop, lap
after lap. \[Click\]

Even the best line leaves errors. Some tall people are light and some
short people are heavy. So what’s missing? What else would help us
predict weight? Age, sex, how much of the weight is muscle, waist size.
Each one is another predictor, another column in X. The eGFR does this
with creatinine, age, and sex.

LIVE: About two and a half minutes. Move the sliders yourself and ask
the room which way to move b. Press the button. Click once to name the
fitting loop, then again for the last question, and take two or three
answers aloud.

</div>

## <span class="year">Part 1</span> The number nobody measured

<div class="notes">

Now to a clinic, and a model you already use.

</div>

## Pre-op clinic, surgery sub-internship

<div class="columns">

<div class="column" width="48%">

A 62-year-old woman is booked for a laparoscopic cholecystectomy. Her
basic metabolic panel is back.

<div class="fragment">

The attending asks: **are her kidneys all right for the contrast CT?**

You say: *“eGFR 57, stage 3a, mildly reduced. Probably fine with
hydration.”*

</div>

</div>

<div class="column" width="52%">

<div class="lab-report">

<div class="hdr">

Basic metabolic panel · collected 07:12

</div>

<table>

<tr>

<td>

Sodium
</td>

<td>

139
</td>

<td>

</td>

<td>

136–145
</td>

</tr>

<tr>

<td>

Potassium
</td>

<td>

4.1
</td>

<td>

</td>

<td>

3.5–5.1
</td>

</tr>

<tr>

<td>

Creatinine
</td>

<td>

1.10
</td>

<td>

</td>

<td>

0.50–1.10
</td>

</tr>

<tr>

<td>

eGFR (CKD-EPI 2021)
</td>

<td>

57
</td>

<td class="flag">

L
</td>

<td>

\>60
</td>

</tr>

</table>

<div class="fine">

Units: mmol/L; creatinine mg/dL; eGFR mL/min/1.73 m². Illustrative
panel, made up for this course. Not a real patient.

</div>

</div>

</div>

</div>

<div class="notes">

Pre-operative clinic, on your surgery sub-internship. A
sixty-two-year-old woman is being worked up for a laparoscopic
cholecystectomy. Her basic metabolic panel is back, and one line is
flagged low: an eGFR of fifty-seven, where the lab’s reference is above
sixty. \[Click\]

The attending asks whether her kidneys are all right for the contrast CT
the surgeon wants. You answer the way everyone answers. eGFR
fifty-seven, stage three a, mildly reduced, probably fine with
hydration. The attending nods, and that’s the whole conversation. You’ll
have it several hundred times in intern year.

The panel on the slide is made up for this course. It isn’t a real
patient.

LIVE: Before the case, tell the room to get a phone nearby; the next
slide is a live question.

</div>

## Where does the 57 come from?

Her report says **eGFR 57**. Where does the 57 come from?

- The analyzer measured it in her blood sample
- It was measured from a timed urine collection
- An equation calculated it from her creatinine, age, and sex

<div class="notes">

Before I go on, commit to an answer. Where does the fifty-seven come
from? Did a machine measure it in her blood? Was it measured from a
timed urine collection? Or did an equation calculate it from three other
numbers?

LIVE: Open the poll. About 45 seconds, then press A to reveal. Add
aloud: no urine was collected, and no blood test measures GFR itself.

PAUSE: 1 min. Pause here and pick one before you go on.

The answer is the equation. Nobody measured her kidney function. Three
numbers went into an equation, and the equation produced fifty-seven.
The point isn’t knowing the answer. It’s noticing that the number was
calculated.

</div>

## Nobody measured her GFR

<div class="incremental">

- Measuring GFR means **infusing a marker** the kidney filters and does
  nothing else with, such as iohexol, and timing how fast it clears.
- That takes hours. It is **rarely done in routine care**.
- The 57 was **computed** from creatinine, age, and sex.
- The equation was **learned from data**, not from physiology. A few
  thousand people did have their GFR measured. A computer was given
  their creatinine, age, and sex, and one instruction: *find the formula
  that best predicts the measured number.*

</div>

<div class="fragment statement">

The eGFR is a **model**. *Remember her:* 45 kg, and a year of muscle
loss.

</div>

<div class="notes">

Nobody measured her glomerular filtration rate. \[Click\] Measuring it
means infusing a marker that the kidney filters and does nothing else
with, such as iohexol, and timing how fast it clears. \[Click\] That
takes hours, and it’s rarely done in routine care.

\[Click\] The fifty-seven was computed from three things the lab did
have: her creatinine, her age, and her sex. Something turned those three
numbers into a kidney function, and you trusted it enough to clear her
for contrast.

\[Click\] So where did that equation come from? Not from the physiology
of nephrons. It was learned from data. A few thousand people did have
their GFR measured. Their creatinine, age, and sex were given to a
computer with one instruction: find the formula that best predicts the
measured number. \[Click\]

The eGFR is a model. It’s the model you already trust most, and you have
probably never asked what it was trained on.

Remember this patient. She weighs forty-five kilograms and has lost a
good deal of muscle over a year of illness. We’ll come back to her at
the end. Nobody has told her that a model had a part in the fifty-seven.

</div>

## Fit the curve yourself

``` {ojs}
dialData = {
  let s = 20261014;
  const rand = () => { s = (s + 0x6d2b79f5) | 0; let t = Math.imul(s ^ (s >>> 15), 1 | s);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
  const gauss = () => Math.sqrt(-2 * Math.log(1 - rand())) * Math.cos(2 * Math.PI * rand());
  return Array.from({ length: 40 }, () => { const cr = 0.6 * Math.pow(4 / 0.6, rand()); return { cr, gfr: 80 * Math.pow(cr, -1.1) * Math.exp(0.15 * gauss()) }; });
}
dialCurve = (a, b) => cr => a * Math.pow(cr, -b)
dialMiss = (a, b) => { const f = dialCurve(a, b); return dialData.reduce((t, p) => t + Math.abs(f(p.cr) - p.gfr), 0) / dialData.length; }
// The best fit, found by trying every setting the two sliders allow.
dialBest = {
  let best = { a: 0, b: 0, miss: Infinity };
  for (let a = 30; a <= 150; a++) for (let k = 0; k <= 40; k++) { const b = +(k * 0.05).toFixed(2), miss = dialMiss(a, b); if (miss < best.miss) best = { a, b, miss }; }
  return best;
}
```

<div class="columns">

<div class="column" width="56%">

<div class="illus-label">

Illustrative data, not the CKD-EPI data. A simpler curve than the real
eGFR.

</div>

``` {ojs}
{
  const W = 640, H = 400, l = 64, r = 14, t = 12, b = 54, ymax = 170;
  const X = cr => l + (cr - 0.5) / 3.5 * (W - l - r), Y = g => H - b - Math.min(g, ymax) / ymax * (H - b - t);
  const f = dialCurve(dialA, dialB);
  const grid = Array.from({ length: 141 }, (_, i) => 0.5 + i * 0.025);
  const path = "M" + grid.map(c => `${X(c).toFixed(1)},${Y(f(c)).toFixed(1)}`).join("L");
  return html`<svg class="wchart" viewBox="0 0 ${W} ${H}" role="img" aria-label="Forty made-up people, creatinine against measured GFR, with the curve for a ${dialA} and b ${dialB}. Average miss ${dialMiss(dialA, dialB).toFixed(1)}.">
    ${[0, 40, 80, 120, 160].map(v => `<line x1="${l}" x2="${W - r}" y1="${Y(v)}" y2="${Y(v)}" stroke="${ink.rule}"/><text x="${l - 8}" y="${Y(v) + 6}" fill="${ink.muted}" font-size="17" text-anchor="end">${v}</text>`).join("")}
    ${[1, 2, 3, 4].map(v => `<text x="${X(v)}" y="${H - b + 24}" fill="${ink.muted}" font-size="17" text-anchor="middle">${v}</text>`).join("")}
    ${dialData.map(p => `<line x1="${X(p.cr).toFixed(1)}" x2="${X(p.cr).toFixed(1)}" y1="${Y(p.gfr).toFixed(1)}" y2="${Y(f(p.cr)).toFixed(1)}" stroke="${ink.terracotta}" stroke-width="1.5" opacity=".8"/>`).join("")}
    ${dialData.map(p => `<circle cx="${X(p.cr).toFixed(1)}" cy="${Y(p.gfr).toFixed(1)}" r="5" fill="${ink.ink}"/>`).join("")}
    <path d="${path}" fill="none" stroke="${ink.teal}" stroke-width="4"/>
    <text x="${(l + W - r) / 2}" y="${H - 8}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle">creatinine, mg/dL</text>
    <text x="22" y="${(t + H - b) / 2}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle" transform="rotate(-90 22 ${(t + H - b) / 2})">measured GFR</text>
  </svg>`;
}
```

</div>

<div class="column dials" width="44%">

<p class="eq">

GFR = <b>a</b> × creatinine<sup>−<b>b</b></sup>
</p>

``` {ojs}
viewof dialA = rangeInput({ label: "a", min: 30, max: 150, step: 1, value: 50 })
viewof dialB = rangeInput({ label: "b", min: 0, max: 2, step: 0.05, value: 0.3 })
```

<span class="defn">**a**: the GFR when creatinine is 1.0. **b**: how
fast GFR falls as creatinine rises.</span>

``` {ojs}
{
  const miss = dialMiss(dialA, dialB), best = dialA === dialBest.a && dialB === dialBest.b;
  return html`<div class="readout" role="status"><span class="k test">Average miss: <b class="dial-miss">${miss.toFixed(1)}</b> mL/min/1.73 m²</span></div>
    ${best ? html`<div class="fb ok">The best fit: no setting of a and b misses by less.</div>` : ""}`;
}
```

``` {ojs}
{
  const button = html`<button class="w-btn" type="button">Show the best fit</button>`;
  button.onclick = () => {
    for (const [view, v] of [[viewof dialA, dialBest.a], [viewof dialB, dialBest.b]]) {
      const input = view.querySelector("input");
      input.value = v;
      input.dispatchEvent(new Event("input", { bubbles: true }));
    }
  };
  return button;
}
```

<div class="fragment">

**Fitting** means a computer turns these dials until the error is as
small as it can get.

</div>

</div>

</div>

<span class="src">● one made-up person · thin line: how far the curve
misses them · thick line: the curve your dials make. Generated in your
browser.</span>

<div class="notes">

Here is fitting, done by hand. Each dot is one made-up person:
creatinine across, measured GFR up. The thick curve is a simple equation
with two numbers in it, a and b. Those two numbers are the dials. The
number a sets how high the curve sits: it’s the GFR the curve gives at a
creatinine of one. The number b sets how fast GFR falls as creatinine
rises. Each thin line is a miss: how far one person’s measured GFR is
from the curve. The number beside the sliders is the average miss.

PAUSE: 1 min. Move the two sliders and make the average miss as small as
you can. Then press “Show the best fit”. \[Click\]

Fitting means a computer turns these dials until the error is as small
as it can get. This is the same fitting loop you ran with height and
weight. Here the computer tries every setting the sliders allow and
keeps the best one. Real fitting uses faster methods, with the same
goal. The real eGFR equation has a few more numbers in it, and it was
fit to eight thousand two hundred fifty-four people whose GFR was
measured, not to forty made-up dots. A chatbot is the same idea with
billions of numbers.

The data on this slide are made up, and the curve is simpler than the
real equation. Its numbers aren’t the real eGFR’s.

LIVE: About one minute; the room fit a line on slide 5. Move the sliders
briefly, press the button, then click for the last line.

</div>

## Add a predictor

``` {ojs}
// Made-up people. Women are built to have a lower GFR at the same creatinine.
addPeople = { let s = 20261014;
  const rand = () => { s = (s + 0x6d2b79f5) | 0; let t = Math.imul(s ^ (s >>> 15), 1 | s);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
  const gauss = () => Math.sqrt(-2 * Math.log(1 - rand())) * Math.cos(2 * Math.PI * rand());
  return Array.from({ length: 40 }, () => { const cr = 0.6 * Math.pow(4 / 0.6, rand()), fem = rand() < 0.5 ? 1 : 0;
    return { cr, fem, gfr: 80 * Math.pow(cr, -1.1) * (fem ? 0.78 : 1) * Math.exp(0.15 * gauss()) }; }); }
addRow = (p, useSex) => useSex ? [1, Math.log(p.cr), p.fem] : [1, Math.log(p.cr)]
// Least squares on the log scale (a straight-line fit): solve the normal equations.
addFit = useSex => {
  const X = addPeople.map(p => addRow(p, useSex)), y = addPeople.map(p => Math.log(p.gfr)), m = X[0].length;
  const M = Array.from({ length: m }, (_, i) => [...Array.from({ length: m }, (_, j) => X.reduce((t, r) => t + r[i] * r[j], 0)), X.reduce((t, r, k) => t + r[i] * y[k], 0)]);
  for (let i = 0; i < m; i++) { let piv = i; for (let j = i + 1; j < m; j++) if (Math.abs(M[j][i]) > Math.abs(M[piv][i])) piv = j;
    [M[i], M[piv]] = [M[piv], M[i]];
    for (let j = i + 1; j < m; j++) { const f = M[j][i] / M[i][i]; for (let k = i; k <= m; k++) M[j][k] -= f * M[i][k]; } }
  const w = Array(m).fill(0);
  for (let i = m - 1; i >= 0; i--) { let t = M[i][m]; for (let j = i + 1; j < m; j++) t -= M[i][j] * w[j]; w[i] = t / M[i][i]; }
  return w;
}
addPredict = (w, p, useSex) => Math.exp(addRow(p, useSex).reduce((t, v, i) => t + v * w[i], 0))
addScore = (w, useSex) => ({
  miss: addPeople.reduce((t, p) => t + Math.abs(addPredict(w, p, useSex) - p.gfr), 0) / addPeople.length,
  rss: addPeople.reduce((t, p) => t + (Math.log(addPredict(w, p, useSex)) - Math.log(p.gfr)) ** 2, 0) })
```

<div class="columns">

<div class="column" width="56%">

<div class="illus-label">

Illustrative data, made up. Women are built to have a lower GFR at the
same creatinine.

</div>

``` {ojs}
{
  const W = 640, H = 400, l = 64, r = 14, t = 12, b = 54, ymax = 170;
  const X = cr => l + (cr - 0.5) / 3.5 * (W - l - r), Y = g => H - b - Math.min(g, ymax) / ymax * (H - b - t);
  const w = addFit(useSex);
  const grid = Array.from({ length: 141 }, (_, i) => 0.5 + i * 0.025);
  const curve = fem => "M" + grid.map(c => `${X(c).toFixed(1)},${Y(addPredict(w, { cr: c, fem }, useSex)).toFixed(1)}`).join("L");
  const dot = p => useSex ? (p.fem ? ink.terracotta : ink.teal) : ink.ink;
  return html`<svg class="wchart" viewBox="0 0 ${W} ${H}" role="img" aria-label="Forty made-up people, creatinine against measured GFR. ${useSex ? "Women and men have separate curves." : "One curve for everyone."}">
    ${[0, 40, 80, 120, 160].map(v => `<line x1="${l}" x2="${W - r}" y1="${Y(v)}" y2="${Y(v)}" stroke="${ink.rule}"/><text x="${l - 8}" y="${Y(v) + 6}" fill="${ink.muted}" font-size="17" text-anchor="end">${v}</text>`).join("")}
    ${[1, 2, 3, 4].map(v => `<text x="${X(v)}" y="${H - b + 24}" fill="${ink.muted}" font-size="17" text-anchor="middle">${v}</text>`).join("")}
    ${addPeople.map(p => `<line x1="${X(p.cr).toFixed(1)}" x2="${X(p.cr).toFixed(1)}" y1="${Y(p.gfr).toFixed(1)}" y2="${Y(addPredict(w, p, useSex)).toFixed(1)}" stroke="${ink.muted}" stroke-width="2" opacity=".6"/>`).join("")}
    ${addPeople.map(p => `<circle cx="${X(p.cr).toFixed(1)}" cy="${Y(p.gfr).toFixed(1)}" r="5" fill="${dot(p)}"/>`).join("")}
    ${useSex ? `<path d="${curve(0)}" fill="none" stroke="${ink.teal}" stroke-width="4"/><path d="${curve(1)}" fill="none" stroke="${ink.terracotta}" stroke-width="4"/>` : `<path d="${curve(0)}" fill="none" stroke="${ink.teal}" stroke-width="4"/>`}
    <text x="${(l + W - r) / 2}" y="${H - 8}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle">creatinine, mg/dL</text>
    <text x="22" y="${(t + H - b) / 2}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle" transform="rotate(-90 22 ${(t + H - b) / 2})">measured GFR</text>
  </svg>`;
}
```

</div>

<div class="column dials" width="44%">

``` {ojs}
toggleInput = label => {
  const box = html`<label class="w-toggle"><input type="checkbox"> <span>${label}</span></label>`;
  const input = box.querySelector("input");
  box.value = false;
  input.onchange = () => { box.value = input.checked; box.dispatchEvent(new Event("input", { bubbles: true })); };
  return box;
}
viewof useSex = toggleInput("Add sex")
```

``` {ojs}
{
  const sc = addScore(addFit(useSex), useSex);
  return html`<div class="readout stack" role="status">
    <div><span class="k">Numbers in the model: <b>${useSex ? 3 : 2}</b></span></div>
    <div><span class="k">Average miss: <b>${sc.miss.toFixed(1)}</b> mL/min/1.73 m²</span></div>
    <div><span class="k">Sum of squared misses (RSS): <b>${sc.rss.toFixed(2)}</b></span></div>
  </div>`;
}
```

<span class="defn">Dots: teal men, terracotta women. Thin lines: the
miss for each person. The fit makes the RSS as small as it can, on a log
scale.</span>

<div class="fragment">

**Fitting** with a second predictor is the same job: find the numbers
that make the misses smallest.

</div>

</div>

</div>

<div class="notes">

Now add a second predictor. The real eGFR equation uses sex as well as
creatinine and age, so we do the same. Same made-up people, same idea,
but now the equation can have a different level for women and men.

PAUSE: 1 min. Turn on “Add sex” and watch the average miss and the RSS.

The fit now gives each sex its own curve, and both numbers fall. In this
made-up data, women have a lower GFR at the same creatinine, so a model
that knows the sex can follow both groups. Statisticians call the sum of
squared misses the RSS. The fit makes it as small as it can, so the RSS
can only go down when a predictor is added. Whether the drop is real, or
only a better fit to these forty people, is a different question, and we
come back to it with the slider in Part 2. \[Click\]

Fitting with a second predictor is the same job as before: find the
numbers that make the misses smallest.

The data are made up. The real equation has more terms, and it was fit
to eight thousand two hundred fifty-four people whose GFR was measured.

LIVE: About one minute. Ask the room which way the average miss will
move, then turn it on.

</div>

## <span class="year">Part 1, continued</span> The loop every model comes from

<div class="notes">

Next: six words, the fitting loop, and two questions. Together they sort
every model you’ll meet in residency.

</div>

## Six words for the rest of the course

| Word | Meaning | In the eGFR |
|----|----|----|
| **Predictors** | the inputs | creatinine, age, sex |
| **Response** | what is being predicted | measured GFR |
| **Training set** | the cases used to fit the equation | 8,254 adults in 10 studies, mostly research participants; 31.5% Black |
| **Validation set** | cases held back *during* fitting, to choose between versions | *see the note below* |
| **Test set** | cases held back to check it once, at the end | 4,050 people in 12 other studies; the paper calls them its “validation data set” |
| **Model** | the equation once fit | `eGFR = f(creatinine, age, sex)` |

<span class="defn">*f* stands for **function**: a rule that turns inputs
into an output. **Fit** means a computer tried numbers in the equation
until its predictions were as close to the measured values as they could
get ([Inker et al., 2021](#ref-pmid:34554658)).</span>

<span class="src">**Words vary by community.** Statistics says
*response* or *outcome*; machine learning says *target* or *label*
(slide 4 used *target*). Medical papers often say *validation set* for
what machine learning calls the test set. Ask what the held-back cases
were used for, not what they are called.</span>

<div class="notes">

The eGFR story contains almost every word you need for the rest of the
course.

Predictors are the inputs: creatinine, age, and sex. The response is the
thing being predicted: the measured GFR. The training set is the eight
thousand two hundred fifty-four adults whose numbers were used to fit
the equation. They came from ten studies and were mostly research
participants. About a third were Black. The validation set is a second
group held back while the model is being fit. It’s used to choose
between versions: when to stop, or which of two models to keep. The test
set is checked once, at the end. For the eGFR, that was four thousand
fifty people from twelve other studies that the equation had never seen.

Watch the names, because they vary by community. The response is what
statisticians call it; machine learning says target, which is the word
we used on the setup slide, or label. Same idea. And the eGFR paper
calls those four thousand fifty people its validation data set, and many
medical papers use the word that way. In machine learning, a group held
back and checked once at the end is the test set. When you read a paper,
ask what the held-back cases were used for, not what they are called.
And the model is the equation itself, once it has been fit.

The letter f stands for function, which just means a rule that turns
inputs into an output. Fit means that a computer tried numbers in the
equation until its predictions were as close to the measured values as
they could get. For the eGFR, the model is a handful of numbers. For a
chatbot, it’s billions. The role is the same.

</div>

## Split the data before you fit

<img src="images/data-split.svg" class="two-q" style="max-height:62vh"
data-fig-alt="Diagram. One table, one row per patient, with columns creatinine, age and sex as predictors and measured GFR as the target. The rows are cut into three groups. The training set, about 70 percent, goes to fit: the loop turns the dials on these cases only. The validation set, about 15 percent, goes to choose: when to stop, and which model to keep; a dashed arrow shows it checking the loop. The test set, about 15 percent, carries a padlock and goes to check once, at the end: the number you report. Footnote: split by patient, before any fitting; shares are typical, not a rule." />

<span class="src">The eGFR’s test set wasn’t a random slice of the same
table: it was 4,050 people from 12 other studies. That’s a harder test,
and a better one.</span>

<div class="notes">

Here are those three sets as a picture. Start with one table, one row
per patient. For the eGFR that’s creatinine, age, and sex, the
predictors, and the measured GFR, the target. Every row has the answer.

Before any fitting, you split the rows. Most go into the training set,
and that’s what the fitting loop turns the dials on. A smaller slice is
the validation set. The loop gets to look at it, to decide when to stop
and which model to keep, so think of it as a pseudo test set. And the
last slice is the test set. It’s locked. Nobody looks at it while the
model is being built, and you check it once, at the very end. That’s the
number you report.

The shares on the slide are typical, not a rule. Two details matter more
than the percentages. Split by patient, so the same person never lands
in two sets. And split before you fit, because once the test cases have
influenced a choice, they’re no longer a test.

The eGFR researchers did one better. Their test set wasn’t a random
slice of the same table. It was four thousand fifty people from twelve
other studies. A test on different people, from different studies, is
harder to pass, and it tells you more.

</div>

## One equation, three generations

| Equation | Learned from | Where it failed, and for whom |
|----|----|----|
| MDRD, 1999 | 1,070 patients who already had kidney disease (558 more held back to test it) | Values too low at higher GFR, so healthy people were labelled as diseased ([Levey et al., 1999](#ref-pmid:10075613); [Levey et al., 2009](#ref-pmid:19414839)) |
| CKD-EPI, 2009 | 8,254 people in 10 studies | Included a race term. Even with it, the equation overestimated measured GFR in Black adults (median 3.7 mL/min/1.73 m²). An overestimate makes kidney disease look milder than it is ([Inker et al., 2021](#ref-pmid:34554658)) |
| CKD-EPI, 2021 | the same 8,254, refit without race | Now a few mL/min too low in Black adults and too high in others. A version that adds cystatin C, a second blood marker that does not depend on muscle, is more accurate ([Delgado et al., 2021](#ref-pmid:34563581); [Inker et al., 2021](#ref-pmid:34554658)) |

<div class="fragment statement">

The training population is part of the model,<br>as much as the equation
is.

</div>

<div class="notes">

Here is the history of one equation, rebuilt from the numbers in the
papers. Read the last column as a pattern, not as nephrology.

MDRD was fit to one thousand and seventy people who already had kidney
disease, and tested on five hundred fifty-eight more. In healthy people
it gave values that were too low, and healthy people were labelled as
diseased. The 2009 equation was more accurate at higher GFR, and it
included a term that adjusted the result for Black race. Even with that
term, the 2021 study found it overestimated measured GFR in Black
adults. An overestimate makes kidney disease look milder than it is. The
2021 refit removed race, and it’s still a few points off in both
directions. A version that adds cystatin C, a second blood marker that
doesn’t depend on muscle, is more accurate. \[Click\]

Every row says the same thing. The model was fit to these people, and
here is what happened to people unlike them. The training population is
part of the model, as much as the equation is. That’s a take-home
message, and we’ll come back to it. Why the race term was there, and who
was harmed by it, is the subject of the bias and equity session later in
the course.

LIVE: If asked why the race term was added: in the 2009 data, Black
participants on average had a higher measured GFR at the same
creatinine, and the term adjusted for that. The 2021 paper states that
race in these equations is a social and not a biologic construct. In
2021 a national task force recommended the refit without race for all US
laboratories, and wider use of cystatin C to confirm the estimate. What
the term did to Black patients is the bias and equity chapter’s subject.

</div>

## The fitting loop

<img src="images/model-loop.svg" class="two-q" style="max-height:68vh"
data-fig-alt="Diagram. A loop of four steps: 1, guess the dials; 2, predict every training case; 3, score, adding up the misses into the loss; 4, adjust, turning the dials to lower it; then back to step 1, repeating while the loss on validation cases improves. A small chart shows two curves lap by lap: the training loss keeps falling, while the validation loss falls, bottoms out, and rises, with a line marking stop here. When validation says stop, an arrow exits the loop to step 5: test, once, on cases the loop never saw. Below, a strip: training set, then the fitting loop checked on a validation set, then a test set used once, giving a model f; a new case goes through f to give a prediction." />

<span class="src">You ran this loop by hand with the height and weight
sliders. Every model in this course is fit this way: only the data and
the number of dials change.</span>

<div class="notes">

Here is the fitting loop as a picture. You ran it by hand with the
height and weight sliders. Step one, guess the dials; anywhere will do.
Step two, use them to predict every training case. Step three, score the
predictions: add up the misses into one number, the loss. Step four,
adjust the dials in the direction that lowers the loss. Then go around
again.

When do you stop? Not when the training loss stops shrinking. Look at
the chart. The red line is the loss on the training cases, and it keeps
falling: with enough laps, a model can fit the cases it learns from
almost perfectly. The gold line is the loss on a second set of cases the
loop never fits to, the validation set. It falls, bottoms out, and then
rises. From there on, the model is memorizing the training cases, noise
included, instead of learning the pattern. That’s overfitting. So the
loop stops where the validation loss is lowest. The validation set is
also how you choose between models, for example a simple one and a
flexible one. You’ll see that happen with the slider in a few minutes.
Think of it as a pseudo test set: the loop gets to look at it, so it
can’t be the final word.

Then you leave the loop, and there’s one more step. Step five: test the
model once, on a third set of cases, the test set, that neither the loop
nor the choices ever saw. The loss inside the loop only tells you how
well the model fits the cases it was built from. The test set tells you
how it does on new ones. We’ll see what happens when that step is
skipped or done badly.

For a straight line, and for the eGFR equation, there’s a formula that
lands where the loop would end, so nobody needs to go around. For a
neural network there’s no such shortcut, and the computer really does
run this loop, many times.

The strip at the bottom is the whole path. Fitting needs training cases
with both the predictors and the truth. The loop is checked on the
validation set as it runs. The model it gives, f, is tested once on the
test set. Then a new patient arrives with only the predictors, and f
turns them into a prediction.

The eGFR, a sepsis alert in the EHR, and a chatbot differ in the data
and in how many dials they have. The loop is the same.

So there are two take-home messages here. There’s a loop. And there’s
such a thing as overfitting: a model can do very well on the cases it
was fit to, and poorly on new ones.

</div>

## Two questions sort any model

<img src="images/two-questions.svg" class="two-q"
data-fig-alt="A flow chart. Question 1: does the training data contain the answer? If no, the model is unsupervised: it finds groups, such as breast cancer subtypes. If yes, question 2: what does it output? A number means regression, such as an eGFR or an ejection fraction, and the model&#39;s errors are distances from the line. A category means classification, such as benign nevus or melanoma, and its errors are on the wrong side of a boundary." />

<div class="fragment" style="font-size:.78em">

A regression is wrong **by an amount**. A classification is wrong **by a
kind**: a miss, or a false alarm. A miss costs the patient who was
missed. A false alarm costs the staff who answer it, and the patient
worked up for nothing.

</div>

<div class="notes">

Two questions sort any model in about ten seconds. Start at the top.

First: does the training data contain the answer? For the eGFR, yes.
Someone measured every one of those GFRs. That’s supervised learning. If
there’s no answer column, only patterns to find, it’s unsupervised
learning. The example is breast cancer subtypes, which were found by
grouping tumors that share gene activity, with nobody telling the
computer the groups ([Perou et al., 2000](#ref-pmid:10963602)). We won’t
say more about unsupervised learning today, but it’s worth knowing it
exists.

Second, and only for a supervised model: what does it output? A number
makes it a regression. The eGFR is a regression, and so is a model that
reports an ejection fraction. In the picture, each red line is how far a
prediction misses. A category makes it a classification: benign or
melanoma, sepsis or no sepsis. Here the model draws a boundary, and a
case on the wrong side is an error. An unsupervised tool that sorts
cases into groups isn’t a classification, because nobody told it what
the groups were. \[Click\]

The difference matters for how you judge a model. A regression is wrong
by an amount. A classification is wrong by a kind, a miss or a false
alarm. A miss costs the patient who was missed. A false alarm costs the
staff who answer it, and the patient who is worked up for nothing.

Sometimes it isn’t obvious which kind a model is. Sorting that out is a
good first step to understanding how it was built and how it’ll be used.

</div>

## A model watches a heart ultrasound

A model watches a heart ultrasound video and reports **an ejection
fraction of 55%**. What kind of model is it?

- Regression: it gives a number
- Classification: it gives a category
- Unsupervised: it finds groups, with no answer column

<div class="notes">

Now you try, with the two questions from the last slide. First: does the
training data contain the answer? Second: is the output a category or a
number?

A model watches a heart ultrasound video and reports an ejection
fraction of fifty-five percent. What kind of model is it?

LIVE: Open the poll. Give the room about 45 seconds, then press A to
reveal. Regression: the training data contain the answer, a measured
ejection fraction, and the output is a number.

PAUSE: 1 min. Pause here and pick an answer before you go on.

This is the EchoNet-Dynamic model ([Ouyang et al.,
2020](#ref-pmid:32269341)). The same model also says whether the heart
failure has reduced ejection fraction, which is a yes or no. One system
can give a number and a category. The type of problem depends on the
output you ask for.

</div>

## A model looks at a skin photo

A model looks at a photo of a skin lesion and answers: **benign nevus or
melanoma**. What kind of model is it?

- Regression: it gives a number
- Classification: it gives a category
- Unsupervised: it finds groups, with no answer column

<div class="notes">

Same two questions. A model looks at a photo of a skin lesion and
answers: benign nevus or melanoma. What kind of model is it?

LIVE: Open the poll. About 30 seconds, then press A. Classification:
every training image carried a diagnosis, and the output is one of two
categories.

PAUSE: 1 min. Pause here and pick an answer before you go on.

This is the dermatology model from Stanford ([Esteva et al.,
2017](#ref-pmid:28117445)). A classification is wrong by a kind: it can
call a melanoma benign, or a nevus malignant. Which of those costs more
is a clinical question, and it’s yours to answer.

</div>

## Four fields, the same two questions

| Field | The model reads | It gives | Type | What the authors reported |
|----|----|----|----|----|
| Cardiology | a heart ultrasound video | ejection fraction | **Regression** | average error 4.1 points at the same hospital, 6.0 at another health system ([Ouyang et al., 2020](#ref-pmid:32269341)) |
| Pediatrics | a hand X-ray | bone age in months | **Regression** | the best of 105 entries was off by 4.2 months on average, on 200 test X-rays ([Halabi et al., 2018](#ref-pmid:30480490)) |
| Dermatology | a skin photo | benign or malignant | **Classification** | on par with 21 dermatologists, on biopsy-proven images ([Esteva et al., 2017](#ref-pmid:28117445)) |
| Pathology | a lymph node slide | cancer present or absent | **Classification** | best entry AUC 0.994; the entries ranged from 0.556 to 0.994 ([Ehteshami Bejnordi et al., 2017](#ref-pmid:29234806)) |

More of the same: an **ECG** rhythm classifier that matched
cardiologists ([Hannun et al., 2019](#ref-pmid:30617320)); **breast
tumors grouped by gene expression**, with no diagnoses attached, which
is unsupervised ([Perou et al., 2000](#ref-pmid:10963602)); and the
diabetic retinopathy system from Tuesday ([Abràmoff et al.,
2018](#ref-pmid:31304320)).

<div class="notes">

Four fields, the same two questions. Read the Type column, then the last
column.

In cardiology, a model reads ultrasound video and gives a number, the
ejection fraction. In pediatrics, a model reads a hand X-ray and gives a
bone age in months. Both are regressions: they are wrong by an amount.
In dermatology and pathology, a model reads an image and gives a
category. Both are classifications: they are wrong by a kind.

Now look at the last column, because the same lesson is in it. The
cardiology model was off by four point one points at the hospital where
it was built, and by six point zero at another health system. In the
pathology challenge, the best entry scored zero point nine nine four on
the area under the curve, and the worst scored zero point five five six,
barely better than a coin flip. Results on the authors’ own test data
are the optimistic number. Ask where it was tested, and by whom.

For the record, three more examples are listed under the table: an ECG
rhythm classifier, tumor subtypes found without any diagnoses, and the
diabetic retinopathy system from Tuesday. They are here so you can find
the papers.

LIVE: About one minute and a half. Don’t read the table. Point at the
Type column and then at the last column.

</div>

## Too simple, too flexible

``` {ojs}
viewof degree = rangeInput({ label: "Flexibility of the model", min: 0, max: 11, step: 1, value: 1 })
```

``` {ojs}
bvData = {
  let s = 20261003;
  const rand = () => { s = (s + 0x6d2b79f5) | 0; let t = Math.imul(s ^ (s >>> 15), 1 | s);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
  const gauss = () => Math.sqrt(-2 * Math.log(1 - rand())) * Math.cos(2 * Math.PI * rand());
  const truth = x => Math.sin(3 * x);
  const make = n => Array.from({ length: n }, () => { const x = rand() * 2 - 1; return { x, y: truth(x) + 0.3 * gauss() }; });
  return { truth, train: make(12), test: make(60) };
}

// Least squares in a Legendre basis, solved by modified Gram-Schmidt QR.
function basis(x, d) { const p = [1, x]; for (let k = 1; k < d; k++) p.push(((2 * k + 1) * x * p[k] - k * p[k - 1]) / (k + 1)); return p.slice(0, d + 1); }
function fitPoly(pts, d) {
  const m = pts.length, n = d + 1, Q = pts.map(p => basis(p.x, d)), R = Array.from({ length: n }, () => Array(n).fill(0));
  for (let j = 0; j < n; j++) {
    for (let i = 0; i < j; i++) { let dot = 0; for (let r = 0; r < m; r++) dot += Q[r][i] * Q[r][j]; R[i][j] = dot; for (let r = 0; r < m; r++) Q[r][j] -= dot * Q[r][i]; }
    let nrm = 0; for (let r = 0; r < m; r++) nrm += Q[r][j] ** 2; nrm = Math.sqrt(nrm); R[j][j] = nrm; for (let r = 0; r < m; r++) Q[r][j] /= nrm || 1;
  }
  const qtb = Array.from({ length: n }, (_, j) => pts.reduce((a, p, r) => a + Q[r][j] * p.y, 0)), c = Array(n).fill(0);
  for (let i = n - 1; i >= 0; i--) { let v = qtb[i]; for (let k = i + 1; k < n; k++) v -= R[i][k] * c[k]; c[i] = R[i][i] ? v / R[i][i] : 0; }
  return x => basis(x, d).reduce((a, v, k) => a + v * c[k], 0);
}
rmse = (f, pts) => Math.sqrt(pts.reduce((a, p) => a + (f(p.x) - p.y) ** 2, 0) / pts.length)
bvErrors = Array.from({ length: 12 }, (_, d) => { const f = fitPoly(bvData.train, d); return { d, train: rmse(f, bvData.train), test: rmse(f, bvData.test) }; })
bvFit = fitPoly(bvData.train, degree)
```

<div class="columns">

<div class="column" width="56%">

``` {ojs}
{
  const W = 600, H = 340, p = 14;
  const x = v => p + (v + 1) / 2 * (W - 2 * p), y = v => H / 2 - Math.max(-3, Math.min(3, v)) / 2.2 * (H / 2 - p);
  const grid = Array.from({ length: 241 }, (_, i) => -1 + i / 120);
  const path = f => "M" + grid.map(v => `${x(v).toFixed(1)},${y(f(v)).toFixed(1)}`).join("L");
  const now = bvErrors[degree];
  return html`<svg class="wchart" viewBox="0 0 ${W} ${H}" role="img" aria-label="Fitted curve at flexibility ${degree}: training error ${now.train.toFixed(2)}, test error ${now.test.toFixed(2)}.">
    <path d="${path(bvData.truth)}" fill="none" stroke="${ink.muted}" stroke-width="2" stroke-dasharray="6 6"/>
    ${bvData.test.map(d => `<circle cx="${x(d.x).toFixed(1)}" cy="${y(d.y).toFixed(1)}" r="4" fill="none" stroke="${ink.terracotta}" stroke-width="1.8"/>`).join("")}
    ${bvData.train.map(d => `<circle cx="${x(d.x).toFixed(1)}" cy="${y(d.y).toFixed(1)}" r="6.5" fill="${ink.ink}"/>`).join("")}
    <path d="${path(bvFit)}" fill="none" stroke="${ink.teal}" stroke-width="4"/>
  </svg>`;
}
```

</div>

<div class="column" width="44%">

``` {ojs}
{
  const W = 520, H = 340, l = 54, r = 16, t = 16, b = 46, cap = 1.5;
  const X = d => l + d / 11 * (W - l - r), Y = e => H - b - Math.min(e, cap) / cap * (H - b - t);
  const line = key => "M" + bvErrors.map(e => `${X(e.d).toFixed(1)},${Y(e[key]).toFixed(1)}`).join("L");
  const off = bvErrors.some(e => e.test > cap);
  const now = bvErrors[degree];
  return html`<svg class="wchart" viewBox="0 0 ${W} ${H}" role="img" aria-label="Error by flexibility. Training error falls steadily. Test error falls, then rises${off ? " off the chart" : ""}. At flexibility ${degree}: training ${now.train.toFixed(2)}, test ${now.test.toFixed(2)}.">
    ${[0, 0.5, 1, 1.5].map(v => `<line x1="${l}" x2="${W - r}" y1="${Y(v)}" y2="${Y(v)}" stroke="${ink.rule}"/><text x="${l - 8}" y="${Y(v) + 6}" fill="${ink.muted}" font-size="17" text-anchor="end">${v.toFixed(1)}</text>`).join("")}
    <line x1="${X(degree)}" x2="${X(degree)}" y1="${t}" y2="${H - b}" stroke="${ink.gold}" stroke-width="2.5"/>
    <path d="${line("train")}" fill="none" stroke="${ink.ink}" stroke-width="3"/>
    <path d="${line("test")}" fill="none" stroke="${ink.terracotta}" stroke-width="3"/>
    ${off ? `<text x="${W - r}" y="${t + 34}" fill="${ink.terracotta}" font-size="17" text-anchor="end">test error off the chart ↑</text>` : ""}
    <text x="${(l + W - r) / 2}" y="${H - 12}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle">more flexible →</text>
    <text x="16" y="${(t + H - b) / 2}" fill="${ink.inkSoft}" font-size="18" text-anchor="middle" transform="rotate(-90 16 ${(t + H - b) / 2})">error</text>
  </svg>`;
}
```

</div>

</div>

``` {ojs}
{
  const now = bvErrors[degree];
  return html`<div class="readout" role="status"><span class="k train">● Training error ${now.train.toFixed(2)}</span><span class="k test">○ Test error ${now.test > 1.5 ? "above 1.5" : now.test.toFixed(2)}</span></div>`;
}
```

<span class="defn">A model **overfits** when a simpler model would do
worse on the training cases but better on new cases from the same
population ([Mitchell, 1997](#ref-mitchell1997)).</span>

<span class="src">● learns from · ○ held back for testing · dashed: the
real pattern. Made-up data. **Bias**: error of a too-simple model.
**Variance**: error of a too-flexible one.</span>

<div class="notes">

Move the slider and watch. I simulated these data, so I know exactly
where they came from: the dashed line is the real pattern, which the
model never gets to see. The filled dots are the cases the model learns
from. The hollow dots were held back for testing, and while we fit, we
pretend they don’t exist. Under the charts, the two numbers are the
error on each set.

At the far left the model is too simple. It’s a flat or straight line
through a curve, and it’s wrong everywhere, on both sets. Statisticians
call that kind of error bias, in a technical sense that has nothing to
do with the social meaning.

Now let the model bend. As you slide right, the training error goes
down, and for a while the test error goes down with it. Then, somewhere
in the middle, something happens. The training error keeps falling, but
the test error starts to climb. Keep going and it gets kind of absurd:
the curve bends through every filled dot, the training error drops to
zero, and the test error goes off the chart. The model has learned the
random scatter in these twelve cases along with the real pattern. That
error is called variance.

That’s overfitting, and here’s the real definition, from a standard
machine learning textbook. A model overfits when a simpler model would
do worse on the training cases but better on new cases from the same
population. Two things in that definition matter. First, it compares two
models. Test error is almost always a little higher than training error,
and that alone isn’t overfitting; the sign is that more flexibility
keeps lowering the training error while the error on held-back cases
rises. Second, it’s about new cases from the same population. A model
that fails at a different hospital, on different patients, has a
different problem, and we’ll see it on the sepsis slide.

If you were only ever shown the training error, you’d pick the worst
model. This is the job of the validation set in the fitting loop: choose
the setting where the error on held-back cases is lowest, and keep a
separate test set for the end. And if you find you’ve overfit, the fix
is to go back and make the model simpler.

PAUSE: 2 min. Pause here and move the slider from one end to the other.

</div>

## A neural network: the same loop, more dials

``` {ojs}
nnLayers = [4, 5, 5, 1]
nnDials = nnLayers.slice(1).reduce((t, n, i) => t + n * nnLayers[i], 0)
```

<div class="columns">

<div class="column" width="54%">

``` {ojs}
{
  const W = 640, H = 400, pad = 56, xs = nnLayers.map((_, i) => pad + i * (W - 2 * pad) / (nnLayers.length - 1));
  const ys = (n) => Array.from({ length: n }, (_, k) => H / 2 + (k - (n - 1) / 2) * 58);
  const lines = nnLayers.slice(1).map((n, i) => ys(nnLayers[i]).flatMap(y0 => ys(n).map(y1 =>
    `<line x1="${xs[i]}" y1="${y0}" x2="${xs[i + 1]}" y2="${y1}" stroke="${ink.teal}" stroke-width="1.5" opacity=".45"/>`))).flat().join("");
  const dots = nnLayers.map((n, i) => ys(n).map(y => `<circle cx="${xs[i]}" cy="${y}" r="14" fill="${i === 0 ? ink.ink : i === nnLayers.length - 1 ? ink.terracotta : "#fffaf0"}" stroke="${ink.ink}" stroke-width="2.5"/>`).join("")).join("");
  const label = (x, t) => `<text x="${x}" y="${H - 12}" fill="${ink.inkSoft}" font-size="21" text-anchor="middle">${t}</text>`;
  return html`<svg class="wchart" viewBox="0 0 ${W} ${H}" role="img" aria-label="A small neural network: four input units, two middle layers of five units each, and one output unit. Every line between units is a dial the fit can turn. ${nnDials} lines in all.">
    ${lines}${dots}${label(xs[0], "inputs")}${label((xs[1] + xs[2]) / 2, "layers of simple units")}${label(xs[3], "output")}
  </svg>`;
}
```

<span class="defn">Illustrative. Every line is a dial. This toy network
has **\${nnDials}**; the networks behind the examples have far
more.</span>

</div>

<div class="column" width="46%">

<div class="incremental">

- The **inputs** can be the raw pixels of an image. Nobody has to write
  down what to look for.
- Each unit adds up what it receives, with its own dials, and passes the
  result on.
- **Fitting** is the same loop: turn every dial until the misses on the
  training cases get smaller.

</div>

</div>

</div>

<div class="notes">

Here is a neural network. It’s the fitting loop again, with a far bigger
equation. \[Click\]

On the left are the inputs. For an image, these can be the raw pixel
values, so nobody has to decide in advance what to look for. \[Click\]
In the middle are layers of simple units. Each unit adds up what it
receives, with its own dials, and passes the result to the next layer.
On the right is the output: a number, such as an ejection fraction, or a
category, such as melanoma. You don’t have to tell it what to look for,
but you do still need the answer for every training case. \[Click\]

Every line in this picture is a dial. This toy network has fifty. The
networks behind the four examples have far more. GPT-3, an early
ancestor of ChatGPT, had a hundred and seventy-five billion
([<span class="nocase">Brown et al.</span>, 2020](#ref-brown2020)).
Turning that many dials takes an enormous amount of computing time and
energy, which is a big part of why a new chatbot model costs so much to
build. Fitting is the loop you already know: turn every dial until the
misses on the training cases get smaller. The word deep in deep learning
means many layers.

Everything from the slider still applies. A network with that many dials
can memorize its training cases, so the test set matters even more. And
what it learned depends on the people in its training data.

LIVE: About one minute. Don’t explain the arithmetic. The picture is for
the idea of many dials, and the next section uses it for words.

</div>

## Simple models are easier to check

<div class="columns">

<div class="column" width="50%">

<div class="panel">

<span class="ptitle">Simple: the eGFR equation</span>

- A **handful of numbers**. You can read each one.
- You can check them against what you know: higher creatinine, lower
  eGFR.
- A few thousand people were enough to fit it.
- It can only follow the shape its equation allows.

</div>

</div>

<div class="column" width="50%">

<div class="panel warm">

<span class="ptitle">Flexible: a neural network</span>

- **Thousands to billions of numbers**. Nobody reads them one by one.
- It can fit complex patterns: an image, a sentence.
- It is harder to explain why it gave one answer.
- It **needs much more data**, or it fits the noise, as in the slider.

</div>

</div>

</div>

<div class="fragment statement">

More flexible: more patterns it can fit,<br>less you can explain, more
data it needs.

</div>

<span class="defn">A model is **interpretable** when a person can read
why it gave its answer.</span>

<div class="notes">

The slider showed one trade-off. Here is a second one that comes with
it.

The eGFR equation is a handful of numbers. You can read each one and
check it against what you know. Higher creatinine gives a lower eGFR,
older age gives a lower eGFR, and you could work it out by hand. A few
thousand people were enough to fit it. The cost is that it can only
follow the shape its equation allows.

A neural network has thousands to billions of numbers. It can fit
patterns no short equation can, such as what a pneumothorax looks like
on a film, or how a sentence goes on. But nobody can read those numbers
one by one, so it’s harder to explain why it gave a particular answer.
And it needs far more data, or it fits the noise, as the slider showed
at the far right. Turn that around: with only a few hundred patients,
you can’t fit a very complex model. That’s one reason the internet and
the electronic health record mattered so much in yesterday’s history.
Machine learning needs a lot of data. \[Click\]

So the more flexible the model, the more patterns it can fit, the less
you can explain, and the more data it needs. A model is called
interpretable when a person can read why it gave its answer. Yesterday’s
expert systems were very interpretable: they could tell you the rules
they followed. Neither kind is better in general. For a lab value, a
simple model you can check is often the right choice. Images and
language need the flexible kind, and then the test set matters even
more.

LIVE: One minute. Read the last line and move on.

</div>

## Cases it has already seen prove nothing

<div class="statement">

Fit a formula to 8,254 people, then report how well it predicts **those
same people**, and you have measured how well it *memorized* them.

</div>

<div class="fragment statement">

A **separate test set** is the least you should accept.<br>A number with
no test set is not a number yet.

</div>

<div class="fragment">

<span class="defn">The usual rule is three sets: a **training set** to
fit the model, a **validation set** to choose between models, and a
**test set**, used once, at the end.</span>

</div>

<div class="notes">

Here is the mistake the eGFR researchers avoided, and the mistake many
vendor slides make.

Suppose you fit a formula to eight thousand people, and then you report
how well it predicts those same eight thousand people. You have measured
how well the formula memorized them, not how well it will work on the
next patient. A flexible enough model can fit its training data
perfectly and still be useless. \[Click\]

That’s why a separate test set is the least you should accept. Fit on
one set of cases, and report performance on a different set the model
never saw. If someone tells you their model’s error is near zero, your
first question is: on the training data or the test data? Did you try it
on new data, and what was the error there? The same goes for a model
trained on synthetic patients. Synthetic training data can help, but the
test still has to be on real ones. When someone gives you one number and
doesn’t say which set it came from, you do not have a number yet.
\[Click\]

The usual rule uses three sets, the same three as in the fitting loop.
The training set is used to fit the model. The validation set is used to
choose between models, for example between a simpler one and a more
flexible one. The test set is kept aside and used once, at the end. If
the test set is used to choose, it becomes a second validation set, and
its number is optimistic too. The next slide shows why this matters.

</div>

## You saw this sepsis model yesterday

<div class="columns">

<div class="column" width="42%">

Here is why it belongs in today’s talk: it is the same failure, **on new
patients**. A vendor sepsis model, in use at hundreds of US hospitals in
2021, was tested at one academic center on **38,455 hospitalizations**
(2018 to 2019), by people who had not built it ([Wong et al.,
2021](#ref-pmid:34152373)).

- About **7 in 100** admissions had sepsis.
- Area under the curve **0.63** (0.5 is chance; 1.0 is perfect).
- It **missed 67%** of patients with sepsis.
- It **alerted on 18%** of all admissions, so most alerts were for
  patients without sepsis.

</div>

<div class="column" width="58%">

<img src="images/sepsis-alert-external-validation.svg" class="card"
data-fig-alt="One grid of 200 squares, one per admission. The first 36 squares are colored for an alert, the rest for no alert. Thirteen squares carry a dot for a patient with sepsis: 4 in alerted squares and 9 in squares with no alert."
alt="Redrawn from the counts reported by Wong et al. (2021), scaled to 200 admissions." />

<span class="defn">Testing on patients from a different setting than the
model was built on is **external validation**. Of the 13 with sepsis, 9
sat in a no-alert box. **No alert does not mean no sepsis.**</span>

</div>

</div>

<div class="notes">

You saw this model yesterday. Here is why it belongs in today’s talk:
it’s the same failure as the slider, in the clinic. One EHR vendor’s
sepsis model, the Epic Sepsis Model, was in use at hundreds of US
hospitals in 2021. One academic center tested it on thirty-eight
thousand four hundred fifty-five of its own hospitalizations. A test on
patients from a different setting than the ones the model was built on
is called external validation.

The area under the curve was zero point six three, where zero point five
is no better than chance. Of every hundred patients who had sepsis, the
model missed sixty-seven. And since it alerted on eighteen of every
hundred admissions, when only about seven had sepsis, most alerts were
for patients who didn’t.

The grid shows the same counts, scaled to two hundred admissions. Each
box is one admission. The colored boxes are the thirty-six where the
alert fired. The dots are the thirteen patients who had sepsis. Only
four of the dots sit in a colored box. The other nine had sepsis and no
alert.

The study doesn’t show why. A common reason is that the patients a model
meets in use differ from the patients it was built on. The name for that
is dataset shift.

For the wards: no alert does not mean no sepsis. And if you silence an
alert, you should be able to say why.

LIVE: About three minutes. They saw this study yesterday. Walk the grid
(colored boxes, then dots), then name the link to the test set.

</div>

## The published number is the optimistic one

<div class="statement">

A model’s performance on the cases it was built from is **usually the
optimistic number**.

</div>

<div class="fragment">

<div class="statement">

Ask what it was *trained on*.<br>Ask what it was *tested on*, and by
whom.<br>If the training set and the test set are the same cases, the
score tells you nothing yet.

</div>

<span class="defn">Better still, a hospital tests the model on its own
past patients before go-live (**localization**), then keeps checking
that it still works (**monitoring**).</span>

</div>

<div class="notes">

That gives you another of the course’s recurring lessons: the published
number is the optimistic one.

A model’s performance on the cases it was built from is usually the best
it will look. On new patients, especially from another hospital, it’s
usually lower. \[Click\] So ask three things of any model. What was it
trained on? What was it tested on, and by whom? If the training set and
the test set are the same cases, the score tells you nothing yet,
because the model has already seen the answers.

Why ask what it was trained on? Because the training data are part of
the model. A model built on patients in one country, or at one kind of
hospital, may not work the same way somewhere else.

Often you won’t get the details: companies frequently don’t publish what
a commercial model was trained or tested on. Ask anyway. But there’s a
second answer that doesn’t depend on them, which is to test it on your
own patients. I sit on the committee that vets machine learning models
for our health system, and one of our first questions about any model is
what it will cost to localize. Localizing means running the model on our
own past patients, say the last six months or the last few years, and
scoring it against what we know happened. Take a fall-risk model. Of the
patients who fell, how many did the alert fire for? And how often did it
fire for patients who didn’t fall? Sometimes the next step is to tune
the model to fit our patients better.

And it doesn’t stop at go-live. Patients change over time, and so do the
records; even the codes used for diagnoses have changed their
definitions over the years. So someone has to keep monitoring the model
to make sure it still works.

</div>

## A language model is the same loop, with a different table

*the patient was started on empiric vancomycin*

| Predictors: the words so far       | Truth: the next word |
|------------------------------------|----------------------|
| the                                | patient              |
| the patient                        | was                  |
| the patient was                    | started              |
| the patient was started            | on                   |
| the patient was started on         | empiric              |
| the patient was started on empiric | vancomycin           |

<div class="fragment">

Nobody labels anything. **Every sentence ever written is already a table
like this.**

</div>

<div class="notes">

Take any sentence from the internet. The patient was started on empiric
vancomycin.

Now turn it into a table, the same kind of table as the eGFR. The
predictors are the words so far. The truth is the very next word. “The”
is followed by “patient”. “The patient” is followed by “was”. And so on,
until “the patient was started on empiric” is followed by “vancomycin”.
You might ask: lots of sentences start with “The”, and many different
words can come next. That’s why the model uses all the words so far, not
just the last one. The more of the sentence it sees, the better it can
guess. \[Click\]

Nobody had to label anything, because the next word is already in the
text. Every sentence ever written becomes training cases with no
labelling work. That’s why these models could be trained on a very large
fraction of the written internet.

Thursday starts here: a chatbot is machine learning on words, and the
same test-set questions apply.

LIVE: About one minute. Stop here. This is the bridge to Thursday.

</div>

## <span class="year">Part 3</span> What this changes for you

<div class="notes">

Last few minutes. Back to the clinic.

</div>

## Back to the pre-op clinic

<div class="columns">

<div class="column" width="50%">

<div class="incremental">

- She weighs **45 kg** and has lost muscle over a year of illness.
- Creatinine comes from **muscle**.
- Her creatinine of 1.10 is a normal number for a normal amount of
  muscle. She does not have a normal amount of muscle.
- So her true GFR is probably **lower than 57**.

</div>

</div>

<div class="column" width="50%">

<div class="fragment">

Name her model’s predictors, response, and training population.

**What do you now say to the attending?**

</div>

<div class="fragment">

*“The 57 is estimated from her creatinine. With her low muscle mass it
probably overstates her kidney function. If the decision depends on it,
I would ask for a cystatin C-based eGFR.”*

It matters most for a renally dosed drug, or if her true value could be
near your radiology department’s contrast threshold. A national task
force recommends wider use of cystatin C, especially to confirm the
estimate ([Delgado et al., 2021](#ref-pmid:34563581)).

</div>

</div>

</div>

<span class="src">Nobody told her a model was involved. Whether someone
should have is a question for the patient communication session.</span>

<div class="notes">

Go back to the clinic. The sixty-two-year-old woman is still on the
schedule.

\[Click\] She weighs forty-five kilograms and has lost muscle over a
year of illness. \[Click\] Creatinine comes from muscle. \[Click\] Her
creatinine of one point one is a normal number for a normal amount of
muscle, and she doesn’t have a normal amount of muscle. \[Click\] So the
same creatinine probably means a lower true GFR than the fifty-seven the
equation returned. Her low muscle mass changes the predictor without
changing the truth, and nothing in the equation can tell. \[Click\]

Name her model’s predictors, response, and training population. And what
do you now say to the attending about the number on the panel?

PAUSE: 2 min. Pause here and answer out loud or in writing.

\[Click\] Here is one good answer. “The fifty-seven is estimated from
her creatinine. With her low muscle mass it probably overstates her
kidney function. If the decision depends on it, I would ask for a
cystatin C-based eGFR.” Whether it changes the contrast decision depends
on how close her true value could be to your department’s threshold. It
matters more for a drug dosed by kidney function. The training
population was eight thousand two hundred fifty-four adult research
participants. Nothing in the equation knows how much muscle she has.

Notice also that nobody told her a model had a part in her care. Whether
someone should have is a question for the patient communication session.

LIVE: Ask for answers in the chat, sent on your count. Cystatin C is the
answer you’re looking for, with the caveat that cystatin C has limits of
its own and illness can affect it. A measured clearance only if the
decision really depends on it.

</div>

## On the wards

<div class="columns">

<div class="column" width="62%">

- **An alert fires, or stays quiet.** No alert does not mean no sepsis.
  If you silence one, be able to say why.
- **A tool comes with an accuracy number.** Ask what it was tested on,
  and by whom. A score on the cases it was built from is the optimistic
  one.
- **A tool drafted your note or message.** Read all of it before you
  sign. You are responsible for what it says.
- **Patient information.** Not in any chatbot, unless your hospital’s
  privacy office has approved that tool in writing.
- **Documentation.** What to record when a tool helped depends on your
  site’s policy. Ask your supervisor.

</div>

<div class="column" width="38%">

<div class="fragment panel warm statement">

Ask what it was tested on,<br>and by whom.

</div>

</div>

</div>

<div class="notes">

Here is what this means on the wards tomorrow.

When an alert fires, or stays quiet, remember the sepsis model. No alert
does not mean no sepsis. If you silence one, you should be able to say
why.

When a tool comes with an accuracy number, ask what it was tested on,
and by whom. A score on the cases the model was built from is the
optimistic number.

When a tool has drafted your note or message, read all of it before you
sign. You’re responsible for what it says. What to document when a tool
helped depends on your site’s policy, and the ethics and regulation
session covers it.

And patient information goes into no chatbot, unless your hospital’s
privacy office has approved that tool in writing. \[Click\]

If you tell a co-intern one sentence, make it this one. Ask what it was
tested on, and by whom.

</div>

## The standing question

<div class="statement">

*What does this change about how you’ll practice?*

</div>

<div class="fragment">

Write it down before you close this deck. Then one more: the next time
someone shows you a model’s accuracy number, **what will you ask in the
next two minutes?** For example: *what was it tested on, and by whom?*

</div>

<div class="fragment">

**Thursday:** we start with the chatbot, the same loop with words. Then
we open ChatGPT Edu together and use it to set up your own learning
goals and journal.

</div>

<div class="notes">

Every session ends on the same question. What does this change about how
you’ll practice? \[Click\] Write your answer down before you close this
deck. It is not optional, and one word isn’t an answer.

PAUSE: 2 min. Pause here and write it.

Then one more. The next time someone shows you a model’s accuracy
number, what will you ask in the next two minutes? For example: what was
it tested on, and by whom? Some of you’ll be in exactly that spot. A
company shows up at lunch with a model that predicts which of your
clinic patients will end up in the emergency department, and asks, don’t
you want to catch those patients? There are a lot of questions to ask
before you say yes, and I hope today’s slides help with some of them.
\[Click\]

Thursday: we start with the chatbot, the same loop with words. Then we
open ChatGPT Edu together and use it to set up your own learning goals
and journal.

</div>

## Summary

- **AI** contains **machine learning**, which contains **deep
  learning**, which contains **generative AI**.
- The eGFR is a **model learned from data**: creatinine, age, and sex
  in; a stand-in for measured GFR out; 8,254 adults as the population it
  was fit to. **Fitting** chose its numbers to make the error as small
  as possible.
- **Supervised** has the answer in the training data; **unsupervised**
  does not. **Classification** gives a category; **regression** gives a
  number.
- A model that is too flexible fits the noise: it **overfits**. A
  **neural network** is the same loop with far more dials, so it needs
  more data, and a test set matters even more.
- A score on the training data describes the fit. **Ask for the test
  set, and who ran it.**
- **Thursday:** a language model is the same loop, with words.

<div class="notes">

To sum up. AI contains machine learning, which contains deep learning,
which contains generative AI.

The eGFR is a model learned from data, with predictors, a response, and
a population it was fit to. Fitting chose its numbers to make the error
as small as possible. Every learned model has those three parts, and you
can ask for them.

Supervised learning has the answer in the training data; unsupervised
doesn’t. Classification gives a category; regression gives a number.

A model that’s too flexible fits the noise. That’s overfitting: a
simpler model would have done better on new patients. A neural network
is the same loop with far more dials, so it needs more data, and a test
set matters even more.

A score on the training data describes the fit. It is not evidence. Ask
for the test set, and who ran it.

Thursday, the same loop with words.

The fifty-seven on the panel came from somewhere. So does everything
else.

LIVE: Skip in a live session.

</div>

## Further exploration

<div class="columns">

<div class="column" width="50%">

<div class="panel warm">

<span class="ptitle">Try: overfitting you can see</span>

**[TensorFlow Playground](https://playground.tensorflow.org)**, 10
minutes, no account. Open [step
1](https://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=spiral&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=50&networkShape=2&seed=0.12345&showTestData=true&discretize=false&percTrainData=20&x=true&y=true&xTimesY=false&xSquared=false&ySquared=false&cosX=false&sinX=false&cosY=false&sinY=false&collectStats=false&problem=classification&initZero=false&hideText=false)
(too simple), [step
2](https://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=spiral&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=50&networkShape=6,6&seed=0.12345&showTestData=true&discretize=false&percTrainData=20&x=true&y=true&xTimesY=false&xSquared=false&ySquared=false&cosX=false&sinX=true&cosY=false&sinY=true&collectStats=false&problem=classification&initZero=false&hideText=false)
(in between) and [step
3](https://playground.tensorflow.org/#activation=tanh&batchSize=10&dataset=spiral&regDataset=reg-plane&learningRate=0.03&regularizationRate=0&noise=50&networkShape=8,8,8,8,8,8&seed=0.12345&showTestData=true&discretize=false&percTrainData=20&x=true&y=true&xTimesY=true&xSquared=true&ySquared=true&cosX=false&sinX=true&cosY=false&sinY=true&collectStats=false&problem=classification&initZero=false&hideText=false)
(too flexible). At about 500 epochs, compare **training loss** with
**test loss**.

</div>

</div>

<div class="column" width="50%">

<div class="panel">

<span class="ptitle">Read: when the patients change</span>

**[*The clinician and dataset shift in
AI*](https://doi.org/10.1056/NEJMc2104626)**, *NEJM*, 2021. Why models
fail on new kinds of patients.

</div>

</div>

</div>

<div class="aside">

Dataset shift: ([Finlayson et al., 2021](#ref-pmid:34260843)). Drafted
with an AI assistant.

</div>

<div class="notes">

For students who want more, two things to follow by interest.

Try: the TensorFlow Playground exercise. Each link opens the site
already set up, with the same noisy spiral and only one dot in five used
for training. Press play, pause at about five hundred epochs, and
compare training loss with test loss. One run gave: step one, both near
0.5; step two, training near 0.09 and test near 0.27; step three,
training near 0.1 but test near 0.55. That’s overfitting: training loss
makes steps two and three look alike, but on new dots step three is the
worst. Don’t press regenerate, which changes the data.

Read: a two-page paper on dataset shift, written for clinicians.

LIVE: Skip in a live session.

</div>

## References

<div id="refs" class="references csl-bib-body hanging-indent"
data-entry-spacing="0" data-line-spacing="2">

<div id="ref-pmid:31304320" class="csl-entry">

Abràmoff, M. D., Lavin, P. T., Birch, M., Shah, N., & Folk, J. C.
(2018). Pivotal trial of an autonomous
<span class="nocase">AI-based</span> diagnostic system for detection of
diabetic retinopathy in primary care offices. *NPJ Digital Medicine*,
*1*, 39. <https://doi.org/10.1038/s41746-018-0040-6>

</div>

<div id="ref-brown2020" class="csl-entry">

<span class="nocase">Brown, T. B., Mann, B., Ryder, N., Subbiah, M.,
Kaplan, J., et al.</span> (2020). Language models are few-shot learners.
*Advances in Neural Information Processing Systems 33 (NeurIPS 2020)*.
<https://arxiv.org/abs/2005.14165>

</div>

<div id="ref-pmid:34563581" class="csl-entry">

Delgado, C., Baweja, M., Crews, D. C., Eneanya, N. D., Gadegbeku, C. A.,
Inker, L. A., Mendu, M. L., Miller, W. G., Moxey-Mims, M. M., Roberts,
G. V., St Peter, W. L., Warfield, C., & Powe, N. R. (2021). A Unifying
Approach for GFR Estimation: Recommendations of the NKF-ASN Task Force
on Reassessing the Inclusion of Race in Diagnosing Kidney Disease.
*American Journal of Kidney Diseases : The Official Journal of the
National Kidney Foundation*, *79*(2), 268–288.e1.
<https://doi.org/10.1053/j.ajkd.2021.08.003>

</div>

<div id="ref-pmid:29234806" class="csl-entry">

Ehteshami Bejnordi, B., Veta, M., Johannes van Diest, P., Ginneken, B.
van, Karssemeijer, N., Litjens, G., Laak, J. A. W. M. van der, Hermsen,
M. and, Manson, Q. F., Balkenhol, M., Geessink, O., Stathonikos, N.,
Dijk, M. C. van, Bult, P., Beca, F., Beck, A. H., Wang, D., Khosla, A.,
Gargeya, R., … Venâncio, R. (2017). Diagnostic Assessment of Deep
Learning Algorithms for Detection of Lymph Node Metastases in Women With
Breast Cancer. *JAMA*, *318*(22), 2199–2210.
<https://doi.org/10.1001/jama.2017.14585>

</div>

<div id="ref-pmid:28117445" class="csl-entry">

Esteva, A., Kuprel, B., Novoa, R. A., Ko, J., Swetter, S. M., Blau, H.
M., & Thrun, S. (2017). Dermatologist-level classification of skin
cancer with deep neural networks. *Nature*, *542*(7639), 115–118.
<https://doi.org/10.1038/nature21056>

</div>

<div id="ref-pmid:34260843" class="csl-entry">

Finlayson, S. G., Subbaswamy, A., Singh, K., Bowers, J., Kupke, A.,
Zittrain, J., Kohane, I. S., & Saria, S. (2021). The Clinician and
Dataset Shift in Artificial Intelligence. *The New England Journal of
Medicine*, *385*(3), 283–286. <https://doi.org/10.1056/nejmc2104626>

</div>

<div id="ref-pmid:30480490" class="csl-entry">

Halabi, S. S., Prevedello, L. M., Kalpathy-Cramer, J., Mamonov, A. B.,
Bilbily, A., Cicero, M., Pan, I., Pereira, L. A., Sousa, R. T., Abdala,
N., Kitamura, F. C., Thodberg, H. H., Chen, L., Shih, G., Andriole, K.,
Kohli, M. D., Erickson, B. J., & Flanders, A. E. (2018). The RSNA
Pediatric Bone Age Machine Learning Challenge. *Radiology*, *290*(2),
498–503. <https://doi.org/10.1148/radiol.2018180736>

</div>

<div id="ref-pmid:30617320" class="csl-entry">

Hannun, A. Y., Rajpurkar, P., Haghpanahi, M., Tison, G. H., Bourn, C.,
Turakhia, M. P., & Ng, A. Y. (2019). Cardiologist-level arrhythmia
detection and classification in ambulatory electrocardiograms using a
deep neural network. *Nature Medicine*, *25*(1), 65–69.
<https://doi.org/10.1038/s41591-018-0268-3>

</div>

<div id="ref-pmid:34554658" class="csl-entry">

Inker, L. A., Eneanya, N. D., Coresh, J., Tighiouart, H., Wang, D.,
Sang, Y., Crews, D. C., Doria, A., Estrella, M. M., Froissart, M.,
Grams, M. E., Greene, T., Grubb, A., Gudnason, V., Gutiérrez, O. M.,
Kalil, R., Karger, A. B., Mauer, M., Navis, G., … Levey, A. S. and.
(2021). New Creatinine- and Cystatin C-Based Equations to Estimate GFR
without Race. *The New England Journal of Medicine*, *385*(19),
1737–1749. <https://doi.org/10.1056/nejmoa2102953>

</div>

<div id="ref-pmid:10075613" class="csl-entry">

Levey, A. S., Bosch, J. P., Lewis, J. B., Greene, T., Rogers, N., &
Roth, D. (1999). A more accurate method to estimate glomerular
filtration rate from serum creatinine: A new prediction equation.
Modification of Diet in Renal Disease Study Group. *Annals of Internal
Medicine*, *130*(6), 461–470.
<https://doi.org/10.7326/0003-4819-130-6-199903160-00002>

</div>

<div id="ref-pmid:19414839" class="csl-entry">

Levey, A. S., Stevens, L. A., Schmid, C. H., Zhang, Y. L., Castro, A.
F., Feldman, H. I., Kusek, J. W., Eggers, P., Van Lente, F., Greene, T.,
& Coresh, J. and. (2009). A new equation to estimate glomerular
filtration rate. *Annals of Internal Medicine*, *150*(9), 604–612.
<https://doi.org/10.7326/0003-4819-150-9-200905050-00006>

</div>

<div id="ref-mitchell1997" class="csl-entry">

Mitchell, T. M. (1997). *Machine learning*. McGraw-Hill.

</div>

<div id="ref-pmid:32269341" class="csl-entry">

Ouyang, D., He, B., Ghorbani, A., Yuan, N., Ebinger, J., Langlotz, C.
P., Heidenreich, P. A., Harrington, R. A., Liang, D. H., Ashley, E. A.,
& Zou, J. Y. (2020). Video-based AI for beat-to-beat assessment of
cardiac function. *Nature*, *580*(7802), 252–256.
<https://doi.org/10.1038/s41586-020-2145-8>

</div>

<div id="ref-pmid:10963602" class="csl-entry">

Perou, C. M., Sørlie, T., Eisen, M. B., Rijn, M. van de, Jeffrey, S. S.,
Rees, C. A., Pollack, J. R., Ross, D. T., Johnsen, H., Akslen, L. A.,
Fluge, O., Pergamenschikov, A., Williams, C., Zhu, S. X., Lønning, P.
E., Børresen-Dale, A. L., Brown, P. O., & Botstein, D. (2000). Molecular
portraits of human breast tumours. *Nature*, *406*(6797), 747–752.
<https://doi.org/10.1038/35021093>

</div>

<div id="ref-pmid:34152373" class="csl-entry">

Wong, A., Otles, E., Donnelly, J. P., Krumm, A., McCullough, J.,
DeTroyer-Cooley, O., Pestrue, J., Phillips, M., Konye, J., Penoza, C.,
Ghous, M., & Singh, K. (2021). External Validation of a Widely
Implemented Proprietary Sepsis Prediction Model in Hospitalized
Patients. *JAMA Internal Medicine*, *181*(8), 1065–1070.
<https://doi.org/10.1001/jamainternmed.2021.2626>

</div>

</div>

``` {ojs}
//| output: false
// The theme's palette (theme.scss), for the widget charts drawn in SVG.
ink = ({ ink: "#24211d", inkSoft: "#4a443c", muted: "#7a7064", rule: "#d8ccb6",
  teal: "#1f5a61", terracotta: "#a9472a", gold: "#b98a2f" })
// Plain HTML controls for the widgets, so the slides need no input library.
function rangeInput({ label, min, max, step, value }) {
  const form = html`<form class="w-range" onsubmit="return false"><label><span>${label}</span><input type="range" min="${min}" max="${max}" step="${step}" value="${value}" aria-label="${label}"><output>${value}</output></label></form>`;
  const input = form.querySelector("input"), out = form.querySelector("output");
  form.value = +input.value;
  input.addEventListener("input", () => { form.value = +input.value; out.textContent = input.value; });
  return form;
}
function textInput({ label, value }) {
  const form = html`<form class="w-text" onsubmit="return false"><label><span>${label}</span><input type="text" aria-label="${label}"></label></form>`;
  const input = form.querySelector("input");
  input.value = value;
  form.value = value;
  input.addEventListener("input", () => { form.value = input.value; });
  return form;
}
function radioInput(options, name) {
  const form = html`<form class="w-radio" onsubmit="return false">${options.map((o, i) => html`<label><input type="radio" name="${name}" value="${i}"> ${o}</label>`)}</form>`;
  form.value = null;
  form.addEventListener("change", () => { const c = form.querySelector("input:checked"); form.value = c ? +c.value : null; });
  return form;
}
```

## Questions

<div class="notes">

Let’s take questions.

LIVE: Jump here from slide 31 with the End key; this is the last slide.
Thursday starts with the chatbot half of this story, and then everyone
opens ChatGPT Edu.

</div>
