Skip to content

Instantly share code, notes, and snippets.

@gt3
Created March 23, 2023 03:48
Show Gist options
  • Save gt3/01782a443d7245bc74091019e4b776cf to your computer and use it in GitHub Desktop.
Save gt3/01782a443d7245bc74091019e4b776cf to your computer and use it in GitHub Desktop.
Understanding Code with Graph Neural Networks
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "XiP6-MBFmKAB"
},
"source": [
"# 0. Introduction\n",
"\n",
"In this notebook, we take a closer look at how to apply Graph Neural Networks (GNNs) to the task of graph-level prediction on the ogbg-code2 dataset. The prediction task is defined as: \n",
"Given a graph representation of a program, specifically the body of a method, generate the set of tokens that form the method's name.\n",
"\n",
"![Graph-level-prediction.png](data:image/png;base64,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+66MsCUgl8vFqFGjyMvLo0aNGrz88stahyQiTJKJGDRy5EgAli5dyokTJ7QNRogQeAsxjx07JoWYCejNN99k7dq1AEyePJk6depoHJGINEkmYtCIESMwm83k5eXx4Ycfah2OEEG78sorueyyywApxEw0R44c4emnnwage/fujBgxQuOIRDRIMhGD6tatS58+fQCk+Y/QrTvvvBOAFStWsG/fPm2DEVFz3333cfbsWaxWK2+99Za0zE4QO7e9wAAAIABJREFUkkzEKG8h5rZt29i4caPG0QgRvKFDh5KamorL5ZKkOEEsWLDAt9HbuHHjaNasmcYRiWiRZCJG9e/f37c1rxRiCj2qVq2arxDznXfekULMOJeZmelrk92qVSsefvhhjSMS0STJRIyyWCwMHjwYgNmzZ2O32zWOSIjgeQsxjx49yueff65xNInD7XYH9S8cHnvsMQ4fPozRaGT69OnSMjvBGNzh+ksSYbd161Zat24NqB33vN/yhNCTSy+9lM2bN9OvXz+WLl2qdTgB8V4WA/1vycfKe7682xU9V9HjkVay/sF7/+eff6ZXr164XC7uvfdeJk6cWOz5sv4LYDQaS72ewWAoddvfz4rYIclEjGvbti0bN26kT58+fPHFF1qHI0TQpk6dyujRozEajezZs4eGDRuG5XVLfmiX/KZd1n3v7bJeQwQnLy+Prl27smPHDho0aMAPP/wQ8U6X/pKNoreNRmOxJMTfPxFekkzEuNdee40HHngAo9HIvn37OP/887UOSYignD17lvr162Oz2Rg7dizPPvtsuUmAy+Uq87lwD82LypswYYJvJGL27Nn07dtX44gCUzSxKJp8eG+XfEyUT5KJGHf69Gnq1auHw+HghRde4Mknn9Q6JJHA/M23u1yuCufj77nnHubOnUu9evXYsmULJpNJ499EhMPOnTvp0qULDoeDm266Ka5X7ZRMNEr+S/QRD0kmdGDgwIHMmzePpk2bsmPHjoT+gxXhUVECUFaiEKoffviBa6+9FoC5c+fy17/+NVy/itCIy+WiX79+/Pjjj6Snp7Nu3TrOPfdcrcPSVFmJhtFojPsEWpIJHVi2bJlv6HDNmjV07txZ44hErPD3oR9IoqCFjh078vvvv9O3b19mz56tSQwifGbMmMFDDz0EqLqYYcOGaRxR7PMmFUUTDO9tvZNkQgdcLheN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)\n",
"\n",
"The dataset is a collection of 450,000 abstract syntax trees (ASTs) generated from Python GitHub repositories. The dataset aligns well with our task as it also incorporates nodes and edges from the AST as well as the tokenized method names from which the AST was generated. The training, validation, and test splits are provided with the dataset."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "cDdFlkbqQYfq"
},
"source": [
"# 1. Install Dependencies"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"id": "Be3f5csQWOBY"
},
"outputs": [],
"source": [
"# Basic Python dependencies\n",
"import os\n",
"import time\n",
"from collections import defaultdict, namedtuple\n",
"import random\n",
"random.seed(2)\n",
"\n",
"# Basic data handling libraries\n",
"import numpy as np\n",
"from tqdm import tqdm, trange\n",
"import pandas as pd\n",
"import copy\n",
"import json\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "hh8eI-xgRcK9"
},
"source": [
"We'll be using [PyG](https://pytorch-geometric.readthedocs.io/en/latest/) (PyTorch Geometric), a library built upon PyTorch to easily write and train Graph Neural Networks (GNNs) on structured datasets.\n",
"\n",
"Next, we will load the [Open Graph Benchmark](https://ogb.stanford.edu/docs/lsc/) (OGB) dataset from the ogb package. OGB is a collection of realistic, large-scale, and diverse benchmark datasets for machine learning on graphs. The ogb package not only provides data loaders for each dataset but also model evaluators.\n",
"\n",
"_Note: This cell might take a while (~5 minutes) to run_"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "pOCAIKchQfK2",
"outputId": "4ca1b8d7-cc15-42e7-e88b-0af60cb483be"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
"Looking in links: https://pytorch-geometric.com/whl/torch-1.13.1+cu116.html\n",
"Requirement already satisfied: torch-scatter in /usr/local/lib/python3.9/dist-packages (2.1.1+pt113cu116)\n",
"Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
"Looking in links: https://pytorch-geometric.com/whl/torch-1.13.1+cu116.html\n",
"Requirement already satisfied: torch-sparse in /usr/local/lib/python3.9/dist-packages (0.6.17+pt113cu116)\n",
"Requirement already satisfied: scipy in /usr/local/lib/python3.9/dist-packages (from torch-sparse) (1.10.1)\n",
"Requirement already satisfied: numpy<1.27.0,>=1.19.5 in /usr/local/lib/python3.9/dist-packages (from scipy->torch-sparse) (1.22.4)\n",
"Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
"Requirement already satisfied: torch-geometric in /usr/local/lib/python3.9/dist-packages (2.2.0)\n",
"Requirement already satisfied: numpy in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (1.22.4)\n",
"Requirement already satisfied: requests in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (2.27.1)\n",
"Requirement already satisfied: jinja2 in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (3.1.2)\n",
"Requirement already satisfied: psutil>=5.8.0 in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (5.9.4)\n",
"Requirement already satisfied: tqdm in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (4.65.0)\n",
"Requirement already satisfied: pyparsing in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (3.0.9)\n",
"Requirement already satisfied: scikit-learn in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (1.2.2)\n",
"Requirement already satisfied: scipy in /usr/local/lib/python3.9/dist-packages (from torch-geometric) (1.10.1)\n",
"Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.9/dist-packages (from jinja2->torch-geometric) (2.1.2)\n",
"Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.9/dist-packages (from requests->torch-geometric) (1.26.15)\n",
"Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.9/dist-packages (from requests->torch-geometric) (2.0.12)\n",
"Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.9/dist-packages (from requests->torch-geometric) (3.4)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/dist-packages (from requests->torch-geometric) (2022.12.7)\n",
"Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-packages (from scikit-learn->torch-geometric) (1.1.1)\n",
"Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.9/dist-packages (from scikit-learn->torch-geometric) (3.1.0)\n",
" Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
"Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
"Requirement already satisfied: ogb in /usr/local/lib/python3.9/dist-packages (1.3.5)\n",
"Requirement already satisfied: pandas>=0.24.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (1.4.4)\n",
"Requirement already satisfied: urllib3>=1.24.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (1.26.15)\n",
"Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (1.2.2)\n",
"Requirement already satisfied: numpy>=1.16.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (1.22.4)\n",
"Requirement already satisfied: six>=1.12.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (1.16.0)\n",
"Requirement already satisfied: outdated>=0.2.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (0.2.2)\n",
"Requirement already satisfied: tqdm>=4.29.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (4.65.0)\n",
"Requirement already satisfied: torch>=1.6.0 in /usr/local/lib/python3.9/dist-packages (from ogb) (1.13.1+cu116)\n",
"Requirement already satisfied: setuptools>=44 in /usr/local/lib/python3.9/dist-packages (from outdated>=0.2.0->ogb) (67.6.0)\n",
"Requirement already satisfied: littleutils in /usr/local/lib/python3.9/dist-packages (from outdated>=0.2.0->ogb) (0.2.2)\n",
"Requirement already satisfied: requests in /usr/local/lib/python3.9/dist-packages (from outdated>=0.2.0->ogb) (2.27.1)\n",
"Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.9/dist-packages (from pandas>=0.24.0->ogb) (2022.7.1)\n",
"Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.9/dist-packages (from pandas>=0.24.0->ogb) (2.8.2)\n",
"Requirement already satisfied: joblib>=1.1.1 in /usr/local/lib/python3.9/dist-packages (from scikit-learn>=0.20.0->ogb) (1.1.1)\n",
"Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.9/dist-packages (from scikit-learn>=0.20.0->ogb) (3.1.0)\n",
"Requirement already satisfied: scipy>=1.3.2 in /usr/local/lib/python3.9/dist-packages (from scikit-learn>=0.20.0->ogb) (1.10.1)\n",
"Requirement already satisfied: typing-extensions in /usr/local/lib/python3.9/dist-packages (from torch>=1.6.0->ogb) (4.5.0)\n",
"Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.9/dist-packages (from requests->outdated>=0.2.0->ogb) (3.4)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.9/dist-packages (from requests->outdated>=0.2.0->ogb) (2022.12.7)\n",
"Requirement already satisfied: charset-normalizer~=2.0.0 in /usr/local/lib/python3.9/dist-packages (from requests->outdated>=0.2.0->ogb) (2.0.12)\n"
]
}
],
"source": [
"!pip install torch-scatter -f https://pytorch-geometric.com/whl/torch-1.13.1+cu116.html\n",
"!pip install torch-sparse -f https://pytorch-geometric.com/whl/torch-1.13.1+cu116.html\n",
"!pip install torch-geometric\n",
"!pip install -q git+https://github.com/snap-stanford/deepsnap.git\n",
"!pip install ogb"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"id": "1T0jRDVgV3iN"
},
"outputs": [],
"source": [
"import torch\n",
"import torch_scatter\n",
"import torch.nn as nn\n",
"import torch.nn.functional as F\n",
"\n",
"import torch_geometric.nn as pyg_nn\n",
"import torch_geometric.utils as pyg_utils\n",
"\n",
"from torch import Tensor\n",
"from typing import Union, Tuple, Optional\n",
"from torch_geometric.typing import (OptPairTensor, Adj, Size, NoneType, OptTensor)\n",
"\n",
"from torch.nn import Parameter, Linear\n",
"from torch_sparse import SparseTensor, set_diag\n",
"from torch_geometric.nn.conv import MessagePassing\n",
"from torch_geometric.utils import remove_self_loops, add_self_loops, softmax\n",
"from torch_geometric.nn import global_add_pool\n",
"\n",
"from torch_geometric.data import DataLoader\n",
"import torch_geometric.transforms as T"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"id": "JaiQ4WZBfDW7"
},
"outputs": [],
"source": [
"# Use GPU when available\n",
"device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ipKWwVO1buFr"
},
"source": [
"# 2. Setup"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "ZPfG9w44qBxt"
},
"source": [
"## 2.1 Load Dataset\n",
"\n",
"The `ogbg-code2` dataset provides 452,741 different graphs, and the task is to learn a model that can predict a set of tokens that represents the method name for a given graph. The dataset has a pre-defined project split, where the ASTs for the train set are obtained from GitHub projects that do not appear in the validation and test sets.\n",
"\n",
"Download, extract, and import the `ogbg-code2` dataset.\n",
"\n",
"https://ogb.stanford.edu/docs/graphprop/"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"id": "eLOsu-8-Y0Tx",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "49aeb1fa-2ce9-4c97-9aa8-3e0eab8a89c3"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"The ogbg-code2 dataset has 452741 graphs\n",
"Sample:\n",
"Data(edge_index=[2, 243], x=[244, 2], node_is_attributed=[244, 1], node_dfs_order=[244, 1], node_depth=[244, 1], y=[1], num_nodes=244)\n",
"Splits: Train: 407976, Val: 22817\n"
]
}
],
"source": [
"from ogb.nodeproppred import PygNodePropPredDataset\n",
"from ogb.graphproppred import PygGraphPropPredDataset, Evaluator\n",
"\n",
"dataset_name = 'ogbg-code2'\n",
"# Load the dataset\n",
"dataset = PygGraphPropPredDataset(name=dataset_name)\n",
"print('The {} dataset has {} graphs'.format(dataset_name, len(dataset)))\n",
"print('Sample:')\n",
"# Extract sample graph\n",
"print(dataset[0])\n",
"split_idx = dataset.get_idx_split()\n",
"train_idx, valid_idx = split_idx['train'], split_idx['valid']\n",
"print(f'Splits: Train: {len(train_idx)}, Val: {len(valid_idx)}')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9ojDO44URLu8"
},
"source": [
"Load provided mapping for node types and attributes."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"id": "kPkqdj4wQ7Vy"
},
"outputs": [],
"source": [
"nodetypes_mapping = pd.read_csv(os.path.join(dataset.root, 'mapping', 'typeidx2type.csv.gz'))\n",
"nodeattributes_mapping = pd.read_csv(os.path.join(dataset.root, 'mapping', 'attridx2attr.csv.gz'))"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Hon0DGcApn2f"
},
"source": [
"## 2.2 Model Configuration"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "OOmHc6Evptds"
},
"source": [
"All model configuration and hyperparameters are stored in a single `Config` object as defined below. The most notable parameters is: `max_vocab_size` which determines the size of our vocabulary. Since our model is generating a set of tokens, we can limit the predictions by having a fixed length vocabulary."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"id": "QoeuE8aDiXnb"
},
"outputs": [],
"source": [
"batch_size, emb_dim, lr, max_iter = 128, 256, .005, 25\n",
"cfg = {\n",
" 'device': device,\n",
" 'dataset_name': dataset_name,\n",
" 'model_save_path': f'gin_{batch_size}bz_{emb_dim}emb_{lr}lr_{max_iter}it.net',\n",
" 'history_write_path': f'gin_{batch_size}bz_{emb_dim}emb_{lr}lr_{max_iter}it_history.json',\n",
" 'max_vocab_size': 5000,\n",
" 'max_seq_len': 5,\n",
" 'batch_size': batch_size,\n",
" 'emb_dim': emb_dim,\n",
" 'num_nodetypes': len(nodetypes_mapping['type']),\n",
" 'num_nodeattributes': len(nodeattributes_mapping['attr']),\n",
" 'max_depth': 20,\n",
" 'num_layers': 5,\n",
" 'max_iter': max_iter,\n",
" 'dropout': 0.2,\n",
" 'lr': lr\n",
"}\n",
"Cfg = namedtuple('Cfg', cfg)\n",
"cfg = Cfg(**cfg)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "8gy3VjT_qTWK"
},
"source": [
"## 2.3 Build Vocabulary\n",
"\n",
"Here we extract the vocabulary from ground truth sequences (list of words) in training set."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"id": "aC3op-nklRXb",
"colab": {
"base_uri": "https://localhost:8080/"
},
"outputId": "d2402390-26cf-4974-ce85-f32e8cc7ab70"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Top 5000 words proportion: 0.9566695094108582\n"
]
}
],
"source": [
"# Given a sequence of words and word-to-index mapping, generate a fixed length tensor representation\n",
"def encode_seq(seq, v2i, PAD):\n",
" words_idx = [v2i[word] for word in seq[:cfg.max_seq_len]]\n",
" pad_factor = max(0, cfg.max_seq_len - len(words_idx))\n",
" return torch.as_tensor(words_idx + [PAD]*pad_factor, dtype = torch.long, device = cfg.device)\n",
"\n",
"def build_vocab(target, target_idx_list):\n",
" # stores mapping between words and indices\n",
" v2i = defaultdict(lambda: len(v2i))\n",
" VOID = v2i['<void>']\n",
" PAD = v2i['<pad>']\n",
" UNK = v2i['<unk>']\n",
"\n",
" target_encodings = torch.stack([encode_seq(target[idx], v2i, PAD) for idx in target_idx_list]).to(cfg.device)\n",
" \n",
" # so far we have an infinite length vocabulary which is not feasible\n",
" # restrict to top-k (cfg.max_vocab_size) appearing words\n",
" counts = torch.bincount(target_encodings.view(-1))\n",
" topk = torch.topk(counts, k = cfg.max_vocab_size)[1]\n",
" print(f'Top {cfg.max_vocab_size} words proportion: {(counts[topk].sum() / counts.sum()).cpu().numpy()}')\n",
"\n",
" topk_keep = torch.as_tensor([VOID,PAD,UNK], dtype = torch.long, device = cfg.device)\n",
" topk = torch.cat([topk, topk_keep])\n",
"\n",
" # rewire the indices based on the top-k search above\n",
" i2v = {v: k for k, v in v2i.items()}\n",
" v2i = {}\n",
" for k, new_k in zip(sorted(topk.unique().cpu().numpy()), range(len(topk))):\n",
" v2i[i2v[k]] = new_k\n",
"\n",
" v2i = defaultdict(lambda: UNK, v2i)\n",
" i2v = {v: k for k, v in v2i.items()}\n",
" return v2i, i2v, UNK, PAD\n",
"\n",
"# Build vocab from existing labels in *training set* only\n",
"v2i, i2v, UNK, PAD = build_vocab(dataset.data.y, train_idx)"
]
},
{
"cell_type": "markdown",
"source": [
"For all the examples in the training set, roughly **96%** are in our fixed-length vocabulary. This ratio provides a litmus test of whether we need to tune `max_vocab_len`."
],
"metadata": {
"id": "PLKtwrCRhgR5"
}
},
{
"cell_type": "markdown",
"metadata": {
"id": "rWVhGhMVW1BN"
},
"source": [
"## 2.4 Augment Features\n",
"\n",
"PyG's transforms are a general way to modify and customize Data objects. Here we define two such transforms: \n",
"1. Encode labels as Tensors with `encode_target_tensor`.\n",
"2. Augment next-token edges with inverse relation and edge attributes with `augment_edge`.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"id": "29RhVmeiXkAP"
},
"outputs": [],
"source": [
"def encode_target_tensor(data):\n",
" data.y_tensor = encode_seq(data.y, v2i, PAD).unsqueeze(dim = 0)\n",
" return data\n",
"\n",
"# The augment_edge transform is originally defined in the official OGB example here:\n",
"# https://github.com/snap-stanford/ogb (source)\n",
"def augment_edge(data):\n",
" '''\n",
" Input:\n",
" data: PyG data object\n",
" Output:\n",
" data (edges are augmented in the following ways):\n",
" data.edge_index: Added next-token edge. The inverse edges were also added.\n",
" data.edge_attr (torch.Long):\n",
" data.edge_attr[:,0]: whether it is AST edge (0) for next-token edge (1)\n",
" data.edge_attr[:,1]: whether it is original direction (0) or inverse direction (1)\n",
" '''\n",
" ##### AST edge\n",
" edge_index_ast = data.edge_index\n",
" edge_attr_ast = torch.zeros((edge_index_ast.size(1), 2))\n",
"\n",
" ##### Inverse AST edge\n",
" edge_index_ast_inverse = torch.stack([edge_index_ast[1], edge_index_ast[0]], dim = 0)\n",
" edge_attr_ast_inverse = torch.cat([torch.zeros(edge_index_ast_inverse.size(1), 1), torch.ones(edge_index_ast_inverse.size(1), 1)], dim = 1)\n",
"\n",
"\n",
" ##### Next-token edge\n",
"\n",
" ## Since the nodes are already sorted in dfs ordering in our case, we can just do the following.\n",
" attributed_node_idx_in_dfs_order = torch.where(data.node_is_attributed.view(-1,) == 1)[0]\n",
"\n",
" ## build next token edge\n",
" # Given: attributed_node_idx_in_dfs_order\n",
" # [1, 3, 4, 5, 8, 9, 12]\n",
" # Output:\n",
" # [[1, 3, 4, 5, 8, 9]\n",
" # [3, 4, 5, 8, 9, 12]\n",
" edge_index_nextoken = torch.stack([attributed_node_idx_in_dfs_order[:-1], attributed_node_idx_in_dfs_order[1:]], dim = 0)\n",
" edge_attr_nextoken = torch.cat([torch.ones(edge_index_nextoken.size(1), 1), torch.zeros(edge_index_nextoken.size(1), 1)], dim = 1)\n",
"\n",
"\n",
" ##### Inverse next-token edge\n",
" edge_index_nextoken_inverse = torch.stack([edge_index_nextoken[1], edge_index_nextoken[0]], dim = 0)\n",
" edge_attr_nextoken_inverse = torch.ones((edge_index_nextoken.size(1), 2))\n",
"\n",
"\n",
" data.edge_index = torch.cat([edge_index_ast, edge_index_ast_inverse, edge_index_nextoken, edge_index_nextoken_inverse], dim = 1)\n",
" data.edge_attr = torch.cat([edge_attr_ast, edge_attr_ast_inverse, edge_attr_nextoken, edge_attr_nextoken_inverse], dim = 0)\n",
"\n",
" return data"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"id": "j80XYIl_f4gI"
},
"outputs": [],
"source": [
"# Compose multiple transforms and apply it to the dataset\n",
"dataset.transform = T.Compose([augment_edge, encode_target_tensor])"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "jcjP4V9IKq30"
},
"source": [
"## 2.5 Create Data Loaders"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "78Mho4d5zVRZ"
},
"source": [
"PyG automatically takes care of batching multiple graphs into a single giant graph with the help of the `DataLoader` class:"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "8knLefdXNrYd",
"outputId": "964a74fe-97cb-4844-8964-b71f1f50786e"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Number of batches: Train: 3188, Val: 179\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"/usr/local/lib/python3.9/dist-packages/torch_geometric/deprecation.py:12: UserWarning: 'data.DataLoader' is deprecated, use 'loader.DataLoader' instead\n",
" warnings.warn(out)\n"
]
}
],
"source": [
"# use evaluation metrics defined in the OGB dataset\n",
"evaluator = Evaluator(cfg.dataset_name)\n",
"\n",
"# create data loaders for each split\n",
"# shuffle = true for training set ensures data is reshuffled for every epoch\n",
"train_loader = DataLoader(dataset[train_idx], batch_size = cfg.batch_size, shuffle = True) # 407976\n",
"valid_loader = DataLoader(dataset[valid_idx], batch_size = cfg.batch_size, shuffle = False) # 22817\n",
"print(f'Number of batches: Train: {len(train_loader)}, Val: {len(valid_loader)}')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MFdpuYnKRuWA"
},
"source": [
"# 3. Create Model\n",
"\n",
"Here are the steps to train a GNN for graph property prediction:\n",
"\n",
"1. Embed each node by performing multiple rounds of message passing\n",
"2. Aggregate node embeddings into a unified graph embedding (readout layer)\n",
"3. Train a final classifier on the graph embedding\n",
"\n",
"![GIN-network-architecture.png](data:image/png;base64,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VquVZcuW4ebmRnh4OAaDgffff58TJ05gMBjo6upCrVbz4YcfsmrVKh566CFiY2MpKCigpKSExsZGduzYgb+/P+np6ezevZsFCxbQ3NxMcnIyHR0dvP3220yePJlnnnkGjUZDdXU1f/vb39iwYQO7du2is7MTnU5HcHAwr7zyCh9//DGNjY2YTCZqa2tZunQpd9xxBz//+c8JDQ3lxIkTLFy4kC+//JLg4GDMZjMnT55kyZIl3Hzzzfz617/+1temrq6Ohx9+mG3btpGWlobRaOSDDz5g2bJlvP766wQFBVFQUMCvf/1rCgsLiY+Px+l08uGHH/L++++zYMECUlNTaWxs5N1336WqqgqtVovdbqezs5P333+fzMxMPD092bt3LyaTifLycnJzc3n00UfJycmhoqKCV155he7ubtrb20lOTsZms7Fs2TK++OILnnvuOXQ6HU6nkzfeeIOnn34aX19foqKi2LJlC2+++SYPPvggs2fPxtPTk7y8PJYtW4bD4cBms+Hm5kZVVRVLly7l7rvvZs6cOfj6+lJTU8OiRYtYvnw5ZrOZgIAA5cHv3nvv5eabb6ajo4MtW7bw6quvMnjwYAoLC/H396e4uJh33nmHBQsWcNNNN1FQUMDhw4dxOp1s2bIFk8nEmDFjePLJJ1mxYgXR0dF4eHiwfv16/v3vf/PHP/6RCRMmYDAYLnp9JNAKIYS4Zhw7dgyz2UxYWBguLi7nXUev1xMcHKz8/5lAq1arSU5Opl+/fjz//PO89957JCUlndP04GxarZaEhAR+/etf8/DDD/OXv/zlnKYHl2L16tWsX7+eqVOnMnPmTNzd3WloaGDhwoVs27aNrKwssrKyiIqKYuXKleTl5TF16lQAysvL2bFjB6mpqej1etasWcNbb73FjTfeyN13341Op6OmpoZf/vKXPProo6xfvx6DwUBrays7duzgtttu44477gAgMjLynIcBp9NJS0uL0kzCbrfT0tJCUVERc+fOZfr06ej1elasWMGbb77J+vXrGTlyJFOmTKG0tJR9+/bx8MMPk5yczKlTp3juueewWq289NJLhIaG0tXVxZo1a/jzn/9Meno6t912G93d3dTV1bF//36ee+45QkJC8PX1ZceOHVRXV2MwGHjkkUdITEykra2NP/7xj+Tm5jJy5EhCQ0P54osv2L59OzfeeCO33nor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)\n",
"\n",
"The figure above illustrates a high-level architecture of our GNN model.\n",
"\n",
"For [Graph Isomorphism Network](https://cs.stanford.edu/people/jure/pubs/gin-iclr19.pdf) (GIN), the readout layer simply takes the sum of \n",
"node embeddings. PyG provides this functionality via `global_sum_pool`, which takes in the node embeddings of all nodes in the mini-batch and the assignment vector batch to compute a graph embedding of size `[batch_size, hidden_dim]` for each graph in the batch.\n",
"\n",
"`NodeEncoder` defines 3 `nn.Embedding` layers to encode node features: type, attribute, and depth."
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "qyhDpe39nQ_E"
},
"source": [
"## 3.1 Define Model\n",
"\n",
"The final architecture for applying GNNs to the task of graph property prediction then looks as follows and allows for complete end-to-end training:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"id": "PAQqTDiwneC7"
},
"outputs": [],
"source": [
"# Embed node type, attribute, and depth\n",
"class NodeEncoder(torch.nn.Module):\n",
" def __init__(self, cfg):\n",
" super(NodeEncoder, self).__init__()\n",
" self.type_enc = nn.Embedding(cfg.num_nodetypes, cfg.emb_dim)\n",
" self.attr_enc = nn.Embedding(cfg.num_nodeattributes, cfg.emb_dim)\n",
" self.depth_enc = nn.Embedding(cfg.max_depth + 1, cfg.emb_dim)\n",
" self.cfg = cfg\n",
" self.apply(self._init_weights)\n",
"\n",
" def _init_weights(self, module):\n",
" for param in module.parameters():\n",
" param.data.uniform_(.0, 1.0)\n",
"\n",
" def forward(self, x, depth):\n",
" depth[depth > self.cfg.max_depth] = self.cfg.max_depth\n",
" # add the node feature embeddings\n",
" return self.type_enc(x[:,0]) + self.attr_enc(x[:,1]) + self.depth_enc(depth)\n",
"\n",
"# Apply GIN convolution given the graph structure\n",
"# message propagation is enabled by inheriting from MessagePassing\n",
"# use sum \"add\" aggregation\n",
"class GINConv(MessagePassing):\n",
" def __init__(self, emb_dim):\n",
" super(GINConv, self).__init__(aggr=\"add\")\n",
" self.mlp = nn.Sequential(\n",
" nn.Linear(emb_dim, 2*emb_dim), \n",
" nn.BatchNorm1d(2*emb_dim), \n",
" nn.ReLU(), \n",
" nn.Linear(2*emb_dim, emb_dim))\n",
" self.eps = nn.Parameter(torch.Tensor([0]))\n",
" self.edge_encoder = nn.Linear(2, emb_dim)\n",
" self.apply(self._init_weights)\n",
"\n",
" def _init_weights(self, module):\n",
" for param in module.parameters():\n",
" param.data.uniform_(.0, 1.0)\n",
"\n",
" def forward(self, x, edge_index, edge_attr):\n",
" emb = self.edge_encoder(edge_attr)\n",
" emb = self.propagate(edge_index, x = x, edge_emb = emb)\n",
" emb += x * (self.eps + 1)\n",
" res = self.mlp(emb)\n",
" return res\n",
"\n",
" def message(self, x_j, edge_emb):\n",
" return F.relu(x_j + edge_emb)\n",
"\n",
"# Node embedding GNN\n",
"# Encodes node attributes with provided NodeEncoder\n",
"# then applies Message Passing with GINConv\n",
"# Intra layers: Linear --> Batch Norm --> Dropout --> Activation\n",
"class NodeGIN(torch.nn.Module):\n",
" def __init__(self, cfg):\n",
" super(NodeGIN, self).__init__()\n",
" self.encoder = NodeEncoder(cfg)\n",
" self.convs = nn.ModuleList([GINConv(cfg.emb_dim) for l in range(cfg.num_layers)])\n",
" self.bns = nn.ModuleList([nn.BatchNorm1d(cfg.emb_dim) for l in range(cfg.num_layers)])\n",
" self.cfg = cfg\n",
" self.apply(self._init_weights)\n",
"\n",
" def _init_weights(self, module):\n",
" for param in module.parameters():\n",
" param.data.uniform_(.0, 1.0)\n",
"\n",
" def forward(self, data_batch):\n",
" x, edge_index, edge_attr, node_depth = data_batch.x, data_batch.edge_index, data_batch.edge_attr, data_batch.node_depth\n",
" h_last = self.encoder(x, node_depth.view(-1,))\n",
" for l in range(self.cfg.num_layers):\n",
" h = self.convs[l](h_last, edge_index, edge_attr)\n",
" h = self.bns[l](h)\n",
" h = F.relu(h) if l < (self.cfg.num_layers-1) else h\n",
" h = F.dropout(h, p = self.cfg.dropout, training = self.training)\n",
" h_last = h\n",
" return h_last\n",
"\n",
"# Wrapper GNN to generate final predictions: \n",
"# sequence of tokens that form method name for given graph\n",
"# Intra layers: Linear --> Batch Norm --> Dropout --> Activation --> Aggregation\n",
"# GIN aggregate with global_add_pool\n",
"class GIN(nn.Module):\n",
" def __init__(self, v2i, cfg):\n",
" super(GIN, self).__init__()\n",
" self.node_GIN = NodeGIN(cfg)\n",
" self.pred_lins = nn.ModuleList([nn.Linear(cfg.emb_dim, len(v2i)) for i in range(cfg.max_seq_len)])\n",
" self.v2i = v2i\n",
" self.cfg = cfg\n",
" self.apply(self._init_weights)\n",
" \n",
" def _init_weights(self, module):\n",
" for param in module.parameters():\n",
" param.data.uniform_(.0, 1.0)\n",
"\n",
" def forward(self, data_batch):\n",
" node_emb = self.node_GIN(data_batch)\n",
" graph_emb = global_add_pool(node_emb, data_batch.batch)\n",
" preds = [lin(graph_emb) for lin in self.pred_lins]\n",
" return preds\n",
" \n",
" def save(self):\n",
" print('')\n",
" print(f'Saving model: {self.cfg.model_save_path}')\n",
" summary = dict(params=self.cfg, v2i=dict(self.v2i), state=self.state_dict())\n",
" torch.save(summary, self.cfg.model_save_path)\n",
"\n",
" @staticmethod\n",
" def load(model_path_fname, use_cuda = True):\n",
" data = torch.load(model_path_fname)\n",
" cfg, v2i, state = data['params'], data['v2i'], data['state']\n",
" model = GIN(v2i, cfg)\n",
" model.load_state_dict(state)\n",
" if use_cuda: model.cuda()\n",
" model.eval()\n",
" return model"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "QNSBk2iKnXDq"
},
"source": [
"## 3.2 Create Model"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "H0_3qNYL6hfV"
},
"source": [
"Instantiate the model and load it on the GPU if available. Use [Adam optimizer](https://pytorch.org/docs/stable/generated/torch.optim.Adam.html) and [Cross-Entropy Loss](https://pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html) for multi-class classificaiton."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"id": "vcCnD9j9R3Vb"
},
"outputs": [],
"source": [
"model = GIN(v2i, cfg).to(cfg.device)\n",
"optimizer = torch.optim.Adam(model.parameters(), lr = cfg.lr)\n",
"ceLoss = torch.nn.CrossEntropyLoss()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "MUhZo6hsVi4Q"
},
"source": [
"## 3.3 Create Evaluator\n",
"\n",
"Wrapper to evaluate on validation and test sets using the OGB provided evaluation metric. The `decode` function takes a sequence of predicted tokens and converts them back to words using the reverse vocabulary mapping `i2v`."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"id": "0z12QwKXVohb"
},
"outputs": [],
"source": [
"class Eval():\n",
" def __init__(self, model, loaders, evaluator, i2v, PAD, cfg):\n",
" self.model = model\n",
" self.loaders = loaders\n",
" self.ogb_eval = evaluator.eval\n",
" self.i2v = i2v\n",
" self.filter_tokens = torch.as_tensor([PAD], dtype = torch.long, device = cfg.device)\n",
" self.cfg = cfg\n",
" \n",
" def decode(self, pred):\n",
" res = []\n",
" for p in pred:\n",
" mask = torch.isin(p, self.filter_tokens, invert = True)\n",
" p_trimmed = p[mask]\n",
" res.append([self.i2v[i] for i in p_trimmed.cpu().numpy()])\n",
" return res\n",
"\n",
" def eval(self):\n",
" loaders_metrics = []\n",
" for loader in self.loaders:\n",
" preds_acc = []\n",
" labels_acc = []\n",
" for batch_id, batch in enumerate(tqdm(loader, desc='Eval Batch')):\n",
" batch = batch.to(self.cfg.device)\n",
" # disable auto grad\n",
" with torch.no_grad():\n",
" pred = self.model(batch)\n",
" pred_max = torch.cat([torch.argmax(p, dim = 1).view(-1,1) for p in pred], dim = 1)\n",
" pred_max_decoded = self.decode(pred_max)\n",
" preds_acc.extend(pred_max_decoded)\n",
" labels_acc.extend([*batch.y])\n",
" metrics = self.ogb_eval({'seq_pred': preds_acc, 'seq_ref': labels_acc})\n",
" metrics['acc'] = Eval.compute_accuracy(preds_acc, labels_acc)\n",
" loaders_metrics.append(metrics)\n",
" return loaders_metrics\n",
"\n",
" @staticmethod\n",
" def compute_accuracy(seq_pred, seq_ref):\n",
" acc = []\n",
" for l, p in zip(seq_ref, seq_pred):\n",
" label = set(l)\n",
" prediction = set(p)\n",
" n = len(label)\n",
" true_positive = len(label.intersection(prediction))\n",
" false_positive = len(prediction - label)\n",
" false_negative = len(label - prediction)\n",
" true_negative = max(n - (true_positive + false_positive + false_negative), 0)\n",
" acc.append((true_positive + true_negative) / n)\n",
" return np.average(acc)\n",
"\n",
"model_evaluator = Eval(model, [valid_loader], evaluator, i2v, PAD, cfg)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "k4R6uvBBSeFc"
},
"source": [
"# 4. Training\n",
"\n",
"Finally let's train our network to see how well it performs on the training as well as test sets.\n",
"\n",
"_Note: Due to the size of the dataset (~450k graphs), the expected training time for each iteration is about 70 minutes on a standard GPU._"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "GEwQQONSSg6t",
"outputId": "25236f72-fbc4-4e08-a8bc-2f1fe4d69474"
},
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [07:23<00:00, 7.18it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.05it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 01, Loss: 25.8113, Accuracy: 0.0128, F1: 0.0176 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:23<00:00, 8.32it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.58it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 02, Loss: 3.8183, Accuracy: 0.0346, F1: 0.0478 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:23<00:00, 8.32it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.09it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 03, Loss: 3.7375, Accuracy: 0.0306, F1: 0.0422 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:20<00:00, 8.38it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.85it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 04, Loss: 3.7877, Accuracy: 0.0326, F1: 0.0431 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:24<00:00, 8.30it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.23it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 05, Loss: 3.8740, Accuracy: 0.0300, F1: 0.0400 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:17<00:00, 8.44it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.09it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 06, Loss: 3.7172, Accuracy: 0.0369, F1: 0.0499 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:17<00:00, 8.45it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.13it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 07, Loss: 3.6980, Accuracy: 0.0259, F1: 0.0345 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:19<00:00, 8.39it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.37it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 08, Loss: 3.5636, Accuracy: 0.0347, F1: 0.0458 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:15<00:00, 8.49it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.43it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 09, Loss: 3.4992, Accuracy: 0.0225, F1: 0.0289 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:17<00:00, 8.45it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.36it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 10, Loss: 3.6468, Accuracy: 0.0338, F1: 0.0420 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:15<00:00, 8.50it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.25it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 11, Loss: 3.4158, Accuracy: 0.0428, F1: 0.0488 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:19<00:00, 8.41it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.38it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 12, Loss: 3.5234, Accuracy: 0.0438, F1: 0.0560 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:19<00:00, 8.39it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.81it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 13, Loss: 3.4410, Accuracy: 0.0471, F1: 0.0596 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:22<00:00, 8.33it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.12it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 14, Loss: 3.3213, Accuracy: 0.0478, F1: 0.0569 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:20<00:00, 8.37it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.68it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 15, Loss: 3.3446, Accuracy: 0.0487, F1: 0.0619 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:15<00:00, 8.48it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.85it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 16, Loss: 3.4295, Accuracy: 0.0461, F1: 0.0562 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:18<00:00, 8.42it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.28it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 17, Loss: 3.2419, Accuracy: 0.0523, F1: 0.0603 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:20<00:00, 8.37it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.65it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 18, Loss: 3.2632, Accuracy: 0.0540, F1: 0.0641 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:22<00:00, 8.33it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.11it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 19, Loss: 3.3026, Accuracy: 0.0603, F1: 0.0707 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:21<00:00, 8.36it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.19it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 20, Loss: 3.2678, Accuracy: 0.0477, F1: 0.0597 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:20<00:00, 8.39it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.20it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 21, Loss: 3.1666, Accuracy: 0.0586, F1: 0.0711 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:21<00:00, 8.35it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:15<00:00, 11.82it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 22, Loss: 3.1788, Accuracy: 0.0558, F1: 0.0669 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:20<00:00, 8.38it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.45it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 23, Loss: 3.1845, Accuracy: 0.0515, F1: 0.0625 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:17<00:00, 8.44it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.37it/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Saving model: gin_128bz_256emb_0.005lr_25it.net\n",
"\n",
"It: 24, Loss: 3.1288, Accuracy: 0.0703, F1: 0.0822 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Training Batch: 100%|██████████| 3188/3188 [06:16<00:00, 8.46it/s]\n",
"Eval Batch: 100%|██████████| 179/179 [00:14<00:00, 12.37it/s]"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"It: 25, Loss: 3.0778, Accuracy: 0.0602, F1: 0.0735 \n",
"------------------------------------------------------------\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"\n"
]
}
],
"source": [
"# store metrics for each iteration\n",
"history = { 'train_loss': [], 'val_metrics': [], 'val_acc': [], 'val_f1': [] }\n",
"\n",
"for it in range(cfg.max_iter):\n",
" # place model in training mode\n",
" model.train()\n",
" train_loss = .0\n",
" # iterate through batches defined in the dataloader\n",
" for batch_id, batch in enumerate(tqdm(train_loader, desc = 'Training Batch')):\n",
" optimizer.zero_grad()\n",
" batch = batch.to(cfg.device)\n",
" pred = model(batch)\n",
" curr_loss = .0\n",
" for i in range(len(pred)):\n",
" curr_loss += ceLoss(pred[i].to(torch.float32), batch.y_tensor[:,i])\n",
" curr_loss = curr_loss / len(pred)\n",
" \n",
" curr_loss.backward()\n",
" optimizer.step()\n",
" train_loss += curr_loss.item()\n",
" avg_loss = train_loss / (batch_id + 1)\n",
" history['train_loss'].append(avg_loss)\n",
"\n",
" # place model in evaluation mode (ignore gradient tracking)\n",
" model.eval()\n",
" \n",
" # evaluate model based on validation and test sets\n",
" val_metrics = model_evaluator.eval()[0] # dataset.eval_metric\n",
" history['val_metrics'].append(val_metrics)\n",
" history['val_acc'].append(val_metrics['acc'])\n",
" history['val_f1'].append(val_metrics['F1'])\n",
"\n",
" # determine best model based on validation accuracy\n",
" best_it = np.argmax(np.array(history['val_f1']))\n",
" if best_it == it:\n",
" model.save()\n",
"\n",
" print('')\n",
" print(f'It: {(it+1):02d}, '\n",
" f'Loss: {avg_loss:.4f}, '\n",
" f'Accuracy: {val_metrics[\"acc\"]:.4f}, '\n",
" f'F1: {val_metrics[\"F1\"]:.4f} ')\n",
" print('-'*60)\n",
"\n",
" with open(cfg.history_write_path, 'w') as f:\n",
" json.dump(history, f)"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "SAnBazBFWx0K"
},
"source": [
"# 5. Results\n",
"\n",
"Plot the training loss with validation/test accuracy to assess model performance and fine-tune hyperparameters."
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"id": "WMEhrYG5Xs8C",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 580
},
"outputId": "4ad6ac17-87d6-40db-9280-4ac5d74119ed"
},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
],
"image/png": "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bm4ub731FgC33XYbGRkZfOc73+kY39LSQlJS93/WBQUFFBQU9LmM1av7/CdWHyo/+MEPuPXWW6PdjCOSl5fHmDFjeOWVV1i8ePGgzz/m9uDbA75CAS8CwOWXX87XvvY1TjnlFG666SbeeOMNTj31VD7ykY+waNEiNm/eDBy+R33bbbfxpS99iSVLljB16lR+8YtfdMwvIyOjY/olS5Zw4YUXctxxx3HJJZfQXn322Wef5bjjjuOkk07iuuuuG9Ce+mOPPcacOXOYPXs2N998MwCtra1cfvnlzJ49mzlz5nDnnXcC8Itf/ILjjz+euXPnctFFF3WZV3V1Ne+88w4nnnhil3FFRUV87GMfY+7cuZx11lns3LkTgG3btrFw4ULmzJnDrbfe2rG+kWpraznvvPM48cQTmT17Nk888QQAa9asYdGiRZx44oksWLCA6upqioqKOO2005g/fz7z58/v9guytbWVG2+8kZNPPpm5c+fyy1/+smPcZz7zGR599NF+//4GIub24HPCyQAcrFcfvETXv/5pPRv2VA3qPI8fO4LvffqEAb+vuLiY1atXk5iYSFVVFatWrSIpKYkXXniBW265haeeeqrLezZt2sSKFSuorq5m5syZXH311V2uuX7zzTdZv349Y8eOZfHixbzyyisUFBRw1VVXsXLlSqZMmcLFF1/c73bu2bOHm2++mbVr15KTk8M555zD8uXLmTBhArt37+a9994D4ODBgwDccccdfPDBB6SkpHS8FqmwsLDHbpBrr72WZcuWsWzZMh588EGuu+46li9fzvXXX8/111/PxRdfzH33dfnXwgA899xzjB07lmeeeQaAyspKmpqa+MIXvsATTzzBySefTFVVFWlpaYwePZrnn3+e1NRUtmzZwsUXX9ylztYDDzxAVlYWa9asobGxkcWLF3POOecwZcoUCgoKhuwIJOb24LPSvA/gQe3Bi3RYunQpiYmJgBdGS5cuZfbs2dxwww2sX7++2/ecd955pKSkMGrUKEaPHs3+/fu7TLNgwQLGjx9PQkIC8+bNo6ioiE2bNjF16tSO67MHEvBr1qxhyZIl5OXlkZSUxCWXXMLKlSuZOnUq27dv59prr+W5555jxIgRAMydO5dLLrmERx55pNuup71795KX131hxVdffZUvfvGLAFx22WW8/PLLHa8vXboUoGN8Z3PmzOH555/n5ptvZtWqVWRlZbF582bGjBnDySefDMCIESNISkqiubmZr3zlK8yZM4elS5eyYcOGLvP761//ykMPPcS8efM45ZRTKCsrY8uWLQCMHj2aPXv29Pt3OBAxtwefGkokNZRAZb0CXqLrSPa0h0p6enrH8He/+13OPPNM/vCHP1BUVMSSJUu6fU9KSkrHcGJiIi0tLUc0zWDIycnh7bff5i9/+Qv33XcfTz75JA8++CDPPPMMK1eu5E9/+hO3334777777mFBn5aWNiR3FR977LGsW7eOZ599lltvvZWzzjqLz372s91Oe+edd5Kfn8/bb79NW1sbqampXaZxznHXXXfxiU98osu4hoYG0tLSBn0dIAb34AGy05J1maRIDyorKxk3bhzgXVUy2GbOnMn27dspKioC6Oif7o8FCxbw97//ndLSUlpbW3nsscc444wzKC0tpa2tjc997nN8//vfZ926dbS1tbFr1y7OPPNMfvSjH1FZWUlNTc1h85s1axZbt27tdlmLFi3i8ccfB+DRRx/ltNNOA2DhwoUdXVbt4zvbs2cP4XCYSy+9lBtvvJF169Yxc+ZM9u7dy5o1awCv/7+lpYXKykrGjBlDQkICDz/8MK2trV3m94lPfIJ7772X5mZvx/T999+ntra2Y7ivq22OVMztwYN3ovWg9uBFunXTTTexbNkyvv/973PeeecN+vzT0tK45557OPfcc0lPT+/osujOiy++yPjx4zue/+53v+OOO+7gzDPPxDnHeeedxwUXXMDbb7/NFVdcQVtbGwA//OEPaW1t5dJLL6WyshLnHNdddx3Z2dmHzf+4446jsrKS6upqMjMzDxt31113ccUVV/DjH/+YvLw8fv3rXwPw85//nEsvvZTbb7+dc889l6ysrC7tfvfdd7nxxhtJSEggFApx7733kpyczBNPPMG1115LfX09aWlpvPDCC3z961/nc5/7HA899FDH76SzK6+8kqKiIubPn49zjry8PJYvXw7AihUrhmQ7QRT/J2tBQYE70n/4cdH9r9LWBk9+7dRBbpVI7zZu3MisWbOi3Yyoq6mpISMjA+cc11xzDTNmzOCGG26ISlvuvPNOMjMzufLKK/s1fV1dHWlpaZgZjz/+OI899hhPP/30ELeyZ6effjpPP/00OTk5XcZ193kzs7XOub6vdyWGu2h0FY1I9PzqV79i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\n"
},
"metadata": {
"needs_background": "light"
}
},
{
"output_type": "display_data",
"data": {
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
],
"image/png": "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Fxr2WbduHRMmTCApKYl58+ZhsVgYMWIE9RcBFouFuLg4uuJFgVKN1dRZOHjKmvjf+yqbrlgB2ZnhnNmcP/nyQC6c0Lp+mywR8QPCgUK79Uto4mq/tX6+OpUDtk9TVxndvydPXjOmyfV9+vRhypQprF27lkWLFrFixQpuuukmRIRnnnmGPn36UFdXx7x589i3bx9jx451eJxdu3axYsUK9uzZQ21tLRMmTGDixIkAXH/99dxzzz0A/PSnP+Wll17iwQcfZOHChVx99dXceOON5x2rsrKSZcuW8emnnxIfH8/tt9/O3//+dx555BEAIiMj2b17N3/729949tlnefHFFy+I580332Tp0qUsWrSIH//4x9TU1ODv789DDz3ErFmzWLlyJXV1dZSVlZGamsovf/lLvvjiCyIjIykqKmrx97p7925SUlIahlu+/PLL9OnTh3PnzjF58mRuuOEGLBYL99xzD1u2bGHo0KEUFRXh4+PDrbfeyuuvv84jjzzCJ598QlJSEl3xokCpxjLyyqiutXDxsAi+PFrIvqxikgb1cndY53Hmin8nMMI2EXUA1iS+qtE2q4A7bI9vBDbUTxAhIj7ATXTT9v169s099s08b7/9NhMmTGD8+PGkpqae1yzT2NatW7nuuusICQmhZ8+eLFy4sGFdSkoKM2bMIDExkddff53U1OanKk1LS2Po0KHEx8cDcMcdd7Bly5aG9ddffz0AEydOJDMz84L9q6urWbNmDddeey09e/Zk6tSpDf0YGzZsaOi/8PX1JTw8nA0bNrB48WIiIyMB64dhS6ZMmXLeGPs///nPJCUlMW3aNE6ePMnhw4fZtm0bM2fObNiu/rh33nknr776KmD9wPjWt77V4usp1RWkZBcD8MP5Iwnw8+G93VlujuhCLV7xG2NqReQBrBMw+wIvG2NSReRpINkYswp4CVguIhlYJ3ZeYneImcBJY8xRVwTc3JV5R1q0aBGPPvoou3fvpqKigokTJ3Ls2DGeffZZdu7cSe/evVm2bFmbx5UvW7aM999/n6SkJP7zn/+wadOmdsUbGBgIWBO3ozb29evXc/bsWRITEwGoqKggODiYq6++ulWv4+fn19AxbLFYGprDAHr06NHweNOmTXzyySd8+eWXhISEMHv27GZ/V4MGDSImJoYNGzawY8cOXn/99VbFpZS7pOaUEBLgy9iB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\n"
},
"metadata": {
"needs_background": "light"
}
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"\n",
"Best model (it: 24): Loss: 3.1288, Accuracy: 0.0703, F1: 0.0822, Precision: 0.1112, Recall: 0.0703 \n"
]
}
],
"source": [
"plt.title(f'Training {cfg.max_iter} iterations on {dataset.name}')\n",
"plt.plot(np.log(history['train_loss']), label=\"Training Loss (log scale)\")\n",
"plt.legend()\n",
"plt.show()\n",
"\n",
"plt.title(f'Evaluation on {dataset.name}')\n",
"plt.plot(history['val_acc'], label=\"Validation Accuracy\")\n",
"plt.legend()\n",
"plt.show()\n",
"\n",
"best_it = np.argmax(np.array(history['val_f1']))\n",
"print('')\n",
"print(f'Best model (it: {best_it+1}): '\n",
" f'Loss: {history[\"train_loss\"][best_it]:.4f}, '\n",
" f'Accuracy: {history[\"val_acc\"][best_it]:.4f}, '\n",
" f'F1: {history[\"val_f1\"][best_it]:.4f}, '\n",
" f'Precision: {history[\"val_metrics\"][best_it][\"precision\"]:.4f}, '\n",
" f'Recall: {history[\"val_metrics\"][best_it][\"recall\"]:.4f} ')"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "S49PVlqe9IIs"
},
"source": [
"# 6. Summary\n",
"\n",
"You have learned how graphs can be batched together for better GPU utilization, and how to apply readout layers for obtaining graph embeddings rather than node embeddings.\n",
"\n",
"We performed the experiments using GIN architecture on a Python code dataset. We highlight how providing structured information from the AST to the Message Passing framework results in better performance.\n",
"\n",
"Finally, you can refer to the [OGB leaderboard](https://ogb.stanford.edu/docs/leader_graphprop/#ogbg-code2) to help you implement more complex GNN architectures and extend this experiment."
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"gpuClass": "standard",
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU"
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment