Views
No views yet
Temporal conv -> POS-GRAPH conv -> Separable conv -> Avg-pool + flatten -> flat vector
(Layer 1) (Layer 2) (Layer 3) (Layer 4) OUTPUT
IDENTICAL THE SWAP IDENTICAL IDENTICALEEGNetGNNPosClassifier attaches
a linear head. Output size is exposed as model.flat_dim (372 with the defaults).H = FiLM_pos(Â X W) + X W_res, then BatchNorm + ELU, then a signed readout collapsing the
electrodes to one. Coordinates enter twice:(x, y). Â alone cannot distinguish C3
from C4: their neighbourhoods are isomorphic, so message passing treats both hemispheres
identically.node_weight[:, n] = MLP(gamma(x_n, y_n)), a continuous spatial field sampled
at each electrode, rather than a free (F_out, N) table indexed by channel order.transfer_to_montage), and a spatial field that can be
evaluated between electrodes (pattern_at).The readout must stay signed. A softmax over electrodes is a non-negative convex combination and cannot build the spatial high-pass filter that isolates focal C3/C4 ERD.
| arg | default | effect |
|---|---|---|
readout | "coord" | "coord" / "lookup" (free table) / "attention" / "mean" |
pos_mode | "film" | "film" / "bias" (shift only) / "none" |
residual | True | adds un-smoothed node features back after message passing |
learn_adjacency | False | makes  a trainable parameter |
spatial | "graph" | "depthwise" restores stock EEGNet for a controlled A/B |
1from eegnet_gnn_pos import EEGNetGNNPos, EEGNetGNNPosClassifier
2import torch
3
4backbone = EEGNetGNNPos(spatial="graph", readout="coord")
5model = EEGNetGNNPosClassifier(backbone, n_classes=4)
6
7x = torch.randn(1, 1, 22, 1000) # (batch, 1, channels, time)
8logits = model(x) # (1, 4)readout="coord" has more parameters than the free table it replaces (~2.1k backbone vs ~1.2k).
On ~288 trials per subject that is a real cost, and the geometric structure has to earn it. The
honest comparison is a three-way A/B against readout="lookup" and spatial="depthwise" with an
identical head, which is what Notebook 2 runs.