Files
2026-07-13 13:35:51 +08:00

1310 lines
40 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"private_outputs": true,
"toc_visible": true,
"gpuType": "T4"
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"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",
"# Uncomment following commands to download Pytorch and DGL\n",
"# !pip install torch==2.0.0+cpu torchvision==0.15.1+cpu torchaudio==2.0.1 --index-url https://download.pytorch.org/whl/cpu > /dev/null\n",
"# !pip install dgl==1.1.0 -f https://data.dgl.ai/wheels/repo.html > /dev/null\n",
"import torch\n",
"os.environ['TORCH'] = torch.__version__\n",
"os.environ['DGLBACKEND'] = \"pytorch\"\n",
"\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 Sparse Matrix\n",
"A DGL Diagonal Sparse Matrix can be initiate by `dglsp.diag()`.\n",
"\n",
"Identity Matrix is a special type of Diagonal Sparse Matrix, in which all the value on the diagonal are 1.0. Use `dglsp.identity()` to initiate a Diagonal Sparse 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": [
"## Operations on Sparse Matrix\n",
"* Elementwise operations\n",
" * `A + B`\n",
" * `A - B`\n",
" * `A * B`\n",
" * `A / B`\n",
" * `A ** scalar`\n",
"* Broadcast operations\n",
" * `sp_<op>_v()`\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",
" * `SparseMatrix.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",
"Element-wise addition on two sparse matrices, returning a sparse matrix."
],
"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",
"Element-wise substraction on two sparse matrices, returning a sparse matrix."
],
"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",
"Element-wise multiplication on two sparse matrices or on a sparse matrix and a scalar, returning a sparse matrix."
],
"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",
"A1 = dglsp.spmatrix(i, val, shape=(3, 3))\n",
"print(\"A1:\")\n",
"print(A1.to_dense())\n",
"\n",
"i = torch.tensor([[0, 1, 2, 2],\n",
" [0, 2, 0, 1]])\n",
"val = torch.tensor([1., 2., 3., 4.])\n",
"A2 = dglsp.spmatrix(i, val, shape=(3, 3))\n",
"\n",
"print(\"A2:\")\n",
"print(A2.to_dense())\n",
"\n",
"print(\"A1 * 3:\")\n",
"print((A1 * 3).to_dense())\n",
"print(\"3 * A1:\")\n",
"print((3 * A1).to_dense())\n",
"\n",
"print(\"A1 * A2\")\n",
"print((A1 * 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",
"print(\"D1 * A2\")\n",
"print((D1 * A2).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",
"Element-wise multiplication on two sparse matrices or on a sparse matrix and a scalar, returning a sparse matrix. If both `A` and `B` are sparse matrices, both of them must have the same sparsity. And the returned matrix has the same order of non-zero entries as `A`."
],
"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",
"A1 = dglsp.spmatrix(i, val, shape=(3, 3))\n",
"print(\"A1:\")\n",
"print(A1.to_dense())\n",
"\n",
"i = torch.tensor([[1, 2, 1],\n",
" [0, 0, 2]])\n",
"val = torch.tensor([1., 3., 2.])\n",
"A2 = dglsp.spmatrix(i, val, shape=(3, 3))\n",
"\n",
"print(\"A1 / 2:\")\n",
"print((A1 / 2).to_dense())\n",
"\n",
"print(\"A1 / A2\")\n",
"print((A1 / 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(\"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",
"Element-wise power of a sparse matrix and a scalar, returning a sparse matrix."
],
"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": [
"### *Broadcast operations*"
],
"metadata": {
"id": "VXBz4j5x_wQ4"
}
},
{
"cell_type": "markdown",
"source": [
"**sp_\\<op\\>_v(A, v)**\n",
"\n",
"Broadcast operations on a sparse matrix and a vector, returning a sparse matrix. `v` is broadcasted to the shape of `A` and then the operator is applied on the non-zero values of `A`. `<op>` can be add, sub, mul, and div. \n",
"\n",
"There are two cases regarding the shape of `v`:\n",
"\n",
"1. `v` is a vector of shape `(1, A.shape[1])` or `(A.shape[1])`. In this case, `v` is broadcasted on the row dimension of `A`.\n",
"\n",
"2. `v` is a vector of shape `(A.shape[0], 1)`. In this case, `v` is broadcasted on the column dimension of `A`."
],
"metadata": {
"id": "PtnyZdXHAZ6Z"
}
},
{
"cell_type": "code",
"source": [
"i = torch.tensor([[1, 0, 2], [0, 3, 2]])\n",
"val = torch.tensor([10, 20, 30])\n",
"A = dglsp.spmatrix(i, val, shape=(3, 4))\n",
"\n",
"v1 = torch.tensor([1, 2, 3, 4])\n",
"print(\"A:\")\n",
"print(A.to_dense())\n",
"\n",
"print(\"v1:\")\n",
"print(v1)\n",
"\n",
"print(\"sp_add_v(A, v1)\")\n",
"print(dglsp.sp_add_v(A, v1).to_dense())\n",
"\n",
"v2 = v1.reshape(1, -1)\n",
"print(\"v2:\")\n",
"print(v2)\n",
"\n",
"print(\"sp_add_v(A, v2)\")\n",
"print(dglsp.sp_add_v(A, v2).to_dense())\n",
"\n",
"v3 = torch.tensor([1, 2, 3]).reshape(-1, 1)\n",
"print(\"v3:\")\n",
"print(v3)\n",
"\n",
"print(\"sp_add_v(A, v3)\")\n",
"print(dglsp.sp_add_v(A, v3).to_dense())"
],
"metadata": {
"id": "xxf3s-uWBRR7"
},
"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": [
"### *Matrix multiplication*"
],
"metadata": {
"id": "4uQlDFb0Uzto"
}
},
{
"cell_type": "markdown",
"source": [
"**matmul(A, B), equivalent to A @ B**\n",
"\n",
"Matrix multiplication on sparse matrices and/or dense matrix. There are two cases as follows."
],
"metadata": {
"id": "THWE30v6WpAk"
}
},
{
"cell_type": "markdown",
"source": [
"**SparseMatrix @ SparseMatrix -> SparseMatrix:**\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": [
"**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.nn.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": []
}
]
}