All Downloads are FREE. Search and download functionalities are using the official Maven repository.

smile.manifold.LaplacianEigenmap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2025 Haifeng Li. All rights reserved.
 *
 * Smile is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Smile is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Smile. If not, see .
 */
package smile.manifold;

import java.util.Collection;
import java.util.Properties;
import smile.data.SparseDataset;
import smile.graph.AdjacencyList;
import smile.graph.Graph.Edge;
import smile.graph.NearestNeighborGraph;
import smile.math.MathEx;
import smile.math.distance.Distance;
import smile.math.matrix.ARPACK;
import smile.math.matrix.Matrix;
import smile.math.matrix.SparseMatrix;
import smile.util.SparseArray;

/**
 * Laplacian Eigenmaps. Using the notion of the Laplacian of the nearest
 * neighbor adjacency graph, Laplacian Eigenmaps computes a low dimensional
 * representation of the dataset that optimally preserves local neighborhood
 * information in a certain sense. The representation map generated by the
 * algorithm may be viewed as a discrete approximation to a continuous map
 * that naturally arises from the geometry of the manifold.
 * 

* The locality preserving character of the Laplacian Eigenmaps algorithm makes * it relatively insensitive to outliers and noise. It is also not prone to * "short-circuiting" as only the local distances are used. * * @see IsoMap * @see LLE * @see UMAP * *

References

*
    *
  1. Mikhail Belkin and Partha Niyogi. Laplacian Eigenmaps and Spectral Techniques for Embedding and Clustering. NIPS, 2001.
  2. *
* * @author Haifeng Li */ public class LaplacianEigenmap { /** Private constructor to prevent object creation. */ private LaplacianEigenmap() { } /** * Laplacian Eigenmaps hyperparameters. * @param k k-nearest neighbor. * @param d the dimension of the manifold. * @param t the smooth/width parameter of heat kernel exp(-||x-y||2 / t). * Non-positive value means discrete weights. */ public record Options(int k, int d, double t) { /** Constructor. */ public Options { if (k < 2) { throw new IllegalArgumentException("Invalid number of nearest neighbors: " + k); } if (d < 2) { throw new IllegalArgumentException("Invalid dimension of feature space: " + d); } } /** * Constructor. * @param k k-nearest neighbor. */ public Options(int k) { this(k, 2, -1); } /** * Returns the persistent set of hyperparameters. * @return the persistent set. */ public Properties toProperties() { Properties props = new Properties(); props.setProperty("smile.laplacian_eigenmap.k", Integer.toString(k)); props.setProperty("smile.laplacian_eigenmap.d", Integer.toString(d)); props.setProperty("smile.laplacian_eigenmap.t", Double.toString(t)); return props; } /** * Returns the options from properties. * * @param props the hyperparameters. * @return the options. */ public static Options of(Properties props) { int k = Integer.parseInt(props.getProperty("smile.laplacian_eigenmap.k", "7")); int d = Integer.parseInt(props.getProperty("smile.laplacian_eigenmap.d", "2")); double t = Double.parseDouble(props.getProperty("smile.laplacian_eigenmap.t", "-1")); return new Options(k, d, t); } } /** * Laplacian Eigenmaps with Gaussian kernel. * @param data the input data. * @param options the hyperparameters. * @return the embedding coordinates. */ public static double[][] fit(double[][] data, Options options) { return fit(data, MathEx::distance, options); } /** * Laplacian Eigenmaps with discrete weights. * @param data the input data. * @param distance the distance function. * @param options the hyperparameters. * @param the data type of points. * @return the embedding coordinates. */ public static double[][] fit(T[] data, Distance distance, Options options) { // Use the largest connected component of nearest neighbor graph. NearestNeighborGraph nng = NearestNeighborGraph.of(data, distance, options.k); return fit(nng.largest(false), options); } /** * Laplacian Eigenmaps with Gaussian kernel. * @param nng the k-nearest neighbor graph. * @param options the hyperparameters. * @return the embedding coordinates. */ public static double[][] fit(NearestNeighborGraph nng, Options options) { AdjacencyList graph = nng.graph(false); int n = graph.getVertexCount(); int d = options.d; double t = options.t; double[] D = new double[n]; double gamma = -1.0 / t; SparseArray[] W = new SparseArray[n]; for (int i = 0; i < n; i++) { SparseArray Wi = new SparseArray(); Collection edges = graph.getEdges(i); for (Edge edge : edges) { double w = t <= 0 ? 1.0 : Math.exp(gamma * edge.weight() * edge.weight()); Wi.set(edge.v(), w); D[i] += w; } D[i] = 1 / Math.sqrt(D[i]); W[i] = Wi; } for (int i = 0; i < n; i++) { double Di = D[i]; SparseArray Wi = W[i]; Wi.update((j, value) -> -Di * value * D[j]); Wi.set(i, 1.0); } // Here L is actually I - D^(-1/2) * W * D^(-1/2) SparseMatrix L = SparseDataset.of(W, n).toMatrix(); // ARPACK may not find all needed eigenvalues for k = d + 1. // Hack it with 10 * (d + 1). Matrix.EVD eigen = ARPACK.syev(L, ARPACK.SymmOption.SM, Math.min(10*(d+1), n-1)); Matrix V = eigen.Vr; double[][] coordinates = new double[n][d]; for (int j = d; --j >= 0; ) { double norm = 0.0; int c = V.ncol() - j - 2; for (int i = 0; i < n; i++) { double xi = V.get(i, c) * D[i]; coordinates[i][j] = xi; norm += xi * xi; } norm = Math.sqrt(norm); for (int i = 0; i < n; i++) { coordinates[i][j] /= norm; } } return coordinates; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy