Agent Wranglers

MLO-39Recommended

Check every new snapshot against the last one before training

Before a training run, compare the new snapshot with the schema and statistics of the snapshot behind the production model. Stop on a missing or new column, a row count change over the set limit or a label share that moved over its limit.

How to do it

A broken export, a changed upstream field or a batch of poisoned rows trains a model that still passes many tests. Google's MLOps guide puts this check before training and stops the pipeline on schema problems. Save the schema, row count and label shares with every snapshot. Set the limits per model, such as a 20% change in rows or 5 points in any label's share. Also flag a source or account that supplies far more rows than before, because injected data often shows up that way. A stopped run leaves the current model serving, as Retrain on written triggers says. Serving input gets the same kind of check in Check every input against what the model was trained on.

Examples

Python example

MAX_ROW_CHANGE = 0.20
MAX_LABEL_SHIFT = 0.05

def snapshot_problems(new: SnapshotStats, last: SnapshotStats) -> list[str]:
    problems = [f"column {c} missing" for c in last.columns - new.columns]
    problems += [f"column {c} is new" for c in new.columns - last.columns]
    if abs(new.rows - last.rows) / last.rows > MAX_ROW_CHANGE:
        problems.append(f"rows changed from {last.rows} to {new.rows}")
    for label, share in last.label_shares.items():
        if abs(new.label_shares.get(label, 0.0) - share) > MAX_LABEL_SHIFT:
            problems.append(f"label {label} share moved from {share:.2f}")
    return problems

TypeScript example

const MAX_ROW_CHANGE = 0.2;
const MAX_LABEL_SHIFT = 0.05;

export function snapshotProblems(next: SnapshotStats, last: SnapshotStats): string[] {
  const problems = [
    ...last.columns.filter(c => !next.columns.includes(c)).map(c => `column ${c} missing`),
    ...next.columns.filter(c => !last.columns.includes(c)).map(c => `column ${c} is new`),
  ];
  if (Math.abs(next.rows - last.rows) / last.rows > MAX_ROW_CHANGE) problems.push(`rows changed from ${last.rows} to ${next.rows}`);
  for (const [label, share] of Object.entries(last.labelShares)) {
    if (Math.abs((next.labelShares[label] ?? 0) - share) > MAX_LABEL_SHIFT) problems.push(`label ${label} share moved from ${share.toFixed(2)}`);
  }
  return problems;
}

How to check

A test runs the pipeline on a snapshot with one column dropped, and on one with a label share moved by 10 points. Both runs stop before training and name the reason.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/machine-learning/training-data/validate-training-snapshot">Agent Wranglers machine learning playbook</a>. In code you write, add a comment next to it with the same link.