def test_lanczos(self): solver = LanczosEig('LM') solver.fit(self.adjacency, 2) self.assertEqual(len(solver.eigenvalues_), 2) self.assertAlmostEqual(eigenvector_err(self.adjacency, solver.eigenvectors_, solver.eigenvalues_), 0) solver.fit(self.slr, 2) self.assertEqual(len(solver.eigenvalues_), 2) self.assertAlmostEqual(eigenvector_err(self.slr, solver.eigenvectors_, solver.eigenvalues_), 0) adjacency = karate_club() solver = LanczosEig('SM') solver.fit(adjacency, 2) self.assertEqual(len(solver.eigenvalues_), 2) self.assertAlmostEqual(eigenvector_err(adjacency, solver.eigenvectors_, solver.eigenvalues_), 0)
def fit( self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'LaplacianEmbedding': """Compute the graph embedding. Parameters ---------- adjacency : Adjacency matrix of the graph (symmetric matrix). Returns ------- self: :class:`LaplacianEmbedding` """ adjacency = check_format(adjacency).asfptype() check_square(adjacency) check_symmetry(adjacency) n = adjacency.shape[0] regularize: bool = not (self.regularization is None or self.regularization == 0.) check_scaling(self.scaling, adjacency, regularize) if regularize: solver: EigSolver = LanczosEig() else: solver = set_solver(self.solver, adjacency) n_components = 1 + check_n_components(self.n_components, n - 2) weights = adjacency.dot(np.ones(n)) regularization = self.regularization if regularization: if self.relative_regularization: regularization = regularization * weights.sum() / n**2 weights += regularization * n laplacian = LaplacianOperator(adjacency, regularization) else: weight_diag = sparse.diags(weights, format='csr') laplacian = weight_diag - adjacency solver.which = 'SM' solver.fit(matrix=laplacian, n_components=n_components) eigenvalues = solver.eigenvalues_[1:] eigenvectors = solver.eigenvectors_[:, 1:] embedding = eigenvectors.copy() if self.scaling: eigenvalues_inv_diag = diag_pinv(eigenvalues**self.scaling) embedding = eigenvalues_inv_diag.dot(embedding.T).T if self.normalized: embedding = normalize(embedding, p=2) self.embedding_ = embedding self.eigenvalues_ = eigenvalues self.eigenvectors_ = eigenvectors self.regularization_ = regularization return self
def __init__(self, n_components: int = 2, normalized_laplacian=True, regularization: Union[None, float] = 0.01, relative_regularization: bool = True, equalize: bool = False, barycenter: bool = True, normalized: bool = True, solver: Union[str, EigSolver] = 'auto'): super(Spectral, self).__init__() self.n_components = n_components self.normalized_laplacian = normalized_laplacian if regularization == 0: self.regularization = None else: self.regularization = regularization self.relative_regularization = relative_regularization self.equalize = equalize self.barycenter = barycenter self.normalized = normalized if solver == 'halko': self.solver: EigSolver = HalkoEig() elif solver == 'lanczos': self.solver: EigSolver = LanczosEig() else: self.solver = solver self.eigenvalues_ = None self.eigenvectors_ = None self.regularization_ = None
def set_solver(solver: str, adjacency): """Eigenvalue solver based on keyword""" if solver == 'auto': solver: str = auto_solver(adjacency.nnz) if solver == 'lanczos': solver: EigSolver = LanczosEig() else: # pragma: no cover solver: EigSolver = HalkoEig() return solver
def test_compare_solvers(self): lanczos = LanczosEig('LM') halko = HalkoEig('LM', random_state=self.random_state) lanczos.fit(self.adjacency, 2) halko.fit(self.adjacency, 2) self.assertAlmostEqual(np.linalg.norm(lanczos.eigenvalues_ - halko.eigenvalues_), 0.) lanczos.fit(self.slr, 2) halko.fit(self.slr, 2) self.assertAlmostEqual(np.linalg.norm(lanczos.eigenvalues_ - halko.eigenvalues_), 0.)
def __init__(self, embedding_dimension: int = 2, normalized_laplacian=True, regularization: Union[None, float] = 0.01, relative_regularization: bool = True, scaling: Union[None, str] = 'multiply', solver: Union[str, EigSolver] = 'auto', tol: float = 1e-10): super(Spectral, self).__init__() self.embedding_dimension = embedding_dimension self.normalized_laplacian = normalized_laplacian if regularization == 0: self.regularization = None else: self.regularization = regularization self.relative_regularization = relative_regularization self.scaling = scaling if scaling == 'multiply' and not normalized_laplacian: self.scaling = None warnings.warn( Warning( "The scaling 'multiply' is valid only with ``normalized_laplacian = 'True'``. " "It will be ignored.")) if solver == 'halko': self.solver: EigSolver = HalkoEig(which='SM') elif solver == 'lanczos': self.solver: EigSolver = LanczosEig(which='SM') else: self.solver = solver self.tol = tol self.eigenvalues_ = None self.regularization_ = None
def fit(self, input_matrix: Union[sparse.csr_matrix, np.ndarray], force_bipartite: bool = False) -> 'Spectral': """Compute the graph embedding. If the input matrix :math:`B` is not square (e.g., biadjacency matrix of a bipartite graph) or not symmetric (e.g., adjacency matrix of a directed graph), use the adjacency matrix :math:`A = \\begin{bmatrix} 0 & B \\\\ B^T & 0 \\end{bmatrix}` and return the embedding for both rows and columns of the input matrix :math:`B`. Parameters ---------- input_matrix : Adjacency matrix or biadjacency matrix of the graph. force_bipartite : bool (default = ``False``) If ``True``, force the input matrix to be considered as a biadjacency matrix. Returns ------- self: :class:`Spectral` """ # input adjacency, self.bipartite = get_adjacency( input_matrix, allow_directed=False, force_bipartite=force_bipartite) n = adjacency.shape[0] # regularization regularization = self._get_regularization(self.regularization, adjacency) self.regularized = regularization > 0 # laplacian laplacian = Laplacian(adjacency, regularization, self.normalized_laplacian) # spectral decomposition n_components = check_n_components(self.n_components, n - 2) + 1 solver = LanczosEig(which='SM') solver.fit(matrix=laplacian, n_components=n_components) index = np.argsort( solver.eigenvalues_)[1:] # increasing order, skip first eigenvalues = solver.eigenvalues_[index] eigenvectors = solver.eigenvectors_[:, index] # embedding if self.normalized_laplacian: embedding = laplacian.norm_diag.dot(eigenvectors) else: embedding = eigenvectors.copy() # output self.embedding_ = embedding self.eigenvalues_ = eigenvalues self.eigenvectors_ = eigenvectors if self.bipartite: self._split_vars(input_matrix.shape) return self
def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'Spectral': """Fits the model from data in adjacency. Parameters ---------- adjacency : Adjacency matrix of the graph (symmetric matrix). Returns ------- self: :class:`Spectral` """ adjacency = check_format(adjacency).asfptype() if not is_square(adjacency): raise ValueError( 'The adjacency matrix is not square. See BiSpectral.') if not is_symmetric(adjacency): raise ValueError( 'The adjacency matrix is not symmetric.' 'Either convert it to a symmetric matrix or use BiSpectral.') n = adjacency.shape[0] if self.solver == 'auto': solver = auto_solver(adjacency.nnz) if solver == 'lanczos': self.solver: EigSolver = LanczosEig() else: self.solver: EigSolver = HalkoEig() if self.embedding_dimension > n - 2: warnings.warn( Warning( "The dimension of the embedding must be less than the number of nodes - 1." )) n_components = n - 2 else: n_components = self.embedding_dimension + 1 if (self.regularization is None or self.regularization == 0.) and not is_connected(adjacency): warnings.warn( Warning( "The graph is not connected and low-rank regularization is not active." "This can cause errors in the computation of the embedding." )) if isinstance(self.solver, HalkoEig) and not self.normalized_laplacian: raise NotImplementedError( "Halko solver is not yet compatible with regular Laplacian." "Call 'fit' with 'normalized_laplacian' = True or force lanczos solver." ) weights = adjacency.dot(np.ones(n)) regularization = self.regularization if regularization: if self.relative_regularization: regularization = regularization * weights.sum() / n**2 weights += regularization * n if self.normalized_laplacian: # Finding the largest eigenvalues of the normalized adjacency is easier for the solver than finding the # smallest eigenvalues of the normalized laplacian. normalizing_matrix = diag_pinv(np.sqrt(weights)) if regularization: norm_adjacency = NormalizedAdjacencyOperator( adjacency, regularization) else: norm_adjacency = normalizing_matrix.dot( adjacency.dot(normalizing_matrix)) self.solver.which = 'LA' self.solver.fit(matrix=norm_adjacency, n_components=n_components) eigenvalues = 1 - self.solver.eigenvalues_ # eigenvalues of the Laplacian in increasing order index = np.argsort(eigenvalues) # skip first eigenvalue eigenvalues = eigenvalues[index][1:] # keep only positive eigenvectors of the normalized adjacency matrix eigenvectors = self.solver.eigenvectors_[:, index][:, 1:] * ( eigenvalues < 1 - self.tol) embedding = np.array(normalizing_matrix.dot(eigenvectors)) else: if regularization: laplacian = LaplacianOperator(adjacency, regularization) else: weight_matrix = sparse.diags(weights, format='csr') laplacian = weight_matrix - adjacency self.solver.which = 'SM' self.solver.fit(matrix=laplacian, n_components=n_components) eigenvalues = self.solver.eigenvalues_[1:] embedding = self.solver.eigenvectors_[:, 1:] if self.scaling: if self.scaling == 'multiply': eigenvalues = np.minimum(eigenvalues, 1) embedding *= np.sqrt(1 - eigenvalues) elif self.scaling == 'divide': inv_eigenvalues = np.zeros_like(eigenvalues) index = np.where(eigenvalues > 0)[0] inv_eigenvalues[index] = 1 / eigenvalues[index] embedding *= np.sqrt(inv_eigenvalues) else: warnings.warn( Warning( "The scaling must be 'multiply' or 'divide'. No scaling done." )) self.embedding_ = embedding self.eigenvalues_ = eigenvalues self.regularization_ = regularization return self
def fit(self, adjacency: Union[sparse.csr_matrix, np.ndarray]) -> 'Spectral': """Compute the graph embedding. Parameters ---------- adjacency : Adjacency matrix of the graph (symmetric matrix). Returns ------- self: :class:`Spectral` """ adjacency = check_format(adjacency).asfptype() check_square(adjacency) check_symmetry(adjacency) n = adjacency.shape[0] if self.solver == 'auto': solver = auto_solver(adjacency.nnz) if solver == 'lanczos': self.solver: EigSolver = LanczosEig() else: # pragma: no cover self.solver: EigSolver = HalkoEig() n_components = check_n_components(self.n_components, n - 2) n_components += 1 if self.equalize and (self.regularization is None or self.regularization == 0.) and not is_connected(adjacency): raise ValueError( "The option 'equalize' is valid only if the graph is connected or with regularization." "Call 'fit' either with 'equalize' = False or positive 'regularization'." ) weights = adjacency.dot(np.ones(n)) regularization = self.regularization if regularization: if self.relative_regularization: regularization = regularization * weights.sum() / n**2 weights += regularization * n if self.normalized_laplacian: # Finding the largest eigenvalues of the normalized adjacency is easier for the solver than finding the # smallest eigenvalues of the normalized laplacian. weights_inv_sqrt_diag = diag_pinv(np.sqrt(weights)) if regularization: norm_adjacency = NormalizedAdjacencyOperator( adjacency, regularization) else: norm_adjacency = weights_inv_sqrt_diag.dot( adjacency.dot(weights_inv_sqrt_diag)) self.solver.which = 'LA' self.solver.fit(matrix=norm_adjacency, n_components=n_components) eigenvalues = 1 - self.solver.eigenvalues_ # eigenvalues of the Laplacian in increasing order index = np.argsort(eigenvalues)[1:] # skip first eigenvalue eigenvalues = eigenvalues[index] # eigenvectors of the Laplacian, skip first eigenvector eigenvectors = np.array( weights_inv_sqrt_diag.dot(self.solver.eigenvectors_[:, index])) else: if regularization: laplacian = LaplacianOperator(adjacency, regularization) else: weight_diag = sparse.diags(weights, format='csr') laplacian = weight_diag - adjacency self.solver.which = 'SM' self.solver.fit(matrix=laplacian, n_components=n_components) eigenvalues = self.solver.eigenvalues_[1:] eigenvectors = self.solver.eigenvectors_[:, 1:] embedding = eigenvectors.copy() if self.equalize: eigenvalues_sqrt_inv_diag = diag_pinv(np.sqrt(eigenvalues)) embedding = eigenvalues_sqrt_inv_diag.dot(embedding.T).T if self.barycenter: eigenvalues_diag = sparse.diags(eigenvalues) subtract = eigenvalues_diag.dot(embedding.T).T if not self.normalized_laplacian: weights_inv_diag = diag_pinv(weights) subtract = weights_inv_diag.dot(subtract) embedding -= subtract if self.normalized: embedding = normalize(embedding, p=2) self.embedding_ = embedding self.eigenvalues_ = eigenvalues self.eigenvectors_ = eigenvectors self.regularization_ = regularization return self