---
name: graph-fraud
description: >
  Audit and align graph fraud detection (GFD) data loading, splits, and
  10-run evaluation to the BWGNN (ICML 2022) community protocol used on
  YelpChi, Amazon, T-Finance, and T-Social. Use when the user mentions
  GFD, graph-fraud, BWGNN protocol, abundant/scarce, 有监督/半监督,
  TFinance, TSocial, YelpChi, Amazon 3305, dataset.py 检查, 对齐划分,
  10 seeds / --run 10, or runs /graph-fraud. Not for GADBench-only
  tables unless the user names GADBench.
---

# Graph fraud protocol (BWGNN)

Align the current repo to the field-default GFD setup from BWGNN
(`squareRoot3/Rethinking-Anomaly-Detection`). Numbers and loaders live in
[references/bwgnn-protocol.md](references/bwgnn-protocol.md). Read it
before editing.

`random_state=2` is the **split seed**, not model init.
`--run 10` is 10 **re-inits on that fixed split**.
Do not implement “10 different splits”.

## When to use

- Check `dataset.py` / `data.py` / configs against YelpChi, Amazon, T-Finance, T-Social
- Add or fix abundant (40%) / scarce (1%, T-Social 0.01%) splits
- Add BWGNN-style `--run 10` mean±std
- User says 对齐 BWGNN / GFD 通用设置 / 10 seeds

## When not to use

| Request | Do this instead |
|---|---|
| Align to GADBench precomputed `train_masks` | Follow GADBench; do not rewrite to `random_state=2` |
| CARE-GNN 40/60 no-val split | Only if the user names CARE-GNN |
| Invent a new dataset protocol | Stop; this skill only standardizes the four GFD datasets |

## Modes

| User signal | Mode |
|---|---|
| 检查 / audit / 是否符合 | `audit` |
| 对齐 / 改数据 / 配 10-run / 修 dataset.py | `fix` (default if they want the repo usable) |

`audit` does not write files. `fix` patches the smallest data/train entry points and re-audits.

## Workflow

### 1. Locate

Find, do not assume names:

- Loaders: `dataset.py`, `data.py`, `data_loader.py`, `DataHelper/`
- Entry: `train.py`, `main.py`, `run_*.py`
- Configs: `configs/*.yaml`, `config/*.yml`
- Data: `YelpChi.mat`, `Amazon.mat`, DGL binaries named `tfinance` / `tsocial`

Run `scripts/audit_gfd_protocol.py` from this skill directory (the folder that contains this `SKILL.md`):

```bash
python scripts/audit_gfd_protocol.py <repo-root>
```

Then read the hit files. The script only finds markers; you judge the code.

### 2. Audit (required in both modes)

Check every box against [references/bwgnn-protocol.md](references/bwgnn-protocol.md).
A failed box in `fix` mode is a patch target.

```text
[ ] Amazon eligible starts at 3305; those nodes never enter train/val/test
[ ] Yelp / T-Finance / T-Social use all labeled nodes
[ ] T-Finance labels go through argmax(1) when loaded from DGL
[ ] T-Finance / T-Social use load_graphs, not .mat
[ ] Split is stratified train_test_split, random_state=2 both times
[ ] Remainder uses test_size=0.67 (val:test ≈ 1:2)
[ ] abundant train_ratio=0.4; scarce=0.01; T-Social scarce=0.0001
[ ] Split is computed once (or identically) and reused for all runs
[ ] --run N (default 1; protocol 10) rebuilds the model each time
[ ] run index is not passed to train_test_split
[ ] No --seed flag that changes the split
[ ] Comments cite BWGNN (Tang et al., ICML 2022)
[ ] Homo vs hetero is a model choice, not a second split
[ ] F1: val search linspace(0.05, 0.95, 19) unless the user keeps 0.5 and labels it
```

Report as a table: `item | status | file:line | needed change`.

### 3. Fix (only in `fix`)

Touch only data loading, split, argparse, and the run loop.
Do not refactor the model.

**Split helper** (put next to the existing loader; names may match the repo):

```python
# BWGNN (Tang et al., ICML 2022, official main.py):
# split seed is fixed at 2. --run only re-inits the model.
SPLIT_SEED = 2


def eligible_index(dataset: str, n: int):
    # BWGNN / CARE-GNN / DGL FraudAmazonDataset: nodes 0-3304 are unlabeled.
    if dataset in {"amazon"}:
        return list(range(3305, n))
    return list(range(n))


def bwgnn_split(labels, dataset: str, train_ratio: float, split_seed: int = SPLIT_SEED):
    from sklearn.model_selection import train_test_split
    index = eligible_index(dataset, len(labels))
    y = labels[index]
    idx_train, idx_rest, _, y_rest = train_test_split(
        index, y, train_size=train_ratio, stratify=y,
        random_state=split_seed, shuffle=True,
    )
    idx_val, idx_test, _, _ = train_test_split(
        idx_rest, y_rest, test_size=0.67, stratify=y_rest,
        random_state=split_seed, shuffle=True,
    )
    return idx_train, idx_val, idx_test
```

**CLI** — BWGNN names, not `--seed 10` as run count:

```python
parser.add_argument("--dataset", choices=["yelp", "amazon", "tfinance", "tsocial"])
parser.add_argument("--train-ratio", type=float, default=0.4,
                    help="BWGNN: 0.4 abundant, 0.01 scarce, 0.0001 T-Social scarce")
parser.add_argument("--run", type=int, default=1,
                    help="BWGNN: training restarts on the fixed split; paper uses 10")
```

If the repo already has `--seed`, keep it as **train-init seed for run 0 only**.
Document that `--run 10` is the 10-experiment knob. Do not let `--seed` change `random_state`.

**Run loop**:

```python
# BWGNN main.py --run: same split, new model each time.
idx_train, idx_val, idx_test = bwgnn_split(labels, args.dataset, args.train_ratio)
metrics = []
for run_id in range(args.run):
    torch.manual_seed(run_id)  # reproducible init; BWGNN itself left RNG unset
    model = build_model(...)
    metrics.append(train_one(model, idx_train, idx_val, idx_test))
# mean and np.std(..., ddof=0) of test Macro-F1 and AUROC
```

Load T-Finance / T-Social with `load_graphs`; add `label.argmax(1)` for T-Finance.
Keep existing `.mat` loaders for Yelp/Amazon if the repo is on that lineage; still apply 3305 + this split.

Every added block gets a one-line `# BWGNN (Tang et al., ICML 2022, ...)` comment.
Do not leave “TODO align later”.

### 4. Re-audit and deliver

Re-run the script. Re-check the boxes. Then return:

1. **Verdict** — conforms / patched / blocked (and why)
2. **Table** — each checklist row
3. **Diff summary** — files changed (empty in `audit`)
4. **Run** — exact command, e.g.

```bash
python train.py --dataset amazon --train-ratio 0.4 --run 10
python train.py --dataset tsocial --train-ratio 0.0001 --run 10
```

5. **Not comparable** — any leftover 0.5-F1, GADBench masks, or unfixed Amazon 3305
