I Built an ML Pipeline That Retrains a Model When Performance Drops

A machine learning model might work great when you first launch it, but it can become unreliable over time. I quickly learned that training a model is just the start when working with production ML systems. You also need a way to spot when the model isn’t performing well and to respond automatically. In this tutorial, I’ll show you how to build an automated ML retraining pipeline that keeps an eye on model performance, checks if it drops below a set threshold, retrains the model with new data, validates the updated version, and deploys it if it does better.

The Problem With a Static ML Model

When I first started thinking about production machine learning, it was easy to imagine the workflow as:

collect data → train model → deploy model → done.

Real ML systems aren’t that simple. The data your model gets in production can change over time. Customers might act differently, product preferences can shift, the economy can change, and even how you collect data might be different.

This is usually called data drift or concept drift, depending on what exactly is changing.

The key thing to remember is that your model’s performance can go down over time.

So rather than checking the model by hand every few weeks, I want the pipeline to handle this automatically:

That’s the main idea behind setting up automated model retraining.

Building an ML Pipeline That Retrains a Model

Step 1: Create Our Initial Model

For this tutorial, I’ll use scikit-learn’s make_classification() to generate training data.

I start with a fairly easy training problem, then later make the production data harder. This helps us see what happens when the environment changes.

Here’s the initial training pipeline:

import os
import joblib

from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


MODEL_PATH = "model/current_model.joblib"

os.makedirs("model", exist_ok=True)


def train_initial_model():
    X, y = make_classification(
        n_samples=5000,
        n_features=10,
        n_informative=6,
        n_redundant=2,
        class_sep=1.5,
        random_state=42
    )

    X_train, X_test, y_train, y_test = train_test_split(
        X,
        y,
        test_size=0.2,
        random_state=42,
        stratify=y
    )

    model = RandomForestClassifier(
        n_estimators=150,
        random_state=42
    )

    model.fit(X_train, y_train)

    predictions = model.predict(X_test)
    accuracy = accuracy_score(y_test, predictions)

    joblib.dump(model, MODEL_PATH)

    print(f"Initial model accuracy: {accuracy:.4f}")

    return model


if __name__ == "__main__":
    train_initial_model()
Initial model accuracy: 0.9520

I use joblib here because it’s an easy way to save a scikit-learn model on your computer.

Step 2: Simulate New Production Data

Now we need something to stand in for data coming from production.

I’ll make this new dataset tougher for the current model by reducing how separate the classes are:

def generate_production_data():
    X, y = make_classification(
        n_samples=1000,
        n_features=10,
        n_informative=6,
        n_redundant=2,
        class_sep=0.5,
        random_state=100
    )

    return X, y

In a real project, this function would pull data from a database, data warehouse, object store, event stream, or another production source.

The key is that, at some point, we get the true labels for the data.

If you don’t have labels, you can still watch for input drift and other signals, but you can’t directly measure things like accuracy, precision, or recall.

Step 3: Monitor Model Performance

Next, I’ll set up the monitoring logic. For this example, I picked 80% accuracy as the point where we retrain:

RETRAIN_THRESHOLD = 0.80


def evaluate_model(model, X, y):
    predictions = model.predict(X)
    accuracy = accuracy_score(y, predictions)

    print(f"Production accuracy: {accuracy:.4f}")

    return accuracy

The pipeline now has a simple decision:

def should_retrain(accuracy):
    return accuracy < RETRAIN_THRESHOLD

This setup is simple, but I like starting with a basic rule. In real production, you might track precision, recall, F1-score, calibration, business KPIs, latency, or several metrics at once. The best metric depends on your problem.

For example, accuracy isn’t a good choice if you’re dealing with a highly imbalanced fraud detection problem.

Step 4: Automatically Retrain the Model

When performance drops below the threshold, we train a new model using the latest available data:

def retrain_model(X, y):
    X_train, X_val, y_train, y_val = train_test_split(
        X,
        y,
        test_size=0.2,
        random_state=42,
        stratify=y
    )

    new_model = RandomForestClassifier(
        n_estimators=200,
        random_state=42
    )

    new_model.fit(X_train, y_train)

    predictions = new_model.predict(X_val)
    validation_accuracy = accuracy_score(
        y_val,
        predictions
    )

    print(
        f"New model validation accuracy: "
        f"{validation_accuracy:.4f}"
    )

    return new_model, validation_accuracy

I don’t recommend automatically deploying a retrained model just because retraining is done. Retraining and deployment should be separate decisions.

Preparing for AI/ML Interviews?
If you’re working on projects like this to get ready for your first AI/ML interview, check out my book Cracking Your First AI/ML Interview. I wrote it to help you focus on the concepts and questions that really matter in interviews.

The new model should outperform the current one or at least meet the validation criteria you’ve set.

Step 5: Validate and Deploy the New Model

Now we can connect everything:

def deploy_model(model):
    joblib.dump(model, MODEL_PATH)
    print("New model deployed successfully.")


def run_pipeline():
    current_model = joblib.load(MODEL_PATH)

    X_prod, y_prod = generate_production_data()

    current_accuracy = evaluate_model(
        current_model,
        X_prod,
        y_prod
    )

    if current_accuracy >= RETRAIN_THRESHOLD:
        print("Performance is healthy. No retraining needed.")
        return

    print("Performance dropped. Starting retraining...")

    new_model, new_accuracy = retrain_model(
        X_prod,
        y_prod
    )

    if new_accuracy > current_accuracy:
        deploy_model(new_model)
        print("Retrained model is better. Deployment complete.")
    else:
        print("New model did not improve performance.")
        print("Keeping the current production model.")


if __name__ == "__main__":
    run_pipeline()
Production accuracy: 0.5330
Performance dropped. Starting retraining...
New model validation accuracy: 0.8250
New model deployed successfully.
Retrained model is better. Deployment complete.

Now the whole workflow runs automatically. The system loads the current model, tests it with new production data, checks the threshold, retrains if needed, validates the new model, and only updates the model if the new one does better.

The Takeaway

What I find most valuable here isn’t the Random Forest model itself, but the feedback loop.

A production ML system shouldn’t just make predictions. It should keep checking if those predictions are still useful and have a clear way to respond when performance changes.

That’s the mindset I hope you develop when working on ML projects. Don’t stop at “I trained a model with 95% accuracy.” Always think about what happens after you deploy it.

I hope you enjoyed this article about building an ML pipeline that retrains a model when performance drops. For more AI and machine learning tips, feel free to follow me on Instagram.

Aman Kharwal
Aman Kharwal

AI/ML Engineer | Published Author. My aim is to decode data science for the real world in the most simple words.

Articles: 2204

Leave a Reply

Discover more from AmanXai by Aman Kharwal

Subscribe now to keep reading and get access to the full archive.

Continue reading