diff --git a/Layer.cpp b/Layer.cpp index 96010c5..f4b7319 100644 --- a/Layer.cpp +++ b/Layer.cpp @@ -21,7 +21,7 @@ void Layer::setOutputValues(const std::vector & outputValues) for (const double &value : outputValues) { (neuronIt++)->setOutputValue(value); - } + } } void Layer::feedForward(const Layer &inputLayer) @@ -54,7 +54,7 @@ void Layer::connectTo(const Layer & nextLayer) void Layer::updateInputWeights(Layer & prevLayer) { - static const double trainingRate = 0.3; + static const double trainingRate = 0.2; for (size_t targetLayerIndex = 0; targetLayerIndex < sizeWithoutBiasNeuron(); ++targetLayerIndex) { diff --git a/Layer.h b/Layer.h index 94bab0e..bed1b66 100644 --- a/Layer.h +++ b/Layer.h @@ -13,9 +13,10 @@ public: Layer(size_t numNeurons); void setOutputValues(const std::vector & outputValues); + void feedForward(const Layer &inputLayer); double getWeightedSum(size_t outputNeuron) const; - void connectTo(const Layer & nextLayer); + void connectTo(const Layer &nextLayer); void updateInputWeights(Layer &prevLayer); diff --git a/Net.cpp b/Net.cpp index 62c786d..4543321 100644 --- a/Net.cpp +++ b/Net.cpp @@ -63,7 +63,7 @@ void Net::feedForward(const std::vector &inputValues) Layer &nextLayer = *(layerIt + 1); nextLayer.feedForward(currentLayer); - } + } } std::vector Net::getOutput() diff --git a/gui/NeuroUI/MNIST Database/t10k-images.idx3-ubyte b/gui/NeuroUI/MNIST Database/t10k-images.idx3-ubyte new file mode 100644 index 0000000..1170b2c Binary files /dev/null and b/gui/NeuroUI/MNIST Database/t10k-images.idx3-ubyte differ diff --git a/gui/NeuroUI/MNIST Database/t10k-labels.idx1-ubyte b/gui/NeuroUI/MNIST Database/t10k-labels.idx1-ubyte new file mode 100644 index 0000000..d1c3a97 Binary files /dev/null and b/gui/NeuroUI/MNIST Database/t10k-labels.idx1-ubyte differ diff --git a/gui/NeuroUI/MNIST Database/train-images.idx3-ubyte b/gui/NeuroUI/MNIST Database/train-images.idx3-ubyte new file mode 100644 index 0000000..bbce276 Binary files /dev/null and b/gui/NeuroUI/MNIST Database/train-images.idx3-ubyte differ diff --git a/gui/NeuroUI/MNIST Database/train-labels.idx1-ubyte b/gui/NeuroUI/MNIST Database/train-labels.idx1-ubyte new file mode 100644 index 0000000..d6b4c5d Binary files /dev/null and b/gui/NeuroUI/MNIST Database/train-labels.idx1-ubyte differ diff --git a/gui/NeuroUI/NeuroUI.pro b/gui/NeuroUI/NeuroUI.pro index 66639e1..7b35e9c 100644 --- a/gui/NeuroUI/NeuroUI.pro +++ b/gui/NeuroUI/NeuroUI.pro @@ -18,14 +18,16 @@ SOURCES += main.cpp\ ../../Net.cpp \ ../../Neuron.cpp \ netlearner.cpp \ - errorplotter.cpp + errorplotter.cpp \ + mnistloader.cpp HEADERS += neuroui.h \ ../../Layer.h \ ../../Net.h \ ../../Neuron.h \ netlearner.h \ - errorplotter.h + errorplotter.h \ + mnistloader.h FORMS += neuroui.ui diff --git a/gui/NeuroUI/icons/NeuroUI Icon.png b/gui/NeuroUI/icons/NeuroUI Icon.png index ce6064e..22a300e 100644 Binary files a/gui/NeuroUI/icons/NeuroUI Icon.png and b/gui/NeuroUI/icons/NeuroUI Icon.png differ diff --git a/gui/NeuroUI/mnistloader.cpp b/gui/NeuroUI/mnistloader.cpp new file mode 100644 index 0000000..67c4c1f --- /dev/null +++ b/gui/NeuroUI/mnistloader.cpp @@ -0,0 +1,97 @@ +#include "mnistloader.h" + +#include + +void MnistLoader::load(const std::string &databaseFileName, const std::string &labelsFileName) +{ + loadDatabase(databaseFileName); + loadLabels(labelsFileName); +} + +const MnistLoader::MnistSample &MnistLoader::getRandomSample() const +{ + size_t sampleIndex = (std::rand() * (samples.size() - 1)) / RAND_MAX; + + return *(samples[sampleIndex].get()); +} + +void MnistLoader::loadDatabase(const std::string &fileName) +{ + std::ifstream databaseFile; + databaseFile.open(fileName, std::ios::binary); + + if (!databaseFile.is_open()) + { + throw std::runtime_error("unable to open MNIST database file"); + } + + int32_t magicNumber = readInt32(databaseFile); + if (magicNumber != DatabaseFileMagicNumber) + { + throw std::runtime_error("unexpected data reading MNIST database file"); + } + + int32_t sampleCount = readInt32(databaseFile); + int32_t sampleWidth = readInt32(databaseFile); + int32_t sampleHeight = readInt32(databaseFile); + + if (sampleWidth != SampleWidth || sampleHeight != SampleHeight) + { + throw std::runtime_error("unexpected sample size loading MNIST database"); + } + + samples.reserve(samples.size() + sampleCount); + + for (int32_t sampleIndex = 0; sampleIndex < sampleCount; ++sampleIndex) + { + std::unique_ptr sample = std::make_unique(); + + databaseFile.read(reinterpret_cast(sample->data), sampleWidth * sampleHeight); + + samples.push_back(std::move(sample)); + } +} + +void MnistLoader::loadLabels(const std::string &fileName) +{ + std::ifstream labelFile; + labelFile.open(fileName, std::ios::binary); + + if (!labelFile.is_open()) + { + throw std::runtime_error("unable to open MNIST label file"); + } + + int32_t magicNumber = readInt32(labelFile); + if (magicNumber != LabelFileMagicNumber) + { + throw std::runtime_error("unexpected data reading MNIST label file"); + } + + int32_t labelCount = readInt32(labelFile); + if (labelCount != static_cast(samples.size())) + { + throw std::runtime_error("MNIST database and label files don't match in size"); + } + + auto sampleIt = samples.begin(); + for (int32_t labelIndex = 0; labelIndex < labelCount; ++labelIndex) + { + (*sampleIt++)->label = readInt8(labelFile); + } +} + +int8_t MnistLoader::readInt8(std::ifstream &file) +{ + int8_t buf8; + file.read(reinterpret_cast(&buf8), sizeof(buf8)); + return buf8; +} + +int32_t MnistLoader::readInt32(std::ifstream &file) +{ + int32_t buf32; + file.read(reinterpret_cast(&buf32), sizeof(buf32)); + return _byteswap_ulong(buf32); +} + diff --git a/gui/NeuroUI/mnistloader.h b/gui/NeuroUI/mnistloader.h new file mode 100644 index 0000000..d2a1e2b --- /dev/null +++ b/gui/NeuroUI/mnistloader.h @@ -0,0 +1,44 @@ +#ifndef MNISTLOADER_H +#define MNISTLOADER_H + +#include +#include +#include +#include + +class MnistLoader +{ +private: + static const uint32_t DatabaseFileMagicNumber = 2051; + static const uint32_t LabelFileMagicNumber = 2049; + static const size_t SampleWidth = 28; + static const size_t SampleHeight = 28; + +public: + template + class Sample + { + public: + uint8_t label; + uint8_t data[SAMPLE_WIDTH * SAMPLE_HEIGHT]; + }; + + using MnistSample = Sample; + +private: + std::vector> samples; + +public: + void load(const std::string &databaseFileName, const std::string &labelsFileName); + + const MnistSample &getRandomSample() const; + +private: + void loadDatabase(const std::string &fileName); + void loadLabels(const std::string &fileName); + + static int8_t readInt8(std::ifstream &file); + static int32_t readInt32(std::ifstream &file); +}; + +#endif // MNISTLOADER_H diff --git a/gui/NeuroUI/netlearner.cpp b/gui/NeuroUI/netlearner.cpp index af849db..b0956e1 100644 --- a/gui/NeuroUI/netlearner.cpp +++ b/gui/NeuroUI/netlearner.cpp @@ -1,7 +1,9 @@ #include "netlearner.h" #include "../../Net.h" +#include "mnistloader.h" #include +#include void NetLearner::run() { @@ -9,67 +11,54 @@ void NetLearner::run() { QElapsedTimer timer; - Net myNet; - try - { - myNet.load("mynet.nnet"); - } - catch (...) - { - myNet.initialize({2, 3, 1}); - } + emit logMessage("Loading training data..."); - size_t batchSize = 5000; - size_t batchIndex = 0; - double batchMaxError = 0.0; - double batchMeanError = 0.0; + MnistLoader mnistLoader; + mnistLoader.load("../NeuroUI/MNIST Database/train-images.idx3-ubyte", + "../NeuroUI/MNIST Database/train-labels.idx1-ubyte"); + + emit logMessage("done"); + + Net digitClassifier({28*28, 256, 1}); timer.start(); - size_t numIterations = 1000000; + size_t numIterations = 100000; for (size_t iteration = 0; iteration < numIterations; ++iteration) { - std::vector inputValues = - { - std::rand() / (double)RAND_MAX, - std::rand() / (double)RAND_MAX - }; + auto trainingSample = mnistLoader.getRandomSample(); + + QImage trainingImage(trainingSample.data, 28, 28, QImage::Format_Grayscale8); + emit sampleImageLoaded(trainingImage); std::vector targetValues = { - (inputValues[0] + inputValues[1]) / 2.0 + trainingSample.label / 10.0 }; - myNet.feedForward(inputValues); + std::vector trainingData; + trainingData.reserve(28*28); + for (const uint8_t &val : trainingSample.data) + { + trainingData.push_back(val / 255.0); + } - std::vector outputValues = myNet.getOutput(); + digitClassifier.feedForward(trainingData); + + std::vector outputValues = digitClassifier.getOutput(); double error = outputValues[0] - targetValues[0]; - batchMeanError += error; - batchMaxError = std::max(batchMaxError, error); + QString logString; - if (batchIndex++ == batchSize) - { - QString logString; + logString.append("Error: "); + logString.append(QString::number(std::abs(error))); - logString.append("Batch error ("); - logString.append(QString::number(batchSize)); - logString.append(" iterations, max/mean): "); - logString.append(QString::number(std::abs(batchMaxError))); - logString.append(" / "); - logString.append(QString::number(std::abs(batchMeanError / batchSize))); + emit logMessage(logString); + emit currentNetError(error); + emit progress((double)iteration / (double)numIterations); - emit logMessage(logString); - emit currentNetError(batchMaxError); - emit progress((double)iteration / (double)numIterations); - - batchIndex = 0; - batchMaxError = 0.0; - batchMeanError = 0.0; - } - - myNet.backProp(targetValues); + digitClassifier.backProp(targetValues); } QString timerLogString; @@ -79,7 +68,7 @@ void NetLearner::run() emit logMessage(timerLogString); - myNet.save("mynet.nnet"); + digitClassifier.save("DigitClassifier.nnet"); } catch (std::exception &ex) { diff --git a/gui/NeuroUI/netlearner.h b/gui/NeuroUI/netlearner.h index 2c2b6fa..70378a4 100644 --- a/gui/NeuroUI/netlearner.h +++ b/gui/NeuroUI/netlearner.h @@ -14,6 +14,7 @@ signals: void logMessage(const QString &logMessage); void progress(double progress); void currentNetError(double error); + void sampleImageLoaded(const QImage &image); }; #endif // NETLEARNER_H diff --git a/gui/NeuroUI/neuroui.cpp b/gui/NeuroUI/neuroui.cpp index bd63ecd..c057984 100644 --- a/gui/NeuroUI/neuroui.cpp +++ b/gui/NeuroUI/neuroui.cpp @@ -31,6 +31,8 @@ void NeuroUI::on_runButton_clicked() connect(m_netLearner.get(), &NetLearner::finished, this, &NeuroUI::netLearnerFinished); connect(m_netLearner.get(), &NetLearner::currentNetError, ui->errorPlotter, &ErrorPlotter::addErrorValue); + + connect(m_netLearner.get(), &NetLearner::sampleImageLoaded, this, &NeuroUI::setImage); } m_netLearner->start(); @@ -61,3 +63,10 @@ void NeuroUI::progress(double progress) ui->progressBar->setValue(value); } + +void NeuroUI::setImage(const QImage &image) +{ + QPixmap pixmap; + pixmap.convertFromImage(image); + ui->label->setPixmap(pixmap); +} diff --git a/gui/NeuroUI/neuroui.h b/gui/NeuroUI/neuroui.h index f988686..0799c53 100644 --- a/gui/NeuroUI/neuroui.h +++ b/gui/NeuroUI/neuroui.h @@ -28,6 +28,7 @@ private slots: void netLearnerStarted(); void netLearnerFinished(); void progress(double progress); + void setImage(const QImage &image); private: Ui::NeuroUI *ui; diff --git a/gui/NeuroUI/neuroui.ui b/gui/NeuroUI/neuroui.ui index 5f135c9..c10c51f 100644 --- a/gui/NeuroUI/neuroui.ui +++ b/gui/NeuroUI/neuroui.ui @@ -20,11 +20,37 @@ - - - true - - + + + + + true + + + + + + + + 0 + 0 + + + + + 128 + 0 + + + + + + + Qt::AlignCenter + + + +