The Problem

Teams that want to turn a Jupyter prototype into a reproducible, production‑grade ML service often lack a single, opinionated code base that demonstrates end‑to‑end data loading, training, tuning, evaluation, and serving while also exposing a CI pipeline and Kubernetes deployment. Maintaining consistency across notebooks, Python modules, and YAML manifests is error‑prone without a clear wiring diagram.

What This Does

The repository bundles a minimal ML product line in the madewithml/ package. Core responsibilities are split across a handful of files:

  • madewithml/data.py – loads CSV datasets, performs text cleaning (clean_text) and stratified splitting.
  • madewithml/train.py & madewithml/tune.py – implement a training loop (train_model) and hyper‑parameter sweep (tune_models).
  • madewithml/predict.py – wraps a PyTorch model with predict_proba, decode, and format_prob.
  • madewithml/serve.py – provides the entry points _evaluate and _predict for a FastAPI‑style service.

Documentation lives under docs/ and example notebooks in notebooks/. CI is defined in .github/workflows/ and deployment manifests for a Kubernetes cluster are in deploy/.

How It Is Wired

Execution starts in madewithml/serve.py:

  1. _evaluate (line 49) calls evaluate() (from madewithml/evaluate.py).
  2. evaluate() pulls a preprocessor via get_preprocessor(), transforms data (transform()), and finally writes results with save_dict() (calls os.makedirs → filesystem).

The other entry point, _predict (line 55), follows:

  1. Calls madewithml/predict.__call__, which invokes predict_proba.
  2. predict_proba routes through collate_fnpad_array, then through the model’s forward (in madewithml/models.py) and finally formats probabilities with format_prob.

The internal call graph shows 25 modules, 28 import edges and no circular dependencies. The most connected hub is madewithml/__init__ (imported by 11 other modules). Functions with the widest blast radius include transform (called from 7 places) and from_checkpoint (5 places).

External interactions are limited to:

  • File I/O (save_dict, load_dict) – 2 functions.
  • One outbound network call (presumably a model‑registry fetch).
  • One external command execution (via subprocess in the deployment scripts).

No database writes are present in the core library; the only persistence is filesystem‑based checkpoint storage.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/Made-With-ML
cd Made-With-ML

# Install dependencies (pyproject.toml defines runtime, requirements.txt pins exact versions)
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt   # or: pip install .

# Run the unit test suite (CI currently lacks a test step, but tests exist)
pytest tests/code

# Start the service locally (entry point)
python -m madewithml.serve   # invokes _predict/_evaluate via the server code

Configuration lives in madewithml/config.py; adjust paths or model hyper‑parameters there before training. Deploy to Kubernetes with the manifests in deploy/ (e.g., kubectl apply -f deploy/services/serve_model.yaml).

Real‑World Use

A data‑science team can iterate on a text classification model by:

from madewithml.train import train_model
from madewithml.tune import tune_models

# Train a baseline
train_model(num_epochs=5, batch_size=32)

# Run a hyper‑parameter sweep
tune_models(search_space={"lr": [1e-4, 1e-3], "batch_size": [16, 32]})

After validation, the same madewithml/serve.py module is packaged into a Docker image and deployed with the provided Helm‑style YAML, ensuring the production service uses identical code to the notebook experiments.

Code Health & Issues

  • Critical.github/workflows/serve.yaml grants permissions: write-all.
  • High – GitHub Actions are not pinned to commit SHAs; no lockfile beside pyproject.toml; CI workflow never runs tests; missing least‑privilege token declarations.
  • Mediumdeploy/services/serve_model.py opens files without a context manager; duplicated logic in train.py/tune.py; deep nesting and high branching in .github/workflows/json_to_md.py; lack of Dependabot; missing job timeouts; notebook outputs not stripped; checkout persists credentials.
  • Low – No job timeout set in documentation.yaml.

All findings are derived from static analysis; no additional issues were inferred beyond the repository contents.

The Bottom Line

The repo offers a coherent, end‑to‑end ML pipeline with clear module boundaries and reproducible notebooks, making it a solid learning and starter kit. However, production readiness is hampered by several CI and security misconfigurations and a few code‑quality smells that should be addressed before commercial deployment. It is best suited for teams that need a reference implementation and are prepared to tighten the CI/security posture.