Official PyTorch/DGL implementation of TopSCAN, a lightweight, structure-aware sparse cross-modal alignment framework for Multimodal Attributed Graphs (MAGs). TopSCAN serves as a drop-in frontend enhancement for classic GNN backbones (GraphSAGE, RevGAT, GCN, GAT, SGC) with negligible extra multimodal modeling overhead.
Paper Reference: TopSCAN: Structure-Aware Sparse Cross-Modal Alignment for Multimodal Attributed Graphs on Digital Platforms. Submitted for review.
- Linux (Ubuntu 20.04+ recommended)
- NVIDIA GPU (≥ 12 GB VRAM for large datasets; CUDA 11.7 / 11.8 tested)
conda create -n topscan python=3.10 -y
conda activate topscanPlease match your CUDA version. Example for CUDA 11.8:
# PyTorch
pip install torch==2.0.1+cu118 --index-url https://download.pytorch.org/whl/cu118
# DGL (Deep Graph Library)
pip install dgl -f https://data.dgl.ai/wheels/cu118/repo.htmlFor CUDA 11.7, replace cu118 with cu117 accordingly. For CPU-only builds, drop the CUDA suffixes.
pip install -r requirements.txtrequirements.txt contains minimal pure-Python dependencies (numpy, pandas, scikit-learn, tqdm).
Only needed if you want to experiment with ogbn-arxiv. Install via:
pip install ogbTopSCAN expects three input artifacts per dataset, placed under any directory (default layout ./data/<DATA_NAME>/):
data/
└── Movies/
├── graph.dgl # DGL graph object with node 'label' in ndata
├── text_feat.pt # torch.Tensor of shape [N, D_text]
└── visual_feat.pt # torch.Tensor of shape [N, D_image]
Saved with dgl.save_graphs(). The graph object must satisfy:
graph.num_nodes() == N # N node count
graph.ndata["label"] # torch.LongTensor [N] # node class labels- If you have an undirected graph, ensure it is bidirectional (use
dgl.to_bidirected()before saving) so that GraphSAGE / RevGAT operate correctly. - Edge features are optional and not required by the current implementation.
text_feat.pt— Pre-extracted textual node embeddings (e.g. from BERT / Sentence-BERT / CLIP-Text). Shape[N, D_text],torch.float32.visual_feat.pt— Pre-extracted visual node embeddings (e.g. from ResNet / ViT / CLIP-Vision). Shape[N, D_image],torch.float32.
Both tensors should be row-wise aligned with node IDs in graph.dgl (the i-th row corresponds to node i).
By default, topscan.data.graph_data.load_data randomly splits nodes with ratios train:val:test = 60%:20%:20% (seed fixed to 42). To reproduce exact paper numbers, you can:
- Either use the defaults (for a quick comparison in the same split regime), or
- Pre-generate split indices and pass them by modifying the
load_datahelper.
git clone <your-repo-url> TopSCAN && cd TopSCANexport PYTHONPATH="$(pwd)/src:$PYTHONPATH"
python -c "from topscan.models.topscan_model import TopSCAN; print('TopSCAN import OK')"# Place your Movies data under ./data/Movies/ as described above, then run:
bash scripts/run_topscan_graphsage.sh MoviesThe script accepts positional arguments:
bash scripts/run_topscan_graphsage.sh <DATA_NAME> <GRAPH_PATH> <TEXT_FEAT> <VISUAL_FEAT> <OUTPUT_CSV> <GPU_ID>bash scripts/run_topscan_revgat.sh Toysbash scripts/run_grid_search.sh GroceryThis sweeps over backbone (GraphSAGE / RevGAT), hidden dims, learning rates, dropout rates, CMA heads, pruning policy thresholds, etc., and writes every configuration's mean±std metrics to a CSV.
The following configurations correspond to the main experiments in our paper (Overall Performance Comparison on Movies / Toys / Grocery / Reddit-S).
bash scripts/run_topscan_graphsage.sh <DATASET_NAME>Key configuration (already set inside script):
--gnn_type GraphSAGE --n_layers 2 --n_hidden 128 --lr 0.005 --dropout 0.5--warmup_fusion concat_absdiff --cma_num_heads 4 --cma_head_dim 32 --cma_norm_by dst--policy_mode on --policy_k 2 --policy_q 0.95 --policy_top_p 0.15--enhance_cross_graph 1 --use_enhanced_graph_in_backbone 0--n_runs 5 --n_epochs 1000 --early_stop_patience 100
bash scripts/run_topscan_revgat.sh <DATASET_NAME>Key configuration:
--gnn_type RevGAT --n_layers 3 --n_hidden 256 --n_heads 3 --lr 0.005 --dropout 0.5--attn_drop 0.1 --edge_drop 0.1 --alpha 0.1 --use_attn_dst 1 --use_symmetric_norm 1- Same TopSCAN / Policy hyper-parameters as GraphSAGE variant (CMA heads increased to 8 for RevGAT's larger capacity).
For reviewers' convenience, the paper's reported numbers use the same n_runs=5, seed=42..46 scheme as the defaults here.
TopSCAN/
├── scripts/
│ ├── run_topscan_graphsage.sh # GraphSAGE+TopSCAN quick run
│ ├── run_topscan_revgat.sh # RevGAT+TopSCAN quick run
│ └── run_grid_search.sh # Full grid search over hyper-params
├── src/
│ └── topscan/
│ ├── __init__.py
│ ├── runner.py # **Main entry point** (CLI + train/eval loop)
│ ├── data/
│ │ └── graph_data.py # DGL graph loading + split + seed
│ ├── models/
│ │ ├── topscan_model.py # TopSCAN class (project + CMA + fuse + GNN + cls)
│ │ └── backbones/
│ │ ├── gcn.py # Minimal GCN
│ │ ├── gat.py # Minimal GAT
│ │ ├── graphsage.py # Minimal GraphSAGE
│ │ └── RevGAT/ # RevGAT full implementation
│ ├── modules/
│ │ ├── cross_modal_attention.py # CrossModalGraphAttention (CMA)
│ │ └── graph_enhanced/
│ │ ├── framework/contracts.py # Typing / data-pack contracts
│ │ └── pre/
│ │ ├── candidate_builder.py # k-hop + source-wise quantile + top-m/p
│ │ └── graph_constructor.py # Enhanced graph construction orchestrator
│ └── utils/
│ ├── loss_function.py # CE with label smoothing, accuracy/F1, EarlyStopping, LR warmup
│ └── model_config.py # argparse groups: add_common_args / add_topscan_args
├── examples/ # (Optional) sample configs / tutorials
├── requirements.txt
├── .gitignore
├── LICENSE
└── README.md
This repository is released under the MIT License. The RevGAT sub-module (see src/topscan/models/backbones/RevGAT/) retains its original license (included in-situ where applicable).
For code-related issues, please open an issue in the repository. For paper correspondence, please contact the authors via the review process channel.