The prediction pipeline
The prediction pipeline is the path a single record takes after training: it loads the preprocessor and the model that a training run saved, transforms the record with that fitted preprocessor, and returns one number.
Last updated: 19 Oct, 2026 · MLOps
The model trainer left two files on disk. Training is finished with them; everything from here reads them. The video builds this as its own module under the pipeline folder, a small class with a predict method, and then hands that class to the web app. The class is written here, and the service that calls it comes later in the course.
The two artifacts a training run leaves behind
Both files were written through the same helper, so a reload is symmetric. No other piece of the training run is needed at prediction time: no training data, no pipeline code of your own, no config.
# written by the two stages through save_object in src/sp/utils/common.py
joblib.dump(pre, "artifacts/data_transformation/preprocessor.joblib")
joblib.dump(model, "artifacts/model_trainer/model.joblib")The clip loads a Keras model and prepares its input with tensorflow.keras.preprocessing.image.load_img, which Keras 3 replaced with keras.utils.load_img; this class loads a joblib-saved scikit-learn pipeline, so what carries over is the shape of the class.
Loading the saved objects once
The constructor takes the two paths and loads both objects. Doing it in __init__ rather than inside predict matters once a server is calling this: the files are read once at start-up instead of on every request.
from pathlib import Path
import pandas as pd
from sp.utils.common import load_objectclass PredictPipeline:
"""Loads what a training run saved, and scores one record with it."""
def __init__(
self,
preprocessor_path=Path("artifacts/data_transformation/preprocessor.joblib"),
model_path=Path("artifacts/model_trainer/model.joblib"),
):
# both files are read once here, not on every request
self.pre = load_object(preprocessor_path)
self.model = load_object(model_path)Turning one record into a row
A record arrives as a dict, one key per feature. Wrapping it in a one-row frame is what lets the preprocessor match by column name, exactly as it did during training. The result is a one-row matrix, and the first value of the prediction is the answer.
def predict(self, record: dict) -> float:
"""Turn one student record into a predicted maths score."""
df = pd.DataFrame([record])
return float(self.model.predict(self.pre.transform(df))[0])Predicting a maths score for one student
The record below is the first row of the dataset with its maths score taken out, so the prediction can be read against the real value of 72.
import joblib
import pandas as pd
pre = joblib.load("artifacts/data_transformation/preprocessor.joblib")
model = joblib.load("artifacts/model_trainer/model.joblib")
# the first row of the dataset, without the maths score the model has to produce
record = {"gender": "female", "race_ethnicity": "group B",
"parental_level_of_education": "bachelor's degree", "lunch": "standard",
"test_preparation_course": "none", "reading_score": 72, "writing_score": 74}
row = pd.DataFrame([record])
print(round(float(model.predict(pre.transform(row))[0]), 2))69.34
- The answer is 69.34 against a true score of 72, which is inside the average miss of about five marks the trainer reported.
- The transform came from the saved object, so
group Blanded in the same one-hot column it occupied during training and the two scores were scaled with the training medians. - No training data was touched. The two joblib files are the whole of what serving needs, which is why they are the files a container copies in.
A record that is missing a column
The failure to meet first is the common one: a form or an API call that leaves a field out. The preprocessor was fitted on seven named columns, and it says so.
# the same record with one field left out, the way a web form drops a field
short = {k: v for k, v in record.items() if k != "test_preparation_course"}
print(pre.transform(pd.DataFrame([short])))Traceback (most recent call last):
File "main.py", line 3, in <module>
print(pre.transform(pd.DataFrame([short])))
ValueError: columns are missing: {'test_preparation_course'}The fix is not to patch the preprocessor. It is to reject the request before it reaches this line, with a schema that names every field the model needs, which is how the serving lesson handles it.
Training against predicting
| The training run | The prediction pipeline | |
|---|---|---|
| Reads | train.csv and test.csv | Two joblib files |
| The preprocessor | Fitted with fit_transform | Loaded and used with transform |
| Rows at a time | 800 | One |
| Runs | When you retrain | On every request |
| Produces | A model and a metrics file | One number |
Where the prediction pipeline sits
- Behind an API, where a request body becomes the record dict and the returned number becomes the response.
- Inside a container, where the image carries the two artifacts and this module, which is all that answering a request needs.
- In a batch job, where the same loaded pair scores a file of records in a loop rather than one at a time.
math_score as well does not help the model and a record missing a feature stops it, so validate the request against the same schema the training data was checked with.Related
- Previous: The model trainer
- Next: Experiment tracking
- Reference: Model persistence with joblib
- Change
reading_scoreandwriting_scoreto 95 and read how far the prediction moves. - Load the preprocessor and call
get_feature_names_out()to see the 19 column names the model was trained on. - Pass a list of three records to
pd.DataFrameand read three predictions back from one call.
Little by little, you're building something great.