🚀 Section 5 · Deployment & MLOps Basics 🏁 Course Finale MODULE 24 · FINAL LESSON

Capstone — Full ML Project, from Data to API

⏱️ 100 min · hands-on
📖 End-to-End Data → API Pipeline
🧩 4 Quiz Questions
🎓 24 of 24 — Course Finale
Your progress in Section 5100%
🎯 The Capstone: this is it — the final lesson of Machine Learning Fundamentals, and every skill from all 24 lessons gets used here. You'll take the "Student Score Predictor" dataset this course has referenced since Lesson 21, run it through preprocessing, train and compare several models (Sections 2–3's algorithms), pick the best one using the metrics from Section 3, save it with joblib (Lesson 21), and wrap it in a working FastAPI endpoint (Lesson 22) — a real, complete data-to-API pipeline. It closes with a full recap of all 5 sections and honest suggestions for what to build next.

The Capstone Brief

Every earlier project in this course (Lesson 12's house price model, Lesson 16's model comparison report, Lesson 20's customer segmentation) practiced one slice of the workflow at a time. This capstone runs the FULL pipeline end to end, on one dataset, finishing with something a real user could actually call over HTTP.

📋 The brief
Using the illustrative student_scores.csv dataset (study hours, attendance, previous score, assignments completed → final score) introduced in Lesson 21: preprocess the data, train and compare several regression models, evaluate them with proper metrics, pick the best one, save it with joblib, and serve it through a FastAPI /predict endpoint — exactly the same schema Lesson 22's API already used.
1
Load the dataset
The same student performance features referenced since Lesson 21.
2
Preprocess
Train/test split (Lesson 4) and feature scaling (Lesson 3) inside a reusable pipeline.
3
Train & compare multiple models
Linear Regression, Ridge, Decision Tree, Random Forest, and Gradient Boosting (Sections 2's algorithms).
4
Evaluate & pick the best
Compare with regression metrics from Lesson 15 — MAE, RMSE, and R².
5
Save the winning pipeline
joblib.dump(), exactly as covered in Lesson 21.
6
Wrap it in a FastAPI endpoint
The same Pydantic + /predict pattern from Lesson 22.

The Dataset

The same four features this course has used since Lesson 21 and Lesson 22's worked examples — kept consistent on purpose, so this capstone is a genuine continuation rather than a brand-new unrelated exercise. Illustrative synthetic data, deliberately similar in spirit to the "500 real student records" a real capstone would collect and clean.

student_scores.csv — illustrative sample rows, not real student records
StudentIDStudyHoursAttendance%PrevScoreAssignmentsDoneFinalScore
14.59278984
21.26552449
36.098881093
42.87461660
53.98070772
load_data.py
PYTHON
import pandas as pd

df = pd.read_csv("student_scores.csv")
print(df.shape)
print(df.describe())
print(df.isnull().sum())  # confirm no missing values before training

Preprocessing

A proper train/test split (Lesson 4) BEFORE any scaling happens — fitting a scaler on the full dataset first would leak information from the test set into training, exactly the mistake Lesson 4 and Lesson 21 both warned against.

preprocess.py
PYTHON
from sklearn.model_selection import train_test_split

features = ["StudyHours", "Attendance", "PrevScore", "AssignmentsDone"]
X = df[features]
y = df["FinalScore"]

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
print(f"Train: {X_train.shape[0]} rows · Test: {X_test.shape[0]} rows")

Scaling isn't applied here directly — instead, each model below gets wrapped in its own Pipeline with a StandardScaler step (Lesson 21's Section 5 pattern), so scaling is always fit ONLY on training data, correctly, no matter which model is being tried.

Train & Compare Multiple Models

Five regression approaches from Section 2 of this course, each wrapped in the same StandardScaler pipeline, trained on the exact same split, and compared fairly with 5-fold cross-validation (Lesson 4) on the training set.

train_and_compare.py
PYTHON
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.model_selection import cross_val_score

candidates = {
    "Linear Regression": LinearRegression(),
    "Ridge": Ridge(alpha=1.0),
    "Decision Tree": DecisionTreeRegressor(max_depth=5, random_state=42),
    "Random Forest": RandomForestRegressor(n_estimators=200, random_state=42),
    "Gradient Boosting": GradientBoostingRegressor(n_estimators=200, random_state=42),
}

results = {}
for name, estimator in candidates.items():
    pipe = Pipeline([("scaler", StandardScaler()), ("model", estimator)])
    scores = cross_val_score(pipe, X_train, y_train, cv=5, scoring="r2")
    results[name] = scores.mean()
    print(f"{name:<20} CV R²: {scores.mean():.3f} (+/- {scores.std():.3f})")
Illustrative cross-validated R² by model on this project's data
Linear Regression
0.71
baseline
Ridge
0.72
≈ same
Decision Tree
0.67
lower
Random Forest
0.84
+13pts
Gradient Boosting
0.87
best ⬅

Consistent with Section 2's general lessons (Lesson 10 especially), the ensemble methods — Random Forest and Gradient Boosting — outperform the plain linear models on this illustrative data, and Gradient Boosting edges out Random Forest slightly. That makes it this capstone's leading candidate, pending a proper held-out test-set check in Section 5.

Evaluate & Pick the Best Model

Cross-validation picked a leading candidate — now it gets a final, honest check against the held-out X_test/y_test set it has never seen, using the full regression metric set from Lesson 15: MAE, RMSE, and R².

evaluate_best_model.py
PYTHON
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np

best_pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("model", GradientBoostingRegressor(n_estimators=200, random_state=42))
])
best_pipeline.fit(X_train, y_train)

y_pred = best_pipeline.predict(X_test)

mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)

print(f"Test MAE:  {mae:.2f}")
print(f"Test RMSE: {rmse:.2f}")
print(f"Test R²:   {r2:.3f}")
Illustrative final test-set comparison — a toy dataset, not real benchmark results
ModelTest MAETest RMSETest R²
Linear Regression6.88.40.70
Random Forest4.15.30.83
Gradient Boosting3.64.90.86

Gradient Boosting wins on all three metrics on the held-out test set — lowest error, highest R². It's the model this capstone carries forward into Section 6.

💡
A quick hyperparameter pass, referencing Lesson 11
Before finalizing, a real project would run GridSearchCV or RandomizedSearchCV over Gradient Boosting's n_estimators, max_depth, and learning_rate (Lesson 11) to squeeze out any further improvement — skipped here for brevity, but a genuinely worthwhile step before shipping a real model.

Save the Winning Pipeline with Joblib

Exactly Lesson 21's pattern: the WHOLE pipeline (scaler + model), saved as a single joblib file, so nothing can get out of sync when it's loaded later.

save_model.py
PYTHON
import joblib

joblib.dump(best_pipeline, "student_score_model_final.joblib")
print("Final capstone pipeline saved to student_score_model_final.joblib")

Wrap It in a FastAPI Endpoint

The exact StudentInput / PredictionOutput / /predict pattern from Lesson 22, pointed at this capstone's newly saved student_score_model_final.joblib.

api.py
PYTHON
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import pandas as pd

app = FastAPI(title="Student Score Predictor API — Capstone", version="1.0")
model = joblib.load("student_score_model_final.joblib")

class StudentInput(BaseModel):
    study_hours: float
    attendance: float
    prev_score: float
    assignments_done: int

class PredictionOutput(BaseModel):
    predicted_score: float
    confidence: str

@app.get("/health")
def health():
    return {"status": "ok", "model_loaded": model is not None}

@app.post("/predict", response_model=PredictionOutput)
def predict(student: StudentInput):
    features = pd.DataFrame([{
        "StudyHours": student.study_hours,
        "Attendance": student.attendance,
        "PrevScore": student.prev_score,
        "AssignmentsDone": student.assignments_done
    }])
    pred = model.predict(features)[0]
    confidence = "high" if pred > 80 else "medium" if pred > 60 else "low"
    return PredictionOutput(predicted_score=round(float(pred), 1), confidence=confidence)

# Run with: uvicorn api:app --reload
GET
/health
Confirms the capstone's final model loaded correctly.
POST
/predict
Accepts a StudentInput body, returns the Gradient Boosting model's predicted final score.

Starting the server with uvicorn api:app --reload and visiting /docs gives a fully working, testable prediction API — trained, evaluated, saved, and served, all from this one lesson.

The Complete Pipeline, Start to Finish

Every step from Sections 2–6, combined into one runnable training script.

train_capstone_model.py — COMPLETE PROGRAM
PYTHON
import pandas as pd
import numpy as np
import joblib
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

# 1. Load
df = pd.read_csv("student_scores.csv")
features = ["StudyHours", "Attendance", "PrevScore", "AssignmentsDone"]
X, y = df[features], df["FinalScore"]

# 2. Split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 3. Compare candidates with cross-validation
candidates = {
    "Linear Regression": LinearRegression(),
    "Ridge": Ridge(alpha=1.0),
    "Decision Tree": DecisionTreeRegressor(max_depth=5, random_state=42),
    "Random Forest": RandomForestRegressor(n_estimators=200, random_state=42),
    "Gradient Boosting": GradientBoostingRegressor(n_estimators=200, random_state=42),
}
best_name, best_score = None, -np.inf
for name, estimator in candidates.items():
    pipe = Pipeline([("scaler", StandardScaler()), ("model", estimator)])
    score = cross_val_score(pipe, X_train, y_train, cv=5, scoring="r2").mean()
    print(f"{name:<20} CV R²: {score:.3f}")
    if score > best_score:
        best_name, best_score = name, score

print(f"\nBest candidate: {best_name} (CV R² = {best_score:.3f})")

# 4. Fit the winner on the full training set, evaluate on the held-out test set
best_pipeline = Pipeline([("scaler", StandardScaler()), ("model", candidates[best_name])])
best_pipeline.fit(X_train, y_train)
y_pred = best_pipeline.predict(X_test)
print(f"Test MAE:  {mean_absolute_error(y_test, y_pred):.2f}")
print(f"Test RMSE: {np.sqrt(mean_squared_error(y_test, y_pred)):.2f}")
print(f"Test R²:   {r2_score(y_test, y_pred):.3f}")

# 5. Save with joblib — ready for Section 7's FastAPI app
joblib.dump(best_pipeline, "student_score_model_final.joblib")
print("Saved to student_score_model_final.joblib")

Everything You Covered — a Five-Section Recap

24 lessons is real ground covered. Before the wrap-up, here's the whole course compressed into five lines — one per section. If any of these feel unfamiliar, that's a completely normal reason to go back and revisit — nothing about finishing this course means every detail has to be perfectly memorized.

1
What Is Machine Learning?
Types of ML, the end-to-end ML pipeline, data preprocessing (encoding & scaling), train/test splits & cross-validation, and overfitting vs. underfitting.
2
Supervised Learning Algorithms
Linear regression math, SVMs, KNN, Naive Bayes, gradient boosting (XGBoost/LightGBM), hyperparameter tuning, and your first capstone — house price prediction.
3
Model Evaluation Mastery
Classification metrics deep dive, handling imbalanced datasets, regression metrics & residual analysis, and a full model comparison report project.
4
Unsupervised Learning
Clustering (K-Means, DBSCAN, hierarchical), PCA for dimensionality reduction, anomaly detection, and the customer segmentation capstone.
5
Deployment & MLOps Basics
Saving/loading models with pickle & joblib, building a real API with FastAPI, monitoring & drift detection, and this final data-to-API capstone.
You built real, end-to-end projects along the way
Lesson 12's house price model, Lesson 16's model comparison report, Lesson 20's customer segmentation, and this capstone's full data-to-API pipeline — that's four genuine hands-on projects, not just theory. Together they're a real starting portfolio.

What to Build Next

Finishing 24 lessons is a real milestone — but the skill itself keeps building through what happens after. A few honest, concrete next steps:

1
Rebuild this capstone with a real dataset
Swap the illustrative student data for a real public dataset (Kaggle is a common source) and run the exact same pipeline — preprocess, compare models, evaluate, save, serve.
2
Deploy the FastAPI app somewhere real
Get this lesson's api.py running on an actual server or hosting platform, not just localhost — that's the difference between a working demo and a genuinely deployed project.
3
Add the monitoring from Lesson 23
Wire up prediction logging on the deployed API, and actually watch the feature-statistics comparison over a few real weeks.
4
Go deeper into one area that clicked
Whether that's deep learning, NLP, more advanced MLOps, or a specific domain — this course is a foundation, and the honest next step is choosing a direction and going deeper into it with a real project.
⚠️
Be honest about where you are
Completing this course is real and worth being proud of — it's also a starting point, not a finish line. Describe your skills accurately wherever you share them; "I've built and deployed ML projects through a hands-on course" holds up far better than an inflated claim, and it's genuinely true.
🧩 Knowledge Check — Lesson 24
4 questions on the end-to-end capstone pipeline.
1. In this capstone, why is the train/test split done BEFORE fitting any StandardScaler?
2. Why does this capstone use cross-validation (Section 4) AND a final held-out test-set evaluation (Section 5), instead of just one or the other?
3. What gets saved with joblib.dump() at the end of this capstone's training script?
4. In the capstone's api.py, what does the /predict endpoint do with the incoming StudentInput before calling model.predict()?
🚀
Try It Yourself — Lesson 24
Your own capstone submission · Course Finale

Use the candidates, best_pipeline, and api.py objects from Sections 4–8 as your starting point for each task below.

Task 1: Tune the winning model 🎛️

Using Lesson 11's GridSearchCV, search over Gradient Boosting's n_estimators (e.g. [100, 200, 300]), max_depth (e.g. [2, 3, 4]), and learning_rate (e.g. [0.01, 0.1, 0.2]). Does the tuned version beat this lesson's default-parameter Test R² from Section 5?
Task 2: Add a /predict/batch endpoint to api.py 📦

Following Lesson 22's Task 3 pattern, add a Pydantic model wrapping list[StudentInput] and a POST /predict/batch endpoint that returns a list of predictions in one request.
Task 3: Rebuild the whole pipeline on a real dataset 🌍

Find a real, public regression dataset (a Kaggle dataset is a common starting point) and re-run Section 8's complete script against it: same structure, different data. Write a short paragraph comparing the winning model and its metrics to this lesson's illustrative results.
💡 Show hints if you're stuck
  • Task 1: GridSearchCV(pipeline, param_grid, cv=5, scoring="r2") — remember the parameter names need the model__ prefix when tuning a step inside a Pipeline, e.g. model__n_estimators.
  • Task 2: class BatchInput(BaseModel): students: list[StudentInput], then build one combined DataFrame from all of them before calling model.predict() once.
  • Task 3: Keep the exact same features/target variable structure — only the CSV and column names need to change, the rest of Section 8's script can stay the same shape.
Finished the capstone?
Mark it complete to finish Machine Learning Fundamentals.
🎓

Course Complete! 🎉

You've finished all 24 lessons of Machine Learning Fundamentals — from your first look at what ML even is, through supervised and unsupervised algorithms, rigorous model evaluation, and now a real, deployed data-to-API pipeline. That's a genuine, complete foundation. Congratulations!

Module 24 of 24 Section 5 — Deployment & MLOps Basics