PyTorch Geometric (PyG) ile çizgesel sinir ağları
Sinir ağları, metin, resim ve ses dosyaları gibi yapılandırılmış veriler için tasarlanmıştır. Ancak, klasik sinir ağlarının işleyemeyeceği çok sayıda düzensiz yapıda veri vardır. Bu tür veriler için PyTorch Geometric’i kullanabiliriz. PyTorch Geometric çizgeler ve örgüler gibi yapılandırılmamış verileri işleyebilen çizgesel sinir ağlarının çalıştırılması için geliştirilmiş bir kütüphanedir.
Kurulum
Not: PyTorch Geometric’i kurabilmeniz için önce PyTorch yüklenmelidir. PyTorch’un nasıl kurulacağına ilişkin talimatlar için PyTorch’u yükleme kılavuzuna başvurabilirsiniz. PyTorch Geometric’i yüklemek için lütfen şu adımları izleyin:
Şu anda yüklü olan PyTorch sürümünü kontrol edin ve bir ortam değişkeninde saklayın. Bunu yapmak için aşağıdaki komutu çağırmanız yeterlidir:
export TORCH=$(python -c "import torch; print(torch.__version__)")
Şu anda yüklü olan
cudatoolkit
sürümünü kontrol edin ve bunu bir ortam değişkeninde saklayın.İlk önce kurduğunuz CUDA sürümünü kontrol edin:
python -c "import torch; print(torch.version.cuda)" >> 11.1
Yukarıda gösterilen sürüme bağlı olarak aşağıdaki ortam değişkenini ayarlayın
export CUDA=cu111 # for cuda 11.1 Bu öğreticinin yazıldığı sırada cuda sürümü için olası değerler cpu (eğer cudatoolkit kurulu değilse), cu92, cu101, cu102, cu110 veya cu111 şeklindedir.
Gerekli kütüphaneleri aşağıdaki çağrılarla kurun:
pip install torch-scatter -f https://pytorch-geometric.com/whl/torch-${TORCH}+${CUDA}.html pip install torch-sparse -f https://pytorch-geometric.com/whl/torch-${TORCH}+${CUDA}.html pip install torch-cluster -f https://pytorch-geometric.com/whl/torch-${TORCH}+${CUDA}.html pip install torch-spline-conv -f https://pytorch-geometric.com/whl/torch-${TORCH}+${CUDA}.html pip install torch-geometric
Veri - çizge veri yapısı
PyG tanımları, çizge veri kümelerini tanımlamak için torch_geometric.data.Data
sınıfını kullanır. Bu türdeki her nesne bir çizgeyi temsil eder. Bir Data
nesnesi birçok veri üyesi içerebilir. İşte içerebileceği bazı önemli veri üyeleri:
edge_index
: COO formatında çige bağlantı bilgisini tutan Torch tensörü. Bu, [2, number_of_edges] boyutunda olacak. Her sütun bir bağlantıyı temsil eder.
x
: çizgedeki tüm düğümlerin özellik vektörleri. [num_nodes, num_node_features] şeklinde bir tensör
edge_attr
: [num_edges, num_edge_features] şeklinde bağlantı özelliklerinin saklandığı matris
y
: eğitmek için etiketler (isteğe bağlı şekle sahip olabilir), ör. düğüm düzeyindeki şekil [num_nodes, *] hedefleri veya şekili [1, * ] olacak şekilde çizge düzeyindeki hedefler
Yukarıdaki niteliklerin tümü bir Data
nesnesi oluşturmak için gerekli değildir. Ek olarak, gerekirse nesneyi, örneğin bağlantı ağırlıkları gibi kendi yarattığımız özelliklerimizle genişletebiliriz.
Aşağıdaki örnekte, dört düğümü ve üç bağlantısı olan yönsüz bir çizge tanımlıyoruz. Ayrıca çizgedeki tüm düğümler için özellik vektörleri ekliyoruz:
import torch
from torch_geometric import Data
edge_index = torch.tensor([[1, 2, 0, 1, 2, 0],
[2, 1, 1, 0, 0, 0]])
graph = Data(edge_index = edge_index)
print(f"Çizge: {graph}")
graph.x = torch.randn((4,5))
print(f"Düğüm özellikleri ekledikten sonraki çizge: {graph}")
print(f"çizgenin {graph.num_nodes} düğümü ve {graph.num_edges} bağlantısı vardır")
Çıktı
Çizge: Data(edge_index=[2, 6]) Düğüm özellikleri ekledikten sonraki çizge: Data(edge_index=[2, 6], x=[4, 5]) çizgenin 4 düğümü ve 6 bağlantısı vardır
Veri objelerinin birçok faydalı yardımcı fonksiyonu vardır:
print(f"`Data` nesnesinde hangi verilerin olduğunu kontrol edin: {data.keys}")
print(f"düğüm özelliği vektörleri\n {data['x']}")
print(f"edge_attr verilerde mi? {'edge_attr' in data}")
print(f"düğüm özellikleri sayısı {data.num_node_features}")
print(f"Çizge izole düğümler içeriyor mu? {data.contains_isolated_nodes()}")
print(f"Çizge kendi kendine döngüler içeriyor mu? {data.contains_self_loops()}")
print(f"Çizge yönlendirilmiş mi? {data.is_directed()}")
Çıktı
`Data` nesnesinde hangi verilerin olduğunu kontrol edin: ['x', 'edge_index'] tensor([[ 1.7464, 0.0523, -0.1089, 0.3255, -0.3031], [-0.8393, 2.7257, 0.7538, 0.0997, -0.3187], [-0.6025, -0.8008, -0.3081, 1.0320, -0.2903], [ 2.2594, 0.0473, -0.7182, 0.1754, -0.8136]]) edge_attr verilerde mi? False düğüm özellikleri sayısı 5 Çizge izole düğümler içeriyor mu? True Çizge kendi kendine döngüler içeriyor mu? True Çizge yönlü mü? True
Mevcut GNN katmanlarını kullanarak bir model oluşturma
PyG, mevcut GNN katmanlarının kapsamlı bir koleksiyonuyla birlikte gelir. Bu katmanları kendi modellerimizi oluşturmak için kullanabiliriz. Aşağıdaki örnekte, düğüm sınıflandırma - çizgedeki düğümleri (köşeler) sınıflandırma görevini yerine getirmek için bazı bilinen GNN’leri kullanarak bir sinir ağı modeli oluşturuyoruz. Modelimiz, her düğüm için bir özellik vektörü ile birlikte bir çizge alacak ve bu düğümleri 7 olası sınıftan birine sınıflandıracaktır.
Veri kümesi
PyG’nin sağladığı veri kümelerinden birini kullanacağız. Sınıflandırma görevlerini değerlendirmek için bu tür eğitimlerde sıklıkla kullanılan Cora veri setini kullanıyoruz.
Bu veri kümesini yüklerken, root
parametresinde veri kümesini indirmek istediğimiz konumu belirtiyoruz. Bu durumda da name
parametresinde istediğimiz veri setinin adını belirtmemiz gerekiyor. Tüm veri kümeleri bu parametreyi gerektirmez. Veri kümelerinin gereksinimlerini PyG’nin belgelerinde kontrol edebilirsiniz.
dataset
nesnesi, veri kümesi içindeki tüm çizgelerin bir listesini içerir. Bizim durumumuzda, Cora
veri seti tek bir çizge içerir.
from torch_geometric.data import DataLoader
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root='data', name='Cora') # dataset bir çizge listesi içerir
print(f"dataset'de {len(dataset)} çizge var")
print(dataset[0])
cora = dataset[0]
num_node_features = cora.num_features
# Çizgedeki düğüm sınıflarının sayısı
num_classes = cora.y.max().item()+1
Çıktı
dataset'de 1 çizge var Data(edge_index=[2, 10556], test_mask=[2708], train_mask=[2708], val_mask=[2708], x=[2708, 1433], y=[2708]) Düğümlerin özellik vektörleri 1433 özelliğe sahiptir. Düğümler için toplam 7 sınıf var
Model oluşturma
Şimdi hem çizge sinir ağı katmanlarını hem de normal bir sinir ağı katmanını içeren bir sinir ağı modeli oluşturuyoruz. Bu model, torch.nn.Module
sınıfından miras alan bir sınıf olacak ve normal bir sinir ağı ile tamamen aynı şekilde çalışacak, yani, düğümlerin (x
tensörü ile temsil edilen) özelliklerini alacak ve bu özellikleri kullanarak sınıflandırmalar yapacaktır. Bizim modelimiz ile normal bir sinir ağı modeli arasındaki tek fark, bizim modelimize çizge sinir ağı katmanları ekleyeceğiz. Bu katmanlar, eğitim sırasında düğümlerin özellik vektörlerinin yanı sıra çizgenin bağlantı bilgilerini de kullanacak.
__init__
fonksiyonunda iki GNN katmanı ve bir lineer katmanın yanı sıra iki aktivasyon fonksiyonu ekliyoruz. GNN katmanları, çizge bağlantı bilgilerinin yanı sıra düğümlerin özelliklerini alırken, doğrusal katman yalnızca düğümlerin özellik vektörlerini alacaktır. Başka bir deyişle, doğrusal katman, özellik vektörlerini çizge yapısı hakkında herhangi bir bilgi kullanmadan işleyecektir.
İleri fonksiyonuna bir Data
nesnesi iletiriz ve ondan düğüm özelliklerini (data.x
) ve çizgenin bağlantı bilgilerini (data.edge_index
) çıkarırız. Unutulmamalıdır ki düğüm özelliklerini GNN katmanlarına geçirdiğimizde bağlantı bilgisini de iletmiş oluyoruz. Bunun nedeni, bu katmanların işlemleri sırasında bağlantı bilgilerini kullanmasıdır.
Grafiği GNN’lerden geçirdikten sonra, düğüm özelliklerini doğrusal bir katmandan geçiriyoruz. Grafiğin bağlantı bilgilerini geçmediğimize dikkat edin. Son olarak, bir log-softmax aktivasyonu kullanarak her düğüm için 7 elemanlık bir satır olacak olan sonuçları döndürüyoruz.
import torch_geometric.nn as pyg_nn
import torch.nn as nn
class GNN(nn.Module):
def __init__(self, in_features, num_hidden_feats, num_classes):
super(GNN, self).__init__()
# ModuleList, sinir ağı katmanlarının bir listesini tutar
self.gnn_layers = nn.ModuleList()
# Bu "Graph Convolutional Network" katmanı, in_feature uzunluğundaki özellik vektörlerini alacak ve her düğüm için num_hidden_feats uzunluğunda özellik vektörleri üretecektir.
self.gnn_layers.append(pyg_nn.GCNConv(in_features, num_hidden_feats))
# Bu "Çizge Dikkat Ağı" katmanı, hidden_layer_features uzunluğundaki özellikleri alacak ve her düğüm için hidden_layer_features uzunluğunda vektörler üretecektir.
self.gnn_layers.append(pyg_nn.GATConv(num_hidden_feats, num_hidden_feats))
# Bu, sıradan bir doğrusal sinir ağı katmanıdır.
self.lin = nn.Linear(num_hidden_feats, num_classes)
self.relu = nn.ReLU()
# Bir çizge yapısı içeren `Data` nesnesini ileri işlevine ileteceğiz.
def forward(self, data):
# Düğüm özelliklerini ve bağlantı bilgisi tensörlerini "data" nesnesinden çıkarıyoruz
node_features, edge_index = data.x, data.edge_index
# Özellik vektörlerini ve bağlantı bilgilerini GNN katmanına aktarıyoruz. GNN katmanı, işlem sırasında bağlantı bilgilerini kullanacaktır.
out_node_features = self.gnn_layers[0](node_features, edge_index)
# GNN katmanı, güncellenmiş düğüm özelliği vektörlerini döndürür
out_node_features = self.relu(out_node_features)
out_node_features = self.gnn_layers[1](out_node_features, edge_index)
out_node_features = self.relu(out_node_features)
# Düğüm özellik vektörlerini doğrusal katmana geçiriyoruz. `self.lin` bir GNN katmanı olmadığı için bağlantı bilgisini iletmemize gerek olmadığına dikkat edin.
out_node_features = self.lin(out_node_features)
return out_node_features
num_hidden_feats = 128
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GNN(num_node_features, num_hidden_feats, num_classes).to(device)
print(model)
Çıktı
(gnn_layers): ModuleList( (0): GCNConv(1433, 128) (1): GATConv(128, 128, heads=1) ) (lin): Linear(in_features=128, out_features=7, bias=True) (relu): ReLU() )
Optimize edici ve kayıp
Bir Adam optimize edici ve bir negatif log-olasılık kaybı fonksiyonu kullanıyoruz. Optimize edici, parametreleri Adam stratejisine göre güncellemeyi yönetecek ve kayıp fonksiyonu, modeldeki eğitilebilir parametrelerin kayıplarını ve gradyanlarını hesaplamak için kullanılacaktır.
optimizer = torch.optim.Adam(model.parameters(), lr = 0.01)
loss_function = nn.functional.nll_loss
Eğitim döngüsü
Eğitim döngüsü, PyTorch ile oluşturulmuş normal bir sinir ağının eğitim döngüsüne tam olarak benziyor. Her eğitim adımında, veri kümesini modelden geçiririz ve model her düğüm için bir puan vektörü döndürür. Ardından, bu puanların kaybını hesaplıyoruz ve kaybı, model parametrelerinin gradyanlarını hesaplamak için kullanıyoruz. Son olarak, hesaplanan gradyanları kullanarak modelin parametrelerini güncellemek için optimize ediciyi kullanıyoruz.
Eğitim sırasında, verilerimizin bir kısmını eğitim için, bir kısmını da test için kullanmak istiyoruz. Diğer bir deyişle, kalan düğümlerin sınıflarını gizli tutarken sadece bazı düğümlerin sınıflarını eğitim için kullanmak istiyoruz. Ancak eğitim örneklerinin çıktılarını hesaplamak için çizgenin tamamı gerektiğinden, çizgenin tamamını modele aktarmamız gerekiyor. Cora çizgesindeki train_mask
özelliğini kullanarak verinin eğitim kısmını alıyoruz. PyG tarafından sağlanan tüm çizgelerin eğitim maskelerine sahip olmadığına dikkat edilmelidir.
epochs = 100
for epoch in range(epochs):
# `Data` nesnesini modele geçiriyoruz. Model, güncelleme işleminden sonra düğümlerin özellik vektörlerini döndürür.
y_score = model(cora)
# Kaybı hesaplamak için eğitim kümesindeki düğümlerin yalnızca özellik vektörlerini seçmek için `train_mask` kullanıyoruz.
y_score_train = y_score[cora.train_mask]
# Ayrıca, yalnızca eğitim kümesindeki düğümlerin etiketlerini seçmek için `train_mask` kullanırız.
y_train = cora.y[cora.train_mask]
# Kaybı hesaplıyoruz, model parametrelerine göre kaybın gradyanlarını hesaplıyoruz ve bunları güncellemek için optimize ediciyi kullanıyoruz.
loss = loss_function(y_score_train, y_train)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if epoch % 20 == 0:
print(f"Epoch {epoch}: loss {loss}")
Çıktı
Epoch 0: loss 1.9444819688796997 Epoch 20: loss 0.0636444166302681 Epoch 40: loss 0.007010670844465494 Epoch 60: loss 0.00019191707542631775 Epoch 80: loss 3.740669853868894e-05
Test döngüsü
Çizgeler üzerinde öğrenme prosedürünü test ediyoruz, bu düzenli veri kümelerini kullanmaya benzer. Test verilerini modelden geçireceğiz, tahminler yapacağız ve doğru tahminlerin sayısını sayacağız. Test verilerini elde etmek için Cora veri seti ile sağlanan test_mask
özelliğini kullanıyoruz.
with torch.no_grad():
y_score = model(cora)[cora.test_mask]
prediction = y_score.argmax(dim=1)
score = prediction.eq(cora.y[cora.test_mask]).sum().item()
print(f"Final accuracy = {100*score/cora.test_mask.sum()}")
Çıktı
Final accuracy = 71.8000%
GNN katmanı oluşturma - mesaj geçiş arayüzü (message passing interface)
Teori
Önceki örnekte, çizgeleri işleyebilen ve düğüm sınıflandırmasını gerçekleştirebilen bir makine öğrenimi modeli oluşturduk. Ancak, zaten var olan çizge sinir ağı katmanlarını kullandık. Aşağıdaki örnekte kendi GNN katmanımızı oluşturacağız ve bunu çizge sınıflandırması yapacak bir modelde kullanacağız.
Konvolüsyonları düzensiz verilere (örneğin çizgeler) genelleştirmeye mesaj geçişi (message passing) denir. Mesaj geçiş şeması, \(\mathbf{x} *i^{k}\) ifadesinin i düğümünün k katmanındaki özellik vektörü olduğu göz önüne alındığında aşağıdaki gibi ifade edilebilir. \(\mathbf{e}_{i,j}\), \((i,j)\) bağlantıyla ilişkili isteğe bağlı bir özellik vektörüdür.
\(\square\) permütasyon değişmez bir fonksiyon olduğunda (işlenenlerin sırası önemli değildir), toplam, maksimum veya ortalama gibi fonksiyonlar toplaşma fonksiyonu olarak adlandırılır. \(\gamma\) ve \(\phi\) türevlenebilir fonksiyonlardır. (örneğin doğrusal sinir ağı katmanları.)
Başka bir deyişle, \(k\) katmanından mesaj geçtikten sonra bir \(i\) düğümünün özellik vektörünü hesaplamak için aşağıdaki adımları yaparız:
\(i\) düğümünün gelen her \(j\) komşusu için bir “mesaj” üretirken \(\phi\) fonksiyonunu uygularız. \(\phi\) fonksiyonu, \(i, j\)’nin özellik vektörlerini ve isteğe bağlı olarak \((i,j)\) bağlantısının özellik vektörünü kullanır.
\(\square\) fonksiyonunu kullanarak \(i\) düğümüne gelen tüm mesajları tek bir vektörde topluyoruz. \(\square\) fonksiyonu, tüm mesajların toplamı, tüm mesajların ortalaması veya maksimum mesaj olabilir. Bu, \(i\) düğümüne gönderilen tüm mesajların tek bir temsilini oluşturacaktır.
Son olarak, \(\gamma\) dönüşümünü mesajların toplu gösterimi ve düğümün kendisinin gömülmesi için uygularız. Nihai çıktı, düğümün yeni özellik vektörü olacaktır.
Torch_geometric.nn.MessagePassing
, kendisini miras alan sınıfların yukarıda açıklanan prosedürü kolaylıkla uygulamasına izin veren bir arayüzdür. Aşağıdaki fonksiyonlar bu özelliği sağlar:
MessagePassing(aggr="add", flow="source_to_target", node_dim=-2)
:aggr
parametresi, toplaşma şemasını(\(\square\)) ("add"
,"sum"
veya"max"
) tanımlar veflow
, mesaj akışının bir uç kaynağın kaynağından hedefe mi yoksa tam tersi mi olduğunu belirler.
MessagePassing.propagate(edge_index, **kwargs)
: bu fonksiyon mesaj geçirme prosedürünü gerçekleştirecektir. İletileri oluşturmak ve yerleştirmeleri güncellemek için gerekli olan uç bağlantı bilgilerini (edge_index
) ve diğer tüm verileri (ör. düğüm özellik vektörlerix
, bağlantı özellik vektörleriedge_attr
, vb.) alır ve her biri için bir vektör içeren bir matris döndürür.propogate()
aşağıdaki üç işlevi çağırır:
MessagePassing.message(...)
: Bu fonksiyon, yukarıdaki formüldeki \(\phi\) fonksiyonunu temsil eder.propagate()
fonksiyonuna iletilen tüm parametreleri alır ve isteğe bağlı olarak, grafiğin bağlantılarının kaynağına ve hedefine eşlenen özellik vektörlerinden de geçirelebilir. Detaylandırmak gerekirse,propagate()
fonksiyonuna köşe özellikleri, çizgedeki her düğüm için bir satır, içeren bir matristen geçilmişse, örnek olaraknode_feats => tensor([num_nodes, num_feats])
matrisi, vemessage()
fonksiyonuna yapılan çağrınode_feats_i
parametresini içeriyorsa, o zamannode_feats_i
,[sayı_edgeleri, sayı_feats]
boyutunda bir matris olur venode_feats_i[a]
venode_feats[edge_index[1][a]
eşdeğer olur. Başka bir deyişle, bu a bağlantısının hedef düğümüne aitnode_feats
satırıdır. Öte yandan, yapılan çağrıya, birnode_feats_j
parametresi iletilirse, o zamannode_feats
matrisinin eşlemelerini içerecek, ancak bağlantıların kaynaklarına dayalı olacaktır. Programcı, mesajları oluşturmak içinpropagate()
fonksiyonuna iletilen diğer parametrelerin yanı sıra bu fonksiyonları kullanabilir. Bu fonksiyon, her bağlantı için bir satır içeren bir matris, msj, döndürmelidir, buradamsgs[a]
satırı, bağlantı a’nın hedef düğümüne gönderilen bir mesaj, yaniedge_index[1][a]
düğümüne gönderilen bir mesaj olacaktır.
MessagePassing.aggregate(msgs, ...)
: bu fonksiyon,message()
fonksiyonu tarafından döndürülen tüm mesajları alacak ve yukarıdaki formüldeki \(\square\) fonksiyonunu uygulayacaktır. Yani, mesajları her köşe için tek bir vektörde toplar (toplar, maksimumlarını bulur veya ortalamalarını bulur) ve düğüm başına bir son vektör içeren matrisi döndürür.
MessagePassing.update(aggr_out, ...)
: Bu fonksiyon,propagate()
öğesine iletilen tüm parametrelerin yanı sıra her bir köşe için ileti toplaşmasının sonucunu içerenpropagate()
öğesinin döndürdüğü matrisi alır ve yukarıdaki formülasyondaki \(\gamma\) dönüşümü ve yayılma sürecinin son çıktısını döndürür.
Aşağıdaki şekil, parametre olarak grafiğin bağlantı bilgilerini (edge_index
) ve ayrıca her düğüm için özellik vektörlerini içeren bir matrisi (node_features
) alan propagate()
fonksiyonuna yapılan bir çağrıyı gösterir.

Dataset - veri kümesi
Çoklu çizgelere sahip bir veri seti kullanacağız ve çizge sınıflandırması yapacağız.
from torch_geometric.datasets import TUDataset
dataset = TUDataset(root='data', name='ENZYMES')
print(f"Bu veri kümesinde {len(dataset)} çizge var ")
Çıktı
Bu veri kümesinde 600 çizge var
Veri yükleyiciler (Dataloaders)
Bu veri seti büyük olduğu için PyG tarafından sağlanan DataLoader mekanizmasını kullanacağız. PyTorch DataLoader sınıfına benzer şekilde davranır, ancak özellikle torch_geometric.data.Dataset
sınıfı için modifiye edilmiştir ve veri kümelerini çoklu çizgelere bölümlemeyi işler. Eğitim verileri için bir veri yükleyici ve test verileri için bir tane oluşturacağız. batch_size
parametresi, parti başına kaç numunenin yükleneceğini belirler.
from torch_geometric.data import DataLoader
# Train_loader eğitmek için çizgelerin %80'ini kullanacak ve test_loader kalan %20'yi test için kullanacak
# batch_size, puanları hesaplarken sınıfları kullanılacak düğüm sayısını belirler
train_loader = DataLoader(dataset[:int(data_size * 0.8)], batch_size=64, shuffle=True)
test_loader = DataLoader(dataset[int(data_size * 0.8):], batch_size=64, shuffle=True)
train_iter = iter(train_loader)
batch = train_iter.next()
print(batch)
print(f"Toplu iş {batch.x.shape[0]} düğümü içermesine rağmen, yalnızca {batch.y.shape[0]} etiketi vardır (çizge sayısı).")
Batch(batch=[2083], edge_index=[2, 7694], ptr=[65], x=[2083, 3], y=[64])
Toplu iş 2083 düğümü içermesine rağmen, yalnızca 64 etiketi vardır (çizge sayısı).
GNN katmanı tanımlama
Şimdi, önceki örnekte kullandığımız GCN katmanına matematiksel olarak eşdeğer bir GNN katmanı tanımlıyoruz. Katmanı tanımlamak için mesaj geçiş arayüzünü kullanacağız.
Yapıcıda, toplaşma fonksiyonunun “toplam” olarak istediğimizi ve mesajların bir bağlantının kaynağından hedefine akması gerektiğini belirtiriz. Ayrıca tek bir doğrusal katman ekliyoruz.
İleri fonksiyonunda , düğüm özelliklerini doğrusal katmandan geçiririz, sonra dönüştürülmüş düğüm özellikleri (node_feats: tensor([num_nodes, in_channels])
) ve bağlantı bilgileri ile propagate()
fonksiyonunu çağırırız. Yayma fonksiyonu önce message()
fonksiyonunu çağırır ve mesaj fonksiyonu node_feats_j
parametresine sahip olduğundan, node_feats
matrisi, node_feats_j
üretmek için çizgedeki tüm bağlantıların kaynaklarıyla eşleştirilir. Bu, node_feats_j[a] == node_feats[edge_index[0][a]]
anlamına gelir.
i
ve j
düğümleri arasındaki a
bağlantısına karşılık gelen her node_feats_j[a]
öğesi için, message()
fonksiyonu node_feats_j[a] * 1/( sqrt(degree(i) değerini döndürür. )) * sqrt(derece(j))))
olarak ifade edilir.
Daha sonra, aggregate()
fonksiyonu otomatik olarak çağrılır ve message()
döndürdüğü matris üzerinde toplaşma işlemi olarak add
yapar. Son olarak, update()
fonksiyonu çağrılacak ve aggregate()
fonksiyonunun döndürdüğü tensörden geçirilecektir. update()
döndürdüğü tensör, propagate()
fonksiyonu tarafından döndürülecektir.
import torch_geometric.utils as pyg_utils
class GCN(pyg_nn.MessagePassing):
def __init__(self, in_channels, out_channels):
# Bu katmanın toplaşma fonksiyonu olarak toplama kullanacağını ve mesajların bir bağlantnın kaynağından ucun hedefine gideceğini belirtiyoruz.
super(GCN, self).__init__(aggr='add', flow='source_to_target')
# Katmanda kullanacağımız doğrusal bir sinir ağı ekliyoruz.
self.lin = nn.Linear(in_channels, out_channels)
def forward(self, x, edge_index):
# Katmanımızdan bir girdi geçtiğinde "forward" fonksiyonunu çağrılır. "x" düğüm özelliklerini ve "edge_index" bağlantı bilgilerini alacağız
# bitişiklik matrisine kendi kendine döngüler ekleyin.
edge_index, _ = pyg_utils.add_self_loops(edge_index)
# Düğüm özelliği matrisini dönüştür
node_feats = self.lin(x)
# Yayılma çağrısı mesaj geçişini yürütecek
# Önce 'message()' çağrılır, ardından 'aggregate()', ardından 'update()' ve 'update()' çıktılanır
# 'propagate()' öğesine iletilen tüm parametreler, çağırdığı diğer üç fonksiyonlara iletilecektir.
return self.propagate(edge_index, node_feats=node_feats)
def message(self, node_feats_j, edge_index, size):
# Fonksiyon argümanlarına `node_feats_j` parametresini eklediğimizde, `node_feats`in çizgenin tüm bağlantılarının kaynakları üzerindeki eşlemesi hesaplanacak ve `node_feats_j` içine yerleştirilecektir.
# node_feats_j şekili =>[num_edges, out_channels]
row, col = edge_index
# GCN belgesine göre normları hesaplayın
deg = pyg_utils.degree(row, size[0], dtype=node_feats_j.dtype)
deg_inv_sqrt = deg.pow(-0.5)
norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]
# Döndürülen matris, çizgedeki her bağlantı için bir mesaj içerir.
return norm.view(node_feats_j.shape[0], 1)*node_feats_j
def update(self, aggr_out):
# "message()" fonksiyonu tarafından döndürülen matristeki mesajlar toplanır ve her düğüm için tek bir kümelenmiş vektör oluşturmak üzere "aggr_out" içine yerleştirilir.
# aggr_out şekili => [N, out_channels]
return aggr_out
Model oluşturma
Oluşturduğumuz GNN katmanını eksiksiz bir modelde kullanacağız. Önceki örnekte kullandığımıza benzer bir model kullanacağız, ancak bir çizgideki düğümlerin tüm özellik vektörlerini tek bir özellik vektöründe toplayacak ek bir havuz fonksiyonu ekleyeceğiz. Bunun nedeni, bu modeli çizge sınıflandırması için kullanmak istememizdir.
Havuzlama fonksiyonunda
, data
nesnesinin içindeki batch
üyesini kullanırız. Bu üye yalnızca, örnekleri almak için bir DataLoader kullandığımızda eklenir. batch
tensörü, data
nesnesindeki her düğümün çizge kimliğini içerir.
import torch
class GNN(nn.Module):
def __init__(self, in_features, num_hidden_feats, num_classes):
super(GNN, self).__init__()
self.gnn_layers = nn.ModuleList()
self.gnn_layers.append(GCN(in_features, num_hidden_feats))
self.gnn_layers.append(pyg_nn.GATConv(num_hidden_feats, num_hidden_feats))
self.lin = nn.Linear(num_hidden_feats, num_classes)
self.relu = nn.ReLU()
self.log_softmax = nn.LogSoftmax(dim=1)
def forward(self, data):
# Bağlantı bilgilerine ('edge_index') ve düğüm özelliklerine ('x') ek olarak, 'batch' tensörünü de çıkarıyoruz. Bu tensör, partideki her düğümü ait olduğu çizgeye eşler.
node_features, edge_index, batch = data.x, data.edge_index, data.batch
# Bu katmanı, GCNConv katmanını kullandığımız şekilde kullanıyoruz.
out_node_features = self.gnn_layers[0](node_features, edge_index)
out_node_features = self.relu(out_node_features)
out_node_features = self.gnn_layers[1](out_node_features, edge_index)
out_node_features = self.relu(out_node_features)
# Bu bir çizge sınıflandırma problemi olduğundan, havuzlama fonksiyonunu kullanarak her bir çizgeye ait düğümlerin tüm özellik vektörlerini tek bir vektörde toplayacağız. Havuzlama fonksiyonu, her düğümün çizge kimliklerini içeren "batch" tensörünü kullanır.
out_graph_features = pyg_nn.global_mean_pool(out_node_features, batch)
out_graph_features = self.lin(out_graph_features)
return self.log_softmax(out_graph_features)
num_hidden_feats = 128
num_node_features = dataset.num_node_features
num_classes = dataset.num_classes
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GNN(num_node_features, num_hidden_feats, num_classes).to(device)
print(model)
Çıktı
GNN( (gnn_layers): ModuleList( (0): GCN( (lin): Linear(in_features=3, out_features=128, bias=True) ) (1): GATConv(128, 128, heads=1) ) (lin): Linear(in_features=128, out_features=2, bias=True) (relu): ReLU() (log_softmax): LogSoftmax(dim=1) )
Eğitim döngüsü
Kullanacağımız eğitim döngüsü, birkaç temel fark dışında son örnekte kullandığımıza benzer. İlk olarak, her eğitim döneminde, birden fazla grup arasında yineleme yapacağız ve bu gruplar üzerinde eğiteceğiz. Bu yinelemeyi yapmak için train_loader
ı kullanacağız. İkincisi, eğitim verilerini seçmek için maske kullanmamıza gerek yok. Bunun nedeni, train_loader
ın yalnızca eğitim verilerini içermesidir.
epochs = 10
for epoch in range(epochs):
epoch_loss = 0
for batch_num, batch in enumerate(train_loader):
y_score = model(batch)
loss = loss_function(y_score, batch.y)
loss.backward()
optimizer.step()
optimizer.zero_grad()
epoch_loss+=loss
if epoch % 2 == 0:
print(f"Loss {epoch_loss}")
Çıktı
Loss 8.446746826171875 Loss 7.77716588973999 Loss 7.491060733795166 Loss 7.148349285125732 Loss 7.039545059204102
Test etmek
Benzer bir şekilde, test için, test verilerini yüklemek için test_loader
ı kullanacağız ve test_loader
dan partileri modele geçirecek ve bunları tahmin için kullanacağız.
with torch.no_grad():
num_correct = 0
total_samples = 0
for batch in test_loader:
y_score = model(batch)
y_pred = y_score.argmax(dim=1)
num_correct += y_pred.eq(batch.y).sum().item()
total_samples +=len(batch.batch.unique())
print(f"Accuracy {num_correct/total_samples*100}")
Çıktı
Accuracy 31.838565022421523