Experiment Tracking with MLflow and Weights & Biases
Log every run, compare experiments, version models, register artifacts. Never lose a good experiment again.
Three weeks ago you trained a model that got 94% accuracy. Today you cannot reproduce it. You do not remember the learning rate, the data version, or which features you included. Experiment tracking means this never happens.
Every ML project goes through dozens of experiments — different models, different hyperparameters, different feature sets, different data slices. Without tracking, all of this knowledge lives in your head and in notebook filenames like model_final_v3_actually_final.ipynb. When the model degrades in production six months later, you cannot reproduce the best version. When a new team member joins, the entire experiment history is lost.
Experiment tracking tools solve this by automatically recording every run: the hyperparameters, metrics at every epoch, code version, data version, environment, and output artifacts. Two runs can be compared side by side. The best model can be registered and promoted to production with a full audit trail. Every ML team of more than two people needs this.
A chef's recipe book vs cooking from memory. A chef who cooks from memory might produce excellent dishes — but cannot replicate them exactly next week, cannot scale the recipe for 200 people, and cannot hand the recipe to a junior chef. A chef who writes down every recipe with precise measurements can reproduce any dish, compare two versions of the same dish scientifically, and build on past experiments. Experiment tracking is the recipe book for ML.
The discipline of logging experiments also forces clarity of thought. When you must decide what to log before running an experiment, you think more carefully about what you are trying to learn. Untracked experiments are usually under-thought experiments.
Parameters, metrics, artifacts, and tags — the four things every run must record
MLflow — self-hosted experiment tracking with model registry
MLflow is four tools in one: Tracking (log experiments), Projects (reproducible code packaging), Models (standard model format), and Registry (model versioning and promotion). For most teams the Tracking and Registry components are what matter. MLflow runs a local server by default — no cloud account required. For production: run the MLflow server backed by PostgreSQL and S3.
MLflow Model Registry — version, stage, and promote models safely
The Model Registry is where experiments become deployable artifacts. Every registered model has a version number, a stage (Staging or Production), and full metadata including which run produced it. Promotion from Staging to Production requires explicit action — this is the deployment gate. The inference service always loads the Production-stage model by name, never by run ID.
Weights & Biases — richer visualisations and collaboration for deep learning
W&B excels where MLflow is weaker: visualising training curves, logging images and audio, comparing runs interactively in a web UI, and team collaboration. The free tier is generous enough for most individual ML engineers. Setup is one line of code — just call wandb.init() and every subsequent print, metric, or artifact is automatically captured.
Experiment tracking conventions — what to standardise across the team
Every common experiment tracking mistake — explained and fixed
Experiment tracking as CI policy — how it actually gets enforced at scale
On a two-person team, experiment tracking can be a personal habit — remembering to call mlflow.start_run() before a training script. On a team of twenty, running dozens of retraining jobs a week across several models, it has to become policy enforced by the pipeline itself, not a habit any individual engineer maintains. The most common way this happens in practice: training runs are triggered by CI/CD, not by anyone running a script from their laptop, and the CI job itself is what logs the run.
A typical setup looks like this. A pull request that touches model training code or feature logic triggers a CI job that spins up a clean, ephemeral container, runs the training script inside it, and logs every parameter, metric, and artifact to the shared MLflow or W&B server automatically as a side effect of that container running — not because the engineer remembered to. The CI job then posts the resulting run's metrics, and a link to the full run, back onto the pull request itself. A merge gate can even require the new run to match or beat the current production model's metric before the PR is mergeable at all.
This buys two things a habit-based approach cannot. First, reproducibility becomes structural rather than aspirational — because the run happened inside a clean, versioned container image, the environment is captured automatically alongside the code version (the PR's commit hash) and the parameters, without anyone hand-logging requirements.txt. Second, in regulated industries — fintech, healthcare, insurance — this CI-enforced logging is often the actual compliance answer to 'show us exactly how this production model was produced and prove you can reproduce it,' an audit question that an untracked notebook simply cannot answer.
Five things people get wrong about experiment tracking
A metric number by itself is close to useless — 'val_mae was 5.8' tells you nothing actionable unless it is paired with exactly what produced it: the hyperparameters, the model type, the data version, and ideally the code version. The four categories this module opened with — parameters, metrics, artifacts, tags — exist together specifically because metrics alone cannot answer the question that actually matters: what should change to get a better number next time. A tracking setup that logs only metrics has built a scoreboard, not an experiment record.
Tracking and version control solve different problems and neither substitutes for the other. Git records how the training code itself evolved over time — every change, who made it, and why. An experiment tracking run records what happened in one specific execution of that code — which parameters, which metrics, which resulting artifact. A run is only actually reproducible if it also logged which git commit produced it, which is why the standardised wrapper pattern in this module logs git_commit as a tag on every run. Skip that link and a perfectly detailed MLflow run becomes unreproducible the moment the training script changes again.
Matching parameters and metrics is necessary but not sufficient. True reproducibility also depends on the exact data snapshot used (not just 'dataset_version=v3' as a label, but the actual rows, which can drift if the underlying table is mutable), the library versions in the environment (a scikit-learn point release can change a default and shift results slightly), every random seed across NumPy, the framework, and the language's own random module, and sometimes hardware-level non-determinism in GPU operations. Logging only hyperparameters and a final metric captures the easy twenty percent of what reproducibility actually requires.
For a team past a couple of people, the tracking server is often the only shared source of truth for what has already been tried — without it, two engineers routinely burn compute re-running an experiment a teammate already tried and rejected three weeks earlier, because that knowledge lived only in their head or a Slack message nobody can find again. In regulated industries it is stronger than a convenience: being able to show exactly which data, code, and parameters produced a production model on demand is frequently an actual compliance requirement, not an engineering nicety a team can choose to skip under deadline pressure.
A metric is only comparable across runs if the evaluation conditions were actually the same — the same test split, the same preprocessing, the same evaluation window of data. A run that scores better because it was accidentally evaluated on an easier slice of data, an older test set before a distribution shift, or with subtle data leakage in its features will show a higher number on the dashboard while being a genuinely worse model in production. Comparing experiments fairly means checking that the comparison itself is apples to apples before trusting the ranking, not just sorting the experiment table by the metric column and promoting whichever run sits on top.
Experiment tracking — 5 questions interviewers actually ask
More than hyperparameters and a final metric. You need the exact data version or snapshot used (ideally a hash or a pointer to an immutable dataset version, not a mutable table name), the code version — a git commit hash logged as a tag — the full library and environment versions the run executed under, and every random seed involved across NumPy, the deep learning framework, and Python's own random module. Miss the data version and the 'same' training code can silently train on different rows six months later; miss the environment version and a library's changed default can shift results even with identical code and data.
First, confirm they were evaluated under identical conditions — same test split, same preprocessing pipeline, same evaluation window of data — because a metric computed on a different slice is not actually comparable no matter how similar the number looks. Second, standardise what gets logged across the team (the required-params-and-tags pattern this module covers) so every run records dataset_version and feature_set consistently, making it possible to filter to only genuinely comparable runs in the first place. Third, look beyond the single headline metric — cross-validation standard deviation, overfitting gap between train and validation, and inference latency all matter for a real promotion decision, not just whichever run has the lowest MAE on the dashboard.
Because a model is a function of far more than its hyperparameters — it is also a function of exactly which data rows it saw, in what order, under which library versions, and with which random seeds. Two runs with identical logged hyperparameters can produce meaningfully different models if the underlying data table was mutated between them, if a dependency was silently upgraded, or if a seed was never set for one of several independent random number generators a typical training script touches. Genuine reproducibility means treating data version and environment version as first-class logged fields, the same as any hyperparameter, not as background assumptions that are 'probably fine.'
The training job itself runs inside CI, triggered by a pull request or a schedule, inside a pinned container image so the environment is captured by construction rather than logged after the fact. The training script logs to the shared tracking server as a normal part of running, tagged with the triggering commit hash. A comparison step then checks the new run's key metric against the current Production-stage model and can block the merge or the deploy if it does not improve or at least hold steady. The result is that every model that ever reaches production has a CI-generated tracking run behind it automatically, with no reliance on an engineer remembering to log anything by hand.
I would not try to retroactively log the 200 old runs — that effort rarely pays for itself. Instead I would start requiring tracking only for new work going forward, beginning with a lightweight shared wrapper that enforces a small set of required tags and params so adoption has almost no friction on day one. I would pick one active, valuable experiment stream to migrate first as a proof of the workflow, then wire the CI gate in once the team trusts the tooling rather than mandating it everywhere at once. The goal is making tracking the path of least resistance — logging a run should be easier than not logging one — rather than treating it as an audit requirement bolted on top of how the team already works.
You can track every experiment. Next: wrap your model in an API and ship it to production.
Experiment tracking gives you a registered model artifact. Module 71 takes that artifact and deploys it — wrapping the model in a FastAPI REST endpoint, containerising it with Docker, and scaling it with Kubernetes. The full deployment path from a pkl file to a production API serving thousands of requests per minute.
Wrap your model in a FastAPI endpoint, containerise with Docker, scale with Kubernetes. Full working deployment of the DoorDash delivery model.
🎯 Key Takeaways
- ✓Experiment tracking automatically records every run: parameters (inputs — hyperparameters, data version, feature set), metrics (outputs — MAE, AUC, training time), artifacts (files — model.pkl, plots, confusion matrices), and tags (labels — team, purpose, ticket). These four categories together make any experiment exactly reproducible.
- ✓MLflow is four tools: Tracking (log runs), Projects (reproducible packaging), Models (standard format), Registry (versioning and promotion). The Tracking and Registry components are what most teams need. Self-host with a PostgreSQL backend and S3 artifact store for production. Free and open source.
- ✓The Model Registry has four stages: None (freshly registered), Staging (under review), Production (serving live traffic), Archived (superseded). The inference service always loads by name and stage — never by run_id. Promotion from Staging to Production is an explicit gate that creates an audit trail.
- ✓W&B excels for deep learning: richer learning curve charts, first-class image/audio logging, built-in hyperparameter sweep agent (Bayesian optimisation across N runs), team reports, and alerts. The free tier covers most individual engineers. One line to start: wandb.init(project="...", config={...}).
- ✓Standardise experiment logging across the team with a shared wrapper class that validates required params and tags before a run starts. Required at minimum: model_type, dataset_version, feature_set, team, purpose. Add git_commit and run_by automatically. Rejected runs cannot pollute the tracking server with unidentifiable experiments.
- ✓Four common failures: runs look identical (enforce naming convention and required tags), artifact store fills up (use S3, set retention policy, gate log_model() on quality threshold), W&B runs stuck as crashed (use context manager or try/finally for wandb.finish()), cannot reproduce (log and set all random seeds — NumPy, PyTorch, Python random, and CUDA each independently).
Discussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.