[mlpack-git] master: Refactor perceptron program to load/save models. (aef3619)

gitdub at big.cc.gt.atl.ga.us gitdub at big.cc.gt.atl.ga.us
Fri Sep 11 11:20:14 EDT 2015


Repository : https://github.com/mlpack/mlpack

On branch  : master
Link       : https://github.com/mlpack/mlpack/compare/a33bc45442b3ce8830ea1a3e930c89d05c6dc9c6...aef36197cd6ed335ab4a9b60e065a5044e192e5d

>---------------------------------------------------------------

commit aef36197cd6ed335ab4a9b60e065a5044e192e5d
Author: Ryan Curtin <ryan at ratml.org>
Date:   Fri Sep 11 15:19:34 2015 +0000

    Refactor perceptron program to load/save models.
    
    Now you can save perceptrons, which is a big functionality boost.  Also the documentation has been redone.


>---------------------------------------------------------------

aef36197cd6ed335ab4a9b60e065a5044e192e5d
 src/mlpack/methods/perceptron/perceptron_main.cpp | 298 ++++++++++++++++------
 1 file changed, 214 insertions(+), 84 deletions(-)

diff --git a/src/mlpack/methods/perceptron/perceptron_main.cpp b/src/mlpack/methods/perceptron/perceptron_main.cpp
index 49fec08..f22d077 100644
--- a/src/mlpack/methods/perceptron/perceptron_main.cpp
+++ b/src/mlpack/methods/perceptron/perceptron_main.cpp
@@ -17,108 +17,238 @@ using namespace std;
 using namespace arma;
 
 PROGRAM_INFO("Perceptron",
-    "This program implements a perceptron, which is a single level "
-    "Neural Network. The perceptron makes its predictions based on "
-    "a linear predictor function combining a set of weights with the feature "
-    "vector.\n"
-    "The perceptron learning rule is able to converge, given enough iterations "
-    "using the --iterations (-i) parameter, if the data supplied is "
-    "linearly separable. "
-    "\n"
-    "The Perceptron is parameterized by a matrix of weight vectors which "
-    "denotes the numerical weights of the Neural Network."
-    "\n"
-    "This program allows training of a perceptron, and then application of "
-    "the learned perceptron to a test dataset.  To train a perceptron, "
-    "a training dataset must be passed to --train_file (-t).  Labels can either "
-    "be present as the last dimension of the training dataset, or given "
-    "explicitly with the --labels_file (-l) parameter."
-    "\n"
-    "A test file is given through the --test_file (-T) parameter.  The "
-    "predicted labels for the test set will be stored in the file specified by "
-    "the --output_file (-o) parameter."
+    "This program implements a perceptron, which is a single level neural "
+    "network. The perceptron makes its predictions based on a linear predictor "
+    "function combining a set of weights with the feature vector.  The "
+    "perceptron learning rule is able to converge, given enough iterations "
+    "using the --max_iterations (-i) parameter, if the data supplied is "
+    "linearly separable.  The perceptron is parameterized by a matrix of weight"
+    " vectors that denote the numerical weights of the neural network."
+    "\n\n"
+    "This program allows loading a perceptron from a model (-i) or training a "
+    "perceptron given training data (-t), or both those things at once.  In "
+    "addition, this program allows classification on a test dataset (-T) and "
+    "will save the classification results to the given output file (-o).  The "
+    "perceptron model itself may be saved with a file specified using the -m "
+    "option."
+    "\n\n"
+    "The training data given with the -t option should have class labels as its"
+    " last dimension (so, if the training data is in CSV format, labels should "
+    "be the last column).  Alternately, the -l (--labels_file) option may be "
+    "used to specify a separate file of labels."
+    "\n\n"
+    "All these options make it easy to train a perceptron, and then re-use that"
+    " perceptron for later classification.  The invocation below trains a "
+    "perceptron on 'training_data.csv' (and 'training_labels.csv)' and saves "
+    "the model to 'perceptron.xml'."
+    "\n\n"
+    "$ perceptron -t training_data.csv -l training_labels.csv -m perceptron.csv"
+    "\n\n"
+    "Then, this model can be re-used for classification on 'test_data.csv'.  "
+    "The example below does precisely that, saving the predicted classes to "
+    "'predictions.csv'."
+    "\n\n"
+    "$ perceptron -i perceptron.xml -T test_data.csv -o predictions.csv"
+    "\n\n"
+    "Note that all of the options may be specified at once: predictions may be "
+    "calculated right after training a model, and model training can occur even"
+    " if an existing perceptron model is passed with -i (--input_model).  "
+    "However, note that the number of classes and the dimensionality of all "
+    "data must match.  So you cannot pass a perceptron model trained on 2 "
+    "classes and then re-train with a 4-class dataset.  Similarly, attempting "
+    "classification on a 3-dimensional dataset with a perceptron that has been "
+    "trained on 8 dimensions will cause an error."
     );
 
-// Necessary parameters
-PARAM_STRING_REQ("train_file", "A file containing the training set.", "t");
+// Training parameters.
+PARAM_STRING("training_file", "A file containing the training set.", "t", "");
 PARAM_STRING("labels_file", "A file containing labels for the training set.",
-  "l","");
-PARAM_STRING_REQ("test_file", "A file containing the test set.", "T");
+  "l", "");
+PARAM_INT("max_iterations","The maximum number of iterations the perceptron is "
+  "to be run", "M", 1000);
+
+// Model loading/saving.
+PARAM_STRING("input_model", "File containing input perceptron model.", "i", "");
+PARAM_STRING("output_model", "File to save trained perceptron model to.", "m",
+    "");
+
+// Testing/classification parameters.
+PARAM_STRING("test_file", "A file containing the test set.", "T", "");
+PARAM_STRING("output_file", "The file in which the predicted labels for the "
+    "test set will be written.", "o", "output.csv");
+
+// When we save a model, we must also save the class mappings.  So we use this
+// auxiliary structure to store both the perceptron and the mapping, and we'll
+// save this.
+class PerceptronModel
+{
+ private:
+  Perceptron<>& p;
+  arma::vec& map;
+
+ public:
+  PerceptronModel(Perceptron<>& p, arma::vec& map) : p(p), map(map) { }
 
-// Optional parameters.
-PARAM_STRING("output", "The file in which the predicted labels for the test set"
-    " will be written.", "o", "output.csv");
-PARAM_INT("iterations","The maximum number of iterations the perceptron is "
-  "to be run", "i", 1000);
+  template<typename Archive>
+  void Serialize(Archive& ar, const unsigned int /* version */)
+  {
+    ar & data::CreateNVP(p, "perceptron");
+    ar & data::CreateNVP(map, "mappings");
+  }
+};
 
 int main(int argc, char** argv)
 {
   CLI::ParseCommandLine(argc, argv);
 
-  // Get reference dataset filename.
-  const string trainingDataFilename = CLI::GetParam<string>("train_file");
-  mat trainingData;
-  data::Load(trainingDataFilename, trainingData, true);
-
-  const string labelsFilename = CLI::GetParam<string>("labels_file");
-  // Load labels.
-  mat labelsIn;
-
-  // Did the user pass in labels?
-  if (CLI::HasParam("labels_file"))
+  // First, get all parameters and validate them.
+  const string trainingDataFile = CLI::GetParam<string>("training_file");
+  const string labelsFile = CLI::GetParam<string>("labels_file");
+  const string inputModelFile = CLI::GetParam<string>("input_model");
+  const string testDataFile = CLI::GetParam<string>("test_file");
+  const string outputModelFile = CLI::GetParam<string>("output_model");
+  const string outputFile = CLI::GetParam<string>("output_file");
+  const size_t maxIterations = (size_t) CLI::GetParam<int>("max_iterations");
+
+  // We must either load a model or train a model.
+  if (inputModelFile == "" && trainingDataFile == "")
+    Log::Fatal << "Either an input model must be specified with --input_model "
+        << "or training data must be given (--training_file)!" << endl;
+
+  // If the user isn't going to save the output model or any predictions, we
+  // should issue a warning.
+  if (outputModelFile == "" && testDataFile == "")
+    Log::Warn << "Output will not be saved!  (Neither --test_file nor "
+        << "--output_model are specified.)" << endl;
+
+  // Now, load our model, if there is one.
+  Perceptron<>* p = NULL;
+  arma::vec mappings;
+  if (inputModelFile != "")
   {
-    // Load labels.
-    const string labelsFilename = CLI::GetParam<string>("labels_file");
-    data::Load(labelsFilename, labelsIn, true);
+    Log::Info << "Loading saved perceptron from model file '" << inputModelFile
+        << "'." << endl;
+
+    // The parameters here are invalid, but we are about to load the model
+    // anyway...
+    p = new Perceptron<>(0, 0);
+    PerceptronModel pm(*p, mappings); // Also load class mappings.
+    data::Load(inputModelFile, "perceptron_model", pm, true);
   }
-  else
+
+  // Next, load the training data and labels (if they have been given).
+  if (trainingDataFile != "")
   {
-    // Use the last row of the training data as the labels.
-    Log::Info << "Using the last dimension of training set as labels." << endl;
-    labelsIn = trainingData.row(trainingData.n_rows - 1).t();
-    trainingData.shed_row(trainingData.n_rows - 1);
+    Log::Info << "Training perceptron on dataset '" << trainingDataFile;
+    if (labelsFile != "")
+      Log::Info << "' with labels in '" << labelsFile << "'";
+    else
+      Log::Info << "'";
+    Log::Info << " for a maximum of " << maxIterations << " iterations."
+        << endl;
+
+    mat trainingData;
+    data::Load(trainingDataFile, trainingData, true);
+
+    // Load labels.
+    mat labelsIn;
+
+    // Did the user pass in labels?
+    if (CLI::HasParam("labels_file"))
+    {
+      // Load labels.
+      const string labelsFile = CLI::GetParam<string>("labels_file");
+      data::Load(labelsFile, labelsIn, true);
+    }
+    else
+    {
+      // Use the last row of the training data as the labels.
+      Log::Info << "Using the last dimension of training set as labels."
+          << endl;
+      labelsIn = trainingData.row(trainingData.n_rows - 1).t();
+      trainingData.shed_row(trainingData.n_rows - 1);
+    }
+
+    // Do the labels need to be transposed?
+    if (labelsIn.n_rows == 1)
+      labelsIn = labelsIn.t();
+
+    // Normalize the labels.
+    Col<size_t> labels;
+    data::NormalizeLabels(labelsIn.unsafe_col(0), labels, mappings);
+
+    // Now, if we haven't already created a perceptron, do it.  Otherwise, make
+    // sure the dimensions are right, then continue training.
+    if (p == NULL)
+    {
+      // Create and train the classifier.
+      Timer::Start("training");
+      p = new Perceptron<>(trainingData, labels.t(), max(labels) + 1,
+          maxIterations);
+      Timer::Stop("training");
+    }
+    else
+    {
+      // Check dimensionality.
+      if (p->Weights().n_rows != trainingData.n_rows)
+      {
+        Log::Fatal << "Perceptron from '" << inputModelFile << "' is built on "
+            << "data with " << p->Weights().n_rows << " dimensions, but data in"
+            << " '" << trainingDataFile << "' has " << trainingData.n_rows
+            << "dimensions!" << endl;
+      }
+
+      // Check the number of labels.
+      if (max(labels) + 1 > p->Weights().n_cols)
+      {
+        Log::Fatal << "Perceptron from '" << inputModelFile << "' has "
+            << p->Weights().n_cols << " classes, but the training data has "
+            << max(labels) + 1 << " classes!" << endl;
+      }
+
+      // Now train.
+      Timer::Start("training");
+      p->MaxIterations() = maxIterations;
+      p->Train(trainingData, labels.t());
+      Timer::Stop("training");
+    }
   }
 
-  // Do the labels need to be transposed?
-  if (labelsIn.n_rows == 1)
+  // Now, the training procedure is complete.  Do we have any test data?
+  if (testDataFile != "")
   {
-    labelsIn = labelsIn.t();
+    Log::Info << "Classifying dataset '" << testDataFile << "'." << endl;
+    mat testData;
+    data::Load(testDataFile, testData, true);
+
+    if (testData.n_rows != p->Weights().n_rows)
+    {
+      Log::Fatal << "Test data dimensionality (" << testData.n_rows << ") must "
+          << "be the same as the dimensionality of the perceptron ("
+          << p->Weights().n_rows << ")!" << endl;
+    }
+
+    // Time the running of the perceptron classifier.
+    Row<size_t> predictedLabels(testData.n_cols);
+    Timer::Start("testing");
+    p->Classify(testData, predictedLabels);
+    Timer::Stop("testing");
+
+    // Un-normalize labels to prepare output.
+    vec results;
+    data::RevertLabels(predictedLabels.t(), mappings, results);
+
+    // Save the predictedLabels, but we have to transpose them.
+    data::Save(outputFile, results, false /* non-fatal */, false);
   }
 
-  // Normalize the labels.
-  Col<size_t> labels;
-  vec mappings;
-  data::NormalizeLabels(labelsIn.unsafe_col(0), labels, mappings);
-
-  // Load test dataset.
-  const string testingDataFilename = CLI::GetParam<string>("test_file");
-  mat testingData;
-  data::Load(testingDataFilename, testingData, true);
-  if (testingData.n_rows != trainingData.n_rows)
+  // Lastly, do we need to save the output model?
+  if (outputModelFile != "")
   {
-    Log::Fatal << "Test data dimensionality (" << testingData.n_rows << ") "
-        << "must be the same as training data (" << trainingData.n_rows - 1
-        << ")!" << std::endl;
+    PerceptronModel pm(*p, mappings);
+    data::Save(outputModelFile, "perceptron_model", pm);
   }
 
-  int iterations = CLI::GetParam<int>("iterations");
-
-  // Create and train the classifier.
-  Timer::Start("Training");
-  Perceptron<> p(trainingData, labels.t(), max(labels) + 1, iterations);
-  Timer::Stop("Training");
-
-  // Time the running of the Perceptron Classifier.
-  Row<size_t> predictedLabels(testingData.n_cols);
-  Timer::Start("Testing");
-  p.Classify(testingData, predictedLabels);
-  Timer::Stop("Testing");
-
-  // Un-normalize labels to prepare output.
-  vec results;
-  data::RevertLabels(predictedLabels.t(), mappings, results);
-
-  // saving the predictedLabels in the transposed manner in output
-  const string outputFilename = CLI::GetParam<string>("output");
-  data::Save(outputFilename, results, true, false);
+  // Clean up memory.
+  delete p;
 }



More information about the mlpack-git mailing list