spectral-clustering-js
Version:
Spectral clustering package for TypeScript
119 lines (92 loc) • 4.01 kB
text/typescript
import { Utils } from "./Utils";
import { Graph } from "./Graph";
import { Matrix, EigenvalueDecomposition } from 'ml-matrix';
import { Kmeans1D } from "./Kmeans1D";
/**
* Class dedicated to spectral clustering :
* * compute the eigen values
* * sort the eigen vectors accorting to the eigen values
* * focus on the 2nd eiven vector (Fiedler's)
* * run a k-means to cluster the components of the Fiedler's vector
*/
export class SpectralClustering {
private graph: Graph;
constructor(graph : Graph){
this.graph = graph;
}
public compute(options = new Map<string, string|number>()):void{
const defaultOptions = new Map<string, string|number>([
["laplacianMatrix" , "connected"],
["requestedNbClusters", -1],
["maxClusters" , 8]
]);
const runningOptions = Utils.resolveRunningParameters(defaultOptions, options);
let laplacianMatrix = null;
if (runningOptions.get("laplacianMatrix") == "connected"){
laplacianMatrix = this.extractStrictLaplacianMatrix();
}else{
laplacianMatrix = this.extractDistanceLaplacianMatrix();
}
const eigen = new EigenvalueDecomposition(laplacianMatrix, {assumeSymmetric: true});
const nodes = this.graph.getNodes();
// the eigen vectors & values are already sorted
// there is not need to do it again
const fiedlerVector = eigen.eigenvectorMatrix.getColumn(1);
// k-means to divide the FiedlerVector
const kmeanCluster = new Kmeans1D(fiedlerVector);
// is the number of clusters already known ?
const requestedNbClusters = <number>(runningOptions.get("requestedNbClusters"));
let clusterResult = null;
if (requestedNbClusters == -1){
clusterResult = kmeanCluster.findBestClustering( <number>(runningOptions.get("maxClusters")) );
}else{
// the methods returns [clusters, centroids], let's keep the clusters only
clusterResult = kmeanCluster.tryToCluster(requestedNbClusters)[0];
}
for (let i = 0; i<fiedlerVector.length; i++){
const nodeI = nodes[i];
nodeI.setCluster(clusterResult[i]);
}
}
private extractStrictLaplacianMatrix(): Matrix{
const result = new Matrix(this.graph.getNodes().length, this.graph.getNodes().length);
const nodes = this.graph.getNodes();
for (let i = 0; i<nodes.length; i++){
const nodeI = nodes[i];
const connected = nodeI.getConnectedNodes();
for (let j = 0; j<nodes.length; j++){
const nodeJ = nodes[j];
if (nodeI.isConnectedTo(nodeJ)){
result.set(i,j,-1);
}
}
result.set(i,i, connected.size );
}
return result;
}
/**
* Build a matrix showing the distance of nodes
*/
private extractDistanceLaplacianMatrix(): Matrix{
const result = new Matrix(this.graph.getNodes().length, this.graph.getNodes().length);
const nodes = this.graph.getNodes();
for (let i = 0; i<nodes.length; i++){
const nodeI = nodes[i];
let sumDistances = 0;
for (let j = 0; j<nodes.length; j++){
if (i != j){
const nodeJ = nodes[j];
if (nodeI.isConnectedTo(nodeJ)){
const distance = nodeI.getPoint().euclieanDistanceTo(nodeJ.getPoint());
//let distanceInLaplacian = 1/distance;
const distanceInLaplacian = 1/Math.log10(distance);
result.set(i,j, -distanceInLaplacian);
sumDistances += distanceInLaplacian;
}
}
}
result.set(i,i, sumDistances);
}
return result;
}
}