kontext#
Kontext is a small self-contained library to manipulate nested trees.
Kontext introduce 2 main concepts: Paths and Keys.
Paths#
A Path is a string pointing to a nested tree value (e.g. 'a[0].b.inner').
Paths are used for tree manipulation/extraction:
tree = {
'a': [{'b': {'inner': 123}, 'c': 456}, {'e': 789}]
}
Extract values from a nested tree:
assert kontext.get_by_path(tree, 'a[0].b.inner') == 123
Flatten a nested tree:
assert kontext.flatten_with_path(tree) == { 'a[0].b.inner': 123, 'a[0].c': 456, 'a[1].e': 789, }
Note: a.b can match both a['b'] (index) or a.b (attribute).
Keys#
A Key is a named path:
input: Key = 'a[0].b.inner'
Here, the input key is assigned to the path value a[0].b.inner.
Keys are defined in a KeyedObject. A KeyedObject can be any arbitrary
Python object annotated with special : Key annotations:
@dataclasses.dataclass
class A:
x: Key
y: Optional[Key]
Here A defines 2 keys: x and y.
Those keys can then be resolved with:
kontext.get_keypaths: Get the key values.a = A(x='a[0].b.inner', y=None) assert kontext.get_keypaths(a) == { 'x': 'a[0].b.inner', 'y': None, }
kontext.resolve_from_keyed_obj: Get the key values and apply them to get the corresponding values from the tree.a = A(x='a[0].b.inner', y='a[1].e') assert kontext.resolve_from_keyed_obj(tree, a) == { 'x': 123, 'y': 789, }
[Advanced] Dynamically extract keys#
Rather than hardcoding the available keys as annotations (x: Key), it is
possible to dynamically define keys through the __kontext_keys__ protocol.
This can be used to propagate keys from an inner objects:
@dataclasses.dataclass
class B:
inner_keyed_obj: A
def __kontext_keys__(self) -> dict[str, str | None]:
return kontext.get_keypaths(self.inner_keyed_obj)
b = B(inner_keyed_obj=A(x='a[0].b.inner', y='a[1].e'))
assert kontext.get_keypaths(b) == {
'x': 'a[0].b.inner',
'y': None,
}
Use case#
In Kauldron, paths and keys are used to link batch, model, losses, metrics,… together without having to hardcode any assumption on the batch structure, model inputs,…:
Each model can define through Key what are the expected model inputs:
class MyModel(nn.Module):
img: kontext.Key = kontext.REQUIRED # Match `__call__` signature
label: kontext.Key = kontext.REQUIRED
@nn.compact
def __call__(self, img, label):
...
Then each user can specify in their config how the model inputs are mapped to their specific batch:
model = MyModel(
img='batch.image',
label='batch.label',
)
Kauldron internally uses kontext to extract the values from batch and
forward them to the model.
context = {
'batch': batch,
...
}
model_kwargs = kontext.resolve_from_keyed_obj(context, model)
pred = model.apply(rng, **model_kwargs)
Helper#
Rather than using string which can be fragile, it is possible to use
kontext.path_builder_from to dynamically generate the paths with auto-complete
and type checking.
Let’s imaging your dataset yield some structured object (e.g.
typing.TypedDict, dataclass,…):
@flax.struct.dataclass
class Batch:
image: jnp.array
label: jnp.array
In your config, you can replace the keys str by their typed version:
batch = kontext.path_builder_from('batch', Batch)
model = MyModel(
img=batch.image, # < Auto-complete and attribute checking, rather than `str`
label=batch.label,
)