HuntExams Academy logo
Data Science · Chapter 41 of 43

Model Deployment

DEPLOYMENT makes your model usable — usually as a REST API endpoint or embedded in a dashboard/app.

Monitor accuracy AND data drift once live. Models decay over time.

Example 1 (python)
# Minimal FastAPI service
from fastapi import FastAPI
import joblib
app = FastAPI()
model = joblib.load('model.pkl')

@app.post('/predict')
def predict(data: dict):
    return {'y': int(model.predict([data['x']])[0])}

One-file inference API.

Example 2 (python)
# Docker + cloud (AWS/GCP/Azure) for scale

Package and ship reproducibly.

Key points

  • Serve via REST API (FastAPI/Flask).
  • Containerise with Docker.
  • Monitor drift and accuracy.
  • Retrain on fresh data.
💡 Note: In production, most failures come from stale data or bad monitoring — not from a bad model architecture.

Understanding Model Deployment

Model deployment means making a trained model available inside a working system, with a defined input contract, predictable outputs, and operational safeguards. A REST endpoint is one option, not the definition of deployment. An overnight batch job can score many records cheaply; an embedded model can avoid network latency; an API lets multiple applications share one prediction service. Choose according to when predictions are needed and how failures should be handled.

The deployed unit is more than the estimator. It includes feature definitions, fitted preprocessing, dependency versions, and the meaning of the response. Suppose a model estimates packing time from item count. A request containing weight instead of count might still be numeric, yet produce a meaningless answer. Validate field names, types, units, missing values, and acceptable ranges before inference. Convert library-specific output types into ordinary values suitable for the response format.

Training and serving must apply identical transformations. The scikit-learn guide separates pipelines, preprocessing, model persistence, and common pitfalls, including inconsistent preprocessing and leakage. A pipeline provides a useful implementation boundary: fit its transformations and estimator on training data only, then reuse that fitted object for validation and prediction. Never recompute scaling statistics from incoming requests. Keep evaluation data outside fitting and model selection decisions so reported performance remains meaningful.

Reproducibility requires recording the model version, training configuration, schema, and software environment. A container can package dependencies and application code, but does not establish model quality, security, or unlimited capacity. Loading a persisted model also introduces trust and compatibility concerns: pickle-based formats can execute code during loading. Use a reviewed artifact process and compatible dependencies rather than accepting arbitrary uploaded model files.

Once deployed, monitor three different things: service health, input behavior, and prediction quality. Latency and failures describe availability; missing-value rates and distribution changes describe incoming data; outcome-based metrics describe usefulness. Drift is a warning, not proof that accuracy decreased. Labels may arrive late or only for selected cases, limiting evaluation. Models do not inevitably deteriorate merely because time passes. Investigate changes, evaluate replacement candidates, and retain a tested rollback path before promoting a new version to production.

Practical walkthrough

Begin with a deliberately narrow contract: accept one integer field, items, between zero and ten, and return estimated packing minutes plus a model version. This toy range is a teaching assumption, not an empirically justified operating limit. Real limits should reflect application requirements and validated model behavior.

Use Python 3.11 or later with scikit-learn already installed; the example uses APIs available in scikit-learn 1.4 and later. Pin exact package versions for an actual release. No pandas, server, network connection, model file, or external dataset is needed. The function represents the inference logic that an HTTP route or batch worker could call.

Fit the pipeline once using four invented training observations. StandardScaler learns its statistics from those observations, and LinearRegression learns the relationship between transformed item counts and minutes. Scaling is unnecessary for this simple regression, but demonstrates how preprocessing stays attached to the estimator. The request is not used for fitting. This example tests inference behavior, not generalization; a real release also needs untouched evaluation data representing expected use.

Next, enforce the contract before constructing the feature row. Reject extra or missing fields, strings, booleans, and values outside the stated range. Build a two-dimensional input because scikit-learn expects rows of samples and columns of features, even for one prediction.

Finally, call the function with a valid request and an invalid string. In an API, translate validation failures into documented client errors. Before release, also test readiness, resource limits, timeout behavior, privacy-conscious logging, and rollback. These operational concerns are intentionally outside this in-memory demonstration.

Worked example

from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

# Invented training data: item count -> packing minutes.
X_train = [[1], [2], [3], [4]]
y_train = [7, 10, 13, 16]
model = make_pipeline(StandardScaler(), LinearRegression())
model.fit(X_train, y_train)
MODEL_VERSION = "packing-v1"


def predict(payload):
    if not isinstance(payload, dict) or set(payload) != {"items"}:
        raise ValueError("expected exactly one field: items")
    items = payload["items"]
    if type(items) is not int or not 0 <= items <= 10:
        raise ValueError("items must be an integer from 0 to 10")
    minutes = float(model.predict([[items]])[0])
    return {
        "model_version": MODEL_VERSION,
        "minutes": round(minutes, 1),
    }


print(predict({"items": 3}))
try:
    predict({"items": "3"})
except ValueError as error:
    print(f"Rejected: {error}")

Expected output

{'model_version': 'packing-v1', 'minutes': 13.0}
Rejected: items must be an integer from 0 to 10

The training observations follow minutes = 4 + 3 × items. Standardization changes the input coordinates, but linear regression still represents that relationship. For three items, the prediction rounds to 13.0 minutes; rounding suppresses irrelevant floating-point differences in the displayed result. The version identifies the predictor used, rather than describing its quality. The second request contains a string, so validation rejects it before model execution. Because three items also appears in training, this is a contract check, not evidence of predictive accuracy. Counts above four are extrapolations even though the illustrative contract permits them; production acceptance requires separate justification and evaluation.

Common mistakes and how to avoid them

  • Fitting preprocessing again for every request.

    Reuse the training-fitted pipeline and call predict only. Repeated fitting changes the feature representation and can make predictions depend on unrelated requests.

  • Treating successful HTTP responses as proof of model quality.

    Track service errors separately from outcome-based metrics. Join predictions to later labels using controlled identifiers, and examine label delays and coverage before interpreting results.

  • Automatically retraining whenever an input distribution changes.

    Investigate schema changes, seasonal shifts, and upstream bugs first. Evaluate a candidate against the current model before promotion, and keep the previous release available.

  • Loading any supplied joblib file because its extension looks familiar.

    Do not deserialize untrusted artifacts. Establish trusted provenance, restrict artifact access, and review persistence-format security and environment compatibility before loading.

Review questions

Why deploy preprocessing together with the estimator?

The estimator expects the representation used during training. Keeping fitted transformations together preserves that representation and reduces mismatches between offline evaluation and live inference.

When might batch deployment be preferable to an API?

Batch processing suits predictions needed on a schedule rather than immediately. It can simplify operation and amortize computation, but cannot provide fresh predictions on demand.

Does drift establish that a model has become inaccurate?

No. Drift describes a distribution change. Quality assessment requires relevant outcomes and suitable metrics; investigate drift while accounting for delayed or selectively observed labels.

What does this example demonstrate, and what does it leave unproven?

It demonstrates validation, reuse of fitted preprocessing, versioned responses, and deterministic inference. It does not establish generalization, realistic packing-time estimates, concurrent serving capacity, or production security.

Further reading

📝 Quick Quiz

1. A common Python API framework is:

2. Docker helps with:

3. In production you should monitor: