CO6 Topic 25 of 25
Revision: the whole PlacementPredict pipeline, end to end
Bring the term together. Train several models, log every run, register the best, deploy it, answer requests, and watch for drift, in one program.
You will be able to
- Run the lifecycle from data to monitoring
- Log parameters, AUC and calibration error per run
- Register the best model as Production
- Trace one prediction back through every stage
Read
- Reference book 2, Ch. 8, pp. 247–286; Ch. 9, pp. 287–318
- Reference book 4, Ch. 7, pp. 163–196; Ch. 8, pp. 197–228
Intuition
On day one, you sent a request to a service you couldn't explain. Now trace it back: the response came from a registered model, chosen by a validated experiment, trained on a pipeline fitted to versioned data. Everything has a reason.
How it works
| Step | Topic | Evidence you should keep |
|---|---|---|
| Data loaded and validated | 4 | Row counts, schema checks |
| Features fitted on training data | 8, 22 | The saved pipeline |
| Models compared | 5–12 | A results table |
| Evaluated honestly | 17–20 | AUC, ECE, significance test |
| Packaged and served | 23 | Model file, API test |
| Monitored | 24 | PSI report |
| Retrained when needed | 24 | Gate decision |
Exam checklist. Can you explain:
- skew, and how a pipeline prevents it?
- why a test set stays hidden?
- why L1 gives zeros?
- bagging against boosting?
- when ROC-AUC misleads?
- PSI, and what it misses?
In MLflow's model registry, you would log hyperparameters, validation AUC, calibration ECE and the ONNX file for each run, then assign the best model the Production alias. The program below does the same with a small tracker, so it can run here.
Run it: the end-to-end demo
import io
import json
import joblib
import numpy as np
import pandas as pd
import xgboost as xgb
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from placement_predict import FEATURES, NUMERIC, load_data, preprocessor, train_val_test
def ece(y, p, bins=10):
index = np.clip((p * bins).astype(int), 0, bins - 1)
return sum(abs(p[index == b].mean() - y[index == b].mean()) * (index == b).mean() for b in range(bins) if (index == b).any())
class Tracker:
"""A small stand-in for MLflow."""
def __init__(self):
self.runs, self.production = [], None
def log(self, name, params, model, auc, calibration):
self.runs.append({"run": len(self.runs) + 1, "name": name, "params": str(params), "val_auc": auc, "val_ece": calibration, "model": model})
def promote_best(self):
self.production = max(self.runs, key=lambda r: r["val_auc"])
return self.production
df = load_data()
X_train, X_val, X_test, y_train, y_val, y_test = train_val_test(df)
print(f"1 Data: {len(df)} rows, {df['PlacementStatus'].mean():.0%} placed")
# Each model is a pair: a fitted feature pipeline and a classifier. They always travel together.
def fit_pair(prep, clf):
prep.fit(X_train)
clf.fit(prep.transform(X_train), y_train)
return prep, clf
def probability(pair, X):
prep, clf = pair
return clf.predict_proba(prep.transform(X))[:, 1]
candidates = [
("LogisticRegression", {"C": 1.0}, preprocessor(), LogisticRegression(C=1.0, max_iter=1000)),
("RandomForest", {"n_estimators": 150}, preprocessor(scale=False), RandomForestClassifier(150, random_state=0, n_jobs=1)),
("XGBoost", {"max_depth": 3, "eta": 0.1}, preprocessor(scale=False), xgb.XGBClassifier(n_estimators=150, max_depth=3, learning_rate=0.1, n_jobs=1, random_state=0)),
]
tracker = Tracker()
for name, params, prep, clf in candidates:
pair = fit_pair(prep, clf)
p = probability(pair, X_val)
tracker.log(name, params, pair, roc_auc_score(y_val, p), ece(y_val.to_numpy(), p))
print("2 Experiments:")
print(pd.DataFrame(tracker.runs).drop(columns="model").round(4).to_string(index=False))
best = tracker.promote_best()
print(f"3 Registered as Production: run {best['run']}, {best['name']}")
print(f"4 Final test AUC, measured once: {roc_auc_score(y_test, probability(best['model'], X_test)):.4f}")
file = io.BytesIO()
joblib.dump(best["model"], file)
file.seek(0)
served = joblib.load(file)
print(f"5 Packaged: {len(file.getvalue()) / 1024:.0f} KB")
def predict_endpoint(body):
request = json.loads(body)
row = pd.DataFrame([request]).reindex(columns=FEATURES).astype({c: float for c in NUMERIC})
p = float(probability(served, row)[0])
return json.dumps({"placed_probability": round(p, 3), "prediction": "Placed" if p >= 0.5 else "Not placed", "model_run": best["run"]})
print("6 Request:", predict_endpoint('{"CollegeTier": "Tier2", "Stream": "CS", "Hostel": "No", "HistoryOfBacklogs": "No", "Specialisation": "AI", "CGPA": 8.2, "CodingTestScore": 78, "AptitudeTestScore": 70, "Internships": 1, "MockInterviewScore": 66}'))
live = X_test.copy()
live["CodingTestScore"] = (live["CodingTestScore"] + 12).clip(upper=100)
reference, current = X_train["CodingTestScore"].dropna(), live["CodingTestScore"].dropna()
edges = np.quantile(reference, np.linspace(0, 1, 11))
edges[0], edges[-1] = -np.inf, np.inf
e = np.clip(np.histogram(reference, edges)[0] / len(reference), 1e-4, None)
a = np.clip(np.histogram(current, edges)[0] / len(current), 1e-4, None)
psi = float(np.sum((a - e) * np.log(a / e)))
print(f"7 Monitoring: PSI of CodingTestScore = {psi:.3f}, so {'retrain' if psi > 0.2 else 'keep watching'}")
Try this: add a fourth candidate with LightGBM. Does the Production choice change? Then trace its run back through the tracker.
Common mistakes
- Choosing the production model by test score. Select on validation, then report test once.
- Ignoring calibration when people read the probability. Look at ECE as well as AUC.
- Treating deployment as the end. It's where monitoring starts.
Problems
- Trace a single request by hand through the 7 stages of this program.
- Why is the final test AUC often a little lower than the validation AUC?
- Lab 13: write
end_to_end_check(model_path, data_path, n_batch=20), as in Topic 23. - Write a one-page design note for PlacementPredict: data, model, metrics, serving and monitoring.
- Explain the system to a placement officer in a few sentences, with no jargon.
Flashcards
Say the meaning to yourself, then select the card to check.
- Choosing the production model
- Select on validation, then report the test score once.
- Model registry
- Records versions, and which one is live.
- ECE
- The weighted gap between predicted confidence and actual accuracy.
- Bagging against boosting
- Bagging mainly cuts variance, and boosting mainly cuts bias.