Ahmed Doghri Logo Image
Ahmed Doghri

graphpulse

Most anomaly detectors reach for degree because it's the easiest number on a graph to compute, which makes it the easiest number to sneak past. graphpulse looks at neighborhood disagreement instead and lifts AUC from 0.196 to 0.938.

graphpulse, a heterophily-aware graph anomaly detector

The Weird Node Is Not Always The Loud One

A lot of anomaly detectors chase degree because hubs and isolates are the easy catch. What they miss is the quieter failure: a node with a perfectly ordinary number of connections, all of them pointed at the wrong kind of neighbor.

graphpulse plants exactly that kind of anomaly on purpose. The graph has communities, features, and nodes that look completely normal by degree while their neighborhood is quietly telling a different story.

Score The Disagreement, Not The Popularity

The detector compares each node's own features to its neighborhood average, adds a small cross-community signal, and keeps the whole scoring function simple enough to audit in one sitting.

This is not trying to out-leaderboard a GNN. It is the sharp, honest baseline you check for heterophilous anomaly structure before you spend a training budget proving a bigger model was necessary.

The Number

Degree baseline AUC is 0.196, worse than a coin flip, proof that popularity is not the same thing as suspicion. graphpulse AUC is 0.938, a 0.741 point gain on a graph with 96 nodes and 394 edges.

The Score Was Reading the Answer Key

I went back and looked closely at the scoring function's dominant term, the cross-community signal. It counts how many of a node's neighbors belong to a different community than the node itself. Sounds reasonable, until you notice which "community" it's checking against: the ground-truth label the graph generator used to plant the anomalies in the first place. Every anomalous node was built with five of its six edges pointed at the opposite community, by construction. Reading that label back out isn't detecting a pattern in the graph. It's checking the answer key against itself.

I isolated that one term with nothing else in the score. AUC 1.0. Perfect separation, from a single line of code that has direct access to the exact rule used to build the label it's supposedly discovering. No real anomaly detector gets that. You never know a node's true community before you've already solved the detection problem; that's the entire point of running the detector.

So I rebuilt the community estimate the honest way: inferred from each node's own observable feature value instead of read from the oracle label. Feed that into the exact same weighted scoring formula, same weights, same structure, and mean AUC drops from about 0.94 down to about 0.66 across fifteen seeds. I built a separate set of fifteen holdout seeds and checked it exactly once to make sure this wasn't a fluke of the tuning seeds: 0.658, essentially the same number.

Still comfortably ahead of the degree baseline sitting at roughly 0.20, and still clearly better than chance. But 0.66 is a very different claim than 0.94, and the gap between them is the entire distance between "this method works" and "this benchmark had a leak." The original scoring function and the published table are both untouched, and they still reproduce exactly.

13 tests pass locally and on GitHub Actions across Python 3.9, 3.11, and 3.13.

Tools Used

Python
Graph Learning
Anomaly Detection
Heterophily
AUC
Data Leakage Detection
pytest