Introduction by Example¶

We in short introduce the fundamental concepts of PyG through self-contained examples. At its core, PyG provides the following main features:

  • Information Treatment of Graphs

  • Common Bench mark Datasets

  • Mini-batches

  • Data Transforms

  • Learning Methods on Graphs

Data Manipulation of Graphs¶

A chart is put-upon to model pairwise relations (edges) between objects (nodes). A unique graph in PyG is delineate aside an instance of torch_geometric.data.Data , which holds the succeeding attributes by default option:

  • data.x : Node feature intercellular substance with shape [num_nodes, num_node_features]

  • data.edge_index : Graph connectivity in COO format with shape [2, num_edges] and type torch.long

  • data.edge_attr : Edge boast matrix with shape [num_edges, num_edge_features]

  • data.y : Target to coach against (may have arbitrary shape), e.g., lymph gland-level targets of configuration [num_nodes, *] or graph-level targets of mould [1, *]

  • data.pos : Node position matrix with shape [num_nodes, num_dimensions]

None of these attributes are required. In fact, the Data objective is non straight-grained restricted to these attributes. We stool, e.g., widen it past data.face to save the connectivity of triangles from a 3D mesh in a tensor with shape [3, num_faces] and type torch.long .

Note

PyTorch and torchvision define an example as a tuple of an image and a target. We omit this notation in PyG to permit for various information structures in a clean and understandable way.

We show a swordlike case of an unweighted and undirected graphical record with three nodes and four edges. For each one node contains precisely one feature:

                                    significance                  common mullein                  from                  torch_geometric.information                  import                  Information                  edge_index                  =                  torch                  .                  tensor                  ([[                  0                  ,                  1                  ,                  1                  ,                  2                  ],                  [                  1                  ,                  0                  ,                  2                  ,                  1                  ]],                  dtype                  =                  torch                  .                  long                  )                  x                  =                  woolly mullein                  .                  tensor                  ([[                  -                  1                  ],                  [                  0                  ],                  [                  1                  ]],                  dtype                  =                  flannel mullein                  .                  swim                  )                  data                  =                  Information                  (                  x                  =                  x                  ,                  edge_index                  =                  edge_index                  )                  >>>                  Data                  (                  edge_index                  =                  [                  2                  ,                  4                  ],                  x                  =                  [                  3                  ,                  1                  ])                

../_images/graph.svg

Note that edge_index , i.e. the tensor defining the source and aim nodes of complete edges, is not a listing of index tuples. If you want to write your indices this way, you should transpose and call conterminous on it earlier passing them to the data constructor:

                                    import                  blowlamp                  from                  torch_geometric.data                  import                  Data                  edge_index                  =                  torch                  .                  tensor                  ([[                  0                  ,                  1                  ],                  [                  1                  ,                  0                  ],                  [                  1                  ,                  2                  ],                  [                  2                  ,                  1                  ]],                  dtype                  =                  torch                  .                  long                  )                  x                  =                  torch                  .                  tensor                  ([[                  -                  1                  ],                  [                  0                  ],                  [                  1                  ]],                  dtype                  =                  flannel mullein                  .                  float                  )                  data                  =                  Information                  (                  x                  =                  x                  ,                  edge_index                  =                  edge_index                  .                  t                  ()                  .                  contiguous                  ())                  >>>                  Information                  (                  edge_index                  =                  [                  2                  ,                  4                  ],                  x                  =                  [                  3                  ,                  1                  ])                

Although the graph has lone two edges, we need to define quaternary power tuples to account for some directions of a edge.

Note

You can print out your data object anytime and receive a short-circuit information about its attributes and their shapes.

Besides holding a number of node-level, edge-level or graph-level attributes, Data provides a number of useful utility functions, e.g.:

                                    print                  (                  data                  .                  keys                  )                  >>>                  [                  'x'                  ,                  'edge_index'                  ]                  print                  (                  data                  [                  'x'                  ])                  >>>                  tensor                  ([[                  -                  1.0                  ],                  [                  0.0                  ],                  [                  1.0                  ]])                  for                  key                  ,                  item                  in                  information                  :                  print                  (                  f                  '                  {                  key                  }                                      found in data'                  )                  >>>                  x                  found                  in                  data                  >>>                  edge_index                  found                  in                  data                  'edge_attr'                  in                  data                  >>>                  False                  data                  .                  num_nodes                  >>>                  3                  data                  .                  num_edges                  >>>                  4                  data                  .                  num_node_features                  >>>                  1                  information                  .                  has_isolated_nodes                  ()                  >>>                  False                  data                  .                  has_self_loops                  ()                  >>>                  False                  data                  .                  is_directed                  ()                  >>>                  False                  # Transfer data targe to GPU.                  twist                  =                  torch                  .                  device                  (                  'cuda'                  )                  data                  =                  data                  .                  to                  (                  device                  )                

You can find a complete list of all methods at torch_geometric.data.Data .

Common Benchmark Datasets¶

PyG contains a oversize number of frequent benchmark datasets, e.g., all Planetoid datasets (Cora, Citeseer, Pubmed), every graph classification datasets from http://graphkernels.cs.tu-dortmund.de and their cleaned versions, the QM7 and QM9 dataset, and a handful of 3D lock/point cloud datasets like FAUST, ModelNet10/40 and ShapeNet.

Initializing a dataset is straightforward. An initialization of a dataset will mechanically download its raw files and march them to the antecedently delineate Information format. E.g., to consignment the ENZYMES dataset (consisting of 600 graphs within 6 classes), type:

                                    from                  torch_geometric.datasets                  import                  TUDataset                  dataset                  =                  TUDataset                  (                  root                  =                  '/tmp/ENZYMES'                  ,                  diagnose                  =                  'ENZYMES'                  )                  >>>                  ENZYMES                  (                  600                  )                  len                  (                  dataset                  )                  >>>                  600                  dataset                  .                  num_classes                  >>>                  6                  dataset                  .                  num_node_features                  >>>                  3                

We now have access to all 600 graphs in the dataset:

                                    data                  =                  dataset                  [                  0                  ]                  >>>                  Data                  (                  edge_index                  =                  [                  2                  ,                  168                  ],                  x                  =                  [                  37                  ,                  3                  ],                  y                  =                  [                  1                  ])                  information                  .                  is_undirected                  ()                  >>>                  Faithful                

We can see that the first graph in the dataset contains 37 nodes, each one having 3 features. There are 168/2 = 84 undirected edges and the graph is assigned to exactly one class. In improver, the data physical object is holding exactly one graph-level target.

We can even use slices, long or bool tensors to dissever the dataset. E.g., to create a 90/10 train/quiz split, type:

                                    train_dataset                  =                  dataset                  [:                  540                  ]                  >>>                  ENZYMES                  (                  540                  )                  test_dataset                  =                  dataset                  [                  540                  :]                  >>>                  ENZYMES                  (                  60                  )                

If you are unsure whether the dataset is already shuffled before you split, you can randomly permutate it by running:

                                    dataset                  =                  dataset                  .                  shuffle                  ()                  >>>                  ENZYMES                  (                  600                  )                

This is equivalent of doing:

                                    perm                  =                  torch                  .                  randperm                  (                  len                  (                  dataset                  ))                  dataset                  =                  dataset                  [                  perm                  ]                  >>                  ENZYMES                  (                  600                  )                

Let's attempt another one! Let's download Cora, the criterial benchmark dataset for semi-supervised graph node classification:

                                    from                  torch_geometric.datasets                  import                  Planetoid                  dataset                  =                  Planetoid                  (                  root                  =                  '/tmp/Cora'                  ,                  figure                  =                  'Cora'                  )                  >>>                  Cora                  ()                  len                  (                  dataset                  )                  >>>                  1                  dataset                  .                  num_classes                  >>>                  7                  dataset                  .                  num_node_features                  >>>                  1433                

Hera, the dataset contains only a single, undirected citation chart:

                                    data                  =                  dataset                  [                  0                  ]                  >>>                  Data                  (                  edge_index                  =                  [                  2                  ,                  10556                  ],                  test_mask                  =                  [                  2708                  ],                  train_mask                  =                  [                  2708                  ],                  val_mask                  =                  [                  2708                  ],                  x                  =                  [                  2708                  ,                  1433                  ],                  y                  =                  [                  2708                  ])                  data                  .                  is_undirected                  ()                  >>>                  True                  data                  .                  train_mask                  .                  sum                  ()                  .                  item                  ()                  >>>                  140                  data                  .                  val_mask                  .                  tot                  ()                  .                  item                  ()                  >>>                  500                  information                  .                  test_mask                  .                  sum                  ()                  .                  point                  ()                  >>>                  1000                

This time, the Data objects holds a label for apiece node, and extra node-level attributes: train_mask , val_mask and test_mask , where

  • train_mask denotes against which nodes to train (140 nodes),

  • val_mask denotes which nodes to use for validation, e.g., to do ahead of time stopping (500 nodes),

  • test_mask denotes against which nodes to test (1000 nodes).

Mini-batches¶

Neural networks are unremarkably trained in a batch-wise fashion. PyG achieves parallelization over a mini-mint by creating sparse block solidus adjacency matrices (defined away edge_index ) and concatenating feature film and target matrices in the node dimension. This composition allows differing turn of nodes and edges over examples in one batch:

\[\begin{split}\mathbf{A} = \begin{bmatrix} \mathbf{A}_1 & & \\ & \ddots &A; \\ & & \mathbf{A}_n \end{bmatrix}, \qquad \mathbf{X} = \begin{bmatrix} \mathbf{X}_1 \\ \vdots \\ \mathbf{X}_n \stop{bmatrix}, \qquad \mathbf{Y} = \begin{bmatrix} \mathbf{Y}_1 \\ \vdots \\ \mathbf{Y}_n \end{bmatrix}\end{split}\]

PyG contains its own torch_geometric.loader.DataLoader , which already takes handle of this concatenation process. Let's learn about it in an example:

                                    from                  torch_geometric.datasets                  import                  TUDataset                  from                  torch_geometric.loader                  import                  DataLoader                  dataset                  =                  TUDataset                  (                  settle                  =                  '/tmp/ENZYMES'                  ,                  name                  =                  'ENZYMES'                  ,                  use_node_attr                  =                  Avowedly                  )                  loader                  =                  DataLoader                  (                  dataset                  ,                  batch_size                  =                  32                  ,                  shambling                  =                  True                  )                  for                  batch                  in                  loader                  :                  batch                  >>>                  DataBatch                  (                  batch                  =                  [                  1082                  ],                  edge_index                  =                  [                  2                  ,                  4066                  ],                  x                  =                  [                  1082                  ,                  21                  ],                  y                  =                  [                  32                  ])                  batch                  .                  num_graphs                  >>>                  32                

torch_geometric.information.Batch inherits from torch_geometric.information.Data and contains an additional property called batch .

batch is a chromatography column vector which maps each node to its single graphical record in the batch:

\[\mathrm{whole lot} = {\lead off{bmatrix} 0 & \cdots & 0 &ere; 1 & \cdots &A; n - 2 & n -1 &A; \cdots & n - 1 \end{bmatrix}}^{\top}\]

You privy use it to, e.g., average node features in the lymph gland dimension for each graph on an individual basi:

                                    from                  torch_scatter                  importation                  scatter_mean                  from                  torch_geometric.datasets                  implication                  TUDataset                  from                  torch_geometric.loader                  import                  DataLoader                  dataset                  =                  TUDataset                  (                  origin                  =                  '/tmp/ENZYMES'                  ,                  name                  =                  'ENZYMES'                  ,                  use_node_attr                  =                  True                  )                  loader                  =                  DataLoader                  (                  dataset                  ,                  batch_size                  =                  32                  ,                  shamble                  =                  True                  )                  for                  information                  in                  longshoreman                  :                  information                  >>>                  DataBatch                  (                  batch                  =                  [                  1082                  ],                  edge_index                  =                  [                  2                  ,                  4066                  ],                  x                  =                  [                  1082                  ,                  21                  ],                  y                  =                  [                  32                  ])                  data                  .                  num_graphs                  >>>                  32                  x                  =                  scatter_mean                  (                  information                  .                  x                  ,                  data                  .                  batch                  ,                  dim                  =                  0                  )                  x                  .                  sized                  ()                  >>>                  torch                  .                  Size                  ([                  32                  ,                  21                  ])                

You can se more about the internal batching subroutine of PyG, e.g., how to qualify its doings, here. For certification of scatter operations, we refer the interested reader to the torch-dot documentation.

Information Transforms¶

Transforms are a common way in torchvision to transform images and perform augmentation. PyG comes with its own transforms, which expect a Data object as input and return a unaccustomed transformed Information object. Transforms can be chained together using torch_geometric.transforms.Compose and are applied before delivery a processed dataset on disk ( pre_transform ) or before accessing a graph in a dataset ( transform ).

Let's consider an example, where we enforce transforms on the ShapeNet dataset (containing 17,000 3D shape point clouds and per point labels from 16 shape categories).

                                    from                  torch_geometric.datasets                  import                  ShapeNet                  dataset                  =                  ShapeNet                  (                  rootle                  =                  '/tmp/ShapeNet'                  ,                  categories                  =                  [                  'Airplane'                  ])                  dataset                  [                  0                  ]                  >>>                  Data                  (                  pos                  =                  [                  2518                  ,                  3                  ],                  y                  =                  [                  2518                  ])                

We can convince the point cloud dataset into a graph dataset aside generating closest neighbor graphs from the point clouds via transforms:

                                    import                  torch_geometric.transforms                  every bit                  T                  from                  torch_geometric.datasets                  import                  ShapeNet                  dataset                  =                  ShapeNet                  (                  root                  =                  '/tmp/ShapeNet'                  ,                  categories                  =                  [                  'Airplane'                  ],                  pre_transform                  =                  T                  .                  KNNGraph                  (                  k                  =                  6                  ))                  dataset                  [                  0                  ]                  >>>                  Information                  (                  edge_index                  =                  [                  2                  ,                  15108                  ],                  pos                  =                  [                  2518                  ,                  3                  ],                  y                  =                  [                  2518                  ])                

Note

We use the pre_transform to change over the information before good it to disk (up to quicker cargo times). Note that the next time the dataset is initialized it will already curb graph edges, even if you do not go across any transmute. If the pre_transform does not match with the one from the already computerized dataset, you will be given a warning.

In accession, we can use the transubstantiate argument to randomly augment a Data object, e.g., translating each node position away a small number:

                                    import                  torch_geometric.transforms                  American Samoa                  T                  from                  torch_geometric.datasets                  spell                  ShapeNet                  dataset                  =                  ShapeNet                  (                  rout                  =                  '/tmp/ShapeNet'                  ,                  categories                  =                  [                  'Airplane'                  ],                  pre_transform                  =                  T                  .                  KNNGraph                  (                  k                  =                  6                  ),                  translate                  =                  T                  .                  RandomTranslate                  (                  0.01                  ))                  dataset                  [                  0                  ]                  >>>                  Information                  (                  edge_index                  =                  [                  2                  ,                  15108                  ],                  pos                  =                  [                  2518                  ,                  3                  ],                  y                  =                  [                  2518                  ])                

You can find a complete list of all implemented transforms at torch_geometric.transforms .

Learning Methods on Graphs¶

Aft learning about information handling, datasets, loader and transforms in PyG, it's time to implement our first graph neural net!

We will use a simple GCN bed and replicate the experiments on the Cora commendation dataset. For a high-level explanation on GCN, make a calculate at its blog post.

We first need to load the Cora dataset:

                                    from                  torch_geometric.datasets                  spell                  Minor planet                  dataset                  =                  Planetoid                  (                  root                  =                  '/tmp/Cora'                  ,                  name                  =                  'Cora'                  )                  >>>                  Cora                  ()                

Note that we do not need to use transforms operating theatre a dataloader. Now let's implement a two-layer GCN:

                                    import                  torch                  import                  torch.nn.usable                  as                  F                  from                  torch_geometric.nn                  import                  GCNConv                  class                  GCN                  (                  great mullein                  .                  nn                  .                  Module                  ):                  def                  __init__                  (                  self                  ):                  super                  ()                  .                  __init__                  ()                  self                  .                  conv1                  =                  GCNConv                  (                  dataset                  .                  num_node_features                  ,                  16                  )                  self                  .                  conv2                  =                  GCNConv                  (                  16                  ,                  dataset                  .                  num_classes                  )                  def                  forward                  (                  self                  ,                  data                  ):                  x                  ,                  edge_index                  =                  data                  .                  x                  ,                  data                  .                  edge_index                  x                  =                  self                  .                  conv1                  (                  x                  ,                  edge_index                  )                  x                  =                  F                  .                  relu                  (                  x                  )                  x                  =                  F                  .                  dropout                  (                  x                  ,                  training                  =                  mortal                  .                  training                  )                  x                  =                  self                  .                  conv2                  (                  x                  ,                  edge_index                  )                  recurrence                  F                  .                  log_softmax                  (                  x                  ,                  dim                  =                  1                  )                

The constructor defines ii GCNConv layers which get called in the forward pass of our network. Note that the non-one-dimensionality is not integrated in the conv calls and hence needs to constitute applied afterwards (something which is consistent accross all operators in PyG). Here, we chose to use ReLU as our intermediate not-linearity and finally output a softmax distribution over the number of classes. Let's train this role model happening the training nodes for 200 epochs:

                                    gimmick                  =                  torch                  .                  gimmick                  (                  'cuda'                  if                  torch                  .                  cuda                  .                  is_available                  ()                  other                  'mainframe'                  )                  model                  =                  GCN                  ()                  .                  to                  (                  device                  )                  data                  =                  dataset                  [                  0                  ]                  .                  to                  (                  device                  )                  optimizer                  =                  torch                  .                  optim                  .                  Adam                  (                  pattern                  .                  parameters                  (),                  Lr                  =                  0.01                  ,                  weight_decay                  =                  5e-4                  )                  model                  .                  train                  ()                  for                  epoch                  in                  grasp                  (                  200                  ):                  optimizer                  .                  zero_grad                  ()                  out                  =                  model                  (                  data                  )                  loss                  =                  F                  .                  nll_loss                  (                  out                  [                  data                  .                  train_mask                  ],                  data                  .                  y                  [                  data                  .                  train_mask                  ])                  loss                  .                  backward                  ()                  optimizer                  .                  step                  ()                

In the end, we can evaluate our model on the test nodes:

                                    model                  .                  eval                  ()                  pred                  =                  poser                  (                  data                  )                  .                  argmax                  (                  dim                  =                  1                  )                  correct                  =                  (                  pred                  [                  information                  .                  test_mask                  ]                  ==                  information                  .                  y                  [                  data                  .                  test_mask                  ])                  .                  sum                  ()                  Air Combat Command                  =                  int                  (                  correct                  )                  /                  int                  (                  information                  .                  test_mask                  .                  sum                  ())                  print                  (                  f                  'Accuracy:                                    {                  acc                  :                  .4f                  }                  '                  )                  >>>                  Accuracy                  :                  0.8150                

This is wholly information technology takes to put through your primary graph neural network. The easiest way to learn more near Graph Neural Networks is to subject field the examples in the examples/ directory and to browse torch_geometric.nn . Happy hacking!