From ff7bfe1fa249a49b447993ce54d7ba51ddad4407 Mon Sep 17 00:00:00 2001 From: davidjacnogueira Date: Wed, 2 Nov 2016 01:04:34 +0000 Subject: [PATCH] Almost complete. Works with zero hidden layers, but not with one or more. Correctness in the backprop phase must be checked. --- .gitignore | 1 + MLP_MVS/LayerTest/LayerTest.vcxproj | 162 ++++++++++++ MLP_MVS/LayerTest/LayerTest.vcxproj.filters | 22 ++ MLP_MVS/MLPTest/MLPTest.vcxproj | 162 ++++++++++++ MLP_MVS/MLPTest/MLPTest.vcxproj.filters | 22 ++ MLP_MVS/MLP_MVS.sln | 34 ++- MLP_MVS/MLP_MVS.vcxproj | 34 ++- MLP_MVS/MLP_MVS.vcxproj.filters | 6 +- MLP_MVS/NodeTest/NodeTest.vcxproj | 157 ++++++++++++ MLP_MVS/NodeTest/NodeTest.vcxproj.filters | 22 ++ src/Chrono.h | 106 ++++++++ src/Layer.h | 67 ++++- src/LayerTest.cpp | 22 ++ src/MLP.cpp | 183 ++++++++++++++ src/MLP.h | 76 +++--- src/MLPTest.cpp | 232 +++++++++++++++++ src/Main.cpp | 172 ------------- src/Node.h | 89 ++++--- src/NodeTest.cpp | 262 ++++++++++++++++++++ src/Sample.h | 39 ++- src/Utils.h | 30 +-- 21 files changed, 1615 insertions(+), 285 deletions(-) create mode 100644 MLP_MVS/LayerTest/LayerTest.vcxproj create mode 100644 MLP_MVS/LayerTest/LayerTest.vcxproj.filters create mode 100644 MLP_MVS/MLPTest/MLPTest.vcxproj create mode 100644 MLP_MVS/MLPTest/MLPTest.vcxproj.filters create mode 100644 MLP_MVS/NodeTest/NodeTest.vcxproj create mode 100644 MLP_MVS/NodeTest/NodeTest.vcxproj.filters create mode 100644 src/Chrono.h create mode 100644 src/LayerTest.cpp create mode 100644 src/MLPTest.cpp delete mode 100644 src/Main.cpp create mode 100644 src/NodeTest.cpp diff --git a/.gitignore b/.gitignore index 6941a19..9a92385 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ ipch/ *.opensdf *.sdf *.cachefile +*.db # Visual Studio profiler *.psess diff --git a/MLP_MVS/LayerTest/LayerTest.vcxproj b/MLP_MVS/LayerTest/LayerTest.vcxproj new file mode 100644 index 0000000..d860ebc --- /dev/null +++ b/MLP_MVS/LayerTest/LayerTest.vcxproj @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {10A8D77B-A596-4B06-87DA-B28492D77905} + Win32Proj + LayerTest + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\deps + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\deps + + + Console + true + true + true + + + + + true + false + true + false + + + + + {6bfa9d94-b136-4985-83a1-ee76fff6f374} + + + + + + \ No newline at end of file diff --git a/MLP_MVS/LayerTest/LayerTest.vcxproj.filters b/MLP_MVS/LayerTest/LayerTest.vcxproj.filters new file mode 100644 index 0000000..4185f8d --- /dev/null +++ b/MLP_MVS/LayerTest/LayerTest.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/MLP_MVS/MLPTest/MLPTest.vcxproj b/MLP_MVS/MLPTest/MLPTest.vcxproj new file mode 100644 index 0000000..7fa8ce3 --- /dev/null +++ b/MLP_MVS/MLPTest/MLPTest.vcxproj @@ -0,0 +1,162 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {BCC41AEC-201D-4861-A756-2E10F95F76C5} + Win32Proj + MLPTest + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\deps + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\deps + + + Console + true + true + true + + + + + true + false + true + false + + + + + {6bfa9d94-b136-4985-83a1-ee76fff6f374} + + + + + + \ No newline at end of file diff --git a/MLP_MVS/MLPTest/MLPTest.vcxproj.filters b/MLP_MVS/MLPTest/MLPTest.vcxproj.filters new file mode 100644 index 0000000..7427754 --- /dev/null +++ b/MLP_MVS/MLPTest/MLPTest.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/MLP_MVS/MLP_MVS.sln b/MLP_MVS/MLP_MVS.sln index 59a6e7f..d87ebd5 100644 --- a/MLP_MVS/MLP_MVS.sln +++ b/MLP_MVS/MLP_MVS.sln @@ -1,9 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MLP_MVS", "MLP_MVS.vcxproj", "{6BFA9D94-B136-4985-83A1-EE76FFF6F374}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MLP_lib", "MLP_MVS.vcxproj", "{6BFA9D94-B136-4985-83A1-EE76FFF6F374}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NodeTest", "NodeTest\NodeTest.vcxproj", "{7CAD640F-63A0-4065-A86B-E65143A8329C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MLPTest", "MLPTest\MLPTest.vcxproj", "{BCC41AEC-201D-4861-A756-2E10F95F76C5}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LayerTest", "LayerTest\LayerTest.vcxproj", "{10A8D77B-A596-4B06-87DA-B28492D77905}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +27,30 @@ Global {6BFA9D94-B136-4985-83A1-EE76FFF6F374}.Release|x64.Build.0 = Release|x64 {6BFA9D94-B136-4985-83A1-EE76FFF6F374}.Release|x86.ActiveCfg = Release|Win32 {6BFA9D94-B136-4985-83A1-EE76FFF6F374}.Release|x86.Build.0 = Release|Win32 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Debug|x64.ActiveCfg = Debug|x64 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Debug|x64.Build.0 = Debug|x64 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Debug|x86.ActiveCfg = Debug|Win32 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Debug|x86.Build.0 = Debug|Win32 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Release|x64.ActiveCfg = Release|x64 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Release|x64.Build.0 = Release|x64 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Release|x86.ActiveCfg = Release|Win32 + {7CAD640F-63A0-4065-A86B-E65143A8329C}.Release|x86.Build.0 = Release|Win32 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Debug|x64.ActiveCfg = Debug|x64 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Debug|x64.Build.0 = Debug|x64 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Debug|x86.ActiveCfg = Debug|Win32 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Debug|x86.Build.0 = Debug|Win32 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Release|x64.ActiveCfg = Release|x64 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Release|x64.Build.0 = Release|x64 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Release|x86.ActiveCfg = Release|Win32 + {BCC41AEC-201D-4861-A756-2E10F95F76C5}.Release|x86.Build.0 = Release|Win32 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Debug|x64.ActiveCfg = Debug|x64 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Debug|x64.Build.0 = Debug|x64 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Debug|x86.ActiveCfg = Debug|Win32 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Debug|x86.Build.0 = Debug|Win32 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Release|x64.ActiveCfg = Release|x64 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Release|x64.Build.0 = Release|x64 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Release|x86.ActiveCfg = Release|Win32 + {10A8D77B-A596-4B06-87DA-B28492D77905}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MLP_MVS/MLP_MVS.vcxproj b/MLP_MVS/MLP_MVS.vcxproj index 2ab1ae2..dc3d832 100644 --- a/MLP_MVS/MLP_MVS.vcxproj +++ b/MLP_MVS/MLP_MVS.vcxproj @@ -23,29 +23,30 @@ Win32Proj MLP_MVS 8.1 + MLP_lib - Application + StaticLibrary true v140 Unicode - Application + StaticLibrary false v140 true Unicode - Application + StaticLibrary true v140 Unicode - Application + StaticLibrary false v140 true @@ -74,12 +75,14 @@ true + MLP false false + MLP @@ -149,15 +152,30 @@ - - + + + false + false + false + false + + + false + false + false + false + - - + + false + false + false + false + diff --git a/MLP_MVS/MLP_MVS.vcxproj.filters b/MLP_MVS/MLP_MVS.vcxproj.filters index f11f046..1289b83 100644 --- a/MLP_MVS/MLP_MVS.vcxproj.filters +++ b/MLP_MVS/MLP_MVS.vcxproj.filters @@ -33,11 +33,11 @@ Header Files + + Header Files + - - Source Files - Source Files diff --git a/MLP_MVS/NodeTest/NodeTest.vcxproj b/MLP_MVS/NodeTest/NodeTest.vcxproj new file mode 100644 index 0000000..12f4808 --- /dev/null +++ b/MLP_MVS/NodeTest/NodeTest.vcxproj @@ -0,0 +1,157 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {7CAD640F-63A0-4065-A86B-E65143A8329C} + Win32Proj + NodeTest + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\deps + + + Console + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(SolutionDir)..\deps + + + Console + true + true + true + + + + + + + + {6bfa9d94-b136-4985-83a1-ee76fff6f374} + + + + + + \ No newline at end of file diff --git a/MLP_MVS/NodeTest/NodeTest.vcxproj.filters b/MLP_MVS/NodeTest/NodeTest.vcxproj.filters new file mode 100644 index 0000000..0974aef --- /dev/null +++ b/MLP_MVS/NodeTest/NodeTest.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/src/Chrono.h b/src/Chrono.h new file mode 100644 index 0000000..eaeec74 --- /dev/null +++ b/src/Chrono.h @@ -0,0 +1,106 @@ +/** +* @file chrono.h +* @author David Alberto Nogueira (dan) +* @brief std::chrono wrapper. +* +* USAGE: +* @code{.cpp} +* chronowrap::Chronometer chrono; //Declare a Chronometer +* chrono.GetTime(); //Start timer +* { +* ... //do your code +* } +* chrono.StopTime(); //Stop timer +* std::cout << "Time: " << chrono.GetElapsedTime() +* << " sec." << std::endl; //Print duration + +* @endcode +* +* @copyright Copyright (c) 2016, David Alberto Nogueira. +* All rights reserved. See licence below. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are +* met: +* +* (1) Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* (2) Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* (3) The name of the author may not be used to +* endorse or promote products derived from this software without +* specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef CHRONO_H +#define CHRONO_H +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace chronowrap { +class Chronometer { +public: + Chronometer() { + time_span = std::chrono::steady_clock::duration::zero(); + }; + virtual ~Chronometer() {}; + + void GetTime() { + clock_begin = std::chrono::steady_clock::now(); + } + void StopTime() { + std::chrono::steady_clock::time_point clock_end = + std::chrono::steady_clock::now(); + time_span += clock_end - clock_begin; + } + //Return elapsed time in seconds + double GetElapsedTime() { + return double(time_span.count()) * resolution; + } + void Reset() { + time_span = std::chrono::steady_clock::duration::zero(); + } + //in us + double GetClockResolutionUS() { + return resolution*1e6; + } + void PrintClockResolution() { + std::cout << "clock::period: " << GetClockResolutionUS() << " us.\n"; + } + bool IsClockSteady() { + return std::chrono::steady_clock::is_steady; + } + void PrintClockSteady() { + printf("clock::is_steady: %s\n", IsClockSteady() ? "yes" : "no"); + } + +protected: + std::chrono::steady_clock::time_point clock_begin; + std::chrono::steady_clock::duration time_span; + const double resolution = double(std::chrono::steady_clock::period::num) / + double(std::chrono::steady_clock::period::den); +}; +} + +#endif // CHRONO_H diff --git a/src/Layer.h b/src/Layer.h index 3f2de89..97fe2a2 100644 --- a/src/Layer.h +++ b/src/Layer.h @@ -23,39 +23,80 @@ public: m_nodes.clear(); }; - - Layer(int num_nodes, int num_inputs_per_node) { + Layer(int num_nodes, + int num_inputs_per_node, + bool use_constant_weight_init = true, + double constant_weight_init = 0.5) { m_num_nodes = num_nodes; m_num_inputs_per_node = num_inputs_per_node; - m_nodes = std::vector(num_nodes, Node(num_inputs_per_node)); + m_nodes.resize(num_nodes, + std::move(Node(num_inputs_per_node, + use_constant_weight_init, + constant_weight_init))); }; + ~Layer() { + m_num_nodes = 0; + m_num_inputs_per_node = 0; m_nodes.clear(); }; - void GetOutput(const std::vector &input, std::vector * output) const { + //std::vector & GetNodes() { + // return m_nodes; + //} + + const std::vector & GetNodes() const { + return m_nodes; + } + + void GetOutputAfterSigmoid(const std::vector &input, std::vector * output) const { assert(input.size() == m_num_inputs_per_node); - + output->resize(m_num_nodes); for (int i = 0; i < m_num_nodes; ++i) { - (*output)[i] = m_nodes[i].GetOutput(input); + m_nodes[i].GetOutputAfterSigmoid(input, &((*output)[i])); } } - void UpdateWeights(const std::vector &x, + void UpdateWeights(const std::vector &input_layer_activation, + const std::vector &deriv_error, double m_learning_rate, - double error) { - assert(x.size() == m_num_inputs_per_node); + std::vector * deltas) { + assert(input_layer_activation.size() == m_num_inputs_per_node); + assert(deriv_error.size() == m_nodes.size()); - for (size_t i = 0; i < m_nodes.size(); i++) - m_nodes[i].UpdateWeights(x, m_learning_rate, error); + deltas->resize(m_num_inputs_per_node, 0); + + for (size_t i = 0; i < m_nodes.size(); i++) { + double net_sum; + m_nodes[i].GetInputInnerProdWithWeights(input_layer_activation, &net_sum); + + //dE/dwij = dE/doj . doj/dnetj . dnetj/dwij + double dE_doj = 0.0; + double doj_dnetj = 0.0; + double dnetj_dwij = 0.0; + + dE_doj = deriv_error[i]; + doj_dnetj = utils::deriv_sigmoid(net_sum); + + + for (int j = 0; j < m_num_inputs_per_node; j++) { + (*deltas)[j] += dE_doj * doj_dnetj * m_nodes[i].GetWeights()[j]; + + dnetj_dwij = input_layer_activation[j]; + + m_nodes[i].UpdateWeight(j, + -(dE_doj * doj_dnetj * dnetj_dwij), + m_learning_rate); + } + } }; protected: - int m_num_nodes; - int m_num_inputs_per_node; + int m_num_nodes{ 0 }; + int m_num_inputs_per_node{ 0 }; std::vector m_nodes; }; diff --git a/src/LayerTest.cpp b/src/LayerTest.cpp new file mode 100644 index 0000000..36b4301 --- /dev/null +++ b/src/LayerTest.cpp @@ -0,0 +1,22 @@ +//============================================================================ +// Name : LayerTest.cpp +// Author : David Nogueira +//============================================================================ + +#include "Layer.h" +#include "Sample.h" +#include "Utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include "microunit.h" + +int main() { + microunit::UnitTester::Run(); + return 0; +} \ No newline at end of file diff --git a/src/MLP.cpp b/src/MLP.cpp index b158b40..3f46d6b 100644 --- a/src/MLP.cpp +++ b/src/MLP.cpp @@ -11,4 +11,187 @@ #include #include +bool MLP::ExportNNWeights(std::vector *weights) const { + return true; +}; +bool MLP::ImportNNWeights(const std::vector & weights) { + return true; +}; + +void MLP::GetOutput(const std::vector &input, + std::vector * output, + std::vector> * all_layers_activations, + bool apply_softmax) const { + assert(input.size() == m_num_inputs); + int temp_size; + if (m_num_hidden_layers == 0) + temp_size = m_num_outputs; + else + temp_size = m_num_nodes_per_hidden_layer; + + std::vector temp_in(m_num_inputs, 0.0); + std::vector temp_out(temp_size, 0.0); + temp_in = input; + + //m_layers.size() equals (m_num_hidden_layers + 1) + for (int i = 0; i < (m_num_hidden_layers + 1); ++i) { + if (i > 0) { + //Store this layer activation + if (all_layers_activations != nullptr) + all_layers_activations->emplace_back(std::move(temp_in)); + + temp_in.clear(); + temp_in = temp_out; + temp_out.clear(); + temp_out.resize((i == m_num_hidden_layers) ? + m_num_outputs : + m_num_nodes_per_hidden_layer); + } + m_layers[i].GetOutputAfterSigmoid(temp_in, &temp_out); + } + + if (apply_softmax && temp_out.size() > 1) + utils::Softmax(&temp_out); + *output = temp_out; + + //Add last layer activation + if (all_layers_activations != nullptr) + all_layers_activations->emplace_back(std::move(temp_in)); +} + +void MLP::GetOutputClass(const std::vector &output, size_t * class_id) const { + utils::GetIdMaxElement(output, class_id); +} + +void MLP::UpdateWeights(const std::vector> & all_layers_activations, + const std::vector &deriv_error, + double learning_rate) { + + std::vector temp_deriv_error = deriv_error; + std::vector deltas{}; + //m_layers.size() equals (m_num_hidden_layers + 1) + for (int i = m_num_hidden_layers; i >= 0; --i) { + m_layers[i].UpdateWeights(all_layers_activations[i], temp_deriv_error, learning_rate, &deltas); + if (i > 0) { + temp_deriv_error.clear(); + temp_deriv_error = std::move(deltas); + deltas.clear(); + } + } +}; + +void MLP::UpdateMiniBatch(const std::vector &training_sample_set_with_bias, + double learning_rate, + int max_iterations, + double min_error_cost) { + int num_examples = training_sample_set_with_bias.size(); + int num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + + { + int layer_i = -1; + int node_i = -1; + std::cout << "Starting weights:" << std::endl; + for (const auto & layer : m_layers) { + layer_i++; + node_i = -1; + std::cout << "Layer " << layer_i << " :" << std::endl; + for (const auto & node : layer.GetNodes()) { + node_i++; + std::cout << "\tNode " << node_i << " :\t"; + for (auto m_weightselement : node.GetWeights()) { + std::cout << m_weightselement << "\t"; + } + std::cout << std::endl; + } + } + } + + for (int i = 0; i < max_iterations; i++) { + std::cout << "******************************" << std::endl; + std::cout << "******** ITER " << i << std::endl; + std::cout << "******************************" << std::endl; + double current_iteration_cost_function = 0.0; + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + std::vector predicted_output; + std::vector< std::vector > all_layers_activations; + GetOutput(training_sample_with_bias.input_vector(), + &predicted_output, + &all_layers_activations); + const std::vector & correct_output = + training_sample_with_bias.output_vector(); + + assert(correct_output.size() == predicted_output.size()); + std::vector deriv_error_output(predicted_output.size()); + + std::cout << training_sample_with_bias << "\t\t"; + { + std::cout << "Predicted output: ["; + for (int i = 0; i < predicted_output.size(); i++) { + if (i != 0) + std::cout << ", "; + std::cout << predicted_output[i]; + } + std::cout << "]" << std::endl; + } + + for (int j = 0; j < predicted_output.size(); j++) { + current_iteration_cost_function += + (std::pow)((correct_output[j] - predicted_output[j]), 2); + deriv_error_output[j] = + -2 * (correct_output[j] - predicted_output[j]); + } + + UpdateWeights(all_layers_activations, + deriv_error_output, + learning_rate); + } + + std::cout << "Iteration cost function f(error): " + << current_iteration_cost_function << std::endl; + if (current_iteration_cost_function < min_error_cost) + break; + + //{ + // int layer_i = -1; + // int node_i = -1; + // std::cout << "Current weights:" << std::endl; + // for (const auto & layer : m_layers) { + // layer_i++; + // node_i = -1; + // std::cout << "Layer " << layer_i << " :" << std::endl; + // for (const auto & node : layer.GetNodes()) { + // node_i++; + // std::cout << "\tNode " << node_i << " :\t"; + // for (auto m_weightselement : node.GetWeights()) { + // std::cout << m_weightselement << "\t"; + // } + // std::cout << std::endl; + // } + // } + //} + } + + std::cout << "******************************" << std::endl; + std::cout << "******* TRAINING ENDED *******" << std::endl; + std::cout << "******************************" << std::endl; + { + int layer_i = -1; + int node_i = -1; + std::cout << "Final weights:" << std::endl; + for (const auto & layer : m_layers) { + layer_i++; + node_i = -1; + std::cout << "Layer " << layer_i << " :" << std::endl; + for (const auto & node : layer.GetNodes()) { + node_i++; + std::cout << "\tNode " << node_i << " :\t"; + for (auto m_weightselement : node.GetWeights()) { + std::cout << m_weightselement << "\t"; + } + std::cout << std::endl; + } + } + } +}; + diff --git a/src/MLP.h b/src/MLP.h index c0060cc..849aa97 100644 --- a/src/MLP.h +++ b/src/MLP.h @@ -23,57 +23,77 @@ public: int num_outputs, int num_hidden_layers, int num_nodes_per_hidden_layer, - double learning_rate) { + bool use_constant_weight_init = true, + double constant_weight_init = 0.5) { m_num_inputs = num_inputs; m_num_outputs = num_outputs; m_num_hidden_layers = num_hidden_layers; m_num_nodes_per_hidden_layer = num_nodes_per_hidden_layer; - m_learning_rate = learning_rate; - }; + CreateMLP(use_constant_weight_init, + constant_weight_init); + } ~MLP() { + m_num_inputs = 0; + m_num_outputs = 0; + m_num_hidden_layers = 0; + m_num_nodes_per_hidden_layer = 0; m_layers.clear(); }; - void CreateMLP() { + bool ExportNNWeights(std::vector *weights)const; + bool ImportNNWeights(const std::vector & weights); + + void GetOutput(const std::vector &input, + std::vector * output, + std::vector> * all_layers_activations = nullptr, + bool apply_softmax = false) const; + void GetOutputClass(const std::vector &output, size_t * class_id) const; + + void UpdateMiniBatch(const std::vector &training_sample_set_with_bias, + double learning_rate, + int max_iterations = 5000, + double min_error_cost = 0.001); +protected: + void UpdateWeights(const std::vector> & all_layers_activations, + const std::vector &error, + double learning_rate); +private: + void CreateMLP(bool use_constant_weight_init, + double constant_weight_init = 0.5) { if (m_num_hidden_layers > 0) { //first layer - m_layers.emplace_back(Layer(m_num_nodes_per_hidden_layer, m_num_inputs)); + m_layers.emplace_back(Layer(m_num_nodes_per_hidden_layer, + m_num_inputs, + use_constant_weight_init, + constant_weight_init)); //subsequent layers for (int i = 0; i < m_num_hidden_layers - 1; i++) { m_layers.emplace_back(Layer(m_num_nodes_per_hidden_layer, - m_num_nodes_per_hidden_layer)); + m_num_nodes_per_hidden_layer, + use_constant_weight_init, + constant_weight_init)); } //last layer - m_layers.emplace_back(Layer(m_num_outputs, m_num_nodes_per_hidden_layer)); + m_layers.emplace_back(Layer(m_num_outputs, + m_num_nodes_per_hidden_layer, + use_constant_weight_init, + constant_weight_init)); } else { - m_layers.emplace_back(Layer(m_num_outputs, m_num_inputs)); + m_layers.emplace_back(Layer(m_num_outputs, + m_num_inputs, + use_constant_weight_init, + constant_weight_init)); } } - size_t GetWeightMatrixCardinality()const; - bool ExportWeights(std::vector *weights)const; - bool ImportWeights(const std::vector & weights); - void GetOutput(const std::vector &input, std::vector * output) const; - void GetOutputClass(const std::vector &output, size_t * class_id) const; - - void Train(const std::vector &training_sample_set, - int max_iterations); -protected: - void UpdateWeights(const std::vector &x, - double error); -private: - - int m_num_inputs; - int m_num_outputs; - int m_num_hidden_layers; - int m_num_nodes_per_hidden_layer; - - double m_learning_rate; - int m_max_iterations; + int m_num_inputs{ 0 }; + int m_num_outputs{ 0 }; + int m_num_hidden_layers{ 0 }; + int m_num_nodes_per_hidden_layer{ 0 }; std::vector m_layers; }; diff --git a/src/MLPTest.cpp b/src/MLPTest.cpp new file mode 100644 index 0000000..bb6ca41 --- /dev/null +++ b/src/MLPTest.cpp @@ -0,0 +1,232 @@ +//============================================================================ +// Name : Main.cpp +// Author : David Nogueira +//============================================================================ +#include "MLP.h" +#include +#include +#include +#include +#include +#include +#include +#include "microunit.h" + +UNIT(LearnAND) { + std::cout << "Train AND function with mlp." << std::endl; + + std::vector training_set = + { + {{ 0, 0 },{0.0}}, + {{ 0, 1 },{0.0}}, + {{ 1, 0 },{0.0}}, + {{ 1, 1 },{1.0}} + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + MLP my_mlp(num_features, 1, 0, 5, true, 0.5); + //Train MLP + my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 2, 1000, 0.245); + + for (const auto & training_sample : training_sample_set_with_bias) { + std::vector output; + my_mlp.GetOutput(training_sample.input_vector(), &output); + bool predicted_output = output[0]> 0.5 ? true : false; + bool correct_output = training_sample.output_vector()[0] > 0.5 ? true : false; + ASSERT_TRUE(predicted_output == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnNAND) { + std::cout << "Train NAND function with mlp." << std::endl; + + std::vector training_set = + { + {{ 0, 0 },{1.0}}, + {{ 0, 1 },{1.0}}, + {{ 1, 0 },{1.0}}, + {{ 1, 1 },{0.0}} + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + MLP my_mlp(num_features, 1, 0, 5, true, 0.5); + //Train MLP + my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 2, 1000, 0.245); + + for (const auto & training_sample : training_sample_set_with_bias) { + std::vector output; + my_mlp.GetOutput(training_sample.input_vector(), &output); + bool predicted_output = output[0]> 0.5 ? true : false; + bool correct_output = training_sample.output_vector()[0] > 0.5 ? true : false; + ASSERT_TRUE(predicted_output == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnOR) { + std::cout << "Train OR function with mlp." << std::endl; + + std::vector training_set = + { + {{ 0, 0 },{0.0}}, + {{ 0, 1 },{1.0}}, + {{ 1, 0 },{1.0}}, + {{ 1, 1 },{1.0}} + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + MLP my_mlp(num_features, 1, 0, 5, true, 0.5); + //Train MLP + my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 2, 1000, 0.245); + + for (const auto & training_sample : training_sample_set_with_bias) { + std::vector output; + my_mlp.GetOutput(training_sample.input_vector(), &output); + bool predicted_output = output[0]> 0.5 ? true : false; + bool correct_output = training_sample.output_vector()[0] > 0.5 ? true : false; + ASSERT_TRUE(predicted_output == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnNOR) { + std::cout << "Train NOR function with mlp." << std::endl; + + std::vector training_set = + { + {{ 0, 0 },{1.0}}, + {{ 0, 1 },{0.0}}, + {{ 1, 0 },{0.0}}, + {{ 1, 1 },{0.0}} + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + MLP my_mlp(num_features, 1, 0, 5, true, 0.5); + //Train MLP + my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 2, 1000, 0.245); + + for (const auto & training_sample : training_sample_set_with_bias) { + std::vector output; + my_mlp.GetOutput(training_sample.input_vector(), &output); + bool predicted_output = output[0]> 0.5 ? true : false; + bool correct_output = training_sample.output_vector()[0] > 0.5 ? true : false; + ASSERT_TRUE(predicted_output == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +//UNIT(LearnXOR) { +// std::cout << "Train XOR function with mlp." << std::endl; +// +// std::vector training_set = +// { +// { { 0, 0 },{ 0.0 } }, +// { { 0, 1 },{ 1.0 } }, +// { { 1, 0 },{ 1.0 } }, +// { { 1, 1 },{ 0.0 } } +// }; +// bool bias_already_in = false; +// std::vector training_sample_set_with_bias(training_set); +// //set up bias +// if (!bias_already_in) { +// for (auto & training_sample_with_bias : training_sample_set_with_bias) { +// training_sample_with_bias.AddBiasValue(1); +// } +// } +// +// size_t num_examples = training_sample_set_with_bias.size(); +// size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); +// MLP my_mlp(num_features, 1, 0, 5, true, 0.5); +// //Train MLP +// my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 2, 1000, 0.245); +// +// for (const auto & training_sample : training_sample_set_with_bias) { +// std::vector output; +// my_mlp.GetOutput(training_sample.input_vector(), &output); +// bool predicted_output = output[0]> 0.5 ? true : false; +// bool correct_output = training_sample.output_vector()[0] > 0.5 ? true : false; +// ASSERT_TRUE(predicted_output == correct_output); +// } +// std::cout << "Trained with success." << std::endl; +// std::cout << std::endl; +//} + +UNIT(LearnNOT) { + std::cout << "Train NOT function with mlp." << std::endl; + + std::vector training_set = + { + {{ 0},{1.0 }}, + {{ 1},{0.0 }} + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + MLP my_mlp(num_features, 1, 0, 5, true, 0.5); + //Train MLP + my_mlp.UpdateMiniBatch(training_sample_set_with_bias, 2, 1000, 0.245); + + for (const auto & training_sample : training_sample_set_with_bias) { + std::vector output; + my_mlp.GetOutput(training_sample.input_vector(), &output); + bool predicted_output = output[0]> 0.5 ? true : false; + bool correct_output = training_sample.output_vector()[0] > 0.5 ? true : false; + ASSERT_TRUE(predicted_output == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +int main() { + microunit::UnitTester::Run(); + return 0; +} \ No newline at end of file diff --git a/src/Main.cpp b/src/Main.cpp deleted file mode 100644 index 24a6505..0000000 --- a/src/Main.cpp +++ /dev/null @@ -1,172 +0,0 @@ -//============================================================================ -// Name : Main.cpp -// Author : David Nogueira -//============================================================================ -#include "MLP.h" -#include -#include -#include -#include -#include -#include -#include -#include "microunit.h" - -UNIT(LearnAND) { - std::cout << "Train AND function with mlp." << std::endl; - - std::vector training_set = - { - {{ 0, 0 },{1,0}}, - {{ 0, 1 },{1,0}}, - {{ 1, 0 },{1,0}}, - {{ 1, 1 },{0,1}} - }; - - MLP my_mlp(2, 2, 1, 5, 0.1); - my_mlp.Train(training_set, 100); - - for (const auto & training_sample : training_set){ - size_t class_id; - my_mlp.GetOutputClass(training_sample.input_vector(), &class_id); - ASSERT_TRUE(class_id == - std::distance(training_sample.output_vector().begin(), - std::max_element(training_sample.output_vector().begin(), - training_sample.output_vector().end()) )); - } - std::cout << "Trained with success." << std::endl; - std::cout << std::endl; -} - -UNIT(LearnNAND) { - std::cout << "Train NAND function with mlp." << std::endl; - - std::vector training_set = - { - {{ 0, 0 },{0,1}}, - {{ 0, 1 },{0,1}}, - {{ 1, 0 },{0,1}}, - {{ 1, 1 },{1,0}} - }; - - MLP my_mlp(2, 2, 1, 5, 0.1); - my_mlp.Train(training_set, 100); - - for (const auto & training_sample : training_set) { - size_t class_id; - my_mlp.GetOutputClass(training_sample.input_vector(), &class_id); - ASSERT_TRUE(class_id == - std::distance(training_sample.output_vector().begin(), - std::max_element(training_sample.output_vector().begin(), - training_sample.output_vector().end()))); - } - std::cout << "Trained with success." << std::endl; - std::cout << std::endl; -} - -UNIT(LearnOR) { - std::cout << "Train OR function with mlp." << std::endl; - - std::vector training_set = - { - {{ 0, 0 },{1,0}}, - {{ 0, 1 },{0,1}}, - {{ 1, 0 },{0,1}}, - {{ 1, 1 },{0,1}} - }; - - MLP my_mlp(2, 2, 1, 5, 0.1); - my_mlp.Train(training_set, 100); - - for (const auto & training_sample : training_set) { - size_t class_id; - my_mlp.GetOutputClass(training_sample.input_vector(), &class_id); - ASSERT_TRUE(class_id == - std::distance(training_sample.output_vector().begin(), - std::max_element(training_sample.output_vector().begin(), - training_sample.output_vector().end()))); - } - std::cout << "Trained with success." << std::endl; - std::cout << std::endl; -} - -UNIT(LearnNOR) { - std::cout << "Train NOR function with mlp." << std::endl; - - std::vector training_set = - { - {{ 0, 0 },{0,1}}, - {{ 0, 1 },{1,0}}, - {{ 1, 0 },{1,0}}, - {{ 1, 1 },{1,0}} - }; - - MLP my_mlp(2, 2, 1, 5, 0.1); - my_mlp.Train(training_set, 100); - - for (const auto & training_sample : training_set) { - size_t class_id; - my_mlp.GetOutputClass(training_sample.input_vector(), &class_id); - ASSERT_TRUE(class_id == - std::distance(training_sample.output_vector().begin(), - std::max_element(training_sample.output_vector().begin(), - training_sample.output_vector().end()))); - } - std::cout << "Trained with success." << std::endl; - std::cout << std::endl; -} - -UNIT(LearnXOR) { - std::cout << "Train XOR function with mlp." << std::endl; - - std::vector training_set = - { - { { 0, 0 },{ 1,0 } }, - { { 0, 1 },{ 0,1 } }, - { { 1, 0 },{ 0,1 } }, - { { 1, 1 },{ 1,0 } } - }; - - MLP my_mlp(2, 2, 1, 5, 0.1); - my_mlp.Train(training_set, 100); - - for (const auto & training_sample : training_set) { - size_t class_id; - my_mlp.GetOutputClass(training_sample.input_vector(), &class_id); - ASSERT_TRUE(class_id == - std::distance(training_sample.output_vector().begin(), - std::max_element(training_sample.output_vector().begin(), - training_sample.output_vector().end()))); - } - std::cout << "Trained with success." << std::endl; - std::cout << std::endl; -} - -UNIT(LearnNOT) { - std::cout << "Train NOT function with mlp." << std::endl; - - std::vector training_set = - { - {{ 0},{0,1}}, - {{ 1},{1,1}} - }; - - MLP my_mlp(1, 2, 1, 5, 0.1); - my_mlp.Train(training_set, 100); - - for (const auto & training_sample : training_set) { - size_t class_id; - my_mlp.GetOutputClass(training_sample.input_vector(), &class_id); - ASSERT_TRUE(class_id == - std::distance(training_sample.output_vector().begin(), - std::max_element(training_sample.output_vector().begin(), - training_sample.output_vector().end()))); - } - std::cout << "Trained with success." << std::endl; - std::cout << std::endl; -} - -int main() { - microunit::UnitTester::Run(); - return 0; -} \ No newline at end of file diff --git a/src/Node.h b/src/Node.h index e21e0d5..e98707e 100644 --- a/src/Node.h +++ b/src/Node.h @@ -5,6 +5,8 @@ #ifndef NODE_H #define NODE_H +#include "Utils.h" + #include #include #include @@ -14,42 +16,58 @@ #include #include // for assert() -#define ZERO_WEIGHT_INITIALIZATION 1 -#define USE_SIGMOID 1 +#define CONSTANT_WEIGHT_INITIALIZATION 0 class Node { public: Node() { - m_bias = 0.0; m_num_inputs = 0; - m_weights.clear(); - }; - Node(int num_inputs) { m_bias = 0.0; - m_num_inputs = num_inputs + 1; m_weights.clear(); - m_weights = std::vector(m_num_inputs); - + }; + Node(int num_inputs, + bool use_constant_weight_init = true, + double constant_weight_init = 0.5) { + m_num_inputs = num_inputs; + m_bias = 0.0; + m_weights.clear(); //initialize weight vector - std::generate_n(m_weights.begin(), - m_num_inputs, - (ZERO_WEIGHT_INITIALIZATION) ? - utils::gen_rand(0) : utils::gen_rand()); + WeightInitialization(m_num_inputs, + use_constant_weight_init, + constant_weight_init); }; + ~Node() { + m_num_inputs = 0; + m_bias = 0.0; m_weights.clear(); - //m_old_weights.clear(); }; + + void WeightInitialization(int m_num_inputs, + bool use_constant_weight_init = true, + double constant_weight_init = 0.5) { + //initialize weight vector + if (use_constant_weight_init) { + m_weights.resize(m_num_inputs, constant_weight_init); + } else { + m_weights.resize(m_num_inputs); + std::generate_n(m_weights.begin(), + m_num_inputs, + utils::gen_rand()); + } + } + int GetInputSize() const { return m_num_inputs; } + void SetInputSize(int num_inputs) { m_num_inputs = num_inputs; } + double GetBias() const { return m_bias; } - void SetBias(double bias) { m_bias = bias; } @@ -66,7 +84,8 @@ public: return m_weights.size(); } - void GetOutput(const std::vector &input, double * output) const { + void GetInputInnerProdWithWeights(const std::vector &input, + double * output) const { assert(input.size() == m_weights.size()); double inner_prod = std::inner_product(begin(input), end(input), @@ -75,27 +94,37 @@ public: *output = inner_prod; } - void GetFilteredOutput(const std::vector &input, double * bool_output) { - double inner_prod; - GetOutput(input, &inner_prod); -#if USE_SIGMOID == 1 - double y = utils::sigmoid(inner_prod); - *bool_output = (y > 0) ? true : false; -#else - *bool_output = (inner_prod > 0) ? true : false; -#endif + void GetOutputAfterSigmoid(const std::vector &input, + double * output) const { + double inner_prod = 0.0; + GetInputInnerProdWithWeights(input, &inner_prod); + *output = utils::sigmoid(inner_prod); + } + + void GetBooleanOutput(const std::vector &input, + bool * bool_output) const { + double value; + GetOutputAfterSigmoid(input, &value); + *bool_output = (value > 0.5) ? true : false; }; void UpdateWeights(const std::vector &x, - double m_learning_rate, - double error) { + double error, + double learning_rate) { assert(x.size() == m_weights.size()); for (size_t i = 0; i < m_weights.size(); i++) - m_weights[i] += x[i] * m_learning_rate * error; + m_weights[i] += x[i] * learning_rate * error; }; + + void UpdateWeight(int weight_id, + double increment, + double learning_rate) { + m_weights[weight_id] += learning_rate*increment; + } + protected: - int m_num_inputs; - double m_bias; + int m_num_inputs{ 0 }; + double m_bias{ 0.0 }; std::vector m_weights; }; diff --git a/src/NodeTest.cpp b/src/NodeTest.cpp new file mode 100644 index 0000000..da87717 --- /dev/null +++ b/src/NodeTest.cpp @@ -0,0 +1,262 @@ +//============================================================================ +// Name : NodeTest.cpp +// Author : David Nogueira +//============================================================================ +#include "Node.h" +#include "Sample.h" +#include "Utils.h" +#include +#include +#include +#include +#include +#include +#include +#include "microunit.h" + +namespace { +void Train(Node & node, + const std::vector &training_sample_set_with_bias, + double learning_rate, + int max_iterations, + bool use_constant_weight_init = true, + double constant_weight_init = 0.5) { + + //initialize weight vector + node.WeightInitialization(training_sample_set_with_bias[0].GetInputVectorSize(), + use_constant_weight_init, + constant_weight_init); + + std::cout << "Starting weights:\t"; + for (auto m_weightselement : node.GetWeights()) + std::cout << m_weightselement << "\t"; + std::cout << std::endl; + + for (int i = 0; i < max_iterations; i++) { + int error_count = 0; + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + bool prediction; + node.GetBooleanOutput(training_sample_with_bias.input_vector(), &prediction); + bool correct_output = training_sample_with_bias.output_vector()[0] > 0.5 ? true : false; + if (prediction != correct_output) { + error_count++; + double error = (correct_output ? 1 : 0) - (prediction ? 1 : 0); + node.UpdateWeights(training_sample_with_bias.input_vector(), + learning_rate, + error); + } + } + if (error_count == 0) break; + } + + std::cout << "Final weights:\t\t"; + for (auto m_weightselement : node.GetWeights()) + std::cout << m_weightselement << "\t"; + std::cout << std::endl; +}; +} + +UNIT(LearnAND) { + std::cout << "Train AND function with Node." << std::endl; + + std::vector training_set = + { + { { 0, 0 },{0.0} }, + { { 0, 1 },{0.0} }, + { { 1, 0 },{0.0} }, + { { 1, 1 },{1.0} } + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + Node my_node(num_features); + Train(my_node, training_sample_set_with_bias, 0.1, 100); + + for (const auto & training_sample : training_sample_set_with_bias) { + bool class_id; + my_node.GetBooleanOutput(training_sample.input_vector(), &class_id); + bool correct_output = training_sample.output_vector()[0] > 0 ? true : false; + ASSERT_TRUE(class_id == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnNAND) { + std::cout << "Train NAND function with Node." << std::endl; + + std::vector training_set = + { + { { 0, 0 },{1.0} }, + { { 0, 1 },{1.0} }, + { { 1, 0 },{1.0} }, + { { 1, 1 },{0.0} } + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + Node my_node(num_features); + Train(my_node, training_sample_set_with_bias, 0.1, 100); + + for (const auto & training_sample : training_sample_set_with_bias) { + bool class_id; + my_node.GetBooleanOutput(training_sample.input_vector(), &class_id); + bool correct_output = training_sample.output_vector()[0] > 0 ? true : false; + ASSERT_TRUE(class_id == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnOR) { + std::cout << "Train OR function with Node." << std::endl; + + std::vector training_set = + { + { { 0, 0 },{0.0} }, + { { 0, 1 },{1.0} }, + { { 1, 0 },{1.0} }, + { { 1, 1 },{1.0} } + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + Node my_node(num_features); + Train(my_node, training_sample_set_with_bias, 0.1, 100); + + for (const auto & training_sample : training_sample_set_with_bias) { + bool class_id; + my_node.GetBooleanOutput(training_sample.input_vector(), &class_id); + bool correct_output = training_sample.output_vector()[0] > 0 ? true : false; + ASSERT_TRUE(class_id == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} +UNIT(LearnNOR) { + std::cout << "Train NOR function with Node." << std::endl; + + std::vector training_set = + { + { { 0, 0 },{1.0} }, + { { 0, 1 },{0.0} }, + { { 1, 0 },{0.0} }, + { { 1, 1 },{0.0} } + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + Node my_node(num_features); + Train(my_node, training_sample_set_with_bias, 0.1, 100); + + for (const auto & training_sample : training_sample_set_with_bias) { + bool class_id; + my_node.GetBooleanOutput(training_sample.input_vector(), &class_id); + bool correct_output = training_sample.output_vector()[0] > 0 ? true : false; + ASSERT_TRUE(class_id == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnNOT) { + std::cout << "Train NOT function with Node." << std::endl; + + std::vector training_set = + { + { { 0 },{1.0} }, + { { 1 },{0.0}} + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + Node my_node(num_features); + Train(my_node, training_sample_set_with_bias, 0.1, 100); + + for (const auto & training_sample : training_sample_set_with_bias) { + bool class_id; + my_node.GetBooleanOutput(training_sample.input_vector(), &class_id); + bool correct_output = training_sample.output_vector()[0] > 0 ? true : false; + ASSERT_TRUE(class_id == correct_output); + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +UNIT(LearnXOR) { + std::cout << "Train XOR function with Node." << std::endl; + + std::vector training_set = + { + { { 0, 0 },{0.0} }, + { { 0, 1 },{1.0} }, + { { 1, 0 },{1.0} }, + { { 1, 1 },{0.0} } + }; + bool bias_already_in = false; + std::vector training_sample_set_with_bias(training_set); + //set up bias + if (!bias_already_in) { + for (auto & training_sample_with_bias : training_sample_set_with_bias) { + training_sample_with_bias.AddBiasValue(1); + } + } + size_t num_examples = training_sample_set_with_bias.size(); + size_t num_features = training_sample_set_with_bias[0].GetInputVectorSize(); + Node my_node(num_features); + Train(my_node, training_sample_set_with_bias, 0.1, 100); + + for (const auto & training_sample : training_sample_set_with_bias) { + bool class_id; + my_node.GetBooleanOutput(training_sample.input_vector(), &class_id); + bool correct_output = training_sample.output_vector()[0] > 0 ? true : false; + if (class_id != correct_output) { + std::cout << "Failed to train. " << + " A simple perceptron cannot learn the XOR function." << std::endl; + FAIL(); + } + } + std::cout << "Trained with success." << std::endl; + std::cout << std::endl; +} + +int main() { + microunit::UnitTester::Run(); + return 0; +} \ No newline at end of file diff --git a/src/Sample.h b/src/Sample.h index 44928f0..8b4522a 100644 --- a/src/Sample.h +++ b/src/Sample.h @@ -23,7 +23,21 @@ public: void AddBiasValue(double bias_value) { m_input_vector.insert(m_input_vector.begin(), bias_value); } + friend std::ostream & operator<<(std::ostream &stream, Sample const & obj) { + obj.PrintMyself(stream); + return stream; + }; protected: + virtual void PrintMyself(std::ostream& stream) const { + stream << "Input vector: ["; + for (int i = 0; i < m_input_vector.size(); i++) { + if (i != 0) + stream << ", "; + stream << m_input_vector[i]; + } + stream << "]"; + } + std::vector m_input_vector; }; @@ -35,13 +49,34 @@ public: Sample(input_vector) { m_output_vector = output_vector; } - const std::vector & output_vector() const { - return m_output_vector; + const std::vector & output_vector() const { + return m_output_vector; } size_t GetOutputVectorSize() const { return m_output_vector.size(); } + protected: + virtual void PrintMyself(std::ostream& stream) const { + stream << "Input vector: ["; + for (int i = 0; i < m_input_vector.size(); i++) { + if (i != 0) + stream << ", "; + stream << m_input_vector[i]; + } + stream << "]"; + + stream << "; "; + + stream << "Output vector: ["; + for (int i = 0; i < m_output_vector.size(); i++) { + if (i != 0) + stream << ", "; + stream << m_output_vector[i]; + } + stream << "]"; + } + std::vector m_output_vector; }; diff --git a/src/Utils.h b/src/Utils.h index 812141c..1879f6e 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -5,6 +5,7 @@ #ifndef UTILS_H #define UTILS_H +#include "Chrono.h" #include #include #include @@ -47,7 +48,7 @@ inline double deriv_sigmoid(double x) { return sigmoid(x)*(1 - sigmoid(x)); }; -void Softmax(std::vector *output) { +inline void Softmax(std::vector *output) { size_t num_elements = output->size(); std::vector exp_output(num_elements); double exp_total = 0.0; @@ -60,35 +61,10 @@ void Softmax(std::vector *output) { } } -void GetIdMaxElement(const std::vector &output, size_t * class_id) { +inline void GetIdMaxElement(const std::vector &output, size_t * class_id) { *class_id = std::distance(output.begin(), std::max_element(output.begin(), output.end())); } - -class Chronometer { -public: - Chronometer() { - time_span = std::chrono::steady_clock::duration::zero(); - }; - virtual ~Chronometer() {}; - - void GetTime() { - clock_begin = std::chrono::steady_clock::now(); - } - void StopTime() { - std::chrono::steady_clock::time_point clock_end = std::chrono::steady_clock::now(); - time_span += clock_end - clock_begin; - } - //Return elapsed time in seconds - double GetElapsedTime() { - return double(time_span.count()) * - std::chrono::steady_clock::period::num / std::chrono::steady_clock::period::den; - } -protected: - std::chrono::steady_clock::time_point clock_begin; - std::chrono::steady_clock::duration time_span; -}; - } #endif // UTILS_H \ No newline at end of file