diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a4d3825354..757975ec0e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -57,7 +57,17 @@ jobs: - name: Install Build Dependencies run: | sudo apt-get update - sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libcereal-dev libensmallen-dev libhdf5-dev libarmadillo-dev libcurl4-openssl-dev + # We don't install cereal via apt, because the Debian packagers + # split the rapidjson dependency into a separate package. We will + # bundle the cereal sources with the R package, so we want them to + # be exactly the upstream sources (with rapidjson included). + sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libensmallen-dev libhdf5-dev libarmadillo-dev libcurl4-openssl-dev + wget https://github.com/USCiLab/cereal/archive/refs/tags/v1.3.2.tar.gz + tar -xvzpf v1.3.2.tar.gz + # These directives cause warnings on CRAN: + # https://github.com/USCiLab/cereal/blob/master/include/cereal/external/base64.hpp#L28-L31 + # The command below comments them out. + sed -i 's|#pragma|// #pragma|' cereal-1.3.2/include/cereal/external/base64.hpp - name: Install R-bindings dependencies run: | @@ -69,7 +79,7 @@ jobs: - name: CMake run: | mkdir build - cd build && cmake -DDEBUG=OFF -DPROFILE=OFF -DBUILD_CLI_EXECUTABLES=OFF -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=ON -DDOWNLOAD_DEPENDENCIES=ON -DBUILD_TESTS=ON .. + cd build && cmake -DDEBUG=OFF -DPROFILE=OFF -DBUILD_CLI_EXECUTABLES=OFF -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=ON -DDOWNLOAD_DEPENDENCIES=ON -DBUILD_TESTS=ON -DCEREAL_INCLUDE_DIR=../cereal-1.3.2/include/ .. - name: Build run: | @@ -121,7 +131,7 @@ jobs: r-version: ${{ matrix.config.r }} http-user-agent: ${{ matrix.config.http-user-agent }} use-public-rspm: true - + - name: Query dependencies run: Rscript -e "install.packages('remotes')" -e "saveRDS(remotes::dev_package_deps('${{ needs.jobR.outputs.r_bindings }}', dependencies = TRUE), 'depends.Rds')" diff --git a/CMake/FindArmadillo.cmake b/CMake/FindArmadillo.cmake index 8c5e0260ca..f494a5f931 100644 --- a/CMake/FindArmadillo.cmake +++ b/CMake/FindArmadillo.cmake @@ -94,6 +94,22 @@ if(NOT _ARMA_USE_WRAPPER OR MSVC) endif() endif() + # On Linux, when statically linking against OpenBLAS, we must also manually + # link against -lgfortran and -lquadmath. See + # https://gitlab.kitware.com/cmake/cmake/-/issues/23693 for more + # information. When that issue is fixed, we may be able to remove this + # section. + if (NOT BUILD_SHARED_LIBS AND + ${CMAKE_SYSTEM_NAME} MATCHES "Linux" AND + NOT CMAKE_CROSSCOMPILING) + string(TOLOWER "${LAPACK_LIBRARIES}" _lower_lapack_libs) + string(FIND "${_lower_lapack_libs}" "openblas" _openblas_found_index) + if (${_openblas_found_index} GREATER_EQUAL 0) + message(STATUS "Using static OpenBLAS on Linux; adding -lgfortran and -lquadmath...") + set(LAPACK_LIBRARIES "${LAPACK_LIBRARIES};gfortran;quadmath") + endif () + endif () + if(LAPACK_FOUND) set(_ARMA_SUPPORT_LIBRARIES "${_ARMA_SUPPORT_LIBRARIES}" "${LAPACK_LIBRARIES}") endif() diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 4cec03a865..4740e37fbc 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -7,7 +7,7 @@ Source: Files: * Copyright: - Copyright 2008-2022, Ryan Curtin + Copyright 2008-2023, Ryan Curtin Copyright 2008-2013, Bill March Copyright 2008-2012, Dongryeol Lee Copyright 2008-2013, Nishant Mehta @@ -26,7 +26,7 @@ Copyright: Copyright 2013, Mudit Raj Gupta Copyright 2013-2018, Sumedh Ghaisas Copyright 2014, Michael Fox - Copyright 2014,2020 Ryan Birmingham + Copyright 2014,2020, Ryan Birmingham Copyright 2014, Siddharth Agrawal Copyright 2014, Saheb Motiani Copyright 2014, Yash Vadalia @@ -83,15 +83,16 @@ Copyright: Copyright 2017, Samikshya Chand Copyright 2017, N Rajiv Vaidyanathan Copyright 2017, Kartik Nighania + Copyright 2017-2023, Dirk Eddelbuettel Copyright 2017-2018, Eugene Freyman Copyright 2017-2019, Manish Kumar Copyright 2017-2018, Haritha Sreedharan Nair Copyright 2017-2018, Sourabh Varshney Copyright 2018, Projyal Dev Copyright 2018, Nikhil Goel - Copyright 2018-2020 Shikhar Jaiswal + Copyright 2018-2020, Shikhar Jaiswal Copyright 2018, B Kartheek Reddy - Copyright 2018-2019 Atharva Khandait + Copyright 2018-2019, Atharva Khandait Copyright 2018, Wenhao Huang Copyright 2018-2019, Roberto Hueso Copyright 2018, Prabhat Sharma @@ -114,9 +115,9 @@ Copyright: Copyright 2019, Miguel Canteras Copyright 2019, Bishwa Karki Copyright 2019, Mehul Kumar Nirala - Copyright 2019-2020 Yashwant Singh Parihar + Copyright 2019-2020, Yashwant Singh Parihar Copyright 2019, Heet Sankesara - Copyright 2019-2020 Jeffin Sam + Copyright 2019-2020, Jeffin Sam Copyright 2019, Vikas S Shetty Copyright 2019, Khizir Siddiqui Copyright 2019, Tejasvi Tomar @@ -124,7 +125,8 @@ Copyright: Copyright 2019, Ziyang Jiang Copyright 2019, Rohit Kartik Copyright 2019, Aditya Viki - Copyright 2019-2020 Kartik Dutt + Copyright 2019-2020, Kartik Dutt + Copyright 2019, Suryoday Basak Copyright 2020, Sriram S K Copyright 2020, Manoranjan Kumar Bharti ( Nakul Bharti ) Copyright 2020, Saraansh Tandon @@ -141,12 +143,15 @@ Copyright: Copyright 2020, Alex Nguyen Copyright 2020, Gaurav Ghati Copyright 2020, Anmolpreet Singh + Copyright 2020, Anjishnu Mukherjee + Copyright 2020-2023, Omar Shrit Copyright 2021, Tru Hoang Copyright 2021, Mark Fischinger Copyright 2021, Muhammad Fawwaz Mayda Copyright 2021, Roshan Nrusing Swain Copyright 2021, Suvarsha Chennareddy Copyright 2021, Shubham Agrawal + Copyright 2020-2022, James Joseph Balamuta Copyright 2022, Sri Madhan M Copyright 2022, Zhuojin Liu Copyright 2022, Richèl Bilderbeek diff --git a/HISTORY.md b/HISTORY.md index 30a213c70c..36232c4eeb 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ ### mlpack ?.?.? ###### ????-??-?? + * Bugfix for non-square convolution kernels (#3376). + + * Fix a few missing includes in `` (#3374). + + * Fix DBSCAN handling of non-core points (#3346). ### mlpack 4.0.1 ###### 2022-12-23 diff --git a/README.md b/README.md index 8ea0dcc058..3cc3641897 100644 --- a/README.md +++ b/README.md @@ -85,29 +85,33 @@ variety of other needs. If you use mlpack in your research or software, please cite mlpack using the citation below (given in BibTeX format): - @article{mlpack2018, - title = {mlpack 3: a fast, flexible machine learning library}, - author = {Curtin, Ryan R. and Edel, Marcus and Lozhnikov, Mikhail and - Mentekidis, Yannis and Ghaisas, Sumedh and Zhang, - Shangtong}, + @article{mlpack2023, + title = {mlpack 4: a fast, header-only C++ machine learning library}, + author = {Ryan R. Curtin and Marcus Edel and Omar Shrit and + Shubham Agrawal and Suryoday Basak and James J. Balamuta and + Ryan Birmingham and Kartik Dutt and Dirk Eddelbuettel and + Rishabh Garg and Shikhar Jaiswal and Aakash Kaushik and + Sangyeon Kim and Anjishnu Mukherjee and Nanubala Gnana Sai and + Nippun Sharma and Yashwant Singh Parihar and Roshan Swain and + Conrad Sanderson}, journal = {Journal of Open Source Software}, - volume = {3}, - issue = {26}, - pages = {726}, - year = {2018}, - doi = {10.21105/joss.00726}, - url = {https://doi.org/10.21105/joss.00726} + volume = {8}, + number = {82}, + pages = {5026}, + year = {2023}, + doi = {10.21105/joss.05026}, + url = {https://doi.org/10.21105/joss.05026} } Citations are beneficial for the growth and improvement of mlpack. ## 2. Dependencies -mlpack requires a C++14 compiler and has the following additional dependencies: - - - Armadillo >= 9.800 - - ensmallen >= 2.10.0 - - cereal >= 1.1.2 +**mlpack** requires the following additional dependencies: + - C++14 compiler + - [Armadillo](https://arma.sourceforge.net)   >= 9.800 + - [ensmallen](https://ensmallen.org)  >= 2.10.0 + - [cereal](http://uscilab.github.io/cereal/)     >= 1.1.2 If the STB library headers are available, image loading support will be available. @@ -119,31 +123,41 @@ If you are compiling Armadillo by hand, ensure that LAPACK and BLAS are enabled. *See also the [C++ quickstart](doc/quickstart/cpp.md).* Since mlpack is a header-only library, installing just the headers for use in a -C++ application is trivial. From the root of the sources, configure and install +C++ application is trivial. + +From the root of the sources, configure and install in the standard CMake way: ```sh mkdir build && cd build/ -cmake ../ +cmake .. sudo make install ``` -Note: Since CMake v3.14.0 the `cmake` command can create the build folder itself. -The above commands can be rewritten as follows: +If the `cmake ..` command fails due to unavailable dependencies, consider either using the +`-DDOWNLOAD_DEPENDENCIES=ON` option as detailed in +[the following subsection](#31-additional-build-options), or ensure that mlpack's dependencies +are installed, e.g. using the system package manager. For example, on Debian and Ubuntu, +all relevant dependencies can be installed with `sudo apt-get install libarmadillo-dev libensmallen-dev libcereal-dev g++ cmake`. + +Alternatively, since CMake v3.14.0 the `cmake` command can create the build folder itself, +the above commands can be rewritten as follows: ```sh cmake -S . -B build sudo cmake --build build --target install ``` +### 3.1. Additional build options + You can add a few arguments to the `cmake` command to control the behavior of the configuration and build process. Simply add these to the `cmake` command. Some options are given below: - - `-DCMAKE_INSTALL_PREFIX=/install/root/` will set the root of the install - directory to `/install/root` when `make install` is run. - `-DDOWNLOAD_DEPENDENCIES=ON` will automatically download mlpack's dependencies (ensmallen, Armadillo, and cereal). + - `-DCMAKE_INSTALL_PREFIX=/install/root/` will set the root of the install + directory to `/install/root` when `make install` is run. - `-DDEBUG=ON` will enable debugging symbols in any compiled bindings or tests. There are also options to enable building bindings to each language that mlpack @@ -171,7 +185,7 @@ See the [C++ quickstart](doc/quickstart/cpp.md) and the [examples](https://github.com/mlpack/examples) repository for some examples of mlpack applications in C++, with corresponding `Makefile`s. -### 3.1. Including mlpack and improving compile time +### 3.2. Reducing compile time mlpack is a template-heavy library, and if care is not used, compilation time of a project can be increased greatly. Fortunately, there are a number of ways to diff --git a/doc/developer/bindings.md b/doc/developer/bindings.md index cd3c4720db..53f6e11b02 100644 --- a/doc/developer/bindings.md +++ b/doc/developer/bindings.md @@ -254,7 +254,7 @@ links above for further documentation. In order to write a new binding, then, you simply must define `BINDING_NAME`, then write `BINDING_USER_NAME()`, `BINDING_SHORT_DESC()`, `BINDING_LONG_DESC()`, `BINDING_EXAMPLE()` and `BINDING_SEE_ALSO()` definitions of the program with -some docuentation, define the input and output parameters as `PARAM` macros, and +some documentation, define the input and output parameters as `PARAM` macros, and then write a `BINDING_FUNCTION()` function that actually performs the functionality of the binding. @@ -695,7 +695,7 @@ There are numerous different macros that can be used: - `PARAM_TMATRIX_OUT()` - transposed double-valued matrix (`arma::mat`) output parameter - `PARAM_MATRIX_AND_INFO_IN()` - matrix with categoricals input parameter - (`std::tuple`) - `PARAM_COL_IN()` - double-valued column vector (`arma::vec`) input parameter - `PARAM_COL_OUT()` - double-valued column vector (`arma::vec`) output parameter @@ -1322,7 +1322,7 @@ If this is the route that is desired, a large amount of CMake boilerplate may be necessary. The Python CMake configuration can be referred to as an example, but probably a large amount of adaptation to other languages will be necessary. -Lastly, when adding a new language, be sure to make sure it works with the +Lastly, when adding a new language, make sure it works with the Markdown documentation generator. In order to make this happen, you will need to modify all of the `add_markdown_docs()` calls in `src/mlpack/methods/CMakeLists.txt` to contain the name of the language you have diff --git a/doc/developer/kernels.md b/doc/developer/kernels.md index 7bd65bd293..09aa23c39a 100644 --- a/doc/developer/kernels.md +++ b/doc/developer/kernels.md @@ -16,7 +16,7 @@ including mlpack implements a number of kernel methods and, accordingly, each of these methods allows arbitrary kernels to be used via the `KernelType` template -parameter. Like the [MetricType policy](metrictype.md), the requirements are +parameter. Like the [MetricType policy](metrics.md), the requirements are quite simple: a class implementing the `KernelType` policy must have - an `Evaluate()` function @@ -42,7 +42,7 @@ Note that for kernels that do not hold any state, the `Evaluate()` method can be marked as `static`. Overall, the `KernelType` template policy is quite simple (much like the -[MetricType policy](metrictype.md)). Below is an example kernel class, which +[MetricType policy](metrics.md)). Below is an example kernel class, which outputs `1` if the vectors are close and `0` otherwise. ```c++ diff --git a/doc/developer/metrics.md b/doc/developer/metrics.md index b4cec62526..4a6ec5bc6a 100644 --- a/doc/developer/metrics.md +++ b/doc/developer/metrics.md @@ -35,7 +35,7 @@ Note that for metrics that do not hold any state, the `Evaluate()` method can be marked as `static`. Overall, the `MetricType` template policy is quite simple (much like the -[KernelType policy](kerneltype.md)). Below is an example metric class, which +[KernelType policy](kernels.md)). Below is an example metric class, which implements the L2 distance: ```c++ @@ -105,4 +105,4 @@ policy: - `ChebyshevDistance` - `MahalanobisDistance` - `LMetric` (for arbitrary L-metrics) - - `IPMetric` (requires a [KernelType](kerneltype.md) parameter) + - `IPMetric` (requires a [KernelType](kernels.md) parameter) diff --git a/doc/developer/trees.md b/doc/developer/trees.md index 97c76c5418..17e9525939 100644 --- a/doc/developer/trees.md +++ b/doc/developer/trees.md @@ -86,11 +86,11 @@ restatement of the fourth part of the definition). Most everything in mlpack is decomposed into a series of configurable template parameters, and trees are no exception. In order to ease usage of high-level -mlpack algorithms, each \c TreeType itself must be a template class taking three +mlpack algorithms, each `TreeType` itself must be a template class taking three parameters: - `MetricType` -- the underlying metric that the tree will be built on (see -[the MetricType policy documentation](metrictype.md)) +[the MetricType policy documentation](metrics.md)) - `StatisticType` -- holds any auxiliary information that individual algorithms may need - `MatType` -- the type of the matrix used to represent the data @@ -424,7 +424,7 @@ This constructor should be called with `(*this)` after the node is constructed The last template parameter is the `MatType` parameter. This is generally `arma::mat` or `arma::sp_mat`, but could be any Armadillo type, including matrices that hold data points of different precisions (such as `float` or even -`int`). It generally suffices to write \c MatType assuming that `arma::mat` +`int`). It generally suffices to write `MatType` assuming that `arma::mat` will be used, since the vast majority of the time this will be what is used. ### Constructors and destructors diff --git a/doc/quickstart/cpp.md b/doc/quickstart/cpp.md index 3f92b2b15f..213d0917fd 100644 --- a/doc/quickstart/cpp.md +++ b/doc/quickstart/cpp.md @@ -105,11 +105,11 @@ int main() testLabels, 0.3); // Create the RandomForest object and train it on the training data. - RandomForest r(trainDataset, - trainLabels, - 7 /* number of classes */, - 10 /* number of trees */, - 3 /* minimum leaf size */); + RandomForest<> r(trainDataset, + trainLabels, + 7 /* number of classes */, + 10 /* number of trees */, + 3 /* minimum leaf size */); // Compute and print the training error. Row trainPredictions; diff --git a/doc/tutorials/amf.md b/doc/tutorials/amf.md index 32c7d56913..5579b169b2 100644 --- a/doc/tutorials/amf.md +++ b/doc/tutorials/amf.md @@ -137,7 +137,8 @@ int main() NMFALSFactorizer nmf; mat W, H; mat V = randu(100, 100); - double residue = nmf.Apply(V, W, H); + size_t r = 10; + double residue = nmf.Apply(V, r, W, H); } ``` @@ -169,11 +170,12 @@ using namespace mlpack; int main() { - sp_mat V = randu(100,100); + sp_mat V = sprandu(100,100,0.1); + size_t r = 10; mat W, H; SVDBatchFactorizer svd; - double residue = svd.Apply(V, W, H); + double residue = svd.Apply(V, r, W, H); } ``` diff --git a/doc/tutorials/approx_kfn.md b/doc/tutorials/approx_kfn.md index c703aefa45..01613b11a0 100644 --- a/doc/tutorials/approx_kfn.md +++ b/doc/tutorials/approx_kfn.md @@ -80,7 +80,7 @@ In order to solve this problem, mlpack provides a number of interfaces. - two simple command-line executables to calculate approximate furthest neighbors - - a simple C++ class for QDAFN" + - a simple C++ class for QDAFN - a simple C++ class for DrusillaSelect - a simple C++ class for tree-based and brute-force search @@ -114,8 +114,8 @@ search: These two programs allow a large number of algorithms to be used to find approximate furthest neighbors. Note that the `mlpack_kfn` program is also -documented in the [KNN tutorial](knn.md) page, as it shares options with the -`mlpack_knn` program. +documented in the [KNN tutorial](neighbor_search.md) page, as it shares options +with the `mlpack_knn` program. Below are several examples of how the `mlpack_approx_kfn` and `mlpack_kfn` programs might be used. The first examples focus on the `mlpack_approx_kfn` @@ -682,7 +682,7 @@ std::cout << ds.CandidateSet().col(4).t(); It is possible to retrain a `DrusillaSelect` model with new parameters or with a new reference set. This is functionally equivalent to creating a new model. -The example code below creates a first \c DrusillaSelect model using 3 tables +The example code below creates a first `DrusillaSelect` model using 3 tables and 10 projections, and then retrains this with the same reference set using 10 tables and 3 projections. @@ -869,7 +869,7 @@ qdafn.Search(querySet, 3, neighbors, distances); The extensive `NeighborSearch` class also provides a way to search for approximate furthest neighbors using a different, tree-based technique. For full documentation on this class, see the [NeighborSearch -tutorial](nstutorial.md). The `KFN` class is a convenient typedef of the +tutorial](neighbor_search.md). The `KFN` class is a convenient typedef of the `NeighborSearch` class that can be used to perform the furthest neighbors task with `kd`-trees. @@ -982,6 +982,6 @@ kfn.Search(querySet, 2, neighbors, distances); ## Further documentation For further documentation on the approximate furthest neighbor facilities -offered by mlpack, see also [the NeighborSearch tutorial](nstutorial.md). Also, +offered by mlpack, see also [the NeighborSearch tutorial](neighbor_search.md). Also, each class (`QDAFN`, `DrusillaSelect`, `NeighborSelect`) are well-documented, and more details can be found in the source code documentation. diff --git a/doc/tutorials/cf.md b/doc/tutorials/cf.md index 2d4a313ba8..9452ebfdd0 100644 --- a/doc/tutorials/cf.md +++ b/doc/tutorials/cf.md @@ -389,8 +389,8 @@ number of rows equal to the number of items and the number of columns equal to the number of users, and each nonzero element in the matrix corresponds to a non-missing rating. -The method that the factorizer implements is specified via the \c -FactorizerTraits class, which is a template metaprogramming traits class: +The method that the factorizer implements is specified via the +`FactorizerTraits` class, which is a template metaprogramming traits class: ```c++ template diff --git a/doc/tutorials/image.md b/doc/tutorials/image.md index dc08a085dd..01169e7afb 100644 --- a/doc/tutorials/image.md +++ b/doc/tutorials/image.md @@ -2,7 +2,7 @@ Image datasets are becoming increasingly popular in deep learning. -mlpack's image saving/loading functionality is based on [stb/](https://github.com/nothings/stb). +mlpack's image saving/loading functionality is based on [stb](https://github.com/nothings/stb). ## Model API diff --git a/doc/tutorials/kmeans.md b/doc/tutorials/kmeans.md index 559327a399..afbcdb6ca5 100644 --- a/doc/tutorials/kmeans.md +++ b/doc/tutorials/kmeans.md @@ -445,11 +445,12 @@ how to modify them. Most machine learning algorithms in mlpack support modifying the distance metric, and `KMeans<>` is no exception. Similar to `NeighborSearch` (see the -section in the [NeighborSearch tutorial](neighbor_search.md)), any of mlpack's +"MetricType policy class" section in the +[NeighborSearch tutorial](neighbor_search.md)), any of mlpack's metric classes (found in `mlpack/core/metrics/`) can be given as an argument. The `LMetric` class is a good example implementation. -A class fulfilling the [MetricType policy](../developer/metrictype.md) must +A class fulfilling the [MetricType policy](../developer/metrics.md) must provide the following two functions: ```c++ diff --git a/doc/tutorials/neighbor_search.md b/doc/tutorials/neighbor_search.md index 8180b46bba..153acf93ed 100644 --- a/doc/tutorials/neighbor_search.md +++ b/doc/tutorials/neighbor_search.md @@ -363,7 +363,7 @@ covariance matrix). Therefore, you can write a non-static MetricType class and use it seamlessly with `NeighborSearch`. For more information on the `MetricType` policy, see the [documentation for -`MetricType`s](../developer/metrics.md). +`MetricType`](../developer/metrics.md). ### `MatType` policy class @@ -418,8 +418,7 @@ The `RuleType` class provides the following functions for use in the traverser: // Evaluate the base case between two points. double BaseCase(const size_t queryIndex, const size_t referenceIndex); -// Score the two nodes to see if they can be pruned, returning DBL_MAX if they -// can be pruned. +// Score the two nodes to see if they can be pruned, returning DBL_MAX if so. double Score(TreeType& queryNode, TreeType& referenceNode); ``` diff --git a/doc/tutorials/range_search.md b/doc/tutorials/range_search.md index 55dd98acae..42fa5b6526 100644 --- a/doc/tutorials/range_search.md +++ b/doc/tutorials/range_search.md @@ -3,8 +3,8 @@ Range search is a simple machine learning task which aims to find all the neighbors of a point that fall into a certain range of distances. In this setting, we have a *query* and a *reference* dataset. Given a certain range, -for each point in the *query* dataset, we wish to know all points in the \b -reference dataset which have distances within that given range to the given +for each point in the *query* dataset, we wish to know all points in the +*reference* dataset which have distances within that given range to the given query point. Alternately, if the query and reference datasets are the same, the problem can diff --git a/doc/tutorials/reinforcement_learning.md b/doc/tutorials/reinforcement_learning.md index 477258a17b..02e627b28e 100644 --- a/doc/tutorials/reinforcement_learning.md +++ b/doc/tutorials/reinforcement_learning.md @@ -143,13 +143,13 @@ can't pass mlpack's `FFN` network directly. Instead, we have to wrap it into int main() { // Set up the network. - FFN, GaussianInitialization> network(MeanSquaredError<>(), + FFN network(MeanSquaredError(), GaussianInitialization(0, 0.001)); - network.Add>(4, 128); - network.Add>(); - network.Add>(128, 128); - network.Add>(); - network.Add>(128, 2); + network.Add(128); + network.Add(); + network.Add(128); + network.Add(); + network.Add(2); SimpleDQN<> model(network); @@ -159,7 +159,7 @@ The next step would be to setup the other components of the Q-learning agent, namely its policy, replay method and hyperparameters. ```c++ - // Set up the policy and replay method. + // Set up the policy and replay method. GreedyPolicy policy(1.0, 1000, 0.1, 0.99); RandomReplay replayMethod(10, 10000); @@ -314,6 +314,7 @@ auto measure = [&returns, &position, &episode](double episodeReturn) std::cout << "Episode No.: " << episode << "; Episode Return: " << episodeReturn << "; Average Return: " << arma::mean(returns) << std::endl; + return false; }; ``` @@ -328,17 +329,16 @@ Here is the full code to try this right away: #include using namespace mlpack; -using namespace mlpack::rl; int main() { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), GaussianInitialization(0, 0.001)); - model.Add>(4, 128); - model.Add>(); - model.Add>(128, 128); - model.Add>(); - model.Add>(128, 2); + FFN model(MeanSquaredError(), GaussianInitialization(0, 0.001)); + model.Add(128); + model.Add(); + model.Add(128); + model.Add(); + model.Add(2); AggregatedPolicy> policy({GreedyPolicy(0.7, 5000, 0.1), GreedyPolicy(0.7, 5000, 0.01), @@ -371,6 +371,7 @@ int main() std::cout << "Episode No.: " << episode << "; Episode Return: " << episodeReturn << "; Average Return: " << arma::mean(returns) << std::endl; + return false; }; for (int i = 0; i < 100; i++) diff --git a/doc/user/cv.md b/doc/user/cv.md index be392c2b0f..53067911d7 100644 --- a/doc/user/cv.md +++ b/doc/user/cv.md @@ -70,7 +70,7 @@ SoftmaxRegression(const arma::mat& data, ``` which has the parameter `lambda` after three conventional arguments (`data`, -\c labels and \c numClasses). We can skip passing `fitIntercept` and +`labels` and `numClasses`). We can skip passing `fitIntercept` and `optimizer` since there are the default values. (Technically, we don't even need to pass `lambda` since there is a default value.) diff --git a/doc/user/formats.md b/doc/user/formats.md index f7c6daae42..eaf928edba 100644 --- a/doc/user/formats.md +++ b/doc/user/formats.md @@ -177,8 +177,8 @@ matrix = matrix.t(); // We must transpose after load! The transposition after loading is necessary if the coordinate list is in row-major format (that is, if each row in the matrix represents a point and each column represents a feature). Be sure that the matrix you use with mlpack -methods has points as columns and features as rows! See \ref matrices for more -information. +methods has points as columns and features as rows! See [matrices](matrices.md) +for more information. ## Categorical features and command line programs diff --git a/doc/user/hpt.md b/doc/user/hpt.md index 88bcb28e23..738a9c6f26 100644 --- a/doc/user/hpt.md +++ b/doc/user/hpt.md @@ -179,7 +179,7 @@ HyperParameterTuner hpt(0.2, dataset, ``` Next, we must set up the hyperparameters to be optimized. If we are doing a -grid search with the \c ens::GridSearch optimizer (the +grid search with the `ens::GridSearch` optimizer (the default), then we only need to pass a `std::vector` (for non-numeric hyperparameters) or an `arma::vec` (for numeric hyperparameters) containing all of the possible choices that we wish to search over. @@ -216,6 +216,6 @@ Optimization" section for more details. ## Further documentation -For more information on the `HyperParameterTuner` class, see the source code fro +For more information on the `HyperParameterTuner` class, see the source code of the `HyperParameterTuner` class (it is very well commented!), and the [cross-validation tutorial](cv.md). diff --git a/src/mlpack.hpp b/src/mlpack.hpp index 91038d4c2e..47ee571b48 100644 --- a/src/mlpack.hpp +++ b/src/mlpack.hpp @@ -48,6 +48,7 @@ #include "mlpack/methods/kmeans.hpp" #include "mlpack/methods/lars.hpp" #include "mlpack/methods/linear_regression.hpp" +#include "mlpack/methods/linear_svm.hpp" #include "mlpack/methods/lmnn.hpp" #include "mlpack/methods/local_coordinate_coding.hpp" #include "mlpack/methods/logistic_regression.hpp" @@ -57,8 +58,11 @@ #include "mlpack/methods/naive_bayes.hpp" #include "mlpack/methods/nca.hpp" #include "mlpack/methods/neighbor_search.hpp" +#include "mlpack/methods/nmf.hpp" +#include "mlpack/methods/nystroem_method.hpp" #include "mlpack/methods/pca.hpp" #include "mlpack/methods/perceptron.hpp" +#include "mlpack/methods/preprocess.hpp" #include "mlpack/methods/quic_svd.hpp" #include "mlpack/methods/radical.hpp" #include "mlpack/methods/random_forest.hpp" diff --git a/src/mlpack/bindings/R/CMakeLists.txt b/src/mlpack/bindings/R/CMakeLists.txt index 804528c099..b8ff0ca988 100644 --- a/src/mlpack/bindings/R/CMakeLists.txt +++ b/src/mlpack/bindings/R/CMakeLists.txt @@ -83,6 +83,11 @@ if (BUILD_R_BINDINGS) "\\1" MLPACK_VERSION_PATCH "${VERSION_HPP_CONTENTS}") set(PACKAGE_VERSION "${MLPACK_VERSION_MAJOR}.${MLPACK_VERSION_MINOR}.${MLPACK_VERSION_PATCH}") + if (USING_GIT) + set(PACKAGE_DOC_VERSION "git") + else () + set(PACKAGE_DOC_VERSION "${PACKAGE_VERSION}") + endif () string(TIMESTAMP PACKAGE_DATE "%Y-%m-%d") diff --git a/src/mlpack/bindings/R/generate_R.cpp.in b/src/mlpack/bindings/R/generate_R.cpp.in index dfef755e84..0a52e1cd80 100644 --- a/src/mlpack/bindings/R/generate_R.cpp.in +++ b/src/mlpack/bindings/R/generate_R.cpp.in @@ -3,6 +3,11 @@ * @author Yashwant Singh Parihar * * This is a template file to call the PrintR() function for a given binding. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #define BINDING_TYPE BINDING_TYPE_R // Disable debug output. diff --git a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in index 4745c42a17..f511fa6e5b 100644 --- a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in +++ b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in @@ -15,7 +15,7 @@ LinkingTo: Rcpp, RcppArmadillo (>= @RcppArmadillo_Version@), RcppEnsmallen (>= @RcppEnsmallen_Version@) Suggests: testthat (>= 2.1.0) -URL: https://www.mlpack.org/doc/mlpack-@PACKAGE_VERSION@/r_documentation.html, +URL: https://www.mlpack.org/doc/mlpack-@PACKAGE_DOC_VERSION@/r_documentation.html, https://github.com/mlpack/mlpack BugReports: https://github.com/mlpack/mlpack/issues RoxygenNote: 7.1.0 diff --git a/src/mlpack/bindings/R/tests/test_r_binding_main.cpp b/src/mlpack/bindings/R/tests/test_r_binding_main.cpp index b12cafc771..290e7d1302 100644 --- a/src/mlpack/bindings/R/tests/test_r_binding_main.cpp +++ b/src/mlpack/bindings/R/tests/test_r_binding_main.cpp @@ -121,11 +121,11 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timers */) // the 3rd row will be multiplied by two. if (params.Has("matrix_in")) { - arma::mat out = move(params.Get("matrix_in")); + arma::mat out = std::move(params.Get("matrix_in")); out.shed_row(4); out.row(2) *= 2.0; - params.Get("matrix_out") = move(out); + params.Get("matrix_out") = std::move(out); } // Input matrices should be at least 5 rows; the 5th row will be dropped and @@ -133,70 +133,70 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timers */) if (params.Has("umatrix_in")) { arma::Mat out = - move(params.Get>("umatrix_in")); + std::move(params.Get>("umatrix_in")); out.shed_row(4); out.row(2) *= 2; - params.Get>("umatrix_out") = move(out); + params.Get>("umatrix_out") = std::move(out); } // An input column or row should have all elements multiplied by two. if (params.Has("col_in")) { - arma::vec out = move(params.Get("col_in")); + arma::vec out = std::move(params.Get("col_in")); out *= 2.0; - params.Get("col_out") = move(out); + params.Get("col_out") = std::move(out); } if (params.Has("ucol_in")) { arma::Col out = - move(params.Get>("ucol_in")); + std::move(params.Get>("ucol_in")); out += 1; - params.Get>("ucol_out") = move(out); + params.Get>("ucol_out") = std::move(out); } if (params.Has("row_in")) { - arma::rowvec out = move(params.Get("row_in")); + arma::rowvec out = std::move(params.Get("row_in")); out *= 2.0; - params.Get("row_out") = move(out); + params.Get("row_out") = std::move(out); } if (params.Has("urow_in")) { arma::Row out = - move(params.Get>("urow_in")); + std::move(params.Get>("urow_in")); out += 1; - params.Get>("urow_out") = move(out); + params.Get>("urow_out") = std::move(out); } // Vector arguments should have the last element removed. if (params.Has("vector_in")) { - vector out = move(params.Get>("vector_in")); + vector out = std::move(params.Get>("vector_in")); out.pop_back(); - params.Get>("vector_out") = move(out); + params.Get>("vector_out") = std::move(out); } if (params.Has("str_vector_in")) { - vector out = move(params.Get>("str_vector_in")); + vector out = std::move(params.Get>("str_vector_in")); out.pop_back(); - params.Get>("str_vector_out") = move(out); + params.Get>("str_vector_out") = std::move(out); } // All numeric elements should be multiplied by 3. if (params.Has("matrix_and_info_in")) { typedef tuple TupleType; - TupleType tuple = move(params.Get("matrix_and_info_in")); + TupleType tuple = std::move(params.Get("matrix_and_info_in")); const data::DatasetInfo& di = std::get<0>(tuple); arma::mat& m = std::get<1>(tuple); @@ -222,7 +222,7 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timers */) } } - params.Get("matrix_and_info_out") = move(m); + params.Get("matrix_and_info_out") = std::move(m); } // If we got a request to build a model, then build it. diff --git a/src/mlpack/bindings/cli/third_party/CLI/CLI11.hpp b/src/mlpack/bindings/cli/third_party/CLI/CLI11.hpp index 739fd8bd5e..3913fa9ca3 100644 --- a/src/mlpack/bindings/cli/third_party/CLI/CLI11.hpp +++ b/src/mlpack/bindings/cli/third_party/CLI/CLI11.hpp @@ -1,11 +1,11 @@ -// CLI11: Version 2.3.1 +// CLI11: Version 2.3.2 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v2.3.1 +// from: v2.3.2 // -// CLI11 2.3.1 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry +// CLI11 2.3.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without @@ -34,34 +34,34 @@ #pragma once // Standard combined includes: -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include #include -#include +#include +#include +#include #include +#include +#include #include #include +#include +#include #include +#include +#include +#include +#include +#include +#include #define CLI11_VERSION_MAJOR 2 #define CLI11_VERSION_MINOR 3 -#define CLI11_VERSION_PATCH 1 -#define CLI11_VERSION "2.3.1" +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.3.2" @@ -972,7 +972,9 @@ constexpr enabler dummy = {}; template using enable_if_t = typename std::enable_if::type; /// A copy of std::void_t from C++17 (helper for C++11 and C++14) -template struct make_void { using type = void; }; +template struct make_void { + using type = void; +}; /// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine template using void_t = typename make_void::type; @@ -1001,10 +1003,14 @@ template struct is_copyable_ptr { }; /// This can be specialized to override the type deduction for IsMember. -template struct IsMemberType { using type = T; }; +template struct IsMemberType { + using type = T; +}; /// The main custom type needed here is const char * should be a string. -template <> struct IsMemberType { using type = std::string; }; +template <> struct IsMemberType { + using type = std::string; +}; namespace detail { @@ -1014,7 +1020,9 @@ namespace detail { /// pointer_traits be valid. /// not a pointer -template struct element_type { using type = T; }; +template struct element_type { + using type = T; +}; template struct element_type::value>::type> { using type = typename std::pointer_traits::element_type; @@ -1022,7 +1030,9 @@ template struct element_type struct element_value_type { using type = typename element_type::type::value_type; }; +template struct element_value_type { + using type = typename element_type::type::value_type; +}; /// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. template struct pair_adaptor : std::false_type { @@ -1283,7 +1293,9 @@ auto value_string(const T &value) -> decltype(to_string(value)) { } /// template to get the underlying value type if it exists or use a default -template struct wrapped_type { using type = def; }; +template struct wrapped_type { + using type = def; +}; /// Type size for regular object types that do not look like a tuple template struct wrapped_type::value>::type> { @@ -1291,7 +1303,9 @@ template struct wrapped_type struct type_count_base { static const int value{0}; }; +template struct type_count_base { + static const int value{0}; +}; /// Type size for regular object types that do not look like a tuple template @@ -1321,7 +1335,9 @@ template struct subtype_count; template struct subtype_count_min; /// This will only trigger for actual void type -template struct type_count { static const int value{0}; }; +template struct type_count { + static const int value{0}; +}; /// Type size for regular object types that do not look like a tuple template @@ -1372,7 +1388,9 @@ template struct subtype_count { }; /// This will only trigger for actual void type -template struct type_count_min { static const int value{0}; }; +template struct type_count_min { + static const int value{0}; +}; /// Type size for regular object types that do not look like a tuple template @@ -1421,7 +1439,9 @@ template struct subtype_count_min { }; /// This will only trigger for actual void type -template struct expected_count { static const int value{0}; }; +template struct expected_count { + static const int value{0}; +}; /// For most types the number of expected items is 1 template @@ -1725,11 +1745,15 @@ inline std::string type_name() { /// Convert to an unsigned integral template ::value, detail::enabler> = detail::dummy> bool integral_conversion(const std::string &input, T &output) noexcept { - if(input.empty()) { + if(input.empty() || input.front() == '-') { return false; } char *val = nullptr; + errno = 0; std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } output = static_cast(output_ll); if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { return true; @@ -1750,7 +1774,11 @@ bool integral_conversion(const std::string &input, T &output) noexcept { return false; } char *val = nullptr; + errno = 0; std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } output = static_cast(output_ll); if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { return true; @@ -1867,18 +1895,18 @@ bool lexical_cast(const std::string &input, T &output) { bool worked = false; auto nloc = str1.find_last_of("+-"); if(nloc != std::string::npos && nloc > 0) { - worked = detail::lexical_cast(str1.substr(0, nloc), x); + worked = lexical_cast(str1.substr(0, nloc), x); str1 = str1.substr(nloc); if(str1.back() == 'i' || str1.back() == 'j') str1.pop_back(); - worked = worked && detail::lexical_cast(str1, y); + worked = worked && lexical_cast(str1, y); } else { if(str1.back() == 'i' || str1.back() == 'j') { str1.pop_back(); - worked = detail::lexical_cast(str1, y); + worked = lexical_cast(str1, y); x = XC{0}; } else { - worked = detail::lexical_cast(str1, x); + worked = lexical_cast(str1, x); y = XC{0}; } } @@ -2099,7 +2127,7 @@ template = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { ConvertTo val{}; - bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; if(parse_result) { output = val; } @@ -2115,7 +2143,7 @@ template < detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { ConvertTo val{}; - bool parse_result = input.empty() ? true : lexical_cast(input, val); + bool parse_result = input.empty() ? true : lexical_cast(input, val); if(parse_result) { output = AssignTo(val); // use () form of constructor to allow some implicit conversions } @@ -2193,7 +2221,7 @@ bool lexical_conversion(const std::vector &strings, AssignTo &outpu if(str1.back() == 'i' || str1.back() == 'j') { str1.pop_back(); } - auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); if(worked) { output = ConvertTo{x, y}; } @@ -2457,7 +2485,7 @@ inline std::string sum_string_vector(const std::vector &values) { std::string output; for(const auto &arg : values) { double tv{0.0}; - auto comp = detail::lexical_cast(arg, tv); + auto comp = lexical_cast(arg, tv); if(!comp) { try { tv = static_cast(detail::to_flag_value(arg)); @@ -2475,8 +2503,7 @@ inline std::string sum_string_vector(const std::vector &values) { } else { if(val <= static_cast((std::numeric_limits::min)()) || val >= static_cast((std::numeric_limits::max)()) || - // NOLINTNEXTLINE(clang-diagnostic-float-equal,bugprone-narrowing-conversions) - val == static_cast(val)) { + std::ceil(val) == std::floor(val)) { output = detail::value_string(static_cast(val)); } else { output = detail::value_string(val); @@ -2998,8 +3025,9 @@ template class TypeValidator : public Validator { public: explicit TypeValidator(const std::string &validator_name) : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; auto val = DesiredType(); - if(!detail::lexical_cast(input_string, val)) { + if(!lexical_cast(input_string, val)) { return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); } return std::string(); @@ -3033,8 +3061,9 @@ class Range : public Validator { } func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; T val; - bool converted = detail::lexical_cast(input, val); + bool converted = lexical_cast(input, val); if((!converted) || (val < min_val || val > max_val)) { std::stringstream out; out << "Value " << input << " not in range ["; @@ -3070,8 +3099,9 @@ class Bound : public Validator { description(out.str()); func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; T val; - bool converted = detail::lexical_cast(input, val); + bool converted = lexical_cast(input, val); if(!converted) { return std::string("Value ") + input + " could not be converted"; } @@ -3262,8 +3292,9 @@ class IsMember : public Validator { // This is the function that validates // It stores a copy of the set pointer-like, so shared_ptr will stay alive func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; local_item_t b; - if(!detail::lexical_cast(input, b)) { + if(!lexical_cast(input, b)) { throw ValidationError(input); // name is added later } if(filter_fn) { @@ -3330,8 +3361,9 @@ class Transformer : public Validator { desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; local_item_t b; - if(!detail::lexical_cast(input, b)) { + if(!lexical_cast(input, b)) { return std::string(); // there is no possible way we can match anything in the mapping if we can't convert so just return } @@ -3399,8 +3431,9 @@ class CheckedTransformer : public Validator { desc_function_ = tfunc; func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; local_item_t b; - bool converted = detail::lexical_cast(input, b); + bool converted = lexical_cast(input, b); if(converted) { if(filter_fn) { b = filter_fn(b); @@ -3502,7 +3535,8 @@ class AsNumberWithUnit : public Validator { unit = detail::to_lower(unit); } if(unit.empty()) { - if(!detail::lexical_cast(input, num)) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { throw ValidationError(std::string("Value ") + input + " could not be converted to " + detail::type_name()); } @@ -3520,7 +3554,8 @@ class AsNumberWithUnit : public Validator { } if(!input.empty()) { - bool converted = detail::lexical_cast(input, num); + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); if(!converted) { throw ValidationError(std::string("Value ") + input + " could not be converted to " + detail::type_name()); @@ -3829,7 +3864,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { } int num = 0; for(const auto &var : result) { - bool retval = detail::lexical_cast(var, num); + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); if(!retval) { return std::string("Failed parsing number (") + var + ')'; } @@ -5548,8 +5584,11 @@ struct AppFriend; } // namespace detail namespace FailureMessage { -std::string simple(const App *app, const Error &e); -std::string help(const App *app, const Error &e); +/// Printout a clean, simple message on error (the default in CLI11 1.5+) +CLI11_INLINE std::string simple(const App *app, const Error &e); + +/// Printout the full help string on error (if this fn is set, the old default for CLI11) +CLI11_INLINE std::string help(const App *app, const Error &e); } // namespace FailureMessage /// enumeration of modes of how to deal with extras in config files @@ -6122,7 +6161,8 @@ class App { std::string flag_description = "") { CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { - return CLI::detail::lexical_cast(res[0], flag_result); + using CLI::detail::lexical_cast; + return lexical_cast(res[0], flag_result); }; auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); return detail::default_flag_modifiers(opt); @@ -6138,8 +6178,9 @@ class App { CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { bool retval = true; for(const auto &elem : res) { + using CLI::detail::lexical_cast; flag_results.emplace_back(); - retval &= detail::lexical_cast(elem, flag_results.back()); + retval &= lexical_cast(elem, flag_results.back()); } return retval; }; @@ -6851,16 +6892,6 @@ CLI11_INLINE void retire_option(App *app, const std::string &option_name); /// Helper function to mark an option as retired CLI11_INLINE void retire_option(App &app, const std::string &option_name); -namespace FailureMessage { - -/// Printout a clean, simple message on error (the default in CLI11 1.5+) -CLI11_INLINE std::string simple(const App *app, const Error &e); - -/// Printout the full help string on error (if this fn is set, the old default for CLI11) -CLI11_INLINE std::string help(const App *app, const Error &e); - -} // namespace FailureMessage - namespace detail { /// This class is simply to allow tests access to App's protected functions struct AppFriend { @@ -7143,8 +7174,9 @@ CLI11_INLINE Option *App::add_flag_callback(std::string flag_name, std::string flag_description) { CLI::callback_t fun = [function](const CLI::results_t &res) { + using CLI::detail::lexical_cast; bool trigger{false}; - auto result = CLI::detail::lexical_cast(res[0], trigger); + auto result = lexical_cast(res[0], trigger); if(result && trigger) { function(); } @@ -7159,8 +7191,9 @@ App::add_flag_function(std::string flag_name, std::string flag_description) { CLI::callback_t fun = [function](const CLI::results_t &res) { + using CLI::detail::lexical_cast; std::int64_t flag_count{0}; - CLI::detail::lexical_cast(res[0], flag_count); + lexical_cast(res[0], flag_count); function(flag_count); return true; }; @@ -7544,7 +7577,7 @@ CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMo // Delegate to subcommand if needed auto selected_subcommands = get_subcommands(); if(!selected_subcommands.empty()) { - return selected_subcommands.at(0)->help(prev, mode); + return selected_subcommands.back()->help(prev, mode); } return formatter_->make_help(this, prev, mode); } @@ -8274,7 +8307,22 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t if(item.inputs.size() <= 1) { // Flag parsing auto res = config_formatter_->to_flag(item); - res = op->get_flag_value(item.name, res); + bool converted{false}; + if(op->get_disable_flag_override()) { + + try { + auto val = detail::to_flag_value(res); + if(val == 1) { + res = op->get_flag_value(item.name, "{}"); + converted = true; + } + } catch(...) { + } + } + + if(!converted) { + res = op->get_flag_value(item.name, res); + } op->add_result(res); return true; @@ -8998,8 +9046,9 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string } // floating point conversion can convert some hex codes, but don't try that here if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { + using CLI::detail::lexical_cast; double val = 0.0; - if(detail::lexical_cast(arg, val)) { + if(lexical_cast(arg, val)) { return arg; } } @@ -9420,7 +9469,7 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const { if(min_options == 1) { desc += " \n[Exactly 1 of the following options is required]"; } else { - desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]"; + desc += " \n[Exactly " + std::to_string(min_options) + " options from the following list are required]"; } } else if(max_options > 0) { if(min_options > 0) { diff --git a/src/mlpack/bindings/go/tests/test_go_binding_main.cpp b/src/mlpack/bindings/go/tests/test_go_binding_main.cpp index 1e0f1ca930..92738bf2e9 100644 --- a/src/mlpack/bindings/go/tests/test_go_binding_main.cpp +++ b/src/mlpack/bindings/go/tests/test_go_binding_main.cpp @@ -117,11 +117,11 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */) // the 3rd row will be multiplied by two. if (params.Has("matrix_in")) { - arma::mat out = move(params.Get("matrix_in")); + arma::mat out = std::move(params.Get("matrix_in")); out.shed_row(4); out.row(2) *= 2.0; - params.Get("matrix_out") = move(out); + params.Get("matrix_out") = std::move(out); } // Input matrices should be at least 5 rows; the 5th row will be dropped and @@ -129,70 +129,70 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */) if (params.Has("umatrix_in")) { arma::Mat out = - move(params.Get>("umatrix_in")); + std::move(params.Get>("umatrix_in")); out.shed_row(4); out.row(2) *= 2; - params.Get>("umatrix_out") = move(out); + params.Get>("umatrix_out") = std::move(out); } // An input column or row should have all elements multiplied by two. if (params.Has("col_in")) { - arma::vec out = move(params.Get("col_in")); + arma::vec out = std::move(params.Get("col_in")); out *= 2.0; - params.Get("col_out") = move(out); + params.Get("col_out") = std::move(out); } if (params.Has("ucol_in")) { arma::Col out = - move(params.Get>("ucol_in")); + std::move(params.Get>("ucol_in")); out *= 2; - params.Get>("ucol_out") = move(out); + params.Get>("ucol_out") = std::move(out); } if (params.Has("row_in")) { - arma::rowvec out = move(params.Get("row_in")); + arma::rowvec out = std::move(params.Get("row_in")); out *= 2.0; - params.Get("row_out") = move(out); + params.Get("row_out") = std::move(out); } if (params.Has("urow_in")) { arma::Row out = - move(params.Get>("urow_in")); + std::move(params.Get>("urow_in")); out *= 2; - params.Get>("urow_out") = move(out); + params.Get>("urow_out") = std::move(out); } // Vector arguments should have the last element removed. if (params.Has("vector_in")) { - vector out = move(params.Get>("vector_in")); + vector out = std::move(params.Get>("vector_in")); out.pop_back(); - params.Get>("vector_out") = move(out); + params.Get>("vector_out") = std::move(out); } if (params.Has("str_vector_in")) { - vector out = move(params.Get>("str_vector_in")); + vector out = std::move(params.Get>("str_vector_in")); out.pop_back(); - params.Get>("str_vector_out") = move(out); + params.Get>("str_vector_out") = std::move(out); } // All numeric elements should be multiplied by 3. if (params.Has("matrix_and_info_in")) { typedef tuple TupleType; - TupleType tuple = move(params.Get("matrix_and_info_in")); + TupleType tuple = std::move(params.Get("matrix_and_info_in")); const data::DatasetInfo& di = std::get<0>(tuple); arma::mat& m = std::get<1>(tuple); @@ -218,7 +218,7 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */) } } - params.Get("matrix_and_info_out") = move(m); + params.Get("matrix_and_info_out") = std::move(m); } // If we got a request to build a model, then build it. diff --git a/src/mlpack/bindings/julia/generate_jl.cpp.in b/src/mlpack/bindings/julia/generate_jl.cpp.in index 34d93b89f8..ae94440129 100644 --- a/src/mlpack/bindings/julia/generate_jl.cpp.in +++ b/src/mlpack/bindings/julia/generate_jl.cpp.in @@ -3,6 +3,11 @@ * @author Ryan Curtin * * This is a template file to call the PrintJL() function for a given binding. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #define BINDING_TYPE BINDING_TYPE_JL // Disable debug output. diff --git a/src/mlpack/bindings/julia/julia_util.h b/src/mlpack/bindings/julia/julia_util.h index b3e01620b1..3a8bf27413 100644 --- a/src/mlpack/bindings/julia/julia_util.h +++ b/src/mlpack/bindings/julia/julia_util.h @@ -4,6 +4,11 @@ * * Some utility functions in C that can be called from Julia with ccall() in * order to interact with the IO interface. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #ifndef MLPACK_BINDINGS_JULIA_JULIA_UTIL_H #define MLPACK_BINDINGS_JULIA_JULIA_UTIL_H diff --git a/src/mlpack/bindings/julia/tests/runtests.jl b/src/mlpack/bindings/julia/tests/runtests.jl index 189995f944..49517782c2 100644 --- a/src/mlpack/bindings/julia/tests/runtests.jl +++ b/src/mlpack/bindings/julia/tests/runtests.jl @@ -2,6 +2,12 @@ # @author Ryan Curtin # # Tests for the Julia bindings. + +# mlpack is free software; you may redistribute it and/or modify it under the +# terms of the 3-clause BSD license. You should have received a copy of the +# 3-clause BSD license along with mlpack. If not, see +# http://www.opensource.org/licenses/BSD-3-Clause for more information. + using Pkg Pkg.activate(".") using Test diff --git a/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp b/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp index 127e304a5a..98e36074dc 100644 --- a/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp +++ b/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp @@ -119,11 +119,11 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timers */) // the 3rd row will be multiplied by two. if (params.Has("matrix_in")) { - arma::mat out = move(params.Get("matrix_in")); + arma::mat out = std::move(params.Get("matrix_in")); out.shed_row(4); out.row(2) *= 2.0; - params.Get("matrix_out") = move(out); + params.Get("matrix_out") = std::move(out); } // Input matrices should be at least 5 rows; the 5th row will be dropped and @@ -131,70 +131,70 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timers */) if (params.Has("umatrix_in")) { arma::Mat out = - move(params.Get>("umatrix_in")); + std::move(params.Get>("umatrix_in")); out.shed_row(4); out.row(2) *= 2; - params.Get>("umatrix_out") = move(out); + params.Get>("umatrix_out") = std::move(out); } // An input column or row should have all elements multiplied by two. if (params.Has("col_in")) { - arma::vec out = move(params.Get("col_in")); + arma::vec out = std::move(params.Get("col_in")); out *= 2.0; - params.Get("col_out") = move(out); + params.Get("col_out") = std::move(out); } if (params.Has("ucol_in")) { arma::Col out = - move(params.Get>("ucol_in")); + std::move(params.Get>("ucol_in")); out *= 2; - params.Get>("ucol_out") = move(out); + params.Get>("ucol_out") = std::move(out); } if (params.Has("row_in")) { - arma::rowvec out = move(params.Get("row_in")); + arma::rowvec out = std::move(params.Get("row_in")); out *= 2.0; - params.Get("row_out") = move(out); + params.Get("row_out") = std::move(out); } if (params.Has("urow_in")) { arma::Row out = - move(params.Get>("urow_in")); + std::move(params.Get>("urow_in")); out *= 2; - params.Get>("urow_out") = move(out); + params.Get>("urow_out") = std::move(out); } // Vector arguments should have the last element removed. if (params.Has("vector_in")) { - vector out = move(params.Get>("vector_in")); + vector out = std::move(params.Get>("vector_in")); out.pop_back(); - params.Get>("vector_out") = move(out); + params.Get>("vector_out") = std::move(out); } if (params.Has("str_vector_in")) { - vector out = move(params.Get>("str_vector_in")); + vector out = std::move(params.Get>("str_vector_in")); out.pop_back(); - params.Get>("str_vector_out") = move(out); + params.Get>("str_vector_out") = std::move(out); } // All numeric elements should be multiplied by 3. if (params.Has("matrix_and_info_in")) { typedef tuple TupleType; - TupleType tuple = move(params.Get("matrix_and_info_in")); + TupleType tuple = std::move(params.Get("matrix_and_info_in")); const data::DatasetInfo& di = std::get<0>(tuple); arma::mat& m = std::get<1>(tuple); @@ -224,7 +224,7 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timers */) } } - params.Get("matrix_and_info_out") = move(m); + params.Get("matrix_and_info_out") = std::move(m); } // If we got a request to build a model, then build it. diff --git a/src/mlpack/bindings/markdown/generate_markdown.cpp.in b/src/mlpack/bindings/markdown/generate_markdown.cpp.in index 77f9e60541..eb8a343925 100644 --- a/src/mlpack/bindings/markdown/generate_markdown.cpp.in +++ b/src/mlpack/bindings/markdown/generate_markdown.cpp.in @@ -4,6 +4,11 @@ * * This file is configured by CMake to generate all of the Markdown required by * the project. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ #include #include "binding_info.hpp" diff --git a/src/mlpack/bindings/python/mlpack/serialization.pxd b/src/mlpack/bindings/python/mlpack/serialization.pxd index b82c9e73a5..19c8b72e74 100644 --- a/src/mlpack/bindings/python/mlpack/serialization.pxd +++ b/src/mlpack/bindings/python/mlpack/serialization.pxd @@ -4,6 +4,11 @@ serialization.pxd: serialization functions for mlpack classes. This simply makes the utility serialization functions from serialization.hpp available from Python. + +mlpack is free software; you may redistribute it and/or modify it under the +terms of the 3-clause BSD license. You should have received a copy of the +3-clause BSD license along with mlpack. If not, see +http://www.opensource.org/licenses/BSD-3-Clause for more information. """ cimport cython diff --git a/src/mlpack/bindings/python/tests/test_python_binding_main.cpp b/src/mlpack/bindings/python/tests/test_python_binding_main.cpp index ce17cf217e..6ecea2910f 100644 --- a/src/mlpack/bindings/python/tests/test_python_binding_main.cpp +++ b/src/mlpack/bindings/python/tests/test_python_binding_main.cpp @@ -140,11 +140,11 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */) // the 3rd row will be multiplied by two. if (params.Has("matrix_in")) { - arma::mat out = move(params.Get("matrix_in")); + arma::mat out = std::move(params.Get("matrix_in")); out.shed_row(4); out.row(2) *= 2.0; - params.Get("matrix_out") = move(out); + params.Get("matrix_out") = std::move(out); } // Input matrices should be at least 5 rows; the 5th row will be dropped and @@ -152,89 +152,89 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */) if (params.Has("umatrix_in")) { arma::Mat out = - move(params.Get>("umatrix_in")); + std::move(params.Get>("umatrix_in")); out.shed_row(4); out.row(2) *= 2; - params.Get>("umatrix_out") = move(out); + params.Get>("umatrix_out") = std::move(out); } // An input matrix (pandas.Series) should have all elements multiplied by two. if (params.Has("smatrix_in")) { - arma::mat out = move(params.Get("smatrix_in")); + arma::mat out = std::move(params.Get("smatrix_in")); out *= 2.0; - params.Get("smatrix_out") = move(out); + params.Get("smatrix_out") = std::move(out); } // An input matrix (pandas.Series) should have all elements multiplied by two. if (params.Has("s_umatrix_in")) { arma::Mat out = - move(params.Get>("s_umatrix_in")); + std::move(params.Get>("s_umatrix_in")); out *= 2; - params.Get>("s_umatrix_out") = move(out); + params.Get>("s_umatrix_out") = std::move(out); } // An input column or row should have all elements multiplied by two. if (params.Has("col_in")) { - arma::vec out = move(params.Get("col_in")); + arma::vec out = std::move(params.Get("col_in")); out *= 2.0; - params.Get("col_out") = move(out); + params.Get("col_out") = std::move(out); } if (params.Has("ucol_in")) { arma::Col out = - move(params.Get>("ucol_in")); + std::move(params.Get>("ucol_in")); out *= 2; - params.Get>("ucol_out") = move(out); + params.Get>("ucol_out") = std::move(out); } if (params.Has("row_in")) { - arma::rowvec out = move(params.Get("row_in")); + arma::rowvec out = std::move(params.Get("row_in")); out *= 2.0; - params.Get("row_out") = move(out); + params.Get("row_out") = std::move(out); } if (params.Has("urow_in")) { arma::Row out = - move(params.Get>("urow_in")); + std::move(params.Get>("urow_in")); out *= 2; - params.Get>("urow_out") = move(out); + params.Get>("urow_out") = std::move(out); } // Vector arguments should have the last element removed. if (params.Has("vector_in")) { - vector out = move(params.Get>("vector_in")); + vector out = std::move(params.Get>("vector_in")); out.pop_back(); - params.Get>("vector_out") = move(out); + params.Get>("vector_out") = std::move(out); } if (params.Has("str_vector_in")) { - vector out = move(params.Get>("str_vector_in")); + vector out = std::move(params.Get>("str_vector_in")); out.pop_back(); - params.Get>("str_vector_out") = move(out); + params.Get>("str_vector_out") = std::move(out); } // All numeric elements should be multiplied by 3. if (params.Has("matrix_and_info_in")) { typedef tuple TupleType; - TupleType tuple = move(params.Get("matrix_and_info_in")); + TupleType tuple = std::move(params.Get("matrix_and_info_in")); const data::DatasetInfo& di = std::get<0>(tuple); arma::mat& m = std::get<1>(tuple); @@ -260,7 +260,7 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */) } } - params.Get("matrix_and_info_out") = move(m); + params.Get("matrix_and_info_out") = std::move(m); } // If we got a request to build a model, then build it. diff --git a/src/mlpack/core.hpp b/src/mlpack/core.hpp index 44d3eefce3..f016df2f88 100644 --- a/src/mlpack/core.hpp +++ b/src/mlpack/core.hpp @@ -61,9 +61,4 @@ #include #endif -// Use Armadillo's C++ version detection. -#ifdef ARMA_USE_CXX11 - #define MLPACK_USE_CX11 -#endif - #endif diff --git a/src/mlpack/core/data/load_numeric_csv.hpp b/src/mlpack/core/data/load_numeric_csv.hpp index a43bdd02de..6cfbbdd389 100644 --- a/src/mlpack/core/data/load_numeric_csv.hpp +++ b/src/mlpack/core/data/load_numeric_csv.hpp @@ -14,8 +14,31 @@ #include "load_csv.hpp" -namespace mlpack{ -namespace data{ +namespace mlpack { +namespace data { + +/** + * A safe function to get negative or positive infinity, which avoids unary + * minus on an unsigned type. This works around a Visual Studio warning. + */ +template +inline eT SafeNegInf( + const bool neg, + const typename std::enable_if::value>::type* = 0) +{ + // For an unsigned type, we cannot return negative infinity, so instead return + // 0. + return neg ? 0 : std::numeric_limits::infinity(); +} + +template +inline eT SafeNegInf( + const bool neg, + const typename std::enable_if::value>::type* = 0) +{ + return neg ? -(std::numeric_limits::infinity()) : + std::numeric_limits::infinity(); +} template bool LoadCSV::ConvertToken(eT& val, @@ -49,8 +72,7 @@ bool LoadCSV::ConvertToken(eT& val, ((sigB == 'n') || (sigB == 'N')) && ((sigC == 'f') || (sigC == 'F'))) { - val = neg ? -(std::numeric_limits - ::infinity()) : std::numeric_limits::infinity(); + val = SafeNegInf(neg); return true; } else if (((sigA == 'n') || (sigA == 'N')) && diff --git a/src/mlpack/core/dists/discrete_distribution.hpp b/src/mlpack/core/dists/discrete_distribution.hpp index 5905028f0e..984cd63483 100644 --- a/src/mlpack/core/dists/discrete_distribution.hpp +++ b/src/mlpack/core/dists/discrete_distribution.hpp @@ -30,10 +30,6 @@ namespace mlpack { * observation is passed (i.e. observation > numObservations), a crash will * probably occur. * - * This distribution only supports one-dimensional observations, so when - * passing an arma::vec as an observation, it should only have one dimension - * (vec.n_rows == 1). Any additional dimensions will simply be ignored. - * * @note * This class, like every other class in mlpack, uses arma::vec to represent * observations. While a discrete distribution only has positive integers diff --git a/src/mlpack/methods/ann/layer/convolution_impl.hpp b/src/mlpack/methods/ann/layer/convolution_impl.hpp index a4bcb367a6..567cc85f85 100644 --- a/src/mlpack/methods/ann/layer/convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/convolution_impl.hpp @@ -384,8 +384,8 @@ void ConvolutionType< (padWLeft != 0 || padWRight != 0 || padHTop != 0 || padHBottom != 0); // To perform the backward pass, we need to rotate all the filters. - arma::Cube rotatedFilters(weight.n_cols, - weight.n_rows, weight.n_slices); + arma::Cube rotatedFilters(weight.n_rows, + weight.n_cols, weight.n_slices); // To perform the backward pass, we need to dilate all the mappedError. arma::Cube dilatedMappedError; diff --git a/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp b/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp index e40ca6968d..5c946014bb 100644 --- a/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp @@ -402,8 +402,8 @@ void GroupedConvolutionType< (padWLeft != 0 || padWRight != 0 || padHTop != 0 || padHBottom != 0); // To perform the backward pass, we need to rotate all the filters. - arma::Cube rotatedFilters(weight.n_cols, - weight.n_rows, weight.n_slices); + arma::Cube rotatedFilters(weight.n_rows, + weight.n_cols, weight.n_slices); #pragma omp parallel for for (size_t map = 0; map < ((maps * inMaps) / groups); ++map) diff --git a/src/mlpack/methods/dbscan/dbscan_impl.hpp b/src/mlpack/methods/dbscan/dbscan_impl.hpp index a49d63e8bf..bb240ccd5d 100644 --- a/src/mlpack/methods/dbscan/dbscan_impl.hpp +++ b/src/mlpack/methods/dbscan/dbscan_impl.hpp @@ -154,18 +154,68 @@ void DBSCAN::PointwiseCluster( std::vector> neighbors; std::vector> distances; + // Note that the strategy here is somewhat different from the original DBSCAN + // paper. The original DBSCAN paper grows each cluster individually to its + // fullest extent; here, we use a UnionFind structure to grow each point into + // a local cluster (if it has enough points), and we combine with other local + // clusters. The end result is the same. + // + // Define points as being either core points, or non-core points. Core points + // have more than `minPoints` neighbors. Non-core points are included into + // the first core point cluster that encounters them; if they are not included + // by anything, they are labeled as noise. + // + // We maintain a list of non-core points so that we can handle that logic + // correctly. + + std::vector visited(data.n_cols, false); + std::vector nonCorePoints(data.n_cols, false); + for (size_t i = 0; i < data.n_cols; ++i) { if (i % 10000 == 0 && i > 0) Log::Info << "DBSCAN clustering on point " << i << "..." << std::endl; + // Get the next index. + const size_t index = pointSelector.Select(i, data); + visited[index] = true; + // Do the range search for only this point. - rangeSearch.Search(data.col(i), Range(0.0, epsilon), neighbors, + rangeSearch.Search(data.col(index), Range(0.0, epsilon), neighbors, distances); - // Union to all neighbors. - for (size_t j = 0; j < neighbors[0].size(); ++j) - uf.Union(i, neighbors[0][j]); + // Union to all neighbors if the point is not noise. + // + // If the point is noise, we leave its label as undefined (i.e. we do no + // unioning). + if (neighbors[0].size() >= minPoints) + { + for (size_t j = 0; j < neighbors[0].size(); ++j) + { + // Union to all neighbors that either do not have a label, or are core + // points of other clusters. (When we union to another core point, we + // are merging clusters.) + if (uf.Find(neighbors[0][j]) == neighbors[0][j]) + { + // This unions unlabeled points. + uf.Union(index, neighbors[0][j]); + } + else if (!nonCorePoints[neighbors[0][j]] && visited[neighbors[0][j]]) + { + // This unions core points of other clusters. Note that we only union + // with other clusters that have already been visited---this is + // because we do not know whether unvisited points are core or + // non-core points. (If an unvisited point is a core point, it'll + // merge with us later.) + uf.Union(index, neighbors[0][j]); + } + } + } + else + { + // This is not a core point---it does not have enough neighbors. + nonCorePoints[index] = true; + } } } @@ -180,21 +230,43 @@ void DBSCAN::BatchCluster( const MatType& data, UnionFind& uf) { - // For each point, find the points in epsilon-nighborhood and their distances. + // For each point, find the points in epsilon-neighborhood and their distances. std::vector> neighbors; std::vector> distances; Log::Info << "Performing range search." << std::endl; rangeSearch.Train(data); - rangeSearch.Search(data, Range(0.0, epsilon), neighbors, distances); + rangeSearch.Search(Range(0.0, epsilon), neighbors, distances); Log::Info << "Range search complete." << std::endl; + // See the description of the algorithm in `PointwiseCluster()`. The strategy + // is the same here, but we have cached all range search results already. + // That means we already have computed whether each point is or is not a core + // point, just based on the size of its neighbors; so we don't need an + // auxiliary std::vector for that. + // Now loop over all points. for (size_t i = 0; i < data.n_cols; ++i) { // Get the next index. const size_t index = pointSelector.Select(i, data); - for (size_t j = 0; j < neighbors[index].size(); ++j) - uf.Union(index, neighbors[index][j]); + // Monochromatic dual-tree range search does not return the point as its own + // neighbor, so we are looking for `minPoints - 1` instead. + if (neighbors[index].size() >= minPoints - 1) + { + for (size_t j = 0; j < neighbors[index].size(); ++j) + { + if (uf.Find(neighbors[index][j]) == neighbors[index][j]) + { + // This unions unlabeled points. + uf.Union(index, neighbors[index][j]); + } + else if (neighbors[neighbors[index][j]].size() >= (minPoints - 1)) + { + // This unions core points of other clusters. + uf.Union(index, neighbors[index][j]); + } + } + } } } diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index 84a48fbf35..78cd244b15 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -88,7 +88,10 @@ class HMM * the given default distribution for emissions. The dimensionality of the * observations is taken from the emissions variable, so it is important that * the given default emission distribution is set with the correct - * dimensionality. Alternately, set the dimensionality with Dimensionality(). + * dimensionality. Alternately, set the dimensionality with Dimensionality(), + * and then use Emission() to access and set the dimensionality of each + * individual distribution correctly. + * * Optionally, the tolerance for convergence of the Baum-Welch algorithm can * be set. * diff --git a/src/mlpack/methods/preprocess/preprocess.hpp b/src/mlpack/methods/preprocess/preprocess.hpp new file mode 100644 index 0000000000..ba5ceead0e --- /dev/null +++ b/src/mlpack/methods/preprocess/preprocess.hpp @@ -0,0 +1,17 @@ +/** + * @file methods/preprocess/preprocess.hpp + * @author Ryan Curtin + * + * Convenience include for preprocessing utilities. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_PREPROCESSING_PREPROCESSING_HPP +#define MLPACK_METHODS_PREPROCESSING_PREPROCESSING_HPP + +#include "scaling_model.hpp" + +#endif diff --git a/src/mlpack/methods/preprocess/scaling_model_impl.hpp b/src/mlpack/methods/preprocess/scaling_model_impl.hpp index 6da36e6f49..78f4bf5645 100644 --- a/src/mlpack/methods/preprocess/scaling_model_impl.hpp +++ b/src/mlpack/methods/preprocess/scaling_model_impl.hpp @@ -19,9 +19,9 @@ namespace mlpack { namespace data { -ScalingModel::ScalingModel(const int minvalue, - const int maxvalue, - double epsilonvalue) : +inline ScalingModel::ScalingModel(const int minvalue, + const int maxvalue, + double epsilonvalue) : scalerType(0), minmaxscale(NULL), maxabsscale(NULL), @@ -37,7 +37,7 @@ ScalingModel::ScalingModel(const int minvalue, } //! Copy constructor. -ScalingModel::ScalingModel(const ScalingModel& other) : +inline ScalingModel::ScalingModel(const ScalingModel& other) : scalerType(other.scalerType), minmaxscale(other.minmaxscale == NULL ? NULL : new data::MinMaxScaler(*other.minmaxscale)), @@ -59,7 +59,7 @@ ScalingModel::ScalingModel(const ScalingModel& other) : } //! Move constructor. -ScalingModel::ScalingModel(ScalingModel&& other) : +inline ScalingModel::ScalingModel(ScalingModel&& other) : scalerType(other.scalerType), minmaxscale(other.minmaxscale), maxabsscale(other.maxabsscale), @@ -84,7 +84,7 @@ ScalingModel::ScalingModel(ScalingModel&& other) : } //! Copy assignment operator. -ScalingModel& ScalingModel::operator=(const ScalingModel& other) +inline ScalingModel& ScalingModel::operator=(const ScalingModel& other) { if (this == &other) { @@ -124,7 +124,7 @@ ScalingModel& ScalingModel::operator=(const ScalingModel& other) } //! Move assignment operator. -ScalingModel& ScalingModel::operator=(ScalingModel&& other) +inline ScalingModel& ScalingModel::operator=(ScalingModel&& other) { if (this != &other) { @@ -153,7 +153,7 @@ ScalingModel& ScalingModel::operator=(ScalingModel&& other) return *this; } -ScalingModel::~ScalingModel() +inline ScalingModel::~ScalingModel() { delete minmaxscale; delete maxabsscale; diff --git a/src/mlpack/tests/ann/layer/convolution.cpp b/src/mlpack/tests/ann/layer/convolution.cpp index bb40ca8e9d..85e102516c 100644 --- a/src/mlpack/tests/ann/layer/convolution.cpp +++ b/src/mlpack/tests/ann/layer/convolution.cpp @@ -441,3 +441,23 @@ TEST_CASE("AdvancedConvolutionLayerWithStrideTest", "[ANNLayerTest]") layer.Backward(input, output, delta); REQUIRE(arma::accu(delta) == Approx(115.3515701294).epsilon(1e-5)); } + +// Make a simple convolutional layer with non-square filters, and make sure the +// forward and backward and gradient passes all return a result. (This checks +// that we don't have any shape errors.) +TEST_CASE("NonSquareConvolutionTest", "[ANNLayerTest]") +{ + Convolution module1(1, 5, 3); + module1.InputDimensions() = std::vector({ 7, 7 }); + module1.ComputeOutputDimensions(); + arma::mat weights1(module1.WeightSize(), 1); + module1.SetWeights(weights1.memptr()); + + arma::mat data(49, 10, arma::fill::randu); + arma::mat forwardResult(module1.OutputSize(), 10, arma::fill::zeros); + REQUIRE_NOTHROW(module1.Forward(data, forwardResult)); + arma::mat backwardResult(49, 10); + REQUIRE_NOTHROW(module1.Backward(data, forwardResult, backwardResult)); + arma::mat gradientResult(module1.WeightSize(), 1); + REQUIRE_NOTHROW(module1.Gradient(data, backwardResult, gradientResult)); +} diff --git a/src/mlpack/tests/ann/layer/grouped_convolution.cpp b/src/mlpack/tests/ann/layer/grouped_convolution.cpp index 09e7e4dc4b..391b520c06 100644 --- a/src/mlpack/tests/ann/layer/grouped_convolution.cpp +++ b/src/mlpack/tests/ann/layer/grouped_convolution.cpp @@ -202,3 +202,23 @@ TEST_CASE("GradientGroupedConvolutionLayerTest", "[ANNLayerTest]") REQUIRE(CheckGradient(function) < 1e-1); } + +// Make a simple grouped convolutional layer with non-square filters, and make +// sure the forward and backward and gradient passes all return a result. (This +// checks that we don't have any shape errors.) +TEST_CASE("NonSquareGroupedConvolutionTest", "[ANNLayerTest]") +{ + GroupedConvolution module1(1, 5, 3, 1); + module1.InputDimensions() = std::vector({ 7, 7 }); + module1.ComputeOutputDimensions(); + arma::mat weights1(module1.WeightSize(), 1); + module1.SetWeights(weights1.memptr()); + + arma::mat data(49, 10, arma::fill::randu); + arma::mat forwardResult(module1.OutputSize(), 10, arma::fill::zeros); + REQUIRE_NOTHROW(module1.Forward(data, forwardResult)); + arma::mat backwardResult(49, 10); + REQUIRE_NOTHROW(module1.Backward(data, forwardResult, backwardResult)); + arma::mat gradientResult(module1.WeightSize(), 1); + REQUIRE_NOTHROW(module1.Gradient(data, backwardResult, gradientResult)); +} diff --git a/src/mlpack/tests/dbscan_test.cpp b/src/mlpack/tests/dbscan_test.cpp index a59f4a47f3..a775c1cf6f 100644 --- a/src/mlpack/tests/dbscan_test.cpp +++ b/src/mlpack/tests/dbscan_test.cpp @@ -302,3 +302,36 @@ TEST_CASE("RandomPointSelectionTest", "[DBSCANTest]") // The number of assignments returned should be the same as points. REQUIRE(assignments.n_elem == points.n_cols); } + +/** + * Check that noise points do not accidentally connect clusters. + * See issue #3339. (Thanks @iad-ABDUL-RAOUF!) + */ +TEST_CASE("NoiseConnectionTest", "[DBSCANTest]") +{ + arma::mat dataset({ + // cluster 1 cluster 2 noise + { 0.0, 0.5, 0.5, 1.0, 3.0, 3.5, 3.5, 4.0, 2.0 }, + { 0.0, 0.5, -0.5, 0.0, 0.0, 0.5, -0.5, 0.0, 0.0 }}); + + // Now perform clustering. + const double epsilon = 1.1; + size_t minPts = 4; + + DBSCAN<> dbscan(epsilon, minPts, false); + + arma::Row labels; + arma::mat centroids; + + size_t numClusters = dbscan.Cluster(dataset, labels, centroids); + + // The noisy element should not link the two clusters together, since it has + // less than minPts neighbors. + REQUIRE(numClusters == 2); + + // Now make sure the same is true with batch clustering. + dbscan = DBSCAN<>(epsilon, minPts, true); + numClusters = dbscan.Cluster(dataset, labels, centroids); + + REQUIRE(numClusters == 2); +} diff --git a/src/mlpack/tests/io_test.cpp b/src/mlpack/tests/io_test.cpp index 21f69829ef..617b713055 100644 --- a/src/mlpack/tests/io_test.cpp +++ b/src/mlpack/tests/io_test.cpp @@ -1100,8 +1100,8 @@ TEST_CASE("MatrixAndDatasetInfoTest", "[IOTest]") "MatrixAndDatasetInfoTest"); // Get the dataset and info. - DatasetInfo info = move(get<0>(p.Get("dataset"))); - arma::mat dataset = move(get<1>(p.Get("dataset"))); + DatasetInfo info = std::move(get<0>(p.Get("dataset"))); + arma::mat dataset = std::move(get<1>(p.Get("dataset"))); REQUIRE(info.Dimensionality() == 3); diff --git a/src/mlpack/tests/main_tests/det_test.cpp b/src/mlpack/tests/main_tests/det_test.cpp index 8ed8a9589e..69eb75d685 100644 --- a/src/mlpack/tests/main_tests/det_test.cpp +++ b/src/mlpack/tests/main_tests/det_test.cpp @@ -86,7 +86,7 @@ TEST_CASE_METHOD(DETTestFixture, "DETParamBoundTest", // Test for folds. - SetInputParam("training", move(trainingData)); + SetInputParam("training", std::move(trainingData)); SetInputParam("folds", (int) -1); REQUIRE_THROWS_AS(RUN_BINDING(), std::runtime_error); diff --git a/src/mlpack/tests/main_tests/range_search_test.cpp b/src/mlpack/tests/main_tests/range_search_test.cpp index 0a389c5758..1cd1b80e38 100644 --- a/src/mlpack/tests/main_tests/range_search_test.cpp +++ b/src/mlpack/tests/main_tests/range_search_test.cpp @@ -57,7 +57,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RangeSearchInputModelNoQuery", if (!data::Load("iris.csv", inputData)) FAIL("Unable to load dataset iris.csv!"); - SetInputParam("reference", move(inputData)); + SetInputParam("reference", std::move(inputData)); SetInputParam("min", minVal); SetInputParam("max", maxVal); SetInputParam("distances_file", distanceFile); @@ -96,7 +96,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RangeSearchDifferentTree", if (!data::Load("iris.csv", inputData)) FAIL("Unable to load dataset iris.csv!"); - SetInputParam("reference", move(inputData)); + SetInputParam("reference", std::move(inputData)); SetInputParam("min", minVal); SetInputParam("max", maxVal); SetInputParam("distances_file", distanceFile); @@ -125,7 +125,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RangeSearchBothReferenceAndModel", if (!data::Load("iris_test.csv", queryData)) FAIL("Unable to load dataset iris_test.csv!"); - SetInputParam("reference", move(inputData)); + SetInputParam("reference", std::move(inputData)); SetInputParam("min", minVal); SetInputParam("max", maxVal); SetInputParam("distances_file", distanceFile); @@ -134,8 +134,8 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RangeSearchBothReferenceAndModel", RUN_BINDING(); - SetInputParam("input_model", move(params.Get("output_model"))); - SetInputParam("query", move(queryData)); + SetInputParam("input_model", std::move(params.Get("output_model"))); + SetInputParam("query", std::move(queryData)); REQUIRE_THROWS_AS(RUN_BINDING(), std::runtime_error); @@ -174,7 +174,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RangeSearchTest", vector> neighbors; vector> distances; - SetInputParam("reference", move(x)); + SetInputParam("reference", std::move(x)); SetInputParam("min", minVal); SetInputParam("max", maxVal); SetInputParam("distances_file", distanceFile); @@ -219,7 +219,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RangeSeachTestwithQuery", double minVal = 0, maxVal = 5; SetInputParam("query", queryData); - SetInputParam("reference", move(x)); + SetInputParam("reference", std::move(x)); SetInputParam("min", minVal); SetInputParam("max", maxVal); SetInputParam("distances_file", distanceFile); @@ -256,7 +256,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "ModelCheck", if (!data::Load("iris_test.csv", queryData)) FAIL("Unable to load dataset iris_test.csv!"); - SetInputParam("reference", move(inputData)); + SetInputParam("reference", std::move(inputData)); SetInputParam("min", minVal); SetInputParam("max", maxVal); SetInputParam("distances_file", distanceFile); @@ -275,7 +275,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "ModelCheck", ResetSettings(); SetInputParam("input_model", outputModel); - SetInputParam("query", move(queryData)); + SetInputParam("query", std::move(queryData)); RUN_BINDING(); @@ -455,7 +455,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "RandomBasisTesting", RUN_BINDING(); - RSModel* outputModel = move(params.Get("output_model")); + RSModel* outputModel = std::move(params.Get("output_model")); SetInputParam("min", minVal); SetInputParam("max", maxVal); @@ -504,7 +504,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "NaiveModeTest", neighbors = ReadData(neighborsFile); distances = ReadData(distanceFile); - RSModel* outputModel = move(params.Get("output_model")); + RSModel* outputModel = std::move(params.Get("output_model")); SetInputParam("min", minVal); SetInputParam("max", maxVal); @@ -559,7 +559,7 @@ TEST_CASE_METHOD(RangeSearchTestFixture, "SingleModeTest", neighbors = ReadData(neighborsFile); distances = ReadData(distanceFile); - RSModel* outputModel = move(params.Get("output_model")); + RSModel* outputModel = std::move(params.Get("output_model")); SetInputParam("min", minVal); SetInputParam("max", maxVal);