Skip to content
This repository was archived by the owner on Jan 5, 2024. It is now read-only.

Commit dfee128

Browse files
authored
make flatten coordinates more generic; add forward_kwargs option; image tensors (#64)
1 parent 7e2f080 commit dfee128

2 files changed

Lines changed: 30 additions & 23 deletions

File tree

model_tools/activations/core.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ def _expand_paths(self, activations, original_paths):
9797
index = [argsort_indices[i] for i in sorted_index]
9898
return activations[{'stimulus_path': index}]
9999

100-
101100
def register_batch_activations_hook(self, hook):
102101
r"""
103102
The hook will be called every time a batch of activations is retrieved.
@@ -195,26 +194,32 @@ def _package(self, layer_activations, stimuli_paths):
195194
def _package_layer(self, layer_activations, layer, stimuli_paths):
196195
assert layer_activations.shape[0] == len(stimuli_paths)
197196
activations, flatten_indices = flatten(layer_activations, return_index=True) # collapse for single neuroid dim
198-
assert flatten_indices.shape[1] in [1, 2, 3]
199-
# see comment in _package for an explanation why we cannot simply have 'channel' for the FC layer
200-
if flatten_indices.shape[1] == 1: # FC
197+
flatten_coord_names = None
198+
if flatten_indices.shape[1] == 1: # fully connected, e.g. classifier
199+
# see comment in _package for an explanation why we cannot simply have 'channel' for the FC layer
201200
flatten_coord_names = ['channel', 'channel_x', 'channel_y']
202-
elif flatten_indices.shape[1] == 2: # Transformer
201+
elif flatten_indices.shape[1] == 2: # Transformer, e.g. ViT
203202
flatten_coord_names = ['channel', 'embedding']
204-
elif flatten_indices.shape[1] == 3: # 2DConv
203+
elif flatten_indices.shape[1] == 3: # 2DConv, e.g. resnet
205204
flatten_coord_names = ['channel', 'channel_x', 'channel_y']
206-
flatten_coords = {flatten_coord_names[i]: [sample_index[i] if i < flatten_indices.shape[1] else np.nan for sample_index in flatten_indices]
207-
for i in range(len(flatten_coord_names))}
208-
layer_assembly = NeuroidAssembly(
209-
activations,
210-
coords={**{'stimulus_path': stimuli_paths,
211-
'neuroid_num': ('neuroid', list(range(activations.shape[1]))),
212-
'model': ('neuroid', [self.identifier] * activations.shape[1]),
213-
'layer': ('neuroid', [layer] * activations.shape[1]),
214-
},
215-
**{coord: ('neuroid', values) for coord, values in flatten_coords.items()}},
216-
dims=['stimulus_path', 'neuroid']
217-
)
205+
elif flatten_indices.shape[1] == 4: # temporal sliding window, e.g. omnivron
206+
flatten_coord_names = ['channel_temporal', 'channel_x', 'channel_y', 'channel']
207+
else:
208+
# we still package the activations, but are unable to provide channel information
209+
self._logger.debug(f"Unknown layer activations shape {layer_activations.shape}, not inferring channels")
210+
211+
# build assembly
212+
coords = {'stimulus_path': stimuli_paths,
213+
'neuroid_num': ('neuroid', list(range(activations.shape[1]))),
214+
'model': ('neuroid', [self.identifier] * activations.shape[1]),
215+
'layer': ('neuroid', [layer] * activations.shape[1]),
216+
}
217+
if flatten_coord_names:
218+
flatten_coords = {flatten_coord_names[i]: [sample_index[i] if i < flatten_indices.shape[1] else np.nan
219+
for sample_index in flatten_indices]
220+
for i in range(len(flatten_coord_names))}
221+
coords = {**coords, **{coord: ('neuroid', values) for coord, values in flatten_coords.items()}}
222+
layer_assembly = NeuroidAssembly(activations, coords=coords, dims=['stimulus_path', 'neuroid'])
218223
neuroid_id = [".".join([f"{value}" for value in values]) for values in zip(*[
219224
layer_assembly[coord].values for coord in ['model', 'layer', 'neuroid_num']])]
220225
layer_assembly['neuroid_id'] = 'neuroid', neuroid_id

model_tools/activations/pytorch.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import logging
21
from collections import OrderedDict
32

3+
import logging
44
import numpy as np
55
from PIL import Image
66

@@ -11,7 +11,7 @@
1111

1212

1313
class PytorchWrapper:
14-
def __init__(self, model, preprocessing, identifier=None, *args, **kwargs):
14+
def __init__(self, model, preprocessing, identifier=None, forward_kwargs=None, *args, **kwargs):
1515
import torch
1616
logger = logging.getLogger(fullname(self))
1717
self._device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -22,6 +22,7 @@ def __init__(self, model, preprocessing, identifier=None, *args, **kwargs):
2222
self._extractor = self._build_extractor(
2323
identifier=identifier, preprocessing=preprocessing, get_activations=self.get_activations, *args, **kwargs)
2424
self._extractor.insert_attrs(self)
25+
self._forward_kwargs = forward_kwargs or {}
2526

2627
def _build_extractor(self, identifier, preprocessing, get_activations, *args, **kwargs):
2728
return ActivationsExtractorHelper(
@@ -42,7 +43,7 @@ def __call__(self, *args, **kwargs): # cannot assign __call__ as attribute due
4243
def get_activations(self, images, layer_names):
4344
import torch
4445
from torch.autograd import Variable
45-
images = [torch.from_numpy(image) for image in images]
46+
images = [torch.from_numpy(image) if not isinstance(image, torch.Tensor) else image for image in images]
4647
images = Variable(torch.stack(images))
4748
images = images.to(self._device)
4849
self._model.eval()
@@ -55,7 +56,8 @@ def get_activations(self, images, layer_names):
5556
hook = self.register_hook(layer, layer_name, target_dict=layer_results)
5657
hooks.append(hook)
5758

58-
self._model(images)
59+
with torch.no_grad():
60+
self._model(images, **self._forward_kwargs)
5961
for hook in hooks:
6062
hook.remove()
6163
return layer_results
@@ -115,7 +117,7 @@ def load_images(image_filepaths):
115117

116118
def load_image(image_filepath):
117119
with Image.open(image_filepath) as pil_image:
118-
if 'L' not in pil_image.mode.upper() and 'A' not in pil_image.mode.upper()\
120+
if 'L' not in pil_image.mode.upper() and 'A' not in pil_image.mode.upper() \
119121
and 'P' not in pil_image.mode.upper(): # not binary and not alpha and not palletized
120122
# work around to https://github.com/python-pillow/Pillow/issues/1144,
121123
# see https://stackoverflow.com/a/30376272/2225200

0 commit comments

Comments
 (0)