MediumMLOps

Detect Train-Serving Skew

MLOps

Medium

Problem

Measure train-serving distribution shift with the Population Stability Index for each feature:

\operatorname{PSI}=\sum_{i=1}^{B}(s_i-t_i)\ln\left(\frac{s_i}{t_i}\right)

Here, B is the number of bins, t_i is a training proportion, and s_i is its serving proportion. Add eps to both proportions before evaluating each term. A feature is skewed when its PSI is at least threshold. Return each PSI rounded to six decimals with its boolean skewed flag in a nested dictionary.

Theory

One of the most frustrating scenarios in machine learning is when a model performs excellently during training and validation, but degrades mysteriously once deployed. Often, the culprit is train-serving skew, a mismatch between the data distribution the model was trained on and the data it encounters in production.

This skew can arise from many sources:

Detecting skew early is critical. If left unchecked, model performance silently degrades, predictions become unreliable, and downstream business decisions suffer.


Measuring Distribution Shift with PSI

The Population Stability Index (PSI) is a standard metric for quantifying how much a distribution has changed. Originally developed in credit risk modeling, it has become a go-to tool for monitoring ML feature distributions.

PSI compares two probability distributions (one from training as the reference, one from production as the target) by measuring the divergence bin by bin.


The Mathematical Definition

Given a feature discretized into n bins, let P_i^{train} be the proportion of training samples in bin i, and P_i^{serving} be the proportion of production samples in bin i.

The PSI is computed as:

PSI = \sum_{i=1}^{n} (P_i^{serving} - P_i^{train}) \times \ln\left(\frac{P_i^{serving}}{P_i^{train}}\right)

Each term in the sum captures both the magnitude of the difference (P_i^{serving} - P_i^{train}) and the relative change (\ln(P_i^{serving} / P_i^{train})). This makes PSI sensitive to both absolute and proportional shifts.


Interpreting PSI Values

Industry practice typically uses these thresholds:

A PSI above threshold indicates that the feature distribution in production has drifted meaningfully from training. This could signal that:


Handling Edge Cases

Zero-probability bins: If a bin has zero probability in either distribution, the logarithm and division become undefined. The standard fix is to add a small smoothing constant \epsilon (typically 10^{-10} to 10^{-6}) to all bin proportions:

P_i \leftarrow P_i + \epsilon

This ensures numerical stability while minimally affecting the PSI value for bins with non-zero counts.


Step-by-Step Computation

Given training proportions [P_1^{train}, P_2^{train}, ..., P_n^{train}] and serving proportions [P_1^{serving}, P_2^{serving}, ..., P_n^{serving}]:

Step 1: Add smoothing constant to prevent division by zero

P_i \leftarrow P_i + \epsilon \quad \text{for all } i

Step 2: For each bin i, compute the contribution:

\text{term}_i = (P_i^{serving} - P_i^{train}) \times \ln\left(\frac{P_i^{serving}}{P_i^{train}}\right)

Step 3: Sum all terms:

PSI = \sum_{i=1}^{n} \text{term}_i

Step 4: Compare against threshold to flag skew


A Concrete Example

Suppose a feature has 5 bins with these proportions:

Computing PSI:

Total PSI = 0.0091 + 0.0112 + 0.0112 + 0.0144 + 0 = 0.0459

With a threshold of 0.1, this feature would not be flagged as skewed.


Where Train-Serving Skew Detection Shows Up

ML Model Monitoring: Production ML systems continuously compare incoming feature distributions against training baselines to detect drift before it impacts predictions.

A/B Testing: Before rolling out a new model, PSI helps verify that test and control groups have similar feature distributions, ensuring valid comparisons.

Data Quality Pipelines: ETL systems use PSI to catch upstream data changes that could propagate errors downstream.

Regulatory Compliance: In finance and healthcare, demonstrating that production data remains representative of training data is often a regulatory requirement.

Examples

Example 1

Input
train_dist = {"age": [0.1, 0.2, 0.3, 0.25, 0.15], "income": [0.2, 0.2, 0.2, 0.2, 0.2]}, serving_dist = {"age": [0.05, 0.1, 0.15, 0.35, 0.35], "income": [0.2, 0.2, 0.2, 0.2, 0.2]}, threshold = 0.2, eps = 1e-10
Output
{"age": {"psi": 0.411051, "skewed": true}, "income": {"psi": 0, "skewed": false}}
Explanation
The age distribution exceeds the threshold, while the identical income distributions have zero PSI.

Example 2

Input
train_dist = {"clicks": [0.3, 0.4, 0.2, 0.1]}, serving_dist = {"clicks": [0.25, 0.35, 0.25, 0.15]}, threshold = 0.2, eps = 1e-10
Output
{"clicks": {"psi": 0.047223, "skewed": false}}

Hints

  1. Convert each pair of bin lists with np.asarray(..., dtype=float) + eps.
  2. Compute one feature with np.sum((serving - train) * np.log(serving / train)).

Requirements

Constraints

Starter Code

import numpy as np

def detect_skew(train_dist: dict, serving_dist: dict, threshold: float = 0.2, eps: float = 1e-10) -> dict:
    """
    Returns a dictionary of feature PSI scores and skew flags.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Age skewed, income notExample 1public
Gentle shift below thresholdExample 2public