--- title: 'Fast Matrix Approximation with the Nyström Method' output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Fast Matrix Approximation with the Nyström Method} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} params: family: red css: albers.css resource_files: - albers.css - albers.js includes: in_header: |- --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width=6, fig.height=4) library(multivarious) library(stats) ``` ## The Scalability Problem When working with similarity or kernel matrices, the computational cost grows rapidly: - **Memory**: O(N²) to store an N × N matrix - **Time**: O(N³) for eigendecomposition For N = 10,000, that's 100 million entries and billions of operations. For N = 100,000, it becomes intractable. ## The Nyström Approximation The Nyström method provides a fast approximation by using only a subset of "landmark" points: 1. Select m << N landmark points 2. Compute the m × m matrix between landmarks 3. Compute the N × m matrix between all points and landmarks 4. Use these to approximate the full eigendecomposition This reduces complexity from O(N³) to O(Nm²) — a massive speedup when m is small. ## Quick Start ```{r quick_start} set.seed(42) N <- 1000 p <- 50 X <- matrix(rnorm(N * p), N, p) # Nyström approximation with linear kernel (default) # Uses only 100 landmarks instead of all 1000 points ny_fit <- nystrom_approx( X, ncomp = 10, nlandmarks = 100, preproc = center() ) print(ny_fit) # Standard bi_projector interface head(scores(ny_fit)) ``` ## Specifying a Kernel Function By default, `nystrom_approx()` uses a linear kernel. You can provide any kernel function: ```{r custom_kernel} # RBF (Gaussian) kernel rbf_kernel <- function(X, Y = NULL, sigma = 1) { if (is.null(Y)) Y <- X sumX2 <- rowSums(X^2) sumY2 <- rowSums(Y^2) sqdist <- outer(sumX2, sumY2, `+`) - 2 * tcrossprod(X, Y) sqdist[sqdist < 0] <- 0 exp(-sqdist / (2 * sigma^2)) } ny_rbf <- nystrom_approx( X, kernel_func = rbf_kernel, ncomp = 10, nlandmarks = 100 ) ``` ## Projecting New Data The result is a `bi_projector`, so you can project new observations: ```{r projection} X_new <- matrix(rnorm(50 * p), 50, p) new_scores <- project(ny_fit, X_new) dim(new_scores) ``` ## Double Nyström for Extra Speed For very large datasets, the "double" method applies the approximation twice, reducing complexity further: ```{r double_nystrom} # Standard method system.time( ny_standard <- nystrom_approx(X, ncomp = 5, nlandmarks = 200, method = "standard") ) # Double Nyström (faster with intermediate rank l) system.time( ny_double <- nystrom_approx(X, ncomp = 5, nlandmarks = 200, method = "double", l = 50) ) ``` ## Choosing Parameters ### Number of Landmarks | Dataset Size | Suggested Landmarks | Notes | |-------------|-------------------|-------| | < 1,000 | 50–200 | Larger fraction acceptable | | 1,000–10,000| 200–500 | Good accuracy/speed balance | | > 10,000 | 500–2,000 | Consider Double Nyström | ### Method Selection - **Standard**: Higher accuracy, use when m < 2,000 - **Double**: Faster for very large m, adds intermediate rank parameter `l` ## Technical Details
Click for mathematical details The Nyström approximation uses the fact that a positive semi-definite matrix K can be approximated as: $$K \approx K_{nm} K_{mm}^{-1} K_{mn}$$ where: - $K_{mm}$ is the m × m matrix between landmarks - $K_{nm}$ is the N × m matrix between all points and landmarks The eigendecomposition of this approximation can be computed efficiently: 1. Compute eigendecomposition of $K_{mm} = U_m \Lambda_m U_m^T$ 2. Approximate eigenvectors of K as: $U \approx K_{nm} U_m \Lambda_m^{-1/2}$ Double Nyström applies this approximation recursively, reducing complexity from O(Nm² + m³) to O(Nml + l³) where l << m.
## References - Williams, C. K. I., & Seeger, M. (2001). Using the Nyström Method to Speed Up Kernel Machines. *NIPS*. - Lim, D., Jin, R., & Zhang, L. (2015). An Efficient and Accurate Nyström Scheme for Large-Scale Data Sets. *AAAI*. ## See Also - `pca()` for standard PCA on smaller datasets - `svd_wrapper()` for various SVD backends