Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Spectral Graph Theory

Open in Colab

Spectral Graph Theory studies graphs using associated matrices such as the adjacency matrix and graph Laplacian. Let G(V,E)G(V, E) be a graph. We’ll let n=∣V∣n = |V| denote the number of vertices/nodes, and m=∣E∣m = |E| denote the number of edges. We’ll assume that vertices are indexed by 0,…,n−10,\dots,n-1, and edges are indexed by 0,…,m−10,\dots,m-1.

The adjacency matrix AA is a n×nn\times n matrix with Ai,j=1A_{i,j} = 1 if (i,j)∈E(i,j) \in E is an edge, and Ai,j=0A_{i,j} = 0 if (i,j)∉E(i,j) \notin E. If GG is an undirected graph, then AA is symmetric. If GG is directed, then AA need not be symmetric.

The degree of a node ii, deg(i)deg(i) is the number of neighbors of ii, meaning the number of edges which ii participates in. You can calculate the vector of degrees (a vector dd of length nn, where di=deg(i)d_i = deg(i)), using matrix-vector mulpilication:

d=A1d = A 1

where 1 is the vector containing all 1s of length nn. You could also just sum the row entries of AA. We will also use D=diag(d)D = diag(d) - a diagonal matrix with Di,i=diD_{i,i} = d_i.

The incidence matrix BB is a n×mn \times m matrix which encodes how edges and vertices are related. Let ek=(i,j)e_k = (i,j) be an edge. Then the kk-th column of BB is all zeros except Bi,k=−1B_{i,k} = -1, and Bj,k=+1B_{j,k} = +1 (for undirected graphs, it doesn’t matter which of Bi,kB_{i,k} and Bj,kB_{j,k} is +1 and which is -1 as long as they have opposite signs). Note that BTB^T acts as a sort of difference operator on functions of vertices, meaning BTfB^T f is a vector of length mm which encodes the difference in fuction value over each edge.

You can check that BT1C=0B^T 1_C = 0, where 1C1_C is a connected component indicator (1C[i]=11_C[i] = 1 if i∈Ci \in C, and 1C[i]=01_C[i] = 0 otherwise). C⊆VC\subseteq V is a connected component of the graph if all vertices in CC have a path between them, and there are no vertices in VV that are connected to CC which are not in CC. This implies BT1=0B^T 1 = 0.

The graph laplacian LL is an n×nn \times n matrix L=D−A=BBTL = D- A = B B^T. If the graph lies on a regular grid, then L=−ΔL = -\Delta up to scaling by a finite difference width h2h^2, but the graph laplacian is defined for all graphs.

Note that the nullspace of LL is the same as the nullspace of BTB^T (the span of indicators on connected components).

In most cases, it makes sense to store all these matrices in sparse format.

<Figure size 432x288 with 1 Axes>
<100x100 sparse matrix of type '<class 'numpy.int64'>' with 1006 stored elements in Compressed Sparse Row format>
<100x503 sparse matrix of type '<class 'numpy.float64'>' with 1006 stored elements in Compressed Sparse Column format>
<100x100 sparse matrix of type '<class 'numpy.int64'>' with 1106 stored elements in Compressed Sparse Row format>

Exercise

For an undirected graph G(V,E)G(V, E), let n=∣V∣n = |V| and m=∣E∣m = |E|. Give an expression for the number of non-zeros in each of AA, BB, and LL in terms of nn and mm.


AA and BB both have 2m2m non-zeros. LL has n+2mn + 2m non-zeros.

Random Walks on Graphs

In a random walk on a graph, we consider an agent who starts at a vertex ii, and then will chose a random neighbor of ii and “walk” along the connecting edge. Typically, we will consider taking a walk where a neighbor is chosen uniformly at random (i.e. with probability 1/di1/d_i). We’ll assume that every vertex of the graph has at least one neighbor so D−1D^{-1} makes sense.

This defines a Markov Chain with transition matrix P=AD−1P = A D^{-1} (columns are scaled to 1). Note that even if AA is symmetric (for undirected graphs) that PP need not be symmetric because of the scaling by D−1D^{-1}.

The stationary distribution xx of the random walk is the top eigenvector of PP, is guaranteed to have eigenvalue 1, and is guaranteed to have non-negative entries. If we scale xx so ∥x∥1=1\|x\|_1 = 1, The entry xix_i can be interpreted as the probability that a random walker which has walked for a very large number of steps is at vertex ii.

Page Rank

PageRank is an early algorithm that was used to rank websites for search engines. The internet can be viewed as a directed graph of websites where there is a directed edge (i,j)(i, j) if webpage jj links to webpage ii. In this case, we compute the degree vector dd using the out-degree (counting the number of links out of a webpage). Then the transition matrix P=AD−1P = A D^{-1} on the directed adjacency matrix defines a random walk on webpages where a user randomly clicks links to get from webpage to webpage. The idea is that more authoritative websites will have more links to them, so a random web surfer will be more likely to end up with them.

One of the issues with this model is that it is easy for a random walker to get “stuck” at a webpage with no out-going links. The idea of PageRank is to add a probability α\alpha that a web surfer will randomly go to another webpage which is not linked to by their current page. In this case, we can write the transition matrix

P=(1−α)AD−1+αn11TP = (1-\alpha) A D^{-1} + \frac{\alpha}{n} 11^T

We then calculate the stationary vector xx of this matrix. Websites with a larger entry in xix_i are deemed more authoritative.

Note that because AA is sparse, you’ll typically want to encode 1n11T\frac{1}{n}11^T as a linear operator (this takes the average of a vector, and broadcasts it to the appropriate shape). For internet-sized graphs this is a necessity.

Let’s look at the Les Miserables graph, which encodes interactions between characters in the novel Les Miserables by Victor Hugo.

<Figure size 432x288 with 1 Axes>
NodeDataView({'Napoleon': {}, 'Myriel': {}, 'MlleBaptistine': {}, 'MmeMagloire': {}, 'CountessDeLo': {}, 'Geborand': {}, 'Champtercier': {}, 'Cravatte': {}, 'Count': {}, 'OldMan': {}, 'Valjean': {}, 'Labarre': {}, 'Marguerite': {}, 'MmeDeR': {}, 'Isabeau': {}, 'Gervais': {}, 'Listolier': {}, 'Tholomyes': {}, 'Fameuil': {}, 'Blacheville': {}, 'Favourite': {}, 'Dahlia': {}, 'Zephine': {}, 'Fantine': {}, 'MmeThenardier': {}, 'Thenardier': {}, 'Cosette': {}, 'Javert': {}, 'Fauchelevent': {}, 'Bamatabois': {}, 'Perpetue': {}, 'Simplice': {}, 'Scaufflaire': {}, 'Woman1': {}, 'Judge': {}, 'Champmathieu': {}, 'Brevet': {}, 'Chenildieu': {}, 'Cochepaille': {}, 'Pontmercy': {}, 'Boulatruelle': {}, 'Eponine': {}, 'Anzelma': {}, 'Woman2': {}, 'MotherInnocent': {}, 'Gribier': {}, 'MmeBurgon': {}, 'Jondrette': {}, 'Gavroche': {}, 'Gillenormand': {}, 'Magnon': {}, 'MlleGillenormand': {}, 'MmePontmercy': {}, 'MlleVaubois': {}, 'LtGillenormand': {}, 'Marius': {}, 'BaronessT': {}, 'Mabeuf': {}, 'Enjolras': {}, 'Combeferre': {}, 'Prouvaire': {}, 'Feuilly': {}, 'Courfeyrac': {}, 'Bahorel': {}, 'Bossuet': {}, 'Joly': {}, 'Grantaire': {}, 'MotherPlutarch': {}, 'Gueulemer': {}, 'Babet': {}, 'Claquesous': {}, 'Montparnasse': {}, 'Toussaint': {}, 'Child1': {}, 'Child2': {}, 'Brujon': {}, 'MmeHucheloup': {}})
<77x77 sparse matrix of type '<class 'numpy.int64'>' with 508 stored elements in Compressed Sparse Row format>
<77x77 sparse matrix of type '<class 'numpy.float64'>' with 77 stored elements (1 diagonals) in DIAgonal format>

Let’s now construct the PageRank matrix and compute the top eigenpairs

array([1. +0.j, 0.83936036+0.j, 0.79746166+0.j, 0.74936377+0.j, 0.70144813+0.j])
True
array([0.0133386 , 0.20399851, 0.0949893 , 0.10668613, 0.0133386 , 0.0133386 , 0.0133386 , 0.0133386 , 0.01926114, 0.0133386 , 0.57767244, 0.0107066 , 0.01679025, 0.0107066 , 0.0107066 , 0.0107066 , 0.07701289, 0.08277018, 0.07701289, 0.07992839, 0.08300515, 0.08007966, 0.07716337, 0.15885074, 0.11720935, 0.20909641, 0.22214954, 0.1583424 , 0.06719718, 0.04831427, 0.01895798, 0.0377782 , 0.0107066 , 0.01702923, 0.06181196, 0.06181196, 0.05030959, 0.05030959, 0.05030959, 0.02101082, 0.01050109, 0.06584516, 0.02292668, 0.02325998, 0.02366606, 0.0160557 , 0.02666986, 0.01541702, 0.1673304 , 0.104165 , 0.01375137, 0.09145499, 0.01729798, 0.01099474, 0.02347196, 0.31496208, 0.0133744 , 0.05356893, 0.23233548, 0.16932592, 0.05145195, 0.09653403, 0.21000629, 0.10143451, 0.16574335, 0.1099175 , 0.04528474, 0.01645582, 0.08526568, 0.09150015, 0.06900207, 0.04435378, 0.01961912, 0.02781421, 0.02781421, 0.0472697 , 0.02410199])
'Valjean'
array(['Valjean', 'Marius', 'Enjolras', 'Cosette'], dtype='<U16')

The Graph Laplacian

Spectral Embeddings

Spectral embeddings are one way of obtaining locations of vertices of a graph for visualization. One way is to pretend that all edges are Hooke’s law springs, and to minimize the potential energy of a configuration of vertex locations subject to the constraint that we can’t have all points in the same location.

In one dimension:

minimizex∑(i,j)∈E(xi−xj)2subject to xT1=0,∥x∥2=1\mathop{\mathsf{minimize}}_x \sum_{(i,j) \in E} (x_i - x_j)^2\\ \text{subject to } x^T 1 = 0, \|x\|_2 = 1

Note that the objective function is a quadratic form on the embedding vector xx:

∑(i,j)∈E(xi−xj)2=xTBBTx=xTLx\sum_{(i,j)\in E} (x_i - x_j)^2 = x^T B B^T x = x^T L x

Because the vector 1 is in the nullspace of LL, this is equivalent to finding the eigenvector with second-smallest eigenvalue.

For a higher-dimensional embedding, we can use the eigenvectors for the next-largest eigenvalues.

Attention: the first formula is not shown in the current notebook!

<Figure size 432x288 with 1 Axes>
<Figure size 432x288 with 1 Axes>

Spectral Clustering

Spectral clustering refers to using a spectral embedding to cluster nodes in a graph. Let A,B⊂VA, B \subset V with A∩B=∅A \cap B = \emptyset. We will denote

E(A,B)={(i,j)∈E∣i∈A,j∈B}E(A, B) = \{(i,j) \in E \mid i\in A, j\in B\}

One way to try to find clusters is to attempt to find a set of nodes S⊂VS \subset V with Sˉ=V∖S\bar{S} = V \setminus S, so that we minimize the cut objective

C(S)=∣E(S,Sˉ)∣min⁡{∣S∣,∣Sˉ∣}C(S) = \frac{|E(S, \bar{S})|}{\min \{|S|, |\bar{S}|\}}

The Cheeger inequality bounds the second-smallest eigenvalue of LL in terms of the optimal value of C(S)C(S). In fact, the way to construct a partition of the graph which is close to the optimal clustering minimizing C(S)C(S) is to look at the eigenvector xx associated with the second smallest eigenvalue, and let S={i∈V∣xi<0}S = \{i \in V \mid x_i < 0\}.

As an example, let’s look at a graph generated by a stochastic block model with two clusters. The “ground-truth” clusters are the ground-truth communities in the model.

<Figure size 432x288 with 1 Axes>

Now, let’s use spectral clustering to partition into two clusters

<Figure size 432x288 with 1 Axes>

We’ll use the adjusted rand index to measure the quality of the clustering we obtained. A value of 1 means that we found the true clusters.

-0.014331210191082872

In general, you should use a dimension dd embedding when looking for d+1d+1 clusters (so we used a dimension 1 embedding for 2 clusters). Let’s look at 4 clusters in a SBM

<Figure size 432x288 with 1 Axes>

we’ll use K-means clustering in scikit learn to assign clusters.

<Figure size 432x288 with 1 Axes>
0.97305763367046

Exercise

We’ll consider the stochastic block model with k=5k=5 clusters and n=25n=25 nodes per cluster.

Let pp be denote the probability of an edge between nodes in the same cluster, and qq denote the probability of an edge between nodes in different clusters.

Plot a phase diagram of the adjusted rand index (ARI) score as pp and qq both vary in the range [0,1][0,1]