diff --git a/.ci/ci.yaml b/.ci/ci.yaml index 7a1f41de0b..ab57443861 100644 --- a/.ci/ci.yaml +++ b/.ci/ci.yaml @@ -19,16 +19,15 @@ jobs: # RAM usage. CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF -DUSE_PRECOMPILED_HEADERS=OFF' Python: + CMakeArgs: '-DDEBUG=OFF -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=ON -DPYTHON_EXECUTABLE=/usr/bin/python3 -DBUILD_GO_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF' binding: 'python' python.version: '3.7' - CMakeArgs: '-DDEBUG=OFF -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=ON -DPYTHON_EXECUTABLE=/usr/bin/python3 -DBUILD_GO_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF' Julia: - julia.version: '1.3.0' - CMakeArgs: '-DDEBUG=OFF -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=ON -DBUILD_GO_BINDINGS=OFF -DJULIA_EXECUTABLE=/opt/julia-1.6.3/bin/julia -DBUILD_R_BINDINGS=OFF' + CMakeArgs: '-DDEBUG=OFF -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=ON -DBUILD_GO_BINDINGS=OFF -DJULIA_EXECUTABLE=/opt/julia-1.10.4/bin/julia -DBUILD_R_BINDINGS=OFF' + binding: 'julia' Go: - binding: 'go' - go.version: '1.11.0' CMakeArgs: '-DDEBUG=OFF -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=ON -DBUILD_R_BINDINGS=OFF' + binding: 'go' Markdown: CMakeArgs: '-DDEBUG=OFF -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_MARKDOWN_BINDINGS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF' @@ -44,20 +43,19 @@ jobs: # clang on OS X segfaults when using precompiled headers, so we disable # them. Plain: + python.version: '3.8' CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF -DUSE_PRECOMPILED_HEADERS=OFF' - python.version: '3.8' Python: - binding: 'python' python.version: '3.8' + binding: 'python' CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=ON -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF -DUSE_PRECOMPILED_HEADERS=OFF' Julia: python.version: '3.8' - julia.version: '1.6.3' + binding: 'julia' CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_JULIA_BINDINGS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_GO_BINDINGS=OFF -DBUILD_R_BINDINGS=OFF -DUSE_PRECOMPILED_HEADERS=OFF' Go: - binding: 'go' python.version: '3.8' - go.version: '1.11.0' + binding: 'go' CMakeArgs: '-DDEBUG=ON -DPROFILE=OFF -DBUILD_TESTS=ON -DBUILD_PYTHON_BINDINGS=OFF -DBUILD_JULIA_BINDINGS=OFF -DBUILD_GO_BINDINGS=ON -DBUILD_R_BINDINGS=OFF -DUSE_PRECOMPILED_HEADERS=OFF' steps: diff --git a/.ci/linux-steps.yaml b/.ci/linux-steps.yaml index ebaa342552..cb669ba27e 100644 --- a/.ci/linux-steps.yaml +++ b/.ci/linux-steps.yaml @@ -7,7 +7,8 @@ steps: # Set python version - task: UsePythonVersion@0 inputs: - versionSpec: '3.7' + versionSpec: '$(python.version)' + condition: ne(variables['python.version'], '') # Install build dependencies. - script: | @@ -24,19 +25,18 @@ steps: sudo apt-get install -y --allow-unauthenticated libopenblas-dev g++ xz-utils - if [ "$(binding)" == "python" ]; then - export PYBIN=$(which python) - $PYBIN -m pip install --upgrade pip - $PYBIN -m pip install --upgrade --ignore-installed setuptools cython pandas wheel + if [ "$BINDING" = "python" ]; then + python -m pip install --upgrade pip + python -m pip install --upgrade --ignore-installed setuptools cython pandas wheel fi - if [ "a$(julia.version)" != "a" ]; then - wget https://julialang-s3.julialang.org/bin/linux/x64/1.6/julia-1.6.3-linux-x86_64.tar.gz - sudo tar -C /opt/ -xvpf julia-1.6.3-linux-x86_64.tar.gz + if [ "$BINDING" = "julia" ]; then + wget https://julialang-s3.julialang.org/bin/linux/x64/1.10/julia-1.10.4-linux-x86_64.tar.gz + sudo tar -C /opt/ -xvpf julia-1.10.4-linux-x86_64.tar.gz fi # Install armadillo. - curl -k -L https://sourceforge.net/projects/arma/files/armadillo-9.800.6.tar.xz | tar -xvJ && \ + curl -k -L https://sourceforge.net/projects/arma/files/armadillo-10.8.2.tar.xz | tar -xvJ && \ cd armadillo* && \ cmake . && \ make && \ @@ -68,14 +68,13 @@ steps: # Configure mlpack (CMake) - script: | - unset BOOST_ROOT mkdir build && cd build - if [ "$(binding)" == "go" ]; then + if [ "$BINDING" = "go" ]; then export GOPATH=$PWD/src/mlpack/bindings/go export GO111MODULE=off go get -u -t gonum.org/v1/gonum/... fi - cmake $(CMakeArgs) -DPYTHON_EXECUTABLE=`which python` -DCEREAL_INCLUDE_DIR=/usr/include/ .. + cmake $CMAKEARGS -DPYTHON_EXECUTABLE=`which python` -DCEREAL_INCLUDE_DIR=/usr/include/ .. displayName: 'CMake' # Build mlpack diff --git a/.ci/macos-steps.yaml b/.ci/macos-steps.yaml index 0607b6d431..d6f177f28a 100644 --- a/.ci/macos-steps.yaml +++ b/.ci/macos-steps.yaml @@ -15,12 +15,12 @@ steps: sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer brew install libomp openblas armadillo cereal ensmallen - if [ "$(binding)" == "python" ]; then + if [ "$BINDING" = "python" ]; then pip install --upgrade pip pip install cython numpy pandas zipp configparser wheel fi - if [ "a$(julia.version)" != "a" ]; then + if [ "$BINDING" = "julia" ]; then brew install --cask julia fi @@ -29,16 +29,15 @@ steps: # Configure mlpack (CMake) - script: | mkdir build && cd build - if [ "$(binding)" == "go" ]; then + if [ "$BINDING" = "go" ]; then export GOPATH=$PWD/src/mlpack/bindings/go export GO111MODULE=off go get -u -t gonum.org/v1/gonum/... fi - if [ "$(binding)" == "python" ]; then - export PYPATH=$(which python) - cmake $(CMakeArgs) -DPYTHON_EXECUTABLE=$PYPATH .. + if [ "$BINDING" = "python" ]; then + cmake $CMAKEARGS -DPYTHON_EXECUTABLE=$(which python) .. else - cmake $(CMakeArgs) .. + cmake $CMAKEARGS .. fi displayName: 'CMake' diff --git a/.ci/windows-steps.yaml b/.ci/windows-steps.yaml index 9d3a91baa2..4c615a428f 100644 --- a/.ci/windows-steps.yaml +++ b/.ci/windows-steps.yaml @@ -27,10 +27,10 @@ steps: - bash: | git clone --depth 1 https://github.com/mlpack/jenkins-conf.git conf - curl -O -L https://sourceforge.net/projects/arma/files/armadillo-9.800.6.tar.xz -o armadillo-9.800.6.tar.xz - tar -xvf armadillo-9.800.6.tar.xz + curl -O -L https://sourceforge.net/projects/arma/files/armadillo-10.8.2.tar.xz -o armadillo-10.8.2.tar.xz + tar -xvf armadillo-10.8.2.tar.xz - cd armadillo-9.800.6/ && cmake $(CMakeGenerator) \ + cd armadillo-10.8.2/ && cmake $(CMakeGenerator) \ -DBLAS_LIBRARY:FILEPATH=$(Agent.ToolsDirectory)/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a \ -DLAPACK_LIBRARY:FILEPATH=$(Agent.ToolsDirectory)/OpenBLAS.0.2.14.1/lib/native/lib/x64/libopenblas.dll.a \ -DCMAKE_PREFIX:FILEPATH=../../armadillo \ @@ -41,7 +41,7 @@ steps: # Build armadillo - task: MSBuild@1 inputs: - solution: 'armadillo-9.800.6/*.sln' + solution: 'armadillo-10.8.2/*.sln' msbuildLocationMethod: 'location' msbuildVersion: $(MSBuildVersion) configuration: 'Release' @@ -60,8 +60,8 @@ steps: $(CMakeArgs) ` -DBLAS_LIBRARIES:FILEPATH=$(Agent.ToolsDirectory)\OpenBLAS.0.2.14.1\lib\native\lib\x64\libopenblas.dll.a ` -DLAPACK_LIBRARIES:FILEPATH=$(Agent.ToolsDirectory)\OpenBLAS.0.2.14.1\lib\native\lib\x64\libopenblas.dll.a ` - -DARMADILLO_INCLUDE_DIR="..\armadillo-9.800.6\tmp\include" ` - -DARMADILLO_LIBRARY="..\armadillo-9.800.6\Release\armadillo.lib" ` + -DARMADILLO_INCLUDE_DIR="..\armadillo-10.8.2\tmp\include" ` + -DARMADILLO_LIBRARY="..\armadillo-10.8.2\Release\armadillo.lib" ` -DCEREAL_INCLUDE_DIR="..\cereal-1.3.2\include" ` -DENSMALLEN_INCLUDE_DIR=$(Agent.ToolsDirectory)\ensmallen.2.17.0\installed\x64-linux\include ` -DBUILD_JULIA_BINDINGS=OFF ` diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml new file mode 100644 index 0000000000..1270e838d1 --- /dev/null +++ b/.github/workflows/auto-approve.yml @@ -0,0 +1,22 @@ +# Once a PR has been approved by one member of the mlpack organization, a second +# approving review will automatically be added 24 hours later. This allows time +# for other maintainers to take a look. +name: Auto-approve pull requests +on: + schedule: + # Run roughly every four hours. + - cron: "15 0,4,8,12,16,20 * * *" + +jobs: + auto-approve: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Auto-approve pull requests + uses: rcurtin/actions/auto-approve@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + approval-message: + 'Second approval provided automatically after 24 hours. :+1:' diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..8f59d84fbf --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,24 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + days-before-issues-stale: 30 + days-before-issue-close: 7 + stale-issue-label: "s: stale" + stale-pr-label: "s: stale" + stale-issue-message: "This issue has been automatically marked as stale because it has not had any recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions! :+1:" + days-before-pr-stale: 30, + days-before-pr-close: 14 + repo-token: ${{ secrets.GITHUB_TOKEN }} + exempt-issue-labels: "s: keep open" + exempt-pr-labels: "s: keep open" diff --git a/.github/workflows/stickers.yaml b/.github/workflows/stickers.yaml new file mode 100644 index 0000000000..aafb7d390b --- /dev/null +++ b/.github/workflows/stickers.yaml @@ -0,0 +1,17 @@ +# Post a message to new contributors that they can get some stickers mailed to +# them. +name: 'Stickers for new contributors' +on: + pull_request: + types: [closed] + +jobs: + sticker_comment: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + steps: + # Forked version of first-interaction that runs only on first merged PR. + - uses: rcurtin/actions/stickers@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: "Hello there! Thanks for your contribution. Congratulations on your first contribution to mlpack! If you'd like to add your name to the list of contributors in `COPYRIGHT.txt` and you haven't already, please feel free to push a change to this PR---or, if it gets merged before you can, feel free to open another PR.\n\nIn addition, if you'd like some stickers to put on your laptop, we can get them in the mail for you. Just send an email with your physical mailing address to stickers@mlpack.org, and then one of the mlpack maintainers will put some stickers in an envelope for you. It may take a few weeks to get them, depending on your location. :+1:" diff --git a/.github/workflows/welcome-pr.yaml b/.github/workflows/welcome-pr.yaml new file mode 100644 index 0000000000..00341842fb --- /dev/null +++ b/.github/workflows/welcome-pr.yaml @@ -0,0 +1,16 @@ +# Post a message to new contributors that they can get some stickers mailed to +# them. +name: 'Welcome message for new contributors' +on: + pull_request: + types: [open] + +jobs: + sticker_comment: + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + steps: + - uses: actions/first-interaction@v1.3.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + pr-message: "Thanks for opening your first pull request in this repository! Someone will review it when they have a chance. In the mean time, please be sure that you've handled the following things, to make the review process quicker and easier:\n\n - All code should follow the [style guide](https://github.com/mlpack/mlpack/wiki/DesignGuidelines#style-guidelines)\n - Documentation added for any new functionality\n - Tests added for any new functionality\n - Tests that are added follow the [testing guide](https://github.com/mlpack/mlpack/wiki/Testing-Guidelines)\n - Headers and license information added to the top of any new code files\n - HISTORY.md updated if the changes are big or user-facing\n - All CI checks should be passing\n\nThank you again for your contributions! :+1:" diff --git a/CMake/ConfigureCrossCompile.cmake b/CMake/ConfigureCrossCompile.cmake index a320d32932..0a39b829ed 100644 --- a/CMake/ConfigureCrossCompile.cmake +++ b/CMake/ConfigureCrossCompile.cmake @@ -28,6 +28,7 @@ macro(search_openblas version) get_deps(https://github.com/xianyi/OpenBLAS/releases/download/v${version}/OpenBLAS-${version}.tar.gz OpenBLAS OpenBLAS-${version}.tar.gz) if (NOT MSVC) if (NOT EXISTS "${CMAKE_BINARY_DIR}/deps/OpenBLAS-${version}/libopenblas.a") + set(ENV{COMMON_OPT} "${CMAKE_OPENBLAS_FLAGS}") # Pass our flags to OpenBLAS execute_process(COMMAND make TARGET=${OPENBLAS_TARGET} BINARY=${OPENBLAS_BINARY} HOSTCC=gcc CC=${CMAKE_C_COMPILER} FC=${CMAKE_FORTRAN_COMPILER} NO_SHARED=1 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/deps/OpenBLAS-${version}) endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 75f6fe1b9d..ced015958b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ option(USE_PRECOMPILED_HEADERS "Use precompiled headers for mlpack_test build." # For Armadillo, try to keep the minimum required version less than or equal to # what's available on the current Ubuntu LTS or most recent stable RHEL release. # See https://github.com/mlpack/mlpack/issues/3033 for some more discussion. -set(ARMADILLO_VERSION "9.800") +set(ARMADILLO_VERSION "10.8") set(ENSMALLEN_VERSION "2.10.0") set(CEREAL_VERSION "1.1.2") @@ -249,8 +249,12 @@ if (DEBUG) else() add_definitions(-DNDEBUG) if (NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O3") + if (NOT CMAKE_CROSSCOMPILING) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O3") + else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") + endif() else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /O3") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /O3") @@ -283,7 +287,7 @@ endif() # Download and compile OpenBLAS if we are cross compiling mlpack for a specific # architecture. The function takes the version of OpenBLAS as variable. if (CMAKE_CROSSCOMPILING) - search_openblas(0.3.13) + search_openblas(0.3.26) endif() if (NOT DOWNLOAD_DEPENDENCIES) diff --git a/HISTORY.md b/HISTORY.md index 68f1bf4d91..3f6ef2d629 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,9 +4,19 @@ _????-??-??_ - * Distribute STB headers as part of R package (#3724, #3726). - * Added OpenMP support for fast approximation (#3685). + * Distribute STB headers as part of R package (#3724, #3726). + * Added OpenMP parallelization to Hamerly, Naive, and Elkan k-means (#3761, #3762, #3764). + + * Added OpenMP support for fast approximation (#3685). + + * Implemented the Find and Fill algorithm into the Dropout Layer and added OpenMP support (#3684). + + * Update Python bindings to support NumPy 2.x (#3752). + + * Bump minimum Armadillo version to 10.8 (#3760). + + * Adapt `NearestInterpolation` ANN layer to new Layer Inteface (#3768). ## mlpack 4.4.0 diff --git a/README.md b/README.md index 7bf1bf301a..70c3c2ab01 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ Citations are beneficial for the growth and improvement of mlpack. **mlpack** requires the following additional dependencies: - C++17 compiler - - [Armadillo](https://arma.sourceforge.net)   >= 9.800 + - [Armadillo](https://arma.sourceforge.net)   >= 10.8 - [ensmallen](https://ensmallen.org)  >= 2.10.0 - [cereal](http://uscilab.github.io/cereal/)     >= 1.1.2 @@ -333,7 +333,7 @@ dependencies are installed: - R >= 4.0 - Rcpp >= 0.12.12 - - RcppArmadillo >= 0.9.800.0 + - RcppArmadillo >= 0.10.8.0 - RcppEnsmallen >= 0.2.10.0 - roxygen2 - testthat diff --git a/board/flags-config.cmake b/board/flags-config.cmake index 94b2060b44..5999ab86ac 100644 --- a/board/flags-config.cmake +++ b/board/flags-config.cmake @@ -4,63 +4,86 @@ # footprints. # Set generic minimization flags for all platforms. -# These flags are the same for all cross-compilation cases. -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -fdata-sections -ffunction-sections") +# These flags are the same for all cross-compilation cases and they are +# mainly to reduce the binary footprint. +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Os -s -fdata-sections -ffunction-sections") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fomit-frame-pointer -fno-unwind-tables") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-asynchronous-unwind-tables -fvisibility=hidden") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fshort-enums -finline-small-functions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -findirect-inlining -fno-common") -#-flto -fuse-ld=gold # There is an issue with gold link when compiling on -# Ubuntu 16. At that point gcc linker did not integrate the flto support -# inside and it was a separate plugin that need to be added. Therefore, -# this can be added when mlpack Azure CI moves toward Ubuntu 20. +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmerge-all-constants -fno-ident") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-unroll-loops -fno-math-errno") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-stack-protector") +set(CMAKE_OPENBLAS_FLAGS "${CMAKE_CXX_FLAGS}") # OpenBLAS does not supoport flto +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--hash-style=gnu -Wl,--build-id=none") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,norelro") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") +## Keep the following flag in comment, they might be relevant in the case of MCU's +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-nmagic,-Bsymbolic -nostartfiles") set(BOARD_NAME "" CACHE STRING "Specify Board name to optimize for.") string(TOUPPER ${BOARD_NAME} BOARD) # Set specific platforms CMAKE CXX flags. -if(BOARD MATCHES "RPI0" OR BOARD MATCHES "RPI1") +if(BOARD MATCHES "RPI0" OR BOARD MATCHES "RPI1" OR BOARD MATCHES "ARM11") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=arm1176jzf-s") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp") set(OPENBLAS_TARGET "ARMV6") set(OPENBLAS_BINARY "32") -elseif(BOARD MATCHES "RPI2") +elseif(BOARD MATCHES "RPI2" OR BOARD MATCHES "CORTEXA7") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a7") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon-vfpv4") set(OPENBLAS_TARGET "ARMV7") set(OPENBLAS_BINARY "32") -elseif(BOARD MATCHES "RPI3") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a53") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") +elseif(BOARD MATCHES "CORTEXA8") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a8") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon") + set(OPENBLAS_TARGET "ARMV7") + set(OPENBLAS_BINARY "32") +elseif(BOARD MATCHES "CORTEXA9") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a9") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon") + set(OPENBLAS_TARGET "CORTEXA9") + set(OPENBLAS_BINARY "32") +elseif(BOARD MATCHES "CORTEXA15") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a15") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=hard -mfpu=neon") + set(OPENBLAS_TARGET "CORTEXA15") + set(OPENBLAS_BINARY "32") +elseif(BOARD MATCHES "RPI3" OR BOARD MATCHES "CORTEXA53") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a53 -ftree-vectorize") set(OPENBLAS_TARGET "CORTEXA53") set(OPENBLAS_BINARY "64") -elseif(BOARD MATCHES "RPI4") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a72") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") +elseif(BOARD MATCHES "RPI4" OR BOARD MATCHES "CORTEXA72") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a72 -ftree-vectorize") set(OPENBLAS_TARGET "CORTEXA72") set(OPENBLAS_BINARY "64") +elseif(BOARD MATCHES "JETSONAGX" OR BOARD MATCHES "CORTEXA76") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=cortex-a76 -ftree-vectorize") + set(OPENBLAS_TARGET "CORTEXA76") + set(OPENBLAS_BINARY "64") elseif(BOARD MATCHES "BV") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") set(OPENBLAS_TARGET "RISCV64_GENERIC") set(OPENBLAS_BINARY "64") -elseif(BOARD MATCHES "JETSONAGX") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -matune=cortex-a76") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") - set(OPENBLAS_TARGET "ARM8") +elseif(BOARD MATCHES "C906") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=thead-c906") + set(OPENBLAS_TARGET "RISCV64_GENERIC") + set(OPENBLAS_BINARY "64") +elseif(BOARD MATCHES "x280") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=sifive-x280") + set(OPENBLAS_TARGET "x280") set(OPENBLAS_BINARY "64") elseif(BOARD MATCHES "KATAMI") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=pentium3") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") set(OPENBLAS_TARGET "KATAMI") set(OPENBLAS_BINARY "32") elseif(BOARD MATCHES "COPPERMINE") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=pentium3") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") set(OPENBLAS_TARGET "COPPERMINE") set(OPENBLAS_BINARY "32") elseif(BOARD MATCHES "NORTHWOOD") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=pentium4") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") set(OPENBLAS_TARGET "NORTHWOOD") set(OPENBLAS_BINARY "32") elseif(BOARD) diff --git a/doc/index.md b/doc/index.md index 607a2ae214..34aa7fea2f 100644 --- a/doc/index.md +++ b/doc/index.md @@ -100,29 +100,54 @@ Predict continuous values. ### Clustering algorithms +***NOTE:*** this documentation is still under construction and so some +algorithms that mlpack implements are not yet listed here. For now, see +[the mlpack/methods directory](https://github.com/mlpack/mlpack/tree/master/src/mlpack/methods) +for a full list of algorithms. + Group points into clusters. - + * [`MeanShift`](user/methods/mean_shift.md): clustering with the density-based + mean shift algorithm ### Geometric algorithms +***NOTE:*** this documentation is still under construction and so no geometric +algorithms in mlpack are documented yet. For now, see +[the mlpack/methods directory](https://github.com/mlpack/mlpack/tree/master/src/mlpack/methods) +for a full list of algorithms. + Computations based on distance metrics. ### Preprocessing utilities +***NOTE:*** this documentation is still under construction and so no +preprocessing utilities in mlpack are documented yet. For now, see +[the mlpack/methods/preprocess directory](https://github.com/mlpack/mlpack/tree/master/src/mlpack/methods) +for a full list of algorithms. + Prepare data for machine learning algorithms. ### Transformations +***NOTE:*** this documentation is still under construction and so some +algorithms that mlpack implements are not yet listed here. For now, see +[the mlpack/methods directory](https://github.com/mlpack/mlpack/tree/master/src/mlpack/methods) +for a full list of algorithms. + Transform data from one space to another. * [`AMF`](user/methods/amf.md): alternating matrix factorization * [`LocalCoordinateCoding`](user/methods/local_coordinate_coding.md): local coordinate coding with dictionary learning + * [`LMNN`](user/methods/lmnn.md): large margin nearest neighbor (distance + metric learning) + * [`NCA`](user/methods/nca.md): neighborhood components analysis (distance + metric learning) * [`NMF`](user/methods/nmf.md): non-negative matrix factorization * [`PCA`](user/methods/pca.md): principal components analysis * [`SparseCoding`](user/methods/sparse_coding.md): sparse coding with diff --git a/doc/quickstart/cpp.md b/doc/quickstart/cpp.md index 18c3781b79..da1fc7d133 100644 --- a/doc/quickstart/cpp.md +++ b/doc/quickstart/cpp.md @@ -24,7 +24,7 @@ sudo apt-get install libmlpack-dev and on Fedora or Red Hat: ```sh -sudo dnf install mlpack +sudo dnf install mlpack-devel ``` You can also use a Docker image from Dockerhub, diff --git a/doc/sidebar.html b/doc/sidebar.html index 568496310e..a5ec834955 100644 --- a/doc/sidebar.html +++ b/doc/sidebar.html @@ -132,9 +132,20 @@ when the sidebar is built for each page.
  • - - Clustering - +
    + + + Clustering + + + +
  • @@ -170,6 +181,16 @@ when the sidebar is built for each page. LocalCoordinateCoding +
  • + + LMNN + +
  • +
  • + + NCA + +
  • NMF diff --git a/doc/user/bindings/cli.md b/doc/user/bindings/cli.md index bc02653831..adfd721f22 100644 --- a/doc/user/bindings/cli.md +++ b/doc/user/bindings/cli.md @@ -1677,9 +1677,9 @@ $ mlpack_linear_svm --input_model_file lsvm_model.bin --test_file test.csv $ mlpack_lmnn [--batch_size 50] [--center] [--distance_file ] [--help] [--info ] --input_file [--k 1] [--labels_file ] [--linear_scan] [--max_iterations 100000] [--normalize] - [--optimizer 'amsgrad'] [--passes 50] [--print_accuracy] [--range 1] - [--rank 0] [--regularization 0.5] [--seed 0] [--step_size 0.01] - [--tolerance 1e-07] [--verbose] [--version] [--centered_data_file + [--optimizer 'amsgrad'] [--passes 50] [--print_accuracy] [--rank 0] + [--regularization 0.5] [--seed 0] [--step_size 0.01] [--tolerance 1e-07] + [--update_interval 1] [--verbose] [--version] [--centered_data_file ] [--output_file ] [--transformed_data_file ] ``` @@ -1706,12 +1706,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning | `--optimizer (-O)` | [`string`](#doc_string) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `'amsgrad'` | | `--passes (-p)` | [`int`](#doc_int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` | | `--print_accuracy (-P)` | [`flag`](#doc_flag) | Print accuracies on initial and transformed dataset | | -| `--range (-R)` | [`int`](#doc_int) | Number of iterations after which impostors needs to be recalculated | `1` | | `--rank (-A)` | [`int`](#doc_int) | Rank of distance matrix to be optimized. | `0` | | `--regularization (-r)` | [`double`](#doc_double) | Regularization for LMNN objective function | `0.5` | | `--seed (-s)` | [`int`](#doc_int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` | | `--step_size (-a)` | [`double`](#doc_double) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` | | `--tolerance (-t)` | [`double`](#doc_double) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` | +| `--update_interval (-R)` | [`int`](#doc_int) | Number of iterations after which impostors need to be recalculated. | `1` | | `--verbose (-v)` | [`flag`](#doc_flag) | Display informational messages and the full list of parameters and timers at the end of execution. | | | `--version (-V)` | [`flag`](#doc_flag) | Display the version of mlpack. Only exists in CLI binding. | | @@ -1731,7 +1731,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `--input_file (-i)`), or alternatively as a separate matrix (specified with `--labels_file (-l)`). Additionally, a starting point for optimization (specified with `--distance_file (-d)`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `--rank (-A)`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point). -The program also requires number of targets neighbors to work with ( specified with `--k (-k)`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `--regularization (-r)`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `--range (-R)`). +The program also requires number of targets neighbors to work with ( specified with `--k (-k)`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `--regularization (-r)`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `--update_interval (-R)`). Output can either be the learned distance matrix (specified with `--output_file (-o)`), or the transformed dataset (specified with `--transformed_data_file (-D)`), or both. Additionally mean-centered dataset (specified with `--centered_data_file (-c)`) can be accessed given mean-centering (specified with `--center (-C)`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `--print_accuracy (-P)`parameter. @@ -1755,10 +1755,10 @@ $ mlpack_lmnn --input_file iris.csv --labels_file iris_labels.csv --k 3 --optimizer bbsgd --output_file output.csv ``` -An another program call making use of range & regularization parameter with dataset having labels as last column can be made as: +Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as: ```bash -$ mlpack_lmnn --input_file letter_recognition.csv --k 5 --range 10 +$ mlpack_lmnn --input_file letter_recognition.csv --k 5 --update_interval 10 --regularization 0.4 --output_file output.csv ``` diff --git a/doc/user/bindings/go.md b/doc/user/bindings/go.md index c9baae764f..71878e2430 100644 --- a/doc/user/bindings/go.md +++ b/doc/user/bindings/go.md @@ -2069,12 +2069,12 @@ param.Normalize = false param.Optimizer = "amsgrad" param.Passes = 50 param.PrintAccuracy = false -param.Range = 1 param.Rank = 0 param.Regularization = 0.5 param.Seed = 0 param.StepSize = 0.01 param.Tolerance = 1e-07 +param.UpdateInterval = 1 param.Verbose = false centered_data, output, transformed_data := mlpack.Lmnn(input, param) @@ -2102,12 +2102,12 @@ There are two types of input options: required options, which are passed directl | `Optimizer` | [`string`](#doc_string) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `"amsgrad"` | | `Passes` | [`int`](#doc_int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` | | `PrintAccuracy` | [`bool`](#doc_bool) | Print accuracies on initial and transformed dataset | `false` | -| `Range` | [`int`](#doc_int) | Number of iterations after which impostors needs to be recalculated | `1` | | `Rank` | [`int`](#doc_int) | Rank of distance matrix to be optimized. | `0` | | `Regularization` | [`float64`](#doc_float64) | Regularization for LMNN objective function | `0.5` | | `Seed` | [`int`](#doc_int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` | | `StepSize` | [`float64`](#doc_float64) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` | | `Tolerance` | [`float64`](#doc_float64) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` | +| `UpdateInterval` | [`int`](#doc_int) | Number of iterations after which impostors need to be recalculated. | `1` | | `Verbose` | [`bool`](#doc_bool) | Display informational messages and the full list of parameters and timers at the end of execution. | `false` | ### Output options @@ -2127,7 +2127,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `Input`), or alternatively as a separate matrix (specified with `Labels`). Additionally, a starting point for optimization (specified with `Distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `Rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point). -The program also requires number of targets neighbors to work with ( specified with `K`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `Regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `Range`). +The program also requires number of targets neighbors to work with ( specified with `K`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `Regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `UpdateInterval`). Output can either be the learned distance matrix (specified with `Output`), or the transformed dataset (specified with `TransformedData`), or both. Additionally mean-centered dataset (specified with `CenteredData`) can be accessed given mean-centering (specified with `Center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `PrintAccuracy`parameter. @@ -2156,13 +2156,13 @@ param.Optimizer = "bbsgd" _, output, _ := mlpack.Lmnn(iris, param) ``` -An another program call making use of range & regularization parameter with dataset having labels as last column can be made as: +Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as: ```go // Initialize optional parameters for Lmnn(). param := mlpack.LmnnOptions() param.K = 5 -param.Range = 10 +param.UpdateInterval = 10 param.Regularization = 0.4 _, output, _ := mlpack.Lmnn(letter_recognition, param) diff --git a/doc/user/bindings/julia.md b/doc/user/bindings/julia.md index 5d9e699354..6b81648246 100644 --- a/doc/user/bindings/julia.md +++ b/doc/user/bindings/julia.md @@ -1696,9 +1696,9 @@ julia> using mlpack: lmnn julia> centered_data, output, transformed_data = lmnn(input; batch_size=50, center=false, distance=zeros(0, 0), k=1, labels=Int[], linear_scan=false, max_iterations=100000, normalize=false, - optimizer="amsgrad", passes=50, print_accuracy=false, range=1, rank=0, + optimizer="amsgrad", passes=50, print_accuracy=false, rank=0, regularization=0.5, seed=0, step_size=0.01, tolerance=1e-07, - verbose=false) + update_interval=1, verbose=false) ``` An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning technique. Given a labeled dataset, this learns a transformation of the data that improves k-nearest-neighbor performance; this can be useful as a preprocessing step. [Detailed documentation](#lmnn_detailed-documentation). @@ -1722,12 +1722,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning | `optimizer` | [`String`](#doc_String) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `"amsgrad"` | | `passes` | [`Int`](#doc_Int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` | | `print_accuracy` | [`Bool`](#doc_Bool) | Print accuracies on initial and transformed dataset | `false` | -| `range` | [`Int`](#doc_Int) | Number of iterations after which impostors needs to be recalculated | `1` | | `rank` | [`Int`](#doc_Int) | Rank of distance matrix to be optimized. | `0` | | `regularization` | [`Float64`](#doc_Float64) | Regularization for LMNN objective function | `0.5` | | `seed` | [`Int`](#doc_Int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` | | `step_size` | [`Float64`](#doc_Float64) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` | | `tolerance` | [`Float64`](#doc_Float64) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` | +| `update_interval` | [`Int`](#doc_Int) | Number of iterations after which impostors need to be recalculated. | `1` | | `verbose` | [`Bool`](#doc_Bool) | Display informational messages and the full list of parameters and timers at the end of execution. | `false` | ### Output options @@ -1747,7 +1747,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `input`), or alternatively as a separate matrix (specified with `labels`). Additionally, a starting point for optimization (specified with `distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point). -The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `range`). +The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `update_interval`). Output can either be the learned distance matrix (specified with `output`), or the transformed dataset (specified with `transformed_data`), or both. Additionally mean-centered dataset (specified with `centered_data`) can be accessed given mean-centering (specified with `center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `print_accuracy`parameter. @@ -1774,13 +1774,13 @@ julia> _, output, _ = lmnn(iris; k=3, labels=iris_labels, optimizer="bbsgd") ``` -An another program call making use of range & regularization parameter with dataset having labels as last column can be made as: +Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as: ```julia julia> using CSV julia> letter_recognition = CSV.read("letter_recognition.csv") -julia> _, output, _ = lmnn(letter_recognition; k=5, range=10, - regularization=0.4) +julia> _, output, _ = lmnn(letter_recognition; k=5, + regularization=0.4, update_interval=10) ``` ### See also diff --git a/doc/user/bindings/python.md b/doc/user/bindings/python.md index a0a777fc72..8399b13531 100644 --- a/doc/user/bindings/python.md +++ b/doc/user/bindings/python.md @@ -1715,8 +1715,8 @@ Then, to use that model to predict classes for the dataset '`'test'`', storing t copy_all_inputs=False, distance=np.empty([0, 0]), input_=np.empty([0, 0]), k=1, labels=np.empty([0], dtype=np.uint64), linear_scan=False, max_iterations=100000, normalize=False, optimizer='amsgrad', passes=50, - print_accuracy=False, range=1, rank=0, regularization=0.5, seed=0, - step_size=0.01, tolerance=1e-07, verbose=False) + print_accuracy=False, rank=0, regularization=0.5, seed=0, + step_size=0.01, tolerance=1e-07, update_interval=1, verbose=False) >>> centered_data = d['centered_data'] >>> output = d['output'] >>> transformed_data = d['transformed_data'] @@ -1744,12 +1744,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning | `optimizer` | [`str`](#doc_str) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `'amsgrad'` | | `passes` | [`int`](#doc_int) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` | | `print_accuracy` | [`bool`](#doc_bool) | Print accuracies on initial and transformed dataset | `False` | -| `range` | [`int`](#doc_int) | Number of iterations after which impostors needs to be recalculated | `1` | | `rank` | [`int`](#doc_int) | Rank of distance matrix to be optimized. | `0` | | `regularization` | [`float`](#doc_float) | Regularization for LMNN objective function | `0.5` | | `seed` | [`int`](#doc_int) | Random seed. If 0, 'std::time(NULL)' is used. | `0` | | `step_size` | [`float`](#doc_float) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` | | `tolerance` | [`float`](#doc_float) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` | +| `update_interval` | [`int`](#doc_int) | Number of iterations after which impostors need to be recalculated. | `1` | | `verbose` | [`bool`](#doc_bool) | Display informational messages and the full list of parameters and timers at the end of execution. | `False` | ### Output options @@ -1769,7 +1769,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `input_`), or alternatively as a separate matrix (specified with `labels`). Additionally, a starting point for optimization (specified with `distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point). -The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `range`). +The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `update_interval`). Output can either be the learned distance matrix (specified with `output`), or the transformed dataset (specified with `transformed_data`), or both. Additionally mean-centered dataset (specified with `centered_data`) can be accessed given mean-centering (specified with `center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `print_accuracy`parameter. @@ -1793,10 +1793,10 @@ Example - Let's say we want to learn distance on iris dataset with number of tar >>> output = output['output'] ``` -An another program call making use of range & regularization parameter with dataset having labels as last column can be made as: +Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as: ```python ->>> output = lmnn(input_=letter_recognition, k=5, range=10, +>>> output = lmnn(input_=letter_recognition, k=5, update_interval=10, regularization=0.4) >>> output = output['output'] ``` diff --git a/doc/user/bindings/r.md b/doc/user/bindings/r.md index 2aa0037959..b6a242b521 100644 --- a/doc/user/bindings/r.md +++ b/doc/user/bindings/r.md @@ -1689,9 +1689,9 @@ R> library(mlpack) R> d <- lmnn(batch_size=50, center=FALSE, distance=matrix(numeric(), 0, 0), input=matrix(numeric(), 0, 0), k=1, labels=matrix(integer(), 0, 0), linear_scan=FALSE, max_iterations=100000, normalize=FALSE, - optimizer="amsgrad", passes=50, print_accuracy=FALSE, range=1, rank=0, + optimizer="amsgrad", passes=50, print_accuracy=FALSE, rank=0, regularization=0.5, seed=0, step_size=0.01, tolerance=1e-07, - verbose=getOption("mlpack.verbose", FALSE)) + update_interval=1, verbose=getOption("mlpack.verbose", FALSE)) R> centered_data <- d$centered_data R> output <- d$output R> transformed_data <- d$transformed_data @@ -1718,12 +1718,12 @@ An implementation of Large Margin Nearest Neighbors (LMNN), a distance learning | `optimizer` | [`character`](#doc_character) | Optimizer to use; 'amsgrad', 'bbsgd', 'sgd', or 'lbfgs'. | `"amsgrad"` | | `passes` | [`integer`](#doc_integer) | Maximum number of full passes over dataset for AMSGrad, BB_SGD and SGD. | `50` | | `print_accuracy` | [`logical`](#doc_logical) | Print accuracies on initial and transformed dataset | `FALSE` | -| `range` | [`integer`](#doc_integer) | Number of iterations after which impostors needs to be recalculated | `1` | | `rank` | [`integer`](#doc_integer) | Rank of distance matrix to be optimized. | `0` | | `regularization` | [`numeric`](#doc_numeric) | Regularization for LMNN objective function | `0.5` | | `seed` | [`integer`](#doc_integer) | Random seed. If 0, 'std::time(NULL)' is used. | `0` | | `step_size` | [`numeric`](#doc_numeric) | Step size for AMSGrad, BB_SGD and SGD (alpha). | `0.01` | | `tolerance` | [`numeric`](#doc_numeric) | Maximum tolerance for termination of AMSGrad, BB_SGD, SGD or L-BFGS. | `1e-07` | +| `update_interval` | [`integer`](#doc_integer) | Number of iterations after which impostors need to be recalculated. | `1` | | `verbose` | [`logical`](#doc_logical) | Display informational messages and the full list of parameters and timers at the end of execution. | `getOption("mlpack.verbose", FALSE)` | ### Output options @@ -1743,7 +1743,7 @@ This program implements Large Margin Nearest Neighbors, a distance learning tech To work, this algorithm needs labeled data. It can be given as the last row of the input dataset (specified with `input`), or alternatively as a separate matrix (specified with `labels`). Additionally, a starting point for optimization (specified with `distance`can be given, having (r x d) dimensionality. Here r should satisfy 1 <= r <= d, Consequently a Low-Rank matrix will be optimized. Alternatively, Low-Rank distance can be learned by specifying the `rank`parameter (A Low-Rank matrix with uniformly distributed values will be used as initial learning point). -The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `range`). +The program also requires number of targets neighbors to work with ( specified with `k`), A regularization parameter can also be passed, It acts as a trade of between the pulling and pushing terms (specified with `regularization`), In addition, this implementation of LMNN includes a parameter to decide the interval after which impostors must be re-calculated (specified with `update_interval`). Output can either be the learned distance matrix (specified with `output`), or the transformed dataset (specified with `transformed_data`), or both. Additionally mean-centered dataset (specified with `centered_data`) can be accessed given mean-centering (specified with `center`) is performed on the dataset. Accuracy on initial dataset and final transformed dataset can be printed by specifying the `print_accuracy`parameter. @@ -1767,10 +1767,10 @@ R> output <- lmnn(input=iris, labels=iris_labels, k=3, optimizer="bbsgd") R> output <- output$output ``` -An another program call making use of range & regularization parameter with dataset having labels as last column can be made as: +Another program call making use of update interval & regularization parameter with dataset having labels as last column can be made as: ```R -R> output <- lmnn(input=letter_recognition, k=5, range=10, +R> output <- lmnn(input=letter_recognition, k=5, update_interval=10, regularization=0.4) R> output <- output$output ``` diff --git a/doc/user/core.md b/doc/user/core.md index 0caaea7459..0062191185 100644 --- a/doc/user/core.md +++ b/doc/user/core.md @@ -862,9 +862,9 @@ including: * [`NeighborSearch`](/src/mlpack/methods/neighbor_search/neighbor_search.hpp) * [`RangeSearch`](/src/mlpack/methods/range_search/range_search.hpp) - * [`LMNN`](/src/mlpack/methods/lmnn/lmnn.hpp) + * [`LMNN`](methods/lmnn.md) * [`EMST`](/src/mlpack/methods/emst/emst.hpp) - * [`NCA`](/src/mlpack/methods/nca/nca.hpp) + * [`NCA`](methods/nca.md) * [`RANN`](/src/mlpack/methods/rann/rann.hpp) * [`KMeans`](/src/mlpack/methods/kmeans/kmeans.hpp) @@ -1281,6 +1281,11 @@ std::cout << "Squared Mahalanobis distance on 32-bit floating point data:" << std::endl; std::cout << " - Points 3 and 5: " << d1 << "." << std::endl; std::cout << " - Points 11 and 31: " << d2 << "." << std::endl; + +// Note that an equivalent transformation matrix can be recovered from Q with +// an upper Cholesky decomposition (Q -> R.t() * R). +arma::mat recoveredW = arma::chol(md.Q(), "lower"); +// A transformed dataset can be created with `(recoveredW * dataset)`. ``` --- @@ -1641,7 +1646,8 @@ where `bw` is the bandwidth parameter of the kernel. * `g.Gradient(distance)` - Compute the (one-dimensional) gradient of the kernel function with respect - to the distance between two points, evaluated at `distance`. + to the distance between two points, evaluated at `distance`. This is used + by [`MeanShift`](methods/mean_shift.md). * `g.Normalizer(dimensionality)` - Return the @@ -1876,7 +1882,8 @@ distance between `x1` and `x2`) is greater than or equal to `bw`. * `e.Gradient(distance)` - Compute the (one-dimensional) gradient of the kernel function with respect - to the distance between two points, evaluated at `distance`. + to the distance between two points, evaluated at `distance`. This is used + by [`MeanShift`](methods/mean_shift.md). * `e.Normalizer(dimensionality)` - Return the @@ -1933,7 +1940,7 @@ std::cout << "Kernel values between two floating-point vectors: " << k5 ### `HyperbolicTangentKernel` The `HyperbolicTangentKernel` implements the -[hyperbolic tangent kernel](https://en.wikipedia.org/wiki/Support_vector_machine#Nonlinear_Kernels), +[hyperbolic tangent kernel](https://en.wikipedia.org/wiki/Support_vector_machine#Nonlinear_kernels), which is defined by the following equation: `f(x1, x2) = tanh(s * (x1^T x2) + t)` where `s` is the scale parameter and `t` is the offset parameter. @@ -2046,7 +2053,8 @@ where `bw` is the bandwidth parameter. * `l.Gradient(distance)` - Compute the (one-dimensional) gradient of the kernel function with respect - to the distance between two points, evaluated at `distance`. + to the distance between two points, evaluated at `distance`. This is used + by [`MeanShift`](methods/mean_shift.md). --- @@ -2365,7 +2373,8 @@ as the uniform kernel, or rectangular window kernel. The value of the * `s.Gradient(distance)` - Compute the (one-dimensional) gradient of the kernel function with respect - to the distance between two points, evaluated at `distance`. + to the distance between two points, evaluated at `distance`. This is used + by [`MeanShift`](methods/mean_shift.md). * `s.Normalizer(dimensionality)` - Return the @@ -2459,7 +2468,8 @@ where `bw` is the bandwidth of the kernel. * `t.Gradient(distance)` - Compute the (one-dimensional) gradient of the kernel function with respect - to the distance between two points, evaluated at `distance`. + to the distance between two points, evaluated at `distance`. This is used + by [`MeanShift`](methods/mean_shift.md). --- diff --git a/doc/user/methods/decision_tree.md b/doc/user/methods/decision_tree.md index f4b4253b9e..cec361d871 100644 --- a/doc/user/methods/decision_tree.md +++ b/doc/user/methods/decision_tree.md @@ -458,11 +458,9 @@ class CustomNumericSplit categorical feature. * The `AllCategoricalSplit` _(default)_ and `BestBinaryCategoricalSplit` are~ available for drop-in usage. - * `AllCategoricalSplit`, the default ID3 split algorithm, splits all categories into their own node. This variant is simple, and has complexity `O(n)`, where `n` is the number of samples. - * `BestBinaryCategoricalSplit` is the preferred algorithm of [the CART system](https://www.taylorfrancis.com/books/mono/10.1201/9781315139470/classification-regression-trees-leo-breiman-jerome-friedman-olshen-charles-stone). It will find the the best (entropy-minimizing) binary partition of the @@ -471,6 +469,9 @@ class CustomNumericSplit more than two _classes_.~ - ***Note***: `BestBinaryCategoricalSplit` should not be chosen when there are multiple classes and many categories. + * A custom class must take a [`FitnessFunction`](#fitnessfunction) as a + template parameter, implement three functions, and have an internal + structure `AuxiliarySplitInfo` that is used at classification time: ```c++ template diff --git a/doc/user/methods/decision_tree_regressor.md b/doc/user/methods/decision_tree_regressor.md index f484b30ce1..b465a5f4f6 100644 --- a/doc/user/methods/decision_tree_regressor.md +++ b/doc/user/methods/decision_tree_regressor.md @@ -462,11 +462,9 @@ class CustomNumericSplit categorical feature. * The `AllCategoricalSplit` _(default)_ and `BestBinaryCategoricalSplit` are~ available for drop-in usage. - * `AllCategoricalSplit`, the default ID3 split algorithm, splits all categories into their own node. This variant is simple, and has complexity `O(n)`, where `n` is the number of samples. - * `BestBinaryCategoricalSplit` is the preferred algorithm of [the CART system](https://www.taylorfrancis.com/books/mono/10.1201/9781315139470/classification-regression-trees-leo-breiman-jerome-friedman-olshen-charles-stone). It will find the the best (entropy-minimizing) binary partition of the @@ -476,10 +474,9 @@ class CustomNumericSplit - ***Note***: `BestBinaryCategoricalSplit` should not be chosen when there are multiple classes and many categories. - ***Note***: for regression tasks, - [W. Fisher's proof of correctness](http://www.csiss.org/SPACE/workshops/2004/SAC/files/fisher.pdf) + [W. Fisher's proof of correctness](https://www.mlpack.org/papers/fisher.pdf) only applies to when `FitnessFunction` is `MSEGain`; therefore, `BestBinaryCategoricalSplit` requires the use of `MSEGain`. - * A custom class must take a [`FitnessFunction`](#fitnessfunction) as a template parameter, implement three functions, and have an internal structure `AuxiliarySplitInfo` that is used at classification time: diff --git a/doc/user/methods/lmnn.md b/doc/user/methods/lmnn.md new file mode 100644 index 0000000000..322a5f683e --- /dev/null +++ b/doc/user/methods/lmnn.md @@ -0,0 +1,454 @@ +## LMNN + +The `LMNN` class implements large margin nearest neighbor, which can be used +as both a linear dimensionality reduction technique and a distance learning +technique (also called metric learning). LMNN finds a linear transformation of +the dataset that improves `k`-nearest-neighbor classification performance. + +#### Simple usage example: + +```c++ +// Learn a distance metric that improves kNN classification performance. + +// All data and labels are uniform random; 10 dimensional data, 5 classes. +// Replace with a data::Load() call or similar for a real application. +arma::mat dataset(10, 1000, arma::fill::randu); // 1000 points. +arma::Row labels = + arma::randi>(1000, arma::distr_param(0, 4)); + +mlpack::LMNN lmnn(3 /* neighbors to consider */); // Step 1: create object. +arma::mat distance; +lmnn.LearnDistance(dataset, labels, distance); // Step 2: learn distance. + +// `distance` can now be used as a transformation matrix for the data. +arma::mat transformedData = distance * dataset; +// Or, you can create a MahalanobisDistance to evaluate points in the +// transformed dataset space. +arma::mat q = distance.t() * distance; +mlpack::MahalanobisDistance d(std::move(q)); + +std::cout << "Distance between points 0 and 1:" << std::endl; +std::cout << " - Before LMNN: " + << mlpack::EuclideanDistance::Evaluate(dataset.col(0), dataset.col(1)) + << "." << std::endl; +std::cout << " - After LMNN: " + << d.Evaluate(dataset.col(0), dataset.col(1)) << "." << std::endl; +``` +

    More examples...

    + +#### Quick links: + + * [Constructors](#constructors): create `LMNN` objects. + * [`LearnDistance()`](#learning-distances): learn distance metrics. + * [Other functionality](#other-functionality) for loading and saving. + * [Examples](#simple-examples) of simple usage and integration with other + techniques. + +#### See also: + + + + * [mlpack distance metrics](../core.md#distances) + * [`NCA`](nca.md) + * [Metric learning on Wikipedia](https://en.wikipedia.org/wiki/Similarity_learning#Metric_learning) + * [Large margin nearest neighbor on Wikipedia](https://en.wikipedia.org/wiki/Large_margin_nearest_neighbor) + * [Distance metric learning for Large Margin Nearest Neighbor Classification (pdf)](https://proceedings.neurips.cc/paper_files/paper/2005/file/a7f592cef8b130a6967a90617db5681b-Paper.pdf) + +### Constructors + + * `lmnn = LMNN(k, regularization=0.5, updateInterval=1)` + - Create an `LMNN` object considering the specified number `k` of neighbors. + - Optionally, specify the regularization to be applied to the LMNN cost + function (a `double`), and the number of iterations between recomputation + of neighbors (`updateInterval`, a `size_t`). + +--- + + * `lmnn = LMNN(k, regularization=0.5, updateInterval=1)` + * `lmnn = LMNN(k, regularization, updateInterval, distance)` + - Create an `LMNN` object using a custom + [`DistanceType`](../core.md#distances). + - `k` specifies the number of neighbors to consider. + - `regularization` specifies the regularization penalty to be applied to the + LMNN cost function (a `double`). + - `updateInterval` specifies the number of iterations between recomputation + of neighbors (a `size_t`). + - An instantiated `DistanceType` can optionally be passed with the `distance` + parameter. + - Using a custom `DistanceType` means that `LearnDistance()` will learn a + linear transformation for the data *in the metric space of the custom + `DistanceType`*. + * This means any learned distance may not necessarily improve + classification performance with the + [Euclidean distance](../core.md#lmetric). + * Instead, classification performance will be improved when the learned + distance is used with the given `DistanceType` only. + - Any mlpack `DistanceType` can be used as a drop-in replacement, or a + [custom `DistanceType`](../../developer/distances.md). + * A list of mlpack's provided distance metrics can be found + [here](../core.md#distances). + - ***Note: be sure that you understand the implications of a custom + `DistanceType` before using this version.*** + +--- + +***Notes***: + + - A larger `k` will cause `LearnDistance()` to take longer to compute, but will + give more accurate results. It is generally suggested to keep `k` in roughly + the `3` to `5` range, depending on the dataset. Using `k = 1` can provide + fast convergence, but the learned distance metric may be of lower quality. + + - `regularization` controls the balance between encouraging small distances for + points of the same class and penalizing small distances for points of + different classes. When `regularization` is increased, small distances for + points of different classes are further penalized. + + - Setting `updateInterval` greater than `1` will allow the LMNN algorithm to + take multiple steps without the expensive recomputation of neighbors, but + this means that subsequent optimization steps may not be using the true + nearest neighbors. + * If using an SGD-like algorithm (i.e. an optimizer for a + [differentiable separable function](https://www.ensmallen.org/docs.html#differentiable-separable-functions)), + this can often be set to a relatively high value (100 is not unreasonable). + * If using an optimizer like L-BFGS (i.e. a full-batch optimizer for + [differentiable functions](https://www.ensmallen.org/docs.html#differentiable-functions)), + this should be kept relatively low (going above 10 is not advised). + * It is worth cross-validating different values of the parameter to see what + works for your dataset. + +--- + +### Learning Distances + +Once an `LMNN` object has been created, the `LearnDistance()` method can be used +to learn a distance. + + * `lmnn.LearnDistance(data, labels, distance, [callbacks...])` + * `lmnn.LearnDistance(data, labels, distance, optimizer, [callbacks...])` + - Learn a distance metric on the given `data` and `labels`, filling + `distance` with a transformation matrix that can be used to map the data + into the space of the learned distance. + - Optionally, pass an instantiated + [ensmallen optimizer](https://www.ensmallen.org) and/or + [ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation) + to be used for the learning process. + - If no optimizer is passed, + [`ens::AMSGrad`](https://www.ensmallen.org/docs.html#amsgrad) is used. + - If `distance` already has size `r` x `data.n_rows` for some `r` less than + or equal to `data.n_rows`, it will be used as the starting point for + optimization. Otherwise, the identity matrix with size `data.n_rows` x + `data.n_rows` will be used. + - When optimization is complete, `distance` will have size `r` x + `data.n_rows`, where `r` is less than or equal to `data.n_rows`. + * *Note*: If `r < data.n_rows`, then LMNN has learned a distance metric + that also reduces the dimensionality of the data. See the + [last example](#simple-examples). + +To use `distance`, either: + + * Compute a new transformed dataset as `distance * data`, or + * Use an instantiated [`MahalanobisDistance`](../core.md#mahalanobisdistance) + with `distance.t() * distance` as the `Q` matrix. + +See the [examples section](#simple-examples) for more details. + +#### `LearnDistance()` Parameters: + +| **name** | **type** | **description** | +|----------|----------|-----------------| +| `data` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) training matrix. | +| `labels` | [`arma::Row`](../matrices.md) | Training labels, [between `0` and `numClasses - 1`](../load_save.md#normalizing-labels) (inclusive). Should have length `data.n_cols`. | +| `distance` | [`arma::mat`](../matrices.md) | Output matrix to store transformation matrix representing learned distance. | +| `optimizer` | [any ensmallen optimizer](https://www.ensmallen.org) | Instantiated ensmallen optimizer for [differentiable functions](https://www.ensmallen.org/docs.html#differentiable-functions) or [differentiable separable functions](https://www.ensmallen.org/docs.html#differentiable-separable-functions). | `ens::AMSGrad()` | +| `callbacks...` | [any set of ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation) | Optional callbacks for the ensmallen optimizer, such as e.g. `ens::ProgressBar()`, `ens::Report()`, or others. | _(N/A)_ | + +***Note***: any matrix type can be used for `data` and `distance`, so long as +that type implements the Armadillo API. So, e.g., `arma::fmat` can be used. + +### Other Functionality + + * An `LMNN` object can be serialized with + [`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects). + Note that this is only meaningful if a custom `DistanceType` is being used, + and that custom `DistanceType` has state to be saved. + + * `lmnn.K()` returns the number of neighbors used by LMNN, and `lmnn.K() = k` + will set the number of neighbors to use to `k`. + + * `lmnn.Regularization()` returns the current regularization value of the LMNN + object (as a `double`), and `lmnn.Regularization() = r` can be used to set + the regularization value to `r`. + + * `lmnn.UpdateInterval()` returns the current number of iterations between + neighbor recomputation (as a `size_t`), and `lmnn.UpdateInterval() = i` sets + the number of iterations between neighbor recomputation to `i`. + + * `lmnn.Distance()` will return the `DistanceType` being used for learning. + Unless a custom `DistanceType` was specified in the constructor, + this simply returns a [`SquaredEuclideanDistance`](../core.md#lmetric) + object. + +### Simple Examples + +Learn a distance metric to improve classification performance on the iris +dataset, and show improved performance when using +[`NaiveBayesClassifier`](naive_bayes_classifier.md). + +```c++ +// See https://datasets.mlpack.org/satellite.test.csv. +// (We are using the test set here just because it is a little smaller and +// we want this example to run quickly.) +arma::mat dataset; +mlpack::data::Load("satellite.test.csv", dataset, true); +// See https://datasets.mlpack.org/satellite.test.labels.csv. +arma::Row labels; +mlpack::data::Load("satellite.test.labels.csv", labels, true); + +// Create an LMNN object using 5 nearest neighbors and learn a distance. +arma::mat distance; +mlpack::LMNN lmnn(5); +lmnn.LearnDistance(dataset, labels, distance); + +// The distance matrix has size equal to the dimensionality of the data. +std::cout << "Learned distance size: " << distance.n_rows << " x " + << distance.n_cols << "." << std::endl; + +// Learn a NaiveBayesClassifier model on the data and print the performance. +mlpack::NaiveBayesClassifier nbc1(dataset, labels, 2); +arma::Row predictions; +nbc1.Classify(dataset, predictions); +std::cout << "Naive Bayes Classifier without LMNN: " + << arma::accu(labels == predictions) << " of " << labels.n_elem + << " correct." << std::endl; + +// Now transform the data and learn another NaiveBayesClassifier. +arma::mat transformedDataset = distance * dataset; +mlpack::NaiveBayesClassifier nbc2(transformedDataset, labels, 2); +nbc2.Classify(transformedDataset, predictions); +std::cout << "Naive Bayes Classifier with LMNN: " + << arma::accu(labels == predictions) << " of " << labels.n_elem + << " correct." << std::endl; +``` + +--- + +Learn a distance metric on the vehicle dataset, using 32-bit floating point to +represent the data and metric. + +```c++ +// See https://datasets.mlpack.org/vehicle.csv. +arma::fmat dataset; +mlpack::data::Load("vehicle.csv", dataset, true); + +// The labels are contained as the last row of the dataset. +arma::Row labels = + arma::conv_to>::from(dataset.row(dataset.n_rows - 1)); +dataset.shed_row(dataset.n_rows - 1); + +// Create an LMNN object with k=1 and learn distance on float32 data. +// Set updateInterval to a large value (100) because we are using the default +// AMSGrad optimizer (which will take very many small steps). +arma::fmat distance; +mlpack::LMNN lmnn(1, 0.5, 100); + +lmnn.LearnDistance(dataset, labels, distance, ens::ProgressBar()); + +// We want to compute six quantities: +// +// - Average distance to points of the same class before LMNN. +// - Average distance to points of the same class after LMNN, using +// MahalanobisDistance. +// - Average distance to points of the same class after LMNN, using the +// transformed dataset. +// +// - The same three quantities above, but for points of the other class. +// +// LMNN should reduce the average distance to points in the same class, while +// increasing the average distance to points in other classes. +float distSums[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; +size_t sameCount = 0; +arma::fmat q = distance.t() * distance; +mlpack::MahalanobisDistance md(std::move(q)); +arma::fmat transformedDataset = distance * dataset; +for (size_t i = 1; i < dataset.n_cols; ++i) +{ + const double d1 = mlpack::EuclideanDistance::Evaluate( + dataset.col(0), dataset.col(i)); + const double d2 = md.Evaluate(dataset.col(0), dataset.col(i)); + const double d3 = mlpack::EuclideanDistance::Evaluate( + transformedDataset.col(0), transformedDataset.col(i)); + + // Determine whether the point has the same label as point 0. + if (labels[i] == labels[0]) + { + distSums[0] += d1; + distSums[1] += d2; + distSums[2] += d3; + ++sameCount; + } + else + { + distSums[3] += d1; + distSums[4] += d2; + distSums[5] += d3; + } +} + +// Turn the results into average distances across the class. +distSums[0] /= sameCount; +distSums[1] /= sameCount; +distSums[2] /= sameCount; +distSums[3] /= (dataset.n_cols - sameCount); +distSums[4] /= (dataset.n_cols - sameCount); +distSums[5] /= (dataset.n_cols - sameCount); + +// Print the results. +std::cout << "Average distance between point 0 and other points of the same " + << "class:" << std::endl; +std::cout << " - Before LMNN: " << distSums[0] << "." + << std::endl; +std::cout << " - After LMNN (with MahalanobisDistance): " << distSums[1] << "." + << std::endl; +std::cout << " - After LMNN (with transformed dataset): " << distSums[2] << "." + << std::endl; +std::cout << std::endl; + +std::cout << "Average distance between point 0 and points of other classes: " + << std::endl; +std::cout << " - Before LMNN: " << distSums[3] << "." + << std::endl; +std::cout << " - After LMNN (with MahalanobisDistance): " << distSums[4] << "." + << std::endl; +std::cout << " - After LMNN (with transformed dataset): " << distSums[5] << "." + << std::endl; +std::cout << std::endl; + +std::cout << "Ratio of other-class to same-class distances:" << std::endl; +std::cout << "(We expect this to go up.)" << std::endl; +std::cout << " - Before LMNN: " << (distSums[3] / distSums[0]) << "." + << std::endl; +std::cout << " - After LMNN: " << (distSums[5] / distSums[2]) << "." + << std::endl; +``` + +--- + +Learn a distance metric on the iris dataset, using the L-BFGS optimizer with +callbacks. + +```c++ +// See https://datasets.mlpack.org/iris.csv. +arma::mat dataset; +mlpack::data::Load("iris.csv", dataset, true); +// See https://datasets.mlpack.org/iris.labels.csv. +arma::Row labels; +mlpack::data::Load("iris.labels.csv", labels, true); + +// Learn a distance with ensmallen's L-BFGS optimizer. +ens::L_BFGS lbfgs; +lbfgs.NumBasis() = 5; +lbfgs.MaxIterations() = 1000; + +// Use 5 neighbors for LMNN, and leave updateInterval at the default of 1, +// because we are using L-BFGS (a full-back optimizer). +mlpack::LMNN lmnn(5); + +// Use a callback that prints a final optimization report. +arma::mat distance; +lmnn.LearnDistance(dataset, labels, distance, lbfgs, ens::Report()); +``` + +--- + +Learn a distance metric on the vehicle dataset, but instead of using the +Euclidean distance as the underlying metric, use the Manhattan distance. This +means that LMNN is optimizing k-NN performance under the Manhattan distance, not +under the Euclidean distance. + +```c++ +// See https://datasets.mlpack.org/vehicle.csv. +arma::mat dataset; +mlpack::data::Load("vehicle.csv", dataset, true); + +// The labels are contained as the last row of the dataset. +arma::Row labels = + arma::conv_to>::from(dataset.row(dataset.n_rows - 1)); +dataset.shed_row(dataset.n_rows - 1); + +// Create the LMNN object and optimize. Use k=3 and Nesterov momentum SGD, +// printing a progress bar during optimization. Because Nesterov momentum SGD +// is an ensmallen optimizer for differentiable separable functions, we increase +// updateInterval to reduce the number of neighbor recomputations. We also set +// the regularization parameter to 1.0 to increase the penalty for nearby +// neighbors of a different class. +mlpack::LMNN lmnn(3, 1.0, 100); +arma::mat distance; +ens::NesterovMomentumSGD opt(0.000001 /* step size */, + 32 /* batch size */, + 20 * dataset.n_cols /* 20 epochs */); +lmnn.LearnDistance(dataset, labels, distance, opt, ens::ProgressBar()); + +// Now inspect distances between points with the Euclidean distance and with the +// inner product distance. +arma::mat transformedDataset = distance * dataset; + +// Points 0 and 1 have the same label (0). See their original distance---with +// both the Euclidean and Manhattan distances---and their transformed distances. +// We expect these points to get closer together, in the Manhattan distance. +const double d1 = mlpack::ManhattanDistance::Evaluate( + dataset.col(0), dataset.col(1)); +const double d2 = mlpack::ManhattanDistance::Evaluate( + transformedDataset.col(0), transformedDataset.col(1)); + +std::cout << "Distance between points 0 and 1 (same class):" << std::endl; +std::cout << " - Manhattan distance:" << std::endl; +std::cout << " * Before LMNN: " << d1 << std::endl; +std::cout << " * After LMNN: " << d2 << std::endl; +std::cout << std::endl; + +// Point 3 has a different label. We therefore expect this point to get further +// from point 0 with the Manhattan distance, but not necessarily with the +// Euclidean distance. +const double d3 = mlpack::ManhattanDistance::Evaluate( + dataset.col(0), dataset.col(3)); +const double d4 = mlpack::ManhattanDistance::Evaluate( + transformedDataset.col(0), transformedDataset.col(3)); + +std::cout << "Distance between points 0 and 3 (different class):" << std::endl; +std::cout << " - Manhattan distance:" << std::endl; +std::cout << " * Before LMNN: " << d3 << std::endl; +std::cout << " * After LMNN: " << d4 << std::endl; + +// Note that point 3 has been moved further away from point 0 than point 1. +``` + +--- + +Learn a distance metric while also performing dimensionality reduction, reducing +the dimensionality of the satellite dataset by 3 dimensions. + +```c++ +// See https://datasets.mlpack.org/satellite.train.csv. +arma::mat dataset; +mlpack::data::Load("satellite.train.csv", dataset, true); +// See https://datasets.mlpack.org/satellite.labels.csv. +arma::Row labels; +mlpack::data::Load("satellite.train.labels.csv", labels, true); + +// Use a random initialization for the distance transformation, with the +// specified output dimensionality. +arma::mat distance(dataset.n_rows - 3, dataset.n_rows, arma::fill::randu); +mlpack::LMNN lmnn(3); +ens::L_BFGS opt; +opt.MaxIterations() = 10; // You may want more in a real application. +lmnn.LearnDistance(dataset, labels, distance, opt, ens::Report()); + +// Now transform the dataset. +arma::mat transformedData = distance * dataset; + +std::cout << "Original data has size " << dataset.n_rows << " x " + << dataset.n_cols << "." << std::endl; +std::cout << "Transformed data has size " << transformedData.n_rows << " x " + << transformedData.n_cols << "." << std::endl; +``` diff --git a/doc/user/methods/mean_shift.md b/doc/user/methods/mean_shift.md new file mode 100644 index 0000000000..a885e45e07 --- /dev/null +++ b/doc/user/methods/mean_shift.md @@ -0,0 +1,341 @@ +## `MeanShift` + +The `MeanShift` class implements mean shift, a clustering technique. Mean shift +models the density of the data using a kernel function (also called Parzen +window), producing a number of clusters that represent the data density. Mean +shift does not require the user to guess the number of clusters, and does not +make any assumptions on the shape of the data. + +mlpack's `MeanShift` class allows control of the kernel function used via +template parameters. + +#### Simple usage example: + +```c++ +// Use mean shift to cluster random data and print the number of points that +// fall into each cluster. + +// Create random dataset with two separated 10-dimensional Gaussians. +arma::mat dataset = arma::join_rows( + arma::randn(10, 1000) + 3.0, // 1000 points from N(-3, 1). + arma::randn(10, 1000) - 3.0); // 1000 points from N( 3, 1). + +mlpack::MeanShift ms; // Step 1: create object. +arma::Row assignments; +arma::mat centroids; +ms.Cluster(dataset, assignments, centroids); // Step 2: perform clustering. + +// Print the number of clusters. +std::cout << "Found " << centroids.n_cols << " centroids." << std::endl; + +// Print the number of points in each cluster. +for (size_t c = 0; c < centroids.n_cols; ++c) +{ + std::cout << " * Cluster " << c << " has " << arma::accu(assignments == c) + << " points." << std::endl; +} +``` +

    More examples...

    + +#### Quick links: + + * [Constructors](#constructors): create `MeanShift` objects. + * [`Cluster()`](#clustering): perform clustering. + * [Other functionality](#other-functionality) for loading, saving, inspecting, + and estimating the radius to use. + * [Examples](#simple-examples) of simple usage and links to detailed example + projects. + * [Template parameters](#advanced-functionality-template-parameters) for custom + behavior. + +#### See also: + + * [mlpack clustering algorithms](../../index.md#clustering-algorithms) + * [mlpack kernels](../core.md#kernels) + * [Mean shift on Wikipedia](https://en.wikipedia.org/wiki/Mean_shift) + * [Mean Shift, Mode Seeking, and Clustering (pdf)](http://users.isr.ist.utl.pt/~alex/Resources/meanshift.pdf) + +### Constructors + + * `ms = MeanShift(radius=0, maxIterations=1000)` + - Create a `MeanShift` object that will use the + default [`GaussianKernel`](../core.md#gaussiankernel) to weight points for + cluster centroid recalculations. + +--- + + * `ms = MeanShift(radius=0, maxIterations=1000)` + - Create a `MeanShift` object that will not weight points differently when + recalculating cluster centroids. + - Centroid recalculation will use all points within a distance of `radius` + from the current cluster centroid, uniformly weighted. + +--- + + * `ms = MeanShift(radius, maxIterations, kernel)` + - Create a `MeanShift` object that will use the given `kernel` object (a + `GaussianKernel`) for weighting points during cluster centroid + recalculations. + +--- + + * `ms = MeanShift(radius, maxIterations, kernel=KernelType())` + - Create a `MeanShift` object that will use the given + [`KernelType`](../core.md#kernels) for weighting points during cluster + centroid recalculations. + - [mlpack kernels](../core.md#kernels) or custom kernel classes implementing + a [`Gradient()` function](#advanced-functionality-template-parameters) can + be used for the `KernelType` template parameter. + - If `kernel` is not specified, a default-constructed `KernelType` will be + used. + - A list of usable `KernelType`s supplied with mlpack can be found in the + [advanced functionality section](#advanced-functionality-template-parameters). + +--- + +#### Constructor Parameters: + +| **name** | **type** | **description** | **default** | +|----------|----------|-----------------|-------------| +| `radius` | `double` | Radius around each centroid for weighting during centroid recomputation. Larger means higher weights for faraway points. Values less than or equal to 0 mean that the radius will be estimated from data. | `0.0` | +| `maxIterations` | `size_t` | Maximum number of iterations of the mean shift algorithm to run. | `1000` | +| `kernel` | [`KernelType`](#advanced-functionality-template-parameters) | Instantiated kernel object to use for density calculations. | [`GaussianKernel()`](../core.md#gaussiankernel) | + +***Notes:*** + + - A larger `radius` value will generally result in fewer clusters (e.g. a + coarser clustering); smaller `radius` values will generally result in more + clusters. + + - When `MeanShift` is used, `radius` is the hard distance threshold for + points to be considered in the recomputation of a centroid. + +### Clustering + + * `ms.Cluster(data, centroids, forceConvergence=true, useSeeds=true)` + - Cluster the given data, storing the resulting cluster centroids in + `centroids`. + - `centroids` will be set to size `data.n_rows` x `numClusters`, where + `numClusters` is the number of clusters found by the mean shift algorithm. + - The `i`th cluster centroid can be obtained with `clusters.col(i)`. + +--- + + * `ms.Cluster(data, assignments, centroids, forceConvergence=true, useSeeds=true)` + - Cluster the given data, storing the resulting cluster centroids in + `centroids` and cluster assignments for each data point in `assignments`. + - `centroids` will be set to size `data.n_rows` x `numClusters`, where + `numClusters` is the number of clusters found by the mean shift algorithm. + - `assignments` will be set to length `data.n_cols`; the assignment of the + `i`th point can be obtained with `assignments[i]`. + - The cluster centroid of the `i`th point's cluster can be obtained with + `centroids.col(assignments[i])`. + +--- + +#### Clustering Parameters: + +| **name** | **type** | **description** | **default** | +|----------|----------|-----------------|-------------| +| `data` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) matrix holding the dataset to be clustered. | _(N/A)_ | +| `centroids` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) matrix that centroids will be stored into. | _(N/A)_ | +| `assignments` | [`arma::Row`](../matrices.md) | Vector to store cluster assignments for each point into. | _(N/A)_ | +| `forceConvergence` | `bool` | If `true`, forces convergence of every cluster, ignoring `maxIterations`. | `false` | +| `useSeeds` | `bool` | If `true`, estimates of high-density regions in the dataset will be used as initial centroids, instead of the full dataset. | `true` + +***Notes***: + + * It is recommended to leave `useSeeds` to its default value of `true`. When + `useSeeds` is set to `false`, the entire dataset is used as the initial set + of centroids. For large datasets, this can be slow! + + * Different types can be used for `data` and `centroids` (e.g., `arma::fmat` or + any dense matrix type implementing the Armadillo API). The types of `data` + and `centroids` must be the same. + +### Other Functionality + + * A `MeanShift` object can be serialized with + [`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects). + + * `EstimateRadius(data, ratio=0.2)` returns a `double` that estimates a good + value to use for the radius parameter. `ratio` (between 0 and 1) controls + the percentage of the dataset used for the estimate. + - This function is called internally by `Cluster()` at the start of + clustering to choose a radius, if `radius` is less than or equal to 0. + + * As an alternative to constructor parameters, the radius can be set with + `ms.Radius(newRadius)`, and the maximum number of iterations can be set with + `ms.MaxIterations() = newMaxIter`. + + * `ms.Radius()` returns the current radius for clustering. + `ms.Radius(r)` sets the radius to `r`. + + * `ms.MaxIterations()` returns the current maximum number of iterations for + clustering. `ms.MaxIterations() = m` sets the maximum number of iterations + to `m`. + +### Simple Examples + +Perform mean shift clustering on the satellite dataset and print the average +distance from each point to its assigned centroid. + +```c++ +// See https://datasets.mlpack.org/satellite.train.csv. +arma::mat dataset; +mlpack::data::Load("satellite.train.csv", dataset, true); + +// Create MeanShift object with default parameters and perform clustering. +mlpack::MeanShift ms; +arma::mat centroids; +arma::Row assignments; +ms.Cluster(dataset, assignments, centroids); + +// Print the number of clusters. +std::cout << "MeanShift computed " << centroids.n_cols << " clusters." + << std::endl; + +// Compute the average distance from each point to its assigned centroid. +double sumDist = 0.0; +for (size_t i = 0; i < dataset.n_cols; ++i) +{ + sumDist += mlpack::EuclideanDistance::Evaluate( + dataset.col(i), centroids.col(assignments[i])); +} +const double avgDist = sumDist / (double) dataset.n_cols; + +std::cout << "Average distance from a point to its assigned centroid: " + << avgDist << "." << std::endl; +``` + +--- + +Perform mean shift clustering with custom settings of `radius` and +`maxIterations` on the wave energy farm dataset, using `EstimateRadius()` +to set the initial radius. + +```c++ +// See https://datasets.mlpack.org/wave_energy_farm_100.csv. +arma::mat dataset; +mlpack::data::Load("wave_energy_farm_100.csv", dataset, true); + +// Create MeanShift object and set parameters. +mlpack::MeanShift ms; +const double radiusEstimate = ms.EstimateRadius(dataset, 0.2); + +// Use 2x the estimate for a coarser clustering. +ms.Radius(2.0 * radiusEstimate); +// Use only 100 iterations. +ms.MaxIterations() = 100; + +// Perform the clustering. +arma::mat centroids; +ms.Cluster(dataset, centroids); + +std::cout << "MeanShift found " << centroids.n_cols << " clusters." + << std::endl; + +// Save the centroids to disk. +mlpack::data::Save("wave_energy_centroids.csv", centroids); +``` + +--- + +Perform mean shift clustering with no kernel (e.g. unit weighting of points in a +centroid) on the cloud dataset. + +```c++ +// See https://datasets.mlpack.org/cloud.csv. +arma::mat dataset; +mlpack::data::Load("cloud.csv", dataset, true); + +// Don't use a kernel for clustering. This means all points within the radius +// are weighted equally. Use a custom radius of 25. +mlpack::MeanShift ms(25.0, 100 /* max iterations */); + +arma::mat centroids; +arma::Row assignments; +ms.Cluster(dataset, assignments, centroids); + +// Print the number of clusters and the number of points in each cluster. +std::cout << "MeanShift found " << centroids.n_cols << " clusters." + << std::endl; +for (size_t i = 0; i < centroids.n_cols; ++i) +{ + std::cout << " - Cluster " << i << " has " << arma::accu(assignments == i) + << " points assigned to it." << std::endl; +} +``` + +--- + +Perform mean shift clustering with the triangular kernel on the cloud dataset, +using 32-bit floating point matrices to represent the data. + +```c++ +// See https://datasets.mlpack.org/cloud.csv. +arma::fmat dataset; +mlpack::data::Load("cloud.csv", dataset, true); + +// Create the MeanShift object using a TriangularKernel. +mlpack::TriangularKernel tk; +mlpack::MeanShift ms(50.0 /* radius */, + 1000 /* max iterations */, + tk); + +// Perform clustering. +arma::fmat centroids; +arma::Row assignments; +ms.Cluster(dataset, assignments, centroids); + +// Print the number of clusters and the number of points in each cluster. +std::cout << "MeanShift found " << centroids.n_cols << " clusters." + << std::endl; +for (size_t i = 0; i < centroids.n_cols; ++i) +{ + std::cout << " - Cluster " << i << " has " << arma::accu(assignments == i) + << " points assigned to it." << std::endl; +} +``` + +--- + +### Advanced Functionality: Template Parameters + +The `MeanShift` class has two template parameters that can be used for custom +behavior. The full signature of the class is: + +``` +MeanShift +``` + + * `UseKernel` (default `true`) is a `bool` parameter representing whether a + kernel function is used to weight points during centroid recomputation. If + it is `false`, then each point within distance `radius` of the centroid will + be used (without weighting) to recompute the centroid. This strategy (with + `UseKernel = false`) is also known as using a 'flat kernel'. + + * `KernelType` represents the kernel function (or Parzen window) to be used to + weight points during centroid recomputation. Although many + [mlpack kernels](../core.md#kernels) are available, only those with + `Gradient()` functions (described below) are supported. Available kernels + for drop-in usage include: + - [`GaussianKernel`](../core.md#gaussiankernel) *(default)* + - [`EpanechnikovKernel`](../core.md#epanechnikovkernel) + - [`LaplacianKernel`](../core.md#laplaciankernel) + - [`SphericalKernel`](../core.md#sphericalkernel) *(note: this is equivalent + to the flat kernel, or, setting `UseKernel = false`)* + - [`TriangularKernel`](../core.md#triangularkernel) + +Custom kernels for mean shift can be easily implemented, and must implement only +one function (`Gradient()`): + +```c++ +class CustomKernel +{ + // Evaluate the gradient of the kernel function given the distance between two + // points. Specifically, given that the kernel function is K(t) (where t is + // the distance between the two points), this function should return K'(t). + double Gradient(const double t); +}; +``` diff --git a/doc/user/methods/nca.md b/doc/user/methods/nca.md new file mode 100644 index 0000000000..4870907af8 --- /dev/null +++ b/doc/user/methods/nca.md @@ -0,0 +1,440 @@ +## NCA + +The `NCA` class implements neighborhood components analysis, which can be used +as both a linear dimensionality reduction technique and a distance learning +technique (also called metric learning). Neighborhood components analysis finds +a linear transformation of the dataset that improves `k`-nearest-neighbor +classification performance. + +Note that `NCA` is a computationally intensive technique (each optimization +iteration takes time quadratic in the data size!), and may be slow to run even +for datasets of only moderate size. See [`LMNN`](lmnn.md) for another distance +learning technique that scales better to larger datasets. + +#### Simple usage example: + +```c++ +// Learn a distance metric that improves kNN classification performance. + +// All data and labels are uniform random; 10 dimensional data, 5 classes. +// Replace with a data::Load() call or similar for a real application. +arma::mat dataset(10, 1000, arma::fill::randu); // 1000 points. +arma::Row labels = + arma::randi>(1000, arma::distr_param(0, 4)); + +mlpack::NCA nca; // Step 1: create object. +arma::mat distance; +nca.LearnDistance(dataset, labels, distance); // Step 2: learn distance. + +// `distance` can now be used as a transformation matrix for the data. +arma::mat transformedData = distance * dataset; +// Or, you can create a MahalanobisDistance to evaluate points in the +// transformed dataset space. +arma::mat q = distance.t() * distance; +mlpack::MahalanobisDistance d(std::move(q)); + +std::cout << "Distance between points 0 and 1:" << std::endl; +std::cout << " - Before NCA: " + << mlpack::EuclideanDistance::Evaluate(dataset.col(0), dataset.col(1)) + << "." << std::endl; +std::cout << " - After NCA: " + << d.Evaluate(dataset.col(0), dataset.col(1)) << "." << std::endl; +``` +

    More examples...

    + +#### Quick links: + + * [Constructors](#constructors): create `NCA` objects. + * [`LearnDistance()`](#learning-distances): learn distance metrics. + * [Other functionality](#other-functionality) for loading and saving. + * [Examples](#simple-examples) of simple usage and integration with other + techniques. + +#### See also: + + + + * [mlpack distance metrics](../core.md#distances) + * [`LMNN`](lmnn.md) + * [Metric learning on Wikipedia](https://en.wikipedia.org/wiki/Similarity_learning#Metric_learning) + * [Neighborhood Components Analysis on Wikipedia](https://en.wikipedia.org/wiki/Neighbourhood_components_analysis) + * [Neighbourhood Components Analysis (pdf)](https://proceedings.neurips.cc/paper_files/paper/2004/file/42fe880812925e520249e808937738d2-Paper.pdf) + +### Constructors + + * `nca = NCA()` + - Create an `NCA` object with default parameters. + +--- + + * `nca = NCA()` + * `nca = NCA(distance)` + - Create an `NCA` object using a custom + [`DistanceType`](../core.md#distances). + - An instantiated `DistanceType` can optionally be passed with the `distance` + parameter. + - Using a custom `DistanceType` means that `LearnDistance()` will learn a + linear transformation for the data *in the metric space of the custom + `DistanceType`*. + * This means any learned distance may not necessarily improve + classification performance with the + [Euclidean distance](../core.md#lmetric). + * Instead, classification performance will be improved when the learned + distance is used with the given `DistanceType` only. + - Any mlpack `DistanceType` can be used as a drop-in replacement, or a + [custom `DistanceType`](../../developer/distances.md). + * A list of mlpack's provided distance metrics can be found + [here](../core.md#distances). + - ***Note: be sure that you understand the implications of a custom + `DistanceType` before using this version.*** + +--- + +### Learning Distances + +Once an `NCA` object has been created, the `LearnDistance()` method can be used +to learn a distance. + + * `nca.LearnDistance(data, labels, distance, [callbacks...])` + * `nca.LearnDistance(data, labels, distance, optimizer, [callbacks...])` + - Learn a distance metric on the given `data` and `labels`, filling + `distance` with a transformation matrix that can be used to map the data + into the space of the learned distance. + - Optionally, pass an instantiated + [ensmallen optimizer](https://www.ensmallen.org) and/or + [ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation) + to be used for the learning process. + - If `distance` already has size `r` x `data.n_rows` for some `r` less than + or equal to `data.n_rows`, it will be used as the starting point for + optimization. Otherwise, the identity matrix with size `data.n_rows` x + `data.n_rows` will be used. + - When optimization is complete, `distance` will have size `r` x + `data.n_rows`, where `r` is less than or equal to `data.n_rows`. + * *Note*: If `r < data.n_rows`, then NCA has learned a distance metric that + also reduces the dimensionality of the data. See the + [last example](#simple-examples). + +To use `distance`, either: + + * Compute a new transformed dataset as `distance * data`, or + * Use an instantiated [`MahalanobisDistance`](../core.md#mahalanobisdistance) + with `distance.t() * distance` as the `Q` matrix. + +See the [examples section](#simple-examples) for more details. + +***Caveat:*** NCA operates by repeatedly computing expressions of the form +`exp(-distance.Evaluate(data.col(i), data.col(j)))` (that is, the exponential of +the negative distance between two points). When distances are very large, this +***quantity underflows to 0*** and results will not be reasonable. + + - This situation can be detected, usually by a result where `distance` is equal + to the identity matrix. + - Alternately, if the [`ens::ProgressBar()` + callback](https://www.ensmallen.org/docs.html#progressbar) is used, a loss of + 0 often means this situation has occurred. + - To mitigate the problem, consider scaling data such that the maximum pairwise + distance is less than 10. See the [simple examples](#simple-examples) that + use the `vehicle` dataset. + +#### `LearnDistance()` Parameters: + +| **name** | **type** | **description** | +|----------|----------|-----------------| +| `data` | [`arma::mat`](../matrices.md) | [Column-major](../matrices.md#representing-data-in-mlpack) training matrix. | +| `labels` | [`arma::Row`](../matrices.md) | Training labels, [between `0` and `numClasses - 1`](../load_save.md#normalizing-labels) (inclusive). Should have length `data.n_cols`. | +| `distance` | [`arma::mat`](../matrices.md) | Output matrix to store transformation matrix representing learned distance. | +| `optimizer` | [any ensmallen optimizer](https://www.ensmallen.org) | Instantiated ensmallen optimizer for [differentiable functions](https://www.ensmallen.org/docs.html#differentiable-functions) or [differentiable separable functions](https://www.ensmallen.org/docs.html#differentiable-separable-functions). | `ens::StandardSGD()` | +| `callbacks...` | [any set of ensmallen callbacks](https://www.ensmallen.org/docs.html#callback-documentation) | Optional callbacks for the ensmallen optimizer, such as e.g. `ens::ProgressBar()`, `ens::Report()`, or others. | _(N/A)_ | + +***Note***: any matrix type can be used for `data` and `distance`, so long as +that type implements the Armadillo API. So, e.g., `arma::fmat` can be used. + +### Other Functionality + + * An `NCA` object can be serialized with + [`data::Save()` and `data::Load()`](../load_save.md#mlpack-objects). + Note that this is only meaningful if a custom `DistanceType` is being used, + and that custom `DistanceType` has state to be saved. + + * `nca.Distance()` will return the `DistanceType` being used for learning. + Unless a custom `DistanceType` was specified in the constructor, + this simply returns a [`SquaredEuclideanDistance`](../core.md#lmetric) + object. + +### Simple Examples + +Learn a distance metric to improve classification performance on the iris +dataset, and show improved performance when using +[`NaiveBayesClassifier`](naive_bayes_classifier.md). + +```c++ +// See https://datasets.mlpack.org/iris.csv. +arma::mat dataset; +mlpack::data::Load("iris.csv", dataset, true); +// See https://datasets.mlpack.org/iris.labels.csv. +arma::Row labels; +mlpack::data::Load("iris.labels.csv", labels, true); + +// Create an NCA object and learn a distance. +arma::mat distance; +mlpack::NCA nca; +nca.LearnDistance(dataset, labels, distance); + +// The distance matrix has size equal to the dimensionality of the data. +std::cout << "Learned distance size: " << distance.n_rows << " x " + << distance.n_cols << "." << std::endl; + +// Learn a NaiveBayesClassifier model on the data and print the performance. +mlpack::NaiveBayesClassifier nbc1(dataset, labels, 3); +arma::Row predictions; +nbc1.Classify(dataset, predictions); +std::cout << "Naive Bayes Classifier without NCA: " + << arma::accu(labels == predictions) << " of " << labels.n_elem + << " correct." << std::endl; + +// Now transform the data and learn another NaiveBayesClassifier. +arma::mat transformedDataset = distance * dataset; +mlpack::NaiveBayesClassifier nbc2(transformedDataset, labels, 3); +nbc2.Classify(transformedDataset, predictions); +std::cout << "Naive Bayes Classifier with NCA: " + << arma::accu(labels == predictions) << " of " << labels.n_elem + << " correct." << std::endl; +``` + +--- + +Learn a distance metric on the ionosphere dataset, using 32-bit floating point +to represent the data and metric. + +```c++ +// See https://datasets.mlpack.org/ionosphere.csv. +arma::fmat dataset; +mlpack::data::Load("ionosphere.csv", dataset, true); + +// The labels are the last row of the dataset. +arma::Row labels = + arma::conv_to>::from(dataset.row(dataset.n_rows - 1)); +dataset.shed_row(dataset.n_rows - 1); + +// Create an NCA object and learn distance on float32 data. +// To keep computation time down, we use an instantiated optimizer that will +// only perform 10 epochs of training. (In a real application you may want to +// train for longer!) +arma::fmat distance; +mlpack::NCA nca; + +ens::StandardSGD opt; +opt.MaxIterations() = 10 * dataset.n_cols; +nca.LearnDistance(dataset, labels, distance, opt, ens::ProgressBar()); + +// We want to compute six quantities: +// +// - Average distance to points of the same class before NCA. +// - Average distance to points of the same class after NCA, using +// MahalanobisDistance. +// - Average distance to points of the same class after NCA, using the +// transformed dataset. +// +// - The same three quantities above, but for points of the other class. +// +// NCA should reduce the average distance to points in the same class, while +// increasing the average distance to points in other classes. +float distSums[6] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; +size_t sameCount = 0; +arma::fmat q = distance.t() * distance; +mlpack::MahalanobisDistance md(std::move(q)); +arma::fmat transformedDataset = distance * dataset; +for (size_t i = 1; i < dataset.n_cols; ++i) +{ + const double d1 = mlpack::EuclideanDistance::Evaluate( + dataset.col(0), dataset.col(i)); + const double d2 = md.Evaluate(dataset.col(0), dataset.col(i)); + const double d3 = mlpack::EuclideanDistance::Evaluate( + transformedDataset.col(0), transformedDataset.col(i)); + + // Determine whether the point has the same label as point 0. + if (labels[i] == labels[0]) + { + distSums[0] += d1; + distSums[1] += d2; + distSums[2] += d3; + ++sameCount; + } + else + { + distSums[3] += d1; + distSums[4] += d2; + distSums[5] += d3; + } +} + +// Turn the results into average distances across the class. +distSums[0] /= sameCount; +distSums[1] /= sameCount; +distSums[2] /= sameCount; +distSums[3] /= (dataset.n_cols - sameCount); +distSums[4] /= (dataset.n_cols - sameCount); +distSums[5] /= (dataset.n_cols - sameCount); + +// Print the results. +std::cout << "Average distance between point 0 and other points of the same " + << "class:" << std::endl; +std::cout << " - Before NCA: " << distSums[0] << "." + << std::endl; +std::cout << " - After NCA (with MahalanobisDistance): " << distSums[1] << "." + << std::endl; +std::cout << " - After NCA (with transformed dataset): " << distSums[2] << "." + << std::endl; +std::cout << std::endl; + +std::cout << "Average distance between point 0 and points of other classes: " + << std::endl; +std::cout << " - Before NCA: " << distSums[3] << "." + << std::endl; +std::cout << " - After NCA (with MahalanobisDistance): " << distSums[4] << "." + << std::endl; +std::cout << " - After NCA (with transformed dataset): " << distSums[5] << "." + << std::endl; +std::cout << std::endl; + +std::cout << "Ratio of other-class to same-class distances:" << std::endl; +std::cout << "(We expect this to go up.)" << std::endl; +std::cout << " - Before NCA: " << (distSums[3] / distSums[0]) << "." + << std::endl; +std::cout << " - After NCA: " << (distSums[5] / distSums[2]) << "." + << std::endl; +``` + +--- + +Learn a distance metric on the iris dataset, using the L-BFGS optimizer with +callbacks. + +```c++ +// See https://datasets.mlpack.org/iris.csv. +arma::mat dataset; +mlpack::data::Load("iris.csv", dataset, true); +// See https://datasets.mlpack.org/iris.labels.csv. +arma::Row labels; +mlpack::data::Load("iris.labels.csv", labels, true); + +// Learn a distance with ensmallen's L-BFGS optimizer. +ens::L_BFGS lbfgs; +lbfgs.NumBasis() = 5; +lbfgs.MaxIterations() = 1000; + +arma::mat distance; +mlpack::NCA nca; + +// Use a callback that prints a final optimization report. +nca.LearnDistance(dataset, labels, distance, lbfgs, ens::Report()); +``` + +--- + + + +Learn a distance metric on the vehicle dataset, but instead of using the +Euclidean distance as the underlying metric, use the Manhattan distance. This +means that NCA is optimizing k-NN performance under the Manhattan distance, not +under the Euclidean distance. + +```c++ +// See https://datasets.mlpack.org/vehicle.csv. +arma::mat dataset; +mlpack::data::Load("vehicle.csv", dataset, true); + +// The labels are contained as the last row of the dataset. +arma::Row labels = + arma::conv_to>::from(dataset.row(dataset.n_rows - 1)); +dataset.shed_row(dataset.n_rows - 1); + +// Because typical distances between points in the vehicle dataset are large, +// we will center the dataset and scale it to have points in the unit ball. +// (That is, all points will have values in each dimension between -1 and 1.) +// This means that the maximum pairwise distance is 2. +dataset.each_col() -= arma::mean(dataset, 1); +dataset /= arma::max(arma::max(arma::abs(dataset))); + +// Create the NCA object and optimize. Use Nesterov momentum SGD, printing a +// progress bar during optimization. +mlpack::NCA nca; +arma::mat distance; +ens::NesterovMomentumSGD opt(0.01 /* step size */, + 32 /* batch size */, + 20 * dataset.n_cols /* 20 epochs */); +nca.LearnDistance(dataset, labels, distance, opt, ens::ProgressBar()); + +// Now inspect distances between points with the Euclidean distance and with the +// inner product distance. +arma::mat transformedDataset = distance * dataset; + +// Points 0 and 1 have the same label (0). See their original distance---with +// both the Euclidean and Manhattan distances---and their transformed distances. +// We expect these points to get closer together, in the Manhattan distance. +const double d1 = mlpack::ManhattanDistance::Evaluate( + dataset.col(0), dataset.col(1)); +const double d2 = mlpack::ManhattanDistance::Evaluate( + transformedDataset.col(0), transformedDataset.col(1)); + +std::cout << "Distance between points 0 and 1 (same class):" << std::endl; +std::cout << " - Manhattan distance:" << std::endl; +std::cout << " * Before NCA: " << d1 << std::endl; +std::cout << " * After NCA: " << d2 << std::endl; +std::cout << std::endl; + +// Point 3 has a different label. We therefore expect this point to get further +// from point 0 with the Manhattan distance, but not necessarily with the +// Euclidean distance. +const double d3 = mlpack::ManhattanDistance::Evaluate( + dataset.col(0), dataset.col(3)); +const double d4 = mlpack::ManhattanDistance::Evaluate( + transformedDataset.col(0), transformedDataset.col(3)); + +std::cout << "Distance between points 0 and 3 (different class):" << std::endl; +std::cout << " - Manhattan distance:" << std::endl; +std::cout << " * Before NCA: " << d3 << std::endl; +std::cout << " * After NCA: " << d4 << std::endl; + +// Note that point 3 has been moved further away from point 0 than point 1. +``` + +--- + +Learn a distance metric while also performing dimensionality reduction, reducing +the dimensionality of the vehicle dataset by 2 dimensions. + +```c++ +// See https://datasets.mlpack.org/vehicle.csv. +arma::mat dataset; +mlpack::data::Load("vehicle.csv", dataset, true); + +// The labels are contained as the last row of the dataset. +arma::Row labels = + arma::conv_to>::from(dataset.row(dataset.n_rows - 1)); +dataset.shed_row(dataset.n_rows - 1); + +// Because typical distances between points in the vehicle dataset are large, +// we will center the dataset and scale it to have points in the unit ball. +// (That is, all points will have values in each dimension between -1 and 1.) +// This means that the maximum pairwise distance is 2. +dataset.each_col() -= arma::mean(dataset, 1); +dataset /= arma::max(arma::max(arma::abs(dataset))); + +// Use a random initialization for the distance transformation, with the +// specified output dimensionality. +arma::mat distance(dataset.n_rows - 2, dataset.n_rows, arma::fill::randu); +mlpack::NCA nca; +ens::L_BFGS opt; +opt.MaxIterations() = 10; // You may want more in a real application. +nca.LearnDistance(dataset, labels, distance, opt); + +// Now transform the dataset. +arma::mat transformedData = distance * dataset; + +std::cout << std::endl << std::endl; +std::cout << "Original data has size " << dataset.n_rows << " x " + << dataset.n_cols << "." << std::endl; +std::cout << "Transformed data has size " << transformedData.n_rows << " x " + << transformedData.n_cols << "." << std::endl; +``` diff --git a/scripts/build-docs.sh b/scripts/build-docs.sh index d3ddd010ea..0e1187ec56 100755 --- a/scripts/build-docs.sh +++ b/scripts/build-docs.sh @@ -422,6 +422,7 @@ then --suppress-broken 405 \ --suppress-broken 503 \ --suppress-broken 301 \ + --suppress-broken 400 \ -X "https://eigen.tuxfamily.org/index.php\?title=Main_Page" \ -X "https://mlpack.slack.com/" "$f" 2>&1 | grep -v 'Use of uninitialized value' > checklink_out; diff --git a/src/mlpack/base.hpp b/src/mlpack/base.hpp index 780435d4c5..c296733d27 100644 --- a/src/mlpack/base.hpp +++ b/src/mlpack/base.hpp @@ -84,6 +84,7 @@ // Now include Armadillo and traits that we use for it. #include #include +#include // On Visual Studio, disable C4519 (default arguments for function templates) // since it's by default an error, which doesn't even make any sense because diff --git a/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp b/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp index 0c57590e26..4034934ba7 100644 --- a/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp +++ b/src/mlpack/bindings/go/mlpack/capi/arma_util.hpp @@ -39,9 +39,7 @@ inline typename T::elem_type* GetMemory(T& m) 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 + arma::access::rw(m.n_alloc) = 0; return m.memptr(); } } diff --git a/src/mlpack/bindings/julia/julia_util.cpp b/src/mlpack/bindings/julia/julia_util.cpp index 5c0320a311..c3340296a6 100644 --- a/src/mlpack/bindings/julia/julia_util.cpp +++ b/src/mlpack/bindings/julia/julia_util.cpp @@ -428,9 +428,7 @@ double* GetParamMat(void* params, const char* paramName) else { arma::access::rw(mat.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(mat.n_alloc) = 0; - #endif + arma::access::rw(mat.n_alloc) = 0; return mat.memptr(); } } @@ -475,9 +473,7 @@ size_t* GetParamUMat(void* params, const char* paramName) else { arma::access::rw(mat.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(mat.n_alloc) = 0; - #endif + arma::access::rw(mat.n_alloc) = 0; return mat.memptr(); } } @@ -513,9 +509,7 @@ double* GetParamCol(void* params, const char* paramName) else { arma::access::rw(vec.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(vec.n_alloc) = 0; - #endif + arma::access::rw(vec.n_alloc) = 0; return vec.memptr(); } } @@ -552,9 +546,7 @@ size_t* GetParamUCol(void* params, const char* paramName) else { arma::access::rw(vec.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(vec.n_alloc) = 0; - #endif + arma::access::rw(vec.n_alloc) = 0; return vec.memptr(); } } @@ -590,9 +582,7 @@ double* GetParamRow(void* params, const char* paramName) else { arma::access::rw(vec.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(vec.n_alloc) = 0; - #endif + arma::access::rw(vec.n_alloc) = 0; return vec.memptr(); } } @@ -629,9 +619,7 @@ size_t* GetParamURow(void* params, const char* paramName) else { arma::access::rw(vec.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(vec.n_alloc) = 0; - #endif + arma::access::rw(vec.n_alloc) = 0; return vec.memptr(); } } @@ -707,9 +695,7 @@ double* GetParamMatWithInfoPtr(void* params, const char* paramName) else { arma::access::rw(m.mem_state) = 1; - #if ARMA_VERSION_MAJOR >= 10 - arma::access::rw(m.n_alloc) = 0; - #endif + arma::access::rw(m.n_alloc) = 0; return m.memptr(); } } diff --git a/src/mlpack/bindings/python/mlpack/arma_util.hpp b/src/mlpack/bindings/python/mlpack/arma_util.hpp index 70f0dd1b3e..ca6a8d1a71 100644 --- a/src/mlpack/bindings/python/mlpack/arma_util.hpp +++ b/src/mlpack/bindings/python/mlpack/arma_util.hpp @@ -25,9 +25,8 @@ void SetMemState(T& t, int 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 + + const_cast(t.n_alloc) = 0; } /** diff --git a/src/mlpack/bindings/python/mlpack/matrix_utils.py b/src/mlpack/bindings/python/mlpack/matrix_utils.py index dc6362cc43..3cf39126d5 100644 --- a/src/mlpack/bindings/python/mlpack/matrix_utils.py +++ b/src/mlpack/bindings/python/mlpack/matrix_utils.py @@ -160,7 +160,10 @@ def to_matrix_with_info(x, dtype, copy=False): dims = len(x) d = np.zeros([dims]) - out = np.array(x, dtype=dtype, copy=copy) # Try to avoid copy... + if np.lib.NumpyVersion(np.__version__) >= '2.0.0b1': + out = np.array(x, dtype=dtype, copy=(True if copy else None)) + else: + out = np.array(x, dtype=dtype, copy=copy) # Since we don't have a great way to check if these are using the same # memory location, we will probe manually (ugh). diff --git a/src/mlpack/core.hpp b/src/mlpack/core.hpp index 73eeaaece4..7205587ab5 100644 --- a/src/mlpack/core.hpp +++ b/src/mlpack/core.hpp @@ -37,6 +37,7 @@ // Now the core mlpack classes. #include #include +#include #include #include #include diff --git a/src/mlpack/core/distributions/gamma_distribution_impl.hpp b/src/mlpack/core/distributions/gamma_distribution_impl.hpp index 87c02d068e..875a437013 100644 --- a/src/mlpack/core/distributions/gamma_distribution_impl.hpp +++ b/src/mlpack/core/distributions/gamma_distribution_impl.hpp @@ -74,9 +74,9 @@ inline void GammaDistribution::Train(const arma::mat& rdata, if (arma::size(rdata) == arma::size(arma::mat())) return; - arma::vec meanLogxVec(rdata.n_rows, arma::fill::zeros); - arma::vec meanxVec(rdata.n_rows, arma::fill::zeros); - arma::vec logMeanxVec(rdata.n_rows, arma::fill::zeros); + arma::vec meanLogxVec(rdata.n_rows); + arma::vec meanxVec(rdata.n_rows); + arma::vec logMeanxVec(rdata.n_rows); for (size_t i = 0; i < rdata.n_cols; ++i) { diff --git a/src/mlpack/core/tree/ballbound.hpp b/src/mlpack/core/tree/ballbound.hpp index 0f1f8f9636..9a7e5f1977 100644 --- a/src/mlpack/core/tree/ballbound.hpp +++ b/src/mlpack/core/tree/ballbound.hpp @@ -27,12 +27,11 @@ namespace mlpack { * @tparam VecType Type of vector (arma::vec or arma::sp_vec or similar). */ template, - typename VecType = arma::vec> + typename ElemType = double, + typename VecType = arma::Col> class BallBound { public: - //! The underlying data type. - typedef typename VecType::elem_type ElemType; //! A public version of the vector type. typedef VecType Vec; diff --git a/src/mlpack/core/tree/ballbound_impl.hpp b/src/mlpack/core/tree/ballbound_impl.hpp index 123f0200cb..8793c3077c 100644 --- a/src/mlpack/core/tree/ballbound_impl.hpp +++ b/src/mlpack/core/tree/ballbound_impl.hpp @@ -20,8 +20,8 @@ namespace mlpack { //! Empty Constructor. -template -BallBound::BallBound() : +template +BallBound::BallBound() : radius(std::numeric_limits::lowest()), distance(new DistanceType()), ownsDistance(true) @@ -32,8 +32,8 @@ BallBound::BallBound() : * * @param dimension Dimensionality of ball bound. */ -template -BallBound::BallBound(const size_t dimension) : +template +BallBound::BallBound(const size_t dimension) : radius(std::numeric_limits::lowest()), center(dimension), distance(new DistanceType()), @@ -46,9 +46,9 @@ BallBound::BallBound(const size_t dimension) : * @param radius Radius of ball bound. * @param center Center of ball bound. */ -template -BallBound::BallBound(const ElemType radius, - const VecType& center) : +template +BallBound::BallBound(const ElemType radius, + const VecType& center) : radius(radius), center(center), distance(new DistanceType()), @@ -56,8 +56,8 @@ BallBound::BallBound(const ElemType radius, { /* Nothing to do. */ } //! Copy Constructor. To prevent memory leaks. -template -BallBound::BallBound(const BallBound& other) : +template +BallBound::BallBound(const BallBound& other) : radius(other.radius), center(other.center), distance(other.distance), @@ -65,8 +65,9 @@ BallBound::BallBound(const BallBound& other) : { /* Nothing to do. */ } //! For the same reason as the copy constructor: to prevent memory leaks. -template -BallBound& BallBound::operator=( +template +BallBound& +BallBound::operator=( const BallBound& other) { if (this != &other) @@ -80,8 +81,8 @@ BallBound& BallBound::operator=( } //! Move constructor. -template -BallBound::BallBound(BallBound&& other) : +template +BallBound::BallBound(BallBound&& other) : radius(other.radius), center(other.center), distance(other.distance), @@ -95,8 +96,9 @@ BallBound::BallBound(BallBound&& other) : } //! Move assignment operator. -template -BallBound& BallBound::operator=( +template +BallBound& +BallBound::operator=( BallBound&& other) { if (this != &other) @@ -115,29 +117,30 @@ BallBound& BallBound::operator=( } //! Destructor to release allocated memory. -template -BallBound::~BallBound() +template +BallBound::~BallBound() { if (ownsDistance) delete distance; } //! Get the range in a certain dimension. -template -RangeType::ElemType> -BallBound::operator[](const size_t i) const +template +RangeType +BallBound::operator[](const size_t i) const { if (radius < 0) - return Range(); + return RangeType(); else - return Range(center[i] - radius, center[i] + radius); + return RangeType(center[i] - radius, center[i] + radius); } /** * Determines if a point is within the bound. */ -template -bool BallBound::Contains(const VecType& point) const +template +bool BallBound::Contains(const VecType& point) + const { if (radius < 0) return false; @@ -148,10 +151,9 @@ bool BallBound::Contains(const VecType& point) const /** * Calculates minimum bound-to-point squared distance. */ -template +template template -typename BallBound::ElemType -BallBound::MinDistance( +ElemType BallBound::MinDistance( const OtherVecType& point, typename std::enable_if_t::value>* /* junk */) const { @@ -164,10 +166,9 @@ BallBound::MinDistance( /** * Calculates minimum bound-to-bound squared distance. */ -template -typename BallBound::ElemType -BallBound::MinDistance(const BallBound& other) - const +template +ElemType BallBound::MinDistance( + const BallBound& other) const { if (radius < 0) return std::numeric_limits::max(); @@ -182,10 +183,9 @@ BallBound::MinDistance(const BallBound& other) /** * Computes maximum distance. */ -template +template template -typename BallBound::ElemType -BallBound::MaxDistance( +ElemType BallBound::MaxDistance( const OtherVecType& point, typename std::enable_if_t::value>* /* junk */) const { @@ -198,10 +198,9 @@ BallBound::MaxDistance( /** * Computes maximum distance. */ -template -typename BallBound::ElemType -BallBound::MaxDistance(const BallBound& other) - const +template +ElemType BallBound::MaxDistance( + const BallBound& other) const { if (radius < 0) return std::numeric_limits::max(); @@ -214,36 +213,36 @@ BallBound::MaxDistance(const BallBound& other) * * Example: bound1.MinDistanceSq(other) for minimum squared distance. */ -template +template template -RangeType::ElemType> -BallBound::RangeDistance( +RangeType BallBound::RangeDistance( const OtherVecType& point, typename std::enable_if_t::value>* /* junk */) const { if (radius < 0) - return Range(std::numeric_limits::max(), - std::numeric_limits::max()); + return RangeType(std::numeric_limits::max(), + std::numeric_limits::max()); else { const ElemType dist = distance->Evaluate(center, point); - return Range(std::max(dist - radius, (ElemType) 0.0), dist + radius); + return RangeType(std::max(dist - radius, (ElemType) 0.0), + dist + radius); } } -template -RangeType::ElemType> -BallBound::RangeDistance( +template +RangeType BallBound::RangeDistance( const BallBound& other) const { if (radius < 0) - return Range(std::numeric_limits::max(), - std::numeric_limits::max()); + return RangeType(std::numeric_limits::max(), + std::numeric_limits::max()); else { const ElemType dist = distance->Evaluate(center, other.center); const ElemType sumradius = radius + other.radius; - return Range(std::max(dist - sumradius, (ElemType) 0.0), dist + sumradius); + return RangeType(std::max(dist - sumradius, (ElemType) 0.0), + dist + sumradius); } } @@ -253,10 +252,10 @@ BallBound::RangeDistance( * The difference lies in the way we initialize the ball bound. The way we * expand the bound is same. */ -template +template template -const BallBound& -BallBound::operator|=(const MatType& data) +const BallBound& +BallBound::operator|=(const MatType& data) { if (radius < 0) { @@ -284,9 +283,9 @@ BallBound::operator|=(const MatType& data) } //! Serialize the BallBound. -template +template template -void BallBound::serialize( +void BallBound::serialize( Archive& ar, const uint32_t /* version */) { diff --git a/src/mlpack/core/tree/binary_space_tree/binary_space_tree.hpp b/src/mlpack/core/tree/binary_space_tree/binary_space_tree.hpp index 050f26e553..500226524e 100644 --- a/src/mlpack/core/tree/binary_space_tree/binary_space_tree.hpp +++ b/src/mlpack/core/tree/binary_space_tree/binary_space_tree.hpp @@ -46,10 +46,11 @@ namespace mlpack { template class BoundType = - HRectBound, - template - class SplitType = MidpointSplit> + template class BoundType = HRectBound, + template class SplitType = MidpointSplit> class BinarySpaceTree { public: @@ -58,7 +59,7 @@ class BinarySpaceTree //! The type of element held in MatType. typedef typename MatType::elem_type ElemType; - typedef SplitType, MatType> Split; + typedef SplitType, MatType> Split; private: //! The left child node. @@ -74,7 +75,7 @@ class BinarySpaceTree //! children). size_t count; //! The bound object for this node. - BoundType bound; + BoundType bound; //! Any extra data contained in the node. StatisticType stat; //! The distance from the centroid of this node to the centroid of the parent. @@ -210,7 +211,8 @@ class BinarySpaceTree BinarySpaceTree(BinarySpaceTree* parent, const size_t begin, const size_t count, - SplitType, MatType>& splitter, + SplitType, MatType>& + splitter, const size_t maxLeafSize = 20); /** @@ -236,7 +238,8 @@ class BinarySpaceTree const size_t begin, const size_t count, std::vector& oldFromNew, - SplitType, MatType>& splitter, + SplitType, MatType>& + splitter, const size_t maxLeafSize = 20); /** @@ -266,7 +269,8 @@ class BinarySpaceTree const size_t count, std::vector& oldFromNew, std::vector& newFromOld, - SplitType, MatType>& splitter, + SplitType, MatType>& + splitter, const size_t maxLeafSize = 20); /** @@ -315,9 +319,9 @@ class BinarySpaceTree ~BinarySpaceTree(); //! Return the bound object for this node. - const BoundType& Bound() const { return bound; } + const BoundType& Bound() const { return bound; } //! Return the bound object for this node. - BoundType& Bound() { return bound; } + BoundType& Bound() { return bound; } //! Return the statistic object for this node. const StatisticType& Stat() const { return stat; } @@ -517,8 +521,9 @@ class BinarySpaceTree * @param maxLeafSize Maximum number of points held in a leaf. * @param splitter Instantiated SplitType object. */ - void SplitNode(const size_t maxLeafSize, - SplitType, MatType>& splitter); + void SplitNode( + const size_t maxLeafSize, + SplitType, MatType>& splitter); /** * Splits the current node, assigning its left and right children recursively. @@ -528,9 +533,10 @@ class BinarySpaceTree * @param maxLeafSize Maximum number of points held in a leaf. * @param splitter Instantiated SplitType object. */ - void SplitNode(std::vector& oldFromNew, - const size_t maxLeafSize, - SplitType, MatType>& splitter); + void SplitNode( + std::vector& oldFromNew, + const size_t maxLeafSize, + SplitType, MatType>& splitter); /** * Update the bound of the current node. This method does not take into @@ -547,7 +553,7 @@ class BinarySpaceTree * * @param boundToUpdate The bound to update. */ - void UpdateBound(HollowBallBound& boundToUpdate); + void UpdateBound(HollowBallBound& boundToUpdate); protected: /** diff --git a/src/mlpack/core/tree/binary_space_tree/binary_space_tree_impl.hpp b/src/mlpack/core/tree/binary_space_tree/binary_space_tree_impl.hpp index 7859364462..74c73d3a25 100644 --- a/src/mlpack/core/tree/binary_space_tree/binary_space_tree_impl.hpp +++ b/src/mlpack/core/tree/binary_space_tree/binary_space_tree_impl.hpp @@ -24,9 +24,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( const MatType& data, @@ -41,7 +43,7 @@ BinarySpaceTree( dataset(new MatType(data)) // Copies the dataset. { // Do the actual splitting of this node. - SplitType, MatType> splitter; + SplitType, MatType> splitter; SplitNode(maxLeafSize, splitter); // Create the statistic depending on if we are a leaf or not. @@ -51,9 +53,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( const MatType& data, @@ -74,7 +78,7 @@ BinarySpaceTree( oldFromNew[i] = i; // Fill with unharmed indices. // Now do the actual splitting. - SplitType, MatType> splitter; + SplitType, MatType> splitter; SplitNode(oldFromNew, maxLeafSize, splitter); // Create the statistic depending on if we are a leaf or not. @@ -84,9 +88,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( const MatType& data, @@ -108,7 +114,7 @@ BinarySpaceTree( oldFromNew[i] = i; // Fill with unharmed indices. // Now do the actual splitting. - SplitType, MatType> splitter; + SplitType, MatType> splitter; SplitNode(oldFromNew, maxLeafSize, splitter); // Create the statistic depending on if we are a leaf or not. @@ -123,9 +129,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree(MatType&& data, const size_t maxLeafSize) : left(NULL), @@ -138,7 +146,7 @@ BinarySpaceTree(MatType&& data, const size_t maxLeafSize) : dataset(new MatType(std::move(data))) { // Do the actual splitting of this node. - SplitType, MatType> splitter; + SplitType, MatType> splitter; SplitNode(maxLeafSize, splitter); // Create the statistic depending on if we are a leaf or not. @@ -148,9 +156,11 @@ BinarySpaceTree(MatType&& data, const size_t maxLeafSize) : template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( MatType&& data, @@ -171,7 +181,7 @@ BinarySpaceTree( oldFromNew[i] = i; // Fill with unharmed indices. // Now do the actual splitting. - SplitType, MatType> splitter; + SplitType, MatType> splitter; SplitNode(oldFromNew, maxLeafSize, splitter); // Create the statistic depending on if we are a leaf or not. @@ -181,9 +191,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( MatType&& data, @@ -205,7 +217,7 @@ BinarySpaceTree( oldFromNew[i] = i; // Fill with unharmed indices. // Now do the actual splitting. - SplitType, MatType> splitter; + SplitType, MatType> splitter; SplitNode(oldFromNew, maxLeafSize, splitter); // Create the statistic depending on if we are a leaf or not. @@ -220,15 +232,17 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( BinarySpaceTree* parent, const size_t begin, const size_t count, - SplitType, MatType>& splitter, + SplitType, MatType>& splitter, const size_t maxLeafSize) : left(NULL), right(NULL), @@ -248,16 +262,18 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( BinarySpaceTree* parent, const size_t begin, const size_t count, std::vector& oldFromNew, - SplitType, MatType>& splitter, + SplitType, MatType>& splitter, const size_t maxLeafSize) : left(NULL), right(NULL), @@ -281,9 +297,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( BinarySpaceTree* parent, @@ -291,7 +309,7 @@ BinarySpaceTree( const size_t count, std::vector& oldFromNew, std::vector& newFromOld, - SplitType, MatType>& splitter, + SplitType, MatType>& splitter, const size_t maxLeafSize) : left(NULL), right(NULL), @@ -324,9 +342,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree( const BinarySpaceTree& other) : @@ -384,9 +404,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree& BinarySpaceTree:: operator=(const BinarySpaceTree& other) @@ -456,9 +478,11 @@ operator=(const BinarySpaceTree& other) template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree& BinarySpaceTree:: operator=(BinarySpaceTree&& other) @@ -504,9 +528,11 @@ operator=(BinarySpaceTree&& other) template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree(BinarySpaceTree&& other) : left(other.left), @@ -546,9 +572,11 @@ BinarySpaceTree(BinarySpaceTree&& other) : template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template BinarySpaceTree:: BinarySpaceTree( @@ -569,9 +597,11 @@ BinarySpaceTree( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: ~BinarySpaceTree() { @@ -586,9 +616,11 @@ BinarySpaceTree:: template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline bool BinarySpaceTree::IsLeaf() const { @@ -601,9 +633,11 @@ inline bool BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline size_t BinarySpaceTree::NumChildren() const { @@ -622,9 +656,11 @@ inline size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template size_t BinarySpaceTree::GetNearestChild( @@ -646,9 +682,11 @@ size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template size_t BinarySpaceTree::GetFurthestChild( @@ -670,9 +708,11 @@ size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> size_t BinarySpaceTree::GetNearestChild(const BinarySpaceTree& queryNode) { @@ -695,9 +735,11 @@ size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> size_t BinarySpaceTree::GetFurthestChild(const BinarySpaceTree& queryNode) { @@ -720,9 +762,11 @@ size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline typename BinarySpaceTree::ElemType @@ -746,9 +790,11 @@ BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline typename BinarySpaceTree::ElemType @@ -762,9 +808,11 @@ BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline typename BinarySpaceTree::ElemType @@ -780,9 +828,11 @@ BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline BinarySpaceTree& BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline size_t BinarySpaceTree::NumPoints() const { @@ -818,9 +870,11 @@ inline size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline size_t BinarySpaceTree::NumDescendants() const { @@ -833,9 +887,11 @@ inline size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline size_t BinarySpaceTree::Descendant(const size_t index) const { @@ -848,9 +904,11 @@ inline size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> inline size_t BinarySpaceTree::Point(const size_t index) const { @@ -860,13 +918,15 @@ inline size_t BinarySpaceTree class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> void BinarySpaceTree:: SplitNode(const size_t maxLeafSize, - SplitType, MatType>& splitter) + SplitType, MatType>& splitter) { // We need to expand the bounds of this node properly. UpdateBound(bound); @@ -927,14 +987,16 @@ BinarySpaceTree:: template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> void BinarySpaceTree:: SplitNode(std::vector& oldFromNew, const size_t maxLeafSize, - SplitType, MatType>& splitter) + SplitType, MatType>& splitter) { // We need to expand the bounds of this node properly. UpdateBound(bound); @@ -996,9 +1058,11 @@ SplitNode(std::vector& oldFromNew, template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template void BinarySpaceTree:: @@ -1011,12 +1075,14 @@ UpdateBound(BoundType2& boundToUpdate) template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> void BinarySpaceTree:: -UpdateBound(HollowBallBound& boundToUpdate) +UpdateBound(HollowBallBound& boundToUpdate) { if (!parent) { @@ -1039,9 +1105,11 @@ UpdateBound(HollowBallBound& boundToUpdate) template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> BinarySpaceTree:: BinarySpaceTree() : left(NULL), @@ -1063,9 +1131,11 @@ BinarySpaceTree:: template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template void BinarySpaceTree:: diff --git a/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser.hpp b/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser.hpp index 937cb965f1..39766c7afe 100644 --- a/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser.hpp +++ b/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser.hpp @@ -35,9 +35,11 @@ struct QueueFrame template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template class BinarySpaceTree::BreadthFirstDualTreeTraverser diff --git a/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser_impl.hpp b/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser_impl.hpp index 7b598eab6d..8496e7b178 100644 --- a/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser_impl.hpp +++ b/src/mlpack/core/tree/binary_space_tree/breadth_first_dual_tree_traverser_impl.hpp @@ -22,9 +22,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template BinarySpaceTree:: BreadthFirstDualTreeTraverser::BreadthFirstDualTreeTraverser( @@ -50,9 +52,11 @@ bool operator<(const QueueFrame& a, template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template void BinarySpaceTree:: @@ -91,9 +95,11 @@ BreadthFirstDualTreeTraverser::Traverse( template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template void BinarySpaceTree:: BreadthFirstDualTreeTraverser::Traverse( diff --git a/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser.hpp b/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser.hpp index fc42d5f15b..c45e4d29d9 100644 --- a/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser.hpp +++ b/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser.hpp @@ -24,9 +24,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template class BinarySpaceTree::DualTreeTraverser diff --git a/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser_impl.hpp b/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser_impl.hpp index ec030e133d..002c9b80c8 100644 --- a/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser_impl.hpp +++ b/src/mlpack/core/tree/binary_space_tree/dual_tree_traverser_impl.hpp @@ -22,9 +22,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template BinarySpaceTree:: DualTreeTraverser::DualTreeTraverser(RuleType& rule) : @@ -38,9 +40,11 @@ DualTreeTraverser::DualTreeTraverser(RuleType& rule) : template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template void BinarySpaceTree:: diff --git a/src/mlpack/core/tree/binary_space_tree/single_tree_traverser.hpp b/src/mlpack/core/tree/binary_space_tree/single_tree_traverser.hpp index 1fe477c907..9616c9e3ab 100644 --- a/src/mlpack/core/tree/binary_space_tree/single_tree_traverser.hpp +++ b/src/mlpack/core/tree/binary_space_tree/single_tree_traverser.hpp @@ -23,9 +23,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template class BinarySpaceTree::SingleTreeTraverser diff --git a/src/mlpack/core/tree/binary_space_tree/single_tree_traverser_impl.hpp b/src/mlpack/core/tree/binary_space_tree/single_tree_traverser_impl.hpp index cb164d8e39..9b783b0e2a 100644 --- a/src/mlpack/core/tree/binary_space_tree/single_tree_traverser_impl.hpp +++ b/src/mlpack/core/tree/binary_space_tree/single_tree_traverser_impl.hpp @@ -24,9 +24,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template BinarySpaceTree:: SingleTreeTraverser::SingleTreeTraverser(RuleType& rule) : @@ -37,9 +39,11 @@ SingleTreeTraverser::SingleTreeTraverser(RuleType& rule) : template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> template void BinarySpaceTree:: diff --git a/src/mlpack/core/tree/binary_space_tree/traits.hpp b/src/mlpack/core/tree/binary_space_tree/traits.hpp index 623cddf5c3..4d563c6bba 100644 --- a/src/mlpack/core/tree/binary_space_tree/traits.hpp +++ b/src/mlpack/core/tree/binary_space_tree/traits.hpp @@ -26,9 +26,11 @@ namespace mlpack { template class BoundType, - template - class SplitType> + template class BoundType, + template class SplitType> class TreeTraits> { @@ -80,7 +82,9 @@ class TreeTraits class BoundType> + template class BoundType> class TreeTraits> { @@ -130,9 +134,11 @@ class TreeTraits class BoundType> -class TreeTraits> + template class BoundType> +class TreeTraits> { public: /** 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 afaa845123..78b0576392 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 @@ -243,7 +243,7 @@ CalculateValue(const VecType& pt, res(i) ^= t; // We should rearrange bits in order to compare two Hilbert values faster. - arma::Col rearrangedResult(pt.n_rows, arma::fill::zeros); + arma::Col rearrangedResult(pt.n_rows); for (size_t i = 0; i < order; ++i) for (size_t j = 0; j < pt.n_rows; ++j) diff --git a/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp b/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp index 6ada7dee23..8c9d43624e 100644 --- a/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp +++ b/src/mlpack/core/tree/rectangle_tree/r_star_tree_split_impl.hpp @@ -106,9 +106,9 @@ void RStarTreeSplit::PickLeafSplit(TreeType* tree, // We'll store each of the three scores for each distribution. const size_t numPossibleSplits = tree->MaxLeafSize() - 2 * tree->MinLeafSize() + 2; - arma::Col areas(numPossibleSplits, arma::fill::zeros); - arma::Col margins(numPossibleSplits, arma::fill::zeros); - arma::Col overlaps(numPossibleSplits, arma::fill::zeros); + arma::Col areas(numPossibleSplits); + arma::Col margins(numPossibleSplits); + arma::Col overlaps(numPossibleSplits); for (size_t i = 0; i < numPossibleSplits; ++i) { @@ -310,9 +310,9 @@ bool RStarTreeSplit::SplitNonLeafNode( // each rectangle. const size_t numPossibleSplits = tree->MaxNumChildren() - 2 * tree->MinNumChildren() + 2; - arma::Col areas(2 * numPossibleSplits, arma::fill::zeros); - arma::Col margins(2 * numPossibleSplits, arma::fill::zeros); - arma::Col overlaps(2 * numPossibleSplits, arma::fill::zeros); + arma::Col areas(2 * numPossibleSplits); + arma::Col margins(2 * numPossibleSplits); + arma::Col overlaps(2 * numPossibleSplits); for (size_t i = 0; i < numPossibleSplits; ++i) { diff --git a/src/mlpack/core/tree/space_split/projection_vector.hpp b/src/mlpack/core/tree/space_split/projection_vector.hpp index ee2e3fea03..8dfc0de532 100644 --- a/src/mlpack/core/tree/space_split/projection_vector.hpp +++ b/src/mlpack/core/tree/space_split/projection_vector.hpp @@ -67,9 +67,9 @@ class AxisParallelProjVector * @param bound Bound to be projected. * @return Range of projected values. */ - template - RangeType Project( - const BallBound& bound) const + template + RangeType Project( + const BallBound& bound) const { return bound[dim]; } @@ -128,11 +128,10 @@ class ProjVector * @param bound Bound to be projected. * @return Range of projected values. */ - template - RangeType Project( - const BallBound& bound) const + template + RangeType Project( + const BallBound& bound) const { - typedef typename VecType::elem_type ElemType; const double center = Project(bound.Center()); const ElemType radius = bound.Radius(); return RangeType(center - radius, center + radius); diff --git a/src/mlpack/core/util/arma_traits.hpp b/src/mlpack/core/util/arma_traits.hpp index 2dd360761b..1a7133feb5 100644 --- a/src/mlpack/core/util/arma_traits.hpp +++ b/src/mlpack/core/util/arma_traits.hpp @@ -50,7 +50,7 @@ struct IsCube }; // Commenting out the first template per case, because -// Visual Studio doesn't like this instantiaion pattern (error C2910). +// Visual Studio doesn't like this instantiation pattern (error C2910). // template<> template struct IsVector > @@ -105,35 +105,17 @@ struct IsCube > const static bool value = true; }; +template +struct IsVector > +{ + const static bool value = true; +}; -#if ((ARMA_VERSION_MAJOR >= 10) || \ - ((ARMA_VERSION_MAJOR == 9) && (ARMA_VERSION_MINOR >= 869))) - - // Armadillo 9.869+ has SpSubview_col and SpSubview_row - - template - struct IsVector > - { - const static bool value = true; - }; - - template - struct IsVector > - { - const static bool value = true; - }; - -#else - - // fallback for older Armadillo versions - - template - struct IsVector > - { - const static bool value = true; - }; - -#endif +template +struct IsVector > +{ + const static bool value = true; +}; // Get the row vector type corresponding to a given MatType. diff --git a/src/mlpack/core/util/first_element_is_arma.hpp b/src/mlpack/core/util/first_element_is_arma.hpp new file mode 100644 index 0000000000..60adcc0fe3 --- /dev/null +++ b/src/mlpack/core/util/first_element_is_arma.hpp @@ -0,0 +1,44 @@ +/** + * @file core/util/first_element_is_arma.hpp + * @author Ryan Curtin + * + * Utility struct to detect whether the first element in a parameter pack is an + * Armadillo type. + */ +#ifndef MLPACK_CORE_UTIL_FIRST_ELEMENT_IS_ARMA_HPP +#define MLPACK_CORE_UTIL_FIRST_ELEMENT_IS_ARMA_HPP + +#include + +namespace mlpack { + +// This utility struct returns the first type of a parameter pack. +template +struct First +{ + typedef void type; +}; + +// This matches whenever CallbackTypes has one or more elements. +template +struct First +{ + typedef T type; +}; + +// This utility template struct detects whether the first element in a +// parameter pack is an Armadillo type. It is entirely for the deprecated +// constructor below and can be removed when that is removed during the +// release of mlpack 5.0.0. +template +struct FirstElementIsArma +{ + static constexpr bool value = arma::is_arma_type< + typename std::remove_reference< + typename First::type + >::type>::value; +}; + +} + +#endif diff --git a/src/mlpack/core/util/omp_reductions.hpp b/src/mlpack/core/util/omp_reductions.hpp new file mode 100644 index 0000000000..1a55028c20 --- /dev/null +++ b/src/mlpack/core/util/omp_reductions.hpp @@ -0,0 +1,27 @@ +/** + * @file core/util/omp_reductions.hpp + * @author Mark Fischinger + * + * Custom OpenMP reductions. + * + * 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_UTIL_OMP_REDUCTIONS_HPP +#define MLPACK_CORE_UTIL_OMP_REDUCTIONS_HPP + +namespace mlpack { + +// Custom reduction for arma::mat +#pragma omp declare reduction(matAdd : arma::mat : omp_out += omp_in) \ + initializer(omp_priv = arma::mat(omp_orig.n_rows, omp_orig.n_cols)) + +// Custom reduction for arma::Col +#pragma omp declare reduction(colAdd : arma::Col : omp_out += omp_in) \ + initializer(omp_priv = arma::Col(omp_orig.n_elem)) + +} // namespace mlpack + +#endif diff --git a/src/mlpack/core/util/using.hpp b/src/mlpack/core/util/using.hpp index aefcdcaf58..b37c826481 100644 --- a/src/mlpack/core/util/using.hpp +++ b/src/mlpack/core/util/using.hpp @@ -1,6 +1,8 @@ /** * @file core/util/using.hpp * @author Omar Shrit + * @author Ryan Curtin + * @author Conrad Sanderson * * This is a set of `using` statements to mitigate any possible risks or * conflicts with local functions. The compiler is supposed to proritise the @@ -75,6 +77,31 @@ namespace mlpack { #endif + // By default, assume that we are using an Armadillo object. + template + struct GetFillType + { + static constexpr const decltype(arma::fill::none)& none = arma::fill::none; + static constexpr const decltype(arma::fill::zeros)& zeros = arma::fill::zeros; + static constexpr const decltype(arma::fill::ones)& ones = arma::fill::ones; + static constexpr const decltype(arma::fill::randu)& randu = arma::fill::randu; + static constexpr const decltype(arma::fill::randn)& randn = arma::fill::randn; + }; + +#ifdef MLPACK_HAS_COOT + // If the matrix type is a Bandicoot type, use Bandicoot fill objects instead. + template::value>::type*> + struct GetFillType + { + static constexpr const decltype(coot::fill::none)& none = coot::fill::none; + static constexpr const decltype(coot::fill::zeros)& zeros = coot::fill::zeros; + static constexpr const decltype(coot::fill::ones)& ones = coot::fill::ones; + static constexpr const decltype(coot::fill::randu)& randu = coot::fill::randu; + static constexpr const decltype(coot::fill::randn)& randn = coot::fill::randn; + }; +#endif + } // namespace mlpack #endif diff --git a/src/mlpack/methods/CMakeLists.txt b/src/mlpack/methods/CMakeLists.txt index 11e926ee69..7dc1c46ad5 100644 --- a/src/mlpack/methods/CMakeLists.txt +++ b/src/mlpack/methods/CMakeLists.txt @@ -26,7 +26,7 @@ add_all_bindings(hmm hmm_train "Misc. / Other") add_all_bindings(hmm hmm_generate "Misc. / Other") add_all_bindings(hmm hmm_loglik "Misc. / Other") add_all_bindings(hmm hmm_viterbi "Misc. / Other") -add_all_bindings(hoeffding_trees hoeffding_tree "Clustering") +add_all_bindings(hoeffding_trees hoeffding_tree "Classification") add_all_bindings(kde kde "Misc. / Other") add_all_bindings(kernel_pca kernel_pca "Transformations") add_all_bindings(kmeans kmeans "Clustering") diff --git a/src/mlpack/methods/ann/convolution_rules/naive_convolution.hpp b/src/mlpack/methods/ann/convolution_rules/naive_convolution.hpp index 21abe51a2a..203fcf7ab3 100644 --- a/src/mlpack/methods/ann/convolution_rules/naive_convolution.hpp +++ b/src/mlpack/methods/ann/convolution_rules/naive_convolution.hpp @@ -132,7 +132,7 @@ class NaiveConvolution // Pad filter and input to the working output shape. InMatType inputPadded(input.n_rows + 2 * paddingRows, - input.n_cols + 2 * paddingCols, arma::fill::zeros); + input.n_cols + 2 * paddingCols); inputPadded.submat(paddingRows, paddingCols, paddingRows + input.n_rows - 1, paddingCols + input.n_cols - 1) = input; diff --git a/src/mlpack/methods/ann/ffn_impl.hpp b/src/mlpack/methods/ann/ffn_impl.hpp index dd0c6ea8ff..8e1bfdbb5f 100644 --- a/src/mlpack/methods/ann/ffn_impl.hpp +++ b/src/mlpack/methods/ann/ffn_impl.hpp @@ -470,7 +470,8 @@ typename MatType::elem_type FFN< { typename MatType::elem_type res = 0; res += EvaluateWithGradient(parameters, 0, gradient, 1); - MatType tmpGradient(gradient.n_rows, gradient.n_cols, arma::fill::none); + MatType tmpGradient(gradient.n_rows, gradient.n_cols, + GetFillType::none); for (size_t i = 1; i < predictors.n_cols; ++i) { res += EvaluateWithGradient(parameters, i, tmpGradient, 1); diff --git a/src/mlpack/methods/ann/layer/convolution_impl.hpp b/src/mlpack/methods/ann/layer/convolution_impl.hpp index c40a703f2c..1351cd3339 100644 --- a/src/mlpack/methods/ann/layer/convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/convolution_impl.hpp @@ -401,7 +401,7 @@ void ConvolutionType< dilatedMappedError.zeros(mappedError.n_rows * strideWidth - (strideWidth - 1), mappedError.n_cols * strideHeight - (strideHeight - 1), mappedError.n_slices); - #pragma omp parallel for collapse(3) + #pragma omp parallel for collapse(3) schedule(static) for (size_t i = 0; i < mappedError.n_slices; ++i) { for (size_t j = 0; j < mappedError.n_cols; ++j) @@ -415,26 +415,26 @@ void ConvolutionType< } } - #pragma omp parallel for + #pragma omp parallel for schedule(static) for (size_t map = 0; map < (size_t) (maps * inMaps); ++map) { Rotate180(weight.slice(map), rotatedFilters.slice(map)); } MatType output(apparentWidth * apparentHeight * inMaps * higherInDimensions, - batchSize, arma::fill::zeros); + batchSize); CubeType outputCube; MakeAlias(outputCube, output, apparentWidth, apparentHeight, inMaps * higherInDimensions * batchSize); // See Forward() for the overall iteration strategy. + #pragma omp parallel for schedule(dynamic) for (size_t offset = 0; offset < (higherInDimensions * batchSize); ++offset) { const size_t fullInputOffset = offset * inMaps; const size_t fullOutputOffset = offset * maps; // Iterate over input maps. - #pragma omp parallel for for (size_t inMap = 0; inMap < (size_t) inMaps; ++inMap) { // Iterate over output maps. diff --git a/src/mlpack/methods/ann/layer/dropout_impl.hpp b/src/mlpack/methods/ann/layer/dropout_impl.hpp index 1a725d149d..e0e1a13106 100644 --- a/src/mlpack/methods/ann/layer/dropout_impl.hpp +++ b/src/mlpack/methods/ann/layer/dropout_impl.hpp @@ -87,8 +87,15 @@ void DropoutType::Forward(const MatType& input, MatType& output) // Scale with input / (1 - ratio) and set values to zero with probability // 'ratio'. mask.randu(input.n_rows, input.n_cols); - mask.transform([&](double val) { return (val > ratio); }); - output = input % mask * scale; + #pragma omp parallel for collapse(2) + for (size_t i = 0; i < input.n_rows; ++i) + { + for (size_t j = 0; j < input.n_cols; ++j) + { + mask(i, j) = (mask(i, j) > this->ratio) ? 1.0 : 0.0; + } + } + output = input % mask * this->scale; } } diff --git a/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp b/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp index 6c60e9b128..a9069751b9 100644 --- a/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp +++ b/src/mlpack/methods/ann/layer/grouped_convolution_impl.hpp @@ -440,7 +440,7 @@ void GroupedConvolutionType< } MatType output(apparentWidth * apparentHeight * inMaps * higherInDimensions, - batchSize, arma::fill::zeros); + batchSize); CubeType outputCube; MakeAlias(outputCube, output, apparentWidth, apparentHeight, inMaps * higherInDimensions * batchSize); diff --git a/src/mlpack/methods/ann/layer/layer_types.hpp b/src/mlpack/methods/ann/layer/layer_types.hpp index 87a9141209..305f3cf0dd 100644 --- a/src/mlpack/methods/ann/layer/layer_types.hpp +++ b/src/mlpack/methods/ann/layer/layer_types.hpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mlpack/methods/ann/layer/nearest_interpolation.hpp b/src/mlpack/methods/ann/layer/nearest_interpolation.hpp new file mode 100644 index 0000000000..bc9041a2fb --- /dev/null +++ b/src/mlpack/methods/ann/layer/nearest_interpolation.hpp @@ -0,0 +1,112 @@ +// +/** + * @filer methods/ann/layer/nearest_interpolation.hpp + * @author Andrew Furey + * + * 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_NEAREST_INTERPOLATION_HPP +#define MLPACK_METHODS_ANN_LAYER_NEAREST_INTERPOLATION_HPP + +#include +#include "layer.hpp" + +namespace mlpack { + +/** + * Definition and Implementation of the Nearest Interpolation Layer. + * + * Nearest interpolation is an mathematical technique, primarily used for + * scaling purposes. The input should be a 2D matrix and it can have + * a number of channels/units. + * + * @tparam InputType Type of the input data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + * @tparam MatType Type of the output data (arma::colvec, arma::mat, + * arma::sp_mat or arma::cube). + */ +template +class NearestInterpolationType : public Layer +{ + public: + //! Create the NearestInterpolation object. + NearestInterpolationType(); + + /**Create NearestInterpolation Object with the same scaleFactor along + * each dimension. + * NOTE: scaleFactors must be a two element vector, the first element + * for scaling the first dimension and the second element for scaling + * the second dimension. + * + * If the input dimensions are n x m x ..., then the output dimensions + * will be (n x scaleFactors[0]) x (m x scaleFactors[1]) x ... + * + * @param scaleFactor Scale factors to scale each dimension by. + */ + NearestInterpolationType(const std::vector scaleFactors); + + NearestInterpolationType* Clone() const { + return new NearestInterpolationType(*this); + } + + virtual ~NearestInterpolationType() { } + + //! Copy the given NearestInterpolationType layer. + NearestInterpolationType(const NearestInterpolationType& other); + //! Take ownership of the given NearestInterpolationType layer. + NearestInterpolationType(NearestInterpolationType&& other); + //! Copy the given NearestInterpolationType layer. + NearestInterpolationType& operator=(const NearestInterpolationType& other); + //! Take ownership of the given NearestInterpolationType layer. + NearestInterpolationType& operator=(NearestInterpolationType&& other); + + /** + * Forward pass through the layer. The layer interpolates + * the matrix using the given Nearest Interpolation method. + * + * @param input The input matrix. + * @param output The resulting interpolated output matrix. + */ + void Forward(const MatType& input, MatType& output); + + /** + * Ordinary feed backward pass of a neural network, calculating the function + * f(x) by propagating x backwards through f. Using the results from the feed + * forward pass. Since the layer does not have any learn-able parameters, + * we just have to down-sample the gradient to make its size compatible with + * the input size. + * + * @param * (input) The input matrix. + * @param gradient The computed backward gradient. + * @param output The resulting down-sampled output. + */ + void Backward(const MatType& /*input*/, + const MatType& gradient, + MatType& output); + + //! Compute the output dimensions of the layer, based on the internal values + //! of `InputDimensions()`. + void ComputeOutputDimensions(); + + /** + * Serialize the layer. + */ + template + void serialize(Archive& ar, const uint32_t /* version */); + + private: + //! Vector of scale factors to scale different dimensions. + std::vector scaleFactors; +}; // class NearestInterpolation + +typedef NearestInterpolationType NearestInterpolation; + +} // namespace mlpack + +// Include implementation. +#include "nearest_interpolation_impl.hpp" + +#endif diff --git a/src/mlpack/methods/ann/layer/nearest_interpolation_impl.hpp b/src/mlpack/methods/ann/layer/nearest_interpolation_impl.hpp new file mode 100644 index 0000000000..bb2ec09ec4 --- /dev/null +++ b/src/mlpack/methods/ann/layer/nearest_interpolation_impl.hpp @@ -0,0 +1,177 @@ +/** + * @file methods/ann/layer/nearest_interpolation_impl.hpp + * @author Andrew Furey + * + * Implementation of the NearestInterpolation layer. + * + * 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_NEAREST_INTERPOLATION_IMPL_HPP +#define MLPACK_METHODS_ANN_LAYER_NEAREST_INTERPOLATION_IMPL_HPP + +// In case it hasn't yet been included. +#include "nearest_interpolation.hpp" +#include + +namespace mlpack { + +template +NearestInterpolationType::NearestInterpolationType(): + Layer() +{ + // Nothing to do here. +} + +template +NearestInterpolationType:: +NearestInterpolationType(const std::vector scaleFactors) : + Layer() +{ + if (scaleFactors.size() != 2) { + throw std::runtime_error("Scale factors must have 2 dimensions"); + } + this->scaleFactors = std::move(scaleFactors); +} + +template +NearestInterpolationType:: +NearestInterpolationType(const NearestInterpolationType& other) : + Layer(), + scaleFactors(other.scaleFactors) +{ + // Nothing to do here. +} + +template +NearestInterpolationType:: +NearestInterpolationType(NearestInterpolationType&& other) : + Layer(std::move(other)), + scaleFactors(std::move(other.scaleFactors)) +{ + // Nothing to do here. +} + +template +NearestInterpolationType& +NearestInterpolationType:: +operator=(const NearestInterpolationType& other) +{ + if (&other != this) + { + Layer::operator=(other); + scaleFactors = other.scaleFactors; + } + return *this; +} + +template +NearestInterpolationType& +NearestInterpolationType:: +operator=(NearestInterpolationType&& other) +{ + if (&other != this) + { + Layer::operator=(std::move(other)); + scaleFactors = std::move(other.scaleFactors); + } + return *this; +} + +template +void NearestInterpolationType::Forward( + const MatType& input, MatType& output) +{ + const size_t channels = this->inputDimensions[2]; + + const size_t outRowSize = this->outputDimensions[0]; + const size_t outColSize = this->outputDimensions[1]; + + const size_t inRowSize = this->inputDimensions[0]; + const size_t inColSize = this->inputDimensions[1]; + + arma::cube inputAsCube; + arma::cube outputAsCube; + + MakeAlias(inputAsCube, input, inRowSize, inColSize, channels, 0, false); + MakeAlias(outputAsCube, output, outRowSize, outColSize, channels, 0, true); + + for (size_t i = 0; i < outRowSize; ++i) + { + size_t rOrigin = std::floor(i / scaleFactors[0]); + for (size_t j = 0; j < outColSize; ++j) + { + size_t cOrigin = std::floor(j / scaleFactors[1]); + for (size_t k = 0; k < channels; ++k) + { + outputAsCube(i, j, k) = inputAsCube(rOrigin, cOrigin, k); + } + } + } +} + +template +void NearestInterpolationType::Backward( + const MatType& /*input*/, + const MatType& gradient, + MatType& output) +{ + const size_t channels = this->inputDimensions[2]; + + const size_t outRowSize = this->outputDimensions[0]; + const size_t outColSize = this->outputDimensions[1]; + + const size_t inRowSize = this->inputDimensions[0]; + const size_t inColSize = this->inputDimensions[1]; + + arma::cube outputAsCube; + arma::cube gradientAsCube; + + MakeAlias(outputAsCube, output, inRowSize, inColSize, channels, 0, true); + MakeAlias(gradientAsCube, gradient, outRowSize, outColSize, channels, 0, false); + + for (size_t i = 0; i < outRowSize; ++i) + { + size_t rOrigin = std::floor(i / scaleFactors[0]); + for (size_t j = 0; j < outColSize; ++j) + { + size_t cOrigin = std::floor(j / scaleFactors[1]); + for (size_t k = 0; k < channels; ++k) + { + outputAsCube(rOrigin, cOrigin, k) += gradientAsCube(i, j, k); + } + } + } +} + +template +void NearestInterpolationType::ComputeOutputDimensions() +{ + if (this->inputDimensions.size() < scaleFactors.size()) { + std::ostringstream oss; + oss << "NearestInterpolation::ComputeOutputDimensions(): input dimensions " + << "must be at least 2 (received input with " << this->inputDimensions.size() + << " dimensions)!"; + throw std::runtime_error(oss.str()); + } + this->outputDimensions = this->inputDimensions; + for (size_t i = 0; i < scaleFactors.size(); i++) + { + this->outputDimensions[i] = std::round( + (double)this->outputDimensions[i] * scaleFactors[i]); + } +} + +template +template +void NearestInterpolationType::serialize( + Archive& ar, const uint32_t /* version */) +{ + ar(CEREAL_NVP(scaleFactors)); +} + +} // namespace mlpack + +#endif diff --git a/src/mlpack/methods/ann/layer/not_adapted/bicubic_interpolation.hpp b/src/mlpack/methods/ann/layer/not_adapted/bicubic_interpolation.hpp index e9e1144928..8daf91f89d 100644 --- a/src/mlpack/methods/ann/layer/not_adapted/bicubic_interpolation.hpp +++ b/src/mlpack/methods/ann/layer/not_adapted/bicubic_interpolation.hpp @@ -40,6 +40,8 @@ class BicubicInterpolation //! Create the Bicubic Interpolation object. BicubicInterpolation(); + // TODO: use scaleFactors instead of outRowSize and outColSize + /** * The constructor for the Bicubic Interpolation. * diff --git a/src/mlpack/methods/ann/layer/not_adapted/bilinear_interpolation.hpp b/src/mlpack/methods/ann/layer/not_adapted/bilinear_interpolation.hpp index 7f2297f771..dcb182afc0 100644 --- a/src/mlpack/methods/ann/layer/not_adapted/bilinear_interpolation.hpp +++ b/src/mlpack/methods/ann/layer/not_adapted/bilinear_interpolation.hpp @@ -42,6 +42,8 @@ class BilinearInterpolationType : public Layer //! Create the BilinearInterpolationType object. BilinearInterpolationType(); + // TODO: use scaleFactors instead of outRowSize and outColSize + /** * The constructor for the Bilinear Interpolation. The input size will be set * by the given input when the layer is used. diff --git a/src/mlpack/methods/ann/layer/not_adapted/nearest_interpolation.hpp b/src/mlpack/methods/ann/layer/not_adapted/nearest_interpolation.hpp deleted file mode 100644 index 93fc6a09f0..0000000000 --- a/src/mlpack/methods/ann/layer/not_adapted/nearest_interpolation.hpp +++ /dev/null @@ -1,151 +0,0 @@ -/** - * @file methods/ann/layer/nearest_interpolation.hpp - * @author Abhinav Anand - * - * 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_NEAREST_INTERPOLATION_HPP -#define MLPACK_METHODS_ANN_LAYER_NEAREST_INTERPOLATION_HPP - -#include - -namespace mlpack { - -/** - * Definition and Implementation of the Nearest Interpolation Layer. - * - * Nearest interpolation is an mathematical technique, primarily used for - * scaling purposes. The input should be a 2D matrix and it can have - * a number of channels/units. - * - * @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 NearestInterpolation -{ - public: - //! Create the NearestInterpolation object. - NearestInterpolation(); - - /** - * The constructor for the NearestInterpolation. - * - * @param inRowSize Number of input rows. - * @param inColSize Number of input columns. - * @param outRowSize Number of output rows. - * @param outColSize Number of output columns. - * @param depth Number of input slices. - */ - NearestInterpolation(const size_t inRowSize, - const size_t inColSize, - const size_t outRowSize, - const size_t outColSize, - const size_t depth); - - /** - * Forward pass through the layer. The layer interpolates - * the matrix using the given Nearest Interpolation method. - * - * @param input The input matrix. - * @param output The resulting interpolated output matrix. - */ - template - void Forward(const arma::Mat& input, arma::Mat& output); - - /** - * Ordinary feed backward pass of a neural network, calculating the function - * f(x) by propagating x backwards through f. Using the results from the feed - * forward pass. Since the layer does not have any learn-able parameters, - * we just have to down-sample the gradient to make its size compatible with - * the input size. - * - * @param * (input) The input matrix. - * @param gradient The computed backward gradient. - * @param output The resulting down-sampled output. - */ - template - void Backward(const arma::Mat& /*input*/, - const arma::Mat& gradient, - arma::Mat& output); - - //! 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 row size of the input. - size_t const& InRowSize() const { return inRowSize; } - //! Modify the row size of the input. - size_t& InRowSize() { return inRowSize; } - - //! Get the column size of the input. - size_t const& InColSize() const { return inColSize; } - //! Modify the column size of the input. - size_t& InColSize() { return inColSize; } - - //! Get the row size of the output. - size_t const& OutRowSize() const { return outRowSize; } - //! Modify the row size of the output. - size_t& OutRowSize() { return outRowSize; } - - //! Get the column size of the output. - size_t const& OutColSize() const { return outColSize; } - //! Modify the column size of the output. - size_t& OutColSize() { return outColSize; } - - //! Get the depth of the input. - size_t const& InDepth() const { return depth; } - //! 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. - */ - template - void serialize(Archive& ar, const uint32_t /* version */); - - private: - //! Locally stored row size of the input. - size_t inRowSize; - //! Locally stored column size of the input. - size_t inColSize; - //! Locally stored row size of the output. - size_t outRowSize; - //! Locally stored column size of the input. - size_t outColSize; - //! Locally stored depth of the input. - size_t depth; - //! Locally stored number of input points. - size_t batchSize; - //! Locally-stored delta object. - OutputDataType delta; - //! Locally-stored output parameter object. - OutputDataType outputParameter; -}; // class NearestInterpolation - -} // namespace mlpack - -// Include implementation. -#include "nearest_interpolation_impl.hpp" - -#endif diff --git a/src/mlpack/methods/ann/layer/not_adapted/nearest_interpolation_impl.hpp b/src/mlpack/methods/ann/layer/not_adapted/nearest_interpolation_impl.hpp deleted file mode 100644 index 52a094b7fd..0000000000 --- a/src/mlpack/methods/ann/layer/not_adapted/nearest_interpolation_impl.hpp +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @file methods/ann/layer/nearest_interpolation_impl.hpp - * @author Abhinav Anand - * - * Implementation of the NearestInterpolation layer. - * - * 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_NEAREST_INTERPOLATION_IMPL_HPP -#define MLPACK_METHODS_ANN_LAYER_NEAREST_INTERPOLATION_IMPL_HPP - -// In case it hasn't yet been included. -#include "nearest_interpolation.hpp" - -namespace mlpack { - - -template -NearestInterpolation:: - NearestInterpolation(): - inRowSize(0), - inColSize(0), - outRowSize(0), - outColSize(0), - depth(0), - batchSize(0) -{ - // Nothing to do here. -} - -template -NearestInterpolation:: -NearestInterpolation(const size_t inRowSize, - const size_t inColSize, - const size_t outRowSize, - const size_t outColSize, - const size_t depth) : - inRowSize(inRowSize), - inColSize(inColSize), - outRowSize(outRowSize), - outColSize(outColSize), - depth(depth), - batchSize(0) -{ - // Nothing to do here. -} - -template -template -void NearestInterpolation::Forward( - const arma::Mat& input, arma::Mat& output) -{ - batchSize = input.n_cols; - if (output.is_empty()) - output.set_size(outRowSize * outColSize * depth, batchSize); - else - { - assert(output.n_rows == outRowSize * outColSize * depth); - assert(output.n_cols == batchSize); - } - - assert(inRowSize >= 2); - assert(inColSize >= 2); - - arma::cube inputAsCube(const_cast&>(input).memptr(), - inRowSize, inColSize, depth * batchSize, false, false); - arma::cube outputAsCube(output.memptr(), outRowSize, outColSize, - depth * batchSize, false, true); - - double scaleRow = (double) inRowSize / (double) outRowSize; - double scaleCol = (double) inColSize / (double) outColSize; - - for (size_t i = 0; i < outRowSize; ++i) - { - const size_t rOrigin = std::floor(i * scaleRow); - - for (size_t j = 0; j < outColSize; ++j) - { - const size_t cOrigin = std::floor(j * scaleCol); - - for (size_t k = 0; k < depth * batchSize; ++k) - { - outputAsCube(i, j, k) = inputAsCube.slice(k)( - rOrigin, cOrigin); - } - } - } -} - -template -template -void NearestInterpolation::Backward( - const arma::Mat& /*input*/, - const arma::Mat& gradient, - arma::Mat& output) -{ - if (output.is_empty()) - { - output.zeros(inRowSize * inColSize * depth, batchSize); - } - else - { - assert(output.n_rows == inRowSize * inColSize * depth); - assert(output.n_cols == batchSize); - } - - assert(outRowSize >= 2); - assert(outColSize >= 2); - - arma::cube outputAsCube(output.memptr(), inRowSize, inColSize, - depth * batchSize, false, true); - arma::cube gradientAsCube(((arma::Mat&) gradient).memptr(), outRowSize, - outColSize, depth * batchSize, false, false); - - double scaleRow = (double)(inRowSize) / outRowSize; - double scaleCol = (double)(inColSize) / outColSize; - - if (gradient.n_elem == output.n_elem) - { - outputAsCube = gradientAsCube; - } - else - { - for (size_t i = 0; i < outRowSize; ++i) - { - const size_t rOrigin = std::floor(i * scaleRow); - - for (size_t j = 0; j < outColSize; ++j) - { - const size_t cOrigin = std::floor(j * scaleCol); - - for (size_t k = 0; k < depth * batchSize; ++k) - { - outputAsCube(rOrigin, cOrigin, k) += - gradientAsCube(i, j, k); - } - } - } - } -} - -template -template -void NearestInterpolation::serialize( - Archive& ar, const uint32_t /* version */) -{ - ar(CEREAL_NVP(inRowSize)); - ar(CEREAL_NVP(inColSize)); - ar(CEREAL_NVP(outRowSize)); - ar(CEREAL_NVP(outColSize)); - ar(CEREAL_NVP(depth)); -} - -} // namespace mlpack - -#endif diff --git a/src/mlpack/methods/ann/layer/repeat_impl.hpp b/src/mlpack/methods/ann/layer/repeat_impl.hpp index ea5a83f93c..b71ff9377d 100644 --- a/src/mlpack/methods/ann/layer/repeat_impl.hpp +++ b/src/mlpack/methods/ann/layer/repeat_impl.hpp @@ -156,7 +156,7 @@ void RepeatType::ComputeOutputDimensions() // element to the input elements. This will be used in the backward // pass with a simple matrix multiplication. backIdxs.set_size(inputSize, sizeMult); - UintCol counts(inputSize, arma::fill::zeros); + UintCol counts(inputSize); for (size_t i = 0; i < outIdxs.n_elem; i++) { arma::uword r = outIdxs.at(i); diff --git a/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp b/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp index 5eb93017be..d1dbad52ab 100644 --- a/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp +++ b/src/mlpack/methods/ann/loss_functions/hinge_loss_impl.hpp @@ -31,9 +31,9 @@ typename MatType::elem_type HingeLossType::Forward( const MatType& target) { MatType temp = target - (target == 0); - MatType temp_zeros(size(target), arma::fill::zeros); + MatType tempZeros(size(target)); - MatType loss = max(temp_zeros, 1 - prediction % temp); + MatType loss = max(tempZeros, 1 - prediction % temp); typename MatType::elem_type lossSum = accu(loss); diff --git a/src/mlpack/methods/cf/decomposition_policies/svdplusplus_method.hpp b/src/mlpack/methods/cf/decomposition_policies/svdplusplus_method.hpp index 28741e1bfc..704d4d1687 100644 --- a/src/mlpack/methods/cf/decomposition_policies/svdplusplus_method.hpp +++ b/src/mlpack/methods/cf/decomposition_policies/svdplusplus_method.hpp @@ -96,7 +96,7 @@ class SVDPlusPlusPolicy { // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(h.n_rows, arma::fill::zeros); + arma::vec userVec(h.n_rows); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -124,7 +124,7 @@ class SVDPlusPlusPolicy { // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(h.n_rows, arma::fill::zeros); + arma::vec userVec(h.n_rows); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; diff --git a/src/mlpack/methods/cf/normalization/item_mean_normalization.hpp b/src/mlpack/methods/cf/normalization/item_mean_normalization.hpp index 47a7e1368a..80676491d6 100644 --- a/src/mlpack/methods/cf/normalization/item_mean_normalization.hpp +++ b/src/mlpack/methods/cf/normalization/item_mean_normalization.hpp @@ -49,9 +49,9 @@ class ItemMeanNormalization void Normalize(arma::mat& data) { const size_t itemNum = max(data.row(1)) + 1; - itemMean = arma::vec(itemNum, arma::fill::zeros); + itemMean = arma::vec(itemNum); // Number of ratings for each item. - arma::Row ratingNum(itemNum, arma::fill::zeros); + arma::Row ratingNum(itemNum); // Sum ratings for each item. data.each_col([&](arma::vec& datapoint) @@ -89,8 +89,8 @@ class ItemMeanNormalization void Normalize(arma::sp_mat& cleanedData) { // Calculate itemMean. - itemMean = arma::vec(cleanedData.n_rows, arma::fill::zeros); - arma::Col ratingNum(cleanedData.n_rows, arma::fill::zeros); + itemMean = arma::vec(cleanedData.n_rows); + arma::Col ratingNum(cleanedData.n_rows); arma::sp_mat::iterator it = cleanedData.begin(); arma::sp_mat::iterator it_end = cleanedData.end(); for (; it != it_end; ++it) diff --git a/src/mlpack/methods/cf/normalization/user_mean_normalization.hpp b/src/mlpack/methods/cf/normalization/user_mean_normalization.hpp index f9ab787bdb..3febb42ac3 100644 --- a/src/mlpack/methods/cf/normalization/user_mean_normalization.hpp +++ b/src/mlpack/methods/cf/normalization/user_mean_normalization.hpp @@ -49,9 +49,9 @@ class UserMeanNormalization void Normalize(arma::mat& data) { const size_t userNum = max(data.row(0)) + 1; - userMean = arma::vec(userNum, arma::fill::zeros); + userMean = arma::vec(userNum); // Number of ratings for each user. - arma::Row ratingNum(userNum, arma::fill::zeros); + arma::Row ratingNum(userNum); // Sum ratings for each user. data.each_col([&](arma::vec& datapoint) @@ -89,8 +89,8 @@ class UserMeanNormalization void Normalize(arma::sp_mat& cleanedData) { // Calculate userMean. - userMean = arma::vec(cleanedData.n_cols, arma::fill::zeros); - arma::Col ratingNum(cleanedData.n_cols, arma::fill::zeros); + userMean = arma::vec(cleanedData.n_cols); + arma::Col ratingNum(cleanedData.n_cols); arma::sp_mat::iterator it = cleanedData.begin(); arma::sp_mat::iterator it_end = cleanedData.end(); for (; it != it_end; ++it) diff --git a/src/mlpack/methods/dbscan/dbscan_impl.hpp b/src/mlpack/methods/dbscan/dbscan_impl.hpp index e2642de484..f6678bc2a8 100644 --- a/src/mlpack/methods/dbscan/dbscan_impl.hpp +++ b/src/mlpack/methods/dbscan/dbscan_impl.hpp @@ -112,7 +112,7 @@ size_t DBSCAN::Cluster( // Get a count of all clusters. const size_t numClusters = max(assignments) + 1; - arma::Col counts(numClusters, arma::fill::zeros); + arma::Col counts(numClusters); for (size_t i = 0; i < assignments.n_elem; ++i) counts[assignments[i]]++; diff --git a/src/mlpack/methods/decision_tree/decision_tree.hpp b/src/mlpack/methods/decision_tree/decision_tree.hpp index e8cc7d08ca..eec026c92f 100644 --- a/src/mlpack/methods/decision_tree/decision_tree.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree.hpp @@ -15,20 +15,9 @@ #include -#include "gini_gain.hpp" -#include "information_gain.hpp" -#include "mad_gain.hpp" -#include "mse_gain.hpp" - -#include "best_binary_numeric_split.hpp" -#include "random_binary_numeric_split.hpp" - -#include "best_binary_categorical_split.hpp" -#include "all_categorical_split.hpp" - -#include "all_dimension_select.hpp" -#include "random_dimension_select.hpp" -#include "multiple_random_dimension_select.hpp" +#include "fitness_functions/fitness_functions.hpp" +#include "split_functions/split_functions.hpp" +#include "select_functions/select_functions.hpp" namespace mlpack { diff --git a/src/mlpack/methods/decision_tree/decision_tree_impl.hpp b/src/mlpack/methods/decision_tree/decision_tree_impl.hpp index 09913c38d7..8d8776b959 100644 --- a/src/mlpack/methods/decision_tree/decision_tree_impl.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree_impl.hpp @@ -727,7 +727,7 @@ double DecisionTree childCounts(numChildren, arma::fill::zeros); + arma::Row childCounts(numChildren); for (size_t i = begin; i < begin + count; ++i) childCounts[childAssignments[i - begin]]++; diff --git a/src/mlpack/methods/decision_tree/decision_tree_regressor.hpp b/src/mlpack/methods/decision_tree/decision_tree_regressor.hpp index 67f50695a8..67c9b9ad9c 100644 --- a/src/mlpack/methods/decision_tree/decision_tree_regressor.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree_regressor.hpp @@ -15,12 +15,9 @@ #include -#include "mad_gain.hpp" -#include "mse_gain.hpp" -#include "best_binary_numeric_split.hpp" -#include "all_categorical_split.hpp" -#include "random_binary_numeric_split.hpp" -#include "all_dimension_select.hpp" +#include "fitness_functions/fitness_functions.hpp" +#include "split_functions/split_functions.hpp" +#include "select_functions/select_functions.hpp" namespace mlpack { diff --git a/src/mlpack/methods/decision_tree/decision_tree_regressor_impl.hpp b/src/mlpack/methods/decision_tree/decision_tree_regressor_impl.hpp index 99e932f892..e6f51c6b1e 100644 --- a/src/mlpack/methods/decision_tree/decision_tree_regressor_impl.hpp +++ b/src/mlpack/methods/decision_tree/decision_tree_regressor_impl.hpp @@ -708,7 +708,7 @@ double DecisionTreeRegressor childCounts(numChildren, arma::fill::zeros); + arma::Row childCounts(numChildren); for (size_t i = begin; i < begin + count; ++i) childCounts[childAssignments[i - begin]]++; diff --git a/src/mlpack/methods/decision_tree/fitness_functions/fitness_functions.hpp b/src/mlpack/methods/decision_tree/fitness_functions/fitness_functions.hpp new file mode 100644 index 0000000000..2ee82c29ca --- /dev/null +++ b/src/mlpack/methods/decision_tree/fitness_functions/fitness_functions.hpp @@ -0,0 +1,4 @@ +#include "gini_gain.hpp" +#include "information_gain.hpp" +#include "mad_gain.hpp" +#include "mse_gain.hpp" \ No newline at end of file diff --git a/src/mlpack/methods/decision_tree/gini_gain.hpp b/src/mlpack/methods/decision_tree/fitness_functions/gini_gain.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/gini_gain.hpp rename to src/mlpack/methods/decision_tree/fitness_functions/gini_gain.hpp index ea7d4a5407..37b9e4fda8 100644 --- a/src/mlpack/methods/decision_tree/gini_gain.hpp +++ b/src/mlpack/methods/decision_tree/fitness_functions/gini_gain.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/gini_gain.hpp + * @file methods/decision_tree/fitness_functions/gini_gain.hpp * @author Ryan Curtin * * The GiniGain class, which is a fitness function (FitnessFunction) for @@ -68,7 +68,7 @@ class GiniGain // Count the number of elements in each class. Use four auxiliary vectors // to exploit SIMD instructions if possible. - arma::vec countSpace(4 * numClasses, arma::fill::zeros); + arma::vec countSpace(4 * numClasses); arma::vec counts(countSpace.memptr(), numClasses, false, true); arma::vec counts2(countSpace.memptr() + numClasses, numClasses, false, true); diff --git a/src/mlpack/methods/decision_tree/information_gain.hpp b/src/mlpack/methods/decision_tree/fitness_functions/information_gain.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/information_gain.hpp rename to src/mlpack/methods/decision_tree/fitness_functions/information_gain.hpp index 7cf0f1158e..305931efd0 100644 --- a/src/mlpack/methods/decision_tree/information_gain.hpp +++ b/src/mlpack/methods/decision_tree/fitness_functions/information_gain.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/information_gain.hpp + * @file methods/decision_tree/fitness_functions/information_gain.hpp * @author Ryan Curtin * * An implementation of information gain, which can be used in place of Gini @@ -69,7 +69,7 @@ class InformationGain // Count the number of elements in each class. Use four auxiliary vectors // to exploit SIMD instructions if possible. - arma::vec countSpace(4 * numClasses, arma::fill::zeros); + arma::vec countSpace(4 * numClasses); arma::vec counts(countSpace.memptr(), numClasses, false, true); arma::vec counts2(countSpace.memptr() + numClasses, numClasses, false, true); diff --git a/src/mlpack/methods/decision_tree/mad_gain.hpp b/src/mlpack/methods/decision_tree/fitness_functions/mad_gain.hpp similarity index 97% rename from src/mlpack/methods/decision_tree/mad_gain.hpp rename to src/mlpack/methods/decision_tree/fitness_functions/mad_gain.hpp index 742700fb34..908af9b966 100644 --- a/src/mlpack/methods/decision_tree/mad_gain.hpp +++ b/src/mlpack/methods/decision_tree/fitness_functions/mad_gain.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/mad_gain.hpp + * @file methods/decision_tree/fitness_functions/mad_gain.hpp * @author Rishabh Garg * * The mean absolute deviation gain class, a fitness function for regression @@ -15,7 +15,7 @@ n. #define MLPACK_METHODS_DECISION_TREE_MAD_GAIN_HPP #include -#include "utils.hpp" +#include "mlpack/methods/decision_tree/utils.hpp" namespace mlpack { diff --git a/src/mlpack/methods/decision_tree/mse_gain.hpp b/src/mlpack/methods/decision_tree/fitness_functions/mse_gain.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/mse_gain.hpp rename to src/mlpack/methods/decision_tree/fitness_functions/mse_gain.hpp index 8e64a97a01..77687d2cd1 100644 --- a/src/mlpack/methods/decision_tree/mse_gain.hpp +++ b/src/mlpack/methods/decision_tree/fitness_functions/mse_gain.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/mse_gain.hpp + * @file methods/decision_tree/fitness_functions/mse_gain.hpp * @author Rishabh Garg * * The mean squared error gain class, which is a fitness funtion for @@ -14,7 +14,7 @@ #define MLPACK_METHODS_DECISION_TREE_MSE_GAIN_HPP #include -#include "utils.hpp" +#include namespace mlpack { diff --git a/src/mlpack/methods/decision_tree/all_dimension_select.hpp b/src/mlpack/methods/decision_tree/select_functions/all_dimension_select.hpp similarity index 95% rename from src/mlpack/methods/decision_tree/all_dimension_select.hpp rename to src/mlpack/methods/decision_tree/select_functions/all_dimension_select.hpp index 332a439fd8..5c59a6f60c 100644 --- a/src/mlpack/methods/decision_tree/all_dimension_select.hpp +++ b/src/mlpack/methods/decision_tree/select_functions/all_dimension_select.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/all_dimension_select.hpp + * @file methods/decision_tree/select_functions/all_dimension_select.hpp * @author Ryan Curtin * * Selects all dimensions for a split. diff --git a/src/mlpack/methods/decision_tree/multiple_random_dimension_select.hpp b/src/mlpack/methods/decision_tree/select_functions/multiple_random_dimension_select.hpp similarity index 96% rename from src/mlpack/methods/decision_tree/multiple_random_dimension_select.hpp rename to src/mlpack/methods/decision_tree/select_functions/multiple_random_dimension_select.hpp index 6ce277d258..d0c3fff36a 100644 --- a/src/mlpack/methods/decision_tree/multiple_random_dimension_select.hpp +++ b/src/mlpack/methods/decision_tree/select_functions/multiple_random_dimension_select.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/multiple_random_dimension_select.hpp + * @file methods/decision_tree/select_functions/multiple_random_dimension_select.hpp * @author Ryan Curtin * * Select a number of random dimensions to pick from. diff --git a/src/mlpack/methods/decision_tree/random_dimension_select.hpp b/src/mlpack/methods/decision_tree/select_functions/random_dimension_select.hpp similarity index 95% rename from src/mlpack/methods/decision_tree/random_dimension_select.hpp rename to src/mlpack/methods/decision_tree/select_functions/random_dimension_select.hpp index fdf024a70a..03a32f5fc2 100644 --- a/src/mlpack/methods/decision_tree/random_dimension_select.hpp +++ b/src/mlpack/methods/decision_tree/select_functions/random_dimension_select.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/random_dimension_select.hpp + * @file methods/decision_tree/select_functions/random_dimension_select.hpp * @author Ryan Curtin * * Selects one single random dimension to split on. diff --git a/src/mlpack/methods/decision_tree/select_functions/select_functions.hpp b/src/mlpack/methods/decision_tree/select_functions/select_functions.hpp new file mode 100644 index 0000000000..da857189f1 --- /dev/null +++ b/src/mlpack/methods/decision_tree/select_functions/select_functions.hpp @@ -0,0 +1,3 @@ +#include "all_dimension_select.hpp" +#include "multiple_random_dimension_select.hpp" +#include "random_dimension_select.hpp" \ No newline at end of file diff --git a/src/mlpack/methods/decision_tree/all_categorical_split.hpp b/src/mlpack/methods/decision_tree/split_functions/all_categorical_split.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/all_categorical_split.hpp rename to src/mlpack/methods/decision_tree/split_functions/all_categorical_split.hpp index 3165e6791e..d9e07e6a20 100644 --- a/src/mlpack/methods/decision_tree/all_categorical_split.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/all_categorical_split.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/all_categorical_split.hpp + * @file methods/decision_tree/split_functions/all_categorical_split.hpp * @author Ryan Curtin * * This file defines a tree splitter that split a categorical feature into all diff --git a/src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp b/src/mlpack/methods/decision_tree/split_functions/all_categorical_split_impl.hpp similarity index 95% rename from src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp rename to src/mlpack/methods/decision_tree/split_functions/all_categorical_split_impl.hpp index 6f0b67ef20..9fab1c3a8d 100644 --- a/src/mlpack/methods/decision_tree/all_categorical_split_impl.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/all_categorical_split_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/all_categorical_split_impl.hpp + * @file methods/decision_tree/split_functions/all_categorical_split_impl.hpp * @author Ryan Curtin * * Implementation of the AllCategoricalSplit categorical split class. @@ -32,7 +32,7 @@ double AllCategoricalSplit::SplitIfBetter( { // Count the number of elements in each potential child. const double epsilon = 1e-7; // Tolerance for floating-point errors. - arma::Col counts(numCategories, arma::fill::zeros); + arma::Col counts(numCategories); // If we are using weighted training, split the weights for each child too. arma::vec childWeightSums; @@ -58,7 +58,7 @@ double AllCategoricalSplit::SplitIfBetter( // Calculate the gain of the split. First we have to calculate the labels // that would be assigned to each child. - arma::uvec childPositions(numCategories, arma::fill::zeros); + arma::uvec childPositions(numCategories); std::vector> childLabels(numCategories); std::vector> childWeights(numCategories); @@ -129,7 +129,7 @@ double AllCategoricalSplit::SplitIfBetter( { // Count the number of elements in each potential child. const double epsilon = 1e-7; // Tolerance for floating-point errors. - arma::Col counts(numCategories, arma::fill::zeros); + arma::Col counts(numCategories); // If we are using weighted training, split the weights for each child too. arma::vec childWeightSums; @@ -155,7 +155,7 @@ double AllCategoricalSplit::SplitIfBetter( // Calculate the gain of the split. First we have to calculate the labels // that would be assigned to each child. - arma::uvec childPositions(numCategories, arma::fill::zeros); + arma::uvec childPositions(numCategories); std::vector childResponses(numCategories); std::vector childWeights(numCategories); diff --git a/src/mlpack/methods/decision_tree/best_binary_categorical_split.hpp b/src/mlpack/methods/decision_tree/split_functions/best_binary_categorical_split.hpp similarity index 100% rename from src/mlpack/methods/decision_tree/best_binary_categorical_split.hpp rename to src/mlpack/methods/decision_tree/split_functions/best_binary_categorical_split.hpp diff --git a/src/mlpack/methods/decision_tree/best_binary_categorical_split_impl.hpp b/src/mlpack/methods/decision_tree/split_functions/best_binary_categorical_split_impl.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/best_binary_categorical_split_impl.hpp rename to src/mlpack/methods/decision_tree/split_functions/best_binary_categorical_split_impl.hpp index c5be2c4de2..c00365568e 100644 --- a/src/mlpack/methods/decision_tree/best_binary_categorical_split_impl.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/best_binary_categorical_split_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/all_categorical_split_impl.hpp + * @file methods/decision_tree/split_functions/all_categorical_split_impl.hpp * @author Nikolay Apanasov (nikolay@apanasov.org) * * Implementation of the BestBinaryCategoricalSplit categorical split class. @@ -46,7 +46,7 @@ double BestBinaryCategoricalSplit::SplitIfBetter( { // Order the categories of variable vₖ by their proportion in class C₁ // and map each categorical vₖ to its categorical rank - arma::umat categoryCounts(numCategories, 2, arma::fill::zeros); + arma::umat categoryCounts(numCategories, 2); arma::vec categoryP(numCategories); size_t totalCount; @@ -172,8 +172,8 @@ double BestBinaryCategoricalSplit::SplitIfBetter( // Order the categories of variable vₖ by increasing mean // of the response y. categoryResponse[i, 0] will contain // the mean response for category Cᵢ. - arma::vec categoryResponse(numCategories, arma::fill::zeros); - arma::uvec categoryCounts(numCategories, arma::fill::zeros); + arma::vec categoryResponse(numCategories); + arma::uvec categoryCounts(numCategories); for (size_t i = 0; i < n; ++i) { diff --git a/src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp b/src/mlpack/methods/decision_tree/split_functions/best_binary_numeric_split.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp rename to src/mlpack/methods/decision_tree/split_functions/best_binary_numeric_split.hpp index bcaa27139e..79c18ee63f 100644 --- a/src/mlpack/methods/decision_tree/best_binary_numeric_split.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/best_binary_numeric_split.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/best_binary_numeric_split.hpp + * @file methods/decision_tree/split_functions/best_binary_numeric_split.hpp * @author Ryan Curtin * * A tree splitter that finds the best binary numeric split. @@ -13,7 +13,7 @@ #define MLPACK_METHODS_DECISION_TREE_BEST_BINARY_NUMERIC_SPLIT_HPP #include -#include "mse_gain.hpp" +#include #include diff --git a/src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp b/src/mlpack/methods/decision_tree/split_functions/best_binary_numeric_split_impl.hpp similarity index 99% rename from src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp rename to src/mlpack/methods/decision_tree/split_functions/best_binary_numeric_split_impl.hpp index b45ab2fabf..c98af633b6 100644 --- a/src/mlpack/methods/decision_tree/best_binary_numeric_split_impl.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/best_binary_numeric_split_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/best_binary_numeric_split_impl.hpp + * @file methods/decision_tree/split_functions/best_binary_numeric_split_impl.hpp * @author Ryan Curtin * * Implementation of strategy that finds the best binary numeric split. diff --git a/src/mlpack/methods/decision_tree/random_binary_numeric_split.hpp b/src/mlpack/methods/decision_tree/split_functions/random_binary_numeric_split.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/random_binary_numeric_split.hpp rename to src/mlpack/methods/decision_tree/split_functions/random_binary_numeric_split.hpp index ef01a8424b..0726862a7e 100644 --- a/src/mlpack/methods/decision_tree/random_binary_numeric_split.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/random_binary_numeric_split.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/random_binary_numeric_split.hpp + * @file methods/decision_tree/split_functions/random_binary_numeric_split.hpp * @author Rishabh Garg * * A tree splitter that finds a random binary numeric split. diff --git a/src/mlpack/methods/decision_tree/random_binary_numeric_split_impl.hpp b/src/mlpack/methods/decision_tree/split_functions/random_binary_numeric_split_impl.hpp similarity index 98% rename from src/mlpack/methods/decision_tree/random_binary_numeric_split_impl.hpp rename to src/mlpack/methods/decision_tree/split_functions/random_binary_numeric_split_impl.hpp index 10d92af69c..d89daa9706 100644 --- a/src/mlpack/methods/decision_tree/random_binary_numeric_split_impl.hpp +++ b/src/mlpack/methods/decision_tree/split_functions/random_binary_numeric_split_impl.hpp @@ -1,5 +1,5 @@ /** - * @file methods/decision_tree/random_binary_numeric_split_impl.hpp + * @file methods/decision_tree/split_functions/random_binary_numeric_split_impl.hpp * @author Rishabh Garg * * Implementation of strategy that finds the random binary numeric split. diff --git a/src/mlpack/methods/decision_tree/split_functions/split_functions.hpp b/src/mlpack/methods/decision_tree/split_functions/split_functions.hpp new file mode 100644 index 0000000000..63d1d5cb5a --- /dev/null +++ b/src/mlpack/methods/decision_tree/split_functions/split_functions.hpp @@ -0,0 +1,4 @@ +#include "all_categorical_split.hpp" +#include "best_binary_numeric_split.hpp" +#include "random_binary_numeric_split.hpp" +#include "best_binary_categorical_split.hpp" \ No newline at end of file diff --git a/src/mlpack/methods/kde/kde_rules_impl.hpp b/src/mlpack/methods/kde/kde_rules_impl.hpp index 81aeb18658..ec60ab0d37 100644 --- a/src/mlpack/methods/kde/kde_rules_impl.hpp +++ b/src/mlpack/methods/kde/kde_rules_impl.hpp @@ -56,11 +56,11 @@ KDERules::KDERules( scores(0) { // Initialize accumError. - accumError = arma::vec(querySet.n_cols, arma::fill::zeros); + accumError = arma::vec(querySet.n_cols); // Initialize accumMCAlpha only if Monte Carlo estimations are available. if (monteCarlo && kernelIsGaussian) - accumMCAlpha = arma::vec(querySet.n_cols, arma::fill::zeros); + accumMCAlpha = arma::vec(querySet.n_cols); } //! The base case. diff --git a/src/mlpack/methods/kmeans/hamerly_kmeans_impl.hpp b/src/mlpack/methods/kmeans/hamerly_kmeans_impl.hpp index d2e11b3304..0aa03562b7 100644 --- a/src/mlpack/methods/kmeans/hamerly_kmeans_impl.hpp +++ b/src/mlpack/methods/kmeans/hamerly_kmeans_impl.hpp @@ -50,6 +50,7 @@ double HamerlyKMeans::Iterate(const arma::mat& centroids, // Calculate minimum intra-cluster distance for each cluster. minClusterDistances.fill(DBL_MAX); + #pragma omp parallel for reduction(+:distanceCalculations) schedule(static) for (size_t i = 0; i < centroids.n_cols; ++i) { for (size_t j = i + 1; j < centroids.n_cols; ++j) @@ -59,13 +60,13 @@ double HamerlyKMeans::Iterate(const arma::mat& centroids, ++distanceCalculations; // Update bounds, if this intra-cluster distance is smaller. - if (dist < minClusterDistances(i)) - minClusterDistances(i) = dist; - if (dist < minClusterDistances(j)) - minClusterDistances(j) = dist; + minClusterDistances(i) = std::min(minClusterDistances(i), dist); + minClusterDistances(j) = std::min(minClusterDistances(j), dist); } } + #pragma omp parallel for reduction(+:hamerlyPruned,distanceCalculations) \ + reduction(matAdd:newCentroids) reduction(colAdd:counts) schedule(static) for (size_t i = 0; i < dataset.n_cols; ++i) { const double m = std::max(minClusterDistances(assignments[i]), @@ -132,6 +133,8 @@ double HamerlyKMeans::Iterate(const arma::mat& centroids, size_t furthestMovingCluster = 0; arma::vec centroidMovements(centroids.n_cols); double centroidMovement = 0.0; + #pragma omp parallel for reduction(+: distanceCalculations, centroidMovement) \ + schedule(static) for (size_t c = 0; c < centroids.n_cols; ++c) { if (counts(c) > 0) @@ -144,19 +147,23 @@ double HamerlyKMeans::Iterate(const arma::mat& centroids, centroidMovement += std::pow(movement, 2.0); ++distanceCalculations; - if (movement > furthestMovement) + #pragma omp critical { - secondFurthestMovement = furthestMovement; - furthestMovement = movement; - furthestMovingCluster = c; - } - else if (movement > secondFurthestMovement) - { - secondFurthestMovement = movement; + if (movement > furthestMovement) + { + secondFurthestMovement = furthestMovement; + furthestMovement = movement; + furthestMovingCluster = c; + } + else if (movement > secondFurthestMovement) + { + secondFurthestMovement = movement; + } } } // Now update bounds (lines 3-8 of Update-Bounds()). + #pragma omp parallel for schedule(static) for (size_t i = 0; i < dataset.n_cols; ++i) { upperBounds(i) += centroidMovements(assignments[i]); diff --git a/src/mlpack/methods/kmeans/naive_kmeans_impl.hpp b/src/mlpack/methods/kmeans/naive_kmeans_impl.hpp index 17a9ff58bd..e848259947 100644 --- a/src/mlpack/methods/kmeans/naive_kmeans_impl.hpp +++ b/src/mlpack/methods/kmeans/naive_kmeans_impl.hpp @@ -43,11 +43,10 @@ double NaiveKMeans::Iterate(const arma::mat& centroids, #pragma omp parallel { // The current state of the K-means is private for each thread - arma::mat localCentroids(centroids.n_rows, centroids.n_cols, - arma::fill::zeros); - arma::Col localCounts(centroids.n_cols, arma::fill::zeros); + arma::mat localCentroids(centroids.n_rows, centroids.n_cols); + arma::Col localCounts(centroids.n_cols); - #pragma omp for + #pragma omp for schedule(static) nowait for (size_t i = 0; i < (size_t) dataset.n_cols; ++i) { // Find the closest centroid to this point. @@ -57,7 +56,7 @@ double NaiveKMeans::Iterate(const arma::mat& centroids, for (size_t j = 0; j < centroids.n_cols; ++j) { const double dist = distance.Evaluate(dataset.col(i), - centroids.unsafe_col(j)); + centroids.col(j)); if (dist < minDistance) { minDistance = dist; @@ -68,7 +67,7 @@ double NaiveKMeans::Iterate(const arma::mat& centroids, Log::Assert(closestCluster != centroids.n_cols); // We now have the minimum distance centroid index. Update that centroid. - localCentroids.unsafe_col(closestCluster) += dataset.col(i); + localCentroids.col(closestCluster) += dataset.col(i); localCounts(closestCluster)++; } // Combine calculated state from each thread @@ -80,6 +79,7 @@ double NaiveKMeans::Iterate(const arma::mat& centroids, } // Now normalize the centroid. + #pragma omp parallel for schedule(static) for (size_t i = 0; i < centroids.n_cols; ++i) if (counts(i) != 0) newCentroids.col(i) /= counts(i); @@ -88,6 +88,7 @@ double NaiveKMeans::Iterate(const arma::mat& centroids, // Calculate cluster distortion for this iteration. double cNorm = 0.0; + #pragma omp parallel for reduction(+:cNorm) schedule(static) for (size_t i = 0; i < centroids.n_cols; ++i) { cNorm += std::pow(distance.Evaluate(centroids.col(i), newCentroids.col(i)), diff --git a/src/mlpack/methods/lars/lars_impl.hpp b/src/mlpack/methods/lars/lars_impl.hpp index c77a4306e5..eb1c260c2c 100644 --- a/src/mlpack/methods/lars/lars_impl.hpp +++ b/src/mlpack/methods/lars/lars_impl.hpp @@ -573,8 +573,8 @@ LARS::Train(const MatType& matX, isIgnored.resize(dataRef.n_cols, false); // Initialize yHat and beta. - arma::Col beta(dataRef.n_cols, arma::fill::zeros); - arma::Col yHat(dataRef.n_rows, arma::fill::zeros); + arma::Col beta(dataRef.n_cols); + arma::Col yHat(dataRef.n_rows); arma::Col yHatDirection(dataRef.n_rows, arma::fill::none); bool lassocond = false; diff --git a/src/mlpack/methods/lmnn/constraints.hpp b/src/mlpack/methods/lmnn/constraints.hpp index 082b8961ed..7704d239fe 100644 --- a/src/mlpack/methods/lmnn/constraints.hpp +++ b/src/mlpack/methods/lmnn/constraints.hpp @@ -27,12 +27,25 @@ namespace mlpack { * data point) and Triplets() (Generates sets of {dataset, target neighbors, * impostors} tripltets.) */ -template +template, + typename DistanceType = SquaredEuclideanDistance> class Constraints { public: //! Convenience typedef. - typedef NeighborSearch KNN; + typedef NeighborSearch KNN; + + // Convenience typedef for element type of data. + typedef typename MatType::elem_type ElemType; + // Convenience typedef for column vector of data. + typedef typename GetColType::type VecType; + // Convenience typedef for cube of data. + typedef typename GetCubeType::type CubeType; + // Convenience typedef for dense matrix of indices. + typedef typename GetUDenseMatType::type UMatType; + // Convenience typedef for dense vector of indices. + typedef typename GetColType::type UVecType; /** * Constructor for creating a Constraints instance. @@ -41,8 +54,8 @@ class Constraints * @param labels Input dataset labels. * @param k Number of target neighbors, impostors & triplets. */ - Constraints(const arma::mat& dataset, - const arma::Row& labels, + Constraints(const MatType& dataset, + const LabelsType& labels, const size_t k); /** @@ -54,10 +67,10 @@ class Constraints * @param labels Input dataset labels. * @param norms Input dataset norms. */ - void TargetNeighbors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms); + void TargetNeighbors(UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms); /** * Calculates k similar labeled nearest neighbors for a batch of dataset and @@ -70,10 +83,10 @@ class Constraints * @param begin Index of the initial point of dataset. * @param batchSize Number of data points to use. */ - void TargetNeighbors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, + void TargetNeighbors(UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, const size_t begin, const size_t batchSize); @@ -86,10 +99,10 @@ class Constraints * @param labels Input dataset labels. * @param norms Input dataset norms. */ - void Impostors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms); + void Impostors(UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms); /** * Calculates k differently labeled nearest neighbors & distances to @@ -101,11 +114,11 @@ class Constraints * @param labels Input dataset labels. * @param norms Input dataset norms. */ - void Impostors(arma::Mat& outputNeighbors, - arma::mat& outputDistance, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms); + void Impostors(UMatType& outputNeighbors, + MatType& outputDistance, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms); /** * Calculates k differently labeled nearest neighbors for a batch of dataset @@ -118,10 +131,10 @@ class Constraints * @param begin Index of the initial point of dataset. * @param batchSize Number of data points to use. */ - void Impostors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, + void Impostors(UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, const size_t begin, const size_t batchSize); @@ -137,11 +150,11 @@ class Constraints * @param begin Index of the initial point of dataset. * @param batchSize Number of data points to use. */ - void Impostors(arma::Mat& outputNeighbors, - arma::mat& outputDistance, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, + void Impostors(UMatType& outputNeighbors, + MatType& outputDistance, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, const size_t begin, const size_t batchSize); @@ -158,12 +171,12 @@ class Constraints * @param points Indices of data points to calculate impostors on. * @param numPoints Number of points to actually calculate impostors on. */ - void Impostors(arma::Mat& outputNeighbors, - arma::mat& outputDistance, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, - const arma::uvec& points, + void Impostors(UMatType& outputNeighbors, + MatType& outputDistance, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, + const UVecType& points, const size_t numPoints); /** @@ -175,10 +188,10 @@ class Constraints * @param labels Input dataset labels. * @param norms Input dataset norms. */ - void Triplets(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms); + void Triplets(UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms); //! Get the number of target neighbors (k). const size_t& K() const { return k; } @@ -195,13 +208,13 @@ class Constraints size_t k; //! Store unique labels. - arma::Row uniqueLabels; + LabelsType uniqueLabels; //! Store indices of data points having similar label. - std::vector indexSame; + std::vector indexSame; //! Store indices of data points having different label. - std::vector indexDiff; + std::vector indexDiff; //! False if nothing has ever been precalculated. bool precalculated; @@ -210,15 +223,15 @@ class Constraints * Precalculate the unique labels, and indices of similar * and different datapoints on the basis of labels. */ - inline void Precalculate(const arma::Row& labels); + inline void Precalculate(const LabelsType& labels); /** * Re-order neighbors on the basis of increasing norm in case * of ties among distances. */ - inline void ReorderResults(const arma::mat& distances, - arma::Mat& neighbors, - const arma::vec& norms); + inline void ReorderResults(const MatType& distances, + UMatType& neighbors, + const VecType& norms); }; } // namespace mlpack diff --git a/src/mlpack/methods/lmnn/constraints_impl.hpp b/src/mlpack/methods/lmnn/constraints_impl.hpp index 6f227fd970..74014f061e 100644 --- a/src/mlpack/methods/lmnn/constraints_impl.hpp +++ b/src/mlpack/methods/lmnn/constraints_impl.hpp @@ -17,10 +17,10 @@ namespace mlpack { -template -Constraints::Constraints( - const arma::mat& /* dataset */, - const arma::Row& labels, +template +Constraints::Constraints( + const MatType& /* dataset */, + const LabelsType& labels, const size_t k) : k(k), precalculated(false) @@ -36,11 +36,11 @@ Constraints::Constraints( } } -template -inline void Constraints::ReorderResults( - const arma::mat& distances, - arma::Mat& neighbors, - const arma::vec& norms) +template +inline void Constraints::ReorderResults( + const MatType& distances, + UMatType& neighbors, + const VecType& norms) { // Shortcut... if (neighbors.n_rows == 1) @@ -64,24 +64,21 @@ inline void Constraints::ReorderResults( if (start != end) { // We must sort these elements by norm. - arma::Col newNeighbors = - neighbors.col(i).subvec(start, end - 1); - arma::uvec indices = ConvTo::From(newNeighbors); - - arma::uvec order = arma::sort_index(norms.elem(indices)); - neighbors.col(i).subvec(start, end - 1) = - newNeighbors.elem(order); + UVecType indices = neighbors.col(i).subvec(start, end - 1); + UVecType order = arma::sort_index(norms.elem(indices)); + neighbors.col(i).subvec(start, end - 1) = indices.elem(order); } } } } // Calculates k similar labeled nearest neighbors. -template -void Constraints::TargetNeighbors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms) +template +void Constraints::TargetNeighbors( + UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms) { // Perform pre-calculation. If neccesary. Precalculate(labels); @@ -89,8 +86,8 @@ void Constraints::TargetNeighbors(arma::Mat& outputMatrix, // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -114,28 +111,29 @@ void Constraints::TargetNeighbors(arma::Mat& outputMatrix, // Calculates k similar labeled nearest neighbors on a // batch of data points. -template -void Constraints::TargetNeighbors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, - const size_t begin, - const size_t batchSize) +template +void Constraints::TargetNeighbors( + UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, + const size_t begin, + const size_t batchSize) { // Perform pre-calculation. If neccesary. Precalculate(labels); - arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1); - arma::Row sublabels = labels.cols(begin, begin + batchSize - 1); + MatType subDataset = dataset.cols(begin, begin + batchSize - 1); + LabelsType sublabels = labels.cols(begin, begin + batchSize - 1); // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; // Vectors to store indices. - arma::uvec subIndexSame; + UVecType subIndexSame; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -161,11 +159,12 @@ void Constraints::TargetNeighbors(arma::Mat& outputMatrix, } // Calculates k differently labeled nearest neighbors. -template -void Constraints::Impostors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms) +template +void Constraints::Impostors( + UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms) { // Perform pre-calculation. If neccesary. Precalculate(labels); @@ -173,8 +172,8 @@ void Constraints::Impostors(arma::Mat& outputMatrix, // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -198,12 +197,13 @@ void Constraints::Impostors(arma::Mat& outputMatrix, // Calculates k differently labeled nearest neighbors. The function // writes back calculated neighbors & distances to passed matrices. -template -void Constraints::Impostors(arma::Mat& outputNeighbors, - arma::mat& outputDistance, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms) +template +void Constraints::Impostors( + UMatType& outputNeighbors, + MatType& outputDistance, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms) { // Perform pre-calculation. If neccesary. Precalculate(labels); @@ -211,8 +211,8 @@ void Constraints::Impostors(arma::Mat& outputNeighbors, // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -237,28 +237,29 @@ void Constraints::Impostors(arma::Mat& outputNeighbors, // Calculates k differently labeled nearest neighbors on a // batch of data points. -template -void Constraints::Impostors(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, - const size_t begin, - const size_t batchSize) +template +void Constraints::Impostors( + UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, + const size_t begin, + const size_t batchSize) { // Perform pre-calculation. If neccesary. Precalculate(labels); - arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1); - arma::Row sublabels = labels.cols(begin, begin + batchSize - 1); + MatType subDataset = dataset.cols(begin, begin + batchSize - 1); + LabelsType sublabels = labels.cols(begin, begin + batchSize - 1); // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; // Vectors to store indices. - arma::uvec subIndexSame; + UVecType subIndexSame; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -285,29 +286,30 @@ void Constraints::Impostors(arma::Mat& outputMatrix, // Calculates k differently labeled nearest neighbors & distances on a // batch of data points. -template -void Constraints::Impostors(arma::Mat& outputNeighbors, - arma::mat& outputDistance, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, - const size_t begin, - const size_t batchSize) +template +void Constraints::Impostors( + UMatType& outputNeighbors, + MatType& outputDistance, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, + const size_t begin, + const size_t batchSize) { // Perform pre-calculation. If neccesary. Precalculate(labels); - arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1); - arma::Row sublabels = labels.cols(begin, begin + batchSize - 1); + MatType subDataset = dataset.cols(begin, begin + batchSize - 1); + LabelsType sublabels = labels.cols(begin, begin + batchSize - 1); // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; // Vectors to store indices. - arma::uvec subIndexSame; + UVecType subIndexSame; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -335,14 +337,15 @@ void Constraints::Impostors(arma::Mat& outputNeighbors, // Calculates k differently labeled nearest neighbors & distances over some // data points. -template -void Constraints::Impostors(arma::Mat& outputNeighbors, - arma::mat& outputDistance, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms, - const arma::uvec& points, - const size_t numPoints) +template +void Constraints::Impostors( + UMatType& outputNeighbors, + MatType& outputDistance, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms, + const UVecType& points, + const size_t numPoints) { // Perform pre-calculation. If neccesary. Precalculate(labels); @@ -350,11 +353,11 @@ void Constraints::Impostors(arma::Mat& outputNeighbors, // KNN instance. KNN knn; - arma::Mat neighbors; - arma::mat distances; + UMatType neighbors; + MatType distances; // Vectors to store indices. - arma::uvec subIndexSame; + UVecType subIndexSame; for (size_t i = 0; i < uniqueLabels.n_cols; ++i) { @@ -384,31 +387,35 @@ void Constraints::Impostors(arma::Mat& outputNeighbors, // Generates {data point, target neighbors, impostors} triplets using // TargetNeighbors() and Impostors(). -template -void Constraints::Triplets(arma::Mat& outputMatrix, - const arma::mat& dataset, - const arma::Row& labels, - const arma::vec& norms) +template +void Constraints::Triplets( + UMatType& outputMatrix, + const MatType& dataset, + const LabelsType& labels, + const VecType& norms) { // Perform pre-calculation. If neccesary. Precalculate(labels); size_t N = dataset.n_cols; - arma::Mat impostors(k, dataset.n_cols); + UMatType impostors(k, dataset.n_cols); Impostors(impostors, dataset, labels, norms); - arma::Mat targetNeighbors(k, dataset.n_cols);; + UMatType targetNeighbors(k, dataset.n_cols);; TargetNeighbors(targetNeighbors, dataset, labels, norms); - outputMatrix = arma::Mat(3, k * k * N , arma::fill::zeros); + outputMatrix = UMatType(3, k * k * N); - for (size_t i = 0, r = 0; i < N; ++i) + #pragma omp parallel for collapse(3) + for (size_t i = 0; i < N; ++i) { for (size_t j = 0; j < k; ++j) { - for (size_t l = 0; l < k; l++, r++) + for (size_t l = 0; l < k; l++) { + const size_t r = i * (k * k) + j * k + l; + // Generate triplets. outputMatrix(0, r) = i; outputMatrix(1, r) = targetNeighbors(j, i); @@ -418,9 +425,9 @@ void Constraints::Triplets(arma::Mat& outputMatrix, } } -template -inline void Constraints::Precalculate( - const arma::Row& labels) +template +inline void Constraints::Precalculate( + const LabelsType& labels) { // Make sure the calculation is necessary. if (precalculated) @@ -431,6 +438,7 @@ inline void Constraints::Precalculate( indexSame.resize(uniqueLabels.n_elem); indexDiff.resize(uniqueLabels.n_elem); + #pragma omp parallel for for (size_t i = 0; i < uniqueLabels.n_elem; ++i) { // Store same and diff indices. diff --git a/src/mlpack/methods/lmnn/lmnn.hpp b/src/mlpack/methods/lmnn/lmnn.hpp index 92646df11a..8dadc5b31e 100644 --- a/src/mlpack/methods/lmnn/lmnn.hpp +++ b/src/mlpack/methods/lmnn/lmnn.hpp @@ -49,7 +49,7 @@ namespace mlpack { * @tparam OptimizerType Optimizer to use for developing distance. */ template + typename DeprecatedOptimizerType = ens::AMSGrad> class LMNN { public: @@ -63,11 +63,27 @@ class LMNN * @param k Number of targets to consider. * @param distance Type of distance metric used for computation. */ + [[deprecated("Will be removed in mlpack 5.0.0. Pass the dataset directly to " + "LearnDistance() instead.")]] LMNN(const arma::mat& dataset, const arma::Row& labels, const size_t k, const DistanceType distance = DistanceType()); + /** + * Construct the LMNN object, optionally with an instantiated distance metric. + * + * @param k Number of target neighbors to consider. + * @param regularization Penalty to apply to objective function. + * @param updateInterval Number of iterations between each recomputation of + * true neighbors and impostors. + * @param distance Instantiated distance metric for computation. + */ + LMNN(const size_t k, + const double regularization = 0.5, + const size_t updateInterval = 1, + DistanceType distance = DistanceType()); + /** * Perform Large Margin Nearest Neighbors metric learning. The output @@ -80,25 +96,99 @@ class LMNN * @param callbacks Callback function for ensmallen optimizer `OptimizerType`. * See https://www.ensmallen.org/docs.html#callback-documentation. */ - template + template::value>::type, + typename = typename std::enable_if< + !FirstElementIsArma::value + >::type> + [[deprecated("Will be removed in mlpack 5.0.0. Use the version that takes a " + "dataset as a parameter.")]] void LearnDistance(arma::mat& outputMatrix, CallbackTypes&&... callbacks); + /** + * Perform Large Margin Nearest Neighbors metric learning. The output + * distance matrix is written into the passed reference. If the + * LearnDistance() is called with an outputMatrix with correct dimensions, + * then that matrix will be used as the starting point for optimization. + * + * @param dataset Dataset to learn distance metric on. + * @param labels Labels for dataset. + * @param outputMatrix Covariance matrix of Mahalanobis distance. + * @param callbacks Callback function for ensmallen optimizer `OptimizerType`. + * See https://www.ensmallen.org/docs.html#callback-documentation. + */ + template::type, + LMNNFunction, + MatType + >::value>::type, + typename = typename std::enable_if::value>::type> + void LearnDistance(const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + CallbackTypes&&... callbacks) const; + + /** + * Perform Large Margin Nearest Neighbors metric learning. The output + * distance matrix is written into the passed reference. If the + * LearnDistance() is called with an outputMatrix with correct dimensions, + * then that matrix will be used as the starting point for optimization. + * + * @param dataset Dataset to learn distance metric on. + * @param labels Labels for dataset. + * @param optimizer Instantiated ensmallen optimizer to use for LMNN. + * @param outputMatrix Covariance matrix of Mahalanobis distance. + * @param callbacks Callback function for ensmallen optimizer `OptimizerType`. + * See https://www.ensmallen.org/docs.html#callback-documentation. + */ + template, + MatType + >::value>::type> + void LearnDistance(const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + OptimizerType& optimizer, + CallbackTypes&&... callbacks) const; //! Get the dataset reference. - const arma::mat& Dataset() const { return dataset; } + [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() " + "version that takes the optimizer as a parameter instead.")]] + const arma::mat& Dataset() const { return *dataset; } //! Get the labels reference. - const arma::Row& Labels() const { return labels; } + [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() " + "version that takes the optimizer as a parameter instead.")]] + const arma::Row& Labels() const { return *labels; } //! Access the regularization value. const double& Regularization() const { return regularization; } //! Modify the regularization value. double& Regularization() { return regularization; } - //! Access the range value. - const size_t& Range() const { return range; } - //! Modify the range value. - size_t& Range() { return range; } + //! Access the iteration update interval value. + const size_t& UpdateInterval() const { return updateInterval; } + //! Modify the iteration update interval value. + size_t& UpdateInterval() { return updateInterval; } + + [[deprecated("Will be removed in mlpack 5.0.0. Use UpdateInterval() " + "instead.")]] + const size_t& Range() const { return updateInterval; } + [[deprecated("Will be removed in mlpack 5.0.0. Use UpdateInterval() " + "instead.")]] + size_t& Range() { return updateInterval; } //! Access the value of k. const size_t& K() const { return k; } @@ -106,15 +196,23 @@ class LMNN size_t K() { return k; } //! Get the optimizer. - const OptimizerType& Optimizer() const { return optimizer; } - OptimizerType& Optimizer() { return optimizer; } + [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() " + "version that takes the optimizer as a parameter instead.")]] + const DeprecatedOptimizerType& Optimizer() const { return optimizer; } + //! Modify the optimizer. + [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() " + "version that takes the optimizer as a parameter instead.")]] + DeprecatedOptimizerType& Optimizer() { return optimizer; } + + // Serialize the LMNN object. + template + void serialize(Archive& ar, const unsigned int /* version */); private: - //! Dataset reference. - const arma::mat& dataset; - - //! Labels reference. - const arma::Row& labels; + //! Dataset pointer (will be removed in mlpack 5.0.0). + const arma::mat* dataset; + //! Labels pointer (will be removed in mlpack 5.0.0). + const arma::Row* labels; //! Number of target points. size_t k; @@ -122,14 +220,14 @@ class LMNN //! Regularization value. double regularization; - //! Range after which impostors need to be recalculated. - size_t range; + //! Number of iterations after which impostors need to be recalculated. + size_t updateInterval; //! Distance to be used. DistanceType distance; - //! The optimizer to use. - OptimizerType optimizer; + //! The optimizer to use (will be removed in mlpack 5.0.0). + DeprecatedOptimizerType optimizer; }; // class LMNN } // namespace mlpack diff --git a/src/mlpack/methods/lmnn/lmnn_function.hpp b/src/mlpack/methods/lmnn/lmnn_function.hpp index f35fcf8cd0..e64b8ae0d0 100644 --- a/src/mlpack/methods/lmnn/lmnn_function.hpp +++ b/src/mlpack/methods/lmnn/lmnn_function.hpp @@ -41,9 +41,22 @@ namespace mlpack { * operate on one point in the dataset. This is useful for optimizers like * stochastic gradient descent (see ens::SGD). */ -template +template, + typename DistanceType = SquaredEuclideanDistance> class LMNNFunction { + // Convenience typedef for element type of data. + typedef typename MatType::elem_type ElemType; + // Convenience typedef for column vector of data. + typedef typename GetColType::type VecType; + // Convenience typedef for cube of data. + typedef typename GetCubeType::type CubeType; + // Convenience typedef for dense matrix of indices. + typedef typename GetUDenseMatType::type UMatType; + // Convenience typedef for dense vector of indices. + typedef typename GetColType::type UVecType; + public: /** * Constructor for LMNNFunction class. @@ -52,14 +65,14 @@ class LMNNFunction * @param labels Input dataset labels. * @param k Number of target neighbors to be used. * @param regularization Regularization value. - * @param range Range after which impostors need to be recalculated. + * @param updateInterval Number of iterations before impostors are recomputed. * @param distance Type of distance metric used for computation. */ - LMNNFunction(const arma::mat& dataset, - const arma::Row& labels, + LMNNFunction(const MatType& dataset, + const LabelsType& labels, size_t k, double regularization, - size_t range, + size_t updateInterval, DistanceType distance = DistanceType()); @@ -69,13 +82,13 @@ class LMNNFunction void Shuffle(); /** - * Evaluate the LMNN function for the given transformation matrix. This is the - * non-separable implementation, where the objective function is not + * Evaluate the LMNN function for the given transformation matrix. This is + * the non-separable implementation, where the objective function is not * decomposed into the sum of several objective functions. * * @param transformation Transformation matrix of Mahalanobis distance. */ - double Evaluate(const arma::mat& transformation); + ElemType Evaluate(const MatType& transformation); /** * Evaluate the LMNN objective function for the given transformation matrix on @@ -89,9 +102,9 @@ class LMNNFunction * @param begin Index of the initial point to use for objective function. * @param batchSize Number of points to use for objective function. */ - double Evaluate(const arma::mat& transformation, - const size_t begin, - const size_t batchSize = 1); + ElemType Evaluate(const MatType& transformation, + const size_t begin, + const size_t batchSize = 1); /** * Evaluate the gradient of the LMNN function for the given transformation @@ -103,7 +116,7 @@ class LMNNFunction * @param gradient Matrix to store the calculated gradient in. */ template - void Gradient(const arma::mat& transformation, GradType& gradient); + void Gradient(const MatType& transformation, GradType& gradient); /** * Evaluate the gradient of the LMNN function for the given transformation @@ -121,7 +134,7 @@ class LMNNFunction * @param batchSize Number of points to use for objective function. */ template - void Gradient(const arma::mat& transformation, + void Gradient(const MatType& transformation, const size_t begin, GradType& gradient, const size_t batchSize = 1); @@ -137,8 +150,8 @@ class LMNNFunction * @param gradient Matrix to store the calculated gradient in. */ template - double EvaluateWithGradient(const arma::mat& transformation, - GradType& gradient); + ElemType EvaluateWithGradient(const MatType& transformation, + GradType& gradient); /** * Evaluate the LMNN objective function together with gradient for the given @@ -156,13 +169,13 @@ class LMNNFunction * @param batchSize Number of points to use for objective function. */ template - double EvaluateWithGradient(const arma::mat& transformation, - const size_t begin, - GradType& gradient, - const size_t batchSize = 1); + ElemType EvaluateWithGradient(const MatType& transformation, + const size_t begin, + GradType& gradient, + const size_t batchSize = 1); //! Return the initial point for the optimization. - const arma::mat& GetInitialPoint() const { return initialPoint; } + const MatType& GetInitialPoint() const { return initialPoint; } /** * Get the number of functions the objective function can be decomposed into. @@ -171,7 +184,7 @@ class LMNNFunction size_t NumFunctions() const { return dataset.n_cols; } //! Return the dataset passed into the constructor. - const arma::mat& Dataset() const { return dataset; } + const MatType& Dataset() const { return dataset; } //! Access the regularization value. const double& Regularization() const { return regularization; } @@ -183,26 +196,26 @@ class LMNNFunction //! Modify the value of k. size_t& K() { return k; } - //! Access the value of range. - const size_t& Range() const { return range; } - //! Modify the value of k. - size_t& Range() { return range; } + //! Access the number of iterations between impostor recomputation. + const size_t& UpdateInterval() const { return updateInterval; } + //! Modify the number of iterations between impostor recomputation.. + size_t& UpdateInterval() { return updateInterval; } private: //! data. This will be an alias until Shuffle() is called. - arma::mat dataset; + MatType dataset; //! labels. This will be an alias until Shuffle() is called. - arma::Row labels; + LabelsType labels; //! Initial parameter point. - arma::mat initialPoint; + MatType initialPoint; //! Store transformed dataset. - arma::mat transformedDataset; + MatType transformedDataset; //! Store target neighbors of data points. - arma::Mat targetNeighbors; + UMatType targetNeighbors; //! Initial impostors. - arma::Mat impostors; + UMatType impostors; //! Cache distance. Used to avoid repetive calculation. - arma::mat distanceMat; + MatType distanceMat; //! Number of target neighbors. size_t k; //! The instantiated distance metric. @@ -211,28 +224,28 @@ class LMNNFunction double regularization; //! Keep iterations count. size_t iteration; - //! Range after which impostors need to be recalculated. - size_t range; + //! Number of iterations before impostors need to be recalculated. + size_t updateInterval; //! Constraints Object. - Constraints constraint; + Constraints constraint; //! Holds pre-calculated cij. - arma::mat pCij; + MatType pCij; //! Holds the norm of each data point. - arma::vec norm; + VecType norm; //! Hold previous eval values for each datapoint. - arma::cube evalOld; + CubeType evalOld; //! Hold previous maximum norm of impostor. - arma::mat maxImpNorm; + MatType maxImpNorm; //! Holds previous transformation matrix. Used for L-BFGS like optimizer. - arma::mat transformationOld; + MatType transformationOld; //! Holds previous transformation matrices. - std::vector oldTransformationMatrices; + std::vector oldTransformationMatrices; //! Holds number of points which are using each transformation matrix. std::vector oldTransformationCounts; //! Holds points to transformation matrix mapping. - arma::vec lastTransformationIndices; + VecType lastTransformationIndices; //! Used for storing points to re-calculate impostors for. - arma::uvec points; + UVecType points; //! Flag for controlling use of bounds over impostors. bool impBounds; /** @@ -242,12 +255,12 @@ class LMNNFunction */ inline void Precalculate(); //! Update cache transformation matrices. - inline void UpdateCache(const arma::mat& transformation, + inline void UpdateCache(const MatType& transformation, const size_t begin, const size_t batchSize); //! Calculate norm of change in transformation. - inline void TransDiff(std::map& transformationDiffs, - const arma::mat& transformation, + inline void TransDiff(std::unordered_map& transDiffs, + const MatType& transformation, const size_t begin, const size_t batchSize); }; diff --git a/src/mlpack/methods/lmnn/lmnn_function_impl.hpp b/src/mlpack/methods/lmnn/lmnn_function_impl.hpp index 01aa77afea..58dc54011c 100644 --- a/src/mlpack/methods/lmnn/lmnn_function_impl.hpp +++ b/src/mlpack/methods/lmnn/lmnn_function_impl.hpp @@ -18,18 +18,19 @@ namespace mlpack { -template -LMNNFunction::LMNNFunction(const arma::mat& datasetIn, - const arma::Row& labelsIn, - size_t k, - double regularization, - size_t range, - DistanceType distance) : +template +LMNNFunction::LMNNFunction( + const MatType& datasetIn, + const LabelsType& labelsIn, + size_t k, + double regularization, + size_t updateInterval, + DistanceType distance) : k(k), distance(distance), regularization(regularization), iteration(0), - range(range), + updateInterval(updateInterval), constraint(datasetIn, labelsIn, k), points(datasetIn.n_cols), impBounds(false) @@ -60,7 +61,7 @@ LMNNFunction::LMNNFunction(const arma::mat& datasetIn, lastTransformationIndices.zeros(); // Reserve the first element of cache. - arma::mat emptyMat; + MatType emptyMat; oldTransformationMatrices.push_back(emptyMat); oldTransformationCounts.push_back(dataset.n_cols); @@ -92,18 +93,18 @@ LMNNFunction::LMNNFunction(const arma::mat& datasetIn, } //! Shuffle the dataset. -template -void LMNNFunction::Shuffle() +template +void LMNNFunction::Shuffle() { - arma::mat newDataset = dataset; - arma::Mat newLabels = labels; - arma::cube newEvalOld = evalOld; - arma::vec newlastTransformationIndices = lastTransformationIndices; - arma::mat newMaxImpNorm = maxImpNorm; - arma::vec newNorm = norm; + MatType newDataset = dataset; + LabelsType newLabels = labels; + CubeType newEvalOld = evalOld; + VecType newlastTransformationIndices = lastTransformationIndices; + MatType newMaxImpNorm = maxImpNorm; + VecType newNorm = norm; // Generate ordering. - arma::uvec ordering = arma::shuffle(arma::linspace(0, + UVecType ordering = arma::shuffle(arma::linspace(0, dataset.n_cols - 1, dataset.n_cols)); ClearAlias(dataset); @@ -126,9 +127,9 @@ void LMNNFunction::Shuffle() } // Update cache transformation matrices. -template -inline void LMNNFunction::UpdateCache( - const arma::mat& transformation, +template +inline void LMNNFunction::UpdateCache( + const MatType& transformation, const size_t begin, const size_t batchSize) { @@ -162,31 +163,13 @@ inline void LMNNFunction::UpdateCache( } oldTransformationCounts[index] += batchSize; - - #ifdef DEBUG - size_t total = 0; - for (size_t i = 1; i < oldTransformationCounts.size(); ++i) - { - std::ostringstream oss; - oss << "transformation counts for matrix " << i - << " invalid (" << oldTransformationCounts[i] << ")!"; - Log::Assert(oldTransformationCounts[i] <= dataset.n_cols, oss.str()); - total += oldTransformationCounts[i]; - } - - std::ostringstream oss; - oss << "total count for transformation matrices invalid (" << total - << ", " << "should be " << dataset.n_cols << "!"; - if (begin + batchSize == dataset.n_cols) - Log::Assert(total == dataset.n_cols, oss.str()); - #endif } // Calculate norm of change in transformation. -template -inline void LMNNFunction::TransDiff( - std::map& transformationDiffs, - const arma::mat& transformation, +template +inline void LMNNFunction::TransDiff( + std::unordered_map& transformationDiffs, + const MatType& transformation, const size_t begin, const size_t batchSize) { @@ -209,22 +192,24 @@ inline void LMNNFunction::TransDiff( } //! Evaluate cost over whole dataset. -template -double LMNNFunction::Evaluate(const arma::mat& transformation) +template +typename MatType::elem_type +LMNNFunction::Evaluate( + const MatType& transformation) { - double cost = 0; + ElemType cost = 0; // Apply distance metric over dataset. transformedDataset = transformation * dataset; - double transformationDiff = 0; + ElemType transformationDiff = 0; if (!transformationOld.is_empty()) { // Calculate norm of change in transformation. transformationDiff = arma::norm(transformation - transformationOld); } - if (!transformationOld.is_empty() && iteration++ % range == 0) + if (!transformationOld.is_empty() && iteration++ % updateInterval == 0) { if (impBounds) { @@ -251,7 +236,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation) norm); } } - else if (iteration++ % range == 0) + else if (iteration++ % updateInterval == 0) { // Re-calculate impostors on transformed dataset. constraint.Impostors(impostors, distanceMat, transformedDataset, labels, @@ -263,7 +248,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation) for (size_t j = 0; j < k ; ++j) { // Calculate cost due to distance between target neighbors & data point. - double eval = distance.Evaluate(transformedDataset.col(i), + ElemType eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))); cost += (1 - regularization) * eval; } @@ -276,7 +261,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation) { // Calculate cost due to {data point, target neighbors, impostors} // triplets. - double eval = 0; + ElemType eval = 0; // Bounds for eval. if (!transformationOld.is_empty() && evalOld(l, j, i) < -1) @@ -292,7 +277,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation) // Calculate exact eval value. if (eval > -1) { - if (iteration - 1 % range == 0) + if (iteration - 1 % updateInterval == 0) { eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - @@ -338,21 +323,23 @@ double LMNNFunction::Evaluate(const arma::mat& transformation) } //! Calculate cost over batches. -template -double LMNNFunction::Evaluate(const arma::mat& transformation, - const size_t begin, - const size_t batchSize) +template +typename MatType::elem_type +LMNNFunction::Evaluate( + const MatType& transformation, + const size_t begin, + const size_t batchSize) { - double cost = 0; + ElemType cost = 0; // Calculate norm of change in transformation. - std::map transformationDiffs; + std::unordered_map transformationDiffs; TransDiff(transformationDiffs, transformation, begin, batchSize); // Apply distance metric over dataset. transformedDataset = transformation * dataset; - if (impBounds && iteration++ % range == 0) + if (impBounds && iteration++ % updateInterval == 0) { // Track number of data points to use for impostors calculatiom. size_t numPoints = 0; @@ -378,7 +365,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation, constraint.Impostors(impostors, distanceMat, transformedDataset, labels, norm, points, numPoints); } - else if (iteration++ % range == 0) + else if (iteration++ % updateInterval == 0) { // Re-calculate impostors on transformed dataset. constraint.Impostors(impostors, distanceMat, transformedDataset, labels, @@ -390,7 +377,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation, for (size_t j = 0; j < k ; ++j) { // Calculate cost due to distance between target neighbors & data point. - double eval = distance.Evaluate(transformedDataset.col(i), + ElemType eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))); cost += (1 - regularization) * eval; } @@ -403,7 +390,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation, { // Calculate cost due to {data point, target neighbors, impostors} // triplets. - double eval = 0; + ElemType eval = 0; // Bounds for eval. if (lastTransformationIndices(i) && evalOld(l, j, i) < -1) @@ -419,7 +406,7 @@ double LMNNFunction::Evaluate(const arma::mat& transformation, // Calculate exact eval value. if (eval > -1) { - if (iteration - 1 % range == 0) + if (iteration - 1 % updateInterval == 0) { eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - @@ -467,16 +454,16 @@ double LMNNFunction::Evaluate(const arma::mat& transformation, } //! Compute gradient over whole dataset. -template +template template -void LMNNFunction::Gradient(const arma::mat& transformation, - GradType& gradient) +void LMNNFunction::Gradient( + const MatType& transformation, GradType& gradient) { // Apply distance metric over dataset. transformedDataset = transformation * dataset; - double transformationDiff = 0; - if (!transformationOld.is_empty() && iteration++ % range == 0) + ElemType transformationDiff = 0; + if (!transformationOld.is_empty() && iteration++ % updateInterval == 0) { // Calculate norm of change in transformation. transformationDiff = arma::norm(transformation - transformationOld); @@ -506,7 +493,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, norm); } } - else if (iteration++ % range == 0) + else if (iteration++ % updateInterval == 0) { // Re-calculate impostors on transformed dataset. constraint.Impostors(impostors, distanceMat, transformedDataset, labels, @@ -516,10 +503,10 @@ void LMNNFunction::Gradient(const arma::mat& transformation, gradient.zeros(transformation.n_rows, transformation.n_cols); // Calculate gradient due to target neighbors. - arma::mat cij = pCij; + MatType cij = pCij; // Calculate gradient due to impostors. - arma::mat cil = zeros(dataset.n_rows, dataset.n_rows); + MatType cil = zeros(dataset.n_rows, dataset.n_rows); for (size_t i = 0; i < dataset.n_cols; ++i) { @@ -530,7 +517,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, { // Calculate cost due to {data point, target neighbors, impostors} // triplets. - double eval = 0; + ElemType eval = 0; // Bounds for eval. if (!transformationOld.is_empty() && evalOld(l, j, i) < -1) @@ -546,7 +533,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, // Calculate exact eval value. if (eval > -1) { - if (iteration - 1 % range == 0) + if (iteration - 1 % updateInterval == 0) { eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - @@ -581,7 +568,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, } // Caculate gradient due to impostors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); cil += diff * trans(diff); diff = dataset.col(i) - dataset.col(impostors(l, i)); @@ -598,21 +585,22 @@ void LMNNFunction::Gradient(const arma::mat& transformation, } //! Compute gradient over a batch of data points. -template +template template -void LMNNFunction::Gradient(const arma::mat& transformation, - const size_t begin, - GradType& gradient, - const size_t batchSize) +void LMNNFunction::Gradient( + const MatType& transformation, + const size_t begin, + GradType& gradient, + const size_t batchSize) { // Apply distance metric over dataset. transformedDataset = transformation * dataset; // Calculate norm of change in transformation. - std::map transformationDiffs; + std::unordered_map transformationDiffs; TransDiff(transformationDiffs, transformation, begin, batchSize); - if (impBounds && iteration++ % range == 0) + if (impBounds && iteration++ % updateInterval == 0) { // Track number of data points to use for impostors calculatiom. size_t numPoints = 0; @@ -638,7 +626,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, constraint.Impostors(impostors, distanceMat, transformedDataset, labels, norm, points, numPoints); } - else if (iteration++ % range == 0) + else if (iteration++ % updateInterval == 0) { // Re-calculate impostors on transformed dataset. constraint.Impostors(impostors, distanceMat, transformedDataset, labels, @@ -647,15 +635,15 @@ void LMNNFunction::Gradient(const arma::mat& transformation, gradient.zeros(transformation.n_rows, transformation.n_cols); - arma::mat cij = zeros(dataset.n_rows, dataset.n_rows); - arma::mat cil = zeros(dataset.n_rows, dataset.n_rows); + MatType cij = zeros(dataset.n_rows, dataset.n_rows); + MatType cil = zeros(dataset.n_rows, dataset.n_rows); for (size_t i = begin; i < begin + batchSize; ++i) { for (size_t j = 0; j < k ; ++j) { // Calculate gradient due to target neighbors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); cij += diff * trans(diff); } @@ -666,7 +654,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, { // Calculate cost due to {data point, target neighbors, impostors} // triplets. - double eval = 0; + ElemType eval = 0; // Bounds for eval. if (lastTransformationIndices(i) && evalOld(l, j, i) < -1) @@ -682,7 +670,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, // Calculate exact eval value. if (eval > -1) { - if (iteration - 1 % range == 0) + if (iteration - 1 % updateInterval == 0) { eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - @@ -719,7 +707,7 @@ void LMNNFunction::Gradient(const arma::mat& transformation, } // Caculate gradient due to impostors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); cil += diff * trans(diff); diff = dataset.col(i) - dataset.col(impostors(l, i)); @@ -736,25 +724,26 @@ void LMNNFunction::Gradient(const arma::mat& transformation, } //! Compute cost & gradient over whole dataset. -template +template template -double LMNNFunction::EvaluateWithGradient( - const arma::mat& transformation, +typename MatType::elem_type +LMNNFunction::EvaluateWithGradient( + const MatType& transformation, GradType& gradient) { - double cost = 0; + ElemType cost = 0; // Apply distance metric over dataset. transformedDataset = transformation * dataset; - double transformationDiff = 0; + ElemType transformationDiff = 0; if (!transformationOld.is_empty()) { // Calculate norm of change in transformation. transformationDiff = arma::norm(transformation - transformationOld); } - if (!transformationOld.is_empty() && iteration++ % range == 0) + if (!transformationOld.is_empty() && iteration++ % updateInterval == 0) { if (impBounds) { @@ -781,7 +770,7 @@ double LMNNFunction::EvaluateWithGradient( norm); } } - else if (iteration++ % range == 0) + else if (iteration++ % updateInterval == 0) { // Re-calculate impostors on transformed dataset. constraint.Impostors(impostors, distanceMat, transformedDataset, labels, @@ -791,17 +780,17 @@ double LMNNFunction::EvaluateWithGradient( gradient.zeros(transformation.n_rows, transformation.n_cols); // Calculate gradient due to target neighbors. - arma::mat cij = pCij; + MatType cij = pCij; // Calculate gradient due to impostors. - arma::mat cil = zeros(dataset.n_rows, dataset.n_rows); + MatType cil = zeros(dataset.n_rows, dataset.n_rows); for (size_t i = 0; i < dataset.n_cols; ++i) { for (size_t j = 0; j < k ; ++j) { // Calculate cost due to distance between target neighbors & data point. - double eval = distance.Evaluate(transformedDataset.col(i), + ElemType eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))); cost += (1 - regularization) * eval; } @@ -813,7 +802,7 @@ double LMNNFunction::EvaluateWithGradient( { // Calculate cost due to {data point, target neighbors, impostors} // triplets. - double eval = 0; + ElemType eval = 0; // Bounds for eval. if (!transformationOld.is_empty() && evalOld(l, j, i) < -1) @@ -829,7 +818,7 @@ double LMNNFunction::EvaluateWithGradient( // Calculate exact eval value. if (eval > -1) { - if (iteration - 1 % range == 0) + if (iteration - 1 % updateInterval == 0) { eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - @@ -858,7 +847,7 @@ double LMNNFunction::EvaluateWithGradient( cost += regularization * (1 + eval); // Caculate gradient due to impostors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); cil += diff * trans(diff); diff = dataset.col(i) - dataset.col(impostors(l, i)); @@ -877,24 +866,25 @@ double LMNNFunction::EvaluateWithGradient( } //! Compute cost & gradient over a batch of data points. -template +template template -double LMNNFunction::EvaluateWithGradient( - const arma::mat& transformation, +typename MatType::elem_type +LMNNFunction::EvaluateWithGradient( + const MatType& transformation, const size_t begin, GradType& gradient, const size_t batchSize) { - double cost = 0; + ElemType cost = 0; // Calculate norm of change in transformation. - std::map transformationDiffs; + std::unordered_map transformationDiffs; TransDiff(transformationDiffs, transformation, begin, batchSize); // Apply distance metric over dataset. transformedDataset = transformation * dataset; - if (impBounds && iteration++ % range == 0) + if (impBounds && iteration++ % updateInterval == 0) { // Track number of data points to use for impostors calculatiom. size_t numPoints = 0; @@ -920,7 +910,7 @@ double LMNNFunction::EvaluateWithGradient( constraint.Impostors(impostors, distanceMat, transformedDataset, labels, norm, points, numPoints); } - else if (iteration++ % range == 0) + else if (iteration++ % updateInterval == 0) { // Re-calculate impostors on transformed dataset. constraint.Impostors(impostors, distanceMat, transformedDataset, labels, @@ -929,20 +919,20 @@ double LMNNFunction::EvaluateWithGradient( gradient.zeros(transformation.n_rows, transformation.n_cols); - arma::mat cij = zeros(dataset.n_rows, dataset.n_rows); - arma::mat cil = zeros(dataset.n_rows, dataset.n_rows); + MatType cij = zeros(dataset.n_rows, dataset.n_rows); + MatType cil = zeros(dataset.n_rows, dataset.n_rows); for (size_t i = begin; i < begin + batchSize; ++i) { for (size_t j = 0; j < k ; ++j) { // Calculate cost due to distance between target neighbors & data point. - double eval = distance.Evaluate(transformedDataset.col(i), + ElemType eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))); cost += (1 - regularization) * eval; // Calculate gradient due to target neighbors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); cij += diff * trans(diff); } @@ -953,7 +943,7 @@ double LMNNFunction::EvaluateWithGradient( { // Calculate cost due to {data point, target neighbors, impostors} // triplets. - double eval = 0; + ElemType eval = 0; // Bounds for eval. if (lastTransformationIndices(i) && evalOld(l, j, i) < -1) @@ -969,7 +959,7 @@ double LMNNFunction::EvaluateWithGradient( // Calculate exact eval value. if (eval > -1) { - if (iteration - 1 % range == 0) + if (iteration - 1 % updateInterval == 0) { eval = distance.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - @@ -998,7 +988,7 @@ double LMNNFunction::EvaluateWithGradient( cost += regularization * (1 + eval); // Caculate gradient due to impostors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); cil += diff * trans(diff); diff = dataset.col(i) - dataset.col(impostors(l, i)); @@ -1016,8 +1006,8 @@ double LMNNFunction::EvaluateWithGradient( return cost; } -template -inline void LMNNFunction::Precalculate() +template +inline void LMNNFunction::Precalculate() { pCij.zeros(dataset.n_rows, dataset.n_rows); @@ -1026,7 +1016,7 @@ inline void LMNNFunction::Precalculate() for (size_t j = 0; j < k ; ++j) { // Calculate gradient due to target neighbors. - arma::vec diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); + VecType diff = dataset.col(i) - dataset.col(targetNeighbors(j, i)); pCij += diff * trans(diff); } } diff --git a/src/mlpack/methods/lmnn/lmnn_impl.hpp b/src/mlpack/methods/lmnn/lmnn_impl.hpp index 740a3a6c0c..5dfc87bb78 100644 --- a/src/mlpack/methods/lmnn/lmnn_impl.hpp +++ b/src/mlpack/methods/lmnn/lmnn_impl.hpp @@ -21,27 +21,83 @@ namespace mlpack { * Takes in a reference to the dataset. Copies the data, initializes * all of the member variables and constraint object and generate constraints. */ -template -LMNN::LMNN(const arma::mat& dataset, - const arma::Row& labels, - const size_t k, - const DistanceType distance) : - dataset(dataset), - labels(labels), +template +LMNN::LMNN( + const arma::mat& dataset, + const arma::Row& labels, + const size_t k, + const DistanceType distance) : + dataset(&dataset), + labels(&labels), k(k), regularization(0.5), - range(1), + updateInterval(1), distance(distance) { /* nothing to do */ } -template -template -void LMNN::LearnDistance(arma::mat& outputMatrix, +template +LMNN::LMNN( + const size_t k, + const double regularization, + const size_t updateInterval, + const DistanceType distance) : + k(k), + regularization(regularization), + updateInterval(updateInterval), + distance(distance) +{ /* nothing to do */ } + +template +template +void LMNN::LearnDistance( + arma::mat& outputMatrix, CallbackTypes&&... callbacks) +{ + if (!dataset || !labels) + { + throw std::runtime_error("LMNN::LearnDistance(): cannot call without a " + "dataset!"); + } + + LearnDistance(*dataset, *labels, outputMatrix, optimizer, + std::forward(callbacks)...); +} + +template +template +void LMNN::LearnDistance( + const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + CallbackTypes&&... callbacks) const +{ + // This should be replaced with ens::StandardSGD when the deprecated members + // are removed for mlpack 5.0.0. + DeprecatedOptimizerType opt; + LearnDistance(dataset, labels, outputMatrix, opt, + std::forward(callbacks)...); +} + +template +template +void LMNN::LearnDistance( + const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + OptimizerType& opt, + CallbackTypes&&... callbacks) const { // LMNN objective function. - LMNNFunction objFunction(dataset, labels, k, - regularization, range); + LMNNFunction objFunction(dataset, labels, + k, regularization, updateInterval); // See if we were passed an initialized matrix. outputMatrix (L) must be // having r x d dimensionality. @@ -49,15 +105,23 @@ void LMNN::LearnDistance(arma::mat& outputMatrix, (outputMatrix.n_rows > dataset.n_rows) || !(arma::is_finite(outputMatrix))) { - Log::Info << "Initial learning point have invalid dimensionality. " - "Identity matrix will be used as initial learning point for " - "optimization." << std::endl; outputMatrix.eye(dataset.n_rows, dataset.n_rows); } - optimizer.Optimize(objFunction, outputMatrix, callbacks...); + opt.Optimize(objFunction, outputMatrix, callbacks...); } +// Serialize the LMNN object. +template +template +void LMNN::serialize( + Archive& ar, const unsigned int /* version */) +{ + ar(CEREAL_NVP(k)); + ar(CEREAL_NVP(regularization)); + ar(CEREAL_NVP(updateInterval)); + ar(CEREAL_NVP(distance)); +} } // namespace mlpack diff --git a/src/mlpack/methods/lmnn/lmnn_main.cpp b/src/mlpack/methods/lmnn/lmnn_main.cpp index 821c4bd62d..0c270624e6 100644 --- a/src/mlpack/methods/lmnn/lmnn_main.cpp +++ b/src/mlpack/methods/lmnn/lmnn_main.cpp @@ -57,7 +57,7 @@ BINDING_LONG_DESC( PRINT_PARAM_STRING("regularization") + "), In addition, this " "implementation of LMNN includes a parameter to decide the interval " "after which impostors must be re-calculated (specified with " + - PRINT_PARAM_STRING("range") + ")." + PRINT_PARAM_STRING("update_interval") + ")." "\n\n" "Output can either be the learned distance matrix (specified with " + PRINT_PARAM_STRING("output") +"), or the transformed dataset " @@ -124,11 +124,11 @@ BINDING_EXAMPLE( PRINT_CALL("lmnn", "input", "iris", "labels", "iris_labels", "k", 3, "optimizer", "bbsgd", "output", "output") + "\n\n" - "An another program call making use of range & regularization parameter " - "with dataset having labels as last column can be made as: " + "Another program call making use of update interval & regularization " + "parameter with dataset having labels as last column can be made as: " "\n\n" + PRINT_CALL("lmnn", "input", "letter_recognition", "k", 5, - "range", 10, "regularization", 0.4, "output", "output")); + "update_interval", 10, "regularization", 0.4, "output", "output")); // See also... BINDING_SEE_ALSO("@nca", "#nca"); @@ -174,8 +174,8 @@ PARAM_DOUBLE_IN("step_size", "Step size for AMSGrad, BB_SGD and SGD (alpha).", PARAM_FLAG("linear_scan", "Don't shuffle the order in which data points are " "visited for SGD or mini-batch SGD.", "L"); PARAM_INT_IN("batch_size", "Batch size for mini-batch SGD.", "b", 50); -PARAM_INT_IN("range", "Number of iterations after which impostors needs to be " - "recalculated", "R", 1); +PARAM_INT_IN("update_interval", "Number of iterations after which impostors " + "need to be recalculated.", "R", 1); PARAM_INT_IN("seed", "Random seed. If 0, 'std::time(NULL)' is used.", "s", 0); using namespace mlpack; @@ -264,8 +264,8 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& timers) RequireParamValue(params, "k", [](int x) { return x > 0; }, true, "number of targets must be positive"); - RequireParamValue(params, "range", [](int x) { return x > 0; }, true, - "range must be positive"); + RequireParamValue(params, "update_interval", [](int x) { return x > 0; }, + true, "update interval must be positive"); RequireParamValue(params, "batch_size", [](int x) { return x > 0; }, true, "batch size must be positive"); RequireParamValue(params, "regularization", [](double x) @@ -294,7 +294,7 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& timers) const bool printAccuracy = params.Has("print_accuracy"); const bool shuffle = !params.Has("linear_scan"); const size_t batchSize = (size_t) params.Get("batch_size"); - const size_t range = (size_t) params.Get("range"); + const size_t updateInterval = (size_t) params.Get("update_interval"); const size_t rank = (size_t) params.Get("rank"); // Load data. @@ -359,56 +359,49 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& timers) // Now create the LMNN object and run the optimization. timers.Start("lmnn_optimization"); + LMNN lmnn(k, regularization, updateInterval); if (optimizerType == "amsgrad") { - LMNN> lmnn(data, labels, k); - lmnn.Regularization() = regularization; - lmnn.Range() = range; - lmnn.Optimizer().StepSize() = stepSize; - lmnn.Optimizer().MaxIterations() = passes * data.n_cols; - lmnn.Optimizer().Tolerance() = tolerance; - lmnn.Optimizer().Shuffle() = shuffle; - lmnn.Optimizer().BatchSize() = batchSize; + ens::AMSGrad opt; + opt.StepSize() = stepSize; + opt.MaxIterations() = passes * data.n_cols; + opt.Tolerance() = tolerance; + opt.Shuffle() = shuffle; + opt.BatchSize() = batchSize; - lmnn.LearnDistance(distance); + lmnn.LearnDistance(data, labels, distance, opt); } else if (optimizerType == "bbsgd") { - LMNN, ens::BBS_BB> lmnn(data, labels, k); - lmnn.Regularization() = regularization; - lmnn.Range() = range; - lmnn.Optimizer().StepSize() = stepSize; - lmnn.Optimizer().MaxIterations() = passes * data.n_cols; - lmnn.Optimizer().Tolerance() = tolerance; - lmnn.Optimizer().Shuffle() = shuffle; - lmnn.Optimizer().BatchSize() = batchSize; + ens::BBS_BB opt; + opt.StepSize() = stepSize; + opt.MaxIterations() = passes * data.n_cols; + opt.Tolerance() = tolerance; + opt.Shuffle() = shuffle; + opt.BatchSize() = batchSize; - lmnn.LearnDistance(distance); + lmnn.LearnDistance(data, labels, distance, opt); } else if (optimizerType == "sgd") { // Using SGD is not recommended as the learning matrix can // diverge to inf causing serious memory problems. - LMNN, ens::StandardSGD> lmnn(data, labels, k); - lmnn.Regularization() = regularization; - lmnn.Range() = range; - lmnn.Optimizer().StepSize() = stepSize; - lmnn.Optimizer().MaxIterations() = passes * data.n_cols; - lmnn.Optimizer().Tolerance() = tolerance; - lmnn.Optimizer().Shuffle() = shuffle; - lmnn.Optimizer().BatchSize() = batchSize; + ens::StandardSGD opt; + opt.StepSize() = stepSize; + opt.MaxIterations() = passes * data.n_cols; + opt.Tolerance() = tolerance; + opt.Shuffle() = shuffle; + opt.BatchSize() = batchSize; - lmnn.LearnDistance(distance); + lmnn.LearnDistance(data, labels, distance, opt); } else if (optimizerType == "lbfgs") { - LMNN, ens::L_BFGS> lmnn(data, labels, k); - lmnn.Regularization() = regularization; - lmnn.Range() = range; - lmnn.Optimizer().MaxIterations() = maxIterations; - lmnn.Optimizer().MinGradientNorm() = tolerance; + ens::L_BFGS opt; + opt.MaxIterations() = maxIterations; + opt.MinGradientNorm() = tolerance; - lmnn.LearnDistance(distance); + lmnn.LearnDistance(data, labels, distance, opt); } timers.Stop("lmnn_optimization"); diff --git a/src/mlpack/methods/lsh/lsh_search_impl.hpp b/src/mlpack/methods/lsh/lsh_search_impl.hpp index 9685cdc843..151c84f238 100644 --- a/src/mlpack/methods/lsh/lsh_search_impl.hpp +++ b/src/mlpack/methods/lsh/lsh_search_impl.hpp @@ -299,7 +299,7 @@ void LSHSearch::Train(MatType referenceSet, // Now, using the hash vectors for each table, count the number of rows we // have in the second hash table. - arma::Row secondHashBinCounts(secondHashSize, arma::fill::zeros); + arma::Row secondHashBinCounts(secondHashSize); for (size_t i = 0; i < secondHashVectors.n_elem; ++i) secondHashBinCounts[secondHashVectors[i]]++; diff --git a/src/mlpack/methods/mean_shift/mean_shift.hpp b/src/mlpack/methods/mean_shift/mean_shift.hpp index 8defa1c990..6316daa8b6 100644 --- a/src/mlpack/methods/mean_shift/mean_shift.hpp +++ b/src/mlpack/methods/mean_shift/mean_shift.hpp @@ -41,8 +41,7 @@ namespace mlpack { * @tparam MatType The type of matrix the data is stored in. */ template + typename KernelType = GaussianKernel> class MeanShift { public: @@ -67,24 +66,49 @@ class MeanShift * @param data Dataset for estimation. * @param ratio Percentage of dataset to use for nearest neighbor search. */ - double EstimateRadius(const MatType& data, const double ratio = 0.2); + template + typename MatType::elem_type EstimateRadius(const MatType& data, + const double ratio = 0.2); + + /** + * Perform mean shift clusteirng on the data, returning a list of centroids. + * + * @tparam MatType Type of matrix. + * @tparam LabelsType Type of labels (should be similar to arma::Row). + * @tparam CentroidsType Type of matrix to store centroids in; should have + * same element type as MatType. + * @param data Dataset to cluster. + * @param centroids Matrix in which centroids are stored. + * @param forceConvergence Flag whether to force each centroid seed to + * converge regardless of maxIterations. + * @param useSeeds Set true to use seeds. + */ + template + void Cluster(const MatType& data, + CentroidsType& centroids, + bool forceConvergence = false, + bool useSeeds = true); /** * Perform mean shift clustering on the data, returning a list of cluster * assignments and centroids. * * @tparam MatType Type of matrix. + * @tparam LabelsType Type of labels (should be similar to arma::Row). + * @tparam CentroidsType Type of matrix to store centroids in; should have + * same element type as MatType. * @param data Dataset to cluster. * @param assignments Vector to store cluster assignments in. * @param centroids Matrix in which centroids are stored. * @param forceConvergence Flag whether to force each centroid seed to - * converge regardless of maxIterations. + * converge regardless of maxIterations. * @param useSeeds Set true to use seeds. */ + template void Cluster(const MatType& data, - arma::Row& assignments, - arma::mat& centroids, - bool forceConvergence = true, + LabelsType& assignments, + CentroidsType& centroids, + bool forceConvergence = false, bool useSeeds = true); //! Get the maximum number of iterations. @@ -114,12 +138,13 @@ class MeanShift * @param data The reference data set. * @param binSize Width of hypercube bins. * @param minFreq Minimum number of points in bin. - * @param seed Matrix to store generated seeds in. + * @param seeds Matrix to store generated seeds in. */ + template void GenSeeds(const MatType& data, const double binSize, const int minFreq, - MatType& seeds); + CentroidsType& seeds); /** * Use kernel to calculate new centroid given dataset and valid neighbors. @@ -129,12 +154,12 @@ class MeanShift * @param distances Distances to neighbors # @param centroid Store calculated centroid */ - template + template typename std::enable_if::type CalculateCentroid(const MatType& data, const std::vector& neighbors, - const std::vector& distances, - arma::colvec& centroid); + const std::vector& distances, + VecType& centroid); /** * Use mean to calculate new centroid given dataset and valid neighbors. @@ -144,12 +169,12 @@ class MeanShift * @param distances Distances to neighbors # @param centroid Store calculated centroid */ - template + template typename std::enable_if::type CalculateCentroid(const MatType& data, const std::vector& neighbors, - const std::vector&, /*unused*/ - arma::colvec& centroid); + const std::vector&, /*unused*/ + VecType& centroid); /** * If distance of two centroids is less than radius, one will be removed. diff --git a/src/mlpack/methods/mean_shift/mean_shift_impl.hpp b/src/mlpack/methods/mean_shift/mean_shift_impl.hpp index b4eaf63107..e3ea0dd09e 100644 --- a/src/mlpack/methods/mean_shift/mean_shift_impl.hpp +++ b/src/mlpack/methods/mean_shift/mean_shift_impl.hpp @@ -28,11 +28,10 @@ namespace mlpack { /** * Construct the Mean Shift object. */ -template -MeanShift:: -MeanShift(const double radius, - const size_t maxIterations, - const KernelType kernel) : +template +MeanShift::MeanShift(const double radius, + const size_t maxIterations, + const KernelType kernel) : radius(radius), maxIterations(maxIterations), kernel(kernel) @@ -40,18 +39,21 @@ MeanShift(const double radius, // Nothing to do. } -template -void MeanShift::Radius(double radius) +template +void MeanShift::Radius(double radius) { this->radius = radius; } // Estimate radius based on given dataset. -template -double MeanShift:: -EstimateRadius(const MatType& data, double ratio) +template +template +typename MatType::elem_type +MeanShift::EstimateRadius(const MatType& data, + double ratio) { - KNN neighborSearch(data); + NeighborSearch + neighborSearch(data); /** * For each point in dataset, select nNeighbors nearest points and get @@ -60,14 +62,11 @@ EstimateRadius(const MatType& data, double ratio) */ const size_t nNeighbors = size_t(data.n_cols * ratio); arma::Mat neighbors; - arma::mat distances; + MatType distances; neighborSearch.Search(nNeighbors, neighbors, distances); - // Get max distance for each point. - arma::rowvec maxDistances = max(distances); - // Calculate and return the radius. - return sum(maxDistances) / (double) data.n_cols; + return sum(max(distances)) / (typename MatType::elem_type) data.n_cols; } // Class to compare two vectors. @@ -88,14 +87,15 @@ class less }; // Generate seeds from given data set. -template -void MeanShift::GenSeeds( - const MatType& data, - const double binSize, - const int minFreq, - MatType& seeds) +template +template +void MeanShift::GenSeeds(const MatType& data, + const double binSize, + const int minFreq, + CentroidsType& seeds) { - typedef arma::colvec VecType; + typedef typename GetColType::type VecType; + typedef typename GetColType::type CentroidVecType; std::map > allSeeds; for (size_t i = 0; i < data.n_cols; ++i) { @@ -108,7 +108,7 @@ void MeanShift::GenSeeds( // Remove seeds with too few points. First we count the number of seeds we // end up with, then we add them. - std::map >::iterator it; + typename std::map >::iterator it; size_t count = 0; for (it = allSeeds.begin(); it != allSeeds.end(); ++it) if (it->second >= minFreq) @@ -120,7 +120,7 @@ void MeanShift::GenSeeds( { if (it->second >= minFreq) { - seeds.col(count) = it->first; + seeds.col(count) = arma::conv_to::from(it->first); ++count; } } @@ -129,22 +129,24 @@ void MeanShift::GenSeeds( } // Calculate new centroid with given kernel. -template -template +template +template typename std::enable_if::type -MeanShift:: -CalculateCentroid(const MatType& data, - const std::vector& neighbors, - const std::vector& distances, - arma::colvec& centroid) +MeanShift::CalculateCentroid( + const MatType& data, + const std::vector& neighbors, + const std::vector& distances, + VecType& centroid) { - double sumWeight = 0; + typedef typename MatType::elem_type ElemType; + + ElemType sumWeight = 0; for (size_t i = 0; i < neighbors.size(); ++i) { if (distances[i] > 0) { - double dist = distances[i] / radius; - double weight = kernel.Gradient(dist) / dist; + ElemType dist = distances[i] / radius; + ElemType weight = kernel.Gradient(dist) / dist; sumWeight += weight; centroid += weight * data.unsafe_col(neighbors[i]); } @@ -159,14 +161,14 @@ CalculateCentroid(const MatType& data, } // Calculate new centroid by mean. -template -template +template +template typename std::enable_if::type -MeanShift:: -CalculateCentroid(const MatType& data, - const std::vector& neighbors, - const std::vector&, /*unused*/ - arma::colvec& centroid) +MeanShift::CalculateCentroid( + const MatType& data, + const std::vector& neighbors, + const std::vector&, /*unused*/ + VecType& centroid) { for (size_t i = 0; i < neighbors.size(); ++i) centroid += data.unsafe_col(neighbors[i]); @@ -176,24 +178,27 @@ CalculateCentroid(const MatType& data, } /** - * Perform Mean Shift clustering on the data set, returning a list of cluster - * assignments and centroids. + * Perform Mean Shift clustering on the data set, returning a list of centroids. */ -template -inline void MeanShift::Cluster( +template +template +inline void MeanShift::Cluster( const MatType& data, - arma::Row& assignments, - arma::mat& centroids, + CentroidsType& centroids, bool forceConvergence, bool useSeeds) { + // Convenience typedefs. + typedef typename MatType::elem_type ElemType; + typedef typename GetColType::type VecType; + if (radius <= 0) { // An invalid radius is given; an estimation is needed. Radius(EstimateRadius(data)); } - MatType seeds; + CentroidsType seeds; const MatType* pSeeds = &data; if (useSeeds) { @@ -202,14 +207,12 @@ inline void MeanShift::Cluster( } // Holds all centroids before removing duplicate ones. - arma::mat allCentroids(pSeeds->n_rows, pSeeds->n_cols); + CentroidsType allCentroids(pSeeds->n_rows, pSeeds->n_cols); - assignments.set_size(data.n_cols); - - RangeSearch<> rangeSearcher(data); - Range validRadius(0, radius); - std::vector > neighbors; - std::vector > distances; + RangeSearch rangeSearcher(data); + RangeType validRadius((ElemType) 0, (ElemType) radius); + std::vector> neighbors; + std::vector> distances; // For each seed, perform mean shift algorithm. for (size_t i = 0; i < pSeeds->n_cols; ++i) @@ -220,7 +223,7 @@ inline void MeanShift::Cluster( || forceConvergence; completedIterations++) { // Store new centroid in this. - arma::colvec newCentroid = zeros(pSeeds->n_rows); + VecType newCentroid = zeros(pSeeds->n_rows); rangeSearcher.Search(allCentroids.unsafe_col(i), validRadius, neighbors, distances); @@ -239,7 +242,7 @@ inline void MeanShift::Cluster( bool isDuplicated = false; for (size_t k = 0; k < centroids.n_cols; ++k) { - const double distance = EuclideanDistance::Evaluate( + const ElemType distance = EuclideanDistance::Evaluate( allCentroids.unsafe_col(i), centroids.unsafe_col(k)); if (distance < radius) { @@ -276,18 +279,43 @@ inline void MeanShift::Cluster( { centroids.insert_cols(centroids.n_cols, allCentroids.col(0)); } - assignments.zeros(); } - else if (centroids.n_cols == 1) +} + +/** + * Perform Mean Shift clustering on the data set, returning a list of cluster + * assignments and centroids. + */ +template +template +inline void MeanShift::Cluster( + const MatType& data, + LabelsType& assignments, + CentroidsType& centroids, + bool forceConvergence, + bool useSeeds) +{ + // Perform the actual clustering. + Cluster(data, centroids, forceConvergence, useSeeds); + + assignments.set_size(data.n_cols); + if (centroids.n_cols == 1) { assignments.zeros(); } else { // Assign centroids to each point. - KNN neighborSearcher(centroids); - arma::mat neighborDistances; + // + // NeighborSearch only supports when the reference and query set have the + // same type, so forcibly convert the centroids to the same type as data if + // needed. This also means we have to separate out the neighbor searching + // operation to a utility function, so that the compiler doesn't try to + // instantiate the NeighborSearch class with invalid types. + arma::Mat neighborDistances; arma::Mat resultingNeighbors; + NeighborSearch + neighborSearcher(centroids); neighborSearcher.Search(data, 1, resultingNeighbors, neighborDistances); assignments = resultingNeighbors; } diff --git a/src/mlpack/methods/nca/nca.hpp b/src/mlpack/methods/nca/nca.hpp index 4bf817124c..fd66154b28 100644 --- a/src/mlpack/methods/nca/nca.hpp +++ b/src/mlpack/methods/nca/nca.hpp @@ -42,7 +42,7 @@ namespace mlpack { * @endcode */ template + typename DeprecatedOptimizerType = ens::StandardSGD> class NCA { public: @@ -55,10 +55,18 @@ class NCA * @param labels Input dataset labels. * @param distance Instantiated distance metric to use. */ + [[deprecated("Will be removed in mlpack 5.0.0. Pass the dataset directly to " + "LearnDistance() instead.")]] NCA(const arma::mat& dataset, const arma::Row& labels, DistanceType distance = DistanceType()); + /** + * Construct the Neighborhood Components Analysis object, optionally with an + * instantiated distance metric. + */ + NCA(DistanceType distance = DistanceType()); + /** * Perform Neighborhood Components Analysis. The output distance learning * matrix is written into the passed reference. If LearnDistance() is called @@ -71,32 +79,109 @@ class NCA * @param callbacks Callback function for ensmallen optimizer `OptimizerType`. * See https://www.ensmallen.org/docs.html#callback-documentation. */ - template + template::value>::type, + typename = typename std::enable_if< + !FirstElementIsArma::value + >::type> + [[deprecated("Will be removed in mlpack 5.0.0. Use the version that takes a " + "dataset as a parameter.")]] void LearnDistance(arma::mat& outputMatrix, CallbackTypes&&... callbacks); + /** + * Perform Neighborhood Components Analysis. The output distance learning + * matrix is written into the passed reference. If LearnDistance() is called + * with an outputMatrix which has the correct size (dataset.n_rows x + * dataset.n_rows), that matrix will be used as the starting point for + * optimization. + * + * @param dataset Dataset to learn distance metric on. + * @param labels Labels for dataset. + * @param outputMatrix Covariance matrix of Mahalanobis distance. + * @param callbacks Callback function for ensmallen optimizer `OptimizerType`. + * See https://www.ensmallen.org/docs.html#callback-documentation. + */ + template::type, + SoftmaxErrorFunction, + MatType + >::value>::type, + typename = typename std::enable_if::value>::type> + void LearnDistance(const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + CallbackTypes&&... callbacks) const; + + /** + * Perform Neighborhood Components Analysis. The output distance learning + * matrix is written into the passed reference. If LearnDistance() is called + * with an outputMatrix which has the correct size (dataset.n_rows x + * dataset.n_rows), that matrix will be used as the starting point for + * optimization. + * + * @param dataset Dataset to learn distance metric on. + * @param labels Labels for dataset. + * @param optimizer Instantiated ensmallen optimizer to use for NCA. + * @param outputMatrix Covariance matrix of Mahalanobis distance. + * @param callbacks Callback function for ensmallen optimizer `OptimizerType`. + * See https://www.ensmallen.org/docs.html#callback-documentation. + */ + template, + MatType + >::value>::type> + void LearnDistance(const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + OptimizerType& optimizer, + CallbackTypes&&... callbacks) const; + //! Get the dataset reference. - const arma::mat& Dataset() const { return dataset; } + [[deprecated("Will be removed in mlpack 5.0.0.")]] + const arma::mat& Dataset() const { return *dataset; } //! Get the labels reference. - const arma::Row& Labels() const { return labels; } + [[deprecated("Will be removed in mlpack 5.0.0.")]] + const arma::Row& Labels() const { return *labels; } //! Get the optimizer. - const OptimizerType& Optimizer() const { return optimizer; } - OptimizerType& Optimizer() { return optimizer; } + [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() " + "version that takes the optimizer as a parameter instead.")]] + const DeprecatedOptimizerType& Optimizer() const { return optimizer; } + //! Modify the optimizer. + [[deprecated("Will be removed in mlpack 5.0.0. Use the LearnDistance() " + "version that takes the optimizer as a parameter instead.")]] + DeprecatedOptimizerType& Optimizer() { return optimizer; } + + //! Get the distance. + const DistanceType Distance() const { return distance; } + //! Modify the distance. + DistanceType& Distance() { return distance; } + + template + void serialize(Archive& ar, const unsigned int /* version */); private: - //! Dataset reference. - const arma::mat& dataset; - //! Labels reference. - const arma::Row& labels; + //! Dataset pointer (will be removed in mlpack 5.0.0). + const arma::mat* dataset; + //! Labels reference (will be removed in mlpack 5.0.0). + const arma::Row* labels; + //! The optimizer to use (will be removed in mlpack 5.0.0). + DeprecatedOptimizerType optimizer; //! Distance to be used. DistanceType distance; - - //! The function to optimize. - SoftmaxErrorFunction errorFunction; - - //! The optimizer to use. - OptimizerType optimizer; }; } // namespace mlpack diff --git a/src/mlpack/methods/nca/nca_impl.hpp b/src/mlpack/methods/nca/nca_impl.hpp index c0b9dd7098..6b3f9f9103 100644 --- a/src/mlpack/methods/nca/nca_impl.hpp +++ b/src/mlpack/methods/nca/nca_impl.hpp @@ -18,27 +18,89 @@ namespace mlpack { // Just set the internal matrix reference. -template -NCA::NCA(const arma::mat& dataset, - const arma::Row& labels, - DistanceType distance) : - dataset(dataset), - labels(labels), - distance(distance), - errorFunction(dataset, labels, distance) +template +NCA::NCA( + const arma::mat& dataset, + const arma::Row& labels, + DistanceType distance) : + dataset(&dataset), + labels(&labels), + distance(std::move(distance)) { /* Nothing to do. */ } -template -template -void NCA::LearnDistance(arma::mat& outputMatrix, +template +NCA::NCA(DistanceType distance) : + distance(std::move(distance)) +{ /* Nothing to do. */ } + +template +template +void NCA::LearnDistance( + arma::mat& outputMatrix, CallbackTypes&&... callbacks) { + if (!dataset || !labels) + { + throw std::runtime_error("NCA::LearnDistance(): cannot call without a " + "dataset!"); + } + + LearnDistance(*dataset, *labels, outputMatrix, optimizer, + std::forward(callbacks)...); +} + +template +template +void NCA::LearnDistance( + const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + CallbackTypes&&... callbacks) const +{ + // This should be replaced with ens::StandardSGD when the deprecated members + // are removed for mlpack 5.0.0. + DeprecatedOptimizerType opt; + LearnDistance(dataset, labels, outputMatrix, opt, + std::forward(callbacks)...); +} + +template +template +void NCA::LearnDistance( + const MatType& dataset, + const LabelsType& labels, + MatType& outputMatrix, + OptimizerType& opt, + CallbackTypes&&... callbacks) const +{ + SoftmaxErrorFunction errorFunction( + dataset, labels, distance); + // See if we were passed an initialized matrix. if ((outputMatrix.n_rows != dataset.n_rows) || (outputMatrix.n_cols != dataset.n_rows)) outputMatrix.eye(dataset.n_rows, dataset.n_rows); - optimizer.Optimize(errorFunction, outputMatrix, callbacks...); + opt.Optimize(errorFunction, outputMatrix, + std::forward(callbacks)...); +} + +template +template +void NCA::serialize( + Archive& ar, const unsigned int /* version */) +{ + ar(CEREAL_NVP(distance)); } } // namespace mlpack diff --git a/src/mlpack/methods/nca/nca_main.cpp b/src/mlpack/methods/nca/nca_main.cpp index cae98ce87c..598efe0e12 100644 --- a/src/mlpack/methods/nca/nca_main.cpp +++ b/src/mlpack/methods/nca/nca_main.cpp @@ -240,30 +240,31 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& timers) // Now create the NCA object and run the optimization. timers.Start("nca_optimization"); + NCA nca; if (optimizerType == "sgd") { - NCA > nca(data, labels); - nca.Optimizer().StepSize() = stepSize; - nca.Optimizer().MaxIterations() = maxIterations; - nca.Optimizer().Tolerance() = tolerance; - nca.Optimizer().Shuffle() = shuffle; - nca.Optimizer().BatchSize() = batchSize; + ens::StandardSGD opt; + opt.StepSize() = stepSize; + opt.MaxIterations() = maxIterations; + opt.Tolerance() = tolerance; + opt.Shuffle() = shuffle; + opt.BatchSize() = batchSize; - nca.LearnDistance(distance); + nca.LearnDistance(data, labels, distance, opt); } else if (optimizerType == "lbfgs") { - NCA, ens::L_BFGS> nca(data, labels); - nca.Optimizer().NumBasis() = numBasis; - nca.Optimizer().MaxIterations() = maxIterations; - nca.Optimizer().ArmijoConstant() = armijoConstant; - nca.Optimizer().Wolfe() = wolfe; - nca.Optimizer().MinGradientNorm() = tolerance; - nca.Optimizer().MaxLineSearchTrials() = maxLineSearchTrials; - nca.Optimizer().MinStep() = minStep; - nca.Optimizer().MaxStep() = maxStep; + ens::L_BFGS opt; + opt.NumBasis() = numBasis; + opt.MaxIterations() = maxIterations; + opt.ArmijoConstant() = armijoConstant; + opt.Wolfe() = wolfe; + opt.MinGradientNorm() = tolerance; + opt.MaxLineSearchTrials() = maxLineSearchTrials; + opt.MinStep() = minStep; + opt.MaxStep() = maxStep; - nca.LearnDistance(distance); + nca.LearnDistance(data, labels, distance, opt); } timers.Stop("nca_optimization"); diff --git a/src/mlpack/methods/nca/nca_softmax_error_function.hpp b/src/mlpack/methods/nca/nca_softmax_error_function.hpp index e4165c9c12..88237d88b3 100644 --- a/src/mlpack/methods/nca/nca_softmax_error_function.hpp +++ b/src/mlpack/methods/nca/nca_softmax_error_function.hpp @@ -40,10 +40,17 @@ namespace mlpack { * operate on one point in the dataset. This is useful for optimizers like * stochastic gradient descent (see mlpack::optimization::SGD). */ -template +template, + typename DistanceType = SquaredEuclideanDistance> class SoftmaxErrorFunction { public: + // Convenience typedef for element type of data. + typedef typename MatType::elem_type ElemType; + // Convenience typedef for column vector of data. + typedef typename GetColType::type VecType; + /** * Initialize with the given kernel; useful when the kernel has some state to * store, which is set elsewhere. If no kernel is given, an empty kernel is @@ -54,8 +61,8 @@ class SoftmaxErrorFunction * @param labels Vector of class labels for each point in the dataset. * @param metric Instantiated metric (optional). */ - SoftmaxErrorFunction(const arma::mat& dataset, - const arma::Row& labels, + SoftmaxErrorFunction(const MatType& dataset, + const LabelsType& labels, DistanceType metric = DistanceType()); /** @@ -70,7 +77,7 @@ class SoftmaxErrorFunction * * @param covariance Covariance matrix of Mahalanobis distance. */ - double Evaluate(const arma::mat& covariance); + ElemType Evaluate(const MatType& covariance); /** * Evaluate the softmax objective function for the given covariance matrix on @@ -84,9 +91,9 @@ class SoftmaxErrorFunction * @param begin Index of the initial point to use for objective function. * @param batchSize Number of points to use for objective function. */ - double Evaluate(const arma::mat& covariance, - const size_t begin, - const size_t batchSize = 1); + ElemType Evaluate(const MatType& covariance, + const size_t begin, + const size_t batchSize = 1); /** * Evaluate the gradient of the softmax function for the given covariance @@ -96,7 +103,7 @@ class SoftmaxErrorFunction * @param covariance Covariance matrix of Mahalanobis distance. * @param gradient Matrix to store the calculated gradient in. */ - void Gradient(const arma::mat& covariance, arma::mat& gradient); + void Gradient(const MatType& covariance, MatType& gradient); /** * Evaluate the gradient of the softmax function for the given covariance @@ -114,7 +121,7 @@ class SoftmaxErrorFunction * @param gradient Matrix to store the calculated gradient in. */ template - void Gradient(const arma::mat& covariance, + void Gradient(const MatType& covariance, const size_t begin, GradType& gradient, const size_t batchSize = 1); @@ -122,7 +129,7 @@ class SoftmaxErrorFunction /** * Get the initial point. */ - const arma::mat GetInitialPoint() const; + const MatType GetInitialPoint() const; /** * Get the number of functions the objective function can be decomposed into. @@ -132,23 +139,23 @@ class SoftmaxErrorFunction private: //! The dataset. This is an alias until Shuffle() is called. - arma::mat dataset; + MatType dataset; //! Labels for each point in the dataset. This is an alias until Shuffle() is //! called. - arma::Row labels; + LabelsType labels; //! The instantiated metric. DistanceType distance; //! Last coordinates. Used for the non-separable Evaluate() and Gradient(). - arma::mat lastCoordinates; + MatType lastCoordinates; //! Stretched dataset. Kept internal to avoid memory reallocations. - arma::mat stretchedDataset; + MatType stretchedDataset; //! Holds calculated p_i, for the non-separable Evaluate() and Gradient(). - arma::vec p; + VecType p; //! Holds denominators for calculation of p_ij, for the non-separable //! Evaluate() and Gradient(). - arma::vec denominators; + VecType denominators; //! False if nothing has ever been precalculated (only at construction time). bool precalculated; @@ -166,7 +173,7 @@ class SoftmaxErrorFunction * * @param coordinates Coordinates matrix to use for precalculation. */ - void Precalculate(const arma::mat& coordinates); + void Precalculate(const MatType& coordinates); }; } // namespace mlpack diff --git a/src/mlpack/methods/nca/nca_softmax_error_function_impl.hpp b/src/mlpack/methods/nca/nca_softmax_error_function_impl.hpp index 2708e6e247..01e133f338 100644 --- a/src/mlpack/methods/nca/nca_softmax_error_function_impl.hpp +++ b/src/mlpack/methods/nca/nca_softmax_error_function_impl.hpp @@ -20,10 +20,10 @@ namespace mlpack { // Initialize with the given kernel. -template -SoftmaxErrorFunction::SoftmaxErrorFunction( - const arma::mat& datasetIn, - const arma::Row& labelsIn, +template +SoftmaxErrorFunction::SoftmaxErrorFunction( + const MatType& datasetIn, + const LabelsType& labelsIn, DistanceType distance) : distance(distance), precalculated(false) @@ -33,11 +33,11 @@ SoftmaxErrorFunction::SoftmaxErrorFunction( } //! Shuffle the dataset. -template -void SoftmaxErrorFunction::Shuffle() +template +void SoftmaxErrorFunction::Shuffle() { - arma::mat newDataset; - arma::Row newLabels; + MatType newDataset; + LabelsType newLabels; ShuffleData(dataset, labels, newDataset, newLabels); @@ -49,8 +49,10 @@ void SoftmaxErrorFunction::Shuffle() } //! The non-separable implementation, which uses Precalculate() to save time. -template -double SoftmaxErrorFunction::Evaluate(const arma::mat& coordinates) +template +typename MatType::elem_type +SoftmaxErrorFunction::Evaluate( + const MatType& coordinates) { // Calculate the denominators and numerators, if necessary. Precalculate(coordinates); @@ -61,19 +63,23 @@ double SoftmaxErrorFunction::Evaluate(const arma::mat& coordinates //! The separated objective function, which does not use Precalculate(), //! for a given batch size and from an initial index. -template -double SoftmaxErrorFunction::Evaluate(const arma::mat& coordinates, - const size_t begin, - const size_t batchSize) +template +typename MatType::elem_type +SoftmaxErrorFunction::Evaluate( + const MatType& coordinates, + const size_t begin, + const size_t batchSize) { // Unfortunately each evaluation will take O(N) time because it requires a // scan over all points in the dataset. Our objective is to compute p_i. - double denominator = 0; - double numerator = 0; - double result = 0; + ElemType denominator = 0; + ElemType numerator = 0; + ElemType result = 0; // It's quicker to do this now than one point at a time later. stretchedDataset = coordinates * dataset; + + #pragma omp parallel for reduction(+:result) for (size_t i = begin; i < begin + batchSize; ++i) { for (size_t k = 0; k < dataset.n_cols; ++k) @@ -83,7 +89,7 @@ double SoftmaxErrorFunction::Evaluate(const arma::mat& coordinates continue; // We want to evaluate exp(-D(A x_i, A x_k)). - double eval = std::exp(-distance.Evaluate( + ElemType eval = std::exp(-distance.Evaluate( stretchedDataset.unsafe_col(i), stretchedDataset.unsafe_col(k))); // If they are in the same class, update the numerator. @@ -108,9 +114,9 @@ double SoftmaxErrorFunction::Evaluate(const arma::mat& coordinates } //! The non-separable implementation, where Precalculate() is used. -template -void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, - arma::mat& gradient) +template +void SoftmaxErrorFunction::Gradient( + const MatType& coordinates, MatType& gradient) { // Calculate the denominators and numerators, if necessary. Precalculate(coordinates); @@ -127,22 +133,22 @@ void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, // (((p_i - (1 / p_i)) p_ik) + ((p_k - (1 / p_k)) p_ki)) x_ik x_ik^T // otherwise, add // (p_i p_ik + p_k p_ki) x_ik x_ik^T - arma::mat sum; + MatType sum; sum.zeros(stretchedDataset.n_rows, stretchedDataset.n_rows); for (size_t i = 0; i < stretchedDataset.n_cols; ++i) { for (size_t k = (i + 1); k < stretchedDataset.n_cols; ++k) { // Calculate p_ik and p_ki first. - double eval = std::exp(-distance.Evaluate( + ElemType eval = std::exp(-distance.Evaluate( stretchedDataset.unsafe_col(i), stretchedDataset.unsafe_col(k))); - double p_ik = 0, p_ki = 0; + ElemType p_ik = 0, p_ki = 0; p_ik = eval / denominators(i); p_ki = eval / denominators(k); // Subtract x_i from x_k. We are not using stretched points here. - arma::vec x_ik = dataset.col(i) - dataset.col(k); - arma::mat secondTerm = (x_ik * trans(x_ik)); + VecType x_ik = dataset.col(i) - dataset.col(k); + MatType secondTerm = (x_ik * trans(x_ik)); if (labels[i] == labels[k]) sum += ((p[i] - 1) * p_ik + (p[k] - 1) * p_ki) * secondTerm; @@ -156,19 +162,20 @@ void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, } //! The separable implementation for a given batch size and an initial index. -template +template template -void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, - const size_t begin, - GradType& gradient, - const size_t batchSize) +void SoftmaxErrorFunction::Gradient( + const MatType& coordinates, + const size_t begin, + GradType& gradient, + const size_t batchSize) { // The gradient involves two matrix terms which are eventually combined into // one. GradType firstTerm, secondTerm; // We will need to calculate p_i before this evaluation is done, so // these two variables will hold the information necessary for that. - double numerator, denominator; + ElemType numerator, denominator; gradient.zeros(coordinates.n_rows, coordinates.n_rows); @@ -189,7 +196,7 @@ void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, continue; // Calculate the numerator of p_ik. - double eval = std::exp(-distance.Evaluate( + ElemType eval = std::exp(-distance.Evaluate( stretchedDataset.unsafe_col(i), stretchedDataset.unsafe_col(k))); // If the points are in the same class, we must add to the second term of @@ -210,7 +217,7 @@ void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, } // Calculate p_i. - double p = 0; + ElemType p = 0; if (denominator == 0) { Log::Warn << "Denominator of p_" << i << " is 0!" << std::endl; @@ -231,15 +238,16 @@ void SoftmaxErrorFunction::Gradient(const arma::mat& coordinates, } } -template -const arma::mat SoftmaxErrorFunction::GetInitialPoint() const +template +const MatType +SoftmaxErrorFunction::GetInitialPoint() const { - return arma::eye(dataset.n_rows, dataset.n_rows); + return arma::eye(dataset.n_rows, dataset.n_rows); } -template -void SoftmaxErrorFunction::Precalculate( - const arma::mat& coordinates) +template +void SoftmaxErrorFunction::Precalculate( + const MatType& coordinates) { // Ensure it is the right size. if (lastCoordinates.n_rows != coordinates.n_rows || @@ -265,22 +273,30 @@ void SoftmaxErrorFunction::Precalculate( // order of O((n * (n + 1)) / 2), which really isn't all that great. p.zeros(stretchedDataset.n_cols); denominators.zeros(stretchedDataset.n_cols); + + // A collapse(2) would be helpful here, but appears to not be supported fully + // until OpenMP 5.0. + #pragma omp parallel for for (size_t i = 0; i < stretchedDataset.n_cols; ++i) { for (size_t j = (i + 1); j < stretchedDataset.n_cols; ++j) { // Evaluate exp(-d(x_i, x_j)). - double eval = std::exp(-distance.Evaluate( + ElemType eval = std::exp(-distance.Evaluate( stretchedDataset.unsafe_col(i), stretchedDataset.unsafe_col(j))); // Add this to the denominators of both p_i and p_j: K(i, j) = K(j, i). + #pragma omp atomic denominators[i] += eval; + #pragma omp atomic denominators[j] += eval; // If i and j are the same class, add to numerator of both. if (labels[i] == labels[j]) { + #pragma omp atomic p[i] += eval; + #pragma omp atomic p[j] += eval; } } @@ -290,6 +306,7 @@ void SoftmaxErrorFunction::Precalculate( p /= denominators; // Clean up any bad values. + #pragma omp parallel for for (size_t i = 0; i < stretchedDataset.n_cols; ++i) { if (denominators[i] == 0.0) @@ -297,7 +314,7 @@ void SoftmaxErrorFunction::Precalculate( Log::Debug << "Denominator of p_{" << i << ", j} is 0." << std::endl; // Set to usable values. - denominators[i] = std::numeric_limits::infinity(); + denominators[i] = std::numeric_limits::infinity(); p[i] = 0; } } diff --git a/src/mlpack/methods/neighbor_search/neighbor_search.hpp b/src/mlpack/methods/neighbor_search/neighbor_search.hpp index b8f61c0155..d2470b1b19 100644 --- a/src/mlpack/methods/neighbor_search/neighbor_search.hpp +++ b/src/mlpack/methods/neighbor_search/neighbor_search.hpp @@ -222,9 +222,11 @@ class NeighborSearch * @param distances Matrix storing distances of neighbors for each query * point. */ + // TODO: templatize further to remove Armadillo type requirement + template void Search(const MatType& querySet, const size_t k, - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances); /** @@ -247,9 +249,11 @@ class NeighborSearch * @param sameSet Denotes whether or not the reference and query sets are the * same. */ + // TODO: templatize further to remove Armadillo type requirement + template void Search(Tree& queryTree, const size_t k, - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances, bool sameSet = false); @@ -267,8 +271,10 @@ class NeighborSearch * @param distances Matrix storing distances of neighbors for each query * point. */ + // TODO: templatize further to remove Armadillo type requirement + template void Search(const size_t k, - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances); /** @@ -300,8 +306,10 @@ class NeighborSearch * query point. * @return Recall. */ - static double Recall(arma::Mat& foundNeighbors, - arma::Mat& realNeighbors); + // TODO: templatize further to remove Armadillo type requirement + template + static double Recall(arma::Mat& foundNeighbors, + arma::Mat& realNeighbors); //! Return the total number of base case evaluations performed during the last //! search. diff --git a/src/mlpack/methods/neighbor_search/neighbor_search_impl.hpp b/src/mlpack/methods/neighbor_search/neighbor_search_impl.hpp index c73b7855f0..0aa0a7de0d 100644 --- a/src/mlpack/methods/neighbor_search/neighbor_search_impl.hpp +++ b/src/mlpack/methods/neighbor_search/neighbor_search_impl.hpp @@ -360,11 +360,12 @@ template class TreeType, template class DualTreeTraversalType, template class SingleTreeTraversalType> +template void NeighborSearch::Search( const MatType& querySet, const size_t k, - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances) { if (k > referenceSet->n_cols) @@ -385,7 +386,7 @@ DualTreeTraversalType, SingleTreeTraversalType>::Search( // indices back to their original indices when this computation is finished. // To avoid an extra copy, we will store the neighbors and distances in a // separate matrix. - arma::Mat* neighborPtr = &neighbors; + arma::Mat* neighborPtr = &neighbors; arma::Mat* distancePtr = &distances; // Mapping is only necessary if the tree rearranges points. @@ -394,10 +395,10 @@ DualTreeTraversalType, SingleTreeTraversalType>::Search( if (searchMode == DUAL_TREE_MODE) { distancePtr = new arma::Mat; // Query indices need to be mapped. - neighborPtr = new arma::Mat; + neighborPtr = new arma::Mat; } else if (!oldFromNewReferences.empty()) - neighborPtr = new arma::Mat; // Reference indices need mapping. + neighborPtr = new arma::Mat; // Reference indices need mapping. } // Set the size of the neighbor and distance matrices. @@ -565,11 +566,12 @@ template class TreeType, template class DualTreeTraversalType, template class SingleTreeTraversalType> +template void NeighborSearch::Search( Tree& queryTree, const size_t k, - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances, bool sameSet) { @@ -593,10 +595,10 @@ DualTreeTraversalType, SingleTreeTraversalType>::Search( const MatType& querySet = queryTree.Dataset(); // We won't need to map query indices, but will we need to map distances? - arma::Mat* neighborPtr = &neighbors; + arma::Mat* neighborPtr = &neighbors; if (!oldFromNewReferences.empty() && TreeTraits::RearrangesDataset) - neighborPtr = new arma::Mat; + neighborPtr = new arma::Mat; neighborPtr->set_size(k, querySet.n_cols); distances.set_size(k, querySet.n_cols); @@ -644,10 +646,11 @@ template class TreeType, template class DualTreeTraversalType, template class SingleTreeTraversalType> +template void NeighborSearch::Search( const size_t k, - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances) { if (k > referenceSet->n_cols) @@ -669,14 +672,14 @@ DualTreeTraversalType, SingleTreeTraversalType>::Search( baseCases = 0; scores = 0; - arma::Mat* neighborPtr = &neighbors; + arma::Mat* neighborPtr = &neighbors; arma::Mat* distancePtr = &distances; if (!oldFromNewReferences.empty() && TreeTraits::RearrangesDataset) { // We will always need to rearrange in this case. distancePtr = new MatType; - neighborPtr = new arma::Mat; + neighborPtr = new arma::Mat; } // Initialize results. @@ -861,10 +864,11 @@ template class TreeType, template class DualTreeTraversalType, template class SingleTreeTraversalType> +template double NeighborSearch::Recall( - arma::Mat& foundNeighbors, - arma::Mat& realNeighbors) + arma::Mat& foundNeighbors, + arma::Mat& realNeighbors) { if (foundNeighbors.n_rows != realNeighbors.n_rows || foundNeighbors.n_cols != realNeighbors.n_cols) diff --git a/src/mlpack/methods/neighbor_search/neighbor_search_rules.hpp b/src/mlpack/methods/neighbor_search/neighbor_search_rules.hpp index ef886663a7..057515ca7a 100644 --- a/src/mlpack/methods/neighbor_search/neighbor_search_rules.hpp +++ b/src/mlpack/methods/neighbor_search/neighbor_search_rules.hpp @@ -63,7 +63,10 @@ class NeighborSearchRules * @param distances Matrix storing distances of neighbors for each query * point. */ - void GetResults(arma::Mat& neighbors, arma::Mat& distances); + // TODO: templatize fully to remove requirement of Armadillo matrix + template + void GetResults(arma::Mat& neighbors, + arma::Mat& distances); /** * Get the distance from the query point to the reference point. diff --git a/src/mlpack/methods/neighbor_search/neighbor_search_rules_impl.hpp b/src/mlpack/methods/neighbor_search/neighbor_search_rules_impl.hpp index 24fa48c6d5..cfbd350090 100644 --- a/src/mlpack/methods/neighbor_search/neighbor_search_rules_impl.hpp +++ b/src/mlpack/methods/neighbor_search/neighbor_search_rules_impl.hpp @@ -59,8 +59,9 @@ NeighborSearchRules::NeighborSearchRules( } template +template void NeighborSearchRules::GetResults( - arma::Mat& neighbors, + arma::Mat& neighbors, arma::Mat& distances) { neighbors.set_size(k, querySet.n_cols); @@ -71,7 +72,7 @@ void NeighborSearchRules::GetResults( CandidateList& pqueue = candidates[i]; for (size_t j = 1; j <= k; ++j) { - neighbors(k - j, i) = pqueue.top().second; + neighbors(k - j, i) = (IndexType) pqueue.top().second; distances(k - j, i) = pqueue.top().first; pqueue.pop(); } diff --git a/src/mlpack/methods/reinforcement_learning/environment/cont_double_pole_cart.hpp b/src/mlpack/methods/reinforcement_learning/environment/cont_double_pole_cart.hpp index 7c0120071a..5b2015630c 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/cont_double_pole_cart.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/cont_double_pole_cart.hpp @@ -156,7 +156,7 @@ class ContinuousDoublePoleCart // Update the number of steps performed. stepsPerformed++; - arma::vec dydx(6, arma::fill::zeros); + arma::vec dydx(6); dydx[0] = state.Velocity(); dydx[2] = state.AngularVelocity(1); dydx[4] = state.AngularVelocity(2); diff --git a/src/mlpack/methods/reinforcement_learning/environment/continuous_mountain_car.hpp b/src/mlpack/methods/reinforcement_learning/environment/continuous_mountain_car.hpp index f17e60b640..cf10c51f12 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/continuous_mountain_car.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/continuous_mountain_car.hpp @@ -37,7 +37,7 @@ class ContinuousMountainCar /** * Construct a state instance. */ - State() : data(dimension, arma::fill::zeros) + State() : data(dimension) { /* Nothing to do here. */ } /** diff --git a/src/mlpack/methods/reinforcement_learning/environment/double_pole_cart.hpp b/src/mlpack/methods/reinforcement_learning/environment/double_pole_cart.hpp index fc470a56e0..f3040874dc 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/double_pole_cart.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/double_pole_cart.hpp @@ -162,7 +162,7 @@ class DoublePoleCart // Update the number of steps performed. stepsPerformed++; - arma::vec dydx(6, arma::fill::zeros); + arma::vec dydx(6); dydx[0] = state.Velocity(); dydx[2] = state.AngularVelocity(1); dydx[4] = state.AngularVelocity(2); diff --git a/src/mlpack/methods/reinforcement_learning/environment/mountain_car.hpp b/src/mlpack/methods/reinforcement_learning/environment/mountain_car.hpp index 3698af6d81..d611a5e403 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/mountain_car.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/mountain_car.hpp @@ -36,7 +36,7 @@ class MountainCar /** * Construct a state instance. */ - State(): data(dimension, arma::fill::zeros) + State(): data(dimension) { /* Nothing to do here. */ } /** diff --git a/src/mlpack/methods/reinforcement_learning/environment/pendulum.hpp b/src/mlpack/methods/reinforcement_learning/environment/pendulum.hpp index bf44b6ac6d..c77a1fd289 100644 --- a/src/mlpack/methods/reinforcement_learning/environment/pendulum.hpp +++ b/src/mlpack/methods/reinforcement_learning/environment/pendulum.hpp @@ -39,7 +39,7 @@ class Pendulum /** * Construct a state instance. */ - State() : theta(0), data(dimension, arma::fill::zeros) + State() : theta(0), data(dimension) { /* Nothing to do here. */ } /** diff --git a/src/mlpack/methods/reinforcement_learning/worker/n_step_q_learning_worker.hpp b/src/mlpack/methods/reinforcement_learning/worker/n_step_q_learning_worker.hpp index fa21e97d9e..f534079137 100644 --- a/src/mlpack/methods/reinforcement_learning/worker/n_step_q_learning_worker.hpp +++ b/src/mlpack/methods/reinforcement_learning/worker/n_step_q_learning_worker.hpp @@ -282,7 +282,7 @@ class NStepQLearningWorker { // Initialize the gradient storage. arma::mat totalGradients(learningNetwork.Parameters().n_rows, - learningNetwork.Parameters().n_cols, arma::fill::zeros); + learningNetwork.Parameters().n_cols); // Bootstrap from the value of next state. arma::colvec actionValue; diff --git a/src/mlpack/methods/reinforcement_learning/worker/one_step_q_learning_worker.hpp b/src/mlpack/methods/reinforcement_learning/worker/one_step_q_learning_worker.hpp index 75e1bed513..9d487beb31 100644 --- a/src/mlpack/methods/reinforcement_learning/worker/one_step_q_learning_worker.hpp +++ b/src/mlpack/methods/reinforcement_learning/worker/one_step_q_learning_worker.hpp @@ -282,7 +282,7 @@ class OneStepQLearningWorker { // Initialize the gradient storage. arma::mat totalGradients(learningNetwork.Parameters().n_rows, - learningNetwork.Parameters().n_cols, arma::fill::zeros); + learningNetwork.Parameters().n_cols); for (size_t i = 0; i < pending.size(); ++i) { TransitionType &transition = pending[i]; diff --git a/src/mlpack/methods/reinforcement_learning/worker/one_step_sarsa_worker.hpp b/src/mlpack/methods/reinforcement_learning/worker/one_step_sarsa_worker.hpp index 814b499898..96fb078d8e 100644 --- a/src/mlpack/methods/reinforcement_learning/worker/one_step_sarsa_worker.hpp +++ b/src/mlpack/methods/reinforcement_learning/worker/one_step_sarsa_worker.hpp @@ -295,7 +295,7 @@ class OneStepSarsaWorker { // Initialize the gradient storage. arma::mat totalGradients(learningNetwork.Parameters().n_rows, - learningNetwork.Parameters().n_cols, arma::fill::zeros); + learningNetwork.Parameters().n_cols); for (size_t i = 0; i < pending.size(); ++i) { TransitionType &transition = pending[i]; diff --git a/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp b/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp index 2b6eaa3e0e..b829651a89 100644 --- a/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp +++ b/src/mlpack/methods/svdplusplus/svdplusplus_function_impl.hpp @@ -96,7 +96,7 @@ double SVDPlusPlusFunction::Evaluate(const arma::mat& parameters, // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -169,7 +169,7 @@ void SVDPlusPlusFunction::Gradient(const arma::mat& parameters, // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -236,7 +236,7 @@ void SVDPlusPlusFunction::Gradient(const arma::mat& parameters, // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -343,7 +343,7 @@ double StandardSGD::Optimize( // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -479,7 +479,7 @@ inline double ParallelSGD::Optimize( const double itemBias = iterate(rank, item); // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; diff --git a/src/mlpack/tests/ann/async_learning_test.cpp b/src/mlpack/tests/ann/async_learning_test.cpp index cca1e52128..eab6351264 100644 --- a/src/mlpack/tests/ann/async_learning_test.cpp +++ b/src/mlpack/tests/ann/async_learning_test.cpp @@ -59,7 +59,7 @@ TEST_CASE("OneStepQLearningTest", "[AsyncLearningTest]") CartPole, decltype(model), ens::VanillaUpdate, decltype(policy)> agent(std::move(config), std::move(model), std::move(policy)); - arma::vec rewards(20, arma::fill::zeros); + arma::vec rewards(20); size_t pos = 0; size_t testEpisodes = 0; auto measure = [&rewards, &pos, &testEpisodes](double reward) @@ -137,7 +137,7 @@ TEST_CASE("OneStepSarsaTest", "[AsyncLearningTest]") decltype(policy)> agent(std::move(config), std::move(model), std::move(policy)); - arma::vec rewards(20, arma::fill::zeros); + arma::vec rewards(20); size_t pos = 0; size_t testEpisodes = 0; auto measure = [&rewards, &pos, &testEpisodes](double reward) @@ -210,7 +210,7 @@ TEST_CASE("NStepQLearningTest", "[AsyncLearningTest]") CartPole, decltype(model), ens::VanillaUpdate, decltype(policy)> agent(std::move(config), std::move(model), std::move(policy)); - arma::vec rewards(20, arma::fill::zeros); + arma::vec rewards(20); size_t pos = 0; size_t testEpisodes = 0; auto measure = [&rewards, &pos, &testEpisodes](double reward) diff --git a/src/mlpack/tests/ann/convolutional_network_test.cpp b/src/mlpack/tests/ann/convolutional_network_test.cpp index 209ac5b131..06ee7cdc1e 100644 --- a/src/mlpack/tests/ann/convolutional_network_test.cpp +++ b/src/mlpack/tests/ann/convolutional_network_test.cpp @@ -313,7 +313,7 @@ TEST_CASE("VanillaNetworkBatchSizeTest", "[ConvolutionalNetworkTest]") // Now compute results with a batch size of 1. arma::mat singleResults(results.n_rows, results.n_cols); - arma::mat singleGradient(gradient.n_rows, gradient.n_cols, arma::fill::zeros); + arma::mat singleGradient(gradient.n_rows, gradient.n_cols); double singleObj = 0.0; for (size_t i = 0; i < batchSize; ++i) diff --git a/src/mlpack/tests/ann/layer/convolution.cpp b/src/mlpack/tests/ann/layer/convolution.cpp index 4b932bdfe1..6097f88d3d 100644 --- a/src/mlpack/tests/ann/layer/convolution.cpp +++ b/src/mlpack/tests/ann/layer/convolution.cpp @@ -454,7 +454,7 @@ TEST_CASE("NonSquareConvolutionTest", "[ANNLayerTest]") module1.SetWeights(weights1); arma::mat data(49, 10, arma::fill::randu); - arma::mat forwardResult(module1.OutputSize(), 10, arma::fill::zeros); + arma::mat forwardResult(module1.OutputSize(), 10); REQUIRE_NOTHROW(module1.Forward(data, forwardResult)); arma::mat backwardResult(49, 10); REQUIRE_NOTHROW(module1.Backward(data, forwardResult, forwardResult, backwardResult)); diff --git a/src/mlpack/tests/ann/layer/grouped_convolution.cpp b/src/mlpack/tests/ann/layer/grouped_convolution.cpp index d203e669e6..b64565dcbf 100644 --- a/src/mlpack/tests/ann/layer/grouped_convolution.cpp +++ b/src/mlpack/tests/ann/layer/grouped_convolution.cpp @@ -215,7 +215,7 @@ TEST_CASE("NonSquareGroupedConvolutionTest", "[ANNLayerTest]") module1.SetWeights(weights1); arma::mat data(49, 10, arma::fill::randu); - arma::mat forwardResult(module1.OutputSize(), 10, arma::fill::zeros); + arma::mat forwardResult(module1.OutputSize(), 10); REQUIRE_NOTHROW(module1.Forward(data, forwardResult)); arma::mat backwardResult(49, 10); REQUIRE_NOTHROW(module1.Backward(data, forwardResult, forwardResult, backwardResult)); diff --git a/src/mlpack/tests/ann/layer/nearest_interpolation.cpp b/src/mlpack/tests/ann/layer/nearest_interpolation.cpp new file mode 100644 index 0000000000..5bc082546f --- /dev/null +++ b/src/mlpack/tests/ann/layer/nearest_interpolation.cpp @@ -0,0 +1,88 @@ +/** + * @file tests/ann/layer/add.cpp + * @author Ryan Curtin + * + * Tests the nearest interpolation layer + * + * 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 "../../test_catch_tools.hpp" +#include "../../catch.hpp" +#include "../../serialization.hpp" +#include "../ann_test_tools.hpp" + +using namespace mlpack; + +/** + * Simple test for the NearestInterpolation layer + */ +TEST_CASE("NearestInterpolationLayerTest", "[ANNLayerTest]") +{ + arma::mat input, output, unzoomedOutput, expectedOutput; + + size_t inColSize = 2; + size_t inRowSize = 2; + size_t channels = 1; + + double scaleFactor = 2.0f; + + input.zeros(inRowSize * inColSize, channels); + output.zeros(inRowSize * scaleFactor * inColSize * scaleFactor, channels); + unzoomedOutput = input; + input[0] = 1.0; + input[1] = 2.0; + input[2] = 3.0; + input[3] = 4.0; + + mlpack::NearestInterpolation layer; + + + layer = mlpack::NearestInterpolation({scaleFactor, scaleFactor}); + + layer.InputDimensions() = { inRowSize, inColSize, channels }; + layer.ComputeOutputDimensions(); + + expectedOutput << 1.0000 << 1.0000 << 2.0000 << 2.0000 + << 1.0000 << 1.0000 << 2.0000 << 2.0000 + << 3.0000 << 3.0000 << 4.0000 << 4.0000 + << 3.0000 << 3.0000 << 4.0000 << 4.0000 << arma::endr; + + expectedOutput.reshape(16, 1); + layer.Forward(input, output); + CheckMatrices(output - expectedOutput, + arma::zeros(output.n_rows), 1e-4); + + expectedOutput.clear(); + expectedOutput << 4.0000 << 8.0000 + << 12.0000 << 16.0000 << arma::endr; + expectedOutput.reshape(4, 1); + layer.Backward(output, output, unzoomedOutput); + CheckMatrices(unzoomedOutput - expectedOutput, + arma::zeros(input.n_rows), 1e-4); + + arma::mat input1, output1, unzoomedOutput1, expectedOutput1; + inRowSize = 2; + inColSize = 3; + + input1 << 1 << 2 << 3 << arma::endr + << 4 << 5 << 6 << arma::endr; + input1.reshape(6, 1); + output1.zeros(17*23, 1); + unzoomedOutput1.zeros(6, 1); + mlpack::NearestInterpolation layer1({17/2.0f, 23/3.0f}); + + layer1.InputDimensions() = { 2, 3, channels }; + layer1.ComputeOutputDimensions(); + + layer1.Forward(input1, output1); + layer1.Backward(output1, output1, unzoomedOutput1); + REQUIRE(accu(output1) - 1317.00 == Approx(0.0).margin(1e-05)); + REQUIRE(accu(unzoomedOutput1) - 1317.00 == + Approx(0.0).margin(1e-05)); +} diff --git a/src/mlpack/tests/ann/layer_test.cpp b/src/mlpack/tests/ann/layer_test.cpp index 372563f433..d074f3d715 100644 --- a/src/mlpack/tests/ann/layer_test.cpp +++ b/src/mlpack/tests/ann/layer_test.cpp @@ -39,6 +39,7 @@ #include "layer/log_softmax.cpp" #include "layer/max_pooling.cpp" #include "layer/mean_pooling.cpp" +#include "layer/nearest_interpolation.cpp" #include "layer/padding.cpp" #include "layer/parametric_relu.cpp" #include "layer/relu6.cpp" diff --git a/src/mlpack/tests/ann/not_adapted/ann_layer_test.cpp b/src/mlpack/tests/ann/not_adapted/ann_layer_test.cpp index 4ceac2ffbd..f2a813b632 100644 --- a/src/mlpack/tests/ann/not_adapted/ann_layer_test.cpp +++ b/src/mlpack/tests/ann/not_adapted/ann_layer_test.cpp @@ -1875,70 +1875,6 @@ TEST_CASE("LookupLayerParametersTest", "[ANNLayerTest]") } */ -/** - * Simple test for the NearestInterpolation layer - * -TEST_CASE("SimpleNearestInterpolationLayerTest", "[ANNLayerTest]") -{ - // Tested output against torch.nn.Upsample(mode="nearest"). - arma::mat input, output, unzoomedOutput, expectedOutput; - size_t inRowSize = 2; - size_t inColSize = 2; - size_t outRowSize = 5; - size_t outColSize = 7; - size_t depth = 1; - input.zeros(inRowSize * inColSize * depth, 1); - input[0] = 1.0; - input[1] = 3.0; - input[2] = 2.0; - input[3] = 4.0; - NearestInterpolation<> layer(inRowSize, inColSize, outRowSize, - outColSize, depth); - - expectedOutput << 1.0000 << 1.0000 << 1.0000 << 1.0000 << 2.0000 - << 2.0000 << 2.0000 << arma::endr - << 1.0000 << 1.0000 << 1.0000 << 1.0000 << 2.0000 - << 2.0000 << 2.0000 << arma::endr - << 1.0000 << 1.0000 << 1.0000 << 1.0000 << 2.0000 - << 2.0000 << 2.0000 << arma::endr - << 3.0000 << 3.0000 << 3.0000 << 3.0000 << 4.0000 - << 4.0000 << 4.0000 << arma::endr - << 3.0000 << 3.0000 << 3.0000 << 3.0000 << 4.0000 - << 4.0000 << 4.0000 << arma::endr; - expectedOutput.reshape(35, 1); - - layer.Forward(input, output); - CheckMatrices(output - expectedOutput, - arma::zeros(output.n_rows), 1e-4); - - expectedOutput.clear(); - expectedOutput << 12.0000 << 18.0000 << arma::endr - << 24.0000 << 24.0000 << arma::endr; - expectedOutput.reshape(4, 1); - layer.Backward(output, output, unzoomedOutput); - CheckMatrices(unzoomedOutput - expectedOutput, - arma::zeros(input.n_rows), 1e-4); - - arma::mat input1, output1, unzoomedOutput1, expectedOutput1; - inRowSize = 2; - inColSize = 3; - outRowSize = 17; - outColSize = 23; - input1 << 1 << 2 << 3 << arma::endr - << 4 << 5 << 6 << arma::endr; - input1.reshape(6, 1); - NearestInterpolation<> layer1(inRowSize, inColSize, outRowSize, - outColSize, depth); - - layer1.Forward(input1, output1); - layer1.Backward(output1, output1, unzoomedOutput1); - - REQUIRE(accu(output1) - 1317.00 == Approx(0.0).margin(1e-05)); - REQUIRE(accu(unzoomedOutput1) - 1317.00 == - Approx(0.0).margin(1e-05)); -} -*/ - /* * Simple test for the BilinearInterpolation layer * @@ -2219,7 +2155,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module1(1, 1, 3, 3, 1, 1, 0, 0, 4, 4, 6, 6); // Test the forward function. input = arma::linspace(0, 15, 16); - module1.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module1.Parameters() = arma::mat(9 + 1, 1); module1.Parameters()(0) = 1.0; module1.Parameters()(8) = 2.0; module1.Reset(); @@ -2235,7 +2171,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module2(1, 1, 4, 4, 1, 1, 1, 1, 5, 5, 6, 6); // Test the forward function. input = arma::linspace(0, 24, 25); - module2.Parameters() = arma::mat(16 + 1, 1, arma::fill::zeros); + module2.Parameters() = arma::mat(16 + 1, 1); module2.Parameters()(0) = 1.0; module2.Parameters()(3) = 1.0; module2.Parameters()(6) = 1.0; @@ -2255,7 +2191,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module3(1, 1, 3, 3, 1, 1, 1, 1, 5, 5, 5, 5); // Test the forward function. input = arma::linspace(0, 24, 25); - module3.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module3.Parameters() = arma::mat(9 + 1, 1); module3.Parameters()(1) = 2.0; module3.Parameters()(2) = 4.0; module3.Parameters()(3) = 3.0; @@ -2273,7 +2209,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module4(1, 1, 3, 3, 1, 1, 0, 0, 5, 5, 7, 7); // Test the forward function. input = arma::linspace(0, 24, 25); - module4.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module4.Parameters() = arma::mat(9 + 1, 1); module4.Parameters()(2) = 2.0; module4.Parameters()(4) = 4.0; module4.Parameters()(6) = 6.0; @@ -2291,7 +2227,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module5(1, 1, 3, 3, 2, 2, 0, 0, 2, 2, 5, 5); // Test the forward function. input = arma::linspace(0, 3, 4); - module5.Parameters() = arma::mat(25 + 1, 1, arma::fill::zeros); + module5.Parameters() = arma::mat(25 + 1, 1); module5.Parameters()(2) = 8.0; module5.Parameters()(4) = 6.0; module5.Parameters()(6) = 4.0; @@ -2309,7 +2245,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module6(1, 1, 3, 3, 2, 2, 1, 1, 3, 3, 5, 5); // Test the forward function. input = arma::linspace(0, 8, 9); - module6.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module6.Parameters() = arma::mat(9 + 1, 1); module6.Parameters()(0) = 8.0; module6.Parameters()(3) = 6.0; module6.Parameters()(6) = 2.0; @@ -2327,7 +2263,7 @@ TEST_CASE("SimpleTransposedConvolutionLayerTest", "[ANNLayerTest]") TransposedConvolution module7(1, 1, 3, 3, 2, 2, 1, 1, 3, 3, 6, 6); // Test the forward function. input = arma::linspace(0, 8, 9); - module7.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module7.Parameters() = arma::mat(9 + 1, 1); module7.Parameters()(0) = 8.0; module7.Parameters()(2) = 6.0; module7.Parameters()(4) = 2.0; @@ -2484,7 +2420,7 @@ TEST_CASE("SimpleMultiplyMergeLayerTest", "[ANNLayerTest]") // AtrousConvolution<> module1(1, 1, 3, 3, 1, 1, 0, 0, 7, 7, 2, 2); // // Test the Forward function. // input = arma::linspace(0, 48, 49); -// module1.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); +// module1.Parameters() = arma::mat(9 + 1, 1); // module1.Parameters()(0) = 1.0; // module1.Parameters()(8) = 2.0; // module1.Reset(); @@ -2499,7 +2435,7 @@ TEST_CASE("SimpleMultiplyMergeLayerTest", "[ANNLayerTest]") // AtrousConvolution<> module2(1, 1, 3, 3, 2, 2, 0, 0, 7, 7, 2, 2); // // Test the forward function. // input = arma::linspace(0, 48, 49); -// module2.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); +// module2.Parameters() = arma::mat(9 + 1, 1); // module2.Parameters()(0) = 1.0; // module2.Parameters()(3) = 1.0; // module2.Parameters()(6) = 1.0; @@ -2629,7 +2565,7 @@ TEST_CASE("SimpleMultiplyMergeLayerTest", "[ANNLayerTest]") // // Test the Forward function. // input = arma::linspace(0, 48, 49); -// module1.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); +// module1.Parameters() = arma::mat(9 + 1, 1); // module1.Reset(); // module1.Forward(input, output); @@ -2647,7 +2583,7 @@ TEST_CASE("SimpleMultiplyMergeLayerTest", "[ANNLayerTest]") // // Test the forward function. // input = arma::linspace(0, 48, 49); -// module2.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); +// module2.Parameters() = arma::mat(9 + 1, 1); // module2.Reset(); // module2.Forward(input, output); @@ -3401,7 +3337,7 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") // Test the forward function. // Valid Should give the same result. input = arma::linspace(0, 15, 16); - module1.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module1.Parameters() = arma::mat(9 + 1, 1); module1.Reset(); module1.Forward(input, output); // Value calculated using tensorflow.nn.conv2d_transpose(). @@ -3417,7 +3353,7 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") 2, 2, 5, 5, "VALID"); // Test the forward function. input = arma::linspace(0, 3, 4); - module2.Parameters() = arma::mat(25 + 1, 1, arma::fill::zeros); + module2.Parameters() = arma::mat(25 + 1, 1); module2.Parameters()(2) = 8.0; module2.Parameters()(4) = 6.0; module2.Parameters()(6) = 4.0; @@ -3435,7 +3371,7 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") TransposedConvolution module3(1, 1, 3, 3, 2, 2, 0, 0, 3, 3, 3, 3, "SAME"); // Test the forward function. input = arma::linspace(0, 8, 9); - module3.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module3.Parameters() = arma::mat(9 + 1, 1); module3.Reset(); module3.Forward(input, output); REQUIRE(accu(output) == 0); @@ -3452,7 +3388,7 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") 5, 5, 5, 5, "SAME"); // Test the forward function. input = arma::linspace(0, 24, 25); - module4.Parameters() = arma::mat(9 + 1, 1, arma::fill::zeros); + module4.Parameters() = arma::mat(9 + 1, 1); module4.Reset(); module4.Forward(input, output); REQUIRE(accu(output) == 0); @@ -3466,7 +3402,7 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") TransposedConvolution module5(1, 1, 3, 3, 2, 2, 0, 0, 2, 2, 2, 2, "SAME"); // Test the forward function. input = arma::linspace(0, 3, 4); - module5.Parameters() = arma::mat(25 + 1, 1, arma::fill::zeros); + module5.Parameters() = arma::mat(25 + 1, 1); module5.Reset(); module5.Forward(input, output); REQUIRE(accu(output) == 0); @@ -3480,7 +3416,7 @@ TEST_CASE("TransposedConvolutionLayerPaddingTest", "[ANNLayerTest]") TransposedConvolution module6(1, 1, 4, 4, 1, 1, 1, 1, 5, 5, 5, 5, "SAME"); // Test the forward function. input = arma::linspace(0, 24, 25); - module6.Parameters() = arma::mat(16 + 1, 1, arma::fill::zeros); + module6.Parameters() = arma::mat(16 + 1, 1); module6.Reset(); module6.Forward(input, output); REQUIRE(accu(output) == 0); diff --git a/src/mlpack/tests/ann/not_adapted/rbm_network_test.cpp b/src/mlpack/tests/ann/not_adapted/rbm_network_test.cpp index feacd4339a..a876a2aeac 100644 --- a/src/mlpack/tests/ann/not_adapted/rbm_network_test.cpp +++ b/src/mlpack/tests/ann/not_adapted/rbm_network_test.cpp @@ -227,7 +227,7 @@ void BuildVanillaNetwork(MatType& trainData, // Check free energy. arma::Mat freeEnergy = MatType( "-0.87523715, 0.50615066, 0.46923476, 1.21509084;"); - arma::vec calculatedFreeEnergy(4, arma::fill::zeros); + arma::vec calculatedFreeEnergy(4); for (size_t i = 0; i < trainData.n_cols; ++i) { calculatedFreeEnergy(i) = model.FreeEnergy(trainData.col(i)); diff --git a/src/mlpack/tests/ann/recurrent_network_test.cpp b/src/mlpack/tests/ann/recurrent_network_test.cpp index 5dc3f5676a..fc150ab516 100644 --- a/src/mlpack/tests/ann/recurrent_network_test.cpp +++ b/src/mlpack/tests/ann/recurrent_network_test.cpp @@ -362,7 +362,7 @@ TEST_CASE("LargeRhoValueRnnTest", "[RecurrentNetworkTest]") // Rows: number of dimensions. // Cols: number of sequences/points. // Slices: number of steps in sequences. - MatType result(numLetters, 1, strLen, arma::fill::zeros); + MatType result(numLetters, 1, strLen); for (size_t i = 0; i < strLen; ++i) { result.at(static_cast(line[i]), 0, i) = 1.0; @@ -375,7 +375,7 @@ TEST_CASE("LargeRhoValueRnnTest", "[RecurrentNetworkTest]") const auto strLen = strlen(line); // Responses for NegativeLogLikelihood should be // non-one-hot-encoded class IDs (from 0 to num_classes - 1). - MatType result(1, 1, strLen, arma::fill::zeros); + MatType result(1, 1, strLen); // The response is the *next* letter in the sequence. for (size_t i = 0; i < strLen - 1; ++i) { diff --git a/src/mlpack/tests/callback_test.cpp b/src/mlpack/tests/callback_test.cpp index bd98dcad85..ae9b912c9d 100644 --- a/src/mlpack/tests/callback_test.cpp +++ b/src/mlpack/tests/callback_test.cpp @@ -151,12 +151,13 @@ TEST_CASE("LMNNWithOptimizerCallback", "[CallbackTest]") " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - LMNN<> lmnn(dataset, labels, 1); + LMNN<> lmnn(1); arma::mat outputMatrix; std::stringstream stream; - lmnn.LearnDistance(outputMatrix, ens::ProgressBar(70, stream)); + lmnn.LearnDistance(dataset, labels, outputMatrix, + ens::ProgressBar(70, stream)); REQUIRE(stream.str().length() > 0); } @@ -170,12 +171,11 @@ TEST_CASE("NCAWithOptimizerCallback", "[CallbackTest]") " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - NCA nca(data, labels); - arma::mat outputMatrix; std::stringstream stream; - nca.LearnDistance(outputMatrix, ens::ProgressBar(70, stream)); + NCA nca; + nca.LearnDistance(data, labels, outputMatrix, ens::ProgressBar(70, stream)); REQUIRE(stream.str().length() > 0); } diff --git a/src/mlpack/tests/cv_test.cpp b/src/mlpack/tests/cv_test.cpp index 8ee7ec8e48..b78e651f9d 100644 --- a/src/mlpack/tests/cv_test.cpp +++ b/src/mlpack/tests/cv_test.cpp @@ -460,7 +460,7 @@ TEST_CASE("FilterNANCVTest", "[CVTest]") // Create a dataset with only one positive label, so it will not be in every // fold. arma::mat data(3, 10, arma::fill::randu); - arma::Row labels(10, arma::fill::zeros); + arma::Row labels(10); labels[0] = 1; const size_t numClasses = 2; diff --git a/src/mlpack/tests/dbscan_test.cpp b/src/mlpack/tests/dbscan_test.cpp index 0c5617d87b..eabf4e4aa0 100644 --- a/src/mlpack/tests/dbscan_test.cpp +++ b/src/mlpack/tests/dbscan_test.cpp @@ -17,18 +17,6 @@ using namespace mlpack; -/** - * A couple of handful declarations for float32 testing. - * These will be removed when we refactor the Bounds to accept MatType. - * For now, we will keep the following declarations. - */ -template -using FloatHRectBound = HRectBound; - -template -using FloatKDTree = BinarySpaceTree; - TEST_CASE("OneClusterTest", "[DBSCANTest]") { // Make sure that if we have points in the unit box, and if we set epsilon @@ -229,7 +217,7 @@ TEST_CASE("Float32OutlierSingleModeTest", "[DBSCANTest]") DBSCAN, - FloatKDTree>, + KDTree>, OrderedPointSelection> d(0.1, 3, false); arma::Row assignments; diff --git a/src/mlpack/tests/distribution_test.cpp b/src/mlpack/tests/distribution_test.cpp index 78dab01161..0862da6c55 100644 --- a/src/mlpack/tests/distribution_test.cpp +++ b/src/mlpack/tests/distribution_test.cpp @@ -458,23 +458,23 @@ TEST_CASE("GaussianDistributionRandomTest", "[DistributionTest]") GaussianDistribution d(mean, cov); - arma::mat obs(2, 5000); + arma::mat obs(2, 7500); - for (size_t i = 0; i < 5000; ++i) + for (size_t i = 0; i < 7500; ++i) obs.col(i) = d.Random(); // Now make sure that reflects the actual distribution. arma::vec obsMean = arma::mean(obs, 1); arma::mat obsCov = ColumnCovariance(obs); - // 10% tolerance because this can be noisy. - REQUIRE(obsMean[0] == Approx(mean[0]).epsilon(0.1)); - REQUIRE(obsMean[1] == Approx(mean[1]).epsilon(0.1)); + // 12.5% tolerance because this can be noisy. + REQUIRE(obsMean[0] == Approx(mean[0]).epsilon(0.125)); + REQUIRE(obsMean[1] == Approx(mean[1]).epsilon(0.125)); - REQUIRE(obsCov(0, 0) == Approx(cov(0, 0)).epsilon(0.1)); - REQUIRE(obsCov(0, 1) == Approx(cov(0, 1)).epsilon(0.1)); - REQUIRE(obsCov(1, 0) == Approx(cov(1, 0)).epsilon(0.1)); - REQUIRE(obsCov(1, 1) == Approx(cov(1, 1)).epsilon(0.1)); + REQUIRE(obsCov(0, 0) == Approx(cov(0, 0)).epsilon(0.125)); + REQUIRE(obsCov(0, 1) == Approx(cov(0, 1)).epsilon(0.125)); + REQUIRE(obsCov(1, 0) == Approx(cov(1, 0)).epsilon(0.125)); + REQUIRE(obsCov(1, 1) == Approx(cov(1, 1)).epsilon(0.125)); } /** @@ -527,7 +527,7 @@ TEST_CASE("GaussianDistributionTrainWithProbabilitiesTest", arma::vec cov = ("2.0"); GaussianDistribution dist(mean, cov); - size_t N = 5000; + size_t N = 15000; size_t d = 1; arma::mat rdata(d, N); @@ -548,10 +548,10 @@ TEST_CASE("GaussianDistributionTrainWithProbabilitiesTest", REQUIRE(guDist.Mean()[0] == Approx(guDist2.Mean()[0]).epsilon(0.06)); REQUIRE(guDist.Covariance()[0] == - Approx(guDist2.Covariance()[0]).epsilon(0.06)); + Approx(guDist2.Covariance()[0]).epsilon(0.08)); REQUIRE(guDist.Mean()[0] == Approx(mean[0]).epsilon(0.06)); - REQUIRE(guDist.Covariance()[0] == Approx(cov[0]).epsilon(0.06)); + REQUIRE(guDist.Covariance()[0] == Approx(cov[0]).epsilon(0.08)); } /** @@ -819,11 +819,11 @@ TEST_CASE("GammaDistributionTrainTwoDistProbabilities1Test", GammaDistribution gDist; gDist.Train(rdata, probabilities); - REQUIRE(alphaReal2 == Approx(gDist.Alpha(0)).epsilon(0.05)); - REQUIRE(betaReal2 == Approx(gDist.Beta(0)).epsilon(0.05)); + REQUIRE(alphaReal2 == Approx(gDist.Alpha(0)).epsilon(0.075)); + REQUIRE(betaReal2 == Approx(gDist.Beta(0)).epsilon(0.075)); - REQUIRE(alphaReal2 == Approx(gDist.Alpha(1)).epsilon(0.05)); - REQUIRE(betaReal2 == Approx(gDist.Beta(1)).epsilon(0.05)); + REQUIRE(alphaReal2 == Approx(gDist.Alpha(1)).epsilon(0.075)); + REQUIRE(betaReal2 == Approx(gDist.Beta(1)).epsilon(0.075)); } /** @@ -933,7 +933,7 @@ TEST_CASE("GammaDistributionTrainStatisticsTest", "[DistributionTest]") TEST_CASE("GammaDistributionRandomTest", "[DistributionTest]") { const arma::vec a("2.0 2.5 3.0"), b("0.4 0.6 1.3"); - const size_t numPoints = 2000; + const size_t numPoints = 4000; // Distribution to generate points. GammaDistribution d1(a, b); @@ -946,8 +946,8 @@ TEST_CASE("GammaDistributionRandomTest", "[DistributionTest]") GammaDistribution d2(data); for (size_t i = 0; i < 3; ++i) { - REQUIRE(d2.Alpha(i) == Approx(a(i)).epsilon(0.1)); // Within 10% - REQUIRE(d2.Beta(i) == Approx(b(i)).epsilon(0.1)); + REQUIRE(d2.Alpha(i) == Approx(a(i)).epsilon(0.15)); // Within 15% + REQUIRE(d2.Beta(i) == Approx(b(i)).epsilon(0.15)); } } diff --git a/src/mlpack/tests/kde_test.cpp b/src/mlpack/tests/kde_test.cpp index 1f79bb4a38..69aaf11b6b 100644 --- a/src/mlpack/tests/kde_test.cpp +++ b/src/mlpack/tests/kde_test.cpp @@ -88,8 +88,8 @@ TEST_CASE("KDETreeAsArguments", "[KDETest]") {-2.1, 1.0} }; arma::inplace_trans(reference); arma::inplace_trans(query); - arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec estimationsResult = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec estimations = arma::vec(query.n_cols); + arma::vec estimationsResult = arma::vec(query.n_cols); const double kernelBandwidth = 0.8; // Get brute force results. @@ -124,8 +124,8 @@ TEST_CASE("GaussianKDEBruteForceTest", "[KDETest]") { arma::mat reference = arma::randu(2, 200); arma::mat query = arma::randu(2, 60); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.12; const double relError = 0.05; @@ -155,8 +155,8 @@ TEST_CASE("GaussianSingleKDEBruteForceTest", "[KDETest]") { arma::mat reference = arma::randu(2, 300); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.3; const double relError = 0.04; @@ -187,8 +187,8 @@ TEST_CASE("EpanechnikovCoverSingleKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 300); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 1.1; const double relError = 0.08; @@ -219,8 +219,8 @@ TEST_CASE("GaussianCoverSingleKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 300); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 1.1; const double relError = 0.08; @@ -251,8 +251,8 @@ TEST_CASE("EpanechnikovOctreeSingleKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 300); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 1.0; const double relError = 0.05; @@ -282,8 +282,8 @@ TEST_CASE("BallTreeGaussianKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 200); arma::mat query = arma::randu(2, 60); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.4; const double relError = 0.05; @@ -322,8 +322,8 @@ TEST_CASE("OctreeGaussianKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 500); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.3; const double relError = 0.01; @@ -353,8 +353,8 @@ TEST_CASE("RTreeGaussianKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 500); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.3; const double relError = 0.01; @@ -385,8 +385,8 @@ TEST_CASE("StandardCoverTreeGaussianKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 500); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.3; const double relError = 0.01; @@ -417,8 +417,8 @@ TEST_CASE("StandardCoverTreeEpanechnikovKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 500); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.3; const double relError = 0.01; @@ -451,8 +451,8 @@ TEST_CASE("DuplicatedReferenceSampleKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 30); arma::mat query = arma::randu(2, 10); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.4; const double relError = 0.05; @@ -494,7 +494,7 @@ TEST_CASE("DuplicatedQuerySampleKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 30); arma::mat query = arma::randu(2, 10); - arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec estimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.4; const double relError = 0.05; @@ -529,8 +529,8 @@ TEST_CASE("BreadthFirstKDETest", "[KDETest]") { arma::mat reference = arma::randu(2, 200); arma::mat query = arma::randu(2, 60); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.8; const double relError = 0.01; @@ -565,8 +565,8 @@ TEST_CASE("OneDimensionalTest", "[KDETest]") { arma::mat reference = arma::randu(1, 200); arma::mat query = arma::randu(1, 60); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.7; const double relError = 0.01; @@ -596,7 +596,7 @@ TEST_CASE("EmptyReferenceTest", "[KDETest]") { arma::mat reference; arma::mat query = arma::randu(1, 10); - arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec estimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.7; const double relError = 0.01; @@ -626,7 +626,7 @@ TEST_CASE("EvaluationMatchDimensionsTest", "[KDETest]") { arma::mat reference = arma::randu(3, 10); arma::mat query = arma::randu(1, 10); - arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec estimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.7; const double relError = 0.01; @@ -660,7 +660,7 @@ TEST_CASE("EmptyQuerySetTest", "[KDETest]") arma::mat reference = arma::randu(1, 10); arma::mat query; // Set estimations to the wrong size. - arma::vec estimations(33, arma::fill::zeros); + arma::vec estimations(33); const double kernelBandwidth = 0.7; const double relError = 0.01; @@ -719,7 +719,7 @@ TEST_CASE("KDESerializationTest", "[KDETest]") // Get estimations to compare. arma::mat query = arma::randu(4, 100);; - arma::vec estimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec estimations = arma::vec(query.n_cols); kde.Evaluate(query, estimations); // Initialize serialized objects. @@ -775,9 +775,9 @@ TEST_CASE("KDESerializationTest", "[KDETest]") REQUIRE(kdeBinary.MCBreakCoef() == Approx(breakCoef).epsilon(1e-10)); // Test if execution gives the same result. - arma::vec xmlEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec textEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec binEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec xmlEstimations = arma::vec(query.n_cols); + arma::vec textEstimations = arma::vec(query.n_cols); + arma::vec binEstimations = arma::vec(query.n_cols); kdeXml.Evaluate(query, xmlEstimations); kdeText.Evaluate(query, textEstimations); @@ -883,8 +883,8 @@ TEST_CASE("GaussianSingleKDTreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.35; const double relError = 0.05; @@ -933,8 +933,8 @@ TEST_CASE("GaussianSingleCoverTreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.35; const double relError = 0.05; @@ -983,8 +983,8 @@ TEST_CASE("GaussianSingleOctreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 100); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.55; const double relError = 0.02; @@ -1033,8 +1033,8 @@ TEST_CASE("GaussianDualKDTreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.4; const double relError = 0.05; @@ -1083,8 +1083,8 @@ TEST_CASE("GaussianDualCoverTreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.5; const double relError = 0.025; @@ -1133,8 +1133,8 @@ TEST_CASE("GaussianDualOctreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.7; const double relError = 0.03; @@ -1183,8 +1183,8 @@ TEST_CASE("GaussianBreadthDualKDTreeMonteCarloKDE", "[KDETest]") { arma::mat reference = arma::randu(2, 3000); arma::mat query = arma::randu(2, 200); - arma::vec bfEstimations = arma::vec(query.n_cols, arma::fill::zeros); - arma::vec treeEstimations = arma::vec(query.n_cols, arma::fill::zeros); + arma::vec bfEstimations = arma::vec(query.n_cols); + arma::vec treeEstimations = arma::vec(query.n_cols); const double kernelBandwidth = 0.7; const double relError = 0.025; diff --git a/src/mlpack/tests/knn_test.cpp b/src/mlpack/tests/knn_test.cpp index 28a2cb59b2..b238fa6309 100644 --- a/src/mlpack/tests/knn_test.cpp +++ b/src/mlpack/tests/knn_test.cpp @@ -16,18 +16,6 @@ using namespace mlpack; -/** - * A couple of handful declarations for float32 testing. - * These will be removed when we refactor the Bounds to accept MatType. - * For now, we will keep the following declarations. - */ -template -using FloatHRectBound = HRectBound; - -template -using FloatKDTree = BinarySpaceTree; - /** * Test that Unmap() works in the dual-tree case (see unmap.hpp). */ @@ -777,14 +765,12 @@ TEST_CASE("KNNSingleTreeVsNaiveF32", "[KNNTest]") NeighborSearch knn(dataset, SINGLE_TREE_MODE); + arma::fmat> knn(dataset, SINGLE_TREE_MODE); // Set up computation for naive mode. NeighborSearch naive(dataset, NAIVE_MODE); + arma::fmat> naive(dataset, NAIVE_MODE); arma::Mat neighborsTree; arma::fmat distancesTree; diff --git a/src/mlpack/tests/lmnn_test.cpp b/src/mlpack/tests/lmnn_test.cpp index 44eb0b0d66..394474511b 100644 --- a/src/mlpack/tests/lmnn_test.cpp +++ b/src/mlpack/tests/lmnn_test.cpp @@ -30,25 +30,27 @@ using namespace ens; * The target neighbors function should be correct. * point. */ -TEST_CASE("LMNNTargetNeighborsTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNTargetNeighborsTest", "[LMNNTest]", float, double) { - // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + typedef TestType ElemType; - Constraints<> constraint(dataset, labels, 1); + // Useful but simple dataset with six points and two classes. + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; + + Constraints, arma::Row> constraint(dataset, + labels, 1); // Calculate norm of datapoints. - arma::vec norm(dataset.n_cols); + arma::Col norm(dataset.n_cols); for (size_t i = 0; i < dataset.n_cols; ++i) { norm(i) = arma::norm(dataset.col(i)); } //! Store target neighbors of data points. - arma::Mat targetNeighbors = - arma::Mat(1, dataset.n_cols, arma::fill::zeros); + arma::umat targetNeighbors(1, dataset.n_cols); constraint.TargetNeighbors(targetNeighbors, dataset, labels, norm); @@ -63,25 +65,27 @@ TEST_CASE("LMNNTargetNeighborsTest", "[LMNNTest]") /** * The impostors function should be correct. */ -TEST_CASE("LMNNImpostorsTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNImpostorsTest", "[LMNNTest]", float, double) { - // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + typedef TestType ElemType; - Constraints<> constraint(dataset, labels, 1); + // Useful but simple dataset with six points and two classes. + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; + + Constraints, arma::Row> constraint(dataset, + labels, 1); // Calculate norm of datapoints. - arma::vec norm(dataset.n_cols); + arma::Col norm(dataset.n_cols); for (size_t i = 0; i < dataset.n_cols; ++i) { norm(i) = arma::norm(dataset.col(i)); } //! Store impostors of data points. - arma::Mat impostors = - arma::Mat(1, dataset.n_cols, arma::fill::zeros); + arma::umat impostors(1, dataset.n_cols); constraint.Impostors(impostors, dataset, labels, norm); @@ -101,300 +105,339 @@ TEST_CASE("LMNNImpostorsTest", "[LMNNTest]") * The LMNN function should return the identity matrix as its initial * point. */ -TEST_CASE("LMNNInitialPointTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNInitialPointTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Cheap fake dataset. - arma::mat dataset = arma::randu(5, 5); + arma::Mat dataset = arma::randu>(5, 5); arma::Row labels = "0 1 1 0 0"; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.5, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.5, 1); // Verify the initial point is the identity matrix. - arma::mat initialPoint = lmnnfn.GetInitialPoint(); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + const double margin = std::is_same::value ? 1e-4 : 1e-5; + arma::Mat initialPoint = lmnnfn.GetInitialPoint(); for (int row = 0; row < 5; row++) { for (int col = 0; col < 5; col++) { if (row == col) - REQUIRE(initialPoint(row, col) == Approx(1.0).epsilon(1e-7)); + REQUIRE(initialPoint(row, col) == Approx(1.0).epsilon(eps)); else - REQUIRE(initialPoint(row, col) == Approx(0.0).margin(1e-5)); + REQUIRE(initialPoint(row, col) == Approx(0.0).margin(margin)); } } } /*** - * Ensure non-seprable objective function is right. + * Ensure non-separable objective function is right. */ -TEST_CASE("LMNNInitialEvaluationTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNInitialEvaluationTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - double objective = lmnnfn.Evaluate(arma::eye(2, 2)); + ElemType objective = lmnnfn.Evaluate(arma::eye>(2, 2)); // Result calculated by hand. - REQUIRE(objective == Approx(9.456).epsilon(1e-7)); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + REQUIRE(objective == Approx(9.456).epsilon(eps)); } /** - * Ensure non-seprable gradient function is right. + * Ensure non-separable gradient function is right. */ -TEST_CASE("LMNNInitialGradientTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNInitialGradientTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - arma::mat gradient; - arma::mat coordinates = arma::eye(2, 2); + arma::Mat gradient; + arma::Mat coordinates = arma::eye>(2, 2); lmnnfn.Gradient(coordinates, gradient); // Result calculated by hand. - REQUIRE(gradient(0, 0) == Approx(-0.288).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).margin(1e-5)); - REQUIRE(gradient(0, 1) == Approx(0.0).margin(1e-5)); - REQUIRE(gradient(1, 1) == Approx(12.0).epsilon(1e-7)); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + const double margin = std::is_same::value ? 1e-4 : 1e-5; + REQUIRE(gradient(0, 0) == Approx(-0.288).epsilon(eps)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(12.0).epsilon(eps)); } /*** - * Ensure non-seprable EvaluateWithGradient function is right. + * Ensure non-separable EvaluateWithGradient function is right. */ -TEST_CASE("LMNNInitialEvaluateWithGradientTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNInitialEvaluateWithGradientTest", "[LMNNTest]", float, + double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - arma::mat gradient; - arma::mat coordinates = arma::eye(2, 2); - double objective = lmnnfn.EvaluateWithGradient(coordinates, gradient); + arma::Mat gradient; + arma::Mat coordinates = arma::eye>(2, 2); + ElemType objective = lmnnfn.EvaluateWithGradient(coordinates, gradient); + + const double eps = std::is_same::value ? 1e-4 : 1e-7; + const double margin = std::is_same::value ? 1e-4 : 1e-5; // Result calculated by hand. - REQUIRE(objective == Approx(9.456).epsilon(1e-7)); + REQUIRE(objective == Approx(9.456).epsilon(eps)); // Check Gradient - REQUIRE(gradient(0, 0) == Approx(-0.288).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).margin(1e-5)); - REQUIRE(gradient(0, 1) == Approx(0.0).margin(1e-5)); - REQUIRE(gradient(1, 1) == Approx(12.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.288).epsilon(eps)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(12.0).epsilon(eps)); } /** * Ensure the separable objective function is right. */ -TEST_CASE("LMNNSeparableObjectiveTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNSeparableObjectiveTest", "[LMNNTest]", float, double) { - // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + typedef TestType ElemType; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + // Useful but simple dataset with six points and two classes. + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; + + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); // Result calculated by hand. - arma::mat coordinates = arma::eye(2, 2); - REQUIRE(lmnnfn.Evaluate(coordinates, 0, 1) == Approx(1.576).epsilon(1e-7)); - REQUIRE(lmnnfn.Evaluate(coordinates, 1, 1) == Approx(1.576).epsilon(1e-7)); - REQUIRE(lmnnfn.Evaluate(coordinates, 2, 1) == Approx(1.576).epsilon(1e-7)); - REQUIRE(lmnnfn.Evaluate(coordinates, 3, 1) == Approx(1.576).epsilon(1e-7)); - REQUIRE(lmnnfn.Evaluate(coordinates, 4, 1) == Approx(1.576).epsilon(1e-7)); - REQUIRE(lmnnfn.Evaluate(coordinates, 5, 1) == Approx(1.576).epsilon(1e-7)); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + arma::Mat coordinates = arma::eye>(2, 2); + REQUIRE(lmnnfn.Evaluate(coordinates, 0, 1) == Approx(1.576).epsilon(eps)); + REQUIRE(lmnnfn.Evaluate(coordinates, 1, 1) == Approx(1.576).epsilon(eps)); + REQUIRE(lmnnfn.Evaluate(coordinates, 2, 1) == Approx(1.576).epsilon(eps)); + REQUIRE(lmnnfn.Evaluate(coordinates, 3, 1) == Approx(1.576).epsilon(eps)); + REQUIRE(lmnnfn.Evaluate(coordinates, 4, 1) == Approx(1.576).epsilon(eps)); + REQUIRE(lmnnfn.Evaluate(coordinates, 5, 1) == Approx(1.576).epsilon(eps)); } /** * Ensure the separable gradient is right. */ -TEST_CASE("LMNNSeparableGradientTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNSeparableGradientTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - arma::mat coordinates = arma::eye(2, 2); - arma::mat gradient(2, 2); + arma::Mat coordinates = arma::eye>(2, 2); + arma::Mat gradient(2, 2); lmnnfn.Gradient(coordinates, 0, gradient, 1); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + const double margin = std::is_same::value ? 1e-4 : 1e-5; + + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); lmnnfn.Gradient(coordinates, 1, gradient, 1); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); lmnnfn.Gradient(coordinates, 2, gradient, 1); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); lmnnfn.Gradient(coordinates, 3, gradient, 1); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); lmnnfn.Gradient(coordinates, 4, gradient, 1); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); lmnnfn.Gradient(coordinates, 5, gradient, 1); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); } /** * Ensure the separable EvaluateWithGradient function is right. */ -TEST_CASE("LMNNSeparableEvaluateWithGradientTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNSeparableEvaluateWithGradientTest", "[LMNNTest]", float, + double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - arma::mat coordinates = arma::eye(2, 2); - arma::mat gradient(2, 2); + arma::Mat coordinates = arma::eye>(2, 2); + arma::Mat gradient(2, 2); - double objective = lmnnfn.EvaluateWithGradient(coordinates, 0, gradient, 1); + ElemType objective = lmnnfn.EvaluateWithGradient(coordinates, 0, gradient, 1); - REQUIRE(objective == Approx(1.576).epsilon(1e-7)); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + const double margin = std::is_same::value ? 1e-4 : 1e-5; - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(objective == Approx(1.576).epsilon(eps)); + + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); objective = lmnnfn.EvaluateWithGradient(coordinates, 1, gradient, 1); - REQUIRE(objective == Approx(1.576).epsilon(1e-7)); + REQUIRE(objective == Approx(1.576).epsilon(eps)); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); objective = lmnnfn.EvaluateWithGradient(coordinates, 2, gradient, 1); - REQUIRE(objective == Approx(1.576).epsilon(1e-7)); + REQUIRE(objective == Approx(1.576).epsilon(eps)); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); objective = lmnnfn.EvaluateWithGradient(coordinates, 3, gradient, 1); - REQUIRE(objective == Approx(1.576).epsilon(1e-7)); + REQUIRE(objective == Approx(1.576).epsilon(eps)); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); objective = lmnnfn.EvaluateWithGradient(coordinates, 4, gradient, 1); - REQUIRE(objective == Approx(1.576).epsilon(1e-7)); + REQUIRE(objective == Approx(1.576).epsilon(eps)); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); objective = lmnnfn.EvaluateWithGradient(coordinates, 5, gradient, 1); - REQUIRE(objective == Approx(1.576).epsilon(1e-7)); + REQUIRE(objective == Approx(1.576).epsilon(eps)); - REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(1e-7)); - REQUIRE(gradient(0, 1) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 0) == Approx(0.0).epsilon(1e-7)); - REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(1e-7)); + REQUIRE(gradient(0, 0) == Approx(-0.048).epsilon(eps)); + REQUIRE(gradient(0, 1) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 0) == Approx(0.0).margin(margin)); + REQUIRE(gradient(1, 1) == Approx(2.0).epsilon(eps)); } // Check that final objective value using SGD optimizer is optimal. -TEST_CASE("LMNNSGDSimpleDatasetTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNSGDSimpleDatasetTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNN<> lmnn(dataset, labels, 1); + LMNN<> lmnn(1); - arma::mat outputMatrix; - lmnn.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + lmnn.LearnDistance(dataset, labels, outputMatrix); // Ensure that the objective function is better now. - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - double initObj = lmnnfn.Evaluate(arma::eye(2, 2)); - double finalObj = lmnnfn.Evaluate(outputMatrix); + ElemType initObj = lmnnfn.Evaluate(arma::eye>(2, 2)); + ElemType finalObj = lmnnfn.Evaluate(outputMatrix); // finalObj must be less than initObj. REQUIRE(finalObj < initObj); } // Check that final objective value using L-BFGS optimizer is optimal. -TEST_CASE("LMNNLBFGSSimpleDatasetTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNLBFGSSimpleDatasetTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; - LMNN lmnn(dataset, labels, 1); + LMNN lmnn(1); - arma::mat outputMatrix; - lmnn.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + ens::L_BFGS lbfgs; + lmnn.LearnDistance(dataset, labels, outputMatrix, lbfgs); // Ensure that the objective function is better now. - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); - double initObj = lmnnfn.Evaluate(arma::eye(2, 2)); - double finalObj = lmnnfn.Evaluate(outputMatrix); + ElemType initObj = lmnnfn.Evaluate(arma::eye>(2, 2)); + ElemType finalObj = lmnnfn.Evaluate(outputMatrix); // finalObj must be less than initObj. REQUIRE(finalObj < initObj); } -double KnnAccuracy(const arma::mat& dataset, - const arma::Row& labels, +template +double KnnAccuracy(const MatType& dataset, + const LabelsType& labels, const size_t k) { - arma::Row uniqueLabels = arma::unique(labels); + typedef typename MatType::elem_type ElemType; + + LabelsType uniqueLabels = arma::unique(labels); arma::Mat neighbors; - arma::mat distances; + arma::Mat distances; - KNN knn; + NeighborSearch knn; knn.Train(dataset); knn.Search(k, neighbors, distances); @@ -404,43 +447,44 @@ double KnnAccuracy(const arma::mat& dataset, for (size_t i = 0; i < dataset.n_cols; ++i) { - arma::vec Map; - Map.zeros(uniqueLabels.n_cols); + arma::Col m; + m.zeros(uniqueLabels.n_cols); for (size_t j = 0; j < k; ++j) - Map(labels(neighbors(j, i))) += - 1 / std::pow(distances(j, i) + 1, 2); + m(labels(neighbors(j, i))) += 1 / std::pow(distances(j, i) + 1, 2); - size_t index = ConvTo::From(arma::find(Map - == arma::max(Map))); + size_t index = ConvTo::From(arma::find(m == arma::max(m))); // Increase count if labels match. if (index == labels(i)) count++; } - // return accuracy. + // Return accuracy. return ((double) count / dataset.n_cols) * 100; } // Check that final accuracy is greater than initial accuracy on // simple dataset. -TEST_CASE("LMNNAccuracyTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNAccuracyTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; // Taking k = 3 as the case of k = 1 can be easily observed. double initAccuracy = KnnAccuracy(dataset, labels, 3); - LMNN<> lmnn(dataset, labels, 2); + LMNN<> lmnn(2); - arma::mat outputMatrix; - lmnn.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + lmnn.LearnDistance(dataset, labels, outputMatrix); - double finalAccuracy = KnnAccuracy(outputMatrix * dataset, labels, 3); + arma::Mat transformedData = outputMatrix * dataset; + double finalAccuracy = KnnAccuracy(transformedData, labels, 3); // finalObj must be less than initObj. REQUIRE(initAccuracy < finalAccuracy); @@ -452,18 +496,20 @@ TEST_CASE("LMNNAccuracyTest", "[LMNNTest]") // Check that accuracy while learning square distance matrix is the same as when // we are learning low rank matrix. I'm ok if this passes only once out of // three tries. -TEST_CASE("LMNNLowRankAccuracyLBFGSTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNLowRankAccuracyLBFGSTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + bool success = false; for (size_t trial = 0; trial < 3; ++trial) { - arma::mat dataPart1; + arma::Mat dataPart1; dataPart1.randn(5, 50); arma::Row labelsPart1(50); labelsPart1.fill(0); - arma::mat dataPart2; + arma::Mat dataPart2; dataPart2.randn(5, 50); arma::Row labelsPart2(50); @@ -473,26 +519,29 @@ TEST_CASE("LMNNLowRankAccuracyLBFGSTest", "[LMNNTest]") arma::uvec ordering = arma::shuffle(arma::linspace(0, 99, 100)); // Generate datasets. - arma::mat dataset = join_rows(dataPart1, dataPart2); + arma::Mat dataset = join_rows(dataPart1, dataPart2); dataset = dataset.cols(ordering); // Generate labels. arma::Row labels = join_rows(labelsPart1, labelsPart2); labels = labels.cols(ordering); - LMNN lmnn(dataset, labels, 1); + LMNN lmnn(1); // Learn a square matrix. - arma::mat outputMatrix; - lmnn.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + L_BFGS lbfgs; + lmnn.LearnDistance(dataset, labels, outputMatrix, lbfgs); - double acc1 = KnnAccuracy(outputMatrix * dataset, labels, 1); + arma::Mat transformedData = outputMatrix * dataset; + double acc1 = KnnAccuracy(transformedData, labels, 1); // Learn a low rank matrix. - outputMatrix = arma::randu(4, 5); - lmnn.LearnDistance(outputMatrix); + outputMatrix = arma::randu>(4, 5); + lmnn.LearnDistance(dataset, labels, outputMatrix, lbfgs); - double acc2 = KnnAccuracy(outputMatrix * dataset, labels, 1); + transformedData = outputMatrix * dataset; + double acc2 = KnnAccuracy(transformedData, labels, 1); // We keep the tolerance very high. We need to ensure the accuracy drop // isn't any more than 10%. @@ -507,18 +556,20 @@ TEST_CASE("LMNNLowRankAccuracyLBFGSTest", "[LMNNTest]") // Check that accuracy while learning square distance matrix is the same as when // we are learning low rank matrix. I'm ok if this passes only once out of // three tries. -TEST_CASE("LMNNLowRankAccuracyTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNLowRankAccuracyTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + bool success = false; for (size_t trial = 0; trial < 3; ++trial) { - arma::mat dataPart1; + arma::Mat dataPart1; dataPart1.randn(5, 50); arma::Row labelsPart1(50); labelsPart1.fill(0); - arma::mat dataPart2; + arma::Mat dataPart2; dataPart2.randn(5, 50); arma::Row labelsPart2(50); @@ -528,26 +579,28 @@ TEST_CASE("LMNNLowRankAccuracyTest", "[LMNNTest]") arma::uvec ordering = arma::shuffle(arma::linspace(0, 99, 100)); // Generate datasets. - arma::mat dataset = join_rows(dataPart1, dataPart2); + arma::Mat dataset = join_rows(dataPart1, dataPart2); dataset = dataset.cols(ordering); // Generate labels. arma::Row labels = join_rows(labelsPart1, labelsPart2); labels = labels.cols(ordering); - LMNN<> lmnn(dataset, labels, 1); + LMNN<> lmnn(1); // Learn a square matrix. - arma::mat outputMatrix; - lmnn.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + lmnn.LearnDistance(dataset, labels, outputMatrix); - double acc1 = KnnAccuracy(outputMatrix * dataset, labels, 1); + arma::Mat transformedData = outputMatrix * dataset; + double acc1 = KnnAccuracy(transformedData, labels, 1); // Learn a low rank matrix. - outputMatrix = arma::randu(4, 5); - lmnn.LearnDistance(outputMatrix); + outputMatrix = arma::randu>(4, 5); + lmnn.LearnDistance(dataset, labels, outputMatrix); - double acc2 = KnnAccuracy(outputMatrix * dataset, labels, 1); + transformedData = outputMatrix * dataset; + double acc2 = KnnAccuracy(transformedData, labels, 1); // We keep the tolerance very high. We need to ensure the accuracy drop // isn't any more than 10%. @@ -621,29 +674,31 @@ TEST_CASE("LMNNLowRankAccuracyBBSGDTest", "[LMNNTest]") // Comprehensive gradient tests by Marcus Edel & Ryan Curtin. // Simple numerical gradient checker. -template +template double CheckGradient(FunctionType& function, - arma::mat& coordinates, - const double eps = 1e-7) + MatType& coordinates, + const typename MatType::elem_type eps = 1e-7) { + typedef typename MatType::elem_type ElemType; + // Get gradients for the current parameters. - arma::mat orgGradient, gradient, estGradient; + MatType orgGradient, gradient, estGradient; function.Gradient(coordinates, orgGradient); - estGradient = arma::zeros(orgGradient.n_rows, orgGradient.n_cols); + estGradient = arma::zeros(orgGradient.n_rows, orgGradient.n_cols); // Compute numeric approximations to gradient. for (size_t i = 0; i < orgGradient.n_elem; ++i) { - double tmp = coordinates(i); + ElemType tmp = coordinates(i); // Perturb parameter with a positive constant and get costs. coordinates(i) += eps; - double costPlus = function.Evaluate(coordinates); + ElemType costPlus = function.Evaluate(coordinates); // Perturb parameter with a negative constant and get costs. coordinates(i) -= (2 * eps); - double costMinus = function.Evaluate(coordinates); + ElemType costMinus = function.Evaluate(coordinates); // Restore the parameter value. coordinates(i) = tmp; @@ -657,74 +712,84 @@ double CheckGradient(FunctionType& function, arma::norm(orgGradient + estGradient); } -TEST_CASE("LMNNFunctionGradientTest", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNFunctionGradientTest", "[LMNNTest]", float, double) { + typedef TestType ElemType; + // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); // 10 trials with random positions. for (size_t i = 0; i < 10; ++i) { - arma::mat coordinates(2, 2, arma::fill::randn); + arma::Mat coordinates(2, 2, arma::fill::randn); CheckGradient(lmnnfn, coordinates); } } -TEST_CASE("LMNNFunctionGradientTest2", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNFunctionGradientTest2", "[LMNNTest]", float, double) { - // Useful but simple dataset with six points and two classes. - arma::mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" - " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; - arma::Row labels = " 0 0 0 1 1 1 "; + typedef TestType ElemType; - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + // Useful but simple dataset with six points and two classes. + arma::Mat dataset = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; + arma::Row labels = " 0 0 0 1 1 1 "; + + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); // 10 trials with random positions. for (size_t i = 0; i < 10; ++i) { - arma::mat coordinates(2, 2, arma::fill::randu); + arma::Mat coordinates(2, 2, arma::fill::randu); CheckGradient(lmnnfn, coordinates); } } -TEST_CASE("LMNNFunctionGradientTest3", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNFunctionGradientTest3", "[LMNNTest]", float, double) { - arma::mat dataset; + typedef TestType ElemType; + + arma::Mat dataset; arma::Row labels; if (!data::Load("iris.csv", dataset)) FAIL("Cannot load dataset iris.csv"); if (!data::Load("iris_labels.txt", labels)) FAIL("Cannot load dataset iris_labels.txt"); - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); // 10 trials with random positions. for (size_t i = 0; i < 10; ++i) { - arma::mat coordinates(dataset.n_rows, dataset.n_rows, arma::fill::randn); + arma::Mat coordinates(dataset.n_rows, dataset.n_rows, + arma::fill::randn); CheckGradient(lmnnfn, coordinates); } } -TEST_CASE("LMNNFunctionGradientTest4", "[LMNNTest]") +TEMPLATE_TEST_CASE("LMNNFunctionGradientTest4", "[LMNNTest]", float, double) { - arma::mat dataset; + typedef TestType ElemType; + + arma::Mat dataset; arma::Row labels; if (!data::Load("iris.csv", dataset)) FAIL("Cannot load dataset iris.csv"); if (!data::Load("iris_labels.txt", labels)) FAIL("Cannot load dataset iris_labels.txt"); - LMNNFunction<> lmnnfn(dataset, labels, 1, 0.6, 1); + LMNNFunction> lmnnfn(dataset, labels, 1, 0.6, 1); // 10 trials with random positions. for (size_t i = 0; i < 10; ++i) { - arma::mat coordinates(dataset.n_rows, dataset.n_rows, arma::fill::randu); + arma::Mat coordinates(dataset.n_rows, dataset.n_rows, + arma::fill::randu); CheckGradient(lmnnfn, coordinates); } } diff --git a/src/mlpack/tests/logistic_regression_test.cpp b/src/mlpack/tests/logistic_regression_test.cpp index c7bc23556a..35c93775e6 100644 --- a/src/mlpack/tests/logistic_regression_test.cpp +++ b/src/mlpack/tests/logistic_regression_test.cpp @@ -1099,7 +1099,7 @@ TEMPLATE_TEST_CASE("LogisticRegressionAllConstructorsTest", // Create random data. MatType data(50, 1000, arma::fill::randu); - arma::Row labels(1000, arma::fill::zeros); + arma::Row labels(1000); labels.subvec(500, 999).fill(1); // Empty constructor. @@ -1162,7 +1162,7 @@ TEMPLATE_TEST_CASE("LogisticRegressionAllTrainTest", "[LogisticRegressionTest]", // Create random data. MatType data(50, 1000, arma::fill::randu); - arma::Row labels(1000, arma::fill::zeros); + arma::Row labels(1000); labels.subvec(500, 999).fill(1); // Construct all objects that we will use, but don't train. @@ -1236,7 +1236,7 @@ TEST_CASE("LogisticRegressionResetTest", "[LogisticRegressionTest]") { // Create random data. arma::mat data(50, 1000, arma::fill::randu); - arma::Row labels(1000, arma::fill::zeros); + arma::Row labels(1000); labels.subvec(500, 999).fill(1); // Create two logistic regression models. diff --git a/src/mlpack/tests/main_tests/lmnn_test.cpp b/src/mlpack/tests/main_tests/lmnn_test.cpp index 13b68b3d47..901033856a 100644 --- a/src/mlpack/tests/main_tests/lmnn_test.cpp +++ b/src/mlpack/tests/main_tests/lmnn_test.cpp @@ -542,7 +542,7 @@ TEST_CASE_METHOD(LMNNTestFixture, "LMNNDiffRegularizationTest", } /** - * Ensure that different value of range results in a + * Ensure that different value of update interval results in a * different output matrix. */ TEST_CASE_METHOD(LMNNTestFixture, "LMNNDiffRangeTest", @@ -573,7 +573,7 @@ TEST_CASE_METHOD(LMNNTestFixture, "LMNNDiffRangeTest", SetInputParam("input", std::move(inputData)); SetInputParam("labels", std::move(labels)); SetInputParam("linear_scan", (bool) true); - SetInputParam("range", 100); + SetInputParam("update_interval", 100); RUN_BINDING(); @@ -674,9 +674,9 @@ TEST_CASE_METHOD(LMNNTestFixture, "LMNNDiffPassesTest", } /** - * Ensure that number of targets, range, batch size must be always positive - * and regularization, step size, max iterations, rank, passes & tolerance are - * always non-negative + * Ensure that number of targets, update interval, batch size must be always + * positive and regularization, step size, max iterations, rank, passes & + * tolerance are always non-negative. */ TEST_CASE_METHOD(LMNNTestFixture, "LMNNBoundsTest", "[LMNNMainTest][BindingTests]") @@ -701,12 +701,12 @@ TEST_CASE_METHOD(LMNNTestFixture, "LMNNBoundsTest", // Reset settings. ResetSettings(); - // Test for range value. + // Test for update interval value. // Input training data. SetInputParam("input", inputData); SetInputParam("labels", labels); - SetInputParam("range", (int) 0); + SetInputParam("update_interval", (int) 0); REQUIRE_THROWS_AS(RUN_BINDING(), std::runtime_error); diff --git a/src/mlpack/tests/math_test.cpp b/src/mlpack/tests/math_test.cpp index f6f89b011f..fa1815a7dc 100644 --- a/src/mlpack/tests/math_test.cpp +++ b/src/mlpack/tests/math_test.cpp @@ -583,7 +583,7 @@ TEST_CASE("RangeContainsRange", "[MathTest]") */ TEST_CASE("ShuffleTest", "[MathTest]") { - arma::mat data(3, 10, arma::fill::zeros); + arma::mat data(3, 10); arma::Row labels(10); for (size_t i = 0; i < 10; ++i) { @@ -601,7 +601,7 @@ TEST_CASE("ShuffleTest", "[MathTest]") REQUIRE(outputLabels.n_elem == labels.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); + arma::Row counts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -639,7 +639,7 @@ TEST_CASE("SparseShuffleTest", "[MathTest]") REQUIRE(outputLabels.n_elem == labels.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); + arma::Row counts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -657,7 +657,7 @@ TEST_CASE("SparseShuffleTest", "[MathTest]") */ TEST_CASE("CubeShuffleTest", "[MathTest]") { - arma::cube data(3, 10, 5, arma::fill::zeros); + arma::cube data(3, 10, 5); arma::cube labels(1, 10, 5); for (size_t i = 0; i < labels.n_slices; ++i) { @@ -681,7 +681,7 @@ TEST_CASE("CubeShuffleTest", "[MathTest]") REQUIRE(outputLabels.n_slices == labels.n_slices); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); + arma::Row counts(10); for (size_t i = 0; i < 10; ++i) { for (size_t s = 0; s < data.n_slices; ++s) @@ -701,7 +701,7 @@ TEST_CASE("CubeShuffleTest", "[MathTest]") */ TEST_CASE("ShuffleWeightsTest", "[MathTest]") { - arma::mat data(3, 10, arma::fill::zeros); + arma::mat data(3, 10); arma::Row labels(10); arma::rowvec weights(10); for (size_t i = 0; i < 10; ++i) @@ -723,8 +723,8 @@ TEST_CASE("ShuffleWeightsTest", "[MathTest]") REQUIRE(outputWeights.n_elem == weights.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); - arma::Row weightCounts(10, arma::fill::zeros); + arma::Row counts(10); + arma::Row weightCounts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -771,8 +771,8 @@ TEST_CASE("SparseShuffleWeightsTest", "[MathTest]") REQUIRE(outputWeights.n_elem == weights.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); - arma::Row weightCounts(10, arma::fill::zeros); + arma::Row counts(10); + arma::Row weightCounts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -796,7 +796,7 @@ TEST_CASE("SparseShuffleWeightsTest", "[MathTest]") */ TEST_CASE("InplaceShuffleTest", "[MathTest]") { - arma::mat data(3, 10, arma::fill::zeros); + arma::mat data(3, 10); arma::Row labels(10); for (size_t i = 0; i < 10; ++i) { @@ -814,7 +814,7 @@ TEST_CASE("InplaceShuffleTest", "[MathTest]") REQUIRE(outputLabels.n_elem == labels.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); + arma::Row counts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -851,7 +851,7 @@ TEST_CASE("InplaceSparseShuffleTest", "[MathTest]") REQUIRE(outputLabels.n_elem == labels.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); + arma::Row counts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -869,7 +869,7 @@ TEST_CASE("InplaceSparseShuffleTest", "[MathTest]") */ TEST_CASE("InplaceCubeShuffleTest", "[MathTest]") { - arma::cube data(3, 10, 5, arma::fill::zeros); + arma::cube data(3, 10, 5); arma::cube labels(1, 10, 5); for (size_t i = 0; i < labels.n_slices; ++i) { @@ -893,7 +893,7 @@ TEST_CASE("InplaceCubeShuffleTest", "[MathTest]") REQUIRE(outputLabels.n_slices == labels.n_slices); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); + arma::Row counts(10); for (size_t i = 0; i < 10; ++i) { for (size_t s = 0; s < data.n_slices; ++s) @@ -914,7 +914,7 @@ TEST_CASE("InplaceCubeShuffleTest", "[MathTest]") */ TEST_CASE("InplaceShuffleWeightsTest", "[MathTest]") { - arma::mat data(3, 10, arma::fill::zeros); + arma::mat data(3, 10); arma::Row labels(10); arma::rowvec weights(10); for (size_t i = 0; i < 10; ++i) @@ -937,8 +937,8 @@ TEST_CASE("InplaceShuffleWeightsTest", "[MathTest]") REQUIRE(outputWeights.n_elem == weights.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); - arma::Row weightCounts(10, arma::fill::zeros); + arma::Row counts(10); + arma::Row weightCounts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); @@ -985,8 +985,8 @@ TEST_CASE("InplaceSparseShuffleWeightsTest", "[MathTest]") REQUIRE(outputWeights.n_elem == weights.n_elem); // Make sure we only have each point once. - arma::Row counts(10, arma::fill::zeros); - arma::Row weightCounts(10, arma::fill::zeros); + arma::Row counts(10); + arma::Row weightCounts(10); for (size_t i = 0; i < 10; ++i) { REQUIRE((size_t) outputData(0, i) == outputLabels[i]); diff --git a/src/mlpack/tests/mean_shift_test.cpp b/src/mlpack/tests/mean_shift_test.cpp index 1bd1fb3001..2d319acd6a 100644 --- a/src/mlpack/tests/mean_shift_test.cpp +++ b/src/mlpack/tests/mean_shift_test.cpp @@ -18,48 +18,54 @@ using namespace mlpack; // Generate dataset; written transposed because it's easier to read. -arma::mat meanShiftData(" 0.0 0.0;" // Class 1. - " 0.3 0.4;" - " 0.1 0.0;" - " 0.1 0.3;" - " -0.2 -0.2;" - " -0.1 0.3;" - " -0.4 0.1;" - " 0.2 -0.1;" - " 0.3 0.0;" - " -0.3 -0.3;" - " 0.1 -0.1;" - " 0.2 -0.3;" - " -0.3 0.2;" - " 10.0 10.0;" // Class 2. - " 10.1 9.9;" - " 9.9 10.0;" - " 10.2 9.7;" - " 10.2 9.8;" - " 9.7 10.3;" - " 9.9 10.1;" - "-10.0 5.0;" // Class 3. - " -9.8 5.1;" - " -9.9 4.9;" - "-10.0 4.9;" - "-10.2 5.2;" - "-10.1 5.1;" - "-10.3 5.3;" - "-10.0 4.8;" - " -9.6 5.0;" - " -9.8 5.1;"); - +template +MatType GetMeanShiftData() +{ + return MatType(" 0.0 0.0;" // Class 1. + " 0.3 0.4;" + " 0.1 0.0;" + " 0.1 0.3;" + " -0.2 -0.2;" + " -0.1 0.3;" + " -0.4 0.1;" + " 0.2 -0.1;" + " 0.3 0.0;" + " -0.3 -0.3;" + " 0.1 -0.1;" + " 0.2 -0.3;" + " -0.3 0.2;" + " 10.0 10.0;" // Class 2. + " 10.1 9.9;" + " 9.9 10.0;" + " 10.2 9.7;" + " 10.2 9.8;" + " 9.7 10.3;" + " 9.9 10.1;" + "-10.0 5.0;" // Class 3. + " -9.8 5.1;" + " -9.9 4.9;" + "-10.0 4.9;" + "-10.2 5.2;" + "-10.1 5.1;" + "-10.3 5.3;" + "-10.0 4.8;" + " -9.6 5.0;" + " -9.8 5.1;").t(); +} /** * 30-point 3-class test case for Mean Shift. */ -TEST_CASE("MeanShiftSimpleTest", "[MeanShiftTest]") +TEMPLATE_TEST_CASE("MeanShiftSimpleTest", "[MeanShiftTest]", float, double) { + typedef TestType ElemType; + MeanShift<> meanShift; arma::Row assignments; - arma::mat centroids; - meanShift.Cluster((arma::mat) trans(meanShiftData), assignments, centroids); + arma::Mat centroids; + meanShift.Cluster(GetMeanShiftData>(), assignments, + centroids); // Now make sure we got it all right. There is no restriction on how the // clusters are ordered, so we have to be careful about that. @@ -86,10 +92,25 @@ TEST_CASE("MeanShiftSimpleTest", "[MeanShiftTest]") REQUIRE(assignments(i) == thirdClass); } +TEMPLATE_TEST_CASE("MeanShiftSimpleCentroidsOnlyTest", "[MeanShiftTest]", float, double) +{ + typedef TestType ElemType; + + MeanShift<> meanShift; + + arma::Mat centroids; + meanShift.Cluster(GetMeanShiftData>(), centroids); + + // Just check that the size is right. + REQUIRE(centroids.n_cols == 3); +} + // Generate samples from four Gaussians, and make sure mean shift nearly // recovers those four centers. -TEST_CASE("GaussianClustering", "[MeanShiftTest]") +TEMPLATE_TEST_CASE("GaussianClustering", "[MeanShiftTest]", float, double) { + typedef TestType ElemType; + GaussianDistribution g1("0.0 0.0 0.0", arma::eye(3, 3)); GaussianDistribution g2("5.0 5.0 5.0", 2 * arma::eye(3, 3)); GaussianDistribution g3("-3.0 3.0 -1.0", arma::eye(3, 3)); @@ -100,21 +121,21 @@ TEST_CASE("GaussianClustering", "[MeanShiftTest]") bool success = false; for (size_t trial = 0; trial < 4; ++trial) { - arma::mat dataset(3, 4000); + arma::Mat dataset(3, 4000); for (size_t i = 0; i < 1000; ++i) - dataset.col(i) = g1.Random(); + dataset.col(i) = arma::conv_to>::from(g1.Random()); for (size_t i = 1000; i < 2000; ++i) - dataset.col(i) = g2.Random(); + dataset.col(i) = arma::conv_to>::from(g2.Random()); for (size_t i = 2000; i < 3000; ++i) - dataset.col(i) = g3.Random(); + dataset.col(i) = arma::conv_to>::from(g3.Random()); for (size_t i = 3000; i < 4000; ++i) - dataset.col(i) = g4.Random(); + dataset.col(i) = arma::conv_to>::from(g4.Random()); // Now that the dataset is generated, run mean shift. Pre-set radius. MeanShift<> meanShift(2.9); arma::Row assignments; - arma::mat centroids; + arma::Mat centroids; meanShift.Cluster(dataset, assignments, centroids); success = (centroids.n_cols == 4); @@ -125,21 +146,103 @@ TEST_CASE("GaussianClustering", "[MeanShiftTest]") continue; // Check that each centroid is close to only one mean. - arma::vec centroidDistances(4); + arma::Col centroidDistances(4); arma::uvec minIndices(4); for (size_t i = 0; i < 4; ++i) { - centroidDistances(0) = EuclideanDistance::Evaluate(g1.Mean(), + centroidDistances(0) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g1.Mean()), centroids.col(i)); - centroidDistances(1) = EuclideanDistance::Evaluate(g2.Mean(), + centroidDistances(1) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g2.Mean()), centroids.col(i)); - centroidDistances(2) = EuclideanDistance::Evaluate(g3.Mean(), + centroidDistances(2) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g3.Mean()), centroids.col(i)); - centroidDistances(3) = EuclideanDistance::Evaluate(g4.Mean(), + centroidDistances(3) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g4.Mean()), centroids.col(i)); // Are we near a centroid of a Gaussian? - const double minVal = centroidDistances.min(minIndices[i]); + const ElemType minVal = centroidDistances.min(minIndices[i]); + success = (std::abs(minVal) <= 0.65); + if (!success) + break; + } + + // Ensure each centroid corresponds to a different Gaussian. + bool innerSuccess = true; + for (size_t i = 0; i < 4; ++i) + for (size_t j = i + 1; j < 4; ++j) + innerSuccess &= (minIndices[i] != minIndices[j]); + + if (innerSuccess) + success = true; + + if (success) + break; + } + + REQUIRE(success == true); +} + +TEMPLATE_TEST_CASE("GaussianClusteringCentroidsOnly", "[MeanShiftTest]", float, double) +{ + typedef TestType ElemType; + + GaussianDistribution g1("0.0 0.0 0.0", arma::eye(3, 3)); + GaussianDistribution g2("5.0 5.0 5.0", 2 * arma::eye(3, 3)); + GaussianDistribution g3("-3.0 3.0 -1.0", arma::eye(3, 3)); + GaussianDistribution g4("6.0 -2.0 -2.0", 3 * arma::eye(3, 3)); + + // We may need to run this multiple times, because sometimes it may converge + // to the wrong number of clusters. + bool success = false; + for (size_t trial = 0; trial < 4; ++trial) + { + arma::Mat dataset(3, 4000); + for (size_t i = 0; i < 1000; ++i) + dataset.col(i) = arma::conv_to>::from(g1.Random()); + for (size_t i = 1000; i < 2000; ++i) + dataset.col(i) = arma::conv_to>::from(g2.Random()); + for (size_t i = 2000; i < 3000; ++i) + dataset.col(i) = arma::conv_to>::from(g3.Random()); + for (size_t i = 3000; i < 4000; ++i) + dataset.col(i) = arma::conv_to>::from(g4.Random()); + + // Now that the dataset is generated, run mean shift. Pre-set radius. + MeanShift<> meanShift(2.9); + + arma::Mat centroids; + meanShift.Cluster(dataset, centroids); + + success = (centroids.n_cols == 4); + if (!success) + continue; + success = (centroids.n_rows == 3); + if (!success) + continue; + + // Check that each centroid is close to only one mean. + arma::Col centroidDistances(4); + arma::uvec minIndices(4); + for (size_t i = 0; i < 4; ++i) + { + centroidDistances(0) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g1.Mean()), + centroids.col(i)); + centroidDistances(1) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g2.Mean()), + centroids.col(i)); + centroidDistances(2) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g3.Mean()), + centroids.col(i)); + centroidDistances(3) = EuclideanDistance::Evaluate( + arma::conv_to>::from(g4.Mean()), + centroids.col(i)); + + // Are we near a centroid of a Gaussian? + const ElemType minVal = centroidDistances.min(minIndices[i]); success = (std::abs(minVal) <= 0.65); if (!success) break; diff --git a/src/mlpack/tests/nca_test.cpp b/src/mlpack/tests/nca_test.cpp index 2dcfc0909d..20df35df87 100644 --- a/src/mlpack/tests/nca_test.cpp +++ b/src/mlpack/tests/nca_test.cpp @@ -26,26 +26,31 @@ using namespace ens; * The Softmax error function should return the identity matrix as its initial * point. */ -TEST_CASE("SoftmaxInitialPoint", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxInitialPoint", "[NCATest]", float, double) { + typedef TestType eT; + // Cheap fake dataset. - arma::mat data; + arma::Mat data; data.randu(5, 5); arma::Row labels; labels.zeros(5); - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); // Verify the initial point is the identity matrix. - arma::mat initialPoint = sef.GetInitialPoint(); + arma::Mat initialPoint = sef.GetInitialPoint(); + const double eps = std::is_same::value ? 1e-4 : 1e-7; + const double margin = std::is_same::value ? 1e-4 : 1e-5; for (int row = 0; row < 5; row++) { for (int col = 0; col < 5; col++) { if (row == col) - REQUIRE(initialPoint(row, col) == Approx(1.0).epsilon(1e-7)); + REQUIRE(initialPoint(row, col) == Approx(1.0).epsilon(eps)); else - REQUIRE(initialPoint(row, col) == Approx(0.0).margin(1e-5)); + REQUIRE(initialPoint(row, col) == Approx(0.0).margin(margin)); } } } @@ -54,16 +59,19 @@ TEST_CASE("SoftmaxInitialPoint", "[NCATesT]") * On a simple fake dataset, ensure that the initial function evaluation is * correct. */ -TEST_CASE("SoftmaxInitialEvaluation", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxInitialEvaluation", "[NCATest]", float, double) { + typedef TestType eT; + // Useful but simple dataset with six points and two classes. - arma::mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + arma::Mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - double objective = sef.Evaluate(arma::eye(2, 2)); + eT objective = sef.Evaluate(arma::eye>(2, 2)); // Result painstakingly calculated by hand by rcurtin (recorded forever in his // notebook). As a result of lack of precision of the by-hand result, the @@ -75,22 +83,28 @@ TEST_CASE("SoftmaxInitialEvaluation", "[NCATesT]") * On a simple fake dataset, ensure that the initial gradient evaluation is * correct. */ -TEST_CASE("SoftmaxInitialGradient", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxInitialGradient", "[NCATest]", float, double) { + typedef TestType eT; + // Useful but simple dataset with six points and two classes. - arma::mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + arma::Mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - arma::mat gradient; - arma::mat coordinates = arma::eye(2, 2); + arma::Mat gradient; + arma::Mat coordinates(2, 2, arma::fill::eye); sef.Gradient(coordinates, gradient); // Results painstakingly calculated by hand by rcurtin (recorded forever in // his notebook). As a result of lack of precision of the by-hand result, the // tolerance is fairly high. + // + // UPDATE 2024: that notebook definitely got thrown away over a decade ago. I + // don't even remember what it looked like. REQUIRE(gradient(0, 0) == Approx(-0.089766).epsilon(0.0005)); REQUIRE(gradient(1, 0) == Approx(0.0).margin(1e-5)); REQUIRE(gradient(0, 1) == Approx(0.0).margin(1e-5)); @@ -101,36 +115,43 @@ TEST_CASE("SoftmaxInitialGradient", "[NCATesT]") * On optimally separated datasets, ensure that the objective function is * optimal (equal to the negative number of points). */ -TEST_CASE("SoftmaxOptimalEvaluation", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxOptimalEvaluation", "[NCATest]", float, double) { + typedef TestType eT; + // Simple optimal dataset. - arma::mat data = " 500 500 -500 -500;" + arma::Mat data = " 500 500 -500 -500;" " 1 0 1 0 "; arma::Row labels = " 0 0 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - double objective = sef.Evaluate(arma::eye(2, 2)); + eT objective = sef.Evaluate(arma::eye>(2, 2)); // Use a very close tolerance for optimality; we need to be sure this function // gives optimal results correctly. - REQUIRE(objective == Approx(-4.0).epsilon(1e-12)); + const double eps = std::is_same::value ? 1e-6 : 1e-12; + REQUIRE(objective == Approx(-4.0).epsilon(eps)); } /** * On optimally separated datasets, ensure that the gradient is zero. */ -TEST_CASE("SoftmaxOptimalGradient", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxOptimalGradient", "[NCATest]", float, double) { + typedef TestType eT; + // Simple optimal dataset. - arma::mat data = " 500 500 -500 -500;" + arma::Mat data = " 500 500 -500 -500;" " 1 0 1 0 "; arma::Row labels = " 0 0 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - arma::mat gradient; - sef.Gradient(arma::eye(2, 2), gradient); + arma::Mat gradient; + sef.Gradient(arma::eye>(2, 2), gradient); REQUIRE(gradient(0, 0) == Approx(0.0).margin(1e-5)); REQUIRE(gradient(0, 1) == Approx(0.0).margin(1e-5)); @@ -141,19 +162,22 @@ TEST_CASE("SoftmaxOptimalGradient", "[NCATesT]") /** * Ensure the separable objective function is right. */ -TEST_CASE("SoftmaxSeparableObjective", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxSeparableObjective", "[NCATest]", float, double) { + typedef TestType eT; + // Useful but simple dataset with six points and two classes. - arma::mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + arma::Mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); // Results painstakingly calculated by hand by rcurtin (recorded forever in // his notebook). As a result of lack of precision of the by-hand result, the // tolerance is fairly high. - arma::mat coordinates = arma::eye(2, 2); + arma::Mat coordinates = arma::eye>(2, 2); REQUIRE(sef.Evaluate(coordinates, 0, 1) == Approx(-0.22480).epsilon(0.0001)); REQUIRE(sef.Evaluate(coordinates, 1, 1) == Approx(-0.30613).epsilon(0.0001)); REQUIRE(sef.Evaluate(coordinates, 2, 1) == Approx(-0.22480).epsilon(0.0001)); @@ -165,16 +189,19 @@ TEST_CASE("SoftmaxSeparableObjective", "[NCATesT]") /** * Ensure the optimal separable objective function is right. */ -TEST_CASE("OptimalSoftmaxSeparableObjective", "[NCATesT]") +TEMPLATE_TEST_CASE("OptimalSoftmaxSeparableObjective", "[NCATest]", float, double) { + typedef TestType eT; + // Simple optimal dataset. - arma::mat data = " 500 500 -500 -500;" + arma::Mat data = " 500 500 -500 -500;" " 1 0 1 0 "; arma::Row labels = " 0 0 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - arma::mat coordinates = arma::eye(2, 2); + arma::Mat coordinates = arma::eye>(2, 2); // Use a very close tolerance for optimality; we need to be sure this function // gives optimal results correctly. @@ -187,17 +214,20 @@ TEST_CASE("OptimalSoftmaxSeparableObjective", "[NCATesT]") /** * Ensure the separable gradient is right. */ -TEST_CASE("SoftmaxSeparableGradient", "[NCATesT]") +TEMPLATE_TEST_CASE("SoftmaxSeparableGradient", "[NCATest]", float, double) { + typedef TestType eT; + // Useful but simple dataset with six points and two classes. - arma::mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + arma::Mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - arma::mat coordinates = arma::eye(2, 2); - arma::mat gradient(2, 2); + arma::Mat coordinates = arma::eye>(2, 2); + arma::Mat gradient(2, 2); sef.Gradient(coordinates, 0, gradient, 1); @@ -250,29 +280,33 @@ TEST_CASE("SoftmaxSeparableGradient", "[NCATesT]") * On our simple dataset, ensure that the NCA algorithm fully separates the * points. */ -TEST_CASE("NCASGDSimpleDataset", "[NCATesT]") +TEMPLATE_TEST_CASE("NCASGDSimpleDataset", "[NCATest]", float, double) { + typedef TestType eT; + // Useful but simple dataset with six points and two classes. - arma::mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + arma::Mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; // Huge learning rate because this is so simple. - NCA nca(data, labels); - nca.Optimizer().StepSize() = 1.2; - nca.Optimizer().MaxIterations() = 300000; - nca.Optimizer().Tolerance() = 0; - nca.Optimizer().Shuffle() = true; + ens::StandardSGD opt; + opt.StepSize() = 1.2; + opt.MaxIterations() = 300000; + opt.Tolerance() = 0; + opt.Shuffle() = true; - arma::mat outputMatrix; - nca.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + NCA nca; + nca.LearnDistance(data, labels, outputMatrix, opt); // Ensure that the objective function is better now. - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - double initObj = sef.Evaluate(arma::eye(2, 2)); - double finalObj = sef.Evaluate(outputMatrix); - arma::mat finalGradient; + eT initObj = sef.Evaluate(arma::eye>(2, 2)); + eT finalObj = sef.Evaluate(outputMatrix); + arma::Mat finalGradient; sef.Gradient(outputMatrix, finalGradient); // finalObj must be less than initObj. @@ -284,33 +318,36 @@ TEST_CASE("NCASGDSimpleDataset", "[NCATesT]") REQUIRE(arma::norm(finalGradient, 2) < 1e-4); } -TEST_CASE("NCALBFGSSimpleDataset", "[NCATesT]") +TEMPLATE_TEST_CASE("NCALBFGSSimpleDataset", "[NCATest]", float, double) { + typedef TestType eT; + // Useful but simple dataset with six points and two classes. - arma::mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" + arma::Mat data = "-0.1 -0.1 -0.1 0.1 0.1 0.1;" " 1.0 0.0 -1.0 1.0 0.0 -1.0 "; arma::Row labels = " 0 0 0 1 1 1 "; - // Huge learning rate because this is so simple. - NCA nca(data, labels); - nca.Optimizer().NumBasis() = 5; + L_BFGS lbfgs; + lbfgs.NumBasis() = 5; - arma::mat outputMatrix; - nca.LearnDistance(outputMatrix); + arma::Mat outputMatrix; + NCA nca; + nca.LearnDistance(data, labels, outputMatrix, lbfgs); // Ensure that the objective function is better now. - SoftmaxErrorFunction sef(data, labels); + SoftmaxErrorFunction, arma::Row, + SquaredEuclideanDistance> sef(data, labels); - double initObj = sef.Evaluate(arma::eye(2, 2)); - double finalObj = sef.Evaluate(outputMatrix); - arma::mat finalGradient; + eT initObj = sef.Evaluate(arma::eye>(2, 2)); + eT finalObj = sef.Evaluate(outputMatrix); + arma::Mat finalGradient; sef.Gradient(outputMatrix, finalGradient); // finalObj must be less than initObj. REQUIRE(finalObj < initObj); // Verify that final objective is optimal. - REQUIRE(finalObj == Approx(-6.0).epsilon(1e-7)); + REQUIRE(finalObj == Approx(-6.0).epsilon(0.00001)); // The solution is not unique, so the best we can do is ensure the gradient // norm is close to 0. - REQUIRE(arma::norm(finalGradient, 2) < 1e-6); + REQUIRE(arma::norm(finalGradient, 2) < 1e-5); } diff --git a/src/mlpack/tests/scaling_test.cpp b/src/mlpack/tests/scaling_test.cpp index 9624311f34..e11fc33a7d 100644 --- a/src/mlpack/tests/scaling_test.cpp +++ b/src/mlpack/tests/scaling_test.cpp @@ -103,7 +103,7 @@ TEST_CASE("SameInputOutputTest", "[ScalingTest]") */ TEST_CASE("ZeroMatrixTest", "[ScalingTest]") { - arma::mat input(2, 4, arma::fill::zeros); + arma::mat input(2, 4); data::MeanNormalization scale; scale.Fit(input); scale.Transform(input, temp); diff --git a/src/mlpack/tests/serialization_test.cpp b/src/mlpack/tests/serialization_test.cpp index 1433c9d9c6..d12235f9b5 100644 --- a/src/mlpack/tests/serialization_test.cpp +++ b/src/mlpack/tests/serialization_test.cpp @@ -152,12 +152,12 @@ TEST_CASE("BallBoundTest", "[SerializationTest]") TEST_CASE("MahalanobisBallBoundTest", "[SerializationTest]") { - BallBound, arma::vec> b(100); + BallBound, double, arma::vec> b(100); b.Center().randu(); b.Radius() = 14.0; b.Distance().Q().randu(100, 100); - BallBound, arma::vec> xmlB, jsonB, binaryB; + BallBound, double, arma::vec> xmlB, jsonB, binaryB; SerializeObjectAll(b, xmlB, jsonB, binaryB); diff --git a/src/mlpack/tests/softmax_regression_test.cpp b/src/mlpack/tests/softmax_regression_test.cpp index 9458f1db61..8492d3cb88 100644 --- a/src/mlpack/tests/softmax_regression_test.cpp +++ b/src/mlpack/tests/softmax_regression_test.cpp @@ -880,7 +880,7 @@ TEST_CASE("SoftmaxRegressionResetTest", "[SoftmaxRegressionTest]") { // Create random data. arma::mat data(50, 1000, arma::fill::randu); - arma::Row labels(1000, arma::fill::zeros); + arma::Row labels(1000); labels.subvec(500, 999).fill(1); // Create two logistic regression models. diff --git a/src/mlpack/tests/split_data_test.cpp b/src/mlpack/tests/split_data_test.cpp index 607dc96a89..0293944805 100644 --- a/src/mlpack/tests/split_data_test.cpp +++ b/src/mlpack/tests/split_data_test.cpp @@ -94,8 +94,7 @@ void CheckDuplication(const Row& trainLabels, TEST_CASE("SplitShuffleDataResultMat", "[SplitDataTest]") { mat input(2, 10); - size_t count = 0; // Counter for unique sequential values. - input.imbue([&count] () { return ++count; }); + input = reshape(linspace(0, 19, 20), 2, 10); const auto value = Split(input, 0.2); REQUIRE(std::get<0>(value).n_cols == 8); // Train data. @@ -108,8 +107,7 @@ TEST_CASE("SplitShuffleDataResultMat", "[SplitDataTest]") TEST_CASE("SplitDataResultMat", "[SplitDataTest]") { mat input(2, 10); - size_t count = 0; // Counter for unique sequential values. - input.imbue([&count] () { return ++count; }); + input = reshape(linspace(0, 19 ,20), 2, 10); const auto value = Split(input, 0.2, false); REQUIRE(std::get<0>(value).n_cols == 8); // Train data. @@ -123,8 +121,7 @@ TEST_CASE("SplitDataResultMat", "[SplitDataTest]") TEST_CASE("ZeroRatioSplitData", "[SplitDataTest]") { mat input(2, 10); - size_t count = 0; // Counter for unique sequential values. - input.imbue([&count] () { return ++count; }); + input = reshape(linspace(0, 19, 20), 2, 10); const auto value = Split(input, 0, false); REQUIRE(std::get<0>(value).n_cols == 10); // Train data. @@ -138,8 +135,7 @@ TEST_CASE("ZeroRatioSplitData", "[SplitDataTest]") TEST_CASE("TotalRatioSplitData", "[SplitDataTest]") { mat input(2, 10); - size_t count = 0; // Counter for unique sequential values. - input.imbue([&count] () { return ++count; }); + input = reshape(linspace(0, 19, 20), 2, 10); const auto value = Split(input, 1, false); REQUIRE(std::get<0>(value).n_cols == 0); // Train data. @@ -192,9 +188,8 @@ TEST_CASE("SplitCheckSize", "[SplitDataTest]") */ TEST_CASE("SplitDataLargerTest", "[SplitDataTest]") { - size_t count = 0; mat input(10, 497); - input.imbue([&count] () { return ++count; }); + input = reshape(linspace(0, 4969, 4970), 10, 497); const auto value = Split(input, 0.3); REQUIRE(std::get<0>(value).n_cols == 497 - size_t(0.3 * 497)); @@ -362,10 +357,9 @@ TEST_CASE("SplitDataResultField", "[SplitDataTest]") mat matA(2, 10); mat matB(2, 10); - size_t count = 0; // Counter for unique sequential values. - matA.imbue([&count]() { return ++count; }); - matB.imbue([&count]() { return ++count; }); - + matA = linspace(0, matA.n_elem - 1); + matB = linspace(matA.n_elem, matA.n_elem + matB.n_elem - 1); + input(0, 0) = matA; input(0, 1) = matB; diff --git a/src/mlpack/tests/svdplusplus_test.cpp b/src/mlpack/tests/svdplusplus_test.cpp index 37bd1fb075..7c56e4a524 100644 --- a/src/mlpack/tests/svdplusplus_test.cpp +++ b/src/mlpack/tests/svdplusplus_test.cpp @@ -62,7 +62,7 @@ TEST_CASE("SVDPlusPlusEvaluate", "[SVDPlusPlusTest]") // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -368,7 +368,7 @@ TEST_CASE("SVDPlusPlusFunctionOptimize", "[SVDPlusPlusTest]") // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -406,7 +406,7 @@ TEST_CASE("SVDPlusPlusFunctionOptimize", "[SVDPlusPlusTest]") // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -475,7 +475,7 @@ TEST_CASE("SVDPlusPlusFunctionParallelOptimize", "[SVDPlusPlusTest]") // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0; @@ -520,7 +520,7 @@ TEST_CASE("SVDPlusPlusFunctionParallelOptimize", "[SVDPlusPlusTest]") // Iterate through each item which the user interacted with to calculate // user vector. - arma::vec userVec(rank, arma::fill::zeros); + arma::vec userVec(rank); arma::sp_mat::const_iterator it = implicitData.begin_col(user); arma::sp_mat::const_iterator it_end = implicitData.end_col(user); size_t implicitCount = 0;