From d05cfd31cc5e92035c4e458318f9947c9996f375 Mon Sep 17 00:00:00 2001 From: Manish Date: Thu, 21 Jun 2018 20:39:09 +0530 Subject: [PATCH] Cache distance to avoid recomputation --- src/mlpack/methods/lmnn/constraints.hpp | 24 ++++++ src/mlpack/methods/lmnn/constraints_impl.hpp | 78 +++++++++++++++++++ .../methods/lmnn/lmnn_function_impl.hpp | 52 +++++++++---- 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/src/mlpack/methods/lmnn/constraints.hpp b/src/mlpack/methods/lmnn/constraints.hpp index b0674ba702..2cf624ce0f 100644 --- a/src/mlpack/methods/lmnn/constraints.hpp +++ b/src/mlpack/methods/lmnn/constraints.hpp @@ -68,6 +68,16 @@ class Constraints */ void Impostors(arma::Mat& outputMatrix); + /** + * Calculates k differently labeled nearest neighbors & distances to + * impostors for each datapoint and writes them back to passed matrices. + * + * @param outputNeighbors Coordinates matrix to store impostors. + * @param outputDistance matrix to store distance. + */ + void Impostors(arma::Mat& outputNeighbors, + arma::mat& outputDistance); + /** * Calculates k differently labeled nearest neighbors for a batch of dataset * and writes them back to passed matrix. @@ -80,6 +90,20 @@ class Constraints const size_t begin, const size_t batchSize); + /** + * Calculates k differently labeled nearest neighbors & distances to + * impostors for a batch of dataset and writes them back to passed matrices. + * + * @param outputNeighbors Coordinates matrix to store impostors. + * @param outputDistance matrix to store distance. + * @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 size_t begin, + const size_t batchSize); + /** * Generate triplets {i, j, l} for each datapoint i and writes back generated * triplets to matrix passed. diff --git a/src/mlpack/methods/lmnn/constraints_impl.hpp b/src/mlpack/methods/lmnn/constraints_impl.hpp index 429d440a42..5eaec385ae 100644 --- a/src/mlpack/methods/lmnn/constraints_impl.hpp +++ b/src/mlpack/methods/lmnn/constraints_impl.hpp @@ -143,6 +143,42 @@ void Constraints::Impostors(arma::Mat& outputMatrix) } } +// Calculates k differently labeled nearest neighbors. The function +// writes back calculated neighbors & distances to passed matrices. +void Constraints::Impostors(arma::Mat& outputNeighbors, + arma::mat& outputDistance) +{ + size_t N = dataset.n_cols; + + // Perform pre-calculation, if necessary. + Precalculate(); + + outputNeighbors = arma::Mat(k, N, arma::fill::zeros); + outputDistance = arma::mat(k, N, arma::fill::zeros); + + // KNN instance. + neighbor::KNN knn; + + arma::Mat neighbors; + arma::mat distances; + + for (size_t i = 0; i < uniqueLabels.n_cols; i++) + { + // Perform KNN search with differently labeled points as reference + // set and same class points as query set. + knn.Train(dataset.cols(indexDiff[i])); + knn.Search(dataset.cols(indexSame[i]), k, neighbors, distances); + + // Re-map neighbors to their index. + for (size_t j = 0; j < neighbors.n_elem; j++) + neighbors(j) = indexDiff[i].at(neighbors(j)); + + // Store impostors. + outputNeighbors.cols(indexSame[i]) = neighbors; + outputDistance.cols(indexSame[i]) = distances; + } +} + // Calculates k differently labeled nearest neighbors on a // batch of data points. void Constraints::Impostors(arma::Mat& outputMatrix, @@ -183,6 +219,48 @@ void Constraints::Impostors(arma::Mat& outputMatrix, } } +// Calculates k differently labeled nearest neighbors & distances on a +// batch of data points. +void Constraints::Impostors(arma::Mat& outputNeighbors, + arma::mat& outputDistance, + const size_t begin, + const size_t batchSize) +{ + arma::mat subDataset = dataset.cols(begin, begin + batchSize - 1); + arma::Row sublabels = labels.cols(begin, begin + batchSize - 1); + + // Perform pre-calculation, if necessary. + Precalculate(); + + // KNN instance. + neighbor::KNN knn; + + arma::Mat neighbors; + arma::mat distances; + + // Vectors to store indices. + arma::uvec subIndexSame; + + for (size_t i = 0; i < uniqueLabels.n_cols; i++) + { + // Calculate impostors. + subIndexSame = arma::find(sublabels == uniqueLabels[i]); + + // Perform KNN search with differently labeled points as reference + // set and same class points as query set. + knn.Train(dataset.cols(indexDiff[i])); + knn.Search(subDataset.cols(subIndexSame), k, neighbors, distances); + + // Re-map neighbors to their index. + for (size_t j = 0; j < neighbors.n_elem; j++) + neighbors(j) = indexDiff[i].at(neighbors(j)); + + // Store impostors. + outputNeighbors.cols(begin + subIndexSame) = neighbors; + outputDistance.cols(begin + subIndexSame) = distances; + } +} + // Generates {data point, target neighbors, impostors} triplets using // TargetNeighbors() and Impostors(). void Constraints::Triplets(arma::Mat& outputMatrix) diff --git a/src/mlpack/methods/lmnn/lmnn_function_impl.hpp b/src/mlpack/methods/lmnn/lmnn_function_impl.hpp index 0d121d6e19..171cda43c9 100644 --- a/src/mlpack/methods/lmnn/lmnn_function_impl.hpp +++ b/src/mlpack/methods/lmnn/lmnn_function_impl.hpp @@ -76,11 +76,12 @@ double LMNNFunction::Evaluate(const arma::mat& coordinates) // Apply metric over dataset. transformedDataset = coordinates * dataset; + arma::mat distance = arma::mat(k, dataset.n_cols, arma::fill::zeros); if (iteration++ % range == 0) { // Re-calculate impostors on transformed dataset. Constraints constraint(transformedDataset, labels, k); - constraint.Impostors(impostors); + constraint.Impostors(impostors, distance); } for (size_t i = 0; i < dataset.n_cols; i++) @@ -99,12 +100,17 @@ double LMNNFunction::Evaluate(const arma::mat& coordinates) // breaking point. for (size_t l = 0, bp = k; l < bp ; l++) { - // Calculate cost due to data point, target neighbors, impostors + // Check if we already have distance between impostor & data point + // stored. + double distImp = distance(l, i) > 0 ? distance(l, i) : + metric.Evaluate(transformedDataset.col(i), + transformedDataset.col(impostors(l, i))); + + // Calculate cost due to {data point, target neighbors, impostors} // triplets. double eval = metric.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - - metric.Evaluate(transformedDataset.col(i), - transformedDataset.col(impostors(l, i))); + distImp; // Check bounding condition. if (eval < -1) @@ -133,11 +139,12 @@ double LMNNFunction::Evaluate(const arma::mat& coordinates, // Apply metric over dataset. transformedDataset = coordinates * dataset; + arma::mat distance = arma::mat(k, dataset.n_cols, arma::fill::zeros); if (iteration++ % range == 0) { // Re-calculate impostors on transformed dataset. Constraints constraint(transformedDataset, labels, k); - constraint.Impostors(impostors, begin, batchSize); + constraint.Impostors(impostors, distance, begin, batchSize); } for (size_t i = begin; i < begin + batchSize; i++) @@ -155,12 +162,17 @@ double LMNNFunction::Evaluate(const arma::mat& coordinates, // Bound constraints to avoid uneccesary computation. for (size_t l = 0, bp = k; l < bp ; l++) { - // Calculate cost due to data point, target neighbors, impostors + // Check if we already have distance between impostor & data point + // stored. + double distImp = distance(l, i) > 0 ? distance(l, i) : + metric.Evaluate(transformedDataset.col(i), + transformedDataset.col(impostors(l, i))); + + // Calculate cost due to {data point, target neighbors, impostors} // triplets. double eval = metric.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - - metric.Evaluate(transformedDataset.col(i), - transformedDataset.col(impostors(l, i))); + distImp; // Check bounding condition. if (eval < -1) @@ -295,11 +307,12 @@ double LMNNFunction::EvaluateWithGradient( // Apply metric over dataset. transformedDataset = coordinates * dataset; + arma::mat distance = arma::mat(k, dataset.n_cols, arma::fill::zeros); if (iteration++ % range == 0) { // Re-calculate impostors on transformed dataset. Constraints constraint(transformedDataset, labels, k); - constraint.Impostors(impostors); + constraint.Impostors(impostors, distance); } gradient.zeros(coordinates.n_rows, coordinates.n_cols); @@ -326,12 +339,17 @@ double LMNNFunction::EvaluateWithGradient( // Bound constraints to avoid uneccesary computation. for (size_t l = 0, bp = k; l < bp ; l++) { + // Check if we already have distance between impostor & data point + // stored. + double distImp = distance(l, i) > 0 ? distance(l, i) : + metric.Evaluate(transformedDataset.col(i), + transformedDataset.col(impostors(l, i))); + // Calculate cost due to {data point, target neighbors, impostors} // triplets. double eval = metric.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - - metric.Evaluate(transformedDataset.col(i), - transformedDataset.col(impostors(l, i))); + distImp; // Check bounding condition. if (eval < -1) @@ -373,11 +391,12 @@ double LMNNFunction::EvaluateWithGradient( // Apply metric over dataset. transformedDataset = coordinates * dataset; + arma::mat distance = arma::mat(k, dataset.n_cols, arma::fill::zeros); if (iteration++ % range == 0) { // Re-calculate impostors on transformed dataset. Constraints constraint(transformedDataset, labels, k); - constraint.Impostors(impostors, begin, batchSize); + constraint.Impostors(impostors, distance, begin, batchSize); } gradient.zeros(coordinates.n_rows, coordinates.n_cols); @@ -404,12 +423,17 @@ double LMNNFunction::EvaluateWithGradient( // Bound constraints to avoid uneccesary computation. for (size_t l = 0, bp = k; l < bp ; l++) { + // Check if we already have distance between impostor & data point + // stored. + double distImp = distance(l, i) > 0 ? distance(l, i) : + metric.Evaluate(transformedDataset.col(i), + transformedDataset.col(impostors(l, i))); + // Calculate cost due to {data point, target neighbors, impostors} // triplets. double eval = metric.Evaluate(transformedDataset.col(i), transformedDataset.col(targetNeighbors(j, i))) - - metric.Evaluate(transformedDataset.col(i), - transformedDataset.col(impostors(l, i))); + distImp; // Check bounding condition. if (eval < -1)