| Title: | Nonlinear Dimensionality Reduction by Manifold Sculpting |
|---|---|
| Description: | Manifold Sculpting (Gashler, Ventura and Martinez (2008) <https://proceedings.neurips.cc/paper/2007/hash/c06d06da9666a219db15cf575aff2824-Abstract.html>) is an iterative nonlinear dimensionality reduction method. It records the distances and angles between each point and its nearest neighbors, then gradually scales the data out of the dimensions to be removed while moving the points in the remaining dimensions to restore these local relationships, as if surface tension were acting on the manifold. New samples are embedded by solving the same local problem against the training embedding. The neighbor search and the sculpting iterations are written in C++ with 'RcppArmadillo', and step_manifold_sculpting() makes the method available as a 'recipes' preprocessing step for 'tidymodels' workflows. |
| Authors: | Christian L. Goueguel [aut, cre] |
| Maintainer: | Christian L. Goueguel <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.2.1 |
| Built: | 2026-10-27 14:51:26 UTC |
| Source: | https://github.com/ChristianGoueguel/manifold-sculpting |
manifold_sculpting() reduces the dimension of numeric data with
Manifold Sculpting (Gashler, Ventura and Martinez, 2008), an iterative
nonlinear dimensionality reduction method that preserves the distances and
angles between neighboring points.
manifold_sculpting( x, ndim = 2, neighbors = 20, squishing_rate = 0.99, nstart = 1, max_iter = 5000, patience = 50, init = "pca", nn_method = c("auto", "kd_tree", "brute_force"), verbose = FALSE )manifold_sculpting( x, ndim = 2, neighbors = 20, squishing_rate = 0.99, nstart = 1, max_iter = 5000, patience = 50, init = "pca", nn_method = c("auto", "kd_tree", "brute_force"), verbose = FALSE )
x |
A numeric matrix or data frame, with one row per sample. Distances are Euclidean, so variables on different scales should be standardized first. |
ndim |
The number of dimensions of the embedding. It must be smaller
than the number of columns of |
neighbors |
The number of nearest neighbors, |
squishing_rate |
The factor, |
nstart |
The number of runs. The runs start from the same configuration but take different random paths, and the embedding with the lowest final error is returned. Several runs make the result more reliable on manifolds that are hard to unfold (see Details). |
max_iter |
The maximum number of iterations. |
patience |
Once the dimensions to remove have shrunk below 1% of
their size, the iterations stop when the error has not improved for
|
init |
The starting configuration. |
nn_method |
The nearest neighbor search: |
verbose |
Print the progress every 50 iterations? |
The algorithm has five steps:
Find the k nearest neighbors of each point.
For each point and neighbor , record the distance
between them, and the angle formed
by , and , the neighbor of
that is the most colinear with . The average
neighbor distance is .
Rotate the data onto their principal components. The first ndim
components are the preserved dimensions; the others are scaled.
Iterate: multiply the scaled dimensions by squishing_rate, then
scale the preserved dimensions up until the average neighbor distance
is again. Visit the points breadth-first, from a
random point, and move each one in the preserved dimensions, by hill
climbing, to minimize the error
where the superscript 0 marks the original values, and the weights
are higher for the neighbors that have already been
moved in this iteration.
Drop the scaled dimensions.
The weighting and the normalization of the error, the adaptive step size
of the hill climbing, and the stopping rule follow the reference
implementation of the authors in the Waffles library. Unlike Waffles, all
the dimensions of the data are kept (as the paper asks): the scaled
dimensions only contribute to the distances and angles through a common
factor, so their contributions are computed once and the iterations only
work on the ndim preserved dimensions. Points that the breadth-first
traversal cannot reach are also adjusted at each iteration.
Like any local optimization, the iterations can end in a local minimum,
where part of the manifold is folded over another. The final error tells
these runs apart: it is much larger than for a run that unfolded the
manifold. The risk depends on the data, and decreases with more
neighbors and more runs (nstart). On 2000 points of the Swiss roll of
sim_swiss_roll(), for example, about one run in seven stays folded with
20 neighbors (more with fewer points), but the best of three runs is
reliable, while the S-curve of sim_s_curve() is unfolded with 14
neighbors.
The neighbor search and the iterations are written in C++. The algorithm
uses the random number generator of R; call set.seed() first for
reproducible results.
An object of class manifold_sculpting, a list with:
The embedding, a matrix with one row per sample and
ndim columns.
The total error at each iteration (of the best run).
The number of iterations (of the best run).
Whether the iterations stopped before max_iter.
The final error of each run.
and the elements needed by predict.manifold_sculpting() to embed new
samples.
Gashler, M., Ventura, D. and Martinez, T. (2008). Iterative non-linear dimensionality reduction with manifold sculpting. In Advances in Neural Information Processing Systems 20, 513–520. https://proceedings.neurips.cc/paper/2007/hash/c06d06da9666a219db15cf575aff2824-Abstract.html
Gashler, M. S. (2011). Waffles: A machine learning toolkit. Journal of Machine Learning Research, 12, 2383–2387.
predict.manifold_sculpting() to embed new samples, and
step_manifold_sculpting() for the 'recipes' step.
set.seed(1) curve <- sim_s_curve(800) fit <- manifold_sculpting(curve[c("x", "y", "z")], ndim = 2) fit # The embedding recovers the intrinsic coordinates of the S-curve cor(fit$embedding, curve[c("u", "v")])set.seed(1) curve <- sim_s_curve(800) fit <- manifold_sculpting(curve[c("x", "y", "z")], ndim = 2) fit # The embedding recovers the intrinsic coordinates of the S-curve cor(fit$embedding, curve[c("u", "v")])
Manifold Sculpting has no explicit mapping from the data to the
embedding. A new sample is embedded from its neighbors nearest training
samples: it starts at the combination of their embeddings given by its
locally linear reconstruction weights (as in locally linear embedding),
and is then moved to minimize the Manifold Sculpting error, the
discrepancy between its original and embedded distances and angles to
its neighbors, whose embeddings are held fixed. A new sample that is
identical to a training sample takes its embedding, so the training data
are mapped to manifold_sculpting()'s embedding.
## S3 method for class 'manifold_sculpting' predict(object, new_data, refine = TRUE, ...)## S3 method for class 'manifold_sculpting' predict(object, new_data, refine = TRUE, ...)
object |
A |
new_data |
A numeric matrix or data frame with the variables used to
fit |
refine |
If |
... |
Not used. |
A matrix with one row per sample of new_data and object$ndim
columns.
set.seed(1) roll <- sim_swiss_roll(1000) train <- roll[1:800, ] test <- roll[801:1000, ] fit <- manifold_sculpting(train[c("x", "y", "z")], nstart = 3) head(predict(fit, test))set.seed(1) roll <- sim_swiss_roll(1000) train <- roll[1:800, ] test <- roll[801:1000, ] fit <- manifold_sculpting(train[c("x", "y", "z")], nstart = 3) head(predict(fit, test))
The Swiss roll and S-curve manifolds of Gashler, Ventura and Martinez (2008), with their intrinsic (two-dimensional) coordinates, to test dimensionality reduction methods.
sim_swiss_roll(n = 2000, noise = 0) sim_s_curve(n = 2000, noise = 0)sim_swiss_roll(n = 2000, noise = 0) sim_s_curve(n = 2000, noise = 0)
n |
The number of points. |
noise |
The standard deviation of Gaussian noise added to the
coordinates |
For the Swiss roll, for , and
Its intrinsic coordinates are the arc length along the roll,
, and
.
For the S-curve, , and
Its intrinsic coordinates are the arc length along the curve,
, and .
(The paper gives , a misprint since is a function
of .)
A tibble with n rows and the columns x, y, z (the
coordinates in three dimensions) and u, v (the intrinsic
coordinates). The points are ordered by u.
Gashler, M., Ventura, D. and Martinez, T. (2008). Iterative non-linear dimensionality reduction with manifold sculpting. In Advances in Neural Information Processing Systems 20, 513–520.
set.seed(1) sim_swiss_roll(5) sim_s_curve(5)set.seed(1) sim_swiss_roll(5) sim_s_curve(5)
The rate at which manifold_sculpting() scales down the dimensions to
remove, for tuning step_manifold_sculpting().
squishing_rate(range = c(0.95, 0.995), trans = NULL)squishing_rate(range = c(0.95, 0.995), trans = NULL)
range |
A two-element vector holding the defaults for the smallest and largest possible values. |
trans |
A |
A quant_param object from the 'dials' package.
squishing_rate()squishing_rate()
step_manifold_sculpting() creates a specification of a recipe step that
converts numeric data into one or more new dimensions with Manifold
Sculpting.
step_manifold_sculpting( recipe, ..., role = "predictor", trained = FALSE, num_comp = 2, neighbors = 20, squishing_rate = 0.99, options = list(), seed = sample.int(10^5, 1), res = NULL, columns = NULL, prefix = "MS", keep_original_cols = FALSE, skip = FALSE, id = recipes::rand_id("manifold_sculpting") ) ## S3 method for class 'step_manifold_sculpting' tidy(x, ...)step_manifold_sculpting( recipe, ..., role = "predictor", trained = FALSE, num_comp = 2, neighbors = 20, squishing_rate = 0.99, options = list(), seed = sample.int(10^5, 1), res = NULL, columns = NULL, prefix = "MS", keep_original_cols = FALSE, skip = FALSE, id = recipes::rand_id("manifold_sculpting") ) ## S3 method for class 'step_manifold_sculpting' tidy(x, ...)
recipe |
A recipe object. The step will be added to the sequence of operations for this recipe. |
... |
One or more selector functions to choose variables for this
step. See |
role |
For model terms created by this step, what analysis role should they be assigned? By default, the new columns created by this step from the original variables will be used as predictors in a model. |
trained |
A logical to indicate if the quantities for preprocessing have been estimated. |
num_comp |
The number of dimensions of the embedding. It is reduced
to one less than the number of selected columns when it is larger. With
|
neighbors |
The number of nearest neighbors that define the local relationships to preserve. |
squishing_rate |
The factor by which the dimensions to remove are scaled down at each iteration, in (0, 1). |
options |
A list of other arguments to |
seed |
A single integer, the seed of the random numbers used when the step is trained, for reproducible results. |
res |
The |
columns |
A character string of the selected variable names. This field
is a placeholder and will be populated once |
prefix |
The prefix of the names of the new columns, which are
numbered from 1 ( |
keep_original_cols |
A logical to keep the original variables in the
output. Defaults to |
skip |
A logical. Should the step be skipped when the recipe is baked by
|
id |
A character string that is unique to this step to identify it. |
x |
A |
Manifold Sculpting (Gashler, Ventura and Martinez, 2008) finds a
low-dimensional embedding that preserves the distances and angles between
neighboring samples; see manifold_sculpting() for the algorithm. It is
based on Euclidean distances, so the predictors should usually be
centered and scaled first, with recipes::step_normalize().
New samples, given to recipes::bake(), are embedded from their nearest
training samples by predict.manifold_sculpting(). The training set gets
the embedding found by the algorithm.
An updated version of recipe with the new step added to the
sequence of any existing operations.
When you tidy() this step, a tibble is returned
with the columns terms (the selectors or variables selected) and id.
This step has 3 tuning parameters: num_comp (# Components), neighbors
(# Nearest Neighbors) and squishing_rate (Squishing Rate, see
squishing_rate()).
The underlying operation does not allow for case weights.
Gashler, M., Ventura, D. and Martinez, T. (2008). Iterative non-linear dimensionality reduction with manifold sculpting. In Advances in Neural Information Processing Systems 20, 513–520.
library(recipes) set.seed(1) roll <- sim_swiss_roll(1000) train <- roll[1:800, ] test <- roll[801:1000, ] rec <- recipe(~ x + y + z, data = train) |> step_manifold_sculpting(all_numeric_predictors(), num_comp = 2, seed = 1) rec_trained <- prep(rec) rec_trained bake(rec_trained, new_data = test) tidy(rec_trained, number = 1)library(recipes) set.seed(1) roll <- sim_swiss_roll(1000) train <- roll[1:800, ] test <- roll[801:1000, ] rec <- recipe(~ x + y + z, data = train) |> step_manifold_sculpting(all_numeric_predictors(), num_comp = 2, seed = 1) rec_trained <- prep(rec) rec_trained bake(rec_trained, new_data = test) tidy(rec_trained, number = 1)