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
This commit is contained in:
2026-07-27 13:39:58 +02:00
co-authored by Claude Sonnet 5
parent 656a0252d3
commit aabf7385ce
+15 -13
View File
@@ -252,23 +252,20 @@ void Rbm::cd_hinton(arma::mat const &v_data, arma::mat &dw, arma::mat &dbh, arma
void Rbm::cd_jens(arma::mat const &v_states, arma::mat &dw, arma::mat &dbh, arma::mat &dbv) void Rbm::cd_jens(arma::mat const &v_states, arma::mat &dw, arma::mat &dbh, arma::mat &dbv)
{ {
arma::mat v_probs(v_states); arma::mat v_probs(v_states);
arma::mat h_states = toHiddenProbs(v_states); arma::mat h_probs = toHiddenProbs(v_states);
arma::mat h_probs; arma::mat h_states;
// Sample hidden // Sample hidden
if (m_params.doGaussianHidden) if (m_params.doGaussianHidden)
{ {
h_probs = h_states;
h_states = h_probs + arma::randn(h_probs.n_rows, h_probs.n_cols); h_states = h_probs + arma::randn(h_probs.n_rows, h_probs.n_cols);
} }
else if (m_params.doRaoBlackwell) else if (m_params.doRaoBlackwell)
{ {
h_probs = toHiddenProbs(v_states);
h_states = h_probs; h_states = h_probs;
} }
else else
{ {
h_probs = toHiddenProbs(v_states);
h_states = sample(h_probs); h_states = sample(h_probs);
} }
@@ -313,7 +310,6 @@ void Rbm::train(arma::mat const &batch, IListener* pListener)
double dProgress = 100.0/(batch.n_rows*m_params.numEpochs); double dProgress = 100.0/(batch.n_rows*m_params.numEpochs);
double progress = 0; double progress = 0;
int lastProgress = -100; int lastProgress = -100;
int batchRowIndex = 0;
arma::mat dbv(arma::zeros(1, m_bv.n_cols)); arma::mat dbv(arma::zeros(1, m_bv.n_cols));
arma::mat dbh(arma::zeros(1, m_bh.n_cols)); arma::mat dbh(arma::zeros(1, m_bh.n_cols));
@@ -322,13 +318,21 @@ void Rbm::train(arma::mat const &batch, IListener* pListener)
arma::mat inc_bv(arma::zeros(1, m_bv.n_cols)); arma::mat inc_bv(arma::zeros(1, m_bv.n_cols));
arma::mat inc_bh(arma::zeros(1, m_bh.n_cols)); arma::mat inc_bh(arma::zeros(1, m_bh.n_cols));
int trainingSizeRemain = batch.n_rows;
bool shouldAbort = false; bool shouldAbort = false;
// One epoch = one full pass over the (freshly reshuffled) data, mini-batch
// by mini-batch. Epochs must be the outer loop: nesting mini-batches on
// the outside would run all numEpochs steps on one fixed chunk before ever
// moving to the next, biasing training toward whatever data comes last.
for (int epoch=0; epoch < m_params.numEpochs && !shouldAbort; epoch++)
{
arma::mat shuffledBatch = arma::shuffle(batch);
int trainingSizeRemain = shuffledBatch.n_rows;
int batchRowIndex = 0;
while (trainingSizeRemain && !shouldAbort) while (trainingSizeRemain && !shouldAbort)
{ {
int miniBatchSizeActual = std::min(m_params.miniBatchSize, trainingSizeRemain); int miniBatchSizeActual = std::min(m_params.miniBatchSize, trainingSizeRemain);
arma::mat miniBatch = batch.rows(batchRowIndex, batchRowIndex+miniBatchSizeActual-1); arma::mat miniBatch = shuffledBatch.rows(batchRowIndex, batchRowIndex+miniBatchSizeActual-1);
trainingSizeRemain -= miniBatchSizeActual; trainingSizeRemain -= miniBatchSizeActual;
batchRowIndex += miniBatchSizeActual; batchRowIndex += miniBatchSizeActual;
int numcases = std::min(m_params.miniBatchSize, (int)batch.n_rows); int numcases = std::min(m_params.miniBatchSize, (int)batch.n_rows);
@@ -342,8 +346,6 @@ void Rbm::train(arma::mat const &batch, IListener* pListener)
v_states = sample(miniBatch); v_states = sample(miniBatch);
} }
for (int epoch=0; epoch < m_params.numEpochs; epoch++)
{
// Contrastive divergence learning: calculate gradients // Contrastive divergence learning: calculate gradients
cd(v_states, dwhv, dbh, dbv); cd(v_states, dwhv, dbh, dbv);
@@ -376,9 +378,9 @@ void Rbm::train(arma::mat const &batch, IListener* pListener)
} }
} }
} // Number of epochs } // mini batches
} // number of mini batches } // epochs
// Update final status // Update final status
status.err_total = rms_error_accu(batch - toVisibleProbs(toHiddenProbs(batch))); status.err_total = rms_error_accu(batch - toVisibleProbs(toHiddenProbs(batch)));