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
This commit is contained in:
2026-07-26 23:09:35 +02:00
co-authored by Claude Sonnet 5
parent b534be9fad
commit 05dc6266fe
+148
View File
@@ -0,0 +1,148 @@
# 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).