5.3.12 · D2MLOps & Deployment

Visual walkthrough — Model monitoring and observability

2,336 words11 min readBack to topic

We build one running example the whole way down: yesterday's users vs today's users, sorted into three age buckets.


Step 1 — Two piles of data become two bar charts

WHAT. We have two batches of a single number (say user age): the reference batch (the data the model trained on) and the new batch (data arriving live today). We chop the number-line into some number of side-by-side bins (buckets), and count how many points fall in each bin. We call that number of bins .

WHY. We cannot compare two clouds of raw numbers directly — there is no single "distance" between two heaps. But if we turn each heap into a bar chart (a histogram), we get heights we can compare bin-by-bin. Binning is the trick that converts "compare two distributions" into "compare pairs of numbers."

PICTURE. Two histograms over the same three age bins (so here ). The pale-yellow bars are yesterday (reference), the blue bars are today (new). Notice they disagree — the young bin shrank, the old bin grew.

Figure — Model monitoring and observability

Step 2 — Turn counts into fractions so size doesn't fool us

WHAT. Divide every bin's count by the total number of points in its batch. Now each bar is a fraction between and , and the bars of one batch sum to .

  • — the expected fraction (reference), the height of bin in yesterday's chart.
  • — the actual fraction (new), the height of bin in today's chart.
  • the subscript — "which bin", running .

WHY. Yesterday we might have had users and today only . Raw counts would scream "everything shrank!" even if the shape is identical. Dividing by the total strips away batch size and leaves pure shape — the thing drift actually cares about. After this step both charts are on the same -to- scale.

PICTURE. The same two histograms, now rescaled: each colour's bars add up to a full block of height . Only the shape difference remains.

Figure — Model monitoring and observability

Step 3 — The first honest attempt: the raw gap

WHAT. For each bin, subtract yesterday's fraction from today's fraction:

  • — the bin grew (today has a bigger share here).
  • — the bin shrank.
  • — the bin is unchanged.

WHY. This is the most obvious "how different?" measure — literally the height difference of the two bars. It is our starting sketch. We will see it has a flaw, and fixing that flaw builds the rest of PSI.

PICTURE. For each bin, an arrow from the yellow bar top to the blue bar top: up-arrows (grew, pink) and down-arrows (shrank). The lengths are the raw gaps .

Figure — Model monitoring and observability

Step 4 — A relative gap: why we reach for the logarithm

WHAT. Look not at the plain gap but at the ratio , then take its natural logarithm:

  • — "how many times bigger is today's share than yesterday's?" A ratio of means doubled; means halved.
  • — the natural logarithm, the tool that turns "times bigger" into a symmetric, additive scale.

WHY this tool and not another? A plain gap treats (a share that doubled) the same as (barely nudged) — both have gap . But doubling a tiny bin is a big relative event. We want a measure of "how many-fold did this move", and that is exactly what a ratio answers. We then wrap it in because:

  • — no change () gives zero, as it should.
  • of "doubled" () and of "halved" () are equal in size, opposite in sign — growth and shrinkage by the same factor are treated even-handedly. Plain ratios don't do this ( vs look lopsided).

This is the same log-of-a-ratio idea behind KL divergence and cross-entropy — it is the natural currency for "distance between two probability shapes."

PICTURE. The log-ratio curve plotted against the ratio : crossing zero at ratio , symmetric on a log x-axis, one bin marked as "doubled" (positive) and one as "halved" (negative).

Figure — Model monitoring and observability

Step 5 — Multiply gap × log-ratio: the sign trick that stops cancellation

WHAT. Combine the two pieces we built — the raw gap and the log-ratio — by multiplying them per bin:

  • how much the share moved (the size).
  • how many-fold it moved (the direction & relative scale).

WHY multiply, and why this kills cancellation. Watch the two factors move together:

Case product
bin grew () positive
bin shrank () positive
bin unchanged ()

Both factors always share the same sign (a bin that grew has a bigger share AND a ratio above ; a bin that shrank has both below). So their product is never negative. This is the elegant fix for Step 3's cancellation problem: instead of adding signed gaps that cancel, we add per-bin products that are all . Every deviation now contributes, none cancel.

PICTURE. For the grown bin and the shrunk bin, a little "sign multiplication" box: and , both arrows pointing up into a positive pile.

Figure — Model monitoring and observability

Step 6 — Sum over all bins: one number for the whole shift

WHAT. Add up every bin's non-negative contribution:

  • — "walk across all bins and add", where is the number of bins we defined in Step 1.
  • the boxed result is a single number , equal to only when every bin matches ( for all ).

WHY sum. Each bin told us its own drift. Drift of the whole feature is the total, because a distribution moved a lot if it moved a lot somewhere. Summing non-negative pieces means drift in one bin can never be hidden by "un-drift" elsewhere.

PICTURE. A stacked bar: each bin's positive contribution stacked on top of the last, the final height labelled PSI. Threshold lines at (green, stable) and (pink, act) drawn across.

Figure — Model monitoring and observability

Step 7 — The degenerate case: an empty bin blows the log up

WHAT. Suppose a bin has today but in the reference (a brand-new region never seen in training). Then and PSI explodes to infinity. The mirror case , gives .

WHY it matters and the fix. A zero in a denominator is a division-by-zero — mathematically undefined, numerically a inf or nan. But an empty bin is real: rare categories, first-day-of-a-launch data. The standard fix is to add a tiny floor (e.g. ) to every fraction, so no bin is ever exactly : Because we nudged the shares, they no longer add up to exactly . So we re-normalise afterwards — divide every floored fraction by its (new) batch total — restoring and . The distributions are not corrupted; the floor only prevents an exact zero, and the re-normalisation keeps both bars honest probability shapes. This keeps PSI finite while still reporting a large value (a genuinely new bin should scream drift). This is exactly the "data validation" concern of Feature stores and data validation.

The both-empty sub-case. If a bin is empty in both batches (), after flooring both become , so Good: a bin that never had anyone, before or after, contributes nothing — exactly the "no drift here" answer we want.

PICTURE. Left panel: the log-ratio curve shooting to as . Right panel: the same curve with the floor, capped at a large-but-finite value.

Figure — Model monitoring and observability

The one-picture summary

Everything at once: raw counts → fractions → per-bin (gap × log-ratio) → summed to one PSI, with the threshold verdict.

Figure — Model monitoring and observability
Recall Feynman retelling — the whole walk in plain words

Imagine two photos of a party crowd, one from last week and one from today. First I split the room into a few zones and count heads (Step 1). Because the crowd size differs, I switch from head-counts to shares — "what fraction of people are in each zone" — so a busier night doesn't fool me (Step 2). For each zone I check the change in share (Step 3), but I notice the gains and losses cancel to zero if I just add them, so that alone is useless. So I also measure the fold-change — did a zone double or halve? — using a logarithm, because a log makes "doubled" and "halved" mirror each other neatly (Step 4). Then the magic: I multiply the share-change by the fold-change. Because a zone that grew is positive in both, and a zone that shrank is negative in both, every product comes out positive — nothing cancels anymore (Step 5). I add up all the zones into one number, PSI (Step 6): means the two photos match perfectly, big means the crowd rearranged a lot. Finally I guard against an empty zone that would make the fold-change infinite by adding a tiny floor and re-normalising before dividing; a zone empty in both photos just contributes zero (Step 7). One number, and I know whether today's crowd is still the crowd my model was built for.


Connections

Why divide bin counts by the total before computing PSI?
To convert counts into fractions (shares) so different batch sizes don't distort the comparison — drift is about shape, not volume.
Why can't we just sum the raw gaps ?
Because both distributions sum to 1, the signed gaps always cancel to 0; the gains and losses offset each other.
Why is each PSI term non-negative?
The gap and the log-ratio always share the same sign, so their product is (0 only when ).
Why use a logarithm of the ratio instead of the plain ratio?
The log makes equal fold-changes symmetric () and gives 0 for no change; a plain ratio treats growth and shrinkage lopsidedly.
What breaks PSI when a bin is empty, and how is it fixed?
makes ; fix by flooring every fraction with a tiny and re-normalising so the shares still sum to 1.
What does a bin empty in both batches contribute after flooring?
Zero — both floored to , so .