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 2d3c13058f..c40acff1a5 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
index 81c58d2033..1270e838d1 100644
--- a/.github/workflows/auto-approve.yml
+++ b/.github/workflows/auto-approve.yml
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Auto-approve pull requests
- uses: rcurtin/auto-approve@v1.0.1
+ uses: rcurtin/actions/auto-approve@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
approval-message:
diff --git a/.github/workflows/stickers.yaml b/.github/workflows/stickers.yaml
index 89ef40d37c..aafb7d390b 100644
--- a/.github/workflows/stickers.yaml
+++ b/.github/workflows/stickers.yaml
@@ -11,7 +11,7 @@ jobs:
if: github.event.pull_request.merged == true
steps:
# Forked version of first-interaction that runs only on first merged PR.
- - uses: rcurtin/first-interaction@v1.0.0
+ - 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/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 e98875f83b..3efc2e024d 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -11,6 +11,8 @@ _????-??-??_
* 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).
## 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 59015a0a7a..8fe5b875b1 100644
--- a/doc/index.md
+++ b/doc/index.md
@@ -124,6 +124,10 @@ 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/sidebar.html b/doc/sidebar.html
index a9555f315f..a5ec834955 100644
--- a/doc/sidebar.html
+++ b/doc/sidebar.html
@@ -181,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 4a81258ac7..22ce081c0b 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)`.
```
---
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/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/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/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/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/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..27ed28417a 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 , arma::fill::zeros);
- 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