Skip to content

Modernize + verify 5 tutorial notebooks (Longformer/IMDB, node2vec, bikeshare, jigsaw) - #2

Open
jlealtru wants to merge 18 commits into
mainfrom
modernize-notebooks
Open

Modernize + verify 5 tutorial notebooks (Longformer/IMDB, node2vec, bikeshare, jigsaw)#2
jlealtru wants to merge 18 commits into
mainfrom
modernize-notebooks

Conversation

@jlealtru

Copy link
Copy Markdown
Owner

Brings five tutorial notebooks to current library versions and verifies each runs end-to-end on the uv / Python 3.11 environment (Apple-Silicon MPS). Re-executed copies confirm 0 error cells.

Notebooks

Notebook Verified Notes
Longformer with IMDB.ipynb smoke (MPS) SMOKE_TEST toggle; fixed dead /media/... save path
processing_capital_bikeshare_data.ipynb full run mid-2020 trip-CSV schema change; ArcGIS station layer now NAME/LATITUDE/LONGITUDE; emits graph_data_full.csv (117k edges)
node2vec with capitol bikeshare data.ipynb full run gensim Word2Vec(iter=)epochs=; 721-node graph
Multi_label_classification_roberta.ipynb smoke (synthetic) SyntaxError fix; dead checkpoint → roberta-base; num_workers=0; SMOKE_TEST
Multi_label_classification_longformer_tutorial.ipynb smoke (synthetic) same fixes as above

Also

  • Normalized all 5 kernelspecs conda-env-torch-pypython3.
  • Added kaggle to dev deps; gitignore large data dirs + model output dirs.
  • README: documented the SMOKE_TEST workflow and data prerequisites.

Notes for reviewer

  • The two jigsaw notebooks are verified on synthetic, schema-accurate data — a full run needs the real Kaggle Jigsaw Toxic Comment dataset (~/.kaggle/kaggle.json + accepted competition rules).
  • Full (non-smoke) transformer fine-tunes remain multi-day on M1 Pro; optimization is a follow-up.
  • Local helper scripts (scripts/fetch_*.sh), regenerated notebooks/images/*, and unrelated prior-session edits are intentionally excluded from this PR.

🤖 Generated with Claude Code

jlealtru and others added 18 commits May 17, 2026 19:37
The notebooks were authored ~2020 against CUDA-only PyTorch with
unpinned/legacy deps. This commit introduces the reproducible
environment plus the first device-portable notebook; the remaining
notebooks will follow in their own commits.

Infrastructure
- pyproject.toml: every direct dep exact-pinned (torch 2.5.1,
  transformers 4.46.3, datasets 3.1.0, gensim 4.3.3, spacy 3.8.2,
  pecanpy 2.0.9, tensorboard 2.18.0, ...). Dev group has
  jupyterlab/ipykernel/nbconvert/tqdm.
- uv.lock: hash-verified resolved graph. uv sync --frozen refuses
  any dep whose hash doesn't match, so installs are reproducible.
- .python-version = 3.11 (gensim 4.3.3 requires scipy<1.14, which
  only has wheels through 3.11).
- .gitignore excludes the venv, HF cache (data/.hf_cache/),
  nbconvert reruns, Trainer checkpoints, training logs, wandb
  dirs, macOS noise.

Scaffolding (notebooks/_utils.py)
- pick_device(): CUDA -> MPS -> CPU fallback.
- set_seed(): torch/numpy/random seeding.
- On import: sets PYTORCH_ENABLE_MPS_FALLBACK=1 and points HF_HOME /
  HF_DATASETS_CACHE / HF_HUB_CACHE at data/.hf_cache/ so the repo
  is self-contained (no surprise gigabytes under ~/.cache).

One-off patchers (scripts/)
- patch_notebooks.py, patch_training_args.py, patch_node2vec.py:
  used to migrate the legacy notebooks to current library APIs
  (transformers 4.46 renames, gensim 4 keyword renames,
  stellargraph -> pecanpy). Run once each; kept in git so the
  migration is reproducible/extendable.
- resume_roberta_imdb.py: restarts RoBERTa+IMDB from the latest
  results/checkpoint-N/ if a long-running nbconvert is killed.

notebooks/RoBERTA with IMDB.ipynb
- Auto-injected setup cell uses pick_device() instead of the
  original hard-coded 'cuda'.
- Drops the 2020-era /media/data_files/... Linux cache_dir; HF
  cache goes through HF_HOME (set by _utils).
- Tokenization is now truncation-only at .map() time. Padding is
  chosen by the data collator at training time based on device:
    MPS: padding='max_length', max_length=512. Dynamic per-batch
         shapes thrash MPSGraph's per-shape cache on Apple Silicon
         -- observed 95 min wall, 0 optimizer steps, with 100% of
         Python frames in MPSGraphSpecializationCache before this
         fix.
    CUDA/CPU: padding='longest'. CUDA tolerates variable shapes
              cheaply and benefits from skipping pad FLOPs on
              short examples.
- TrainingArguments: bf16=True replaces fp16=True (MPS doesn't
  support fp16 well; CUDA/CPU ignore bf16 gracefully). Bigger
  micro-batch (4 -> 32) with halved gradient_accumulation_steps
  (16 -> 2) for the same effective batch 64 in fewer optimizer
  steps. 4 dataloader workers (was 0). report_to='tensorboard'
  with logging_dir='../results/runs' -- no external wandb account
  needed.
…ikeshare, jigsaw)

Bring five notebooks to current library versions and verify each runs end-to-end
on the uv/Python 3.11 env (Apple Silicon MPS). Re-executed copies confirm 0 error
cells.

- Longformer+IMDB: add SMOKE_TEST toggle (subsample/short-seq/1-epoch); fix dead
  /media/... save path. Smoke-verified on MPS.
- processing_capital_bikeshare: full run. Handle the mid-2020 trip-CSV schema
  change and the ArcGIS station-locations layer's new NAME/LATITUDE/LONGITUDE
  columns (was ADDRESS/ID). Produces graph_data_full.csv (117k edges).
- node2vec: full run (721-node graph). Fix gensim Word2Vec iter -> epochs.
- Multi_label roberta + longformer (jigsaw): fix pre-existing SyntaxErrors in
  from_pretrained(...) (missing commas), repoint dead /media checkpoint to
  roberta-base, DataLoader num_workers=0 (macOS spawn can't pickle notebook
  classes), add SMOKE_TEST toggle. Smoke-verified on synthetic schema-accurate
  data (real Kaggle data still required for a full run).
- Normalize all 5 kernelspecs conda-env-torch-py -> python3.
- Add kaggle to dev deps; gitignore large data dirs + model output dirs; document
  the SMOKE_TEST workflow and data prerequisites in README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- scripts/fetch_bikeshare.sh: download the 24 monthly Capital Bikeshare trip
  zips (2019+2020) from the public S3 bucket into data/capital_bikes/.
- scripts/fetch_jigsaw.sh: download + unzip the Jigsaw Toxic Comment
  competition data into data/jigsaw/ via the kaggle CLI (requires token).
- README: point the data-prerequisites table back at the two scripts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both IMDB transformer notebooks now execute end-to-end (0 error cells) under
SMOKE_TEST=1 on the uv/Py3.11/MPS env.

BigBird text classification.ipynb:
- Add SMOKE_TEST env toggle (subsample + short seq + 1 epoch); full config is the
  default. Threads MAX_LENGTH/N_SAMPLE/NUM_EPOCHS/WARMUP/GRAD_ACCUM through the
  tokenizer, tokenization, training args, collator, and dataset subsample.
- Fix the leftover hard-coded /media/... save path in the save-model cell
  (was OSError: Read-only file system) -> ../results/bigbird_base_imdb.
- Normalize dead conda-env-torch-py kernelspec -> python3.

RoBERTA with IMDB.ipynb:
- Add the same SMOKE_TEST toggle (it had none) so it can be re-smoke-verified
  without the ~24h full fine-tune. Normalize kernelspec -> python3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both ETM notebooks now execute end-to-end (0 error cells) under SMOKE_TEST=1 on
the uv/Py3.11/MPS env.

Data plumbing:
- scripts/fetch_pitchfork.py: pulls reviews.csv from the HF mattismegevand/pitchfork
  dataset, remaps to the notebooks' schema (rating->score, synthetic unique `link`),
  and generates the missing stop.txt (spaCy English stopwords) into data/pitchfork/.
- pyproject: new `etm` dependency-group pinning the spaCy models the notebooks load
  (en_core_web_lg + en_core_web_md); install with `uv sync --frozen --group etm`.

Notebook changes (both):
- SMOKE_TEST toggle: subsample documents, relax vocab pruning (no_below), and cut
  epochs so the full pipeline runs in minutes; default reproduces the full config.
- WANDB_MODE=disabled by default (was hardcoded to the private jlealtru/ETM_runs_p
  entity, which no one else can write to).
- spaCy nlp.pipe n_process=1 and DataLoader num_workers=0: spaCy multiprocessing and
  notebook-defined Dataset/collate_fn deadlock/fail under nbconvert on macOS (spawn).
- Corpus/dictionary caches tagged by mode so smoke and full runs don't clobber.
- Normalize dead conda-env-torch-py kernelspec -> python3.
- etm_preprocessed_data: clamp a hard-coded inspection index (docs[9984]) so it also
  works on the smaller smoke subsample.

Removed trailing scratch cells that were already broken before modernization (never
executed in the original: truncated pyLDAvis call, `.to.` typo, undefined
vocab/model/gammas, an ETM-API-mismatched EVAE experiment), so each notebook now runs
clean top to bottom.

README: document scripts/fetch_pitchfork.py + `uv sync --group etm`, and add BigBird
and the two ETM notebooks to the SMOKE_TEST list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e sampling

Fixes three P1 findings from code review. All 7 affected notebooks re-verified
end-to-end under SMOKE_TEST=1 (0 error cells each).

P1a — _utils import failed from the documented repo-root Jupyter launch.
The setup cell did `sys.path.insert(0, '.')`, but _utils.py lives in notebooks/, so a
kernel started at the repo root raised ModuleNotFoundError. Replaced with a small loop
that locates notebooks/_utils.py from the repo root, notebooks/, or a subdir. Applied
to all 7 notebooks that import _utils. Verified the import works from both the repo
root and notebooks/.

P1b — BigBird no longer reliably used block-sparse attention.
Transformers 4.46.3 silently switches BigBird to dense original_full attention when
seq_len <= 704 (2*block_size + 3*block_size + num_random_blocks*block_size, block_size
64). The prior dynamic 'longest' padding on CUDA/CPU let batches fall below that and go
dense (defeating the tutorial's point; OOM risk). Now pad every batch to a fixed
MAX_LENGTH on all backends. Bumped the smoke seq from 128 to 768 (smallest multiple of
64 above 704) so the smoke run still exercises block-sparse. Verified: no
"Changing attention type" warning at 768; the full run at 1024 stays block-sparse.

P1c — Jigsaw smoke runs were neither short nor sampled.
The custom Dataset classes hard-coded max_length (3048/2048 for Longformer, 512 for
RoBERTa) instead of MAX_LENGTH, and prediction ran over the entire Kaggle test set.
Threaded MAX_LENGTH into every encode_plus call and slice the test set to N_SAMPLE
under SMOKE_TEST. Verified on synthetic data: test sampled 300 -> 64 rows, seq 128
(RoBERTa) / 512 (Longformer).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
etm_preprocessed_data.ipynb and etm_spacy_pipeline.ipynb both cached
corpus{SUFFIX}.mm / dict{SUFFIX}.pkl / docs{SUFFIX}.pkl under
data/pitchfork/ despite using different preprocessing (en_core_web_lg
lemmas vs en_core_web_md lowercase tokens), and only the former writes
doc_ids{SUFFIX}.pkl. Its "skip tokenization if corpus exists" check then
trips over the other notebook's cache: FileNotFoundError on doc_ids if
the spacy notebook ran first, or a silent corpus/doc_ids mismatch in the
opposite order. Namespace this notebook's cache files with _lg.

Found by running both notebooks in sequence on CUDA (RTX 3090).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified on CUDA (RTX 3090, torch 2.5.1+cu124): all smoke tests pass.
The MPS-tuned compromises now switch on device.type so one notebook
serves both machines:

- IMDB notebooks (RoBERTa/Longformer/BigBird): dataloader_pin_memory
  was hard-coded False for MPS; pinned host memory is a straight win
  on CUDA.
- Multilabel notebooks (RoBERTa/Longformer): fp16=False becomes
  bf16=(device.type=='cuda') — matching the bf16 choice of the IMDB
  notebooks (Ampere+) while keeping MPS/CPU in fp32 — and
  dataloader_num_workers 0 -> 4 with pinned memory on CUDA (the
  per-item Python tokenization in Data_Processing benefits directly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ETM notebooks already call spacy.prefer_gpu(), which silently
stays on CPU unless cupy is installed. cupy is CUDA-only, so it cannot
live in the base dependencies (the env must stay installable on
macOS); expose it as an opt-in group instead:

    uv sync --frozen --group spacy-cuda

Pinned to the same era as the rest of the stack. The sys_platform
marker makes the group a no-op on macOS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both Pitchfork ETM notebooks rebuilt every document's dense BOW in
python inside Data_Processing.__getitem__, once per document per
epoch, pinning one CPU core while the GPU idled at ~4%. Expand the
sparse corpus once into a float32 (num_docs x vocab_size) matrix
(1.1-1.7 GB, fits comfortably on the GPU), move it to the device, and
train on shuffled index batches sliced from it. Loss computation,
hyperparameters and epoch counts are unchanged; the loops now also
track per-epoch losses and plot NELBO/KL curves after training.

Verified on the RTX 3090 (cold caches, full config; executed copies
kept locally in results/_nbruns/speedup_{smoke,full}/):

- etm_preprocessed_data: 53m07s -> 18m32s wall, training cell
  43m53s -> 9m03s; final NELBO 1913.01 vs 1913.17 baseline; topic
  diversity 0.352 vs 0.351. GPU util ~4% -> ~96% during optimization;
  most of the remaining cell time is the every-40-epoch topic
  metrics (CPU gensim coherence), unchanged from baseline.
- etm_spacy_pipeline: 1h44m57s -> 13m32s wall, training cell
  1h36m05s -> 4m39s; final loss/doc 2289.9 vs 2345.9; GPU util
  sustained 97-98%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
etm_spacy_pipeline only reads lexical attributes (lower_, is_stop,
is_punct, is_digit), which the tokenizer alone provides, yet the
pipeline still ran tok2vec/tagger/attribute_ruler/lemmatizer on all
20,869 reviews. Disable every component: benchmarked byte-identical
output on 1,000 docs, 88.8s -> 7.1s CPU (projected ~2.5 min full
corpus vs the 7m58s the cell took with GPU tokenization). The other
ETM notebook keeps its full pipeline because it lemmatizes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rewrite the comments added during modernization (SMOKE_TEST block,
setup shim, dense-matrix and training-loop notes) in the notebooks'
own short style. Comment-only edits were patched via nbformat so
existing cell outputs are preserved.

etm_spacy_pipeline defined get_topics/get_topic_diversity/
get_most_similar_words but never called them, ending at the training
cell. Add the post-training exploration the other ETM notebook has:
beta from the trained model, topic words, topic diversity, and
nearest neighbors. nearest_neighbors called vocab.index(word), which
gensim's Dictionary does not have (latent bug, function was never
exercised); use vocab.token2id[word].

Both notebooks pass SMOKE_TEST=1 nbconvert end to end including the
new cells.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieng et al.'s training script feeds the encoder length-normalized
bows while keeping the reconstruction loss on raw counts; the
notebook passed raw counts to both. Normalize at the three input
sites (training loop, document_inference, and the single-doc demo)
so inference sees the same input scale as training.

Full 800-epoch run (warm caches, executed copy kept in
results/_nbruns/speedup_full/etm_preprocessed_data__bownorm.ipynb):
final NELBO 1912.54 vs 1913.01 unnormalized, topic diversity
0.352 -> 0.367, and much sharper document-topic assignments on the
test set (a country record at 0.93 on the country/folk topic where
nothing cleared 0.68 before). Topics remain clean genre topics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compares google/embeddinggemma-300m against MiniLM and MPNet on the same
train split, reduced to the ETM's 20 topics. The section self-skips when
sentence-transformers/transformers are too old or HF_TOKEN is missing,
since the model is gated.

Bumps transformers, tokenizers and sentence-transformers to the versions
that carry the Gemma3 encoder, adds python-dotenv to read HF_TOKEN from
.env, and pins default-groups so uv sync stops pruning the notebook deps.

Also fixes what the rerun uncovered: the stopword list now unions in
sklearn's list plus the contraction stems its tokenizer leaves behind,
the embedding cells force a re-encode instead of loading a stale cache,
and the split-size assert is dropped now that pitchfork.csv has grown
past the counts the ETM notebooks were written against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trainer renamed evaluation_strategy to eval_strategy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The saved outputs predated the corpus growing to 25,705 reviews, so the
topic lists, inference tables and timings below the split cell still
described the old 17,738/3,131 run. Re-executed top to bottom against the
current corpus: 21,849/3,856, minilm 27 topics reduced to 20 at 0.282
diversity, mpnet 29 reduced to 20 at 0.305, the etm reference is 0.367.

Also moves the mpnet encode up beside the minilm one. Encoding after the
first pipeline has been fitted leaves umap's knn graph, hdbscan's
prediction data and the soft membership matrix resident, and on unified
memory the encoder competes with all of it: 59.6 min against 8.7 for the
same work in an empty kernel. Moving it cut the encode to 10.0 min and,
unexpectedly, the mpnet fit from 16.4 min to 0.4 -- the fit was being
starved too. Whole notebook is ~12 min against ~77, with identical
clusters, diversity and per-document probabilities.

The comment claiming mpnet is five times slower to encode was wrong on
both counts and now records the measurement instead.

Fixes the EmbeddingGemma requirements note, which told readers to build a
separate env after pyproject had already been bumped to the versions that
carry the Gemma3 encoder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant