Instructions to use kvenanzi/vandf-rxnorm-biencoder with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use kvenanzi/vandf-rxnorm-biencoder with sentence-transformers:
from sentence_transformers import SentenceTransformer model = SentenceTransformer("kvenanzi/vandf-rxnorm-biencoder") sentences = [ "That is a happy person", "That is a happy dog", "That is a very happy person", "Today is a sunny day" ] embeddings = model.encode(sentences) similarities = model.similarity(embeddings, embeddings) print(similarities.shape) # [4, 4] - Notebooks
- Google Colab
- Kaggle
VANDF → RxNorm clinical drug bi-encoder
Maps a VA National Drug File (VANDF) drug string to the RxNorm clinical drug it means (SCD or SBD: ingredient, strength, and dose form), with a calibrated confidence so you can auto-accept the sure cases and route the rest to review.
| Input | Output | Confidence |
|---|---|---|
METOPROLOL TARTRATE 12.5MG TAB |
metoprolol tartrate 12.5 MG Oral Tablet (RXCUI 866508) |
0.97 → accept |
ACETIC ACID 0.25% IRRG SOLN |
acetic acid 2.5 MG/ML Irrigation Solution |
0.96 → accept |
HYOSCYAMINE SO4 0.125MG/5ML ELIXIR |
hyoscyamine sulfate 0.125 MG/ML Oral Solution (wrong: truth is 0.025 MG/ML, listed 2nd) |
0.77 → review |
CATHETER,FOLEY SILICONE 22FR 5CC |
(nearest candidate, meaningless) | 0.02 → review |
Published tools such as RxMap normalize to the ingredient level (IN/MIN). This model targets the full clinical drug, where a miss on strength or dose form is a wrong answer. Method, hypotheses, the full sweep, and the error analysis are in the write-up: https://kettlelabs.dev/blog/posts/vandf-rxnorm-biencoder/
Usage
pip install "rxnorm-vandf @ git+https://github.com/kvenanzi/rxnorm"
from rxnorm_vandf.infer import Mapper
mapper = Mapper.from_pretrained("kvenanzi/vandf-rxnorm-biencoder") # ~450 MB download
for p in mapper.map(["METOPROLOL TARTRATE 12.5MG TAB", "CATHETER,FOLEY SILICONE 22FR 5CC"]):
print(p.rxcui, p.name, p.tty, f"{p.confidence:.2f}", "accept" if p.accept else "review")
# p.alternatives: the next four candidates as (rxcui, name, cosine)
Mapper loads the encoder, train_config.json (input preprocessing),
calibration.json (score → probability), and candidates.parquet (the 27,287
active RxNorm SCD/SBD names it searches) from this repo. The default acceptance
threshold (0.92) was chosen on validation for 99% precision on a population
that includes real drugs with no SCD/SBD; pass threshold= to change it.
How it works
- Preprocessing. The VA string is lowercased, punctuation is stripped, and a
deterministic rule appends the RxNorm-style concentration
(
0.125MG/5ML→0.025 mg/ml;0.25%→2.5 mg/ml;mg/mgfor gels and ointments). An embedding model can't do this arithmetic; a regex can. - Retrieval. A SapBERT
bi-encoder, fine-tuned with
MultipleNegativesRankingLosson 9,287 (VA string, RxNorm name) pairs plus hard negatives (same ingredients, different strength or dose form), embeds the query and all candidates; top-k by cosine. - Calibration. A logistic (Platt) layer over [cosine, top-1−top-2 margin, temperature-scaled softmax] gives P(correct). Fit on validation only.
Results
Test split: 1,848 VA strings whose ingredients never appear in training. The candidate pool is all 27,287 active SCD/SBD in RxNorm 2026-09-08.
| Method | acc@1 | recall@5 | ingredient | strength | dose form |
|---|---|---|---|---|---|
| Exact string match | 0.000 | 0.000 | – | – | – |
| TF-IDF char 3–5-grams | 0.509 | 0.820 | 0.978 | 0.617 | 0.698 |
| MiniLM-L6 fine-tuned | 0.836 | 0.967 | 0.983 | 0.884 | 0.935 |
| + strength normalizer | 0.886 | 0.975 | 0.982 | 0.941 | 0.943 |
| SapBERT + normalizer (this model) | 0.931 | 0.984 | 0.983 | 0.961 | 0.972 |
Validation: acc@1 0.883, recall@5 0.961.
The 0.931 is one draw of the ingredient split. Re-drawing the split five more times and retraining the same recipe gives test acc@1 0.907 ± 0.019 (range 0.874–0.931; the published split is the most favorable of the six), and seven-fold cross-validation by ingredient gives a pooled out-of-fold acc@1 of 0.894 (Wilson 95% 0.889–0.899, n = 12,227). The training seed contributes a standard deviation of about 0.0045 (two seeds on each of the six draws). Read the headline as a band of about two points. Recall@5 (0.974–0.988) and the component accuracies vary little across draws.
Abstention. Thresholds chosen on validation, measured on test:
| Population | Signal | Auto-accepted | Precision of accepted |
|---|---|---|---|
| VA strings that have an SCD/SBD | softmax | 79.0% | 0.988 |
| + real drugs with no SCD/SBD (packs, ingredient-only) | platt (default) | 46.0% | 0.982 |
The validation target was 99%; test lands at 98.2–98.8%. Treat the achieved number as the estimate, not the target. In cross-validation of the same recipe, a 99% threshold fit on the pooled out-of-fold predictions (about 11,000 strings) achieved 98.6–99.8% on each held-out fold, which indicates that the shortfall here comes from choosing the threshold on one validation split of about 2,300 strings.
An 18-run sweep (3 encoders × 3 negative strategies × normalizer on/off) found the three effects roughly additive: domain pre-training (SapBERT vs general encoders) +8.6 points val acc@1, the strength normalizer +4.8, ingredient-matched hard negatives +3. The hard-negatives effect was then re-run on all six ingredient splits (18 runs): ingredient-matched negatives beat in-batch-only on every split, by 1.6 points of test acc@1 on average (95% CI 0.8–2.4). Live charts: https://wandb.ai/kettle-labs/rxnorm-vandf.
A follow-up tested a second source vocabulary (the FDA label names), an auxiliary strength head, and cross-validation by ingredient; neither training change altered accuracy on VA strings. Write-up: https://kettlelabs.dev/blog/posts/vandf-rxnorm-interventions/
For mapping new strings, the same recipe trained on every ingredient family is published as https://huggingface.co/kvenanzi/vandf-rxnorm-biencoder-all (no test set of its own; cross-validated acc@1 0.894 on unseen ingredients).
Limitations
- Trained on VA strings only. Other systems' drug names are a different distribution. On FDA Structured Product Label names (MTHSPL) the same recipe scores acc@1 0.709 ± 0.057 across twelve draw-and-seed runs; other vocabularies are unmeasured.
- One split for calibration. This model's abstention thresholds were chosen on one validation draw. Re-choose thresholds on your own held-out data.
- Candidates are RxNorm 2026-09-08. RxNorm changes monthly; rebuild
candidates.parquetfor a newer release (scripts/03_build_dataset.pyin the repo). - Not for unsupervised clinical use. A 7% top-1 error rate on medication codes is a safety problem; use the confidence to route uncertain strings to a pharmacist, or use the top-5 as suggestions.
- Of the 128 test errors, 46 involve strength (often an underdetermined string:
MANNITOL 250MG/ML INJvs RxNorm's50 ML mannitol 250 MG/ML Injection), 43 dose form, 31 ingredient, and 25 are SCD-vs-SBD twins with identical components.
Training details
- Base:
cambridgeltl/SapBERT-from-PubMedBERT-fulltext(110M params, Apache-2.0) - Loss:
MultipleNegativesRankingLoss, triplets (anchor, positive, hard negative),NO_DUPLICATESbatch sampler; negatives drawn from train-split candidates only - 4 epochs, batch 64, lr 2e-5, 10% warmup, fp16, max_seq_length 96, seed 42
- Best epoch by validation acc@1; test scored once
- ~20 min on a GTX 1070, ~2 min on a Colab A100
Data and license
Training pairs come from RxNorm itself: a VANDF atom and an RxNorm SCD/SBD atom
that share an RXCUI are the same drug by NLM's curation. Only the VANDF and
RXNORM source vocabularies were used (UMLS source restriction category 0:
"general terms of the License apply with no additional restrictions"). No PHI.
Dataset: kvenanzi/vandf-rxnorm-pairs.
Model weights: Apache-2.0, as the base model.
References
- Liu F, Shareghi E, Meng Z, Basaldella M, Collier N (2021). "Self-Alignment Pretraining for Biomedical Entity Representations". NAACL-HLT 2021:4228–4238. https://aclanthology.org/2021.naacl-main.334/ (SapBERT)
- Reimers N, Gurevych I (2019). "Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks". EMNLP-IJCNLP 2019:3982–3992. https://aclanthology.org/D19-1410/
- Henderson M et al (2017). "Efficient Natural Language Response Suggestion for Smart Reply". arXiv:1705.00652. (the in-batch negatives loss)
- Guo C, Pleiss G, Sun Y, Weinberger KQ (2017). "On Calibration of Modern Neural Networks". ICML 2017. arXiv:1706.04599. (temperature scaling)
- Platt JC (1999). "Probabilistic Outputs for Support Vector Machines and Comparisons to Regularized Likelihood Methods". In Advances in Large Margin Classifiers, MIT Press, pp. 61–74.
- Nelson SJ, Zeng K, Kilbourne J, Powell T, Moore R (2011). "Normalized names for clinical drugs: RxNorm at 6 years". JAMIA 18(4):441–448. https://doi.org/10.1136/amiajnl-2011-000116
- Korpela E, Rubin LH, Dastgheyb RM, Xu Y (2026). "RxMap: an LLM-assisted tool for medication normalization". JAMIA Open 9(3):ooag085. https://doi.org/10.1093/jamiaopen/ooag085
- RxNorm is produced by the U.S. National Library of Medicine; VANDF by the U.S. Department of Veterans Affairs.
- Downloads last month
- 39
Model tree for kvenanzi/vandf-rxnorm-biencoder
Dataset used to train kvenanzi/vandf-rxnorm-biencoder
Papers for kvenanzi/vandf-rxnorm-biencoder
On Calibration of Modern Neural Networks
Efficient Natural Language Response Suggestion for Smart Reply
Evaluation results
- acc@1 on VANDF-RxNorm pairs, test split (held-out ingredients)self-reported0.931
- recall@5 on VANDF-RxNorm pairs, test split (held-out ingredients)self-reported0.984