diff --git a/.appveyor.yml b/.appveyor.yml index 6ac262d..e55e366 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,2 +1,10 @@ +configuration: + - Release + build_script: - - msbuild %solution_name% \ No newline at end of file + - msbuild %solution_name% + +test_script: + - C:\projects\jnf-neat\x64\Release\xor_two_bits.exe + - C:\projects\jnf-neat\x64\Release\xor_three_bits.exe + - C:\projects\jnf-neat\x64\Release\EvenNumbers.exe \ No newline at end of file diff --git a/.gitignore b/.gitignore index a26fb1a..eea3203 100644 --- a/.gitignore +++ b/.gitignore @@ -410,3 +410,5 @@ cmake_install.cmake install_manifest.txt Testing/ *.cmake +cmake-build-debug/ + diff --git a/.travis.yml b/.travis.yml index 9d5dc48..a941829 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ addons: sources: - ubuntu-toolchain-r-test script: - - cmake -DCMAKE_CXX_COMPILER=g++-6 CMakeLists.txt - - make - - travis_wait make test + - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++-6 . + - make + - ctest --verbose + diff --git a/CMakeLists.txt b/CMakeLists.txt index ab9305a..fcc2dab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,13 +8,20 @@ endif() set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/out/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/out/tests) +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Wall") +set(CMAKE_C_FLAGS -fPIC) set(CMAKE_CXX_FLAGS -fPIC) add_subdirectory(Core) add_library(Hippocrates SHARED $) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + target_link_libraries(Hippocrates -lstdc++fs) +target_link_libraries(Hippocrates Threads::Threads) enable_testing() add_subdirectory(Tests) + diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 55101b4..2c0866a 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -1,2 +1,2 @@ -file(GLOB SOURCES Sources/Headers/*.h Sources/Implementations/*.cpp) +file(GLOB_RECURSE SOURCES [RELATIVE Sources] *.cpp *.hpp *.c *.h) add_library(Core SHARED OBJECT ${SOURCES}) diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 43325d6..be65b53 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -78,6 +78,7 @@ /DLL %(AdditionalOptions) + _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -85,6 +86,7 @@ Level3 Disabled true + _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -94,6 +96,7 @@ true true true + _UNICODE;UNICODE;%(PreprocessorDefinitions) true @@ -107,6 +110,7 @@ true true true + _UNICODE;UNICODE;%(PreprocessorDefinitions) true @@ -114,24 +118,31 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + @@ -139,8 +150,8 @@ - + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index bb3f257..a38393e 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -9,48 +9,66 @@ {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd + + {1b3a181c-c7b8-45a2-a69a-d9d44c2916a2} + - + + Headers + + + Headers + + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + Headers - + + Headers + + + Headers\trained + + + Headers\trained + + Headers @@ -79,14 +97,20 @@ Implementations - - Implementations - Implementations Implementations + + Implementations + + + Implementations + + + Implementations + \ No newline at end of file diff --git a/Core/Sources/Headers/body.h b/Core/Sources/Headers/body.hpp similarity index 63% rename from Core/Sources/Headers/body.h rename to Core/Sources/Headers/body.hpp index c3f6b64..4d65e5d 100644 --- a/Core/Sources/Headers/body.h +++ b/Core/Sources/Headers/body.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include "type.hpp" namespace Hippocrates { @@ -14,16 +15,16 @@ class IBody { virtual auto operator=(IBody&&)&->IBody& = default; virtual auto Reset() -> void = 0; - virtual auto Update(const std::vector& networkOutputs) -> void = 0; + virtual auto Update(const Type::neuron_values_t& networkOutputs) -> void = 0; virtual auto HasFinishedTask() const -> bool = 0; - virtual auto GetFitness() const -> double = 0; + virtual auto GetFitness() const -> Type::fitness_t = 0; - virtual auto ProvideNetworkWithInputs() const -> std::vector = 0; + virtual auto ProvideNetworkWithInputs() const -> Type::neuron_values_t= 0; virtual auto GetInputCount() const -> std::size_t = 0; virtual auto GetOutputCount() const -> std::size_t = 0; - virtual auto GetMaximumFitness() const -> double = 0; + virtual auto GetMaximumFitness() const ->Type::fitness_t = 0; }; } diff --git a/Core/Sources/Headers/gene.h b/Core/Sources/Headers/gene.hpp similarity index 58% rename from Core/Sources/Headers/gene.h rename to Core/Sources/Headers/gene.hpp index 67466d4..2e59a12 100644 --- a/Core/Sources/Headers/gene.h +++ b/Core/Sources/Headers/gene.hpp @@ -1,30 +1,32 @@ #pragma once #include #include +#include "../Headers/utility.hpp" +#include "../Headers/type.hpp" + namespace Hippocrates { struct Gene { -public: Gene(); Gene(const Gene& other) = default; Gene(Gene&& other) = default; + Gene(std::string json); ~Gene() = default; auto operator=(const Gene& other) -> Gene& = default; auto operator=(Gene&& other) -> Gene& = default; + auto operator==(const Gene& other) const -> bool; auto SetRandomWeight() -> void; auto GetJSON() const->std::string; std::size_t from = 0; std::size_t to = 0; - float weight = 0.0f; - std::size_t historicalMarking = numberOfExistingGenes++; + Type::connection_weight_t weight = 0.0f; + static constexpr auto invalidHistoricalMarking = std::numeric_limits::max(); + std::size_t historicalMarking = invalidHistoricalMarking; bool isEnabled = true; bool isRecursive = false; - -private: - static std::size_t numberOfExistingGenes; }; } diff --git a/Core/Sources/Headers/genome.h b/Core/Sources/Headers/genome.hpp similarity index 85% rename from Core/Sources/Headers/genome.h rename to Core/Sources/Headers/genome.hpp index 6e914d9..fc24152 100644 --- a/Core/Sources/Headers/genome.h +++ b/Core/Sources/Headers/genome.hpp @@ -1,7 +1,7 @@ #pragma once -#include "gene.h" -#include "training_parameters.h" +#include "gene.hpp" +#include "training_parameters.hpp" #include namespace Hippocrates { @@ -13,11 +13,15 @@ class Genome { std::size_t inputCount = 0U; std::size_t outputCount = 0U; std::size_t neuronCount = 0U; + auto ParseGenesJson(std::string json) -> std::vector; public: explicit Genome(std::size_t inputCount, std::size_t outputCount, TrainingParameters parameters); + explicit Genome(std::string json); + explicit Genome() = default; Genome(const Genome& other) = default; Genome(Genome&& other) = default; + ~Genome() = default; auto operator=(const Genome& other) -> Genome& = default; @@ -41,7 +45,7 @@ class Genome { auto InsertGeneAt(Gene gene, size_t index) -> void; auto GetTrainingParameters() const -> const TrainingParameters& { return parameters; } - auto GetGeneticalDistanceFrom(const Genome& other) const -> double; + auto GetGeneticalDistanceFrom(const Genome& other) const -> Type::connection_weight_t; auto DoesContainGene(const Gene& gene) const -> bool; auto GetJSON() const->std::string; diff --git a/Core/Sources/Headers/innovation_cacher.hpp b/Core/Sources/Headers/innovation_cacher.hpp new file mode 100644 index 0000000..e26e9fe --- /dev/null +++ b/Core/Sources/Headers/innovation_cacher.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include "genome.hpp" + +namespace Hippocrates { + +class InnovationCacher { +public: + auto AssignAndCacheHistoricalMarkings(Genome& genome) -> void; + auto AssignAndCacheHistoricalMarking(Gene& gene) -> void; + auto Clear() -> void; + +private: + std::vector cache; + using cache_iterator = decltype(cache)::iterator; + std::size_t nextHighestHistoricalMarking = 0; + +private: + auto GetAndIncrementNextHighestHistoricalMarking() -> std::size_t; + auto FindGeneInCache(const Gene& gene) -> cache_iterator; +}; + +} \ No newline at end of file diff --git a/Core/Sources/Headers/jsmn.h b/Core/Sources/Headers/jsmn.h new file mode 100644 index 0000000..971194f --- /dev/null +++ b/Core/Sources/Headers/jsmn.h @@ -0,0 +1,100 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_UNDEFINED = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3, + JSMN_PRIMITIVE = 4 +} jsmntype_t; + +enum jsmnerr { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3 +}; + +/** + * JSON token description. + * type type (object, array, string etc.) + * start start position in JSON data string + * end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +#ifdef JSMN_PARENT_LINKS + int parent; +#endif +} jsmntok_t; + +/** + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + unsigned int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/** + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/** + * Run JSON parser. It parses a JSON data string into and array of tokens, each describing + * a single JSON object. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +#ifdef __cplusplus +} +#endif + +#endif /* __JSMN_H_ */ + +/* + +Copyright (c) 2010 Serge A. Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ diff --git a/Core/Sources/Headers/logger.h b/Core/Sources/Headers/logger.hpp similarity index 82% rename from Core/Sources/Headers/logger.h rename to Core/Sources/Headers/logger.hpp index f082778..86ebfd7 100644 --- a/Core/Sources/Headers/logger.h +++ b/Core/Sources/Headers/logger.hpp @@ -2,12 +2,16 @@ #include #include #include + +#include "type.hpp" + namespace Hippocrates { class Logger { public: auto CreateLoggingDirs() -> void; auto LogGeneration(std::size_t generation, const std::string& log) -> void; + auto LogMetadata(Type::fitness_t maxFitness) -> void; private: auto GetCurrentDir(const char *) -> std::string; @@ -16,13 +20,14 @@ class Logger { auto GetLogFolder(const std::string&) -> std::string; auto GetLogFolder(const std::wstring&) -> std::wstring; - auto GetSessionDir(const std::string& dumpDir) -> std::string; auto GetSessionDir(const std::wstring& dumpDir) -> std::wstring; auto GetLogFileExtension(const std::string&) -> std::string; auto GetLogFileExtension(const std::wstring&) -> std::wstring; + auto GetMetadataFileName(const std::string &sessionDir) -> std::string; + auto GetMetadataFileName(const std::wstring &sessionDir) -> std::wstring; auto GetLogFileName(const std::string& sessionDir, std::size_t generationsPassed) -> std::string; auto GetLogFileName(const std::wstring& sessionDir, std::size_t generationsPassed) -> std::wstring; @@ -33,6 +38,8 @@ class Logger { std::chrono::system_clock::time_point timestamp; std::string fullLoggingPathOnUnix; std::wstring fullLoggingPathOnWindows; + + std::string metaData = ""; }; } diff --git a/Core/Sources/Headers/neural_network.h b/Core/Sources/Headers/neural_network.h deleted file mode 100644 index 877ee5d..0000000 --- a/Core/Sources/Headers/neural_network.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -#include "gene.h" -#include "neuron.h" -#include "genome.h" -#include -#include - -namespace Hippocrates { - -class NeuralNetwork { -private: - Genome genome; - std::vector neurons; - std::vector inputNeurons; - std::vector outputNeurons; - std::map> layerMap; - -public: - explicit NeuralNetwork(const Genome& genome, bool shouldMutate = false); - explicit NeuralNetwork(Genome&& genome, bool shouldMutate = false); - NeuralNetwork(const NeuralNetwork& other); - NeuralNetwork(NeuralNetwork&& other) = default; - ~NeuralNetwork() = default; - - auto operator= (const NeuralNetwork& other) -> NeuralNetwork&; - auto operator= (NeuralNetwork&& other) -> NeuralNetwork& = default; - - auto GetGenome() const -> const Genome&{ return genome; } - auto GetOutputsUsingInputs(std::vector inputs) -> std::vector; - auto GetTrainingParameters() const -> const TrainingParameters& { return GetGenome().GetTrainingParameters(); } - - auto GetJSON() const -> std::string; - -private: - static auto DidChanceOccure(float chance) -> bool; - - auto SetInputs(std::vector inputs) -> void; - auto GetOutputs() -> std::vector; - - auto MutateGenesAndBuildNetwork() -> void; - auto BuildNetworkFromGenes() -> void; - auto InterpretInputsAndOutputs() -> void; - - auto ShouldAddNeuron() const -> bool; - auto ShouldAddConnection() const -> bool; - auto ShouldMutateWeight() const -> bool; - - auto AddRandomNeuron() -> void; - auto AddRandomConnection() -> void; - - auto GetTwoUnconnectedNeurons() -> std::pair; - auto GetRandomEnabledGene() -> Gene&; - - auto CanNeuronsBeConnected(const Neuron& lhs, const Neuron& rhs) const -> bool; - auto AreBothNeuronsOutputs(const Neuron& lhs, const Neuron& rhs) const -> bool; - static auto AreNeuronsConnected(const Neuron& lhs, const Neuron& rhs) -> bool; - - auto ShuffleWeights() -> void; - auto MutateWeightOfGeneAt(std::size_t index) -> void; - auto PerturbWeightAt(std::size_t index) -> void; - - auto CategorizeNeuronsIntoLayers() -> void; - auto CategorizeNeuronBranchIntoLayers(Neuron& currNode, size_t currentDepth = 0) const -> void; - -}; - -} diff --git a/Core/Sources/Headers/neural_network.hpp b/Core/Sources/Headers/neural_network.hpp new file mode 100644 index 0000000..dfac0ab --- /dev/null +++ b/Core/Sources/Headers/neural_network.hpp @@ -0,0 +1,99 @@ +#pragma once +#include +#include + +#include "gene.hpp" +#include "neuron.hpp" +#include "genome.hpp" +#include "innovation_cacher.hpp" +#include "type.hpp" + +namespace Hippocrates { + +class NeuralNetwork { +private: + Genome genome; + std::vector neurons; + std::map> layerMap; + +public: + NeuralNetwork(const Genome& genome); + NeuralNetwork(Genome&& genome); + explicit NeuralNetwork(const Genome& genome, InnovationCacher& currGenerationInnovations); + explicit NeuralNetwork(Genome&& genome, InnovationCacher& currGenerationInnovations); + explicit NeuralNetwork(const std::string& json); + NeuralNetwork(const NeuralNetwork& other); + NeuralNetwork(NeuralNetwork&& other) = default; + virtual ~NeuralNetwork() = default; + + auto operator= (const NeuralNetwork& other) -> NeuralNetwork&; + auto operator= (NeuralNetwork&& other) -> NeuralNetwork& = default; + + auto GetGenome() const -> const Genome&{ return genome; } + auto GetOutputsUsingInputs(Type::neuron_values_t inputs) -> Type::neuron_values_t; + auto GetTrainingParameters() const -> const TrainingParameters& { return GetGenome().GetTrainingParameters(); } + + auto GetJSON() const -> std::string; + + auto Reset() -> void; + +private: + auto SetInputs(Type::neuron_values_t inputs) -> void; + auto GetOutputs() -> Type::neuron_values_t; + + auto GetOutputNeurons()->std::vector; + auto GetOutputNeurons() const ->std::vector; + auto GetInputNeurons()->std::vector; + auto GetInputNeurons() const ->std::vector; + + auto MutateGenesAndBuildNetwork(InnovationCacher& currGenerationInnovations) -> void; + auto BuildNetworkFromGenes() -> void; + + auto ShouldAddNeuron() const -> bool; + auto ShouldAddConnection() const -> bool; + auto ShouldMutateWeight() const -> bool; + + auto AddRandomNeuron(InnovationCacher& currGenerationInnovations) -> void; + auto AddRandomConnection(InnovationCacher& currGenerationInnovations) -> void; + + auto GetTwoUnconnectedNeurons() -> std::pair; + auto GetRandomEnabledGene() -> Gene&; + + auto CanNeuronsBeConnected(const Neuron& lhs, const Neuron& rhs) const -> bool; + auto AreBothNeuronsOutputs(const Neuron& lhs, const Neuron& rhs) const -> bool; + static auto AreNeuronsConnected(const Neuron& lhs, const Neuron& rhs) -> bool; + + auto ShuffleWeights() -> void; + auto MutateWeightOfGeneAt(std::size_t index) -> void; + auto PerturbWeightAt(std::size_t index) -> void; + + auto CategorizeNeuronsIntoLayers() -> void; + auto CategorizeNeuronBranchIntoLayers(Neuron& currNode, size_t currentDepth = 0) const -> void; + + auto ParseNeuronsJson(std::string json) -> std::vector; + + + template + auto GetNeuronsByRangeAndIndex(std::size_t range, Lambda&& index) { + std::vector neuronsInRange; + neuronsInRange.reserve(range); + + for (auto i = 0U; i < range; i++) { + neuronsInRange.push_back(&neurons[index(i)]); + } + return neuronsInRange; + } + + template + auto GetNeuronsByRangeAndIndex(std::size_t range, Lambda&& index) const { + std::vector neuronsInRange; + neuronsInRange.reserve(range); + + for (auto i = 0U; i < range; i++) { + neuronsInRange.push_back(&neurons[index(i)]); + } + return neuronsInRange; + } +}; + +} diff --git a/Core/Sources/Headers/neural_network_trainer.h b/Core/Sources/Headers/neural_network_trainer.h deleted file mode 100644 index 0d7c9a8..0000000 --- a/Core/Sources/Headers/neural_network_trainer.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once -#include "training_parameters.h" -#include "species.h" -#include "trained_neural_network.h" -#include "logger.h" -#include "species_manager.h" -#include "training_data.h" -#include "supervised_training_body.h" -#include -#include - -namespace Hippocrates { - -class NeuralNetworkTrainer { -public: - bool loggingEnabled = false; - -private: - TrainingParameters parameters; - SpeciesManager species; - Logger logger; - size_t generationsPassed = 0; - -public: - explicit NeuralNetworkTrainer(TrainingParameters parameters = TrainingParameters()); - NeuralNetworkTrainer(const NeuralNetworkTrainer& other) = default; - NeuralNetworkTrainer(NeuralNetworkTrainer&& other) = default; - - auto operator=(const NeuralNetworkTrainer&) -> NeuralNetworkTrainer& = default; - auto operator=(NeuralNetworkTrainer&&) -> NeuralNetworkTrainer& = default; - - auto TrainUnsupervised(SpeciesManager::Bodies& bodies) -> TrainedNeuralNetwork; - template - auto TrainSupervised(const TrainingData& data, std::size_t trainingInstances) -> TrainedNeuralNetwork; - auto GetJSON() const -> std::string; - -private: - auto TrainGenerationAndLogUsingBodies(SpeciesManager::Bodies& bodies) -> void; -}; - -template -auto NeuralNetworkTrainer::TrainSupervised(const TrainingData& data, std::size_t trainingInstances) -> TrainedNeuralNetwork { - using Body = SupervisedTrainigBody; - std::vector bodies; - bodies.reserve(trainingInstances); - for (std::size_t i = 0; i < trainingInstances; ++i) { - Body body(data); - bodies.push_back(std::move(body)); - } - SpeciesManager::Bodies bodyRefs(bodies.begin(), bodies.end()); - return TrainUnsupervised(bodyRefs); -} - -} diff --git a/Core/Sources/Headers/neural_network_trainer.hpp b/Core/Sources/Headers/neural_network_trainer.hpp new file mode 100644 index 0000000..807854a --- /dev/null +++ b/Core/Sources/Headers/neural_network_trainer.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "training_parameters.hpp" +#include "species.hpp" +#include "trained/trained_neural_network.hpp" +#include "logger.hpp" +#include "species_manager.hpp" +#include "training_data.hpp" +#include "supervised_training_body.hpp" +#include "trained/classifier.hpp" + +#include +#include + +namespace Hippocrates { + +class NeuralNetworkTrainer { +public: + bool loggingEnabled = false; + +private: + TrainingParameters parameters; + SpeciesManager species; + Logger logger; + size_t generationsPassed = 0; + +public: + explicit NeuralNetworkTrainer(TrainingParameters parameters = TrainingParameters()); + NeuralNetworkTrainer(const NeuralNetworkTrainer& other) = default; + NeuralNetworkTrainer(NeuralNetworkTrainer&& other) = default; + + auto operator=(const NeuralNetworkTrainer&) -> NeuralNetworkTrainer& = default; + auto operator=(NeuralNetworkTrainer&&) -> NeuralNetworkTrainer& = default; + + auto TrainUnsupervised(SpeciesManager::Bodies& bodies) ->Trained::TrainedNeuralNetwork; + template + auto TrainSupervised(const TrainingData& data, std::size_t trainingInstances) -> Trained::Classifier; + + auto GetGenerationsPassed() const { return generationsPassed; } + + auto GetJSON() const -> std::string; + + +private: + auto TrainGenerationAndLogUsingBodies(SpeciesManager::Bodies& bodies) -> void; +}; + +template +auto NeuralNetworkTrainer::TrainSupervised(const TrainingData& data, std::size_t trainingInstances) -> Trained::Classifier { + using Body = SupervisedTrainigBody; + std::vector bodies; + bodies.reserve(trainingInstances); + for (std::size_t i = 0; i < trainingInstances; ++i) { + Body body(data); + bodies.push_back(std::move(body)); + } + SpeciesManager::Bodies bodyRefs(bodies.begin(), bodies.end()); + auto champ = TrainUnsupervised(bodyRefs); + return Trained::Classifier(std::move(champ)); +} + +} diff --git a/Core/Sources/Headers/neuron.h b/Core/Sources/Headers/neuron.hpp similarity index 71% rename from Core/Sources/Headers/neuron.h rename to Core/Sources/Headers/neuron.hpp index 44eed71..0b40171 100644 --- a/Core/Sources/Headers/neuron.h +++ b/Core/Sources/Headers/neuron.hpp @@ -3,6 +3,8 @@ #include #include +#include "type.hpp" + namespace Hippocrates { class NeuralNetwork; class Neuron { @@ -10,13 +12,13 @@ class Neuron { public: struct Connection { Neuron* neuron = nullptr; - float weight = 0.0f; + Type::connection_weight_t weight = 0.0f; bool isRecursive = false; bool outGoing = false; }; private: std::vector connections; - float lastActionPotential = 0.0f; + Type::neuron_value_t lastActionPotential = 0.0f; std::size_t layer = 0U; public: @@ -24,20 +26,23 @@ class Neuron { explicit Neuron(std::vector connections); Neuron(const Neuron& other) = default; Neuron(Neuron&& other) = default; + Neuron(std::string json); ~Neuron() = default; auto operator=(const Neuron& other) -> Neuron& = default; auto operator=(Neuron&& other) -> Neuron& = default; - auto SetInput(float input) -> void; + auto SetInput(Type::neuron_value_t input) -> void; auto GetConnections() const -> const std::vector& { return connections; } - auto RequestDataAndGetActionPotential() -> float; + auto RequestDataAndGetActionPotential() ->Type::neuron_value_t; auto GetLayer() const -> std::size_t { return layer; } auto GetJSON() const->std::string; + auto Reset() -> void; + private: auto AddConnection(Connection connection) -> void; - static auto sigmoid(float d) -> float; + static auto sigmoid(Type::neuron_value_t d) ->Type::neuron_value_t; }; } diff --git a/Core/Sources/Headers/organism.h b/Core/Sources/Headers/organism.hpp similarity index 60% rename from Core/Sources/Headers/organism.h rename to Core/Sources/Headers/organism.hpp index 8eb29af..3dc16c0 100644 --- a/Core/Sources/Headers/organism.h +++ b/Core/Sources/Headers/organism.hpp @@ -1,8 +1,8 @@ #pragma once -#include "neural_network.h" -#include "body.h" -#include "training_parameters.h" -#include "genome.h" +#include "neural_network.hpp" +#include "body.hpp" +#include "training_parameters.hpp" +#include "genome.hpp" #include namespace Hippocrates { @@ -10,9 +10,9 @@ namespace Hippocrates { class Organism { private: IBody* body = nullptr; - mutable double fitness = 0.0; + mutable Type::fitness_t fitness = 0.0; mutable bool isFitnessUpToDate = false; - double fitnessModifier = 1.0; + Type::fitness_t fitnessModifier = 1.0; NeuralNetwork network; public: @@ -26,12 +26,13 @@ class Organism { auto Reset() -> void { body->Reset(); } auto Update() -> void; - auto SetFitnessModifier(double factor) -> void { fitnessModifier = factor; } - auto GetOrCalculateFitness() const -> double; - auto GetOrCalculateRawFitness() const -> double; + auto SetFitnessModifier(Type::fitness_t factor) -> void { fitnessModifier = factor; } + auto GetOrCalculateFitness() const ->Type::fitness_t; + auto GetOrCalculateRawFitness() const ->Type::fitness_t; auto GetMaxFitness() const { return body->GetMaximumFitness(); } - auto BreedWith(Organism& partner) -> NeuralNetwork; + auto BreedWith(const Organism& partner, InnovationCacher& currGenerationInnovations) const -> NeuralNetwork; auto GetGenome() const -> const Genome&{ return network.GetGenome(); } + auto GetNeuralNetwork() const -> const NeuralNetwork& {return network; } auto HasFinishedTask() const -> bool { return body->HasFinishedTask(); } auto GetTrainingParameters() const -> const TrainingParameters&{ return network.GetTrainingParameters(); } auto GetJSON() const->std::string; diff --git a/Core/Sources/Headers/species.h b/Core/Sources/Headers/species.hpp similarity index 63% rename from Core/Sources/Headers/species.h rename to Core/Sources/Headers/species.hpp index bbf35de..f1b4481 100644 --- a/Core/Sources/Headers/species.h +++ b/Core/Sources/Headers/species.hpp @@ -1,6 +1,6 @@ #pragma once -#include "training_parameters.h" -#include "organism.h" +#include "training_parameters.hpp" +#include "organism.hpp" #include #include @@ -12,8 +12,8 @@ class Species { Organism representative; mutable bool isSortedByFitness = false; std::size_t numberOfStagnantGenerations = 0; - double fitnessHighscore = 0; - const TrainingParameters& parameters; + Type::fitness_t fitnessHighscore = 0; + auto GetTrainingParameters() const -> const TrainingParameters& { return representative.GetTrainingParameters(); }; public: explicit Species(Organism representative); @@ -21,28 +21,31 @@ class Species { Species(Species&& other) = default; ~Species() = default; - auto operator=(Species&& other) noexcept -> Species&; + auto operator=(Species& other) & ->Species& = default; + auto operator=(Species&& other) & -> Species& = default; auto AddOrganism(Organism&& organism) -> void; - auto AnalyzePopulation() -> void; auto IsCompatible(const Genome& genome) const -> bool; - auto IsEmpty() const -> bool { return population.empty(); } + auto GetSize() const { return population.size(); } + auto IsEmpty() const { return population.empty(); } + auto GetAverageFitness() const ->Type::fitness_t; + auto GetTotalFitness() const ->Type::fitness_t; auto IsStagnant() const -> bool; + auto GetOffspringCount(Type::fitness_t averageFitness) const -> std::size_t; auto LetPopulationLive() -> void; auto ResetToTeachableState() -> void; auto SetPopulationsFitnessModifier() -> void; auto ClearPopulation() -> void; + auto RemoveWorst() -> void; auto GetFittestOrganism() const -> const Organism&; auto SortPopulationIfNeeded() const -> void; - auto GetOrganismToBreed() -> Organism&; + auto GetOrganismToBreed() const -> const Organism&; auto GetJSON() const->std::string; - - private: auto ElectRepresentative() -> void; auto SelectRandomRepresentative() -> void; diff --git a/Core/Sources/Headers/species_manager.h b/Core/Sources/Headers/species_manager.hpp similarity index 74% rename from Core/Sources/Headers/species_manager.h rename to Core/Sources/Headers/species_manager.hpp index 064d3aa..dca517b 100644 --- a/Core/Sources/Headers/species_manager.h +++ b/Core/Sources/Headers/species_manager.hpp @@ -1,5 +1,6 @@ #pragma once -#include "species.h" +#include "species.hpp" +#include "innovation_cacher.hpp" namespace Hippocrates { class Organism; @@ -9,6 +10,7 @@ class SpeciesManager { const TrainingParameters& parameters; mutable bool areSpeciesSortedByFitness = false; std::vector species; + InnovationCacher currGenerationInnovations; public: using Bodies = std::vector>; @@ -18,11 +20,16 @@ class SpeciesManager { auto CreateInitialOrganisms(Bodies& bodies) -> void; auto Repopulate(Bodies& bodies) -> void; + auto BreedInSpecies(const Species & species) -> NeuralNetwork; + auto GetFittestSpecies() -> const Species &; auto GetFittestOrganism() -> const Organism&; auto LetGenerationLive() -> void; auto GetSpeciesCount() const { return species.size(); } + auto GetPopulationCount() const -> std::size_t; + auto GetTotalFitness() const ->Type::fitness_t; + auto GetAverageFitness() const ->Type::fitness_t; auto begin() const { return species.begin(); } auto end() const { return species.end(); } @@ -30,13 +37,9 @@ class SpeciesManager { private: auto ResetPopulationToTeachableState() -> void; auto FillOrganismIntoSpecies(Organism&& organism) -> void; - auto PrepareSpeciesForPopulation() -> void; - auto AnalyzeSpeciesPopulation() -> void; - auto DeleteStagnantSpecies() -> void; auto DeleteEmptySpecies() -> void; auto SortSpeciesIfNeeded() -> void; auto ClearSpeciesPopulation() -> void; - auto GetSpeciesToBreed() -> Species&; }; diff --git a/Core/Sources/Headers/supervised_training_body.h b/Core/Sources/Headers/supervised_training_body.hpp similarity index 55% rename from Core/Sources/Headers/supervised_training_body.h rename to Core/Sources/Headers/supervised_training_body.hpp index 174f475..b3570fa 100644 --- a/Core/Sources/Headers/supervised_training_body.h +++ b/Core/Sources/Headers/supervised_training_body.hpp @@ -1,28 +1,30 @@ #pragma once -#include "body.h" -#include "training_data.h" #include +#include "body.hpp" +#include "training_data.hpp" +#include "type.hpp" + namespace Hippocrates { -template +template class SupervisedTrainigBody : public IBody { public: - using Data = TrainingData; + using Data = TrainingData; explicit SupervisedTrainigBody(const Data& data) : trainingData{ data }, inputCount{data.begin()->input.size()}, outputCount{ClassificationCount}, - maxFitness{static_cast(data.GetSize())} + maxFitness{static_cast(data.GetSize())} {}; auto Reset() -> void override { currSet = trainingData.begin(); fitness = 0.0; }; - auto Update(const std::vector& networkOutputs) -> void override; - auto GetFitness() const -> double override { return fitness; } + auto Update(const Type::neuron_values_t& networkOutputs) -> void override; + auto GetFitness() const -> Type::fitness_t override { return fitness; } auto HasFinishedTask() const -> bool override { return currSet == trainingData.end(); }; - auto ProvideNetworkWithInputs() const->std::vector override { + auto ProvideNetworkWithInputs() const-> Type::neuron_values_t override { if (HasFinishedTask()) throw std::runtime_error("Tried to get inputs out of trainingdata that was already completly processed"); return currSet->input; @@ -31,22 +33,22 @@ class SupervisedTrainigBody : public IBody { auto GetInputCount() const -> std::size_t override { return inputCount; }; auto GetOutputCount() const -> std::size_t override { return outputCount; }; - auto GetMaximumFitness() const -> double override { return maxFitness; }; + auto GetMaximumFitness() const -> Type::fitness_t override { return maxFitness; }; private: const Data& trainingData; typename Data::const_iterator currSet = trainingData.begin(); - double fitness = 0.0; + Type::fitness_t fitness = static_cast(0.0); const size_t inputCount; const size_t outputCount; - const double maxFitness; + const Type::fitness_t maxFitness; }; -template -auto SupervisedTrainigBody::Update(const std::vector& networkOutputs) -> void { +template +auto SupervisedTrainigBody::Update(const Type::neuron_values_t& networkOutputs) -> void { auto maxOutput = std::distance(networkOutputs.begin(), std::max_element(networkOutputs.begin(), networkOutputs.end())); if (static_cast(maxOutput) == static_cast(currSet->classification)) { - fitness += 1.0; + fitness += static_cast(1.0); } ++currSet; } diff --git a/Core/Sources/Headers/trained/classifier.hpp b/Core/Sources/Headers/trained/classifier.hpp new file mode 100644 index 0000000..afc6d17 --- /dev/null +++ b/Core/Sources/Headers/trained/classifier.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "trained_neural_network.hpp" + +namespace Hippocrates { +namespace Trained { + +template +class Classifier : public TrainedNeuralNetwork { +public: + using TrainedNeuralNetwork::TrainedNeuralNetwork; + Classifier() : TrainedNeuralNetwork(){ }; + Classifier(const TrainedNeuralNetwork& other) : TrainedNeuralNetwork(other){}; + Classifier(TrainedNeuralNetwork&& other) : TrainedNeuralNetwork(std::move(other)){}; + + auto Classify(const Type::neuron_values_t& inputs) { + auto outputs = GetOutputsUsingInputs(inputs); + auto maxOutput = std::max_element(outputs.begin(), outputs.end()); + auto outputIndex = std::distance(outputs.begin(), maxOutput); + return static_cast(outputIndex); + } +}; + +} +} \ No newline at end of file diff --git a/Core/Sources/Headers/trained/trained_neural_network.hpp b/Core/Sources/Headers/trained/trained_neural_network.hpp new file mode 100644 index 0000000..b3696bf --- /dev/null +++ b/Core/Sources/Headers/trained/trained_neural_network.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "../neural_network.hpp" + +namespace Hippocrates { +namespace Trained { + +class TrainedNeuralNetwork : public NeuralNetwork { +public: + using NeuralNetwork::NeuralNetwork; +}; + +} +} diff --git a/Core/Sources/Headers/trained_neural_network.h b/Core/Sources/Headers/trained_neural_network.h deleted file mode 100644 index d03d234..0000000 --- a/Core/Sources/Headers/trained_neural_network.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "neural_network.h" -#include - -namespace Hippocrates { - -class TrainedNeuralNetwork : public NeuralNetwork { -public: - using NeuralNetwork::NeuralNetwork; - static auto LoadFromFile(const std::ifstream& fileName) -> TrainedNeuralNetwork; - auto SaveToFile(std::ofstream& file) const -> void; - -private: - - static auto LoadGenomeFromFile(const std::ifstream& file) -> Genome; - static auto LoadTrainigParametersFromFile(const std::ifstream& file) -> TrainingParameters; -}; - -} diff --git a/Core/Sources/Headers/training_data.h b/Core/Sources/Headers/training_data.h deleted file mode 100644 index 75fb5a0..0000000 --- a/Core/Sources/Headers/training_data.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -namespace Hippocrates { - -template (Classification::ClassificationCount)> -class TrainingData { -public: - struct Set { - InputType input; - Classification classification; - }; - using const_iterator = typename std::vector::const_iterator; - -public: - TrainingData() = default; - explicit TrainingData(std::vector categorizedData) : sets(std::move(categorizedData)) {}; - - auto AddSet(Set data) { sets.push_back(std::move(data)); } - - auto GetSize() const { return sets.size(); } - - auto begin() const noexcept { return sets.begin(); } - auto end() const noexcept { return sets.end(); } - -private: - std::vector sets; - - -}; - -} diff --git a/Core/Sources/Headers/training_data.hpp b/Core/Sources/Headers/training_data.hpp new file mode 100644 index 0000000..2607f97 --- /dev/null +++ b/Core/Sources/Headers/training_data.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include "neural_network.hpp" + +namespace Hippocrates { + +template (Classification::ClassificationCount)> +class TrainingData { +public: + struct Set { + Type::neuron_values_t input; + Classification classification; + }; + using const_iterator = typename std::vector::const_iterator; + +public: + TrainingData() = default; + explicit TrainingData(std::vector categorizedData) : sets(std::move(categorizedData)) {}; + + auto AddSet(Set data) { sets.push_back(std::move(data)); } + template>::value, bool>::type> + auto AddSet(std::initializer_list input, Classification&& classification) + { + Set set; + Type::neuron_values_t formattedInput(std::move(input.begin()), std::move(input.end())); + set.input = std::move(formattedInput); + set.classification = std::forward(classification); + sets.push_back(std::move(set)); + } + + auto GetSize() const { return sets.size(); } + + auto begin() const noexcept { return sets.begin(); } + auto end() const noexcept { return sets.end(); } + +private: + std::vector sets; + + +}; + +} diff --git a/Core/Sources/Headers/training_parameters.h b/Core/Sources/Headers/training_parameters.hpp similarity index 71% rename from Core/Sources/Headers/training_parameters.h rename to Core/Sources/Headers/training_parameters.hpp index 63b2743..77fbf82 100644 --- a/Core/Sources/Headers/training_parameters.h +++ b/Core/Sources/Headers/training_parameters.hpp @@ -2,12 +2,17 @@ #include #include +#include "type.hpp" + namespace Hippocrates { struct TrainingParameters { + TrainingParameters() = default; + TrainingParameters(std::string json); + struct Ranges { - float minWeight = -1.0f; - float maxWeight = 1.0f; + Type::connection_weight_t minWeight = -8.0f; + Type::connection_weight_t maxWeight = 8.0f; } ranges; struct Mutation { float chanceForWeightMutation = 0.8f; @@ -25,10 +30,13 @@ struct TrainingParameters { struct Reproduction { float chanceForInterspecialReproduction = 0.001f; std::size_t minSpeciesSizeForChampConservation = 5; + float reproductionThreshold = 0.2f; + std::size_t minParents = 1; } reproduction; struct Structure { std::size_t numberOfBiasNeurons = 1; - bool areRecursiveConnectionsAllowed = true; + std::size_t memoryResetsBeforeTotalReset = 0; + bool allowRecurrentConnections = false; } structure; auto GetJSON() const -> std::string; diff --git a/Core/Sources/Headers/type.hpp b/Core/Sources/Headers/type.hpp new file mode 100644 index 0000000..cdebdc6 --- /dev/null +++ b/Core/Sources/Headers/type.hpp @@ -0,0 +1,19 @@ +#pragma once +#include + +namespace Hippocrates { +namespace Type { + +#ifdef _WIN32 +#define HIPPOCRATES_SSCANF sscanf_s +#else +#define HIPPOCRATES_SSCANF sscanf +#endif + +using connection_weight_t = float; +using neuron_value_t = float; +using neuron_values_t = std::vector; +using fitness_t = double; + +} +} \ No newline at end of file diff --git a/Core/Sources/Headers/utility.hpp b/Core/Sources/Headers/utility.hpp new file mode 100644 index 0000000..9e6ea8e --- /dev/null +++ b/Core/Sources/Headers/utility.hpp @@ -0,0 +1,70 @@ +#pragma once +#include +#include + +namespace Hippocrates { + +class Utility { + +public: +template +static auto GetRandomNumberBetween(T min, T max) +{ + std::uniform_int_distribution distribution(min, max); + return distribution(engine); +} + +static inline auto FlipACoin() { + // The currency of the coin is determined by the OS + return GetRandomNumberBetween(0, 1) == 0; +} + +template +static inline auto DidChanceOccure(T chance) { + auto randnum = GetRandomNumberBetween(static_cast(0.0), static_cast(1.0)); + return randnum < chance; +} + +template +static auto GetRandomElement(It begin, It end) { + auto dist = std::distance(begin, end); + if (dist != 0) { + auto rnd = GetRandomNumberBetween(0, static_cast(dist) - 1); + std::advance(begin, rnd); + } + return begin; +} + +template +static auto GetRandomElement(T&& container){ + return GetRandomElement(container.begin(), container.end()); +} + + +template +static auto Shuffle(T&& container) { + return std::shuffle(container.begin(), container.end(), engine); +} + + +private: + static std::random_device randomDevice; + static std::mt19937 engine; +}; + + +template <> +inline auto Utility::GetRandomNumberBetween(float min, float max) +{ + std::uniform_real_distribution distribution(min, max); + return distribution(engine); +} + +template <> +inline auto Utility::GetRandomNumberBetween(double min, double max) +{ + std::uniform_real_distribution distribution(min, max); + return distribution(engine); +} + +} diff --git a/Core/Sources/Implementations/gene.cpp b/Core/Sources/Implementations/gene.cpp index 4d0961c..7d09ddf 100644 --- a/Core/Sources/Implementations/gene.cpp +++ b/Core/Sources/Implementations/gene.cpp @@ -1,30 +1,62 @@ #include -#include "../Headers/gene.h" +#include +#include + +#include "../Headers/gene.hpp" +#include "../Headers/jsmn.h" +#include "../Headers/type.hpp" + using namespace Hippocrates; using namespace std; -size_t Gene::numberOfExistingGenes = 0U; - Gene::Gene() { SetRandomWeight(); } -auto Gene::SetRandomWeight() -> void { - /* - const auto& min = parameters.ranges.minWeight; - const auto& max = parameters.ranges.maxWeight; - if (min == max) { - weight = min; - } - else { - weight = min + static_cast (rand()) / (static_cast (RAND_MAX / (max - min))); +Gene::Gene(std::string json) { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + for (size_t i = 0; i < token_count - 1; i++) { + auto key = json.substr(tokens[i].start, tokens[i].end - tokens[i].start); + auto value = json.substr(tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + + if (key == "historicalMarking") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &historicalMarking); + } else + if (key == "to") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &to); + } else + if (key == "weight") { + weight = stof(value); + } else + if (key == "isEnabled") { + isEnabled = value == "true"; + } else + if (key == "isRecursive") { + isRecursive = value == "true"; + } } - */ - weight = (float)(rand() % 10'000) / 9'999.0f; - if (rand() % 2) { - weight = -weight; +} + +auto Gene::operator==(const Gene & other) const -> bool +{ + if (historicalMarking == other.historicalMarking + || (from == other.from + && to == other.to)) { + if (isRecursive != other.isRecursive) + throw std::logic_error("Two identical genes have different recursive flags"); + return true; } + return false; +} + +auto Gene::SetRandomWeight() -> void { + weight = Utility::GetRandomNumberBetween(-8.0f, 8.0f); } auto Gene::GetJSON() const -> string { diff --git a/Core/Sources/Implementations/genome.cpp b/Core/Sources/Implementations/genome.cpp index e43198c..92dbc01 100644 --- a/Core/Sources/Implementations/genome.cpp +++ b/Core/Sources/Implementations/genome.cpp @@ -1,6 +1,10 @@ #include #include -#include "../Headers/genome.h" +#include + +#include "../Headers/genome.hpp" +#include "../Headers/jsmn.h" +#include "../Headers/type.hpp" using namespace Hippocrates; using namespace std; @@ -22,6 +26,35 @@ Genome::Genome(std::size_t inputCount, std::size_t outputCount, TrainingParamete } } +Genome::Genome(std::string json) { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + for (size_t i = 0; i < token_count - 1; i++) { + auto key = json.substr(tokens[i].start, tokens[i].end - tokens[i].start); + auto value = json.substr(tokens[i+1].start, tokens[i+1].end - tokens[i+1].start); + + if (key == "parameters") { + parameters = TrainingParameters(value); + } else + if (key == "inputCount") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &inputCount); + } else + if (key == "outputCount") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &outputCount); + } else + if (key == "neuronCount") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &neuronCount); + } else + if (key == "genes") { + genes = ParseGenesJson(value); + } + } +} + auto Genome::AppendGene(Gene gene) -> void { AdjustNeuronCount(gene); genes.push_back(std::move(gene)); @@ -32,8 +65,8 @@ auto Genome::InsertGeneAt(Gene gene, size_t index) -> void { genes.insert(genes.begin() + index, std::move(gene)); } -auto Genome::GetGeneticalDistanceFrom(const Genome& other) const -> double { - double totalWeightDifference = 0.0; +auto Genome::GetGeneticalDistanceFrom(const Genome& other) const -> Type::connection_weight_t { + Type::connection_weight_t totalWeightDifference = 0.0; size_t numberOfOverlapingGenes = 0; size_t sizeOfSmallerGenome = min(this->GetGeneCount(), other.GetGeneCount()); @@ -42,30 +75,27 @@ auto Genome::GetGeneticalDistanceFrom(const Genome& other) const -> double { }; for (size_t i = 0; i < sizeOfSmallerGenome && IsHistoricalMarkingSameAt(i); ++i) { - totalWeightDifference += (double)abs(this->GetGeneAt(i).weight - other[i].weight); + totalWeightDifference += (Type::connection_weight_t)abs(this->GetGeneAt(i).weight - other[i].weight); ++numberOfOverlapingGenes; } auto numberOfDisjointGenes = this->GetGeneCount() + other.GetGeneCount() - (size_t)2 * numberOfOverlapingGenes; auto sizeOfBiggerGenome = max(this->GetGeneCount(), other.GetGeneCount()); - // TODO jnf: Think how we'll handle the next line - auto disjointGenesInfluence = (double)numberOfDisjointGenes /* / (double)sizeOfBiggerGenome*/; + // half of the next line has been commented out because stanley's original implementation does it this way, + // despite it not being strictly conform to his paper. + // It makes more sense this way though (see http://sharpneat.sourceforge.net/research/speciation-canonical-neat.html) + auto disjointGenesInfluence = (Type::connection_weight_t)numberOfDisjointGenes /* / (double)sizeOfBiggerGenome*/; - auto averageWeightDifference = totalWeightDifference / (double)numberOfOverlapingGenes; + auto averageWeightDifference = totalWeightDifference / (Type::connection_weight_t)numberOfOverlapingGenes; - disjointGenesInfluence *= (double)parameters.speciation.importanceOfDisjointGenes; - averageWeightDifference *= (double)parameters.speciation.importanceOfAverageWeightDifference; + disjointGenesInfluence *= (Type::connection_weight_t)parameters.speciation.importanceOfDisjointGenes; + averageWeightDifference *= (Type::connection_weight_t)parameters.speciation.importanceOfAverageWeightDifference; return disjointGenesInfluence + averageWeightDifference; } auto Genome::DoesContainGene(const Gene& gene) const -> bool { - for (auto & g : genes) { - if (g.from == gene.from && g.to == gene.to && g.isRecursive == gene.isRecursive) { - return true; - } - } - return false; + return std::find(begin(), end(), gene) != end(); } auto Genome::GetJSON() const -> string { @@ -76,6 +106,8 @@ auto Genome::GetJSON() const -> string { s += std::to_string(inputCount); s += ",\"outputCount\":"; s += std::to_string(outputCount); + s += ",\"neuronCount\":"; + s += std::to_string(neuronCount); s += ",\"genes\":["; for (size_t i = 0; i < genes.size() - 1; ++i) { s += genes[i].GetJSON(); @@ -87,6 +119,24 @@ auto Genome::GetJSON() const -> string { return s; } +auto Genome::ParseGenesJson(std::string json) -> std::vector { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + vector genes; + + for (size_t i = 0; i < token_count - 1; i++) { + if (tokens[i].type == JSMN_OBJECT) { + genes.push_back(Gene(json.substr(tokens[i].start, tokens[i].end - tokens[i].start))); + } + } + + return genes; +} + auto Genome::AdjustNeuronCount(const Gene & gene) -> void { if (gene.to + 1 > neuronCount) { neuronCount = gene.to + 1; diff --git a/Core/Sources/Implementations/innovation_cacher.cpp b/Core/Sources/Implementations/innovation_cacher.cpp new file mode 100644 index 0000000..df341b5 --- /dev/null +++ b/Core/Sources/Implementations/innovation_cacher.cpp @@ -0,0 +1,30 @@ +#include "../Headers/innovation_cacher.hpp" +using namespace Hippocrates; + +auto Hippocrates::InnovationCacher::AssignAndCacheHistoricalMarkings(Genome & genome) -> void { + for (auto& gene: genome) + AssignAndCacheHistoricalMarking(gene); +} + +auto InnovationCacher::AssignAndCacheHistoricalMarking(Gene & gene) -> void { + const auto cachedInnovation = FindGeneInCache(gene); + if (cachedInnovation == cache.end()) { + gene.historicalMarking = GetAndIncrementNextHighestHistoricalMarking(); + cache.push_back(gene); + } + else { + gene.historicalMarking = cachedInnovation->historicalMarking; + } +} + +auto InnovationCacher::Clear() -> void { + cache.clear(); +} + +auto InnovationCacher::GetAndIncrementNextHighestHistoricalMarking() -> std::size_t { + return nextHighestHistoricalMarking++; +} + +auto InnovationCacher::FindGeneInCache(const Gene & gene) -> cache_iterator { + return std::find(cache.begin(), cache.end(), gene); +} diff --git a/Core/Sources/Implementations/jsmn.c b/Core/Sources/Implementations/jsmn.c new file mode 100644 index 0000000..fceb9de --- /dev/null +++ b/Core/Sources/Implementations/jsmn.c @@ -0,0 +1,337 @@ +#include "../Headers/jsmn.h" + +/** + * Allocates a fresh unused token from the token pull. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *tok; + if (parser->toknext >= num_tokens) { + return NULL; + } + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; +#ifdef JSMN_PARENT_LINKS + tok->parent = -1; +#endif + return tok; +} + +/** + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) { + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/** + * Fills next available token with JSON primitive. + */ +static int jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* In strict mode primitive must be followed by "," or "}" or "]" */ + case ':': +#endif + case '\t' : case '\r' : case '\n' : case ' ' : + case ',' : case ']' : case '}' : + goto found; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* In strict mode primitive must be followed by a comma/object/array */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + if (tokens == NULL) { + parser->pos--; + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + parser->pos--; + return 0; +} + +/** + * Fills next token with JSON string. + */ +static int jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, jsmntok_t *tokens, size_t num_tokens) { + jsmntok_t *token; + + int start = parser->pos; + + parser->pos++; + + /* Skip starting quote */ + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + if (tokens == NULL) { + return 0; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos); +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + return 0; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\' && parser->pos + 1 < len) { + int i; + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': case '/' : case '\\' : case 'b' : + case 'f' : case 'r' : case 'n' : case 't' : + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + parser->pos++; + for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) { + /* If it isn't a hex character we have an error */ + if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ + (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ + (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ + parser->pos = start; + return JSMN_ERROR_INVAL; + } + parser->pos++; + } + parser->pos--; + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/** + * Parse JSON string and fill tokens. + */ +int jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) { + int r; + int i; + jsmntok_t *token; + int count = parser->toknext; + + for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': case '[': + count++; + if (tokens == NULL) { + break; + } + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) { + tokens[parser->toksuper].size++; +#ifdef JSMN_PARENT_LINKS + token->parent = parser->toksuper; +#endif + } + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': case ']': + if (tokens == NULL) + break; + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); +#ifdef JSMN_PARENT_LINKS + if (parser->toknext < 1) { + return JSMN_ERROR_INVAL; + } + token = &tokens[parser->toknext - 1]; + for (;;) { + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + token->end = parser->pos + 1; + parser->toksuper = token->parent; + break; + } + if (token->parent == -1) { + if(token->type != type || parser->toksuper == -1) { + return JSMN_ERROR_INVAL; + } + break; + } + token = &tokens[token->parent]; + } +#else + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) { + return JSMN_ERROR_INVAL; + } + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } +#endif + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + case '\t' : case '\r' : case '\n' : case ' ': + break; + case ':': + parser->toksuper = parser->toknext - 1; + break; + case ',': + if (tokens != NULL && parser->toksuper != -1 && + tokens[parser->toksuper].type != JSMN_ARRAY && + tokens[parser->toksuper].type != JSMN_OBJECT) { +#ifdef JSMN_PARENT_LINKS + parser->toksuper = tokens[parser->toksuper].parent; +#else + for (i = parser->toknext - 1; i >= 0; i--) { + if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) { + if (tokens[i].start != -1 && tokens[i].end == -1) { + parser->toksuper = i; + break; + } + } + } +#endif + } + break; +#ifdef JSMN_STRICT + /* In strict mode primitives are: numbers and booleans */ + case '-': case '0': case '1' : case '2': case '3' : case '4': + case '5': case '6': case '7' : case '8': case '9': + case 't': case 'f': case 'n' : + /* And they must not be keys of the object */ + if (tokens != NULL && parser->toksuper != -1) { + jsmntok_t *t = &tokens[parser->toksuper]; + if (t->type == JSMN_OBJECT || + (t->type == JSMN_STRING && t->size != 0)) { + return JSMN_ERROR_INVAL; + } + } +#else + /* In non-strict mode every unquoted value is a primitive */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); + if (r < 0) return r; + count++; + if (parser->toksuper != -1 && tokens != NULL) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + if (tokens != NULL) { + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) { + return JSMN_ERROR_PART; + } + } + } + + return count; +} + +/** + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) { + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +/* + +Copyright (c) 2010 Serge A. Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ diff --git a/Core/Sources/Implementations/logger.cpp b/Core/Sources/Implementations/logger.cpp index fecf35b..c11c89d 100644 --- a/Core/Sources/Implementations/logger.cpp +++ b/Core/Sources/Implementations/logger.cpp @@ -1,5 +1,6 @@ #include -#include "../Headers/logger.h" +#include "../Headers/logger.hpp" + using namespace std; using namespace Hippocrates; @@ -37,12 +38,15 @@ auto Logger::GetCurrentDir(const wchar_t*) -> wstring { auto Logger::GetLogFolder(const string&) -> string { return string("/json_dumps/"); } + auto Logger::GetLogFolder(const wstring&) -> wstring { return wstring(L"/json_dumps/"); } + auto Logger::GetLogFileExtension(const string&) -> string { return string(".json"); } + auto Logger::GetLogFileExtension(const wstring&) -> wstring { return wstring(L".json"); } @@ -55,6 +59,14 @@ auto Logger::GetSessionDir(const wstring& dumpDir) -> wstring { return wstring(dumpDir + to_wstring(timestamp.time_since_epoch().count()) + L"/"); } +auto Logger::GetMetadataFileName(const std::string &sessionDir) -> std::string { + return sessionDir + "meta" + GetLogFileExtension(sessionDir); +} + +auto Logger::GetMetadataFileName(const std::wstring &sessionDir) -> std::wstring { + return sessionDir + L"meta" + GetLogFileExtension(sessionDir); +} + auto Logger::GetLogFileName(const string& sessionDir, size_t generationsPassed) -> string { return string(sessionDir + "generation_" + to_string(generationsPassed) + GetLogFileExtension(sessionDir)); } @@ -93,3 +105,29 @@ auto Logger::LogGeneration(size_t generation, const std::string& log) -> void { #endif throw runtime_error("No logging directory found. Did you forget to call Logger::CreateLoggingDirs()?"); } + +auto Logger::LogMetadata(Type::fitness_t maxFitness) -> void { + metaData += "{\"max_fitness\":" + to_string(maxFitness) + "},"; + auto logToPrint = "{\"generations\":[" + metaData.substr(0, metaData.length() - 1) + "]}"; + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) + if (!fullLoggingPathOnWindows.empty()) { + auto logFileName = GetMetadataFileName(fullLoggingPathOnWindows); + ofstream logFile(logFileName); + logFile << logToPrint; + logFile.close(); + + return; + } +#else + if (!fullLoggingPathOnUnix.empty()) { + auto logFileName = GetMetadataFileName(fullLoggingPathOnUnix); + ofstream logFile(logFileName); + logFile << logToPrint; + logFile.close(); + + return; + } +#endif + throw runtime_error("No logging directory found. Did you forget to call Logger::CreateLoggingDirs()?"); +} diff --git a/Core/Sources/Implementations/neural_network.cpp b/Core/Sources/Implementations/neural_network.cpp index ff3c9c5..d5853f8 100644 --- a/Core/Sources/Implementations/neural_network.cpp +++ b/Core/Sources/Implementations/neural_network.cpp @@ -1,47 +1,64 @@ #include -#include "../Headers/neural_network.h" +#include +#include +#include +#include "../Headers/neural_network.hpp" +#include "../Headers/jsmn.h" using namespace Hippocrates; using namespace std; - -NeuralNetwork::NeuralNetwork(const Genome& genome, bool shouldMutate) : - genome(genome), - inputNeurons(genome.GetInputCount()), - outputNeurons(genome.GetOutputCount()) +NeuralNetwork::NeuralNetwork(const Genome& genome) : + genome{genome} { - if (shouldMutate) { - MutateGenesAndBuildNetwork(); - } - else { - BuildNetworkFromGenes(); - } + BuildNetworkFromGenes(); +} +NeuralNetwork::NeuralNetwork(Genome&& genome) : + genome {std::move(genome)} +{ + BuildNetworkFromGenes(); +} +NeuralNetwork::NeuralNetwork(const Genome& genome, InnovationCacher& currGenerationInnovations) : + genome(genome) +{ + MutateGenesAndBuildNetwork(currGenerationInnovations); } -NeuralNetwork::NeuralNetwork(Genome&& genome, bool shouldMutate) : - genome(move(genome)), - inputNeurons(genome.GetInputCount()), - outputNeurons(genome.GetOutputCount()) +NeuralNetwork::NeuralNetwork(Genome&& genome, InnovationCacher& currGenerationInnovations) : + genome(move(genome)) { - if (shouldMutate) { - MutateGenesAndBuildNetwork(); - } - else { - BuildNetworkFromGenes(); + MutateGenesAndBuildNetwork(currGenerationInnovations); +} + +NeuralNetwork::NeuralNetwork(const std::string& json) { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + for (size_t i = 0; i < token_count - 1; i++) { + auto key = json.substr(tokens[i].start, tokens[i].end - tokens[i].start); + auto value = json.substr(tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + + if (key == "genome") { + genome = Genome(value); + } else + if (key == "neurons") { + neurons = ParseNeuronsJson(value); + } } + + BuildNetworkFromGenes(); } NeuralNetwork::NeuralNetwork(const NeuralNetwork& other) : - genome(other.genome), - inputNeurons(other.inputNeurons.size()), - outputNeurons(other.outputNeurons.size()) + genome(other.genome) { BuildNetworkFromGenes(); } auto NeuralNetwork::operator=(const NeuralNetwork& other) -> NeuralNetwork& { genome = other.genome; - inputNeurons.resize(other.inputNeurons.size()); - outputNeurons.resize(other.outputNeurons.size()); neurons.clear(); neurons.reserve(other.neurons.size()); layerMap.clear(); @@ -67,11 +84,16 @@ auto NeuralNetwork::BuildNetworkFromGenes() -> void { neurons[gene.from].AddConnection(move(out)); } } - InterpretInputsAndOutputs(); + + for (auto i = 0U; i < GetTrainingParameters().structure.numberOfBiasNeurons; i++) { + neurons[i].SetInput(1.0f); + } + CategorizeNeuronsIntoLayers(); } -auto NeuralNetwork::SetInputs(vector inputs) -> void { +auto NeuralNetwork::SetInputs(Type::neuron_values_t inputs) -> void { + auto inputNeurons = GetInputNeurons(); if (inputNeurons.size() != inputs.size()) { throw out_of_range("Number of inputs provided doesn't match genetic information"); } @@ -80,13 +102,14 @@ auto NeuralNetwork::SetInputs(vector inputs) -> void { }; } -auto NeuralNetwork::GetOutputs() -> vector { +auto NeuralNetwork::GetOutputs() -> Type::neuron_values_t { for (size_t i = 1; i < layerMap.size() - 1; ++i) { for (auto& neuron : layerMap[i]) { neuron->RequestDataAndGetActionPotential(); } } - vector outputs; + Type::neuron_values_t outputs; + const auto outputNeurons = GetOutputNeurons(); outputs.reserve(outputNeurons.size()); for (auto& outputNeuron : outputNeurons) { outputs.push_back(outputNeuron->RequestDataAndGetActionPotential()); @@ -94,69 +117,89 @@ auto NeuralNetwork::GetOutputs() -> vector { return outputs; } -auto NeuralNetwork::GetOutputsUsingInputs(vector inputs) -> vector { - SetInputs(move(inputs)); - return GetOutputs(); +auto Hippocrates::NeuralNetwork::GetOutputNeurons() -> std::vector { + return GetNeuronsByRangeAndIndex(genome.GetOutputCount(), [&](std::size_t i) { + return genome[i].to; + } + ); } -auto NeuralNetwork::InterpretInputsAndOutputs() -> void { - // Bias - for (auto i = 0U; i < GetTrainingParameters().structure.numberOfBiasNeurons; i++) { - neurons[i].SetInput(1.0f); +auto Hippocrates::NeuralNetwork::GetOutputNeurons() const -> std::vector { + return GetNeuronsByRangeAndIndex(genome.GetOutputCount(), [&](std::size_t i) { + return genome[i].to; } + ); +} - // Inputs - for (auto i = 0U; i < genome.GetInputCount(); i++) { - inputNeurons[i] = &neurons[i + GetTrainingParameters().structure.numberOfBiasNeurons]; +auto Hippocrates::NeuralNetwork::GetInputNeurons() -> std::vector { + return GetNeuronsByRangeAndIndex(genome.GetInputCount(), [&](std::size_t i) { + return i + GetTrainingParameters().structure.numberOfBiasNeurons; } + ); +} - // Outputs - for (auto i = 0U; i < genome.GetOutputCount(); i++) { - outputNeurons[i] = &neurons[genome[i].to]; +auto Hippocrates::NeuralNetwork::GetInputNeurons() const -> std::vector { + return GetNeuronsByRangeAndIndex(genome.GetInputCount(), [&](std::size_t i) { + return i + GetTrainingParameters().structure.numberOfBiasNeurons; } + ); +} + +auto NeuralNetwork::GetOutputsUsingInputs(Type::neuron_values_t inputs) -> Type::neuron_values_t { + SetInputs(move(inputs)); + return GetOutputs(); } auto NeuralNetwork::ShouldAddNeuron() const -> bool { - return DidChanceOccure( - GetTrainingParameters(). - + return Utility::DidChanceOccure( + GetTrainingParameters(). mutation. chanceForNeuralMutation ); } auto NeuralNetwork::ShouldAddConnection() const -> bool { - const bool hasChanceOccured = DidChanceOccure(GetTrainingParameters().mutation.chanceForConnectionalMutation); - if (!hasChanceOccured) { + const auto chance = + GetTrainingParameters(). + mutation. + chanceForConnectionalMutation; + if (!Utility::DidChanceOccure(chance)) { return false; } - const size_t inputLayerSize = genome.GetInputCount() + GetTrainingParameters().structure.numberOfBiasNeurons; - const size_t outputLayerSize = genome.GetOutputCount(); - const size_t hiddenLayerSize = genome.GetNeuronCount() - inputLayerSize - outputLayerSize; - size_t numberOfPossibleConnections = hiddenLayerSize * (hiddenLayerSize - 1); - numberOfPossibleConnections += hiddenLayerSize * inputLayerSize; - numberOfPossibleConnections += hiddenLayerSize * outputLayerSize; - const size_t generatedNeurons = genome.GetNeuronCount() - (inputLayerSize + genome.GetOutputCount()); - const bool hasSpaceForNewConnections = genome.GetGeneCount() < (numberOfPossibleConnections + generatedNeurons); - return hasSpaceForNewConnections; + const auto inputLayerSize = genome.GetInputCount() + GetTrainingParameters().structure.numberOfBiasNeurons; + const auto outputLayerSize = genome.GetOutputCount(); + const auto hiddenLayerSize = genome.GetNeuronCount() - inputLayerSize - outputLayerSize; + + + const auto startingConnections = inputLayerSize * outputLayerSize; + auto hiddenConnections = hiddenLayerSize * (hiddenLayerSize - 1); + if (!GetTrainingParameters().structure.allowRecurrentConnections) { + hiddenConnections /= 2; + } + const auto connectionsFromInputs = inputLayerSize * hiddenLayerSize; + auto connectionsToOutputs = outputLayerSize * hiddenLayerSize; + if (GetTrainingParameters().structure.allowRecurrentConnections) { + connectionsToOutputs *= 2; + } + + const auto possibleConnections = + startingConnections + + hiddenConnections + + connectionsFromInputs + + connectionsToOutputs; + return genome.GetGeneCount() < possibleConnections; } auto NeuralNetwork::ShouldMutateWeight() const -> bool { - return DidChanceOccure( - GetTrainingParameters(). - + return Utility::DidChanceOccure( + GetTrainingParameters(). mutation. chanceForWeightMutation ); } -auto NeuralNetwork::DidChanceOccure(float chance) -> bool { - auto num = rand() % 100; - return num < int(100.0f * chance); -} - -auto NeuralNetwork::AddRandomNeuron() -> void { +auto NeuralNetwork::AddRandomNeuron(InnovationCacher& currGenerationInnovations) -> void { auto& randGene = GetRandomEnabledGene(); auto indexOfNewNeuron = genome.GetNeuronCount(); @@ -165,12 +208,14 @@ auto NeuralNetwork::AddRandomNeuron() -> void { g1.to = indexOfNewNeuron; g1.weight = 1.0f; g1.isRecursive = randGene.isRecursive; + currGenerationInnovations.AssignAndCacheHistoricalMarking(g1); Gene g2; g2.from = indexOfNewNeuron; g2.to = randGene.to; g2.weight = randGene.weight; g2.isRecursive = randGene.isRecursive; + currGenerationInnovations.AssignAndCacheHistoricalMarking(g2); randGene.isEnabled = false; @@ -178,24 +223,26 @@ auto NeuralNetwork::AddRandomNeuron() -> void { genome.AppendGene(move(g2)); } -auto NeuralNetwork::AddRandomConnection() -> void { +auto NeuralNetwork::AddRandomConnection(InnovationCacher& currGenerationInnovations) -> void { // Data auto NeuronPair(GetTwoUnconnectedNeurons()); - auto& fromNeuron = *NeuronPair.first; - auto& toNeuron = *NeuronPair.second; + auto& fromNeuron = NeuronPair.first; + auto& toNeuron = NeuronPair.second; // Gene Gene newConnectionGene; - while (&neurons[newConnectionGene.from] != &fromNeuron) { - newConnectionGene.from++; - } - while (&neurons[newConnectionGene.to] != &toNeuron) { - newConnectionGene.to++; - } + newConnectionGene.from = std::distance(&*neurons.begin(), &fromNeuron); + newConnectionGene.to = std::distance(&*neurons.begin(), &toNeuron); + if (fromNeuron.GetLayer() > toNeuron.GetLayer()) { + if (!GetTrainingParameters().structure.allowRecurrentConnections) { + throw std::runtime_error("Created illegal recurrent connection"); + } newConnectionGene.isRecursive = true; } + currGenerationInnovations.AssignAndCacheHistoricalMarking(newConnectionGene); + // Connection Neuron::Connection in; in.isRecursive = newConnectionGene.isRecursive; @@ -214,22 +261,20 @@ auto NeuralNetwork::AddRandomConnection() -> void { CategorizeNeuronsIntoLayers(); } -auto NeuralNetwork::GetTwoUnconnectedNeurons() -> pair { - vector possibleFromNeurons; - possibleFromNeurons.reserve(neurons.size()); - for (auto& n : neurons) { - possibleFromNeurons.push_back(&n); - } +auto NeuralNetwork::GetTwoUnconnectedNeurons() -> pair { + using NeuronRefs = vector>; + NeuronRefs possibleFromNeurons(neurons.begin(), neurons.end()); + auto inputRange = genome.GetInputCount() + GetTrainingParameters().structure.numberOfBiasNeurons; - vector possibleToNeurons(possibleFromNeurons.begin() + inputRange, possibleFromNeurons.end()); + NeuronRefs possibleToNeurons(neurons.begin() + inputRange, neurons.end()); - random_shuffle(possibleFromNeurons.begin(), possibleFromNeurons.end()); - random_shuffle(possibleToNeurons.begin(), possibleToNeurons.end()); + Utility::Shuffle(possibleFromNeurons); + Utility::Shuffle(possibleToNeurons); - for (auto* from : possibleFromNeurons) { - for (auto* to : possibleToNeurons) { - if (CanNeuronsBeConnected(*from, *to)) { + for (auto from : possibleFromNeurons) { + for (auto to : possibleToNeurons) { + if (CanNeuronsBeConnected(from, to)) { return{ from, to }; } } @@ -238,15 +283,25 @@ auto NeuralNetwork::GetTwoUnconnectedNeurons() -> pair { throw runtime_error("Tried to get two unconnected Neurons while every neuron is already connected"); } -auto Hippocrates::NeuralNetwork::CanNeuronsBeConnected(const Neuron & lhs, const Neuron & rhs) const -> bool { - bool AreNeuronsTheSame = &lhs == &rhs; - return (!AreNeuronsTheSame && !AreBothNeuronsOutputs(lhs, rhs) && !AreNeuronsConnected(lhs, rhs)); +auto Hippocrates::NeuralNetwork::CanNeuronsBeConnected(const Neuron& lhs, const Neuron& rhs) const -> bool { + auto areNeuronsTheSame = &lhs == &rhs; + auto canBeConnected = + !areNeuronsTheSame + && !AreBothNeuronsOutputs(lhs, rhs) + && !AreNeuronsConnected(lhs, rhs); + + if (!GetTrainingParameters().structure.allowRecurrentConnections) { + auto isRecurrent = lhs.GetLayer() > rhs.GetLayer(); + canBeConnected = canBeConnected && !isRecurrent; + } + + return canBeConnected; } -auto NeuralNetwork::AreBothNeuronsOutputs(const Neuron &lhs, const Neuron &rhs) const -> bool { +auto NeuralNetwork::AreBothNeuronsOutputs(const Neuron& lhs, const Neuron& rhs) const -> bool { bool isLhsOutput = false; bool isRhsOutput = false; - for (const auto& output : outputNeurons) { + for (const auto& output : GetOutputNeurons()) { if (output == &lhs) { isLhsOutput = true; } @@ -278,7 +333,7 @@ auto NeuralNetwork::ShuffleWeights() -> void { } auto NeuralNetwork::MutateWeightOfGeneAt(size_t index) -> void { - if (DidChanceOccure(GetTrainingParameters().mutation.chanceOfTotalWeightReset)) { + if (Utility::DidChanceOccure(GetTrainingParameters().mutation.chanceOfTotalWeightReset)) { genome[index].SetRandomWeight(); } else { @@ -287,28 +342,28 @@ auto NeuralNetwork::MutateWeightOfGeneAt(size_t index) -> void { } auto NeuralNetwork::PerturbWeightAt(size_t index) -> void { - constexpr float perturbanceBoundaries = 0.5f; - auto perturbance = static_cast(rand() % 10'000) / 9'999.0f * perturbanceBoundaries; - if (rand() % 2) { - perturbance = -perturbance; - } + constexpr auto perturbRange = 2.5f; + auto perturbance = Utility::GetRandomNumberBetween(-perturbRange, perturbRange); genome[index].weight += perturbance; - if (genome[index].weight < -1.0f) { - genome[index].weight = -1.0f; + // In C++17 + // std::clamp(genome[index].weight, -1.0f, 1.0f)); + if (genome[index].weight < -8.0f) { + genome[index].weight = -8.0f; } - else if (genome[index].weight > 1.0f) { - genome[index].weight = 1.0f; + else if (genome[index].weight > 8.0f) { + genome[index].weight = 8.0f; } + } -auto NeuralNetwork::MutateGenesAndBuildNetwork() -> void { +auto NeuralNetwork::MutateGenesAndBuildNetwork(InnovationCacher& currGenerationInnovations) -> void { if (ShouldAddConnection()) { BuildNetworkFromGenes(); - AddRandomConnection(); + AddRandomConnection(currGenerationInnovations); } else { if (ShouldAddNeuron()) { - AddRandomNeuron(); + AddRandomNeuron(currGenerationInnovations); } else { ShuffleWeights(); } @@ -321,11 +376,12 @@ auto NeuralNetwork::CategorizeNeuronsIntoLayers() -> void { for (auto i = 0U; i < GetTrainingParameters().structure.numberOfBiasNeurons; i++) { CategorizeNeuronBranchIntoLayers(neurons[i]); } - for (auto* in : inputNeurons) { + for (auto* in : GetInputNeurons()) { CategorizeNeuronBranchIntoLayers(*in); } size_t highestLayer = 0U; + auto outputNeurons = GetOutputNeurons(); for (auto* out : outputNeurons) { highestLayer = max(out->GetLayer(), highestLayer); } @@ -358,7 +414,7 @@ auto NeuralNetwork::CategorizeNeuronBranchIntoLayers(Neuron& currNode, size_t cu } auto NeuralNetwork::GetRandomEnabledGene() -> Gene& { - size_t num = rand() % genome.GetGeneCount(); + size_t num = Utility::GetRandomNumberBetween(0ULL, genome.GetGeneCount() - 1ULL); auto randGene = genome.begin(); randGene += num; while (randGene != genome.end() && !randGene->isEnabled) { @@ -378,13 +434,37 @@ auto NeuralNetwork::GetRandomEnabledGene() -> Gene& { auto NeuralNetwork::GetJSON() const -> string { string s("{\"neurons\":["); - for (size_t i = 0; i < neurons.size() - 1; ++i) { - s += neurons[i].GetJSON(); + for (const auto& neuron : neurons) { + s += neuron.GetJSON(); s += ","; } - s += neurons.back().GetJSON(); + s.pop_back(); s += "],\"genome\":"; s += genome.GetJSON(); s += "}"; return s; } + +auto NeuralNetwork::Reset() -> void { + for (auto& neuron : neurons) { + neuron.Reset(); + } +} + +auto NeuralNetwork::ParseNeuronsJson(std::string json) -> std::vector { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + vector neurons; + + for (size_t i = 0; i < token_count - 1; i++) { + if (tokens[i].type == JSMN_OBJECT) { + neurons.push_back(Neuron(json.substr(tokens[i].start, tokens[i].end - tokens[i].start))); + } + } + + return neurons; +} diff --git a/Core/Sources/Implementations/neural_network_trainer.cpp b/Core/Sources/Implementations/neural_network_trainer.cpp index 58d9906..044f5f9 100644 --- a/Core/Sources/Implementations/neural_network_trainer.cpp +++ b/Core/Sources/Implementations/neural_network_trainer.cpp @@ -1,4 +1,4 @@ -#include "../Headers/neural_network_trainer.h" +#include "../Headers/neural_network_trainer.hpp" using namespace Hippocrates; using namespace std; @@ -9,7 +9,7 @@ NeuralNetworkTrainer::NeuralNetworkTrainer(TrainingParameters parameters) : { } -auto NeuralNetworkTrainer::TrainUnsupervised(SpeciesManager::Bodies& bodies) -> TrainedNeuralNetwork { +auto NeuralNetworkTrainer::TrainUnsupervised(SpeciesManager::Bodies& bodies) -> Trained::TrainedNeuralNetwork { if (loggingEnabled) { logger.CreateLoggingDirs(); } @@ -22,7 +22,7 @@ auto NeuralNetworkTrainer::TrainUnsupervised(SpeciesManager::Bodies& bodies) -> TrainGenerationAndLogUsingBodies(bodies); champ = &species.GetFittestOrganism(); } - return TrainedNeuralNetwork(champ->GetGenome()); + return Trained::TrainedNeuralNetwork(champ->GetGenome()); } auto NeuralNetworkTrainer::TrainGenerationAndLogUsingBodies(SpeciesManager::Bodies& bodies) -> void { @@ -31,6 +31,7 @@ auto NeuralNetworkTrainer::TrainGenerationAndLogUsingBodies(SpeciesManager::Bodi generationsPassed++; if (loggingEnabled) { logger.LogGeneration(generationsPassed, GetJSON()); + logger.LogMetadata(species.GetFittestOrganism().GetOrCalculateRawFitness()); } } diff --git a/Core/Sources/Implementations/neuron.cpp b/Core/Sources/Implementations/neuron.cpp index 4e8459f..ba2f4e6 100644 --- a/Core/Sources/Implementations/neuron.cpp +++ b/Core/Sources/Implementations/neuron.cpp @@ -1,6 +1,9 @@ #include #include -#include "../Headers/neuron.h" +#include "../Headers/neuron.hpp" +#include "../Headers/jsmn.h" +#include "../Headers/utility.hpp" +#include "../Headers/type.hpp" using namespace Hippocrates; using namespace std; @@ -9,6 +12,26 @@ Neuron::Neuron(vector connections) : connections(std::move(connections)) { } +Neuron::Neuron(std::string json) { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + for (size_t i = 0; i < token_count - 1; i++) { + auto key = json.substr(tokens[i].start, tokens[i].end - tokens[i].start); + auto value = json.substr(tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + + if (key == "lastActionPotential") { + lastActionPotential = stof(value); + } else + if (key == "layer") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &layer); + } + } +} + auto Neuron::AddConnection(Connection connection) -> void { if (connection.neuron == this || connection.neuron == nullptr) { throw invalid_argument("Invalid incoming connection"); @@ -16,8 +39,8 @@ auto Neuron::AddConnection(Connection connection) -> void { connections.push_back(move(connection)); } -auto Neuron::RequestDataAndGetActionPotential() -> float { - float incomingPotentials = 0.0f; +auto Neuron::RequestDataAndGetActionPotential() -> Type::neuron_value_t { + Type::neuron_value_t incomingPotentials = 0.0f; for (auto& in : connections) { if (!in.outGoing) { incomingPotentials += in.neuron->lastActionPotential * in.weight; @@ -27,15 +50,14 @@ auto Neuron::RequestDataAndGetActionPotential() -> float { return lastActionPotential; } -auto Neuron::sigmoid(float d) -> float { +auto Neuron::sigmoid(Type::neuron_value_t d) -> Type::neuron_value_t { return tanh(d); } -auto Neuron::SetInput(float input) -> void { +auto Neuron::SetInput(Type::neuron_value_t input) -> void { lastActionPotential = input; } - string Neuron::GetJSON() const { string s("{\"layer\":"); s += to_string(layer); @@ -46,3 +68,6 @@ string Neuron::GetJSON() const { return s; } +void Neuron::Reset() { + lastActionPotential = 0.0f; +} diff --git a/Core/Sources/Implementations/organism.cpp b/Core/Sources/Implementations/organism.cpp index fbc0451..677b432 100644 --- a/Core/Sources/Implementations/organism.cpp +++ b/Core/Sources/Implementations/organism.cpp @@ -1,6 +1,6 @@ #include #include -#include "../Headers/organism.h" +#include "../Headers/organism.hpp" using namespace Hippocrates; using namespace std; @@ -12,17 +12,27 @@ Organism::Organism(IBody& body, NeuralNetwork&& network) : } auto Organism::Update() -> void { - const auto inputs(move(body->ProvideNetworkWithInputs())); - const auto outputs(move(network.GetOutputsUsingInputs(inputs))); - body->Update(outputs); - isFitnessUpToDate = false; + size_t numberOfTimesToFinishTask = 1; + + if (GetTrainingParameters().structure.allowRecurrentConnections) { + numberOfTimesToFinishTask = GetTrainingParameters().structure.memoryResetsBeforeTotalReset; + } + + for (size_t i = 0; i < numberOfTimesToFinishTask; i++) { + while (!body->HasFinishedTask()) { + const auto inputs(move(body->ProvideNetworkWithInputs())); + const auto outputs(move(network.GetOutputsUsingInputs(inputs))); + body->Update(outputs); + isFitnessUpToDate = false; + } + } } -auto Organism::GetOrCalculateFitness() const -> double { +auto Organism::GetOrCalculateFitness() const -> Type::fitness_t { return GetOrCalculateRawFitness() * fitnessModifier; } -auto Organism::GetOrCalculateRawFitness() const -> double { +auto Organism::GetOrCalculateRawFitness() const -> Type::fitness_t { if (!isFitnessUpToDate) { fitness = body->GetFitness(); isFitnessUpToDate = true; @@ -30,28 +40,29 @@ auto Organism::GetOrCalculateRawFitness() const -> double { return fitness; } -auto Organism::BreedWith(Organism& partner) -> NeuralNetwork { +auto Organism::BreedWith(const Organism& partner, InnovationCacher& currGenerationInnovations) const -> NeuralNetwork { auto parentsHaveSameFitness = this->GetOrCalculateFitness() == partner.GetOrCalculateFitness(); - Organism* dominantParent = nullptr; + const Organism* dominantParent = nullptr; if (parentsHaveSameFitness) { - dominantParent = rand() % 2 == 0 ? this : &partner; + dominantParent = Utility::FlipACoin() ? this : &partner; } else { dominantParent = this->GetOrCalculateFitness() > partner.GetOrCalculateFitness() ? this : &partner; } + // TODO jnf: conform to the paper auto childGenome(dominantParent->GetGenome()); const auto sizeOfSmallerParent = min(this->GetGenome().GetGeneCount(), partner.GetGenome().GetGeneCount()); auto& partnerGenome = partner.GetGenome(); - auto AreMarkingsSameAt = [&](size_t i) { - return childGenome[i].historicalMarking == partnerGenome[i].historicalMarking; + auto AreSameAt = [&](size_t i) { + return childGenome[i] == partnerGenome[i]; }; - for (size_t i = 0U; i < sizeOfSmallerParent && AreMarkingsSameAt(i); ++i) { - if (rand() % 2 == 0) { + for (size_t i = 0U; i < sizeOfSmallerParent && AreSameAt(i); ++i) { + if (Utility::FlipACoin()) { childGenome[i].weight = partnerGenome[i].weight; } } - NeuralNetwork child(move(childGenome), true); + NeuralNetwork child(move(childGenome), currGenerationInnovations); return child; } diff --git a/Core/Sources/Implementations/species.cpp b/Core/Sources/Implementations/species.cpp index 58e800c..73db78d 100644 --- a/Core/Sources/Implementations/species.cpp +++ b/Core/Sources/Implementations/species.cpp @@ -1,11 +1,10 @@ #include -#include "../Headers/species.h" +#include "../Headers/species.hpp" using namespace Hippocrates; using namespace std; Species::Species(Organism representative): -parameters(representative.GetTrainingParameters()), representative(representative) { population.push_back(move(representative)); @@ -13,40 +12,46 @@ representative(representative) auto Species::AddOrganism(Organism&& organism) -> void { population.push_back(move(organism)); - ElectRepresentative(); isSortedByFitness = false; SetPopulationsFitnessModifier(); } -auto Species::AnalyzePopulation() -> void { - const auto currentBestFitness = GetFittestOrganism().GetOrCalculateRawFitness(); - if (currentBestFitness > fitnessHighscore) { - fitnessHighscore = currentBestFitness; - numberOfStagnantGenerations = 0; - } - else { - numberOfStagnantGenerations++; - } - isSortedByFitness = false; -} - - auto Species::IsCompatible(const Genome& genome) const -> bool { auto distanceToSpecies = representative.GetGenome().GetGeneticalDistanceFrom(genome); return !IsAboveCompatibilityThreshold(distanceToSpecies); } auto Species::SetPopulationsFitnessModifier() -> void { - double fitnessModifier = 1.0 / static_cast(population.size()); + auto fitnessModifier = static_cast(1.0) / static_cast(population.size()); for (auto& organism : population) { organism.SetFitnessModifier(fitnessModifier); } } auto Species::ClearPopulation() -> void { + const auto currentBestFitness = GetFittestOrganism().GetOrCalculateRawFitness(); + if (currentBestFitness > fitnessHighscore) { + fitnessHighscore = currentBestFitness; + numberOfStagnantGenerations = 0; + } else { + numberOfStagnantGenerations++; + } + + ElectRepresentative(); population.clear(); } +auto Species::RemoveWorst() -> void { + const auto threshold = GetTrainingParameters().reproduction.reproductionThreshold; + const auto size = static_cast(GetSize()); + const auto numberOfPotentionalParents = static_cast(size * threshold); + + const auto minParents = GetTrainingParameters().reproduction.minParents; + const auto lastParent = population.begin() + std::max(numberOfPotentionalParents, minParents); + + population.erase(lastParent, population.end()); +} + auto Species::ElectRepresentative() -> void { if (!population.empty()) { SelectRandomRepresentative(); @@ -55,8 +60,7 @@ auto Species::ElectRepresentative() -> void { } auto Species::SelectRandomRepresentative() -> void { - auto randomMember = rand() % population.size(); - representative = population[randomMember]; + representative = *Utility::GetRandomElement(population); } auto Species::SelectFittestOrganismAsRepresentative() -> void { @@ -64,18 +68,33 @@ auto Species::SelectFittestOrganismAsRepresentative() -> void { } auto Hippocrates::Species::IsStagnant() const -> bool { - return numberOfStagnantGenerations >= - parameters. + return numberOfStagnantGenerations >= + GetTrainingParameters(). speciation. stagnantSpeciesClearThreshold; } +auto Species::GetOffspringCount(Type::fitness_t averageFitness) const -> std::size_t { + if (IsStagnant()) + return 0; + + if (averageFitness == 0.0) + return GetSize(); + + + std::size_t offspringCount = 0; + for (auto & organism : population) { + // TODO jnf: Should we round this? + offspringCount += static_cast(std::round(organism.GetOrCalculateFitness() / averageFitness)); + } + return offspringCount; +} + auto Species::LetPopulationLive() -> void { for (auto& organism : population) { - while (!organism.HasFinishedTask()) { - organism.Update(); - } + organism.Update(); } + isSortedByFitness = false; } @@ -86,9 +105,6 @@ auto Species::ResetToTeachableState() -> void { } auto Species::GetFittestOrganism() const -> const Organism& { - if (population.empty()) { - return representative; - } SortPopulationIfNeeded(); return population.front(); } @@ -103,26 +119,15 @@ auto Species::SortPopulationIfNeeded() const -> void { } } -auto Species::operator=(Species&& other) noexcept -> Species& { - population = move(other.population); - representative = move(other.representative); - isSortedByFitness = move(other.isSortedByFitness); - numberOfStagnantGenerations = move(other.numberOfStagnantGenerations); - fitnessHighscore = move(other.fitnessHighscore); - return *this; -} -auto Species::GetOrganismToBreed() -> Organism& { +auto Species::GetOrganismToBreed() const -> const Organism& { // TODO jnf: Switch to stochastic universal sampling - if (population.empty()) { - return representative; - } - double totalPopulationFitness = 0.0; + Type::fitness_t totalPopulationFitness = 0.0; for (auto& organism : population) { totalPopulationFitness += organism.GetOrCalculateFitness(); } if (totalPopulationFitness == 0) { - return population[rand() % population.size()]; + return *Utility::GetRandomElement(population); } double chance = 0.0; auto GetChanceForOrganism = [&chance, &totalPopulationFitness](const Organism& organism) { @@ -131,11 +136,9 @@ auto Species::GetOrganismToBreed() -> Organism& { while (true) { for (auto& organism : population) { - double randNum = static_cast(rand() % 10'000) / 9'999.0;; chance = GetChanceForOrganism(organism); - if (randNum < chance) { + if (Utility::DidChanceOccure(chance)) return organism; - } } } } @@ -157,3 +160,14 @@ auto Species::GetJSON() const -> string { return s; } +auto Species::GetAverageFitness() const -> Type::fitness_t { + return GetTotalFitness() / GetSize(); +} + +auto Species::GetTotalFitness() const -> Type::fitness_t { + auto totalFitness = 0.0; + for (auto & organism : population) { + totalFitness += organism.GetOrCalculateFitness(); + } + return totalFitness; +} diff --git a/Core/Sources/Implementations/species_manager.cpp b/Core/Sources/Implementations/species_manager.cpp index 7e0c243..b3f4dbc 100644 --- a/Core/Sources/Implementations/species_manager.cpp +++ b/Core/Sources/Implementations/species_manager.cpp @@ -1,10 +1,12 @@ -#include "../Headers/species_manager.h" +#include "../Headers/species_manager.hpp" #include using namespace Hippocrates; auto SpeciesManager::CreateInitialOrganisms(Bodies& bodies) -> void { + species.clear(); for (auto& currTrainer : bodies) { Genome standardGenes(currTrainer.get().GetInputCount(), currTrainer.get().GetOutputCount(), parameters); + currGenerationInnovations.AssignAndCacheHistoricalMarkings(standardGenes); NeuralNetwork network(std::move(standardGenes)); Organism organism(currTrainer, std::move(network)); FillOrganismIntoSpecies(std::move(organism)); @@ -12,23 +14,53 @@ auto SpeciesManager::CreateInitialOrganisms(Bodies& bodies) -> void { } auto SpeciesManager::Repopulate(Bodies& bodies) -> void { - PrepareSpeciesForPopulation(); - auto DidChanceOccure = [](float chance) { - auto num = rand() % 1000; - return num < int(1000.0f * chance); - }; + auto populationCount = GetPopulationCount(); + auto averageFitness = GetAverageFitness(); + std::vector newGeneration; newGeneration.reserve(bodies.size()); - for (auto& body : bodies) { - auto sp = &GetSpeciesToBreed(); - auto& father = sp->GetOrganismToBreed(); - if (DidChanceOccure(parameters.reproduction.chanceForInterspecialReproduction)) { - sp = &GetSpeciesToBreed(); + + auto currBody = bodies.begin(); + auto EmplaceChild = [&](NeuralNetwork && network) { + newGeneration.emplace_back(*currBody, std::forward(network)); + ++currBody; + }; + + auto Breed = [&](const Species & species) { + auto child = BreedInSpecies(species); + EmplaceChild(std::move(child)); + }; + + auto CloneChamp = [&](const Species & species) { + auto champNetwork = species.GetFittestOrganism().GetNeuralNetwork(); + EmplaceChild(std::move(champNetwork)); + }; + + currGenerationInnovations.Clear(); + // In the original implementation the offspring were tossed directly into their new species and, as a result, in the mating pool. + // We instead separate the generations + for (auto& s : species) { + auto offspringCount = s.GetOffspringCount(averageFitness); + offspringCount = std::min(offspringCount, s.GetSize()); + s.RemoveWorst(); + + if (offspringCount >= 1 + && s.GetSize() > parameters.reproduction.minSpeciesSizeForChampConservation) { + CloneChamp(s); + offspringCount--; + } + + for (std::size_t i = 0; i < offspringCount; ++i) { + Breed(s); } - auto& mother = sp->GetOrganismToBreed(); - auto childNeuralNetwork(std::move(father.BreedWith(mother))); - newGeneration.emplace_back(body, std::move(childNeuralNetwork)); } + + // Account for rounding Errors + while (newGeneration.size() < populationCount) { + Breed(GetFittestSpecies()); + } + + ClearSpeciesPopulation(); for (auto&& child : newGeneration) { FillOrganismIntoSpecies(std::move(child)); @@ -36,6 +68,27 @@ auto SpeciesManager::Repopulate(Bodies& bodies) -> void { DeleteEmptySpecies(); } +auto SpeciesManager::BreedInSpecies(const Species& species) -> NeuralNetwork { + const auto& mother = species.GetOrganismToBreed(); + + // Note that the father can be the same as the mother + const Organism* father = nullptr; + if (Utility::DidChanceOccure(parameters.reproduction.chanceForInterspecialReproduction)) + father = &Utility::GetRandomElement(this->species)->GetFittestOrganism(); + else + father = &species.GetOrganismToBreed(); + + return father->BreedWith(mother, currGenerationInnovations); +} + +auto SpeciesManager::GetFittestSpecies() -> const Species & { + if (species.empty()) { + throw std::out_of_range("Your population is empty"); + } + SortSpeciesIfNeeded(); + return species.front(); +} + auto SpeciesManager::DeleteEmptySpecies() -> void { species.erase( std::remove_if(species.begin(), species.end(), [](const Species& s) {return s.IsEmpty(); }), @@ -60,68 +113,32 @@ auto SpeciesManager::FillOrganismIntoSpecies(Organism&& organism) -> void { } } -auto SpeciesManager::PrepareSpeciesForPopulation() -> void { - AnalyzeSpeciesPopulation(); - DeleteStagnantSpecies(); -} -auto SpeciesManager::AnalyzeSpeciesPopulation() -> void { - for (auto& currSpecies : species) { - currSpecies.AnalyzePopulation(); - } +auto SpeciesManager::ClearSpeciesPopulation() -> void { + for (auto& sp : species) + sp.ClearPopulation(); } -auto SpeciesManager::DeleteStagnantSpecies() -> void { - auto IsStagnant = [](const Species& species) { - return species.IsStagnant(); - }; - auto removePos = remove_if(species.begin(), species.end(), IsStagnant); - // Let at least one species survive - if (!species.empty() && removePos == species.begin()) { - ++removePos; - } - species.erase(removePos, species.end()); +auto SpeciesManager::GetAverageFitness() const -> Type::fitness_t { + return GetTotalFitness() / GetPopulationCount(); } -auto SpeciesManager::ClearSpeciesPopulation() -> void { - for (auto& sp : species) { - sp.ClearPopulation(); - } +auto SpeciesManager::GetPopulationCount() const -> std::size_t { + std::size_t populationCount = 0; + for (const auto & s : species) + populationCount += s.GetSize(); + return populationCount; } -auto SpeciesManager::GetSpeciesToBreed() -> Species& { - // TODO jnf: Switch to stochastic universal sampling - if (species.empty()) { - throw std::out_of_range("There are no species"); - } - auto totalSpeciesFitness = 0.0; - for (const auto& s : species) { - totalSpeciesFitness += s.GetFittestOrganism().GetOrCalculateFitness(); - } - if (totalSpeciesFitness == 0) { - return species[rand() % species.size()]; - } - auto chance = 0.0; - auto GetChanceForSpecies = [&chance, &totalSpeciesFitness](const Species& species) { - return chance + (species.GetFittestOrganism().GetOrCalculateFitness() / totalSpeciesFitness); - }; - while (true) { - for (auto& s : species) { - auto randNum = static_cast(rand() % 10'000) / 9'999.0; - chance = GetChanceForSpecies(s); - if (randNum < chance) { - return s; - } - } - } +auto SpeciesManager::GetTotalFitness() const -> Type::fitness_t { + auto totalFitness = 0.0; + for (auto & s : species) + totalFitness += s.GetTotalFitness(); + return totalFitness; } auto SpeciesManager::GetFittestOrganism() -> const Organism& { - if (species.empty()) { - throw std::out_of_range("Your population is empty"); - } - SortSpeciesIfNeeded(); - return species.front().GetFittestOrganism(); + return GetFittestSpecies().GetFittestOrganism(); } auto SpeciesManager::SortSpeciesIfNeeded() -> void { @@ -136,14 +153,12 @@ auto SpeciesManager::SortSpeciesIfNeeded() -> void { auto SpeciesManager::LetGenerationLive() -> void { ResetPopulationToTeachableState(); - for (auto& sp : species) { + for (auto& sp : species) sp.LetPopulationLive(); - } areSpeciesSortedByFitness = false; } auto SpeciesManager::ResetPopulationToTeachableState() -> void { - for (auto& sp : species) { + for (auto& sp : species) sp.ResetToTeachableState(); - } } diff --git a/Core/Sources/Implementations/trained_neural_network.cpp b/Core/Sources/Implementations/trained_neural_network.cpp deleted file mode 100644 index cbbfe94..0000000 --- a/Core/Sources/Implementations/trained_neural_network.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include "../Headers/trained_neural_network.h" - -using namespace Hippocrates; -using namespace std; - -auto TrainedNeuralNetwork::LoadFromFile(const ifstream& file) -> TrainedNeuralNetwork { - auto genome {LoadGenomeFromFile(file)}; - return TrainedNeuralNetwork {move(genome)}; -} - -auto TrainedNeuralNetwork::SaveToFile(std::ofstream& file) const -> void { - file << GetGenome().GetJSON(); -} - -auto TrainedNeuralNetwork::LoadGenomeFromFile(const std::ifstream& file) -> Genome { - // TODO sara: implementation - TrainingParameters parameters; - parameters.ranges.minWeight = -1.0f; - parameters.ranges.maxWeight = 1.0f; - // etc. - - std::size_t inputCount = 0; - std::size_t outputCount = 0; - Genome genome{ inputCount, outputCount, move(parameters) }; - - bool readAllGenes = false; - while (!readAllGenes) { - Gene gene; - gene.from = 0; - gene.to = 0; - gene.weight = 0.0f; - // etc. - - //genome.AppendGene(move(gene)); - readAllGenes = true; - } - - return genome; -} diff --git a/Core/Sources/Implementations/training_parameters.cpp b/Core/Sources/Implementations/training_parameters.cpp index 52068c2..5c02a7a 100644 --- a/Core/Sources/Implementations/training_parameters.cpp +++ b/Core/Sources/Implementations/training_parameters.cpp @@ -1,7 +1,76 @@ -#include "../Headers/training_parameters.h" +#include +#include "../Headers/training_parameters.hpp" +#include "../Headers/jsmn.h" +#include "../Headers/type.hpp" + using namespace Hippocrates; using namespace std; +TrainingParameters::TrainingParameters(std::string json) { + jsmn_parser parser; + jsmn_init(&parser); + jsmntok_t tokens[256]; + + auto token_count = jsmn_parse(&parser, json.c_str(), json.length(), tokens, 256); + + for (size_t i = 0; i < token_count - 1; i++) { + auto key = json.substr(tokens[i].start, tokens[i].end - tokens[i].start); + auto value = json.substr(tokens[i + 1].start, tokens[i + 1].end - tokens[i + 1].start); + + if (key == "minWeight") { + ranges.minWeight = stof(value); + } else + if (key == "maxWeight") { + ranges.maxWeight = stof(value); + } else + if (key == "chanceForWeightMutation") { + mutation.chanceForWeightMutation = stof(value); + } else + if (key == "chanceForConnectionalMutation") { + mutation.chanceForConnectionalMutation = stof(value); + } else + if (key == "chanceForNeuralMutation") { + mutation.chanceForNeuralMutation = stof(value); + } else + if (key == "chanceOfTotalWeightReset") { + mutation.chanceOfTotalWeightReset = stof(value); + } else + if (key == "importanceOfDisjointGenes") { + speciation.importanceOfDisjointGenes = stof(value); + } else + if (key == "importanceOfAverageWeightDifference") { + speciation.importanceOfAverageWeightDifference = stof(value); + } else + if (key == "compatibilityThreshold") { + speciation.compatibilityThreshold = stof(value); + } else + if (key == "stagnantSpeciesClearThreshold") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &speciation.stagnantSpeciesClearThreshold); + } else + if (key == "normalizeForLargerGenome") { + speciation.normalizeForLargerGenome = value == "true"; + } else + if (key == "chanceForInterspecialReproduction") { + reproduction.chanceForInterspecialReproduction = stof(value); + } else + if (key == "minSpeciesSizeForChampConservation") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &reproduction.minSpeciesSizeForChampConservation); + } else + if (key == "reproductionThreshold") { + reproduction.reproductionThreshold = stof(value); + } else + if (key == "minParents") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &reproduction.minParents); + } else + if (key == "numberOfBiasNeurons") { + HIPPOCRATES_SSCANF(value.c_str(), "%zu", &structure.numberOfBiasNeurons); + } else + if (key == "allowRecurrentConnections") { + structure.allowRecurrentConnections = value == "true"; + } + } +} + auto TrainingParameters::GetJSON() const -> std::string { auto BoolToString = [](bool b) { return b ? "true" : "false"; @@ -49,14 +118,18 @@ auto TrainingParameters::GetJSON() const -> std::string { s += to_string(reproduction.chanceForInterspecialReproduction); s += ",\"minSpeciesSizeForChampConservation\":"; s += to_string(reproduction.minSpeciesSizeForChampConservation); + s += ",\"reproductionThreshold\":"; + s += to_string(reproduction.reproductionThreshold); + s += ",\"minParents\":"; + s += to_string(reproduction.minParents); s += "}"; s += ",\"structure\":"; s += "{"; s += "\"numberOfBiasNeurons\":"; s += to_string(structure.numberOfBiasNeurons); - s += ",\"minSpeciesSizeForChampConservation\":"; - s += BoolToString(structure.areRecursiveConnectionsAllowed); + s += ",\"allowRecurrentConnections\":"; + s += BoolToString(structure.allowRecurrentConnections); s += "}"; s += "}"; diff --git a/Core/Sources/Implementations/utility.cpp b/Core/Sources/Implementations/utility.cpp new file mode 100644 index 0000000..c1caf5d --- /dev/null +++ b/Core/Sources/Implementations/utility.cpp @@ -0,0 +1,3 @@ +#include "../Headers/utility.hpp" +std::random_device Hippocrates::Utility::randomDevice; +std::mt19937 Hippocrates::Utility::engine(Hippocrates::Utility::randomDevice()); \ No newline at end of file diff --git a/Hippocrates.sln b/Hippocrates.sln index 956e101..286d4fb 100644 --- a/Hippocrates.sln +++ b/Hippocrates.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "XORSolver", "Tests\XOR\XORSolver.vcxproj", "{3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Core", "Core\Core.vcxproj", "{2E0E7930-3B86-4440-9F8C-AD90D19A2A94}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{7F7468AA-325D-40AC-8DCE-4141107AF2C9}" @@ -13,6 +11,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestingUtilities", "Tests\T EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EvenNumbers", "Tests\EvenNumbers\EvenNumbers.vcxproj", "{7595137A-0CD6-46FB-8B5C-DB7BB555CC3D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xor_two_bits", "Tests\xor_two_bits\xor_two_bits.vcxproj", "{3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "xor_three_bits", "Tests\xor_three_bits\xor_three_bits.vcxproj", "{21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -21,14 +23,6 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x64.ActiveCfg = Debug|x64 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x64.Build.0 = Debug|x64 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x86.ActiveCfg = Debug|Win32 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x86.Build.0 = Debug|Win32 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x64.ActiveCfg = Release|x64 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x64.Build.0 = Release|x64 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x86.ActiveCfg = Release|Win32 - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x86.Build.0 = Release|Win32 {2E0E7930-3B86-4440-9F8C-AD90D19A2A94}.Debug|x64.ActiveCfg = Debug|x64 {2E0E7930-3B86-4440-9F8C-AD90D19A2A94}.Debug|x64.Build.0 = Debug|x64 {2E0E7930-3B86-4440-9F8C-AD90D19A2A94}.Debug|x86.ActiveCfg = Debug|Win32 @@ -53,13 +47,30 @@ Global {7595137A-0CD6-46FB-8B5C-DB7BB555CC3D}.Release|x64.Build.0 = Release|x64 {7595137A-0CD6-46FB-8B5C-DB7BB555CC3D}.Release|x86.ActiveCfg = Release|Win32 {7595137A-0CD6-46FB-8B5C-DB7BB555CC3D}.Release|x86.Build.0 = Release|Win32 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x64.ActiveCfg = Debug|x64 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x64.Build.0 = Debug|x64 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x86.ActiveCfg = Debug|Win32 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Debug|x86.Build.0 = Debug|Win32 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x64.ActiveCfg = Release|x64 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x64.Build.0 = Release|x64 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x86.ActiveCfg = Release|Win32 + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF}.Release|x86.Build.0 = Release|Win32 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Debug|x64.ActiveCfg = Debug|x64 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Debug|x64.Build.0 = Debug|x64 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Debug|x86.ActiveCfg = Debug|Win32 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Debug|x86.Build.0 = Debug|Win32 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Release|x64.ActiveCfg = Release|x64 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Release|x64.Build.0 = Release|x64 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Release|x86.ActiveCfg = Release|Win32 + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF} = {7F7468AA-325D-40AC-8DCE-4141107AF2C9} {495DE3F6-9335-4278-A4EC-EC714DB06144} = {7F7468AA-325D-40AC-8DCE-4141107AF2C9} {7595137A-0CD6-46FB-8B5C-DB7BB555CC3D} = {7F7468AA-325D-40AC-8DCE-4141107AF2C9} + {3AD3F4B0-1414-4355-AC9F-A6118C6C52EF} = {7F7468AA-325D-40AC-8DCE-4141107AF2C9} + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0} = {7F7468AA-325D-40AC-8DCE-4141107AF2C9} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index e32d34b..a7608c8 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ |Linux |Windows :---:|:---:|:---: -Master | [![Build Status](https://travis-ci.org/SirRade/Hippocrates.svg?branch=master)](https://travis-ci.org/SirRade/Hippocrates)|[![Build status](https://ci.appveyor.com/api/projects/status/ioyvgn7a6mu3xgbl?svg=true)](https://ci.appveyor.com/project/SirRade/jnf-neat) +Master | [![Build Status](https://travis-ci.org/SirRade/Hippocrates.svg?branch=master)](https://travis-ci.org/SirRade/Hippocrates)|[![Build status](https://ci.appveyor.com/api/projects/status/ioyvgn7a6mu3xgbl/branch/master?svg=true)](https://ci.appveyor.com/project/SirRade/jnf-neat/branch/master) Development | [![Build Status](https://travis-ci.org/SirRade/Hippocrates.svg?branch=development)](https://travis-ci.org/SirRade/Hippocrates)|[![Build status](https://ci.appveyor.com/api/projects/status/ioyvgn7a6mu3xgbl/branch/development?svg=true)](https://ci.appveyor.com/project/SirRade/jnf-neat/branch/development) ----------------- -[Visualizing Tool](https://github.com/IDPA-2016-NEAT-CNN/NEAT_Visualizer) by [@Mafii](https://github.com/Mafii) +[Visualization Tool](https://github.com/IDPA-2016-NEAT-CNN/NEAT_Visualizer) by [@Mafii](https://github.com/Mafii) Implementation of Kenneth Stanley and Risto Miikkulainen's NEAT (NeuroEvolution of Augmenting Topologies, http://nn.cs.utexas.edu/downloads/papers/stanley.ec02.pdf). @@ -21,3 +21,7 @@ It focuses (in contrast to other implementations) on - Clean Code - through constant ongoing refactoring and a deep care for aesthetics - Usability - through being able to be used without much knowledge of Neural Networks - Platform Independency - written on three different operating systems (Windows, Ubuntu, MacOS X) and two different IDEs (Visual Studio 2015, CLion), it is safe to say that it will work on multiple platforms, flawlessly. + + +# Acknowledgements +- Serge A. Zaitsev ([jsmn](https://github.com/zserge/jsmn)) diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 18406e6..14f57a3 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,2 +1,3 @@ -add_subdirectory(XOR) +add_subdirectory(xor_two_bits) +add_subdirectory(xor_three_bits) add_subdirectory(EvenNumbers) diff --git a/Tests/EvenNumbers/CMakeLists.txt b/Tests/EvenNumbers/CMakeLists.txt index 2726368..c3955f0 100644 --- a/Tests/EvenNumbers/CMakeLists.txt +++ b/Tests/EvenNumbers/CMakeLists.txt @@ -1,4 +1,4 @@ -file(GLOB SOURCES Sources/Implementations/*.cpp) +file(GLOB_RECURSE SOURCES [RELATIVE Sources] *.cpp *.hpp) add_executable(EvenNumbers_EXAMPLE ${SOURCES}) target_link_libraries(EvenNumbers_EXAMPLE Hippocrates) diff --git a/Tests/EvenNumbers/EvenNumbers.vcxproj b/Tests/EvenNumbers/EvenNumbers.vcxproj index fe6c0f5..2908fc5 100644 --- a/Tests/EvenNumbers/EvenNumbers.vcxproj +++ b/Tests/EvenNumbers/EvenNumbers.vcxproj @@ -87,7 +87,7 @@ Level3 Disabled - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true ..\TestingUtilities\Sources\Headers;Sources\Headers @@ -102,7 +102,7 @@ Level3 Disabled - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true ..\TestingUtilities\Sources\Headers;Sources\Headers @@ -119,7 +119,7 @@ MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true ..\TestingUtilities\Sources\Headers;Sources\Headers @@ -138,7 +138,7 @@ MaxSpeed true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true ..\TestingUtilities\Sources\Headers;Sources\Headers diff --git a/Tests/EvenNumbers/EvenNumbers.vcxproj.filters b/Tests/EvenNumbers/EvenNumbers.vcxproj.filters index a741ac5..13584c1 100644 --- a/Tests/EvenNumbers/EvenNumbers.vcxproj.filters +++ b/Tests/EvenNumbers/EvenNumbers.vcxproj.filters @@ -1,10 +1,6 @@  - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx diff --git a/Tests/EvenNumbers/Sources/Implementations/main.cpp b/Tests/EvenNumbers/Sources/Implementations/main.cpp index 0d7e8b0..aa84e4e 100644 --- a/Tests/EvenNumbers/Sources/Implementations/main.cpp +++ b/Tests/EvenNumbers/Sources/Implementations/main.cpp @@ -1,4 +1,4 @@ -#include "testing_utilities.h" +#include "testing_utilities.hpp" using namespace std; using namespace Hippocrates; @@ -10,55 +10,58 @@ enum class Evenness { }; int main() { - srand(static_cast(time(nullptr))); - - TrainingData, Evenness> data; - data.AddSet({ { 1.0f }, Evenness::Uneven }); - data.AddSet({ { 2.0f }, Evenness::Even }); - data.AddSet({ { 3.0f }, Evenness::Uneven }); - data.AddSet({ { 4.0f }, Evenness::Even }); - data.AddSet({ { 5.0f }, Evenness::Uneven }); - data.AddSet({ { 6.0f }, Evenness::Even }); - data.AddSet({ { 7.0f }, Evenness::Uneven }); - data.AddSet({ { 8.0f }, Evenness::Even }); - data.AddSet({ { 9.0f }, Evenness::Uneven }); - data.AddSet({ { 10.0f }, Evenness::Even }); - data.AddSet({ { 11.0f }, Evenness::Uneven }); - data.AddSet({ { 12.0f }, Evenness::Even }); - data.AddSet({ { 13.0f }, Evenness::Uneven }); - data.AddSet({ { 14.0f }, Evenness::Even }); - data.AddSet({ { 15.0f }, Evenness::Uneven }); - data.AddSet({ { 16.0f }, Evenness::Even }); - data.AddSet({ { 17.0f }, Evenness::Uneven }); - data.AddSet({ { 18.0f }, Evenness::Even }); - data.AddSet({ { 19.0f }, Evenness::Uneven }); - data.AddSet({ { 20.0f }, Evenness::Even }); + constexpr auto maxValue = 40.0f; + TrainingData data; + data.AddSet( { 1.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 2.0f / maxValue }, Evenness::Even ); + data.AddSet( { 3.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 4.0f / maxValue }, Evenness::Even ); + // runs until here + /* + data.AddSet( { 5.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 6.0f / maxValue }, Evenness::Even ); + data.AddSet( { 7.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 8.0f / maxValue }, Evenness::Even ); + data.AddSet( { 9.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 10.0f / maxValue }, Evenness::Even ); + data.AddSet( { 11.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 12.0f / maxValue }, Evenness::Even ); + data.AddSet( { 13.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 14.0f / maxValue }, Evenness::Even ); + data.AddSet( { 15.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 16.0f / maxValue }, Evenness::Even ); + data.AddSet( { 17.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 18.0f / maxValue }, Evenness::Even ); + data.AddSet( { 19.0f / maxValue }, Evenness::Uneven ); + data.AddSet( { 20.0f / maxValue }, Evenness::Even ); + */ NeuralNetworkTrainer trainer; + std::chrono::seconds timeout(45); + auto champ = Tests::TestingUtilities::TrainWithTimeout(trainer, data, timeout); + std::cout << "Finished training in " << trainer.GetGenerationsPassed() << " generations\n"; - auto champ = trainer.TrainSupervised(data, 50); - - TrainingData, Evenness> expectedData; - expectedData.AddSet({ { 21.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 22.0f }, Evenness::Even }); - expectedData.AddSet({ { 23.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 24.0f }, Evenness::Even }); - expectedData.AddSet({ { 25.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 26.0f }, Evenness::Even }); - expectedData.AddSet({ { 27.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 28.0f }, Evenness::Even }); - expectedData.AddSet({ { 29.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 30.0f }, Evenness::Even }); - expectedData.AddSet({ { 31.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 32.0f }, Evenness::Even }); - expectedData.AddSet({ { 33.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 34.0f }, Evenness::Even }); - expectedData.AddSet({ { 35.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 36.0f }, Evenness::Even }); - expectedData.AddSet({ { 37.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 38.0f }, Evenness::Even }); - expectedData.AddSet({ { 39.0f }, Evenness::Uneven }); - expectedData.AddSet({ { 40.0f }, Evenness::Even }); + TrainingData expectedData; + expectedData.AddSet( { 21.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 22.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 23.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 24.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 25.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 26.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 27.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 28.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 29.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 30.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 31.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 32.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 33.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 34.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 35.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 36.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 37.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 38.0f / maxValue }, Evenness::Even ); + expectedData.AddSet( { 39.0f / maxValue }, Evenness::Uneven ); + expectedData.AddSet( { 40.0f / maxValue }, Evenness::Even ); - return Tests::TestingUtilities::TestNetwork(champ, expectedData); + return Tests::TestingUtilities::TestNetwork(champ, /*expectedData*/ data); } diff --git a/Tests/README.md b/Tests/README.md index ad78dba..8b96e97 100644 --- a/Tests/README.md +++ b/Tests/README.md @@ -3,3 +3,6 @@ ## Supervised learning - [XOR](XOR) (with recursive connections) - [Even numbers](EvenNumbers) (with recursive connections) + +## Unsupervised learning +- [Sine](Sine) (without recursive connections) diff --git a/Tests/TestingUtilities/Sources/Headers/testing_utilities.h b/Tests/TestingUtilities/Sources/Headers/testing_utilities.h deleted file mode 100644 index f1a5c9c..0000000 --- a/Tests/TestingUtilities/Sources/Headers/testing_utilities.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -// TODO: Replace with library import -#include "../../../../Core/Sources/Headers/neural_network_trainer.h" -#include - -namespace Hippocrates { -namespace Tests { -namespace TestingUtilities { - -template -auto TestNetwork(NeuralNetwork &network, TrainingData &data) { - auto errorCount = 0; - - for (const auto& dataSet : data) { - auto networkOutputs = network.GetOutputsUsingInputs(dataSet.input); - - auto maxOutput = std::max_element(networkOutputs.begin(), networkOutputs.end()); - auto outputIndex = std::distance(networkOutputs.begin(), maxOutput); - - if (outputIndex != static_cast(dataSet.classification)) { - std::cout << "Incorrect classification for inputs:"; - for (const auto& input : dataSet.input) { - std::cout << " - " << input << '\n'; - } - - std::cout << "Got outputs:"; - for (const auto& output : networkOutputs) { - std::cout << " - " << output << '\n'; - } - - std::cout << '\n'; - } - } - - return errorCount; -} - -template -struct measure { - template - static typename TimeT::rep execution(F&& func, Args&&... args) - { - auto start = std::chrono::steady_clock::now(); - std::forward(func)(std::forward(args)...); - auto duration = std::chrono::duration_cast< TimeT> - (std::chrono::steady_clock::now() - start); - return duration.count(); - } -}; - -} -} -} diff --git a/Tests/TestingUtilities/Sources/Headers/testing_utilities.hpp b/Tests/TestingUtilities/Sources/Headers/testing_utilities.hpp new file mode 100644 index 0000000..cc0bb4d --- /dev/null +++ b/Tests/TestingUtilities/Sources/Headers/testing_utilities.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include +#include + +// TODO: Replace with library import +#include "../../../../Core/Sources/Headers/neural_network_trainer.hpp" +#include "../../../../Core/Sources/Headers/trained/classifier.hpp" + +namespace Hippocrates { +namespace Tests { +namespace TestingUtilities { + +template +auto TrainWithTimeout(NeuralNetworkTrainer& trainer, const TrainingData &data, std::chrono::duration span) { + using classifier_t = Trained::Classifier; + auto func = [&]() { + auto champ = trainer.TrainSupervised(data, static_cast(150)); + return std::make_unique(std::move(champ)); + }; + std::future> fut = std::async(std::launch::async, func); + + if (fut.wait_for(span) == std::future_status::timeout) { + std::terminate(); + } + + return *(fut.get().get()); +} + + +template +auto TestNetwork(Trained::Classifier & network, TrainingData &data) { + for (const auto& dataSet : data) { + if (network.Classify(dataSet.input) != dataSet.classification) { + return 1; + } + } + return 0; +} + +template +struct measure { + template + static typename TimeT::rep execution(F&& func, Args&&... args) + { + auto start = std::chrono::steady_clock::now(); + std::forward(func)(std::forward(args)...); + auto duration = std::chrono::duration_cast< TimeT> + (std::chrono::steady_clock::now() - start); + return duration.count(); + } +}; + +} +} +} diff --git a/Tests/TestingUtilities/TestingUtilities.vcxproj b/Tests/TestingUtilities/TestingUtilities.vcxproj index 3d58a7b..c372c93 100644 --- a/Tests/TestingUtilities/TestingUtilities.vcxproj +++ b/Tests/TestingUtilities/TestingUtilities.vcxproj @@ -18,14 +18,14 @@ x64 - - - {2e0e7930-3b86-4440-9f8c-ad90d19a2a94} + + + {495DE3F6-9335-4278-A4EC-EC714DB06144} Win32Proj diff --git a/Tests/TestingUtilities/TestingUtilities.vcxproj.filters b/Tests/TestingUtilities/TestingUtilities.vcxproj.filters index 3c92612..ed8f828 100644 --- a/Tests/TestingUtilities/TestingUtilities.vcxproj.filters +++ b/Tests/TestingUtilities/TestingUtilities.vcxproj.filters @@ -1,17 +1,13 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - {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 - + Headers diff --git a/Tests/XOR/CMakeLists.txt b/Tests/XOR/CMakeLists.txt deleted file mode 100644 index 2b98036..0000000 --- a/Tests/XOR/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -file(GLOB SOURCES Sources/Implementations/*.cpp) - -add_executable(XOR_EXAMPLE ${SOURCES}) -target_link_libraries(XOR_EXAMPLE Hippocrates) -target_include_directories(XOR_EXAMPLE PUBLIC ../TestingUtilities/Sources/Headers) - -add_test(XOR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/XOR_EXAMPLE) diff --git a/Tests/XOR/Sources/Implementations/main.cpp b/Tests/XOR/Sources/Implementations/main.cpp deleted file mode 100644 index 5cdc4c6..0000000 --- a/Tests/XOR/Sources/Implementations/main.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include "testing_utilities.h" - -using namespace std; -using namespace Hippocrates; - -enum class XORResult { - Zero, - One, - ClassificationCount -}; - -auto SaveNetwork(TrainedNeuralNetwork &champ, string filename) { - ofstream outFile(filename); - champ.SaveToFile(outFile); - outFile.close(); -} - -auto LoadNetwork(string filename) { - ifstream inFile(filename); - auto champ = TrainedNeuralNetwork::LoadFromFile(inFile); - inFile.close(); - - return champ; -} - -int main() { - srand(static_cast(time(nullptr))); - - TrainingData, XORResult> data; - data.AddSet({ {0.0f, 0.0f}, XORResult::Zero }); - data.AddSet({ {0.0f, 1.0f}, XORResult::One }); - data.AddSet({ {1.0f, 0.0f}, XORResult::One }); - data.AddSet({ {1.0f, 1.0f}, XORResult::Zero }); - - NeuralNetworkTrainer trainer; - trainer.loggingEnabled = false; - - #ifdef CI - trainer.loggingEnabled = false; - #endif - - auto champ = trainer.TrainSupervised(data, 50); - - SaveNetwork(champ, "champ.nn"); - LoadNetwork("champ.nn"); - - return Tests::TestingUtilities::TestNetwork(champ, data); -} diff --git a/Tests/xor_three_bits/CMakeLists.txt b/Tests/xor_three_bits/CMakeLists.txt new file mode 100644 index 0000000..da9bc3d --- /dev/null +++ b/Tests/xor_three_bits/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCES [RELATIVE Sources] *.cpp *.hpp) + +add_executable(xor_three_bits ${SOURCES}) +target_link_libraries(xor_three_bits Hippocrates) +target_include_directories(xor_three_bits PUBLIC ../TestingUtilities/Sources/Headers) + +add_test(xor_three_bits ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/xor_three_bits) diff --git a/Tests/XOR/README.md b/Tests/xor_three_bits/README.md similarity index 100% rename from Tests/XOR/README.md rename to Tests/xor_three_bits/README.md diff --git a/Tests/xor_three_bits/Sources/Implementations/main.cpp b/Tests/xor_three_bits/Sources/Implementations/main.cpp new file mode 100644 index 0000000..1f293ef --- /dev/null +++ b/Tests/xor_three_bits/Sources/Implementations/main.cpp @@ -0,0 +1,37 @@ +#include "testing_utilities.hpp" + +using namespace std; +using namespace Hippocrates; + +enum class XORResult { + Zero, + One, + ClassificationCount +}; + +int main() { + TrainingData data; + data.AddSet({0, 0, 0}, XORResult::Zero); + data.AddSet({0, 1, 0}, XORResult::One); + data.AddSet({1, 0, 0}, XORResult::One); + data.AddSet({1, 0, 1}, XORResult::Zero); + data.AddSet({1, 1, 1}, XORResult::Zero); + + NeuralNetworkTrainer trainer; + std::chrono::seconds timeout(10); + auto champ = Tests::TestingUtilities::TrainWithTimeout(trainer, data, timeout); + std::cout << "Finished training in " << trainer.GetGenerationsPassed() << " generations\n"; + + TrainingData expectedData; + data.AddSet({0, 0, 0}, XORResult::Zero); + data.AddSet({0, 0, 1}, XORResult::One); + data.AddSet({0, 1, 1}, XORResult::Zero); + data.AddSet({1, 0, 1}, XORResult::Zero); + data.AddSet({1, 0, 0}, XORResult::One); + data.AddSet({1, 1, 1}, XORResult::Zero); + data.AddSet({1, 1, 0}, XORResult::Zero); + data.AddSet({0, 1, 0}, XORResult::One); + + + return Tests::TestingUtilities::TestNetwork(champ, expectedData); +} diff --git a/Tests/xor_three_bits/xor_three_bits.vcxproj b/Tests/xor_three_bits/xor_three_bits.vcxproj new file mode 100644 index 0000000..897391e --- /dev/null +++ b/Tests/xor_three_bits/xor_three_bits.vcxproj @@ -0,0 +1,134 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {21BF0885-F1EF-4BF6-A8FB-3384199CBEB0} + XORSolver + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) + + + + + Level3 + Disabled + true + ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) + + + + + Level3 + MaxSpeed + true + true + true + ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) + + + true + true + + + + + Level3 + MaxSpeed + true + true + true + ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) + + + true + true + + + + + + + + {2e0e7930-3b86-4440-9f8c-ad90d19a2a94} + + + {495de3f6-9335-4278-a4ec-ec714db06144} + + + + + + \ No newline at end of file diff --git a/Tests/XOR/XORSolver.vcxproj.filters b/Tests/xor_three_bits/xor_three_bits.vcxproj.filters similarity index 73% rename from Tests/XOR/XORSolver.vcxproj.filters rename to Tests/xor_three_bits/xor_three_bits.vcxproj.filters index f6ca5ad..13584c1 100644 --- a/Tests/XOR/XORSolver.vcxproj.filters +++ b/Tests/xor_three_bits/xor_three_bits.vcxproj.filters @@ -5,10 +5,6 @@ {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 - diff --git a/Tests/xor_two_bits/CMakeLists.txt b/Tests/xor_two_bits/CMakeLists.txt new file mode 100644 index 0000000..78d236a --- /dev/null +++ b/Tests/xor_two_bits/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCES [RELATIVE Sources] *.cpp *.hpp) + +add_executable(xor_two_bits ${SOURCES}) +target_link_libraries(xor_two_bits Hippocrates) +target_include_directories(xor_two_bits PUBLIC ../TestingUtilities/Sources/Headers) + +add_test(xor_two_bits ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/xor_two_bits) diff --git a/Tests/xor_two_bits/README.md b/Tests/xor_two_bits/README.md new file mode 100644 index 0000000..9fe7455 --- /dev/null +++ b/Tests/xor_two_bits/README.md @@ -0,0 +1,17 @@ +# XOR + +This test uses supervised learning to solve the XOR-problem. + +### Key information +- Supervised +- Recursive connections allowed +- Population size: 50 + +### Datasets + +Inputs | Expected output +:---: | :---: +0 / 0 | 0 +0 / 1 | 1 +1 / 0 | 1 +1 / 1 | 0 diff --git a/Tests/xor_two_bits/Sources/Implementations/main.cpp b/Tests/xor_two_bits/Sources/Implementations/main.cpp new file mode 100644 index 0000000..1b77006 --- /dev/null +++ b/Tests/xor_two_bits/Sources/Implementations/main.cpp @@ -0,0 +1,38 @@ +#include "testing_utilities.hpp" + +using namespace std; +using namespace Hippocrates; + +enum class XORResult { + Zero, + One, + ClassificationCount +}; + +int main() { + TrainingData data; + data.AddSet( {0, 0}, XORResult::Zero ); + data.AddSet( {0, 1}, XORResult::One ); + data.AddSet( {1, 0}, XORResult::One ); + data.AddSet( {1, 1}, XORResult::Zero ); + + NeuralNetworkTrainer trainer; + std::chrono::seconds timeout(10); + auto champ = Tests::TestingUtilities::TrainWithTimeout(trainer, data, timeout); + std::cout << "Finished training in " << trainer.GetGenerationsPassed() << " generations\n"; + + TrainingData expectedData; + expectedData.AddSet( { 1, 0 }, XORResult::One ); + expectedData.AddSet( { 1, 1 }, XORResult::Zero ); + expectedData.AddSet( { 1, 1 }, XORResult::Zero ); + expectedData.AddSet( { 1, 1 }, XORResult::Zero ); + expectedData.AddSet( { 0, 1 }, XORResult::One ); + expectedData.AddSet( { 0, 0 }, XORResult::Zero ); + expectedData.AddSet( { 0, 0 }, XORResult::Zero ); + expectedData.AddSet( { 0, 1 }, XORResult::One ); + expectedData.AddSet( { 0, 0 }, XORResult::Zero ); + expectedData.AddSet( { 1, 1 }, XORResult::Zero ); + + + return Tests::TestingUtilities::TestNetwork(champ, expectedData); +} diff --git a/Tests/XOR/XORSolver.vcxproj b/Tests/xor_two_bits/xor_two_bits.vcxproj similarity index 94% rename from Tests/XOR/XORSolver.vcxproj rename to Tests/xor_two_bits/xor_two_bits.vcxproj index c611cf7..9ce58a5 100644 --- a/Tests/XOR/XORSolver.vcxproj +++ b/Tests/xor_two_bits/xor_two_bits.vcxproj @@ -75,6 +75,7 @@ Disabled true ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -83,6 +84,7 @@ Disabled true ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) @@ -93,6 +95,7 @@ true true ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) true @@ -107,6 +110,7 @@ true true ..\TestingUtilities\Sources\Headers;Sources\Headers + _UNICODE;UNICODE;%(PreprocessorDefinitions) true diff --git a/Tests/xor_two_bits/xor_two_bits.vcxproj.filters b/Tests/xor_two_bits/xor_two_bits.vcxproj.filters new file mode 100644 index 0000000..13584c1 --- /dev/null +++ b/Tests/xor_two_bits/xor_two_bits.vcxproj.filters @@ -0,0 +1,14 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Implementations + + + \ No newline at end of file