Haaribo commited on
Commit
40e7aed
·
1 Parent(s): 53e4c55

Upload all files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. DIC.py +17 -0
  2. FeatureDiversityLoss.py +59 -0
  3. __pycache__/get_data.cpython-310.pyc +0 -0
  4. __pycache__/load_model.cpython-310.pyc +0 -0
  5. app.py +143 -0
  6. architectures/FinalLayer.py +36 -0
  7. architectures/SLDDLevel.py +37 -0
  8. architectures/__pycache__/FinalLayer.cpython-310.pyc +0 -0
  9. architectures/__pycache__/SLDDLevel.cpython-310.pyc +0 -0
  10. architectures/__pycache__/model_mapping.cpython-310.pyc +0 -0
  11. architectures/__pycache__/resnet.cpython-310.pyc +0 -0
  12. architectures/__pycache__/utils.cpython-310.pyc +0 -0
  13. architectures/model_mapping.py +7 -0
  14. architectures/resnet.py +420 -0
  15. architectures/utils.py +17 -0
  16. configs/__pycache__/dataset_params.cpython-310.pyc +0 -0
  17. configs/__pycache__/optim_params.cpython-310.pyc +0 -0
  18. configs/architecture_params.py +1 -0
  19. configs/dataset_params.py +22 -0
  20. configs/optim_params.py +22 -0
  21. configs/qsenn_training_params.py +11 -0
  22. configs/sldd_training_params.py +17 -0
  23. dataset_classes/__pycache__/cub200.cpython-310.pyc +0 -0
  24. dataset_classes/__pycache__/stanfordcars.cpython-310.pyc +0 -0
  25. dataset_classes/__pycache__/travelingbirds.cpython-310.pyc +0 -0
  26. dataset_classes/__pycache__/utils.cpython-310.pyc +0 -0
  27. dataset_classes/cub200.py +96 -0
  28. dataset_classes/stanfordcars.py +121 -0
  29. dataset_classes/travelingbirds.py +59 -0
  30. dataset_classes/utils.py +16 -0
  31. environment.yml +117 -0
  32. evaluation/Metrics/Dependence.py +21 -0
  33. evaluation/Metrics/__pycache__/Dependence.cpython-310.pyc +0 -0
  34. evaluation/Metrics/__pycache__/cub_Alignment.cpython-310.pyc +0 -0
  35. evaluation/Metrics/cub_Alignment.py +30 -0
  36. evaluation/__pycache__/diversity.cpython-310.pyc +0 -0
  37. evaluation/__pycache__/helpers.cpython-310.pyc +0 -0
  38. evaluation/__pycache__/qsenn_metrics.cpython-310.pyc +0 -0
  39. evaluation/__pycache__/utils.cpython-310.pyc +0 -0
  40. evaluation/diversity.py +111 -0
  41. evaluation/helpers.py +6 -0
  42. evaluation/qsenn_metrics.py +39 -0
  43. evaluation/utils.py +57 -0
  44. fig/AutoML4FAS_Logo.jpeg +0 -0
  45. fig/Bund.png +0 -0
  46. fig/LUH.png +0 -0
  47. fig/birds.png +0 -0
  48. finetuning/map_function.py +11 -0
  49. finetuning/qsenn.py +30 -0
  50. 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
+