Skip to content
Laviyx Learn

Try

Machine Learning course menu

Saving and Loading Machine Learning Models in Python

Save trained models so they can be used later: joblib for scikit-learn pipelines, saving PyTorch state_dicts, recording versions and metadata, the security risk of loading untrusted pickle files, and portable formats like ONNX.

Intermediate7 min readUpdated
On this page

Training can take minutes or days; predictions need to be available instantly, in a different program, often on a different machine. So you train once, save the fitted model to a file, and load it wherever predictions are needed.

scikit-learn: joblib#

Save the whole pipeline, preprocessing included, so the loaded model accepts raw input exactly as in training:

Python
import joblib
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_breast_cancer(return_X_y=True)
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)).fit(X, y)
joblib.dump(model, "cancer_model.joblib")

loaded = joblib.load("cancer_model.joblib")
print(type(loaded).__name__, [name for name, _ in loaded.steps])
print("same predictions:", (loaded.predict(X) == model.predict(X)).all())
Output
Pipeline ['standardscaler', 'logisticregression']
same predictions: True

joblib is scikit-learn's recommended tool; it stores large NumPy arrays efficiently. The standard library's pickle works the same way (pickle.dump(model, file)).

Save metadata with the model#

Months later you will need to know what a file contains. Store details alongside it:

Python
import json
from datetime import date

import joblib
import sklearn
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
cv_accuracy = cross_val_score(model, data.data, data.target, cv=5).mean()
model.fit(data.data, data.target)

joblib.dump(model, "cancer_model_v1.joblib")
metadata = {
    "model_file": "cancer_model_v1.joblib",
    "trained_on": str(date(2026, 10, 24)),
    "sklearn_version": sklearn.__version__,
    "features": list(data.feature_names[:3]) + ["..."],
    "n_features": len(data.feature_names),
    "cv_accuracy": round(cv_accuracy, 3),
}
with open("cancer_model_v1.json", "w") as f:
    json.dump(metadata, f, indent=2)
print(json.dumps(metadata, indent=2))
Output
{
  "model_file": "cancer_model_v1.joblib",
  "trained_on": "2026-10-24",
  "sklearn_version": "1.7.2",
  "features": [
    "mean radius",
    "mean texture",
    "mean perimeter",
    "..."
  ],
  "n_features": 30,
  "cv_accuracy": 0.981
}

Recording the library version matters: a model saved with one scikit-learn version is not guaranteed to load in another. Pin versions in production (for example in requirements.txt) and retrain when upgrading.

Security: never load untrusted files#

For sharing models publicly, safer formats exist: ONNX (a portable model format runnable from many languages) and safetensors for neural network weights store numbers only, not code.

PyTorch: save the state_dict#

For PyTorch, the recommended approach is to save the model's learned weights (its state_dict) and rebuild the architecture in code when loading:

Python
import torch
from torch import nn

def build_model():
    return nn.Sequential(nn.Linear(4, 16), nn.ReLU(), nn.Linear(16, 3))

torch.manual_seed(0)
model = build_model()
torch.save(model.state_dict(), "iris_net.pt")

restored = build_model()
restored.load_state_dict(torch.load("iris_net.pt", weights_only=True))
restored.eval()

x = torch.randn(2, 4)
with torch.no_grad():
    print("same outputs:", torch.equal(model(x), restored(x)))
print("saved tensors:", list(restored.state_dict().keys()))
Output
same outputs: True
saved tensors: ['0.weight', '0.bias', '2.weight', '2.bias']

weights_only=True loads only tensors, not arbitrary objects, which makes loading safer. Remember to call eval() before predicting.

Common mistakes#

Practice#

Practice exercise

Train a RandomForestClassifier(n_estimators=50, random_state=0) on the wine dataset, save it with joblib, load it back, and print whether the loaded model's predictions on the first 10 rows match the original's.

Show hint

joblib.dump(model, "wine.joblib"), then joblib.load("wine.joblib"), and compare predict(X[:10]) from both.

Show solution
Python
import joblib
from sklearn.datasets import load_wine
from sklearn.ensemble import RandomForestClassifier

X, y = load_wine(return_X_y=True)
model = RandomForestClassifier(n_estimators=50, random_state=0).fit(X, y)
joblib.dump(model, "wine.joblib")
loaded = joblib.load("wine.joblib")
print(loaded.predict(X[:10]).tolist() == model.predict(X[:10]).tolist())
Output
True

Key takeaway#

Next: Serving a Model with an API.

Finished this lesson?