[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>
This commit is contained in:
2026-05-31 14:37:08 +02:00
co-authored by Claude Sonnet 4.6
parent 3698c85ed4
commit 952045b9f1
4 changed files with 611 additions and 289 deletions
+30 -12
View File
@@ -209,13 +209,30 @@ class StackRnn(Stack):
})
def _train_unrolled(self, seqs: Mat, num_seq: int, T: int, status: Status):
"""N entities, one per time step — each has its own W, b_v, b_h."""
"""N entities, one per time step — each has its own W, b_v, b_h.
Uses temporal-shift padding (matching C++ RnnStack):
Flatten (num_seq, T, sensory_size) → (N, sensory_size), append T-1 zero
rows, then layer t trains on batch_padded[t : N+t] — a one-step delay.
Joint training: all layers are updated together each epoch.
Context c from layer t feeds layer t+1 within the same epoch pass,
so gradients propagate through the full temporal chain.
"""
params = self.from_index(0).entity.training_params
h_sz = self.from_index(0).entity.shape[1]
s_sz = seqs.shape[2]
d_progress = 100.0 / params.num_epochs
progress = 0.0
keep_running = True
# Flatten to (N, s_sz) keeping sequence-major order, on CPU for slicing
flat = _np_cpu.asarray(convert(seqs) if hasattr(seqs, 'get') else seqs)
flat = flat.reshape(-1, s_sz)
N = flat.shape[0]
pad = _np_cpu.zeros((T - 1, s_sz), dtype=flat.dtype)
batch_pad = _np_cpu.concatenate([flat, pad], axis=0) # (N+T-1, s_sz)
for layer in self.layers:
layer.entity.grad_zero()
status.on_change(self.from_index(0).entity)
@@ -224,22 +241,22 @@ class StackRnn(Stack):
if not keep_running:
break
h = np.zeros((num_seq, h_sz))
c = np.zeros((N, h_sz))
err_total = 0.0
for t, layer in enumerate(self.layers):
entity = layer.entity
cd_func = _CD_FUNC[entity.type]
x_t = _to_gpu(seqs[:, t, :])
visible = np.concatenate([h, x_t], axis=1)
x_t = _to_gpu(batch_pad[t : N + t]) # shifted slice, (N, s_sz)
visible = np.concatenate([c, x_t], axis=1)
dwhv, dbv, dbh = cd_func(entity, visible)
grad = entity.grad_compute(dbv, dbh, dwhv)
entity.state_adjust(grad, 1.0 / num_seq)
entity.state_adjust(grad, 1.0 / N)
h = entity.forward(visible)
err_total += rms_error_accu(visible - entity.reconstruct(h))
c = entity.forward(visible)
err_total += rms_error_accu(visible - entity.reconstruct(c))
progress += d_progress
if status.want_report(round(progress)):
@@ -250,14 +267,15 @@ class StackRnn(Stack):
keep_running = False
break
h = np.zeros((num_seq, h_sz))
# Final report pass
c = np.zeros((N, h_sz))
err_total = 0.0
for t, layer in enumerate(self.layers):
entity = layer.entity
x_t = _to_gpu(seqs[:, t, :])
visible = np.concatenate([h, x_t], axis=1)
h = entity.forward(visible)
err_total += rms_error_accu(visible - entity.reconstruct(h))
x_t = _to_gpu(batch_pad[t : N + t])
visible = np.concatenate([c, x_t], axis=1)
c = entity.forward(visible)
err_total += rms_error_accu(visible - entity.reconstruct(c))
status.on_change(self.from_index(0).entity, {
"progress": {"value": 100, "unit": "%"},
"err_rms_total": {"value": err_total / T, "unit": ""},
-3
View File
@@ -22,7 +22,6 @@ class RbmState:
with np.load(filename) as X:
w_hv, b_v, b_h = [X[i] for i in ('whv', 'bv', 'bh')]
obj = cls(w_hv, b_v, b_h)
print(f"{filename} loaded successfully!")
except FileNotFoundError:
pass
except KeyError:
@@ -44,7 +43,6 @@ class RbmState:
self.w_hv = w_hv
self.b_v = b_v
self.b_h = b_h
print(f"{filename} loaded successfully!")
except FileNotFoundError:
pass
except KeyError:
@@ -53,7 +51,6 @@ class RbmState:
def save(self, filename: str):
np.savez(filename, whv=self.w_hv, bv=self.b_v, bh=self.b_h)
print(f"{filename} saved successfully!")
def init(self, mu: float = 0.0, std: float = 1.0):
self.w_hv = uniform(self.w_hv.shape, mu, std)