kozo2 commited on
Commit
a037de7
Β·
verified Β·
1 Parent(s): 698d0b3

Upload README.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. README.md +107 -0
README.md ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-4.0
3
+ library_name: pytorch
4
+ pipeline_tag: feature-extraction
5
+ tags:
6
+ - node2vec
7
+ - graph-embedding
8
+ - metabolomics
9
+ - pytorch-geometric
10
+ - metabolights
11
+ - metabolomics-workbench
12
+ metrics:
13
+ - roc_auc
14
+ ---
15
+
16
+ # Node2Vec embeddings for cross-study metabolomics co-response graphs
17
+
18
+ 128-dimensional Node2Vec embeddings for two undirected graphs of metabolite co-response
19
+ edges that pass a chi-square validity filter (every expected frequency β‰₯ 5). Held-out
20
+ link prediction reaches **AUC 0.932** and **0.928**, against degree-only baselines of
21
+ 0.826 and 0.888.
22
+
23
+ The graphs, node properties and full pipeline are in the companion dataset repository:
24
+ [kozo2/metabolomics-edges-expected-ge5](https://huggingface.co/datasets/kozo2/metabolomics-edges-expected-ge5).
25
+
26
+ ## Files
27
+
28
+ | File | Contents | Size |
29
+ |---|---|---|
30
+ | `edge_ML_filtered_expected_ge5_n2v.pt` | `{embedding: [848, 128] float32, node_id: [848], args: {...}}` | 453 KB |
31
+ | `edge_MLvsMW_filtered_expected_ge5_n2v.pt` | `{embedding: [15758, 128] float32, node_id: [15758], args: {...}}` | 8.6 MB |
32
+ | `node2vec_model.py`, `config.py` | Model definition, training loop, embedding export | β€” |
33
+ | `heldout_check.py` | The held-out link-prediction evaluation reported below | β€” |
34
+
35
+ Each file records the exact hyperparameters that produced it under `args`, and
36
+ `node_id[i]` is the original study/assay/feature ID of embedding row `i`.
37
+
38
+ ## Using it
39
+
40
+ ```python
41
+ import torch
42
+
43
+ ck = torch.load("edge_MLvsMW_filtered_expected_ge5_n2v.pt", weights_only=False)
44
+ z = torch.nn.functional.normalize(ck["embedding"], dim=1) # cosine space
45
+ idx = {node: i for i, node in enumerate(ck["node_id"])}
46
+
47
+ q = z[idx["MTBLS311_0001_00001537"]]
48
+ top = (z @ q).topk(11).indices[1:] # drop self
49
+ print([ck["node_id"][i] for i in top])
50
+ ```
51
+
52
+ Scores are cosine similarities β€” that is the metric the embeddings were trained under
53
+ random walks and evaluated with, so use normalized vectors rather than raw dot products.
54
+
55
+ ## Training
56
+
57
+ | | `edge_ML` | `edge_MLvsMW` |
58
+ |---|---|---|
59
+ | graph | 848 nodes, 3,697 edges | 15,758 nodes, 89,277 edges |
60
+ | parameters | 108,544 | 2,017,024 |
61
+ | `embedding_dim` | 128 | 128 |
62
+ | `walk_length` / `context_size` | 20 / 10 | 20 / 10 |
63
+ | `walks_per_node` / negatives | 10 / 1 | 10 / 1 |
64
+ | `p` / `q` | 1.0 / 1.0 (unbiased) | 1.0 / 1.0 (unbiased) |
65
+ | `batch_size` / `lr` / optimizer | 128 / 0.01 / SparseAdam | 128 / 0.01 / SparseAdam |
66
+ | epochs / wall time | 200 / 31 s | 200 / 3 m 05 s |
67
+ | loss, first β†’ last | 9.02 β†’ 0.815 | 6.73 β†’ 0.876 |
68
+
69
+ Trained on one NVIDIA H100 NVL. `sparse=True` makes the embedding table the only
70
+ parameter tensor, which is why the optimizer is `SparseAdam`.
71
+
72
+ ## Evaluation
73
+
74
+ `heldout_check.py` deduplicates undirected edges, holds out a random 10%, retrains from
75
+ scratch on the remaining 90%, then scores held-out edges against an equal number of
76
+ sampled non-edges by cosine similarity. The control is the preferential-attachment
77
+ baseline `d_u Β· d_v` on training degrees, which answers whether the embedding learned
78
+ anything beyond "popular nodes connect".
79
+
80
+ | | `edge_ML` | `edge_MLvsMW` |
81
+ |---|---|---|
82
+ | train / held-out edges | 3,328 / 369 | 80,350 / 8,927 |
83
+ | nodes isolated by the split | 42 | 413 |
84
+ | **held-out AUC (never seen)** | **0.932** | **0.928** |
85
+ | train AUC (in-sample) | 0.985 | 0.985 |
86
+ | degree baseline (held-out) | 0.826 | 0.888 |
87
+ | margin over baseline | +0.106 | +0.040 |
88
+
89
+ **Species neighbour purity** β€” of each node's 10 nearest embeddings by cosine, the
90
+ fraction sharing its species label β€” is 48.8% overall for `edge_ML` and 59.2% for
91
+ `edge_MLvsMW`, rising to 75–79% for the dominant host species. For the bipartite
92
+ `edge_MLvsMW` graph, the same measure on *source database* gives 67.5% overall (75.4% ST,
93
+ 47.7% MTBLS).
94
+
95
+ ## Caveats
96
+
97
+ - **Training budget matters more than it looks.** At 20 epochs, `edge_ML` scored a
98
+ held-out AUC of 0.791 β€” *below* its own 0.826 degree baseline. The embeddings only beat
99
+ preferential attachment once training converges. `--epochs` defaults to 200 for this
100
+ reason; do not shorten it without re-running `heldout_check.py`.
101
+ - On `edge_MLvsMW` the margin over the degree baseline is thin (+0.040). That graph is
102
+ bipartite with a few very high-degree studies, so much of its link structure is
103
+ explained by degree alone.
104
+ - `edge_MLvsMW` loss is flat from about epoch 19, so 200 epochs is more budget than that
105
+ graph needs.
106
+ - Embeddings are transductive: there is no way to embed a node that was not in the
107
+ training graph without retraining.