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.
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")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=.
# 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 laterThe 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.
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])MlflowException raised from UntrustedTypesFoundException The saved sklearn model references untrusted types. If you are sure loading these types is safe, set the 'skops_trusted_types' parameter when calling 'log_model' or 'save_model' to the list of trusted types. Root error: Untrusted types found in the file: ['sklearn.tree._tree.Tree'].
Reading the failure
- The error comes from two layers. MLflow raises
MlflowException, and the exception it is raised from is skops' ownUntrustedTypesFoundException, 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_typesparameter oflog_model, which is the list of types you declare safe to load. - The run still exists, because
start_runopened 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.
# 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))scored before logging: 0.8485 reloaded : RandomForestRegressor 0.8485
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 whypredictworks on it directly. - The model lives in the store, not in your folder.
info.model_uriis the handle other code uses to fetch it, and it is what the registry registers.
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 types | cloudpickle | |
|---|---|---|
| Default on 3.17.1 | Yes | No, you ask for it |
| Tree models | Need the type declared trusted | Save with no extra argument |
| Loading runs code from the file | No | Yes, which is the warning |
| Argument | skops_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.
Related
- Previous: Logging params and metrics
- Next: Comparing runs in the MLflow UI
- Reference: mlflow.sklearn
- Remove
skops_trusted_typesand confirm you get the untrusted-types error back. - Swap the forest for
LinearRegressionand log it with no trusted types at all. - Log with
artifact_path="model"instead ofname="model"and read the deprecation warning MLflow prints.
This is what real progress feels like.