[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