Source code for peal.sparse_dictionaries.probe_sparse_autoencoder

"""Sparse dictionary made of the weight rows of a pretrained linear probe.

``ProbeSAE`` is the degenerate member of PEAL's sparse dictionary family: instead
of learning atoms from activations it takes the final ``fc`` layer of a trained
classifier checkpoint and exposes its class weight vectors as the components. It
lets the DiDAE / CFKD pipelines use the classifier's own decision directions as
candidate edit directions without any fitting step.
"""

from typing import Union

import torch

from peal.sparse_dictionaries.interfaces import SparseDictionary, SparseDictionaryConfig


[docs] class ProbeSAEConfig(SparseDictionaryConfig): """Config of :class:`ProbeSAE`. Parameters ---------- pretrained_model_path : str Path of a ``torch.save``-d Lightning predictor whose ``model.model.fc`` is the linear layer whose weight rows become the dictionary atoms. n_components : int or None, default 2 Number of components; informational only, the probe decides the count. sparse_dictionaries_type : str Registry key, fixed to ``"ProbeSAE"``. ending : str File ending the factory expects for saved dictionaries (``".npz"``). """ pretrained_model_path: str n_components: Union[int, None] = 2 sparse_dictionaries_type: str = "ProbeSAE" ending: str = ".npz"
[docs] class ProbeSAE(SparseDictionary): """Dictionary whose atoms are the rows of a classifier's last linear layer. Parameters ---------- config : ProbeSAEConfig Points to the checkpoint; ``fc.weight`` is cloned into ``self.W`` with shape ``(n_classes, feature_dim)``, and ``self.mu`` is a zero mean vector of length ``feature_dim`` so callers treating the dictionary as centered get an identity offset. """
[docs] def __init__(self, config): """Load the checkpoint on CPU and copy its ``fc`` weight matrix.""" self.config = config model = torch.load(config.pretrained_model_path, map_location="cpu") self.W = model.model.model.fc.weight.data.clone() self.mu = torch.zeros(self.W.size(1))
[docs] def fit(self, X): """No-op: the probe weights are fixed by the checkpoint."""
[docs] def fit_from_dataloaders(self, dataloaders, feature_extractor): """No-op: the probe weights are fixed by the checkpoint."""
[docs] def get_components(self): """Return the atoms as columns, i.e. ``W.t()`` of shape ``(D, n_classes)``.""" return self.W.t()
[docs] def save_on_disk(self, path): """Save ``{"W": self.W}`` with ``torch.save`` to ``path``.""" torch.save( { "W": self.W, }, path, )
[docs] def load_from_disk(self, path): """Restore ``self.W`` from a file written by :meth:`save_on_disk`.""" checkpoint = torch.load(path) self.W = checkpoint["W"]