Skip to content

Commit 964fced

Browse files
committed
feat: add Node2Vec for node classification
1 parent c888c87 commit 964fced

14 files changed

Lines changed: 1544 additions & 190 deletions

File tree

docs/getting-started/tutorials.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ scripts along with a brief description.
4444
| [hgnnp.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/node_classification/hgnnp.py) | HGNNP NC pipeline on `AlgebraDataset` (LPE enricher) | `make run examples/node_classification/hgnnp.py` |
4545
| [hypergcn.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/node_classification/hypergcn.py) | HyperGCN NC pipeline on `AlgebraDataset` (LPE enricher) | `make run examples/node_classification/hypergcn.py` |
4646
| [mlp.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/node_classification/mlp.py) | MLP NC pipeline on `AlgebraDataset` (LPE enricher) | `make run examples/node_classification/mlp.py` |
47+
| [node2vecgcn.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/node_classification/node2vecgcn.py) | Compute Node2Vec embeddings then train Node2Vec+GCN NC | `make run examples/node_classification/node2vecgcn.py` |
48+
| [node2vecslp.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/node_classification/node2vecslp.py) | Compute Node2Vec embeddings then train Node2Vec+SLP NC | `make run examples/node_classification/node2vecslp.py` |
4749
| [villain.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/node_classification/villain.py) | VilLain NC pipeline on `AlgebraDataset` (degree labels) | `make run examples/node_classification/villain.py` |
4850
| **Sampling strategies** | | |
4951
| [custom_negative_sampler.py](https://github.com/hypernetwork-research-group/hypertorch/blob/main/examples/sampling/custom_negative_sampler.py) | Custom negative sampling for HLP | `make run examples/sampling/custom_negative_sampler.py` |

docs/user-guide/models.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Supported models include:
3535
- `HNHN`.
3636
- `HyperGCN`.
3737
- `MLP`.
38+
- `Node2VecGCN`.
39+
- `Node2VecSLP`.
3840
- `VilLain`.
3941

4042
## Minimal hyperlink prediction example: NHP
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
from torchmetrics import MetricCollection
2+
from torchmetrics.classification import MulticlassAUROC, MulticlassAccuracy, MulticlassF1Score
3+
4+
from hypertorch.data import AlgebraDataset, DataLoader, Node2VecEnricher
5+
from hypertorch.nc import Node2VecGCNNcConfig, Node2VecGCNEncoderConfig, Node2VecGCNNcModule
6+
from hypertorch.train import MultiModelTrainer
7+
from hypertorch.types import ModelConfig
8+
from hypertorch.utils import node_labels_from_node_degrees
9+
10+
11+
if __name__ == "__main__":
12+
verbose = False
13+
num_workers = 8
14+
num_features = 32
15+
num_classes = 3
16+
metrics = MetricCollection(
17+
{
18+
"auc": MulticlassAUROC(num_classes=num_classes),
19+
"accuracy": MulticlassAccuracy(num_classes=num_classes),
20+
"f1": MulticlassF1Score(num_classes=num_classes),
21+
}
22+
)
23+
24+
print("Loading and preparing dataset...")
25+
26+
dataset = AlgebraDataset(sampling_strategy="node", task="node-classification")
27+
dataset.hdata.y = node_labels_from_node_degrees(
28+
node_incidences=dataset.hdata.hyperedge_index[0],
29+
num_nodes=dataset.hdata.num_nodes,
30+
num_classes=num_classes,
31+
)
32+
33+
train_dataset, val_dataset, test_dataset = dataset.split(
34+
ratios=[0.7, 0.1, 0.2],
35+
node_space_setting="transductive",
36+
shuffle=True,
37+
seed=42,
38+
)
39+
40+
print("Computing Node2Vec embeddings from the train graph...")
41+
42+
node2vec_enricher = Node2VecEnricher(
43+
num_features=num_features,
44+
context_size=10,
45+
walk_length=20,
46+
num_walks_per_node=10,
47+
num_negative_samples=1,
48+
num_nodes=dataset.hdata.num_nodes,
49+
num_epochs=10,
50+
learning_rate=0.01,
51+
batch_size=128,
52+
sparse=False,
53+
verbose=verbose,
54+
)
55+
train_dataset.enrich_node_features(
56+
enricher=node2vec_enricher,
57+
enrichment_mode="replace",
58+
)
59+
val_dataset.enrich_node_features_from(train_dataset)
60+
test_dataset.enrich_node_features_from(train_dataset)
61+
62+
print("Creating dataloaders...")
63+
64+
train_loader = DataLoader(
65+
train_dataset,
66+
batch_size=128,
67+
shuffle=False,
68+
num_workers=num_workers,
69+
persistent_workers=True,
70+
)
71+
val_loader = DataLoader(
72+
val_dataset,
73+
batch_size=128,
74+
shuffle=False,
75+
num_workers=num_workers,
76+
persistent_workers=True,
77+
)
78+
test_loader = DataLoader(
79+
test_dataset,
80+
sample_full_hypergraph=True,
81+
shuffle=False,
82+
num_workers=num_workers,
83+
persistent_workers=True,
84+
)
85+
86+
gcn_config: Node2VecGCNNcConfig = {
87+
"out_channels": num_classes,
88+
"hidden_channels": num_features,
89+
"num_layers": 2,
90+
"drop_rate": 0.1,
91+
"bias": True,
92+
"improved": False,
93+
"add_self_loops": True,
94+
"normalize": True,
95+
"cached": False,
96+
"graph_reduction_strategy": "clique_expansion",
97+
"num_nodes": dataset.hdata.num_nodes,
98+
}
99+
precomputed_config: Node2VecGCNEncoderConfig = {
100+
"mode": "precomputed",
101+
"num_features": num_features,
102+
"node2vec_config": {},
103+
"gcn_config": gcn_config,
104+
}
105+
106+
node2vecgcn_precomputed = Node2VecGCNNcModule(
107+
classifier_config=precomputed_config,
108+
lr=0.001,
109+
weight_decay=0.0,
110+
metrics=metrics,
111+
)
112+
113+
node2vecgcn_joint = Node2VecGCNNcModule(
114+
classifier_config={
115+
"mode": "joint",
116+
"num_features": num_features,
117+
"node2vec_config": {
118+
"context_size": 10,
119+
"walk_length": 20,
120+
"num_walks_per_node": 10,
121+
"p": 1.0,
122+
"q": 1.0,
123+
"num_negative_samples": 1,
124+
"train_hyperedge_index": train_dataset.hdata.hyperedge_index,
125+
"num_nodes": dataset.hdata.num_nodes,
126+
"graph_reduction_strategy": "clique_expansion",
127+
"random_walk_batch_size": 128,
128+
"node2vec_loss_weight": 0.4,
129+
},
130+
"gcn_config": gcn_config,
131+
},
132+
lr=0.001,
133+
weight_decay=0.0,
134+
metrics=metrics,
135+
)
136+
137+
configs = [
138+
ModelConfig(
139+
name="node2vecgcn-precomputed",
140+
version="node-classification",
141+
model=node2vecgcn_precomputed,
142+
train_dataloader=train_loader,
143+
val_dataloader=val_loader,
144+
test_dataloader=test_loader,
145+
),
146+
ModelConfig(
147+
name="node2vecgcn-joint",
148+
version="node-classification",
149+
model=node2vecgcn_joint,
150+
train_dataloader=train_loader,
151+
val_dataloader=val_loader,
152+
test_dataloader=test_loader,
153+
),
154+
]
155+
156+
print("Starting training and evaluation...")
157+
158+
with MultiModelTrainer(
159+
model_configs=configs,
160+
max_epochs=60,
161+
accelerator="auto",
162+
log_every_n_steps=1,
163+
enable_checkpointing=False,
164+
devices=1,
165+
test_devices=1,
166+
enable_model_summary=True,
167+
) as trainer:
168+
trainer.fit_all(train_dataloader=train_loader, val_dataloader=val_loader, verbose=True)
169+
trainer.test_all(dataloader=test_loader, verbose=True)
170+
171+
print("Complete!")
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
from torchmetrics import MetricCollection
2+
from torchmetrics.classification import MulticlassAUROC, MulticlassAccuracy, MulticlassF1Score
3+
4+
from hypertorch.data import AlgebraDataset, DataLoader, Node2VecEnricher
5+
from hypertorch.nc import Node2VecSLPNcModule
6+
from hypertorch.train import MultiModelTrainer
7+
from hypertorch.types import ModelConfig
8+
from hypertorch.utils import node_labels_from_node_degrees
9+
10+
11+
if __name__ == "__main__":
12+
verbose = False
13+
num_workers = 8
14+
num_features = 32
15+
num_classes = 3
16+
metrics = MetricCollection(
17+
{
18+
"auc": MulticlassAUROC(num_classes=num_classes),
19+
"accuracy": MulticlassAccuracy(num_classes=num_classes),
20+
"f1": MulticlassF1Score(num_classes=num_classes),
21+
}
22+
)
23+
24+
print("Loading and preparing dataset...")
25+
26+
dataset = AlgebraDataset(sampling_strategy="node", task="node-classification")
27+
dataset.hdata.y = node_labels_from_node_degrees(
28+
node_incidences=dataset.hdata.hyperedge_index[0],
29+
num_nodes=dataset.hdata.num_nodes,
30+
num_classes=num_classes,
31+
)
32+
33+
train_dataset, val_dataset, test_dataset = dataset.split(
34+
ratios=[0.7, 0.1, 0.2],
35+
node_space_setting="transductive",
36+
shuffle=True,
37+
seed=42,
38+
)
39+
40+
print("Computing Node2Vec embeddings from the train graph...")
41+
42+
node2vec_enricher = Node2VecEnricher(
43+
num_features=num_features,
44+
context_size=10,
45+
walk_length=20,
46+
num_walks_per_node=10,
47+
num_negative_samples=1,
48+
num_nodes=dataset.hdata.num_nodes,
49+
num_epochs=10,
50+
learning_rate=0.01,
51+
batch_size=128,
52+
sparse=False,
53+
verbose=verbose,
54+
)
55+
train_dataset.enrich_node_features(
56+
enricher=node2vec_enricher,
57+
enrichment_mode="replace",
58+
)
59+
val_dataset.enrich_node_features_from(train_dataset)
60+
test_dataset.enrich_node_features_from(train_dataset)
61+
62+
print("Creating dataloaders...")
63+
64+
train_loader = DataLoader(
65+
train_dataset,
66+
batch_size=128,
67+
shuffle=False,
68+
num_workers=num_workers,
69+
persistent_workers=True,
70+
)
71+
val_loader = DataLoader(
72+
val_dataset,
73+
batch_size=128,
74+
shuffle=False,
75+
num_workers=num_workers,
76+
persistent_workers=True,
77+
)
78+
test_loader = DataLoader(
79+
test_dataset,
80+
sample_full_hypergraph=True,
81+
shuffle=False,
82+
num_workers=num_workers,
83+
persistent_workers=True,
84+
)
85+
86+
node2vecslp_precomputed = Node2VecSLPNcModule(
87+
classifier_config={
88+
"mode": "precomputed",
89+
"num_features": num_features,
90+
"out_channels": num_classes,
91+
"node2vec_config": {},
92+
},
93+
lr=0.001,
94+
weight_decay=0.0,
95+
metrics=metrics,
96+
)
97+
98+
node2vecslp_joint = Node2VecSLPNcModule(
99+
classifier_config={
100+
"mode": "joint",
101+
"num_features": num_features,
102+
"out_channels": num_classes,
103+
"node2vec_config": {
104+
"context_size": 10,
105+
"walk_length": 20,
106+
"num_walks_per_node": 10,
107+
"p": 1.0,
108+
"q": 1.0,
109+
"num_negative_samples": 1,
110+
"train_hyperedge_index": train_dataset.hdata.hyperedge_index,
111+
"num_nodes": dataset.hdata.num_nodes,
112+
"graph_reduction_strategy": "clique_expansion",
113+
"random_walk_batch_size": 128,
114+
"node2vec_loss_weight": 0.4,
115+
},
116+
},
117+
lr=0.001,
118+
weight_decay=0.0,
119+
metrics=metrics,
120+
)
121+
122+
configs = [
123+
ModelConfig(
124+
name="node2vecslp-precomputed",
125+
version="node-classification",
126+
model=node2vecslp_precomputed,
127+
train_dataloader=train_loader,
128+
val_dataloader=val_loader,
129+
test_dataloader=test_loader,
130+
),
131+
ModelConfig(
132+
name="node2vecslp-joint",
133+
version="node-classification",
134+
model=node2vecslp_joint,
135+
train_dataloader=train_loader,
136+
val_dataloader=val_loader,
137+
test_dataloader=test_loader,
138+
),
139+
]
140+
141+
print("Starting training and evaluation...")
142+
143+
with MultiModelTrainer(
144+
model_configs=configs,
145+
max_epochs=60,
146+
accelerator="auto",
147+
log_every_n_steps=1,
148+
enable_checkpointing=False,
149+
devices=1,
150+
test_devices=1,
151+
) as trainer:
152+
trainer.fit_all(train_dataloader=train_loader, val_dataloader=val_loader, verbose=True)
153+
trainer.test_all(dataloader=test_loader, verbose=True)
154+
155+
print("Complete!")

hypertorch/hlp/__init__.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,18 @@
1616

1717
from .nhp_hlp import NHPEncoderConfig, NHPHlpModule, NHPRankingLoss
1818

19-
from .node2vec_common import (
20-
NODE2VEC_JOINT_MODE,
21-
NODE2VEC_PRECOMPUTED_MODE,
22-
Node2VecGCNHlpConfig,
23-
Node2VecHlpConfig,
24-
Node2VecMode,
25-
)
26-
2719
from .node2vecgcn_hlp import Node2VecGCNEncoderConfig, Node2VecGCNHlpModule
2820

2921
from .node2vecslp_hlp import Node2VecSLPEncoderConfig, Node2VecSLPHlpModule
3022

3123
from .villain_hlp import VilLainEncoderConfig, VilLainHlpModule
3224

25+
from hypertorch.models.node2vec_common import (
26+
Node2VecGCNEncoderConfig as Node2VecGCNHlpConfig,
27+
Node2VecEncoderConfig as Node2VecHlpConfig,
28+
)
29+
3330
__all__ = [
34-
"NODE2VEC_JOINT_MODE",
35-
"NODE2VEC_PRECOMPUTED_MODE",
3631
"CommonNeighborsHlpModule",
3732
"GCNEncoderConfig",
3833
"GCNHlpModule",
@@ -54,7 +49,6 @@
5449
"Node2VecGCNHlpConfig",
5550
"Node2VecGCNHlpModule",
5651
"Node2VecHlpConfig",
57-
"Node2VecMode",
5852
"Node2VecSLPEncoderConfig",
5953
"Node2VecSLPHlpModule",
6054
"VilLainEncoderConfig",

0 commit comments

Comments
 (0)