Spaces:
Sleeping
Sleeping
initial commit
Browse files- .gitignore +2 -0
- README.md +56 -13
- abstractions/Box.py +104 -0
- abstractions/__init__.py +1 -0
- base_cam.py +223 -0
- detectron2monitor.py +231 -0
- enlarge.py +320 -0
- interface.py +95 -0
- interface_tabbed.py +155 -0
- models/configs/Base-RCNN-FPN.yaml +50 -0
- models/configs/vanilla_regnet.yaml +33 -0
- models/configs/vanilla_resnet.yaml +28 -0
- models/metadata.py +35 -0
- models/regnet.py +84 -0
- models/regnet_model.py +446 -0
- models/weights/model_final_resnet_kitti.pth +3 -0
- monitors/kitti/resnet/kmeans/1.pkl +3 -0
- monitors/kitti/resnet/kmeans/4.pkl +3 -0
- monitors/kitti/resnet/kmeans/5.pkl +3 -0
- monitors/kitti/resnet/kmeans/6.pkl +3 -0
- requirements.txt +12 -0
- runtime_monitors/Monitor.py +56 -0
- runtime_monitors/__init__.py +2 -0
- train_feats/kitti/resnet/kitti-train_feats_tp_dict.pickle +3 -0
- util/Clustering.py +138 -0
- util/Monitor_construction.py +144 -0
- util/__init__.py +3 -0
- utils_clustering.py +61 -0
- val_feats/kitti/resnet/ID-bdd-OOD-coco_feats_fp_dict.pickle +3 -0
- val_feats/kitti/resnet/OOD-open_feats_fp_dict.pickle +3 -0
- val_feats/kitti/resnet/kitti-val_feats_fp_dict.pickle +3 -0
- val_feats/kitti/resnet/kitti-val_feats_tp_dict.pickle +3 -0
- val_feats/kitti/resnet/voc-ood_feats_fp_dict.pickle +3 -0
.gitignore
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
+
*.pyc
|
README.md
CHANGED
@@ -1,13 +1,56 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# monitoring-interface
|
2 |
+
|
3 |
+
## Requirements
|
4 |
+
```
|
5 |
+
pip install -r requiremnets.txt
|
6 |
+
```
|
7 |
+
To install Detectron2, please follow [here](https://detectron2.readthedocs.io/tutorials/install.html).
|
8 |
+
## Dataset Preparation
|
9 |
+
We use [Fiftyone](https://docs.voxel51.com) library to load and visualize datasets.
|
10 |
+
|
11 |
+
BDD100k, COCO, KITTI and OpenImage can be loaded directly through [Fiftyone Datasets Zoo](https://docs.voxel51.com/user_guide/dataset_zoo/datasets.html?highlight=zoo).
|
12 |
+
|
13 |
+
For other datasets, such as NuScene can be loaded manually via the following simple pattern:
|
14 |
+
```python
|
15 |
+
import fiftyone as fo
|
16 |
+
|
17 |
+
# A name for the dataset
|
18 |
+
name = "my-dataset"
|
19 |
+
|
20 |
+
# The directory containing the dataset to import
|
21 |
+
dataset_dir = "/path/to/dataset"
|
22 |
+
|
23 |
+
# The type of the dataset being imported
|
24 |
+
dataset_type = fo.types.COCODetectionDataset # for example
|
25 |
+
|
26 |
+
dataset = fo.Dataset.from_dir(
|
27 |
+
dataset_dir=dataset_dir,
|
28 |
+
dataset_type=dataset_type,
|
29 |
+
name=name,
|
30 |
+
)
|
31 |
+
```
|
32 |
+
The custom dataset folder should have the following structure:
|
33 |
+
```
|
34 |
+
└── /path/to/dataset
|
35 |
+
|
|
36 |
+
├── Data
|
37 |
+
└── labels.json
|
38 |
+
```
|
39 |
+
Notice that the annotation file `labels.json` should be prepared in COCO format.
|
40 |
+
|
41 |
+
## Interface demo
|
42 |
+
|
43 |
+
Three interfaces are provided:
|
44 |
+
|
45 |
+
- `interface.py`: all-in-1 interface
|
46 |
+
- `interface_tabbed.py`: tabbed interface
|
47 |
+
- `enlarge.py`: interface for monitor interval enlargement
|
48 |
+
|
49 |
+
To run any of these interfaces, just execute `python <script name.py>`.
|
50 |
+
|
51 |
+
Please note that feature extraction for both training data and evaluation data can be a time-consuming process. However, if you are only interested in testing monitor construction, monitor evaluation, or monitoring demo, you can use the following settings to load a pretrained model along with the corresponding extracted features and monitors.
|
52 |
+
|
53 |
+
| ID | Backbone | Clustering method for Monitors |
|
54 |
+
| ----- | -------- | -------------------------------- |
|
55 |
+
| KITTI | ResNet | KMeans(nb_clusters=[1, 4, 5, 6]) |
|
56 |
+
|
abstractions/Box.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from copy import deepcopy
|
2 |
+
|
3 |
+
class Box:
|
4 |
+
def __init__(self):
|
5 |
+
self.dimensions = None
|
6 |
+
self.ivals = []
|
7 |
+
self.element_indexes = [] # record this box is built for what samples
|
8 |
+
self.low_bound_indexes = dict() # record which samples visit the low bound for each dimension
|
9 |
+
self.high_bound_indexes = dict() # record which samples visit the low bound for each dimension
|
10 |
+
|
11 |
+
def build(self, dimensions, points):
|
12 |
+
# a point is a tuple (index, n-dim numpy)
|
13 |
+
# index = point[0]
|
14 |
+
# value = point[1]
|
15 |
+
piter = iter(points)
|
16 |
+
self.dimensions = dimensions
|
17 |
+
self.ivals = []
|
18 |
+
self.element_indexes = []
|
19 |
+
self.low_bound_indexes = dict()
|
20 |
+
self.high_bound_indexes = dict()
|
21 |
+
|
22 |
+
try:
|
23 |
+
point = next(piter)
|
24 |
+
except StopIteration:
|
25 |
+
return
|
26 |
+
else:
|
27 |
+
self.element_indexes.append(point[0]) # update index list
|
28 |
+
i = 0
|
29 |
+
for coord in point[1]:
|
30 |
+
if(i >= self.dimensions):
|
31 |
+
break
|
32 |
+
self.ivals.append([coord, coord])
|
33 |
+
self.low_bound_indexes["n"+str(i+1)] = [point[0]] # update low bound visiting index list
|
34 |
+
self.high_bound_indexes["n"+str(i+1)] = [point[0]] # update upper bound visiting index list
|
35 |
+
i += 1
|
36 |
+
if(len(self.ivals) != self.dimensions):
|
37 |
+
raise "IllegalArgument"
|
38 |
+
|
39 |
+
while True:
|
40 |
+
try:
|
41 |
+
point = next(piter)
|
42 |
+
except StopIteration:
|
43 |
+
break
|
44 |
+
else:
|
45 |
+
self.element_indexes.append(point[0]) # update index list
|
46 |
+
i = 0
|
47 |
+
for coord in point[1]:
|
48 |
+
if(i >= self.dimensions):
|
49 |
+
break
|
50 |
+
ival = self.ivals[i]
|
51 |
+
if(coord < ival[0]):
|
52 |
+
ival[0] = coord
|
53 |
+
self.low_bound_indexes["n"+str(i+1)] = [point[0]] # update the bound and its index
|
54 |
+
elif(coord == ival[0]):
|
55 |
+
low_index_list = self.low_bound_indexes["n"+str(i+1)]
|
56 |
+
low_index_list.append(point[0])
|
57 |
+
|
58 |
+
if(coord > ival[1]):
|
59 |
+
ival[1] = coord
|
60 |
+
self.high_bound_indexes["n"+str(i+1)] = [point[0]] # update the bound and its index
|
61 |
+
elif(coord == ival[1]):
|
62 |
+
high_index_list = self.high_bound_indexes["n"+str(i+1)]
|
63 |
+
high_index_list.append(point[0])
|
64 |
+
i += 1
|
65 |
+
|
66 |
+
def query(self, point):
|
67 |
+
i = 0
|
68 |
+
for coord in point:
|
69 |
+
if(i >= self.dimensions):
|
70 |
+
break
|
71 |
+
ival = self.ivals[i]
|
72 |
+
if(coord < ival[0] or coord > ival[1]):
|
73 |
+
return False
|
74 |
+
i += 1
|
75 |
+
return True
|
76 |
+
|
77 |
+
def __str__(self):
|
78 |
+
return self.ivals.__str__()
|
79 |
+
|
80 |
+
def query_delta(self, point, delta):
|
81 |
+
i = 0
|
82 |
+
for coord in point:
|
83 |
+
if(i >= self.dimensions):
|
84 |
+
break
|
85 |
+
ival = self.ivals[i]
|
86 |
+
if(coord < ival[0]*(1+delta) or coord > ival[1]*(1+delta)):
|
87 |
+
return False
|
88 |
+
i += 1
|
89 |
+
return True
|
90 |
+
|
91 |
+
|
92 |
+
def boxes_query(point, boxes):
|
93 |
+
for box in boxes:
|
94 |
+
if len(box.ivals):
|
95 |
+
if box.query(point):
|
96 |
+
return True
|
97 |
+
return False
|
98 |
+
|
99 |
+
def boxes_query_delta(point, boxes, delta):
|
100 |
+
for box in boxes:
|
101 |
+
if len(box.ivals):
|
102 |
+
if box.query_delta(point, delta):
|
103 |
+
return True
|
104 |
+
return False
|
abstractions/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
from .Box import *
|
base_cam.py
ADDED
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
import ttach as tta
|
4 |
+
from typing import Callable, List, Tuple
|
5 |
+
from pytorch_grad_cam.activations_and_gradients import ActivationsAndGradients
|
6 |
+
from pytorch_grad_cam.utils.svd_on_activations import get_2d_projection
|
7 |
+
from pytorch_grad_cam.utils.image import scale_cam_image
|
8 |
+
from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget
|
9 |
+
from pytorch_grad_cam.utils.svd_on_activations import get_2d_projection
|
10 |
+
|
11 |
+
# https://arxiv.org/abs/2008.00299
|
12 |
+
|
13 |
+
class BaseCAM:
|
14 |
+
def __init__(self,
|
15 |
+
model: torch.nn.Module,
|
16 |
+
target_layers: List[torch.nn.Module],
|
17 |
+
use_cuda: bool = False,
|
18 |
+
reshape_transform: Callable = None,
|
19 |
+
compute_input_gradient: bool = False,
|
20 |
+
uses_gradients: bool = True) -> None:
|
21 |
+
self.model = model.eval()
|
22 |
+
self.target_layers = target_layers
|
23 |
+
self.cuda = use_cuda
|
24 |
+
if self.cuda:
|
25 |
+
self.model = model.cuda()
|
26 |
+
self.reshape_transform = reshape_transform
|
27 |
+
self.compute_input_gradient = compute_input_gradient
|
28 |
+
self.uses_gradients = uses_gradients
|
29 |
+
self.activations_and_grads = ActivationsAndGradients(
|
30 |
+
self.model, target_layers, reshape_transform)
|
31 |
+
|
32 |
+
""" Get a vector of weights for every channel in the target layer.
|
33 |
+
Methods that return weights channels,
|
34 |
+
will typically need to only implement this function. """
|
35 |
+
|
36 |
+
def get_cam_weights(self,
|
37 |
+
input_tensor: torch.Tensor,
|
38 |
+
target_layers: List[torch.nn.Module],
|
39 |
+
targets: List[torch.nn.Module],
|
40 |
+
activations: torch.Tensor,
|
41 |
+
grads: torch.Tensor) -> np.ndarray:
|
42 |
+
raise Exception("Not Implemented")
|
43 |
+
|
44 |
+
def get_cam_image(self,
|
45 |
+
input_tensor: torch.Tensor,
|
46 |
+
target_layer: torch.nn.Module,
|
47 |
+
targets: List[torch.nn.Module],
|
48 |
+
activations: torch.Tensor,
|
49 |
+
grads: torch.Tensor,
|
50 |
+
eigen_smooth: bool = False) -> np.ndarray:
|
51 |
+
|
52 |
+
weights = self.get_cam_weights(input_tensor,
|
53 |
+
target_layer,
|
54 |
+
targets,
|
55 |
+
activations,
|
56 |
+
grads)
|
57 |
+
weighted_activations = weights[:, :, None, None] * activations
|
58 |
+
if eigen_smooth:
|
59 |
+
cam = get_2d_projection(weighted_activations)
|
60 |
+
else:
|
61 |
+
cam = weighted_activations.sum(axis=1)
|
62 |
+
return cam
|
63 |
+
|
64 |
+
def forward(self,
|
65 |
+
input_tensor: torch.Tensor,
|
66 |
+
targets: List[torch.nn.Module],
|
67 |
+
eigen_smooth: bool = False) -> np.ndarray:
|
68 |
+
if self.cuda:
|
69 |
+
input_tensor = input_tensor.cuda()
|
70 |
+
|
71 |
+
if self.compute_input_gradient:
|
72 |
+
input_tensor = torch.autograd.Variable(input_tensor,
|
73 |
+
requires_grad=True)
|
74 |
+
|
75 |
+
outputs = self.activations_and_grads(input_tensor)
|
76 |
+
if targets is None:
|
77 |
+
target_categories = np.argmax(outputs.cpu().data.numpy(), axis=-1)
|
78 |
+
targets = [ClassifierOutputTarget(
|
79 |
+
category) for category in target_categories]
|
80 |
+
|
81 |
+
if self.uses_gradients:
|
82 |
+
self.model.zero_grad()
|
83 |
+
loss = sum([target(output)
|
84 |
+
for target, output in zip(targets, outputs)])
|
85 |
+
loss.backward(retain_graph=True)
|
86 |
+
|
87 |
+
# In most of the saliency attribution papers, the saliency is
|
88 |
+
# computed with a single target layer.
|
89 |
+
# Commonly it is the last convolutional layer.
|
90 |
+
# Here we support passing a list with multiple target layers.
|
91 |
+
# It will compute the saliency image for every image,
|
92 |
+
# and then aggregate them (with a default mean aggregation).
|
93 |
+
# This gives you more flexibility in case you just want to
|
94 |
+
# use all conv layers for example, all Batchnorm layers,
|
95 |
+
# or something else.
|
96 |
+
cam_per_layer = self.compute_cam_per_layer(input_tensor,
|
97 |
+
targets,
|
98 |
+
eigen_smooth)
|
99 |
+
return self.aggregate_multi_layers(cam_per_layer)
|
100 |
+
|
101 |
+
def get_target_width_height(self,
|
102 |
+
input_tensor: torch.Tensor) -> Tuple[int, int]:
|
103 |
+
width, height = input_tensor.size(-1), input_tensor.size(-2)
|
104 |
+
return width, height
|
105 |
+
|
106 |
+
def compute_cam_per_layer(
|
107 |
+
self,
|
108 |
+
input_tensor: torch.Tensor,
|
109 |
+
targets: List[torch.nn.Module],
|
110 |
+
eigen_smooth: bool) -> np.ndarray:
|
111 |
+
activations_list = [a.cpu().data.numpy()
|
112 |
+
for a in self.activations_and_grads.activations]
|
113 |
+
grads_list = [g.cpu().data.numpy()
|
114 |
+
for g in self.activations_and_grads.gradients]
|
115 |
+
target_size = self.get_target_width_height(input_tensor[0]["image"])
|
116 |
+
|
117 |
+
|
118 |
+
cam_per_target_layer = []
|
119 |
+
# Loop over the saliency image from every layer
|
120 |
+
for i in range(len(self.target_layers)):
|
121 |
+
target_layer = self.target_layers[i]
|
122 |
+
layer_activations = None
|
123 |
+
layer_grads = None
|
124 |
+
if i < len(activations_list):
|
125 |
+
layer_activations = activations_list[i]
|
126 |
+
if i < len(grads_list):
|
127 |
+
layer_grads = grads_list[i]
|
128 |
+
|
129 |
+
cam = self.get_cam_image(input_tensor,
|
130 |
+
target_layer,
|
131 |
+
targets,
|
132 |
+
layer_activations,
|
133 |
+
layer_grads,
|
134 |
+
eigen_smooth)
|
135 |
+
cam = np.maximum(cam, 0)
|
136 |
+
scaled = scale_cam_image(cam, target_size)
|
137 |
+
cam_per_target_layer.append(scaled[:, None, :])
|
138 |
+
|
139 |
+
return cam_per_target_layer
|
140 |
+
|
141 |
+
def aggregate_multi_layers(
|
142 |
+
self,
|
143 |
+
cam_per_target_layer: np.ndarray) -> np.ndarray:
|
144 |
+
cam_per_target_layer = np.concatenate(cam_per_target_layer, axis=1)
|
145 |
+
cam_per_target_layer = np.maximum(cam_per_target_layer, 0)
|
146 |
+
result = np.mean(cam_per_target_layer, axis=1)
|
147 |
+
return scale_cam_image(result)
|
148 |
+
|
149 |
+
def forward_augmentation_smoothing(self,
|
150 |
+
input_tensor: torch.Tensor,
|
151 |
+
targets: List[torch.nn.Module],
|
152 |
+
eigen_smooth: bool = False) -> np.ndarray:
|
153 |
+
transforms = tta.Compose(
|
154 |
+
[
|
155 |
+
tta.HorizontalFlip(),
|
156 |
+
tta.Multiply(factors=[0.9, 1, 1.1]),
|
157 |
+
]
|
158 |
+
)
|
159 |
+
cams = []
|
160 |
+
for transform in transforms:
|
161 |
+
augmented_tensor = transform.augment_image(input_tensor)
|
162 |
+
cam = self.forward(augmented_tensor,
|
163 |
+
targets,
|
164 |
+
eigen_smooth)
|
165 |
+
|
166 |
+
# The ttach library expects a tensor of size BxCxHxW
|
167 |
+
cam = cam[:, None, :, :]
|
168 |
+
cam = torch.from_numpy(cam)
|
169 |
+
cam = transform.deaugment_mask(cam)
|
170 |
+
|
171 |
+
# Back to numpy float32, HxW
|
172 |
+
cam = cam.numpy()
|
173 |
+
cam = cam[:, 0, :, :]
|
174 |
+
cams.append(cam)
|
175 |
+
|
176 |
+
cam = np.mean(np.float32(cams), axis=0)
|
177 |
+
return cam
|
178 |
+
|
179 |
+
def __call__(self,
|
180 |
+
input_tensor: torch.Tensor,
|
181 |
+
targets: List[torch.nn.Module] = None,
|
182 |
+
aug_smooth: bool = False,
|
183 |
+
eigen_smooth: bool = False) -> np.ndarray:
|
184 |
+
|
185 |
+
# Smooth the CAM result with test time augmentation
|
186 |
+
if aug_smooth is True:
|
187 |
+
return self.forward_augmentation_smoothing(
|
188 |
+
input_tensor, targets, eigen_smooth)
|
189 |
+
|
190 |
+
return self.forward(input_tensor,
|
191 |
+
targets, eigen_smooth)
|
192 |
+
|
193 |
+
def __del__(self):
|
194 |
+
self.activations_and_grads.release()
|
195 |
+
|
196 |
+
def __enter__(self):
|
197 |
+
return self
|
198 |
+
|
199 |
+
def __exit__(self, exc_type, exc_value, exc_tb):
|
200 |
+
self.activations_and_grads.release()
|
201 |
+
if isinstance(exc_value, IndexError):
|
202 |
+
# Handle IndexError here...
|
203 |
+
print(
|
204 |
+
f"An exception occurred in CAM with block: {exc_type}. Message: {exc_value}")
|
205 |
+
return True
|
206 |
+
|
207 |
+
class EigenCAM(BaseCAM):
|
208 |
+
def __init__(self, model, target_layers, use_cuda=False,
|
209 |
+
reshape_transform=None):
|
210 |
+
super(EigenCAM, self).__init__(model,
|
211 |
+
target_layers,
|
212 |
+
use_cuda,
|
213 |
+
reshape_transform,
|
214 |
+
uses_gradients=False)
|
215 |
+
|
216 |
+
def get_cam_image(self,
|
217 |
+
input_tensor,
|
218 |
+
target_layer,
|
219 |
+
target_category,
|
220 |
+
activations,
|
221 |
+
grads,
|
222 |
+
eigen_smooth):
|
223 |
+
return get_2d_projection(activations)
|
detectron2monitor.py
ADDED
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from detectron2.utils.logger import setup_logger
|
3 |
+
setup_logger()
|
4 |
+
|
5 |
+
from detectron2.config import get_cfg
|
6 |
+
import detectron2.data.transforms as T
|
7 |
+
from detectron2.checkpoint import DetectionCheckpointer
|
8 |
+
from detectron2.modeling import build_model
|
9 |
+
from detectron2.data.detection_utils import read_image
|
10 |
+
from detectron2.utils.visualizer import Visualizer
|
11 |
+
from detectron2.data import MetadataCatalog
|
12 |
+
|
13 |
+
|
14 |
+
import numpy as np
|
15 |
+
import cv2
|
16 |
+
import os
|
17 |
+
import time
|
18 |
+
import pickle
|
19 |
+
import gradio as gr
|
20 |
+
import tqdm
|
21 |
+
import matplotlib.pyplot as plt
|
22 |
+
import io
|
23 |
+
from PIL import Image
|
24 |
+
torch.manual_seed(0)
|
25 |
+
np.random.seed(0)
|
26 |
+
|
27 |
+
torch.backends.cudnn.deterministic = True
|
28 |
+
torch.backends.cudnn.benchmark = False
|
29 |
+
|
30 |
+
from models.regnet import build_regnet_fpn_backbone
|
31 |
+
import models.metadata as metadata
|
32 |
+
|
33 |
+
from utils_clustering import *
|
34 |
+
|
35 |
+
from base_cam import EigenCAM
|
36 |
+
from pytorch_grad_cam.utils.model_targets import FasterRCNNBoxScoreTarget
|
37 |
+
|
38 |
+
fullName2ab_dict = {'PASCAL-VOC':"voc", 'BDD100K':"bdd", 'KITTI':"kitti", 'Speed signs':"speed", 'NuScenes':"nu"}
|
39 |
+
ab2FullName_dict = {'voc':"PASCAL-VOC", 'bdd':"BDD100K", 'kitti':"KITTI", 'speed':"Speed signs", 'nu':"NuScenes"}
|
40 |
+
class Detectron2Monitor():
|
41 |
+
def __init__(self, id, backbone, confidence_threshold=0.05):
|
42 |
+
self.id, self.label_list = self._get_label_list(id)
|
43 |
+
self.backbone = backbone
|
44 |
+
self.confidence_threshold = confidence_threshold
|
45 |
+
self.cfg, self.device, self.model = self._get_model()
|
46 |
+
self.label_dict = {i:label for i, label in enumerate(self.label_list)}
|
47 |
+
self.eval_list = ["ID-voc-OOD-coco", "OOD-open", "voc-val"] if self.id == "voc" else ["ID-bdd-OOD-coco", "OOD-open", "voc-ood", f"{self.id}-val"]
|
48 |
+
MetadataCatalog.get("custom_dataset").set(thing_classes=self.label_list)
|
49 |
+
|
50 |
+
def _get_label_list(self, id):
|
51 |
+
id = fullName2ab_dict[id]
|
52 |
+
if id == 'voc':
|
53 |
+
label_list = metadata.VOC_THING_CLASSES
|
54 |
+
elif id == 'bdd':
|
55 |
+
label_list = metadata.BDD_THING_CLASSES
|
56 |
+
elif id == 'kitti':
|
57 |
+
label_list = metadata.KITTI_THING_CLASSES
|
58 |
+
elif id == 'speed' or id == 'prescan':
|
59 |
+
label_list = metadata.SPEED_THING_CLASSES
|
60 |
+
else:
|
61 |
+
label_list = metadata.NU_THING_CLASSES
|
62 |
+
return id, label_list
|
63 |
+
|
64 |
+
def _get_model(self):
|
65 |
+
cfg = get_cfg()
|
66 |
+
cfg.merge_from_file(f"models/configs/vanilla_{self.backbone}.yaml")
|
67 |
+
cfg.MODEL.WEIGHTS = f"models/weights/model_final_{self.backbone}_{self.id}.pth"
|
68 |
+
cfg.MODEL.DEVICE='cpu'
|
69 |
+
cfg.MODEL.ROI_HEADS.NUM_CLASSES = len(self.label_list)
|
70 |
+
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = self.confidence_threshold
|
71 |
+
model = build_model(cfg)
|
72 |
+
model.eval()
|
73 |
+
checkpointer = DetectionCheckpointer(model)
|
74 |
+
checkpointer.load(cfg.MODEL.WEIGHTS)
|
75 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
76 |
+
model = model.to(device)
|
77 |
+
return cfg, device, model
|
78 |
+
|
79 |
+
def _inference(self, model, inputs):
|
80 |
+
with torch.no_grad():
|
81 |
+
images = model.preprocess_image(inputs)
|
82 |
+
features = model.backbone(images.tensor)
|
83 |
+
proposals, _ = model.proposal_generator(images, features, None) # RPN
|
84 |
+
|
85 |
+
features_ = [features[f] for f in model.roi_heads.box_in_features]
|
86 |
+
box_features = model.roi_heads.box_pooler(features_, [x.proposal_boxes for x in proposals])
|
87 |
+
box_features = model.roi_heads.box_head(box_features) # features of all 1k candidates
|
88 |
+
predictions = model.roi_heads.box_predictor(box_features)
|
89 |
+
pred_instances, pred_inds = model.roi_heads.box_predictor.inference(predictions, proposals)
|
90 |
+
pred_instances = model.roi_heads.forward_with_given_boxes(features, pred_instances)
|
91 |
+
|
92 |
+
# output boxes, masks, scores, etc
|
93 |
+
pred_instances = model._postprocess(pred_instances, inputs, images.image_sizes) # scale box to orig size
|
94 |
+
# features of the proposed boxes
|
95 |
+
feats = box_features[pred_inds].cpu().numpy()
|
96 |
+
return pred_instances, feats
|
97 |
+
|
98 |
+
def _load_monitors(self, clustering_algo, nb_clusters, eps=5, min_samples=10):
|
99 |
+
if clustering_algo == "dbscan":
|
100 |
+
with open(f"monitors/{self.id}/{self.backbone}/{clustering_algo}/eps{eps}_min_samples{min_samples}.pkl", 'rb') as f:
|
101 |
+
monitors_dict = pickle.load(f)
|
102 |
+
else:
|
103 |
+
with open(f"monitors/{self.id}/{self.backbone}/{clustering_algo}/{nb_clusters}.pkl", 'rb') as f:
|
104 |
+
monitors_dict = pickle.load(f)
|
105 |
+
return monitors_dict
|
106 |
+
|
107 |
+
def _evaluate(self, clustering_algo, nb_clusters, eps, min_samples):
|
108 |
+
dataset_name = f"{self.id}-val"
|
109 |
+
with open(f'val_feats/{self.id}/{self.backbone}/{dataset_name}_feats_tp_dict.pickle', 'rb') as f:
|
110 |
+
feats_tp_dict = pickle.load(f)
|
111 |
+
with open(f'val_feats/{self.id}/{self.backbone}/{dataset_name}_feats_fp_dict.pickle', 'rb') as f:
|
112 |
+
feats_fp_dict = pickle.load(f)
|
113 |
+
monitors_dict = self._load_monitors(clustering_algo, nb_clusters, eps, min_samples)
|
114 |
+
# make verdicts on ID data
|
115 |
+
data_tp = []
|
116 |
+
data_fp = []
|
117 |
+
accept_sum = {"tp": 0, "fp": 0}
|
118 |
+
reject_sum = {"tp": 0, "fp": 0}
|
119 |
+
for label in tqdm.tqdm(self.label_list, desc="Evaluation on ID data"):
|
120 |
+
if label in monitors_dict:
|
121 |
+
verdict = monitors_dict[label].make_verdicts(feats_tp_dict[label])
|
122 |
+
data_tp.append([label, len(verdict), np.sum(verdict)/len(verdict)])
|
123 |
+
accept_sum["tp"] += np.sum(verdict)
|
124 |
+
reject_sum["tp"] += len(verdict) - np.sum(verdict)
|
125 |
+
verdict = monitors_dict[label].make_verdicts(feats_fp_dict[label])
|
126 |
+
data_fp.append([label, len(verdict), (len(verdict)-np.sum(verdict))/len(verdict)])
|
127 |
+
accept_sum["fp"] += np.sum(verdict)
|
128 |
+
reject_sum["fp"] += len(verdict) - np.sum(verdict)
|
129 |
+
TPR = round((accept_sum['tp'] / (reject_sum['tp'] + accept_sum['tp'])*100), 2)
|
130 |
+
FPR = round((accept_sum['fp'] / (reject_sum['fp'] + accept_sum['fp'])*100), 2)
|
131 |
+
id_name = ab2FullName_dict[self.id]
|
132 |
+
df_id = pd.DataFrame([[id_name, f"{TPR}%", f"{FPR}%"]], columns=["Dataset", "TPR", "FPR"])
|
133 |
+
|
134 |
+
data_ood = []
|
135 |
+
i = 0
|
136 |
+
self.eval_list.remove(dataset_name)
|
137 |
+
for dataset_name in tqdm.tqdm(self.eval_list, desc="Evaluation on OOD data"):
|
138 |
+
accept_sum = {"tp": 0, "fp": 0}
|
139 |
+
reject_sum = {"tp": 0, "fp": 0}
|
140 |
+
with open(f'val_feats/{self.id}/{self.backbone}/{dataset_name}_feats_fp_dict.pickle', 'rb') as f:
|
141 |
+
feats_fp_dict = pickle.load(f)
|
142 |
+
for label in self.label_list:
|
143 |
+
if label in monitors_dict:
|
144 |
+
verdict = monitors_dict[label].make_verdicts(feats_fp_dict[label])
|
145 |
+
accept_sum["fp"] += np.sum(verdict)
|
146 |
+
reject_sum["fp"] += len(verdict) - np.sum(verdict)
|
147 |
+
FPR = round((accept_sum['fp'] / (reject_sum['fp'] + accept_sum['fp'])*100), 2)
|
148 |
+
data_ood.append([dataset_name, str(FPR)+"%"])
|
149 |
+
i += 1
|
150 |
+
# prepare dataframes
|
151 |
+
df_ood = pd.DataFrame(data_ood, columns=["Dataset", "FPR"])
|
152 |
+
df_ood["Dataset"] = ["COCO", "Open Images"] if self.id == "voc" else ["COCO", "Open Images", "VOC-OOD"]
|
153 |
+
return df_id, df_ood
|
154 |
+
|
155 |
+
def _postprocess_cam(self, raw_cam, img_width, img_height):
|
156 |
+
cam_orig = np.sum(raw_cam, axis=0) # [H,W]
|
157 |
+
cam_orig = np.maximum(cam_orig, 0) # ReLU
|
158 |
+
cam_orig -= np.min(cam_orig)
|
159 |
+
cam_orig /= np.max(cam_orig)
|
160 |
+
cam = cv2.resize(cam_orig, (img_width, img_height))
|
161 |
+
return cam
|
162 |
+
|
163 |
+
def _fasterrcnn_reshape_transform(self, x):
|
164 |
+
target_size = x['p6'].size()[-2 : ]
|
165 |
+
activations = []
|
166 |
+
for key, value in x.items():
|
167 |
+
activations.append(torch.nn.functional.interpolate(torch.abs(value), target_size, mode='bilinear'))
|
168 |
+
activations = torch.cat(activations, axis=1)
|
169 |
+
return activations
|
170 |
+
|
171 |
+
def _get_input_dict(self, original_image):
|
172 |
+
height, width = original_image.shape[:2]
|
173 |
+
transform_gen = T.ResizeShortestEdge(
|
174 |
+
[self.cfg.INPUT.MIN_SIZE_TEST, self.cfg.INPUT.MIN_SIZE_TEST], self.cfg.INPUT.MAX_SIZE_TEST
|
175 |
+
)
|
176 |
+
image = transform_gen.get_transform(original_image).apply_image(original_image)
|
177 |
+
image = torch.as_tensor(image.astype("float32").transpose(2, 0, 1))
|
178 |
+
inputs = {"image": image, "height": height, "width": width}
|
179 |
+
return inputs
|
180 |
+
|
181 |
+
def get_output(self, monitors_dict, img):
|
182 |
+
image = read_image(img, format="BGR")
|
183 |
+
input_image_dict = [self._get_input_dict(image)]
|
184 |
+
pred_instances, feats = self._inference(self.model, input_image_dict)
|
185 |
+
detections = pred_instances[0]["instances"].to("cpu")
|
186 |
+
cls_idxs = detections.pred_classes.detach().numpy()
|
187 |
+
# get labels from class indices
|
188 |
+
labels = [self.label_dict[i] for i in cls_idxs]
|
189 |
+
# count values in labels, and return a dictionary
|
190 |
+
labels_count_dict = dict((i, labels.count(i)) for i in labels)
|
191 |
+
v = Visualizer(image[..., ::-1], MetadataCatalog.get("custom_dataset"), scale=1)
|
192 |
+
v = v.draw_instance_predictions(detections)
|
193 |
+
img_detection = v.get_image()
|
194 |
+
df = pd.DataFrame(list(labels_count_dict.items()), columns=['Object', 'Count'])
|
195 |
+
verdicts = []
|
196 |
+
for label, feat in zip(labels, feats):
|
197 |
+
verdict = monitors_dict[label].make_verdicts(feat[np.newaxis,:])[0]
|
198 |
+
verdicts.append(verdict)
|
199 |
+
detections_ood = detections[[i for i, x in enumerate(verdicts) if not x]]
|
200 |
+
detections_ood.pred_classes = torch.tensor([5]*len(detections_ood.pred_classes))
|
201 |
+
labels_ood = [label for label, verdict in zip(labels, verdicts) if not verdict]
|
202 |
+
verdicts_ood = ["Rejected"]*len(labels_ood)
|
203 |
+
df_verdict = pd.DataFrame(list(zip(labels_ood, verdicts_ood)), columns=['Object', 'Verdict'])
|
204 |
+
v = Visualizer(image[..., ::-1], MetadataCatalog.get("custom_dataset"), scale=1)
|
205 |
+
for box in detections_ood.pred_boxes.to('cpu'):
|
206 |
+
v.draw_box(box)
|
207 |
+
v.draw_text("OOD", tuple(box[:2].numpy()))
|
208 |
+
v = v.get_output()
|
209 |
+
img_ood = v.get_image()
|
210 |
+
pred_bboxes = detections.pred_boxes.tensor.numpy().astype(np.int32)
|
211 |
+
target_layers = [self.model.backbone]
|
212 |
+
targets = [FasterRCNNBoxScoreTarget(labels=labels, bounding_boxes=pred_bboxes)]
|
213 |
+
cam = EigenCAM(self.model,
|
214 |
+
target_layers,
|
215 |
+
use_cuda=False,
|
216 |
+
reshape_transform=self._fasterrcnn_reshape_transform)
|
217 |
+
grayscale_cam = cam(input_image_dict, targets)
|
218 |
+
cam = self._postprocess_cam(grayscale_cam, input_image_dict[0]["width"], input_image_dict[0]["height"])
|
219 |
+
plt.rcParams["figure.figsize"] = (30,10)
|
220 |
+
plt.imshow(img_detection[..., ::-1], interpolation='none')
|
221 |
+
plt.imshow(cam, cmap='jet', alpha=0.5)
|
222 |
+
plt.axis("off")
|
223 |
+
img_buff = io.BytesIO()
|
224 |
+
plt.savefig(img_buff, format='png', bbox_inches='tight', pad_inches=0)
|
225 |
+
img_cam = Image.open(img_buff)
|
226 |
+
image_dict = {}
|
227 |
+
image_dict["image"] = image
|
228 |
+
image_dict["cam"] = img_cam
|
229 |
+
image_dict["detection"] = img_detection
|
230 |
+
image_dict["verdict"] = img_ood
|
231 |
+
return image_dict, df, df_verdict
|
enlarge.py
ADDED
@@ -0,0 +1,320 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from detectron2.utils.logger import setup_logger
|
3 |
+
setup_logger()
|
4 |
+
|
5 |
+
from detectron2.config import get_cfg
|
6 |
+
import detectron2.data.transforms as T
|
7 |
+
from detectron2.checkpoint import DetectionCheckpointer
|
8 |
+
from detectron2.modeling import build_model
|
9 |
+
|
10 |
+
|
11 |
+
import numpy as np
|
12 |
+
import cv2
|
13 |
+
import os
|
14 |
+
import argparse
|
15 |
+
import time
|
16 |
+
import h5py
|
17 |
+
import pickle
|
18 |
+
import gradio as gr
|
19 |
+
import fiftyone as fo
|
20 |
+
from fiftyone import ViewField as F
|
21 |
+
import tqdm
|
22 |
+
torch.manual_seed(0)
|
23 |
+
np.random.seed(0)
|
24 |
+
|
25 |
+
torch.backends.cudnn.deterministic = True
|
26 |
+
torch.backends.cudnn.benchmark = False
|
27 |
+
|
28 |
+
from vos.detection.modeling.regnet import build_regnet_fpn_backbone
|
29 |
+
import core.metadata as metadata
|
30 |
+
|
31 |
+
from utils_clustering import *
|
32 |
+
|
33 |
+
fullName2ab_dict = {'PASCAL-VOC':"voc", 'BDD100K':"bdd", 'KITTI':"kitti", 'Speed signs':"speed", 'NuScenes':"nu"}
|
34 |
+
ab2FullName_dict = {'voc':"PASCAL-VOC", 'bdd':"BDD100K", 'kitti':"KITTI", 'speed':"Speed signs", 'nu':"NuScenes"}
|
35 |
+
class Detectron2Monitor():
|
36 |
+
def __init__(self, id, backbone):
|
37 |
+
self.id, self.label_list = self._get_label_list(id)
|
38 |
+
self.backbone = backbone
|
39 |
+
self.cfg, self.device, self.model = self._get_model()
|
40 |
+
self.label_dict = {i:label for i, label in enumerate(self.label_list)}
|
41 |
+
self.eval_list = ["ID-voc-OOD-coco", "OOD-open", "voc-val"] if self.id == "voc" else ["ID-bdd-OOD-coco", "OOD-open", "voc-ood", f"{self.id}-val"]
|
42 |
+
|
43 |
+
def _get_label_list(self, id):
|
44 |
+
id = fullName2ab_dict[id]
|
45 |
+
if id == 'voc':
|
46 |
+
label_list = metadata.VOC_THING_CLASSES
|
47 |
+
elif id == 'bdd':
|
48 |
+
label_list = metadata.BDD_THING_CLASSES
|
49 |
+
elif id == 'kitti':
|
50 |
+
label_list = metadata.KITTI_THING_CLASSES
|
51 |
+
elif id == 'speed' or id == 'prescan':
|
52 |
+
label_list = metadata.SPEED_THING_CLASSES
|
53 |
+
else:
|
54 |
+
label_list = metadata.NU_THING_CLASSES
|
55 |
+
return id, label_list
|
56 |
+
|
57 |
+
def _get_model(self):
|
58 |
+
cfg = get_cfg()
|
59 |
+
cfg.merge_from_file(f"/home/hugo/bdd100k-monitoring/monitoringObjectDetection/vanilla_{self.backbone}.yaml")
|
60 |
+
cfg.MODEL.WEIGHTS = f"models/model_final_{self.backbone}_{self.id}.pth"
|
61 |
+
cfg.MODEL.DEVICE='cuda'
|
62 |
+
cfg.MODEL.ROI_HEADS.NUM_CLASSES = len(self.label_list)
|
63 |
+
model = build_model(cfg)
|
64 |
+
model.eval()
|
65 |
+
checkpointer = DetectionCheckpointer(model)
|
66 |
+
checkpointer.load(cfg.MODEL.WEIGHTS)
|
67 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
68 |
+
model = model.to(device)
|
69 |
+
return cfg, device, model
|
70 |
+
|
71 |
+
def _inference(self, model, inputs):
|
72 |
+
with torch.no_grad():
|
73 |
+
images = model.preprocess_image(inputs)
|
74 |
+
features = model.backbone(images.tensor)
|
75 |
+
proposals, _ = model.proposal_generator(images, features, None) # RPN
|
76 |
+
|
77 |
+
features_ = [features[f] for f in model.roi_heads.box_in_features]
|
78 |
+
box_features = model.roi_heads.box_pooler(features_, [x.proposal_boxes for x in proposals])
|
79 |
+
box_features = model.roi_heads.box_head(box_features) # features of all 1k candidates
|
80 |
+
predictions = model.roi_heads.box_predictor(box_features)
|
81 |
+
pred_instances, pred_inds = model.roi_heads.box_predictor.inference(predictions, proposals)
|
82 |
+
pred_instances = model.roi_heads.forward_with_given_boxes(features, pred_instances)
|
83 |
+
|
84 |
+
# output boxes, masks, scores, etc
|
85 |
+
pred_instances = model._postprocess(pred_instances, inputs, images.image_sizes) # scale box to orig size
|
86 |
+
# features of the proposed boxes
|
87 |
+
feats = box_features[pred_inds].cpu().numpy()
|
88 |
+
return pred_instances, feats
|
89 |
+
|
90 |
+
def _save_features(self, feats_npy, dataset_view, file_path):
|
91 |
+
features_idx_dict = {cls:[] for cls in self.label_list}
|
92 |
+
for sample in tqdm.tqdm(dataset_view, desc="Saving features"):
|
93 |
+
for detection in sample.prediction.detections:
|
94 |
+
label_pred = detection.label
|
95 |
+
feature_idx = detection.feature_idx
|
96 |
+
features_idx_dict[label_pred].append(feature_idx)
|
97 |
+
feats_dict = {cls:feats_npy[features_idx_dict[cls]] for cls in self.label_list}
|
98 |
+
if not os.path.exists(file_path):
|
99 |
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
100 |
+
with open(file_path, 'wb') as f:
|
101 |
+
pickle.dump(feats_dict, f)
|
102 |
+
|
103 |
+
def _extract(self, dataset_name):
|
104 |
+
dataset = fo.load_dataset(dataset_name)
|
105 |
+
aug = T.AugmentationList([T.ResizeShortestEdge(
|
106 |
+
[self.cfg.INPUT.MIN_SIZE_TEST, self.cfg.INPUT.MIN_SIZE_TEST], self.cfg.INPUT.MAX_SIZE_TEST),
|
107 |
+
]
|
108 |
+
)
|
109 |
+
i = 0
|
110 |
+
feats_list = []
|
111 |
+
for sample in tqdm.tqdm(dataset, desc="Extracting features"):
|
112 |
+
image = cv2.imread(sample.filepath)
|
113 |
+
height, width = image.shape[:2]
|
114 |
+
input = T.AugInput(image)
|
115 |
+
transform = aug(input)
|
116 |
+
image = input.image
|
117 |
+
image = torch.as_tensor(image.astype("float32").transpose(2, 0, 1)).to(self.device)
|
118 |
+
inputs = [{"image": image, "height": height, "width": width}]
|
119 |
+
|
120 |
+
preds, feats = self._inference(self.model, inputs)
|
121 |
+
boxes = preds[0]["instances"].pred_boxes.tensor.cpu().detach().numpy()
|
122 |
+
classes = preds[0]["instances"].pred_classes.cpu().detach().numpy()
|
123 |
+
scores = preds[0]["instances"].scores.cpu().detach().numpy()
|
124 |
+
|
125 |
+
feats_list.extend(feats)
|
126 |
+
if i == 1000:
|
127 |
+
np.save('feats.npy', feats_list)
|
128 |
+
|
129 |
+
detections = []
|
130 |
+
for score, label, box in zip(scores, classes, boxes):
|
131 |
+
x1, y1, x2, y2 = box
|
132 |
+
rel_box = [x1/width, y1/height, (x2 - x1) / width, (y2 - y1) / height]
|
133 |
+
label = self.label_dict[label]
|
134 |
+
detections.append(
|
135 |
+
fo.Detection(
|
136 |
+
label=label,
|
137 |
+
bounding_box=rel_box,
|
138 |
+
confidence=score,
|
139 |
+
feature_idx=i
|
140 |
+
),
|
141 |
+
)
|
142 |
+
i += 1
|
143 |
+
sample["prediction"] = fo.Detections(detections=detections)
|
144 |
+
sample.save()
|
145 |
+
feats_npy = np.array(feats_list)
|
146 |
+
with h5py.File(f'feats_{self.id}-train_{self.backbone}.h5', 'w') as f:
|
147 |
+
dset = f.create_dataset(f"feats_{self.id}-train_{self.backbone}", data=feats_npy)
|
148 |
+
if dataset.name.endswith(("train", "val")):
|
149 |
+
results = dataset.evaluate_detections(
|
150 |
+
"prediction",
|
151 |
+
gt_field="detections",
|
152 |
+
eval_key="eval",
|
153 |
+
compute_mAP=True)
|
154 |
+
# results.print_report()
|
155 |
+
# print("mAP: ", results.mAP())
|
156 |
+
tp_prediction_view = dataset.filter_labels("prediction", F("eval") == "tp")
|
157 |
+
self._save_features(feats_npy, tp_prediction_view, f"train_feats/{self.id}/{self.backbone}/{self.id}-train_feats_tp_dict.pickle")
|
158 |
+
if dataset.name.endswith("val"):
|
159 |
+
fp_prediction_view = dataset.filter_labels("prediction", F("eval") == "fp")
|
160 |
+
self._save_features(feats_npy, fp_prediction_view, f"val_feats/{self.id}/{self.backbone}/{self.dataset_name}_feats_fp_dict.pickle")
|
161 |
+
else:
|
162 |
+
self._save_features(feats_npy, dataset, f"val_feats/{self.id}/{self.backbone}/{self.dataset_name}_feats_fp_dict.pickle")
|
163 |
+
|
164 |
+
def _construct(self, clustering_algo, nb_clusters=4, eps=5, min_samples=10):
|
165 |
+
with open(f"/home/hugo/bdd100k-monitoring/train_feats/{self.id}/{self.backbone}/{self.id}-train_feats_tp_dict.pickle", 'rb') as f:
|
166 |
+
feats_dict = pickle.load(f)
|
167 |
+
dir_path = f'monitors/{self.id}/{self.backbone}/{clustering_algo}'
|
168 |
+
if not os.path.exists(dir_path):
|
169 |
+
os.makedirs(dir_path)
|
170 |
+
monitor_dict = {}
|
171 |
+
for class_, fts in tqdm.tqdm(feats_dict.items(), desc="Constructing monitors"):
|
172 |
+
if clustering_algo == "kmeans":
|
173 |
+
clusters = k_means_cluster(fts, nb_clusters)
|
174 |
+
elif clustering_algo == "spectral":
|
175 |
+
clusters = spectral_cluster(fts, nb_clusters)
|
176 |
+
elif clustering_algo == "dbscan":
|
177 |
+
clusters = dbscan_cluster(fts, eps, min_samples)
|
178 |
+
dims = fts.shape[1]
|
179 |
+
box_list = []
|
180 |
+
for cl_id, points in clusters.items():
|
181 |
+
box = Box()
|
182 |
+
box.build(dims, points)
|
183 |
+
box_list.append(box)
|
184 |
+
monitor = Monitor(good_ref=box_list)
|
185 |
+
monitor_dict[class_] = monitor
|
186 |
+
if clustering_algo == "dbscan":
|
187 |
+
with open(f"monitors/{self.id}/{self.backbone}/{clustering_algo}/eps{eps}_min_samples{min_samples}.pkl" , 'wb') as f:
|
188 |
+
pickle.dump(monitor_dict, f)
|
189 |
+
else:
|
190 |
+
with open(f"monitors/{self.id}/{self.backbone}/{clustering_algo}/{nb_clusters}.pkl" , 'wb') as f:
|
191 |
+
pickle.dump(monitor_dict, f)
|
192 |
+
|
193 |
+
def _load_monitors(self, clustering_algo, nb_clusters, eps=5, min_samples=10):
|
194 |
+
if clustering_algo == "dbscan":
|
195 |
+
with open(f"monitors/{self.id}/{self.backbone}/{clustering_algo}/eps{eps}_min_samples{min_samples}.pkl", 'rb') as f:
|
196 |
+
monitors_dict = pickle.load(f)
|
197 |
+
else:
|
198 |
+
with open(f"monitors/{self.id}/{self.backbone}/{clustering_algo}/{nb_clusters}.pkl", 'rb') as f:
|
199 |
+
monitors_dict = pickle.load(f)
|
200 |
+
return monitors_dict
|
201 |
+
|
202 |
+
def _evaluate(self, monitors_dict):
|
203 |
+
dataset_name = f"{self.id}-val"
|
204 |
+
with open(f'val_feats/{self.id}/{self.backbone}/{dataset_name}_feats_tp_dict.pickle', 'rb') as f:
|
205 |
+
feats_tp_dict = pickle.load(f)
|
206 |
+
with open(f'val_feats/{self.id}/{self.backbone}/{dataset_name}_feats_fp_dict.pickle', 'rb') as f:
|
207 |
+
feats_fp_dict = pickle.load(f)
|
208 |
+
# monitors_dict = self._load_monitors(clustering_algo, nb_clusters, eps, min_samples)
|
209 |
+
# make verdicts on ID data
|
210 |
+
data_tp = []
|
211 |
+
data_fp = []
|
212 |
+
accept_sum = {"tp": 0, "fp": 0}
|
213 |
+
reject_sum = {"tp": 0, "fp": 0}
|
214 |
+
for label in tqdm.tqdm(self.label_list, desc="Evaluation on ID data"):
|
215 |
+
if label in monitors_dict:
|
216 |
+
verdict = monitors_dict[label].make_verdicts(feats_tp_dict[label])
|
217 |
+
data_tp.append([label, len(verdict), np.sum(verdict)/len(verdict)])
|
218 |
+
accept_sum["tp"] += np.sum(verdict)
|
219 |
+
reject_sum["tp"] += len(verdict) - np.sum(verdict)
|
220 |
+
verdict = monitors_dict[label].make_verdicts(feats_fp_dict[label])
|
221 |
+
data_fp.append([label, len(verdict), (len(verdict)-np.sum(verdict))/len(verdict)])
|
222 |
+
accept_sum["fp"] += np.sum(verdict)
|
223 |
+
reject_sum["fp"] += len(verdict) - np.sum(verdict)
|
224 |
+
TPR = round((accept_sum['tp'] / (reject_sum['tp'] + accept_sum['tp'])*100), 2)
|
225 |
+
FPR = round((accept_sum['fp'] / (reject_sum['fp'] + accept_sum['fp'])*100), 2)
|
226 |
+
id_name = ab2FullName_dict[self.id]
|
227 |
+
df_id = pd.DataFrame([[id_name, f"{TPR}%", f"{FPR}%"]], columns=["Dataset", "TPR", "FPR"])
|
228 |
+
|
229 |
+
data_ood = []
|
230 |
+
i = 0
|
231 |
+
self.eval_list.remove(dataset_name)
|
232 |
+
for dataset_name in tqdm.tqdm(self.eval_list, desc="Evaluation on OOD data"):
|
233 |
+
accept_sum = {"tp": 0, "fp": 0}
|
234 |
+
reject_sum = {"tp": 0, "fp": 0}
|
235 |
+
with open(f'val_feats/{self.id}/{self.backbone}/{dataset_name}_feats_fp_dict.pickle', 'rb') as f:
|
236 |
+
feats_fp_dict = pickle.load(f)
|
237 |
+
for label in self.label_list:
|
238 |
+
if label in monitors_dict:
|
239 |
+
verdict = monitors_dict[label].make_verdicts(feats_fp_dict[label])
|
240 |
+
accept_sum["fp"] += np.sum(verdict)
|
241 |
+
reject_sum["fp"] += len(verdict) - np.sum(verdict)
|
242 |
+
FPR = round((accept_sum['fp'] / (reject_sum['fp'] + accept_sum['fp'])*100), 2)
|
243 |
+
data_ood.append([dataset_name, str(FPR)+"%"])
|
244 |
+
i += 1
|
245 |
+
# prepare dataframes
|
246 |
+
df_ood = pd.DataFrame(data_ood, columns=["Dataset", "FPR"])
|
247 |
+
df_ood["Dataset"] = ["COCO", "Open Images"] if self.id == "voc" else ["COCO", "Open Images", "VOC-OOD"]
|
248 |
+
return df_id, df_ood
|
249 |
+
|
250 |
+
def _enlarge(self, monitors_dict, delta):
|
251 |
+
for label, monitor in monitors_dict.items():
|
252 |
+
for i in range(len(monitor.good_ref)):
|
253 |
+
monitor.good_ref[i].ivals = monitor.good_ref[i].ivals*np.array([1-delta, 1+delta])
|
254 |
+
monitors_dict[label] = monitor
|
255 |
+
return monitors_dict
|
256 |
+
|
257 |
+
def fx_gradio(id, backbone, progress=gr.Progress(track_tqdm=True)):
|
258 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
259 |
+
t0 = time.time()
|
260 |
+
detectron2monitor._extract(f"{detectron2monitor.id}-train")
|
261 |
+
minutes, seconds = divmod(time.time()-t0, 60)
|
262 |
+
return f"Total feature extraction time: {int(minutes):02d}:{int(seconds):02d}"
|
263 |
+
|
264 |
+
def construct_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, progress=gr.Progress(track_tqdm=True)):
|
265 |
+
detection2monitor = Detectron2Monitor(id, backbone)
|
266 |
+
t0 = time.time()
|
267 |
+
detection2monitor._construct(clustering_algo, nb_clusters, eps, min_samples)
|
268 |
+
minutes, seconds = divmod(time.time()-t0, 60)
|
269 |
+
return f"Total monitor construction time: {int(minutes):02d}:{int(seconds):02d}"
|
270 |
+
|
271 |
+
def fx_eval_gradio(id, backbone, progress=gr.Progress(track_tqdm=True)):
|
272 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
273 |
+
t0 = time.time()
|
274 |
+
for dataset_name in tqdm.tqdm(detectron2monitor.eval_list, desc="Evaluation data preparation"):
|
275 |
+
detectron2monitor._extract(dataset_name)
|
276 |
+
minutes, seconds = divmod(time.time()-t0, 60)
|
277 |
+
return f"Total evaluation data preparation time: {int(minutes):02d}:{int(seconds):02d}"
|
278 |
+
|
279 |
+
def eval_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, progress=gr.Progress(track_tqdm=True)):
|
280 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
281 |
+
monitors_dict = detectron2monitor._load_monitors(clustering_algo, nb_clusters, eps, min_samples)
|
282 |
+
df_id, df_ood = detectron2monitor._evaluate(monitors_dict)
|
283 |
+
return df_id, df_ood
|
284 |
+
|
285 |
+
def enlarge_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, delta, progress=gr.Progress(track_tqdm=True)):
|
286 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
287 |
+
monitors_dict = detectron2monitor._load_monitors(clustering_algo, nb_clusters, eps, min_samples)
|
288 |
+
monitors_dict_enlarge = detectron2monitor._enlarge(monitors_dict, delta)
|
289 |
+
df_id, df_ood = detectron2monitor._evaluate(monitors_dict_enlarge)
|
290 |
+
# if clustering_algo == "dbscan":
|
291 |
+
# with open(f"monitors/{id}/{backbone}/{clustering_algo}/eps{eps}_min_samples{min_samples}_delta{delta}.pkl" , 'wb') as f:
|
292 |
+
# pickle.dump(monitors_dict, f)
|
293 |
+
# else:
|
294 |
+
# with open(f"monitors/{id}/{backbone}/{clustering_algo}/{nb_clusters}_delta{delta}.pkl" , 'wb') as f:
|
295 |
+
# pickle.dump(monitors_dict, f)
|
296 |
+
# return f"Monitors enlarged by {delta*100}%"
|
297 |
+
return df_id, df_ood
|
298 |
+
with gr.Blocks(theme='soft') as demo:
|
299 |
+
gr.Markdown("# Monitor enlargment utility")
|
300 |
+
id = gr.Radio(['PASCAL-VOC', 'BDD100K', 'KITTI', 'Speed signs', 'NuScenes'], label="Dataset")
|
301 |
+
backbone = gr.Radio(['regnet', 'resnet'], label="Backbone")
|
302 |
+
clustering_algo = gr.Dropdown(['kmeans', 'spectral', 'dbscan', 'opticals'], label="Clustering algorithm")
|
303 |
+
with gr.Row():
|
304 |
+
nb_clusters = gr.Number(value=5, label="Number of clusters", precision=0)
|
305 |
+
eps = gr.Number(value=5, label="Epsilon", precision=0)
|
306 |
+
min_samples = gr.Number(value=10, label="Minimum samples", precision=0)
|
307 |
+
delta = gr.Slider(minimum=0, maximum=2.5, step=0.05, label="Delta")
|
308 |
+
with gr.Row():
|
309 |
+
with gr.Group("Original monitors"):
|
310 |
+
eval_id = gr.Dataframe(type="pandas", label="ID performance")
|
311 |
+
eavl_ood = gr.Dataframe(type="pandas", label="OOD performance")
|
312 |
+
eval_btn = gr.Button("Monitor Evaluation")
|
313 |
+
with gr.Column("Enlarged monitors"):
|
314 |
+
eval_id2 = gr.Dataframe(type="pandas", label="ID performance")
|
315 |
+
eavl_ood2 = gr.Dataframe(type="pandas", label="OOD performance")
|
316 |
+
enlarge_btn = gr.Button("Monitor Enlargement")
|
317 |
+
eval_btn.click(fn=eval_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples], outputs=[eval_id, eavl_ood])
|
318 |
+
enlarge_btn.click(fn=enlarge_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples, delta], outputs=[eval_id2, eavl_ood2])
|
319 |
+
|
320 |
+
demo.queue().launch()
|
interface.py
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from detectron2.utils.logger import setup_logger
|
3 |
+
setup_logger()
|
4 |
+
|
5 |
+
|
6 |
+
import numpy as np
|
7 |
+
import time
|
8 |
+
import gradio as gr
|
9 |
+
import tqdm
|
10 |
+
torch.manual_seed(0)
|
11 |
+
np.random.seed(0)
|
12 |
+
|
13 |
+
torch.backends.cudnn.deterministic = True
|
14 |
+
torch.backends.cudnn.benchmark = False
|
15 |
+
|
16 |
+
from detectron2monitor import Detectron2Monitor
|
17 |
+
|
18 |
+
def eval_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, progress=gr.Progress(track_tqdm=True)):
|
19 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
20 |
+
df_id, df_ood = detectron2monitor._evaluate(clustering_algo, nb_clusters, eps, min_samples)
|
21 |
+
return df_id, df_ood
|
22 |
+
|
23 |
+
def inference_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, file):
|
24 |
+
detectron2monitor = Detectron2Monitor(id, backbone, 0.5)
|
25 |
+
monitors_dict = detectron2monitor._load_monitors(clustering_algo, nb_clusters, eps, min_samples)
|
26 |
+
image_dict, df, df_verdict = detectron2monitor.get_output(monitors_dict, file)
|
27 |
+
return image_dict["detection"], image_dict["verdict"], image_dict["cam"], df, df_verdict
|
28 |
+
|
29 |
+
with gr.Blocks(theme='soft') as demo:
|
30 |
+
gr.Markdown("# Runtime Monitoring Computer Vision Models")
|
31 |
+
gr.Markdown(
|
32 |
+
"""
|
33 |
+
This interactive demo presents an approach to monitoring neural networks-based computer vision models using box abstraction-based techniques. Our method involves abstracting features extracted from training data to construct monitors. The demo walks users through the entire process, from monitor construction to evaluation.
|
34 |
+
<!-- The interface is divided into several basic modules:
|
35 |
+
|
36 |
+
- **In-distribution dataset and backbone**: This module allows users to select their target model and dataset.
|
37 |
+
|
38 |
+
- **Feature extraction**: Neuron activation pattern are extracted from the model's intermediate layers using training data. These features represent the good behaviors of the model.
|
39 |
+
- **Monitor construction**: Extracted features are grouped using different clustering techniques. These clusters are then abstracted to serve as references for the monitors.
|
40 |
+
- **Evaluation preparation**: To facilate the evalution, the features should be extracted from evaluation datasets prior to monitor evalution.
|
41 |
+
- **Monitor Evaluation**: The effectiveness of monitors in detecting Out-of-Distribution (OoD) objects are assessed. One of our core metric is FPR 95, which represents the false positive (incorrectly detected objects) rate when the true positive rate for ID is set at 95%. -->
|
42 |
+
|
43 |
+
"""
|
44 |
+
)
|
45 |
+
with gr.Tab("Image Classification"):
|
46 |
+
id = gr.Radio(['MNIST', 'CIFAR-10', 'CIFAR-100', 'ImageNet-100', 'ImageNet-1K'], label="Dataset")
|
47 |
+
backbone = gr.Radio(['LeNet-5', 'ResNet-18', 'WideResNet-28', 'ResNet-50'], label="Backbone")
|
48 |
+
with gr.Tab("Object Detection"):
|
49 |
+
id = gr.Radio(['PASCAL-VOC', 'BDD100K', 'KITTI', 'Speed signs', 'NuScenes'], label="Dataset")
|
50 |
+
backbone = gr.Radio(['regnet', 'resnet'], label="Backbone")
|
51 |
+
clustering_algo = gr.Dropdown(['kmeans', 'spectral', 'dbscan', 'opticals'], label="Clustering algorithm")
|
52 |
+
with gr.Row():
|
53 |
+
nb_clusters = gr.Number(value=5, label="Number of clusters", precision=0)
|
54 |
+
eps = gr.Number(value=5, label="Epsilon", precision=0)
|
55 |
+
min_samples = gr.Number(value=10, label="Minimum samples", precision=0)
|
56 |
+
with gr.Column():
|
57 |
+
# with gr.Column():
|
58 |
+
# with gr.Group():
|
59 |
+
# extract_btn = gr.Button("Extract features")
|
60 |
+
# output1 = gr.Textbox(label="Output")
|
61 |
+
# with gr.Group():
|
62 |
+
# construct_btn = gr.Button("Monitor Construction")
|
63 |
+
# clustering_algo = gr.Dropdown(['kmeans', 'spectral', 'dbscan', 'opticals'], label="Clustering algorithm")
|
64 |
+
# with gr.Row():
|
65 |
+
# nb_clusters = gr.Number(value=5, label="Number of clusters", precision=0)
|
66 |
+
# eps = gr.Number(value=5, label="Epsilon", precision=0)
|
67 |
+
# min_samples = gr.Number(value=10, label="Minimum samples", precision=0)
|
68 |
+
# output2 = gr.Textbox(label="Output")
|
69 |
+
# with gr.Column():
|
70 |
+
# with gr.Group():
|
71 |
+
# prep_btn = gr.Button("Evaluation Data Preparation")
|
72 |
+
# prep_output = gr.Textbox(label="Output")
|
73 |
+
with gr.Group():
|
74 |
+
eval_btn = gr.Button("Monitor Evaluation")
|
75 |
+
eval_id = gr.Dataframe(type="pandas", label="ID performance")
|
76 |
+
eavl_ood = gr.Dataframe(type="pandas", label="OOD performance")
|
77 |
+
with gr.Row():
|
78 |
+
with gr.Column():
|
79 |
+
image = gr.Image(type="filepath", label="Input")
|
80 |
+
button = gr.Button("Infer")
|
81 |
+
with gr.Column():
|
82 |
+
with gr.Tab("Detection"):
|
83 |
+
detection = gr.Image(label="Output")
|
84 |
+
df = gr.Dataframe(label="Detection summary")
|
85 |
+
with gr.Tab("Verdict"):
|
86 |
+
verdict = gr.Image(label="Output")
|
87 |
+
df_verdict = gr.Dataframe(label="Verdict summary")
|
88 |
+
with gr.Tab("Explainable AI"):
|
89 |
+
cam = gr.Image(label="Output")
|
90 |
+
button.click(fn=inference_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples, image], outputs=[detection, verdict, cam, df, df_verdict])
|
91 |
+
# extract_btn.click(fn=fx_gradio, inputs=[id, backbone], outputs=[output1])
|
92 |
+
# construct_btn.click(fn=construct_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples], outputs=[output2])
|
93 |
+
# prep_btn.click(fn=fx_eval_gradio, inputs=[id, backbone], outputs=[prep_output])
|
94 |
+
eval_btn.click(fn=eval_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples], outputs=[eval_id, eavl_ood])
|
95 |
+
demo.queue().launch()
|
interface_tabbed.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from detectron2.utils.logger import setup_logger
|
3 |
+
setup_logger()
|
4 |
+
|
5 |
+
from detectron2.config import get_cfg
|
6 |
+
import detectron2.data.transforms as T
|
7 |
+
from detectron2.checkpoint import DetectionCheckpointer
|
8 |
+
from detectron2.modeling import build_model
|
9 |
+
from detectron2.data.detection_utils import read_image
|
10 |
+
from detectron2.utils.visualizer import Visualizer
|
11 |
+
from detectron2.data import MetadataCatalog
|
12 |
+
|
13 |
+
|
14 |
+
import numpy as np
|
15 |
+
import time
|
16 |
+
import gradio as gr
|
17 |
+
import fiftyone as fo
|
18 |
+
from fiftyone import ViewField as F
|
19 |
+
import tqdm
|
20 |
+
import matplotlib.pyplot as plt
|
21 |
+
import io
|
22 |
+
from PIL import Image
|
23 |
+
torch.manual_seed(0)
|
24 |
+
np.random.seed(0)
|
25 |
+
|
26 |
+
torch.backends.cudnn.deterministic = True
|
27 |
+
torch.backends.cudnn.benchmark = False
|
28 |
+
from detectron2monitor import Detectron2Monitor
|
29 |
+
|
30 |
+
|
31 |
+
|
32 |
+
def fx_gradio(id, backbone, progress=gr.Progress(track_tqdm=True)):
|
33 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
34 |
+
t0 = time.time()
|
35 |
+
detectron2monitor._extract(f"{detectron2monitor.id}-train")
|
36 |
+
minutes, seconds = divmod(time.time()-t0, 60)
|
37 |
+
return f"Total feature extraction time: {int(minutes):02d}:{int(seconds):02d}"
|
38 |
+
|
39 |
+
def construct_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, progress=gr.Progress(track_tqdm=True)):
|
40 |
+
detection2monitor = Detectron2Monitor(id, backbone)
|
41 |
+
t0 = time.time()
|
42 |
+
detection2monitor._construct(clustering_algo, nb_clusters, eps, min_samples)
|
43 |
+
minutes, seconds = divmod(time.time()-t0, 60)
|
44 |
+
return f"Total monitor construction time: {int(minutes):02d}:{int(seconds):02d}"
|
45 |
+
|
46 |
+
def fx_eval_gradio(id, backbone, progress=gr.Progress(track_tqdm=True)):
|
47 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
48 |
+
t0 = time.time()
|
49 |
+
for dataset_name in tqdm.tqdm(detectron2monitor.eval_list, desc="Evaluation data preparation"):
|
50 |
+
detectron2monitor._extract(dataset_name)
|
51 |
+
minutes, seconds = divmod(time.time()-t0, 60)
|
52 |
+
return f"Total evaluation data preparation time: {int(minutes):02d}:{int(seconds):02d}"
|
53 |
+
|
54 |
+
def eval_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, progress=gr.Progress(track_tqdm=True)):
|
55 |
+
detectron2monitor = Detectron2Monitor(id, backbone)
|
56 |
+
df_id, df_ood = detectron2monitor._evaluate(clustering_algo, nb_clusters, eps, min_samples)
|
57 |
+
return df_id, df_ood
|
58 |
+
|
59 |
+
def inference_gradio(id, backbone, clustering_algo, nb_clusters, eps, min_samples, file):
|
60 |
+
detectron2monitor = Detectron2Monitor(id, backbone, 0.5)
|
61 |
+
monitors_dict = detectron2monitor._load_monitors(clustering_algo, nb_clusters, eps, min_samples)
|
62 |
+
image_dict, df, df_verdict = detectron2monitor.get_output(monitors_dict, file)
|
63 |
+
return image_dict["detection"], image_dict["verdict"], image_dict["cam"], df, df_verdict
|
64 |
+
|
65 |
+
with gr.Blocks(theme='soft') as demo:
|
66 |
+
gr.Markdown("# Runtime Monitoring Computer Vision Models")
|
67 |
+
gr.Markdown(
|
68 |
+
"""
|
69 |
+
This interactive demo presents an approach to monitoring neural networks-based computer vision models using box abstraction-based techniques. Our method involves abstracting features extracted from training data to construct monitors. The demo walks users through the entire process, from monitor construction to evaluation.
|
70 |
+
The interface is divided into several basic modules:
|
71 |
+
|
72 |
+
- **In-distribution dataset and backbone**: This module allows users to select their target model and dataset.
|
73 |
+
- **Feature extraction**: Neuron activation pattern are extracted from the model's intermediate layers using training data. These features represent the good behaviors of the model.
|
74 |
+
- **Monitor construction**: Extracted features are grouped using different clustering techniques. These clusters are then abstracted to serve as references for the monitors.
|
75 |
+
- **Evaluation preparation**: To facilate the evalution, the features should be extracted from evaluation datasets prior to monitor evalution.
|
76 |
+
- **Monitor Evaluation**: The effectiveness of monitors in detecting Out-of-Distribution (OoD) objects are assessed. One of our core metric is FPR 95, which represents the false positive (incorrectly detected objects) rate when the true positive rate for ID is set at 95%.
|
77 |
+
"""
|
78 |
+
)
|
79 |
+
with gr.Tab("Image Classification"):
|
80 |
+
id = gr.Radio(['MNIST', 'CIFAR-10', 'CIFAR-100', 'ImageNet-100', 'ImageNet-1K'], label="Dataset")
|
81 |
+
backbone = gr.Radio(['LeNet-5', 'ResNet-18', 'WideResNet-28', 'ResNet-50'], label="Backbone")
|
82 |
+
with gr.Tab("Feature extraction"):
|
83 |
+
extract_btn = gr.Button("Extract features")
|
84 |
+
output1 = gr.Textbox(label="Output")
|
85 |
+
with gr.Tab("Monitor construction"):
|
86 |
+
construct_btn = gr.Button("Monitor Construction")
|
87 |
+
clustering_algo = gr.Dropdown(['kmeans', 'spectral', 'dbscan', 'opticals'], label="Clustering algorithm")
|
88 |
+
with gr.Row():
|
89 |
+
nb_clusters = gr.Number(value=5, label="Number of clusters", precision=0)
|
90 |
+
eps = gr.Number(value=5, label="Epsilon", precision=0)
|
91 |
+
min_samples = gr.Number(value=10, label="Minimum samples", precision=0)
|
92 |
+
output2 = gr.Textbox(label="Output")
|
93 |
+
with gr.Tab("Evaluation"):
|
94 |
+
prep_btn = gr.Button("Evaluation Data Preparation")
|
95 |
+
prep_output = gr.Textbox(label="Output")
|
96 |
+
with gr.Tab("Evaluation results"):
|
97 |
+
eval_btn = gr.Button("Monitor Evaluation")
|
98 |
+
eval_id = gr.Dataframe(type="pandas", label="ID performance")
|
99 |
+
eavl_ood = gr.Dataframe(type="pandas", label="OOD performance")
|
100 |
+
with gr.Tab("Inference"):
|
101 |
+
with gr.Row().style(equal_height=True):
|
102 |
+
with gr.Column():
|
103 |
+
image = gr.Image(type="filepath", label="Input")
|
104 |
+
button = gr.Button("Infer")
|
105 |
+
|
106 |
+
with gr.Column():
|
107 |
+
with gr.Tab("Detection"):
|
108 |
+
detection = gr.Image(label="Output")
|
109 |
+
df = gr.Dataframe(label="Detection summary")
|
110 |
+
with gr.Tab("Verdict"):
|
111 |
+
verdict = gr.Image(label="Output")
|
112 |
+
df_verdict = gr.Dataframe(label="Verdict summary")
|
113 |
+
with gr.Tab("Explainable AI"):
|
114 |
+
cam = gr.Image(label="Output")
|
115 |
+
with gr.Tab("Object Detection"):
|
116 |
+
id = gr.Radio(['PASCAL-VOC', 'BDD100K', 'KITTI', 'Speed signs', 'NuScenes'], label="Dataset")
|
117 |
+
backbone = gr.Radio(['regnet', 'resnet'], label="Backbone")
|
118 |
+
with gr.Tab("Feature extraction"):
|
119 |
+
extract_btn = gr.Button("Extract features")
|
120 |
+
output1 = gr.Textbox(label="Output")
|
121 |
+
with gr.Tab("Monitor construction"):
|
122 |
+
construct_btn = gr.Button("Monitor Construction")
|
123 |
+
clustering_algo = gr.Dropdown(['kmeans', 'spectral', 'dbscan', 'opticals'], label="Clustering algorithm")
|
124 |
+
with gr.Row():
|
125 |
+
nb_clusters = gr.Number(value=5, label="Number of clusters", precision=0)
|
126 |
+
eps = gr.Number(value=5, label="Epsilon", precision=0)
|
127 |
+
min_samples = gr.Number(value=10, label="Minimum samples", precision=0)
|
128 |
+
output2 = gr.Textbox(label="Output")
|
129 |
+
with gr.Tab("Evaluation preparation"):
|
130 |
+
prep_btn = gr.Button("Evaluation Data Preparation")
|
131 |
+
prep_output = gr.Textbox(label="Output")
|
132 |
+
with gr.Tab("Evaluation results"):
|
133 |
+
eval_btn = gr.Button("Monitor Evaluation")
|
134 |
+
eval_id = gr.Dataframe(type="pandas", label="ID performance")
|
135 |
+
eavl_ood = gr.Dataframe(type="pandas", label="OOD performance")
|
136 |
+
with gr.Tab("Inference"):
|
137 |
+
with gr.Row().style(equal_height=True):
|
138 |
+
with gr.Column():
|
139 |
+
image = gr.Image(type="filepath", label="Input")
|
140 |
+
button = gr.Button("Infer")
|
141 |
+
with gr.Column():
|
142 |
+
with gr.Tab("Detection"):
|
143 |
+
detection = gr.Image(label="Output")
|
144 |
+
df = gr.Dataframe(label="Detection summary")
|
145 |
+
with gr.Tab("Verdict"):
|
146 |
+
verdict = gr.Image(label="Output")
|
147 |
+
df_verdict = gr.Dataframe(label="Verdict summary")
|
148 |
+
with gr.Tab("Explainable AI"):
|
149 |
+
cam = gr.Image(label="Output")
|
150 |
+
button.click(fn=inference_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples, image], outputs=[detection, verdict, cam, df, df_verdict])
|
151 |
+
extract_btn.click(fn=fx_gradio, inputs=[id, backbone], outputs=[output1])
|
152 |
+
construct_btn.click(fn=construct_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples], outputs=[output2])
|
153 |
+
prep_btn.click(fn=fx_eval_gradio, inputs=[id, backbone], outputs=[prep_output])
|
154 |
+
eval_btn.click(fn=eval_gradio, inputs=[id, backbone, clustering_algo, nb_clusters, eps, min_samples], outputs=[eval_id, eavl_ood])
|
155 |
+
demo.queue().launch()
|
models/configs/Base-RCNN-FPN.yaml
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MODEL:
|
2 |
+
META_ARCHITECTURE: "GeneralizedRCNN"
|
3 |
+
|
4 |
+
BACKBONE:
|
5 |
+
NAME: "build_resnet_fpn_backbone"
|
6 |
+
|
7 |
+
RESNETS:
|
8 |
+
OUT_FEATURES: ["res2", "res3", "res4", "res5"]
|
9 |
+
|
10 |
+
FPN:
|
11 |
+
IN_FEATURES: ["res2", "res3", "res4", "res5"]
|
12 |
+
|
13 |
+
ANCHOR_GENERATOR:
|
14 |
+
# One size for each in feature map
|
15 |
+
SIZES: [[32], [64], [128], [256], [512]]
|
16 |
+
# Three aspect ratios (same for all in feature maps)
|
17 |
+
ASPECT_RATIOS: [[0.5, 1.0, 2.0]]
|
18 |
+
|
19 |
+
RPN:
|
20 |
+
IN_FEATURES: ["p2", "p3", "p4", "p5", "p6"]
|
21 |
+
PRE_NMS_TOPK_TRAIN: 2000 # Per FPN level
|
22 |
+
PRE_NMS_TOPK_TEST: 1000 # Per FPN level
|
23 |
+
# Detectron1 uses 2000 proposals per-batch,
|
24 |
+
# (See "modeling/rpn/rpn_outputs.py" for details of this legacy issue)
|
25 |
+
# which is approximately 1000 proposals per-image since the default
|
26 |
+
# batch size for FPN is 2.
|
27 |
+
POST_NMS_TOPK_TRAIN: 1000
|
28 |
+
POST_NMS_TOPK_TEST: 1000
|
29 |
+
|
30 |
+
ROI_HEADS:
|
31 |
+
NAME: "StandardROIHeads"
|
32 |
+
IN_FEATURES: ["p2", "p3", "p4", "p5"]
|
33 |
+
|
34 |
+
ROI_BOX_HEAD:
|
35 |
+
NAME: "FastRCNNConvFCHead"
|
36 |
+
NUM_FC: 2
|
37 |
+
POOLER_RESOLUTION: 7
|
38 |
+
|
39 |
+
ROI_MASK_HEAD:
|
40 |
+
NAME: "MaskRCNNConvUpsampleHead"
|
41 |
+
NUM_CONV: 4
|
42 |
+
POOLER_RESOLUTION: 14
|
43 |
+
|
44 |
+
INPUT:
|
45 |
+
MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
|
46 |
+
|
47 |
+
SOLVER:
|
48 |
+
CHECKPOINT_PERIOD: 210000
|
49 |
+
|
50 |
+
VERSION: 2
|
models/configs/vanilla_regnet.yaml
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
_BASE_: "Base-RCNN-FPN.yaml"
|
2 |
+
MODEL:
|
3 |
+
PIXEL_STD: [57.375, 57.120, 58.395]
|
4 |
+
BACKBONE:
|
5 |
+
NAME: "build_regnetx_fpn_backbone"
|
6 |
+
META_ARCHITECTURE: "GeneralizedRCNN"
|
7 |
+
WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
|
8 |
+
# WEIGHTS: "./data/VOC-Detection/faster-rcnn/faster_rcnn_R_50_FPN_all_logistic/random_seed_0/model_final.pth"
|
9 |
+
|
10 |
+
# PROPOSAL_GENERATOR:
|
11 |
+
# NAME: "RPNLogistic"
|
12 |
+
FPN:
|
13 |
+
IN_FEATURES: ["s1", "s2", "s3", "s4"]
|
14 |
+
MASK_ON: False
|
15 |
+
RESNETS:
|
16 |
+
DEPTH: 50
|
17 |
+
ROI_HEADS:
|
18 |
+
NAME: "StandardROIHeads"
|
19 |
+
NUM_CLASSES: 10
|
20 |
+
INPUT:
|
21 |
+
MIN_SIZE_TRAIN: (480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800)
|
22 |
+
MIN_SIZE_TEST: 800
|
23 |
+
DATASETS:
|
24 |
+
TRAIN: ('bdd_custom_train',)
|
25 |
+
TEST: ('bdd_custom_val',)
|
26 |
+
SOLVER:
|
27 |
+
IMS_PER_BATCH: 16
|
28 |
+
BASE_LR: 0.02
|
29 |
+
STEPS: (60000, 80000)
|
30 |
+
MAX_ITER: 90000 # 17.4 epochs
|
31 |
+
WARMUP_ITERS: 100
|
32 |
+
DATALOADER:
|
33 |
+
NUM_WORKERS: 8 # Depends on the available memory
|
models/configs/vanilla_resnet.yaml
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
_BASE_: "Base-RCNN-FPN.yaml"
|
2 |
+
MODEL:
|
3 |
+
META_ARCHITECTURE: "GeneralizedRCNN"
|
4 |
+
WEIGHTS: "detectron2://ImageNetPretrained/MSRA/R-50.pkl"
|
5 |
+
# WEIGHTS: "./data/VOC-Detection/faster-rcnn/faster_rcnn_R_50_FPN_all_logistic/random_seed_0/model_final.pth"
|
6 |
+
|
7 |
+
# PROPOSAL_GENERATOR:
|
8 |
+
# NAME: "RPNLogistic"
|
9 |
+
MASK_ON: False
|
10 |
+
RESNETS:
|
11 |
+
DEPTH: 50
|
12 |
+
ROI_HEADS:
|
13 |
+
NAME: "StandardROIHeads"
|
14 |
+
NUM_CLASSES: 10
|
15 |
+
INPUT:
|
16 |
+
MIN_SIZE_TRAIN: (480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800)
|
17 |
+
MIN_SIZE_TEST: 800
|
18 |
+
DATASETS:
|
19 |
+
TRAIN: ('bdd_custom_train',)
|
20 |
+
TEST: ('bdd_custom_val',)
|
21 |
+
SOLVER:
|
22 |
+
IMS_PER_BATCH: 16
|
23 |
+
BASE_LR: 0.02
|
24 |
+
STEPS: (60000, 80000)
|
25 |
+
MAX_ITER: 90000 # 17.4 epochs
|
26 |
+
WARMUP_ITERS: 100
|
27 |
+
DATALOADER:
|
28 |
+
NUM_WORKERS: 8 # Depends on the available memory
|
models/metadata.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
VOC_THING_CLASSES = ['person',
|
2 |
+
'bird',
|
3 |
+
'cat',
|
4 |
+
'cow',
|
5 |
+
'dog',
|
6 |
+
'horse',
|
7 |
+
'sheep',
|
8 |
+
'airplane',
|
9 |
+
'bicycle',
|
10 |
+
'boat',
|
11 |
+
'bus',
|
12 |
+
'car',
|
13 |
+
'motorcycle',
|
14 |
+
'train',
|
15 |
+
'bottle',
|
16 |
+
'chair',
|
17 |
+
'dining table',
|
18 |
+
'potted plant',
|
19 |
+
'couch',
|
20 |
+
'tv',
|
21 |
+
]
|
22 |
+
|
23 |
+
BDD_THING_CLASSES = ['pedestrian',
|
24 |
+
'rider',
|
25 |
+
'car',
|
26 |
+
'truck',
|
27 |
+
'bus',
|
28 |
+
'train',
|
29 |
+
'motorcycle',
|
30 |
+
'bicycle',
|
31 |
+
'traffic light',
|
32 |
+
'traffic sign']
|
33 |
+
KITTI_THING_CLASSES = ["Car", "Pedestrian", "Cyclist", "Van", "Truck", "Tram"]
|
34 |
+
SPEED_THING_CLASSES = ['100kph','120kph','20kph','30kph','40kph','5kph','50kph','60kph','70kph','80kph']
|
35 |
+
NU_THING_CLASSES = ['car','truck','trailer','bus','construction_vehicle','bicycle','motorcycle','pedestrian','traffic_cone','barrier']
|
models/regnet.py
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from .regnet_model import RegNet
|
2 |
+
from .regnet_model import SimpleStem, ResBottleneckBlock
|
3 |
+
|
4 |
+
from detectron2.modeling.backbone.build import BACKBONE_REGISTRY
|
5 |
+
from detectron2.modeling.backbone.fpn import FPN, LastLevelMaxPool
|
6 |
+
|
7 |
+
from detectron2.layers import (
|
8 |
+
Conv2d,
|
9 |
+
DeformConv,
|
10 |
+
FrozenBatchNorm2d,
|
11 |
+
ModulatedDeformConv,
|
12 |
+
ShapeSpec,
|
13 |
+
get_norm,
|
14 |
+
)
|
15 |
+
|
16 |
+
|
17 |
+
# train.cudnn_benchmark = True
|
18 |
+
|
19 |
+
@BACKBONE_REGISTRY.register()
|
20 |
+
def build_regnet_fpn_backbone(cfg, input_shape: ShapeSpec):
|
21 |
+
"""
|
22 |
+
Args:
|
23 |
+
cfg: a detectron2 CfgNode
|
24 |
+
Returns:
|
25 |
+
backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
|
26 |
+
"""
|
27 |
+
bottom_up = RegNet(
|
28 |
+
stem_class=SimpleStem,
|
29 |
+
stem_width=32,
|
30 |
+
block_class=ResBottleneckBlock,
|
31 |
+
depth=22,
|
32 |
+
w_a=31.41,
|
33 |
+
w_0=96,
|
34 |
+
w_m=2.24,
|
35 |
+
group_width=64,
|
36 |
+
se_ratio=0.25,
|
37 |
+
freeze_at=2,
|
38 |
+
norm="FrozenBN",
|
39 |
+
out_features=["s1", "s2", "s3", "s4"],
|
40 |
+
)
|
41 |
+
in_features = cfg.MODEL.FPN.IN_FEATURES
|
42 |
+
out_channels = cfg.MODEL.FPN.OUT_CHANNELS
|
43 |
+
backbone = FPN(
|
44 |
+
bottom_up=bottom_up,
|
45 |
+
in_features=in_features,
|
46 |
+
out_channels=out_channels,
|
47 |
+
norm=cfg.MODEL.FPN.NORM,
|
48 |
+
top_block=LastLevelMaxPool(),
|
49 |
+
fuse_type=cfg.MODEL.FPN.FUSE_TYPE,
|
50 |
+
)
|
51 |
+
return backbone
|
52 |
+
|
53 |
+
@BACKBONE_REGISTRY.register()
|
54 |
+
def build_regnetx_fpn_backbone(cfg, input_shape: ShapeSpec):
|
55 |
+
"""
|
56 |
+
Args:
|
57 |
+
cfg: a detectron2 CfgNode
|
58 |
+
Returns:
|
59 |
+
backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.
|
60 |
+
"""
|
61 |
+
bottom_up = RegNet(
|
62 |
+
stem_class=SimpleStem,
|
63 |
+
stem_width=32,
|
64 |
+
block_class=ResBottleneckBlock,
|
65 |
+
depth=23,
|
66 |
+
w_a=38.65,
|
67 |
+
w_0=96,
|
68 |
+
w_m=2.43,
|
69 |
+
group_width=40,
|
70 |
+
freeze_at=2,
|
71 |
+
norm="FrozenBN",
|
72 |
+
out_features=["s1", "s2", "s3", "s4"],
|
73 |
+
)
|
74 |
+
in_features = cfg.MODEL.FPN.IN_FEATURES
|
75 |
+
out_channels = cfg.MODEL.FPN.OUT_CHANNELS
|
76 |
+
backbone = FPN(
|
77 |
+
bottom_up=bottom_up,
|
78 |
+
in_features=in_features,
|
79 |
+
out_channels=out_channels,
|
80 |
+
norm=cfg.MODEL.FPN.NORM,
|
81 |
+
top_block=LastLevelMaxPool(),
|
82 |
+
fuse_type=cfg.MODEL.FPN.FUSE_TYPE,
|
83 |
+
)
|
84 |
+
return backbone
|
models/regnet_model.py
ADDED
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
2 |
+
"""
|
3 |
+
Implementation of RegNet models from :paper:`dds` and :paper:`scaling`.
|
4 |
+
This code is adapted from https://github.com/facebookresearch/pycls with minimal modifications.
|
5 |
+
Some code duplication exists between RegNet and ResNets (e.g., ResStem) in order to simplify
|
6 |
+
model loading.
|
7 |
+
"""
|
8 |
+
|
9 |
+
import numpy as np
|
10 |
+
from torch import nn
|
11 |
+
|
12 |
+
from detectron2.layers import CNNBlockBase, ShapeSpec, get_norm
|
13 |
+
|
14 |
+
from detectron2.modeling.backbone import Backbone
|
15 |
+
|
16 |
+
__all__ = [
|
17 |
+
"AnyNet",
|
18 |
+
"RegNet",
|
19 |
+
"ResStem",
|
20 |
+
"SimpleStem",
|
21 |
+
"VanillaBlock",
|
22 |
+
"ResBasicBlock",
|
23 |
+
"ResBottleneckBlock",
|
24 |
+
]
|
25 |
+
|
26 |
+
|
27 |
+
def conv2d(w_in, w_out, k, *, stride=1, groups=1, bias=False):
|
28 |
+
"""Helper for building a conv2d layer."""
|
29 |
+
assert k % 2 == 1, "Only odd size kernels supported to avoid padding issues."
|
30 |
+
s, p, g, b = stride, (k - 1) // 2, groups, bias
|
31 |
+
return nn.Conv2d(w_in, w_out, k, stride=s, padding=p, groups=g, bias=b)
|
32 |
+
|
33 |
+
|
34 |
+
def gap2d():
|
35 |
+
"""Helper for building a global average pooling layer."""
|
36 |
+
return nn.AdaptiveAvgPool2d((1, 1))
|
37 |
+
|
38 |
+
|
39 |
+
def pool2d(k, *, stride=1):
|
40 |
+
"""Helper for building a pool2d layer."""
|
41 |
+
assert k % 2 == 1, "Only odd size kernels supported to avoid padding issues."
|
42 |
+
return nn.MaxPool2d(k, stride=stride, padding=(k - 1) // 2)
|
43 |
+
|
44 |
+
|
45 |
+
def init_weights(m):
|
46 |
+
"""Performs ResNet-style weight initialization."""
|
47 |
+
if isinstance(m, nn.Conv2d):
|
48 |
+
# Note that there is no bias due to BN
|
49 |
+
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
|
50 |
+
m.weight.data.normal_(mean=0.0, std=np.sqrt(2.0 / fan_out))
|
51 |
+
elif isinstance(m, nn.BatchNorm2d):
|
52 |
+
m.weight.data.fill_(1.0)
|
53 |
+
m.bias.data.zero_()
|
54 |
+
elif isinstance(m, nn.Linear):
|
55 |
+
m.weight.data.normal_(mean=0.0, std=0.01)
|
56 |
+
m.bias.data.zero_()
|
57 |
+
|
58 |
+
|
59 |
+
class ResStem(CNNBlockBase):
|
60 |
+
"""ResNet stem for ImageNet: 7x7, BN, AF, MaxPool."""
|
61 |
+
|
62 |
+
def __init__(self, w_in, w_out, norm, activation_class):
|
63 |
+
super().__init__(w_in, w_out, 4)
|
64 |
+
self.conv = conv2d(w_in, w_out, 7, stride=2)
|
65 |
+
self.bn = get_norm(norm, w_out)
|
66 |
+
self.af = activation_class()
|
67 |
+
self.pool = pool2d(3, stride=2)
|
68 |
+
|
69 |
+
def forward(self, x):
|
70 |
+
for layer in self.children():
|
71 |
+
x = layer(x)
|
72 |
+
return x
|
73 |
+
|
74 |
+
|
75 |
+
class SimpleStem(CNNBlockBase):
|
76 |
+
"""Simple stem for ImageNet: 3x3, BN, AF."""
|
77 |
+
|
78 |
+
def __init__(self, w_in, w_out, norm, activation_class):
|
79 |
+
super().__init__(w_in, w_out, 2)
|
80 |
+
self.conv = conv2d(w_in, w_out, 3, stride=2)
|
81 |
+
self.bn = get_norm(norm, w_out)
|
82 |
+
self.af = activation_class()
|
83 |
+
|
84 |
+
def forward(self, x):
|
85 |
+
for layer in self.children():
|
86 |
+
x = layer(x)
|
87 |
+
return x
|
88 |
+
|
89 |
+
|
90 |
+
class SE(nn.Module):
|
91 |
+
"""Squeeze-and-Excitation (SE) block: AvgPool, FC, Act, FC, Sigmoid."""
|
92 |
+
|
93 |
+
def __init__(self, w_in, w_se, activation_class):
|
94 |
+
super().__init__()
|
95 |
+
self.avg_pool = gap2d()
|
96 |
+
self.f_ex = nn.Sequential(
|
97 |
+
conv2d(w_in, w_se, 1, bias=True),
|
98 |
+
activation_class(),
|
99 |
+
conv2d(w_se, w_in, 1, bias=True),
|
100 |
+
nn.Sigmoid(),
|
101 |
+
)
|
102 |
+
|
103 |
+
def forward(self, x):
|
104 |
+
return x * self.f_ex(self.avg_pool(x))
|
105 |
+
|
106 |
+
|
107 |
+
class VanillaBlock(CNNBlockBase):
|
108 |
+
"""Vanilla block: [3x3 conv, BN, Relu] x2."""
|
109 |
+
|
110 |
+
def __init__(self, w_in, w_out, stride, norm, activation_class, _params):
|
111 |
+
super().__init__(w_in, w_out, stride)
|
112 |
+
self.a = conv2d(w_in, w_out, 3, stride=stride)
|
113 |
+
self.a_bn = get_norm(norm, w_out)
|
114 |
+
self.a_af = activation_class()
|
115 |
+
self.b = conv2d(w_out, w_out, 3)
|
116 |
+
self.b_bn = get_norm(norm, w_out)
|
117 |
+
self.b_af = activation_class()
|
118 |
+
|
119 |
+
def forward(self, x):
|
120 |
+
for layer in self.children():
|
121 |
+
x = layer(x)
|
122 |
+
return x
|
123 |
+
|
124 |
+
|
125 |
+
class BasicTransform(nn.Module):
|
126 |
+
"""Basic transformation: [3x3 conv, BN, Relu] x2."""
|
127 |
+
|
128 |
+
def __init__(self, w_in, w_out, stride, norm, activation_class, _params):
|
129 |
+
super().__init__()
|
130 |
+
self.a = conv2d(w_in, w_out, 3, stride=stride)
|
131 |
+
self.a_bn = get_norm(norm, w_out)
|
132 |
+
self.a_af = activation_class()
|
133 |
+
self.b = conv2d(w_out, w_out, 3)
|
134 |
+
self.b_bn = get_norm(norm, w_out)
|
135 |
+
self.b_bn.final_bn = True
|
136 |
+
|
137 |
+
def forward(self, x):
|
138 |
+
for layer in self.children():
|
139 |
+
x = layer(x)
|
140 |
+
return x
|
141 |
+
|
142 |
+
|
143 |
+
class ResBasicBlock(CNNBlockBase):
|
144 |
+
"""Residual basic block: x + f(x), f = basic transform."""
|
145 |
+
|
146 |
+
def __init__(self, w_in, w_out, stride, norm, activation_class, params):
|
147 |
+
super().__init__(w_in, w_out, stride)
|
148 |
+
self.proj, self.bn = None, None
|
149 |
+
if (w_in != w_out) or (stride != 1):
|
150 |
+
self.proj = conv2d(w_in, w_out, 1, stride=stride)
|
151 |
+
self.bn = get_norm(norm, w_out)
|
152 |
+
self.f = BasicTransform(w_in, w_out, stride, norm, activation_class, params)
|
153 |
+
self.af = activation_class()
|
154 |
+
|
155 |
+
def forward(self, x):
|
156 |
+
x_p = self.bn(self.proj(x)) if self.proj else x
|
157 |
+
return self.af(x_p + self.f(x))
|
158 |
+
|
159 |
+
|
160 |
+
class BottleneckTransform(nn.Module):
|
161 |
+
"""Bottleneck transformation: 1x1, 3x3 [+SE], 1x1."""
|
162 |
+
|
163 |
+
def __init__(self, w_in, w_out, stride, norm, activation_class, params):
|
164 |
+
super().__init__()
|
165 |
+
w_b = int(round(w_out * params["bot_mul"]))
|
166 |
+
w_se = int(round(w_in * params["se_r"]))
|
167 |
+
groups = w_b // params["group_w"]
|
168 |
+
self.a = conv2d(w_in, w_b, 1)
|
169 |
+
self.a_bn = get_norm(norm, w_b)
|
170 |
+
self.a_af = activation_class()
|
171 |
+
self.b = conv2d(w_b, w_b, 3, stride=stride, groups=groups)
|
172 |
+
self.b_bn = get_norm(norm, w_b)
|
173 |
+
self.b_af = activation_class()
|
174 |
+
self.se = SE(w_b, w_se, activation_class) if w_se else None
|
175 |
+
self.c = conv2d(w_b, w_out, 1)
|
176 |
+
self.c_bn = get_norm(norm, w_out)
|
177 |
+
self.c_bn.final_bn = True
|
178 |
+
|
179 |
+
def forward(self, x):
|
180 |
+
for layer in self.children():
|
181 |
+
x = layer(x)
|
182 |
+
return x
|
183 |
+
|
184 |
+
|
185 |
+
class ResBottleneckBlock(CNNBlockBase):
|
186 |
+
"""Residual bottleneck block: x + f(x), f = bottleneck transform."""
|
187 |
+
|
188 |
+
def __init__(self, w_in, w_out, stride, norm, activation_class, params):
|
189 |
+
super().__init__(w_in, w_out, stride)
|
190 |
+
self.proj, self.bn = None, None
|
191 |
+
if (w_in != w_out) or (stride != 1):
|
192 |
+
self.proj = conv2d(w_in, w_out, 1, stride=stride)
|
193 |
+
self.bn = get_norm(norm, w_out)
|
194 |
+
self.f = BottleneckTransform(w_in, w_out, stride, norm, activation_class, params)
|
195 |
+
self.af = activation_class()
|
196 |
+
|
197 |
+
def forward(self, x):
|
198 |
+
x_p = self.bn(self.proj(x)) if self.proj else x
|
199 |
+
return self.af(x_p + self.f(x))
|
200 |
+
|
201 |
+
|
202 |
+
class AnyStage(nn.Module):
|
203 |
+
"""AnyNet stage (sequence of blocks w/ the same output shape)."""
|
204 |
+
|
205 |
+
def __init__(self, w_in, w_out, stride, d, block_class, norm, activation_class, params):
|
206 |
+
super().__init__()
|
207 |
+
for i in range(d):
|
208 |
+
block = block_class(w_in, w_out, stride, norm, activation_class, params)
|
209 |
+
self.add_module("b{}".format(i + 1), block)
|
210 |
+
stride, w_in = 1, w_out
|
211 |
+
|
212 |
+
def forward(self, x):
|
213 |
+
for block in self.children():
|
214 |
+
x = block(x)
|
215 |
+
return x
|
216 |
+
|
217 |
+
|
218 |
+
class AnyNet(Backbone):
|
219 |
+
"""AnyNet model. See :paper:`dds`."""
|
220 |
+
|
221 |
+
def __init__(
|
222 |
+
self,
|
223 |
+
*,
|
224 |
+
stem_class,
|
225 |
+
stem_width,
|
226 |
+
block_class,
|
227 |
+
depths,
|
228 |
+
widths,
|
229 |
+
group_widths,
|
230 |
+
strides,
|
231 |
+
bottleneck_ratios,
|
232 |
+
se_ratio,
|
233 |
+
activation_class,
|
234 |
+
freeze_at=0,
|
235 |
+
norm="BN",
|
236 |
+
out_features=None,
|
237 |
+
):
|
238 |
+
"""
|
239 |
+
Args:
|
240 |
+
stem_class (callable): A callable taking 4 arguments (channels in, channels out,
|
241 |
+
normalization, callable returning an activation function) that returns another
|
242 |
+
callable implementing the stem module.
|
243 |
+
stem_width (int): The number of output channels that the stem produces.
|
244 |
+
block_class (callable): A callable taking 6 arguments (channels in, channels out,
|
245 |
+
stride, normalization, callable returning an activation function, a dict of
|
246 |
+
block-specific parameters) that returns another callable implementing the repeated
|
247 |
+
block module.
|
248 |
+
depths (list[int]): Number of blocks in each stage.
|
249 |
+
widths (list[int]): For each stage, the number of output channels of each block.
|
250 |
+
group_widths (list[int]): For each stage, the number of channels per group in group
|
251 |
+
convolution, if the block uses group convolution.
|
252 |
+
strides (list[int]): The stride that each network stage applies to its input.
|
253 |
+
bottleneck_ratios (list[float]): For each stage, the ratio of the number of bottleneck
|
254 |
+
channels to the number of block input channels (or, equivalently, output channels),
|
255 |
+
if the block uses a bottleneck.
|
256 |
+
se_ratio (float): The ratio of the number of channels used inside the squeeze-excitation
|
257 |
+
(SE) module to it number of input channels, if SE the block uses SE.
|
258 |
+
activation_class (callable): A callable taking no arguments that returns another
|
259 |
+
callable implementing an activation function.
|
260 |
+
freeze_at (int): The number of stages at the beginning to freeze.
|
261 |
+
see :meth:`freeze` for detailed explanation.
|
262 |
+
norm (str or callable): normalization for all conv layers.
|
263 |
+
See :func:`layers.get_norm` for supported format.
|
264 |
+
out_features (list[str]): name of the layers whose outputs should
|
265 |
+
be returned in forward. RegNet's use "stem" and "s1", "s2", etc for the stages after
|
266 |
+
the stem. If None, will return the output of the last layer.
|
267 |
+
"""
|
268 |
+
super().__init__()
|
269 |
+
self.stem = stem_class(3, stem_width, norm, activation_class)
|
270 |
+
|
271 |
+
current_stride = self.stem.stride
|
272 |
+
self._out_feature_strides = {"stem": current_stride}
|
273 |
+
self._out_feature_channels = {"stem": self.stem.out_channels}
|
274 |
+
self.stages_and_names = []
|
275 |
+
prev_w = stem_width
|
276 |
+
|
277 |
+
for i, (d, w, s, b, g) in enumerate(
|
278 |
+
zip(depths, widths, strides, bottleneck_ratios, group_widths)
|
279 |
+
):
|
280 |
+
params = {"bot_mul": b, "group_w": g, "se_r": se_ratio}
|
281 |
+
stage = AnyStage(prev_w, w, s, d, block_class, norm, activation_class, params)
|
282 |
+
name = "s{}".format(i + 1)
|
283 |
+
self.add_module(name, stage)
|
284 |
+
self.stages_and_names.append((stage, name))
|
285 |
+
self._out_feature_strides[name] = current_stride = int(
|
286 |
+
current_stride * np.prod([k.stride for k in stage.children()])
|
287 |
+
)
|
288 |
+
self._out_feature_channels[name] = list(stage.children())[-1].out_channels
|
289 |
+
prev_w = w
|
290 |
+
|
291 |
+
self.apply(init_weights)
|
292 |
+
|
293 |
+
if out_features is None:
|
294 |
+
out_features = [name]
|
295 |
+
self._out_features = out_features
|
296 |
+
assert len(self._out_features)
|
297 |
+
children = [x[0] for x in self.named_children()]
|
298 |
+
for out_feature in self._out_features:
|
299 |
+
assert out_feature in children, "Available children: {} does not include {}".format(
|
300 |
+
", ".join(children), out_feature
|
301 |
+
)
|
302 |
+
self.freeze(freeze_at)
|
303 |
+
|
304 |
+
def forward(self, x):
|
305 |
+
"""
|
306 |
+
Args:
|
307 |
+
x: Tensor of shape (N,C,H,W). H, W must be a multiple of ``self.size_divisibility``.
|
308 |
+
Returns:
|
309 |
+
dict[str->Tensor]: names and the corresponding features
|
310 |
+
"""
|
311 |
+
assert x.dim() == 4, f"Model takes an input of shape (N, C, H, W). Got {x.shape} instead!"
|
312 |
+
outputs = {}
|
313 |
+
x = self.stem(x)
|
314 |
+
if "stem" in self._out_features:
|
315 |
+
outputs["stem"] = x
|
316 |
+
for stage, name in self.stages_and_names:
|
317 |
+
x = stage(x)
|
318 |
+
if name in self._out_features:
|
319 |
+
outputs[name] = x
|
320 |
+
return outputs
|
321 |
+
|
322 |
+
def output_shape(self):
|
323 |
+
return {
|
324 |
+
name: ShapeSpec(
|
325 |
+
channels=self._out_feature_channels[name], stride=self._out_feature_strides[name]
|
326 |
+
)
|
327 |
+
for name in self._out_features
|
328 |
+
}
|
329 |
+
|
330 |
+
def freeze(self, freeze_at=0):
|
331 |
+
"""
|
332 |
+
Freeze the first several stages of the model. Commonly used in fine-tuning.
|
333 |
+
Layers that produce the same feature map spatial size are defined as one
|
334 |
+
"stage" by :paper:`FPN`.
|
335 |
+
Args:
|
336 |
+
freeze_at (int): number of stages to freeze.
|
337 |
+
`1` means freezing the stem. `2` means freezing the stem and
|
338 |
+
one residual stage, etc.
|
339 |
+
Returns:
|
340 |
+
nn.Module: this model itself
|
341 |
+
"""
|
342 |
+
if freeze_at >= 1:
|
343 |
+
self.stem.freeze()
|
344 |
+
for idx, (stage, _) in enumerate(self.stages_and_names, start=2):
|
345 |
+
if freeze_at >= idx:
|
346 |
+
for block in stage.children():
|
347 |
+
block.freeze()
|
348 |
+
return self
|
349 |
+
|
350 |
+
|
351 |
+
def adjust_block_compatibility(ws, bs, gs):
|
352 |
+
"""Adjusts the compatibility of widths, bottlenecks, and groups."""
|
353 |
+
assert len(ws) == len(bs) == len(gs)
|
354 |
+
assert all(w > 0 and b > 0 and g > 0 for w, b, g in zip(ws, bs, gs))
|
355 |
+
vs = [int(max(1, w * b)) for w, b in zip(ws, bs)]
|
356 |
+
gs = [int(min(g, v)) for g, v in zip(gs, vs)]
|
357 |
+
ms = [np.lcm(g, b) if b > 1 else g for g, b in zip(gs, bs)]
|
358 |
+
vs = [max(m, int(round(v / m) * m)) for v, m in zip(vs, ms)]
|
359 |
+
ws = [int(v / b) for v, b in zip(vs, bs)]
|
360 |
+
assert all(w * b % g == 0 for w, b, g in zip(ws, bs, gs))
|
361 |
+
return ws, bs, gs
|
362 |
+
|
363 |
+
|
364 |
+
def generate_regnet_parameters(w_a, w_0, w_m, d, q=8):
|
365 |
+
"""Generates per stage widths and depths from RegNet parameters."""
|
366 |
+
assert w_a >= 0 and w_0 > 0 and w_m > 1 and w_0 % q == 0
|
367 |
+
# Generate continuous per-block ws
|
368 |
+
ws_cont = np.arange(d) * w_a + w_0
|
369 |
+
# Generate quantized per-block ws
|
370 |
+
ks = np.round(np.log(ws_cont / w_0) / np.log(w_m))
|
371 |
+
ws_all = w_0 * np.power(w_m, ks)
|
372 |
+
ws_all = np.round(np.divide(ws_all, q)).astype(int) * q
|
373 |
+
# Generate per stage ws and ds (assumes ws_all are sorted)
|
374 |
+
ws, ds = np.unique(ws_all, return_counts=True)
|
375 |
+
# Compute number of actual stages and total possible stages
|
376 |
+
num_stages, total_stages = len(ws), ks.max() + 1
|
377 |
+
# Convert numpy arrays to lists and return
|
378 |
+
ws, ds, ws_all, ws_cont = (x.tolist() for x in (ws, ds, ws_all, ws_cont))
|
379 |
+
return ws, ds, num_stages, total_stages, ws_all, ws_cont
|
380 |
+
|
381 |
+
|
382 |
+
class RegNet(AnyNet):
|
383 |
+
"""RegNet model. See :paper:`dds`."""
|
384 |
+
|
385 |
+
def __init__(
|
386 |
+
self,
|
387 |
+
*,
|
388 |
+
stem_class,
|
389 |
+
stem_width,
|
390 |
+
block_class,
|
391 |
+
depth,
|
392 |
+
w_a,
|
393 |
+
w_0,
|
394 |
+
w_m,
|
395 |
+
group_width,
|
396 |
+
stride=2,
|
397 |
+
bottleneck_ratio=1.0,
|
398 |
+
se_ratio=0.0,
|
399 |
+
activation_class=None,
|
400 |
+
freeze_at=0,
|
401 |
+
norm="BN",
|
402 |
+
out_features=None,
|
403 |
+
):
|
404 |
+
"""
|
405 |
+
Build a RegNet from the parameterization described in :paper:`dds` Section 3.3.
|
406 |
+
Args:
|
407 |
+
See :class:`AnyNet` for arguments that are not listed here.
|
408 |
+
depth (int): Total number of blocks in the RegNet.
|
409 |
+
w_a (float): Factor by which block width would increase prior to quantizing block widths
|
410 |
+
by stage. See :paper:`dds` Section 3.3.
|
411 |
+
w_0 (int): Initial block width. See :paper:`dds` Section 3.3.
|
412 |
+
w_m (float): Parameter controlling block width quantization.
|
413 |
+
See :paper:`dds` Section 3.3.
|
414 |
+
group_width (int): Number of channels per group in group convolution, if the block uses
|
415 |
+
group convolution.
|
416 |
+
bottleneck_ratio (float): The ratio of the number of bottleneck channels to the number
|
417 |
+
of block input channels (or, equivalently, output channels), if the block uses a
|
418 |
+
bottleneck.
|
419 |
+
stride (int): The stride that each network stage applies to its input.
|
420 |
+
"""
|
421 |
+
ws, ds = generate_regnet_parameters(w_a, w_0, w_m, depth)[0:2]
|
422 |
+
ss = [stride for _ in ws]
|
423 |
+
bs = [bottleneck_ratio for _ in ws]
|
424 |
+
gs = [group_width for _ in ws]
|
425 |
+
ws, bs, gs = adjust_block_compatibility(ws, bs, gs)
|
426 |
+
|
427 |
+
def default_activation_class():
|
428 |
+
return nn.ReLU(inplace=True)
|
429 |
+
|
430 |
+
super().__init__(
|
431 |
+
stem_class=stem_class,
|
432 |
+
stem_width=stem_width,
|
433 |
+
block_class=block_class,
|
434 |
+
depths=ds,
|
435 |
+
widths=ws,
|
436 |
+
strides=ss,
|
437 |
+
group_widths=gs,
|
438 |
+
bottleneck_ratios=bs,
|
439 |
+
se_ratio=se_ratio,
|
440 |
+
activation_class=default_activation_class
|
441 |
+
if activation_class is None
|
442 |
+
else activation_class,
|
443 |
+
freeze_at=freeze_at,
|
444 |
+
norm=norm,
|
445 |
+
out_features=out_features,
|
446 |
+
)
|
models/weights/model_final_resnet_kitti.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e577cbf96b80d227eb4959271de68c69f72d653a7ed406cda5c17c4b83bfd388
|
3 |
+
size 330233039
|
monitors/kitti/resnet/kmeans/1.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:fc623c2a3c64b52fc93d099f365352d4d206d8f94298f2cee7c40c94ddba7b8a
|
3 |
+
size 136810565
|
monitors/kitti/resnet/kmeans/4.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:d88075b898e5bcf962ff03babe51150db2e5b8287777c878e2e4854134b47d39
|
3 |
+
size 152229171
|
monitors/kitti/resnet/kmeans/5.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:84b571641d7207d14e31390a465f3bfbe494da90deac82d9e68cd6d3574f376b
|
3 |
+
size 157291017
|
monitors/kitti/resnet/kmeans/6.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:6d85e0d97dccfba7f618e87813db07514d2dacf8ab5655a195da0ad5f2091856
|
3 |
+
size 160503921
|
requirements.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
torch
|
2 |
+
detectron2
|
3 |
+
numpy
|
4 |
+
opencv-python
|
5 |
+
h5py
|
6 |
+
pickle
|
7 |
+
gradio
|
8 |
+
fiftyone
|
9 |
+
tqdm
|
10 |
+
matplotlib
|
11 |
+
grad-cam
|
12 |
+
scikit-learn
|
runtime_monitors/Monitor.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
# -*- coding: utf-8 -*-
|
3 |
+
|
4 |
+
from abstractions import *
|
5 |
+
import pickle
|
6 |
+
import numpy as np
|
7 |
+
|
8 |
+
class Monitor(object):
|
9 |
+
|
10 |
+
def __init__(self, good_ref=None):
|
11 |
+
# self.abs_type = abs_type
|
12 |
+
self.good_ref = good_ref
|
13 |
+
|
14 |
+
|
15 |
+
def set_reference(self, good_ref):
|
16 |
+
self.good_ref = good_ref
|
17 |
+
|
18 |
+
# def get_identity(self):
|
19 |
+
# print("Monitor for network:" + self.netName + "class: " + str(self.classification) + "at layer " + str(self.location))
|
20 |
+
|
21 |
+
|
22 |
+
def make_verdicts(self, features):
|
23 |
+
if len(self.good_ref):
|
24 |
+
verdicts = ref_query(features, self.good_ref)
|
25 |
+
else:
|
26 |
+
raise RuntimeError("No reference exists!")
|
27 |
+
return verdicts
|
28 |
+
|
29 |
+
# def make_verdicts_delta(self, features, delta):
|
30 |
+
# if len(self.good_ref):
|
31 |
+
# verdicts = ref_query_delta(features, self.good_ref, delta)
|
32 |
+
# else:
|
33 |
+
# raise RuntimeError("No reference exists!")
|
34 |
+
# return verdicts
|
35 |
+
|
36 |
+
|
37 |
+
def ref_query(features, reference):
|
38 |
+
query_results = [boxes_query(x, reference) for x in features]
|
39 |
+
return query_results
|
40 |
+
|
41 |
+
# def ref_query_delta(features, reference, delta):
|
42 |
+
# query_results = [boxes_query_delta(x, reference, delta) for x in features]
|
43 |
+
# return query_results
|
44 |
+
|
45 |
+
|
46 |
+
# def query_infusion(in_good_ref, in_bad_ref):
|
47 |
+
# if len(in_good_ref) == len(in_bad_ref): #0: acceptance (true, false), 1: rejection (false, true or false), 2: uncertainty (true, true)
|
48 |
+
# verdicts = np.zeros(len(in_good_ref), dtype=int)
|
49 |
+
# for i in range(len(in_good_ref)):
|
50 |
+
# if not in_good_ref[i]:
|
51 |
+
# verdicts[i] = 1
|
52 |
+
# elif in_bad_ref[i]:
|
53 |
+
# verdicts[i] = 2
|
54 |
+
# return verdicts
|
55 |
+
# else:
|
56 |
+
# print("Error: IllegalArgument")
|
runtime_monitors/__init__.py
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
from .Monitor import *
|
2 |
+
|
train_feats/kitti/resnet/kitti-train_feats_tp_dict.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:59624dd3527292e3cde0107ecc38f7fc85bb96bbe3b9a3458f6a7dab25b2fe76
|
3 |
+
size 126263743
|
util/Clustering.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python
|
2 |
+
# coding: utf-8
|
3 |
+
|
4 |
+
import numpy as np
|
5 |
+
import pandas as pd
|
6 |
+
import time
|
7 |
+
import os.path
|
8 |
+
# from sklearnex import patch_sklearn, unpatch_sklearn
|
9 |
+
# patch_sklearn()
|
10 |
+
from sklearn.cluster import KMeans
|
11 |
+
from sklearn.cluster import MeanShift
|
12 |
+
|
13 |
+
|
14 |
+
# values: a two-dimensional array, m number of n-dimensional vectors to be clustered;
|
15 |
+
def modified_kmeans_cluster(values_to_cluster, threshold, k_start, n_clusters=None):
|
16 |
+
if n_clusters is not None:
|
17 |
+
kmeans = KMeans(n_clusters=n_clusters, n_init="auto", random_state=0).fit(values_to_cluster)
|
18 |
+
return kmeans.labels_
|
19 |
+
else:
|
20 |
+
n_clusters = k_start
|
21 |
+
n_values = len(values_to_cluster)
|
22 |
+
assert n_values > 0
|
23 |
+
kmeans = KMeans(n_clusters=n_clusters, n_init="auto", random_state=0).fit(values_to_cluster)
|
24 |
+
inertias = [kmeans.inertia_]
|
25 |
+
while n_values > n_clusters:
|
26 |
+
n_clusters_new = n_clusters + 1
|
27 |
+
kmeans_new = KMeans(n_clusters=n_clusters_new, n_init="auto", random_state=0).fit(values_to_cluster)
|
28 |
+
inertias.append(kmeans_new.inertia_)
|
29 |
+
if terminate_clustering(inertias, threshold):
|
30 |
+
break
|
31 |
+
kmeans = kmeans_new
|
32 |
+
n_clusters += 1
|
33 |
+
return kmeans.labels_
|
34 |
+
|
35 |
+
|
36 |
+
def terminate_clustering(inertias, threshold):
|
37 |
+
# method: compute relative improvement toward previous step
|
38 |
+
assert len(inertias) > 1
|
39 |
+
improvement = 1 - (inertias[-1] / inertias[-2])
|
40 |
+
return improvement < threshold
|
41 |
+
|
42 |
+
|
43 |
+
|
44 |
+
|
45 |
+
def cluster_existed_features(network_folder_path, classes, layers_indexes, taus):
|
46 |
+
appendixes = ["_correctly_classified_features.csv", "_incorrectly_classified_features.csv"]
|
47 |
+
product = ((i, y, appendix) for i in layers_indexes for y in classes for appendix in appendixes)
|
48 |
+
|
49 |
+
for i, y, appendix in product:
|
50 |
+
start_time = time.time()
|
51 |
+
# load data for class y at layer minus i
|
52 |
+
features_file_path = network_folder_path +"Layer_minus_" + str(i) + "/class_" + str(y) + appendix
|
53 |
+
df = pd.read_csv(features_file_path)
|
54 |
+
index_values = df["index"].to_numpy()
|
55 |
+
values_to_cluster = df[df.columns[3:]].to_numpy()
|
56 |
+
|
57 |
+
if len(values_to_cluster):
|
58 |
+
# specify path and then load existing clustering results
|
59 |
+
k_and_taus = dict()
|
60 |
+
taus_existed = []
|
61 |
+
clustering_results = pd.DataFrame(df, columns=["index", "true_label", "pred_label"])
|
62 |
+
clustering_results_path = network_folder_path + "Layer_minus_" + str(i) + "/clustering_results_class_" + str(y) + appendix
|
63 |
+
|
64 |
+
if os.path.exists(clustering_results_path):
|
65 |
+
clustering_results = pd.read_csv(clustering_results_path)
|
66 |
+
for col in clustering_results.columns[3:]:
|
67 |
+
k_and_taus[col] = clustering_results[col].max() + 1
|
68 |
+
|
69 |
+
# update the existing values of tau
|
70 |
+
taus_existed = [float(key) for key in k_and_taus.keys()]
|
71 |
+
|
72 |
+
# remove existing tau from list existed_taus
|
73 |
+
taus_new = [tau for tau in taus if tau not in taus_existed]
|
74 |
+
|
75 |
+
# iterate every tau to cluster the given data
|
76 |
+
for tau in taus_new:
|
77 |
+
# fix starting searching point
|
78 |
+
k_start = 1
|
79 |
+
bigger_taus = [x for x in taus_existed if x > tau]
|
80 |
+
if len(bigger_taus):
|
81 |
+
tau_closest = min(bigger_taus)
|
82 |
+
k_start = k_and_taus[str(tau_closest)]
|
83 |
+
|
84 |
+
# start to cluster
|
85 |
+
cluster_labels = modified_kmeans_cluster(values_to_cluster, tau, k_start)
|
86 |
+
clustering_results[str(tau)] = cluster_labels
|
87 |
+
taus_existed.append(tau)
|
88 |
+
k_and_taus[str(tau)] = max(cluster_labels) + 1
|
89 |
+
|
90 |
+
clustering_results.to_csv(clustering_results_path, index = False)
|
91 |
+
elapsed_time = time.time() - start_time
|
92 |
+
print("file:" + "Layer_minus_" + str(i) + "_class_" + str(y) + appendix + ",", "lasting time:", elapsed_time, "seconds")
|
93 |
+
|
94 |
+
|
95 |
+
def features_clustering(features, taus, nb_clusters):
|
96 |
+
start_time = time.time()
|
97 |
+
values_to_cluster = features
|
98 |
+
|
99 |
+
if len(values_to_cluster):
|
100 |
+
# specify path and then load existing clustering results
|
101 |
+
k_and_taus = dict()
|
102 |
+
taus_existed = []
|
103 |
+
|
104 |
+
|
105 |
+
# if os.path.exists(clustering_results_path):
|
106 |
+
# clustering_results = pd.read_csv(clustering_results_path)
|
107 |
+
# for col in clustering_results.columns[3:]:
|
108 |
+
# k_and_taus[col] = clustering_results[col].max() + 1
|
109 |
+
# else:
|
110 |
+
# clustering_results = pd.DataFrame()
|
111 |
+
|
112 |
+
# update the existing values of tau
|
113 |
+
taus_existed = [float(key) for key in k_and_taus.keys()]
|
114 |
+
|
115 |
+
# remove existing tau from list existed_taus
|
116 |
+
taus_new = [tau for tau in taus if tau not in taus_existed]
|
117 |
+
clustering_results = dict()
|
118 |
+
# iterate every tau to cluster the given data
|
119 |
+
for tau in taus_new:
|
120 |
+
# fix starting searching point
|
121 |
+
k_start = 1
|
122 |
+
bigger_taus = [x for x in taus_existed if x > tau]
|
123 |
+
if len(bigger_taus):
|
124 |
+
tau_closest = min(bigger_taus)
|
125 |
+
k_start = k_and_taus[str(tau_closest)]
|
126 |
+
|
127 |
+
# start to cluster
|
128 |
+
cluster_labels = modified_kmeans_cluster(values_to_cluster, tau, k_start, nb_clusters)
|
129 |
+
clustering_results[str(tau)] = cluster_labels
|
130 |
+
taus_existed.append(tau)
|
131 |
+
k_and_taus[str(tau)] = max(cluster_labels) + 1
|
132 |
+
|
133 |
+
# clustering_results.to_csv(clustering_results_path, index = False)
|
134 |
+
elapsed_time = time.time() - start_time
|
135 |
+
# print("clustering time:", elapsed_time, "seconds")
|
136 |
+
return clustering_results
|
137 |
+
|
138 |
+
|
util/Monitor_construction.py
ADDED
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import numpy as np
|
3 |
+
import pandas as pd
|
4 |
+
import pickle
|
5 |
+
import os
|
6 |
+
import sys
|
7 |
+
# append the path of the parent directory
|
8 |
+
sys.path.append("..")
|
9 |
+
|
10 |
+
from abstractions import *
|
11 |
+
from runtime_monitors import *
|
12 |
+
|
13 |
+
|
14 |
+
# def monitors_offline_construction(network_name, network_folder_path, classes, layers_indexes, taus):
|
15 |
+
# appendixes = ["_correctly_classified_features.csv", "_incorrectly_classified_features.csv"]
|
16 |
+
# product = ((i, y) for i in layers_indexes for y in classes)
|
17 |
+
|
18 |
+
# for i, y in product:
|
19 |
+
|
20 |
+
# # load obtained features to creat reference
|
21 |
+
# path_bad_features = network_folder_path +"Layer_minus_" + str(i) + "/class_" + str(y) + appendixes[1]
|
22 |
+
# path_good_features = network_folder_path +"Layer_minus_" + str(i) + "/class_" + str(y) + appendixes[0]
|
23 |
+
|
24 |
+
# bad_feat_clustering_results = []
|
25 |
+
# good_feat_clustering_results = []
|
26 |
+
|
27 |
+
|
28 |
+
# if os.path.exists(path_bad_features):
|
29 |
+
# df_bad_features = pd.read_csv(path_bad_features)
|
30 |
+
# bad_features_to_cluster = df_bad_features[df_bad_features.columns[3:]].to_numpy()
|
31 |
+
# bad_features_index = df_bad_features["index"].to_numpy()
|
32 |
+
|
33 |
+
# # load clustering results to partition the features
|
34 |
+
# bad_feat_clustering_results_path = network_folder_path + "Layer_minus_" + str(i) + "/clustering_results_class_" + str(y) + appendixes[1]
|
35 |
+
# if os.path.exists(bad_feat_clustering_results_path):
|
36 |
+
# bad_feat_clustering_results = pd.read_csv(bad_feat_clustering_results_path)
|
37 |
+
|
38 |
+
# if os.path.exists(path_good_features):
|
39 |
+
# df_good_features = pd.read_csv(path_good_features)
|
40 |
+
# good_features_to_cluster = df_good_features[df_good_features.columns[3:]].to_numpy()
|
41 |
+
# good_features_index = df_good_features["index"].to_numpy()
|
42 |
+
|
43 |
+
|
44 |
+
# # load clustering results to partition the features
|
45 |
+
# good_feat_clustering_results_path = network_folder_path + "Layer_minus_" + str(i) + "/clustering_results_class_" + str(y) + appendixes[0]
|
46 |
+
# n_dim = good_features_to_cluster.shape[1]
|
47 |
+
# if os.path.exists(good_feat_clustering_results_path):
|
48 |
+
# good_feat_clustering_results = pd.read_csv(good_feat_clustering_results_path)
|
49 |
+
|
50 |
+
# for tau in taus:
|
51 |
+
# good_loc_boxes = []
|
52 |
+
# bad_loc_boxes = []
|
53 |
+
|
54 |
+
# if len(bad_feat_clustering_results):
|
55 |
+
# # load clustering result related to tau
|
56 |
+
# bad_feat_clustering_result = bad_feat_clustering_results[str(tau)]
|
57 |
+
# # determine the labels of clusters
|
58 |
+
# bad_num_clusters = np.amax(bad_feat_clustering_result) + 1
|
59 |
+
# bad_clustering_labels = np.arange(bad_num_clusters)
|
60 |
+
|
61 |
+
# # extract the indices of vectors in a cluster
|
62 |
+
# bad_clusters_indices = []
|
63 |
+
# for k in bad_clustering_labels:
|
64 |
+
# bad_indices_cluster_k, = np.where(bad_feat_clustering_result == k)
|
65 |
+
# bad_clusters_indices.append(bad_indices_cluster_k)
|
66 |
+
|
67 |
+
# # creat local box for each cluster
|
68 |
+
# bad_loc_boxes = [Box() for i in bad_clustering_labels]
|
69 |
+
# for j in range(len(bad_loc_boxes)):
|
70 |
+
# bad_points_j = [(bad_features_index[i], bad_features_to_cluster[i]) for i in bad_clusters_indices[j]]
|
71 |
+
# bad_loc_boxes[j].build(n_dim, bad_points_j)
|
72 |
+
|
73 |
+
|
74 |
+
# if len(good_feat_clustering_results):
|
75 |
+
# # load clustering result related to tau
|
76 |
+
# good_feat_clustering_result = good_feat_clustering_results[str(tau)]
|
77 |
+
# # determine the labels of clusters
|
78 |
+
# good_num_clusters = np.amax(good_feat_clustering_result) + 1
|
79 |
+
# good_clustering_labels = np.arange(good_num_clusters)
|
80 |
+
|
81 |
+
# # extract the indices of vectors in a cluster
|
82 |
+
# good_clusters_indices = []
|
83 |
+
# for k in good_clustering_labels:
|
84 |
+
# good_indices_cluster_k, = np.where(good_feat_clustering_result == k)
|
85 |
+
# good_clusters_indices.append(good_indices_cluster_k)
|
86 |
+
|
87 |
+
# # creat local box for each cluster
|
88 |
+
# good_loc_boxes = [Box() for i in good_clustering_labels]
|
89 |
+
# for j in range(len(good_loc_boxes)):
|
90 |
+
# good_points_j = [(good_features_index[i], good_features_to_cluster[i]) for i in good_clusters_indices[j]]
|
91 |
+
# good_loc_boxes[j].build(n_dim, good_points_j)
|
92 |
+
|
93 |
+
# # creat the monitor for class y at layer i
|
94 |
+
# monitor_y_i = Monitor("Box", network_name, y, i, good_ref=good_loc_boxes, bad_ref=bad_loc_boxes)
|
95 |
+
# # save the created monitor
|
96 |
+
# monitor_stored_folder_path = network_folder_path + "Monitors/"
|
97 |
+
# if not os.path.exists(monitor_stored_folder_path):
|
98 |
+
# os.makedirs(monitor_stored_folder_path)
|
99 |
+
# monitor_stored_path = monitor_stored_folder_path + network_name + "_monitor_for_class_" + str(y) + "_at_layer_minus_" + str(i) + "_tau_" + str(tau) + ".pkl"
|
100 |
+
# with open(monitor_stored_path, 'wb') as f:
|
101 |
+
# pickle.dump(monitor_y_i, f)
|
102 |
+
|
103 |
+
|
104 |
+
def monitor_construction_from_features(features, taus, clustering_results, class_name, monitor_saving_folder):
|
105 |
+
# if os.path.exists(clustering_result_path):
|
106 |
+
# clustering_results = pd.read_csv(clustering_result_path)
|
107 |
+
# else:
|
108 |
+
# raise RuntimeError("Please partition your data first!")
|
109 |
+
|
110 |
+
for tau in taus:
|
111 |
+
loc_boxes = []
|
112 |
+
|
113 |
+
if len(features):
|
114 |
+
n_dim = features.shape[1]
|
115 |
+
|
116 |
+
# load clustering result related to tau
|
117 |
+
clustering_result = clustering_results[str(tau)]
|
118 |
+
# determine the labels of clusters
|
119 |
+
num_clusters = np.amax(clustering_result) + 1
|
120 |
+
clustering_labels = np.arange(num_clusters)
|
121 |
+
|
122 |
+
# extract the indices of vectors in a cluster
|
123 |
+
clusters_indices = []
|
124 |
+
for k in clustering_labels:
|
125 |
+
indices_cluster_k, = np.where(clustering_result == k)
|
126 |
+
clusters_indices.append(indices_cluster_k)
|
127 |
+
|
128 |
+
# creat local box for each cluster
|
129 |
+
loc_boxes = [Box() for i in clustering_labels]
|
130 |
+
for j in range(len(loc_boxes)):
|
131 |
+
points_j = [(i, features[i]) for i in clusters_indices[j]]
|
132 |
+
loc_boxes[j].build(n_dim, points_j)
|
133 |
+
else:
|
134 |
+
raise RuntimeError("There exists no feature for building monitor!!")
|
135 |
+
|
136 |
+
# creat the monitor for class y at layer i
|
137 |
+
monitor = Monitor(good_ref=loc_boxes)
|
138 |
+
# save the created monitor
|
139 |
+
if not os.path.exists(monitor_saving_folder):
|
140 |
+
os.makedirs(monitor_saving_folder)
|
141 |
+
monitor_saving_path = monitor_saving_folder + "monitor_for_clustering_parameter" + "_tau_" + str(tau) + ".pkl"
|
142 |
+
with open(monitor_saving_path, 'wb') as f:
|
143 |
+
pickle.dump(monitor, f)
|
144 |
+
|
util/__init__.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
from .Clustering import *
|
2 |
+
from .Monitor_construction import *
|
3 |
+
|
utils_clustering.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from util import *
|
2 |
+
from sklearn.cluster import KMeans, SpectralClustering, DBSCAN
|
3 |
+
from sklearn import metrics
|
4 |
+
import numpy as np
|
5 |
+
import warnings
|
6 |
+
import os
|
7 |
+
from util.Monitor_construction import Box
|
8 |
+
from runtime_monitors import *
|
9 |
+
def k_means_cluster(data, n_clusters):
|
10 |
+
kmeans = KMeans(n_clusters=n_clusters, init='k-means++', random_state=0, n_init="auto")
|
11 |
+
kmeans.fit_predict(data)
|
12 |
+
|
13 |
+
lbs = kmeans.labels_ # cluster labels
|
14 |
+
clusters = dict()
|
15 |
+
for lb in set(lbs):
|
16 |
+
idx = np.where(lbs == lb)[0]
|
17 |
+
clusters[lb] = list(zip(idx, data[idx]))
|
18 |
+
return clusters
|
19 |
+
|
20 |
+
def spectral_cluster(data, n_clusters):
|
21 |
+
n_neighbors = min(n_clusters, 10)
|
22 |
+
spectral = SpectralClustering(n_clusters=n_clusters, affinity='nearest_neighbors', n_neighbors=n_neighbors,
|
23 |
+
gamma=1.0, eigen_solver="arpack", random_state=0)
|
24 |
+
|
25 |
+
# catch warnings related to kneighbors_graph
|
26 |
+
with warnings.catch_warnings():
|
27 |
+
warnings.filterwarnings(
|
28 |
+
"ignore",
|
29 |
+
message="the number of connected components of the "
|
30 |
+
+ "connectivity matrix is [0-9]{1,2}"
|
31 |
+
+ " > 1. Completing it to avoid stopping the tree early.",
|
32 |
+
category=UserWarning,
|
33 |
+
)
|
34 |
+
warnings.filterwarnings(
|
35 |
+
"ignore",
|
36 |
+
message="Graph is not fully connected, spectral embedding"
|
37 |
+
+ " may not work as expected.",
|
38 |
+
category=UserWarning,
|
39 |
+
)
|
40 |
+
spectral = spectral.fit(data)
|
41 |
+
|
42 |
+
lbs = spectral.labels_ # cluster labels
|
43 |
+
clusters = dict()
|
44 |
+
for lb in set(lbs):
|
45 |
+
idx = np.where(lbs == lb)[0]
|
46 |
+
clusters[lb] = list(zip(idx, data[idx]))
|
47 |
+
return clusters
|
48 |
+
|
49 |
+
def dbscan_cluster(data, eps, min_samples):
|
50 |
+
|
51 |
+
db = DBSCAN(eps=eps, min_samples=min_samples).fit(data)
|
52 |
+
lbs = db.labels_ # cluster labels
|
53 |
+
n_cls = len(set(lbs)) - (1 if -1 in lbs else 0) # number of clusters
|
54 |
+
n_noise = list(lbs).count(-1)
|
55 |
+
|
56 |
+
clusters = dict()
|
57 |
+
for lb in set(lbs):
|
58 |
+
idx = np.where(lbs == lb)[0]
|
59 |
+
clusters[lb] = list(zip(idx, data[idx]))
|
60 |
+
|
61 |
+
return clusters
|
val_feats/kitti/resnet/ID-bdd-OOD-coco_feats_fp_dict.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e0a450635f32e07fe9ff5a4a04dbda1c8188984fb5b2443ff117dc53d7df778d
|
3 |
+
size 53633470
|
val_feats/kitti/resnet/OOD-open_feats_fp_dict.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:0d18d1c5d2f94d059979bce4c1f812707cc3539f798d630b5fc8de2f7e7c80b6
|
3 |
+
size 45707710
|
val_feats/kitti/resnet/kitti-val_feats_fp_dict.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:636fc37b49df78c5670ff65e0138640cf873985c06df15fc2fa918b256b39901
|
3 |
+
size 8860091
|
val_feats/kitti/resnet/kitti-val_feats_tp_dict.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:1d7ec3e6034d0c7164f50aab06397cb19176ef5466f766b53d0e3099369ef82a
|
3 |
+
size 31691197
|
val_feats/kitti/resnet/voc-ood_feats_fp_dict.pickle
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:4c6e2edb71aa70fdc42e49db901e7fbf3143d98c060c7d9c8ef8eb543be05b6f
|
3 |
+
size 45461950
|