Files
pyRBM/CLAUDE.md
T
jensandClaude Sonnet 5 05dc6266fe Add CLAUDE.md with architecture overview and dev commands
Documents the RBM/Stack/Model composition patterns, the numpy/cupy
backend switch gotcha, and test/run commands for future Claude Code sessions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VSd9diGXwX5E9mAsw4Wp7R
2026-07-26 23:09:35 +02:00

149 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
pyRBM is a from-scratch, hand-derived implementation of Restricted Boltzmann Machines (RBMs) and
Recurrent Temporal RBMs (RTRBMs) — no autodiff, no PyTorch training loop. Every gradient (Contrastive
Divergence) is coded by hand in `src/rbm/train.py`. It's a research/educational codebase, not a
competitive ML framework — see `README.rbm.md` for a full Q&A on the design rationale, why a
Transformer can't be bolted on without adding autodiff, and how the `src/compat/torch.py` bridge
relates to that.
## Environment
- Python >=3.12, project uses a `.venv` at the repo root (`.venv/bin/python`, `.venv/bin/pytest`).
- Package is installed editable (`pip install -e .`), src-layout: `src/{rbm,stack,model,label,image,compat}`
import as top-level packages (`from rbm.entity import Entity`, `from stack.rnn import StackRnn`, etc.)
— there is no `pyrbm.` prefix.
- GPU (CUDA) is used by default — see "CPU vs GPU" below.
- No linter/formatter config exists (no ruff/black/flake8/mypy) and no pytest config file — match
existing style (tabs for indentation in most `src/rbm` and `src/stack` files, spaces elsewhere;
check the file you're editing).
## Running tests
```bash
.venv/bin/pytest tests/ # whole suite
.venv/bin/pytest tests/test_rnn.py -q # single file
.venv/bin/pytest tests/test_rnn.py::test_rnn_shared -q # single test
```
`tests/README.md` documents each test's purpose and is worth reading before adding new tests — it
also has a fix-log at the bottom explaining several non-obvious bugs already found and fixed in
`train.py`/`entity.py` (e.g. why L1/L2 regularization must NOT be scaled by `1/mini_batch_size`, why
CD-k>1 Gibbs loops must sample rather than use means). Some `tests/*.py` files are pytest suites
(`def test_...`), others (`test_faces_sub_image.py`, `test_norbs.py`, `test_rbm.py`, etc.) are
standalone scripts invoked directly (`python tests/test_rbm.py <project_name>`) that need external
data/images not in the repo (see the table in `tests/README.md`).
## CPU vs GPU (important gotcha)
`src/rbm/matrix.py` is the single backend switch for the entire codebase:
```python
USE_CUDA = 1
if USE_CUDA:
import cupy as np
else:
import numpy as np
```
Every module imports the array module as `np` from here (`from .matrix import np`), so flipping
`USE_CUDA` to `0` switches the whole codebase to CPU-only numpy with no other code changes. This
machine has cupy-cuda12x + an RTX 4070 Ti, so GPU is the default/expected path. If you see
`cupy`-related errors on a machine without a GPU, this flag is the first place to check. Note that
`cupy-cuda12x` is a de-facto hard runtime dependency (nothing works with `USE_CUDA=1` without it) but
is *not* listed in `pyproject.toml`'s `dependencies` — it's only present because it was installed
manually into this `.venv`. `torch` is also installed but only backs `src/compat/torch.py` /
notebook use, not core training.
## Architecture
### Core primitives (`src/rbm/`)
- **`matrix.py`** — numpy/cupy backend switch (see above) + shared math helpers (`sample`, `prob`
(sigmoid), `gaussian`, `rms_error`, `read_armadillo` for loading legacy Armadillo `.dat` files).
- **`state.py`** (`RbmState`) — holds `w_hv`, `b_v`, `b_h`; pure linear algebra (`v_to_h`, `h_to_v`)
plus `.npz` save/load.
- **`entity.py`** (`Entity`) — one RBM unit. `Entity.Type` is one of `BB_RBM`/`BG_RBM`/`GB_RBM`/`GG_RBM`
(binary/Gaussian visible × binary/Gaussian hidden), derived from `EntityParams`. Owns a `RbmState`
(weights) and a `grad` (momentum accumulator). `forward`/`reconstruct` do (optionally multi-step)
Gibbs sampling; `h_given_v`/`v_given_h` are the raw (unsampled) linear step. `grad_compute` /
`state_adjust` apply momentum + L1/L2/weight-decay (regularization is applied at full strength,
NOT scaled by mini-batch size — see the fix-log note above).
- **`train.py`** — the four CD variants (`cd_binary_binary`, `cd_gaussian_binary`,
`cd_gaussian_gaussian`, `cd_binary_gaussian`), each a positive-phase/negative-phase Gibbs pass
mirroring the classic Hinton Matlab RBM code (comments in the file reference the original Matlab
pseudocode). `train()` is the standard epoch/mini-batch loop for a single `Entity`, dispatching to
the right CD function via `entity.type` and reporting through a `Status`.
- **`status.py`** — pluggable progress reporting/checkpointing (`Status`, `CheckpointStatus`) driven
by a callback (`on_report`) that can also halt training early.
- **`layer.py`** (`Layer`) — thin wrapper: one `Entity` + name + save/load, sized from
`(numVisibleX, numVisibleY, numContext, numHidden)`.
### Composition patterns — two different ways to build multi-entity models
1. **`Stack` family (`src/stack/`)** — an ordered `list[Layer]` with save/load-by-index into
`{name}-{i}-state.npz` files under `work_dir`.
- `StackDeep` (`deep.py`) — classic layer-wise-pretrained deep belief net: `train()` trains layer 0,
forwards its output as layer 1's input, etc. `pass_up`/`pass_down`/`pass_down_up` for
encode/decode/round-trip through the stack.
- `StackRnn` (`rnn.py`) — the RTRBM. Visible = `[context h_{t-1} | sensory x_t]`. Two modes based on
how many layers you `append()`:
- **Shared weights** (1 layer): the same `Entity` is reused every time step; sequences can be any
length T.
- **Unrolled** (N layers): `layers[t % N]` handles time step t, each with its own weights;
training requires sequences of length exactly N (uses "temporal-shift padding" — see the
docstring in `_train_unrolled`).
Use `StackRnn.make_layer(...)` / `StackRnn.make_unrolled(...)` to construct layers, `.step(x)` /
`.reconstruct(h)` for inference, `.train(sequences)` for training.
- `factory.py` (`StackFactory`) — builds a `Stack` from a JSON project dict/file (legacy `.prj`
format from a prior C++ implementation this was ported from — field names like `numVisibleX`,
`doSampleBatch` reflect that origin).
- `rnn_helper.py` — one-hot vocab encode/decode (`ch2idx`/`idx2ch`/`vec2idx`/`idx2vec`) and
visible-vector `concat`/`split`/`shift_*` helpers used by the RNN scripts/notebooks. `VOCAB` here
is a small ad-hoc set (currently space + digits) — the top-level scripts/notebooks each define
their own, larger vocab inline rather than importing this one.
2. **`Model` (`src/model/model.py`)** — an alternate, reflection-based composition: subclass `Model`,
assign `Entity` instances as plain attributes in `__init__`, implement `forward()`. Base class
discovers entities via `self.objects(Entity)` (walks `__dict__`) for `train`/`save`/`load`/`init`,
training each entity in sequence and feeding its output forward — used by `test_model.py`'s
`DeepModel` and by `src/label/label.py`'s `Label.Fitter`.
### Other modules
- **`src/label/label.py`** (`Label`) — encodes discrete labels as binary or one-hot vectors, then fits
a `Model` (an RBM) on the vectors so labels can be embedded/decoded through learned features.
- **`src/image/sub_image.py`** — patch-based image tiling: `SubImageExtract` cuts overlapping patches
(with stride) for training on image patches instead of whole images; `SubImageCombine` blends
overlapping reconstructed patches back into a full image (pixel-averaged); also a small `conv2d`.
- **`src/compat/torch.py`** — a thin PyTorch-`optim`-shaped wrapper (`LossFunction`, `Optimizer` with
`zero_grad()`/`step()`) around the *same* hand-coded CD functions from `train.py` — it mimics the
PyTorch API surface but is not autodiff; see `README.rbm.md` for why real backprop would require
replacing the numpy/cupy backend with torch tensors throughout.
### Top-level scripts and notebooks
These are the char-level RTRBM demos/experiments, each self-contained and runnable directly
(`python Rnn.py`, no install needed — they `sys.path.insert(0, "src")` themselves):
- **`Rnn.py`** — minimal unrolled-mode demo matching `docs/Rnn.drawio.png` (sliding window + context,
small hard-coded text).
- **`JayRnn.py`** / `README_JayRnn.md` — larger char-level language model, single shared-weight RBM,
fixed vocabulary, checkpoint/resume via `results/JayRnn-*-state.npz`.
- **`moby_rnn.ipynb`** / `README_moby_rnn.md` — the most fully worked example: trains on
`data/moby.txt`, unrolled `StackRnn` with `TEMPORAL_DEPTH` positions, includes evaluation
(teacher-forced reconstruction vs. honest next-step prediction), clamped-Gibbs text generation at
multiple temperatures, and hidden-state visualization.
- **`RNN-RBM.ipynb`** — the notebook the top-level `README.md` documents (context/sensory concat,
`UNROLL_DEPTH` modes).
- **`mnist_test*.ipynb`, `cifar_test*.ipynb`, `layer_test.ipynb`** — image-domain (non-recurrent)
RBM/DBN experiments using `data/MNIST` and `data/cifar-10-batches-py`.
Checkpoints from all of the above land in `results/{name}-{index}-state.npz`, loaded automatically on
the next run if present (`RbmState.load` silently no-ops on missing file/shape mismatch, so deleting a
checkpoint is the way to force retraining from scratch).