Skip to content

Commit 2d289ec

Browse files
committed
fix: renovating graph laplacian - old implementation seemed strange
1 parent ecf593d commit 2d289ec

File tree

2 files changed

+42
-93
lines changed

2 files changed

+42
-93
lines changed

sklearn/manifold/utils.py

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -22,74 +22,7 @@
2222

2323
# follow details from luxberg - tutorial on spectral clustering
2424

25-
# from ..utils.graph
26-
def _graph_laplacian_sparse(graph, normed=False, return_diag=False):
27-
n_nodes = graph.shape[0]
28-
if not graph.format == 'coo':
29-
lap = (-graph).tocoo()
30-
else:
31-
lap = -graph.copy()
32-
diag_mask = (lap.row == lap.col)
33-
if not diag_mask.sum() == n_nodes:
34-
# The sparsity pattern of the matrix has holes on the diagonal,
35-
# we need to fix that
36-
diag_idx = lap.row[diag_mask]
37-
38-
lap = lap.tolil()
39-
40-
diagonal_holes = list(set(range(n_nodes)).difference(
41-
diag_idx))
42-
lap[diagonal_holes, diagonal_holes] = 1
43-
lap = lap.tocoo()
44-
diag_mask = (lap.row == lap.col)
45-
lap.data[diag_mask] = 0
46-
w = -np.asarray(lap.sum(axis=1)).squeeze()
47-
if normed:
48-
w = np.sqrt(w)
49-
w_zeros = w == 0
50-
w[w_zeros] = 1
51-
lap.data /= w[lap.row]
52-
lap.data /= w[lap.col]
53-
lap.data[diag_mask] = (1 - w_zeros).astype(lap.data.dtype)
54-
else:
55-
lap.data[diag_mask] = w[lap.row[diag_mask]]
56-
if return_diag:
57-
return lap, w
58-
return lap
59-
60-
61-
def _graph_laplacian_dense(graph, normed=False, return_diag=False):
62-
n_nodes = graph.shape[0]
63-
lap = -graph.copy()
64-
lap.flat[::n_nodes + 1] = 0
65-
w = -lap.sum(axis=0)
66-
if normed:
67-
w = np.sqrt(w)
68-
w_zeros = w == 0
69-
w[w_zeros] = 1
70-
lap /= w
71-
lap /= w[:, np.newaxis]
72-
lap.flat[::n_nodes + 1] = 1 - w_zeros
73-
else:
74-
lap.flat[::n_nodes + 1] = w
75-
if return_diag:
76-
return lap, w
77-
return lap
78-
79-
80-
def graph_laplacian(graph, normed=False, return_diag=False):
81-
""" Return the Laplacian of the given graph.
82-
"""
83-
if normed and (np.issubdtype(graph.dtype, np.int)
84-
or np.issubdtype(graph.dtype, np.uint)):
85-
graph = graph.astype(np.float)
86-
if sparse.isspmatrix(graph):
87-
return _graph_laplacian_sparse(graph, normed=normed,
88-
return_diag=return_diag)
89-
else:
90-
# We have a numpy array
91-
return _graph_laplacian_dense(graph, normed=normed,
92-
return_diag=return_diag)
25+
# being implemented in ..utils.graph
9326

9427
# Eigenvalue estimation
9528
# ---------------------

sklearn/utils/graph.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -110,35 +110,51 @@ def _graph_laplacian_sparse(graph, normed=False, return_diag=False):
110110
return lap
111111

112112

113-
def _graph_laplacian_dense(graph, normed=False, return_diag=False):
113+
def _graph_laplacian_dense(graph, normalize=None):
114114
n_nodes = graph.shape[0]
115-
lap = -graph.copy()
116-
lap.flat[::n_nodes + 1] = 0
117-
w = -lap.sum(axis=0)
118-
if normed:
119-
w = np.sqrt(w)
120-
w_zeros = w == 0
121-
w[w_zeros] = 1
122-
lap /= w
123-
lap /= w[:, np.newaxis]
124-
lap.flat[::n_nodes + 1] = 1 - w_zeros
125-
else:
126-
lap.flat[::n_nodes + 1] = w
127-
if return_diag:
128-
return lap, w
129-
return lap
130-
131-
132-
def graph_laplacian(graph, normed=False, return_diag=False):
115+
L = -graph.copy()
116+
d = -lap.sum(axis=0) # Compute the degrees of each node
117+
L.flat[::n_nodes + 1] += d
118+
if normalize == 'sym':
119+
Dinv = np.diag(1/d)
120+
L = np.dot(np.sqrt(Dinv), np.dot(L, np.sqrt(Dinv)))
121+
elif normalize == 'rw':
122+
Dinv = np.diag(1/d)
123+
L = np.dot(Dinv, L)
124+
elif not normalize is None:
125+
raise ValueError('normalize must be one of None, "sym", "rw"')
126+
return L
127+
128+
129+
def graph_laplacian(graph, normalize=None):
133130
""" Return the Laplacian of the given graph.
131+
132+
Parameters
133+
----------
134+
graph: [n, n]
135+
sparse or dense array representing a similarity matrix (symmetric,
136+
s_ij >=0)
137+
normalize: (None, 'sym', 'rw')
138+
return the normalized graph Laplacian
139+
None: $L = D - S$
140+
'sym': $L_{sym} = D^{-1/2}S*D^{-1/2}$
141+
'rw': $L_{rw} = D^{-1}*S$
142+
143+
Returns
144+
-------
145+
laplacian: [n, n]
146+
Laplacian of the input similarity matrix
147+
148+
Reference
149+
---------
150+
.. [1]: Von Luxberg (2007). Stat Comput (2007) 17: 395–416
151+
http://dx.doi.org/10.1007/s11222-007-9033-z
134152
"""
135-
if normed and (np.issubdtype(graph.dtype, np.int)
136-
or np.issubdtype(graph.dtype, np.uint)):
153+
if normalize is None and (np.issubdtype(graph.dtype, np.int)
154+
or np.issubdtype(graph.dtype, np.uint)):
137155
graph = graph.astype(np.float)
138156
if sparse.isspmatrix(graph):
139-
return _graph_laplacian_sparse(graph, normed=normed,
140-
return_diag=return_diag)
157+
return _graph_laplacian_sparse(graph, normalize=normalize)
141158
else:
142159
# We have a numpy array
143-
return _graph_laplacian_dense(graph, normed=normed,
144-
return_diag=return_diag)
160+
return _graph_laplacian_dense(graph, normalize=normalize)

0 commit comments

Comments
 (0)