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

Commit ef0bec5

Browse files
author
Ludwig Schubert
committed
Minor improvements to Activation Atlas code, switches to Shan Carter's transforms, removes some debug statements
1 parent 914a7ae commit ef0bec5

File tree

3 files changed

+82
-45
lines changed

3 files changed

+82
-45
lines changed

lucid/recipes/activation_atlas/layout.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,15 @@ def aligned_umap(activations, umap_options={}, normalize=True, verbose=False):
6060
combined_activations = activations
6161
try:
6262
layout = UMAP(**umap_defaults).fit_transform(combined_activations)
63-
except RecursionError:
63+
except (RecursionError, SystemError) as exception:
6464
log.error("UMAP failed to fit these activations. We're not yet sure why this sometimes occurs.")
65-
raise
65+
raise ValueError("UMAP failed to fit activations: %s", exception)
6666

6767
if normalize:
6868
layout = normalize_layout(layout)
6969

7070
if num_activation_groups > 1:
71-
return np.split(layout, num_activation_groups, axis=0)
71+
layouts = np.split(layout, num_activation_groups, axis=0)
72+
return layouts
7273
else:
7374
return layout

lucid/recipes/activation_atlas/main.py

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
# limitations under the License.
1414
# ==============================================================================
1515

16-
import numpy as np
1716
from enum import Enum, auto
1817

18+
import numpy as np
19+
1920
from lucid.modelzoo.aligned_activations import (
2021
push_activations,
2122
NUMBER_OF_AVAILABLE_SAMPLES,
23+
layer_inverse_covariance,
2224
)
2325
from lucid.recipes.activation_atlas.layout import aligned_umap
2426
from lucid.recipes.activation_atlas.render import render_icons
@@ -37,7 +39,7 @@ def activation_atlas(
3739

3840
activations = layer.activations[:number_activations, ...]
3941
layout, = aligned_umap(activations, verbose=verbose)
40-
directions, coordinates, _ = _bin_laid_out_activations(
42+
directions, coordinates, _ = bin_laid_out_activations(
4143
layout, activations, grid_size
4244
)
4345
icons = []
@@ -46,7 +48,7 @@ def activation_atlas(
4648
directions_batch, model, layer=layer.name, size=icon_size, num_attempts=1
4749
)
4850
icons += icon_batch
49-
canvas = _make_canvas(icons, coordinates, grid_size)
51+
canvas = make_canvas(icons, coordinates, grid_size)
5052

5153
return canvas
5254

@@ -57,36 +59,43 @@ def aligned_activation_atlas(
5759
model2,
5860
layer2,
5961
grid_size=10,
60-
icon_size=96,
62+
icon_size=80,
63+
num_steps=1024,
64+
whiten_layers=False,
6165
number_activations=NUMBER_OF_AVAILABLE_SAMPLES,
6266
verbose=False,
6367
):
68+
"""Renders two aligned Activation Atlases of the given models' layers.
69+
70+
Returns a generator of the two atlasses, and a nested generator for intermediate
71+
atlasses while they're being rendered.
72+
"""
6473
combined_activations = _combine_activations(
6574
layer1, layer2, number_activations=number_activations
6675
)
6776
layouts = aligned_umap(combined_activations, verbose=verbose)
6877

69-
atlasses = []
7078
for model, layer, layout in zip((model1, model2), (layer1, layer2), layouts):
71-
directions, coordinates, densities = _bin_laid_out_activations(
72-
layout, layer.activations[:number_activations, ...], grid_size
79+
directions, coordinates, densities = bin_laid_out_activations(
80+
layout, layer.activations[:number_activations, ...], grid_size, threshold=10
7381
)
74-
icons = []
75-
for directions_batch in batch(directions, batch_size=64):
76-
icon_batch, losses = render_icons(
77-
directions_batch,
78-
model,
79-
alpha=False,
80-
layer=layer.name,
81-
size=icon_size,
82-
num_attempts=1,
83-
n_steps=1024,
84-
)
85-
icons += icon_batch
86-
canvas = _make_canvas(icons, coordinates, grid_size)
87-
atlasses.append(canvas)
88-
89-
return atlasses
82+
83+
def _progressive_canvas_iterator():
84+
icons = []
85+
for directions_batch in batch(directions, batch_size=32, as_list=True):
86+
icon_batch, losses = render_icons(
87+
directions_batch,
88+
model,
89+
alpha=False,
90+
layer=layer.name,
91+
size=icon_size,
92+
n_steps=num_steps,
93+
S=layer_inverse_covariance(layer) if whiten_layers else None,
94+
)
95+
icons += icon_batch
96+
yield make_canvas(icons, coordinates, grid_size)
97+
98+
yield _progressive_canvas_iterator()
9099

91100

92101
# Helpers
@@ -100,6 +109,8 @@ class ActivationTranslation(Enum):
100109
def _combine_activations(
101110
layer1,
102111
layer2,
112+
activations1=None,
113+
activations2=None,
103114
mode=ActivationTranslation.BIDIRECTIONAL,
104115
number_activations=NUMBER_OF_AVAILABLE_SAMPLES,
105116
):
@@ -114,8 +125,8 @@ def _combine_activations(
114125
into the space of layer 1, concatenate them along their channels, and returns a
115126
tuple of the concatenated activations for each layer.
116127
"""
117-
activations1 = layer1.activations[:number_activations, ...]
118-
activations2 = layer2.activations[:number_activations, ...]
128+
activations1 = activations1 or layer1.activations[:number_activations, ...]
129+
activations2 = activations2 or layer2.activations[:number_activations, ...]
119130

120131
if mode is ActivationTranslation.ONE_TO_TWO:
121132

@@ -133,10 +144,10 @@ def _combine_activations(
133144
return activations_model1, activations_model2
134145

135146

136-
def _bin_laid_out_activations(layout, activations, grid_size, threshold=5):
147+
def bin_laid_out_activations(layout, activations, grid_size, threshold=5):
137148
"""Given a layout and activations, overlays a grid on the layout and returns
138149
averaged activations for each grid cell. If a cell contains less than `threshold`
139-
activations it will not be used, so the number of returned directions is variable."""
150+
activations it will be discarded, so the number of returned data is variable."""
140151

141152
assert layout.shape[0] == activations.shape[0]
142153

@@ -151,28 +162,30 @@ def _bin_laid_out_activations(layout, activations, grid_size, threshold=5):
151162

152163
# iterate over all grid cell coordinates to compute their average directions
153164
grid_coordinates = np.indices((grid_size, grid_size)).transpose().reshape(-1, 2)
154-
for xy in grid_coordinates:
155-
mask = np.equal(xy, indices).all(axis=1)
165+
for xy_coordinates in grid_coordinates:
166+
mask = np.equal(xy_coordinates, indices).all(axis=1)
156167
count = np.count_nonzero(mask)
157168
if count > threshold:
158169
counts.append(count)
159-
coordinates.append(xy)
170+
coordinates.append(xy_coordinates)
160171
mean = np.average(activations[mask], axis=0)
161172
means.append(mean)
162173

163174
assert len(means) == len(coordinates) == len(counts)
175+
if len(coordinates) == 0:
176+
raise RuntimeError("Binning activations led to 0 cells containing activations!")
164177

165-
return np.array(means), np.array(coordinates), np.array(counts)
178+
return means, coordinates, counts
166179

167180

168-
def _make_canvas(icon_batch, coordinates, grid_size):
181+
def make_canvas(icon_batch, coordinates, grid_size):
169182
"""Given a list of images and their coordinates, places them on a white canvas."""
170183

171184
grid_shape = (grid_size, grid_size)
172185
icon_shape = icon_batch[0].shape
173186
canvas = np.ones((*grid_shape, *icon_shape))
174187

175-
for (x, y), icon in zip(coordinates, icon_batch):
188+
for icon, (x, y) in zip(icon_batch, coordinates):
176189
canvas[x, y] = icon
177190

178191
return np.hstack(np.hstack(canvas))

lucid/recipes/activation_atlas/render.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import tensorflow as tf
1919
import numpy as np
20+
from itertools import chain
2021

2122

2223
# TODO(schubert@): simplify, cleanup, dedupe objectives
@@ -82,20 +83,26 @@ def render_icons(
8283
n_steps=128,
8384
verbose=False,
8485
S=None,
85-
num_attempts=2,
86+
num_attempts=3,
8687
cossim=True,
8788
alpha=False,
8889
):
8990

91+
model.load_graphdef()
92+
9093
image_attempts = []
9194
loss_attempts = []
9295

96+
depth = 4 if alpha else 3
97+
batch = len(directions)
98+
input_shape = (batch, size, size, depth)
99+
93100
# Render two attempts, and pull the one with the lowest loss score.
94101
for attempt in range(num_attempts):
95102

96103
# Render an image for each activation vector
97104
param_f = lambda: param.image(
98-
size, batch=directions.shape[0], fft=True, decorrelate=True, alpha=alpha
105+
size, batch=len(directions), fft=True, decorrelate=True, alpha=alpha
99106
)
100107

101108
if cossim is True:
@@ -109,15 +116,31 @@ def render_icons(
109116
for n, v in enumerate(directions)
110117
]
111118

119+
obj_list += [
120+
objectives.penalize_boundary_complexity(input_shape, w=5)
121+
]
122+
112123
obj = objectives.Objective.sum(obj_list)
113124

114-
transforms = transform.standard_transforms
125+
# holy mother of transforms
126+
transforms = [
127+
transform.pad(16, mode='constant'),
128+
transform.jitter(4),
129+
transform.jitter(4),
130+
transform.jitter(8),
131+
transform.jitter(8),
132+
transform.jitter(8),
133+
transform.random_scale(0.998**n for n in range(20,40)),
134+
transform.random_rotate(chain(range(-20,20), range(-10,10), range(-5,5), 5*[0])),
135+
transform.jitter(2),
136+
transform.crop_or_pad_to(size, size)
137+
]
115138
if alpha:
116139
transforms.append(transform.collapse_alpha_random())
117140

118141
# This is the tensorflow optimization process
119142

120-
print("attempt: ", attempt)
143+
# print("attempt: ", attempt)
121144
with tf.Graph().as_default(), tf.Session() as sess:
122145
learning_rate = 0.05
123146
losses = []
@@ -129,13 +152,13 @@ def render_icons(
129152
for i in range(n_steps):
130153
loss, _ = sess.run([losses_, vis_op])
131154
losses.append(loss)
132-
if i % 100 == 0:
133-
print(i)
155+
# if i % 100 == 0:
156+
# print(i)
134157

135158
img = t_image.eval()
136159
img_rgb = img[:, :, :, :3]
137160
if alpha:
138-
print("alpha true")
161+
# print("alpha true")
139162
k = 0.8
140163
bg_color = 0.0
141164
img_a = img[:, :, :, 3:]
@@ -144,7 +167,7 @@ def render_icons(
144167
)
145168
image_attempts.append(img_merged)
146169
else:
147-
print("alpha false")
170+
# print("alpha false")
148171
image_attempts.append(img_rgb)
149172

150173
loss_attempts.append(losses[-1])
@@ -153,7 +176,7 @@ def render_icons(
153176
loss_attempts = np.asarray(loss_attempts)
154177
loss_final = []
155178
image_final = []
156-
print("merging best scores from attempts...")
179+
# print("merging best scores from attempts...")
157180
for i, d in enumerate(directions):
158181
# note, this should be max, it is not a traditional loss
159182
mi = np.argmax(loss_attempts[:, i])

0 commit comments

Comments
 (0)