MNIST-Superpixel 数据集
Understanding Graph Neural Network with hands-on example| Part-2 Medium的链接https://medium.com/@rtsrumi07/understanding-graph-neural-network-with-hands-on-example-part-2-139a691ebeac
colab链接:https://colab.research.google.com/drive/1EMgPuFaD-xpboG_ZwZcytnlOlr39rakd

主要是了解GCN 模型在实际数据集上的使用,所以代码不是完整的

安装PyG

限定torch版本

  1. # Enforce pytorch version 1.6.0
  2. import torch
  3. if torch.__version__ != '1.6.0':
  4. !pip uninstall torch -y
  5. !pip uninstall torchvision -y
  6. !pip install torch==1.6.0
  7. !pip install torchvision==0.7.0
  8. # Check pytorch version and make sure you use a GPU Kernel
  9. !python -c "import torch; print(torch.__version__)"
  10. !python -c "import torch; print(torch.version.cuda)"
  11. !python --version
  12. !nvidia-smi

相应的PyG GPU版本

  1. # If something breaks in the notebook it is probably related to a mismatch between the Python version, CUDA or torch
  2. import torch
  3. pytorch_version = f"torch-{torch.__version__}+cu{torch.version.cuda.replace('.', '')}.html"
  4. !pip install --no-index torch-scatter -f https://pytorch-geometric.com/whl/$pytorch_version
  5. !pip install --no-index torch-sparse -f https://pytorch-geometric.com/whl/$pytorch_version
  6. !pip install --no-index torch-cluster -f https://pytorch-geometric.com/whl/$pytorch_version
  7. !pip install --no-index torch-spline-conv -f https://pytorch-geometric.com/whl/$pytorch_version
  8. !pip install torch-geometric

加载数据集

  1. from torch_geometric.datasets import MNISTSuperpixels
  2. from torch_geometric.data import DataLoader
  3. # Load the MNISTSuperpixel dataset
  4. data = MNISTSuperpixels(root=".")
  5. data

模型

我们应用三个卷积层,这意味着我们学习了3个邻居的信息。之后,我们应用一个pooling层来结合各个节点的信息,因为我们要进行graph级预测。

请记住,不同的学习问题(节点、边缘或图的预测)需要不同的GNN架构。

例如,对于节点级的预测,你会经常遇到掩码mask。另一方面,对于图层面的预测,你需要结合节点嵌入。

在层之间,我使用了tanh激活函数来创建对比度。之后,我通过对节点嵌入使用池化操作将节点嵌入合并为单个嵌入向量。在这种情况下,对节点状态执行了平均值和最大值运算(global_mean_pool , global_max_pool )。这样做的原因是我想在图形级别进行预测,因此需要复合嵌入。在节点级别处理预测时。
PyTorch Geometric 中有多种替代池化层可用,但我想在这里保持简单并利用这种均值和最大值的组合。
最后,线性输出层确保我收到一个连续且无界的输出值。扁平向量用作此函数的输入。

  1. import torch
  2. from torch.nn import Linear
  3. import torch.nn.functional as F
  4. from torch_geometric.nn import GCNConv, TopKPooling, global_mean_pool
  5. from torch_geometric.nn import global_mean_pool as gap, global_max_pool as gmp
  6. embedding_size = 64
  7. class GCN(torch.nn.Module):
  8. def __init__(self):
  9. # Init parent
  10. super(GCN, self).__init__()
  11. torch.manual_seed(42)
  12. # GCN layers
  13. self.initial_conv = GCNConv(data.num_features, embedding_size)
  14. self.conv1 = GCNConv(embedding_size, embedding_size)
  15. self.conv2 = GCNConv(embedding_size, embedding_size)
  16. self.conv3 = GCNConv(embedding_size, embedding_size)
  17. # Output layer
  18. self.out = Linear(embedding_size*2, data.num_classes)
  19. def forward(self, x, edge_index, batch_index):
  20. # First Conv layer
  21. hidden = self.initial_conv(x, edge_index)
  22. hidden = F.tanh(hidden)
  23. # Other Conv layers
  24. hidden = self.conv1(hidden, edge_index)
  25. hidden = F.tanh(hidden)
  26. hidden = self.conv2(hidden, edge_index)
  27. hidden = F.tanh(hidden)
  28. hidden = self.conv3(hidden, edge_index)
  29. hidden = F.tanh(hidden)
  30. # Global Pooling (stack different aggregations)
  31. hidden = torch.cat([gmp(hidden, batch_index),
  32. gap(hidden, batch_index)], dim=1)
  33. # Apply a final (linear) classifier.
  34. out = self.out(hidden)
  35. return out, hidden
  36. model = GCN()
  37. print(model)
  38. print("Number of parameters: ", sum(p.numel() for p in model.parameters()))
  1. GCN(
  2. (initial_conv): GCNConv(1, 64)
  3. (conv1): GCNConv(64, 64)
  4. (conv2): GCNConv(64, 64)
  5. (conv3): GCNConv(64, 64)
  6. (out): Linear(in_features=128, out_features=10, bias=True)
  7. )
  8. Number of parameters: 13898

打印层的模型摘要后,可以看到每个层中有 10 个特征被馈送到消息传递层,产生大小为 64 的隐藏状态,最终使用均值和最大值操作组合。嵌入大小 (64) 的选择是一个超参数,取决于数据集中图形的大小等因素。
最后,这个模型有 13898 个参数,这似乎是合理的,因为我有 9000 个样本。出于演示目的,使用了总数据集的 15%。

训练

批次大小为 64(这意味着我们的批次中有 64 个图)并使用 shuffle 选项在批次中分布图。主数据集的 15% 的前 80% 将用于训练数据,主数据集的 15% 的其余 20% 将用于测试数据。在我的分析中,交叉熵用作损失度量。选择 Adam(自适应运动估计)作为优化器,初始学习率为 0.0007。
然后是简单的迭代 Data Loader 加载的每批数据;幸运的是,这个函数为我们处理了一切,就像它在 train 函数中所做的一样。这个训练函数被命名为 # epochs 次,在这个例子中是 500。

  1. from torch_geometric.data import DataLoader
  2. import warnings
  3. warnings.filterwarnings("ignore")
  4. # Cross EntrophyLoss
  5. loss_fn = torch.nn.CrossEntropyLoss()
  6. optimizer = torch.optim.Adam(model.parameters(), lr=0.0007)
  7. # Use GPU for training
  8. device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
  9. model = model.to(device)
  10. # Wrap data in a data loader
  11. data_size = len(data)
  12. NUM_GRAPHS_PER_BATCH = 64
  13. loader = DataLoader(data[:int(data_size * 0.8)],
  14. batch_size=NUM_GRAPHS_PER_BATCH, shuffle=True)
  15. test_loader = DataLoader(data[int(data_size * 0.8):],
  16. batch_size=NUM_GRAPHS_PER_BATCH, shuffle=True)
  17. def train(data):
  18. # Enumerate over the data
  19. for batch in loader:
  20. # Use GPU
  21. batch.to(device)
  22. # Reset gradients
  23. optimizer.zero_grad()
  24. # Passing the node features and the connection info
  25. pred, embedding = model(batch.x.float(), batch.edge_index, batch.batch)
  26. # Calculating the loss and gradients
  27. loss = torch.sqrt(loss_fn(pred, batch.y))
  28. loss.backward()
  29. # Update using the gradients
  30. optimizer.step()
  31. return loss, embedding
  32. print("Starting training...")
  33. losses = []
  34. for epoch in range(500):
  35. loss, h = train(data)
  36. losses.append(loss)
  37. if epoch % 10 == 0:
  38. print(f"Epoch {epoch} | Train Loss {loss}")

train loss

  1. # Visualize learning (training loss)
  2. import seaborn as sns
  3. losses_float = [float(loss.cpu().detach().numpy()) for loss in losses]
  4. loss_indices = [i for i,l in enumerate(losses_float)]
  5. plt = sns.lineplot(loss_indices, losses_float)
  6. plt

image.png

预测

  1. import pandas as pd
  2. test_batch = next(iter(test_loader))
  3. with torch.no_grad():
  4. test_batch.to(device)
  5. pred, embed = model(test_batch.x.float(), test_batch.edge_index, test_batch.batch)
  6. pred=torch.argmax(pred,dim=1)
  7. print(test_batch.y[0])#Actual REsult
  8. print(pred[0])#Predicted Result
  9. >>>
  10. tensor(1, device='cuda:0')
  11. tensor(1, device='cuda:0')
  1. import torch
  2. import networkx as nx
  3. import matplotlib.pyplot as plt
  4. def visualize(h, color, epoch=None, loss=None):
  5. plt.figure(figsize=(7,7))
  6. plt.xticks([])
  7. plt.yticks([])
  8. if torch.is_tensor(h):
  9. h = h.detach().cpu().numpy()
  10. plt.scatter(h[:, 0], h[:, 1], s=140, c=color, cmap="Set2")
  11. if epoch is not None and loss is not None:
  12. plt.xlabel(f'Epoch: {epoch}, Loss: {loss.item():.4f}', fontsize=16)
  13. else:
  14. nx.draw_networkx(G, pos=nx.spring_layout(G, seed=42), with_labels=False,
  15. node_color=color, cmap="Set2")
  16. plt.show()
  1. dataset=data[1]
  2. print(f'Is undirected: {dataset.is_undirected()}')
  3. from torch_geometric.utils import to_networkx
  4. G = to_networkx(dataset, to_undirected=True)
  5. visualize(G, "yellow")
  6. visualize(G, "red")

image.png
image.png