Persisted scikit-learn models let trained estimators move from an interactive notebook or training script into a repeatable scoring step. joblib fits trusted Python reuse when the saved object should come back as the original fitted estimator instead of a neutral interchange format.

joblib.dump() stores Python objects through a pickle-based format with efficient handling for large NumPy arrays. Save the fitted object that owns the full prediction path, usually a Pipeline, so preprocessing and estimator parameters stay together.

Only load .joblib artifacts from trusted storage. joblib.load() can execute code through the pickle protocol, and scikit-learn does not support loading models across different scikit-learn versions even when a mismatched artifact appears to load.

Steps to save and load a scikit-learn model with joblib:

  1. Create a smoke-test script that trains a fitted pipeline, saves it, reloads it, and compares one prediction.
    persist_model_joblib.py
    from pathlib import Path
     
    import joblib
    import sklearn
    from joblib import __version__ as joblib_version
    from sklearn.datasets import load_iris
    from sklearn.linear_model import LogisticRegression
    from sklearn.pipeline import make_pipeline
    from sklearn.preprocessing import StandardScaler
     
     
    artifact = Path("iris-classifier.joblib")
     
    iris = load_iris()
    model = make_pipeline(
        StandardScaler(),
        LogisticRegression(max_iter=200),
    )
    model.fit(iris.data, iris.target)
     
    sample = iris.data[[0]]
    trained_prediction = model.predict(sample)
     
    joblib.dump(model, artifact)
    loaded_model = joblib.load(artifact)
    loaded_prediction = loaded_model.predict(sample)
     
    print(f"saved_artifact: {artifact}")
    print(f"artifact_exists: {artifact.exists()}")
    print(f"trained_prediction: {iris.target_names[trained_prediction[0]]}")
    print(f"loaded_prediction: {iris.target_names[loaded_prediction[0]]}")
    print(f"prediction_match: {bool((trained_prediction == loaded_prediction).all())}")
    print(f"sklearn_version: {sklearn.__version__}")
    print(f"joblib_version: {joblib_version}")

    The fitted Pipeline keeps StandardScaler and LogisticRegression together, so the loaded object receives raw feature rows in the same shape as the training code.

  2. Run the smoke-test script in the Python environment that has scikit-learn and joblib installed.
    $ python persist_model_joblib.py
    saved_artifact: iris-classifier.joblib
    artifact_exists: True
    trained_prediction: setosa
    loaded_prediction: setosa
    prediction_match: True
    sklearn_version: 1.9.0
    joblib_version: 1.5.3

    Never point joblib.load() at an artifact from an untrusted source. Use skops.io when the file needs type inspection before loading, or ONNX when the scoring environment should not load a Python object.

  3. Check the two proof fields in the output.
    artifact_exists: True
    prediction_match: True

    artifact_exists confirms that joblib.dump() wrote the artifact, and prediction_match confirms that the reloaded estimator predicts the same class for the sample row.

  4. Keep the artifact on the same dependency stack that trained it.

    Use matching Python, scikit-learn, NumPy, SciPy, and joblib versions for training and scoring. Store those versions beside the artifact in project metadata, a lock file, or the deployment image.

  5. Remove the smoke-test files after adapting the pattern.
    $ rm persist_model_joblib.py iris-classifier.joblib