Module 1 · ML Foundations

The End-to-End ML Workflow

Lesson 2 of 24 13 min read Updated

An end-to-end machine learning workflow is the disciplined path from a useful question to a measured prediction service or batch model. The outcome is not simply a trained algorithm. The outcome is a reproducible chain: define the prediction target, assemble data available at prediction time, build a baseline, train and evaluate candidate models, package the winning pipeline, deploy it, monitor it, and decide when to retrain or roll back.

In this ML Foundations lesson, the workflow matters because every later modeling technique depends on it. A strong model trained on the wrong target, leaked data, or a shifting feature definition gives confident but misleading answers. The workflow keeps the problem, data, code, metrics, and operations aligned.

Purpose and Outcome

The practical purpose is to turn a product objective into a controlled learning system. Suppose a real estate site wants to estimate sale price from property attributes. The ML workflow translates that into a supervised regression task: each historical row contains features such as square footage and bedroom count, plus a target such as final sale price. The finished workflow should produce predictions for new listings and expose enough evidence to answer whether the predictions are accurate, stale, or unusable for a particular segment.

A good workflow creates checkpoints. Before training, it checks whether the label is defined consistently. During training, it compares against a naive baseline. During evaluation, it uses data that was not used for fitting. During deployment, it ensures preprocessing and model coefficients move together. After release, it monitors input distributions, prediction errors when labels arrive, latency, and business impact.

How the Workflow Works Internally

The internal structure starts with problem framing. You choose a prediction unit, a target, a prediction time, and an action. The prediction unit might be one listing, one customer session, or one transaction. The target is the value learned from history. The prediction time decides which features are legal. If a house price is predicted before sale, final sale date cannot be a feature because it is unknown at prediction time.

Next comes data construction. Raw tables are joined into examples. Each example must align one target value with feature values from the correct time window. This is where many ML systems fail. Leakage occurs when training data includes information that would not be available when the model is used. Sampling bias occurs when the training examples underrepresent the future population. Label delay occurs when the true outcome arrives days or months later, forcing monitoring to distinguish immediate input checks from delayed accuracy checks.

Feature engineering converts raw fields into numeric, categorical, text, or time-derived signals. The exact transformation must be fitted only on training data where fitting is required. For example, the mean used to impute missing square footage should be learned from the training split, then applied unchanged to validation and production. This is why production ML systems package preprocessing and the estimator together as one pipeline.

Training searches for parameters that reduce a loss function on training data. Evaluation estimates generalization on held-out data. A split can be random for independent rows, time-based for forecasting-like tasks, or group-based when examples from the same entity would otherwise appear in both train and validation. Model selection compares candidates by a metric connected to the use case, such as mean absolute error for prices or recall at a fixed review capacity for fraud triage.

Deployment moves the same pipeline into an execution environment. Batch deployment writes predictions to a table on a schedule. Online deployment receives one request and returns one prediction with low latency. Monitoring then checks whether the service is working and whether the learning assumptions still hold. Retraining is triggered by time, enough new labels, drift, or measured quality decay.

API and Artifact Anatomy

An ML workflow usually has several artifacts with different responsibilities. The dataset definition identifies source tables, filters, join keys, label logic, and the as-of time. The feature schema names each input column, type, allowed range, missing-value behavior, and categorical vocabulary. The training configuration records the split strategy, model family, hyperparameters, random seed, metric, and acceptance threshold. The model artifact contains learned parameters plus preprocessing state. The evaluation report records metrics, segment breakdowns, and comparison to the baseline. The inference API accepts feature values and returns a prediction, version, and optional confidence or explanation fields.

Even in small Python scripts, keep those ideas separate. A function that validates rows should not silently train a model. A function that predicts should not refit preprocessing state. This separation makes failures easier to locate and prevents accidental differences between notebooks, tests, and deployment code.

Example 1: Establish a Baseline

The first worked example predicts every house price as the average training price. This is intentionally simple. A baseline is the model a real candidate must beat, and it catches workflows that look sophisticated but fail to improve on a constant answer.

from statistics import mean

rows = [
    {"sqft": 900, "bedrooms": 1, "price": 310000},
    {"sqft": 1200, "bedrooms": 2, "price": 390000},
    {"sqft": 1500, "bedrooms": 3, "price": 470000},
]

average_price = mean(row["price"] for row in rows)
predictions = [average_price for _ in rows]
mae = mean(abs(row["price"] - pred) for row, pred in zip(rows, predictions))

print(round(average_price, 2))
print(round(mae, 2))

The average price is 390000. The mean absolute error is 53333.33 on these three rows because each prediction ignores square footage and bedrooms. In a real workflow, this score becomes the reference point. If a later model cannot beat it on validation data, the problem may be poorly framed, the features may be weak, or the evaluation split may be wrong.

Example 2: Add a Simple Learned Rule

The second example learns a price-per-square-foot coefficient from training rows, then evaluates on separate validation rows. This is still a crude model, but it introduces the core workflow pattern: fit on training data, predict on validation data, and calculate the metric on rows the fitting step did not see.

from statistics import mean

train = [
    {"sqft": 900, "bedrooms": 1, "price": 310000},
    {"sqft": 1200, "bedrooms": 2, "price": 390000},
    {"sqft": 1500, "bedrooms": 3, "price": 470000},
]
valid = [
    {"sqft": 1000, "bedrooms": 1, "price": 340000},
    {"sqft": 1400, "bedrooms": 3, "price": 455000},
]

price_per_sqft = sum(row["price"] for row in train) / sum(row["sqft"] for row in train)
predictions = [row["sqft"] * price_per_sqft for row in valid]
mae = mean(abs(row["price"] - pred) for row, pred in zip(valid, predictions))

print(round(price_per_sqft, 2))
print([round(value, 2) for value in predictions])
print(round(mae, 2))

The learned coefficient is 325.0. The validation predictions are [325000.0, 455000.0], and the mean absolute error is 7500.0. The improvement over the baseline is meaningful for this toy data because square footage carries signal. The important workflow lesson is that validation data stays outside fitting. If the coefficient were tuned directly on the validation rows repeatedly, the validation score would become an optimistic development score rather than an honest estimate.

Example 3: Package Prediction with a Feature Schema

The third example represents a tiny model as an intercept plus feature weights. The prediction function receives an explicit list of training columns and rejects rows that do not match the schema. This mirrors a real deployed model that must know exactly which features it was trained to consume.

from statistics import mean

training_columns = ["sqft", "bedrooms"]
model = {"intercept": 50000, "sqft": 250, "bedrooms": 15000}

def predict(row: dict[str, float], columns: list[str]) -> float:
    missing = [name for name in columns if name not in row]
    if missing:
        raise ValueError(f"missing feature columns: {missing}")
    return model["intercept"] + sum(model[name] * row[name] for name in columns)

batch = [
    {"sqft": 1000, "bedrooms": 2, "price": 335000},
    {"sqft": 1300, "bedrooms": 3, "price": 420000},
]

predictions = [predict(row, training_columns) for row in batch]
mae = mean(abs(row["price"] - pred) for row, pred in zip(batch, predictions))

print(predictions)
print(round(mae, 2))

The predictions are [330000, 420000], and the mean absolute error is 2500.0. If a request omits bedrooms, the function raises ValueError with the missing column name. That behavior is better than silently treating the missing value as zero because the caller receives a clear symptom tied to a correctable input defect.

Design Choices and Trade-offs

The first design choice is batch versus online inference. Batch scoring is simpler, cheaper, and easier to inspect, but predictions can be stale. Online inference gives fresh predictions at the point of decision, but it adds latency budgets, request validation, and service reliability concerns.

The second choice is model complexity. Linear models and small trees are easier to debug and often strong baselines. Larger ensembles or neural networks can capture richer patterns, but they require more data, stricter evaluation, more expensive inference, and better tooling for explanation and rollback. Complexity should be earned by validation improvement on representative data, not by preference.

The third choice is split strategy. Random splits are acceptable when rows are independent and identically distributed. Time-based splits are safer when the future differs from the past. Group splits prevent the same customer, patient, device, or property from appearing in both training and validation. The split encodes your claim about generalization.

The fourth choice is retraining policy. Scheduled retraining is predictable but may waste compute or miss sudden drift. Triggered retraining reacts to data or quality changes but requires reliable monitors and guardrails. Many teams combine both: scheduled candidate training plus promotion only when evaluation and operational checks pass.

Failure Modes and Troubleshooting

A common failure is target leakage. The symptom is an unusually high validation score that collapses after release. The cause is a feature that encodes the answer, such as including a post-sale field when predicting pre-sale price. Diagnose it by reviewing feature availability at prediction time, comparing feature importances against business logic, and running a time-based split. Correct it by removing illegal features and rebuilding the dataset with an explicit as-of timestamp.

Another failure is training-serving skew. The symptom is good offline evaluation but poor online predictions. The cause is different preprocessing in training and inference, such as one-hot encoding categories in a notebook but handling unknown categories differently in the service. Diagnose it by replaying production requests through the training pipeline and comparing intermediate feature vectors. Correct it by packaging preprocessing and the estimator together and versioning the feature schema.

A third failure is data drift. The symptom is stable service uptime but gradually worsening business results or delayed label metrics. The cause is a change in input distribution, user behavior, seasonality, inventory, or policy. Diagnose it by comparing current feature distributions with the training window and checking segment-level errors when labels arrive. Correct it by retraining with recent labeled data, adding missing features that explain the shift, or narrowing where the model is allowed to make automated decisions.

Security, Performance, and Reliability

ML workflows handle sensitive data because examples often describe people, money, health, or behavior. Keep only fields needed for the prediction task, restrict access to raw training data, and avoid logging full feature payloads when they contain personal information. Model artifacts should be traceable to datasets and code revisions so a bad model can be removed and its training inputs audited.

Performance depends on feature computation as much as model math. A small model can still be slow if it performs expensive joins at request time. Precompute stable features for batch or low-latency online use, cache carefully, and set clear fallbacks when features are unavailable. Reliability requires model versioning, canary releases, rollback, and monitoring for both technical health and prediction quality.

Hands-on Lab: Build a Tiny Workflow

Prerequisites: Python 3 with only the standard library. The lab builds a minimal regression workflow with validation, training, evaluation, and deterministic output.

  1. Create a new temporary script named tiny_ml_workflow.py.
  2. Paste the following code into the script.
  3. Run python3 tiny_ml_workflow.py.
  4. Verify that the coefficient and validation error match the expected output described after the block.
  5. Cleanup by deleting the temporary script when finished.
from dataclasses import dataclass
from statistics import mean

@dataclass(frozen=True)
class HouseSale:
    sqft: float
    bedrooms: int
    price: float


def validate(rows: list[HouseSale]) -> None:
    if len(rows) < 2:
        raise ValueError("need at least two labeled rows")
    for row in rows:
        if row.sqft <= 0 or row.bedrooms <= 0 or row.price <= 0:
            raise ValueError(f"invalid positive-valued row: {row}")


def fit_price_per_sqft(rows: list[HouseSale]) -> float:
    validate(rows)
    return sum(row.price for row in rows) / sum(row.sqft for row in rows)


def predict(row: HouseSale, coefficient: float) -> float:
    return row.sqft * coefficient

train = [HouseSale(900, 1, 310000), HouseSale(1200, 2, 390000), HouseSale(1500, 3, 470000)]
valid = [HouseSale(1000, 1, 340000), HouseSale(1400, 3, 455000)]
coefficient = fit_price_per_sqft(train)
errors = [abs(row.price - predict(row, coefficient)) for row in valid]

print(round(coefficient, 2))
print(round(mean(errors), 2))

The expected output is 325.0 on the first line and 7500.0 on the second line. To verify the validation guard, change one training row to have sqft=0 and rerun the script. The program should raise ValueError before training, which confirms invalid data is rejected before it can create a misleading model artifact. Roll back the change by restoring the positive square footage value or deleting the lab file.

Assessment Exercises

  • You are predicting whether a customer will renew next month. Which fields are illegal if they are only known after the renewal decision, and how would you prove they are excluded?
  • A model beats the baseline on a random split but fails on the next month of data. Which split strategy would you try next, and what hypothesis does it test?
  • Design a prediction response for an online model. Include the minimum fields needed to debug model version, schema version, and invalid input without exposing private raw data.
  • A feature column disappears from production requests. Describe the symptom, the diagnostic check, and the correction in the pipeline or API contract.
  • Choose between batch and online inference for nightly product recommendations. Explain the trade-off using freshness, latency, cost, and operational complexity.

Summary

The end-to-end ML workflow is the operating system around model training. It defines the prediction task, constructs legal examples, fits transformations only where appropriate, compares models against a baseline, evaluates on held-out data, deploys the same preprocessing and estimator, and monitors quality after release. The workflow is what turns machine learning from an isolated experiment into a system whose predictions can be tested, explained, improved, and retired when conditions change.

Progress is saved in this browser.