Download 45_text_feats.py from Pathstudy/fourmodal-pathology-bind: direct link, hf CLI and curl.
- Browser
- Download file 4.11 kB
-
https://huggingface.co/Pathstudy/fourmodal-pathology-bind/resolve/main/45_text_feats.py
- Command line
-
hf download hf://Pathstudy/fourmodal-pathology-bind/45_text_feats.py
-
curl -L -o 45_text_feats.py https://huggingface.co/Pathstudy/fourmodal-pathology-bind/resolve/main/45_text_feats.py
4.11 kB
| """Frozen embeddings of every text set with additional (bio)medical / general text-embedding models. | |
| python 45_text_feats.py <name> [<name> ...] | |
| Text sets (same row order as the existing BiomedCLIP files): | |
| alltxt cache/alltxt_meta.csv caption (TCGA real PathGen texts, 6480, junk rows kept, filtered later) | |
| orion_gentext cache/orion_gentext_meta.csv caption (GPT synthetic captions of the ORION tiles, test only) | |
| tcga_notext cache/tcga_notext_meta.csv caption (synthetic captions of TCGA images without real text, test only) | |
| dlpfc_gentext cache/dlpfc_gentext_meta.csv caption (external DLPFC synthetic captions, test only) | |
| Models (pooling / normalisation as on each model card; documents embedded without a query instruction): | |
| biomedclip BiomedCLIP text tower (biomedclip_text.py, 256 tokens) - only for the sets that lack it | |
| medcpt ncbi/MedCPT-Article-Encoder, [CLS], 512 tokens (PubMed search-log contrastive) | |
| biolord FremyCompany/BioLORD-2023, mean pooling, 512 (clinical sentences / concepts, UMLS-based) | |
| pubmedbert NeuML/pubmedbert-base-embeddings, mean pooling, 512 (PubMed title-abstract) | |
| medembed abhinand/MedEmbed-large-v0.1 (bge-large medical fine-tune), [CLS], 512 | |
| bge BAAI/bge-large-en-v1.5, [CLS], 512 (general-domain reference) | |
| qwen3-0.6b / qwen3-4b Qwen/Qwen3-Embedding-*, last-token pooling, left padding, 1024 tokens | |
| Output cache/<set>_text_<name>.npy (n, 1, D) float16 (raw, not normalised). | |
| """ | |
| import os | |
| import sys | |
| import time | |
| import numpy as np | |
| import pandas as pd | |
| import torch | |
| from transformers import AutoModel, AutoTokenizer | |
| from common import CACHE | |
| dev = "cuda" | |
| SETS = {"alltxt": "alltxt_meta.csv", "orion_gentext": "orion_gentext_meta.csv", "tcga_notext": "tcga_notext_meta.csv", | |
| "dlpfc_gentext": "dlpfc_gentext_meta.csv"} | |
| HF = {"medcpt": ("ncbi/MedCPT-Article-Encoder", "cls", 512), "biolord": ("FremyCompany/BioLORD-2023", "mean", 512), | |
| "pubmedbert": ("NeuML/pubmedbert-base-embeddings", "mean", 512), | |
| "medembed": ("abhinand/MedEmbed-large-v0.1", "cls", 512), "bge": ("BAAI/bge-large-en-v1.5", "cls", 512), | |
| "qwen3-0.6b": ("Qwen/Qwen3-Embedding-0.6B", "last", 1024), "qwen3-4b": ("Qwen/Qwen3-Embedding-4B", "last", 1024)} | |
| def encoder(name): | |
| if name == "biomedclip": | |
| from biomedclip_text import BiomedCLIPText | |
| m = BiomedCLIPText().to(dev).eval() | |
| return m, 64 | |
| repo, pool, L = HF[name] | |
| tk = AutoTokenizer.from_pretrained(repo, padding_side="left" if pool == "last" else "right") | |
| m = AutoModel.from_pretrained(repo, torch_dtype=torch.bfloat16 if pool == "last" else torch.float32).to(dev).eval() | |
| def f(b): | |
| x = tk(b, padding=True, truncation=True, max_length=L, return_tensors="pt").to(dev) | |
| h = m(**x).last_hidden_state | |
| if pool == "cls": | |
| return h[:, 0] | |
| if pool == "mean": | |
| a = x["attention_mask"][..., None].to(h.dtype) | |
| return (h * a).sum(1) / a.sum(1) | |
| return h[:, -1] # left padding -> last position is the last real token | |
| return f, (16 if name == "qwen3-4b" else 64) | |
| for name in sys.argv[1:]: | |
| t0 = time.time() | |
| f, bs = encoder(name) | |
| for st, meta in SETS.items(): | |
| out_f = os.path.join(CACHE, f"{st}_text_{name}.npy") | |
| if os.path.exists(out_f): | |
| continue | |
| txt = pd.read_csv(os.path.join(CACHE, meta)).caption.fillna("").astype(str).tolist() | |
| order = np.argsort([len(t) for t in txt]) # length-sorted batches, restored below | |
| E = None | |
| with torch.no_grad(), torch.autocast("cuda", dtype=torch.bfloat16): | |
| for i in range(0, len(txt), bs): | |
| idx = order[i:i + bs] | |
| z = f([txt[j] for j in idx]).float().cpu().numpy() | |
| if E is None: | |
| E = np.zeros((len(txt), z.shape[1]), np.float32) | |
| E[idx] = z | |
| assert np.isfinite(E).all() | |
| np.save(out_f, E[:, None].astype(np.float16)) | |
| print(f"[{name}] {st}: {E.shape} {time.time() - t0:.0f}s", flush=True) | |
| del f | |
| torch.cuda.empty_cache() | |