Skip to content

MixHop on Cora

Author: K3-Node Team
Backend: Multi-Backend
Dataset: Cora (Planetoid)
Description: Classify papers in the Cora citation network by topic.

View in Colab   GitHub source


MixHop on Cora

Classify papers in the Cora citation network by topic. MixHop (Abu-El-Haija et al., 2019) mixes neighbors at several distances in one layer: each layer concatenates the features of the node itself, its neighbors, and its 2-hop neighbors. Training uses SGD with a learning rate that drops 100x every 40 epochs.

Same model as PyG's examples/mixhop.py.

Install K3-Node, then choose a backend: "tensorflow", "torch" or "jax"

!pip install k3-node[examples]
import os
os.environ["KERAS_BACKEND"] = "tensorflow"

Load the data

data holds one graph: node features x, edges edge_index, labels y, and masks marking the training, validation and test nodes.

import keras
from keras import ops
from k3_node.datasets import Planetoid
from k3_node.loader import FullGraphDataset

dataset = Planetoid("data/Planetoid", name="Cora")
data = dataset[0]
print(data)

Define the model

from k3_node.layers import BatchNorm, MixHopConv


class MixHop(keras.Model):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.convs = [MixHopConv(in_channels, 60, powers=[0, 1, 2]),
                      MixHopConv(3 * 60, 60, powers=[0, 1, 2]),
                      MixHopConv(3 * 60, 60, powers=[0, 1, 2])]
        self.norms = [BatchNorm(3 * 60) for _ in range(3)]
        self.input_dropout = keras.layers.Dropout(0.7)
        self.dropout = keras.layers.Dropout(0.9)
        self.lin = keras.layers.Dense(out_channels)

    def call(self, data, training=False):
        x = self.input_dropout(data.x, training=training)
        for conv, norm in zip(self.convs, self.norms):
            x = norm(conv(x, data.edge_index), training=training)
            x = self.dropout(x, training=training)
        return self.lin(x)


model = MixHop(dataset.num_features, dataset.num_classes)

Train

FullGraphDataset feeds the whole graph to Keras; mask selects which nodes count in the loss and the accuracy.

model.compile(
    optimizer=keras.optimizers.SGD(
        learning_rate=keras.optimizers.schedules.ExponentialDecay(
            0.5, decay_steps=40, decay_rate=0.01, staircase=True),  # x0.01 every 40 epochs
        weight_decay=0.005,
    ),
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    weighted_metrics=["accuracy"],
)
model.fit(
    FullGraphDataset(data, mask="train_mask"),
    validation_data=FullGraphDataset(data, mask="val_mask"),
    epochs=100,
    verbose=2,
)

Evaluate

loss, accuracy = model.evaluate(FullGraphDataset(data, mask="test_mask"), verbose=0)
print(f"Test accuracy: {accuracy:.4f}")