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
- Google Cloud, MLOps continuous delivery and automation pipelines
- OWASP Secure AI Model Ops Cheat Sheet
Related rules
- MLO-32 Retrain on written triggers in Machine learning
- MLO-24 Check every input against what the model was trained on in Machine learning
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.