Spaces:
Sleeping
Sleeping
Upload all files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- DIC.py +17 -0
- FeatureDiversityLoss.py +59 -0
- __pycache__/get_data.cpython-310.pyc +0 -0
- __pycache__/load_model.cpython-310.pyc +0 -0
- app.py +143 -0
- architectures/FinalLayer.py +36 -0
- architectures/SLDDLevel.py +37 -0
- architectures/__pycache__/FinalLayer.cpython-310.pyc +0 -0
- architectures/__pycache__/SLDDLevel.cpython-310.pyc +0 -0
- architectures/__pycache__/model_mapping.cpython-310.pyc +0 -0
- architectures/__pycache__/resnet.cpython-310.pyc +0 -0
- architectures/__pycache__/utils.cpython-310.pyc +0 -0
- architectures/model_mapping.py +7 -0
- architectures/resnet.py +420 -0
- architectures/utils.py +17 -0
- configs/__pycache__/dataset_params.cpython-310.pyc +0 -0
- configs/__pycache__/optim_params.cpython-310.pyc +0 -0
- configs/architecture_params.py +1 -0
- configs/dataset_params.py +22 -0
- configs/optim_params.py +22 -0
- configs/qsenn_training_params.py +11 -0
- configs/sldd_training_params.py +17 -0
- dataset_classes/__pycache__/cub200.cpython-310.pyc +0 -0
- dataset_classes/__pycache__/stanfordcars.cpython-310.pyc +0 -0
- dataset_classes/__pycache__/travelingbirds.cpython-310.pyc +0 -0
- dataset_classes/__pycache__/utils.cpython-310.pyc +0 -0
- dataset_classes/cub200.py +96 -0
- dataset_classes/stanfordcars.py +121 -0
- dataset_classes/travelingbirds.py +59 -0
- dataset_classes/utils.py +16 -0
- environment.yml +117 -0
- evaluation/Metrics/Dependence.py +21 -0
- evaluation/Metrics/__pycache__/Dependence.cpython-310.pyc +0 -0
- evaluation/Metrics/__pycache__/cub_Alignment.cpython-310.pyc +0 -0
- evaluation/Metrics/cub_Alignment.py +30 -0
- evaluation/__pycache__/diversity.cpython-310.pyc +0 -0
- evaluation/__pycache__/helpers.cpython-310.pyc +0 -0
- evaluation/__pycache__/qsenn_metrics.cpython-310.pyc +0 -0
- evaluation/__pycache__/utils.cpython-310.pyc +0 -0
- evaluation/diversity.py +111 -0
- evaluation/helpers.py +6 -0
- evaluation/qsenn_metrics.py +39 -0
- evaluation/utils.py +57 -0
- fig/AutoML4FAS_Logo.jpeg +0 -0
- fig/Bund.png +0 -0
- fig/LUH.png +0 -0
- fig/birds.png +0 -0
- finetuning/map_function.py +11 -0
- finetuning/qsenn.py +30 -0
- finetuning/sldd.py +22 -0
DIC.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
|
5 |
+
dir=Path.home() / f"tmp/resnet50/CUB2011/123456/"
|
6 |
+
dic=torch.load(dir/ f"SlDD_Selection_50.pt")
|
7 |
+
|
8 |
+
print (dic)
|
9 |
+
|
10 |
+
#if 'linear.selection' in dic.keys():
|
11 |
+
#print("key 'linear.selection' exist")
|
12 |
+
#else:
|
13 |
+
#print("no such key")
|
14 |
+
|
15 |
+
|
16 |
+
|
17 |
+
|
FeatureDiversityLoss.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from torch import nn
|
3 |
+
|
4 |
+
"""
|
5 |
+
Feature Diversity Loss:
|
6 |
+
Usage to replicate paper:
|
7 |
+
Call
|
8 |
+
loss_function = FeatureDiversityLoss(0.196, linear)
|
9 |
+
to inititalize loss with linear layer of model.
|
10 |
+
At each mini batch get feature maps (Output of final convolutional layer) and add to Loss:
|
11 |
+
loss += loss_function(feature_maps, outputs)
|
12 |
+
"""
|
13 |
+
|
14 |
+
|
15 |
+
class FeatureDiversityLoss(nn.Module):
|
16 |
+
def __init__(self, scaling_factor, linear):
|
17 |
+
super().__init__()
|
18 |
+
self.scaling_factor = scaling_factor #* 0
|
19 |
+
print("Scaling Factor: ", self.scaling_factor)
|
20 |
+
self.linearLayer = linear
|
21 |
+
|
22 |
+
def initialize(self, linearLayer):
|
23 |
+
self.linearLayer = linearLayer
|
24 |
+
|
25 |
+
def get_weights(self, outputs):
|
26 |
+
weight_matrix = self.linearLayer.weight
|
27 |
+
weight_matrix = torch.abs(weight_matrix)
|
28 |
+
top_classes = torch.argmax(outputs, dim=1)
|
29 |
+
relevant_weights = weight_matrix[top_classes]
|
30 |
+
return relevant_weights
|
31 |
+
|
32 |
+
def forward(self, feature_maps, outputs):
|
33 |
+
relevant_weights = self.get_weights(outputs)
|
34 |
+
relevant_weights = norm_vector(relevant_weights)
|
35 |
+
feature_maps = preserve_avg_func(feature_maps)
|
36 |
+
flattened_feature_maps = feature_maps.flatten(2)
|
37 |
+
batch, features, map_size = flattened_feature_maps.size()
|
38 |
+
relevant_feature_maps = flattened_feature_maps * relevant_weights[..., None]
|
39 |
+
diversity_loss = torch.sum(
|
40 |
+
torch.amax(relevant_feature_maps, dim=1))
|
41 |
+
return -diversity_loss / batch * self.scaling_factor
|
42 |
+
|
43 |
+
|
44 |
+
def norm_vector(x):
|
45 |
+
return x / (torch.norm(x, dim=1) + 1e-5)[:, None]
|
46 |
+
|
47 |
+
|
48 |
+
def preserve_avg_func(x):
|
49 |
+
avgs = torch.mean(x, dim=[2, 3])
|
50 |
+
max_avgs = torch.max(avgs, dim=1)[0]
|
51 |
+
scaling_factor = avgs / torch.clamp(max_avgs[..., None], min=1e-6)
|
52 |
+
softmaxed_maps = softmax_feature_maps(x)
|
53 |
+
scaled_maps = softmaxed_maps * scaling_factor[..., None, None]
|
54 |
+
return scaled_maps
|
55 |
+
|
56 |
+
|
57 |
+
def softmax_feature_maps(x):
|
58 |
+
return torch.softmax(x.reshape(x.size(0), x.size(1), -1), 2).view_as(x)
|
59 |
+
|
__pycache__/get_data.cpython-310.pyc
ADDED
Binary file (3.46 kB). View file
|
|
__pycache__/load_model.cpython-310.pyc
ADDED
Binary file (2.69 kB). View file
|
|
app.py
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from load_model import extract_sel_mean_std_bias_assignemnt
|
3 |
+
from pathlib import Path
|
4 |
+
from architectures.model_mapping import get_model
|
5 |
+
from configs.dataset_params import dataset_constants
|
6 |
+
import torch
|
7 |
+
import torchvision.transforms as transforms
|
8 |
+
import pandas as pd
|
9 |
+
import cv2
|
10 |
+
import numpy as np
|
11 |
+
|
12 |
+
def overlapping_features_on_input(model,output, feature_maps, input, target):
|
13 |
+
W=model.linear.layer.weight
|
14 |
+
output=output.detach().cpu().numpy()
|
15 |
+
feature_maps=feature_maps.detach().cpu().numpy().squeeze()
|
16 |
+
|
17 |
+
if target !=None:
|
18 |
+
label=target
|
19 |
+
else:
|
20 |
+
label=np.argmax(output)+1
|
21 |
+
|
22 |
+
Interpretable_Selection= W[label,:]
|
23 |
+
print("W",Interpretable_Selection)
|
24 |
+
input_np=np.array(input)
|
25 |
+
h,w= input.shape[:2]
|
26 |
+
print("h,w:",h,w)
|
27 |
+
Interpretable_Features=[]
|
28 |
+
Feature_image_list=[]
|
29 |
+
for S in range(len(Interpretable_Selection)):
|
30 |
+
if Interpretable_Selection[S] > 0:
|
31 |
+
Interpretable_Features.append(feature_maps[S])
|
32 |
+
Feature_image=cv2.resize(feature_maps[S],(w,h))
|
33 |
+
Feature_image=((Feature_image-np.min(Feature_image))/(np.max(Feature_image)-np.min(Feature_image)))*255
|
34 |
+
Feature_image=Feature_image.astype(np.uint8)
|
35 |
+
Feature_image=cv2.applyColorMap(Feature_image,cv2.COLORMAP_JET)
|
36 |
+
Feature_image=0.3*Feature_image+0.7*input_np
|
37 |
+
Feature_image=np.clip(Feature_image, 0, 255).astype(np.uint8)
|
38 |
+
Feature_image_list.append(Feature_image)
|
39 |
+
#path_to_featureimage=f"/home/qixuan/tmp/FeatureImage/FI{S}.jpg"
|
40 |
+
#cv2.imwrite(path_to_featureimage,Feature_image)
|
41 |
+
print("len of Features:",len(Interpretable_Features))
|
42 |
+
|
43 |
+
return Feature_image_list
|
44 |
+
|
45 |
+
|
46 |
+
def genreate_intepriable_output(input,dataset="CUB2011", arch="resnet50",seed=123456, model_type="qsenn", n_features = 50, n_per_class=5, img_size=448, reduced_strides=False, folder = None):
|
47 |
+
n_classes = dataset_constants[dataset]["num_classes"]
|
48 |
+
|
49 |
+
model = get_model(arch, n_classes, reduced_strides)
|
50 |
+
tr=transforms.ToTensor()
|
51 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
52 |
+
if folder is None:
|
53 |
+
folder = Path(f"tmp/{arch}/{dataset}/{seed}/")
|
54 |
+
|
55 |
+
state_dict = torch.load(folder / f"{model_type}_{n_features}_{n_per_class}_FinetunedModel.pth")
|
56 |
+
selection= torch.load(folder / f"SlDD_Selection_50.pt")
|
57 |
+
state_dict['linear.selection']=selection
|
58 |
+
|
59 |
+
feature_sel, sparse_layer, current_mean, current_std, bias_sparse = extract_sel_mean_std_bias_assignemnt(state_dict)
|
60 |
+
model.set_model_sldd(feature_sel, sparse_layer, current_mean, current_std, bias_sparse)
|
61 |
+
model.load_state_dict(state_dict)
|
62 |
+
|
63 |
+
input = tr(input)
|
64 |
+
input= input.unsqueeze(0)
|
65 |
+
input= input.to(device)
|
66 |
+
model = model.to(device)
|
67 |
+
output, feature_maps, final_features = model(input, with_feature_maps=True, with_final_features=True)
|
68 |
+
print("final features:",final_features)
|
69 |
+
output=output.detach().cpu().numpy()
|
70 |
+
output= np.argmax(output)+1
|
71 |
+
|
72 |
+
|
73 |
+
print("outputclass:",output)
|
74 |
+
data_dir=Path("tmp/Datasets/CUB200/CUB_200_2011/")
|
75 |
+
labels = pd.read_csv(data_dir/"image_class_labels.txt", sep=' ', names=['img_id', 'target'])
|
76 |
+
namelist=pd.read_csv(data_dir/"images.txt",sep=' ',names=['img_id','file_name'])
|
77 |
+
classlist=pd.read_csv(data_dir/"classes.txt",sep=' ',names=['cl_id','class_name'])
|
78 |
+
options_output=labels[labels['target']==output]
|
79 |
+
options_output=options_output.sample(1)
|
80 |
+
others=labels[labels['target']!=output]
|
81 |
+
options_others=others.sample(3)
|
82 |
+
options = pd.concat([options_others, options_output], ignore_index=True)
|
83 |
+
shuffled_options = options.sample(frac=1).reset_index(drop=True)
|
84 |
+
print("shuffled:",shuffled_options)
|
85 |
+
op=[]
|
86 |
+
|
87 |
+
for i in shuffled_options['img_id']:
|
88 |
+
print(i)
|
89 |
+
filenames=namelist.loc[namelist['img_id']==i,'file_name'].values[0]
|
90 |
+
targets=shuffled_options.loc[shuffled_options['img_id']==i,'target'].values[0]
|
91 |
+
print("targets",targets)
|
92 |
+
print("name",filenames)
|
93 |
+
|
94 |
+
classes=classlist.loc[classlist['cl_id']==targets, 'class_name'].values[0]
|
95 |
+
print(data_dir/f"images/{filenames}")
|
96 |
+
|
97 |
+
op_img=cv2.imread(data_dir/f"images/{filenames}")
|
98 |
+
|
99 |
+
op_images=tr(op_img)
|
100 |
+
op_images=op_images.unsqueeze(0)
|
101 |
+
op_images=op_images.to(device)
|
102 |
+
OP, feature_maps_op =model(op_images,with_feature_maps=True,with_final_features=False)
|
103 |
+
print("OP:",OP,
|
104 |
+
"feature_maps_op:",feature_maps_op.shape)
|
105 |
+
opt= overlapping_features_on_input(model,OP, feature_maps_op,op_img,targets)
|
106 |
+
op+=opt
|
107 |
+
|
108 |
+
return op
|
109 |
+
|
110 |
+
def post_next_image(op):
|
111 |
+
if len(op)<=1:
|
112 |
+
return [],None, "all done, thank you!"
|
113 |
+
else:
|
114 |
+
op=op[1:len(op)]
|
115 |
+
return op,op[0], "Is this feature also in your input?"
|
116 |
+
|
117 |
+
def get_features_on_interface(input):
|
118 |
+
op=genreate_intepriable_output(input,dataset="CUB2011",
|
119 |
+
arch="resnet50",seed=123456,
|
120 |
+
model_type="qsenn", n_features = 50,n_per_class=5,
|
121 |
+
img_size=448, reduced_strides=False, folder = None)
|
122 |
+
return op, op[0],"Is this feature also in your input?",gr.update(interactive=False)
|
123 |
+
|
124 |
+
|
125 |
+
with gr.Blocks() as demo:
|
126 |
+
|
127 |
+
gr.Markdown("<h1 style='text-align: center;'>Interiable Bird Classification</h1>")
|
128 |
+
image_input=gr.Image()
|
129 |
+
image_output=gr.Image()
|
130 |
+
text_output=gr.Markdown()
|
131 |
+
but_generate=gr.Button("Get some interpriable Features")
|
132 |
+
but_feedback_y=gr.Button("Yes")
|
133 |
+
but_feedback_n=gr.Button("No")
|
134 |
+
image_list = gr.State([])
|
135 |
+
but_generate.click(fn=get_features_on_interface, inputs=image_input, outputs=[image_list,image_output,text_output,but_generate])
|
136 |
+
but_feedback_y.click(fn=post_next_image, inputs=image_list, outputs=[image_list,image_output,text_output])
|
137 |
+
but_feedback_n.click(fn=post_next_image, inputs=image_list, outputs=[image_list,image_output,text_output])
|
138 |
+
|
139 |
+
demo.launch()
|
140 |
+
|
141 |
+
|
142 |
+
|
143 |
+
|
architectures/FinalLayer.py
ADDED
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from torch import nn
|
3 |
+
|
4 |
+
from architectures.SLDDLevel import SLDDLevel
|
5 |
+
|
6 |
+
|
7 |
+
class FinalLayer():
|
8 |
+
def __init__(self, num_classes, n_features):
|
9 |
+
super().__init__()
|
10 |
+
self.avgpool = torch.nn.AdaptiveAvgPool2d((1, 1))
|
11 |
+
self.linear = nn.Linear(n_features, num_classes)
|
12 |
+
self.featureDropout = torch.nn.Dropout(0.2)
|
13 |
+
self.selection = None
|
14 |
+
|
15 |
+
def transform_output(self, feature_maps, with_feature_maps,
|
16 |
+
with_final_features):
|
17 |
+
if self.selection is not None:
|
18 |
+
feature_maps = feature_maps[:, self.selection]
|
19 |
+
x = self.avgpool(feature_maps)
|
20 |
+
pre_out = torch.flatten(x, 1)
|
21 |
+
final_features = self.featureDropout(pre_out)
|
22 |
+
final = self.linear(final_features)
|
23 |
+
final = [final]
|
24 |
+
if with_feature_maps:
|
25 |
+
final.append(feature_maps)
|
26 |
+
if with_final_features:
|
27 |
+
final.append(final_features)
|
28 |
+
if len(final) == 1:
|
29 |
+
final = final[0]
|
30 |
+
return final
|
31 |
+
|
32 |
+
|
33 |
+
def set_model_sldd(self, selection, weight, mean, std, bias = None):
|
34 |
+
self.selection = selection
|
35 |
+
self.linear = SLDDLevel(selection, weight, mean, std, bias)
|
36 |
+
self.featureDropout = torch.nn.Dropout(0.1)
|
architectures/SLDDLevel.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch.nn
|
2 |
+
|
3 |
+
|
4 |
+
class SLDDLevel(torch.nn.Module):
|
5 |
+
def __init__(self, selection, weight_at_selection,mean, std, bias=None):
|
6 |
+
super().__init__()
|
7 |
+
self.register_buffer('selection', torch.tensor(selection, dtype=torch.long))
|
8 |
+
num_classes, n_features = weight_at_selection.shape
|
9 |
+
selected_mean = mean
|
10 |
+
selected_std = std
|
11 |
+
if len(selected_mean) != len(selection):
|
12 |
+
selected_mean = selected_mean[selection]
|
13 |
+
selected_std = selected_std[selection]
|
14 |
+
self.mean = torch.nn.Parameter(selected_mean)
|
15 |
+
self.std = torch.nn.Parameter(selected_std)
|
16 |
+
if bias is not None:
|
17 |
+
self.layer = torch.nn.Linear(n_features, num_classes)
|
18 |
+
self.layer.bias = torch.nn.Parameter(bias, requires_grad=False)
|
19 |
+
else:
|
20 |
+
self.layer = torch.nn.Linear(n_features, num_classes, bias=False)
|
21 |
+
self.layer.weight = torch.nn.Parameter(weight_at_selection, requires_grad=False)
|
22 |
+
|
23 |
+
@property
|
24 |
+
def weight(self):
|
25 |
+
return self.layer.weight
|
26 |
+
|
27 |
+
@property
|
28 |
+
def bias(self):
|
29 |
+
if self.layer.bias is None:
|
30 |
+
return torch.zeros(self.layer.out_features)
|
31 |
+
else:
|
32 |
+
return self.layer.bias
|
33 |
+
|
34 |
+
|
35 |
+
def forward(self, input):
|
36 |
+
input = (input - self.mean) / torch.clamp(self.std, min=1e-6)
|
37 |
+
return self.layer(input)
|
architectures/__pycache__/FinalLayer.cpython-310.pyc
ADDED
Binary file (1.46 kB). View file
|
|
architectures/__pycache__/SLDDLevel.cpython-310.pyc
ADDED
Binary file (1.52 kB). View file
|
|
architectures/__pycache__/model_mapping.cpython-310.pyc
ADDED
Binary file (411 Bytes). View file
|
|
architectures/__pycache__/resnet.cpython-310.pyc
ADDED
Binary file (12.7 kB). View file
|
|
architectures/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (657 Bytes). View file
|
|
architectures/model_mapping.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from architectures.resnet import resnet50
|
2 |
+
|
3 |
+
|
4 |
+
def get_model(arch, num_classes, changed_strides=True):
|
5 |
+
if arch == "resnet50":
|
6 |
+
model = resnet50(True, num_classes=num_classes, changed_strides=changed_strides)
|
7 |
+
return model
|
architectures/resnet.py
ADDED
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import copy
|
2 |
+
import time
|
3 |
+
|
4 |
+
import torch
|
5 |
+
import torch.nn as nn
|
6 |
+
from torch.hub import load_state_dict_from_url
|
7 |
+
from torchvision.models import get_model
|
8 |
+
|
9 |
+
# from scripts.modelExtensions.crossModelfunctions import init_experiment_stuff
|
10 |
+
|
11 |
+
|
12 |
+
|
13 |
+
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
|
14 |
+
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
|
15 |
+
'wide_resnet50_2', 'wide_resnet101_2',
|
16 |
+
'wide_resnet50_3', 'wide_resnet50_4', 'wide_resnet50_5',
|
17 |
+
'wide_resnet50_6', ]
|
18 |
+
|
19 |
+
from architectures.FinalLayer import FinalLayer
|
20 |
+
from architectures.utils import SequentialWithArgs
|
21 |
+
|
22 |
+
model_urls = {
|
23 |
+
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
|
24 |
+
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
|
25 |
+
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
|
26 |
+
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
|
27 |
+
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
|
28 |
+
'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
|
29 |
+
'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
|
30 |
+
'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
|
31 |
+
'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
|
32 |
+
}
|
33 |
+
|
34 |
+
|
35 |
+
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
|
36 |
+
"""3x3 convolution with padding"""
|
37 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
38 |
+
padding=dilation, groups=groups, bias=False, dilation=dilation)
|
39 |
+
|
40 |
+
|
41 |
+
def conv1x1(in_planes, out_planes, stride=1):
|
42 |
+
"""1x1 convolution"""
|
43 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
|
44 |
+
|
45 |
+
|
46 |
+
class BasicBlock(nn.Module):
|
47 |
+
expansion = 1
|
48 |
+
__constants__ = ['downsample']
|
49 |
+
|
50 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
51 |
+
base_width=64, dilation=1, norm_layer=None, features=None):
|
52 |
+
super(BasicBlock, self).__init__()
|
53 |
+
if norm_layer is None:
|
54 |
+
norm_layer = nn.BatchNorm2d
|
55 |
+
if groups != 1 or base_width != 64:
|
56 |
+
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
|
57 |
+
if dilation > 1:
|
58 |
+
raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
|
59 |
+
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
|
60 |
+
self.conv1 = conv3x3(inplanes, planes, stride)
|
61 |
+
self.bn1 = norm_layer(planes)
|
62 |
+
self.relu = nn.ReLU(inplace=True)
|
63 |
+
self.conv2 = conv3x3(planes, planes)
|
64 |
+
self.bn2 = norm_layer(planes)
|
65 |
+
self.downsample = downsample
|
66 |
+
self.stride = stride
|
67 |
+
|
68 |
+
|
69 |
+
def forward(self, x, no_relu=False):
|
70 |
+
identity = x
|
71 |
+
|
72 |
+
out = self.conv1(x)
|
73 |
+
out = self.bn1(out)
|
74 |
+
out = self.relu(out)
|
75 |
+
|
76 |
+
out = self.conv2(out)
|
77 |
+
out = self.bn2(out)
|
78 |
+
|
79 |
+
if self.downsample is not None:
|
80 |
+
identity = self.downsample(x)
|
81 |
+
|
82 |
+
|
83 |
+
|
84 |
+
out += identity
|
85 |
+
|
86 |
+
if no_relu:
|
87 |
+
return out
|
88 |
+
return self.relu(out)
|
89 |
+
|
90 |
+
|
91 |
+
class Bottleneck(nn.Module):
|
92 |
+
expansion = 4
|
93 |
+
__constants__ = ['downsample']
|
94 |
+
|
95 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
96 |
+
base_width=64, dilation=1, norm_layer=None, features=None):
|
97 |
+
super(Bottleneck, self).__init__()
|
98 |
+
if norm_layer is None:
|
99 |
+
norm_layer = nn.BatchNorm2d
|
100 |
+
width = int(planes * (base_width / 64.)) * groups
|
101 |
+
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
|
102 |
+
self.conv1 = conv1x1(inplanes, width)
|
103 |
+
self.bn1 = norm_layer(width)
|
104 |
+
self.conv2 = conv3x3(width, width, stride, groups, dilation)
|
105 |
+
self.bn2 = norm_layer(width)
|
106 |
+
if features is None:
|
107 |
+
self.conv3 = conv1x1(width, planes * self.expansion)
|
108 |
+
self.bn3 = norm_layer(planes * self.expansion)
|
109 |
+
else:
|
110 |
+
self.conv3 = conv1x1(width, features)
|
111 |
+
self.bn3 = norm_layer(features)
|
112 |
+
|
113 |
+
self.relu = nn.ReLU(inplace=True)
|
114 |
+
self.downsample = downsample
|
115 |
+
self.stride = stride
|
116 |
+
|
117 |
+
def forward(self, x, no_relu=False, early_exit=False):
|
118 |
+
identity = x
|
119 |
+
out = self.conv1(x)
|
120 |
+
out = self.bn1(out)
|
121 |
+
out = self.relu(out)
|
122 |
+
|
123 |
+
out = self.conv2(out)
|
124 |
+
out = self.bn2(out)
|
125 |
+
out = self.relu(out)
|
126 |
+
|
127 |
+
out = self.conv3(out)
|
128 |
+
out = self.bn3(out)
|
129 |
+
|
130 |
+
if self.downsample is not None:
|
131 |
+
identity = self.downsample(x)
|
132 |
+
|
133 |
+
out += identity
|
134 |
+
|
135 |
+
if no_relu:
|
136 |
+
return out
|
137 |
+
return self.relu(out)
|
138 |
+
|
139 |
+
|
140 |
+
class ResNet(nn.Module, FinalLayer):
|
141 |
+
|
142 |
+
def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
|
143 |
+
groups=1, width_per_group=64, replace_stride_with_dilation=None,
|
144 |
+
norm_layer=None, changed_strides=False,):
|
145 |
+
super(ResNet, self).__init__()
|
146 |
+
if norm_layer is None:
|
147 |
+
norm_layer = nn.BatchNorm2d
|
148 |
+
self._norm_layer = norm_layer
|
149 |
+
widths = [64, 128, 256, 512]
|
150 |
+
self.inplanes = 64
|
151 |
+
self.dilation = 1
|
152 |
+
if replace_stride_with_dilation is None:
|
153 |
+
# each element in the tuple indicates if we should replace
|
154 |
+
# the 2x2 stride with a dilated convolution instead
|
155 |
+
replace_stride_with_dilation = [False, False, False]
|
156 |
+
if len(replace_stride_with_dilation) != 3:
|
157 |
+
raise ValueError("replace_stride_with_dilation should be None "
|
158 |
+
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
|
159 |
+
self.groups = groups
|
160 |
+
self.base_width = width_per_group
|
161 |
+
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
|
162 |
+
bias=False)
|
163 |
+
self.bn1 = norm_layer(self.inplanes)
|
164 |
+
self.relu = nn.ReLU(inplace=True)
|
165 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
166 |
+
self.layer1 = self._make_layer(block, 64, layers[0])
|
167 |
+
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
|
168 |
+
dilate=replace_stride_with_dilation[0])
|
169 |
+
self.sstride = 2
|
170 |
+
if changed_strides:
|
171 |
+
self.sstride = 1
|
172 |
+
self.layer3 = self._make_layer(block, 256, layers[2], stride=self.sstride,
|
173 |
+
dilate=replace_stride_with_dilation[1])
|
174 |
+
self.stride = 2
|
175 |
+
|
176 |
+
if changed_strides:
|
177 |
+
self.stride = 1
|
178 |
+
self.layer4 = self._make_layer(block, 512, layers[3], stride=self.stride,
|
179 |
+
dilate=replace_stride_with_dilation[2])
|
180 |
+
FinalLayer.__init__(self, num_classes, 512 * block.expansion)
|
181 |
+
for m in self.modules():
|
182 |
+
if isinstance(m, nn.Conv2d):
|
183 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
184 |
+
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
|
185 |
+
nn.init.constant_(m.weight, 1)
|
186 |
+
nn.init.constant_(m.bias, 0)
|
187 |
+
|
188 |
+
# Zero-initialize the last BN in each residual branch,
|
189 |
+
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
|
190 |
+
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
|
191 |
+
if zero_init_residual:
|
192 |
+
for m in self.modules():
|
193 |
+
if isinstance(m, Bottleneck):
|
194 |
+
nn.init.constant_(m.bn3.weight, 0)
|
195 |
+
elif isinstance(m, BasicBlock):
|
196 |
+
nn.init.constant_(m.bn2.weight, 0)
|
197 |
+
|
198 |
+
def _make_layer(self, block, planes, blocks, stride=1, dilate=False, last_block_f=None):
|
199 |
+
norm_layer = self._norm_layer
|
200 |
+
downsample = None
|
201 |
+
previous_dilation = self.dilation
|
202 |
+
if dilate:
|
203 |
+
self.dilation *= stride
|
204 |
+
stride = 1
|
205 |
+
if stride != 1 or self.inplanes != planes * block.expansion:
|
206 |
+
downsample = nn.Sequential(
|
207 |
+
conv1x1(self.inplanes, planes * block.expansion, stride),
|
208 |
+
norm_layer(planes * block.expansion),
|
209 |
+
)
|
210 |
+
|
211 |
+
layers = []
|
212 |
+
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
|
213 |
+
self.base_width, previous_dilation, norm_layer))
|
214 |
+
self.inplanes = planes * block.expansion
|
215 |
+
for _ in range(1, blocks):
|
216 |
+
krepeep = None
|
217 |
+
if last_block_f is not None and _ == blocks - 1:
|
218 |
+
krepeep = last_block_f
|
219 |
+
layers.append(block(self.inplanes, planes, groups=self.groups,
|
220 |
+
base_width=self.base_width, dilation=self.dilation,
|
221 |
+
norm_layer=norm_layer, features=krepeep))
|
222 |
+
|
223 |
+
return SequentialWithArgs(*layers)
|
224 |
+
|
225 |
+
def _forward(self, x, with_feature_maps=False, with_final_features=False):
|
226 |
+
x = self.conv1(x)
|
227 |
+
x = self.bn1(x)
|
228 |
+
x = self.relu(x)
|
229 |
+
x = self.maxpool(x)
|
230 |
+
|
231 |
+
x = self.layer1(x)
|
232 |
+
x = self.layer2(x)
|
233 |
+
x = self.layer3(x)
|
234 |
+
feature_maps = self.layer4(x, no_relu=True)
|
235 |
+
feature_maps = torch.functional.F.relu(feature_maps)
|
236 |
+
return self.transform_output( feature_maps, with_feature_maps,
|
237 |
+
with_final_features)
|
238 |
+
|
239 |
+
# Allow for accessing forward method in a inherited class
|
240 |
+
forward = _forward
|
241 |
+
|
242 |
+
|
243 |
+
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
|
244 |
+
model = ResNet(block, layers, **kwargs)
|
245 |
+
if pretrained:
|
246 |
+
state_dict = load_state_dict_from_url(model_urls[arch],
|
247 |
+
progress=progress)
|
248 |
+
if kwargs["num_classes"] == 1000:
|
249 |
+
state_dict["linear.weight"] = state_dict["fc.weight"]
|
250 |
+
state_dict["linear.bias"] = state_dict["fc.bias"]
|
251 |
+
model.load_state_dict(state_dict, strict=False)
|
252 |
+
return model
|
253 |
+
|
254 |
+
|
255 |
+
def resnet18(pretrained=False, progress=True, **kwargs):
|
256 |
+
r"""ResNet-18 model from
|
257 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
258 |
+
|
259 |
+
Args:
|
260 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
261 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
262 |
+
"""
|
263 |
+
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
|
264 |
+
**kwargs)
|
265 |
+
|
266 |
+
|
267 |
+
def resnet34(pretrained=False, progress=True, **kwargs):
|
268 |
+
r"""ResNet-34 model from
|
269 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
270 |
+
|
271 |
+
Args:
|
272 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
273 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
274 |
+
"""
|
275 |
+
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,
|
276 |
+
**kwargs)
|
277 |
+
|
278 |
+
|
279 |
+
def resnet50(pretrained=False, progress=True, **kwargs):
|
280 |
+
r"""ResNet-50 model from
|
281 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
282 |
+
|
283 |
+
Args:
|
284 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
285 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
286 |
+
"""
|
287 |
+
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
|
288 |
+
**kwargs)
|
289 |
+
|
290 |
+
|
291 |
+
def resnet101(pretrained=False, progress=True, **kwargs):
|
292 |
+
r"""ResNet-101 model from
|
293 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
294 |
+
|
295 |
+
Args:
|
296 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
297 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
298 |
+
"""
|
299 |
+
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress,
|
300 |
+
**kwargs)
|
301 |
+
|
302 |
+
|
303 |
+
def resnet152(pretrained=False, progress=True, **kwargs):
|
304 |
+
r"""ResNet-152 model from
|
305 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
306 |
+
|
307 |
+
Args:
|
308 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
309 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
310 |
+
"""
|
311 |
+
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress,
|
312 |
+
**kwargs)
|
313 |
+
|
314 |
+
|
315 |
+
def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
|
316 |
+
r"""ResNeXt-50 32x4d model from
|
317 |
+
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
318 |
+
|
319 |
+
Args:
|
320 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
321 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
322 |
+
"""
|
323 |
+
kwargs['groups'] = 32
|
324 |
+
kwargs['width_per_group'] = 4
|
325 |
+
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
|
326 |
+
pretrained, progress, **kwargs)
|
327 |
+
|
328 |
+
|
329 |
+
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
|
330 |
+
r"""ResNeXt-101 32x8d model from
|
331 |
+
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
332 |
+
|
333 |
+
Args:
|
334 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
335 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
336 |
+
"""
|
337 |
+
kwargs['groups'] = 32
|
338 |
+
kwargs['width_per_group'] = 8
|
339 |
+
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
|
340 |
+
pretrained, progress, **kwargs)
|
341 |
+
|
342 |
+
|
343 |
+
def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
|
344 |
+
r"""Wide ResNet-50-2 model from
|
345 |
+
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
346 |
+
|
347 |
+
The model is the same as ResNet except for the bottleneck number of channels
|
348 |
+
which is twice larger in every block. The number of channels in outer 1x1
|
349 |
+
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
350 |
+
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
351 |
+
|
352 |
+
Args:
|
353 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
354 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
355 |
+
"""
|
356 |
+
kwargs['width_per_group'] = 64 * 2
|
357 |
+
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
|
358 |
+
pretrained, progress, **kwargs)
|
359 |
+
|
360 |
+
|
361 |
+
def wide_resnet50_3(pretrained=False, progress=True, **kwargs):
|
362 |
+
r"""Wide ResNet-50-3 model
|
363 |
+
Args:
|
364 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
365 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
366 |
+
"""
|
367 |
+
kwargs['width_per_group'] = 64 * 3
|
368 |
+
return _resnet('wide_resnet50_3', Bottleneck, [3, 4, 6, 3],
|
369 |
+
pretrained, progress, **kwargs)
|
370 |
+
|
371 |
+
|
372 |
+
def wide_resnet50_4(pretrained=False, progress=True, **kwargs):
|
373 |
+
r"""Wide ResNet-50-4 model
|
374 |
+
Args:
|
375 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
376 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
377 |
+
"""
|
378 |
+
kwargs['width_per_group'] = 64 * 4
|
379 |
+
return _resnet('wide_resnet50_4', Bottleneck, [3, 4, 6, 3],
|
380 |
+
pretrained, progress, **kwargs)
|
381 |
+
|
382 |
+
|
383 |
+
def wide_resnet50_5(pretrained=False, progress=True, **kwargs):
|
384 |
+
r"""Wide ResNet-50-5 model
|
385 |
+
Args:
|
386 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
387 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
388 |
+
"""
|
389 |
+
kwargs['width_per_group'] = 64 * 5
|
390 |
+
return _resnet('wide_resnet50_5', Bottleneck, [3, 4, 6, 3],
|
391 |
+
pretrained, progress, **kwargs)
|
392 |
+
|
393 |
+
|
394 |
+
def wide_resnet50_6(pretrained=False, progress=True, **kwargs):
|
395 |
+
r"""Wide ResNet-50-6 model
|
396 |
+
Args:
|
397 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
398 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
399 |
+
"""
|
400 |
+
kwargs['width_per_group'] = 64 * 6
|
401 |
+
return _resnet('wide_resnet50_6', Bottleneck, [3, 4, 6, 3],
|
402 |
+
pretrained, progress, **kwargs)
|
403 |
+
|
404 |
+
|
405 |
+
def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
|
406 |
+
r"""Wide ResNet-101-2 model from
|
407 |
+
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
408 |
+
|
409 |
+
The model is the same as ResNet except for the bottleneck number of channels
|
410 |
+
which is twice larger in every block. The number of channels in outer 1x1
|
411 |
+
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
412 |
+
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
413 |
+
|
414 |
+
Args:
|
415 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
416 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
417 |
+
"""
|
418 |
+
kwargs['width_per_group'] = 64 * 2
|
419 |
+
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
|
420 |
+
pretrained, progress, **kwargs)
|
architectures/utils.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
|
3 |
+
|
4 |
+
|
5 |
+
class SequentialWithArgs(torch.nn.Sequential):
|
6 |
+
def forward(self, input, *args, **kwargs):
|
7 |
+
vs = list(self._modules.values())
|
8 |
+
l = len(vs)
|
9 |
+
for i in range(l):
|
10 |
+
if i == l-1:
|
11 |
+
input = vs[i](input, *args, **kwargs)
|
12 |
+
else:
|
13 |
+
input = vs[i](input)
|
14 |
+
return input
|
15 |
+
|
16 |
+
|
17 |
+
|
configs/__pycache__/dataset_params.cpython-310.pyc
ADDED
Binary file (1.15 kB). View file
|
|
configs/__pycache__/optim_params.cpython-310.pyc
ADDED
Binary file (1.25 kB). View file
|
|
configs/architecture_params.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
architecture_params = {"resnet50": {"beta":0.196}}
|
configs/dataset_params.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
|
3 |
+
from configs.optim_params import EvaluatedDict
|
4 |
+
|
5 |
+
dataset_constants = {"CUB2011":{"num_classes":200},
|
6 |
+
"TravelingBirds":{"num_classes":200},
|
7 |
+
"ImageNet":{"num_classes":1000},
|
8 |
+
"StanfordCars":{"num_classes":196},
|
9 |
+
"FGVCAircraft": {"num_classes":100}}
|
10 |
+
|
11 |
+
normalize_params = {"CUB2011":{"mean": torch.tensor([0.4853, 0.4964, 0.4295]),"std":torch.tensor([0.2300, 0.2258, 0.2625])},
|
12 |
+
"TravelingBirds":{"mean": torch.tensor([0.4584, 0.4369, 0.3957]),"std":torch.tensor([0.2610, 0.2569, 0.2722])},
|
13 |
+
"ImageNet":{'mean': torch.tensor([0.485, 0.456, 0.406]),'std': torch.tensor([0.229, 0.224, 0.225])} ,
|
14 |
+
"StanfordCars":{'mean': torch.tensor([0.4593, 0.4466, 0.4453]),'std': torch.tensor([0.2920, 0.2910, 0.2988])} ,
|
15 |
+
"FGVCAircraft":{'mean': torch.tensor([0.4827, 0.5130, 0.5352]),
|
16 |
+
'std': torch.tensor([0.2236, 0.2170, 0.2478]),}
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
dense_batch_size = EvaluatedDict({False: 16,True: 1024,}, lambda x: x == "ImageNet")
|
21 |
+
|
22 |
+
ft_batch_size = EvaluatedDict({False: 16,True: 1024,}, lambda x: x == "ImageNet")# Untested
|
configs/optim_params.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# order: lr,weight_decay, step_lr, step_lr_gamma
|
2 |
+
import math
|
3 |
+
|
4 |
+
|
5 |
+
class EvaluatedDict:
|
6 |
+
def __init__(self, d, func):
|
7 |
+
self.dict = d
|
8 |
+
self.func = func
|
9 |
+
|
10 |
+
def __getitem__(self, key):
|
11 |
+
return self.dict[self.func(key)]
|
12 |
+
|
13 |
+
dense_params = EvaluatedDict({False: [0.005, 0.0005, 30, 0.4, 150],True: [None,None,None,None,None],}, lambda x: x == "ImageNet")
|
14 |
+
def calculate_lr_from_args( epochs, step_lr, start_lr, step_lr_decay):
|
15 |
+
# Gets the final learning rate after dense training with step_lr_schedule.
|
16 |
+
n_steps = math.floor((epochs - step_lr) / step_lr)
|
17 |
+
final_lr = start_lr * step_lr_decay ** n_steps
|
18 |
+
return final_lr
|
19 |
+
|
20 |
+
ft_params =EvaluatedDict({False: [1e-4, 0.0005, 10, 0.4, 40],True:[[calculate_lr_from_args(150,30,0.005, 0.4), 0.0005, 10, 0.4, 40]]}, lambda x: x == "ImageNet")
|
21 |
+
|
22 |
+
|
configs/qsenn_training_params.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from configs.sldd_training_params import OptimizationScheduler
|
2 |
+
|
3 |
+
|
4 |
+
class QSENNScheduler(OptimizationScheduler):
|
5 |
+
def get_params(self):
|
6 |
+
params = super().get_params()
|
7 |
+
if self.n_calls >= 2:
|
8 |
+
params[0] = params[0] * 0.9**(self.n_calls-2)
|
9 |
+
if 2 <= self.n_calls <= 4:
|
10 |
+
params[-2] = 10# Change num epochs to 10 for iterative finetuning
|
11 |
+
return params
|
configs/sldd_training_params.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from configs.optim_params import dense_params, ft_params
|
2 |
+
|
3 |
+
|
4 |
+
class OptimizationScheduler:
|
5 |
+
def __init__(self, dataset):
|
6 |
+
self.dataset = dataset
|
7 |
+
self.n_calls = 0
|
8 |
+
|
9 |
+
|
10 |
+
def get_params(self):
|
11 |
+
if self.n_calls == 0: # Return Deńse Params
|
12 |
+
params = dense_params[self.dataset]+ [False]
|
13 |
+
else: # Return Finetuning Params
|
14 |
+
params = ft_params[self.dataset]+ [True]
|
15 |
+
self.n_calls += 1
|
16 |
+
return params
|
17 |
+
|
dataset_classes/__pycache__/cub200.cpython-310.pyc
ADDED
Binary file (3.71 kB). View file
|
|
dataset_classes/__pycache__/stanfordcars.cpython-310.pyc
ADDED
Binary file (4.98 kB). View file
|
|
dataset_classes/__pycache__/travelingbirds.cpython-310.pyc
ADDED
Binary file (2.83 kB). View file
|
|
dataset_classes/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (839 Bytes). View file
|
|
dataset_classes/cub200.py
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dataset should lie under /root/
|
2 |
+
# root is currently set to ~/tmp/Datasets/CUB200
|
3 |
+
# If cropped iamges, like for PIP-Net, ProtoPool, etc. are used, then the crop_root should be set to a folder containing the
|
4 |
+
# cropped images in the expected structure, obtained by following ProtoTree's instructions.
|
5 |
+
# https://github.com/M-Nauta/ProtoTree/blob/main/README.md#preprocessing-cub
|
6 |
+
import os
|
7 |
+
from pathlib import Path
|
8 |
+
|
9 |
+
import numpy as np
|
10 |
+
import pandas as pd
|
11 |
+
from torch.utils.data import Dataset
|
12 |
+
from torchvision.datasets.folder import default_loader
|
13 |
+
|
14 |
+
from dataset_classes.utils import txt_load
|
15 |
+
|
16 |
+
|
17 |
+
class CUB200Class(Dataset):
|
18 |
+
root = Path.home() / "tmp/Datasets/CUB200"
|
19 |
+
crop_root = Path.home() / "tmp/Datasets/PPCUB200"
|
20 |
+
base_folder = 'CUB_200_2011/images'
|
21 |
+
def __init__(self, train, transform, crop=True):
|
22 |
+
self.train = train
|
23 |
+
self.transform = transform
|
24 |
+
self.crop = crop
|
25 |
+
self._load_metadata()
|
26 |
+
self.loader = default_loader
|
27 |
+
|
28 |
+
if crop:
|
29 |
+
self.adapt_to_crop()
|
30 |
+
|
31 |
+
def _load_metadata(self):
|
32 |
+
images = pd.read_csv(os.path.join(self.root, 'CUB_200_2011', 'images.txt'), sep=' ',
|
33 |
+
names=['img_id', 'filepath'])
|
34 |
+
image_class_labels = pd.read_csv(os.path.join(self.root, 'CUB_200_2011', 'image_class_labels.txt'),
|
35 |
+
sep=' ', names=['img_id', 'target'])
|
36 |
+
train_test_split = pd.read_csv(os.path.join(self.root, 'CUB_200_2011', 'train_test_split.txt'),
|
37 |
+
sep=' ', names=['img_id', 'is_training_img'])
|
38 |
+
data = images.merge(image_class_labels, on='img_id')
|
39 |
+
self.data = data.merge(train_test_split, on='img_id')
|
40 |
+
if self.train:
|
41 |
+
self.data = self.data[self.data.is_training_img == 1]
|
42 |
+
else:
|
43 |
+
self.data = self.data[self.data.is_training_img == 0]
|
44 |
+
|
45 |
+
def __len__(self):
|
46 |
+
return len(self.data)
|
47 |
+
|
48 |
+
def adapt_to_crop(self):
|
49 |
+
# ds_name = [x for x in self.cropped_dict.keys() if x in self.root][0]
|
50 |
+
self.root = self.crop_root
|
51 |
+
folder_name = "train" if self.train else "test"
|
52 |
+
folder_name = folder_name + "_cropped"
|
53 |
+
self.base_folder = 'CUB_200_2011/' + folder_name
|
54 |
+
|
55 |
+
def __getitem__(self, idx):
|
56 |
+
sample = self.data.iloc[idx]
|
57 |
+
path = os.path.join(self.root, self.base_folder, sample.filepath)
|
58 |
+
target = sample.target - 1 # Targets start at 1 by default, so shift to 0
|
59 |
+
img = self.loader(path)
|
60 |
+
img = self.transform(img)
|
61 |
+
return img, target
|
62 |
+
|
63 |
+
@classmethod
|
64 |
+
def get_image_attribute_labels(self, train=False):
|
65 |
+
image_attribute_labels = pd.read_csv(
|
66 |
+
os.path.join('/home/norrenbr/tmp/Datasets/CUB200', 'CUB_200_2011', "attributes",
|
67 |
+
'image_attribute_labels.txt'),
|
68 |
+
sep=' ', names=['img_id', 'attribute', "is_present", "certainty", "time"], on_bad_lines="skip")
|
69 |
+
train_test_split = pd.read_csv(os.path.join(self.root, 'CUB_200_2011', 'train_test_split.txt'),
|
70 |
+
sep=' ', names=['img_id', 'is_training_img'])
|
71 |
+
merged = image_attribute_labels.merge(train_test_split, on="img_id")
|
72 |
+
filtered_data = merged[merged["is_training_img"] == train]
|
73 |
+
return filtered_data
|
74 |
+
|
75 |
+
|
76 |
+
@staticmethod
|
77 |
+
def filter_attribute_labels(labels, min_certainty=3):
|
78 |
+
is_invisible_present = labels[labels["certainty"] == 1]["is_present"].sum()
|
79 |
+
if is_invisible_present != 0:
|
80 |
+
raise ValueError("Invisible present")
|
81 |
+
labels["img_id"] -= min(labels["img_id"])
|
82 |
+
labels["img_id"] = fillholes_in_array(labels["img_id"])
|
83 |
+
labels[labels["certainty"] == 1]["certainty"] = 4
|
84 |
+
labels = labels[labels["certainty"] >= min_certainty]
|
85 |
+
labels["attribute"] -= min(labels["attribute"])
|
86 |
+
labels = labels[["img_id", "attribute", "is_present"]]
|
87 |
+
labels["is_present"] = labels["is_present"].astype(bool)
|
88 |
+
return labels
|
89 |
+
|
90 |
+
|
91 |
+
|
92 |
+
def fillholes_in_array(array):
|
93 |
+
unique_values = np.unique(array)
|
94 |
+
mapping = {x: i for i, x in enumerate(unique_values)}
|
95 |
+
array = array.map(mapping)
|
96 |
+
return array
|
dataset_classes/stanfordcars.py
ADDED
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pathlib
|
2 |
+
from typing import Callable, Optional, Any, Tuple
|
3 |
+
|
4 |
+
import numpy as np
|
5 |
+
import pandas as pd
|
6 |
+
from PIL import Image
|
7 |
+
from torchvision.datasets import VisionDataset
|
8 |
+
from torchvision.datasets.utils import download_and_extract_archive, download_url
|
9 |
+
|
10 |
+
|
11 |
+
class StanfordCarsClass(VisionDataset):
|
12 |
+
"""`Stanford Cars <https://ai.stanford.edu/~jkrause/cars/car_dataset.html>`_ Dataset
|
13 |
+
|
14 |
+
The Cars dataset contains 16,185 images of 196 classes of cars. The data is
|
15 |
+
split into 8,144 training images and 8,041 testing images, where each class
|
16 |
+
has been split roughly in a 50-50 split
|
17 |
+
|
18 |
+
.. note::
|
19 |
+
|
20 |
+
This class needs `scipy <https://docs.scipy.org/doc/>`_ to load target files from `.mat` format.
|
21 |
+
|
22 |
+
Args:
|
23 |
+
root (string): Root directory of dataset
|
24 |
+
split (string, optional): The dataset split, supports ``"train"`` (default) or ``"test"``.
|
25 |
+
transform (callable, optional): A function/transform that takes in an PIL image
|
26 |
+
and returns a transformed version. E.g, ``transforms.RandomCrop``
|
27 |
+
target_transform (callable, optional): A function/transform that takes in the
|
28 |
+
target and transforms it.
|
29 |
+
download (bool, optional): If True, downloads the dataset from the internet and
|
30 |
+
puts it in root directory. If dataset is already downloaded, it is not
|
31 |
+
downloaded again."""
|
32 |
+
root = pathlib.Path.home() / "tmp" / "Datasets" / "StanfordCars"
|
33 |
+
def __init__(
|
34 |
+
self,
|
35 |
+
train: bool = True,
|
36 |
+
transform: Optional[Callable] = None,
|
37 |
+
target_transform: Optional[Callable] = None,
|
38 |
+
download: bool = True,
|
39 |
+
) -> None:
|
40 |
+
|
41 |
+
try:
|
42 |
+
import scipy.io as sio
|
43 |
+
except ImportError:
|
44 |
+
raise RuntimeError("Scipy is not found. This dataset needs to have scipy installed: pip install scipy")
|
45 |
+
|
46 |
+
super().__init__(self.root, transform=transform, target_transform=target_transform)
|
47 |
+
|
48 |
+
self.train = train
|
49 |
+
self._base_folder = pathlib.Path(self.root) / "stanford_cars"
|
50 |
+
devkit = self._base_folder / "devkit"
|
51 |
+
|
52 |
+
if train:
|
53 |
+
self._annotations_mat_path = devkit / "cars_train_annos.mat"
|
54 |
+
self._images_base_path = self._base_folder / "cars_train"
|
55 |
+
else:
|
56 |
+
self._annotations_mat_path = self._base_folder / "cars_test_annos_withlabels.mat"
|
57 |
+
self._images_base_path = self._base_folder / "cars_test"
|
58 |
+
|
59 |
+
if download:
|
60 |
+
self.download()
|
61 |
+
|
62 |
+
if not self._check_exists():
|
63 |
+
raise RuntimeError("Dataset not found. You can use download=True to download it")
|
64 |
+
|
65 |
+
self.samples = [
|
66 |
+
(
|
67 |
+
str(self._images_base_path / annotation["fname"]),
|
68 |
+
annotation["class"] - 1, # Original target mapping starts from 1, hence -1
|
69 |
+
)
|
70 |
+
for annotation in sio.loadmat(self._annotations_mat_path, squeeze_me=True)["annotations"]
|
71 |
+
]
|
72 |
+
self.targets = np.array([x[1] for x in self.samples])
|
73 |
+
self.classes = sio.loadmat(str(devkit / "cars_meta.mat"), squeeze_me=True)["class_names"].tolist()
|
74 |
+
self.class_to_idx = {cls: i for i, cls in enumerate(self.classes)}
|
75 |
+
|
76 |
+
def __len__(self) -> int:
|
77 |
+
return len(self.samples)
|
78 |
+
|
79 |
+
def __getitem__(self, idx: int) -> Tuple[Any, Any]:
|
80 |
+
"""Returns pil_image and class_id for given index"""
|
81 |
+
image_path, target = self.samples[idx]
|
82 |
+
pil_image = Image.open(image_path).convert("RGB")
|
83 |
+
|
84 |
+
if self.transform is not None:
|
85 |
+
pil_image = self.transform(pil_image)
|
86 |
+
if self.target_transform is not None:
|
87 |
+
target = self.target_transform(target)
|
88 |
+
return pil_image, target
|
89 |
+
|
90 |
+
def download(self) -> None:
|
91 |
+
if self._check_exists():
|
92 |
+
return
|
93 |
+
|
94 |
+
download_and_extract_archive(
|
95 |
+
url="https://ai.stanford.edu/~jkrause/cars/car_devkit.tgz",
|
96 |
+
download_root=str(self._base_folder),
|
97 |
+
md5="c3b158d763b6e2245038c8ad08e45376",
|
98 |
+
)
|
99 |
+
if self.train:
|
100 |
+
download_and_extract_archive(
|
101 |
+
url="https://ai.stanford.edu/~jkrause/car196/cars_train.tgz",
|
102 |
+
download_root=str(self._base_folder),
|
103 |
+
md5="065e5b463ae28d29e77c1b4b166cfe61",
|
104 |
+
)
|
105 |
+
else:
|
106 |
+
download_and_extract_archive(
|
107 |
+
url="https://ai.stanford.edu/~jkrause/car196/cars_test.tgz",
|
108 |
+
download_root=str(self._base_folder),
|
109 |
+
md5="4ce7ebf6a94d07f1952d94dd34c4d501",
|
110 |
+
)
|
111 |
+
download_url(
|
112 |
+
url="https://ai.stanford.edu/~jkrause/car196/cars_test_annos_withlabels.mat",
|
113 |
+
root=str(self._base_folder),
|
114 |
+
md5="b0a2b23655a3edd16d84508592a98d10",
|
115 |
+
)
|
116 |
+
|
117 |
+
def _check_exists(self) -> bool:
|
118 |
+
if not (self._base_folder / "devkit").is_dir():
|
119 |
+
return False
|
120 |
+
|
121 |
+
return self._annotations_mat_path.exists() and self._images_base_path.is_dir()
|
dataset_classes/travelingbirds.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# TravelingBirds dataset needs to be downloaded from https://worksheets.codalab.org/bundles/0x518829de2aa440c79cd9d75ef6669f27
|
2 |
+
# as it comes from https://github.com/yewsiang/ConceptBottleneck
|
3 |
+
import os
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
import numpy as np
|
7 |
+
import pandas as pd
|
8 |
+
|
9 |
+
from dataset_classes.cub200 import CUB200Class
|
10 |
+
from dataset_classes.utils import index_list_with_sorting, mask_list
|
11 |
+
|
12 |
+
|
13 |
+
class TravelingBirds(CUB200Class):
|
14 |
+
init_base_folder = 'CUB_fixed'
|
15 |
+
root = Path.home() / "tmp/Datasets/TravelingBirds"
|
16 |
+
crop_root = Path.home() / "tmp/Datasets/PPTravelingBirds"
|
17 |
+
def get_all_samples_dir(self, dir):
|
18 |
+
|
19 |
+
self.base_folder = os.path.join(self.init_base_folder, dir)
|
20 |
+
main_dir = Path(self.root) / self.init_base_folder / dir
|
21 |
+
return self.get_all_sample(main_dir)
|
22 |
+
|
23 |
+
def adapt_to_crop(self):
|
24 |
+
self.root = self.crop_root
|
25 |
+
folder_name = "train" if self.train else "test"
|
26 |
+
folder_name = folder_name + "_cropped"
|
27 |
+
self.base_folder = 'CUB_fixed/' + folder_name
|
28 |
+
|
29 |
+
def get_all_sample(self, dir):
|
30 |
+
answer = []
|
31 |
+
for i, sub_dir in enumerate(sorted(os.listdir(dir))):
|
32 |
+
class_dir = dir / sub_dir
|
33 |
+
for single_img in os.listdir(class_dir):
|
34 |
+
answer.append([Path(sub_dir) / single_img, i + 1])
|
35 |
+
return answer
|
36 |
+
def _load_metadata(self):
|
37 |
+
train_test_split = pd.read_csv(
|
38 |
+
os.path.join(Path(self.root).parent / "CUB200", 'CUB_200_2011', 'train_test_split.txt'),
|
39 |
+
sep=' ', names=['img_id', 'is_training_img'])
|
40 |
+
data = pd.read_csv(
|
41 |
+
os.path.join(Path(self.root).parent / "CUB200", 'CUB_200_2011', 'images.txt'),
|
42 |
+
sep=' ', names=['img_id', "path"])
|
43 |
+
img_dict = {x[1]: x[0] for x in data.values}
|
44 |
+
# TravelingBirds has all train+test images in both folders, just with different backgrounds.
|
45 |
+
# They are separated by train_test_split of CUB200.
|
46 |
+
if self.train:
|
47 |
+
samples = self.get_all_samples_dir("train")
|
48 |
+
mask = train_test_split["is_training_img"] == 1
|
49 |
+
else:
|
50 |
+
samples = self.get_all_samples_dir("test")
|
51 |
+
mask = train_test_split["is_training_img"] == 0
|
52 |
+
ids = np.array([img_dict[str(x[0])] for x in samples])
|
53 |
+
sorted = np.argsort(ids)
|
54 |
+
samples = index_list_with_sorting(samples, sorted)
|
55 |
+
samples = mask_list(samples, mask)
|
56 |
+
filepaths = [x[0] for x in samples]
|
57 |
+
labels = [x[1] for x in samples]
|
58 |
+
samples = pd.DataFrame({"filepath": filepaths, "target": labels})
|
59 |
+
self.data = samples
|
dataset_classes/utils.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def index_list_with_sorting(list_to_sort, sorting_list):
|
2 |
+
answer = []
|
3 |
+
for entry in sorting_list:
|
4 |
+
answer.append(list_to_sort[entry])
|
5 |
+
return answer
|
6 |
+
|
7 |
+
|
8 |
+
def mask_list(list_input, mask):
|
9 |
+
return [x for i, x in enumerate(list_input) if mask[i]]
|
10 |
+
|
11 |
+
|
12 |
+
def txt_load(filename):
|
13 |
+
with open(filename, 'r') as f:
|
14 |
+
data = f.read()
|
15 |
+
return data
|
16 |
+
|
environment.yml
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: QSENNEnv
|
2 |
+
channels:
|
3 |
+
- pytorch
|
4 |
+
- nvidia
|
5 |
+
- defaults
|
6 |
+
dependencies:
|
7 |
+
- _libgcc_mutex=0.1=main
|
8 |
+
- _openmp_mutex=5.1=1_gnu
|
9 |
+
- blas=1.0=mkl
|
10 |
+
- brotli-python=1.0.9=py310h6a678d5_7
|
11 |
+
- bzip2=1.0.8=h7b6447c_0
|
12 |
+
- ca-certificates=2023.12.12=h06a4308_0
|
13 |
+
- certifi=2023.11.17=py310h06a4308_0
|
14 |
+
- cffi=1.16.0=py310h5eee18b_0
|
15 |
+
- charset-normalizer=2.0.4=pyhd3eb1b0_0
|
16 |
+
- cryptography=41.0.7=py310hdda0065_0
|
17 |
+
- cuda-cudart=12.1.105=0
|
18 |
+
- cuda-cupti=12.1.105=0
|
19 |
+
- cuda-libraries=12.1.0=0
|
20 |
+
- cuda-nvrtc=12.1.105=0
|
21 |
+
- cuda-nvtx=12.1.105=0
|
22 |
+
- cuda-opencl=12.3.101=0
|
23 |
+
- cuda-runtime=12.1.0=0
|
24 |
+
- ffmpeg=4.3=hf484d3e_0
|
25 |
+
- filelock=3.13.1=py310h06a4308_0
|
26 |
+
- freetype=2.12.1=h4a9f257_0
|
27 |
+
- giflib=5.2.1=h5eee18b_3
|
28 |
+
- gmp=6.2.1=h295c915_3
|
29 |
+
- gmpy2=2.1.2=py310heeb90bb_0
|
30 |
+
- gnutls=3.6.15=he1e5248_0
|
31 |
+
- idna=3.4=py310h06a4308_0
|
32 |
+
- intel-openmp=2023.1.0=hdb19cb5_46306
|
33 |
+
- jinja2=3.1.2=py310h06a4308_0
|
34 |
+
- jpeg=9e=h5eee18b_1
|
35 |
+
- lame=3.100=h7b6447c_0
|
36 |
+
- lcms2=2.12=h3be6417_0
|
37 |
+
- ld_impl_linux-64=2.38=h1181459_1
|
38 |
+
- lerc=3.0=h295c915_0
|
39 |
+
- libcublas=12.1.0.26=0
|
40 |
+
- libcufft=11.0.2.4=0
|
41 |
+
- libcufile=1.8.1.2=0
|
42 |
+
- libcurand=10.3.4.107=0
|
43 |
+
- libcusolver=11.4.4.55=0
|
44 |
+
- libcusparse=12.0.2.55=0
|
45 |
+
- libdeflate=1.17=h5eee18b_1
|
46 |
+
- libffi=3.4.4=h6a678d5_0
|
47 |
+
- libgcc-ng=11.2.0=h1234567_1
|
48 |
+
- libgomp=11.2.0=h1234567_1
|
49 |
+
- libiconv=1.16=h7f8727e_2
|
50 |
+
- libidn2=2.3.4=h5eee18b_0
|
51 |
+
- libjpeg-turbo=2.0.0=h9bf148f_0
|
52 |
+
- libnpp=12.0.2.50=0
|
53 |
+
- libnvjitlink=12.1.105=0
|
54 |
+
- libnvjpeg=12.1.1.14=0
|
55 |
+
- libpng=1.6.39=h5eee18b_0
|
56 |
+
- libstdcxx-ng=11.2.0=h1234567_1
|
57 |
+
- libtasn1=4.19.0=h5eee18b_0
|
58 |
+
- libtiff=4.5.1=h6a678d5_0
|
59 |
+
- libunistring=0.9.10=h27cfd23_0
|
60 |
+
- libuuid=1.41.5=h5eee18b_0
|
61 |
+
- libwebp=1.3.2=h11a3e52_0
|
62 |
+
- libwebp-base=1.3.2=h5eee18b_0
|
63 |
+
- llvm-openmp=14.0.6=h9e868ea_0
|
64 |
+
- lz4-c=1.9.4=h6a678d5_0
|
65 |
+
- markupsafe=2.1.3=py310h5eee18b_0
|
66 |
+
- mkl=2023.1.0=h213fc3f_46344
|
67 |
+
- mkl-service=2.4.0=py310h5eee18b_1
|
68 |
+
- mkl_fft=1.3.8=py310h5eee18b_0
|
69 |
+
- mkl_random=1.2.4=py310hdb19cb5_0
|
70 |
+
- mpc=1.1.0=h10f8cd9_1
|
71 |
+
- mpfr=4.0.2=hb69a4c5_1
|
72 |
+
- mpmath=1.3.0=py310h06a4308_0
|
73 |
+
- ncurses=6.4=h6a678d5_0
|
74 |
+
- nettle=3.7.3=hbbd107a_1
|
75 |
+
- networkx=3.1=py310h06a4308_0
|
76 |
+
- numpy=1.26.3=py310h5f9d8c6_0
|
77 |
+
- numpy-base=1.26.3=py310hb5e798b_0
|
78 |
+
- openh264=2.1.1=h4ff587b_0
|
79 |
+
- openjpeg=2.4.0=h3ad879b_0
|
80 |
+
- openssl=3.0.12=h7f8727e_0
|
81 |
+
- pillow=10.0.1=py310ha6cbd5a_0
|
82 |
+
- pip=23.3.1=py310h06a4308_0
|
83 |
+
- pycparser=2.21=pyhd3eb1b0_0
|
84 |
+
- pyopenssl=23.2.0=py310h06a4308_0
|
85 |
+
- pysocks=1.7.1=py310h06a4308_0
|
86 |
+
- python=3.10.13=h955ad1f_0
|
87 |
+
- pytorch=2.1.2=py3.10_cuda12.1_cudnn8.9.2_0
|
88 |
+
- pytorch-cuda=12.1=ha16c6d3_5
|
89 |
+
- pytorch-mutex=1.0=cuda
|
90 |
+
- pyyaml=6.0.1=py310h5eee18b_0
|
91 |
+
- readline=8.2=h5eee18b_0
|
92 |
+
- requests=2.31.0=py310h06a4308_0
|
93 |
+
- setuptools=68.2.2=py310h06a4308_0
|
94 |
+
- sqlite=3.41.2=h5eee18b_0
|
95 |
+
- sympy=1.12=py310h06a4308_0
|
96 |
+
- tbb=2021.8.0=hdb19cb5_0
|
97 |
+
- tk=8.6.12=h1ccaba5_0
|
98 |
+
- torchaudio=2.1.2=py310_cu121
|
99 |
+
- torchtriton=2.1.0=py310
|
100 |
+
- torchvision=0.16.2=py310_cu121
|
101 |
+
- typing_extensions=4.7.1=py310h06a4308_0
|
102 |
+
- urllib3=1.26.18=py310h06a4308_0
|
103 |
+
- wheel=0.41.2=py310h06a4308_0
|
104 |
+
- xz=5.4.5=h5eee18b_0
|
105 |
+
- yaml=0.2.5=h7b6447c_0
|
106 |
+
- zlib=1.2.13=h5eee18b_0
|
107 |
+
- zstd=1.5.5=hc292b87_0
|
108 |
+
- pip:
|
109 |
+
- fsspec==2023.12.2
|
110 |
+
- glm-saga==0.1.2
|
111 |
+
- pandas==2.1.4
|
112 |
+
- python-dateutil==2.8.2
|
113 |
+
- pytz==2023.3.post1
|
114 |
+
- six==1.16.0
|
115 |
+
- tqdm==4.66.1
|
116 |
+
- tzdata==2023.4
|
117 |
+
prefix: /home/norrenbr/anaconda/tmp/envs/QSENN-Minimal
|
evaluation/Metrics/Dependence.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
|
3 |
+
|
4 |
+
def compute_contribution_top_feature(features, outputs, weights, labels):
|
5 |
+
with torch.no_grad():
|
6 |
+
total_pre_softmax, predicted_classes = torch.max(outputs, dim=1)
|
7 |
+
feature_part = features * weights.to(features.device)[predicted_classes]
|
8 |
+
class_specific_feature_part = torch.zeros((weights.shape[0], features.shape[1],))
|
9 |
+
feature_class_part = torch.zeros((weights.shape[0], features.shape[1],))
|
10 |
+
for unique_class in predicted_classes.unique():
|
11 |
+
mask = predicted_classes == unique_class
|
12 |
+
class_specific_feature_part[unique_class] = feature_part[mask].mean(dim=0)
|
13 |
+
gt_mask = labels == unique_class
|
14 |
+
feature_class_part[unique_class] = feature_part[gt_mask].mean(dim=0)
|
15 |
+
abs_features = feature_part.abs()
|
16 |
+
abs_sum = abs_features.sum(dim=1)
|
17 |
+
fractions_abs = abs_features / abs_sum[:, None]
|
18 |
+
abs_max = fractions_abs.max(dim=1)[0]
|
19 |
+
mask = ~torch.isnan(abs_max)
|
20 |
+
abs_max = abs_max[mask]
|
21 |
+
return abs_max.mean()
|
evaluation/Metrics/__pycache__/Dependence.cpython-310.pyc
ADDED
Binary file (934 Bytes). View file
|
|
evaluation/Metrics/__pycache__/cub_Alignment.cpython-310.pyc
ADDED
Binary file (1.28 kB). View file
|
|
evaluation/Metrics/cub_Alignment.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
|
3 |
+
from dataset_classes.cub200 import CUB200Class
|
4 |
+
|
5 |
+
|
6 |
+
def get_cub_alignment_from_features(features_train_sorted):
|
7 |
+
metric_matrix = compute_metric_matrix(np.array(features_train_sorted), "train")
|
8 |
+
return np.mean(np.max(metric_matrix, axis=1))
|
9 |
+
pass
|
10 |
+
|
11 |
+
|
12 |
+
def compute_metric_matrix(features, mode):
|
13 |
+
image_attribute_labels = CUB200Class.get_image_attribute_labels(train=mode == "train")
|
14 |
+
image_attribute_labels = CUB200Class.filter_attribute_labels(image_attribute_labels)
|
15 |
+
matrix_shape = (
|
16 |
+
features.shape[1], max(image_attribute_labels["attribute"]) + 1)
|
17 |
+
accuracy_matrix = np.zeros(matrix_shape)
|
18 |
+
sensitivity_matrix = np.zeros_like(accuracy_matrix)
|
19 |
+
grouped_attributes = image_attribute_labels.groupby("attribute")
|
20 |
+
for attribute_id, group in grouped_attributes:
|
21 |
+
is_present = group[group["is_present"]]
|
22 |
+
not_present = group[~group["is_present"]]
|
23 |
+
is_present_avg = np.mean(features[is_present["img_id"]], axis=0)
|
24 |
+
not_present_avg = np.mean(features[not_present["img_id"]], axis=0)
|
25 |
+
sensitivity_matrix[:, attribute_id] = not_present_avg
|
26 |
+
accuracy_matrix[:, attribute_id] = is_present_avg
|
27 |
+
metric_matrix = accuracy_matrix - sensitivity_matrix
|
28 |
+
no_abs_features = features - np.min(features, axis=0)
|
29 |
+
no_abs_feature_mean = metric_matrix / no_abs_features.mean(axis=0)[:, None]
|
30 |
+
return no_abs_feature_mean
|
evaluation/__pycache__/diversity.cpython-310.pyc
ADDED
Binary file (3.93 kB). View file
|
|
evaluation/__pycache__/helpers.cpython-310.pyc
ADDED
Binary file (378 Bytes). View file
|
|
evaluation/__pycache__/qsenn_metrics.cpython-310.pyc
ADDED
Binary file (1.53 kB). View file
|
|
evaluation/__pycache__/utils.cpython-310.pyc
ADDED
Binary file (1.85 kB). View file
|
|
evaluation/diversity.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
|
4 |
+
from evaluation.helpers import softmax_feature_maps
|
5 |
+
|
6 |
+
|
7 |
+
class MultiKCrossChannelMaxPooledSum:
|
8 |
+
def __init__(self, top_k_range, weights, interactions, func="softmax"):
|
9 |
+
self.top_k_range = top_k_range
|
10 |
+
self.weights = weights
|
11 |
+
self.failed = False
|
12 |
+
self.max_ks = self.get_max_ks(weights)
|
13 |
+
self.locality_of_used_features = torch.zeros(len(top_k_range), device=weights.device)
|
14 |
+
self.locality_of_exclusely_used_features = torch.zeros(len(top_k_range), device=weights.device)
|
15 |
+
self.ns_k = torch.zeros(len(top_k_range), device=weights.device)
|
16 |
+
self.exclusive_ns = torch.zeros(len(top_k_range), device=weights.device)
|
17 |
+
self.interactions = interactions
|
18 |
+
self.func = func
|
19 |
+
|
20 |
+
def get_max_ks(self, weights):
|
21 |
+
nonzeros = torch.count_nonzero(torch.tensor(weights), 1)
|
22 |
+
return nonzeros
|
23 |
+
|
24 |
+
def get_top_n_locality(self, outputs, initial_feature_maps, k):
|
25 |
+
feature_maps, relevant_weights, vector_size, top_classes = self.adapt_feature_maps(outputs,
|
26 |
+
initial_feature_maps)
|
27 |
+
max_ks = self.max_ks[top_classes]
|
28 |
+
max_k_based_row_selection = max_ks >= k
|
29 |
+
|
30 |
+
result = self.get_crosspooled(relevant_weights, max_k_based_row_selection, k, vector_size, feature_maps,
|
31 |
+
separated=True)
|
32 |
+
return result
|
33 |
+
|
34 |
+
def get_locality(self, outputs, initial_feature_maps, n):
|
35 |
+
answer = self.get_top_n_locality(outputs, initial_feature_maps, n)
|
36 |
+
return answer
|
37 |
+
|
38 |
+
def get_result(self):
|
39 |
+
# if torch.sum(self.exclusive_ns) ==0:
|
40 |
+
# end_idx = len(self.exclusive_ns) - 1
|
41 |
+
# else:
|
42 |
+
|
43 |
+
exclusive_array = torch.zeros_like(self.locality_of_exclusely_used_features)
|
44 |
+
local_array = torch.zeros_like(self.locality_of_used_features)
|
45 |
+
# if self.failed:
|
46 |
+
# return local_array, exclusive_array
|
47 |
+
cumulated = torch.cumsum(self.exclusive_ns, 0)
|
48 |
+
end_idx = torch.argmax(cumulated)
|
49 |
+
exclusivity_array = self.locality_of_exclusely_used_features[:end_idx + 1] / self.exclusive_ns[:end_idx + 1]
|
50 |
+
exclusivity_array[exclusivity_array != exclusivity_array] = 0
|
51 |
+
exclusive_array[:len(exclusivity_array)] = exclusivity_array
|
52 |
+
locality_array = self.locality_of_used_features[self.locality_of_used_features != 0] / self.ns_k[
|
53 |
+
self.locality_of_used_features != 0]
|
54 |
+
local_array[:len(locality_array)] = locality_array
|
55 |
+
return local_array, exclusive_array
|
56 |
+
|
57 |
+
def get_crosspooled(self, relevant_weights, mask, k, vector_size, feature_maps, separated=False):
|
58 |
+
relevant_indices = get_relevant_indices(relevant_weights, k)[mask]
|
59 |
+
# this should have size batch x k x featuremapsize squared]
|
60 |
+
indices = relevant_indices.unsqueeze(2).repeat(1, 1, vector_size)
|
61 |
+
sub_feature_maps = torch.gather(feature_maps[mask], 1, indices)
|
62 |
+
# shape batch x featuremapsquared: For each "pixel" the highest value
|
63 |
+
cross_pooled = torch.max(sub_feature_maps, 1)[0]
|
64 |
+
if separated:
|
65 |
+
return torch.sum(cross_pooled, 1) / k
|
66 |
+
else:
|
67 |
+
ns = len(cross_pooled)
|
68 |
+
result = torch.sum(cross_pooled) / (k)
|
69 |
+
# should be batch x map size
|
70 |
+
|
71 |
+
return ns, result
|
72 |
+
|
73 |
+
def adapt_feature_maps(self, outputs, initial_feature_maps):
|
74 |
+
if self.func == "softmax":
|
75 |
+
feature_maps = softmax_feature_maps(initial_feature_maps)
|
76 |
+
feature_maps = torch.flatten(feature_maps, 2)
|
77 |
+
vector_size = feature_maps.shape[2]
|
78 |
+
top_classes = torch.argmax(outputs, dim=1)
|
79 |
+
relevant_weights = self.weights[top_classes]
|
80 |
+
if relevant_weights.shape[1] != feature_maps.shape[1]:
|
81 |
+
feature_maps = self.interactions.get_localized_features(initial_feature_maps)
|
82 |
+
feature_maps = softmax_feature_maps(feature_maps)
|
83 |
+
feature_maps = torch.flatten(feature_maps, 2)
|
84 |
+
return feature_maps, relevant_weights, vector_size, top_classes
|
85 |
+
|
86 |
+
def calculate_locality(self, outputs, initial_feature_maps):
|
87 |
+
feature_maps, relevant_weights, vector_size, top_classes = self.adapt_feature_maps(outputs,
|
88 |
+
initial_feature_maps)
|
89 |
+
max_ks = self.max_ks[top_classes]
|
90 |
+
for k in self.top_k_range:
|
91 |
+
# relevant_k_s = max_ks[]
|
92 |
+
max_k_based_row_selection = max_ks >= k
|
93 |
+
if torch.sum(max_k_based_row_selection) == 0:
|
94 |
+
break
|
95 |
+
|
96 |
+
exclusive_k = max_ks == k
|
97 |
+
if torch.sum(exclusive_k) != 0:
|
98 |
+
ns, result = self.get_crosspooled(relevant_weights, exclusive_k, k, vector_size, feature_maps)
|
99 |
+
self.locality_of_exclusely_used_features[k - 1] += result
|
100 |
+
self.exclusive_ns[k - 1] += ns
|
101 |
+
ns, result = self.get_crosspooled(relevant_weights, max_k_based_row_selection, k, vector_size, feature_maps)
|
102 |
+
self.ns_k[k - 1] += ns
|
103 |
+
self.locality_of_used_features[k - 1] += result
|
104 |
+
|
105 |
+
def __call__(self, outputs, initial_feature_maps):
|
106 |
+
self.calculate_locality(outputs, initial_feature_maps)
|
107 |
+
|
108 |
+
|
109 |
+
def get_relevant_indices(weights, top_k):
|
110 |
+
top_k = weights.topk(top_k)[1]
|
111 |
+
return top_k
|
evaluation/helpers.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
|
3 |
+
|
4 |
+
def softmax_feature_maps(x):
|
5 |
+
# done: verify that this applies softmax along first dimension
|
6 |
+
return torch.softmax(x.reshape(x.size(0), x.size(1), -1), 2).view_as(x)
|
evaluation/qsenn_metrics.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
|
4 |
+
from evaluation.Metrics.Dependence import compute_contribution_top_feature
|
5 |
+
from evaluation.Metrics.cub_Alignment import get_cub_alignment_from_features
|
6 |
+
from evaluation.diversity import MultiKCrossChannelMaxPooledSum
|
7 |
+
from evaluation.utils import get_metrics_for_model
|
8 |
+
|
9 |
+
|
10 |
+
def evaluateALLMetricsForComps(features_train, outputs_train, feature_maps_test,
|
11 |
+
outputs_test, linear_matrix, labels_train):
|
12 |
+
with torch.no_grad():
|
13 |
+
if len(features_train) < 7000: # recognize CUB and TravelingBirds
|
14 |
+
cub_alignment = get_cub_alignment_from_features(features_train)
|
15 |
+
else:
|
16 |
+
cub_alignment = 0
|
17 |
+
print("cub_alignment: ", cub_alignment)
|
18 |
+
localizer = MultiKCrossChannelMaxPooledSum(range(1, 6), linear_matrix, None)
|
19 |
+
batch_size = 300
|
20 |
+
for i in range(np.floor(len(features_train) / batch_size).astype(int)):
|
21 |
+
localizer(outputs_test[i * batch_size:(i + 1) * batch_size].to("cuda"),
|
22 |
+
feature_maps_test[i * batch_size:(i + 1) * batch_size].to("cuda"))
|
23 |
+
|
24 |
+
locality, exlusive_locality = localizer.get_result()
|
25 |
+
diversity = locality[4]
|
26 |
+
print("diversity@5: ", diversity)
|
27 |
+
abs_frac_mean = compute_contribution_top_feature(
|
28 |
+
features_train,
|
29 |
+
outputs_train,
|
30 |
+
linear_matrix,
|
31 |
+
labels_train)
|
32 |
+
print("Dependence ", abs_frac_mean)
|
33 |
+
answer_dict = {"diversity": diversity.item(), "Dependence": abs_frac_mean.item(), "Alignment":cub_alignment}
|
34 |
+
return answer_dict
|
35 |
+
|
36 |
+
def eval_model_on_all_qsenn_metrics(model, test_loader, train_loader):
|
37 |
+
return get_metrics_for_model(train_loader, test_loader, model, evaluateALLMetricsForComps)
|
38 |
+
|
39 |
+
|
evaluation/utils.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from tqdm import tqdm
|
3 |
+
|
4 |
+
|
5 |
+
|
6 |
+
def get_metrics_for_model(train_loader, test_loader, model, metric_evaluator):
|
7 |
+
(features_train, feature_maps_train, outputs_train, features_test, feature_maps_test,
|
8 |
+
outputs_test, labels) = [], [], [], [], [], [], []
|
9 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
10 |
+
model.eval()
|
11 |
+
model = model.to(device)
|
12 |
+
training_transforms = train_loader.dataset.transform
|
13 |
+
train_loader.dataset.transform = test_loader.dataset.transform # Use test transform for train
|
14 |
+
train_loader = torch.utils.data.DataLoader(train_loader.dataset, batch_size=100, shuffle=False) # Turn off shuffling
|
15 |
+
print("Going in get metrics")
|
16 |
+
linear_matrix = model.linear.weight
|
17 |
+
entries = torch.nonzero(linear_matrix)
|
18 |
+
rel_features = torch.unique(entries[:, 1])
|
19 |
+
with torch.no_grad():
|
20 |
+
iterator = tqdm(enumerate(train_loader), total=len(train_loader))
|
21 |
+
for batch_idx, (data, target) in iterator:
|
22 |
+
xs1 = data.to("cuda")
|
23 |
+
output, feature_maps, final_features = model(xs1, with_feature_maps=True, with_final_features=True,)
|
24 |
+
outputs_train.append(output.to("cpu"))
|
25 |
+
features_train.append(final_features.to("cpu"))
|
26 |
+
labels.append(target.to("cpu"))
|
27 |
+
total = 0
|
28 |
+
correct = 0
|
29 |
+
iterator = tqdm(enumerate(test_loader), total=len(test_loader))
|
30 |
+
for batch_idx, (data, target) in iterator:
|
31 |
+
xs1 = data.to("cuda")
|
32 |
+
output, feature_maps, final_features = model(xs1, with_feature_maps=True,
|
33 |
+
with_final_features=True, )
|
34 |
+
feature_maps_test.append(feature_maps[:, rel_features].to("cpu"))
|
35 |
+
outputs_test.append(output.to("cpu"))
|
36 |
+
total += target.size(0)
|
37 |
+
_, predicted = output.max(1)
|
38 |
+
correct += predicted.eq(target.to("cuda")).sum().item()
|
39 |
+
print("test accuracy: ", correct / total)
|
40 |
+
features_train = torch.cat(features_train)
|
41 |
+
outputs_train = torch.cat(outputs_train)
|
42 |
+
feature_maps_test = torch.cat(feature_maps_test)
|
43 |
+
outputs_test = torch.cat(outputs_test)
|
44 |
+
labels = torch.cat(labels)
|
45 |
+
linear_matrix = linear_matrix[:, rel_features]
|
46 |
+
print("Shape of linear matrix: ", linear_matrix.shape)
|
47 |
+
all_metrics_dict = metric_evaluator(features_train, outputs_train,
|
48 |
+
feature_maps_test,
|
49 |
+
outputs_test, linear_matrix, labels)
|
50 |
+
result_dict = {"Accuracy": correct / total, "NFfeatures": linear_matrix.shape[1],
|
51 |
+
"PerClass": torch.nonzero(linear_matrix).shape[0] / linear_matrix.shape[0],
|
52 |
+
}
|
53 |
+
result_dict.update(all_metrics_dict)
|
54 |
+
print(result_dict)
|
55 |
+
# Reset Train transforms
|
56 |
+
train_loader.dataset.transform = training_transforms
|
57 |
+
return result_dict
|
fig/AutoML4FAS_Logo.jpeg
ADDED
![]() |
fig/Bund.png
ADDED
![]() |
fig/LUH.png
ADDED
![]() |
fig/birds.png
ADDED
![]() |
finetuning/map_function.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from finetuning.qsenn import finetune_qsenn
|
2 |
+
from finetuning.sldd import finetune_sldd
|
3 |
+
|
4 |
+
|
5 |
+
def finetune(key, model, train_loader, test_loader, log_dir, n_classes, seed, beta, optimization_schedule, per_class, n_features):
|
6 |
+
if key == 'sldd':
|
7 |
+
return finetune_sldd(model, train_loader, test_loader, log_dir, n_classes, seed, beta, optimization_schedule,per_class, n_features)
|
8 |
+
elif key == 'qsenn':
|
9 |
+
return finetune_qsenn(model, train_loader, test_loader, log_dir, n_classes, seed, beta, optimization_schedule,n_features,per_class, )
|
10 |
+
else:
|
11 |
+
raise ValueError(f"Unknown Finetuning key: {key}")
|
finetuning/qsenn.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
import torch
|
4 |
+
|
5 |
+
from finetuning.utils import train_n_epochs
|
6 |
+
from sparsification.qsenn import compute_qsenn_feature_selection_and_assignment
|
7 |
+
|
8 |
+
|
9 |
+
def finetune_qsenn(model, train_loader, test_loader, log_dir, n_classes, seed, beta, optimization_schedule ,n_features, n_per_class):
|
10 |
+
for iteration_epoch in range(4):
|
11 |
+
print(f"Starting iteration epoch {iteration_epoch}")
|
12 |
+
this_log_dir = log_dir / f"iteration_epoch_{iteration_epoch}"
|
13 |
+
this_log_dir.mkdir(parents=True, exist_ok=True)
|
14 |
+
feature_sel, sparse_layer,bias_sparse, current_mean, current_std = compute_qsenn_feature_selection_and_assignment(model, train_loader,
|
15 |
+
test_loader,
|
16 |
+
this_log_dir, n_classes, seed, n_features, n_per_class)
|
17 |
+
model.set_model_sldd(feature_sel, sparse_layer, current_mean, current_std, bias_sparse)
|
18 |
+
if os.path.exists(this_log_dir / "trained_model.pth"):
|
19 |
+
model.load_state_dict(torch.load(this_log_dir / "trained_model.pth"))
|
20 |
+
_ = optimization_schedule.get_params() # count up, to have get correct lr
|
21 |
+
continue
|
22 |
+
|
23 |
+
model = train_n_epochs( model, beta, optimization_schedule, train_loader, test_loader)
|
24 |
+
torch.save(model.state_dict(), this_log_dir / "trained_model.pth")
|
25 |
+
print(f"Finished iteration epoch {iteration_epoch}")
|
26 |
+
return model
|
27 |
+
|
28 |
+
|
29 |
+
|
30 |
+
|
finetuning/sldd.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import torch
|
3 |
+
|
4 |
+
from FeatureDiversityLoss import FeatureDiversityLoss
|
5 |
+
from finetuning.utils import train_n_epochs
|
6 |
+
from sparsification.glmBasedSparsification import compute_feature_selection_and_assignment
|
7 |
+
from sparsification.sldd import compute_sldd_feature_selection_and_assignment
|
8 |
+
from train import train, test
|
9 |
+
from training.optim import get_optimizer
|
10 |
+
|
11 |
+
|
12 |
+
|
13 |
+
|
14 |
+
def finetune_sldd(model, train_loader, test_loader, log_dir, n_classes, seed, beta, optimization_schedule,n_per_class, n_features, ):
|
15 |
+
feature_sel, weight, bias, mean, std = compute_sldd_feature_selection_and_assignment(model, train_loader,
|
16 |
+
test_loader,
|
17 |
+
log_dir, n_classes, seed,n_per_class, n_features)
|
18 |
+
model.set_model_sldd(feature_sel, weight, mean, std, bias)
|
19 |
+
model = train_n_epochs( model, beta, optimization_schedule, train_loader, test_loader)
|
20 |
+
return model
|
21 |
+
|
22 |
+
|