File size: 12,588 Bytes
3eb682b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
import itertools
import numpy as np
import matplotlib.pyplot as plt
import torch
from sklearn.metrics import confusion_matrix
import timesformer.utils.logging as logging
from timesformer.datasets.utils import pack_pathway_output, tensor_normalize
logger = logging.get_logger(__name__)
def get_confusion_matrix(preds, labels, num_classes, normalize="true"):
"""
Calculate confusion matrix on the provided preds and labels.
Args:
preds (tensor or lists of tensors): predictions. Each tensor is in
in the shape of (n_batch, num_classes). Tensor(s) must be on CPU.
labels (tensor or lists of tensors): corresponding labels. Each tensor is
in the shape of either (n_batch,) or (n_batch, num_classes).
num_classes (int): number of classes. Tensor(s) must be on CPU.
normalize (Optional[str]) : {‘true’, ‘pred’, ‘all’}, default="true"
Normalizes confusion matrix over the true (rows), predicted (columns)
conditions or all the population. If None, confusion matrix
will not be normalized.
Returns:
cmtx (ndarray): confusion matrix of size (num_classes x num_classes)
"""
if isinstance(preds, list):
preds = torch.cat(preds, dim=0)
if isinstance(labels, list):
labels = torch.cat(labels, dim=0)
# If labels are one-hot encoded, get their indices.
if labels.ndim == preds.ndim:
labels = torch.argmax(labels, dim=-1)
# Get the predicted class indices for examples.
preds = torch.flatten(torch.argmax(preds, dim=-1))
labels = torch.flatten(labels)
cmtx = confusion_matrix(
labels, preds, labels=list(range(num_classes)), normalize=normalize
)
return cmtx
def plot_confusion_matrix(cmtx, num_classes, class_names=None, figsize=None):
"""
A function to create a colored and labeled confusion matrix matplotlib figure
given true labels and preds.
Args:
cmtx (ndarray): confusion matrix.
num_classes (int): total number of classes.
class_names (Optional[list of strs]): a list of class names.
figsize (Optional[float, float]): the figure size of the confusion matrix.
If None, default to [6.4, 4.8].
Returns:
img (figure): matplotlib figure.
"""
if class_names is None or type(class_names) != list:
class_names = [str(i) for i in range(num_classes)]
figure = plt.figure(figsize=figsize)
plt.imshow(cmtx, interpolation="nearest", cmap=plt.cm.Blues)
plt.title("Confusion matrix")
plt.colorbar()
tick_marks = np.arange(len(class_names))
plt.xticks(tick_marks, class_names, rotation=45)
plt.yticks(tick_marks, class_names)
# Use white text if squares are dark; otherwise black.
threshold = cmtx.max() / 2.0
for i, j in itertools.product(range(cmtx.shape[0]), range(cmtx.shape[1])):
color = "white" if cmtx[i, j] > threshold else "black"
plt.text(
j,
i,
format(cmtx[i, j], ".2f") if cmtx[i, j] != 0 else ".",
horizontalalignment="center",
color=color,
)
plt.tight_layout()
plt.ylabel("True label")
plt.xlabel("Predicted label")
return figure
def plot_topk_histogram(tag, array, k=10, class_names=None, figsize=None):
"""
Plot histogram of top-k value from the given array.
Args:
tag (str): histogram title.
array (tensor): a tensor to draw top k value from.
k (int): number of top values to draw from array.
Defaut to 10.
class_names (list of strings, optional):
a list of names for values in array.
figsize (Optional[float, float]): the figure size of the confusion matrix.
If None, default to [6.4, 4.8].
Returns:
fig (matplotlib figure): a matplotlib figure of the histogram.
"""
val, ind = torch.topk(array, k)
fig = plt.Figure(figsize=figsize, facecolor="w", edgecolor="k")
ax = fig.add_subplot(1, 1, 1)
if class_names is None:
class_names = [str(i) for i in ind]
else:
class_names = [class_names[i] for i in ind]
tick_marks = np.arange(k)
width = 0.75
ax.bar(
tick_marks,
val,
width,
color="orange",
tick_label=class_names,
edgecolor="w",
linewidth=1,
)
ax.set_xlabel("Candidates")
ax.set_xticks(tick_marks)
ax.set_xticklabels(class_names, rotation=-45, ha="center")
ax.xaxis.set_label_position("bottom")
ax.xaxis.tick_bottom()
y_tick = np.linspace(0, 1, num=10)
ax.set_ylabel("Frequency")
ax.set_yticks(y_tick)
y_labels = [format(i, ".1f") for i in y_tick]
ax.set_yticklabels(y_labels, ha="center")
for i, v in enumerate(val.numpy()):
ax.text(
i - 0.1,
v + 0.03,
format(v, ".2f"),
color="orange",
fontweight="bold",
)
ax.set_title(tag)
fig.set_tight_layout(True)
return fig
class GetWeightAndActivation:
"""
A class used to get weights and activations from specified layers from a Pytorch model.
"""
def __init__(self, model, layers):
"""
Args:
model (nn.Module): the model containing layers to obtain weights and activations from.
layers (list of strings): a list of layer names to obtain weights and activations from.
Names are hierarchical, separated by /. For example, If a layer follow a path
"s1" ---> "pathway0_stem" ---> "conv", the layer path is "s1/pathway0_stem/conv".
"""
self.model = model
self.hooks = {}
self.layers_names = layers
# eval mode
self.model.eval()
self._register_hooks()
def _get_layer(self, layer_name):
"""
Return a layer (nn.Module Object) given a hierarchical layer name, separated by /.
Args:
layer_name (str): the name of the layer.
"""
layer_ls = layer_name.split("/")
prev_module = self.model
for layer in layer_ls:
prev_module = prev_module._modules[layer]
return prev_module
def _register_single_hook(self, layer_name):
"""
Register hook to a layer, given layer_name, to obtain activations.
Args:
layer_name (str): name of the layer.
"""
def hook_fn(module, input, output):
self.hooks[layer_name] = output.clone().detach()
layer = get_layer(self.model, layer_name)
layer.register_forward_hook(hook_fn)
def _register_hooks(self):
"""
Register hooks to layers in `self.layers_names`.
"""
for layer_name in self.layers_names:
self._register_single_hook(layer_name)
def get_activations(self, input, bboxes=None):
"""
Obtain all activations from layers that we register hooks for.
Args:
input (tensors, list of tensors): the model input.
bboxes (Optional): Bouding boxes data that might be required
by the model.
Returns:
activation_dict (Python dictionary): a dictionary of the pair
{layer_name: list of activations}, where activations are outputs returned
by the layer.
"""
input_clone = [inp.clone() for inp in input]
if bboxes is not None:
preds = self.model(input_clone, bboxes)
else:
preds = self.model(input_clone)
activation_dict = {}
for layer_name, hook in self.hooks.items():
# list of activations for each instance.
activation_dict[layer_name] = hook
return activation_dict, preds
def get_weights(self):
"""
Returns weights from registered layers.
Returns:
weights (Python dictionary): a dictionary of the pair
{layer_name: weight}, where weight is the weight tensor.
"""
weights = {}
for layer in self.layers_names:
cur_layer = get_layer(self.model, layer)
if hasattr(cur_layer, "weight"):
weights[layer] = cur_layer.weight.clone().detach()
else:
logger.error(
"Layer {} does not have weight attribute.".format(layer)
)
return weights
def get_indexing(string):
"""
Parse numpy-like fancy indexing from a string.
Args:
string (str): string represent the indices to take
a subset of from array. Indices for each dimension
are separated by `,`; indices for different dimensions
are separated by `;`.
e.g.: For a numpy array `arr` of shape (3,3,3), the string "1,2;1,2"
means taking the sub-array `arr[[1,2], [1,2]]
Returns:
final_indexing (tuple): the parsed indexing.
"""
index_ls = string.strip().split(";")
final_indexing = []
for index in index_ls:
index_single_dim = index.split(",")
index_single_dim = [int(i) for i in index_single_dim]
final_indexing.append(index_single_dim)
return tuple(final_indexing)
def process_layer_index_data(layer_ls, layer_name_prefix=""):
"""
Extract layer names and numpy-like fancy indexing from a string.
Args:
layer_ls (list of strs): list of strings containing data about layer names
and their indexing. For each string, layer name and indexing is separated by whitespaces.
e.g.: [layer1 1,2;2, layer2, layer3 150;3,4]
layer_name_prefix (Optional[str]): prefix to be added to each layer name.
Returns:
layer_name (list of strings): a list of layer names.
indexing_dict (Python dict): a dictionary of the pair
{one_layer_name: indexing_for_that_layer}
"""
layer_name, indexing_dict = [], {}
for layer in layer_ls:
ls = layer.split()
name = layer_name_prefix + ls[0]
layer_name.append(name)
if len(ls) == 2:
indexing_dict[name] = get_indexing(ls[1])
else:
indexing_dict[name] = ()
return layer_name, indexing_dict
def process_cv2_inputs(frames, cfg):
"""
Normalize and prepare inputs as a list of tensors. Each tensor
correspond to a unique pathway.
Args:
frames (list of array): list of input images (correspond to one clip) in range [0, 255].
cfg (CfgNode): configs. Details can be found in
slowfast/config/defaults.py
"""
inputs = torch.from_numpy(np.array(frames)).float() / 255
inputs = tensor_normalize(inputs, cfg.DATA.MEAN, cfg.DATA.STD)
# T H W C -> C T H W.
inputs = inputs.permute(3, 0, 1, 2)
# Sample frames for num_frames specified.
index = torch.linspace(0, inputs.shape[1] - 1, cfg.DATA.NUM_FRAMES).long()
inputs = torch.index_select(inputs, 1, index)
inputs = pack_pathway_output(cfg, inputs)
inputs = [inp.unsqueeze(0) for inp in inputs]
return inputs
def get_layer(model, layer_name):
"""
Return the targeted layer (nn.Module Object) given a hierarchical layer name,
separated by /.
Args:
model (model): model to get layers from.
layer_name (str): name of the layer.
Returns:
prev_module (nn.Module): the layer from the model with `layer_name` name.
"""
layer_ls = layer_name.split("/")
prev_module = model
for layer in layer_ls:
prev_module = prev_module._modules[layer]
return prev_module
class TaskInfo:
def __init__(self):
self.frames = None
self.id = -1
self.bboxes = None
self.action_preds = None
self.num_buffer_frames = 0
self.img_height = -1
self.img_width = -1
self.crop_size = -1
self.clip_vis_size = -1
def add_frames(self, idx, frames):
"""
Add the clip and corresponding id.
Args:
idx (int): the current index of the clip.
frames (list[ndarray]): list of images in "BGR" format.
"""
self.frames = frames
self.id = idx
def add_bboxes(self, bboxes):
"""
Add correspondding bounding boxes.
"""
self.bboxes = bboxes
def add_action_preds(self, preds):
"""
Add the corresponding action predictions.
"""
self.action_preds = preds
|