Haaribo commited on
Commit
dc96f30
·
1 Parent(s): def1313

commit from qixuan

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. DIC.py +17 -0
  3. FeatureDiversityLoss.py +59 -0
  4. ReadME.md +138 -0
  5. __pycache__/get_data.cpython-310.pyc +0 -0
  6. __pycache__/load_model.cpython-310.pyc +0 -0
  7. __pycache__/visualization.cpython-310.pyc +0 -0
  8. __pycache__/visualization_gary.cpython-310.pyc +0 -0
  9. app.py +87 -0
  10. architectures/FinalLayer.py +36 -0
  11. architectures/SLDDLevel.py +37 -0
  12. architectures/__pycache__/FinalLayer.cpython-310.pyc +0 -0
  13. architectures/__pycache__/SLDDLevel.cpython-310.pyc +0 -0
  14. architectures/__pycache__/model_mapping.cpython-310.pyc +0 -0
  15. architectures/__pycache__/resnet.cpython-310.pyc +0 -0
  16. architectures/__pycache__/utils.cpython-310.pyc +0 -0
  17. architectures/model_mapping.py +7 -0
  18. architectures/resnet.py +420 -0
  19. architectures/utils.py +17 -0
  20. configs/__pycache__/dataset_params.cpython-310.pyc +0 -0
  21. configs/__pycache__/optim_params.cpython-310.pyc +0 -0
  22. configs/architecture_params.py +1 -0
  23. configs/dataset_params.py +22 -0
  24. configs/optim_params.py +22 -0
  25. configs/qsenn_training_params.py +11 -0
  26. configs/sldd_training_params.py +17 -0
  27. dataset_classes/__pycache__/cub200.cpython-310.pyc +0 -0
  28. dataset_classes/__pycache__/stanfordcars.cpython-310.pyc +0 -0
  29. dataset_classes/__pycache__/travelingbirds.cpython-310.pyc +0 -0
  30. dataset_classes/__pycache__/utils.cpython-310.pyc +0 -0
  31. dataset_classes/cub200.py +96 -0
  32. dataset_classes/stanfordcars.py +121 -0
  33. dataset_classes/travelingbirds.py +59 -0
  34. dataset_classes/utils.py +16 -0
  35. environment.yml +117 -0
  36. evaluation/Metrics/Dependence.py +21 -0
  37. evaluation/Metrics/__pycache__/Dependence.cpython-310.pyc +0 -0
  38. evaluation/Metrics/__pycache__/cub_Alignment.cpython-310.pyc +0 -0
  39. evaluation/Metrics/cub_Alignment.py +30 -0
  40. evaluation/__pycache__/diversity.cpython-310.pyc +0 -0
  41. evaluation/__pycache__/helpers.cpython-310.pyc +0 -0
  42. evaluation/__pycache__/qsenn_metrics.cpython-310.pyc +0 -0
  43. evaluation/__pycache__/utils.cpython-310.pyc +0 -0
  44. evaluation/diversity.py +111 -0
  45. evaluation/helpers.py +6 -0
  46. evaluation/qsenn_metrics.py +39 -0
  47. evaluation/utils.py +57 -0
  48. fig/AutoML4FAS_Logo.jpeg +0 -0
  49. fig/Bund.png +0 -0
  50. fig/LUH.png +0 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.jpg filter=lfs diff=lfs merge=lfs -text
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
+
ReadME.md ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Q-SENN - Quantized Self-Explaining Neural Networks
2
+
3
+ This repository contains the code for the AAAI 2024 paper
4
+ [*Q-SENN: Quantized Self-Explaining Neural Network*](https://ojs.aaai.org/index.php/AAAI/article/view/30145) by Thomas
5
+ Norrenbrock ,
6
+ Marco Rudolph,
7
+ and Bodo Rosenhahn.
8
+ Additonally, the SLDD-model from [*Take 5:
9
+ Interpretable Image Classification with a Handful of Features*](https://arxiv.org/pdf/2303.13166) (NeurIPS
10
+ Workshop) from the same authors is included.
11
+
12
+
13
+ <p align="center">
14
+ <img width="400" height="400" src="fig/birds.png">
15
+ </p>
16
+
17
+ ---
18
+ Abstract:
19
+ >Explanations in Computer Vision are often desired, but most Deep Neural Networks can only provide saliency maps with questionable faithfulness. Self-Explaining Neural Networks (SENN) extract interpretable concepts with fidelity, diversity, and grounding to combine them linearly for decision-making. While they can explain what was recognized, initial realizations lack accuracy and general applicability. We propose the Quantized-Self-Explaining Neural Network Q-SENN. Q-SENN satisfies or exceeds the desiderata of SENN while being applicable to more complex datasets and maintaining most or all of the accuracy of an uninterpretable baseline model, out-performing previous work in all considered metrics. Q-SENN describes the relationship between every class and feature as either positive, negative or neutral instead of an arbitrary number of possible relations, enforcing more binary human-friendly features. Since every class is assigned just 5 interpretable features on average, Q-SENN shows convincing local and global interpretability. Additionally, we propose a feature alignment method, capable of aligning learned features with human language-based concepts without additional supervision. Thus, what is learned can be more easily verbalized.
20
+
21
+
22
+
23
+
24
+ ---
25
+
26
+ ## Installation
27
+ You will need the usual libaries for deep learning, e.g. pytorch,
28
+ torchvision, numpy, etc. Additionally, we use
29
+ [GLM-Saga](https://github.com/MadryLab/glm_saga) that can be installed via pip.
30
+ In case you are lazy (or like to spend your time otherwise), a suitable
31
+ environment can be created using [Anaconda](https://www.anaconda.com/) and the
32
+ provided environment.yml file:
33
+ ```shell
34
+ conda env create -f environment.yml
35
+ ```
36
+
37
+ ## Data
38
+ Supported datasets are:
39
+ - [Cub2011](https://www.vision.caltech.edu/datasets/cub_200_2011/)
40
+ - [StanfordCars](https://ai.stanford.edu/~jkrause/cars/car_dataset.html)
41
+ - [TravelingBirds](https://worksheets.codalab.org/bundles/0x518829de2aa440c79cd9d75ef6669f27)
42
+ - [ImageNet](https://www.image-net.org/)
43
+
44
+ To use the data for training, the datasets have to be downloaded and put into the
45
+ respective folder under ~/tmp/datasets such that the final structure looks like
46
+
47
+ ```shell
48
+ ~/tmp/datasets
49
+ ├── CUB200
50
+ │   └── CUB_200_2011
51
+ │   ├── ...
52
+ ├── StanfordCars
53
+ │   ├── stanford_cars
54
+ │      ├── ...
55
+ ├── TravelingBirds
56
+ │   ├── CUB_fixed
57
+ │      ├── ...
58
+ ├── imagenet
59
+ │   ├── ...
60
+ ```
61
+
62
+ The default paths could be changed in the dataset_classes or for Imagenet in
63
+ get_data.py
64
+
65
+ Note:
66
+ If cropped images, like for PIP-Net, ProtoPool, etc. are desired, then the
67
+ crop_root should be set to a folder containing the cropped images in the
68
+ expected structure, obtained by following ProtoTree's instructions:
69
+ https://github.com/M-Nauta/ProtoTree/blob/main/README.md#preprocessing-cub,
70
+ default path is: PPCUB200 instead of CUB200 for Protopool. Using these images
71
+ can be set using an additional flag `--cropGT` introduced later.
72
+
73
+
74
+
75
+ ## Usage
76
+ The code to create a Q-SENN model can be started from the file main.py.
77
+ Available parameters are:
78
+ - `--dataset`: The dataset to use. Default: Cub2011
79
+ - `--arch`: The backbone to use. Default: resnet50
80
+ - `--model_type`: The model type to use. Default: qsenn
81
+ - `--seed`: The seed to use. Default: None
82
+ - `--do_dense`: Whether to train the dense model. Default: True
83
+ - `--cropGT`: Whether to crop CUB/TravelingBirds based on GT Boundaries. Default: False
84
+ - `--n_features`: How many features to select. Default: 50
85
+ - `--n_per_class`: How many features to assign to each class. Default: 5
86
+ - `--img_size`: Image size. Default: 448
87
+ - `--reduced_strides`: Whether to use reduced strides for resnets. Default: False
88
+
89
+
90
+ For Example the next command will start the creation of Q-SENN with resnet50 on
91
+ StanfordCars using the default arguments in the paper.
92
+ ```shell
93
+ python main.py --dataset StanfordCars
94
+ ```
95
+
96
+ **Note:**
97
+ All experiments on ImageNet in the paper skipped the dense training from
98
+ scratch on ImageNet. The pretrained models are used directly.
99
+ This can be replicated with the argument --do-dense False.
100
+ ## Citations
101
+ Please cite this work as:\
102
+ Q-SENN
103
+ ```bibtex
104
+ @inproceedings{norrenbrock2024q,
105
+ title={Q-senn: Quantized self-explaining neural networks},
106
+ author={Norrenbrock, Thomas and Rudolph, Marco and Rosenhahn, Bodo},
107
+ booktitle={Proceedings of the AAAI Conference on Artificial Intelligence},
108
+ volume={38},
109
+ number={19},
110
+ pages={21482--21491},
111
+ year={2024}
112
+ }
113
+ ```
114
+ SLDD-Model
115
+ ```bibtex
116
+ @inproceedings{norrenbrocktake,
117
+ title={Take 5: Interpretable Image Classification with a Handful of Features},
118
+ author={Norrenbrock, Thomas and Rudolph, Marco and Rosenhahn, Bodo},
119
+ year={2022},
120
+ booktitle={Progress and Challenges in Building Trustworthy Embodied AI}
121
+ }
122
+ ```
123
+ ## Pretrained Model
124
+ One pretrained model for Q-SENN on CUB can be obtained via this link: https://drive.google.com/drive/folders/1agWqKhcWOVWueV4Fzaowr80lQroCJFYn?usp=drive_link
125
+ ## Acknowledgement
126
+ This work was supported by the Federal Ministry of Education and Research (BMBF), Germany under the AI service center KISSKI (grant no. 01IS22093C) and the Deutsche Forschungsgemeinschaft (DFG) under Germany’s Excellence Strategy within the Cluster of Excellence PhoenixD (EXC 2122).
127
+ This work was partially supported by Intel Corporation and by the German Federal Ministry
128
+ of the Environment, Nature Conservation, Nuclear Safety
129
+ and Consumer Protection (GreenAutoML4FAS project no.
130
+ 67KI32007A).
131
+
132
+ The work was done at the Leibniz University Hannover and published at AAAI 2024.
133
+
134
+ <p align="center">
135
+ <img width="100" height="100" src="fig/AutoML4FAS_Logo.jpeg">
136
+ <img width="300" height="100" src="fig/Bund.png">
137
+ <img width="300" height="100" src="fig/LUH.png">
138
+ </p>
__pycache__/get_data.cpython-310.pyc ADDED
Binary file (3.46 kB). View file
 
__pycache__/load_model.cpython-310.pyc ADDED
Binary file (2.74 kB). View file
 
__pycache__/visualization.cpython-310.pyc ADDED
Binary file (10.2 kB). View file
 
__pycache__/visualization_gary.cpython-310.pyc ADDED
Binary file (9.65 kB). View file
 
app.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from visualization_gary import*
3
+
4
+ # 定义模式选择操作,加载不同的界面
5
+ def choose_mode(selected_mode):
6
+ if selected_mode == "get interpretable Result ":
7
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), "in Mode 1"
8
+ elif selected_mode == "To give Feedbacks":
9
+ return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), "in Mode 2"
10
+ else:
11
+ return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), "please choose a mode"
12
+
13
+ # clear if reupload
14
+ def reset_state_mod1():
15
+ return None, None, "State has been reset."
16
+
17
+ def reset_state_mod2():
18
+ return None, None,None,gr.update(interactive=True),gr.update(interactive=True),gr.update(interactive=True),gr.update(interactive=True),gr.update(interactive=True)
19
+
20
+ # 主界面,包含模式选择和每种模式的界面
21
+ with gr.Blocks() as demo:
22
+ gr.Markdown("# please choose a mode")
23
+
24
+ # 模式选择器
25
+ mode_selector = gr.Radio(["get interpretable Result ", "To give Feedbacks"], label="Mode Selection")
26
+
27
+ # 模式1界面
28
+ with gr.Column(visible=False) as mode1_ui:
29
+ gr.Markdown("### Please keep the object in the center of your image, click the Button to get interpretebale Result")
30
+ mode1_button = gr.Button("get interpretable Classification")
31
+ mode1_input_img=gr.Image()
32
+ mode1_output_img= gr.Image()
33
+ mode1_output_txt=gr.Markdown()
34
+
35
+ #clear if reupload
36
+ mode1_state = gr.State()
37
+ mode1_input_img.upload(fn=reset_state_mod1, outputs=[mode1_output_img, mode1_state, mode1_output_txt])
38
+ #clear if reupload
39
+
40
+
41
+ mode1_button.click(fn=direct_inference,inputs=mode1_input_img, outputs=[mode1_output_img,mode1_output_txt])
42
+
43
+ # 模式2界面
44
+ with gr.Column(visible=False) as mode2_ui:
45
+ gr.Markdown("### Please keep the object in the center of your image, click the Button 'Get some interpretable Features' to get options")
46
+ image_input=gr.Image()
47
+ gallery_output = gr.Gallery(label="Initial Label")
48
+ text_output=gr.Markdown()
49
+ but_generate=gr.Button("Get some interpretable Features")
50
+ but_feedback_A=gr.Button("A")
51
+ but_feedback_B=gr.Button("B")
52
+ but_feedback_C=gr.Button("C")
53
+ but_feedback_D=gr.Button("D")
54
+
55
+ key_op = gr.State()
56
+ A= gr.State("A")
57
+ B= gr.State("B")
58
+ C= gr.State("C")
59
+ D= gr.State("D")
60
+
61
+ #clear if reupload
62
+ image_input.upload(
63
+ fn=reset_state_mod2,
64
+ outputs=[gallery_output, key_op, text_output,but_generate,but_feedback_A,but_feedback_B,but_feedback_C,but_feedback_D]
65
+ )
66
+
67
+ #clear if reupload
68
+
69
+
70
+
71
+ but_generate.click(fn=get_features_on_interface, inputs=image_input, outputs=[gallery_output,key_op,text_output,but_generate])
72
+
73
+ but_feedback_A.click(fn=post_next_image, inputs=[A,key_op], outputs=[text_output,but_feedback_A,but_feedback_B,but_feedback_C,but_feedback_D])
74
+ but_feedback_B.click(fn=post_next_image, inputs=[B,key_op], outputs=[text_output,but_feedback_A,but_feedback_B,but_feedback_C,but_feedback_D])
75
+ but_feedback_C.click(fn=post_next_image, inputs=[C,key_op], outputs=[text_output,but_feedback_A,but_feedback_B,but_feedback_C,but_feedback_D])
76
+ but_feedback_D.click(fn=post_next_image, inputs=[D,key_op], outputs=[text_output,but_feedback_A,but_feedback_B,but_feedback_C,but_feedback_D])
77
+ # but_feedback_B.click(fn=post_next_image, inputs=image_list, outputs=[image_list,image_output,text_output])
78
+
79
+
80
+
81
+ # 状态输出
82
+ status_output = gr.Textbox(label="Status")
83
+
84
+ # 选择器点击事件绑定
85
+ mode_selector.change(choose_mode, inputs=mode_selector, outputs=[mode1_ui, mode2_ui, status_output])
86
+
87
+ demo.launch()
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/qixuan/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