← For Students

Habits & experiments

The engineering habits that keep you productive, and the tooling that keeps your experiments reproducible once you're running more than a couple.

Good habits

The best way to pick up good engineering is to build it into your workflow from day one and let it run automatically.

Start a project so it's reproducible from day one:

shell
uv init my-project && cd my-project
uv add torch torchvision lightning scikit-learn
uv run python train.py     # runs in the project env — no activate needed

Wire up automatic quality checks that run on every commit:

shell
uv add --dev ruff codespell pre-commit
uv run pre-commit install          # installs the git hook
uv run ruff check . && uv run ruff format .

…with a .pre-commit-config.yaml in your repo root:

.pre-commit-config.yaml
# then run: uv run pre-commit autoupdate  (pins the latest versions)
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.9
    hooks:
      - id: ruff
      - id: ruff-format
  - repo: https://github.com/codespell-project/codespell
    rev: v2.3.0
    hooks:
      - id: codespell

A starter .gitignore — never commit data or model weights:

.gitignore
__pycache__/
.venv/
data/
outputs/
*.ckpt
*.pth
.env
.DS_Store
Running & tracking experiments

Once you're running more than a couple of experiments, these tools keep your work reproducible and your results organized. Don't front-load them on day one — reach for them when scattered scripts and logs start to hurt.

shell
uv add hydra-zen "mushin-py[eval]" xarray   # [eval] adds compare, metric batteries & LLM eval

hydra-zen — capture a config you can override and reproduce later:

python
from hydra_zen import builds, instantiate

OptimCfg = builds(dict, lr=1e-3, weight_decay=0.0)
cfg = OptimCfg(lr=3e-4)     # override one field
instantiate(cfg)           # -> {'lr': 0.0003, 'weight_decay': 0.0}

xarray — hold results across seeds and settings in one labeled array:

python
import numpy as np, xarray as xr

acc = xr.DataArray(
    np.random.rand(3, 2),
    dims=("seed", "lr"),
    coords={"seed": [0, 1, 2], "lr": [1e-3, 1e-2]},
    name="accuracy",
)
acc.mean("seed")     # average across seeds, per learning rate

mushin end-to-end — decorate a function that trains a Lightning model, sweep it across learning rates and seeds, get the results back as a labeled xarray dataset, and plot straight from it:

python
import lightning as L
import matplotlib.pyplot as plt
import mushin

@mushin.sweep
def lr_sweep(lr, seed):
    L.seed_everything(seed)
    model = MyLitModule(lr=lr)                   # your LightningModule
    trainer = L.Trainer(max_epochs=5, accelerator="auto",
                        logger=False, enable_checkpointing=False)
    trainer.fit(model, datamodule=dm)            # dm: your LightningDataModule
    return dict(accuracy=trainer.callback_metrics["val_acc"].item())

ds = lr_sweep.run(                               # 9 runs -> labeled xarray.Dataset
    lr=mushin.multirun([1e-3, 1e-2, 1e-1]),
    seed=mushin.multirun([0, 1, 2]),
)                                               # dims (lr, seed)
mean = ds["accuracy"].mean("seed")              # average over seeds
std  = ds["accuracy"].std("seed")               # spread across seeds

# plot straight from the xarray: val accuracy vs. learning rate, mean ± spread
plt.errorbar(mean["lr"], mean, yerr=std, marker="o", capsize=4)
plt.xscale("log"); plt.xlabel("learning rate"); plt.ylabel("val accuracy")
plt.tight_layout(); plt.savefig("acc_vs_lr.png")

Need the full toolkit — failure handling, provenance, custom analysis? Drop to lr_sweep.workflow, or subclass MultiRunMetricsWorkflow directly.