commit from qixuan
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +1 -0
- DIC.py +17 -0
- FeatureDiversityLoss.py +59 -0
- ReadME.md +138 -0
- __pycache__/get_data.cpython-310.pyc +0 -0
- __pycache__/load_model.cpython-310.pyc +0 -0
- __pycache__/visualization.cpython-310.pyc +0 -0
- __pycache__/visualization_gary.cpython-310.pyc +0 -0
- app.py +87 -0
- architectures/FinalLayer.py +36 -0
- architectures/SLDDLevel.py +37 -0
- architectures/__pycache__/FinalLayer.cpython-310.pyc +0 -0
- architectures/__pycache__/SLDDLevel.cpython-310.pyc +0 -0
- architectures/__pycache__/model_mapping.cpython-310.pyc +0 -0
- architectures/__pycache__/resnet.cpython-310.pyc +0 -0
- architectures/__pycache__/utils.cpython-310.pyc +0 -0
- architectures/model_mapping.py +7 -0
- architectures/resnet.py +420 -0
- architectures/utils.py +17 -0
- configs/__pycache__/dataset_params.cpython-310.pyc +0 -0
- configs/__pycache__/optim_params.cpython-310.pyc +0 -0
- configs/architecture_params.py +1 -0
- configs/dataset_params.py +22 -0
- configs/optim_params.py +22 -0
- configs/qsenn_training_params.py +11 -0
- configs/sldd_training_params.py +17 -0
- dataset_classes/__pycache__/cub200.cpython-310.pyc +0 -0
- dataset_classes/__pycache__/stanfordcars.cpython-310.pyc +0 -0
- dataset_classes/__pycache__/travelingbirds.cpython-310.pyc +0 -0
- dataset_classes/__pycache__/utils.cpython-310.pyc +0 -0
- dataset_classes/cub200.py +96 -0
- dataset_classes/stanfordcars.py +121 -0
- dataset_classes/travelingbirds.py +59 -0
- dataset_classes/utils.py +16 -0
- environment.yml +117 -0
- evaluation/Metrics/Dependence.py +21 -0
- evaluation/Metrics/__pycache__/Dependence.cpython-310.pyc +0 -0
- evaluation/Metrics/__pycache__/cub_Alignment.cpython-310.pyc +0 -0
- evaluation/Metrics/cub_Alignment.py +30 -0
- evaluation/__pycache__/diversity.cpython-310.pyc +0 -0
- evaluation/__pycache__/helpers.cpython-310.pyc +0 -0
- evaluation/__pycache__/qsenn_metrics.cpython-310.pyc +0 -0
- evaluation/__pycache__/utils.cpython-310.pyc +0 -0
- evaluation/diversity.py +111 -0
- evaluation/helpers.py +6 -0
- evaluation/qsenn_metrics.py +39 -0
- evaluation/utils.py +57 -0
- fig/AutoML4FAS_Logo.jpeg +0 -0
- fig/Bund.png +0 -0
- fig/LUH.png +0 -0
.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