Skip to content
advanced Phase 18 · Production Capstone and ML Interview Preparation

Portfolio and Reproducibility

Publish repositories that reviewers can run and evaluate quickly.

50m
0 problems
Topic Progress 0%

Ten Minutes, One Command: The First Screen a Hiring Manager Sees

A reviewer opens forty repositories in an afternoon. Yours gets the first screen — the README above the fold — and roughly ten minutes of attention. Everything in that screen exists to answer one question: can I trust this person's numbers, and can I run them myself? Structure the page for that question and nothing else.

README section Question it answers The common failure
One-paragraph summary What problem, what approach, what result? "advanced LSTM project" with no number anywhere
Result table Headline metric vs baseline, on which split? a lone 0.93 with nothing to compare against
Quickstart What do I type first? notebook-first, manual downloads, "adjust paths"
Data Source, license, fetch instructions, checksum? "contact me for data"
Repository layout Where does the code actually live? thirty flat files at the root
Trade-offs What did you give up, and why? absent entirely
Reproduce Expected output and tolerance? absent entirely

The quickstart is the load-bearing section. Lay the repository out so the commands map to the pipeline stages:

amz-churn/
├── configs/v1.yaml          # full experiment config, committed
├── data/                    # empty; fetch_data.py fills it and verifies hashes
├── src/{ingest,train,evaluate,serve}.py
├── reports/expected_metrics.json   # what a rerun should print
├── requirements.lock              # pinned, not floating
└── README.md
make reproduce
# equivalent to:
python scripts/fetch_data.py --verify-checksum
python -m src.train --config configs/v1.yaml --seed 42
python -m src.evaluate --expect reports/expected_metrics.json --tolerance 0.005

The first command a reviewer runs must not require a decision — no version choices, no manual downloads, no editing paths in a notebook. Every decision you push to the reader is a reader who leaves. Data instructions deserve the same rigor: a fetch script that validates a checksum, a note on licensing and size, and a fallback sample so someone on a slow connection can still run the smoke config.

Test the screen the way a reviewer experiences it: open the repository in a fresh session — or hand it to a friend who owes you nothing — start a timer, and note where the ten minutes go. Every pause spent hunting for the run command, guessing at data provenance, or waiting on an unpinned install is a design defect, not a reader problem. Two cheap fixes cover most of them: a make help target listing the four commands that matter, and a committed reports/ directory holding your outputs (result table, slice report, one training curve) so a reader can inspect the evidence before running anything. Where the data cannot be redistributed, say so plainly and offer the alternative — a public mirror, a synthetic generator committed as a script, or the exact query against an open dataset — because "available on request" converts an interested reviewer into someone who closes the tab.

Two paragraphs then do more credibility work than any badge wall. First, the result: model versus baselines, per-slice, on a time-based split. Second, the trade-offs: what you sacrificed and what it bought you. A reviewer who reaches "I chose precision over recall because the review queue holds 4,000 items a day, so recall 0.80 at precision 0.60 beats recall 0.95 at precision 0.20" already understands how you think — that is the whole screening purpose of the page, and it is precisely the reasoning you will be asked to defend live in ML Interview Question Patterns.

The Rerun Test: Seeds, Pins, Data Snapshots, and the Tolerance You Declare

You reported 0.871. The reviewer reruns and gets 0.864. Is that a contradiction? Without a declared tolerance, you have no idea — and neither do they. Reproducibility is not bitwise equality; it is a published expectation with a stated variance, plus the controls that keep the variance small.

Work through the sources of surprise before someone else does:

Source of nondeterminism Symptom Fix
Unseeded shuffling different batches every run seed Python, NumPy, and the framework at startup
Nondeterministic GPU kernels jitter on GPU, stable on CPU deterministic ops flags, or declare the tolerance
Floating-point reduction order small drift with thread count fix thread counts; accept ±0.005
Unpinned libraries behavior changes after an upgrade lockfile with exact versions
Upstream data file changed wholesale score shift frozen snapshot + checksum
Hyperparameter search rerun a different winning config commit the chosen config; reproduction never re-searches

Seed everything in one place, and let the framework fail loudly rather than silently fall back to fast nondeterministic kernels:

import os, random, numpy as np, torch

def seed_everything(seed: int = 42):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    torch.use_deterministic_algorithms(True)  # raises instead of quietly varying

Pin the environment with a generated lockfile rather than a hand-edited requirements file, and record the command that produced it so pins change deliberately instead of drifting:

pip-compile pyproject.toml -o requirements.lock   # or: uv lock && uv export --frozen > requirements.lock
pip install -r requirements.lock                  # installs the exact resolved graph

Configuration is part of the contract as much as code: commit configs/v1.yaml holding every hyperparameter, the seed, and the data hash it was trained against, so a rerun that differs can be diffed field by field — code, config, or data, the same three-way localization an interviewer will ask you to perform on a production regression.

Then make the rerun an automated check, not a favor you perform manually. A CI job on every push proves the claim continuously:

name: reproduce
on: [push]
jobs:
  rerun:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.lock
      - run: python scripts/fetch_data.py --verify-checksum
      - run: python -m src.train --config configs/smoke.yaml --seed 42
      - run: python -m src.evaluate --expect reports/expected_metrics.json --tolerance 0.005

Two details decide whether that job is useful or theater. First, the smoke config: two epochs on a 5% data slice that finishes in minutes, which proves the pipeline executes end to end — while the full run stays a documented local command. State the distinction in the README, or a reviewer will judge your project by a run it never claimed to reproduce. Second, the failure message is a user interface: print expected and actual metrics side by side with the tolerance, so whoever opens the log knows immediately whether the pipeline broke or the tolerance is too tight.

Finally, report honestly. A single number is a claim; a mean over three seeds with a declared tolerance is a result. Write 0.871 ± 0.004 across 3 seeds in the README, commit the expected-metrics file the CI compares against, and a reviewer whose rerun prints 0.868 concludes the pipeline is sound instead of concluding you got lucky. That sentence — mean, spread, seed count, tolerance — is the cheapest credibility you will ever buy, and it is the same contract the capstone's rerun test enforces at ship time in End-to-End Capstone Project.

Baselines, Error Slices, and the Trade-off Paragraph That Beats a Framework List

Anyone can list tools. What separates a portfolio that survives technical review from one that does not is evidence of comparison: against baselines, across slices, and against the alternatives you rejected. Rigor is a table of comparisons, not a list of tools.

Start with the baseline table — same split, same code path, same evaluation script:

Model PR-AUC Params What it proves
Majority class 0.003 0 the base rate itself
Domain heuristic rule 0.18 — what an analyst does today
Logistic regression, 2 features 0.31 ~3 the problem is learnable at all
Gradient boosting, all features 0.63 ~400k your contribution: +0.32 over the simple model

Each row answers a question a reviewer is silently asking. Skipping the logistic regression row is the most common gap: without it, nobody can tell whether your 0.63 came from gradient boosting or from the features, and your effort is unattributable. Include the domain heuristic even when it embarrasses you — beating a rule your grandmother could apply is the most legible kind of evidence.

Then show where the model fails, because error analysis is the interview answer your repo proves you can do. Two moves suffice:

# slice-wise metrics: same metric, segmented — not a new metric per segment
slices = df.groupby(["device_tier", "tenure_band"]).apply(
    lambda g: pd.Series({"pr_auc": pr_auc(g.y, g.p), "n": len(g)})
)
# then read the 100 worst errors by hand and cluster them by cause

Publish the resulting table — it is shorter than it looks:

Slice PR-AUC n Read
desktop 0.71 180k strong
mobile, mid-tier 0.62 140k acceptable
mobile, low-end 0.44 60k investigate: timeout label noise

A reviewer who sees 0.71 versus 0.44 next to a paragraph noting timeout-related label noise on low-memory devices learns more about your engineering judgment than 500 more notebook cells would. Hand-clustering a hundred errors usually reveals the cause is label noise or a missing feature rather than model capacity — a finding worth committing to reports/error_analysis.md so it survives the interview.

Close with the trade-off paragraph — three or four sentences, explicit about what you gave up:

I optimized precision@4,000 over recall because the review team cannot staff more queue, accepting ~600 missed fraud cases a day as the cost of keeping analyst idle time near zero. Inference runs at 38 ms p99 on CPU, which rules out the larger ensemble that gained +0.02 PR-AUC; the latency budget mattered more than the leaderboard.

That paragraph is the payload. It demonstrates that you know metrics are decision rules, that constraints drive architecture, and that every choice has a price — the exact reasoning you rehearsed in ML Interview Question Patterns. What to delete while you edit: badge walls, twenty-framework stack lists, unedited notebooks with dead kernel outputs, and screenshots of loss curves with no comparison drawn. A page of tooling logos next to a single unexplained number reads as compensation. A short, specific trade-off section reads as depth — and depth is what the reviewer came to find.

One last discipline: replace each deletion with a sentence of reasoning. The reviewer's question never changes — compared to what, and can I run it? — so every element on the page either answers that question or competes with the ones that do. Read your repository once with that sentence as the only lens, and cut whatever fails to answer it.

Quiz

1. A reviewer wants to verify your claimed PR-AUC of 0.63. Which repository property lets them do it fastest?

Question 1 options

2. Training prints 0.871 on your laptop and 0.864 on a teammate's identically configured GPU machine. What is the most likely explanation?

Question 2 options

3. What does "0.871 ± 0.004 across 3 seeds" communicate that "0.871" does not?

Question 3 options

4. Which addition most raises a reviewer's confidence in your results section?

Question 4 options

Flashcards

Question

What must the first screen of a portfolio README contain?

Answer

A one-paragraph problem-and-result summary, a result table with baselines on a named split, a one-command quickstart, data source with license and checksum, repository layout, a trade-offs paragraph, and the expected rerun output with its tolerance — arranged so no decision is pushed onto the reader.

Question

Name the top sources of rerun nondeterminism and their fixes.

Answer

Unseeded shuffling (seed Python/NumPy/framework at startup), nondeterministic GPU kernels (deterministic ops flags or a declared tolerance), floating-point reduction order (fix thread counts), unpinned libraries (exact lockfile), changed upstream data (frozen snapshot + checksum), and rerunning hyperparameter search (commit the chosen config instead of searching again).

Question

What is the rerun contract, and how is it enforced?

Answer

Report metrics as a mean with spread over at least three seeds, declare the tolerance in the README, commit the expected-metrics file, and run a CI job that installs pins, fetches checksummed data, trains with a fixed seed, and fails if evaluation drifts outside tolerance. Reproducibility becomes an automated check, not a manual favor.

Question

Why does a portfolio need a logistic regression row in its baseline table?

Answer

Without a simple model on the same split and evaluation script, reviewers cannot attribute your gain: the improvement might come from better features rather than a better model. Baselines turn "my model scores 0.63" into "gradient boosting added +0.32 over a linear model and beat the analyst's heuristic by +0.45" — evidence instead of a claim.

Question

What does the trade-off paragraph prove that a stack list cannot?

Answer

That you treat metrics as decision rules under constraints: which axis you optimized, what it cost, which rejected alternative would have won on one metric and lost on another, and which budget (latency, data, review capacity) overruled it. It shows judgment; a list of tools only shows exposure.

Revision Notes

Key Takeaways

  • 1. The README's job is trust and execution: result vs baselines up top, one command below it, no reader-side decisions anywhere
  • 2. Reproducibility means a declared expectation with variance — `0.871 ± 0.004 across 3 seeds` — not bitwise equality across hardware
  • 3. Nondeterminism has a short list of causes (seeds, kernels, threads, pins, data, re-searched configs); fix what you can and publish the tolerance for the rest
  • 4. Baselines and slice tables attribute your gain; without a logistic regression row, a reviewer cannot tell whether the model or the features did the work
  • 5. A three-sentence trade-off paragraph outperforms a wall of framework badges, because it proves you understand constraints — the exact reasoning interviews reward

Interview Tips

  • • "Walk me through your repository" — narrate the quickstart: one command, pinned environment, checksummed data, metrics compared against committed expectations; narrating the rerun is stronger than describing the architecture
  • • "How reproducible is your work?" — answer with the contract: seeds, lockfile, data hash, config plus git SHA, mean over three seeds, tolerance in the README, CI enforces it; naming the tolerance shows you understand nondeterminism rather than fearing it
  • • "What would you do differently?" — reach for a real trade-off from the write-up: a metric you optimized at the cost of another, a budget that ruled out a better model; concrete sacrifices read as experience, generic hindsight reads as preparation
  • • "Why is your model better than the baseline?" — walk the baseline table row by row (heuristic, logistic regression, your model) on the same split, then name the slice where the gain is smallest; volunteering the weak slice is what senior candidates do

Cheat Sheet

Portfolio and Reproducibility

README skeleton (above the fold):

Section Must show
Summary problem + approach + one result number
Results model vs baselines, per slice, named split
Quickstart one command, zero reader decisions
Data source, license, size, checksum fetch
Layout where each pipeline stage lives
Trade-offs what you gave up and why
Reproduce expected output + tolerance

Rerun contract:

python scripts/fetch_data.py --verify-checksum
python -m src.train --config configs/v1.yaml --seed 42
python -m src.evaluate --expect reports/expected_metrics.json --tolerance 0.005
# report: 0.871 ± 0.004 across 3 seeds

Nondeterminism fixes: seed all RNGs · torch.use_deterministic_algorithms(True) · fixed thread counts · lockfile pins · frozen data snapshot + hash · commit the chosen config, never re-search.

Baseline ladder: majority class → domain heuristic → logistic regression → your model, all on the same split with the same evaluation script.

Edit out: badge walls, framework lists, dead-kernel notebooks, unexplained lone metrics.

Continue Learning