Files
pyRBM/README_moby_rnn.md
jensandClaude Sonnet 4.6 952045b9f1 [StackRnn] - temporal-shift padding, joint training, vocab reduction, TEMPORAL_DEPTH=64
stack_rnn.py:
  - _train_unrolled: C++-style temporal-shift padding — flatten (num_seq, T, s)
    → (N, s), append T-1 zero rows, layer t trains on batch[t:N+t]; joint
    training replaces greedy sequential (all layers update each epoch)
  - Reverted sequential/greedy training path (poor next-step prediction)

moby_rnn.ipynb:
  - Vocabulary reduced 85 → 40 chars (A-Z, 0-9, space, .!?)
  - T renamed to TEMPORAL_DEPTH throughout
  - TEMPORAL_DEPTH increased 5 → 64; CONTEXT_SIZE 128
  - SEED "Call me Ishmael." now falls within training data coverage

state.py:
  - Remove per-file load/save print messages (too noisy with 64-layer checkpoints)

README_moby_rnn.md:
  - Update vocab, constants, parameter count, checkpoint listing
  - Add temporal-shift padding and joint training sections
  - Clarify reconstruction accuracy vs next-step prediction as honest metric

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 14:37:08 +02:00

200 lines
8.2 KiB
Markdown
Raw Permalink 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.
# Moby RNN — Character-Level Language Model with Recurrent RBM
A character-level language model built on `StackRnn`, a recurrent Restricted
Boltzmann Machine where the visible layer at each time step is the concatenation
of a **context** vector (the previous hidden state) and the current sensory input
(one-hot encoded character).
---
## Architecture
### Single time step
```
Sensory input x_t (one-hot, vocab_size = 40)
┌──────────────────────────────────────────┐
│ visible layer │
│ ┌─────────────────┬────────────────┐ │
│ │ context_t-1 │ x_t │ │
│ │ (CONTEXT_SIZE) │ (vocab_size) │ │
│ └─────────────────┴────────────────┘ │
│ │ │
│ W_t, b_v_t, b_h_t │
│ │ │
│ ┌───────────────────────────────────┐ │
│ │ hidden layer │ │
│ │ context_t │ │
│ │ (CONTEXT_SIZE) │ │
│ └───────────────────────────────────┘ │
└──────────────────┬───────────────────────┘
┌──────────┴──────────┐
│ │
▼ ▼
context_{t+1} reconstruct x_t
(next time step) (predict char)
```
### Temporal unrolling — own weights per position
```
x_{t-1} x_t x_{t+1}
│ │ │
┌──────┴──────┐ ┌───────┴──────┐ ┌───────┴──────┐
│ ctx │x_{t-1} │ ctx │ x_t │ │ ctx │x_{t+1}│
│ ╠═══════╣ │ ╠════════╣ │ ╠════════╣
│ W_{t-1} │ │ W_t │ │ W_{t+1} │
│ ╠═══════╣ │ ╠════════╣ │ ╠════════╣
│ hidden_{t-1} │ │ hidden_t │ │ hidden_{t+1} │
└───────┬───────┘ └───────┬────────┘ └───────┬────────┘
│ context_{t-1} │ context_t │ context_{t+1}
└─────────────────►┘ ──────────────────►┘ ──────► ...
```
Each time step has its **own weight matrix** W_t, b_v_t, b_h_t.
`TEMPORAL_DEPTH` positions → `TEMPORAL_DEPTH` separate RBMs, each specialising
for the statistical patterns at that offset in a sequence.
During generation, position indices wrap modulo `TEMPORAL_DEPTH`.
For the shared-weights variant (1 entity via `make_layer`), a single W is reused
at every time step — the RTRBM concatenation variant of Sutskever & Hinton (2007).
---
## Vocabulary
The raw text is preprocessed to a 40-character vocabulary:
```
uppercase letters AZ (26)
digits 09 (10)
punctuation . ! ? (3)
separator space (1)
─────
40
```
Lowercase is folded to uppercase; all other characters collapse to a single space;
consecutive spaces are merged.
---
## Configuration
All constants live in the **Configuration** cell of `moby_rnn.ipynb`:
| Constant | Current | Description |
|---|---|---|
| `TEMPORAL_DEPTH` | 64 | Sequence length = number of RBMs |
| `NUM_SEQ` | 2000 | Training sequences (covers first ~128k chars) |
| `CONTEXT_SIZE` | 128 | Recurrent hidden / context state size |
| `PRJ_NAME` | `"moby_rnn"` | Checkpoint file prefix |
| `WORK_DIR` | `"results"` | Directory for saved weights |
| `EVAL_CHARS` | 2000 | Characters for reconstruction accuracy |
| `PRED_CHARS` | 500 | Characters for next-step prediction |
| `N_GIBBS` | 3 | Gibbs steps in clamped-Gibbs generation |
| `SEED` | `"Call me Ishmael."` | Seed text for free generation |
| `SEQ_IDX` | 42 | Sequence index for hidden-state trace plot |
**Model size:** `(CONTEXT_SIZE + vocab_size) × CONTEXT_SIZE × TEMPORAL_DEPTH`
— with defaults: `(128 + 40) × 128 × 64 = `**1,376,256 parameters** total.
---
## Training
### Temporal-shift padding
Sequences are flattened from `(NUM_SEQ, TEMPORAL_DEPTH, vocab_size)` to a
flat batch of `N = NUM_SEQ × TEMPORAL_DEPTH` rows. `TEMPORAL_DEPTH 1` zero
rows are appended, then **layer t trains on `batch[t : N+t]`** — a one-step
temporal delay matching the C++ `RnnStack` implementation:
```
Layer 0: batch[0 .. N-1] context = zeros
Layer 1: batch[1 .. N] context = h from layer 0 on rows 0..N-1
Layer 2: batch[2 .. N+1] context = h from layer 1 on rows 1..N
...
```
Context from layer t at row j feeds layer t+1 at row j, whose sensory input is
the original row j+1 — a one-step look-ahead.
### Joint training
All `TEMPORAL_DEPTH` layers are updated **together each epoch**:
the context chain is live during training, so each layer sees realistic context
from the layers below rather than frozen approximations.
### Resuming
Cell 5 saves a checkpoint at every progress report. Re-run it to continue:
```
results/moby_rnn-0-state.npz ← RBM at position 0
results/moby_rnn-1-state.npz ← RBM at position 1
...
results/moby_rnn-63-state.npz ← RBM at position TEMPORAL_DEPTH-1
```
---
## Notebook walkthrough
| Cell | Title | What it does |
|---|---|---|
| 1 | Imports | Standard + rbm imports |
| 2 | **Configuration** | All constants in one place |
| 3 | Load text | Read and preprocess `data/moby.txt`, build vocabulary |
| 4 | Encode sequences | One-hot encode → `(NUM_SEQ, TEMPORAL_DEPTH, vocab_size)` |
| 5 | Build model | Construct `StackRnn` with `TEMPORAL_DEPTH` layers; load checkpoint |
| 6 | Train | Joint CD training with temporal-shift padding; checkpoint every 5% |
| 7 | Generation helpers | `predict_next` (clamped Gibbs) and `generate_text` |
| 8 | Reconstruction accuracy | Teacher-forced: how well does `h_t` encode `x_t`? |
| 9 | Next-step prediction | Given `context_{t-1}`, predict `x_t` before seeing it |
| 10 | Free generation | Generate text at temperatures 0.5 / 1.0 / 1.5 |
| 11 | Hidden state trace | Heatmap of context activations over one sequence |
| 12 | Character distribution | Data vs model character frequency comparison |
### Evaluation metrics
**Reconstruction accuracy** (cell 8) is teacher-forced — the model sees `x_t`
as part of its visible input, so high accuracy just means the RBM is a decent
autoencoder. It does not measure sequence modeling ability.
**Next-step prediction accuracy** (cell 9) is the honest metric: given only
`context_{t-1}` (no `x_t`), predict the next character via clamped Gibbs.
Random baseline is `100 / vocab_size = 2.5%`.
---
## Text generation
`predict_next` uses **clamped Gibbs sampling**: context is held fixed while
the sensory part of the visible layer iterates for `N_GIBBS` steps.
The Bernoulli outputs are temperature-scaled and normalised to a categorical
distribution.
```python
text = generate_text(rnn, "CALL ME ISHMAEL.", length=500, temperature=0.8)
```
Note: the seed is preprocessed to the reduced vocabulary (uppercase, allowed
chars only) before priming the hidden state.
Lower temperature → more conservative / repetitive.
Higher temperature → more diverse / noisier.
---
## References
- Sutskever & Hinton (2007) — *Learning Multilevel Distributed Representations
for High-Dimensional Sequences*
- Boulanger-Lewandowski et al. (2012) — *Modeling Temporal Dependencies in
High-Dimensional Sequences: Application to Polyphonic Music Generation*