From ccdfe60f8b4e8da89a5add3ca09ca2d9a32cff9c Mon Sep 17 00:00:00 2001 From: Ryan Curtin Date: Fri, 7 Apr 2017 17:28:20 -0400 Subject: [PATCH] Add some documentation about the bindings project. --- doc/guide/bindings.hpp | 196 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 doc/guide/bindings.hpp diff --git a/doc/guide/bindings.hpp b/doc/guide/bindings.hpp new file mode 100644 index 0000000000..3145fe765a --- /dev/null +++ b/doc/guide/bindings.hpp @@ -0,0 +1,196 @@ +/*! @page bindings mlpack automatic bindings to other languages + +@section bindings_overview Overview + +mlpack has a system to automatically generate bindings to other languages, such +as Python and command-line programs, and it is extensible to other languages +with some amount of ease. The maintenance burden of this system is low, and it +is designed in such a way that the bindings produced are always up to date +across languages and up to date with the mlpack library itself. + +This document describes the full functioning of the system, and is a good place +to start for someone who wishes to understand the system so that they can +contribute a new binding language, or someone who wants to understand so they +can adapt the system for use in their own project, or someone who is simply +curious enough to see how the sausage is made. + +@section bindings_intro Introduction + +C++ is not the most popular language on the planet, and it (unfortunately) can +scare many away with its ultra-verbose error messages, confusing template rules, +and complex metaprogramming techniques. Most practitioners of machine learning +tend to avoid writing native C++ and instead prefer other languages---probably +most notably Python. + +In the case of Python, many projects will use tools like SWIG +(http://www.swig.org/) to automatically generate bindings, or they might +hand-write Cython. The same types of strategies may be used for other +languages; hand-written MEX files may be used for MATLAB, hand-written RCpp +bindings might be used for R bindings, and so forth. + +However, these approaches have a fundamental flaw: the hand-written bindings +must be maintained, and risk going out of date as the rest of the library +changes or new functionality is added. This incurs a maintenance burden: each +major change to the library means that someone must update the bindings and test +that they are still working. mlpack is not prepared to handle this maintenance +workload; therefore an alternate solution is needed. + +At the time of the design of this system, mlpack shipped headers for a C++ +library as well as many (~40) hand-written command-line programs that used the +mlpack::CLI object to manage command-line arguments. These programs all had +similar structure, and could be logically split into three sections: + + - parse the input options supplied by the user + - run the machine learning algorithm + - prepare the output to return to the user + +The user might interface with this command-line program like the following: + +@code +$ mlpack_knn -r reference.csv -q query.csv -k 3 -d d.csv -n n.csv +@endcode + +That is, they would pass a number of input options---some were numeric values +(like @c -k @c 3 ); some were filenames (like @c -r @c reference.csv ); and a +few other types also. Therefore, the first stage of the program---parsing input +options---would be handled by reading the command line and loading any input +matrices. Preparing the output, which usually consists of data matrices (i.e. +@c -d @c d.csv ) involves saving the matrix returned by the algorithm to the +user's desired file. + +Ideally, any binding to any language would have this same structure, and the +actual "run the machine learning algorithm" code could be identical. For +MATLAB, for instance, we would not need to read the file @c reference.csv but +instead the user would simply pass their data matrix as an argument. So each +input and output parameter would need to be handled differently, but the +algorithm could be run identically across all bindings. + +Therefore, design of an automatically-generated binding system would simply +involve generating the boilerplate code necessary to parse input options for a +given language, and to return output options to a user. + +@section bindings_code Writing code that can be turned into a binding + +This section details what a binding file might actually look like. It is good +to have this API in mind when reading the following sections. + +Each mlpack binding is typically contained in the @c src/mlpack/methods/ folder +corresponding to a given machine learning algorithm, with the suffix +@c _main.cpp ; so an example is @c src/mlpack/methods/pca/pca_main.cpp . + +These files have roughly two parts: + + - definition of the input and output parameters with @c PARAM macros + - implementation of @c mlpackMain(), which is the actual machine learning code + +Here is a simple example file: + +@code +// This is a stripped version of mean_shift_main.cpp. +#include +#include +#include + +#include +#include "mean_shift.hpp" + +using namespace mlpack; +using namespace mlpack::meanshift; +using namespace mlpack::kernel; +using namespace std; + +// Define parameters for the executable. +PROGRAM_INFO("Mean Shift Clustering", "This program performs mean shift " + "clustering on the given dataset, storing the learned cluster assignments " + "either as a column of labels in the file containing the input dataset or " + "in a separate file."); + +// Required option: the user must give us a matrix. +PARAM_MATRIX_IN_REQ("input", "Input dataset to perform clustering on.", "i"); + +// Output options: the user can save the output matrix of labels and/or the +// centroids. +PARAM_UCOL_OUT("output", "Matrix to write output labels to.", "o"); +PARAM_MATRIX_OUT("centroid", "If specified, the centroids of each cluster will " + "be written to the given matrix.", "C"); + +// Mean shift configuration options. +PARAM_INT_IN("max_iterations", "Maximum number of iterations before mean shift " + "terminates.", "m", 1000); +PARAM_DOUBLE_IN("radius", "If the distance between two centroids is less than " + "the given radius, one will be removed. A radius of 0 or less means an " + "estimate will be calculated and used for the radius.", "r", 0); + +void mlpackMain() +{ + // Process the parameters that the user passed. + const double radius = CLI::GetParam("radius"); + const int maxIterations = CLI::GetParam("max_iterations"); + + if (maxIterations < 0) + { + Log::Fatal << "Invalid value for maximum iterations (" << maxIterations << + ")! Must be greater than or equal to 0." << endl; + } + + // Warn, if the user did not specify that they wanted any output. + if (!CLI::HasParam("output") && !CLI::HasParam("centroid")) + { + Log::Warn << "--output_file, --in_place, and --centroid_file are not set; " + << "no results will be saved." << endl; + } + + arma::mat dataset = std::move(CLI::GetParam("input")); + arma::mat centroids; + arma::Col assignments; + + // Prepare and run the actual algorithm. + MeanShift<> meanShift(radius, maxIterations); + + Timer::Start("clustering"); + Log::Info << "Performing mean shift clustering..." << endl; + meanShift.Cluster(dataset, assignments, centroids); + Timer::Stop("clustering"); + + Log::Info << "Found " << centroids.n_cols << " centroids." << endl; + if (radius <= 0.0) + Log::Info << "Estimated radius was " << meanShift.Radius() << ".\n"; + + // Should we give the user the output matrix? + if (CLI::HasParam("output")) + CLI::GetParam>("output") = std::move(assignments); + + // Should we give the user the centroid matrix? + if (CLI::HasParam("centroid")) + CLI::GetParam("centroid") = std::move(centroids); +} +@endcode + +We can see that we have defined the basic program information in the +@c PROGRAM_INFO() macro. This is, for instance, what is displayed to describe +the binding if the user passed the @c --help option for a command-line program. + +Then, we define five parameters, three input and two output, that define the +data and options that the mean shift clustering will function on. These +parameters are defined with the @c PARAM macros, of which there are many. The +names of these macros specify the type, whether the parameter is required, and +whether the parameter is input or output. Some examples: + + - @c PARAM_STRING_IN() -- a string-type input parameter + - @c PARAM_MATRIX_OUT() -- a matrix-type output parameter + - @c PARAM_DOUBLE_IN_REQ() -- a required double-type input parameter + - @c PARAM_UMATRIX_IN() -- an unsigned matrix-type input parameter + - @c PARAM_MODEL_IN() -- a serializable model-type input parameter + +Note that each of these macros may have slightly different syntax. See the +links above for further documentation. + +@section bindings_general General structure of code + +@section bindings_cli Command-line program bindings + +@section bindings_python Python bindings + +@section bindings_new Adding new binding types + +*/