How to Save and Load Machine Learning Models in Python

How to Save and Load Machine Learning Models in Python

You spend three hours training a machine learning model. You tune the hyperparameters, run cross validation, get the accuracy where you want it, and feel good about the result. Then you close the notebook.

The next morning you open a new script to make predictions on fresh data. The model is gone. You have to retrain it from scratch, wait through the entire training process again, and hope you remember exactly which parameters you used the night before.

This is the problem every machine learning practitioner runs into within their first few projects, and it has a completely straightforward solution. You save the trained model to a file before closing the notebook. You load it from that file whenever you need it again. No retraining. No waiting. No guessing at parameters. The model loads in a fraction of a second exactly as it was when you saved it.

In Python there are two main tools for doing this: joblib and pickle. Both serialize your trained model object into a file that can be saved to disk and loaded back into memory later. This guide covers both approaches, when to use each one, how to save entire pipelines including scalers, and what to watch out for when saving models for production deployment.

What Does Saving a Model Actually Mean?

When you train a machine learning model in Python, the result is a Python object sitting in memory. A LogisticRegression object with fitted coefficients. A RandomForestClassifier with all its decision trees built and stored. A StandardScaler with the mean and standard deviation it calculated from your training data.

The moment your script ends or your notebook kernel restarts, that object disappears from memory. Saving a model means serializing that object, converting it from a live Python object into a sequence of bytes that can be written to a file on disk. Loading a model means deserializing that file back into the original Python object so your code can use it exactly as if it had just finished training.

Think of it like saving a document. The document exists as formatted text in your word processor. Saving it converts it to a file format stored on disk. Opening it converts it back to formatted text in your word processor. The content is identical. The document did not have to be rewritten from scratch.

Setting Up Your Environment

Both joblib and pickle are available without additional installation. joblib comes with scikit-learn. pickle is part of Python’s standard library. Install scikit-learn if you have not already:

pip install scikit-learn numpy pandas

Import everything you need at the top of your script:

python

import joblib
import pickle
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris, load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

Step by Step: Saving and Loading Models in Python

Step 1: Train a Model to Save

Before saving anything you need a trained model. This example trains a logistic regression classifier on the Iris dataset:

python

from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
    iris.data, iris.target,
    test_size=0.2,
    random_state=42
)

model = LogisticRegression(max_iter=200, random_state=42)
model.fit(X_train, y_train)

train_accuracy = accuracy_score(y_train, model.predict(X_train))
test_accuracy  = accuracy_score(y_test,  model.predict(X_test))

print(f"Train Accuracy: {train_accuracy:.4f}")
print(f"Test Accuracy:  {test_accuracy:.4f}")

Record the test accuracy before saving. After loading the model later you will verify it produces identical predictions to confirm the save and load worked correctly.

Step 2: Save the Model With joblib

joblib is the recommended tool for saving scikit-learn models. It is optimized for objects that contain large numpy arrays, which is exactly what most trained models are. It is faster than pickle for these objects and produces smaller files:

python

import joblib

joblib.dump(model, 'iris_logistic_model.pkl')
print("Model saved with joblib.")

joblib.dump() takes the object to save as the first argument and the file path as the second. The .pkl extension is conventional for serialized Python objects. The file is created in the current working directory unless you specify a full path.

To save to a specific folder:

python

import os

os.makedirs('models', exist_ok=True)
joblib.dump(model, 'models/iris_logistic_model.pkl')
print("Model saved to models folder.")

os.makedirs() with exist_ok=True creates the folder if it does not already exist and does nothing if it does. This prevents the save from failing because the target directory was not created yet.

Step 3: Load the Model With joblib

Loading is a single line:

python

loaded_model = joblib.load('models/iris_logistic_model.pkl')
print("Model loaded successfully.")
print(type(loaded_model))

The loaded_model object is identical to the original model object. It has all the same fitted parameters, all the same methods, and produces identical predictions. Verify this immediately after loading:

python

original_predictions = model.predict(X_test)
loaded_predictions   = loaded_model.predict(X_test)

predictions_match = np.array_equal(original_predictions, loaded_predictions)
print(f"Predictions match: {predictions_match}")

loaded_accuracy = accuracy_score(y_test, loaded_predictions)
print(f"Loaded model accuracy: {loaded_accuracy:.4f}")

If predictions_match is True and the accuracy matches what you recorded before saving, the model was saved and loaded correctly. This verification step should be part of every model saving workflow, not an optional check.

Step 4: Save and Load With pickle

pickle is Python’s built-in serialization library. It works for any Python object, not just machine learning models. The syntax requires a bit more code than joblib because you manage the file handle manually:

python

import pickle

with open('models/iris_logistic_pickle.pkl', 'wb') as f:
    pickle.dump(model, f)
print("Model saved with pickle.")

with open('models/iris_logistic_pickle.pkl', 'rb') as f:
    pickle_model = pickle.load(f)
print("Model loaded with pickle.")

pickle_accuracy = accuracy_score(y_test, pickle_model.predict(X_test))
print(f"Pickle model accuracy: {pickle_accuracy:.4f}")

The ‘wb’ mode opens the file for writing in binary mode. The ‘rb’ mode opens it for reading in binary mode. Machine learning model files are binary files, not text files, so always use the b flag in both open calls. Forgetting it causes an error that can be confusing if you are not expecting it.

Step 5: Save the Scaler Alongside the Model

This is the step most beginners miss and it causes problems that are difficult to debug. If your model was trained on scaled features you must save the fitted scaler separately and apply it to new data before passing it to the model. Without the scaler the model receives raw unscaled values when you expect scaled ones and the predictions are silently wrong.

python

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled  = scaler.transform(X_test)

scaled_model = LogisticRegression(max_iter=200, random_state=42)
scaled_model.fit(X_train_scaled, y_train)

joblib.dump(scaled_model, 'models/scaled_model.pkl')
joblib.dump(scaler,       'models/scaler.pkl')
print("Model and scaler saved separately.")

loaded_scaled_model = joblib.load('models/scaled_model.pkl')
loaded_scaler       = joblib.load('models/scaler.pkl')

new_data = X_test[:5]
new_data_scaled = loaded_scaler.transform(new_data)
predictions     = loaded_scaled_model.predict(new_data_scaled)
print(f"Predictions on new data: {predictions}")

Two files, one for the model and one for the scaler, both saved and both loaded. Any prediction workflow that uses this model must always apply the scaler first. Saving them as separate files makes this dependency explicit and easy to manage.

Step 6: Save an Entire Pipeline as One Object

A cleaner approach than saving the scaler and model separately is to wrap them together in a sklearn Pipeline and save the entire pipeline as one file. The pipeline applies the scaler automatically as part of every predict call, so nothing can be forgotten or applied in the wrong order:

python

from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', LogisticRegression(max_iter=200, random_state=42))
])

pipeline.fit(X_train, y_train)

pipeline_accuracy = accuracy_score(y_test, pipeline.predict(X_test))
print(f"Pipeline accuracy: {pipeline_accuracy:.4f}")

joblib.dump(pipeline, 'models/iris_pipeline.pkl')
print("Pipeline saved.")

loaded_pipeline = joblib.load('models/iris_pipeline.pkl')

raw_predictions = loaded_pipeline.predict(X_test)
pipeline_loaded_accuracy = accuracy_score(y_test, raw_predictions)
print(f"Loaded pipeline accuracy: {pipeline_loaded_accuracy:.4f}")

Saving the pipeline as one file means you load one file and call predict() on raw unscaled data. The pipeline handles the scaling internally, in the correct order, every time. This is the production-ready pattern that eliminates an entire category of prediction errors.

Step 7: Save Multiple Models and Compare Them

A common workflow in machine learning is training several models, comparing their performance, and saving the best one. Here is how to manage that cleanly:

python

from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC

models_to_train = {
    'logistic_regression': LogisticRegression(max_iter=200, random_state=42),
    'random_forest':       RandomForestClassifier(n_estimators=100, random_state=42),
    'svm':                 SVC(kernel='rbf', random_state=42)
}

results = {}

for name, mdl in models_to_train.items():
    mdl.fit(X_train, y_train)
    accuracy = accuracy_score(y_test, mdl.predict(X_test))
    results[name] = {'model': mdl, 'accuracy': accuracy}
    print(f"{name}: {accuracy:.4f}")

best_name = max(results, key=lambda k: results[k]['accuracy'])
best_model = results[best_name]['model']

print(f"\nBest model: {best_name} ({results[best_name]['accuracy']:.4f})")

joblib.dump(best_model, f'models/best_model_{best_name}.pkl')
print(f"Best model saved as best_model_{best_name}.pkl")

This pattern trains all candidates, selects the winner by accuracy, and saves only the best model with a filename that identifies which algorithm won. Including the algorithm name in the filename means you know what you are loading months later without having to open the file and inspect it.

joblib vs pickle: When to Use Each

Featurejoblibpickle
Best for sklearn modelsYes, optimized for numpy arraysWorks but slower for large models
File size for large modelsSmallerLarger
Speed for large numpy arraysFasterSlower
Built into PythonNo, comes with scikit-learnYes, standard library
Works for any Python objectYesYes
Recommended for ML modelsYesOnly when joblib unavailable
Compression supportYes, with compress parameterNo built-in compression
Parallel processing supportYesNo

For any scikit-learn model, joblib is the right choice. Use pickle only when you need to serialize a Python object that does not contain large numpy arrays, or when you are working in an environment where scikit-learn is not installed.

Common Limitations

Saved models are Python version and library version sensitive. A model saved with scikit-learn 1.2 may not load correctly in an environment running scikit-learn 1.5. The same applies to Python version differences. Always record the exact library versions used when training a model and document them alongside the saved file. A requirements.txt file in the same folder as the model file is a practical way to handle this.

Saved model files are not secure to load from untrusted sources. Both joblib and pickle can execute arbitrary Python code during deserialization. Never load a model file that came from an unknown or untrusted source. This is not a concern for models you trained and saved yourself but it is an important security consideration when model files are shared across teams or downloaded from external sources.

File size grows with model complexity. A simple logistic regression model saves to a file of a few kilobytes. A random forest with a thousand deep trees or a gradient boosting model can produce files of hundreds of megabytes. For very large models, use joblib’s compression option: joblib.dump(model, ‘model.pkl’, compress=3). The compress parameter accepts values from 1 to 9 with higher values producing smaller files at the cost of longer save and load times.

Common Mistakes to Avoid

Saving the model without saving the scaler. If you scaled your training data with StandardScaler or MinMaxScaler, the model expects scaled input at prediction time. Saving only the model and not the scaler means you cannot correctly preprocess new data later. Always save both together, or better yet, wrap them in a Pipeline and save the pipeline as one object.

Not verifying predictions after loading. Loading a model without verifying that it produces the same predictions as the original is an assumption you cannot afford to make in production. Always run a quick check immediately after loading by comparing predictions on a small known dataset to the predictions you recorded before saving.

Overwriting a good model with a worse one. If you retrain and save to the same filename without checking performance first, you may overwrite a well-performing model with a worse one. Include the date, algorithm name, or accuracy in the filename, or keep versioned copies before overwriting. Losing a good model because the filename was reused is a frustrating and avoidable mistake.

Saving inside a notebook without specifying the full path. When you save a file from a Jupyter notebook without a full path, the file goes to whichever directory the notebook server was launched from, which is often not where you expect it. Always use absolute paths or explicitly construct the save path with os.path.join() so you know exactly where the file is being written.

Save and Load Model Cheat Sheet

TaskCode
Save with joblibjoblib.dump(model, ‘model.pkl’)
Load with joblibmodel = joblib.load(‘model.pkl’)
Save with picklepickle.dump(model, open(‘model.pkl’, ‘wb’))
Load with picklemodel = pickle.load(open(‘model.pkl’, ‘rb’))
Save with compressionjoblib.dump(model, ‘model.pkl’, compress=3)
Save scaler separatelyjoblib.dump(scaler, ‘scaler.pkl’)
Save pipeline as one filejoblib.dump(pipeline, ‘pipeline.pkl’)
Create save directoryos.makedirs(‘models’, exist_ok=True)
Verify predictions matchnp.array_equal(original_preds, loaded_preds)
Check file sizeos.path.getsize(‘model.pkl’)

Saving and loading machine learning models is not a finishing step you add at the end of a project. It is a fundamental part of the workflow that belongs in every training script from the beginning.

The pattern is always the same. Train the model. Record the performance metrics. Save the model and everything it depends on, including the scaler, to clearly named files. Verify the loaded model produces identical predictions before trusting it for anything downstream.

Use joblib for all scikit-learn models. Wrap your scaler and model together in a Pipeline and save the pipeline as one file whenever possible. Include the algorithm name, date, or version number in your filenames so you always know what you are loading. Document the library versions used at training time alongside the saved file so you can recreate the environment if loading fails.

The two minutes it takes to save a model correctly at the end of a training session saves hours of retraining and debugging later. Make it a habit on every project.

FAQs

What is the best way to save a machine learning model in Python?

Use joblib.dump() for scikit-learn models. It is faster than pickle for objects containing large numpy arrays, produces smaller files, and is the approach recommended by the scikit-learn documentation. Save the model with joblib.dump(model, ‘model.pkl’) and load it with joblib.load(‘model.pkl’).

What is the difference between joblib and pickle for saving models?

Both serialize Python objects to disk but joblib is optimized for objects containing large numpy arrays, which is what most trained machine learning models are. joblib produces smaller files and loads faster than pickle for these objects. pickle is Python’s built-in serializer and works for any object but is slower and produces larger files for typical ML models.

Do I need to save the scaler when saving a machine learning model?

Yes, if the model was trained on scaled features. The scaler must be applied to any new data before passing it to the model. Save the fitted scaler separately alongside the model, or better yet, wrap both in a sklearn Pipeline and save the pipeline as one file. Loading the pipeline and calling predict() on raw data applies the scaler automatically without any extra steps.

Can I save an entire sklearn Pipeline with joblib?

Yes. A Pipeline object serializes completely with joblib just like any other sklearn object. Save it with joblib.dump(pipeline, ‘pipeline.pkl’) and load it with joblib.load(‘pipeline.pkl’). The loaded pipeline retains all fitted transformers and the trained model, so calling predict() on raw input data applies every preprocessing step automatically in the correct order.

Why might a saved model fail to load correctly?

The most common cause is a version mismatch between the scikit-learn or Python version used to save the model and the version used to load it. Always document the exact library versions at training time alongside the saved model file. Other causes include file corruption during transfer, incomplete saves due to disk space issues, or loading a file that was not saved with joblib or pickle in the first place.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top