utils.loggable
The Loggable
module provides tools for configuring the logging of Sapicore models.
It supports logging simple properties to tensorboard as well as arbitrary properties,
including tensors and arrays, to HDF5 files.
Individual properties can be included or excluded from logging using a config
dict that can be saved to a YAML, similarly to tree-config
configuration.
Loggable
Loggable
defines an API for properties to support opting-in to logging.
Users may list loggable properties by including them in _loggable_props_
on a per class basis.
Subclassing extends _loggable_props_
, which are accumulated across
all superclasses when loggable_props
is called. The latter also
updates the cache list _loggable_props
.
Setting utils.loggable.Loggable._loggable_props_, in and of itself, will NOT update the cache.
To support the logging of nested objects, we use _loggable_children_
to list all objects that should be inspected for loggable properties. These are similarly listed in
loggable_children
.
API
Users may obtain the loggable properties as YAML files, dictionaries, or flat lists of properties using the following functions:
read_loggable_from_object()
, read_loggable_from_file()
, update_loggable_from_object()
,
dump_loggable()
, load_save_get_loggable_properties()
, and get_loggable_properties()
.
They all provide a mapping from the property name to a boolean indicting whether the property should be logged.
Example
The following class inherits from Loggable:
>>> class Example(Loggable):
>>> _loggable_props_: tuple[str] = ("voltage",)
>>> def __init__(self, voltage: list[float]):
>>> super().__init__()
>>> self.voltage = voltage
After instantiating an object, refresh the cache and obtain the loggable properties:
>>> dummy = Example(voltage=[1, 2, 3])
>>> print(dummy.loggable_props)
['voltage']
To check which attributes are loggable:
>>> read_loggable_from_object(dummy, True)
{'voltage': True}
Note
log_tensor_board()
supports taking the flat list of properties and
logging all the properties that are set to be logged to the tensorboard
format on disk using a SummaryWriter
instance.
Similarly, NixLogWriter
supports logging arbitrary tensors and numpy
arrays to a nix HDF5 file. NixLogReader
can then be used to load the
data from the nix file into scalars or numpy arrays of the original shape.
Warning
This module may change significantly in Sapicore > 0.3.0.
- class utils.loggable.Loggable
The
Loggable
can be used as a base class for objects that need to control which of its properties can be logged.When getting loggable properties,
read_loggable_from_object()
will callloggable_props()
andloggable_children()
, which looks up all the properties from_loggable_props_
and_loggable_children_
.Example
The following class has two loggable properties:
>>> class LoggableModel(Loggable): >>> _loggable_props_ = ("frame", "color") >>> frame: str = "square" >>> color: str = "blue"
This snippet will return what they are after instantiating an object of this class:
>>> LoggableModel().loggable_props ['frame', 'color']
To accumulate over loggable children as well:
>>> read_loggable_from_object(LoggableModel(), True) {'frame': True, 'color': True}
- property loggable_children: Dict[str, str]
- A property containing a dictionary of loggable attribute names from derivative classes (i.e.,
listed in _loggable_children_ of this class and its parent classes).
Example
>>> class Thing(Loggable): >>> _loggable_children_ = {"useful": "box"} >>> box: str = None >>> >>> class Box(Loggable): >>> pass >>> >>> model = Thing() >>> model.box = Box() >>> model.loggable_children {'useful': 'box'}
- property loggable_props: List[str]
A property containing a list of instance attribute names that are loggable (i.e., listed in _loggable_props_ of this class and its parent classes).
- class utils.loggable.NixLogReader(filename: str | Path)
A reader that can read and return the data written to a nix file using
NixLogWriter
.E.g.:
. code-block:: python
- with NixLogReader(‘log.h5’) as reader:
print(‘Logged experiments: ‘, reader.get_experiment_names()) # prints e.g. Logged experiments: [‘example’] print(‘Logged properties: ‘, reader.get_experiment_property_paths(‘example’)) # prints e.g. Logged properties: [(‘neuron_1’, ‘activation’), … print(‘Activation: ‘, reader.get_experiment_property_data(‘example’, (‘neuron_1’, ‘activation’))) # prints Activation: (array([0, 1, 2], dtype=int64), [array([0…
- close_file()
Closes the nix file.
- filename: str | Path = ''
The filename of the nix file that will be opened when
open_file()
is called.
- get_experiment_names() List[str]
Returns the list of block names found in the file that was created with
NixLogWriter.create_block()
.E.g.:
. code-block:: python
>>> with NixLogReader('log.h5') as reader: >>> print(reader.get_experiment_names()) ['example']
- get_experiment_property_data(name: str, property_path: Tuple[str, ...]) Tuple[ndarray, List[ndarray | float | int]]
Returns all the data of a property previously logged with
NixLogWriter.log()
.- Parameters:
name – The name of the block that contains the data.
property_path – The full attribute path to the property, so we can locate it in the data. It is an item from the list returned by
get_experiment_property_paths()
.
- Returns:
A 2-tuple of
(counter, data)
.counter
anddata
, have the same length.counter
is an array containing the count value that corresponds the each data item as passed to thecounter
parameter inNixLogWriter.log()
.data
is a list of the values logged for this property, one item for each call toNixLogWriter.log()
. It can be scalars or numpy arrays.
E.g.:
. code-block:: python
>>> with NixLogReader('log.h5') as reader: >>> print(reader.get_experiment_property_data( ... 'example', ('neuron_1', 'activation'))) (array([0, 1, 2], dtype=int64), [ array([0.56843126, 1.0845224 , 1.3985955 ], dtype=float32), array([0.40334684, 0.83802634, 0.7192576 ], dtype=float32), array([0.40334353, 0.59663534, 0.18203649], dtype=float32) ])
- get_experiment_property_paths(name: str) List[Tuple[str]]
Returns the list of properties found in the file that was created with
NixLogWriter.get_property_arrays()
.Each item in the list is itself a tuple of strings. This tuple represents the path to the property starting from the root object (e.g. the model) as attributes. E.g.
model.child.prop
will be represented as('model', 'child', 'prop')
.- Parameters:
name – The name of the block to scan for properties.
E.g.:
. code-block:: python
>>> with NixLogReader('log.h5') as reader: >>> print(reader.get_experiment_property_paths('example')) [('neuron_1', 'activation'), ('neuron_1', 'intensity'), ('synapse', 'activation'), ('neuron_2', 'activation'), ('neuron_2', 'intensity'), ('activation_sum',)]
- nix_file: File | None = None
The internal nix file object.
- open_file()
Opens the file named in
filename
that was created withNixLogWriter.create_file()
.close_file()
must be called after this to close the file.An alternative syntax is to use it as a context manager, which is a little safer:
. code-block:: python
- with NixLogReader(‘log.h5’) as reader:
…
This internally calls
open_file()
andclose_file()
as needed.
- class utils.loggable.NixLogWriter(filename: str | Path, git_hash='', compression=Compression.Auto, metadata: dict | None = None, config_data: dict | None = None)
Writer that supports logging properties to a Nix HDF5 file.
It supports logging arbitrary torch tensors, numpy arrays, and numerical scalars. The logged data can then be retrieved with
NixLogReader
.A typical example is:
. code-block:: python
log_props = read_loggable_from_object(Model(), True) writer = NixLogWriter(h5_filename) # create the file writer.create_file() # create a section for this session writer.create_block(‘example’) # create the objects required for logging using the loggable properties property_arrays = writer.get_property_arrays(‘example’, log_props) do_work… # all the enabled loggable props will be logged writer.log(property_arrays, 0) do_more_work… writer.log(property_arrays, 1) # now close writer.close_file()
- close_file() None
Closes the nix file.
- compression = 'Auto'
The compression to use in the file.
It is one of
nix.Compression
.
- config_data: dict | None = None
A dict of
tree_config
config data used to config the model that can be included in the file.It is saved to the file after being encoded using
yaml_dumps()
.
- create_block(name: str) None
Creates a new block using the given that could subsequently be used with
get_property_arrays()
to log to this block.It is used to organize the data into sessions.
- create_file() None
Creates the file named in
filename
. If it exists, an error is raised.close_file()
must be called after this to close the file.
- filename: str | Path = ''
The filename of the nix file that will be created when
create_file()
is called.
- get_property_arrays(name: str, loggable_properties: List[Tuple[List[str], Any, str, bool]]) List[Tuple[Any, str, DataArray, DataArray, DataArray, DataArray]]
Takes the list of properties to be logged and it creates a list of objects that can be used with
log
to log all these properties.See the class for an example.
- Parameters:
name – The name of the block to which the properties will be logged. The block must have been created previously with
create_block()
.loggable_properties – The list of properties (in the format of
get_loggable_properties()
) to read and log.
- Returns:
A list of objects that can be passed to
log()
to log the properties.
- git_hash = ''
An optional git hash that can be included in the file. It can be used to label the model version for example.
- static log(property_arrays: List[Tuple[Any, str, DataArray, DataArray, DataArray, DataArray]], counter: int) None
Logs all the properties passed to
get_property_arrays()
.See the class for an example.
- Parameters:
property_arrays – The list of objects returned by
get_property_arrays()
counter – An arbitrary integer that gets logged with the properties that could be later used to identify the data, e.g. the epoch when it was logged.
- metadata: dict | None = None
A dict of arbitrary metadata that can be included in the file.
It is saved to the file after being encoded using
yaml_dumps()
.
- nix_file: File | None = None
The internal nix file object.
- utils.loggable.dump_loggable(filename: str | Path, data: Dict[str, Any]) None
Dumps the loggable data gotten with e.g.
read_loggable_from_object()
to a yaml file.- Parameters:
filename – The yaml filename.
data – The loggable data.
E.g.:
>>> class Model(Loggable): >>> _config_props_ = ('name', ) >>> name = 'chair' >>> model = Model() >>> d = read_loggable_from_object(model, True) >>> dump_config('loggable.yaml', d) >>> with open('loggable.yaml') as fh: ... print(fh.read())
Which prints
name: true
.
- utils.loggable.get_loggable_properties(obj: Loggable, loggable: Dict[str, Any], property_path: List[str] | None = None) List[Tuple[List[str], Any, str, bool]]
Takes the loggable data read with
read_loggable_from_object()
orread_loggable_from_file()
, filters those properties that should be logged (i.e. those that are True) and flattens them into a single flat list.This list could be used to efficiently log all the properties that need to be logged, by iterating the list.
- Parameters:
obj – The object from which to get the loggables.
loggable – The loggable data previously read with
read_loggable_from_object()
orread_loggable_from_file()
.property_path –
A list of strings, indicating the object children names, starting from some root object that lead to the
obj
. This can be used to reconstruct the full object path to each property starting fromobj
.Should just be None (default) in user code.
- Returns:
A list of 4-tuples, each containing
(property_path, item, prop, value)
.property_path
is the list of strings, indicating the object children names, starting from some root object that lead to theitem
.item
is the object or child whose propertyprop
is being considered.value
is a bool indicating whether to log the property.
E.g.:
class Model(Loggable): _loggable_props_ = ('name', ) _loggable_children_ = {'the box': 'box'} name = 'chair' box = None class Box(Loggable): _loggable_props_ = ('volume', ) volume = 12
then:
>>> model = Model() >>> model.box = Box() >>> d = read_loggable_from_object(model, False) {'the box': {'volume': False}, 'name': False'} >>> d['the box']['volume'] = True >>> get_loggable_properties(model, d) [(['the box', 'volume'], <Box at 0x16f0b2b5320>, 'volume', True)]
- utils.loggable.load_save_get_loggable_properties(obj: Any, filename: str | Path, default_value: bool) List[Tuple[List[str], Any, str, bool]]
Loads the loggable configuration from the yaml file (
read_loggable_from_file()
, if the file doesn’t exist it creates it withread_loggable_from_object()
anddump_loggable()
), updates it withupdate_loggable_from_object()
and then dumps it back to the yaml file withdump_loggable()
. It also returns the flattened loggable properties usingget_loggable_properties()
.This can be used to get the loggable properties, but also making sure the file contains the current loggables including any new properties not previously there.
- Parameters:
obj – The loggable object.
filename – The yaml filename.
default_value – A bool (True/False), indicating whether to default the new loggable properties to True (they are logged) or False (they are not logged). The individual properties can subsequently be set either way manually in the file.
- Returns:
The loggable list returned by
get_loggable_properties()
.
E.x.:
class Model(Loggable): _config_props_ = ('name', ) name = 'chair' class ModelV2(Loggable): _config_props_ = ('name', 'side') name = 'chair' side = 'left'
then:
>>> model = Model() >>> load_save_get_loggable_properties(model, 'loggable.yaml', True) [(['name'], <Model at 0x16f0b2b5278>, 'name', True)] >>> # then later for v2 of the model >>> model_v2 = ModelV2() >>> load_save_get_loggable_properties(model_v2, 'loggable.yaml', True) [(['name'], <ModelV2 at 0x16f0b2b5695>, 'name', True), (['side'], <ModelV2 at 0x16f0b2b5695>, 'side', True)] >>> with open('loggable.yaml') as fh: ... print(fh.read())
this prints:
name: true side: true
- utils.loggable.log_tensor_board(writer, loggable_properties: List[Tuple[List[str], Any, str, bool]], global_step: int | None = None, walltime: float | None = None, prefix: str = '') None
Logs the current value of all the given properties to the TensorBoard writer object for live display.
Properties that are arrays are logged as a sequence of scalars, which is inefficient. It is suggested to use
NixLogWriter
for complex data andlog_tensor_board()
only for scalars.- Parameters:
writer – An instantiated
torch.utils.tensorboard.SummaryWriter
object to which the data will be written.loggable_properties – The list of properties (in the format of
get_loggable_properties()
) to read and log.global_step – The optional global timestep, which must be monotonically increasing if providing.
walltime – The optional wall time.
prefix – An optional prefix used to log the data under.
- utils.loggable.read_loggable_from_file(filename: str | Path) Dict[str, Any]
Reads and returns the dict indicating whether to log objects from a file that was previously dumped with
dump_loggable()
.- Parameters:
filename – The filename to the yaml file.
E.g.:
>>> class Model(Loggable): >>> _loggable_props_ = ('name', ) >>> name = 'chair' >>> model = Model() >>> d = read_loggable_from_object(model, True) >>> dump_loggable('loggable.yaml', d) >>> read_loggable_from_file('loggable.yaml') {'name': True}
- utils.loggable.read_loggable_from_object(obj: Loggable, default_value: bool) Dict[str, Any]
Returns a recursive dict containing all the loggable properties of the obj and its loggable children. Each item maps the item name to
default_value
, indicating whether to log the item by default.- Parameters:
obj – The object from which to get the loggables.
default_value – A bool (True/False), indicating whether to default the loggable properties to True (they are logged) or False (they are not logged). The individual properties can subsequently be set either way manually in the dict.
E.g.:
class Model(Loggable): _loggable_props_ = ('name', ) _loggable_children_ = {'the box': 'box'} name = 'chair' box = None class Box(Loggable): _loggable_props_ = ('volume', ) volume = 12
then:
>>> model = Model() >>> model.box = Box() >>> loggables = read_loggable_from_object(model, False) >>> loggables {'the box': {'volume': False}, 'name': False'} >>> # to log name >>> loggables['name'] = True
- utils.loggable.update_loggable_from_object(obj: Loggable, loggable: Dict[str, Any], default_value: bool) Dict[str, Any]
Takes the loggable data read with
read_loggable_from_object()
orread_loggable_from_file()
and creates a new loggable dict from it and updates it by adding any new loggable properties of the object, that has been added to the object or its children since the last time it was created.- Parameters:
obj – The object from which to get the loggables.
loggable – The the loggable data read with
read_loggable_from_object()
orread_loggable_from_file()
.default_value – A bool (True/False), indicating whether to default the new loggable properties to True (they are logged) or False (they are not logged). The individual properties can subsequently be set either way manually in the dict.
- Returns:
The updated loggable dict.