2 · Reproducibility & project structure
If you can't rebuild the exact same model tomorrow, you don't really have a model. A disciplined project layout, pinned dependencies, fixed random seeds, and a single serialized artifact are what make ML reproducible.
Reproducibility means anyone (including future-you) can recreate the exact same model from scratch — achieved with a clean project layout, PINNED dependency versions, FIXED random seeds, and a single serialized artifact (`joblib`) that travels with its own preprocessing.
Without this:
Without reproducibility, a model that worked last month can't be rebuilt — a dependency silently upgraded, a seed was never set, the script lived only in a notebook — and you can never explain or fix what production is doing.
In a notebook, "it works" often means "it worked once, on my machine, in this exact session". Production demands more: anyone must be able to rebuild the identical model. Four habits get you there.
1. A disciplined project layout. Code, data, and outputs live in separate, predictable folders instead of all jumbled in one notebook:
my-model/
├── src/ # the code: train.py, predict.py, data.py
├── data/ # datasets (versioned with DVC — chapter 5)
├── models/ # the serialized artifact: model.joblib
├── config.yaml # all knobs: paths, hyperparameters, the seed
├── requirements.txt # PINNED dependency versions
└── README.md
Anyone can clone this, read config.yaml, run python src/train.py, and get models/model.joblib — no hidden notebook state.
2. Pinned dependencies. scikit-learn is not one fixed thing — it changes between versions. A model trained under scikit-learn==1.3.0 may not even load under 1.5.0, and numerical results can shift. So you don't write scikit-learn in requirements.txt; you write scikit-learn==1.3.2 with an exact version. "Pinning" freezes every dependency so a rebuild months later uses the same libraries, not whatever was newest that day.
3. Fixed random seeds. Train/test splits, weight initialization, and model internals all use randomness. Set the seed (np.random.seed(42), random_state=42) and the "random" choices become deterministic and repeatable — the same split, the same model, every run. Without a seed, two runs of the identical script produce two different models.
4. One serialized artifact. As you proved in bc-py-59, joblib.dump of the whole fitted Pipeline saves the preprocessing (scaler means, encoder categories) together with the model. That single file is the deployable deliverable — reload it and it predicts on raw rows by itself, with no manual preprocessing to get wrong. The runnable cell below proves this artifact round-trips end-to-end.
A `config.yaml` centralizing the seed, data paths, hyperparameters, and output path — every knob that changes the result, in one version-controlled file.
Python (in browser)
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
Why pin exact dependency versions (`scikit-learn==1.3.2`) instead of just listing the package name?
- A reproducible project separates code (`src/`), data (`data/`), and artifacts (`models/`), with all knobs in `config.yaml` and exact versions in `requirements.txt`.
- Pinned dependency versions + a fixed random seed make a rebuild deterministic: the same libraries and the same randomness produce the same model every time.
- `joblib.dump` of the whole fitted Pipeline is the single deployable artifact; reloaded, it predicts on raw rows by itself and matches the original exactly.
Every serious ML repo has this skeleton — `src/`, `config.yaml`, a pinned `requirements.txt`, a saved artifact — because reproducibility is the precondition for everything else in this track (Docker, CI/CD, deployment).
If you remove it: You'd ship a model nobody (not even you) can rebuild — when production misbehaves you couldn't recreate the exact model to debug it, the worst place to be in ML operations.