Commit Graph
251 Commits
Author SHA1 Message Date
jensandClaude Sonnet 5 797339a02f Rename GUI labels to L1 Lambda / L2 Lambda, wire up L1 control
Visible captions "Lambda" and "Weight decay" renamed to "L1 Lambda" and
"L2 Lambda" in both the main params panel (MainComponent.cpp) and the
per-layer view (RbmComponent.cpp), matching the backend Rbm::Params
field names (l1Lambda, l2Lambda).

lambdaLabel's change handler was a dead stub (tooltip said "Unused",
body did nothing) -- wired it up to write params().l1Lambda, mirroring
weightDecayLabel's existing l2Lambda handler. Also added the matching
lambdaLabel->setText(...) sync in updateControls(), which weightDecay
already had but lambdaLabel never did, so the field now actually
displays the loaded value instead of always showing its default.

Verified by launching the GUI and screenshotting it: both labels render
correctly, values populate, no layout/truncation issues.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 15:50:07 +02:00
jensandClaude Sonnet 5 0149cdf7ce Rename weightDecay to l2Lambda
Rbm::Params::weightDecay is now applied directly to the weights outside
the momentum recursion (previous commit), exactly like the newly-added
l1Lambda and matching pyRBM's l2_lambda -- same lambda*W gradient form,
same decoupled-from-momentum treatment. The two are functionally the
same mechanism in this codebase, so name it accordingly. Renamed the
Params field, its JSON key, and the two GUI references in
MainComponent.cpp that read/write it (the "weightDecayLabel" widget
identifier and its JUCE-generated marker comment are left as-is --
cosmetic, not part of the actual API).

Existing .prj files still have a "weightDecay" JSON key; fromJson()
no longer reads it, so it's silently ignored on load. Harmless today
since every current project has it set to 0.0 (confirmed: unchanged
poet.elf output, unchanged test-suite results). Not migrating the .prj
files themselves in this change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 15:41:01 +02:00
jensandClaude Sonnet 5 cb20060732 Implement L1 regularization
Rbm::Params had no l1Lambda field at all -- L1 didn't exist anywhere in
the C++ codebase. Status::L1 was a dead field always -1 (never assigned
outside its constructor), and the GUI's "Lambda" control was explicitly
tooltipped "Unused" with an empty change-handler stub. Scaffolded, never
implemented.

Add Params::l1Lambda (default 0.0, inert unless set) with full toJson/
fromJson round-trip. Apply its subgradient (lambda*sign(W), matching
pyRBM's l1_lambda) directly to m_whv in Rbm::train, in the same place
and same decoupled-from-momentum manner as the weightDecay fix from the
previous commit -- folding it into inc_whv's momentum recursion would
cause the same momentum-amplification bug. Doesn't touch the biases,
matching pyRBM's state_adjust().

Also wire up Status::L1 to actually report something (the current L1
norm of the weights, sum(abs(W))) instead of permanently printing -1,
computed alongside status.err/err_total.

Inert by default (l1Lambda=0.0 in every existing .prj): confirmed via
unchanged poet.elf output and unchanged test-suite results (9 passed,
1 known issue, 2 failed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 15:31:59 +02:00
jensandClaude Sonnet 5 0dd2b89cf0 Decouple weight decay from momentum in Rbm::train
inc_whv = momentum*inc_whv + lr*(dwhv/numcases - weightDecay*m_whv) folded
the weight-decay term into the momentum-accumulated velocity, so its
steady-state effective strength was amplified by roughly
weightDecay/(1-momentum) rather than the configured value -- e.g. ~2x at
momentum=0.5, ~10x at momentum=0.9. This is the same L2-regularization-
vs-momentum interaction that motivated decoupled weight decay (AdamW) in
the broader literature; pyRBM's state_adjust() already applies its
regularization terms directly to the weights outside the momentum
recursion, at full undiscounted strength every step.

Apply weightDecay directly to m_whv after the momentum-accumulated CD
update instead. weightDecay defaults to 0.0 in every .prj in this repo,
so this is currently inert in practice -- confirmed via unchanged
poet.elf output and unchanged test-suite results (9 passed, 1 known
issue, 2 failed) -- but anyone who sets it will now get the strength
they actually configured.

L1 regularization has no equivalent to fix: there's no l1Lambda field in
Rbm::Params at all, Status::L1 is a dead field always -1, and the GUI's
"Lambda" control is explicitly tooltipped "Unused" with an empty
change-handler stub -- it was scaffolded and never implemented.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 15:21:51 +02:00
jensandClaude Sonnet 5 5736541744 Mark mnist_h32 as a known issue instead of a plain FAIL
Its saved weights load fine but genuinely reconstruct worse than the
mean baseline (likely an under-trained or abandoned snapshot) -- a data
problem, not a code bug, and retraining is out of scope for now. Add a
KNOWN_ISSUES map so cases like this report SKIP with a reason instead of
FAIL, and stop counting toward the suite's failure exit code, while
still being visible in the output. norb_small_16h_v2/many (missing
weight files entirely) remain plain FAILs since that wasn't in scope
for this change.

9 passed, 1 known issue, 2 failed (12 total).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 15:04:04 +02:00
jensandClaude Sonnet 5 af35c0902d Add mnist to the test suite
Single-layer Deep stack (28x28->256), has saved weights and a larger
training batch than the other mnist variants (test run took noticeably
longer). Passes: reconstruction error 0.005 vs. mean-baseline 0.067.
9/12 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 15:01:31 +02:00
jensandClaude Sonnet 5 10cdd6fb9b Add mnist_21 to the test suite
3-layer Deep stack (28x28->256->512->64), has saved weights for all
three layers. Passes: reconstruction error 0.039 vs. mean-baseline
0.063. 8/11 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:59:43 +02:00
jensandClaude Sonnet 5 db7eb72174 Add mnist_h32 to the test suite
Single-layer Deep stack (28x28->32), weights load fine but genuinely
fail the reconstruction check: error 0.231 vs. mean-baseline 0.067,
worse than the 1.5x tolerance. Unlike norb_small_16h_v2/many (which fail
because no current-format weights exist at all), this one has weights
that load successfully but just don't reconstruct well -- training
params look ordinary (RaoBlackwell, 1000 epochs) and the training data
looks like normal-scale MNIST pixels, so this looks like a genuinely
under-trained or abandoned saved snapshot rather than a test bug.
7/11 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:58:04 +02:00
jensandClaude Sonnet 5 86764bb261 Add mnist_deep to the test suite
3-layer Deep stack (28x28->256->256->16), has saved weights for all
three layers. Passes: reconstruction error 0.039 vs. mean-baseline 0.067.
7/9 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:54:54 +02:00
jensandClaude Sonnet 5 7b842ea50c Add many to the test suite
101-layer Deep stack (64->64 x100). Fails the same way as
norb_small_16h_v2: only legacy *.weights.dat files exist (the pre-split
combined-weights format current code never reads), no .w/.bh/.bv.dat for
any layer -- a pre-existing data gap, not a bug. 6/8 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:52:13 +02:00
jensandClaude Sonnet 5 1acc05bc24 Add count to the test suite
Single-layer Deep stack (4x2->16), has saved weights and training data.
Passes: reconstruction error ~0.000000 vs. mean-baseline 0.16.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:51:27 +02:00
jensandClaude Sonnet 5 82d019ba48 Add prims_deep to the test suite
2-layer DeepStack (9x9->24, 24->4), has saved weights and training data.
Passes: reconstruction error 0.0007 vs. mean-baseline 0.14 -- exercises
upDownPass through more than one layer for the first time in the suite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:50:14 +02:00
jensandClaude Sonnet 5 156babeb49 Add norb_small_16h to the test suite
Has saved weights and training data (unlike norb_small_16h_v2, which
still has none). Passes: reconstruction error 0.0003 vs. mean-baseline
0.54.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:49:15 +02:00
jensandClaude Sonnet 5 834723dcad Replace stale main.cpp with a minimal project test suite
The old main.cpp (TEST target) called APIs that no longer exist
(DeepStack::load()/loadWeights()/loadTrainingBatch() with no args,
Layer::upDownPass()) -- it hadn't compiled against the current AStack/
Layer API in a long time and was a manual smoke-test script, not an
actual test suite.

Replace it with a small dependency-free runner matching this repo's
existing no-framework style: for each named project, load it, load
weights and training batch, run a full up-pass/down-pass reconstruction
via DeepStack::upDownPass, and check the reconstruction error is finite
and beats the trivial per-feature-mean baseline (a project-agnostic sanity
bound, rather than a hardcoded threshold). Prints PASS/FAIL per project.

Starting coverage: 1-hot, prims, norb_small_16h_v2, mnist_2. 3/4 pass;
norb_small_16h_v2 fails because it has no saved weight files at all
(only .prj/.training.dat/.test.dat) -- a pre-existing gap in that
fixture's data, not a bug introduced here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:47:32 +02:00
jensandClaude Sonnet 5 ca27e1e705 Route project files through prj/<name>/ instead of flat at repo root
Repo root had ~35 groups of .prj/.dat files loose alongside the build
system and source, all sharing one flat namespace. Centralize the new
prj/<name>/ convention inside AStack::loadWeights/saveWeights/
loadTrainingBatch/saveTrainingBatch and StackCreator::fromFile/toFile
(via a new AStack::projectDir() helper) -- these already have access to
the project name, so callers (poet.cpp, the GUI) need no changes at all;
they keep passing the same base directory they always did.

Filenames inside each project folder are unchanged (e.g.
prj/mnist_2/mnist_2.prj, prj/mnist_2/mnist_2.Layer.0.w.dat) -- only the
directory moves. Added Matutils::ensureDir() (mkdir -p equivalent; no
std::filesystem in C++11) since ofstream won't create directories, used
wherever a project is saved for the first time.

File migration for existing projects is a separate commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 14:41:05 +02:00
jensandClaude Sonnet 5 4cf5f15555 Fix cd_jens positive phase using noisy sample instead of mean
doGaussianHidden took priority over doRaoBlackwell when deciding h_states
for the positive-phase gradient, so a Gaussian-hidden RBM always got the
noisy sample (h_probs + randn) for dw/dbh even with doRaoBlackwell set --
ignoring the flag and adding avoidable gradient variance. Standard CD
practice (and pyRBM's cd_gaussian_gaussian) uses the mean for the
weight/bias gradient when Rao-Blackwellizing, regardless of unit type.

Reordered so doRaoBlackwell is checked first (mean, any unit type) and
only samples otherwise, dispatching via the sampleHidden() helper added
in the previous commit so Gaussian/binary hidden units are each sampled
correctly. Behavior-preserving for poet (doRaoBlackwell=true,
doGaussianHidden=false already took the mean branch); only changes
behavior for a Gaussian-hidden RBM trained with doRaoBlackwell enabled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 13:57:46 +02:00
jensandClaude Sonnet 5 eb29e33b81 Fix Bernoulli-only sample() applied to Gaussian visible/hidden units
Matutils::sample() always binary-thresholds (src > uniform(src)), but it
was the only sampler in the codebase and was called unconditionally on
h_probs/v_probs/miniBatch in several CD Gibbs-loop branches regardless
of doGaussianVisible/doGaussianHidden. Binary-thresholding a Gaussian
unit's continuous activation is meaningless -- it would corrupt any
Gaussian-visible/hidden RBM (image-domain experiments via the GUI or
TEST target); doesn't affect poet's plain BB-RBM path since both flags
are false there.

Add sample_gaussian() (mean + N(0,1) noise) alongside the existing
Bernoulli sample() in matutils.hpp, plus Rbm::sampleVisible/sampleHidden
helpers that dispatch to the right one per the RBM's configured type.
Replace every visible/hidden Gibbs-step sample() call in cd_jens (the
active path) and cd_hinton (compiled but currently unused, behind
USE_CD_HINTON) with the appropriate dispatch helper, and fix the same
issue in Rbm::train's doSampleBatch path.

Behavior is unchanged for any RBM with doGaussianVisible/doGaussianHidden
both false (confirmed: poet.elf f output identical before/after).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 13:45:03 +02:00
jensandClaude Sonnet 5 aabf7385ce Fix RBM training: invert epoch/mini-batch loop nesting, drop duplicate CD computation
Rbm::train had mini-batch chunks as the outer loop and epochs as the
inner loop: each fixed, never-reshuffled slice of the data got all
numEpochs gradient steps back-to-back before ever being revisited, so
"numEpochs" didn't mean "passes over the whole dataset" and training
was biased toward whatever data came last. Invert the nesting (epochs
outer, mini-batches inner, batch reshuffled via arma::shuffle at the
start of each epoch) so every epoch is an actual full pass over the
data in a fresh random order.

Also drop a redundant toHiddenProbs(v_states) call in cd_jens: the
positive-phase hidden probabilities were computed once unconditionally
and then discarded, recomputed a second time with identical input in
two of the three sampling branches. Same result, half the cost, on
every mini-batch of every epoch of every layer.

Both only affect training; inference (step_forward/generation) is
unchanged, confirmed by identical poet.elf f output before and after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 13:39:58 +02:00
jensandClaude Sonnet 5 656a0252d3 Wire up temperature-based sampling for poet text generation
Greedy argmax decoding in RbmListener::forward always produces the
exact same character sequence and quickly falls into short repeating
loops once the 5-character lookback state revisits a prior cycle.

Add an optional temperature argument (poet f <seed> [temperature]) that
switches decoding to RnnStack::sample_one_hot, which now does proper
categorical sampling (temperature-scaled, renormalized draw) instead of
the old per-code Bernoulli approach that could leave the result as a
non-one-hot probability vector. Also seed Armadillo's RNG in main(),
since it otherwise defaults to a fixed seed and every run would sample
identically.

Add docs/RNN_ARCHITECTURE.md documenting how the RnnStack/Layer stack
implements the RNN (context chaining across layers, training/generation
data flow, and the decoding behavior above).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016K8Gu7Qejd11JbdiHZqYAs
2026-07-27 12:34:44 +02:00
jens f805b571d9 commit stale changes 2026-07-26 21:49:32 +02:00
jens 32c3526ba7 - Rbm::Params: Momentum default 0.9 2024-02-03 09:49:13 +01:00
jens 324a56557b - use cd_jens() 2024-02-02 18:59:04 +01:00
jens 5321e01cca - improved cd_hinton()
- uniform() returns vector
2024-02-01 19:03:13 +01:00
jens 8e44aff7a5 - prepare cd_jens() for linear hidden units 2024-01-31 20:52:18 +01:00
jens 861e03decf - DrawComponent: fixed normalizing to screen 2024-01-31 16:55:41 +01:00
jens 75e71bd060 - use auto detect for training data load 2024-01-31 15:29:38 +01:00
jens ccc3e0ac87 - fixed total error calc 2024-01-31 12:52:29 +01:00
jens 2a2be76b2c - AStack:Load show mean and stddev 2024-01-31 12:20:15 +01:00
jens e211c568ac Rbm
- enable linear hidden : instead of prob(v_to_h()): use toHiddenProbs()
- enable linear visible: instead of prob(h_to_v()): use toVisibleProbs()
- make v_to_h() and h_to_v() private and force to use toHiddenProbs() and toVisibleProbs()
- use cd_jens or cd_hinton. cd_hinton_hid_lineaer is not of use anymre, since it is not capable of CDn
2024-01-31 12:03:47 +01:00
jens 285be92cdb - finally return normalized data 2024-01-31 11:40:41 +01:00
jens c0445f3bf8 - moved Rbm:prob() to Matutils::prob()
- Matutils::Normalize uses prob()
2024-01-29 13:04:13 +01:00
jens fe8142627d cd_hinton_hid_binary: added raoBlackwell, gibbs-sampling 2024-01-25 22:45:50 +01:00
jens 34b20f4fb3 - refactored
- switch between cd_hinton hid/linear and cd_jens using doGaussionVisible (temporary solution)
2024-01-24 19:22:57 +01:00
jens a5ed0be991 - added original implementation for binary RBM 2024-01-24 18:41:42 +01:00
jens 84a5dd4550 Refactored 2024-01-24 17:04:05 +01:00
jens b467180535 Initialize buttons rbmUseVisibleGaussianToggleButton and rbmUseHiddenGaussianToggleButton 2024-01-24 17:02:46 +01:00
jens 88f649e8f4 - refactored 2024-01-24 16:56:49 +01:00
jens 9a381c1899 - its gaussion hidden (not gaussian visible) 2024-01-24 15:14:30 +01:00
jens fe9b1951ea - Added gaussian hidden units 2024-01-24 14:47:21 +01:00
jens fb5ac6d124 Fixed normalization 2024-01-24 14:44:20 +01:00
jens 4a7fcb387c - fixed normalization
- added sigmoid after normalization
2024-01-23 21:05:13 +01:00
jens 4a16926030 - improved training data normalization 2024-01-22 19:53:12 +01:00
jens 44445d3fd2 - fixed JUCE includes 2024-01-22 13:55:51 +01:00
jens 938368f1fa - refactored
- constify
2024-01-22 12:26:03 +01:00
jens d296ca5a1d - fixed crash 2024-01-21 20:44:56 +01:00
jens 6eb76febdc - added ToolTips for RBM windows 2024-01-21 15:26:48 +01:00
jens d5a64f6e6b - DeppeStack: simplified upPass, downPass 2024-01-21 15:25:50 +01:00
jens 3e819d32a8 - fixed crashes 2024-01-21 14:10:37 +01:00
jens 46ded370f6 - Layer: fixed privacy 2024-01-21 14:10:01 +01:00
jens aca008d4ff - refactored 2024-01-21 14:09:30 +01:00