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/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/go/AppendModel.cmake b/CMake/go/AppendModel.cmake index eeb28f7ada..ec85510239 100644 --- a/CMake/go/AppendModel.cmake +++ b/CMake/go/AppendModel.cmake @@ -44,8 +44,8 @@ function(append_model SERIALIZATION_FILE PROGRAM_MAIN_FILE) else () string(APPEND GOMODEL_SAFE_TYPE ${MODEL_CHAR}) endif() - endif() - endforeach() + endforeach() + endif() # See if the model type already exists. file(READ "${SERIALIZATION_FILE}" SERIALIZATION_FILE_CONTENTS) @@ -77,7 +77,7 @@ function(append_model SERIALIZATION_FILE PROGRAM_MAIN_FILE) " C.mlpackSet${MODEL_SAFE_TYPE}" "Ptr(C.CString(identifier), (unsafe.Pointer)(ptr.mem))\n" "}\n\n") - endif () + endif() endforeach () endif() endfunction() 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 2ecd13769c..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) diff --git a/HISTORY.md b/HISTORY.md index f0b696d6ac..2f7aab9538 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,8 @@ ### mlpack ?.?.? ###### ????-??-?? + * Added warm start feature to Random Forest (#2881); this feature is + accessible from mlpack's bindings to different languages. + * Added Pixel Shuffle layer (#2563). * Add "check_input_matrices" option to python bindings that checks @@ -37,6 +40,12 @@ * Add `Lambda1()`, `Lambda2()`, `UseCholesky()`, and `Tolerance()` members to `LARS` so parameters for training can be modified (#2861). + * Remove unused `ElemType` template parameter from `DecisionTree` and + `RandomForest` (#2874). + + * Fix Python binding build when the CMake variable `USE_OPENMP` is set to + `OFF` (#2884). + ### mlpack 3.4.2 ###### 2020-10-26 * Added Mean Absolute Percentage Error. diff --git a/README.md b/README.md index bb8ba1be6c..21b9e09e7e 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ src="https://cdn.rawgit.com/mlpack/mlpack.org/e7d36ed8/mlpack-black.svg" style="

- Jenkins - Coveralls + Azure DevOps builds (job) License NumFOCUS

@@ -142,10 +141,10 @@ If you are compiling Armadillo by hand, ensure that LAPACK and BLAS are enabled. ### 4. Building mlpack from source -This document discusses how to build mlpack from source. These build directions +This document discusses how to build mlpack from source. These build directions will work for any Linux-like shell environment (for example Ubuntu, macOS, -FreeBSD etc). However, mlpack is in the repositories of many Linux distributions -and so it may be easier to use the package manager for your system. For example, +FreeBSD etc). However, mlpack is in the repositories of many Linux distributions +and so it may be easier to use the package manager for your system. For example, on Ubuntu, you can install the mlpack library and command-line executables (e.g. mlpack_pca, mlpack_kmeans etc.) with the following command: @@ -183,7 +182,7 @@ sufficient. The next step is to run CMake to configure the project. Running CMake is the equivalent to running `./configure` with autotools. If you run CMake with no -options, it will configure the project to build with no debugging symbols and +options, it will configure the project to build with no debugging symbols and no profiling information: $ cmake ../ @@ -239,7 +238,7 @@ This will build all library components as well as 'mlpack_test'. $ make -If you do not want to build everything in the library, individual components +If you do not want to build everything in the library, individual components of the build can be specified: $ make mlpack_pca mlpack_knn mlpack_kfn @@ -252,7 +251,7 @@ and submit an issue. The mlpack developers will quickly help you figure it out: Alternately, mlpack help can be found in IRC at `#mlpack` on chat.freenode.net. If you wish to install mlpack to `/usr/local/include/mlpack/`, `/usr/local/lib/`, -and `/usr/local/bin/`, make sure you have root privileges (or write permissions +and `/usr/local/bin/`, make sure you have root privileges (or write permissions to those three directories), and simply type $ make install diff --git a/doc/guide/build.hpp b/doc/guide/build.hpp index 9996f5132d..9356f725cc 100644 --- a/doc/guide/build.hpp +++ b/doc/guide/build.hpp @@ -191,8 +191,6 @@ 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 diff --git a/doc/tutorials/linear_regression/linear_regression.txt b/doc/tutorials/linear_regression/linear_regression.txt index a0baec8ec3..751ff0c935 100644 --- a/doc/tutorials/linear_regression/linear_regression.txt +++ b/doc/tutorials/linear_regression/linear_regression.txt @@ -46,6 +46,7 @@ A list of all the sections this tutorial contains. - \ref cli_ex2_lrtut - \ref cli_ex3_lrtut - \ref cli_ex4_lrtut + - \ref cli_ex5_lrtut - \ref linreg_lrtut - \ref linreg_ex1_lrtut - \ref linreg_ex2_lrtut @@ -113,22 +114,23 @@ $ cat dataset.csv 4,4 $ cat lr.xml - - - - - - 2 - 1 - 2 - 1 - -3.97205464519563669e-16 - 1.00000000000000022e+00 - - 0.00000000000000000e+00 - 1 - - + + + + 0 + + 2 + 1 + 1 + 0 + 1 + + 0 + true + + + + @endcode As you can see, the function for this input is \f$f(y)=0+1x_1\f$. We can see @@ -141,36 +143,102 @@ dataset is one dimensional, and the last column has the \f$y\f$ values, or responses, for each row. You can specify these responses in a separate file if you want, using the \c --input_responses, or \c -r, option. -@subsection cli_ex2_lrtut Compute model and predict at the same time +@subsection cli_ex2_lrtut Train a multivariate linear regression model + +Multivariate linear regression means that the response variable is predicted by +more than just one input variable. In this example we will try to fit a +multivariate linear regression model to data that contains four variables, stored in +\c dataset_2.csv. @code -$ mlpack_linear_regression --training_file dataset.csv --test_file predict.csv \ +$ cat dataset_2.csv +0,0,0,0,14 +1,1,1,1,24 +2,1,0,2,27 +1,2,2,2,32 +-1,-3,0,2,17 +@endcode + +Now let's run \c mlpack_linear_regression as usual: + +@code +$ mlpack_linear_regression --training_file dataset_2.csv -v -M lr.xml +[INFO ] Loading 'dataset_2.csv' as CSV data. Size is 5 x 5. +[INFO ] +[INFO ] Execution parameters: +[INFO ] help: 0 +[INFO ] info: +[INFO ] input_model_file: +[INFO ] lambda: 0 +[INFO ] output_model_file: lr.xml +[INFO ] output_predictions_file: +[INFO ] test_file: +[INFO ] training_file: dataset_2.csv +[INFO ] training_responses_file: +[INFO ] verbose: 1 +[INFO ] version: 0 +[INFO ] Program timers: +[INFO ] load_regressors: 0.000060s +[INFO ] loading_data: 0.000050s +[INFO ] regression: 0.000049s +[INFO ] total_time: 0.000118s + +$ cat lr.xml + + + + 0 + + 5 + 1 + 1 + 14.00000000000002 + 1.9999999999999447 + 1.0000000000000431 + 2.9999999999999516 + 4.0000000000000249 + + 0 + true + + +@endcode + +If we take a look at the \c lr.xml output we can see the \c \ part has five elements which +the first corresponds to \f$\beta_0\f$ , the second corresponds to \f$\beta_1\f$ , and so on. This is equivalent +to \f$f(y) = \beta_0 + \beta_1x_1 + \beta_2x_2 + \beta_3x_3 + \beta_4x_4\f$ or \f$f(y)=14+2x_1+1x_2+3x_3+4x_4\f$. + +@subsection cli_ex3_lrtut Compute model and predict at the same time + +@code +$ mlpack_linear_regression --training_file dataset.csv --test_file predict.csv --output_predictions_file predictions.csv \ > -v +[WARN ] '--output_predictions_file (-o)' ignored because '--test_file (-T)' is specified! [INFO ] Loading 'dataset.csv' as CSV data. Size is 2 x 5. [INFO ] Loading 'predict.csv' as raw ASCII formatted data. Size is 1 x 3. [INFO ] Saving CSV data to 'predictions.csv'. -[INFO ] +[INFO ] [INFO ] Execution parameters: -[INFO ] help: false -[INFO ] info: "" -[INFO ] input_model_file: "" +[INFO ] help: 0 +[INFO ] info: +[INFO ] input_model_file: [INFO ] lambda: 0 -[INFO ] output_model_file: "" -[INFO ] output_predictions: predictions.csv -[INFO ] test_file: predict.csv -[INFO ] training_file: dataset.csv -[INFO ] training_responses: "" -[INFO ] verbose: true -[INFO ] version: false -[INFO ] +[INFO ] output_model_file: +[INFO ] output_predictions_file: 'predictions.csv' (1x3 matrix) +[INFO ] test_file: 'predict.csv' (0x0 matrix) +[INFO ] training_file: 'dataset.csv' (0x0 matrix) +[INFO ] training_responses_file: '' +[INFO ] verbose: 1 +[INFO ] version: 0 [INFO ] Program timers: -[INFO ] load_regressors: 0.000371s -[INFO ] load_test_points: 0.000229s -[INFO ] loading_data: 0.000491s -[INFO ] prediction: 0.000075s -[INFO ] regression: 0.000449s -[INFO ] saving_data: 0.000186s -[INFO ] total_time: 0.002731s +[INFO ] load_regressors: 0.000069s +[INFO ] load_test_points: 0.000031s +[INFO ] loading_data: 0.000079s +[INFO ] prediction: 0.000001s +[INFO ] regression: 0.000054s +[INFO ] saving_data: 0.000055s +[INFO ] total_time: 0.000203s + $ cat dataset.csv 0,0 @@ -195,51 +263,52 @@ about the \c predict.csv dataset is that it has the same dimensionality as the dataset used to create the model, one. If the model generating dataset has \f$d\f$ dimensions, so must the dataset we want to predict for. -@subsection cli_ex3_lrtut Prediction using a precomputed model +@subsection cli_ex4_lrtut Prediction using a precomputed model @code -$ mlpack_linear_regression --input_model_file lr.xml --test_file predict.csv -v +$ mlpack_linear_regression --input_model_file lr.xml --test_file predict.csv --output_predictions_file predictions.csv -v +[WARN ] '--output_predictions_file (-o)' ignored because '--test_file (-T)' is specified! [INFO ] Loading 'predict.csv' as raw ASCII formatted data. Size is 1 x 3. [INFO ] Saving CSV data to 'predictions.csv'. -[INFO ] +[INFO ] [INFO ] Execution parameters: -[INFO ] help: false -[INFO ] info: "" +[INFO ] help: 0 +[INFO ] info: [INFO ] input_model_file: lr.xml [INFO ] lambda: 0 -[INFO ] output_model_file: "" -[INFO ] output_predictions: predictions.csv -[INFO ] test_file: predict.csv -[INFO ] training_file: "" -[INFO ] training_responses: "" -[INFO ] verbose: true -[INFO ] version: false -[INFO ] +[INFO ] output_model_file: +[INFO ] output_predictions_file: 'predictions.csv' (1x3 matrix) +[INFO ] test_file: 'predict.csv' (0x0 matrix) +[INFO ] training_file: '' +[INFO ] training_responses_file: '' +[INFO ] verbose: 1 +[INFO ] version: 0 [INFO ] Program timers: -[INFO ] load_model: 0.000264s -[INFO ] load_test_points: 0.000186s -[INFO ] loading_data: 0.000157s -[INFO ] prediction: 0.000098s -[INFO ] saving_data: 0.000157s -[INFO ] total_time: 0.001688s +[INFO ] load_model: 0.000051s +[INFO ] load_test_points: 0.000052s +[INFO ] loading_data: 0.000044s +[INFO ] prediction: 0.000010s +[INFO ] saving_data: 0.000079s +[INFO ] total_time: 0.000160s + $ cat lr.xml - - - - - - 2 - 1 - 2 - 1 - -3.97205464519563669e-16 - 1.00000000000000022e+00 - - 0.00000000000000000e+00 - 1 - - + + + + 0 + + 2 + 1 + 1 + 0 + 1 + + 0 + true + + + $ cat predict.csv 2 @@ -252,7 +321,7 @@ $ cat predictions.csv 4.0000000000e+00 @endcode -@subsection cli_ex4_lrtut Using ridge regression +@subsection cli_ex5_lrtut Using ridge regression Sometimes, the input matrix of predictors has a covariance matrix that is not invertible, or the system is overdetermined. In this case, ridge regression is diff --git a/doc/tutorials/reinforcement_learning/reinforcement_learning.txt b/doc/tutorials/reinforcement_learning/reinforcement_learning.txt index d8fb6cec58..e53edd0c74 100644 --- a/doc/tutorials/reinforcement_learning/reinforcement_learning.txt +++ b/doc/tutorials/reinforcement_learning/reinforcement_learning.txt @@ -153,19 +153,22 @@ the output shape is represented by the number of possible actions, which in this (`foward` and `backward`). We can also use mlpack's ann module to setup a custom FFN network. For example, here we use a single -hidden layer. +hidden layer. However, the Q-Learning agent expects the object to have a `ResetNoise` method which `SimpleDQN` has. +We can't pass mlpack's FFN network directly. Instead, we have to wrap it into `SimpleDQN` object. @code int main() { // Set up the network. - FFN, GaussianInitialization> model(MeanSquaredError<>(), + FFN, GaussianInitialization> network(MeanSquaredError<>(), GaussianInitialization(0, 0.001)); - model.Add>(4, 128); - model.Add>(); - model.Add>(128, 128); - model.Add>(); - model.Add>(128, 2); + network.Add>(4, 128); + network.Add>(); + network.Add>(128, 128); + network.Add>(); + network.Add>(128, 2); + + SimpleDQN<> model(network); @endcode @@ -323,7 +326,7 @@ auto measure = [&returns, &position, &episode](double episodeReturn) std::cout << "Episode No.: " << episode << "; Episode Return: " << episodeReturn - << "; Average Return: " << arma::mean(returns) << endl; + << "; Average Return: " << arma::mean(returns) << std::endl; }; @endcode @@ -389,7 +392,7 @@ int main() std::cout << "Episode No.: " << episode << "; Episode Return: " << episodeReturn - << "; Average Return: " << arma::mean(returns) << endl; + << "; Average Return: " << arma::mean(returns) << std::endl; }; for (int i = 0; i < 100; i++) diff --git a/src/mlpack/bindings/R/CMakeLists.txt b/src/mlpack/bindings/R/CMakeLists.txt index b1c7daab0a..73ebe3cd1a 100644 --- a/src/mlpack/bindings/R/CMakeLists.txt +++ b/src/mlpack/bindings/R/CMakeLists.txt @@ -284,6 +284,11 @@ if (BUILD_R_BINDINGS) DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/mlpack/") + file(COPY + "${CMAKE_CURRENT_SOURCE_DIR}/mlpack/configure" + DESTINATION + "${CMAKE_CURRENT_BINARY_DIR}/mlpack/") + # Do the actual build. add_custom_target(r_build ALL) diff --git a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in index dc583f0dd9..1c64a50a72 100644 --- a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in +++ b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in @@ -20,6 +20,5 @@ Suggests: testthat (>= 2.1.0) URL: https://www.mlpack.org/doc/mlpack-@PACKAGE_VERSION@/r_documentation.html, https://github.com/mlpack/mlpack BugReports: https://github.com/mlpack/mlpack/issues -LazyData: true RoxygenNote: 7.1.0 Encoding: UTF-8 diff --git a/src/mlpack/bindings/R/mlpack/configure b/src/mlpack/bindings/R/mlpack/configure new file mode 100755 index 0000000000..608e27d17e --- /dev/null +++ b/src/mlpack/bindings/R/mlpack/configure @@ -0,0 +1,8 @@ +#!/bin/sh + +if test `uname` = "SunOS" ; +then +sed '1 s/$/ -ftrack-macro-expansion=0 -pipe --param ggc-min-expand=10 --param ggc-min-heapsize=8192/' ./src/Makevars > ./src/Makevars.tmp && cat ./src/Makevars.tmp > ./src/Makevars && rm ./src/Makevars.tmp +fi + +exit 0 diff --git a/src/mlpack/bindings/R/mlpack/src/Makevars.win b/src/mlpack/bindings/R/mlpack/src/Makevars.win index bb6a88cc84..12f71348e6 100644 --- a/src/mlpack/bindings/R/mlpack/src/Makevars.win +++ b/src/mlpack/bindings/R/mlpack/src/Makevars.win @@ -1,3 +1,3 @@ -PKG_CXXFLAGS = -DBOOST_MATH_PROMOTE_DOUBLE_POLICY=false -I. $(SHLIB_OPENMP_CXXFLAGS) +PKG_CXXFLAGS = -DBOOST_MATH_PROMOTE_DOUBLE_POLICY=false -I. $(SHLIB_OPENMP_CXXFLAGS) -ftrack-macro-expansion=0 -pipe --param ggc-min-expand=10 --param ggc-min-heapsize=8192 PKG_LIBS = $(SHLIB_OPENMP_CXXFLAGS) $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) CXX_STD = CXX11 diff --git a/src/mlpack/bindings/R/mlpack/src/rcpp_mlpack.h b/src/mlpack/bindings/R/mlpack/src/rcpp_mlpack.h index 8c285e2193..a66e7ed637 100644 --- a/src/mlpack/bindings/R/mlpack/src/rcpp_mlpack.h +++ b/src/mlpack/bindings/R/mlpack/src/rcpp_mlpack.h @@ -15,7 +15,7 @@ #include -// To suppress Found ‘__assert_fail’, possibly from ‘assert’ (C). +// To suppress Found '__assert_fail', possibly from 'assert' (C). #define BOOST_DISABLE_ASSERTS // Rcpp has its own stream object which cooperates more nicely with R's i/o diff --git a/src/mlpack/bindings/cli/print_type_doc_impl.hpp b/src/mlpack/bindings/cli/print_type_doc_impl.hpp index e6aa78d915..51e3106de5 100644 --- a/src/mlpack/bindings/cli/print_type_doc_impl.hpp +++ b/src/mlpack/bindings/cli/print_type_doc_impl.hpp @@ -98,8 +98,9 @@ std::string PrintTypeDoc( "of the data is detected by the extension of the filename. The storage" " should be such that one row corresponds to one point, and one column " "corresponds to one dimension (this is the typical storage format for " - "on-disk data). All values of the matrix will be loaded as double-" - "precision floating point data."; + "on-disk data). CSV files will be checked for a header; if no header " + "is found, the first row will be loaded as a data point. All values of" + " the matrix will be loaded as double-precision floating point data."; } else if (std::is_same>::value) { @@ -111,8 +112,10 @@ std::string PrintTypeDoc( "compiled with HDF5 support. The type of the data is detected by the " "extension of the filename. The storage should be such that one row " "corresponds to one point, and one column corresponds to one dimension " - "(this is the typical storage format for on-disk data). All values of " - "the matrix will be loaded as unsigned integers."; + "(this is the typical storage format for on-disk data). CSV files will" + " be checked for a header; if no header is found, the first row will be" + " loaded as a data point. All values of the matrix will be loaded as " + "unsigned integers."; } else if (std::is_same::value || std::is_same::value) diff --git a/src/mlpack/bindings/python/setup.py.in b/src/mlpack/bindings/python/setup.py.in index 4762f3194b..f049f0e002 100644 --- a/src/mlpack/bindings/python/setup.py.in +++ b/src/mlpack/bindings/python/setup.py.in @@ -52,18 +52,15 @@ if os.getenv('NO_BUILD') == '1': else: cxx_flags = '${CMAKE_CXX_FLAGS}'.strip() cxx_flags = re.sub(' +', ' ', cxx_flags) + extra_args = ['-DBINDING_TYPE=BINDING_TYPE_PYX', '-std=c++11'] + if '${OpenMP_CXX_FLAGS}' != '': + extra_args.append('${OpenMP_CXX_FLAGS}') if cxx_flags: - extra_args = ['-DBINDING_TYPE=BINDING_TYPE_PYX', - '-std=c++11', - '${OpenMP_CXX_FLAGS}'] + cxx_flags.split(' ') - else: - extra_args = ['-DBINDING_TYPE=BINDING_TYPE_PYX', - '-std=c++11', - '${OpenMP_CXX_FLAGS}'] + extra_args.extend(cxx_flags.split(' ')) # Extra options for MSVC compiler. if platform.system() == 'Windows': - extra_args = extra_args + ['/MD', '/O2', '/Ob2', '/DNDEBUG'] + extra_args.extend(['/MD', '/O2', '/Ob2', '/DNDEBUG']) # This is used for parallel builds; CMake will set PYX_TO_BUILD accordingly. if module is not None: diff --git a/src/mlpack/core.hpp b/src/mlpack/core.hpp index 34cf60dd09..d29a174724 100644 --- a/src/mlpack/core.hpp +++ b/src/mlpack/core.hpp @@ -1,4 +1,4 @@ -/** +/** * @file core.hpp * * Include all of the base components required to write mlpack methods, and the diff --git a/src/mlpack/core/data/detect_file_type.cpp b/src/mlpack/core/data/detect_file_type.cpp index 740ffabd93..c00954f1c0 100644 --- a/src/mlpack/core/data/detect_file_type.cpp +++ b/src/mlpack/core/data/detect_file_type.cpp @@ -47,6 +47,10 @@ std::string GetStringType(const arma::file_type& type) * from Armadillo's function guess_file_type_internal(), but we avoid using * internal Armadillo functionality. * + * If the file is detected as a CSV, and the CSV is detected to have a header + * row, the stream `f` will be fast-forwarded to point at the second line of the + * file. + * * @param f Opened istream to look into to guess the file type. */ arma::file_type GuessFileType(std::istream& f) @@ -114,6 +118,53 @@ arma::file_type GuessFileType(std::istream& f) } } + if (hasComma && (hasBracket == false)) + { + // If we believe we have a CSV file, then we want to try to skip any header + // row. We'll detect a header row by simply seeing if anything in the first + // line doesn't parse as a number. + // + // TODO: this is not a foolproof algorithm, so there should eventually be a + // way added for the user to explicitly indicate that there is or isn't a + // header. + std::string firstLine; + std::getline(f, firstLine); + + std::stringstream str(firstLine); + std::string token; + bool allNumeric = true; + // We'll abuse 'getline()' to split on commas. + while (std::getline(str, token, ',')) + { + // Let's see if we can parse the token into a number. + double num; + std::string rest; + + // Try to parse into a number. + std::stringstream s(token); + s >> num; + if (s.fail()) + { + allNumeric = false; + break; + } + + // Now check to see there isn't anything else. (This catches cases like, + // e.g., "1a".) + s >> rest; + if (rest.length() > 0) + { + allNumeric = false; + break; + } + } + + // If we could parse everything into a number, then let's rewind `f` so that + // it's at the start of the file. + if (allNumeric) + f.seekg(pos1); + } + delete[] dataMem; if (hasBinary) @@ -131,12 +182,14 @@ arma::file_type GuessFileType(std::istream& f) * necessary. (For instance, a .csv file could be delimited by spaces, commas, * or tabs.) This is meant to be used during loading. * + * If the file is detected as a CSV, and the CSV is detected to have a header + * row, `stream` will be fast-forwarded to point at the second line of the file. + * * @param stream Opened file stream to look into for autodetection. * @param filename Name of the file. * @return The detected file type. */ -arma::file_type AutoDetect(std::fstream& stream, - const std::string& filename) +arma::file_type AutoDetect(std::fstream& stream, const std::string& filename) { // Get the extension. std::string extension = Extension(filename); diff --git a/src/mlpack/core/data/detect_file_type.hpp b/src/mlpack/core/data/detect_file_type.hpp index ab387ad0ba..8856de29fe 100644 --- a/src/mlpack/core/data/detect_file_type.hpp +++ b/src/mlpack/core/data/detect_file_type.hpp @@ -30,6 +30,10 @@ std::string GetStringType(const arma::file_type& type); * from Armadillo's function guess_file_type_internal(), but we avoid using * internal Armadillo functionality. * + * If the file is detected as a CSV, and the CSV is detected to have a header + * row, the stream `f` will be fast-forwarded to point at the second line of the + * file. + * * @param f Opened istream to look into to guess the file type. */ arma::file_type GuessFileType(std::istream& f); @@ -40,6 +44,9 @@ arma::file_type GuessFileType(std::istream& f); * necessary. (For instance, a .csv file could be delimited by spaces, commas, * or tabs.) This is meant to be used during loading. * + * If the file is detected as a CSV, and the CSV is detected to have a header + * row, `stream` will be fast-forwarded to point at the second line of the file. + * * @param stream Opened file stream to look into for autodetection. * @param filename Name of the file. * @return The detected file type. arma::file_type_unknown if unknown. diff --git a/src/mlpack/core/data/load.hpp b/src/mlpack/core/data/load.hpp index d6681d5dce..9b54f43ce1 100644 --- a/src/mlpack/core/data/load.hpp +++ b/src/mlpack/core/data/load.hpp @@ -48,6 +48,10 @@ namespace data /** Functions to load and save matrices and models. */ { * `inputLoadType` parameter with the correct type above (e.g. * `arma::csv_ascii`.) * + * If the detected file type is CSV (`arma::csv_ascii`), the first row will be + * checked for a CSV header. If a CSV header is not detected, the first row + * will be treated as data; otherwise, the first row will be skipped. + * * If the parameter 'fatal' is set to true, a std::runtime_error exception will * be thrown if the matrix does not load successfully. The parameter * 'transpose' controls whether or not the matrix is transposed after loading. 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/dists/gaussian_distribution.hpp b/src/mlpack/core/dists/gaussian_distribution.hpp index d7d3800c06..2d47f7d686 100644 --- a/src/mlpack/core/dists/gaussian_distribution.hpp +++ b/src/mlpack/core/dists/gaussian_distribution.hpp @@ -90,11 +90,11 @@ class GaussianDistribution */ void Probability(const arma::mat& x, arma::vec& probabilities) const { - probabilities.set_size(x.n_cols); - for (size_t i = 0; i < x.n_cols; ++i) - { - probabilities(i) = Probability(x.unsafe_col(i)); - } + // Use LogProbability(), then transform the log-probabilities out of + // logspace. + arma::vec logProbs; + LogProbability(x, logProbs); + probabilities = arma::exp(logProbs); } /** @@ -110,17 +110,11 @@ class GaussianDistribution // Column i of 'diffs' is the difference between x.col(i) and the mean. arma::mat diffs = x; diffs.each_col() -= mean; - // Now, we only want to calculate the diagonal elements of (diffs' * cov^-1 - // * diffs). We just don't need any of the other elements. We can - // calculate the right hand part of the equation (instead of the left side) - // so that later we are referencing columns, not rows -- that is faster. - const arma::mat rhs = -0.5 * invCov * diffs; - arma::vec logExponents(diffs.n_cols); // We will now fill this. - for (size_t i = 0; i < diffs.n_cols; ++i) - logExponents(i) = accu(diffs.unsafe_col(i) % rhs.unsafe_col(i)); + // Now, we only want to calculate the diagonal elements of (diffs' * cov^-1 + // * diffs). We just don't need any of the other elements. logProbabilities = -0.5 * x.n_rows * log2pi - 0.5 * logDetCov + - logExponents; + sum(diffs % (-0.5 * invCov * diffs), 0).t(); } /** diff --git a/src/mlpack/core/math/log_add.hpp b/src/mlpack/core/math/log_add.hpp index 51497f8d0d..6613954f11 100644 --- a/src/mlpack/core/math/log_add.hpp +++ b/src/mlpack/core/math/log_add.hpp @@ -28,7 +28,7 @@ template T LogAdd(T x, T y); /** - * Sum a vector of log values. (T should be an Armadillo type.) + * Log-sum a vector of log values. (T should be an Armadillo type.) * * @param x vector of log values * @return log(e^x0 + e^x1 + ...) @@ -36,6 +36,38 @@ T LogAdd(T x, T y); template typename T::elem_type AccuLog(const T& x); +/** + * Compute the sum of exponentials of each element in each column, then compute + * the log of that. If InPlace is true, then the values of `y` will also be + * added to the sum. + * + * That is, if InPlace is false, then this method will set `y` such that: + * + * `y_i = log(sum(exp(x.col(i))))` + * + * and if InPlace is true, then `y` will be set such that: + * + * `y_i = log(sum(exp(x.col(i))) + exp(y_i))`. + */ +template +void LogSumExp(const T& x, arma::Col& y); + +/** + * Compute the sum of exponentials of each element in each row, then compute the + * log of that. If InPlace is true, then the values of `y` will also be added + * to the sum. + * + * That is, if InPlace is false, then this method will set `y` such that: + * + * `y_i = log(sum(exp(x.row(i))))` + * + * and if InPlace is true, then `y` will be set such that: + * + * `y_i = log(sum(exp(x.row(i))) + exp(y_i))`. + */ +template +void LogSumExpT(const T& x, arma::Col& y); + } // namespace math } // namespace mlpack diff --git a/src/mlpack/core/math/log_add_impl.hpp b/src/mlpack/core/math/log_add_impl.hpp index f86dce4cd9..6be66b91b9 100644 --- a/src/mlpack/core/math/log_add_impl.hpp +++ b/src/mlpack/core/math/log_add_impl.hpp @@ -47,8 +47,10 @@ T LogAdd(T x, T y) r = y; } - return (r == -std::numeric_limits::infinity() || - d == -std::numeric_limits::infinity()) ? r : r + log(1 + exp(d)); + if (std::isinf(d) || std::isinf(r)) + return r; + + return r + log(1 + exp(d)); } /** @@ -60,15 +62,77 @@ T LogAdd(T x, T y) template typename T::elem_type AccuLog(const T& x) { - typename T::elem_type sum = - -std::numeric_limits::infinity(); + typename T::elem_type maxVal = max(x); + if (maxVal == -std::numeric_limits::infinity()) + return maxVal; - for (size_t i = 0; i < x.n_elem; ++i) + return maxVal + log(sum(exp(x - maxVal)));; +} + +/** + * Compute the sum of exponentials of each element in each column, then compute + * the log of that. If InPlace is true, then the values of `y` will also be + * added to the sum. + */ +template +void LogSumExp(const T& x, arma::Col& y) +{ + arma::Col maxs; + + if (InPlace) { - sum = LogAdd(sum, x[i]); + // Compute the maximum in each column (treating y as a column too). + maxs = max(max(x, 1), y); + + y = maxs + log(sum(exp(x - repmat(maxs, 1, x.n_cols)), 1) + + exp(y - maxs)); + } + else + { + // Compute the maximum element in each column. + maxs = max(x, 1); + + y = maxs + log(sum(exp(x - repmat(maxs, 1, x.n_cols)), 1)); } - return sum; + if (maxs.has_inf()) + { + y.replace(-std::numeric_limits::quiet_NaN(), + -std::numeric_limits::infinity()); + } +} + +/** + * Compute the sum of exponentials of each element in each row, then compute the + * log of that. If InPlace is true, then the values of `y` will also be added + * to the sum. + */ +template +void LogSumExpT(const T& x, arma::Col& y) +{ + arma::Row maxs; + + if (InPlace) + { + // Compute the maximum element in each column. + maxs = max(max(x, 0), y.t()); + + y = maxs.t() + log(sum(exp(x - repmat(maxs, x.n_rows, 1)), 0) + + exp(y.t() - maxs)).t(); + } + else + { + // Compute the maximum element in each column. + arma::Row maxs = max(x, 0); + + y = (maxs + log(sum(exp(x - repmat(maxs, x.n_rows, 1)), 0))).t(); + } + + if (maxs.has_inf()) + { + y.replace(-std::numeric_limits::quiet_NaN(), + -std::numeric_limits::infinity()); + } } } // namespace math diff --git a/src/mlpack/core/math/random.hpp b/src/mlpack/core/math/random.hpp index 738702f4b0..5091a08ff3 100644 --- a/src/mlpack/core/math/random.hpp +++ b/src/mlpack/core/math/random.hpp @@ -42,7 +42,7 @@ inline void RandomSeed(const size_t seed) #if (!defined(BINDING_TYPE) || BINDING_TYPE != BINDING_TYPE_TEST) randGen.seed((uint32_t) seed); #if (BINDING_TYPE == BINDING_TYPE_R) - // To suppress Found ‘srand’, possibly from ‘srand’ (C). + // To suppress Found 'srand', possibly from 'srand' (C). (void) seed; #else srand((unsigned int) seed); diff --git a/src/mlpack/core/util/timers.hpp b/src/mlpack/core/util/timers.hpp index fc83b4984a..8316695377 100644 --- a/src/mlpack/core/util/timers.hpp +++ b/src/mlpack/core/util/timers.hpp @@ -129,23 +129,23 @@ class Timers void PrintTimer(const std::string& timerName); /** -  * Initializes a timer, available like a normal value specified on -  * the command line.  Timers are of type timeval. If a timer is started, then + * Initializes a timer, available like a normal value specified on + * the command line. Timers are of type timeval. If a timer is started, then * stopped, then re-started, then stopped, the final timer value will be the * length of both runs of the timer. -  * -  * @param timerName The name of the timer in question. + * + * @param timerName The name of the timer in question. * @param threadId Id of the thread accessing the timer. -  */ + */ void StartTimer(const std::string& timerName, const std::thread::id& threadId = std::thread::id()); /** -  * Halts the timer, and replaces its value with the delta time from its start. -  * -  * @param timerName The name of the timer in question. + * Halts the timer, and replaces its value with the delta time from its start. + * + * @param timerName The name of the timer in question. * @param threadId Id of the thread accessing the timer. -  */ + */ void StopTimer(const std::string& timerName, const std::thread::id& threadId = std::thread::id()); diff --git a/src/mlpack/core/util/to_lower.hpp b/src/mlpack/core/util/to_lower.hpp index f427449baf..0e420a45a2 100644 --- a/src/mlpack/core/util/to_lower.hpp +++ b/src/mlpack/core/util/to_lower.hpp @@ -16,10 +16,10 @@ namespace mlpack { namespace util { /** - * Convert a string to lowercase letters. - * - * @param input The string to convert. - */ + * Convert a string to lowercase letters. + * + * @param input The string to convert. + */ inline std::string ToLower(const std::string& input) { std::string output; diff --git a/src/mlpack/methods/amf/update_rules/nmf_mult_div.hpp b/src/mlpack/methods/amf/update_rules/nmf_mult_div.hpp index f744dd0b38..8a318a2ad1 100644 --- a/src/mlpack/methods/amf/update_rules/nmf_mult_div.hpp +++ b/src/mlpack/methods/amf/update_rules/nmf_mult_div.hpp @@ -31,7 +31,7 @@ namespace amf { * } * @endcode * - * This is a multiplicative rule that ensures that the Kullback–Leibler + * This is a multiplicative rule that ensures that the Kullback-Leibler * divergence * * \f[ diff --git a/src/mlpack/methods/ann/activation_functions/CMakeLists.txt b/src/mlpack/methods/ann/activation_functions/CMakeLists.txt index d5c0868c1c..1639817716 100644 --- a/src/mlpack/methods/ann/activation_functions/CMakeLists.txt +++ b/src/mlpack/methods/ann/activation_functions/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES poisson1_function.hpp gaussian_function.hpp hard_swish_function.hpp + tanh_exponential_function.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/ann/activation_functions/tanh_exponential_function.hpp b/src/mlpack/methods/ann/activation_functions/tanh_exponential_function.hpp new file mode 100644 index 0000000000..cabe427a88 --- /dev/null +++ b/src/mlpack/methods/ann/activation_functions/tanh_exponential_function.hpp @@ -0,0 +1,96 @@ +/** + * @file methods/ann/activation_functions/tanh_exponential_function.hpp + * @author Mayank Raj + * + * Definition and implementation of the Tanh exponential function. + * + * For more information see the following paper + * + * @code + * @misc{The Institution of Engineering and Technology 2015 , + * title = {TanhExp: A Smooth Activation Function with High Convergence Speed for Lightweight Neural Networks}, + * author = {Xinyu Liu and Xiaoguang Di}, + * year = {2020}, + * url = {https://arxiv.org/pdf/2003.09855v2.pdf}, + * eprint = {2003.09855v2}, + * archivePrefix = {arXiv}, + * primaryClass = {cs.LG} } + * @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_TANH_EXPONENTIAL_FUNCTION_HPP +#define MLPACK_METHODS_ANN_ACTIVATION_FUNCTIONS_TANH_EXPONENTIAL_FUNCTION_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * The TanhExp function, defined by + * + * @f{eqnarray*}{ + * f(x) = x * tanh(e^x)\\ + * f'(x) = tanh(e^x) - x*e^x*(tanh(e^x)^2 - 1)\\ + * @f} + */ + class TanhExpFunction +{ + public: + /** + * Computes the TanhExp function. + * + * @param x Input data. + * @return f(x). + */ + static double Fn(const double x) + { + return x * std::tanh(std::exp(x)); + } + + /** + * Computes the TanhExp function. + * + * @param x Input data. + * @param y The resulting output activation. + */ + template + static void Fn(const InputVecType& x, OutputVecType& y) + { + y = x % arma::tanh(arma::exp(x)); + } + + /** + * Computes the first derivative of the TanhExp function. + * + * @param y Input activation. + * @return f'(x) + */ + static double Deriv(const double y) + { + return std::tanh(std::exp(y)) - y * std::exp(y) * + (std::pow(std::tanh(std::exp(y)), 2) - 1); + } + + /** + * Computes the first derivatives of the tanh function. + * + * @param y Input activations. + * @param x The resulting derivatives. + */ + template + static void Deriv(const InputVecType& y, OutputVecType& x) + { + x = arma::tanh(arma::exp(y)) - y % arma::exp(y) % + (arma::pow(arma::tanh(arma::exp(y)), 2) - 1); + } +}; // class TanhExpFunction + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/base_layer.hpp b/src/mlpack/methods/ann/layer/base_layer.hpp index ae49f30fe6..7169d5f474 100644 --- a/src/mlpack/methods/ann/layer/base_layer.hpp +++ b/src/mlpack/methods/ann/layer/base_layer.hpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -52,6 +53,7 @@ namespace ann /** Artificial Neural Network. */ { * - ElliotLayer * - GaussianLayer * - HardSwishLayer + * - TanhExpLayer * * @tparam ActivationFunction Activation function used for the embedding layer. * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, @@ -290,6 +292,17 @@ template < using HardSwishFunctionLayer = BaseLayer< ActivationFunction, InputDataType, OutputDataType>; +/** + * Standard TanhExp-Layer using the TanhExp activation function. + */ +template < + class ActivationFunction = TanhExpFunction, + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +using TanhExpFunctionLayer = 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/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 input.n_rows - 1) + rowEnd = input.n_rows - 1; + if (colEnd > input.n_cols - 1) + colEnd = input.n_cols - 1; + arma::mat subInput = input( - arma::span(rowidx, rowidx + kernelWidth - 1 - offset), - arma::span(colidx, colidx + kernelHeight - 1 - offset)); + arma::span(rowidx, rowEnd), + arma::span(colidx, colEnd)); output(i, j) = arma::mean(arma::mean(subInput)); } @@ -186,22 +194,36 @@ class MeanPooling 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 j = 0, colidx = 0; j < input.n_cols; j += strideHeight, colidx++) { - 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)); + for (size_t i = 0, rowidx = 0; i < input.n_rows; i += strideWidth, rowidx++) + { + size_t rowEnd = i + kernelWidth - 1; + size_t colEnd = j + kernelHeight - 1; - unpooledError = arma::Mat(inputArea.n_rows, inputArea.n_cols); - unpooledError.fill(error(i / rStep, j / cStep) / inputArea.n_elem); + if (rowEnd > input.n_rows - 1) + { + if (floor) + continue; + rowEnd = input.n_rows - 1; + } - output(arma::span(i, i + rStep - 1 - offset), - arma::span(j, j + cStep - 1 - offset)) += unpooledError; + if (colEnd > input.n_cols - 1) + { + if (floor) + continue; + colEnd = input.n_cols - 1; + } + + arma::mat InputArea = input(arma::span(i, rowEnd), arma::span(j, colEnd)); + + unpooledError = arma::Mat(InputArea.n_rows, InputArea.n_cols); + unpooledError.fill(error(rowidx, colidx) / InputArea.n_elem); + + output(arma::span(i, i + InputArea.n_rows - 1), + arma::span(j, j + InputArea.n_cols - 1)) += unpooledError; } } } @@ -245,9 +267,6 @@ class MeanPooling //! If true use maximum a posteriori during the forward pass. bool deterministic; - //! Locally-stored stored rounding offset. - size_t offset; - //! Locally-stored number of input units. size_t batchSize; diff --git a/src/mlpack/methods/ann/layer/mean_pooling_impl.hpp b/src/mlpack/methods/ann/layer/mean_pooling_impl.hpp index ce4e6c7895..ad4a8e6943 100644 --- a/src/mlpack/methods/ann/layer/mean_pooling_impl.hpp +++ b/src/mlpack/methods/ann/layer/mean_pooling_impl.hpp @@ -45,7 +45,6 @@ MeanPooling::MeanPooling( outputHeight(0), reset(false), deterministic(false), - offset(0), batchSize(0) { // Nothing to do here. @@ -67,8 +66,6 @@ void MeanPooling::Forward( (double) kernelWidth) / (double) strideWidth + 1); outputHeight = std::floor((inputHeight - (double) kernelHeight) / (double) strideHeight + 1); - - offset = 0; } else { @@ -76,8 +73,6 @@ void MeanPooling::Forward( (double) kernelWidth) / (double) strideWidth + 1); outputHeight = std::ceil((inputHeight - (double) kernelHeight) / (double) strideHeight + 1); - - offset = 1; } outputTemp = arma::zeros >(outputWidth, outputHeight, 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/bayesian_linear_regression/bayesian_linear_regression.cpp b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.cpp index 10a5f92bd5..c163d0d148 100644 --- a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.cpp +++ b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.cpp @@ -18,12 +18,12 @@ using namespace mlpack::regression; BayesianLinearRegression::BayesianLinearRegression(const bool centerData, const bool scaleData, - const size_t nIterMax, - const double tol) : + const size_t maxIterations, + const double tolerance) : centerData(centerData), scaleData(scaleData), - nIterMax(nIterMax), - tol(tol), + maxIterations(maxIterations), + tolerance(tolerance), responsesOffset(0.0), alpha(0.0), beta(0.0), @@ -60,7 +60,7 @@ double BayesianLinearRegression::Train(const arma::mat& data, unsigned short i = 0; double deltaAlpha = 1.0, deltaBeta = 1.0, crit = 1.0; - while ((crit > tol) && (i < nIterMax)) + while ((crit > tolerance) && (i < maxIterations)) { deltaAlpha = -alpha; deltaBeta = -beta; diff --git a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp index a15ce1ced1..417e795bff 100644 --- a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp +++ b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression.hpp @@ -21,30 +21,30 @@ namespace mlpack { namespace regression { /** - * A Bayesian approach to the maximum likelihood estimation of the parameters - * \f$ \omega \f$ of the linear regression model. The Complexity is governed by - * the addition of a gaussian isotropic prior of precision \f$ \alpha \f$ over - * \f$ \omega \f$: + * A Bayesian approach to the maximum likelihood estimation of the parameters + * \f$ \omega \f$ of the linear regression model. The Complexity is governed by + * the addition of a gaussian isotropic prior of precision \f$ \alpha \f$ over + * \f$ \omega \f$: * * \f[ * p(\omega|\alpha) = \mathcal{N}(\omega|0, \alpha^{-1}I) * \f] - * - * The optimization procedure calculates the posterior distribution of - * \f$ \omega \f$ knowing the data by maximizing an approximation of the log - * marginal likelihood derived from a type II maximum likelihood approximation. + * + * The optimization procedure calculates the posterior distribution of + * \f$ \omega \f$ knowing the data by maximizing an approximation of the log + * marginal likelihood derived from a type II maximum likelihood approximation. * The determination of \f$ alpha \f$ and of the noise precision \f$ beta \f$ - * is part of the optimization process, leading to an automatic determination of - * w. The model being entirely based on probabilty distributions, uncertainties + * is part of the optimization process, leading to an automatic determination of + * w. The model being entirely based on probabilty distributions, uncertainties * are available and easly computed for both the parameters and the predictions. * - * The advantage over linear regression and ridge regression is that the + * The advantage over linear regression and ridge regression is that the * regularization is determined from all the training data alone without any - * require to an hold out method. + * require to an hold out method. * - * The code below is an implementation of the maximization of the evidence + * The code below is an implementation of the maximization of the evidence * function described in the section 3.5.2 of the C.Bishop book, Pattern - * Recognition and Machine Learning. + * Recognition and Machine Learning. * * @code * @article{MacKay91bayesianinterpolation, @@ -60,36 +60,37 @@ namespace regression { * @code * @book{Bishop:2006:PRM:1162264, * author = {Bishop, Christopher M.}, - * title = {Pattern Recognition and Machine Learning (Information Science + * title = {Pattern Recognition and Machine Learning (Information Science * and Statistics)}, * chapter = {3} * year = {2006}, * isbn = {0387310738}, * publisher = {Springer-Verlag}, * address = {Berlin, Heidelberg}, - * } + * } * @endcode - * + * * Example of use: * * @code * arma::mat xTrain; // Train data matrix. Column-major. * arma::rowvec yTrain; // Train target values. - + * * // Train the model. Regularization strength is optimally tunned with the * // training data alone by applying the Train method. - * BayesianLinearRegression estimator(); // Instanciate the estimator with default option. + * // Instantiate the estimator with default option. + * BayesianLinearRegression estimator; * estimator.Train(xTrain, yTrain); - + * * // Prediction on test points. * arma::mat xTest; // Test data matrix. Column-major. * arma::rowvec predictions; - + * * estimator.Predict(xTest, prediction); - + * * arma::rowvec yTest; // Test target values. * estimator.RMSE(xTest, yTest); // Evaluate using the RMSE score. - + * * // Compute the standard deviations of the predictions. * arma::rowvec stds; * estimator.Predict(xTest, responses, stds) @@ -107,19 +108,20 @@ class BayesianLinearRegression * examples. * @param scaleData Whether or not scale the data according to the * standard deviation of each feature. - * @param nIterMax Maximum number of iterations for convergency. - * @param tol Level from which the solution is considered sufficientlly - * stable. + * @param maxIterations Maximum number of iterations for convergency. + * @param tolerance Level from which the solution is considered sufficientlly + * stable. */ BayesianLinearRegression(const bool centerData = true, const bool scaleData = false, - const size_t nIterMax = 50, - const double tol = 1e-4); + const size_t maxIterations = 50, + const double tolerance = 1e-4); /** - * Run BayesianLinearRegression. The input matrix (like all mlpack matrices) should be - * column-major -- each column is an observation and each row is a dimension. - * + * Run BayesianLinearRegression. The input matrix (like all mlpack matrices) + * should be column-major -- each column is an observation and each row is a + * dimension. + * * @param data Column-major input data, dim(P, N). * @param responses A vector of targets, dim(N). * @return Root mean squared error. @@ -139,12 +141,13 @@ class BayesianLinearRegression arma::rowvec& predictions) const; /** - * Predict \f$y_{i}\f$ and the standard deviation of the predictive posterior + * Predict \f$y_{i}\f$ and the standard deviation of the predictive posterior * distribution for each data point in the given data matrix, using the * currently-trained Bayesian Ridge estimator. * * @param points The data point to apply the model. - * @param predictions Vector which will contain calculated values on completion. + * @param predictions Vector which will contain calculated values on + * completion. * @param std Standard deviations of the predictions. */ void Predict(const arma::mat& points, @@ -163,7 +166,7 @@ class BayesianLinearRegression const arma::rowvec& responses) const; /** - * Get the solution vector. + * Get the solution vector. * * @return omega Solution vector. */ @@ -187,7 +190,7 @@ class BayesianLinearRegression /** * Get the estimated variance. Train() must be called before. - * + * * @return 1.0 / \f$ \beta \f$ */ double Variance() const { return 1.0 / Beta(); } @@ -200,7 +203,7 @@ class BayesianLinearRegression const arma::colvec& DataOffset() const { return dataOffset; } /** - * Get the vector of standard deviations computed on the features over the + * Get the vector of standard deviations computed on the features over the * training points. * * @return dataOffset @@ -214,9 +217,31 @@ class BayesianLinearRegression */ double ResponsesOffset() const { return responsesOffset; } + //! Get whether the data will be centered during training. + bool CenterData() const { return centerData; } + //! Modify whether the data will be centered during training. + bool& CenterData() { return centerData; } + + //! Get whether the data will be scaled by standard deviations during + //! training. + bool ScaleData() const { return scaleData; } + //! Modify whether the data will be scaled by standard deviations during + //! training. + bool& ScaleData() { return scaleData; } + + //! Get the maximum number of iterations for training. + size_t MaxIterations() const { return maxIterations; } + //! Modify the maximum number of iterations for training. + size_t& MaxIterations() { return maxIterations; } + + //! Get the tolerance for training to converge. + double Tolerance() const { return tolerance; } + //! Modify the tolerance for training to converge. + double& Tolerance() { return tolerance; } + /** * Serialize the BayesianLinearRegression model. - **/ + */ template void serialize(Archive& ar, const uint32_t version); @@ -227,11 +252,11 @@ class BayesianLinearRegression //! Scale the data by standard deviations if true. bool scaleData; - //! Maximum number of iterations for convergency. - size_t nIterMax; + //! Maximum number of iterations for convergence. + size_t maxIterations; //! Level from which the solution is considered sufficientlly stable. - double tol; + double tolerance; //! Mean vector computed over the points. arma::colvec dataOffset; @@ -251,7 +276,7 @@ class BayesianLinearRegression //! Effective number of parameters. double gamma; - //! Solution vector + //! Solution vector. arma::colvec omega; //! Covariance matrix of the solution vector omega. diff --git a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp index d0881dd06a..65a4d70998 100644 --- a/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp +++ b/src/mlpack/methods/bayesian_linear_regression/bayesian_linear_regression_impl.hpp @@ -26,8 +26,8 @@ void BayesianLinearRegression::serialize(Archive& ar, { ar(CEREAL_NVP(centerData)); ar(CEREAL_NVP(scaleData)); - ar(CEREAL_NVP(nIterMax)); - ar(CEREAL_NVP(tol)); + ar(CEREAL_NVP(maxIterations)); + ar(CEREAL_NVP(tolerance)); ar(CEREAL_NVP(dataOffset)); ar(CEREAL_NVP(dataScale)); ar(CEREAL_NVP(responsesOffset)); diff --git a/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp b/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp index ff2377f312..101d7cd477 100644 --- a/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp +++ b/src/mlpack/methods/block_krylov_svd/randomized_block_krylov_svd.cpp @@ -83,7 +83,7 @@ void RandomizedBlockKrylovSVD::Apply(const arma::mat& data, arma::qr_econ(Q, R, K); - // Approximate eigenvalues and eigenvectors using Rayleigh–Ritz method. + // Approximate eigenvalues and eigenvectors using Rayleigh-Ritz method. arma::svd_econ(u, s, v, Q.t() * data); // Do economical singular value decomposition and compute only the diff --git a/src/mlpack/methods/decision_tree/all_categorical_split.hpp b/src/mlpack/methods/decision_tree/all_categorical_split.hpp index 2a9de942a9..faa8f16c6b 100644 --- a/src/mlpack/methods/decision_tree/all_categorical_split.hpp +++ b/src/mlpack/methods/decision_tree/all_categorical_split.hpp @@ -29,7 +29,6 @@ class AllCategoricalSplit { public: // No extra info needed for split. - template class AuxiliarySplitInfo { }; /** @@ -64,8 +63,8 @@ class AllCategoricalSplit const WeightVecType& weights, const size_t minimumLeafSize, const double minimumGainSplit, - arma::Col& classProbabilities, - AuxiliarySplitInfo& aux); + arma::vec& classProbabilities, + AuxiliarySplitInfo& aux); /** * Return the number of children in the split. @@ -73,9 +72,8 @@ class AllCategoricalSplit * @param classProbabilities Auxiliary information for the split. * @param * (aux) Auxiliary information for the split (Unused). */ - template - static size_t NumChildren(const arma::Col& classProbabilities, - const AuxiliarySplitInfo& /* aux */); + static size_t NumChildren(const arma::vec& classProbabilities, + const AuxiliarySplitInfo& /* aux */); /** * Calculate the direction a point should percolate to. @@ -87,8 +85,8 @@ class AllCategoricalSplit template static size_t CalculateDirection( const ElemType& point, - const arma::Col& classProbabilities, - const AuxiliarySplitInfo& /* aux */); + const arma::vec& classProbabilities, + const AuxiliarySplitInfo& /* aux */); }; } // namespace tree diff --git a/src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp b/src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp index 12ac592ad4..00135625ab 100644 --- a/src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp +++ b/src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp @@ -26,8 +26,8 @@ double AllCategoricalSplit::SplitIfBetter( const WeightVecType& weights, const size_t minimumLeafSize, const double minimumGainSplit, - arma::Col& classProbabilities, - AuxiliarySplitInfo& /* aux */) + arma::vec& classProbabilities, + AuxiliarySplitInfo& /* aux */) { // Count the number of elements in each potential child. const double epsilon = 1e-7; // Tolerance for floating-point errors. @@ -110,20 +110,19 @@ double AllCategoricalSplit::SplitIfBetter( } template -template size_t AllCategoricalSplit::NumChildren( - const arma::Col& classProbabilities, - const AuxiliarySplitInfo& /* aux */) + const arma::vec& classProbabilities, + const AuxiliarySplitInfo& /* aux */) { - return classProbabilities[0]; + return size_t(classProbabilities[0]); } template template size_t AllCategoricalSplit::CalculateDirection( const ElemType& point, - const arma::Col& /* classProbabilities */, - const AuxiliarySplitInfo& /* aux */) + const arma::vec& /* classProbabilities */, + const AuxiliarySplitInfo& /* aux */) { return (size_t) point; } diff --git a/src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp b/src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp index 976b810c63..ab081c84fc 100644 --- a/src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp +++ b/src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp @@ -28,7 +28,6 @@ class BestBinaryNumericSplit { public: // No extra info needed for split. - template class AuxiliarySplitInfo { }; /** @@ -37,6 +36,10 @@ class BestBinaryNumericSplit * return the value 'bestGain'. If a split is made, then classProbabilities * and aux may be modified. * + * It's not necessary that `ElemType` is the same as the type of the data in + * `VecType`---if they are different, casting will be done to store the + * auxiliary information. + * * @param bestGain Best gain seen so far (we'll only split if we find gain * better than this). * @param data The dimension of data points to check for a split in. @@ -60,15 +63,14 @@ class BestBinaryNumericSplit const WeightVecType& weights, const size_t minimumLeafSize, const double minimumGainSplit, - arma::Col& classProbabilities, - AuxiliarySplitInfo& aux); + arma::vec& classProbabilities, + AuxiliarySplitInfo& aux); /** * Returns 2, since the binary split always has two children. */ - template - static size_t NumChildren(const arma::Col& /* classProbabilities */, - const AuxiliarySplitInfo& /* aux */) + static size_t NumChildren(const arma::vec& /* classProbabilities */, + const AuxiliarySplitInfo& /* aux */) { return 2; } @@ -83,8 +85,8 @@ class BestBinaryNumericSplit template static size_t CalculateDirection( const ElemType& point, - const arma::Col& classProbabilities, - const AuxiliarySplitInfo& /* aux */); + const arma::vec& classProbabilities, + const AuxiliarySplitInfo& /* aux */); }; } // namespace tree diff --git a/src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp b/src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp index f5b38220ca..14bd0e3fb9 100644 --- a/src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp +++ b/src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp @@ -25,8 +25,8 @@ double BestBinaryNumericSplit::SplitIfBetter( const WeightVecType& weights, const size_t minimumLeafSize, const double minimumGainSplit, - arma::Col& classProbabilities, - AuxiliarySplitInfo& /* aux */) + arma::vec& classProbabilities, + AuxiliarySplitInfo& /* aux */) { // First sanity check: if we don't have enough points, we can't split. if (data.n_elem < (minimumLeafSize * 2)) @@ -189,8 +189,8 @@ template template size_t BestBinaryNumericSplit::CalculateDirection( const ElemType& point, - const arma::Col& classProbabilities, - const AuxiliarySplitInfo& /* aux */) + const arma::vec& classProbabilities, + const AuxiliarySplitInfo& /* aux */) { if (point <= classProbabilities[0]) return 0; // Go left. diff --git a/src/mlpack/methods/decision_tree/decision_tree.hpp b/src/mlpack/methods/decision_tree/decision_tree.hpp index 9ab8599aec..fee81909e3 100644 --- a/src/mlpack/methods/decision_tree/decision_tree.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree.hpp @@ -30,18 +30,20 @@ namespace tree { * * The class inherits from the auxiliary split information in order to prevent * an empty auxiliary split information struct from taking any extra size. + * + * Note that `ElemType` is a template parameter controlling the type that is + * used to store split information. In general, you would want to set this to + * be the same as the type of the data that you will be using, but it's not + * required to do that. */ template class NumericSplitType = BestBinaryNumericSplit, template class CategoricalSplitType = AllCategoricalSplit, typename DimensionSelectionType = AllDimensionSelect, - typename ElemType = double, bool NoRecursion = false> class DecisionTree : - public NumericSplitType::template - AuxiliarySplitInfo, - public CategoricalSplitType::template - AuxiliarySplitInfo + public NumericSplitType::AuxiliarySplitInfo, + public CategoricalSplitType::AuxiliarySplitInfo { public: //! Allow access to the numeric split type. @@ -500,9 +502,9 @@ class DecisionTree : //! Note that this class will also hold the members of the NumericSplit and //! CategoricalSplit AuxiliarySplitInfo classes, since it inherits from them. //! We'll define some convenience typedefs here. - typedef typename NumericSplit::template AuxiliarySplitInfo + typedef typename NumericSplit::AuxiliarySplitInfo NumericAuxiliarySplitInfo; - typedef typename CategoricalSplit::template AuxiliarySplitInfo + typedef typename CategoricalSplit::AuxiliarySplitInfo CategoricalAuxiliarySplitInfo; /** @@ -578,13 +580,11 @@ class DecisionTree : template class NumericSplitType = BestBinaryNumericSplit, template class CategoricalSplitType = AllCategoricalSplit, - typename DimensionSelectType = AllDimensionSelect, - typename ElemType = double> + typename DimensionSelectType = AllDimensionSelect> using DecisionStump = DecisionTree; /** @@ -595,7 +595,6 @@ typedef DecisionTree ID3DecisionStump; } // namespace tree } // namespace mlpack diff --git a/src/mlpack/methods/decision_tree/decision_tree_impl.hpp b/src/mlpack/methods/decision_tree/decision_tree_impl.hpp index b99075f5b2..e4cd77851b 100644 --- a/src/mlpack/methods/decision_tree/decision_tree_impl.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree_impl.hpp @@ -22,14 +22,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template DecisionTree::DecisionTree( MatType data, const data::DatasetInfo& datasetInfo, @@ -62,14 +60,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template DecisionTree::DecisionTree( MatType data, LabelsType labels, @@ -100,14 +96,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template DecisionTree::DecisionTree( MatType data, const data::DatasetInfo& datasetInfo, @@ -144,14 +138,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template DecisionTree::DecisionTree( const DecisionTree& other, MatType data, @@ -185,14 +177,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template DecisionTree::DecisionTree( MatType data, LabelsType labels, @@ -229,14 +219,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template DecisionTree::DecisionTree( const DecisionTree& other, MatType data, @@ -275,13 +263,11 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> DecisionTree::DecisionTree(const size_t numClasses) : splitDimension(0), dimensionTypeOrMajorityClass(0), @@ -296,13 +282,11 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> DecisionTree::DecisionTree(const DecisionTree& other) : NumericAuxiliarySplitInfo(other), CategoricalAuxiliarySplitInfo(other), @@ -320,13 +304,11 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> DecisionTree::DecisionTree(DecisionTree&& other) : NumericAuxiliarySplitInfo(std::move(other)), CategoricalAuxiliarySplitInfo(std::move(other)), @@ -344,19 +326,16 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> DecisionTree& DecisionTree::operator=(const DecisionTree& other) { if (this == &other) @@ -388,19 +367,16 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> DecisionTree& DecisionTree::operator=(DecisionTree&& other) { if (this == &other) @@ -432,13 +408,11 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> DecisionTree::~DecisionTree() { for (size_t i = 0; i < children.size(); ++i) @@ -450,14 +424,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template double DecisionTree::Train( MatType data, const data::DatasetInfo& datasetInfo, @@ -493,14 +465,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template double DecisionTree::Train( MatType data, LabelsType labels, @@ -535,14 +505,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template double DecisionTree::Train( MatType data, const data::DatasetInfo& datasetInfo, @@ -584,14 +552,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template double DecisionTree::Train( MatType data, LabelsType labels, @@ -632,14 +598,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template double DecisionTree::Train( MatType& data, const size_t begin, @@ -818,14 +782,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template double DecisionTree::Train( MatType& data, const size_t begin, @@ -976,14 +938,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template size_t DecisionTree::Classify(const VecType& point) const { if (children.size() == 0) @@ -1000,14 +960,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template void DecisionTree::Classify(const VecType& point, size_t& prediction, arma::vec& probabilities) const @@ -1028,14 +986,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template void DecisionTree::Classify(const MatType& data, arma::Row& predictions) const { @@ -1056,14 +1012,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template void DecisionTree::Classify(const MatType& data, arma::Row& predictions, arma::mat& probabilities) const @@ -1095,14 +1049,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template void DecisionTree::serialize(Archive& ar, const uint32_t /* version */) { @@ -1126,14 +1078,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template size_t DecisionTree::CalculateDirection(const VecType& point) const { if ((data::Datatype) dimensionTypeOrMajorityClass == @@ -1150,13 +1100,11 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> size_t DecisionTree::NumClasses() const { // Recurse to the nearest child and return the number of elements in the @@ -1171,14 +1119,12 @@ template class NumericSplitType, template class CategoricalSplitType, typename DimensionSelectionType, - typename ElemType, bool NoRecursion> template void DecisionTree::CalculateClassProbabilities( const RowType& labels, const size_t numClasses, diff --git a/src/mlpack/methods/decision_tree/information_gain.hpp b/src/mlpack/methods/decision_tree/information_gain.hpp index 6126a41918..cc48600fa6 100644 --- a/src/mlpack/methods/decision_tree/information_gain.hpp +++ b/src/mlpack/methods/decision_tree/information_gain.hpp @@ -26,7 +26,7 @@ class InformationGain { public: /** - * Evaluate the Gini impurity given a vector of class weight counts. + * Evaluate the information gain given a vector of class weight counts. */ template static double EvaluatePtr(const CountType* counts, diff --git a/src/mlpack/methods/gmm/diagonal_gmm.cpp b/src/mlpack/methods/gmm/diagonal_gmm.cpp index e50d2ca7ea..ab7c30e1ac 100644 --- a/src/mlpack/methods/gmm/diagonal_gmm.cpp +++ b/src/mlpack/methods/gmm/diagonal_gmm.cpp @@ -67,6 +67,37 @@ double DiagonalGMM::LogProbability(const arma::vec& observation) const return sum; } +/** + * Return the log probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute log-probabilty. + * @param logProbs Stores the value of log-probability for input. + */ +void DiagonalGMM::LogProbability(const arma::mat& observation, + arma::vec& logProbs) const +{ + // Sum the probability for each Gaussian in our mixture (and we have to + // multiply by the prior for each Gaussian too). + logProbs.set_size(observation.n_cols); + + // Store log-probability value in a matrix. + arma::mat logProb(observation.n_cols, gaussians); + + // Assign value to the matrix. + for (size_t i = 0; i < gaussians; i++) + { + arma::vec temp(logProb.colptr(i), observation.n_cols, false, true); + dists[i].LogProbability(observation, temp); + } + + // Save log(weights) as a vector. + arma::vec logWeights = arma::log(weights); + + // Compute log-probability. + logProb += repmat(logWeights.t(), logProb.n_rows, 1); + math::LogSumExp(logProb, logProbs); +} + /** * Return the probability of the given observation being from this GMM. */ @@ -75,6 +106,20 @@ double DiagonalGMM::Probability(const arma::vec& observation) const return exp(LogProbability(observation)); } +/** + * Return the probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute probabilty. + * @param probs Stores the value of probability for observation. + */ +void DiagonalGMM::Probability(const arma::mat& observation, + arma::vec& probs) const +{ + LogProbability(observation, probs); + probs = exp(probs); +} + + /** * Return the log probability of the given observation being from the given * component in the mixture. diff --git a/src/mlpack/methods/gmm/diagonal_gmm.hpp b/src/mlpack/methods/gmm/diagonal_gmm.hpp index c935e4ac83..ef8fe9445a 100644 --- a/src/mlpack/methods/gmm/diagonal_gmm.hpp +++ b/src/mlpack/methods/gmm/diagonal_gmm.hpp @@ -166,14 +166,30 @@ class DiagonalGMM */ double Probability(const arma::vec& observation) const; + /** + * Return the probability that the given observation matrix. + * + * @param observation Observation to evaluate the probability of. + * @param probs Stores the value of probability for observation. + */ + void Probability(const arma::mat& observation, arma::vec& probs) const; + /** * Return the log probability that the given observation came from this * distribution. * - * @param observation Observation to evaluate the probability of. + * @param observation Observation to evaluate the log-probability of. */ double LogProbability(const arma::vec& observation) const; + /** + * Return the log probability that the given observation matrix. + * + * @param observation Observation to evaluate the log-probability of. + * @param logProbs Stores the value of log-probability for observation. + */ + void LogProbability(const arma::mat& observation, arma::vec& logProbs) const; + /** * Return the probability that the given observation came from the given * Gaussian component in this distribution. diff --git a/src/mlpack/methods/gmm/gmm.cpp b/src/mlpack/methods/gmm/gmm.cpp index 83b8beeecf..42603d45c7 100644 --- a/src/mlpack/methods/gmm/gmm.cpp +++ b/src/mlpack/methods/gmm/gmm.cpp @@ -53,6 +53,8 @@ GMM& GMM::operator=(const GMM& other) /** * Return the log probability of the given observation being from this GMM. + * + * @param observation Observation vector to compute log-probabilty. */ double GMM::LogProbability(const arma::vec& observation) const { @@ -66,17 +68,67 @@ double GMM::LogProbability(const arma::vec& observation) const return sum; } +/** + * Return the log probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute log-probabilty. + * @param logProbs Stores the value of log-probability for Observation. + */ +void GMM::LogProbability(const arma::mat& observation, + arma::vec& logProbs) const +{ + // Sum the probability for each Gaussian in our mixture (and we have to + // multiply by the prior for each Gaussian too). + logProbs.set_size(observation.n_cols); + + // Store log-probability value in a matrix. + arma::mat logProb(observation.n_cols, gaussians); + + // Assign value to the matrix. + for (size_t i = 0; i < gaussians; i++) + { + arma::vec temp(logProb.colptr(i), observation.n_cols, false, true); + dists[i].LogProbability(observation, temp); + } + + // Save log(weights) as a vector. + arma::vec logWeights = arma::log(weights); + + // Compute log-probability. + logProb += repmat(logWeights.t(), logProb.n_rows, 1); + math::LogSumExp(logProb, logProbs); +} + /** * Return the probability of the given observation being from this GMM. + * + * @param observation Observation vector to compute probabilty. */ double GMM::Probability(const arma::vec& observation) const { return exp(LogProbability(observation)); } +/** + * Return the probability of the given observation GMM matrix. + * + * @param observation Observation matrix to compute probabilty. + * @param probs Stores the value of probability for x. + */ +void GMM::Probability(const arma::mat& observation, + arma::vec& probs) const +{ + LogProbability(observation, probs); + probs = exp(probs); +} + + /** * Return the log probability of the given observation being from the given * component in the mixture. + * + * @param observation Observation vector to compute log-probabilty. + * @param component Calculate the log-probability for given observation vector. */ double GMM::LogProbability(const arma::vec& observation, const size_t component) const @@ -89,6 +141,9 @@ double GMM::LogProbability(const arma::vec& observation, /** * Return the probability of the given observation being from the given * component in the mixture. + * + * @param observation Observation matrix to compute probabilty. + * @param component Calculate the probability for given component. */ double GMM::Probability(const arma::vec& observation, const size_t component) const @@ -129,6 +184,9 @@ arma::vec GMM::Random() const /** * Classify the given observations as being from an individual component in this * GMM. + * + * @param observation Observation matrix for classification. + * @param labels Save the labels for the given observation matrix. */ void GMM::Classify(const arma::mat& observations, arma::Row& labels) const @@ -158,6 +216,10 @@ void GMM::Classify(const arma::mat& observations, /** * Get the log-likelihood of this data's fit to the model. + * + * @param data Data matrix to compute log-likelihood. + * @parma distsL Vector of Gaussian distribution. + * @param weightsL Vector of weights for computing likelihoods. */ double GMM::LogLikelihood( const arma::mat& data, diff --git a/src/mlpack/methods/gmm/gmm.hpp b/src/mlpack/methods/gmm/gmm.hpp index 5800e53260..b8436ce995 100644 --- a/src/mlpack/methods/gmm/gmm.hpp +++ b/src/mlpack/methods/gmm/gmm.hpp @@ -160,18 +160,34 @@ class GMM * Return the probability that the given observation came from this * distribution. * - * @param observation Observation to evaluate the probability of. + * @param observation Observation vector to evaluate the probability of. */ double Probability(const arma::vec& observation) const; + /** + * Return the probability of the given observation matrix. + * + * @param observation Observation matrix. + * @param probs Vector to store probability value of observation x. + */ + void Probability(const arma::mat& observation, arma::vec& probs) const; + /** * Return the log probability that the given observation came from this * distribution. * - * @param observation Observation to evaluate the probability of. + * @param observation Observation vector to evaluate the probability of. */ double LogProbability(const arma::vec& observation) const; + /** + * Return the log-probability of the given observation (x) matrix. + * + * @param observation Observation matrix. + * @param logProbs Vector to store log-probability value of observation. + */ + void LogProbability(const arma::mat& observation, arma::vec& logProbs) const; + /** * Return the probability that the given observation came from the given * Gaussian component in this distribution. diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index 1821083a0e..164a1390f4 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -316,8 +316,8 @@ class HMM * * @param emissionLogProb emission probability at time t. * @param logLikelihood Log-likelihood of the given sequence of emission - * probability up to time t-1. This will be overwritten with the log-likelihood - * of the given emission probability up to time t. + * probability up to time t-1. This will be overwritten with the + * log-likelihood of the given emission probability up to time t. * @param forwardLogProb Vector in which forward probabilities will be saved. * Passing forwardLogProb as an empty vector indicates the start of the * sequence (i.e. time t=0). @@ -446,9 +446,8 @@ class HMM * @param logScales Vector in which the log of scaling factors will be saved. * @return Forward probabilities */ - arma::vec ForwardAtT0( - const arma::vec& emissionLogProb, - double& logScales) const; + arma::vec ForwardAtT0(const arma::vec& emissionLogProb, + double& logScales) const; /** * Given emission probabilities, computes forward probabilities for time t>0. @@ -458,10 +457,9 @@ class HMM * @param prevForwardLogProb Previous forward probabilities. * @return Forward probabilities */ - arma::vec ForwardAtTn( - const arma::vec& emissionLogProb, - double& logScales, - const arma::vec& prevForwardLogProb) const; + arma::vec ForwardAtTn(const arma::vec& emissionLogProb, + double& logScales, + const arma::vec& prevForwardLogProb) const; // Helper functions. /** @@ -476,7 +474,8 @@ class HMM */ void Forward(const arma::mat& dataSeq, arma::vec& logScales, - arma::mat& forwardLogProb) const; + arma::mat& forwardLogProb, + arma::mat& logProbs) const; /** * The Backward algorithm (part of the Forward-Backward algorithm). Computes @@ -491,7 +490,8 @@ class HMM */ void Backward(const arma::mat& dataSeq, const arma::vec& logScales, - arma::mat& backwardLogProb) const; + arma::mat& backwardLogProb, + arma::mat& logProbs) const; //! Set of emission probability distributions; one for each state. std::vector emission; diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 09ac8113fe..05b4de2e3f 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -149,8 +149,19 @@ double HMM::Train(const std::vector& dataSeq) backwardLog, logScales); // Add to estimate of initial probability for state j. - for (size_t j = 0; j < logTransition.n_cols; ++j) - newLogInitial[j] = math::LogAdd(newLogInitial[j], stateLogProb(j, 0)); + math::LogSumExp(stateLogProb.unsafe_col(0), + newLogInitial); + + // Define a variable to store the value of log-probability for data. + arma::mat logProbs(dataSeq[seq].n_cols, logTransition.n_rows); + // Save the values of log-probability to logProbs. + for (size_t i = 0; i < logTransition.n_rows; i++) + { + // Define alias of desired column. + arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true); + // Use advanced constructor for using logProbs directly. + emission[i].LogProbability(dataSeq[seq], alias); + } // Now re-estimate the parameters. This is the M-step. // pi_i = sum_d ((1 / P(seq[d])) sum_t (f(i, 0) b(i, 0)) @@ -160,25 +171,31 @@ double HMM::Train(const std::vector& dataSeq) // We store the new estimates in a different matrix. for (size_t t = 0; t < dataSeq[seq].n_cols; ++t) { - for (size_t j = 0; j < logTransition.n_cols; ++j) + // Assemble temporary vector that's used in log-sum computation. + if (t < dataSeq[seq].n_cols - 1) { - if (t < dataSeq[seq].n_cols - 1) - { - // Estimate of T_ij (probability of transition from state j to state - // i). We postpone multiplication of the old T_ij until later. - for (size_t i = 0; i < logTransition.n_rows; i++) - { - newLogTransition(i, j) = math::LogAdd(newLogTransition(i, j), - forwardLog(j, t) + backwardLog(i, t + 1) + - emission[i].LogProbability(dataSeq[seq].unsafe_col(t + 1)) - - logScales[t + 1]); - } - } + // This term is the same across all states, so compute it once and + // cache it. + const arma::vec tmp = backwardLog.col(t + 1) + + logProbs.row(t + 1).t() - logScales[t + 1]; + arma::vec output; + math::LogSumExp(tmp, output); - // Add to list of emission observations, for Distribution::Train(). - emissionList.col(sumTime) = dataSeq[seq].col(t); - emissionProb[j][sumTime] = exp(stateLogProb(j, t)); + for (size_t j = 0; j < logTransition.n_cols; ++j) + { + // Compute the estimate of T_ij (probability of transition from + // state j to state i). We postpone multiplication of the old T_ij + // until later. + arma::vec tmp2 = output + forwardLog(j, t); + arma::vec alias = newLogTransition.unsafe_col(j); + math::LogSumExp(tmp2, alias); + } } + + // Add to list of emission observations, for Distribution::Train(). + for (size_t j = 0; j < logTransition.n_cols; ++j) + emissionProb[j][sumTime] = exp(stateLogProb(j, t)); + emissionList.col(sumTime) = dataSeq[seq].col(t); sumTime++; } } @@ -337,9 +354,20 @@ double HMM::LogEstimate(const arma::mat& dataSeq, arma::mat& backwardLogProb, arma::vec& logScales) const { + arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows); + + // Save the values of log-probability to logProbs. + for (size_t i = 0; i < logTransition.n_rows; i++) + { + // Define alias of desired column. + arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true); + // Use advanced constructor for using logProbs directly. + emission[i].LogProbability(dataSeq, alias); + } + // First run the forward-backward algorithm. - Forward(dataSeq, logScales, forwardLogProb); - Backward(dataSeq, logScales, backwardLogProb); + Forward(dataSeq, logScales, forwardLogProb, logProbs); + Backward(dataSeq, logScales, backwardLogProb, logProbs); // Now assemble the state probability matrix based on the forward and backward // probabilities. @@ -481,6 +509,19 @@ double HMM::Predict(const arma::mat& dataSeq, // Store the best first state. arma::uword index; + + // Define a variable to store the value of log-probability for dataSeq. + arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows); + + // Save the values of log-probability to logProbs. + for (size_t i = 0; i < logTransition.n_rows; i++) + { + // Define alias of desired column. + arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true); + // Use advanced constructor for using logProbs directly. + emission[i].LogProbability(dataSeq, alias); + } + for (size_t t = 1; t < dataSeq.n_cols; t++) { // Assemble the state probability for this element. @@ -489,8 +530,7 @@ double HMM::Predict(const arma::mat& dataSeq, for (size_t j = 0; j < logTransition.n_rows; j++) { arma::vec prob = logStateProb.col(t - 1) + logTransition.row(j).t(); - logStateProb(j, t) = prob.max(index) + - emission[j].LogProbability(dataSeq.unsafe_col(t)); + logStateProb(j, t) = prob.max(index) + logProbs(t, j); stateSeqBack(j, t) = index; } } @@ -516,7 +556,19 @@ double HMM::LogLikelihood(const arma::mat& dataSeq) const arma::mat forwardLog; arma::vec logScales; - Forward(dataSeq, logScales, forwardLog); + // This is needed here. + arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows); + + // Save the values of log-probability to logProbs. + for (size_t i = 0; i < logTransition.n_rows; i++) + { + // Define alias of desired column. + arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true); + // Use advanced constructor for using logProbs directly. + emission[i].LogProbability(dataSeq, alias); + } + + Forward(dataSeq, logScales, forwardLog, logProbs); // The log-likelihood is the log of the scales for each time step. return accu(logScales); @@ -557,8 +609,7 @@ double HMM::EmissionLogLikelihood( arma::vec& forwardLogProb) const { bool isStartOfSeq = forwardLogProb.empty(); - double curLogScale = EmissionLogScaleFactor(emissionLogProb, - forwardLogProb); + double curLogScale = EmissionLogScaleFactor(emissionLogProb, forwardLogProb); logLikelihood = isStartOfSeq ? curLogScale : curLogScale + logLikelihood; return logLikelihood; } @@ -607,7 +658,19 @@ void HMM::Filter(const arma::mat& dataSeq, // First run the forward algorithm. arma::mat forwardLogProb; arma::vec logScales; - Forward(dataSeq, logScales, forwardLogProb); + // This is needed here. + arma::mat logProbs(dataSeq.n_cols, logTransition.n_rows); + + // Save the values of log-probability to logProbs. + for (size_t i = 0; i < logTransition.n_rows; i++) + { + // Define alias of desired column. + arma::vec alias(logProbs.colptr(i), logProbs.n_rows, false, true); + // Use advanced constructor for using logProbs directly. + emission[i].LogProbability(dataSeq, alias); + } + + Forward(dataSeq, logScales, forwardLogProb, logProbs); // Propagate state ahead. if (ahead != 0) @@ -655,14 +718,12 @@ arma::vec HMM::ForwardAtT0(const arma::vec& emissionLogProb, // P(X_k | o_{1:k}) for all possible states X_k, for each time point k. ConvertToLogSpace(); - arma::vec forwardLogProb(logTransition.n_rows); - forwardLogProb.fill(-std::numeric_limits::infinity()); // The first entry in the forward algorithm uses the initial state // probabilities. Note that MATLAB assumes that the starting state (at // t = -1) is state 0; this is not our assumption here. To force that // behavior, you could append a single starting state to every single data // sequence and that should produce results in line with MATLAB. - forwardLogProb = logInitial + emissionLogProb; + arma::vec forwardLogProb = logInitial + emissionLogProb; // Normalize probability. logScales = math::AccuLog(forwardLogProb); @@ -677,22 +738,23 @@ arma::vec HMM::ForwardAtT0(const arma::vec& emissionLogProb, */ template arma::vec HMM::ForwardAtTn(const arma::vec& emissionLogProb, - double& logScales, - const arma::vec& prevForwardLogProb) const + double& logScales, + const arma::vec& prevForwardLogProb) + const { // Our goal is to calculate the forward probabilities: // P(X_k | o_{1:k}) for all possible states X_k, for each time point k. - arma::vec forwardLogProb(logTransition.n_rows); - forwardLogProb.fill(-std::numeric_limits::infinity()); - // Now compute the probabilities for each successive observation. - for (size_t state = 0; state < logTransition.n_rows; state++) { - // The forward probability of state j at time t is the sum over all - // states of the probability of the previous state transitioning to - // the current state and emitting the given observation. - arma::vec tmp = prevForwardLogProb + logTransition.row(state).t(); - forwardLogProb(state) = math::AccuLog(tmp) + emissionLogProb(state); - } + // The forward probability of state j at time t is the sum over all states of + // the probability of the previous state transitioning to the current state + // and emitting the given observation. To do this computation in log-space, + // we can use LogSumExp(). + arma::vec forwardLogProb; + arma::mat tmp = logTransition + repmat(prevForwardLogProb.t(), + logTransition.n_rows, 1); + math::LogSumExp(tmp, forwardLogProb); + forwardLogProb += emissionLogProb; + // Normalize probability. logScales = math::AccuLog(forwardLogProb); if (std::isfinite(logScales)) @@ -707,7 +769,8 @@ arma::vec HMM::ForwardAtTn(const arma::vec& emissionLogProb, template void HMM::Forward(const arma::mat& dataSeq, arma::vec& logScales, - arma::mat& forwardLogProb) const + arma::mat& forwardLogProb, + arma::mat& logProbs) const { // Our goal is to calculate the forward probabilities: // P(X_k | o_{1:k}) for all possible states X_k, for each time point k. @@ -722,33 +785,21 @@ void HMM::Forward(const arma::mat& dataSeq, // behavior, you could append a single starting state to every single data // sequence and that should produce results in line with MATLAB. - arma::vec emissionLogProb(logTransition.n_rows); - for (size_t state = 0; state < logTransition.n_rows; state++) - { - emissionLogProb(state) = - emission[state].LogProbability(dataSeq.unsafe_col(0)); - } - - forwardLogProb.col(0) = ForwardAtT0(emissionLogProb, logScales(0)); + forwardLogProb.col(0) = ForwardAtT0(logProbs.row(0).t(), logScales(0)); // Now compute the probabilities for each successive observation. for (size_t t = 1; t < dataSeq.n_cols; t++) { - for (size_t state = 0; state < logTransition.n_rows; state++) - { - emissionLogProb(state) = - emission[state].LogProbability(dataSeq.unsafe_col(t)); - } - - forwardLogProb.col(t) = - ForwardAtTn(emissionLogProb, logScales(t), forwardLogProb.col(t-1)); + forwardLogProb.col(t) = ForwardAtTn(logProbs.row(t).t(), logScales(t), + forwardLogProb.col(t - 1)); } } template void HMM::Backward(const arma::mat& dataSeq, const arma::vec& logScales, - arma::mat& backwardLogProb) const + arma::mat& backwardLogProb, + arma::mat& logProbs) const { // Our goal is to calculate the backward probabilities: // P(X_k | o_{k + 1:T}) for all possible states X_k, for each time point k. @@ -761,23 +812,20 @@ void HMM::Backward(const arma::mat& dataSeq, // Now step backwards through all other observations. for (size_t t = dataSeq.n_cols - 2; t + 1 > 0; t--) { - for (size_t j = 0; j < logTransition.n_rows; j++) - { - // The backward probability of state j at time t is the sum over all state - // of the probability of the next state having been a transition from the - // current state multiplied by the probability of each of those states - // emitting the given observation. - for (size_t state = 0; state < logTransition.n_rows; state++) - { - backwardLogProb(j, t) = math::LogAdd(backwardLogProb(j, t), - logTransition(state, j) + backwardLogProb(state, t + 1) - + emission[state].LogProbability(dataSeq.unsafe_col(t + 1))); - } + // The backward probability of state j at time t is the sum over all + // states of the probability of the next state having been a transition + // from the current state multiplied by the probability of each of those + // states emitting the given observation. To compute this in log-space, we + // can use LogSumExpT(). + const arma::mat tmp = logTransition + + repmat(backwardLogProb.col(t + 1), 1, logTransition.n_cols) + + repmat(logProbs.row(t + 1).t(), 1, logTransition.n_cols); + arma::vec alias = backwardLogProb.unsafe_col(t); + math::LogSumExpT(tmp, alias); - // Normalize by the weights from the forward algorithm. - if (std::isfinite(logScales[t + 1])) - backwardLogProb(j, t) -= logScales[t + 1]; - } + // Normalize by the weights from the forward algorithm. + if (std::isfinite(logScales[t + 1])) + backwardLogProb.col(t) -= logScales[t + 1]; } } diff --git a/src/mlpack/methods/random_forest/random_forest.hpp b/src/mlpack/methods/random_forest/random_forest.hpp index b297f8a5ef..3790f1a725 100644 --- a/src/mlpack/methods/random_forest/random_forest.hpp +++ b/src/mlpack/methods/random_forest/random_forest.hpp @@ -19,23 +19,39 @@ namespace mlpack { namespace tree { +/** + * The RandomForest class provides an implementation of random forests, + * described in Breiman's seminal paper: + * + * @code + * @article{breiman2001random, + * title={Random forests}, + * author={Breiman, Leo}, + * journal={Machine Learning}, + * volume={45}, + * number={1}, + * pages={5--32}, + * year={2001}, + * publisher={Springer} + * } + * @endcode + */ template class NumericSplitType = BestBinaryNumericSplit, - template class CategoricalSplitType = AllCategoricalSplit, - typename ElemType = double> + template class CategoricalSplitType = AllCategoricalSplit> class RandomForest { public: //! Allow access to the underlying decision tree type. typedef DecisionTree DecisionTreeType; + DimensionSelectionType> DecisionTreeType; /** * Construct the random forest without any training or specifying the number * of trees. Predict() will throw an exception until Train() is called. */ - RandomForest() { } + RandomForest(); /** * Create a random forest, training on the given labeled training data with @@ -167,6 +183,8 @@ class RandomForest * @param minimumLeafSize Minimum number of points in each tree's leaf nodes. * @param minimumGainSplit Minimum gain for splitting a decision tree node. * @param maximumDepth Maximum depth for the tree. + * @param warmStart When set to `true`, it adds `numTrees` new trees to the + * existing random forest otherwise a new forest is trained from scratch. * @param dimensionSelector Instantiated dimension selection policy. * @return The average entropy of all the decision trees trained under forest. */ @@ -178,6 +196,7 @@ class RandomForest const size_t minimumLeafSize = 1, const double minimumGainSplit = 1e-7, const size_t maximumDepth = 0, + const bool warmStart = false, DimensionSelectionType dimensionSelector = DimensionSelectionType()); @@ -198,6 +217,8 @@ class RandomForest * @param minimumLeafSize Minimum number of points in each tree's leaf nodes. * @param minimumGainSplit Minimum gain for splitting a decision tree node. * @param maximumDepth Maximum depth for the tree. + * @param warmStart When set to `true`, it adds `numTrees` new trees to the + * existing random forest else a new forest is trained from scratch. * @param dimensionSelector Instantiated dimension selection policy. * @return The average entropy of all the decision trees trained under forest. */ @@ -210,6 +231,7 @@ class RandomForest const size_t minimumLeafSize = 1, const double minimumGainSplit = 1e-7, const size_t maximumDepth = 0, + const bool warmStart = false, DimensionSelectionType dimensionSelector = DimensionSelectionType()); @@ -228,6 +250,8 @@ class RandomForest * @param minimumLeafSize Minimum number of points in each tree's leaf nodes. * @param minimumGainSplit Minimum gain for splitting a decision tree node. * @param maximumDepth Maximum depth for the tree. + * @param warmStart When set to `true`, it adds `numTrees` new trees to the + * existing random forest else a new forest is trained from scratch. * @param dimensionSelector Instantiated dimension selection policy. * @return The average entropy of all the decision trees trained under forest. */ @@ -240,6 +264,7 @@ class RandomForest const size_t minimumLeafSize = 1, const double minimumGainSplit = 1e-7, const size_t maximumDepth = 0, + const bool warmStart = false, DimensionSelectionType dimensionSelector = DimensionSelectionType()); @@ -260,6 +285,8 @@ class RandomForest * @param minimumLeafSize Minimum number of points in each tree's leaf nodes. * @param minimumGainSplit Minimum gain for splitting a decision tree node. * @param maximumDepth Maximum depth for the tree. + * @param warmStart When set to `true`, it adds `numTrees` new trees to the + * existing random forest else a new forest is trained from scratch. * @param dimensionSelector Instantiated dimension selection policy. * @return The average entropy of all the decision trees trained under forest. */ @@ -273,6 +300,7 @@ class RandomForest const size_t minimumLeafSize = 1, const double minimumGainSplit = 1e-7, const size_t maximumDepth = 0, + const bool warmStart = false, DimensionSelectionType dimensionSelector = DimensionSelectionType()); @@ -354,6 +382,8 @@ class RandomForest * @param minimumGainSplit Minimum gain for splitting a decision tree node. * @param maximumDepth Maximum depth for the tree. * @param dimensionSelector Instantiated dimension selection policy. + * @param warmStart When set to `true`, it fits new trees and add them to the + * previous forest else a new forest is trained from scratch. * @tparam UseWeights Whether or not to use the weights parameter. * @tparam UseDatasetInfo Whether or not to use the datasetInfo parameter. * @tparam MatType The type of data matrix (i.e. arma::mat). @@ -369,10 +399,14 @@ class RandomForest const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, - DimensionSelectionType& dimensionSelector); + DimensionSelectionType& dimensionSelector, + const bool warmStart = false); //! The trees in the forest. std::vector trees; + + //! The average gain of the forest. + double avgGain; }; } // namespace tree diff --git a/src/mlpack/methods/random_forest/random_forest_impl.hpp b/src/mlpack/methods/random_forest/random_forest_impl.hpp index 124199ee8a..32152d2c91 100644 --- a/src/mlpack/methods/random_forest/random_forest_impl.hpp +++ b/src/mlpack/methods/random_forest/random_forest_impl.hpp @@ -22,16 +22,31 @@ template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType +> +RandomForest< + FitnessFunction, + DimensionSelectionType, + NumericSplitType, + CategoricalSplitType +>::RandomForest() : + avgGain(0.0) +{ + // Nothing to do here. +} + +template< + typename FitnessFunction, + typename DimensionSelectionType, + template class NumericSplitType, + template class CategoricalSplitType > template RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::RandomForest(const MatType& dataset, const arma::Row& labels, const size_t numClasses, @@ -39,29 +54,29 @@ RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, - DimensionSelectionType dimensionSelector) + DimensionSelectionType dimensionSelector) : + avgGain(0.0) { // Pass off work to the Train() method. data::DatasetInfo info; // Ignored. arma::rowvec weights; // Fake weights, not used. Train(dataset, info, labels, numClasses, weights, numTrees, - minimumLeafSize, minimumGainSplit, maximumDepth, dimensionSelector); + minimumLeafSize, minimumGainSplit, maximumDepth, dimensionSelector, + false); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::RandomForest(const MatType& dataset, const data::DatasetInfo& datasetInfo, const arma::Row& labels, @@ -70,29 +85,28 @@ RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, - DimensionSelectionType dimensionSelector) + DimensionSelectionType dimensionSelector): + avgGain(0.0) { // Pass off work to the Train() method. arma::rowvec weights; // Fake weights, not used. Train(dataset, datasetInfo, labels, numClasses, weights, numTrees, minimumLeafSize, minimumGainSplit, maximumDepth, - dimensionSelector); + dimensionSelector, false); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::RandomForest(const MatType& dataset, const arma::Row& labels, const size_t numClasses, @@ -101,28 +115,28 @@ RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, - DimensionSelectionType dimensionSelector) + DimensionSelectionType dimensionSelector) : + avgGain(0.0) { // Pass off work to the Train() method. data::DatasetInfo info; // Ignored by Train(). Train(dataset, info, labels, numClasses, weights, numTrees, - minimumLeafSize, minimumGainSplit, maximumDepth, dimensionSelector); + minimumLeafSize, minimumGainSplit, maximumDepth, dimensionSelector, + false); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::RandomForest(const MatType& dataset, const data::DatasetInfo& datasetInfo, const arma::Row& labels, @@ -132,27 +146,27 @@ RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, - DimensionSelectionType dimensionSelector) + DimensionSelectionType dimensionSelector) : + avgGain(0.0) { // Pass off work to the Train() method. - Train(dataset, datasetInfo, labels, numClasses, weights, numTrees, - minimumLeafSize, minimumGainSplit, maximumDepth, dimensionSelector); + Train(dataset, datasetInfo, labels, numClasses, weights, + numTrees, minimumLeafSize, minimumGainSplit, maximumDepth, + dimensionSelector, false); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template double RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Train(const MatType& dataset, const arma::Row& labels, const size_t numClasses, @@ -160,30 +174,29 @@ double RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, + const bool warmStart, DimensionSelectionType dimensionSelector) { // Pass off to Train(). - data::DatasetInfo info; // Ignored by Train(). + data::DatasetInfo datasetInfo; // Ignored by Train(). arma::rowvec weights; // Ignored by Train(). - return Train(dataset, info, labels, numClasses, weights, + return Train(dataset, datasetInfo, labels, numClasses, weights, numTrees, minimumLeafSize, minimumGainSplit, maximumDepth, - dimensionSelector); + dimensionSelector, warmStart); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template double RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Train(const MatType& dataset, const data::DatasetInfo& datasetInfo, const arma::Row& labels, @@ -192,29 +205,28 @@ double RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, + const bool warmStart, DimensionSelectionType dimensionSelector) { // Pass off to Train(). arma::rowvec weights; // Ignored by Train(). return Train(dataset, datasetInfo, labels, numClasses, weights, numTrees, minimumLeafSize, minimumGainSplit, maximumDepth, - dimensionSelector); + dimensionSelector, warmStart); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template double RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Train(const MatType& dataset, const arma::Row& labels, const size_t numClasses, @@ -223,29 +235,28 @@ double RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, + const bool warmStart, DimensionSelectionType dimensionSelector) { // Pass off to Train(). - data::DatasetInfo info; // Ignored by Train(). - return Train(dataset, info, labels, numClasses, weights, + data::DatasetInfo datasetInfo; // Ignored by Train(). + return Train(dataset, datasetInfo, labels, numClasses, weights, numTrees, minimumLeafSize, minimumGainSplit, maximumDepth, - dimensionSelector); + dimensionSelector, warmStart); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template double RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Train(const MatType& dataset, const data::DatasetInfo& datasetInfo, const arma::Row& labels, @@ -255,28 +266,27 @@ double RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, + const bool warmStart, DimensionSelectionType dimensionSelector) { // Pass off to Train(). return Train(dataset, datasetInfo, labels, numClasses, weights, numTrees, minimumLeafSize, minimumGainSplit, maximumDepth, - dimensionSelector); + dimensionSelector, warmStart); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template size_t RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Classify(const VecType& point) const { // Pass off to another Classify() overload. @@ -291,16 +301,14 @@ template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template void RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Classify(const VecType& point, size_t& prediction, arma::vec& probabilities) const @@ -338,16 +346,14 @@ template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template void RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Classify(const MatType& data, arma::Row& predictions) const { @@ -373,16 +379,14 @@ template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template void RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Classify(const MatType& data, arma::Row& predictions, arma::mat& probabilities) const @@ -411,16 +415,14 @@ template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template void RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::serialize(Archive& ar, const uint32_t /* version */) { size_t numTrees; @@ -436,22 +438,21 @@ void RandomForest< trees.resize(numTrees); ar(CEREAL_NVP(trees)); + ar(CEREAL_NVP(avgGain)); } template< typename FitnessFunction, typename DimensionSelectionType, template class NumericSplitType, - template class CategoricalSplitType, - typename ElemType + template class CategoricalSplitType > template double RandomForest< FitnessFunction, DimensionSelectionType, NumericSplitType, - CategoricalSplitType, - ElemType + CategoricalSplitType >::Train(const MatType& dataset, const data::DatasetInfo& datasetInfo, const arma::Row& labels, @@ -461,13 +462,20 @@ double RandomForest< const size_t minimumLeafSize, const double minimumGainSplit, const size_t maximumDepth, - DimensionSelectionType& dimensionSelector) + DimensionSelectionType& dimensionSelector, + const bool warmStart) { - // Train each tree individually. - trees.resize(numTrees); // This will fill the vector with untrained trees. - double avgGain = 0.0; + // Reset the forest if we are not doing a warm-start. + if (!warmStart) + trees.clear(); + const size_t oldNumTrees = trees.size(); + trees.resize(trees.size() + numTrees); - #pragma omp parallel for reduction( + : avgGain) + // Convert avgGain to total gain. + double totalGain = avgGain * oldNumTrees; + + // Train each tree individually. + #pragma omp parallel for reduction( + : totalGain) for (omp_size_t i = 0; i < numTrees; ++i) { Timer::Start("bootstrap"); @@ -478,40 +486,44 @@ double RandomForest< bootstrapLabels, bootstrapWeights); Timer::Stop("bootstrap"); - // Now build the decision tree. Timer::Start("train_tree"); if (UseWeights) { if (UseDatasetInfo) { - avgGain += trees[i].Train(bootstrapDataset, datasetInfo, - bootstrapLabels, numClasses, bootstrapWeights, minimumLeafSize, - minimumGainSplit, maximumDepth, dimensionSelector); + totalGain += trees[oldNumTrees + i].Train(bootstrapDataset, + datasetInfo, bootstrapLabels, numClasses, bootstrapWeights, + minimumLeafSize, minimumGainSplit, maximumDepth, + dimensionSelector); } else { - avgGain += trees[i].Train(bootstrapDataset, bootstrapLabels, numClasses, - bootstrapWeights, minimumLeafSize, minimumGainSplit, maximumDepth, - dimensionSelector); + totalGain += trees[oldNumTrees + i].Train(bootstrapDataset, + bootstrapLabels, numClasses, bootstrapWeights, minimumLeafSize, + minimumGainSplit, maximumDepth, dimensionSelector); } } else { if (UseDatasetInfo) { - avgGain += trees[i].Train(bootstrapDataset, datasetInfo, - bootstrapLabels, numClasses, minimumLeafSize, minimumGainSplit, - maximumDepth, dimensionSelector); + totalGain += trees[oldNumTrees + i].Train(bootstrapDataset, + datasetInfo, bootstrapLabels, numClasses, minimumLeafSize, + minimumGainSplit, maximumDepth, dimensionSelector); } else { - avgGain += trees[i].Train(bootstrapDataset, bootstrapLabels, numClasses, - minimumLeafSize, minimumGainSplit, maximumDepth, dimensionSelector); + totalGain += trees[oldNumTrees + i].Train(bootstrapDataset, + bootstrapLabels, numClasses, minimumLeafSize, minimumGainSplit, + maximumDepth, dimensionSelector); } } + Timer::Stop("train_tree"); } - return avgGain / numTrees; + + avgGain = totalGain / trees.size(); + return avgGain; } } // namespace tree diff --git a/src/mlpack/methods/random_forest/random_forest_main.cpp b/src/mlpack/methods/random_forest/random_forest_main.cpp index 9b0ab53ff3..c73385a709 100644 --- a/src/mlpack/methods/random_forest/random_forest_main.cpp +++ b/src/mlpack/methods/random_forest/random_forest_main.cpp @@ -130,6 +130,8 @@ PARAM_INT_IN("subspace_dim", "Dimensionality of random subspace to use for " "d", 0); PARAM_INT_IN("seed", "Random seed. If 0, 'std::time(NULL)' is used.", "s", 0); +PARAM_FLAG("warm_start", "If true and passed along with `training` and " + "`input_model` then trains more trees on top of existing model.", "w"); /** * This is the class that we will serialize. It is a pretty simple wrapper @@ -167,7 +169,11 @@ static void mlpackMain() math::RandomSeed((size_t) std::time(NULL)); // Check for incompatible input parameters. - RequireOnlyOnePassed({ "training", "input_model" }, true); + if (!IO::HasParam("warm_start")) + RequireOnlyOnePassed({ "training", "input_model" }, true); + else + // When warm_start is passed, training and input_model must also be passed. + RequireNoneOrAllPassed({"warm_start", "training", "input_model"}, true); ReportIgnoredParam({{ "training", false }}, "print_training_accuracy"); ReportIgnoredParam({{ "test", false }}, "test_labels"); @@ -201,10 +207,17 @@ static void mlpackMain() ReportIgnoredParam({{ "training", false }}, "minimum_leaf_size"); RandomForestModel* rfModel; + // Input model is loaded when we are either doing warm-started training or + // else we are making predictions only or both. + if (IO::HasParam("input_model")) + rfModel = IO::GetParam("input_model"); + // Handles the case when we are training new forest from scratch. + else + rfModel = new RandomForestModel(); + if (IO::HasParam("training")) { Timer::Start("rf_training"); - rfModel = new RandomForestModel(); // Train the model on the given input data. arma::mat data = std::move(IO::GetParam("training")); @@ -233,7 +246,8 @@ static void mlpackMain() // Train the model. rfModel->rf.Train(data, labels, numClasses, numTrees, minimumLeafSize, - minimumGainSplit, maxDepth, mrds); + minimumGainSplit, maxDepth, IO::HasParam("warm_start"), mrds); + Timer::Stop("rf_training"); // Did we want training accuracy? @@ -251,11 +265,6 @@ static void mlpackMain() Timer::Stop("rf_prediction"); } } - else - { - // Then we must be loading a model. - rfModel = IO::GetParam("input_model"); - } if (IO::HasParam("test")) { diff --git a/src/mlpack/tests/activation_functions_test.cpp b/src/mlpack/tests/activation_functions_test.cpp index 85cc1f7608..2ae2ea344d 100644 --- a/src/mlpack/tests/activation_functions_test.cpp +++ b/src/mlpack/tests/activation_functions_test.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "catch.hpp" @@ -1220,3 +1221,23 @@ TEST_CASE("HardSwishFunctionTest", "[ActivationFunctionsTest]") CheckDerivativeCorrect (desiredActivations, desiredDerivatives); } + +/** + * Basic test of the TanhExp function. + */ +TEST_CASE("TanhExpFunctionTest", "[ActivationFunctionsTest]") +{ + + const arma::colvec activationData("-2 3.2 4.5 1 -1 2 0"); + + // Hand-calculated values. + const arma::colvec desiredActivations("-0.26903 3.20000 4.50000 \ + 0.991329 -0.352135 2.0 0.0000"); + + // Hand-calculated values. + const arma::colvec desiredDerivatives("0.523051 1.0000 1.0000 \ + 1.03924 0.449818 1.00002 0.761594"); + + CheckActivationCorrect(activationData, desiredActivations); + CheckDerivativeCorrect(desiredActivations, desiredDerivatives); +} diff --git a/src/mlpack/tests/ann_layer_test.cpp b/src/mlpack/tests/ann_layer_test.cpp index 54105b1f9e..13576eb11a 100644 --- a/src/mlpack/tests/ann_layer_test.cpp +++ b/src/mlpack/tests/ann_layer_test.cpp @@ -3883,6 +3883,57 @@ TEST_CASE("LpMaxPoolingTestCase", "[ANNLayerTest]") REQUIRE(output.n_elem == 4); } +/** + * Simple test for Mean Pooling layer. + */ +TEST_CASE("MeanPoolingTestCase", "[ANNLayerTest]") +{ + // For rectangular input to pooling layers. + arma::mat input = arma::mat(28, 1); + input.zeros(); + input(0) = input(16) = 1; + input(1) = input(17) = 2; + input(2) = input(18) = 3; + input(3) = input(19) = 4; + input(4) = input(20) = 5; + input(5) = input(23) = 6; + input(6) = input(24) = 7; + input(14) = input(25) = 8; + input(15) = input(26) = 9; + + MeanPooling<> module1(2, 2, 2, 2, false); + MeanPooling<> module2(2, 2, 2, 2, true); + module1.InputWidth() = 7; + module1.InputHeight() = 4; + module2.InputWidth() = 7; + module2.InputHeight() = 4; + + // Calculated using torch.nn.MeanPool2d(). + arma::mat result1, result2; + result1 << 0.7500 << 4.2500 << arma::endr + << 1.7500 << 4.0000 << arma::endr + << 2.7500 << 6.0000 << arma::endr + << 3.5000 << 2.5000 << arma::endr; + + result2 << 0.7500 << 4.2500 << arma::endr + << 1.7500 << 4.0000 << arma::endr + << 2.7500 << 6.0000 << arma::endr; + + arma::mat output1, output2; + module1.Forward(input, output1); + module2.Forward(input, output2); + output1.reshape(4, 2); + output2.reshape(3, 2); + CheckMatrices(output1, result1, 1e-1); + CheckMatrices(output2, result2, 1e-1); + + arma::mat delta1, delta2; + module1.Backward(input, output1, delta1); + REQUIRE(arma::accu(delta1) == 25.5); + module2.Backward(input, output2, delta2); + REQUIRE(arma::accu(delta2) == 19.5); +} + /** * Simple test for Max Pooling layer. */ @@ -4155,7 +4206,7 @@ TEST_CASE("AdaptiveMeanPoolingTestCase", "[ANNLayerTest]") REQUIRE(output.n_cols == 1); // Test the Backward Function. module1.Backward(input, output, delta); - REQUIRE(arma::accu(delta) == 7.0); + REQUIRE(arma::accu(delta) == 19.75); // For Square input. input = arma::mat(9, 1); @@ -4177,7 +4228,7 @@ TEST_CASE("AdaptiveMeanPoolingTestCase", "[ANNLayerTest]") REQUIRE(output.n_cols == 1); // Test the Backward Function. module2.Backward(input, output, delta); - REQUIRE(arma::accu(delta) == 0.0); + REQUIRE(arma::accu(delta) == 4.50); // For Square input. input = arma::mat(16, 1); @@ -4219,7 +4270,7 @@ TEST_CASE("AdaptiveMeanPoolingTestCase", "[ANNLayerTest]") REQUIRE(output.n_cols == 1); // Test the Backward Function. module4.Backward(input, output, delta); - REQUIRE(arma::accu(delta) == 1.5); + REQUIRE(arma::accu(delta) == 2.25); } TEST_CASE("TransposedConvolutionalLayerOptionalParameterTest", "[ANNLayerTest]") diff --git a/src/mlpack/tests/catch.hpp b/src/mlpack/tests/catch.hpp index 0384171ae4..9c1c854fd5 100644 --- a/src/mlpack/tests/catch.hpp +++ b/src/mlpack/tests/catch.hpp @@ -1,9 +1,9 @@ /* - * Catch v2.13.4 - * Generated: 2020-12-29 14:48:00.116107 + * Catch v2.13.5 + * Generated: 2021-04-10 23:43:17.560525 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,7 +15,7 @@ #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 4 +#define CATCH_VERSION_PATCH 5 #ifdef __clang__ # pragma clang system_header @@ -66,13 +66,16 @@ #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ -# include -# if TARGET_OS_OSX == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif +# include +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX @@ -132,9 +135,9 @@ namespace Catch { #endif -// We have to avoid both ICC and Clang, because they try to mask themselves -// as gcc, and we want only GCC in this block -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) @@ -7054,8 +7057,8 @@ namespace Catch { double b2 = bias - z1; double a1 = a(b1); double a2 = a(b2); - auto lo = std::max(cumn(a1), 0); - auto hi = std::min(cumn(a2), n - 1); + auto lo = (std::max)(cumn(a1), 0); + auto hi = (std::min)(cumn(a2), n - 1); return { point, resample[lo], resample[hi], confidence_level }; } @@ -7124,7 +7127,9 @@ namespace Catch { } template EnvironmentEstimate> estimate_clock_cost(FloatDuration resolution) { - auto time_limit = std::min(resolution * clock_cost_estimation_tick_limit, FloatDuration(clock_cost_estimation_time_limit)); + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FloatDuration(clock_cost_estimation_time_limit)); auto time_clock = [](int k) { return Detail::measure([k] { for (int i = 0; i < k; ++i) { @@ -7771,7 +7776,7 @@ namespace Catch { double sb = stddev.point; double mn = mean.point / n; double mg_min = mn / 2.; - double sg = std::min(mg_min / 4., sb / std::sqrt(n)); + double sg = (std::min)(mg_min / 4., sb / std::sqrt(n)); double sg2 = sg * sg; double sb2 = sb * sb; @@ -7790,7 +7795,7 @@ namespace Catch { return (nc / n) * (sb2 - nc * sg2); }; - return std::min(var_out(1), var_out(std::min(c_max(0.), c_max(mg_min)))) / sb2; + return (std::min)(var_out(1), var_out((std::min)(c_max(0.), c_max(mg_min)))) / sb2; } bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector::iterator first, std::vector::iterator last) { @@ -7980,86 +7985,58 @@ namespace Catch { // start catch_fatal_condition.h -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include -#else -#include -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -#if defined( CATCH_CONFIG_WINDOWS_SEH ) +#include namespace Catch { - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); - FatalConditionHandler(); - static void reset(); - ~FatalConditionHandler(); - - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -#elif defined ( CATCH_CONFIG_POSIX_SIGNALS ) - -#include - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); + // Wrapper for platform-specific fatal error (signals/SEH) handlers + // + // Tries to be cooperative with other handlers, and not step over + // other handlers. This means that unknown structured exceptions + // are passed on, previous signal handlers are called, and so on. + // + // Can only be instantiated once, and assumes that once a signal + // is caught, the binary will end up terminating. Thus, there + class FatalConditionHandler { + bool m_started = false; + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform(); + public: + // Should also have platform-specific implementations as needed FatalConditionHandler(); ~FatalConditionHandler(); - static void reset(); + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } }; -} // namespace Catch - -#else - -namespace Catch { - struct FatalConditionHandler { - void reset(); + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } }; -} -#endif +} // end namespace Catch // end catch_fatal_condition.h #include @@ -8185,6 +8162,7 @@ namespace Catch { std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; + FatalConditionHandler m_fatalConditionhandler; bool m_lastAssertionPassed = false; bool m_shouldReportUnexpected = true; bool m_includeSuccessfulResults; @@ -10057,6 +10035,36 @@ namespace Catch { } // end catch_errno_guard.h +// start catch_windows_h_proxy.h + + +#if defined(CATCH_PLATFORM_WINDOWS) + +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +// end catch_windows_h_proxy.h #include namespace Catch { @@ -10573,7 +10581,7 @@ namespace Catch { // Extracts the actual name part of an enum instance // In other words, it returns the Blue part of Bikeshed::Colour::Blue StringRef extractInstanceName(StringRef enumInstance) { - // Find last occurence of ":" + // Find last occurrence of ":" size_t name_start = enumInstance.size(); while (name_start > 0 && enumInstance[name_start - 1] != ':') { --name_start; @@ -10735,25 +10743,47 @@ namespace Catch { // end catch_exception_translator_registry.cpp // start catch_fatal_condition.cpp -#if defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wmissing-field-initializers" -#endif +#include + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + FatalConditionHandler::engage_platform() {} + FatalConditionHandler::disengage_platform() {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) namespace { - // Report the error condition + //! Signals fatal error message to the run context void reportFatal( char const * const message ) { Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } -} -#endif // signals/SEH handling + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked anecdotally, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS #if defined( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; // There is no 1-1 mapping between signals and windows exceptions. @@ -10766,7 +10796,7 @@ namespace Catch { { static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error" }, }; - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { for (auto const& def : signalDefs) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { reportFatal(def.name); @@ -10777,38 +10807,50 @@ namespace Catch { return EXCEPTION_CONTINUE_SEARCH; } - FatalConditionHandler::FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static PVOID exceptionHandlerHandle = nullptr; - void FatalConditionHandler::reset() { - if (isSet) { - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. + FatalConditionHandler::FatalConditionHandler() { + ULONG guaranteeSize = static_cast(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; } } - FatalConditionHandler::~FatalConditionHandler() { - reset(); + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + void FatalConditionHandler::engage_platform() { + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + if (!exceptionHandlerHandle) { + CATCH_RUNTIME_ERROR("Could not register vectored exception handler"); + } } -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; + void FatalConditionHandler::disengage_platform() { + if (!RemoveVectoredExceptionHandler(exceptionHandlerHandle)) { + CATCH_RUNTIME_ERROR("Could not unregister vectored exception handler"); + } + exceptionHandlerHandle = nullptr; + } -} // namespace Catch +} // end namespace Catch -#elif defined( CATCH_CONFIG_POSIX_SIGNALS ) +#endif // CATCH_CONFIG_WINDOWS_SEH + +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) + +#include namespace Catch { @@ -10817,10 +10859,6 @@ namespace Catch { const char* name; }; - // 32kb for the alternate stack seems to be sufficient. However, this value - // is experimentally determined, so that's not guaranteed. - static constexpr std::size_t sigStackSize = 32768 >= MINSIGSTKSZ ? 32768 : MINSIGSTKSZ; - static SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, @@ -10830,7 +10868,32 @@ namespace Catch { { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; - void FatalConditionHandler::handleSignal( int sig ) { +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { char const * name = ""; for (auto const& def : signalDefs) { if (sig == def.id) { @@ -10838,16 +10901,33 @@ namespace Catch { break; } } - reset(); - reportFatal(name); + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); raise( sig ); } FatalConditionHandler::FatalConditionHandler() { - isSet = true; + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sigStackSize; + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { }; @@ -10859,40 +10939,17 @@ namespace Catch { } } - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[sigStackSize] = {}; - -} // namespace Catch - -#else - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -#endif // signals/SEH handling - #if defined(__GNUC__) # pragma GCC diagnostic pop #endif + + void FatalConditionHandler::disengage_platform() { + restorePreviousSignalHandlers(); + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_POSIX_SIGNALS // end catch_fatal_condition.cpp // start catch_generators.cpp @@ -11447,7 +11504,8 @@ namespace { return lhs == rhs; } - auto ulpDiff = std::abs(lc - rc); + // static cast as a workaround for IBM XLC + auto ulpDiff = std::abs(static_cast(lc - rc)); return static_cast(ulpDiff) <= maxUlpDiff; } @@ -11621,7 +11679,6 @@ Floating::WithinRelMatcher WithinRel(float target) { } // namespace Matchers } // namespace Catch - // end catch_matchers_floating.cpp // start catch_matchers_generic.cpp @@ -12955,9 +13012,8 @@ namespace Catch { } void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals + FatalConditionHandlerGuard _(&m_fatalConditionhandler); m_activeTestCase->invoke(); - fatalConditionHandler.reset(); } void RunContext::handleUnfinishedSections() { @@ -15320,7 +15376,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 13, 4, "", 0 ); + static Version version( 2, 13, 5, "", 0 ); return version; } diff --git a/src/mlpack/tests/decision_tree_test.cpp b/src/mlpack/tests/decision_tree_test.cpp index 7722c54fe4..f5ff789857 100644 --- a/src/mlpack/tests/decision_tree_test.cpp +++ b/src/mlpack/tests/decision_tree_test.cpp @@ -289,7 +289,7 @@ TEST_CASE("BestBinaryNumericSplitSimpleSplitTest", "[DecisionTreeTest]") weights.ones(); arma::vec classProbabilities; - BestBinaryNumericSplit::template AuxiliarySplitInfo aux; + BestBinaryNumericSplit::AuxiliarySplitInfo aux; // Call the method to do the splitting. const double bestGain = GiniGain::Evaluate(labels, 2, weights); @@ -327,7 +327,7 @@ TEST_CASE("BestBinaryNumericSplitMinSamplesTest", "[DecisionTreeTest]") arma::rowvec weights(labels.n_elem); arma::vec classProbabilities; - BestBinaryNumericSplit::template AuxiliarySplitInfo aux; + BestBinaryNumericSplit::AuxiliarySplitInfo aux; // Call the method to do the splitting. const double bestGain = GiniGain::Evaluate(labels, 2, weights); @@ -363,7 +363,7 @@ TEST_CASE("BestBinaryNumericSplitNoGainTest", "[DecisionTreeTest]") } arma::vec classProbabilities; - BestBinaryNumericSplit::template AuxiliarySplitInfo aux; + BestBinaryNumericSplit::AuxiliarySplitInfo aux; // Call the method to do the splitting. const double bestGain = GiniGain::Evaluate(labels, 2, weights); @@ -388,7 +388,7 @@ TEST_CASE("AllCategoricalSplitSimpleSplitTest", "[DecisionTreeTest]") weights.ones(); arma::vec classProbabilities; - AllCategoricalSplit::template AuxiliarySplitInfo aux; + AllCategoricalSplit::AuxiliarySplitInfo aux; // Call the method to do the splitting. const double bestGain = GiniGain::Evaluate(labels, 3, weights); @@ -424,7 +424,7 @@ TEST_CASE("AllCategoricalSplitMinSamplesTest", "[DecisionTreeTest]") weights.ones(); arma::vec classProbabilities; - AllCategoricalSplit::template AuxiliarySplitInfo aux; + AllCategoricalSplit::AuxiliarySplitInfo aux; // Call the method to do the splitting. const double bestGain = GiniGain::Evaluate(labels, 3, weights); @@ -457,7 +457,7 @@ TEST_CASE("AllCategoricalSplitNoGainTest", "[DecisionTreeTest]") } arma::vec classProbabilities; - AllCategoricalSplit::template AuxiliarySplitInfo aux; + AllCategoricalSplit::AuxiliarySplitInfo aux; // Call the method to do the splitting. const double bestGain = GiniGain::Evaluate(labels, 3, weights); @@ -701,6 +701,66 @@ TEST_CASE("SimpleGeneralizationTest", "[DecisionTreeTest]") REQUIRE(wdcorrect > 0.75); } +/** + * Test that the decision tree generalizes reasonably when built on float data. + */ +TEST_CASE("SimpleGeneralizationFMatTest", "[DecisionTreeTest]") +{ + arma::fmat inputData; + if (!data::Load("vc2.csv", inputData)) + FAIL("Cannot load test dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load labels for vc2_labels.txt"); + + // Initialize an all-ones weight matrix. + arma::rowvec weights(labels.n_cols, arma::fill::ones); + + // Build decision tree. + DecisionTree<> d(inputData, labels, 3, 10 /* Leaf size of 10. */); + DecisionTree<> wd(inputData, labels, 3, weights, 10 /* Leaf size of 10. */); + + // Load testing data. + arma::mat testData; + if (!data::Load("vc2_test.csv", testData)) + FAIL("Cannot load test dataset vc2_test.csv!"); + + arma::Mat trueTestLabels; + if (!data::Load("vc2_test_labels.txt", trueTestLabels)) + FAIL("Cannot load labels for vc2_test_labels.txt"); + + // Get the predicted test labels. + arma::Row predictions; + d.Classify(testData, predictions); + + REQUIRE(predictions.n_elem == testData.n_cols); + + // Figure out the accuracy. + double correct = 0.0; + for (size_t i = 0; i < predictions.n_elem; ++i) + if (predictions[i] == trueTestLabels[i]) + ++correct; + correct /= predictions.n_elem; + + REQUIRE(correct > 0.75); + + // Reset the prediction. + predictions.zeros(); + wd.Classify(testData, predictions); + + REQUIRE(predictions.n_elem == testData.n_cols); + + // Figure out the accuracy. + double wdcorrect = 0.0; + for (size_t i = 0; i < predictions.n_elem; ++i) + if (predictions[i] == trueTestLabels[i]) + ++wdcorrect; + wdcorrect /= predictions.n_elem; + + REQUIRE(wdcorrect > 0.75); +} + /** * Test that we can build a decision tree on a simple categorical dataset. */ diff --git a/src/mlpack/tests/gmm_test.cpp b/src/mlpack/tests/gmm_test.cpp index 3618dc1747..8c3b0241ca 100644 --- a/src/mlpack/tests/gmm_test.cpp +++ b/src/mlpack/tests/gmm_test.cpp @@ -27,7 +27,7 @@ using namespace mlpack; using namespace mlpack::gmm; /** - * Test GMM::Probability() for a single observation for a few cases. + * Test GMM::Probability() with a single observation at a time for a few cases. */ TEST_CASE("GMMProbabilityTest", "[GMMTest]") diff --git a/src/mlpack/tests/hmm_test.cpp b/src/mlpack/tests/hmm_test.cpp index 03a22f31e6..394d1a688e 100644 --- a/src/mlpack/tests/hmm_test.cpp +++ b/src/mlpack/tests/hmm_test.cpp @@ -1079,10 +1079,11 @@ TEST_CASE("GaussianHMMPredictTest", "[HMMTest]") { double loglikelihood = 0; arma::vec forwardLogProb; - for (size_t t = 0; t labels; + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load labels for vc2_labels.txt"); + + // Input training data. + SetInputParam("training", std::move(inputData)); + SetInputParam("labels", std::move(labels)); + + mlpackMain(); + + // Setting warm_start flag. + SetInputParam("warm_start", false); + + Log::Fatal.ignoreInput = true; + REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error); + Log::Fatal.ignoreInput = false; +} + +/** + * Ensuring that model does gets trained on top of existing one when warm_start + * and input_model are both passed. + */ +TEST_CASE_METHOD(RandomForestTestFixture, "RandomForestWarmStart", + "[RandomForestMainTest][BindingTests]") +{ + arma::mat inputData; + if (!data::Load("vc2.csv", inputData)) + FAIL("Cannot load train dataset vc2.csv!"); + + arma::Row labels; + if (!data::Load("vc2_labels.txt", labels)) + FAIL("Cannot load labels for vc2_labels.txt"); + + // Input training data. + SetInputParam("training", inputData); + SetInputParam("labels", labels); + + mlpackMain(); + + // Old number of trees in the model. + size_t oldNumTrees = + IO::GetParam("output_model")->rf.NumTrees(); + + // Input training data. + SetInputParam("training", std::move(inputData)); + SetInputParam("labels", std::move(labels)); + SetInputParam("warm_start", true); + + // Input pre-trained model. + SetInputParam("input_model", + IO::GetParam("output_model")); + + mlpackMain(); + + size_t newNumTrees = + IO::GetParam("output_model")->rf.NumTrees(); + + REQUIRE(oldNumTrees + 10 == newNumTrees); +} diff --git a/src/mlpack/tests/random_forest_test.cpp b/src/mlpack/tests/random_forest_test.cpp index 95d1f09a6a..62592c0907 100644 --- a/src/mlpack/tests/random_forest_test.cpp +++ b/src/mlpack/tests/random_forest_test.cpp @@ -457,14 +457,14 @@ TEST_CASE("RandomForestCategoricalTrainReturnEntropy", "[RandomForestTest]") // Test random forest on unweighted categorical dataset. RandomForest<> rf; double entropy = rf.Train(fullData, di, fullLabels, 5, 15 /* 15 trees */, 1, - 1e-7, 0, MultipleRandomDimensionSelect(3)); + 1e-7, 0, false, MultipleRandomDimensionSelect(3)); REQUIRE(std::isfinite(entropy) == true); // Test random forest on weighted categorical dataset. RandomForest<> wrf; entropy = wrf.Train(fullData, di, fullLabels, 5, weights, 15 /* 15 trees */, - 1, 1e-7, 0, MultipleRandomDimensionSelect(3)); + 1, 1e-7, 0, false, MultipleRandomDimensionSelect(3)); REQUIRE(std::isfinite(entropy) == true); } @@ -498,3 +498,63 @@ TEST_CASE("DifferentTreesTest", "[RandomForestTest]") REQUIRE(success == true); } + +/** + * Test that RandomForest::Train() when passed warmStart = True trains on top + * of exixting forest and adds the newly trained trees to the previously + * exixting forest. + */ +TEST_CASE("WarmStartTreesTest", "[RandomForestTest]") +{ + arma::mat trainingData; + arma::Row trainingLabels; + data::DatasetInfo di; + MockCategoricalData(trainingData, trainingLabels, di); + + // Train a random forest. + RandomForest<> rf(trainingData, di, trainingLabels, 5, 25 /* 25 trees */, 1, + 1e-7, 0, MultipleRandomDimensionSelect(4)); + + REQUIRE(rf.NumTrees() == 25); + + rf.Train(trainingData, di, trainingLabels, 5, 20 /* 20 trees */, 1, 1e-7, 0, + true /* warmStart */, MultipleRandomDimensionSelect(4)); + + REQUIRE(rf.NumTrees() == 25 + 20); +} + +/** + * Test that RandomForest::Train() when passed warmStart = True does not drop + * prediction quality on train data. Note that prediction quality may drop due + * to overfitting in some cases. + */ +TEST_CASE("WarmStartTreesPredictionsQualityTest", "[RandomForestTest]") +{ + arma::mat trainingData; + arma::Row trainingLabels; + data::DatasetInfo di; + MockCategoricalData(trainingData, trainingLabels, di); + + // Train a random forest. + RandomForest<> rf(trainingData, di, trainingLabels, 5, 3 /* 3 trees */, 1, + 1e-7, 0, MultipleRandomDimensionSelect(4)); + + // Get performance statistics on train data. + arma::Row oldPredictions; + rf.Classify(trainingData, oldPredictions); + + // Calculate the number of correct points. + size_t oldCorrect = arma::accu(oldPredictions == trainingLabels); + + rf.Train(trainingData, di, trainingLabels, 5, 20 /* 20 trees */, 1, 1e-7, 0, + true /* warmStart */, MultipleRandomDimensionSelect(4)); + + // Get performance statistics on train data. + arma::Row newPredictions; + rf.Classify(trainingData, newPredictions); + + // Calculate the number of correct points. + size_t newCorrect = arma::accu(newPredictions == trainingLabels); + + REQUIRE(newCorrect - oldCorrect >= 0); +} diff --git a/src/mlpack/tests/split_data_test.cpp b/src/mlpack/tests/split_data_test.cpp index 8de9d5f66b..4ef35924c0 100644 --- a/src/mlpack/tests/split_data_test.cpp +++ b/src/mlpack/tests/split_data_test.cpp @@ -223,9 +223,9 @@ TEST_CASE("ZeroRatioStratifiedSplitData", "[SplitDataTest]") // Set the labels to 5 0s and 10 1s. const Row labels = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - const double test_ratio = 0; + const double testRatio = 0; - const auto value = Split(input, labels, test_ratio, false, true); + const auto value = Split(input, labels, testRatio, false, true); REQUIRE(std::get<0>(value).n_cols == 15); REQUIRE(std::get<1>(value).n_cols == 0); REQUIRE(std::get<2>(value).n_cols == 15); @@ -242,9 +242,9 @@ TEST_CASE("TotalRatioStratifiedSplitData", "[SplitDataTest]") // Set the labels to 5 0s and 10 1s. const Row labels = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; - const double test_ratio = 1; + const double testRatio = 1; - const auto value = Split(input, labels, test_ratio, false, true); + const auto value = Split(input, labels, testRatio, false, true); REQUIRE(std::get<0>(value).n_cols == 0); REQUIRE(std::get<1>(value).n_cols == 15); REQUIRE(std::get<2>(value).n_cols == 0); @@ -263,9 +263,9 @@ TEST_CASE("StratifiedSplitDataResultTest", "[SplitDataTest]") const Row labels = { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }; - const double test_ratio = 0.25; + const double testRatio = 0.25; - const auto value = Split(input, labels, test_ratio, true, true); + const auto value = Split(input, labels, testRatio, true, true); REQUIRE(static_cast(find(std::get<2>(value) == 0)).n_rows == 3); REQUIRE(static_cast(find(std::get<2>(value) == 1)).n_rows == 6); REQUIRE(static_cast(find(std::get<2>(value) == 2)).n_rows == 9); @@ -293,22 +293,22 @@ TEST_CASE("StratifiedSplitLargerDataResultTest", "[SplitDataTest]") input.randu(); // 256 0s, 128 1s, 64 2s and 32 3s. - Row zero_label(256); - Row one_label(128); - Row two_label(64); - Row three_label(32); + Row zeroLabel(256); + Row oneLabel(128); + Row twoLabel(64); + Row threeLabel(32); - zero_label.fill(0); - one_label.fill(1); - two_label.fill(2); - three_label.fill(3); + zeroLabel.fill(0); + oneLabel.fill(1); + twoLabel.fill(2); + threeLabel.fill(3); - Row labels = arma::join_rows(zero_label, one_label); - labels = arma::join_rows(labels, two_label); - labels = arma::join_rows(labels, three_label); - const double test_ratio = 0.3; + Row labels = arma::join_rows(zeroLabel, oneLabel); + labels = arma::join_rows(labels, twoLabel); + labels = arma::join_rows(labels, threeLabel); + const double testRatio = 0.3; - const auto value = Split(input, labels, test_ratio, false, true); + const auto value = Split(input, labels, testRatio, false, true); REQUIRE(static_cast(find(std::get<2>(value) == 0)).n_rows == 180); REQUIRE(static_cast(find(std::get<2>(value) == 1)).n_rows == 90); REQUIRE(static_cast(find(std::get<2>(value) == 2)).n_rows == 45); @@ -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 testRatio = 0.3; + + REQUIRE_THROWS_AS(Split(input, labels, testRatio, 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 inputConcat = arma::join_rows(std::get<0>(value), std::get<1>(value)); + mat labelsConcat = arma::join_rows(std::get<2>(value), std::get<3>(value)); + + // Order matters here. + CheckMatrices(input, inputConcat); + CheckMatrices(labels, labelsConcat); +} + +/** + * 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 inputConcat = {std::get<0>(value)(0), std::get<1>(value)(0)}; + field labelConcat = {std::get<2>(value)(0), std::get<3>(value)(0)}; + + // Order matters here. + CheckFields(input, inputConcat); + CheckFields(label, labelConcat); +} 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;