Source code for peal.sparse_dictionaries.interfaces
"""Base config and interface shared by every PEAL sparse dictionary.
A sparse dictionary (SAE, Matryoshka SAE, Procrustes/SVD, SpLICE, ...) turns an
encoder activation vector into a sparse code over named components and back.
DiDAE edits a generator's semantic latent along those components, and CFKD can
attach one to its generator via ``adaptor_config.sparse_dictionary``. This module
holds the pydantic config every concrete dictionary config extends and the
abstract ``SparseDictionary`` API that ``sparse_dictionary_factory`` instantiates.
"""
from typing import Union
from pydantic import BaseModel
from peal.data.interfaces import DataConfig
from abc import ABC, abstractmethod
[docs]
class SparseDictionaryConfig(BaseModel):
"""Common configuration fields of every sparse dictionary.
Concrete dictionaries subclass this and override ``sparse_dictionaries_type``
so that ``get_sparse_dictionary`` can resolve the class from a yaml file.
Parameters
----------
n_components : int or None
Number of dictionary components (columns of the component matrix).
Some dictionaries set this themselves after fitting.
dict_size : int or None
Size of the learned dictionary for SAE-style dictionaries; treated as
a synonym of ``n_components`` by several implementations.
sparse_dictionaries_type : str
Registry key of the concrete dictionary class.
category : str
Fixed to ``"sparse_dictionaries"``; used when resolving default paths.
base_path, name, run_name : str or None
Where fitted weights, plots and logs of this dictionary live.
weights_path : str or None
Explicit path of the fitted weights; overrides ``base_path``/``name``.
act_size : int
Dimensionality of the encoder activations the dictionary is fitted on.
visualizations_per_component : int
How many top-activating samples to render per component.
weights_name : str
File name of the weights inside ``base_path``.
batch_size : int
Batch size used when the dictionary itself iterates over data.
component_strings : list or None
Optional human-readable names for the components.
data : DataConfig, dict, str or None
The data the dictionary is fitted on (config object, dict or yaml path).
fitted_on_encoder : str or None
Identity of the encoder whose activations the dictionary was fitted on;
see the attribute docstring below.
"""
n_components: Union[int, None] = None
dict_size: Union[int, None] = None
sparse_dictionaries_type: str
category: str = "sparse_dictionaries"
base_path: Union[str, None] = None
name: Union[str, None] = None
run_name: Union[str, None] = None
weights_path: Union[str, None] = None
act_size: int = 512
visualizations_per_component: int = 100
weights_name: str = "weights.npz"
batch_size: int = 16
component_strings: Union[list, None] = None
data: Union[DataConfig, dict, str, None] = None
fitted_on_encoder: Union[str, None] = None
"""
Which encoder's activations this dictionary was fitted on, stamped at fit
time. Consumers that edit in the generator's z_sem space (DiDAE) must refuse
a dictionary fitted somewhere else: an OpenCLIP dictionary and a diffusion
autoencoder's z_sem are both 768-d, so nothing about the shapes catches the
mismatch and the edits would simply be meaningless.
"""
[docs]
class SparseDictionary(ABC):
"""Abstract interface of a sparse dictionary.
Subclasses hold a ``config`` (a ``SparseDictionaryConfig``) and implement
fitting from a tensor of activations or from dataloaders, plus
serialisation. Most concrete implementations additionally offer
``encode``/``decompose``, ``get_components`` and ``eval``, which the
explainers call but which are not part of this minimal base class.
Attributes
----------
config : SparseDictionaryConfig
The configuration the dictionary was built from.
"""
config: SparseDictionaryConfig
[docs]
@abstractmethod
def fit(self, X):
"""Fit the dictionary on a tensor of activations.
Parameters
----------
X : torch.Tensor
Activations of shape ``[N, act_size]``.
Raises
------
NotImplementedError
Always, in the base class.
"""
raise NotImplementedError("Subclasses should implement this method.")
[docs]
@abstractmethod
def fit_from_dataloaders(self, dataloaders):
"""Fit the dictionary by iterating over one or more dataloaders.
Parameters
----------
dataloaders : list of torch.utils.data.DataLoader
Loaders yielding ``(x, y)`` batches; implementations run the
encoder themselves and collect the activations.
Raises
------
NotImplementedError
Always, in the base class.
"""
raise NotImplementedError("Subclasses should implement this method.")
[docs]
@abstractmethod
def save_on_disk(self, path):
"""Serialise the fitted state to ``path``.
Raises
------
NotImplementedError
Always, in the base class.
"""
raise NotImplementedError("Subclasses should implement this method.")
[docs]
@abstractmethod
def load_from_disk(self, path):
"""Restore the fitted state from ``path``.
Raises
------
NotImplementedError
Always, in the base class.
"""
raise NotImplementedError("Subclasses should implement this method.")