Skip to content

Commit f1bf87a

Browse files
authored
MedLink Bounty (#728)
* medlink bounty implementation * Notebook Clean Up * Further notebook modification * Removed redecleration of methods * MedLink bounty, processor-native model + tests + MIMIC-III notebook * Docstrings + ehr -> sampledataset * Path config for datasets, build error * samples helper mismatch * Comments Adressed + Import Ambiguity Correction * SEP Fixes * Docstring correction w/ increased clarity + repo cleanup
1 parent c7bef09 commit f1bf87a

21 files changed

Lines changed: 1717 additions & 207 deletions

examples/medlink_mimic3.ipynb

Lines changed: 681 additions & 0 deletions
Large diffs are not rendered by default.

examples/patient_linkage_mimic3_medlink.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pyhealth.models.medlink import get_eval_dataloader
1212
from pyhealth.models.medlink import get_train_dataloader
1313
from pyhealth.models.medlink import tvt_split
14-
from pyhealth.tasks import patient_linkage_mimic3_fn
14+
from pyhealth.tasks import PatientLinkageMIMIC3Task
1515
from pyhealth.trainer import Trainer, logger
1616

1717
"""
@@ -36,7 +36,8 @@
3636
base_dataset.stat()
3737

3838
""" STEP 2: set task """
39-
sample_dataset = base_dataset.set_task(patient_linkage_mimic3_fn)
39+
task = PatientLinkageMIMIC3Task()
40+
sample_dataset = base_dataset.set_task(task)
4041
sample_dataset.stat()
4142
corpus, queries, qrels, corpus_meta, queries_meta = convert_to_ir_format(
4243
sample_dataset.samples

examples/test_eICU_addition.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

pyhealth/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
formatter = logging.Formatter("%(message)s")
1919
handler.setFormatter(formatter)
2020
logger.addHandler(handler)
21+

pyhealth/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@
3535
from .transformer import Transformer, TransformerLayer
3636
from .transformers_model import TransformersModel
3737
from .vae import VAE
38-
from .sdoh import SdohClassifier
38+
from .sdoh import SdohClassifier
39+
from .medlink import MedLink

pyhealth/models/embedding.py

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from typing import Dict
1+
from __future__ import annotations
2+
3+
from typing import Dict, Any, Optional, Union
4+
import os
25

36
import torch
47
import torch.nn as nn
@@ -18,6 +21,94 @@
1821
)
1922
from .base_model import BaseModel
2023

24+
25+
def _iter_text_vectors(
26+
path: str,
27+
embedding_dim: int,
28+
wanted_tokens: set[str],
29+
encoding: str = "utf-8",
30+
) -> Dict[str, torch.Tensor]:
31+
"""Loads word vectors from a text file (e.g., GloVe) for a subset of tokens.
32+
33+
Expected format: one token per line followed by embedding_dim floats.
34+
35+
This function reads the file line-by-line and only retains vectors for
36+
tokens present in `wanted_tokens`.
37+
"""
38+
39+
if not os.path.exists(path):
40+
raise FileNotFoundError(f"pretrained embedding file not found: {path}")
41+
42+
vectors: Dict[str, torch.Tensor] = {}
43+
with open(path, "r", encoding=encoding) as f:
44+
for line in f:
45+
line = line.strip()
46+
if not line:
47+
continue
48+
parts = line.split()
49+
# token + embedding_dim values
50+
if len(parts) < embedding_dim + 1:
51+
continue
52+
token = parts[0]
53+
if token not in wanted_tokens:
54+
continue
55+
try:
56+
vec = torch.tensor(
57+
[float(x) for x in parts[1 : embedding_dim + 1]],
58+
dtype=torch.float,
59+
)
60+
except ValueError:
61+
continue
62+
vectors[token] = vec
63+
return vectors
64+
65+
66+
def init_embedding_with_pretrained(
67+
embedding: nn.Embedding,
68+
code_vocab: Dict[Any, int],
69+
pretrained_path: str,
70+
embedding_dim: int,
71+
pad_token: str = "<pad>",
72+
unk_token: str = "<unk>",
73+
normalize: bool = False,
74+
freeze: bool = False,
75+
) -> int:
76+
"""Initializes an nn.Embedding from a pretrained text-vector file.
77+
78+
Tokens not found in the pretrained file are left as the module's existing
79+
random initialization.
80+
81+
Returns:
82+
int: number of tokens successfully initialized from the file.
83+
"""
84+
85+
# Build wanted token set (stringified)
86+
vocab_tokens = {str(t) for t in code_vocab.keys()}
87+
vectors = _iter_text_vectors(pretrained_path, embedding_dim, vocab_tokens)
88+
89+
loaded = 0
90+
with torch.no_grad():
91+
for tok, idx in code_vocab.items():
92+
tok_s = str(tok)
93+
if tok_s in vectors:
94+
vec = vectors[tok_s]
95+
if normalize:
96+
vec = vec / (vec.norm(p=2) + 1e-12)
97+
embedding.weight[idx].copy_(vec)
98+
loaded += 1
99+
100+
# Ensure pad row is zero
101+
if pad_token in code_vocab:
102+
embedding.weight[code_vocab[pad_token]].zero_()
103+
# If embedding has a padding_idx, keep it consistent
104+
if embedding.padding_idx is not None:
105+
embedding.weight[embedding.padding_idx].zero_()
106+
107+
if freeze:
108+
embedding.weight.requires_grad_(False)
109+
110+
return loaded
111+
21112
class EmbeddingModel(BaseModel):
22113
"""
23114
EmbeddingModel is responsible for creating embedding layers for different types of input data.
@@ -46,7 +137,14 @@ class EmbeddingModel(BaseModel):
46137
- MultiHotProcessor: nn.Linear over multi-hot vector
47138
"""
48139

49-
def __init__(self, dataset: SampleDataset, embedding_dim: int = 128):
140+
def __init__(
141+
self,
142+
dataset: SampleDataset,
143+
embedding_dim: int = 128,
144+
pretrained_emb_path: Optional[Union[str, Dict[str, str]]] = None,
145+
freeze_pretrained: bool = False,
146+
normalize_pretrained: bool = False,
147+
):
50148
super().__init__(dataset)
51149
self.embedding_dim = embedding_dim
52150
self.embedding_layers = nn.ModuleDict()
@@ -81,6 +179,22 @@ def __init__(self, dataset: SampleDataset, embedding_dim: int = 128):
81179
padding_idx=0,
82180
)
83181

182+
# Optional pretrained initialization (e.g., GloVe).
183+
if pretrained_emb_path is not None:
184+
if isinstance(pretrained_emb_path, str):
185+
path = pretrained_emb_path
186+
else:
187+
path = pretrained_emb_path.get(field_name)
188+
if path:
189+
init_embedding_with_pretrained(
190+
self.embedding_layers[field_name],
191+
processor.code_vocab,
192+
path,
193+
embedding_dim=embedding_dim,
194+
normalize=normalize_pretrained,
195+
freeze=freeze_pretrained,
196+
)
197+
84198
# Numeric features (including deep nested floats) -> nn.Linear over last dim
85199
elif isinstance(
86200
processor,

0 commit comments

Comments
 (0)