🎯 What You'll Learn

  • The 8 canonical stages of an ML project and how they connect to one another
  • How to write a Scope of Work (SOW) that defines success before touching data
  • What distinguishes high-quality training data from poor-quality data, and how to evaluate it
  • How to choose a baseline model, fine-tune it, and deploy it via a REST API
  • What model drift is, why it happens, and how MLOps practices (MLflow, PSI) catch it early
💡
Why start with the lifecycle?

Most courses dive straight into algorithms. But in the real world, the majority of ML project failures are not algorithmic — they fail at the problem definition, data quality, or deployment stage. Understanding the full lifecycle before you write a single line of model code is what separates engineers who ship from engineers who prototype.

🗺️
Read this lesson like a map, not a manual

Because this lesson tours the entire journey, it names many tools and metrics you haven't learned yet — algorithm names like XGBoost (Lesson 24), evaluation metrics like AUC-ROC and recall (Lessons 19–20), deployment tools like MLflow (Phase 6). None of them are prerequisites. When an unfamiliar name appears, note which stage it belongs to and move on — every one of them gets its own lesson later in the course. What you should take from this lesson is the shape of the journey.

1 The 8 Stages of an ML Project

Every ML project — whether it's a fraud detection system at a bank, a recommendation engine at a streaming service, or a predictive maintenance model at a factory — passes through the same eight stages. The stages are rarely purely sequential; in practice they are deeply iterative, with frequent loops between stages 3–6 especially. But understanding the full map gives you the mental model to navigate any project.

Stage Name Key Question Output
1Business Understanding & SOWWhat problem are we solving?Scope of Work document
2Data CollectionDo we have the right data?Raw dataset(s)
3Exploratory Data AnalysisWhat does the data look like?EDA notebook, insights
4Feature EngineeringWhat inputs should the model use?Feature matrix X, target y
5Model Selection & TrainingWhich algorithm and why?Trained candidate models
6Fine-Tuning & EvaluationIs this good enough to ship?Final model + metrics report
7DeploymentHow do users/systems consume it?API endpoint, batch job
8Monitoring & FeedbackIs the model still accurate?Alerts, retrain triggers
🔑
ML is Iterative, Not Linear

The diagram above shows stages 1–8 in order, but in practice you will loop. EDA (stage 3) reveals that you need more data (back to stage 2). Fine-tuning (stage 6) reveals that a feature is leaking information (back to stage 4). Monitoring (stage 8) detects data drift (back to stages 2–4). Plan for iteration from the start — a project that never revisits earlier stages is probably missing something.

The table above lists the stages in reading order, but the real shape of an ML project is a loop, not a line. The diagram below makes that loop explicit — and color-codes each stage by the kind of work it really is: defining the problem, working with data, building the model, or running it in production.

1 · Business Understanding & SOW define the problem 2 · Data Collection data work 3 · Exploratory Data Analysis data work 4 · Feature Engineering data work 5 · Model Selection & Training modeling work 6 · Fine-Tuning & Evaluation modeling work 7 · Deployment ops work 8 · Monitoring & Feedback ops work feedback loop: drift detected → recollect / retrain Problem definition Data work Modeling work Ops / production work

The 8 lifecycle stages flow left-to-right across the top (problem definition → data work), then continue right-to-left across the bottom (modeling → ops). The dashed amber arrow shows the feedback loop: monitoring detects drift in production and sends the project back to data collection.

2 Stage 1: Business Understanding & Scope of Work

The single most important stage of any ML project is the one that happens before any code is written: agreeing on precisely what problem you are solving, what success looks like, and what constraints you are operating under. This agreement is captured in a Scope of Work (SOW) document.

A good SOW answers five questions:

  1. Problem type — Is this classification (spam/not spam), regression (predict a price), clustering (group customers), ranking, or something else?
  2. Success metric — What number defines "good enough"? For credit risk: AUC-ROC ≥ 0.85. For a recommender: CTR uplift ≥ 5%. The metric must be tied to a business outcome.
  3. Data requirements — What features are needed? How much historical data? Any labeling effort required?
  4. Constraints — Latency (must respond in <100ms?), interpretability (regulators require explainability?), fairness, data privacy.
  5. Deliverables and timeline — A deployed API? A report? A reusable pipeline? By when?

Example SOW: Credit Risk Model

In [1]:
"""
SCOPE OF WORK — Credit Default Risk Model
==========================================
Client:       Finova Lending Group
Project:      Predict probability that a loan applicant will default within 12 months.

PROBLEM TYPE: Binary classification (default=1, no default=0)
TARGET:       loan_default (1 = defaulted within 12 months)

SUCCESS METRICS:
  Primary:    AUC-ROC >= 0.82 on held-out test set
  Secondary:  Recall >= 0.70 at a precision threshold of 0.60
              (catch 70% of defaults while keeping false positives manageable)

DATA REQUIREMENTS:
  - 3 years of loan application records (~150,000 rows)
  - Features: applicant income, credit score, debt-to-income ratio,
              loan amount, loan purpose, employment length, home ownership
  - Label: binary flag from payment records team (already available)

CONSTRAINTS:
  - Model must be explainable (regulatory requirement — SHAP values required)
  - Inference latency: < 200ms per application
  - No use of race, gender, or protected attributes as direct features
  - Data stored on-premises — no cloud processing of raw PII

DELIVERABLES:
  1. EDA report with data quality findings
  2. Trained model (XGBoost or Logistic Regression) + SHAP analysis
  3. FastAPI inference endpoint with Swagger docs
  4. Monthly drift monitoring report (PSI on input features)

TIMELINE: 8 weeks (EDA: wk1-2, Modeling: wk3-5, Deploy: wk6-7, Monitor setup: wk8)
"""
⚠️
Wrong Metric = Wrong Model

If you optimize for accuracy on a dataset that is 95% "no default" and 5% "default", a model that predicts "no default" for every applicant achieves 95% accuracy while catching zero defaults. Always define success metrics that match business goals — not just model accuracy. (Lessons 19–20 teach the metrics designed for exactly this situation: precision, recall, F1, and AUC-ROC.)

The SOW above also commits to a timeline: 8 weeks, broken down by stage. Notice how little time is actually spent on the part most beginners associate with "doing ML" — training the model:

Planned effort across the 8-week credit risk SOW. EDA and modeling together still take less calendar time than deployment plus standing up monitoring.

3 Stage 2: Data Collection

The best algorithm trained on bad data will underperform the simplest algorithm trained on good data. Data collection is not glamorous, but it is often the highest-leverage stage of the entire project. There are two main categories of data sources:

Primary vs Secondary Sources

  • Primary sources (you collect it yourself): internal databases (SQL, data warehouses), operational logs, surveys, sensors, web scraping, manual labeling (annotation platforms like Label Studio or Scale AI).
  • Secondary sources (someone else collected it): public datasets (Kaggle, UCI ML Repository, HuggingFace Datasets, government open data portals), third-party data vendors, open web APIs (Twitter/X, OpenWeather, Alpha Vantage).

The Four Dimensions of Data Quality

Before you trust a dataset for training, evaluate it on four dimensions:

Dimension Definition Warning signs
CompletenessAll required records and fields are present>5% missing in key features, sparse label coverage
AccuracyValues reflect realityAge = 200, negative salary, inconsistent label rates
ConsistencySame concept encoded the same way across records"M", "Male", "male", "1" all meaning the same thing
TimelinessData is recent enough to reflect current patternsTraining on 2015 data for a 2026 model
In [2]:
import pandas as pd

df = pd.read_csv('loan_applications.csv')

# Quick data quality audit
print("Shape:", df.shape)
print("\nMissing values (%):")
print((df.isnull().mean() * 100).sort_values(ascending=False).head(10))

print("\nValue counts for key categorical columns:")
print(df['loan_purpose'].value_counts())

print("\nNumerical summary:")
print(df[['income', 'loan_amount', 'credit_score']].describe())

# Check for obvious data quality issues
print("\nInvalid ages (< 18 or > 100):")
print(df[(df['age'] < 18) | (df['age'] > 100)].shape[0])

print("\nNegative income rows:")
print(df[df['income'] < 0].shape[0])

4 Stages 3–4: EDA & Feature Engineering

Exploratory Data Analysis (EDA) and Feature Engineering are covered in depth in dedicated lessons, but their role in the lifecycle deserves a clear description here.

EDA (Stage 3) is the process of getting to know your data before you model it. You visualize distributions, check for outliers, examine correlations, and understand the relationship between features and the target. EDA outputs are not just pretty charts — they directly inform the decisions in every subsequent stage. A rare-outcome imbalance found in EDA means you'll need the special handling taught in Lesson 26. A bimodal distribution in an income feature might suggest two distinct customer segments.

Feature Engineering (Stage 4) is transforming raw data into representations the model can learn from effectively. This includes:

  • Encoding categorical variables (one-hot encoding, ordinal encoding, target encoding)
  • Scaling numerical features (StandardScaler, MinMaxScaler)
  • Creating interaction features (credit_score × debt_to_income)
  • Extracting date components (day_of_week, month, is_holiday)
  • Handling missing values (imputation strategies)
  • Log-transforming skewed distributions
🔑
Feature Engineering is Where Domain Knowledge Wins

A domain expert who knows that "credit_utilization = revolving_balance / credit_limit" is a better predictor than either feature alone will outperform a pure data scientist who blindly throws raw features at a model. The best features come from understanding why data is collected, not just what it contains. Invest time in conversations with business stakeholders before finalizing your feature set.

The Iterative Nature of ML Development

In textbooks, stages appear sequential. In practice, the loop between EDA, feature engineering, model training, and evaluation repeats many times. A typical iteration looks like:

In [3]:
"""
Iteration Loop:
  1. Train baseline model on raw features
  2. Examine feature importances and error cases
  3. Form hypothesis: "credit_utilization_ratio might help"
  4. Engineer new feature, add to pipeline
  5. Retrain, compare metrics
  6. If improved: keep feature; if not: drop it
  7. Repeat until diminishing returns
"""

# Iteration 1: baseline features
baseline_features = ['income', 'loan_amount', 'credit_score', 'employment_years']

# After EDA insight: credit utilization is highly predictive
# Iteration 2: add engineered feature
df['credit_utilization'] = df['revolving_balance'] / df['credit_limit']
df['debt_to_income']      = df['total_debt'] / df['annual_income']
df['loan_to_income']      = df['loan_amount'] / df['annual_income']

enhanced_features = baseline_features + ['credit_utilization', 'debt_to_income', 'loan_to_income']

5 Stages 5–6: Model Selection & Fine-Tuning

Model selection is not about finding "the best algorithm in the abstract" — it's about finding the right algorithm for your specific problem, data size, and constraints. A framework for making this decision:

Choosing the Algorithm Family

  • Problem type first: classification → logistic regression, decision trees, random forests, XGBoost, SVMs; regression → linear regression, ridge, lasso, gradient boosting, SVR.
  • Data size: small data (<10k rows) → regularized linear models, SVMs; medium data → tree ensembles; large data (>1M rows) → gradient boosting, shallow networks or mini-batch SGD.
  • Interpretability requirement: regulatory/medical settings → logistic regression, decision trees with depth limit; otherwise → tree ensembles, neural networks fine.
  • Baseline first — always: before training a complex model, train the simplest plausible model (logistic regression, a single decision tree, predicting the mean). This establishes a floor — a complex model that doesn't beat the baseline is not worth deploying.
In [4]:
# A sneak peek at what model comparison looks like in practice.
# Every model below is a Phase 2 lesson (11-24); every one follows the
# same 3-button interface: .fit() / .predict() / .score()
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

# Always start with a baseline
models = {
    'Logistic Regression (baseline)': LogisticRegression(max_iter=1000),
    'Decision Tree':                  DecisionTreeClassifier(max_depth=5),
    'Random Forest':                  RandomForestClassifier(n_estimators=100, random_state=42),
    'Gradient Boosting':              GradientBoostingClassifier(n_estimators=100, random_state=42),
}

results = {}
for name, model in models.items():
    # cross_val_score: train/score 5 times on rotating folds (see Lesson 10)
    scores = cross_val_score(model, X_train, y_train, cv=5, scoring='accuracy')
    results[name] = scores.mean()
    print(f"{name:40s}  accuracy = {scores.mean():.4f} ± {scores.std():.4f}")

# Pick the best model, then fine-tune it
# Logistic Regression (baseline)              accuracy = 0.7812 ± 0.0124
# Decision Tree                               accuracy = 0.7941 ± 0.0198
# Random Forest                               accuracy = 0.8347 ± 0.0087
# Gradient Boosting                           accuracy = 0.8512 ± 0.0063  <-- winner
# (In practice you'd often score with metrics from Lessons 19-20 instead)

Fine-Tuning with Cross-Validation

Hyperparameter tuning means searching over settings that you don't learn from data — the number of trees, learning rate, maximum depth, regularization strength. Always tune using cross-validation on the training set only. Never touch the test set until your final evaluation.

In [5]:
# GridSearchCV tries every combination of the settings below and
# cross-validates each one — a tool you'll learn to wield in Lesson 27.
# For now, just see WHERE tuning sits in the lifecycle.
from sklearn.model_selection import GridSearchCV

param_grid = {
    'n_estimators':    [100, 200, 300],
    'max_depth':       [3, 5, 7],
    'learning_rate':   [0.05, 0.1, 0.2],
    'subsample':       [0.8, 1.0],
}

grid_search = GridSearchCV(
    GradientBoostingClassifier(random_state=42),
    param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    verbose=1
)
grid_search.fit(X_train, y_train)

print("Best params:", grid_search.best_params_)
print("Best CV accuracy:", grid_search.best_score_)
# Best params: {'learning_rate': 0.1, 'max_depth': 5, 'n_estimators': 200, 'subsample': 0.8}
# Best CV accuracy: 0.8589

6 Stage 7: Deployment

A model that lives in a notebook is not a product. Deployment is the stage where your trained model is packaged and integrated into a system that real users or other software can consume. There are two main deployment patterns:

  • Real-time inference: a REST API that accepts a single prediction request and returns a result within milliseconds. Used for: loan approval at point-of-application, spam filtering, fraud detection. Tool: FastAPI, Flask.
  • Batch inference: a scheduled job that runs the model on thousands or millions of records at once (nightly, weekly). Used for: churn scoring all customers, generating product recommendations. Tool: scheduled Python scripts, Airflow, Spark.

Model Serialization

Before deploying, you serialize your trained model to disk — converting the in-memory Python object into a file that can be reloaded without retraining.

In [6]:
import joblib
import pickle

# joblib — recommended for sklearn models (handles large arrays efficiently)
joblib.dump(grid_search.best_estimator_, 'credit_risk_model_v1.pkl')

# Load it back
model = joblib.load('credit_risk_model_v1.pkl')

# Make a prediction (same as the original model)
sample = [[65000, 25000, 720, 3, 0.28, 0.38, 0.38]]  # 7 features
prob = model.predict_proba(sample)[0][1]
print(f"Default probability: {prob:.3f}")  # e.g., 0.124

Serving with FastAPI

In [7]:
# app.py  — minimal FastAPI inference endpoint
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import numpy as np

app = FastAPI(title="Credit Risk API", version="1.0")
model = joblib.load("credit_risk_model_v1.pkl")

class LoanApplication(BaseModel):
    annual_income:       float
    loan_amount:         float
    credit_score:        int
    employment_years:    int
    credit_utilization:  float
    debt_to_income:      float
    loan_to_income:      float

@app.post("/predict")
def predict_default_risk(application: LoanApplication):
    features = [[
        application.annual_income,
        application.loan_amount,
        application.credit_score,
        application.employment_years,
        application.credit_utilization,
        application.debt_to_income,
        application.loan_to_income,
    ]]
    prob = model.predict_proba(features)[0][1]
    decision = "HIGH RISK" if prob > 0.35 else "LOW RISK"
    return {"default_probability": round(prob, 4), "decision": decision}

# Run with: uvicorn app:app --reload
# Test: POST http://localhost:8000/predict
💡
Model Versioning

Include a version tag in your model filename (credit_risk_model_v1.pkl). When you retrain, save as v2, v3, etc. Never overwrite a production model in place — always keep the previous version so you can roll back if the new one has issues. Tools like MLflow Model Registry automate this at scale.

7 Stage 8: Monitoring & MLOps

Deploying a model is not the end — it's the beginning of a new responsibility. Models degrade in production for predictable reasons, and catching degradation early prevents real business harm.

Types of Model Degradation

  • Concept drift: the statistical relationship between features and target changes. Example: a credit risk model trained before a recession may underestimate default rates after one, because the relationship between income and default probability shifts.
  • Data drift (covariate shift): the distribution of input features changes, even if the relationship to the target stays the same. Example: the average loan amount grows over time due to inflation, pushing values outside the training distribution.
  • Upstream data issues: a data pipeline change means a feature arrives as null, or in a different unit, or with different encoding.

Detecting Drift with PSI (Population Stability Index)

In [8]:
import numpy as np
import pandas as pd

def calculate_psi(expected, actual, buckets=10):
    """
    Population Stability Index (PSI).
    PSI < 0.1:  stable — no significant change
    PSI 0.1-0.2: slight shift — investigate
    PSI > 0.2:  significant drift — consider retraining
    """
    expected_pct = np.histogram(expected, bins=buckets)[0] / len(expected)
    actual_pct   = np.histogram(actual,   bins=np.histogram(expected, bins=buckets)[1])[0] / len(actual)

    # Avoid log(0)
    expected_pct = np.where(expected_pct == 0, 0.0001, expected_pct)
    actual_pct   = np.where(actual_pct == 0,   0.0001, actual_pct)

    psi = np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))
    return psi

# Compare training distribution vs recent production data
train_income    = df_train['annual_income'].values
current_income  = df_production_last_30d['annual_income'].values

psi_score = calculate_psi(train_income, current_income)
print(f"PSI for annual_income: {psi_score:.4f}")
# PSI for annual_income: 0.0312  → stable ✓

MLflow for Experiment Tracking

In [9]:
import mlflow
import mlflow.sklearn

# Track an experiment
with mlflow.start_run(run_name="GradientBoosting_v2"):
    mlflow.log_params(grid_search.best_params_)
    mlflow.log_metric("cv_auc",   grid_search.best_score_)
    mlflow.log_metric("test_auc", roc_auc_score(y_test, y_pred_proba))
    mlflow.sklearn.log_model(grid_search.best_estimator_, "model")
    print("Run logged to MLflow. View at: mlflow ui")
⚠️
Set Up Monitoring Before You Deploy, Not After

The worst time to design your monitoring strategy is after a model has been silently wrong for three months. Build in alerts, dashboards, and retraining triggers as part of the deployment process. A model without monitoring is not a production system — it's a time bomb.

8 Common Pitfalls at Each Stage

Every stage of the ML lifecycle has characteristic failure modes. Knowing them before you encounter them is half the battle:

Stage Pitfall Prevention
SOWWrong success metric (accuracy on imbalanced data)Always check class balance; prefer AUC/F1 for imbalanced tasks
Data CollectionCollecting data that doesn't exist at prediction timeMap each feature to its availability at inference time
Feature EngineeringData leakage — using the target to create a featureNever use target or future info to create features
PreprocessingFitting scaler on the full dataset (train + test)Always fit transformers on train set only; transform test set
Fine-TuningOverfitting to the validation set during tuningUse cross-validation, not a single validation split, for tuning
DeploymentTraining/serving skew — different preprocessing in productionSerialize entire pipeline (not just the model); use sklearn Pipeline
MonitoringNo retraining trigger; model silently degradesSet automated alerts on PSI, prediction distribution, business KPIs
⚠️
Data Leakage: The Silent Killer

Data leakage is when information about the target variable (or future events) is inadvertently incorporated into your training features. A model trained with leakage will appear to perform brilliantly in evaluation — 99% AUC! — and then fail completely in production. Common sources: including post-event features, scaling on the full dataset, using the target column as a feature by accident, date-based splits that mix future data into training.

🌍

Real-World Spotlight: End-to-End Credit Risk Model

Let's trace all 8 stages for the credit risk project introduced in the SOW above:

🏦
Stage 1 — SOW: Predict 12-month default probability. Success = AUC-ROC ≥ 0.82. Interpretability required (SHAP). No protected attributes as direct inputs.
In [10]:
# Stage 2 — Data Collection
df = pd.read_sql("SELECT * FROM loan_applications WHERE year >= 2022", conn)
print(df.shape)          # (148321, 23)
print(df.isnull().sum()) # credit_score: 1204 missing, employment_years: 89 missing

# Stage 3 — EDA
print(df['loan_default'].value_counts(normalize=True))
# 0    0.937    — 93.7% no default
# 1    0.063    — 6.3% default → class imbalance detected! Use scale_pos_weight

import matplotlib.pyplot as plt
df.boxplot(column='annual_income', by='loan_default')  # income distribution by class

# Stage 4 — Feature Engineering
df['credit_utilization'] = df['revolving_balance'] / df['credit_limit'].clip(lower=1)
df['loan_to_income']     = df['loan_amount'] / df['annual_income'].clip(lower=1)
df['credit_score'].fillna(df['credit_score'].median(), inplace=True)  # impute

# Stage 5 — Model Selection (baseline first)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=500)
lr_auc = cross_val_score(lr, X_train_scaled, y_train, cv=5, scoring='roc_auc').mean()
print(f"Baseline LR AUC: {lr_auc:.4f}")  # 0.7812

# XGBoost as primary model (handles imbalance via scale_pos_weight)
import xgboost as xgb
ratio = (y_train == 0).sum() / (y_train == 1).sum()  # ~14.9
xgb_model = xgb.XGBClassifier(scale_pos_weight=ratio, n_estimators=200,
                                max_depth=5, learning_rate=0.1, random_state=42)
xgb_auc = cross_val_score(xgb_model, X_train, y_train, cv=5, scoring='roc_auc').mean()
print(f"XGBoost CV AUC: {xgb_auc:.4f}")  # 0.8641  ✓ meets threshold

# Stage 6 — Final evaluation on held-out test set
xgb_model.fit(X_train, y_train)
test_auc = roc_auc_score(y_test, xgb_model.predict_proba(X_test)[:, 1])
print(f"Test AUC: {test_auc:.4f}")  # 0.8589

# Stage 7 — Serialize and deploy
import joblib
joblib.dump(xgb_model, "credit_risk_xgb_v1.pkl")
# Deploy with FastAPI (see section 6 above)

# Stage 8 — Monitoring (run monthly)
psi = calculate_psi(X_train['credit_utilization'], X_prod_last_30d['credit_utilization'])
print(f"PSI credit_utilization: {psi:.4f}")  # 0.042 → stable

Quick Check

✍️ Practice Exercises

  1. Write a one-page SOW for the following scenario: "A retail company wants to predict which customers will churn in the next 30 days." Define the problem type, success metric, key features, constraints, and deliverables.
  2. You have a dataset of 50,000 loan applications. Write Python code that outputs: (a) percentage of missing values per column, (b) count of rows with any missing value, (c) the class balance of the target variable.
  3. A teammate proposes using "days_until_payment_missed" as a feature for a default prediction model. Explain why this is an example of data leakage and how you would fix it.
  4. Using the mlflow library, log one complete training run that records: model type, hyperparameters, cross-validation AUC, and the serialized model artifact.
▶ Show hints
In [11]:
# Exercise 2
df = pd.read_csv('loan_applications.csv')
# (a) Missing per column
missing_pct = (df.isnull().mean() * 100).sort_values(ascending=False)
print(missing_pct[missing_pct > 0])
# (b) Rows with any missing value
print(df.isnull().any(axis=1).sum())
# (c) Class balance
print(df['loan_default'].value_counts(normalize=True))

# Exercise 3 — Leakage explanation:
# 'days_until_payment_missed' is derived from the future (the event we're predicting).
# At the time of application, this value does not exist.
# Fix: remove it from features; only use information available at application time.

📚 Primary Source for This Lesson

Rules of Machine Learning — Google Engineering
43 practical rules for ML projects, written by engineers who have shipped hundreds of real-world models. Especially relevant: Rules 1–12 on problem definition and baselines, Rules 27–38 on training/serving skew and model freshness.

💬 Have a specific ML project in mind and not sure how to structure the SOW or which stage you're at? Describe your project — your AI tutor will help you map it to the lifecycle and identify your next step.