Skip to content

Commit dfa0506

Browse files
committed
Fix docstrings
1 parent e09261d commit dfa0506

1 file changed

Lines changed: 129 additions & 118 deletions

File tree

pyhealth/models/keep_embedding.py

Lines changed: 129 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,39 @@
1212
from pyhealth.datasets import SampleDataset
1313
from .base_model import BaseModel
1414

15-
class N2V():
16-
"""Node2Vec embeddings for OMOP concepts.
15+
class N2V:
16+
"""Generate Node2Vec embeddings for OMOP concepts.
1717
18-
This class builds a directed knowledge graph from OMOP concept and
19-
concept relationship tables, then trains Node2Vec to generate
20-
ontology-informed embeddings for medical concepts.
18+
Builds a directed knowledge graph from OMOP concept relationship tables
19+
and trains Node2Vec to create graph embeddings.
2120
2221
Attributes:
23-
path: Path to the OMOP CSV files.
24-
domain_type: List of OMOP domains used to filter concepts.
25-
embedding_dim: Dimension of the learned embeddings.
22+
embedding_dim: Dimension of learned embeddings.
2623
walk_length: Length of each random walk.
27-
num_walks: Number of walks generated per node.
28-
graph: Directed graph constructed from OMOP concepts and relations.
24+
num_walks: Number of walks per node.
2925
"""
3026
def __init__(
31-
self,
32-
embedding_dim:int=None,
33-
walk_length:int=None,
34-
num_walks:int=None
35-
):
27+
self,
28+
embedding_dim: int | None = None,
29+
walk_length: int | None = None,
30+
num_walks: int | None = None,
31+
) -> None:
3632
self.embedding_dim = embedding_dim
3733
self.walk_length = walk_length
3834
self.num_walks = num_walks
3935

40-
# Create graph from concept and their relationships data
41-
def create_graph(self, path, domain_type) -> nx.DiGraph:
42-
"""
43-
Create a directed graph from OMOP concept relationships.
44-
45-
Loads concepts and their relationships from CSV files, filters by domain_type,
46-
and builds a NetworkX DiGraph where nodes are concept IDs and edges are
47-
concept relationships (maps_to).
48-
36+
def create_graph(
37+
self, path: str, domain_type: list[str]
38+
) -> nx.DiGraph:
39+
"""Create a Network directed graph from OMOP concept relationships.
40+
41+
Args:
42+
path: Path to OMOP concept CSV files.
43+
domain_type: List of domain IDs to include.
44+
4945
Returns:
50-
nx.DiGraph: Directed graph with concept_id as nodes and relationships as edges.
51-
52-
Raises:
53-
FileNotFoundError: If CSV files are not found.
54-
ValueError: If no concepts found for specified domains.
46+
Directed graph with concept IDs as nodes and relationships
47+
as edges.
5548
"""
5649
# Load concept table
5750
concept_path = os.path.join(path, "2b_concept.csv")
@@ -110,30 +103,36 @@ def create_graph(self, path, domain_type) -> nx.DiGraph:
110103

111104
return graph
112105

113-
def _build_index_mapping(self, node_embeddings):
114-
"""
115-
Build a dictionary to map concept code to the index in node_embeddings.
116-
106+
def _build_index_mapping(
107+
self, node_embeddings: object
108+
) -> dict[int, int]:
109+
"""Map concept codes to embedding indices.
110+
117111
Args:
118-
node_embeddings: Gensim Word2Vec model word vectors
119-
112+
node_embeddings: Word2Vec model word vectors.
113+
120114
Returns:
121-
dict: Mapping from concept_id (int) to index in embeddings
115+
Mapping from concept_id to index in embeddings.
122116
"""
123117
return {int(key): i for i, key in enumerate(node_embeddings.index_to_key)}
124118

125-
def _get_vector_iso(self, code, node_embeddings, index_mapping, mean_vector):
126-
"""
127-
Return concept embedding for the given code or mean vector if not found.
128-
119+
def _get_vector_iso(
120+
self,
121+
code: int,
122+
node_embeddings: object,
123+
index_mapping: dict,
124+
mean_vector: np.ndarray,
125+
) -> np.ndarray:
126+
"""Get embedding vector for code or mean vector if not found.
127+
129128
Args:
130-
code: Concept ID
131-
node_embeddings: Gensim Word2Vec model word vectors
132-
index_mapping: Dictionary mapping concept_id to index
133-
mean_vector: Mean vector to use as fallback
134-
129+
code: Concept ID.
130+
node_embeddings: Word2Vec model word vectors.
131+
index_mapping: Concept ID to embedding index mapping.
132+
mean_vector: Fallback vector to use if code not found.
133+
135134
Returns:
136-
np.ndarray: Embedding vector for the concept
135+
Embedding vector for the concept.
137136
"""
138137
index = index_mapping.get(int(code))
139138
if index is not None:
@@ -142,16 +141,18 @@ def _get_vector_iso(self, code, node_embeddings, index_mapping, mean_vector):
142141
print(f"Code {code} not found, returning mean vector.")
143142
return mean_vector
144143

145-
def generate_embeddings(self, graph):
146-
"""
147-
Generate node embeddings using Node2Vec algorithm.
148-
149-
Creates a graph from OMOP concepts and applies Node2Vec to generate
150-
embeddings for each concept based on its network structure.
151-
144+
def generate_embeddings(
145+
self, graph: nx.DiGraph
146+
) -> tuple[np.ndarray, list]:
147+
"""Generate node embeddings using Node2Vec.
148+
149+
Args:
150+
graph: Directed graph of OMOP concepts and relationships.
151+
152152
Returns:
153-
tuple: (embedding_matrix, node_ids) where embedding_matrix is the numpy array
154-
of embeddings and node_ids is the list of graph node IDs in order.
153+
Tuple of (embedding_matrix, node_ids) where embedding_matrix
154+
is numpy array of embeddings and node_ids is the list of
155+
node IDs in order.
155156
"""
156157
print(f"Graph created with {len(graph.nodes())} nodes and {len(graph.edges())} edges")
157158

@@ -191,43 +192,65 @@ def generate_embeddings(self, graph):
191192
return embedding_matrix, keys
192193

193194
class KeepEmbedding(BaseModel):
194-
"""KEEP Embedding: Fine-tune Node2Vec embeddings using GloVe while penalizing
195-
deviation from original embeddings.
196-
197-
Balances:
198-
- Co-occurrence structure (GloVe objective)
199-
- Graph structure prior (Node2Vec via regularization)
200-
195+
"""KEEP Embedding Framework
196+
197+
198+
Fine-tune Node2Vec embeddings using GloVe with graph regularization.
199+
200+
Balances co-occurrence structure (GloVe) with graph (Node2Vec) via regularization
201+
to generate medical concept embeddings.
202+
201203
Args:
202-
dataset (SampleDataset): The dataset to train the model.
203-
path (str): Path to OMOP data files for graph construction.
204-
domain_type (list[str]): Domain types to include in graph.
205-
embedding_dim (int): Dimension of embeddings.
206-
walk_length (int): Length of random walks for Node2Vec.
207-
num_walks (int): Number of random walks per node for Node2Vec.
208-
lambda_reg (float): Regularization strength for Node2Vec prior. Default: 1.0.
209-
reg_norm (str or float): Norm type for regularization ('cosine' or numeric p-norm).
210-
Default: None (cosine similarity).
211-
log_scale (bool): Whether to apply log scaling to regularization distance.
212-
Default: False.
213-
code_to_index (dict, optional): Mapping from concept codes to vocabulary indices.
214-
If provided, embeddings are filtered to only include codes in this mapping.
215-
device (str): Device to use ('cuda' or 'cpu'). Default: 'cpu'.
204+
dataset: Dataset to train the model.
205+
graph: Directed graph of concepts and relationships.
206+
embedding_dim: Dimension of embeddings.
207+
walk_length: Length of random walks for Node2Vec.
208+
num_walks: Number of random walks per node.
209+
num_words: Size of vocabulary.
210+
lambda_reg: Regularization strength (default: 1.0).
211+
reg_norm: Norm type ('cosine' or numeric p-norm, default: None).
212+
log_scale: Apply log scaling to regularization distance
213+
(default: False).
214+
code_to_index: Optional mapping from concept codes to indices.
215+
device: Device to use ('cuda' or 'cpu', default: 'cpu').
216+
217+
Examples:
218+
>>> from pyhealth.datasets import OMOPDataset
219+
>>> from pyhealth.models import KeepEmbedding
220+
>>> dataset = SampleDataset(num_patients=100, num_visits=10, num_codes=50)
221+
>>> graph = n2v.create_graph() # Build knowledge graph from concept and relationship tables
222+
>>> dataset = OMOPDataset(...)
223+
>>> # Build co-occurrence matrix from dataset
224+
>>> # Load co-occurrence matrix as GloveDatset Dataloader
225+
>>> model = KeepEmbedding(
226+
... dataset=None,
227+
... graph=graph,
228+
... embedding_dim=128,
229+
... walk_length=10,
230+
... num_walks=5,
231+
... num_words=50,
232+
... lambda_reg=0.5,
233+
... reg_norm='cosine',
234+
... log_scale=True,
235+
... device='cuda'
236+
... )
237+
>>> # Use embeddings for with downstream PyHealth models
216238
"""
217239

218-
def __init__(self,
219-
dataset: SampleDataset,
220-
graph: nx.Graph,
221-
embedding_dim:int,
222-
walk_length:int,
223-
num_walks:int,
224-
num_words: int,
225-
lambda_reg: float = 1.0,
226-
reg_norm: str | float = None,
227-
log_scale: bool = False,
228-
code_to_index: dict = None,
229-
device: str = "cpu"
230-
):
240+
def __init__(
241+
self,
242+
dataset: SampleDataset,
243+
graph: nx.Graph,
244+
embedding_dim: int,
245+
walk_length: int,
246+
num_walks: int,
247+
num_words: int,
248+
lambda_reg: float = 1.0,
249+
reg_norm: str | float | None = None,
250+
log_scale: bool = False,
251+
code_to_index: dict | None = None,
252+
device: str = "cpu",
253+
) -> None:
231254
"""Initialize KEEP Embedding model."""
232255
super().__init__(dataset=dataset)
233256

@@ -301,38 +324,26 @@ def __init__(self,
301324
print(f"Regularization norm: {reg_norm}")
302325
print(f"Log scaling: {log_scale}")
303326

304-
def forward(self,
305-
i_indices: torch.Tensor = None,
306-
j_indices: torch.Tensor = None,
307-
counts: torch.Tensor = None,
308-
weights: torch.Tensor = None,
309-
**kwargs) -> dict[str, torch.Tensor]:
310-
"""Forward pass for KEEP Embedding.
311-
312-
Computes GloVe loss with optional Node2Vec regularization. For compatibility
313-
with BaseModel.forward(), returns a dictionary with keys: loss, y_prob,
314-
y_true, logit.
315-
316-
For training GloVe objective, pass:
317-
- i_indices: Token indices (batch_size,)
318-
- j_indices: Context token indices (batch_size,)
319-
- counts: Co-occurrence counts (batch_size,)
320-
- weights: Weights for each co-occurrence pair (batch_size,)
321-
327+
def forward(
328+
self,
329+
i_indices: torch.Tensor | None = None,
330+
j_indices: torch.Tensor | None = None,
331+
counts: torch.Tensor | None = None,
332+
weights: torch.Tensor | None = None,
333+
**kwargs,
334+
) -> dict[str, torch.Tensor]:
335+
"""Compute GloVe loss with optional Node2Vec regularization.
336+
322337
Args:
323-
i_indices (torch.Tensor, optional): Token indices.
324-
j_indices (torch.Tensor, optional): Context token indices.
325-
counts (torch.Tensor, optional): Co-occurrence counts.
326-
weights (torch.Tensor, optional): Weights for loss terms.
338+
i_indices: Token indices (batch_size,).
339+
j_indices: Context token indices (batch_size,).
340+
counts: Co-occurrence counts (batch_size,).
341+
weights: Weights for loss terms (batch_size,).
327342
**kwargs: Additional arguments for compatibility.
328-
343+
329344
Returns:
330-
dict: Dictionary with keys:
331-
- loss: Total loss (GloVe + regularization if applicable)
332-
- logit: Placeholder tensor (for BaseModel compatibility)
333-
- y_prob: Placeholder tensor (for BaseModel compatibility)
334-
- y_true: Placeholder tensor (for BaseModel compatibility)
335-
- reg_loss: Regularization loss component (if applicable)
345+
Dictionary with keys: loss, logit, y_prob, y_true,
346+
reg_loss.
336347
"""
337348

338349
# If no GloVe inputs provided, return dummy output

0 commit comments

Comments
 (0)