diff --git a/source/main.cpp b/source/main.cpp index 661ac52..8acc192 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,107 +1,109 @@ #include -#include -#include -#include -#include #include +#include +#include #include -#include #include "Rbm.hpp" -#include "Layer.hpp" +#include "AStack.hpp" #include "DeepStack.hpp" +#include "StackCreator.hpp" -using namespace std; -class RbmListener : public Rbm::IListener +// Minimal, dependency-free smoke test suite: for each named project under +// prj//, 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 { - public: - RbmListener() {} - virtual ~RbmListener() {} - - bool onProgress(Rbm *pRbm, const Rbm::Status &status) - { - std::cout << "Progress : " << status.progress << " %" << std::endl; - std::cout << "error (per mini batch) = " << status.err << std::endl; - std::cout << "error (total) = " << status.err_total << std::endl; - std::cout << "L1 = " << status.L1 << std::endl; - std::cout << "L2 = " << status.L2 << std::endl; - - return true; - } + +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(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) + ")"}; +} + +} // namespace + int main() { - printf("Hallo, Welt!\n"); + std::vector projects = {"1-hot", "prims", "norb_small_16h_v2", "mnist_2"}; - const string project("context99"); - - DeepStack stack(project); - -#if CREATE_TEST - stack.addTraining(stack.trainingBatch().row(1)); - printf("There are %d training samples\n", (int)stack.trainingBatch().n_rows); - - stack.delTraining(0); - printf("There are %d training samples\n", (int)stack.trainingBatch().n_rows); - - const int numLayers = 4; - int i = 0; - Layer *lowerLayer = new Layer("Layer", i, 16, 16, 8); - stack.addLayer(lowerLayer); - - for (++i; i < numLayers; i++) + int numPassed = 0; + for (const std::string &name : projects) { - Layer *layer = new Layer("Layer", i, lowerLayer->bh().n_elem, 1, lowerLayer->bh().n_elem >> 1); - layer->params().learningRate = lowerLayer->params().learningRate/2; - layer->params().numEpochs = lowerLayer->params().numEpochs/2; - lowerLayer = layer; - stack.addLayer(layer); + std::cout << "=== " << name << " ===" << std::endl; + TestResult result = testProject(name); + std::cout << (result.passed ? "PASS" : "FAIL") << ": " << result.message << std::endl << std::endl; + if (result.passed) + { + numPassed++; + } } - // Save project - stack.save(); - - // Shake weights - stack.weightsInit(0.01); - - // Save weights - stack.saveWeights(); - -#else - // Load project - stack.load(); - - // Load weights - stack.loadWeights(); - - // Load training - stack.loadTrainingBatch(); - -#endif - -#if TRAIN_TEST - RbmListener statusDisplay; - - // Train stack - stack.train(&statusDisplay); - - // Save weights - stack.saveWeights(); -#endif - Layer *layer = stack.getLayer(0); - arma::mat v = stack.trainingBatch(); - v.print("t"); - layer->calcContextBatch(v); - arma::mat h = layer->toHiddenProbs(v); - arma::mat r = layer->toVisibleProbs(h); - - v.print("v1"); - r = layer->upDownPass(v); - r.print("v2"); - - arma::mat err = Rbm::rms_error(r-v); - err.print("err"); - - - return 0; + std::cout << numPassed << "/" << projects.size() << " projects passed" << std::endl; + return numPassed == (int)projects.size() ? 0 : 1; }