diff --git a/neural_network/include/ReversedMLP.hpp b/neural_network/include/ReversedMLP.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e2287c37e07aca1798104009425b5e5ee94d92a2 --- /dev/null +++ b/neural_network/include/ReversedMLP.hpp @@ -0,0 +1,95 @@ +/// \file ReversedMLP.hpp +/// \author Franck Dary +/// @version 1.0 +/// @date 2019-08-08 + +#ifndef REVERSEDMLP__H +#define REVERSEDMLP__H + +#include "NeuralNetwork.hpp" +#include "MLPBase.hpp" +#include "ProgramParameters.hpp" + +/// @brief Classifier consisting in 2 MLP, can be trained with negative or positive classes. +/// +/// It is capable of training itself given a batch of examples.\n +/// Once trained, it can also be used to predict the class of a certain input. +class ReversedMLP : public NeuralNetwork +{ + private : + + /// @brief The mlp that will be trained using positive gold classes. + MLPBase mlpPos; + /// @brief The mlp that will be trained using negative gold classes. + MLPBase mlpNeg; + /// @brief The training algorithm that will be used. + std::unique_ptr<dynet::Trainer> trainer; + + public : + + /// @brief initialize a new untrained ReversedMLP from a desired topology. + /// + /// topology example for 2 hidden layers : (150,RELU,0.3)(50,ELU,0.2)\n + /// Of sizes 150 and 50, activation functions RELU and ELU, and dropout rates + /// of 0.3 and 0.2. + /// @param nbInputs The size of the input layer of the MLP. + /// @param topology Description of each hidden Layer of the MLP. + /// @param nbOutputs The size of the output layer of the MLP. + void init(int nbInputs, const std::string & topology, int nbOutputs) override; + /// @brief Construct a new ReversedMLP for training. + ReversedMLP(); + /// @brief Read and construct a trained ReversedMLP from a file. + /// + /// The file must have been written by save. + /// @param filename The file to read the ReversedMLP from. + ReversedMLP(const std::string & filename); + /// @brief Give a score to each possible class, given an input. + /// + /// @param fd The input to use. + /// + /// @return A vector containing one score per possible class. + std::vector<float> predict(FeatureModel::FeatureDescription & fd) override; + /// @brief Update the parameters according to the given gold class. + /// + /// @param fd The input to use. + /// @param gold The gold class of this input. + /// + /// @return The loss. + float update(FeatureModel::FeatureDescription & fd, int gold) override; + /// @brief Get the loss according to the given gold class. + /// + /// @param fd The input to use. + /// @param gold The gold class of this input. + /// + /// @return The loss. + float update(FeatureModel::FeatureDescription & fd, const std::vector<float> & gold) override; + /// @brief Get the loss according to the given gold class. + /// + /// @param fd The input to use. + /// @param gold The gold class of this input. + /// + /// @return The loss. + float getLoss(FeatureModel::FeatureDescription & fd, int gold) override; + /// @brief Get the loss according to the given gold vector. + /// + /// @param fd The input to use. + /// @param gold The gold vector for this input. + /// + /// @return The loss. + float getLoss(FeatureModel::FeatureDescription & fd, const std::vector<float> & gold) override; + /// @brief Save the ReversedMLP to a file. + /// + /// @param filename The file to write the ReversedMLP to. + void save(const std::string & filename) override; + /// @brief Print the topology (Layers) of the ReversedMLP. + /// + /// @param output Where the topology will be printed. + void printTopology(FILE * output) override; + /// @brief Allocate the correct trainer type depending on the program parameters. + /// + /// @return A pointer to the newly allocated trainer. + dynet::Trainer * createTrainer(); + void endOfIteration(); +}; + +#endif diff --git a/neural_network/src/MLPBase.cpp b/neural_network/src/MLPBase.cpp index fe11c48ec3e7a605a59a956d266532f94ba77872..5ff466ed177a87f264a13314145830f6dc2c22fc 100644 --- a/neural_network/src/MLPBase.cpp +++ b/neural_network/src/MLPBase.cpp @@ -170,7 +170,7 @@ float MLPBase::update(FeatureModel::FeatureDescription & fd, const std::vector<f goldExpressions.emplace_back(dynet::input(cg, dynet::Dim({(unsigned int)gold.size()}), gold)); dynet::Expression batchedGold = dynet::concatenate_to_batch(goldExpressions); - batchedLoss = dynet::sum_batches(dynet::l1_distance(output, batchedGold)); + batchedLoss = dynet::sum_batches(dynet::squared_distance(output, batchedGold)); cg.backward(batchedLoss); @@ -260,7 +260,7 @@ float MLPBase::getLoss(FeatureModel::FeatureDescription & fd, const std::vector< goldExpressions.emplace_back(dynet::input(cg, dynet::Dim({1,(unsigned int)gold.size()}), gold)); dynet::Expression batchedGold = dynet::concatenate_to_batch(goldExpressions); - batchedLoss = dynet::sum_batches(dynet::l1_distance(output, batchedGold)); + batchedLoss = dynet::sum_batches(dynet::squared_distance(output, batchedGold)); checkGradients(); diff --git a/neural_network/src/ReversedMLP.cpp b/neural_network/src/ReversedMLP.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ed5aead75440e478cef89533592687c36900798 --- /dev/null +++ b/neural_network/src/ReversedMLP.cpp @@ -0,0 +1,158 @@ +#include "ReversedMLP.hpp" + +ReversedMLP::ReversedMLP() : mlpPos("MLP_POS"), mlpNeg("MLP_NEG") +{ + randomSeed = ProgramParameters::seed; + trainer.reset(createTrainer()); + initDynet(); +} + +ReversedMLP::ReversedMLP(const std::string & filename) : mlpPos("MLP_POS"), mlpNeg("MLP_NEG") +{ + randomSeed = ProgramParameters::seed; + trainer.reset(createTrainer()); + initDynet(); + + mlpPos.loadStruct(model, filename, 0); + mlpPos.loadParameters(model, filename); + + mlpNeg.loadStruct(model, filename, 1); + mlpNeg.loadParameters(model, filename); +} + +void ReversedMLP::init(int nbInputs, const std::string & topology, int nbOutputs) +{ + std::string safeTopology = ""; + for (unsigned int i = 1; i < topology.size(); i++) + safeTopology.push_back(topology[i]); + + setBatchSize(0); + mlpPos.init(model, nbInputs, safeTopology, nbOutputs); + mlpNeg.init(model, nbInputs, safeTopology, nbOutputs); +} + +dynet::Trainer * ReversedMLP::createTrainer() +{ + auto optimizer = noAccentLower(ProgramParameters::optimizer); + + dynet::Trainer * trainer = nullptr; + + if (optimizer == "amsgrad") + trainer = new dynet::AmsgradTrainer(model, ProgramParameters::learningRate, ProgramParameters::beta1, ProgramParameters::beta2, ProgramParameters::bias); + else if (optimizer == "adam") + trainer = new dynet::AdamTrainer(model, ProgramParameters::learningRate, ProgramParameters::beta1, ProgramParameters::beta2, ProgramParameters::bias); + else if (optimizer == "sgd") + trainer = new dynet::SimpleSGDTrainer(model, ProgramParameters::learningRate); + else if (optimizer == "none") + return nullptr; + + if (trainer) + { + trainer->sparse_updates_enabled = true; + return trainer; + } + + fprintf(stderr, "ERROR (%s) : unknown optimizer \'%s\'. Aborting.\n", ERRINFO, optimizer.c_str()); + + exit(1); + + return nullptr; +} + +std::vector<float> ReversedMLP::predict(FeatureModel::FeatureDescription & fd) +{ + auto predPos = mlpPos.predict(fd); + auto predNeg = mlpNeg.predict(fd); + + for (unsigned int i = 0; i < predPos.size(); i++) + predPos[i] -= predNeg[i]; + + return predPos; +} + +float ReversedMLP::update(FeatureModel::FeatureDescription & fd, int gold) +{ + mlpPos.setBatchSize(getBatchSize()); + mlpNeg.setBatchSize(getBatchSize()); + try + { + float loss = 0.0; + if (gold >= 0) + { + loss = mlpPos.update(fd, gold); + } + else + { + gold = -gold; + gold--; + loss = mlpPos.update(fd, gold); + } + + trainer->update(); + return loss; + } catch (BatchNotFull &) + { + return 0.0; + } +} + +float ReversedMLP::update(FeatureModel::FeatureDescription &, const std::vector<float> &) +{ + fprintf(stderr, "ERROR (%s) : only classification is supported. Aborting.\n", ERRINFO); + exit(1); + return 0.0; +} + +float ReversedMLP::getLoss(FeatureModel::FeatureDescription & fd, int gold) +{ + mlpPos.setBatchSize(getBatchSize()); + mlpNeg.setBatchSize(getBatchSize()); + try + { + float loss = 0.0; + if (gold >= 0) + { + loss = mlpPos.getLoss(fd, gold); + } + else + { + gold = -gold; + gold--; + loss = mlpPos.getLoss(fd, gold); + } + return loss; + } catch (BatchNotFull &) + { + return 0.0; + } +} + +float ReversedMLP::getLoss(FeatureModel::FeatureDescription &, const std::vector<float> &) +{ + fprintf(stderr, "ERROR (%s) : only classification is supported. Aborting.\n", ERRINFO); + exit(1); + return 0.0; +} + +void ReversedMLP::save(const std::string & filename) +{ + File * file = new File(filename, "w"); + delete file; + mlpPos.saveStruct(filename); + mlpPos.saveParameters(filename); + + mlpNeg.saveStruct(filename); + mlpNeg.saveParameters(filename); +} + +void ReversedMLP::printTopology(FILE * output) +{ + mlpPos.printTopology(output); +} + +void ReversedMLP::endOfIteration() +{ + mlpPos.endOfIteration(); + mlpNeg.endOfIteration(); +} + diff --git a/trainer/src/Trainer.cpp b/trainer/src/Trainer.cpp index 38c6a35a41b03a4287aa3bc1f98f47b490aa8a52..6e906845bb30db4dda380ad1889e9c34826832a3 100644 --- a/trainer/src/Trainer.cpp +++ b/trainer/src/Trainer.cpp @@ -404,26 +404,38 @@ void Trainer::doStepTrain() if (newCost >= lastCost) { -// loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], tm.getCurrentClassifier()->getActionIndex("EPSILON")); - int nbActions = tm.getCurrentClassifier()->getNbActions(); - int backIndex = tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top()); - float value = 1.0 / (nbActions-1); - std::vector<float> goldOutput(nbActions, value); - goldOutput[backIndex] = 0.0; + if (true) + { + loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], -(tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top())+1)); + } + else + { + int nbActions = tm.getCurrentClassifier()->getNbActions(); + int backIndex = tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top()); + float value = 1.0 / (nbActions-1); + std::vector<float> goldOutput(nbActions, value); + goldOutput[backIndex] = 0.0; - loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], goldOutput); + loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], goldOutput); + } updateInfos = "predicted : <"+trainConfig.getCurrentStateHistory().top()+">, bad decision"; } else { -//loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top())); - int nbActions = tm.getCurrentClassifier()->getNbActions(); - int backIndex = tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top()); - std::vector<float> goldOutput(nbActions, 0.0); - goldOutput[backIndex] = 1.0; + if (true) + { + loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top())); + } + else + { + int nbActions = tm.getCurrentClassifier()->getNbActions(); + int backIndex = tm.getCurrentClassifier()->getActionIndex(trainConfig.getCurrentStateHistory().top()); + std::vector<float> goldOutput(nbActions, 0.0); + goldOutput[backIndex] = 1.0; - loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], goldOutput); + loss = tm.getCurrentClassifier()->trainOnExample(pendingFD[tm.getCurrentClassifier()->name], goldOutput); + } updateInfos = "predicted : <"+trainConfig.getCurrentStateHistory().top()+">, good decision"; } diff --git a/transition_machine/src/Classifier.cpp b/transition_machine/src/Classifier.cpp index ba054bd67bd0d65290121cf0afeb6f04e4569170..4468a93328bf83c6654b76a3e9cc3e41732a24c2 100644 --- a/transition_machine/src/Classifier.cpp +++ b/transition_machine/src/Classifier.cpp @@ -2,6 +2,7 @@ #include "File.hpp" #include "util.hpp" #include "MLP.hpp" +#include "ReversedMLP.hpp" #include "GeneticAlgorithm.hpp" Classifier::Classifier(const std::string & filename, bool trainMode) @@ -372,6 +373,9 @@ NeuralNetwork * Classifier::createNeuralNetwork() if (splited.size() == 2) return new GeneticAlgorithm(); + if (topology[0] == 'R') + return new ReversedMLP(); + return new MLP(); }