├── .gitignore ├── karate.txt ├── README.md ├── glee.py └── GLEE example usage.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *out.npy 3 | .ipynb_checkpoints/ 4 | -------------------------------------------------------------------------------- /karate.txt: -------------------------------------------------------------------------------- 1 | 0 1 {} 2 | 0 2 {} 3 | 0 3 {} 4 | 0 4 {} 5 | 0 5 {} 6 | 0 6 {} 7 | 0 7 {} 8 | 0 8 {} 9 | 0 10 {} 10 | 0 11 {} 11 | 0 12 {} 12 | 0 13 {} 13 | 0 17 {} 14 | 0 19 {} 15 | 0 21 {} 16 | 0 31 {} 17 | 1 17 {} 18 | 1 2 {} 19 | 1 3 {} 20 | 1 21 {} 21 | 1 19 {} 22 | 1 7 {} 23 | 1 13 {} 24 | 1 30 {} 25 | 2 3 {} 26 | 2 32 {} 27 | 2 7 {} 28 | 2 8 {} 29 | 2 9 {} 30 | 2 27 {} 31 | 2 28 {} 32 | 2 13 {} 33 | 3 7 {} 34 | 3 12 {} 35 | 3 13 {} 36 | 4 10 {} 37 | 4 6 {} 38 | 5 16 {} 39 | 5 10 {} 40 | 5 6 {} 41 | 6 16 {} 42 | 8 32 {} 43 | 8 30 {} 44 | 8 33 {} 45 | 9 33 {} 46 | 13 33 {} 47 | 14 32 {} 48 | 14 33 {} 49 | 15 32 {} 50 | 15 33 {} 51 | 18 32 {} 52 | 18 33 {} 53 | 19 33 {} 54 | 20 32 {} 55 | 20 33 {} 56 | 22 32 {} 57 | 22 33 {} 58 | 23 32 {} 59 | 23 25 {} 60 | 23 27 {} 61 | 23 29 {} 62 | 23 33 {} 63 | 24 25 {} 64 | 24 27 {} 65 | 24 31 {} 66 | 25 31 {} 67 | 26 33 {} 68 | 26 29 {} 69 | 27 33 {} 70 | 28 33 {} 71 | 28 31 {} 72 | 29 32 {} 73 | 29 33 {} 74 | 30 33 {} 75 | 30 32 {} 76 | 31 32 {} 77 | 31 33 {} 78 | 32 33 {} 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GLEE: Geometric Laplacian Eigenmap Embedding 2 | 3 | GLEE is a graph embedding technique based on the spectral and geometric 4 | properties of the Laplacian matrix of an undirected graph. For more details 5 | on the theoretical framework behind GLEE, see 6 | 7 | > *Leo Torres*, K. S. Chan, and T. Eliassi-Rad. GLEE: Geometric Laplacian 8 | > Eigenmap Embedding. Journal of Complex Networks, Volume 8, Issue 2, April 9 | > 2020, cnaa007. 10 | 11 | If you use the code in this repository, please cite the above article. 12 | 13 | 14 | ## Installation 15 | 16 | Clone this repo to access the GLEE functionality. Requirements are numpy, 17 | scipy and networkX. 18 | 19 | 20 | ## Usage 21 | 22 | This library can be used in two different ways. It can be directly imported 23 | for use within your Python application using `import glee`. For an example 24 | of this kind of usage see `GLEE example usage.ipynb`. 25 | 26 | It can also be used from the command line by executing `python glee.py 27 | `. Running `python glee.py -h` will show an explanation of the 28 | available arguments. 29 | 30 | ``` 31 | usage: glee.py [-h] --input INPUT --output OUTPUT [--dim DIM] 32 | [--method METHOD] 33 | 34 | optional arguments: 35 | -h, --help show this help message and exit 36 | --input INPUT edge list file path 37 | --output OUTPUT embedding output file path 38 | --dim DIM dimension of embedding (default 128) 39 | --method METHOD "glee" or "eigen" 40 | ``` 41 | 42 | A usual way to use GLEE in this way is, for example, 43 | 44 | ``` 45 | python glee.py --input karate.txt --output karate.out --dim 8 46 | ``` 47 | 48 | One can also compute the original version of Laplacian Eigenmaps by doing 49 | 50 | ``` 51 | python glee.py --input karate.txt --output karate.out --dim 8 --method eigen 52 | ``` 53 | -------------------------------------------------------------------------------- /glee.py: -------------------------------------------------------------------------------- 1 | """ 2 | glee.py 3 | ------- 4 | 5 | Geometric Laplacian Eigenmap Embedding. 6 | 7 | """ 8 | 9 | import argparse 10 | import numpy as np 11 | from scipy import sparse 12 | import networkx as nx 13 | 14 | 15 | def laplacian_degrees(graph): 16 | """Return the Laplacian matrix and the diagonal degree matrix.""" 17 | adj = nx.to_scipy_sparse_matrix(graph, format='csr') 18 | degs = sparse.diags(adj.sum(axis=1).flatten(), [0], adj.shape, format='csr') 19 | return degs - adj, degs 20 | 21 | 22 | def eigenmaps(graph, dim, method='glee', return_vals=False): 23 | """Return the Eigenmap embedding of the graph. 24 | 25 | Params 26 | ------ 27 | 28 | graph (nx.Graph): the graph to embed. 29 | 30 | method (str): 'glee' for GLEE (default), or 'eigen' for original 31 | Laplacian Eigenmaps. 32 | 33 | dim (int): number of embedding dimensions. 34 | 35 | return_vals (bool): whether to return the eigenvalues. Default False. 36 | 37 | """ 38 | if dim > graph.order() - 1 or dim < 2: 39 | raise ValueError('dim must be grater than 0 and less than graph.order()') 40 | 41 | lapl, degs = laplacian_degrees(graph) 42 | shape = (graph.order(), graph.order()) 43 | invdegs = sparse.diags(1 / degs.diagonal(), 0, shape, format='csr') 44 | 45 | eig_fun = { 46 | 'glee': { 47 | True: np.linalg.eigh, 48 | False: lambda m: sparse.linalg.eigsh( 49 | m, k=dim, which='LM', return_eigenvectors=True)}, 50 | 'eigen': { 51 | True: np.linalg.eig, 52 | # In the following we need to compute dim+1 eigenvectors so 53 | # that after deleting the zero eigenvalue we get exactly dim. 54 | False: lambda m: sparse.linalg.eigs( 55 | m, k=dim+1, which='SM', return_eigenvectors=True)} 56 | } 57 | matrix_fun = { 58 | 'glee': {True: lambda: lapl.A, False: lambda: lapl}, 59 | 'eigen': {True: lambda: invdegs.A.dot(lapl.A), 60 | False: lambda: invdegs.dot(lapl)} 61 | } 62 | post_process = { 63 | 'glee': lambda vals, vecs: \ 64 | (vals, vecs.dot(np.diag(np.sqrt(vals)))), 65 | 'eigen': lambda vals, vecs: \ 66 | (vals[1:], vecs[:, 1:].dot( 67 | np.diag([1/np.sqrt(v.dot(degs.A).dot(v)) 68 | for v in vecs[:, 1:].T]))) 69 | } 70 | 71 | is_full = dim is None or dim > graph.order() - 3 72 | eig = eig_fun[method][is_full] 73 | matrix = matrix_fun[method][is_full]() 74 | vals, vecs = eig(matrix) 75 | # All eigenvalues are guaranteed to be non-negative, but sometimes the 76 | # zero eigenvalues can come very close to zero but negative, so we take 77 | # the absolute value as we need to take sqrt. 78 | vals, vecs = np.abs(vals.real), vecs.real 79 | vals, vecs = post_process[method](vals, vecs) 80 | 81 | indices = np.argsort(vals) 82 | indices = indices[::-1] if method == 'glee' else indices 83 | vals = vals[indices][:dim] 84 | vecs = (vecs.T[indices].T)[:, :dim] 85 | return (vecs, vals) if return_vals else vecs 86 | 87 | 88 | def main(infile, outfile, dim, method, nodetype): 89 | """Load an edge list and compute its GLEE to disk.""" 90 | try: 91 | nodetype = {'int': int, 'str': str}[nodetype] 92 | except KeyError as exc: 93 | raise KeyError('Supported node label types are int or str.') 94 | graph = nx.read_edgelist(infile, nodetype=nodetype) 95 | emb = eigenmaps(graph, dim, method) 96 | np.save(outfile, emb) 97 | 98 | 99 | 100 | if __name__ == '__main__': 101 | parser = argparse.ArgumentParser() 102 | parser.add_argument('--input', type=str, required=True, 103 | help='edge list file path') 104 | parser.add_argument('--output', type=str, required=True, 105 | help='embedding output file path') 106 | parser.add_argument('--dim', default=128, type=int, 107 | help='dimension of embedding (default 128)') 108 | parser.add_argument('--method', default='glee', type=str, 109 | help='"glee" or "eigen"') 110 | parser.add_argument('--nodetype', default='int', type=str, 111 | help='type of node labels in input file') 112 | args = parser.parse_args() 113 | main(args.input, args.output, int(args.dim), args.method, 114 | args.nodetype) 115 | -------------------------------------------------------------------------------- /GLEE example usage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# GLEE: Geometric Laplacian Eigenmap Embedding\n", 8 | "\n", 9 | "GLEE can be used in two different ways. This notebook contains an example use case when `glee` is imported into your Python session. For an example use case from the command line, see the README.md file." 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Example use case" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "metadata": {}, 22 | "source": [ 23 | "First, we load a small graph with $34$ nodes and compute its 8-dimensional embedding." 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 1, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "import networkx as nx\n", 33 | "graph = nx.karate_club_graph()\n", 34 | "\n", 35 | "from glee import eigenmaps\n", 36 | "emb = eigenmaps(graph, 8, method='glee')" 37 | ] 38 | }, 39 | { 40 | "cell_type": "markdown", 41 | "metadata": {}, 42 | "source": [ 43 | "The result contains the embedding of each node as rows. The rows are ordered in the same order as `graph.nodes()`." 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 2, 49 | "metadata": {}, 50 | "outputs": [ 51 | { 52 | "data": { 53 | "text/plain": [ 54 | "(34, 8)" 55 | ] 56 | }, 57 | "execution_count": 2, 58 | "metadata": {}, 59 | "output_type": "execute_result" 60 | } 61 | ], 62 | "source": [ 63 | "emb.shape" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "metadata": {}, 69 | "source": [ 70 | "Using the geometric interpretation of these embeddings, we can approximate the number of common neighbors between a pair of nodes." 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 3, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "import numpy as np\n", 80 | "\n", 81 | "def common(graph, emb):\n", 82 | " pairs = [(i, j)\n", 83 | " for i in range(graph.order())\n", 84 | " for j in range(i + 1, graph.order())\n", 85 | " if (i, j) not in graph.edges()]\n", 86 | "\n", 87 | " common_neighbors = [len(set(graph.neighbors(i)) & set(graph.neighbors(j)))\n", 88 | " for (i, j) in pairs]\n", 89 | "\n", 90 | " approx_common_neighbors = [- graph.degree(i) * \\\n", 91 | " np.sum([emb[i].dot(emb[n]) for n in graph.neighbors(j)]) / graph.degree(i)\n", 92 | " for (i, j) in pairs]\n", 93 | " return common_neighbors, approx_common_neighbors" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 4, 99 | "metadata": {}, 100 | "outputs": [ 101 | { 102 | "data": { 103 | "text/plain": [ 104 | "
" 105 | ] 106 | }, 107 | "metadata": {}, 108 | "output_type": "display_data" 109 | } 110 | ], 111 | "source": [ 112 | "import matplotlib.pylab as plt\n", 113 | "\n", 114 | "def make_plot(graph, emb):\n", 115 | " plt.plot([0, 7], [0, 7], 'k--')\n", 116 | " plt.scatter(*common(graph, emb))\n", 117 | " plt.title('dim = 8')\n", 118 | " plt.xlabel('True number of common neighbors')\n", 119 | " plt.ylabel('Geometric approximation')\n", 120 | " plt.show()\n", 121 | " \n", 122 | "make_plot(graph, emb)" 123 | ] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "metadata": {}, 128 | "source": [ 129 | "Moreover, this approximation improves as the embedding dimension improves." 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 5, 135 | "metadata": {}, 136 | "outputs": [ 137 | { 138 | "data": { 139 | "image/png": "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\n", 140 | "text/plain": [ 141 | "
" 142 | ] 143 | }, 144 | "metadata": { 145 | "needs_background": "light" 146 | }, 147 | "output_type": "display_data" 148 | } 149 | ], 150 | "source": [ 151 | "make_plot(graph, eigenmaps(graph, 32, method='glee'))" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": {}, 157 | "source": [ 158 | "## Visualization" 159 | ] 160 | }, 161 | { 162 | "cell_type": "code", 163 | "execution_count": 6, 164 | "metadata": {}, 165 | "outputs": [], 166 | "source": [ 167 | "HL_COLOR = '#93c47dff'\n", 168 | "DEF_COLOR = '#999999'\n", 169 | "\n", 170 | "def plot_arrows(points, ax, highlight, hl_color=HL_COLOR, lines=None):\n", 171 | " if lines is None:\n", 172 | " lines = []\n", 173 | " for idx, row in enumerate(points):\n", 174 | " color = hl_color if idx in highlight else DEF_COLOR\n", 175 | " if idx not in lines:\n", 176 | " ax.annotate('', xy=row, xytext=(0, 0), annotation_clip=True,\n", 177 | " arrowprops={'headwidth': 3, 'headlength': 4,\n", 178 | " 'width': 1, 'color': color})\n", 179 | " else:\n", 180 | " ax.plot([0, row[0]], [0, row[1]], color=color)\n", 181 | "\n", 182 | "\n", 183 | "def plot_glee(points, highlight, max_val, lines=None):\n", 184 | " \"\"\"PLot 2D-GLEE of a graph.\"\"\"\n", 185 | " ax = plt.gca()\n", 186 | " plot_arrows(points, ax, highlight, lines=lines)\n", 187 | " ax.set_aspect('equal')\n", 188 | " ax.set_ylim(-max_val, max_val)\n", 189 | " ax.set_xlim(-max_val, max_val)\n", 190 | " ax.xaxis.set_tick_params(labelsize=10)\n", 191 | " ax.yaxis.set_tick_params(labelsize=10)\n", 192 | " ax.set_xticks([-1, 0, 1])\n", 193 | " ax.set_yticks([-1, 0, 1])\n", 194 | "\n", 195 | "def plot_perpendicular(ax, row, neighbors):\n", 196 | " perp = np.array([-row[1], row[0]])\n", 197 | " points = np.array([-20*perp, 20*perp])\n", 198 | " ax.plot(points.T[0], points.T[1], ':')\n", 199 | " # just draw over the previous\n", 200 | " highlight = list(range(len(neighbors)))\n", 201 | " plot_arrows(neighbors, ax, highlight, hl_color='#8e7cc3ff')\n", 202 | "\n", 203 | "\n", 204 | "def plot_karate(hl_node):\n", 205 | " plt.figure()\n", 206 | " graph = nx.karate_club_graph()\n", 207 | " simpl = eigenmaps(graph, method='glee', dim=2)\n", 208 | " # The eigenvectors can rotate, and for this picture we want a\n", 209 | " # particular orientation/rotation of them.\n", 210 | " while not np.allclose(simpl[0], [-0.82511663, -3.885854]):\n", 211 | " simpl = eigenmaps(graph, method='glee', dim=2)\n", 212 | "\n", 213 | " plot_glee(simpl, [hl_node], 1, [0, 32, 33])\n", 214 | " plot_perpendicular(plt.gca(), simpl[hl_node], [simpl[n] for n in graph.neighbors(hl_node)])\n", 215 | " plt.gca().set_xticks([-0.2, 0, 0.2, 0.4, 0.6])\n", 216 | " plt.gca().set_yticks([-0.2, 0, 0.2, 0.4, 0.6])\n", 217 | " plt.xlim(-0.3, 0.6)\n", 218 | " plt.ylim(-0.3, 0.6)\n" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": 7, 224 | "metadata": {}, 225 | "outputs": [ 226 | { 227 | "data": { 228 | "image/png": "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\n", 229 | "text/plain": [ 230 | "
" 231 | ] 232 | }, 233 | "metadata": { 234 | "needs_background": "light" 235 | }, 236 | "output_type": "display_data" 237 | } 238 | ], 239 | "source": [ 240 | "plot_karate(0)\n", 241 | "plt.show()" 242 | ] 243 | }, 244 | { 245 | "cell_type": "markdown", 246 | "metadata": {}, 247 | "source": [ 248 | "In the figure, the dotted blue line is orthogonal to the embedding of node $0$ (green). Neighbors of the green noe are in purple. Non-neighbors in gray." 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [] 257 | } 258 | ], 259 | "metadata": { 260 | "kernelspec": { 261 | "display_name": "Python 3", 262 | "language": "python", 263 | "name": "python3" 264 | }, 265 | "language_info": { 266 | "codemirror_mode": { 267 | "name": "ipython", 268 | "version": 3 269 | }, 270 | "file_extension": ".py", 271 | "mimetype": "text/x-python", 272 | "name": "python", 273 | "nbconvert_exporter": "python", 274 | "pygments_lexer": "ipython3", 275 | "version": "3.6.9" 276 | }, 277 | "latex_envs": { 278 | "LaTeX_envs_menu_present": true, 279 | "autoclose": false, 280 | "autocomplete": false, 281 | "bibliofile": "biblio.bib", 282 | "cite_by": "apalike", 283 | "current_citInitial": 1, 284 | "eqLabelWithNumbers": true, 285 | "eqNumInitial": 1, 286 | "hotkeys": { 287 | "equation": "Ctrl-E", 288 | "itemize": "Ctrl-I" 289 | }, 290 | "labels_anchors": false, 291 | "latex_user_defs": false, 292 | "report_style_numbering": true, 293 | "user_envs_cfg": false 294 | } 295 | }, 296 | "nbformat": 4, 297 | "nbformat_minor": 1 298 | } 299 | --------------------------------------------------------------------------------