Files
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

131 lines
3.9 KiB
C++

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#include <armadillo>
#include "Rbm.hpp"
#include "AStack.hpp"
#include "DeepStack.hpp"
#include "StackCreator.hpp"
// Minimal, dependency-free smoke test suite: for each named project under
// prj/<name>/, load it, load its weights and training batch, run a full
// up-pass/down-pass reconstruction, and sanity-check the result. This
// replaces the old ad-hoc, since-bitrotted manual test program.
namespace
{
struct TestResult
{
bool passed;
std::string message;
};
// A model that hasn't learned anything shouldn't beat the trivial baseline
// of reconstructing every row as just the training batch's per-feature mean.
double meanBaselineError(const arma::mat &batch)
{
arma::mat meanRow = arma::mean(batch, 0);
arma::mat baseline = arma::repmat(meanRow, batch.n_rows, 1);
return Rbm::rms_error_accu(batch - baseline);
}
TestResult testProject(const std::string &name)
{
AStack *pStack = StackCreator::fromFile(".", name);
if (!pStack)
{
return {false, "failed to parse project file"};
}
bool weightsOk = pStack->loadWeights(".");
size_t numTraining = pStack->loadTrainingBatch(".");
if (!weightsOk)
{
delete pStack;
return {false, "failed to load weights"};
}
if (numTraining == 0)
{
delete pStack;
return {false, "failed to load training batch"};
}
if (pStack->type() != AStack::StackType::Deep)
{
delete pStack;
return {true, "skipped (reconstruction check only supports Deep stacks so far)"};
}
DeepStack *pDeep = static_cast<DeepStack*>(pStack);
arma::mat batch = pStack->trainingBatch();
arma::mat reconstruction = pDeep->upDownPass(pStack->getFirstLayer(), batch);
delete pStack;
if (reconstruction.n_rows != batch.n_rows || reconstruction.n_cols != batch.n_cols)
{
return {false, "reconstruction shape mismatch"};
}
double err = Rbm::rms_error_accu(batch - reconstruction);
if (!std::isfinite(err))
{
return {false, "reconstruction error is not finite (NaN/Inf)"};
}
double baseline = meanBaselineError(batch);
if (err > baseline * 1.5)
{
return {false, "reconstruction error " + std::to_string(err) +
" is worse than 1.5x the trivial mean-baseline " + std::to_string(baseline)};
}
return {true, "reconstruction error = " + std::to_string(err) +
" (mean-baseline = " + std::to_string(baseline) + ")"};
}
// Projects whose saved weights are known to fail the reconstruction check for
// reasons that have nothing to do with the test suite or the RBM code itself
// (e.g. an under-trained or abandoned saved snapshot). Retraining would fix
// these for real, but until that happens they're reported as a known issue
// rather than a plain FAIL so the suite's pass/fail signal stays meaningful.
const std::map<std::string, std::string> KNOWN_ISSUES = {
{"mnist_h32", "saved weights reconstruct worse than the mean baseline -- "
"likely an under-trained or abandoned snapshot, not a code bug"},
};
} // namespace
int main()
{
std::vector<std::string> projects = {"1-hot", "prims", "norb_small_16h_v2", "norb_small_16h", "mnist_2", "prims_deep", "count", "many", "mnist_deep", "mnist_h32", "mnist_21", "mnist"};
int numPassed = 0;
int numKnownIssue = 0;
int numFailed = 0;
for (const std::string &name : projects)
{
std::cout << "=== " << name << " ===" << std::endl;
TestResult result = testProject(name);
auto knownIssue = KNOWN_ISSUES.find(name);
if (!result.passed && knownIssue != KNOWN_ISSUES.end())
{
std::cout << "SKIP (known issue): " << knownIssue->second << std::endl << std::endl;
numKnownIssue++;
}
else
{
std::cout << (result.passed ? "PASS" : "FAIL") << ": " << result.message << std::endl << std::endl;
result.passed ? numPassed++ : numFailed++;
}
}
std::cout << numPassed << " passed, " << numKnownIssue << " known issue, "
<< numFailed << " failed (" << projects.size() << " total)" << std::endl;
return numFailed == 0 ? 0 : 1;
}