{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "provenance": [], "private_outputs": true, "toc_visible": true }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" }, "gpuClass": "standard", "accelerator": "GPU" }, "cells": [ { "cell_type": "markdown", "source": [ "# Quickstart\n", "\n", "The tutorial provides a quick walkthrough of the classes and operators provided by the `dgl.sparse` package.\n", "\n", "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dmlc/dgl/blob/master/notebooks/sparse/quickstart.ipynb) [![GitHub](https://img.shields.io/badge/-View%20on%20GitHub-181717?logo=github&logoColor=ffffff)](https://github.com/dmlc/dgl/blob/master/notebooks/sparse/quickstart.ipynb)" ], "metadata": { "id": "E0DAKDMuWz7I" } }, { "cell_type": "code", "source": [ "# Install the required packages.\n", "\n", "import os\n", "import torch\n", "os.environ['TORCH'] = torch.__version__\n", "os.environ['DGLBACKEND'] = \"pytorch\"\n", "\n", "# Uncomment below to install required packages. If the CUDA version is not 11.8,\n", "# check the https://www.dgl.ai/pages/start.html to find the supported CUDA\n", "# version and corresponding command to install DGL.\n", "#!pip install dgl -f https://data.dgl.ai/wheels/cu118/repo.html > /dev/null\n", "\n", "try:\n", " import dgl.sparse as dglsp\n", " installed = True\n", "except ImportError:\n", " installed = False\n", "print(\"DGL installed!\" if installed else \"DGL not found!\")" ], "metadata": { "id": "19UZd7wyWzpT" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Sparse Matrix\n", "\n", "The core abstraction of DGL's sparse package is the `SparseMatrix` class. Compared with other sparse matrix libraries (such as `scipy.sparse` and `torch.sparse`), DGL's `SparseMatrix` is specialized for the deep learning workloads on structure data (e.g., Graph Neural Networks), with the following features:\n", "\n", "* **Auto sparse format.** Don't bother choosing between different sparse formats. There is only one `SparseMatrix` and it will select the best format for the operation to be performed.\n", "* **Non-zero elements can be scalar or vector.** Easy for modeling relations (e.g., edges) by vector representation.\n", "* **Fully PyTorch compatible.** The package is built upon PyTorch and is natively compatible with other tools in the PyTorch ecosystem.\n" ], "metadata": { "id": "GsWoAGC4RpHw" } }, { "cell_type": "markdown", "source": [ "### Creating a DGL Sparse Matrix\n", "\n", "The simplest way to create a sparse matrix is using the `spmatrix` API by providing the indices of the non-zero elements. The indices are stored in a tensor of shape `(2, nnz)`, where the `i`-th non-zero element is stored at position `(indices[0][i], indices[1][i])`. The code below creates a 3x3 sparse matrix.\n" ], "metadata": { "id": "_q4HYodcWenB" } }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "h-ryVEs1PuIP" }, "outputs": [], "source": [ "import torch\n", "import dgl.sparse as dglsp\n", "\n", "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "A = dglsp.spmatrix(i) # 1.0 is default value for nnz elements.\n", "\n", "print(A)\n", "print(\"\")\n", "print(\"In dense format:\")\n", "print(A.to_dense())" ] }, { "cell_type": "markdown", "source": [ "If not specified, the shape is inferred automatically from the indices but you can specify it explicitly too." ], "metadata": { "id": "W1JJg-eZ7K3t" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 0, 1],\n", " [0, 2, 0]])\n", "\n", "A1 = dglsp.spmatrix(i)\n", "print(f\"Implicit Shape: {A1.shape}\")\n", "print(A1.to_dense())\n", "print(\"\")\n", "\n", "A2 = dglsp.spmatrix(i, shape=(3, 3))\n", "print(f\"Explicit Shape: {A2.shape}\")\n", "print(A2.to_dense())" ], "metadata": { "id": "80NNSQfd7L5V" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Both scalar values and vector values can be set for nnz elements in Sparse Matrix." ], "metadata": { "id": "zdNgUf0ShfCe" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "# The length of the value should match the nnz elements represented by the\n", "# sparse matrix format.\n", "scalar_val = torch.tensor([1., 2., 3.])\n", "vector_val = torch.tensor([[1., 1.], [2., 2.], [3., 3.]])\n", "\n", "print(\"-----Scalar Values-----\")\n", "A = dglsp.spmatrix(i, scalar_val)\n", "print(A)\n", "print(\"\")\n", "print(\"In dense format:\")\n", "print(A.to_dense())\n", "print(\"\")\n", "\n", "print(\"-----Vector Values-----\")\n", "A = dglsp.spmatrix(i, vector_val)\n", "print(A)\n", "print(\"\")\n", "print(\"In dense format:\")\n", "print(A.to_dense())" ], "metadata": { "id": "buE9ZkKvhp1f" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "*Duplicated indices*" ], "metadata": { "id": "7ufTCDAVsrmP" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 0, 0, 1],\n", " [0, 2, 2, 0]])\n", "val = torch.tensor([1., 2., 3., 4])\n", "A = dglsp.spmatrix(i, val)\n", "print(A)\n", "print(f\"Whether A contains duplicate indices: {A.has_duplicate()}\")\n", "print(\"\")\n", "\n", "B = A.coalesce()\n", "print(B)\n", "print(f\"Whether B contains duplicate indices: {B.has_duplicate()}\")" ], "metadata": { "id": "ilSAlFLOs0o8" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**val_like**\n", "\n", "You can create a new sparse matrix by retaining the non-zero indices of a given sparse matrix but with different non-zero values." ], "metadata": { "id": "ZJ09qM5NaxuI" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A = dglsp.spmatrix(i, val)\n", "\n", "new_val = torch.tensor([4., 5., 6.])\n", "B = dglsp.val_like(A, new_val)\n", "print(B)" ], "metadata": { "id": "UB3lKJVBbsUD" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**Create a sparse matrix from various sparse formats**\n", "\n", "* `from_coo()`: Create a sparse matrix from [COO](https://en.wikipedia.org/wiki/Sparse_matrix#Coordinate_list_(COO)) format.\n", "* `from_csr()`: Create a sparse matrix from [CSR](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format)) format.\n", "* `from_csc()`: Create a sparse matrix from [CSC](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_column_(CSC_or_CCS)) format." ], "metadata": { "id": "nWjBSFDBXDPJ" } }, { "cell_type": "code", "source": [ "row = torch.tensor([0, 1, 2, 2, 2])\n", "col = torch.tensor([1, 2, 0, 1, 2])\n", "\n", "print(\"-----Create from COO format-----\")\n", "A = dglsp.from_coo(row, col)\n", "print(A)\n", "print(\"\")\n", "print(\"In dense format:\")\n", "print(A.to_dense())\n", "print(\"\")\n", "\n", "indptr = torch.tensor([0, 1, 2, 5])\n", "indices = torch.tensor([1, 2, 0, 1, 2])\n", "\n", "print(\"-----Create from CSR format-----\")\n", "A = dglsp.from_csr(indptr, indices)\n", "print(A)\n", "print(\"\")\n", "print(\"In dense format:\")\n", "print(A.to_dense())\n", "print(\"\")\n", "\n", "print(\"-----Create from CSC format-----\")\n", "B = dglsp.from_csc(indptr, indices)\n", "print(B)\n", "print(\"\")\n", "print(\"In dense format:\")\n", "print(B.to_dense())" ], "metadata": { "id": "3puXyMFsvdlj" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### Attributes and methods of a DGL Sparse Matrix" ], "metadata": { "id": "nd4hJ9ysd4St" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.tensor([1., 2., 3., 4.])\n", "A = dglsp.spmatrix(i, val)\n", "\n", "print(f\"Shape of sparse matrix: {A.shape}\")\n", "print(f\"The number of nonzero elements of sparse matrix: {A.nnz}\")\n", "print(f\"Datatype of sparse matrix: {A.dtype}\")\n", "print(f\"Device sparse matrix is stored on: {A.device}\")\n", "print(f\"Get the values of the nonzero elements: {A.val}\")\n", "print(f\"Get the row indices of the nonzero elements: {A.row}\")\n", "print(f\"Get the column indices of the nonzero elements: {A.col}\")\n", "print(f\"Get the coordinate (COO) representation: {A.coo()}\")\n", "print(f\"Get the compressed sparse row (CSR) representation: {A.csr()}\")\n", "print(f\"Get the compressed sparse column (CSC) representation: {A.csc()}\")" ], "metadata": { "id": "OKbFiWKIzZVe" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**dtype and/or device conversion**" ], "metadata": { "id": "VzosM7i3yQPK" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.tensor([1., 2., 3., 4.])\n", "A = dglsp.spmatrix(i, val)\n", "\n", "B = A.to(device='cpu', dtype=torch.int32)\n", "print(f\"Device sparse matrix is stored on: {B.device}\")\n", "print(f\"Datatype of sparse matrix: {B.dtype}\")" ], "metadata": { "id": "y_RJihw-ypXp" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Similar to pytorch, we also provide various fine-grained APIs ([Doc](https://docs.dgl.ai/en/latest/api/python/dgl.sparse_v0.html)) for dtype and/or device conversion." ], "metadata": { "id": "U26arLlJzfkN" } }, { "cell_type": "markdown", "source": [ "## Diagonal Matrix\n", "\n", "Diagonal Matrix is a special type of Sparse Matrix, in which the entries outside the main diagonal are all zero.\n", "\n", "\n" ], "metadata": { "id": "EFe9ABRuWHqf" } }, { "cell_type": "markdown", "source": [ "### Initializing a DGL Diagonal Matrix\n", "A DGL Diagonal Matrix can be initiate by `dglsp.diag()`.\n", "\n", "Identity Matrix is a special type of Diagonal Matrix, in which all the value on the diagonal are 1.0. Use `dglsp.identity()` to initiate a Diagonal Matrix." ], "metadata": { "id": "1CeCoE2Fgl_x" } }, { "cell_type": "code", "source": [ "val = torch.tensor([1., 2., 3., 4.])\n", "D = dglsp.diag(val)\n", "print(D)\n", "\n", "I = dglsp.identity(shape=(3, 3))\n", "print(I)" ], "metadata": { "id": "9wzJNApahXAR" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### Attributes and methods of a DGL Diagonal Matrix" ], "metadata": { "id": "s-JpSHGLhWlm" } }, { "cell_type": "code", "source": [ "val = torch.tensor([1., 2., 3., 4.])\n", "D = dglsp.diag(val)\n", "\n", "print(f\"Shape of sparse matrix: {D.shape}\")\n", "print(f\"The number of nonzero elements of sparse matrix: {D.nnz}\")\n", "print(f\"Datatype of sparse matrix: {D.dtype}\")\n", "print(f\"Device sparse matrix is stored on: {D.device}\")\n", "print(f\"Get the values of the nonzero elements: {D.val}\")" ], "metadata": { "id": "QMV0u-kQWsWd" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Operations on Sparse Matrix and Diagonal Matrix\n", "* Elementwise operations\n", " * `A + B`\n", " * `A - B`\n", " * `A * B`\n", " * `A / B`\n", " * `A ** scalar`\n", "* Reduce operations\n", " * `reduce()`\n", " * `sum()`\n", " * `smax()`\n", " * `smin()`\n", " * `smean()`\n", "* Matrix transformations\n", " * `SparseMatrix.transpose()` or `SparseMatrix.T`\n", " * `SparseMatrix.neg()`\n", " * `DiagMatrix.transpose()` or `DiagMatrix.T`\n", " * `DiagMatrix.neg()`\n", " * `DiagMatrix.inv()`\n", "* Matrix multiplication\n", " * `matmul()`\n", " * `sddmm()`\n", "\n", "\n", "*We are using dense format to print sparse matrix in this tutorial since it is more intuitive to read.*" ], "metadata": { "id": "Tjsapqp6zSFR" } }, { "cell_type": "markdown", "source": [ "### *Elementwise operations*" ], "metadata": { "id": "psvGwcIqYvC2" } }, { "cell_type": "markdown", "source": [ "**add(A, B), equivalent to A + B**\n", "\n", "The supported combinations are shown as follows.\n", "\n", "A \\\\ B | **DiagMatrix**|**SparseMatrix**|**scalar**\n", "----------------|---------------|----------------|----------\n", "**DiagMatrix** |Y |Y |N\n", "**SparseMatrix**|Y |Y |N\n", "**scalar** |N |N |N" ], "metadata": { "id": "39YJitpW-K9v" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A1 = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A1:\")\n", "print(A1.to_dense())\n", "\n", "i = torch.tensor([[0, 1, 2],\n", " [0, 2, 1]])\n", "val = torch.tensor([4., 5., 6.])\n", "A2 = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A2:\")\n", "print(A2.to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D1 = dglsp.diag(val)\n", "print(\"D1:\")\n", "print(D1.to_dense())\n", "\n", "val = torch.tensor([-4., -5., -6.])\n", "D2 = dglsp.diag(val)\n", "print(\"D2:\")\n", "print(D2.to_dense())\n", "\n", "print(\"A1 + A2:\")\n", "print((A1 + A2).to_dense())\n", "\n", "print(\"A1 + D1:\")\n", "print((A1 + D1).to_dense())\n", "\n", "print(\"D1 + D2:\")\n", "print((D1 + D2).to_dense())" ], "metadata": { "id": "pj3Ckx41-BSu" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**sub(A, B), equivalent to A - B**\n", "\n", "The supported combinations are shown as follows.\n", "\n", "A \\\\ B | **DiagMatrix**|**SparseMatrix**|**scalar**\n", "----------------|---------------|----------------|----------\n", "**DiagMatrix** |Y |Y |N\n", "**SparseMatrix**|Y |Y |N\n", "**scalar** |N |N |N" ], "metadata": { "id": "i25N0JHUTUX9" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A1 = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A1:\")\n", "print(A1.to_dense())\n", "\n", "i = torch.tensor([[0, 1, 2],\n", " [0, 2, 1]])\n", "val = torch.tensor([4., 5., 6.])\n", "A2 = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A2:\")\n", "print(A2.to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D1 = dglsp.diag(val)\n", "print(\"D1:\")\n", "print(D1.to_dense())\n", "\n", "val = torch.tensor([-4., -5., -6.])\n", "D2 = dglsp.diag(val)\n", "print(\"D2:\")\n", "print(D2.to_dense())\n", "\n", "print(\"A1 - A2:\")\n", "print((A1 - A2).to_dense())\n", "\n", "print(\"A1 - D1:\")\n", "print((A1 - D1).to_dense())\n", "\n", "print(\"D1 - A1:\")\n", "print((D1 - A1).to_dense())\n", "\n", "print(\"D1 - D2:\")\n", "print((D1 - D2).to_dense())" ], "metadata": { "id": "GMxfz-cyT129" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**mul(A, B), equivalent to A * B**\n", "\n", "The supported combinations are shown as follows.\n", "\n", "A \\\\ B | **DiagMatrix**|**SparseMatrix**|**scalar**\n", "----------------|---------------|----------------|----------\n", "**DiagMatrix** |Y |N |Y\n", "**SparseMatrix**|N |N |Y\n", "**scalar** |Y |Y |N" ], "metadata": { "id": "bg45jnq8T9EJ" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "print(\"A * 3:\")\n", "print((A * 3).to_dense())\n", "print(\"3 * A:\")\n", "print((3 * A).to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D1 = dglsp.diag(val)\n", "print(\"D1:\")\n", "print(D1.to_dense())\n", "\n", "val = torch.tensor([-4., -5., -6.])\n", "D2 = dglsp.diag(val)\n", "print(\"D2:\")\n", "print(D2.to_dense())\n", "\n", "print(\"D1 * -2:\")\n", "print((D1 * -2).to_dense())\n", "print(\"-2 * D1:\")\n", "print((-2 * D1).to_dense())\n", "\n", "print(\"D1 * D2:\")\n", "print((D1 * D2).to_dense())" ], "metadata": { "id": "4PAITJqHUB8J" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**div(A, B), equivalent to A / B**\n", "\n", "The supported combinations are shown as follows.\n", "\n", "A \\\\ B | **DiagMatrix**|**SparseMatrix**|**scalar**\n", "----------------|---------------|----------------|----------\n", "**DiagMatrix** |Y |N |Y\n", "**SparseMatrix**|N |N |Y\n", "**scalar** |N |N |N" ], "metadata": { "id": "Xb2RU6H4UBCs" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "print(\"A / 2:\")\n", "print((A / 2).to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D1 = dglsp.diag(val)\n", "print(\"D1:\")\n", "print(D1.to_dense())\n", "\n", "val = torch.tensor([-4., -5., -6.])\n", "D2 = dglsp.diag(val)\n", "print(\"D2:\")\n", "print(D2.to_dense())\n", "\n", "print(\"D1 / D2:\")\n", "print((D1 / D2).to_dense())\n", "\n", "print(\"D1 / 2:\")\n", "print((D1 / 2).to_dense())" ], "metadata": { "id": "TFB_UcmEUdr3" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**power(A, B), equivalent to A \\*\\* B**\n", "\n", "The supported combinations are shown as follows.\n", "\n", "A \\\\ B | **DiagMatrix**|**SparseMatrix**|**scalar**\n", "----------------|---------------|----------------|----------\n", "**DiagMatrix** |N |N |Y\n", "**SparseMatrix**|N |N |Y\n", "**scalar** |N |N |N" ], "metadata": { "id": "2lZbyTYUUgSi" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "print(\"A ** 3:\")\n", "print((A ** 3).to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D = dglsp.diag(val)\n", "print(\"D:\")\n", "print(D.to_dense())\n", "\n", "print(\"D1 ** 2:\")\n", "print((D1 ** 2).to_dense())" ], "metadata": { "id": "ox-XxCnuUqAy" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### *Reduce operations*\n", "\n", "All DGL sparse reduce operations only consider non-zero elements. To distinguish them from dense PyTorch reduce operations that consider zero elements, we use name `smax`, `smin` and `smean` (`s` stands for sparse)." ], "metadata": { "id": "TQJJlctZjYPv" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.tensor([1., 2., 3., 4.])\n", "A = dglsp.spmatrix(i, val)\n", "print(A.T.to_dense())\n", "print(\"\")\n", "\n", "# O1, O2 will have the same value.\n", "O1 = A.reduce(0, 'sum')\n", "O2 = A.sum(0)\n", "print(\"Reduce with reducer:sum along dim = 0:\")\n", "print(O1)\n", "print(\"\")\n", "\n", "# O3, O4 will have the same value.\n", "O3 = A.reduce(0, 'smax')\n", "O4 = A.smax(0)\n", "print(\"Reduce with reducer:max along dim = 0:\")\n", "print(O3)\n", "print(\"\")\n", "\n", "# O5, O6 will have the same value.\n", "O5 = A.reduce(0, 'smin')\n", "O6 = A.smin(0)\n", "print(\"Reduce with reducer:min along dim = 0:\")\n", "print(O5)\n", "print(\"\")\n", "\n", "# O7, O8 will have the same value.\n", "O7 = A.reduce(0, 'smean')\n", "O8 = A.smean(0)\n", "print(\"Reduce with reducer:smean along dim = 0:\")\n", "print(O7)\n", "print(\"\")" ], "metadata": { "id": "GhS49Js1jW4b" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### *Matrix transformations*" ], "metadata": { "id": "kanwnB7LOQui" } }, { "cell_type": "markdown", "source": [ "*Sparse Matrix*" ], "metadata": { "id": "NiiXso9elM2p" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.tensor([1., 2., 3., 4.])\n", "A = dglsp.spmatrix(i, val)\n", "print(A.to_dense())\n", "print(\"\")\n", "\n", "print(\"Get transpose of sparse matrix.\")\n", "print(A.T.to_dense())\n", "# Alias\n", "# A.transpose()\n", "# A.t()\n", "print(\"\")\n", "\n", "print(\"Get a sparse matrix with the negation of the original nonzero values.\")\n", "print(A.neg().to_dense())\n", "print(\"\")" ], "metadata": { "id": "qJcmZHmf-oTY" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "*Diagonal Matrix*" ], "metadata": { "id": "iE3ANjFolIJu" } }, { "cell_type": "code", "source": [ "val = torch.tensor([1., 2., 3., 4.])\n", "D = dglsp.diag(val)\n", "print(D.to_dense())\n", "print(\"\")\n", "\n", "print(\"Get inverse of diagonal matrix:\")\n", "print(D.inv().to_dense())\n", "print(\"\")\n", "\n", "print(\"Get a diagonal matrix with the negation of the original nonzero values.\")\n", "print(D.neg().to_dense())\n", "print(\"\")" ], "metadata": { "id": "j9kjY9RdlGXx" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### *Matrix multiplication*" ], "metadata": { "id": "4uQlDFb0Uzto" } }, { "cell_type": "markdown", "source": [ "**matmul(A, B), equivalent to A @ B**\n", "\n", "The supported combinations are shown as follows.\n", "\n", "A \\\\ B | **Tensor**|**DiagMatrix**|**SparseMatrix**\n", "----------------|-----------|--------------|----------\n", "**Tensor** |Y |N |N\n", "**DiagMatrix** |Y |Y |Y\n", "**SparseMatrix**|Y |Y |Y" ], "metadata": { "id": "THWE30v6WpAk" } }, { "cell_type": "markdown", "source": [ "**Union[DiagMatrix, SparseMatrix] @ Union[DiagMatrix, SparseMatrix] -> Union[SparseMatrix, DiagMatrix]:**\n", "\n", "For a $L \\times M$ sparse matrix A and a $M \\times N$ sparse matrix B, the shape of `A @ B` will be $L \\times N$ sparse matrix." ], "metadata": { "id": "VxyykR-vX7lF" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A1 = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A1:\")\n", "print(A1.to_dense())\n", "\n", "i = torch.tensor([[0, 1, 2],\n", " [0, 2, 1]])\n", "val = torch.tensor([4., 5., 6.])\n", "A2 = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A2:\")\n", "print(A2.to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D1 = dglsp.diag(val)\n", "print(\"D1:\")\n", "print(D1.to_dense())\n", "\n", "val = torch.tensor([-4., -5., -6.])\n", "D2 = dglsp.diag(val)\n", "print(\"D2:\")\n", "print(D2.to_dense())\n", "\n", "print(\"A1 @ A2:\")\n", "print((A1 @ A2).to_dense())\n", "\n", "print(\"A1 @ D1:\")\n", "print((A1 @ D1).to_dense())\n", "\n", "print(\"D1 @ A1:\")\n", "print((D1 @ A1).to_dense())\n", "\n", "print(\"D1 @ D2:\")\n", "print((D1 @ D2).to_dense())" ], "metadata": { "id": "XRDFC2rOYQM4" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**Union[DiagMatrix, SparseMatrix] @ Tensor -> Tensor:**\n", "\n", "For a $L \\times M$ sparse matrix A and a $M \\times N$ dense matrix B, the shape of `A @ B` will be $L \\times N$ dense matrix." ], "metadata": { "id": "g13fG8nvaVOt" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([1., 2., 3.])\n", "A = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "val = torch.tensor([-1., -2., -3.])\n", "D = dglsp.diag(val)\n", "print(\"D:\")\n", "print(D.to_dense())\n", "\n", "X = torch.tensor([[11., 22.], [33., 44.], [55., 66.]])\n", "print(\"X:\")\n", "print(X)\n", "\n", "print(\"A @ X:\")\n", "print(A @ X)\n", "\n", "print(\"D @ X:\")\n", "print(D @ X)" ], "metadata": { "id": "FcQ-CnqdlgWF" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "This operator also supports batched sparse-dense matrix multiplication. The sparse matrix A should have shape $L \\times M$, where the non-zero values are vectors of length $K$. The dense matrix B should have shape $M \\times N \\times K$. The output is a dense matrix of shape $L \\times N \\times K$." ], "metadata": { "id": "_KZiULLbmEZE" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [0, 2, 0]])\n", "val = torch.tensor([[1., 1.], [2., 2.], [3., 3.]])\n", "A = dglsp.spmatrix(i, val, shape=(3, 3))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "X = torch.tensor([[[1., 1.], [1., 2.]],\n", " [[1., 3.], [1., 4.]],\n", " [[1., 5.], [1., 6.]]])\n", "print(\"X:\")\n", "print(X)\n", "\n", "print(\"A @ X:\")\n", "print(A @ X)" ], "metadata": { "id": "ZUzXQk7Ab2wG" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**Sampled-Dense-Dense Matrix Multiplication (SDDMM)**\n", "\n", "``sddmm`` matrix-multiplies two dense matrices X1 and X2, then elementwise-multiplies the result with sparse matrix A at the nonzero locations. This is designed for sparse matrix with scalar values.\n", "\n", "$$out = (X_1 @ X_2) * A$$\n", "\n", "For a $L \\times N$ sparse matrix A, a $L \\times M$ dense matrix X1 and a $M \\times N$ dense matrix X2, `sddmm(A, X1, X2)` will be a $L \\times N$ sparse matrix." ], "metadata": { "id": "qO_8f_vhPKtf" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [2, 3, 3]])\n", "val = torch.tensor([1., 2., 3.])\n", "A = dglsp.spmatrix(i, val, (3, 4))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "X1 = torch.randn(3, 5)\n", "X2 = torch.randn(5, 4)\n", "print(\"X1:\")\n", "print(X1)\n", "print(\"X2:\")\n", "print(X2)\n", "\n", "O = dglsp.sddmm(A, X1, X2)\n", "print(\"dglsp.sddmm(A, X1, X2):\")\n", "print(O.to_dense())" ], "metadata": { "id": "3ZIFV0TgPhwH" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "This operator also supports batched sampled-dense-dense matrix multiplication. For a $L \\times N$ sparse matrix A with non-zero vector values of length $𝐾$, a $L \\times M \\times K$ dense matrix X1 and a $M \\times N \\times K$ dense matrix X2, `sddmm(A, X1, X2)` will be a $L \\times N \\times K$ sparse matrix." ], "metadata": { "id": "RmNmXU_ZqyF7" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[1, 1, 2],\n", " [2, 3, 3]])\n", "val = torch.tensor([[1., 1.], [2., 2.], [3., 3.]])\n", "A = dglsp.spmatrix(i, val, (3, 4))\n", "print(\"A:\")\n", "print(A.to_dense())\n", "\n", "X1 = torch.randn(3, 5, 2)\n", "X2 = torch.randn(5, 4, 2)\n", "print(\"X1:\")\n", "print(X1)\n", "print(\"X2:\")\n", "print(X2)\n", "\n", "O = dglsp.sddmm(A, X1, X2)\n", "print(\"dglsp.sddmm(A, X1, X2):\")\n", "print(O.to_dense())" ], "metadata": { "id": "DuSAjamyrIO_" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Non-linear activation functions" ], "metadata": { "id": "fVkbTT28ZzPr" } }, { "cell_type": "markdown", "source": [ "### Element-wise functions\n", "\n", "Most activation functions are element-wise and can be further grouped into two categories:\n", "\n", "**Sparse-preserving functions** such as `sin()`, `tanh()`, `sigmoid()`, `relu()`, etc. You can directly apply them on the `val` tensor of the sparse matrix and then recreate a new matrix of the same sparsity using `val_like`." ], "metadata": { "id": "XuaNdFO7XG2r" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.randn(4)\n", "A = dglsp.spmatrix(i, val)\n", "print(A.to_dense())\n", "\n", "print(\"Apply tanh.\")\n", "A_new = dglsp.val_like(A, torch.tanh(A.val))\n", "print(A_new.to_dense())" ], "metadata": { "id": "GZkCJJ0TX0cI" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "**Non-sparse-preserving functions** such as `exp()`, `cos()`, etc. You can first convert the sparse matrix to dense before applying the functions." ], "metadata": { "id": "i92lhMEnYas3" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.randn(4)\n", "A = dglsp.spmatrix(i, val)\n", "print(A.to_dense())\n", "\n", "print(\"Apply exp.\")\n", "A_new = A.to_dense().exp()\n", "print(A_new)" ], "metadata": { "id": "sroJpzRNYZq5" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### Softmax\n", "\n", "Apply row-wise softmax to the nonzero entries of the sparse matrix." ], "metadata": { "id": "y8OQZReVXpo3" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 1, 1, 2],\n", " [1, 0, 2, 0]])\n", "val = torch.tensor([1., 2., 3., 4.])\n", "A = dglsp.spmatrix(i, val)\n", "\n", "print(A.softmax())\n", "print(\"In dense format:\")\n", "print(A.softmax().to_dense())\n", "print(\"\\n\")" ], "metadata": { "id": "CQaKgzCJULjt" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Exercise \\#1\n", "\n", "*Let's test what you've learned. Feel free to [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/dmlc/dgl/blob/master/notebooks/sparse/quickstart.ipynb).*\n", "\n", "Given a sparse symmetrical adjacency matrix $A$, calculate its symmetrically normalized adjacency matrix: $$norm = \\bar{D}^{-\\frac{1}{2}}\\bar{A}\\bar{D}^{-\\frac{1}{2}}$$\n", "\n", "Where $\\bar{A} = A + I$, $I$ is the identity matrix, and $\\bar{D}$ is the diagonal node degree matrix of $\\bar{A}$." ], "metadata": { "id": "1iBNlJVYz3zi" } }, { "cell_type": "code", "source": [ "i = torch.tensor([[0, 0, 1, 1, 2, 2, 3],\n", " [1, 3, 2, 5, 3, 5, 4]])\n", "asym_A = dglsp.spmatrix(i, shape=(6, 6))\n", "# Step 1: create symmetrical adjacency matrix A from asym_A.\n", "# A =\n", "\n", "# Step 2: calculate A_hat from A.\n", "# A_hat =\n", "\n", "# Step 3: diagonal node degree matrix of A_hat\n", "# D_hat =\n", "\n", "# Step 4: calculate the norm from D_hat and A_hat.\n", "# norm = " ], "metadata": { "id": "0dDhfbJo0ByV" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## Exercise \\#2\n", "\n", "Let's implement a simplified version of the Graph Attention Network (GAT) layer.\n", "\n", "A GAT layer has two inputs: the adjacency matrix $A$ and the node input features $X$. The idea of GAT layer is to update each node's representation with a weighted average of the node's own representation and its neighbors' representations. In particular, when computing the output for node $i$, the GAT layer does the following:\n", "1. Compute the scores $S_{ij}$ representing the attention logit from neighbor $j$ to node $i$. $S_{ij}$ is a function of $i$ and $j$'s input features $X_i$ and $X_j$: $$S_{ij} = LeakyReLU(X_i^\\top v_1 + X_j^\\top v_2)$$, where $v_1$ and $v_2$ are trainable vectors.\n", "2. Compute a softmax attention $R_{ij} = \\exp S_{ij} / \\left( \\sum_{j' \\in \\mathcal{N}_i} s_{ij'} \\right)$, where $\\mathcal{N}_j$ means the neighbors of $j$. This means that $R$ is a row-wise softmax attention of $S$.\n", "3. Compute the weighted average $H_i = \\sum_{j' : j' \\in \\mathcal{N}_i} R_{j'} X_{j'} W$, where $W$ is a trainable matrix.\n", "\n", "The following code defined all the parameters you need but only completes step 1. Could you implement step 2 and step 3?" ], "metadata": { "id": "yfEVQBUuI-cE" } }, { "cell_type": "code", "source": [ "import torch.nn as nn\n", "import torch.functional as F\n", "\n", "class SimplifiedGAT(nn.Module):\n", " def __init__(self, in_size, out_size):\n", " super().__init__()\n", "\n", " self.W = nn.Parameter(torch.randn(in_size, out_size))\n", " self.v1 = nn.Parameter(torch.randn(in_size))\n", " self.v2 = nn.Parameter(torch.randn(in_size))\n", "\n", " def forward(self, A, X):\n", " # A: A sparse matrix with size (N, N). A[i, j] represent the edge from j to i.\n", " # X: A dense matrix with size (N, D)\n", " # Step 1: compute S[i, j]\n", " Xv1 = X @ self.v1\n", " Xv2 = X @ self.v2\n", " s = F.leaky_relu(Xv1[A.col] + Xv2[A.row])\n", " S = dglsp.val_like(A, s)\n", "\n", " # Step 2: compute R[i, j] which is the row-wise attention of $S$.\n", " # EXERCISE: replace the statement below.\n", " R = S\n", "\n", " # Step 3: compute H.\n", " # EXERCISE: replace the statement below.\n", " H = X\n", "\n", " return H" ], "metadata": { "id": "pYrgSxq6La5c" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "# Test:\n", "# Let's use the symmetric A created above.\n", "X = torch.randn(6, 20)\n", "module = SimplifiedGAT(20, 10)\n", "Y = module(A, X)" ], "metadata": { "id": "qjcXiidYCqGK" }, "execution_count": null, "outputs": [] } ] }