00 Graf Veri Yapısı — Neden Klasik NN Değil?
Moleküler yapılar, sosyal ağlar, bilgi grafları — bu veriler grid veya sekans değildir; ilişki yapısı da bilginin bir parçasıdır.
Klasik sinir ağları grid (görüntüler için CNN) veya sekans (metin için RNN/Transformer) veriye göre tasarlanmıştır. Graf verisi ise düzensiz topoloji taşır: her düğümün farklı sayıda komşusu olabilir, düğüm sıralaması önemsizdir ve bilgi sadece düğüm özelliklerinde değil kenar yapısında da bulunur. Sosyal ağlarda arkadaş ilişkileri, moleküllerde atom bağları, bilgi graflarında varlık ilişkileri bu kategoriye girer.
Graf iki tür olabilir: homojen — tüm düğümler ve kenarlar aynı tipte (örn. kullanıcı-kullanıcı arkadaşlık ağı); heterojen — birden fazla düğüm/kenar tipi (örn. kullanıcı-film-yönetmen ağı). GNN'in temel fikri message passing: her düğüm komşularından mesaj toplar, kendi durumunu günceller.
01 Graf G = (V, E): düğümler V, kenarlar E, özellik matrisi X ∈ ℝ^{n×d} 02 Her katmanda: h_v^{(l+1)} = UPDATE(h_v^{(l)}, AGGREGATE({h_u^{(l)} | u ∈ N(v)})) 03 Çıkış: node embedding h_v^{(L)} sınıflandırma/tahmin için kullanılır 04 Permutation invariance: düğüm sırası sonucu değiştirmez
import torch
from torch_geometric.data import Data
from torch_geometric.utils import to_networkx
import networkx as nx
# ── Basit graf oluşturma ─────────────────────────────────────
# 4 düğümlü graf: 0-1, 1-2, 2-3, 3-0
edge_index = torch.tensor([
[0, 1, 1, 2, 2, 3, 3, 0], # kaynak
[1, 0, 2, 1, 3, 2, 0, 3], # hedef (yönlü, her ikisi de)
], dtype=torch.long)
# Düğüm özellikleri: 4 düğüm, 3 özellik
x = torch.randn(4, 3)
# Kenar özellikleri: 8 kenar, 2 özellik
edge_attr = torch.randn(8, 2)
# Grafı etiket (graph sınıflandırma için)
y = torch.tensor([1])
data = Data(x=x, edge_index=edge_index,
edge_attr=edge_attr, y=y)
print(data)
print(f"Düğüm sayısı: {data.num_nodes}")
print(f"Kenar sayısı: {data.num_edges}")
print(f"Ortalama derece: {data.num_edges / data.num_nodes:.1f}")
print(f"İzole düğüm var mı? {data.has_isolated_nodes()}")
print(f"Kendi kendine kenar? {data.has_self_loops()}")
01 GCN — Graph Convolutional Network Matematiği ve PyG
GCN, spektral graf teorisinden türetilmiş bir normalize komşu toplama operasyonudur; Kipf & Welling 2017'de yayımlandı.
GCN'in propagasyon kuralı: H^{(l+1)} = σ(D̃^{-1/2} à D̃^{-1/2} H^{(l)} W^{(l)}). Burada à = A + I (self-loop eklenmiş komşuluk matrisi), D̃ onun derece matrisi ve W eğitilebilir ağırlık matrisi. Normalize faktör D̃^{-1/2} gradyan patlamasını önler. Sezgisel açıklama: her düğüm komşularının özelliklerini (derece-normalize edilmiş) toplayıp dönüştürür.
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
# ── Sıfırdan GCNConv implementasyonu ────────────────────────
class ManualGCNConv(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.W = nn.Linear(in_channels, out_channels, bias=False)
def forward(self, x, edge_index):
from torch_geometric.utils import add_self_loops, degree
# 1. Self-loop ekle
edge_index, _ = add_self_loops(edge_index, num_nodes=x.size(0))
# 2. Derece normalize
row, col = edge_index
deg = degree(col, x.size(0), dtype=x.dtype)
deg_inv_sqrt = deg.pow(-0.5)
norm = deg_inv_sqrt[row] * deg_inv_sqrt[col]
# 3. Mesaj toplama: scatter_add
out = self.W(x) # (N, out)
out = out[col] * norm.unsqueeze(-1)
import torch_scatter
out = torch_scatter.scatter_add(out, row, dim=0,
dim_size=x.size(0))
return out
# ── PyG ile 3 katman GCN ─────────────────────────────────────
class GCN(nn.Module):
def __init__(self, in_ch, hidden, out_ch, dropout=0.5):
super().__init__()
self.conv1 = GCNConv(in_ch, hidden)
self.conv2 = GCNConv(hidden, hidden)
self.conv3 = GCNConv(hidden, out_ch)
self.dropout = dropout
def forward(self, x, edge_index):
x = F.relu(self.conv1(x, edge_index))
x = F.dropout(x, p=self.dropout, training=self.training)
x = F.relu(self.conv2(x, edge_index))
x = F.dropout(x, p=self.dropout, training=self.training)
return self.conv3(x, edge_index)
# ── Cora dataset ────────────────────────────────────────────
dataset = Planetoid(root="/tmp/Cora", name="Cora")
data = dataset[0]
print(f"Düğüm: {data.num_nodes}, Kenar: {data.num_edges}")
print(f"Sınıf: {dataset.num_classes}, Özellik: {dataset.num_features}")
model = GCN(dataset.num_features, 64, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
for epoch in range(200):
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.cross_entropy(out[data.train_mask], data.y[data.train_mask])
loss.backward(); optimizer.step()
model.eval()
with torch.no_grad():
pred = model(data.x, data.edge_index).argmax(dim=1)
acc = (pred[data.test_mask] == data.y[data.test_mask]).float().mean()
print(f"Test doğruluk: {acc:.3f}") # ~0.81
02 GraphSAGE — Inductive Learning & Neighbor Sampling
GCN tüm grafı belleğe alır; GraphSAGE ise komşu örneklemesiyle yeni düğümlere genelleme yapan (inductive) bir yaklaşım sunar.
GCN'in temel sorunu: eğitim sırasında tüm grafın komşuluk matrisine ihtiyaç duyar (transductive). GraphSAGE (Hamilton et al., 2017) bunu çözer: her düğüm için sabit sayıda komşu örnekler ve bir AGGREGATE fonksiyonu uygular (mean, max, LSTM). Bu sayede eğitimde görülmemiş düğümlere de embedding üretilebilir — sosyal ağlarda yeni kullanıcılar, e-ticarette yeni ürünler için idealdir.
import torch
import torch.nn.functional as F
from torch_geometric.nn import SAGEConv
from torch_geometric.loader import NeighborLoader
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root="/tmp/Cora", name="Cora")
data = dataset[0]
# ── Mini-batch NeighborLoader ────────────────────────────────
# Her batch'te seed düğümler + 2 hop komşu örnekleme
train_loader = NeighborLoader(
data,
num_neighbors=[25, 10], # 1. hop: 25, 2. hop: 10 komşu
batch_size=256,
input_nodes=data.train_mask,
)
# ── GraphSAGE modeli ─────────────────────────────────────────
class GraphSAGE(torch.nn.Module):
def __init__(self, in_ch, hidden, out_ch):
super().__init__()
self.conv1 = SAGEConv(in_ch, hidden, aggr="mean")
self.conv2 = SAGEConv(hidden, out_ch, aggr="mean")
def forward(self, x, edge_index):
x = F.relu(self.conv1(x, edge_index))
x = F.dropout(x, p=0.5, training=self.training)
return self.conv2(x, edge_index)
model = GraphSAGE(dataset.num_features, 64, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.003)
def train_epoch():
model.train()
total_loss = 0
for batch in train_loader:
optimizer.zero_grad()
out = model(batch.x, batch.edge_index)
# Yalnızca seed düğümler (batch_size kadar ilk düğüm)
loss = F.cross_entropy(
out[:batch.batch_size],
batch.y[:batch.batch_size]
)
loss.backward()
optimizer.step()
total_loss += float(loss)
return total_loss / len(train_loader)
for epoch in range(10):
loss = train_epoch()
print(f"Epoch {epoch:3d} | Loss: {loss:.4f}")
03 GAT — Graph Attention Network & Multi-head Attention
GAT, komşuların eşit ağırlıkta toplanması yerine öğrenilmiş dikkat katsayılarıyla farklı önem verir; Veličković et al. 2018.
GCN'de tüm komşular derece-normalize ağırlıklarla toplanır. GAT ise dikkat mekanizması öğrenir: α_{ij} = softmax(LeakyReLU(a^T [Wh_i || Wh_j])). Bu sayede model hangi komşunun daha önemli olduğuna bağlamsal olarak karar verebilir. Çok başlı dikkat (multi-head attention) çeşitliliği artırır; başlar genellikle concat veya ortalama ile birleştirilir.
import torch
import torch.nn.functional as F
from torch_geometric.nn import GATConv, GATv2Conv
from torch_geometric.datasets import Planetoid
dataset = Planetoid(root="/tmp/Cora", name="Cora")
data = dataset[0]
class GAT(torch.nn.Module):
def __init__(self, in_ch, hidden, out_ch,
heads=8, dropout=0.6):
super().__init__()
# İlk katman: 8 başlı, concat → hidden*heads boyut
self.conv1 = GATv2Conv(
in_ch, hidden, heads=heads,
dropout=dropout, concat=True
)
# Son katman: tek başlı, ortalama
self.conv2 = GATv2Conv(
hidden * heads, out_ch, heads=1,
dropout=dropout, concat=False
)
self.dropout = dropout
def forward(self, x, edge_index):
x = F.dropout(x, p=self.dropout, training=self.training)
x = F.elu(self.conv1(x, edge_index))
x = F.dropout(x, p=self.dropout, training=self.training)
return self.conv2(x, edge_index)
def attention_weights(self, x, edge_index):
"""Dikkat ağırlıklarını döndür — yorumlama için."""
_, (edge_idx, alpha) = self.conv1(
x, edge_index, return_attention_weights=True
)
return edge_idx, alpha # alpha: (E, heads)
model = GAT(dataset.num_features, 8, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.005,
weight_decay=5e-4)
for epoch in range(200):
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.cross_entropy(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
model.eval()
with torch.no_grad():
pred = model(data.x, data.edge_index).argmax(1)
acc = (pred[data.test_mask] == data.y[data.test_mask]).float().mean()
print(f"GAT Test Acc: {acc:.3f}") # ~0.83
04 Node Classification — Cora Dataset ile 3 Katman GCN
Cora, 7 sınıflı akademik makale ağıdır; node classification için standart benchmark ve PyG ile birlikte gelir.
Cora dataseti: 2708 düğüm (makale), 5429 kenar (atıf), 1433 boyutlu BOW özellik vektörü ve 7 sınıf. Standart split: 140 train, 500 validation, 1000 test. Tüm grafı belleğe almak mümkündür (transductive setting) — bu yüzden Cora başlangıç GCN deneyleri için idealdir. Daha büyük graflar için mini-batch gerekir.
import torch, torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root="/tmp/Cora", name="Cora",
transform=NormalizeFeatures())
data = dataset[0]
class GCN3(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv1 = GCNConv(1433, 128)
self.conv2 = GCNConv(128, 64)
self.conv3 = GCNConv(64, 7)
def forward(self, x, edge_index):
x = F.relu(self.conv1(x, edge_index))
x = F.dropout(x, 0.5, training=self.training)
x = F.relu(self.conv2(x, edge_index))
x = F.dropout(x, 0.5, training=self.training)
return self.conv3(x, edge_index)
def embed(self, x, edge_index):
"""Katman 2 çıkışını embedding olarak döndür."""
x = F.relu(self.conv1(x, edge_index))
return F.relu(self.conv2(x, edge_index))
device = "cuda" if torch.cuda.is_available() else "cpu"
model = GCN3().to(device)
data = data.to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01,
weight_decay=5e-4)
best_val_acc = 0
for epoch in range(300):
model.train()
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.cross_entropy(out[data.train_mask], data.y[data.train_mask])
loss.backward(); optimizer.step()
model.eval()
with torch.no_grad():
val_acc = (out[data.val_mask].argmax(1) == data.y[data.val_mask]).float().mean()
if float(val_acc) > best_val_acc:
best_val_acc = float(val_acc)
torch.save(model.state_dict(), "best_gcn.pt")
model.load_state_dict(torch.load("best_gcn.pt"))
model.eval()
with torch.no_grad():
out = model(data.x, data.edge_index)
acc = (out[data.test_mask].argmax(1) == data.y[data.test_mask]).float().mean()
print(f"Test Acc: {acc:.4f}")
05 Link Prediction — Pozitif/Negatif Örnek & Dot Product
Link prediction, var olmayan bir kenarın var olup olmayacağını tahmin eder; öneri sistemleri ve bilgi grafiği tamamlama için kritiktir.
Link prediction görevinde var olan kenarlar pozitif örnektir; rastgele seçilen var olmayan düğüm çiftleri negatif örnektir. Model, iki düğüm embedding'inin dot product'ı veya MLP skoruyla kenar olasılığı üretir. Eğitimde mevcut kenarların bir kısmı gizlenerek test için kullanılır.
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.utils import negative_sampling
from torch_geometric.transforms import RandomLinkSplit
# ── Train/val/test kenar split ───────────────────────────────
transform = RandomLinkSplit(
num_val=0.05, num_test=0.1,
is_undirected=True,
add_negative_train_samples=False,
)
data_train, data_val, data_test = transform(data)
# ── GCN encoder ──────────────────────────────────────────────
class GCNEncoder(torch.nn.Module):
def __init__(self, in_ch, hidden):
super().__init__()
self.conv1 = GCNConv(in_ch, 2 * hidden)
self.conv2 = GCNConv(2 * hidden, hidden)
def forward(self, x, edge_index):
x = F.relu(self.conv1(x, edge_index))
return self.conv2(x, edge_index)
# ── Dot product decoder ──────────────────────────────────────
def decode(z, edge_index):
"""Dot product: sigmoid(z_u · z_v)"""
src, dst = edge_index
return (z[src] * z[dst]).sum(dim=-1)
encoder = GCNEncoder(dataset.num_features, 64)
optimizer = torch.optim.Adam(encoder.parameters(), lr=0.01)
for epoch in range(100):
encoder.train()
optimizer.zero_grad()
z = encoder(data_train.x, data_train.edge_index)
# Negatif örnekler üret
neg_edge = negative_sampling(
edge_index=data_train.edge_index,
num_nodes=data_train.num_nodes,
num_neg_samples=data_train.edge_label_index.size(1),
)
edge_label_index = torch.cat(
[data_train.edge_label_index, neg_edge], dim=-1
)
edge_label = torch.cat([
data_train.edge_label,
data_train.edge_label.new_zeros(neg_edge.size(1))
])
out = decode(z, edge_label_index)
loss = F.binary_cross_entropy_with_logits(out, edge_label.float())
loss.backward(); optimizer.step()
if epoch % 20 == 0:
print(f"Epoch {epoch:3d} | Loss: {loss:.4f}")
06 Graph Classification — Global Pooling & Readout
Graf sınıflandırmada tüm düğüm embedding'lerini tek bir vektöre dönüştürmek için global pooling (readout) fonksiyonları kullanılır.
Node embedding'den graph embedding'e geçiş: global mean pool, global max pool, global add pool veya daha sofistike DiffPool ve Set2Set. Ortalama ve toplamın birleştirilmesi (sum+mean concat) pratikte iyi çalışır. Moleküler özellik tahmini, protein fonksiyon tahmini ve sosyal ağ analizi en yaygın uygulamalardır.
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv, global_mean_pool, global_max_pool
from torch_geometric.datasets import TUDataset
from torch_geometric.loader import DataLoader
# ── MUTAG: 188 molekül, 2 sınıf (mutagen/non-mutagen) ────────
dataset = TUDataset(root="/tmp/MUTAG", name="MUTAG")
dataset = dataset.shuffle()
train_ds = dataset[:150]
test_ds = dataset[150:]
train_loader = DataLoader(train_ds, batch_size=32, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=32)
class GraphCNN(torch.nn.Module):
def __init__(self, in_ch, hidden, out_ch):
super().__init__()
self.conv1 = GCNConv(in_ch, hidden)
self.conv2 = GCNConv(hidden, hidden)
self.conv3 = GCNConv(hidden, hidden)
self.fc = torch.nn.Linear(hidden * 2, out_ch)
def forward(self, x, edge_index, batch):
x = F.relu(self.conv1(x, edge_index))
x = F.relu(self.conv2(x, edge_index))
x = F.relu(self.conv3(x, edge_index))
# Global readout: mean || max concatenation
g = torch.cat([
global_mean_pool(x, batch),
global_max_pool(x, batch),
], dim=1)
return self.fc(g)
model = GraphCNN(dataset.num_features, 64, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
for epoch in range(100):
model.train()
for batch in train_loader:
optimizer.zero_grad()
out = model(batch.x, batch.edge_index, batch.batch)
loss = F.cross_entropy(out, batch.y)
loss.backward(); optimizer.step()
model.eval()
correct = 0
with torch.no_grad():
for batch in test_loader:
out = model(batch.x, batch.edge_index, batch.batch)
correct += (out.argmax(1) == batch.y).sum().item()
print(f"Test Acc: {correct / len(test_ds):.3f}")
07 Heterogeneous Graphs — HGT & R-GCN & HeteroData
Gerçek dünya grafları genellikle heterojendir: farklı tipte düğümler ve kenarlar farklı dönüşümler gerektirir.
Heterojen graflar, akademik ağlarda yazar-makale-dergi, e-ticarette kullanıcı-ürün-kategori gibi çoklu tip içerir. R-GCN (Relational GCN), her kenar tipi için ayrı ağırlık matrisi kullanır. HGT (Heterogeneous Graph Transformer), dikkat mekanizmasını düğüm ve kenar tipine göre uyarlar. PyG'de HeteroData nesnesi farklı tipleri ayrı ayrı saklar.
import torch
from torch_geometric.data import HeteroData
from torch_geometric.nn import HGTConv, Linear
import torch.nn.functional as F
# ── HeteroData oluşturma ─────────────────────────────────────
hetero = HeteroData()
# Kullanıcı ve ürün düğümleri
hetero["user"].x = torch.randn(100, 16) # 100 kullanıcı
hetero["product"].x = torch.randn(200, 32) # 200 ürün
hetero["user"].y = torch.randint(0, 2, (100,)) # ikili etiket
# Kenar tipleri
hetero["user", "rates", "product"].edge_index = torch.randint(0, 100, (2, 300))
hetero["user", "follows", "user"].edge_index = torch.randint(0, 100, (2, 150))
hetero["product", "similar_to", "product"].edge_index = torch.randint(0, 200, (2, 400))
# ── HGT modeli ───────────────────────────────────────────────
class HGT(torch.nn.Module):
def __init__(self, hidden, out, num_heads, metadata):
super().__init__()
self.lin_dict = torch.nn.ModuleDict({
t: Linear(-1, hidden) for t in metadata[0]
})
self.conv1 = HGTConv(hidden, hidden, metadata,
heads=num_heads)
self.conv2 = HGTConv(hidden, hidden, metadata,
heads=num_heads)
self.clf = Linear(hidden, out)
def forward(self, x_dict, edge_index_dict):
for node_type, lin in self.lin_dict.items():
x_dict[node_type] = F.gelu(lin(x_dict[node_type]))
x_dict = self.conv1(x_dict, edge_index_dict)
x_dict = {k: F.relu(v) for k, v in x_dict.items()}
x_dict = self.conv2(x_dict, edge_index_dict)
return self.clf(x_dict["user"])
model = HGT(64, 2, 4, hetero.metadata())
out = model(hetero.x_dict, hetero.edge_index_dict)
print(f"User embedding: {out.shape}") # (100, 2)
08 Knowledge Graph Embeddings — TransE, RotatE, PyKEEN
Bilgi grafiği embedding modelleri, (baş, ilişki, kuyruk) üçlülerini vektör uzayında temsil ederek eksik bağlantıları tamamlar.
TransE (Bordes et al., 2013): h + r ≈ t, yani baş vektörü + ilişki vektörü ≈ kuyruk vektörü. Basit ve etkili. RotatE: ilişkiyi kompleks uzayda rotasyon olarak modeller; simetri/asimetri ve tersinirlik gibi ilişki özelliklerini yakalayabilir. PyKEEN kütüphanesi 30+ modeli tek API ile sunar.
# pip install pykeen
from pykeen.pipeline import pipeline
from pykeen.datasets import Nations
# ── TransE ───────────────────────────────────────────────────
result_transe = pipeline(
dataset="Nations",
model="TransE",
model_kwargs={"embedding_dim": 50},
training_kwargs={"num_epochs": 100, "batch_size": 128},
optimizer_kwargs={"lr": 0.01},
loss="MarginRankingLoss",
random_seed=42,
)
print(result_transe.metric_results.to_dict())
# ── RotatE ────────────────────────────────────────────────────
result_rotate = pipeline(
dataset="FB15k237",
model="RotatE",
model_kwargs={"embedding_dim": 256},
training_kwargs={"num_epochs": 500},
negative_sampler="bernoulli",
)
# ── Tahmin: eksik bağlantı tamamlama ────────────────────────
model = result_transe.model
from pykeen.models.predict import predict_target
preds = predict_target(
model=model,
head="USA",
relation="commonbloc0",
triples_factory=result_transe.training,
)
print(preds.head(5)) # en yüksek skorlu kuyruk entity'leri
# ── Embedding'e erişim ────────────────────────────────────────
entity_emb = model.entity_representations[0]() # (n_entities, dim)
rel_emb = model.relation_representations[0]() # (n_rels, dim)
print(entity_emb.shape, rel_emb.shape)
09 Ölçekleme — Mini-batch Node Sampling & DGL ile Büyük Graflar
Milyonlarca düğüme sahip graflar belleğe sığmaz; neighbor sampling ve cluster partitioning bellek bütçesini sabit tutar.
Büyük ölçekli GNN eğitiminde iki temel yaklaşım: Neighbor Sampling (GraphSAGE, NeighborLoader) — her mini-batch için sabit boyutlu komşu örneklemesi yapar, bellek O(batch_size × fanout^layers) ile sınırlıdır. Cluster-GCN (Google, 2019) — grafı kümelere böler, her batch bir kümenin içinde kalır ve kenarlar arası kesimleri yok sayar. DGL (Deep Graph Library), büyük graflar için optimize edilmiş dağıtık eğitim desteği sunar.
import torch
import torch.nn.functional as F
from torch_geometric.nn import SAGEConv
from torch_geometric.loader import NeighborLoader, ClusterData, ClusterLoader
from torch_geometric.datasets import Reddit
# ── Reddit dataset: 232k düğüm, 11.6M kenar ─────────────────
dataset = Reddit(root="/tmp/Reddit")
data = dataset[0]
# ── Yöntem 1: NeighborLoader ─────────────────────────────────
train_loader = NeighborLoader(
data,
num_neighbors=[25, 10],
batch_size=1024,
input_nodes=data.train_mask,
num_workers=4,
)
# ── Yöntem 2: Cluster-GCN ────────────────────────────────────
cluster_data = ClusterData(data, num_parts=1500, recursive=False)
cluster_loader = ClusterLoader(cluster_data, batch_size=20, shuffle=True)
# ── Büyük ölçek GraphSAGE ────────────────────────────────────
class SAGE(torch.nn.Module):
def __init__(self, in_ch, hidden, out_ch, layers=3):
super().__init__()
self.convs = torch.nn.ModuleList()
self.convs.append(SAGEConv(in_ch, hidden))
for _ in range(layers - 2):
self.convs.append(SAGEConv(hidden, hidden))
self.convs.append(SAGEConv(hidden, out_ch))
def forward(self, x, edge_index):
for i, conv in enumerate(self.convs):
x = conv(x, edge_index)
if i < len(self.convs) - 1:
x = F.relu(x)
x = F.dropout(x, 0.5, training=self.training)
return x
model = SAGE(dataset.num_features, 256, dataset.num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# ── Eğitim ───────────────────────────────────────────────────
for epoch in range(5):
model.train()
total_loss = total_nodes = 0
for batch in train_loader:
optimizer.zero_grad()
out = model(batch.x, batch.edge_index)[:batch.batch_size]
loss = F.cross_entropy(out, batch.y[:batch.batch_size])
loss.backward(); optimizer.step()
total_loss += float(loss) * batch.batch_size
total_nodes += batch.batch_size
print(f"Epoch {epoch} | Loss: {total_loss/total_nodes:.4f}")
GCN: basit ve etkili, küçük-orta graflar için. GraphSAGE: inductive, büyük graflar ve mini-batch için. GAT: dikkat mekanizmasıyla daha iyi performans, ancak bellek gereksinimleri yüksek. Büyük ölçek için NeighborLoader veya Cluster-GCN ile GraphSAGE kombinasyonu en yaygın üretim tercihidir.