{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "private_outputs": true,
      "provenance": [],
      "authorship_tag": "ABX9TyMxpiQDo/pG6bIgkfWOPqXY"
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Neighbor Sampling Overview\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/stochastic_training/neighbor_sampling_overview.ipynb) [![GitHub](https://img.shields.io/badge/-View%20on%20GitHub-181717?logo=github&logoColor=ffffff)](https://github.com/dmlc/dgl/blob/master/notebooks/stochastic_training/neighbor_sampling_overview.ipynb)\n",
        "\n",
        "In previous tutorials you have learned how to train GNNs by computing the representations of all nodes on a graph. However, sometimes your graph is too large to fit the computation of all nodes in a single GPU.\n",
        "\n",
        "By the end of this tutorial, you will be able to\n",
        "\n",
        "- Understand the pipeline of stochastic GNN training.\n",
        "\n",
        "- Understand what is neighbor sampling and why it yields a bipartite graph for each GNN layer."
      ],
      "metadata": {
        "id": "p7tTmsjh3dEy"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Message Passing Review\n",
        "Recall that in [Gilmer et al.](https://arxiv.org/abs/1704.01212), the message passing formulation is as follows:\n",
        "\n",
        "$$m_{u \\to v}^{(l)} = M^{(l)}\\left(h_v^{(l-1)}, h_u^{(l-1)}, e_{u \\to v}^{(l-1)}\\right)$$\n",
        "\n",
        "$$m_{v}^{(l)} = \\sum_{u \\in \\mathcal{N}(v)} m_{u \\to v}^{(l)}$$\n",
        "\n",
        "$$h_v^{(l)} = U^{(l)}\\left(h_v^{(l-1)}, m_v^{(l)}\\right)$$\n",
        "\n",
        "\n",
        "where DGL calls\n",
        "- message function: $M^{(l)}$\n",
        "- reduce function: $\\sum$\n",
        "- update function: $U^{(l)}$\n",
        "\n",
        "Note that $\\sum$ here can represent any function and is not necessarily a summation.\n",
        "\n",
        "Essentially, the $l$-th layer representation of a single node depends on the $(l-1)$-th layer representation of the same node, as well as the $(l-1)$-th layer representation of the neighboring nodes. Those $(l-1)$-th layer representations then depend on the $(l-2)$-th layer representation of those nodes, as well as their neighbors.\n",
        "\n",
        "The following animation shows how a 2-layer GNN is supposed to compute the output of node 5:\n",
        "\n",
        "![image1](https://data.dgl.ai/tutorial/img/sampling.gif)\n",
        "\n",
        "You can see that to compute node 5 from the second layer, you will need its direct neighbors’ first layer representations (colored in yellow), which in turn needs their direct neighbors’ (i.e. node 5’s second-hop neighbors’) representations (colored in green)."
      ],
      "metadata": {
        "id": "eJs-O2Vz88Kd"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Neighbor Sampling Overview\n",
        "You can also see from the previous example that computing representation for a small number of nodes often requires input features of a significantly larger number of nodes. Taking all neighbors for message aggregation is often too costly since the nodes needed for input features would easily cover a large portion of the graph, especially for real-world graphs which are often [scale-free](https://en.wikipedia.org/wiki/Scale-free_network).\n",
        "\n",
        "Neighbor sampling addresses this issue by selecting a subset of the neighbors to perform aggregation. For instance, to compute ${h}_5^{(2)}$, you can choose two of the neighbors instead of all of them to aggregate, as in the following animation:\n",
        "\n",
        "![image2](https://data.dgl.ai/tutorial/img/bipartite.gif)\n",
        "\n",
        "You can see that this method uses much fewer nodes needed in message passing for a single minibatch.\n",
        "\n",
        "You can also notice in the animation above that the computation dependencies in the animation above can be described as a series of bipartite graphs. The output nodes (called destination nodes) are on one side and all the nodes necessary for inputs (called source nodes) are on the other side. The arrows indicate how the sampled neighbors propagates messages to the nodes. DGL calls such graphs **message flow graphs (MFG)**.\n",
        "\n",
        "Note that some GNN modules, such as `SAGEConv`, need to use the destination nodes’ features on the previous layer to compute the outputs. Without loss of generality, DGL always includes the destination nodes themselves in the source nodes."
      ],
      "metadata": {
        "id": "0yYSBM8s9M_P"
      }
    }
  ]
}
