smallvis exists to make it easier to experiment with
neighborhood embedding methods and to understand how their component
parts affect the results. The usage guide cover
the current interface; this page collects the background and dated
development notes.
Earlier implementation notes
- March 23 2019: Methods that use the exponential function (e.g. NeRV, JSE, SSNE, ASNE) are now more robust, but sadly a lot slower, due to me implementing the log-sum-exp “trick” to avoid numeric underflow. This mainly helps JSE, which showed a tendency to have its gradients suddenly explode. It’s still difficult to optimize, though. Perhaps symmetric JSE can help under those circumstances.
- Feb 13 2018: the UMAP
paper is out, but I have yet to read and understand it fully, so the
implementation in
smallviscurrently relies on my examination of the UMAP source code, with some much-appreciated clarification from UMAP creator Leland McInnes. Expect some bugs, and any horrifically bogus results should be double-checked with the output of the official UMAP implementation before casting calumnies on the quality of UMAP itself. - LargeVis requires the use of a
gammaparameter, which weights the contribution of attractive and repulsive contributions to the cost function. In the real LargeVis, it is recommended to set this value to7, but this relies on the specifics of the stochastic gradient descent method. Insmallvis, this value is very dataset dependent: the more data you have, the smaller gamma should be to avoid over-emphasising repulsive interactions. - LargeVis partitions each pairwise interaction into either an
attractive or repulsive contribution. In
smallvis, each interaction is a combination of both. - Both UMAP and LargeVis use a classic stochastic gradient descent
approach with a decaying learning rate. The implementation in
smallvisuses the same delta-bar-delta optimization method used in t-SNE. It works well in my experience, but may require some tuning and more iterations compared to optimizing t-SNE. - In this setting, LargeVis and UMAP gradient requires quite a large
value of epsilon to avoid division by zero and get decent results. It’s
hard-coded to 0.1 in the LargeVis source code, so I have used the same
value by default in
smallvis. It can be controlled with the method optiongr_eps. - Mainly for my own benefit, there is also a theory page showing a comparison of cost functions and gradients. Also, some material on the various spectral methods, which justifies the use of Laplacian Eigenmaps (a bit).
Background
August 11 2024 The Turbo Championship Edition
Update. I have briefly brought smallvis back from
the dead to speed it up a bit. I have added:
- Barnes-Hut t-SNE. This will scale up to larger datasets and it is
feasible to run it on the full MNIST digits dataset (i.e. 70,000 items).
This uses a (2D only) C++ translation of the cython implementation in
the Python openTSNE package
originally authored by Pavlin Poličar. It’s BSD
3-clause licensed (and can be found in
src/bh.h). Use it withmethod = "tsne", bh = TRUE. The degree of approximation can be controlled withtheta. Be aware that it’s not as fast as e.g. Rtsne, at least during the optimization step. That package uses Laurens van der Maaten’s original C++ code which I am very unsure can be redistributed with R code due to its BSD 4-clause license. I would love to be wrong about that though! The Quad Tree implementation could be used with other embedding methods, but I haven’t got round to implementing that yet. - A C++ multi-threaded perplexity search using only the nearest neighbors of each point (3 times the perplexity) is used with BH t-SNE.
- My own rnndescent
package replaces FNN for nearest neighbor search. Apart from being a
monument to my ego, it can be faster than FNN for brute force search
because it can be multi-threaded (use
n_threadsto control this). Also, approximate nearest neighbor search becomes quite important with larger datasets. - For exact search, I have started adding multi-threaded C++ code to
calculate the gradient. Set
use_cpp = TRUEto use this. It’s not as big a win in speed up as you might hope because the R code is using some very optimized linear algebra for some steps which will blow my puny C++ code out of the water. However in many cases the linear algebra libraries won’t be mult-threaded so sheer brute force threads can overcome this. Just don’t expect settingnthreads to give yountimes the speed. Like Barnes-Hut, this requires me to implement the gradients in C++ for each method and I haven’t done that yet. - irlba is now a dependency for doing PCA on larger datasets.
I will probably at least attempt to apply Barnes-Hut and multi-threading to some other methods. On the other hand, it’s taken me five years to get back to this, so don’t hold your breath.
My Idle Thoughts
smallvis exists mainly to satisfy my urge to answer the
various, minor, stamp-collecting
questions that have occurred to me as I have read the dimensionality
reduction literature. Those that I have cobbled together into something
that demonstrates the use of smallvis can be found at the
articles page.
See Also
For large scale visualization in R see:
Also of relevance are:
- UMAP (in Python)
- uwot a package implementing LargeVis and UMAP.
- LargeVis (in C++)
- Spectra, the C++ library that RSpectra wraps.
- FIt-SNE, an FFT-based t-SNE library. I have implemented the “late exaggeration” method that it uses. See their paper for more details.
Much of the code here is based on my fork of Justin Donaldson’s R package for t-SNE.
License
GPLv2 or later. Any LargeVis-specific code (e.g. cost and gradient calculation) can also be considered Apache 2.0. Similarly, Barnes-Hut and UMAP-related code is also licensed as BSD 3-clause.