Filter concepts by levelShowing all levels.

AI Full-Stack · Section 8

ML Pipelines

Level
intermediate
Read
50 min
Concepts
5

Everything around training that most real ML effort actually goes into: the eight-stage pipeline from raw data to a monitored, live system; bundling preprocessing into a reusable, leakage-aware pipeline object; the silent, crash-free bug of training/serving feature skew and its architectural fix; tying model, dataset and experiment versioning together so any result is traceable and reproducible; and what a random seed actually guarantees about reproducibility, and what else it takes.

What is true here

  1. Data → preprocessing → training → validation → evaluation → packaging → deployment → monitoring
  2. Packaging is the most commonly skipped stage; monitoring is the stage that never ends
  3. A pipeline object, fit once on training data, prevents both leakage and preprocessing duplication
  4. Training/serving skew is silent — the fix is one shared feature implementation, not two that must stay in sync
  5. A random seed alone does not guarantee reproducibility — it needs consistent seeding, data version, and software versions together

What you will be able to do

  • Name all eight pipeline stages and what each one is responsible for
  • Explain why a pipeline object prevents both leakage and training/serving skew
  • Diagnose a production model degrading with no code change as likely training/serving skew or distribution shift
  • Explain what a full experiment record needs beyond just the model file
  • List what reproducibility needs beyond a fixed random seed

From a trained model to a maintained system

The stages, tooling and discipline that turn a model that scored well in a notebook into a system that keeps working — and stays explainable — in production.

The ML pipeline, end to end

coreintermediate

An ML pipeline is the full sequence a model passes through, from raw data to a live system that someone actually uses and keeps watching: data, preprocessing, training, validation, evaluation, packaging, deployment, monitoring. Skipping or informalizing any one stage is where most real ML projects fail, not the modeling step itself.

Think of it as

Most of the effort in a real ML project is not training a model — it is everything around it, and this eight-stage pipeline is the map of where that effort goes. Data and preprocessing (§4, §5) get raw data into a clean, feature-ready, leakage-free shape. Training fits a model's parameters; validation guides decisions during that process (§1); evaluation (§7) scores the finished model honestly, against metrics that actually matter. Packaging is the step many tutorials skip entirely: turning a trained model object into something that can actually be deployed — serialized weights, a defined input/output contract, pinned dependencies. Deployment puts it somewhere it can be called, online or in batch (§1). Monitoring is the stage a shipped model needs forever, not once: watching for the input distribution drifting away from what the model was trained on, for the model's own predictions and their downstream outcomes, and for the pipeline's infrastructure health — because a model that was correct on ship day can quietly become wrong months later with no code change at all, purely because the world it is making predictions about changed.

text
data -> preprocessing -> training -> validation -> evaluation
                                                          |
                                                          v
                                packaging -> deployment -> monitoring
                                                  ^              |
                                                  |______________|
                                          (monitoring often triggers
                                           a return to data/training)

What we're doing: Trace a real failure back to the pipeline stage it actually belongs to, instead of the stage where it was noticed.

incident-notes.txttext
Symptom: model's precision dropped from 0.85 to 0.60
         over three weeks, no code deployed in that window.

Stage-by-stage check:
  training/validation/evaluation: unchanged, same model
    binary still running -> rule these out
  packaging/deployment: same artifact serving -> rule out
  monitoring: input feature distributions ARE drifting
    (a feature's typical range shifted 40% from training)
  -> root cause is upstream of training: the DATA the
     production world produces has shifted (distribution
     shift, see section 1), and monitoring is what
     surfaced it, not training or deployment.

Fix: retrain on recent data, add an automated
     distribution-drift check to monitoring going forward.
6
Ruling out stages methodically — rather than guessing — is what the eight-stage model is for: it turns "something is wrong" into "which specific stage owns this."
11
The symptom appeared as a metrics problem, but its root cause lives in the data stage — monitoring exists specifically to catch this class of failure, which evaluation (a one-time check) cannot.
16
The fix closes the loop back to an earlier stage (data/training) rather than patching the stage where the symptom was observed (monitoring) — monitoring's job is detection, not correction.

Why this works: Naming the eight stages explicitly turns "the model got worse, not sure why" into a systematic elimination — most real production incidents are a failure in one specific, nameable stage, and stage-by-stage tracing finds it far faster than re-examining the whole system at once.

Treating "the pipeline" as one undifferentiated script

Wrong

text
train_and_deploy.py:
  load data, clean it, train, evaluate,
  save the model file, deploy it
  # one 300-line script, no stage boundaries,
  # no monitoring after it exits

Better

text
data_pipeline.py    -> writes a versioned dataset
train.py             -> reads a dataset version, writes a model artifact
evaluate.py          -> reads a model artifact, writes a metrics report
package.py           -> reads a model artifact, writes a deployable bundle
deploy.py            -> reads a bundle, updates the serving system
monitoring/           -> runs continuously after deploy.py exits

What you see: When something goes wrong in production, nobody can say which stage is responsible without re-reading the entire pipeline script from the top, because there were never any named boundaries between data, training, evaluation, packaging and deployment to begin with.

Why: A pipeline with no stage boundaries has no place to insert a check, a version tag, or a monitor without touching the whole thing — naming and separating the eight stages is what makes each one independently testable, versionable, and debuggable.

The eight stages, in order

Data

The raw material the model will learn from

Preprocessing

Clean, consistent, leakage-free

Training

Fit the model's parameters

Validation

Guide model/hyperparameter choices during development

Evaluation

Score the finished model, honestly

Packaging

Serialized, contracted, pinned — the most commonly skipped stage

Deployment

Put it somewhere it can actually be called

Monitoring

Watch it forever, not once — this stage never ends

  1. Data — The raw material the model will learn from
  2. Preprocessing — Clean, consistent, leakage-free
  3. Training — Fit the model's parameters
  4. Validation — Guide model/hyperparameter choices during development
  5. Evaluation — Score the finished model, honestly
  6. Packaging — Serialized, contracted, pinned — the most commonly skipped stage
  7. Deployment — Put it somewhere it can actually be called
  8. Monitoring — Watch it forever, not once — this stage never ends

Eight pipeline stages, and what each one is actually for

Eight pipeline stages, and what each one is actually for
StageAnswers
DataWhat raw material does the model learn from?
PreprocessingIs it clean, consistent, and leakage-free?
TrainingFit the model's parameters to the training data
ValidationWhich model, which hyperparameters, during development?
EvaluationHow good is the finished model, honestly, on metrics that matter?
PackagingCan this be deployed at all — serialized, contracted, pinned?
DeploymentWhere does it run, and how does it get called?
MonitoringIs it still correct, weeks or months after ship day?

Remember: Data → preprocessing → training → validation → evaluation → packaging → deployment → monitoring. Packaging is the most commonly skipped stage; monitoring is the stage that never ends. Most real project effort and most real failures live in the stages around training, not training itself.

See also: online vs batch inference · training serving feature consistency · offline vs online evaluation

Reusable preprocessing pipelines

standardintermediate

A reusable preprocessing pipeline bundles every transformation step — scaling, encoding, imputing — into one object that can be fit once on training data and then applied, unchanged, anywhere the model needs the same transformations: validation, test, and production.

Think of it as

The alternative to a pipeline object is re-writing the same preprocessing steps by hand in multiple places — once for training, once for evaluation, once for the serving code — and that duplication is exactly where training/serving skew (§8.3) creeps in, because it is easy for the hand-written copies to quietly drift apart. A pipeline object fixes this structurally: it is fit once, on training data, and the fitted object itself — not the code that produced it — is what gets saved and reused everywhere else, so validation, test and production all run through the literal same transformation logic and the literal same fitted parameters (the same mean/std from a scaler, the same category mappings from an encoder). This is also what makes a pipeline object a leakage-aware pipeline (§5.5): because fitting only happens once, on training data, there is no opportunity for a later step to accidentally fit on validation or test data.

python
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer

pipeline = Pipeline([
    ("features", ColumnTransformer([
        ("num", StandardScaler(), numeric_cols),
        ("cat", OneHotEncoder(handle_unknown="ignore"), categorical_cols),
    ])),
    ("model", model),
])
pipeline.fit(X_train, y_train)   # every step fits once, on train only
pipeline.predict(X_new)           # same fitted transforms, everywhere

Remember: A pipeline object fits every preprocessing step once on training data and reuses the exact fitted result everywhere else — validation, test, production — preventing both training/serving skew and leakage from hand-duplicated preprocessing code.

See also: feature importance and leakage aware pipelines · training serving feature consistency

Training/serving feature consistency

standardintermediate

Training/serving skew is when the code that computes a feature at training time and the code that computes the same feature at prediction time compute it slightly differently — and the model, which learned on the training version, silently gets worse predictions in production without any error being thrown.

Think of it as

This is one of the most common production ML bugs precisely because it produces no crash and no obvious symptom — the model just quietly performs worse than its offline evaluation predicted, and the cause looks anywhere except feature computation. It typically happens because training features are often computed in a batch job (Python, SQL, Spark) working over historical data, while serving features are computed in a low-latency production service (often a different language, a different codebase, sometimes a different team) working over live data — and small differences creep in: a rolling average computed over a slightly different window, a null handled differently, a timestamp in a different timezone. The fix is architectural: a shared feature store or a shared feature-computation library that both training and serving call into, so there is exactly one implementation of 'how do we compute this feature,' not two that are supposed to agree by convention.

text
bad:  training computes "avg_order_value" in a batch SQL job
      serving computes "avg_order_value" in a separate Python service
      -> two implementations, free to silently drift apart

good: both training and serving call the SAME feature-computation
      function/feature-store lookup -> one implementation, no drift

Remember: Training/serving skew is a silent bug: the model quietly performs worse in production because feature computation differs between training and serving code, with no error thrown. Fix it architecturally with one shared feature implementation, not two that must stay in sync by convention.

See also: reusable preprocessing pipelines · feature target and temporal leakage

Model, dataset and experiment versioning

standardintermediate

Model versioning tracks which exact trained artifact is deployed where. Dataset versioning (§4.6) tracks which exact snapshot of data produced it. Experiment tracking ties both together with the code version, hyperparameters, and metrics of every training run, so any result can be traced back and reproduced.

Think of it as

Together, these three answer one question a team eventually always needs to answer: 'exactly what produced this result, and can we get it back?' Without them, that question becomes archaeology — digging through Slack messages and commit history hoping someone remembers what changed between the good run and the current one. Model versioning is the deployment-facing half: which model artifact is live, which was live last week, and how to roll back to it instantly if the new one misbehaves. Experiment tracking is the development-facing half: every training run's code version, data version, hyperparameters and resulting metrics, recorded automatically rather than in a spreadsheet someone forgets to update — so comparing 40 experiments to find the best one is a query, not a memory exercise. The three pieces only work together: a model version with no linked data version and hyperparameters is a black box nobody can explain or reproduce.

python
experiment_tracker.log_run({
    "code_version": git_commit_sha,
    "data_version": "users_snapshot_v2026-09-10",
    "hyperparameters": {"lr": 0.01, "n_estimators": 200},
    "metrics": {"val_f1": 0.87, "val_auc": 0.93},
    "model_artifact": "model_v42.pkl",
})
# a query over many logged runs finds the best one --
# no memory or spreadsheet required

Remember: Model versioning enables rollback; dataset versioning ties a model to the exact data it trained on; experiment tracking records code, data, hyperparameters and metrics automatically for every run. Together they answer "what produced this result, and can we get it back" — separately, none of them can.

See also: data versioning and reproducibility · reproducible training and seeds

Reproducible training and random seeds

standardintermediate

Many parts of training are randomized — how data gets shuffled, how a model's weights are initialized, which rows a random forest samples — and a random seed fixes that randomness to a specific starting point, so re-running the exact same code with the exact same seed reproduces the exact same result.

Think of it as

A random seed alone does not guarantee reproducibility — it is one necessary ingredient among several, and 'where practical' in this concept's roadmap wording is doing real work. Setting a seed fixes the sequence of 'random' numbers a program generates, but only within the parts of the system that actually respect it: a GPU running certain parallel operations can produce a different floating-point result on different hardware even with an identical seed, because operations execute in a different order across threads. So true, bit-for-bit reproducibility needs the seed set consistently across every library involved (NumPy, the ML framework, the language's own random module), plus the same hardware and software versions, plus the same data version (§8.4) — and even then, some operations are documented as non-deterministic regardless. The practical target most teams aim for is not bit-for-bit identical results but reproducible-enough: a rerun that lands within a small, explainable tolerance of the original metrics, with the seed fixed as one deliberate step among several, not treated as the whole solution.

python
import random, numpy as np

SEED = 42
random.seed(SEED)
np.random.seed(SEED)
# plus the ML framework's own seed call (e.g. torch.manual_seed(SEED))
# plus recording the data version and library versions alongside it

Remember: A random seed fixes randomness, but only within what respects it — full reproducibility needs a consistent seed across every library, plus the same data version and software versions. On GPU training, "reproducible enough" is often the realistic target, not bit-for-bit identical.

See also: notebooks environments and reproducibility · model dataset and experiment versioning

Advertisement