Skip to content

fix(kg_emb): migrate SampleKGDataset off the removed 1.x dataset API - #1202

Open
AxelNoun wants to merge 8 commits into
sunlabuiuc:masterfrom
AxelNoun:fix/kg-emb-2.0-migration
Open

fix(kg_emb): migrate SampleKGDataset off the removed 1.x dataset API#1202
AxelNoun wants to merge 8 commits into
sunlabuiuc:masterfrom
AxelNoun:fix/kg-emb-2.0-migration

Conversation

@AxelNoun

Copy link
Copy Markdown
Contributor

Closes #952. Supersedes #1192. Part of #1201.

What the rename does not reach

#1192 renames SampleBaseDataset to SampleDataset across 7 files. The rename
clears the ImportError, but the module stays unusable: since 2.0,
SampleDataset is a litdata.StreamingDataset whose constructor expects a
directory containing schema.pkl, while SampleKGDataset.__init__ still passes
a list of samples, an argument the rename does not touch.

>>> SampleKGDataset(samples=[{"triple": (0, 0, 1)}], entity2id={}, relation2id={})
ValueError: dir_path must be either a string, Path, or Dir, got: <class 'list'>
  litdata.streaming.resolver._resolve_dir

self.samples is then never assigned, so __getitem__ and stat() raise
AttributeError, and BaseKGDataset.set_task() cannot return anything usable.
The failure moves from import time to call time, which is where #1192 has no
coverage: its CI is green because none of its three tests instantiates the
class. They assert an import, a tautology (isinstance(cls, type)), and a type
annotation.

To be fair to the author: #952 explicitly asked for the rename across eight
files, and #1192 does exactly that. The gap is in how the issue was framed.

Root cause

SampleKGDataset inherited from SampleBaseDataset to reuse __len__ and
.samples. It required none of the parent's behavioural contract and used none
of the services the hierarchy exists to provide: a knowledge graph has no
feature schema, no processors and no patient/visit index. The inheritance was
convenience reuse, not an is-a relationship.

The 2.0 migration did not introduce this. It removed the coincidence that made
it harmless, namely the overlap between the interface actually used and the one
the parent exposed. Adapting SampleKGDataset to the streaming contract would
mean satisfying a constraint the module has no use for, so removing the
inheritance is the minimal fix rather than the drastic one.

What this PR does

SampleKGDataset becomes a standalone torch.utils.data.Dataset.

Models depend on a structural typing.Protocol (PEP 544) instead of a concrete
class. KGEBaseModel.__init__ only ever reads entity_num, relation_num and
task_spec_param, so both the old and the new annotation over-specified the
contract, and both were wrong: SampleDataset has neither attribute.
KGDatasetProtocol states the capability actually required. The annotation
becomes checkable by mypy, the model layer becomes testable with lightweight
doubles, and kg_emb stops being coupled to changes in the main pipeline. That
last point is the durable one: a rename would have held until the next
SampleDataset refactor.

Also in scope:

  • split() no longer overwrites NumPy's global random state, and validates its
    input with ValueError rather than assert, which is stripped under
    python -O and should not be load-bearing for user input validation.
  • The four models' __main__ examples imported SampleKGDataset from
    pyhealth.datasets, where it has never existed. They now use
    torch.utils.data.DataLoader with collate_fn_dict_with_padding directly,
    since get_dataloader calls set_shuffle and is streaming-only.
  • The pandarallel import, of a package never declared in pyproject.toml, is
    removed rather than added to the dependencies, since no parallel_apply
    exists anywhere in kg_emb. There is no lockfile entry to regenerate.
  • base_kg_dataset.py and umls.py imported their siblings through the
    absolute package path, so the package only initialised correctly for one
    ordering of the imports in __init__.py, which nothing enforced. Enabling
    ruff's isort rule would have sorted base_kg_dataset first and broken it.

Tests

tests/core/test_kg_emb.py, 22 behavioural tests: construction, indexing,
vocabularies, cardinality validation, split partitioning and reproducibility,
generic collation of variable-length ground truths, BaseKGDataset.set_task()
on a synthetic graph, and scoring invariants (DistMult symmetry in head and
tail, TransE margin on an exact triple).

No test asserts on a type annotation: annotations are metadata, and under
PEP 563 such an assertion compares against a string.

Every test was validated by fault injection. Reverting the fix it protects makes
it fail:

Test Mutation Caught
test_construction_and_length restore super().__init__(samples, dataset_name, task_name) yes
test_is_a_map_style_dataset add a fake set_shuffle yes
test_set_task_on_a_synthetic_graph drop **kwargs from the SampleKGDataset(...) call yes
test_global_numpy_state_is_untouched restore np.random.seed plus shuffle yes
test_variable_length_ground_truth_stays_a_python_list tensorise ground_truth_head in __getitem__ yes
... (22/22, full table on request) yes

These are hand-seeded mutants aimed at this PR's changes, not a systematic
mutation run, so 22/22 is not a mutation score in the usual sense. It supports a
weaker and sufficient claim: a rename-only fix cannot make this suite go green.

Scope and limits

  • This decouples kg_emb from the 2.0 pipeline; it does not migrate it into
    the streaming pipeline. If you would rather see the latter, the dataset layer
    here is isolated and tested and would be the starting point.
  • Correctness of the plumbing, not empirical validation: no MRR or Hits@k
    compared against the original papers. Verified on synthetic graphs only, as
    UMLS requires a licence.
  • get_dataloader remains streaming-only. kg_emb no longer depends on it, so
    this is not blocking here; tracked in PyHealth 2.0: several modules still target the 1.x dataset API #1201 together with the other
    modules still targeting the 1.x API.

Credit to @userjuma for the original diagnosis in #1192. The import chain
described there is accurate and was the starting point for this work.

AxelNoun and others added 8 commits August 25, 2026 10:46
SampleDataset is now a litdata.StreamingDataset that expects schema.pkl. A knowledge-graph task is an in-memory list of triples, so SampleKGDataset subclasses torch.utils.data.Dataset and exposes KGDatasetProtocol for the models.

Co-authored-by: Cursor <cursoragent@cursor.com>
KGE models only need entity_num, relation_num and task_spec_param. Annotate that structural contract with KGDatasetProtocol so the model layer no longer imports the removed SampleBaseDataset.

Co-authored-by: Cursor <cursoragent@cursor.com>
SampleKGDataset was never exported from pyhealth.datasets. The examples now import it from kg_emb.datasets and build a torch DataLoader with collate_fn_dict_with_padding, because get_dataloader requires litdata.StreamingDataset.set_shuffle().

Co-authored-by: Cursor <cursoragent@cursor.com>
Validate ratios with ValueError so the check survives python -O, and shuffle with a local Generator so the function no longer mutates global NumPy state.

Co-authored-by: Cursor <cursoragent@cursor.com>
pandarallel was never declared in pyproject.toml or pixi.lock. The undeclared import in umls.py and base_kg_dataset.py is what produced the ModuleNotFoundError on the kg_emb import path in issue sunlabuiuc#952. initialize() ran in umls.py with no parallel_apply in kg_emb; mimicextract's parallel_apply calls are unreachable on the empty BaseEHRDataset stub. There is no lockfile entry to regenerate.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cover construction, split reproducibility, generic collation of variable-length ground truths, set_task on a synthetic graph, and scoring invariants. Tests instantiate SampleKGDataset so a rename-only fix cannot go green.

Co-authored-by: Cursor <cursoragent@cursor.com>
Document the map-style SampleKGDataset path in the MedCode API page and add a synthetic TransE example that uses DataLoader instead of get_dataloader.

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace double-hyphen asides in five docstrings with periods or commas so they remain readable in a terminal and under Sphinx.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: kg_emb broken import in PyHealth 2.0

1 participant