| """Cora citation network dataset loader.""" |
|
|
| import os |
| import urllib.request |
| import numpy as np |
| import torch |
|
|
|
|
| CORA_URL = "https://raw.githubusercontent.com/tkipf/pygraph/gcn/master/data/cora/" |
|
|
|
|
| def _download(raw_dir="graph/gcn/raw"): |
| os.makedirs(raw_dir, exist_ok=True) |
| for fname in ["cora.content", "cora.cites"]: |
| path = os.path.join(raw_dir, fname) |
| if not os.path.exists(path): |
| urllib.request.urlretrieve(CORA_URL + fname, path) |
|
|
|
|
| def _encode_onehot(labels): |
| classes = sorted(set(labels)) |
| mapping = {c: i for i, c in enumerate(classes)} |
| return torch.eye(len(classes))[torch.tensor([mapping[l] for l in labels])], mapping |
|
|
|
|
| def load_cora(raw_dir="graph/gcn/raw"): |
| _download(raw_dir) |
|
|
| |
| content = np.genfromtxt(os.path.join(raw_dir, "cora.content"), dtype=np.dtype(str)) |
| node_ids = content[:, 0].astype(int) |
| features = torch.tensor(content[:, 1:-1].astype(np.float32)) |
| labels_onehot, class_mapping = _encode_onehot(content[:, -1]) |
| labels = labels_onehot.argmax(dim=1) |
|
|
| n = len(node_ids) |
| id_to_idx = {int(nid): i for i, nid in enumerate(node_ids)} |
|
|
| |
| cites = np.genfromtxt(os.path.join(raw_dir, "cora.cites"), dtype=np.int32) |
| adj = torch.zeros((n, n), dtype=torch.float32) |
| for src, tgt in cites: |
| if src in id_to_idx and tgt in id_to_idx: |
| adj[id_to_idx[src], id_to_idx[tgt]] = 1.0 |
| adj[id_to_idx[tgt], id_to_idx[src]] = 1.0 |
|
|
| |
| rowsum = adj.sum(dim=1).clamp(min=1e-8) |
| d_inv_sqrt = torch.diag(rowsum ** -0.5) |
| adj_norm = d_inv_sqrt @ adj @ d_inv_sqrt |
|
|
| |
| n_classes = len(class_mapping) |
| idx_per_class = [torch.where(labels == c)[0] for c in range(n_classes)] |
|
|
| train_idx = torch.cat([idx[:20] for idx in idx_per_class]) |
| rest = torch.cat([idx[20:] for idx in idx_per_class]) |
| val_idx = rest[:500] |
| test_idx = rest[500:1500] |
|
|
| train_mask = torch.zeros(n, dtype=torch.bool) |
| val_mask = torch.zeros(n, dtype=torch.bool) |
| test_mask = torch.zeros(n, dtype=torch.bool) |
| train_mask[train_idx] = True |
| val_mask[val_idx] = True |
| test_mask[test_idx] = True |
|
|
| print(f"Cora: {n} nodes, {features.size(1)} features, {n_classes} classes") |
| print(f" Train: {train_idx.size(0)} Val: {val_idx.size(0)} Test: {test_idx.size(0)}") |
|
|
| return features, adj_norm, labels, train_mask, val_mask, test_mask, class_mapping |
|
|