Add some documentation about the bindings project.
This commit is contained in:
@@ -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 <mlpack/prereqs.hpp>
|
||||
#include <mlpack/core/util/cli.hpp>
|
||||
#include <mlpack/core/util/mlpack_main.hpp>
|
||||
|
||||
#include <mlpack/core/kernels/gaussian_kernel.hpp>
|
||||
#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<double>("radius");
|
||||
const int maxIterations = CLI::GetParam<int>("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<arma::mat>("input"));
|
||||
arma::mat centroids;
|
||||
arma::Col<size_t> 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<arma::Col<size_t>>("output") = std::move(assignments);
|
||||
|
||||
// Should we give the user the centroid matrix?
|
||||
if (CLI::HasParam("centroid"))
|
||||
CLI::GetParam<arma::mat>("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
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user