The Beauty of Pytorch

Começando.

Vamos escolher um conjunto de dados de gráfico simples, como o Zachary Karate Club . Aqui, os nós representam 34 alunos que estiveram envolvidos no clube e os links representam 78 interações diferentes entre pares de sócios fora do clube. Existem dois tipos diferentes de rótulos , ou seja, as duas facções. Podemos usar essas informações para formular uma tarefa de classificação de nós.

Dividimos o gráfico em conjuntos de treinamento e de teste, onde usamos o conjunto de treinamento para construir um modelo de rede neural de gráfico e usamos o modelo para prever os rótulos de nós ausentes no conjunto de teste.

Aqui, usamos a biblioteca python PyTorch Geometric (PyG) para modelar a rede neural de gráfico. Como alternativa, a Deep Graph Library (DGL) também pode ser usada para o mesmo propósito.

PyTorch Geometric é uma biblioteca de aprendizado geométrico profundo construída sobre PyTorch. Vários métodos populares de rede neural de gráfico foram implementados usando PyG e você pode brincar com o código usando conjuntos de dados integrados ou criar seu próprio conjunto de dados. PyG usa uma implementação bacana onde fornece uma classe InMemoryDataset que pode ser usada para criar o conjunto de dados personalizado ( Nota: InMemoryDataset deve ser usado para conjuntos de dados pequenos o suficiente para carregar na memória ).

Uma visualização simples do conjunto de dados do gráfico do Zachary Karate Club é a seguinte:Visualização dos dados gráficos do Zachary Karate Club (Fonte: mim)

Formule o problema.

Para formular o problema, precisamos:

  1. O próprio gráfico e os rótulos de cada nó
  2. Os dados de borda no formato de coordenada (COO)
  3. Embeddings ou representações numéricas para os nós

Vamos entrar na parte de codificação.

Preparações.

import networkx as nx
import numpy as np
import torch
from sklearn.preprocessing import StandardScaler
# load graph from networkx library
G = nx.karate_club_graph()
# retrieve the labels for each node
labels = np.asarray([G.nodes[i][‘club’] != ‘Mr. Hi’ for i in G.nodes]).astype(np.int64)
# create edge index from
adj = nx.to_scipy_sparse_matrix(G).tocoo()
row = torch.from_numpy(adj.row.astype(np.int64)).to(torch.long)
col = torch.from_numpy(adj.col.astype(np.int64)).to(torch.long)
edge_index = torch.stack([row, col], dim=0)
# using degree as embedding
embeddings = np.array(list(dict(G.degree()).values()))
# normalizing degree values
scale = StandardScaler()
embeddings = scale.fit_transform(embeddings.reshape(-1,1))

O conjunto de dados do karate club pode ser carregado diretamente da biblioteca NetworkX. Recuperamos os rótulos do gráfico e criamos um índice de borda no formato de coordenadas. O grau de nó foi usado como embeddings / representações numéricas para os nós (no caso de um gráfico direcionado, em grau pode ser usado para o mesmo propósito). Como os valores de graus tendem a ser diversos, nós os normalizamos antes de usar os valores como entrada para o modelo GNN.

Com isso, preparamos todas as peças necessárias para construir o conjunto de dados personalizado do Pytorch Geometric.

O conjunto de dados personalizado.

import torch
import pandas as pd
from torch_geometric.data import InMemoryDataset, Data
from sklearn.model_selection import train_test_split
import torch_geometric.transforms as T
# custom dataset
class KarateDataset(InMemoryDataset):
def __init__(self, transform=None):
super(KarateDataset, self).__init__(‘.’, transform, None, None)
data = Data(edge_index=edge_index)
data.num_nodes = G.number_of_nodes()
# embedding
data.x = torch.from_numpy(embeddings).type(torch.float32)
# labels
y = torch.from_numpy(labels).type(torch.long)
data.y = y.clone().detach()
data.num_classes = 2
# splitting the data into train, validation and test
X_train, X_test, y_train, y_test = train_test_split(pd.Series(G.nodes()),
pd.Series(labels),
test_size=0.30,
random_state=42)
n_nodes = G.number_of_nodes()
# create train and test masks for data
train_mask = torch.zeros(n_nodes, dtype=torch.bool)
test_mask = torch.zeros(n_nodes, dtype=torch.bool)
train_mask[X_train.index] = True
test_mask[X_test.index] = True
data[‘train_mask’] = train_mask
data[‘test_mask’] = test_mask
self.data, self.slices = self.collate([data])
def _download(self):
return
def _process(self):
return
def __repr__(self):
return ‘{}()’.format(self.__class__.__name__)
dataset = KarateDataset()
data = dataset[0]

A classe KarateDataset herda da classe InMemoryDataset e usa um objeto Data para reunir todas as informações relacionadas ao conjunto de dados do clube de caratê. Os dados do gráfico são então divididos em conjuntos de treinamento e teste, criando assim as máscaras de treinamento e teste usando as divisões.

O objeto de dados contém as seguintes variáveis:

Dados (edge_index = [2, 156], num_classes = [1], test_mask = [34], train_mask = [34], x = [34, 1], y = [34])

Este conjunto de dados personalizado agora pode ser usado com vários modelos de rede neural de gráfico da biblioteca Pytorch Geometric. Vamos escolher um modelo de rede convolucional de gráfico e usá-lo para prever os rótulos ausentes no conjunto de teste.

Observação: a biblioteca PyG se concentra mais na tarefa de classificação de nós, mas também pode ser usada para previsão de link.

Rede convolucional do gráfico.

import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
# GCN model with 2 layers
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = GCNConv(data.num_features, 16)
self.conv2 = GCNConv(16, int(data.num_classes))
def forward(self):
x, edge_index = data.x, data.edge_index
x = F.relu(self.conv1(x, edge_index))
x = F.dropout(x, training=self.training)
x = self.conv2(x, edge_index)
return F.log_softmax(x, dim=1)
device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)
data = data.to(device)
model = Net().to(device)

view rawgcn-karateclub.py hosted with ❤ by GitHub

O modelo GCN é construído com 2 camadas ocultas e cada camada oculta contém 16 neurônios. Vamos treinar o modelo!

Treine o modelo GCN.

torch.manual_seed(42)
optimizer_name = “Adam”
lr = 1e-1
optimizer = getattr(torch.optim, optimizer_name)(model.parameters(), lr=lr)
epochs = 200
def train():
model.train()
optimizer.zero_grad()
F.nll_loss(model()[data.train_mask], data.y[data.train_mask]).backward()
optimizer.step()
@torch.no_grad()
def test():
model.eval()
logits = model()
mask1 = data[‘train_mask’]
pred1 = logits[mask1].max(1)[1]
acc1 = pred1.eq(data.y[mask1]).sum().item() / mask1.sum().item()
mask = data[‘test_mask’]
pred = logits[mask].max(1)[1]
acc = pred.eq(data.y[mask]).sum().item() / mask.sum().item()
return acc1,acc
for epoch in range(1, epochs):
train()
train_acc,test_acc = test()
print(‘#’ * 70)
print(‘Train Accuracy: %s’ %train_acc )
print(‘Test Accuracy: %s’ % test_acc)
print(‘#’ * 70)

Experimentos iniciais com hiperparâmetros aleatórios deram os seguintes resultados:
Precisão do trem: 0,913
Precisão do teste: 0,727

Isso não é impressionante e certamente podemos fazer melhor. Em meu próximo post, discutirei como podemos usar Optuna (biblioteca python sobre ajuste de hiperparâmetros) para ajustar os hiperparâmetros facilmente e encontrar o melhor modelo.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *