MLOpsThe MLOps lifecycle · MLflow 3.17 · DVC 3.67 · FastAPI · Docker · GitHub Actions · Evidently · Python 3.11+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
50 small wins to finish your pathNext lesson →

Logging a model with MLflow

Logging a model stores the trained estimator itself inside the run, so the scores in the record can be reproduced by loading the exact object that earned them.

Last updated: 19 Oct, 2026 · MLOps

The Logging params and metrics lesson recorded the configuration and the numbers. The model was still a loose file beside the code. The video logs the model into the run as well, which is the step that makes a run complete: settings, scores and the artifact they belong to. One call does it, and on this release that call needs an argument the videos do not have.

Pointing MLflow at a local store

The same two required lines open every tracked run: the opt-in MLflow 3.17 wants before it will use a file store, and the tracking URI that names the folder.

python
import os, shutil

os.environ["MLFLOW_ALLOW_FILE_STORE"] = "true"   # 3.17 refuses a local file store without this
shutil.rmtree("mlruns", ignore_errors=True)      # each lesson starts from an empty store
os.makedirs("mlruns/.trash", exist_ok=True)      # MLflow keeps deleted runs in .trash

import mlflow
mlflow.set_tracking_uri("file:./mlruns")
mlflow.set_experiment("student-performance")
One flavour per framework: mlflow.sklearn, mlflow.keras, mlflow.pytorch · from the Complete End to End Deep Learning Project With MLFLOW,DVC And Deployment · 2:48:50 to 2:49:54

The clip calls mlflow.keras.log_model(model, "model", registered_model_name=...) because its project is Keras; MLflow 3 deprecates that positional artifact_path in favour of name=, and this page calls mlflow.sklearn, whose default save format is what raises the error below.

Logging the estimator

Syntax: mlflow.sklearn.log_model saves the estimator under a name inside the run and returns an object whose model_uri points at it. The videos pass artifact_path="model". That parameter still exists on MLflow 3.17.1 and warns artifact_path is deprecated. Please use name instead., so the pages here pass name=.

python
# save the estimator inside the open run and keep the handle it hands back
info = mlflow.sklearn.log_model(model, name="model")
# info.model_uri is how other code fetches this model later

The error a RandomForest raises

Run that call on the student-performance forest and the model is not saved. MLflow 3 writes sklearn models in the skops format, which refuses to load any type it has not been told to trust, and the node storage every tree model uses is on that untrusted list. The failure is caught here so the two lines worth reading are printed rather than buried in a traceback.

ExampleThe default save format refusing a forest
model, scores = fit(max_depth=12)

# the call as the videos write it, against MLflow 3's default save format
try:
    with mlflow.start_run(run_name="depth-12"):
        mlflow.sklearn.log_model(model, name="model")
except Exception as err:
    print(type(err).__name__, "raised from", type(err.__context__).__name__)
    print(str(err).split("\n")[0])

Reading the failure

  • The error comes from two layers. MLflow raises MlflowException, and the exception it is raised from is skops' own UntrustedTypesFoundException, which is the save format objecting rather than MLflow.
  • The type it names is sklearn.tree._tree.Tree, the node array behind every decision tree in the forest, so every tree model hits this and a linear model does not.
  • The message names the fix, the skops_trusted_types parameter of log_model, which is the list of types you declare safe to load.
  • The run still exists, because start_run opened and closed it; the model is what is missing from it, which is the quiet part of this failure.

Trusting the tree type

Pass the one type the message named. The forest is now saved in the skops format, and loading it back gives an estimator that scores what the original scored, which is the round trip worth proving.

ExampleThe forest logged, loaded back and rescored
# naming the one tree type as trusted lets the skops format save the forest
with mlflow.start_run(run_name="depth-12"):
    info = mlflow.sklearn.log_model(model, name="model",
                                    skops_trusted_types=["sklearn.tree._tree.Tree"])

back = mlflow.sklearn.load_model(info.model_uri)    # read the saved model back out of the store
print("scored before logging:", scores["r2"])
print("reloaded              :", type(back).__name__, round(r2_score(yte, back.predict(B)), 4))

What the round trip showed

  • The saved model is the model that was scored. The r2 before logging and the r2 of the reloaded estimator are the same number, so the record is self-consistent.
  • What came back is a RandomForestRegressor, not a wrapper, which is why predict works on it directly.
  • The model lives in the store, not in your folder. info.model_uri is the handle other code uses to fetch it, and it is what the registry registers.
Watch out. The other way out of the error is serialization_format="cloudpickle", which works and prints a warning worth reading: Saving scikit-learn models in the pickle or cloudpickle format requires exercising caution because these formats rely on Python's object serialization mechanism, which can execute arbitrary code during deserialization. The recommended safe alternative is the 'skops' format. Loading a pickle runs whatever is inside it, so prefer naming the trusted types.

skops against cloudpickle

skops with trusted typescloudpickle
Default on 3.17.1YesNo, you ask for it
Tree modelsNeed the type declared trustedSave with no extra argument
Loading runs code from the fileNoYes, which is the warning
Argumentskops_trusted_types=[...]serialization_format="cloudpickle"

Where logging the model matters

  • A handover, where the person reading the run can load the estimator instead of asking you for the file.
  • A rollback, where last month's model is still in the store with the scores it earned.
  • Serving, where the registry takes the logged model by its URI, which is the next lesson's subject.
Try it yourself
  • Remove skops_trusted_types and confirm you get the untrusted-types error back.
  • Swap the forest for LinearRegression and log it with no trusted types at all.
  • Log with artifact_path="model" instead of name="model" and read the deprecation warning MLflow prints.

This is what real progress feels like.