Writing ML Code Someone Else Can Run
Writing modular, reproducible machine learning code bases with configuration management, clean functions, and unit test coverage.
Moving Beyond Jupyter Notebooks
Jupyter notebooks are useful for early data exploration and quick plotting.
However, deploying raw Jupyter notebooks into production creates serious engineering problems:
- Hidden Out of Order State: Running cells out of sequential order creates hidden state that cannot be reproduced.
- Global Variable Scope Leaks: Variables defined in one cell accidentally pollute global scope in later cells.
- Difficult Code Reviews and Testing: Git diffs on JSON notebook files are unreadable, and running automated unit tests on
.ipynbfiles is cumbersome.
Messy Notebook: Global state pollution ──► Hardcoded paths ──► Cannot be unit tested!
Modular Codebase: Pure Python functions ──► External YAML configs ──► Automated pytest suite!
4 Principles of Production ML Code
┌──────────────────────────┬──────────────────────────┬──────────────────────────┬──────────────────────────┐
│ 1. MODULARIZATION │ 2. CONFIG MANAGEMENT │ 3. PURE FUNCTIONS │ 4. DETERMINISTIC SEEDS │
├──────────────────────────┼──────────────────────────┼──────────────────────────┼──────────────────────────┤
│ Refactor notebooks into │ Store paths, learning │ Write functions that │ Set fixed random seeds │
│ modular Python packages │ rates, and parameters │ depend exclusively on │ for Python, NumPy, and │
│ (src/data, src/models). │ in YAML or Hydra files. │ explicit arguments. │ PyTorch. │
└──────────────────────────┴──────────────────────────┴──────────────────────────┘
1. Modular Directory Structure
Structure your codebase like a professional Python software package:
my_ml_project/
├── configs/
│ └── train_config.yaml
├── src/
│ ├── data/
│ │ └── make_dataset.py
│ ├── features/
│ │ └── build_features.py
│ └── models/
│ └── train_model.py
├── tests/
│ └── test_features.py
└── requirements.txt
2. Parameter Configuration Management
Never hardcode file paths or hyperparameters inside Python scripts:
# train_config.yaml
data:
raw_path: "data/raw/housing.csv"
processed_path: "data/processed/housing_features.parquet"
model:
learning_rate: 0.001
batch_size: 64
random_seed: 42
Parse configuration files cleanly using PyYAML or Hydra.
3. Pure Functions and Unit Testing
Write pure, single-purpose functions that take inputs and return outputs without mutating global variables:
# src/features/build_features.py
def scale_income_feature(df, max_income=100000.0):
"""Scales income column linearly to [0, 1] range."""
return (df['income'] / max_income).clip(0.0, 1.0)
# tests/test_features.py
def test_scale_income_feature():
df = pd.DataFrame({'income': [50000.0, 150000.0, -10.0]})
result = scale_income_feature(df, max_income=100000.0)
assert result.tolist() == [0.5, 1.0, 0.0]
Say this out loud
Writing testable machine learning code requires moving beyond Jupyter notebooks into modular Python packages. Extracting hyperparameters into external YAML config files separates configuration from execution code. Writing pure functions with explicit inputs and outputs enables automated unit testing with pytest and guarantees code reproducibility.
Followups to expect
- What is Cookiecutter Data Science? A popular standardized project directory structure template for organizing machine learning data science codebases.
- What is Type Hinting in Python ML code? Adding static type annotations (
def predict(X: np.ndarray) -> np.ndarray:) to function signatures, catching type errors early using static type checkers likemypy.
Check yourself
Why are raw Jupyter notebooks unsuitable as production machine learning codebase deployments?