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.
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:
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())Pipeline ['standardscaler', 'logisticregression']
same predictions: Truejoblib 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:
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)){
"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:
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()))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
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())TrueKey takeaway#
Next: Serving a Model with an API.
Finished this lesson?