diff --git a/.appveyor.yml b/.appveyor.yml index 08c8dc4468..722a597468 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -71,7 +71,6 @@ build_script: -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% - -DBOOST_LIBRARYDIR:PATH="C:/projects/mlpack/boost_libs" -DDEBUG=OFF -DPROFILE=OFF -DBUILD_PYTHON_BINDINGS=OFF diff --git a/.ci/ci.yaml b/.ci/ci.yaml index 5348c55cc9..26efee03ae 100644 --- a/.ci/ci.yaml +++ b/.ci/ci.yaml @@ -59,25 +59,6 @@ jobs: steps: - template: macos-steps.yaml -# - job: WindowsVS15 -# timeoutInMinutes: 360 -# displayName: Windows VS15 -# pool: -# vmImage: vs2017-win2016 -# strategy: -# matrix: -# Plain: -# CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF' -# python.version: '2.7' -# CMakeGenerator: '-G "Visual Studio 15 2017 Win64"' -# MSBuildVersion: '15.0' -# ArchiveNoLibs: 'mlpack-windows-vs15-no-libs.zip' -# ArchiveLibs: 'mlpack-windows-vs15.zip' -# ArchiveTests: 'mlpack_test-vs15.xml' - -# steps: -# - template: windows-steps.yaml - - job: WindowsVS16 timeoutInMinutes: 360 displayName: Windows VS16 diff --git a/.ci/linux-steps.yaml b/.ci/linux-steps.yaml index 21baace148..f695c14fe1 100644 --- a/.ci/linux-steps.yaml +++ b/.ci/linux-steps.yaml @@ -21,7 +21,7 @@ steps: unset BOOST_ROOT echo "##vso[task.setvariable variable=BOOST_ROOT]"$BOOST_ROOT - sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost1.70-dev libarmadillo-dev xz-utils + sudo apt-get install -y --allow-unauthenticated libopenblas-dev g++ libboost1.70-dev xz-utils if [ "$(binding)" == "python" ]; then export PYBIN=$(which python) diff --git a/.ci/macos-steps.yaml b/.ci/macos-steps.yaml index c437344050..85e92fe3b3 100644 --- a/.ci/macos-steps.yaml +++ b/.ci/macos-steps.yaml @@ -22,7 +22,7 @@ steps: fi if [ "a$(julia.version)" != "a" ]; then - brew cask install julia + brew install --cask julia fi git clone --depth 1 https://github.com/mlpack/jenkins-conf.git conf diff --git a/.ci/windows-steps.yaml b/.ci/windows-steps.yaml index 4281ab23f8..e2a9ed38e0 100644 --- a/.ci/windows-steps.yaml +++ b/.ci/windows-steps.yaml @@ -116,6 +116,56 @@ steps: replaceExistingArchive: true displayName: 'Build artifacts' +# Build MSI installer. +- powershell: | + # Pull the documentation for the installer. + try { + $url = "http://ci.mlpack.org/job/mlpack%20-%20doxygen%20build/lastSuccessfulBuild/artifact/build/doc/html/*zip*/html.zip" + (new-object net.webclient).DownloadFile($url, 'dist\win-installer\jenkinsdoc.zip') + } + catch { + Write-Output "Unable to download precompiled Doxygen documentation from Jenkins!" + } + try { + (Add-Type -AssemblyName System.IO.Compression.FileSystem); + [System.IO.Compression.ZipFile]::ExtractToDirectory('dist\win-installer\jenkinsdoc.zip', 'dist\win-installer\mlpack-win-installer\Sources\doc') + } + catch{Write-Output "Unable to add doc to installer, skipping!"} + # Preparing installer staging. + mkdir dist\win-installer\mlpack-win-installer\Sources\lib + cp build\Release\*.lib dist\win-installer\mlpack-win-installer\Sources\lib\ + cp build\Release\*.exp dist\win-installer\mlpack-win-installer\Sources\lib\ + cp build\Release\*.dll dist\win-installer\mlpack-win-installer\Sources\ + cp build\Release\*.exe dist\win-installer\mlpack-win-installer\Sources\ + cp $(Agent.ToolsDirectory)\OpenBLAS.0.2.14.1\lib\native\bin\x64\*.dll dist\win-installer\mlpack-win-installer\Sources\ + cp build\include\mlpack dist\win-installer\mlpack-win-installer\Sources -recurse + cp doc\examples dist\win-installer\mlpack-win-installer\Sources -recurse + cp src\mlpack\tests\data\german.csv dist\win-installer\mlpack-win-installer\Sources\examples\sample-ml-app\sample-ml-app\data\ + # Check current git version or mlpack version. + $ver = (Get-Content "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 "src/mlpack/core/util/gitversion.hpp") + { + $ver = (Get-Content "src/mlpack/core/util/gitversion.hpp"); + $env:INSTALL_VERSION = $ver.Split('"')[1].Split(' ')[1]; + } + else + { + $env:INSTALL_VERSION = $env:MLPACK_VERSION; + } + + # Build the MSI installer. + cd dist\win-installer\mlpack-win-installer + & 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe' ` + -t:rebuild ` + -p:Configuration=Release ` + -p:TreatWarningsAsErrors=True ` + mlpack-win-installer.wixproj + displayName: 'Build MSI Windows installer' + # Publish artifacts to Azure Pipelines - task: PublishBuildArtifacts@1 inputs: @@ -132,6 +182,11 @@ steps: pathtoPublish: 'build/Testing/' artifactName: 'Tests' displayName: 'Publish artifacts test results' +- task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: 'dist\win-installer\mlpack-win-installer\bin\Release\mlpack-windows.msi' + artifactName: mlpack-windows-installer + displayName: 'Publish Windows MSI installer' # Publish test results to Azure Pipelines - task: PublishTestResults@2 diff --git a/.github/workflows/update-boost-version.yaml b/.github/workflows/update-boost-version.yaml new file mode 100644 index 0000000000..c9bb937c37 --- /dev/null +++ b/.github/workflows/update-boost-version.yaml @@ -0,0 +1,81 @@ +name: Update Boost Version +on: + workflow_dispatch: + schedule: + - cron: '0 10 * * *' +jobs: + updateBoostVersion: + if: ${{ github.repository == 'mlpack/mlpack' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Build Dependencies + run: | + sudo apt-get update + sudo apt-get install -y --allow-unauthenticated libopenblas-dev liblapack-dev g++ libboost-all-dev libcereal-dev + curl https://data.kurg.org/armadillo-8.400.0.tar.xz | tar -xvJ && cd armadillo* + cmake . && make && sudo make install && cd .. && rm -r armadillo* + + - name: Get Latest Boost Tagged Release + id: boost-version + run: | + # CMake for extracting present boost version. + mkdir build && cd build + cmake .. + + # Ping version information upstream. + BOOST_RELEASE_JSON=$(curl -sL https://api.github.com/repos/boostorg/boost/tags) + FOUND_NEW="NO" + + # Compare present and upstream boost version. + for i in `jq -r .[].name <<< "$BOOST_RELEASE_JSON" | awk '!/.beta/' | \ + grep -Po "(\d+\.)+\d+"` + do + FOUND_SIMILAR="NO" + for j in `grep Boost_ADDITIONAL_VERSIONS_LAST CMakeCache.txt | \ + cut -d "=" -f2 | sed "s/;/ /g"` + do + if [[ "$i" == "$j" ]]; + then + FOUND_SIMILAR="YES" + break + fi + done + if [[ "$FOUND_SIMILAR" != "YES" ]]; + then + FOUND_NEW="YES" + BOOST_VERSION="$BOOST_VERSION\"$i\" " + fi + FOUND_SIMILAR="NO" + for j in `grep Boost_ADDITIONAL_VERSIONS_LAST CMakeCache.txt | \ + cut -d "=" -f2 | sed "s/;/ /g"` + do + if [[ $(echo $i | grep -Po "(\d+)\.\d+") == "$j" ]]; + then + FOUND_SIMILAR="YES" + break + fi + done + if [[ "$FOUND_SIMILAR" != "YES" ]]; + then + FOUND_NEW="YES" + BOOST_VERSION="$BOOST_VERSION\"$(echo $i | grep -Po "(\d+)\.\d+")\" " + fi + done + + # If found the new boost version, then update the CMake script. + if [[ "$FOUND_NEW" == "YES" ]] + then + sed --in-place "s/set(Boost_ADDITIONAL_VERSIONS/set(Boost_ADDITIONAL_VERSIONS\n ${BOOST_VERSION: : -1}/" ../CMakeLists.txt + fi + + - name: Create Pull Request For Boost Version + uses: peter-evans/create-pull-request@v3 + with: + commit-message: Upgrade Boost Version in CMake script. + title: Upgrade Boost Version in CMake script. + body: | + Updates [boostorg/boost](https://github.com/boostorg/boost) in CMake script. + Auto-generated by [create-pull-request](https://github.com/peter-evans/create-pull-request). + labels: update dependencies, automated PR + branch: boost-version-updates diff --git a/CMake/Findcereal.cmake b/CMake/Findcereal.cmake index b4d99fe823..aa30354145 100644 --- a/CMake/Findcereal.cmake +++ b/CMake/Findcereal.cmake @@ -35,7 +35,7 @@ if(CEREAL_INCLUDE_DIR) set(CEREAL_VERSION_MAJOR 1) set(CEREAL_VERSION_MINOR 1) set(CEREAL_VERSION_PATCH 2) -elseif(EXISTS "${CEREAL_INCLUDE_DIR}/cereal/cereal.hpp") + elseif(EXISTS "${CEREAL_INCLUDE_DIR}/cereal/cereal.hpp") set(CEREAL_VERSION_MAJOR 1) set(CEREAL_VERSION_MINOR 1) diff --git a/CMake/julia/AppendType.cmake b/CMake/julia/AppendType.cmake index 299be1c6a4..d8c15d87d2 100644 --- a/CMake/julia/AppendType.cmake +++ b/CMake/julia/AppendType.cmake @@ -32,8 +32,19 @@ function(append_type TYPES_FILE PROGRAM_NAME PROGRAM_MAIN_FILE) # function. file(APPEND "${TYPES_FILE}" - "struct ${MODEL_SAFE_TYPE}\n" + "mutable struct ${MODEL_SAFE_TYPE}\n" " ptr::Ptr{Nothing}\n" + "\n" + " # Construct object and set finalizer to free memory if `finalize` is true.\n" + " function ${MODEL_SAFE_TYPE}(ptr::Ptr{Nothing}; finalize::Bool = false)::${MODEL_SAFE_TYPE}\n" + " result = new(ptr)\n" + " if finalize\n" + " finalizer(\n" + " x -> _Internal.${PROGRAM_NAME}_internal.Delete${MODEL_SAFE_TYPE}(x.ptr),\n" + " result)\n" + " end\n" + " return result\n" + " end\n" "end\n" "\n") endif () diff --git a/CMake/julia/ConfigureJuliaHCPP.cmake b/CMake/julia/ConfigureJuliaHCPP.cmake index 1fb9a4b4be..38c7be5cbf 100644 --- a/CMake/julia/ConfigureJuliaHCPP.cmake +++ b/CMake/julia/ConfigureJuliaHCPP.cmake @@ -29,6 +29,8 @@ if (${NUM_MODEL_TYPES} GREATER 0) void* IO_GetParam${MODEL_SAFE_TYPE}Ptr(const char* paramName); // Set the pointer to a ${MODEL_TYPE} parameter. void IO_SetParam${MODEL_SAFE_TYPE}Ptr(const char* paramName, void* ptr); +// Delete a ${MODEL_TYPE} pointer. +void Delete${MODEL_SAFE_TYPE}Ptr(void* ptr); // Serialize a ${MODEL_TYPE} pointer. char* Serialize${MODEL_SAFE_TYPE}Ptr(void* ptr, size_t* length); // Deserialize a ${MODEL_TYPE} pointer. @@ -50,6 +52,13 @@ void IO_SetParam${MODEL_SAFE_TYPE}Ptr(const char* paramName, void* ptr) IO::SetPassed(paramName); } +// Delete a ${MODEL_TYPE} pointer. +void Delete${MODEL_SAFE_TYPE}Ptr(void* ptr) +{ + ${MODEL_TYPE}* modelPtr = (${MODEL_TYPE}*) ptr; + delete modelPtr; +} + // Serialize a ${MODEL_TYPE} pointer. char* Serialize${MODEL_SAFE_TYPE}Ptr(void* ptr, size_t* length) { diff --git a/CMakeLists.txt b/CMakeLists.txt index 10f126283a..e0be77df06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ option(DISABLE_DOWNLOADS "Disable downloads of dependencies during build." OFF) option(DOWNLOAD_ENSMALLEN "If ensmallen is not found, download it." ON) option(DOWNLOAD_STB_IMAGE "Download stb_image for image loading." ON) option(BUILD_GO_SHLIB "Build Go shared library." OFF) +option(BUILD_DOCS "Build doxygen documentation (if doxygen is available)." ON) # Set minimum library version required by mlpack. set(ARMADILLO_VERSION "8.400.0") @@ -51,7 +52,7 @@ if (BUILD_JULIA_BINDINGS) else() set(FORCE_BUILD_JULIA_BINDINGS OFF) endif() -option(BUILD_JULIA_BINDINGS "Build Julia bindings." ON) +option(BUILD_JULIA_BINDINGS "Build Julia bindings." OFF) # Detect whether the user passed BUILD_GO_BINDINGS in order to determine if # we should fail if Go isn't found. @@ -60,7 +61,7 @@ if (BUILD_GO_BINDINGS) else() set(FORCE_BUILD_GO_BINDINGS OFF) endif() -option(BUILD_GO_BINDINGS "Build Go bindings." ON) +option(BUILD_GO_BINDINGS "Build Go bindings." OFF) # If building Go bindings then build go shared libraries. if (BUILD_GO_BINDINGS) @@ -74,7 +75,7 @@ if (BUILD_R_BINDINGS) else() set(FORCE_BUILD_R_BINDINGS OFF) endif() -option(BUILD_R_BINDINGS "Build R bindings." ON) +option(BUILD_R_BINDINGS "Build R bindings." OFF) # Build Markdown bindings for documentation. This is used as part of website # generation. option(BUILD_MARKDOWN_BINDINGS "Build Markdown bindings for website documentation." OFF) @@ -288,7 +289,6 @@ endif() # ARMADILLO_INCLUDE_DIRS - directories necessary for Armadillo includes # BOOST_ROOT - root of Boost installation # BOOST_INCLUDEDIR - include directory for Boost -# BOOST_LIBRARYDIR - library directory for Boost # ENSMALLEN_INCLUDE_DIR - include directory for ensmallen # STB_IMAGE_INCLUDE_DIR - include directory for STB image library # MATHJAX_ROOT - root of MathJax installation @@ -419,8 +419,9 @@ set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} ${CEREAL_INCLUDE_DIR}) # Unfortunately this configuration variable is necessary and will need to be # updated as time goes on and new versions are released. set(Boost_ADDITIONAL_VERSIONS - "1.74.0" "1.74" - "17.3.0" "17.3" + "1.75.0" "1.75" + "1.74.0" "1.74" + "1.73.0" "1.73" "1.72.0" "1.72" "1.71.0" "1.71" "1.70.0" "1.70" @@ -442,31 +443,11 @@ set(Boost_ADDITIONAL_VERSIONS # TODO for the brave: transition all mlpack's CMake to 'target-based modern # CMake'. Good luck! You'll need it. set(Boost_NO_BOOST_CMAKE 1) -find_package(Boost "${BOOST_VERSION}" - COMPONENTS - REQUIRED -) - -link_directories(${Boost_LIBRARY_DIRS}) - -# In Visual Studio, automatic linking is performed, so we don't need to worry -# about it. Clear the list of libraries to link against and let Visual Studio -# handle it. -if (MSVC) - link_directories(${Boost_LIBRARY_DIRS}) - set(CMAKE_MSVCIDE_RUN_PATH ${CMAKE_MSVCIDE_RUN_PATH} ${Boost_LIBRARY_DIRS}) - message("boost lib dirs ${Boost_LIBRARY_DIRS}") - set(Boost_LIBRARIES "") -endif () +find_package(Boost "${BOOST_VERSION}") set(MLPACK_INCLUDE_DIRS ${MLPACK_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS}) -set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES} ${Boost_LIBRARIES}) -set(MLPACK_LIBRARY_DIRS ${MLPACK_LIBRARY_DIRS} ${Boost_LIBRARY_DIRS}) - -# For Boost testing framework (will have no effect on non-testing executables). -# This specifies to Boost that we are dynamically linking to the Boost test -# library. -add_definitions(-DBOOST_TEST_DYN_LINK) +set(MLPACK_LIBRARIES ${MLPACK_LIBRARIES}) +set(MLPACK_LIBRARY_DIRS ${MLPACK_LIBRARY_DIRS}) # Detect OpenMP support in a compiler. If the compiler supports OpenMP, flags # to compile with OpenMP are returned and added and the HAS_OPENMP definition @@ -617,43 +598,45 @@ add_dependencies(mlpack_headers mlpack_arma_config) # Make a target to generate the documentation. If Doxygen isn't installed, then # I guess this option will just be unavailable. -find_package(Doxygen) -if (DOXYGEN_FOUND) - if (MATHJAX) - find_package(MathJax) - if (NOT MATHJAX_FOUND) - message(STATUS "Using MathJax at the MathJax Content Delivery Network. " - "Be careful, formulas will not be shown without the internet.") +if (BUILD_DOCS) + find_package(Doxygen) + if (DOXYGEN_FOUND) + if (MATHJAX) + find_package(MathJax) + if (NOT MATHJAX_FOUND) + message(STATUS "Using MathJax at the MathJax Content Delivery Network. " + "Be careful, formulas will not be shown without the internet.") + endif () endif () + # Preprocess the Doxyfile. This is done before 'make doc'. + add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/Doxyfile + PRE_BUILD + COMMAND ${CMAKE_COMMAND} + -D DESTDIR=${CMAKE_BINARY_DIR} + -D MATHJAX="${MATHJAX}" + -D MATHJAX_FOUND="${MATHJAX_FOUND}" + -D MATHJAX_PATH="${MATHJAX_PATH}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/CMake/GenerateDoxyfile.cmake" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile" + COMMENT "Creating Doxyfile to generate Doxygen documentation" + ) + + # Generate documentation. + add_custom_target(doc + COMMAND "${DOXYGEN_EXECUTABLE}" "${CMAKE_BINARY_DIR}/Doxyfile" + DEPENDS "${CMAKE_BINARY_DIR}/Doxyfile" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" + COMMENT "Generating API documentation with Doxygen" + ) + + install(DIRECTORY "${CMAKE_BINARY_DIR}/doc/html" + DESTINATION "${CMAKE_INSTALL_DOCDIR}" + COMPONENT doc + OPTIONAL + ) endif () - # Preprocess the Doxyfile. This is done before 'make doc'. - add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/Doxyfile - PRE_BUILD - COMMAND ${CMAKE_COMMAND} - -D DESTDIR=${CMAKE_BINARY_DIR} - -D MATHJAX="${MATHJAX}" - -D MATHJAX_FOUND="${MATHJAX_FOUND}" - -D MATHJAX_PATH="${MATHJAX_PATH}" - -P "${CMAKE_CURRENT_SOURCE_DIR}/CMake/GenerateDoxyfile.cmake" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile" - COMMENT "Creating Doxyfile to generate Doxygen documentation" - ) - - # Generate documentation. - add_custom_target(doc - COMMAND "${DOXYGEN_EXECUTABLE}" "${CMAKE_BINARY_DIR}/Doxyfile" - DEPENDS "${CMAKE_BINARY_DIR}/Doxyfile" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" - COMMENT "Generating API documentation with Doxygen" - ) - - install(DIRECTORY "${CMAKE_BINARY_DIR}/doc/html" - DESTINATION "${CMAKE_INSTALL_DOCDIR}" - COMPONENT doc - OPTIONAL - ) -endif () +endif() # Create the pkg-config file, if we have pkg-config. find_package(PkgConfig) diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 8e4088dceb..d2d177da71 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -7,7 +7,7 @@ Source: Files: * Copyright: - Copyright 2008-2018, Ryan Curtin + Copyright 2008-2021, Ryan Curtin Copyright 2008-2013, Bill March Copyright 2008-2012, Dongryeol Lee Copyright 2008-2013, Nishant Mehta @@ -22,11 +22,11 @@ Copyright: Copyright 2012, Rajendran Mohan Copyright 2012, Trironk Kiatkungwanglai Copyright 2012, Patrick Mason - Copyright 2013-2018, Marcus Edel + Copyright 2013-2020, Marcus Edel Copyright 2013, Mudit Raj Gupta Copyright 2013-2018, Sumedh Ghaisas Copyright 2014, Michael Fox - Copyright 2014, Ryan Birmingham + Copyright 2014,2020 Ryan Birmingham Copyright 2014, Siddharth Agrawal Copyright 2014, Saheb Motiani Copyright 2014, Yash Vadalia @@ -37,7 +37,7 @@ Copyright: Copyright 2014, Udit Saxena Copyright 2014-2015, Stephen Tu Copyright 2014-2015, Jaskaran Singh - Copyright 2015&2017, Shangtong Zhang + Copyright 2015,2017, Shangtong Zhang Copyright 2015, Hritik Jain Copyright 2015, Vladimir Glazachev Copyright 2015, QiaoAn Chen @@ -55,7 +55,7 @@ Copyright: Copyright 2016, Palash Ahuja Copyright 2016, Yannis Mentekidis Copyright 2016, Ranjan Mondal - Copyright 2016-2018, Mikhail Lozhnikov + Copyright 2016-2020, Mikhail Lozhnikov Copyright 2016, Marcos Pividori Copyright 2016, Keon Kim Copyright 2016, Nilay Jain @@ -84,14 +84,14 @@ Copyright: Copyright 2017, N Rajiv Vaidyanathan Copyright 2017, Kartik Nighania Copyright 2017-2018, Eugene Freyman - Copyright 2017-2018, Manish Kumar + Copyright 2017-2019, Manish Kumar Copyright 2017-2018, Haritha Sreedharan Nair Copyright 2017-2018, Sourabh Varshney Copyright 2018, Projyal Dev Copyright 2018, Nikhil Goel - Copyright 2018, Shikhar Jaiswal + Copyright 2018-2020 Shikhar Jaiswal Copyright 2018, B Kartheek Reddy - Copyright 2018, Atharva Khandait + Copyright 2018-2019 Atharva Khandait Copyright 2018, Wenhao Huang Copyright 2018-2019, Roberto Hueso Copyright 2018, Prabhat Sharma @@ -114,9 +114,9 @@ Copyright: Copyright 2019, Miguel Canteras Copyright 2019, Bishwa Karki Copyright 2019, Mehul Kumar Nirala - Copyright 2019, Yashwant Singh Parihar + Copyright 2019-2020 Yashwant Singh Parihar Copyright 2019, Heet Sankesara - Copyright 2019, Jeffin Sam + Copyright 2019-2020 Jeffin Sam Copyright 2019, Vikas S Shetty Copyright 2019, Khizir Siddiqui Copyright 2019, Tejasvi Tomar @@ -124,7 +124,7 @@ Copyright: Copyright 2019, Ziyang Jiang Copyright 2019, Rohit Kartik Copyright 2019, Aditya Viki - Copyright 2019, Kartik Dutt + Copyright 2019-2020 Kartik Dutt Copyright 2020, Sriram S K Copyright 2020, Manoranjan Kumar Bharti ( Nakul Bharti ) Copyright 2020, Saraansh Tandon @@ -136,6 +136,11 @@ Copyright: Copyright 2020, Aakash Kaushik Copyright 2020, Anush Kini Copyright 2020, Nippun Sharma + Copyright 2020, Rishabh Garg + Copyright 2020, Sudhakar Brar + Copyright 2020, Alex Nguyen + Copyright 2020, Gaurav Ghati + Copyright 2020, Anmolpreet Singh License: BSD-3-clause All rights reserved. diff --git a/Doxyfile b/Doxyfile index 465aa90974..b7afc561b9 100644 --- a/Doxyfile +++ b/Doxyfile @@ -75,7 +75,8 @@ FILE_VERSION_FILTER = #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES -WARN_AS_ERROR = YES +# This will be set to YES for the Jenkins doxygen check build. +WARN_AS_ERROR = NO WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = YES diff --git a/HISTORY.md b/HISTORY.md index be8a4d66b0..1039ed9d13 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,7 +1,37 @@ ### mlpack ?.?.? ###### ????-??-?? + * Add "check_input_matrices" option to python bindings that checks + for NaN and inf values in all the input matrices (#2787). + + * Add Adjusted R squared functionality to R2Score::Evaluate (#2624). + + * Disabled all the bindings by default in CMake (#2782). + * Added an implementation to Stratify Data (#2671). + * Add `BUILD_DOCS` CMake option to control whether Doxygen documentation is + built (default ON) (#2730). + + * Add Triplet Margin Loss function (#2762). + + * Add finalizers to Julia binding model types to fix memory handling (#2756). + + * HMM: add functions to calculate likelihood for data stream with/without + pre-calculated emission probability (#2142). + + * Replace Boost serialization library with Cereal (#2458). + + * Add `PYTHON_INSTALL_PREFIX` CMake option to specify installation root for + Python bindings (#2797). + + * Removed `boost::visitor` from model classes for `knn`, `kfn`, `cf`, + `range_search`, `krann`, and `kde` bindings (#2803). + + * Add k-means++ initialization strategy (#2813). + + * `NegativeLogLikelihood<>` now expects classes in the range `0` to + `numClasses - 1` (#2534). + ### mlpack 3.4.2 ###### 2020-10-26 * Added Mean Absolute Percentage Error. diff --git a/README.md b/README.md index ab804757b1..bb8ba1be6c 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,13 @@ 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, -on Ubuntu, you can install mlpack with the following command: +on Ubuntu, you can install the mlpack library and command-line executables (e.g. +mlpack_pca, mlpack_kmeans etc.) with the following command: - $ sudo apt-get install libmlpack-dev + $ sudo apt-get install libmlpack-dev mlpack-bin + +On Fedora or Red Hat (EPEL): + $ sudo dnf install mlpack-devel mlpack-bin Note: Older Ubuntu versions may not have the most recent version of mlpack available---for instance, at the time of this writing, Ubuntu 16.04 only has @@ -199,6 +203,7 @@ Options are specified with the -D flag. The allowed options include: BUILD_CLI_EXECUTABLES=(ON/OFF): whether or not to build command-line programs BUILD_PYTHON_BINDINGS=(ON/OFF): whether or not to build Python bindings PYTHON_EXECUTABLE=(/path/to/python_version): Path to specific Python executable + PYTHON_INSTALL_PREFIX=(/path/to/python/): Path to root of Python installation BUILD_JULIA_BINDINGS=(ON/OFF): whether or not to build Julia bindings JULIA_EXECUTABLE=(/path/to/julia): Path to specific Julia executable BUILD_GO_BINDINGS=(ON/OFF): whether or not to build Go bindings @@ -217,6 +222,8 @@ Options are specified with the -D flag. The allowed options include: STB_IMAGE_INCLUDE_DIR=(/path/to/stb/include): path to include directory for STB image library USE_OPENMP=(ON/OFF): whether or not to use OpenMP if available + BUILD_DOCS=(ON/OFF): build Doxygen documentation, if Doxygen is available + (default ON) Other tools can also be used to configure CMake, but those are not documented here. See [this section of the build guide](https://www.mlpack.org/doc/mlpack-git/doxygen/build.html#build_config) diff --git a/dist/win-installer/mlpack-win-installer/Product.wxs b/dist/win-installer/mlpack-win-installer/Product.wxs index 4adafc2a23..3cd7598a85 100644 --- a/dist/win-installer/mlpack-win-installer/Product.wxs +++ b/dist/win-installer/mlpack-win-installer/Product.wxs @@ -2,47 +2,38 @@ - - - + + - - - + + - - - - $(env.MLPACK_VERSION) - - - - - - + + + + + + + - - - - - - - - - - - - - - + + + + $(env.MLPACK_VERSION) + + + + + + diff --git a/dist/win-installer/mlpack-win-installer/mlpack-win-installer.wixproj b/dist/win-installer/mlpack-win-installer/mlpack-win-installer.wixproj index 8795a920eb..0e89ee825c 100644 --- a/dist/win-installer/mlpack-win-installer/mlpack-win-installer.wixproj +++ b/dist/win-installer/mlpack-win-installer/mlpack-win-installer.wixproj @@ -9,33 +9,36 @@ mlpack-windows Package mlpack-win-installer + false + SourceDir=.\Sources + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.targets bin\$(Configuration)\ obj\$(Configuration)\ - Debug + Debug;$(DefineConstants) bin\$(Configuration)\ obj\$(Configuration)\ - Debug bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ + Debug;$(DefineConstants) bin\$(Platform)\$(Configuration)\ obj\$(Platform)\$(Configuration)\ - - HarvestPath=..\staging - - - - + + Sources + Sources + var.SourceDir + true + $(WixExtDir)\WixUIExtension.dll WixUIExtension @@ -46,14 +49,4 @@ - - - - - - \ No newline at end of file + diff --git a/doc/guide/build.hpp b/doc/guide/build.hpp index 08068eaa73..d889652e81 100644 --- a/doc/guide/build.hpp +++ b/doc/guide/build.hpp @@ -2,11 +2,25 @@ @section build_buildintro Introduction -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, -on Ubuntu, you can install mlpack with the following command: +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: + +@code +$ sudo apt-get install libmlpack-dev mlpack-bin +@endcode + +On Fedora or Red Hat(EPEL): + +@code +$ sudo dnf install mlpack-devel mlpack-bin +@endcode + +For installing only the header files and library for building C++ applications +on top of mlpack, one could use: @code $ sudo apt-get install libmlpack-dev @@ -25,7 +39,7 @@ mlpack uses CMake as a build system and allows several flexible build configuration options. One can consult any of numerous CMake tutorials for further documentation, but this tutorial should be enough to get mlpack built and installed on most Linux and UNIX-like systems (including OS X). If you want -to build mlpack on Windows, see \ref build_windows (alternatively, you can read +to build mlpack on Windows, see \ref build_windows (alternatively, you can read Keon's excellent tutorial which is based on older versions). @@ -78,7 +92,7 @@ mlpack depends on the following libraries, which need to be installed on the system and have headers present: - Armadillo >= 8.400.0 (with LAPACK support) - - Boost (math_c99, unit_test_framework, heap, spirit) >= 1.58 + - Boost (math_c99, spirit) >= 1.58 - cereal >= 1.1.2 - ensmallen >= 2.10.0 (will be downloaded if not found) @@ -95,11 +109,11 @@ For Python bindings, the following packages are required: - pandas >= 0.15.0 - pytest-runner -In Ubuntu (>= 18.04) and Debian (>= 10) all of these dependencies can be +In Ubuntu (>= 18.04) and Debian (>= 10) all of these dependencies can be installed through apt: @code -# apt-get install libboost-math-dev libboost-test-dev libcereal-dev +# apt-get install libboost-math-dev libcereal-dev libarmadillo-dev binutils-dev python3-pandas python3-numpy cython3 python3-setuptools @endcode @@ -112,18 +126,18 @@ packages: # apt-get install libensmallen-dev libstb-dev @endcode -@note For older versions of Ubuntu and Debian, Armadillo needs to be built from -source as apt installs an older version. So you need to omit +@note For older versions of Ubuntu and Debian, Armadillo needs to be built from +source as apt installs an older version. So you need to omit \c libarmadillo-dev from the code snippet above and instead use this link - to download the required file. Extract this file and follow the README in the + to download the required file. Extract this file and follow the README in the uncompressed folder to build and install Armadillo. On Fedora, Red Hat, or CentOS, these same dependencies can be obtained via dnf: @code -# dnf install boost-devel boost-test boost-math armadillo-devel binutils-devel - python3-Cython python3-setuptools python3-numpy python3-pandas ensmallen-devel +# dnf install boost-devel boost-math armadillo-devel binutils-devel + python3-Cython python3-setuptools python3-numpy python3-pandas ensmallen-devel stbi-devel cereal-devel @endcode @@ -176,9 +190,12 @@ The full list of options mlpack allows: - BUILD_WITH_COVERAGE=(ON/OFF): Build with support for code coverage tools (gcc only) (default OFF) - PYTHON_EXECUTABLE=(/path/to/python_version): Path to specific Python executable + - PYTHON_INSTALL_PREFIX=(/path/to/python/): Path to root of Python installation - JULIA_EXECUTABLE=(/path/to/julia): Path to specific Julia executable - BUILD_MARKDOWN_BINDINGS=(ON/OFF): Build Markdown bindings for website documentation (default OFF) + - BUILD_DOCS=(ON/OFF): build Doxygen documentation, if Doxygen is available + (default ON) - MATHJAX=(ON/OFF): use MathJax for generated Doxygen documentation (default OFF) - FORCE_CXX11=(ON/OFF): assume that the compiler supports C++11 instead of @@ -217,7 +234,8 @@ src/mlpack/CMakeFiles/mlpack.dir/core/optimizers/aug_lagrangian/aug_lagrangian_t @endcode It's often useful to specify \c -jN to the \c make command, which will build on -\c N processor cores. That can accelerate the build significantly. +\c N processor cores. That can accelerate the build significantly. Sometimes +using many cores may exhaust the memory so choose accordingly. You can specify individual components which you want to build, if you do not want to build everything in the library: @@ -233,11 +251,37 @@ suite. You can build this component with $ make mlpack_test @endcode -and then run all of the tests, or an individual test suite: +We use Catch2 to write our tests. +To run all tests, you can simply run: @code -$ bin/mlpack_test -$ bin/mlpack_test -t KNNTest +$ ./bin/mlpack_test +@endcode + +To run all tests in a particular file you can run: + +@code +$ ./bin/mlpack_test "[testname]" +@endcode + +where testname is the name of the test suite. +For example to run all collaborative filtering tests implemented in cf_test.cpp you can run: + +@code +./bin/mlpack_test "[CFTest]" +@endcode + +Now similarly you can run all the binding related tests using: + +@code +./bin/mlpack_test "[BindingTests]" +@endcode + +To run a single test, you can explicitly provide the name of the test; for example, +to run BinaryClassificationMetricsTest implemented in cv_test.cpp you can run the following: + +@code +./bin/mlpack_test BinaryClassificationMetricsTest @endcode If the build fails and you cannot figure out why, register an account on Github diff --git a/doc/guide/build_windows.hpp b/doc/guide/build_windows.hpp index 90fe93c0db..41eb911659 100644 --- a/doc/guide/build_windows.hpp +++ b/doc/guide/build_windows.hpp @@ -9,8 +9,13 @@ @section build_windows_intro Introduction -This tutorial will show you how to build mlpack for Windows from source, so you can -later create your own C++ applications. Before you try building mlpack, you may +This tutorial will show you how to build mlpack for Windows from source, so +you can later create your own C++ applications, using two different ways: + + - Using CMake to generate an intermeditate Visual Studio solution (`.sln`). + - @ref build_visual_studio_cmake_integration "Use Visual Studio's CMake integration to directly build from the `CMakeLists`." + +Before you try building mlpack, you may want to install mlpack using vcpkg for Windows. If you don't want to install using vcpkg, skip this section and continue with the build tutorial. @@ -78,6 +83,23 @@ system environment variables or manually set the PATH before running CMake) - Click on OpenBlas and check the mlpack project, then click Install - Once it has finished installing, close Visual Studio + Building OpenBLAS from Source + +Unfortunately, the support for building `LAPACK` and `BLAS` on Windows is quite poor, due to the need for Fortran +compiler and libraries. The easiest method to get the necessary `BLAS/LAPACK` libraries built on Windows is to +compile OpenBLAS with LLVM's `clang-cl` and `flang` to produce the required static library (`.lib`) files +compatible with the MSVC compiler. A comprehensive guide on the +compilation +of OpenBLAS for Windows can be found here. + +One could always download prebuilt `LAPACK` and `BLAS` libraries for Windows. However, there are few official +sources, and some of those libraries may require further `dll`s at runtime which may not be available in your +system. + +It you choose to build `OpenBLAS` from source, make sure that `LAPACK` functions are also built. Finally, make +sure that the `openblas.lib` library is linked in your `Armadillo` build (see below), as well as the library +path used for the CMake options `BLAS_LIBRARIES` and `LAPACK_LIBRARIES` in the mlpack CMake project. + Boost Dependency You can either get Boost via NuGet or you can download the prebuilt Windows binaries separately. @@ -110,7 +132,7 @@ compiler version, check if the Visual Studio compiler and Windows SDK are instal - Build > Build Solution - Once it has successfully finished, close Visual Studio -@section build_windows_mlpack Building mlpack +@section build_windows_mlpack Building mlpack with CMake-Generated Solution - Create a "build" directory into "C:\mlpack\mlpack\" - You can generate the project using either cmake via command line or GUI. If you prefer to use GUI, refer to the \ref build_windows_appendix "appendix" @@ -129,6 +151,96 @@ cmake -G "Visual Studio 16 2019" -A x64 -DBLAS_LIBRARIES:FILEPATH="C:/mlpack/mlp You are ready to create your first application, take a look at the @ref sample_ml_app "Sample C++ ML App" +@section build_visual_studio_cmake_integration Building mlpack with Visual Studio's CMake Integration + +This project can be directly built from the `CMakeLists.txt` with the latest version of MS Visual Studio, +given you have CMake integration via the +C++ +CMake tools for Windows. To open the CMake project with Visual Studio, select File->Open->CMake +in the top menu, followed by selecting the root `CMakeLists.txt` located in mlpack's root directory. + +In order to allow Visual Studio to configure the CMake project, the CMake configuration json will have +to be edited to provide the relevant options +shown in the `README` needed to find all the dependencies. The options that you +must provide to Visual Studio's CMake are: + + - `ARMADILLO_INCLUDE_DIR` + - `ARMADILLO_LIBRARY` + - `BOOST_ROOT` + - `CEREAL_INCLUDE_DIR` + - `BLAS_LIBRARIES` + - `LAPACK_LIBRARIES` + +The CMake configuration json can be editted in Visual Studio by right clicking the root `CMakeLists.txt` +in the project view, selecting CMake settings for mlpack and finally clicking on edit JSON. +Adding a new CMake option can be done by adding object fields with the following format to the variables +array in the `CMakeSettings.json`: + +@code +{ + "name": "options_name_string", + "value": "options_value_string", + "type" : "{BOOL|FILEPATH|PATH|STRING}" +} +@endcode + +Here is a full example of the `CMakeSettings.json`file: + +@code +{ + "configurations": [ + { + "name": "x64-Debug (default)", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "variables": [ + { + "name": "ARMADILLO_INCLUDE_DIR", + "value": "PATH/TO/CPP/DEPENDENCY/armadillo-10.1.2/include", + "type": "PATH" + }, + { + "name": "ARMADILLO_LIBBRARY", + "value": "PATH/TO/CPP/DEPENDENCY/armadillo-10.1.2/lib/armadillo.lib", + "type": "PATH" + }, + { + "name": "CEREAL_INCLUDE_DIR", + "value": "PATH/TO/CPP/DEPENDENCY/cereal-1.3.0/include", + "type": "PATH" + }, + { + "name": "BUILD_ROOT", + "value": "PATH/TO/CPP/DEPENDENCY/boost_1_66_0", + "type": "PATH" + }, + { + "name": "BOOST_INCLUDEDIR", + "value": "PATH/TO/CPP/DEPENDENCY/boost_1_66_0", + "type": "PATH" + }, + { + "name": "BLAS_LIBRARIES", + "value": "PATH/TO/CPP/DEPENDENCY/OpenBLAS/lib/openblas.lib", + "type": "PATH" + }, + { + "name": "LAPACK_LIBRARIES", + "value": "PATH/TO/CPP/DEPENDENCY/OpenBLAS/lib/openblas.lib", + "type": "PATH" + } + ] + } + ] +} +@endcode + @section build_windows_appendix Appendix If you prefer to use cmake GUI, follow these instructions: @@ -147,13 +259,6 @@ If you prefer to use cmake GUI, follow these instructions: following variables and reconfigure: - Name: `BOOST_INCLUDEDIR`; type `PATH`; value `C:/boost/` - Name: `BOOST_LIBRARYDIR`; type `PATH`; value `C:/boost/lib64-msvc-14.2` - - If Boost is still not found, try adding the following variables and - reconfigure: - - Name: `Boost_INCLUDE_DIR`; type `PATH`; value `C:/boost/` - - Name: `Boost_SERIALIZATION_LIBRARY_DEBUG`; type `FILEPATH`; value should be `C:/boost/lib64-msvc-14.2/boost_serialization-vc142-mt-gd-x64-1_71.lib` - - Name: `Boost_SERIALIZATION_LIBRARY_RELEASE`; type `FILEPATH`; value should be `C:/boost/lib64-msvc-14.2/boost_serialization-vc142-mt-x64-1_71.lib` - - Name: `Boost_UNIT_TEST_FRAMEWORK_LIBRARY_DEBUG`; type `FILEPATH`; value should be `C:/boost/lib64-msvc-14.2/boost_unit_test_framework-vc142-mt-gd-x64-1_71.lib` - - Name: `Boost_UNIT_TEST_FRAMEWORK_LIBRARY_RELEASE`; type `FILEPATH`; value should be `C:/boost/lib64-msvc-14.2/boost_unit_test_framework-vc142-mt-x64-1_71.lib` - Once CMake has configured successfully, hit "Generate" to create the `.sln` file. @section build_windows_additional_information Additional Information diff --git a/doc/guide/sample_ml_app.hpp b/doc/guide/sample_ml_app.hpp index 72a7253d5a..b8282a9ade 100644 --- a/doc/guide/sample_ml_app.hpp +++ b/doc/guide/sample_ml_app.hpp @@ -34,7 +34,6 @@ mlpack and dependencies in Release Mode). - Under Linker > Input > Additional Dependencies add: @code - C:\mlpack\mlpack-3.4.2\build\Debug\mlpack.lib - - C:\boost\boost_1_71_0\lib64-msvc-14.2\libboost_serialization-vc142-mt-gd-x64-1_71.lib @endcode - Under Build Events > Post-Build Event > Command Line add: @code diff --git a/doc/tutorials/ann/ann.txt b/doc/tutorials/ann/ann.txt index 7cdb9d1f57..43678fb84e 100644 --- a/doc/tutorials/ann/ann.txt +++ b/doc/tutorials/ann/ann.txt @@ -210,8 +210,9 @@ int main() data::Load("thyroid_test.csv", testData, true); // Split the labels from the training set and testing set respectively. - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); - arma::mat testLabels = testData.row(testData.n_rows - 1); + // Decrement the labels by 1, so they are in the range 0 to (numClasses - 1). + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; + arma::mat testLabels = testData.row(testData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); testData.shed_row(testData.n_rows - 1); @@ -246,9 +247,8 @@ int main() // Find index of max prediction for each data point and store in "prediction" for (size_t i = 0; i < predictionTemp.n_cols; ++i) { - // we add 1 to the max index, so that it matches the actual test labels. prediction(i) = arma::as_scalar(arma::find( - arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)) + 1; + arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)); } /* @@ -311,7 +311,7 @@ void RNNModel() for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.col(i).fill(value); } @@ -589,8 +589,9 @@ arma::mat trainData = dataset.submat(0, 0, dataset.n_rows - 4, dataset.n_cols - 1); // Split the data from the training set. +// Subtract 1 so the labels are the range from 0 to (numClasses - 1). arma::mat trainLabels = dataset.submat(dataset.n_rows - 3, 0, - dataset.n_rows - 1, dataset.n_cols - 1); + dataset.n_rows - 1, dataset.n_cols - 1) - 1; // Initialize the network. FFN<> model; diff --git a/src/mlpack/bindings/R/CMakeLists.txt b/src/mlpack/bindings/R/CMakeLists.txt index a6e8ee16e1..b1c7daab0a 100644 --- a/src/mlpack/bindings/R/CMakeLists.txt +++ b/src/mlpack/bindings/R/CMakeLists.txt @@ -87,6 +87,93 @@ if (BUILD_R_BINDINGS) string(TIMESTAMP PACKAGE_DATE "%Y-%m-%d") + # We need to generate an Authors@R list using every single contributor in + # COPYRIGHT.txt. That takes a little bit of processing. + file(READ "${CMAKE_SOURCE_DIR}/COPYRIGHT.txt" COPYRIGHT_TXT_CONTENTS) + string(REGEX MATCHALL " Copyright [0-9-]*, ([^\n]*)\n" CONTRIBUTORS_LIST + "${COPYRIGHT_TXT_CONTENTS}") + + # These are the authors meant to be listed as 'authors' and not + # 'contributors'. If you contributed specifically to the R bindings, you + # should probably be listed here, so if you're not, open a PR to fix it! :) + set(SPECIAL_AUTHORS "Yashwant Singh Parihar" "Ryan Curtin" "Dirk Eddelbuettel" + "James Balamuta") + + string(CONCAT AUTHORS_R "c(\n" + " person(\"Yashwant\", \"Singh Parihar\", " + "email = \"yashwantsingh.sngh@gmail.com\", " + "role = c(\"aut\", \"ctb\", \"cph\")),\n" + " person(\"Ryan\", \"Curtin\", email = \"ryan@ratml.org\", " + "role = c(\"aut\", \"ctb\", \"cph\", \"cre\")),\n" + " person(\"Dirk\", \"Eddelbuettel\", email = \"edd@debian.org\", " + "role = c(\"aut\", \"ctb\", \"cph\")),\n" + " person(\"James\", \"Balamuta\", " + "email = \"james.balamuta@gmail.com\", " + "role = c(\"aut\", \"ctb\", \"cph\")),") + foreach (CONTRIBUTOR_LINE ${CONTRIBUTORS_LIST}) + # Strip 'Copyright XXXX-YYYY, '. + string(REGEX REPLACE "^ Copyright [0-9-]*, (.*)\n$" "\\1" + CONTRIBUTOR_FILTERED "${CONTRIBUTOR_LINE}") + + # Extract the email if it exists. + string(REGEX MATCH "^[^<]*<(.*)>.*$" HAS_EMAIL "${CONTRIBUTOR_FILTERED}") + + # The first name is just the first space-delimited word. (That may not + # always be right, but we have no way to know what is a first name and last + # name and therefore must assume.) + string(REGEX REPLACE "^([^ ]*) .*$" "\\1" CONTRIBUTOR_FIRST_NAME + "${CONTRIBUTOR_FILTERED}") + + # Extracting the last name is just the rest of the tokens, but the regex is + # different depending on whether we managed to get an email. + if (HAS_EMAIL) + string(REGEX REPLACE "^[^<]*<(.*)>.*$" "\\1" CONTRIBUTOR_EMAIL + "${CONTRIBUTOR_FILTERED}") + string(REGEX MATCH "^[^ ]* (.*) <.*$" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + if (NOT CONTRIBUTOR_LAST_NAME) + set (CONTRIBUTOR_LAST_NAME "") + else () + string(REGEX REPLACE "^[^ ]* (.*) <.*$" "\\1" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + endif () + + # Skip anyone already listed as an author. + if ("${CONTRIBUTOR_FIRST_NAME} ${CONTRIBUTOR_LAST_NAME}" IN_LIST + SPECIAL_AUTHORS) + continue() + endif () + + string(CONCAT AUTHORS_R "${AUTHORS_R}\n " + "person(\"${CONTRIBUTOR_FIRST_NAME}\", \"${CONTRIBUTOR_LAST_NAME}\", " + "email = \"${CONTRIBUTOR_EMAIL}\", role = c(\"ctb\", \"cph\")),") + + else () + # No email is available. So just get the last name. + string(REGEX MATCH "^[^ ]* (.*)$" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + if (NOT CONTRIBUTOR_LAST_NAME) + set (CONTRIBUTOR_LAST_NAME "") + else () + string(REGEX REPLACE "^[^ ]* (.*)$" "\\1" CONTRIBUTOR_LAST_NAME + "${CONTRIBUTOR_FILTERED}") + endif () + + # Skip anyone already listed as an author. + if ("${CONTRIBUTOR_FIRST_NAME} ${CONTRIBUTOR_LAST_NAME}" IN_LIST + SPECIAL_AUTHORS) + continue() + endif () + + string(CONCAT AUTHORS_R "${AUTHORS_R}\n " + "person(\"${CONTRIBUTOR_FIRST_NAME}\", \"${CONTRIBUTOR_LAST_NAME}\", " + "role = c(\"ctb\", \"cph\")),") + endif () + endforeach () + # We also have to remove the final comma... + string(REGEX REPLACE ",$" "" AUTHORS_R_OUT "${AUTHORS_R}") + set(AUTHORS_R "${AUTHORS_R_OUT})") + configure_file(${CMAKE_SOURCE_DIR}/src/mlpack/bindings/R/mlpack/DESCRIPTION.in ${CMAKE_CURRENT_BINARY_DIR}/mlpack/DESCRIPTION @ONLY) @@ -136,9 +223,11 @@ if (BUILD_R_BINDINGS) "${CMAKE_CURRENT_SOURCE_DIR}/mlpack/tests/testthat.R" ) - set(LICENSE_SOURCES - "${CMAKE_SOURCE_DIR}/LICENSE.txt" - ) + # Configure the license file. + string(TIMESTAMP LICENSE_YEAR "%Y") + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/mlpack/LICENSE.in" + "${CMAKE_CURRENT_BINARY_DIR}/mlpack/LICENSE") + add_custom_target(r_copy ALL) # First we have to create all the required directories for copy. @@ -160,22 +249,22 @@ if (BUILD_R_BINDINGS) # Copy all necessary files for building package. foreach(cpp_file ${CPP_SOURCES}) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${cpp_file} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/) + add_custom_command(TARGET r_copy PRE_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different + ${cpp_file} + ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/) endforeach() foreach(r_file ${R_SOURCES}) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${r_file} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack/R/) + add_custom_command(TARGET r_copy PRE_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different + ${r_file} + ${CMAKE_CURRENT_BINARY_DIR}/mlpack/R/) endforeach() foreach(bindings_file ${BINDINGS_SOURCES}) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${bindings_file} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/mlpack/bindings/R) + add_custom_command(TARGET r_copy PRE_BUILD + COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different + ${bindings_file} + ${CMAKE_CURRENT_BINARY_DIR}/mlpack/src/mlpack/bindings/R) endforeach() add_custom_command(TARGET r_copy PRE_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different @@ -185,14 +274,6 @@ if (BUILD_R_BINDINGS) COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different ${R_TESTS_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/mlpack/tests) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E copy_if_different - ${LICENSE_SOURCES} - ${CMAKE_CURRENT_BINARY_DIR}/mlpack) - add_custom_command(TARGET r_copy PRE_BUILD - COMMAND ${CMAKE_COMMAND} ARGS -E rename - "${CMAKE_CURRENT_BINARY_DIR}/mlpack/LICENSE.txt" - "${CMAKE_CURRENT_BINARY_DIR}/mlpack/LICENSE") # This file will take care of multiple definition of functions in .cpp files. add_custom_command(TARGET r_copy PRE_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E touch @@ -229,8 +310,8 @@ if (BUILD_R_BINDINGS) # Installation script for the packagae. install(CODE "execute_process( - COMMAND R CMD INSTALL mlpack_${PACKAGE_VERSION}.tar.gz - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}" + COMMAND ${R_EXECUTABLE} CMD INSTALL mlpack_${PACKAGE_VERSION}.tar.gz + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})" ) add_dependencies(R r_build) diff --git a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in index 9183f6bb62..dc583f0dd9 100644 --- a/src/mlpack/bindings/R/mlpack/DESCRIPTION.in +++ b/src/mlpack/bindings/R/mlpack/DESCRIPTION.in @@ -2,11 +2,11 @@ Package: mlpack Title: 'Rcpp' Integration for the 'mlpack' Library Version: @PACKAGE_VERSION@ Date: @PACKAGE_DATE@ -Author: mlpack Team -Maintainer: Ryan Curtin -Description: 'mlpack' is a fast, flexible machine learning library, written - in C++, that aims to provide fast, extensible implementations of - cutting-edge machine learning algorithms. +Authors@R: @AUTHORS_R@ +Description: A fast, flexible machine learning library, written in C++, that + aims to provide fast, extensible implementations of cutting-edge + machine learning algorithms. See also Curtin et al. (2018) + . SystemRequirements: A C++11 compiler. Versions 4.8.*, 4.9.* or later of GCC will be fine. License: BSD_3_clause + file LICENSE diff --git a/src/mlpack/bindings/R/mlpack/LICENSE.in b/src/mlpack/bindings/R/mlpack/LICENSE.in new file mode 100644 index 0000000000..188ac6207d --- /dev/null +++ b/src/mlpack/bindings/R/mlpack/LICENSE.in @@ -0,0 +1,3 @@ +YEAR: ${LICENSE_YEAR} +COPYRIGHT HOLDER: mlpack Team +ORGANIZATION: mlpack diff --git a/src/mlpack/bindings/cli/CMakeLists.txt b/src/mlpack/bindings/cli/CMakeLists.txt index 1083ec41f2..4b94805fe5 100644 --- a/src/mlpack/bindings/cli/CMakeLists.txt +++ b/src/mlpack/bindings/cli/CMakeLists.txt @@ -53,7 +53,6 @@ if (BUILD_CLI_EXECUTABLES) target_link_libraries(mlpack_${name} mlpack ${ARMADILLO_LIBRARIES} - ${Boost_LIBRARIES} ${COMPILER_SUPPORT_LIBRARIES} ) # Make sure that we set BINDING_TYPE to cli so the command-line program is diff --git a/src/mlpack/bindings/go/get_printable_type_impl.hpp b/src/mlpack/bindings/go/get_printable_type_impl.hpp index 2ab074f4cd..da4df3bec9 100644 --- a/src/mlpack/bindings/go/get_printable_type_impl.hpp +++ b/src/mlpack/bindings/go/get_printable_type_impl.hpp @@ -98,17 +98,8 @@ inline std::string GetPrintableType( std::tuple>>::type*) { std::string type = "*mat.Dense"; - if (std::is_same::value) - { - if (T::is_row || T::is_col) - type = "*mat.Dense (1d)"; - } - else if (std::is_same::value) - { - type = "*mat.Dense (with ints)"; - if (T::is_row || T::is_col) - type = "*mat.Dense (1d with ints)"; - } + if (T::is_row || T::is_col) + type = "*mat.Dense (1d)"; return type; } diff --git a/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp b/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp index 74629141f5..0c57590e26 100644 --- a/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp +++ b/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp @@ -37,6 +37,11 @@ inline typename T::elem_type* GetMemory(T& m) else { arma::access::rw(m.mem_state) = 1; + // With Armadillo 10 and newer, we must set `n_alloc` to 0 so that + // Armadillo does not deallocate the memory. + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(m.n_alloc) = 0; + #endif return m.memptr(); } } diff --git a/src/mlpack/bindings/go/print_type_doc_impl.hpp b/src/mlpack/bindings/go/print_type_doc_impl.hpp index 55f79b243f..0755f60b8a 100644 --- a/src/mlpack/bindings/go/print_type_doc_impl.hpp +++ b/src/mlpack/bindings/go/print_type_doc_impl.hpp @@ -87,35 +87,15 @@ std::string PrintTypeDoc( util::ParamData& data, const typename std::enable_if::value>::type*) { - if (std::is_same::value) + if (T::is_col || T::is_row) { - if (T::is_col || T::is_row) - { - return "A 1-d gonum Matrix (that is, a Matrix where either the number" - " of rows or number of columns is 1)."; - } - else - { - return "A 2-d gonum Matrix. If the type is not already `float64`, it " - "will be converted."; - } - } - else if (std::is_same::value) - { - if (T::is_col || T::is_row) - { - return "A 1-d gonum Matrix (that is, a Matrix where either the number" - " of rows or number of columns is 1)."; - } - else - { - return "A 2-d gonum Matrix. If the type is not already `int64`, it " - "will be converted."; - } + return "A 1-d gonum Matrix (that is, a Matrix where either the number" + " of rows or number of columns is 1)."; } else { - throw std::invalid_argument("unknown matrix type " + data.cppType); + return "A 2-d gonum Matrix. If the type is not already `float64`, it " + "will be converted."; } } diff --git a/src/mlpack/bindings/julia/julia_util.cpp b/src/mlpack/bindings/julia/julia_util.cpp index d888b10296..ac8663eab0 100644 --- a/src/mlpack/bindings/julia/julia_util.cpp +++ b/src/mlpack/bindings/julia/julia_util.cpp @@ -66,7 +66,7 @@ void IO_SetParamBool(const char* paramName, bool paramValue) * Call IO::SetParam>() to set the length. */ void IO_SetParamVectorStrLen(const char* paramName, - const size_t length) + const size_t length) { IO::GetParam>(paramName).clear(); IO::GetParam>(paramName).resize(length); @@ -77,8 +77,8 @@ void IO_SetParamVectorStrLen(const char* paramName, * Call IO::SetParam>() to set an individual element. */ void IO_SetParamVectorStrStr(const char* paramName, - const char* str, - const size_t element) + const char* str, + const size_t element) { IO::GetParam>(paramName)[element] = std::string(str); @@ -88,8 +88,8 @@ void IO_SetParamVectorStrStr(const char* paramName, * Call IO::SetParam>(). */ void IO_SetParamVectorInt(const char* paramName, - int* ints, - const size_t length) + int* ints, + const size_t length) { // Create a std::vector object; unfortunately this requires copying the // vector elements. @@ -106,10 +106,10 @@ void IO_SetParamVectorInt(const char* paramName, * Call IO::SetParam(). */ void IO_SetParamMat(const char* paramName, - double* memptr, - const size_t rows, - const size_t cols, - const bool pointsAsRows) + double* memptr, + const size_t rows, + const size_t cols, + const bool pointsAsRows) { // Create the matrix as an alias. arma::mat m(memptr, arma::uword(rows), arma::uword(cols), false, true); @@ -121,10 +121,10 @@ void IO_SetParamMat(const char* paramName, * Call IO::SetParam>(). */ void IO_SetParamUMat(const char* paramName, - size_t* memptr, - const size_t rows, - const size_t cols, - const bool pointsAsRows) + size_t* memptr, + const size_t rows, + const size_t cols, + const bool pointsAsRows) { // Create the matrix as an alias. arma::Mat m(memptr, arma::uword(rows), arma::uword(cols), false, @@ -138,8 +138,8 @@ void IO_SetParamUMat(const char* paramName, * Call IO::SetParam(). */ void IO_SetParamRow(const char* paramName, - double* memptr, - const size_t cols) + double* memptr, + const size_t cols) { arma::rowvec m(memptr, arma::uword(cols), false, true); IO::GetParam(paramName) = std::move(m); @@ -150,8 +150,8 @@ void IO_SetParamRow(const char* paramName, * Call IO::SetParam>(). */ void IO_SetParamURow(const char* paramName, - size_t* memptr, - const size_t cols) + size_t* memptr, + const size_t cols) { arma::Row m(memptr, arma::uword(cols), false, true); IO::GetParam>(paramName) = std::move(m); @@ -162,8 +162,8 @@ void IO_SetParamURow(const char* paramName, * Call IO::SetParam(). */ void IO_SetParamCol(const char* paramName, - double* memptr, - const size_t rows) + double* memptr, + const size_t rows) { arma::vec m(memptr, arma::uword(rows), false, true); IO::GetParam(paramName) = std::move(m); @@ -174,8 +174,8 @@ void IO_SetParamCol(const char* paramName, * Call IO::SetParam>(). */ void IO_SetParamUCol(const char* paramName, - size_t* memptr, - const size_t rows) + size_t* memptr, + const size_t rows) { arma::Col m(memptr, arma::uword(rows), false, true); IO::GetParam>(paramName) = std::move(m); @@ -186,11 +186,11 @@ void IO_SetParamUCol(const char* paramName, * Call IO::SetParam>(). */ void IO_SetParamMatWithInfo(const char* paramName, - bool* dimensions, - double* memptr, - const size_t rows, - const size_t cols, - const bool pointsAreRows) + bool* dimensions, + double* memptr, + const size_t rows, + const size_t cols, + const bool pointsAreRows) { data::DatasetInfo d(pointsAreRows ? cols : rows); for (size_t i = 0; i < d.Dimensionality(); ++i) @@ -316,6 +316,9 @@ double* IO_GetParamMat(const char* paramName) else { arma::access::rw(mat.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(mat.n_alloc) = 0; + #endif return mat.memptr(); } } @@ -352,12 +355,14 @@ size_t* IO_GetParamUMat(const char* paramName) // Copy the memory to something that we can give back to Julia. size_t* newMem = new size_t[mat.n_elem]; arma::arrayops::copy(newMem, mat.mem, mat.n_elem); - // We believe Julia will free it. Hopefully we are right. - return newMem; + return newMem; // We believe Julia will free it. Hopefully we are right. } else { arma::access::rw(mat.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(mat.n_alloc) = 0; + #endif return mat.memptr(); } } @@ -390,6 +395,9 @@ double* IO_GetParamCol(const char* paramName) else { arma::access::rw(vec.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(vec.n_alloc) = 0; + #endif return vec.memptr(); } } @@ -418,12 +426,14 @@ size_t* IO_GetParamUCol(const char* paramName) // Copy the memory to something we can give back to Julia. size_t* newMem = new size_t[vec.n_elem]; arma::arrayops::copy(newMem, vec.mem, vec.n_elem); - // We believe Julia will free it. Hopefully we are right. - return newMem; + return newMem; // We believe Julia will free it. Hopefully we are right. } else { arma::access::rw(vec.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(vec.n_alloc) = 0; + #endif return vec.memptr(); } } @@ -456,6 +466,9 @@ double* IO_GetParamRow(const char* paramName) else { arma::access::rw(vec.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(vec.n_alloc) = 0; + #endif return vec.memptr(); } } @@ -489,6 +502,9 @@ size_t* IO_GetParamURow(const char* paramName) else { arma::access::rw(vec.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(vec.n_alloc) = 0; + #endif return vec.memptr(); } } @@ -547,6 +563,9 @@ double* IO_GetParamMatWithInfoPtr(const char* paramName) else { arma::access::rw(m.mem_state) = 1; + #if ARMA_VERSION_MAJOR >= 10 + arma::access::rw(m.n_alloc) = 0; + #endif return m.memptr(); } } diff --git a/src/mlpack/bindings/julia/print_input_processing_impl.hpp b/src/mlpack/bindings/julia/print_input_processing_impl.hpp index e30827f95d..bfb5608929 100644 --- a/src/mlpack/bindings/julia/print_input_processing_impl.hpp +++ b/src/mlpack/bindings/julia/print_input_processing_impl.hpp @@ -127,6 +127,13 @@ void PrintInputProcessing( // "type" is a reserved keyword or function. const std::string juliaName = (d.name == "type") ? "type_" : d.name; + // For a non-required argument, this gives code like the following: + // + // if !ismissing() + // push!(model_ptrs, convert(, ).ptr) + // IOSetParam("", convert(, )) + // end + // If the argument is not required, then we have to encase the code in an if. size_t extraIndent = 0; if (!d.required) @@ -137,6 +144,9 @@ void PrintInputProcessing( std::string indent(extraIndent + 2, ' '); std::string type = util::StripType(d.cppType); + std::cout << indent << "push!(modelPtrs, convert(" + << GetJuliaType::type>(d) << ", " + << juliaName << ").ptr)" << std::endl; std::cout << indent << functionName << "_internal.IOSetParam" << type << "(\"" << d.name << "\", convert(" << GetJuliaType::type>(d) << ", " diff --git a/src/mlpack/bindings/julia/print_jl.cpp b/src/mlpack/bindings/julia/print_jl.cpp index ac1f7d8b60..6b4c4f5ac0 100644 --- a/src/mlpack/bindings/julia/print_jl.cpp +++ b/src/mlpack/bindings/julia/print_jl.cpp @@ -251,6 +251,12 @@ void PrintJL(const util::BindingDetails& doc, << endl; cout << endl; + // Create the set of model pointers. + cout << " # Create the set of model pointers to avoid setting multiple " + << "finalizers." << endl; + cout << " modelPtrs = Set{Ptr{Nothing}}()" << endl; + cout << endl; + // Restore IO settings. cout << " IORestoreSettings(\"" << programName << "\")" << endl; cout << endl; diff --git a/src/mlpack/bindings/julia/print_output_processing_impl.hpp b/src/mlpack/bindings/julia/print_output_processing_impl.hpp index 1d066f7bca..5515fbf2df 100644 --- a/src/mlpack/bindings/julia/print_output_processing_impl.hpp +++ b/src/mlpack/bindings/julia/print_output_processing_impl.hpp @@ -107,7 +107,7 @@ void PrintOutputProcessing( { std::string type = util::StripType(d.cppType); std::cout << functionName << "_internal.IOGetParam" - << type << "(\"" << d.name << "\")"; + << type << "(\"" << d.name << "\", modelPtrs)"; } /** diff --git a/src/mlpack/bindings/julia/print_param_defn.hpp b/src/mlpack/bindings/julia/print_param_defn.hpp index 450b614d5b..1ee6d7d164 100644 --- a/src/mlpack/bindings/julia/print_param_defn.hpp +++ b/src/mlpack/bindings/julia/print_param_defn.hpp @@ -58,16 +58,22 @@ void PrintParamDefn( // // import ... // - // function IOGetParam(paramName::String) - // (ccall((:IOGetParamPtr, Library), - // Ptr{Nothing}, (Cstring,), paramName)) + // function IOGetParam(paramName::String, modelPtrs::Set{Ptr{Nothing}}) + // ptr = ccall((:IO_GetParamPtr, Library), + // Ptr{Nothing}, (Cstring,), paramName) + // return (ptr; finalize=!(ptr in modelPtrs)) // end // // function IOSetParam(paramName::String, model::) - // ccall((:IOSetParamPtr, Library), Nothing, + // ccall((:IO_SetParamPtr, Library), Nothing, // (Cstring, Ptr{Nothing}), paramName, model.ptr) // end // + // function Delete(ptr::Ptr{Nothing}) + // ccall((:DeletePtr, Library), Nothing, + // (Ptr{Nothing},), ptr) + // end + // // function serialize(stream::IO, model::) // buf_len = UInt[0] // buffer = ccall((:SerializePtr, Library), @@ -92,11 +98,13 @@ void PrintParamDefn( // Now, IOGetParam(). std::cout << "# Get the value of a model pointer parameter of type " << type << "." << std::endl; - std::cout << "function IOGetParam" << type << "(paramName::String)::" - << type << std::endl; - std::cout << " " << type << "(ccall((:IO_GetParam" << type + std::cout << "function IOGetParam" << type << "(paramName::String, " + << "modelPtrs::Set{Ptr{Nothing}})::" << type << std::endl; + std::cout << " ptr = ccall((:IO_GetParam" << type << "Ptr, " << programName << "Library), Ptr{Nothing}, (Cstring,), " - << "paramName))" << std::endl; + << "paramName)" << std::endl; + std::cout << " return " << type << "(ptr; finalize=!(ptr in modelPtrs))" + << std::endl; std::cout << "end" << std::endl; std::cout << std::endl; @@ -111,6 +119,15 @@ void PrintParamDefn( std::cout << "end" << std::endl; std::cout << std::endl; + // Next, Delete(). + std::cout << "# Delete an instantiated model pointer." << std::endl; + std::cout << "function Delete" << type << "(ptr::Ptr{Nothing})" + << std::endl; + std::cout << " ccall((:Delete" << type << "Ptr, " << programName + << "Library), Nothing, (Ptr{Nothing},), ptr)" << std::endl; + std::cout << "end" << std::endl; + std::cout << std::endl; + // Now the serialization functionality. std::cout << "# Serialize a model to the given stream." << std::endl; std::cout << "function serialize" << type << "(stream::IO, model::" << type diff --git a/src/mlpack/bindings/julia/tests/runtests.jl b/src/mlpack/bindings/julia/tests/runtests.jl index fe1bf189f6..bb98c54435 100644 --- a/src/mlpack/bindings/julia/tests/runtests.jl +++ b/src/mlpack/bindings/julia/tests/runtests.jl @@ -377,3 +377,20 @@ end Filesystem.rm("model.bin") end + +# Ensure that we don't accidentally free a model multiple times. +@testset "TestMultipleModelDealloc" begin + _, _, _, _, _, _, model, _, _, _, _, _, _, _ = + test_julia_binding(4.0, 12, "hello", build_model=true) + + begin + for i = 1:100 + out = test_julia_binding(4.0, 12, "hello", model_in=model, + duplicate_model=true) + end + end + + # This should free the other models. It's likely to crash if a model might be + # freed multiple times. + GC.gc() +end diff --git a/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp b/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp index 5ba385cf2b..28885ca13b 100644 --- a/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp +++ b/src/mlpack/bindings/julia/tests/test_julia_binding_main.cpp @@ -47,6 +47,8 @@ PARAM_VECTOR_IN(int, "vector_in", "Input vector of numbers.", ""); PARAM_VECTOR_IN(string, "str_vector_in", "Input vector of strings.", ""); PARAM_MODEL_IN(GaussianKernel, "model_in", "Input model.", ""); PARAM_FLAG("build_model", "If true, a model will be returned.", ""); +PARAM_FLAG("duplicate_model", "If true, return the input model as the output " + "model.", ""); PARAM_STRING_OUT("string_out", "Output string, will be 'hello2'.", "S"); PARAM_INT_OUT("int_out", "Output int, will be 13."); @@ -194,4 +196,11 @@ static void mlpackMain() IO::GetParam("model_bw_out") = IO::GetParam("model_in")->Bandwidth() * 2.0; } + + // If requested, duplicate the input model as the output model. + if (IO::HasParam("duplicate_model")) + { + IO::GetParam("model_out") = + IO::GetParam("model_in"); + } } diff --git a/src/mlpack/bindings/python/CMakeLists.txt b/src/mlpack/bindings/python/CMakeLists.txt index 180014ed3c..c36a026590 100644 --- a/src/mlpack/bindings/python/CMakeLists.txt +++ b/src/mlpack/bindings/python/CMakeLists.txt @@ -18,7 +18,6 @@ macro (post_python_bindings) -D GENERATE_CPP_IN=${CMAKE_SOURCE_DIR}/src/mlpack/bindings/python/setup.py.in -D GENERATE_CPP_OUT=${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/setup.py -D PACKAGE_VERSION="${PACKAGE_VERSION}" - -D Boost_LIBRARY_DIRS="${Boost_LIBRARY_DIRS}" -D ARMADILLO_LIBRARIES="${ARMADILLO_LIBRARIES}" -D MLPACK_LIBRARY=$ -D MLPACK_LIBDIR=$ @@ -215,14 +214,20 @@ add_custom_command(TARGET python POST_BUILD add_dependencies(python python_configured) # Configure installation script file. +if (NOT PYTHON_INSTALL_PREFIX) + set(PYTHON_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") +endif () + execute_process(COMMAND ${PYTHON_EXECUTABLE} - "${CMAKE_CURRENT_SOURCE_DIR}/print_python_version.py" "${CMAKE_INSTALL_PREFIX}" + "${CMAKE_CURRENT_SOURCE_DIR}/print_python_version.py" + "${PYTHON_INSTALL_PREFIX}" OUTPUT_VARIABLE CMAKE_PYTHON_PATH) string(STRIP "${CMAKE_PYTHON_PATH}" CMAKE_PYTHON_PATH) install(CODE "set(ENV{PYTHONPATH} ${CMAKE_PYTHON_PATH})") install(CODE "set(PYTHON_EXECUTABLE \"${PYTHON_EXECUTABLE}\")") install(CODE "set(CMAKE_BINARY_DIR \"${CMAKE_BINARY_DIR}\")") -install(CODE "set(CMAKE_INSTALL_PREFIX \"${CMAKE_INSTALL_PREFIX}\")") + +install(CODE "set(PYTHON_INSTALL_PREFIX \"${PYTHON_INSTALL_PREFIX}\")") install(CODE "execute_process(COMMAND mkdir -p $ENV{DESTDIR}${CMAKE_PYTHON_PATH})") install(SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/PythonInstall.cmake") @@ -240,14 +245,6 @@ if (WIN32) foreach (dll ${DLL_COPY_LIBS}) file(COPY ${dll} DESTINATION ${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/mlpack/) endforeach () - - # We also need to copy the boost DLLs over. - file(GLOB boost_ser_dll_files "${Boost_LIBRARY_DIRS}/*serialization*.dll") - file(COPY ${boost_ser_dll_files} DESTINATION ${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/mlpack/) - file(GLOB boost_po_dll_files "${Boost_LIBRARY_DIRS}/*program*options*.dll") - file(COPY ${boost_po_dll_files} DESTINATION ${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/mlpack/) - file(GLOB boost_utf_dll_files "${Boost_LIBRARY_DIRS}/*unit*test*framework*.dll") - file(COPY ${boost_utf_dll_files} DESTINATION ${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/mlpack/) endif () # Add a macro to build a python binding. diff --git a/src/mlpack/bindings/python/PythonInstall.cmake b/src/mlpack/bindings/python/PythonInstall.cmake index 881b48344a..6e25fb926e 100644 --- a/src/mlpack/bindings/python/PythonInstall.cmake +++ b/src/mlpack/bindings/python/PythonInstall.cmake @@ -5,13 +5,13 @@ if (DEFINED ENV{DESTDIR}) execute_process(COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/setup.py" install - --prefix=${CMAKE_INSTALL_PREFIX} --root=$ENV{DESTDIR} + --prefix=${PYTHON_INSTALL_PREFIX} --root=$ENV{DESTDIR} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/" RESULT_VARIABLE setup_res) else () execute_process(COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/setup.py" install - --prefix=${CMAKE_INSTALL_PREFIX} + --prefix=${PYTHON_INSTALL_PREFIX} WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/" RESULT_VARIABLE setup_res) endif () diff --git a/src/mlpack/bindings/python/mlpack/arma_util.hpp b/src/mlpack/bindings/python/mlpack/arma_util.hpp index af3a87d4e3..70f0dd1b3e 100644 --- a/src/mlpack/bindings/python/mlpack/arma_util.hpp +++ b/src/mlpack/bindings/python/mlpack/arma_util.hpp @@ -22,6 +22,12 @@ template void SetMemState(T& t, int state) { const_cast(t.mem_state) = state; + // If we just "released" the memory, so that the matrix does not own it, with + // Armadillo 10 we must also ensure that the matrix does not deallocate the + // memory by specifying `n_alloc = 0`. + #if ARMA_VERSION_MAJOR >= 10 + const_cast(t.n_alloc) = 0; + #endif } /** diff --git a/src/mlpack/bindings/python/mlpack/io.pxd b/src/mlpack/bindings/python/mlpack/io.pxd index 91d696d011..67961d59c9 100644 --- a/src/mlpack/bindings/python/mlpack/io.pxd +++ b/src/mlpack/bindings/python/mlpack/io.pxd @@ -38,6 +38,9 @@ cdef extern from "" namespace "mlpack" nogil: @staticmethod void ClearSettings() nogil except + + @staticmethod + void CheckInputMatrices() nogil except + + cdef extern from "" \ namespace "mlpack::util" nogil: void SetParam[T](string, T&) nogil except + diff --git a/src/mlpack/bindings/python/print_pyx.cpp b/src/mlpack/bindings/python/print_pyx.cpp index 87a412346b..6853c969da 100644 --- a/src/mlpack/bindings/python/print_pyx.cpp +++ b/src/mlpack/bindings/python/print_pyx.cpp @@ -224,6 +224,16 @@ void PrintPYX(const util::BindingDetails& doc, cout << " IO.SetPassed( '" << d.name << "')" << endl; } + // Checking the type of check_input_matrices parameter. + cout << " if not isinstance(check_input_matrices, bool):" << endl; + cout << " raise TypeError(" <<"\"'check_input_matrices\' must have type " + << "\'bool'!\")" << endl; + cout << endl; + + // Before calling mlpackMain(), we check input matrices for NaN values if needed. + cout << " if check_input_matrices:" << endl; + cout << " IO.CheckInputMatrices()" << endl; + // Call the method. cout << " # Call the mlpack program." << endl; cout << " mlpackMain()" << endl; diff --git a/src/mlpack/bindings/python/py_option.hpp b/src/mlpack/bindings/python/py_option.hpp index 98b9f91844..b3d8519f84 100644 --- a/src/mlpack/bindings/python/py_option.hpp +++ b/src/mlpack/bindings/python/py_option.hpp @@ -64,8 +64,10 @@ class PyOption data.required = required; data.input = input; data.loaded = false; - // Only "verbose" and "copy_all_inputs" will be persistent. - if (identifier == "verbose" || identifier == "copy_all_inputs") + // Only "verbose", "copy_all_inputs" and "check_input_matrices" + // will be persistent. + if (identifier == "verbose" || identifier == "copy_all_inputs" || + identifier == "check_input_matrices") data.persistent = true; else data.persistent = false; diff --git a/src/mlpack/bindings/python/setup.py.in b/src/mlpack/bindings/python/setup.py.in index a69432f539..4762f3194b 100644 --- a/src/mlpack/bindings/python/setup.py.in +++ b/src/mlpack/bindings/python/setup.py.in @@ -34,8 +34,7 @@ else: # directories with a (valid) space in the name will be given to us as '\ '; so, # in order to split these right, we first convert all spaces to ';', then # convert '\;' back to ' ', then split on ';'. -library_dirs = list(filter(None, ['${MLPACK_LIBDIR}'] + - '${Boost_LIBRARY_DIRS}'.replace(' ', ';').replace('\;', ' ').split(' '))) +library_dirs = ['${MLPACK_LIBDIR}'] # We'll link with the exact paths to each library using extra_objects, instead # of linking with 'libraries' and 'library_dirs', because of differences in diff --git a/src/mlpack/bindings/python/tests/test_python_binding.py b/src/mlpack/bindings/python/tests/test_python_binding.py index 4d8206b16c..dd67aed974 100644 --- a/src/mlpack/bindings/python/tests/test_python_binding.py +++ b/src/mlpack/bindings/python/tests/test_python_binding.py @@ -1336,5 +1336,101 @@ class TestPythonBinding(unittest.TestCase): self.assertEqual(output2['model_bw_out'], 20.0) self.assertEqual(output3['model_bw_out'], 20.0) + def testCheckInputMatricesNaN(self): + """ + Checks that an exception is thrown if the input matrix contains + NaN values. + """ + x = np.random.rand(100, 5) + a = np.random.randint(low=0, high=100) + b = np.random.randint(low=0, high=5) + x[a][b] = np.nan + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_in=x, + check_input_matrices=True)) + + x_vec = np.random.rand(100) + a = np.random.randint(low=0, high=100) + x_vec[a] = np.nan + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + row_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + col_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_and_info_in=x, + check_input_matrices=True)) + + def testCheckInputMatricesInf(self): + """ + Checks that an exception is thrown if the input matrix contains + inf values. + """ + x = np.random.rand(100, 5) + a = np.random.randint(low=0, high=100) + b = np.random.randint(low=0, high=5) + x[a][b] = np.inf + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_in=x, + check_input_matrices=True)) + + x_vec = np.random.rand(100) + a = np.random.randint(low=0, high=100) + x_vec[a] = np.inf + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + row_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + col_in=x_vec, + check_input_matrices=True)) + + self.assertRaises(RuntimeError, + lambda : test_python_binding(string_in="hello", + int_in=12, + double_in=4.0, + mat_req_in=[[1.0]], + col_req_in=[1.0], + matrix_and_info_in=x, + check_input_matrices=True)) + if __name__ == '__main__': unittest.main() diff --git a/src/mlpack/core/cv/metrics/r2_score.hpp b/src/mlpack/core/cv/metrics/r2_score.hpp index f565239ec1..e677f6e177 100644 --- a/src/mlpack/core/cv/metrics/r2_score.hpp +++ b/src/mlpack/core/cv/metrics/r2_score.hpp @@ -42,12 +42,17 @@ namespace cv { * where @f$ \bar{y} = frac{1}{y}\sum_{i=1}^{n} y_i @f$. * For example, a model having R2Score = 0.85, explains 85 \% variability of * the response data around its mean. + * + * @tparam AdjustedR2 If true, then the Adjusted R2 score will be used. + * Otherwise, the regular R2 score is used. */ + +template class R2Score { public: /** - * Run prediction and calculate the R squared error. + * Run prediction and calculate the R squared or Adjusted R squared error. * * @param model A regression model. * @param data Column-major data containing test items. diff --git a/src/mlpack/core/cv/metrics/r2_score_impl.hpp b/src/mlpack/core/cv/metrics/r2_score_impl.hpp index ef2733ff39..2859a17f9d 100644 --- a/src/mlpack/core/cv/metrics/r2_score_impl.hpp +++ b/src/mlpack/core/cv/metrics/r2_score_impl.hpp @@ -15,10 +15,11 @@ namespace mlpack { namespace cv { +template template -double R2Score::Evaluate(MLAlgorithm& model, - const DataType& data, - const ResponsesType& responses) +double R2Score::Evaluate(MLAlgorithm& model, + const DataType& data, + const ResponsesType& responses) { if (data.n_cols != responses.n_cols) { @@ -46,7 +47,18 @@ double R2Score::Evaluate(MLAlgorithm& model, if (residualSumSquared == 0.0) return totalSumSquared ? 1.0 : DBL_MIN; - return 1 - residualSumSquared / totalSumSquared; + if (AdjustedR2) + { + // Returning adjusted R-squared. + double rsq = 1 - (residualSumSquared / totalSumSquared); + return (1 - ((1 - rsq) * ((data.n_cols - 1) / + (data.n_cols - data.n_rows - 1)))); + } + else + { + // Returning R-squared + return 1 - residualSumSquared / totalSumSquared; + } } } // namespace cv diff --git a/src/mlpack/core/data/image_info_impl.hpp b/src/mlpack/core/data/image_info_impl.hpp index b0257c5d89..3040a38415 100644 --- a/src/mlpack/core/data/image_info_impl.hpp +++ b/src/mlpack/core/data/image_info_impl.hpp @@ -1,77 +1,77 @@ -/** +/** * @file core/data/image_info_impl.hpp - * @author Mehul Kumar Nirala - * - * An image information holder implementation. - * - * 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_CORE_DATA_IMAGE_INFO_IMPL_HPP -#define MLPACK_CORE_DATA_IMAGE_INFO_IMPL_HPP - -#ifdef HAS_STB // Compile this only if stb is present. - -// In case it hasn't been included yet. -#include "image_info.hpp" - -namespace mlpack { -namespace data { - -static const std::vector loadFileTypes({"jpg", "png", "tga", - "bmp", "psd", "gif", "hdr", "pic", "pnm", "jpeg"}); - -static const std::vector saveFileTypes({"jpg", "png", "tga", - "bmp", "hdr"}); - -inline bool ImageFormatSupported(const std::string& fileName, const bool save) -{ - if (save) - { - // Iterate over all supported file types that can be saved. - for (auto extension : saveFileTypes) - { - if (extension == Extension(fileName)) - return true; - } - } - else - { - // Iterate over all supported file types that can be loaded. - for (auto extension : loadFileTypes) - { - if (extension == Extension(fileName)) - return true; - } - } - - return false; -} - -} // namespace data -} // namespace mlpack - -#endif // HAS_STB. - -namespace mlpack { -namespace data { - -inline ImageInfo::ImageInfo(const size_t width, - const size_t height, - const size_t channels, - const size_t quality) : - width(width), - height(height), - channels(channels), - quality(quality) -{ - // Do nothing. -} - -} // namespace data -} // namespace mlpack - -#endif + * @author Mehul Kumar Nirala + * + * An image information holder implementation. + * + * 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_CORE_DATA_IMAGE_INFO_IMPL_HPP +#define MLPACK_CORE_DATA_IMAGE_INFO_IMPL_HPP + +#ifdef HAS_STB // Compile this only if stb is present. + +// In case it hasn't been included yet. +#include "image_info.hpp" + +namespace mlpack { +namespace data { + +static const std::vector loadFileTypes({"jpg", "png", "tga", + "bmp", "psd", "gif", "hdr", "pic", "pnm", "jpeg"}); + +static const std::vector saveFileTypes({"jpg", "png", "tga", + "bmp", "hdr"}); + +inline bool ImageFormatSupported(const std::string& fileName, const bool save) +{ + if (save) + { + // Iterate over all supported file types that can be saved. + for (auto extension : saveFileTypes) + { + if (extension == Extension(fileName)) + return true; + } + } + else + { + // Iterate over all supported file types that can be loaded. + for (auto extension : loadFileTypes) + { + if (extension == Extension(fileName)) + return true; + } + } + + return false; +} + +} // namespace data +} // namespace mlpack + +#endif // HAS_STB. + +namespace mlpack { +namespace data { + +inline ImageInfo::ImageInfo(const size_t width, + const size_t height, + const size_t channels, + const size_t quality) : + width(width), + height(height), + channels(channels), + quality(quality) +{ + // Do nothing. +} + +} // namespace data +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/data/load_image_impl.hpp b/src/mlpack/core/data/load_image_impl.hpp index 8cd9b9a2ef..9a757838b9 100644 --- a/src/mlpack/core/data/load_image_impl.hpp +++ b/src/mlpack/core/data/load_image_impl.hpp @@ -1,96 +1,96 @@ -/** +/** * @file core/data/load_image_impl.hpp - * @author Mehul Kumar Nirala - * - * An image loading utility implementation. - * - * 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_CORE_DATA_LOAD_IMAGE_IMPL_HPP -#define MLPACK_CORE_DATA_LOAD_IMAGE_IMPL_HPP - -// In case it hasn't been included yet. -#include "load.hpp" - -namespace mlpack { -namespace data { - -// Image loading API. -template -bool Load(const std::string& filename, - arma::Mat& matrix, - ImageInfo& info, - const bool fatal) -{ - Timer::Start("loading_image"); - - // STB loads into unsigned char matrices, so we may have to convert once - // loaded. - arma::Mat tempMatrix; - const bool result = LoadImage(filename, tempMatrix, info, fatal); - - // If fatal is true, then the program will have already thrown an exception. - if (!result) - { - Timer::Stop("loading_image"); - return false; - } - - matrix = arma::conv_to>::from(tempMatrix); - Timer::Stop("loading_image"); - return true; -} - -// Image loading API for multiple files. -template -bool Load(const std::vector& files, - arma::Mat& matrix, - ImageInfo& info, - const bool fatal) -{ - if (files.size() == 0) - { - std::ostringstream oss; - oss << "Load(): vector of image files is empty." << std::endl; - - if (fatal) - Log::Fatal << oss.str(); - else - Log::Warn << oss.str(); - - return false; - } - - arma::Mat img; - bool status = LoadImage(files[0], img, info, fatal); - - if (!status) - return false; - - // Decide matrix dimension using the image height and width. - arma::Mat tmpMatrix( - info.Width() * info.Height() * info.Channels(), files.size()); - tmpMatrix.col(0) = img; - - for (size_t i = 1; i < files.size() ; ++i) - { - arma::Mat colImg(tmpMatrix.colptr(i), tmpMatrix.n_rows, 1, - false, true); - status = LoadImage(files[i], colImg, info, fatal); - - if (!status) - return false; - } - - matrix = arma::conv_to>::from(tmpMatrix); - return true; -} - -} // namespace data -} // namespace mlpack - -#endif + * @author Mehul Kumar Nirala + * + * An image loading utility implementation. + * + * 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_CORE_DATA_LOAD_IMAGE_IMPL_HPP +#define MLPACK_CORE_DATA_LOAD_IMAGE_IMPL_HPP + +// In case it hasn't been included yet. +#include "load.hpp" + +namespace mlpack { +namespace data { + +// Image loading API. +template +bool Load(const std::string& filename, + arma::Mat& matrix, + ImageInfo& info, + const bool fatal) +{ + Timer::Start("loading_image"); + + // STB loads into unsigned char matrices, so we may have to convert once + // loaded. + arma::Mat tempMatrix; + const bool result = LoadImage(filename, tempMatrix, info, fatal); + + // If fatal is true, then the program will have already thrown an exception. + if (!result) + { + Timer::Stop("loading_image"); + return false; + } + + matrix = arma::conv_to>::from(tempMatrix); + Timer::Stop("loading_image"); + return true; +} + +// Image loading API for multiple files. +template +bool Load(const std::vector& files, + arma::Mat& matrix, + ImageInfo& info, + const bool fatal) +{ + if (files.size() == 0) + { + std::ostringstream oss; + oss << "Load(): vector of image files is empty." << std::endl; + + if (fatal) + Log::Fatal << oss.str(); + else + Log::Warn << oss.str(); + + return false; + } + + arma::Mat img; + bool status = LoadImage(files[0], img, info, fatal); + + if (!status) + return false; + + // Decide matrix dimension using the image height and width. + arma::Mat tmpMatrix( + info.Width() * info.Height() * info.Channels(), files.size()); + tmpMatrix.col(0) = img; + + for (size_t i = 1; i < files.size() ; ++i) + { + arma::Mat colImg(tmpMatrix.colptr(i), tmpMatrix.n_rows, 1, + false, true); + status = LoadImage(files[i], colImg, info, fatal); + + if (!status) + return false; + } + + matrix = arma::conv_to>::from(tmpMatrix); + return true; +} + +} // namespace data +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/math/columns_to_blocks.hpp b/src/mlpack/core/math/columns_to_blocks.hpp index de482ee573..a6d7e3a391 100644 --- a/src/mlpack/core/math/columns_to_blocks.hpp +++ b/src/mlpack/core/math/columns_to_blocks.hpp @@ -66,10 +66,10 @@ namespace math { * @code * // This matrix has two columns. * arma::mat input; - * input << -1.0000 << 0.1429 << arma::endr - * << -0.7143 << 0.4286 << arma::endr - * << -0.4286 << 0.7143 << arma::endr - * << -0.1429 << 1.0000 << arma::endr; + * input = { { -1.0000, 0.1429 }, + * { -0.7143, 0.4286 }, + * { -0.4286, 0.7143 }, + * { -0.1429, 1.0000 } }; * * arma::mat output; * ColumnsToBlocks ctb(1, 2); diff --git a/src/mlpack/core/tree/ballbound.hpp b/src/mlpack/core/tree/ballbound.hpp index 0d6633f7ca..e1a8674f03 100644 --- a/src/mlpack/core/tree/ballbound.hpp +++ b/src/mlpack/core/tree/ballbound.hpp @@ -81,6 +81,9 @@ class BallBound //! Move constructor: take possession of another bound. BallBound(BallBound&& other); + //! Move assignment operator. + BallBound& operator=(BallBound&& other); + //! Destructor to release allocated memory. ~BallBound(); diff --git a/src/mlpack/core/tree/ballbound_impl.hpp b/src/mlpack/core/tree/ballbound_impl.hpp index 59ef8bffc3..59cc86a5ea 100644 --- a/src/mlpack/core/tree/ballbound_impl.hpp +++ b/src/mlpack/core/tree/ballbound_impl.hpp @@ -71,10 +71,14 @@ template BallBound& BallBound::operator=( const BallBound& other) { - radius = other.radius; - center = other.center; - metric = other.metric; - ownsMetric = false; + if (this != &other) + { + radius = other.radius; + center = other.center; + metric = other.metric; + ownsMetric = false; + } + return *this; } //! Move constructor. @@ -92,6 +96,26 @@ BallBound::BallBound(BallBound&& other) : other.ownsMetric = false; } +//! Move assignment operator. +template +BallBound& BallBound::operator=( + BallBound&& other) +{ + if (this != &other) + { + radius = other.radius; + center = std::move(other.center); + metric = other.metric; + ownsMetric = other.ownsMetric; + + other.radius = 0.0; + other.center = VecType(); + other.metric = nullptr; + other.ownsMetric = false; + } + return *this; +} + //! Destructor to release allocated memory. template BallBound::~BallBound() diff --git a/src/mlpack/core/tree/hollow_ball_bound.hpp b/src/mlpack/core/tree/hollow_ball_bound.hpp index d8b65dcf87..d699eab693 100644 --- a/src/mlpack/core/tree/hollow_ball_bound.hpp +++ b/src/mlpack/core/tree/hollow_ball_bound.hpp @@ -86,6 +86,9 @@ class HollowBallBound //! Move constructor: take possession of another bound. HollowBallBound(HollowBallBound&& other); + //! Move assignment operator. + HollowBallBound& operator=(HollowBallBound&& other); + //! Destructor to release allocated memory. ~HollowBallBound(); diff --git a/src/mlpack/core/tree/hollow_ball_bound_impl.hpp b/src/mlpack/core/tree/hollow_ball_bound_impl.hpp index b8446ec350..8ccd06225c 100644 --- a/src/mlpack/core/tree/hollow_ball_bound_impl.hpp +++ b/src/mlpack/core/tree/hollow_ball_bound_impl.hpp @@ -80,15 +80,17 @@ template HollowBallBound& HollowBallBound:: operator=(const HollowBallBound& other) { - if (ownsMetric) - delete metric; - - radii = other.radii; - center = other.center; - hollowCenter = other.hollowCenter; - metric = other.metric; - ownsMetric = false; + if (this != &other) + { + if (ownsMetric) + delete metric; + radii = other.radii; + center = other.center; + hollowCenter = other.hollowCenter; + metric = other.metric; + ownsMetric = false; + } return *this; } @@ -111,6 +113,29 @@ HollowBallBound::HollowBallBound( other.ownsMetric = false; } +//! Move assignment operator. +template +HollowBallBound& HollowBallBound:: +operator=(HollowBallBound&& other) +{ + if (this != &other) + { + radii = other.radii; + center = std::move(other.center); + hollowCenter = std::move(other.hollowCenter); + metric = other.metric; + ownsMetric = other.ownsMetric; + + other.radii.Hi() = 0.0; + other.radii.Lo() = 0.0; + other.center = arma::Col(); + other.hollowCenter = arma::Col(); + other.metric = nullptr; + other.ownsMetric = false; + } + return *this; +} + //! Destructor to release allocated memory. template HollowBallBound::~HollowBallBound() diff --git a/src/mlpack/core/tree/hrectbound.hpp b/src/mlpack/core/tree/hrectbound.hpp index 1d15fe6582..6b8ef6c69a 100644 --- a/src/mlpack/core/tree/hrectbound.hpp +++ b/src/mlpack/core/tree/hrectbound.hpp @@ -73,12 +73,16 @@ class HRectBound //! Copy constructor; necessary to prevent memory leaks. HRectBound(const HRectBound& other); + //! Same as copy constructor; necessary to prevent memory leaks. HRectBound& operator=(const HRectBound& other); //! Move constructor: take possession of another bound's information. HRectBound(HRectBound&& other); + //! Move assignment operator. + HRectBound& operator=(HRectBound&& other); + //! Destructor: clean up memory. ~HRectBound(); diff --git a/src/mlpack/core/tree/hrectbound_impl.hpp b/src/mlpack/core/tree/hrectbound_impl.hpp index 2b73eb020a..491e25fed6 100644 --- a/src/mlpack/core/tree/hrectbound_impl.hpp +++ b/src/mlpack/core/tree/hrectbound_impl.hpp @@ -103,6 +103,26 @@ inline HRectBound::HRectBound( other.minWidth = 0.0; } +/** + * Move assignment operator. + */ +template +inline HRectBound& +HRectBound::operator=( + HRectBound&& other) +{ + if (this != &other) + { + bounds = other.bounds; + minWidth = other.minWidth; + dim = other.dim; + other.dim = 0; + other.bounds = nullptr; + other.minWidth = 0.0; + } + return *this; +} + /** * Destructor: clean up memory. */ diff --git a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp index 405188a4f6..a21dd1af3d 100644 --- a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp +++ b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value.hpp @@ -177,10 +177,18 @@ class DiscreteHilbertValue /** * Copy the local Hilbert value's pointer. * - * @param val The DiscreteHilbertValue object from which the dataset + * @param other The DiscreteHilbertValue object from which the dataset * will be copied. */ - DiscreteHilbertValue& operator=(const DiscreteHilbertValue& val); + DiscreteHilbertValue& operator=(const DiscreteHilbertValue& other); + + /** + * Move the local Hilbert object. + * + * @param other The DiscreteHilbertValue object from which the dataset + * will be copied. + */ + DiscreteHilbertValue& operator=(DiscreteHilbertValue&& other); /** * Nullify the localHilbertValues pointer in order to prevent an invalid free. diff --git a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp index c4baa38a90..bd3c9cb87e 100644 --- a/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/discrete_hilbert_value_impl.hpp @@ -434,22 +434,43 @@ RemoveNode(TreeType* node, const size_t nodeIndex) template DiscreteHilbertValue& DiscreteHilbertValue:: -operator=(const DiscreteHilbertValue& val) +operator=(const DiscreteHilbertValue& other) { - if (this == &val) + if (this == &other) return *this; if (ownsLocalHilbertValues) delete localHilbertValues; localHilbertValues = const_cast* > - (val.LocalHilbertValues()); + (other.LocalHilbertValues()); ownsLocalHilbertValues = false; - numValues = val.NumValues(); + numValues = other.NumValues(); return *this; } +template +DiscreteHilbertValue& DiscreteHilbertValue:: +operator=(DiscreteHilbertValue&& other) +{ + if (this != &other) + { + localHilbertValues = other.localHilbertValues; + ownsLocalHilbertValues = other.ownsLocalHilbertValues; + numValues = other.numValues; + valueToInsert = other.valueToInsert; + ownsValueToInsert = other.ownsValueToInsert; + + other.localHilbertValues = nullptr; + other.ownsLocalHilbertValues = false; + other.numValues = 0; + other.valueToInsert = nullptr; + other.ownsValueToInsert = false; + } + return *this; +} + template void DiscreteHilbertValue::NullifyData() { diff --git a/src/mlpack/core/util/io.cpp b/src/mlpack/core/util/io.cpp index 0c8703c406..904a155cc0 100644 --- a/src/mlpack/core/util/io.cpp +++ b/src/mlpack/core/util/io.cpp @@ -267,3 +267,32 @@ void IO::ClearSettings() GetSingleton().aliases = persistentAliases; GetSingleton().functionMap = persistentFunctions; } + +void IO::CheckInputMatrices() +{ + typedef typename std::tuple TupleType; + std::map::iterator itr; + + for (itr = IO::Parameters().begin(); itr != IO::Parameters().end(); ++itr) + { + std::string paramName = itr->first; + std::string paramType = itr->second.cppType; + if (paramType == "arma::mat") + { + IO::CheckInputMatrix(IO::GetParam(paramName), paramName); + } + else if (paramType == "arma::vec") + { + IO::CheckInputMatrix(IO::GetParam(paramName), paramName); + } + else if (paramType == "arma::rowvec") + { + IO::CheckInputMatrix(IO::GetParam(paramName), paramName); + } + else if (paramType == "std::tuple") + { + IO::CheckInputMatrix( + std::get<1>(IO::GetParam(paramName)), paramName); + } + } +} diff --git a/src/mlpack/core/util/io.hpp b/src/mlpack/core/util/io.hpp index 427142c897..aa9d71c16f 100644 --- a/src/mlpack/core/util/io.hpp +++ b/src/mlpack/core/util/io.hpp @@ -219,6 +219,15 @@ class IO template static T& GetRawParam(const std::string& identifier); + /** + * Utility function for CheckInputMatrices(). + * + * @param matrix Matrix to check. + * @param identifier Name of the parameter in question. + */ + template + static void CheckInputMatrix(const T& matrix, const std::string& identifier); + /** * Given two (matrix) parameters, ensure that the first is an in-place copy of * the second. This will generally do nothing (as the bindings already do @@ -285,6 +294,11 @@ class IO */ static void ClearSettings(); + /** + * Checks all input matrices for NaN and inf values, exits if found any. + */ + static void CheckInputMatrices(); + private: //! Convenience map from alias values to names. std::map aliases; diff --git a/src/mlpack/core/util/io_impl.hpp b/src/mlpack/core/util/io_impl.hpp index feb892325c..e7407efd7f 100644 --- a/src/mlpack/core/util/io_impl.hpp +++ b/src/mlpack/core/util/io_impl.hpp @@ -145,6 +145,18 @@ T& IO::GetRawParam(const std::string& identifier) } } +template +void IO::CheckInputMatrix(const T& matrix, const std::string& identifier) +{ + std::string errMsg1 = "The input " + identifier + " has NaN values."; + std::string errMsg2 = "The input " + identifier + " has inf values."; + + if (matrix.has_nan()) + Log::Fatal << errMsg1 << std::endl; + if (matrix.has_inf()) + Log::Fatal << errMsg2 << std::endl; +} + } // namespace mlpack #endif diff --git a/src/mlpack/core/util/mlpack_main.hpp b/src/mlpack/core/util/mlpack_main.hpp index 06f6f8b060..34f8689e1a 100644 --- a/src/mlpack/core/util/mlpack_main.hpp +++ b/src/mlpack/core/util/mlpack_main.hpp @@ -230,6 +230,8 @@ PARAM_FLAG("copy_all_inputs", "If specified, all input parameters will be deep" " copied before the method is run. This is useful for debugging problems " "where the input parameters are being modified by the algorithm, but can " "slow down the code.", ""); +PARAM_FLAG("check_input_matrices", "If specified, the input matrix is checked for" + " NaN and inf values; an exception is thrown if any are found.", ""); // Nothing else needs to be defined---the binding will use mlpackMain() as-is. diff --git a/src/mlpack/core/util/param.hpp b/src/mlpack/core/util/param.hpp index 208ca64b2f..fc809c5b6e 100644 --- a/src/mlpack/core/util/param.hpp +++ b/src/mlpack/core/util/param.hpp @@ -1015,7 +1015,9 @@ using DatasetInfo = DatasetMapper; */ #define TUPLE_TYPE std::tuple #define PARAM_MATRIX_AND_INFO_IN(ID, DESC, ALIAS) \ - PARAM_IN(TUPLE_TYPE, ID, DESC, ALIAS, TUPLE_TYPE(), false) + PARAM(TUPLE_TYPE, ID, DESC, ALIAS, \ + "std::tuple", false, true, true, \ + TUPLE_TYPE()) /** * Define an input model. From the command line, the user can specify the file @@ -1207,11 +1209,44 @@ using DatasetInfo = DatasetMapper; PARAM_IN(std::vector, ID, DESC, ALIAS, std::vector(), true); /** - * Define an input parameter. Don't use this function; use the other ones above - * that call it. Note that we are using the __LINE__ macro for naming these - * actual parameters when __COUNTER__ does not exist, which is a bit of an ugly - * hack... but this is the preprocessor, after all. We don't have much choice - * other than ugliness. + * Defining useful macros using PARAM macro defined later. + */ +#define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \ + PARAM(T, ID, DESC, ALIAS, #T, REQ, true, false, DEF); + +#define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \ + PARAM(T, ID, DESC, ALIAS, #T, REQ, false, false, DEF); + +#define PARAM_MATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::mat, ID, DESC, ALIAS, "arma::mat", REQ, IN, \ + TRANS, arma::mat()); + +#define PARAM_UMATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::Mat, ID, DESC, ALIAS, "arma::Mat", \ + REQ, IN, TRANS, arma::Mat()); + +#define PARAM_COL(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::vec, ID, DESC, ALIAS, "arma::vec", REQ, IN, TRANS, \ + arma::vec()); + +#define PARAM_UCOL(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::Col, ID, DESC, ALIAS, "arma::Col", \ + REQ, IN, TRANS, arma::Col()); + +#define PARAM_ROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::rowvec, ID, DESC, ALIAS, "arma::rowvec", REQ, IN, \ + TRANS, arma::rowvec()); + +#define PARAM_UROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ + PARAM(arma::Row, ID, DESC, ALIAS, "arma::Row", \ + REQ, IN, TRANS, arma::Row()); + +/** + * Define the PARAM(), PARAM_MODEL() macro. Don't use this function; + * use the other ones above that call it. Note that we are using the __LINE__ + * macro for naming these actual parameters when __COUNTER__ does not exist, + * which is a bit of an ugly hack... but this is the preprocessor, after all. + * We don't have much choice other than ugliness. * * @param T Type of the parameter. * @param ID Name of the parameter. @@ -1223,51 +1258,10 @@ using DatasetInfo = DatasetMapper; * @param REQ Whether or not parameter is required (boolean value). */ #ifdef __COUNTER__ - #define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \ + #define PARAM(T, ID, DESC, ALIAS, NAME, REQ, IN, TRANS, DEF) \ static mlpack::util::Option \ JOIN(io_option_dummy_object_in_, __COUNTER__) \ - (DEF, ID, DESC, ALIAS, #T, REQ, true, false, testName); - - #define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_object_out_, __COUNTER__) \ - (DEF, ID, DESC, ALIAS, #T, REQ, false, false, testName); - - #define PARAM_MATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_matrix_, __COUNTER__) \ - (arma::mat(), ID, DESC, ALIAS, "arma::mat", \ - REQ, IN, !TRANS, testName); - - #define PARAM_UMATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_umatrix_, __COUNTER__) \ - (arma::Mat(), ID, DESC, ALIAS, "arma::Mat", \ - REQ, IN, !TRANS, testName); - - #define PARAM_COL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_col_, __COUNTER__) \ - (arma::vec(), ID, DESC, ALIAS, "arma::vec", \ - REQ, IN, !TRANS, testName); - - #define PARAM_UCOL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_ucol_, __COUNTER__) \ - (arma::Col(), ID, DESC, ALIAS, "arma::Col", \ - REQ, IN, !TRANS, testName); - - #define PARAM_ROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_row_, __COUNTER__) \ - (arma::rowvec(), ID, DESC, ALIAS, "arma::rowvec", \ - REQ, IN, !TRANS, testName); - - #define PARAM_UROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_urow_, __COUNTER__) \ - (arma::Row(), ID, DESC, ALIAS, "arma::Row", \ - REQ, IN, !TRANS, testName); + (DEF, ID, DESC, ALIAS, NAME, REQ, IN, !TRANS, testName); // There are no uses of required models, so that is not an option to this // macro (it would be easy to add). @@ -1280,51 +1274,10 @@ using DatasetInfo = DatasetMapper; // don't think we can absolutely guarantee success, but it should be "good // enough". We use the __LINE__ macro and the type of the parameter to try // and get a good guess at something unique. - #define PARAM_IN(T, ID, DESC, ALIAS, DEF, REQ) \ + #define PARAM(T, ID, DESC, ALIAS, NAME, REQ, IN, TRANS, DEF) \ static mlpack::util::Option \ JOIN(JOIN(io_option_dummy_object_in_, __LINE__), opt) \ - (DEF, ID, DESC, ALIAS, #T, REQ, true, false, testName); - - #define PARAM_OUT(T, ID, DESC, ALIAS, DEF, REQ) \ - static mlpack::util::Option \ - JOIN(JOIN(io_option_dummy_object_out_, __LINE__), opt) \ - (DEF, ID, DESC, ALIAS, #T, REQ, false, false, testName); - - #define PARAM_MATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(JOIN(io_option_dummy_object_matrix_, __LINE__), opt) \ - (arma::mat(), ID, DESC, ALIAS, "arma::mat", REQ, IN, !TRANS, \ - testName); - - #define PARAM_UMATRIX(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(JOIN(io_option_dummy_object_umatrix_, __LINE__), opt) \ - (arma::Mat(), ID, DESC, ALIAS, "arma::Mat", REQ, IN, \ - !TRANS, testName); - - #define PARAM_COL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_object_col_, __LINE__) \ - (arma::vec(), ID, DESC, ALIAS, "arma::vec", REQ, IN, !TRANS, \ - testName); - - #define PARAM_UCOL(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_object_ucol_, __LINE__) \ - (arma::Col(), ID, DESC, ALIAS, "arma::Col", REQ, IN, \ - !TRANS, testName); - - #define PARAM_ROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option \ - JOIN(io_option_dummy_object_row_, __LINE__) \ - (arma::rowvec(), ID, DESC, ALIAS, "arma::rowvec", REQ, IN, !TRANS, \ - testName); - - #define PARAM_UROW(ID, DESC, ALIAS, REQ, TRANS, IN) \ - static mlpack::util::Option> \ - JOIN(io_option_dummy_object_urow_, __LINE__) \ - (arma::Row(), ID, DESC, ALIAS, "arma::Row", REQ, IN, \ - !TRANS, testName); + (DEF, ID, DESC, ALIAS, NAME, REQ, IN, !TRANS, testName); #define PARAM_MODEL(TYPE, ID, DESC, ALIAS, REQ, IN) \ static mlpack::util::Option \ diff --git a/src/mlpack/core/util/param_checks.hpp b/src/mlpack/core/util/param_checks.hpp index a9180a4816..c1ac39aeea 100644 --- a/src/mlpack/core/util/param_checks.hpp +++ b/src/mlpack/core/util/param_checks.hpp @@ -43,11 +43,14 @@ namespace util { * @param fatal If true, output goes to Log::Fatal instead of Log::Warn and an * exception is thrown. * @param customErrorMessage Error message to append. + * @param allowNone If true, then no error message will be thrown if none of the + * parameters in the constraints were passed. */ void RequireOnlyOnePassed( const std::vector& constraints, const bool fatal = true, - const std::string& customErrorMessage = ""); + const std::string& customErrorMessage = "", + const bool allowNone = false); /** * Require that at least one of the given parameters in the constraints set was diff --git a/src/mlpack/core/util/param_checks_impl.hpp b/src/mlpack/core/util/param_checks_impl.hpp index be88c8a3e9..8562e1341f 100644 --- a/src/mlpack/core/util/param_checks_impl.hpp +++ b/src/mlpack/core/util/param_checks_impl.hpp @@ -21,7 +21,8 @@ namespace util { inline void RequireOnlyOnePassed( const std::vector& constraints, const bool fatal, - const std::string& errorMessage) + const std::string& errorMessage, + const bool allowNone) { if (BINDING_IGNORE_CHECK(constraints)) return; @@ -57,7 +58,7 @@ inline void RequireOnlyOnePassed( stream << "; " << errorMessage; stream << "!" << std::endl; } - else if (set == 0) + else if (set == 0 && !allowNone) { stream << (fatal ? "Must " : "Should "); diff --git a/src/mlpack/core/util/prefixedoutstream_impl.hpp b/src/mlpack/core/util/prefixedoutstream_impl.hpp index 601c81c4fc..3cb9eea353 100644 --- a/src/mlpack/core/util/prefixedoutstream_impl.hpp +++ b/src/mlpack/core/util/prefixedoutstream_impl.hpp @@ -178,8 +178,7 @@ PrefixedOutStream::BaseLogic(const T& val) if (maxVal == 0.0) maxVal = 1; - int maxLog = log10(maxVal); - maxLog = (maxLog > 0) ? floor(maxLog) + 1 : 1; + const int maxLog = int(log10(maxVal)) + 1; const int padding = 4; convert.width(convert.precision() + maxLog + padding); printVal.raw_print(convert); diff --git a/src/mlpack/methods/CMakeLists.txt b/src/mlpack/methods/CMakeLists.txt index d548d9c769..4268a2e1bc 100644 --- a/src/mlpack/methods/CMakeLists.txt +++ b/src/mlpack/methods/CMakeLists.txt @@ -10,7 +10,6 @@ set(DIRS block_krylov_svd cf dbscan - decision_stump decision_tree det emst diff --git a/src/mlpack/methods/adaboost/adaboost.hpp b/src/mlpack/methods/adaboost/adaboost.hpp index 36d43fd5df..493f3f8d4e 100644 --- a/src/mlpack/methods/adaboost/adaboost.hpp +++ b/src/mlpack/methods/adaboost/adaboost.hpp @@ -71,7 +71,7 @@ namespace adaboost { * @endcode * * For more information on and examples of weak learners, see - * perceptron::Perceptron<> and decision_stump::DecisionStump<>. + * perceptron::Perceptron<> and tree::ID3DecisionStump. * * @tparam MatType Data matrix type (i.e. arma::mat or arma::sp_mat). * @tparam WeakLearnerType Type of weak learner to use. diff --git a/src/mlpack/methods/adaboost/adaboost_model.cpp b/src/mlpack/methods/adaboost/adaboost_model.cpp index a48659b4bd..d71b857d74 100644 --- a/src/mlpack/methods/adaboost/adaboost_model.cpp +++ b/src/mlpack/methods/adaboost/adaboost_model.cpp @@ -72,19 +72,40 @@ AdaBoostModel::AdaBoostModel(AdaBoostModel&& other) : //! Copy assignment operator. AdaBoostModel& AdaBoostModel::operator=(const AdaBoostModel& other) { - mappings = other.mappings; - weakLearnerType = other.weakLearnerType; + if (this != &other) + { + mappings = other.mappings; + weakLearnerType = other.weakLearnerType; - delete dsBoost; - dsBoost = (other.dsBoost == NULL) ? NULL : - new AdaBoost(*other.dsBoost); + delete dsBoost; + dsBoost = (other.dsBoost == NULL) ? NULL : + new AdaBoost(*other.dsBoost); - delete pBoost; - pBoost = (other.pBoost == NULL) ? NULL : - new AdaBoost>(*other.pBoost); + delete pBoost; + pBoost = (other.pBoost == NULL) ? NULL : + new AdaBoost>(*other.pBoost); - dimensionality = other.dimensionality; + dimensionality = other.dimensionality; + } + return *this; +} +//! Move assignment operator. +AdaBoostModel& AdaBoostModel::operator=(AdaBoostModel&& other) +{ + if (this != &other) + { + mappings = std::move(other.mappings); + weakLearnerType = other.weakLearnerType; + + dsBoost = other.dsBoost; + other.dsBoost = nullptr; + + pBoost = other.pBoost; + other.pBoost = nullptr; + + dimensionality = other.dimensionality; + } return *this; } diff --git a/src/mlpack/methods/adaboost/adaboost_model.hpp b/src/mlpack/methods/adaboost/adaboost_model.hpp index e8dcac3a82..36743c4e18 100644 --- a/src/mlpack/methods/adaboost/adaboost_model.hpp +++ b/src/mlpack/methods/adaboost/adaboost_model.hpp @@ -61,6 +61,9 @@ class AdaBoostModel //! Copy assignment operator. AdaBoostModel& operator=(const AdaBoostModel& other); + //! Move assignment operator. + AdaBoostModel& operator=(AdaBoostModel&& other); + //! Clean up memory. ~AdaBoostModel(); diff --git a/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp b/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp index e3a030836d..78eb24108e 100644 --- a/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp +++ b/src/mlpack/methods/amf/termination_policies/complete_incremental_termination.hpp @@ -36,7 +36,8 @@ class CompleteIncrementalTermination */ CompleteIncrementalTermination( TerminationPolicy tPolicy = TerminationPolicy()) : - tPolicy(tPolicy) { } + tPolicy(tPolicy), incrementalIndex(0), iteration(0) + { /* Nothing to do here. */ } /** * Initializes the termination policy before stating the factorization. @@ -119,4 +120,3 @@ class CompleteIncrementalTermination } // namespace mlpack #endif // MLPACK_METHODS_AMF_COMPLETE_INCREMENTAL_TERMINATION_HPP - diff --git a/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp b/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp index 62b112b061..5646b0d205 100644 --- a/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp +++ b/src/mlpack/methods/amf/termination_policies/incomplete_incremental_termination.hpp @@ -35,7 +35,8 @@ class IncompleteIncrementalTermination */ IncompleteIncrementalTermination( TerminationPolicy tPolicy = TerminationPolicy()) : - tPolicy(tPolicy) { } + tPolicy(tPolicy), incrementalIndex(0), iteration(0) + { /* Nothing to do here. */ } /** * Initializes the termination policy before stating the factorization. diff --git a/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp b/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp index 970b24289f..86631e32ce 100644 --- a/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp +++ b/src/mlpack/methods/amf/termination_policies/simple_residue_termination.hpp @@ -40,8 +40,16 @@ class SimpleResidueTermination * @param maxIterations Maximum number of iterations. */ SimpleResidueTermination(const double minResidue = 1e-5, - const size_t maxIterations = 10000) - : minResidue(minResidue), maxIterations(maxIterations) { } + const size_t maxIterations = 10000) : + minResidue(minResidue), + maxIterations(maxIterations), + residue(0.0), + iteration(0), + nm(0), + normOld(0) + { + // Nothing to do here. + } /** * Initializes the termination policy before stating the factorization. diff --git a/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp b/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp index 4ab1c0d610..37b7ab8c0a 100644 --- a/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp +++ b/src/mlpack/methods/amf/update_rules/svd_complete_incremental_learning.hpp @@ -56,7 +56,7 @@ class SVDCompleteIncrementalLearning SVDCompleteIncrementalLearning(double u = 0.0001, double kw = 0, double kh = 0) - : u(u), kw(kw), kh(kh) + : u(u), kw(kw), kh(kh), currentUserIndex(0), currentItemIndex(0) { // Nothing to do. } @@ -172,7 +172,7 @@ class SVDCompleteIncrementalLearning SVDCompleteIncrementalLearning(double u = 0.01, double kw = 0, double kh = 0) - : u(u), kw(kw), kh(kh), it(NULL) + : u(u), kw(kw), kh(kh), it(NULL), m(0), n(0), isStart(false) {} ~SVDCompleteIncrementalLearning() diff --git a/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp b/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp index 0082824129..9880ea2945 100644 --- a/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp +++ b/src/mlpack/methods/amf/update_rules/svd_incomplete_incremental_learning.hpp @@ -53,7 +53,7 @@ class SVDIncompleteIncrementalLearning SVDIncompleteIncrementalLearning(double u = 0.001, double kw = 0, double kh = 0) - : u(u), kw(kw), kh(kh) + : u(u), kw(kw), kh(kh), currentUserIndex(0) { // Nothing to do. } diff --git a/src/mlpack/methods/ann/CMakeLists.txt b/src/mlpack/methods/ann/CMakeLists.txt index 3c8236809c..8888113548 100644 --- a/src/mlpack/methods/ann/CMakeLists.txt +++ b/src/mlpack/methods/ann/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(gan) add_subdirectory(rbm) add_subdirectory(augmented) add_subdirectory(regularizer) +add_subdirectory(util) # Add directory name to sources. set(DIR_SRCS) diff --git a/src/mlpack/methods/ann/activation_functions/CMakeLists.txt b/src/mlpack/methods/ann/activation_functions/CMakeLists.txt index fd4e765006..d5c0868c1c 100644 --- a/src/mlpack/methods/ann/activation_functions/CMakeLists.txt +++ b/src/mlpack/methods/ann/activation_functions/CMakeLists.txt @@ -19,6 +19,7 @@ set(SOURCES multi_quadratic_function.hpp poisson1_function.hpp gaussian_function.hpp + hard_swish_function.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/ann/activation_functions/hard_swish_function.hpp b/src/mlpack/methods/ann/activation_functions/hard_swish_function.hpp new file mode 100644 index 0000000000..d387e86474 --- /dev/null +++ b/src/mlpack/methods/ann/activation_functions/hard_swish_function.hpp @@ -0,0 +1,116 @@ +/** + * @file methods/ann/activation_functions/hard_swish_function.hpp + * @author Anush Kini + * + * Definition and implementation of the Hard Swish function as described by + * Howard A, Sandler M, Chu G, Chen LC, Chen B, Tan M, Wang W, Zhu Y, Pang R, + * Vasudevan V and Le QV. + * For more information, see the following paper. + * + * @code + * @misc{ + * author = {Howard A, Sandler M, Chu G, Chen LC, Chen B, Tan M, Wang W, + * Zhu Y, Pang R, Vasudevan V and Le QV}, + * title = {Searching for MobileNetV3}, + * year = {2019} + * } + * @endcode + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_ACTIVATION_FUNCTIONS_HARD_SWISH_FUNCTION_HPP +#define MLPACK_METHODS_ANN_ACTIVATION_FUNCTIONS_HARD_SWISH_FUNCTION_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { +/** + * The Hard Swish function, defined by + * + * @f{eqnarray*}{ + * f(x) &=& \begin{cases} + * 0 & x \leq -3\\ + * x & x \geq +3\\ + * \frac{x * (x + 3)}{6} & otherwise\\ + * \end{cases} \\ + * f'(x) &=& \begin{cases} + * 0 & x \leq -3\\ + * 1 & x \geq +3\\ + * \frac{2x + 3}{6} & otherwise\\ + * \end{cases} + * @f} + */ +class HardSwishFunction +{ + public: + /** + * Computes the Hard Swish function. + * + * @param x Input data. + * @return f(x). + */ + static double Fn(const double x) + { + if (x <= -3) + return 0; + else if (x >= 3) + return x; + + return x * (x + 3) / 6; + } + + /** + * Computes the Hard Swish function. + * + * @param x Input data. + * @param y The resulting output activation. + */ + template + static void Fn(const InputVecType &x, OutputVecType &y) + { + y.set_size(size(x)); + + for (size_t i = 0; i < x.n_elem; i++) + y(i) = Fn(x(i)); + } + + /** + * Computes the first derivative of the Hard Swish function. + * + * @param y Input data. + * @return f'(x). + */ + static double Deriv(const double y) + { + if (y <= -3) + return 0; + else if (y >= 3) + return 1; + + return (2 * y + 3.0) / 6.0; + } + + /** + * Computes the first derivatives of the Hard Swish function. + * + * @param y Input data. + * @param x The resulting derivatives. + */ + template + static void Deriv(const InputVecType &y, OutputVecType &x) + { + x.set_size(size(y)); + + for (size_t i = 0; i < y.n_elem; i++) + x(i) = Deriv(y(i)); + } +}; // class HardSwishFunction + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/ffn_impl.hpp b/src/mlpack/methods/ann/ffn_impl.hpp index c16de548f4..401c094ca6 100644 --- a/src/mlpack/methods/ann/ffn_impl.hpp +++ b/src/mlpack/methods/ann/ffn_impl.hpp @@ -23,6 +23,8 @@ #include "visitor/set_input_height_visitor.hpp" #include "visitor/set_input_width_visitor.hpp" +#include "util/check_input_shape.hpp" + namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -109,6 +111,10 @@ double FFN::Train( OptimizerType& optimizer, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Train()"); + ResetData(std::move(predictors), std::move(responses)); WarnMessageMaxIterations(optimizer, this->predictors.n_cols); @@ -131,6 +137,10 @@ double FFN::Train( arma::mat responses, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Train()"); + ResetData(std::move(predictors), std::move(responses)); OptimizerType optimizer; @@ -217,6 +227,10 @@ template::Predict( arma::mat predictors, arma::mat& results) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Predict()"); + if (parameter.is_empty()) ResetParameters(); @@ -250,6 +264,10 @@ template double FFN::Evaluate( const PredictorsType& predictors, const ResponsesType& responses) { + CheckInputShape > >(network, + predictors.n_rows, + "FFN<>::Evaluate()"); + if (parameter.is_empty()) ResetParameters(); diff --git a/src/mlpack/methods/ann/layer/CMakeLists.txt b/src/mlpack/methods/ann/layer/CMakeLists.txt index b4726b0c6f..5fe560edd4 100644 --- a/src/mlpack/methods/ann/layer/CMakeLists.txt +++ b/src/mlpack/methods/ann/layer/CMakeLists.txt @@ -63,6 +63,8 @@ set(SOURCES log_softmax_impl.hpp lookup.hpp lookup_impl.hpp + lp_pooling.hpp + lp_pooling_impl.hpp lstm.hpp lstm_impl.hpp max_pooling.hpp diff --git a/src/mlpack/methods/ann/layer/adaptive_max_pooling.hpp b/src/mlpack/methods/ann/layer/adaptive_max_pooling.hpp index b465ca5bb8..fd080c42dd 100644 --- a/src/mlpack/methods/ann/layer/adaptive_max_pooling.hpp +++ b/src/mlpack/methods/ann/layer/adaptive_max_pooling.hpp @@ -114,6 +114,9 @@ class AdaptiveMaxPooling //! Get the output size. size_t OutputSize() const { return poolingLayer.OutputSize(); } + //! Get the size of the weights. + size_t WeightSize() const { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/adaptive_mean_pooling.hpp b/src/mlpack/methods/ann/layer/adaptive_mean_pooling.hpp index 25509247d5..46a434ab54 100644 --- a/src/mlpack/methods/ann/layer/adaptive_mean_pooling.hpp +++ b/src/mlpack/methods/ann/layer/adaptive_mean_pooling.hpp @@ -115,6 +115,9 @@ class AdaptiveMeanPooling //! Get the output size. size_t OutputSize() const { return poolingLayer.OutputSize(); } + //! Get the size of the weights. + size_t WeightSize() const { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/atrous_convolution.hpp b/src/mlpack/methods/ann/layer/atrous_convolution.hpp index 478f62abe2..daddab76f2 100644 --- a/src/mlpack/methods/ann/layer/atrous_convolution.hpp +++ b/src/mlpack/methods/ann/layer/atrous_convolution.hpp @@ -263,6 +263,12 @@ class AtrousConvolution return (outSize * inSize * kernelWidth * kernelHeight) + outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inputHeight * inputWidth * inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/base_layer.hpp b/src/mlpack/methods/ann/layer/base_layer.hpp index 8429c818a7..ae49f30fe6 100644 --- a/src/mlpack/methods/ann/layer/base_layer.hpp +++ b/src/mlpack/methods/ann/layer/base_layer.hpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -50,6 +51,7 @@ namespace ann /** Artificial Neural Network. */ { * - ELiSHLayer * - ElliotLayer * - GaussianLayer + * - HardSwishLayer * * @tparam ActivationFunction Activation function used for the embedding layer. * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, @@ -277,6 +279,17 @@ template < using GaussianFunctionLayer = BaseLayer< ActivationFunction, InputDataType, OutputDataType>; +/** + * Standard HardSwish-Layer using the HardSwish activation function. + */ +template < + class ActivationFunction = HardSwishFunction, + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +using HardSwishFunctionLayer = BaseLayer< + ActivationFunction, InputDataType, OutputDataType>; + } // namespace ann } // namespace mlpack diff --git a/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp b/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp index 817763e973..8595bc4d57 100644 --- a/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp +++ b/src/mlpack/methods/ann/layer/bilinear_interpolation.hpp @@ -118,6 +118,12 @@ class BilinearInterpolation //! Modify the depth of the input. size_t& InDepth() { return depth; } + //! Get the shape of the input. + size_t InputShape() const + { + return inRowSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/c_relu.hpp b/src/mlpack/methods/ann/layer/c_relu.hpp index da317918ae..365111a7d7 100644 --- a/src/mlpack/methods/ann/layer/c_relu.hpp +++ b/src/mlpack/methods/ann/layer/c_relu.hpp @@ -88,6 +88,9 @@ class CReLU //! Modify the delta. OutputDataType& Delta() { return delta; } + //! Get size of weights. + size_t WeightSize() const { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/celu.hpp b/src/mlpack/methods/ann/layer/celu.hpp index 45bdc01321..ae508703ad 100644 --- a/src/mlpack/methods/ann/layer/celu.hpp +++ b/src/mlpack/methods/ann/layer/celu.hpp @@ -111,6 +111,9 @@ class CELU //! Modify the value of deterministic parameter. bool& Deterministic() { return deterministic; } + //! Get size of weights. + size_t WeightSize() { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/concatenate.hpp b/src/mlpack/methods/ann/layer/concatenate.hpp index b27d92afee..561ecf2595 100644 --- a/src/mlpack/methods/ann/layer/concatenate.hpp +++ b/src/mlpack/methods/ann/layer/concatenate.hpp @@ -41,6 +41,18 @@ class Concatenate */ Concatenate(); + //! Copy constructor. + Concatenate(const Concatenate& layer); + + //! Move constructor. + Concatenate(Concatenate&& layer); + + //! Operator= copy constructor. + Concatenate& operator=(const Concatenate& layer); + + //! Operator= move constructor. + Concatenate& operator=(Concatenate&& layer); + /** * Ordinary feed forward pass of a neural network, evaluating the function * f(x) by propagating the activity forward through f. @@ -82,7 +94,7 @@ class Concatenate //! Get the concat matrix. OutputDataType const& Concat() const { return concat; } - //! Modify the delta. + //! Modify the concat. OutputDataType& Concat() { return concat; } /** diff --git a/src/mlpack/methods/ann/layer/concatenate_impl.hpp b/src/mlpack/methods/ann/layer/concatenate_impl.hpp index 20c7ba6d15..bfede6c162 100644 --- a/src/mlpack/methods/ann/layer/concatenate_impl.hpp +++ b/src/mlpack/methods/ann/layer/concatenate_impl.hpp @@ -20,11 +20,63 @@ namespace mlpack { namespace ann /** Artificial Neural Network. */ { template -Concatenate::Concatenate() +Concatenate::Concatenate() : + inRows(0) { // Nothing to do here. } +template +Concatenate::Concatenate(const Concatenate& layer) : + inRows(layer.inRows), + weights(layer.weights), + delta(layer.delta), + concat(layer.concat) +{ + // Nothing to to here. +} + +template +Concatenate::Concatenate(Concatenate&& layer) : + inRows(layer.inRows), + weights(std::move(layer.weights)), + delta(std::move(layer.delta)), + concat(std::move(layer.concat)) +{ + // Nothing to do here. +} + +template +Concatenate& +Concatenate:: +operator=(const Concatenate& layer) +{ + if (this != &layer) + { + inRows = layer.inRows; + weights = layer.weights; + delta = layer.delta; + concat = layer.concat; + } + + return *this; +} + +template +Concatenate& +Concatenate:: +operator=(Concatenate&& layer) +{ + if (this != &layer) + { + inRows = layer.inRows; + weights = std::move(layer.weights); + delta = std::move(layer.delta); + concat = std::move(layer.concat); + } + return *this; +} + template template void Concatenate::Forward( diff --git a/src/mlpack/methods/ann/layer/convolution.hpp b/src/mlpack/methods/ann/layer/convolution.hpp index ab26c7e80e..5ea92f37ea 100644 --- a/src/mlpack/methods/ann/layer/convolution.hpp +++ b/src/mlpack/methods/ann/layer/convolution.hpp @@ -29,6 +29,35 @@ namespace ann /** Artificial Neural Network. */ { /** * Implementation of the Convolution class. The Convolution class represents a * single layer of a neural network. + * Example usage: + * + * Suppose we want to pass a matrix M (2744x100) to a `Convolution` layer; + * in this example, `M` was obtained from "flattening" 100 images (or Mel + * cepstral coefficients, if we talk about speech, or whatever you like) of + * dimension 196x14. In other words, the first 196 columns of each row of M + * will be made of the 196 columns of the first row of each of the 100 images + * (or Mel cepstral coefficients). Then the next 295 columns of M (196 - 393) + * will be made of the 196 columns of the second row of the 100 images (or Mel + * cepstral coefficients), etc. Given that the size of our 2-D input images is + * 196x14, the parameters for our `Convolution` layer will be something like + * this: + * + * ``` + * Convolution<> c(1, // Number of input activation maps. + * 14, // Number of output activation maps. + * 3, // Filter width. + * 3, // Filter height. + * 1, // Stride along width. + * 1, // Stride along height. + * 0, // Padding width. + * 0, // Padding height. + * 196, // Input width. + * 14); // Input height. + * ``` + * + * This `Convolution<>` layer will treat each column of the input matrix `M` as + * a 2-D image (or object) of the original 196x14 size, using this as the input + * for the 14 filters of this example. * * @tparam ForwardConvolutionRule Convolution to perform forward process. * @tparam BackwardConvolutionRule Convolution to perform backward process. @@ -207,10 +236,10 @@ class Convolution //! Modify the output height. size_t& OutputHeight() { return outputHeight; } - //! Get the input size. + //! Get the number of input maps. size_t InputSize() const { return inSize; } - //! Get the output size. + //! Get the number of output maps. size_t OutputSize() const { return outSize; } //! Get the kernel width. @@ -259,6 +288,12 @@ class Convolution return (outSize * inSize * kernelWidth * kernelHeight) + outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inputHeight * inputWidth * inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/dropout.hpp b/src/mlpack/methods/ann/layer/dropout.hpp index 56734a9ca0..f3fb8f18b4 100644 --- a/src/mlpack/methods/ann/layer/dropout.hpp +++ b/src/mlpack/methods/ann/layer/dropout.hpp @@ -60,6 +60,18 @@ class Dropout */ Dropout(const double ratio = 0.5); + //! Copy Constructor + Dropout(const Dropout& layer); + + //! Move Constructor + Dropout(const Dropout&&); + + //! Copy assignment operator + Dropout& operator=(const Dropout& layer); + + //! Move assignment operator + Dropout& operator=(Dropout&& layer); + /** * Ordinary feed forward pass of the dropout layer. * diff --git a/src/mlpack/methods/ann/layer/dropout_impl.hpp b/src/mlpack/methods/ann/layer/dropout_impl.hpp index dbc6a92e59..80f0d81fec 100644 --- a/src/mlpack/methods/ann/layer/dropout_impl.hpp +++ b/src/mlpack/methods/ann/layer/dropout_impl.hpp @@ -29,6 +29,54 @@ Dropout::Dropout( // Nothing to do here. } +template +Dropout::Dropout( + const Dropout& layer) : + ratio(layer.ratio), + scale(layer.scale), + deterministic(layer.deterministic) +{ + // Nothing to do here. +} + +template +Dropout::Dropout( + const Dropout&& layer) : + ratio(std::move(layer.ratio)), + scale(std::move(scale)), + deterministic(std::move(deterministic)) +{ + // Nothing to do here. +} + +template +Dropout& +Dropout:: +operator=(const Dropout& layer) +{ + if (this != &layer) + { + ratio = layer.ratio; + scale = layer.scale; + deterministic = layer.deterministic; + } + return *this; +} + +template +Dropout& +Dropout:: +operator=(Dropout&& layer) +{ + if (this != &layer) + { + ratio = std::move(layer.ratio); + scale = std::move(layer.scale); + deterministic = std::move(layer.deterministic); + } + return *this; +} + template template void Dropout::Forward( diff --git a/src/mlpack/methods/ann/layer/fast_lstm.hpp b/src/mlpack/methods/ann/layer/fast_lstm.hpp index 121c97e176..80ddcabca6 100644 --- a/src/mlpack/methods/ann/layer/fast_lstm.hpp +++ b/src/mlpack/methods/ann/layer/fast_lstm.hpp @@ -73,6 +73,18 @@ class FastLSTM //! Create the Fast LSTM object. FastLSTM(); + //! Copy Constructor + FastLSTM(const FastLSTM& layer); + + //! Move Constructor + FastLSTM(FastLSTM&& layer); + + //! Copy assignment operator + FastLSTM& operator=(const FastLSTM& layer); + + //! Move assignment operator + FastLSTM& operator=(FastLSTM&& layer); + /** * Create the Fast LSTM layer object using the specified parameters. * @@ -170,6 +182,12 @@ class FastLSTM return 4 * outSize * inSize + 4 * outSize + 4 * outSize * outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp b/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp index 5f5502cf9a..c72416bdeb 100644 --- a/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp +++ b/src/mlpack/methods/ann/layer/fast_lstm_impl.hpp @@ -45,6 +45,90 @@ FastLSTM::FastLSTM( weights.set_size(WeightSize(), 1); } +template +FastLSTM::FastLSTM(const FastLSTM& layer) : + inSize(layer.inSize), + outSize(layer.outSize), + rho(layer.rho), + forwardStep(layer.forwardStep), + backwardStep(layer.backwardStep), + gradientStep(layer.gradientStep), + weights(layer.weights), + batchSize(layer.batchSize), + batchStep(layer.batchStep), + gradientStepIdx(layer.gradientStepIdx), + grad(layer.grad), + rhoSize(layer.rho), + bpttSteps(layer.bpttSteps) +{ + // Nothing to do here. +} + +template +FastLSTM::FastLSTM(FastLSTM&& layer) : + inSize(std::move(layer.inSize)), + outSize(std::move(layer.outSize)), + rho(std::move(layer.rho)), + forwardStep(std::move(layer.forwardStep)), + backwardStep(std::move(layer.backwardStep)), + gradientStep(std::move(layer.gradientStep)), + weights(std::move(layer.weights)), + batchSize(std::move(layer.batchSize)), + batchStep(std::move(layer.batchStep)), + gradientStepIdx(std::move(layer.gradientStepIdx)), + grad(std::move(layer.grad)), + rhoSize(std::move(layer.rho)), + bpttSteps(std::move(layer.bpttSteps)) +{ + // Nothing to do here. +} + +template +FastLSTM& +FastLSTM::operator=(const FastLSTM& layer) +{ + if (this != &layer) + { + inSize = layer.inSize; + outSize = layer.outSize; + rho = layer.rho; + forwardStep = layer.forwardStep; + backwardStep = layer.backwardStep; + gradientStep = layer.gradientStep; + weights = layer.weights; + batchSize = layer.batchSize; + batchStep = layer.batchStep; + gradientStepIdx = layer.gradientStepIdx; + grad = layer.grad; + rhoSize = layer.rho; + bpttSteps = layer.bpttSteps; + } + return *this; +} + +template +FastLSTM& +FastLSTM::operator=(FastLSTM&& layer) +{ + if (this != &layer) + { + inSize = std::move(layer.inSize); + outSize = std::move(layer.outSize); + rho = std::move(layer.rho); + forwardStep = std::move(layer.forwardStep); + backwardStep = std::move(layer.backwardStep); + gradientStep = std::move(layer.gradientStep); + weights = std::move(layer.weights); + batchSize = std::move(layer.batchSize); + batchStep = std::move(layer.batchStep); + gradientStepIdx = std::move(layer.gradientStepIdx); + grad = std::move(layer.grad); + rhoSize = std::move(layer.rho); + bpttSteps = std::move(layer.bpttSteps); + } + return *this; +} + template void FastLSTM::Reset() { @@ -79,33 +163,20 @@ void FastLSTM::ResetCell(const size_t size) gradientStep = batchSize * size - 1; const size_t rhoBatchSize = size * batchSize; - if (gate.is_empty() || gate.n_cols != rhoBatchSize) - { - gate.set_size(4 * outSize, rhoBatchSize); - gateActivation.set_size(outSize * 3, rhoBatchSize); - stateActivation.set_size(outSize, rhoBatchSize); - cellActivation.set_size(outSize, rhoBatchSize); - prevError.set_size(4 * outSize, batchSize); - if (prevOutput.is_empty()) - { - prevOutput = arma::zeros(outSize, batchSize); - cell = arma::zeros(outSize, size * batchSize); - cellActivationError = arma::zeros(outSize, batchSize); - outParameter = arma::zeros( - outSize, (size + 1) * batchSize); - } - else - { - // To preserve the leading zeros, recreate the object according to given - // size specifications, while preserving the elements as well as the - // layout of the elements. - prevOutput.resize(outSize, batchSize); - cell.resize(outSize, size * batchSize); - cellActivationError.resize(outSize, batchSize); - outParameter.resize(outSize, (size + 1) * batchSize); - } - } + // Make sure all of the matrices we use to store state are at least as large + // as we need. + gate.set_size(4 * outSize, rhoBatchSize); + gateActivation.set_size(outSize * 3, rhoBatchSize); + stateActivation.set_size(outSize, rhoBatchSize); + cellActivation.set_size(outSize, rhoBatchSize); + prevError.set_size(4 * outSize, batchSize); + + // Reset stored state to zeros. + prevOutput.zeros(outSize, batchSize); + cell.zeros(outSize, size * batchSize); + cellActivationError.zeros(outSize, batchSize); + outParameter.zeros(outSize, (size + 1) * batchSize); } template diff --git a/src/mlpack/methods/ann/layer/glimpse.hpp b/src/mlpack/methods/ann/layer/glimpse.hpp index 661fab551d..99a268b6a8 100644 --- a/src/mlpack/methods/ann/layer/glimpse.hpp +++ b/src/mlpack/methods/ann/layer/glimpse.hpp @@ -182,6 +182,12 @@ class Glimpse //! Get the used glimpse size (height = width). size_t GlimpseSize() const { return size;} + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/gru.hpp b/src/mlpack/methods/ann/layer/gru.hpp index b732b41af1..3d98a712d8 100644 --- a/src/mlpack/methods/ann/layer/gru.hpp +++ b/src/mlpack/methods/ann/layer/gru.hpp @@ -155,6 +155,12 @@ class GRU //! Get the number of output units. size_t OutSize() const { return outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/highway.hpp b/src/mlpack/methods/ann/layer/highway.hpp index 7e5893c424..aa539a4972 100644 --- a/src/mlpack/methods/ann/layer/highway.hpp +++ b/src/mlpack/methods/ann/layer/highway.hpp @@ -177,6 +177,12 @@ class Highway //! Get the number of input units. size_t InSize() const { return inSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/layer.hpp b/src/mlpack/methods/ann/layer/layer.hpp index 947395fd6b..9cf806b7e4 100644 --- a/src/mlpack/methods/ann/layer/layer.hpp +++ b/src/mlpack/methods/ann/layer/layer.hpp @@ -47,6 +47,7 @@ #include "linear3d.hpp" #include "log_softmax.hpp" #include "lookup.hpp" +#include "lp_pooling.hpp" #include "lstm.hpp" #include "max_pooling.hpp" #include "mean_pooling.hpp" diff --git a/src/mlpack/methods/ann/layer/layer_norm.hpp b/src/mlpack/methods/ann/layer/layer_norm.hpp index ba0d3f4c29..c22408c221 100644 --- a/src/mlpack/methods/ann/layer/layer_norm.hpp +++ b/src/mlpack/methods/ann/layer/layer_norm.hpp @@ -148,6 +148,12 @@ class LayerNorm //! Get the value of epsilon. double Epsilon() const { return eps; } + //! Get the shape of the input. + size_t InputShape() const + { + return size; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/layer_traits.hpp b/src/mlpack/methods/ann/layer/layer_traits.hpp index b9e2621d89..a6a447f43d 100644 --- a/src/mlpack/methods/ann/layer/layer_traits.hpp +++ b/src/mlpack/methods/ann/layer/layer_traits.hpp @@ -120,6 +120,10 @@ HAS_MEM_FUNC(Bias, HasBiasCheck); // we can use with SFINAE to catch when a type has a MaxIterations() function. HAS_MEM_FUNC(MaxIterations, HasMaxIterations); +// This gives us a HasInShapeCheck type we can use with SFINAE to catch when +// a type has a function named InputShape. +HAS_ANY_METHOD_FORM(InputShape, HasInputShapeCheck); + } // namespace ann } // namespace mlpack diff --git a/src/mlpack/methods/ann/layer/layer_types.hpp b/src/mlpack/methods/ann/layer/layer_types.hpp index 1d7fd0ccba..091d7ece35 100644 --- a/src/mlpack/methods/ann/layer/layer_types.hpp +++ b/src/mlpack/methods/ann/layer/layer_types.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -219,6 +220,7 @@ class AdaptiveMeanPooling; using MoreTypes = boost::variant< Linear3D*, + LpPooling*, Glimpse*, Highway*, MultiheadAttention*, diff --git a/src/mlpack/methods/ann/layer/leaky_relu.hpp b/src/mlpack/methods/ann/layer/leaky_relu.hpp index a8c9f5e591..52b2896fca 100644 --- a/src/mlpack/methods/ann/layer/leaky_relu.hpp +++ b/src/mlpack/methods/ann/layer/leaky_relu.hpp @@ -90,6 +90,9 @@ class LeakyReLU //! Modify the non zero gradient. double& Alpha() { return alpha; } + //! Get size of weights. + size_t WeightSize() const { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/linear.hpp b/src/mlpack/methods/ann/layer/linear.hpp index f2c8015e04..cc31117c53 100644 --- a/src/mlpack/methods/ann/layer/linear.hpp +++ b/src/mlpack/methods/ann/layer/linear.hpp @@ -152,6 +152,12 @@ class Linear return (inSize * outSize) + outSize; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/linear3d.hpp b/src/mlpack/methods/ann/layer/linear3d.hpp index b4579a6c62..24e3de56e6 100644 --- a/src/mlpack/methods/ann/layer/linear3d.hpp +++ b/src/mlpack/methods/ann/layer/linear3d.hpp @@ -54,6 +54,18 @@ class Linear3D const size_t outSize, RegularizerType regularizer = RegularizerType()); + //! Copy constructor. + Linear3D(const Linear3D& layer); + + //! Move constructor. + Linear3D(Linear3D&&); + + //! Copy assignment operator. + Linear3D& operator=(const Linear3D& layer); + + //! Move assignment operator. + Linear3D& operator=(Linear3D&& layer); + /* * Reset the layer parameter. */ @@ -136,6 +148,12 @@ class Linear3D //! Modify the bias weights of the layer. OutputDataType& Bias() { return bias; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/linear3d_impl.hpp b/src/mlpack/methods/ann/layer/linear3d_impl.hpp index 5aced4493b..37cc9e39e5 100644 --- a/src/mlpack/methods/ann/layer/linear3d_impl.hpp +++ b/src/mlpack/methods/ann/layer/linear3d_impl.hpp @@ -40,6 +40,62 @@ Linear3D::Linear3D( weights.set_size(outSize * inSize + outSize, 1); } +template +Linear3D::Linear3D( + const Linear3D& layer) : + inSize(layer.inSize), + outSize(layer.outSize), + weights(layer.weights), + regularizer(layer.regularizer) +{ + // Nothing to do here. +} + +template +Linear3D::Linear3D( + Linear3D&& layer) : + inSize(0), + outSize(0), + weights(std::move(layer.weights)), + regularizer(std::move(layer.regularizer)) +{ + // Nothing to do here. +} + +template +Linear3D& +Linear3D:: +operator=(const Linear3D& layer) +{ + if (this != &layer) + { + inSize = layer.inSize; + outSize = layer.outSize; + weights = layer.weights; + regularizer = layer.regularizer; + } + return *this; +} + +template +Linear3D& +Linear3D:: +operator=(Linear3D&& layer) +{ + if (this != &layer) + { + inSize = 0; + outSize = 0; + weights = std::move(layer.weights); + regularizer = std::move(layer.regularizer); + } + return *this; +} + template void Linear3D::Reset() diff --git a/src/mlpack/methods/ann/layer/linear_no_bias.hpp b/src/mlpack/methods/ann/layer/linear_no_bias.hpp index a06e97e735..7182e84238 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 shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/lp_pooling.hpp b/src/mlpack/methods/ann/layer/lp_pooling.hpp new file mode 100644 index 0000000000..8423dda79f --- /dev/null +++ b/src/mlpack/methods/ann/layer/lp_pooling.hpp @@ -0,0 +1,284 @@ +/** + * @file methods/ann/layer/lp_pooling.hpp + * @author Abhinav Anan + * + * Definition of the LpPooling layer class. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LAYER_LP_POOLING_HPP +#define MLPACK_METHODS_ANN_LAYER_LP_POOLING_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Implementation of the LPPooling. + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class LpPooling +{ + public: + //! Create the LpPooling object. + LpPooling(); + + /** + * Create the LpPooling object using the specified number of units. + * + * @param normType Parameter for type of norm. + * @param kernelWidth Width of the pooling window. + * @param kernelHeight Height of the pooling window. + * @param strideWidth Width of the stride operation. + * @param strideHeight Width of the stride operation. + * @param floor Set to true to use floor method. + */ + LpPooling(const size_t normType, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth = 1, + const size_t strideHeight = 1, + const bool floor = true); + + /** + * Ordinary feed forward pass of a neural network, evaluating the function + * f(x) by propagating the activity forward through f. + * + * @param input Input data used for evaluating the specified function. + * @param output Resulting output activation. + */ + template + void Forward(const arma::Mat& input, arma::Mat& output); + + /** + * Ordinary feed backward pass of a neural network, using 3rd-order tensors as + * input, calculating the function f(x) by propagating x backwards through f. + * Using the results from the feed forward pass. + * + * @param * (input) The propagated input activation. + * @param gy The backpropagated error. + * @param g The calculated gradient. + */ + template + void Backward(const arma::Mat& /* input */, + const arma::Mat& gy, + arma::Mat& g); + + //! Get the output parameter. + OutputDataType const& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the delta. + OutputDataType const& Delta() const { return delta; } + //! Modify the delta. + OutputDataType& Delta() { return delta; } + + //! Get the intput width. + size_t const& InputWidth() const { return inputWidth; } + //! Modify the input width. + size_t& InputWidth() { return inputWidth; } + + //! Get the input height. + size_t const& InputHeight() const { return inputHeight; } + //! Modify the input height. + size_t& InputHeight() { return inputHeight; } + + //! Get the output width. + size_t const& OutputWidth() const { return outputWidth; } + //! Modify the output width. + size_t& OutputWidth() { return outputWidth; } + + //! Get the output height. + size_t const& OutputHeight() const { return outputHeight; } + //! Modify the output height. + size_t& OutputHeight() { return outputHeight; } + + //! Get the input size. + size_t InputSize() const { return inSize; } + + //! Get the output size. + size_t OutputSize() const { return outSize; } + + //! Get the normType. + size_t NormType() const { return normType; } + //! Modify the normType. + size_t& NormType() { return normType; } + + //! Get the kernel width. + size_t KernelWidth() const { return kernelWidth; } + //! Modify the kernel width. + size_t& KernelWidth() { return kernelWidth; } + + //! Get the kernel height. + size_t KernelHeight() const { return kernelHeight; } + //! Modify the kernel height. + size_t& KernelHeight() { return kernelHeight; } + + //! Get the stride width. + size_t StrideWidth() const { return strideWidth; } + //! Modify the stride width. + size_t& StrideWidth() { return strideWidth; } + + //! Get the stride height. + size_t StrideHeight() const { return strideHeight; } + //! Modify the stride height. + size_t& StrideHeight() { return strideHeight; } + + //! Get the value of the rounding operation + bool const& Floor() const { return floor; } + //! Modify the value of the rounding operation + bool& Floor() { return floor; } + + //! Get the size of the weights. + size_t WeightSize() const { return 0; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + /** + * Apply pooling to the input and store the results. + * + * @param input The input to be apply the pooling rule. + * @param output The pooled result. + */ + template + void Pooling(const arma::Mat& input, arma::Mat& output) + { + for (size_t j = 0, colidx = 0; j < output.n_cols; + ++j, colidx += strideHeight) + { + for (size_t i = 0, rowidx = 0; i < output.n_rows; + ++i, rowidx += strideWidth) + { + arma::mat subInput = input( + arma::span(rowidx, rowidx + kernelWidth - 1 - offset), + arma::span(colidx, colidx + kernelHeight - 1 - offset)); + + output(i, j) = pow(arma::accu(arma::pow(subInput, + normType)), 1.0 / normType); + } + } + } + + /** + * Apply unpooling to the input and store the results. + * + * @param input The input to be apply the unpooling rule. + * @param output The pooled result. + */ + template + void Unpooling(const arma::Mat& input, + const arma::Mat& error, + arma::Mat& output) + { + const size_t rStep = input.n_rows / error.n_rows - offset; + const size_t cStep = input.n_cols / error.n_cols - offset; + + arma::Mat unpooledError; + for (size_t j = 0; j < input.n_cols - cStep; j += cStep) + { + for (size_t i = 0; i < input.n_rows - rStep; i += rStep) + { + const arma::Mat& inputArea = input(arma::span(i, i + rStep - 1), + arma::span(j, j + cStep - 1)); + size_t sum = pow(arma::accu(arma::pow(inputArea, normType)), + (normType - 1) / normType); + unpooledError = arma::Mat(inputArea.n_rows, inputArea.n_cols); + unpooledError.fill(error(i / rStep, j / cStep)); + unpooledError %= arma::pow(inputArea, normType - 1); + unpooledError /= sum; + output(arma::span(i, i + rStep - 1 - offset), + arma::span(j, j + cStep - 1 - offset)) += unpooledError; + } + } + } + + //! Locally-stored norm type. + size_t normType; + + //! Locally-stored width of the pooling window. + size_t kernelWidth; + + //! Locally-stored height of the pooling window. + size_t kernelHeight; + + //! Locally-stored width of the stride operation. + size_t strideWidth; + + //! Locally-stored height of the stride operation. + size_t strideHeight; + + //! Rounding operation used. + bool floor; + + //! Locally-stored number of input channels. + size_t inSize; + + //! Locally-stored number of output channels. + size_t outSize; + + //! Locally-stored input width. + size_t inputWidth; + + //! Locally-stored input height. + size_t inputHeight; + + //! Locally-stored output width. + size_t outputWidth; + + //! Locally-stored output height. + size_t outputHeight; + + //! Locally-stored reset parameter used to initialize the module once. + bool reset; + + //! Locally-stored stored rounding offset. + size_t offset; + + //! Locally-stored number of input units. + size_t batchSize; + + //! Locally-stored output parameter. + arma::cube outputTemp; + + //! Locally-stored transformed input parameter. + arma::cube inputTemp; + + //! Locally-stored transformed output parameter. + arma::cube gTemp; + + //! Locally-stored delta object. + OutputDataType delta; + + //! Locally-stored gradient object. + OutputDataType gradient; + + //! Locally-stored output parameter object. + OutputDataType outputParameter; +}; // class LpPooling + + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "lp_pooling_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/layer/lp_pooling_impl.hpp b/src/mlpack/methods/ann/layer/lp_pooling_impl.hpp new file mode 100644 index 0000000000..0abe08ada6 --- /dev/null +++ b/src/mlpack/methods/ann/layer/lp_pooling_impl.hpp @@ -0,0 +1,141 @@ +/** + * @file methods/ann/layer/lp_pooling_impl.hpp + * @author Marcus Edel + * @author Nilay Jain + * + * Implementation of the lpPooling layer class. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LAYER_LP_POOLING_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_LP_POOLING_IMPL_HPP + +// In case it hasn't yet been included. +#include "lp_pooling.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +LpPooling::LpPooling() +{ + // Nothing to do here. +} + +template +LpPooling::LpPooling( + const size_t normType, + const size_t kernelWidth, + const size_t kernelHeight, + const size_t strideWidth, + const size_t strideHeight, + const bool floor) : + normType(normType), + kernelWidth(kernelWidth), + kernelHeight(kernelHeight), + strideWidth(strideWidth), + strideHeight(strideHeight), + floor(floor), + inSize(0), + outSize(0), + inputWidth(0), + inputHeight(0), + outputWidth(0), + outputHeight(0), + reset(false), + offset(0), + batchSize(0) +{ + // Nothing to do here. +} + +template +template +void LpPooling::Forward( + const arma::Mat& input, arma::Mat& output) +{ + batchSize = input.n_cols; + inSize = input.n_elem / (inputWidth * inputHeight * batchSize); + inputTemp = arma::cube(const_cast&>(input).memptr(), + inputWidth, inputHeight, batchSize * inSize, false, false); + + if (floor) + { + outputWidth = std::floor((inputWidth - + (double) kernelWidth) / (double) strideWidth + 1); + outputHeight = std::floor((inputHeight - + (double) kernelHeight) / (double) strideHeight + 1); + + offset = 0; + } + else + { + outputWidth = std::ceil((inputWidth - + (double) kernelWidth) / (double) strideWidth + 1); + outputHeight = std::ceil((inputHeight - + (double) kernelHeight) / (double) strideHeight + 1); + + offset = 1; + } + + outputTemp = arma::zeros >(outputWidth, outputHeight, + batchSize * inSize); + + for (size_t s = 0; s < inputTemp.n_slices; s++) + Pooling(inputTemp.slice(s), outputTemp.slice(s)); + + output = arma::Mat(outputTemp.memptr(), outputTemp.n_elem / batchSize, + batchSize); + + outputWidth = outputTemp.n_rows; + outputHeight = outputTemp.n_cols; + outSize = batchSize * inSize; +} + +template +template +void LpPooling::Backward( + const arma::Mat& /* input */, + const arma::Mat& gy, + arma::Mat& g) +{ + arma::cube mappedError = arma::cube(((arma::Mat&) gy).memptr(), + outputWidth, outputHeight, outSize, false, false); + + gTemp = arma::zeros(inputTemp.n_rows, + inputTemp.n_cols, inputTemp.n_slices); + + for (size_t s = 0; s < mappedError.n_slices; s++) + { + Unpooling(inputTemp.slice(s), mappedError.slice(s), gTemp.slice(s)); + } + + g = arma::mat(gTemp.memptr(), gTemp.n_elem / batchSize, batchSize); +} + +template +template +void LpPooling::serialize( + Archive& ar, + const uint32_t /* version */) +{ + ar(CEREAL_NVP(normType)); + ar(CEREAL_NVP(kernelWidth)); + ar(CEREAL_NVP(kernelHeight)); + ar(CEREAL_NVP(strideWidth)); + ar(CEREAL_NVP(strideHeight)); + ar(CEREAL_NVP(batchSize)); + ar(CEREAL_NVP(floor)); + ar(CEREAL_NVP(inputWidth)); + ar(CEREAL_NVP(inputHeight)); + ar(CEREAL_NVP(outputWidth)); + ar(CEREAL_NVP(outputHeight)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/lstm.hpp b/src/mlpack/methods/ann/layer/lstm.hpp index 1941778ce9..98be4b500f 100644 --- a/src/mlpack/methods/ann/layer/lstm.hpp +++ b/src/mlpack/methods/ann/layer/lstm.hpp @@ -76,6 +76,18 @@ class LSTM const size_t outSize, const size_t rho = std::numeric_limits::max()); + //! Copy constructor. + LSTM(const LSTM& layer); + + //! Move constructor. + LSTM(LSTM&&); + + //! Copy assignment operator. + LSTM& operator=(const LSTM& layer); + + //! Move assignment operator. + LSTM& operator=(LSTM&& layer); + /** * Ordinary feed-forward pass of a neural network, evaluating the function * f(x) by propagating the activity forward through f. @@ -171,6 +183,15 @@ class LSTM //! Get the number of output units. size_t OutSize() const { return outSize; } + //! Get the size of the weights. + size_t WeightSize() const { return (4 * outSize * inSize + 7 * outSize + 4 * outSize * outSize); } + + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/lstm_impl.hpp b/src/mlpack/methods/ann/layer/lstm_impl.hpp index 36dd9f1be3..9d720732c4 100644 --- a/src/mlpack/methods/ann/layer/lstm_impl.hpp +++ b/src/mlpack/methods/ann/layer/lstm_impl.hpp @@ -24,6 +24,90 @@ LSTM::LSTM() // Nothing to do here. } +template +LSTM::LSTM( + const LSTM& layer) : + inSize(layer.inSize), + outSize(layer.outSize), + rho(layer.rho), + forwardStep(layer.forwardStep), + backwardStep(layer.backwardStep), + gradientStep(layer.gradientStep), + weights(layer.weights), + batchSize(layer.batchSize), + batchStep(layer.batchStep), + gradientStepIdx(layer.gradientStepIdx), + rhoSize(layer.rho), + bpttSteps(layer.bpttSteps) +{ + // Nothing to do here. +} + +template +LSTM::LSTM( + LSTM&& layer) : + inSize(std::move(layer.inSize)), + outSize(std::move(layer.outSize)), + rho(std::move(layer.rho)), + forwardStep(std::move(layer.forwardStep)), + backwardStep(std::move(layer.backwardStep)), + gradientStep(std::move(layer.gradientStep)), + weights(std::move(layer.weights)), + batchSize(std::move(layer.batchSize)), + batchStep(std::move(layer.batchStep)), + gradientStepIdx(std::move(layer.gradientStepIdx)), + rhoSize(std::move(layer.rho)), + bpttSteps(std::move(layer.bpttSteps)) +{ + // Nothing to do here. +} + +template +LSTM& +LSTM :: operator=(const LSTM& layer) +{ + if (this != &layer) + { + inSize = layer.inSize; + outSize = layer.outSize; + rho = layer.rho; + forwardStep = layer.forwardStep; + backwardStep = layer.backwardStep; + gradientStep = layer.gradientStep; + weights = layer.weights; + batchSize = layer.batchSize; + batchStep = layer.batchStep; + gradientStepIdx = layer.gradientStepIdx; + grad = layer.grad; + rhoSize = layer.rho; + bpttSteps = layer.bpttSteps; + } + return *this; +} + +template +LSTM& +LSTM :: operator=(LSTM&& layer) +{ + if (this != &layer) + { + inSize = std::move(layer.inSize); + outSize = std::move(layer.outSize); + rho = std::move(layer.rho); + forwardStep = std::move(layer.forwardStep); + backwardStep = std::move(layer.backwardStep); + gradientStep = std::move(layer.gradientStep); + weights = std::move(layer.weights); + batchSize = std::move(layer.batchSize); + batchStep = std::move(layer.batchStep); + gradientStepIdx = std::move(layer.gradientStepIdx); + grad = std::move(layer.grad); + rhoSize = std::move(layer.rho); + bpttSteps = std::move(layer.bpttSteps); + } + return *this; +} + template LSTM::LSTM( const size_t inSize, const size_t outSize, const size_t rho) : @@ -39,8 +123,7 @@ LSTM::LSTM( rhoSize(rho), bpttSteps(0) { - weights.set_size(4 * outSize * inSize + 7 * outSize + - 4 * outSize * outSize, 1); + weights.set_size(WeightSize(), 1); } template @@ -61,36 +144,25 @@ void LSTM::ResetCell(const size_t size) gradientStep = batchSize * size - 1; const size_t rhoBatchSize = size * batchSize; - if (inputGate.is_empty() || inputGate.n_cols < rhoBatchSize) - { - inputGate.set_size(outSize, rhoBatchSize); - forgetGate.set_size(outSize, rhoBatchSize); - hiddenLayer.set_size(outSize, rhoBatchSize); - outputGate.set_size(outSize, rhoBatchSize); - inputGateActivation.set_size(outSize, rhoBatchSize); - forgetGateActivation.set_size(outSize, rhoBatchSize); - outputGateActivation.set_size(outSize, rhoBatchSize); - hiddenLayerActivation.set_size(outSize, rhoBatchSize); + // Make sure all of the different matrices we will use to hold parameters are + // at least as large as we need. + inputGate.set_size(outSize, rhoBatchSize); + forgetGate.set_size(outSize, rhoBatchSize); + hiddenLayer.set_size(outSize, rhoBatchSize); + outputGate.set_size(outSize, rhoBatchSize); - cellActivation.set_size(outSize, rhoBatchSize); - prevError.set_size(4 * outSize, batchSize); + inputGateActivation.set_size(outSize, rhoBatchSize); + forgetGateActivation.set_size(outSize, rhoBatchSize); + outputGateActivation.set_size(outSize, rhoBatchSize); + hiddenLayerActivation.set_size(outSize, rhoBatchSize); - if (cell.is_empty()) - { - cell = arma::zeros(outSize, size * batchSize); - outParameter = arma::zeros( - outSize, (size + 1) * batchSize); - } - else - { - // To preserve the leading zeros, recreate the object according to given - // size specifications, while preserving the elements as well as the - // layout of the elements. - cell.resize(outSize, size * batchSize); - outParameter.resize(outSize, (size + 1) * batchSize); - } - } + cellActivation.set_size(outSize, rhoBatchSize); + prevError.set_size(4 * outSize, batchSize); + + // Now reset recurrent values to 0. + cell.zeros(outSize, size * batchSize); + outParameter.zeros(outSize, (size + 1) * batchSize); } template diff --git a/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp b/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp index 88f8dff1ba..3448f36b5d 100644 --- a/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp +++ b/src/mlpack/methods/ann/layer/minibatch_discrimination.hpp @@ -134,6 +134,12 @@ class MiniBatchDiscrimination //! Modify the gradient. OutputDataType& Gradient() { return gradient; } + //! Get the shape of the input. + size_t InputShape() const + { + return A; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/multihead_attention.hpp b/src/mlpack/methods/ann/layer/multihead_attention.hpp index 3421fa4183..0d7506ea51 100644 --- a/src/mlpack/methods/ann/layer/multihead_attention.hpp +++ b/src/mlpack/methods/ann/layer/multihead_attention.hpp @@ -120,6 +120,9 @@ class MultiheadAttention const arma::Mat& error, arma::Mat& gradient); + //! Get the size of the weights. + size_t WeightSize() const { return 4 * (embedDim + 1) * embedDim; } + /** * Serialize the layer. */ @@ -176,6 +179,11 @@ class MultiheadAttention //! Modify the parameters. OutputDataType& Parameters() { return weights; } + size_t InputShape() const + { + return embedDim * (tgtSeqLen + 2 * srcSeqLen); + } + private: //! Element Type of the input. typedef typename OutputDataType::elem_type ElemType; diff --git a/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp b/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp index d2da8788e9..3d687d93af 100644 --- a/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp +++ b/src/mlpack/methods/ann/layer/multihead_attention_impl.hpp @@ -54,7 +54,7 @@ MultiheadAttention( } headDim = embedDim / numHeads; - weights.set_size(4 * (embedDim + 1) * embedDim, 1); + weights.set_size(WeightSize(), 1); } template ::MultiplyConstant( // Nothing to do here. } +template +MultiplyConstant::MultiplyConstant( + const MultiplyConstant& layer) : + scalar(layer.scalar) +{ + // Nothing to do here. +} + +template +MultiplyConstant::MultiplyConstant( + MultiplyConstant&& layer) : + scalar(std::move(layer.scalar)) +{ + // Nothing to do here. +} + +template +MultiplyConstant& +MultiplyConstant::operator=( + const MultiplyConstant& layer) +{ + if (this != &layer) + { + scalar = layer.scalar; + } + return *this; +} + +template +MultiplyConstant& +MultiplyConstant::operator=( + MultiplyConstant&& layer) +{ + if (this != &layer) + { + scalar = std::move(layer.scalar); + } + return *this; +} + template template void MultiplyConstant::Forward( diff --git a/src/mlpack/methods/ann/layer/multiply_merge.hpp b/src/mlpack/methods/ann/layer/multiply_merge.hpp index f459ab2f81..5c3d9ba6c0 100644 --- a/src/mlpack/methods/ann/layer/multiply_merge.hpp +++ b/src/mlpack/methods/ann/layer/multiply_merge.hpp @@ -50,6 +50,18 @@ class MultiplyMerge */ MultiplyMerge(const bool model = false, const bool run = true); + //! Copy Constructor. + MultiplyMerge(const MultiplyMerge& layer); + + //! Move Constructor. + MultiplyMerge(MultiplyMerge&& layer); + + //! Copy assignment operator. + MultiplyMerge& operator=(const MultiplyMerge& layer); + + //! Move assignment operator. + MultiplyMerge& operator=(MultiplyMerge&& layer); + //! Destructor to release allocated memory. ~MultiplyMerge(); @@ -135,6 +147,9 @@ class MultiplyMerge //! Modify the parameters. OutputDataType& Parameters() { return weights; } + //! Get the size of the weights. + size_t WeightSize() const { return 0; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp b/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp index ee4c8ed917..29cd111482 100644 --- a/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp +++ b/src/mlpack/methods/ann/layer/multiply_merge_impl.hpp @@ -32,6 +32,66 @@ MultiplyMerge::MultiplyMerge( // Nothing to do here. } +template +MultiplyMerge::MultiplyMerge( + const MultiplyMerge& layer) : + model(layer.model), + run(layer.run), + ownsLayer(layer.ownsLayer), + network(layer.network), + weights(layer.weights) +{ + // Nothing to do here. +} + +template +MultiplyMerge::MultiplyMerge( + MultiplyMerge&& layer) : + model(std::move(layer.model)), + run(std::move(layer.run)), + ownsLayer(std::move(layer.ownsLayer)), + network(std::move(layer.network)), + weights(std::move(layer.weights)) +{ + // Nothing to do here. +} + +template +MultiplyMerge& +MultiplyMerge::operator=( + const MultiplyMerge& layer) +{ + if (this != &layer) + { + model = layer.model; + run = layer.run; + ownsLayer = layer.ownsLayer; + network = layer.network; + weights = layer.weights; + } + return *this; +} + +template +MultiplyMerge& +MultiplyMerge::operator=( + MultiplyMerge&& layer) +{ + if (this != &layer) + { + model = std::move(layer.model); + run = std::move(layer.run); + ownsLayer = std::move(layer.ownsLayer); + network = std::move(layer.network); + weights = std::move(layer.weights); + } + return *this; +} + template MultiplyMerge::~MultiplyMerge() diff --git a/src/mlpack/methods/ann/layer/noisylinear.hpp b/src/mlpack/methods/ann/layer/noisylinear.hpp index 34ca70193b..993f80725f 100644 --- a/src/mlpack/methods/ann/layer/noisylinear.hpp +++ b/src/mlpack/methods/ann/layer/noisylinear.hpp @@ -48,6 +48,15 @@ class NoisyLinear //! Copy constructor. NoisyLinear(const NoisyLinear&); + //! Move constructor. + NoisyLinear(NoisyLinear&&); + + //! Operator= copy constructor. + NoisyLinear& operator=(const NoisyLinear& layer); + + //! Operator= move constructor. + NoisyLinear& operator=(NoisyLinear&& layer); + /* * Reset the layer parameter. */ @@ -130,9 +139,17 @@ class NoisyLinear //! Modify the gradient. OutputDataType& Gradient() { return gradient; } + //! Get the shape of the input. + size_t InputShape() const + { + return inSize; + } + //! Modify the bias weights of the layer. arma::mat& Bias() { return bias; } + //! Get size of weights. + size_t WeightSize() const { return (outSize * inSize + outSize) * 2; } /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/noisylinear_impl.hpp b/src/mlpack/methods/ann/layer/noisylinear_impl.hpp index 82a3a35fc6..c7a37b5863 100644 --- a/src/mlpack/methods/ann/layer/noisylinear_impl.hpp +++ b/src/mlpack/methods/ann/layer/noisylinear_impl.hpp @@ -43,11 +43,55 @@ NoisyLinear::NoisyLinear( inSize(inSize), outSize(outSize) { - weights.set_size((outSize * inSize + outSize) * 2, 1); + weights.set_size(WeightSize(), 1); weightEpsilon.set_size(outSize, inSize); biasEpsilon.set_size(outSize, 1); } +template +NoisyLinear::NoisyLinear( + NoisyLinear&& layer) : + inSize(std::move(layer.inSize)), + outSize(std::move(layer.outSize)), + weights(std::move(layer.weights)) +{ + layer.inSize = 0; + layer.outSize = 0; + layer.weights = nullptr; + Reset(); +} + +template +NoisyLinear& +NoisyLinear::operator=(const NoisyLinear& layer) +{ + if (this != &layer) + { + inSize = layer.inSize; + outSize = layer.outSize; + weights = layer.weights; + Reset(); + } + return *this; +} + +template +NoisyLinear& +NoisyLinear::operator=(NoisyLinear&& layer) +{ + if (this != &layer) + { + inSize = std::move(layer.inSize); + layer.inSize = 0; + outSize = std::move(layer.outSize); + layer.outSize = 0; + weights = std::move(layer.weights); + layer.weights = nullptr; + Reset(); + } + return *this; +} + template void NoisyLinear::Reset() { diff --git a/src/mlpack/methods/ann/layer/parametric_relu.hpp b/src/mlpack/methods/ann/layer/parametric_relu.hpp index 728b6c5db9..f40be33b2a 100644 --- a/src/mlpack/methods/ann/layer/parametric_relu.hpp +++ b/src/mlpack/methods/ann/layer/parametric_relu.hpp @@ -119,6 +119,9 @@ class PReLU //! Modify the non zero gradient. double& Alpha() { return alpha(0); } + //! Get size of weights. + size_t WeightSize() const { return 1; } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/parametric_relu_impl.hpp b/src/mlpack/methods/ann/layer/parametric_relu_impl.hpp index 2650c5d863..6636e776b7 100644 --- a/src/mlpack/methods/ann/layer/parametric_relu_impl.hpp +++ b/src/mlpack/methods/ann/layer/parametric_relu_impl.hpp @@ -25,7 +25,7 @@ template PReLU::PReLU( const double userAlpha) : userAlpha(userAlpha) { - alpha.set_size(1, 1); + alpha.set_size(WeightSize(), 1); alpha(0) = userAlpha; } diff --git a/src/mlpack/methods/ann/layer/positional_encoding.hpp b/src/mlpack/methods/ann/layer/positional_encoding.hpp index 1e6cb445a0..8678426414 100644 --- a/src/mlpack/methods/ann/layer/positional_encoding.hpp +++ b/src/mlpack/methods/ann/layer/positional_encoding.hpp @@ -93,6 +93,11 @@ class PositionalEncoding //! Get the positional encoding vector. InputDataType const& Encoding() const { return positionalEncoding; } + size_t InputShape() const + { + return embedDim * maxSequenceLength; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/radial_basis_function.hpp b/src/mlpack/methods/ann/layer/radial_basis_function.hpp index b867e9e936..2387d612ab 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 shape of the input. + size_t InputShape() const + { + return inSize; + } + /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/recurrent.hpp b/src/mlpack/methods/ann/layer/recurrent.hpp index 0466b265b0..b82fcb175b 100644 --- a/src/mlpack/methods/ann/layer/recurrent.hpp +++ b/src/mlpack/methods/ann/layer/recurrent.hpp @@ -2,8 +2,7 @@ * @file methods/ann/layer/recurrent.hpp * @author Marcus Edel * - * Definition of the LinearLayer class also known as fully-connected layer or - * affine transformation. + * Definition of the Recurrent class. * * mlpack is free software; you may redistribute it and/or modify it under the * terms of the 3-clause BSD license. You should have received a copy of the @@ -19,6 +18,7 @@ #include "../visitor/delta_visitor.hpp" #include "../visitor/copy_visitor.hpp" #include "../visitor/output_parameter_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" #include "layer_types.hpp" #include "add_merge.hpp" @@ -139,6 +139,9 @@ class Recurrent //! Get the number of steps to backpropagate through time. size_t const& Rho() const { return rho; } + //! Get the shape of the input. + size_t InputShape() const; + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp b/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp index 4cdb912756..dcc60055d5 100644 --- a/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp +++ b/src/mlpack/methods/ann/layer/recurrent_attention_impl.hpp @@ -31,7 +31,8 @@ RecurrentAttention::RecurrentAttention() : rho(0), forwardStep(0), backwardStep(0), - deterministic(false) + deterministic(false), + outSize(0) { // Nothing to do. } diff --git a/src/mlpack/methods/ann/layer/recurrent_impl.hpp b/src/mlpack/methods/ann/layer/recurrent_impl.hpp index 23a1dc4625..e6b933bd20 100644 --- a/src/mlpack/methods/ann/layer/recurrent_impl.hpp +++ b/src/mlpack/methods/ann/layer/recurrent_impl.hpp @@ -2,8 +2,7 @@ * @file methods/ann/layer/recurrent_impl.hpp * @author Marcus Edel * - * Implementation of the LinearLayer class also known as fully-connected layer - * or affine transformation. + * Implementation of the Recurrent class. * * mlpack is free software; you may redistribute it and/or modify it under the * terms of the 3-clause BSD license. You should have received a copy of the @@ -20,6 +19,7 @@ #include "../visitor/backward_visitor.hpp" #include "../visitor/gradient_visitor.hpp" #include "../visitor/gradient_zero_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -126,6 +126,53 @@ Recurrent::Recurrent( this->network.push_back(recurrentModule); } +template +size_t Recurrent::InputShape() const +{ + const size_t inputShapeStartModule = boost::apply_visitor(InShapeVisitor(), startModule); + // Return the input shape of the first module that we have. + if (inputShapeStartModule != 0) + { + return inputShapeStartModule; + } + // If input shape of first module is 0. + else + { + // Return input shape of the second module that we have. + const size_t inputShapeInputModule = boost::apply_visitor(InShapeVisitor(), inputModule); + if (inputShapeInputModule != 0) + { + return inputShapeInputModule; + // If the input shape of second module is 0. + } + else + { + // Return input shape of the third module that we have. + const size_t inputShapeFeedbackModule = boost::apply_visitor(InShapeVisitor(), + feedbackModule); + if (inputShapeFeedbackModule != 0) + { + return inputShapeFeedbackModule; + // If the input shape of the third module is 0. + } + else + { + // Return the shape of the fourth module that we have. + const size_t inputShapeTransferModule = boost::apply_visitor(InShapeVisitor(), + transferModule); + if (inputShapeTransferModule != 0) + { + return inputShapeTransferModule; + } + // If the input shape of the fourth module is 0. + else + return 0; + } + } + } +} + template template diff --git a/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp b/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp index 67eebf107d..c2f92df476 100644 --- a/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp +++ b/src/mlpack/methods/ann/layer/reinforce_normal_impl.hpp @@ -21,7 +21,7 @@ namespace ann /** Artificial Neural Network. */ { template ReinforceNormal::ReinforceNormal( - const double stdev) : stdev(stdev) + const double stdev) : stdev(stdev), reward(0.0), deterministic(false) { // Nothing to do here. } @@ -34,8 +34,7 @@ void ReinforceNormal::Forward( if (!deterministic) { // Multiply by standard deviations and re-center the means to the mean. - output = arma::randn >(input.n_rows, input.n_cols) * - stdev + input; + output = output.randn(input.n_rows, input.n_cols) * stdev + input; moduleInputParameter.push_back(input); } diff --git a/src/mlpack/methods/ann/layer/reparametrization.hpp b/src/mlpack/methods/ann/layer/reparametrization.hpp index f6e6fe3b7f..a526d746bf 100644 --- a/src/mlpack/methods/ann/layer/reparametrization.hpp +++ b/src/mlpack/methods/ann/layer/reparametrization.hpp @@ -71,6 +71,18 @@ class Reparametrization const bool stochastic = true, const bool includeKl = true, const double beta = 1); + + //! Copy Constructor. + Reparametrization(const Reparametrization& layer); + + //! Move Constructor. + Reparametrization(Reparametrization&& layer); + + //! Copy assignment operator. + Reparametrization& operator=(const Reparametrization& layer); + + //! Move assignment operator. + Reparametrization& operator=(Reparametrization&& layer); /** * Ordinary feed forward pass of a neural network, evaluating the function @@ -130,6 +142,11 @@ class Reparametrization //! Get the value of the beta hyperparameter. double Beta() const { return beta; } + size_t InputShape() const + { + return 2 * latentSize; + } + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/reparametrization_impl.hpp b/src/mlpack/methods/ann/layer/reparametrization_impl.hpp index 09ac41f5de..cef6a32b0d 100644 --- a/src/mlpack/methods/ann/layer/reparametrization_impl.hpp +++ b/src/mlpack/methods/ann/layer/reparametrization_impl.hpp @@ -46,7 +46,60 @@ Reparametrization::Reparametrization( << "included." << std::endl; } } + +template +Reparametrization::Reparametrization( + const Reparametrization& layer) : + latentSize(layer.latentSize), + stochastic(layer.stochastic), + includeKl(layer.includeKl), + beta(layer.beta) +{ + // Nothing to do here. +} +template +Reparametrization::Reparametrization( + Reparametrization&& layer) : + latentSize(std::move(layer.latentSize)), + stochastic(std::move(layer.stochastic)), + includeKl(std::move(layer.includeKl)), + beta(std::move(layer.beta)) +{ + // Nothing to do here. +} + +template +Reparametrization& +Reparametrization:: +operator=(const Reparametrization& layer) +{ + if (this != &layer) + { + latentSize = layer.latentSize; + stochastic = layer.stochastic; + includeKl = layer.includeKl; + beta = layer.beta; + } + return *this; +} + +template +Reparametrization& +Reparametrization:: +operator=(Reparametrization&& layer) +{ + if (this != &layer) + { + latentSize = std::move(layer.latentSize); + stochastic = std::move(layer.stochastic); + includeKl = std::move(layer.includeKl); + beta = std::move(layer.beta); + } + return *this; +} + + template template void Reparametrization::Forward( diff --git a/src/mlpack/methods/ann/layer/sequential.hpp b/src/mlpack/methods/ann/layer/sequential.hpp index f4466161a0..ebf3c6fd6a 100644 --- a/src/mlpack/methods/ann/layer/sequential.hpp +++ b/src/mlpack/methods/ann/layer/sequential.hpp @@ -23,6 +23,7 @@ #include "../visitor/output_height_visitor.hpp" #include "../visitor/output_parameter_visitor.hpp" #include "../visitor/output_width_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" #include "layer_types.hpp" #include "add_merge.hpp" @@ -184,6 +185,8 @@ class Sequential //! Modify the gradient. arma::mat& Gradient() { return gradient; } + size_t InputShape() const; + /** * Serialize the layer */ diff --git a/src/mlpack/methods/ann/layer/sequential_impl.hpp b/src/mlpack/methods/ann/layer/sequential_impl.hpp index 5e9c4cd6f6..1290a15ebb 100644 --- a/src/mlpack/methods/ann/layer/sequential_impl.hpp +++ b/src/mlpack/methods/ann/layer/sequential_impl.hpp @@ -21,6 +21,7 @@ #include "../visitor/gradient_visitor.hpp" #include "../visitor/set_input_height_visitor.hpp" #include "../visitor/set_input_width_visitor.hpp" +#include "../visitor/input_shape_visitor.hpp" namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -94,6 +95,24 @@ Sequential< } } +template +size_t Sequential:: +InputShape() const +{ + size_t inputShape = 0; + + for (size_t l = 0; l < network.size(); ++l) + { + if (inputShape == 0) + inputShape = boost::apply_visitor(InShapeVisitor(), network[l]); + else + break; + } + + return inputShape; +} + template template diff --git a/src/mlpack/methods/ann/layer/spatial_dropout_impl.hpp b/src/mlpack/methods/ann/layer/spatial_dropout_impl.hpp index 79114d2471..4bd2cb767b 100644 --- a/src/mlpack/methods/ann/layer/spatial_dropout_impl.hpp +++ b/src/mlpack/methods/ann/layer/spatial_dropout_impl.hpp @@ -51,6 +51,9 @@ template void SpatialDropout::Forward( const arma::Mat& input, arma::Mat& output) { + Log::Assert(input.n_rows % size == 0, "Input features must be divisible \ + by feature maps."); + if (!reset) { batchSize = input.n_cols; diff --git a/src/mlpack/methods/ann/layer/transposed_convolution.hpp b/src/mlpack/methods/ann/layer/transposed_convolution.hpp index a7a89b1dbc..f637ca3355 100644 --- a/src/mlpack/methods/ann/layer/transposed_convolution.hpp +++ b/src/mlpack/methods/ann/layer/transposed_convolution.hpp @@ -274,6 +274,17 @@ class TransposedConvolution //! Modify the right padding width. size_t& PadWRight() { return padWRight; } + //! Get the shape of the input. + size_t InputShape() const + { + return inputHeight * inputWidth * inSize; + } + + //! Get the size of the weight matrix. + size_t WeightSize() const + { + return (outSize * inSize * kernelWidth * kernelHeight) + outSize; + } /** * Serialize the layer. */ diff --git a/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp b/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp index 03770c9095..47cf2cd6c8 100644 --- a/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/transposed_convolution_impl.hpp @@ -124,8 +124,7 @@ TransposedConvolution< outputWidth(outputWidth), outputHeight(outputHeight) { - weights.set_size((outSize * inSize * kernelWidth * kernelHeight) + outSize, - 1); + weights.set_size(WeightSize(), 1); // Transform paddingType to lowercase. std::string paddingTypeLow = paddingType; util::ToLower(paddingType, paddingTypeLow); diff --git a/src/mlpack/methods/ann/layer/virtual_batch_norm_impl.hpp b/src/mlpack/methods/ann/layer/virtual_batch_norm_impl.hpp index 2b93960b82..7bd20415a2 100644 --- a/src/mlpack/methods/ann/layer/virtual_batch_norm_impl.hpp +++ b/src/mlpack/methods/ann/layer/virtual_batch_norm_impl.hpp @@ -67,6 +67,9 @@ template void VirtualBatchNorm::Forward( const arma::Mat& input, arma::Mat& output) { + Log::Assert(input.n_rows % size == 0, "Input features must be divisible \ + by feature maps."); + inputParameter = input; arma::mat inputMean = arma::mean(input, 1); arma::mat inputMeanSquared = arma::mean(arma::square(input), 1); diff --git a/src/mlpack/methods/ann/layer_names.hpp b/src/mlpack/methods/ann/layer_names.hpp index be1b1f7fcb..15596efea3 100644 --- a/src/mlpack/methods/ann/layer_names.hpp +++ b/src/mlpack/methods/ann/layer_names.hpp @@ -206,6 +206,17 @@ class LayerNameVisitor : public boost::static_visitor return "meanpooling"; } + /** + * Return the name of the given layer of type LpPooling as a string. + * + * @param * Given layer of type LpPooling. + * @return The string representation of the layer. + */ + std::string LayerString(LpPooling<>* /*layer*/) const + { + return "lppooling"; + } + /** * Return the name of the given layer of type MultiplyConstant as a string. * diff --git a/src/mlpack/methods/ann/loss_functions/CMakeLists.txt b/src/mlpack/methods/ann/loss_functions/CMakeLists.txt index e04e38dc4c..12d9d1718a 100644 --- a/src/mlpack/methods/ann/loss_functions/CMakeLists.txt +++ b/src/mlpack/methods/ann/loss_functions/CMakeLists.txt @@ -1,32 +1,40 @@ # Define the files we need to compile # Anything not in this list will not be compiled into mlpack. set(SOURCES - cross_entropy_error.hpp - cross_entropy_error_impl.hpp + binary_cross_entropy_loss.hpp + binary_cross_entropy_loss_impl.hpp cosine_embedding_loss.hpp cosine_embedding_loss_impl.hpp dice_loss.hpp dice_loss_impl.hpp earth_mover_distance.hpp earth_mover_distance_impl.hpp + empty_loss.hpp + empty_loss_impl.hpp huber_loss.hpp huber_loss_impl.hpp + hinge_embedding_loss.hpp + hinge_embedding_loss_impl.hpp + hinge_loss.hpp + hinge_loss_impl.hpp kl_divergence.hpp kl_divergence_impl.hpp - margin_ranking_loss.hpp - margin_ranking_loss_impl.hpp - mean_bias_error.hpp - mean_bias_error_impl.hpp l1_loss.hpp l1_loss_impl.hpp + log_cosh_loss.hpp + log_cosh_loss_impl.hpp + margin_ranking_loss.hpp + margin_ranking_loss_impl.hpp + mean_absolute_percentage_error.hpp + mean_absolute_percentage_error_impl.hpp + mean_bias_error.hpp + mean_bias_error_impl.hpp mean_squared_error.hpp mean_squared_error_impl.hpp mean_squared_logarithmic_error.hpp mean_squared_logarithmic_error_impl.hpp negative_log_likelihood.hpp negative_log_likelihood_impl.hpp - log_cosh_loss.hpp - log_cosh_loss_impl.hpp poisson_nll_loss.hpp poisson_nll_loss_impl.hpp reconstruction_loss.hpp @@ -35,12 +43,8 @@ set(SOURCES sigmoid_cross_entropy_error_impl.hpp soft_margin_loss.hpp soft_margin_loss_impl.hpp - hinge_embedding_loss.hpp - hinge_embedding_loss_impl.hpp - empty_loss.hpp - empty_loss_impl.hpp - mean_absolute_percentage_error.hpp - mean_absolute_percentage_error_impl.hpp + triplet_margin_loss.hpp + triplet_margin_loss_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/ann/loss_functions/cross_entropy_error.hpp b/src/mlpack/methods/ann/loss_functions/binary_cross_entropy_loss.hpp similarity index 57% rename from src/mlpack/methods/ann/loss_functions/cross_entropy_error.hpp rename to src/mlpack/methods/ann/loss_functions/binary_cross_entropy_loss.hpp index 3696abb0ab..cc114da81f 100644 --- a/src/mlpack/methods/ann/loss_functions/cross_entropy_error.hpp +++ b/src/mlpack/methods/ann/loss_functions/binary_cross_entropy_loss.hpp @@ -2,7 +2,7 @@ * @file methods/ann/loss_functions/cross_entropy_error.hpp * @author Konstantin Sidorov * - * Definition of the cross-entropy performance function. + * Definition of the binary-cross-entropy performance function. * * mlpack is free software; you may redistribute it and/or modify it under the * terms of the 3-clause BSD license. You should have received a copy of the @@ -18,9 +18,8 @@ namespace mlpack { namespace ann /** Artificial Neural Network. */ { /** - * The cross-entropy performance function measures the network's - * performance according to the cross-entropy - * between the input and target distributions. + * The binary-cross-entropy performance function measures the + * Binary Cross Entropy between the target and the output. * * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, * arma::sp_mat or arma::cube). @@ -31,38 +30,42 @@ template < typename InputDataType = arma::mat, typename OutputDataType = arma::mat > -class CrossEntropyError +class BCELoss { public: /** - * Create the CrossEntropyError object. + * Create the BinaryCrossEntropyLoss object. * * @param eps The minimum value used for computing logarithms * and denominators in a numerically stable way. + * @param reduction Reduction type. If true, it returns the mean of + * the loss. Else, it returns the sum. */ - CrossEntropyError(const double eps = 1e-10); + BCELoss(const double eps = 1e-10, const bool reduction = true); /** * Computes the cross-entropy function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } @@ -74,6 +77,11 @@ class CrossEntropyError //! Modify the epsilon. double& Eps() { return eps; } + //! Get the reduction. + bool Reduction() const { return reduction; } + //! Set the reduction. + bool& Reduction() { return reduction; } + /** * Serialize the layer. */ @@ -86,12 +94,25 @@ class CrossEntropyError //! The minimum value used for computing logarithms and denominators double eps; -}; // class CrossEntropyError + + //! Reduction type. If true, performs mean of loss else sum. + bool reduction; +}; // class BCELoss + +/** + * Adding alias of BCELoss. + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +using CrossEntropyError = BCELoss< + InputDataType, OutputDataType>; } // namespace ann } // namespace mlpack // Include implementation. -#include "cross_entropy_error_impl.hpp" +#include "binary_cross_entropy_loss_impl.hpp" #endif diff --git a/src/mlpack/methods/ann/loss_functions/binary_cross_entropy_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/binary_cross_entropy_loss_impl.hpp new file mode 100644 index 0000000000..89e7aaf1c2 --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/binary_cross_entropy_loss_impl.hpp @@ -0,0 +1,68 @@ +/** + * @file methods/ann/loss_functions/binary_cross_entropy_loss_impl.hpp + * @author Konstantin Sidorov + * + * Implementation of the binary-cross-entropy performance function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTIONS_CROSS_ENTROPY_ERROR_IMPL_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTIONS_CROSS_ENTROPY_ERROR_IMPL_HPP + +// In case it hasn't yet been included. +#include "binary_cross_entropy_loss.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +BCELoss::BCELoss( + const double eps, const bool reduction) : eps(eps), reduction(reduction) +{ + // Nothing to do here. +} + +template +template +typename PredictionType::elem_type +BCELoss::Forward( + const PredictionType& prediction, + const TargetType& target) +{ + typedef typename PredictionType::elem_type ElemType; + + ElemType loss = -arma::accu(target % arma::log(prediction + eps) + + (1. - target) % arma::log(1. - prediction + eps)); + if (reduction) + loss /= prediction.n_elem; + return loss; +} + +template +template +void BCELoss::Backward( + const PredictionType& prediction, + const TargetType& target, + LossType& loss) +{ + loss = (1. - target) / (1. - prediction + eps) - target / (prediction + eps); + if (reduction) + loss /= prediction.n_elem; +} + +template +template +void BCELoss::serialize( + Archive& ar, + const uint32_t /* version */) +{ + ar(CEREAL_NVP(eps)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp index ab3296b41d..c7df95e4a4 100644 --- a/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss.hpp @@ -57,24 +57,26 @@ class CosineEmbeddingLoss /** * Ordinary feed forward pass of a neural network. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the input parameter. InputDataType& InputParameter() const { return inputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp index 194d6a912a..129a12c26c 100644 --- a/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/cosine_embedding_loss_impl.hpp @@ -27,20 +27,20 @@ CosineEmbeddingLoss::CosineEmbeddingLoss( } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type CosineEmbeddingLoss::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - typedef typename InputType::elem_type ElemType; + typedef typename PredictionType::elem_type ElemType; - const size_t cols = input.n_cols; - const size_t batchSize = input.n_elem / cols; - if (arma::size(input) != arma::size(target)) + const size_t cols = prediction.n_cols; + const size_t batchSize = prediction.n_elem / cols; + if (arma::size(prediction) != arma::size(target)) Log::Fatal << "Input Tensors must have same dimensions." << std::endl; - arma::colvec inputTemp1 = arma::vectorise(input); + arma::colvec inputTemp1 = arma::vectorise(prediction); arma::colvec inputTemp2 = arma::vectorise(target); ElemType loss = 0.0; @@ -65,23 +65,23 @@ CosineEmbeddingLoss::Forward( } template -template +template void CosineEmbeddingLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - typedef typename InputType::elem_type ElemType; + typedef typename PredictionType::elem_type ElemType; - const size_t cols = input.n_cols; - if (arma::size(input) != arma::size(target)) + const size_t cols = prediction.n_cols; + if (arma::size(prediction) != arma::size(target)) Log::Fatal << "Input Tensors must have same dimensions." << std::endl; - arma::colvec inputTemp1 = arma::vectorise(input); + arma::colvec inputTemp1 = arma::vectorise(prediction); arma::colvec inputTemp2 = arma::vectorise(target); - output.set_size(arma::size(inputTemp1)); + loss.set_size(arma::size(inputTemp1)); - arma::colvec outputTemp(output.memptr(), inputTemp1.n_elem, + arma::colvec outputTemp(loss.memptr(), inputTemp1.n_elem, false, false); for (size_t i = 0; i < inputTemp1.n_elem; i += cols) { diff --git a/src/mlpack/methods/ann/loss_functions/cross_entropy_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/cross_entropy_error_impl.hpp deleted file mode 100644 index 6428714a52..0000000000 --- a/src/mlpack/methods/ann/loss_functions/cross_entropy_error_impl.hpp +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file methods/ann/loss_functions/cross_entropy_error_impl.hpp - * @author Konstantin Sidorov - * - * Implementation of the cross-entropy performance function. - * - * mlpack is free software; you may redistribute it and/or modify it under the - * terms of the 3-clause BSD license. You should have received a copy of the - * 3-clause BSD license along with mlpack. If not, see - * http://www.opensource.org/licenses/BSD-3-Clause for more information. - */ -#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTIONS_CROSS_ENTROPY_ERROR_IMPL_HPP -#define MLPACK_METHODS_ANN_LOSS_FUNCTIONS_CROSS_ENTROPY_ERROR_IMPL_HPP - -// In case it hasn't yet been included. -#include "cross_entropy_error.hpp" - -namespace mlpack { -namespace ann /** Artificial Neural Network. */ { - -template -CrossEntropyError::CrossEntropyError( - const double eps) : eps(eps) -{ - // Nothing to do here. -} - -template -template -typename InputType::elem_type -CrossEntropyError::Forward( - const InputType& input, - const TargetType& target) -{ - return -arma::accu(target % arma::log(input + eps) + - (1. - target) % arma::log(1. - input + eps)); -} - -template -template -void CrossEntropyError::Backward( - const InputType& input, - const TargetType& target, - OutputType& output) -{ - output = (1. - target) / (1. - input + eps) - target / (input + eps); -} - -template -template -void CrossEntropyError::serialize( - Archive& ar, - const uint32_t /* version */) -{ - ar(CEREAL_NVP(eps)); -} - -} // namespace ann -} // namespace mlpack - -#endif diff --git a/src/mlpack/methods/ann/loss_functions/dice_loss.hpp b/src/mlpack/methods/ann/loss_functions/dice_loss.hpp index c4dd6da2d6..ce4a80f54d 100644 --- a/src/mlpack/methods/ann/loss_functions/dice_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/dice_loss.hpp @@ -60,24 +60,26 @@ class DiceLoss /** * Computes the dice loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/dice_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/dice_loss_impl.hpp index 3df59dee7a..2a1835dc70 100644 --- a/src/mlpack/methods/ann/loss_functions/dice_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/dice_loss_impl.hpp @@ -26,27 +26,27 @@ DiceLoss::DiceLoss( } template -template -typename InputType::elem_type DiceLoss::Forward( - const InputType& input, - const TargetType& target) +template +typename PredictionType::elem_type DiceLoss + ::Forward(const PredictionType& prediction, + const TargetType& target) { - return 1 - ((2 * arma::accu(target % input) + smooth) / + return 1 - ((2 * arma::accu(target % prediction) + smooth) / (arma::accu(target % target) + arma::accu( - input % input) + smooth)); + prediction % prediction) + smooth)); } template -template +template void DiceLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = -2 * (target * (arma::accu(input % input) + - arma::accu(target % target) + smooth) - input * - (2 * arma::accu(target % input) + smooth)) / std::pow( - arma::accu(target % target) + arma::accu(input % input) + loss = -2 * (target * (arma::accu(prediction % prediction) + + arma::accu(target % target) + smooth) - prediction * + (2 * arma::accu(target % prediction) + smooth)) / std::pow( + arma::accu(target % target) + arma::accu(prediction % prediction) + smooth, 2.0); } diff --git a/src/mlpack/methods/ann/loss_functions/earth_mover_distance.hpp b/src/mlpack/methods/ann/loss_functions/earth_mover_distance.hpp index 7b4ebd479b..a5afbf37c2 100644 --- a/src/mlpack/methods/ann/loss_functions/earth_mover_distance.hpp +++ b/src/mlpack/methods/ann/loss_functions/earth_mover_distance.hpp @@ -41,24 +41,26 @@ class EarthMoverDistance /** * Ordinary feed forward pass of a neural network. * - * @param input Input data used for evaluating the specified function. + * @param prediction Prediction used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Prediction used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/earth_mover_distance_impl.hpp b/src/mlpack/methods/ann/loss_functions/earth_mover_distance_impl.hpp index 452a80ca99..8f6ab6f52e 100644 --- a/src/mlpack/methods/ann/loss_functions/earth_mover_distance_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/earth_mover_distance_impl.hpp @@ -25,23 +25,23 @@ EarthMoverDistance::EarthMoverDistance() } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type EarthMoverDistance::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - return -arma::accu(target % input); + return -arma::accu(target % prediction); } template -template +template void EarthMoverDistance::Backward( - const InputType& /* input */, + const PredictionType& /* prediction */, const TargetType& target, - OutputType& output) + LossType& loss) { - output = -target; + loss = -target; } template diff --git a/src/mlpack/methods/ann/loss_functions/empty_loss.hpp b/src/mlpack/methods/ann/loss_functions/empty_loss.hpp index 4d44bcfcfe..8cc8caae9d 100644 --- a/src/mlpack/methods/ann/loss_functions/empty_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/empty_loss.hpp @@ -43,23 +43,25 @@ class EmptyLoss /** * Computes the Empty loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Prediction used for evaluating the specified loss + * function. * @param target The target vector. */ - template - double Forward(const InputType& input, const TargetType& target); + template + double Forward(const PredictionType& input, const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Prediction used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); }; // class EmptyLoss } // namespace ann diff --git a/src/mlpack/methods/ann/loss_functions/empty_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/empty_loss_impl.hpp index 8af2c51655..190792030e 100644 --- a/src/mlpack/methods/ann/loss_functions/empty_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/empty_loss_impl.hpp @@ -27,21 +27,21 @@ EmptyLoss::EmptyLoss() } template -template +template double EmptyLoss::Forward( - const InputType& /* input */, const TargetType& /* target */) + const PredictionType& /* prediction */, const TargetType& /* target */) { return 0; } template -template +template void EmptyLoss::Backward( - const InputType& /* input */, + const PredictionType& /* prediction */, const TargetType& target, - OutputType& output) + LossType& loss) { - output = target; + loss = target; } } // namespace ann diff --git a/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss.hpp b/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss.hpp index b2a75502f2..99e5d50ff2 100644 --- a/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss.hpp @@ -44,24 +44,26 @@ class HingeEmbeddingLoss /** * Computes the Hinge Embedding loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Prediction used for evaluating the specified loss + * function. * @param target Target data to compare with. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Prediction used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss_impl.hpp index f0f48fb42a..1f6456c71f 100644 --- a/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/hinge_embedding_loss_impl.hpp @@ -26,25 +26,25 @@ HingeEmbeddingLoss::HingeEmbeddingLoss() } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type HingeEmbeddingLoss::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { TargetType temp = target - (target == 0); - return (arma::accu(arma::max(1-input % temp, 0.))) / target.n_elem; + return (arma::accu(arma::max(1 - prediction % temp, 0.))) / target.n_elem; } template -template +template void HingeEmbeddingLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { TargetType temp = target - (target == 0); - output = (input < 1 / temp) % -temp; + loss = (prediction < 1 / temp) % -temp; } template diff --git a/src/mlpack/methods/ann/loss_functions/hinge_loss.hpp b/src/mlpack/methods/ann/loss_functions/hinge_loss.hpp new file mode 100644 index 0000000000..60a2002782 --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/hinge_loss.hpp @@ -0,0 +1,105 @@ +/** + * @file methods/ann/loss_functions/hinge_loss.hpp + * @author Anush Kini + * + * Definition of the Hinge Loss Function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_HINGE_LOSS_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_HINGE_LOSS_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * Computes the hinge loss between \f$y_true\f$ and \f$y_pred\f$. Expects + * \f$y_true\f$ to be either -1 or 1. If \f$y_true\f$ is either 0 or 1, a + * temporary conversion is made to calculate the loss. + * The hinge loss \f$l(y_true, y_pred)\f$ is defined as + * \f$l(y_true, y_pred) = max(0, 1 - y_true*y_pred)\f$. + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class HingeLoss +{ + public: + /** + * Create HingeLoss object. + * + * @param reduction Specifies the reduction to apply to the output. If false, + * 'mean' reduction is used, where sum of the output will be + * divided by the number of elements in the output. If + * true, 'sum' reduction is used and the output will be + * summed. It is set to true by default. + */ + HingeLoss(const bool reduction = true); + + /** + * Computes the Hinge loss function. + * + * @param prediction Prediction used for evaluating the specified loss + * function. + * @param target Target data to compare with. + */ + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); + + /** + * Ordinary feed backward pass of a neural network. + * + * @param prediction Prediction used for evaluating the specified loss + * function. + * @param target The target vector. + * @param loss The calculated error. + */ + template + void Backward(const PredictionType& prediction, + const TargetType& target, + LossType& loss); + + //! Get the output parameter. + OutputDataType& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the type of reduction used. + bool Reduction() const { return reduction; } + //! Modify the type of reduction used. + bool& Reduction() { return reduction; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! The boolean value that tells if reduction is sum or mean. + bool reduction; +}; // class HingeLoss + +} // namespace ann +} // namespace mlpack + +// include implementation +#include "hinge_loss_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp new file mode 100644 index 0000000000..6de5a553fa --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp @@ -0,0 +1,75 @@ +/** + * @file methods/ann/loss_functions/hinge_loss_impl.hpp + * @author Anush Kini + * + * Implementation of the Hinge loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ + +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_HINGE_LOSS_IMPL_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_HINGE_LOSS_IMPL_HPP + +// In case it hasn't yet been included. +#include "hinge_loss.hpp" + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +template +HingeLoss::HingeLoss(const bool reduction): + reduction(reduction) +{ + // Nothing to do here. +} + +template +template +typename PredictionType::elem_type +HingeLoss::Forward( + const PredictionType& prediction, + const TargetType& target) +{ + TargetType temp = target - (target == 0); + TargetType temp_zeros(size(target), arma::fill::zeros); + + PredictionType loss = arma::max(temp_zeros, 1 - prediction % temp); + + typename PredictionType::elem_type lossSum = arma::accu(loss); + + if (reduction) + return lossSum; + + return lossSum / loss.n_elem; +} + +template +template +void HingeLoss::Backward( + const PredictionType& prediction, + const TargetType& target, + LossType& loss) +{ + TargetType temp = target - (target == 0); + loss = (prediction < (1 / temp)) % -temp; + + if (!reduction) + loss /= target.n_elem; +} + +template +template +void HingeLoss::serialize( + Archive& ar, + const uint32_t /* version */) +{ + ar(CEREAL_NVP(reduction)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/loss_functions/huber_loss.hpp b/src/mlpack/methods/ann/loss_functions/huber_loss.hpp index 29896f7ac3..f5ce03dba4 100644 --- a/src/mlpack/methods/ann/loss_functions/huber_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/huber_loss.hpp @@ -48,24 +48,26 @@ class HuberLoss /** * Computes the Huber Loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/huber_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/huber_loss_impl.hpp index c97305d7b5..d692734754 100644 --- a/src/mlpack/methods/ann/loss_functions/huber_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/huber_loss_impl.hpp @@ -29,39 +29,39 @@ HuberLoss::HuberLoss( } template -template -typename InputType::elem_type -HuberLoss::Forward(const InputType& input, +template +typename PredictionType::elem_type +HuberLoss::Forward(const PredictionType& prediction, const TargetType& target) { - typedef typename InputType::elem_type ElemType; + typedef typename PredictionType::elem_type ElemType; ElemType loss = 0; - for (size_t i = 0; i < input.n_elem; ++i) + for (size_t i = 0; i < prediction.n_elem; ++i) { - const ElemType absError = std::abs(target[i] - input[i]); + const ElemType absError = std::abs(target[i] - prediction[i]); loss += absError > delta ? delta * (absError - 0.5 * delta) : 0.5 * std::pow(absError, 2); } - return mean ? loss / input.n_elem : loss; + return mean ? loss / prediction.n_elem : loss; } template -template +template void HuberLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - typedef typename InputType::elem_type ElemType; + typedef typename PredictionType::elem_type ElemType; - output.set_size(size(input)); - for (size_t i = 0; i < output.n_elem; ++i) + loss.set_size(size(prediction)); + for (size_t i = 0; i < loss.n_elem; ++i) { - const ElemType absError = std::abs(target[i] - input[i]); - output[i] = absError > delta - ? - delta * (target[i] - input[i]) / absError : input[i] - target[i]; + const ElemType absError = std::abs(target[i] - prediction[i]); + loss[i] = absError > delta + ? - delta * (target[i] - prediction[i]) / absError : prediction[i] - target[i]; if (mean) - output[i] /= output.n_elem; + loss[i] /= loss.n_elem; } } diff --git a/src/mlpack/methods/ann/loss_functions/kl_divergence.hpp b/src/mlpack/methods/ann/loss_functions/kl_divergence.hpp index 680343b803..5eacb03b36 100644 --- a/src/mlpack/methods/ann/loss_functions/kl_divergence.hpp +++ b/src/mlpack/methods/ann/loss_functions/kl_divergence.hpp @@ -56,24 +56,26 @@ class KLDivergence /** * Computes the Kullback–Leibler divergence error function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target Target data to compare with. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/kl_divergence_impl.hpp b/src/mlpack/methods/ann/loss_functions/kl_divergence_impl.hpp index 3c84345ee3..aa1a5c1b62 100644 --- a/src/mlpack/methods/ann/loss_functions/kl_divergence_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/kl_divergence_impl.hpp @@ -27,36 +27,36 @@ KLDivergence::KLDivergence(const bool takeMean) : } template -template -typename InputType::elem_type -KLDivergence::Forward(const InputType& input, +template +typename PredictionType::elem_type +KLDivergence::Forward(const PredictionType& prediction, const TargetType& target) { if (takeMean) { return arma::as_scalar(arma::mean( - arma::mean(input % (arma::log(input) - arma::log(target))))); + arma::mean(prediction % (arma::log(prediction) - arma::log(target))))); } else { - return arma::accu(input % (arma::log(input) - arma::log(target))); + return arma::accu(prediction % (arma::log(prediction) - arma::log(target))); } } template -template +template void KLDivergence::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { if (takeMean) { - output = arma::mean(arma::mean(arma::log(input) - arma::log(target) + 1)); + loss = arma::mean(arma::mean(arma::log(prediction) - arma::log(target) + 1)); } else { - output = arma::accu(arma::log(input) - arma::log(target) + 1); + loss = arma::accu(arma::log(prediction) - arma::log(target) + 1); } } diff --git a/src/mlpack/methods/ann/loss_functions/l1_loss.hpp b/src/mlpack/methods/ann/loss_functions/l1_loss.hpp index dadf1fe99b..552089bbd0 100644 --- a/src/mlpack/methods/ann/loss_functions/l1_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/l1_loss.hpp @@ -44,24 +44,26 @@ class L1Loss /** * Computes the L1 Loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/l1_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/l1_loss_impl.hpp index d1ab0599cc..100823ad90 100644 --- a/src/mlpack/methods/ann/loss_functions/l1_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/l1_loss_impl.hpp @@ -26,26 +26,26 @@ L1Loss::L1Loss(const bool mean): } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type L1Loss::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { if (mean) - return arma::accu(arma::mean(input - target)); + return arma::accu(arma::mean(prediction - target)); - return arma::accu(input - target); + return arma::accu(prediction - target); } template -template +template void L1Loss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = arma::sign(input - target); + loss = arma::sign(prediction - target); } template diff --git a/src/mlpack/methods/ann/loss_functions/log_cosh_loss.hpp b/src/mlpack/methods/ann/loss_functions/log_cosh_loss.hpp index 9cc8172d33..db3090488e 100644 --- a/src/mlpack/methods/ann/loss_functions/log_cosh_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/log_cosh_loss.hpp @@ -51,24 +51,26 @@ class LogCoshLoss /** * Computes the Log-Hyperbolic-Cosine loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target Target data to compare with. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/log_cosh_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/log_cosh_loss_impl.hpp index f4c4da63fe..63a72cfa97 100644 --- a/src/mlpack/methods/ann/loss_functions/log_cosh_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/log_cosh_loss_impl.hpp @@ -27,22 +27,23 @@ LogCoshLoss::LogCoshLoss(const double a) : } template -template -typename InputType::elem_type -LogCoshLoss::Forward(const InputType& input, - const TargetType& target) +template +typename PredictionType::elem_type +LogCoshLoss::Forward( + const PredictionType& prediction, + const TargetType& target) { - return arma::accu(arma::log(arma::cosh(a * (target - input)))) / a; + return arma::accu(arma::log(arma::cosh(a * (target - prediction)))) / a; } template -template +template void LogCoshLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = arma::tanh(a * (target - input)); + loss = arma::tanh(a * (target - prediction)); } template diff --git a/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp index a590cdf4d1..28971c89f0 100644 --- a/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss.hpp @@ -45,29 +45,30 @@ class MarginRankingLoss /** * Computes the Margin Ranking Loss function. * - * @param input Concatenation of the two inputs for evaluating the specified - * function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The label vector which contains values of -1 or 1. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated concatenated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The label vector which contains -1 or 1 values. - * @param output The calculated error. + * @param loss The calculated error. */ template < - typename InputType, + typename PredictionType, typename TargetType, - typename OutputType + typename LossType > - void Backward(const InputType& input, + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp index cb16e3f79f..17c63ca8e8 100644 --- a/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/margin_ranking_loss_impl.hpp @@ -26,37 +26,41 @@ MarginRankingLoss::MarginRankingLoss( } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type MarginRankingLoss::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - const int inputRows = input.n_rows; - const InputType& input1 = input.rows(0, inputRows / 2 - 1); - const InputType& input2 = input.rows(inputRows / 2, inputRows - 1); + const int predictionRows = prediction.n_rows; + const PredictionType& prediction1 = prediction.rows(0, + predictionRows / 2 - 1); + const PredictionType& prediction2 = prediction.rows(predictionRows / 2, + predictionRows - 1); return arma::accu(arma::max(arma::zeros(size(target)), - -target % (input1 - input2) + margin)) / target.n_cols; + -target % (prediction1 - prediction2) + margin)) / target.n_cols; } template template < - typename InputType, + typename PredictionType, typename TargetType, - typename OutputType + typename LossType > void MarginRankingLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - const int inputRows = input.n_rows; - const InputType& input1 = input.rows(0, inputRows / 2 - 1); - const InputType& input2 = input.rows(inputRows / 2, inputRows - 1); - output = -target % (input1 - input2) + margin; - output.elem(arma::find(output >= 0)).ones(); - output.elem(arma::find(output < 0)).zeros(); - output = (input2 - input1) % output / target.n_cols; + const int predictionRows = prediction.n_rows; + const PredictionType& prediction1 = prediction.rows(0, + predictionRows / 2 - 1); + const PredictionType& prediction2 = prediction.rows(predictionRows / 2, + predictionRows - 1); + loss = -target % (prediction1 - prediction2) + margin; + loss.elem(arma::find(loss >= 0)).ones(); + loss.elem(arma::find(loss < 0)).zeros(); + loss = (prediction2 - prediction1) % loss / target.n_cols; } template diff --git a/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error.hpp b/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error.hpp index 17a0f355e4..d6eb6e5e89 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error.hpp @@ -57,24 +57,26 @@ class MeanAbsolutePercentageError /** * Computes the mean absolute percentage error function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error_impl.hpp index 52b6281a18..b573654e7f 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_absolute_percentage_error_impl.hpp @@ -26,25 +26,25 @@ MeanAbsolutePercentageError() } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type MeanAbsolutePercentageError::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - InputType loss = arma::abs((input - target) / target); + PredictionType loss = arma::abs((prediction - target) / target); return arma::accu(loss) * (100 / target.n_cols); } template -template +template void MeanAbsolutePercentageError::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = (((arma::conv_to::from(input < target) * -2) + 1) / + loss = (((arma::conv_to::from(prediction < target) * -2) + 1) / target) * (100 / target.n_cols); } diff --git a/src/mlpack/methods/ann/loss_functions/mean_bias_error.hpp b/src/mlpack/methods/ann/loss_functions/mean_bias_error.hpp index 043ff97d5b..b9836f856f 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_bias_error.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_bias_error.hpp @@ -41,24 +41,26 @@ class MeanBiasError /** * Computes the mean bias error function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/mean_bias_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/mean_bias_error_impl.hpp index 3a9f18b51f..0343558693 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_bias_error_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_bias_error_impl.hpp @@ -26,23 +26,24 @@ MeanBiasError::MeanBiasError() } template -template -typename InputType::elem_type -MeanBiasError::Forward(const InputType& input, - const TargetType& target) +template +typename PredictionType::elem_type +MeanBiasError::Forward( + const PredictionType& prediction, + const TargetType& target) { - return arma::accu(target - input) / target.n_cols; + return arma::accu(target - prediction) / target.n_cols; } template -template +template void MeanBiasError::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& /* target */, - OutputType& output) + LossType& loss) { - output.set_size(arma::size(input)); - output.fill(-1.0); + loss.set_size(arma::size(prediction)); + loss.fill(-1.0); } template diff --git a/src/mlpack/methods/ann/loss_functions/mean_squared_error.hpp b/src/mlpack/methods/ann/loss_functions/mean_squared_error.hpp index 3c36b33611..0cc3f6378d 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_squared_error.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_squared_error.hpp @@ -42,24 +42,26 @@ class MeanSquaredError /** * Computes the mean squared error function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp index 85cdbb3b45..ee4ae8c021 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_squared_error_impl.hpp @@ -25,23 +25,23 @@ MeanSquaredError::MeanSquaredError() } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type MeanSquaredError::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - return arma::accu(arma::square(input - target)) / target.n_cols; + return arma::accu(arma::square(prediction - target)) / target.n_cols; } template -template +template void MeanSquaredError::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = 2 * (input - target) / target.n_cols; + loss = 2 * (prediction - target) / target.n_cols; } template diff --git a/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error.hpp b/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error.hpp index 4e9ca3c4de..14a7a08ad0 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error.hpp @@ -41,24 +41,26 @@ class MeanSquaredLogarithmicError /** * Computes the mean squared logarithmic error function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error_impl.hpp index b34c8019cb..ffb1ea7cd8 100644 --- a/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/mean_squared_logarithmic_error_impl.hpp @@ -26,25 +26,25 @@ MeanSquaredLogarithmicError } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type MeanSquaredLogarithmicError::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { return arma::accu(arma::square(arma::log(1. + target) - - arma::log(1. + input))) / target.n_cols; + arma::log(1. + prediction))) / target.n_cols; } template -template +template void MeanSquaredLogarithmicError::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = 2 * (arma::log(1. + input) - arma::log(1. + target)) / - ((1. + input) * target.n_cols); + loss = 2 * (arma::log(1. + prediction) - arma::log(1. + target)) / + ((1. + prediction) * target.n_cols); } template diff --git a/src/mlpack/methods/ann/loss_functions/negative_log_likelihood.hpp b/src/mlpack/methods/ann/loss_functions/negative_log_likelihood.hpp index bd5ad313cb..4f97c152f5 100644 --- a/src/mlpack/methods/ann/loss_functions/negative_log_likelihood.hpp +++ b/src/mlpack/methods/ann/loss_functions/negative_log_likelihood.hpp @@ -43,13 +43,14 @@ class NegativeLogLikelihood /** * Computes the Negative log likelihood. * - * @param input Input data used for evaluating the specified function. + * @param iprediction Predictions used for evaluating the specified loss + * function. * @param target The target vector, that contains the class index in the range * between 1 and the number of classes. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. The negative log @@ -57,15 +58,16 @@ class NegativeLogLikelihood * each class. The layer also expects a class index, in the range between 1 * and the number of classes, as target when calling the Forward function. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector, that contains the class index in the range * between 1 and the number of classes. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the input parameter. InputDataType& InputParameter() const { return inputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp b/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp index 3634bc4738..1eace1d772 100644 --- a/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/negative_log_likelihood_impl.hpp @@ -25,41 +25,39 @@ NegativeLogLikelihood::NegativeLogLikelihood() } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type NegativeLogLikelihood::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - typedef typename InputType::elem_type ElemType; + typedef typename PredictionType::elem_type ElemType; ElemType output = 0; - for (size_t i = 0; i < input.n_cols; ++i) + for (size_t i = 0; i < prediction.n_cols; ++i) { - size_t currentTarget = target(i) - 1; - Log::Assert(currentTarget < input.n_rows, + Log::Assert(target(i) >= 0 && target(i) < prediction.n_rows, "Target class out of range."); - output -= input(currentTarget, i); + output -= prediction(target(i), i); } return output; } template -template +template void NegativeLogLikelihood::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = arma::zeros(input.n_rows, input.n_cols); - for (size_t i = 0; i < input.n_cols; ++i) + loss = arma::zeros(prediction.n_rows, prediction.n_cols); + for (size_t i = 0; i < prediction.n_cols; ++i) { - size_t currentTarget = target(i) - 1; - Log::Assert(currentTarget < input.n_rows, + Log::Assert(target(i) >= 0 && target(i) < prediction.n_rows, "Target class out of range."); - output(currentTarget, i) = -1; + loss(target(i), i) = -1; } } diff --git a/src/mlpack/methods/ann/loss_functions/poisson_nll_loss.hpp b/src/mlpack/methods/ann/loss_functions/poisson_nll_loss.hpp index 415deaa00a..31e1cb5620 100644 --- a/src/mlpack/methods/ann/loss_functions/poisson_nll_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/poisson_nll_loss.hpp @@ -55,12 +55,13 @@ class PoissonNLLLoss /** * Computes the Poisson negative log likelihood Loss. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector, that contains the class index in the range * between 1 and the number of classes. */ - template - typename InputDataType::elem_type Forward(const InputType& input, + template + typename InputDataType::elem_type Forward(const PredictionType& prediction, const TargetType& target); /** @@ -69,15 +70,16 @@ class PoissonNLLLoss * It expects a class index, in the range between 1 and the number of classes, * as target when calling the Forward function. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector, that contains the class index in the range * between 1 and the number of classes. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the input parameter. InputDataType& InputParameter() const { return inputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/poisson_nll_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/poisson_nll_loss_impl.hpp index a3233eff81..05d2d79886 100644 --- a/src/mlpack/methods/ann/loss_functions/poisson_nll_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/poisson_nll_loss_impl.hpp @@ -34,26 +34,26 @@ PoissonNLLLoss::PoissonNLLLoss( } template -template +template typename InputDataType::elem_type PoissonNLLLoss::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - InputType loss(arma::size(input)); + PredictionType loss(arma::size(prediction)); if (logInput) - loss = arma::exp(input) - target % input; + loss = arma::exp(prediction) - target % prediction; else { - CheckProbs(input); - loss = input - target % arma::log(input + eps); + CheckProbs(prediction); + loss = prediction - target % arma::log(prediction + eps); } if (full) { const auto mask = target > 1.0; - const InputType approx = target % arma::log(target) - target + const PredictionType approx = target % arma::log(target) - target + 0.5 * arma::log(2 * M_PI * target); loss.elem(arma::find(mask)) += approx.elem(arma::find(mask)); } @@ -62,21 +62,21 @@ PoissonNLLLoss::Forward( } template -template +template void PoissonNLLLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output.set_size(size(input)); + loss.set_size(size(prediction)); if (logInput) - output = (arma::exp(input) - target); + loss = (arma::exp(prediction) - target); else - output = (1 - target / (input + eps)); + loss = (1 - target / (prediction + eps)); if (mean) - output = output / output.n_elem; + loss = loss / loss.n_elem; } template diff --git a/src/mlpack/methods/ann/loss_functions/reconstruction_loss.hpp b/src/mlpack/methods/ann/loss_functions/reconstruction_loss.hpp index 4ae46b125a..56f6f39cc3 100644 --- a/src/mlpack/methods/ann/loss_functions/reconstruction_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/reconstruction_loss.hpp @@ -45,24 +45,26 @@ class ReconstructionLoss /** * Computes the reconstruction loss. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target matrix. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target matrix. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/reconstruction_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/reconstruction_loss_impl.hpp index 1821ae2f5d..ca5c986f05 100644 --- a/src/mlpack/methods/ann/loss_functions/reconstruction_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/reconstruction_loss_impl.hpp @@ -29,24 +29,24 @@ ReconstructionLoss< } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type ReconstructionLoss::Forward( - const InputType& input, const TargetType& target) + const PredictionType& prediction, const TargetType& target) { - dist = DistType(input); + dist = DistType(prediction); return -dist.LogProbability(target); } template -template +template void ReconstructionLoss::Backward( - const InputType& /* input */, + const PredictionType& /* prediction */, const TargetType& target, - OutputType& output) + LossType& loss) { - dist.LogProbBackward(target, output); - output *= -1; + dist.LogProbBackward(target, loss); + loss *= -1; } template diff --git a/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error.hpp b/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error.hpp index c577fc998e..2d0bed9721 100644 --- a/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error.hpp +++ b/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error.hpp @@ -60,23 +60,25 @@ class SigmoidCrossEntropyError /** * Computes the Sigmoid CrossEntropy Error functions. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. */ - template - inline typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + inline typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - inline void Backward(const InputType& input, + template + inline void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error_impl.hpp b/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error_impl.hpp index 3cfcb0b04f..93b3775e6a 100644 --- a/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/sigmoid_cross_entropy_error_impl.hpp @@ -28,31 +28,31 @@ SigmoidCrossEntropyError } template -template -inline typename InputType::elem_type +template +inline typename PredictionType::elem_type SigmoidCrossEntropyError::Forward( - const InputType& input, + const PredictionType& prediction, const TargetType& target) { - typedef typename InputType::elem_type ElemType; + typedef typename PredictionType::elem_type ElemType; ElemType maximum = 0; - for (size_t i = 0; i < input.n_elem; ++i) + for (size_t i = 0; i < prediction.n_elem; ++i) { - maximum += std::max(input[i], 0.0) + - std::log(1 + std::exp(-std::abs(input[i]))); + maximum += std::max(prediction[i], 0.0) + + std::log(1 + std::exp(-std::abs(prediction[i]))); } - return maximum - arma::accu(input % target); + return maximum - arma::accu(prediction % target); } template -template +template inline void SigmoidCrossEntropyError::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output = 1.0 / (1.0 + arma::exp(-input)) - target; + loss = 1.0 / (1.0 + arma::exp(-prediction)) - target; } template diff --git a/src/mlpack/methods/ann/loss_functions/soft_margin_loss.hpp b/src/mlpack/methods/ann/loss_functions/soft_margin_loss.hpp index 40b8965e83..a35db04d14 100644 --- a/src/mlpack/methods/ann/loss_functions/soft_margin_loss.hpp +++ b/src/mlpack/methods/ann/loss_functions/soft_margin_loss.hpp @@ -48,24 +48,26 @@ class SoftMarginLoss /** * Computes the Soft Margin Loss function. * - * @param input Input data used for evaluating the specified function. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector with same shape as input. */ - template - typename InputType::elem_type Forward(const InputType& input, - const TargetType& target); + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); /** * Ordinary feed backward pass of a neural network. * - * @param input The propagated input activation. + * @param prediction Predictions used for evaluating the specified loss + * function. * @param target The target vector. - * @param output The calculated error. + * @param loss The calculated error. */ - template - void Backward(const InputType& input, + template + void Backward(const PredictionType& prediction, const TargetType& target, - OutputType& output); + LossType& loss); //! Get the output parameter. OutputDataType& OutputParameter() const { return outputParameter; } diff --git a/src/mlpack/methods/ann/loss_functions/soft_margin_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/soft_margin_loss_impl.hpp index d89c20170d..40453564a3 100644 --- a/src/mlpack/methods/ann/loss_functions/soft_margin_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/soft_margin_loss_impl.hpp @@ -26,35 +26,35 @@ SoftMarginLoss(const bool reduction) : reduction(reduction) } template -template -typename InputType::elem_type +template +typename PredictionType::elem_type SoftMarginLoss::Forward( - const InputType& input, const TargetType& target) + const PredictionType& prediction, const TargetType& target) { - InputType loss = arma::log(1 + arma::exp(-target % input)); - typename InputType::elem_type lossSum = arma::accu(loss); + PredictionType loss = arma::log(1 + arma::exp(-target % prediction)); + typename PredictionType::elem_type lossSum = arma::accu(loss); if (reduction) return lossSum; - return lossSum / input.n_elem; + return lossSum / prediction.n_elem; } template -template +template void SoftMarginLoss::Backward( - const InputType& input, + const PredictionType& prediction, const TargetType& target, - OutputType& output) + LossType& loss) { - output.set_size(size(input)); - InputType temp = arma::exp(-target % input); - InputType numerator = -target % temp; - InputType denominator = 1 + temp; - output = numerator / denominator; + loss.set_size(size(prediction)); + PredictionType temp = arma::exp(-target % prediction); + PredictionType numerator = -target % temp; + PredictionType denominator = 1 + temp; + loss = numerator / denominator; if (!reduction) - output = output / input.n_elem; + loss = loss / prediction.n_elem; } template diff --git a/src/mlpack/methods/ann/loss_functions/triplet_margin_loss.hpp b/src/mlpack/methods/ann/loss_functions/triplet_margin_loss.hpp new file mode 100644 index 0000000000..fba54973f0 --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/triplet_margin_loss.hpp @@ -0,0 +1,111 @@ +/** + * @file methods/ann/loss_functions/triplet_margin_loss.hpp + * @author Prince Gupta + * @author Ayush Singh + * + * Definition of the Triplet Margin Loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_ANN_LOSS_FUNCTION_TRIPLET_MARGIN_LOSS_HPP +#define MLPACK_ANN_LOSS_FUNCTION_TRIPLET_MARGIN_LOSS_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */ { + +/** + * The Triplet Margin Loss performance function measures the network's + * performance according to the relative distance from the anchor input + * of the positive (truthy) and negative (falsy) inputs. + * The distance between two samples A and B is defined as square of L2 norm + * of A-B. + * + * For more information, refer the following paper. + * + * @code + * @article{Schroff2015, + * author = {Florian Schroff, Dmitry Kalenichenko, James Philbin}, + * title = {FaceNet: A Unified Embedding for Face Recognition and Clustering}, + * year = {2015}, + * url = {https://arxiv.org/abs/1503.03832}, + * } + * @endcode + * + * @tparam InputDataType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam OutputDataType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template < + typename InputDataType = arma::mat, + typename OutputDataType = arma::mat +> +class TripletMarginLoss +{ + public: + /** + * Create the TripletMarginLoss object. + * + * @param margin The minimum value by which the distance between + * Anchor and Negative sample exceeds the distance + * between Anchor and Positive sample. + */ + TripletMarginLoss(const double margin = 1.0); + + /** + * Computes the Triplet Margin Loss function. + * + * @param prediction Concatenated anchor and positive sample. + * @param target The negative sample. + */ + template + typename PredictionType::elem_type Forward(const PredictionType& prediction, + const TargetType& target); + /** + * Ordinary feed backward pass of a neural network. + * + * @param prediction Concatenated anchor and positive sample. + * @param target The negative sample. + * @param loss The calculated error. + */ + template + void Backward(const PredictionType& prediction, + const TargetType& target, + LossType& loss); + + //! Get the output parameter. + OutputDataType& OutputParameter() const { return outputParameter; } + //! Modify the output parameter. + OutputDataType& OutputParameter() { return outputParameter; } + + //! Get the value of margin. + double Margin() const { return margin; } + //! Modify the value of margin. + double& Margin() { return margin; } + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const unsigned int /* version */); + + private: + //! Locally-stored output parameter object. + OutputDataType outputParameter; + + //! The margin value used in calculating Triplet Margin Loss. + double margin; +}; // class TripletLossMargin + +} // namespace ann +} // namespace mlpack + +// include implementation. +#include "triplet_margin_loss_impl.hpp" + +#endif \ No newline at end of file diff --git a/src/mlpack/methods/ann/loss_functions/triplet_margin_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/triplet_margin_loss_impl.hpp new file mode 100644 index 0000000000..a007490be0 --- /dev/null +++ b/src/mlpack/methods/ann/loss_functions/triplet_margin_loss_impl.hpp @@ -0,0 +1,71 @@ +/** + * @file methods/ann/loss_functions/triplet_margin_loss_impl.hpp + * @author Prince Gupta + * @author Ayush Singh + * + * Implementation of the Triplet Margin Loss function. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#ifndef MLPACK_METHODS_ANN_LOSS_FUNCTION_TRIPLET_MARGIN_IMPL_LOSS_HPP +#define MLPACK_METHODS_ANN_LOSS_FUNCTION_TRIPLET_MARGIN_IMPL_LOSS_HPP + +// In case it hasn't been included. +#include "triplet_margin_loss.hpp" + +namespace mlpack { +namespace ann /** Artifical Neural Network. */ { + +template +TripletMarginLoss::TripletMarginLoss( + const double margin) : margin(margin) +{ + // Nothing to do here. +} + +template +template +typename PredictionType::elem_type +TripletMarginLoss::Forward( + const PredictionType& prediction, + const TargetType& target) +{ + PredictionType anchor = prediction.submat(0, 0, prediction.n_rows / 2 - 1, prediction.n_cols - 1); + PredictionType positive = prediction.submat(prediction.n_rows / 2, 0, prediction.n_rows - 1, + prediction.n_cols - 1); + return std::max(0.0, arma::accu(arma::pow(anchor - positive, 2)) - + arma::accu(arma::pow(anchor - target, 2)) + margin) / anchor.n_cols; +} + +template +template < + typename PredictionType, + typename TargetType, + typename LossType +> +void TripletMarginLoss::Backward( + const PredictionType& prediction, + const TargetType& target, + LossType& loss) +{ + PredictionType positive = prediction.submat(prediction.n_rows / 2, 0, prediction.n_rows - 1, + prediction.n_cols - 1); + loss = 2 * (target - positive) / target.n_cols; +} + +template +template +void TripletMarginLoss::serialize( + Archive& ar, + const unsigned int /* version */) +{ + ar(CEREAL_NVP(margin)); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/rnn.hpp b/src/mlpack/methods/ann/rnn.hpp index e9e6815de4..949aecee9b 100644 --- a/src/mlpack/methods/ann/rnn.hpp +++ b/src/mlpack/methods/ann/rnn.hpp @@ -70,6 +70,18 @@ class RNN OutputLayerType outputLayer = OutputLayerType(), InitializationRuleType initializeRule = InitializationRuleType()); + //! Copy constructor. + RNN(const RNN&); + + //! Move constructor. + RNN(RNN&&); + + //! Copy assignment operator. + RNN& operator=(const RNN&); + + //! Move assignment operator + RNN& operator=(RNN&&); + //! Destructor to release allocated memory. ~RNN(); @@ -412,6 +424,9 @@ class RNN //! Locally-stored weight size visitor. WeightSizeVisitor weightSizeVisitor; + //! Locally-stored copy visitor + CopyVisitor copyVisitor; + //! Locally-stored reset visitor. ResetVisitor resetVisitor; diff --git a/src/mlpack/methods/ann/rnn_impl.hpp b/src/mlpack/methods/ann/rnn_impl.hpp index b81bc397b2..5077eb9896 100644 --- a/src/mlpack/methods/ann/rnn_impl.hpp +++ b/src/mlpack/methods/ann/rnn_impl.hpp @@ -25,6 +25,8 @@ #include "visitor/gradient_visitor.hpp" #include "visitor/weight_set_visitor.hpp" +#include "util/check_input_shape.hpp" + namespace mlpack { namespace ann /** Artificial Neural Network. */ { @@ -49,6 +51,50 @@ RNN::RNN( /* Nothing to do here */ } +template +RNN::RNN( + const RNN& network) : + rho(network.rho), + outputLayer(network.outputLayer), + initializeRule(network.initializeRule), + inputSize(network.inputSize), + outputSize(network.outputSize), + targetSize(network.targetSize), + reset(network.reset), + single(network.single), + parameter(network.parameter), + numFunctions(network.numFunctions), + deterministic(network.deterministic) +{ + for (size_t i = 0; i < network.network.size(); ++i) + { + this->network.push_back(boost::apply_visitor(copyVisitor, + network.network[i])); + boost::apply_visitor(resetVisitor, this->network.back()); + } +} + +template +RNN::RNN( + RNN&& network) : + rho(std::move(network.rho)), + outputLayer(std::move(network.outputLayer)), + initializeRule(std::move(network.initializeRule)), + inputSize(std::move(network.inputSize)), + outputSize(std::move(network.outputSize)), + targetSize(std::move(network.targetSize)), + reset(std::move(network.reset)), + single(std::move(network.single)), + network(std::move(network.network)), + parameter(std::move(network.parameter)), + numFunctions(std::move(network.numFunctions)), + deterministic(std::move(network.deterministic)) +{ + // Nothing to do here. +} + template RNN::~RNN() @@ -103,6 +149,10 @@ double RNN::Train( OptimizerType& optimizer, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "RNN<>::Train()"); + numFunctions = responses.n_cols; this->predictors = std::move(predictors); @@ -147,6 +197,10 @@ double RNN::Train( arma::cube responses, CallbackTypes&&... callbacks) { + CheckInputShape > >(network, + predictors.n_rows, + "RNN<>::Train()"); + numFunctions = responses.n_cols; this->predictors = std::move(predictors); @@ -179,6 +233,10 @@ template::Predict( arma::cube predictors, arma::cube& results, const size_t batchSize) { + CheckInputShape > >(network, + predictors.n_rows, + "RNN<>::Predict()"); + ResetCells(); if (parameter.is_empty()) diff --git a/src/mlpack/methods/ann/util/CMakeLists.txt b/src/mlpack/methods/ann/util/CMakeLists.txt new file mode 100644 index 0000000000..dffec0c265 --- /dev/null +++ b/src/mlpack/methods/ann/util/CMakeLists.txt @@ -0,0 +1,14 @@ +# Define the files we need to compile +# Anything not in this list will not be compiled into mlpack. +set(SOURCES + check_input_shape.hpp +) + +# Add directory name to sources. +set(DIR_SRCS) +foreach(file ${SOURCES}) + set(DIR_SRCS ${DIR_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/${file}) +endforeach() +# Append sources (with directory name) to list of all mlpack sources (used at +# the parent scope). +set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE) \ No newline at end of file diff --git a/src/mlpack/methods/ann/util/check_input_shape.hpp b/src/mlpack/methods/ann/util/check_input_shape.hpp new file mode 100644 index 0000000000..566c363e3f --- /dev/null +++ b/src/mlpack/methods/ann/util/check_input_shape.hpp @@ -0,0 +1,52 @@ +/** + * @file methods/ann/util/check_input_shape.hpp + * @author Khizir Siddiqui + * @author Nippun Sharma + * + * Definition of the CheckInputShape() function that checks + * whether the shape of input is consistent with the first layer + * of the neural network. + * + * 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_UTIL_CHECK_INPUT_SHAPE_HPP +#define MLPACK_METHODS_ANN_UTIL_CHECK_INPUT_SHAPE_HPP + +#include + +namespace mlpack { +namespace ann /** Artificial Neural Network. */{ + +template +void CheckInputShape(const T& network, const size_t inputShape, + const std::string& functionName) +{ + for (size_t l = 0; l < network.size(); ++l) + { + size_t layerInShape = boost::apply_visitor(InShapeVisitor(), network[l]); + if (layerInShape == 0) + { + continue; + } + else if (layerInShape == inputShape) + { + break; + } + else + { + std::string estr = functionName + ": the first layer of the network " + + "expects " + std::to_string(layerInShape) + " elements, but the " + + "input has " + std::to_string(inputShape) + " dimensions!"; + throw std::logic_error(estr); + } + } +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/visitor/CMakeLists.txt b/src/mlpack/methods/ann/visitor/CMakeLists.txt index 43bcf71225..fa207d6092 100644 --- a/src/mlpack/methods/ann/visitor/CMakeLists.txt +++ b/src/mlpack/methods/ann/visitor/CMakeLists.txt @@ -57,6 +57,8 @@ set(SOURCES weight_set_visitor_impl.hpp weight_size_visitor.hpp weight_size_visitor_impl.hpp + input_shape_visitor.hpp + input_shape_visitor_impl.hpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/ann/visitor/input_shape_visitor.hpp b/src/mlpack/methods/ann/visitor/input_shape_visitor.hpp new file mode 100644 index 0000000000..c27135aae3 --- /dev/null +++ b/src/mlpack/methods/ann/visitor/input_shape_visitor.hpp @@ -0,0 +1,58 @@ +/** + * @file methods/ann/visitor/input_shape_visitor.hpp + * @author Khizir Siddiqui + * @author Nippun Sharma + * + * This file provides an abstraction for the InputShape() function for + * different layers and automatically directs any parameter to the right layer + * type. + * + * 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_VISITOR_INPUT_SHAPE_VISITOR_HPP +#define MLPACK_METHODS_ANN_VISITOR_INPUT_SHAPE_VISITOR_HPP + +#include +#include + +#include + +namespace mlpack { +namespace ann { + +/** + * InShapeVisitor returns the input shape a Layer expects. + */ +class InShapeVisitor : public boost::static_visitor +{ + public: + //! Return the input shape of layer. + template + size_t operator()(LayerType* layer) const; + + size_t operator()(MoreTypes layer) const; + + private: + //! If the module doesn't implement the InputShape() function return 0. + template + typename std::enable_if< + !HasInputShapeCheck::value, size_t>::type + LayerInputShape(T* layer) const; + + //! If the module implements the InputShape() function returns the input shape. + template + typename std::enable_if< + HasInputShapeCheck::value, size_t>::type + LayerInputShape(T* layer) const; +}; + +} // namespace ann +} // namespace mlpack + +// Include implementation. +#include "input_shape_visitor_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/visitor/input_shape_visitor_impl.hpp b/src/mlpack/methods/ann/visitor/input_shape_visitor_impl.hpp new file mode 100644 index 0000000000..bda5f7b604 --- /dev/null +++ b/src/mlpack/methods/ann/visitor/input_shape_visitor_impl.hpp @@ -0,0 +1,53 @@ +/** + * @file methods/ann/visitor/input_shape_visitor_impl.hpp + * @author Khizir Siddiqui + * @author Nippun Sharma + * + * Implementation of the InputShape() function layer abstraction. + * + * 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_VISITOR_INPUT_SHAPE_VISITOR_IMPL_HPP +#define MLPACK_METHODS_ANN_VISITOR_INPUT_SHAPE_VISITOR_IMPL_HPP + +// In case it hasn't been included yet. +#include "input_shape_visitor.hpp" + +namespace mlpack { +namespace ann { + +//! InShapeVisitor visitor class. +template +inline std::size_t InShapeVisitor::operator()(LayerType* layer) const +{ + return LayerInputShape(layer); +} + +inline std::size_t InShapeVisitor::operator()(MoreTypes layer) const +{ + return layer.apply_visitor(*this); +} + +template +inline typename std::enable_if< + !HasInputShapeCheck::value, std::size_t>::type +InShapeVisitor::LayerInputShape(T* /* layer */) const +{ + return 0; +} + +template +inline typename std::enable_if< + HasInputShapeCheck::value, std::size_t>::type +InShapeVisitor::LayerInputShape(T* layer) const +{ + return layer->InputShape(); +} + +} // namespace ann +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/cf/CMakeLists.txt b/src/mlpack/methods/cf/CMakeLists.txt index a7c552ae28..c59a4f12ed 100644 --- a/src/mlpack/methods/cf/CMakeLists.txt +++ b/src/mlpack/methods/cf/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES cf_impl.hpp cf_model.hpp cf_model_impl.hpp + cf_model.cpp svd_wrapper.hpp svd_wrapper_impl.hpp ) diff --git a/src/mlpack/methods/cf/cf_main.cpp b/src/mlpack/methods/cf/cf_main.cpp index 760ddf107d..0c8cd49539 100644 --- a/src/mlpack/methods/cf/cf_main.cpp +++ b/src/mlpack/methods/cf/cf_main.cpp @@ -194,279 +194,6 @@ PARAM_STRING_IN("interpolation", "Algorithm used for weight interpolation.", PARAM_STRING_IN("neighbor_search", "Algorithm used for neighbor search.", "S", "euclidean"); -template -void ComputeRecommendations(CFModel* cf, - const size_t numRecs, - arma::Mat& recommendations) -{ - // Reading users. - if (IO::HasParam("query")) - { - // User matrix. - arma::Mat users = - std::move(IO::GetParam>("query")); - if (users.n_rows > 1) - users = users.t(); - if (users.n_rows > 1) - Log::Fatal << "List of query users must be one-dimensional!" - << std::endl; - - Log::Info << "Generating recommendations for " - << users.n_elem << " users." - << endl; - - cf->GetRecommendations - (numRecs, recommendations, users.row(0).t()); - } - else - { - Log::Info << "Generating recommendations for all users." << endl; - cf->GetRecommendations - (numRecs, recommendations); - } -} - -template -void ComputeRecommendations(CFModel* cf, - const size_t numRecs, - arma::Mat& recommendations) -{ - // Verify the Interpolation algorithms. - RequireParamInSet("interpolation", { "average", - "regression", "similarity" }, true, "unknown interpolation algorithm"); - - // Taking Interpolation Alternatives - const string interpolationAlgorithm = IO::GetParam("interpolation"); - - // Determining the Interpolation Algorithm - if (interpolationAlgorithm == "average") - { - ComputeRecommendations - (cf, numRecs, recommendations); - } - else if (interpolationAlgorithm == "regression") - { - ComputeRecommendations - (cf, numRecs, recommendations); - } - else if (interpolationAlgorithm == "similarity") - { - ComputeRecommendations - (cf, numRecs, recommendations); - } -} - -void ComputeRecommendations(CFModel* cf, - const size_t numRecs, - arma::Mat& recommendations) -{ - // Verifying the Neighbor Search algorithms - RequireParamInSet("neighbor_search", { "cosine", - "euclidean", "pearson" }, true, "unknown neighbor search algorithm"); - - // Taking Neighbor Search alternatives - const string neighborSearchAlgorithm = IO::GetParam - ("neighbor_search"); - - - // Determining the Neighbor Search Algorithms - if (neighborSearchAlgorithm == "cosine") - { - ComputeRecommendations(cf, numRecs, recommendations); - } - else if (neighborSearchAlgorithm == "euclidean") - { - ComputeRecommendations(cf, numRecs, recommendations); - } - else if (neighborSearchAlgorithm == "pearson") - { - ComputeRecommendations(cf, numRecs, recommendations); - } -} - -template -void ComputeRMSE(CFModel* cf) -{ - // Now, compute each test point. - arma::mat testData = std::move(IO::GetParam("test")); - - // Assemble the combination matrix to get RMSE value. - arma::Mat combinations(2, testData.n_cols); - for (size_t i = 0; i < testData.n_cols; ++i) - { - combinations(0, i) = size_t(testData(0, i)); - combinations(1, i) = size_t(testData(1, i)); - } - - // Now compute the RMSE. - arma::vec predictions; - cf->Predict - (combinations, predictions); - - // Compute the root of the sum of the squared errors, divide by the number of - // points to get the RMSE. It turns out this is just the L2-norm divided by - // the square root of the number of points, if we interpret the predictions - // and the true values as vectors. - const double rmse = arma::norm(predictions - testData.row(2).t(), 2) / - std::sqrt((double) testData.n_cols); - - Log::Info << "RMSE is " << rmse << "." << endl; -} - -template -void ComputeRMSE(CFModel* cf) -{ - // Verifying the Interpolation algorithms - RequireParamInSet("interpolation", { "average", - "regression", "similarity" }, true, "unknown interpolation algorithm"); - - // Taking Interpolation Alternatives - const string interpolationAlgorithm = IO::GetParam("interpolation"); - - if (interpolationAlgorithm == "average") - { - ComputeRMSE(cf); - } - else if (interpolationAlgorithm == "regression") - { - ComputeRMSE(cf); - } - else if (interpolationAlgorithm == "similarity") - { - ComputeRMSE(cf); - } -} - -void ComputeRMSE(CFModel* cf) -{ - // Verifying the Neighbor Search algorithms - RequireParamInSet("neighbor_search", { "cosine", - "euclidean", "pearson" }, true, "unknown neighbor search algorithm"); - - // Taking Neighbor Search alternatives - const string neighborSearchAlgorithm = IO::GetParam - ("neighbor_search"); - - if (neighborSearchAlgorithm == "cosine") - { - ComputeRMSE(cf); - } - else if (neighborSearchAlgorithm == "euclidean") - { - ComputeRMSE(cf); - } - else if (neighborSearchAlgorithm == "pearson") - { - ComputeRMSE(cf); - } -} - -void PerformAction(CFModel* c) -{ - if (IO::HasParam("query") || IO::HasParam("all_user_recommendations")) - { - // Get parameters for generating recommendations. - const size_t numRecs = (size_t) IO::GetParam("recommendations"); - - // Get the recommendations. - arma::Mat recommendations; - ComputeRecommendations(c, numRecs, recommendations); - - // Save the output. - IO::GetParam>("output") = recommendations; - } - - if (IO::HasParam("test")) - ComputeRMSE(c); - - IO::GetParam("output_model") = c; -} - -template -void PerformAction(arma::mat& dataset, - const size_t rank, - const size_t maxIterations, - const double minResidue) -{ - const size_t neighborhood = (size_t) IO::GetParam("neighborhood"); - - // Make sure the normalization strategy is valid. - RequireParamInSet("normalization", { "overall_mean", "item_mean", - "user_mean", "z_score", "none" }, true, "unknown normalization type"); - - CFModel* c = new CFModel(); - - const string normalizationType = IO::GetParam("normalization"); - - c->template Train(dataset, neighborhood, rank, - maxIterations, minResidue, IO::HasParam("iteration_only_termination"), - normalizationType); - - try - { - PerformAction(c); - } - catch (std::exception& e) - { - // Clean the memory before throwing completely. - delete c; - throw; - } -} - -void AssembleFactorizerType(const std::string& algorithm, - arma::mat& dataset, - const size_t rank) -{ - const size_t maxIterations = (size_t) IO::GetParam("max_iterations"); - const double minResidue = IO::GetParam("min_residue"); - - if (algorithm == "NMF") - { - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "BatchSVD") - { - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "SVDIncompleteIncremental") - { - PerformAction(dataset, rank, maxIterations, - minResidue); - } - else if (algorithm == "SVDCompleteIncremental") - { - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "RegSVD") - { - ReportIgnoredParam("min_residue", "Regularized SVD terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "RandSVD") - { - ReportIgnoredParam("min_residue", "Randomized SVD terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, - minResidue); - } - else if (algorithm == "BiasSVD") - { - ReportIgnoredParam("min_residue", "Bias SVD terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, minResidue); - } - else if (algorithm == "SVDPP") - { - ReportIgnoredParam("min_residue", "SVD++ terminates only " - "when max_iterations is reached"); - PerformAction(dataset, rank, maxIterations, minResidue); - } -} - static void mlpackMain() { if (IO::GetParam("seed") == 0) @@ -496,6 +223,7 @@ static void mlpackMain() "recommendations must be positive"); // Either load from a model, or train a model. + CFModel* cf; if (IO::HasParam("training")) { // Train a model. @@ -523,23 +251,179 @@ static void mlpackMain() // Get parameters. const size_t rank = (size_t) IO::GetParam("rank"); + cf = new CFModel(); + // Perform decomposition to prepare for recommendations. Log::Info << "Performing CF matrix decomposition on dataset..." << endl; const string algo = IO::GetParam("algorithm"); + if (algo == "NMF") + { + cf->DecompositionType() = CFModel::NMF; + } + else if (algo == "BatchSVD") + { + cf->DecompositionType() = CFModel::BATCH_SVD; + } + else if (algo == "SVDIncompleteIncremental") + { + cf->DecompositionType() = CFModel::SVD_INCOMPLETE; + } + else if (algo == "SVDCompleteIncremental") + { + cf->DecompositionType() = CFModel::SVD_COMPLETE; + } + else if (algo == "RegSVD") + { + ReportIgnoredParam("min_residue", "Regularized SVD terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::REG_SVD; + } + else if (algo == "RandSVD") + { + ReportIgnoredParam("min_residue", "Randomized SVD terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::RANDOMIZED_SVD; + } + else if (algo == "BiasSVD") + { + ReportIgnoredParam("min_residue", "Bias SVD terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::BIAS_SVD; + } + else if (algo == "SVDPP") + { + ReportIgnoredParam("min_residue", "SVD++ terminates only " + "when max_iterations is reached"); + cf->DecompositionType() = CFModel::SVD_PLUS_PLUS; + } // Perform the factorization and do whatever the user wanted. - AssembleFactorizerType(algo, dataset, rank); + const size_t neighborhood = (size_t) IO::GetParam("neighborhood"); + + // Make sure the normalization strategy is valid. + RequireParamInSet("normalization", { "overall_mean", "item_mean", + "user_mean", "z_score", "none" }, true, "unknown normalization type"); + + const string normalizationType = IO::GetParam("normalization"); + if (normalizationType == "none") + cf->NormalizationType() = CFModel::NO_NORMALIZATION; + else if (normalizationType == "item_mean") + cf->NormalizationType() = CFModel::ITEM_MEAN_NORMALIZATION; + else if (normalizationType == "user_mean") + cf->NormalizationType() = CFModel::USER_MEAN_NORMALIZATION; + else if (normalizationType == "overall_mean") + cf->NormalizationType() = CFModel::OVERALL_MEAN_NORMALIZATION; + else if (normalizationType == "z_score") + cf->NormalizationType() = CFModel::Z_SCORE_NORMALIZATION; + + cf->Train(dataset, + neighborhood, + rank, + size_t(IO::GetParam("max_iterations")), + IO::GetParam("min_residue"), + IO::HasParam("iteration_only_termination")); } else { // Load from a model after validating parameters. - RequireAtLeastOnePassed({ "query", "all_user_recommendations", - "test" }, true); + RequireAtLeastOnePassed({ "query", "all_user_recommendations", "test" }, + true); // Load an input model. - CFModel* c = std::move(IO::GetParam("input_model")); - - PerformAction(c); + cf = std::move(IO::GetParam("input_model")); } + + // Get the types of the neighbor search method and the interpolation. (These + // may or may not be used.) + NeighborSearchTypes nsType; + RequireParamInSet("neighbor_search", { "cosine", + "euclidean", "pearson" }, true, "unknown neighbor search algorithm"); + if (IO::GetParam("neighbor_search") == "cosine") + nsType = COSINE_SEARCH; + else if (IO::GetParam("neighbor_search") == "euclidean") + nsType = EUCLIDEAN_SEARCH; + else // if (IO::GetParam("neighbor_search") == "pearson") + nsType = PEARSON_SEARCH; + + InterpolationTypes interpolationType; + RequireParamInSet("interpolation", { "average", + "regression", "similarity" }, true, "unknown interpolation algorithm"); + if (IO::GetParam("interpolation") == "average") + interpolationType = AVERAGE_INTERPOLATION; + else if (IO::GetParam("interpolation") == "regression") + interpolationType = REGRESSION_INTERPOLATION; + else // if (IO::GetParam("interpolation") == "similarity") + interpolationType = SIMILARITY_INTERPOLATION; + + if (IO::HasParam("query") || IO::HasParam("all_user_recommendations")) + { + // Get parameters for generating recommendations. + const size_t numRecs = (size_t) IO::GetParam("recommendations"); + + // Get the recommendations. + arma::Mat recommendations; + + // Reading users. + if (IO::HasParam("query")) + { + // User matrix. + arma::Mat users = + std::move(IO::GetParam>("query")); + if (users.n_rows > 1) + { + users = users.t(); + } + + if (users.n_rows > 1) + { + Log::Fatal << "List of query users must be one-dimensional!" + << std::endl; + } + + Log::Info << "Generating recommendations for " << users.n_elem + << " users." << endl; + + cf->GetRecommendations(nsType, interpolationType, numRecs, + recommendations, users.row(0).t()); + } + else + { + Log::Info << "Generating recommendations for all users." << endl; + cf->GetRecommendations(nsType, interpolationType, numRecs, + recommendations); + } + + // Save the output. + IO::GetParam>("output") = recommendations; + } + + if (IO::HasParam("test")) + { + // Now, compute each test point. + arma::mat testData = std::move(IO::GetParam("test")); + + // Assemble the combination matrix to get RMSE value. + arma::Mat combinations(2, testData.n_cols); + for (size_t i = 0; i < testData.n_cols; ++i) + { + combinations(0, i) = size_t(testData(0, i)); + combinations(1, i) = size_t(testData(1, i)); + } + + // Now compute the RMSE. + arma::vec predictions; + cf->Predict(nsType, interpolationType, combinations, predictions); + + // Compute the root of the sum of the squared errors, divide by the number + // of points to get the RMSE. It turns out this is just the L2-norm divided + // by the square root of the number of points, if we interpret the + // predictions and the true values as vectors. + const double rmse = arma::norm(predictions - testData.row(2).t(), 2) / + std::sqrt((double) testData.n_cols); + + Log::Info << "RMSE is " << rmse << "." << endl; + } + + IO::GetParam("output_model") = cf; } diff --git a/src/mlpack/methods/cf/cf_model.cpp b/src/mlpack/methods/cf/cf_model.cpp new file mode 100644 index 0000000000..226edcf1be --- /dev/null +++ b/src/mlpack/methods/cf/cf_model.cpp @@ -0,0 +1,207 @@ +/** + * @file methods/cf/cf_model_impl.hpp + * @author Wenhao Huang + * + * A serializable CF model, used by the main program. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#include "cf_model.hpp" + +namespace mlpack { +namespace cf { + +CFModel::CFModel() : + decompositionType(NMF), + normalizationType(NO_NORMALIZATION), + cf(NULL) +{ + // Nothing else to do. +} + +CFModel::CFModel(const CFModel& other) : + decompositionType(other.decompositionType), + normalizationType(other.normalizationType), + cf(other.cf->Clone()) +{ + // Nothing else to do. +} + +CFModel::CFModel(CFModel&& other) : + decompositionType(other.decompositionType), + normalizationType(other.normalizationType), + cf(std::move(other.cf)) +{ + // Reset properties of the other one. + other.decompositionType = NMF; + other.normalizationType = NO_NORMALIZATION; +} + +CFModel& CFModel::operator=(const CFModel& other) +{ + if (this != &other) + { + decompositionType = other.decompositionType; + normalizationType = other.normalizationType; + cf = other.cf->Clone(); + } + + return *this; +} + +CFModel& CFModel::operator=(CFModel&& other) +{ + if (this != &other) + { + decompositionType = other.decompositionType; + normalizationType = other.normalizationType; + cf = std::move(other.cf); + + // Reset the other object. + other.decompositionType = NMF; + other.normalizationType = NO_NORMALIZATION; + } + + return *this; +} + +CFModel::~CFModel() +{ + delete cf; +} + +template +CFWrapperBase* TrainHelper(const DecompositionPolicy& decomposition, + const CFModel::NormalizationTypes normalizationType, + const arma::mat& data, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const double minResidue, + const bool mit) +{ + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::ITEM_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::USER_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::OVERALL_MEAN_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + + case CFModel::Z_SCORE_NORMALIZATION: + return new CFWrapper(data, + decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, + mit); + break; + } + + // This shouldn't ever happen. + return NULL; +} + +void CFModel::Train(const arma::mat& data, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const double minResidue, + const bool mit) +{ + // Delete the current CFType object, if there is one. + delete cf; + + switch (decompositionType) + { + case NMF: + cf = TrainHelper(NMFPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case BATCH_SVD: + cf = TrainHelper(BatchSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case RANDOMIZED_SVD: + cf = TrainHelper(RandomizedSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case REG_SVD: + cf = TrainHelper(RegSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_COMPLETE: + cf = TrainHelper(SVDCompletePolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_INCOMPLETE: + cf = TrainHelper(SVDIncompletePolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case BIAS_SVD: + cf = TrainHelper(BiasSVDPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + + case SVD_PLUS_PLUS: + cf = TrainHelper(SVDPlusPlusPolicy(), normalizationType, data, + numUsersForSimilarity, rank, maxIterations, minResidue, mit); + break; + } +} + +//! Make predictions. +void CFModel::Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) +{ + cf->Predict(nsType, interpolationType, combinations, predictions); +} + +//! Compute recommendations for queried users. +void CFModel::GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) +{ + cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations, + users); +} + +//! Compute recommendations for all users. +void CFModel::GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) +{ + cf->GetRecommendations(nsType, interpolationType, numRecs, recommendations); +} + +} // namespace cf +} // namespace mlpack diff --git a/src/mlpack/methods/cf/cf_model.hpp b/src/mlpack/methods/cf/cf_model.hpp index 93ff371a02..354f8b51b5 100644 --- a/src/mlpack/methods/cf/cf_model.hpp +++ b/src/mlpack/methods/cf/cf_model.hpp @@ -14,105 +14,146 @@ #define MLPACK_METHODS_CF_CF_MODEL_HPP #include -#include #include "cf.hpp" -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - namespace mlpack { namespace cf { /** - * DeleteVisitor deletes the CFType<> object which is pointed to by the - * variable cf in class CFModel. + * NeighborSearchTypes contains the set of NeighborSearchPolicy classes that are + * usable by CFModel at prediction time. */ -class DeleteVisitor : public boost::static_visitor +enum NeighborSearchTypes { - public: - //! Delete CFType object. - template - void operator()(CFType* c) const; + COSINE_SEARCH, + EUCLIDEAN_SEARCH, + PEARSON_SEARCH }; /** - * GetValueVisitor returns the pointer which points to the CFType object. + * InterpolationTypes contains the set of InterpolationPolicy classes that are + * usable by CFModel at prediction time. */ -class GetValueVisitor : public boost::static_visitor +enum InterpolationTypes { - public: - //! Return stored pointer as void* type. - template - void* operator()(CFType* c) const; + AVERAGE_INTERPOLATION, + REGRESSION_INTERPOLATION, + SIMILARITY_INTERPOLATION }; /** - * PredictVisitor uses the CFType object to make predictions on the given - * combinations of users and items. + * The CFWrapperBase class provides a unified interface that can be used by the + * CFModel class to interact with all different CF types at runtime. All CF + * wrapper types inherit from this base class. */ -template -class PredictVisitor : public boost::static_visitor +class CFWrapperBase { - private: - //! User/item combinations to predict. - const arma::Mat& combinations; - //! Predicted ratings for each user/item combination. - arma::vec& predictions; - public: - //! Predict ratings for each user-item combination. - template - void operator()(CFType* c) const; + //! Create the object. The base class has nothing to hold. + CFWrapperBase() { } - //! Visitor constructor. - PredictVisitor(const arma::Mat& combinations, - arma::vec& predictions); + //! Make a copy of the object. + virtual CFWrapperBase* Clone() const = 0; + + //! Delete the object. + virtual ~CFWrapperBase() { } + + //! Compute predictions for users. + virtual void Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) = 0; + + //! Compute recommendations for all users. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) = 0; + + //! Compute recommendations. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) = 0; }; /** - * RecommendationVisitor uses the CFType object to get recommendations for the - * given users. + * The CFWrapper class wraps the functionality of all CF types. If special + * handling is needed for a future CF type, this class can be extended. */ -template -class RecommendationVisitor : public boost::static_visitor +template +class CFWrapper : public CFWrapperBase { - private: - //! Number of Recommendations. - const size_t numRecs; - //! Recommendations matrix to save recommendations. - arma::Mat& recommendations; - //! Users for which recommendations are to be generated. - const arma::Col& users; - //! Whether users are given. - const bool usersGiven; + protected: + typedef CFType CFModelType; public: - //! Visitor constructor. - RecommendationVisitor(const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users, - const bool usersGiven); + //! Create the CFWrapper object, using default parameters to initialize the + //! held CF object. + CFWrapper() { } - //! Generates the given number of recommendations. - template - void operator()(CFType* c) const; + //! Create the CFWrapper object, initializing the held CF object. + CFWrapper(const arma::mat& data, + const DecompositionPolicy& decomposition, + const size_t numUsersForSimilarity, + const size_t rank, + const size_t maxIterations, + const size_t minResidue, + const bool mit) : + cf(data, + decomposition, + numUsersForSimilarity, + rank, + maxIterations, + minResidue, + mit) + { + // Nothing else to do. + } + + //! Clone the CFWrapper object. This handles polymorphism correctly. + virtual CFWrapper* Clone() const { return new CFWrapper(*this); } + + //! Destroy the CFWrapper object. + virtual ~CFWrapper() { } + + //! Get the CFType object. + CFModelType& CF() { return cf; } + + //! Compute predictions for users. + virtual void Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions); + + //! Compute recommendations for all users. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations); + + //! Compute recommendations. + virtual void GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users); + + //! Serialize the model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(cf)); + } + + protected: + //! This is the CF object that we are wrapping. + CFModelType cf; }; /** @@ -120,98 +161,110 @@ class RecommendationVisitor : public boost::static_visitor */ class CFModel { + public: + enum DecompositionTypes + { + NMF, + BATCH_SVD, + RANDOMIZED_SVD, + REG_SVD, + SVD_COMPLETE, + SVD_INCOMPLETE, + BIAS_SVD, + SVD_PLUS_PLUS + }; + + enum NormalizationTypes + { + NO_NORMALIZATION, + ITEM_MEAN_NORMALIZATION, + USER_MEAN_NORMALIZATION, + OVERALL_MEAN_NORMALIZATION, + Z_SCORE_NORMALIZATION + }; + private: + //! The current decomposition policy type. + DecompositionTypes decompositionType; + //! The current normalization policy type. + NormalizationTypes normalizationType; + /** * cf holds an instance of the CFType class for the current * decompositionPolicy and normalizationType. It is initialized every time - * Train() is executed. We access to the contained value through the visitor - * classes defined above. + * Train() is executed. */ - boost::variant*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*, - CFType*> cf; + CFWrapperBase* cf; public: //! Create an empty CF model. - CFModel() { } + CFModel(); + + //! Create a CF model by copying the given model. + CFModel(const CFModel& other); + + //! Create a CF model by taking ownership of the data of the other model. + CFModel(CFModel&& other); + + //! Make this CF model a copy of the other model. + CFModel& operator=(const CFModel& other); + + //! Make this CF model take ownership of the data of the other model. + CFModel& operator=(CFModel&& other); //! Clean up memory. ~CFModel(); - //! Get the pointer to CFType<> object. - template - const CFType* CFPtr() const; + //! Get the CFWrapperBase object. (Be careful!) + CFWrapperBase* CF() const { return cf; } + + //! Get the decomposition type. + const DecompositionTypes& DecompositionType() const + { + return decompositionType; + } + //! Set the decomposition type. + DecompositionTypes& DecompositionType() + { + return decompositionType; + } + + //! Get the normalization type. + const NormalizationTypes& NormalizationType() const + { + return normalizationType; + } + //! Set the normalization type. + NormalizationTypes& NormalizationType() + { + return normalizationType; + } //! Train the model. - template - void Train(const MatType& data, + void Train(const arma::mat& data, const size_t numUsersForSimilarity, const size_t rank, const size_t maxIterations, const double minResidue, - const bool mit, - const std::string& normalizationType = "none"); + const bool mit); //! Make predictions. - template - void Predict(const arma::Mat& combinations, + void Predict(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, arma::vec& predictions); //! Compute recommendations for query users. - template - void GetRecommendations(const size_t numRecs, + void GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, arma::Mat& recommendations, const arma::Col& users); //! Compute recommendations for all users. - template - void GetRecommendations(const size_t numRecs, + void GetRecommendations(const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, arma::Mat& recommendations); //! Serialize the model. diff --git a/src/mlpack/methods/cf/cf_model_impl.hpp b/src/mlpack/methods/cf/cf_model_impl.hpp index 6df3491e59..fa2634a823 100644 --- a/src/mlpack/methods/cf/cf_model_impl.hpp +++ b/src/mlpack/methods/cf/cf_model_impl.hpp @@ -14,204 +14,364 @@ #include "cf_model.hpp" -#include -#include -#include -#include -#include +#include "interpolation_policies/average_interpolation.hpp" +#include "interpolation_policies/regression_interpolation.hpp" +#include "interpolation_policies/similarity_interpolation.hpp" -using namespace mlpack::cf; +#include "neighbor_search_policies/cosine_search.hpp" +#include "neighbor_search_policies/lmetric_search.hpp" +#include "neighbor_search_policies/pearson_search.hpp" -template -void DeleteVisitor:: -operator()(CFType* c) const +#include "decomposition_policies/batch_svd_method.hpp" +#include "decomposition_policies/bias_svd_method.hpp" +#include "decomposition_policies/nmf_method.hpp" +#include "decomposition_policies/randomized_svd_method.hpp" +#include "decomposition_policies/regularized_svd_method.hpp" +#include "decomposition_policies/svd_complete_method.hpp" +#include "decomposition_policies/svd_incomplete_method.hpp" +#include "decomposition_policies/svdplusplus_method.hpp" + +#include "normalization/no_normalization.hpp" +#include "normalization/overall_mean_normalization.hpp" +#include "normalization/user_mean_normalization.hpp" +#include "normalization/item_mean_normalization.hpp" +#include "normalization/z_score_normalization.hpp" + +namespace mlpack { +namespace cf { + +template +void PredictHelper(CFType& cf, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) { - if (c) - delete c; -} - -template -void* GetValueVisitor:: -operator()(CFType* c) const -{ - if (!c) - throw std::runtime_error("no cf model initialized"); - - return (void*) c; -} - -template -PredictVisitor::PredictVisitor( - const arma::Mat& combinations, - arma::vec& predictions) : - combinations(combinations), - predictions(predictions) -{ } - -template -template -void PredictVisitor - ::operator()(CFType* c) const -{ - if (!c) + switch (interpolationType) { - throw std::runtime_error("no cf model initialized"); - return; - } + case AVERAGE_INTERPOLATION: + cf.template Predict(combinations, predictions); + break; - c->template Predict(combinations, predictions); -} + case REGRESSION_INTERPOLATION: + cf.template Predict(combinations, predictions); + break; -template -RecommendationVisitor - ::RecommendationVisitor( - const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users, - const bool usersGiven) : - numRecs(numRecs), - recommendations(recommendations), - users(users), - usersGiven(usersGiven) -{ } - -template -template -void RecommendationVisitor - ::operator()(CFType* c) const -{ - if (!c) - { - throw std::runtime_error("no cf model initialized"); - return; - } - - if (usersGiven) - c->template GetRecommendations - (numRecs, recommendations, users); - else - c->template GetRecommendations - (numRecs, recommendations); -} - -CFModel::~CFModel() -{ - boost::apply_visitor(DeleteVisitor(), cf); -} - -template -void CFModel::Train(const MatType& data, - const size_t numUsersForSimilarity, - const size_t rank, - const size_t maxIterations, - const double minResidue, - const bool mit, - const std::string& normalization) -{ - // Delete the current CFType object, if there is one. - boost::apply_visitor(DeleteVisitor(), cf); - - // Instantiate a new CFType object. - DecompositionPolicy decomposition; - if (normalization == "overall_mean") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "item_mean") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "user_mean") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "z_score") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else if (normalization == "none") - { - cf = new CFType(data, - decomposition, numUsersForSimilarity, rank, maxIterations, minResidue, - mit); - } - else - { - throw std::runtime_error("Unsupported normalization algorithm." - " It should be one of none, overall_mean, " - "item_mean, user_mean or z_score"); + case SIMILARITY_INTERPOLATION: + cf.template Predict(combinations, predictions); + break; } } //! Make predictions. -template -void CFModel::Predict(const arma::Mat& combinations, - arma::vec& predictions) +template +void CFWrapper::Predict( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const arma::Mat& combinations, + arma::vec& predictions) { - PredictVisitor - predict(combinations, predictions); - boost::apply_visitor(predict, cf); + switch (nsType) + { + case COSINE_SEARCH: + PredictHelper(cf, interpolationType, combinations, + predictions); + break; + + case EUCLIDEAN_SEARCH: + PredictHelper(cf, interpolationType, combinations, + predictions); + break; + + case PEARSON_SEARCH: + PredictHelper(cf, interpolationType, combinations, + predictions); + break; + } +} + +template +void GetRecommendationsHelper( + CFType& cf, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) +{ + switch (interpolationType) + { + case AVERAGE_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations, users); + break; + + case REGRESSION_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations, users); + break; + + case SIMILARITY_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations, users); + break; + } } //! Compute recommendations for queried users. -template -void CFModel::GetRecommendations(const size_t numRecs, - arma::Mat& recommendations, - const arma::Col& users) +template +void CFWrapper::GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations, + const arma::Col& users) { - RecommendationVisitor - recommendation(numRecs, recommendations, users, true); - boost::apply_visitor(recommendation, cf); + switch (nsType) + { + case COSINE_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations, users); + break; + + case EUCLIDEAN_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations, users); + break; + + case PEARSON_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations, users); + break; + } +} + +template +void GetRecommendationsHelper( + CFType& cf, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) +{ + switch (interpolationType) + { + case AVERAGE_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations); + break; + + case REGRESSION_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations); + break; + + case SIMILARITY_INTERPOLATION: + cf.template GetRecommendations( + numRecs, recommendations); + break; + } } //! Compute recommendations for all users. -template -void CFModel::GetRecommendations(const size_t numRecs, - arma::Mat& recommendations) +template +void CFWrapper::GetRecommendations( + const NeighborSearchTypes nsType, + const InterpolationTypes interpolationType, + const size_t numRecs, + arma::Mat& recommendations) { - arma::Col users; - RecommendationVisitor - recommendation(numRecs, recommendations, users, false); - boost::apply_visitor(recommendation, cf); + switch (nsType) + { + case COSINE_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations); + break; + + case EUCLIDEAN_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations); + break; + + case PEARSON_SEARCH: + GetRecommendationsHelper(cf, interpolationType, numRecs, + recommendations); + break; + } } -template -const CFType* CFModel::CFPtr() const +template +CFWrapperBase* InitializeModelHelper( + CFModel::NormalizationTypes normalizationType) { - void* pointer = boost::apply_visitor(GetValueVisitor(), cf); - return (CFType*) pointer; + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + return new CFWrapper(); + + case CFModel::ITEM_MEAN_NORMALIZATION: + return new CFWrapper(); + + case CFModel::USER_MEAN_NORMALIZATION: + return new CFWrapper(); + + case CFModel::OVERALL_MEAN_NORMALIZATION: + return new CFWrapper(); + + case CFModel::Z_SCORE_NORMALIZATION: + return new CFWrapper(); + } + + // This shouldn't ever happen. + return NULL; +} + +inline CFWrapperBase* InitializeModel( + CFModel::DecompositionTypes decompositionType, + CFModel::NormalizationTypes normalizationType) +{ + switch (decompositionType) + { + case CFModel::NMF: + return InitializeModelHelper(normalizationType); + + case CFModel::BATCH_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::RANDOMIZED_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::REG_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::SVD_COMPLETE: + return InitializeModelHelper(normalizationType); + + case CFModel::SVD_INCOMPLETE: + return InitializeModelHelper(normalizationType); + + case CFModel::BIAS_SVD: + return InitializeModelHelper(normalizationType); + + case CFModel::SVD_PLUS_PLUS: + return InitializeModelHelper(normalizationType); + } + + // This shouldn't ever happen. + return NULL; +}; + +template +void SerializeHelper(Archive& ar, + CFWrapperBase* cf, + CFModel::NormalizationTypes normalizationType) +{ + switch (normalizationType) + { + case CFModel::NO_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::ITEM_MEAN_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::USER_MEAN_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::OVERALL_MEAN_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + + case CFModel::Z_SCORE_NORMALIZATION: + { + CFWrapper& typedModel = + dynamic_cast&>(*cf); + ar(CEREAL_NVP(typedModel)); + break; + } + } } template void CFModel::serialize(Archive& ar, const uint32_t /* version */) { + ar(CEREAL_NVP(decompositionType)); + ar(CEREAL_NVP(normalizationType)); + // This should never happen, but just in case, be clean with memory. if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), cf); + { + delete cf; + cf = InitializeModel(decompositionType, normalizationType); + } - ar(CEREAL_VARIANT_POINTER(cf)); + // Avoid polymorphic serialization by determining the type directly. + switch (decompositionType) + { + case NMF: + SerializeHelper(ar, cf, normalizationType); + break; + + case BATCH_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case RANDOMIZED_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case REG_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case SVD_COMPLETE: + SerializeHelper(ar, cf, normalizationType); + break; + + case SVD_INCOMPLETE: + SerializeHelper(ar, cf, normalizationType); + break; + + case BIAS_SVD: + SerializeHelper(ar, cf, normalizationType); + break; + + case SVD_PLUS_PLUS: + SerializeHelper(ar, cf, normalizationType); + break; + } } +} // namespace cf +} // namespace mlpack + #endif diff --git a/src/mlpack/methods/decision_stump/CMakeLists.txt b/src/mlpack/methods/decision_stump/CMakeLists.txt deleted file mode 100644 index 5a0405b675..0000000000 --- a/src/mlpack/methods/decision_stump/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Define the files we need to compile. -# Anything not in this list will not be compiled into mlpack. -set(SOURCES - decision_stump.hpp - decision_stump_impl.hpp -) - -# Add directory name to sources. -set(DIR_SRCS) -foreach(file ${SOURCES}) - set(DIR_SRCS ${DIR_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/${file}) -endforeach() -# Append sources (with directory name) to list of all mlpack sources (used at -# the parent scope). -set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE) - -add_cli_executable(decision_stump) -add_python_binding(decision_stump) -add_julia_binding(decision_stump) -add_go_binding(decision_stump) -add_markdown_docs(decision_stump "cli;python;julia;go" "classification") diff --git a/src/mlpack/methods/decision_stump/decision_stump.hpp b/src/mlpack/methods/decision_stump/decision_stump.hpp deleted file mode 100644 index 649fb79b40..0000000000 --- a/src/mlpack/methods/decision_stump/decision_stump.hpp +++ /dev/null @@ -1,239 +0,0 @@ -/** - * @file methods/decision_stump/decision_stump.hpp - * @author Udit Saxena - * - * Definition of decision stumps. - * - * 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_DECISION_STUMP_DECISION_STUMP_HPP -#define MLPACK_METHODS_DECISION_STUMP_DECISION_STUMP_HPP - -#include - -namespace mlpack { -namespace decision_stump { - -/** - * This class implements a decision stump. It constructs a single level - * decision tree, i.e., a decision stump. It uses entropy to decide splitting - * ranges. - * - * The stump is parameterized by a splitting dimension (the dimension on which - * points are split), a vector of bin split values, and a vector of labels for - * each bin. Bin i is specified by the range [split[i], split[i + 1]). The - * last bin has range up to @f$ \infty @f$ (split[i + 1] does not exist in that - * case). - * Points that are below the first bin will take the label of the first bin. - * - * @note - * This class has been deprecated and should be removed in mlpack 4.0.0. Use - * `ID3DecisionStump`, found in src/mlpack/methods/decision_tree/, instead. - * - * @tparam MatType Type of matrix that is being used (sparse or dense). - */ -template -class DecisionStump -{ - public: - /** - * Constructor. Train on the provided data. Generate a decision stump from - * data. - * - * @param data Input, training data. - * @param labels Labels of training data. - * @param numClasses Number of distinct classes in labels. - * @param bucketSize Minimum size of bucket when splitting. - */ - mlpack_deprecated DecisionStump(const MatType& data, - const arma::Row& labels, - const size_t numClasses, - const size_t bucketSize = 10); - - /** - * Alternate constructor which copies the parameters bucketSize and classes - * from an already initiated decision stump, other. It appropriately sets the - * weight vector. - * - * @param other The other initiated Decision Stump object from - * which we copy the values. - * @param data The data on which to train this object on. - * @param labels The labels of data. - * @param numClasses The number of classes. - * @param weights Weight vector to use while training. For boosting purposes. - */ - mlpack_deprecated DecisionStump(const DecisionStump<>& other, - const MatType& data, - const arma::Row& labels, - const size_t numClasses, - const arma::rowvec& weights); - - /** - * Create a decision stump without training. This stump will not be useful - * and will always return a class of 0 for anything that is to be classified, - * so it would be a prudent idea to call Train() after using this constructor. - */ - DecisionStump(); - - /** - * Train the decision stump on the given data. This completely overwrites any - * previous training data, so after training the stump may be completely - * different. - * - * @param data Dataset to train on. - * @param labels Labels for each point in the dataset. - * @param numClasses Number of classes in the dataset. - * @param bucketSize Minimum size of bucket when splitting. - * @return The final entropy after splitting. - */ - mlpack_deprecated double Train(const MatType& data, - const arma::Row& labels, - const size_t numClasses, - const size_t bucketSize); - - /** - * Train the decision stump on the given data, with the given weights. This - * completely overwrites any previous training data, so after training the - * stump may be completely different. - * - * @param data Dataset to train on. - * @param labels Labels for each point in the dataset. - * @param weights Weights for each point in the dataset. - * @param numClasses Number of classes in the dataset. - * @param bucketSize Minimum size of bucket when splitting. - * @return The final entropy after splitting. - */ - mlpack_deprecated double Train(const MatType& data, - const arma::Row& labels, - const arma::rowvec& weights, - const size_t numClasses, - const size_t bucketSize); - - /** - * Classification function. After training, classify test, and put the - * predicted classes in predictedLabels. - * - * @param test Testing data or data to classify. - * @param predictedLabels Vector to store the predicted classes after - * classifying test data. - */ - mlpack_deprecated void Classify(const MatType& test, - arma::Row& predictedLabels); - - //! Access the splitting dimension. - size_t SplitDimension() const { return splitDimension; } - //! Modify the splitting dimension (be careful!). - size_t& SplitDimension() { return splitDimension; } - - //! Access the splitting values. - const arma::vec& Split() const { return split; } - //! Modify the splitting values (be careful!). - arma::vec& Split() { return split; } - - //! Access the labels for each split bin. - const arma::Col BinLabels() const { return binLabels; } - //! Modify the labels for each split bin (be careful!). - arma::Col& BinLabels() { return binLabels; } - - //! Serialize the decision stump. - template - void serialize(Archive& ar, const uint32_t /* version */); - - private: - //! The number of classes (we must store this for boosting). - size_t numClasses; - //! The minimum number of points in a bucket. - size_t bucketSize; - - //! Stores the value of the dimension on which to split. - size_t splitDimension; - //! Stores the splitting values after training. - arma::vec split; - //! Stores the labels for each splitting bin. - arma::Col binLabels; - - /** - * Sets up dimension as if it were splitting on it and finds entropy when - * splitting on dimension. - * - * @param dimension A row from the training data, which might be a - * candidate for the splitting dimension. - * @tparam UseWeights Whether we need to run a weighted Decision Stump. - */ - template - double SetupSplitDimension(const VecType& dimension, - const arma::Row& labels, - const arma::rowvec& weightD); - - /** - * After having decided the dimension on which to split, train on that - * dimension. - * - * @tparam dimension dimension is the dimension decided by the constructor - * on which we now train the decision stump. - */ - template - void TrainOnDim(const VecType& dimension, - const arma::Row& labels); - - /** - * After the "split" matrix has been set up, merge ranges with identical class - * labels. - */ - void MergeRanges(); - - /** - * Count the most frequently occurring element in subCols. - * - * @param subCols The vector in which to find the most frequently occurring - * element. - */ - template - double CountMostFreq(const VecType& subCols); - - /** - * Returns 1 if all the values of featureRow are not same. - * - * @param featureRow The dimension which is checked for identical values. - */ - template - int IsDistinct(const VecType& featureRow); - - /** - * Calculate the entropy of the given dimension. - * - * @param labels Corresponding labels of the dimension. - * @param classes Number of classes. - * @param weights Weights for this set of labels. - * @tparam UseWeights If true, the weights in the weight vector will be used - * (otherwise they are ignored). - */ - template - double CalculateEntropy(const VecType& labels, - const WeightVecType& weights); - - /** - * Train the decision stump on the given data and labels. - * - * @param data Dataset to train on. - * @param labels Labels for dataset. - * @param weights Weights for this set of labels. - * @tparam UseWeights If true, the weights in the weight vector will be used - * (otherwise they are ignored). - * @return The final entropy after splitting. - */ - template - double Train(const MatType& data, - const arma::Row& labels, - const arma::rowvec& weights); -}; - -} // namespace decision_stump -} // namespace mlpack - -#include "decision_stump_impl.hpp" - -#endif diff --git a/src/mlpack/methods/decision_stump/decision_stump_impl.hpp b/src/mlpack/methods/decision_stump/decision_stump_impl.hpp deleted file mode 100644 index 7722eecb38..0000000000 --- a/src/mlpack/methods/decision_stump/decision_stump_impl.hpp +++ /dev/null @@ -1,518 +0,0 @@ -/** - * @file methods/decision_stump/decision_stump_impl.hpp - * @author Udit Saxena - * - * Implementation of DecisionStump class. - * - * mlpack is free software; you may redistribute it and/or modify it under the - * terms of the 3-clause BSD license. You should have received a copy of the - * 3-clause BSD license along with mlpack. If not, see - * http://www.opensource.org/licenses/BSD-3-Clause for more information. - */ -#ifndef MLPACK_METHODS_DECISION_STUMP_DECISION_STUMP_IMPL_HPP -#define MLPACK_METHODS_DECISION_STUMP_DECISION_STUMP_IMPL_HPP - -// In case it hasn't been included yet. -#include "decision_stump.hpp" - -namespace mlpack { -namespace decision_stump { - -/** - * Constructor. Train on the provided data. Generate a decision stump from data. - * - * @param data Input, training data. - * @param labels Labels of data. - * @param numClasses Number of distinct classes in labels. - * @param bucketSize Minimum size of bucket when splitting. - */ -template -DecisionStump::DecisionStump(const MatType& data, - const arma::Row& labels, - const size_t numClasses, - const size_t bucketSize) : - numClasses(numClasses), - bucketSize(bucketSize) -{ - arma::rowvec weights; - Train(data, labels, weights); -} - -/** - * Empty constructor. - */ -template -DecisionStump::DecisionStump() : - numClasses(1), - bucketSize(0), - splitDimension(0), - split(1), - binLabels(1) -{ - split[0] = DBL_MAX; - binLabels[0] = 0; -} - -/** - * Train on the given data and labels. - */ -template -double DecisionStump::Train(const MatType& data, - const arma::Row& labels, - const size_t numClasses, - const size_t bucketSize) -{ - this->numClasses = numClasses; - this->bucketSize = bucketSize; - - // Pass to unweighted training function. - arma::rowvec weights; - return Train(data, labels, weights); -} - -/** - * Train the decision stump on the given data, with the given weights. This - * completely overwrites any previous training data, so after training the - * stump may be completely different. - */ -template -double DecisionStump::Train(const MatType& data, - const arma::Row& labels, - const arma::rowvec& weights, - const size_t numClasses, - const size_t bucketSize) -{ - this->numClasses = numClasses; - this->bucketSize = bucketSize; - - // Pass to weighted training function. - return Train(data, labels, weights); -} - -/** - * Train the decision stump on the given data and labels. - * - * @param data Dataset to train on. - * @param labels Labels for dataset. - * @param UseWeights Whether we need to run a weighted Decision Stump. - */ -template -template -double DecisionStump::Train(const MatType& data, - const arma::Row& labels, - const arma::rowvec& weights) -{ - // If classLabels are not all identical, proceed with training. - size_t bestDim = 0; - double entropy; - const double rootEntropy = CalculateEntropy(labels, weights); - - double gain, bestGain = 0.0; - for (size_t i = 0; i < data.n_rows; ++i) - { - // Go through each dimension of the data. - if (IsDistinct(data.row(i))) - { - // For each dimension with non-identical values, treat it as a potential - // splitting dimension and calculate entropy if split on it. - entropy = SetupSplitDimension(data.row(i), labels, weights); - - gain = rootEntropy - entropy; - // Find the dimension with the best entropy so that the gain is - // maximized. - - // We are maximizing gain, which is what is returned from - // SetupSplitDimension(). - if (gain < bestGain) - { - bestDim = i; - bestGain = gain; - } - } - } - splitDimension = bestDim; - - // Once the splitting column/dimension has been decided, train on it. - TrainOnDim(data.row(splitDimension), labels); - return -bestGain; -} - -/** - * Classification function. After training, classify test, and put the predicted - * classes in predictedLabels. - * - * @param test Testing data or data to classify. - * @param predictedLabels Vector to store the predicted classes after - * classifying test - */ -template -void DecisionStump::Classify(const MatType& test, - arma::Row& predictedLabels) -{ - predictedLabels.set_size(test.n_cols); - for (size_t i = 0; i < test.n_cols; ++i) - { - // Determine which bin the test point falls into. - // Assume first that it falls into the first bin, then proceed through the - // bins until it is known which bin it falls into. - size_t bin = 0; - const double val = test(splitDimension, i); - - while (bin < split.n_elem - 1) - { - if (val < split(bin + 1)) - break; - - ++bin; - } - - predictedLabels(i) = binLabels(bin); - } -} - -/** - * Alternate constructor which copies parameters bucketSize and numClasses - * from an already initiated decision stump, other. It appropriately - * sets the Weight vector. - * - * @param other The other initiated Decision Stump object from - * which we copy the values from. - * @param data The data on which to train this object on. - * @param D Weight vector to use while training. For boosting purposes. - * @param labels The labels of data. - * @param UseWeights Whether we need to run a weighted Decision Stump. - */ -template -DecisionStump::DecisionStump(const DecisionStump<>& other, - const MatType& data, - const arma::Row& labels, - const size_t numClasses, - const arma::rowvec& weights) : - numClasses(numClasses), - bucketSize(other.bucketSize) -{ - Train(data, labels, weights); -} - -/** - * Serialize the decision stump. - */ -template -template -void DecisionStump::serialize(Archive& ar, - const uint32_t /* version */) -{ - // This is straightforward; just serialize all of the members of the class. - // None need special handling. - ar(CEREAL_NVP(numClasses)); - ar(CEREAL_NVP(bucketSize)); - ar(CEREAL_NVP(splitDimension)); - ar(CEREAL_NVP(split)); - ar(CEREAL_NVP(binLabels)); -} - -/** - * Sets up dimension as if it were splitting on it and finds entropy when - * splitting on dimension. - * - * @param dimension A row from the training data, which might be a candidate for - * the splitting dimension. - * @param UseWeights Whether we need to run a weighted Decision Stump. - */ -template -template -double DecisionStump::SetupSplitDimension( - const VecType& dimension, - const arma::Row& labels, - const arma::rowvec& weights) -{ - size_t i, count, begin, end; - double entropy = 0.0; - - // Store the indices of the sorted dimension to build a vector of sorted - // labels. This sort is stable. - arma::uvec sortedIndexDim = arma::stable_sort_index(dimension.t()); - - arma::Row sortedLabels(dimension.n_elem); - arma::rowvec sortedWeights(dimension.n_elem); - - for (i = 0; i < dimension.n_elem; ++i) - { - sortedLabels(i) = labels(sortedIndexDim(i)); - - // Apply weights if necessary. - if (UseWeights) - sortedWeights(i) = weights(sortedIndexDim(i)); - } - - i = 0; - count = 0; - - // This splits the sorted data into buckets of size greater than or equal to - // bucketSize. - while (i < sortedLabels.n_elem) - { - count++; - if (i == sortedLabels.n_elem - 1) - { - // If we're at the end, then don't worry about the bucket size; just take - // this as the last bin. - begin = i - count + 1; - end = i; - - // Use ratioEl to calculate the ratio of elements in this split. - const double ratioEl = ((double) (end - begin + 1) / sortedLabels.n_elem); - - entropy += ratioEl * CalculateEntropy( - sortedLabels.subvec(begin, end), sortedWeights.subvec(begin, end)); - ++i; - } - else if (sortedLabels(i) != sortedLabels(i + 1)) - { - // If we're not at the last element of sortedLabels, then check whether - // count is less than the current bucket size. - if (count < bucketSize) - { - // If it is, then take the minimum bucket size anyways. - // This is where the inpBucketSize comes into use. - // This makes sure there isn't a bucket for every change in labels. - begin = i - count + 1; - end = begin + bucketSize - 1; - - if (end > sortedLabels.n_elem - 1) - end = sortedLabels.n_elem - 1; - } - else - { - // If it is not, then take the bucket size as the value of count. - begin = i - count + 1; - end = i; - } - const double ratioEl = ((double) (end - begin + 1) / sortedLabels.n_elem); - - entropy += ratioEl * CalculateEntropy( - sortedLabels.subvec(begin, end), sortedWeights.subvec(begin, end)); - - i = end + 1; - count = 0; - } - else - ++i; - } - return entropy; -} - -/** - * After having decided the dimension on which to split, train on that - * dimension. - * - * @param dimension Dimension is the dimension decided by the constructor on - * which we now train the decision stump. - */ -template -template -void DecisionStump::TrainOnDim(const VecType& dimension, - const arma::Row& labels) -{ - size_t i, count, begin, end; - - typename MatType::row_type sortedSplitDim = arma::sort(dimension); - arma::uvec sortedSplitIndexDim = arma::stable_sort_index(dimension.t()); - arma::Row sortedLabels(dimension.n_elem); - sortedLabels.fill(0); - - for (i = 0; i < dimension.n_elem; ++i) - sortedLabels(i) = labels(sortedSplitIndexDim(i)); - - arma::rowvec subCols; - double mostFreq; - i = 0; - count = 0; - while (i < sortedLabels.n_elem) - { - count++; - if (i == sortedLabels.n_elem - 1) - { - begin = i - count + 1; - end = i; - - mostFreq = CountMostFreq(sortedLabels.cols(begin, end)); - - split.resize(split.n_elem + 1); - split(split.n_elem - 1) = sortedSplitDim(begin); - binLabels.resize(binLabels.n_elem + 1); - binLabels(binLabels.n_elem - 1) = mostFreq; - - ++i; - } - else if (sortedLabels(i) != sortedLabels(i + 1)) - { - if (count < bucketSize) - { - // Test for different values of bucketSize, especially extreme cases. - begin = i - count + 1; - end = begin + bucketSize - 1; - - if (end > sortedLabels.n_elem - 1) - end = sortedLabels.n_elem - 1; - } - else - { - begin = i - count + 1; - end = i; - } - - // Find the most frequent element in subCols so as to assign a label to - // the bucket of subCols. - mostFreq = CountMostFreq(sortedLabels.cols(begin, end)); - - split.resize(split.n_elem + 1); - split(split.n_elem - 1) = sortedSplitDim(begin); - binLabels.resize(binLabels.n_elem + 1); - binLabels(binLabels.n_elem - 1) = mostFreq; - - i = end + 1; - count = 0; - } - else - ++i; - } - - // Now trim the split matrix so that buckets one after the after which point - // to the same classLabel are merged as one big bucket. - MergeRanges(); -} - -/** - * After the "split" matrix has been set up, merge ranges with identical class - * labels. - */ -template -void DecisionStump::MergeRanges() -{ - for (size_t i = 1; i < split.n_rows; ++i) - { - if (binLabels(i) == binLabels(i - 1)) - { - // Remove this row, as it has the same label as the previous bucket. - binLabels.shed_row(i); - split.shed_row(i); - // Go back to previous row. - i--; - } - } -} - -template -template -double DecisionStump::CountMostFreq(const VecType& subCols) -{ - // We'll create a map of elements and the number of times that each element is - // seen. - std::map countMap; - - for (size_t i = 0; i < subCols.n_elem; ++i) - { - if (countMap.count(subCols[i]) == 0) - countMap[subCols[i]] = 1; - else - ++countMap[subCols[i]]; - } - - // Now find the maximum value. - typename std::map::iterator it = countMap.begin(); - double mostFreq = it->first; - size_t mostFreqCount = it->second; - while (it != countMap.end()) - { - if (it->second >= mostFreqCount) - { - mostFreq = it->first; - mostFreqCount = it->second; - } - - ++it; - } - - return mostFreq; -} - -/** - * Returns 1 if all the values of featureRow are not the same. - * - * @param featureRow The dimension which is checked for identical values. - */ -template -template -int DecisionStump::IsDistinct(const VecType& featureRow) -{ - typename VecType::elem_type val = featureRow(0); - for (size_t i = 1; i < featureRow.n_elem; ++i) - if (val != featureRow(i)) - return 1; - return 0; -} - -/** - * Calculate entropy of dimension. - * - * @param labels Corresponding labels of the dimension. - * @param UseWeights Whether we need to run a weighted Decision Stump. - */ -template -template -double DecisionStump::CalculateEntropy( - const VecType& labels, - const WeightVecType& weights) -{ - double entropy = 0.0; - size_t j; - - arma::rowvec numElem(numClasses); - numElem.fill(0); - - // Variable to accumulate the weight in this subview_row. - double accWeight = 0.0; - // Populate numElem; they are used as helpers to calculate entropy. - - if (UseWeights) - { - for (j = 0; j < labels.n_elem; ++j) - { - numElem(labels(j)) += weights(j); - accWeight += weights(j); - } - - for (j = 0; j < numClasses; ++j) - { - const double p1 = ((double) numElem(j) / accWeight); - - // Instead of using log2(), which is C99 and may not exist on some - // compilers, use std::log(), then use the change-of-base formula to make - // the result correct. - entropy += (p1 == 0) ? 0 : p1 * std::log(p1); - } - } - else - { - for (j = 0; j < labels.n_elem; ++j) - numElem(labels(j))++; - - for (j = 0; j < numClasses; ++j) - { - const double p1 = ((double) numElem(j) / labels.n_elem); - - // Instead of using log2(), which is C99 and may not exist on some - // compilers, use std::log(), then use the change-of-base formula to make - // the result correct. - entropy += (p1 == 0) ? 0 : p1 * std::log(p1); - } - } - - return entropy / std::log(2.0); -} - -} // namespace decision_stump -} // namespace mlpack - -#endif diff --git a/src/mlpack/methods/decision_stump/decision_stump_main.cpp b/src/mlpack/methods/decision_stump/decision_stump_main.cpp deleted file mode 100644 index 463c680dfd..0000000000 --- a/src/mlpack/methods/decision_stump/decision_stump_main.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/** - * @file methods/decision_stump/decision_stump_main.cpp - * @author Udit Saxena - * - * Main executable for the decision stump. - * - * mlpack is free software; you may redistribute it and/or modify it under the - * terms of the 3-clause BSD license. You should have received a copy of the - * 3-clause BSD license along with mlpack. If not, see - * http://www.opensource.org/licenses/BSD-3-Clause for more information. - */ -#include -#include -#include -#include -#include "decision_stump.hpp" - -using namespace mlpack; -using namespace mlpack::decision_stump; -using namespace mlpack::util; -using namespace std; -using namespace arma; - -// Program Name. -BINDING_NAME("Decision Stump"); - -// Short description. -BINDING_SHORT_DESC( - "An implementation of a decision stump, which is a single-level decision " - "tree. Given labeled data, a new decision stump can be trained; or, an " - "existing decision stump can be used to classify points."); - -// Long description. -BINDING_LONG_DESC( - "This program implements a decision stump, which is a single-level decision" - " tree. The decision stump will split on one dimension of the input data, " - "and will split into multiple buckets. The dimension and bins are selected" - " by maximizing the information gain of the split. Optionally, the minimum" - " number of training points in each bin can be specified with the " + - PRINT_PARAM_STRING("bucket_size") + " parameter." - "\n\n" - "The decision stump is parameterized by a splitting dimension and a vector " - "of values that denote the splitting values of each bin." - "\n\n" - "This program enables several applications: a decision tree may be trained " - "or loaded, and then that decision tree may be used to classify a given set" - " of test points. The decision tree may also be saved to a file for later " - "usage." - "\n\n" - "To train a decision stump, training data should be passed with the " + - PRINT_PARAM_STRING("training") + " parameter, and their corresponding " - "labels should be passed with the " + PRINT_PARAM_STRING("labels") + " " - "option. Optionally, if " + PRINT_PARAM_STRING("labels") + " is not " - "specified, the labels are assumed to be the last dimension of the " - "training dataset. The " + PRINT_PARAM_STRING("bucket_size") + " " - "parameter controls the minimum number of training points in each decision " - "stump bucket." - "\n\n" - "For classifying a test set, a decision stump may be loaded with the " + - PRINT_PARAM_STRING("input_model") + " parameter (useful for the situation " - "where a stump has already been trained), and a test set may be specified " - "with the " + PRINT_PARAM_STRING("test") + " parameter. The predicted " - "labels can be saved with the " + PRINT_PARAM_STRING("predictions") + " " - "output parameter." - "\n\n" - "Because decision stumps are trained in batch, retraining does not make " - "sense and thus it is not possible to pass both " + - PRINT_PARAM_STRING("training") + " and " + - PRINT_PARAM_STRING("input_model") + "; instead, simply build a new " - "decision stump with the training data." - "\n\n" - "After training, a decision stump can be saved with the " + - PRINT_PARAM_STRING("output_model") + " output parameter. That stump may " - "later be re-used in subsequent calls to this program (or others)."); - -// See also... -BINDING_SEE_ALSO("Decision tree", "#decision_tree"); -BINDING_SEE_ALSO("Decision stumps on Wikipedia", - "https://en.wikipedia.org/wiki/Decision_stump"); -BINDING_SEE_ALSO("mlpack::decision_stump::DecisionStump class documentation", - "@doxygen/classmlpack_1_1decision__stump_1_1DecisionStump.html"); - -// Datasets we might load. -PARAM_MATRIX_IN("training", "The dataset to train on.", "t"); -PARAM_UROW_IN("labels", "Labels for the training set. If not specified, the " - "labels are assumed to be the last row of the training data.", "l"); -PARAM_MATRIX_IN("test", "A dataset to calculate predictions for.", "T"); - -// Output. -PARAM_UROW_OUT("predictions", "The output matrix that will hold the " - "predicted labels for the test set.", "p"); - -/** - * This is the structure that actually saves to disk. We have to save the - * label mappings, too, otherwise everything we load at test time in a future - * run will end up being borked. - */ -struct DSModel -{ - //! The mappings. - arma::Col mappings; - //! The stump. - DecisionStump<> stump; - - //! Serialize the model. - template - void serialize(Archive& ar, const uint32_t /* version */) - { - ar(CEREAL_NVP(mappings)); - ar(CEREAL_NVP(stump)); - } -}; - -// We may load or save a model. -PARAM_MODEL_IN(DSModel, "input_model", "Decision stump model to " - "load.", "m"); -PARAM_MODEL_OUT(DSModel, "output_model", "Output decision stump model to save.", - "M"); - -PARAM_INT_IN("bucket_size", "The minimum number of training points in each " - "decision stump bucket.", "b", 6); - -static void mlpackMain() -{ - // Check that the parameters are reasonable. - RequireOnlyOnePassed({ "training", "input_model" }, true); - RequireAtLeastOnePassed({ "output_model", "predictions" }, false, "no results" - " will be saved"); - - RequireParamValue("bucket_size", [](int x) { return x > 0; }, true, - "bucket size must be positive"); - - ReportIgnoredParam({{ "test", false }}, "predictions"); - - Log::Warn << "DecisionStump is deprecated and will be removed in mlpack " - << "4.0.0. Please use DecisionTree instead with the maximum tree " - << "depth option set to 1 (that will produce a stump)." - << std::endl; - - // We must either load a model, or train a new stump. - DSModel* model; - if (IO::HasParam("training")) - { - model = new DSModel(); - mat trainingData = std::move(IO::GetParam("training")); - - // Load labels, if necessary. - Row labelsIn; - if (IO::HasParam("labels")) - { - labelsIn = std::move(IO::GetParam>("labels")); - } - else - { - // Extract the labels as the last - Log::Info << "Using the last dimension of training set as labels." - << endl; - - labelsIn = arma::conv_to>::from( - trainingData.row(trainingData.n_rows - 1)); - trainingData.shed_row(trainingData.n_rows - 1); - } - - // Normalize the labels. - Row labels; - data::NormalizeLabels(labelsIn, labels, model->mappings); - - const size_t bucketSize = IO::GetParam("bucket_size"); - const size_t classes = labels.max() + 1; - - Timer::Start("training"); - model->stump.Train(trainingData, labels, classes, bucketSize); - Timer::Stop("training"); - } - else - { - model = IO::GetParam("input_model"); - } - - // Now, do we need to do any testing? - if (IO::HasParam("test")) - { - // Load the test file. - mat testingData = std::move(IO::GetParam("test")); - - if (testingData.n_rows <= model->stump.SplitDimension()) - Log::Fatal << "Test data dimensionality (" << testingData.n_rows << ") " - << "is too low; the trained stump requires at least " - << model->stump.SplitDimension() << " dimensions!" << endl; - - Row predictedLabels(testingData.n_cols); - Timer::Start("testing"); - model->stump.Classify(testingData, predictedLabels); - Timer::Stop("testing"); - - // Denormalize predicted labels, if we want to save them. - if (IO::HasParam("predictions")) - { - Row actualLabels; - data::RevertLabels(predictedLabels, model->mappings, actualLabels); - - // Save the predicted labels as output. - IO::GetParam>("predictions") = std::move(actualLabels); - } - } - - // Save the model, if desired. - IO::GetParam("output_model") = model; -} diff --git a/src/mlpack/methods/fastmks/fastmks.hpp b/src/mlpack/methods/fastmks/fastmks.hpp index 93d234d541..ea2057b0b9 100644 --- a/src/mlpack/methods/fastmks/fastmks.hpp +++ b/src/mlpack/methods/fastmks/fastmks.hpp @@ -163,6 +163,11 @@ class FastMKS */ FastMKS& operator=(const FastMKS& other); + /** + * Move assignment operator. + */ + FastMKS& operator=(FastMKS&& other); + //! Destructor for the FastMKS object. ~FastMKS(); diff --git a/src/mlpack/methods/fastmks/fastmks_impl.hpp b/src/mlpack/methods/fastmks/fastmks_impl.hpp index 660617fdb0..3b2d12eaae 100644 --- a/src/mlpack/methods/fastmks/fastmks_impl.hpp +++ b/src/mlpack/methods/fastmks/fastmks_impl.hpp @@ -250,6 +250,35 @@ FastMKS::operator=(const FastMKS& other) naive = other.naive; } +template class TreeType> +FastMKS& +FastMKS::operator=(FastMKS&& other) +{ + if (this != &other) + { + referenceSet = other.referenceSet; + referenceTree = other.referenceTree; + treeOwner = other.treeOwner; + setOwner = other.setOwner; + singleMode = other.singleMode; + naive = other.naive; + metric = std::move(other.metric); + + // Clear information from the other. + other.referenceSet = nullptr; + other.referenceTree = nullptr; + other.treeOwner = false; + other.setOwner = false; + other.singleMode = false; + other.naive = false; + } + return *this; +} + template(*other.linear); - if (other.polynomial) - polynomial = new FastMKS(*other.polynomial); - if (other.cosine) - cosine = new FastMKS(*other.cosine); - if (other.gaussian) - gaussian = new FastMKS(*other.gaussian); - if (other.epan) - epan = new FastMKS(*other.epan); - if (other.triangular) - triangular = new FastMKS(*other.triangular); - if (other.hyptan) - hyptan = new FastMKS(*other.hyptan); + kernelType = other.kernelType; + if (other.linear) + linear = new FastMKS(*other.linear); + if (other.polynomial) + polynomial = new FastMKS(*other.polynomial); + if (other.cosine) + cosine = new FastMKS(*other.cosine); + if (other.gaussian) + gaussian = new FastMKS(*other.gaussian); + if (other.epan) + epan = new FastMKS(*other.epan); + if (other.triangular) + triangular = new FastMKS(*other.triangular); + if (other.hyptan) + hyptan = new FastMKS(*other.hyptan); + } + return *this; +} +FastMKSModel& FastMKSModel::operator=(FastMKSModel&& other) +{ + if (this != &other) + { + kernelType = other.kernelType; + linear = other.linear; + polynomial = other.polynomial; + cosine = other.cosine; + gaussian = other.gaussian; + epan = other.epan; + triangular = other.triangular; + hyptan = other.hyptan; + + // Clear other object. + other.kernelType = KernelTypes::LINEAR_KERNEL; + other.linear = nullptr; + other.polynomial = nullptr; + other.cosine = nullptr; + other.gaussian = nullptr; + other.epan = nullptr; + other.triangular = nullptr; + other.hyptan = nullptr; + } return *this; } diff --git a/src/mlpack/methods/fastmks/fastmks_model.hpp b/src/mlpack/methods/fastmks/fastmks_model.hpp index e84eee0c28..0b7568c641 100644 --- a/src/mlpack/methods/fastmks/fastmks_model.hpp +++ b/src/mlpack/methods/fastmks/fastmks_model.hpp @@ -60,6 +60,9 @@ class FastMKSModel //! Copy assignment operator. FastMKSModel& operator=(const FastMKSModel& other); + //! Move assignment operator. + FastMKSModel& operator=(FastMKSModel&& other); + /** * Clean memory. */ diff --git a/src/mlpack/methods/gmm/em_fit_impl.hpp b/src/mlpack/methods/gmm/em_fit_impl.hpp index 6b4168bf2d..c8d8b6ca9e 100644 --- a/src/mlpack/methods/gmm/em_fit_impl.hpp +++ b/src/mlpack/methods/gmm/em_fit_impl.hpp @@ -156,7 +156,7 @@ Estimate(const arma::mat& observations, // Calculate the new values for omega using the updated conditional // probabilities. - weights = arma::exp(probRowSums - log(observations.n_cols)); + weights = arma::exp(probRowSums - std::log(observations.n_cols)); // Update values of l; calculate new log-likelihood. lOld = l; diff --git a/src/mlpack/methods/hmm/hmm.hpp b/src/mlpack/methods/hmm/hmm.hpp index eceb51b527..1821083a0e 100644 --- a/src/mlpack/methods/hmm/hmm.hpp +++ b/src/mlpack/methods/hmm/hmm.hpp @@ -290,6 +290,79 @@ class HMM */ double LogLikelihood(const arma::mat& dataSeq) const; + /** + * Compute the log of the scaling factor of the given emission probability + * at time t. To calculate the log-likelihood for the whole sequence, + * accumulate log scale over the entire sequence + * This is meant for incremental or streaming computation of the + * log-likelihood of a sequence. For the first data point, provide an empty + * forwardLogProb vector. + * + * @param emissionLogProb emission probability at 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). + * @return Log scale factor of the given sequence of emission at time t. + */ + double EmissionLogScaleFactor(const arma::vec& emissionLogProb, + arma::vec& forwardLogProb) const; + + /** + * Compute the log-likelihood of the given emission probability up to time t, + * storing the result in logLikelihood. + * This is meant for incremental or streaming computation of the + * log-likelihood of a sequence. For the first data point, provide an empty + * forwardLogProb vector. + * + * @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. + * @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). + * @return Log-likelihood of the given sequence of emission up to time t. + */ + double EmissionLogLikelihood(const arma::vec& emissionLogProb, + double &logLikelihood, + arma::vec& forwardLogProb) const; + + /** + * Compute the log of the scaling factor of the given data at time t. + * To calculate the log-likelihood for the whole sequence, accumulate the + * log scale factor (the return value of this function) over the entire + * sequence. + * This is meant for incremental or streaming computation of the + * log-likelihood of a sequence. For the first data point, provide an empty + * forwardLogProb vector. + * + * @param data observation at 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). + * @return Log scale factor of the given sequence of data up at time t. + */ + double LogScaleFactor(const arma::vec &data, + arma::vec& forwardLogProb) const; + + /** + * Compute the log-likelihood of the given data up to time t, storing the + * result in logLikelihood. + * This is meant for incremental or streaming computation of the + * log-likelihood of a sequence. For the first data point, provide an empty + * forwardLogProb vector. + * + * @param data observation at time t. + * @param logLikelihood Log-likelihood of the given sequence of data + * up to time t-1. + * @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). + * @return Log-likelihood of the given sequence of data up to time t. + */ + double LogLikelihood(const arma::vec &data, + double &logLikelihood, + arma::vec& forwardLogProb) const; /** * HMM filtering. Computes the k-step-ahead expected emission at each time * conditioned only on prior observations. That is @@ -366,6 +439,30 @@ class HMM void save(Archive& ar, const uint32_t version) const; protected: + /** + * Given emission probabilities, computes forward probabilities at time t=0. + * + * @param emissionLogProb Emission probability at time t=0. + * @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; + + /** + * Given emission probabilities, computes forward probabilities for time t>0. + * + * @param emissionLogProb Emission probability at time t>0. + * @param logScales Vector in which the log of scaling factors will be saved. + * @param prevForwardLogProb Previous forward probabilities. + * @return Forward probabilities + */ + arma::vec ForwardAtTn( + const arma::vec& emissionLogProb, + double& logScales, + const arma::vec& prevForwardLogProb) const; + // Helper functions. /** * The Forward algorithm (part of the Forward-Backward algorithm). Computes @@ -374,7 +471,7 @@ class HMM * states and columns equal to the number of observations. * * @param dataSeq Data sequence to compute probabilities for. - * @param logScales Vector in which scaling factors will be saved. + * @param logScales Vector in which the log of scaling factors will be saved. * @param forwardLogProb Matrix in which forward probabilities will be saved. */ void Forward(const arma::mat& dataSeq, @@ -389,7 +486,7 @@ class HMM * columns equal to the number of observations. * * @param dataSeq Data sequence to compute probabilities for. - * @param logScales Vector of scaling factors. + * @param logScales Vector of log of scaling factors. * @param backwardLogProb Matrix in which backward probabilities will be saved. */ void Backward(const arma::mat& dataSeq, diff --git a/src/mlpack/methods/hmm/hmm_impl.hpp b/src/mlpack/methods/hmm/hmm_impl.hpp index 8e4d8a2b2f..09ac8113fe 100644 --- a/src/mlpack/methods/hmm/hmm_impl.hpp +++ b/src/mlpack/methods/hmm/hmm_impl.hpp @@ -166,7 +166,7 @@ double HMM::Train(const std::vector& dataSeq) { // 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) + 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) + @@ -193,7 +193,7 @@ double HMM::Train(const std::vector& dataSeq) // Normalize the new initial probabilities. if (dataSeq.size() > 1) - logInitial = newLogInitial - log(dataSeq.size()); + logInitial = newLogInitial - std::log(dataSeq.size()); else logInitial = newLogInitial; @@ -204,7 +204,7 @@ double HMM::Train(const std::vector& dataSeq) logTransition += newLogTransition; // Now we normalize the transition matrix. - for (size_t i = 0; i < logTransition.n_cols; ++i) + for (size_t i = 0; i < logTransition.n_cols; i++) { const double sum = math::AccuLog(logTransition.col(i)); if (std::isfinite(sum)) @@ -309,7 +309,7 @@ void HMM::Train(const std::vector& dataSeq, if (emissionList[state].size() > 0) { arma::mat emissions(dimensionality, emissionList[state].size()); - for (size_t i = 0; i < emissions.n_cols; ++i) + for (size_t i = 0; i < emissions.n_cols; i++) { emissions.col(i) = dataSeq[emissionList[state][i].first].col( emissionList[state][i].second); @@ -486,7 +486,7 @@ double HMM::Predict(const arma::mat& dataSeq, // Assemble the state probability for this element. // Given that we are in state j, we use state with the highest probability // of being the previous state. - for (size_t j = 0; j < logTransition.n_rows; ++j) + 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) + @@ -522,6 +522,80 @@ double HMM::LogLikelihood(const arma::mat& dataSeq) const return accu(logScales); } +/** + * Compute the log of the scaling factor of the given emission probability + * at time t. To calculate the log-likelihood for the whole sequence, + * accumulate log scale over the entire sequence + */ +template +double HMM::EmissionLogScaleFactor( + const arma::vec& emissionLogProb, + arma::vec& forwardLogProb) const +{ + double curLogScale; + if (forwardLogProb.empty()) + { + // We are at the start of the sequence (i.e. time t=0). + forwardLogProb = ForwardAtT0(emissionLogProb, curLogScale); + } + else + { + forwardLogProb = ForwardAtTn(emissionLogProb, curLogScale, + forwardLogProb); + } + + return curLogScale; +} + +/** + * Compute the log-likelihood of the given emission probability up to time t + */ +template +double HMM::EmissionLogLikelihood( + const arma::vec& emissionLogProb, + double& logLikelihood, + arma::vec& forwardLogProb) const +{ + bool isStartOfSeq = forwardLogProb.empty(); + double curLogScale = EmissionLogScaleFactor(emissionLogProb, + forwardLogProb); + logLikelihood = isStartOfSeq ? curLogScale : curLogScale + logLikelihood; + return logLikelihood; +} + +/** + * Compute the log of the scaling factor of the given data at time t. + * To calculate the log-likelihood for the whole sequence, accumulate log + * scale over the entire sequence + */ +template +double HMM::LogScaleFactor(const arma::vec &data, + arma::vec& forwardLogProb) const +{ + arma::vec emissionLogProb(logTransition.n_rows); + + for (size_t state = 0; state < logTransition.n_rows; state++) + { + emissionLogProb(state) = emission[state].LogProbability(data); + } + + return EmissionLogScaleFactor(emissionLogProb, forwardLogProb); +} + +/** + * Compute the log-likelihood of the given data up to time t + */ +template +double HMM::LogLikelihood(const arma::vec& data, + double& logLikelihood, + arma::vec& forwardLogProb) const +{ + bool isStartOfSeq = forwardLogProb.empty(); + double curLogScale = LogScaleFactor(data, forwardLogProb); + logLikelihood = isStartOfSeq ? curLogScale : curLogScale + logLikelihood; + return logLikelihood; +} + /** * HMM filtering. */ @@ -544,7 +618,7 @@ void HMM::Filter(const arma::mat& dataSeq, // Compute expected emissions. // Will not work for distributions without a Mean() function. filterSeq.zeros(dimensionality, dataSeq.n_cols); - for (size_t i = 0; i < emission.size(); ++i) + for (size_t i = 0; i < emission.size(); i++) filterSeq += emission[i].Mean() * forwardProb.row(i); } @@ -566,10 +640,67 @@ void HMM::Smooth(const arma::mat& dataSeq, // Compute expected emissions. // Will not work for distributions without a Mean() function. smoothSeq.zeros(dimensionality, dataSeq.n_cols); - for (size_t i = 0; i < emission.size(); ++i) + for (size_t i = 0; i < emission.size(); i++) smoothSeq += emission[i].Mean() * exp(stateLogProb.row(i)); } +/** + * The Forward procedure (part of the Forward-Backward algorithm). + */ +template +arma::vec HMM::ForwardAtT0(const arma::vec& emissionLogProb, + double& logScales) 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. + 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; + + // Normalize probability. + logScales = math::AccuLog(forwardLogProb); + if (std::isfinite(logScales)) + forwardLogProb -= logScales; + + return forwardLogProb; +} + +/** + * The Forward procedure (part of the Forward-Backward algorithm). + */ +template +arma::vec HMM::ForwardAtTn(const arma::vec& emissionLogProb, + 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); + } + // Normalize probability. + logScales = math::AccuLog(forwardLogProb); + if (std::isfinite(logScales)) + forwardLogProb -= logScales; + + return forwardLogProb; +} + /** * The Forward procedure (part of the Forward-Backward algorithm). */ @@ -585,41 +716,32 @@ void HMM::Forward(const arma::mat& dataSeq, logScales.resize(dataSeq.n_cols); logScales.fill(-std::numeric_limits::infinity()); - ConvertToLogSpace(); - // 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. + + arma::vec emissionLogProb(logTransition.n_rows); for (size_t state = 0; state < logTransition.n_rows; state++) { - forwardLogProb(state, 0) = logInitial(state) + + emissionLogProb(state) = emission[state].LogProbability(dataSeq.unsafe_col(0)); } - // Then normalize the column. - logScales[0] = math::AccuLog(forwardLogProb.col(0)); - if (std::isfinite(logScales[0])) - forwardLogProb.col(0) -= logScales[0]; + forwardLogProb.col(0) = ForwardAtT0(emissionLogProb, logScales(0)); // Now compute the probabilities for each successive observation. for (size_t t = 1; t < dataSeq.n_cols; t++) { - for (size_t j = 0; j < logTransition.n_rows; ++j) + 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 = forwardLogProb.col(t - 1) + logTransition.row(j).t(); - forwardLogProb(j, t) = math::AccuLog(tmp) + - emission[j].LogProbability(dataSeq.unsafe_col(t)); + emissionLogProb(state) = + emission[state].LogProbability(dataSeq.unsafe_col(t)); } - // Normalize probability. - logScales[t] = math::AccuLog(forwardLogProb.col(t)); - if (std::isfinite(logScales[t])) - forwardLogProb.col(t) -= logScales[t]; + forwardLogProb.col(t) = + ForwardAtTn(emissionLogProb, logScales(t), forwardLogProb.col(t-1)); } } @@ -639,7 +761,7 @@ 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) + 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 @@ -660,7 +782,8 @@ void HMM::Backward(const arma::mat& dataSeq, } /** - * Make sure the variables in log space are in sync with the linear counter parts + * Make sure the variables in log space are in sync with the linear + * counterparts. */ template void HMM::ConvertToLogSpace() const diff --git a/src/mlpack/methods/hmm/hmm_model.hpp b/src/mlpack/methods/hmm/hmm_model.hpp index 41a7fd406b..7665397bdc 100644 --- a/src/mlpack/methods/hmm/hmm_model.hpp +++ b/src/mlpack/methods/hmm/hmm_model.hpp @@ -129,6 +129,26 @@ class HMMModel return *this; } + //! Move assignment operator. + HMMModel& operator=(HMMModel&& other) + { + if (this != &other) + { + type = other.type; + discreteHMM = other.discreteHMM; + gaussianHMM = other.gaussianHMM; + gmmHMM = other.gmmHMM; + diagGMMHMM = other.diagGMMHMM; + + other.type = HMMType::DiscreteHMM; + other.discreteHMM = new HMM(); + other.gaussianHMM = nullptr; + other.gmmHMM = nullptr; + other.diagGMMHMM = nullptr; + } + return *this; + } + //! Clean memory. ~HMMModel() { diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp index 488048f4e6..b58d97a423 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree.hpp @@ -155,6 +155,27 @@ class HoeffdingTree */ HoeffdingTree(const HoeffdingTree& other); + /** + * Move another tree. + * + * @param other Tree to move. + */ + HoeffdingTree(HoeffdingTree&& other); + + /** + * Copy assignment operator. + * + * @param other Tree to copy. + */ + HoeffdingTree& operator=(const HoeffdingTree& other); + + /** + * Move assignment operator. + * + * @param other Tree to move. + */ + HoeffdingTree& operator=(HoeffdingTree&& other); + /** * Clean up memory. */ diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp index e6172f8324..f79b0eb027 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_impl.hpp @@ -224,6 +224,138 @@ HoeffdingTree:: } } +// Move constructor. +template class NumericSplitType, + template class CategoricalSplitType> +HoeffdingTree:: + HoeffdingTree(HoeffdingTree&& other) : + numericSplits(std::move(other.numericSplits)), + categoricalSplits(std::move(other.categoricalSplits)), + dimensionMappings(other.dimensionMappings), + ownsMappings(true), + numSamples(other.numSamples), + numClasses(other.numClasses), + maxSamples(other.maxSamples), + checkInterval(other.checkInterval), + minSamples(other.minSamples), + datasetInfo(other.datasetInfo), + ownsInfo(true), + successProbability(other.successProbability), + splitDimension(other.splitDimension), + majorityClass(other.majorityClass), + majorityProbability(other.majorityProbability), + categoricalSplit(std::move(other.categoricalSplit)), + numericSplit(std::move(other.numericSplit)) +{ + // Remove pointers. + other.dimensionMappings = nullptr; + other.datasetInfo = nullptr; + + // Reset primary type variables. + other.numSamples = 0; + other.numClasses = 0; + other.checkInterval = 0; + other.minSamples = 0; + other.successProbability = 0.0; + other.splitDimension = 0; + other.majorityClass = 0; + other.majorityProbability = 0.0; +} + +// Copy assignment operator. +template class NumericSplitType, + template class CategoricalSplitType> +HoeffdingTree& + HoeffdingTree:: + operator=(const HoeffdingTree& other) +{ + if (this != &other) + { + numericSplits = other.numericSplits; + categoricalSplits = other.categoricalSplits; + dimensionMappings = new std::unordered_map>(*other.dimensionMappings); + ownsMappings = true; + numSamples = other.numSamples; + numClasses = other.numClasses; + maxSamples = other.maxSamples; + checkInterval = other.checkInterval; + minSamples = other.minSamples; + datasetInfo = new data::DatasetInfo(*other.datasetInfo); + ownsInfo = true; + successProbability = other.successProbability; + splitDimension = other.splitDimension; + majorityClass = other.majorityClass; + majorityProbability = other.majorityProbability; + categoricalSplit = other.categoricalSplit; + numericSplit = other.numericSplit; + + // Copy each of the children. + for (size_t i = 0; i < other.children.size(); ++i) + { + children.push_back(new HoeffdingTree(*other.children[i])); + + // Delete copied datasetInfo and dimension mappings. + delete children[i]->datasetInfo; + children[i]->datasetInfo = this->datasetInfo; + children[i]->ownsInfo = false; + + delete children[i]->dimensionMappings; + children[i]->dimensionMappings = this->dimensionMappings; + children[i]->ownsMappings = false; + } + } + return *this; +} + +// Move assignment operator. +template class NumericSplitType, + template class CategoricalSplitType> +HoeffdingTree& + HoeffdingTree:: + operator=(HoeffdingTree&& other) +{ + if (this != &other) + { + numericSplits = std::move(other.numericSplits); + categoricalSplits = std::move(other.categoricalSplits); + dimensionMappings = other.dimensionMappings; + ownsMappings = true; + numSamples = other.numSamples; + numClasses = other.numClasses; + maxSamples = other.maxSamples; + checkInterval = other.checkInterval; + minSamples = other.minSamples; + datasetInfo = other.datasetInfo; + ownsInfo = true; + successProbability = other.successProbability; + splitDimension = other.splitDimension; + majorityClass = other.majorityClass; + majorityProbability = other.majorityProbability; + categoricalSplit = std::move(other.categoricalSplit); + numericSplit = std::move(other.numericSplit); + + // Remove pointers. + other.dimensionMappings = nullptr; + other.datasetInfo = nullptr; + + // Reset primary type variables. + other.numSamples = 0; + other.numClasses = 0; + other.checkInterval = 0; + other.minSamples = 0; + other.successProbability = 0.0; + other.splitDimension = 0; + other.majorityClass = 0; + other.majorityProbability = 0.0; + } + return *this; +} + + template class NumericSplitType, template class CategoricalSplitType> @@ -341,7 +473,7 @@ void HoeffdingTree< delete dimensionMappings; const CategoricalSplitType categoricalSplitIn(0, 0); - const NumericSplitType& numericSplitIn(0); + const NumericSplitType numericSplitIn(0); dimensionMappings = new std::unordered_map>(); diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp index d35970dd5b..2dfe857edf 100644 --- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp +++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_model.cpp @@ -62,53 +62,57 @@ HoeffdingTreeModel::HoeffdingTreeModel(HoeffdingTreeModel&& other) : HoeffdingTreeModel& HoeffdingTreeModel::operator=( const HoeffdingTreeModel& other) { - // Clear this model. - delete giniHoeffdingTree; - delete giniBinaryTree; - delete infoHoeffdingTree; - delete infoBinaryTree; + if (this != &other) + { + // Clear this model. + delete giniHoeffdingTree; + delete giniBinaryTree; + delete infoHoeffdingTree; + delete infoBinaryTree; - giniHoeffdingTree = NULL; - giniBinaryTree = NULL; - infoHoeffdingTree = NULL; - infoBinaryTree = NULL; - - // Create the right tree. - type = other.type; - if (other.giniHoeffdingTree && (type == GINI_HOEFFDING)) - giniHoeffdingTree = new GiniHoeffdingTreeType(*other.giniHoeffdingTree); - else if (other.giniBinaryTree && (type == GINI_BINARY)) - giniBinaryTree = new GiniBinaryTreeType(*other.giniBinaryTree); - else if (other.infoHoeffdingTree && (type == INFO_HOEFFDING)) - infoHoeffdingTree = new InfoHoeffdingTreeType(*other.infoHoeffdingTree); - else if (other.infoBinaryTree && (type == INFO_BINARY)) - infoBinaryTree = new InfoBinaryTreeType(*other.infoBinaryTree); + giniHoeffdingTree = NULL; + giniBinaryTree = NULL; + infoHoeffdingTree = NULL; + infoBinaryTree = NULL; + // Create the right tree. + type = other.type; + if (other.giniHoeffdingTree && (type == GINI_HOEFFDING)) + giniHoeffdingTree = new GiniHoeffdingTreeType(*other.giniHoeffdingTree); + else if (other.giniBinaryTree && (type == GINI_BINARY)) + giniBinaryTree = new GiniBinaryTreeType(*other.giniBinaryTree); + else if (other.infoHoeffdingTree && (type == INFO_HOEFFDING)) + infoHoeffdingTree = new InfoHoeffdingTreeType(*other.infoHoeffdingTree); + else if (other.infoBinaryTree && (type == INFO_BINARY)) + infoBinaryTree = new InfoBinaryTreeType(*other.infoBinaryTree); + } return *this; } // Move operator. HoeffdingTreeModel& HoeffdingTreeModel::operator=(HoeffdingTreeModel&& other) { - // Clear this model. - delete giniHoeffdingTree; - delete giniBinaryTree; - delete infoHoeffdingTree; - delete infoBinaryTree; + if (this != &other) + { + // Clear this model. + delete giniHoeffdingTree; + delete giniBinaryTree; + delete infoHoeffdingTree; + delete infoBinaryTree; - type = other.type; - giniHoeffdingTree = other.giniHoeffdingTree; - giniBinaryTree = other.giniBinaryTree; - infoHoeffdingTree = other.infoHoeffdingTree; - infoBinaryTree = other.infoBinaryTree; - - // Clear the other model. - other.type = GINI_HOEFFDING; - other.giniHoeffdingTree = NULL; - other.giniBinaryTree = NULL; - other.infoHoeffdingTree = NULL; - other.infoBinaryTree = NULL; + type = other.type; + giniHoeffdingTree = other.giniHoeffdingTree; + giniBinaryTree = other.giniBinaryTree; + infoHoeffdingTree = other.infoHoeffdingTree; + infoBinaryTree = other.infoBinaryTree; + // Clear the other model. + other.type = GINI_HOEFFDING; + other.giniHoeffdingTree = NULL; + other.giniBinaryTree = NULL; + other.infoHoeffdingTree = NULL; + other.infoBinaryTree = NULL; + } return *this; } diff --git a/src/mlpack/methods/kde/CMakeLists.txt b/src/mlpack/methods/kde/CMakeLists.txt index 31dacaee43..81bee212e3 100644 --- a/src/mlpack/methods/kde/CMakeLists.txt +++ b/src/mlpack/methods/kde/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES kde_stat.hpp kde_model.hpp kde_model_impl.hpp + kde_model.cpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/kde/kde.hpp b/src/mlpack/methods/kde/kde.hpp index 448d32dd84..8885c2e894 100644 --- a/src/mlpack/methods/kde/kde.hpp +++ b/src/mlpack/methods/kde/kde.hpp @@ -140,11 +140,16 @@ class KDE /** * Copy a KDE model. * - * Use std::move if the object to copy is no longer needed. + * @param other KDE model to copy. + */ + KDE& operator=(const KDE& other); + + /** + * Move a KDE model. * * @param other KDE model to copy. */ - KDE& operator=(KDE other); + KDE& operator=(KDE&& other); /** * Destroy the KDE object. If this object created any trees, they will be diff --git a/src/mlpack/methods/kde/kde_impl.hpp b/src/mlpack/methods/kde/kde_impl.hpp index b48190e686..054c02119d 100644 --- a/src/mlpack/methods/kde/kde_impl.hpp +++ b/src/mlpack/methods/kde/kde_impl.hpp @@ -190,31 +190,95 @@ KDE:: -operator=(KDE other) +operator=(const KDE& other) { - // Clean memory. - if (ownsReferenceTree) + if (this != &other) { - delete referenceTree; - delete oldFromNewReferences; + // Clean memory. + if (ownsReferenceTree) + { + delete referenceTree; + delete oldFromNewReferences; + } + kernel = KernelType(other.kernel); + metric = MetricType(other.metric); + relError = other.relError; + absError = other.absError; + ownsReferenceTree = other.ownsReferenceTree; + trained = other.trained; + mode = other.mode; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + if (trained) + { + if (ownsReferenceTree) + { + oldFromNewReferences = + new std::vector(*other.oldFromNewReferences); + referenceTree = new Tree(*other.referenceTree); + } + else + { + oldFromNewReferences = other.oldFromNewReferences; + referenceTree = other.referenceTree; + } + } } + return *this; +} - // Move the other object. - this->kernel = std::move(other.kernel); - this->metric = std::move(other.metric); - this->referenceTree = std::move(other.referenceTree); - this->oldFromNewReferences = std::move(other.oldFromNewReferences); - this->relError = other.relError; - this->absError = other.absError; - this->ownsReferenceTree = other.ownsReferenceTree; - this->trained = other.trained; - this->mode = other.mode; - this->monteCarlo = other.monteCarlo; - this->mcProb = other.mcProb; - this->initialSampleSize = other.initialSampleSize; - this->mcEntryCoef = other.mcEntryCoef; - this->mcBreakCoef = other.mcBreakCoef; +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +KDE& +KDE:: +operator=(KDE&& other) +{ + if (this != &other) + { + // Clean memory. + if (ownsReferenceTree) + { + delete referenceTree; + delete oldFromNewReferences; + } + // Move the other object. + this->kernel = std::move(other.kernel); + this->metric = std::move(other.metric); + // TODO: This should be: this->referenceTree = other.referenceTree; + this->referenceTree = std::move(other.referenceTree); + // TODO: This should be: this->oldFromNewReferences = other.oldFromNewReferences; + this->oldFromNewReferences = std::move(other.oldFromNewReferences); + this->relError = other.relError; + this->absError = other.absError; + this->ownsReferenceTree = other.ownsReferenceTree; + this->trained = other.trained; + this->mode = other.mode; + this->monteCarlo = other.monteCarlo; + this->mcProb = other.mcProb; + this->initialSampleSize = other.initialSampleSize; + this->mcEntryCoef = other.mcEntryCoef; + this->mcBreakCoef = other.mcBreakCoef; + } return *this; } diff --git a/src/mlpack/methods/kde/kde_model.cpp b/src/mlpack/methods/kde/kde_model.cpp new file mode 100644 index 0000000000..7a78c78df5 --- /dev/null +++ b/src/mlpack/methods/kde/kde_model.cpp @@ -0,0 +1,317 @@ +/** + * @file methods/kde/kde_model.cpp + * @author Roberto Hueso + * + * Implementation of KDE Model. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#include "kde_model.hpp" + +namespace mlpack { +namespace kde { + +//! Initialize the KDEModel with the given parameters. +KDEModel::KDEModel(const double bandwidth, + const double relError, + const double absError, + const KernelTypes kernelType, + const TreeTypes treeType, + const bool monteCarlo, + const double mcProb, + const size_t initialSampleSize, + const double mcEntryCoef, + const double mcBreakCoef) : + bandwidth(bandwidth), + relError(relError), + absError(absError), + kernelType(kernelType), + treeType(treeType), + monteCarlo(monteCarlo), + mcProb(mcProb), + initialSampleSize(initialSampleSize), + mcEntryCoef(mcEntryCoef), + mcBreakCoef(mcBreakCoef), + kdeModel(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +KDEModel::KDEModel(const KDEModel& other) : + bandwidth(other.bandwidth), + relError(other.relError), + absError(other.absError), + kernelType(other.kernelType), + treeType(other.treeType), + monteCarlo(other.monteCarlo), + mcProb(other.mcProb), + initialSampleSize(other.initialSampleSize), + mcEntryCoef(other.mcEntryCoef), + mcBreakCoef(other.mcBreakCoef), + kdeModel(other.kdeModel->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +KDEModel::KDEModel(KDEModel&& other) : + bandwidth(other.bandwidth), + relError(other.relError), + absError(other.absError), + kernelType(other.kernelType), + treeType(other.treeType), + monteCarlo(other.monteCarlo), + mcProb(other.mcProb), + initialSampleSize(other.initialSampleSize), + mcEntryCoef(other.mcEntryCoef), + mcBreakCoef(other.mcBreakCoef), + kdeModel(std::move(other.kdeModel)) +{ + // Reset other model. + other.bandwidth = 1.0; + other.relError = KDEDefaultParams::relError; + other.absError = KDEDefaultParams::absError; + other.kernelType = KernelTypes::GAUSSIAN_KERNEL; + other.treeType = TreeTypes::KD_TREE; + other.monteCarlo = KDEDefaultParams::monteCarlo; + other.mcProb = KDEDefaultParams::mcProb; + other.initialSampleSize = KDEDefaultParams::initialSampleSize; + other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; + other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; +} + +KDEModel& KDEModel::operator=(const KDEModel& other) +{ + if (this != &other) + { + delete kdeModel; + + bandwidth = other.bandwidth; + relError = other.relError; + absError = other.absError; + kernelType = other.kernelType; + treeType = other.treeType; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + kdeModel = other.kdeModel->Clone(); + } + + return *this; +} + +KDEModel& KDEModel::operator=(KDEModel&& other) +{ + if (this != &other) + { + delete kdeModel; + + bandwidth = other.bandwidth; + relError = other.relError; + absError = other.absError; + kernelType = other.kernelType; + treeType = other.treeType; + monteCarlo = other.monteCarlo; + mcProb = other.mcProb; + initialSampleSize = other.initialSampleSize; + mcEntryCoef = other.mcEntryCoef; + mcBreakCoef = other.mcBreakCoef; + kdeModel = std::move(other.kdeModel); + + // Reset other model. + other.bandwidth = 1.0; + other.relError = KDEDefaultParams::relError; + other.absError = KDEDefaultParams::absError; + other.kernelType = KernelTypes::GAUSSIAN_KERNEL; + other.treeType = TreeTypes::KD_TREE; + other.monteCarlo = KDEDefaultParams::monteCarlo; + other.mcProb = KDEDefaultParams::mcProb; + other.initialSampleSize = KDEDefaultParams::initialSampleSize; + other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; + other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; + } + + return *this; +} + +// Clean memory. +KDEModel::~KDEModel() +{ + delete kdeModel; +} + +template class TreeType> +KDEWrapperBase* InitializeModelHelper(const KDEModel::KernelTypes kernelType, + const double relError, + const double absError, + const double bandwidth) +{ + switch (kernelType) + { + case KDEModel::GAUSSIAN_KERNEL: + return new KDEWrapper( + relError, absError, kernel::GaussianKernel(bandwidth)); + + case KDEModel::EPANECHNIKOV_KERNEL: + return new KDEWrapper( + relError, absError, kernel::EpanechnikovKernel(bandwidth)); + + case KDEModel::LAPLACIAN_KERNEL: + return new KDEWrapper( + relError, absError, kernel::LaplacianKernel(bandwidth)); + + case KDEModel::SPHERICAL_KERNEL: + return new KDEWrapper( + relError, absError, kernel::SphericalKernel(bandwidth)); + + case KDEModel::TRIANGULAR_KERNEL: + return new KDEWrapper( + relError, absError, kernel::TriangularKernel(bandwidth)); + } + + // This should never happen. + return NULL; +} + +void KDEModel::InitializeModel() +{ + // Clean memory, if necessary. + delete kdeModel; + + // Build the actual model. + switch (treeType) + { + case KD_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case BALL_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case COVER_TREE: + kdeModel = InitializeModelHelper(kernelType, + relError, absError, bandwidth); + break; + + case OCTREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + + case R_TREE: + kdeModel = InitializeModelHelper(kernelType, relError, + absError, bandwidth); + break; + } +} + +void KDEModel::BuildModel(arma::mat&& referenceSet) +{ + InitializeModel(); + + // Set whether to use Monte Carlo estimations or not. + kdeModel->MonteCarlo() = monteCarlo; + + // Set Monte Carlo probability. + kdeModel->MCProb(mcProb); + + // Set Monte Carlo initial sample size. + kdeModel->MCInitialSampleSize() = initialSampleSize; + + // Set Monte Carlo entry coefficient. + kdeModel->MCEntryCoef(mcEntryCoef); + + // Set Monte Carlo break coefficient. + kdeModel->MCBreakCoef(mcBreakCoef); + + // Train the model. + kdeModel->Train(std::move(referenceSet)); +} + +// Perform bichromatic evaluation. +void KDEModel::Evaluate(arma::mat&& querySet, arma::vec& estimates) +{ + kdeModel->Evaluate(std::move(querySet), estimates); +} + +// Perform monochromatic evaluation. +void KDEModel::Evaluate(arma::vec& estimates) +{ + kdeModel->Evaluate(estimates); +} + +// Clean memory. +void KDEModel::CleanMemory() +{ + delete kdeModel; +} + +// Modify model kernel bandwidth. +void KDEModel::Bandwidth(const double newBandwidth) +{ + bandwidth = newBandwidth; + kdeModel->Bandwidth(bandwidth); +} + +// Modify model relative error tolerance. +void KDEModel::RelativeError(const double newRelError) +{ + relError = newRelError; + kdeModel->RelativeError(relError); +} + +// Modify model absolute error tolerance. +void KDEModel::AbsoluteError(const double newAbsError) +{ + absError = newAbsError; + kdeModel->AbsoluteError(absError); +} + +// Modify whether Monte Carlo estimations will be used. +void KDEModel::MonteCarlo(const bool newMonteCarlo) +{ + monteCarlo = newMonteCarlo; + kdeModel->MonteCarlo() = monteCarlo; +} + +// Modify model Monte Carlo probability. +void KDEModel::MCProbability(const double newMCProb) +{ + mcProb = newMCProb; + kdeModel->MCProb(mcProb); +} + +// Modify model Monte Carlo initial sample size. +void KDEModel::MCInitialSampleSize(const size_t newSampleSize) +{ + initialSampleSize = newSampleSize; + kdeModel->MCInitialSampleSize() = initialSampleSize; +} + +// Modify model Monte Carlo entry coefficient. +void KDEModel::MCEntryCoefficient(const double newEntryCoef) +{ + mcEntryCoef = newEntryCoef; + kdeModel->MCEntryCoef(mcEntryCoef); +} + +// Modify model Monte Carlo break coefficient. +void KDEModel::MCBreakCoefficient(const double newBreakCoef) +{ + mcBreakCoef = newBreakCoef; + kdeModel->MCBreakCoef(mcBreakCoef); +} + +} // namespace kde +} // namespace mlpack diff --git a/src/mlpack/methods/kde/kde_model.hpp b/src/mlpack/methods/kde/kde_model.hpp index 220213ba5e..48b06c6382 100644 --- a/src/mlpack/methods/kde/kde_model.hpp +++ b/src/mlpack/methods/kde/kde_model.hpp @@ -22,28 +22,11 @@ #include // Remaining includes. -#include #include "kde.hpp" namespace mlpack { namespace kde { -//! Alias template. -template class TreeType> -using KDEType = KDE::template DualTreeTraverser, - TreeType::template SingleTreeTraverser>; - /** * KernelNormalizer holds a set of methods to normalize estimations applying * in each case the appropiate kernel normalizer function. @@ -81,284 +64,168 @@ class KernelNormalizer }; /** - * DualMonoKDE computes a Kernel Density Estimation on the given KDEType. - * It performs a monochromatic KDE. + * KDEWrapperBase is a base wrapper class for holding all KDE types supported by + * KDEModel. All KDE type wrappers inheirt from this class, allowing a simple + * interface via inheritance for all the different types we want to support. */ -class DualMonoKDE : public boost::static_visitor +class KDEWrapperBase { - private: - //! Vector to store the KDE results. - arma::vec& estimations; - public: - //! Alias template necessary for Visual C++ compiler. - template class TreeType> - using KDETypeT = KDEType; + //! Create the KDEWrapperBase object. The base class does not hold anything, + //! so this constructor does nothing. + KDEWrapperBase() { } - //! Default DualMonoKDE on some KDEType. - template class TreeType> - void operator()(KDETypeT* kde) const; + //! Create a new KDEWrapperBase that is the same as this one. This function + //! will properly handle polymorphism. + virtual KDEWrapperBase* Clone() const = 0; - // TODO Implement specific cases where a leaf size can be selected. + //! Destruct the KDEWrapperBase (nothing to do). + virtual ~KDEWrapperBase() { } - //! DualMonoKDE constructor. - DualMonoKDE(arma::vec& estimations); + //! Modify the bandwidth of the kernel. + virtual void Bandwidth(const double bw) = 0; + + //! Modify the relative error tolerance. + virtual void RelativeError(const double relError) = 0; + + //! Modify the absolute error tolerance. + virtual void AbsoluteError(const double absError) = 0; + + //! Get whether Monte Carlo search is being used. + virtual bool MonteCarlo() const = 0; + //! Modify whether Monte Carlo search is being used. + virtual bool& MonteCarlo() = 0; + + //! Modify the Monte Carlo probability. + virtual void MCProb(const double mcProb) = 0; + + //! Get the Monte Carlo sample size. + virtual size_t MCInitialSampleSize() const = 0; + //! Modify the Monte Carlo sample size. + virtual size_t& MCInitialSampleSize() = 0; + + //! Modify the Monte Carlo entry coefficient. + virtual void MCEntryCoef(const double entryCoef) = 0; + + //! Modify the Monte Carlo break coefficient. + virtual void MCBreakCoef(const double breakCoef) = 0; + + //! Get the search mode. + virtual KDEMode Mode() const = 0; + //! Modify the search mode. + virtual KDEMode& Mode() = 0; + + //! Train the model (build the tree). + virtual void Train(arma::mat&& referenceSet) = 0; + + //! Perform bichromatic KDE (i.e. KDE with a separate query set). + virtual void Evaluate(arma::mat&& querySet, + arma::vec& estimates) = 0; + + //! Perform monochromatic KDE (i.e. with the reference set as the query set). + virtual void Evaluate(arma::vec& estimates) = 0; }; /** - * DualBiKDE computes a Kernel Density Estimation on the given KDEType. - * It performs a bichromatic KDE. + * KDEWrapper is a wrapper class for all KDE types supported by KDEModel. It + * can be extended with new child classes if new functionality for certain types + * is needed. */ -class DualBiKDE : public boost::static_visitor +template class TreeType> +class KDEWrapper : public KDEWrapperBase { - private: - //! Query set dimensionality. - const size_t dimension; - - //! The query set for the KDE. - const arma::mat& querySet; - - //! Vector to store the KDE results. - arma::vec& estimations; - public: - //! Alias template necessary for Visual C++ compiler. - template class TreeType> - using KDETypeT = KDEType; + //! Create the KDEWrapper object, initializing the internally-held KDE object. + KDEWrapper(const double relError, + const double absError, + const KernelType& kernel) : + kde(relError, absError, kernel) + { + // Nothing left to do. + } - //! Default DualBiKDE on some KDEType. - template class TreeType> - void operator()(KDETypeT* kde) const; + //! Create a new KDEWrapper that is the same as this one. This function + //! will properly handle polymorphism. + virtual KDEWrapper* Clone() const { return new KDEWrapper(*this); } - // TODO Implement specific cases where a leaf size can be selected. + //! Destruct the KDEWrapper (nothing to do). + virtual ~KDEWrapper() { } - //! DualBiKDE constructor. Takes ownership of the given querySet. - DualBiKDE(arma::mat&& querySet, arma::vec& estimations); + //! Modify the bandwidth of the kernel. + virtual void Bandwidth(const double bw) { kde.Kernel() = KernelType(bw); } + + //! Modify the relative error tolerance. + virtual void RelativeError(const double eps) { kde.RelativeError(eps); } + + //! Modify the absolute error tolerance. + virtual void AbsoluteError(const double eps) { kde.AbsoluteError(eps); } + + //! Get whether Monte Carlo search is being used. + virtual bool MonteCarlo() const { return kde.MonteCarlo(); } + //! Modify whether Monte Carlo search is being used. + virtual bool& MonteCarlo() { return kde.MonteCarlo(); } + + //! Modify the Monte Carlo probability. + virtual void MCProb(const double mcProb) { kde.MCProb(mcProb); } + + //! Get the Monte Carlo sample size. + virtual size_t MCInitialSampleSize() const + { + return kde.MCInitialSampleSize(); + } + //! Modify the Monte Carlo sample size. + virtual size_t& MCInitialSampleSize() + { + return kde.MCInitialSampleSize(); + } + + //! Modify the Monte Carlo entry coefficient. + virtual void MCEntryCoef(const double e) { kde.MCEntryCoef(e); } + + //! Modify the Monte Carlo break coefficient. + virtual void MCBreakCoef(const double b) { kde.MCBreakCoef(b); } + + //! Get the search mode. + virtual KDEMode Mode() const { return kde.Mode(); } + //! Modify the search mode. + virtual KDEMode& Mode() { return kde.Mode(); } + + //! Train the model (build the tree). + virtual void Train(arma::mat&& referenceSet); + + //! Perform bichromatic KDE (i.e. KDE with a separate query set). + virtual void Evaluate(arma::mat&& querySet, + arma::vec& estimates); + + //! Perform monochromatic KDE (i.e. with the reference set as the query set). + virtual void Evaluate(arma::vec& estimates); + + //! Serialize the KDE model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(kde)); + } + + protected: + typedef KDE KDEType; + + //! The instantiated KDE object that we are wrapping. + KDEType kde; }; /** - * TrainVisitor trains a given KDEType using a reference set. + * The KDEModel provides an abstraction for the KDE class, abstracting away the + * KernelType and TreeType parameters and allowing those to be specified at + * runtime. This class is written for the sake of the `kde` binding, but it is + * not necessarily restricted to that usage. */ -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set used for training. - arma::mat&& referenceSet; - - public: - //! Default TrainVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - // TODO Implement specific cases where a leaf size can be selected. - - //! TrainVisitor constructor. Takes ownership of the given referenceSet. - TrainVisitor(arma::mat&& referenceSet); -}; - -/** - * BandwidthVisitor modifies the bandwidth of a KDEType kernel. - */ -class BandwidthVisitor : public boost::static_visitor -{ - private: - //! Relative error tolerance. - const double bandwidth; - - public: - //! Default BandwidthVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! BandwidthVisitor constructor. - BandwidthVisitor(const double bandwidth); -}; - -/** - * RelErrorVisitor modifies relative error tolerance for a KDEType. - */ -class RelErrorVisitor : public boost::static_visitor -{ - private: - //! Relative error tolerance. - const double relError; - - public: - //! Default RelErrorVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! RelErrorVisitor constructor. - RelErrorVisitor(const double relError); -}; - -/** - * AbsErrorVisitor modifies absolute error tolerance for a KDEType. - */ -class AbsErrorVisitor : public boost::static_visitor -{ - private: - //! Absolute error tolerance. - const double absError; - - public: - //! Default AbsErrorVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! AbsErrorVisitor constructor. - AbsErrorVisitor(const double absError); -}; - -/** - * MonteCarloVisitor activates or deactivates Monte Carlo for a given KDEType. - */ -class MonteCarloVisitor : public boost::static_visitor -{ - private: - //! Whether to use Monte Carlo estimations or not. - const bool monteCarlo; - - public: - //! Default MonteCarloVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MonteCarloVisitor constructor. - MonteCarloVisitor(const bool monteCarlo); -}; - -/** - * MCProbabilityVisitor sets the Monte Carlo probability for a given KDEType. - */ -class MCProbabilityVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo probability. - const double probability; - - public: - //! Default MCProbabilityVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCProbabilityVisitor constructor. - MCProbabilityVisitor(const double probability); -}; - -/** - * MCSampleSizeVisitor sets the Monte Carlo intial sample size for a given - * KDEType. - */ -class MCSampleSizeVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo sample size. - const size_t sampleSize; - - public: - //! Default MCSampleSizeVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCSampleSizeVisitor constructor. - MCSampleSizeVisitor(const size_t sampleSize); -}; - -/** - * MCEntryCoefVisitor sets the Monte Carlo entry coefficient. - */ -class MCEntryCoefVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo entry coefficient. - const double entryCoef; - - public: - //! Default MCEntryCoefVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCEntryCoefVisitor constructor. - MCEntryCoefVisitor(const double entryCoef); -}; - -/** - * MCBreakCoefVisitor sets the Monte Carlo break coefficient. - */ -class MCBreakCoefVisitor : public boost::static_visitor -{ - private: - //! Monte Carlo break coefficient. - const double breakCoef; - - public: - //! Default MCBreakCoefVisitor on some KDEType. - template class TreeType> - void operator()(KDEType* kde) const; - - //! MCBreakCoefVisitor constructor. - MCBreakCoefVisitor(const double breakCoef); -}; - -/** - * ModeVisitor exposes the Mode() method of the KDEType. - */ -class ModeVisitor : public boost::static_visitor -{ - public: - //! Return mode of KDEType instance. - template - KDEMode& operator()(KDEType* kde) const; -}; - -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete KDEType instance. - template - void operator()(KDEType* kde) const; -}; - class KDEModel { public: @@ -413,34 +280,10 @@ class KDEModel double mcBreakCoef; /** - * kdeModel holds an instance of each possible combination of KernelType and - * TreeType. It is initialized using BuildModel. + * kdeModel holds whatever KDE type we are using. It is initialized using the + * `BuildModel()` method. */ - boost::variant*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*, - KDEType*> kdeModel; + KDEWrapperBase* kdeModel; public: /** @@ -487,11 +330,16 @@ class KDEModel /** * Copy the given model. * - * Use std::move if the object to copy is no longer needed. - * * @param other KDEModel to copy. */ - KDEModel& operator=(KDEModel other); + KDEModel& operator=(const KDEModel& other); + + /** + * Take ownership of the contents of the given model. + * + * @param other KDEModel to take ownership of. + */ + KDEModel& operator=(KDEModel&& other); //! Destroy the KDEModel object. ~KDEModel(); @@ -561,10 +409,15 @@ class KDEModel void MCBreakCoefficient(const double newBreakCoef); //! Get the mode of the model. - KDEMode Mode() const; + KDEMode Mode() const { return kdeModel->Mode(); } //! Modify the mode of the model. - KDEMode& Mode(); + KDEMode& Mode() { return kdeModel->Mode(); } + + /** + * Initialize the KDE model. + */ + void InitializeModel(); /** * Build the KDE model with the given parameters and then trains it with the diff --git a/src/mlpack/methods/kde/kde_model_impl.hpp b/src/mlpack/methods/kde/kde_model_impl.hpp index 4b59e7657a..325b071cb3 100644 --- a/src/mlpack/methods/kde/kde_model_impl.hpp +++ b/src/mlpack/methods/kde/kde_model_impl.hpp @@ -18,521 +18,96 @@ namespace mlpack { namespace kde { -//! Initialize the KDEModel with the given parameters. -inline KDEModel::KDEModel(const double bandwidth, - const double relError, - const double absError, - const KernelTypes kernelType, - const TreeTypes treeType, - const bool monteCarlo, - const double mcProb, - const size_t initialSampleSize, - const double mcEntryCoef, - const double mcBreakCoef) : - bandwidth(bandwidth), - relError(relError), - absError(absError), - kernelType(kernelType), - treeType(treeType), - monteCarlo(monteCarlo), - mcProb(mcProb), - initialSampleSize(initialSampleSize), - mcEntryCoef(mcEntryCoef), - mcBreakCoef(mcBreakCoef) -{ - // Nothing to do. -} - -// Copy constructor. -inline KDEModel::KDEModel(const KDEModel& other) : - bandwidth(other.bandwidth), - relError(other.relError), - absError(other.absError), - kernelType(other.kernelType), - treeType(other.treeType), - monteCarlo(other.monteCarlo), - mcProb(other.mcProb), - initialSampleSize(other.initialSampleSize), - mcEntryCoef(other.mcEntryCoef), - mcBreakCoef(other.mcBreakCoef) -{ - // Nothing to do. -} - -// Move constructor. -inline KDEModel::KDEModel(KDEModel&& other) : - bandwidth(other.bandwidth), - relError(other.relError), - absError(other.absError), - kernelType(other.kernelType), - treeType(other.treeType), - monteCarlo(other.monteCarlo), - mcProb(other.mcProb), - initialSampleSize(other.initialSampleSize), - mcEntryCoef(other.mcEntryCoef), - mcBreakCoef(other.mcBreakCoef), - kdeModel(std::move(other.kdeModel)) -{ - // Reset other model. - other.bandwidth = 1.0; - other.relError = KDEDefaultParams::relError; - other.absError = KDEDefaultParams::absError; - other.kernelType = KernelTypes::GAUSSIAN_KERNEL; - other.treeType = TreeTypes::KD_TREE; - other.monteCarlo = KDEDefaultParams::monteCarlo; - other.mcProb = KDEDefaultParams::mcProb; - other.initialSampleSize = KDEDefaultParams::initialSampleSize; - other.mcEntryCoef = KDEDefaultParams::mcEntryCoef; - other.mcBreakCoef = KDEDefaultParams::mcBreakCoef; - other.kdeModel = decltype(other.kdeModel)(); -} - -inline KDEModel& KDEModel::operator=(KDEModel other) -{ - boost::apply_visitor(DeleteVisitor(), kdeModel); - bandwidth = other.bandwidth; - relError = other.relError; - absError = other.absError; - kernelType = other.kernelType; - treeType = other.treeType; - monteCarlo = other.monteCarlo; - mcProb = other.mcProb; - initialSampleSize = other.initialSampleSize; - mcEntryCoef = other.mcEntryCoef; - mcBreakCoef = other.mcBreakCoef; - kdeModel = std::move(other.kdeModel); - return *this; -} - -// Clean memory. -inline KDEModel::~KDEModel() -{ - boost::apply_visitor(DeleteVisitor(), kdeModel); -} - -inline void KDEModel::BuildModel(arma::mat&& referenceSet) -{ - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), kdeModel); - - // Build the actual model. - if (kernelType == GAUSSIAN_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == GAUSSIAN_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::GaussianKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == EPANECHNIKOV_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::EpanechnikovKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == LAPLACIAN_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::LaplacianKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == SPHERICAL_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::SphericalKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == KD_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == BALL_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == COVER_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == OCTREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - else if (kernelType == TRIANGULAR_KERNEL && treeType == R_TREE) - { - kdeModel = new KDEType - (relError, absError, kernel::TriangularKernel(bandwidth)); - } - - // Set whether to use Monte Carlo estimations or not. - MonteCarloVisitor MCVisitor(monteCarlo); - boost::apply_visitor(MCVisitor, kdeModel); - - // Set Monte Carlo probability. - MCProbabilityVisitor probabilityVisitor(mcProb); - boost::apply_visitor(probabilityVisitor, kdeModel); - - // Set Monte Carlo initial sample size. - MCSampleSizeVisitor sampleSizeVisitor(initialSampleSize); - boost::apply_visitor(sampleSizeVisitor, kdeModel); - - // Set Monte Carlo entry coefficient. - MCEntryCoefVisitor entryCoefficientVisitor(mcEntryCoef); - boost::apply_visitor(entryCoefficientVisitor, kdeModel); - - // Set Monte Carlo break coefficient. - MCBreakCoefVisitor breakCoefficientVisitor(mcBreakCoef); - boost::apply_visitor(breakCoefficientVisitor, kdeModel); - - // Train the model. - TrainVisitor train(std::move(referenceSet)); - boost::apply_visitor(train, kdeModel); -} - -// Perform bichromatic evaluation. -inline void KDEModel::Evaluate(arma::mat&& querySet, arma::vec& estimations) -{ - Log::Info << "Evaluating KDE..." << std::endl; - DualBiKDE eval(std::move(querySet), estimations); - boost::apply_visitor(eval, kdeModel); -} - -// Perform monochromatic evaluation. -inline void KDEModel::Evaluate(arma::vec& estimations) -{ - Log::Info << "Evaluating KDE..." << std::endl; - DualMonoKDE eval(estimations); - boost::apply_visitor(eval, kdeModel); -} - -// Clean memory. -inline void KDEModel::CleanMemory() -{ - boost::apply_visitor(DeleteVisitor(), kdeModel); -} - -// Parameters for KDE evaluation. -DualMonoKDE::DualMonoKDE(arma::vec& estimations): - estimations(estimations) -{} - -// Default KDE evaluation. +//! Train the model (build the tree). template class TreeType> -void DualMonoKDE::operator()(KDETypeT* kde) const +void KDEWrapper::Train(arma::mat&& referenceSet) { - if (kde) + kde.Train(std::move(referenceSet)); +} + +//! Perform bichromatic KDE (i.e. KDE with a separate query set). +template class TreeType> +void KDEWrapper::Evaluate(arma::mat&& querySet, + arma::vec& estimates) +{ + const size_t dimension = querySet.n_rows; + kde.Evaluate(std::move(querySet), estimates); + KernelNormalizer::ApplyNormalizer(kde.Kernel(), + dimension, + estimates); +} + +//! Perform monochromatic KDE (i.e. with the reference set as the query set). +template class TreeType> +void KDEWrapper::Evaluate(arma::vec& estimates) +{ + kde.Evaluate(estimates); + const size_t dimension = kde.ReferenceTree()->Dataset().n_rows; + KernelNormalizer::ApplyNormalizer(kde.Kernel(), + dimension, + estimates); +} + +template class TreeType, + typename Archive> +void SerializationHelper(Archive& ar, + KDEWrapperBase* kdeModel, + const KDEModel::KernelTypes kernelType) +{ + switch (kernelType) { - kde->Evaluate(estimations); - const size_t dimension = (kde->ReferenceTree())->Dataset().n_rows; - KernelNormalizer::ApplyNormalizer(kde->Kernel(), - dimension, - estimations); + case KDEModel::GAUSSIAN_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::EPANECHNIKOV_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::LAPLACIAN_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::SPHERICAL_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } + case KDEModel::TRIANGULAR_KERNEL: + { + KDEWrapper& typedModel = + dynamic_cast&>(*kdeModel); + ar(CEREAL_NVP(typedModel)); + break; + } } - else - { - throw std::runtime_error("no KDE model initialized"); - } -} - -// Parameters for KDE evaluation. -DualBiKDE::DualBiKDE(arma::mat&& querySet, arma::vec& estimations): - dimension(querySet.n_rows), - querySet(std::move(querySet)), - estimations(estimations) -{} - -// Default KDE evaluation. -template class TreeType> -void DualBiKDE::operator()(KDETypeT* kde) const -{ - if (kde) - { - kde->Evaluate(std::move(querySet), estimations); - KernelNormalizer::ApplyNormalizer(kde->Kernel(), - dimension, - estimations); - } - else - { - throw std::runtime_error("no KDE model initialized"); - } -} - -// Parameters for Train. -TrainVisitor::TrainVisitor(arma::mat&& referenceSet) : - referenceSet(std::move(referenceSet)) -{} - -// Default Train. -template class TreeType> -void TrainVisitor::operator()(KDEType* kde) const -{ - Log::Info << "Training KDE model..." << std::endl; - if (kde) - kde->Train(std::move(referenceSet)); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Modify kernel bandwidth. -BandwidthVisitor::BandwidthVisitor(const double bandwidth) : - bandwidth(bandwidth) -{} - -// Default modify kernel bandwidth. -template class TreeType> -void BandwidthVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->Kernel() = KernelType(bandwidth); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Modify relative error tolerance. -RelErrorVisitor::RelErrorVisitor(const double relError) : - relError(relError) -{} - -// Default modify relative error tolerance. -template class TreeType> -void RelErrorVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->RelativeError(relError); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Modify absolute error tolerance. -AbsErrorVisitor::AbsErrorVisitor(const double absError) : - absError(absError) -{} - -// Default modify absolute error tolerance. -template class TreeType> -void AbsErrorVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->AbsoluteError(absError); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Activate or deactivate Monte Carlo. -MonteCarloVisitor::MonteCarloVisitor(const bool monteCarlo) : - monteCarlo(monteCarlo) -{} - -// Default activate or deactivate Monte Carlo. -template class TreeType> -void MonteCarloVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MonteCarlo() = monteCarlo; - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo probability. -MCProbabilityVisitor::MCProbabilityVisitor(const double probability) : - probability(probability) -{} - -// Default probability for Monte Carlo. -template class TreeType> -void MCProbabilityVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCProb(probability); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo sample size. -MCSampleSizeVisitor::MCSampleSizeVisitor(const size_t sampleSize) : - sampleSize(sampleSize) -{} - -// Default sample size for Monte Carlo. -template class TreeType> -void MCSampleSizeVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCInitialSampleSize() = sampleSize; - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo entry coefficient. -MCEntryCoefVisitor::MCEntryCoefVisitor(const double entryCoef) : - entryCoef(entryCoef) -{} - -// Default entry coefficient for Monte Carlo. -template class TreeType> -void MCEntryCoefVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCEntryCoef(entryCoef); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Set Monte Carlo break coefficient. -MCBreakCoefVisitor::MCBreakCoefVisitor(const double breakCoef) : - breakCoef(breakCoef) -{} - -// Default break coefficient for Monte Carlo. -template class TreeType> -void MCBreakCoefVisitor::operator()(KDEType* kde) const -{ - if (kde) - kde->MCBreakCoef(breakCoef); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Delete model. -template -void DeleteVisitor::operator()(KDEType* kde) const -{ - if (kde) - delete kde; -} - -// Mode of model. -template -KDEMode& ModeVisitor::operator()(KDEType* kde) const -{ - if (kde) - return kde->Mode(); - else - throw std::runtime_error("no KDE model initialized"); -} - -// Get mode of model. -KDEMode KDEModel::Mode() const -{ - return boost::apply_visitor(ModeVisitor(), kdeModel); -} - -// Modify mode of model. -KDEMode& KDEModel::Mode() -{ - return boost::apply_visitor(ModeVisitor(), kdeModel); } // Serialize the model. @@ -560,73 +135,31 @@ void KDEModel::serialize(Archive& ar, const uint32_t /* version */) } if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), kdeModel); + InitializeModel(); // Values will be overwritten. - ar(CEREAL_VARIANT_POINTER(kdeModel)); -} + // Avoid polymorphism in serialization by serializing directly by the type. + switch (treeType) + { + case KD_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify model kernel bandwidth. -void KDEModel::Bandwidth(const double newBandwidth) -{ - bandwidth = newBandwidth; - BandwidthVisitor bandwidthVisitor(newBandwidth); - boost::apply_visitor(bandwidthVisitor, kdeModel); -} + case BALL_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify model relative error tolerance. -void KDEModel::RelativeError(const double newRelError) -{ - relError = newRelError; - RelErrorVisitor relErrorVisitor(newRelError); - boost::apply_visitor(relErrorVisitor, kdeModel); -} + case COVER_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify model absolute error tolerance. -void KDEModel::AbsoluteError(const double newAbsError) -{ - absError = newAbsError; - AbsErrorVisitor absErrorVisitor(newAbsError); - boost::apply_visitor(absErrorVisitor, kdeModel); -} + case OCTREE: + SerializationHelper(ar, kdeModel, kernelType); + break; -// Modify whether Monte Carlo estimations will be used. -void KDEModel::MonteCarlo(const bool newMonteCarlo) -{ - monteCarlo = newMonteCarlo; - MonteCarloVisitor monteCarloVisitor(newMonteCarlo); - boost::apply_visitor(monteCarloVisitor, kdeModel); -} - -// Modify model Monte Carlo probability. -void KDEModel::MCProbability(const double newMCProb) -{ - mcProb = newMCProb; - MCProbabilityVisitor mcProbVisitor(newMCProb); - boost::apply_visitor(mcProbVisitor, kdeModel); -} - -// Modify model Monte Carlo initial sample size. -void KDEModel::MCInitialSampleSize(const size_t newSampleSize) -{ - initialSampleSize = newSampleSize; - MCSampleSizeVisitor mcSampleSizeVisitor(newSampleSize); - boost::apply_visitor(mcSampleSizeVisitor, kdeModel); -} - -// Modify model Monte Carlo entry coefficient. -void KDEModel::MCEntryCoefficient(const double newEntryCoef) -{ - mcEntryCoef = newEntryCoef; - MCEntryCoefVisitor mcEntryCoefVisitor(newEntryCoef); - boost::apply_visitor(mcEntryCoefVisitor, kdeModel); -} - -// Modify model Monte Carlo break coefficient. -void KDEModel::MCBreakCoefficient(const double newBreakCoef) -{ - mcBreakCoef = newBreakCoef; - MCBreakCoefVisitor mcBreakCoefVisitor(newBreakCoef); - boost::apply_visitor(mcBreakCoefVisitor, kdeModel); + case R_TREE: + SerializationHelper(ar, kdeModel, kernelType); + break; + } } } // namespace kde diff --git a/src/mlpack/methods/kmeans/CMakeLists.txt b/src/mlpack/methods/kmeans/CMakeLists.txt index 1dbbbba626..6782ed2f66 100644 --- a/src/mlpack/methods/kmeans/CMakeLists.txt +++ b/src/mlpack/methods/kmeans/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES kill_empty_clusters.hpp kmeans.hpp kmeans_impl.hpp + kmeans_plus_plus_initialization.hpp max_variance_new_cluster.hpp max_variance_new_cluster_impl.hpp naive_kmeans.hpp diff --git a/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp b/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp index 6f180c2e99..2d1a66fe12 100644 --- a/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp +++ b/src/mlpack/methods/kmeans/dual_tree_kmeans_rules_impl.hpp @@ -40,7 +40,8 @@ DualTreeKMeansRules::DualTreeKMeansRules( baseCases(0), scores(0), lastQueryIndex(dataset.n_cols), - lastReferenceIndex(centroids.n_cols) + lastReferenceIndex(centroids.n_cols), + lastBaseCase(0.0) { // We must set the traversal info last query and reference node pointers to // something that is both invalid (i.e. not a tree node) and not NULL. We'll @@ -156,8 +157,7 @@ inline double DualTreeKMeansRules::Score( traversalInfo.LastQueryNode()->MinimumBoundDistance(); const double lastRefDescDist = traversalInfo.LastReferenceNode()->MinimumBoundDistance(); - adjustedScore = lastScore + lastQueryDescDist; - adjustedScore = lastScore + lastRefDescDist; + adjustedScore = lastScore + lastQueryDescDist + lastRefDescDist; } // Assemble an adjusted score. For nearest neighbor search, this adjusted diff --git a/src/mlpack/methods/kmeans/kmeans_main.cpp b/src/mlpack/methods/kmeans/kmeans_main.cpp index 4c7a689aec..4707c05b7e 100644 --- a/src/mlpack/methods/kmeans/kmeans_main.cpp +++ b/src/mlpack/methods/kmeans/kmeans_main.cpp @@ -17,6 +17,7 @@ #include "allow_empty_clusters.hpp" #include "kill_empty_clusters.hpp" #include "refined_start.hpp" +#include "kmeans_plus_plus_initialization.hpp" #include "elkan_kmeans.hpp" #include "hamerly_kmeans.hpp" #include "pelleg_moore_kmeans.hpp" @@ -44,14 +45,17 @@ BINDING_LONG_DESC( " the point furthest from the centroid of the cluster with maximum variance" " is taken to fill that cluster." "\n\n" - "Optionally, the Bradley and Fayyad approach (\"Refining initial points for" - " k-means clustering\", 1998) can be used to select initial points by " - "specifying the " + PRINT_PARAM_STRING("refined_start") + " parameter. " - "This approach works by taking random samplings of the dataset; to specify " - "the number of samplings, the " + PRINT_PARAM_STRING("samplings") + - " parameter is used, and to specify the percentage of the dataset to be " - "used in each sample, the " + PRINT_PARAM_STRING("percentage") + - " parameter is used (it should be a value between 0.0 and 1.0)." + "Optionally, the strategy to choose initial centroids can be specified. " + "The k-means++ algorithm can be used to choose initial centroids with " + "the " + PRINT_PARAM_STRING("kmeans_plus_plus") + " parameter. The " + "Bradley and Fayyad approach (\"Refining initial points for k-means " + "clustering\", 1998) can be used to select initial points by specifying " + "the " + PRINT_PARAM_STRING("refined_start") + " parameter. This approach " + "works by taking random samplings of the dataset; to specify the number of " + "samplings, the " + PRINT_PARAM_STRING("samplings") + " parameter is used, " + "and to specify the percentage of the dataset to be used in each sample, " + "the " + PRINT_PARAM_STRING("percentage") + " parameter is used (it should " + "be a value between 0.0 and 1.0)." "\n\n" "There are several options available for the algorithm used for each Lloyd " "iteration, specified with the " + PRINT_PARAM_STRING("algorithm") + " " @@ -102,6 +106,7 @@ BINDING_EXAMPLE( // See also... BINDING_SEE_ALSO("K-Means tutorial", "@doxygen/kmtutorial.html"); BINDING_SEE_ALSO("@dbscan", "#dbscan"); +BINDING_SEE_ALSO("k-means++", "https://en.wikipedia.org/wiki/K-means%2B%2B"); BINDING_SEE_ALSO("Using the triangle inequality to accelerate k-means (pdf)", "http://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf"); BINDING_SEE_ALSO("Making k-means even faster (pdf)", @@ -147,6 +152,8 @@ PARAM_INT_IN("samplings", "Number of samplings to perform for refined start " "(use when --refined_start is specified).", "S", 100); PARAM_DOUBLE_IN("percentage", "Percentage of dataset to use for each refined " "start sampling (use when --refined_start is specified).", "p", 0.02); +PARAM_FLAG("kmeans_plus_plus", "Use the k-means++ initialization strategy to " + "choose initial points.", "K"); PARAM_STRING_IN("algorithm", "Algorithm to use for the Lloyd iteration " "('naive', 'pelleg-moore', 'elkan', 'hamerly', 'dualtree', or " @@ -176,6 +183,9 @@ static void mlpackMain() else math::RandomSeed((size_t) std::time(NULL)); + RequireOnlyOnePassed({ "refined_start", "kmeans_plus_plus" }, true, + "Only one initialization strategy can be specified!", true); + // Now, start building the KMeans type that we'll be using. Start with the // initial partition policy. The call to FindEmptyClusterPolicy<> results in // a call to RunKMeans<> and the algorithm is completed. @@ -191,6 +201,11 @@ static void mlpackMain() FindEmptyClusterPolicy(RefinedStart(samplings, percentage)); } + else if (IO::HasParam("kmeans_plus_plus")) + { + FindEmptyClusterPolicy( + KMeansPlusPlusInitialization()); + } else { FindEmptyClusterPolicy(SampleInitialization()); @@ -271,7 +286,7 @@ void RunKMeans(const InitialPartitionPolicy& ipp) const int maxIterations = IO::GetParam("max_iterations"); // Make sure we have an output file if we're not doing the work in-place. - RequireAtLeastOnePassed({ "in_place", "output", "centroid" }, false, + RequireOnlyOnePassed({ "in_place", "output", "centroid" }, false, "no results will be saved"); arma::mat dataset = IO::GetParam("input"); // Load our dataset. diff --git a/src/mlpack/methods/kmeans/kmeans_plus_plus_initialization.hpp b/src/mlpack/methods/kmeans/kmeans_plus_plus_initialization.hpp new file mode 100644 index 0000000000..e43c59fe1a --- /dev/null +++ b/src/mlpack/methods/kmeans/kmeans_plus_plus_initialization.hpp @@ -0,0 +1,103 @@ +/** + * @file methods/kmeans/kmeans_plus_plus_initialization.hpp + * @author Ryan Curtin + * + * This file implements the k-means++ initialization strategy. + */ +#ifndef MLPACK_METHODS_KMEANS_KMEANS_PLUS_PLUS_INITIALIZATION_HPP +#define MLPACK_METHODS_KMEANS_KMEANS_PLUS_PLUS_INITIALIZATION_HPP + +#include + +/** + * This class implements the k-means++ initialization, as described in the + * following paper: + * + * @code + * @inproceedings{arthur2007k, + * title={k-means++: The advantages of careful seeding}, + * author={Arthur, David and Vassilvitskii, Sergei}, + * booktitle={Proceedings of the Eighteenth Annual ACM-SIAM Symposium on + * Discrete Algorithms (SODA '07)}, + * pages={1027--1035}, + * year={2007}, + * organization={Society for Industrial and Applied Mathematics} + * } + * @endcode + * + * In accordance with mlpack's InitialPartitionPolicy template type, we only + * need to implement a constructor and a method to compute the initial + * centroids. + */ +class KMeansPlusPlusInitialization +{ + public: + //! Empty constructor, required by the InitialPartitionPolicy type definition. + KMeansPlusPlusInitialization() { } + + /** + * Initialize the centroids matrix by randomly sampling points from the data + * matrix. + * + * @param data Dataset. + * @param clusters Number of clusters. + * @param centroids Matrix to put initial centroids into. + */ + template + inline static void Cluster(const MatType& data, + const size_t clusters, + arma::mat& centroids) + { + centroids.set_size(data.n_rows, clusters); + + // We'll sample our first point fully randomly. + size_t firstPoint = mlpack::math::RandInt(0, data.n_cols); + centroids.col(0) = data.col(firstPoint); + + // Utility variable. + arma::vec distribution(data.n_cols); + + // Now, sample other points... + for (size_t i = 1; i < clusters; ++i) + { + // We must compute the CDF for sampling... this depends on the computation + // of the minimum distance between each point and its closest + // already-chosen centroid. + // + // This computation is ripe for speedup with trees! I am not sure exactly + // how much we would need to approximate, but I think it could be done + // without breaking the O(log k)-competitive guarantee (I think). + for (size_t p = 0; p < data.n_cols; ++p) + { + double minDistance = std::numeric_limits::max(); + for (size_t j = 0; j < i; ++j) + { + const double distance = + mlpack::metric::SquaredEuclideanDistance::Evaluate(data.col(p), + centroids.col(j)); + minDistance = std::min(distance, minDistance); + } + + distribution[p] = minDistance; + } + + // Next normalize the distribution (actually technically we could avoid + // this). + distribution /= arma::accu(distribution); + + // Turn it into a CDF for convenience... + for (size_t j = 1; j < distribution.n_elem; ++j) + distribution[j] += distribution[j - 1]; + + // Sample a point... + const double sampleValue = mlpack::math::Random(); + const double* elem = std::lower_bound(distribution.begin(), + distribution.end(), sampleValue); + const size_t position = (size_t) + (elem - distribution.begin()) / sizeof(double); + centroids.col(i) = data.col(position); + } + } +}; + +#endif diff --git a/src/mlpack/methods/lars/lars.cpp b/src/mlpack/methods/lars/lars.cpp index b86c361a3f..03612406fc 100644 --- a/src/mlpack/methods/lars/lars.cpp +++ b/src/mlpack/methods/lars/lars.cpp @@ -366,13 +366,16 @@ double LARS::Train(const arma::mat& matX, if (isActive[ind] || isIgnored[ind]) continue; - double dirCorr = dot(dataRef.col(ind), yHatDirection); - double val1 = (maxCorr - corr(ind)) / (normalization - dirCorr); - double val2 = (maxCorr + corr(ind)) / (normalization + dirCorr); - if ((val1 > 0) && (val1 < gamma)) - gamma = val1; - if ((val2 > 0) && (val2 < gamma)) - gamma = val2; + const double dirCorr = dot(dataRef.col(ind), yHatDirection); + const double val1 = (maxCorr - corr(ind)) / (normalization - dirCorr); + const double val2 = (maxCorr + corr(ind)) / (normalization + dirCorr); + if ((val1 > 0.0) && (val1 < gamma)) + gamma = val1; + if ((val2 > 0.0) && (val2 < gamma)) + gamma = val2; + // Handle edge case where the largest actually is equal to 0. + if (std::max(val1, val2) == 0.0) + gamma = 0.0; } } diff --git a/src/mlpack/methods/neighbor_search/kfn_main.cpp b/src/mlpack/methods/neighbor_search/kfn_main.cpp index 73b9dbd64f..65f3083d56 100644 --- a/src/mlpack/methods/neighbor_search/kfn_main.cpp +++ b/src/mlpack/methods/neighbor_search/kfn_main.cpp @@ -237,14 +237,14 @@ static void mlpackMain() kfn->TreeType() = tree; kfn->RandomBasis() = randomBasis; + kfn->LeafSize() = size_t(lsInt); Log::Info << "Using reference data from " << IO::GetPrintableParam("reference") << "." << endl; arma::mat referenceSet = std::move(IO::GetParam("reference")); - kfn->BuildModel(std::move(referenceSet), size_t(lsInt), searchMode, - epsilon); + kfn->BuildModel(std::move(referenceSet), searchMode, epsilon); } else { diff --git a/src/mlpack/methods/neighbor_search/knn_main.cpp b/src/mlpack/methods/neighbor_search/knn_main.cpp index 9f643ecd61..87ca2203b0 100644 --- a/src/mlpack/methods/neighbor_search/knn_main.cpp +++ b/src/mlpack/methods/neighbor_search/knn_main.cpp @@ -261,8 +261,7 @@ static void mlpackMain() arma::mat referenceSet = std::move(IO::GetParam("reference")); - knn->BuildModel(std::move(referenceSet), size_t(lsInt), searchMode, - epsilon); + knn->BuildModel(std::move(referenceSet), searchMode, epsilon); } else { diff --git a/src/mlpack/methods/neighbor_search/neighbor_search.hpp b/src/mlpack/methods/neighbor_search/neighbor_search.hpp index 1475970af6..2476e0484a 100644 --- a/src/mlpack/methods/neighbor_search/neighbor_search.hpp +++ b/src/mlpack/methods/neighbor_search/neighbor_search.hpp @@ -31,8 +31,13 @@ namespace mlpack { namespace neighbor { // Forward declaration. -template -class TrainVisitor; +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +class LeafSizeNSWrapper; //! NeighborSearchMode represents the different neighbor search modes available. enum NeighborSearchMode @@ -359,8 +364,8 @@ class NeighborSearch bool treeNeedsReset; //! The NSModel class should have access to internal members. - template - friend class TrainVisitor; + friend class LeafSizeNSWrapper; }; // class NeighborSearch } // namespace neighbor diff --git a/src/mlpack/methods/neighbor_search/ns_model.hpp b/src/mlpack/methods/neighbor_search/ns_model.hpp index 981d0f9be9..b13918fa7d 100644 --- a/src/mlpack/methods/neighbor_search/ns_model.hpp +++ b/src/mlpack/methods/neighbor_search/ns_model.hpp @@ -4,8 +4,9 @@ * * This is a model for nearest or furthest neighbor search. It is useful in * that it provides an easy way to serialize a model, abstracts away the - * different types of trees, and also reflects the NeighborSearch API and - * automatically directs to the right tree type. + * different types of trees, and also (roughly) reflects the NeighborSearch API + * and automatically directs to the right tree type. It is meant to be used by + * the knn and kfn bindings. * * mlpack is free software; you may redistribute it and/or modify it under the * terms of the 3-clause BSD license. You should have received a copy of the @@ -20,218 +21,302 @@ #include #include #include -#include #include "neighbor_search.hpp" namespace mlpack { namespace neighbor { /** - * Alias template for euclidean neighbor search. + * NSWrapperBase is a base wrapper class for holding all NeighborSearch types + * supported by NSModel. All NeighborSearch type wrappers inherit from this + * class, allowing a simple interface via inheritance for all the different + * types we want to support. + */ +class NSWrapperBase +{ + public: + //! Create the NSWrapperBase object. The base class does not hold anything, + //! so this constructor does not do anything. + NSWrapperBase() { } + + //! Create a new NSWrapperBase that is the same as this one. This function + //! will properly handle polymorphism. + virtual NSWrapperBase* Clone() const = 0; + + //! Destruct the NSWrapperBase (nothing to do). + virtual ~NSWrapperBase() { }; + + //! Return a reference to the dataset. + virtual const arma::mat& Dataset() const = 0; + + //! Get the search mode. + virtual NeighborSearchMode SearchMode() const = 0; + //! Modify the search modem + virtual NeighborSearchMode& SearchMode() = 0; + + //! Get the approximation parameter epsilon. + virtual double Epsilon() const = 0; + //! Modify the approximation parameter epsilon. + virtual double& Epsilon() = 0; + + //! Train the NeighborSearch model with the given parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize, + const double tau, + const double rho) = 0; + + //! Perform bichromatic neighbor search (i.e. search with a separate query + //! set). + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double rho) = 0; + + //! Perform monochromatic neighbor search (i.e. use the reference set as the + //! query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) = 0; +}; + +/** + * NSWrapper is a wrapper class for most NeighborSearch types. */ template class TreeType> -using NSType = NeighborSearch, - arma::mat>::template DualTreeTraverser>; - -/** - * MonoSearchVisitor executes a monochromatic neighbor search on the given - * NSType. We don't make any difference for different instantiations of NSType. - */ -class MonoSearchVisitor : public boost::static_visitor + typename TreeMatType> class TreeType, + template class DualTreeTraversalType = + TreeType, + arma::mat>::template DualTreeTraverser, + template class SingleTreeTraversalType = + TreeType, + arma::mat>::template SingleTreeTraverser> +class NSWrapper : public NSWrapperBase { - private: - //! Number of neighbors to search for. - const size_t k; - //! Result matrix for neighbors. - arma::Mat& neighbors; - //! Result matrix for distances. - arma::mat& distances; - public: - //! Perform monochromatic nearest neighbor search. - template - void operator()(NSType* ns) const; + //! Construct the NSWrapper object, initializing the internally-held + //! NeighborSearch object. + NSWrapper(const NeighborSearchMode searchMode, + const double epsilon) : + ns(searchMode, epsilon) + { + // Nothing else to do. + } - //! Construct the MonoSearchVisitor object with the given parameters. - MonoSearchVisitor(const size_t k, - arma::Mat& neighbors, - arma::mat& distances) : - k(k), - neighbors(neighbors), - distances(distances) - {}; + //! Delete the NSWrapper object. + virtual ~NSWrapper() { } + + //! Create a copy of this NSWrapper object. This correctly handles + //! polymorphism. + virtual NSWrapper* Clone() const { return new NSWrapper(*this); } + + //! Get a reference to the reference set. + const arma::mat& Dataset() const { return ns.ReferenceSet(); } + + //! Get the search mode. + NeighborSearchMode SearchMode() const { return ns.SearchMode(); } + //! Modify the search mode. + NeighborSearchMode& SearchMode() { return ns.SearchMode(); } + + //! Get epsilon, the approximation parameter. + double Epsilon() const { return ns.Epsilon(); } + //! Modify epsilon, the approximation parameter. + double& Epsilon() { return ns.Epsilon(); } + + //! Train the model with the given options. For NSWrapper, we ignore the + //! extra parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t /* leafSize */, + const double /* tau */, + const double /* rho */); + + //! Perform bichromatic neighbor search (i.e. search with a separate query + //! set). For NSWrapper, we ignore the extra parameters. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */, + const double /* rho */); + + //! Perform monochromatic neighbor search (i.e. use the reference set as the + //! query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances); + + //! Serialize the NeighborSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ns)); + } + + protected: + // Convenience typedef for the neighbor search type held by this class. + typedef NeighborSearch NSType; + + //! The instantiated NeighborSearch object that we are wrapping. + NSType ns; }; /** - * BiSearchVisitor executes a bichromatic neighbor search on the given NSType. - * We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, before doing neighbor search, - * a query tree with proper leafSize is built from the querySet. + * LeafSizeNSWrapper wraps any NeighborSearch types that take a leaf size for + * tree construction. The implementations of Train() and Search() take the leaf + * size into account. + */ +template class TreeType, + template class DualTreeTraversalType = + TreeType, + arma::mat>::template DualTreeTraverser, + template class SingleTreeTraversalType = + TreeType, + arma::mat>::template SingleTreeTraverser> +class LeafSizeNSWrapper : + public NSWrapper +{ + public: + //! Construct the LeafSizeNSWrapper by delegating to the NSWrapper + //! constructor. + LeafSizeNSWrapper(const NeighborSearchMode searchMode, + const double epsilon) : + NSWrapper(searchMode, epsilon) + { + // Nothing to do. + } + + //! Delete the LeafSizeNSWrapper. + virtual ~LeafSizeNSWrapper() { } + + //! Return a copy of the LeafSizeNSWrapper. + virtual LeafSizeNSWrapper* Clone() const + { + return new LeafSizeNSWrapper(*this); + } + + //! Train a model with the given parameters. This overload uses leafSize but + //! ignores the other parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize, + const double /* tau */, + const double /* rho */); + + //! Perform bichromatic search (e.g. search with a separate query set). This + //! overload uses the leaf size, but ignores the other parameters. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double /* rho */); + + //! Serialize the NeighborSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ns)); + } + + protected: + using NSWrapper::ns; +}; + +/** + * The SpillNSWrapper class wraps the NeighborSearch class when the spill tree + * is used. */ template -class BiSearchVisitor : public boost::static_visitor -{ - private: - //! The query set for the bichromatic search. - const arma::mat& querySet; - //! The number of neighbors to search for. - const size_t k; - //! The result matrix for neighbors. - arma::Mat& neighbors; - //! The result matrix for distances. - arma::mat& distances; - //! The number of points in a leaf (for BinarySpaceTrees). - const size_t leafSize; - //! Overlapping size (for spill trees). - const double tau; - //! Balance threshold (for spill trees). - const double rho; - - //! Bichromatic neighbor search on the given NSType considering the leafSize. - template - void SearchLeaf(NSType* ns) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using NSTypeT = NSType; - - //! Default Bichromatic neighbor search on the given NSType instance. - template class TreeType> - void operator()(NSTypeT* ns) const; - - //! Bichromatic neighbor search on the given NSType specialized for KDTrees. - void operator()(NSTypeT* ns) const; - - //! Bichromatic neighbor search on the given NSType specialized for BallTrees. - void operator()(NSTypeT* ns) const; - - //! Bichromatic neighbor search specialized for SPTrees. - void operator()(SpillKNN* ns) const; - - //! Bichromatic neighbor search specialized for octrees. - void operator()(NSTypeT* ns) const; - - //! Construct the BiSearchVisitor. - BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize, - const double tau, - const double rho); -}; - -/** - * TrainVisitor sets the reference set to a new reference set on the given - * NSType. We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, a reference tree with proper - * leafSize is built from the referenceSet. - */ -template -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set to use for training. - arma::mat&& referenceSet; - //! The leaf size, used only by BinarySpaceTree. - size_t leafSize; - //! Overlapping size (for spill trees). - const double tau; - //! Balance threshold (for spill trees). - const double rho; - - //! Train on the given NSType considering the leafSize. - template - void TrainLeaf(NSType* ns) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using NSTypeT = NSType; - - //! Default Train on the given NSType instance. - template class TreeType> - void operator()(NSTypeT* ns) const; - - //! Train on the given NSType specialized for KDTrees. - void operator()(NSTypeT* ns) const; - - //! Train on the given NSType specialized for BallTrees. - void operator()(NSTypeT* ns) const; - - //! Train specialized for SPTrees. - void operator()(SpillKNN* ns) const; - - //! Train specialized for octrees. - void operator()(NSTypeT* ns) const; - - //! Construct the TrainVisitor object with the given reference set, leafSize - //! for BinarySpaceTrees, and tau and rho for spill trees. - TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize, - const double tau, - const double rho); -}; - -/** - * SearchModeVisitor exposes the SearchMode() method of the given NSType. - */ -class SearchModeVisitor : public boost::static_visitor +class SpillNSWrapper : + public NSWrapper< + SortPolicy, + tree::SPTree, + tree::SPTree, + arma::mat>::template DefeatistDualTreeTraverser, + tree::SPTree, + arma::mat>::template DefeatistSingleTreeTraverser> { public: - //! Return the search mode. - template - NeighborSearchMode& operator()(NSType* ns) const; -}; + //! Construct the SpillNSWrapper. + SpillNSWrapper(const NeighborSearchMode searchMode, + const double epsilon) : + NSWrapper< + SortPolicy, + tree::SPTree, + tree::SPTree, + arma::mat>::template DefeatistDualTreeTraverser, + tree::SPTree, + arma::mat>::template DefeatistSingleTreeTraverser>( + searchMode, epsilon) + { + // Nothing to do. + } -/** - * EpsilonVisitor exposes the Epsilon method of the given NSType. - */ -class EpsilonVisitor : public boost::static_visitor -{ - public: - //! Return epsilon, the approximation parameter. - template - double& operator()(NSType *ns) const; -}; + //! Destruct the SpillNSWrapper. + virtual ~SpillNSWrapper() { } -/** - * ReferenceSetVisitor exposes the referenceSet of the given NSType. - */ -class ReferenceSetVisitor : public boost::static_visitor -{ - public: - //! Return the reference set. - template - const arma::mat& operator()(NSType *ns) const; -}; + //! Return a copy of the SpillNSWrapper. + virtual SpillNSWrapper* Clone() const { return new SpillNSWrapper(*this); } -/** - * DeleteVisitor deletes the given NSType instance. - */ -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete the NSType object. - template - void operator()(NSType *ns) const; + //! Train the model using the given parameters. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize, + const double tau, + const double rho); + + //! Perform bichromatic search (i.e. search with a different query set) using + //! the given parameters. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double rho); + + //! Serialize the NeighborSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ns)); + } + + protected: + using NSWrapper< + SortPolicy, + tree::SPTree, + tree::SPTree, + arma::mat>::template DefeatistDualTreeTraverser, + tree::SPTree, + arma::mat>::template DefeatistSingleTreeTraverser>::ns; }; /** @@ -272,39 +357,20 @@ class NSModel //! Tree type considered for neighbor search. TreeTypes treeType; - //! For tree types that accept the maxLeafSize parameter. - size_t leafSize; - - //! Overlapping size (for spill trees). - double tau; - //! Balance threshold (for spill trees). - double rho; - //! If true, random projections are used. bool randomBasis; //! This is the random projection matrix; only used if randomBasis is true. arma::mat q; + size_t leafSize; + double tau; + double rho; + /** - * nSearch holds an instance of the NeigborSearch class for the current + * nSearch holds an instance of the NeighborSearch class for the current * treeType. It is initialized every time BuildModel is executed. - * We access to the contained value through the visitor classes defined above. */ - boost::variant*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - NSType*, - SpillKNN*, - NSType*, - NSType*> nSearch; + NSWrapperBase* nSearch; public: /** @@ -359,22 +425,22 @@ class NSModel NeighborSearchMode SearchMode() const; NeighborSearchMode& SearchMode(); - //! Expose Epsilon. - double Epsilon() const; - double& Epsilon(); - - //! Expose leafSize. + //! Expose LeafSize. size_t LeafSize() const { return leafSize; } size_t& LeafSize() { return leafSize; } - //! Expose tau. + //! Expose Tau. double Tau() const { return tau; } double& Tau() { return tau; } - //! Expose rho. + //! Expose Rho. double Rho() const { return rho; } double& Rho() { return rho; } + //! Expose Epsilon. + double Epsilon() const; + double& Epsilon(); + //! Expose treeType. TreeTypes TreeType() const { return treeType; } TreeTypes& TreeType() { return treeType; } @@ -383,9 +449,12 @@ class NSModel bool RandomBasis() const { return randomBasis; } bool& RandomBasis() { return randomBasis; } + //! Initialize the model type. (This does not perform any training.) + void InitializeModel(const NeighborSearchMode searchMode, + const double epsilon); + //! Build the reference tree. void BuildModel(arma::mat&& referenceSet, - const size_t leafSize, const NeighborSearchMode searchMode, const double epsilon = 0); diff --git a/src/mlpack/methods/neighbor_search/ns_model_impl.hpp b/src/mlpack/methods/neighbor_search/ns_model_impl.hpp index 8c90aa9ec8..319fb652af 100644 --- a/src/mlpack/methods/neighbor_search/ns_model_impl.hpp +++ b/src/mlpack/methods/neighbor_search/ns_model_impl.hpp @@ -21,107 +21,121 @@ namespace mlpack { namespace neighbor { -//! Monochromatic neighbor search on the given NSType instance. -template -void MonoSearchVisitor::operator()(NSType *ns) const -{ - if (ns) - return ns->Search(k, neighbors, distances); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Save parameters for bichromatic neighbor search. -template -BiSearchVisitor::BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize, - const double tau, - const double rho) : - querySet(querySet), - k(k), - neighbors(neighbors), - distances(distances), - leafSize(leafSize), - tau(tau), - rho(rho) -{} - -//! Default Bichromatic neighbor search on the given NSType instance. -template -template class TreeType> -void BiSearchVisitor::operator()(NSTypeT* ns) const + typename TreeMatType> class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void NSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Train(arma::mat&& referenceSet, + const size_t /* leafSize */, + const double /* tau */, + const double /* rho */) { - if (ns) - return ns->Search(querySet, k, neighbors, distances); - throw std::runtime_error("no neighbor search model initialized"); + ns.Train(std::move(referenceSet)); } -//! Bichromatic neighbor search on the given NSType specialized for KDTrees. -template -void BiSearchVisitor::operator()(NSTypeT* ns) const +//! Perform bichromatic neighbor search (i.e. search with a separate query +//! set). For NSWrapper, we ignore the extra parameters. +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void NSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */, + const double /* rho */) { - if (ns) - return SearchLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); + ns.Search(std::move(querySet), k, neighbors, distances); } -//! Bichromatic neighbor search on the given NSType specialized for BallTrees. -template -void BiSearchVisitor::operator()(NSTypeT* ns) const +//! Perform monochromatic neighbor search (i.e. use the reference set as the +//! query set). +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void NSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) { - if (ns) - return SearchLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); + ns.Search(k, neighbors, distances); } -//! Bichromatic neighbor search specialized for SPTrees. -template -void BiSearchVisitor::operator()(SpillKNN* ns) const +//! Train a model with the given parameters. This overload uses leafSize but +//! ignores the other parameters. +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void LeafSizeNSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Train(arma::mat&& referenceSet, + const size_t leafSize, + const double /* tau */, + const double /* rho */) { - if (ns) + if (ns.SearchMode() == NAIVE_MODE) { - if (ns->SearchMode() == DUAL_TREE_MODE) - { - // For Dual Tree Search on SpillTrees, the queryTree must be built with - // non overlapping (tau = 0). - typename SpillKNN::Tree queryTree(std::move(querySet), 0 /* tau*/, - leafSize, rho); - ns->Search(queryTree, k, neighbors, distances); - } - else - ns->Search(querySet, k, neighbors, distances); + ns.Train(std::move(referenceSet)); } else - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Bichromatic neighbor search specialized for octrees. -template -void BiSearchVisitor::operator()(NSTypeT* ns) const -{ - if (ns) - return SearchLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Bichromatic neighbor search on the given NSType considering the leafSize. -template -template -void BiSearchVisitor::SearchLeaf(NSType* ns) const -{ - if (ns->SearchMode() == DUAL_TREE_MODE) { + // Build the tree with the specified leaf size. + std::vector oldFromNewReferences; + typename decltype(ns)::Tree referenceTree(std::move(referenceSet), + oldFromNewReferences, leafSize); + ns.Train(std::move(referenceTree)); + ns.oldFromNewReferences = std::move(oldFromNewReferences); + } +} + +//! Perform bichromatic search (e.g. search with a separate query set). This +//! overload uses the leaf size, but ignores the other parameters. +template class TreeType, + template class DualTreeTraversalType, + template class SingleTreeTraversalType> +void LeafSizeNSWrapper< + SortPolicy, TreeType, DualTreeTraversalType, SingleTreeTraversalType +>::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double /* rho */) +{ + if (ns.SearchMode() == DUAL_TREE_MODE) + { + // We actually have to do the mapping of query points ourselves, since the + // NeighborSearch class does not provide a way for us to specify the leaf + // size when building the query tree. (Therefore we must also build the + // query tree manually.) std::vector oldFromNewQueries; - typename NSType::Tree queryTree(std::move(querySet), oldFromNewQueries, - leafSize); + typename decltype(ns)::Tree queryTree(std::move(querySet), + oldFromNewQueries, leafSize); arma::Mat neighborsOut; arma::mat distancesOut; - ns->Search(queryTree, k, neighborsOut, distancesOut); + ns.Search(queryTree, k, neighborsOut, distancesOut); // Unmap the query points. distances.set_size(distancesOut.n_rows, distancesOut.n_cols); @@ -133,131 +147,47 @@ void BiSearchVisitor::SearchLeaf(NSType* ns) const } } else - ns->Search(querySet, k, neighbors, distances); + { + ns.Search(querySet, k, neighbors, distances); + } } -//! Save parameters for Train. +//! Train the model using the given parameters. template -TrainVisitor::TrainVisitor(arma::mat&& referenceSet, +void SpillNSWrapper::Train(arma::mat&& referenceSet, const size_t leafSize, const double tau, - const double rho) : - referenceSet(std::move(referenceSet)), - leafSize(leafSize), - tau(tau), - rho(rho) -{} - -//! Default Train on the given NSType instance. -template -template class TreeType> -void TrainVisitor::operator()(NSTypeT* ns) const + const double rho) { - if (ns) - return ns->Train(std::move(referenceSet)); - throw std::runtime_error("no neighbor search model initialized"); + typename decltype(ns)::Tree tree(std::move(referenceSet), tau, leafSize, + rho); + ns.Train(std::move(tree)); } -//! Train on the given NSType specialized for KDTrees. +//! Perform bichromatic search (i.e. search with a different query set) using +//! the given parameters. template -void TrainVisitor::operator()(NSTypeT* ns) const +void SpillNSWrapper::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize, + const double rho) { - if (ns) - return TrainLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train on the given NSType specialized for BallTrees. -template -void TrainVisitor::operator()(NSTypeT* ns) const -{ - if (ns) - return TrainLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train specialized for SPTrees. -template -void TrainVisitor::operator()(SpillKNN* ns) const -{ - if (ns) + if (ns.SearchMode() == DUAL_TREE_MODE) { - if (ns->SearchMode() == NAIVE_MODE) - ns->Train(std::move(referenceSet)); - else - { - typename SpillKNN::Tree tree(std::move(referenceSet), tau, leafSize, rho); - ns->Train(std::move(tree)); - } + // For Dual Tree Search on SpillTrees, the queryTree must be built with + // non overlapping (tau = 0). + typename decltype(ns)::Tree queryTree(std::move(querySet), 0 /* tau */, + leafSize, rho); + ns.Search(queryTree, k, neighbors, distances); } - else - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train specialized for Octrees. -template -void TrainVisitor::operator()(NSTypeT* ns) const -{ - if (ns) - return TrainLeaf(ns); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Train on the given NSType considering the leafSize. -template -template -void TrainVisitor::TrainLeaf(NSType* ns) const -{ - if (ns->SearchMode() == NAIVE_MODE) - ns->Train(std::move(referenceSet)); else { - std::vector oldFromNewReferences; - typename NSType::Tree referenceTree(std::move(referenceSet), - oldFromNewReferences, leafSize); - ns->Train(std::move(referenceTree)); - // Set the mappings. - ns->oldFromNewReferences = std::move(oldFromNewReferences); + ns.Search(querySet, k, neighbors, distances); } } -//! Return the search mode. -template -NeighborSearchMode& SearchModeVisitor::operator()(NSType* ns) const -{ - if (ns) - return ns->SearchMode(); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Expose the Epsilon method of the given NSType. -template -double& EpsilonVisitor::operator()(NSType* ns) const -{ - if (ns) - return ns->Epsilon(); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Expose the referenceSet of the given NSType. -template -const arma::mat& ReferenceSetVisitor::operator()(NSType* ns) const -{ - if (ns) - return ns->ReferenceSet(); - throw std::runtime_error("no neighbor search model initialized"); -} - -//! Clean memory, if necessary. -template -void DeleteVisitor::operator()(NSType* ns) const -{ - if (ns) - delete ns; -} - /** * Initialize the NSModel with the given type and whether or not a random * basis should be used. @@ -265,10 +195,11 @@ void DeleteVisitor::operator()(NSType* ns) const template NSModel::NSModel(TreeTypes treeType, bool randomBasis) : treeType(treeType), + randomBasis(randomBasis), leafSize(20), - tau(0), + tau(0.0), rho(0.7), - randomBasis(randomBasis) + nSearch(NULL) { // Nothing to do. } @@ -276,12 +207,12 @@ NSModel::NSModel(TreeTypes treeType, bool randomBasis) : template NSModel::NSModel(const NSModel& other) : treeType(other.treeType), + randomBasis(other.randomBasis), + q(other.q), leafSize(other.leafSize), tau(other.tau), rho(other.rho), - randomBasis(other.randomBasis), - q(other.q), - nSearch(other.nSearch) + nSearch(other.nSearch->Clone()) { // Nothing to do. } @@ -289,34 +220,37 @@ NSModel::NSModel(const NSModel& other) : template NSModel::NSModel(NSModel&& other) : treeType(other.treeType), + randomBasis(other.randomBasis), + q(std::move(other.q)), leafSize(other.leafSize), tau(other.tau), rho(other.rho), - randomBasis(other.randomBasis), - q(std::move(other.q)), nSearch(other.nSearch) { // Reset parameters of the other model. other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.tau = 0; - other.rho = 0.7; other.randomBasis = false; - other.nSearch = decltype(other.nSearch)(); + other.leafSize = 20; + other.tau = 0.0; + other.rho = 0.7; + other.nSearch = NULL; } template NSModel& NSModel::operator=(const NSModel& other) { - boost::apply_visitor(DeleteVisitor(), nSearch); + if (this != &other) + { + delete nSearch; - treeType = other.treeType; - leafSize = other.leafSize; - tau = other.tau; - rho = other.rho; - randomBasis = other.randomBasis; - q = other.q; - nSearch = other.nSearch; + treeType = other.treeType; + randomBasis = other.randomBasis; + q = other.q; + leafSize = other.leafSize; + tau = other.tau; + rho = other.rho; + nSearch = other.nSearch->Clone(); + } return *this; } @@ -324,24 +258,26 @@ NSModel& NSModel::operator=(const NSModel& other) template NSModel& NSModel::operator=(NSModel&& other) { - boost::apply_visitor(DeleteVisitor(), nSearch); + if (this != &other) + { + delete nSearch; - treeType = other.treeType; - leafSize = other.leafSize; - tau = other.tau; - rho = other.rho; - randomBasis = other.randomBasis; - q = std::move(other.q); - // Copy the pointer and type. - nSearch = other.nSearch; + treeType = other.treeType; + randomBasis = other.randomBasis; + q = std::move(other.q); + leafSize = other.leafSize; + tau = other.tau; + rho = other.rho; + nSearch = other.nSearch; - // Reset parameters of the other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.tau = 0; - other.rho = 0.7; - other.randomBasis = false; - other.nSearch = decltype(other.nSearch)(); + // Reset parameters of the other model. + other.treeType = TreeTypes::KD_TREE; + other.randomBasis = false; + other.leafSize = 20; + other.tau = 0.0; + other.rho = 0.7; + other.nSearch = NULL; + } return *this; } @@ -350,7 +286,7 @@ NSModel& NSModel::operator=(NSModel&& other) template NSModel::~NSModel() { - boost::apply_visitor(DeleteVisitor(), nSearch); + delete nSearch; } //! Serialize the kNN model. @@ -359,60 +295,236 @@ template void NSModel::serialize(Archive& ar, const uint32_t /* version */) { ar(CEREAL_NVP(treeType)); + ar(CEREAL_NVP(randomBasis)); + ar(CEREAL_NVP(q)); ar(CEREAL_NVP(leafSize)); ar(CEREAL_NVP(tau)); ar(CEREAL_NVP(rho)); - ar(CEREAL_NVP(randomBasis)); - ar(CEREAL_NVP(q)); // This should never happen, but just in case, be clean with memory. if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), nSearch); + InitializeModel(DUAL_TREE_MODE, 0.0); // Values will be overwritten. - ar(CEREAL_VARIANT_POINTER(nSearch)); + // Avoid polymorphic serialization by explicitly serializing the correct type. + switch (treeType) + { + case KD_TREE: + { + LeafSizeNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case COVER_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_STAR_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case BALL_TREE: + { + LeafSizeNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case X_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case HILBERT_R_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_PLUS_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case R_PLUS_PLUS_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case SPILL_TREE: + { + SpillNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case VP_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case RP_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case MAX_RP_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case UB_TREE: + { + NSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case OCTREE: + { + LeafSizeNSWrapper& typedSearch = + dynamic_cast&>(*nSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + } } //! Expose the dataset. template const arma::mat& NSModel::Dataset() const { - return boost::apply_visitor(ReferenceSetVisitor(), nSearch); + return nSearch->Dataset(); } //! Access the search mode. template NeighborSearchMode NSModel::SearchMode() const { - return boost::apply_visitor(SearchModeVisitor(), nSearch); + return nSearch->SearchMode(); } //! Modify the search mode. template NeighborSearchMode& NSModel::SearchMode() { - return boost::apply_visitor(SearchModeVisitor(), nSearch); + return nSearch->SearchMode(); } template double NSModel::Epsilon() const { - return boost::apply_visitor(EpsilonVisitor(), nSearch); + return nSearch->Epsilon(); } template double& NSModel::Epsilon() { - return boost::apply_visitor(EpsilonVisitor(), nSearch); + return nSearch->Epsilon(); +} + +//! Initialize a model given the tree type. (No training happens here.) +template +void NSModel::InitializeModel(const NeighborSearchMode searchMode, + const double epsilon) +{ + // Clear existing memory. + if (nSearch) + delete nSearch; + + switch (treeType) + { + case KD_TREE: + nSearch = new LeafSizeNSWrapper(searchMode, + epsilon); + break; + case COVER_TREE: + nSearch = new NSWrapper(searchMode, + epsilon); + break; + case R_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case R_STAR_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case BALL_TREE: + nSearch = new LeafSizeNSWrapper(searchMode, + epsilon); + break; + case X_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case HILBERT_R_TREE: + nSearch = new NSWrapper(searchMode, + epsilon); + break; + case R_PLUS_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case R_PLUS_PLUS_TREE: + nSearch = new NSWrapper(searchMode, + epsilon); + break; + case VP_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case RP_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case MAX_RP_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case SPILL_TREE: + nSearch = new SpillNSWrapper(searchMode, epsilon); + break; + case UB_TREE: + nSearch = new NSWrapper(searchMode, epsilon); + break; + case OCTREE: + nSearch = new LeafSizeNSWrapper(searchMode, + epsilon); + break; + } + } //! Build the reference tree. template void NSModel::BuildModel(arma::mat&& referenceSet, - const size_t leafSize, const NeighborSearchMode searchMode, const double epsilon) { - this->leafSize = leafSize; // Initialize random basis if necessary. if (randomBasis) { @@ -445,9 +557,6 @@ void NSModel::BuildModel(arma::mat&& referenceSet, } } - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), nSearch); - // Do we need to modify the reference set? if (randomBasis) referenceSet = q * referenceSet; @@ -458,59 +567,8 @@ void NSModel::BuildModel(arma::mat&& referenceSet, Log::Info << "Building reference tree..." << std::endl; } - switch (treeType) - { - case KD_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case COVER_TREE: - nSearch = new NSType(searchMode, - epsilon); - break; - case R_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case R_STAR_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case BALL_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case X_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case HILBERT_R_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case R_PLUS_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case R_PLUS_PLUS_TREE: - nSearch = new NSType(searchMode, - epsilon); - break; - case VP_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case RP_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case MAX_RP_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case SPILL_TREE: - nSearch = new SpillKNN(searchMode, epsilon); - break; - case UB_TREE: - nSearch = new NSType(searchMode, epsilon); - break; - case OCTREE: - nSearch = new NSType(searchMode, epsilon); - break; - } - - TrainVisitor tn(std::move(referenceSet), leafSize, tau, rho); - boost::apply_visitor(tn, nSearch); + InitializeModel(searchMode, epsilon); + nSearch->Train(std::move(referenceSet), leafSize, tau, rho); if (searchMode != NAIVE_MODE) { @@ -549,9 +607,7 @@ void NSModel::Search(arma::mat&& querySet, break; } - BiSearchVisitor search(querySet, k, neighbors, distances, - leafSize, tau, rho); - boost::apply_visitor(search, nSearch); + nSearch->Search(std::move(querySet), k, neighbors, distances, leafSize, rho); } //! Perform neighbor search. @@ -583,8 +639,7 @@ void NSModel::Search(const size_t k, Log::Info << "Maximum of " << Epsilon() * 100 << "% relative error." << std::endl; - MonoSearchVisitor search(k, neighbors, distances); - boost::apply_visitor(search, nSearch); + nSearch->Search(k, neighbors, distances); } //! Get the name of the tree type. diff --git a/src/mlpack/methods/pca/pca.hpp b/src/mlpack/methods/pca/pca.hpp index 594cb47344..feae5322a5 100644 --- a/src/mlpack/methods/pca/pca.hpp +++ b/src/mlpack/methods/pca/pca.hpp @@ -68,6 +68,14 @@ class PCA void Apply(const arma::mat& data, arma::mat& transformedData, arma::vec& eigVal); + /** + * Apply Principal Component Analysis to the provided data set. It is safe + * to pass the same matrix reference for both data and transformedData. + * @param data Data matrix. + * @param transformedData Matrix to store results of PCA in. + */ + void Apply(const arma::mat& data, + arma::mat& transformedData); /** * Use PCA for dimensionality reduction on the given dataset. This will save diff --git a/src/mlpack/methods/pca/pca_impl.hpp b/src/mlpack/methods/pca/pca_impl.hpp index 360586360a..f469933c14 100644 --- a/src/mlpack/methods/pca/pca_impl.hpp +++ b/src/mlpack/methods/pca/pca_impl.hpp @@ -74,6 +74,21 @@ void PCA::Apply(const arma::mat& data, arma::mat eigvec; Apply(data, transformedData, eigVal, eigvec); } + +/** + * Apply Principal Component Analysis to the provided data set. + * + * @param data - Data matrix. + * @param transformedData Data with PCA applied. + */ +template +void PCA::Apply(const arma::mat& data, + arma::mat& transformedData) +{ + arma::mat eigvec; + arma::vec eigVal; + Apply(data, transformedData, eigVal, eigvec); +} /** * Use PCA for dimensionality reduction on the given dataset. This will save diff --git a/src/mlpack/methods/preprocess/preprocess_split_main.cpp b/src/mlpack/methods/preprocess/preprocess_split_main.cpp index 357450f516..8b935e16db 100644 --- a/src/mlpack/methods/preprocess/preprocess_split_main.cpp +++ b/src/mlpack/methods/preprocess/preprocess_split_main.cpp @@ -35,7 +35,7 @@ BINDING_LONG_DESC( PRINT_PARAM_STRING("training") + " and " + PRINT_PARAM_STRING("test") + " output parameters." "\n\n" - "Optionally, labels can be also be split along with the data by specifying " + "Optionally, labels can also be split along with the data by specifying " "the " + PRINT_PARAM_STRING("input_labels") + " parameter. Splitting " "labels works the same way as splitting the data. The output training and " "test labels may be saved with the " + @@ -96,7 +96,7 @@ PARAM_DOUBLE_IN("test_ratio", "Ratio of test set; if not set," "the ratio defaults to 0.2", "r", 0.2); PARAM_INT_IN("seed", "Random seed (0 for std::time(NULL)).", "s", 0); -PARAM_FLAG("no_shuffle", "Avoid shuffling and splitting the data.", "S"); +PARAM_FLAG("no_shuffle", "Avoid shuffling the data before splitting.", "S"); PARAM_FLAG("stratify_data", "Stratify the data according to labels", "z") using namespace mlpack; @@ -141,12 +141,6 @@ static void mlpackMain() [](double x) { return x >= 0.0 && x <= 1.0; }, true, "test ratio must be between 0.0 and 1.0"); - if (!IO::HasParam("test_ratio")) // If test_ratio is not set, warn the user. - { - Log::Warn << "You did not specify " << PRINT_PARAM_STRING("test_ratio") - << ", so it will be automatically set to 0.2." << endl; - } - // Load the data. arma::mat& data = IO::GetParam("input"); diff --git a/src/mlpack/methods/preprocess/scaling_model.hpp b/src/mlpack/methods/preprocess/scaling_model.hpp index 87693cc796..a5d7082658 100644 --- a/src/mlpack/methods/preprocess/scaling_model.hpp +++ b/src/mlpack/methods/preprocess/scaling_model.hpp @@ -65,6 +65,9 @@ class ScalingModel //! Copy assignment operator. ScalingModel& operator=(const ScalingModel& other); + //! Move assignment operator. + ScalingModel& operator=(ScalingModel&& other); + //! Clean up memory. ~ScalingModel(); diff --git a/src/mlpack/methods/preprocess/scaling_model_impl.hpp b/src/mlpack/methods/preprocess/scaling_model_impl.hpp index dd918de9d9..6da36e6f49 100644 --- a/src/mlpack/methods/preprocess/scaling_model_impl.hpp +++ b/src/mlpack/methods/preprocess/scaling_model_impl.hpp @@ -84,7 +84,7 @@ ScalingModel::ScalingModel(ScalingModel&& other) : } //! Copy assignment operator. -ScalingModel& ScalingModel::operator= (const ScalingModel& other) +ScalingModel& ScalingModel::operator=(const ScalingModel& other) { if (this == &other) { @@ -123,6 +123,36 @@ ScalingModel& ScalingModel::operator= (const ScalingModel& other) return *this; } +//! Move assignment operator. +ScalingModel& ScalingModel::operator=(ScalingModel&& other) +{ + if (this != &other) + { + scalerType = other.scalerType; + minmaxscale = other.minmaxscale; + maxabsscale = other.maxabsscale; + meanscale = other.meanscale; + standardscale = other.standardscale; + pcascale = other.pcascale; + zcascale = other.zcascale; + minValue = other.minValue; + maxValue = other.maxValue; + epsilon = other.epsilon; + + other.scalerType = 0; + other.minmaxscale = nullptr; + other.maxabsscale = nullptr; + other.meanscale = nullptr; + other.standardscale = nullptr; + other.pcascale = nullptr; + other.zcascale = nullptr; + other.minValue = 0; + other.maxValue = 1; + other.epsilon = 0.00005; + } + return *this; +} + ScalingModel::~ScalingModel() { delete minmaxscale; diff --git a/src/mlpack/methods/range_search/CMakeLists.txt b/src/mlpack/methods/range_search/CMakeLists.txt index 0a1912b6b4..8a0ff5925f 100644 --- a/src/mlpack/methods/range_search/CMakeLists.txt +++ b/src/mlpack/methods/range_search/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES range_search_stat.hpp rs_model.hpp rs_model_impl.hpp + rs_model.cpp ) # Add directory name to sources. diff --git a/src/mlpack/methods/range_search/range_search.hpp b/src/mlpack/methods/range_search/range_search.hpp index 06575005ac..a8363c2c9e 100644 --- a/src/mlpack/methods/range_search/range_search.hpp +++ b/src/mlpack/methods/range_search/range_search.hpp @@ -22,7 +22,10 @@ namespace mlpack { namespace range /** Range-search routines. */ { //! Forward declaration. -class TrainVisitor; +template class TreeType> +class LeafSizeRSWrapper; /** * The RangeSearch class is a template class for performing range searches. It @@ -122,12 +125,18 @@ class RangeSearch RangeSearch(RangeSearch&& other); /** - * Copy the given RangeSearch model. - * Use std::move to pass in the model if the old copy is no longer needed. - * + * Deep copy the given RangeSearch model. + * * @param other RangeSearch model to copy. */ - RangeSearch& operator=(RangeSearch other); + RangeSearch& operator=(const RangeSearch& other); + + /** + * Move the given RangeSearch model. + * + * @param other RangeSearch model to move. + */ + RangeSearch& operator=(RangeSearch&& other); /** * Destroy the RangeSearch object. If trees were created, they will be @@ -310,7 +319,7 @@ class RangeSearch size_t scores; //! For access to mappings when building models. - friend class TrainVisitor; + friend class LeafSizeRSWrapper; }; } // namespace range diff --git a/src/mlpack/methods/range_search/range_search_impl.hpp b/src/mlpack/methods/range_search/range_search_impl.hpp index 298aae995e..03f90b3057 100644 --- a/src/mlpack/methods/range_search/range_search_impl.hpp +++ b/src/mlpack/methods/range_search/range_search_impl.hpp @@ -169,25 +169,61 @@ template class TreeType> RangeSearch& -RangeSearch::operator=(RangeSearch other) +RangeSearch::operator=(const RangeSearch& other) { - // Clean memory first. - if (treeOwner) - delete referenceTree; - if (naive) - delete referenceSet; + if (this != &other) + { + oldFromNewReferences = other.oldFromNewReferences; + referenceTree = other.referenceTree ? new Tree(*other.referenceTree) : nullptr; + referenceSet = other.referenceTree ? &referenceTree->Dataset() : + new MatType(*other.referenceSet); + treeOwner = other.referenceTree; + naive = other.naive; + singleMode = other.singleMode; + metric = other.metric; + baseCases = other.baseCases; + scores = other.scores; + } + return *this; +} - // Move the other model. - oldFromNewReferences = std::move(other.oldFromNewReferences); - referenceTree = other.referenceTree; - referenceSet = other.referenceSet; - treeOwner = other.treeOwner; - naive = other.naive; - singleMode = other.singleMode; - metric = std::move(other.metric); - baseCases = other.baseCases; - scores = other.scores; +template class TreeType> +RangeSearch& +RangeSearch::operator=(RangeSearch&& other) +{ + if (this != &other) + { + // Clean memory first. + if (treeOwner) + delete referenceTree; + if (naive) + delete referenceSet; + // Move the other model. + oldFromNewReferences = std::move(other.oldFromNewReferences); + referenceTree = other.referenceTree; + referenceSet = other.referenceSet; + treeOwner = other.treeOwner; + naive = other.naive; + singleMode = other.singleMode; + metric = std::move(other.metric); + baseCases = other.baseCases; + scores = other.scores; + + // Clear other object. + other.referenceTree = nullptr; + other.referenceSet = nullptr; + other.treeOwner = false; + other.naive = false; + other.singleMode = false; + other.baseCases = 0; + other.scores = 0; + + } return *this; } @@ -254,12 +290,15 @@ void RangeSearch::Train( throw std::invalid_argument("cannot train on given reference tree when " "naive search (without trees) is desired"); + // Can only train when passed argument `referenceTree` is not nullptr. if (treeOwner && referenceTree) + { delete this->referenceTree; - this->referenceTree = referenceTree; - this->referenceSet = &referenceTree->Dataset(); - treeOwner = false; + this->referenceTree = referenceTree; + this->referenceSet = &referenceTree->Dataset(); + treeOwner = false; + } } template + +namespace mlpack { +namespace range { + +/** + * Initialize the RSModel with the given tree type and whether or not a random + * basis should be used. + */ +RSModel::RSModel(TreeTypes treeType, bool randomBasis) : + treeType(treeType), + leafSize(0), + randomBasis(randomBasis), + rSearch(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +RSModel::RSModel(const RSModel& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(other.q), + rSearch(other.rSearch->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +RSModel::RSModel(RSModel&& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(std::move(other.q)), + rSearch(std::move(other.rSearch)) +{ + // Reset other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 0; + other.randomBasis = false; +} + +// Copy operator. +RSModel& RSModel::operator=(const RSModel& other) +{ + if (this != &other) + { + delete rSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = other.q; + rSearch = other.rSearch->Clone(); + } + + return *this; +} + +// Move operator. +RSModel& RSModel::operator=(RSModel&& other) +{ + if (this != &other) + { + delete rSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = std::move(other.q); + rSearch = std::move(other.rSearch); + + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 0; + other.randomBasis = false; + } + + return *this; +} + +// Clean memory, if necessary. +RSModel::~RSModel() +{ + delete rSearch; +} + +void RSModel::InitializeModel(const bool naive, const bool singleMode) +{ + // Clean memory, if necessary. + delete rSearch; + + switch (treeType) + { + case KD_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case COVER_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_STAR_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case BALL_TREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + + case X_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case HILBERT_R_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_PLUS_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case R_PLUS_PLUS_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case VP_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case RP_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case MAX_RP_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case UB_TREE: + rSearch = new RSWrapper(naive, singleMode); + break; + + case OCTREE: + rSearch = new LeafSizeRSWrapper(naive, singleMode); + break; + } +} + +void RSModel::BuildModel(arma::mat&& referenceSet, + const size_t leafSize, + const bool naive, + const bool singleMode) +{ + // Initialize random basis if necessary. + if (randomBasis) + { + Log::Info << "Creating random basis..." << std::endl; + math::RandomBasis(q, referenceSet.n_rows); + } + + this->leafSize = leafSize; + + // Do we need to modify the reference set? + if (randomBasis) + referenceSet = q * referenceSet; + + if (!naive) + { + Timer::Start("tree_building"); + Log::Info << "Building reference tree..." << std::endl; + } + + InitializeModel(naive, singleMode); + + rSearch->Train(std::move(referenceSet), leafSize); + + if (!naive) + { + Timer::Stop("tree_building"); + Log::Info << "Tree built." << std::endl; + } +} + +// Perform range search. +void RSModel::Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) +{ + // We may need to map the query set randomly. + if (randomBasis) + querySet = q * querySet; + + Log::Info << "Search for points in the range [" << range.Lo() << ", " + << range.Hi() << "] with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; + else if (!Naive()) + Log::Info << "single-tree " << TreeName() << " search..." << std::endl; + else + Log::Info << "brute-force (naive) search..." << std::endl; + + rSearch->Search(std::move(querySet), range, neighbors, distances, leafSize); +} + +// Perform range search (monochromatic case). +void RSModel::Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) +{ + Log::Info << "Search for points in the range [" << range.Lo() << ", " + << range.Hi() << "] with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; + else if (!Naive()) + Log::Info << "single-tree " << TreeName() << " search..." << std::endl; + else + Log::Info << "brute-force (naive) search..." << std::endl; + + rSearch->Search(range, neighbors, distances); +} + +// Get the name of the tree type. +std::string RSModel::TreeName() const +{ + switch (treeType) + { + case KD_TREE: + return "kd-tree"; + case COVER_TREE: + return "cover tree"; + case R_TREE: + return "R tree"; + case R_STAR_TREE: + return "R* tree"; + case BALL_TREE: + return "ball tree"; + case X_TREE: + return "X tree"; + case HILBERT_R_TREE: + return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; + case VP_TREE: + return "vantage point tree"; + case RP_TREE: + return "random projection tree (mean split)"; + case MAX_RP_TREE: + return "random projection tree (max split)"; + case UB_TREE: + return "UB tree"; + case OCTREE: + return "octree"; + default: + return "unknown tree"; + } +} + +// Clean memory. +void RSModel::CleanMemory() +{ + delete rSearch; +} + +} // namespace range +} // namespace mlpack diff --git a/src/mlpack/methods/range_search/rs_model.hpp b/src/mlpack/methods/range_search/rs_model.hpp index ab71f20a21..430274cd9b 100644 --- a/src/mlpack/methods/range_search/rs_model.hpp +++ b/src/mlpack/methods/range_search/rs_model.hpp @@ -19,7 +19,6 @@ #include #include #include -#include #include "range_search.hpp" @@ -27,189 +26,183 @@ namespace mlpack { namespace range { /** - * Alias template for Range Search. + * RSWrapperBase is a base wrapper class for holding all RangeSearch types + * supported by RSModel. All RangeSearch type wrappers inherit from this class, + * allowing a simple interface via inheritance for all the different types we + * want to support. + */ +class RSWrapperBase +{ + public: + //! Create the RSWrapperBase object. The base class does not hold anything, + //! so this constructor does nothing. + RSWrapperBase() { } + + //! Create a new RSWrapperBase that is the same as this one. This function + //! will properly handle polymorphism. + virtual RSWrapperBase* Clone() const = 0; + + //! Destruct the RSWrapperBase (nothing to do). + virtual ~RSWrapperBase() { } + + //! Get the dataset. + virtual const arma::mat& Dataset() const = 0; + + //! Get whether single-tree search is being used. + virtual bool SingleMode() const = 0; + //! Modify whether single-tree search is being used. + virtual bool& SingleMode() = 0; + + //! Get whether naive search is being used. + virtual bool Naive() const = 0; + //! Modify whether naive search is being used. + virtual bool& Naive() = 0; + + //! Train the model (build the reference tree if needed). + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize) = 0; + + //! Perform bichromatic range search (i.e. a search with a separate query + //! set). + virtual void Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t leafSize) = 0; + + //! Perform monochromatic range search (i.e. a search with the reference set + //! as the query set). + virtual void Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) = 0; +}; + +/** + * RSWrapper is a wrapper class for most RangeSearch types. */ template class TreeType> -using RSType = RangeSearch; - -/** - * MonoSearchVisitor executes a monochromatic range search on the given - * RSType. Range Search is performed on the reference set itself, no querySet. - */ -class MonoSearchVisitor : public boost::static_visitor +class RSWrapper : public RSWrapperBase { - private: - //! The range to search for. - const math::Range& range; - //! Output neighbors. - std::vector>& neighbors; - //! Output distances. - std::vector>& distances; - public: - //! Perform monochromatic search with the given RangeSearch object. - template - void operator()(RSType* rs) const; + //! Create the RSWrapper object. + RSWrapper(const bool singleMode, const bool naive) : + rs(singleMode, naive) + { + // Nothing else to do. + } - //! Construct the MonoSearchVisitor with the given parameters. - MonoSearchVisitor(const math::Range& range, - std::vector>& neighbors, - std::vector>& distances): - range(range), - neighbors(neighbors), - distances(distances) - {}; + //! Create a new RSWrapper that is the same as this one. This function + //! will properly handle polymorphism. + virtual RSWrapper* Clone() const { return new RSWrapper(*this); } + + //! Destruct the RSWrapper (nothing to do). + virtual ~RSWrapper() { } + + //! Get the dataset. + const arma::mat& Dataset() const { return rs.ReferenceSet(); } + + //! Get whether single-tree search is being used. + bool SingleMode() const { return rs.SingleMode(); } + //! Modify whether single-tree search is being used. + bool& SingleMode() { return rs.SingleMode(); } + + //! Get whether naive search is being used. + bool Naive() const { return rs.Naive(); } + //! Modify whether naive search is being used. + bool& Naive() { return rs.Naive(); } + + //! Train the model (build the reference tree if needed). This ignores the + //! leaf size. + virtual void Train(arma::mat&& referenceSet, + const size_t /* leafSize */); + + //! Perform bichromatic range search (i.e. a search with a separate query + //! set). This ignores the leaf size. + virtual void Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t /* leafSize */); + + //! Perform monochromatic range search (i.e. a search with the reference set + //! as the query set). + virtual void Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances); + + //! Serialize the RangeSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(rs)); + } + + protected: + typedef RangeSearch RSType; + + //! The instantiated RangeSearch object that we are wrapping. + RSType rs; }; /** - * BiSearchVisitor executes a bichromatic range search on the given RSType. - * We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, before doing range search, - * a query tree with proper leafSize is built from the querySet. + * LeafSizeRSWrapper wraps any RangeSearch type that needs to be able to take + * the leaf size into account when building trees. The implementations of + * Train() and bichromatic Search() take this leaf size into account. */ -class BiSearchVisitor : public boost::static_visitor +template class TreeType> +class LeafSizeRSWrapper : public RSWrapper { - private: - //! The query set for the bichromatic search. - const arma::mat& querySet; - //! Range to search neighbours for. - const math::Range& range; - //! The result vector for neighbors. - std::vector>& neighbors; - //! The result vector for distances. - std::vector>& distances; - //! The number of points in a leaf (for BinarySpaceTrees). - const size_t leafSize; - - //! Bichromatic range search on the given RSType considering the leafSize. - template - void SearchLeaf(RSType* rs) const; - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RSTypeT = RSType; + //! Construct the LeafSizeRSWrapper by delegating to the RSWrapper + //! constructor. + LeafSizeRSWrapper(const bool singleMode, const bool naive) : + RSWrapper(singleMode, naive) + { + // Nothing else to do. + } - //! Default Bichromatic range search on the given RSType instance. - template class TreeType> - void operator()(RSTypeT* rs) const; + //! Delete the LeafSizeRSWrapper. + virtual ~LeafSizeRSWrapper() { } - //! Bichromatic range search on the given RSType specialized for KDTrees. - void operator()(RSTypeT* rs) const; + //! Return a copy of the LeafSizeRSWrapper. + virtual LeafSizeRSWrapper* Clone() const + { + return new LeafSizeRSWrapper(*this); + } - //! Bichromatic range search on the given RSType specialized for BallTrees. - void operator()(RSTypeT* rs) const; + //! Train a model with the given parameters. This overload uses leafSize. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize); - //! Bichromatic range search specialized for octrees. - void operator()(RSTypeT* rs) const; + //! Perform bichromatic search (e.g. search with a separate query set). This + //! overload takes the leaf size into account when building the query tree. + virtual void Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t leafSize); - //! Construct the BiSearchVisitor. - BiSearchVisitor(const arma::mat& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances, - const size_t leafSize); + //! Serialize the RangeSearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(rs)); + } + + protected: + using RSWrapper::rs; }; /** - * TrainVisitor sets the reference set to a new reference set on the given - * RSType. We use template specialization to differentiate those tree types that - * accept leafSize as a parameter. In these cases, a reference tree with proper - * leafSize is built from the referenceSet. + * The RSModel class provides an abstraction for the RangeSearch class, + * abstracting away the TreeType parameter and allowing it to be specified at + * runtime. This class is written for the sake of the `range_search` binding, + * but is not necessarily restricted to that usage. */ -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set to use for training. - arma::mat&& referenceSet; - //! The leaf size, used only by BinarySpaceTree. - size_t leafSize; - //! Train on the given RsType considering the leafSize. - template - void TrainLeaf(RSType* rs) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RSTypeT = RSType; - - //! Default Train on the given RSType instance. - template class TreeType> - void operator()(RSTypeT* rs) const; - - //! Train on the given RSType specialized for KDTrees. - void operator()(RSTypeT* rs) const; - - //! Train on the given RSType specialized for BallTrees. - void operator()(RSTypeT* rs) const; - - //! Train specialized for octrees. - void operator()(RSTypeT* rs) const; - - //! Construct the TrainVisitor object with the given reference set, leafSize - TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize); -}; - -/** - * ReferenceSetVisitor exposes the referenceSet of the given RSType. - */ -class ReferenceSetVisitor : public boost::static_visitor -{ - public: - //! Return the reference set. - template - const arma::mat& operator()(RSType* rs) const; -}; - -/** - * DeleteVisitor deletes the given RSType instance. - */ -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete the RSType object. - template - void operator()(RSType* rs) const; -}; - -/** - * SingleModeVisitor exposes the SingleMode() method of the given RSType. - */ -class SingleModeVisitor : public boost::static_visitor -{ - public: - /** - * Get a reference to the singleMode parameter of the given RangeSeach - * object. - */ - template - bool& operator()(RSType* rs) const; -}; - -/** - * NaiveVisitor exposes the Naive() method of the given RSType. - */ -class NaiveVisitor : public boost::static_visitor -{ - public: - /** - * Get a reference to the naive parameter of the given RangeSearch object. - */ - template - bool& operator()(RSType* rs) const; -}; - class RSModel { public: @@ -231,36 +224,6 @@ class RSModel OCTREE }; - private: - TreeTypes treeType; - size_t leafSize; - - //! If true, we randomly project the data into a new basis before search. - bool randomBasis; - //! Random projection matrix. - arma::mat q; - - /** - * rSearch holds an instance of the RangeSearch class for the current - * treeType. It is initialized every time BuildModel is executed. - * We access to the contained value through the visitor classes defined above. - */ - boost::variant*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*, - RSType*> rSearch; - - public: /** * Initialize the RSModel with the given type and whether or not a random * basis should be used. @@ -288,11 +251,16 @@ class RSModel /** * Copy the given RSModel. * - * Use std::move to pass in the model if the old copy is no longer needed. + * @param other RSModel to copy. + */ + RSModel& operator=(const RSModel& other); + + /** + * Take ownership of the given RSModel's data. * * @param other RSModel to copy. */ - RSModel& operator=(RSModel other); + RSModel& operator=(RSModel&& other); /** * Clean memory, if necessary. @@ -304,17 +272,17 @@ class RSModel void serialize(Archive& ar, const uint32_t /* version */); //! Expose the dataset. - const arma::mat& Dataset() const; + const arma::mat& Dataset() const { return rSearch->Dataset(); } //! Get whether the model is in single-tree search mode. - bool SingleMode() const; + bool SingleMode() const { return rSearch->SingleMode(); } //! Modify whether the model is in single-tree search mode. - bool& SingleMode(); + bool& SingleMode() { return rSearch->SingleMode(); } //! Get whether the model is in naive search mode. - bool Naive() const; + bool Naive() const { return rSearch->Naive(); } //! Modify whether the model is in naive search mode. - bool& Naive(); + bool& Naive() { return rSearch->Naive(); } //! Get the leaf size (applicable to everything but the cover tree). size_t LeafSize() const { return leafSize; } @@ -332,6 +300,11 @@ class RSModel //! been built). bool& RandomBasis() { return randomBasis; } + /** + * Allocate the memory for the range search model. + */ + void InitializeModel(const bool naive, const bool singleMode); + /** * Build the reference tree on the given dataset with the given parameters. * This takes possession of the reference set to avoid a copy. @@ -375,6 +348,23 @@ class RSModel std::vector>& distances); private: + //! The type of tree we are using. + TreeTypes treeType; + //! (Only used for some tree types.) The leaf size to use when building a + //! tree. + size_t leafSize; + + //! If true, we randomly project the data into a new basis before search. + bool randomBasis; + //! Random projection matrix. + arma::mat q; + + /** + * rSearch holds an instance of the RangeSearch class for the current + * treeType. It is initialized every time BuildModel is executed. + */ + RSWrapperBase* rSearch; + /** * Return a string representing the name of the tree. This is used for * logging output. @@ -390,7 +380,7 @@ class RSModel } // namespace range } // namespace mlpack -// Include implementation (of serialize() and inline functions). +// Include implementation (of serialize() and templated wrapper classes). #include "rs_model_impl.hpp" #endif diff --git a/src/mlpack/methods/range_search/rs_model_impl.hpp b/src/mlpack/methods/range_search/rs_model_impl.hpp index ea94903104..a59180e1b2 100644 --- a/src/mlpack/methods/range_search/rs_model_impl.hpp +++ b/src/mlpack/methods/range_search/rs_model_impl.hpp @@ -20,322 +20,87 @@ namespace mlpack { namespace range { -/** - * Initialize the RSModel with the given tree type and whether or not a random - * basis should be used. - */ -inline RSModel::RSModel(TreeTypes treeType, bool randomBasis) : - treeType(treeType), - leafSize(0), - randomBasis(randomBasis) -{ - // Nothing to do. -} - -// Copy constructor. -inline RSModel::RSModel(const RSModel& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(other.q), - rSearch(other.rSearch) -{ - // Nothing to do. -} - -// Move constructor. -inline RSModel::RSModel(RSModel&& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(std::move(other.q)), - rSearch(std::move(other.rSearch)) -{ - // Reset other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 0; - other.randomBasis = false; - other.rSearch = decltype(other.rSearch)(); -} - -inline RSModel& RSModel::operator=(RSModel other) -{ - boost::apply_visitor(DeleteVisitor(), rSearch); - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = std::move(other.q); - rSearch = std::move(other.rSearch); - - return *this; -} - -// Clean memory, if necessary. -inline RSModel::~RSModel() -{ - boost::apply_visitor(DeleteVisitor(), rSearch); -} - -inline void RSModel::BuildModel(arma::mat&& referenceSet, - const size_t leafSize, - const bool naive, - const bool singleMode) -{ - // Initialize random basis if necessary. - if (randomBasis) - { - Log::Info << "Creating random basis..." << std::endl; - math::RandomBasis(q, referenceSet.n_rows); - } - - this->leafSize = leafSize; - - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), rSearch); - - // Do we need to modify the reference set? - if (randomBasis) - referenceSet = q * referenceSet; - - if (!naive) - { - Timer::Start("tree_building"); - Log::Info << "Building reference tree..." << std::endl; - } - - switch (treeType) - { - case KD_TREE: - rSearch = new RSType (naive, singleMode); - break; - - case COVER_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_STAR_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case BALL_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case X_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case HILBERT_R_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_PLUS_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case R_PLUS_PLUS_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case VP_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case RP_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case MAX_RP_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case UB_TREE: - rSearch = new RSType(naive, singleMode); - break; - - case OCTREE: - rSearch = new RSType(naive, singleMode); - break; - } - - TrainVisitor tn(std::move(referenceSet), leafSize); - boost::apply_visitor(tn, rSearch); - - if (!naive) - { - Timer::Stop("tree_building"); - Log::Info << "Tree built." << std::endl; - } -} - -// Perform range search. -inline void RSModel::Search(arma::mat&& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances) -{ - // We may need to map the query set randomly. - if (randomBasis) - querySet = q * querySet; - - Log::Info << "Search for points in the range [" << range.Lo() << ", " - << range.Hi() << "] with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; - else if (!Naive()) - Log::Info << "single-tree " << TreeName() << " search..." << std::endl; - else - Log::Info << "brute-force (naive) search..." << std::endl; - - - BiSearchVisitor search(querySet, range, neighbors, distances, - leafSize); - boost::apply_visitor(search, rSearch); -} - -// Perform range search (monochromatic case). -inline void RSModel::Search(const math::Range& range, - std::vector>& neighbors, - std::vector>& distances) -{ - Log::Info << "Search for points in the range [" << range.Lo() << ", " - << range.Hi() << "] with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree " << TreeName() << " search..." << std::endl; - else if (!Naive()) - Log::Info << "single-tree " << TreeName() << " search..." << std::endl; - else - Log::Info << "brute-force (naive) search..." << std::endl; - - MonoSearchVisitor search(range, neighbors, distances); - boost::apply_visitor(search, rSearch); -} - -// Get the name of the tree type. -inline std::string RSModel::TreeName() const -{ - switch (treeType) - { - case KD_TREE: - return "kd-tree"; - case COVER_TREE: - return "cover tree"; - case R_TREE: - return "R tree"; - case R_STAR_TREE: - return "R* tree"; - case BALL_TREE: - return "ball tree"; - case X_TREE: - return "X tree"; - case HILBERT_R_TREE: - return "Hilbert R tree"; - case R_PLUS_TREE: - return "R+ tree"; - case R_PLUS_PLUS_TREE: - return "R++ tree"; - case VP_TREE: - return "vantage point tree"; - case RP_TREE: - return "random projection tree (mean split)"; - case MAX_RP_TREE: - return "random projection tree (max split)"; - case UB_TREE: - return "UB tree"; - case OCTREE: - return "octree"; - default: - return "unknown tree"; - } -} - -// Clean memory. -inline void RSModel::CleanMemory() -{ - boost::apply_visitor(DeleteVisitor(), rSearch); -} - -//! Monochromatic range search on the given RSType instance. -template -void MonoSearchVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->Search(range, neighbors, distances); - throw std::runtime_error("no range search model initialized"); -} - -//! Save parameters for bichromatic range search. -inline BiSearchVisitor::BiSearchVisitor( - const arma::mat& querySet, - const math::Range& range, - std::vector>& neighbors, - std::vector>& distances, - const size_t leafSize) : - querySet(querySet), - range(range), - neighbors(neighbors), - distances(distances), - leafSize(leafSize) -{} - -//! Default Bichromatic range search on the given RSType instance. template class TreeType> -void BiSearchVisitor::operator()(RSTypeT* rs) const +void RSWrapper::Train(arma::mat&& referenceSet, + const size_t /* leafSize */) { - if (rs) - return rs->Search(querySet, range, neighbors, distances); - throw std::runtime_error("no range search model initialized"); + rs.Train(std::move(referenceSet)); } -//! Bichromatic range search on the given RSType specialized for KDTrees. -inline void BiSearchVisitor::operator()(RSTypeT* rs) const +template class TreeType> +void RSWrapper::Search(arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t /* leafSize */) { - if (rs) - return SearchLeaf(rs); - throw std::runtime_error("no range search model initialized"); + rs.Search(std::move(querySet), range, neighbors, distances); } -//! Bichromatic range search on the given RSType specialized for BallTrees. -inline void BiSearchVisitor::operator()(RSTypeT* rs) const +template class TreeType> +void RSWrapper::Search(const math::Range& range, + std::vector>& neighbors, + std::vector>& distances) { - if (rs) - return SearchLeaf(rs); - throw std::runtime_error("no range search model initialized"); + rs.Search(range, neighbors, distances); } -//! Bichromatic range search specialized for Ocrees. -inline void BiSearchVisitor::operator()(RSTypeT* rs) const +template class TreeType> +void LeafSizeRSWrapper::Train(arma::mat&& referenceSet, + const size_t leafSize) { - if (rs) - return SearchLeaf(rs); - throw std::runtime_error("no range search model initialized"); + if (rs.Naive()) + { + rs.Train(std::move(referenceSet)); + } + else + { + std::vector oldFromNewReferences; + typename decltype(rs)::Tree* tree = + new typename decltype(rs)::Tree(std::move(referenceSet), + oldFromNewReferences, + leafSize); + rs.Train(tree); + + // Give the model ownership of the tree and the mappings. + rs.treeOwner = true; + rs.oldFromNewReferences = std::move(oldFromNewReferences); + } } -//! Bichromatic range search on the given RSType considering the leafSize. -template -void BiSearchVisitor::SearchLeaf(RSType* rs) const +template class TreeType> +void LeafSizeRSWrapper::Search( + arma::mat&& querySet, + const math::Range& range, + std::vector>& neighbors, + std::vector>& distances, + const size_t leafSize) { - if (!rs->Naive() && !rs->SingleMode()) + if (!rs.Naive() && !rs.SingleMode()) { // Build a second tree and search. Timer::Start("tree_building"); Log::Info << "Building query tree..." << std::endl; std::vector oldFromNewQueries; - typename RSType::Tree queryTree(std::move(querySet), oldFromNewQueries, - leafSize); + typename decltype(rs)::Tree queryTree(std::move(querySet), + oldFromNewQueries, + leafSize); Log::Info << "Tree built." << std::endl; Timer::Stop("tree_building"); std::vector> neighborsOut; std::vector> distancesOut; - rs->Search(&queryTree, range, neighborsOut, distancesOut); + rs.Search(&queryTree, range, neighborsOut, distancesOut); // Remap the query points. neighbors.resize(queryTree.Dataset().n_cols); @@ -346,107 +111,12 @@ void BiSearchVisitor::SearchLeaf(RSType* rs) const distances[oldFromNewQueries[i]] = distancesOut[i]; } } - else - rs->Search(querySet, range, neighbors, distances); -} - -//! Save parameters for Train. -inline TrainVisitor::TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize) : - referenceSet(std::move(referenceSet)), - leafSize(leafSize) -{} - -//! Default Train on the given RSType instance. -template class TreeType> -void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return rs->Train(std::move(referenceSet)); - throw std::runtime_error("no range search model initialized"); -} - -//! Train on the given RSType specialized for KDTrees. -inline void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return TrainLeaf(rs); - throw std::runtime_error("no range search model initialized"); -} - -//! Train on the given RSType specialized for BallTrees. -inline void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return TrainLeaf(rs); - throw std::runtime_error("no range search model initialized"); -} - -//! Train specialized for Octrees. -inline void TrainVisitor::operator()(RSTypeT* rs) const -{ - if (rs) - return TrainLeaf(rs); - throw std::runtime_error("no range search model initialized"); -} - -//! Train on the given RSType considering the leafSize. -template -void TrainVisitor::TrainLeaf(RSType* rs) const -{ - if (rs->Naive()) - rs->Train(std::move(referenceSet)); else { - std::vector oldFromNewReferences; - typename RSType::Tree* tree = - new typename RSType::Tree(std::move(referenceSet), oldFromNewReferences, - leafSize); - rs->Train(tree); - - // Give the model ownership of the tree and the mappings. - rs->treeOwner = true; - rs->oldFromNewReferences = std::move(oldFromNewReferences); + rs.Search(std::move(querySet), range, neighbors, distances); } } -//! Expose the referenceSet of the given RSType. -template -const arma::mat& ReferenceSetVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->ReferenceSet(); - throw std::runtime_error("no range search model initialized"); -} - -//! For cleaning memory -template -void DeleteVisitor::operator()(RSType* rs) const -{ - if (rs) - delete rs; -} - -//! Return whether single mode enabled -template -bool& SingleModeVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->SingleMode(); - throw std::runtime_error("no range search model initialized"); -} - -//! Exposes Naive() function of given RSType -template -bool& NaiveVisitor::operator()(RSType* rs) const -{ - if (rs) - return rs->Naive(); - throw std::runtime_error("no range search model initialized"); -} - // Serialize the model. template void RSModel::serialize(Archive& ar, const uint32_t /* version */) @@ -457,35 +127,119 @@ void RSModel::serialize(Archive& ar, const uint32_t /* version */) // This should never happen, but just in case... if (cereal::is_loading()) - boost::apply_visitor(DeleteVisitor(), rSearch); + InitializeModel(false, false); // Values will be overwritten. - // We'll only need to serialize one of the model objects, based on the type. - ar(CEREAL_VARIANT_POINTER(rSearch)); -} + // Avoid polymorphic serialization by explicitly serializing the correct type. + switch (treeType) + { + case KD_TREE: + { + LeafSizeRSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case COVER_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline const arma::mat& RSModel::Dataset() const -{ - return boost::apply_visitor(ReferenceSetVisitor(), rSearch); -} + case R_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool RSModel::SingleMode() const -{ - return boost::apply_visitor(SingleModeVisitor(), rSearch); -} + case R_STAR_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool& RSModel::SingleMode() -{ - return boost::apply_visitor(SingleModeVisitor(), rSearch); -} + case BALL_TREE: + { + LeafSizeRSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case X_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool RSModel::Naive() const -{ - return boost::apply_visitor(NaiveVisitor(), rSearch); -} + case HILBERT_R_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } -inline bool& RSModel::Naive() -{ - return boost::apply_visitor(NaiveVisitor(), rSearch); + case R_PLUS_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case R_PLUS_PLUS_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case VP_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case RP_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + + case MAX_RP_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case UB_TREE: + { + RSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + case OCTREE: + { + LeafSizeRSWrapper& typedSearch = + dynamic_cast&>(*rSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } + } } } // namespace range diff --git a/src/mlpack/methods/rann/CMakeLists.txt b/src/mlpack/methods/rann/CMakeLists.txt index 99e838b459..42a70dbc26 100644 --- a/src/mlpack/methods/rann/CMakeLists.txt +++ b/src/mlpack/methods/rann/CMakeLists.txt @@ -23,6 +23,7 @@ set(SOURCES # model ra_model.hpp ra_model_impl.hpp + ra_model.cpp ) # add directory name to sources diff --git a/src/mlpack/methods/rann/krann_main.cpp b/src/mlpack/methods/rann/krann_main.cpp index 9d830f5fff..0ed34fd0f2 100644 --- a/src/mlpack/methods/rann/krann_main.cpp +++ b/src/mlpack/methods/rann/krann_main.cpp @@ -25,9 +25,6 @@ using namespace mlpack::tree; using namespace mlpack::metric; using namespace mlpack::util; -// Convenience typedef. -typedef RAModel RANNModel; - // Program Name. BINDING_NAME("K-Rank-Approximate-Nearest-Neighbors (kRANN)"); @@ -86,8 +83,8 @@ PARAM_MATRIX_OUT("distances", "Matrix to output distances into.", "d"); PARAM_UMATRIX_OUT("neighbors", "Matrix to output neighbors into.", "n"); // The option exists to load or save models. -PARAM_MODEL_IN(RANNModel, "input_model", "Pre-trained kNN model.", "m"); -PARAM_MODEL_OUT(RANNModel, "output_model", "If specified, the kNN model will be" +PARAM_MODEL_IN(RAModel, "input_model", "Pre-trained kNN model.", "m"); +PARAM_MODEL_OUT(RAModel, "output_model", "If specified, the kNN model will be" " output here.", "M"); // The user may specify a query file of query points and a number of nearest @@ -170,12 +167,12 @@ static void mlpackMain() "alpha must be in range [0.0, 1.0]"); // We either have to load the reference data, or we have to load the model. - RANNModel* rann; + RAModel* rann; const bool naive = IO::HasParam("naive"); const bool singleMode = IO::HasParam("single_mode"); if (IO::HasParam("reference")) { - rann = new RANNModel(); + rann = new RAModel(); // Get all the parameters. const string treeType = IO::GetParam("tree_type"); @@ -184,27 +181,27 @@ static void mlpackMain() "unknown tree type"); const bool randomBasis = IO::HasParam("random_basis"); - RANNModel::TreeTypes tree = RANNModel::KD_TREE; + RAModel::TreeTypes tree = RAModel::KD_TREE; if (treeType == "kd") - tree = RANNModel::KD_TREE; + tree = RAModel::KD_TREE; else if (treeType == "cover") - tree = RANNModel::COVER_TREE; + tree = RAModel::COVER_TREE; else if (treeType == "r") - tree = RANNModel::R_TREE; + tree = RAModel::R_TREE; else if (treeType == "r-star") - tree = RANNModel::R_STAR_TREE; + tree = RAModel::R_STAR_TREE; else if (treeType == "x") - tree = RANNModel::X_TREE; + tree = RAModel::X_TREE; else if (treeType == "hilbert-r") - tree = RANNModel::HILBERT_R_TREE; + tree = RAModel::HILBERT_R_TREE; else if (treeType == "r-plus") - tree = RANNModel::R_PLUS_TREE; + tree = RAModel::R_PLUS_TREE; else if (treeType == "r-plus-plus") - tree = RANNModel::R_PLUS_PLUS_TREE; + tree = RAModel::R_PLUS_PLUS_TREE; else if (treeType == "ub") - tree = RANNModel::UB_TREE; + tree = RAModel::UB_TREE; else if (treeType == "oct") - tree = RANNModel::OCTREE; + tree = RAModel::OCTREE; rann->TreeType() = tree; rann->RandomBasis() = randomBasis; @@ -218,10 +215,10 @@ static void mlpackMain() else { // Load the model from file. - rann = IO::GetParam("input_model"); + rann = IO::GetParam("input_model"); Log::Info << "Using rank-approximate kNN model from '" - << IO::GetPrintableParam("input_model") << "' (trained on " + << IO::GetPrintableParam("input_model") << "' (trained on " << rann->Dataset().n_rows << "x" << rann->Dataset().n_cols << " dataset)." << endl; @@ -285,5 +282,5 @@ static void mlpackMain() } // Save the output model. - IO::GetParam("output_model") = rann; + IO::GetParam("output_model") = rann; } diff --git a/src/mlpack/methods/rann/ra_model.cpp b/src/mlpack/methods/rann/ra_model.cpp new file mode 100644 index 0000000000..6342acf6b4 --- /dev/null +++ b/src/mlpack/methods/rann/ra_model.cpp @@ -0,0 +1,239 @@ +/** + * @file methods/rann/ra_model.cpp + * @author Ryan Curtin + * + * Implementation of the RAModel class. + * + * mlpack is free software; you may redistribute it and/or modify it under the + * terms of the 3-clause BSD license. You should have received a copy of the + * 3-clause BSD license along with mlpack. If not, see + * http://www.opensource.org/licenses/BSD-3-Clause for more information. + */ +#include "ra_model.hpp" +#include + +namespace mlpack { +namespace neighbor { + +RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : + treeType(treeType), + leafSize(20), + randomBasis(randomBasis), + raSearch(NULL) +{ + // Nothing to do. +} + +// Copy constructor. +RAModel::RAModel(const RAModel& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(other.q), + raSearch(other.raSearch->Clone()) +{ + // Nothing to do. +} + +// Move constructor. +RAModel::RAModel(RAModel&& other) : + treeType(other.treeType), + leafSize(other.leafSize), + randomBasis(other.randomBasis), + q(std::move(other.q)), + raSearch(std::move(other.raSearch)) +{ + // Clear other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 20; + other.randomBasis = false; +} + +// Copy operator. +RAModel& RAModel::operator=(const RAModel& other) +{ + if (this != &other) + { + // Clear current model. + delete raSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = other.q; + raSearch = other.raSearch->Clone(); + } + + return *this; +} + +RAModel& RAModel::operator=(RAModel&& other) +{ + if (this != &other) + { + // Clear current model. + delete raSearch; + + treeType = other.treeType; + leafSize = other.leafSize; + randomBasis = other.randomBasis; + q = std::move(other.q); + raSearch = std::move(other.raSearch); + + // Reset other model. + other.treeType = TreeTypes::KD_TREE; + other.leafSize = 20; + other.randomBasis = false; + } + + return *this; +} + +// Clean memory, if necessary +RAModel::~RAModel() +{ + delete raSearch; +} + +void RAModel::InitializeModel(const bool naive, const bool singleMode) +{ + // Clean memory, if necessary. + delete raSearch; + + switch (treeType) + { + case KD_TREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + case COVER_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_STAR_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case X_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case HILBERT_R_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_PLUS_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case R_PLUS_PLUS_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case UB_TREE: + raSearch = new RAWrapper(naive, singleMode); + break; + case OCTREE: + raSearch = new LeafSizeRAWrapper(naive, singleMode); + break; + } +} + +void RAModel::BuildModel(arma::mat&& referenceSet, + const size_t leafSize, + const bool naive, + const bool singleMode) +{ + // Initialize random basis, if necessary. + if (randomBasis) + { + Log::Info << "Creating random basis..." << std::endl; + math::RandomBasis(q, referenceSet.n_rows); + } + + this->leafSize = leafSize; + + if (randomBasis) + referenceSet = q * referenceSet; + + if (!naive) + { + Timer::Start("tree_building"); + Log::Info << "Building reference tree..." << std::endl; + } + + InitializeModel(naive, singleMode); + + raSearch->Train(std::move(referenceSet), leafSize); + + if (!naive) + { + Timer::Stop("tree_building"); + Log::Info << "Tree built." << std::endl; + } +} + +void RAModel::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances) +{ + // Apply the random basis if necessary. + if (randomBasis) + querySet = q * querySet; + + Log::Info << "Searching for " << k << " approximate nearest neighbors with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; + else if (!Naive()) + Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; + else + Log::Info << "brute-force (naive) rank-approximate search..."; + Log::Info << std::endl; + + raSearch->Search(std::move(querySet), k, neighbors, distances, leafSize); +} + +void RAModel::Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) +{ + Log::Info << "Searching for " << k << " approximate nearest neighbors with "; + if (!Naive() && !SingleMode()) + Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; + else if (!Naive()) + Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; + else + Log::Info << "brute-force (naive) rank-approximate search..."; + Log::Info << std::endl; + + raSearch->Search(k, neighbors, distances); +} + +std::string RAModel::TreeName() const +{ + switch (treeType) + { + case KD_TREE: + return "kd-tree"; + case COVER_TREE: + return "cover tree"; + case R_TREE: + return "R tree"; + case R_STAR_TREE: + return "R* tree"; + case X_TREE: + return "X tree"; + case HILBERT_R_TREE: + return "Hilbert R tree"; + case R_PLUS_TREE: + return "R+ tree"; + case R_PLUS_PLUS_TREE: + return "R++ tree"; + case UB_TREE: + return "UB tree"; + case OCTREE: + return "octree"; + default: + return "unknown tree"; + } +} + +} // namespace neighbor +} // namespace mlpack diff --git a/src/mlpack/methods/rann/ra_model.hpp b/src/mlpack/methods/rann/ra_model.hpp index ed32d4a352..572d599a0d 100644 --- a/src/mlpack/methods/rann/ra_model.hpp +++ b/src/mlpack/methods/rann/ra_model.hpp @@ -18,245 +18,234 @@ #include #include #include -#include #include "ra_search.hpp" namespace mlpack { namespace neighbor { /** - * Alias template for RASearch + * RAWrapperBase is a base wrapper class for holding all RASearch types + * supported by RAModel. All RASearch type wrappers inherit from this class, + * allowing a simple interface via inheritance for all the different types we + * want to support. */ -template& neighbors, + arma::mat& distances, + const size_t leafSize) = 0; + + //! Perform monochromatic rank-approximate nearest neighbor search (i.e. a + //! search with the reference set as the query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) = 0; +}; + +/** + * RAWrapper is a wrapper class for most RASearch types. + */ +template class TreeType> -using RAType = RASearch; - -/** - * MonoSearchVisitor executes a monochromatic neighbor search on the given - * RAType. We don't make any difference for different instantiation of RAType. - */ -class MonoSearchVisitor : public boost::static_visitor +class RAWrapper : public RAWrapperBase { - private: - //! Number of neighbors to search for. - const size_t k; - //! Result matrix for neighbors. - arma::Mat& neighbors; - //! Result matrix for distances. - arma::mat& distances; - public: - //! Perform monochromatic nearest neighbor search. - template - void operator()(RAType* ra) const; + //! Construct the RAWrapper object, initializing the internally-held RASearch + //! object. + RAWrapper(const bool singleMode, const bool naive) : + ra(singleMode, naive) + { + // Nothing else to do. + } - //! Construct the MonoSearchVisitor object with the given parameters. - MonoSearchVisitor(const size_t k, - arma::Mat& neighbors, - arma::mat& distances) : - k(k), - neighbors(neighbors), - distances(distances) - {}; + //! Delete the RAWrapper object. + virtual ~RAWrapper() { } + + //! Create a copy of this RAWrapper object. This correctly handles + //! polymorphism. + virtual RAWrapper* Clone() const { return new RAWrapper(*this); } + + //! Get a reference to the reference set. + const arma::mat& Dataset() const { return ra.ReferenceSet(); } + + //! Get the single sample limit. + size_t SingleSampleLimit() const { return ra.SingleSampleLimit(); } + //! Modify the single sample limit. + size_t& SingleSampleLimit() { return ra.SingleSampleLimit(); } + + //! Get whether to do exact search at the first leaf. + bool FirstLeafExact() const { return ra.FirstLeafExact(); } + //! Modify whether to do exact search at the first leaf. + bool& FirstLeafExact() { return ra.FirstLeafExact(); } + + //! Get whether to do sampling at leaves. + bool SampleAtLeaves() const { return ra.SampleAtLeaves(); } + //! Modify whether to do sampling at leaves. + bool& SampleAtLeaves() { return ra.SampleAtLeaves(); } + + //! Get the value of alpha. + double Alpha() const { return ra.Alpha(); } + //! Modify the value of alpha. + double& Alpha() { return ra.Alpha(); } + + //! Get the value of tau. + double Tau() const { return ra.Tau(); } + //! Modify the value of tau. + double& Tau() { return ra.Tau(); } + + //! Get whether single-tree search is being used. + bool SingleMode() const { return ra.SingleMode(); } + //! Modify whether single-tree search is being used. + bool& SingleMode() { return ra.SingleMode(); } + + //! Get whether naive search is being used. + bool Naive() const { return ra.Naive(); } + //! Modify whether naive search is being used. + bool& Naive() { return ra.Naive(); } + + //! Train the model. For RAWrapper, we ignore the leaf size. + virtual void Train(arma::mat&& referenceSet, + const size_t /* leafSize */); + + //! Perform bichromatic neighbor search (i.e. search with a separate query + //! set). For RAWrapper, we ignore the leaf size. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */); + + //! Perform monochromatic neighbor search (i.e. search where the reference set + //! is used as the query set). + virtual void Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances); + + //! Serialize the RASearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ra)); + } + + protected: + typedef RASearch RAType; + + //! The instantiated RASearch object that we are wrapping. + RAType ra; }; /** - * BiSearchVisitor executes a bichromatic neighbor search on the given RAType. - * We use template specialization to differentiate those tree types types that - * accept leafSize as a parameter. In these cases, before doing neighbor search - * a query tree with proper leafSize is built from the querySet. + * LeafSizeRAWrapper wraps any RASearch type that needs to be able to take the + * leaf size into account when building trees. The implementations of Train() + * and bichromatic Search() take this leaf size into account. */ -template -class BiSearchVisitor : public boost::static_visitor -{ - private: - //! The query set for the bichromatic search. - const arma::mat& querySet; - //! The number of neighbors to search for. - const size_t k; - //! The results matrix for neighbors. - arma::Mat& neighbors; - //! The result matrix for distances. - arma::mat& distances; - //! The number of points in a leaf (for BinarySpaceTrees). - const size_t leafSize; - - //! Bichromatic neighbor search on the given RAType considering leafSize. - template - void SearchLeaf(RAType* ra) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RATypeT = RAType; - - //! Default Bichromatic neighbor search on the given RAType instance. - template class TreeType> - void operator()(RATypeT* ra) const; - - //! Bichromatic search on the given RAType specialized for KDTrees. - void operator()(RATypeT* ra) const; - - //! Bichromatic search on the given RAType specialized for octrees. - void operator()(RATypeT* ra) const; - - //! Construct the BiSearchVisitor. - BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize); -}; - -/** - * TrainVisitor sets the reference set to a new reference set on the given - * RAType. We use template specialization to differentiate those trees that - * accept leafSize as a parameter. In these cases, a reference tree with proper - * leafSize is built from the referenceSet. - */ -template -class TrainVisitor : public boost::static_visitor -{ - private: - //! The reference set to use for training. - arma::mat&& referenceSet; - //! The leaf size, used only by BinarySpaceTree. - size_t leafSize; - - //! Train on the given RAType considering the leafSize. - template - void TrainLeaf(RAType* ra) const; - - public: - //! Alias template necessary for visual c++ compiler. - template class TreeType> - using RATypeT = RAType; - - //! Default Train on the given RAType instance. - template class TreeType> - void operator()(RATypeT* ra) const; - - //! Train on the given RAType specialized for KDTrees. - void operator()(RATypeT* ra) const; - - //! Train on the given RAType specialized for Octrees. - void operator()(RATypeT* ra) const; - - //! Construct the TrainVisitor object with the given reference set, leafSize - //! for BinarySpaceTrees. - TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize); -}; - -/** - * Exposes the SingleSampleLimit() method of the given RAType. - */ -class SingleSampleLimitVisitor : public boost::static_visitor +template class TreeType> +class LeafSizeRAWrapper : public RAWrapper { public: - template - size_t& operator()(RAType* ra) const; -}; + //! Construct the LeafSizeRAWrapper by delegating to the RAWrapper + //! constructor. + LeafSizeRAWrapper(const bool singleMode, const bool naive) : + RAWrapper(singleMode, naive) + { + // Nothing else to do. + } -/** - * Exposes the FirstLeafExact() method of the given RAType. - */ -class FirstLeafExactVisitor : public boost::static_visitor -{ - public: - template - bool& operator()(RAType* ra) const; -}; + //! Delete the LeafSizeRAWrapper. + virtual ~LeafSizeRAWrapper() { } -/** - * Exposes the SampleAtLeaves() method of the given RAType. - */ -class SampleAtLeavesVisitor : public boost::static_visitor -{ - public: - //! Return SampleAtLeaves (whether or not sampling is done at leaves). - template - bool& operator()(RAType *) const; -}; + //! Return a copy of the LeafSizeRAWrapper. + virtual LeafSizeRAWrapper* Clone() const + { + return new LeafSizeRAWrapper(*this); + } -/** - * Exposes the Alpha() method of the given RAType. - */ -class AlphaVisitor : public boost::static_visitor -{ - public: - //! Return Alpha parameter. - template - double& operator()(RAType* ra) const; -}; + //! Train a model with the given parameters. This overload uses leafSize. + virtual void Train(arma::mat&& referenceSet, + const size_t leafSize); -/** - * Exposes the Tau() method of the given RAType. - */ -class TauVisitor : public boost::static_visitor -{ - public: - //! Get a reference to the Tau parameter. - template - double& operator()(RAType* ra) const; -}; + //! Perform bichromatic search (e.g. search with a separate query set). This + //! overload takes the leaf size into account to build the query tree. + virtual void Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize); -/** - * Exposes the SingleMode() method of the given RAType. - */ -class SingleModeVisitor : public boost::static_visitor -{ - public: - //! Get a reference to the SingleMode parameter of the given RASearch object. - template - bool& operator()(RAType* ra) const; -}; + //! Serialize the RASearch model. + template + void serialize(Archive& ar, const uint32_t /* version */) + { + ar(CEREAL_NVP(ra)); + } -/** - * Exposes the referenceSet of the given RAType. - */ -class ReferenceSetVisitor : public boost::static_visitor -{ - public: - //! Return the reference set. - template - const arma::mat& operator()(RAType* ra) const; -}; - -/** - * DeleteVisitor deletes the give RAType Instance. - */ -class DeleteVisitor : public boost::static_visitor -{ - public: - //! Delete the RAType Object. - template void operator()(RAType* ra) const; -}; - -/** - * NaiveVisitor exposes the Naive() method of the given RAType. - */ -class NaiveVisitor : public boost::static_visitor -{ - public: - /** - * Get a reference to the naive parameter of the given RASearch object. - */ - template - bool& operator()(RAType* ra) const; + protected: + using RAWrapper::ra; }; /** @@ -264,10 +253,7 @@ class NaiveVisitor : public boost::static_visitor * away the TreeType parameter and allowing it to be specified at runtime in * this class. This class is written for the sake of the 'allkrann' program, * but is not necessarily restricted to that use. - * - * @param SortPolicy Sorting policy for neighbor searching (see RASearch). */ -template class RAModel { public: @@ -301,16 +287,7 @@ class RAModel arma::mat q; //! The rank-approximate model. - boost::variant*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*, - RAType*> raSearch; + RAWrapperBase* raSearch; public: /** @@ -355,58 +332,61 @@ class RAModel void serialize(Archive& ar, const uint32_t /* version */); //! Expose the dataset. - const arma::mat& Dataset() const; + const arma::mat& Dataset() const { return raSearch->Dataset(); } //! Get whether or not single-tree search is being used. - bool SingleMode() const; + bool SingleMode() const { return raSearch->SingleMode(); } //! Modify whether or not single-tree search is being used. - bool& SingleMode(); + bool& SingleMode() { return raSearch->SingleMode(); } //! Get whether or not naive search is being used. - bool Naive() const; + bool Naive() const { return raSearch->Naive(); } //! Modify whether or not naive search is being used. - bool& Naive(); + bool& Naive() { return raSearch->Naive(); } //! Get the rank-approximation in percentile of the data. - double Tau() const; + double Tau() const { return raSearch->Tau(); } //! Modify the rank-approximation in percentile of the data. - double& Tau(); + double& Tau() { return raSearch->Tau(); } //! Get the desired success probability. - double Alpha() const; + double Alpha() const { return raSearch->Alpha(); } //! Modify the desired success probability. - double& Alpha(); + double& Alpha() { return raSearch->Alpha(); } //! Get whether or not sampling is done at the leaves. - bool SampleAtLeaves() const; + bool SampleAtLeaves() const { return raSearch->SampleAtLeaves(); } //! Modify whether or not sampling is done at the leaves. - bool& SampleAtLeaves(); + bool& SampleAtLeaves() { return raSearch->SampleAtLeaves(); } //! Get whether or not we traverse to the first leaf without approximation. - bool FirstLeafExact() const; + bool FirstLeafExact() const { return raSearch->FirstLeafExact(); } //! Modify whether or not we traverse to the first leaf without approximation. - bool& FirstLeafExact(); + bool& FirstLeafExact() { return raSearch->FirstLeafExact(); } //! Get the limit on the size of a node that can be approximated. - size_t SingleSampleLimit() const; + size_t SingleSampleLimit() const { return raSearch->SingleSampleLimit(); } //! Modify the limit on the size of a node that can be approximation. - size_t& SingleSampleLimit(); + size_t& SingleSampleLimit() { return raSearch->SingleSampleLimit(); } //! Get the leaf size (only relevant when the kd-tree is used). - size_t LeafSize() const; + size_t LeafSize() const { return leafSize; } //! Modify the leaf size (only relevant when the kd-tree is used). - size_t& LeafSize(); + size_t& LeafSize() { return leafSize; } //! Get the type of tree being used. - TreeTypes TreeType() const; + TreeTypes TreeType() const { return treeType; } //! Modify the type of tree being used. - TreeTypes& TreeType(); + TreeTypes& TreeType() { return treeType; } //! Get whether or not a random basis is being used. - bool RandomBasis() const; + bool RandomBasis() const { return randomBasis; } //! Modify whether or not a random basis is being used. Be sure to rebuild //! the model using BuildModel(). - bool& RandomBasis(); + bool& RandomBasis() { return randomBasis; } + + //! Initialize the model's memory. + void InitializeModel(const bool naive, const bool singleMode); //! Build the reference tree. void BuildModel(arma::mat&& referenceSet, diff --git a/src/mlpack/methods/rann/ra_model_impl.hpp b/src/mlpack/methods/rann/ra_model_impl.hpp index 3b27bfa2d6..c986c4570c 100644 --- a/src/mlpack/methods/rann/ra_model_impl.hpp +++ b/src/mlpack/methods/rann/ra_model_impl.hpp @@ -19,78 +19,87 @@ namespace mlpack { namespace neighbor { -//! Monochromatic search for the given RAType instance. -template -void MonoSearchVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Search(k, neighbors, distances); - throw std::runtime_error("no rank-approximate model initialized"); -} - -//! Save the parameters for the rank-approximate search. -template -BiSearchVisitor::BiSearchVisitor(const arma::mat& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances, - const size_t leafSize) : - querySet(querySet), - k(k), - neighbors(neighbors), - distances(distances), - leafSize(leafSize) -{}; - -//! Default Bichromatic search on the given RAType instance. -template template class TreeType> -void BiSearchVisitor::operator()(RATypeT* ra) const +void RAWrapper::Train(arma::mat&& referenceSet, + const size_t /* leafSize */) { - if (ra) - return ra->Search(querySet, k, neighbors, distances); - throw std::runtime_error("no rank-approximate model initialized"); + ra.Train(std::move(referenceSet)); } -//! Bichromatic search on the given RAType specialized for KDTrees. -template -void BiSearchVisitor::operator()(RATypeT* ra) const +template class TreeType> +void RAWrapper::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t /* leafSize */) { - if (ra) - return SearchLeaf(ra); - throw std::runtime_error("no rank-approximate search model initialized"); + ra.Search(querySet, k, neighbors, distances); } -//! Bichromatic search on the given RAType specialized for Octrees. -template -void BiSearchVisitor::operator()(RATypeT* ra) const +template class TreeType> +void RAWrapper::Search(const size_t k, + arma::Mat& neighbors, + arma::mat& distances) { - if (ra) - return SearchLeaf(ra); - throw std::runtime_error("no rank-approximate search model initialized"); + ra.Search(k, neighbors, distances); } -//! Bichromatic search on the given RAType considering the leafSize. -template -template -void BiSearchVisitor::SearchLeaf(RAType* ra) const +template class TreeType> +void LeafSizeRAWrapper::Train(arma::mat&& referenceSet, + const size_t leafSize) { - if (!ra->Naive() && !ra->SingleMode()) + // Build tree, if necessary. + if (ra.Naive()) { - // Build a second tree and search + ra.Train(std::move(referenceSet)); + } + else + { + std::vector oldFromNewReferences; + typename decltype(ra)::Tree* tree = + new typename decltype(ra)::Tree(std::move(referenceSet), + oldFromNewReferences, + leafSize); + ra.Train(tree); + + // Give the model ownership of the tree and the mappings. + ra.treeOwner = true; + ra.oldFromNewReferences = std::move(oldFromNewReferences); + } +} + +template class TreeType> +void LeafSizeRAWrapper::Search(arma::mat&& querySet, + const size_t k, + arma::Mat& neighbors, + arma::mat& distances, + const size_t leafSize) +{ + if (!ra.Naive() && !ra.SingleMode()) + { + // Build a second tree and search, taking the leaf size into account. Timer::Start("tree_building"); Log::Info << "Building query tree...."<< std::endl; std::vector oldFromNewQueries; - typename RAType::Tree queryTree(std::move(querySet), oldFromNewQueries, - leafSize); - Log::Info << "Tree Built." << std::endl; + typename decltype(ra)::Tree queryTree(std::move(querySet), + oldFromNewQueries, + leafSize); + Log::Info << "Tree built." << std::endl; Timer::Stop("tree_building"); arma::Mat neighborsOut; arma::mat distancesOut; - ra->Search(&queryTree, k, neighborsOut, distancesOut); + ra.Search(&queryTree, k, neighborsOut, distancesOut); // Unmap the query points. distances.set_size(distancesOut.n_rows, distancesOut.n_cols); @@ -104,236 +113,12 @@ void BiSearchVisitor::SearchLeaf(RAType* ra) const else { // Search without building a second tree. - ra->Search(querySet, k, neighbors, distances); + ra.Search(querySet, k, neighbors, distances); } } -//! Save parameters for the Train. -template -TrainVisitor::TrainVisitor(arma::mat&& referenceSet, - const size_t leafSize) : - referenceSet(std::move(referenceSet)), - leafSize(leafSize) -{}; - -//! Default Train on the given RAType instance. -template -template class TreeType> -void TrainVisitor::operator()(RATypeT* ra) const -{ - if (ra) - return ra->Train(std::move(referenceSet)); - throw std::runtime_error("no rank-approximate search model initialized"); -} - -//! Train on the given RAType specialized for KDTrees. -template -void TrainVisitor::operator()(RATypeT* ra) const -{ - if (ra) - return TrainLeaf(ra); - throw std::runtime_error("no rank-approximate search model initialized"); -} - -//! Train on the given RAType specialized for Octrees. -template -void TrainVisitor::operator()(RATypeT* ra) const -{ - if (ra) - return TrainLeaf(ra); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Train on the given RAType considering the leafSize. -template -template -void TrainVisitor::TrainLeaf(RAType* ra) const -{ - // Build tree, if necessary - if (ra->Naive()) - { - ra->Train(std::move(referenceSet)); - } - else - { - std::vector oldFromNewReferences; - typename RAType::Tree* tree = - new typename RAType::Tree(std::move(referenceSet), oldFromNewReferences, - leafSize); - ra->Train(tree); - - // Give the model ownership of the tree and the mappings. - ra->treeOwner = true; - ra->oldFromNewReferences = std::move(oldFromNewReferences); - } -} - -//! Exposes the SingleSampleLimit() method of the given RAType. -template -size_t& SingleSampleLimitVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->SingleSampleLimit(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Exposes the FirstLeafExact() method of the given RAType. -template -bool& FirstLeafExactVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->FirstLeafExact(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Exposes the SampleAtLeaves() method of the given RAType. -template -bool& SampleAtLeavesVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->SampleAtLeaves(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! Exposes the Alpha() method of the given RAType instance. -template -double& AlphaVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Alpha(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the Tau() method of the given RAType instance. -template -double& TauVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Tau(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the SingleMode() method of the given RAType. -template -bool& SingleModeVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->SingleMode(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the referenceSet of the given RAType. -template -const arma::mat& ReferenceSetVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->ReferenceSet(); - throw std::runtime_error("no rank-approximate model is initialized"); -} - -//! Exposes the Naive() method of the given RAType instance. -template -bool& NaiveVisitor::operator()(RAType* ra) const -{ - if (ra) - return ra->Naive(); - throw std::runtime_error("no rank-approximate search model is initialized"); -} - -//! For cleaning memory -template -void DeleteVisitor::operator()(RSType* rs) const -{ - if (rs) - delete rs; -} - -template -RAModel::RAModel(const TreeTypes treeType, const bool randomBasis) : - treeType(treeType), - leafSize(20), - randomBasis(randomBasis) -{ - // Nothing to do. -} - -// Copy constructor. -template -RAModel::RAModel(const RAModel& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(other.q), - raSearch(other.raSearch) -{ - // Nothing to do. -} - -// Move constructor. -template -RAModel::RAModel(RAModel&& other) : - treeType(other.treeType), - leafSize(other.leafSize), - randomBasis(other.randomBasis), - q(std::move(other.q)), - raSearch(std::move(other.raSearch)) -{ - // Clear other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.randomBasis = false; - other.raSearch = decltype(other.raSearch)(); -} - -// Copy operator. -template -RAModel& RAModel::operator=(const RAModel& other) -{ - // Clear current model. - boost::apply_visitor(DeleteVisitor(), raSearch); - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = other.q; - raSearch = other.raSearch; - - return *this; -} - -template -RAModel& RAModel::operator=(RAModel&& other) -{ - boost::apply_visitor(DeleteVisitor(), raSearch); - - treeType = other.treeType; - leafSize = other.leafSize; - randomBasis = other.randomBasis; - q = std::move(other.q); - raSearch = std::move(other.raSearch); - - // Reset other model. - other.treeType = TreeTypes::KD_TREE; - other.leafSize = 20; - other.randomBasis = false; - other.raSearch = decltype(other.raSearch)(); - - return *this; -} - -// Clean memory, if necessary -template -RAModel::~RAModel() -{ - boost::apply_visitor(DeleteVisitor(), raSearch); -} - -template template -void RAModel::serialize(Archive& ar, - const uint32_t /* version */) +void RAModel::serialize(Archive& ar, const uint32_t /* version */) { ar(CEREAL_NVP(treeType)); ar(CEREAL_NVP(randomBasis)); @@ -341,282 +126,81 @@ void RAModel::serialize(Archive& ar, // This should never happen, but just in case, be clean with memory. if (cereal::is_loading()) - { - boost::apply_visitor(DeleteVisitor(), raSearch); - } - - // We only need to serialize one of the kRANN objects. - ar(CEREAL_VARIANT_POINTER(raSearch)); -} - -template -const arma::mat& RAModel::Dataset() const -{ - return boost::apply_visitor(ReferenceSetVisitor(), raSearch); -} - -template -bool RAModel::Naive() const -{ - return boost::apply_visitor(NaiveVisitor(), raSearch); -} - -template -bool& RAModel::Naive() -{ - return boost::apply_visitor(NaiveVisitor(), raSearch); -} - -template -bool RAModel::SingleMode() const -{ - return boost::apply_visitor(SingleModeVisitor(), raSearch); -} - -template -bool& RAModel::SingleMode() -{ - return boost::apply_visitor(SingleModeVisitor(), raSearch); -} - -template -double RAModel::Tau() const -{ - return boost::apply_visitor(TauVisitor(), raSearch); -} - -template -double& RAModel::Tau() -{ - return boost::apply_visitor(TauVisitor(), raSearch); -} - -template -double RAModel::Alpha() const -{ - return boost::apply_visitor(AlphaVisitor(), raSearch); -} - -template -double& RAModel::Alpha() -{ - return boost::apply_visitor(AlphaVisitor(), raSearch); -} - -template -bool RAModel::SampleAtLeaves() const -{ - return boost::apply_visitor(SampleAtLeavesVisitor(), raSearch); -} - -template -bool& RAModel::SampleAtLeaves() -{ - return boost::apply_visitor(SampleAtLeavesVisitor(), raSearch); -} - -template -bool RAModel::FirstLeafExact() const -{ - return boost::apply_visitor(FirstLeafExactVisitor(), raSearch); -} - -template -bool& RAModel::FirstLeafExact() -{ - return boost::apply_visitor(FirstLeafExactVisitor(), raSearch); -} - -template -size_t RAModel::SingleSampleLimit() const -{ - return boost::apply_visitor(SingleSampleLimitVisitor(), raSearch); -} - -template -size_t& RAModel::SingleSampleLimit() -{ - return boost::apply_visitor(SingleSampleLimitVisitor(), raSearch); -} - -template -size_t RAModel::LeafSize() const -{ - return leafSize; -} - -template -size_t& RAModel::LeafSize() -{ - return leafSize; -} - -template -typename RAModel::TreeTypes RAModel::TreeType() const -{ - return treeType; -} - -template -typename RAModel::TreeTypes& RAModel::TreeType() -{ - return treeType; -} - -template -bool RAModel::RandomBasis() const -{ - return randomBasis; -} - -template -bool& RAModel::RandomBasis() -{ - return randomBasis; -} - -template -void RAModel::BuildModel(arma::mat&& referenceSet, - const size_t leafSize, - const bool naive, - const bool singleMode) -{ - // Initialize random basis, if necessary. - if (randomBasis) - { - Log::Info << "Creating random basis..." << std::endl; - math::RandomBasis(q, referenceSet.n_rows); - } - - // Clean memory, if necessary. - boost::apply_visitor(DeleteVisitor(), raSearch); - - this->leafSize = leafSize; - - if (randomBasis) - referenceSet = q * referenceSet; - - if (!naive) - { - Timer::Start("tree_building"); - Log::Info << "Building reference tree..." << std::endl; - } + InitializeModel(false, false); // Values will be overwritten. + // Avoid polymorphic serialization by explicitly serializing the correct type. switch (treeType) { case KD_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + LeafSizeRAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case COVER_TREE: - raSearch = new RAType(naive, - singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_STAR_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case X_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case HILBERT_R_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_PLUS_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case R_PLUS_PLUS_TREE: - raSearch = new RAType(naive, - singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case UB_TREE: - raSearch = new RAType(naive, singleMode); - break; + { + RAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } case OCTREE: - raSearch = new RAType(naive, singleMode); - break; - } - - TrainVisitor tn(std::move(referenceSet), leafSize); - boost::apply_visitor(tn, raSearch); - - if (!naive) - { - Timer::Stop("tree_building"); - Log::Info << "Tree built." << std::endl; - } -} - -template -void RAModel::Search(arma::mat&& querySet, - const size_t k, - arma::Mat& neighbors, - arma::mat& distances) -{ - // Apply the random basis if necessary. - if (randomBasis) - querySet = q * querySet; - - Log::Info << "Searching for " << k << " approximate nearest neighbors with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; - else if (!Naive()) - Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; - else - Log::Info << "brute-force (naive) rank-approximate search..."; - Log::Info << std::endl; - - BiSearchVisitor search(querySet, k, neighbors, distances, - leafSize); - boost::apply_visitor(search, raSearch); -} - -template -void RAModel::Search(const size_t k, - arma::Mat& neighbors, - arma::mat& distances) -{ - Log::Info << "Searching for " << k << " approximate nearest neighbors with "; - if (!Naive() && !SingleMode()) - Log::Info << "dual-tree rank-approximate " << TreeName() << " search..."; - else if (!Naive()) - Log::Info << "single-tree rank-approximate " << TreeName() << " search..."; - else - Log::Info << "brute-force (naive) rank-approximate search..."; - Log::Info << std::endl; - - MonoSearchVisitor search(k, neighbors, distances); - boost::apply_visitor(search, raSearch); -} - -template -std::string RAModel::TreeName() const -{ - switch (treeType) - { - case KD_TREE: - return "kd-tree"; - case COVER_TREE: - return "cover tree"; - case R_TREE: - return "R tree"; - case R_STAR_TREE: - return "R* tree"; - case X_TREE: - return "X tree"; - case HILBERT_R_TREE: - return "Hilbert R tree"; - case R_PLUS_TREE: - return "R+ tree"; - case R_PLUS_PLUS_TREE: - return "R++ tree"; - case UB_TREE: - return "UB tree"; - case OCTREE: - return "octree"; - default: - return "unknown tree"; + { + LeafSizeRAWrapper& typedSearch = + dynamic_cast&>(*raSearch); + ar(CEREAL_NVP(typedSearch)); + break; + } } } diff --git a/src/mlpack/methods/rann/ra_search.hpp b/src/mlpack/methods/rann/ra_search.hpp index da3f61c48d..634260c213 100644 --- a/src/mlpack/methods/rann/ra_search.hpp +++ b/src/mlpack/methods/rann/ra_search.hpp @@ -39,8 +39,10 @@ namespace mlpack { namespace neighbor { // Forward declaration. -template -class TrainVisitor; +template class TreeType> +class LeafSizeRAWrapper; /** * The RASearch class: This class provides a generic manner to perform @@ -394,8 +396,7 @@ class RASearch MetricType metric; //! For access to mappings when building models. - template - friend class TrainVisitor; + friend class LeafSizeRAWrapper; }; // class RASearch } // namespace neighbor diff --git a/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp b/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp index b52110d744..82ce15e77e 100644 --- a/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp +++ b/src/mlpack/methods/reinforcement_learning/q_networks/categorical_dqn.hpp @@ -53,7 +53,7 @@ class CategoricalDQN /** * Default constructor. */ - CategoricalDQN() : network(), isNoisy(false) + CategoricalDQN() : network(), isNoisy(false), atomSize(0), vMin(0.0), vMax(0.0) { /* Nothing to do here. */ } /** diff --git a/src/mlpack/tests/CMakeLists.txt b/src/mlpack/tests/CMakeLists.txt index 48d05959cf..e6dd629266 100644 --- a/src/mlpack/tests/CMakeLists.txt +++ b/src/mlpack/tests/CMakeLists.txt @@ -26,7 +26,6 @@ add_executable(mlpack_test cv_test.cpp dbscan_test.cpp dcgan_test.cpp - decision_stump_test.cpp decision_tree_test.cpp det_test.cpp distribution_test.cpp @@ -99,8 +98,8 @@ add_executable(mlpack_test reward_clipping_test.cpp rl_components_test.cpp scaling_test.cpp - serialization_catch.cpp - serialization_catch.hpp + serialization.cpp + serialization.hpp serialization_test.cpp sfinae_test.cpp softmax_regression_test.cpp @@ -129,7 +128,6 @@ add_executable(mlpack_test main_tests/bayesian_linear_regression_test.cpp main_tests/cf_test.cpp main_tests/dbscan_test.cpp - main_tests/decision_stump_test.cpp main_tests/decision_tree_test.cpp main_tests/det_test.cpp main_tests/emst_test.cpp @@ -179,7 +177,6 @@ add_executable(mlpack_test target_link_libraries(mlpack_test mlpack ${ARMADILLO_LIBRARIES} - ${BOOST_LIBRARIES} ${COMPILER_SUPPORT_LIBRARIES} ) @@ -203,4 +200,4 @@ add_custom_command(TARGET mlpack_test WORKING_DIRECTORY ${PROJECT_BINARY_DIR} ) -add_test(NAME "catch_test" COMMAND mlpack_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) \ No newline at end of file +add_test(NAME "catch_test" COMMAND mlpack_test WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/src/mlpack/tests/README.md b/src/mlpack/tests/README.md index ea61fe08a8..a8f9fee22d 100644 --- a/src/mlpack/tests/README.md +++ b/src/mlpack/tests/README.md @@ -43,4 +43,4 @@ To run a single test, you can explicitly provide the name of the test, for examp `./bin/mlpack_test BinaryClassificationMetricsTest` -Catch2 provides many other features like filter, checkout the [Catch2 reference section](docs/Readme.md#top) - for more details. +Catch2 provides many other features like filter, checkout the [Catch2 reference section](https://github.com/catchorg/Catch2/blob/devel/docs/Readme.md#top) - for more details. diff --git a/src/mlpack/tests/activation_functions_test.cpp b/src/mlpack/tests/activation_functions_test.cpp index d88c0a5109..5e4c5aa217 100644 --- a/src/mlpack/tests/activation_functions_test.cpp +++ b/src/mlpack/tests/activation_functions_test.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "catch.hpp" @@ -1134,3 +1135,24 @@ TEST_CASE("SoftminFunctionTest", "[ActivationFunctionsTest]") CheckSoftminDerivativeCorrect(activationData, desiredDerivatives); } + +/** + * Basic test of the Hard Swish function. + */ +TEST_CASE("HardSwishFunctionTest", "[ActivationFunctionsTest]") +{ + // Randomly generated data. + const arma::colvec activationData("3.6544 -1.9714 -5.2277 1.5448 2.1164"); + + // Hand-calculated values. + const arma::colvec desiredActivations("3.6544 -0.3379636 0.0 \ + 1.1701345 1.8047248"); + + // Hand-calculated values. + const arma::colvec desiredDerivatives("1.0 0.38734546 0.5 \ + 0.89004483 1.1015749"); + + CheckActivationCorrect(activationData, desiredActivations); + CheckDerivativeCorrect + (desiredActivations, desiredDerivatives); +} diff --git a/src/mlpack/tests/adaboost_test.cpp b/src/mlpack/tests/adaboost_test.cpp index 89e6699c76..cccf6f42d8 100644 --- a/src/mlpack/tests/adaboost_test.cpp +++ b/src/mlpack/tests/adaboost_test.cpp @@ -12,7 +12,7 @@ #include #include -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "test_catch_tools.hpp" #include "catch.hpp" diff --git a/src/mlpack/tests/aknn_test.cpp b/src/mlpack/tests/aknn_test.cpp index 37b5e820da..9358b57de7 100644 --- a/src/mlpack/tests/aknn_test.cpp +++ b/src/mlpack/tests/aknn_test.cpp @@ -370,14 +370,13 @@ TEST_CASE("AKNNModelTest", "[AKNNTest]") // We only have std::move() constructors so make a copy of our data. arma::mat referenceCopy(referenceData); arma::mat queryCopy(queryData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE, - 0.05); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE, 0.05); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE, 0.05); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE, 0.05); if (j == 2) - models[i].BuildModel(std::move(referenceCopy), 20, NAIVE_MODE); + models[i].BuildModel(std::move(referenceCopy), NAIVE_MODE); arma::Mat neighborsApprox; arma::mat distancesApprox; @@ -448,12 +447,11 @@ TEST_CASE("AKNNModelMonochromaticTest", "[AKNNTest]") { // We only have a std::move() constructor... so copy the data. arma::mat referenceCopy(referenceData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE, - 0.05); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE, 0.05); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE, 0.05); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE, 0.05); arma::Mat neighborsApprox; arma::mat distancesApprox; diff --git a/src/mlpack/tests/ann_layer_test.cpp b/src/mlpack/tests/ann_layer_test.cpp index 63da44b2ee..dbb5266798 100644 --- a/src/mlpack/tests/ann_layer_test.cpp +++ b/src/mlpack/tests/ann_layer_test.cpp @@ -19,18 +19,64 @@ #include #include #include -#include +#include #include #include #include "test_catch_tools.hpp" #include "catch.hpp" #include "ann_test_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::ann; +// network1 should be allocated with `new`, and trained on some data. +template +void CheckRNNCopyFunction(ModelType* network1, + MatType& trainData, + MatType& trainLabels, + const size_t maxEpochs) +{ + arma::cube predictions1; + arma::cube predictions2; + ens::StandardSGD opt(0.1, 1, maxEpochs * trainData.n_slices, -100, false); + + network1->Train(trainData, trainLabels, opt); + network1->Predict(trainData, predictions1); + + RNN<> network2 = *network1; + delete network1; + + // Deallocating all of network1's memory, so that network2 does not use any + // of that memory. + network2.Predict(trainData, predictions2); + CheckMatrices(predictions1, predictions2); +} + +// network1 should be allocated with `new`, and trained on some data. +template +void CheckRNNMoveFunction(ModelType* network1, + MatType& trainData, + MatType& trainLabels, + const size_t maxEpochs) +{ + arma::cube predictions1; + arma::cube predictions2; + ens::StandardSGD opt(0.1, 1, maxEpochs * trainData.n_slices, -100, false); + + network1->Train(trainData, trainLabels, opt); + network1->Predict(trainData, predictions1); + + RNN<> network2(std::move(*network1)); + delete network1; + + // Deallocating all of network1's memory, so that network2 does not use any + // of that memory. + network2.Predict(trainData, predictions2); + CheckMatrices(predictions1, predictions2); +} + /** * Simple add module test. */ @@ -89,7 +135,7 @@ TEST_CASE("GradientAddLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -417,7 +463,7 @@ TEST_CASE("GradientLinearLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -594,7 +640,7 @@ TEST_CASE("GradientNoisyLinearLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -697,7 +743,7 @@ TEST_CASE("GradientLinearNoBiasLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -743,7 +789,7 @@ TEST_CASE("JacobianNegativeLogLikelihoodLayerTest", "[ANNLayerTest]") init.Initialize(input, inputElements, 1); arma::mat target(1, 1); - target(0) = math::RandInt(1, inputElements - 1); + target(0) = math::RandInt(0, inputElements - 2); double error = JacobianPerformanceTest(module, input, target); REQUIRE(error <= 1e-5); @@ -798,7 +844,7 @@ TEST_CASE("GradientFlexibleReLULayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(2, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, RandomInitialization>( NegativeLogLikelihood<>(), RandomInitialization(0.1, 0.5)); @@ -851,6 +897,39 @@ TEST_CASE("JacobianMultiplyConstantLayerTest", "[ANNLayerTest]") } } +/** + * Check whether copying and moving network with MultiplyConstant is working or + * not. + */ +TEST_CASE("CheckCopyMoveMultiplyConstantTest", "[ANNLayerTest]") +{ + arma::mat input(2, 1000); + input.randu(); + + arma::mat output1; + arma::mat output2; + arma::mat output3; + arma::mat output4; + + MultiplyConstant<> *module1 = new MultiplyConstant<>(3.0); + module1->Forward(input, output1); + + MultiplyConstant<> module2 = *module1; + delete module1; + + module2.Forward(input, output2); + CheckMatrices(output1, output2); + + MultiplyConstant<> *module3 = new MultiplyConstant<>(3.0); + module3->Forward(input, output3); + + MultiplyConstant<> module4(std::move(*module3)); + delete module3; + + module4.Forward(input, output4); + CheckMatrices(output3, output4); +} + /** * Jacobian HardTanH module test. */ @@ -977,7 +1056,7 @@ TEST_CASE("LSTMRrhoTest", "[ANNLayerTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. @@ -1017,7 +1096,7 @@ TEST_CASE("GradientLSTMLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(1, 1, 5)), - target(arma::ones(1, 1, 5)) + target(arma::zeros(1, 1, 5)) { const size_t rho = 5; @@ -1082,7 +1161,7 @@ TEST_CASE("FastLSTMRrhoTest", "[ANNLayerTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. @@ -1122,7 +1201,7 @@ TEST_CASE("GradientFastLSTMLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(1, 1, 5)), - target(arma::ones(1, 1, 5)) + target(arma::zeros(1, 1, 5)) { const size_t rho = 5; @@ -1183,6 +1262,74 @@ TEST_CASE("FastLSTMLayerParametersTest", "[ANNLayerTest]") REQUIRE(layer1.Rho() == layer2.Rho()); } +/** + * Check whether copying and moving network with FastLSTM is working or not. + */ +TEST_CASE("CheckCopyMoveFastLSTMTest", "[ANNLayerTest]") +{ + arma::cube input = arma::randu(1, 1, 5); + arma::cube target = arma::ones(1, 1, 5); + const size_t rho = 5; + + RNN > *model1 = + new RNN >(rho); + model1->Predictors() = input; + model1->Responses() = target; + model1->Add >(); + model1->Add >(1, 10); + model1->Add >(10, 3, rho); + model1->Add >(); + + RNN > *model2 = + new RNN >(rho); + model2->Predictors() = input; + model2->Responses() = target; + model2->Add >(); + model2->Add >(1, 10); + model2->Add >(10, 3, rho); + model2->Add >(); + + // Check whether copy constructor is working or not. + CheckRNNCopyFunction<>(model1, input, target, 1); + + // Check whether move constructor is working or not. + CheckRNNMoveFunction<>(model2, input, target, 1); +} + +/** + * Check whether copying and moving network with LSTM is working or not. + */ +TEST_CASE("CheckCopyMoveLSTMTest", "[ANNLayerTest]") +{ + arma::cube input = arma::randu(1, 1, 5); + arma::cube target = arma::ones(1, 1, 5); + const size_t rho = 5; + + RNN > *model1 = + new RNN >(rho); + model1->Predictors() = input; + model1->Responses() = target; + model1->Add >(); + model1->Add >(1, 10); + model1->Add >(10, 3, rho); + model1->Add >(); + + RNN > *model2 = + new RNN >(rho); + model2->Predictors() = input; + model2->Responses() = target; + model2->Add >(); + model2->Add >(1, 10); + model2->Add >(10, 3, rho); + model2->Add >(); + + // Check whether copy constructor is working or not. + CheckRNNCopyFunction<>(model1, input, target, 1); + + // Check whether move constructor is working or not. + CheckRNNMoveFunction<>(model2, input, target, 1); +} + /** * Testing the overloaded Forward() of the LSTM layer, for retrieving the cell * state. Besides output, the overloaded function provides read access to cell @@ -1391,7 +1538,7 @@ TEST_CASE("GradientGRULayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(1, 1, 5)), - target(arma::ones(1, 1, 5)) + target(arma::zeros(1, 1, 5)) { const size_t rho = 5; @@ -1631,7 +1778,7 @@ TEST_CASE("GradientConcatLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -1699,7 +1846,7 @@ TEST_CASE("GradientConcatenateLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -1800,7 +1947,7 @@ TEST_CASE("GradientLookupLayerTest", "[ANNLayerTest]") target(targetWord, i) = 1; } - model = new FFN, GlorotInitialization>(); + model = new FFN, GlorotInitialization>(BCELoss<>(1e-10, false)); model->Predictors() = input; model->Responses() = target; model->Add >(vocabSize, embeddingSize); @@ -1822,7 +1969,7 @@ TEST_CASE("GradientLookupLayerTest", "[ANNLayerTest]") arma::mat& Parameters() { return model->Parameters(); } - FFN, GlorotInitialization>* model; + FFN, GlorotInitialization>* model; arma::mat input, target; const size_t seqLength = 10; @@ -2009,9 +2156,9 @@ TEST_CASE("BilinearInterpolationLayerParametersTest", "[ANNLayerTest]") TEST_CASE("BatchNormTest", "[ANNLayerTest]") { arma::mat input, output; - input << 5.1 << 3.5 << 1.4 << arma::endr - << 4.9 << 3.0 << 1.4 << arma::endr - << 4.7 << 3.2 << 1.3 << arma::endr; + input = { { 5.1, 3.5, 1.4 }, + { 4.9, 3.0, 1.4 }, + { 4.7, 3.2, 1.3 } }; // BatchNorm layer with average parameter set to true. BatchNorm<> model(input.n_rows); @@ -2027,9 +2174,9 @@ TEST_CASE("BatchNormTest", "[ANNLayerTest]") // Value calculates using torch.nn.BatchNorm2d(momentum = None). arma::mat result; - result << 1.1658 << 0.1100 << -1.2758 << arma::endr - << 1.2579 << -0.0699 << -1.1880 << arma::endr - << 1.1737 << 0.0958 << -1.2695 << arma::endr; + result = { { 1.1658, 0.1100, -1.2758 }, + { 1.2579, -0.0699, -1.1880}, + { 1.1737, 0.0958, -1.2695 } }; CheckMatrices(output, result, 1e-1); @@ -2039,35 +2186,27 @@ TEST_CASE("BatchNormTest", "[ANNLayerTest]") // Values calculated using torch.nn.BatchNorm2d(momentum = None). output = model.TrainingMean(); - result << 3.33333333 << arma::endr - << 3.1 << arma::endr - << 3.06666666 << arma::endr; + result = arma::mat({ 3.33333333, 3.1, 3.06666666 }).t(); CheckMatrices(output, result, 1e-1); // Values calculated using torch.nn.BatchNorm2d(). output = model2.TrainingMean(); - result << 0.3333 << arma::endr - << 0.3100 << arma::endr - << 0.3067 << arma::endr; + result = arma::mat({ 0.3333, 0.3100, 0.3067 }).t(); CheckMatrices(output, result, 1e-1); result.clear(); // Values calculated using torch.nn.BatchNorm2d(momentum = None). output = model.TrainingVariance(); - result << 3.4433 << arma::endr - << 3.0700 << arma::endr - << 2.9033 << arma::endr; + result = arma::mat({ 3.4433, 3.0700, 2.9033 }).t(); CheckMatrices(output, result, 1e-1); result.clear(); // Values calculated using torch.nn.BatchNorm2d(). output = model2.TrainingVariance(); - result << 1.2443 << arma::endr - << 1.2070 << arma::endr - << 1.1903 << arma::endr; + result = arma::mat({ 1.2443, 1.2070, 1.1903 }).t(); CheckMatrices(output, result, 1e-1); result.clear(); @@ -2077,9 +2216,9 @@ TEST_CASE("BatchNormTest", "[ANNLayerTest]") model.Forward(input, output); // Values calculated using torch.nn.BatchNorm2d(momentum = None). - result << 0.9521 << 0.0898 << -1.0419 << arma::endr - << 1.0273 << -0.0571 << -0.9702 << arma::endr - << 0.9586 << 0.0783 << -1.0368 << arma::endr; + result = { { 0.9521, 0.0898, -1.0419 }, + { 1.0273, -0.0571, -0.9702 }, + { 0.9586, 0.0783, -1.0368 } }; CheckMatrices(output, result, 1e-1); @@ -2087,9 +2226,10 @@ TEST_CASE("BatchNormTest", "[ANNLayerTest]") model2.Deterministic() = true; model2.Forward(input, output); - result << 4.2731 << 2.8388 << 0.9562 << arma::endr - << 4.1779 << 2.4485 << 0.9921 << arma::endr - << 4.0268 << 2.6519 << 0.9105 << arma::endr; + result = { { 4.2731, 2.8388, 0.9562 }, + { 4.1779, 2.4485, 0.9921 }, + { 4.0268, 2.6519, 0.9105 } }; + CheckMatrices(output, result, 1e-1); } @@ -2106,7 +2246,7 @@ TEST_CASE("GradientBatchNormTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(32, 2048)), - target(arma::ones(1, 2048)) + target(arma::zeros(1, 2048)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -2179,7 +2319,7 @@ TEST_CASE("GradientVirtualBatchNormTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(5, 256)), - target(arma::ones(1, 256)) + target(arma::zeros(1, 256)) { arma::mat referenceBatch = arma::mat(input.memptr(), input.n_rows, 16); @@ -2241,7 +2381,7 @@ TEST_CASE("MiniBatchDiscriminationTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(5, 4)), - target(arma::ones(1, 4)) + target(arma::zeros(1, 4)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -2419,7 +2559,7 @@ TEST_CASE("GradientTransposedConvolutionLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::linspace(0, 35, 36)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, RandomInitialization>(); model->Predictors() = input; @@ -2486,6 +2626,56 @@ TEST_CASE("SimpleMultiplyMergeLayerTest", "[ANNLayerTest]") } } +/** + * Check whether copying and moving network with MultiplyMerge is working or + * not. + */ +TEST_CASE("CheckCopyMoveMultiplyMergeTest", "[ANNLayerTest]") +{ + arma::mat input(10, 1); + input.randu(); + + arma::mat output1; + arma::mat output2; + arma::mat output3; + arma::mat output4; + + const size_t numMergeModules = math::RandInt(2, 10); + + MultiplyMerge<> *module1 = new MultiplyMerge<>(true, false); + for (size_t m = 0; m < numMergeModules; ++m) + { + IdentityLayer<> identityLayer; + identityLayer.Forward(input, identityLayer.OutputParameter()); + + module1->Add >(identityLayer); + } + + module1->Forward(input, output1); + + MultiplyMerge<> module2 = *module1; + delete module1; + + module2.Forward(input, output2); + CheckMatrices(output1, output2); + + MultiplyMerge<> *module3 = new MultiplyMerge<>(true, false); + for (size_t m = 0; m < numMergeModules; ++m) + { + IdentityLayer<> identityLayer; + identityLayer.Forward(input, identityLayer.OutputParameter()); + + module3->Add >(identityLayer); + } + module3->Forward(input, output3); + + MultiplyMerge<> module4(std::move(*module3)); + delete module3; + + module4.Forward(input, output4); + CheckMatrices(output3, output4); +} + /** * Simple Atrous Convolution layer test. */ @@ -2535,7 +2725,7 @@ TEST_CASE("GradientAtrousConvolutionLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::linspace(0, 35, 36)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, RandomInitialization>(); model->Predictors() = input; @@ -2678,30 +2868,30 @@ TEST_CASE("AtrousConvolutionLayerPaddingTest", "[ANNLayerTest]") TEST_CASE("LayerNormTest", "[ANNLayerTest]") { arma::mat input, output; - input << 5.1 << 3.5 << arma::endr - << 4.9 << 3.0 << arma::endr - << 4.7 << 3.2 << arma::endr; + input = { { 5.1, 3.5 }, + { 4.9, 3.0 }, + { 4.7, 3.2 } }; LayerNorm<> model(input.n_rows); model.Reset(); model.Forward(input, output); arma::mat result; - result << 1.2247 << 1.2978 << arma::endr - << 0 << -1.1355 << arma::endr - << -1.2247 << -0.1622 << arma::endr; + result = { { 1.2247, 1.2978 }, + { 0, -1.1355 }, + { -1.2247, -0.1622 } }; CheckMatrices(output, result, 1e-1); result.clear(); output = model.Mean(); - result << 4.9000 << 3.2333 << arma::endr; + result = { 4.9000, 3.2333 }; CheckMatrices(output, result, 1e-1); result.clear(); output = model.Variance(); - result << 0.0267 << 0.0422 << arma::endr; + result = { 0.0267, 0.0422 }; CheckMatrices(output, result, 1e-1); } @@ -2716,7 +2906,7 @@ TEST_CASE("GradientLayerNormTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(10, 256)), - target(arma::ones(1, 256)) + target(arma::zeros(1, 256)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3036,7 +3226,7 @@ TEST_CASE("GradientReparametrizationLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3079,7 +3269,7 @@ TEST_CASE("GradientReparametrizationLayerBetaTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 2)), - target(arma::mat("1 1")) + target(arma::mat("0 0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3234,7 +3424,7 @@ TEST_CASE("GradientHighwayLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(5, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3285,7 +3475,7 @@ TEST_CASE("GradientSequentialLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3335,7 +3525,7 @@ TEST_CASE("GradientWeightNormLayerTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randu(10, 1)), - target(arma::mat("1")) + target(arma::mat("0")) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -3648,6 +3838,51 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") REQUIRE(arma::accu(delta) == 0.0); } +/** + * Simple test for Lp Pooling layer. + */ +TEST_CASE("LpMaxPoolingTestCase", "[ANNLayerTest]") +{ + // For rectangular input to pooling layers. + arma::mat input = arma::mat(8, 1); + arma::mat output; + input.zeros(); + input(0) = input(6) = 30; + input(1) = input(7) = 120; + input(2) = input(4) = 272; + input(3) = input(5) = 315; + // Output-Size should be 1 x 2. + // Square output. + LpPooling<> module1(4, 2, 2, 2, 2); + module1.InputHeight() = 2; + module1.InputWidth() = 4; + module1.Forward(input, output); + // Calculated using torch.nn.LPPool2d(). + REQUIRE(arma::accu(output) - 706.0 == Approx(0.0).margin(2e-5)); + REQUIRE(output.n_elem == 2); + + // For Square input. + input = arma::mat(16, 1); + input.zeros(); + input(0) = 4; + input(1) = 3; + input(3) = 12; + input(7) = 35; + input(8) = 6; + input(11) = 7; + input(12) = 8; + input(15) = 24; + // Output-Size should be 2 x 2. + // Square output. + LpPooling<> module3(2, 2, 2, 2, 2); + module3.InputHeight() = 4; + module3.InputWidth() = 4; + module3.Forward(input, output); + // Calculated using torch.nn.LPPool2d(). + REQUIRE(arma::accu(output) - 77.0 == Approx(0.0).margin(2e-5)); + REQUIRE(output.n_elem == 4); +} + /** * Simple test for Max Pooling layer. */ @@ -4007,26 +4242,24 @@ TEST_CASE("BatchNormWithMinBatchesTest", "[ANNLayerTest]") // The input test matrix is of the form 3 x 2 x 4 x 1 where // number of images are 3 and number of feature maps are 2. - input = arma::mat(8, 3); - input << 1 << 446 << 42 << arma::endr - << 2 << 16 << 63 << arma::endr - << 3 << 13 << 63 << arma::endr - << 4 << 21 << 21 << arma::endr - << 1 << 13 << 11 << arma::endr - << 32 << 45 << 42 << arma::endr - << 22 << 16 << 63 << arma::endr - << 32 << 13 << 42 << arma::endr; + input = { { 1, 446, 42 }, + { 2, 16, 63 }, + { 3, 13, 63 }, + { 4, 21, 21 }, + { 1, 13, 11 }, + { 32, 45, 42 }, + { 22, 16, 63 }, + { 32, 13, 42 } }; // Output calculated using torch.nn.BatchNorm2d(). - result = arma::mat(8, 3); - result << -0.4786 << 3.2634 << -0.1338 << arma::endr - << -0.4702 << -0.3525 << 0.0427 << arma::endr - << -0.4618 << -0.3777 << 0.0427 << arma::endr - << -0.4534 << -0.3104 << -0.3104 << arma::endr - << -1.5429 << -0.8486 << -0.9643 << arma::endr - << 0.2507 << 1.0029 << 0.8293 << arma::endr - << -0.3279 << -0.675 << 2.0443 << arma::endr - << 0.2507 << -0.8486 << 0.8293 << arma::endr; + result = { { -0.4786, 3.2634, -0.1338 }, + { -0.4702, -0.3525, 0.0427 }, + { -0.4618, -0.3777, 0.0427 }, + { -0.4534, -0.3104, -0.3104 }, + { -1.5429, -0.8486, -0.9643 }, + { 0.2507, 1.0029, 0.8293 }, + { -0.3279, -0.675, 2.0443 }, + { 0.2507 , -0.8486 , 0.8293 } }; // Check correctness of batch normalization. BatchNorm<> module1(2, 1e-5, false, 0.1); @@ -4073,15 +4306,14 @@ TEST_CASE("BatchNormWithMinBatchesTest", "[ANNLayerTest]") module1.Forward(input, deterministicOutput); result.clear(); - result = arma::mat(8, 3); - result << -0.12195 << 11.20426 << 0.92158 << arma::endr - << -0.0965 << 0.259824 << 1.4560 << arma::endr - << -0.071054 << 0.183567 << 1.45607 << arma::endr - << -0.045601<< 0.3870852 << 0.38708 << arma::endr - << -0.305288 << 1.7683 << 1.4227 << arma::endr - << 5.05166 << 7.29812<< 6.7797 << arma::endr - << 3.323614 << 2.2867 << 10.4086 << arma::endr - << 5.05166 << 1.7683 << 6.7797 << arma::endr; + result = { { -0.12195, 11.20426, 0.92158 }, + { -0.0965, 0.259824, 1.4560 }, + { -0.071054, 0.183567, 1.45607 }, + { -0.045601, 0.3870852, 0.38708 }, + { -0.305288, 1.7683, 1.4227 }, + { 5.05166, 7.29812, 6.7797 }, + { 3.323614, 2.2867, 10.4086 }, + { 5.05166, 1.7683, 6.7797 } }; CheckMatrices(result, deterministicOutput, 1e-1); @@ -4094,20 +4326,19 @@ TEST_CASE("BatchNormWithMinBatchesTest", "[ANNLayerTest]") // The input test matrix is of the form 2 x 2 x 3 x 1 where // number of images are 2 and number of feature maps are 2. - input = arma::mat(6, 2); - input << 12 << 443 << arma::endr - << 134 << 45 << arma::endr - << 11 << 13 << arma::endr - << 14 << 55 << arma::endr - << 110 << 4 << arma::endr - << 1 << 45 << arma::endr; + input = { { 12, 443 }, + { 134, 45 }, + { 11, 13 }, + { 14, 55 }, + { 110, 4 }, + { 1, 45 } }; - result << -0.629337 << 2.14791 << arma::endr - << 0.156797 << -0.416694 << arma::endr - << -0.63578 << -0.622893 << arma::endr - << -0.637481 << 0.4440386 << arma::endr - << 1.894857 << -0.901267 << arma::endr - << -0.980402 << 0.180253 << arma::endr; + result = { { -0.629337, 2.14791 }, + { 0.156797, -0.416694 }, + { -0.63578, -0.622893 }, + { -0.637481, 0.4440386 }, + { 1.894857, -0.901267 }, + { -0.980402, 0.180253 } }; module1.Forward(input, output); CheckMatrices(result, output, 1e-3); @@ -4143,12 +4374,12 @@ TEST_CASE("BatchNormWithMinBatchesTest", "[ANNLayerTest]") module1.Forward(input, deterministicOutput); result.clear(); - result << -0.06388436 << 6.524754114 << arma::endr - << 1.799655281 << 0.44047968 << arma::endr - << -0.07913291 << -0.04784981 << arma::endr - << 0.5405045 << 3.4210097 << arma::endr - << 7.2851023 << -0.1620577 << arma::endr - << -0.37282639 << 2.7184474 << arma::endr; + result = { { -0.06388436, 6.524754114 }, + { 1.799655281, 0.44047968 }, + { -0.07913291, -0.04784981 }, + { 0.5405045, 3.4210097 }, + { 7.2851023, -0.1620577 }, + { -0.37282639, 2.7184474 } }; // Calculated using torch.nn.BatchNorm2d(). CheckMatrices(result, deterministicOutput, 1e-1); @@ -4168,7 +4399,7 @@ TEST_CASE("GradientBatchNormWithMiniBatchesTest", "[ANNLayerTest]") { GradientFunction() : input(arma::randn(16, 1024)), - target(arma::ones(1, 1024)) + target(arma::zeros(1, 1024)) { model = new FFN, NguyenWidrowInitialization>(); model->Predictors() = input; @@ -4215,15 +4446,14 @@ TEST_CASE("ConvolutionLayerTestCase", "[ANNLayerTest]") // The input test matrix is of the form 3 x 2 x 4 x 1 where // number of images are 3 and number of feature maps are 2. - input = arma::mat(8, 3); - input << 1 << 446 << 42 << arma::endr - << 2 << 16 << 63 << arma::endr - << 3 << 13 << 63 << arma::endr - << 4 << 21 << 21 << arma::endr - << 1 << 13 << 11 << arma::endr - << 32 << 45 << 42 << arma::endr - << 22 << 16 << 63 << arma::endr - << 32 << 13 << 42 << arma::endr; + input = { { 1, 446, 42 }, + { 2, 16, 63 }, + { 3, 13, 63 }, + { 4, 21, 21 }, + { 1, 13, 11 }, + { 32, 45, 42 }, + { 22, 16 , 63 }, + { 32, 13 , 42 } }; Convolution<> layer(2, 4, 1, 1, 1, 1, 0, 0, 4, 1); layer.Reset(); @@ -4389,60 +4619,51 @@ TEST_CASE("SpatialDropoutLayerTest", "[ANNLayerTest]") SpatialDropout<> module(3, 0.2); // Input is a batch of 2 images, each of size (2,2) and having 4 channels. - input << 0.4963 << 0.0885 << 0.7682 << 0.1320 << 0.3074 << 0.4901 << 0.6341 - << 0.8964 << 0.4556 << 0.3489 << 0.6323 << 0.4017 << arma::endr; + input = { 0.4963, 0.0885, 0.7682, 0.1320, 0.3074, 0.4901, 0.6341, 0.8964, + 0.4556, 0.3489, 0.6323, 0.4017 }; - gy << 1 << 3 << 2 << 4 << 5 << 7 << 6 << 8 - << 9 << 11 << 10 << 12 << arma::endr; + gy = { 1, 3, 2, 4, 5, 7, 6, 8, 9, 11, 10, 12 }; // Following values have been calculated using torch.nn.Dropout2d(p=0.2). - temp << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 - << 0 << 0 << 0 << 0 << arma::endr; + temp = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; outputsExpected.row(0) = temp; - temp << 0 << 0 << 0 << 0 << 0.3842 << 0.6126 << 0.7926 << 1.1205 - << 0.5695 << 0.4361 << 0.7904 << 0.5021 << arma::endr; + temp = { 0, 0, 0, 0, 0.3842, 0.6126, 0.7926, 1.1205, 0.5695, 0.4361, 0.7904, + 0.5021 }; outputsExpected.row(1) = temp; - temp << 0.6204 << 0.1106 << 0.9603 << 0.1650 << 0 << 0 << 0 << 0 - << 0.5695 << 0.4361 << 0.7904 << 0.5021 << arma::endr; + temp = { 0.6204, 0.1106, 0.9603, 0.1650, 0, 0, 0, 0, 0.5695, 0.4361, + 0.7904, 0.5021 }; outputsExpected.row(2) = temp; - temp << 0.6204 << 0.1106 << 0.9603 << 0.1650 << 0.3842 << 0.6126 - << 0.7926 << 1.1205 << 0 << 0 << 0 << 0 << arma::endr; + temp = { 0.6204, 0.1106, 0.9603, 0.1650, 0.3842, 0.6126, 0.7926, 1.1205, 0, + 0, 0, 0 }; outputsExpected.row(3) = temp; - temp << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 - << 0.5695 << 0.4361 << 0.7904 << 0.5021 << arma::endr; + temp = { 0, 0, 0, 0, 0, 0, 0, 0, 0.5695, 0.4361, 0.7904, 0.5021 }; outputsExpected.row(4) = temp; - temp << 0 << 0 << 0 << 0 << 0.3842 << 0.6126 << 0.7926 << 1.1205 - << 0 << 0 << 0 << 0 << arma::endr; + temp = { 0, 0, 0, 0, 0.3842, 0.6126, 0.7926, 1.1205, 0, 0, 0, 0 }; outputsExpected.row(5) = temp; - temp << 0.6204 << 0.1106 << 0.9603 << 0.1650 << 0 << 0 << 0 << 0 - << 0 << 0 << 0 << 0 << arma::endr; + temp = { 0.6204, 0.1106, 0.9603, 0.1650, 0, 0, 0, 0, 0, 0, 0, 0 }; outputsExpected.row(6) = temp; - temp << 0.6204 << 0.1106 << 0.9603 << 0.1650 << 0.3842 << 0.6126 << 0.7926 - << 1.1205 << 0.5695 << 0.4361 << 0.7904 << 0.5021 << arma::endr; + temp = { 0.6204, 0.1106, 0.9603, 0.1650, 0.3842, 0.6126, 0.7926, 1.1205, + 0.5695, 0.4361, 0.7904, 0.5021 }; outputsExpected.row(7) = temp; - temp << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 - << 0 << 0 << 0 << 0 << arma::endr; + temp = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; gsExpected.row(0) = temp; - temp << 0 << 0 << 0 << 0 << 6.2500 << 8.7500 << 7.5000 << 10.0000 - << 11.2500 << 13.7500 << 12.5000 << 15.0000 << arma::endr; + temp = { 0, 0, 0, 0, 6.2500, 8.7500, 7.5000, 10.0000, 11.2500, 13.7500, + 12.5000, 15.0000 }; gsExpected.row(1) = temp; - temp << 1.2500 << 3.7500 << 2.5000 << 5.0000 << 0 << 0 << 0 << 0 - << 11.2500 << 13.7500 << 12.5000 << 15.0000 << arma::endr; + temp = { 1.2500, 3.7500, 2.5000, 5.0000, 0, 0, 0, 0, 11.2500, 13.7500, + 12.5000, 15.0000 }; gsExpected.row(2) = temp; - temp << 1.2500 << 3.7500 << 2.5000 << 5.0000 << 6.2500 << 8.7500 - << 7.5000 << 10.0000 << 0 << 0 << 0 << 0 << arma::endr; + temp = { 1.2500, 3.7500, 2.5000, 5.0000, 6.2500, 8.7500, 7.5000, 10.0000, 0, + 0, 0, 0 }; gsExpected.row(3) = temp; - temp << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 - << 11.2500 << 13.7500 << 12.5000 << 15.0000 << arma::endr; + temp = { 0, 0, 0, 0, 0, 0, 0, 0, 11.2500, 13.7500, 12.5000, 15.0000 }; gsExpected.row(4) = temp; - temp << 0 << 0 << 0 << 0 << 6.2500 << 8.7500 << 7.5000 << 10.0000 - << 0 << 0 << 0 << 0 << arma::endr; + temp = { 0, 0, 0, 0, 6.2500, 8.7500, 7.5000, 10.0000, 0, 0, 0, 0 }; gsExpected.row(5) = temp; - temp << 1.2500 << 3.7500 << 2.5000 << 5.0000 << 0 << 0 << 0 << 0 - << 0 << 0 << 0 << 0 << arma::endr; + temp = { 1.2500, 3.7500, 2.5000, 5.0000, 0, 0, 0, 0, 0, 0, 0, 0 }; gsExpected.row(6) = temp; - temp << 1.2500 << 3.7500 << 2.5000 << 5.0000 << 6.2500 << 8.7500 << 7.5000 - << 10.0000 << 11.2500 << 13.7500 << 12.5000 << 15.0000 << arma::endr; + temp = { 1.2500, 3.7500, 2.5000, 5.0000, 6.2500, 8.7500, 7.5000, 10.0000, + 11.2500, 13.7500, 12.5000, 15.0000 }; gsExpected.row(7) = temp; input = input.t(); diff --git a/src/mlpack/tests/ann_regularizer_test.cpp b/src/mlpack/tests/ann_regularizer_test.cpp index 2252852ee8..e300e6a9b1 100644 --- a/src/mlpack/tests/ann_regularizer_test.cpp +++ b/src/mlpack/tests/ann_regularizer_test.cpp @@ -18,7 +18,7 @@ #include "catch.hpp" #include "ann_test_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::ann; diff --git a/src/mlpack/tests/ann_visitor_test.cpp b/src/mlpack/tests/ann_visitor_test.cpp index f42a0a367e..b9316e8f0f 100644 --- a/src/mlpack/tests/ann_visitor_test.cpp +++ b/src/mlpack/tests/ann_visitor_test.cpp @@ -95,8 +95,6 @@ TEST_CASE("WeightSizeVisitorTestForLinearLayer", "[ANNVisitorTest]") LayerTypes<> linearLayer = new Linear<>(randomInSize, randomOutSize); - size_t weightSize = boost::apply_visitor(WeightSizeVisitor(), linearLayer); - CheckCorrectnessOfWeightSize(linearLayer); } @@ -107,8 +105,6 @@ TEST_CASE("WeightSizeVisitorTestForConcatLayer", "[ANNVisitorTest]") { LayerTypes<> concatLayer = new Concat<>(); - size_t weightSize = boost::apply_visitor(WeightSizeVisitor(), concatLayer); - CheckCorrectnessOfWeightSize(concatLayer); } @@ -122,8 +118,6 @@ TEST_CASE("WeightSizeVisitorTestForFastLSTMLayer", "[ANNVisitorTest]") LayerTypes<> fastLSTMLayer = new FastLSTM<>(randomInSize, randomOutSize); - size_t weightSize = boost::apply_visitor(WeightSizeVisitor(), fastLSTMLayer); - CheckCorrectnessOfWeightSize(fastLSTMLayer); } @@ -136,8 +130,6 @@ TEST_CASE("WeightSizeVisitorTestForAddLayer", "[ANNVisitorTest]") LayerTypes<> addLayer = new Add<>(randomOutSize); - size_t weightSize = boost::apply_visitor(WeightSizeVisitor(), addLayer); - CheckCorrectnessOfWeightSize(addLayer); } @@ -154,9 +146,6 @@ TEST_CASE("WeightSizeVisitorTestForAtrousConvolutionLayer", "[ANNVisitorTest]") LayerTypes<> atrousConvLayer = new AtrousConvolution<>(randomInSize, randomOutSize, randomKernelWidth, randomKernelHeight); - size_t weightSize = boost::apply_visitor(WeightSizeVisitor(), - atrousConvLayer); - CheckCorrectnessOfWeightSize(atrousConvLayer); } @@ -186,3 +175,61 @@ TEST_CASE("WeightSizeVisitorTestForBatchNormLayer", "[ANNVisitorTest]") LayerTypes<> batchNorm = new BatchNorm<>(randomSize); CheckCorrectnessOfWeightSize(batchNorm); } + +/** + * Test that WeightSizeVisitor works properly for LSTM layer. + */ +TEST_CASE("WeightSizeVisitorTestForLSTMLayer", "[ANNVisitorTest]") +{ + size_t randomInSize = arma::randi(arma::distr_param(1, 100)); + size_t randomOutSize = arma::randi(arma::distr_param(1, 100)); + + LayerTypes<> lstm = new LSTM<>(randomInSize, randomOutSize); + CheckCorrectnessOfWeightSize(lstm); +} + +/** + * Test that WeightSizeVisitor works properly for Transposed Convolution layer. + */ +TEST_CASE("WeightSizeVisitorTestForTransposedConvLayer", "[ANNVisitorTest]") +{ + size_t randomInSize = arma::randi(arma::distr_param(1, 100)); + size_t randomOutSize = arma::randi(arma::distr_param(1, 100)); + size_t randomKernelWidth = arma::randi(arma::distr_param(1, 100)); + size_t randomKernelHeight = arma::randi(arma::distr_param(1, 100)); + + LayerTypes<> transposedConvLayer = new TransposedConvolution<>(randomInSize, + randomOutSize, randomKernelWidth, randomKernelHeight); + + CheckCorrectnessOfWeightSize(transposedConvLayer); +} + +/** + * Test that WeightSizeVisitor works properly for noisy linear layer. + */ +TEST_CASE("WeightSizeVisitorTestForNoisyLinearLayer", "[ANNVisitorTest]") +{ + size_t randomInSize = arma::randi(arma::distr_param(1, 100)); + size_t randomOutSize = arma::randi(arma::distr_param(1, 100)); + + LayerTypes<> noisyLinearLayer = new NoisyLinear<>(randomInSize, + randomOutSize); + + CheckCorrectnessOfWeightSize(noisyLinearLayer); +} + +/** + * Test that WeightSizeVisitor works properly for Multihead Attention layer. + */ +TEST_CASE("WeightSizeVisitorTestForMultiheadAttentionLayer", "[ANNVisitorTest]") +{ + size_t randomtgtSeqLen = arma::randi(arma::distr_param(1, 100)); + size_t randomsrcSeqLen = arma::randi(arma::distr_param(1, 100)); + size_t randomembedDim = 768; + size_t randomnumHeads = 12; + + LayerTypes<> MultiheadAttentionLayer = new MultiheadAttention<>(randomtgtSeqLen, + randomsrcSeqLen, randomembedDim, randomnumHeads); + + CheckCorrectnessOfWeightSize(MultiheadAttentionLayer); +} diff --git a/src/mlpack/tests/binarize_test.cpp b/src/mlpack/tests/binarize_test.cpp index 8991515724..b6ae6b2885 100644 --- a/src/mlpack/tests/binarize_test.cpp +++ b/src/mlpack/tests/binarize_test.cpp @@ -23,9 +23,9 @@ using namespace mlpack::data; TEST_CASE("BinarizeOneDimension", "[BinarizeTest]") { mat input; - input << 1 << 2 << 3 << endr - << 4 << 5 << 6 << endr // this row will be tested - << 7 << 8 << 9; + input = { { 1, 2, 3 }, + { 4, 5, 6 }, // this row will be tested + { 7, 8, 9 } }; mat output; const double threshold = 5.0; @@ -46,9 +46,9 @@ TEST_CASE("BinarizeOneDimension", "[BinarizeTest]") TEST_CASE("BinerizeAll", "[BinarizeTest]") { mat input; - input << 1 << 2 << 3 << endr - << 4 << 5 << 6 << endr // this row will be tested - << 7 << 8 << 9; + input = { { 1, 2, 3 }, + { 4, 5, 6 }, // This row will be tested. + { 7, 8, 9 } }; mat output; const double threshold = 5.0; diff --git a/src/mlpack/tests/callback_test.cpp b/src/mlpack/tests/callback_test.cpp index e66fc1e051..aeeed3fe26 100644 --- a/src/mlpack/tests/callback_test.cpp +++ b/src/mlpack/tests/callback_test.cpp @@ -42,9 +42,9 @@ TEST_CASE("FFNCallbackTest", "[CallbackTest]") arma::mat data; arma::mat labels; - if (!data::Load("lab1.csv", data, true)) + if (!data::Load("lab1.csv", data)) FAIL("Cannot load test dataset lab1.csv!"); - if (!data::Load("lab3.csv", labels, true)) + if (!data::Load("lab3.csv", labels)) FAIL("Cannot load test dataset lab3.csv!"); FFN, RandomInitialization> model; @@ -68,9 +68,9 @@ TEST_CASE("FFNWithOptimizerCallbackTest", "[CallbackTest]") arma::mat data; arma::mat labels; - if (!data::Load("lab1.csv", data, true)) + if (!data::Load("lab1.csv", data)) FAIL("Cannot load test dataset lab1.csv!"); - if (!data::Load("lab3.csv", labels, true)) + if (!data::Load("lab3.csv", labels)) FAIL("Cannot load test dataset lab3.csv!"); FFN, RandomInitialization> model; @@ -94,7 +94,7 @@ TEST_CASE("RNNCallbackTest", "[CallbackTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. @@ -120,7 +120,7 @@ TEST_CASE("RNNWithOptimizerCallbackTest", "[CallbackTest]") { const size_t rho = 5; arma::cube input = arma::randu(1, 1, 5); - arma::cube target = arma::ones(1, 1, 5); + arma::cube target = arma::zeros(1, 1, 5); RandomInitialization init(0.5, 0.5); // Create model with user defined rho parameter. diff --git a/src/mlpack/tests/catch.hpp b/src/mlpack/tests/catch.hpp index 2a2d77a27f..0384171ae4 100644 --- a/src/mlpack/tests/catch.hpp +++ b/src/mlpack/tests/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v2.13.3 - * Generated: 2020-10-31 18:20:31.045274 + * Catch v2.13.4 + * Generated: 2020-12-29 14:48:00.116107 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. @@ -15,7 +15,7 @@ #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 3 +#define CATCH_VERSION_PATCH 4 #ifdef __clang__ # pragma clang system_header @@ -14126,24 +14126,28 @@ namespace Catch { namespace { struct TestHasher { - explicit TestHasher(Catch::SimplePcg32& rng_instance) { - basis = rng_instance(); - basis <<= 32; - basis |= rng_instance(); - } + using hash_t = uint64_t; - uint64_t basis; + explicit TestHasher( hash_t hashSuffix ): + m_hashSuffix{ hashSuffix } {} - uint64_t operator()(TestCase const& t) const { - // Modified FNV-1a hash - static constexpr uint64_t prime = 1099511628211; - uint64_t hash = basis; - for (const char c : t.name) { + uint32_t operator()( TestCase const& t ) const { + // FNV-1a hash with multiplication fold. + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { hash ^= c; hash *= prime; } - return hash; + hash ^= m_hashSuffix; + hash *= prime; + const uint32_t low{ static_cast( hash ) }; + const uint32_t high{ static_cast( hash >> 32 ) }; + return low * high; } + + private: + hash_t m_hashSuffix; }; } // end unnamed namespace @@ -14161,9 +14165,9 @@ namespace Catch { case RunTests::InRandomOrder: { seedRng( config ); - TestHasher h( rng() ); + TestHasher h{ config.rngSeed() }; - using hashedTest = std::pair; + using hashedTest = std::pair; std::vector indexed_tests; indexed_tests.reserve( unsortedTestCases.size() ); @@ -15316,7 +15320,7 @@ namespace Catch { } Version const& libraryVersion() { - static Version version( 2, 13, 3, "", 0 ); + static Version version( 2, 13, 4, "", 0 ); return version; } diff --git a/src/mlpack/tests/cf_test.cpp b/src/mlpack/tests/cf_test.cpp index 735fe65c20..af5623f708 100644 --- a/src/mlpack/tests/cf_test.cpp +++ b/src/mlpack/tests/cf_test.cpp @@ -37,7 +37,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::cf; @@ -346,7 +346,7 @@ void TrainWithCoordinateList(DecompositionPolicy& decomposition) { arma::mat randomData(3, 100); randomData.row(0) = arma::linspace(0, 99, 100); - randomData.row(1) = arma::linspace(0, 99, 100); + randomData.row(1) = randomData.row(0); randomData.row(2).fill(3); CFType c(randomData, decomposition, 5, 5, 30); diff --git a/src/mlpack/tests/convolution_test.cpp b/src/mlpack/tests/convolution_test.cpp index f60b8cf033..c2798090ce 100644 --- a/src/mlpack/tests/convolution_test.cpp +++ b/src/mlpack/tests/convolution_test.cpp @@ -17,7 +17,7 @@ #include #include -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "catch.hpp" #include "test_catch_tools.hpp" @@ -124,17 +124,17 @@ TEST_CASE("ValidConvolution2DTest", "[ConvolutionTest]") { // Generate dataset for convolution function tests. arma::mat input, filter, output; - input << 1 << 2 << 3 << 4 << arma::endr - << 4 << 1 << 2 << 3 << arma::endr - << 3 << 4 << 1 << 2 << arma::endr - << 2 << 3 << 4 << 1; + input = { { 1, 2, 3, 4 }, + { 4, 1, 2, 3 }, + { 3, 4, 1, 2 }, + { 2, 3, 4, 1 } }; - filter << 1 << 0 << -1 << arma::endr - << 0 << 1 << 0 << arma::endr - << -1 << 0 << 1; + filter = { { 1, 0, -1 }, + { 0, 1, 0 }, + { -1, 0, 1 } }; - output << -3 << -2 << arma::endr - << 8 << -3; + output = { { -3, -2 }, + { 8, -3 } }; // Perform the naive convolution approach. Convolution2DMethodTest >(input, filter, @@ -157,21 +157,21 @@ TEST_CASE("FullConvolution2DTest", "[ConvolutionTest]") { // Generate dataset for convolution function tests. arma::mat input, filter, output; - input << 1 << 2 << 3 << 4 << arma::endr - << 4 << 1 << 2 << 3 << arma::endr - << 3 << 4 << 1 << 2 << arma::endr - << 2 << 3 << 4 << 1; + input = { { 1, 2, 3, 4 }, + { 4, 1, 2, 3 }, + { 3, 4, 1, 2 }, + { 2, 3, 4, 1 } }; - filter << 1 << 0 << -1 << arma::endr - << 1 << 1 << 1 << arma::endr - << -1 << 0 << 1; + filter = { { 1, 0, -1 }, + { 1, 1, 1 }, + { -1, 0, 1 } }; - output << 1 << 2 << 2 << 2 << -3 << -4 << arma::endr - << 5 << 4 << 4 << 11 << 5 << 1 << arma::endr - << 6 << 7 << 3 << 2 << 7 << 5 << arma::endr - << 1 << 9 << 12 << 3 << 1 << 4 << arma::endr - << -1 << 1 << 11 << 10 << 6 << 3 << arma::endr - << -2 << -3 << -2 << 2 << 4 << 1; + output = { { 1, 2, 2, 2, -3, -4 }, + { 5, 4, 4, 11, 5, 1 }, + { 6, 7, 3, 2, 7, 5 }, + { 1, 9, 12, 3, 1, 4 }, + { -1, 1, 11, 10, 6, 3 }, + { -2, -3, -2, 2, 4, 1 } }; // Perform the naive convolution approach. Convolution2DMethodTest >(input, filter, @@ -194,17 +194,17 @@ TEST_CASE("ValidConvolution3DTest", "[ConvolutionTest]") { // Generate dataset for convolution function tests. arma::mat input, filter, output; - input << 1 << 2 << 3 << 4 << arma::endr - << 4 << 1 << 2 << 3 << arma::endr - << 3 << 4 << 1 << 2 << arma::endr - << 2 << 3 << 4 << 1; + input = { { 1, 2, 3, 4 }, + { 4, 1, 2, 3 }, + { 3, 4, 1, 2 }, + { 2, 3, 4, 1 } }; - filter << 1 << 0 << -1 << arma::endr - << 0 << 1 << 0 << arma::endr - << -1 << 0 << 1; + filter = { { 1, 0, -1 }, + { 0, 1, 0 }, + { -1, 0, 1 } }; - output << -3 << -2 << arma::endr - << 8 << -3; + output = { { -3, -2 }, + { 8, -3 } }; arma::cube inputCube(input.n_rows, input.n_cols, 2); inputCube.slice(0) = input; @@ -239,21 +239,21 @@ TEST_CASE("FullConvolution3DTest", "[ConvolutionTest]") { // Generate dataset for convolution function tests. arma::mat input, filter, output; - input << 1 << 2 << 3 << 4 << arma::endr - << 4 << 1 << 2 << 3 << arma::endr - << 3 << 4 << 1 << 2 << arma::endr - << 2 << 3 << 4 << 1; + input = { { 1, 2, 3, 4 }, + { 4, 1, 2, 3 }, + { 3, 4, 1, 2 }, + { 2, 3, 4, 1 } }; - filter << 1 << 0 << -1 << arma::endr - << 1 << 1 << 1 << arma::endr - << -1 << 0 << 1; + filter = { { 1, 0, -1 }, + { 1, 1, 1 }, + { -1, 0, 1 } }; - output << 1 << 2 << 2 << 2 << -3 << -4 << arma::endr - << 5 << 4 << 4 << 11 << 5 << 1 << arma::endr - << 6 << 7 << 3 << 2 << 7 << 5 << arma::endr - << 1 << 9 << 12 << 3 << 1 << 4 << arma::endr - << -1 << 1 << 11 << 10 << 6 << 3 << arma::endr - << -2 << -3 << -2 << 2 << 4 << 1; + output = { { 1, 2, 2, 2, -3, -4 }, + { 5, 4, 4, 11, 5, 1 }, + { 6, 7, 3, 2, 7, 5 }, + { 1, 9, 12, 3, 1, 4 }, + { -1, 1, 11, 10, 6, 3 }, + { -2, -3, -2, 2, 4, 1 } }; arma::cube inputCube(input.n_rows, input.n_cols, 2); inputCube.slice(0) = input; @@ -289,17 +289,17 @@ TEST_CASE("ValidConvolutionBatchTest", "[ConvolutionTest]") { // Generate dataset for convolution function tests. arma::mat input, filter, output; - input << 1 << 2 << 3 << 4 << arma::endr - << 4 << 1 << 2 << 3 << arma::endr - << 3 << 4 << 1 << 2 << arma::endr - << 2 << 3 << 4 << 1; + input = { { 1, 2, 3, 4 }, + { 4, 1, 2, 3 }, + { 3, 4, 1, 2 }, + { 2, 3, 4, 1 } }; - filter << 1 << 0 << -1 << arma::endr - << 0 << 1 << 0 << arma::endr - << -1 << 0 << 1; + filter = { { 1, 0, -1 }, + { 0, 1, 0 }, + { -1, 0, 1 } }; - output << -3 << -2 << arma::endr - << 8 << -3; + output = { { -3, -2 }, + { 8, -3 } }; arma::cube filterCube(filter.n_rows, filter.n_cols, 2); filterCube.slice(0) = filter; @@ -331,21 +331,21 @@ TEST_CASE("FullConvolutionBatchTest", "[ConvolutionTest]") { // Generate dataset for convolution function tests. arma::mat input, filter, output; - input << 1 << 2 << 3 << 4 << arma::endr - << 4 << 1 << 2 << 3 << arma::endr - << 3 << 4 << 1 << 2 << arma::endr - << 2 << 3 << 4 << 1; + input = { { 1, 2, 3, 4 }, + { 4, 1, 2, 3 }, + { 3, 4, 1, 2 }, + { 2, 3, 4, 1 } }; - filter << 1 << 0 << -1 << arma::endr - << 1 << 1 << 1 << arma::endr - << -1 << 0 << 1; + filter = { { 1, 0, -1 }, + { 1, 1, 1 }, + { -1, 0, 1 } }; - output << 1 << 2 << 2 << 2 << -3 << -4 << arma::endr - << 5 << 4 << 4 << 11 << 5 << 1 << arma::endr - << 6 << 7 << 3 << 2 << 7 << 5 << arma::endr - << 1 << 9 << 12 << 3 << 1 << 4 << arma::endr - << -1 << 1 << 11 << 10 << 6 << 3 << arma::endr - << -2 << -3 << -2 << 2 << 4 << 1; + output = { { 1, 2, 2, 2, -3, -4 }, + { 5, 4, 4, 11, 5, 1 }, + { 6, 7, 3, 2, 7, 5 }, + { 1, 9, 12, 3, 1, 4 }, + { -1, 1, 11, 10, 6, 3 }, + { -2, -3, -2, 2, 4, 1 } }; arma::cube filterCube(filter.n_rows, filter.n_cols, 2); filterCube.slice(0) = filter; diff --git a/src/mlpack/tests/convolutional_network_test.cpp b/src/mlpack/tests/convolutional_network_test.cpp index c04b3af701..529274643d 100644 --- a/src/mlpack/tests/convolutional_network_test.cpp +++ b/src/mlpack/tests/convolutional_network_test.cpp @@ -18,7 +18,7 @@ #include -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "catch.hpp" #include "test_catch_tools.hpp" @@ -46,13 +46,13 @@ TEST_CASE("VanillaNetworkTest", "[ConvolutionalNetworkTest]") { if (i < nPoints / 2) { - // Assign label "1" to all samples with digit = 4 - Y(i) = 1; + // Assign label "0" to all samples with digit = 4 + Y(i) = 0; } else { - // Assign label "2" to all samples with digit = 9 - Y(i) = 2; + // Assign label "1" to all samples with digit = 9 + Y(i) = 1; } } @@ -110,7 +110,7 @@ TEST_CASE("VanillaNetworkTest", "[ConvolutionalNetworkTest]") for (size_t i = 0; i < predictionTemp.n_cols; ++i) { prediction(i) = arma::as_scalar(arma::find( - arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)) + 1; + arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)); } size_t correct = arma::accu(prediction == Y); diff --git a/src/mlpack/tests/cv_test.cpp b/src/mlpack/tests/cv_test.cpp index 48bb9a6481..75f1e87998 100644 --- a/src/mlpack/tests/cv_test.cpp +++ b/src/mlpack/tests/cv_test.cpp @@ -187,10 +187,31 @@ TEST_CASE("R2ScoreTest", "[CVTest]") double expectedR2 = 0.99999779; - REQUIRE(R2Score::Evaluate(lr, data, responses) + REQUIRE(R2Score::Evaluate(lr, data, responses) == Approx(expectedR2).epsilon(1e-7)); } +/** + * Test the Adjusted R squared metric. + */ +TEST_CASE("AdjR2ScoreTest", "[CVTest]") +{ + // Making two variables that define the linear function is + // f(x1, x2) = x1 + x2. + arma::mat X; + X = { { 1, 2, 3, 4, 5, 6 }, + { 2, 3, 4, 5, 6, 7 } }; + arma::rowvec Y; + Y = { 3, 5, 7, 9, 11, 13 }; + + LinearRegression lr(X, Y); + + // Theoretically Adjusted R squared should be equal 1 + double expAdjR2 = 1; + REQUIRE(std::abs(R2Score::Evaluate(lr, X, Y) - expAdjR2) + <= 1e-7); +} + /** * Test the mean squared error with matrix responses. */ @@ -750,10 +771,10 @@ TEST_CASE("KFoldCVWithDTTestUnevenBinsWeighted", "[CVTest]") TEST_CASE("SilhouetteScoreTest", "[CVTest]") { arma::mat X; - X << 0 << 1 << 1 << 0 << 0 << arma::endr - << 0 << 1 << 2 << 0 << 0 << arma::endr - << 1 << 1 << 3 << 2 << 0 << arma::endr; - arma::Row labels = {0, 1, 2, 0, 0}; + X = { { 0, 1, 1, 0, 0 }, + { 0, 1, 2, 0, 0 }, + { 1, 1, 3, 2, 0 } }; + arma::Row labels = { 0, 1, 2, 0, 0 }; metric::EuclideanDistance metric; double silhouetteScore = SilhouetteScore::Overall(X, labels, metric); REQUIRE(silhouetteScore == Approx(0.1121684822489150).epsilon(1e-7)); diff --git a/src/mlpack/tests/data/nbc_high_dim_test_labels.csv b/src/mlpack/tests/data/nbc_high_dim_test_labels.csv index dd6bde5f4b..59847158f6 100644 --- a/src/mlpack/tests/data/nbc_high_dim_test_labels.csv +++ b/src/mlpack/tests/data/nbc_high_dim_test_labels.csv @@ -1,50 +1,50 @@ -3 -2 -0 -0 -0 -1 -2 -3 -3 -2 -4 -2 -1 -2 -3 -1 -2 -4 -4 -1 -3 -0 -2 -0 -0 -2 -0 -1 -3 -3 -2 -2 -2 -3 -3 -3 -3 -3 -0 -0 -4 -3 -3 -0 -3 -2 -3 -2 -1 -1 +3 +2 +0 +0 +0 +1 +2 +3 +3 +2 +4 +2 +1 +2 +3 +1 +2 +4 +4 +1 +3 +0 +2 +0 +0 +2 +0 +1 +3 +3 +2 +2 +2 +3 +3 +3 +3 +3 +0 +0 +4 +3 +3 +0 +3 +2 +3 +2 +1 +1 diff --git a/src/mlpack/tests/data/nbc_high_dim_train_labels.csv b/src/mlpack/tests/data/nbc_high_dim_train_labels.csv index 064f0e24a2..c25922d7a4 100644 --- a/src/mlpack/tests/data/nbc_high_dim_train_labels.csv +++ b/src/mlpack/tests/data/nbc_high_dim_train_labels.csv @@ -1,200 +1,200 @@ -1 -4 -2 -2 -1 -0 -1 -0 -0 -4 -0 -4 -3 -4 -3 -2 -4 -2 -2 -2 -4 -1 -2 -1 -3 -0 -4 -1 -4 -4 -4 -0 -3 -4 -3 -1 -3 -2 -3 -0 -4 -1 -4 -1 -4 -2 -1 -4 -2 -1 -2 -0 -2 -2 -4 -2 -0 -2 -0 -3 -3 -3 -0 -2 -1 -4 -3 -1 -2 -2 -4 -0 -1 -3 -4 -4 -4 -2 -4 -2 -3 -4 -4 -3 -2 -3 -3 -4 -3 -4 -2 -4 -0 -3 -3 -1 -3 -4 -2 -1 -2 -3 -1 -3 -3 -0 -4 -0 -0 -3 -2 -1 -0 -3 -2 -1 -0 -0 -1 -0 -2 -2 -4 -2 -3 -1 -4 -4 -2 -3 -4 -0 -2 -2 -0 -4 -0 -3 -1 -4 -4 -2 -0 -0 -0 -0 -3 -4 -3 -2 -0 -4 -3 -3 -4 -0 -3 -1 -3 -4 -3 -2 -2 -4 -0 -0 -0 -0 -1 -4 -0 -3 -4 -3 -1 -4 -0 -1 -4 -3 -2 -1 -3 -2 -4 -3 -2 -0 -1 -4 -2 -0 -2 -3 -0 -0 -2 -1 -3 -1 +1 +4 +2 +2 +1 +0 +1 +0 +0 +4 +0 +4 +3 +4 +3 +2 +4 +2 +2 +2 +4 +1 +2 +1 +3 +0 +4 +1 +4 +4 +4 +0 +3 +4 +3 +1 +3 +2 +3 +0 +4 +1 +4 +1 +4 +2 +1 +4 +2 +1 +2 +0 +2 +2 +4 +2 +0 +2 +0 +3 +3 +3 +0 +2 +1 +4 +3 +1 +2 +2 +4 +0 +1 +3 +4 +4 +4 +2 +4 +2 +3 +4 +4 +3 +2 +3 +3 +4 +3 +4 +2 +4 +0 +3 +3 +1 +3 +4 +2 +1 +2 +3 +1 +3 +3 +0 +4 +0 +0 +3 +2 +1 +0 +3 +2 +1 +0 +0 +1 +0 +2 +2 +4 +2 +3 +1 +4 +4 +2 +3 +4 +0 +2 +2 +0 +4 +0 +3 +1 +4 +4 +2 +0 +0 +0 +0 +3 +4 +3 +2 +0 +4 +3 +3 +4 +0 +3 +1 +3 +4 +3 +2 +2 +4 +0 +0 +0 +0 +1 +4 +0 +3 +4 +3 +1 +4 +0 +1 +4 +3 +2 +1 +3 +2 +4 +3 +2 +0 +1 +4 +2 +0 +2 +3 +0 +0 +2 +1 +3 +1 diff --git a/src/mlpack/tests/dcgan_test.cpp b/src/mlpack/tests/dcgan_test.cpp index 2496cb1418..0fdfdbd496 100644 --- a/src/mlpack/tests/dcgan_test.cpp +++ b/src/mlpack/tests/dcgan_test.cpp @@ -22,7 +22,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::ann; diff --git a/src/mlpack/tests/decision_stump_test.cpp b/src/mlpack/tests/decision_stump_test.cpp deleted file mode 100644 index d2fe36cc08..0000000000 --- a/src/mlpack/tests/decision_stump_test.cpp +++ /dev/null @@ -1,421 +0,0 @@ -/** - * @file tests/decision_stump_test.cpp - * @author Udit Saxena - * - * Tests for DecisionStump class. - * - * mlpack is free software; you may redistribute it and/or modify it under the - * terms of the 3-clause BSD license. You should have received a copy of the - * 3-clause BSD license along with mlpack. If not, see - * http://www.opensource.org/licenses/BSD-3-Clause for more information. - */ -#include -#include - -#include "catch.hpp" - -using namespace mlpack; -using namespace mlpack::decision_stump; -using namespace arma; -using namespace mlpack::distribution; - -/** - * This tests handles the case wherein only one class exists in the input - * labels. It checks whether the only class supplied was the only class - * predicted. - */ -TEST_CASE("OneClass", "[DecisionStumpTest]") -{ - const size_t numClasses = 2; - const size_t inpBucketSize = 6; - - mat trainingData; - trainingData << 2.4 << 3.8 << 3.8 << endr - << 1 << 1 << 2 << endr - << 1.3 << 1.9 << 1.3 << endr; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 1 << 1 << 1; - - mat testingData; - testingData << 2.4 << 2.5 << 2.6; - - DecisionStump<> ds(trainingData, labelsIn.row(0), numClasses, inpBucketSize); - - Row predictedLabels; - ds.Classify(testingData, predictedLabels); - - for (size_t i = 0; i < predictedLabels.size(); ++i) - REQUIRE(predictedLabels(i) == 1); -} - -/** - * This tests whether the entropy is being correctly calculated by checking the - * correct value of the splitting column value. This test is for an - * inpBucketSize of 4 and the correct value of the splitting dimension is 0. - */ -TEST_CASE("CorrectDimensionChosen", "[DecisionStumpTest]") -{ - const size_t numClasses = 2; - const size_t inpBucketSize = 4; - - // This dataset comes from Chapter 6 of the book "Data Mining: Concepts, - // Models, Methods, and Algorithms" (2nd Edition) by Mehmed Kantardzic. It is - // found on page 176 (and a description of the correct splitting dimension is - // given below that). - mat trainingData; - trainingData << 0 << 0 << 0 << 0 << 0 << 1 << 1 << 1 << 1 - << 2 << 2 << 2 << 2 << 2 << endr - << 70 << 90 << 85 << 95 << 70 << 90 << 78 << 65 << 75 - << 80 << 70 << 80 << 80 << 96 << endr - << 1 << 1 << 0 << 0 << 0 << 1 << 0 << 1 << 0 - << 1 << 1 << 0 << 0 << 0 << endr; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 1 << 1 << 1 << 0 << 0 << 0 << 0 - << 0 << 1 << 1 << 0 << 0 << 0; - - DecisionStump<> ds(trainingData, labelsIn.row(0), numClasses, inpBucketSize); - - // Only need to check the value of the splitting column, no need of - // classification. - REQUIRE(ds.SplitDimension() == 0); -} - -/** - * This tests for the classification: - * if testinput < 0 - class 0 - * if testinput > 0 - class 1 - * An almost perfect split on zero. - */ -TEST_CASE("PerfectSplitOnZero", "[DecisionStumpTest]") -{ - const size_t numClasses = 2; - const size_t inpBucketSize = 2; - - mat trainingData; - trainingData << -1 << 1 << -2 << 2 << -3 << 3; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 1 << 0 << 1 << 0 << 1; - - mat testingData; - testingData << -4 << 7 << -7 << -5 << 6; - - DecisionStump<> ds(trainingData, labelsIn.row(0), numClasses, inpBucketSize); - - Row predictedLabels; - ds.Classify(testingData, predictedLabels); - - REQUIRE(predictedLabels(0, 0) == 0); - REQUIRE(predictedLabels(0, 1) == 1); - REQUIRE(predictedLabels(0, 2) == 0); - REQUIRE(predictedLabels(0, 3) == 0); - REQUIRE(predictedLabels(0, 4) == 1); -} - -/** - * This tests the binning function for the case when a dataset with cardinality - * of input < inpBucketSize is provided. - */ -TEST_CASE("BinningTesting", "[DecisionStumpTest]") -{ - const size_t numClasses = 2; - const size_t inpBucketSize = 10; - - mat trainingData; - trainingData << -1 << 1 << -2 << 2 << -3 << 3 << -4; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 1 << 0 << 1 << 0 << 1 << 0; - - mat testingData; - testingData << 5; - - DecisionStump<> ds(trainingData, labelsIn.row(0), numClasses, inpBucketSize); - - Row predictedLabels; - ds.Classify(testingData, predictedLabels); - - REQUIRE(predictedLabels(0, 0) == 0); -} - -/** - * This is a test for the case when non-overlapping, multiple classes are - * provided. It tests for a perfect split due to the non-overlapping nature of - * the input classes. - */ -TEST_CASE("PerfectMultiClassSplit", "[DecisionStumpTest]") -{ - const size_t numClasses = 4; - const size_t inpBucketSize = 3; - - mat trainingData; - trainingData << -8 << -7 << -6 << -5 << -4 << -3 << -2 << -1 - << 0 << 1 << 2 << 3 << 4 << 5 << 6 << 7; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 0 << 0 << 0 << 1 << 1 << 1 << 1 - << 2 << 2 << 2 << 2 << 3 << 3 << 3 << 3; - - mat testingData; - testingData << -6.1 << -2.1 << 1.1 << 5.1; - - DecisionStump<> ds(trainingData, labelsIn.row(0), numClasses, inpBucketSize); - - Row predictedLabels; - ds.Classify(testingData, predictedLabels); - - REQUIRE(predictedLabels(0, 0) == 0); - REQUIRE(predictedLabels(0, 1) == 1); - REQUIRE(predictedLabels(0, 2) == 2); - REQUIRE(predictedLabels(0, 3) == 3); -} - -/** - * This test is for the case when reasonably overlapping, multiple classes are - * provided in the input label set. It tests whether classification takes place - * with a reasonable amount of error due to the overlapping nature of input - * classes. - */ -TEST_CASE("MultiClassSplit", "[DecisionStumpTest]") -{ - const size_t numClasses = 3; - const size_t inpBucketSize = 3; - - mat trainingData; - trainingData << -7 << -6 << -5 << -4 << -3 << -2 << -1 << 0 << 1 - << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 0 << 0 << 0 << 1 << 1 << 0 << 0 - << 1 << 1 << 1 << 2 << 1 << 2 << 2 << 2 << 2 << 2; - - - mat testingData; - testingData << -6.1 << -5.9 << -2.1 << -0.7 << 2.5 << 4.7 << 7.2 << 9.1; - - DecisionStump<> ds(trainingData, labelsIn.row(0), numClasses, inpBucketSize); - - Row predictedLabels; - ds.Classify(testingData, predictedLabels); - - REQUIRE(predictedLabels(0, 0) == 0); - REQUIRE(predictedLabels(0, 1) == 0); - REQUIRE(predictedLabels(0, 2) == 1); - REQUIRE(predictedLabels(0, 3) == 1); - REQUIRE(predictedLabels(0, 4) == 1); - REQUIRE(predictedLabels(0, 5) == 1); - REQUIRE(predictedLabels(0, 6) == 2); - REQUIRE(predictedLabels(0, 7) == 2); -} - -/** - * This tests that the decision stump can learn a good split on a dataset with - * four dimensions that have progressing levels of separation. - */ -TEST_CASE("DimensionSelectionTest", "[DecisionStumpTest]") -{ - const size_t numClasses = 2; - const size_t inpBucketSize = 2500; - - arma::mat dataset(4, 5000); - - // The most separable dimension. - GaussianDistribution g1("-5", "1"); - GaussianDistribution g2("5", "1"); - - for (size_t i = 0; i < 2500; ++i) - { - arma::vec tmp = g1.Random(); - dataset(1, i) = tmp[0]; - } - for (size_t i = 2500; i < 5000; ++i) - { - arma::vec tmp = g2.Random(); - dataset(1, i) = tmp[0]; - } - - g1 = GaussianDistribution("-3", "1"); - g2 = GaussianDistribution("3", "1"); - - for (size_t i = 0; i < 2500; ++i) - { - arma::vec tmp = g1.Random(); - dataset(3, i) = tmp[0]; - } - for (size_t i = 2500; i < 5000; ++i) - { - arma::vec tmp = g2.Random(); - dataset(3, i) = tmp[0]; - } - - g1 = GaussianDistribution("-1", "1"); - g2 = GaussianDistribution("1", "1"); - - for (size_t i = 0; i < 2500; ++i) - { - arma::vec tmp = g1.Random(); - dataset(0, i) = tmp[0]; - } - for (size_t i = 2500; i < 5000; ++i) - { - arma::vec tmp = g2.Random(); - dataset(0, i) = tmp[0]; - } - - // Not separable at all. - g1 = GaussianDistribution("0", "1"); - g2 = GaussianDistribution("0", "1"); - - for (size_t i = 0; i < 2500; ++i) - { - arma::vec tmp = g1.Random(); - dataset(2, i) = tmp[0]; - } - for (size_t i = 2500; i < 5000; ++i) - { - arma::vec tmp = g2.Random(); - dataset(2, i) = tmp[0]; - } - - // Generate the labels. - arma::Row labels(5000); - for (size_t i = 0; i < 2500; ++i) - labels[i] = 0; - for (size_t i = 2500; i < 5000; ++i) - labels[i] = 1; - - // Now create a decision stump. - DecisionStump<> ds(dataset, labels, numClasses, inpBucketSize); - - // Make sure it split on the dimension that is most separable. - REQUIRE(ds.SplitDimension() == 1); - - // Make sure every bin below -1 classifies as label 0, and every bin above 1 - // classifies as label 1 (What happens in [-1, 1] isn't that big a deal.). - for (size_t i = 0; i < ds.Split().n_elem; ++i) - { - if (ds.Split()[i] <= -3.0) - REQUIRE(ds.BinLabels()[i] == 0); - else if (ds.Split()[i] >= 3.0) - REQUIRE(ds.BinLabels()[i] == 1); - } -} - -/** - * Ensure that the default constructor works and that it classifies things as 0 - * always. - */ -TEST_CASE("EmptyConstructorTest", "[DecisionStumpTest]") -{ - DecisionStump<> d; - - arma::mat data = arma::randu(3, 10); - arma::Row labels; - - d.Classify(data, labels); - - for (size_t i = 0; i < 10; ++i) - REQUIRE(labels[i] == 0); - - // Now train on another dataset and make sure something kind of makes sense. - mat trainingData; - trainingData << -7 << -6 << -5 << -4 << -3 << -2 << -1 << 0 << 1 - << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 0 << 0 << 0 << 1 << 1 << 0 << 0 - << 1 << 1 << 1 << 2 << 1 << 2 << 2 << 2 << 2 << 2; - - - mat testingData; - testingData << -6.1 << -5.9 << -2.1 << -0.7 << 2.5 << 4.7 << 7.2 << 9.1; - - DecisionStump<> ds(trainingData, labelsIn.row(0), 4, 3); - - Row predictedLabels(testingData.n_cols); - ds.Classify(testingData, predictedLabels); - - REQUIRE(predictedLabels(0, 0) == 0); - REQUIRE(predictedLabels(0, 1) == 0); - REQUIRE(predictedLabels(0, 2) == 1); - REQUIRE(predictedLabels(0, 3) == 1); - REQUIRE(predictedLabels(0, 4) == 1); - REQUIRE(predictedLabels(0, 5) == 1); - REQUIRE(predictedLabels(0, 6) == 2); - REQUIRE(predictedLabels(0, 7) == 2); -} - -/** - * Ensure that a matrix holding ints can be trained. The bigger issue here is - * just compilation. - */ -TEST_CASE("IntTest", "[DecisionStumpTest]") -{ - // Train on a dataset and make sure something kind of makes sense. - imat trainingData; - trainingData << -7 << -6 << -5 << -4 << -3 << -2 << -1 << 0 << 1 - << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 0 << 0 << 0 << 1 << 1 << 0 << 0 - << 1 << 1 << 1 << 2 << 1 << 2 << 2 << 2 << 2 << 2; - - DecisionStump ds(trainingData, labelsIn.row(0), 4, 3); - - imat testingData; - testingData << -6 << -6 << -2 << -1 << 3 << 5 << 7 << 9; - - arma::Row predictedLabels; - ds.Classify(testingData, predictedLabels); - - REQUIRE(predictedLabels(0, 0) == 0); - REQUIRE(predictedLabels(0, 1) == 0); - REQUIRE(predictedLabels(0, 2) == 1); - REQUIRE(predictedLabels(0, 3) == 1); - REQUIRE(predictedLabels(0, 4) == 1); - REQUIRE(predictedLabels(0, 5) == 1); - REQUIRE(predictedLabels(0, 6) == 2); - REQUIRE(predictedLabels(0, 7) == 2); -} - -/** - * Test that DecisionStump::Train() returns finite gain. - */ -TEST_CASE("DecisionStumpTrainReturnEntropy", "[DecisionStumpTest]") -{ - const size_t numClasses = 2; - const size_t inpBucketSize = 2; - - mat trainingData; - trainingData << -1 << 1 << -2 << 2 << -3 << 3; - - // No need to normalize labels here. - Mat labelsIn; - labelsIn << 0 << 1 << 0 << 1 << 0 << 1; - - arma::Row weights = arma::ones>(labelsIn.n_elem); - - // Train a simple decision stump without weights. - DecisionStump<> ds; - double gain = ds.Train(trainingData, labelsIn.row(0), numClasses, - inpBucketSize); - - REQUIRE(std::isfinite(gain) == true); - - // Train decision stump with weights. - DecisionStump<> wds; - gain = wds.Train(trainingData, labelsIn.row(0), weights, numClasses, - inpBucketSize); - - REQUIRE(std::isfinite(gain) == true); -} diff --git a/src/mlpack/tests/decision_tree_test.cpp b/src/mlpack/tests/decision_tree_test.cpp index 67257c34fc..7722c54fe4 100644 --- a/src/mlpack/tests/decision_tree_test.cpp +++ b/src/mlpack/tests/decision_tree_test.cpp @@ -17,7 +17,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "mock_categorical_data.hpp" using namespace mlpack; @@ -773,28 +773,6 @@ TEST_CASE("CategoricalBuildTestWithWeight", "[DecisionTreeTest]") REQUIRE(correctPct > 0.70); } -/** - * Make sure that when we ask for a decision stump, we get one. - */ -TEST_CASE("DTDecisionStumpTest", "[DecisionTreeTest]") -{ - // Use a random dataset. - arma::mat dataset(10, 1000, arma::fill::randu); - arma::Row labels(1000); - for (size_t i = 0; i < 1000; ++i) - labels[i] = i % 3; // 3 classes. - - // Build a decision stump. - DecisionTree stump(dataset, labels, 3, 1); - - // Check that it has children. - REQUIRE(stump.NumChildren() == 2); - // Check that its children doesn't have children. - REQUIRE(stump.Child(0).NumChildren() == 0); - REQUIRE(stump.Child(1).NumChildren() == 0); -} - /** * Test that we can build a decision tree using weighted data (where the * low-weighted data is random noise), and that the tree still builds correctly diff --git a/src/mlpack/tests/det_test.cpp b/src/mlpack/tests/det_test.cpp index c0989768eb..ab55a541cd 100644 --- a/src/mlpack/tests/det_test.cpp +++ b/src/mlpack/tests/det_test.cpp @@ -40,9 +40,9 @@ TEST_CASE("TestGetMaxMinVals", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; DTree tree(testData); @@ -81,11 +81,11 @@ TEST_CASE("TestWithinRange", "[DETTest]") DTree testDTree(maxVals, minVals, 5); arma::vec testQuery(3); - testQuery << 4.5 << 2.5 << 2; + testQuery = { 4.5, 2.5, 2 }; REQUIRE(testDTree.WithinRange(testQuery) == true); - testQuery << 8.5 << 2.5 << 2; + testQuery = { 8.5, 2.5, 2 }; REQUIRE(testDTree.WithinRange(testQuery) == false); } @@ -94,9 +94,9 @@ TEST_CASE("TestFindSplit", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; DTree testDTree(testData); @@ -124,14 +124,14 @@ TEST_CASE("TestSplitData", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; DTree testDTree(testData); arma::Col oTest(5); - oTest << 1 << 2 << 3 << 4 << 5; + oTest = { 1, 2, 3, 4, 5 }; size_t splitDim = 2; double trueSplitVal = 5.5; @@ -152,10 +152,10 @@ TEST_CASE("TestSparseFindSplit", "[DETTest]") { arma::mat realData(4, 7); - realData << .0 << 4 << 5 << 7 << 0 << 5 << 0 << arma::endr - << .0 << 5 << 0 << 0 << 1 << 7 << 1 << arma::endr - << .0 << 5 << 6 << 7 << 1 << 0 << 8 << arma::endr - << -1 << 2 << 5 << 0 << 0 << 0 << 0 << arma::endr; + realData = { { .0, 4, 5, 7, 0, 5, 0 }, + { .0, 5, 0, 0, 1, 7, 1 }, + { .0, 5, 6, 7, 1, 0, 8 }, + { -1, 2, 5, 0, 0, 0, 0 } }; arma::sp_mat testData(realData); @@ -186,17 +186,17 @@ TEST_CASE("TestSparseSplitData", "[DETTest]") { arma::mat realData(4, 7); - realData << .0 << 4 << 5 << 7 << 0 << 5 << 0 << arma::endr - << .0 << 5 << 0 << 0 << 1 << 7 << 1 << arma::endr - << .0 << 5 << 6 << 7 << 1 << 0 << 8 << arma::endr - << -1 << 2 << 5 << 0 << 0 << 0 << 0 << arma::endr; + realData = { { .0, 4, 5, 7, 0, 5, 0 }, + { .0, 5, 0, 0, 1, 7, 1 }, + { .0, 5, 6, 7, 1, 0, 8 }, + { -1, 2, 5, 0, 0, 0, 0 } }; arma::sp_mat testData(realData); DTree testDTree(testData); arma::Col oTest(7); - oTest << 1 << 2 << 3 << 4 << 5 << 6 << 7; + oTest = { 1, 2, 3, 4, 5, 6, 7 }; size_t splitDim = 1; double trueSplitVal = .5; @@ -223,12 +223,12 @@ TEST_CASE("TestGrow", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; double rootError, lError, rError, rlError, rrError; @@ -289,12 +289,12 @@ TEST_CASE("TestPruneAndUpdate", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = {0, 1, 2, 3, 4}; DTree testDTree(testData); double alpha = testDTree.Grow(testData, oTest, false, 2, 1); alpha = testDTree.PruneAndUpdate(alpha, testData.n_cols, false); @@ -315,19 +315,19 @@ TEST_CASE("TestComputeValue", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; arma::vec q1(3), q2(3), q3(3), q4(3); - q1 << 4 << 2 << 2; - q2 << 5 << 0.25 << 6; - q3 << 5 << 3 << 7; - q4 << 2 << 3 << 3; + q1 = { 4, 2, 2 }; + q2 = { 5, 0.25, 6 }; + q3 = { 5, 3, 7 }; + q4 = { 2, 3, 3 }; arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; DTree testDTree(testData); double alpha = testDTree.Grow(testData, oTest, false, 2, 1); @@ -355,9 +355,9 @@ TEST_CASE("TestVariableImportance", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; double rootError, lError, rError, rlError, rrError; @@ -370,7 +370,7 @@ TEST_CASE("TestVariableImportance", "[DETTest]") rrError = -1.0 * exp(2 * log(2.0 / 5.0) - (log(6.5) + log(4.0) + log(2.5))); arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; DTree testDTree(testData); testDTree.Grow(testData, oTest, false, 2, 1); @@ -390,14 +390,14 @@ TEST_CASE("TestSparsePruneAndUpdate", "[DETTest]") { arma::mat realData(3, 5); - realData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + realData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; arma::sp_mat testData(realData); arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; DTree testDTree(testData); double alpha = testDTree.Grow(testData, oTest, false, 2, 1); @@ -419,22 +419,22 @@ TEST_CASE("TestSparseComputeValue", "[DETTest]") { arma::mat realData(3, 5); - realData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + realData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; arma::vec q1d(3), q2d(3), q3d(3), q4d(3); - q1d << 4 << 2 << 2; - q2d << 5 << 0.25 << 6; - q3d << 5 << 3 << 7; - q4d << 2 << 3 << 3; + q1d = { 4, 2, 2 }; + q2d = { 5, 0.25, 6 }; + q3d = { 5, 3, 7 }; + q4d = { 2, 3, 3 }; arma::sp_mat testData(realData); arma::sp_vec q1(q1d), q2(q2d), q3(q3d), q4(q4d); arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; DTree testDTree(testData); double alpha = testDTree.Grow(testData, oTest, false, 2, 1); @@ -465,9 +465,9 @@ TEST_CASE("TestTagTree", "[DETTest]") { MatType testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; DTree<>* testDTree = new DTree<>(&testData); @@ -478,9 +478,9 @@ TEST_CASE("TestFindBucket", "[DETTest]") { MatType testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; DTree<>* testDTree = new DTree<>(&testData); @@ -510,13 +510,13 @@ TEST_CASE("CopyConstructorAndOperatorTest", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; // Construct another DTree for testing the children. arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; DTree *testDTree = new DTree(testData); testDTree->Grow(testData, oTest, false, 2, 1); @@ -622,13 +622,13 @@ TEST_CASE("MoveConstructorTest", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; // Construct another DTree for testing the children. arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = { 0, 1, 2, 3, 4 }; DTree *testDTree = new DTree(testData); testDTree->Grow(testData, oTest, false, 2, 1); @@ -705,13 +705,13 @@ TEST_CASE("MoveOperatorTest", "[DETTest]") { arma::mat testData(3, 5); - testData << 4 << 5 << 7 << 3 << 5 << arma::endr - << 5 << 0 << 1 << 7 << 1 << arma::endr - << 5 << 6 << 7 << 1 << 8 << arma::endr; + testData = { { 4, 5, 7, 3, 5 }, + { 5, 0, 1, 7, 1 }, + { 5, 6, 7, 1, 8 } }; // Construct another DTree for testing the children. arma::Col oTest(5); - oTest << 0 << 1 << 2 << 3 << 4; + oTest = {0, 1, 2, 3, 4}; DTree *testDTree = new DTree(testData); testDTree->Grow(testData, oTest, false, 2, 1); diff --git a/src/mlpack/tests/distribution_test.cpp b/src/mlpack/tests/distribution_test.cpp index 7346098d43..99282e07ce 100644 --- a/src/mlpack/tests/distribution_test.cpp +++ b/src/mlpack/tests/distribution_test.cpp @@ -20,7 +20,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "test_catch_tools.hpp" using namespace mlpack; @@ -981,8 +981,8 @@ TEST_CASE("GammaDistributionProbabilityTest", "[DistributionTest]") // Combine into one 2-dimensional distribution. const arma::vec a3("2.0 3.1"), b3("0.9 1.4"); arma::mat x3(2, 2); - x3 << 2.0 << 2.94 << arma::endr - << 2.0 << 2.94; + x3 = { { 2.0, 2.94 }, + { 2.0, 2.94 } }; arma::vec prob3; // Expect that the 2-dimensional distribution returns the product of the @@ -1017,9 +1017,8 @@ TEST_CASE("GammaDistributionLogProbabilityTest", "[DistributionTest]") // Combine into one 2-dimensional distribution. const arma::vec a3("2.0 3.1"), b3("0.9 1.4"); arma::mat x3(2, 2); - x3 - << 2.0 << 2.94 << arma::endr - << 2.0 << 2.94; + x3 = { { 2.0, 2.94 }, + { 2.0, 2.94 } }; arma::vec logprob3; // Expect that the 2-dimensional distribution returns the product of the diff --git a/src/mlpack/tests/drusilla_select_test.cpp b/src/mlpack/tests/drusilla_select_test.cpp index e33ba16841..2c8e4c23bb 100644 --- a/src/mlpack/tests/drusilla_select_test.cpp +++ b/src/mlpack/tests/drusilla_select_test.cpp @@ -13,7 +13,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::neighbor; diff --git a/src/mlpack/tests/facilities_test.cpp b/src/mlpack/tests/facilities_test.cpp index 7cf1507048..65b754bb4e 100644 --- a/src/mlpack/tests/facilities_test.cpp +++ b/src/mlpack/tests/facilities_test.cpp @@ -45,9 +45,9 @@ TEST_CASE("AssertSizesTest", "[FacilitiesTest]") TEST_CASE("PairwiseDistanceTest", "[FacilitiesTest]") { arma::mat X; - X << 0 << 1 << 1 << 0 << 0 << arma::endr - << 0 << 1 << 2 << 0 << 0 << arma::endr - << 1 << 1 << 3 << 2 << 0 << arma::endr; + X = { { 0, 1, 1, 0, 0 }, + { 0, 1, 2, 0, 0 }, + { 1, 1, 3, 2, 0 } }; metric::EuclideanDistance metric; arma::mat dist = PairwiseDistances(X, metric); REQUIRE(dist(0, 0) == 0); diff --git a/src/mlpack/tests/fastmks_test.cpp b/src/mlpack/tests/fastmks_test.cpp index 8f99e4f19f..3f50d8c9b4 100644 --- a/src/mlpack/tests/fastmks_test.cpp +++ b/src/mlpack/tests/fastmks_test.cpp @@ -14,7 +14,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::tree; diff --git a/src/mlpack/tests/feedforward_network_test.cpp b/src/mlpack/tests/feedforward_network_test.cpp index 629df89749..a41d745676 100644 --- a/src/mlpack/tests/feedforward_network_test.cpp +++ b/src/mlpack/tests/feedforward_network_test.cpp @@ -19,7 +19,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "custom_layer.hpp" using namespace mlpack; @@ -47,7 +47,7 @@ void TestNetwork(ModelType& model, for (size_t i = 0; i < predictionTemp.n_cols; ++i) { prediction(i) = arma::as_scalar(arma::find( - arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)) + 1; + arma::max(predictionTemp.col(i)) == predictionTemp.col(i), 1)); } size_t correct = arma::accu(prediction == testLabels); @@ -71,8 +71,8 @@ void CheckCopyFunction(ModelType* network1, network2 = *network1; delete network1; - // Deallocating all of network1's memory, so that - // if network2 is trying to use any of that memory. + // Deallocating all of network1's memory, so that network2 does not use any + // of that memory. arma::mat predictions2; network2.Predict(trainData, predictions2); CheckMatrices(predictions1, predictions2); @@ -93,8 +93,8 @@ void CheckMoveFunction(ModelType* network1, FFN<> network2(std::move(*network1)); delete network1; - // Deallocating all of network1's memory, so that - // if network2 is trying to use any of that memory. + // Deallocating all of network1's memory, so that network2 does not use any + // of that memory. arma::mat predictions2; network2.Predict(trainData, predictions2); CheckMatrices(predictions1, predictions2); @@ -110,7 +110,8 @@ TEST_CASE("CheckCopyMovingVanillaNetworkTest", "[FeedForwardNetworkTest]") if (!data::Load("thyroid_train.csv", trainData)) FAIL("Cannot open thyroid_train.csv"); - arma::mat trainLabels = trainData.row(trainData.n_rows - 1); + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; trainData.shed_row(trainData.n_rows - 1); /* @@ -154,6 +155,236 @@ TEST_CASE("CheckCopyMovingVanillaNetworkTest", "[FeedForwardNetworkTest]") CheckMoveFunction<>(model1, trainData, trainLabels, 1); } +/** + * Check whether copying and moving network with Reparametrization is working or not. + */ +TEST_CASE("CheckCopyMovingReparametrizationNetworkTest", "[FeedForwardNetworkTest]") +{ + // Load the dataset. + arma::mat trainData; + data::Load("thyroid_train.csv", trainData, true); + + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; + trainData.shed_row(trainData.n_rows - 1); + + /* + * Construct a feed forward network with trainData.n_rows input nodes, + * followed by a linear layer and then a reparametrization layer. + */ + + FFN > *model = new FFN >; + model->Add >(trainData.n_rows, 8); + model->Add >(4, false, true, 1); + model->Add >(); + + FFN > *model1 = new FFN >; + model1->Add >(trainData.n_rows, 8); + model1->Add >(4, false, true, 1); + model1->Add >(); + + // Check whether copy constructor is working or not. + CheckCopyFunction<>(model, trainData, trainLabels, 1); + + // Check whether move constructor is working or not. + CheckMoveFunction<>(model1, trainData, trainLabels, 1); +} + +/** + * Check whether copying and moving network with linear3d is working or not. + */ +TEST_CASE("CheckCopyMovingLinear3DNetworkTest", "[FeedForwardNetworkTest]") +{ + // Load the dataset. + arma::mat trainData; + data::Load("thyroid_train.csv", trainData, true); + + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; + trainData.shed_row(trainData.n_rows - 1); + + /* + * Construct a feed forward network with trainData.n_rows input nodes, + * hiddenLayerSize hidden nodes and trainLabels.n_rows output nodes. The + * network structure looks like: + * + * Input Hidden Output + * Layer Layer Layer + * +-----+ +-----+ +-----+ + * | | | | | | + * | +------>| +------>| | + * | | +>| | +>| | + * +-----+ | +--+--+ | +-----+ + * | | + * Bias | Bias | + * Layer | Layer | + * +-----+ | +-----+ | + * | | | | | | + * | +-----+ | +-----+ + * | | | | + * +-----+ +-----+ + */ + + FFN > *model = new FFN >; + model->Add >(trainData.n_rows, 8); + model->Add >(); + model->Add >(8, 3); + model->Add >(); + + FFN > *model1 = new FFN >; + model1->Add >(trainData.n_rows, 8); + model1->Add >(); + model1->Add >(8, 3); + model1->Add >(); + + // Check whether copy constructor is working or not. + CheckCopyFunction<>(model, trainData, trainLabels, 1); + + // Check whether move constructor is working or not. + CheckMoveFunction<>(model1, trainData, trainLabels, 1); +} + +/** + * Check whether copying and moving of Noisy Linear layer is working or not. + */ +TEST_CASE("CheckCopyMovingNoisyLinearTest", "[FeedForwardNetworkTest]") +{ + // Create training input by 10x1 matrix (only 1 point). + arma::mat input = arma::randu(10, 1); + // Create training output by 1-point matrix. + arma::mat output = arma::mat("0"); + + // Check copying constructor. + FFN> *model1 = new FFN>(); + model1->Predictors() = input; + model1->Responses() = output; + model1->Add>(); + model1->Add>(10, 5); + model1->Add >(5, 1); + model1->Add>(); + + // Check whether copy constructor is working or not. + CheckCopyFunction<>(model1, input, output, 1); + + // Check moving constructor. + FFN> *model2 = new FFN>(); + model2->Predictors() = input; + model2->Responses() = output; + model2->Add>(); + model2->Add>(10, 5); + model2->Add >(5, 1); + model2->Add>(); + + // Check whether move constructor is working or not. + CheckMoveFunction<>(model2, input, output, 1); +} + +/** + * Check whether copying and moving of concatenate layer is working or not. + */ +TEST_CASE("CheckCopyMovingConcatenateTest", "[FeedForwardNetworkTest]") +{ + // Create training input by 5x5 matrix. + arma::mat input = arma::randu(10,1); + // Create training output by 1 matrix. + arma::mat output = arma::mat("1"); + + // Check copying constructor. + FFN> *model1 = new FFN>(); + model1->Predictors() = input; + model1->Responses() = output; + model1->Add>(); + model1->Add>(10, 5); + + // Create concatenate layer. + arma::mat concatMatrix = arma::ones(5, 1); + Concatenate<>* concatLayer = new Concatenate<>(); + concatLayer->Concat() = concatMatrix; + + // Add concatenate layer to the current network. + model1->Add(concatLayer); + model1->Add >(10, 5); + model1->Add>(); + + // Check whether copy constructor is working or not. + CheckCopyFunction<>(model1, input, output, 1); + + // Check moving constructor. + FFN> *model2 = new FFN>(); + model2->Predictors() = input; + model2->Responses() = output; + model2->Add>(); + model2->Add>(10, 5); + + // Create new concat layer. + Concatenate<>* concatLayer2 = new Concatenate<>(); + concatLayer2->Concat() = concatMatrix; + + // Add concatenate layer to the current network. + model2->Add(concatLayer2); + model2->Add >(10, 5); + model2->Add>(); + + // Check whether move constructor is working or not. + CheckMoveFunction<>(model2, input, output, 1); +} + +/** + * Check whether copying and moving of Dropout network is working or not. + */ +TEST_CASE("CheckCopyMovingDropoutNetworkTest", "[FeedForwardNetworkTest]") +{ + // Load the dataset. + arma::mat trainData; + data::Load("thyroid_train.csv", trainData, true); + + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; + trainData.shed_row(trainData.n_rows - 1); + + /* + * Construct a feed forward network with trainData.n_rows input nodes, + * hiddenLayerSize hidden nodes and trainLabels.n_rows output nodes. The + * network structure looks like: + * + * Input Hidden Output + * Layer Layer Layer + * +-----+ +-----+ +-----+ + * | | | | | | + * | +------>| +------>| | + * | | +>| | +>| | + * +-----+ | +--+--+ | +-----+ + * | | + * Bias | Bias | + * Layer | Layer | + * +-----+ | +-----+ | + * | | | | | | + * | +-----+ | +-----+ + * | | | | + * +-----+ +-----+ + */ + + FFN > *model = new FFN >; + model->Add >(trainData.n_rows, 8); + model->Add >(); + model->Add >(0.3); + model->Add >(8, 3); + model->Add >(); + + FFN > *model1 = new FFN >; + model1->Add >(trainData.n_rows, 8); + model1->Add >(); + model1->Add >(0.3); + model1->Add >(8, 3); + model1->Add >(); + + // Check whether copy constructor is working or not. + CheckCopyFunction<>(model, trainData, trainLabels, 1); + + // Check whether move constructor is working or not. + CheckMoveFunction<>(model1, trainData, trainLabels, 1); +} + /** * Train the vanilla network on a larger dataset. */ @@ -166,6 +397,7 @@ TEST_CASE("FFVanillaNetworkTest", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // Labels should be from 0 to numClasses - 1. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -173,6 +405,7 @@ TEST_CASE("FFVanillaNetworkTest", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // Labels should be from 0 to numClasses - 1. /* * Construct a feed forward network with trainData.n_rows input nodes, @@ -216,7 +449,6 @@ TEST_CASE("FFVanillaNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model1; model1.Add >(dataset.n_rows, 10); @@ -238,7 +470,6 @@ TEST_CASE("ForwardBackwardTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model; model.Add >(dataset.n_rows, 50); @@ -285,7 +516,7 @@ TEST_CASE("ForwardBackwardTest", "[FeedForwardNetworkTest]") for (size_t i = 0; i < currentResuls.n_cols; ++i) { prediction(i) = arma::as_scalar(arma::find( - arma::max(currentResuls.col(i)) == currentResuls.col(i), 1)) + 1; + arma::max(currentResuls.col(i)) == currentResuls.col(i), 1)); } size_t correct = arma::accu(prediction == currentLabels); @@ -315,6 +546,7 @@ TEST_CASE("DropoutNetworkTest", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // Labels should be from 0 to numClasses - 1. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -322,6 +554,7 @@ TEST_CASE("DropoutNetworkTest", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // Labels should be from 0 to numClasses - 1. /* * Construct a feed forward network with trainData.n_rows input nodes, @@ -367,7 +600,6 @@ TEST_CASE("DropoutNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model1; model1.Add >(dataset.n_rows, 10); @@ -393,7 +625,6 @@ TEST_CASE("HighwayNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model; model.Add >(dataset.n_rows, 10); @@ -418,6 +649,7 @@ TEST_CASE("DropConnectNetworkTest", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The range should be between 0 and numClasses - 1. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -425,6 +657,7 @@ TEST_CASE("DropConnectNetworkTest", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The range should be between 0 and numClasses - 1. /* * Construct a feed forward network with trainData.n_rows input nodes, @@ -470,7 +703,6 @@ TEST_CASE("DropConnectNetworkTest", "[FeedForwardNetworkTest]") arma::mat labels = arma::zeros(1, dataset.n_cols); labels.submat(0, labels.n_cols / 2, 0, labels.n_cols - 1).fill(1); - labels += 1; FFN > model1; model1.Add >(dataset.n_rows, 10); @@ -509,6 +741,7 @@ TEST_CASE("FFSerializationTest", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses - 1. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -516,6 +749,7 @@ TEST_CASE("FFSerializationTest", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses - 1. // Vanilla neural net with logistic activation function. // Because 92% of the patients are not hyperthyroid the neural @@ -560,6 +794,7 @@ TEST_CASE("CustomLayerTest", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses - 1. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -567,6 +802,7 @@ TEST_CASE("CustomLayerTest", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses - 1. FFN, RandomInitialization, CustomLayer<> > model; model.Add >(trainData.n_rows, 8); @@ -641,6 +877,7 @@ TEST_CASE("FFNTrainReturnObjective", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -648,6 +885,7 @@ TEST_CASE("FFNTrainReturnObjective", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses. // Vanilla neural net with logistic activation function. // Because 92% of the patients are not hyperthyroid the neural @@ -713,6 +951,7 @@ TEST_CASE("OptimizerTest", "[FeedForwardNetworkTest]") arma::mat trainLabels = trainData.row(trainData.n_rows - 1); trainData.shed_row(trainData.n_rows - 1); + trainLabels -= 1; // The labels should be between 0 and numClasses. arma::mat testData; if (!data::Load("thyroid_test.csv", testData)) @@ -720,6 +959,7 @@ TEST_CASE("OptimizerTest", "[FeedForwardNetworkTest]") arma::mat testLabels = testData.row(testData.n_rows - 1); testData.shed_row(testData.n_rows - 1); + testLabels -= 1; // The labels should be between 0 and numClasses. FFN, RandomInitialization, CustomLayer<> > model; model.Add >(trainData.n_rows, 8); @@ -730,3 +970,41 @@ TEST_CASE("OptimizerTest", "[FeedForwardNetworkTest]") ens::DE opt(200, 1000, 0.6, 0.8, 1e-5); model.Train(trainData, trainLabels, opt); } + +/** + * Test to see if an exception is thrown when input with + * wrong shape is provided to a FFN. + */ +TEST_CASE("FFNCheckInputShapeTest", "[FeedForwardNetworkTest]") +{ + // Load the dataset. + arma::mat trainData; + if (!data::Load("thyroid_train.csv", trainData)) + FAIL("Cannot open thyroid_train.csv"); + + // Normalize labels to [0, 2]. + arma::mat trainLabels = trainData.row(trainData.n_rows - 1) - 1; + trainData.shed_row(trainData.n_rows - 1); + + arma::mat testData; + data::Load("thyroid_test.csv", testData, true); + + arma::mat testLabels = testData.row(testData.n_rows - 1) - 1; + testData.shed_row(testData.n_rows - 1); + + FFN, RandomInitialization, CustomLayer<> > model; + // Purposely putting wrong input shape so that error is thrown. + model.Add >(trainData.n_rows - 3, 8); + model.Add >(); + model.Add >(8, 3); + model.Add >(); + + std::string expectedMsg = "FFN<>::Train(): "; + expectedMsg += "the first layer of the network expects "; + expectedMsg += std::to_string(trainData.n_rows - 3) + " elements, "; + expectedMsg += "but the input has " + std::to_string(trainData.n_rows) + " dimensions! "; + + ens::DE opt(200, 1000, 0.6, 0.8, 1e-5); + + REQUIRE_THROWS_AS(model.Train(trainData, trainLabels, opt), std::logic_error); +} diff --git a/src/mlpack/tests/function_test.cpp b/src/mlpack/tests/function_test.cpp deleted file mode 100644 index 5486ac1e87..0000000000 --- a/src/mlpack/tests/function_test.cpp +++ /dev/null @@ -1,681 +0,0 @@ -/** - * @file tests/function_test.cpp - * @author Ryan Curtin - * @author Shikhar Bhardwaj - * - * Test the Function<> class to see that it properly adds functionality. - * - * mlpack is free software; you may redistribute it and/or modify it under the - * terms of the 3-clause BSD license. You should have received a copy of the - * 3-clause BSD license along with mlpack. If not, see - * http://www.opensource.org/licenses/BSD-3-Clause for more information. - */ -#include -#include -#include -#include -#include -#include - -#include -#include "test_tools.hpp" - -using namespace mlpack; -using namespace mlpack::optimization; -using namespace ens::traits; // For some SFINAE checks. -using namespace mlpack::regression; - -/** - * Utility class with no functions. - */ -class EmptyTestFunction { }; - -/** - * Utility class with Evaluate() but no Evaluate(). - */ -class EvaluateTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) - { - return arma::accu(coordinates); - } - - double Evaluate(const arma::mat& coordinates, - const size_t begin, - const size_t batchSize) - { - return arma::accu(coordinates) + begin + batchSize; - } -}; - -/** - * Utility class with Gradient() but no Evaluate(). - */ -class GradientTestFunction -{ - public: - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - void Gradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -/** - * Utility class with Gradient() and Evaluate(). - */ -class EvaluateGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) - { - return arma::accu(coordinates); - } - - double Evaluate(const arma::mat& coordinates, - const size_t /* begin */, - const size_t /* batchSize */) - { - return arma::accu(coordinates); - } - - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - void Gradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -/** - * Utility class with EvaluateWithGradient(). - */ -class EvaluateWithGradientTestFunction -{ - public: - double EvaluateWithGradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } - - double EvaluateWithGradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } -}; - -/** - * Utility class with all three functions. - */ -class EvaluateAndWithGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) - { - return arma::accu(coordinates); - } - - double Evaluate(const arma::mat& coordinates, - const size_t begin, - const size_t batchSize) - { - return arma::accu(coordinates) + batchSize + begin; - } - - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - void Gradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } - - double EvaluateWithGradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } - - double EvaluateWithGradient(const arma::mat& coordinates, - const size_t /* begin */, - arma::mat& gradient, - const size_t /* batchSize */) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - return arma::accu(coordinates); - } -}; - -/** - * Utility class with const Evaluate() and non-const Gradient(). - */ -class EvaluateAndNonConstGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) const - { - return arma::accu(coordinates); - } - - void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -/** - * Utility class with const Evaluate() and non-const Gradient(). - */ -class EvaluateAndStaticGradientTestFunction -{ - public: - double Evaluate(const arma::mat& coordinates) const - { - return arma::accu(coordinates); - } - - static void Gradient(const arma::mat& coordinates, arma::mat& gradient) - { - gradient.ones(coordinates.n_rows, coordinates.n_cols); - } -}; - -BOOST_AUTO_TEST_SUITE(FunctionTest); - -/** - * Make sure that an empty class doesn't have any methods added to it. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientEmptyTest) -{ - const bool hasEvaluate = HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Evaluate(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientEvaluateOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientGradientOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we add EvaluateWithGradient() when we have both Evaluate() and - * Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientBothTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add Evaluate() and Gradient() when we have only - * EvaluateWithGradient(). - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientEvaluateWithGradientTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add no methods when we already have all three. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientAllThreeTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -BOOST_AUTO_TEST_CASE(LogisticRegressionEvaluateWithGradientTest) -{ - const bool hasEvaluate = - HasEvaluate>, - EvaluateConstForm>::value; - const bool hasGradient = - HasGradient>, - GradientConstForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient>, - EvaluateWithGradientConstForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -BOOST_AUTO_TEST_CASE(SDPTest) -{ - typedef AugLagrangianFunction>> FunctionType; - - const bool hasEvaluate = - HasEvaluate, EvaluateConstForm>::value; - const bool hasGradient = - HasGradient, GradientConstForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientConstForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure that an empty class doesn't have any methods added to it. - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientEmptyTest) -{ - const bool hasEvaluate = HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Evaluate(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientEvaluateOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, false); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we don't add any functions if we only have Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientGradientOnlyTest) -{ - const bool hasEvaluate = HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, false); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, false); -} - -/** - * Make sure we add EvaluateWithGradient() when we have both Evaluate() and - * Gradient(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientBothTest) -{ - const bool hasEvaluate = - HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = - HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add Evaluate() and Gradient() when we have only - * EvaluateWithGradient(). - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWGradientEvaluateWithGradientTest) -{ - const bool hasEvaluate = - HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = - HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - Function f; - arma::mat coordinates(10, 10, arma::fill::ones); - arma::mat gradient; - f.Gradient(coordinates, 0, gradient, 5); - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we add no methods when we already have all three. - */ -BOOST_AUTO_TEST_CASE(AddDecomposableEvaluateWithGradientAllThreeTest) -{ - const bool hasEvaluate = - HasEvaluate, - DecomposableEvaluateForm>::value; - const bool hasGradient = - HasGradient, - DecomposableGradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - DecomposableEvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we can properly create EvaluateWithGradient() even when one of the - * functions is non-const. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientMixedTypesTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateConstForm>::value; - const bool hasGradient = - HasGradient, - GradientForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -/** - * Make sure we can properly create EvaluateWithGradient() even when one of the - * functions is static. - */ -BOOST_AUTO_TEST_CASE(AddEvaluateWithGradientMixedTypesStaticTest) -{ - const bool hasEvaluate = - HasEvaluate, - EvaluateConstForm>::value; - const bool hasGradient = - HasGradient, - GradientStaticForm>::value; - const bool hasEvaluateWithGradient = - HasEvaluateWithGradient, - EvaluateWithGradientConstForm>::value; - - BOOST_REQUIRE_EQUAL(hasEvaluate, true); - BOOST_REQUIRE_EQUAL(hasGradient, true); - BOOST_REQUIRE_EQUAL(hasEvaluateWithGradient, true); -} - -class A -{ - public: - size_t NumFunctions() const; - size_t NumFeatures() const; - double Evaluate(const arma::mat&, const size_t, const size_t) const; - void Gradient(const arma::mat&, const size_t, arma::mat&, const size_t) const; - void Gradient(const arma::mat&, const size_t, arma::sp_mat&, const size_t) - const; - void PartialGradient(const arma::mat&, const size_t, arma::sp_mat&) const; -}; - -class B -{ - public: - size_t NumFunctions(); - size_t NumFeatures(); - double Evaluate(const arma::mat&, const size_t, const size_t); - void Gradient(const arma::mat&, const size_t, arma::mat&, const size_t); - void Gradient(const arma::mat&, const size_t, arma::sp_mat&, const size_t); - void PartialGradient(const arma::mat&, const size_t, arma::sp_mat&); -}; - -class C -{ - public: - size_t NumConstraints() const; - double Evaluate(const arma::mat&) const; - void Gradient(const arma::mat&, arma::mat&) const; - double EvaluateConstraint(const size_t, const arma::mat&) const; - void GradientConstraint(const size_t, const arma::mat&, arma::mat&) const; -}; - -class D -{ - public: - size_t NumConstraints(); - double Evaluate(const arma::mat&); - void Gradient(const arma::mat&, arma::mat&); - double EvaluateConstraint(const size_t, const arma::mat&); - void GradientConstraint(const size_t, const arma::mat&, arma::mat&); -}; - - -/** - * Test the correctness of the static check for DecomposableFunctionType API. - */ -BOOST_AUTO_TEST_CASE(DecomposableFunctionTypeCheckTest) -{ - static_assert(CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - static_assert(CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - static_assert(!CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - static_assert(!CheckNumFunctions::value, - "CheckNumFunctions static check failed."); - - static_assert(CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - static_assert(CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - static_assert(!CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - static_assert(!CheckDecomposableEvaluate::value, - "CheckDecomposableEvaluate static check failed."); - - static_assert(CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); - static_assert(CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); - static_assert(!CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); - static_assert(!CheckDecomposableGradient::value, - "CheckDecomposableGradient static check failed."); -} - -/** - * Test the correctness of the static check for LagrangianFunctionType API. - */ -BOOST_AUTO_TEST_CASE(LagrangianFunctionTypeCheckTest) -{ - static_assert(!CheckEvaluate::value, "CheckEvaluate static check failed."); - static_assert(!CheckEvaluate::value, "CheckEvaluate static check failed."); - static_assert(CheckEvaluate::value, "CheckEvaluate static check failed."); - static_assert(CheckEvaluate::value, "CheckEvaluate static check failed."); - - static_assert(!CheckGradient::value, "CheckGradient static check failed."); - static_assert(!CheckGradient::value, "CheckGradient static check failed."); - static_assert(CheckGradient::value, "CheckGradient static check failed."); - static_assert(CheckGradient::value, "CheckGradient static check failed."); - - static_assert(!CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - static_assert(!CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - static_assert(CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - static_assert(CheckNumConstraints::value, - "CheckNumConstraints static check failed."); - - static_assert(!CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - static_assert(!CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - static_assert(CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - static_assert(CheckEvaluateConstraint::value, - "CheckEvaluateConstraint static check failed."); - - static_assert(!CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); - static_assert(!CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); - static_assert(CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); - static_assert(CheckGradientConstraint::value, - "CheckGradientConstraint static check failed."); -} - -/** - * Test the correctness of the static check for SparseFunctionType API. - */ -BOOST_AUTO_TEST_CASE(SparseFunctionTypeCheckTest) -{ - static_assert(CheckSparseGradient::value, - "CheckSparseGradient static check failed."); - static_assert(CheckSparseGradient::value, - "CheckSparseGradient static check failed."); - static_assert(!CheckSparseGradient::value, - "CheckSparseGradient static check failed."); - static_assert(!CheckSparseGradient::value, - "CheckSparseGradient static check failed."); -} - -/** - * Test the correctness of the static check for SparseFunctionType API. - */ -BOOST_AUTO_TEST_CASE(ResolvableFunctionTypeCheckTest) -{ - static_assert(CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - static_assert(CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - static_assert(!CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - static_assert(!CheckNumFeatures::value, - "CheckNumFeatures static check failed."); - - static_assert(CheckPartialGradient::value, - "CheckPartialGradient static check failed."); - static_assert(CheckPartialGradient::value, - "CheckPartialGradient static check failed."); - static_assert(!CheckPartialGradient::value, - "CheckPartialGradient static check failed."); - static_assert(!CheckPartialGradient::value, - "CheckPartialGradient static check failed."); -} - -BOOST_AUTO_TEST_SUITE_END(); diff --git a/src/mlpack/tests/gan_test.cpp b/src/mlpack/tests/gan_test.cpp index db4a397eac..a7350cb1f1 100644 --- a/src/mlpack/tests/gan_test.cpp +++ b/src/mlpack/tests/gan_test.cpp @@ -22,7 +22,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::ann; diff --git a/src/mlpack/tests/hmm_test.cpp b/src/mlpack/tests/hmm_test.cpp index da1debd3a2..03a22f31e6 100644 --- a/src/mlpack/tests/hmm_test.cpp +++ b/src/mlpack/tests/hmm_test.cpp @@ -800,6 +800,7 @@ TEST_CASE("GaussianHMMPredictTest", "[HMMTest]") emission.Covariance(cov.at(i)); } + // 100 2D observations. arma::mat obs = { { -0.0424, -0.0395, -0.0336, -0.0294, -0.0299, -0.032, -0.0289, -0.0148, @@ -832,9 +833,261 @@ TEST_CASE("GaussianHMMPredictTest", "[HMMTest]") 0.0521, 0.0313, 0.0188, 0.0113, 0.0068, 0.0042, 0.0026, 0.0018, 0.0014 } }; + + //100 pre-calculated emission probabilities each for 10 states + std::vector emissionProb = { + { -2.7301e+03, 1.7874e+00, -1.9428e+00, -3.6365e+00, -4.0397e-01, + -1.5115e-01, -1.0328e+00, -1.1071e+00, 5.2876e-01, -1.0643e-01 }, + { -2.3684e+03, 1.8059e+00, -2.2058e+00, -4.0514e+00, -5.0935e-01, + -2.1126e-01, -1.1962e+00, -1.2567e+00, 4.1247e-01, -3.0199e-01 }, + { -1.7117e+03, 1.7981e+00, -2.5275e+00, -4.5634e+00, -6.4839e-01, + -2.9579e-01, -1.4000e+00, -1.4461e+00, 2.3795e-01, -5.5622e-01 }, + { -1.3089e+03, 1.7393e+00, -2.8685e+00, -5.0996e+00, -8.0288e-01, + -3.9863e-01, -1.6229e+00, -1.6478e+00, 2.3300e-02, -8.6617e-01 }, + { -1.3541e+03, 1.6414e+00, -3.1971e+00, -5.6043e+00, -9.5605e-01, + -5.1013e-01, -1.8460e+00, -1.8395e+00, -2.0603e-01, -1.2176e+00 }, + { -1.5521e+03, 1.5367e+00, -3.4806e+00, -6.0349e+00, -1.0924e+00, + -6.1426e-01, -2.0436e+00, -2.0051e+00, -4.2045e-01, -1.5500e+00 }, + { -1.2647e+03, 1.4680e+00, -3.6577e+00, -6.3144e+00, -1.1823e+00, + -6.8009e-01, -2.1646e+00, -2.1147e+00, -5.6512e-01, -1.7360e+00 }, + { -3.2650e+02, 1.4646e+00, -3.6693e+00, -6.3649e+00, -1.1957e+00, + -6.7711e-01, -2.1592e+00, -2.1377e+00, -5.8400e-01, -1.6543e+00 }, + { -1.3035e+02, 1.5123e+00, -3.5018e+00, -6.1593e+00, -1.1254e+00, + -6.0037e-01, -2.0181e+00, -2.0646e+00, -4.6413e-01, -1.3011e+00 }, + { -2.6279e+03, 1.5809e+00, -3.1559e+00, -5.6861e+00, -9.7699e-01, + -4.5903e-01, -1.7490e+00, -1.8956e+00, -2.2135e-01, -7.2772e-01 }, + { -9.6164e+03, 1.6193e+00, -2.6708e+00, -4.9944e+00, -7.8159e-01, + -2.8441e-01, -1.3909e+00, -1.6574e+00, 7.8411e-02, -5.0595e-02 }, + { -2.0944e+04, 1.5980e+00, -2.1094e+00, -4.1681e+00, -5.8143e-01, + -1.2105e-01, -1.0055e+00, -1.3879e+00, 3.4591e-01, 5.6464e-01 }, + { -3.3843e+04, 1.5241e+00, -1.5331e+00, -3.2977e+00, -4.1342e-01, + -8.0772e-03, -6.5026e-01, -1.1226e+00, 5.0244e-01, 9.8522e-01 }, + { -4.6678e+04, 1.3796e+00, -9.8223e-01, -2.4507e+00, -3.0368e-01, + 3.0609e-02, -3.5787e-01, -8.8913e-01, 4.9234e-01, 1.1530e+00 }, + { -6.0839e+04, 1.1013e+00, -4.8302e-01, -1.6712e+00, -2.7541e-01, + -2.2814e-02, -1.4691e-01, -7.1095e-01, 2.6698e-01, 1.0338e+00 }, + { -7.8940e+04, 6.2341e-01, -6.4826e-02, -1.0034e+00, -3.5198e-01, + -1.8517e-01, -3.7803e-02, -6.1240e-01, -2.1704e-01, 5.8353e-01 }, + { -1.0182e+05, -8.9362e-02, 2.5888e-01, -4.6429e-01, -5.5297e-01, + -4.7752e-01, -4.9871e-02, -6.0739e-01, -1.0089e+00, -2.6587e-01 }, + { -1.2437e+05, -9.8625e-01, 4.7236e-01, -8.1256e-02, -8.8097e-01, + -9.0979e-01, -2.0039e-01, -6.9837e-01, -2.1229e+00, -1.5424e+00 }, + { -1.3878e+05, -1.9546e+00, 5.6976e-01, 1.2361e-01, -1.3043e+00, + -1.4534e+00, -4.7690e-01, -8.6807e-01, -3.4831e+00, -3.1393e+00 }, + { -1.3979e+05, -2.8896e+00, 5.6962e-01, 1.6577e-01, -1.7631e+00, + -2.0456e+00, -8.3102e-01, -1.0792e+00, -4.9380e+00, -4.8430e+00 }, + { -1.2717e+05, -3.7474e+00, 5.0493e-01, 9.2444e-02, -2.1969e+00, + -2.6201e+00, -1.2028e+00, -1.2907e+00, -6.3319e+00, -6.4416e+00 }, + { -1.0548e+05, -4.5565e+00, 4.0397e-01, -4.5775e-02, -2.5711e+00, + -3.1354e+00, -1.5493e+00, -1.4771e+00, -7.5697e+00, -7.8170e+00 }, + { -8.0621e+04, -5.3691e+00, 2.8365e-01, -2.1252e-01, -2.8783e+00, + -3.5784e+00, -1.8523e+00, -1.6312e+00, -8.6245e+00, -8.9480e+00 }, + { -5.6310e+04, -6.2411e+00, 1.5008e-01, -3.9022e-01, -3.1294e+00, + -3.9597e+00, -2.1142e+00, -1.7569e+00, -9.5239e+00, -9.8785e+00 }, + { -3.4173e+04, -7.2306e+00, 3.0396e-03, -5.7242e-01, -3.3347e+00, + -4.2928e+00, -2.3417e+00, -1.8583e+00, -1.0301e+01, -1.0652e+01 }, + { -1.5877e+04, -8.3900e+00, -1.5871e-01, -7.5362e-01, -3.4959e+00, + -4.5816e+00, -2.5356e+00, -1.9353e+00, -1.0963e+01, -1.1284e+01 }, + { -3.3829e+03, -9.7572e+00, -3.3006e-01, -9.1554e-01, -3.5912e+00, + -4.8035e+00, -2.6770e+00, -1.9722e+00, -1.1452e+01, -1.1714e+01 }, + { -5.6088e+02, -1.1394e+01, -5.0305e-01, -1.0261e+00, -3.5777e+00, + -4.9138e+00, -2.7301e+00, -1.9403e+00, -1.1653e+01, -1.1829e+01 }, + { -1.4303e+04, -1.3346e+01, -6.7336e-01, -1.0564e+00, -3.4266e+00, + -4.8757e+00, -2.6690e+00, -1.8219e+00, -1.1470e+01, -1.1561e+01 }, + { -4.9066e+04, -1.5534e+01, -8.4176e-01, -1.0079e+00, -3.1636e+00, + -4.7028e+00, -2.5116e+00, -1.6369e+00, -1.0937e+01, -1.0995e+01 }, + { -9.9717e+04, -1.7702e+01, -1.0039e+00, -9.1443e-01, -2.8597e+00, + -4.4595e+00, -2.3138e+00, -1.4339e+00, -1.0224e+01, -1.0331e+01 }, + { -1.5886e+05, -1.9676e+01, -1.1535e+00, -7.9762e-01, -2.5479e+00, + -4.1805e+00, -2.1039e+00, -1.2332e+00, -9.4233e+00, -9.6530e+00 }, + { -2.2947e+05, -2.1635e+01, -1.3117e+00, -6.6325e-01, -2.2133e+00, + -3.8587e+00, -1.8780e+00, -1.0253e+00, -8.5051e+00, -8.9416e+00 }, + { -3.1968e+05, -2.3792e+01, -1.5095e+00, -5.1672e-01, -1.8381e+00, + -3.4770e+00, -1.6312e+00, -8.0190e-01, -7.4108e+00, -8.1836e+00 }, + { -4.3323e+05, -2.6183e+01, -1.7728e+00, -3.8390e-01, -1.4394e+00, + -3.0487e+00, -1.3857e+00, -5.7953e-01, -6.1647e+00, -7.4521e+00 }, + { -5.6473e+05, -2.8589e+01, -2.1061e+00, -3.0168e-01, -1.0547e+00, + -2.6054e+00, -1.1773e+00, -3.8708e-01, -4.8475e+00, -6.8476e+00 }, + { -6.9974e+05, -3.0612e+01, -2.4921e+00, -3.0913e-01, -7.2535e-01, + -2.1849e+00, -1.0419e+00, -2.5359e-01, -3.5677e+00, -6.4479e+00 }, + { -8.0655e+05, -3.1539e+01, -2.8524e+00, -4.2185e-01, -4.8692e-01, + -1.8260e+00, -9.9484e-01, -1.9514e-01, -2.4629e+00, -6.2373e+00 }, + { -8.5216e+05, -3.0655e+01, -3.0833e+00, -6.1881e-01, -3.3169e-01, + -1.5249e+00, -1.0091e+00, -1.9595e-01, -1.5717e+00, -6.0513e+00 }, + { -8.2392e+05, -2.7811e+01, -3.1362e+00, -8.7526e-01, -2.3459e-01, + -1.2631e+00, -1.0480e+00, -2.3278e-01, -8.7344e-01, -5.7341e+00 }, + { -7.3612e+05, -2.3582e+01, -3.0425e+00, -1.1744e+00, -1.7841e-01, + -1.0351e+00, -1.0893e+00, -2.9210e-01, -3.4780e-01, -5.2495e+00 }, + { -6.1397e+05, -1.8706e+01, -2.8744e+00, -1.5195e+00, -1.5516e-01, + -8.3816e-01, -1.1304e+00, -3.7330e-01, 3.7744e-02, -4.6424e+00 }, + { -4.8041e+05, -1.3799e+01, -2.7054e+00, -1.9262e+00, -1.6637e-01, + -6.7558e-01, -1.1810e+00, -4.8405e-01, 3.0197e-01, -3.9898e+00 }, + { -3.4790e+05, -9.2300e+00, -2.5518e+00, -2.3683e+00, -2.0524e-01, + -5.4582e-01, -1.2297e+00, -6.1666e-01, 4.5657e-01, -3.3063e+00 }, + { -2.2370e+05, -5.1887e+00, -2.3941e+00, -2.7911e+00, -2.5500e-01, + -4.3560e-01, -1.2487e+00, -7.5224e-01, 5.3161e-01, -2.5570e+00 }, + { -1.1273e+05, -1.7195e+00, -2.2258e+00, -3.1794e+00, -3.0915e-01, + -3.2974e-01, -1.2221e+00, -8.8867e-01, 5.5755e-01, -1.7017e+00 }, + { -2.8363e+04, 9.0588e-01, -2.0601e+00, -3.5233e+00, -3.7171e-01, + -2.2162e-01, -1.1370e+00, -1.0334e+00, 5.4434e-01, -7.3209e-01 }, + { -1.2122e+03, 1.9784e+00, -1.9455e+00, -3.7971e+00, -4.5862e-01, + -1.2081e-01, -9.8979e-01, -1.2000e+00, 4.7839e-01, 2.5783e-01 }, + { -7.1694e+04, 5.6327e-01, -2.0051e+00, -4.0345e+00, -6.1306e-01, + -6.6602e-02, -8.2833e-01, -1.4287e+00, 3.0684e-01, 1.0022e+00 }, + { -2.6198e+05, -3.8345e+00, -2.3396e+00, -4.2798e+00, -8.6900e-01, + -9.8822e-02, -7.1489e-01, -1.7486e+00, -1.6219e-02, 1.2358e+00 }, + { -5.5328e+05, -1.0687e+01, -2.9124e+00, -4.5058e+00, -1.2121e+00, + -2.2259e-01, -6.7273e-01, -2.1347e+00, -4.7585e-01, 8.8080e-01 }, + { -8.9436e+05, -1.8602e+01, -3.5518e+00, -4.6037e+00, -1.5911e+00, + -4.1140e-01, -6.7958e-01, -2.5173e+00, -1.0137e+00, 3.7886e-02 }, + { -1.2162e+06, -2.5781e+01, -4.0541e+00, -4.4848e+00, -1.9485e+00, + -6.3137e-01, -7.0903e-01, -2.8240e+00, -1.5699e+00, -1.1063e+00 }, + { -1.4436e+06, -3.0414e+01, -4.2395e+00, -4.1197e+00, -2.2265e+00, + -8.4654e-01, -7.3852e-01, -2.9921e+00, -2.0869e+00, -2.2970e+00 }, + { -1.5227e+06, -3.1337e+01, -3.9989e+00, -3.5197e+00, -2.3836e+00, + -1.0315e+00, -7.4887e-01, -2.9823e+00, -2.5313e+00, -3.3017e+00 }, + { -1.4386e+06, -2.8472e+01, -3.3472e+00, -2.7563e+00, -2.4087e+00, + -1.1801e+00, -7.3524e-01, -2.7971e+00, -2.9034e+00, -3.9803e+00 }, + { -1.2257e+06, -2.2958e+01, -2.4364e+00, -1.9521e+00, -2.3275e+00, + -1.3043e+00, -7.0875e-01, -2.4858e+00, -3.2295e+00, -4.3252e+00 }, + { -9.4813e+05, -1.6527e+01, -1.4675e+00, -1.2121e+00, -2.1965e+00, + -1.4367e+00, -6.9389e-01, -2.1228e+00, -3.5740e+00, -4.4844e+00 }, + { -6.6589e+05, -1.0680e+01, -6.1313e-01, -6.1440e-01, -2.0638e+00, + -1.5984e+00, -7.0917e-01, -1.7727e+00, -3.9726e+00, -4.5979e+00 }, + { -4.1809e+05, -6.2975e+00, 3.1651e-02, -1.8731e-01, -1.9586e+00, + -1.7982e+00, -7.6241e-01, -1.4730e+00, -4.4365e+00, -4.7645e+00 }, + { -2.2534e+05, -3.7546e+00, 4.3188e-01, 7.1872e-02, -1.8959e+00, + -2.0366e+00, -8.5455e-01, -1.2417e+00, -4.9637e+00, -5.0424e+00 }, + { -9.4330e+04, -3.0403e+00, 5.9517e-01, 1.8422e-01, -1.8702e+00, + -2.2952e+00, -9.7314e-01, -1.0776e+00, -5.5109e+00, -5.4155e+00 }, + { -2.1454e+04, -3.9202e+00, 5.5647e-01, 1.8381e-01, -1.8704e+00, + -2.5578e+00, -1.1056e+00, -9.6899e-01, -6.0419e+00, -5.8579e+00 }, + { -31.4830, -6.0953, 0.3567, 0.1044, -1.8840, -2.8086, -1.2397, + -0.9026, -6.5224, -6.3374 }, + { -2.2442e+04, -9.2735e+00, 3.4960e-02, -2.1605e-02, -1.8931e+00, + -3.0282e+00, -1.3611e+00, -8.6066e-01, -6.9076e+00, -6.8075e+00 }, + { -8.1676e+04, -1.3138e+01, -3.6831e-01, -1.6104e-01, -1.8763e+00, + -3.1905e+00, -1.4522e+00, -8.2511e-01, -7.1362e+00, -7.2081e+00 }, + { -1.6865e+05, -1.7287e+01, -8.0643e-01, -2.8264e-01, -1.8178e+00, + -3.2726e+00, -1.4987e+00, -7.8144e-01, -7.1585e+00, -7.4877e+00 }, + { -2.7001e+05, -2.1213e+01, -1.2247e+00, -3.6116e-01, -1.7095e+00, + -3.2596e+00, -1.4928e+00, -7.2002e-01, -6.9485e+00, -7.6058e+00 }, + { -3.7506e+05, -2.4628e+01, -1.5962e+00, -3.9394e-01, -1.5583e+00, + -3.1610e+00, -1.4428e+00, -6.4101e-01, -6.5350e+00, -7.5763e+00 }, + { -4.7871e+05, -2.7455e+01, -1.9194e+00, -3.9090e-01, -1.3720e+00, + -2.9900e+00, -1.3606e+00, -5.4763e-01, -5.9492e+00, -7.4279e+00 }, + { -5.7329e+05, -2.9501e+01, -2.1830e+00, -3.6323e-01, -1.1594e+00, + -2.7564e+00, -1.2564e+00, -4.4501e-01, -5.2194e+00, -7.1738e+00 }, + { -6.4968e+05, -3.0560e+01, -2.3747e+00, -3.2775e-01, -9.3375e-01, + -2.4742e+00, -1.1428e+00, -3.4141e-01, -4.3880e+00, -6.8281e+00 }, + { -6.9933e+05, -3.0501e+01, -2.4875e+00, -3.0631e-01, -7.1262e-01, + -2.1653e+00, -1.0343e+00, -2.4789e-01, -3.5174e+00, -6.4120e+00 }, + { -7.1802e+05, -2.9350e+01, -2.5271e+00, -3.2061e-01, -5.1194e-01, + -1.8521e+00, -9.4328e-01, -1.7450e-01, -2.6686e+00, -5.9486e+00 }, + { -7.0553e+05, -2.7236e+01, -2.5060e+00, -3.8730e-01, -3.4217e-01, + -1.5515e+00, -8.7707e-01, -1.2819e-01, -1.8857e+00, -5.4542e+00 }, + { -6.6569e+05, -2.4393e+01, -2.4435e+00, -5.1663e-01, -2.1031e-01, + -1.2775e+00, -8.3941e-01, -1.1339e-01, -1.2023e+00, -4.9470e+00 }, + { -6.1301e+05, -2.1269e+01, -2.3992e+00, -7.3370e-01, -1.2064e-01, + -1.0383e+00, -8.4300e-01, -1.3855e-01, -6.1878e-01, -4.4864e+00 }, + { -5.6195e+05, -1.8233e+01, -2.4507e+00, -1.0921e+00, -8.5743e-02, + -8.4467e-01, -9.1749e-01, -2.2378e-01, -1.3441e-01, -4.1677e+00 }, + { -5.0308e+05, -1.5078e+01, -2.5824e+00, -1.6122e+00, -1.1850e-01, + -7.0720e-01, -1.0690e+00, -3.7916e-01, 2.0948e-01, -3.9737e+00 }, + { -4.2417e+05, -1.1613e+01, -2.7333e+00, -2.2592e+00, -2.1392e-01, + -6.2529e-01, -1.2691e+00, -5.9232e-01, 3.8196e-01, -3.8021e+00 }, + { -3.4311e+05, -8.4490e+00, -2.9262e+00, -2.9840e+00, -3.6201e-01, + -6.0612e-01, -1.5036e+00, -8.4657e-01, 3.8172e-01, -3.6994e+00 }, + { -2.6553e+05, -5.7959e+00, -3.0657e+00, -3.6135e+00, -5.0450e-01, + -6.1056e-01, -1.6893e+00, -1.0726e+00, 2.9263e-01, -3.5310e+00 }, + { -1.6581e+05, -2.8806e+00, -2.9242e+00, -3.9480e+00, -5.5108e-01, + -5.3603e-01, -1.6743e+00, -1.1832e+00, 2.8121e-01, -2.8215e+00 }, + { -6.3112e+04, -4.4355e-02, -2.4673e+00, -3.8848e+00, -4.8010e-01, + -3.5415e-01, -1.4075e+00, -1.1547e+00, 4.0803e-01, -1.5227e+00 }, + { -5.4196e+03, 1.6750e+00, -1.9272e+00, -3.5655e+00, -3.8433e-01, + -1.5578e-01, -1.0312e+00, -1.0745e+00, 5.4628e-01, -1.8838e-01 }, + { -7.9742e+03, 1.9542e+00, -1.5297e+00, -3.2224e+00, -3.5023e-01, + -2.9557e-02, -7.1541e-01, -1.0340e+00, 5.7335e-01, 7.0234e-01 }, + { -4.6838e+04, 1.3383e+00, -1.2840e+00, -2.9202e+00, -3.6943e-01, + 2.1035e-02, -4.9879e-01, -1.0295e+00, 5.0388e-01, 1.1296e+00 }, + { -9.2965e+04, 5.0293e-01, -1.1033e+00, -2.6251e+00, -4.0461e-01, + 2.4992e-02, -3.5062e-01, -1.0246e+00, 3.8909e-01, 1.2595e+00 }, + { -1.3250e+05, -2.3738e-01, -9.9398e-01, -2.4136e+00, -4.4740e-01, + 6.0968e-03, -2.6559e-01, -1.0308e+00, 2.6684e-01, 1.2440e+00 }, + { -1.7149e+05, -9.7999e-01, -9.1698e-01, -2.2384e+00, -4.9912e-01, + -2.5475e-02, -2.0762e-01, -1.0468e+00, 1.3078e-01, 1.1599e+00 }, + { -2.2091e+05, -1.9350e+00, -8.5497e-01, -2.0582e+00, -5.7508e-01, + -7.7816e-02, -1.6138e-01, -1.0794e+00, -5.6045e-02, 9.8875e-01 }, + { -2.8140e+05, -3.1219e+00, -8.2568e-01, -1.8962e+00, -6.7862e-01, + -1.5242e-01, -1.3554e-01, -1.1353e+00, -2.9320e-01, 7.2082e-01 }, + { -3.4167e+05, -4.3171e+00, -8.2824e-01, -1.7733e+00, -7.8907e-01, + -2.3483e-01, -1.3167e-01, -1.2015e+00, -5.3627e-01, 4.0854e-01 }, + { -3.7868e+05, -5.0537e+00, -8.3691e-01, -1.7046e+00, -8.6035e-01, + -2.9036e-01, -1.3690e-01, -1.2447e+00, -6.9304e-01, 1.9260e-01 }, + { -3.7429e+05, -4.9406e+00, -7.8456e-01, -1.6323e+00, -8.6203e-01, + -3.0644e-01, -1.3159e-01, -1.2267e+00, -7.3358e-01, 1.3468e-01 }, + { -3.3293e+05, -4.0758e+00, -6.6873e-01, -1.5416e+00, -8.0032e-01, + -2.8877e-01, -1.1346e-01, -1.1498e+00, -6.7458e-01, 2.1365e-01 }, + { -2.7541e+05, -2.9085e+00, -5.3210e-01, -1.4470e+00, -7.0706e-01, + -2.5517e-01, -9.1435e-02, -1.0445e+00, -5.6402e-01, 3.5113e-01 }, + { -2.2010e+05, -1.8209e+00, -4.1116e-01, -1.3627e+00, -6.1220e-01, + -2.2144e-01, -7.3319e-02, -9.4005e-01, -4.4660e-01, 4.7992e-01 }, + { -1.7809e+05, -1.0242e+00, -3.2646e-01, -1.3011e+00, -5.3612e-01, + -1.9567e-01, -6.2291e-02, -8.5731e-01, -3.5032e-01, 5.7022e-01 }, + { -1.5426e+05, -5.8691e-01, -2.8121e-01, -1.2660e+00, -4.9111e-01, + -1.8141e-01, -5.7387e-02, -8.0842e-01, -2.9317e-01, 6.1601e-01 }, + }; + + const double loglikelihoodRef = -2734.43; + + // Test log-likelihood calculation for the whole data. + { + const double loglikelihood = hmm.LogLikelihood(obs); + REQUIRE(loglikelihood == Approx(loglikelihoodRef).epsilon(1e-3)); + } + + // Test loglikelihood calculation in an incremental way. + // It simulates the case where we have a stream of data. + { + double loglikelihood; + arma::vec forwardLogProb; + for (size_t t = 0; t stateSeq; - auto likelihood = hmm.LogLikelihood(obs); hmm.Predict(obs, stateSeq); arma::Row stateSeqRef = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -843,8 +1096,6 @@ TEST_CASE("GaussianHMMPredictTest", "[HMMTest]") 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 }; - REQUIRE(likelihood == Approx(-2734.43).epsilon(1e-5)); - for (size_t i = 0; i < stateSeqRef.n_cols; ++i) { REQUIRE(stateSeqRef.at(i) == stateSeq.at(i)); diff --git a/src/mlpack/tests/hoeffding_tree_test.cpp b/src/mlpack/tests/hoeffding_tree_test.cpp index 49c60b0cea..de7db90443 100644 --- a/src/mlpack/tests/hoeffding_tree_test.cpp +++ b/src/mlpack/tests/hoeffding_tree_test.cpp @@ -19,7 +19,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include diff --git a/src/mlpack/tests/image_load_test.cpp b/src/mlpack/tests/image_load_test.cpp index df9ac6f979..656a9dbfb6 100644 --- a/src/mlpack/tests/image_load_test.cpp +++ b/src/mlpack/tests/image_load_test.cpp @@ -11,7 +11,7 @@ */ #include -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "test_catch_tools.hpp" #include "catch.hpp" diff --git a/src/mlpack/tests/kde_test.cpp b/src/mlpack/tests/kde_test.cpp index da5417f2a2..bb0b3d4eca 100644 --- a/src/mlpack/tests/kde_test.cpp +++ b/src/mlpack/tests/kde_test.cpp @@ -16,7 +16,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::kde; diff --git a/src/mlpack/tests/kernel_test.cpp b/src/mlpack/tests/kernel_test.cpp index e32180a3f9..2eda7cc7ae 100644 --- a/src/mlpack/tests/kernel_test.cpp +++ b/src/mlpack/tests/kernel_test.cpp @@ -25,7 +25,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::kernel; diff --git a/src/mlpack/tests/kmeans_test.cpp b/src/mlpack/tests/kmeans_test.cpp index 5d4bae2abf..c20014c046 100644 --- a/src/mlpack/tests/kmeans_test.cpp +++ b/src/mlpack/tests/kmeans_test.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -486,6 +487,72 @@ TEST_CASE("RefinedStartTest", "[KMeansTest]") REQUIRE(distortion < 14000.0); } +/** + * Test that the k-means++ initialization strategy returns decent initial + * cluster estimates. + */ +TEST_CASE("KMeansPlusPlusTest", "[KMeansTest]") +{ + // Our dataset will be five Gaussians of largely varying numbers of points and + // we expect that the refined starting policy should return good guesses at + // what these Gaussians are. + arma::mat data(3, 3000); + data.randn(); + + // First Gaussian: 10000 points, centered at (0, 0, 0). + // Second Gaussian: 2000 points, centered at (5, 0, -2). + // Third Gaussian: 5000 points, centered at (-2, -2, -2). + // Fourth Gaussian: 1000 points, centered at (-6, 8, 8). + // Fifth Gaussian: 12000 points, centered at (1, 6, 1). + arma::mat centroids(" 0 5 -2 -6 1;" + " 0 0 -2 8 6;" + " 0 -2 -2 8 1"); + + for (size_t i = 1000; i < 1200; ++i) + data.col(i) += centroids.col(1); + for (size_t i = 1200; i < 1700; ++i) + data.col(i) += centroids.col(2); + for (size_t i = 1700; i < 1800; ++i) + data.col(i) += centroids.col(3); + for (size_t i = 1800; i < 3000; ++i) + data.col(i) += centroids.col(4); + + KMeansPlusPlusInitialization k; + arma::mat resultingCentroids; + k.Cluster(data, 5, resultingCentroids); + + // Calculate resulting assignments. + arma::Row assignments(data.n_cols); + for (size_t i = 0; i < data.n_cols; ++i) + { + double bestDist = DBL_MAX; + for (size_t j = 0; j < 5; ++j) + { + const double dist = metric::EuclideanDistance::Evaluate(data.col(i), + resultingCentroids.col(j)); + if (dist < bestDist) + { + bestDist = dist; + assignments[i] = j; + } + } + } + + // Calculate sum of distances from centroid means. + double distortion = 0; + for (size_t i = 0; i < 3000; ++i) + distortion += metric::EuclideanDistance::Evaluate(data.col(i), + resultingCentroids.col(assignments[i])); + + // Using k-means++, the distance for this dataset is usually around + // 10000. Regular k-means is between 10000 and 30000 (I think the 10000 + // figure is a corner case which actually does not give good clusters), and + // random initial starts give distortion around 22000. So we'll require that + // our distortion is less than 14500. (It seems like there is a lot of noise + // in the result.) + REQUIRE(distortion < 14500.0); +} + #ifdef ARMA_HAS_SPMAT /** * Make sure sparse k-means works okay. diff --git a/src/mlpack/tests/knn_test.cpp b/src/mlpack/tests/knn_test.cpp index 5f151eb4b6..449099556f 100644 --- a/src/mlpack/tests/knn_test.cpp +++ b/src/mlpack/tests/knn_test.cpp @@ -1114,28 +1114,28 @@ TEST_CASE("KNNModelTest", "[KNNTest]") // We only have std::move() constructors so make a copy of our data. arma::mat referenceCopy(referenceData); arma::mat queryCopy(queryData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE); if (j == 2) - models[i].BuildModel(std::move(referenceCopy), 20, NAIVE_MODE); + models[i].BuildModel(std::move(referenceCopy), NAIVE_MODE); arma::Mat neighbors; arma::mat distances; models[i].Search(std::move(queryCopy), 3, neighbors, distances); - REQUIRE(neighbors.n_rows ==baselineNeighbors.n_rows); - REQUIRE(neighbors.n_cols ==baselineNeighbors.n_cols); - REQUIRE(neighbors.n_elem ==baselineNeighbors.n_elem); - REQUIRE(distances.n_rows ==baselineDistances.n_rows); - REQUIRE(distances.n_cols ==baselineDistances.n_cols); - REQUIRE(distances.n_elem ==baselineDistances.n_elem); + REQUIRE(neighbors.n_rows == baselineNeighbors.n_rows); + REQUIRE(neighbors.n_cols == baselineNeighbors.n_cols); + REQUIRE(neighbors.n_elem == baselineNeighbors.n_elem); + REQUIRE(distances.n_rows == baselineDistances.n_rows); + REQUIRE(distances.n_cols == baselineDistances.n_cols); + REQUIRE(distances.n_elem == baselineDistances.n_elem); for (size_t k = 0; k < distances.n_elem; ++k) { - REQUIRE(neighbors[k] ==baselineNeighbors[k]); + REQUIRE(neighbors[k] == baselineNeighbors[k]); if (std::abs(baselineDistances[k]) < 1e-5) REQUIRE(distances[k] == Approx(0.0).margin(1e-7)); else @@ -1196,28 +1196,28 @@ TEST_CASE("KNNModelMonochromaticTest", "[KNNTest]") { // We only have a std::move() constructor... so copy the data. arma::mat referenceCopy(referenceData); + models[i].LeafSize() = 20; if (j == 0) - models[i].BuildModel(std::move(referenceCopy), 20, DUAL_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), DUAL_TREE_MODE); if (j == 1) - models[i].BuildModel(std::move(referenceCopy), 20, - SINGLE_TREE_MODE); + models[i].BuildModel(std::move(referenceCopy), SINGLE_TREE_MODE); if (j == 2) - models[i].BuildModel(std::move(referenceCopy), 20, NAIVE_MODE); + models[i].BuildModel(std::move(referenceCopy), NAIVE_MODE); arma::Mat neighbors; arma::mat distances; models[i].Search(3, neighbors, distances); - REQUIRE(neighbors.n_rows ==baselineNeighbors.n_rows); - REQUIRE(neighbors.n_cols ==baselineNeighbors.n_cols); - REQUIRE(neighbors.n_elem ==baselineNeighbors.n_elem); - REQUIRE(distances.n_rows ==baselineDistances.n_rows); - REQUIRE(distances.n_cols ==baselineDistances.n_cols); - REQUIRE(distances.n_elem ==baselineDistances.n_elem); + REQUIRE(neighbors.n_rows == baselineNeighbors.n_rows); + REQUIRE(neighbors.n_cols == baselineNeighbors.n_cols); + REQUIRE(neighbors.n_elem == baselineNeighbors.n_elem); + REQUIRE(distances.n_rows == baselineDistances.n_rows); + REQUIRE(distances.n_cols == baselineDistances.n_cols); + REQUIRE(distances.n_elem == baselineDistances.n_elem); for (size_t k = 0; k < distances.n_elem; ++k) { - REQUIRE(neighbors[k] ==baselineNeighbors[k]); + REQUIRE(neighbors[k] == baselineNeighbors[k]); if (std::abs(baselineDistances[k]) < 1e-5) REQUIRE(distances[k] == Approx(0.0).margin(1e-7)); else diff --git a/src/mlpack/tests/krann_search_test.cpp b/src/mlpack/tests/krann_search_test.cpp index 29aa5335ff..68214b4a25 100644 --- a/src/mlpack/tests/krann_search_test.cpp +++ b/src/mlpack/tests/krann_search_test.cpp @@ -635,8 +635,6 @@ TEST_CASE("RAModelTest", "[KRANNTest]") { // Ensure that we can build an RAModel and get correct // results. - typedef RAModel KNNModel; - arma::mat queryData, referenceData; if (!data::Load("rann_test_r_3_900.csv", referenceData)) FAIL("Cannot load dataset rann_test_r_3_900.csv"); @@ -644,27 +642,27 @@ TEST_CASE("RAModelTest", "[KRANNTest]") FAIL("Cannot load dataset rann_test_q_3_100.csv"); // Build all the possible models. - KNNModel models[20]; - models[0] = KNNModel(KNNModel::TreeTypes::KD_TREE, false); - models[1] = KNNModel(KNNModel::TreeTypes::KD_TREE, true); - models[2] = KNNModel(KNNModel::TreeTypes::COVER_TREE, false); - models[3] = KNNModel(KNNModel::TreeTypes::COVER_TREE, true); - models[4] = KNNModel(KNNModel::TreeTypes::R_TREE, false); - models[5] = KNNModel(KNNModel::TreeTypes::R_TREE, true); - models[6] = KNNModel(KNNModel::TreeTypes::R_STAR_TREE, false); - models[7] = KNNModel(KNNModel::TreeTypes::R_STAR_TREE, true); - models[8] = KNNModel(KNNModel::TreeTypes::X_TREE, false); - models[9] = KNNModel(KNNModel::TreeTypes::X_TREE, true); - models[10] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, false); - models[11] = KNNModel(KNNModel::TreeTypes::HILBERT_R_TREE, true); - models[12] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, false); - models[13] = KNNModel(KNNModel::TreeTypes::R_PLUS_TREE, true); - models[14] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, false); - models[15] = KNNModel(KNNModel::TreeTypes::R_PLUS_PLUS_TREE, true); - models[16] = KNNModel(KNNModel::TreeTypes::UB_TREE, false); - models[17] = KNNModel(KNNModel::TreeTypes::UB_TREE, true); - models[18] = KNNModel(KNNModel::TreeTypes::OCTREE, false); - models[19] = KNNModel(KNNModel::TreeTypes::OCTREE, true); + RAModel models[20]; + models[0] = RAModel(RAModel::TreeTypes::KD_TREE, false); + models[1] = RAModel(RAModel::TreeTypes::KD_TREE, true); + models[2] = RAModel(RAModel::TreeTypes::COVER_TREE, false); + models[3] = RAModel(RAModel::TreeTypes::COVER_TREE, true); + models[4] = RAModel(RAModel::TreeTypes::R_TREE, false); + models[5] = RAModel(RAModel::TreeTypes::R_TREE, true); + models[6] = RAModel(RAModel::TreeTypes::R_STAR_TREE, false); + models[7] = RAModel(RAModel::TreeTypes::R_STAR_TREE, true); + models[8] = RAModel(RAModel::TreeTypes::X_TREE, false); + models[9] = RAModel(RAModel::TreeTypes::X_TREE, true); + models[10] = RAModel(RAModel::TreeTypes::HILBERT_R_TREE, false); + models[11] = RAModel(RAModel::TreeTypes::HILBERT_R_TREE, true); + models[12] = RAModel(RAModel::TreeTypes::R_PLUS_TREE, false); + models[13] = RAModel(RAModel::TreeTypes::R_PLUS_TREE, true); + models[14] = RAModel(RAModel::TreeTypes::R_PLUS_PLUS_TREE, false); + models[15] = RAModel(RAModel::TreeTypes::R_PLUS_PLUS_TREE, true); + models[16] = RAModel(RAModel::TreeTypes::UB_TREE, false); + models[17] = RAModel(RAModel::TreeTypes::UB_TREE, true); + models[18] = RAModel(RAModel::TreeTypes::OCTREE, false); + models[19] = RAModel(RAModel::TreeTypes::OCTREE, true); arma::Mat qrRanks; if (!data::Load("rann_test_qr_ranks.csv", qrRanks, false, false)) // No transpose. diff --git a/src/mlpack/tests/linear_regression_test.cpp b/src/mlpack/tests/linear_regression_test.cpp index bf294bc2d1..e8b3371265 100644 --- a/src/mlpack/tests/linear_regression_test.cpp +++ b/src/mlpack/tests/linear_regression_test.cpp @@ -11,7 +11,7 @@ #include #include -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "test_catch_tools.hpp" #include "catch.hpp" @@ -73,8 +73,8 @@ TEST_CASE("LinearRegressionTestCase", "[LinearRegressionTest]") TEST_CASE("ComputeErrorTest", "[LinearRegressionTest]") { arma::mat predictors; - predictors << 0 << 1 << 2 << 4 << 8 << 16 << arma::endr - << 16 << 8 << 4 << 2 << 1 << 0 << arma::endr; + predictors = { { 0, 1, 2, 4, 8, 16 }, + { 16, 8, 4, 2, 1, 0 } }; arma::rowvec responses = "0 2 4 3 8 8"; // http://www.mlpack.org/trac/ticket/298 @@ -92,8 +92,8 @@ TEST_CASE("ComputeErrorPerfectFitTest", "[LinearRegressionTest]") { // Linear regression should perfectly model this dataset. arma::mat predictors; - predictors << 0 << 1 << 2 << 1 << 6 << 2 << arma::endr - << 0 << 1 << 2 << 2 << 2 << 6 << arma::endr; + predictors = { { 0, 1, 2, 1, 6, 2 }, + { 0, 1, 2, 2, 2, 6 } }; arma::rowvec responses = "0 2 4 3 8 8"; LinearRegression lr(predictors, responses); diff --git a/src/mlpack/tests/local_coordinate_coding_test.cpp b/src/mlpack/tests/local_coordinate_coding_test.cpp index 55644de877..8ef6b3a555 100644 --- a/src/mlpack/tests/local_coordinate_coding_test.cpp +++ b/src/mlpack/tests/local_coordinate_coding_test.cpp @@ -16,7 +16,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace arma; using namespace mlpack; diff --git a/src/mlpack/tests/logistic_regression_test.cpp b/src/mlpack/tests/logistic_regression_test.cpp index 270d6c933f..5c6c845709 100644 --- a/src/mlpack/tests/logistic_regression_test.cpp +++ b/src/mlpack/tests/logistic_regression_test.cpp @@ -1036,9 +1036,9 @@ TEST_CASE("ConstructionThenTraining", "[LogisticRegressionTest]") arma::mat myMatrix; // Four points, three dimensions. - myMatrix << 0.555950 << 0.274690 << 0.540605 << 0.798938 << arma::endr - << 0.948014 << 0.973234 << 0.216504 << 0.883152 << arma::endr - << 0.023787 << 0.675382 << 0.231751 << 0.450332 << arma::endr; + myMatrix = { { 0.555950, 0.274690, 0.540605, 0.798938 }, + { 0.948014, 0.973234, 0.216504, 0.883152 }, + { 0.023787, 0.675382, 0.231751, 0.450332 } }; arma::Row myTargets("1 0 1 0"); diff --git a/src/mlpack/tests/loss_functions_test.cpp b/src/mlpack/tests/loss_functions_test.cpp index 42762bd96d..1fa4283c1a 100644 --- a/src/mlpack/tests/loss_functions_test.cpp +++ b/src/mlpack/tests/loss_functions_test.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include @@ -267,27 +269,36 @@ TEST_CASE("SimpleMeanSquaredErrorTest", "[LossFunctionsTest]") } /* - * Simple test for the cross-entropy error performance function. + * Simple test for the binary-cross-entropy lossfunction. */ -TEST_CASE("SimpleCrossEntropyErrorTest", "[LossFunctionsTest]") +TEST_CASE("SimpleBinaryCrossEntropyLossTest", "[LossFunctionsTest]") { - arma::mat input1, input2, output, target1, target2; - CrossEntropyError<> module(1e-6); - + arma::mat input1, input2, input3, output, target1, target2, target3; + BCELoss<> module1(1e-6, false); + BCELoss<> module2(1e-6, true); // Test the Forward function on a user generator input and compare it against // the manually calculated result. input1 = arma::mat("0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5"); target1 = arma::zeros(1, 8); - double error1 = module.Forward(input1, target1); + double error1 = module1.Forward(input1, target1); REQUIRE(error1 - 8 * std::log(2) == Approx(0.0).margin(2e-5)); + input2 = arma::mat("0.5 0.5 0.5 0.5 0.5 0.5"); + target2 = arma::zeros(1, 6); + input2.reshape(2, 3); + target2.reshape(2, 3); + double error2 = module2.Forward(input2, target2); + REQUIRE(error2 - std::log(2) == Approx(0.0).margin(2e-5)); + input2 = arma::mat("0 1 1 0 1 0 0 1"); target2 = arma::mat("0 1 1 0 1 0 0 1"); - double error2 = module.Forward(input2, target2); - REQUIRE(error2 == Approx(0.0).margin(1e-5)); + double error3 = module1.Forward(input2, target2); + REQUIRE(error3 == Approx(0.0).margin(1e-5)); + double error4 = module2.Forward(input2, target2); + REQUIRE(error4 == Approx(0.0).margin(1e-5)); // Test the Backward function. - module.Backward(input1, target1, output); + module1.Backward(input1, target1, output); for (double el : output) { // For the 0.5 constant vector we should get 1 / (1 - 0.5) = 2 everywhere. @@ -296,7 +307,7 @@ TEST_CASE("SimpleCrossEntropyErrorTest", "[LossFunctionsTest]") REQUIRE(output.n_rows == input1.n_rows); REQUIRE(output.n_cols == input1.n_cols); - module.Backward(input2, target2, output); + module1.Backward(input2, target2, output); for (size_t i = 0; i < 8; ++i) { double el = output.at(0, i); @@ -897,3 +908,128 @@ TEST_CASE("MeanAbsolutePercentageErrorTest", "[LossFunctionsTest]") REQUIRE(output.n_cols == input.n_cols); CheckMatrices(output, expectedOutput, 0.1); } + +/* + * Simple test for the Triplet Margin Loss function. + */ +TEST_CASE("TripletMarginLossTest") +{ + arma::mat anchor, positive, negative; + arma::mat input, target, output; + TripletMarginLoss<> module; + + // Test the Forward function on a user generated input and compare it against + // the manually calculated result. + anchor = arma::mat("2 3 5"); + positive = arma::mat("10 12 13"); + negative = arma::mat("4 5 7"); + + input = { {2, 3, 5}, {10, 12, 13} }; + + double loss = module.Forward(input, negative); + REQUIRE(loss == 66); + + // Test the Backward function. + module.Backward(input, negative, output); + // According to the used backward formula: + // output = 2 * (negative - positive) / anchor.n_cols, + // output * nofColumns / 2 + positive should be equal to negative. + CheckMatrices(negative, output * output.n_cols / 2 + positive); + REQUIRE(output.n_rows == anchor.n_rows); + REQUIRE(output.n_cols == anchor.n_cols); + + // Test the loss function on a single input. + anchor = arma::mat("4"); + positive = arma::mat("7"); + negative = arma::mat("1"); + + input = arma::mat(2, 1); + input[0] = 4; + input[1] = 7; + + loss = module.Forward(input, negative); + REQUIRE(loss == 1.0); + + // Test the Backward function on a single input. + module.Backward(input, negative, output); + // Test whether the output is negative. + REQUIRE(arma::accu(output) == -12); + REQUIRE(output.n_elem == 1); +} + +/** + * Simple test for the Hinge loss function. + */ +TEST_CASE("HingeLossTest", "[LossFunctionsTest]") +{ + arma::mat input, target, target_b, output; + double loss, loss_b; + HingeLoss<> module1; + HingeLoss<> module2(false); + + // Test the Forward function. Loss should be 0 if input = target. + input = arma::ones(10, 1); + target = arma::ones(10, 1); + loss = module1.Forward(input, target); + REQUIRE(loss == 0); + + // Test the Backward function for input = target. + module1.Backward(input, target, output); + for (double el : output) + { + // For input = target we should get 0.0 everywhere. + REQUIRE(el == Approx(0.0).epsilon(1e-5)); + } + + REQUIRE(output.n_rows == input.n_rows); + REQUIRE(output.n_cols == input.n_cols); + + // Randomly generated input. + input = { { 0.90599973, -0.33040298, 0.07123354}, + { 0.71988434, 0.49657596, 0.39873373}, + { -0.57646927, 0.3951491 , -0.1003365}, + { 0.12528634, 0.68122971, 0.85448826} }; + + // Randomly generated target. + target = { { -1, -1, 1}, + { -1, 1, 1}, + { 1, -1, -1}, + { 1, -1, -1} }; + + // Binary target can be obtained by replacing -1 with 0 in target. + target_b = { { 0, 0, 1}, + { 0, 1, 1}, + { 1, 0, 0}, + { 1, 0, 0} }; + + // Test for binary labels as target. + loss = module1.Forward(input, target); + loss_b = module1.Forward(input, target_b); + + // Loss should be same due to internal conversion of binary labels. + REQUIRE(loss == loss_b); + + // Test for sum reduction. + // Test the Forward function. + // Loss calculated by referring to implementation of tf.keras.losses.hinge. + loss = module1.Forward(input, target); + REQUIRE(loss == Approx(14.61065).epsilon(1e-3)); + + // Test the Backward function + module1.Backward(input, target, output); + REQUIRE(arma::accu(output) == Approx(-5).epsilon(1e-3)); + REQUIRE(output.n_rows == input.n_rows); + REQUIRE(output.n_cols == input.n_cols); + + // Test for mean reduction. + // Test for the Forward function. + // Loss calculated by referring to implementation of tf.keras.losses.hinge. + loss = module2.Forward(input, target); + REQUIRE(loss == Approx(1.21755).epsilon(1e-3)); + + // Test the Backward function. + module2.Backward(input, target, output); + REQUIRE(arma::accu(output) == Approx(-0.41667).epsilon(1e-3)); + REQUIRE(output.n_rows == input.n_rows); + REQUIRE(output.n_cols == input.n_cols); +} diff --git a/src/mlpack/tests/lsh_test.cpp b/src/mlpack/tests/lsh_test.cpp index 7eca5fbcc5..1d445192b5 100644 --- a/src/mlpack/tests/lsh_test.cpp +++ b/src/mlpack/tests/lsh_test.cpp @@ -39,16 +39,16 @@ void GetPointset(const size_t N, arma::mat& rdata) arma::mat c4(d, N / 4, arma::fill::randu); arma::colvec offset1; - offset1 << 0 << arma::endr - << 3 << arma::endr; + offset1 = { { 0 }, + { 3 } }; arma::colvec offset2; - offset2 << 3 << arma::endr - << 3 << arma::endr; + offset2 = { { 3 }, + { 3 } }; arma::colvec offset4; - offset4 << 3 << arma::endr - << 0 << arma::endr; + offset4 = { { 3 }, + { 0 } }; // Spread points in plane. for (size_t p = 0; p < N / 4; ++p) @@ -132,8 +132,8 @@ TEST_CASE("NumTablesTest", "[LSHTest]") fail = false; const int lSize = 6; // Number of runs. - const int lValue[] = {1, 8, 16, 32, 64, 128}; // Number of tables. - double lValueRecall[lSize] = {0.0}; // Recall of each LSH run. + const int lValue[] = { 1, 8, 16, 32, 64, 128 }; // Number of tables. + double lValueRecall[lSize] = { 0.0 }; // Recall of each LSH run. for (size_t l = 0; l < lSize; ++l) { @@ -200,8 +200,8 @@ TEST_CASE("HashWidthTest", "[LSHTest]") arma::mat groundDistances; knn.Search(qdata, k, groundTruth, groundDistances); const int hSize = 7; // Number of runs. - const double hValue[] = {0.1, 0.5, 1, 5, 10, 50, 500}; // Hash width. - double hValueRecall[hSize] = {0.0}; // Recall of each run. + const double hValue[] = { 0.1, 0.5, 1, 5, 10, 50, 500 }; // Hash width. + double hValueRecall[hSize] = { 0.0 }; // Recall of each run. for (size_t h = 0; h < hSize; ++h) { @@ -264,8 +264,8 @@ TEST_CASE("NumProjTest", "[LSHTest]") // LSH test parameters for numProj. const int pSize = 5; // Number of runs. - const int pValue[] = {1, 10, 20, 50, 100}; // Number of projections. - double pValueRecall[pSize] = {0.0}; // Recall of each run. + const int pValue[] = { 1, 10, 20, 50, 100 }; // Number of projections. + double pValueRecall[pSize] = { 0.0 }; // Recall of each run. for (size_t p = 0; p < pSize; ++p) { @@ -496,7 +496,7 @@ TEST_CASE("MultiprobeTest", "[LSHTest]") const size_t repetitions = 5; // Train five objects. const size_t probeTrials = 5; - const size_t numProbes[probeTrials] = {0, 1, 2, 3, 4}; + const size_t numProbes[probeTrials] = { 0, 1, 2, 3, 4 }; // Algorithm parameters. const int k = 4; @@ -597,12 +597,12 @@ TEST_CASE("MultiprobeDeterministicTest", "[LSHTest]") // Construct q1 so it is hashed directly under C2. arma::mat q1; - q1 << 3.9 << arma::endr << 2.99; + q1 = arma::mat({ 3.9, 2.99 }).t(); q1 -= offsets; // Construct q2 so it is hashed near the center of C2. arma::mat q2; - q2 << 3.6 << arma::endr << 3.6; + q2 = arma::mat({ 3.6, 3.6 }).t(); q2 -= offsets; arma::Mat neighbors; @@ -697,12 +697,7 @@ TEST_CASE("RecallTestPartiallyCorrect", "[LSHTest]") // be 0 but recall should not be. arma::Mat q2; q2.set_size(k, numQueries); - q2 << - 2 << arma::endr << - 3 << arma::endr << - 4 << arma::endr << - 6 << arma::endr << - 7 << arma::endr; + q2 = arma::Mat({ 2, 3, 4, 6, 7 }).t(); REQUIRE(LSHSearch<>::ComputeRecall(base, q2) == Approx(0.6).epsilon(1e-6)); } diff --git a/src/mlpack/tests/main_tests/cf_test.cpp b/src/mlpack/tests/main_tests/cf_test.cpp index b136d9b730..da1c8c77fc 100644 --- a/src/mlpack/tests/main_tests/cf_test.cpp +++ b/src/mlpack/tests/main_tests/cf_test.cpp @@ -213,13 +213,13 @@ TEST_CASE_METHOD(CFTestFixture, "CFModelReuseTest", IO::GetSingleton().Parameters()["algorithm"].wasPassed = false; // Reuse the model to get recommendations. - int recommendations = 3; - const int querySize = 7; + size_t recommendations = 3; + const size_t querySize = 7; Mat query = arma::linspace>(0, querySize - 1, querySize); SetInputParam("query", std::move(query)); - SetInputParam("recommendations", recommendations); + SetInputParam("recommendations", int(recommendations)); SetInputParam("input_model", std::move(IO::GetParam("output_model"))); @@ -261,18 +261,21 @@ TEST_CASE_METHOD(CFTestFixture, "CFRankTest", { mat dataset; data::Load("GroupLensSmall.csv", dataset); - int rank = 7; + size_t rank = 7; SetInputParam("training", std::move(dataset)); - SetInputParam("rank", rank); + SetInputParam("rank", int(rank)); SetInputParam("max_iterations", int(10)); SetInputParam("algorithm", std::string("NMF")); mlpackMain(); const CFModel* outputModel = IO::GetParam("output_model"); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); - REQUIRE(outputModel->template CFPtr()->Rank() == rank); + REQUIRE(cf.Rank() == rank); } /** @@ -295,10 +298,13 @@ TEST_CASE_METHOD(CFTestFixture, "CFMinResidueTest", mlpack::math::FixedRandomSeed(); mlpackMain(); - outputModel = IO::GetParam("output_model"); + outputModel = IO::GetParam("output_model"); // By default the main program use NMFPolicy. - const mat w1 = outputModel->template CFPtr()->Decomposition().W(); - const mat h1 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w1 = cf.Decomposition().W(); + const mat h1 = cf.Decomposition().H(); ResetSettings(); @@ -314,15 +320,18 @@ TEST_CASE_METHOD(CFTestFixture, "CFMinResidueTest", outputModel = IO::GetParam("output_model"); // By default the main program use NMFPolicy. - const mat w2 = outputModel->template CFPtr()->Decomposition().W(); - const mat h2 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf2 = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w2 = cf2.Decomposition().W(); + const mat h2 = cf2.Decomposition().H(); // The resulting matrices should be different. REQUIRE((arma::norm(w1 - w2) > 1e-5 || arma::norm(h1 - h2) > 1e-5)); } /** - * Test that itertaion_only_termination is used. + * Test that iteration_only_termination is used. */ TEST_CASE_METHOD(CFTestFixture, "CFIterationOnlyTerminationTest", "[CFMainTest][BindingTests]") @@ -341,10 +350,13 @@ TEST_CASE_METHOD(CFTestFixture, "CFIterationOnlyTerminationTest", mlpack::math::FixedRandomSeed(); mlpackMain(); - outputModel = IO::GetParam("output_model"); + outputModel = IO::GetParam("output_model"); // By default, the main program use NMFPolicy. - const mat w1 = outputModel->template CFPtr()->Decomposition().W(); - const mat h1 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w1 = cf.Decomposition().W(); + const mat h1 = cf.Decomposition().H(); ResetSettings(); @@ -359,8 +371,11 @@ TEST_CASE_METHOD(CFTestFixture, "CFIterationOnlyTerminationTest", outputModel = IO::GetParam("output_model"); // By default, the main program use NMFPolicy. - const mat w2 = outputModel->template CFPtr()->Decomposition().W(); - const mat h2 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf2 = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w2 = cf2.Decomposition().W(); + const mat h2 = cf2.Decomposition().H(); // The resulting matrices should be different. REQUIRE((arma::norm(w1 - w2) > 1e-5 || arma::norm(h1 - h2) > 1e-5)); @@ -387,8 +402,11 @@ TEST_CASE_METHOD(CFTestFixture, "CFMaxIterationsTest", outputModel = IO::GetParam("output_model"); // By default, the main program use NMFPolicy. - const mat w1 = outputModel->template CFPtr()->Decomposition().W(); - const mat h1 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w1 = cf.Decomposition().W(); + const mat h1 = cf.Decomposition().H(); ResetSettings(); @@ -403,8 +421,11 @@ TEST_CASE_METHOD(CFTestFixture, "CFMaxIterationsTest", outputModel = IO::GetParam("output_model"); // By default the main program use NMFPolicy. - const mat w2 = outputModel->template CFPtr()->Decomposition().W(); - const mat h2 = outputModel->template CFPtr()->Decomposition().H(); + CFType& cf2 = + dynamic_cast&>(*(outputModel->CF())).CF(); + const mat w2 = cf2.Decomposition().W(); + const mat h2 = cf2.Decomposition().H(); // The resulting matrices should be different. REQUIRE((arma::norm(w1 - w2) > 1e-5 || arma::norm(h1 - h2) > 1e-5)); diff --git a/src/mlpack/tests/main_tests/krann_test.cpp b/src/mlpack/tests/main_tests/krann_test.cpp index b61044f104..57cb0905d6 100644 --- a/src/mlpack/tests/main_tests/krann_test.cpp +++ b/src/mlpack/tests/main_tests/krann_test.cpp @@ -192,7 +192,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNRefModelTest", // Input pre-trained model. SetInputParam("input_model", - std::move(IO::GetParam("output_model"))); + std::move(IO::GetParam("output_model"))); Log::Fatal.ignoreInput = true; REQUIRE_THROWS_AS(mlpackMain(), std::runtime_error); @@ -285,10 +285,10 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNModelReuseTest", arma::Mat neighbors; arma::mat distances; - RANNModel* output_model; + RAModel* output_model; neighbors = std::move(IO::GetParam>("neighbors")); distances = std::move(IO::GetParam("distances")); - output_model = std::move(IO::GetParam("output_model")); + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -324,8 +324,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentLeafSizes", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -341,7 +341,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentLeafSizes", // Check that initial output matrices and the output matrices using // saved model are equal. CHECK(output_model->LeafSize() == (int) 1); - CHECK(IO::GetParam("output_model")->LeafSize() == (int) 10); + CHECK(IO::GetParam("output_model")->LeafSize() == (int) 10); delete output_model; } @@ -361,8 +361,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTau", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset the passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -378,7 +378,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTau", // Check that initial output matrices and the output matrices using // saved model are equal CHECK(output_model->Tau() == (double) 5); - CHECK(IO::GetParam("output_model")->Tau() == + CHECK(IO::GetParam("output_model")->Tau() == (double) 10); delete output_model; } @@ -399,8 +399,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentAlpha", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset the passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -416,7 +416,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentAlpha", // Check that initial output matrices and the output matrices using // saved model are equal CHECK(output_model->Alpha() == (double) 0.95); - CHECK(IO::GetParam("output_model")->Alpha() == + CHECK(IO::GetParam("output_model")->Alpha() == (double) 0.80); delete output_model; } @@ -437,8 +437,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTreeType", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset the passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -455,7 +455,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentTreeType", // saved model are equal const bool check = output_model->TreeType() == 0; CHECK(check == true); - CHECK(IO::GetParam("output_model")->TreeType() == + CHECK(IO::GetParam("output_model")->TreeType() == 8); delete output_model; } @@ -476,8 +476,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSingleSampleLimit", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -492,7 +492,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSingleSampleLimit", // Check that initial output matrices and the output matrices using // saved model are equal. - CHECK(IO::GetParam("output_model")->SingleSampleLimit() == + CHECK(IO::GetParam("output_model")->SingleSampleLimit() == (int) 15); CHECK(output_model->SingleSampleLimit() == (int) 20); delete output_model; @@ -514,8 +514,8 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSampleAtLeaves", mlpack::math::FixedRandomSeed(); mlpackMain(); - RANNModel* output_model; - output_model = std::move(IO::GetParam("output_model")); + RAModel* output_model; + output_model = std::move(IO::GetParam("output_model")); // Reset passed parameters. IO::GetSingleton().Parameters()["reference"].wasPassed = false; @@ -530,7 +530,7 @@ TEST_CASE_METHOD(KRANNTestFixture, "KRANNDifferentSampleAtLeaves", // Check that initial output matrices and the output matrices using // saved model are equal. - CHECK(IO::GetParam("output_model")->SampleAtLeaves() == + CHECK(IO::GetParam("output_model")->SampleAtLeaves() == (bool) true); CHECK(output_model->SampleAtLeaves() == (bool) false); delete output_model; diff --git a/src/mlpack/tests/main_tests/logistic_regression_test.cpp b/src/mlpack/tests/main_tests/logistic_regression_test.cpp index 82f7235719..85ee5d5b32 100644 --- a/src/mlpack/tests/main_tests/logistic_regression_test.cpp +++ b/src/mlpack/tests/main_tests/logistic_regression_test.cpp @@ -52,7 +52,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, { arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << arma::endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; SetInputParam("labels", std::move(trainY)); @@ -95,7 +95,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRPridictionSizeCheck", arma::mat trainX = arma::randu(D, N); arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << arma::endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; arma::mat testX = arma::randu(D, M); SetInputParam("training", std::move(trainX)); @@ -128,7 +128,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, arma::Row trainY; // Response vector with wrong size. // 8 responses - incorrect size. - trainY << 0 << 0 << 1 << 0 << 1 << 1 << 1 << 0 << arma::endr; + trainY = { 0, 0, 1, 0, 1, 1, 1, 0 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -200,7 +200,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << arma::endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; arma::mat testX = arma::randu(D, M); @@ -250,7 +250,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRWrongDimOfTestData", arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << arma::endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; // Test data with wrong dimensionality. arma::mat testX = arma::randu(D-1, N); @@ -278,7 +278,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRWrongDimOfTestData2", arma::mat trainX = arma::randu(D, N); arma::Row trainY; // 10 responses - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << arma::endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -319,7 +319,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, arma::Row trainY; // 8 responses containing more than two classes. - trainY << 0 << 1 << 0 << 1 << 2 << 1 << 3 << 1 << arma::endr; + trainY = { 0, 1, 0, 1, 2, 1, 3, 1 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -345,7 +345,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << arma::endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -370,7 +370,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRNonNegativeStepSizeTest", arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -396,7 +396,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRNonNegativeToleranceTest", arma::Row trainY; // 10 responses. - trainY << 1 << 1 << 0 << 1 << 0 << 0 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 1, 1, 0, 1, 0, 0, 0, 1, 0, 1 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -421,7 +421,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRMaxIterationsChangeTest", arma::Row trainY; // 10 responses. - trainY << 1 << 0 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 1, 0, 0, 1, 0, 1, 0, 1, 0, 1 }; SetInputParam("training", trainX); SetInputParam("labels", trainY); @@ -474,7 +474,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRLambdaChangeTest", arma::Row trainY; // 10 responses. - trainY << 1 << 0 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 1, 0, 0, 1, 0, 1, 0, 1, 0, 1 }; SetInputParam("training", trainX); SetInputParam("labels", trainY); @@ -527,7 +527,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRStepSizeChangeTest", arma::Row trainY; // 10 responses. - trainY << 1 << 0 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 1, 0, 0, 1, 0, 1, 0, 1, 0, 1 }; SetInputParam("training", trainX); SetInputParam("labels", trainY); @@ -582,7 +582,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LROptimizerChangeTest", arma::Row trainY; // 10 responses. - trainY << 1 << 0 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 1, 0, 0, 1, 0, 1, 0, 1, 0, 1 }; SetInputParam("training", trainX); SetInputParam("labels", trainY); @@ -638,7 +638,7 @@ TEST_CASE_METHOD(LogisticRegressionTestFixture, "LRDecisionBoundaryTest", arma::Row trainY; // 10 responses. - trainY << 1 << 0 << 0 << 1 << 0 << 1 << 0 << 1 << 0 << 1 << arma::endr; + trainY = { 1, 0, 0, 1, 0, 1, 0, 1, 0, 1 }; arma::mat testX = arma::randu(D, M); diff --git a/src/mlpack/tests/main_tests/perceptron_test.cpp b/src/mlpack/tests/main_tests/perceptron_test.cpp index a8e9c01368..8e8b6a8087 100644 --- a/src/mlpack/tests/main_tests/perceptron_test.cpp +++ b/src/mlpack/tests/main_tests/perceptron_test.cpp @@ -307,7 +307,7 @@ TEST_CASE_METHOD(PerceptronTestFixture, "PerceptronReTrainWithWrongClasses", arma::Row labelsX2; // 10 responses. - labelsX2 << 0 << 1 << 4 << 1 << 2 << 1 << 0 << 3 << 3 << 0 << endr; + labelsX2 = { 0, 1, 4, 1, 2, 1, 0, 3, 3, 0 }; // Last column of trainX2 contains the class labels. SetInputParam("training", std::move(trainX2)); @@ -334,7 +334,7 @@ TEST_CASE_METHOD(PerceptronTestFixture, "PerceptronWrongDimOfTestData", arma::Row trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << endr; + trainY = { 0 , 1, 0, 1, 1, 1, 0, 1, 0, 0 }; // Test data with wrong dimensionality. arma::mat testX = arma::randu(D-3, M); @@ -362,7 +362,7 @@ TEST_CASE_METHOD(PerceptronTestFixture, "PerceptronWrongResponseSizeTest", arma::Row trainY; // Response vector with wrong size. // 8 responses. - trainY << 0 << 0 << 1 << 0 << 1 << 1 << 1 << 0 << endr; + trainY = { 0, 0, 1, 0, 1, 1, 1, 0 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); @@ -398,7 +398,7 @@ TEST_CASE_METHOD(PerceptronTestFixture, "PerceptronNoTrainingDataTest", "[PerceptronMainTest][BindingTests]") { arma::Row trainY; - trainY << 1 << 1 << 0 << 1 << 0 << 0 < trainY; // 10 responses. - trainY << 0 << 1 << 0 << 1 << 1 << 1 << 0 << 1 << 0 << 0 << endr; + trainY = { 0, 1, 0, 1, 1, 1, 0, 1, 0, 0 }; SetInputParam("training", std::move(trainX)); SetInputParam("labels", std::move(trainY)); diff --git a/src/mlpack/tests/maximal_inputs_test.cpp b/src/mlpack/tests/maximal_inputs_test.cpp index 4fc0161f11..2dc9db3542 100644 --- a/src/mlpack/tests/maximal_inputs_test.cpp +++ b/src/mlpack/tests/maximal_inputs_test.cpp @@ -20,8 +20,8 @@ using namespace mlpack; arma::mat CreateMaximalInput() { arma::mat w1(2, 4); - w1 << 0 << 1 << 2 << 3 << arma::endr - << 4 << 5 << 6 << 7; + w1 = { {0, 1, 2, 3}, + {4, 5, 6, 7} }; arma::mat input(5, 5); input.submat(0, 0, 1, 3) = w1; @@ -50,12 +50,10 @@ TEST_CASE("ColumnToBlocksEvaluate", "[MaximalInputsTest]") ctb.Transform(CreateMaximalInput(), output); arma::mat matlabResults; - matlabResults << -1 << -1 << -1 << -1 << -1 << -1 << -1 << arma::endr - << -1 << -1<< -0.42857 << -1 << 0.14286 << 0.71429 << -1 - << arma::endr - << -1 << -0.71429 << -0.14286 << -1 << 0.42857 << 1 << -1 - << arma::endr - << -1 << -1 << -1 << -1 << -1 << -1 << -1; + matlabResults = { { -1, -1, -1, -1, -1, -1, -1 }, + { -1, -1, -0.42857, -1, 0.14286, 0.71429, -1 }, + { -1, -0.71429, -0.14286, -1, 0.42857, 1, -1 }, + { -1, -1, -1, -1, -1, -1, -1 } }; TestResults(output, matlabResults); } @@ -70,12 +68,10 @@ TEST_CASE("ColumnToBlocksChangeBlockSize", "[MaximalInputsTest]") ctb.Transform(CreateMaximalInput(), output); arma::mat matlabResults; - matlabResults<< -3 << -3 << -3 << -3 << -3 - << -3 << -3 << -3 << -3 << -3 << -3 << arma::endr - << -3 << -1 << -0.71429 << -0.42857 << -0.14286 - << -3 << 0.14286 << 0.42857 << 0.71429 << 1 << -3 << arma::endr - << -3 << -3 << -3 << -3 << -3 << -3 << -3 << -3 << -3 << -3 - << -3 << arma::endr; + matlabResults = { { -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 }, + { -3, -1, -0.71429, -0.42857, -0.14286, -3, 0.14286, + 0.42857, 0.71429, 1, -3 }, + { -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3 } }; TestResults(output, matlabResults); } diff --git a/src/mlpack/tests/metric_test.cpp b/src/mlpack/tests/metric_test.cpp index db3f164650..7103c7867f 100644 --- a/src/mlpack/tests/metric_test.cpp +++ b/src/mlpack/tests/metric_test.cpp @@ -31,10 +31,10 @@ TEST_CASE("L1MetricTest", "[MetricTest]") b1.randn(); arma::Col a2(5); - a2 << 1 << 2 << 1 << 0 << 5; + a2 = { 1, 2, 1, 0, 5 }; arma::Col b2(5); - b2 << 2 << 5 << 2 << 0 << 1; + b2 = { 2, 5, 2, 0, 1 }; ManhattanDistance lMetric; @@ -57,10 +57,10 @@ TEST_CASE("L2MetricTest", "[MetricTest]") b1.randn(); arma::vec a2(5); - a2 << 1 << 2 << 1 << 0 << 5; + a2 = { 1, 2, 1, 0, 5 }; arma::vec b2(5); - b2 << 2 << 5 << 2 << 0 << 1; + b2 = { 2, 5, 2, 0, 1 }; EuclideanDistance lMetric; @@ -83,10 +83,10 @@ TEST_CASE("LINFMetricTest", "[MetricTest]") b1.randn(); arma::Col a2(5); - a2 << 1 << 2 << 1 << 0 << 5; + a2 = { 1, 2, 1, 0, 5 }; arma::Col b2(5); - b2 << 2 << 5 << 2 << 0 << 1; + b2 = { 2, 5, 2, 0, 1 }; ChebyshevDistance lMetric; @@ -103,34 +103,34 @@ TEST_CASE("LINFMetricTest", "[MetricTest]") TEST_CASE("IoUMetricTest", "[MetricTest]") { arma::vec bbox1(4), bbox2(4); - bbox1 << 1 << 2 << 100 << 200; - bbox2 << 1 << 2 << 100 << 200; + bbox1 = { 1, 2, 100, 200 }; + bbox2 = { 1, 2, 100, 200 }; // IoU of same bounding boxes equals 1.0. REQUIRE(1.0 == Approx(IoU<>::Evaluate(bbox1, bbox2)).epsilon(1e-6)); // Use coordinate system to represent bounding boxes. // Bounding boxes represent {x0, y0, x1, y1}. - bbox1 << 39 << 63 << 203 << 112; - bbox2 << 54 << 66 << 198 << 114; + bbox1 = { 39, 63, 203, 112 }; + bbox2 = { 54, 66, 198, 114 }; // Value calculated using Python interpreter. REQUIRE(IoU::Evaluate(bbox1, bbox2) == Approx(0.7980093).epsilon(1e-6)); - bbox1 << 31 << 69 << 201 << 125; - bbox2 << 18 << 63 << 235 << 135; + bbox1 = { 31, 69, 201, 125 }; + bbox2 = { 18, 63, 235, 135 }; // Value calculated using Python interpreter. REQUIRE(IoU::Evaluate(bbox1, bbox2) == Approx(0.612479577).epsilon(1e-6)); // Use hieght - width representation of bounding boxes. // Bounding boxes represent {x0, y0, h, w}. - bbox1 << 49 << 75 << 154 << 50; - bbox2 << 42 << 78 << 144 << 48; + bbox1 = { 49, 75, 154, 50 }; + bbox2 = { 42, 78, 144, 48 }; // Value calculated using Python interpreter. REQUIRE(IoU<>::Evaluate(bbox1, bbox2) == Approx(0.7898879).epsilon(1e-6)); - bbox1 << 35 << 51 << 161 << 59; - bbox2 << 36 << 60 << 144 << 48; + bbox1 = { 35, 51, 161, 59 }; + bbox2 = { 36, 60, 144, 48 }; // Value calculated using Python interpreter. REQUIRE(IoU<>::Evaluate(bbox1, bbox2) == Approx(0.7309670).epsilon(1e-6)); } @@ -144,9 +144,9 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") // Set values of each bounding box. // Use coordinate system to represent bounding boxes. // Bounding boxes represent {x0, y0, x1, y1}. - bbox1 << 0.5 << 0.5 << 41.0 << 31.0; - bbox2 << 1.0 << 1.0 << 42.0 << 22.0; - bbox3 << 10.0 << 13.0 << 90.0 << 100.0; + bbox1 = { 0.5, 0.5, 41.0, 31.0 }; + bbox2 = { 1.0, 1.0, 42.0, 22.0 }; + bbox3 = { 10.0, 13.0, 90.0, 100.0 }; // Fill bounding box. bbox.insert_cols(0, bbox3); @@ -155,7 +155,7 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") // Fill confidence scores for each bounding box. arma::vec confidenceScores(3); - confidenceScores << 0.7 << 0.6 << 0.4; + confidenceScores = { 0.7, 0.6, 0.4 }; // Selected bounding box using torchvision.ops.nms(). desiredBoundingBox.insert_cols(0, bbox3); @@ -163,8 +163,7 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") // Selected indices of bounding boxes using // torchvision.ops.nms(). - desiredIndices = arma::ucolvec(2); - desiredIndices << 0 << 2; + desiredIndices = { 0, 2 }; // Evaluate the bounding box. NMS::Evaluate(bbox, confidenceScores, @@ -190,7 +189,7 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") bbox.insert_cols(0, bbox1); bbox.insert_cols(0, bbox2); bbox.insert_cols(0, bbox1); - confidenceScores << 1.0 << 0.6 << 0.9; + confidenceScores = { 1.0, 0.6, 0.9 }; // Output calculated using using torchvision.ops.nms(). desiredBoundingBox.insert_cols(0, bbox2); @@ -212,9 +211,9 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") // Use coordinate system to represent bounding boxes. // Bounding boxes represent {x0, y0, x1, y1}. - bbox1 << 39 << 63 << 203 << 112; - bbox2 << 31 << 69 << 201 << 125; - bbox3 << 54 << 66 << 198 << 114; + bbox1 = { 39, 63, 203, 112 }; + bbox2 = { 31, 69, 201, 125 }; + bbox3 = { 54, 66, 198, 114 }; // Fill bounding box. bbox.insert_cols(0, bbox3); @@ -222,7 +221,7 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") bbox.insert_cols(0, bbox1); // Fill confidence scores of bounding boxes. - confidenceScores << 1.0 << 0.6 << 0.9; + confidenceScores = { 1.0, 0.6, 0.9 }; // Selected bounding box using torchvision.ops.nms(). desiredBoundingBox.insert_cols(0, bbox2); @@ -245,9 +244,9 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") // Set values of each bounding box. // Use coordinate system to represent bounding boxes. // Bounding boxes represent {x0, y0, h, w}. - bbox1 << 0.0 << 0.0 << 41.0 << 31.0; - bbox2 << 1.0 << 1.0 << 41.0 << 21.0; - bbox3 << 10.0 << 13.0 << 80.0 << 87.0; + bbox1 = { 0.0, 0.0, 41.0, 31.0 }; + bbox2 = { 1.0, 1.0, 41.0, 21.0 }; + bbox3 = { 10.0, 13.0, 80.0, 87.0 }; // Fill bounding box. bbox.insert_cols(0, bbox3); @@ -255,7 +254,7 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") bbox.insert_cols(0, bbox1); // Fill confidence scores for each bounding box. - confidenceScores << 0.7 << 0.6 << 0.4; + confidenceScores = { 0.7, 0.6, 0.4 }; // Selected bounding box using torchvision.ops.nms(). desiredBoundingBox.insert_cols(0, bbox3); @@ -277,9 +276,9 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") // Use coordinate system to represent bounding boxes. // Bounding boxes represent {x0, y0, h, w}. - bbox1 << 39 << 63 << 164 << 49; - bbox2 << 31 << 69 << 170 << 56; - bbox3 << 54 << 66 << 144 << 48; + bbox1 = { 39, 63, 164, 49 }; + bbox2 = { 31, 69, 170, 56 }; + bbox3 = { 54, 66, 144, 48 }; // Fill bounding box. bbox.insert_cols(0, bbox3); @@ -287,7 +286,7 @@ TEST_CASE("NMSMetricTest", "[MetricTest]") bbox.insert_cols(0, bbox1); // Fill confidence scores of bounding boxes. - confidenceScores << 1.0 << 0.6 << 0.4; + confidenceScores = { 1.0, 0.6, 0.4 }; // Selected bounding box using torchvision.ops.nms(). desiredBoundingBox.insert_cols(0, bbox2); diff --git a/src/mlpack/tests/octree_test.cpp b/src/mlpack/tests/octree_test.cpp index 610bbd2fbc..bdf7110930 100644 --- a/src/mlpack/tests/octree_test.cpp +++ b/src/mlpack/tests/octree_test.cpp @@ -14,7 +14,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::math; diff --git a/src/mlpack/tests/perceptron_test.cpp b/src/mlpack/tests/perceptron_test.cpp index bcb64cc9d6..d53903c259 100644 --- a/src/mlpack/tests/perceptron_test.cpp +++ b/src/mlpack/tests/perceptron_test.cpp @@ -110,16 +110,16 @@ TEST_CASE("SimpleWeightUpdateInstanceWeight", "[PerceptronTest]") TEST_CASE("And", "[PerceptronTest]") { mat trainData; - trainData << 0 << 1 << 1 << 0 << endr - << 1 << 0 << 1 << 0 << endr; + trainData = { { 0, 1, 1, 0 }, + { 1, 0, 1, 0 } }; Mat labels; - labels << 0 << 0 << 1 << 0; + labels = { 0, 0, 1, 0 }; Perceptron<> p(trainData, labels.row(0), 2, 1000); mat testData; - testData << 0 << 1 << 1 << 0 << endr - << 1 << 0 << 1 << 0 << endr; + testData = { { 0, 1, 1, 0 }, + { 1, 0, 1, 0 } }; Row predictedLabels(testData.n_cols); p.Classify(testData, predictedLabels); @@ -135,17 +135,17 @@ TEST_CASE("And", "[PerceptronTest]") TEST_CASE("Or", "[PerceptronTest]") { mat trainData; - trainData << 0 << 1 << 1 << 0 << endr - << 1 << 0 << 1 << 0 << endr; + trainData = { { 0, 1, 1, 0 }, + { 1, 0, 1, 0 } }; Mat labels; - labels << 1 << 1 << 1 << 0; + labels = { 1, 1, 1, 0 }; Perceptron<> p(trainData, labels.row(0), 2, 1000); mat testData; - testData << 0 << 1 << 1 << 0 << endr - << 1 << 0 << 1 << 0 << endr; + testData = { { 0, 1, 1, 0 }, + { 1, 0, 1, 0 } }; Row predictedLabels(testData.n_cols); p.Classify(testData, predictedLabels); @@ -162,17 +162,17 @@ TEST_CASE("Or", "[PerceptronTest]") TEST_CASE("Random3", "[PerceptronTest]") { mat trainData; - trainData << 0 << 1 << 1 << 4 << 5 << 4 << 1 << 2 << 1 << endr - << 1 << 0 << 1 << 1 << 1 << 2 << 4 << 5 << 4 << endr; + trainData = { { 0, 1, 1, 4, 5, 4, 1, 2, 1 }, + { 1, 0, 1, 1, 1, 2, 4, 5, 4 } }; Mat labels; - labels << 0 << 0 << 0 << 1 << 1 << 1 << 2 << 2 << 2; + labels = { 0, 0, 0, 1, 1, 1, 2, 2, 2 }; Perceptron<> p(trainData, labels.row(0), 3, 1000); mat testData; - testData << 0 << 1 << 1 << endr - << 1 << 0 << 1 << endr; + testData = { { 0, 1, 1 }, + { 1, 0, 1 } }; Row predictedLabels(testData.n_cols); p.Classify(testData, predictedLabels); @@ -187,17 +187,17 @@ TEST_CASE("Random3", "[PerceptronTest]") TEST_CASE("TwoPoints", "[PerceptronTest]") { mat trainData; - trainData << 0 << 1 << endr - << 1 << 0 << endr; + trainData = { { 0, 1 }, + { 1, 0 } }; Mat labels; - labels << 0 << 1; + labels = { 0, 1 }; Perceptron<> p(trainData, labels.row(0), 2, 1000); mat testData; - testData << 0 << 1 << endr - << 1 << 0 << endr; + testData = { { 0, 1 }, + { 1, 0 } }; Row predictedLabels(testData.n_cols); p.Classify(testData, predictedLabels); @@ -212,20 +212,17 @@ TEST_CASE("TwoPoints", "[PerceptronTest]") TEST_CASE("NonLinearlySeparableDataset", "[PerceptronTest]") { mat trainData; - trainData << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 - << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << endr - << 1 << 1 << 1 << 1 << 1 << 1 << 1 << 1 - << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << endr; + trainData = { { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 } }; Mat labels; - labels << 0 << 0 << 0 << 1 << 0 << 1 << 1 << 1 - << 0 << 0 << 0 << 1 << 0 << 1 << 1 << 1; + labels = { 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1 }; Perceptron<> p(trainData, labels.row(0), 2, 1000); mat testData; - testData << 3 << 4 << 5 << 6 << endr - << 3 << 2.3 << 1.7 << 1.5 << endr; + testData = { { 3, 4, 5, 6 }, + { 3, 2.3, 1.7, 1.5 } }; Row predictedLabels(testData.n_cols); p.Classify(testData, predictedLabels); @@ -238,14 +235,11 @@ TEST_CASE("NonLinearlySeparableDataset", "[PerceptronTest]") TEST_CASE("SecondaryConstructor", "[PerceptronTest]") { mat trainData; - trainData << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 - << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << endr - << 1 << 1 << 1 << 1 << 1 << 1 << 1 << 1 - << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << endr; + trainData = { { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2 } }; Mat labels; - labels << 0 << 0 << 0 << 1 << 0 << 1 << 1 << 1 - << 0 << 0 << 0 << 1 << 0 << 1 << 1 << 1; + labels = { 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1 }; Perceptron<> p1(trainData, labels.row(0), 2, 1000); diff --git a/src/mlpack/tests/qdafn_test.cpp b/src/mlpack/tests/qdafn_test.cpp index bf71af6057..b7ee07bba6 100644 --- a/src/mlpack/tests/qdafn_test.cpp +++ b/src/mlpack/tests/qdafn_test.cpp @@ -14,7 +14,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace std; using namespace arma; diff --git a/src/mlpack/tests/random_forest_test.cpp b/src/mlpack/tests/random_forest_test.cpp index 20397641e0..95d1f09a6a 100644 --- a/src/mlpack/tests/random_forest_test.cpp +++ b/src/mlpack/tests/random_forest_test.cpp @@ -13,7 +13,7 @@ #include #include -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "test_catch_tools.hpp" #include "catch.hpp" #include "mock_categorical_data.hpp" diff --git a/src/mlpack/tests/recurrent_network_test.cpp b/src/mlpack/tests/recurrent_network_test.cpp index 987c808ef2..7781b6c50c 100644 --- a/src/mlpack/tests/recurrent_network_test.cpp +++ b/src/mlpack/tests/recurrent_network_test.cpp @@ -20,7 +20,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "custom_layer.hpp" using namespace mlpack; @@ -435,7 +435,7 @@ TEST_CASE("SequenceClassificationBRNNTest", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -463,10 +463,10 @@ TEST_CASE("SequenceClassificationBRNNTest", "[RecurrentNetworkTest]") { const int predictionValue = arma::as_scalar(arma::find( arma::max(prediction.slice(rho - 1).col(i)) == - prediction.slice(rho - 1).col(i), 1) + 1); + prediction.slice(rho - 1).col(i), 1)); const int targetValue = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); if (predictionValue == targetValue) { @@ -510,7 +510,7 @@ TEST_CASE("SequenceClassificationTest", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -554,10 +554,10 @@ TEST_CASE("SequenceClassificationTest", "[RecurrentNetworkTest]") { const int predictionValue = arma::as_scalar(arma::find( arma::max(prediction.slice(rho - 1).col(i)) == - prediction.slice(rho - 1).col(i), 1) + 1); + prediction.slice(rho - 1).col(i), 1)); const int targetValue = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); if (predictionValue == targetValue) { @@ -728,7 +728,7 @@ TEST_CASE("RNNTrainReturnObjective", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -782,7 +782,7 @@ TEST_CASE("BRNNTrainReturnObjective", "[RecurrentNetworkTest]") for (size_t i = 0; i < labelsTemp.n_cols; ++i) { const int value = arma::as_scalar(arma::find( - arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)); labels.tube(0, i).fill(value); } @@ -845,15 +845,15 @@ TEST_CASE("LargeRhoValueRnnTest", "[RecurrentNetworkTest]") { const auto strLen = strlen(line); // Responses for NegativeLogLikelihood should be - // non-one-hot-encoded class IDs (from 1 to num_classes). + // non-one-hot-encoded class IDs (from 0 to num_classes - 1). MatType result(1, 1, strLen, arma::fill::zeros); // The response is the *next* letter in the sequence. for (size_t i = 0; i < strLen - 1; ++i) { - result.at(0, 0, i) = static_cast(line[i + 1]) + 1.0; + result.at(0, 0, i) = static_cast(line[i + 1]); } // The final response is empty, so we set it to class 0. - result.at(0, 0, strLen - 1) = 1.0; + result.at(0, 0, strLen - 1) = 0.0; return result; }; @@ -868,3 +868,66 @@ TEST_CASE("LargeRhoValueRnnTest", "[RecurrentNetworkTest]") model.Train(inputs[0], targets[0], opt); INFO("Training over"); } + +/** + * Test to make sure that an error is thrown when input with + * wrong input shape is provided to a RNN. + */ +TEST_CASE("RNNCheckInputShapeTest", "[RecurrentNetworkTest]") +{ + const size_t rho = 10; + + // Generate 12 (2 * 6) noisy sines. A single sine contains rho + // points/features. + arma::cube input; + arma::mat labelsTemp; + GenerateNoisySines(input, labelsTemp, rho, 6); + + arma::cube labels = arma::zeros(1, labelsTemp.n_cols, rho); + for (size_t i = 0; i < labelsTemp.n_cols; ++i) + { + const int value = arma::as_scalar(arma::find( + arma::max(labelsTemp.col(i)) == labelsTemp.col(i), 1)) + 1; + labels.tube(0, i).fill(value); + } + + /** + * Construct a network with 1 input unit, 4 hidden units and 10 output + * units. The hidden layer is connected to itself. The network structure + * looks like: + * + * Input Hidden Output + * Layer(1) Layer(4) Layer(10) + * +-----+ +-----+ +-----+ + * | | | | | | + * | +------>| +------>| | + * | | ..>| | | | + * +-----+ . +--+--+ +-----+ + * . . + * . . + * ....... + */ + Add<> add(4); + // Purposely providing wrong input shape of 3. + // The correct input shape is 1. + Linear<> lookup(3, 4); + SigmoidLayer<> sigmoidLayer; + Linear<> linear(4, 4); + Recurrent<>* recurrent = new Recurrent<>(add, lookup, linear, + sigmoidLayer, rho); + + RNN<> model(rho); + model.Add >(); + model.Add(recurrent); + model.Add >(4, 10); + model.Add >(); + + std::string expectedMsg = "RNN<>::Train(): "; + expectedMsg += "the first layer of the network expects "; + expectedMsg += std::to_string(3) + " elements, "; + expectedMsg += "but the input has " + std::to_string(1) + " dimensions! "; + + StandardSGD opt(0.1, 1, input.n_cols /* 1 epoch */, -100); + + REQUIRE_THROWS_AS(model.Train(input, labels, opt), std::logic_error); +} diff --git a/src/mlpack/tests/rnn_reber_test.cpp b/src/mlpack/tests/rnn_reber_test.cpp index b583a1c64a..da5d245592 100644 --- a/src/mlpack/tests/rnn_reber_test.cpp +++ b/src/mlpack/tests/rnn_reber_test.cpp @@ -20,7 +20,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "custom_layer.hpp" using namespace mlpack; @@ -106,7 +106,7 @@ template void ReberReverseTranslation(const MatType& translation, char& symbol) { arma::Col symbols; - symbols << 'B' << 'T' << 'S' << 'X' << 'P' << 'V' << 'E' << arma::endr; + symbols = { 'B', 'T', 'S', 'X', 'P', 'V', 'E' }; const int idx = arma::as_scalar(arma::find(translation == 1, 1, "first")); symbol = symbols(idx); @@ -121,7 +121,7 @@ void ReberReverseTranslation(const MatType& translation, char& symbol) void ReberTranslation(const char symbol, arma::colvec& translation) { arma::Col symbols; - symbols << 'B' << 'T' << 'S' << 'X' << 'P' << 'V' << 'E' << arma::endr; + symbols = { 'B', 'T', 'S', 'X', 'P', 'V', 'E' }; const int idx = arma::as_scalar(arma::find(symbols == symbol, 1, "first")); translation = arma::zeros(7); @@ -179,7 +179,6 @@ void GenerateNextRecursiveReber(const arma::Mat& transitions, else if (c == 'P' && state == 1) { numPs++; - state = 1; } else if (c == 'T' && state == 1) { @@ -206,7 +205,6 @@ void GenerateNextRecursiveReber(const arma::Mat& transitions, else if (c == 'P' && state == 5) { numPs--; - state = 5; } } @@ -261,12 +259,12 @@ arma::Mat GenerateReberGrammarData( // Reber state transition matrix. (The last two columns are the indices to the // next path). arma::Mat transitions; - transitions << 'T' << 'P' << '1' << '2' << arma::endr - << 'X' << 'S' << '3' << '1' << arma::endr - << 'V' << 'T' << '4' << '2' << arma::endr - << 'X' << 'S' << '2' << '5' << arma::endr - << 'P' << 'V' << '3' << '5' << arma::endr - << 'E' << 'E' << '0' << '0' << arma::endr; + transitions = { { 'T', 'P', '1', '2' }, + { 'X', 'S', '3', '1' }, + { 'V', 'T', '4', '2' }, + { 'X', 'S', '2', '5' }, + { 'P', 'V', '3', '5' }, + { 'E', 'E', '0', '0' } }; std::string trainReber, testReber; diff --git a/src/mlpack/tests/serialization_catch.cpp b/src/mlpack/tests/serialization.cpp similarity index 97% rename from src/mlpack/tests/serialization_catch.cpp rename to src/mlpack/tests/serialization.cpp index a07c4d84f6..0631be7b73 100644 --- a/src/mlpack/tests/serialization_catch.cpp +++ b/src/mlpack/tests/serialization.cpp @@ -1,5 +1,5 @@ /** - * @file tests/serialization_catch.cpp + * @file tests/serialization.cpp * @author Ryan Curtin * * Miscellaneous utility functions for serialization tests. @@ -9,7 +9,7 @@ * 3-clause BSD license along with mlpack. If not, see * http://www.opensource.org/licenses/BSD-3-Clause for more information. */ -#include "serialization_catch.hpp" +#include "serialization.hpp" #include "catch.hpp" namespace mlpack { diff --git a/src/mlpack/tests/serialization_catch.hpp b/src/mlpack/tests/serialization.hpp similarity index 99% rename from src/mlpack/tests/serialization_catch.hpp rename to src/mlpack/tests/serialization.hpp index f5caff467c..b88ff5e957 100644 --- a/src/mlpack/tests/serialization_catch.hpp +++ b/src/mlpack/tests/serialization.hpp @@ -1,5 +1,5 @@ /** - * @file tests/serialization_catch.hpp + * @file tests/serialization.hpp * @author Ryan Curtin * * Miscellaneous utility functions for serialization tests. diff --git a/src/mlpack/tests/serialization_test.cpp b/src/mlpack/tests/serialization_test.cpp index 364a9b1ff2..17b51cc865 100644 --- a/src/mlpack/tests/serialization_test.cpp +++ b/src/mlpack/tests/serialization_test.cpp @@ -17,7 +17,7 @@ #include #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" #include #include @@ -36,7 +36,6 @@ #include #include #include -#include #include #include #include @@ -52,7 +51,6 @@ using namespace mlpack::perceptron; using namespace mlpack::regression; using namespace mlpack::naive_bayes; using namespace mlpack::neighbor; -using namespace mlpack::decision_stump; using namespace mlpack::ann; using namespace arma; @@ -1087,42 +1085,6 @@ TEST_CASE("LSHTest", "[SerializationTest]") jsonLsh.SecondHashTable()[i], binaryLsh.SecondHashTable()[i]); } -// Make sure serialization works for the decision stump. -TEST_CASE("DecisionStumpTest", "[SerializationTest]") -{ - // Generate dataset. - arma::mat trainingData = arma::randu(4, 100); - arma::Row labels(100); - for (size_t i = 0; i < 25; ++i) - labels[i] = 0; - for (size_t i = 25; i < 50; ++i) - labels[i] = 3; - for (size_t i = 50; i < 75; ++i) - labels[i] = 1; - for (size_t i = 75; i < 100; ++i) - labels[i] = 2; - - DecisionStump<> ds(trainingData, labels, 4, 3); - - arma::mat otherData = arma::randu(3, 100); - arma::Row otherLabels = arma::randu>(100); - DecisionStump<> xmlDs(otherData, otherLabels, 2, 3); - - DecisionStump<> jsonDs; - DecisionStump<> binaryDs(trainingData, labels, 4, 10); - - SerializeObjectAll(ds, xmlDs, jsonDs, binaryDs); - - // Make sure that everything is the same about the new decision stumps. - REQUIRE(ds.SplitDimension() == xmlDs.SplitDimension()); - REQUIRE(ds.SplitDimension() == jsonDs.SplitDimension()); - REQUIRE(ds.SplitDimension() == binaryDs.SplitDimension()); - - CheckMatrices(ds.Split(), xmlDs.Split(), jsonDs.Split(), binaryDs.Split()); - CheckMatrices(ds.BinLabels(), xmlDs.BinLabels(), jsonDs.BinLabels(), - binaryDs.BinLabels()); -} - // Make sure serialization works for LARS. TEST_CASE("LARSTest", "[SerializationTest]") { @@ -1682,7 +1644,7 @@ TEST_CASE("CerealEmptyArrayWrapperTest", "[SerializationTest]") jsonT.mem = new int[5]; jsonT.len = 5; - SerializeObjectAll(t, xmlT, binaryT, jsonT); + SerializeObjectAll(t, xmlT, jsonT, binaryT); // Ensure that all the results are correct. REQUIRE(xmlT.mem == (int*) NULL); diff --git a/src/mlpack/tests/sparse_coding_test.cpp b/src/mlpack/tests/sparse_coding_test.cpp index c2981c776e..a4a83ac791 100644 --- a/src/mlpack/tests/sparse_coding_test.cpp +++ b/src/mlpack/tests/sparse_coding_test.cpp @@ -17,7 +17,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace arma; using namespace mlpack; diff --git a/src/mlpack/tests/string_encoding_test.cpp b/src/mlpack/tests/string_encoding_test.cpp index 8793981fce..b3af269d02 100644 --- a/src/mlpack/tests/string_encoding_test.cpp +++ b/src/mlpack/tests/string_encoding_test.cpp @@ -21,7 +21,7 @@ #include #include "test_catch_tools.hpp" #include "catch.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::data; diff --git a/src/mlpack/tests/svd_batch_test.cpp b/src/mlpack/tests/svd_batch_test.cpp index bac6df4917..41b005a28d 100644 --- a/src/mlpack/tests/svd_batch_test.cpp +++ b/src/mlpack/tests/svd_batch_test.cpp @@ -70,7 +70,8 @@ class SpecificRandomInitialization TEST_CASE("SVDBatchMomentumTest", "[SVDBatchTest]") { mat dataset; - data::Load("GroupLensSmall.csv", dataset); + if (!data::Load("GroupLensSmall.csv", dataset)) + FAIL("Cannot load dataset GroupLensSmall.csv!"); // Generate list of locations for batch insert constructor for sparse // matrices. @@ -117,7 +118,8 @@ TEST_CASE("SVDBatchMomentumTest", "[SVDBatchTest]") TEST_CASE("SVDBatchRegularizationTest", "[SVDBatchTest]") { mat dataset; - data::Load("GroupLensSmall.csv", dataset); + if (!data::Load("GroupLensSmall.csv", dataset)) + FAIL("Cannot load dataset GroupLensSmall.csv!"); // Generate list of locations for batch insert constructor for sparse // matrices. diff --git a/src/mlpack/tests/wgan_test.cpp b/src/mlpack/tests/wgan_test.cpp index 9bd73449ad..99998723c4 100644 --- a/src/mlpack/tests/wgan_test.cpp +++ b/src/mlpack/tests/wgan_test.cpp @@ -22,7 +22,7 @@ #include "catch.hpp" #include "test_catch_tools.hpp" -#include "serialization_catch.hpp" +#include "serialization.hpp" using namespace mlpack; using namespace mlpack::ann;