CO1 Topic 3 of 25
Tracing a prediction: the training-serving boundary
Follow one request from the API to the response. Then see how a tiny difference between training code and serving code quietly ruins predictions.
You will be able to
- Trace a request through validation, features, inference and response
- Compare batch and real-time prediction
- Define training-serving skew
- Diagnose a skew bug in a running program
Read
- Textbook 1, Ch. 1, pp. 3–37
- Reference book 2, Ch. 2, pp. 21–58
Intuition
Imagine training with attendance recorded as a percentage, 85, while the serving code receives it as a fraction, 0.85. Nothing crashes, and the model returns a neat probability. It is just wrong, for every student, every day. This is the most common kind of production ML bug, because no error message ever appears.
How it works
One request, step by step:
| Step | What happens | If it goes wrong |
|---|---|---|
| 1. Receive | Service accepts the JSON | HTTP 400 |
| 2. Validate | Types and ranges are checked, such as CGPA ≤ 10 | Clear error to the caller |
| 3. Transform | The fitted pipeline makes the features | Skew, if this differs from training |
| 4. Infer | The model calculates a score | |
| 5. Format | A probability becomes a response | |
| 6. Log | Request and result are stored for monitoring | Blind to drift later |
Batch or real-time?
| Batch | Real-time (online) | |
|---|---|---|
| When | On a schedule | At each request |
| Example | Score all final-year students overnight | Score a student as a form is submitted |
| Latency matters? | Little | A lot |
| Features | Can use heavy joins | Must be available right now |
| Failure | Rerun the job | The user sees an error |
The training-serving boundary is the line between the code that made the model and the code that uses it. Skew means the inputs differ across that line. Common causes:
- two separate implementations of the same feature;
- different units or scales;
- a different meaning for missing values;
- using information at training time that isn't available at serving time.
The main cure is to ship the fitted pipeline with the model, so that the very same code runs on both sides. Topic 22 extends this idea to feature stores.
Run it: trace a request, then cause a skew bug
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.pipeline import make_pipeline
from placement_predict import FEATURES, load_data, preprocessor, train_val_test
df = load_data()
X_train, X_val, X_test, y_train, y_val, y_test = train_val_test(df)
pipeline = preprocessor()
clf = LogisticRegression(max_iter=1000)
model = make_pipeline(pipeline, clf).fit(X_train, y_train)
request = {"CollegeTier": "Tier2", "Stream": "IT", "Specialisation": "DataScience", "Hostel": "No", "HistoryOfBacklogs": "No",
"CGPA": 8.1, "AttendancePercent": 84, "Internships": 1, "Projects": 3, "Workshops": 2, "Certifications": 2,
"Publications": 0, "AptitudeTestScore": 70, "SoftSkillsRating": 3.4, "CodingTestScore": 76,
"MockInterviewScore": 68, "ExtraCurricular": 1}
print("1 Received: ", request["CGPA"], request["Stream"], "...")
assert 0 <= request["CGPA"] <= 10, "CGPA out of range"
print("2 Validated")
row = pd.DataFrame([request]).reindex(columns=FEATURES)
features = pipeline.transform(row)
print("3 Features: ", np.round(features[0][:4], 2), "...", features.shape[1], "values")
score = clf.decision_function(features)[0]
print(f"4 Score: {score:.3f}")
p = 1 / (1 + np.exp(-score))
print(f"5 Response: placed_probability = {p:.3f}")
print("\nTraining-serving skew")
print(f"Correct serving code: accuracy = {accuracy_score(y_val, model.predict(X_val)):.3f}")
skewed = X_val.copy()
skewed["AttendancePercent"] = skewed["AttendancePercent"] / 100 # the serving team used fractions
print(f"Attendance sent as a fraction: accuracy = {accuracy_score(y_val, model.predict(skewed)):.3f}")
skewed = X_val.copy()
skewed["CGPA"] = skewed["CGPA"] * 9.5 # the serving team used percentages
print(f"CGPA sent as a percentage: accuracy = {accuracy_score(y_val, model.predict(skewed)):.3f}")
skewed = X_val.copy()
skewed["MockInterviewScore"] = skewed["MockInterviewScore"].fillna(0) # a different meaning for missing
print(f"Missing values filled with 0: accuracy = {accuracy_score(y_val, model.predict(skewed)):.3f}")
print("\nNo error was raised in any of these runs.")
Try this: change the attendance bug to skewed["AttendancePercent"] * 2. Compare what the fractions do with what the larger values do. Which is harder to notice?
Common mistakes
- Rewriting feature code for the serving side. Sooner or later, the two copies will differ.
- Using
fit_transformon serving data. That learns new statistics from a single request. Usetransform. - Testing only that the service returns a response. A wrong number looks just like a right one.
Problems
- In your own words, explain skew to a teammate who has never heard of it.
- For each, decide on batch or real-time: (a) a weekly report to the placement cell; (b) a chatbot that estimates chances as you type; (c) a yearly retraining job.
- A feature "offers received so far this year" is used in training. Why is it a problem at prediction time?
- Add range checks for all numeric fields to the validation step.
- Write a test that would have caught the fraction bug before release.
Flashcards
Say the meaning to yourself, then select the card to check.
- Training-serving skew
- Inputs at prediction time differ from what the model saw in training.
- Main cure for skew
- Ship the fitted feature pipeline with the model.
- Batch prediction
- Scoring on a schedule, when nobody is waiting.
- Real-time prediction
- Scoring per request, where latency matters.