diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 722a597468..0000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,219 +0,0 @@ -clone_depth: 10 - -environment: - BOOST_MATH : "C:/projects/mlpack/\ - boost_math_c99-vc140.1.60.0.0/lib/native/address-model-64/lib/*.*" - BOOST_RANDOM : "C:/projects/mlpack/\ - boost_random-vc140.1.60.0.0/lib/native/address-model-64/lib/*.*" - ARMADILLO_DOWNLOAD : "https://data.kurg.org/armadillo-8.400.0.tar.xz" - ARMADILLO_LIBRARY : "C:/projects/mlpack/armadillo-8.400.0/\ - build/Debug/armadillo.lib" - BLAS_LIBRARY : "%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/\ - libopenblas.dll.a" - BOOST_INCLUDE : "C:/projects/mlpack/boost.1.60.0.0/lib/native/include" - JENKINS_DOC_DOWNLOAD : "http://ci.mlpack.org/job/mlpack%20-%20doxygen%20\ - build/lastSuccessfulBuild/artifact/build/doc/html/*zip*/html.zip" - JENKINS_DOC : "C:/projects/mlpack/dist/win-installer/jenkinsdoc.zip" - GIT_VERSION_FILE : "C:/projects/mlpack/src/mlpack/core/util/gitversion.hpp" - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - VSVER: Visual Studio 16 2019 - MSBUILD: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe - -# We have removed the VS studio 15 2017 build since it is not possible to complete -# or finish the build due to the `compiler out of heap space issues`. -# Therefore, in the meanwhile, we are only doing the installation for VS 16 2019. - -configuration: Release - -os: Visual Studio 2019 - -install: - - ps: nuget install boost -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0 - - ps: > - nuget install boost_random-vc140 - -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0 - - ps: > - nuget install boost_math_c99-vc140 - -o "${env:APPVEYOR_BUILD_FOLDER}" -Version 1.60.0 - - ps: > - nuget install unofficial-flayan-cereal - -o "${env:APPVEYOR_BUILD_FOLDER}" - - ps: nuget install OpenBLAS -o "${env:APPVEYOR_BUILD_FOLDER}" - - set path=C:\Program Files (x86)\WiX Toolset v3.11\bin;%path% - -build_script: - - mkdir boost_libs - - ps: cp ${env:BOOST_MATH} C:\projects\mlpack\boost_libs\ - - ps: cp ${env:BOOST_RANDOM} C:\projects\mlpack\boost_libs\ - - echo TEST_ARMA is %ARMADILLO_DOWNLOAD% - - > - appveyor DownloadFile %ARMADILLO_DOWNLOAD% - -FileName armadillo.tar.xz - - 7z x armadillo.tar.xz -so -txz | 7z x -si -ttar > nul - - cd armadillo-8.400.0 && mkdir build && cd build - - > - cmake -G "%VSVER%" - -DBLAS_LIBRARY:FILEPATH=%BLAS_LIBRARY% - -DLAPACK_LIBRARY:FILEPATH=%BLAS_LIBRARY% - -DCMAKE_PREFIX:FILEPATH="%APPVEYOR_BUILD_FOLDER%/armadillo" - -DBUILD_SHARED_LIBS=OFF - -DCMAKE_BUILD_TYPE=Release .. - - > - "%MSBUILD%" "C:\projects\mlpack\armadillo-8.400.0\build\armadillo.sln" - /m /verbosity:quiet /p:Configuration=Release;Platform=x64 - - cd C:\projects\mlpack && mkdir build && cd build - - > - cmake -G "%VSVER%" - -DBLAS_LIBRARIES:FILEPATH=%BLAS_LIBRARY% - -DLAPACK_LIBRARIES:FILEPATH=%BLAS_LIBRARY% - -DARMADILLO_INCLUDE_DIR="C:/projects/mlpack/armadillo-8.400.0/include" - -DARMADILLO_LIBRARY:FILEPATH=%ARMADILLO_LIBRARY% - -DCEREAL_INCLUDE_DIR="C:/projects/mlpack/unofficial-flayan-cereal.1.2.2/build/native/include" - -DBOOST_INCLUDEDIR:PATH=%BOOST_INCLUDE% - -DDEBUG=OFF - -DPROFILE=OFF - -DBUILD_PYTHON_BINDINGS=OFF - -DBUILD_GO_BINDINGS=OFF - -DBUILD_R_BINDINGS=OFF - -DBUILD_TESTS=OFF - -DCMAKE_BUILD_TYPE=Release .. - - > - "%MSBUILD%" "C:\projects\mlpack\build\mlpack.sln" - /m /verbosity:minimal /nologo /p:BuildInParallel=true - /p:Configuration=Release;Platform=x64 - - # Zip Artifacts. - - > - 7z a mlpack-windows-no-libs.zip - "%APPVEYOR_BUILD_FOLDER%\build\Release\*.exe" - - > - 7z a mlpack-windows.zip - "%APPVEYOR_BUILD_FOLDER%\build\Release\*.*" - "%APPVEYOR_BUILD_FOLDER%/OpenBLAS.0.2.14.1/lib/native/lib/x64/*.*" - - # Pulling documentation for the installer. - - ps: > - try{(new-object net.webclient).DownloadFile(${env:JENKINS_DOC_DOWNLOAD}, - 'C:\projects\mlpack\dist\win-installer\jenkinsdoc.zip')} - catch{Write-Output "Unable to pull jenkins doc, skipping!"} - - ps: > - try{(Add-Type -AssemblyName System.IO.Compression.FileSystem); - [System.IO.Compression.ZipFile]::ExtractToDirectory(${env:JENKINS_DOC}, - 'C:\projects\mlpack\dist\win-installer\staging\doc')} - catch{Write-Output "Unable to add doc to installer, skipping!"} - - # Preparing installer staging. - - cd C:\projects\mlpack\dist\win-installer\staging && mkdir lib - - ps: > - cp C:\projects\mlpack\build\Release\*.lib - C:\projects\mlpack\dist\win-installer\staging\lib\ - - ps: > - cp C:\projects\mlpack\build\Release\*.exp - C:\projects\mlpack\dist\win-installer\staging\lib\ - - ps: > - cp C:\projects\mlpack\build\Release\*.dll - C:\projects\mlpack\dist\win-installer\staging\ - - ps: > - cp C:\projects\mlpack\build\Release\*.exe - C:\projects\mlpack\dist\win-installer\staging\ - - ps: > - cp C:\projects\mlpack\OpenBLAS.0.2.14.1\lib\native\bin\x64\*.dll - C:\projects\mlpack\dist\win-installer\staging\ - - ps: > - cp C:\projects\mlpack\build\include\mlpack - C:\projects\mlpack\dist\win-installer\staging -recurse - - ps: > - cp C:\projects\mlpack\doc\examples - C:\projects\mlpack\dist\win-installer\staging -recurse - - ps: > - cp C:\projects\mlpack\src\mlpack\tests\data\german.csv - C:\projects\mlpack\dist\win-installer\staging\examples\sample-ml-app\sample-ml-app\data\ - - # Checking current gitversion or mlpack version. - - ps: > - $ver = (Get-Content - "${env:APPVEYOR_BUILD_FOLDER}\src\mlpack\core\util\version.hpp" | - where {$_ -like "*MLPACK_VERSION*"}); - $env:MLPACK_VERSION += $ver[0].substring($ver[0].length - 1, 1) + '.'; - $env:MLPACK_VERSION += $ver[1].substring($ver[1].length - 1, 1) + '.'; - $env:MLPACK_VERSION += $ver[2].substring($ver[2].length - 1, 1); - - if (Test-Path ${env:GIT_VERSION_FILE}) - { - $ver = (Get-Content ${env:GIT_VERSION_FILE}); - $env:INSTALL_VERSION = $ver.Split('"')[1].Split(' ')[1]; - } - else - { - $env:INSTALL_VERSION = $env:MLPACK_VERSION; - } - - echo INSTALL_VERSION is %INSTALL_VERSION% - - # Building MSI installer. - - cd C:\projects\mlpack\dist\win-installer\mlpack-win-installer - - > - heat dir ..\staging - -cg HeatGenerated - -dr INSTALLFOLDER - -sreg - -srd - -var var.HarvestPath - -ag - -sfrag - -out HeatGeneratedFileList.wxs - - > - candle -dHarvestPath=..\staging - -dConfiguration=Release - -dOutDir=bin\x64\Release\ - -dPlatform=x64 - -dProjectDir=. - -dProjectExt=.wixproj - -dProjectFileName=mlpack-win-installer.wixproj - -dProjectName=mlpack-win-installer - -dProjectPath=mlpack-win-installer.wixproj - -dTargetDir=.\bin\x64\Release\ - -dTargetExt=.msi - -dTargetFileName=mlpack-windows.msi - -dTargetName=mlpack-windows - -dTargetPath=.\bin\x64\Release\mlpack-windows.msi - -out obj\x64\Release\ - -arch x64 - -ext "C:\Program Files (x86)\WiX Toolset v3.11\bin\\WixUIExtension.dll" - Product.wxs HeatGeneratedFileList.wxs - - > - light -out .\bin\x64\Release\mlpack-%INSTALL_VERSION%.msi - -pdbout .\bin\x64\Release\mlpack-windows.wixpdb - -cultures:null - -loc mlpack-localization.wxl - -ext "C:\Program Files (x86)\WiX Toolset v3.11\bin\\WixUIExtension.dll" - -contentsfile - obj\x64\Release\mlpack-win-installer.wixproj.BindContentsFileListnull.txt - -outputsfile - obj\x64\Release\mlpack-win-installer.wixproj.BindOutputsFileListnull.txt - -builtoutputsfile - obj\x64\Release\mlpack-win-installer.wixproj.BindBuiltOutputsFileListnull.txt - -wixprojectfile - mlpack-win-installer.wixproj - obj\x64\Release\Product.wixobj - obj\x64\Release\HeatGeneratedFileList.wixobj - -artifacts: - - path: 'build\*.zip' - name: mlpack-windows-zip - - - path: 'dist\win-installer\mlpack-win-installer\bin\x64\Release\*.msi' - name: mlpack-windows-installer - -notifications: -- provider: Email - to: - - mlpack-git@lists.mlpack.org - on_build_success: true - on_build_failure: true - on_build_status_changed: true - -cache: - - packages -> **\packages.config - - armadillo.tar.xz -> appveyor.yaml - diff --git a/.ci/linux-steps.yaml b/.ci/linux-steps.yaml index 21baace148..f695c14fe1 100644 --- a/.ci/linux-steps.yaml +++ b/.ci/linux-steps.yaml @@ -21,7 +21,7 @@ steps: unset BOOST_ROOT echo "##vso[task.setvariable variable=BOOST_ROOT]"$BOOST_ROOT - sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost1.70-dev libarmadillo-dev xz-utils + sudo apt-get install -y --allow-unauthenticated libopenblas-dev g++ libboost1.70-dev xz-utils if [ "$(binding)" == "python" ]; then export PYBIN=$(which python) diff --git a/.ci/macos-steps.yaml b/.ci/macos-steps.yaml index c437344050..ce3c7796f5 100644 --- a/.ci/macos-steps.yaml +++ b/.ci/macos-steps.yaml @@ -14,7 +14,7 @@ steps: set -e sudo xcode-select --switch /Applications/Xcode_12.2.app/Contents/Developer unset BOOST_ROOT - brew install openblas armadillo boost cereal + brew install libomp openblas armadillo boost cereal if [ "$(binding)" == "python" ]; then pip install --upgrade pip @@ -22,7 +22,7 @@ steps: fi if [ "a$(julia.version)" != "a" ]; then - brew cask install julia + brew install --cask julia fi git clone --depth 1 https://github.com/mlpack/jenkins-conf.git conf @@ -65,4 +65,4 @@ steps: inputs: pathtoPublish: 'build/Testing/' artifactName: 'Tests' - displayName: 'Publish artifacts test results' + displayName: 'Publish artifacts test results' \ No newline at end of file diff --git a/CMake/FindMatlabMex.cmake b/CMake/FindMatlabMex.cmake deleted file mode 100644 index 43b342c4a5..0000000000 --- a/CMake/FindMatlabMex.cmake +++ /dev/null @@ -1,110 +0,0 @@ -# This module looks for mex, the MATLAB compiler. -# The following variables are defined when the script completes: -# MATLAB_MEX: location of mex compiler -# MATLAB_ROOT: root of MATLAB installation -# MATLABMEX_FOUND: 0 if not found, 1 if found - -set(MATLABMEX_FOUND 0) - -if(WIN32) - # This is untested but taken from the older FindMatlab.cmake script as well as - # the modifications by Ramon Casero and Tom Doel for Gerardus. - - # Search for a version of Matlab available, starting from the most modern one - # to older versions. - foreach(MATVER "7.20" "7.19" "7.18" "7.17" "7.16" "7.15" "7.14" "7.13" "7.12" -"7.11" "7.10" "7.9" "7.8" "7.7" "7.6" "7.5" "7.4") - if((NOT DEFINED MATLAB_ROOT) - OR ("${MATLAB_ROOT}" STREQUAL "") - OR ("${MATLAB_ROOT}" STREQUAL "/registry")) - get_filename_component(MATLAB_ROOT - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\MathWorks\\MATLAB\\${MATVER};MATLABROOT]" - ABSOLUTE) - set(MATLAB_VERSION ${MATVER}) - endif() - OR ("${MATLAB_ROOT}" STREQUAL "") - OR ("${MATLAB_ROOT}" STREQUAL "/registry")) - endforeach() - - find_program(MATLAB_MEX - mex - ${MATLAB_ROOT}/bin - ) -else() - # Check if this is a Mac. - if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - # This code is untested but taken from the older FindMatlab.cmake script as - # well as the modifications by Ramon Casero and Tom Doel for Gerardus. - - set(LIBRARY_EXTENSION .dylib) - - # If this is a Mac and the attempts to find MATLAB_ROOT have so far failed,~ - # we look in the applications folder - if((NOT DEFINED MATLAB_ROOT) OR ("${MATLAB_ROOT}" STREQUAL "")) - - # Search for a version of Matlab available, starting from the most modern - # one to older versions - foreach(MATVER "R2013b" "R2013a" "R2012b" "R2012a" "R2011b" "R2011a" -"R2010b" "R2010a" "R2009b" "R2009a" "R2008b") - if((NOT DEFINED MATLAB_ROOT) OR ("${MATLAB_ROOT}" STREQUAL "")) - if(EXISTS /Applications/MATLAB_${MATVER}.app) - set(MATLAB_ROOT /Applications/MATLAB_${MATVER}.app) - - endif() - endif() - endforeach() - - endif() - - find_program(MATLAB_MEX - mex - PATHS - ${MATLAB_ROOT}/bin - ) - - else() - # On a Linux system. The goal is to find MATLAB_ROOT. - set(LIBRARY_EXTENSION .so) - - find_program(MATLAB_MEX_POSSIBLE_LINK - mex - PATHS - ${MATLAB_ROOT}/bin - /opt/matlab/bin - /usr/local/matlab/bin - $ENV{HOME}/matlab/bin - # Now all the versions - /opt/matlab/[rR]20[0-9][0-9][abAB]/bin - /usr/local/matlab/[rR]20[0-9][0-9][abAB]/bin - /opt/matlab-[rR]20[0-9][0-9][abAB]/bin - /opt/matlab_[rR]20[0-9][0-9][abAB]/bin - /usr/local/matlab-[rR]20[0-9][0-9][abAB]/bin - /usr/local/matlab_[rR]20[0-9][0-9][abAB]/bin - $ENV{HOME}/matlab/[rR]20[0-9][0-9][abAB]/bin - $ENV{HOME}/matlab-[rR]20[0-9][0-9][abAB]/bin - $ENV{HOME}/matlab_[rR]20[0-9][0-9][abAB]/bin - ) - - get_filename_component(MATLAB_MEX "${MATLAB_MEX_POSSIBLE_LINK}" REALPATH) - get_filename_component(MATLAB_BIN_ROOT "${MATLAB_MEX}" PATH) - # Strip ./bin/. - get_filename_component(MATLAB_ROOT "${MATLAB_BIN_ROOT}" PATH) - endif() -endif() - -if(NOT EXISTS "${MATLAB_MEX}" AND "${MatlabMex_FIND_REQUIRED}") - message(FATAL_ERROR "Could not find MATLAB mex compiler; try specifying MATLAB_ROOT.") -else() - if(EXISTS "${MATLAB_MEX}") - message(STATUS "Found MATLAB mex compiler: ${MATLAB_MEX}") - message(STATUS "MATLAB root: ${MATLAB_ROOT}") - set(MATLABMEX_FOUND 1) - endif() -endif() - -mark_as_advanced( - MATLAB_MEX - MATLABMEX_FOUND - MATLAB_ROOT -) - diff --git a/CMake/mlpack_coverage.in b/CMake/mlpack_coverage.in deleted file mode 100755 index b67ecf3cc1..0000000000 --- a/CMake/mlpack_coverage.in +++ /dev/null @@ -1,135 +0,0 @@ -#!/bin/bash -# This script gets the test coverage for mlpack_test. -test_case="ALL" -gcov_loc="" -token="" -clean=true -current_log_file=`date +'%Y.%h.%d:%H:%M:%S-coverage.log'` -current_coverage_file=`date +'%Y.%h.%d:%H:%M:%S-coverage.info'` -max_cov_count=50000 - -# default directories -root_dir="../" - -# Extract arguments. -for i in "$@" -do -case $i in - -h|--help) - echo "Usage: mlpack_coverage --help|-h" - echo " mlpack_coverage [-r=test_suite] [-g=gcov_tool_location]" - echo " [--token=coveralls_token]" - echo "Optional parameters:" - echo " -n|--no_test Do not run test before coverage computation" - echo " -r|--run_test Run tests with specific test suite" - echo " --no_clean Do not remove existing gcda file" - echo " -g|--gcov_tool_location Gcov location if not default" - echo " -t|--token Upload to coveralls with given token" - echo " --max_cov_count Max line coverage count (default 50000)" - echo " --root_dir Set the root directory from which gcov will be called. (default ../)" - exit 0 - shift - ;; - -n|--no_test) - test_case="" - shift - ;; - -r=*|--run_test=*) - test_case="${i#*=}" - shift # past argument=value - ;; - --no_clean) - clean=false - shift - ;; - -g=*|--gcov_tool_location=*) - gcov_loc="${i#*=}" - shift # past argument=value - ;; - -t=*|--token=*) - token="${i#*=}" - shift # past argument=value - ;; - --max_cov_count) - max_cov_count="${i#*=}" - shift - ;; - --root_dir=*) - root_dir="${i#*=}" - shift - ;; - *) - # unknown option - ;; -esac -done - -if [ "$clean" = true ]; then - echo "Deleting existing coverage data..." - find ./ -name "*.gcda" -type f -delete -fi - -# Initial pass. -echo "Generating primary coverage report." -[[ -d ./coveragehistory/ ]] || mkdir coveragehistory -lcov -b . -c -i -d ./ -o .coverage.wtest.base > ./coveragehistory/$current_log_file - -# Run the tests. -if [ "$test_case" = "ALL" ]; then - echo "Running all the tests..." - "@CMAKE_BINARY_DIR@"/bin/mlpack_test -elif ! [ "$test_case" = "" ]; then - echo "Running test suite: $test_case" - "@CMAKE_BINARY_DIR@"/bin/mlpack_test --run_test=$test_case -fi - -# Generate coverage based on executed tests. -echo "Computing coverage..." -if [ "$gcov_loc" = "" ]; -then lcov -b . -c -d ./ -o .coverage.wtest.run >> ./coveragehistory/$current_log_file -else - lcov -b . -c -d ./ -o .coverage.wtest.run --gcov-tool=$gcov_loc >> ./coveragehistory/$current_log_file -fi - -echo "Filtering coverage files..." -# Clear negative entries in coverage file -sed -E 's/-([0-9]+)/$max_cov_count/g' -i .coverage.wtest.run -# Merge coverage tracefiles. -lcov -a .coverage.wtest.base -a .coverage.wtest.run -o .coverage.total >> ./coveragehistory/$current_log_file - -# Filtering, extracting project files. -lcov -e .coverage.total "@CMAKE_CURRENT_SOURCE_DIR@/src/mlpack/*" -o .coverage.total.filtered >> ./coveragehistory/$current_log_file - -# Filtering, removing test-files and main.cpp. -lcov -r .coverage.total.filtered "@CMAKE_CURRENT_SOURCE_DIR@/src/mlpack/*/*_main.cpp" -o .coverage.total.filtered >> ./coveragehistory/$current_log_file -lcov -r .coverage.total.filtered "@CMAKE_CURRENT_SOURCE_DIR@/src/mlpack/tests/*" -o .coverage.total.filtered >> ./coveragehistory/$current_log_file - -# Remove untestable files. -lcov -r .coverage.total.filtered "@CMAKE_CURRENT_SOURCE_DIR@/src/mlpack/core/util/gitversion.hpp" -o .coverage.total.filtered >> ./coveragehistory/$current_log_file -lcov -r .coverage.total.filtered "@CMAKE_CURRENT_SOURCE_DIR@/src/mlpack/core/util/arma_config.hpp" -o .coverage.total.filtered >> ./coveragehistory/$current_log_file - -# Extra: Replace /build/ with /src/ to unify directories. -cat .coverage.total.filtered > .coverage.total - -# Extra: Clear up previous data, create html folder. -if [[ -d ./coverage/ ]] ; then - rm -rf ./coverage/* -else - mkdir coverage -fi - -# Step 9: Generate webpage. -genhtml -o ./coverage/ .coverage.total - -# Extra: Preserve coverage file in coveragehistory folder. -coverage_file=$current_coverage_file -cp .coverage.total ./coveragehistory/$current_coverage_file - -# Clean temporary coverage files. -#rm .coverage.* - -# Upload the result to coveralls if token is provided. -if ! [ "$token" = "" ]; then - cpp-coveralls -n -r $root_dir -b $root_dir -l ./coveragehistory/$current_coverage_file -t "$token" --max-cov-count $max_cov_count -fi - diff --git a/CMakeLists.txt b/CMakeLists.txt index e0be77df06..73937c2a4b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,6 @@ include(CMake/CheckHash.cmake) option(DEBUG "Compile with debugging information." OFF) option(PROFILE "Compile with profiling information." OFF) option(ARMA_EXTRA_DEBUG "Compile with extra Armadillo debugging symbols." OFF) -option(MATLAB_BINDINGS "Compile MATLAB bindings if MATLAB is found." OFF) option(TEST_VERBOSE "Run test cases with verbose output." OFF) option(BUILD_TESTS "Build tests." ON) option(BUILD_CLI_EXECUTABLES "Build command-line executables." ON) @@ -80,8 +79,6 @@ option(BUILD_R_BINDINGS "Build R bindings." OFF) # generation. option(BUILD_MARKDOWN_BINDINGS "Build Markdown bindings for website documentation." OFF) -option(BUILD_WITH_COVERAGE - "Build with support for code coverage tools (gcc only)." OFF) option(MATHJAX "Use MathJax for HTML Doxygen output (disabled by default)." OFF) option(FORCE_CXX11 @@ -196,38 +193,6 @@ if(CMAKE_COMPILER_IS_GNUCC) ${CMAKE_THREAD_LIBS_INIT}) endif() -# Setup build for test coverage -if(BUILD_WITH_COVERAGE) - # Currently coverage only works with GNU g++. - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - # Find gcov and lcov - find_program(GCOV gcov) - find_program(LCOV lcov) - - if(NOT GCOV) - message(FATAL_ERROR - "gcov not found! gcov is required when BUILD_WITH_COVERAGE=ON.") - endif() - - set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} "supc++") - set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} "quadmath") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -fno-inline -fno-inline-small-functions -fno-default-inline -fprofile-arcs -fkeep-inline-functions") - message(STATUS "Adding debug compile options for code coverage.") - # Remove optimizations for better line coverage - set(DEBUG ON) - - if(LCOV) - configure_file(CMake/mlpack_coverage.in mlpack_coverage @ONLY) - add_custom_target(mlpack_coverage DEPENDS mlpack_test COMMAND ${PROJECT_BINARY_DIR}/mlpack_coverage) - else() - message(WARNING "'lcov' not found; local coverage report is disabled. " - "Install 'lcov' and rerun cmake to generate local coverage report.") - endif() - else() - message(FATAL_ERROR "BUILD_WITH_COVERAGE can only work with GNU environment.") - endif() -endif() - # Debugging CFLAGS. Turn optimizations off; turn debugging symbols on. if(DEBUG) if (NOT MSVC) @@ -467,6 +432,9 @@ if (OPENMP_FOUND) add_definitions(-DHAS_OPENMP) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + if(OpenMP_CXX_FOUND) + set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} ${OpenMP_CXX_LIBRARIES}) + endif () else () # Disable warnings for all the unknown OpenMP pragmas. if (NOT MSVC) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index a3581e1d03..a3c8f90f38 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -7,7 +7,7 @@ Source: Files: * Copyright: - Copyright 2008-2020, Ryan Curtin + Copyright 2008-2021, Ryan Curtin Copyright 2008-2013, Bill March Copyright 2008-2012, Dongryeol Lee Copyright 2008-2013, Nishant Mehta @@ -141,6 +141,7 @@ Copyright: Copyright 2020, Alex Nguyen Copyright 2020, Gaurav Ghati Copyright 2020, Anmolpreet Singh + Copyright 2021, Tru Hoang License: BSD-3-clause All rights reserved. diff --git a/HISTORY.md b/HISTORY.md index c8a3c94497..f0b696d6ac 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ ### mlpack ?.?.? ###### ????-??-?? + * Added Pixel Shuffle layer (#2563). + + * Add "check_input_matrices" option to python bindings that checks + for NaN and inf values in all the input matrices (#2787). + * Add Adjusted R squared functionality to R2Score::Evaluate (#2624). * Disabled all the bindings by default in CMake (#2782). @@ -12,10 +17,26 @@ * Add Triplet Margin Loss function (#2762). * Add finalizers to Julia binding model types to fix memory handling (#2756). - + * HMM: add functions to calculate likelihood for data stream with/without pre-calculated emission probability (#2142). + * Replace Boost serialization library with Cereal (#2458). + + * Add `PYTHON_INSTALL_PREFIX` CMake option to specify installation root for + Python bindings (#2797). + + * Removed `boost::visitor` from model classes for `knn`, `kfn`, `cf`, + `range_search`, `krann`, and `kde` bindings (#2803). + + * Add k-means++ initialization strategy (#2813). + + * `NegativeLogLikelihood<>` now expects classes in the range `0` to + `numClasses - 1` (#2534). + + * Add `Lambda1()`, `Lambda2()`, `UseCholesky()`, and `Tolerance()` members to + `LARS` so parameters for training can be modified (#2861). + ### mlpack 3.4.2 ###### 2020-10-26 * Added Mean Absolute Percentage Error. @@ -36,7 +57,6 @@ ### mlpack 3.4.0 ###### 2020-09-01 - * Issue warnings when metrics produce NaNs in KFoldCV (#2595). * Added bindings for _R_ during Google Summer of Code (#2556). diff --git a/README.md b/README.md index 610e4f6d33..84eb8260b4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ src="https://cdn.rawgit.com/mlpack/mlpack.org/e7d36ed8/mlpack-black.svg" style="

Jenkins - Coveralls License NumFOCUS

@@ -203,6 +202,7 @@ Options are specified with the -D flag. The allowed options include: BUILD_CLI_EXECUTABLES=(ON/OFF): whether or not to build command-line programs BUILD_PYTHON_BINDINGS=(ON/OFF): whether or not to build Python bindings PYTHON_EXECUTABLE=(/path/to/python_version): Path to specific Python executable + PYTHON_INSTALL_PREFIX=(/path/to/python/): Path to root of Python installation BUILD_JULIA_BINDINGS=(ON/OFF): whether or not to build Julia bindings JULIA_EXECUTABLE=(/path/to/julia): Path to specific Julia executable BUILD_GO_BINDINGS=(ON/OFF): whether or not to build Go bindings diff --git a/doc/guide/build.hpp b/doc/guide/build.hpp index a7b5d149ad..9356f725cc 100644 --- a/doc/guide/build.hpp +++ b/doc/guide/build.hpp @@ -175,9 +175,13 @@ The full list of options mlpack allows: (i.e. \c mlpack_knn, \c mlpack_kfn, \c mlpack_logistic_regression, etc.) (default ON) - BUILD_PYTHON_BINDINGS=(ON/OFF): compile the bindings for Python, if the - necessary Python libraries are available (default ON except on Windows) + necessary Python libraries are available (default OFF) + - BUILD_R_BINDINGS=(ON/OFF): compile the bindings for R, if R is found + (default OFF) + - BUILD_GO_BINDINGS=(ON/OFF): compile Go bindings, if Go and the necessary Go + and Gonum exist. (default OFF) - BUILD_JULIA_BINDINGS=(ON/OFF): compile Julia bindings, if Julia is found - (default ON) + (default OFF) - BUILD_SHARED_LIBS=(ON/OFF): compile shared libraries as opposed to static libraries (default ON) - TEST_VERBOSE=(ON/OFF): run test cases in \c mlpack_test with verbose output @@ -187,9 +191,8 @@ The full list of options mlpack allows: - DOWNLOAD_ENSMALLEN=(ON/OFF): If ensmallen is not found, download it (default ON) - DOWNLOAD_STB_IMAGE=(ON/OFF): If STB is not found, download it (default ON) - - BUILD_WITH_COVERAGE=(ON/OFF): Build with support for code coverage tools - (gcc only) (default OFF) - PYTHON_EXECUTABLE=(/path/to/python_version): Path to specific Python executable + - PYTHON_INSTALL_PREFIX=(/path/to/python/): Path to root of Python installation - JULIA_EXECUTABLE=(/path/to/julia): Path to specific Julia executable - BUILD_MARKDOWN_BINDINGS=(ON/OFF): Build Markdown bindings for website documentation (default OFF) diff --git a/doc/tutorials/ann/ann.txt b/doc/tutorials/ann/ann.txt index 7cdb9d1f57..43678fb84e 100644 --- a/doc/tutorials/ann/ann.txt +++ b/doc/tutorials/ann/ann.txt @@ -210,8 +210,9 @@ int main() data::Load("thyroid_test.csv", testData, true); // Split the labels from the training set and testing set respectively. - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); - arma::mat testLabels = testData.row(testData.n_rows - 1); + // Decrement the labels by 1, so they are in the range 0 to (numClasses - 1). + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; + arma::mat testLabels = testData.row(testData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); testData.shed_row(testData.n_rows - 1); @@ -246,9 +247,8 @@ int main() // Find index of max prediction for each data point and store in "prediction" for (size_t i = 0; i < predictionTemp.n_cols; ++i) { - // we add 1 to the max index, so that it matches the actual test labels. prediction(i) = arma::as_scalar(arma::find( - arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)) + 1; + arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)); } /* @@ -311,7 +311,7 @@ void RNNModel() for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.col(i).fill(value); } @@ -589,8 +589,9 @@ arma::mat trainData = dataset.submat(0, 0, dataset.n_rows - 4, dataset.n_cols - 1); // Split the data from the training set. +// Subtract 1 so the labels are the range from 0 to (numClasses - 1). arma::mat trainLabels = dataset.submat(dataset.n_rows - 3, 0, - dataset.n_rows - 1, dataset.n_cols - 1); + dataset.n_rows - 1, dataset.n_cols - 1) - 1; // Initialize the network. FFN<> model; diff --git a/src/mlpack/bindings/R/CMakeLists.txt b/src/mlpack/bindings/R/CMakeLists.txt index 56cdb36c31..73ebe3cd1a 100644 --- a/src/mlpack/bindings/R/CMakeLists.txt +++ b/src/mlpack/bindings/R/CMakeLists.txt @@ -87,6 +87,93 @@ if (BUILD_R_BINDINGS) string(TIMESTAMP PACKAGE_DATE "%Y-%m-%d") + # We need to generate an Authors@R list using every single contributor in + # COPYRIGHT.txt. That takes a little bit of processing. + file(READ "${CMAKE_SOURCE_DIR}/COPYRIGHT.txt" COPYRIGHT_TXT_CONTENTS) + string(REGEX MATCHALL " Copyright [0-9-]*, ([^\n]*)\n" CONTRIBUTORS_LIST + "${COPYRIGHT_TXT_CONTENTS}") + + # These are the authors meant to be listed as 'authors' and not + # 'contributors'. If you contributed specifically to the R bindings, you + # should probably be listed here, so if you're not, open a PR to fix it! :) + set(SPECIAL_AUTHORS "Yashwant Singh Parihar" "Ryan Curtin" "Dirk Eddelbuettel" + "James Balamuta") + + string(CONCAT AUTHORS_R "c(\n" + " person(\"Yashwant\", \"Singh Parihar\", " + "email = \"yashwantsingh.sngh@gmail.com\", " + "role = c(\"aut\", \"ctb\", \"cph\")),\n" + " person(\"Ryan\", \"Curtin\", email = \"ryan@ratml.org\", " + "role = c(\"aut\", \"ctb\", \"cph\", \"cre\")),\n" + " person(\"Dirk\", \"Eddelbuettel\", email = \"edd@debian.org\", " + "role = c(\"aut\", \"ctb\", \"cph\")),\n" + " person(\"James\", \"Balamuta\", " + "email = \"james.balamuta@gmail.com\", " + "role = c(\"aut\", \"ctb\", \"cph\")),") + foreach (CONTRIBUTOR_LINE ${CONTRIBUTORS_LIST}) + # Strip 'Copyright XXXX-YYYY, '. + string(REGEX REPLACE "^ Copyright [0-9-]*, (.*)\n$" "\\1" + CONTRIBUTOR_FILTERED "${CONTRIBUTOR_LINE}") + + # Extract the email if it exists. + string(REGEX MATCH "^[^<]*<(.*)>.*$" HAS_EMAIL "${CONTRIBUTOR_FILTERED}") + + # The first name is just the first space-delimited word. (That may not + # always be right, but we have no way to know what is a first name and last + # name and therefore must assume.) + string(REGEX REPLACE "^([^ ]*) .*$" "\\1" CONTRIBUTOR_FIRST_NAME + "${CONTRIBUTOR_FILTERED}") + + # Extracting the last name is just the rest of the tokens, but the regex is + # different depending on whether we managed to get an email. + if (HAS_EMAIL) + string(REGEX REPLACE "^[^<]*<(.*)>.*$" "\\1" CONTRIBUTOR_EMAIL + "${CONTRIBUTOR_FILTERED}") + string(REGEX MATCH "^[^ ]* (.*) <.*$" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + if (NOT CONTRIBUTOR_LAST_NAME) + set (CONTRIBUTOR_LAST_NAME "") + else () + string(REGEX REPLACE "^[^ ]* (.*) <.*$" "\\1" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + endif () + + # Skip anyone already listed as an author. + if ("${CONTRIBUTOR_FIRST_NAME} ${CONTRIBUTOR_LAST_NAME}" IN_LIST + SPECIAL_AUTHORS) + continue() + endif () + + string(CONCAT AUTHORS_R "${AUTHORS_R}\n " + "person(\"${CONTRIBUTOR_FIRST_NAME}\", \"${CONTRIBUTOR_LAST_NAME}\", " + "email = \"${CONTRIBUTOR_EMAIL}\", role = c(\"ctb\", \"cph\")),") + + else () + # No email is available. So just get the last name. + string(REGEX MATCH "^[^ ]* (.*)$" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + if (NOT CONTRIBUTOR_LAST_NAME) + set (CONTRIBUTOR_LAST_NAME "") + else () + string(REGEX REPLACE "^[^ ]* (.*)$" "\\1" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + endif () + + # Skip anyone already listed as an author. + if ("${CONTRIBUTOR_FIRST_NAME} ${CONTRIBUTOR_LAST_NAME}" IN_LIST + SPECIAL_AUTHORS) + continue() + endif () + + string(CONCAT AUTHORS_R "${AUTHORS_R}\n " + "person(\"${CONTRIBUTOR_FIRST_NAME}\", \"${CONTRIBUTOR_LAST_NAME}\", " + "role = c(\"ctb\", \"cph\")),") + endif () + endforeach () + # We also have to remove the final comma... + string(REGEX REPLACE ",$" "" AUTHORS_R_OUT "${AUTHORS_R}") + set(AUTHORS_R "${AUTHORS_R_OUT})") + configure_file(${CMAKE_SOURCE_DIR}/src/mlpack/bindings/R/mlpack/DESCRIPTION.in ${CMAKE_CURRENT_BINARY_DIR}/mlpack/DESCRIPTION @ONLY) @@ -136,9 +223,11 @@ if (BUILD_R_BINDINGS) "${CMAKE_CURRENT_SOURCE_DIR}/mlpack/tests/testthat.R" ) - set(LICENSE_SOURCES - "${CMAKE_SOURCE_DIR}/LICENSE.txt" - ) + # Configure the license file. + string(TIMESTAMP LICENSE_YEAR "%Y") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mlpack/LICENSE.in" + "${CMAKE_CURRENT_BINARY_DIR}/mlpack/LICENSE") + add_custom_target(r_copy ALL) # First we have to create all the required directories for copy. @@ -160,22 +249,22 @@ if (BUILD_R_BINDINGS) # Copy all necessary files for building package. foreach(cpp_file ${CPP_SOURCES}) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${cpp_file} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/) + add_custom_command(TARGET r_copy PRE_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different + ${cpp_file} + ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/) endforeach() foreach(r_file ${R_SOURCES}) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${r_file} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack/R/) + add_custom_command(TARGET r_copy PRE_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different + ${r_file} + ${CMAKE_CURRENT_BINARY_DIR}/mlpack/R/) endforeach() foreach(bindings_file ${BINDINGS_SOURCES}) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${bindings_file} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/mlpack/bindings/R) + add_custom_command(TARGET r_copy PRE_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different + ${bindings_file} + ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/mlpack/bindings/R) endforeach() add_custom_command(TARGET r_copy PRE_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different @@ -185,14 +274,6 @@ if (BUILD_R_BINDINGS) COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${R_TESTS_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/mlpack/tests) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${LICENSE_SOURCES} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E rename - "${CMAKE_CURRENT_BINARY_DIR}/mlpack/LICENSE.txt" - "${CMAKE_CURRENT_BINARY_DIR}/mlpack/LICENSE") # This file will take care of multiple definition of functions in .cpp files. add_custom_command(TARGET r_copy PRE_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E touch @@ -234,8 +315,8 @@ if (BUILD_R_BINDINGS) # Installation script for the packagae. install(CODE "execute_process( - COMMAND R CMD INSTALL mlpack_${PACKAGE_VERSION}.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}" + COMMAND ${R_EXECUTABLE} CMD INSTALL mlpack_${PACKAGE_VERSION}.tar.gz + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) add_dependencies(R r_build) diff --git a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in index 9183f6bb62..dc583f0dd9 100644 --- a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in +++ b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in @@ -2,11 +2,11 @@ Package: mlpack Title: 'Rcpp' Integration for the 'mlpack' Library Version: @PACKAGE_VERSION@ Date: @PACKAGE_DATE@ -Author: mlpack Team -Maintainer: Ryan Curtin -Description: 'mlpack' is a fast, flexible machine learning library, written - in C++, that aims to provide fast, extensible implementations of - cutting-edge machine learning algorithms. +Authors@R: @AUTHORS_R@ +Description: A fast, flexible machine learning library, written in C++, that + aims to provide fast, extensible implementations of cutting-edge + machine learning algorithms. See also Curtin et al. (2018) + . SystemRequirements: A C++11 compiler. Versions 4.8.*, 4.9.* or later of GCC will be fine. License: BSD_3_clause + file LICENSE diff --git a/src/mlpack/bindings/R/mlpack/LICENSE.in b/src/mlpack/bindings/R/mlpack/LICENSE.in new file mode 100644 index 0000000000..188ac6207d --- /dev/null +++ b/src/mlpack/bindings/R/mlpack/LICENSE.in @@ -0,0 +1,3 @@ +YEAR: ${LICENSE_YEAR} +COPYRIGHT HOLDER: mlpack Team +ORGANIZATION: mlpack diff --git a/src/mlpack/bindings/go/print_type_doc_impl.hpp b/src/mlpack/bindings/go/print_type_doc_impl.hpp index 0755f60b8a..d0a5fef659 100644 --- a/src/mlpack/bindings/go/print_type_doc_impl.hpp +++ b/src/mlpack/bindings/go/print_type_doc_impl.hpp @@ -84,7 +84,7 @@ std::string PrintTypeDoc( */ template std::string PrintTypeDoc( - util::ParamData& data, + util::ParamData& /* data */, const typename std::enable_if::value>::type*) { if (T::is_col || T::is_row) diff --git a/src/mlpack/bindings/markdown/print_docs.cpp b/src/mlpack/bindings/markdown/print_docs.cpp index 45b9807afa..44adf69e93 100644 --- a/src/mlpack/bindings/markdown/print_docs.cpp +++ b/src/mlpack/bindings/markdown/print_docs.cpp @@ -36,8 +36,18 @@ void PrintHeaders(const std::string& bindingName, { BindingInfo::Language() = languages[i]; - cout << " - [" << GetBindingName(bindingName) << "](#" << languages[i] - << "_" << bindingName << "){: .language-link #" << languages[i] << " }" + // Get the name of the binding in the target language, and convert it to + // lowercase (since the anchor link will be in lowercase). + const std::string langBindingName = GetBindingName(bindingName); + std::string anchorName = langBindingName; + std::transform(anchorName.begin(), anchorName.end(), anchorName.begin(), + [](unsigned char c) { return std::tolower(c); }); + // Strip '()' from the end if needed. + if (anchorName.substr(anchorName.size() - 2, 2) == "()") + anchorName = anchorName.substr(0, anchorName.size() - 2); + + cout << " - [" << langBindingName << "](#" << languages[i] + << "_" << anchorName << "){: .language-link #" << languages[i] << " }" << endl; } } diff --git a/src/mlpack/bindings/python/CMakeLists.txt b/src/mlpack/bindings/python/CMakeLists.txt index 65490997c3..c36a026590 100644 --- a/src/mlpack/bindings/python/CMakeLists.txt +++ b/src/mlpack/bindings/python/CMakeLists.txt @@ -214,14 +214,20 @@ add_custom_command(TARGET python POST_BUILD add_dependencies(python python_configured) # Configure installation script file. +if (NOT PYTHON_INSTALL_PREFIX) + set(PYTHON_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") +endif () + execute_process(COMMAND ${PYTHON_EXECUTABLE} - "${CMAKE_CURRENT_SOURCE_DIR}/print_python_version.py" "${CMAKE_INSTALL_PREFIX}" + "${CMAKE_CURRENT_SOURCE_DIR}/print_python_version.py" + "${PYTHON_INSTALL_PREFIX}" OUTPUT_VARIABLE CMAKE_PYTHON_PATH) string(STRIP "${CMAKE_PYTHON_PATH}" CMAKE_PYTHON_PATH) install(CODE "set(ENV{PYTHONPATH} ${CMAKE_PYTHON_PATH})") install(CODE "set(PYTHON_EXECUTABLE \"${PYTHON_EXECUTABLE}\")") install(CODE "set(CMAKE_BINARY_DIR \"${CMAKE_BINARY_DIR}\")") -install(CODE "set(CMAKE_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\")") + +install(CODE "set(PYTHON_INSTALL_PREFIX \"${PYTHON_INSTALL_PREFIX}\")") install(CODE "execute_process(COMMAND mkdir -p $ENV{DESTDIR}${CMAKE_PYTHON_PATH})") install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/PythonInstall.cmake") diff --git a/src/mlpack/bindings/python/PythonInstall.cmake b/src/mlpack/bindings/python/PythonInstall.cmake index 881b48344a..6e25fb926e 100644 --- a/src/mlpack/bindings/python/PythonInstall.cmake +++ b/src/mlpack/bindings/python/PythonInstall.cmake @@ -5,13 +5,13 @@ if (DEFINED ENV{DESTDIR}) execute_process(COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/setup.py" install - --prefix=${CMAKE_INSTALL_PREFIX} --root=$ENV{DESTDIR} + --prefix=${PYTHON_INSTALL_PREFIX} --root=$ENV{DESTDIR} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/" RESULT_VARIABLE setup_res) else () execute_process(COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/setup.py" install - --prefix=${CMAKE_INSTALL_PREFIX} + --prefix=${PYTHON_INSTALL_PREFIX} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/" RESULT_VARIABLE setup_res) endif () diff --git a/src/mlpack/bindings/python/mlpack/io.pxd b/src/mlpack/bindings/python/mlpack/io.pxd index 91d696d011..67961d59c9 100644 --- a/src/mlpack/bindings/python/mlpack/io.pxd +++ b/src/mlpack/bindings/python/mlpack/io.pxd @@ -38,6 +38,9 @@ cdef extern from "" namespace "mlpack" nogil: @staticmethod void ClearSettings() nogil except + + @staticmethod + void CheckInputMatrices() nogil except + + cdef extern from "" \ namespace "mlpack::util" nogil: void SetParam[T](string, T&) nogil except + diff --git a/src/mlpack/bindings/python/print_pyx.cpp b/src/mlpack/bindings/python/print_pyx.cpp index 87a412346b..6853c969da 100644 --- a/src/mlpack/bindings/python/print_pyx.cpp +++ b/src/mlpack/bindings/python/print_pyx.cpp @@ -224,6 +224,16 @@ void PrintPYX(const util::BindingDetails& doc, cout << " IO.SetPassed( '" << d.name << "')" << endl; } + // Checking the type of check_input_matrices parameter. + cout << " if not isinstance(check_input_matrices, bool):" << endl; + cout << " raise TypeError(" <<"\"'check_input_matrices\' must have type " + << "\'bool'!\")" << endl; + cout << endl; + + // Before calling mlpackMain(), we check input matrices for NaN values if needed. + cout << " if check_input_matrices:" << endl; + cout << " IO.CheckInputMatrices()" << endl; + // Call the method. cout << " # Call the mlpack program." << endl; cout << " mlpackMain()" << endl; diff --git a/src/mlpack/bindings/python/py_option.hpp b/src/mlpack/bindings/python/py_option.hpp index 98b9f91844..b3d8519f84 100644 --- a/src/mlpack/bindings/python/py_option.hpp +++ b/src/mlpack/bindings/python/py_option.hpp @@ -64,8 +64,10 @@ class PyOption data.required = required; data.input = input; data.loaded = false; - // Only "verbose" and "copy_all_inputs" will be persistent. - if (identifier == "verbose" || identifier == "copy_all_inputs") + // Only "verbose", "copy_all_inputs" and "check_input_matrices" + // will be persistent. + if (identifier == "verbose" || identifier == "copy_all_inputs" || + identifier == "check_input_matrices") data.persistent = true; else data.persistent = false; diff --git a/src/mlpack/bindings/python/tests/test_python_binding.py b/src/mlpack/bindings/python/tests/test_python_binding.py index 4d8206b16c..dd67aed974 100644 --- a/src/mlpack/bindings/python/tests/test_python_binding.py +++ b/src/mlpack/bindings/python/tests/test_python_binding.py @@ -1336,5 +1336,101 @@ class TestPythonBinding(unittest.TestCase): self.assertEqual(output2['model_bw_out'], 20.0) self.assertEqual(output3['model_bw_out'], 20.0) + def testCheckInputMatricesNaN(self): + """ + Checks that an exception is thrown if the input matrix contains + NaN values. + """ + x = np.random.rand(100, 5) + a = np.random.randint(low=0, high=100) + b = np.random.randint(low=0, high=5) + x[a][b] = np.nan + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_in=x, + check_input_matrices=True)) + + x_vec = np.random.rand(100) + a = np.random.randint(low=0, high=100) + x_vec[a] = np.nan + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + row_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + col_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_and_info_in=x, + check_input_matrices=True)) + + def testCheckInputMatricesInf(self): + """ + Checks that an exception is thrown if the input matrix contains + inf values. + """ + x = np.random.rand(100, 5) + a = np.random.randint(low=0, high=100) + b = np.random.randint(low=0, high=5) + x[a][b] = np.inf + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_in=x, + check_input_matrices=True)) + + x_vec = np.random.rand(100) + a = np.random.randint(low=0, high=100) + x_vec[a] = np.inf + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + row_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + col_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_and_info_in=x, + check_input_matrices=True)) + if __name__ == '__main__': unittest.main() diff --git a/src/mlpack/core.hpp b/src/mlpack/core.hpp index e53eae7e45..d29a174724 100644 --- a/src/mlpack/core.hpp +++ b/src/mlpack/core.hpp @@ -89,6 +89,7 @@ #include #include #include +#include // mlpack::backtrace only for linux #ifdef HAS_BFD_DL diff --git a/src/mlpack/core/cv/cv_base_impl.hpp b/src/mlpack/core/cv/cv_base_impl.hpp index a7017d770a..0da9f8f4fa 100644 --- a/src/mlpack/core/cv/cv_base_impl.hpp +++ b/src/mlpack/core/cv/cv_base_impl.hpp @@ -12,6 +12,8 @@ #ifndef MLPACK_CORE_CV_CV_BASE_IMPL_HPP #define MLPACK_CORE_CV_CV_BASE_IMPL_HPP +#include + namespace mlpack { namespace cv { @@ -106,14 +108,8 @@ void CVBase::AssertDataConsistency(const MatType& xs, const PredictionsType& ys) { - if (xs.n_cols != ys.n_cols) - { - std::ostringstream oss; - oss << "CVBase::AssertDataConsistency(): number of data points (" - << xs.n_cols << ") does not match number of predictions (" << ys.n_cols - << ")!" << std::endl; - throw std::invalid_argument(oss.str()); - } + util::CheckSameSizes(xs, (size_t) ys.n_cols, "CVBase::AssertDataConsistency()", + "predictions"); } template +#include namespace mlpack { namespace cv { diff --git a/src/mlpack/core/cv/metrics/accuracy_impl.hpp b/src/mlpack/core/cv/metrics/accuracy_impl.hpp index 91f3dc1c3e..b6f08332af 100644 --- a/src/mlpack/core/cv/metrics/accuracy_impl.hpp +++ b/src/mlpack/core/cv/metrics/accuracy_impl.hpp @@ -12,8 +12,6 @@ #ifndef MLPACK_CORE_CV_METRICS_ACCURACY_IMPL_HPP #define MLPACK_CORE_CV_METRICS_ACCURACY_IMPL_HPP -#include - namespace mlpack { namespace cv { @@ -22,7 +20,7 @@ double Accuracy::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Accuracy::Evaluate()"); + util::CheckSameSizes(data, labels, "Accuracy::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); diff --git a/src/mlpack/core/cv/metrics/f1_impl.hpp b/src/mlpack/core/cv/metrics/f1_impl.hpp index 6e89754941..5af0558a68 100644 --- a/src/mlpack/core/cv/metrics/f1_impl.hpp +++ b/src/mlpack/core/cv/metrics/f1_impl.hpp @@ -13,7 +13,6 @@ #define MLPACK_CORE_CV_METRICS_F1_IMPL_HPP #include -#include namespace mlpack { namespace cv { @@ -33,7 +32,7 @@ double F1::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "F1::Evaluate()"); + util::CheckSameSizes(data, labels, "F1::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); @@ -56,7 +55,7 @@ double F1::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "F1::Evaluate()"); + util::CheckSameSizes(data, labels, "F1::Evaluate()"); // Microaveraged F1 is really the same as microaveraged precision and // microaveraged recall, which are in turn the same as accuracy. @@ -70,7 +69,7 @@ double F1::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "F1::Evaluate()"); + util::CheckSameSizes(data, labels, "F1::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); diff --git a/src/mlpack/core/cv/metrics/facilities.hpp b/src/mlpack/core/cv/metrics/facilities.hpp index c7b9b4b70e..fdd5b1216a 100644 --- a/src/mlpack/core/cv/metrics/facilities.hpp +++ b/src/mlpack/core/cv/metrics/facilities.hpp @@ -19,29 +19,6 @@ namespace mlpack { namespace cv { -/** - * Assert there is the same number of the given data points and labels. - * - * @param data Column-major data. - * @param labels Labels. - * @param callerDescription A description of the caller that can be used for - * error generation. - */ -template -void AssertSizes(const DataType& data, - const arma::Row& labels, - const std::string& callerDescription) -{ - if (data.n_cols != labels.n_elem) - { - std::ostringstream oss; - oss << callerDescription << ": number of points (" << data.n_cols << ") " - << "does not match number of labels (" << labels.n_elem << ")!" - << std::endl; - throw std::invalid_argument(oss.str()); - } -} - /** * Pairwise distance of the given data. * diff --git a/src/mlpack/core/cv/metrics/mse_impl.hpp b/src/mlpack/core/cv/metrics/mse_impl.hpp index 79a26476aa..d2fdcf8e74 100644 --- a/src/mlpack/core/cv/metrics/mse_impl.hpp +++ b/src/mlpack/core/cv/metrics/mse_impl.hpp @@ -20,14 +20,8 @@ double MSE::Evaluate(MLAlgorithm& model, const DataType& data, const ResponsesType& responses) { - if (data.n_cols != responses.n_cols) - { - std::ostringstream oss; - oss << "MSE::Evaluate(): number of points (" << data.n_cols << ") " - << "does not match number of responses (" << responses.n_cols << ")!" - << std::endl; - throw std::invalid_argument(oss.str()); - } + util::CheckSameSizes(data, (size_t) responses.n_cols, "MSE::Evaluate()", + "responses"); ResponsesType predictedResponses; model.Predict(data, predictedResponses); diff --git a/src/mlpack/core/cv/metrics/precision_impl.hpp b/src/mlpack/core/cv/metrics/precision_impl.hpp index b8831fe132..25afd43454 100644 --- a/src/mlpack/core/cv/metrics/precision_impl.hpp +++ b/src/mlpack/core/cv/metrics/precision_impl.hpp @@ -13,7 +13,6 @@ #define MLPACK_CORE_CV_METRICS_PRECISION_IMPL_HPP #include -#include namespace mlpack { namespace cv { @@ -33,7 +32,7 @@ double Precision::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Precision::Evaluate()"); + util::CheckSameSizes(data, labels, "Precision::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); @@ -51,7 +50,7 @@ double Precision::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Precision::Evaluate()"); + util::CheckSameSizes(data, labels, "Precision::Evaluate()"); // Microaveraged precision turns out to be just accuracy. return Accuracy::Evaluate(model, data, labels); @@ -64,7 +63,7 @@ double Precision::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Precision::Evaluate()"); + util::CheckSameSizes(data, labels, "Precision::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); diff --git a/src/mlpack/core/cv/metrics/r2_score_impl.hpp b/src/mlpack/core/cv/metrics/r2_score_impl.hpp index 2859a17f9d..00eb9a1448 100644 --- a/src/mlpack/core/cv/metrics/r2_score_impl.hpp +++ b/src/mlpack/core/cv/metrics/r2_score_impl.hpp @@ -21,14 +21,8 @@ double R2Score::Evaluate(MLAlgorithm& model, const DataType& data, const ResponsesType& responses) { - if (data.n_cols != responses.n_cols) - { - std::ostringstream oss; - oss << "R2Score::Evaluate(): number of points (" << data.n_cols << ") " - << "does not match number of responses (" << responses.n_cols << ")!" - << std::endl; - throw std::invalid_argument(oss.str()); - } + util::CheckSameSizes(data, (size_t) responses.n_cols, "R2Score::Evaluate()", + "responses"); ResponsesType predictedResponses; // Taking Predicted Output from the model. diff --git a/src/mlpack/core/cv/metrics/recall_impl.hpp b/src/mlpack/core/cv/metrics/recall_impl.hpp index 5ff7bd7400..bbfbe7071d 100644 --- a/src/mlpack/core/cv/metrics/recall_impl.hpp +++ b/src/mlpack/core/cv/metrics/recall_impl.hpp @@ -13,7 +13,6 @@ #define MLPACK_CORE_CV_METRICS_RECALL_IMPL_HPP #include -#include namespace mlpack { namespace cv { @@ -33,7 +32,7 @@ double Recall::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Recall::Evaluate()"); + util::CheckSameSizes(data, labels, "Recall::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); @@ -51,7 +50,7 @@ double Recall::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Recall::Evaluate()"); + util::CheckSameSizes(data, labels, "Recall::Evaluate()"); // Microaveraged recall is really the same as accuracy. return Accuracy::Evaluate(model, data, labels); @@ -64,7 +63,7 @@ double Recall::Evaluate(MLAlgorithm& model, const DataType& data, const arma::Row& labels) { - AssertSizes(data, labels, "Recall::Evaluate()"); + util::CheckSameSizes(data, labels, "Recall::Evaluate()"); arma::Row predictedLabels; model.Classify(data, predictedLabels); diff --git a/src/mlpack/core/cv/metrics/silhouette_score_impl.hpp b/src/mlpack/core/cv/metrics/silhouette_score_impl.hpp index 89041f736e..b271f0d6df 100644 --- a/src/mlpack/core/cv/metrics/silhouette_score_impl.hpp +++ b/src/mlpack/core/cv/metrics/silhouette_score_impl.hpp @@ -22,7 +22,7 @@ double SilhouetteScore::Overall(const DataType& X, const arma::Row& labels, const Metric& metric) { - AssertSizes(X, labels, "SilhouetteScore::Overall()"); + util::CheckSameSizes(X, labels, "SilhouetteScore::Overall()"); return arma::mean(SamplesScore(X, labels, metric)); } @@ -30,7 +30,7 @@ template arma::rowvec SilhouetteScore::SamplesScore(const DataType& distances, const arma::Row& labels) { - AssertSizes(distances, labels, "SilhouetteScore::SamplesScore()"); + util::CheckSameSizes(distances, labels, "SilhouetteScore::SamplesScore()"); // Stores the silhouette scores of individual samples. arma::rowvec sampleScores(distances.n_rows); @@ -76,7 +76,7 @@ arma::rowvec SilhouetteScore::SamplesScore(const DataType& X, const arma::Row& labels, const Metric& metric) { - AssertSizes(X, labels, "SilhouetteScore::SamplesScore()"); + util::CheckSameSizes(X, labels, "SilhouetteScore::SamplesScore()"); DataType distances = PairwiseDistances(X, metric); return SamplesScore(distances, labels); } diff --git a/src/mlpack/core/data/split_data.hpp b/src/mlpack/core/data/split_data.hpp index 42b7e03b3a..f9b0c1558f 100644 --- a/src/mlpack/core/data/split_data.hpp +++ b/src/mlpack/core/data/split_data.hpp @@ -18,11 +18,57 @@ namespace mlpack { namespace data { +/** + * This helper function splits any `input` data into training and testing parts. + * In order to shuffle the input data before spliting, an array of shuffled + * indices of the input data is passed in the form of argument `order`. + */ +template +void SplitHelper(const InputType& input, + InputType& train, + InputType& test, + const double testRatio, + const arma::uvec& order = arma::uvec()) +{ + const size_t testSize = static_cast(input.n_cols * testRatio); + const size_t trainSize = input.n_cols - testSize; + + // Initialising the sizes of outputs if not already initialized. + train.set_size(input.n_rows, trainSize); + test.set_size(input.n_rows, testSize); + + // Shuffling and spliting simultaneously. + if (!order.is_empty()) + { + if (trainSize > 0) + { + for (size_t i = 0; i < trainSize; ++i) + train.col(i) = input.col(order(i)); + } + if (trainSize < input.n_cols) + { + for (size_t i = trainSize; i < input.n_cols; ++i) + test.col(i - trainSize) = input.col(order(i)); + } + } + // Spliting only. + else + { + if (trainSize > 0) + train = input.cols(0, trainSize - 1); + + if (trainSize < input.n_cols) + test = input.cols(trainSize, input.n_cols - 1); + } +} + /** * Given an input dataset and labels, stratify into a training set and test set. * It is recommended to have the input labels between the range [0, n) where n * is the number of different labels. The NormalizeLabels() function in * mlpack::data can be used for this. + * Expects labels to be of type arma::Row<> or arma::Col<>. + * Throws a runtime error if this is not the case. * Example usage below. This overload places the stratified dataset into the * four output parameters given (trainData, testData, trainLabel, * and testLabel). @@ -52,13 +98,14 @@ namespace data { * @param shuffleData If true, the sample order is shuffled; otherwise, each * sample is visited in linear order. (Default true.) */ -template +template::value> > void StratifiedSplit(const arma::Mat& input, - const arma::Row& inputLabel, + const LabelsType& inputLabel, arma::Mat& trainData, arma::Mat& testData, - arma::Row& trainLabel, - arma::Row& testLabel, + LabelsType& trainLabel, + LabelsType& testLabel, const double testRatio, const bool shuffleData = true) { @@ -95,29 +142,24 @@ void StratifiedSplit(const arma::Mat& input, * 0 * 1 1 */ + const bool typeCheck = (arma::is_Row::value) + || (arma::is_Col::value); + if (!typeCheck) + throw std::runtime_error("data::Split(): when stratified sampling is done, " + "labels must have type `arma::Row<>`!"); size_t trainIdx = 0; size_t testIdx = 0; size_t trainSize = 0; size_t testSize = 0; arma::uvec labelCounts; arma::uvec testLabelCounts; - U maxLabel = inputLabel.max(); + typename LabelsType::elem_type maxLabel = inputLabel.max(); labelCounts.zeros(maxLabel+1); testLabelCounts.zeros(maxLabel+1); - arma::uvec order = - arma::linspace(0, input.n_cols - 1, input.n_cols); - - if (shuffleData) - { - order = arma::shuffle(order); - } - - for (U label : inputLabel) - { + for (typename LabelsType::elem_type label : inputLabel) ++labelCounts[label]; - } for (arma::uword labelCount : labelCounts) { @@ -127,24 +169,50 @@ void StratifiedSplit(const arma::Mat& input, trainData.set_size(input.n_rows, trainSize); testData.set_size(input.n_rows, testSize); - trainLabel.set_size(trainSize); - testLabel.set_size(testSize); + trainLabel.set_size(inputLabel.n_rows, trainSize); + testLabel.set_size(inputLabel.n_rows, testSize); - for (arma::uword i : order) + if (shuffleData) { - U label = inputLabel[i]; - if (testLabelCounts[label] < floor(labelCounts[label] * testRatio)) + arma::uvec order = arma::shuffle( + arma::linspace(0, input.n_cols - 1, input.n_cols)); + + for (arma::uword i : order) { - testLabelCounts[label] += 1; - testData.col(testIdx) = input.col(i); - testLabel[testIdx] = inputLabel[i]; - testIdx += 1; + typename LabelsType::elem_type label = inputLabel[i]; + if (testLabelCounts[label] < floor(labelCounts[label] * testRatio)) + { + testLabelCounts[label] += 1; + testData.col(testIdx) = input.col(i); + testLabel[testIdx] = inputLabel[i]; + testIdx += 1; + } + else + { + trainData.col(trainIdx) = input.col(i); + trainLabel[trainIdx] = inputLabel[i]; + trainIdx += 1; + } } - else + } + else + { + for (arma::uword i = 0; i < input.n_cols; i++) { - trainData.col(trainIdx) = input.col(i); - trainLabel[trainIdx] = inputLabel[i]; - trainIdx += 1; + typename LabelsType::elem_type label = inputLabel[i]; + if (testLabelCounts[label] < floor(labelCounts[label] * testRatio)) + { + testLabelCounts[label] += 1; + testData.col(testIdx) = input.col(i); + testLabel[testIdx] = inputLabel[i]; + testIdx += 1; + } + else + { + trainData.col(trainIdx) = input.col(i); + trainLabel[trainIdx] = inputLabel[i]; + trainIdx += 1; + } } } } @@ -169,6 +237,9 @@ void StratifiedSplit(const arma::Mat& input, * testData, trainLabel, testLabel, 0.3); * @endcode * + * @tparam T Type of the elements of the input matrix. + * @tparam LabelsType Type of input labels. It can be arma::Mat, arma::Row, + * arma::Cube or arma::SpMat. * @param input Input dataset to split. * @param inputLabel Input labels to split. * @param trainData Matrix to store training data into. @@ -179,50 +250,28 @@ void StratifiedSplit(const arma::Mat& input, * @param shuffleData If true, the sample order is shuffled; otherwise, each * sample is visited in linear order. (Default true.) */ -template +template::value> > void Split(const arma::Mat& input, - const arma::Row& inputLabel, + const LabelsType& inputLabel, arma::Mat& trainData, arma::Mat& testData, - arma::Row& trainLabel, - arma::Row& testLabel, + LabelsType& trainLabel, + LabelsType& testLabel, const double testRatio, const bool shuffleData = true) { - const size_t testSize = static_cast(input.n_cols * testRatio); - const size_t trainSize = input.n_cols - testSize; - trainData.set_size(input.n_rows, trainSize); - testData.set_size(input.n_rows, testSize); - trainLabel.set_size(trainSize); - testLabel.set_size(testSize); - if (shuffleData) { - arma::uvec order = arma::shuffle(arma::linspace( - 0, input.n_cols - 1, input.n_cols)); - if (trainSize > 0) - { - trainData = input.cols(order.subvec(0, trainSize - 1)); - trainLabel = inputLabel.cols(order.subvec(0, trainSize - 1)); - } - if (trainSize < input.n_cols) - { - testData = input.cols(order.subvec(trainSize, input.n_cols - 1)); - testLabel = inputLabel.cols(order.subvec(trainSize, input.n_cols - 1)); - } + arma::uvec order = arma::shuffle(arma::linspace(0, + input.n_cols - 1, input.n_cols)); + SplitHelper(input, trainData, testData, testRatio, order); + SplitHelper(inputLabel, trainLabel, testLabel, testRatio, order); } else { - if (trainSize > 0) - { - trainData = input.cols(0, trainSize - 1); - trainLabel = inputLabel.subvec(0, trainSize - 1); - } - if (trainSize < input.n_cols) - { - testData = input.cols(trainSize , input.n_cols - 1); - testLabel = inputLabel.subvec(trainSize , input.n_cols - 1); - } + SplitHelper(input, trainData, testData, testRatio); + SplitHelper(inputLabel, trainLabel, testLabel, testRatio); } } @@ -256,29 +305,15 @@ void Split(const arma::Mat& input, const double testRatio, const bool shuffleData = true) { - const size_t testSize = static_cast(input.n_cols * testRatio); - const size_t trainSize = input.n_cols - testSize; - trainData.set_size(input.n_rows, trainSize); - testData.set_size(input.n_rows, testSize); - if (shuffleData) { - arma::uvec order = arma::shuffle(arma::linspace( - 0, input.n_cols - 1, input.n_cols)); - - if (trainSize > 0) - trainData = input.cols(order.subvec(0, trainSize - 1)); - - if (trainSize < input.n_cols) - testData = input.cols(order.subvec(trainSize, input.n_cols - 1)); + arma::uvec order = arma::shuffle(arma::linspace(0, + input.n_cols - 1, input.n_cols)); + SplitHelper(input, trainData, testData, testRatio, order); } else { - if (trainSize > 0) - trainData = input.cols(0, trainSize - 1); - - if (trainSize < input.n_cols) - testData = input.cols(trainSize , input.n_cols - 1); + SplitHelper(input, trainData, testData, testRatio); } } @@ -295,6 +330,9 @@ void Split(const arma::Mat& input, * auto splitResult = Split(input, label, 0.2); * @endcode * + * @tparam T Type of the elements of the input matrix. + * @tparam LabelsType Type of input labels. It can be arma::Mat, arma::Row, + * arma::Cube or arma::SpMat. * @param input Input dataset to split. * @param inputLabel Input labels to split. * @param testRatio Percentage of dataset to use for test set (between 0 and 1). @@ -302,22 +340,24 @@ void Split(const arma::Mat& input, * sample is visited in linear order. (Default true). * @param stratifyData If true, the train and test splits are stratified * so that the ratio of each class in the training and test sets is the same - * as in the original dataset. + * as in the original dataset. Expects labels to be of type arma::Row<> or + * arma::Col<>. * @return std::tuple containing trainData (arma::Mat), testData * (arma::Mat), trainLabel (arma::Row), and testLabel (arma::Row). */ -template -std::tuple, arma::Mat, arma::Row, arma::Row> +template::value> > +std::tuple, arma::Mat, LabelsType, LabelsType> Split(const arma::Mat& input, - const arma::Row& inputLabel, + const LabelsType& inputLabel, const double testRatio, const bool shuffleData = true, const bool stratifyData = false) { arma::Mat trainData; arma::Mat testData; - arma::Row trainLabel; - arma::Row testLabel; + LabelsType trainLabel; + LabelsType testLabel; if (stratifyData) { @@ -368,6 +408,208 @@ Split(const arma::Mat& input, std::move(testData)); } +/** + * Given an input dataset and labels, split into a training set and test set. + * Example usage below. This overload places the split dataset into the four + * output parameters given (trainData, testData, trainLabel, and testLabel). + * + * The input dataset must be of type arma::field. It should have the shape - + * (n_rows = 1, n_cols = Number of samples, n_slices = 1). + * + * NOTE: Here FieldType could be arma::field or arma::field. + * + * @code + * arma::field input = loadData(); + * arma::field label = loadLabel(); + * arma::field trainData; + * arma::field testData; + * arma::field trainLabel; + * arma::field testLabel; + * math::RandomSeed(100); // Set the seed if you like. + * + * // Split the dataset into a training and test set, with 30% of the data being + * // held out for the test set. + * Split(input, label, trainData, testData, trainLabel, testLabel, 0.3); + * @endcode + * + * @param input Input dataset to split. + * @param inputLabel Input labels to split. + * @param trainData FieldType to store training data into. + * @param testData FieldType test data into. + * @param trainLabel Field vector to store training labels into. + * @param testLabel Field vector to store test labels into. + * @param testRatio Percentage of dataset to use for test set (between 0 and 1). + * @param shuffleData If true, the sample order is shuffled; otherwise, each + * sample is visited in linear order. (Default true.) + */ +template ::value || + arma::is_Mat_only::value>> +void Split(const FieldType& input, + const arma::field& inputLabel, + FieldType& trainData, + arma::field& trainLabel, + FieldType& testData, + arma::field& testLabel, + const double testRatio, + const bool shuffleData = true) +{ + if (shuffleData) + { + arma::uvec order = arma::shuffle(arma::linspace(0, + input.n_cols - 1, input.n_cols)); + SplitHelper(input, trainData, testData, testRatio, order); + SplitHelper(inputLabel, trainLabel, testLabel, testRatio, order); + } + else + { + SplitHelper(input, trainData, testData, testRatio); + SplitHelper(inputLabel, trainLabel, testLabel, testRatio); + } +} + +/** + * Given an input dataset, split into a training set and test set. + * Example usage below. This overload places the split dataset into the two + * output parameters given (trainData, testData). + * + * The input dataset must be of type arma::field. It should have the shape - + * (n_rows = 1, n_cols = Number of samples, n_slices = 1) + * + * NOTE: Here FieldType could be arma::field or arma::field + * + * @code + * arma::field input = loadData(); + * arma::field trainData; + * arma::field testData; + * math::RandomSeed(100); // Set the seed if you like. + * + * // Split the dataset into a training and test set, with 30% of the data being + * // held out for the test set. + * Split(input, trainData, testData, 0.3); + * @endcode + * + * @param input Input dataset to split. + * @param trainData FieldType to store training data into. + * @param testData FieldType test data into. + * @param testRatio Percentage of dataset to use for test set (between 0 and 1). + * @param shuffleData If true, the sample order is shuffled; otherwise, each + * sample is visited in linear order. (Default true). + */ +template ::value || + arma::is_Mat_only::value>> +void Split(const FieldType& input, + FieldType& trainData, + FieldType& testData, + const double testRatio, + const bool shuffleData = true) +{ + if (shuffleData) + { + arma::uvec order = arma::shuffle(arma::linspace(0, + input.n_cols - 1, input.n_cols)); + SplitHelper(input, trainData, testData, testRatio, order); + } + else + { + SplitHelper(input, trainData, testData, testRatio); + } +} + +/** + * Given an input dataset and labels, split into a training set and test set. + * Example usage below. This overload returns the split dataset as a std::tuple + * with four elements: an FieldType containing the training data, an + * FieldType containing the test data, an arma::field containing the + * training labels, and an arma::field containing the test labels. + * + * The input dataset must be of type arma::field. It should have the shape - + * (n_rows = 1, n_cols = Number of samples, n_slices = 1) + * + * NOTE: Here FieldType could be arma::field or arma::field + * + * @code + * arma::field input = loadData(); + * arma::field label = loadLabel(); + * auto splitResult = Split(input, label, 0.2); + * @endcode + * + * @param input Input dataset to split. + * @param inputLabel Input labels to split. + * @param testRatio Percentage of dataset to use for test set (between 0 and 1). + * @param shuffleData If true, the sample order is shuffled; otherwise, each + * sample is visited in linear order. (Default true). + * @return std::tuple containing trainData (FieldType), testData + * (FieldType), trainLabel (arma::field), and + * testLabel (arma::field). + */ +template ::value || + arma::is_Mat_only::value>> +std::tuple, arma::field> +Split(const FieldType& input, + const arma::field& inputLabel, + const double testRatio, + const bool shuffleData = true) +{ + FieldType trainData; + FieldType testData; + arma::field trainLabel; + arma::field testLabel; + + Split(input, inputLabel, trainData, trainLabel, testData, testLabel, + testRatio, shuffleData); + + return std::make_tuple(std::move(trainData), + std::move(testData), + std::move(trainLabel), + std::move(testLabel)); +} + +/** + * Given an input dataset, split into a training set and test set. + * Example usage below. This overload returns the split dataset as a std::tuple + * with two elements: an FieldType containing the training data and an + * FieldType containing the test data. + * + * The input dataset must be of type arma::field. It should have the shape - + * (n_rows = 1, n_cols = Number of samples, n_slices = 1) + * + * NOTE: Here FieldType could be arma::field or arma::field + * + * @code + * arma::field input = loadData(); + * auto splitResult = Split(input, 0.2); + * @endcode + * + * @param input Input dataset to split. + * @param testRatio Percentage of dataset to use for test set (between 0 and 1). + * @param shuffleData If true, the sample order is shuffled; otherwise, each + * sample is visited in linear order. (Default true). + * @return std::tuple containing trainData (FieldType) + * and testData (FieldType). + */ +template ::value || + arma::is_Mat_only::value>> +std::tuple +Split(const FieldType& input, + const double testRatio, + const bool shuffleData = true) +{ + FieldType trainData; + FieldType testData; + Split(input, trainData, testData, testRatio, shuffleData); + + return std::make_tuple(std::move(trainData), + std::move(testData)); +} + } // namespace data } // namespace mlpack diff --git a/src/mlpack/core/tree/ballbound.hpp b/src/mlpack/core/tree/ballbound.hpp index 0d6633f7ca..e1a8674f03 100644 --- a/src/mlpack/core/tree/ballbound.hpp +++ b/src/mlpack/core/tree/ballbound.hpp @@ -81,6 +81,9 @@ class BallBound //! Move constructor: take possession of another bound. BallBound(BallBound&& other); + //! Move assignment operator. + BallBound& operator=(BallBound&& other); + //! Destructor to release allocated memory. ~BallBound(); diff --git a/src/mlpack/core/tree/ballbound_impl.hpp b/src/mlpack/core/tree/ballbound_impl.hpp index 59ef8bffc3..59cc86a5ea 100644 --- a/src/mlpack/core/tree/ballbound_impl.hpp +++ b/src/mlpack/core/tree/ballbound_impl.hpp @@ -71,10 +71,14 @@ template BallBound& BallBound::operator=( const BallBound& other) { - radius = other.radius; - center = other.center; - metric = other.metric; - ownsMetric = false; + if (this != &other) + { + radius = other.radius; + center = other.center; + metric = other.metric; + ownsMetric = false; + } + return *this; } //! Move constructor. @@ -92,6 +96,26 @@ BallBound::BallBound(BallBound&& other) : other.ownsMetric = false; } +//! Move assignment operator. +template +BallBound& BallBound::operator=( + BallBound&& other) +{ + if (this != &other) + { + radius = other.radius; + center = std::move(other.center); + metric = other.metric; + ownsMetric = other.ownsMetric; + + other.radius = 0.0; + other.center = VecType(); + other.metric = nullptr; + other.ownsMetric = false; + } + return *this; +} + //! Destructor to release allocated memory. template BallBound::~BallBound() diff --git a/src/mlpack/core/tree/hollow_ball_bound.hpp b/src/mlpack/core/tree/hollow_ball_bound.hpp index d8b65dcf87..d699eab693 100644 --- a/src/mlpack/core/tree/hollow_ball_bound.hpp +++ b/src/mlpack/core/tree/hollow_ball_bound.hpp @@ -86,6 +86,9 @@ class HollowBallBound //! Move constructor: take possession of another bound. HollowBallBound(HollowBallBound&& other); + //! Move assignment operator. + HollowBallBound& operator=(HollowBallBound&& other); + //! Destructor to release allocated memory. ~HollowBallBound(); diff --git a/src/mlpack/core/tree/hollow_ball_bound_impl.hpp b/src/mlpack/core/tree/hollow_ball_bound_impl.hpp index b8446ec350..8ccd06225c 100644 --- a/src/mlpack/core/tree/hollow_ball_bound_impl.hpp +++ b/src/mlpack/core/tree/hollow_ball_bound_impl.hpp @@ -80,15 +80,17 @@ template HollowBallBound& HollowBallBound:: operator=(const HollowBallBound& other) { - if (ownsMetric) - delete metric; - - radii = other.radii; - center = other.center; - hollowCenter = other.hollowCenter; - metric = other.metric; - ownsMetric = false; + if (this != &other) + { + if (ownsMetric) + delete metric; + radii = other.radii; + center = other.center; + hollowCenter = other.hollowCenter; + metric = other.metric; + ownsMetric = false; + } return *this; } @@ -111,6 +113,29 @@ HollowBallBound::HollowBallBound( other.ownsMetric = false; } +//! Move assignment operator. +template +HollowBallBound& HollowBallBound:: +operator=(HollowBallBound&& other) +{ + if (this != &other) + { + radii = other.radii; + center = std::move(other.center); + hollowCenter = std::move(other.hollowCenter); + metric = other.metric; + ownsMetric = other.ownsMetric; + + other.radii.Hi() = 0.0; + other.radii.Lo() = 0.0; + other.center = arma::Col(); + other.hollowCenter = arma::Col(); + other.metric = nullptr; + other.ownsMetric = false; + } + return *this; +} + //! Destructor to release allocated memory. template HollowBallBound::~HollowBallBound() diff --git a/src/mlpack/core/tree/hrectbound.hpp b/src/mlpack/core/tree/hrectbound.hpp index 1d15fe6582..6b8ef6c69a 100644 --- a/src/mlpack/core/tree/hrectbound.hpp +++ b/src/mlpack/core/tree/hrectbound.hpp @@ -73,12 +73,16 @@ class HRectBound //! Copy constructor; necessary to prevent memory leaks. HRectBound(const HRectBound& other); + //! Same as copy constructor; necessary to prevent memory leaks. HRectBound& operator=(const HRectBound& other); //! Move constructor: take possession of another bound's information. HRectBound(HRectBound&& other); + //! Move assignment operator. + HRectBound& operator=(HRectBound&& other); + //! Destructor: clean up memory. ~HRectBound(); diff --git a/src/mlpack/core/tree/hrectbound_impl.hpp b/src/mlpack/core/tree/hrectbound_impl.hpp index 2b73eb020a..491e25fed6 100644 --- a/src/mlpack/core/tree/hrectbound_impl.hpp +++ b/src/mlpack/core/tree/hrectbound_impl.hpp @@ -103,6 +103,26 @@ inline HRectBound::HRectBound( other.minWidth = 0.0; } +/** + * Move assignment operator. + */ +template +inline HRectBound& +HRectBound::operator=( + HRectBound&& other) +{ + if (this != &other) + { + bounds = other.bounds; + minWidth = other.minWidth; + dim = other.dim; + other.dim = 0; + other.bounds = nullptr; + other.minWidth = 0.0; + } + return *this; +} + /** * Destructor: clean up memory. */ diff --git a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp index 405188a4f6..a21dd1af3d 100644 --- a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp +++ b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp @@ -177,10 +177,18 @@ class DiscreteHilbertValue /** * Copy the local Hilbert value's pointer. * - * @param val The DiscreteHilbertValue object from which the dataset + * @param other The DiscreteHilbertValue object from which the dataset * will be copied. */ - DiscreteHilbertValue& operator=(const DiscreteHilbertValue& val); + DiscreteHilbertValue& operator=(const DiscreteHilbertValue& other); + + /** + * Move the local Hilbert object. + * + * @param other The DiscreteHilbertValue object from which the dataset + * will be copied. + */ + DiscreteHilbertValue& operator=(DiscreteHilbertValue&& other); /** * Nullify the localHilbertValues pointer in order to prevent an invalid free. diff --git a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp index c4baa38a90..bd3c9cb87e 100644 --- a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp @@ -434,22 +434,43 @@ RemoveNode(TreeType* node, const size_t nodeIndex) template DiscreteHilbertValue& DiscreteHilbertValue:: -operator=(const DiscreteHilbertValue& val) +operator=(const DiscreteHilbertValue& other) { - if (this == &val) + if (this == &other) return *this; if (ownsLocalHilbertValues) delete localHilbertValues; localHilbertValues = const_cast* > - (val.LocalHilbertValues()); + (other.LocalHilbertValues()); ownsLocalHilbertValues = false; - numValues = val.NumValues(); + numValues = other.NumValues(); return *this; } +template +DiscreteHilbertValue& DiscreteHilbertValue:: +operator=(DiscreteHilbertValue&& other) +{ + if (this != &other) + { + localHilbertValues = other.localHilbertValues; + ownsLocalHilbertValues = other.ownsLocalHilbertValues; + numValues = other.numValues; + valueToInsert = other.valueToInsert; + ownsValueToInsert = other.ownsValueToInsert; + + other.localHilbertValues = nullptr; + other.ownsLocalHilbertValues = false; + other.numValues = 0; + other.valueToInsert = nullptr; + other.ownsValueToInsert = false; + } + return *this; +} + template void DiscreteHilbertValue::NullifyData() { diff --git a/src/mlpack/core/util/CMakeLists.txt b/src/mlpack/core/util/CMakeLists.txt index 19ddff3293..ef21ff91ca 100644 --- a/src/mlpack/core/util/CMakeLists.txt +++ b/src/mlpack/core/util/CMakeLists.txt @@ -26,6 +26,7 @@ set(SOURCES prefixedoutstream_impl.hpp program_doc.hpp program_doc.cpp + size_checks.hpp sfinae_utility.hpp singletons.cpp timers.hpp diff --git a/src/mlpack/core/util/io.cpp b/src/mlpack/core/util/io.cpp index 0c8703c406..904a155cc0 100644 --- a/src/mlpack/core/util/io.cpp +++ b/src/mlpack/core/util/io.cpp @@ -267,3 +267,32 @@ void IO::ClearSettings() GetSingleton().aliases = persistentAliases; GetSingleton().functionMap = persistentFunctions; } + +void IO::CheckInputMatrices() +{ + typedef typename std::tuple TupleType; + std::map::iterator itr; + + for (itr = IO::Parameters().begin(); itr != IO::Parameters().end(); ++itr) + { + std::string paramName = itr->first; + std::string paramType = itr->second.cppType; + if (paramType == "arma::mat") + { + IO::CheckInputMatrix(IO::GetParam(paramName), paramName); + } + else if (paramType == "arma::vec") + { + IO::CheckInputMatrix(IO::GetParam(paramName), paramName); + } + else if (paramType == "arma::rowvec") + { + IO::CheckInputMatrix(IO::GetParam(paramName), paramName); + } + else if (paramType == "std::tuple") + { + IO::CheckInputMatrix( + std::get<1>(IO::GetParam(paramName)), paramName); + } + } +} diff --git a/src/mlpack/core/util/io.hpp b/src/mlpack/core/util/io.hpp index 427142c897..aa9d71c16f 100644 --- a/src/mlpack/core/util/io.hpp +++ b/src/mlpack/core/util/io.hpp @@ -219,6 +219,15 @@ class IO template static T& GetRawParam(const std::string& identifier); + /** + * Utility function for CheckInputMatrices(). + * + * @param matrix Matrix to check. + * @param identifier Name of the parameter in question. + */ + template + static void CheckInputMatrix(const T& matrix, const std::string& identifier); + /** * Given two (matrix) parameters, ensure that the first is an in-place copy of * the second. This will generally do nothing (as the bindings already do @@ -285,6 +294,11 @@ class IO */ static void ClearSettings(); + /** + * Checks all input matrices for NaN and inf values, exits if found any. + */ + static void CheckInputMatrices(); + private: //! Convenience map from alias values to names. std::map aliases; diff --git a/src/mlpack/core/util/io_impl.hpp b/src/mlpack/core/util/io_impl.hpp index feb892325c..e7407efd7f 100644 --- a/src/mlpack/core/util/io_impl.hpp +++ b/src/mlpack/core/util/io_impl.hpp @@ -145,6 +145,18 @@ T& IO::GetRawParam(const std::string& identifier) } } +template +void IO::CheckInputMatrix(const T& matrix, const std::string& identifier) +{ + std::string errMsg1 = "The input " + identifier + " has NaN values."; + std::string errMsg2 = "The input " + identifier + " has inf values."; + + if (matrix.has_nan()) + Log::Fatal << errMsg1 << std::endl; + if (matrix.has_inf()) + Log::Fatal << errMsg2 << std::endl; +} + } // namespace mlpack #endif diff --git a/src/mlpack/core/util/mlpack_main.hpp b/src/mlpack/core/util/mlpack_main.hpp index 06f6f8b060..34f8689e1a 100644 --- a/src/mlpack/core/util/mlpack_main.hpp +++ b/src/mlpack/core/util/mlpack_main.hpp @@ -230,6 +230,8 @@ PARAM_FLAG("copy_all_inputs", "If specified, all input parameters will be deep" " copied before the method is run. This is useful for debugging problems " "where the input parameters are being modified by the algorithm, but can " "slow down the code.", ""); +PARAM_FLAG("check_input_matrices", "If specified, the input matrix is checked for" + " NaN and inf values; an exception is thrown if any are found.", ""); // Nothing else needs to be defined---the binding will use mlpackMain() as-is. diff --git a/src/mlpack/core/util/param.hpp b/src/mlpack/core/util/param.hpp index 208ca64b2f..fc809c5b6e 100644 --- a/src/mlpack/core/util/param.hpp +++ b/src/mlpack/core/util/param.hpp @@ -1015,7 +1015,9 @@ using DatasetInfo = DatasetMapper; */ #define TUPLE_TYPE std::tuple #define PARAM_MATRIX_AND_INFO_IN(ID, DESC, ALIAS) \ - PARAM_IN(TUPLE_TYPE, ID, DESC, ALIAS, TUPLE_TYPE(), false) + PARAM(TUPLE_TYPE, ID, DESC, ALIAS, \ + "std::tuple", false, true, true, \ + TUPLE_TYPE()) /** * Define an input model. From the command line, the user can specify the file @@ -1207,11 +1209,44 @@ using DatasetInfo = DatasetMapper; PARAM_IN(std::vector, ID, DESC, ALIAS, std::vector(), true); /** - * Define an input parameter. Don't use this function; use the other ones above - * that call it. Note that we are using the __LINE__ macro for naming these - * actual parameters when __COUNTER__ does not exist, which is a bit of an ugly - * hack... but this is the preprocessor, after all. We don't have much choice - * other than ugliness. + * Defining useful macros using PARAM macro defined later. + */ +#define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \ + PARAM(T, ID, DESC, ALIAS, #T, REQ, true, false, DEF); + +#define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \ + PARAM(T, ID, DESC, ALIAS, #T, REQ, false, false, DEF); + +#define PARAM_MATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::mat, ID, DESC, ALIAS, "arma::mat", REQ, IN, \ + TRANS, arma::mat()); + +#define PARAM_UMATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::Mat, ID, DESC, ALIAS, "arma::Mat", \ + REQ, IN, TRANS, arma::Mat()); + +#define PARAM_COL(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::vec, ID, DESC, ALIAS, "arma::vec", REQ, IN, TRANS, \ + arma::vec()); + +#define PARAM_UCOL(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::Col, ID, DESC, ALIAS, "arma::Col", \ + REQ, IN, TRANS, arma::Col()); + +#define PARAM_ROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::rowvec, ID, DESC, ALIAS, "arma::rowvec", REQ, IN, \ + TRANS, arma::rowvec()); + +#define PARAM_UROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::Row, ID, DESC, ALIAS, "arma::Row", \ + REQ, IN, TRANS, arma::Row()); + +/** + * Define the PARAM(), PARAM_MODEL() macro. Don't use this function; + * use the other ones above that call it. Note that we are using the __LINE__ + * macro for naming these actual parameters when __COUNTER__ does not exist, + * which is a bit of an ugly hack... but this is the preprocessor, after all. + * We don't have much choice other than ugliness. * * @param T Type of the parameter. * @param ID Name of the parameter. @@ -1223,51 +1258,10 @@ using DatasetInfo = DatasetMapper; * @param REQ Whether or not parameter is required (boolean value). */ #ifdef __COUNTER__ - #define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \ + #define PARAM(T, ID, DESC, ALIAS, NAME, REQ, IN, TRANS, DEF) \ static mlpack::util::Option \ JOIN(io_option_dummy_object_in_, __COUNTER__) \ - (DEF, ID, DESC, ALIAS, #T, REQ, true, false, testName); - - #define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_object_out_, __COUNTER__) \ - (DEF, ID, DESC, ALIAS, #T, REQ, false, false, testName); - - #define PARAM_MATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_matrix_, __COUNTER__) \ - (arma::mat(), ID, DESC, ALIAS, "arma::mat", \ - REQ, IN, !TRANS, testName); - - #define PARAM_UMATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_umatrix_, __COUNTER__) \ - (arma::Mat(), ID, DESC, ALIAS, "arma::Mat", \ - REQ, IN, !TRANS, testName); - - #define PARAM_COL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_col_, __COUNTER__) \ - (arma::vec(), ID, DESC, ALIAS, "arma::vec", \ - REQ, IN, !TRANS, testName); - - #define PARAM_UCOL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_ucol_, __COUNTER__) \ - (arma::Col(), ID, DESC, ALIAS, "arma::Col", \ - REQ, IN, !TRANS, testName); - - #define PARAM_ROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_row_, __COUNTER__) \ - (arma::rowvec(), ID, DESC, ALIAS, "arma::rowvec", \ - REQ, IN, !TRANS, testName); - - #define PARAM_UROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_urow_, __COUNTER__) \ - (arma::Row(), ID, DESC, ALIAS, "arma::Row", \ - REQ, IN, !TRANS, testName); + (DEF, ID, DESC, ALIAS, NAME, REQ, IN, !TRANS, testName); // There are no uses of required models, so that is not an option to this // macro (it would be easy to add). @@ -1280,51 +1274,10 @@ using DatasetInfo = DatasetMapper; // don't think we can absolutely guarantee success, but it should be "good // enough". We use the __LINE__ macro and the type of the parameter to try // and get a good guess at something unique. - #define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \ + #define PARAM(T, ID, DESC, ALIAS, NAME, REQ, IN, TRANS, DEF) \ static mlpack::util::Option \ JOIN(JOIN(io_option_dummy_object_in_, __LINE__), opt) \ - (DEF, ID, DESC, ALIAS, #T, REQ, true, false, testName); - - #define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \ - static mlpack::util::Option \ - JOIN(JOIN(io_option_dummy_object_out_, __LINE__), opt) \ - (DEF, ID, DESC, ALIAS, #T, REQ, false, false, testName); - - #define PARAM_MATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(JOIN(io_option_dummy_object_matrix_, __LINE__), opt) \ - (arma::mat(), ID, DESC, ALIAS, "arma::mat", REQ, IN, !TRANS, \ - testName); - - #define PARAM_UMATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(JOIN(io_option_dummy_object_umatrix_, __LINE__), opt) \ - (arma::Mat(), ID, DESC, ALIAS, "arma::Mat", REQ, IN, \ - !TRANS, testName); - - #define PARAM_COL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_object_col_, __LINE__) \ - (arma::vec(), ID, DESC, ALIAS, "arma::vec", REQ, IN, !TRANS, \ - testName); - - #define PARAM_UCOL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_object_ucol_, __LINE__) \ - (arma::Col(), ID, DESC, ALIAS, "arma::Col", REQ, IN, \ - !TRANS, testName); - - #define PARAM_ROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_object_row_, __LINE__) \ - (arma::rowvec(), ID, DESC, ALIAS, "arma::rowvec", REQ, IN, !TRANS, \ - testName); - - #define PARAM_UROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_object_urow_, __LINE__) \ - (arma::Row(), ID, DESC, ALIAS, "arma::Row", REQ, IN, \ - !TRANS, testName); + (DEF, ID, DESC, ALIAS, NAME, REQ, IN, !TRANS, testName); #define PARAM_MODEL(TYPE, ID, DESC, ALIAS, REQ, IN) \ static mlpack::util::Option \ diff --git a/src/mlpack/core/util/param_checks.hpp b/src/mlpack/core/util/param_checks.hpp index a9180a4816..c1ac39aeea 100644 --- a/src/mlpack/core/util/param_checks.hpp +++ b/src/mlpack/core/util/param_checks.hpp @@ -43,11 +43,14 @@ namespace util { * @param fatal If true, output goes to Log::Fatal instead of Log::Warn and an * exception is thrown. * @param customErrorMessage Error message to append. + * @param allowNone If true, then no error message will be thrown if none of the + * parameters in the constraints were passed. */ void RequireOnlyOnePassed( const std::vector& constraints, const bool fatal = true, - const std::string& customErrorMessage = ""); + const std::string& customErrorMessage = "", + const bool allowNone = false); /** * Require that at least one of the given parameters in the constraints set was diff --git a/src/mlpack/core/util/param_checks_impl.hpp b/src/mlpack/core/util/param_checks_impl.hpp index be88c8a3e9..8562e1341f 100644 --- a/src/mlpack/core/util/param_checks_impl.hpp +++ b/src/mlpack/core/util/param_checks_impl.hpp @@ -21,7 +21,8 @@ namespace util { inline void RequireOnlyOnePassed( const std::vector& constraints, const bool fatal, - const std::string& errorMessage) + const std::string& errorMessage, + const bool allowNone) { if (BINDING_IGNORE_CHECK(constraints)) return; @@ -57,7 +58,7 @@ inline void RequireOnlyOnePassed( stream << "; " << errorMessage; stream << "!" << std::endl; } - else if (set == 0) + else if (set == 0 && !allowNone) { stream << (fatal ? "Must " : "Should "); diff --git a/src/mlpack/core/util/prefixedoutstream_impl.hpp b/src/mlpack/core/util/prefixedoutstream_impl.hpp index 601c81c4fc..3cb9eea353 100644 --- a/src/mlpack/core/util/prefixedoutstream_impl.hpp +++ b/src/mlpack/core/util/prefixedoutstream_impl.hpp @@ -178,8 +178,7 @@ PrefixedOutStream::BaseLogic(const T& val) if (maxVal == 0.0) maxVal = 1; - int maxLog = log10(maxVal); - maxLog = (maxLog > 0) ? floor(maxLog) + 1 : 1; + const int maxLog = int(log10(maxVal)) + 1; const int padding = 4; convert.width(convert.precision() + maxLog + padding); printVal.raw_print(convert); diff --git a/src/mlpack/core/util/size_checks.hpp b/src/mlpack/core/util/size_checks.hpp new file mode 100644 index 0000000000..ab81a9b355 --- /dev/null +++ b/src/mlpack/core/util/size_checks.hpp @@ -0,0 +1,117 @@ +/** + * @file size_checks.hpp + * @author Kirill Mishchenko + * @author Bisakh Mondal + * + * Utility for checking same size & same dimensionality. + * + * 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_UTIL_SIZE_CHECKS_HPP +#define MLPACK_UTIL_SIZE_CHECKS_HPP + + +namespace mlpack { +namespace util { + +/** + * Check for if the given data points & labels have same size. + * + * @param data data. + * @param labels Labels. + * @param callerDescription A description of the caller that can be used for + * error generation. + * @param addInfo Name to use for labels for precise error generation. Default + * is "labels"; for example, "weights" could also be used. + */ +template +inline void CheckSameSizes(const DataType& data, + const LabelsType& label, + const std::string& callerDescription, + const std::string& addInfo = "labels") +{ + if (data.n_cols != label.n_elem) + { + std::ostringstream oss; + oss << callerDescription << ": number of points (" << data.n_cols << ") " + << "does not match number of " << addInfo << " (" << label.n_elem << ")!" + << std::endl; + throw std::invalid_argument(oss.str()); + } +} + +/** + * An overload of CheckSameSizes() where the size to be checked is known + * previously. The second parameter is of type unsigned int. + */ +template +inline void CheckSameSizes(const DataType& data, + const size_t& size, + const std::string& callerDescription, + const std::string& addInfo = "labels") +{ + if (data.n_cols != size) + { + std::ostringstream oss; + oss << callerDescription << ": number of points (" << data.n_cols << ") " + << "does not match number of " << addInfo << " (" << size << ")!" + << std::endl; + throw std::invalid_argument(oss.str()); + } +} + + +/** + * Check for if the given dataset dimension matches with the model's. + * + * @param data dataset. + * @param dimension Dimension of the model. + * @param callerDescription A description of the caller that can be used for + * error generation. + * @param addInfo Name to use for dataset for precise error generation. Default + * is "dataset"; for example, "weights" could also be used. + */ +template +inline void CheckSameDimensionality(const DataType& data, + const DimType& dimension, + const std::string& callerDescription, + const std::string& addInfo = "dataset") +{ + if (data.n_rows != dimension.n_rows) + { + std::ostringstream oss; + oss << callerDescription << ": dimensionality of " << addInfo << " (" + << data.n_rows << ") is not equal to the dimensionality of the model" + " (" << dimension.n_rows << ")!"; + + throw std::invalid_argument(oss.str()); + } +} + +/** + * An overload of CheckSameDimensionality() where the dimension to be checked + * is known second param is unsigned long int. + */ +template +inline void CheckSameDimensionality(const DataType& data, + const size_t& dimension, + const std::string& callerDescription, + const std::string& addInfo = "dataset") +{ + if (data.n_rows != dimension) + { + std::ostringstream oss; + oss << callerDescription << ": dimensionality of " << addInfo << " (" + << data.n_rows << ") is not equal to the dimensionality of the model" + " (" << dimension << ")!"; + throw std::invalid_argument(oss.str()); + } +} + +} // namespace util +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/util/to_lower.hpp b/src/mlpack/core/util/to_lower.hpp index 6107911e0e..0e420a45a2 100644 --- a/src/mlpack/core/util/to_lower.hpp +++ b/src/mlpack/core/util/to_lower.hpp @@ -19,12 +19,13 @@ namespace util { * Convert a string to lowercase letters. * * @param input The string to convert. - * @param output The string to be converted. */ -inline void ToLower(const std::string& input, std::string& output) +inline std::string ToLower(const std::string& input) { - std::transform(input.begin(), input.end(), output.begin(), + std::string output; + std::transform(input.begin(), input.end(), std::back_inserter(output), [](unsigned char c){ return std::tolower(c); }); + return output; } } // namespace util diff --git a/src/mlpack/methods/adaboost/adaboost_model.cpp b/src/mlpack/methods/adaboost/adaboost_model.cpp index a48659b4bd..d71b857d74 100644 --- a/src/mlpack/methods/adaboost/adaboost_model.cpp +++ b/src/mlpack/methods/adaboost/adaboost_model.cpp @@ -72,19 +72,40 @@ AdaBoostModel::AdaBoostModel(AdaBoostModel&& other) : //! Copy assignment operator. AdaBoostModel& AdaBoostModel::operator=(const AdaBoostModel& other) { - mappings = other.mappings; - weakLearnerType = other.weakLearnerType; + if (this != &other) + { + mappings = other.mappings; + weakLearnerType = other.weakLearnerType; - delete dsBoost; - dsBoost = (other.dsBoost == NULL) ? NULL : - new AdaBoost(*other.dsBoost); + delete dsBoost; + dsBoost = (other.dsBoost == NULL) ? NULL : + new AdaBoost(*other.dsBoost); - delete pBoost; - pBoost = (other.pBoost == NULL) ? NULL : - new AdaBoost>(*other.pBoost); + delete pBoost; + pBoost = (other.pBoost == NULL) ? NULL : + new AdaBoost>(*other.pBoost); - dimensionality = other.dimensionality; + dimensionality = other.dimensionality; + } + return *this; +} +//! Move assignment operator. +AdaBoostModel& AdaBoostModel::operator=(AdaBoostModel&& other) +{ + if (this != &other) + { + mappings = std::move(other.mappings); + weakLearnerType = other.weakLearnerType; + + dsBoost = other.dsBoost; + other.dsBoost = nullptr; + + pBoost = other.pBoost; + other.pBoost = nullptr; + + dimensionality = other.dimensionality; + } return *this; } diff --git a/src/mlpack/methods/adaboost/adaboost_model.hpp b/src/mlpack/methods/adaboost/adaboost_model.hpp index e8dcac3a82..36743c4e18 100644 --- a/src/mlpack/methods/adaboost/adaboost_model.hpp +++ b/src/mlpack/methods/adaboost/adaboost_model.hpp @@ -61,6 +61,9 @@ class AdaBoostModel //! Copy assignment operator. AdaBoostModel& operator=(const AdaBoostModel& other); + //! Move assignment operator. + AdaBoostModel& operator=(AdaBoostModel&& other); + //! Clean up memory. ~AdaBoostModel(); diff --git a/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp b/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp index e3a030836d..78eb24108e 100644 --- a/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp +++ b/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp @@ -36,7 +36,8 @@ class CompleteIncrementalTermination */ CompleteIncrementalTermination( TerminationPolicy tPolicy = TerminationPolicy()) : - tPolicy(tPolicy) { } + tPolicy(tPolicy), incrementalIndex(0), iteration(0) + { /* Nothing to do here. */ } /** * Initializes the termination policy before stating the factorization. @@ -119,4 +120,3 @@ class CompleteIncrementalTermination } // namespace mlpack #endif // MLPACK_METHODS_AMF_COMPLETE_INCREMENTAL_TERMINATION_HPP - diff --git a/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp b/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp index 62b112b061..5646b0d205 100644 --- a/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp +++ b/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp @@ -35,7 +35,8 @@ class IncompleteIncrementalTermination */ IncompleteIncrementalTermination( TerminationPolicy tPolicy = TerminationPolicy()) : - tPolicy(tPolicy) { } + tPolicy(tPolicy), incrementalIndex(0), iteration(0) + { /* Nothing to do here. */ } /** * Initializes the termination policy before stating the factorization. diff --git a/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp b/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp index 970b24289f..6c214795c4 100644 --- a/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp +++ b/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp @@ -40,8 +40,16 @@ class SimpleResidueTermination * @param maxIterations Maximum number of iterations. */ SimpleResidueTermination(const double minResidue = 1e-5, - const size_t maxIterations = 10000) - : minResidue(minResidue), maxIterations(maxIterations) { } + const size_t maxIterations = 10000) : + minResidue(minResidue), + maxIterations(maxIterations), + residue(0.0), + iteration(0), + normOld(0), + nm(0) + { + // Nothing to do here. + } /** * Initializes the termination policy before stating the factorization. diff --git a/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp b/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp index 4ab1c0d610..ab2e9d2503 100644 --- a/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp +++ b/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp @@ -56,7 +56,7 @@ class SVDCompleteIncrementalLearning SVDCompleteIncrementalLearning(double u = 0.0001, double kw = 0, double kh = 0) - : u(u), kw(kw), kh(kh) + : u(u), kw(kw), kh(kh), currentUserIndex(0), currentItemIndex(0) { // Nothing to do. } @@ -172,7 +172,7 @@ class SVDCompleteIncrementalLearning SVDCompleteIncrementalLearning(double u = 0.01, double kw = 0, double kh = 0) - : u(u), kw(kw), kh(kh), it(NULL) + : u(u), kw(kw), kh(kh), n(0), m(0), it(NULL), isStart(false) {} ~SVDCompleteIncrementalLearning() diff --git a/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp b/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp index 0082824129..9880ea2945 100644 --- a/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp +++ b/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp @@ -53,7 +53,7 @@ class SVDIncompleteIncrementalLearning SVDIncompleteIncrementalLearning(double u = 0.001, double kw = 0, double kh = 0) - : u(u), kw(kw), kh(kh) + : u(u), kw(kw), kh(kh), currentUserIndex(0) { // Nothing to do. } diff --git a/src/mlpack/methods/ann/activation_functions/CMakeLists.txt b/src/mlpack/methods/ann/activation_functions/CMakeLists.txt index fd4e765006..d5c0868c1c 100644 --- a/src/mlpack/methods/ann/activation_functions/CMakeLists.txt +++ b/src/mlpack/methods/ann/activation_functions/CMakeLists.txt @@ -19,6 +19,7 @@ set(SOURCES multi_quadratic_function.hpp poisson1_function.hpp gaussian_function.hpp + hard_swish_function.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/ann/activation_functions/hard_swish_function.hpp b/src/mlpack/methods/ann/activation_functions/hard_swish_function.hpp new file mode 100644 index 0000000000..d387e86474 --- /dev/null +++ b/src/mlpack/methods/ann/activation_functions/hard_swish_function.hpp @@ -0,0 +1,116 @@ +/** + * @file methods/ann/activation_functions/hard_swish_function.hpp + * @author Anush Kini + * + * Definition and implementation of the Hard Swish function as described by + * Howard A, Sandler M, Chu G, Chen LC, Chen B, Tan M, Wang W, Zhu Y, Pang R, + * Vasudevan V and Le QV. + * For more information, see the following paper. + * + * @code + * @misc{ + * author = {Howard A, Sandler M, Chu G, Chen LC, Chen B, Tan M, Wang W, + * Zhu Y, Pang R, Vasudevan V and Le QV}, + * title = {Searching for MobileNetV3}, + * year = {2019} + * } + * @endcode + * + * 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_ANN_ACTIVATION_FUNCTIONS_HARD_SWISH_FUNCTION_HPP +#define MLPACK_METHODS_ANN_ACTIVATION_FUNCTIONS_HARD_SWISH_FUNCTION_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { +/** + * The Hard Swish function, defined by + * + * @f{eqnarray*}{ + * f(x) &=& \begin{cases} + * 0 & x \leq -3\\ + * x & x \geq +3\\ + * \frac{x * (x + 3)}{6} & otherwise\\ + * \end{cases} \\ + * f'(x) &=& \begin{cases} + * 0 & x \leq -3\\ + * 1 & x \geq +3\\ + * \frac{2x + 3}{6} & otherwise\\ + * \end{cases} + * @f} + */ +class HardSwishFunction +{ + public: + /** + * Computes the Hard Swish function. + * + * @param x Input data. + * @return f(x). + */ + static double Fn(const double x) + { + if (x <= -3) + return 0; + else if (x >= 3) + return x; + + return x * (x + 3) / 6; + } + + /** + * Computes the Hard Swish function. + * + * @param x Input data. + * @param y The resulting output activation. + */ + template + static void Fn(const InputVecType &x, OutputVecType &y) + { + y.set_size(size(x)); + + for (size_t i = 0; i < x.n_elem; i++) + y(i) = Fn(x(i)); + } + + /** + * Computes the first derivative of the Hard Swish function. + * + * @param y Input data. + * @return f'(x). + */ + static double Deriv(const double y) + { + if (y <= -3) + return 0; + else if (y >= 3) + return 1; + + return (2 * y + 3.0) / 6.0; + } + + /** + * Computes the first derivatives of the Hard Swish function. + * + * @param y Input data. + * @param x The resulting derivatives. + */ + template + static void Deriv(const InputVecType &y, OutputVecType &x) + { + x.set_size(size(y)); + + for (size_t i = 0; i < y.n_elem; i++) + x(i) = Deriv(y(i)); + } +}; // class HardSwishFunction + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/CMakeLists.txt b/src/mlpack/methods/ann/layer/CMakeLists.txt index b4726b0c6f..2b181012c7 100644 --- a/src/mlpack/methods/ann/layer/CMakeLists.txt +++ b/src/mlpack/methods/ann/layer/CMakeLists.txt @@ -46,6 +46,8 @@ set(SOURCES hard_tanh_impl.hpp highway.hpp highway_impl.hpp + isrlu.hpp + isrlu_impl.hpp join.hpp join_impl.hpp layer.hpp @@ -63,6 +65,8 @@ set(SOURCES log_softmax_impl.hpp lookup.hpp lookup_impl.hpp + lp_pooling.hpp + lp_pooling_impl.hpp lstm.hpp lstm_impl.hpp max_pooling.hpp @@ -81,6 +85,8 @@ set(SOURCES noisylinear_impl.hpp parametric_relu.hpp parametric_relu_impl.hpp + pixel_shuffle.hpp + pixel_shuffle_impl.hpp positional_encoding.hpp positional_encoding_impl.hpp recurrent.hpp diff --git a/src/mlpack/methods/ann/layer/atrous_convolution_impl.hpp b/src/mlpack/methods/ann/layer/atrous_convolution_impl.hpp index cfb200e3ec..2377ddee00 100644 --- a/src/mlpack/methods/ann/layer/atrous_convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/atrous_convolution_impl.hpp @@ -125,8 +125,7 @@ AtrousConvolution< weights.set_size(WeightSize(), 1); // Transform paddingType to lowercase. - std::string paddingTypeLow = paddingType; - util::ToLower(paddingType, paddingTypeLow); + const std::string paddingTypeLow = util::ToLower(paddingType); size_t padWLeft = std::get<0>(padW); size_t padWRight = std::get<1>(padW); diff --git a/src/mlpack/methods/ann/layer/base_layer.hpp b/src/mlpack/methods/ann/layer/base_layer.hpp index 8429c818a7..ae49f30fe6 100644 --- a/src/mlpack/methods/ann/layer/base_layer.hpp +++ b/src/mlpack/methods/ann/layer/base_layer.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -50,6 +51,7 @@ namespace ann /** Artificial Neural Network. */ { * - ELiSHLayer * - ElliotLayer * - GaussianLayer + * - HardSwishLayer * * @tparam ActivationFunction Activation function used for the embedding layer. * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, @@ -277,6 +279,17 @@ template < using GaussianFunctionLayer = BaseLayer< ActivationFunction, InputDataType, OutputDataType>; +/** + * Standard HardSwish-Layer using the HardSwish activation function. + */ +template < + class ActivationFunction = HardSwishFunction, + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +using HardSwishFunctionLayer = BaseLayer< + ActivationFunction, InputDataType, OutputDataType>; + } // namespace ann } // namespace mlpack diff --git a/src/mlpack/methods/ann/layer/constant.hpp b/src/mlpack/methods/ann/layer/constant.hpp index b908a0018e..9a0956ddf8 100644 --- a/src/mlpack/methods/ann/layer/constant.hpp +++ b/src/mlpack/methods/ann/layer/constant.hpp @@ -79,6 +79,12 @@ class Constant //! Get the output size. size_t OutSize() const { return outSize; } + //! Get the size of the weights. + size_t WeightSize() const + { + return 0; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/convolution.hpp b/src/mlpack/methods/ann/layer/convolution.hpp index 1571c3e414..5ea92f37ea 100644 --- a/src/mlpack/methods/ann/layer/convolution.hpp +++ b/src/mlpack/methods/ann/layer/convolution.hpp @@ -29,6 +29,35 @@ namespace ann /** Artificial Neural Network. */ { /** * Implementation of the Convolution class. The Convolution class represents a * single layer of a neural network. + * Example usage: + * + * Suppose we want to pass a matrix M (2744x100) to a `Convolution` layer; + * in this example, `M` was obtained from "flattening" 100 images (or Mel + * cepstral coefficients, if we talk about speech, or whatever you like) of + * dimension 196x14. In other words, the first 196 columns of each row of M + * will be made of the 196 columns of the first row of each of the 100 images + * (or Mel cepstral coefficients). Then the next 295 columns of M (196 - 393) + * will be made of the 196 columns of the second row of the 100 images (or Mel + * cepstral coefficients), etc. Given that the size of our 2-D input images is + * 196x14, the parameters for our `Convolution` layer will be something like + * this: + * + * ``` + * Convolution<> c(1, // Number of input activation maps. + * 14, // Number of output activation maps. + * 3, // Filter width. + * 3, // Filter height. + * 1, // Stride along width. + * 1, // Stride along height. + * 0, // Padding width. + * 0, // Padding height. + * 196, // Input width. + * 14); // Input height. + * ``` + * + * This `Convolution<>` layer will treat each column of the input matrix `M` as + * a 2-D image (or object) of the original 196x14 size, using this as the input + * for the 14 filters of this example. * * @tparam ForwardConvolutionRule Convolution to perform forward process. * @tparam BackwardConvolutionRule Convolution to perform backward process. diff --git a/src/mlpack/methods/ann/layer/convolution_impl.hpp b/src/mlpack/methods/ann/layer/convolution_impl.hpp index 5018593279..7e6cebf184 100644 --- a/src/mlpack/methods/ann/layer/convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/convolution_impl.hpp @@ -120,8 +120,7 @@ Convolution< weights.set_size(WeightSize(), 1); // Transform paddingType to lowercase. - std::string paddingTypeLow = paddingType; - util::ToLower(paddingType, paddingTypeLow); + const std::string paddingTypeLow = util::ToLower(paddingType); if (paddingTypeLow == "valid") { diff --git a/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp b/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp index 752b132ae4..c72416bdeb 100644 --- a/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp +++ b/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp @@ -163,33 +163,20 @@ void FastLSTM::ResetCell(const size_t size) gradientStep = batchSize * size - 1; const size_t rhoBatchSize = size * batchSize; - if (gate.is_empty() || gate.n_cols != rhoBatchSize) - { - gate.set_size(4 * outSize, rhoBatchSize); - gateActivation.set_size(outSize * 3, rhoBatchSize); - stateActivation.set_size(outSize, rhoBatchSize); - cellActivation.set_size(outSize, rhoBatchSize); - prevError.set_size(4 * outSize, batchSize); - if (prevOutput.is_empty()) - { - prevOutput = arma::zeros(outSize, batchSize); - cell = arma::zeros(outSize, size * batchSize); - cellActivationError = arma::zeros(outSize, batchSize); - outParameter = arma::zeros( - outSize, (size + 1) * batchSize); - } - else - { - // To preserve the leading zeros, recreate the object according to given - // size specifications, while preserving the elements as well as the - // layout of the elements. - prevOutput.resize(outSize, batchSize); - cell.resize(outSize, size * batchSize); - cellActivationError.resize(outSize, batchSize); - outParameter.resize(outSize, (size + 1) * batchSize); - } - } + // Make sure all of the matrices we use to store state are at least as large + // as we need. + gate.set_size(4 * outSize, rhoBatchSize); + gateActivation.set_size(outSize * 3, rhoBatchSize); + stateActivation.set_size(outSize, rhoBatchSize); + cellActivation.set_size(outSize, rhoBatchSize); + prevError.set_size(4 * outSize, batchSize); + + // Reset stored state to zeros. + prevOutput.zeros(outSize, batchSize); + cell.zeros(outSize, size * batchSize); + cellActivationError.zeros(outSize, batchSize); + outParameter.zeros(outSize, (size + 1) * batchSize); } template diff --git a/src/mlpack/methods/ann/layer/isrlu.hpp b/src/mlpack/methods/ann/layer/isrlu.hpp new file mode 100644 index 0000000000..b0a786c6ba --- /dev/null +++ b/src/mlpack/methods/ann/layer/isrlu.hpp @@ -0,0 +1,138 @@ +/** + * @file methods/ann/layer/isrlu.hpp + * @author Abhinav Anand + * + * Definition of the ISRLU activation function as described by Jonathan T. Barron. + * + * For more information, read the following paper. + * + * @code + * @article{ + * author = {Carlile, Brad and Delamarter, Guy and Kinney, Paul and Marti, + * Akiko and Whitney, Brian}, + * title = {Improving deep learning by inverse square root linear units (ISRLUs)}, + * year = {2017}, + * url = {https://arxiv.org/pdf/1710.09967.pdf} + * } + * @endcode + * + * 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_ANN_LAYER_ISRLU_HPP +#define MLPACK_METHODS_ANN_LAYER_ISRLU_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * The ISRLU activation function, defined by + * + * @f{eqnarray*}{ + * f(x) &=& \left\{ + * \begin{array}{lr} + * x & : x \ge 0 \\ + * x(\frac{1}{1 + \alpha x^2}) & : x < 0 + * \end{array} + * \right. \\ + * f'(x) &=& \left\{ + * \begin{array}{lr} + * x & : 1 \ge 0 \\ + * (\frac{1}{1 + \alpha x^2})^3 & : x < 0 + * \end{array} + * \right. + * @f} + * + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class ISRLU +{ + public: + /** + * Create the ISRLU object using the specified parameter. + * + * @param alpha Scale parameter controls the value to which an ISRLU + * saturates for negative inputs. + */ + ISRLU(const double alpha = 1.0); + + /** + * Ordinary feed forward pass of a neural network, evaluating the function + * f(x) by propagating the activity forward through f. + * + * @param input Input data used for evaluating the specified function. + * @param output Resulting output activation. + */ + template + void Forward(const InputType& input, OutputType& output); + + /** + * Ordinary feed backward pass of a neural network, calculating the function + * f(x) by propagating x backwards through f. Using the results from the feed + * forward pass. + * + * @param input The propagated input activation f(x). + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const DataType& input, const DataType& gy, DataType& g); + + //! Get the output parameter. + OutputDataType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType const& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the non zero gradient. + double const& Alpha() const { return alpha; } + //! Modify the non zero gradient. + double& Alpha() { return alpha; } + + //! Get size of weights. + size_t WeightSize() { return 0; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! Locally stored first derivative of the activation function. + arma::mat derivative; + + //! ISRLU Hyperparameter (alpha > 0). + double alpha; + +}; // class ISRLU + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "isrlu_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/layer/isrlu_impl.hpp b/src/mlpack/methods/ann/layer/isrlu_impl.hpp new file mode 100644 index 0000000000..a91830f51d --- /dev/null +++ b/src/mlpack/methods/ann/layer/isrlu_impl.hpp @@ -0,0 +1,65 @@ +/** + * @file methods/ann/layer/isrlu_impl.hpp + * @author Abhinav Anand + * + * Implementation of the ISRLU activation function as described by Jonathan T. Barron. + * + * 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_ANN_LAYER_ISRLU_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_ISRLU_IMPL_HPP + +// In case it hasn't yet been included. +#include "isrlu.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +ISRLU::ISRLU(const double alpha) : + alpha(alpha) +{} + +template +template +void ISRLU::Forward( + const InputType& input, OutputType& output) +{ + output = arma::ones(arma::size(input)); + for (size_t i = 0; i < input.n_elem; ++i) + { + output(i) = (input(i) >= 0) ? input(i) : input(i) * + (1 / std::sqrt(1 + alpha * (input(i) * input(i)))); + } +} + +template +template +void ISRLU::Backward( + const DataType& input, const DataType& gy, DataType& g) +{ + derivative.set_size(arma::size(input)); + for (size_t i = 0; i < input.n_elem; ++i) + { + derivative(i) = (input(i) >= 0) ? 1 : + std::pow(1 / std::sqrt(1 + alpha * input(i) * input(i)), 3); + } + g = gy % derivative; +} + +template +template +void ISRLU::serialize( + Archive& ar, + const uint32_t /* version */) +{ + ar(CEREAL_NVP(alpha)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/layer.hpp b/src/mlpack/methods/ann/layer/layer.hpp index 947395fd6b..6d13a26772 100644 --- a/src/mlpack/methods/ann/layer/layer.hpp +++ b/src/mlpack/methods/ann/layer/layer.hpp @@ -47,6 +47,7 @@ #include "linear3d.hpp" #include "log_softmax.hpp" #include "lookup.hpp" +#include "lp_pooling.hpp" #include "lstm.hpp" #include "max_pooling.hpp" #include "mean_pooling.hpp" @@ -57,6 +58,7 @@ #include "noisylinear.hpp" #include "padding.hpp" #include "parametric_relu.hpp" +#include "pixel_shuffle.hpp" #include "positional_encoding.hpp" #include "recurrent_attention.hpp" #include "recurrent.hpp" diff --git a/src/mlpack/methods/ann/layer/layer_types.hpp b/src/mlpack/methods/ann/layer/layer_types.hpp index 1d7fd0ccba..2532efecfe 100644 --- a/src/mlpack/methods/ann/layer/layer_types.hpp +++ b/src/mlpack/methods/ann/layer/layer_types.hpp @@ -38,10 +38,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -52,6 +54,7 @@ #include #include #include +#include #include #include @@ -219,6 +222,8 @@ class AdaptiveMeanPooling; using MoreTypes = boost::variant< Linear3D*, + LpPooling*, + PixelShuffle*, Glimpse*, Highway*, MultiheadAttention*, @@ -234,7 +239,8 @@ using MoreTypes = boost::variant< VirtualBatchNorm*, RBF*, BaseLayer*, - PositionalEncoding* + PositionalEncoding*, + ISRLU* >; template diff --git a/src/mlpack/methods/ann/layer/linear_no_bias.hpp b/src/mlpack/methods/ann/layer/linear_no_bias.hpp index 7182e84238..5426f1ff4f 100644 --- a/src/mlpack/methods/ann/layer/linear_no_bias.hpp +++ b/src/mlpack/methods/ann/layer/linear_no_bias.hpp @@ -123,6 +123,12 @@ class LinearNoBias //! Modify the gradient. OutputDataType& Gradient() { return gradient; } + //! Get the size of the weights. + size_t WeightSize() const + { + return inSize * outSize; + } + //! Get the shape of the input. size_t InputShape() const { diff --git a/src/mlpack/methods/ann/layer/linear_no_bias_impl.hpp b/src/mlpack/methods/ann/layer/linear_no_bias_impl.hpp index 032552f599..dbea56e94f 100644 --- a/src/mlpack/methods/ann/layer/linear_no_bias_impl.hpp +++ b/src/mlpack/methods/ann/layer/linear_no_bias_impl.hpp @@ -38,7 +38,7 @@ LinearNoBias::LinearNoBias( outSize(outSize), regularizer(regularizer) { - weights.set_size(outSize * inSize, 1); + weights.set_size(WeightSize(), 1); } template + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Implementation of the LPPooling. + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class LpPooling +{ + public: + //! Create the LpPooling object. + LpPooling(); + + /** + * Create the LpPooling object using the specified number of units. + * + * @param normType Parameter for type of norm. + * @param kernelWidth Width of the pooling window. + * @param kernelHeight Height of the pooling window. + * @param strideWidth Width of the stride operation. + * @param strideHeight Width of the stride operation. + * @param floor Set to true to use floor method. + */ + LpPooling(const size_t normType, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth = 1, + const size_t strideHeight = 1, + const bool floor = true); + + /** + * Ordinary feed forward pass of a neural network, evaluating the function + * f(x) by propagating the activity forward through f. + * + * @param input Input data used for evaluating the specified function. + * @param output Resulting output activation. + */ + template + void Forward(const arma::Mat& input, arma::Mat& output); + + /** + * Ordinary feed backward pass of a neural network, using 3rd-order tensors as + * input, calculating the function f(x) by propagating x backwards through f. + * Using the results from the feed forward pass. + * + * @param * (input) The propagated input activation. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const arma::Mat& /* input */, + const arma::Mat& gy, + arma::Mat& g); + + //! Get the output parameter. + OutputDataType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType const& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the intput width. + size_t const& InputWidth() const { return inputWidth; } + //! Modify the input width. + size_t& InputWidth() { return inputWidth; } + + //! Get the input height. + size_t const& InputHeight() const { return inputHeight; } + //! Modify the input height. + size_t& InputHeight() { return inputHeight; } + + //! Get the output width. + size_t const& OutputWidth() const { return outputWidth; } + //! Modify the output width. + size_t& OutputWidth() { return outputWidth; } + + //! Get the output height. + size_t const& OutputHeight() const { return outputHeight; } + //! Modify the output height. + size_t& OutputHeight() { return outputHeight; } + + //! Get the input size. + size_t InputSize() const { return inSize; } + + //! Get the output size. + size_t OutputSize() const { return outSize; } + + //! Get the normType. + size_t NormType() const { return normType; } + //! Modify the normType. + size_t& NormType() { return normType; } + + //! Get the kernel width. + size_t KernelWidth() const { return kernelWidth; } + //! Modify the kernel width. + size_t& KernelWidth() { return kernelWidth; } + + //! Get the kernel height. + size_t KernelHeight() const { return kernelHeight; } + //! Modify the kernel height. + size_t& KernelHeight() { return kernelHeight; } + + //! Get the stride width. + size_t StrideWidth() const { return strideWidth; } + //! Modify the stride width. + size_t& StrideWidth() { return strideWidth; } + + //! Get the stride height. + size_t StrideHeight() const { return strideHeight; } + //! Modify the stride height. + size_t& StrideHeight() { return strideHeight; } + + //! Get the value of the rounding operation + bool const& Floor() const { return floor; } + //! Modify the value of the rounding operation + bool& Floor() { return floor; } + + //! Get the size of the weights. + size_t WeightSize() const { return 0; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + /** + * Apply pooling to the input and store the results. + * + * @param input The input to be apply the pooling rule. + * @param output The pooled result. + */ + template + void Pooling(const arma::Mat& input, arma::Mat& output) + { + for (size_t j = 0, colidx = 0; j < output.n_cols; + ++j, colidx += strideHeight) + { + for (size_t i = 0, rowidx = 0; i < output.n_rows; + ++i, rowidx += strideWidth) + { + arma::mat subInput = input( + arma::span(rowidx, rowidx + kernelWidth - 1 - offset), + arma::span(colidx, colidx + kernelHeight - 1 - offset)); + + output(i, j) = pow(arma::accu(arma::pow(subInput, + normType)), 1.0 / normType); + } + } + } + + /** + * Apply unpooling to the input and store the results. + * + * @param input The input to be apply the unpooling rule. + * @param output The pooled result. + */ + template + void Unpooling(const arma::Mat& input, + const arma::Mat& error, + arma::Mat& output) + { + const size_t rStep = input.n_rows / error.n_rows - offset; + const size_t cStep = input.n_cols / error.n_cols - offset; + + arma::Mat unpooledError; + for (size_t j = 0; j < input.n_cols - cStep; j += cStep) + { + for (size_t i = 0; i < input.n_rows - rStep; i += rStep) + { + const arma::Mat& inputArea = input(arma::span(i, i + rStep - 1), + arma::span(j, j + cStep - 1)); + size_t sum = pow(arma::accu(arma::pow(inputArea, normType)), + (normType - 1) / normType); + unpooledError = arma::Mat(inputArea.n_rows, inputArea.n_cols); + unpooledError.fill(error(i / rStep, j / cStep)); + unpooledError %= arma::pow(inputArea, normType - 1); + unpooledError /= sum; + output(arma::span(i, i + rStep - 1 - offset), + arma::span(j, j + cStep - 1 - offset)) += unpooledError; + } + } + } + + //! Locally-stored norm type. + size_t normType; + + //! Locally-stored width of the pooling window. + size_t kernelWidth; + + //! Locally-stored height of the pooling window. + size_t kernelHeight; + + //! Locally-stored width of the stride operation. + size_t strideWidth; + + //! Locally-stored height of the stride operation. + size_t strideHeight; + + //! Rounding operation used. + bool floor; + + //! Locally-stored number of input channels. + size_t inSize; + + //! Locally-stored number of output channels. + size_t outSize; + + //! Locally-stored input width. + size_t inputWidth; + + //! Locally-stored input height. + size_t inputHeight; + + //! Locally-stored output width. + size_t outputWidth; + + //! Locally-stored output height. + size_t outputHeight; + + //! Locally-stored reset parameter used to initialize the module once. + bool reset; + + //! Locally-stored stored rounding offset. + size_t offset; + + //! Locally-stored number of input units. + size_t batchSize; + + //! Locally-stored output parameter. + arma::cube outputTemp; + + //! Locally-stored transformed input parameter. + arma::cube inputTemp; + + //! Locally-stored transformed output parameter. + arma::cube gTemp; + + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored gradient object. + OutputDataType gradient; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; +}; // class LpPooling + + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "lp_pooling_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/layer/lp_pooling_impl.hpp b/src/mlpack/methods/ann/layer/lp_pooling_impl.hpp new file mode 100644 index 0000000000..0abe08ada6 --- /dev/null +++ b/src/mlpack/methods/ann/layer/lp_pooling_impl.hpp @@ -0,0 +1,141 @@ +/** + * @file methods/ann/layer/lp_pooling_impl.hpp + * @author Marcus Edel + * @author Nilay Jain + * + * Implementation of the lpPooling layer class. + * + * 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_ANN_LAYER_LP_POOLING_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_LP_POOLING_IMPL_HPP + +// In case it hasn't yet been included. +#include "lp_pooling.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +LpPooling::LpPooling() +{ + // Nothing to do here. +} + +template +LpPooling::LpPooling( + const size_t normType, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth, + const size_t strideHeight, + const bool floor) : + normType(normType), + kernelWidth(kernelWidth), + kernelHeight(kernelHeight), + strideWidth(strideWidth), + strideHeight(strideHeight), + floor(floor), + inSize(0), + outSize(0), + inputWidth(0), + inputHeight(0), + outputWidth(0), + outputHeight(0), + reset(false), + offset(0), + batchSize(0) +{ + // Nothing to do here. +} + +template +template +void LpPooling::Forward( + const arma::Mat& input, arma::Mat& output) +{ + batchSize = input.n_cols; + inSize = input.n_elem / (inputWidth * inputHeight * batchSize); + inputTemp = arma::cube(const_cast&>(input).memptr(), + inputWidth, inputHeight, batchSize * inSize, false, false); + + if (floor) + { + outputWidth = std::floor((inputWidth - + (double) kernelWidth) / (double) strideWidth + 1); + outputHeight = std::floor((inputHeight - + (double) kernelHeight) / (double) strideHeight + 1); + + offset = 0; + } + else + { + outputWidth = std::ceil((inputWidth - + (double) kernelWidth) / (double) strideWidth + 1); + outputHeight = std::ceil((inputHeight - + (double) kernelHeight) / (double) strideHeight + 1); + + offset = 1; + } + + outputTemp = arma::zeros >(outputWidth, outputHeight, + batchSize * inSize); + + for (size_t s = 0; s < inputTemp.n_slices; s++) + Pooling(inputTemp.slice(s), outputTemp.slice(s)); + + output = arma::Mat(outputTemp.memptr(), outputTemp.n_elem / batchSize, + batchSize); + + outputWidth = outputTemp.n_rows; + outputHeight = outputTemp.n_cols; + outSize = batchSize * inSize; +} + +template +template +void LpPooling::Backward( + const arma::Mat& /* input */, + const arma::Mat& gy, + arma::Mat& g) +{ + arma::cube mappedError = arma::cube(((arma::Mat&) gy).memptr(), + outputWidth, outputHeight, outSize, false, false); + + gTemp = arma::zeros(inputTemp.n_rows, + inputTemp.n_cols, inputTemp.n_slices); + + for (size_t s = 0; s < mappedError.n_slices; s++) + { + Unpooling(inputTemp.slice(s), mappedError.slice(s), gTemp.slice(s)); + } + + g = arma::mat(gTemp.memptr(), gTemp.n_elem / batchSize, batchSize); +} + +template +template +void LpPooling::serialize( + Archive& ar, + const uint32_t /* version */) +{ + ar(CEREAL_NVP(normType)); + ar(CEREAL_NVP(kernelWidth)); + ar(CEREAL_NVP(kernelHeight)); + ar(CEREAL_NVP(strideWidth)); + ar(CEREAL_NVP(strideHeight)); + ar(CEREAL_NVP(batchSize)); + ar(CEREAL_NVP(floor)); + ar(CEREAL_NVP(inputWidth)); + ar(CEREAL_NVP(inputHeight)); + ar(CEREAL_NVP(outputWidth)); + ar(CEREAL_NVP(outputHeight)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/lstm_impl.hpp b/src/mlpack/methods/ann/layer/lstm_impl.hpp index c3da6c0930..9d720732c4 100644 --- a/src/mlpack/methods/ann/layer/lstm_impl.hpp +++ b/src/mlpack/methods/ann/layer/lstm_impl.hpp @@ -144,36 +144,25 @@ void LSTM::ResetCell(const size_t size) gradientStep = batchSize * size - 1; const size_t rhoBatchSize = size * batchSize; - if (inputGate.is_empty() || inputGate.n_cols < rhoBatchSize) - { - inputGate.set_size(outSize, rhoBatchSize); - forgetGate.set_size(outSize, rhoBatchSize); - hiddenLayer.set_size(outSize, rhoBatchSize); - outputGate.set_size(outSize, rhoBatchSize); - inputGateActivation.set_size(outSize, rhoBatchSize); - forgetGateActivation.set_size(outSize, rhoBatchSize); - outputGateActivation.set_size(outSize, rhoBatchSize); - hiddenLayerActivation.set_size(outSize, rhoBatchSize); + // Make sure all of the different matrices we will use to hold parameters are + // at least as large as we need. + inputGate.set_size(outSize, rhoBatchSize); + forgetGate.set_size(outSize, rhoBatchSize); + hiddenLayer.set_size(outSize, rhoBatchSize); + outputGate.set_size(outSize, rhoBatchSize); - cellActivation.set_size(outSize, rhoBatchSize); - prevError.set_size(4 * outSize, batchSize); + inputGateActivation.set_size(outSize, rhoBatchSize); + forgetGateActivation.set_size(outSize, rhoBatchSize); + outputGateActivation.set_size(outSize, rhoBatchSize); + hiddenLayerActivation.set_size(outSize, rhoBatchSize); - if (cell.is_empty()) - { - cell = arma::zeros(outSize, size * batchSize); - outParameter = arma::zeros( - outSize, (size + 1) * batchSize); - } - else - { - // To preserve the leading zeros, recreate the object according to given - // size specifications, while preserving the elements as well as the - // layout of the elements. - cell.resize(outSize, size * batchSize); - outParameter.resize(outSize, (size + 1) * batchSize); - } - } + cellActivation.set_size(outSize, rhoBatchSize); + prevError.set_size(4 * outSize, batchSize); + + // Now reset recurrent values to 0. + cell.zeros(outSize, size * batchSize); + outParameter.zeros(outSize, (size + 1) * batchSize); } template diff --git a/src/mlpack/methods/ann/layer/multihead_attention.hpp b/src/mlpack/methods/ann/layer/multihead_attention.hpp index ec079d197d..0d7506ea51 100644 --- a/src/mlpack/methods/ann/layer/multihead_attention.hpp +++ b/src/mlpack/methods/ann/layer/multihead_attention.hpp @@ -120,6 +120,9 @@ class MultiheadAttention const arma::Mat& error, arma::Mat& gradient); + //! Get the size of the weights. + size_t WeightSize() const { return 4 * (embedDim + 1) * embedDim; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp b/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp index d2da8788e9..3d687d93af 100644 --- a/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp +++ b/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp @@ -54,7 +54,7 @@ MultiheadAttention( } headDim = embedDim / numHeads; - weights.set_size(4 * (embedDim + 1) * embedDim, 1); + weights.set_size(WeightSize(), 1); } template ::MultiplyConstant( // Nothing to do here. } +template +MultiplyConstant::MultiplyConstant( + const MultiplyConstant& layer) : + scalar(layer.scalar) +{ + // Nothing to do here. +} + +template +MultiplyConstant::MultiplyConstant( + MultiplyConstant&& layer) : + scalar(std::move(layer.scalar)) +{ + // Nothing to do here. +} + +template +MultiplyConstant& +MultiplyConstant::operator=( + const MultiplyConstant& layer) +{ + if (this != &layer) + { + scalar = layer.scalar; + } + return *this; +} + +template +MultiplyConstant& +MultiplyConstant::operator=( + MultiplyConstant&& layer) +{ + if (this != &layer) + { + scalar = std::move(layer.scalar); + } + return *this; +} + template template void MultiplyConstant::Forward( diff --git a/src/mlpack/methods/ann/layer/multiply_merge.hpp b/src/mlpack/methods/ann/layer/multiply_merge.hpp index f459ab2f81..5c3d9ba6c0 100644 --- a/src/mlpack/methods/ann/layer/multiply_merge.hpp +++ b/src/mlpack/methods/ann/layer/multiply_merge.hpp @@ -50,6 +50,18 @@ class MultiplyMerge */ MultiplyMerge(const bool model = false, const bool run = true); + //! Copy Constructor. + MultiplyMerge(const MultiplyMerge& layer); + + //! Move Constructor. + MultiplyMerge(MultiplyMerge&& layer); + + //! Copy assignment operator. + MultiplyMerge& operator=(const MultiplyMerge& layer); + + //! Move assignment operator. + MultiplyMerge& operator=(MultiplyMerge&& layer); + //! Destructor to release allocated memory. ~MultiplyMerge(); @@ -135,6 +147,9 @@ class MultiplyMerge //! Modify the parameters. OutputDataType& Parameters() { return weights; } + //! Get the size of the weights. + size_t WeightSize() const { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp b/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp index ee4c8ed917..29cd111482 100644 --- a/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp +++ b/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp @@ -32,6 +32,66 @@ MultiplyMerge::MultiplyMerge( // Nothing to do here. } +template +MultiplyMerge::MultiplyMerge( + const MultiplyMerge& layer) : + model(layer.model), + run(layer.run), + ownsLayer(layer.ownsLayer), + network(layer.network), + weights(layer.weights) +{ + // Nothing to do here. +} + +template +MultiplyMerge::MultiplyMerge( + MultiplyMerge&& layer) : + model(std::move(layer.model)), + run(std::move(layer.run)), + ownsLayer(std::move(layer.ownsLayer)), + network(std::move(layer.network)), + weights(std::move(layer.weights)) +{ + // Nothing to do here. +} + +template +MultiplyMerge& +MultiplyMerge::operator=( + const MultiplyMerge& layer) +{ + if (this != &layer) + { + model = layer.model; + run = layer.run; + ownsLayer = layer.ownsLayer; + network = layer.network; + weights = layer.weights; + } + return *this; +} + +template +MultiplyMerge& +MultiplyMerge::operator=( + MultiplyMerge&& layer) +{ + if (this != &layer) + { + model = std::move(layer.model); + run = std::move(layer.run); + ownsLayer = std::move(layer.ownsLayer); + network = std::move(layer.network); + weights = std::move(layer.weights); + } + return *this; +} + template MultiplyMerge::~MultiplyMerge() diff --git a/src/mlpack/methods/ann/layer/pixel_shuffle.hpp b/src/mlpack/methods/ann/layer/pixel_shuffle.hpp new file mode 100644 index 0000000000..f425d7c508 --- /dev/null +++ b/src/mlpack/methods/ann/layer/pixel_shuffle.hpp @@ -0,0 +1,181 @@ +/** + * @file methods/ann/layer/pixel_shuffle.hpp + * @author Anjishnu Mukherjee + * @author Abhinav Anand + * + * Definition of the PixelShuffle class. + * + * 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_ANN_LAYER_PIXEL_SHUFFLE_HPP +#define MLPACK_METHODS_ANN_LAYER_PIXEL_SHUFFLE_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Implementation of the PixelShuffle layer. + * + * For more information, refer to the following paper, + * + * @code + * @article{Shi16, + * author = {Wenzhe Shi, Jose Caballero,Ferenc Huszár, Johannes Totz, + * Andrew P. Aitken, Rob Bishop, Daniel Rueckert, Zehan Wang}, + * title = {Real-Time Single Image and Video Super-Resolution Using an + * Efficient Sub-Pixel Convolutional Neural Network}, + * journal = {CoRR}, + * volume = {abs/1609.05158}, + * year = {2016}, + * url = {https://arxiv.org/abs/1609.05158}, + * eprint = {1609.05158}, + * } + * @endcode + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class PixelShuffle +{ + public: + //! Create the PixelShuffle object. + PixelShuffle(); + + /** + * Create the PixelShuffle object using the specified parameters. + * The number of input channels should be an integral multiple of the square + * of the upscale factor. + * + * @param upscaleFactor The scaling factor for Pixel Shuffle. + * @param height The height of each input image. + * @param width The width of each input image. + * @param size The number of channels of each input image. + */ + PixelShuffle(const size_t upscaleFactor, + const size_t height, + const size_t width, + const size_t size); + + /** + * Ordinary feed forward pass of the PixelShuffle layer. + * + * @param input Input data used for evaluating the specified function. + * @param output Resulting output activation. + */ + template + void Forward(const arma::Mat& input, arma::Mat& output); + + /** + * Ordinary feed backward pass of the PixelShuffle layer. + * + * @param input The propagated input activation. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const arma::Mat& input, + const arma::Mat& gy, + arma::Mat& g); + + //! Get the output parameter. + OutputDataType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType const& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the upscale factor. + size_t UpscaleFactor() const { return upscaleFactor; } + + //! Modify the upscale factor. + size_t& UpscaleFactor() { return upscaleFactor; } + + //! Get the input image height. + size_t InputHeight() const { return height; } + + //! Modify the input image height. + size_t& InputHeight() { return height; } + + //! Get the input image width. + size_t InputWidth() const { return width; } + + //! Modify the input image width. + size_t& InputWidth() { return width; } + + //! Get the number of input channels. + size_t InputChannels() const { return size; } + + //! Modify the number of input channels. + size_t& InputChannels() { return size; } + + //! Get the output image height. + size_t OutputHeight() const { return outputHeight; } + + //! Get the output image width. + size_t OutputWidth() const { return outputWidth; } + + //! Get the number of output channels. + size_t OutputChannels() const { return sizeOut; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const unsigned int /* version */); + + private: + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! The scaling factor for Pixel Shuffle. + size_t upscaleFactor; + + //! The height of each input image. + size_t height; + + //! The width of each input image. + size_t width; + + //! The number of channels of each input image. + size_t size; + + //! The number of images in the batch. + size_t batchSize; + + //! The height of each output image. + size_t outputHeight; + + //! The width of each output image. + size_t outputWidth; + + //! The number of channels of each output image. + size_t sizeOut; + + //! A boolean used to do some internal calculations once initially. + bool reset; +}; // class PixelShuffle + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "pixel_shuffle_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/layer/pixel_shuffle_impl.hpp b/src/mlpack/methods/ann/layer/pixel_shuffle_impl.hpp new file mode 100644 index 0000000000..f56f708981 --- /dev/null +++ b/src/mlpack/methods/ann/layer/pixel_shuffle_impl.hpp @@ -0,0 +1,143 @@ +/** + * @file methods/ann/layer/pixel_shuffle_impl.hpp + * @author Anjishnu Mukherjee + * @author Abhinav Anand + * + * Implementation of the PixelShuffle class. + * + * 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_ANN_LAYER_PIXEL_SHUFFLE_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_PIXEL_SHUFFLE_IMPL_HPP + +// In case it hasn't yet been included. +#include "pixel_shuffle.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +PixelShuffle::PixelShuffle() : + PixelShuffle(0, 0, 0, 0) +{ + // Nothing to do here. +} + +template +PixelShuffle::PixelShuffle( + const size_t upscaleFactor, + const size_t height, + const size_t width, + const size_t size) : + upscaleFactor(upscaleFactor), + height(height), + width(width), + size(size), + batchSize(0), + outputHeight(0), + outputWidth(0), + sizeOut(0), + reset(false) +{ + // Nothing to do here. +} + +template +template +void PixelShuffle::Forward( + const arma::Mat& input, arma::Mat& output) +{ + if (!reset) + { + batchSize = input.n_cols; + sizeOut = size / std::pow(upscaleFactor, 2); + outputHeight = height * upscaleFactor; + outputWidth = width * upscaleFactor; + reset = true; + } + + output.zeros(outputHeight * outputWidth * sizeOut, batchSize); + for (size_t n = 0; n < batchSize; n++) + { + arma::cube inputTemp(const_cast(input).memptr(), height, + width, size * batchSize, false, false); + arma::cube outputTemp(const_cast(output).memptr(), + outputHeight, outputWidth, sizeOut * batchSize, false, false); + + for (size_t c = 0; c < sizeOut; c++) + { + for (size_t h = 0; h < outputHeight; h++) + { + for (size_t w = 0; w < outputWidth; w++) + { + size_t height_index = h / upscaleFactor; + size_t width_index = w / upscaleFactor; + size_t channel_index = (upscaleFactor * (h % upscaleFactor)) + + (w % upscaleFactor) + (c * std::pow(upscaleFactor, 2)); + outputTemp(w, h, c + n * sizeOut) = inputTemp(width_index, height_index, + channel_index + n * size); + } + } + } + + } +} + +template +template +void PixelShuffle::Backward( + const arma::Mat& input, const arma::Mat& gy, arma::Mat& g) +{ + g.zeros(arma::size(input)); + for (size_t n = 0; n < batchSize; n++) + { + arma::cube gyTemp(const_cast(gy).memptr(), outputHeight, + outputWidth, sizeOut * batchSize, false, false); + arma::cube gTemp(const_cast(g).memptr(), height, width, + size * batchSize, false, false); + + for (size_t c = 0; c < sizeOut; c++) + { + for (size_t h = 0; h < outputHeight; h++) + { + for (size_t w = 0; w < outputWidth; w++) + { + size_t height_index = h / upscaleFactor; + size_t width_index = w / upscaleFactor; + size_t channel_index = (upscaleFactor * (h % upscaleFactor)) + + (w % upscaleFactor) + (c * std::pow(upscaleFactor, 2)); + gTemp(width_index, height_index, channel_index + n * size) = gyTemp(w, h, + c + n * sizeOut); + } + } + } + + } +} + +template +template +void PixelShuffle::serialize( + Archive& ar, + const unsigned int /* version */) +{ + ar(CEREAL_NVP(delta)); + ar(CEREAL_NVP(outputParameter)); + ar(CEREAL_NVP(upscaleFactor)); + ar(CEREAL_NVP(height)); + ar(CEREAL_NVP(width)); + ar(CEREAL_NVP(size)); + ar(CEREAL_NVP(batchSize)); + ar(CEREAL_NVP(outputHeight)); + ar(CEREAL_NVP(outputHeight)); + ar(CEREAL_NVP(outputWidth)); + ar(CEREAL_NVP(sizeOut)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/radial_basis_function.hpp b/src/mlpack/methods/ann/layer/radial_basis_function.hpp index 2387d612ab..6f84303360 100644 --- a/src/mlpack/methods/ann/layer/radial_basis_function.hpp +++ b/src/mlpack/methods/ann/layer/radial_basis_function.hpp @@ -110,6 +110,12 @@ class RBF //! Modify the delta. OutputDataType& Delta() { return delta; } + //! Get the size of the weights. + size_t WeightSize() const + { + return 0; + } + //! Get the shape of the input. size_t InputShape() const { diff --git a/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp b/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp index 4cdb912756..dcc60055d5 100644 --- a/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp +++ b/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp @@ -31,7 +31,8 @@ RecurrentAttention::RecurrentAttention() : rho(0), forwardStep(0), backwardStep(0), - deterministic(false) + deterministic(false), + outSize(0) { // Nothing to do. } diff --git a/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp b/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp index 67eebf107d..c2f92df476 100644 --- a/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp +++ b/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp @@ -21,7 +21,7 @@ namespace ann /** Artificial Neural Network. */ { template ReinforceNormal::ReinforceNormal( - const double stdev) : stdev(stdev) + const double stdev) : stdev(stdev), reward(0.0), deterministic(false) { // Nothing to do here. } @@ -34,8 +34,7 @@ void ReinforceNormal::Forward( if (!deterministic) { // Multiply by standard deviations and re-center the means to the mean. - output = arma::randn >(input.n_rows, input.n_cols) * - stdev + input; + output = output.randn(input.n_rows, input.n_cols) * stdev + input; moduleInputParameter.push_back(input); } diff --git a/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp b/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp index 47cf2cd6c8..d932acb6a7 100644 --- a/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp @@ -126,8 +126,7 @@ TransposedConvolution< { weights.set_size(WeightSize(), 1); // Transform paddingType to lowercase. - std::string paddingTypeLow = paddingType; - util::ToLower(paddingType, paddingTypeLow); + const std::string paddingTypeLow = util::ToLower(paddingType); if (paddingTypeLow == "valid") { diff --git a/src/mlpack/methods/ann/layer_names.hpp b/src/mlpack/methods/ann/layer_names.hpp index be1b1f7fcb..15596efea3 100644 --- a/src/mlpack/methods/ann/layer_names.hpp +++ b/src/mlpack/methods/ann/layer_names.hpp @@ -206,6 +206,17 @@ class LayerNameVisitor : public boost::static_visitor return "meanpooling"; } + /** + * Return the name of the given layer of type LpPooling as a string. + * + * @param * Given layer of type LpPooling. + * @return The string representation of the layer. + */ + std::string LayerString(LpPooling<>* /*layer*/) const + { + return "lppooling"; + } + /** * Return the name of the given layer of type MultiplyConstant as a string. * diff --git a/src/mlpack/methods/ann/loss_functions/CMakeLists.txt b/src/mlpack/methods/ann/loss_functions/CMakeLists.txt index 70b570e0ff..12d9d1718a 100644 --- a/src/mlpack/methods/ann/loss_functions/CMakeLists.txt +++ b/src/mlpack/methods/ann/loss_functions/CMakeLists.txt @@ -9,24 +9,32 @@ set(SOURCES dice_loss_impl.hpp earth_mover_distance.hpp earth_mover_distance_impl.hpp + empty_loss.hpp + empty_loss_impl.hpp huber_loss.hpp huber_loss_impl.hpp + hinge_embedding_loss.hpp + hinge_embedding_loss_impl.hpp + hinge_loss.hpp + hinge_loss_impl.hpp kl_divergence.hpp kl_divergence_impl.hpp - margin_ranking_loss.hpp - margin_ranking_loss_impl.hpp - mean_bias_error.hpp - mean_bias_error_impl.hpp l1_loss.hpp l1_loss_impl.hpp + log_cosh_loss.hpp + log_cosh_loss_impl.hpp + margin_ranking_loss.hpp + margin_ranking_loss_impl.hpp + mean_absolute_percentage_error.hpp + mean_absolute_percentage_error_impl.hpp + mean_bias_error.hpp + mean_bias_error_impl.hpp mean_squared_error.hpp mean_squared_error_impl.hpp mean_squared_logarithmic_error.hpp mean_squared_logarithmic_error_impl.hpp negative_log_likelihood.hpp negative_log_likelihood_impl.hpp - log_cosh_loss.hpp - log_cosh_loss_impl.hpp poisson_nll_loss.hpp poisson_nll_loss_impl.hpp reconstruction_loss.hpp @@ -35,12 +43,6 @@ set(SOURCES sigmoid_cross_entropy_error_impl.hpp soft_margin_loss.hpp soft_margin_loss_impl.hpp - hinge_embedding_loss.hpp - hinge_embedding_loss_impl.hpp - empty_loss.hpp - empty_loss_impl.hpp - mean_absolute_percentage_error.hpp - mean_absolute_percentage_error_impl.hpp triplet_margin_loss.hpp triplet_margin_loss_impl.hpp ) diff --git a/src/mlpack/methods/ann/loss_functions/hinge_loss.hpp b/src/mlpack/methods/ann/loss_functions/hinge_loss.hpp new file mode 100644 index 0000000000..60a2002782 --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/hinge_loss.hpp @@ -0,0 +1,105 @@ +/** + * @file methods/ann/loss_functions/hinge_loss.hpp + * @author Anush Kini + * + * Definition of the Hinge Loss Function. + * + * 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_ANN_LOSS_FUNCTION_HINGE_LOSS_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_HINGE_LOSS_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Computes the hinge loss between \f$y_true\f$ and \f$y_pred\f$. Expects + * \f$y_true\f$ to be either -1 or 1. If \f$y_true\f$ is either 0 or 1, a + * temporary conversion is made to calculate the loss. + * The hinge loss \f$l(y_true, y_pred)\f$ is defined as + * \f$l(y_true, y_pred) = max(0, 1 - y_true*y_pred)\f$. + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class HingeLoss +{ + public: + /** + * Create HingeLoss object. + * + * @param reduction Specifies the reduction to apply to the output. If false, + * 'mean' reduction is used, where sum of the output will be + * divided by the number of elements in the output. If + * true, 'sum' reduction is used and the output will be + * summed. It is set to true by default. + */ + HingeLoss(const bool reduction = true); + + /** + * Computes the Hinge loss function. + * + * @param prediction Prediction used for evaluating the specified loss + * function. + * @param target Target data to compare with. + */ + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); + + /** + * Ordinary feed backward pass of a neural network. + * + * @param prediction Prediction used for evaluating the specified loss + * function. + * @param target The target vector. + * @param loss The calculated error. + */ + template + void Backward(const PredictionType& prediction, + const TargetType& target, + LossType& loss); + + //! Get the output parameter. + OutputDataType& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the type of reduction used. + bool Reduction() const { return reduction; } + //! Modify the type of reduction used. + bool& Reduction() { return reduction; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! The boolean value that tells if reduction is sum or mean. + bool reduction; +}; // class HingeLoss + +} // namespace ann +} // namespace mlpack + +// include implementation +#include "hinge_loss_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp new file mode 100644 index 0000000000..6de5a553fa --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp @@ -0,0 +1,75 @@ +/** + * @file methods/ann/loss_functions/hinge_loss_impl.hpp + * @author Anush Kini + * + * Implementation of the Hinge loss function. + * + * 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_ANN_LOSS_FUNCTION_HINGE_LOSS_IMPL_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_HINGE_LOSS_IMPL_HPP + +// In case it hasn't yet been included. +#include "hinge_loss.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +HingeLoss::HingeLoss(const bool reduction): + reduction(reduction) +{ + // Nothing to do here. +} + +template +template +typename PredictionType::elem_type +HingeLoss::Forward( + const PredictionType& prediction, + const TargetType& target) +{ + TargetType temp = target - (target == 0); + TargetType temp_zeros(size(target), arma::fill::zeros); + + PredictionType loss = arma::max(temp_zeros, 1 - prediction % temp); + + typename PredictionType::elem_type lossSum = arma::accu(loss); + + if (reduction) + return lossSum; + + return lossSum / loss.n_elem; +} + +template +template +void HingeLoss::Backward( + const PredictionType& prediction, + const TargetType& target, + LossType& loss) +{ + TargetType temp = target - (target == 0); + loss = (prediction < (1 / temp)) % -temp; + + if (!reduction) + loss /= target.n_elem; +} + +template +template +void HingeLoss::serialize( + Archive& ar, + const uint32_t /* version */) +{ + ar(CEREAL_NVP(reduction)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp b/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp index 870a840f4f..1eace1d772 100644 --- a/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp @@ -35,11 +35,10 @@ NegativeLogLikelihood::Forward( ElemType output = 0; for (size_t i = 0; i < prediction.n_cols; ++i) { - size_t currentTarget = target(i) - 1; - Log::Assert(currentTarget < prediction.n_rows, + Log::Assert(target(i) >= 0 && target(i) < prediction.n_rows, "Target class out of range."); - output -= prediction(currentTarget, i); + output -= prediction(target(i), i); } return output; @@ -55,11 +54,10 @@ void NegativeLogLikelihood::Backward( loss = arma::zeros(prediction.n_rows, prediction.n_cols); for (size_t i = 0; i < prediction.n_cols; ++i) { - size_t currentTarget = target(i) - 1; - Log::Assert(currentTarget < prediction.n_rows, + Log::Assert(target(i) >= 0 && target(i) < prediction.n_rows, "Target class out of range."); - loss(currentTarget, i) = -1; + loss(target(i), i) = -1; } } diff --git a/src/mlpack/methods/ann/rnn_impl.hpp b/src/mlpack/methods/ann/rnn_impl.hpp index 2852dcceef..5077eb9896 100644 --- a/src/mlpack/methods/ann/rnn_impl.hpp +++ b/src/mlpack/methods/ann/rnn_impl.hpp @@ -87,10 +87,10 @@ RNN::RNN( targetSize(std::move(network.targetSize)), reset(std::move(network.reset)), single(std::move(network.single)), + network(std::move(network.network)), parameter(std::move(network.parameter)), numFunctions(std::move(network.numFunctions)), - deterministic(std::move(network.deterministic)), - network(std::move(network.network)) + deterministic(std::move(network.deterministic)) { // Nothing to do here. } diff --git a/src/mlpack/methods/cf/CMakeLists.txt b/src/mlpack/methods/cf/CMakeLists.txt index a7c552ae28..c59a4f12ed 100644 --- a/src/mlpack/methods/cf/CMakeLists.txt +++ b/src/mlpack/methods/cf/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES cf_impl.hpp cf_model.hpp cf_model_impl.hpp + cf_model.cpp svd_wrapper.hpp svd_wrapper_impl.hpp ) diff --git a/src/mlpack/methods/cf/cf_main.cpp b/src/mlpack/methods/cf/cf_main.cpp index 760ddf107d..0c8cd49539 100644 --- a/src/mlpack/methods/cf/cf_main.cpp +++ b/src/mlpack/methods/cf/cf_main.cpp @@ -194,279 +194,6 @@ PARAM_STRING_IN("interpolation", "Algorithm used for weight interpolation.", PARAM_STRING_IN("neighbor_search", "Algorithm used for neighbor search.", "S", "euclidean"); -template -void ComputeRecommendations(CFModel* cf, - const size_t numRecs, - arma::Mat& recommendations) -{ - // Reading users. - if (IO::HasParam("query")) - { - // User matrix. - arma::Mat users = - std::move(IO::GetParam>("query")); - if (users.n_rows > 1) - users = users.t(); - if (users.n_rows > 1) - Log::Fatal << "List of query users must be one-dimensional!" - << std::endl; - - Log::Info << "Generating recommendations for " - << users.n_elem << " users." - << endl; - - cf->GetRecommendations - (numRecs, recommendations, users.row(0).t()); - } - else - { - Log::Info << "Generating recommendations for all users." << endl; - cf->GetRecommendations - (numRecs, recommendations); - } -} - -template -void ComputeRecommendations(CFModel* cf, - const size_t numRecs, - arma::Mat& recommendations) -{ - // Verify the Interpolation algorithms. - RequireParamInSet("interpolation", { "average", - "regression", "similarity" }, true, "unknown interpolation algorithm"); - - // Taking Interpolation Alternatives - const string interpolationAlgorithm = IO::GetParam("interpolation"); - - // Determining the Interpolation Algorithm - if (interpolationAlgorithm == "average") - { - ComputeRecommendations - (cf, numRecs, recommendations); - } - else if (interpolationAlgorithm == "regression") - { - ComputeRecommendations - (cf, numRecs, recommendations); - } - else if (interpolationAlgorithm == "similarity") - { - ComputeRecommendations - (cf, numRecs, recommendations); - } -} - -void ComputeRecommendations(CFModel* cf, - const size_t numRecs, - arma::Mat& recommendations) -{ - // Verifying the Neighbor Search algorithms - RequireParamInSet("neighbor_search", { "cosine", - "euclidean", "pearson" }, true, "unknown neighbor search algorithm"); - - // Taking Neighbor Search alternatives - const string neighborSearchAlgorithm = IO::GetParam - ("neighbor_search"); - - - // Determining the Neighbor Search Algorithms - if (neighborSearchAlgorithm == "cosine") - { - ComputeRecommendations(cf, numRecs, recommendations); - } - else if (neighborSearchAlgorithm == "euclidean") - { - ComputeRecommendations(cf, numRecs, recommendations); - } - else if (neighborSearchAlgorithm == "pearson") - { - ComputeRecommendations(cf, numRecs, recommendations); - } -} - -template -void ComputeRMSE(CFModel* cf) -{ - // Now, compute each test point. - arma::mat testData = std::move(IO::GetParam("test")); - - // Assemble the combination matrix to get RMSE value. - arma::Mat combinations(2, testData.n_cols); - for (size_t i = 0; i < testData.n_cols; ++i) - { - combinations(0, i) = size_t(testData(0, i)); - combinations(1, i) = size_t(testData(1, i)); - } - - // Now compute the RMSE. - arma::vec predictions; - cf->Predict - (combinations, predictions); - - // Compute the root of the sum of the squared errors, divide by the number of - // points to get the RMSE. It turns out this is just the L2-norm divided by - // the square root of the number of points, if we interpret the predictions - // and the true values as vectors. - const double rmse = arma::norm(predictions - testData.row(2).t(), 2) / - std::sqrt((double) testData.n_cols); - - Log::Info << "RMSE is " << rmse << "." << endl; -} - -template -void ComputeRMSE(CFModel* cf) -{ - // Verifying the Interpolation algorithms - RequireParamInSet("interpolation", { "average", - "regression", "similarity" }, true, "unknown interpolation algorithm"); - - // Taking Interpolation Alternatives - const string interpolationAlgorithm = IO::GetParam("interpolation"); - - if (interpolationAlgorithm == "average") - { - ComputeRMSE(cf); - } - else if (interpolationAlgorithm == "regression") - { - ComputeRMSE(cf); - } - else if (interpolationAlgorithm == "similarity") - { - ComputeRMSE(cf); - } -} - -void ComputeRMSE(CFModel* cf) -{ - // Verifying the Neighbor Search algorithms - RequireParamInSet("neighbor_search", { "cosine", - "euclidean", "pearson" }, true, "unknown neighbor search algorithm"); - - // Taking Neighbor Search alternatives - const string neighborSearchAlgorithm = IO::GetParam - ("neighbor_search"); - - if (neighborSearchAlgorithm == "cosine") - { - ComputeRMSE(cf); - } - else if (neighborSearchAlgorithm == "euclidean") - { - ComputeRMSE(cf); - } - else if (neighborSearchAlgorithm == "pearson") - { - ComputeRMSE(cf); - } -} - -void PerformAction(CFModel* c) -{ - if (IO::HasParam("query") || IO::HasParam("all_user_recommendations")) - { - // Get parameters for generating recommendations. - const size_t numRecs = (size_t) IO::GetParam("recommendations"); - - // Get the recommendations. - arma::Mat recommendations; - ComputeRecommendations(c, numRecs, recommendations); - - // Save the output. - IO::GetParam>("output") = recommendations; - } - - if (IO::HasParam("test")) - ComputeRMSE(c); - - IO::GetParam("output_model") = c; -} - -template -void PerformAction(arma::mat& dataset, - const size_t rank, - const size_t maxIterations, - const double minResidue) -{ - const size_t neighborhood = (size_t) IO::GetParam("neighborhood"); - - // Make sure the normalization strategy is valid. - RequireParamInSet("normalization", { "overall_mean", "item_mean", - "user_mean", "z_score", "none" }, true, "unknown normalization type"); - - CFModel* c = new CFModel(); - - const string normalizationType = IO::GetParam("normalization"); - - c->template Train(dataset, neighborhood, rank, - maxIterations, minResidue, IO::HasParam("iteration_only_termination"), - normalizationType); - - try - { - PerformAction(c); - } - catch (std::exception& e) - { - // Clean the memory before throwing completely. - delete c; - throw; - } -} - -void AssembleFactorizerType(const std::string& algorithm, - arma::mat& dataset, - const size_t rank) -{ - const size_t maxIterations = (size_t) IO::GetParam("max_iterations"); - const double minResidue = IO::GetParam("min_residue"); - - if (algorithm == "NMF") - { - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "BatchSVD") - { - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "SVDIncompleteIncremental") - { - PerformAction(dataset, rank, maxIterations, - minResidue); - } - else if (algorithm == "SVDCompleteIncremental") - { - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "RegSVD") - { - ReportIgnoredParam("min_residue", "Regularized SVD terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "RandSVD") - { - ReportIgnoredParam("min_residue", "Randomized SVD terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, - minResidue); - } - else if (algorithm == "BiasSVD") - { - ReportIgnoredParam("min_residue", "Bias SVD terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "SVDPP") - { - ReportIgnoredParam("min_residue", "SVD++ terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, minResidue); - } -} - static void mlpackMain() { if (IO::GetParam("seed") == 0) @@ -496,6 +223,7 @@ static void mlpackMain() "recommendations must be positive"); // Either load from a model, or train a model. + CFModel* cf; if (IO::HasParam("training")) { // Train a model. @@ -523,23 +251,179 @@ static void mlpackMain() // Get parameters. const size_t rank = (size_t) IO::GetParam("rank"); + cf = new CFModel(); + // Perform decomposition to prepare for recommendations. Log::Info << "Performing CF matrix decomposition on dataset..." << endl; const string algo = IO::GetParam("algorithm"); + if (algo == "NMF") + { + cf->DecompositionType() = CFModel::NMF; + } + else if (algo == "BatchSVD") + { + cf->DecompositionType() = CFModel::BATCH_SVD; + } + else if (algo == "SVDIncompleteIncremental") + { + cf->DecompositionType() = CFModel::SVD_INCOMPLETE; + } + else if (algo == "SVDCompleteIncremental") + { + cf->DecompositionType() = CFModel::SVD_COMPLETE; + } + else if (algo == "RegSVD") + { + ReportIgnoredParam("min_residue", "Regularized SVD terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::REG_SVD; + } + else if (algo == "RandSVD") + { + ReportIgnoredParam("min_residue", "Randomized SVD terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::RANDOMIZED_SVD; + } + else if (algo == "BiasSVD") + { + ReportIgnoredParam("min_residue", "Bias SVD terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::BIAS_SVD; + } + else if (algo == "SVDPP") + { + ReportIgnoredParam("min_residue", "SVD++ terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::SVD_PLUS_PLUS; + } // Perform the factorization and do whatever the user wanted. - AssembleFactorizerType(algo, dataset, rank); + const size_t neighborhood = (size_t) IO::GetParam("neighborhood"); + + // Make sure the normalization strategy is valid. + RequireParamInSet("normalization", { "overall_mean", "item_mean", + "user_mean", "z_score", "none" }, true, "unknown normalization type"); + + const string normalizationType = IO::GetParam("normalization"); + if (normalizationType == "none") + cf->NormalizationType() = CFModel::NO_NORMALIZATION; + else if (normalizationType == "item_mean") + cf->NormalizationType() = CFModel::ITEM_MEAN_NORMALIZATION; + else if (normalizationType == "user_mean") + cf->NormalizationType() = CFModel::USER_MEAN_NORMALIZATION; + else if (normalizationType == "overall_mean") + cf->NormalizationType() = CFModel::OVERALL_MEAN_NORMALIZATION; + else if (normalizationType == "z_score") + cf->NormalizationType() = CFModel::Z_SCORE_NORMALIZATION; + + cf->Train(dataset, + neighborhood, + rank, + size_t(IO::GetParam("max_iterations")), + IO::GetParam("min_residue"), + IO::HasParam("iteration_only_termination")); } else { // Load from a model after validating parameters. - RequireAtLeastOnePassed({ "query", "all_user_recommendations", - "test" }, true); + RequireAtLeastOnePassed({ "query", "all_user_recommendations", "test" }, + true); // Load an input model. - CFModel* c = std::move(IO::GetParam("input_model")); - - PerformAction(c); + cf = std::move(IO::GetParam("input_model")); } + + // Get the types of the neighbor search method and the interpolation. (These + // may or may not be used.) + NeighborSearchTypes nsType; + RequireParamInSet("neighbor_search", { "cosine", + "euclidean", "pearson" }, true, "unknown neighbor search algorithm"); + if (IO::GetParam("neighbor_search") == "cosine") + nsType = COSINE_SEARCH; + else if (IO::GetParam("neighbor_search") == "euclidean") + nsType = EUCLIDEAN_SEARCH; + else // if (IO::GetParam("neighbor_search") == "pearson") + nsType = PEARSON_SEARCH; + + InterpolationTypes interpolationType; + RequireParamInSet("interpolation", { "average", + "regression", "similarity" }, true, "unknown interpolation algorithm"); + if (IO::GetParam("interpolation") == "average") + interpolationType = AVERAGE_INTERPOLATION; + else if (IO::GetParam("interpolation") == "regression") + interpolationType = REGRESSION_INTERPOLATION; + else // if (IO::GetParam("interpolation") == "similarity") + interpolationType = SIMILARITY_INTERPOLATION; + + if (IO::HasParam("query") || IO::HasParam("all_user_recommendations")) + { + // Get parameters for generating recommendations. + const size_t numRecs = (size_t) IO::GetParam("recommendations"); + + // Get the recommendations. + arma::Mat recommendations; + + // Reading users. + if (IO::HasParam("query")) + { + // User matrix. + arma::Mat users = + std::move(IO::GetParam>("query")); + if (users.n_rows > 1) + { + users = users.t(); + } + + if (users.n_rows > 1) + { + Log::Fatal << "List of query users must be one-dimensional!" + << std::endl; + } + + Log::Info << "Generating recommendations for " << users.n_elem + << " users." << endl; + + cf->GetRecommendations(nsType, interpolationType, numRecs, + recommendations, users.row(0).t()); + } + else + { + Log::Info << "Generating recommendations for all users." << endl; + cf->GetRecommendations(nsType, interpolationType, numRecs, + recommendations); + } + + // Save the output. + IO::GetParam>("output") = recommendations; + } + + if (IO::HasParam("test")) + { + // Now, compute each test point. + arma::mat testData = std::move(IO::GetParam("test")); + + // Assemble the combination matrix to get RMSE value. + arma::Mat combinations(2, testData.n_cols); + for (size_t i = 0; i < testData.n_cols; ++i) + { + combinations(0, i) = size_t(testData(0, i)); + combinations(1, i) = size_t(testData(1, i)); + } + + // Now compute the RMSE. + arma::vec predictions; + cf->Predict(nsType, interpolationType, combinations, predictions); + + // Compute the root of the sum of the squared errors, divide by the number + // of points to get the RMSE. It turns out this is just the L2-norm divided + // by the square root of the number of points, if we interpret the + // predictions and the true values as vectors. + const double rmse = arma::norm(predictions - testData.row(2).t(), 2) / + std::sqrt((double) testData.n_cols); + + Log::Info << "RMSE is " << rmse << "." << endl; + } + + IO::GetParam("output_model") = cf; } diff --git a/src/mlpack/methods/cf/cf_model.cpp b/src/mlpack/methods/cf/cf_model.cpp new file mode 100644 index 0000000000..226edcf1be --- /dev/null +++ b/src/mlpack/methods/cf/cf_model.cpp @@ -0,0 +1,207 @@ +/** + * @file methods/cf/cf_model_impl.hpp + * @author Wenhao Huang + * + * A serializable CF model, used by the main program. + * + * 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 "cf_model.hpp" + +namespace mlpack { +namespace cf { + +CFModel::CFModel() : + decompositionType(NMF), + normalizationType(NO_NORMALIZATION), + cf(NULL) +{ + // Nothing else to do. +} + +CFModel::CFModel(const CFModel& other) : + decompositionType(other.decompositionType), + normalizationType(other.normalizationType), + cf(other.cf->Clone()) +{ + // Nothing else to do. +} + +CFModel::CFModel(CFModel&& other) : + decompositionType(other.decompositionType), + normalizationType(other.normalizationType), + cf(std::move(other.cf)) +{ + // Reset properties of the other one. + other.decompositionType = NMF; + other.normalizationType = NO_NORMALIZATION; +} + +CFModel& CFModel::operator=(const CFModel& other) +{ + if (this != &other) + { + decompositionType = other.decompositionType; + normalizationType = other.normalizationType; + cf = other.cf->Clone(); + } + + return *this; +} + +CFModel& CFModel::operator=(CFModel&& other) +{ + if (this != &other) + { + decompositionType = other.decompositionType; + normalizationType = other.normalizationType; + cf = std::move(other.cf); + + // Reset the other object. + other.decompositionType = NMF; + other.normalizationType = NO_NORMALIZATION; + } + + return *this; +} + +CFModel::~CFModel() +{ + delete cf; +} + +template +CFWrapperBase* TrainHelper(const DecompositionPolicy& decomposition, + const CFModel::NormalizationTypes normalizationType, + const arma::mat& data, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const double minResidue, + const bool mit) +{ + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::ITEM_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::USER_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::OVERALL_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::Z_SCORE_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + } + + // This shouldn't ever happen. + return NULL; +} + +void CFModel::Train(const arma::mat& data, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const double minResidue, + const bool mit) +{ + // Delete the current CFType object, if there is one. + delete cf; + + switch (decompositionType) + { + case NMF: + cf = TrainHelper(NMFPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case BATCH_SVD: + cf = TrainHelper(BatchSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case RANDOMIZED_SVD: + cf = TrainHelper(RandomizedSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case REG_SVD: + cf = TrainHelper(RegSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_COMPLETE: + cf = TrainHelper(SVDCompletePolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_INCOMPLETE: + cf = TrainHelper(SVDIncompletePolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case BIAS_SVD: + cf = TrainHelper(BiasSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_PLUS_PLUS: + cf = TrainHelper(SVDPlusPlusPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + } +} + +//! Make predictions. +void CFModel::Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) +{ + cf->Predict(nsType, interpolationType, combinations, predictions); +} + +//! Compute recommendations for queried users. +void CFModel::GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) +{ + cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations, + users); +} + +//! Compute recommendations for all users. +void CFModel::GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) +{ + cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations); +} + +} // namespace cf +} // namespace mlpack diff --git a/src/mlpack/methods/cf/cf_model.hpp b/src/mlpack/methods/cf/cf_model.hpp index 93ff371a02..354f8b51b5 100644 --- a/src/mlpack/methods/cf/cf_model.hpp +++ b/src/mlpack/methods/cf/cf_model.hpp @@ -14,105 +14,146 @@ #define MLPACK_METHODS_CF_CF_MODEL_HPP #include -#include #include "cf.hpp" -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - namespace mlpack { namespace cf { /** - * DeleteVisitor deletes the CFType<> object which is pointed to by the - * variable cf in class CFModel. + * NeighborSearchTypes contains the set of NeighborSearchPolicy classes that are + * usable by CFModel at prediction time. */ -class DeleteVisitor : public boost::static_visitor +enum NeighborSearchTypes { - public: - //! Delete CFType object. - template - void operator()(CFType* c) const; + COSINE_SEARCH, + EUCLIDEAN_SEARCH, + PEARSON_SEARCH }; /** - * GetValueVisitor returns the pointer which points to the CFType object. + * InterpolationTypes contains the set of InterpolationPolicy classes that are + * usable by CFModel at prediction time. */ -class GetValueVisitor : public boost::static_visitor +enum InterpolationTypes { - public: - //! Return stored pointer as void* type. - template - void* operator()(CFType* c) const; + AVERAGE_INTERPOLATION, + REGRESSION_INTERPOLATION, + SIMILARITY_INTERPOLATION }; /** - * PredictVisitor uses the CFType object to make predictions on the given - * combinations of users and items. + * The CFWrapperBase class provides a unified interface that can be used by the + * CFModel class to interact with all different CF types at runtime. All CF + * wrapper types inherit from this base class. */ -template -class PredictVisitor : public boost::static_visitor +class CFWrapperBase { - private: - //! User/item combinations to predict. - const arma::Mat& combinations; - //! Predicted ratings for each user/item combination. - arma::vec& predictions; - public: - //! Predict ratings for each user-item combination. - template - void operator()(CFType* c) const; + //! Create the object. The base class has nothing to hold. + CFWrapperBase() { } - //! Visitor constructor. - PredictVisitor(const arma::Mat& combinations, - arma::vec& predictions); + //! Make a copy of the object. + virtual CFWrapperBase* Clone() const = 0; + + //! Delete the object. + virtual ~CFWrapperBase() { } + + //! Compute predictions for users. + virtual void Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) = 0; + + //! Compute recommendations for all users. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) = 0; + + //! Compute recommendations. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) = 0; }; /** - * RecommendationVisitor uses the CFType object to get recommendations for the - * given users. + * The CFWrapper class wraps the functionality of all CF types. If special + * handling is needed for a future CF type, this class can be extended. */ -template -class RecommendationVisitor : public boost::static_visitor +template +class CFWrapper : public CFWrapperBase { - private: - //! Number of Recommendations. - const size_t numRecs; - //! Recommendations matrix to save recommendations. - arma::Mat& recommendations; - //! Users for which recommendations are to be generated. - const arma::Col& users; - //! Whether users are given. - const bool usersGiven; + protected: + typedef CFType CFModelType; public: - //! Visitor constructor. - RecommendationVisitor(const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users, - const bool usersGiven); + //! Create the CFWrapper object, using default parameters to initialize the + //! held CF object. + CFWrapper() { } - //! Generates the given number of recommendations. - template - void operator()(CFType* c) const; + //! Create the CFWrapper object, initializing the held CF object. + CFWrapper(const arma::mat& data, + const DecompositionPolicy& decomposition, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const size_t minResidue, + const bool mit) : + cf(data, + decomposition, + numUsersForSimilarity, + rank, + maxIterations, + minResidue, + mit) + { + // Nothing else to do. + } + + //! Clone the CFWrapper object. This handles polymorphism correctly. + virtual CFWrapper* Clone() const { return new CFWrapper(*this); } + + //! Destroy the CFWrapper object. + virtual ~CFWrapper() { } + + //! Get the CFType object. + CFModelType& CF() { return cf; } + + //! Compute predictions for users. + virtual void Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions); + + //! Compute recommendations for all users. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations); + + //! Compute recommendations. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users); + + //! Serialize the model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(cf)); + } + + protected: + //! This is the CF object that we are wrapping. + CFModelType cf; }; /** @@ -120,98 +161,110 @@ class RecommendationVisitor : public boost::static_visitor */ class CFModel { + public: + enum DecompositionTypes + { + NMF, + BATCH_SVD, + RANDOMIZED_SVD, + REG_SVD, + SVD_COMPLETE, + SVD_INCOMPLETE, + BIAS_SVD, + SVD_PLUS_PLUS + }; + + enum NormalizationTypes + { + NO_NORMALIZATION, + ITEM_MEAN_NORMALIZATION, + USER_MEAN_NORMALIZATION, + OVERALL_MEAN_NORMALIZATION, + Z_SCORE_NORMALIZATION + }; + private: + //! The current decomposition policy type. + DecompositionTypes decompositionType; + //! The current normalization policy type. + NormalizationTypes normalizationType; + /** * cf holds an instance of the CFType class for the current * decompositionPolicy and normalizationType. It is initialized every time - * Train() is executed. We access to the contained value through the visitor - * classes defined above. + * Train() is executed. */ - boost::variant*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*> cf; + CFWrapperBase* cf; public: //! Create an empty CF model. - CFModel() { } + CFModel(); + + //! Create a CF model by copying the given model. + CFModel(const CFModel& other); + + //! Create a CF model by taking ownership of the data of the other model. + CFModel(CFModel&& other); + + //! Make this CF model a copy of the other model. + CFModel& operator=(const CFModel& other); + + //! Make this CF model take ownership of the data of the other model. + CFModel& operator=(CFModel&& other); //! Clean up memory. ~CFModel(); - //! Get the pointer to CFType<> object. - template - const CFType* CFPtr() const; + //! Get the CFWrapperBase object. (Be careful!) + CFWrapperBase* CF() const { return cf; } + + //! Get the decomposition type. + const DecompositionTypes& DecompositionType() const + { + return decompositionType; + } + //! Set the decomposition type. + DecompositionTypes& DecompositionType() + { + return decompositionType; + } + + //! Get the normalization type. + const NormalizationTypes& NormalizationType() const + { + return normalizationType; + } + //! Set the normalization type. + NormalizationTypes& NormalizationType() + { + return normalizationType; + } //! Train the model. - template - void Train(const MatType& data, + void Train(const arma::mat& data, const size_t numUsersForSimilarity, const size_t rank, const size_t maxIterations, const double minResidue, - const bool mit, - const std::string& normalizationType = "none"); + const bool mit); //! Make predictions. - template - void Predict(const arma::Mat& combinations, + void Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, arma::vec& predictions); //! Compute recommendations for query users. - template - void GetRecommendations(const size_t numRecs, + void GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, arma::Mat& recommendations, const arma::Col& users); //! Compute recommendations for all users. - template - void GetRecommendations(const size_t numRecs, + void GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, arma::Mat& recommendations); //! Serialize the model. diff --git a/src/mlpack/methods/cf/cf_model_impl.hpp b/src/mlpack/methods/cf/cf_model_impl.hpp index 6df3491e59..fa2634a823 100644 --- a/src/mlpack/methods/cf/cf_model_impl.hpp +++ b/src/mlpack/methods/cf/cf_model_impl.hpp @@ -14,204 +14,364 @@ #include "cf_model.hpp" -#include -#include -#include -#include -#include +#include "interpolation_policies/average_interpolation.hpp" +#include "interpolation_policies/regression_interpolation.hpp" +#include "interpolation_policies/similarity_interpolation.hpp" -using namespace mlpack::cf; +#include "neighbor_search_policies/cosine_search.hpp" +#include "neighbor_search_policies/lmetric_search.hpp" +#include "neighbor_search_policies/pearson_search.hpp" -template -void DeleteVisitor:: -operator()(CFType* c) const +#include "decomposition_policies/batch_svd_method.hpp" +#include "decomposition_policies/bias_svd_method.hpp" +#include "decomposition_policies/nmf_method.hpp" +#include "decomposition_policies/randomized_svd_method.hpp" +#include "decomposition_policies/regularized_svd_method.hpp" +#include "decomposition_policies/svd_complete_method.hpp" +#include "decomposition_policies/svd_incomplete_method.hpp" +#include "decomposition_policies/svdplusplus_method.hpp" + +#include "normalization/no_normalization.hpp" +#include "normalization/overall_mean_normalization.hpp" +#include "normalization/user_mean_normalization.hpp" +#include "normalization/item_mean_normalization.hpp" +#include "normalization/z_score_normalization.hpp" + +namespace mlpack { +namespace cf { + +template +void PredictHelper(CFType& cf, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) { - if (c) - delete c; -} - -template -void* GetValueVisitor:: -operator()(CFType* c) const -{ - if (!c) - throw std::runtime_error("no cf model initialized"); - - return (void*) c; -} - -template -PredictVisitor::PredictVisitor( - const arma::Mat& combinations, - arma::vec& predictions) : - combinations(combinations), - predictions(predictions) -{ } - -template -template -void PredictVisitor - ::operator()(CFType* c) const -{ - if (!c) + switch (interpolationType) { - throw std::runtime_error("no cf model initialized"); - return; - } + case AVERAGE_INTERPOLATION: + cf.template Predict(combinations, predictions); + break; - c->template Predict(combinations, predictions); -} + case REGRESSION_INTERPOLATION: + cf.template Predict(combinations, predictions); + break; -template -RecommendationVisitor - ::RecommendationVisitor( - const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users, - const bool usersGiven) : - numRecs(numRecs), - recommendations(recommendations), - users(users), - usersGiven(usersGiven) -{ } - -template -template -void RecommendationVisitor - ::operator()(CFType* c) const -{ - if (!c) - { - throw std::runtime_error("no cf model initialized"); - return; - } - - if (usersGiven) - c->template GetRecommendations - (numRecs, recommendations, users); - else - c->template GetRecommendations - (numRecs, recommendations); -} - -CFModel::~CFModel() -{ - boost::apply_visitor(DeleteVisitor(), cf); -} - -template -void CFModel::Train(const MatType& data, - const size_t numUsersForSimilarity, - const size_t rank, - const size_t maxIterations, - const double minResidue, - const bool mit, - const std::string& normalization) -{ - // Delete the current CFType object, if there is one. - boost::apply_visitor(DeleteVisitor(), cf); - - // Instantiate a new CFType object. - DecompositionPolicy decomposition; - if (normalization == "overall_mean") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "item_mean") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "user_mean") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "z_score") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "none") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else - { - throw std::runtime_error("Unsupported normalization algorithm." - " It should be one of none, overall_mean, " - "item_mean, user_mean or z_score"); + case SIMILARITY_INTERPOLATION: + cf.template Predict(combinations, predictions); + break; } } //! Make predictions. -template -void CFModel::Predict(const arma::Mat& combinations, - arma::vec& predictions) +template +void CFWrapper::Predict( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) { - PredictVisitor - predict(combinations, predictions); - boost::apply_visitor(predict, cf); + switch (nsType) + { + case COSINE_SEARCH: + PredictHelper(cf, interpolationType, combinations, + predictions); + break; + + case EUCLIDEAN_SEARCH: + PredictHelper(cf, interpolationType, combinations, + predictions); + break; + + case PEARSON_SEARCH: + PredictHelper(cf, interpolationType, combinations, + predictions); + break; + } +} + +template +void GetRecommendationsHelper( + CFType& cf, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) +{ + switch (interpolationType) + { + case AVERAGE_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations, users); + break; + + case REGRESSION_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations, users); + break; + + case SIMILARITY_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations, users); + break; + } } //! Compute recommendations for queried users. -template -void CFModel::GetRecommendations(const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users) +template +void CFWrapper::GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) { - RecommendationVisitor - recommendation(numRecs, recommendations, users, true); - boost::apply_visitor(recommendation, cf); + switch (nsType) + { + case COSINE_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations, users); + break; + + case EUCLIDEAN_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations, users); + break; + + case PEARSON_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations, users); + break; + } +} + +template +void GetRecommendationsHelper( + CFType& cf, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) +{ + switch (interpolationType) + { + case AVERAGE_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations); + break; + + case REGRESSION_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations); + break; + + case SIMILARITY_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations); + break; + } } //! Compute recommendations for all users. -template -void CFModel::GetRecommendations(const size_t numRecs, - arma::Mat& recommendations) +template +void CFWrapper::GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) { - arma::Col users; - RecommendationVisitor - recommendation(numRecs, recommendations, users, false); - boost::apply_visitor(recommendation, cf); + switch (nsType) + { + case COSINE_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations); + break; + + case EUCLIDEAN_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations); + break; + + case PEARSON_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations); + break; + } } -template -const CFType* CFModel::CFPtr() const +template +CFWrapperBase* InitializeModelHelper( + CFModel::NormalizationTypes normalizationType) { - void* pointer = boost::apply_visitor(GetValueVisitor(), cf); - return (CFType*) pointer; + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + return new CFWrapper(); + + case CFModel::ITEM_MEAN_NORMALIZATION: + return new CFWrapper(); + + case CFModel::USER_MEAN_NORMALIZATION: + return new CFWrapper(); + + case CFModel::OVERALL_MEAN_NORMALIZATION: + return new CFWrapper(); + + case CFModel::Z_SCORE_NORMALIZATION: + return new CFWrapper(); + } + + // This shouldn't ever happen. + return NULL; +} + +inline CFWrapperBase* InitializeModel( + CFModel::DecompositionTypes decompositionType, + CFModel::NormalizationTypes normalizationType) +{ + switch (decompositionType) + { + case CFModel::NMF: + return InitializeModelHelper(normalizationType); + + case CFModel::BATCH_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::RANDOMIZED_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::REG_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::SVD_COMPLETE: + return InitializeModelHelper(normalizationType); + + case CFModel::SVD_INCOMPLETE: + return InitializeModelHelper(normalizationType); + + case CFModel::BIAS_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::SVD_PLUS_PLUS: + return InitializeModelHelper(normalizationType); + } + + // This shouldn't ever happen. + return NULL; +}; + +template +void SerializeHelper(Archive& ar, + CFWrapperBase* cf, + CFModel::NormalizationTypes normalizationType) +{ + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::ITEM_MEAN_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::USER_MEAN_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::OVERALL_MEAN_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::Z_SCORE_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + } } template void CFModel::serialize(Archive& ar, const uint32_t /* version */) { + ar(CEREAL_NVP(decompositionType)); + ar(CEREAL_NVP(normalizationType)); + // This should never happen, but just in case, be clean with memory. if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), cf); + { + delete cf; + cf = InitializeModel(decompositionType, normalizationType); + } - ar(CEREAL_VARIANT_POINTER(cf)); + // Avoid polymorphic serialization by determining the type directly. + switch (decompositionType) + { + case NMF: + SerializeHelper(ar, cf, normalizationType); + break; + + case BATCH_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case RANDOMIZED_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case REG_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case SVD_COMPLETE: + SerializeHelper(ar, cf, normalizationType); + break; + + case SVD_INCOMPLETE: + SerializeHelper(ar, cf, normalizationType); + break; + + case BIAS_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case SVD_PLUS_PLUS: + SerializeHelper(ar, cf, normalizationType); + break; + } } +} // namespace cf +} // namespace mlpack + #endif diff --git a/src/mlpack/methods/decision_tree/decision_tree_impl.hpp b/src/mlpack/methods/decision_tree/decision_tree_impl.hpp index 1646608d0b..b99075f5b2 100644 --- a/src/mlpack/methods/decision_tree/decision_tree_impl.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree_impl.hpp @@ -469,14 +469,7 @@ double DecisionTree::type; using TrueLabelsType = typename std::decay::type; @@ -518,14 +511,7 @@ double DecisionTree::type; using TrueLabelsType = typename std::decay::type; @@ -573,14 +559,7 @@ double DecisionTree::type>::value>*) { // Sanity check on data. - if (data.n_cols != labels.n_elem) - { - std::ostringstream oss; - oss << "DecisionTree::Train(): number of points (" << data.n_cols << ") " - << "does not match number of labels (" << labels.n_elem << ")!" - << std::endl; - throw std::invalid_argument(oss.str()); - } + util::CheckSameSizes(data, labels, "DecisionTree::Train()"); using TrueMatType = typename std::decay::type; using TrueLabelsType = typename std::decay::type; @@ -628,14 +607,7 @@ double DecisionTree::type>::value>*) { // Sanity check on data. - if (data.n_cols != labels.n_elem) - { - std::ostringstream oss; - oss << "DecisionTree::Train(): number of points (" << data.n_cols << ") " - << "does not match number of labels (" << labels.n_elem << ")!" - << std::endl; - throw std::invalid_argument(oss.str()); - } + util::CheckSameSizes(data, labels, "DecisionTree::Train()"); using TrueMatType = typename std::decay::type; using TrueLabelsType = typename std::decay::type; diff --git a/src/mlpack/methods/fastmks/fastmks.hpp b/src/mlpack/methods/fastmks/fastmks.hpp index 93d234d541..ea2057b0b9 100644 --- a/src/mlpack/methods/fastmks/fastmks.hpp +++ b/src/mlpack/methods/fastmks/fastmks.hpp @@ -163,6 +163,11 @@ class FastMKS */ FastMKS& operator=(const FastMKS& other); + /** + * Move assignment operator. + */ + FastMKS& operator=(FastMKS&& other); + //! Destructor for the FastMKS object. ~FastMKS(); diff --git a/src/mlpack/methods/fastmks/fastmks_impl.hpp b/src/mlpack/methods/fastmks/fastmks_impl.hpp index 660617fdb0..3b2d12eaae 100644 --- a/src/mlpack/methods/fastmks/fastmks_impl.hpp +++ b/src/mlpack/methods/fastmks/fastmks_impl.hpp @@ -250,6 +250,35 @@ FastMKS::operator=(const FastMKS& other) naive = other.naive; } +template class TreeType> +FastMKS& +FastMKS::operator=(FastMKS&& other) +{ + if (this != &other) + { + referenceSet = other.referenceSet; + referenceTree = other.referenceTree; + treeOwner = other.treeOwner; + setOwner = other.setOwner; + singleMode = other.singleMode; + naive = other.naive; + metric = std::move(other.metric); + + // Clear information from the other. + other.referenceSet = nullptr; + other.referenceTree = nullptr; + other.treeOwner = false; + other.setOwner = false; + other.singleMode = false; + other.naive = false; + } + return *this; +} + template(*other.linear); - if (other.polynomial) - polynomial = new FastMKS(*other.polynomial); - if (other.cosine) - cosine = new FastMKS(*other.cosine); - if (other.gaussian) - gaussian = new FastMKS(*other.gaussian); - if (other.epan) - epan = new FastMKS(*other.epan); - if (other.triangular) - triangular = new FastMKS(*other.triangular); - if (other.hyptan) - hyptan = new FastMKS(*other.hyptan); + kernelType = other.kernelType; + if (other.linear) + linear = new FastMKS(*other.linear); + if (other.polynomial) + polynomial = new FastMKS(*other.polynomial); + if (other.cosine) + cosine = new FastMKS(*other.cosine); + if (other.gaussian) + gaussian = new FastMKS(*other.gaussian); + if (other.epan) + epan = new FastMKS(*other.epan); + if (other.triangular) + triangular = new FastMKS(*other.triangular); + if (other.hyptan) + hyptan = new FastMKS(*other.hyptan); + } + return *this; +} +FastMKSModel& FastMKSModel::operator=(FastMKSModel&& other) +{ + if (this != &other) + { + kernelType = other.kernelType; + linear = other.linear; + polynomial = other.polynomial; + cosine = other.cosine; + gaussian = other.gaussian; + epan = other.epan; + triangular = other.triangular; + hyptan = other.hyptan; + + // Clear other object. + other.kernelType = KernelTypes::LINEAR_KERNEL; + other.linear = nullptr; + other.polynomial = nullptr; + other.cosine = nullptr; + other.gaussian = nullptr; + other.epan = nullptr; + other.triangular = nullptr; + other.hyptan = nullptr; + } return *this; } diff --git a/src/mlpack/methods/fastmks/fastmks_model.hpp b/src/mlpack/methods/fastmks/fastmks_model.hpp index e84eee0c28..0b7568c641 100644 --- a/src/mlpack/methods/fastmks/fastmks_model.hpp +++ b/src/mlpack/methods/fastmks/fastmks_model.hpp @@ -60,6 +60,9 @@ class FastMKSModel //! Copy assignment operator. FastMKSModel& operator=(const FastMKSModel& other); + //! Move assignment operator. + FastMKSModel& operator=(FastMKSModel&& other); + /** * Clean memory. */ diff --git a/src/mlpack/methods/gmm/em_fit_impl.hpp b/src/mlpack/methods/gmm/em_fit_impl.hpp index 6b4168bf2d..c8d8b6ca9e 100644 --- a/src/mlpack/methods/gmm/em_fit_impl.hpp +++ b/src/mlpack/methods/gmm/em_fit_impl.hpp @@ -156,7 +156,7 @@ Estimate(const arma::mat& observations, // Calculate the new values for omega using the updated conditional // probabilities. - weights = arma::exp(probRowSums - log(observations.n_cols)); + weights = arma::exp(probRowSums - std::log(observations.n_cols)); // Update values of l; calculate new log-likelihood. lOld = l; diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index d3d1d72e61..09ac8113fe 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -193,7 +193,7 @@ double HMM::Train(const std::vector& dataSeq) // Normalize the new initial probabilities. if (dataSeq.size() > 1) - logInitial = newLogInitial - log(dataSeq.size()); + logInitial = newLogInitial - std::log(dataSeq.size()); else logInitial = newLogInitial; diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 41a7fd406b..7665397bdc 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -129,6 +129,26 @@ class HMMModel return *this; } + //! Move assignment operator. + HMMModel& operator=(HMMModel&& other) + { + if (this != &other) + { + type = other.type; + discreteHMM = other.discreteHMM; + gaussianHMM = other.gaussianHMM; + gmmHMM = other.gmmHMM; + diagGMMHMM = other.diagGMMHMM; + + other.type = HMMType::DiscreteHMM; + other.discreteHMM = new HMM(); + other.gaussianHMM = nullptr; + other.gmmHMM = nullptr; + other.diagGMMHMM = nullptr; + } + return *this; + } + //! Clean memory. ~HMMModel() { diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp index 488048f4e6..b58d97a423 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp @@ -155,6 +155,27 @@ class HoeffdingTree */ HoeffdingTree(const HoeffdingTree& other); + /** + * Move another tree. + * + * @param other Tree to move. + */ + HoeffdingTree(HoeffdingTree&& other); + + /** + * Copy assignment operator. + * + * @param other Tree to copy. + */ + HoeffdingTree& operator=(const HoeffdingTree& other); + + /** + * Move assignment operator. + * + * @param other Tree to move. + */ + HoeffdingTree& operator=(HoeffdingTree&& other); + /** * Clean up memory. */ diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp index e6172f8324..f79b0eb027 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp @@ -224,6 +224,138 @@ HoeffdingTree:: } } +// Move constructor. +template class NumericSplitType, + template class CategoricalSplitType> +HoeffdingTree:: + HoeffdingTree(HoeffdingTree&& other) : + numericSplits(std::move(other.numericSplits)), + categoricalSplits(std::move(other.categoricalSplits)), + dimensionMappings(other.dimensionMappings), + ownsMappings(true), + numSamples(other.numSamples), + numClasses(other.numClasses), + maxSamples(other.maxSamples), + checkInterval(other.checkInterval), + minSamples(other.minSamples), + datasetInfo(other.datasetInfo), + ownsInfo(true), + successProbability(other.successProbability), + splitDimension(other.splitDimension), + majorityClass(other.majorityClass), + majorityProbability(other.majorityProbability), + categoricalSplit(std::move(other.categoricalSplit)), + numericSplit(std::move(other.numericSplit)) +{ + // Remove pointers. + other.dimensionMappings = nullptr; + other.datasetInfo = nullptr; + + // Reset primary type variables. + other.numSamples = 0; + other.numClasses = 0; + other.checkInterval = 0; + other.minSamples = 0; + other.successProbability = 0.0; + other.splitDimension = 0; + other.majorityClass = 0; + other.majorityProbability = 0.0; +} + +// Copy assignment operator. +template class NumericSplitType, + template class CategoricalSplitType> +HoeffdingTree& + HoeffdingTree:: + operator=(const HoeffdingTree& other) +{ + if (this != &other) + { + numericSplits = other.numericSplits; + categoricalSplits = other.categoricalSplits; + dimensionMappings = new std::unordered_map>(*other.dimensionMappings); + ownsMappings = true; + numSamples = other.numSamples; + numClasses = other.numClasses; + maxSamples = other.maxSamples; + checkInterval = other.checkInterval; + minSamples = other.minSamples; + datasetInfo = new data::DatasetInfo(*other.datasetInfo); + ownsInfo = true; + successProbability = other.successProbability; + splitDimension = other.splitDimension; + majorityClass = other.majorityClass; + majorityProbability = other.majorityProbability; + categoricalSplit = other.categoricalSplit; + numericSplit = other.numericSplit; + + // Copy each of the children. + for (size_t i = 0; i < other.children.size(); ++i) + { + children.push_back(new HoeffdingTree(*other.children[i])); + + // Delete copied datasetInfo and dimension mappings. + delete children[i]->datasetInfo; + children[i]->datasetInfo = this->datasetInfo; + children[i]->ownsInfo = false; + + delete children[i]->dimensionMappings; + children[i]->dimensionMappings = this->dimensionMappings; + children[i]->ownsMappings = false; + } + } + return *this; +} + +// Move assignment operator. +template class NumericSplitType, + template class CategoricalSplitType> +HoeffdingTree& + HoeffdingTree:: + operator=(HoeffdingTree&& other) +{ + if (this != &other) + { + numericSplits = std::move(other.numericSplits); + categoricalSplits = std::move(other.categoricalSplits); + dimensionMappings = other.dimensionMappings; + ownsMappings = true; + numSamples = other.numSamples; + numClasses = other.numClasses; + maxSamples = other.maxSamples; + checkInterval = other.checkInterval; + minSamples = other.minSamples; + datasetInfo = other.datasetInfo; + ownsInfo = true; + successProbability = other.successProbability; + splitDimension = other.splitDimension; + majorityClass = other.majorityClass; + majorityProbability = other.majorityProbability; + categoricalSplit = std::move(other.categoricalSplit); + numericSplit = std::move(other.numericSplit); + + // Remove pointers. + other.dimensionMappings = nullptr; + other.datasetInfo = nullptr; + + // Reset primary type variables. + other.numSamples = 0; + other.numClasses = 0; + other.checkInterval = 0; + other.minSamples = 0; + other.successProbability = 0.0; + other.splitDimension = 0; + other.majorityClass = 0; + other.majorityProbability = 0.0; + } + return *this; +} + + template class NumericSplitType, template class CategoricalSplitType> @@ -341,7 +473,7 @@ void HoeffdingTree< delete dimensionMappings; const CategoricalSplitType categoricalSplitIn(0, 0); - const NumericSplitType& numericSplitIn(0); + const NumericSplitType numericSplitIn(0); dimensionMappings = new std::unordered_map>(); diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp index d35970dd5b..2dfe857edf 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp @@ -62,53 +62,57 @@ HoeffdingTreeModel::HoeffdingTreeModel(HoeffdingTreeModel&& other) : HoeffdingTreeModel& HoeffdingTreeModel::operator=( const HoeffdingTreeModel& other) { - // Clear this model. - delete giniHoeffdingTree; - delete giniBinaryTree; - delete infoHoeffdingTree; - delete infoBinaryTree; + if (this != &other) + { + // Clear this model. + delete giniHoeffdingTree; + delete giniBinaryTree; + delete infoHoeffdingTree; + delete infoBinaryTree; - giniHoeffdingTree = NULL; - giniBinaryTree = NULL; - infoHoeffdingTree = NULL; - infoBinaryTree = NULL; - - // Create the right tree. - type = other.type; - if (other.giniHoeffdingTree && (type == GINI_HOEFFDING)) - giniHoeffdingTree = new GiniHoeffdingTreeType(*other.giniHoeffdingTree); - else if (other.giniBinaryTree && (type == GINI_BINARY)) - giniBinaryTree = new GiniBinaryTreeType(*other.giniBinaryTree); - else if (other.infoHoeffdingTree && (type == INFO_HOEFFDING)) - infoHoeffdingTree = new InfoHoeffdingTreeType(*other.infoHoeffdingTree); - else if (other.infoBinaryTree && (type == INFO_BINARY)) - infoBinaryTree = new InfoBinaryTreeType(*other.infoBinaryTree); + giniHoeffdingTree = NULL; + giniBinaryTree = NULL; + infoHoeffdingTree = NULL; + infoBinaryTree = NULL; + // Create the right tree. + type = other.type; + if (other.giniHoeffdingTree && (type == GINI_HOEFFDING)) + giniHoeffdingTree = new GiniHoeffdingTreeType(*other.giniHoeffdingTree); + else if (other.giniBinaryTree && (type == GINI_BINARY)) + giniBinaryTree = new GiniBinaryTreeType(*other.giniBinaryTree); + else if (other.infoHoeffdingTree && (type == INFO_HOEFFDING)) + infoHoeffdingTree = new InfoHoeffdingTreeType(*other.infoHoeffdingTree); + else if (other.infoBinaryTree && (type == INFO_BINARY)) + infoBinaryTree = new InfoBinaryTreeType(*other.infoBinaryTree); + } return *this; } // Move operator. HoeffdingTreeModel& HoeffdingTreeModel::operator=(HoeffdingTreeModel&& other) { - // Clear this model. - delete giniHoeffdingTree; - delete giniBinaryTree; - delete infoHoeffdingTree; - delete infoBinaryTree; + if (this != &other) + { + // Clear this model. + delete giniHoeffdingTree; + delete giniBinaryTree; + delete infoHoeffdingTree; + delete infoBinaryTree; - type = other.type; - giniHoeffdingTree = other.giniHoeffdingTree; - giniBinaryTree = other.giniBinaryTree; - infoHoeffdingTree = other.infoHoeffdingTree; - infoBinaryTree = other.infoBinaryTree; - - // Clear the other model. - other.type = GINI_HOEFFDING; - other.giniHoeffdingTree = NULL; - other.giniBinaryTree = NULL; - other.infoHoeffdingTree = NULL; - other.infoBinaryTree = NULL; + type = other.type; + giniHoeffdingTree = other.giniHoeffdingTree; + giniBinaryTree = other.giniBinaryTree; + infoHoeffdingTree = other.infoHoeffdingTree; + infoBinaryTree = other.infoBinaryTree; + // Clear the other model. + other.type = GINI_HOEFFDING; + other.giniHoeffdingTree = NULL; + other.giniBinaryTree = NULL; + other.infoHoeffdingTree = NULL; + other.infoBinaryTree = NULL; + } return *this; } diff --git a/src/mlpack/methods/kde/CMakeLists.txt b/src/mlpack/methods/kde/CMakeLists.txt index 31dacaee43..81bee212e3 100644 --- a/src/mlpack/methods/kde/CMakeLists.txt +++ b/src/mlpack/methods/kde/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES kde_stat.hpp kde_model.hpp kde_model_impl.hpp + kde_model.cpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/kde/kde.hpp b/src/mlpack/methods/kde/kde.hpp index 448d32dd84..8885c2e894 100644 --- a/src/mlpack/methods/kde/kde.hpp +++ b/src/mlpack/methods/kde/kde.hpp @@ -140,11 +140,16 @@ class KDE /** * Copy a KDE model. * - * Use std::move if the object to copy is no longer needed. + * @param other KDE model to copy. + */ + KDE& operator=(const KDE& other); + + /** + * Move a KDE model. * * @param other KDE model to copy. */ - KDE& operator=(KDE other); + KDE& operator=(KDE&& other); /** * Destroy the KDE object. If this object created any trees, they will be diff --git a/src/mlpack/methods/kde/kde_impl.hpp b/src/mlpack/methods/kde/kde_impl.hpp index b48190e686..054c02119d 100644 --- a/src/mlpack/methods/kde/kde_impl.hpp +++ b/src/mlpack/methods/kde/kde_impl.hpp @@ -190,31 +190,95 @@ KDE:: -operator=(KDE other) +operator=(const KDE& other) { - // Clean memory. - if (ownsReferenceTree) + if (this != &other) { - delete referenceTree; - delete oldFromNewReferences; + // Clean memory. + if (ownsReferenceTree) + { + delete referenceTree; + delete oldFromNewReferences; + } + kernel = KernelType(other.kernel); + metric = MetricType(other.metric); + relError = other.relError; + absError = other.absError; + ownsReferenceTree = other.ownsReferenceTree; + trained = other.trained; + mode = other.mode; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + if (trained) + { + if (ownsReferenceTree) + { + oldFromNewReferences = + new std::vector(*other.oldFromNewReferences); + referenceTree = new Tree(*other.referenceTree); + } + else + { + oldFromNewReferences = other.oldFromNewReferences; + referenceTree = other.referenceTree; + } + } } + return *this; +} - // Move the other object. - this->kernel = std::move(other.kernel); - this->metric = std::move(other.metric); - this->referenceTree = std::move(other.referenceTree); - this->oldFromNewReferences = std::move(other.oldFromNewReferences); - this->relError = other.relError; - this->absError = other.absError; - this->ownsReferenceTree = other.ownsReferenceTree; - this->trained = other.trained; - this->mode = other.mode; - this->monteCarlo = other.monteCarlo; - this->mcProb = other.mcProb; - this->initialSampleSize = other.initialSampleSize; - this->mcEntryCoef = other.mcEntryCoef; - this->mcBreakCoef = other.mcBreakCoef; +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +KDE& +KDE:: +operator=(KDE&& other) +{ + if (this != &other) + { + // Clean memory. + if (ownsReferenceTree) + { + delete referenceTree; + delete oldFromNewReferences; + } + // Move the other object. + this->kernel = std::move(other.kernel); + this->metric = std::move(other.metric); + // TODO: This should be: this->referenceTree = other.referenceTree; + this->referenceTree = std::move(other.referenceTree); + // TODO: This should be: this->oldFromNewReferences = other.oldFromNewReferences; + this->oldFromNewReferences = std::move(other.oldFromNewReferences); + this->relError = other.relError; + this->absError = other.absError; + this->ownsReferenceTree = other.ownsReferenceTree; + this->trained = other.trained; + this->mode = other.mode; + this->monteCarlo = other.monteCarlo; + this->mcProb = other.mcProb; + this->initialSampleSize = other.initialSampleSize; + this->mcEntryCoef = other.mcEntryCoef; + this->mcBreakCoef = other.mcBreakCoef; + } return *this; } diff --git a/src/mlpack/methods/kde/kde_model.cpp b/src/mlpack/methods/kde/kde_model.cpp new file mode 100644 index 0000000000..7a78c78df5 --- /dev/null +++ b/src/mlpack/methods/kde/kde_model.cpp @@ -0,0 +1,317 @@ +/** + * @file methods/kde/kde_model.cpp + * @author Roberto Hueso + * + * Implementation of KDE Model. + * + * 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 "kde_model.hpp" + +namespace mlpack { +namespace kde { + +//! Initialize the KDEModel with the given parameters. +KDEModel::KDEModel(const double bandwidth, + const double relError, + const double absError, + const KernelTypes kernelType, + const TreeTypes treeType, + const bool monteCarlo, + const double mcProb, + const size_t initialSampleSize, + const double mcEntryCoef, + const double mcBreakCoef) : + bandwidth(bandwidth), + relError(relError), + absError(absError), + kernelType(kernelType), + treeType(treeType), + monteCarlo(monteCarlo), + mcProb(mcProb), + initialSampleSize(initialSampleSize), + mcEntryCoef(mcEntryCoef), + mcBreakCoef(mcBreakCoef), + kdeModel(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +KDEModel::KDEModel(const KDEModel& other) : + bandwidth(other.bandwidth), + relError(other.relError), + absError(other.absError), + kernelType(other.kernelType), + treeType(other.treeType), + monteCarlo(other.monteCarlo), + mcProb(other.mcProb), + initialSampleSize(other.initialSampleSize), + mcEntryCoef(other.mcEntryCoef), + mcBreakCoef(other.mcBreakCoef), + kdeModel(other.kdeModel->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +KDEModel::KDEModel(KDEModel&& other) : + bandwidth(other.bandwidth), + relError(other.relError), + absError(other.absError), + kernelType(other.kernelType), + treeType(other.treeType), + monteCarlo(other.monteCarlo), + mcProb(other.mcProb), + initialSampleSize(other.initialSampleSize), + mcEntryCoef(other.mcEntryCoef), + mcBreakCoef(other.mcBreakCoef), + kdeModel(std::move(other.kdeModel)) +{ + // Reset other model. + other.bandwidth = 1.0; + other.relError = KDEDefaultParams::relError; + other.absError = KDEDefaultParams::absError; + other.kernelType = KernelTypes::GAUSSIAN_KERNEL; + other.treeType = TreeTypes::KD_TREE; + other.monteCarlo = KDEDefaultParams::monteCarlo; + other.mcProb = KDEDefaultParams::mcProb; + other.initialSampleSize = KDEDefaultParams::initialSampleSize; + other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; + other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; +} + +KDEModel& KDEModel::operator=(const KDEModel& other) +{ + if (this != &other) + { + delete kdeModel; + + bandwidth = other.bandwidth; + relError = other.relError; + absError = other.absError; + kernelType = other.kernelType; + treeType = other.treeType; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + kdeModel = other.kdeModel->Clone(); + } + + return *this; +} + +KDEModel& KDEModel::operator=(KDEModel&& other) +{ + if (this != &other) + { + delete kdeModel; + + bandwidth = other.bandwidth; + relError = other.relError; + absError = other.absError; + kernelType = other.kernelType; + treeType = other.treeType; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + kdeModel = std::move(other.kdeModel); + + // Reset other model. + other.bandwidth = 1.0; + other.relError = KDEDefaultParams::relError; + other.absError = KDEDefaultParams::absError; + other.kernelType = KernelTypes::GAUSSIAN_KERNEL; + other.treeType = TreeTypes::KD_TREE; + other.monteCarlo = KDEDefaultParams::monteCarlo; + other.mcProb = KDEDefaultParams::mcProb; + other.initialSampleSize = KDEDefaultParams::initialSampleSize; + other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; + other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; + } + + return *this; +} + +// Clean memory. +KDEModel::~KDEModel() +{ + delete kdeModel; +} + +template class TreeType> +KDEWrapperBase* InitializeModelHelper(const KDEModel::KernelTypes kernelType, + const double relError, + const double absError, + const double bandwidth) +{ + switch (kernelType) + { + case KDEModel::GAUSSIAN_KERNEL: + return new KDEWrapper( + relError, absError, kernel::GaussianKernel(bandwidth)); + + case KDEModel::EPANECHNIKOV_KERNEL: + return new KDEWrapper( + relError, absError, kernel::EpanechnikovKernel(bandwidth)); + + case KDEModel::LAPLACIAN_KERNEL: + return new KDEWrapper( + relError, absError, kernel::LaplacianKernel(bandwidth)); + + case KDEModel::SPHERICAL_KERNEL: + return new KDEWrapper( + relError, absError, kernel::SphericalKernel(bandwidth)); + + case KDEModel::TRIANGULAR_KERNEL: + return new KDEWrapper( + relError, absError, kernel::TriangularKernel(bandwidth)); + } + + // This should never happen. + return NULL; +} + +void KDEModel::InitializeModel() +{ + // Clean memory, if necessary. + delete kdeModel; + + // Build the actual model. + switch (treeType) + { + case KD_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case BALL_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case COVER_TREE: + kdeModel = InitializeModelHelper(kernelType, + relError, absError, bandwidth); + break; + + case OCTREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case R_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + } +} + +void KDEModel::BuildModel(arma::mat&& referenceSet) +{ + InitializeModel(); + + // Set whether to use Monte Carlo estimations or not. + kdeModel->MonteCarlo() = monteCarlo; + + // Set Monte Carlo probability. + kdeModel->MCProb(mcProb); + + // Set Monte Carlo initial sample size. + kdeModel->MCInitialSampleSize() = initialSampleSize; + + // Set Monte Carlo entry coefficient. + kdeModel->MCEntryCoef(mcEntryCoef); + + // Set Monte Carlo break coefficient. + kdeModel->MCBreakCoef(mcBreakCoef); + + // Train the model. + kdeModel->Train(std::move(referenceSet)); +} + +// Perform bichromatic evaluation. +void KDEModel::Evaluate(arma::mat&& querySet, arma::vec& estimates) +{ + kdeModel->Evaluate(std::move(querySet), estimates); +} + +// Perform monochromatic evaluation. +void KDEModel::Evaluate(arma::vec& estimates) +{ + kdeModel->Evaluate(estimates); +} + +// Clean memory. +void KDEModel::CleanMemory() +{ + delete kdeModel; +} + +// Modify model kernel bandwidth. +void KDEModel::Bandwidth(const double newBandwidth) +{ + bandwidth = newBandwidth; + kdeModel->Bandwidth(bandwidth); +} + +// Modify model relative error tolerance. +void KDEModel::RelativeError(const double newRelError) +{ + relError = newRelError; + kdeModel->RelativeError(relError); +} + +// Modify model absolute error tolerance. +void KDEModel::AbsoluteError(const double newAbsError) +{ + absError = newAbsError; + kdeModel->AbsoluteError(absError); +} + +// Modify whether Monte Carlo estimations will be used. +void KDEModel::MonteCarlo(const bool newMonteCarlo) +{ + monteCarlo = newMonteCarlo; + kdeModel->MonteCarlo() = monteCarlo; +} + +// Modify model Monte Carlo probability. +void KDEModel::MCProbability(const double newMCProb) +{ + mcProb = newMCProb; + kdeModel->MCProb(mcProb); +} + +// Modify model Monte Carlo initial sample size. +void KDEModel::MCInitialSampleSize(const size_t newSampleSize) +{ + initialSampleSize = newSampleSize; + kdeModel->MCInitialSampleSize() = initialSampleSize; +} + +// Modify model Monte Carlo entry coefficient. +void KDEModel::MCEntryCoefficient(const double newEntryCoef) +{ + mcEntryCoef = newEntryCoef; + kdeModel->MCEntryCoef(mcEntryCoef); +} + +// Modify model Monte Carlo break coefficient. +void KDEModel::MCBreakCoefficient(const double newBreakCoef) +{ + mcBreakCoef = newBreakCoef; + kdeModel->MCBreakCoef(mcBreakCoef); +} + +} // namespace kde +} // namespace mlpack diff --git a/src/mlpack/methods/kde/kde_model.hpp b/src/mlpack/methods/kde/kde_model.hpp index 220213ba5e..48b06c6382 100644 --- a/src/mlpack/methods/kde/kde_model.hpp +++ b/src/mlpack/methods/kde/kde_model.hpp @@ -22,28 +22,11 @@ #include // Remaining includes. -#include #include "kde.hpp" namespace mlpack { namespace kde { -//! Alias template. -template class TreeType> -using KDEType = KDE::template DualTreeTraverser, - TreeType::template SingleTreeTraverser>; - /** * KernelNormalizer holds a set of methods to normalize estimations applying * in each case the appropiate kernel normalizer function. @@ -81,284 +64,168 @@ class KernelNormalizer }; /** - * DualMonoKDE computes a Kernel Density Estimation on the given KDEType. - * It performs a monochromatic KDE. + * KDEWrapperBase is a base wrapper class for holding all KDE types supported by + * KDEModel. All KDE type wrappers inheirt from this class, allowing a simple + * interface via inheritance for all the different types we want to support. */ -class DualMonoKDE : public boost::static_visitor +class KDEWrapperBase { - private: - //! Vector to store the KDE results. - arma::vec& estimations; - public: - //! Alias template necessary for Visual C++ compiler. - template class TreeType> - using KDETypeT = KDEType; + //! Create the KDEWrapperBase object. The base class does not hold anything, + //! so this constructor does nothing. + KDEWrapperBase() { } - //! Default DualMonoKDE on some KDEType. - template class TreeType> - void operator()(KDETypeT* kde) const; + //! Create a new KDEWrapperBase that is the same as this one. This function + //! will properly handle polymorphism. + virtual KDEWrapperBase* Clone() const = 0; - // TODO Implement specific cases where a leaf size can be selected. + //! Destruct the KDEWrapperBase (nothing to do). + virtual ~KDEWrapperBase() { } - //! DualMonoKDE constructor. - DualMonoKDE(arma::vec& estimations); + //! Modify the bandwidth of the kernel. + virtual void Bandwidth(const double bw) = 0; + + //! Modify the relative error tolerance. + virtual void RelativeError(const double relError) = 0; + + //! Modify the absolute error tolerance. + virtual void AbsoluteError(const double absError) = 0; + + //! Get whether Monte Carlo search is being used. + virtual bool MonteCarlo() const = 0; + //! Modify whether Monte Carlo search is being used. + virtual bool& MonteCarlo() = 0; + + //! Modify the Monte Carlo probability. + virtual void MCProb(const double mcProb) = 0; + + //! Get the Monte Carlo sample size. + virtual size_t MCInitialSampleSize() const = 0; + //! Modify the Monte Carlo sample size. + virtual size_t& MCInitialSampleSize() = 0; + + //! Modify the Monte Carlo entry coefficient. + virtual void MCEntryCoef(const double entryCoef) = 0; + + //! Modify the Monte Carlo break coefficient. + virtual void MCBreakCoef(const double breakCoef) = 0; + + //! Get the search mode. + virtual KDEMode Mode() const = 0; + //! Modify the search mode. + virtual KDEMode& Mode() = 0; + + //! Train the model (build the tree). + virtual void Train(arma::mat&& referenceSet) = 0; + + //! Perform bichromatic KDE (i.e. KDE with a separate query set). + virtual void Evaluate(arma::mat&& querySet, + arma::vec& estimates) = 0; + + //! Perform monochromatic KDE (i.e. with the reference set as the query set). + virtual void Evaluate(arma::vec& estimates) = 0; }; /** - * DualBiKDE computes a Kernel Density Estimation on the given KDEType. - * It performs a bichromatic KDE. + * KDEWrapper is a wrapper class for all KDE types supported by KDEModel. It + * can be extended with new child classes if new functionality for certain types + * is needed. */ -class DualBiKDE : public boost::static_visitor +template class TreeType> +class KDEWrapper : public KDEWrapperBase { - private: - //! Query set dimensionality. - const size_t dimension; - - //! The query set for the KDE. - const arma::mat& querySet; - - //! Vector to store the KDE results. - arma::vec& estimations; - public: - //! Alias template necessary for Visual C++ compiler. - template class TreeType> - using KDETypeT = KDEType; + //! Create the KDEWrapper object, initializing the internally-held KDE object. + KDEWrapper(const double relError, + const double absError, + const KernelType& kernel) : + kde(relError, absError, kernel) + { + // Nothing left to do. + } - //! Default DualBiKDE on some KDEType. - template class TreeType> - void operator()(KDETypeT* kde) const; + //! Create a new KDEWrapper that is the same as this one. This function + //! will properly handle polymorphism. + virtual KDEWrapper* Clone() const { return new KDEWrapper(*this); } - // TODO Implement specific cases where a leaf size can be selected. + //! Destruct the KDEWrapper (nothing to do). + virtual ~KDEWrapper() { } - //! DualBiKDE constructor. Takes ownership of the given querySet. - DualBiKDE(arma::mat&& querySet, arma::vec& estimations); + //! Modify the bandwidth of the kernel. + virtual void Bandwidth(const double bw) { kde.Kernel() = KernelType(bw); } + + //! Modify the relative error tolerance. + virtual void RelativeError(const double eps) { kde.RelativeError(eps); } + + //! Modify the absolute error tolerance. + virtual void AbsoluteError(const double eps) { kde.AbsoluteError(eps); } + + //! Get whether Monte Carlo search is being used. + virtual bool MonteCarlo() const { return kde.MonteCarlo(); } + //! Modify whether Monte Carlo search is being used. + virtual bool& MonteCarlo() { return kde.MonteCarlo(); } + + //! Modify the Monte Carlo probability. + virtual void MCProb(const double mcProb) { kde.MCProb(mcProb); } + + //! Get the Monte Carlo sample size. + virtual size_t MCInitialSampleSize() const + { + return kde.MCInitialSampleSize(); + } + //! Modify the Monte Carlo sample size. + virtual size_t& MCInitialSampleSize() + { + return kde.MCInitialSampleSize(); + } + + //! Modify the Monte Carlo entry coefficient. + virtual void MCEntryCoef(const double e) { kde.MCEntryCoef(e); } + + //! Modify the Monte Carlo break coefficient. + virtual void MCBreakCoef(const double b) { kde.MCBreakCoef(b); } + + //! Get the search mode. + virtual KDEMode Mode() const { return kde.Mode(); } + //! Modify the search mode. + virtual KDEMode& Mode() { return kde.Mode(); } + + //! Train the model (build the tree). + virtual void Train(arma::mat&& referenceSet); + + //! Perform bichromatic KDE (i.e. KDE with a separate query set). + virtual void Evaluate(arma::mat&& querySet, + arma::vec& estimates); + + //! Perform monochromatic KDE (i.e. with the reference set as the query set). + virtual void Evaluate(arma::vec& estimates); + + //! Serialize the KDE model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(kde)); + } + + protected: + typedef KDE KDEType; + + //! The instantiated KDE object that we are wrapping. + KDEType kde; }; /** - * TrainVisitor trains a given KDEType using a reference set. + * The KDEModel provides an abstraction for the KDE class, abstracting away the + * KernelType and TreeType parameters and allowing those to be specified at + * runtime. This class is written for the sake of the `kde` binding, but it is + * not necessarily restricted to that usage. */ -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set used for training. - arma::mat&& referenceSet; - - public: - //! Default TrainVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - // TODO Implement specific cases where a leaf size can be selected. - - //! TrainVisitor constructor. Takes ownership of the given referenceSet. - TrainVisitor(arma::mat&& referenceSet); -}; - -/** - * BandwidthVisitor modifies the bandwidth of a KDEType kernel. - */ -class BandwidthVisitor : public boost::static_visitor -{ - private: - //! Relative error tolerance. - const double bandwidth; - - public: - //! Default BandwidthVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! BandwidthVisitor constructor. - BandwidthVisitor(const double bandwidth); -}; - -/** - * RelErrorVisitor modifies relative error tolerance for a KDEType. - */ -class RelErrorVisitor : public boost::static_visitor -{ - private: - //! Relative error tolerance. - const double relError; - - public: - //! Default RelErrorVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! RelErrorVisitor constructor. - RelErrorVisitor(const double relError); -}; - -/** - * AbsErrorVisitor modifies absolute error tolerance for a KDEType. - */ -class AbsErrorVisitor : public boost::static_visitor -{ - private: - //! Absolute error tolerance. - const double absError; - - public: - //! Default AbsErrorVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! AbsErrorVisitor constructor. - AbsErrorVisitor(const double absError); -}; - -/** - * MonteCarloVisitor activates or deactivates Monte Carlo for a given KDEType. - */ -class MonteCarloVisitor : public boost::static_visitor -{ - private: - //! Whether to use Monte Carlo estimations or not. - const bool monteCarlo; - - public: - //! Default MonteCarloVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MonteCarloVisitor constructor. - MonteCarloVisitor(const bool monteCarlo); -}; - -/** - * MCProbabilityVisitor sets the Monte Carlo probability for a given KDEType. - */ -class MCProbabilityVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo probability. - const double probability; - - public: - //! Default MCProbabilityVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCProbabilityVisitor constructor. - MCProbabilityVisitor(const double probability); -}; - -/** - * MCSampleSizeVisitor sets the Monte Carlo intial sample size for a given - * KDEType. - */ -class MCSampleSizeVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo sample size. - const size_t sampleSize; - - public: - //! Default MCSampleSizeVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCSampleSizeVisitor constructor. - MCSampleSizeVisitor(const size_t sampleSize); -}; - -/** - * MCEntryCoefVisitor sets the Monte Carlo entry coefficient. - */ -class MCEntryCoefVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo entry coefficient. - const double entryCoef; - - public: - //! Default MCEntryCoefVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCEntryCoefVisitor constructor. - MCEntryCoefVisitor(const double entryCoef); -}; - -/** - * MCBreakCoefVisitor sets the Monte Carlo break coefficient. - */ -class MCBreakCoefVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo break coefficient. - const double breakCoef; - - public: - //! Default MCBreakCoefVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCBreakCoefVisitor constructor. - MCBreakCoefVisitor(const double breakCoef); -}; - -/** - * ModeVisitor exposes the Mode() method of the KDEType. - */ -class ModeVisitor : public boost::static_visitor -{ - public: - //! Return mode of KDEType instance. - template - KDEMode& operator()(KDEType* kde) const; -}; - -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete KDEType instance. - template - void operator()(KDEType* kde) const; -}; - class KDEModel { public: @@ -413,34 +280,10 @@ class KDEModel double mcBreakCoef; /** - * kdeModel holds an instance of each possible combination of KernelType and - * TreeType. It is initialized using BuildModel. + * kdeModel holds whatever KDE type we are using. It is initialized using the + * `BuildModel()` method. */ - boost::variant*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*> kdeModel; + KDEWrapperBase* kdeModel; public: /** @@ -487,11 +330,16 @@ class KDEModel /** * Copy the given model. * - * Use std::move if the object to copy is no longer needed. - * * @param other KDEModel to copy. */ - KDEModel& operator=(KDEModel other); + KDEModel& operator=(const KDEModel& other); + + /** + * Take ownership of the contents of the given model. + * + * @param other KDEModel to take ownership of. + */ + KDEModel& operator=(KDEModel&& other); //! Destroy the KDEModel object. ~KDEModel(); @@ -561,10 +409,15 @@ class KDEModel void MCBreakCoefficient(const double newBreakCoef); //! Get the mode of the model. - KDEMode Mode() const; + KDEMode Mode() const { return kdeModel->Mode(); } //! Modify the mode of the model. - KDEMode& Mode(); + KDEMode& Mode() { return kdeModel->Mode(); } + + /** + * Initialize the KDE model. + */ + void InitializeModel(); /** * Build the KDE model with the given parameters and then trains it with the diff --git a/src/mlpack/methods/kde/kde_model_impl.hpp b/src/mlpack/methods/kde/kde_model_impl.hpp index 4b59e7657a..325b071cb3 100644 --- a/src/mlpack/methods/kde/kde_model_impl.hpp +++ b/src/mlpack/methods/kde/kde_model_impl.hpp @@ -18,521 +18,96 @@ namespace mlpack { namespace kde { -//! Initialize the KDEModel with the given parameters. -inline KDEModel::KDEModel(const double bandwidth, - const double relError, - const double absError, - const KernelTypes kernelType, - const TreeTypes treeType, - const bool monteCarlo, - const double mcProb, - const size_t initialSampleSize, - const double mcEntryCoef, - const double mcBreakCoef) : - bandwidth(bandwidth), - relError(relError), - absError(absError), - kernelType(kernelType), - treeType(treeType), - monteCarlo(monteCarlo), - mcProb(mcProb), - initialSampleSize(initialSampleSize), - mcEntryCoef(mcEntryCoef), - mcBreakCoef(mcBreakCoef) -{ - // Nothing to do. -} - -// Copy constructor. -inline KDEModel::KDEModel(const KDEModel& other) : - bandwidth(other.bandwidth), - relError(other.relError), - absError(other.absError), - kernelType(other.kernelType), - treeType(other.treeType), - monteCarlo(other.monteCarlo), - mcProb(other.mcProb), - initialSampleSize(other.initialSampleSize), - mcEntryCoef(other.mcEntryCoef), - mcBreakCoef(other.mcBreakCoef) -{ - // Nothing to do. -} - -// Move constructor. -inline KDEModel::KDEModel(KDEModel&& other) : - bandwidth(other.bandwidth), - relError(other.relError), - absError(other.absError), - kernelType(other.kernelType), - treeType(other.treeType), - monteCarlo(other.monteCarlo), - mcProb(other.mcProb), - initialSampleSize(other.initialSampleSize), - mcEntryCoef(other.mcEntryCoef), - mcBreakCoef(other.mcBreakCoef), - kdeModel(std::move(other.kdeModel)) -{ - // Reset other model. - other.bandwidth = 1.0; - other.relError = KDEDefaultParams::relError; - other.absError = KDEDefaultParams::absError; - other.kernelType = KernelTypes::GAUSSIAN_KERNEL; - other.treeType = TreeTypes::KD_TREE; - other.monteCarlo = KDEDefaultParams::monteCarlo; - other.mcProb = KDEDefaultParams::mcProb; - other.initialSampleSize = KDEDefaultParams::initialSampleSize; - other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; - other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; - other.kdeModel = decltype(other.kdeModel)(); -} - -inline KDEModel& KDEModel::operator=(KDEModel other) -{ - boost::apply_visitor(DeleteVisitor(), kdeModel); - bandwidth = other.bandwidth; - relError = other.relError; - absError = other.absError; - kernelType = other.kernelType; - treeType = other.treeType; - monteCarlo = other.monteCarlo; - mcProb = other.mcProb; - initialSampleSize = other.initialSampleSize; - mcEntryCoef = other.mcEntryCoef; - mcBreakCoef = other.mcBreakCoef; - kdeModel = std::move(other.kdeModel); - return *this; -} - -// Clean memory. -inline KDEModel::~KDEModel() -{ - boost::apply_visitor(DeleteVisitor(), kdeModel); -} - -inline void KDEModel::BuildModel(arma::mat&& referenceSet) -{ - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), kdeModel); - - // Build the actual model. - if (kernelType == GAUSSIAN_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - - // Set whether to use Monte Carlo estimations or not. - MonteCarloVisitor MCVisitor(monteCarlo); - boost::apply_visitor(MCVisitor, kdeModel); - - // Set Monte Carlo probability. - MCProbabilityVisitor probabilityVisitor(mcProb); - boost::apply_visitor(probabilityVisitor, kdeModel); - - // Set Monte Carlo initial sample size. - MCSampleSizeVisitor sampleSizeVisitor(initialSampleSize); - boost::apply_visitor(sampleSizeVisitor, kdeModel); - - // Set Monte Carlo entry coefficient. - MCEntryCoefVisitor entryCoefficientVisitor(mcEntryCoef); - boost::apply_visitor(entryCoefficientVisitor, kdeModel); - - // Set Monte Carlo break coefficient. - MCBreakCoefVisitor breakCoefficientVisitor(mcBreakCoef); - boost::apply_visitor(breakCoefficientVisitor, kdeModel); - - // Train the model. - TrainVisitor train(std::move(referenceSet)); - boost::apply_visitor(train, kdeModel); -} - -// Perform bichromatic evaluation. -inline void KDEModel::Evaluate(arma::mat&& querySet, arma::vec& estimations) -{ - Log::Info << "Evaluating KDE..." << std::endl; - DualBiKDE eval(std::move(querySet), estimations); - boost::apply_visitor(eval, kdeModel); -} - -// Perform monochromatic evaluation. -inline void KDEModel::Evaluate(arma::vec& estimations) -{ - Log::Info << "Evaluating KDE..." << std::endl; - DualMonoKDE eval(estimations); - boost::apply_visitor(eval, kdeModel); -} - -// Clean memory. -inline void KDEModel::CleanMemory() -{ - boost::apply_visitor(DeleteVisitor(), kdeModel); -} - -// Parameters for KDE evaluation. -DualMonoKDE::DualMonoKDE(arma::vec& estimations): - estimations(estimations) -{} - -// Default KDE evaluation. +//! Train the model (build the tree). template class TreeType> -void DualMonoKDE::operator()(KDETypeT* kde) const +void KDEWrapper::Train(arma::mat&& referenceSet) { - if (kde) + kde.Train(std::move(referenceSet)); +} + +//! Perform bichromatic KDE (i.e. KDE with a separate query set). +template class TreeType> +void KDEWrapper::Evaluate(arma::mat&& querySet, + arma::vec& estimates) +{ + const size_t dimension = querySet.n_rows; + kde.Evaluate(std::move(querySet), estimates); + KernelNormalizer::ApplyNormalizer(kde.Kernel(), + dimension, + estimates); +} + +//! Perform monochromatic KDE (i.e. with the reference set as the query set). +template class TreeType> +void KDEWrapper::Evaluate(arma::vec& estimates) +{ + kde.Evaluate(estimates); + const size_t dimension = kde.ReferenceTree()->Dataset().n_rows; + KernelNormalizer::ApplyNormalizer(kde.Kernel(), + dimension, + estimates); +} + +template class TreeType, + typename Archive> +void SerializationHelper(Archive& ar, + KDEWrapperBase* kdeModel, + const KDEModel::KernelTypes kernelType) +{ + switch (kernelType) { - kde->Evaluate(estimations); - const size_t dimension = (kde->ReferenceTree())->Dataset().n_rows; - KernelNormalizer::ApplyNormalizer(kde->Kernel(), - dimension, - estimations); + case KDEModel::GAUSSIAN_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::EPANECHNIKOV_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::LAPLACIAN_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::SPHERICAL_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::TRIANGULAR_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } } - else - { - throw std::runtime_error("no KDE model initialized"); - } -} - -// Parameters for KDE evaluation. -DualBiKDE::DualBiKDE(arma::mat&& querySet, arma::vec& estimations): - dimension(querySet.n_rows), - querySet(std::move(querySet)), - estimations(estimations) -{} - -// Default KDE evaluation. -template class TreeType> -void DualBiKDE::operator()(KDETypeT* kde) const -{ - if (kde) - { - kde->Evaluate(std::move(querySet), estimations); - KernelNormalizer::ApplyNormalizer(kde->Kernel(), - dimension, - estimations); - } - else - { - throw std::runtime_error("no KDE model initialized"); - } -} - -// Parameters for Train. -TrainVisitor::TrainVisitor(arma::mat&& referenceSet) : - referenceSet(std::move(referenceSet)) -{} - -// Default Train. -template class TreeType> -void TrainVisitor::operator()(KDEType* kde) const -{ - Log::Info << "Training KDE model..." << std::endl; - if (kde) - kde->Train(std::move(referenceSet)); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Modify kernel bandwidth. -BandwidthVisitor::BandwidthVisitor(const double bandwidth) : - bandwidth(bandwidth) -{} - -// Default modify kernel bandwidth. -template class TreeType> -void BandwidthVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->Kernel() = KernelType(bandwidth); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Modify relative error tolerance. -RelErrorVisitor::RelErrorVisitor(const double relError) : - relError(relError) -{} - -// Default modify relative error tolerance. -template class TreeType> -void RelErrorVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->RelativeError(relError); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Modify absolute error tolerance. -AbsErrorVisitor::AbsErrorVisitor(const double absError) : - absError(absError) -{} - -// Default modify absolute error tolerance. -template class TreeType> -void AbsErrorVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->AbsoluteError(absError); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Activate or deactivate Monte Carlo. -MonteCarloVisitor::MonteCarloVisitor(const bool monteCarlo) : - monteCarlo(monteCarlo) -{} - -// Default activate or deactivate Monte Carlo. -template class TreeType> -void MonteCarloVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MonteCarlo() = monteCarlo; - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo probability. -MCProbabilityVisitor::MCProbabilityVisitor(const double probability) : - probability(probability) -{} - -// Default probability for Monte Carlo. -template class TreeType> -void MCProbabilityVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCProb(probability); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo sample size. -MCSampleSizeVisitor::MCSampleSizeVisitor(const size_t sampleSize) : - sampleSize(sampleSize) -{} - -// Default sample size for Monte Carlo. -template class TreeType> -void MCSampleSizeVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCInitialSampleSize() = sampleSize; - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo entry coefficient. -MCEntryCoefVisitor::MCEntryCoefVisitor(const double entryCoef) : - entryCoef(entryCoef) -{} - -// Default entry coefficient for Monte Carlo. -template class TreeType> -void MCEntryCoefVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCEntryCoef(entryCoef); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo break coefficient. -MCBreakCoefVisitor::MCBreakCoefVisitor(const double breakCoef) : - breakCoef(breakCoef) -{} - -// Default break coefficient for Monte Carlo. -template class TreeType> -void MCBreakCoefVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCBreakCoef(breakCoef); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Delete model. -template -void DeleteVisitor::operator()(KDEType* kde) const -{ - if (kde) - delete kde; -} - -// Mode of model. -template -KDEMode& ModeVisitor::operator()(KDEType* kde) const -{ - if (kde) - return kde->Mode(); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Get mode of model. -KDEMode KDEModel::Mode() const -{ - return boost::apply_visitor(ModeVisitor(), kdeModel); -} - -// Modify mode of model. -KDEMode& KDEModel::Mode() -{ - return boost::apply_visitor(ModeVisitor(), kdeModel); } // Serialize the model. @@ -560,73 +135,31 @@ void KDEModel::serialize(Archive& ar, const uint32_t /* version */) } if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), kdeModel); + InitializeModel(); // Values will be overwritten. - ar(CEREAL_VARIANT_POINTER(kdeModel)); -} + // Avoid polymorphism in serialization by serializing directly by the type. + switch (treeType) + { + case KD_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify model kernel bandwidth. -void KDEModel::Bandwidth(const double newBandwidth) -{ - bandwidth = newBandwidth; - BandwidthVisitor bandwidthVisitor(newBandwidth); - boost::apply_visitor(bandwidthVisitor, kdeModel); -} + case BALL_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify model relative error tolerance. -void KDEModel::RelativeError(const double newRelError) -{ - relError = newRelError; - RelErrorVisitor relErrorVisitor(newRelError); - boost::apply_visitor(relErrorVisitor, kdeModel); -} + case COVER_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify model absolute error tolerance. -void KDEModel::AbsoluteError(const double newAbsError) -{ - absError = newAbsError; - AbsErrorVisitor absErrorVisitor(newAbsError); - boost::apply_visitor(absErrorVisitor, kdeModel); -} + case OCTREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify whether Monte Carlo estimations will be used. -void KDEModel::MonteCarlo(const bool newMonteCarlo) -{ - monteCarlo = newMonteCarlo; - MonteCarloVisitor monteCarloVisitor(newMonteCarlo); - boost::apply_visitor(monteCarloVisitor, kdeModel); -} - -// Modify model Monte Carlo probability. -void KDEModel::MCProbability(const double newMCProb) -{ - mcProb = newMCProb; - MCProbabilityVisitor mcProbVisitor(newMCProb); - boost::apply_visitor(mcProbVisitor, kdeModel); -} - -// Modify model Monte Carlo initial sample size. -void KDEModel::MCInitialSampleSize(const size_t newSampleSize) -{ - initialSampleSize = newSampleSize; - MCSampleSizeVisitor mcSampleSizeVisitor(newSampleSize); - boost::apply_visitor(mcSampleSizeVisitor, kdeModel); -} - -// Modify model Monte Carlo entry coefficient. -void KDEModel::MCEntryCoefficient(const double newEntryCoef) -{ - mcEntryCoef = newEntryCoef; - MCEntryCoefVisitor mcEntryCoefVisitor(newEntryCoef); - boost::apply_visitor(mcEntryCoefVisitor, kdeModel); -} - -// Modify model Monte Carlo break coefficient. -void KDEModel::MCBreakCoefficient(const double newBreakCoef) -{ - mcBreakCoef = newBreakCoef; - MCBreakCoefVisitor mcBreakCoefVisitor(newBreakCoef); - boost::apply_visitor(mcBreakCoefVisitor, kdeModel); + case R_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; + } } } // namespace kde diff --git a/src/mlpack/methods/kmeans/CMakeLists.txt b/src/mlpack/methods/kmeans/CMakeLists.txt index 1dbbbba626..6782ed2f66 100644 --- a/src/mlpack/methods/kmeans/CMakeLists.txt +++ b/src/mlpack/methods/kmeans/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES kill_empty_clusters.hpp kmeans.hpp kmeans_impl.hpp + kmeans_plus_plus_initialization.hpp max_variance_new_cluster.hpp max_variance_new_cluster_impl.hpp naive_kmeans.hpp diff --git a/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp b/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp index 6f180c2e99..2d1a66fe12 100644 --- a/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp +++ b/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp @@ -40,7 +40,8 @@ DualTreeKMeansRules::DualTreeKMeansRules( baseCases(0), scores(0), lastQueryIndex(dataset.n_cols), - lastReferenceIndex(centroids.n_cols) + lastReferenceIndex(centroids.n_cols), + lastBaseCase(0.0) { // We must set the traversal info last query and reference node pointers to // something that is both invalid (i.e. not a tree node) and not NULL. We'll @@ -156,8 +157,7 @@ inline double DualTreeKMeansRules::Score( traversalInfo.LastQueryNode()->MinimumBoundDistance(); const double lastRefDescDist = traversalInfo.LastReferenceNode()->MinimumBoundDistance(); - adjustedScore = lastScore + lastQueryDescDist; - adjustedScore = lastScore + lastRefDescDist; + adjustedScore = lastScore + lastQueryDescDist + lastRefDescDist; } // Assemble an adjusted score. For nearest neighbor search, this adjusted diff --git a/src/mlpack/methods/kmeans/kmeans_main.cpp b/src/mlpack/methods/kmeans/kmeans_main.cpp index 4c7a689aec..4707c05b7e 100644 --- a/src/mlpack/methods/kmeans/kmeans_main.cpp +++ b/src/mlpack/methods/kmeans/kmeans_main.cpp @@ -17,6 +17,7 @@ #include "allow_empty_clusters.hpp" #include "kill_empty_clusters.hpp" #include "refined_start.hpp" +#include "kmeans_plus_plus_initialization.hpp" #include "elkan_kmeans.hpp" #include "hamerly_kmeans.hpp" #include "pelleg_moore_kmeans.hpp" @@ -44,14 +45,17 @@ BINDING_LONG_DESC( " the point furthest from the centroid of the cluster with maximum variance" " is taken to fill that cluster." "\n\n" - "Optionally, the Bradley and Fayyad approach (\"Refining initial points for" - " k-means clustering\", 1998) can be used to select initial points by " - "specifying the " + PRINT_PARAM_STRING("refined_start") + " parameter. " - "This approach works by taking random samplings of the dataset; to specify " - "the number of samplings, the " + PRINT_PARAM_STRING("samplings") + - " parameter is used, and to specify the percentage of the dataset to be " - "used in each sample, the " + PRINT_PARAM_STRING("percentage") + - " parameter is used (it should be a value between 0.0 and 1.0)." + "Optionally, the strategy to choose initial centroids can be specified. " + "The k-means++ algorithm can be used to choose initial centroids with " + "the " + PRINT_PARAM_STRING("kmeans_plus_plus") + " parameter. The " + "Bradley and Fayyad approach (\"Refining initial points for k-means " + "clustering\", 1998) can be used to select initial points by specifying " + "the " + PRINT_PARAM_STRING("refined_start") + " parameter. This approach " + "works by taking random samplings of the dataset; to specify the number of " + "samplings, the " + PRINT_PARAM_STRING("samplings") + " parameter is used, " + "and to specify the percentage of the dataset to be used in each sample, " + "the " + PRINT_PARAM_STRING("percentage") + " parameter is used (it should " + "be a value between 0.0 and 1.0)." "\n\n" "There are several options available for the algorithm used for each Lloyd " "iteration, specified with the " + PRINT_PARAM_STRING("algorithm") + " " @@ -102,6 +106,7 @@ BINDING_EXAMPLE( // See also... BINDING_SEE_ALSO("K-Means tutorial", "@doxygen/kmtutorial.html"); BINDING_SEE_ALSO("@dbscan", "#dbscan"); +BINDING_SEE_ALSO("k-means++", "https://en.wikipedia.org/wiki/K-means%2B%2B"); BINDING_SEE_ALSO("Using the triangle inequality to accelerate k-means (pdf)", "http://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf"); BINDING_SEE_ALSO("Making k-means even faster (pdf)", @@ -147,6 +152,8 @@ PARAM_INT_IN("samplings", "Number of samplings to perform for refined start " "(use when --refined_start is specified).", "S", 100); PARAM_DOUBLE_IN("percentage", "Percentage of dataset to use for each refined " "start sampling (use when --refined_start is specified).", "p", 0.02); +PARAM_FLAG("kmeans_plus_plus", "Use the k-means++ initialization strategy to " + "choose initial points.", "K"); PARAM_STRING_IN("algorithm", "Algorithm to use for the Lloyd iteration " "('naive', 'pelleg-moore', 'elkan', 'hamerly', 'dualtree', or " @@ -176,6 +183,9 @@ static void mlpackMain() else math::RandomSeed((size_t) std::time(NULL)); + RequireOnlyOnePassed({ "refined_start", "kmeans_plus_plus" }, true, + "Only one initialization strategy can be specified!", true); + // Now, start building the KMeans type that we'll be using. Start with the // initial partition policy. The call to FindEmptyClusterPolicy<> results in // a call to RunKMeans<> and the algorithm is completed. @@ -191,6 +201,11 @@ static void mlpackMain() FindEmptyClusterPolicy(RefinedStart(samplings, percentage)); } + else if (IO::HasParam("kmeans_plus_plus")) + { + FindEmptyClusterPolicy( + KMeansPlusPlusInitialization()); + } else { FindEmptyClusterPolicy(SampleInitialization()); @@ -271,7 +286,7 @@ void RunKMeans(const InitialPartitionPolicy& ipp) const int maxIterations = IO::GetParam("max_iterations"); // Make sure we have an output file if we're not doing the work in-place. - RequireAtLeastOnePassed({ "in_place", "output", "centroid" }, false, + RequireOnlyOnePassed({ "in_place", "output", "centroid" }, false, "no results will be saved"); arma::mat dataset = IO::GetParam("input"); // Load our dataset. diff --git a/src/mlpack/methods/kmeans/kmeans_plus_plus_initialization.hpp b/src/mlpack/methods/kmeans/kmeans_plus_plus_initialization.hpp new file mode 100644 index 0000000000..e43c59fe1a --- /dev/null +++ b/src/mlpack/methods/kmeans/kmeans_plus_plus_initialization.hpp @@ -0,0 +1,103 @@ +/** + * @file methods/kmeans/kmeans_plus_plus_initialization.hpp + * @author Ryan Curtin + * + * This file implements the k-means++ initialization strategy. + */ +#ifndef MLPACK_METHODS_KMEANS_KMEANS_PLUS_PLUS_INITIALIZATION_HPP +#define MLPACK_METHODS_KMEANS_KMEANS_PLUS_PLUS_INITIALIZATION_HPP + +#include + +/** + * This class implements the k-means++ initialization, as described in the + * following paper: + * + * @code + * @inproceedings{arthur2007k, + * title={k-means++: The advantages of careful seeding}, + * author={Arthur, David and Vassilvitskii, Sergei}, + * booktitle={Proceedings of the Eighteenth Annual ACM-SIAM Symposium on + * Discrete Algorithms (SODA '07)}, + * pages={1027--1035}, + * year={2007}, + * organization={Society for Industrial and Applied Mathematics} + * } + * @endcode + * + * In accordance with mlpack's InitialPartitionPolicy template type, we only + * need to implement a constructor and a method to compute the initial + * centroids. + */ +class KMeansPlusPlusInitialization +{ + public: + //! Empty constructor, required by the InitialPartitionPolicy type definition. + KMeansPlusPlusInitialization() { } + + /** + * Initialize the centroids matrix by randomly sampling points from the data + * matrix. + * + * @param data Dataset. + * @param clusters Number of clusters. + * @param centroids Matrix to put initial centroids into. + */ + template + inline static void Cluster(const MatType& data, + const size_t clusters, + arma::mat& centroids) + { + centroids.set_size(data.n_rows, clusters); + + // We'll sample our first point fully randomly. + size_t firstPoint = mlpack::math::RandInt(0, data.n_cols); + centroids.col(0) = data.col(firstPoint); + + // Utility variable. + arma::vec distribution(data.n_cols); + + // Now, sample other points... + for (size_t i = 1; i < clusters; ++i) + { + // We must compute the CDF for sampling... this depends on the computation + // of the minimum distance between each point and its closest + // already-chosen centroid. + // + // This computation is ripe for speedup with trees! I am not sure exactly + // how much we would need to approximate, but I think it could be done + // without breaking the O(log k)-competitive guarantee (I think). + for (size_t p = 0; p < data.n_cols; ++p) + { + double minDistance = std::numeric_limits::max(); + for (size_t j = 0; j < i; ++j) + { + const double distance = + mlpack::metric::SquaredEuclideanDistance::Evaluate(data.col(p), + centroids.col(j)); + minDistance = std::min(distance, minDistance); + } + + distribution[p] = minDistance; + } + + // Next normalize the distribution (actually technically we could avoid + // this). + distribution /= arma::accu(distribution); + + // Turn it into a CDF for convenience... + for (size_t j = 1; j < distribution.n_elem; ++j) + distribution[j] += distribution[j - 1]; + + // Sample a point... + const double sampleValue = mlpack::math::Random(); + const double* elem = std::lower_bound(distribution.begin(), + distribution.end(), sampleValue); + const size_t position = (size_t) + (elem - distribution.begin()) / sizeof(double); + centroids.col(i) = data.col(position); + } + } +}; + +#endif diff --git a/src/mlpack/methods/lars/lars.cpp b/src/mlpack/methods/lars/lars.cpp index 03612406fc..16bfa64124 100644 --- a/src/mlpack/methods/lars/lars.cpp +++ b/src/mlpack/methods/lars/lars.cpp @@ -178,6 +178,10 @@ double LARS::Train(const arma::mat& matX, isIgnored.clear(); matUtriCholFactor.reset(); + // Update values in case lambda1 or lambda2 changed. + lasso = (lambda1 != 0); + elasticNet = (lambda1 != 0 && lambda2 != 0); + // This matrix may end up holding the transpose -- if necessary. arma::mat dataTrans; // dataRef is row-major. diff --git a/src/mlpack/methods/lars/lars.hpp b/src/mlpack/methods/lars/lars.hpp index 8989d13e55..d019fec900 100644 --- a/src/mlpack/methods/lars/lars.hpp +++ b/src/mlpack/methods/lars/lars.hpp @@ -249,6 +249,26 @@ class LARS arma::rowvec& predictions, const bool rowMajor = false) const; + //! Get the L1 regularization coefficient. + double Lambda1() const { return lambda1; } + //! Modify the L1 regularization coefficient. + double& Lambda1() { return lambda1; } + + //! Get the L2 regularization coefficient. + double Lambda2() const { return lambda2; } + //! Modify the L2 regularization coefficient. + double& Lambda2() { return lambda2; } + + //! Get whether to use the Cholesky decomposition. + bool UseCholesky() const { return useCholesky; } + //! Modify whether to use the Cholesky decomposition. + bool& UseCholesky() { return useCholesky; } + + //! Get the tolerance for maximum correlation during training. + double Tolerance() const { return tolerance; } + //! Modify the tolerance for maximum correlation during training. + double& Tolerance() { return tolerance; } + //! Access the set of active dimensions. const std::vector& ActiveSet() const { return activeSet; } diff --git a/src/mlpack/methods/linear_svm/linear_svm_impl.hpp b/src/mlpack/methods/linear_svm/linear_svm_impl.hpp index 243ba772ce..69f0887f9d 100644 --- a/src/mlpack/methods/linear_svm/linear_svm_impl.hpp +++ b/src/mlpack/methods/linear_svm/linear_svm_impl.hpp @@ -173,13 +173,7 @@ void LinearSVM::Classify( const MatType& data, arma::mat& scores) const { - if (data.n_rows != FeatureSize()) - { - std::ostringstream oss; - oss << "LinearSVM::Classify(): dataset has " << data.n_rows - << " dimensions, but model has " << FeatureSize() << " dimensions!"; - throw std::invalid_argument(oss.str()); - } + util::CheckSameDimensionality(data, FeatureSize(), "LinearSVM::Classify()"); if (fitIntercept) { diff --git a/src/mlpack/methods/lsh/lsh_search_impl.hpp b/src/mlpack/methods/lsh/lsh_search_impl.hpp index c3838bd4b6..6c3aeb8259 100644 --- a/src/mlpack/methods/lsh/lsh_search_impl.hpp +++ b/src/mlpack/methods/lsh/lsh_search_impl.hpp @@ -865,14 +865,8 @@ void LSHSearch::Search( const size_t T) { // Ensure the dimensionality of the query set is correct. - if (querySet.n_rows != referenceSet.n_rows) - { - std::ostringstream oss; - oss << "LSHSearch::Search(): dimensionality of query set (" - << querySet.n_rows << ") is not equal to the dimensionality the model " - << "was trained on (" << referenceSet.n_rows << ")!" << std::endl; - throw std::invalid_argument(oss.str()); - } + util::CheckSameDimensionality(querySet, referenceSet, "LSHSearch::Search()", + "query set"); if (k > referenceSet.n_cols) { diff --git a/src/mlpack/methods/neighbor_search/kfn_main.cpp b/src/mlpack/methods/neighbor_search/kfn_main.cpp index 73b9dbd64f..65f3083d56 100644 --- a/src/mlpack/methods/neighbor_search/kfn_main.cpp +++ b/src/mlpack/methods/neighbor_search/kfn_main.cpp @@ -237,14 +237,14 @@ static void mlpackMain() kfn->TreeType() = tree; kfn->RandomBasis() = randomBasis; + kfn->LeafSize() = size_t(lsInt); Log::Info << "Using reference data from " << IO::GetPrintableParam("reference") << "." << endl; arma::mat referenceSet = std::move(IO::GetParam("reference")); - kfn->BuildModel(std::move(referenceSet), size_t(lsInt), searchMode, - epsilon); + kfn->BuildModel(std::move(referenceSet), searchMode, epsilon); } else { diff --git a/src/mlpack/methods/neighbor_search/knn_main.cpp b/src/mlpack/methods/neighbor_search/knn_main.cpp index 9f643ecd61..87ca2203b0 100644 --- a/src/mlpack/methods/neighbor_search/knn_main.cpp +++ b/src/mlpack/methods/neighbor_search/knn_main.cpp @@ -261,8 +261,7 @@ static void mlpackMain() arma::mat referenceSet = std::move(IO::GetParam("reference")); - knn->BuildModel(std::move(referenceSet), size_t(lsInt), searchMode, - epsilon); + knn->BuildModel(std::move(referenceSet), searchMode, epsilon); } else { diff --git a/src/mlpack/methods/neighbor_search/neighbor_search.hpp b/src/mlpack/methods/neighbor_search/neighbor_search.hpp index 1475970af6..2476e0484a 100644 --- a/src/mlpack/methods/neighbor_search/neighbor_search.hpp +++ b/src/mlpack/methods/neighbor_search/neighbor_search.hpp @@ -31,8 +31,13 @@ namespace mlpack { namespace neighbor { // Forward declaration. -template -class TrainVisitor; +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +class LeafSizeNSWrapper; //! NeighborSearchMode represents the different neighbor search modes available. enum NeighborSearchMode @@ -359,8 +364,8 @@ class NeighborSearch bool treeNeedsReset; //! The NSModel class should have access to internal members. - template - friend class TrainVisitor; + friend class LeafSizeNSWrapper; }; // class NeighborSearch } // namespace neighbor diff --git a/src/mlpack/methods/neighbor_search/ns_model.hpp b/src/mlpack/methods/neighbor_search/ns_model.hpp index 981d0f9be9..b13918fa7d 100644 --- a/src/mlpack/methods/neighbor_search/ns_model.hpp +++ b/src/mlpack/methods/neighbor_search/ns_model.hpp @@ -4,8 +4,9 @@ * * This is a model for nearest or furthest neighbor search. It is useful in * that it provides an easy way to serialize a model, abstracts away the - * different types of trees, and also reflects the NeighborSearch API and - * automatically directs to the right tree type. + * different types of trees, and also (roughly) reflects the NeighborSearch API + * and automatically directs to the right tree type. It is meant to be used by + * the knn and kfn 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 @@ -20,218 +21,302 @@ #include #include #include -#include #include "neighbor_search.hpp" namespace mlpack { namespace neighbor { /** - * Alias template for euclidean neighbor search. + * NSWrapperBase is a base wrapper class for holding all NeighborSearch types + * supported by NSModel. All NeighborSearch type wrappers inherit from this + * class, allowing a simple interface via inheritance for all the different + * types we want to support. + */ +class NSWrapperBase +{ + public: + //! Create the NSWrapperBase object. The base class does not hold anything, + //! so this constructor does not do anything. + NSWrapperBase() { } + + //! Create a new NSWrapperBase that is the same as this one. This function + //! will properly handle polymorphism. + virtual NSWrapperBase* Clone() const = 0; + + //! Destruct the NSWrapperBase (nothing to do). + virtual ~NSWrapperBase() { }; + + //! Return a reference to the dataset. + virtual const arma::mat& Dataset() const = 0; + + //! Get the search mode. + virtual NeighborSearchMode SearchMode() const = 0; + //! Modify the search modem + virtual NeighborSearchMode& SearchMode() = 0; + + //! Get the approximation parameter epsilon. + virtual double Epsilon() const = 0; + //! Modify the approximation parameter epsilon. + virtual double& Epsilon() = 0; + + //! Train the NeighborSearch model with the given parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize, + const double tau, + const double rho) = 0; + + //! Perform bichromatic neighbor search (i.e. search with a separate query + //! set). + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double rho) = 0; + + //! Perform monochromatic neighbor search (i.e. use the reference set as the + //! query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) = 0; +}; + +/** + * NSWrapper is a wrapper class for most NeighborSearch types. */ template class TreeType> -using NSType = NeighborSearch, - arma::mat>::template DualTreeTraverser>; - -/** - * MonoSearchVisitor executes a monochromatic neighbor search on the given - * NSType. We don't make any difference for different instantiations of NSType. - */ -class MonoSearchVisitor : public boost::static_visitor + typename TreeMatType> class TreeType, + template class DualTreeTraversalType = + TreeType, + arma::mat>::template DualTreeTraverser, + template class SingleTreeTraversalType = + TreeType, + arma::mat>::template SingleTreeTraverser> +class NSWrapper : public NSWrapperBase { - private: - //! Number of neighbors to search for. - const size_t k; - //! Result matrix for neighbors. - arma::Mat& neighbors; - //! Result matrix for distances. - arma::mat& distances; - public: - //! Perform monochromatic nearest neighbor search. - template - void operator()(NSType* ns) const; + //! Construct the NSWrapper object, initializing the internally-held + //! NeighborSearch object. + NSWrapper(const NeighborSearchMode searchMode, + const double epsilon) : + ns(searchMode, epsilon) + { + // Nothing else to do. + } - //! Construct the MonoSearchVisitor object with the given parameters. - MonoSearchVisitor(const size_t k, - arma::Mat& neighbors, - arma::mat& distances) : - k(k), - neighbors(neighbors), - distances(distances) - {}; + //! Delete the NSWrapper object. + virtual ~NSWrapper() { } + + //! Create a copy of this NSWrapper object. This correctly handles + //! polymorphism. + virtual NSWrapper* Clone() const { return new NSWrapper(*this); } + + //! Get a reference to the reference set. + const arma::mat& Dataset() const { return ns.ReferenceSet(); } + + //! Get the search mode. + NeighborSearchMode SearchMode() const { return ns.SearchMode(); } + //! Modify the search mode. + NeighborSearchMode& SearchMode() { return ns.SearchMode(); } + + //! Get epsilon, the approximation parameter. + double Epsilon() const { return ns.Epsilon(); } + //! Modify epsilon, the approximation parameter. + double& Epsilon() { return ns.Epsilon(); } + + //! Train the model with the given options. For NSWrapper, we ignore the + //! extra parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t /* leafSize */, + const double /* tau */, + const double /* rho */); + + //! Perform bichromatic neighbor search (i.e. search with a separate query + //! set). For NSWrapper, we ignore the extra parameters. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */, + const double /* rho */); + + //! Perform monochromatic neighbor search (i.e. use the reference set as the + //! query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances); + + //! Serialize the NeighborSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ns)); + } + + protected: + // Convenience typedef for the neighbor search type held by this class. + typedef NeighborSearch NSType; + + //! The instantiated NeighborSearch object that we are wrapping. + NSType ns; }; /** - * BiSearchVisitor executes a bichromatic neighbor search on the given NSType. - * We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, before doing neighbor search, - * a query tree with proper leafSize is built from the querySet. + * LeafSizeNSWrapper wraps any NeighborSearch types that take a leaf size for + * tree construction. The implementations of Train() and Search() take the leaf + * size into account. + */ +template class TreeType, + template class DualTreeTraversalType = + TreeType, + arma::mat>::template DualTreeTraverser, + template class SingleTreeTraversalType = + TreeType, + arma::mat>::template SingleTreeTraverser> +class LeafSizeNSWrapper : + public NSWrapper +{ + public: + //! Construct the LeafSizeNSWrapper by delegating to the NSWrapper + //! constructor. + LeafSizeNSWrapper(const NeighborSearchMode searchMode, + const double epsilon) : + NSWrapper(searchMode, epsilon) + { + // Nothing to do. + } + + //! Delete the LeafSizeNSWrapper. + virtual ~LeafSizeNSWrapper() { } + + //! Return a copy of the LeafSizeNSWrapper. + virtual LeafSizeNSWrapper* Clone() const + { + return new LeafSizeNSWrapper(*this); + } + + //! Train a model with the given parameters. This overload uses leafSize but + //! ignores the other parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize, + const double /* tau */, + const double /* rho */); + + //! Perform bichromatic search (e.g. search with a separate query set). This + //! overload uses the leaf size, but ignores the other parameters. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double /* rho */); + + //! Serialize the NeighborSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ns)); + } + + protected: + using NSWrapper::ns; +}; + +/** + * The SpillNSWrapper class wraps the NeighborSearch class when the spill tree + * is used. */ template -class BiSearchVisitor : public boost::static_visitor -{ - private: - //! The query set for the bichromatic search. - const arma::mat& querySet; - //! The number of neighbors to search for. - const size_t k; - //! The result matrix for neighbors. - arma::Mat& neighbors; - //! The result matrix for distances. - arma::mat& distances; - //! The number of points in a leaf (for BinarySpaceTrees). - const size_t leafSize; - //! Overlapping size (for spill trees). - const double tau; - //! Balance threshold (for spill trees). - const double rho; - - //! Bichromatic neighbor search on the given NSType considering the leafSize. - template - void SearchLeaf(NSType* ns) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using NSTypeT = NSType; - - //! Default Bichromatic neighbor search on the given NSType instance. - template class TreeType> - void operator()(NSTypeT* ns) const; - - //! Bichromatic neighbor search on the given NSType specialized for KDTrees. - void operator()(NSTypeT* ns) const; - - //! Bichromatic neighbor search on the given NSType specialized for BallTrees. - void operator()(NSTypeT* ns) const; - - //! Bichromatic neighbor search specialized for SPTrees. - void operator()(SpillKNN* ns) const; - - //! Bichromatic neighbor search specialized for octrees. - void operator()(NSTypeT* ns) const; - - //! Construct the BiSearchVisitor. - BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize, - const double tau, - const double rho); -}; - -/** - * TrainVisitor sets the reference set to a new reference set on the given - * NSType. We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, a reference tree with proper - * leafSize is built from the referenceSet. - */ -template -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set to use for training. - arma::mat&& referenceSet; - //! The leaf size, used only by BinarySpaceTree. - size_t leafSize; - //! Overlapping size (for spill trees). - const double tau; - //! Balance threshold (for spill trees). - const double rho; - - //! Train on the given NSType considering the leafSize. - template - void TrainLeaf(NSType* ns) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using NSTypeT = NSType; - - //! Default Train on the given NSType instance. - template class TreeType> - void operator()(NSTypeT* ns) const; - - //! Train on the given NSType specialized for KDTrees. - void operator()(NSTypeT* ns) const; - - //! Train on the given NSType specialized for BallTrees. - void operator()(NSTypeT* ns) const; - - //! Train specialized for SPTrees. - void operator()(SpillKNN* ns) const; - - //! Train specialized for octrees. - void operator()(NSTypeT* ns) const; - - //! Construct the TrainVisitor object with the given reference set, leafSize - //! for BinarySpaceTrees, and tau and rho for spill trees. - TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize, - const double tau, - const double rho); -}; - -/** - * SearchModeVisitor exposes the SearchMode() method of the given NSType. - */ -class SearchModeVisitor : public boost::static_visitor +class SpillNSWrapper : + public NSWrapper< + SortPolicy, + tree::SPTree, + tree::SPTree, + arma::mat>::template DefeatistDualTreeTraverser, + tree::SPTree, + arma::mat>::template DefeatistSingleTreeTraverser> { public: - //! Return the search mode. - template - NeighborSearchMode& operator()(NSType* ns) const; -}; + //! Construct the SpillNSWrapper. + SpillNSWrapper(const NeighborSearchMode searchMode, + const double epsilon) : + NSWrapper< + SortPolicy, + tree::SPTree, + tree::SPTree, + arma::mat>::template DefeatistDualTreeTraverser, + tree::SPTree, + arma::mat>::template DefeatistSingleTreeTraverser>( + searchMode, epsilon) + { + // Nothing to do. + } -/** - * EpsilonVisitor exposes the Epsilon method of the given NSType. - */ -class EpsilonVisitor : public boost::static_visitor -{ - public: - //! Return epsilon, the approximation parameter. - template - double& operator()(NSType *ns) const; -}; + //! Destruct the SpillNSWrapper. + virtual ~SpillNSWrapper() { } -/** - * ReferenceSetVisitor exposes the referenceSet of the given NSType. - */ -class ReferenceSetVisitor : public boost::static_visitor -{ - public: - //! Return the reference set. - template - const arma::mat& operator()(NSType *ns) const; -}; + //! Return a copy of the SpillNSWrapper. + virtual SpillNSWrapper* Clone() const { return new SpillNSWrapper(*this); } -/** - * DeleteVisitor deletes the given NSType instance. - */ -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete the NSType object. - template - void operator()(NSType *ns) const; + //! Train the model using the given parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize, + const double tau, + const double rho); + + //! Perform bichromatic search (i.e. search with a different query set) using + //! the given parameters. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double rho); + + //! Serialize the NeighborSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ns)); + } + + protected: + using NSWrapper< + SortPolicy, + tree::SPTree, + tree::SPTree, + arma::mat>::template DefeatistDualTreeTraverser, + tree::SPTree, + arma::mat>::template DefeatistSingleTreeTraverser>::ns; }; /** @@ -272,39 +357,20 @@ class NSModel //! Tree type considered for neighbor search. TreeTypes treeType; - //! For tree types that accept the maxLeafSize parameter. - size_t leafSize; - - //! Overlapping size (for spill trees). - double tau; - //! Balance threshold (for spill trees). - double rho; - //! If true, random projections are used. bool randomBasis; //! This is the random projection matrix; only used if randomBasis is true. arma::mat q; + size_t leafSize; + double tau; + double rho; + /** - * nSearch holds an instance of the NeigborSearch class for the current + * nSearch holds an instance of the NeighborSearch class for the current * treeType. It is initialized every time BuildModel is executed. - * We access to the contained value through the visitor classes defined above. */ - boost::variant*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - SpillKNN*, - NSType*, - NSType*> nSearch; + NSWrapperBase* nSearch; public: /** @@ -359,22 +425,22 @@ class NSModel NeighborSearchMode SearchMode() const; NeighborSearchMode& SearchMode(); - //! Expose Epsilon. - double Epsilon() const; - double& Epsilon(); - - //! Expose leafSize. + //! Expose LeafSize. size_t LeafSize() const { return leafSize; } size_t& LeafSize() { return leafSize; } - //! Expose tau. + //! Expose Tau. double Tau() const { return tau; } double& Tau() { return tau; } - //! Expose rho. + //! Expose Rho. double Rho() const { return rho; } double& Rho() { return rho; } + //! Expose Epsilon. + double Epsilon() const; + double& Epsilon(); + //! Expose treeType. TreeTypes TreeType() const { return treeType; } TreeTypes& TreeType() { return treeType; } @@ -383,9 +449,12 @@ class NSModel bool RandomBasis() const { return randomBasis; } bool& RandomBasis() { return randomBasis; } + //! Initialize the model type. (This does not perform any training.) + void InitializeModel(const NeighborSearchMode searchMode, + const double epsilon); + //! Build the reference tree. void BuildModel(arma::mat&& referenceSet, - const size_t leafSize, const NeighborSearchMode searchMode, const double epsilon = 0); diff --git a/src/mlpack/methods/neighbor_search/ns_model_impl.hpp b/src/mlpack/methods/neighbor_search/ns_model_impl.hpp index 8c90aa9ec8..319fb652af 100644 --- a/src/mlpack/methods/neighbor_search/ns_model_impl.hpp +++ b/src/mlpack/methods/neighbor_search/ns_model_impl.hpp @@ -21,107 +21,121 @@ namespace mlpack { namespace neighbor { -//! Monochromatic neighbor search on the given NSType instance. -template -void MonoSearchVisitor::operator()(NSType *ns) const -{ - if (ns) - return ns->Search(k, neighbors, distances); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Save parameters for bichromatic neighbor search. -template -BiSearchVisitor::BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize, - const double tau, - const double rho) : - querySet(querySet), - k(k), - neighbors(neighbors), - distances(distances), - leafSize(leafSize), - tau(tau), - rho(rho) -{} - -//! Default Bichromatic neighbor search on the given NSType instance. -template -template class TreeType> -void BiSearchVisitor::operator()(NSTypeT* ns) const + typename TreeMatType> class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void NSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Train(arma::mat&& referenceSet, + const size_t /* leafSize */, + const double /* tau */, + const double /* rho */) { - if (ns) - return ns->Search(querySet, k, neighbors, distances); - throw std::runtime_error("no neighbor search model initialized"); + ns.Train(std::move(referenceSet)); } -//! Bichromatic neighbor search on the given NSType specialized for KDTrees. -template -void BiSearchVisitor::operator()(NSTypeT* ns) const +//! Perform bichromatic neighbor search (i.e. search with a separate query +//! set). For NSWrapper, we ignore the extra parameters. +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void NSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */, + const double /* rho */) { - if (ns) - return SearchLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); + ns.Search(std::move(querySet), k, neighbors, distances); } -//! Bichromatic neighbor search on the given NSType specialized for BallTrees. -template -void BiSearchVisitor::operator()(NSTypeT* ns) const +//! Perform monochromatic neighbor search (i.e. use the reference set as the +//! query set). +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void NSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) { - if (ns) - return SearchLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); + ns.Search(k, neighbors, distances); } -//! Bichromatic neighbor search specialized for SPTrees. -template -void BiSearchVisitor::operator()(SpillKNN* ns) const +//! Train a model with the given parameters. This overload uses leafSize but +//! ignores the other parameters. +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void LeafSizeNSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Train(arma::mat&& referenceSet, + const size_t leafSize, + const double /* tau */, + const double /* rho */) { - if (ns) + if (ns.SearchMode() == NAIVE_MODE) { - if (ns->SearchMode() == DUAL_TREE_MODE) - { - // For Dual Tree Search on SpillTrees, the queryTree must be built with - // non overlapping (tau = 0). - typename SpillKNN::Tree queryTree(std::move(querySet), 0 /* tau*/, - leafSize, rho); - ns->Search(queryTree, k, neighbors, distances); - } - else - ns->Search(querySet, k, neighbors, distances); + ns.Train(std::move(referenceSet)); } else - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Bichromatic neighbor search specialized for octrees. -template -void BiSearchVisitor::operator()(NSTypeT* ns) const -{ - if (ns) - return SearchLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Bichromatic neighbor search on the given NSType considering the leafSize. -template -template -void BiSearchVisitor::SearchLeaf(NSType* ns) const -{ - if (ns->SearchMode() == DUAL_TREE_MODE) { + // Build the tree with the specified leaf size. + std::vector oldFromNewReferences; + typename decltype(ns)::Tree referenceTree(std::move(referenceSet), + oldFromNewReferences, leafSize); + ns.Train(std::move(referenceTree)); + ns.oldFromNewReferences = std::move(oldFromNewReferences); + } +} + +//! Perform bichromatic search (e.g. search with a separate query set). This +//! overload uses the leaf size, but ignores the other parameters. +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void LeafSizeNSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double /* rho */) +{ + if (ns.SearchMode() == DUAL_TREE_MODE) + { + // We actually have to do the mapping of query points ourselves, since the + // NeighborSearch class does not provide a way for us to specify the leaf + // size when building the query tree. (Therefore we must also build the + // query tree manually.) std::vector oldFromNewQueries; - typename NSType::Tree queryTree(std::move(querySet), oldFromNewQueries, - leafSize); + typename decltype(ns)::Tree queryTree(std::move(querySet), + oldFromNewQueries, leafSize); arma::Mat neighborsOut; arma::mat distancesOut; - ns->Search(queryTree, k, neighborsOut, distancesOut); + ns.Search(queryTree, k, neighborsOut, distancesOut); // Unmap the query points. distances.set_size(distancesOut.n_rows, distancesOut.n_cols); @@ -133,131 +147,47 @@ void BiSearchVisitor::SearchLeaf(NSType* ns) const } } else - ns->Search(querySet, k, neighbors, distances); + { + ns.Search(querySet, k, neighbors, distances); + } } -//! Save parameters for Train. +//! Train the model using the given parameters. template -TrainVisitor::TrainVisitor(arma::mat&& referenceSet, +void SpillNSWrapper::Train(arma::mat&& referenceSet, const size_t leafSize, const double tau, - const double rho) : - referenceSet(std::move(referenceSet)), - leafSize(leafSize), - tau(tau), - rho(rho) -{} - -//! Default Train on the given NSType instance. -template -template class TreeType> -void TrainVisitor::operator()(NSTypeT* ns) const + const double rho) { - if (ns) - return ns->Train(std::move(referenceSet)); - throw std::runtime_error("no neighbor search model initialized"); + typename decltype(ns)::Tree tree(std::move(referenceSet), tau, leafSize, + rho); + ns.Train(std::move(tree)); } -//! Train on the given NSType specialized for KDTrees. +//! Perform bichromatic search (i.e. search with a different query set) using +//! the given parameters. template -void TrainVisitor::operator()(NSTypeT* ns) const +void SpillNSWrapper::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double rho) { - if (ns) - return TrainLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train on the given NSType specialized for BallTrees. -template -void TrainVisitor::operator()(NSTypeT* ns) const -{ - if (ns) - return TrainLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train specialized for SPTrees. -template -void TrainVisitor::operator()(SpillKNN* ns) const -{ - if (ns) + if (ns.SearchMode() == DUAL_TREE_MODE) { - if (ns->SearchMode() == NAIVE_MODE) - ns->Train(std::move(referenceSet)); - else - { - typename SpillKNN::Tree tree(std::move(referenceSet), tau, leafSize, rho); - ns->Train(std::move(tree)); - } + // For Dual Tree Search on SpillTrees, the queryTree must be built with + // non overlapping (tau = 0). + typename decltype(ns)::Tree queryTree(std::move(querySet), 0 /* tau */, + leafSize, rho); + ns.Search(queryTree, k, neighbors, distances); } - else - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train specialized for Octrees. -template -void TrainVisitor::operator()(NSTypeT* ns) const -{ - if (ns) - return TrainLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train on the given NSType considering the leafSize. -template -template -void TrainVisitor::TrainLeaf(NSType* ns) const -{ - if (ns->SearchMode() == NAIVE_MODE) - ns->Train(std::move(referenceSet)); else { - std::vector oldFromNewReferences; - typename NSType::Tree referenceTree(std::move(referenceSet), - oldFromNewReferences, leafSize); - ns->Train(std::move(referenceTree)); - // Set the mappings. - ns->oldFromNewReferences = std::move(oldFromNewReferences); + ns.Search(querySet, k, neighbors, distances); } } -//! Return the search mode. -template -NeighborSearchMode& SearchModeVisitor::operator()(NSType* ns) const -{ - if (ns) - return ns->SearchMode(); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Expose the Epsilon method of the given NSType. -template -double& EpsilonVisitor::operator()(NSType* ns) const -{ - if (ns) - return ns->Epsilon(); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Expose the referenceSet of the given NSType. -template -const arma::mat& ReferenceSetVisitor::operator()(NSType* ns) const -{ - if (ns) - return ns->ReferenceSet(); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Clean memory, if necessary. -template -void DeleteVisitor::operator()(NSType* ns) const -{ - if (ns) - delete ns; -} - /** * Initialize the NSModel with the given type and whether or not a random * basis should be used. @@ -265,10 +195,11 @@ void DeleteVisitor::operator()(NSType* ns) const template NSModel::NSModel(TreeTypes treeType, bool randomBasis) : treeType(treeType), + randomBasis(randomBasis), leafSize(20), - tau(0), + tau(0.0), rho(0.7), - randomBasis(randomBasis) + nSearch(NULL) { // Nothing to do. } @@ -276,12 +207,12 @@ NSModel::NSModel(TreeTypes treeType, bool randomBasis) : template NSModel::NSModel(const NSModel& other) : treeType(other.treeType), + randomBasis(other.randomBasis), + q(other.q), leafSize(other.leafSize), tau(other.tau), rho(other.rho), - randomBasis(other.randomBasis), - q(other.q), - nSearch(other.nSearch) + nSearch(other.nSearch->Clone()) { // Nothing to do. } @@ -289,34 +220,37 @@ NSModel::NSModel(const NSModel& other) : template NSModel::NSModel(NSModel&& other) : treeType(other.treeType), + randomBasis(other.randomBasis), + q(std::move(other.q)), leafSize(other.leafSize), tau(other.tau), rho(other.rho), - randomBasis(other.randomBasis), - q(std::move(other.q)), nSearch(other.nSearch) { // Reset parameters of the other model. other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.tau = 0; - other.rho = 0.7; other.randomBasis = false; - other.nSearch = decltype(other.nSearch)(); + other.leafSize = 20; + other.tau = 0.0; + other.rho = 0.7; + other.nSearch = NULL; } template NSModel& NSModel::operator=(const NSModel& other) { - boost::apply_visitor(DeleteVisitor(), nSearch); + if (this != &other) + { + delete nSearch; - treeType = other.treeType; - leafSize = other.leafSize; - tau = other.tau; - rho = other.rho; - randomBasis = other.randomBasis; - q = other.q; - nSearch = other.nSearch; + treeType = other.treeType; + randomBasis = other.randomBasis; + q = other.q; + leafSize = other.leafSize; + tau = other.tau; + rho = other.rho; + nSearch = other.nSearch->Clone(); + } return *this; } @@ -324,24 +258,26 @@ NSModel& NSModel::operator=(const NSModel& other) template NSModel& NSModel::operator=(NSModel&& other) { - boost::apply_visitor(DeleteVisitor(), nSearch); + if (this != &other) + { + delete nSearch; - treeType = other.treeType; - leafSize = other.leafSize; - tau = other.tau; - rho = other.rho; - randomBasis = other.randomBasis; - q = std::move(other.q); - // Copy the pointer and type. - nSearch = other.nSearch; + treeType = other.treeType; + randomBasis = other.randomBasis; + q = std::move(other.q); + leafSize = other.leafSize; + tau = other.tau; + rho = other.rho; + nSearch = other.nSearch; - // Reset parameters of the other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.tau = 0; - other.rho = 0.7; - other.randomBasis = false; - other.nSearch = decltype(other.nSearch)(); + // Reset parameters of the other model. + other.treeType = TreeTypes::KD_TREE; + other.randomBasis = false; + other.leafSize = 20; + other.tau = 0.0; + other.rho = 0.7; + other.nSearch = NULL; + } return *this; } @@ -350,7 +286,7 @@ NSModel& NSModel::operator=(NSModel&& other) template NSModel::~NSModel() { - boost::apply_visitor(DeleteVisitor(), nSearch); + delete nSearch; } //! Serialize the kNN model. @@ -359,60 +295,236 @@ template void NSModel::serialize(Archive& ar, const uint32_t /* version */) { ar(CEREAL_NVP(treeType)); + ar(CEREAL_NVP(randomBasis)); + ar(CEREAL_NVP(q)); ar(CEREAL_NVP(leafSize)); ar(CEREAL_NVP(tau)); ar(CEREAL_NVP(rho)); - ar(CEREAL_NVP(randomBasis)); - ar(CEREAL_NVP(q)); // This should never happen, but just in case, be clean with memory. if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), nSearch); + InitializeModel(DUAL_TREE_MODE, 0.0); // Values will be overwritten. - ar(CEREAL_VARIANT_POINTER(nSearch)); + // Avoid polymorphic serialization by explicitly serializing the correct type. + switch (treeType) + { + case KD_TREE: + { + LeafSizeNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case COVER_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_STAR_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case BALL_TREE: + { + LeafSizeNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case X_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case HILBERT_R_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_PLUS_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_PLUS_PLUS_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case SPILL_TREE: + { + SpillNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case VP_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case RP_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case MAX_RP_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case UB_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case OCTREE: + { + LeafSizeNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + } } //! Expose the dataset. template const arma::mat& NSModel::Dataset() const { - return boost::apply_visitor(ReferenceSetVisitor(), nSearch); + return nSearch->Dataset(); } //! Access the search mode. template NeighborSearchMode NSModel::SearchMode() const { - return boost::apply_visitor(SearchModeVisitor(), nSearch); + return nSearch->SearchMode(); } //! Modify the search mode. template NeighborSearchMode& NSModel::SearchMode() { - return boost::apply_visitor(SearchModeVisitor(), nSearch); + return nSearch->SearchMode(); } template double NSModel::Epsilon() const { - return boost::apply_visitor(EpsilonVisitor(), nSearch); + return nSearch->Epsilon(); } template double& NSModel::Epsilon() { - return boost::apply_visitor(EpsilonVisitor(), nSearch); + return nSearch->Epsilon(); +} + +//! Initialize a model given the tree type. (No training happens here.) +template +void NSModel::InitializeModel(const NeighborSearchMode searchMode, + const double epsilon) +{ + // Clear existing memory. + if (nSearch) + delete nSearch; + + switch (treeType) + { + case KD_TREE: + nSearch = new LeafSizeNSWrapper(searchMode, + epsilon); + break; + case COVER_TREE: + nSearch = new NSWrapper(searchMode, + epsilon); + break; + case R_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case R_STAR_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case BALL_TREE: + nSearch = new LeafSizeNSWrapper(searchMode, + epsilon); + break; + case X_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case HILBERT_R_TREE: + nSearch = new NSWrapper(searchMode, + epsilon); + break; + case R_PLUS_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case R_PLUS_PLUS_TREE: + nSearch = new NSWrapper(searchMode, + epsilon); + break; + case VP_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case RP_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case MAX_RP_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case SPILL_TREE: + nSearch = new SpillNSWrapper(searchMode, epsilon); + break; + case UB_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case OCTREE: + nSearch = new LeafSizeNSWrapper(searchMode, + epsilon); + break; + } + } //! Build the reference tree. template void NSModel::BuildModel(arma::mat&& referenceSet, - const size_t leafSize, const NeighborSearchMode searchMode, const double epsilon) { - this->leafSize = leafSize; // Initialize random basis if necessary. if (randomBasis) { @@ -445,9 +557,6 @@ void NSModel::BuildModel(arma::mat&& referenceSet, } } - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), nSearch); - // Do we need to modify the reference set? if (randomBasis) referenceSet = q * referenceSet; @@ -458,59 +567,8 @@ void NSModel::BuildModel(arma::mat&& referenceSet, Log::Info << "Building reference tree..." << std::endl; } - switch (treeType) - { - case KD_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case COVER_TREE: - nSearch = new NSType(searchMode, - epsilon); - break; - case R_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case R_STAR_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case BALL_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case X_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case HILBERT_R_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case R_PLUS_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case R_PLUS_PLUS_TREE: - nSearch = new NSType(searchMode, - epsilon); - break; - case VP_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case RP_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case MAX_RP_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case SPILL_TREE: - nSearch = new SpillKNN(searchMode, epsilon); - break; - case UB_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case OCTREE: - nSearch = new NSType(searchMode, epsilon); - break; - } - - TrainVisitor tn(std::move(referenceSet), leafSize, tau, rho); - boost::apply_visitor(tn, nSearch); + InitializeModel(searchMode, epsilon); + nSearch->Train(std::move(referenceSet), leafSize, tau, rho); if (searchMode != NAIVE_MODE) { @@ -549,9 +607,7 @@ void NSModel::Search(arma::mat&& querySet, break; } - BiSearchVisitor search(querySet, k, neighbors, distances, - leafSize, tau, rho); - boost::apply_visitor(search, nSearch); + nSearch->Search(std::move(querySet), k, neighbors, distances, leafSize, rho); } //! Perform neighbor search. @@ -583,8 +639,7 @@ void NSModel::Search(const size_t k, Log::Info << "Maximum of " << Epsilon() * 100 << "% relative error." << std::endl; - MonoSearchVisitor search(k, neighbors, distances); - boost::apply_visitor(search, nSearch); + nSearch->Search(k, neighbors, distances); } //! Get the name of the tree type. diff --git a/src/mlpack/methods/pca/pca.hpp b/src/mlpack/methods/pca/pca.hpp index 594cb47344..feae5322a5 100644 --- a/src/mlpack/methods/pca/pca.hpp +++ b/src/mlpack/methods/pca/pca.hpp @@ -68,6 +68,14 @@ class PCA void Apply(const arma::mat& data, arma::mat& transformedData, arma::vec& eigVal); + /** + * Apply Principal Component Analysis to the provided data set. It is safe + * to pass the same matrix reference for both data and transformedData. + * @param data Data matrix. + * @param transformedData Matrix to store results of PCA in. + */ + void Apply(const arma::mat& data, + arma::mat& transformedData); /** * Use PCA for dimensionality reduction on the given dataset. This will save diff --git a/src/mlpack/methods/pca/pca_impl.hpp b/src/mlpack/methods/pca/pca_impl.hpp index 360586360a..f469933c14 100644 --- a/src/mlpack/methods/pca/pca_impl.hpp +++ b/src/mlpack/methods/pca/pca_impl.hpp @@ -74,6 +74,21 @@ void PCA::Apply(const arma::mat& data, arma::mat eigvec; Apply(data, transformedData, eigVal, eigvec); } + +/** + * Apply Principal Component Analysis to the provided data set. + * + * @param data - Data matrix. + * @param transformedData Data with PCA applied. + */ +template +void PCA::Apply(const arma::mat& data, + arma::mat& transformedData) +{ + arma::mat eigvec; + arma::vec eigVal; + Apply(data, transformedData, eigVal, eigvec); +} /** * Use PCA for dimensionality reduction on the given dataset. This will save diff --git a/src/mlpack/methods/preprocess/scaling_model.hpp b/src/mlpack/methods/preprocess/scaling_model.hpp index 87693cc796..a5d7082658 100644 --- a/src/mlpack/methods/preprocess/scaling_model.hpp +++ b/src/mlpack/methods/preprocess/scaling_model.hpp @@ -65,6 +65,9 @@ class ScalingModel //! Copy assignment operator. ScalingModel& operator=(const ScalingModel& other); + //! Move assignment operator. + ScalingModel& operator=(ScalingModel&& other); + //! Clean up memory. ~ScalingModel(); diff --git a/src/mlpack/methods/preprocess/scaling_model_impl.hpp b/src/mlpack/methods/preprocess/scaling_model_impl.hpp index dd918de9d9..6da36e6f49 100644 --- a/src/mlpack/methods/preprocess/scaling_model_impl.hpp +++ b/src/mlpack/methods/preprocess/scaling_model_impl.hpp @@ -84,7 +84,7 @@ ScalingModel::ScalingModel(ScalingModel&& other) : } //! Copy assignment operator. -ScalingModel& ScalingModel::operator= (const ScalingModel& other) +ScalingModel& ScalingModel::operator=(const ScalingModel& other) { if (this == &other) { @@ -123,6 +123,36 @@ ScalingModel& ScalingModel::operator= (const ScalingModel& other) return *this; } +//! Move assignment operator. +ScalingModel& ScalingModel::operator=(ScalingModel&& other) +{ + if (this != &other) + { + scalerType = other.scalerType; + minmaxscale = other.minmaxscale; + maxabsscale = other.maxabsscale; + meanscale = other.meanscale; + standardscale = other.standardscale; + pcascale = other.pcascale; + zcascale = other.zcascale; + minValue = other.minValue; + maxValue = other.maxValue; + epsilon = other.epsilon; + + other.scalerType = 0; + other.minmaxscale = nullptr; + other.maxabsscale = nullptr; + other.meanscale = nullptr; + other.standardscale = nullptr; + other.pcascale = nullptr; + other.zcascale = nullptr; + other.minValue = 0; + other.maxValue = 1; + other.epsilon = 0.00005; + } + return *this; +} + ScalingModel::~ScalingModel() { delete minmaxscale; diff --git a/src/mlpack/methods/random_forest/bootstrap.hpp b/src/mlpack/methods/random_forest/bootstrap.hpp index fa6e5af6be..abbbbbaa05 100644 --- a/src/mlpack/methods/random_forest/bootstrap.hpp +++ b/src/mlpack/methods/random_forest/bootstrap.hpp @@ -38,13 +38,10 @@ void Bootstrap(const MatType& dataset, // Random sampling with replacement. arma::uvec indices = arma::randi(dataset.n_cols, arma::distr_param(0, dataset.n_cols - 1)); - for (size_t i = 0; i < dataset.n_cols; ++i) - { - bootstrapDataset.col(i) = dataset.col(indices[i]); - bootstrapLabels[i] = labels[indices[i]]; - if (UseWeights) - bootstrapWeights[i] = weights[indices[i]]; - } + bootstrapDataset = dataset.cols(indices); + bootstrapLabels = labels.cols(indices); + if (UseWeights) + bootstrapWeights = weights.cols(indices); } } // namespace tree diff --git a/src/mlpack/methods/range_search/CMakeLists.txt b/src/mlpack/methods/range_search/CMakeLists.txt index 0a1912b6b4..8a0ff5925f 100644 --- a/src/mlpack/methods/range_search/CMakeLists.txt +++ b/src/mlpack/methods/range_search/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES range_search_stat.hpp rs_model.hpp rs_model_impl.hpp + rs_model.cpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/range_search/range_search.hpp b/src/mlpack/methods/range_search/range_search.hpp index 06575005ac..a8363c2c9e 100644 --- a/src/mlpack/methods/range_search/range_search.hpp +++ b/src/mlpack/methods/range_search/range_search.hpp @@ -22,7 +22,10 @@ namespace mlpack { namespace range /** Range-search routines. */ { //! Forward declaration. -class TrainVisitor; +template class TreeType> +class LeafSizeRSWrapper; /** * The RangeSearch class is a template class for performing range searches. It @@ -122,12 +125,18 @@ class RangeSearch RangeSearch(RangeSearch&& other); /** - * Copy the given RangeSearch model. - * Use std::move to pass in the model if the old copy is no longer needed. - * + * Deep copy the given RangeSearch model. + * * @param other RangeSearch model to copy. */ - RangeSearch& operator=(RangeSearch other); + RangeSearch& operator=(const RangeSearch& other); + + /** + * Move the given RangeSearch model. + * + * @param other RangeSearch model to move. + */ + RangeSearch& operator=(RangeSearch&& other); /** * Destroy the RangeSearch object. If trees were created, they will be @@ -310,7 +319,7 @@ class RangeSearch size_t scores; //! For access to mappings when building models. - friend class TrainVisitor; + friend class LeafSizeRSWrapper; }; } // namespace range diff --git a/src/mlpack/methods/range_search/range_search_impl.hpp b/src/mlpack/methods/range_search/range_search_impl.hpp index 298aae995e..cce20339a3 100644 --- a/src/mlpack/methods/range_search/range_search_impl.hpp +++ b/src/mlpack/methods/range_search/range_search_impl.hpp @@ -169,25 +169,61 @@ template class TreeType> RangeSearch& -RangeSearch::operator=(RangeSearch other) +RangeSearch::operator=(const RangeSearch& other) { - // Clean memory first. - if (treeOwner) - delete referenceTree; - if (naive) - delete referenceSet; + if (this != &other) + { + oldFromNewReferences = other.oldFromNewReferences; + referenceTree = other.referenceTree ? new Tree(*other.referenceTree) : nullptr; + referenceSet = other.referenceTree ? &referenceTree->Dataset() : + new MatType(*other.referenceSet); + treeOwner = other.referenceTree; + naive = other.naive; + singleMode = other.singleMode; + metric = other.metric; + baseCases = other.baseCases; + scores = other.scores; + } + return *this; +} - // Move the other model. - oldFromNewReferences = std::move(other.oldFromNewReferences); - referenceTree = other.referenceTree; - referenceSet = other.referenceSet; - treeOwner = other.treeOwner; - naive = other.naive; - singleMode = other.singleMode; - metric = std::move(other.metric); - baseCases = other.baseCases; - scores = other.scores; +template class TreeType> +RangeSearch& +RangeSearch::operator=(RangeSearch&& other) +{ + if (this != &other) + { + // Clean memory first. + if (treeOwner) + delete referenceTree; + if (naive) + delete referenceSet; + // Move the other model. + oldFromNewReferences = std::move(other.oldFromNewReferences); + referenceTree = other.referenceTree; + referenceSet = other.referenceSet; + treeOwner = other.treeOwner; + naive = other.naive; + singleMode = other.singleMode; + metric = std::move(other.metric); + baseCases = other.baseCases; + scores = other.scores; + + // Clear other object. + other.referenceTree = nullptr; + other.referenceSet = nullptr; + other.treeOwner = false; + other.naive = false; + other.singleMode = false; + other.baseCases = 0; + other.scores = 0; + + } return *this; } @@ -254,12 +290,15 @@ void RangeSearch::Train( throw std::invalid_argument("cannot train on given reference tree when " "naive search (without trees) is desired"); + // Can only train when passed argument `referenceTree` is not nullptr. if (treeOwner && referenceTree) + { delete this->referenceTree; - this->referenceTree = referenceTree; - this->referenceSet = &referenceTree->Dataset(); - treeOwner = false; + this->referenceTree = referenceTree; + this->referenceSet = &referenceTree->Dataset(); + treeOwner = false; + } } template::Search( std::vector>& neighbors, std::vector>& distances) { - if (querySet.n_rows != referenceSet->n_rows) - { - std::ostringstream oss; - oss << "RangeSearch::Search(): dimensionalities of query set (" - << querySet.n_rows << ") and reference set (" << referenceSet->n_rows - << ") do not match!"; - throw std::invalid_argument(oss.str()); - } + util::CheckSameDimensionality(querySet, *referenceSet, + "RangeSearch::Search()", "query set"); // If there are no points, there is no search to be done. if (referenceSet->n_cols == 0) diff --git a/src/mlpack/methods/range_search/rs_model.cpp b/src/mlpack/methods/range_search/rs_model.cpp new file mode 100644 index 0000000000..33308cee35 --- /dev/null +++ b/src/mlpack/methods/range_search/rs_model.cpp @@ -0,0 +1,285 @@ +/** + * @file methods/range_search/rs_model.cpp + * @author Ryan Curtin + * + * Implementation of serialize() and inline functions for RSModel. + * + * 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 "rs_model.hpp" + +#include + +namespace mlpack { +namespace range { + +/** + * Initialize the RSModel with the given tree type and whether or not a random + * basis should be used. + */ +RSModel::RSModel(TreeTypes treeType, bool randomBasis) : + treeType(treeType), + leafSize(0), + randomBasis(randomBasis), + rSearch(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +RSModel::RSModel(const RSModel& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(other.q), + rSearch(other.rSearch->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +RSModel::RSModel(RSModel&& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(std::move(other.q)), + rSearch(std::move(other.rSearch)) +{ + // Reset other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 0; + other.randomBasis = false; +} + +// Copy operator. +RSModel& RSModel::operator=(const RSModel& other) +{ + if (this != &other) + { + delete rSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = other.q; + rSearch = other.rSearch->Clone(); + } + + return *this; +} + +// Move operator. +RSModel& RSModel::operator=(RSModel&& other) +{ + if (this != &other) + { + delete rSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = std::move(other.q); + rSearch = std::move(other.rSearch); + + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 0; + other.randomBasis = false; + } + + return *this; +} + +// Clean memory, if necessary. +RSModel::~RSModel() +{ + delete rSearch; +} + +void RSModel::InitializeModel(const bool naive, const bool singleMode) +{ + // Clean memory, if necessary. + delete rSearch; + + switch (treeType) + { + case KD_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case COVER_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_STAR_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case BALL_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case X_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case HILBERT_R_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_PLUS_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_PLUS_PLUS_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case VP_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case RP_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case MAX_RP_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case UB_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case OCTREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + } +} + +void RSModel::BuildModel(arma::mat&& referenceSet, + const size_t leafSize, + const bool naive, + const bool singleMode) +{ + // Initialize random basis if necessary. + if (randomBasis) + { + Log::Info << "Creating random basis..." << std::endl; + math::RandomBasis(q, referenceSet.n_rows); + } + + this->leafSize = leafSize; + + // Do we need to modify the reference set? + if (randomBasis) + referenceSet = q * referenceSet; + + if (!naive) + { + Timer::Start("tree_building"); + Log::Info << "Building reference tree..." << std::endl; + } + + InitializeModel(naive, singleMode); + + rSearch->Train(std::move(referenceSet), leafSize); + + if (!naive) + { + Timer::Stop("tree_building"); + Log::Info << "Tree built." << std::endl; + } +} + +// Perform range search. +void RSModel::Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) +{ + // We may need to map the query set randomly. + if (randomBasis) + querySet = q * querySet; + + Log::Info << "Search for points in the range [" << range.Lo() << ", " + << range.Hi() << "] with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; + else if (!Naive()) + Log::Info << "single-tree " << TreeName() << " search..." << std::endl; + else + Log::Info << "brute-force (naive) search..." << std::endl; + + rSearch->Search(std::move(querySet), range, neighbors, distances, leafSize); +} + +// Perform range search (monochromatic case). +void RSModel::Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) +{ + Log::Info << "Search for points in the range [" << range.Lo() << ", " + << range.Hi() << "] with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; + else if (!Naive()) + Log::Info << "single-tree " << TreeName() << " search..." << std::endl; + else + Log::Info << "brute-force (naive) search..." << std::endl; + + rSearch->Search(range, neighbors, distances); +} + +// Get the name of the tree type. +std::string RSModel::TreeName() const +{ + switch (treeType) + { + case KD_TREE: + return "kd-tree"; + case COVER_TREE: + return "cover tree"; + case R_TREE: + return "R tree"; + case R_STAR_TREE: + return "R* tree"; + case BALL_TREE: + return "ball tree"; + case X_TREE: + return "X tree"; + case HILBERT_R_TREE: + return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; + case VP_TREE: + return "vantage point tree"; + case RP_TREE: + return "random projection tree (mean split)"; + case MAX_RP_TREE: + return "random projection tree (max split)"; + case UB_TREE: + return "UB tree"; + case OCTREE: + return "octree"; + default: + return "unknown tree"; + } +} + +// Clean memory. +void RSModel::CleanMemory() +{ + delete rSearch; +} + +} // namespace range +} // namespace mlpack diff --git a/src/mlpack/methods/range_search/rs_model.hpp b/src/mlpack/methods/range_search/rs_model.hpp index ab71f20a21..430274cd9b 100644 --- a/src/mlpack/methods/range_search/rs_model.hpp +++ b/src/mlpack/methods/range_search/rs_model.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include "range_search.hpp" @@ -27,189 +26,183 @@ namespace mlpack { namespace range { /** - * Alias template for Range Search. + * RSWrapperBase is a base wrapper class for holding all RangeSearch types + * supported by RSModel. All RangeSearch type wrappers inherit from this class, + * allowing a simple interface via inheritance for all the different types we + * want to support. + */ +class RSWrapperBase +{ + public: + //! Create the RSWrapperBase object. The base class does not hold anything, + //! so this constructor does nothing. + RSWrapperBase() { } + + //! Create a new RSWrapperBase that is the same as this one. This function + //! will properly handle polymorphism. + virtual RSWrapperBase* Clone() const = 0; + + //! Destruct the RSWrapperBase (nothing to do). + virtual ~RSWrapperBase() { } + + //! Get the dataset. + virtual const arma::mat& Dataset() const = 0; + + //! Get whether single-tree search is being used. + virtual bool SingleMode() const = 0; + //! Modify whether single-tree search is being used. + virtual bool& SingleMode() = 0; + + //! Get whether naive search is being used. + virtual bool Naive() const = 0; + //! Modify whether naive search is being used. + virtual bool& Naive() = 0; + + //! Train the model (build the reference tree if needed). + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize) = 0; + + //! Perform bichromatic range search (i.e. a search with a separate query + //! set). + virtual void Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t leafSize) = 0; + + //! Perform monochromatic range search (i.e. a search with the reference set + //! as the query set). + virtual void Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) = 0; +}; + +/** + * RSWrapper is a wrapper class for most RangeSearch types. */ template class TreeType> -using RSType = RangeSearch; - -/** - * MonoSearchVisitor executes a monochromatic range search on the given - * RSType. Range Search is performed on the reference set itself, no querySet. - */ -class MonoSearchVisitor : public boost::static_visitor +class RSWrapper : public RSWrapperBase { - private: - //! The range to search for. - const math::Range& range; - //! Output neighbors. - std::vector>& neighbors; - //! Output distances. - std::vector>& distances; - public: - //! Perform monochromatic search with the given RangeSearch object. - template - void operator()(RSType* rs) const; + //! Create the RSWrapper object. + RSWrapper(const bool singleMode, const bool naive) : + rs(singleMode, naive) + { + // Nothing else to do. + } - //! Construct the MonoSearchVisitor with the given parameters. - MonoSearchVisitor(const math::Range& range, - std::vector>& neighbors, - std::vector>& distances): - range(range), - neighbors(neighbors), - distances(distances) - {}; + //! Create a new RSWrapper that is the same as this one. This function + //! will properly handle polymorphism. + virtual RSWrapper* Clone() const { return new RSWrapper(*this); } + + //! Destruct the RSWrapper (nothing to do). + virtual ~RSWrapper() { } + + //! Get the dataset. + const arma::mat& Dataset() const { return rs.ReferenceSet(); } + + //! Get whether single-tree search is being used. + bool SingleMode() const { return rs.SingleMode(); } + //! Modify whether single-tree search is being used. + bool& SingleMode() { return rs.SingleMode(); } + + //! Get whether naive search is being used. + bool Naive() const { return rs.Naive(); } + //! Modify whether naive search is being used. + bool& Naive() { return rs.Naive(); } + + //! Train the model (build the reference tree if needed). This ignores the + //! leaf size. + virtual void Train(arma::mat&& referenceSet, + const size_t /* leafSize */); + + //! Perform bichromatic range search (i.e. a search with a separate query + //! set). This ignores the leaf size. + virtual void Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t /* leafSize */); + + //! Perform monochromatic range search (i.e. a search with the reference set + //! as the query set). + virtual void Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances); + + //! Serialize the RangeSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(rs)); + } + + protected: + typedef RangeSearch RSType; + + //! The instantiated RangeSearch object that we are wrapping. + RSType rs; }; /** - * BiSearchVisitor executes a bichromatic range search on the given RSType. - * We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, before doing range search, - * a query tree with proper leafSize is built from the querySet. + * LeafSizeRSWrapper wraps any RangeSearch type that needs to be able to take + * the leaf size into account when building trees. The implementations of + * Train() and bichromatic Search() take this leaf size into account. */ -class BiSearchVisitor : public boost::static_visitor +template class TreeType> +class LeafSizeRSWrapper : public RSWrapper { - private: - //! The query set for the bichromatic search. - const arma::mat& querySet; - //! Range to search neighbours for. - const math::Range& range; - //! The result vector for neighbors. - std::vector>& neighbors; - //! The result vector for distances. - std::vector>& distances; - //! The number of points in a leaf (for BinarySpaceTrees). - const size_t leafSize; - - //! Bichromatic range search on the given RSType considering the leafSize. - template - void SearchLeaf(RSType* rs) const; - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RSTypeT = RSType; + //! Construct the LeafSizeRSWrapper by delegating to the RSWrapper + //! constructor. + LeafSizeRSWrapper(const bool singleMode, const bool naive) : + RSWrapper(singleMode, naive) + { + // Nothing else to do. + } - //! Default Bichromatic range search on the given RSType instance. - template class TreeType> - void operator()(RSTypeT* rs) const; + //! Delete the LeafSizeRSWrapper. + virtual ~LeafSizeRSWrapper() { } - //! Bichromatic range search on the given RSType specialized for KDTrees. - void operator()(RSTypeT* rs) const; + //! Return a copy of the LeafSizeRSWrapper. + virtual LeafSizeRSWrapper* Clone() const + { + return new LeafSizeRSWrapper(*this); + } - //! Bichromatic range search on the given RSType specialized for BallTrees. - void operator()(RSTypeT* rs) const; + //! Train a model with the given parameters. This overload uses leafSize. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize); - //! Bichromatic range search specialized for octrees. - void operator()(RSTypeT* rs) const; + //! Perform bichromatic search (e.g. search with a separate query set). This + //! overload takes the leaf size into account when building the query tree. + virtual void Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t leafSize); - //! Construct the BiSearchVisitor. - BiSearchVisitor(const arma::mat& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances, - const size_t leafSize); + //! Serialize the RangeSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(rs)); + } + + protected: + using RSWrapper::rs; }; /** - * TrainVisitor sets the reference set to a new reference set on the given - * RSType. We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, a reference tree with proper - * leafSize is built from the referenceSet. + * The RSModel class provides an abstraction for the RangeSearch class, + * abstracting away the TreeType parameter and allowing it to be specified at + * runtime. This class is written for the sake of the `range_search` binding, + * but is not necessarily restricted to that usage. */ -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set to use for training. - arma::mat&& referenceSet; - //! The leaf size, used only by BinarySpaceTree. - size_t leafSize; - //! Train on the given RsType considering the leafSize. - template - void TrainLeaf(RSType* rs) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RSTypeT = RSType; - - //! Default Train on the given RSType instance. - template class TreeType> - void operator()(RSTypeT* rs) const; - - //! Train on the given RSType specialized for KDTrees. - void operator()(RSTypeT* rs) const; - - //! Train on the given RSType specialized for BallTrees. - void operator()(RSTypeT* rs) const; - - //! Train specialized for octrees. - void operator()(RSTypeT* rs) const; - - //! Construct the TrainVisitor object with the given reference set, leafSize - TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize); -}; - -/** - * ReferenceSetVisitor exposes the referenceSet of the given RSType. - */ -class ReferenceSetVisitor : public boost::static_visitor -{ - public: - //! Return the reference set. - template - const arma::mat& operator()(RSType* rs) const; -}; - -/** - * DeleteVisitor deletes the given RSType instance. - */ -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete the RSType object. - template - void operator()(RSType* rs) const; -}; - -/** - * SingleModeVisitor exposes the SingleMode() method of the given RSType. - */ -class SingleModeVisitor : public boost::static_visitor -{ - public: - /** - * Get a reference to the singleMode parameter of the given RangeSeach - * object. - */ - template - bool& operator()(RSType* rs) const; -}; - -/** - * NaiveVisitor exposes the Naive() method of the given RSType. - */ -class NaiveVisitor : public boost::static_visitor -{ - public: - /** - * Get a reference to the naive parameter of the given RangeSearch object. - */ - template - bool& operator()(RSType* rs) const; -}; - class RSModel { public: @@ -231,36 +224,6 @@ class RSModel OCTREE }; - private: - TreeTypes treeType; - size_t leafSize; - - //! If true, we randomly project the data into a new basis before search. - bool randomBasis; - //! Random projection matrix. - arma::mat q; - - /** - * rSearch holds an instance of the RangeSearch class for the current - * treeType. It is initialized every time BuildModel is executed. - * We access to the contained value through the visitor classes defined above. - */ - boost::variant*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*> rSearch; - - public: /** * Initialize the RSModel with the given type and whether or not a random * basis should be used. @@ -288,11 +251,16 @@ class RSModel /** * Copy the given RSModel. * - * Use std::move to pass in the model if the old copy is no longer needed. + * @param other RSModel to copy. + */ + RSModel& operator=(const RSModel& other); + + /** + * Take ownership of the given RSModel's data. * * @param other RSModel to copy. */ - RSModel& operator=(RSModel other); + RSModel& operator=(RSModel&& other); /** * Clean memory, if necessary. @@ -304,17 +272,17 @@ class RSModel void serialize(Archive& ar, const uint32_t /* version */); //! Expose the dataset. - const arma::mat& Dataset() const; + const arma::mat& Dataset() const { return rSearch->Dataset(); } //! Get whether the model is in single-tree search mode. - bool SingleMode() const; + bool SingleMode() const { return rSearch->SingleMode(); } //! Modify whether the model is in single-tree search mode. - bool& SingleMode(); + bool& SingleMode() { return rSearch->SingleMode(); } //! Get whether the model is in naive search mode. - bool Naive() const; + bool Naive() const { return rSearch->Naive(); } //! Modify whether the model is in naive search mode. - bool& Naive(); + bool& Naive() { return rSearch->Naive(); } //! Get the leaf size (applicable to everything but the cover tree). size_t LeafSize() const { return leafSize; } @@ -332,6 +300,11 @@ class RSModel //! been built). bool& RandomBasis() { return randomBasis; } + /** + * Allocate the memory for the range search model. + */ + void InitializeModel(const bool naive, const bool singleMode); + /** * Build the reference tree on the given dataset with the given parameters. * This takes possession of the reference set to avoid a copy. @@ -375,6 +348,23 @@ class RSModel std::vector>& distances); private: + //! The type of tree we are using. + TreeTypes treeType; + //! (Only used for some tree types.) The leaf size to use when building a + //! tree. + size_t leafSize; + + //! If true, we randomly project the data into a new basis before search. + bool randomBasis; + //! Random projection matrix. + arma::mat q; + + /** + * rSearch holds an instance of the RangeSearch class for the current + * treeType. It is initialized every time BuildModel is executed. + */ + RSWrapperBase* rSearch; + /** * Return a string representing the name of the tree. This is used for * logging output. @@ -390,7 +380,7 @@ class RSModel } // namespace range } // namespace mlpack -// Include implementation (of serialize() and inline functions). +// Include implementation (of serialize() and templated wrapper classes). #include "rs_model_impl.hpp" #endif diff --git a/src/mlpack/methods/range_search/rs_model_impl.hpp b/src/mlpack/methods/range_search/rs_model_impl.hpp index ea94903104..a59180e1b2 100644 --- a/src/mlpack/methods/range_search/rs_model_impl.hpp +++ b/src/mlpack/methods/range_search/rs_model_impl.hpp @@ -20,322 +20,87 @@ namespace mlpack { namespace range { -/** - * Initialize the RSModel with the given tree type and whether or not a random - * basis should be used. - */ -inline RSModel::RSModel(TreeTypes treeType, bool randomBasis) : - treeType(treeType), - leafSize(0), - randomBasis(randomBasis) -{ - // Nothing to do. -} - -// Copy constructor. -inline RSModel::RSModel(const RSModel& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(other.q), - rSearch(other.rSearch) -{ - // Nothing to do. -} - -// Move constructor. -inline RSModel::RSModel(RSModel&& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(std::move(other.q)), - rSearch(std::move(other.rSearch)) -{ - // Reset other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 0; - other.randomBasis = false; - other.rSearch = decltype(other.rSearch)(); -} - -inline RSModel& RSModel::operator=(RSModel other) -{ - boost::apply_visitor(DeleteVisitor(), rSearch); - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = std::move(other.q); - rSearch = std::move(other.rSearch); - - return *this; -} - -// Clean memory, if necessary. -inline RSModel::~RSModel() -{ - boost::apply_visitor(DeleteVisitor(), rSearch); -} - -inline void RSModel::BuildModel(arma::mat&& referenceSet, - const size_t leafSize, - const bool naive, - const bool singleMode) -{ - // Initialize random basis if necessary. - if (randomBasis) - { - Log::Info << "Creating random basis..." << std::endl; - math::RandomBasis(q, referenceSet.n_rows); - } - - this->leafSize = leafSize; - - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), rSearch); - - // Do we need to modify the reference set? - if (randomBasis) - referenceSet = q * referenceSet; - - if (!naive) - { - Timer::Start("tree_building"); - Log::Info << "Building reference tree..." << std::endl; - } - - switch (treeType) - { - case KD_TREE: - rSearch = new RSType (naive, singleMode); - break; - - case COVER_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_STAR_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case BALL_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case X_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case HILBERT_R_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_PLUS_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_PLUS_PLUS_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case VP_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case RP_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case MAX_RP_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case UB_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case OCTREE: - rSearch = new RSType(naive, singleMode); - break; - } - - TrainVisitor tn(std::move(referenceSet), leafSize); - boost::apply_visitor(tn, rSearch); - - if (!naive) - { - Timer::Stop("tree_building"); - Log::Info << "Tree built." << std::endl; - } -} - -// Perform range search. -inline void RSModel::Search(arma::mat&& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances) -{ - // We may need to map the query set randomly. - if (randomBasis) - querySet = q * querySet; - - Log::Info << "Search for points in the range [" << range.Lo() << ", " - << range.Hi() << "] with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; - else if (!Naive()) - Log::Info << "single-tree " << TreeName() << " search..." << std::endl; - else - Log::Info << "brute-force (naive) search..." << std::endl; - - - BiSearchVisitor search(querySet, range, neighbors, distances, - leafSize); - boost::apply_visitor(search, rSearch); -} - -// Perform range search (monochromatic case). -inline void RSModel::Search(const math::Range& range, - std::vector>& neighbors, - std::vector>& distances) -{ - Log::Info << "Search for points in the range [" << range.Lo() << ", " - << range.Hi() << "] with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; - else if (!Naive()) - Log::Info << "single-tree " << TreeName() << " search..." << std::endl; - else - Log::Info << "brute-force (naive) search..." << std::endl; - - MonoSearchVisitor search(range, neighbors, distances); - boost::apply_visitor(search, rSearch); -} - -// Get the name of the tree type. -inline std::string RSModel::TreeName() const -{ - switch (treeType) - { - case KD_TREE: - return "kd-tree"; - case COVER_TREE: - return "cover tree"; - case R_TREE: - return "R tree"; - case R_STAR_TREE: - return "R* tree"; - case BALL_TREE: - return "ball tree"; - case X_TREE: - return "X tree"; - case HILBERT_R_TREE: - return "Hilbert R tree"; - case R_PLUS_TREE: - return "R+ tree"; - case R_PLUS_PLUS_TREE: - return "R++ tree"; - case VP_TREE: - return "vantage point tree"; - case RP_TREE: - return "random projection tree (mean split)"; - case MAX_RP_TREE: - return "random projection tree (max split)"; - case UB_TREE: - return "UB tree"; - case OCTREE: - return "octree"; - default: - return "unknown tree"; - } -} - -// Clean memory. -inline void RSModel::CleanMemory() -{ - boost::apply_visitor(DeleteVisitor(), rSearch); -} - -//! Monochromatic range search on the given RSType instance. -template -void MonoSearchVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->Search(range, neighbors, distances); - throw std::runtime_error("no range search model initialized"); -} - -//! Save parameters for bichromatic range search. -inline BiSearchVisitor::BiSearchVisitor( - const arma::mat& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances, - const size_t leafSize) : - querySet(querySet), - range(range), - neighbors(neighbors), - distances(distances), - leafSize(leafSize) -{} - -//! Default Bichromatic range search on the given RSType instance. template class TreeType> -void BiSearchVisitor::operator()(RSTypeT* rs) const +void RSWrapper::Train(arma::mat&& referenceSet, + const size_t /* leafSize */) { - if (rs) - return rs->Search(querySet, range, neighbors, distances); - throw std::runtime_error("no range search model initialized"); + rs.Train(std::move(referenceSet)); } -//! Bichromatic range search on the given RSType specialized for KDTrees. -inline void BiSearchVisitor::operator()(RSTypeT* rs) const +template class TreeType> +void RSWrapper::Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t /* leafSize */) { - if (rs) - return SearchLeaf(rs); - throw std::runtime_error("no range search model initialized"); + rs.Search(std::move(querySet), range, neighbors, distances); } -//! Bichromatic range search on the given RSType specialized for BallTrees. -inline void BiSearchVisitor::operator()(RSTypeT* rs) const +template class TreeType> +void RSWrapper::Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) { - if (rs) - return SearchLeaf(rs); - throw std::runtime_error("no range search model initialized"); + rs.Search(range, neighbors, distances); } -//! Bichromatic range search specialized for Ocrees. -inline void BiSearchVisitor::operator()(RSTypeT* rs) const +template class TreeType> +void LeafSizeRSWrapper::Train(arma::mat&& referenceSet, + const size_t leafSize) { - if (rs) - return SearchLeaf(rs); - throw std::runtime_error("no range search model initialized"); + if (rs.Naive()) + { + rs.Train(std::move(referenceSet)); + } + else + { + std::vector oldFromNewReferences; + typename decltype(rs)::Tree* tree = + new typename decltype(rs)::Tree(std::move(referenceSet), + oldFromNewReferences, + leafSize); + rs.Train(tree); + + // Give the model ownership of the tree and the mappings. + rs.treeOwner = true; + rs.oldFromNewReferences = std::move(oldFromNewReferences); + } } -//! Bichromatic range search on the given RSType considering the leafSize. -template -void BiSearchVisitor::SearchLeaf(RSType* rs) const +template class TreeType> +void LeafSizeRSWrapper::Search( + arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t leafSize) { - if (!rs->Naive() && !rs->SingleMode()) + if (!rs.Naive() && !rs.SingleMode()) { // Build a second tree and search. Timer::Start("tree_building"); Log::Info << "Building query tree..." << std::endl; std::vector oldFromNewQueries; - typename RSType::Tree queryTree(std::move(querySet), oldFromNewQueries, - leafSize); + typename decltype(rs)::Tree queryTree(std::move(querySet), + oldFromNewQueries, + leafSize); Log::Info << "Tree built." << std::endl; Timer::Stop("tree_building"); std::vector> neighborsOut; std::vector> distancesOut; - rs->Search(&queryTree, range, neighborsOut, distancesOut); + rs.Search(&queryTree, range, neighborsOut, distancesOut); // Remap the query points. neighbors.resize(queryTree.Dataset().n_cols); @@ -346,107 +111,12 @@ void BiSearchVisitor::SearchLeaf(RSType* rs) const distances[oldFromNewQueries[i]] = distancesOut[i]; } } - else - rs->Search(querySet, range, neighbors, distances); -} - -//! Save parameters for Train. -inline TrainVisitor::TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize) : - referenceSet(std::move(referenceSet)), - leafSize(leafSize) -{} - -//! Default Train on the given RSType instance. -template class TreeType> -void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return rs->Train(std::move(referenceSet)); - throw std::runtime_error("no range search model initialized"); -} - -//! Train on the given RSType specialized for KDTrees. -inline void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return TrainLeaf(rs); - throw std::runtime_error("no range search model initialized"); -} - -//! Train on the given RSType specialized for BallTrees. -inline void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return TrainLeaf(rs); - throw std::runtime_error("no range search model initialized"); -} - -//! Train specialized for Octrees. -inline void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return TrainLeaf(rs); - throw std::runtime_error("no range search model initialized"); -} - -//! Train on the given RSType considering the leafSize. -template -void TrainVisitor::TrainLeaf(RSType* rs) const -{ - if (rs->Naive()) - rs->Train(std::move(referenceSet)); else { - std::vector oldFromNewReferences; - typename RSType::Tree* tree = - new typename RSType::Tree(std::move(referenceSet), oldFromNewReferences, - leafSize); - rs->Train(tree); - - // Give the model ownership of the tree and the mappings. - rs->treeOwner = true; - rs->oldFromNewReferences = std::move(oldFromNewReferences); + rs.Search(std::move(querySet), range, neighbors, distances); } } -//! Expose the referenceSet of the given RSType. -template -const arma::mat& ReferenceSetVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->ReferenceSet(); - throw std::runtime_error("no range search model initialized"); -} - -//! For cleaning memory -template -void DeleteVisitor::operator()(RSType* rs) const -{ - if (rs) - delete rs; -} - -//! Return whether single mode enabled -template -bool& SingleModeVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->SingleMode(); - throw std::runtime_error("no range search model initialized"); -} - -//! Exposes Naive() function of given RSType -template -bool& NaiveVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->Naive(); - throw std::runtime_error("no range search model initialized"); -} - // Serialize the model. template void RSModel::serialize(Archive& ar, const uint32_t /* version */) @@ -457,35 +127,119 @@ void RSModel::serialize(Archive& ar, const uint32_t /* version */) // This should never happen, but just in case... if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), rSearch); + InitializeModel(false, false); // Values will be overwritten. - // We'll only need to serialize one of the model objects, based on the type. - ar(CEREAL_VARIANT_POINTER(rSearch)); -} + // Avoid polymorphic serialization by explicitly serializing the correct type. + switch (treeType) + { + case KD_TREE: + { + LeafSizeRSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case COVER_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline const arma::mat& RSModel::Dataset() const -{ - return boost::apply_visitor(ReferenceSetVisitor(), rSearch); -} + case R_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool RSModel::SingleMode() const -{ - return boost::apply_visitor(SingleModeVisitor(), rSearch); -} + case R_STAR_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool& RSModel::SingleMode() -{ - return boost::apply_visitor(SingleModeVisitor(), rSearch); -} + case BALL_TREE: + { + LeafSizeRSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case X_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool RSModel::Naive() const -{ - return boost::apply_visitor(NaiveVisitor(), rSearch); -} + case HILBERT_R_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool& RSModel::Naive() -{ - return boost::apply_visitor(NaiveVisitor(), rSearch); + case R_PLUS_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case R_PLUS_PLUS_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case VP_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case RP_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case MAX_RP_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case UB_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case OCTREE: + { + LeafSizeRSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + } } } // namespace range diff --git a/src/mlpack/methods/rann/CMakeLists.txt b/src/mlpack/methods/rann/CMakeLists.txt index 99e838b459..42a70dbc26 100644 --- a/src/mlpack/methods/rann/CMakeLists.txt +++ b/src/mlpack/methods/rann/CMakeLists.txt @@ -23,6 +23,7 @@ set(SOURCES # model ra_model.hpp ra_model_impl.hpp + ra_model.cpp ) # add directory name to sources diff --git a/src/mlpack/methods/rann/krann_main.cpp b/src/mlpack/methods/rann/krann_main.cpp index 9d830f5fff..0ed34fd0f2 100644 --- a/src/mlpack/methods/rann/krann_main.cpp +++ b/src/mlpack/methods/rann/krann_main.cpp @@ -25,9 +25,6 @@ using namespace mlpack::tree; using namespace mlpack::metric; using namespace mlpack::util; -// Convenience typedef. -typedef RAModel RANNModel; - // Program Name. BINDING_NAME("K-Rank-Approximate-Nearest-Neighbors (kRANN)"); @@ -86,8 +83,8 @@ PARAM_MATRIX_OUT("distances", "Matrix to output distances into.", "d"); PARAM_UMATRIX_OUT("neighbors", "Matrix to output neighbors into.", "n"); // The option exists to load or save models. -PARAM_MODEL_IN(RANNModel, "input_model", "Pre-trained kNN model.", "m"); -PARAM_MODEL_OUT(RANNModel, "output_model", "If specified, the kNN model will be" +PARAM_MODEL_IN(RAModel, "input_model", "Pre-trained kNN model.", "m"); +PARAM_MODEL_OUT(RAModel, "output_model", "If specified, the kNN model will be" " output here.", "M"); // The user may specify a query file of query points and a number of nearest @@ -170,12 +167,12 @@ static void mlpackMain() "alpha must be in range [0.0, 1.0]"); // We either have to load the reference data, or we have to load the model. - RANNModel* rann; + RAModel* rann; const bool naive = IO::HasParam("naive"); const bool singleMode = IO::HasParam("single_mode"); if (IO::HasParam("reference")) { - rann = new RANNModel(); + rann = new RAModel(); // Get all the parameters. const string treeType = IO::GetParam("tree_type"); @@ -184,27 +181,27 @@ static void mlpackMain() "unknown tree type"); const bool randomBasis = IO::HasParam("random_basis"); - RANNModel::TreeTypes tree = RANNModel::KD_TREE; + RAModel::TreeTypes tree = RAModel::KD_TREE; if (treeType == "kd") - tree = RANNModel::KD_TREE; + tree = RAModel::KD_TREE; else if (treeType == "cover") - tree = RANNModel::COVER_TREE; + tree = RAModel::COVER_TREE; else if (treeType == "r") - tree = RANNModel::R_TREE; + tree = RAModel::R_TREE; else if (treeType == "r-star") - tree = RANNModel::R_STAR_TREE; + tree = RAModel::R_STAR_TREE; else if (treeType == "x") - tree = RANNModel::X_TREE; + tree = RAModel::X_TREE; else if (treeType == "hilbert-r") - tree = RANNModel::HILBERT_R_TREE; + tree = RAModel::HILBERT_R_TREE; else if (treeType == "r-plus") - tree = RANNModel::R_PLUS_TREE; + tree = RAModel::R_PLUS_TREE; else if (treeType == "r-plus-plus") - tree = RANNModel::R_PLUS_PLUS_TREE; + tree = RAModel::R_PLUS_PLUS_TREE; else if (treeType == "ub") - tree = RANNModel::UB_TREE; + tree = RAModel::UB_TREE; else if (treeType == "oct") - tree = RANNModel::OCTREE; + tree = RAModel::OCTREE; rann->TreeType() = tree; rann->RandomBasis() = randomBasis; @@ -218,10 +215,10 @@ static void mlpackMain() else { // Load the model from file. - rann = IO::GetParam("input_model"); + rann = IO::GetParam("input_model"); Log::Info << "Using rank-approximate kNN model from '" - << IO::GetPrintableParam("input_model") << "' (trained on " + << IO::GetPrintableParam("input_model") << "' (trained on " << rann->Dataset().n_rows << "x" << rann->Dataset().n_cols << " dataset)." << endl; @@ -285,5 +282,5 @@ static void mlpackMain() } // Save the output model. - IO::GetParam("output_model") = rann; + IO::GetParam("output_model") = rann; } diff --git a/src/mlpack/methods/rann/ra_model.cpp b/src/mlpack/methods/rann/ra_model.cpp new file mode 100644 index 0000000000..6342acf6b4 --- /dev/null +++ b/src/mlpack/methods/rann/ra_model.cpp @@ -0,0 +1,239 @@ +/** + * @file methods/rann/ra_model.cpp + * @author Ryan Curtin + * + * Implementation of the RAModel class. + * + * 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 "ra_model.hpp" +#include + +namespace mlpack { +namespace neighbor { + +RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : + treeType(treeType), + leafSize(20), + randomBasis(randomBasis), + raSearch(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +RAModel::RAModel(const RAModel& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(other.q), + raSearch(other.raSearch->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +RAModel::RAModel(RAModel&& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(std::move(other.q)), + raSearch(std::move(other.raSearch)) +{ + // Clear other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 20; + other.randomBasis = false; +} + +// Copy operator. +RAModel& RAModel::operator=(const RAModel& other) +{ + if (this != &other) + { + // Clear current model. + delete raSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = other.q; + raSearch = other.raSearch->Clone(); + } + + return *this; +} + +RAModel& RAModel::operator=(RAModel&& other) +{ + if (this != &other) + { + // Clear current model. + delete raSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = std::move(other.q); + raSearch = std::move(other.raSearch); + + // Reset other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 20; + other.randomBasis = false; + } + + return *this; +} + +// Clean memory, if necessary +RAModel::~RAModel() +{ + delete raSearch; +} + +void RAModel::InitializeModel(const bool naive, const bool singleMode) +{ + // Clean memory, if necessary. + delete raSearch; + + switch (treeType) + { + case KD_TREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + case COVER_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_STAR_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case X_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case HILBERT_R_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_PLUS_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_PLUS_PLUS_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case UB_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case OCTREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + } +} + +void RAModel::BuildModel(arma::mat&& referenceSet, + const size_t leafSize, + const bool naive, + const bool singleMode) +{ + // Initialize random basis, if necessary. + if (randomBasis) + { + Log::Info << "Creating random basis..." << std::endl; + math::RandomBasis(q, referenceSet.n_rows); + } + + this->leafSize = leafSize; + + if (randomBasis) + referenceSet = q * referenceSet; + + if (!naive) + { + Timer::Start("tree_building"); + Log::Info << "Building reference tree..." << std::endl; + } + + InitializeModel(naive, singleMode); + + raSearch->Train(std::move(referenceSet), leafSize); + + if (!naive) + { + Timer::Stop("tree_building"); + Log::Info << "Tree built." << std::endl; + } +} + +void RAModel::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances) +{ + // Apply the random basis if necessary. + if (randomBasis) + querySet = q * querySet; + + Log::Info << "Searching for " << k << " approximate nearest neighbors with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; + else if (!Naive()) + Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; + else + Log::Info << "brute-force (naive) rank-approximate search..."; + Log::Info << std::endl; + + raSearch->Search(std::move(querySet), k, neighbors, distances, leafSize); +} + +void RAModel::Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) +{ + Log::Info << "Searching for " << k << " approximate nearest neighbors with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; + else if (!Naive()) + Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; + else + Log::Info << "brute-force (naive) rank-approximate search..."; + Log::Info << std::endl; + + raSearch->Search(k, neighbors, distances); +} + +std::string RAModel::TreeName() const +{ + switch (treeType) + { + case KD_TREE: + return "kd-tree"; + case COVER_TREE: + return "cover tree"; + case R_TREE: + return "R tree"; + case R_STAR_TREE: + return "R* tree"; + case X_TREE: + return "X tree"; + case HILBERT_R_TREE: + return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; + case UB_TREE: + return "UB tree"; + case OCTREE: + return "octree"; + default: + return "unknown tree"; + } +} + +} // namespace neighbor +} // namespace mlpack diff --git a/src/mlpack/methods/rann/ra_model.hpp b/src/mlpack/methods/rann/ra_model.hpp index ed32d4a352..572d599a0d 100644 --- a/src/mlpack/methods/rann/ra_model.hpp +++ b/src/mlpack/methods/rann/ra_model.hpp @@ -18,245 +18,234 @@ #include #include #include -#include #include "ra_search.hpp" namespace mlpack { namespace neighbor { /** - * Alias template for RASearch + * RAWrapperBase is a base wrapper class for holding all RASearch types + * supported by RAModel. All RASearch type wrappers inherit from this class, + * allowing a simple interface via inheritance for all the different types we + * want to support. */ -template& neighbors, + arma::mat& distances, + const size_t leafSize) = 0; + + //! Perform monochromatic rank-approximate nearest neighbor search (i.e. a + //! search with the reference set as the query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) = 0; +}; + +/** + * RAWrapper is a wrapper class for most RASearch types. + */ +template class TreeType> -using RAType = RASearch; - -/** - * MonoSearchVisitor executes a monochromatic neighbor search on the given - * RAType. We don't make any difference for different instantiation of RAType. - */ -class MonoSearchVisitor : public boost::static_visitor +class RAWrapper : public RAWrapperBase { - private: - //! Number of neighbors to search for. - const size_t k; - //! Result matrix for neighbors. - arma::Mat& neighbors; - //! Result matrix for distances. - arma::mat& distances; - public: - //! Perform monochromatic nearest neighbor search. - template - void operator()(RAType* ra) const; + //! Construct the RAWrapper object, initializing the internally-held RASearch + //! object. + RAWrapper(const bool singleMode, const bool naive) : + ra(singleMode, naive) + { + // Nothing else to do. + } - //! Construct the MonoSearchVisitor object with the given parameters. - MonoSearchVisitor(const size_t k, - arma::Mat& neighbors, - arma::mat& distances) : - k(k), - neighbors(neighbors), - distances(distances) - {}; + //! Delete the RAWrapper object. + virtual ~RAWrapper() { } + + //! Create a copy of this RAWrapper object. This correctly handles + //! polymorphism. + virtual RAWrapper* Clone() const { return new RAWrapper(*this); } + + //! Get a reference to the reference set. + const arma::mat& Dataset() const { return ra.ReferenceSet(); } + + //! Get the single sample limit. + size_t SingleSampleLimit() const { return ra.SingleSampleLimit(); } + //! Modify the single sample limit. + size_t& SingleSampleLimit() { return ra.SingleSampleLimit(); } + + //! Get whether to do exact search at the first leaf. + bool FirstLeafExact() const { return ra.FirstLeafExact(); } + //! Modify whether to do exact search at the first leaf. + bool& FirstLeafExact() { return ra.FirstLeafExact(); } + + //! Get whether to do sampling at leaves. + bool SampleAtLeaves() const { return ra.SampleAtLeaves(); } + //! Modify whether to do sampling at leaves. + bool& SampleAtLeaves() { return ra.SampleAtLeaves(); } + + //! Get the value of alpha. + double Alpha() const { return ra.Alpha(); } + //! Modify the value of alpha. + double& Alpha() { return ra.Alpha(); } + + //! Get the value of tau. + double Tau() const { return ra.Tau(); } + //! Modify the value of tau. + double& Tau() { return ra.Tau(); } + + //! Get whether single-tree search is being used. + bool SingleMode() const { return ra.SingleMode(); } + //! Modify whether single-tree search is being used. + bool& SingleMode() { return ra.SingleMode(); } + + //! Get whether naive search is being used. + bool Naive() const { return ra.Naive(); } + //! Modify whether naive search is being used. + bool& Naive() { return ra.Naive(); } + + //! Train the model. For RAWrapper, we ignore the leaf size. + virtual void Train(arma::mat&& referenceSet, + const size_t /* leafSize */); + + //! Perform bichromatic neighbor search (i.e. search with a separate query + //! set). For RAWrapper, we ignore the leaf size. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */); + + //! Perform monochromatic neighbor search (i.e. search where the reference set + //! is used as the query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances); + + //! Serialize the RASearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ra)); + } + + protected: + typedef RASearch RAType; + + //! The instantiated RASearch object that we are wrapping. + RAType ra; }; /** - * BiSearchVisitor executes a bichromatic neighbor search on the given RAType. - * We use template specialization to differentiate those tree types types that - * accept leafSize as a parameter. In these cases, before doing neighbor search - * a query tree with proper leafSize is built from the querySet. + * LeafSizeRAWrapper wraps any RASearch type that needs to be able to take the + * leaf size into account when building trees. The implementations of Train() + * and bichromatic Search() take this leaf size into account. */ -template -class BiSearchVisitor : public boost::static_visitor -{ - private: - //! The query set for the bichromatic search. - const arma::mat& querySet; - //! The number of neighbors to search for. - const size_t k; - //! The results matrix for neighbors. - arma::Mat& neighbors; - //! The result matrix for distances. - arma::mat& distances; - //! The number of points in a leaf (for BinarySpaceTrees). - const size_t leafSize; - - //! Bichromatic neighbor search on the given RAType considering leafSize. - template - void SearchLeaf(RAType* ra) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RATypeT = RAType; - - //! Default Bichromatic neighbor search on the given RAType instance. - template class TreeType> - void operator()(RATypeT* ra) const; - - //! Bichromatic search on the given RAType specialized for KDTrees. - void operator()(RATypeT* ra) const; - - //! Bichromatic search on the given RAType specialized for octrees. - void operator()(RATypeT* ra) const; - - //! Construct the BiSearchVisitor. - BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize); -}; - -/** - * TrainVisitor sets the reference set to a new reference set on the given - * RAType. We use template specialization to differentiate those trees that - * accept leafSize as a parameter. In these cases, a reference tree with proper - * leafSize is built from the referenceSet. - */ -template -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set to use for training. - arma::mat&& referenceSet; - //! The leaf size, used only by BinarySpaceTree. - size_t leafSize; - - //! Train on the given RAType considering the leafSize. - template - void TrainLeaf(RAType* ra) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RATypeT = RAType; - - //! Default Train on the given RAType instance. - template class TreeType> - void operator()(RATypeT* ra) const; - - //! Train on the given RAType specialized for KDTrees. - void operator()(RATypeT* ra) const; - - //! Train on the given RAType specialized for Octrees. - void operator()(RATypeT* ra) const; - - //! Construct the TrainVisitor object with the given reference set, leafSize - //! for BinarySpaceTrees. - TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize); -}; - -/** - * Exposes the SingleSampleLimit() method of the given RAType. - */ -class SingleSampleLimitVisitor : public boost::static_visitor +template class TreeType> +class LeafSizeRAWrapper : public RAWrapper { public: - template - size_t& operator()(RAType* ra) const; -}; + //! Construct the LeafSizeRAWrapper by delegating to the RAWrapper + //! constructor. + LeafSizeRAWrapper(const bool singleMode, const bool naive) : + RAWrapper(singleMode, naive) + { + // Nothing else to do. + } -/** - * Exposes the FirstLeafExact() method of the given RAType. - */ -class FirstLeafExactVisitor : public boost::static_visitor -{ - public: - template - bool& operator()(RAType* ra) const; -}; + //! Delete the LeafSizeRAWrapper. + virtual ~LeafSizeRAWrapper() { } -/** - * Exposes the SampleAtLeaves() method of the given RAType. - */ -class SampleAtLeavesVisitor : public boost::static_visitor -{ - public: - //! Return SampleAtLeaves (whether or not sampling is done at leaves). - template - bool& operator()(RAType *) const; -}; + //! Return a copy of the LeafSizeRAWrapper. + virtual LeafSizeRAWrapper* Clone() const + { + return new LeafSizeRAWrapper(*this); + } -/** - * Exposes the Alpha() method of the given RAType. - */ -class AlphaVisitor : public boost::static_visitor -{ - public: - //! Return Alpha parameter. - template - double& operator()(RAType* ra) const; -}; + //! Train a model with the given parameters. This overload uses leafSize. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize); -/** - * Exposes the Tau() method of the given RAType. - */ -class TauVisitor : public boost::static_visitor -{ - public: - //! Get a reference to the Tau parameter. - template - double& operator()(RAType* ra) const; -}; + //! Perform bichromatic search (e.g. search with a separate query set). This + //! overload takes the leaf size into account to build the query tree. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize); -/** - * Exposes the SingleMode() method of the given RAType. - */ -class SingleModeVisitor : public boost::static_visitor -{ - public: - //! Get a reference to the SingleMode parameter of the given RASearch object. - template - bool& operator()(RAType* ra) const; -}; + //! Serialize the RASearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ra)); + } -/** - * Exposes the referenceSet of the given RAType. - */ -class ReferenceSetVisitor : public boost::static_visitor -{ - public: - //! Return the reference set. - template - const arma::mat& operator()(RAType* ra) const; -}; - -/** - * DeleteVisitor deletes the give RAType Instance. - */ -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete the RAType Object. - template void operator()(RAType* ra) const; -}; - -/** - * NaiveVisitor exposes the Naive() method of the given RAType. - */ -class NaiveVisitor : public boost::static_visitor -{ - public: - /** - * Get a reference to the naive parameter of the given RASearch object. - */ - template - bool& operator()(RAType* ra) const; + protected: + using RAWrapper::ra; }; /** @@ -264,10 +253,7 @@ class NaiveVisitor : public boost::static_visitor * away the TreeType parameter and allowing it to be specified at runtime in * this class. This class is written for the sake of the 'allkrann' program, * but is not necessarily restricted to that use. - * - * @param SortPolicy Sorting policy for neighbor searching (see RASearch). */ -template class RAModel { public: @@ -301,16 +287,7 @@ class RAModel arma::mat q; //! The rank-approximate model. - boost::variant*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*> raSearch; + RAWrapperBase* raSearch; public: /** @@ -355,58 +332,61 @@ class RAModel void serialize(Archive& ar, const uint32_t /* version */); //! Expose the dataset. - const arma::mat& Dataset() const; + const arma::mat& Dataset() const { return raSearch->Dataset(); } //! Get whether or not single-tree search is being used. - bool SingleMode() const; + bool SingleMode() const { return raSearch->SingleMode(); } //! Modify whether or not single-tree search is being used. - bool& SingleMode(); + bool& SingleMode() { return raSearch->SingleMode(); } //! Get whether or not naive search is being used. - bool Naive() const; + bool Naive() const { return raSearch->Naive(); } //! Modify whether or not naive search is being used. - bool& Naive(); + bool& Naive() { return raSearch->Naive(); } //! Get the rank-approximation in percentile of the data. - double Tau() const; + double Tau() const { return raSearch->Tau(); } //! Modify the rank-approximation in percentile of the data. - double& Tau(); + double& Tau() { return raSearch->Tau(); } //! Get the desired success probability. - double Alpha() const; + double Alpha() const { return raSearch->Alpha(); } //! Modify the desired success probability. - double& Alpha(); + double& Alpha() { return raSearch->Alpha(); } //! Get whether or not sampling is done at the leaves. - bool SampleAtLeaves() const; + bool SampleAtLeaves() const { return raSearch->SampleAtLeaves(); } //! Modify whether or not sampling is done at the leaves. - bool& SampleAtLeaves(); + bool& SampleAtLeaves() { return raSearch->SampleAtLeaves(); } //! Get whether or not we traverse to the first leaf without approximation. - bool FirstLeafExact() const; + bool FirstLeafExact() const { return raSearch->FirstLeafExact(); } //! Modify whether or not we traverse to the first leaf without approximation. - bool& FirstLeafExact(); + bool& FirstLeafExact() { return raSearch->FirstLeafExact(); } //! Get the limit on the size of a node that can be approximated. - size_t SingleSampleLimit() const; + size_t SingleSampleLimit() const { return raSearch->SingleSampleLimit(); } //! Modify the limit on the size of a node that can be approximation. - size_t& SingleSampleLimit(); + size_t& SingleSampleLimit() { return raSearch->SingleSampleLimit(); } //! Get the leaf size (only relevant when the kd-tree is used). - size_t LeafSize() const; + size_t LeafSize() const { return leafSize; } //! Modify the leaf size (only relevant when the kd-tree is used). - size_t& LeafSize(); + size_t& LeafSize() { return leafSize; } //! Get the type of tree being used. - TreeTypes TreeType() const; + TreeTypes TreeType() const { return treeType; } //! Modify the type of tree being used. - TreeTypes& TreeType(); + TreeTypes& TreeType() { return treeType; } //! Get whether or not a random basis is being used. - bool RandomBasis() const; + bool RandomBasis() const { return randomBasis; } //! Modify whether or not a random basis is being used. Be sure to rebuild //! the model using BuildModel(). - bool& RandomBasis(); + bool& RandomBasis() { return randomBasis; } + + //! Initialize the model's memory. + void InitializeModel(const bool naive, const bool singleMode); //! Build the reference tree. void BuildModel(arma::mat&& referenceSet, diff --git a/src/mlpack/methods/rann/ra_model_impl.hpp b/src/mlpack/methods/rann/ra_model_impl.hpp index 3b27bfa2d6..c986c4570c 100644 --- a/src/mlpack/methods/rann/ra_model_impl.hpp +++ b/src/mlpack/methods/rann/ra_model_impl.hpp @@ -19,78 +19,87 @@ namespace mlpack { namespace neighbor { -//! Monochromatic search for the given RAType instance. -template -void MonoSearchVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Search(k, neighbors, distances); - throw std::runtime_error("no rank-approximate model initialized"); -} - -//! Save the parameters for the rank-approximate search. -template -BiSearchVisitor::BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize) : - querySet(querySet), - k(k), - neighbors(neighbors), - distances(distances), - leafSize(leafSize) -{}; - -//! Default Bichromatic search on the given RAType instance. -template template class TreeType> -void BiSearchVisitor::operator()(RATypeT* ra) const +void RAWrapper::Train(arma::mat&& referenceSet, + const size_t /* leafSize */) { - if (ra) - return ra->Search(querySet, k, neighbors, distances); - throw std::runtime_error("no rank-approximate model initialized"); + ra.Train(std::move(referenceSet)); } -//! Bichromatic search on the given RAType specialized for KDTrees. -template -void BiSearchVisitor::operator()(RATypeT* ra) const +template class TreeType> +void RAWrapper::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */) { - if (ra) - return SearchLeaf(ra); - throw std::runtime_error("no rank-approximate search model initialized"); + ra.Search(querySet, k, neighbors, distances); } -//! Bichromatic search on the given RAType specialized for Octrees. -template -void BiSearchVisitor::operator()(RATypeT* ra) const +template class TreeType> +void RAWrapper::Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) { - if (ra) - return SearchLeaf(ra); - throw std::runtime_error("no rank-approximate search model initialized"); + ra.Search(k, neighbors, distances); } -//! Bichromatic search on the given RAType considering the leafSize. -template -template -void BiSearchVisitor::SearchLeaf(RAType* ra) const +template class TreeType> +void LeafSizeRAWrapper::Train(arma::mat&& referenceSet, + const size_t leafSize) { - if (!ra->Naive() && !ra->SingleMode()) + // Build tree, if necessary. + if (ra.Naive()) { - // Build a second tree and search + ra.Train(std::move(referenceSet)); + } + else + { + std::vector oldFromNewReferences; + typename decltype(ra)::Tree* tree = + new typename decltype(ra)::Tree(std::move(referenceSet), + oldFromNewReferences, + leafSize); + ra.Train(tree); + + // Give the model ownership of the tree and the mappings. + ra.treeOwner = true; + ra.oldFromNewReferences = std::move(oldFromNewReferences); + } +} + +template class TreeType> +void LeafSizeRAWrapper::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize) +{ + if (!ra.Naive() && !ra.SingleMode()) + { + // Build a second tree and search, taking the leaf size into account. Timer::Start("tree_building"); Log::Info << "Building query tree...."<< std::endl; std::vector oldFromNewQueries; - typename RAType::Tree queryTree(std::move(querySet), oldFromNewQueries, - leafSize); - Log::Info << "Tree Built." << std::endl; + typename decltype(ra)::Tree queryTree(std::move(querySet), + oldFromNewQueries, + leafSize); + Log::Info << "Tree built." << std::endl; Timer::Stop("tree_building"); arma::Mat neighborsOut; arma::mat distancesOut; - ra->Search(&queryTree, k, neighborsOut, distancesOut); + ra.Search(&queryTree, k, neighborsOut, distancesOut); // Unmap the query points. distances.set_size(distancesOut.n_rows, distancesOut.n_cols); @@ -104,236 +113,12 @@ void BiSearchVisitor::SearchLeaf(RAType* ra) const else { // Search without building a second tree. - ra->Search(querySet, k, neighbors, distances); + ra.Search(querySet, k, neighbors, distances); } } -//! Save parameters for the Train. -template -TrainVisitor::TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize) : - referenceSet(std::move(referenceSet)), - leafSize(leafSize) -{}; - -//! Default Train on the given RAType instance. -template -template class TreeType> -void TrainVisitor::operator()(RATypeT* ra) const -{ - if (ra) - return ra->Train(std::move(referenceSet)); - throw std::runtime_error("no rank-approximate search model initialized"); -} - -//! Train on the given RAType specialized for KDTrees. -template -void TrainVisitor::operator()(RATypeT* ra) const -{ - if (ra) - return TrainLeaf(ra); - throw std::runtime_error("no rank-approximate search model initialized"); -} - -//! Train on the given RAType specialized for Octrees. -template -void TrainVisitor::operator()(RATypeT* ra) const -{ - if (ra) - return TrainLeaf(ra); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Train on the given RAType considering the leafSize. -template -template -void TrainVisitor::TrainLeaf(RAType* ra) const -{ - // Build tree, if necessary - if (ra->Naive()) - { - ra->Train(std::move(referenceSet)); - } - else - { - std::vector oldFromNewReferences; - typename RAType::Tree* tree = - new typename RAType::Tree(std::move(referenceSet), oldFromNewReferences, - leafSize); - ra->Train(tree); - - // Give the model ownership of the tree and the mappings. - ra->treeOwner = true; - ra->oldFromNewReferences = std::move(oldFromNewReferences); - } -} - -//! Exposes the SingleSampleLimit() method of the given RAType. -template -size_t& SingleSampleLimitVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->SingleSampleLimit(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Exposes the FirstLeafExact() method of the given RAType. -template -bool& FirstLeafExactVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->FirstLeafExact(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Exposes the SampleAtLeaves() method of the given RAType. -template -bool& SampleAtLeavesVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->SampleAtLeaves(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Exposes the Alpha() method of the given RAType instance. -template -double& AlphaVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Alpha(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the Tau() method of the given RAType instance. -template -double& TauVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Tau(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the SingleMode() method of the given RAType. -template -bool& SingleModeVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->SingleMode(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the referenceSet of the given RAType. -template -const arma::mat& ReferenceSetVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->ReferenceSet(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the Naive() method of the given RAType instance. -template -bool& NaiveVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Naive(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! For cleaning memory -template -void DeleteVisitor::operator()(RSType* rs) const -{ - if (rs) - delete rs; -} - -template -RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : - treeType(treeType), - leafSize(20), - randomBasis(randomBasis) -{ - // Nothing to do. -} - -// Copy constructor. -template -RAModel::RAModel(const RAModel& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(other.q), - raSearch(other.raSearch) -{ - // Nothing to do. -} - -// Move constructor. -template -RAModel::RAModel(RAModel&& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(std::move(other.q)), - raSearch(std::move(other.raSearch)) -{ - // Clear other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.randomBasis = false; - other.raSearch = decltype(other.raSearch)(); -} - -// Copy operator. -template -RAModel& RAModel::operator=(const RAModel& other) -{ - // Clear current model. - boost::apply_visitor(DeleteVisitor(), raSearch); - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = other.q; - raSearch = other.raSearch; - - return *this; -} - -template -RAModel& RAModel::operator=(RAModel&& other) -{ - boost::apply_visitor(DeleteVisitor(), raSearch); - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = std::move(other.q); - raSearch = std::move(other.raSearch); - - // Reset other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.randomBasis = false; - other.raSearch = decltype(other.raSearch)(); - - return *this; -} - -// Clean memory, if necessary -template -RAModel::~RAModel() -{ - boost::apply_visitor(DeleteVisitor(), raSearch); -} - -template template -void RAModel::serialize(Archive& ar, - const uint32_t /* version */) +void RAModel::serialize(Archive& ar, const uint32_t /* version */) { ar(CEREAL_NVP(treeType)); ar(CEREAL_NVP(randomBasis)); @@ -341,282 +126,81 @@ void RAModel::serialize(Archive& ar, // This should never happen, but just in case, be clean with memory. if (cereal::is_loading()) - { - boost::apply_visitor(DeleteVisitor(), raSearch); - } - - // We only need to serialize one of the kRANN objects. - ar(CEREAL_VARIANT_POINTER(raSearch)); -} - -template -const arma::mat& RAModel::Dataset() const -{ - return boost::apply_visitor(ReferenceSetVisitor(), raSearch); -} - -template -bool RAModel::Naive() const -{ - return boost::apply_visitor(NaiveVisitor(), raSearch); -} - -template -bool& RAModel::Naive() -{ - return boost::apply_visitor(NaiveVisitor(), raSearch); -} - -template -bool RAModel::SingleMode() const -{ - return boost::apply_visitor(SingleModeVisitor(), raSearch); -} - -template -bool& RAModel::SingleMode() -{ - return boost::apply_visitor(SingleModeVisitor(), raSearch); -} - -template -double RAModel::Tau() const -{ - return boost::apply_visitor(TauVisitor(), raSearch); -} - -template -double& RAModel::Tau() -{ - return boost::apply_visitor(TauVisitor(), raSearch); -} - -template -double RAModel::Alpha() const -{ - return boost::apply_visitor(AlphaVisitor(), raSearch); -} - -template -double& RAModel::Alpha() -{ - return boost::apply_visitor(AlphaVisitor(), raSearch); -} - -template -bool RAModel::SampleAtLeaves() const -{ - return boost::apply_visitor(SampleAtLeavesVisitor(), raSearch); -} - -template -bool& RAModel::SampleAtLeaves() -{ - return boost::apply_visitor(SampleAtLeavesVisitor(), raSearch); -} - -template -bool RAModel::FirstLeafExact() const -{ - return boost::apply_visitor(FirstLeafExactVisitor(), raSearch); -} - -template -bool& RAModel::FirstLeafExact() -{ - return boost::apply_visitor(FirstLeafExactVisitor(), raSearch); -} - -template -size_t RAModel::SingleSampleLimit() const -{ - return boost::apply_visitor(SingleSampleLimitVisitor(), raSearch); -} - -template -size_t& RAModel::SingleSampleLimit() -{ - return boost::apply_visitor(SingleSampleLimitVisitor(), raSearch); -} - -template -size_t RAModel::LeafSize() const -{ - return leafSize; -} - -template -size_t& RAModel::LeafSize() -{ - return leafSize; -} - -template -typename RAModel::TreeTypes RAModel::TreeType() const -{ - return treeType; -} - -template -typename RAModel::TreeTypes& RAModel::TreeType() -{ - return treeType; -} - -template -bool RAModel::RandomBasis() const -{ - return randomBasis; -} - -template -bool& RAModel::RandomBasis() -{ - return randomBasis; -} - -template -void RAModel::BuildModel(arma::mat&& referenceSet, - const size_t leafSize, - const bool naive, - const bool singleMode) -{ - // Initialize random basis, if necessary. - if (randomBasis) - { - Log::Info << "Creating random basis..." << std::endl; - math::RandomBasis(q, referenceSet.n_rows); - } - - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), raSearch); - - this->leafSize = leafSize; - - if (randomBasis) - referenceSet = q * referenceSet; - - if (!naive) - { - Timer::Start("tree_building"); - Log::Info << "Building reference tree..." << std::endl; - } + InitializeModel(false, false); // Values will be overwritten. + // Avoid polymorphic serialization by explicitly serializing the correct type. switch (treeType) { case KD_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + LeafSizeRAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case COVER_TREE: - raSearch = new RAType(naive, - singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_STAR_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case X_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case HILBERT_R_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_PLUS_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_PLUS_PLUS_TREE: - raSearch = new RAType(naive, - singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case UB_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case OCTREE: - raSearch = new RAType(naive, singleMode); - break; - } - - TrainVisitor tn(std::move(referenceSet), leafSize); - boost::apply_visitor(tn, raSearch); - - if (!naive) - { - Timer::Stop("tree_building"); - Log::Info << "Tree built." << std::endl; - } -} - -template -void RAModel::Search(arma::mat&& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances) -{ - // Apply the random basis if necessary. - if (randomBasis) - querySet = q * querySet; - - Log::Info << "Searching for " << k << " approximate nearest neighbors with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; - else if (!Naive()) - Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; - else - Log::Info << "brute-force (naive) rank-approximate search..."; - Log::Info << std::endl; - - BiSearchVisitor search(querySet, k, neighbors, distances, - leafSize); - boost::apply_visitor(search, raSearch); -} - -template -void RAModel::Search(const size_t k, - arma::Mat& neighbors, - arma::mat& distances) -{ - Log::Info << "Searching for " << k << " approximate nearest neighbors with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; - else if (!Naive()) - Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; - else - Log::Info << "brute-force (naive) rank-approximate search..."; - Log::Info << std::endl; - - MonoSearchVisitor search(k, neighbors, distances); - boost::apply_visitor(search, raSearch); -} - -template -std::string RAModel::TreeName() const -{ - switch (treeType) - { - case KD_TREE: - return "kd-tree"; - case COVER_TREE: - return "cover tree"; - case R_TREE: - return "R tree"; - case R_STAR_TREE: - return "R* tree"; - case X_TREE: - return "X tree"; - case HILBERT_R_TREE: - return "Hilbert R tree"; - case R_PLUS_TREE: - return "R+ tree"; - case R_PLUS_PLUS_TREE: - return "R++ tree"; - case UB_TREE: - return "UB tree"; - case OCTREE: - return "octree"; - default: - return "unknown tree"; + { + LeafSizeRAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } } } diff --git a/src/mlpack/methods/rann/ra_search.hpp b/src/mlpack/methods/rann/ra_search.hpp index da3f61c48d..634260c213 100644 --- a/src/mlpack/methods/rann/ra_search.hpp +++ b/src/mlpack/methods/rann/ra_search.hpp @@ -39,8 +39,10 @@ namespace mlpack { namespace neighbor { // Forward declaration. -template -class TrainVisitor; +template class TreeType> +class LeafSizeRAWrapper; /** * The RASearch class: This class provides a generic manner to perform @@ -394,8 +396,7 @@ class RASearch MetricType metric; //! For access to mappings when building models. - template - friend class TrainVisitor; + friend class LeafSizeRAWrapper; }; // class RASearch } // namespace neighbor diff --git a/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt b/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt index 04e5b8b2a8..f93995ab75 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt +++ b/src/mlpack/methods/reinforcement_learning/environment/CMakeLists.txt @@ -2,6 +2,7 @@ # Anything not in this list will not be compiled into mlpack. set(SOURCES env_type.hpp + env_type.cpp mountain_car.hpp cart_pole.hpp continuous_mountain_car.hpp diff --git a/src/mlpack/methods/reinforcement_learning/environment/env_type.cpp b/src/mlpack/methods/reinforcement_learning/environment/env_type.cpp new file mode 100644 index 0000000000..d5363b5501 --- /dev/null +++ b/src/mlpack/methods/reinforcement_learning/environment/env_type.cpp @@ -0,0 +1,27 @@ +/** + * @file methods/reinforcement_learning/environment/env_type.cpp + * @author Nishant Kumar + * + * This file defines the static variables used by the discrete and continuous + * environments. + * + * 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 "env_type.hpp" + +namespace mlpack { +namespace rl { + +// Instantiate static members. + +size_t DiscreteActionEnv::State::dimension = 0; +size_t DiscreteActionEnv::Action::size = 0; + +size_t ContinuousActionEnv::State::dimension = 0; +size_t ContinuousActionEnv::Action::size = 0; + +} // namespace rl +} // namespace mlpack diff --git a/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp b/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp index e8513e3bb9..4fcf797322 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/env_type.hpp @@ -105,8 +105,6 @@ class DiscreteActionEnv */ bool IsTerminal(const State& /* state */) const { return false; } }; -size_t DiscreteActionEnv::State::dimension = 0; -size_t DiscreteActionEnv::Action::size = 0; /** * To use the dummy environment, one may start by specifying the state and @@ -201,8 +199,6 @@ class ContinuousActionEnv */ bool IsTerminal(const State& /* state */) const { return false; } }; -size_t ContinuousActionEnv::State::dimension = 0; -size_t ContinuousActionEnv::Action::size = 0; } // namespace rl } // namespace mlpack diff --git a/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp b/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp index b52110d744..82ce15e77e 100644 --- a/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp +++ b/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp @@ -53,7 +53,7 @@ class CategoricalDQN /** * Default constructor. */ - CategoricalDQN() : network(), isNoisy(false) + CategoricalDQN() : network(), isNoisy(false), atomSize(0), vMin(0.0), vMax(0.0) { /* Nothing to do here. */ } /** diff --git a/src/mlpack/methods/softmax_regression/softmax_regression.cpp b/src/mlpack/methods/softmax_regression/softmax_regression.cpp index ae39513df6..567241b35a 100644 --- a/src/mlpack/methods/softmax_regression/softmax_regression.cpp +++ b/src/mlpack/methods/softmax_regression/softmax_regression.cpp @@ -91,13 +91,8 @@ void SoftmaxRegression::Classify(const arma::mat& dataset, arma::mat& probabilities) const { - if (dataset.n_rows != FeatureSize()) - { - std::ostringstream oss; - oss << "SoftmaxRegression::Classify(): dataset has " << dataset.n_rows - << " dimensions, but model has " << FeatureSize() << " dimensions!"; - throw std::invalid_argument(oss.str()); - } + util::CheckSameDimensionality(dataset, FeatureSize(), + "SoftmaxRegression::Classify()"); // Calculate the probabilities for each test input. arma::mat hypothesis; diff --git a/src/mlpack/prereqs.hpp b/src/mlpack/prereqs.hpp index 4ec1031235..1d049d711c 100644 --- a/src/mlpack/prereqs.hpp +++ b/src/mlpack/prereqs.hpp @@ -140,4 +140,7 @@ or upgrade Boost to 1.59 or newer. // We need to be able to mark functions deprecated. #include +// Include ready to use utility function to check sizes of datasets. +#include + #endif diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index e6dd629266..de0f56d8df 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable(mlpack_test facilities_test.cpp fastmks_test.cpp feedforward_network_test.cpp + feedforward_network_2_test.cpp gan_test.cpp gmm_test.cpp hmm_test.cpp @@ -98,6 +99,7 @@ add_executable(mlpack_test reward_clipping_test.cpp rl_components_test.cpp scaling_test.cpp + size_checks_test.cpp serialization.cpp serialization.hpp serialization_test.cpp diff --git a/src/mlpack/tests/activation_functions_test.cpp b/src/mlpack/tests/activation_functions_test.cpp index d88c0a5109..85cc1f7608 100644 --- a/src/mlpack/tests/activation_functions_test.cpp +++ b/src/mlpack/tests/activation_functions_test.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "catch.hpp" @@ -558,6 +559,54 @@ void CheckCELUDerivativeCorrect(const arma::colvec input, } } +/** + * Implementation of the ISRLU activation function test. The function is + * implemented as ISRLU layer in the file isrlu.hpp. + * + * @param input Input data used for evaluating the ISRLU activation function. + * @param target Target data used to evaluate the ISRLU activation. + */ +void CheckISRLUActivationCorrect(const arma::colvec input, + const arma::colvec target) +{ + // Initialize ISRLU object with alpha = 1.0. + ISRLU<> lrf(1.0); + + // Test the activation function using the entire vector as input. + arma::colvec activations; + lrf.Forward(input, activations); + for (size_t i = 0; i < activations.n_elem; ++i) + { + REQUIRE(activations.at(i) == Approx(target.at(i)).epsilon(1e-5)); + } +} + +/** + * Implementation of the ISRLU activation function derivative test. The function + * is implemented as ISRLU layer in the file isrlu.hpp. + * + * @param input Input data used for evaluating the ISRLU activation function. + * @param target Target data used to evaluate the ISRLU activation. + */ +void CheckISRLUDerivativeCorrect(const arma::colvec input, + const arma::colvec target) +{ + // Initialize ISRLU object with alpha = 1.0. + ISRLU<> lrf(1.0); + + // Test the calculation of the derivatives using the entire vector as input. + arma::colvec derivatives, activations; + + // This error vector will be set to 1 to get the derivatives. + arma::colvec error = arma::ones(input.n_elem); + lrf.Forward(input, activations); + lrf.Backward(activations, error, derivatives); + for (size_t i = 0; i < derivatives.n_elem; ++i) + { + REQUIRE(derivatives.at(i) == Approx(target.at(i)).epsilon(1e-5)); + } +} + /** * Implementation of the Softmin activation function test. The function is * implemented as Softmin layer in the file softmin.hpp. @@ -991,6 +1040,22 @@ TEST_CASE("CELUFunctionTest", "[ActivationFunctionsTest]") CheckCELUDerivativeCorrect(desiredActivations, desiredDerivatives); } +/** + * Basic test of the ISRLU activation function. + */ +TEST_CASE("ISRLUFunctionTest", "[ActivationFunctionsTest]") +{ + const arma::colvec desiredActivations("-0.89442719 3.2 4.5 \ + -0.99995020 1 -0.70710678 2 0"); + + const arma::colvec desiredDerivatives("0.41408666 1 1 \ + 0.35357980 1 \ + 0.54433105 1 1"); + + CheckISRLUActivationCorrect(activationData, desiredActivations); + CheckISRLUDerivativeCorrect(activationData, desiredDerivatives); +} + /** * Basic test of the inverse quadratic function. */ @@ -1134,3 +1199,24 @@ TEST_CASE("SoftminFunctionTest", "[ActivationFunctionsTest]") CheckSoftminDerivativeCorrect(activationData, desiredDerivatives); } + +/** + * Basic test of the Hard Swish function. + */ +TEST_CASE("HardSwishFunctionTest", "[ActivationFunctionsTest]") +{ + // Randomly generated data. + const arma::colvec activationData("3.6544 -1.9714 -5.2277 1.5448 2.1164"); + + // Hand-calculated values. + const arma::colvec desiredActivations("3.6544 -0.3379636 0.0 \ + 1.1701345 1.8047248"); + + // Hand-calculated values. + const arma::colvec desiredDerivatives("1.0 0.38734546 0.5 \ + 0.89004483 1.1015749"); + + CheckActivationCorrect(activationData, desiredActivations); + CheckDerivativeCorrect + (desiredActivations, desiredDerivatives); +} diff --git a/src/mlpack/tests/adaboost_test.cpp b/src/mlpack/tests/adaboost_test.cpp index cccf6f42d8..6f584a0f04 100644 --- a/src/mlpack/tests/adaboost_test.cpp +++ b/src/mlpack/tests/adaboost_test.cpp @@ -67,7 +67,7 @@ TEST_CASE("HammingLossBoundIris", "[AdaBoostTest]") /** * This test case runs the AdaBoost.mh algorithm on the UCI Iris dataset. It * checks if the error returned by running a single instance of the weak learner - * is worse than running the boosted weak learner using adaboost. + * close to that of the boosted weak learner using adaboost. */ TEST_CASE("WeakLearnerErrorIris", "[AdaBoostTest]") { @@ -105,7 +105,7 @@ TEST_CASE("WeakLearnerErrorIris", "[AdaBoostTest]") size_t countError = arma::accu(labels != predictedLabels);; double error = (double) countError / labels.n_cols; - REQUIRE(error <= weakLearnerErrorRate); + REQUIRE(error <= weakLearnerErrorRate + 0.03); } /** @@ -151,7 +151,7 @@ TEST_CASE("HammingLossBoundVertebralColumn", "[AdaBoostTest]") /** * This test case runs the AdaBoost.mh algorithm on the UCI Vertebral Column * dataset. It checks if the error returned by running a single instance of the - * weak learner is worse than running the boosted weak learner using adaboost. + * weak learner is close to that of a boosted weak learner using adaboost. */ TEST_CASE("WeakLearnerErrorVertebralColumn", "[AdaBoostTest]") { @@ -187,7 +187,7 @@ TEST_CASE("WeakLearnerErrorVertebralColumn", "[AdaBoostTest]") size_t countError = arma::accu(labels != predictedLabels); double error = (double) countError / labels.n_cols; - REQUIRE(error <= weakLearnerErrorRate); + REQUIRE(error <= weakLearnerErrorRate + 0.03); } /** @@ -233,7 +233,7 @@ TEST_CASE("HammingLossBoundNonLinearSepData", "[AdaBoostTest]") /** * This test case runs the AdaBoost.mh algorithm on a non-linearly separable * dataset. It checks if the error returned by running a single instance of the - * weak learner is worse than running the boosted weak learner using AdaBoost. + * weak learner is close to that of a boosted weak learner using AdaBoost. */ TEST_CASE("WeakLearnerErrorNonLinearSepData", "[AdaBoostTest]") { @@ -269,7 +269,7 @@ TEST_CASE("WeakLearnerErrorNonLinearSepData", "[AdaBoostTest]") size_t countError = arma::accu(labels != predictedLabels); double error = (double) countError / labels.n_cols; - REQUIRE(error == weakLearnerErrorRate); + REQUIRE(error <= weakLearnerErrorRate + 0.03); } /** @@ -314,7 +314,7 @@ TEST_CASE("HammingLossIris_DS", "[AdaBoostTest]") /** * This test case runs the AdaBoost.mh algorithm on a non-linearly separable * dataset. It checks if the error returned by running a single instance of the - * weak learner is worse than running the boosted weak learner using adaboost. + * weak learner is close to that of a boosted weak learner using adaboost. * This is for the weak learner: decision stumps. */ TEST_CASE("WeakLearnerErrorIris_DS", "[AdaBoostTest]") @@ -355,13 +355,13 @@ TEST_CASE("WeakLearnerErrorIris_DS", "[AdaBoostTest]") size_t countError = arma::accu(labels != predictedLabels); double error = (double) countError / labels.n_cols; - REQUIRE(error <= weakLearnerErrorRate); + REQUIRE(error <= weakLearnerErrorRate + 0.03); } /** * This test case runs the AdaBoost.mh algorithm on the UCI Vertebral Column * dataset. It checks if the error returned by running a single instance of the - * weak learner is worse than running the boosted weak learner using adaboost. + * weak learner is close to that of a boosted weak learner using adaboost. * This is for the weak learner: decision stumps. */ TEST_CASE("HammingLossBoundVertebralColumn_DS", "[AdaBoostTest]") @@ -403,7 +403,7 @@ TEST_CASE("HammingLossBoundVertebralColumn_DS", "[AdaBoostTest]") /** * This test case runs the AdaBoost.mh algorithm on the UCI Vertebral Column * dataset. It checks if the error returned by running a single instance of the - * weak learner is worse than running the boosted weak learner using adaboost. + * weak learner is close to that of a boosted weak learner using adaboost. * This is for the weak learner: decision stumps. */ TEST_CASE("WeakLearnerErrorVertebralColumn_DS", "[AdaBoostTest]") @@ -440,7 +440,7 @@ TEST_CASE("WeakLearnerErrorVertebralColumn_DS", "[AdaBoostTest]") size_t countError = arma::accu(labels != predictedLabels); double error = (double) countError / labels.n_cols; - REQUIRE(error <= weakLearnerErrorRate); + REQUIRE(error <= weakLearnerErrorRate + 0.03); } /** @@ -487,7 +487,7 @@ TEST_CASE("HammingLossBoundNonLinearSepData_DS", "[AdaBoostTest]") /** * This test case runs the AdaBoost.mh algorithm on a non-linearly separable * dataset. It checks if the error returned by running a single instance of the - * weak learner is worse than running the boosted weak learner using adaboost. + * weak learner is close to that of a boosted weak learner using adaboost. * This for the weak learner: decision stumps. */ TEST_CASE("WeakLearnerErrorNonLinearSepData_DS", "[AdaBoostTest]") @@ -526,7 +526,7 @@ TEST_CASE("WeakLearnerErrorNonLinearSepData_DS", "[AdaBoostTest]") size_t countError = arma::accu(labels != predictedLabels); double error = (double) countError / labels.n_cols; - REQUIRE(error <= weakLearnerErrorRate); + REQUIRE(error <= weakLearnerErrorRate + 0.03); } /** diff --git a/src/mlpack/tests/aknn_test.cpp b/src/mlpack/tests/aknn_test.cpp index 37b5e820da..9358b57de7 100644 --- a/src/mlpack/tests/aknn_test.cpp +++ b/src/mlpack/tests/aknn_test.cpp @@ -370,14 +370,13 @@ TEST_CASE("AKNNModelTest", "[AKNNTest]") // We only have std::move() constructors so make a copy of our data. arma::mat referenceCopy(referenceData); arma::mat queryCopy(queryData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE, - 0.05); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE, 0.05); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE, 0.05); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE, 0.05); if (j == 2) - models[i].BuildModel(std::move(referenceCopy), 20, NAIVE_MODE); + models[i].BuildModel(std::move(referenceCopy), NAIVE_MODE); arma::Mat neighborsApprox; arma::mat distancesApprox; @@ -448,12 +447,11 @@ TEST_CASE("AKNNModelMonochromaticTest", "[AKNNTest]") { // We only have a std::move() constructor... so copy the data. arma::mat referenceCopy(referenceData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE, - 0.05); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE, 0.05); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE, 0.05); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE, 0.05); arma::Mat neighborsApprox; arma::mat distancesApprox; diff --git a/src/mlpack/tests/ann_layer_test.cpp b/src/mlpack/tests/ann_layer_test.cpp index 00376fd0a2..54105b1f9e 100644 --- a/src/mlpack/tests/ann_layer_test.cpp +++ b/src/mlpack/tests/ann_layer_test.cpp @@ -135,7 +135,7 @@ TEST_CASE("GradientAddLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -463,7 +463,7 @@ TEST_CASE("GradientLinearLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -640,7 +640,7 @@ TEST_CASE("GradientNoisyLinearLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -743,7 +743,7 @@ TEST_CASE("GradientLinearNoBiasLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -789,7 +789,7 @@ TEST_CASE("JacobianNegativeLogLikelihoodLayerTest", "[ANNLayerTest]") init.Initialize(input, inputElements, 1); arma::mat target(1, 1); - target(0) = math::RandInt(1, inputElements - 1); + target(0) = math::RandInt(0, inputElements - 2); double error = JacobianPerformanceTest(module, input, target); REQUIRE(error <= 1e-5); @@ -844,7 +844,7 @@ TEST_CASE("GradientFlexibleReLULayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(2, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, RandomInitialization>( NegativeLogLikelihood<>(), RandomInitialization(0.1, 0.5)); @@ -897,6 +897,39 @@ TEST_CASE("JacobianMultiplyConstantLayerTest", "[ANNLayerTest]") } } +/** + * Check whether copying and moving network with MultiplyConstant is working or + * not. + */ +TEST_CASE("CheckCopyMoveMultiplyConstantTest", "[ANNLayerTest]") +{ + arma::mat input(2, 1000); + input.randu(); + + arma::mat output1; + arma::mat output2; + arma::mat output3; + arma::mat output4; + + MultiplyConstant<> *module1 = new MultiplyConstant<>(3.0); + module1->Forward(input, output1); + + MultiplyConstant<> module2 = *module1; + delete module1; + + module2.Forward(input, output2); + CheckMatrices(output1, output2); + + MultiplyConstant<> *module3 = new MultiplyConstant<>(3.0); + module3->Forward(input, output3); + + MultiplyConstant<> module4(std::move(*module3)); + delete module3; + + module4.Forward(input, output4); + CheckMatrices(output3, output4); +} + /** * Jacobian HardTanH module test. */ @@ -1023,7 +1056,7 @@ TEST_CASE("LSTMRrhoTest", "[ANNLayerTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. @@ -1063,7 +1096,7 @@ TEST_CASE("GradientLSTMLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(1, 1, 5)), - target(arma::ones(1, 1, 5)) + target(arma::zeros(1, 1, 5)) { const size_t rho = 5; @@ -1128,7 +1161,7 @@ TEST_CASE("FastLSTMRrhoTest", "[ANNLayerTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. @@ -1168,7 +1201,7 @@ TEST_CASE("GradientFastLSTMLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(1, 1, 5)), - target(arma::ones(1, 1, 5)) + target(arma::zeros(1, 1, 5)) { const size_t rho = 5; @@ -1505,7 +1538,7 @@ TEST_CASE("GradientGRULayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(1, 1, 5)), - target(arma::ones(1, 1, 5)) + target(arma::zeros(1, 1, 5)) { const size_t rho = 5; @@ -1745,7 +1778,7 @@ TEST_CASE("GradientConcatLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -1813,7 +1846,7 @@ TEST_CASE("GradientConcatenateLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -2213,7 +2246,7 @@ TEST_CASE("GradientBatchNormTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(32, 2048)), - target(arma::ones(1, 2048)) + target(arma::zeros(1, 2048)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -2286,7 +2319,7 @@ TEST_CASE("GradientVirtualBatchNormTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(5, 256)), - target(arma::ones(1, 256)) + target(arma::zeros(1, 256)) { arma::mat referenceBatch = arma::mat(input.memptr(), input.n_rows, 16); @@ -2348,7 +2381,7 @@ TEST_CASE("MiniBatchDiscriminationTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(5, 4)), - target(arma::ones(1, 4)) + target(arma::zeros(1, 4)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -2526,7 +2559,7 @@ TEST_CASE("GradientTransposedConvolutionLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::linspace(0, 35, 36)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, RandomInitialization>(); model->Predictors() = input; @@ -2593,6 +2626,56 @@ TEST_CASE("SimpleMultiplyMergeLayerTest", "[ANNLayerTest]") } } +/** + * Check whether copying and moving network with MultiplyMerge is working or + * not. + */ +TEST_CASE("CheckCopyMoveMultiplyMergeTest", "[ANNLayerTest]") +{ + arma::mat input(10, 1); + input.randu(); + + arma::mat output1; + arma::mat output2; + arma::mat output3; + arma::mat output4; + + const size_t numMergeModules = math::RandInt(2, 10); + + MultiplyMerge<> *module1 = new MultiplyMerge<>(true, false); + for (size_t m = 0; m < numMergeModules; ++m) + { + IdentityLayer<> identityLayer; + identityLayer.Forward(input, identityLayer.OutputParameter()); + + module1->Add >(identityLayer); + } + + module1->Forward(input, output1); + + MultiplyMerge<> module2 = *module1; + delete module1; + + module2.Forward(input, output2); + CheckMatrices(output1, output2); + + MultiplyMerge<> *module3 = new MultiplyMerge<>(true, false); + for (size_t m = 0; m < numMergeModules; ++m) + { + IdentityLayer<> identityLayer; + identityLayer.Forward(input, identityLayer.OutputParameter()); + + module3->Add >(identityLayer); + } + module3->Forward(input, output3); + + MultiplyMerge<> module4(std::move(*module3)); + delete module3; + + module4.Forward(input, output4); + CheckMatrices(output3, output4); +} + /** * Simple Atrous Convolution layer test. */ @@ -2642,7 +2725,7 @@ TEST_CASE("GradientAtrousConvolutionLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::linspace(0, 35, 36)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, RandomInitialization>(); model->Predictors() = input; @@ -2823,7 +2906,7 @@ TEST_CASE("GradientLayerNormTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(10, 256)), - target(arma::ones(1, 256)) + target(arma::zeros(1, 256)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3143,7 +3226,7 @@ TEST_CASE("GradientReparametrizationLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3186,7 +3269,7 @@ TEST_CASE("GradientReparametrizationLayerBetaTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 2)), - target(arma::mat("1 1")) + target(arma::mat("0 0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3341,7 +3424,7 @@ TEST_CASE("GradientHighwayLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(5, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3392,7 +3475,7 @@ TEST_CASE("GradientSequentialLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3442,7 +3525,7 @@ TEST_CASE("GradientWeightNormLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3755,6 +3838,51 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") REQUIRE(arma::accu(delta) == 0.0); } +/** + * Simple test for Lp Pooling layer. + */ +TEST_CASE("LpMaxPoolingTestCase", "[ANNLayerTest]") +{ + // For rectangular input to pooling layers. + arma::mat input = arma::mat(8, 1); + arma::mat output; + input.zeros(); + input(0) = input(6) = 30; + input(1) = input(7) = 120; + input(2) = input(4) = 272; + input(3) = input(5) = 315; + // Output-Size should be 1 x 2. + // Square output. + LpPooling<> module1(4, 2, 2, 2, 2); + module1.InputHeight() = 2; + module1.InputWidth() = 4; + module1.Forward(input, output); + // Calculated using torch.nn.LPPool2d(). + REQUIRE(arma::accu(output) - 706.0 == Approx(0.0).margin(2e-5)); + REQUIRE(output.n_elem == 2); + + // For Square input. + input = arma::mat(16, 1); + input.zeros(); + input(0) = 4; + input(1) = 3; + input(3) = 12; + input(7) = 35; + input(8) = 6; + input(11) = 7; + input(12) = 8; + input(15) = 24; + // Output-Size should be 2 x 2. + // Square output. + LpPooling<> module3(2, 2, 2, 2, 2); + module3.InputHeight() = 4; + module3.InputWidth() = 4; + module3.Forward(input, output); + // Calculated using torch.nn.LPPool2d(). + REQUIRE(arma::accu(output) - 77.0 == Approx(0.0).margin(2e-5)); + REQUIRE(output.n_elem == 4); +} + /** * Simple test for Max Pooling layer. */ @@ -4271,7 +4399,7 @@ TEST_CASE("GradientBatchNormWithMiniBatchesTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(16, 1024)), - target(arma::ones(1, 1024)) + target(arma::zeros(1, 1024)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -4478,6 +4606,106 @@ TEST_CASE("TransposedConvolutionWeightInitializationTest", "[ANNLayerTest]") } /** + * Simple Test for PixelShuffle layer. + */ +TEST_CASE("PixelShuffleLayerTest", "[ANNLayerTest]") +{ + arma::mat input1, output1, gy1, g1, outputExpected1, gExpected1; + arma::mat input2, output2, gy2, g2, outputExpected2, gExpected2; + PixelShuffle<> module1(2, 2, 2, 4); + PixelShuffle<> module2(2, 2, 2, 4); + + // Input is a single image, of size (2,2) and having 4 channels. + input1 << 1 << 3 << 2 << 4 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << 0 << 0 << arma::endr; + gy1 << 1 << 5 << 9 << 13 << 2 << 6 << 10 << 14 << 3 << 7 << 11 << 15 << 4 << 8 + << 12 << 16 << arma::endr; + + // Calculated using torch.nn.PixelShuffle(). + outputExpected1 << 1 << 0 << 3 << 0 << 0 << 0 << 0 << 0 << 2 << 0 << 4 << 0 + << 0 << 0 << 0 << 0 << arma::endr; + gExpected1 << 1 << 9 << 3 << 11 << 5 << 13 << 7 << 15 << 2 << 10 << 4 << 12 + << 6 << 14 << 8 << 16 << arma::endr; + + input1 = input1.t(); + outputExpected1 = outputExpected1.t(); + gy1 = gy1.t(); + gExpected1 = gExpected1.t(); + + // Check the Forward pass of the layer. + module1.Forward(input1, output1); + CheckMatrices(output1, outputExpected1); + + // Check the Backward pass of the layer. + module1.Backward(input1, gy1, g1); + CheckMatrices(g1, gExpected1); + + // Input is a batch of 2 images, each of size (2,2) and having 4 channels. + input2 << 1 << 3 << 2 << 4 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << 0 << 0 << arma::endr << 5 << 7 << 6 << 8 << 0 << 0 << 0 << 0 << 0 << 0 + << 0 << 0 << 0 << 0 << 0 << 0 << arma::endr; + gy2 << 1 << 5 << 9 << 13 << 2 << 6 << 10 << 14 << 3 << 7 << 11 << 15 << 4 << 8 + << 12 << 16 << arma::endr << 17 << 21 << 25 << 29 << 18 << 22 << 26 << 30 + << 19 << 23 << 27 << 31 << 20 << 24 << 28 << 32 << arma::endr; + + // Calculated using torch.nn.PixelShuffle(). + outputExpected2 << 1 << 0 << 3 << 0 << 0 << 0 << 0 << 0 << 2 << 0 << 4 << 0 + << 0 << 0 << 0 << 0 << arma::endr << 5 << 0 << 7 << 0 << 0 << 0 << 0 << 0 + << 6 << 0 << 8 << 0 << 0 << 0 << 0 << 0 << arma::endr; + gExpected2 << 1 << 9 << 3 << 11 << 5 << 13 << 7 << 15 << 2 << 10 << 4 << 12 + << 6 << 14 << 8 << 16 << arma::endr << 17 << 25 << 19 << 27 << 21 << 29 + << 23 << 31 << 18 << 26 << 20 << 28 << 22 << 30 << 24 << 32 << arma::endr; + + input2 = input2.t(); + outputExpected2 = outputExpected2.t(); + gy2 = gy2.t(); + gExpected2 = gExpected2.t(); + + // Check the Forward pass of the layer. + module2.Forward(input2, output2); + CheckMatrices(output2, outputExpected2); + + // Check the Backward pass of the layer. + module2.Backward(input2, gy2, g2); + CheckMatrices(g2, gExpected2); +} + +/** + * Test that the function that can access the parameters of the + * PixelShuffle layer works. + */ +TEST_CASE("PixelShuffleLayerParametersTest", "[ANNLayerTest]") +{ + // Create the layer using the empty constructor. + PixelShuffle<> layer; + + // Set the different input parameters of the layer. + layer.UpscaleFactor() = 2; + layer.InputHeight() = 2; + layer.InputWidth() = 2; + layer.InputChannels() = 4; + + // Make sure we can get the parameters successfully. + REQUIRE(layer.UpscaleFactor() == 2); + REQUIRE(layer.InputHeight() == 2); + REQUIRE(layer.InputWidth() == 2); + REQUIRE(layer.InputChannels() == 4); + + arma::mat input, output; + // Input is a batch of 2 images, each of size (2,2) and having 4 channels. + input << 1 << 3 << 2 << 4 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << 0 << 0 << arma::endr << 5 << 7 << 6 << 8 << 0 << 0 << 0 << 0 << 0 << 0 + << 0 << 0 << 0 << 0 << 0 << 0 << arma::endr; + input = input.t(); + layer.Forward(input, output); + + // Check whether output parameters are returned correctly. + REQUIRE(layer.OutputHeight() == 4); + REQUIRE(layer.OutputWidth() == 4); + REQUIRE(layer.OutputChannels() == 1); +} + +/* * Simple Test for SpatialDropout layer. */ TEST_CASE("SpatialDropoutLayerTest", "[ANNLayerTest]") diff --git a/src/mlpack/tests/ann_visitor_test.cpp b/src/mlpack/tests/ann_visitor_test.cpp index e9aec4e686..b9316e8f0f 100644 --- a/src/mlpack/tests/ann_visitor_test.cpp +++ b/src/mlpack/tests/ann_visitor_test.cpp @@ -217,3 +217,19 @@ TEST_CASE("WeightSizeVisitorTestForNoisyLinearLayer", "[ANNVisitorTest]") CheckCorrectnessOfWeightSize(noisyLinearLayer); } + +/** + * Test that WeightSizeVisitor works properly for Multihead Attention layer. + */ +TEST_CASE("WeightSizeVisitorTestForMultiheadAttentionLayer", "[ANNVisitorTest]") +{ + size_t randomtgtSeqLen = arma::randi(arma::distr_param(1, 100)); + size_t randomsrcSeqLen = arma::randi(arma::distr_param(1, 100)); + size_t randomembedDim = 768; + size_t randomnumHeads = 12; + + LayerTypes<> MultiheadAttentionLayer = new MultiheadAttention<>(randomtgtSeqLen, + randomsrcSeqLen, randomembedDim, randomnumHeads); + + CheckCorrectnessOfWeightSize(MultiheadAttentionLayer); +} diff --git a/src/mlpack/tests/callback_test.cpp b/src/mlpack/tests/callback_test.cpp index 0d532df67c..aeeed3fe26 100644 --- a/src/mlpack/tests/callback_test.cpp +++ b/src/mlpack/tests/callback_test.cpp @@ -94,7 +94,7 @@ TEST_CASE("RNNCallbackTest", "[CallbackTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. @@ -120,7 +120,7 @@ TEST_CASE("RNNWithOptimizerCallbackTest", "[CallbackTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. diff --git a/src/mlpack/tests/convolutional_network_test.cpp b/src/mlpack/tests/convolutional_network_test.cpp index ac5a3e4983..529274643d 100644 --- a/src/mlpack/tests/convolutional_network_test.cpp +++ b/src/mlpack/tests/convolutional_network_test.cpp @@ -46,13 +46,13 @@ TEST_CASE("VanillaNetworkTest", "[ConvolutionalNetworkTest]") { if (i < nPoints / 2) { - // Assign label "1" to all samples with digit = 4 - Y(i) = 1; + // Assign label "0" to all samples with digit = 4 + Y(i) = 0; } else { - // Assign label "2" to all samples with digit = 9 - Y(i) = 2; + // Assign label "1" to all samples with digit = 9 + Y(i) = 1; } } @@ -110,7 +110,7 @@ TEST_CASE("VanillaNetworkTest", "[ConvolutionalNetworkTest]") for (size_t i = 0; i < predictionTemp.n_cols; ++i) { prediction(i) = arma::as_scalar(arma::find( - arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)) + 1; + arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)); } size_t correct = arma::accu(prediction == Y); diff --git a/src/mlpack/tests/facilities_test.cpp b/src/mlpack/tests/facilities_test.cpp index 65b754bb4e..d4bcec3c3b 100644 --- a/src/mlpack/tests/facilities_test.cpp +++ b/src/mlpack/tests/facilities_test.cpp @@ -20,25 +20,6 @@ using namespace mlpack; using namespace mlpack::cv; -/** - * The unequal sizes for data and labels show throw an error. - */ -TEST_CASE("AssertSizesTest", "[FacilitiesTest]") -{ - // Load the dataset. - arma::mat dataset; - if (!data::Load("iris_train.csv", dataset)) - FAIL("Cannot load test dataset iris_train.csv!"); - // Load the labels. - arma::Row labels; - if (!data::Load("iris_test_labels.csv", labels)) - FAIL("Cannot load test dataset iris_test_labels.csv!"); - - REQUIRE_THROWS_AS( - AssertSizes(dataset, labels, "test"), std::invalid_argument); -} - - /** * Pairwise distances. */ diff --git a/src/mlpack/tests/feedforward_network_2_test.cpp b/src/mlpack/tests/feedforward_network_2_test.cpp index f0cfcd0672..90ba3c3fbd 100644 --- a/src/mlpack/tests/feedforward_network_2_test.cpp +++ b/src/mlpack/tests/feedforward_network_2_test.cpp @@ -65,7 +65,8 @@ TEST_CASE("RBFNetworkTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); @@ -77,7 +78,8 @@ TEST_CASE("RBFNetworkTest", "[FeedForwardNetworkTest]") } arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot open thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); diff --git a/src/mlpack/tests/feedforward_network_test.cpp b/src/mlpack/tests/feedforward_network_test.cpp index 2a5e1b3a27..a41d745676 100644 --- a/src/mlpack/tests/feedforward_network_test.cpp +++ b/src/mlpack/tests/feedforward_network_test.cpp @@ -47,7 +47,7 @@ void TestNetwork(ModelType& model, for (size_t i = 0; i < predictionTemp.n_cols; ++i) { prediction(i) = arma::as_scalar(arma::find( - arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)) + 1; + arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)); } size_t correct = arma::accu(prediction == testLabels); @@ -107,9 +107,11 @@ TEST_CASE("CheckCopyMovingVanillaNetworkTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); /* @@ -162,7 +164,8 @@ TEST_CASE("CheckCopyMovingReparametrizationNetworkTest", "[FeedForwardNetworkTes arma::mat trainData; data::Load("thyroid_train.csv", trainData, true); - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); /* @@ -196,7 +199,8 @@ TEST_CASE("CheckCopyMovingLinear3DNetworkTest", "[FeedForwardNetworkTest]") arma::mat trainData; data::Load("thyroid_train.csv", trainData, true); - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); /* @@ -245,10 +249,10 @@ TEST_CASE("CheckCopyMovingLinear3DNetworkTest", "[FeedForwardNetworkTest]") */ TEST_CASE("CheckCopyMovingNoisyLinearTest", "[FeedForwardNetworkTest]") { - // Create training input by 5x5 matrix. - arma::mat input = arma::randu(10,1); - // Create training output by 1 matrix. - arma::mat output = arma::mat("1"); + // Create training input by 10x1 matrix (only 1 point). + arma::mat input = arma::randu(10, 1); + // Create training output by 1-point matrix. + arma::mat output = arma::mat("0"); // Check copying constructor. FFN> *model1 = new FFN>(); @@ -334,7 +338,8 @@ TEST_CASE("CheckCopyMovingDropoutNetworkTest", "[FeedForwardNetworkTest]") arma::mat trainData; data::Load("thyroid_train.csv", trainData, true); - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); /* @@ -387,16 +392,20 @@ TEST_CASE("FFVanillaNetworkTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // Labels should be from 0 to numClasses - 1. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // Labels should be from 0 to numClasses - 1. /* * Construct a feed forward network with trainData.n_rows input nodes, @@ -440,7 +449,6 @@ TEST_CASE("FFVanillaNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model1; model1.Add >(dataset.n_rows, 10); @@ -462,7 +470,6 @@ TEST_CASE("ForwardBackwardTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model; model.Add >(dataset.n_rows, 50); @@ -509,7 +516,7 @@ TEST_CASE("ForwardBackwardTest", "[FeedForwardNetworkTest]") for (size_t i = 0; i < currentResuls.n_cols; ++i) { prediction(i) = arma::as_scalar(arma::find( - arma::max(currentResuls.col(i)) == currentResuls.col(i), 1)) + 1; + arma::max(currentResuls.col(i)) == currentResuls.col(i), 1)); } size_t correct = arma::accu(prediction == currentLabels); @@ -534,16 +541,20 @@ TEST_CASE("DropoutNetworkTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // Labels should be from 0 to numClasses - 1. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // Labels should be from 0 to numClasses - 1. /* * Construct a feed forward network with trainData.n_rows input nodes, @@ -589,7 +600,6 @@ TEST_CASE("DropoutNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model1; model1.Add >(dataset.n_rows, 10); @@ -615,7 +625,6 @@ TEST_CASE("HighwayNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model; model.Add >(dataset.n_rows, 10); @@ -635,16 +644,20 @@ TEST_CASE("DropConnectNetworkTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The range should be between 0 and numClasses - 1. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The range should be between 0 and numClasses - 1. /* * Construct a feed forward network with trainData.n_rows input nodes, @@ -690,7 +703,6 @@ TEST_CASE("DropConnectNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model1; model1.Add >(dataset.n_rows, 10); @@ -724,16 +736,20 @@ TEST_CASE("FFSerializationTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses - 1. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses - 1. // Vanilla neural net with logistic activation function. // Because 92% of the patients are not hyperthyroid the neural @@ -773,16 +789,20 @@ TEST_CASE("CustomLayerTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses - 1. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses - 1. FFN, RandomInitialization, CustomLayer<> > model; model.Add >(trainData.n_rows, 8); @@ -852,16 +872,20 @@ TEST_CASE("FFNTrainReturnObjective", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses. // Vanilla neural net with logistic activation function. // Because 92% of the patients are not hyperthyroid the neural @@ -922,16 +946,20 @@ TEST_CASE("OptimizerTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses. arma::mat testData; - data::Load("thyroid_test.csv", testData, true); + if (!data::Load("thyroid_test.csv", testData)) + FAIL("Cannot load dataset thyroid_test.csv"); arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses. FFN, RandomInitialization, CustomLayer<> > model; model.Add >(trainData.n_rows, 8); @@ -951,15 +979,17 @@ TEST_CASE("FFNCheckInputShapeTest", "[FeedForwardNetworkTest]") { // Load the dataset. arma::mat trainData; - data::Load("thyroid_train.csv", trainData, true); + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); arma::mat testData; data::Load("thyroid_test.csv", testData, true); - arma::mat testLabels = testData.row(testData.n_rows - 1); + arma::mat testLabels = testData.row(testData.n_rows - 1) - 1; testData.shed_row(testData.n_rows - 1); FFN, RandomInitialization, CustomLayer<> > model; diff --git a/src/mlpack/tests/function_test.cpp b/src/mlpack/tests/function_test.cpp deleted file mode 100644 index 5486ac1e87..0000000000 --- a/src/mlpack/tests/function_test.cpp +++ /dev/null @@ -1,681 +0,0 @@ -/** - * @file tests/function_test.cpp - * @author Ryan Curtin - * @author Shikhar Bhardwaj - * - * Test the Function<> class to see that it properly adds functionality. - * - * 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 -#include -#include -#include -#include - -#include -#include "test_tools.hpp" - -using namespace mlpack; -using namespace mlpack::optimization; -using namespace ens::traits; // For some SFINAE checks. -using namespace mlpack::regression; - -/** - * Utility class with no functions. - */ -class EmptyTestFunction { }; - -/** - * Utility class with Evaluate() but no Evaluate(). - */ -class EvaluateTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) - { - return arma::accu(coordinates); - } - - double Evaluate(const arma::mat& coordinates, - const size_t begin, - const size_t batchSize) - { - return arma::accu(coordinates) + begin + batchSize; - } -}; - -/** - * Utility class with Gradient() but no Evaluate(). - */ -class GradientTestFunction -{ - public: - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - void Gradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -/** - * Utility class with Gradient() and Evaluate(). - */ -class EvaluateGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) - { - return arma::accu(coordinates); - } - - double Evaluate(const arma::mat& coordinates, - const size_t /* begin */, - const size_t /* batchSize */) - { - return arma::accu(coordinates); - } - - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - void Gradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -/** - * Utility class with EvaluateWithGradient(). - */ -class EvaluateWithGradientTestFunction -{ - public: - double EvaluateWithGradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } - - double EvaluateWithGradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } -}; - -/** - * Utility class with all three functions. - */ -class EvaluateAndWithGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) - { - return arma::accu(coordinates); - } - - double Evaluate(const arma::mat& coordinates, - const size_t begin, - const size_t batchSize) - { - return arma::accu(coordinates) + batchSize + begin; - } - - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - void Gradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - double EvaluateWithGradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } - - double EvaluateWithGradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } -}; - -/** - * Utility class with const Evaluate() and non-const Gradient(). - */ -class EvaluateAndNonConstGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) const - { - return arma::accu(coordinates); - } - - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -/** - * Utility class with const Evaluate() and non-const Gradient(). - */ -class EvaluateAndStaticGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) const - { - return arma::accu(coordinates); - } - - static void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -BOOST_AUTO_TEST_SUITE(FunctionTest); - -/** - * Make sure that an empty class doesn't have any methods added to it. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientEmptyTest) -{ - const bool hasEvaluate = HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Evaluate(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientEvaluateOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientGradientOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we add EvaluateWithGradient() when we have both Evaluate() and - * Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientBothTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add Evaluate() and Gradient() when we have only - * EvaluateWithGradient(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientEvaluateWithGradientTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add no methods when we already have all three. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientAllThreeTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -BOOST_AUTO_TEST_CASE(LogisticRegressionEvaluateWithGradientTest) -{ - const bool hasEvaluate = - HasEvaluate>, - EvaluateConstForm>::value; - const bool hasGradient = - HasGradient>, - GradientConstForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient>, - EvaluateWithGradientConstForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -BOOST_AUTO_TEST_CASE(SDPTest) -{ - typedef AugLagrangianFunction>> FunctionType; - - const bool hasEvaluate = - HasEvaluate, EvaluateConstForm>::value; - const bool hasGradient = - HasGradient, GradientConstForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientConstForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure that an empty class doesn't have any methods added to it. - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientEmptyTest) -{ - const bool hasEvaluate = HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Evaluate(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientEvaluateOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientGradientOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we add EvaluateWithGradient() when we have both Evaluate() and - * Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientBothTest) -{ - const bool hasEvaluate = - HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = - HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add Evaluate() and Gradient() when we have only - * EvaluateWithGradient(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWGradientEvaluateWithGradientTest) -{ - const bool hasEvaluate = - HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = - HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - Function f; - arma::mat coordinates(10, 10, arma::fill::ones); - arma::mat gradient; - f.Gradient(coordinates, 0, gradient, 5); - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add no methods when we already have all three. - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientAllThreeTest) -{ - const bool hasEvaluate = - HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = - HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we can properly create EvaluateWithGradient() even when one of the - * functions is non-const. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientMixedTypesTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateConstForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we can properly create EvaluateWithGradient() even when one of the - * functions is static. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientMixedTypesStaticTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateConstForm>::value; - const bool hasGradient = - HasGradient, - GradientStaticForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientConstForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -class A -{ - public: - size_t NumFunctions() const; - size_t NumFeatures() const; - double Evaluate(const arma::mat&, const size_t, const size_t) const; - void Gradient(const arma::mat&, const size_t, arma::mat&, const size_t) const; - void Gradient(const arma::mat&, const size_t, arma::sp_mat&, const size_t) - const; - void PartialGradient(const arma::mat&, const size_t, arma::sp_mat&) const; -}; - -class B -{ - public: - size_t NumFunctions(); - size_t NumFeatures(); - double Evaluate(const arma::mat&, const size_t, const size_t); - void Gradient(const arma::mat&, const size_t, arma::mat&, const size_t); - void Gradient(const arma::mat&, const size_t, arma::sp_mat&, const size_t); - void PartialGradient(const arma::mat&, const size_t, arma::sp_mat&); -}; - -class C -{ - public: - size_t NumConstraints() const; - double Evaluate(const arma::mat&) const; - void Gradient(const arma::mat&, arma::mat&) const; - double EvaluateConstraint(const size_t, const arma::mat&) const; - void GradientConstraint(const size_t, const arma::mat&, arma::mat&) const; -}; - -class D -{ - public: - size_t NumConstraints(); - double Evaluate(const arma::mat&); - void Gradient(const arma::mat&, arma::mat&); - double EvaluateConstraint(const size_t, const arma::mat&); - void GradientConstraint(const size_t, const arma::mat&, arma::mat&); -}; - - -/** - * Test the correctness of the static check for DecomposableFunctionType API. - */ -BOOST_AUTO_TEST_CASE(DecomposableFunctionTypeCheckTest) -{ - static_assert(CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - static_assert(CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - static_assert(!CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - static_assert(!CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - - static_assert(CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - static_assert(CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - static_assert(!CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - static_assert(!CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - - static_assert(CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); - static_assert(CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); - static_assert(!CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); - static_assert(!CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); -} - -/** - * Test the correctness of the static check for LagrangianFunctionType API. - */ -BOOST_AUTO_TEST_CASE(LagrangianFunctionTypeCheckTest) -{ - static_assert(!CheckEvaluate::value, "CheckEvaluate static check failed."); - static_assert(!CheckEvaluate::value, "CheckEvaluate static check failed."); - static_assert(CheckEvaluate::value, "CheckEvaluate static check failed."); - static_assert(CheckEvaluate::value, "CheckEvaluate static check failed."); - - static_assert(!CheckGradient::value, "CheckGradient static check failed."); - static_assert(!CheckGradient::value, "CheckGradient static check failed."); - static_assert(CheckGradient::value, "CheckGradient static check failed."); - static_assert(CheckGradient::value, "CheckGradient static check failed."); - - static_assert(!CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - static_assert(!CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - static_assert(CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - static_assert(CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - - static_assert(!CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - static_assert(!CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - static_assert(CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - static_assert(CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - - static_assert(!CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); - static_assert(!CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); - static_assert(CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); - static_assert(CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); -} - -/** - * Test the correctness of the static check for SparseFunctionType API. - */ -BOOST_AUTO_TEST_CASE(SparseFunctionTypeCheckTest) -{ - static_assert(CheckSparseGradient::value, - "CheckSparseGradient static check failed."); - static_assert(CheckSparseGradient::value, - "CheckSparseGradient static check failed."); - static_assert(!CheckSparseGradient::value, - "CheckSparseGradient static check failed."); - static_assert(!CheckSparseGradient::value, - "CheckSparseGradient static check failed."); -} - -/** - * Test the correctness of the static check for SparseFunctionType API. - */ -BOOST_AUTO_TEST_CASE(ResolvableFunctionTypeCheckTest) -{ - static_assert(CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - static_assert(CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - static_assert(!CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - static_assert(!CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - - static_assert(CheckPartialGradient::value, - "CheckPartialGradient static check failed."); - static_assert(CheckPartialGradient::value, - "CheckPartialGradient static check failed."); - static_assert(!CheckPartialGradient::value, - "CheckPartialGradient static check failed."); - static_assert(!CheckPartialGradient::value, - "CheckPartialGradient static check failed."); -} - -BOOST_AUTO_TEST_SUITE_END(); diff --git a/src/mlpack/tests/io_test.cpp b/src/mlpack/tests/io_test.cpp index fa363face9..26319a8d79 100644 --- a/src/mlpack/tests/io_test.cpp +++ b/src/mlpack/tests/io_test.cpp @@ -413,7 +413,8 @@ TEST_CASE_METHOD(IOTestDestroyer, "OutputColParamTest", // Now load the vector back and make sure it was saved correctly. arma::vec dataset2; - data::Load("test.csv", dataset2); + if (!data::Load("test.csv", dataset2)) + FAIL("Cannot load dataset test.csv"); REQUIRE(dataset.n_rows == dataset2.n_rows); for (size_t i = 0; i < dataset.n_elem; ++i) @@ -461,7 +462,8 @@ TEST_CASE_METHOD(IOTestDestroyer, "OutputUnsignedColParamTest", // Now load the vector back and make sure it was saved correctly. arma::Col dataset2; - data::Load("test.csv", dataset2); + if (!data::Load("test.csv", dataset2)) + FAIL("Cannot load dataset test.csv"); REQUIRE(dataset.n_rows == dataset2.n_rows); for (size_t i = 0; i < dataset.n_elem; ++i) @@ -509,7 +511,8 @@ TEST_CASE_METHOD(IOTestDestroyer, "OutputRowParamTest", // Now load the row vector back and make sure it was saved correctly. arma::rowvec dataset2; - data::Load("test.csv", dataset2); + if (!data::Load("test.csv", dataset2)) + FAIL("Cannot load dataset test.csv"); REQUIRE(dataset.n_cols == dataset2.n_cols); for (size_t i = 0; i < dataset.n_elem; ++i) @@ -556,7 +559,8 @@ TEST_CASE_METHOD(IOTestDestroyer, "OutputUnsignedRowParamTest", "[IOTest]") // Now load the row vector back and make sure it was saved correctly. arma::Row dataset2; - data::Load("test.csv", dataset2); + if (!data::Load("test.csv", dataset2)) + FAIL("Cannot load dataset test.csv"); REQUIRE(dataset.n_cols == dataset2.n_cols); for (size_t i = 0; i < dataset.n_elem; ++i) @@ -784,7 +788,8 @@ TEST_CASE_METHOD(IOTestDestroyer, "OutputMatrixParamTest", // Now load the matrix back and make sure it was saved correctly. arma::mat dataset2; - data::Load("test.csv", dataset2); + if (!data::Load("test.csv", dataset2)) + FAIL("Cannot load dataset test.csv"); REQUIRE(dataset.n_cols == dataset2.n_cols); REQUIRE(dataset.n_rows == dataset2.n_rows); @@ -833,7 +838,8 @@ TEST_CASE_METHOD(IOTestDestroyer, "OutputMatrixNoTransposeParamTest", // Now load the matrix back and make sure it was saved correctly. arma::mat dataset2; - data::Load("test.csv", dataset2, true, false); + if (!data::Load("test.csv", dataset2, false, false)) + FAIL("Cannot load dataset test.csv"); REQUIRE(dataset.n_cols == dataset2.n_cols); REQUIRE(dataset.n_rows == dataset2.n_rows); diff --git a/src/mlpack/tests/kfn_test.cpp b/src/mlpack/tests/kfn_test.cpp index f3fefadb7a..1fb813b2e1 100644 --- a/src/mlpack/tests/kfn_test.cpp +++ b/src/mlpack/tests/kfn_test.cpp @@ -335,7 +335,7 @@ TEST_CASE("KFNDualTreeVsNaive1", "[KFNTest]") // Hard-coded filename: bad? if (!data::Load("test_data_3_1000.csv", dataset)) - FAIL("Cannot load test dataset test_data_3_1000.csv!"); + FAIL("Cannot load test dataset test_data_3_1000.csv"); KFN kfn(dataset); @@ -369,7 +369,7 @@ TEST_CASE("KFNDualTreeVsNaive2", "[KFNTest]") // Hard-coded filename: bad? // Code duplication: also bad! if (!data::Load("test_data_3_1000.csv", dataset)) - FAIL("Cannot load test dataset test_data_3_1000.csv!"); + FAIL("Cannot load test dataset test_data_3_1000.csv"); KFN kfn(dataset); @@ -403,7 +403,7 @@ TEST_CASE("KFNSingleTreeVsNaive", "[KFNTest]") // Hard-coded filename: bad! // Code duplication: also bad! if (!data::Load("test_data_3_1000.csv", dataset)) - FAIL("Cannot load test dataset test_data_3_1000.csv!"); + FAIL("Cannot load test dataset test_data_3_1000.csv"); KFN kfn(dataset, SINGLE_TREE_MODE); @@ -466,7 +466,8 @@ TEST_CASE("KFNSingleCoverTreeTest", "[KFNTest]") TEST_CASE("KFNDualCoverTreeTest", "[KFNTest]") { arma::mat dataset; - data::Load("test_data_3_1000.csv", dataset); + if (!data::Load("test_data_3_1000.csv", dataset)) + FAIL("Cannot load test dataset test_data_3_1000.csv"); KFN tree(dataset); @@ -538,7 +539,8 @@ TEST_CASE("KFNSingleBallTreeTest", "[KFNTest]") TEST_CASE("KFNDualBallTreeTest", "[KFNTest]") { arma::mat dataset; - data::Load("test_data_3_1000.csv", dataset); + if (!data::Load("test_data_3_1000.csv", dataset)) + FAIL("Cannot load test dataset test_data_3_1000.csv"); KFN tree(dataset); diff --git a/src/mlpack/tests/kmeans_test.cpp b/src/mlpack/tests/kmeans_test.cpp index 5d4bae2abf..c20014c046 100644 --- a/src/mlpack/tests/kmeans_test.cpp +++ b/src/mlpack/tests/kmeans_test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -486,6 +487,72 @@ TEST_CASE("RefinedStartTest", "[KMeansTest]") REQUIRE(distortion < 14000.0); } +/** + * Test that the k-means++ initialization strategy returns decent initial + * cluster estimates. + */ +TEST_CASE("KMeansPlusPlusTest", "[KMeansTest]") +{ + // Our dataset will be five Gaussians of largely varying numbers of points and + // we expect that the refined starting policy should return good guesses at + // what these Gaussians are. + arma::mat data(3, 3000); + data.randn(); + + // First Gaussian: 10000 points, centered at (0, 0, 0). + // Second Gaussian: 2000 points, centered at (5, 0, -2). + // Third Gaussian: 5000 points, centered at (-2, -2, -2). + // Fourth Gaussian: 1000 points, centered at (-6, 8, 8). + // Fifth Gaussian: 12000 points, centered at (1, 6, 1). + arma::mat centroids(" 0 5 -2 -6 1;" + " 0 0 -2 8 6;" + " 0 -2 -2 8 1"); + + for (size_t i = 1000; i < 1200; ++i) + data.col(i) += centroids.col(1); + for (size_t i = 1200; i < 1700; ++i) + data.col(i) += centroids.col(2); + for (size_t i = 1700; i < 1800; ++i) + data.col(i) += centroids.col(3); + for (size_t i = 1800; i < 3000; ++i) + data.col(i) += centroids.col(4); + + KMeansPlusPlusInitialization k; + arma::mat resultingCentroids; + k.Cluster(data, 5, resultingCentroids); + + // Calculate resulting assignments. + arma::Row assignments(data.n_cols); + for (size_t i = 0; i < data.n_cols; ++i) + { + double bestDist = DBL_MAX; + for (size_t j = 0; j < 5; ++j) + { + const double dist = metric::EuclideanDistance::Evaluate(data.col(i), + resultingCentroids.col(j)); + if (dist < bestDist) + { + bestDist = dist; + assignments[i] = j; + } + } + } + + // Calculate sum of distances from centroid means. + double distortion = 0; + for (size_t i = 0; i < 3000; ++i) + distortion += metric::EuclideanDistance::Evaluate(data.col(i), + resultingCentroids.col(assignments[i])); + + // Using k-means++, the distance for this dataset is usually around + // 10000. Regular k-means is between 10000 and 30000 (I think the 10000 + // figure is a corner case which actually does not give good clusters), and + // random initial starts give distortion around 22000. So we'll require that + // our distortion is less than 14500. (It seems like there is a lot of noise + // in the result.) + REQUIRE(distortion < 14500.0); +} + #ifdef ARMA_HAS_SPMAT /** * Make sure sparse k-means works okay. diff --git a/src/mlpack/tests/knn_test.cpp b/src/mlpack/tests/knn_test.cpp index 7e84700843..449099556f 100644 --- a/src/mlpack/tests/knn_test.cpp +++ b/src/mlpack/tests/knn_test.cpp @@ -794,7 +794,8 @@ TEST_CASE("KNNSingleCoverTreeTest", "[KNNTest]") TEST_CASE("KNNDualCoverTreeTest", "[KNNTest]") { arma::mat dataset; - data::Load("test_data_3_1000.csv", dataset); + if (!data::Load("test_data_3_1000.csv", dataset)) + FAIL("Cannot load test dataset test_data_3_1000.csv"); KNN tree(dataset); @@ -865,7 +866,8 @@ TEST_CASE("KNNSingleBallTreeTest", "[KNNTest]") TEST_CASE("KNNDualBallTreeTest", "[KNNTest]") { arma::mat dataset; - data::Load("test_data_3_1000.csv", dataset); + if (!data::Load("test_data_3_1000.csv", dataset)) + FAIL("Cannot load test dataset test_data_3_1000.csv"); KNN tree(dataset); @@ -1112,28 +1114,28 @@ TEST_CASE("KNNModelTest", "[KNNTest]") // We only have std::move() constructors so make a copy of our data. arma::mat referenceCopy(referenceData); arma::mat queryCopy(queryData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE); if (j == 2) - models[i].BuildModel(std::move(referenceCopy), 20, NAIVE_MODE); + models[i].BuildModel(std::move(referenceCopy), NAIVE_MODE); arma::Mat neighbors; arma::mat distances; models[i].Search(std::move(queryCopy), 3, neighbors, distances); - REQUIRE(neighbors.n_rows ==baselineNeighbors.n_rows); - REQUIRE(neighbors.n_cols ==baselineNeighbors.n_cols); - REQUIRE(neighbors.n_elem ==baselineNeighbors.n_elem); - REQUIRE(distances.n_rows ==baselineDistances.n_rows); - REQUIRE(distances.n_cols ==baselineDistances.n_cols); - REQUIRE(distances.n_elem ==baselineDistances.n_elem); + REQUIRE(neighbors.n_rows == baselineNeighbors.n_rows); + REQUIRE(neighbors.n_cols == baselineNeighbors.n_cols); + REQUIRE(neighbors.n_elem == baselineNeighbors.n_elem); + REQUIRE(distances.n_rows == baselineDistances.n_rows); + REQUIRE(distances.n_cols == baselineDistances.n_cols); + REQUIRE(distances.n_elem == baselineDistances.n_elem); for (size_t k = 0; k < distances.n_elem; ++k) { - REQUIRE(neighbors[k] ==baselineNeighbors[k]); + REQUIRE(neighbors[k] == baselineNeighbors[k]); if (std::abs(baselineDistances[k]) < 1e-5) REQUIRE(distances[k] == Approx(0.0).margin(1e-7)); else @@ -1194,28 +1196,28 @@ TEST_CASE("KNNModelMonochromaticTest", "[KNNTest]") { // We only have a std::move() constructor... so copy the data. arma::mat referenceCopy(referenceData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE); if (j == 2) - models[i].BuildModel(std::move(referenceCopy), 20, NAIVE_MODE); + models[i].BuildModel(std::move(referenceCopy), NAIVE_MODE); arma::Mat neighbors; arma::mat distances; models[i].Search(3, neighbors, distances); - REQUIRE(neighbors.n_rows ==baselineNeighbors.n_rows); - REQUIRE(neighbors.n_cols ==baselineNeighbors.n_cols); - REQUIRE(neighbors.n_elem ==baselineNeighbors.n_elem); - REQUIRE(distances.n_rows ==baselineDistances.n_rows); - REQUIRE(distances.n_cols ==baselineDistances.n_cols); - REQUIRE(distances.n_elem ==baselineDistances.n_elem); + REQUIRE(neighbors.n_rows == baselineNeighbors.n_rows); + REQUIRE(neighbors.n_cols == baselineNeighbors.n_cols); + REQUIRE(neighbors.n_elem == baselineNeighbors.n_elem); + REQUIRE(distances.n_rows == baselineDistances.n_rows); + REQUIRE(distances.n_cols == baselineDistances.n_cols); + REQUIRE(distances.n_elem == baselineDistances.n_elem); for (size_t k = 0; k < distances.n_elem; ++k) { - REQUIRE(neighbors[k] ==baselineNeighbors[k]); + REQUIRE(neighbors[k] == baselineNeighbors[k]); if (std::abs(baselineDistances[k]) < 1e-5) REQUIRE(distances[k] == Approx(0.0).margin(1e-7)); else diff --git a/src/mlpack/tests/krann_search_test.cpp b/src/mlpack/tests/krann_search_test.cpp index 4efa022776..68214b4a25 100644 --- a/src/mlpack/tests/krann_search_test.cpp +++ b/src/mlpack/tests/krann_search_test.cpp @@ -35,13 +35,16 @@ TEST_CASE("NaiveGuaranteeTest", "[KRANNTest]") arma::mat refData; arma::mat queryData; - data::Load("rann_test_r_3_900.csv", refData, true); - data::Load("rann_test_q_3_100.csv", queryData, true); + if (!data::Load("rann_test_r_3_900.csv", refData)) + FAIL("Cannot load dataset rann_test_r_3_900.csv"); + if (!data::Load("rann_test_q_3_100.csv", queryData)) + FAIL("Cannot load dataset rann_test_q_3_100.csv"); RASearch<> rsRann(refData, true, false, 1.0); arma::mat qrRanks; - data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. + if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. + FAIL("Cannot load dataset rann_test_qr_ranks.csv"); size_t numRounds = 1000; arma::Col numSuccessRounds(queryData.n_cols); @@ -88,8 +91,10 @@ TEST_CASE("SingleTreeSearch", "[KRANNTest]") arma::mat refData; arma::mat queryData; - data::Load("rann_test_r_3_900.csv", refData, true); - data::Load("rann_test_q_3_100.csv", queryData, true); + if (!data::Load("rann_test_r_3_900.csv", refData)) + FAIL("Cannot load dataset rann_test_r_3_900.csv"); + if (!data::Load("rann_test_q_3_100.csv", queryData)) + FAIL("Cannot load dataset rann_test_q_3_100.csv"); // Search for 1 rank-approximate nearest-neighbors in the top 30% of the point // (rank error of 3). @@ -100,7 +105,8 @@ TEST_CASE("SingleTreeSearch", "[KRANNTest]") // The relative ranks for the given query reference pair arma::Mat qrRanks; - data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. + if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. + FAIL("Cannot load dataset rann_test_qr_ranks.csv"); size_t numRounds = 1000; arma::Col numSuccessRounds(queryData.n_cols); @@ -147,8 +153,10 @@ TEST_CASE("DualTreeSearch", "[KRANNTest]") arma::mat refData; arma::mat queryData; - data::Load("rann_test_r_3_900.csv", refData, true); - data::Load("rann_test_q_3_100.csv", queryData, true); + if (!data::Load("rann_test_r_3_900.csv", refData)) + FAIL("Cannot load dataset rann_test_r_3_900.csv"); + if (!data::Load("rann_test_q_3_100.csv", queryData)) + FAIL("Cannot load dataset rann_test_q_3_100.csv"); // Search for 1 rank-approximate nearest-neighbors in the top 30% of the point // (rank error of 3). @@ -158,7 +166,8 @@ TEST_CASE("DualTreeSearch", "[KRANNTest]") RASearch<> tsdRann(refData, false, false, 1.0, 0.95, false, false, 5); arma::Mat qrRanks; - data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. + if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. + FAIL("Cannot load dataset rann_test_qr_ranks.csv"); size_t numRounds = 1000; arma::Col numSuccessRounds(queryData.n_cols); @@ -274,8 +283,10 @@ TEST_CASE("SingleCoverTreeTest", "[KRANNTest]") arma::mat refData; arma::mat queryData; - data::Load("rann_test_r_3_900.csv", refData, true); - data::Load("rann_test_q_3_100.csv", queryData, true); + if (!data::Load("rann_test_r_3_900.csv", refData)) + FAIL("Cannot load dataset rann_test_r_3_900.csv"); + if (!data::Load("rann_test_q_3_100.csv", queryData)) + FAIL("Cannot load dataset rann_test_q_3_100.csv"); // Search for 1 rank-approximate nearest-neighbors in the top 30% of the point // (rank error of 3). @@ -289,7 +300,8 @@ TEST_CASE("SingleCoverTreeTest", "[KRANNTest]") // The relative ranks for the given query reference pair. arma::Mat qrRanks; - data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. + if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. + FAIL("Cannot load dataset rann_test_qr_ranks.csv"); size_t numRounds = 100; arma::Col numSuccessRounds(queryData.n_cols); @@ -335,8 +347,10 @@ TEST_CASE("DualCoverTreeTest", "[KRANNTest]") arma::mat refData; arma::mat queryData; - data::Load("rann_test_r_3_900.csv", refData, true); - data::Load("rann_test_q_3_100.csv", queryData, true); + if (!data::Load("rann_test_r_3_900.csv", refData)) + FAIL("Cannot load dataset rann_test_r_3_900.csv"); + if (!data::Load("rann_test_q_3_100.csv", queryData)) + FAIL("Cannot load dataset rann_test_q_3_100.csv"); // Search for 1 rank-approximate nearest-neighbors in the top 30% of the point // (rank error of 3). @@ -354,7 +368,8 @@ TEST_CASE("DualCoverTreeTest", "[KRANNTest]") RACoverTreeSearch tsdRann(&refTree, false, 1.0, 0.95, false, false, 5); arma::Mat qrRanks; - data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. + if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. + FAIL("Cannot load dataset rann_test_qr_ranks.csv"); size_t numRounds = 100; arma::Col numSuccessRounds(queryData.n_cols); @@ -620,37 +635,38 @@ TEST_CASE("RAModelTest", "[KRANNTest]") { // Ensure that we can build an RAModel and get correct // results. - typedef RAModel KNNModel; - arma::mat queryData, referenceData; - data::Load("rann_test_r_3_900.csv", referenceData, true); - data::Load("rann_test_q_3_100.csv", queryData, true); + if (!data::Load("rann_test_r_3_900.csv", referenceData)) + FAIL("Cannot load dataset rann_test_r_3_900.csv"); + if (!data::Load("rann_test_q_3_100.csv", queryData)) + FAIL("Cannot load dataset rann_test_q_3_100.csv"); // Build all the possible models. - KNNModel models[20]; - models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, false); - models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, true); - models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, false); - models[3] = KNNModel(KNNModel::TreeTypes::COVER_TREE, true); - models[4] = KNNModel(KNNModel::TreeTypes::R_TREE, false); - models[5] = KNNModel(KNNModel::TreeTypes::R_TREE, true); - models[6] = KNNModel(KNNModel::TreeTypes::R_STAR_TREE, false); - models[7] = KNNModel(KNNModel::TreeTypes::R_STAR_TREE, true); - models[8] = KNNModel(KNNModel::TreeTypes::X_TREE, false); - models[9] = KNNModel(KNNModel::TreeTypes::X_TREE, true); - models[10] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false); - models[11] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true); - models[12] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, false); - models[13] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, true); - models[14] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, false); - models[15] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, true); - models[16] = KNNModel(KNNModel::TreeTypes::UB_TREE, false); - models[17] = KNNModel(KNNModel::TreeTypes::UB_TREE, true); - models[18] = KNNModel(KNNModel::TreeTypes::OCTREE, false); - models[19] = KNNModel(KNNModel::TreeTypes::OCTREE, true); + RAModel models[20]; + models[0] = RAModel(RAModel::TreeTypes::KD_TREE, false); + models[1] = RAModel(RAModel::TreeTypes::KD_TREE, true); + models[2] = RAModel(RAModel::TreeTypes::COVER_TREE, false); + models[3] = RAModel(RAModel::TreeTypes::COVER_TREE, true); + models[4] = RAModel(RAModel::TreeTypes::R_TREE, false); + models[5] = RAModel(RAModel::TreeTypes::R_TREE, true); + models[6] = RAModel(RAModel::TreeTypes::R_STAR_TREE, false); + models[7] = RAModel(RAModel::TreeTypes::R_STAR_TREE, true); + models[8] = RAModel(RAModel::TreeTypes::X_TREE, false); + models[9] = RAModel(RAModel::TreeTypes::X_TREE, true); + models[10] = RAModel(RAModel::TreeTypes::HILBERT_R_TREE, false); + models[11] = RAModel(RAModel::TreeTypes::HILBERT_R_TREE, true); + models[12] = RAModel(RAModel::TreeTypes::R_PLUS_TREE, false); + models[13] = RAModel(RAModel::TreeTypes::R_PLUS_TREE, true); + models[14] = RAModel(RAModel::TreeTypes::R_PLUS_PLUS_TREE, false); + models[15] = RAModel(RAModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[16] = RAModel(RAModel::TreeTypes::UB_TREE, false); + models[17] = RAModel(RAModel::TreeTypes::UB_TREE, true); + models[18] = RAModel(RAModel::TreeTypes::OCTREE, false); + models[19] = RAModel(RAModel::TreeTypes::OCTREE, true); arma::Mat qrRanks; - data::Load("rann_test_qr_ranks.csv", qrRanks, true, false); // No transpose. + if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. + FAIL("Cannot load dataset rann_test_qr_ranks.csv"); for (size_t j = 0; j < 3; ++j) { diff --git a/src/mlpack/tests/ksinit_test.cpp b/src/mlpack/tests/ksinit_test.cpp index 11d6cdae40..930df617e4 100644 --- a/src/mlpack/tests/ksinit_test.cpp +++ b/src/mlpack/tests/ksinit_test.cpp @@ -230,8 +230,10 @@ TEST_CASE("IrisDataset", "[KSInitialization]") arma::mat dataset, labels; - data::Load("iris.csv", dataset, true); - data::Load("iris_labels.txt", labels, true); + if (!data::Load("iris.csv", dataset)) + FAIL("Cannot load dataset iris.csv"); + if (!data::Load("iris_labels.txt", labels)) + FAIL("Cannot load dataset iris_labels.txt"); dataset.insert_rows(dataset.n_rows, labels); diff --git a/src/mlpack/tests/lars_test.cpp b/src/mlpack/tests/lars_test.cpp index ef030343ae..bb3a2aa767 100644 --- a/src/mlpack/tests/lars_test.cpp +++ b/src/mlpack/tests/lars_test.cpp @@ -111,8 +111,10 @@ TEST_CASE("CholeskySingularityTest", "[LARSTest]") arma::mat X; arma::mat Y; - data::Load("lars_dependent_x.csv", X); - data::Load("lars_dependent_y.csv", Y); + if (!data::Load("lars_dependent_x.csv", X)) + FAIL("Cannot load dataset lars_dependent_x.csv"); + if (!data::Load("lars_dependent_y.csv", Y)) + FAIL("Cannot load dataset lars_dependent_y.csv"); arma::rowvec y = Y.row(0); @@ -135,8 +137,10 @@ TEST_CASE("NoCholeskySingularityTest", "[LARSTest]") arma::mat X; arma::mat Y; - data::Load("lars_dependent_x.csv", X); - data::Load("lars_dependent_y.csv", Y); + if (!data::Load("lars_dependent_x.csv", X)) + FAIL("Cannot load dataset lars_dependent_x.csv"); + if (!data::Load("lars_dependent_y.csv", Y)) + FAIL("Cannot load dataset lars_dependent_y.csv"); arma::rowvec y = Y.row(0); @@ -357,8 +361,10 @@ TEST_CASE("LARSTrainReturnCorrelation", "[LARSTest]") arma::mat X; arma::mat Y; - data::Load("lars_dependent_x.csv", X); - data::Load("lars_dependent_y.csv", Y); + if (!data::Load("lars_dependent_x.csv", X)) + FAIL("Cannot load dataset lars_dependent_x.csv"); + if (!data::Load("lars_dependent_y.csv", Y)) + FAIL("Cannot load dataset lars_dependent_y.csv"); arma::rowvec y = Y.row(0); @@ -403,8 +409,10 @@ TEST_CASE("LARSTestComputeError", "[LARSTest]") arma::mat X; arma::mat Y; - data::Load("lars_dependent_x.csv", X); - data::Load("lars_dependent_y.csv", Y); + if (!data::Load("lars_dependent_x.csv", X)) + FAIL("Cannot load dataset lars_dependent_x.csv"); + if (!data::Load("lars_dependent_y.csv", Y)) + FAIL("Cannot load dataset lars_dependent_y.csv"); arma::rowvec y = Y.row(0); @@ -427,8 +435,10 @@ TEST_CASE("LARSCopyConstructorTest", "[LARSTest]") arma::rowvec targets; // Load training input and predictions for testing. - data::Load("lars_dependent_x.csv", features); - data::Load("lars_dependent_y.csv", Y); + if (!data::Load("lars_dependent_x.csv", features)) + FAIL("Cannot load dataset lars_dependent_x.csv"); + if (!data::Load("lars_dependent_y.csv", Y)) + FAIL("Cannot load dataset lars_dependent_y.csv"); targets = Y.row(0); // Check if the copy is accessible even after deleting the pointer to the diff --git a/src/mlpack/tests/lin_alg_test.cpp b/src/mlpack/tests/lin_alg_test.cpp index a43821d75d..4b8af04b79 100644 --- a/src/mlpack/tests/lin_alg_test.cpp +++ b/src/mlpack/tests/lin_alg_test.cpp @@ -91,7 +91,8 @@ TEST_CASE("TestOrthogonalize", "[LinAlgTest]") // Generate a random matrix; then, orthogonalize it and test if it's // orthogonal. mat tmp, orth; - data::Load("fake.csv", tmp); + if (!data::Load("fake.csv", tmp)) + FAIL("Cannot load dataset fake.csv"); Orthogonalize(tmp, orth); // test orthogonality diff --git a/src/mlpack/tests/lmnn_test.cpp b/src/mlpack/tests/lmnn_test.cpp index b767f4a940..4a0ce99360 100644 --- a/src/mlpack/tests/lmnn_test.cpp +++ b/src/mlpack/tests/lmnn_test.cpp @@ -699,8 +699,10 @@ TEST_CASE("LMNNFunctionGradientTest3", "[LMNNTest]") { arma::mat dataset; arma::Row labels; - data::Load("iris.csv", dataset); - data::Load("iris_labels.txt", labels); + if (!data::Load("iris.csv", dataset)) + FAIL("Cannot load dataset iris.csv"); + if (!data::Load("iris_labels.txt", labels)) + FAIL("Cannot load dataset iris_labels.txt"); LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); @@ -716,8 +718,10 @@ TEST_CASE("LMNNFunctionGradientTest4", "[LMNNTest]") { arma::mat dataset; arma::Row labels; - data::Load("iris.csv", dataset); - data::Load("iris_labels.txt", labels); + if (!data::Load("iris.csv", dataset)) + FAIL("Cannot load dataset iris.csv"); + if (!data::Load("iris_labels.txt", labels)) + FAIL("Cannot load dataset iris_labels.txt"); LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); diff --git a/src/mlpack/tests/load_save_test.cpp b/src/mlpack/tests/load_save_test.cpp index b9e8a839cf..602533a84b 100644 --- a/src/mlpack/tests/load_save_test.cpp +++ b/src/mlpack/tests/load_save_test.cpp @@ -1414,8 +1414,10 @@ TEST_CASE("RegularCSVDatasetInfoLoad", "[LoadSaveTest]") { arma::mat one, two; DatasetInfo info; - data::Load(testFiles[i], one); - data::Load(testFiles[i], two, info); + if (!data::Load(testFiles[i], one)) + FAIL("Cannot load dataset"); + if (!data::Load(testFiles[i], two, info)) + FAIL("Cannot load dataset"); // Check that the matrices contain the same information. REQUIRE(one.n_elem == two.n_elem); @@ -1454,8 +1456,10 @@ TEST_CASE("NontransposedCSVDatasetInfoLoad", "[LoadSaveTest]") { arma::mat one, two; DatasetInfo info; - data::Load(testFiles[i], one, true, false); // No transpose. - data::Load(testFiles[i], two, info, true, false); + if (!data::Load(testFiles[i], one, false, false)) // No transpose. + FAIL("Cannot load dataset"); + if (!data::Load(testFiles[i], two, info, false, false)) + FAIL("Cannot load dataset"); // Check that the matrices contain the same information. REQUIRE(one.n_elem == two.n_elem); @@ -1494,7 +1498,8 @@ TEST_CASE("CategoricalCSVLoadTest00", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info); + if (!data::Load("test.csv", matrix, info)) + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 7); REQUIRE(matrix.n_rows == 3); @@ -1551,7 +1556,8 @@ TEST_CASE("CategoricalCSVLoadTest01", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true); + if (!data::Load("test.csv", matrix, info)) + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 4); REQUIRE(matrix.n_rows == 3); @@ -1596,7 +1602,8 @@ TEST_CASE("CategoricalCSVLoadTest02", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true); + if (!data::Load("test.csv", matrix, info)) + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 4); REQUIRE(matrix.n_rows == 3); @@ -1640,7 +1647,8 @@ TEST_CASE("CategoricalCSVLoadTest03", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true); + if (!data::Load("test.csv", matrix, info)) + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 4); REQUIRE(matrix.n_rows == 3); @@ -1684,7 +1692,8 @@ TEST_CASE("CategoricalCSVLoadTest04", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true); + if (!data::Load("test.csv", matrix, info)) + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 4); REQUIRE(matrix.n_rows == 3); @@ -1731,7 +1740,8 @@ TEST_CASE("CategoricalNontransposedCSVLoadTest00", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true, false); // No transpose. + if (!data::Load("test.csv", matrix, info, false, false)) // No transpose. + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 3); REQUIRE(matrix.n_rows == 7); @@ -1820,7 +1830,8 @@ TEST_CASE("CategoricalNontransposedCSVLoadTest01", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true, false); // No transpose. + if (!data::Load("test.csv", matrix, info, false, false)) // No transpose. + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 3); REQUIRE(matrix.n_rows == 4); @@ -1865,7 +1876,8 @@ TEST_CASE("CategoricalNontransposedCSVLoadTest02", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true, false); // No transpose. + if (!data::Load("test.csv", matrix, info, false, false)) // No transpose. + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 3); REQUIRE(matrix.n_rows == 4); @@ -1910,7 +1922,8 @@ TEST_CASE("CategoricalNontransposedCSVLoadTest03", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true, false); // No transpose. + if (!data::Load("test.csv", matrix, info, false, false)) // No transpose. + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 3); REQUIRE(matrix.n_rows == 4); @@ -1955,7 +1968,8 @@ TEST_CASE("CategoricalNontransposedCSVLoadTest04", "[LoadSaveTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info, true, false); // No transpose. + if (!data::Load("test.csv", matrix, info, false, false)) // No transpose. + FAIL("Cannot load dataset"); REQUIRE(matrix.n_cols == 3); REQUIRE(matrix.n_rows == 4); @@ -2003,7 +2017,8 @@ TEST_CASE("HarderKeonTest", "[LoadSaveTest]") // Load transposed. arma::mat dataset; data::DatasetInfo info; - data::Load("test.csv", dataset, info, true, true); + if (!data::Load("test.csv", dataset, info, false, true)) + FAIL("Cannot load dataset"); REQUIRE(dataset.n_rows == 5); REQUIRE(dataset.n_cols == 4); @@ -2017,7 +2032,8 @@ TEST_CASE("HarderKeonTest", "[LoadSaveTest]") // Now load non-transposed. data::DatasetInfo ntInfo; - data::Load("test.csv", dataset, ntInfo, true, false); + if (!data::Load("test.csv", dataset, ntInfo, false, false)) + FAIL("Cannot load dataset"); REQUIRE(dataset.n_rows == 4); REQUIRE(dataset.n_cols == 5); @@ -2052,7 +2068,8 @@ TEST_CASE("SimpleARFFTest", "[LoadSaveTest]") arma::mat dataset; DatasetInfo info; - data::Load("test.arff", dataset, info); + if (!data::Load("test.arff", dataset, info)) + FAIL("Cannot load dataset"); REQUIRE(info.Dimensionality() == 2); REQUIRE(info.Type(0) == Datatype::numeric); @@ -2093,7 +2110,8 @@ TEST_CASE("SimpleARFFCategoricalTest", "[LoadSaveTest]") arma::mat dataset; DatasetInfo info; - data::Load("test.arff", dataset, info); + if (!data::Load("test.arff", dataset, info)) + FAIL("Cannot load dataset"); REQUIRE(info.Dimensionality() == 3); @@ -2152,7 +2170,8 @@ TEST_CASE("HarderARFFTest", "[LoadSaveTest]") arma::mat dataset; DatasetInfo info; - data::Load("test.arff", dataset, info); + if (!data::Load("test.arff", dataset, info)) + FAIL("Cannot load dataset"); REQUIRE(info.Dimensionality() == 5); diff --git a/src/mlpack/tests/loss_functions_test.cpp b/src/mlpack/tests/loss_functions_test.cpp index 5a208984dd..1fa4283c1a 100644 --- a/src/mlpack/tests/loss_functions_test.cpp +++ b/src/mlpack/tests/loss_functions_test.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -955,3 +956,80 @@ TEST_CASE("TripletMarginLossTest") REQUIRE(arma::accu(output) == -12); REQUIRE(output.n_elem == 1); } + +/** + * Simple test for the Hinge loss function. + */ +TEST_CASE("HingeLossTest", "[LossFunctionsTest]") +{ + arma::mat input, target, target_b, output; + double loss, loss_b; + HingeLoss<> module1; + HingeLoss<> module2(false); + + // Test the Forward function. Loss should be 0 if input = target. + input = arma::ones(10, 1); + target = arma::ones(10, 1); + loss = module1.Forward(input, target); + REQUIRE(loss == 0); + + // Test the Backward function for input = target. + module1.Backward(input, target, output); + for (double el : output) + { + // For input = target we should get 0.0 everywhere. + REQUIRE(el == Approx(0.0).epsilon(1e-5)); + } + + REQUIRE(output.n_rows == input.n_rows); + REQUIRE(output.n_cols == input.n_cols); + + // Randomly generated input. + input = { { 0.90599973, -0.33040298, 0.07123354}, + { 0.71988434, 0.49657596, 0.39873373}, + { -0.57646927, 0.3951491 , -0.1003365}, + { 0.12528634, 0.68122971, 0.85448826} }; + + // Randomly generated target. + target = { { -1, -1, 1}, + { -1, 1, 1}, + { 1, -1, -1}, + { 1, -1, -1} }; + + // Binary target can be obtained by replacing -1 with 0 in target. + target_b = { { 0, 0, 1}, + { 0, 1, 1}, + { 1, 0, 0}, + { 1, 0, 0} }; + + // Test for binary labels as target. + loss = module1.Forward(input, target); + loss_b = module1.Forward(input, target_b); + + // Loss should be same due to internal conversion of binary labels. + REQUIRE(loss == loss_b); + + // Test for sum reduction. + // Test the Forward function. + // Loss calculated by referring to implementation of tf.keras.losses.hinge. + loss = module1.Forward(input, target); + REQUIRE(loss == Approx(14.61065).epsilon(1e-3)); + + // Test the Backward function + module1.Backward(input, target, output); + REQUIRE(arma::accu(output) == Approx(-5).epsilon(1e-3)); + REQUIRE(output.n_rows == input.n_rows); + REQUIRE(output.n_cols == input.n_cols); + + // Test for mean reduction. + // Test for the Forward function. + // Loss calculated by referring to implementation of tf.keras.losses.hinge. + loss = module2.Forward(input, target); + REQUIRE(loss == Approx(1.21755).epsilon(1e-3)); + + // Test the Backward function. + module2.Backward(input, target, output); + REQUIRE(arma::accu(output) == Approx(-0.41667).epsilon(1e-3)); + REQUIRE(output.n_rows == input.n_rows); + REQUIRE(output.n_cols == input.n_cols); +} diff --git a/src/mlpack/tests/lrsdp_test.cpp b/src/mlpack/tests/lrsdp_test.cpp index f07b13ad7f..b2ac089997 100644 --- a/src/mlpack/tests/lrsdp_test.cpp +++ b/src/mlpack/tests/lrsdp_test.cpp @@ -95,7 +95,8 @@ BOOST_AUTO_TEST_CASE(Johnson844LovaszThetaSDP) { // Load the edges. arma::mat edges; - data::Load("johnson8-4-4.csv", edges, true); + if (!data::Load("johnson8-4-4.csv", edges)) + FAIL("Cannot load dataset johnson8-4-4.csv"); // The LRSDP itself and the initial point. arma::mat coordinates; @@ -150,7 +151,8 @@ BOOST_AUTO_TEST_CASE(ErdosRenyiRandomGraphMaxCutSDP) { // Load the edges. arma::mat edges; - data::Load("erdosrenyi-n100.csv", edges, true); + if (!data::Load("erdosrenyi-n100.csv", edges) + FAIL("Cannot load dataset erdosrenyi-n100.csv"); arma::sp_mat laplacian; CreateSparseGraphLaplacian(edges, laplacian); @@ -221,8 +223,10 @@ BOOST_AUTO_TEST_CASE(GaussianMatrixSensingSDP) arma::mat Xorig, A; // read the unknown matrix X and the measurement matrices A_i in - data::Load("sensing_X.csv", Xorig, true, false); - data::Load("sensing_A.csv", A, true, false); + if (!data::Load("sensing_X.csv", Xorig, false, false)) + FAIL("Cannot load dataset sensing_X.csv"); + if (!data::Load("sensing_A.csv", A, false, false)) + FAIL("Cannot load dataset sensing_A.csv"); const size_t m = Xorig.n_rows; const size_t n = Xorig.n_cols; diff --git a/src/mlpack/tests/lsh_test.cpp b/src/mlpack/tests/lsh_test.cpp index 4e10e4b1f0..1d445192b5 100644 --- a/src/mlpack/tests/lsh_test.cpp +++ b/src/mlpack/tests/lsh_test.cpp @@ -115,8 +115,10 @@ TEST_CASE("NumTablesTest", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Run classic knn on reference data. KNN knn(rdata); @@ -187,8 +189,10 @@ TEST_CASE("HashWidthTest", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Run classic knn on reference data. KNN knn(rdata); @@ -247,8 +251,10 @@ TEST_CASE("NumProjTest", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Run classic knn on reference data. KNN knn(rdata); @@ -307,8 +313,10 @@ TEST_CASE("RecallTest", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Run classic knn on reference data. KNN knn(rdata); @@ -502,8 +510,10 @@ TEST_CASE("MultiprobeTest", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Add a slight amount of noise to the dataset, so that we don't end up with // points that have the same distance (hopefully). @@ -775,8 +785,10 @@ TEST_CASE("ParallelBichromatic", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Where to store neighbors and distances arma::Mat sequentialNeighbors; @@ -814,7 +826,8 @@ TEST_CASE("ParallelMonochromatic", "[LSHTest]") // Read iris training data as reference and query set. const string trainSet = "iris_train.csv"; arma::mat rdata; - data::Load(trainSet, rdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); // Where to store neighbors and distances arma::Mat sequentialNeighbors; @@ -931,8 +944,10 @@ TEST_CASE("SparseLSHTest", "[LSHTest]") const string testSet = "iris_test.csv"; arma::mat rdata; arma::mat qdata; - data::Load(trainSet, rdata, true); - data::Load(testSet, qdata, true); + if (!data::Load(trainSet, rdata)) + FAIL("Cannot load dataset"); + if (!data::Load(testSet, qdata)) + FAIL("Cannot load dataset"); // Run on dense data. LSHSearch<> denseLSH( diff --git a/src/mlpack/tests/main_tests/cf_test.cpp b/src/mlpack/tests/main_tests/cf_test.cpp index b136d9b730..da1c8c77fc 100644 --- a/src/mlpack/tests/main_tests/cf_test.cpp +++ b/src/mlpack/tests/main_tests/cf_test.cpp @@ -213,13 +213,13 @@ TEST_CASE_METHOD(CFTestFixture, "CFModelReuseTest", IO::GetSingleton().Parameters()["algorithm"].wasPassed = false; // Reuse the model to get recommendations. - int recommendations = 3; - const int querySize = 7; + size_t recommendations = 3; + const size_t querySize = 7; Mat query = arma::linspace>(0, querySize - 1, querySize); SetInputParam("query", std::move(query)); - SetInputParam("recommendations", recommendations); + SetInputParam("recommendations", int(recommendations)); SetInputParam("input_model", std::move(IO::GetParam("output_model"))); @@ -261,18 +261,21 @@ TEST_CASE_METHOD(CFTestFixture, "CFRankTest", { mat dataset; data::Load("GroupLensSmall.csv", dataset); - int rank = 7; + size_t rank = 7; SetInputParam("training", std::move(dataset)); - SetInputParam("rank", rank); + SetInputParam("rank", int(rank)); SetInputParam("max_iterations", int(10)); SetInputParam("algorithm", std::string("NMF")); mlpackMain(); const CFModel* outputModel = IO::GetParam("output_model"); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); - REQUIRE(outputModel->template CFPtr()->Rank() == rank); + REQUIRE(cf.Rank() == rank); } /** @@ -295,10 +298,13 @@ TEST_CASE_METHOD(CFTestFixture, "CFMinResidueTest", mlpack::math::FixedRandomSeed(); mlpackMain(); - outputModel = IO::GetParam("output_model"); + outputModel = IO::GetParam("output_model"); // By default the main program use NMFPolicy. - const mat w1 = outputModel->template CFPtr()->Decomposition().W(); - const mat h1 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w1 = cf.Decomposition().W(); + const mat h1 = cf.Decomposition().H(); ResetSettings(); @@ -314,15 +320,18 @@ TEST_CASE_METHOD(CFTestFixture, "CFMinResidueTest", outputModel = IO::GetParam("output_model"); // By default the main program use NMFPolicy. - const mat w2 = outputModel->template CFPtr()->Decomposition().W(); - const mat h2 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf2 = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w2 = cf2.Decomposition().W(); + const mat h2 = cf2.Decomposition().H(); // The resulting matrices should be different. REQUIRE((arma::norm(w1 - w2) > 1e-5 || arma::norm(h1 - h2) > 1e-5)); } /** - * Test that itertaion_only_termination is used. + * Test that iteration_only_termination is used. */ TEST_CASE_METHOD(CFTestFixture, "CFIterationOnlyTerminationTest", "[CFMainTest][BindingTests]") @@ -341,10 +350,13 @@ TEST_CASE_METHOD(CFTestFixture, "CFIterationOnlyTerminationTest", mlpack::math::FixedRandomSeed(); mlpackMain(); - outputModel = IO::GetParam("output_model"); + outputModel = IO::GetParam("output_model"); // By default, the main program use NMFPolicy. - const mat w1 = outputModel->template CFPtr()->Decomposition().W(); - const mat h1 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w1 = cf.Decomposition().W(); + const mat h1 = cf.Decomposition().H(); ResetSettings(); @@ -359,8 +371,11 @@ TEST_CASE_METHOD(CFTestFixture, "CFIterationOnlyTerminationTest", outputModel = IO::GetParam("output_model"); // By default, the main program use NMFPolicy. - const mat w2 = outputModel->template CFPtr()->Decomposition().W(); - const mat h2 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf2 = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w2 = cf2.Decomposition().W(); + const mat h2 = cf2.Decomposition().H(); // The resulting matrices should be different. REQUIRE((arma::norm(w1 - w2) > 1e-5 || arma::norm(h1 - h2) > 1e-5)); @@ -387,8 +402,11 @@ TEST_CASE_METHOD(CFTestFixture, "CFMaxIterationsTest", outputModel = IO::GetParam("output_model"); // By default, the main program use NMFPolicy. - const mat w1 = outputModel->template CFPtr()->Decomposition().W(); - const mat h1 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w1 = cf.Decomposition().W(); + const mat h1 = cf.Decomposition().H(); ResetSettings(); @@ -403,8 +421,11 @@ TEST_CASE_METHOD(CFTestFixture, "CFMaxIterationsTest", outputModel = IO::GetParam("output_model"); // By default the main program use NMFPolicy. - const mat w2 = outputModel->template CFPtr()->Decomposition().W(); - const mat h2 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf2 = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w2 = cf2.Decomposition().W(); + const mat h2 = cf2.Decomposition().H(); // The resulting matrices should be different. REQUIRE((arma::norm(w1 - w2) > 1e-5 || arma::norm(h1 - h2) > 1e-5)); diff --git a/src/mlpack/tests/main_tests/krann_test.cpp b/src/mlpack/tests/main_tests/krann_test.cpp index b61044f104..57cb0905d6 100644 --- a/src/mlpack/tests/main_tests/krann_test.cpp +++ b/src/mlpack/tests/main_tests/krann_test.cpp @@ -192,7 +192,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNRefModelTest", // Input pre-trained model. SetInputParam("input_model", - std::move(IO::GetParam("output_model"))); + std::move(IO::GetParam("output_model"))); Log::Fatal.ignoreInput = true; REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error); @@ -285,10 +285,10 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNModelReuseTest", arma::Mat neighbors; arma::mat distances; - RANNModel* output_model; + RAModel* output_model; neighbors = std::move(IO::GetParam>("neighbors")); distances = std::move(IO::GetParam("distances")); - output_model = std::move(IO::GetParam("output_model")); + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -324,8 +324,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentLeafSizes", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -341,7 +341,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentLeafSizes", // Check that initial output matrices and the output matrices using // saved model are equal. CHECK(output_model->LeafSize() == (int) 1); - CHECK(IO::GetParam("output_model")->LeafSize() == (int) 10); + CHECK(IO::GetParam("output_model")->LeafSize() == (int) 10); delete output_model; } @@ -361,8 +361,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTau", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset the passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -378,7 +378,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTau", // Check that initial output matrices and the output matrices using // saved model are equal CHECK(output_model->Tau() == (double) 5); - CHECK(IO::GetParam("output_model")->Tau() == + CHECK(IO::GetParam("output_model")->Tau() == (double) 10); delete output_model; } @@ -399,8 +399,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentAlpha", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset the passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -416,7 +416,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentAlpha", // Check that initial output matrices and the output matrices using // saved model are equal CHECK(output_model->Alpha() == (double) 0.95); - CHECK(IO::GetParam("output_model")->Alpha() == + CHECK(IO::GetParam("output_model")->Alpha() == (double) 0.80); delete output_model; } @@ -437,8 +437,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTreeType", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset the passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -455,7 +455,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTreeType", // saved model are equal const bool check = output_model->TreeType() == 0; CHECK(check == true); - CHECK(IO::GetParam("output_model")->TreeType() == + CHECK(IO::GetParam("output_model")->TreeType() == 8); delete output_model; } @@ -476,8 +476,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSingleSampleLimit", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -492,7 +492,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSingleSampleLimit", // Check that initial output matrices and the output matrices using // saved model are equal. - CHECK(IO::GetParam("output_model")->SingleSampleLimit() == + CHECK(IO::GetParam("output_model")->SingleSampleLimit() == (int) 15); CHECK(output_model->SingleSampleLimit() == (int) 20); delete output_model; @@ -514,8 +514,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSampleAtLeaves", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -530,7 +530,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSampleAtLeaves", // Check that initial output matrices and the output matrices using // saved model are equal. - CHECK(IO::GetParam("output_model")->SampleAtLeaves() == + CHECK(IO::GetParam("output_model")->SampleAtLeaves() == (bool) true); CHECK(output_model->SampleAtLeaves() == (bool) false); delete output_model; diff --git a/src/mlpack/tests/matrix_completion_test.cpp b/src/mlpack/tests/matrix_completion_test.cpp index 104d169ef6..6fb3baff79 100644 --- a/src/mlpack/tests/matrix_completion_test.cpp +++ b/src/mlpack/tests/matrix_completion_test.cpp @@ -34,8 +34,10 @@ TEST_CASE("UniformMatrixCompletionSDP", "[MatrixCompletionTest]") arma::mat Xorig, values; arma::umat indices; - data::Load("completion_X.csv", Xorig, true, false); - data::Load("completion_indices.csv", indices, true, false); + if (!data::Load("completion_X.csv", Xorig, false, false)) + FAIL("Cannot load dataset completion_X.csv"); + if (!data::Load("completion_indices.csv", indices, false, false)) + FAIL("Cannot load dataset completion_indices.csv"); values.set_size(indices.n_cols); for (size_t i = 0; i < indices.n_cols; ++i) diff --git a/src/mlpack/tests/nbc_test.cpp b/src/mlpack/tests/nbc_test.cpp index ddc0487315..3b757d3b76 100644 --- a/src/mlpack/tests/nbc_test.cpp +++ b/src/mlpack/tests/nbc_test.cpp @@ -26,8 +26,10 @@ TEST_CASE("NaiveBayesClassifierTest", "[NBCTest]") size_t classes = 2; arma::mat trainData, trainRes, calcMat; - data::Load(trainFilename, trainData, true); - data::Load(trainResultFilename, trainRes, true); + if (!data::Load(trainFilename, trainData)) + FAIL("Cannot load dataset"); + if (!data::Load(trainResultFilename, trainRes)) + FAIL("Cannot load dataset"); // Get the labels out. arma::Row labels(trainData.n_cols); @@ -66,9 +68,12 @@ TEST_CASE("NaiveBayesClassifierTest", "[NBCTest]") arma::mat testResProbs; arma::Row calcVec; arma::mat calcProbs; - data::Load(testFilename, testData, true); - data::Load(testResultFilename, testRes, true); - data::Load(testResultProbsFilename, testResProbs, true); + if (!data::Load(testFilename, testData)) + FAIL("Cannot load dataset"); + if (!data::Load(testResultFilename, testRes)) + FAIL("Cannot load dataset"); + if (!data::Load(testResultProbsFilename, testResProbs)) + FAIL("Cannot load dataset"); testData.shed_row(testData.n_rows - 1); // Remove the labels. @@ -99,8 +104,10 @@ TEST_CASE("NaiveBayesClassifierIncrementalTest", "[NBCTest]") size_t classes = 2; arma::mat trainData, trainRes, calcMat; - data::Load(trainFilename, trainData, true); - data::Load(trainResultFilename, trainRes, true); + if (!data::Load(trainFilename, trainData)) + FAIL("Cannot load dataset"); + if (!data::Load(trainResultFilename, trainRes)) + FAIL("Cannot load dataset"); // Get the labels out. arma::Row labels(trainData.n_cols); @@ -139,9 +146,12 @@ TEST_CASE("NaiveBayesClassifierIncrementalTest", "[NBCTest]") arma::mat testResProba; arma::Row calcVec; arma::mat calcProbs; - data::Load(testFilename, testData, true); - data::Load(testResultFilename, testRes, true); - data::Load(testResultProbsFilename, testResProba, true); + if (!data::Load(testFilename, testData)) + FAIL("Cannot load dataset"); + if (!data::Load(testResultFilename, testRes)) + FAIL("Cannot load dataset"); + if (!data::Load(testResultProbsFilename, testResProba)) + FAIL("Cannot load dataset"); testData.shed_row(testData.n_rows - 1); // Remove the labels. @@ -170,8 +180,10 @@ TEST_CASE("SeparateTrainTest", "[NBCTest]") size_t classes = 2; arma::mat trainData, trainRes, calcMat; - data::Load(trainFilename, trainData, true); - data::Load(trainResultFilename, trainRes, true); + if (!data::Load(trainFilename, trainData)) + FAIL("Cannot load dataset"); + if (!data::Load(trainResultFilename, trainRes)) + FAIL("Cannot load dataset"); // Get the labels out. arma::Row labels(trainData.n_cols); @@ -228,8 +240,10 @@ TEST_CASE("SeparateTrainIncrementalTest", "[NBCTest]") size_t classes = 2; arma::mat trainData, trainRes, calcMat; - data::Load(trainFilename, trainData, true); - data::Load(trainResultFilename, trainRes, true); + if (!data::Load(trainFilename, trainData)) + FAIL("Cannot load dataset"); + if (!data::Load(trainResultFilename, trainRes)) + FAIL("Cannot load dataset"); // Get the labels out. arma::Row labels(trainData.n_cols); @@ -286,8 +300,10 @@ TEST_CASE("SeparateTrainIndividualIncrementalTest", "[NBCTest]") size_t classes = 2; arma::mat trainData, trainRes, calcMat; - data::Load(trainFilename, trainData, true); - data::Load(trainResultFilename, trainRes, true); + if (!data::Load(trainFilename, trainData)) + FAIL("Cannot load dataset"); + if (!data::Load(trainResultFilename, trainRes)) + FAIL("Cannot load dataset"); // Get the labels out. arma::Row labels(trainData.n_cols); @@ -356,8 +372,10 @@ TEST_CASE("NaiveBayesClassifierHighDimensionsTest", "[NBCTest]") // Create variables for training and assign data to them. arma::mat trainData; arma::Row trainLabels; - data::Load(trainFilename, trainData, true); - data::Load(trainLabelsFileName, trainLabels, true); + if (!data::Load(trainFilename, trainData)) + FAIL("Cannot load dataset"); + if (!data::Load(trainLabelsFileName, trainLabels)) + FAIL("Cannot load dataset"); // Initialize and train a NBC model. NaiveBayesClassifier<> nbcTest(trainData, trainLabels, classes); @@ -366,8 +384,10 @@ TEST_CASE("NaiveBayesClassifierHighDimensionsTest", "[NBCTest]") arma::mat testData, calcProbs; arma::Row testLabels; arma::Row calcVec; - data::Load(testFilename, testData, true); - data::Load(testLabelsFilename, testLabels, true); + if (!data::Load(testFilename, testData)) + FAIL("Cannot load dataset"); + if (!data::Load(testLabelsFilename, testLabels)) + FAIL("Cannot load dataset"); // Classify observations in the test dataset. To use Classify() method with // a parameter for probabilities of predictions, we pass 'calcProbs' to the diff --git a/src/mlpack/tests/nystroem_method_test.cpp b/src/mlpack/tests/nystroem_method_test.cpp index 1c99a7b7e6..32bf189d49 100644 --- a/src/mlpack/tests/nystroem_method_test.cpp +++ b/src/mlpack/tests/nystroem_method_test.cpp @@ -146,7 +146,8 @@ TEST_CASE("GermanTest", "[NystroemMethodTest]") { // Load the dataset. arma::mat dataset; - data::Load("german.csv", dataset, true); + if (!data::Load("german.csv", dataset)) + FAIL("Cannot load dataset german.csv"); // These are our tolerance bounds. double results[5] = { 32.0, 20.0, 15.0, 12.0, 9.0 }; diff --git a/src/mlpack/tests/one_hot_encoding_test.cpp b/src/mlpack/tests/one_hot_encoding_test.cpp index a3b19363f6..1844539f0f 100644 --- a/src/mlpack/tests/one_hot_encoding_test.cpp +++ b/src/mlpack/tests/one_hot_encoding_test.cpp @@ -191,7 +191,8 @@ TEST_CASE("OneHotEncodingDatasetinfoTest", "[OneHotEncodingTest]") // Load the test CSV. arma::umat matrix; DatasetInfo info; - data::Load("test.csv", matrix, info); + if (!data::Load("test.csv", matrix, info)) + FAIL("Cannot load dataset test.csv"); arma::umat output; data::OneHotEncoding(matrix, output, info); REQUIRE(output.n_cols == 7); diff --git a/src/mlpack/tests/pca_test.cpp b/src/mlpack/tests/pca_test.cpp index 6ccefbbaeb..1d4a1657ed 100644 --- a/src/mlpack/tests/pca_test.cpp +++ b/src/mlpack/tests/pca_test.cpp @@ -240,7 +240,8 @@ TEST_CASE("RandomizedPCADimensionalityReductionTest", "[PCATest]") TEST_CASE("QUICPCADimensionalityReductionTest", "[PCATest]") { arma::mat data, data1; - data::Load("test_data_3_1000.csv", data); + if (!data::Load("test_data_3_1000.csv", data)) + FAIL("Cannot load dataset test_data_3_1000.csv"); data1 = data; arma::mat backupData(data); diff --git a/src/mlpack/tests/quic_svd_test.cpp b/src/mlpack/tests/quic_svd_test.cpp index 9ae2e33a79..16cbfa98af 100644 --- a/src/mlpack/tests/quic_svd_test.cpp +++ b/src/mlpack/tests/quic_svd_test.cpp @@ -24,7 +24,8 @@ TEST_CASE("QUICSVDReconstructionError", "[QUICSVDTest]") { // Load the dataset. arma::mat dataset; - data::Load("test_data_3_1000.csv", dataset); + if (!data::Load("test_data_3_1000.csv", dataset)) + FAIL("Cannot load dataset test_data_3_1000.csv"); // The QUIC-SVD procedure can fail---the Monte Carlo error calculation is // random. Therefore we simply require at least one success. diff --git a/src/mlpack/tests/radical_test.cpp b/src/mlpack/tests/radical_test.cpp index feb687ee52..39b77a11f7 100644 --- a/src/mlpack/tests/radical_test.cpp +++ b/src/mlpack/tests/radical_test.cpp @@ -21,7 +21,8 @@ using namespace arma; TEST_CASE("Radical_Test_Radical3D", "[RadicalTest]") { mat matX; - data::Load("data_3d_mixed.txt", matX); + if (!data::Load("data_3d_mixed.txt", matX)) + FAIL("Cannot load dataset data_3d_mixed.txt"); Radical rad(0.175, 5, 100, matX.n_rows - 1); @@ -39,7 +40,8 @@ TEST_CASE("Radical_Test_Radical3D", "[RadicalTest]") } mat matS; - data::Load("data_3d_ind.txt", matS); + if (!data::Load("data_3d_ind.txt", matS)) + FAIL("Cannot load dataset data_3d_ind.txt"); rad.DoRadical(matS, matY, matW); matYT = trans(matY); diff --git a/src/mlpack/tests/random_forest_test.cpp b/src/mlpack/tests/random_forest_test.cpp index 3b9999914e..95d1f09a6a 100644 --- a/src/mlpack/tests/random_forest_test.cpp +++ b/src/mlpack/tests/random_forest_test.cpp @@ -122,9 +122,11 @@ TEST_CASE("UnweightedNumericLearningTest", "[RandomForestTest]") { // Load the vc2 dataset. arma::mat dataset; - data::Load("vc2.csv", dataset); + if (!data::Load("vc2.csv", dataset)) + FAIL("Cannot load dataset vc2.csv"); arma::Row labels; - data::Load("vc2_labels.txt", labels); + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load dataset vc2.csv"); // Build a random forest and a decision tree. RandomForest<> rf(dataset, labels, 3, 20 /* 20 trees */, 1, 1e-7); @@ -132,9 +134,11 @@ TEST_CASE("UnweightedNumericLearningTest", "[RandomForestTest]") // Get performance statistics on test data. arma::mat testDataset; - data::Load("vc2_test.csv", testDataset); + if (!data::Load("vc2_test.csv", testDataset)) + FAIL("Cannot load dataset vc2_test.csv"); arma::Row testLabels; - data::Load("vc2_test_labels.txt", testLabels); + if (!data::Load("vc2_test_labels.txt", testLabels)) + FAIL("Cannot load dataset vc2_test_labels.txt"); arma::Row rfPredictions; arma::Row dtPredictions; @@ -158,8 +162,10 @@ TEST_CASE("WeightedNumericLearningTest", "[RandomForestTest]") { arma::mat dataset; arma::Row labels; - data::Load("vc2.csv", dataset); - data::Load("vc2_labels.txt", labels); + if (!data::Load("vc2.csv", dataset)) + FAIL("Cannot load dataset vc2.csv"); + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load dataset vc2_labels.txt"); // Add some noise. arma::mat noise(dataset.n_rows, 1000, arma::fill::randu); @@ -184,9 +190,11 @@ TEST_CASE("WeightedNumericLearningTest", "[RandomForestTest]") // Get performance statistics on test data. arma::mat testDataset; - data::Load("vc2_test.csv", testDataset); + if (!data::Load("vc2_test.csv", testDataset)) + FAIL("Cannot load dataset vc2_test.csv"); arma::Row testLabels; - data::Load("vc2_test_labels.txt", testLabels); + if (!data::Load("vc2_test_labels.txt", testLabels)) + FAIL("Cannot load dataset vc2_test_labels.txt"); arma::Row rfPredictions; arma::Row dtPredictions; @@ -304,9 +312,11 @@ TEST_CASE("LeafSizeDatasetTest", "[RandomForestTest]") { // Load the vc2 dataset. arma::mat dataset; - data::Load("vc2.csv", dataset); + if (!data::Load("vc2.csv", dataset)) + FAIL("Cannot load dataset vc2.csv"); arma::Row labels; - data::Load("vc2_labels.txt", labels); + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load dataset vc2.csv"); // Build a random forest with a leaf size equal to the number of points in the // dataset. @@ -338,9 +348,11 @@ TEST_CASE("RandomForestSerializationTest", "[RandomForestTest]") { // Load the vc2 dataset. arma::mat dataset; - data::Load("vc2.csv", dataset); + if (!data::Load("vc2.csv", dataset)) + FAIL("Cannot load dataset vc2.csv"); arma::Row labels; - data::Load("vc2_labels.txt", labels); + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load dataset vc2.csv"); RandomForest<> rf(dataset, labels, 3, 10 /* 10 trees */, 1); @@ -374,8 +386,10 @@ TEST_CASE("RandomForestNumericTrainReturnEntropy", "[RandomForestTest]") { arma::mat dataset; arma::Row labels; - data::Load("vc2.csv", dataset); - data::Load("vc2_labels.txt", labels); + if (!data::Load("vc2.csv", dataset)) + FAIL("Cannot load dataset vc2.csv"); + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load dataset vc2_labels.txt"); // Add some noise. arma::mat noise(dataset.n_rows, 1000, arma::fill::randu); diff --git a/src/mlpack/tests/recurrent_network_test.cpp b/src/mlpack/tests/recurrent_network_test.cpp index 0993b78d5c..7781b6c50c 100644 --- a/src/mlpack/tests/recurrent_network_test.cpp +++ b/src/mlpack/tests/recurrent_network_test.cpp @@ -435,7 +435,7 @@ TEST_CASE("SequenceClassificationBRNNTest", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -463,10 +463,10 @@ TEST_CASE("SequenceClassificationBRNNTest", "[RecurrentNetworkTest]") { const int predictionValue = arma::as_scalar(arma::find( arma::max(prediction.slice(rho - 1).col(i)) == - prediction.slice(rho - 1).col(i), 1) + 1); + prediction.slice(rho - 1).col(i), 1)); const int targetValue = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); if (predictionValue == targetValue) { @@ -510,7 +510,7 @@ TEST_CASE("SequenceClassificationTest", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -554,10 +554,10 @@ TEST_CASE("SequenceClassificationTest", "[RecurrentNetworkTest]") { const int predictionValue = arma::as_scalar(arma::find( arma::max(prediction.slice(rho - 1).col(i)) == - prediction.slice(rho - 1).col(i), 1) + 1); + prediction.slice(rho - 1).col(i), 1)); const int targetValue = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); if (predictionValue == targetValue) { @@ -728,7 +728,7 @@ TEST_CASE("RNNTrainReturnObjective", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -782,7 +782,7 @@ TEST_CASE("BRNNTrainReturnObjective", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -845,15 +845,15 @@ TEST_CASE("LargeRhoValueRnnTest", "[RecurrentNetworkTest]") { const auto strLen = strlen(line); // Responses for NegativeLogLikelihood should be - // non-one-hot-encoded class IDs (from 1 to num_classes). + // non-one-hot-encoded class IDs (from 0 to num_classes - 1). MatType result(1, 1, strLen, arma::fill::zeros); // The response is the *next* letter in the sequence. for (size_t i = 0; i < strLen - 1; ++i) { - result.at(0, 0, i) = static_cast(line[i + 1]) + 1.0; + result.at(0, 0, i) = static_cast(line[i + 1]); } // The final response is empty, so we set it to class 0. - result.at(0, 0, strLen - 1) = 1.0; + result.at(0, 0, strLen - 1) = 0.0; return result; }; diff --git a/src/mlpack/tests/size_checks_test.cpp b/src/mlpack/tests/size_checks_test.cpp new file mode 100644 index 0000000000..d5c7f22e46 --- /dev/null +++ b/src/mlpack/tests/size_checks_test.cpp @@ -0,0 +1,56 @@ +/** + * @file size_checks_test.cpp + * @author Bisakh Mondal + * + * Test file for Utility size_checks. + * + * 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 "catch.hpp" + +using namespace mlpack; +using namespace mlpack::util; + +/** + * Test that CheckSameSizes() works in different cases. + */ +TEST_CASE("CheckSizeTest", "[SizeCheckTest]") +{ + arma::mat data = arma::randu(20, 30); + arma::colvec firstLabels = arma::randu(20); + arma::colvec secondLabels = arma::randu(30); + arma::mat thirdLabels = arma::randu(40, 30); + + REQUIRE_THROWS_AS(CheckSameSizes(data, firstLabels, "TestChecking"), + std::invalid_argument); + REQUIRE_THROWS_AS(CheckSameSizes(data, (size_t) 20, "TestChecking"), + std::invalid_argument); + + REQUIRE_NOTHROW(CheckSameSizes(data, secondLabels, "TestChecking")); + REQUIRE_NOTHROW(CheckSameSizes(data, (size_t) 30, "TestChecking")); + REQUIRE_NOTHROW(CheckSameSizes(data, (size_t) thirdLabels.n_cols, "TestChecking")); +} + +/** + * Test that CheckSameDimensionality() works in different cases. + */ +TEST_CASE("CheckDimensionality", "[SizeCheckTest]") +{ + arma::mat dataset = arma::randu(20, 30); + arma::colvec refSet = arma::randu(20); + arma::colvec refSet2 = arma::randu(40); + + REQUIRE_NOTHROW(CheckSameDimensionality(dataset, (size_t) 20, + "TestingDim")); + REQUIRE_THROWS_AS(CheckSameDimensionality(dataset, (size_t) 100, + "TestingDim"), std::invalid_argument); + + REQUIRE_THROWS_AS(CheckSameDimensionality(dataset, refSet2, "TestingDim"), + std::invalid_argument); + REQUIRE_NOTHROW(CheckSameDimensionality(dataset, refSet, + "TestingDim")); +} diff --git a/src/mlpack/tests/split_data_test.cpp b/src/mlpack/tests/split_data_test.cpp index 8de9d5f66b..075347bd3a 100644 --- a/src/mlpack/tests/split_data_test.cpp +++ b/src/mlpack/tests/split_data_test.cpp @@ -322,3 +322,102 @@ TEST_CASE("StratifiedSplitLargerDataResultTest", "[SplitDataTest]") mat concat = arma::join_rows(std::get<0>(value), std::get<1>(value)); CheckMatEqual(input, concat); } + +/** + * Check that Split() with stratifyData true throws a runtime error if labels + * are not of type arma::Row<>. + */ +TEST_CASE("StratifiedSplitRunTimeErrorTest", "[SplitDataTest]") +{ + mat input(3, 480); + mat labels(2, 480); + input.randu(); + labels.randu(); + + const double test_ratio = 0.3; + + REQUIRE_THROWS_AS(Split(input, labels, test_ratio, false, true), + std::runtime_error); +} + +/* + * Split with input of type field. + */ +TEST_CASE("SplitDataResultField", "[SplitDataTest]") +{ + field input(1, 2); + + mat matA(2, 10); + mat matB(2, 10); + + size_t count = 0; // Counter for unique sequential values. + matA.imbue([&count]() { return ++count; }); + matB.imbue([&count]() { return ++count; }); + + input(0, 0) = matA; + input(0, 1) = matB; + + const auto value = Split(input, 0.5, false); + REQUIRE(std::get<0>(value).n_cols == 1); // Train data. + REQUIRE(std::get<1>(value).n_cols == 1); // Test data. + + field concat = {std::get<0>(value)(0), std::get<1>(value)(0)}; + // Order matters here. + CheckFields(input, concat); +} + +/** + * Test for Split() with labels of type arma::Mat with shuffleData = False. + */ +TEST_CASE("SplitMatrixLabeledData", "[SplitDataTest]") +{ + const mat input(2, 10, fill::randu); + const mat labels(2, 10, fill::randu); + + const auto value = Split(input, labels, 0.2, false); + REQUIRE(std::get<0>(value).n_cols == 8); + REQUIRE(std::get<1>(value).n_cols == 2); + REQUIRE(std::get<2>(value).n_cols == 8); + REQUIRE(std::get<3>(value).n_cols == 2); + + mat input_concat = arma::join_rows(std::get<0>(value), std::get<1>(value)); + mat labels_concat = arma::join_rows(std::get<2>(value), std::get<3>(value)); + + // Order matters here. + CheckMatrices(input, input_concat); + CheckMatrices(labels, labels_concat); +} + +/** + * Split with input of type field and label of type field. + */ +TEST_CASE("SplitLabeledDataResultField", "[SplitDataTest]") +{ + field input(1, 2); + field label(1, 2); + + mat matA(2, 10, fill::randu); + mat matB(2, 10, fill::randu); + + vec vecA(10, fill::randu); + vec vecB(10, fill::randu); + + input(0, 0) = matA; + input(0, 1) = matB; + + label(0, 0) = vecA; + label(0, 1) = vecB; + + const auto value = Split(input, label, 0.5, false); + REQUIRE(std::get<0>(value).n_cols == 1); // Train data. + REQUIRE(std::get<1>(value).n_cols == 1); // Test data. + REQUIRE(std::get<2>(value).n_cols == 1); // Train label. + REQUIRE(std::get<3>(value).n_cols == 1); // Test label. + + field input_concat = {std::get<0>(value)(0), std::get<1>(value)(0)}; + field label_concat = {std::get<2>(value)(0), std::get<3>(value)(0)}; + + // Order matters here. + CheckFields(input, input_concat); + CheckFields(label, label_concat); +} diff --git a/src/mlpack/tests/svd_incremental_test.cpp b/src/mlpack/tests/svd_incremental_test.cpp index 433a86755f..56a55459d7 100644 --- a/src/mlpack/tests/svd_incremental_test.cpp +++ b/src/mlpack/tests/svd_incremental_test.cpp @@ -98,7 +98,8 @@ class SpecificRandomInitialization TEST_CASE("SVDIncompleteIncrementalRegularizationTest", "[SVDIncrementalTest]") { mat dataset; - data::Load("GroupLensSmall.csv", dataset); + if (!data::Load("GroupLensSmall.csv", dataset)) + FAIL("Cannot load dataset GroupLensSmall.csv"); // Generate list of locations for batch insert constructor for sparse // matrices. diff --git a/src/mlpack/tests/svdplusplus_test.cpp b/src/mlpack/tests/svdplusplus_test.cpp index dbf1417397..4ca13e55ad 100644 --- a/src/mlpack/tests/svdplusplus_test.cpp +++ b/src/mlpack/tests/svdplusplus_test.cpp @@ -255,7 +255,8 @@ TEST_CASE("SVDplusPlusOutputSizeTest", "[SVDPlusPlusTest]") { // Load small GroupLens dataset. arma::mat data; - data::Load("GroupLensSmall.csv", data); + if (!data::Load("GroupLensSmall.csv", data)) + FAIL("Cannot load dataset GroupLensSmall.csv"); // Define useful constants. const size_t numUsers = max(data.row(0)) + 1; @@ -288,7 +289,8 @@ TEST_CASE("SVDPlusPlusCleanDataTest", "[SVDPlusPlusTest]") { // Load small GroupLens dataset. arma::mat data; - data::Load("GroupLensSmall.csv", data); + if (!data::Load("GroupLensSmall.csv", data)) + FAIL("Cannot load dataset GroupLensSmall.csv"); // Define useful constants. const size_t numUsers = max(data.row(0)) + 1; diff --git a/src/mlpack/tests/test_catch_tools.hpp b/src/mlpack/tests/test_catch_tools.hpp index 1bac310ddc..879cac8b49 100644 --- a/src/mlpack/tests/test_catch_tools.hpp +++ b/src/mlpack/tests/test_catch_tools.hpp @@ -50,6 +50,20 @@ inline void CheckMatrices(const arma::Mat& a, REQUIRE(a[i] == b[i]); } +template ::value>> +// Check the values of two field types. +inline void CheckFields(const FieldType& a, + const FieldType& b) +{ + REQUIRE(a.n_rows == b.n_rows); + REQUIRE(a.n_cols == b.n_cols); + + for (size_t i = 0; i < a.n_slices; ++i) + CheckMatrices(a(i), b(i)); +} + // Check the values of two cubes. inline void CheckMatrices(const arma::cube& a, const arma::cube& b, diff --git a/src/mlpack/tests/tree_traits_test.cpp b/src/mlpack/tests/tree_traits_test.cpp index caa642c71f..e7c95d1cd4 100644 --- a/src/mlpack/tests/tree_traits_test.cpp +++ b/src/mlpack/tests/tree_traits_test.cpp @@ -31,7 +31,7 @@ using namespace mlpack::metric; // weird things and will cause bizarre problems. // Test the defaults. -TEST_CASE("DefaultsTraitsTest", "[TreeTraitsTestt]") +TEST_CASE("DefaultsTraitsTest", "[TreeTraitsTest]") { // An irrelevant non-tree type class is used here so that the default // implementation of TreeTraits is chosen. @@ -48,7 +48,7 @@ TEST_CASE("DefaultsTraitsTest", "[TreeTraitsTestt]") } // Test the binary space tree traits. -TEST_CASE("BinarySpaceTreeTraitsTest", "[TreeTraitsTestt]") +TEST_CASE("BinarySpaceTreeTraitsTest", "[TreeTraitsTest]") { typedef BinarySpaceTree> TreeType; @@ -74,7 +74,7 @@ TEST_CASE("BinarySpaceTreeTraitsTest", "[TreeTraitsTestt]") } // Test the cover tree traits. -TEST_CASE("CoverTreeTraitsTest", "[TreeTraitsTestt]") +TEST_CASE("CoverTreeTraitsTest", "[TreeTraitsTest]") { // Children may be overlapping. bool b = TreeTraits>::HasOverlappingChildren;