diff --git a/MLP_MVS/MLP_MVS.sln b/MLP_MVS/MLP_MVS.sln index 0a40f7e..fa5cc84 100644 --- a/MLP_MVS/MLP_MVS.sln +++ b/MLP_MVS/MLP_MVS.sln @@ -13,6 +13,13 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LayerTest", "LayerTest\Laye EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "IrisDatasetTest", "IrisDatasetTest\IrisDatasetTest.vcxproj", "{D58D3DD3-DF71-479D-A8EF-C52308C34C11}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0D44BA4E-E6F3-41E0-811E-5D832620728E}" + ProjectSection(SolutionItems) = preProject + ..\logo.png = ..\logo.png + ..\Makefile = ..\Makefile + ..\README.md = ..\README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 diff --git a/Makefile b/Makefile index 0481e8b..caf8dae 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ OBJS = $(SRCS:.cpp=.o) TXTS = $(wildcard *.txt) SCRIPTS = $(wildcard *.sh) -all : MLPTest LayerTest NodeTest $(PROJNAME).a $(PROJNAME).so +all : IrisDatasetTest MLPTest LayerTest NodeTest $(PROJNAME).a $(PROJNAME).so $(PROJNAME).a : $(SOURCEPATH)/MLP.o @echo Creating static lib $@ @@ -42,6 +42,10 @@ $(PROJNAME).so : $(SOURCEPATH)/MLP.o %.o: %.cpp $(HDRS) $(CC) -c $(CFLAGS) $(LFLAGS) -o $@ $< +IrisDatasetTest: $(SOURCEPATH)/IrisDatasetTest.o $(SOURCEPATH)/MLP.o + @echo Compiling program $@ + $(CC) $^ $(CFLAGS) $(LFLAGS) -o $@ + MLPTest: $(SOURCEPATH)/MLPTest.o $(SOURCEPATH)/MLP.o @echo Compiling program $@ $(CC) $^ $(CFLAGS) $(LFLAGS) -o $@ @@ -60,5 +64,5 @@ clean: cleanall: @echo Clean All - rm -f *~ $(SOURCEPATH)/*.o *~ $(PROJNAME).a $(PROJNAME).so MLPTest LayerTest NodeTest + rm -f *~ $(SOURCEPATH)/*.o *~ $(PROJNAME).a $(PROJNAME).so IrisDatasetTest MLPTest LayerTest NodeTest @echo Success \ No newline at end of file diff --git a/README.md b/README.md index fc2852e..716f4fd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,78 @@ + +MLP logo + # MLP +## About -Simple multilayer perceptron c++ implementation. +MLP stands for [multilayer perceptron](https://en.wikipedia.org/wiki/Multilayer_perceptron). +This project is a simple & fast C++ implementation of a MLP, oriented towards hacking and rapid prototyping. +It is well-tested and includes multiple tests for each component as well as use cases. + +This project is maintained by [David Nogueira](http://web.tecnico.ulisboa.pt/david.jacome.nogueira/). -David Nogueira, 2016.11.01 +## Featuring + +- C++ implementation. +- Modular-oriented, with classes built on top of each other: Node, Layer and network classes. +- Easy to use and to hack. +- Simple, fast and thread-safe. +- Tests for each component module as well as use-case tests. +- Supports saving & loading models. + +## OS Support + +MLP offers support both for Windows (MVS) & Linux (g++/clang++). + +## Tests/Example Code + +Some example programs are included with the source code. + +- [`IrisDatasetTest.cpp`](./src/IrisDatasetTest.cpp) - Using the [IRIS data-set](https://archive.ics.uci.edu/ml/datasets/Iris) trains a MLP using backpropagation and tries to predict the classes. +- [`MLPTest.cpp`](./src/MLPTest.cpp) - Includes tests to train a MLP for AND, NAND, NOR, OR, NOT and XOR using backpropagation. +- [`Node.cpp`](./src/Node.cpp) - Includes tests to train a single node (aka, perceptron) for AND, NAND, NOR, OR, NOT and XOR using backpropagation. (*A simple perceptron cannot learn the XOR function.*) + +## Example + +Let us look at an example. After loading the data and creating the training/dev/test data structures, we will create a MLP with input size 5 (assuming 4 input data features + 1 bias), a hidden layer of 4 neuros and an output layer with 3 outputs (3 possible predicted classes). The activation functions will be a sigmoid for the hidden layer and a linear one for the output layer. + +```cpp +#include "MLP.h" +#include + +// ... +std::vector training_set; +// ... + +// assuming 4 inputs + 1 bias. +// 1 hidden layer(s) of 4 neurons. +// assuming 3 outputs +MLP my_mlp({ 4 + 1, 4 , 3 }, { "sigmoid", "linear" }, false); + +int loops = 5000; +my_mlp.Train(training_set, .01, loops, 0.10, false); + +int correct = 0; +for (int j = 0; j < samples; ++j) { + std::vector guess; + my_mlp.GetOutput(training_set[j].input_vector(), &guess); + size_t class_id; + my_mlp.GetOutputClass(guess, &class_id); + + // Compare class_id with gold class id for each instance +} +``` + +Saving and loading models is also very intuitive: + +```cpp +#include "MLP.h" +{ + //... + my_mlp.SaveMLPNetwork(std::string("../../data/iris.mlp")); //saving +} +{ + MLP my_mlp(std::string("../../data/iris.mlp")); //load a model in constructor + //... +} +``` diff --git a/logo.pdn b/logo.pdn new file mode 100644 index 0000000..8a904d5 Binary files /dev/null and b/logo.pdn differ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..c20c8fe Binary files /dev/null and b/logo.png differ diff --git a/src/IrisDatasetTest.cpp b/src/IrisDatasetTest.cpp index e4c44dd..3fcaa42 100644 --- a/src/IrisDatasetTest.cpp +++ b/src/IrisDatasetTest.cpp @@ -115,19 +115,16 @@ int main(int argc, char *argv[]) { } { - /* 4 inputs + 1 bias. - * 1 hidden layer(s) of 4 neurons. - * 3 outputs (1 per iris_class) - */ + // 4 inputs + 1 bias. + // 1 hidden layer(s) of 4 neurons. + // 3 outputs (1 per iris_class) MLP my_mlp({ 4 + 1, 4 ,3 }, { "sigmoid", "linear" }, false); - int loops = 5000; - // Train the network with backpropagation. LOG(INFO) << "Training for " << loops << " loops over data."; - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, .01, loops, 0.10, false); + my_mlp.Train(training_sample_set_with_bias, .01, loops, 0.10, false); my_mlp.SaveMLPNetwork(std::string("../../data/iris.mlp")); } diff --git a/src/MLP.cpp b/src/MLP.cpp index 70cdc47..558ca44 100644 --- a/src/MLP.cpp +++ b/src/MLP.cpp @@ -148,7 +148,7 @@ void MLP::UpdateWeights(const std::vector> & all_layers_acti } }; -void MLP::UpdateMiniBatch(const std::vector &training_sample_set_with_bias, +void MLP::Train(const std::vector &training_sample_set_with_bias, double learning_rate, int max_iterations, double min_error_cost, diff --git a/src/MLP.h b/src/MLP.h index 28fe8d9..783cc05 100644 --- a/src/MLP.h +++ b/src/MLP.h @@ -35,7 +35,7 @@ public: std::vector> * all_layers_activations = nullptr) const; void GetOutputClass(const std::vector &output, size_t * class_id) const; - void UpdateMiniBatch(const std::vector &training_sample_set_with_bias, + void Train(const std::vector &training_sample_set_with_bias, double learning_rate, int max_iterations = 5000, double min_error_cost = 0.001, diff --git a/src/MLPTest.cpp b/src/MLPTest.cpp index 397633e..bec06d2 100644 --- a/src/MLPTest.cpp +++ b/src/MLPTest.cpp @@ -41,7 +41,7 @@ UNIT(LearnAND) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -81,7 +81,7 @@ UNIT(LearnNAND) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -121,7 +121,7 @@ UNIT(LearnOR) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -161,7 +161,7 @@ UNIT(LearnNOR) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -199,7 +199,7 @@ UNIT(LearnXOR) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -235,7 +235,7 @@ UNIT(LearnNOT) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -273,7 +273,7 @@ UNIT(LearnX1) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output; @@ -311,7 +311,7 @@ UNIT(LearnX2) { size_t num_outputs = training_sample_set_with_bias[0].GetOutputVectorSize(); MLP my_mlp({ num_features, 2 ,num_outputs }, { "sigmoid", "linear" }); //Train MLP - my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 0.5, 500, 0.25); + my_mlp.Train(training_sample_set_with_bias, 0.5, 500, 0.25); for (const auto & training_sample : training_sample_set_with_bias) { std::vector output;