Comparator Utils#

utils #

FUNCTION DESCRIPTION
get_layer_names

Get names of all layers in a model.

filter_layers_by_type

Filter layers in a model by their type.

match_layers_by_name

Match layers with the same names.

match_layers_by_position

Match layers by their position in the model.

match_layers_by_depth

Match layers by their relative depth in the network.

match_layers_by_type

Match all layers of a specific type between two models.

Functions#

get_layer_names(model: torch.nn.Module, include_container_modules: bool = False) -> list[str] #

Get names of all layers in a model.

PARAMETER DESCRIPTION
model

Model to inspect.

TYPE: Module

include_container_modules

If True, include container modules (Sequential, ModuleList, etc.). If False, only include leaf modules (Conv2d, Linear, etc.).

TYPE: bool, optional, by default False DEFAULT: False

RETURNS DESCRIPTION
list[str]

List of layer names.

Examples:

>>> import torch.nn as nn
>>> model = nn.Sequential(nn.Linear(10, 20), nn.ReLU(), nn.Linear(20, 5))
>>> get_layer_names(model)
['0', '1', '2']

Custom model:

>>> class Net(nn.Module):
...     def __init__(self):
...         super().__init__()
...         self.conv1 = nn.Conv2d(3, 64, 3)
...         self.fc = nn.Linear(64, 10)
...
...     def forward(self, x):
...         return self.fc(self.conv1(x).flatten(1))
>>> model = Net()
>>> get_layer_names(model)
['conv1', 'fc']
Source code in spectre/comparator/utils.py
def get_layer_names(
    model: torch.nn.Module, include_container_modules: bool = False
) -> list[str]:
    """
    Get names of all layers in a model.

    Parameters
    ----------
    model : nn.Module
        Model to inspect.

    include_container_modules : bool, optional, by default False
        If True, include container modules (Sequential, ModuleList, etc.).
        If False, only include leaf modules (Conv2d, Linear, etc.).

    Returns
    -------
    list[str]
        List of layer names.

    Examples
    --------
    >>> import torch.nn as nn
    >>> model = nn.Sequential(nn.Linear(10, 20), nn.ReLU(), nn.Linear(20, 5))
    >>> get_layer_names(model)
    ['0', '1', '2']

    Custom model:

    >>> class Net(nn.Module):
    ...     def __init__(self):
    ...         super().__init__()
    ...         self.conv1 = nn.Conv2d(3, 64, 3)
    ...         self.fc = nn.Linear(64, 10)
    ...
    ...     def forward(self, x):
    ...         return self.fc(self.conv1(x).flatten(1))
    >>> model = Net()
    >>> get_layer_names(model)
    ['conv1', 'fc']
    """
    if include_container_modules:
        return [name for name, _ in model.named_modules() if name != ""]
    else:
        # Only include leaf modules (no children)
        layer_names = []
        for name, module in model.named_modules():
            if name != "" and len(list(module.children())) == 0:
                layer_names.append(name)
        return layer_names

filter_layers_by_type(model: torch.nn.Module, layer_types: list[type] | type) -> dict[str, torch.nn.Module] #

Filter layers in a model by their type.

PARAMETER DESCRIPTION
model

Model to filter.

TYPE: Module

layer_types

Layer type(s) to filter for (e.g., nn.Conv2d, nn.Linear).

TYPE: list[type] | type

RETURNS DESCRIPTION
dict[str, Module]

Dictionary mapping layer names to modules of specified type(s).

Examples:

>>> import torch.nn as nn
>>> model = nn.Sequential(nn.Conv2d(3, 64, 3), nn.ReLU(), nn.Linear(64, 10))
>>> filter_layers_by_type(model, nn.Linear)
{'2': Linear(in_features=64, out_features=10, bias=True)}

Multiple types:

>>> filter_layers_by_type(model, [nn.Conv2d, nn.Linear])
{'0': Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1)),
 '2': Linear(in_features=64, out_features=10, bias=True)}
Source code in spectre/comparator/utils.py
def filter_layers_by_type(
    model: torch.nn.Module, layer_types: list[type] | type
) -> dict[str, torch.nn.Module]:
    """
    Filter layers in a model by their type.

    Parameters
    ----------
    model : nn.Module
        Model to filter.

    layer_types : list[type] | type
        Layer type(s) to filter for (e.g., nn.Conv2d, nn.Linear).

    Returns
    -------
    dict[str, nn.Module]
        Dictionary mapping layer names to modules of specified type(s).

    Examples
    --------
    >>> import torch.nn as nn
    >>> model = nn.Sequential(nn.Conv2d(3, 64, 3), nn.ReLU(), nn.Linear(64, 10))
    >>> filter_layers_by_type(model, nn.Linear)
    {'2': Linear(in_features=64, out_features=10, bias=True)}

    Multiple types:

    >>> filter_layers_by_type(model, [nn.Conv2d, nn.Linear])
    {'0': Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1)),
     '2': Linear(in_features=64, out_features=10, bias=True)}
    """
    if not isinstance(layer_types, list):
        layer_types = [layer_types]

    filtered = OrderedDict()
    for name, module in model.named_modules():
        if name != "" and any(isinstance(module, t) for t in layer_types):
            filtered[name] = module

    return filtered

match_layers_by_name(layers1: list[str], layers2: list[str]) -> list[tuple[str, str]] #

Match layers with the same names.

PARAMETER DESCRIPTION
layers1

Layer names from first model.

TYPE: list[str]

layers2

Layer names from second model.

TYPE: list[str]

RETURNS DESCRIPTION
list[tuple[str, str]]

List of (layer1_name, layer2_name) pairs for matching names.

Examples:

>>> layers1 = ["conv1", "relu", "conv2"]
>>> layers2 = ["conv1", "bn", "conv2", "relu"]
>>> match_layers_by_name(layers1, layers2)
[('conv1', 'conv1'), ('relu', 'relu'), ('conv2', 'conv2')]
Source code in spectre/comparator/utils.py
def match_layers_by_name(
    layers1: list[str], layers2: list[str]
) -> list[tuple[str, str]]:
    """
    Match layers with the same names.

    Parameters
    ----------
    layers1 : list[str]
        Layer names from first model.

    layers2 : list[str]
        Layer names from second model.

    Returns
    -------
    list[tuple[str, str]]
        List of (layer1_name, layer2_name) pairs for matching names.

    Examples
    --------
    >>> layers1 = ["conv1", "relu", "conv2"]
    >>> layers2 = ["conv1", "bn", "conv2", "relu"]
    >>> match_layers_by_name(layers1, layers2)
    [('conv1', 'conv1'), ('relu', 'relu'), ('conv2', 'conv2')]
    """
    common_names = set(layers1) & set(layers2)
    # Preserve order from layers1
    return [(name, name) for name in layers1 if name in common_names]

match_layers_by_position(layers1: list[str], layers2: list[str]) -> list[tuple[str, str]] #

Match layers by their position in the model.

PARAMETER DESCRIPTION
layers1

Layer names from first model.

TYPE: list[str]

layers2

Layer names from second model.

TYPE: list[str]

RETURNS DESCRIPTION
list[tuple[str, str]]

List of (layer1_name, layer2_name) pairs matched by position. Length is min(len(layers1), len(layers2)).

Examples:

>>> layers1 = ["layer1", "layer2", "layer3"]
>>> layers2 = ["conv1", "conv2"]
>>> match_layers_by_position(layers1, layers2)
[('layer1', 'conv1'), ('layer2', 'conv2')]
Source code in spectre/comparator/utils.py
def match_layers_by_position(
    layers1: list[str], layers2: list[str]
) -> list[tuple[str, str]]:
    """
    Match layers by their position in the model.

    Parameters
    ----------
    layers1 : list[str]
        Layer names from first model.

    layers2 : list[str]
        Layer names from second model.

    Returns
    -------
    list[tuple[str, str]]
        List of (layer1_name, layer2_name) pairs matched by position.
        Length is min(len(layers1), len(layers2)).

    Examples
    --------
    >>> layers1 = ["layer1", "layer2", "layer3"]
    >>> layers2 = ["conv1", "conv2"]
    >>> match_layers_by_position(layers1, layers2)
    [('layer1', 'conv1'), ('layer2', 'conv2')]
    """
    n_pairs = min(len(layers1), len(layers2))
    return [(layers1[i], layers2[i]) for i in range(n_pairs)]

match_layers_by_depth(model1: torch.nn.Module, model2: torch.nn.Module) -> list[tuple[str, str]] #

Match layers by their relative depth in the network.

This function computes the depth of each layer (number of parent modules) and matches layers at similar relative positions in the network hierarchy.

PARAMETER DESCRIPTION
model1

First model.

TYPE: Module

model2

Second model.

TYPE: Module

RETURNS DESCRIPTION
list[tuple[str, str]]

List of (layer1_name, layer2_name) pairs matched by depth.

Examples:

>>> import torch.nn as nn
>>> model1 = nn.Sequential(
...     nn.Sequential(nn.Linear(10, 20), nn.ReLU()),
...     nn.Linear(20, 5),
... )
>>> model2 = nn.Sequential(nn.Linear(10, 15), nn.ReLU(), nn.Linear(15, 5))
>>> match_layers_by_depth(model1, model2)
[('0.0', '0'), ('0.1', '1'), ('1', '2')]
Source code in spectre/comparator/utils.py
def match_layers_by_depth(
    model1: torch.nn.Module, model2: torch.nn.Module
) -> list[tuple[str, str]]:
    """
    Match layers by their relative depth in the network.

    This function computes the depth of each layer (number of parent modules)
    and matches layers at similar relative positions in the network hierarchy.

    Parameters
    ----------
    model1 : torch.nn.Module
        First model.

    model2 : torch.nn.Module
        Second model.

    Returns
    -------
    list[tuple[str, str]]
        List of (layer1_name, layer2_name) pairs matched by depth.

    Examples
    --------
    >>> import torch.nn as nn
    >>> model1 = nn.Sequential(
    ...     nn.Sequential(nn.Linear(10, 20), nn.ReLU()),
    ...     nn.Linear(20, 5),
    ... )
    >>> model2 = nn.Sequential(nn.Linear(10, 15), nn.ReLU(), nn.Linear(15, 5))
    >>> match_layers_by_depth(model1, model2)
    [('0.0', '0'), ('0.1', '1'), ('1', '2')]
    """

    def get_layer_depths(model: torch.nn.Module) -> dict[int, list[str]]:
        """Get depth (number of dots in name) for each layer."""
        depths: dict[int, list[str]] = {}
        for name in get_layer_names(model):
            depth = name.count(".")
            if depth not in depths:
                depths[depth] = []
            depths[depth].append(name)
        return depths

    depths1 = get_layer_depths(model1)
    depths2 = get_layer_depths(model2)

    # Match layers at each depth level
    pairs = []
    for depth in sorted(set(depths1.keys()) & set(depths2.keys())):
        layers_at_depth1 = depths1[depth]
        layers_at_depth2 = depths2[depth]
        n_pairs = min(len(layers_at_depth1), len(layers_at_depth2))
        for i in range(n_pairs):
            pairs.append((layers_at_depth1[i], layers_at_depth2[i]))

    return pairs

match_layers_by_type(model1: torch.nn.Module, model2: torch.nn.Module, layer_type: type | list[type] = torch.nn.Linear) -> list[tuple[str, str]] #

Match all layers of a specific type between two models.

PARAMETER DESCRIPTION
model1

First model.

TYPE: Module

model2

Second model.

TYPE: Module

layer_type

Layer type(s) to match (e.g., torch.nn.Conv2d, torch.nn.Linear).

TYPE: type | list[type], optional, by default torch.nn.Linear DEFAULT: Linear

RETURNS DESCRIPTION
list[tuple[str, str]]

List of (layer1_name, layer2_name) pairs for matching layer types. Matched by position within layers of that type.

Examples:

>>> import torch.nn as nn
>>> model1 = nn.Sequential(nn.Conv2d(3, 64, 3), nn.ReLU(), nn.Linear(64, 10))
>>> model2 = nn.Sequential(nn.Conv2d(3, 32, 3), nn.ReLU(), nn.Linear(32, 10))
>>> match_layers_by_type(model1, model2, nn.Linear)
[('2', '2')]

Multiple types:

>>> match_layers_by_type(model1, model2, [nn.Conv2d, nn.Linear])
[('0', '0'), ('2', '2')]
Source code in spectre/comparator/utils.py
def match_layers_by_type(
    model1: torch.nn.Module,
    model2: torch.nn.Module,
    layer_type: type | list[type] = torch.nn.Linear,
) -> list[tuple[str, str]]:
    """
    Match all layers of a specific type between two models.

    Parameters
    ----------
    model1 : torch.nn.Module
        First model.

    model2 : torch.nn.Module
        Second model.

    layer_type : type | list[type], optional, by default torch.nn.Linear
        Layer type(s) to match (e.g., torch.nn.Conv2d, torch.nn.Linear).

    Returns
    -------
    list[tuple[str, str]]
        List of (layer1_name, layer2_name) pairs for matching layer types.
        Matched by position within layers of that type.

    Examples
    --------
    >>> import torch.nn as nn
    >>> model1 = nn.Sequential(nn.Conv2d(3, 64, 3), nn.ReLU(), nn.Linear(64, 10))
    >>> model2 = nn.Sequential(nn.Conv2d(3, 32, 3), nn.ReLU(), nn.Linear(32, 10))
    >>> match_layers_by_type(model1, model2, nn.Linear)
    [('2', '2')]

    Multiple types:

    >>> match_layers_by_type(model1, model2, [nn.Conv2d, nn.Linear])
    [('0', '0'), ('2', '2')]
    """
    layers1 = filter_layers_by_type(model1, layer_type)
    layers2 = filter_layers_by_type(model2, layer_type)

    # Match by position within layers of specified type
    keys1 = list(layers1.keys())
    keys2 = list(layers2.keys())
    n_pairs = min(len(keys1), len(keys2))

    return [(keys1[i], keys2[i]) for i in range(n_pairs)]