Spaces:
Runtime error
Runtime error
Update
Browse files- app.py +5 -5
- augment.py +8 -2
- calculate.py +18 -0
- combine_dats.py → combine_data.py +22 -21
- configs.py +187 -30
- convert.py +1 -1
- data-splitting.py +23 -0
- data_loader.py +19 -11
- ensemble.py +249 -0
- eval.py +137 -55
- eval_orig.py +181 -0
- extract-ensemble.py +110 -0
- extract.py +50 -0
- genetric_algorithm.py +248 -0
- lazy_predict.py +60 -0
- models.py +28 -0
- plot-gradcam.py +30 -0
- predict.py +20 -7
- shap_eval.py +49 -0
- test.py +213 -29
- testing.py +5 -0
- train-svm.py +101 -0
- train.py +103 -33
- tuning.py +123 -46
- weight_averaging.py +235 -0
app.py
CHANGED
@@ -45,15 +45,15 @@ demo = gr.Interface(
|
|
45 |
theme="gradio/soft",
|
46 |
fn=process_file,
|
47 |
title="HANDETECT",
|
48 |
-
description=generate_description,
|
49 |
inputs=[
|
50 |
gr.components.Image(type="filepath", label="Choose Image", source="upload"),
|
51 |
],
|
52 |
outputs=[
|
53 |
-
gr.outputs.Textbox(label="
|
54 |
-
gr.outputs.Textbox(label="
|
55 |
-
gr.outputs.Textbox(label="
|
56 |
],
|
57 |
)
|
58 |
|
59 |
-
demo.launch()
|
|
|
45 |
theme="gradio/soft",
|
46 |
fn=process_file,
|
47 |
title="HANDETECT",
|
48 |
+
# description=generate_description,
|
49 |
inputs=[
|
50 |
gr.components.Image(type="filepath", label="Choose Image", source="upload"),
|
51 |
],
|
52 |
outputs=[
|
53 |
+
gr.outputs.Textbox(label="Probability 1"),
|
54 |
+
gr.outputs.Textbox(label="Probability 2"),
|
55 |
+
gr.outputs.Textbox(label="Probability 3"),
|
56 |
],
|
57 |
)
|
58 |
|
59 |
+
demo.launch(inbrowser=True)
|
augment.py
CHANGED
@@ -5,8 +5,13 @@ from configs import *
|
|
5 |
import uuid
|
6 |
|
7 |
tasks = ["1", "2", "3", "4", "5", "6"]
|
|
|
|
|
|
|
|
|
8 |
|
9 |
for task in ["1"]:
|
|
|
10 |
# Loop through all folders in Task 1 and generate augmented images for each class
|
11 |
for class_label in [
|
12 |
"Alzheimer Disease",
|
@@ -46,8 +51,7 @@ for task in ["1"]:
|
|
46 |
p.random_contrast(probability=0.8, min_factor=0.5, max_factor=1.5)
|
47 |
p.random_color(probability=0.8, min_factor=0.5, max_factor=1.5)
|
48 |
p.rotate_random_90(probability=0.8)
|
49 |
-
|
50 |
-
p.sample(100 - len(p.augmentor_images))
|
51 |
# Move the folder to data/train/Task 1/augmented
|
52 |
# Create the folder if it does not exist
|
53 |
if not os.path.exists(f"{AUG_DATA_DIR}{task}/"):
|
@@ -67,3 +71,5 @@ for task in ["1"]:
|
|
67 |
f"{AUG_DATA_DIR}{task}/{class_label}/{file}",
|
68 |
f"{AUG_DATA_DIR}{task}/{class_label}/{number}.png",
|
69 |
)
|
|
|
|
|
|
5 |
import uuid
|
6 |
|
7 |
tasks = ["1", "2", "3", "4", "5", "6"]
|
8 |
+
num_of_images = 100
|
9 |
+
|
10 |
+
shutil.rmtree(TEMP_DATA_DIR + "1/", ignore_errors=True)
|
11 |
+
|
12 |
|
13 |
for task in ["1"]:
|
14 |
+
shutil.rmtree(AUG_DATA_DIR + task, ignore_errors=True)
|
15 |
# Loop through all folders in Task 1 and generate augmented images for each class
|
16 |
for class_label in [
|
17 |
"Alzheimer Disease",
|
|
|
51 |
p.random_contrast(probability=0.8, min_factor=0.5, max_factor=1.5)
|
52 |
p.random_color(probability=0.8, min_factor=0.5, max_factor=1.5)
|
53 |
p.rotate_random_90(probability=0.8)
|
54 |
+
p.sample(num_of_images - len(p.augmentor_images))
|
|
|
55 |
# Move the folder to data/train/Task 1/augmented
|
56 |
# Create the folder if it does not exist
|
57 |
if not os.path.exists(f"{AUG_DATA_DIR}{task}/"):
|
|
|
71 |
f"{AUG_DATA_DIR}{task}/{class_label}/{file}",
|
72 |
f"{AUG_DATA_DIR}{task}/{class_label}/{number}.png",
|
73 |
)
|
74 |
+
|
75 |
+
shutil.rmtree(TEMP_DATA_DIR + task, ignore_errors=True)
|
calculate.py
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from scipy.optimize import linprog
|
2 |
+
|
3 |
+
# Coefficients for the objective function (negative because linprog does minimization)
|
4 |
+
c = [-0.88, -0.88, -0.85]
|
5 |
+
|
6 |
+
# Coefficients for the inequality constraint (sum of weights = 1)
|
7 |
+
A = [[1, 1, 1]]
|
8 |
+
b = [1]
|
9 |
+
|
10 |
+
# Bounds for each weight (between 0 and 1)
|
11 |
+
bounds = [(0, 1), (0, 1), (0, 1)]
|
12 |
+
|
13 |
+
# Solve the linear programming problem
|
14 |
+
result = linprog(c, A_eq=A, b_eq=b, bounds=bounds)
|
15 |
+
|
16 |
+
# The optimal weights
|
17 |
+
optimal_weights = result.x
|
18 |
+
print("Optimal weights:", optimal_weights)
|
combine_dats.py → combine_data.py
RENAMED
@@ -5,38 +5,39 @@ import uuid
|
|
5 |
|
6 |
from configs import *
|
7 |
|
|
|
|
|
8 |
for disease in CLASSES:
|
9 |
# check if the original folder exists
|
10 |
-
if os.path.exists(RAW_DATA_DIR +
|
11 |
print("Copying raw data for disease: ", disease)
|
12 |
-
if not os.path.exists(COMBINED_DATA_DIR +
|
13 |
-
os.makedirs(COMBINED_DATA_DIR +
|
14 |
-
for file in os.listdir(RAW_DATA_DIR +
|
15 |
random_name = str(uuid.uuid4()) + ".png"
|
16 |
shutil.copy(
|
17 |
-
RAW_DATA_DIR +
|
18 |
-
COMBINED_DATA_DIR +
|
19 |
)
|
20 |
-
|
21 |
-
if os.path.exists(EXTERNAL_DATA_DIR +
|
22 |
print("Copying external data for disease: ", disease)
|
23 |
-
if not os.path.exists(COMBINED_DATA_DIR +
|
24 |
-
os.makedirs(COMBINED_DATA_DIR +
|
25 |
-
for file in os.listdir(EXTERNAL_DATA_DIR +
|
26 |
random_name = str(uuid.uuid4()) + ".png"
|
27 |
shutil.copy(
|
28 |
-
EXTERNAL_DATA_DIR +
|
29 |
-
COMBINED_DATA_DIR +
|
30 |
)
|
31 |
-
|
32 |
-
if os.path.exists(AUG_DATA_DIR +
|
33 |
print("Copying augmented data for disease: ", disease)
|
34 |
-
if not os.path.exists(COMBINED_DATA_DIR +
|
35 |
-
os.makedirs(COMBINED_DATA_DIR +
|
36 |
-
for file in os.listdir(AUG_DATA_DIR +
|
37 |
random_name = str(uuid.uuid4()) + ".png"
|
38 |
shutil.copy(
|
39 |
-
AUG_DATA_DIR +
|
40 |
-
COMBINED_DATA_DIR +
|
41 |
)
|
42 |
-
|
|
|
5 |
|
6 |
from configs import *
|
7 |
|
8 |
+
shutil.rmtree(COMBINED_DATA_DIR + "1/", ignore_errors=True)
|
9 |
+
|
10 |
for disease in CLASSES:
|
11 |
# check if the original folder exists
|
12 |
+
if os.path.exists(RAW_DATA_DIR + "1/" + disease):
|
13 |
print("Copying raw data for disease: ", disease)
|
14 |
+
if not os.path.exists(COMBINED_DATA_DIR + "1/" + disease):
|
15 |
+
os.makedirs(COMBINED_DATA_DIR + "1/" + disease)
|
16 |
+
for file in os.listdir(RAW_DATA_DIR + "1/" + disease):
|
17 |
random_name = str(uuid.uuid4()) + ".png"
|
18 |
shutil.copy(
|
19 |
+
RAW_DATA_DIR + "1/" + disease + "/" + file,
|
20 |
+
COMBINED_DATA_DIR + "1/" + disease + "/" + random_name,
|
21 |
)
|
22 |
+
|
23 |
+
if os.path.exists(EXTERNAL_DATA_DIR + "1/" + disease):
|
24 |
print("Copying external data for disease: ", disease)
|
25 |
+
if not os.path.exists(COMBINED_DATA_DIR + "1/" + disease):
|
26 |
+
os.makedirs(COMBINED_DATA_DIR + "1/" + disease)
|
27 |
+
for file in os.listdir(EXTERNAL_DATA_DIR + "1/" + disease):
|
28 |
random_name = str(uuid.uuid4()) + ".png"
|
29 |
shutil.copy(
|
30 |
+
EXTERNAL_DATA_DIR + "1/" + disease + "/" + file,
|
31 |
+
COMBINED_DATA_DIR + "1/" + disease + "/" + random_name,
|
32 |
)
|
33 |
+
|
34 |
+
if os.path.exists(AUG_DATA_DIR + "1/" + disease):
|
35 |
print("Copying augmented data for disease: ", disease)
|
36 |
+
if not os.path.exists(COMBINED_DATA_DIR + "1/" + disease):
|
37 |
+
os.makedirs(COMBINED_DATA_DIR + "1/" + disease)
|
38 |
+
for file in os.listdir(AUG_DATA_DIR + "1/" + disease):
|
39 |
random_name = str(uuid.uuid4()) + ".png"
|
40 |
shutil.copy(
|
41 |
+
AUG_DATA_DIR + "1/" + disease + "/" + file,
|
42 |
+
COMBINED_DATA_DIR + "1/" + disease + "/" + random_name,
|
43 |
)
|
|
configs.py
CHANGED
@@ -13,29 +13,50 @@ from torchvision.models import (
|
|
13 |
ShuffleNet_V2_X2_0_Weights,
|
14 |
mobilenet_v3_small,
|
15 |
MobileNet_V3_Small_Weights,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
)
|
17 |
|
18 |
import torch.nn.functional as F
|
19 |
-
from pytorchcv.model_provider import get_model as ptcv_get_model
|
20 |
|
21 |
# Constants
|
22 |
RANDOM_SEED = 123
|
23 |
-
BATCH_SIZE =
|
24 |
-
NUM_EPOCHS =
|
25 |
-
|
|
|
26 |
STEP_SIZE = 10
|
27 |
-
GAMMA = 0.
|
28 |
-
|
|
|
|
|
29 |
NUM_PRINT = 100
|
30 |
TASK = 1
|
|
|
31 |
RAW_DATA_DIR = r"data/train/raw/Task "
|
32 |
AUG_DATA_DIR = r"data/train/augmented/Task "
|
33 |
EXTERNAL_DATA_DIR = r"data/train/external/Task "
|
34 |
COMBINED_DATA_DIR = r"data/train/combined/Task "
|
35 |
-
|
|
|
36 |
NUM_CLASSES = 7
|
37 |
LABEL_SMOOTHING_EPSILON = 0.1
|
38 |
-
MIXUP_ALPHA = 0.2
|
39 |
EARLY_STOPPING_PATIENCE = 20
|
40 |
CLASSES = [
|
41 |
"Alzheimer Disease",
|
@@ -46,12 +67,29 @@ CLASSES = [
|
|
46 |
"Huntington Disease",
|
47 |
"Parkinson Disease",
|
48 |
]
|
49 |
-
MODEL_SAVE_PATH = r"output/checkpoints/model.pth"
|
50 |
|
51 |
|
52 |
-
class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
def __init__(self, num_classes, dropout_prob=0.5):
|
54 |
-
super(
|
55 |
squeezenet = squeezenet1_0(weights=SqueezeNet1_0_Weights.DEFAULT)
|
56 |
self.features = squeezenet.features
|
57 |
self.classifier = nn.Sequential(
|
@@ -64,17 +102,26 @@ class SqueezeNet1_0WithDropout(nn.Module):
|
|
64 |
dropout_prob
|
65 |
) # Add dropout layer with the specified probability
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
def forward(self, x):
|
68 |
x = self.features(x)
|
69 |
x = self.classifier(x)
|
|
|
70 |
x = F.dropout(x, training=self.training) # Apply dropout during training
|
71 |
x = torch.flatten(x, 1)
|
72 |
return x
|
73 |
|
74 |
|
75 |
-
class
|
76 |
-
def __init__(self, num_classes, dropout_prob=0.
|
77 |
-
super(
|
78 |
squeezenet = squeezenet1_1(weights=SqueezeNet1_1_Weights.DEFAULT)
|
79 |
self.features = squeezenet.features
|
80 |
self.classifier = nn.Sequential(
|
@@ -87,21 +134,26 @@ class SqueezeNet1_1WithDropout(nn.Module):
|
|
87 |
dropout_prob
|
88 |
) # Add dropout layer with the specified probability
|
89 |
|
|
|
|
|
|
|
90 |
def forward(self, x):
|
91 |
x = self.features(x)
|
92 |
x = self.classifier(x)
|
|
|
93 |
x = F.dropout(x, training=self.training) # Apply dropout during training
|
94 |
x = torch.flatten(x, 1)
|
95 |
return x
|
96 |
|
97 |
|
98 |
-
class
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
|
|
103 |
self.classifier = nn.Sequential(
|
104 |
-
nn.Conv2d(
|
105 |
nn.BatchNorm2d(num_classes), # add batch normalization
|
106 |
nn.ReLU(inplace=True),
|
107 |
nn.AdaptiveAvgPool2d((1, 1)),
|
@@ -118,13 +170,62 @@ class ShuffleNetV2WithDropout(nn.Module):
|
|
118 |
return x
|
119 |
|
120 |
|
121 |
-
class
|
122 |
-
def __init__(self, num_classes, dropout_prob=0.
|
123 |
-
super(
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
self.features = mobilenet.features
|
126 |
self.classifier = nn.Sequential(
|
127 |
-
nn.Conv2d(
|
128 |
nn.BatchNorm2d(num_classes), # add batch normalization
|
129 |
nn.ReLU(inplace=True),
|
130 |
nn.AdaptiveAvgPool2d((1, 1)),
|
@@ -141,15 +242,60 @@ class MobileNetV3SmallWithDropout(nn.Module):
|
|
141 |
return x
|
142 |
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
147 |
|
|
|
|
|
|
|
148 |
preprocess = transforms.Compose(
|
149 |
[
|
150 |
-
transforms.Resize((
|
151 |
transforms.ToTensor(), # Convert to tensor
|
152 |
-
transforms.Grayscale(num_output_channels=3), # Convert to 3 channels
|
153 |
# Normalize 3 channels
|
154 |
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
|
155 |
]
|
@@ -167,3 +313,14 @@ class CustomDataset(Dataset):
|
|
167 |
def __getitem__(self, idx):
|
168 |
img, label = self.data[idx]
|
169 |
return img, label
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
ShuffleNet_V2_X2_0_Weights,
|
14 |
mobilenet_v3_small,
|
15 |
MobileNet_V3_Small_Weights,
|
16 |
+
efficientnet_v2_s,
|
17 |
+
EfficientNet_V2_S_Weights,
|
18 |
+
efficientnet_b0,
|
19 |
+
EfficientNet_B0_Weights,
|
20 |
+
efficientnet_b1,
|
21 |
+
EfficientNet_B1_Weights,
|
22 |
+
efficientnet_b2,
|
23 |
+
EfficientNet_B2_Weights,
|
24 |
+
efficientnet_b3,
|
25 |
+
EfficientNet_B3_Weights,
|
26 |
+
mobilenet_v3_small,
|
27 |
+
MobileNet_V3_Small_Weights,
|
28 |
+
mobilenet_v3_large,
|
29 |
+
MobileNet_V3_Large_Weights,
|
30 |
+
googlenet,
|
31 |
+
GoogLeNet_Weights,
|
32 |
+
MobileNet_V2_Weights,
|
33 |
+
mobilenet_v2,
|
34 |
)
|
35 |
|
36 |
import torch.nn.functional as F
|
|
|
37 |
|
38 |
# Constants
|
39 |
RANDOM_SEED = 123
|
40 |
+
BATCH_SIZE = 8
|
41 |
+
NUM_EPOCHS = 150
|
42 |
+
WARMUP_EPOCHS = 5
|
43 |
+
LEARNING_RATE = 0.0001
|
44 |
STEP_SIZE = 10
|
45 |
+
GAMMA = 0.3
|
46 |
+
CUTMIX_ALPHA = 0.3
|
47 |
+
# DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
48 |
+
DEVICE = torch.device("cpu")
|
49 |
NUM_PRINT = 100
|
50 |
TASK = 1
|
51 |
+
WARMUP_EPOCHS = 5
|
52 |
RAW_DATA_DIR = r"data/train/raw/Task "
|
53 |
AUG_DATA_DIR = r"data/train/augmented/Task "
|
54 |
EXTERNAL_DATA_DIR = r"data/train/external/Task "
|
55 |
COMBINED_DATA_DIR = r"data/train/combined/Task "
|
56 |
+
TEST_DATA_DIR = r"data/test/Task "
|
57 |
+
TEMP_DATA_DIR = "data/temp/Task "
|
58 |
NUM_CLASSES = 7
|
59 |
LABEL_SMOOTHING_EPSILON = 0.1
|
|
|
60 |
EARLY_STOPPING_PATIENCE = 20
|
61 |
CLASSES = [
|
62 |
"Alzheimer Disease",
|
|
|
67 |
"Huntington Disease",
|
68 |
"Parkinson Disease",
|
69 |
]
|
|
|
70 |
|
71 |
|
72 |
+
class SE_Block(nn.Module):
|
73 |
+
def __init__(self, channel, reduction=16):
|
74 |
+
super(SE_Block, self).__init__()
|
75 |
+
self.avg_pool = nn.AdaptiveAvgPool2d(1)
|
76 |
+
self.fc = nn.Sequential(
|
77 |
+
nn.Linear(channel, channel // reduction, bias=False),
|
78 |
+
nn.ReLU(inplace=True),
|
79 |
+
nn.Linear(channel // reduction, channel, bias=False),
|
80 |
+
nn.Sigmoid(), # Sigmoid activation to produce attention scores
|
81 |
+
)
|
82 |
+
|
83 |
+
def forward(self, x):
|
84 |
+
b, c, _, _ = x.size()
|
85 |
+
y = self.avg_pool(x).view(b, c)
|
86 |
+
y = self.fc(y).view(b, c, 1, 1)
|
87 |
+
return x * y.expand_as(x)
|
88 |
+
|
89 |
+
|
90 |
+
class SqueezeNet1_0WithSE(nn.Module):
|
91 |
def __init__(self, num_classes, dropout_prob=0.5):
|
92 |
+
super(SqueezeNet1_0WithSE, self).__init__()
|
93 |
squeezenet = squeezenet1_0(weights=SqueezeNet1_0_Weights.DEFAULT)
|
94 |
self.features = squeezenet.features
|
95 |
self.classifier = nn.Sequential(
|
|
|
102 |
dropout_prob
|
103 |
) # Add dropout layer with the specified probability
|
104 |
|
105 |
+
# Adjust channel for SqueezeNet1.0 (original SqueezeNet1.0 has 1000 classes)
|
106 |
+
num_classes_squeezenet1_0 = 7
|
107 |
+
|
108 |
+
# Add Squeeze-and-Excitation block
|
109 |
+
self.se_block = SE_Block(
|
110 |
+
channel=num_classes_squeezenet1_0
|
111 |
+
) # Adjust channel as needed
|
112 |
+
|
113 |
def forward(self, x):
|
114 |
x = self.features(x)
|
115 |
x = self.classifier(x)
|
116 |
+
# x = self.se_block(x) # Apply the SE block
|
117 |
x = F.dropout(x, training=self.training) # Apply dropout during training
|
118 |
x = torch.flatten(x, 1)
|
119 |
return x
|
120 |
|
121 |
|
122 |
+
class SqueezeNet1_1WithSE(nn.Module):
|
123 |
+
def __init__(self, num_classes, dropout_prob=0.2):
|
124 |
+
super(SqueezeNet1_1WithSE, self).__init__()
|
125 |
squeezenet = squeezenet1_1(weights=SqueezeNet1_1_Weights.DEFAULT)
|
126 |
self.features = squeezenet.features
|
127 |
self.classifier = nn.Sequential(
|
|
|
134 |
dropout_prob
|
135 |
) # Add dropout layer with the specified probability
|
136 |
|
137 |
+
# Add Squeeze-and-Excitation block
|
138 |
+
self.se_block = SE_Block(channel=num_classes) # Adjust channel as needed
|
139 |
+
|
140 |
def forward(self, x):
|
141 |
x = self.features(x)
|
142 |
x = self.classifier(x)
|
143 |
+
x = self.se_block(x) # Apply the SE block
|
144 |
x = F.dropout(x, training=self.training) # Apply dropout during training
|
145 |
x = torch.flatten(x, 1)
|
146 |
return x
|
147 |
|
148 |
|
149 |
+
class EfficientNetB2WithDropout(nn.Module):
|
150 |
+
# 0.00022015769999619205
|
151 |
+
def __init__(self, num_classes, dropout_prob=0.2):
|
152 |
+
super(EfficientNetB2WithDropout, self).__init__()
|
153 |
+
efficientnet = efficientnet_b2(weights=EfficientNet_B2_Weights.DEFAULT)
|
154 |
+
self.features = efficientnet.features
|
155 |
self.classifier = nn.Sequential(
|
156 |
+
nn.Conv2d(1408, num_classes, kernel_size=1),
|
157 |
nn.BatchNorm2d(num_classes), # add batch normalization
|
158 |
nn.ReLU(inplace=True),
|
159 |
nn.AdaptiveAvgPool2d((1, 1)),
|
|
|
170 |
return x
|
171 |
|
172 |
|
173 |
+
class EfficientNetB3WithDropout(nn.Module):
|
174 |
+
def __init__(self, num_classes, dropout_prob=0.2):
|
175 |
+
super(EfficientNetB3WithDropout, self).__init__()
|
176 |
+
efficientnet = efficientnet_b3(weights=EfficientNet_B3_Weights.DEFAULT)
|
177 |
+
self.features = efficientnet.features
|
178 |
+
self.classifier = nn.Sequential(
|
179 |
+
nn.Conv2d(1536, num_classes, kernel_size=1),
|
180 |
+
nn.BatchNorm2d(num_classes), # add batch normalization
|
181 |
+
nn.ReLU(inplace=True),
|
182 |
+
nn.AdaptiveAvgPool2d((1, 1)),
|
183 |
+
)
|
184 |
+
self.dropout = nn.Dropout(
|
185 |
+
dropout_prob
|
186 |
+
) # Add dropout layer with the specified probability
|
187 |
+
|
188 |
+
def forward(self, x):
|
189 |
+
x = self.features(x)
|
190 |
+
x = self.classifier(x)
|
191 |
+
x = F.dropout(x, training=self.training) # Apply dropout during training
|
192 |
+
x = torch.flatten(x, 1)
|
193 |
+
return x
|
194 |
+
|
195 |
+
|
196 |
+
class ResNet18WithNorm(nn.Module):
|
197 |
+
def __init__(self, num_classes=1000):
|
198 |
+
super(ResNet18WithNorm, self).__init__()
|
199 |
+
resnet = resnet18(pretrained=False)
|
200 |
+
|
201 |
+
# Remove the last block (Block 4)
|
202 |
+
self.features = nn.Sequential(
|
203 |
+
*list(resnet.children())[:-1] # Exclude the last block
|
204 |
+
)
|
205 |
+
|
206 |
+
self.classifier = nn.Sequential(
|
207 |
+
nn.AdaptiveAvgPool2d((1, 1)),
|
208 |
+
nn.Flatten(),
|
209 |
+
nn.Linear(
|
210 |
+
512, num_classes
|
211 |
+
), # Adjust input size for the fully connected layer
|
212 |
+
nn.BatchNorm1d(num_classes), # Add batch normalization
|
213 |
+
)
|
214 |
+
|
215 |
+
def forward(self, x):
|
216 |
+
x = self.features(x)
|
217 |
+
x = self.classifier(x)
|
218 |
+
x = torch.flatten(x, 1)
|
219 |
+
return x
|
220 |
+
|
221 |
+
|
222 |
+
class MobileNetV3LargeWithDropout(nn.Module):
|
223 |
+
def __init__(self, num_classes, dropout_prob=0.2):
|
224 |
+
super(MobileNetV3LargeWithDropout, self).__init__()
|
225 |
+
mobilenet = mobilenet_v3_large(weights=MobileNet_V3_Large_Weights.DEFAULT)
|
226 |
self.features = mobilenet.features
|
227 |
self.classifier = nn.Sequential(
|
228 |
+
nn.Conv2d(960, num_classes, kernel_size=1),
|
229 |
nn.BatchNorm2d(num_classes), # add batch normalization
|
230 |
nn.ReLU(inplace=True),
|
231 |
nn.AdaptiveAvgPool2d((1, 1)),
|
|
|
242 |
return x
|
243 |
|
244 |
|
245 |
+
class GoogLeNetWithSE(nn.Module):
|
246 |
+
def __init__(self, num_classes):
|
247 |
+
super(GoogLeNetWithSE, self).__init__()
|
248 |
+
googlenet = googlenet(weights=GoogLeNet_Weights.DEFAULT)
|
249 |
+
# self.features = googlenet.features
|
250 |
+
self.classifier = nn.Sequential(
|
251 |
+
nn.Conv2d(1024, num_classes, kernel_size=1),
|
252 |
+
nn.BatchNorm2d(num_classes), # add batch normalization
|
253 |
+
nn.ReLU(inplace=True),
|
254 |
+
nn.AdaptiveAvgPool2d((1, 1)),
|
255 |
+
)
|
256 |
+
|
257 |
+
# Add Squeeze-and-Excitation block
|
258 |
+
self.se_block = SE_Block(channel=num_classes) # Adjust channel as needed
|
259 |
+
|
260 |
+
def forward(self, x):
|
261 |
+
# x = self.features(x)
|
262 |
+
x = self.classifier(x)
|
263 |
+
x = self.se_block(x) # Apply the SE block
|
264 |
+
x = torch.flatten(x, 1)
|
265 |
+
return x
|
266 |
+
|
267 |
+
|
268 |
+
class MobileNetV2WithDropout(nn.Module):
|
269 |
+
def __init__(self, num_classes, dropout_prob=0.2):
|
270 |
+
super(MobileNetV2WithDropout, self).__init__()
|
271 |
+
mobilenet = mobilenet_v2(weights=MobileNet_V2_Weights.DEFAULT)
|
272 |
+
self.features = mobilenet.features
|
273 |
+
self.classifier = nn.Sequential(
|
274 |
+
nn.Conv2d(1280, num_classes, kernel_size=1),
|
275 |
+
nn.BatchNorm2d(num_classes), # add batch normalization
|
276 |
+
nn.ReLU(inplace=True),
|
277 |
+
nn.AdaptiveAvgPool2d((1, 1)),
|
278 |
+
)
|
279 |
+
self.dropout = nn.Dropout(
|
280 |
+
dropout_prob
|
281 |
+
) # Add dropout layer with the specified probability
|
282 |
+
|
283 |
+
def forward(self, x):
|
284 |
+
x = self.features(x)
|
285 |
+
x = self.classifier(x)
|
286 |
+
x = F.dropout(x, training=self.training) # Apply dropout during training
|
287 |
+
x = torch.flatten(x, 1)
|
288 |
+
return x
|
289 |
+
|
290 |
|
291 |
+
MODEL = EfficientNetB3WithDropout(num_classes=NUM_CLASSES)
|
292 |
+
MODEL_SAVE_PATH = r"output/checkpoints/" + MODEL.__class__.__name__ + ".pth"
|
293 |
+
# MODEL_SAVE_PATH = r"C:\Users\User\Downloads\bestsqueezenetSE.pth"
|
294 |
preprocess = transforms.Compose(
|
295 |
[
|
296 |
+
transforms.Resize((224, 224)),
|
297 |
transforms.ToTensor(), # Convert to tensor
|
298 |
+
# transforms.Grayscale(num_output_channels=3), # Convert to 3 channels
|
299 |
# Normalize 3 channels
|
300 |
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
|
301 |
]
|
|
|
313 |
def __getitem__(self, idx):
|
314 |
img, label = self.data[idx]
|
315 |
return img, label
|
316 |
+
|
317 |
+
|
318 |
+
def ensemble_predictions(models, image):
|
319 |
+
all_predictions = []
|
320 |
+
|
321 |
+
with torch.no_grad():
|
322 |
+
for model in models:
|
323 |
+
output = model(image)
|
324 |
+
all_predictions.append(output)
|
325 |
+
|
326 |
+
return torch.stack(all_predictions, dim=0).mean(dim=0)
|
convert.py
CHANGED
@@ -3,7 +3,7 @@ import onnx2tf
|
|
3 |
from configs import *
|
4 |
|
5 |
torch.onnx.export(
|
6 |
-
model=
|
7 |
args=torch.randn(1, 3, 64, 64),
|
8 |
f="output/checkpoints/model.onnx",
|
9 |
verbose=True,
|
|
|
3 |
from configs import *
|
4 |
|
5 |
torch.onnx.export(
|
6 |
+
model=model,
|
7 |
args=torch.randn(1, 3, 64, 64),
|
8 |
f="output/checkpoints/model.onnx",
|
9 |
verbose=True,
|
data-splitting.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Take 10% of the data in data\train\combined\Task 1\<class> and move it to data\test\Task 1\<class>
|
2 |
+
|
3 |
+
import os
|
4 |
+
import shutil
|
5 |
+
import uuid
|
6 |
+
|
7 |
+
from configs import *
|
8 |
+
|
9 |
+
shutil.rmtree(TEST_DATA_DIR + "1/", ignore_errors=True)
|
10 |
+
|
11 |
+
for disease in CLASSES:
|
12 |
+
# check if the original folder exists
|
13 |
+
if os.path.exists(COMBINED_DATA_DIR + "1/" + disease):
|
14 |
+
print("Splitting data for disease: ", disease)
|
15 |
+
if not os.path.exists(TEST_DATA_DIR + "1/" + disease):
|
16 |
+
os.makedirs(TEST_DATA_DIR + "1/" + disease)
|
17 |
+
files = os.listdir(COMBINED_DATA_DIR + "1/" + disease)
|
18 |
+
files.sort()
|
19 |
+
for file in files[: int(len(files) * 0.1)]:
|
20 |
+
shutil.move(
|
21 |
+
COMBINED_DATA_DIR + "1/" + disease + "/" + file,
|
22 |
+
TEST_DATA_DIR + "1/" + disease + "/" + file,
|
23 |
+
)
|
data_loader.py
CHANGED
@@ -1,22 +1,20 @@
|
|
1 |
from configs import *
|
2 |
from torchvision.datasets import ImageFolder
|
3 |
from torch.utils.data import random_split, DataLoader, Dataset
|
|
|
4 |
|
|
|
5 |
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
dataset = raw_dataset + external_dataset + augmented_dataset
|
12 |
|
13 |
# Classes
|
14 |
-
classes =
|
15 |
|
16 |
print("Classes: ", *classes, sep=", ")
|
17 |
-
print("Length of raw dataset: ", len(raw_dataset))
|
18 |
-
print("Length of external dataset: ", len(external_dataset))
|
19 |
-
print("Length of augmented dataset: ", len(augmented_dataset))
|
20 |
print("Length of total dataset: ", len(dataset))
|
21 |
|
22 |
# Split the dataset into train and validation sets
|
@@ -29,8 +27,18 @@ def load_data(raw_dir, augmented_dir, external_dir, preprocess, batch_size=BATCH
|
|
29 |
CustomDataset(train_dataset), batch_size=batch_size, shuffle=True, num_workers=0
|
30 |
)
|
31 |
valid_loader = DataLoader(
|
32 |
-
CustomDataset(val_dataset), batch_size=batch_size, num_workers=0
|
33 |
)
|
34 |
|
35 |
return train_loader, valid_loader
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from configs import *
|
2 |
from torchvision.datasets import ImageFolder
|
3 |
from torch.utils.data import random_split, DataLoader, Dataset
|
4 |
+
import torch
|
5 |
|
6 |
+
torch.manual_seed(RANDOM_SEED)
|
7 |
|
8 |
+
# Set seed
|
9 |
+
torch.manual_seed(RANDOM_SEED)
|
10 |
+
|
11 |
+
def load_data(combined_dir, preprocess, batch_size=BATCH_SIZE):
|
12 |
+
dataset = ImageFolder(combined_dir, transform=preprocess)
|
|
|
13 |
|
14 |
# Classes
|
15 |
+
classes = dataset.classes
|
16 |
|
17 |
print("Classes: ", *classes, sep=", ")
|
|
|
|
|
|
|
18 |
print("Length of total dataset: ", len(dataset))
|
19 |
|
20 |
# Split the dataset into train and validation sets
|
|
|
27 |
CustomDataset(train_dataset), batch_size=batch_size, shuffle=True, num_workers=0
|
28 |
)
|
29 |
valid_loader = DataLoader(
|
30 |
+
CustomDataset(val_dataset), batch_size=batch_size, num_workers=0, shuffle=False
|
31 |
)
|
32 |
|
33 |
return train_loader, valid_loader
|
34 |
|
35 |
+
|
36 |
+
def load_test_data(test_dir, preprocess, batch_size=BATCH_SIZE):
|
37 |
+
test_dataset = ImageFolder(test_dir, transform=preprocess)
|
38 |
+
|
39 |
+
# Create a DataLoader for the test data
|
40 |
+
test_dataloader = DataLoader(
|
41 |
+
CustomDataset(test_dataset), batch_size=batch_size, shuffle=False, num_workers=0
|
42 |
+
)
|
43 |
+
|
44 |
+
return test_dataloader
|
ensemble.py
ADDED
@@ -0,0 +1,249 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import matplotlib.pyplot as plt
|
2 |
+
from torch.optim.lr_scheduler import CosineAnnealingLR
|
3 |
+
import torch
|
4 |
+
import torch.nn as nn
|
5 |
+
from torchvision.datasets import ImageFolder
|
6 |
+
from torch.utils.data import DataLoader
|
7 |
+
from data_loader import load_data, load_test_data
|
8 |
+
from configs import *
|
9 |
+
import numpy as np
|
10 |
+
|
11 |
+
torch.cuda.empty_cache()
|
12 |
+
|
13 |
+
#
|
14 |
+
|
15 |
+
|
16 |
+
class MLP(nn.Module):
|
17 |
+
def __init__(self, num_classes, num_models):
|
18 |
+
super(MLP, self).__init__()
|
19 |
+
self.layers = nn.Sequential(
|
20 |
+
nn.Linear(num_classes * num_models, 1024),
|
21 |
+
nn.LayerNorm(1024),
|
22 |
+
nn.LeakyReLU(negative_slope=0.01, inplace=True),
|
23 |
+
nn.Dropout(0.8),
|
24 |
+
nn.Linear(1024, 2048),
|
25 |
+
nn.LeakyReLU(negative_slope=0.01, inplace=True),
|
26 |
+
nn.Dropout(0.5),
|
27 |
+
nn.Linear(2048, 2048),
|
28 |
+
nn.LeakyReLU(negative_slope=0.01, inplace=True),
|
29 |
+
nn.Dropout(0.5),
|
30 |
+
nn.Linear(2048, num_classes),
|
31 |
+
)
|
32 |
+
|
33 |
+
def forward(self, x):
|
34 |
+
x = x.view(x.size(0), -1)
|
35 |
+
x = self.layers(x)
|
36 |
+
return x
|
37 |
+
|
38 |
+
|
39 |
+
def mlp_meta(num_classes, num_models):
|
40 |
+
model = MLP(num_classes, num_models)
|
41 |
+
return model
|
42 |
+
|
43 |
+
|
44 |
+
# Hyperparameters
|
45 |
+
input_dim = 3 * 224 * 224 # Modify this based on your input size
|
46 |
+
hidden_dim = 256
|
47 |
+
output_dim = NUM_CLASSES
|
48 |
+
|
49 |
+
# Create the data loaders using your data_loader functions50
|
50 |
+
train_loader, val_loader = load_data(COMBINED_DATA_DIR + "1", preprocess, BATCH_SIZE)
|
51 |
+
test_loader = load_test_data("data/test/Task 1", preprocess, BATCH_SIZE)
|
52 |
+
|
53 |
+
model_paths = [
|
54 |
+
"output/checkpoints/bestsqueezenetSE3.pth",
|
55 |
+
"output/checkpoints/EfficientNetB3WithDropout.pth",
|
56 |
+
"output/checkpoints/MobileNetV2WithDropout2.pth",
|
57 |
+
]
|
58 |
+
|
59 |
+
|
60 |
+
# Define a function to load pretrained models
|
61 |
+
def load_pretrained_model(path, model):
|
62 |
+
model.load_state_dict(torch.load(path))
|
63 |
+
return model.to(DEVICE)
|
64 |
+
|
65 |
+
|
66 |
+
def rand_bbox(size, lam):
|
67 |
+
W = size[2]
|
68 |
+
H = size[3]
|
69 |
+
cut_rat = np.sqrt(1.0 - lam)
|
70 |
+
cut_w = np.int_(W * cut_rat)
|
71 |
+
cut_h = np.int_(H * cut_rat)
|
72 |
+
|
73 |
+
# uniform
|
74 |
+
cx = np.random.randint(W)
|
75 |
+
cy = np.random.randint(H)
|
76 |
+
|
77 |
+
bbx1 = np.clip(cx - cut_w // 2, 0, W)
|
78 |
+
bby1 = np.clip(cy - cut_h // 2, 0, H)
|
79 |
+
bbx2 = np.clip(cx + cut_w // 2, 0, W)
|
80 |
+
bby2 = np.clip(cy + cut_h // 2, 0, H)
|
81 |
+
|
82 |
+
return bbx1, bby1, bbx2, bby2
|
83 |
+
|
84 |
+
|
85 |
+
def cutmix_data(input, target, alpha=1.0):
|
86 |
+
if alpha > 0:
|
87 |
+
lam = np.random.beta(alpha, alpha)
|
88 |
+
else:
|
89 |
+
lam = 1
|
90 |
+
|
91 |
+
batch_size = input.size()[0]
|
92 |
+
index = torch.randperm(batch_size)
|
93 |
+
rand_index = torch.randperm(input.size()[0])
|
94 |
+
|
95 |
+
bbx1, bby1, bbx2, bby2 = rand_bbox(input.size(), lam)
|
96 |
+
input[:, :, bbx1:bbx2, bby1:bby2] = input[rand_index, :, bbx1:bbx2, bby1:bby2]
|
97 |
+
|
98 |
+
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (input.size()[-1] * input.size()[-2]))
|
99 |
+
targets_a = target
|
100 |
+
targets_b = target[rand_index]
|
101 |
+
|
102 |
+
return input, targets_a, targets_b, lam
|
103 |
+
|
104 |
+
|
105 |
+
def cutmix_criterion(criterion, outputs, targets_a, targets_b, lam):
|
106 |
+
return lam * criterion(outputs, targets_a) + (1 - lam) * criterion(
|
107 |
+
outputs, targets_b
|
108 |
+
)
|
109 |
+
|
110 |
+
|
111 |
+
# Load pretrained models
|
112 |
+
model1 = load_pretrained_model(
|
113 |
+
model_paths[0], SqueezeNet1_0WithSE(num_classes=NUM_CLASSES)
|
114 |
+
).to(DEVICE)
|
115 |
+
model2 = load_pretrained_model(
|
116 |
+
model_paths[1], EfficientNetB3WithDropout(num_classes=NUM_CLASSES)
|
117 |
+
).to(DEVICE)
|
118 |
+
model3 = load_pretrained_model(
|
119 |
+
model_paths[2], MobileNetV2WithDropout(num_classes=NUM_CLASSES)
|
120 |
+
).to(DEVICE)
|
121 |
+
|
122 |
+
models = [model1, model2, model3]
|
123 |
+
|
124 |
+
# Create the meta learner
|
125 |
+
meta_learner_model = mlp_meta(NUM_CLASSES, len(models)).to(DEVICE)
|
126 |
+
meta_optimizer = torch.optim.Adam(meta_learner_model.parameters(), lr=0.001)
|
127 |
+
meta_loss_fn = torch.nn.CrossEntropyLoss()
|
128 |
+
|
129 |
+
# Define the Cosine Annealing Learning Rate Scheduler
|
130 |
+
scheduler = CosineAnnealingLR(
|
131 |
+
meta_optimizer, T_max=700
|
132 |
+
) # T_max is the number of epochs for the cosine annealing.
|
133 |
+
|
134 |
+
# Define loss function and optimizer for the meta learner
|
135 |
+
criterion = nn.CrossEntropyLoss().to(DEVICE)
|
136 |
+
|
137 |
+
# Record learning rate
|
138 |
+
lr_hist = []
|
139 |
+
|
140 |
+
# Training loop
|
141 |
+
num_epochs = 160
|
142 |
+
for epoch in range(num_epochs):
|
143 |
+
print("[Epoch: {}]".format(epoch + 1))
|
144 |
+
print("Total number of batches: {}".format(len(train_loader)))
|
145 |
+
for batch_idx, data in enumerate(train_loader, 0):
|
146 |
+
print("Batch: {}".format(batch_idx + 1))
|
147 |
+
inputs, labels = data
|
148 |
+
inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)
|
149 |
+
inputs, targets_a, targets_b, lam = cutmix_data(inputs, labels, alpha=1)
|
150 |
+
|
151 |
+
# Forward pass through the three pretrained models
|
152 |
+
features1 = model1(inputs)
|
153 |
+
features2 = model2(inputs)
|
154 |
+
features3 = model3(inputs)
|
155 |
+
|
156 |
+
# Stack the features from the three models
|
157 |
+
stacked_features = torch.cat((features1, features2, features3), dim=1).to(
|
158 |
+
DEVICE
|
159 |
+
)
|
160 |
+
|
161 |
+
# Forward pass through the meta learner
|
162 |
+
meta_output = meta_learner_model(stacked_features)
|
163 |
+
|
164 |
+
# Compute the loss
|
165 |
+
loss = cutmix_criterion(criterion, meta_output, targets_a, targets_b, lam)
|
166 |
+
|
167 |
+
# Compute the accuracy
|
168 |
+
_, predicted = torch.max(meta_output, 1)
|
169 |
+
total = labels.size(0)
|
170 |
+
correct = (predicted == labels).sum().item()
|
171 |
+
|
172 |
+
# Backpropagation and optimization
|
173 |
+
meta_optimizer.zero_grad()
|
174 |
+
loss.backward()
|
175 |
+
meta_optimizer.step()
|
176 |
+
|
177 |
+
lr_hist.append(meta_optimizer.param_groups[0]["lr"])
|
178 |
+
|
179 |
+
scheduler.step()
|
180 |
+
|
181 |
+
print("Train Loss: {}".format(loss.item()))
|
182 |
+
print("Train Accuracy: {}%".format(100 * correct / total))
|
183 |
+
|
184 |
+
# Validation
|
185 |
+
meta_learner_model.eval()
|
186 |
+
correct = 0
|
187 |
+
total = 0
|
188 |
+
val_loss = 0
|
189 |
+
with torch.no_grad():
|
190 |
+
for data in val_loader:
|
191 |
+
inputs, labels = data
|
192 |
+
inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)
|
193 |
+
features1 = model1(inputs)
|
194 |
+
features2 = model2(inputs)
|
195 |
+
features3 = model3(inputs)
|
196 |
+
stacked_features = torch.cat((features1, features2, features3), dim=1).to(
|
197 |
+
DEVICE
|
198 |
+
)
|
199 |
+
outputs = meta_learner_model(stacked_features)
|
200 |
+
loss = criterion(outputs, labels) # Use the validation loss
|
201 |
+
val_loss += loss.item()
|
202 |
+
_, predicted = torch.max(outputs, 1)
|
203 |
+
total += labels.size(0)
|
204 |
+
correct += (predicted == labels).sum().item()
|
205 |
+
|
206 |
+
print(
|
207 |
+
"Validation Loss: {}".format(val_loss / len(val_loader))
|
208 |
+
) # Calculate the average loss
|
209 |
+
print("Validation Accuracy: {}%".format(100 * correct / total))
|
210 |
+
|
211 |
+
|
212 |
+
print("Finished Training")
|
213 |
+
|
214 |
+
# Test the ensemble
|
215 |
+
print("Testing the ensemble")
|
216 |
+
meta_learner_model.eval()
|
217 |
+
correct = 0
|
218 |
+
total = 0
|
219 |
+
with torch.no_grad():
|
220 |
+
for data in test_loader:
|
221 |
+
inputs, labels = data
|
222 |
+
inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)
|
223 |
+
features1 = model1(inputs)
|
224 |
+
features2 = model2(inputs)
|
225 |
+
features3 = model3(inputs)
|
226 |
+
stacked_features = torch.cat((features1, features2, features3), dim=1)
|
227 |
+
outputs = meta_learner_model(stacked_features)
|
228 |
+
_, predicted = torch.max(outputs, 1)
|
229 |
+
total += labels.size(0)
|
230 |
+
correct += (predicted == labels).sum().item()
|
231 |
+
|
232 |
+
print(
|
233 |
+
"Accuracy of the ensemble network on the test images: {}%".format(
|
234 |
+
100 * correct / total
|
235 |
+
)
|
236 |
+
)
|
237 |
+
|
238 |
+
|
239 |
+
# Plot the learning rate history
|
240 |
+
|
241 |
+
plt.plot(lr_hist)
|
242 |
+
plt.xlabel("Iterations")
|
243 |
+
plt.ylabel("Learning Rate")
|
244 |
+
plt.title("Learning Rate History")
|
245 |
+
plt.show()
|
246 |
+
|
247 |
+
|
248 |
+
# Save the model
|
249 |
+
torch.save(meta_learner_model.state_dict(), "output/checkpoints/ensemble.pth")
|
eval.py
CHANGED
@@ -11,88 +11,172 @@ from sklearn.metrics import (
|
|
11 |
f1_score,
|
12 |
confusion_matrix,
|
13 |
ConfusionMatrixDisplay,
|
14 |
-
roc_curve,
|
15 |
-
auc,
|
16 |
)
|
17 |
from sklearn.preprocessing import label_binarize
|
|
|
18 |
from configs import *
|
19 |
-
|
|
|
|
|
|
|
|
|
20 |
|
21 |
# Constants
|
22 |
DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
|
24 |
# Load the model
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
-
def predict_image(image_path, model, transform):
|
30 |
-
model.eval()
|
31 |
-
correct_predictions = 0
|
32 |
|
33 |
-
|
34 |
-
|
|
|
35 |
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
true_classes = []
|
39 |
-
|
40 |
-
|
|
|
41 |
|
42 |
with torch.no_grad():
|
|
|
|
|
|
|
43 |
for image_file in images:
|
44 |
-
print("---------------------------")
|
45 |
-
# Check the true label of the image by checking the sequence of the folder in Task 1
|
46 |
true_class = CLASSES.index(image_file.parts[-2])
|
47 |
-
print("Image path:", image_file)
|
48 |
-
print("True class:", true_class)
|
49 |
-
image = Image.open(image_file).convert("RGB")
|
50 |
-
image = transform(image).unsqueeze(0)
|
51 |
-
image = image.to(DEVICE)
|
52 |
-
output = model(image)
|
53 |
-
predicted_class = torch.argmax(output, dim=1).item()
|
54 |
-
# Print the predicted class
|
55 |
-
print("Predicted class:", predicted_class)
|
56 |
-
# Append true and predicted labels to their respective lists
|
57 |
-
true_classes.append(true_class)
|
58 |
-
predicted_labels.append(predicted_class)
|
59 |
-
predicted_scores.append(
|
60 |
-
output.softmax(dim=1).cpu().numpy()
|
61 |
-
) # Store predicted class probabilities
|
62 |
|
63 |
-
|
64 |
-
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
print("Weighted F1 Score:", f1)
|
72 |
|
73 |
-
|
74 |
-
|
75 |
-
true_classes_tensor = torch.tensor(true_classes)
|
76 |
|
77 |
-
#
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
# Classification report
|
86 |
class_names = [str(cls) for cls in range(NUM_CLASSES)]
|
87 |
report = classification_report(
|
88 |
-
true_classes,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
)
|
90 |
-
print("Classification Report:\n",
|
91 |
|
92 |
# Calculate precision and recall for each class
|
93 |
true_classes_binary = label_binarize(true_classes, classes=range(NUM_CLASSES))
|
94 |
precision, recall, _ = precision_recall_curve(
|
95 |
-
true_classes_binary.ravel(), np.array(
|
96 |
)
|
97 |
|
98 |
# Plot precision-recall curve
|
@@ -103,6 +187,4 @@ def predict_image(image_path, model, transform):
|
|
103 |
plt.ylabel("Precision")
|
104 |
plt.show()
|
105 |
|
106 |
-
|
107 |
-
# Call predict_image function with your image path
|
108 |
-
predict_image("data/test/Task 1/", MODEL, preprocess)
|
|
|
11 |
f1_score,
|
12 |
confusion_matrix,
|
13 |
ConfusionMatrixDisplay,
|
|
|
|
|
14 |
)
|
15 |
from sklearn.preprocessing import label_binarize
|
16 |
+
from torchvision import transforms
|
17 |
from configs import *
|
18 |
+
|
19 |
+
# EfficientNet: 0.901978973407545
|
20 |
+
# MobileNet: 0.8731189445475158
|
21 |
+
# SquuezeNet: 0.8559218559218559
|
22 |
+
|
23 |
|
24 |
# Constants
|
25 |
DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
26 |
+
NUM_AUGMENTATIONS = 10 # Number of augmentations to perform
|
27 |
+
|
28 |
+
model2 = EfficientNetB2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
29 |
+
model2.load_state_dict(torch.load("output/checkpoints/EfficientNetB2WithDropout.pth"))
|
30 |
+
model1 = SqueezeNet1_0WithSE(num_classes=NUM_CLASSES).to(DEVICE)
|
31 |
+
model1.load_state_dict(torch.load("output/checkpoints/SqueezeNet1_0WithSE.pth"))
|
32 |
+
model3 = MobileNetV2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
33 |
+
model3.load_state_dict(torch.load("output\checkpoints\MobileNetV2WithDropout.pth"))
|
34 |
+
|
35 |
+
best_weights = [0.901978973407545, 0.8731189445475158, 0.8559218559218559]
|
36 |
|
37 |
# Load the model
|
38 |
+
model = WeightedVoteEnsemble([model1, model2, model3], best_weights)
|
39 |
+
# model.load_state_dict(torch.load(MODEL_SAVE_PATH, map_location=DEVICE))
|
40 |
+
model.load_state_dict(torch.load('output/checkpoints/WeightedVoteEnsemble.pth', map_location=DEVICE))
|
41 |
+
model.eval()
|
42 |
+
|
43 |
+
# define augmentations for TTA
|
44 |
+
tta_transforms = transforms.Compose(
|
45 |
+
[
|
46 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
47 |
+
transforms.RandomVerticalFlip(p=0.5),
|
48 |
+
]
|
49 |
+
)
|
50 |
|
|
|
|
|
|
|
51 |
|
52 |
+
def perform_tta(model, image, tta_transforms):
|
53 |
+
augmented_predictions = []
|
54 |
+
augmented_scores = []
|
55 |
|
56 |
+
for _ in range(NUM_AUGMENTATIONS):
|
57 |
+
augmented_image = tta_transforms(image)
|
58 |
+
output = model(augmented_image)
|
59 |
+
predicted_class = torch.argmax(output, dim=1).item()
|
60 |
+
augmented_predictions.append(predicted_class)
|
61 |
+
augmented_scores.append(output.softmax(dim=1).cpu().numpy())
|
62 |
|
63 |
+
# max voting
|
64 |
+
final_predicted_class_max = max(
|
65 |
+
set(augmented_predictions), key=augmented_predictions.count
|
66 |
+
)
|
67 |
+
|
68 |
+
# average probabilities
|
69 |
+
final_predicted_scores_avg = np.mean(np.array(augmented_scores), axis=0)
|
70 |
+
|
71 |
+
# rotate and average probabilities
|
72 |
+
rotation_transforms = [
|
73 |
+
transforms.RandomRotation(degrees=i) for i in range(0, 360, 30)
|
74 |
+
]
|
75 |
+
rotated_scores = []
|
76 |
+
for rotation_transform in rotation_transforms:
|
77 |
+
augmented_image = rotation_transform(image)
|
78 |
+
output = model(augmented_image)
|
79 |
+
rotated_scores.append(output.softmax(dim=1).cpu().numpy())
|
80 |
+
|
81 |
+
final_predicted_scores_rotation = np.mean(np.array(rotated_scores), axis=0)
|
82 |
+
|
83 |
+
return (
|
84 |
+
final_predicted_class_max,
|
85 |
+
final_predicted_scores_avg,
|
86 |
+
final_predicted_scores_rotation,
|
87 |
+
)
|
88 |
+
|
89 |
+
|
90 |
+
def predict_image_with_tta(image_path, model, transform, tta_transforms):
|
91 |
+
model.eval()
|
92 |
+
correct_predictions = 0
|
93 |
true_classes = []
|
94 |
+
predicted_labels_max = []
|
95 |
+
predicted_labels_avg = []
|
96 |
+
predicted_labels_rotation = []
|
97 |
|
98 |
with torch.no_grad():
|
99 |
+
images = list(pathlib.Path(image_path).rglob("*.png"))
|
100 |
+
total_predictions = len(images)
|
101 |
+
|
102 |
for image_file in images:
|
|
|
|
|
103 |
true_class = CLASSES.index(image_file.parts[-2])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
|
105 |
+
original_image = Image.open(image_file).convert("RGB")
|
106 |
+
original_image = transform(original_image).unsqueeze(0)
|
107 |
+
original_image = original_image.to(DEVICE)
|
108 |
+
|
109 |
+
# Perform TTA with different strategies
|
110 |
+
final_predicted_class_max, _, _ = perform_tta(
|
111 |
+
model, original_image, tta_transforms
|
112 |
+
)
|
113 |
+
_, final_predicted_scores_avg, _ = perform_tta(
|
114 |
+
model, original_image, tta_transforms
|
115 |
+
)
|
116 |
+
_, _, final_predicted_scores_rotation = perform_tta(
|
117 |
+
model, original_image, tta_transforms
|
118 |
+
)
|
119 |
|
120 |
+
true_classes.append(true_class)
|
121 |
+
predicted_labels_max.append(final_predicted_class_max)
|
122 |
+
predicted_labels_avg.append(np.argmax(final_predicted_scores_avg))
|
123 |
+
predicted_labels_rotation.append(np.argmax(final_predicted_scores_rotation))
|
|
|
124 |
|
125 |
+
if final_predicted_class_max == true_class:
|
126 |
+
correct_predictions += 1
|
|
|
127 |
|
128 |
+
# accuracy for each strategy
|
129 |
+
accuracy_max = accuracy_score(true_classes, predicted_labels_max)
|
130 |
+
accuracy_avg = accuracy_score(true_classes, predicted_labels_avg)
|
131 |
+
accuracy_rotation = accuracy_score(true_classes, predicted_labels_rotation)
|
132 |
+
|
133 |
+
print("Accuracy (Max Voting):", accuracy_max)
|
134 |
+
print("Accuracy (Average Probabilities):", accuracy_avg)
|
135 |
+
print("Accuracy (Rotation and Average):", accuracy_rotation)
|
136 |
+
|
137 |
+
# final prediction using ensemble (choose the strategy with the highest accuracy)
|
138 |
+
final_predicted_labels = []
|
139 |
+
for i in range(len(true_classes)):
|
140 |
+
max_strategy_accuracy = max(accuracy_max, accuracy_avg, accuracy_rotation)
|
141 |
+
if accuracy_max == max_strategy_accuracy:
|
142 |
+
final_predicted_labels.append(predicted_labels_max[i])
|
143 |
+
elif accuracy_avg == max_strategy_accuracy:
|
144 |
+
final_predicted_labels.append(predicted_labels_avg[i])
|
145 |
+
else:
|
146 |
+
final_predicted_labels.append(predicted_labels_rotation[i])
|
147 |
+
|
148 |
+
# calculate accuracy and f1 score(ensemble)
|
149 |
+
accuracy_ensemble = accuracy_score(true_classes, final_predicted_labels)
|
150 |
+
f1_ensemble = f1_score(true_classes, final_predicted_labels, average="weighted")
|
151 |
+
|
152 |
+
print("Ensemble Accuracy:", accuracy_ensemble)
|
153 |
+
print("Ensemble Weighted F1 Score:", f1_ensemble)
|
154 |
|
155 |
# Classification report
|
156 |
class_names = [str(cls) for cls in range(NUM_CLASSES)]
|
157 |
report = classification_report(
|
158 |
+
true_classes, final_predicted_labels, target_names=class_names
|
159 |
+
)
|
160 |
+
print("Classification Report of", MODEL.__class__.__name__, ":\n", report)
|
161 |
+
|
162 |
+
# confusion matrix and classification report for the ensemble
|
163 |
+
conf_matrix_ensemble = confusion_matrix(true_classes, final_predicted_labels)
|
164 |
+
ConfusionMatrixDisplay(
|
165 |
+
confusion_matrix=conf_matrix_ensemble, display_labels=range(NUM_CLASSES)
|
166 |
+
).plot(cmap=plt.cm.Blues)
|
167 |
+
plt.title("Confusion Matrix (Ensemble)")
|
168 |
+
plt.show()
|
169 |
+
|
170 |
+
class_names = [str(cls) for cls in range(NUM_CLASSES)]
|
171 |
+
report_ensemble = classification_report(
|
172 |
+
true_classes, final_predicted_labels, target_names=class_names
|
173 |
)
|
174 |
+
print("Classification Report (Ensemble):\n", report_ensemble)
|
175 |
|
176 |
# Calculate precision and recall for each class
|
177 |
true_classes_binary = label_binarize(true_classes, classes=range(NUM_CLASSES))
|
178 |
precision, recall, _ = precision_recall_curve(
|
179 |
+
true_classes_binary.ravel(), np.array(final_predicted_scores_rotation).ravel()
|
180 |
)
|
181 |
|
182 |
# Plot precision-recall curve
|
|
|
187 |
plt.ylabel("Precision")
|
188 |
plt.show()
|
189 |
|
190 |
+
predict_image_with_tta("data/test/Task 1/", model, preprocess, tta_transforms)
|
|
|
|
eval_orig.py
ADDED
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import torchvision
|
3 |
+
import shap
|
4 |
+
import torch
|
5 |
+
import numpy as np
|
6 |
+
import pathlib
|
7 |
+
from PIL import Image
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
from matplotlib import rcParams
|
10 |
+
from sklearn.metrics import (
|
11 |
+
classification_report,
|
12 |
+
precision_recall_curve,
|
13 |
+
accuracy_score,
|
14 |
+
f1_score,
|
15 |
+
confusion_matrix,
|
16 |
+
ConfusionMatrixDisplay,
|
17 |
+
roc_curve,
|
18 |
+
auc,
|
19 |
+
average_precision_score,
|
20 |
+
)
|
21 |
+
from sklearn.preprocessing import label_binarize
|
22 |
+
from configs import *
|
23 |
+
from data_loader import load_data # Import the load_data function
|
24 |
+
|
25 |
+
# MobileNet: 0.8731189445475158
|
26 |
+
# EfficientNet: 0.873118944547516
|
27 |
+
# SquuezeNet: 0.8865856365856365
|
28 |
+
|
29 |
+
|
30 |
+
rcParams['font.family'] = 'Times New Roman'
|
31 |
+
|
32 |
+
# Load the model
|
33 |
+
model = MODEL.to(DEVICE)
|
34 |
+
model.load_state_dict(torch.load(MODEL_SAVE_PATH, map_location=DEVICE))
|
35 |
+
model.eval()
|
36 |
+
|
37 |
+
# model2 = EfficientNetB3WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
38 |
+
# model2.load_state_dict(torch.load("output/checkpoints/EfficientNetB3WithDropout.pth"))
|
39 |
+
# model1 = SqueezeNet1_0WithSE(num_classes=NUM_CLASSES).to(DEVICE)
|
40 |
+
# model1.load_state_dict(torch.load("output/checkpoints/SqueezeNet1_0WithSE.pth"))
|
41 |
+
# model3 = MobileNetV2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
42 |
+
# model3.load_state_dict(torch.load("output\checkpoints\MobileNetV2WithDropout.pth"))
|
43 |
+
|
44 |
+
# model1.eval()
|
45 |
+
# model2.eval()
|
46 |
+
# model3.eval()
|
47 |
+
|
48 |
+
# # Load the model
|
49 |
+
# model = WeightedVoteEnsemble([model1, model2, model3], [0.38, 0.34, 0.28])
|
50 |
+
# # model.load_state_dict(torch.load(MODEL_SAVE_PATH, map_location=DEVICE))
|
51 |
+
# model.load_state_dict(
|
52 |
+
# torch.load("output/checkpoints/WeightedVoteEnsemble.pth", map_location=DEVICE)
|
53 |
+
# )
|
54 |
+
# model.eval()
|
55 |
+
|
56 |
+
|
57 |
+
def predict_image(image_path, model, transform):
|
58 |
+
model.eval()
|
59 |
+
correct_predictions = 0
|
60 |
+
|
61 |
+
# Get a list of image files
|
62 |
+
images = list(pathlib.Path(image_path).rglob("*.png"))
|
63 |
+
|
64 |
+
total_predictions = len(images)
|
65 |
+
|
66 |
+
true_classes = []
|
67 |
+
predicted_labels = []
|
68 |
+
predicted_scores = [] # To store predicted class probabilities
|
69 |
+
|
70 |
+
with torch.no_grad():
|
71 |
+
for image_file in images:
|
72 |
+
print("---------------------------")
|
73 |
+
# Check the true label of the image by checking the sequence of the folder in Task 1
|
74 |
+
true_class = CLASSES.index(image_file.parts[-2])
|
75 |
+
print("Image path:", image_file)
|
76 |
+
print("True class:", true_class)
|
77 |
+
image = Image.open(image_file).convert("RGB")
|
78 |
+
image = transform(image).unsqueeze(0)
|
79 |
+
image = image.to(DEVICE)
|
80 |
+
output = model(image)
|
81 |
+
predicted_class = torch.argmax(output, dim=1).item()
|
82 |
+
# Print the predicted class
|
83 |
+
print("Predicted class:", predicted_class)
|
84 |
+
# Append true and predicted labels to their respective lists
|
85 |
+
true_classes.append(true_class)
|
86 |
+
predicted_labels.append(predicted_class)
|
87 |
+
predicted_scores.append(
|
88 |
+
output.softmax(dim=1).cpu().numpy()
|
89 |
+
) # Store predicted class probabilities
|
90 |
+
|
91 |
+
# Check if the prediction is correct
|
92 |
+
if predicted_class == true_class:
|
93 |
+
correct_predictions += 1
|
94 |
+
|
95 |
+
# Calculate accuracy and f1 score
|
96 |
+
accuracy = accuracy_score(true_classes, predicted_labels)
|
97 |
+
print("Accuracy:", accuracy)
|
98 |
+
f1 = f1_score(true_classes, predicted_labels, average="weighted")
|
99 |
+
print("Weighted F1 Score:", f1)
|
100 |
+
|
101 |
+
# Convert the lists to tensors
|
102 |
+
predicted_labels_tensor = torch.tensor(predicted_labels)
|
103 |
+
true_classes_tensor = torch.tensor(true_classes)
|
104 |
+
|
105 |
+
# Calculate the confusion matrix
|
106 |
+
conf_matrix = confusion_matrix(true_classes, predicted_labels)
|
107 |
+
|
108 |
+
# Plot the confusion matrix
|
109 |
+
ConfusionMatrixDisplay(
|
110 |
+
confusion_matrix=conf_matrix, display_labels=range(NUM_CLASSES)
|
111 |
+
).plot(cmap=plt.cm.Blues)
|
112 |
+
plt.title("Confusion Matrix")
|
113 |
+
plt.show()
|
114 |
+
|
115 |
+
# Classification report
|
116 |
+
class_names = [str(cls) for cls in range(NUM_CLASSES)]
|
117 |
+
report = classification_report(
|
118 |
+
true_classes, predicted_labels, target_names=class_names
|
119 |
+
)
|
120 |
+
print("Classification Report:\n", report)
|
121 |
+
|
122 |
+
# Calculate precision and recall for each class
|
123 |
+
true_classes_binary = label_binarize(true_classes, classes=range(NUM_CLASSES))
|
124 |
+
precision, recall, _ = precision_recall_curve(
|
125 |
+
true_classes_binary.ravel(), np.array(predicted_scores).ravel()
|
126 |
+
)
|
127 |
+
|
128 |
+
fpr, tpr, _ = roc_curve(
|
129 |
+
true_classes_binary.ravel(), np.array(predicted_scores).ravel()
|
130 |
+
)
|
131 |
+
auc_roc = auc(fpr, tpr)
|
132 |
+
print("AUC-ROC:", auc_roc)
|
133 |
+
|
134 |
+
# Calculate PRC AUC
|
135 |
+
precision, recall, _ = precision_recall_curve(
|
136 |
+
true_classes_binary.ravel(), np.array(predicted_scores).ravel()
|
137 |
+
)
|
138 |
+
auc_prc = average_precision_score(
|
139 |
+
true_classes_binary.ravel(), np.array(predicted_scores).ravel()
|
140 |
+
)
|
141 |
+
print("AUC PRC:", auc_prc)
|
142 |
+
|
143 |
+
# Plot precision-recall curve
|
144 |
+
plt.figure(figsize=(10, 6))
|
145 |
+
plt.plot(recall, precision)
|
146 |
+
plt.title("Precision-Recall Curve")
|
147 |
+
plt.xlabel("Recall")
|
148 |
+
plt.ylabel("Precision")
|
149 |
+
# Show the AUC value on the plot
|
150 |
+
plt.text(
|
151 |
+
0.6,
|
152 |
+
0.2,
|
153 |
+
"AUC-PRC = {:.3f}".format(auc_prc),
|
154 |
+
bbox=dict(boxstyle="round", facecolor="white", alpha=0.8),
|
155 |
+
)
|
156 |
+
plt.savefig("docs/efficientnet/prc.png")
|
157 |
+
plt.show()
|
158 |
+
|
159 |
+
# Plot ROC curve
|
160 |
+
plt.figure(figsize=(10, 6))
|
161 |
+
plt.plot(fpr, tpr)
|
162 |
+
plt.title("ROC Curve")
|
163 |
+
plt.xlabel("False Positive Rate")
|
164 |
+
plt.ylabel("True Positive Rate")
|
165 |
+
# Show the AUC value on the plot
|
166 |
+
plt.text(
|
167 |
+
0.6,
|
168 |
+
0.2,
|
169 |
+
"AUC-ROC = {:.3f}".format(auc_roc),
|
170 |
+
bbox=dict(boxstyle="round", facecolor="white", alpha=0.8),
|
171 |
+
)
|
172 |
+
plt.savefig("docs/efficientnet/roc.png")
|
173 |
+
plt.show()
|
174 |
+
|
175 |
+
|
176 |
+
predict_image("data/test/Task 1/", model, preprocess)
|
177 |
+
|
178 |
+
|
179 |
+
# 89 EfficientNetB2WithDropout / 0.873118944547516
|
180 |
+
# 89 MobileNetV2WithDropout / 0.8731189445475158
|
181 |
+
# 89 SqueezeNet1_0WithSE / .8865856365856365
|
extract-ensemble.py
ADDED
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pytorch_grad_cam import GradCAMPlusPlus
|
2 |
+
from pytorch_grad_cam.utils.image import show_cam_on_image, preprocess_image
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
import torch
|
6 |
+
import torch.nn as nn # Replace with your model
|
7 |
+
from configs import *
|
8 |
+
|
9 |
+
# Load your model (change this according to your model definition)
|
10 |
+
model2 = EfficientNetB2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
11 |
+
model2.load_state_dict(torch.load("output/checkpoints/EfficientNetB2WithDropout.pth"))
|
12 |
+
model1 = SqueezeNet1_0WithSE(num_classes=NUM_CLASSES).to(DEVICE)
|
13 |
+
model1.load_state_dict(torch.load("output/checkpoints/SqueezeNet1_0WithSE.pth"))
|
14 |
+
model3 = MobileNetV2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
15 |
+
model3.load_state_dict(torch.load("output\checkpoints\MobileNetV2WithDropout.pth"))
|
16 |
+
|
17 |
+
model1.eval()
|
18 |
+
model2.eval()
|
19 |
+
model3.eval()
|
20 |
+
|
21 |
+
|
22 |
+
# Find the target layer (modify this based on your model architecture)
|
23 |
+
# EfficientNetB2WithDropout - model.features[-1]
|
24 |
+
# SqueezeNet1_0WithSE - model.features
|
25 |
+
# MobileNetV2WithDropout - model.features[-1]
|
26 |
+
|
27 |
+
target_layer_efficientnet = None
|
28 |
+
for child in model2.features[-1]:
|
29 |
+
if isinstance(child, nn.Conv2d):
|
30 |
+
target_layer_efficientnet = child
|
31 |
+
|
32 |
+
if target_layer_efficientnet is None:
|
33 |
+
raise ValueError(
|
34 |
+
"Invalid EfficientNet layer name: {}".format(target_layer_efficientnet)
|
35 |
+
)
|
36 |
+
|
37 |
+
target_layer_squeezenet = None
|
38 |
+
for child in model1.features:
|
39 |
+
if isinstance(child, nn.Conv2d):
|
40 |
+
target_layer_squeezenet = child
|
41 |
+
|
42 |
+
if target_layer_squeezenet is None:
|
43 |
+
raise ValueError(
|
44 |
+
"Invalid SqueezeNet layer name: {}".format(target_layer_squeezenet)
|
45 |
+
)
|
46 |
+
|
47 |
+
target_layer_mobilenet = None
|
48 |
+
for child in model3.features[-1]:
|
49 |
+
if isinstance(child, nn.Conv2d):
|
50 |
+
target_layer_mobilenet = child
|
51 |
+
|
52 |
+
if target_layer_mobilenet is None:
|
53 |
+
raise ValueError("Invalid MobileNet layer name: {}".format(target_layer_mobilenet))
|
54 |
+
|
55 |
+
# Load and preprocess the image
|
56 |
+
image_path = r"data\test\Task 1\Cerebral Palsy\89.png"
|
57 |
+
rgb_img = cv2.imread(image_path, 1)
|
58 |
+
rgb_img = np.float32(rgb_img) / 255
|
59 |
+
input_tensor = preprocess_image(rgb_img, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
|
60 |
+
input_tensor = input_tensor.to(DEVICE)
|
61 |
+
input_tensor.requires_grad = True # Enable gradients for the input tensor
|
62 |
+
|
63 |
+
|
64 |
+
# Create a GradCAMPlusPlus object
|
65 |
+
efficientnet_cam = GradCAMPlusPlus(model=model2, target_layers=[target_layer_efficientnet], use_cuda=True)
|
66 |
+
squeezenet_cam = GradCAMPlusPlus(model=model1, target_layers=[target_layer_squeezenet], use_cuda=True)
|
67 |
+
mobilenet_cam = GradCAMPlusPlus(model=model3, target_layers=[target_layer_mobilenet], use_cuda=True)
|
68 |
+
|
69 |
+
|
70 |
+
efficientnet_grayscale_cam = efficientnet_cam(input_tensor=input_tensor)[0]
|
71 |
+
squeezenet_grayscale_cam = squeezenet_cam(input_tensor=input_tensor)[0]
|
72 |
+
mobilenet_grayscale_cam = mobilenet_cam(input_tensor=input_tensor)[0]
|
73 |
+
|
74 |
+
# Apply a colormap to the grayscale heatmap
|
75 |
+
efficientnet_heatmap_colored = cv2.applyColorMap(np.uint8(255 * efficientnet_grayscale_cam), cv2.COLORMAP_JET)
|
76 |
+
squeezenet_heatmap_colored = cv2.applyColorMap(np.uint8(255 * squeezenet_grayscale_cam), cv2.COLORMAP_JET)
|
77 |
+
mobilenet_heatmap_colored = cv2.applyColorMap(np.uint8(255 * mobilenet_grayscale_cam), cv2.COLORMAP_JET)
|
78 |
+
|
79 |
+
# normalized_efficientnet_heatmap = efficientnet_heatmap_colored / np.max(efficientnet_heatmap_colored)
|
80 |
+
# normalized_squeezenet_heatmap = squeezenet_heatmap_colored / np.max(squeezenet_heatmap_colored)
|
81 |
+
# normalized_mobilenet_heatmap = mobilenet_heatmap_colored / np.max(mobilenet_heatmap_colored)
|
82 |
+
|
83 |
+
# # Ensure heatmap_colored has the same dtype as rgb_img
|
84 |
+
# normalized_efficientnet_heatmap = normalized_efficientnet_heatmap.astype(np.float32) / 255
|
85 |
+
# normalized_squeezenet_heatmap = normalized_squeezenet_heatmap.astype(np.float32) / 255
|
86 |
+
# normalized_mobilenet_heatmap = normalized_mobilenet_heatmap.astype(np.float32) / 255
|
87 |
+
|
88 |
+
efficientnet_heatmap_colored = efficientnet_heatmap_colored.astype(np.float32) / 255
|
89 |
+
squeezenet_heatmap_colored = squeezenet_heatmap_colored.astype(np.float32) / 255
|
90 |
+
mobilenet_heatmap_colored = mobilenet_heatmap_colored.astype(np.float32) / 255
|
91 |
+
|
92 |
+
# Adjust the alpha value to control transparency
|
93 |
+
alpha = (
|
94 |
+
0.1 # You can change this value to make the original image more or less transparent
|
95 |
+
)
|
96 |
+
|
97 |
+
|
98 |
+
# [0.38, 0.34, 0.28]
|
99 |
+
weighted_heatmap = (
|
100 |
+
efficientnet_heatmap_colored * 0.38
|
101 |
+
+ squeezenet_heatmap_colored * 0.34
|
102 |
+
+ mobilenet_heatmap_colored * 0.28
|
103 |
+
)
|
104 |
+
|
105 |
+
|
106 |
+
# Overlay the colored heatmap on the original image
|
107 |
+
final_output = cv2.addWeighted(rgb_img, 0.3, weighted_heatmap, 0.7, 0)
|
108 |
+
|
109 |
+
# Save the final output
|
110 |
+
cv2.imwrite("cam.jpg", (final_output * 255).astype(np.uint8))
|
extract.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pytorch_grad_cam import GradCAMPlusPlus
|
2 |
+
from pytorch_grad_cam.utils.image import show_cam_on_image, preprocess_image
|
3 |
+
import cv2
|
4 |
+
import numpy as np
|
5 |
+
import torch
|
6 |
+
import torch.nn as nn # Replace with your model
|
7 |
+
from configs import *
|
8 |
+
|
9 |
+
# Load your model (replace with your model class)
|
10 |
+
model = MODEL # Replace with your model
|
11 |
+
model.load_state_dict(torch.load(MODEL_SAVE_PATH))
|
12 |
+
model.eval()
|
13 |
+
model = model.to(DEVICE)
|
14 |
+
|
15 |
+
# Find the target layer (modify this based on your model architecture)
|
16 |
+
target_layer = None
|
17 |
+
for child in model.features[-1]:
|
18 |
+
if isinstance(child, nn.Conv2d):
|
19 |
+
target_layer = child
|
20 |
+
|
21 |
+
if target_layer is None:
|
22 |
+
raise ValueError("Invalid layer name: {}".format(target_layer))
|
23 |
+
|
24 |
+
# Load and preprocess the image
|
25 |
+
image_path = r'data\test\Task 1\Parkinson Disease\V14PE02.png'
|
26 |
+
rgb_img = cv2.imread(image_path, 1)
|
27 |
+
rgb_img = np.float32(rgb_img) / 255
|
28 |
+
input_tensor = preprocess_image(rgb_img, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
|
29 |
+
input_tensor = input_tensor.to(DEVICE)
|
30 |
+
|
31 |
+
# Create a GradCAMPlusPlus object
|
32 |
+
cam = GradCAMPlusPlus(model=model, target_layers=[target_layer], use_cuda=True)
|
33 |
+
|
34 |
+
# Generate the GradCAM heatmap
|
35 |
+
grayscale_cam = cam(input_tensor=input_tensor)[0]
|
36 |
+
|
37 |
+
# Apply a colormap to the grayscale heatmap
|
38 |
+
heatmap_colored = cv2.applyColorMap(np.uint8(255 * grayscale_cam), cv2.COLORMAP_JET)
|
39 |
+
|
40 |
+
# Ensure heatmap_colored has the same dtype as rgb_img
|
41 |
+
heatmap_colored = heatmap_colored.astype(np.float32) / 255
|
42 |
+
|
43 |
+
# Adjust the alpha value to control transparency
|
44 |
+
alpha = 0.3 # You can change this value to make the original image more or less transparent
|
45 |
+
|
46 |
+
# Overlay the colored heatmap on the original image
|
47 |
+
final_output = cv2.addWeighted(rgb_img, 0.3, heatmap_colored, 0.7, 0)
|
48 |
+
|
49 |
+
# Save the final output
|
50 |
+
cv2.imwrite('cam.jpg', (final_output * 255).astype(np.uint8))
|
genetric_algorithm.py
ADDED
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import optuna
|
3 |
+
from optuna.trial import TrialState
|
4 |
+
import torch
|
5 |
+
import torch.nn as nn
|
6 |
+
import torch.optim as optim
|
7 |
+
from configs import *
|
8 |
+
import data_loader
|
9 |
+
from torch.utils.tensorboard import SummaryWriter
|
10 |
+
import numpy as np
|
11 |
+
import pygad
|
12 |
+
import pygad.torchga
|
13 |
+
|
14 |
+
torch.cuda.empty_cache()
|
15 |
+
model = MODEL.to(DEVICE)
|
16 |
+
|
17 |
+
EPOCHS = 10
|
18 |
+
N_TRIALS = 20
|
19 |
+
TIMEOUT = 1800
|
20 |
+
EARLY_STOPPING_PATIENCE = (
|
21 |
+
4 # Number of epochs with no improvement to trigger early stopping
|
22 |
+
)
|
23 |
+
NUM_GENERATIONS = 10
|
24 |
+
SOL_PER_POP = 10 # Number of solutions in the population
|
25 |
+
NUM_GENES = 2
|
26 |
+
NUM_PARENTS_MATING = 4
|
27 |
+
|
28 |
+
# Create a TensorBoard writer
|
29 |
+
writer = SummaryWriter(log_dir="output/tensorboard/tuning")
|
30 |
+
|
31 |
+
|
32 |
+
# Function to create or modify data loaders with the specified batch size
|
33 |
+
def create_data_loaders(batch_size):
|
34 |
+
train_loader, valid_loader = data_loader.load_data(
|
35 |
+
COMBINED_DATA_DIR + "1",
|
36 |
+
preprocess,
|
37 |
+
batch_size=batch_size,
|
38 |
+
)
|
39 |
+
return train_loader, valid_loader
|
40 |
+
|
41 |
+
|
42 |
+
# Objective function for optimization
|
43 |
+
def objective(trial):
|
44 |
+
global data_inputs, data_outputs
|
45 |
+
|
46 |
+
batch_size = trial.suggest_categorical("batch_size", [16, 32, 64])
|
47 |
+
train_loader, valid_loader = create_data_loaders(batch_size)
|
48 |
+
|
49 |
+
lr = trial.suggest_float("lr", 1e-5, 1e-3, log=True)
|
50 |
+
optimizer = optim.Adam(model.parameters(), lr=lr)
|
51 |
+
criterion = nn.CrossEntropyLoss()
|
52 |
+
|
53 |
+
gamma = trial.suggest_float("gamma", 0.1, 0.9, step=0.1)
|
54 |
+
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)
|
55 |
+
|
56 |
+
past_trials = 0 # Number of trials already completed
|
57 |
+
|
58 |
+
# Print best hyperparameters:
|
59 |
+
if past_trials > 0:
|
60 |
+
print("\nBest Hyperparameters:")
|
61 |
+
print(f"{study.best_trial.params}")
|
62 |
+
|
63 |
+
print(f"\n[INFO] Trial: {trial.number}")
|
64 |
+
print(f"Batch Size: {batch_size}")
|
65 |
+
print(f"Learning Rate: {lr}")
|
66 |
+
print(f"Gamma: {gamma}\n")
|
67 |
+
|
68 |
+
early_stopping_counter = 0
|
69 |
+
best_accuracy = 0.0
|
70 |
+
|
71 |
+
for epoch in range(EPOCHS):
|
72 |
+
model.train()
|
73 |
+
for batch_idx, (data, target) in enumerate(train_loader, 0):
|
74 |
+
data, target = data.to(DEVICE), target.to(DEVICE)
|
75 |
+
optimizer.zero_grad()
|
76 |
+
output = model(data)
|
77 |
+
loss = criterion(output, target)
|
78 |
+
loss.backward()
|
79 |
+
optimizer.step()
|
80 |
+
|
81 |
+
scheduler.step()
|
82 |
+
|
83 |
+
model.eval()
|
84 |
+
correct = 0
|
85 |
+
with torch.no_grad():
|
86 |
+
for batch_idx, (data, target) in enumerate(valid_loader, 0):
|
87 |
+
data, target = data.to(DEVICE), target.to(DEVICE)
|
88 |
+
output = model(data)
|
89 |
+
pred = output.argmax(dim=1, keepdim=True)
|
90 |
+
correct += pred.eq(target.view_as(pred)).sum().item()
|
91 |
+
|
92 |
+
accuracy = correct / len(valid_loader.dataset)
|
93 |
+
|
94 |
+
# Log hyperparameters and accuracy to TensorBoard
|
95 |
+
writer.add_scalar("Accuracy", accuracy, trial.number)
|
96 |
+
writer.add_hparams(
|
97 |
+
{"batch_size": batch_size, "lr": lr, "gamma": gamma},
|
98 |
+
{"accuracy": accuracy},
|
99 |
+
)
|
100 |
+
|
101 |
+
print(f"[EPOCH {epoch + 1}] Accuracy: {accuracy:.4f}")
|
102 |
+
|
103 |
+
trial.report(accuracy, epoch)
|
104 |
+
|
105 |
+
if accuracy > best_accuracy:
|
106 |
+
best_accuracy = accuracy
|
107 |
+
early_stopping_counter = 0
|
108 |
+
else:
|
109 |
+
early_stopping_counter += 1
|
110 |
+
|
111 |
+
# Early stopping check
|
112 |
+
if early_stopping_counter >= EARLY_STOPPING_PATIENCE:
|
113 |
+
print(f"\nEarly stopping at epoch {epoch + 1}")
|
114 |
+
break
|
115 |
+
|
116 |
+
if trial.number > 10 and trial.params["lr"] < 1e-3 and best_accuracy < 0.7:
|
117 |
+
return float("inf")
|
118 |
+
|
119 |
+
past_trials += 1
|
120 |
+
|
121 |
+
return best_accuracy
|
122 |
+
|
123 |
+
|
124 |
+
# Custom genetic algorithm
|
125 |
+
def run_genetic_algorithm(fitness_func):
|
126 |
+
# Initial population
|
127 |
+
population = np.random.rand(SOL_PER_POP, NUM_GENES) # Random initialization
|
128 |
+
|
129 |
+
# Run for a fixed number of generations
|
130 |
+
for generation in range(NUM_GENERATIONS):
|
131 |
+
# Calculate fitness for each solution in the population
|
132 |
+
fitness = np.array(
|
133 |
+
[fitness_func(solution, idx) for idx, solution in enumerate(population)]
|
134 |
+
)
|
135 |
+
|
136 |
+
# Get the index of the best solution
|
137 |
+
best_idx = np.argmax(fitness)
|
138 |
+
best_solution = population[best_idx]
|
139 |
+
best_fitness = fitness[best_idx]
|
140 |
+
|
141 |
+
# Print the best solution and fitness for this generation
|
142 |
+
print(f"Generation {generation + 1}:")
|
143 |
+
print("Best Solution:")
|
144 |
+
print("Learning Rate = {lr}".format(lr=best_solution[0]))
|
145 |
+
print("Gamma = {gamma}".format(gamma=best_solution[1]))
|
146 |
+
print("Best Fitness = {fitness}".format(fitness=best_fitness))
|
147 |
+
|
148 |
+
# Perform selection and crossover to create the next generation
|
149 |
+
population = selection_and_crossover(population, fitness)
|
150 |
+
|
151 |
+
|
152 |
+
# Selection and crossover logic
|
153 |
+
def selection_and_crossover(population, fitness):
|
154 |
+
# Perform tournament selection
|
155 |
+
parents = []
|
156 |
+
for _ in range(SOL_PER_POP):
|
157 |
+
tournament_idxs = np.random.choice(range(SOL_PER_POP), NUM_PARENTS_MATING)
|
158 |
+
tournament_fitness = [fitness[idx] for idx in tournament_idxs]
|
159 |
+
selected_parent_idx = tournament_idxs[np.argmax(tournament_fitness)]
|
160 |
+
parents.append(population[selected_parent_idx])
|
161 |
+
|
162 |
+
# Perform single-point crossover
|
163 |
+
offspring = []
|
164 |
+
for i in range(0, SOL_PER_POP, 2):
|
165 |
+
if i + 1 < SOL_PER_POP:
|
166 |
+
crossover_point = np.random.randint(0, NUM_GENES)
|
167 |
+
offspring.extend(
|
168 |
+
[
|
169 |
+
np.concatenate(
|
170 |
+
(parents[i][:crossover_point], parents[i + 1][crossover_point:])
|
171 |
+
)
|
172 |
+
]
|
173 |
+
)
|
174 |
+
offspring.extend(
|
175 |
+
[
|
176 |
+
np.concatenate(
|
177 |
+
(parents[i + 1][:crossover_point], parents[i][crossover_point:])
|
178 |
+
)
|
179 |
+
]
|
180 |
+
)
|
181 |
+
|
182 |
+
return np.array(offspring)
|
183 |
+
|
184 |
+
|
185 |
+
# Modify callback function to log best accuracy
|
186 |
+
def callback_generation(ga_instance):
|
187 |
+
global study
|
188 |
+
|
189 |
+
# Fetch the parameters of the best solution
|
190 |
+
solution, solution_fitness, _ = ga_instance.best_solution()
|
191 |
+
best_learning_rate, best_gamma = solution
|
192 |
+
|
193 |
+
# Report the best accuracy to Optuna study
|
194 |
+
study.set_user_attr("best_accuracy", solution_fitness)
|
195 |
+
|
196 |
+
# Print generation number and best fitness
|
197 |
+
print(
|
198 |
+
"Generation = {generation}".format(generation=ga_instance.generations_completed)
|
199 |
+
)
|
200 |
+
print("Best Fitness = {fitness}".format(fitness=solution_fitness))
|
201 |
+
print("Best Learning Rate = {lr}".format(lr=best_learning_rate))
|
202 |
+
print("Best Gamma = {gamma}".format(gamma=best_gamma))
|
203 |
+
|
204 |
+
|
205 |
+
if __name__ == "__main__":
|
206 |
+
global study
|
207 |
+
pruner = optuna.pruners.HyperbandPruner()
|
208 |
+
study = optuna.create_study(
|
209 |
+
direction="maximize",
|
210 |
+
pruner=pruner,
|
211 |
+
study_name="hyperparameter_tuning",
|
212 |
+
)
|
213 |
+
|
214 |
+
# Define data_inputs and data_outputs
|
215 |
+
# You need to populate these with your own data
|
216 |
+
|
217 |
+
# Define the loss function
|
218 |
+
loss_function = nn.CrossEntropyLoss()
|
219 |
+
|
220 |
+
def fitness_func(solution, sol_idx):
|
221 |
+
global data_inputs, data_outputs, model, loss_function
|
222 |
+
|
223 |
+
learning_rate, momentum = solution
|
224 |
+
|
225 |
+
# Update optimizer with the current learning rate and momentum
|
226 |
+
optimizer = torch.optim.SGD(
|
227 |
+
model.parameters(), lr=learning_rate, momentum=momentum
|
228 |
+
)
|
229 |
+
|
230 |
+
# Load the model weights
|
231 |
+
model_weights_dict = pygad.torchga.model_weights_as_dict(
|
232 |
+
model=model, weights_vector=solution
|
233 |
+
)
|
234 |
+
model.load_state_dict(model_weights_dict)
|
235 |
+
|
236 |
+
# Forward pass
|
237 |
+
predictions = model(data_inputs)
|
238 |
+
|
239 |
+
# Calculate cross-entropy loss
|
240 |
+
loss = loss_function(predictions, data_outputs)
|
241 |
+
|
242 |
+
# Higher fitness for lower loss
|
243 |
+
solution_fitness = 1.0 / (loss.detach().numpy() + 1e-8)
|
244 |
+
|
245 |
+
return solution_fitness
|
246 |
+
|
247 |
+
# Run the custom genetic algorithm
|
248 |
+
run_genetic_algorithm(fitness_func)
|
lazy_predict.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import torch
|
3 |
+
import torch.nn as nn
|
4 |
+
import torch.optim as optim
|
5 |
+
import matplotlib.pyplot as plt
|
6 |
+
from models import *
|
7 |
+
from torch.utils.tensorboard import SummaryWriter
|
8 |
+
from configs import *
|
9 |
+
import data_loader
|
10 |
+
import numpy as np
|
11 |
+
from lazypredict.Supervised import LazyClassifier
|
12 |
+
from sklearn.utils import shuffle
|
13 |
+
|
14 |
+
def extract_features_labels(loader):
|
15 |
+
data = []
|
16 |
+
labels = []
|
17 |
+
for inputs, labels_batch in loader:
|
18 |
+
for img in inputs:
|
19 |
+
data.append(img.view(-1).numpy())
|
20 |
+
labels.extend(labels_batch.numpy())
|
21 |
+
return np.array(data), np.array(labels)
|
22 |
+
|
23 |
+
def load_and_preprocess_data():
|
24 |
+
train_loader, valid_loader = data_loader.load_data(
|
25 |
+
RAW_DATA_DIR + str(TASK),
|
26 |
+
AUG_DATA_DIR + str(TASK),
|
27 |
+
EXTERNAL_DATA_DIR + str(TASK),
|
28 |
+
preprocess,
|
29 |
+
)
|
30 |
+
return train_loader, valid_loader
|
31 |
+
|
32 |
+
def initialize_model_optimizer_scheduler(train_loader, valid_loader):
|
33 |
+
model = MODEL.to(DEVICE)
|
34 |
+
criterion = nn.CrossEntropyLoss()
|
35 |
+
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
|
36 |
+
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=NUM_EPOCHS)
|
37 |
+
return model, criterion, optimizer, scheduler
|
38 |
+
|
39 |
+
# Load and preprocess data
|
40 |
+
train_loader, valid_loader = load_and_preprocess_data()
|
41 |
+
|
42 |
+
# Initialize the model, criterion, optimizer, and scheduler
|
43 |
+
model, criterion, optimizer, scheduler = initialize_model_optimizer_scheduler(train_loader, valid_loader)
|
44 |
+
|
45 |
+
# Extract features and labels
|
46 |
+
X_train, y_train = extract_features_labels(train_loader)
|
47 |
+
X_valid, y_valid = extract_features_labels(valid_loader)
|
48 |
+
|
49 |
+
# LazyClassifier
|
50 |
+
clf = LazyClassifier(verbose=0, ignore_warnings=True, custom_metric=None)
|
51 |
+
models, predictions = clf.fit(X_train, X_valid, y_train, y_valid)
|
52 |
+
|
53 |
+
print("Models:", models)
|
54 |
+
print("Predictions:", predictions)
|
55 |
+
|
56 |
+
|
57 |
+
|
58 |
+
|
59 |
+
|
60 |
+
|
models.py
CHANGED
@@ -39,3 +39,31 @@ from torchvision.models import efficientnet_v2_m
|
|
39 |
from torchvision.models import efficientnet_v2_l
|
40 |
from torchvision.models import efficientnet_b0
|
41 |
from torchvision.models import efficientnet_b1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
from torchvision.models import efficientnet_v2_l
|
40 |
from torchvision.models import efficientnet_b0
|
41 |
from torchvision.models import efficientnet_b1
|
42 |
+
import torch
|
43 |
+
import torch.nn as nn
|
44 |
+
|
45 |
+
class WeightedVoteEnsemble(nn.Module):
|
46 |
+
def __init__(self, models, weights):
|
47 |
+
super(WeightedVoteEnsemble, self).__init__()
|
48 |
+
self.models = models
|
49 |
+
self.weights = weights
|
50 |
+
|
51 |
+
def forward(self, x):
|
52 |
+
predictions = [model(x) for model in self.models]
|
53 |
+
weighted_predictions = torch.stack(
|
54 |
+
[w * pred for w, pred in zip(self.weights, predictions)], dim=0
|
55 |
+
)
|
56 |
+
avg_predictions = weighted_predictions.sum(dim=0)
|
57 |
+
return avg_predictions
|
58 |
+
|
59 |
+
|
60 |
+
def ensemble_predictions(models, image):
|
61 |
+
all_predictions = []
|
62 |
+
|
63 |
+
with torch.no_grad():
|
64 |
+
for model in models:
|
65 |
+
output = model(image)
|
66 |
+
all_predictions.append(output)
|
67 |
+
|
68 |
+
return torch.stack(all_predictions, dim=0).mean(dim=0)
|
69 |
+
|
plot-gradcam.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Plot the gradcam pics of 7 classes from C:\Users\User\Documents\PISTEK\HANDETECT\docs\efficientnet\gradcam folder
|
2 |
+
# Each picture is named as <class_name>.jpg
|
3 |
+
# Usage: python plot-gradcam.py
|
4 |
+
|
5 |
+
import os
|
6 |
+
import cv2
|
7 |
+
import numpy as np
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
from matplotlib import rcParams
|
10 |
+
|
11 |
+
rcParams['font.family'] = 'Times New Roman'
|
12 |
+
|
13 |
+
# Load the gradcam pics
|
14 |
+
gradcam_dir = r'C:\Users\User\Documents\PISTEK\HANDETECT\docs\efficientnet\gradcam'
|
15 |
+
gradcam_pics = []
|
16 |
+
for pic in os.listdir(gradcam_dir):
|
17 |
+
gradcam_pics.append(cv2.imread(os.path.join(gradcam_dir, pic), 1))
|
18 |
+
|
19 |
+
# Plot the gradcam pics
|
20 |
+
plt.figure(figsize=(20, 20))
|
21 |
+
# Very tight layout
|
22 |
+
plt.tight_layout(pad=0.1)
|
23 |
+
for i, pic in enumerate(gradcam_pics):
|
24 |
+
plt.subplot(3, 3, i + 1)
|
25 |
+
plt.imshow(pic)
|
26 |
+
plt.axis('off')
|
27 |
+
plt.title(os.listdir(gradcam_dir)[i].split('.')[0], fontsize=13)
|
28 |
+
plt.savefig('docs/efficientnet/gradcam.jpg')
|
29 |
+
plt.show()
|
30 |
+
|
predict.py
CHANGED
@@ -10,16 +10,29 @@ from configs import *
|
|
10 |
|
11 |
|
12 |
# Load your model (change this according to your model definition)
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
torch.set_grad_enabled(False)
|
20 |
|
21 |
|
22 |
-
def predict_image(image_path, model=
|
23 |
classes = CLASSES
|
24 |
|
25 |
print("---------------------------")
|
|
|
10 |
|
11 |
|
12 |
# Load your model (change this according to your model definition)
|
13 |
+
model2 = EfficientNetB2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
14 |
+
model2.load_state_dict(torch.load("output/checkpoints/EfficientNetB2WithDropout.pth"))
|
15 |
+
model1 = SqueezeNet1_0WithSE(num_classes=NUM_CLASSES).to(DEVICE)
|
16 |
+
model1.load_state_dict(torch.load("output/checkpoints/SqueezeNet1_0WithSE.pth"))
|
17 |
+
model3 = MobileNetV2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
18 |
+
model3.load_state_dict(torch.load("output\checkpoints\MobileNetV2WithDropout.pth"))
|
19 |
+
|
20 |
+
model1.eval()
|
21 |
+
model2.eval()
|
22 |
+
model3.eval()
|
23 |
+
|
24 |
+
# Load the model
|
25 |
+
model = MODEL.to(DEVICE)
|
26 |
+
# model.load_state_dict(torch.load(MODEL_SAVE_PATH, map_location=DEVICE))
|
27 |
+
model.load_state_dict(
|
28 |
+
torch.load("output/checkpoints/EfficientNetB3WithDropout.pth", map_location=DEVICE)
|
29 |
+
)
|
30 |
+
model.eval()
|
31 |
+
|
32 |
torch.set_grad_enabled(False)
|
33 |
|
34 |
|
35 |
+
def predict_image(image_path, model=model, transform=preprocess):
|
36 |
classes = CLASSES
|
37 |
|
38 |
print("---------------------------")
|
shap_eval.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
from lime.lime_image import LimeImageExplainer
|
3 |
+
from PIL import Image
|
4 |
+
import torch
|
5 |
+
import torchvision.transforms as transforms
|
6 |
+
import matplotlib.pyplot as plt
|
7 |
+
from configs import *
|
8 |
+
|
9 |
+
|
10 |
+
model = MODEL.to(DEVICE)
|
11 |
+
model.load_state_dict(torch.load(MODEL_SAVE_PATH))
|
12 |
+
model.eval()
|
13 |
+
|
14 |
+
# Load the image
|
15 |
+
image = Image.open(
|
16 |
+
r"data\test\Task 1\Healthy\0a7259b2-e650-43aa-93a0-e8b1063476fc.png"
|
17 |
+
).convert("RGB")
|
18 |
+
image = preprocess(image)
|
19 |
+
image = image.unsqueeze(0) # Add batch dimension
|
20 |
+
image = image.to(DEVICE)
|
21 |
+
|
22 |
+
|
23 |
+
# Define a function to predict with the model
|
24 |
+
def predict(input_image):
|
25 |
+
input_image = torch.tensor(input_image, dtype=torch.float32)
|
26 |
+
if input_image.dim() == 4:
|
27 |
+
input_image = input_image.permute(0, 3, 1, 2) # Permute the dimensions
|
28 |
+
input_image = input_image.to(DEVICE) # Move to the appropriate device
|
29 |
+
with torch.no_grad():
|
30 |
+
output = model(input_image)
|
31 |
+
return output
|
32 |
+
|
33 |
+
|
34 |
+
# Create the LIME explainer
|
35 |
+
explainer = LimeImageExplainer()
|
36 |
+
|
37 |
+
# Explain the model's predictions for the image
|
38 |
+
explanation = explainer.explain_instance(
|
39 |
+
image[0].permute(1, 2, 0).numpy(), predict, top_labels=5, num_samples=2000
|
40 |
+
)
|
41 |
+
|
42 |
+
# Get the image and mask for the explanation
|
43 |
+
image, mask = explanation.get_image_and_mask(
|
44 |
+
explanation.top_labels[0], positive_only=False, num_features=5, hide_rest=False
|
45 |
+
)
|
46 |
+
|
47 |
+
# Display the explanation
|
48 |
+
plt.imshow(image)
|
49 |
+
plt.show()
|
test.py
CHANGED
@@ -1,39 +1,223 @@
|
|
|
|
1 |
import torch
|
2 |
-
import torch.
|
3 |
-
|
4 |
-
import
|
5 |
from configs import *
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
11 |
|
12 |
-
#
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
# Define
|
16 |
-
|
17 |
|
18 |
-
#
|
19 |
-
|
20 |
|
21 |
-
# Optimization loop
|
22 |
-
for _ in range(1000):
|
23 |
-
optimizer.zero_grad()
|
24 |
-
output = MODEL(image)
|
25 |
-
loss = -output[0, 'Healthy'] # Maximize the activation of a specific class
|
26 |
-
loss.backward()
|
27 |
-
optimizer.step()
|
28 |
|
29 |
-
#
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
-
# Convert the tensor to an image
|
34 |
-
image = transforms.ToPILImage()(image.squeeze())
|
35 |
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
1 |
+
import sys
|
2 |
import torch
|
3 |
+
import torch.nn as nn
|
4 |
+
from PIL import Image
|
5 |
+
import os
|
6 |
from configs import *
|
7 |
+
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
import random
|
10 |
+
from itertools import product
|
11 |
|
12 |
+
random.seed(RANDOM_SEED)
|
13 |
+
torch.cuda.manual_seed(RANDOM_SEED)
|
14 |
+
torch.manual_seed(RANDOM_SEED)
|
15 |
+
print("PyTorch Seed:", torch.initial_seed())
|
16 |
+
print("Random Seed:", random.getstate()[1][0])
|
17 |
+
print("PyTorch CUDA Seed:", torch.cuda.initial_seed())
|
18 |
|
19 |
+
# Define your model paths
|
20 |
+
# Load your pre-trained models
|
21 |
+
model2 = EfficientNetB2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
22 |
+
model2.load_state_dict(torch.load("output/checkpoints/EfficientNetB2WithDropout.pth"))
|
23 |
+
model1 = SqueezeNet1_0WithSE(num_classes=NUM_CLASSES).to(DEVICE)
|
24 |
+
model1.load_state_dict(torch.load("output/checkpoints/SqueezeNet1_0WithSE.pth"))
|
25 |
+
model3 = MobileNetV2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
26 |
+
model3.load_state_dict(torch.load("output\checkpoints\MobileNetV2WithDropout.pth"))
|
27 |
|
28 |
+
# Define the class labels
|
29 |
+
class_labels = CLASSES
|
30 |
|
31 |
+
# Define your test data folder path
|
32 |
+
test_data_folder = "data/test/Task 1/"
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
+
# Put models in evaluation mode
|
36 |
+
def set_models_eval(models):
|
37 |
+
for model in models:
|
38 |
+
model.eval()
|
39 |
+
|
40 |
+
|
41 |
+
# Define the ensemble model using a list of models
|
42 |
+
class WeightedVoteEnsemble(nn.Module):
|
43 |
+
def __init__(self, models, weights):
|
44 |
+
super(WeightedVoteEnsemble, self).__init__()
|
45 |
+
self.models = models
|
46 |
+
self.weights = weights
|
47 |
+
|
48 |
+
def forward(self, x):
|
49 |
+
predictions = [model(x) for model in self.models]
|
50 |
+
weighted_predictions = torch.stack(
|
51 |
+
[w * pred for w, pred in zip(self.weights, predictions)], dim=0
|
52 |
+
)
|
53 |
+
avg_predictions = weighted_predictions.sum(dim=0)
|
54 |
+
return avg_predictions
|
55 |
+
|
56 |
+
|
57 |
+
def ensemble_predictions(models, image):
|
58 |
+
all_predictions = []
|
59 |
+
|
60 |
+
with torch.no_grad():
|
61 |
+
for model in models:
|
62 |
+
output = model(image)
|
63 |
+
all_predictions.append(output)
|
64 |
+
|
65 |
+
return torch.stack(all_predictions, dim=0).mean(dim=0)
|
66 |
+
|
67 |
+
|
68 |
+
# Load a single image and make predictions
|
69 |
+
def evaluate_image(models, image_path, transform=preprocess):
|
70 |
+
image = Image.open(image_path).convert("RGB")
|
71 |
+
image = transform(image).unsqueeze(0)
|
72 |
+
image = image.to(DEVICE)
|
73 |
+
outputs = ensemble_predictions(models, image)
|
74 |
+
|
75 |
+
return outputs.argmax(dim=1).item()
|
76 |
+
|
77 |
+
|
78 |
+
# Evaluate and plot a confusion matrix for an ensemble of models
|
79 |
+
def evaluate_and_plot_confusion_matrix(models, test_data_folder):
|
80 |
+
all_predictions = []
|
81 |
+
true_labels = []
|
82 |
+
|
83 |
+
with torch.no_grad():
|
84 |
+
for class_label in class_labels:
|
85 |
+
class_path = os.path.join(test_data_folder, class_label)
|
86 |
+
for image_file in os.listdir(class_path):
|
87 |
+
image_path = os.path.join(class_path, image_file)
|
88 |
+
# print(image_path)
|
89 |
+
predicted_label = evaluate_image(models, image_path, preprocess)
|
90 |
+
all_predictions.append(predicted_label)
|
91 |
+
true_labels.append(class_labels.index(class_label))
|
92 |
+
|
93 |
+
# Print accuracy
|
94 |
+
accuracy = (
|
95 |
+
(torch.tensor(all_predictions) == torch.tensor(true_labels)).float().mean()
|
96 |
+
)
|
97 |
+
print("Accuracy:", accuracy)
|
98 |
+
|
99 |
+
# Create the confusion matrix
|
100 |
+
cm = confusion_matrix(true_labels, all_predictions)
|
101 |
+
|
102 |
+
# Plot the confusion matrix
|
103 |
+
display = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_labels)
|
104 |
+
display.plot(cmap=plt.cm.Blues, values_format="d")
|
105 |
+
|
106 |
+
# Show the plot
|
107 |
+
plt.show()
|
108 |
+
|
109 |
+
return accuracy
|
110 |
+
|
111 |
+
# Set the models to evaluation mode
|
112 |
+
set_models_eval([model1, model2, model3])
|
113 |
+
|
114 |
+
# Define different weight configurations
|
115 |
+
# [SqueezeNet, EfficientNetB2WithDropout, MobileNetV2WithDropout]
|
116 |
+
weights_configurations = [
|
117 |
+
# Random set of weights using random.random() and all weights sum to 1
|
118 |
+
[
|
119 |
+
random.randrange(1, 10) / 10,
|
120 |
+
random.randrange(1, 10) / 10,
|
121 |
+
random.randrange(1, 10) / 10,
|
122 |
+
],
|
123 |
+
]
|
124 |
+
|
125 |
+
|
126 |
+
## NOTE OF PREVIOUS WEIGHTS
|
127 |
+
# Best weights: [0.2, 0.3, 0.5] with accuracy: 0.9428571462631226 at iteration: 15 with torch seed: 28434738589300 and random seed: 3188652458777471118 and torch cuda seed: None
|
128 |
+
|
129 |
+
|
130 |
+
best_weights = {
|
131 |
+
"weights": 0,
|
132 |
+
"accuracy": 0,
|
133 |
+
"iteration": 0,
|
134 |
+
"torch_seed": 0,
|
135 |
+
"random_seed": 0,
|
136 |
+
"torch_cuda_seed": 0,
|
137 |
+
}
|
138 |
+
|
139 |
+
i = 0
|
140 |
+
|
141 |
+
# weights_hist = []
|
142 |
+
|
143 |
+
target_sum = 1.0
|
144 |
+
number_of_numbers = 3
|
145 |
+
lower_limit = 0.20
|
146 |
+
upper_limit = 0.9
|
147 |
+
step = 0.1
|
148 |
+
|
149 |
+
valid_combinations = []
|
150 |
+
|
151 |
+
# Generate all unique combinations of three numbers with values to two decimal places
|
152 |
+
range_values = list(range(int(lower_limit * 100), int(upper_limit * 100) + 1))
|
153 |
+
for combo in product(range_values, repeat=number_of_numbers):
|
154 |
+
combo_float = [x / 100.0 for x in combo]
|
155 |
+
|
156 |
+
# Check if the sum of the numbers is equal to 1
|
157 |
+
if sum(combo_float) == target_sum:
|
158 |
+
valid_combinations.append(combo_float)
|
159 |
+
|
160 |
+
# Calculate the total number of possibilities
|
161 |
+
total_possibilities = len(valid_combinations)
|
162 |
+
|
163 |
+
print("Total number of possibilities:", total_possibilities)
|
164 |
+
|
165 |
+
valid_combinations = [[0.37, 0.34, 0.29]]
|
166 |
+
|
167 |
+
for weights in valid_combinations:
|
168 |
+
# while True:
|
169 |
+
print("---------------------------")
|
170 |
+
print("Iteration:", i)
|
171 |
+
# Should iterate until all possible weights are exhausted
|
172 |
+
# Create an ensemble model with weighted voting
|
173 |
+
|
174 |
+
random.seed(RANDOM_SEED)
|
175 |
+
torch.cuda.manual_seed(RANDOM_SEED)
|
176 |
+
torch.manual_seed(RANDOM_SEED)
|
177 |
+
# print("PyTorch Seed:", torch.initial_seed())
|
178 |
+
# weights_hist.append(weights)
|
179 |
+
weighted_vote_ensemble_model = WeightedVoteEnsemble(
|
180 |
+
# [model1, model2, model3], weights
|
181 |
+
[model1, model2, model3],
|
182 |
+
weights,
|
183 |
+
)
|
184 |
+
# print("Weights:", weights)
|
185 |
+
print("Weights:", weights)
|
186 |
+
# Call the evaluate_and_plot_confusion_matrix function with your models and test data folder
|
187 |
+
accuracy = evaluate_and_plot_confusion_matrix(
|
188 |
+
[weighted_vote_ensemble_model], test_data_folder
|
189 |
+
)
|
190 |
+
# Convert tensor to float
|
191 |
+
accuracy = accuracy.item()
|
192 |
+
if accuracy > best_weights["accuracy"]:
|
193 |
+
# best_weights["weights"] = weights
|
194 |
+
best_weights["weights"] = weights
|
195 |
+
best_weights["accuracy"] = accuracy
|
196 |
+
best_weights["iteration"] = i
|
197 |
+
best_weights["torch_seed"] = torch.initial_seed()
|
198 |
+
seed = random.randrange(sys.maxsize)
|
199 |
+
rng = random.Random(seed)
|
200 |
+
best_weights["random_seed"] = seed
|
201 |
+
best_weights["torch_cuda_seed"] = torch.cuda.initial_seed()
|
202 |
+
|
203 |
+
print(
|
204 |
+
"Best weights:",
|
205 |
+
best_weights["weights"],
|
206 |
+
"with accuracy:",
|
207 |
+
best_weights["accuracy"],
|
208 |
+
"at iteration:",
|
209 |
+
best_weights["iteration"],
|
210 |
+
"with torch seed:",
|
211 |
+
best_weights["torch_seed"],
|
212 |
+
"and random seed:",
|
213 |
+
best_weights["random_seed"],
|
214 |
+
"and torch cuda seed:",
|
215 |
+
best_weights["torch_cuda_seed"],
|
216 |
+
)
|
217 |
+
i += 1
|
218 |
|
|
|
|
|
219 |
|
220 |
+
torch.save(
|
221 |
+
weighted_vote_ensemble_model.state_dict(),
|
222 |
+
"output/checkpoints/WeightedVoteEnsemble.pth",
|
223 |
+
)
|
testing.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
|
3 |
+
print("Torch version:",torch.__version__)
|
4 |
+
|
5 |
+
print("Is CUDA enabled?",torch.cuda.is_available())
|
train-svm.py
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import numpy as np
|
3 |
+
from sklearn import svm
|
4 |
+
from sklearn.model_selection import train_test_split
|
5 |
+
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
|
6 |
+
from skimage.io import imread
|
7 |
+
from skimage.transform import resize
|
8 |
+
from sklearn.model_selection import train_test_split, RandomizedSearchCV
|
9 |
+
from scipy.stats import uniform
|
10 |
+
from configs import *
|
11 |
+
|
12 |
+
# Set the path to your dataset folder, where each subfolder represents a class
|
13 |
+
dataset_path = COMBINED_DATA_DIR + str(1)
|
14 |
+
|
15 |
+
|
16 |
+
# Function to load, resize, and convert images to grayscale
|
17 |
+
def load_resize_and_convert_to_gray(folder, target_size=(100, 100)):
|
18 |
+
images = []
|
19 |
+
for filename in os.listdir(folder):
|
20 |
+
img_path = os.path.join(folder, filename)
|
21 |
+
if os.path.isfile(img_path):
|
22 |
+
img = imread(img_path, as_gray=True)
|
23 |
+
img = resize(img, target_size, anti_aliasing=True)
|
24 |
+
images.append(img)
|
25 |
+
return images
|
26 |
+
|
27 |
+
|
28 |
+
# Load, resize, and convert images to grayscale from folders
|
29 |
+
X = [] # List to store images
|
30 |
+
y = [] # List to store corresponding labels
|
31 |
+
|
32 |
+
class_folders = os.listdir(dataset_path)
|
33 |
+
class_folders.sort() # Sort the class folders to ensure consistent class ordering
|
34 |
+
|
35 |
+
for class_folder in class_folders:
|
36 |
+
class_path = os.path.join(dataset_path, class_folder)
|
37 |
+
if os.path.isdir(class_path):
|
38 |
+
images = load_resize_and_convert_to_gray(class_path)
|
39 |
+
X.extend(images)
|
40 |
+
y.extend([class_folder] * len(images)) # Assign labels based on folder name
|
41 |
+
|
42 |
+
# Convert data to NumPy arrays
|
43 |
+
X = np.array(X)
|
44 |
+
y = np.array(y)
|
45 |
+
|
46 |
+
# Split the dataset into training and testing sets
|
47 |
+
X_train, X_test, y_train, y_test = train_test_split(
|
48 |
+
X, y, test_size=0.2, random_state=42
|
49 |
+
)
|
50 |
+
|
51 |
+
# Define the parameter distributions for random search
|
52 |
+
param_dist = {
|
53 |
+
"C": uniform(loc=0, scale=10), # Randomly sample from [0, 10]
|
54 |
+
"kernel": ["linear", "rbf", "poly"],
|
55 |
+
"gamma": uniform(loc=0.001, scale=0.1), # Randomly sample from [0.001, 0.1]
|
56 |
+
}
|
57 |
+
|
58 |
+
# Flatten the images to a 1D array
|
59 |
+
X_train_flat = X_train.reshape(X_train.shape[0], -1)
|
60 |
+
X_test_flat = X_test.reshape(X_test.shape[0], -1)
|
61 |
+
|
62 |
+
# Create an SVM classifier
|
63 |
+
svm_classifier = svm.SVC()
|
64 |
+
|
65 |
+
# Perform Randomized Search with cross-validation
|
66 |
+
random_search = RandomizedSearchCV(
|
67 |
+
svm_classifier,
|
68 |
+
param_distributions=param_dist,
|
69 |
+
n_iter=50,
|
70 |
+
cv=5,
|
71 |
+
n_jobs=-1,
|
72 |
+
verbose=2,
|
73 |
+
random_state=42,
|
74 |
+
)
|
75 |
+
|
76 |
+
# Fit the Randomized Search on the training data
|
77 |
+
random_search.fit(X_train_flat, y_train)
|
78 |
+
|
79 |
+
# Print the best hyperparameters
|
80 |
+
print("Best Hyperparameters:")
|
81 |
+
print(random_search.best_params_)
|
82 |
+
|
83 |
+
# Get the best SVM model with the tuned hyperparameters
|
84 |
+
best_svm_model = random_search.best_estimator_
|
85 |
+
|
86 |
+
# Evaluate the best model on the test set
|
87 |
+
y_pred = best_svm_model.predict(X_test_flat)
|
88 |
+
|
89 |
+
# Calculate accuracy and other metrics
|
90 |
+
accuracy = accuracy_score(y_test, y_pred)
|
91 |
+
print("Accuracy:", accuracy)
|
92 |
+
|
93 |
+
# Confusion Matrix
|
94 |
+
conf_matrix = confusion_matrix(y_test, y_pred)
|
95 |
+
print("Confusion Matrix:\n", conf_matrix)
|
96 |
+
|
97 |
+
# You can also print other classification metrics like precision, recall, and F1-score
|
98 |
+
from sklearn.metrics import classification_report
|
99 |
+
|
100 |
+
report = classification_report(y_test, y_pred)
|
101 |
+
print("Classification Report:\n", report)
|
train.py
CHANGED
@@ -3,53 +3,75 @@ import torch
|
|
3 |
import torch.nn as nn
|
4 |
import torch.optim as optim
|
5 |
import matplotlib.pyplot as plt
|
|
|
6 |
from models import *
|
7 |
from torch.utils.tensorboard import SummaryWriter
|
8 |
from configs import *
|
9 |
import data_loader
|
10 |
import torch.nn.functional as F
|
|
|
11 |
import numpy as np
|
|
|
12 |
|
13 |
|
14 |
-
|
15 |
-
def __init__(self, epsilon=0.1, num_classes=2):
|
16 |
-
super(LabelSmoothingLoss, self).__init__()
|
17 |
-
self.epsilon = epsilon
|
18 |
-
self.num_classes = num_classes
|
19 |
|
20 |
-
|
21 |
-
|
22 |
-
return nn.CrossEntropyLoss()(input, target_smooth)
|
23 |
|
24 |
|
25 |
-
def
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
|
|
|
|
|
|
|
|
28 |
|
29 |
-
|
30 |
-
|
|
|
|
|
31 |
if alpha > 0:
|
32 |
lam = np.random.beta(alpha, alpha)
|
33 |
else:
|
34 |
lam = 1
|
35 |
|
36 |
-
batch_size =
|
37 |
index = torch.randperm(batch_size)
|
|
|
|
|
|
|
|
|
38 |
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
|
|
|
43 |
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
|
48 |
def load_and_preprocess_data():
|
49 |
return data_loader.load_data(
|
50 |
-
|
51 |
-
AUG_DATA_DIR + str(TASK),
|
52 |
-
EXTERNAL_DATA_DIR + str(TASK),
|
53 |
preprocess,
|
54 |
)
|
55 |
|
@@ -77,13 +99,13 @@ def train_one_epoch(model, criterion, optimizer, train_loader, epoch, alpha):
|
|
77 |
inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)
|
78 |
optimizer.zero_grad()
|
79 |
|
80 |
-
# Apply
|
81 |
-
inputs, targets_a, targets_b, lam =
|
82 |
|
83 |
outputs = model(inputs)
|
84 |
|
85 |
-
# Calculate
|
86 |
-
loss =
|
87 |
|
88 |
loss.backward()
|
89 |
optimizer.step()
|
@@ -91,8 +113,8 @@ def train_one_epoch(model, criterion, optimizer, train_loader, epoch, alpha):
|
|
91 |
|
92 |
if (i + 1) % NUM_PRINT == 0:
|
93 |
print(
|
94 |
-
"[Epoch
|
95 |
-
|
96 |
)
|
97 |
running_loss = 0.0
|
98 |
|
@@ -132,18 +154,22 @@ def main_training_loop():
|
|
132 |
best_val_loss = float("inf")
|
133 |
best_val_accuracy = 0.0
|
134 |
no_improvement_count = 0
|
|
|
135 |
|
136 |
AVG_TRAIN_LOSS_HIST = []
|
137 |
AVG_VAL_LOSS_HIST = []
|
138 |
TRAIN_ACC_HIST = []
|
139 |
VAL_ACC_HIST = []
|
140 |
|
|
|
|
|
|
|
141 |
for epoch in range(NUM_EPOCHS):
|
142 |
-
print(f"[Epoch: {epoch + 1}]")
|
143 |
print("Learning rate:", scheduler.get_last_lr()[0])
|
144 |
|
145 |
avg_train_loss, train_accuracy = train_one_epoch(
|
146 |
-
model, criterion, optimizer, train_loader, epoch,
|
147 |
)
|
148 |
AVG_TRAIN_LOSS_HIST.append(avg_train_loss)
|
149 |
TRAIN_ACC_HIST.append(train_accuracy)
|
@@ -154,9 +180,27 @@ def main_training_loop():
|
|
154 |
"Accuracy": train_accuracy,
|
155 |
}
|
156 |
plot_and_log_metrics(train_metrics, epoch, writer=writer, prefix="Train")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
|
158 |
# Learning rate scheduling
|
159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
160 |
|
161 |
avg_val_loss, val_accuracy = validate_model(model, criterion, valid_loader)
|
162 |
AVG_VAL_LOSS_HIST.append(avg_val_loss)
|
@@ -167,7 +211,7 @@ def main_training_loop():
|
|
167 |
"Loss": avg_val_loss,
|
168 |
"Accuracy": val_accuracy,
|
169 |
}
|
170 |
-
plot_and_log_metrics(
|
171 |
|
172 |
# Print average training and validation metrics
|
173 |
print(f"Average Training Loss: {avg_train_loss:.6f}")
|
@@ -190,11 +234,37 @@ def main_training_loop():
|
|
190 |
)
|
191 |
)
|
192 |
break
|
193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
# Ensure the parent directory exists
|
195 |
os.makedirs(os.path.dirname(MODEL_SAVE_PATH), exist_ok=True)
|
196 |
torch.save(model.state_dict(), MODEL_SAVE_PATH)
|
197 |
-
print("
|
198 |
|
199 |
# Plot loss and accuracy curves
|
200 |
plt.figure(figsize=(12, 4))
|
|
|
3 |
import torch.nn as nn
|
4 |
import torch.optim as optim
|
5 |
import matplotlib.pyplot as plt
|
6 |
+
from matplotlib import rcParams
|
7 |
from models import *
|
8 |
from torch.utils.tensorboard import SummaryWriter
|
9 |
from configs import *
|
10 |
import data_loader
|
11 |
import torch.nn.functional as F
|
12 |
+
import csv
|
13 |
import numpy as np
|
14 |
+
from torchcontrib.optim import SWA
|
15 |
|
16 |
|
17 |
+
rcParams["font.family"] = "Times New Roman"
|
|
|
|
|
|
|
|
|
18 |
|
19 |
+
SWA_START = 5 # Starting epoch for SWA
|
20 |
+
SWA_FREQ = 5 # Frequency of updating SWA weights
|
|
|
21 |
|
22 |
|
23 |
+
def rand_bbox(size, lam):
|
24 |
+
W = size[2]
|
25 |
+
H = size[3]
|
26 |
+
cut_rat = np.sqrt(1.0 - lam)
|
27 |
+
cut_w = np.int_(W * cut_rat)
|
28 |
+
cut_h = np.int_(H * cut_rat)
|
29 |
+
|
30 |
+
# uniform
|
31 |
+
cx = np.random.randint(W)
|
32 |
+
cy = np.random.randint(H)
|
33 |
|
34 |
+
bbx1 = np.clip(cx - cut_w // 2, 0, W)
|
35 |
+
bby1 = np.clip(cy - cut_h // 2, 0, H)
|
36 |
+
bbx2 = np.clip(cx + cut_w // 2, 0, W)
|
37 |
+
bby2 = np.clip(cy + cut_h // 2, 0, H)
|
38 |
|
39 |
+
return bbx1, bby1, bbx2, bby2
|
40 |
+
|
41 |
+
|
42 |
+
def cutmix_data(input, target, alpha=1.0):
|
43 |
if alpha > 0:
|
44 |
lam = np.random.beta(alpha, alpha)
|
45 |
else:
|
46 |
lam = 1
|
47 |
|
48 |
+
batch_size = input.size()[0]
|
49 |
index = torch.randperm(batch_size)
|
50 |
+
rand_index = torch.randperm(input.size()[0])
|
51 |
+
|
52 |
+
bbx1, bby1, bbx2, bby2 = rand_bbox(input.size(), lam)
|
53 |
+
input[:, :, bbx1:bbx2, bby1:bby2] = input[rand_index, :, bbx1:bbx2, bby1:bby2]
|
54 |
|
55 |
+
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (input.size()[-1] * input.size()[-2]))
|
56 |
+
targets_a = target
|
57 |
+
targets_b = target[rand_index]
|
58 |
|
59 |
+
return input, targets_a, targets_b, lam
|
60 |
|
61 |
+
|
62 |
+
def cutmix_criterion(criterion, outputs, targets_a, targets_b, lam):
|
63 |
+
return lam * criterion(outputs, targets_a) + (1 - lam) * criterion(
|
64 |
+
outputs, targets_b
|
65 |
+
)
|
66 |
+
|
67 |
+
|
68 |
+
def setup_tensorboard():
|
69 |
+
return SummaryWriter(log_dir="output/tensorboard/training")
|
70 |
|
71 |
|
72 |
def load_and_preprocess_data():
|
73 |
return data_loader.load_data(
|
74 |
+
COMBINED_DATA_DIR + "1",
|
|
|
|
|
75 |
preprocess,
|
76 |
)
|
77 |
|
|
|
99 |
inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)
|
100 |
optimizer.zero_grad()
|
101 |
|
102 |
+
# Apply CutMix
|
103 |
+
inputs, targets_a, targets_b, lam = cutmix_data(inputs, labels, alpha=1)
|
104 |
|
105 |
outputs = model(inputs)
|
106 |
|
107 |
+
# Calculate CutMix loss
|
108 |
+
loss = cutmix_criterion(criterion, outputs, targets_a, targets_b, lam)
|
109 |
|
110 |
loss.backward()
|
111 |
optimizer.step()
|
|
|
113 |
|
114 |
if (i + 1) % NUM_PRINT == 0:
|
115 |
print(
|
116 |
+
f"[Epoch {epoch + 1}, Batch {i + 1}/{len(train_loader)}] "
|
117 |
+
f"Loss: {running_loss / NUM_PRINT:.6f}"
|
118 |
)
|
119 |
running_loss = 0.0
|
120 |
|
|
|
154 |
best_val_loss = float("inf")
|
155 |
best_val_accuracy = 0.0
|
156 |
no_improvement_count = 0
|
157 |
+
epoch_metrics = []
|
158 |
|
159 |
AVG_TRAIN_LOSS_HIST = []
|
160 |
AVG_VAL_LOSS_HIST = []
|
161 |
TRAIN_ACC_HIST = []
|
162 |
VAL_ACC_HIST = []
|
163 |
|
164 |
+
# Initialize SWA optimizer
|
165 |
+
swa_optimizer = SWA(optimizer, swa_start=SWA_START, swa_freq=SWA_FREQ)
|
166 |
+
|
167 |
for epoch in range(NUM_EPOCHS):
|
168 |
+
print(f"\n[Epoch: {epoch + 1}/{NUM_EPOCHS}]")
|
169 |
print("Learning rate:", scheduler.get_last_lr()[0])
|
170 |
|
171 |
avg_train_loss, train_accuracy = train_one_epoch(
|
172 |
+
model, criterion, optimizer, train_loader, epoch, CUTMIX_ALPHA
|
173 |
)
|
174 |
AVG_TRAIN_LOSS_HIST.append(avg_train_loss)
|
175 |
TRAIN_ACC_HIST.append(train_accuracy)
|
|
|
180 |
"Accuracy": train_accuracy,
|
181 |
}
|
182 |
plot_and_log_metrics(train_metrics, epoch, writer=writer, prefix="Train")
|
183 |
+
epoch_metrics.append(
|
184 |
+
{
|
185 |
+
"Epoch": epoch + 1,
|
186 |
+
"Train Loss": avg_train_loss,
|
187 |
+
"Train Accuracy": train_accuracy,
|
188 |
+
"Validation Loss": avg_val_loss,
|
189 |
+
"Validation Accuracy": val_accuracy,
|
190 |
+
"Learning Rate": scheduler.get_last_lr()[0],
|
191 |
+
}
|
192 |
+
)
|
193 |
|
194 |
# Learning rate scheduling
|
195 |
+
|
196 |
+
if epoch < WARMUP_EPOCHS:
|
197 |
+
# Linear warm-up phase
|
198 |
+
lr = LEARNING_RATE * (epoch + 1) / WARMUP_EPOCHS
|
199 |
+
for param_group in optimizer.param_groups:
|
200 |
+
param_group["lr"] = lr
|
201 |
+
else:
|
202 |
+
# Cosine annealing scheduler after warm-up
|
203 |
+
scheduler.step()
|
204 |
|
205 |
avg_val_loss, val_accuracy = validate_model(model, criterion, valid_loader)
|
206 |
AVG_VAL_LOSS_HIST.append(avg_val_loss)
|
|
|
211 |
"Loss": avg_val_loss,
|
212 |
"Accuracy": val_accuracy,
|
213 |
}
|
214 |
+
plot_and_log_metrics(val_metrics, epoch, writer=writer, prefix="Validation")
|
215 |
|
216 |
# Print average training and validation metrics
|
217 |
print(f"Average Training Loss: {avg_train_loss:.6f}")
|
|
|
234 |
)
|
235 |
)
|
236 |
break
|
237 |
+
|
238 |
+
# Update SWA weights
|
239 |
+
if epoch >= SWA_START and epoch % SWA_FREQ == 0:
|
240 |
+
swa_optimizer.update_swa()
|
241 |
+
|
242 |
+
# Apply SWA to the final model weights
|
243 |
+
swa_optimizer.swap_swa_sgd()
|
244 |
+
csv_filename = "training_metrics.csv"
|
245 |
+
|
246 |
+
with open(csv_filename, mode="w", newline="") as csv_file:
|
247 |
+
fieldnames = [
|
248 |
+
"Epoch",
|
249 |
+
"Train Loss",
|
250 |
+
"Train Accuracy",
|
251 |
+
"Validation Loss",
|
252 |
+
"Validation Accuracy",
|
253 |
+
"Learning Rate",
|
254 |
+
]
|
255 |
+
|
256 |
+
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
|
257 |
+
writer.writeheader()
|
258 |
+
|
259 |
+
for metric in epoch_metrics:
|
260 |
+
writer.writerow(metric)
|
261 |
+
|
262 |
+
print(f"Metrics saved to {csv_filename}")
|
263 |
+
|
264 |
# Ensure the parent directory exists
|
265 |
os.makedirs(os.path.dirname(MODEL_SAVE_PATH), exist_ok=True)
|
266 |
torch.save(model.state_dict(), MODEL_SAVE_PATH)
|
267 |
+
print("\nModel saved at", MODEL_SAVE_PATH)
|
268 |
|
269 |
# Plot loss and accuracy curves
|
270 |
plt.figure(figsize=(12, 4))
|
tuning.py
CHANGED
@@ -8,59 +8,115 @@ import torch.utils.data
|
|
8 |
from configs import *
|
9 |
import data_loader
|
10 |
from torch.utils.tensorboard import SummaryWriter
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
13 |
EPOCHS = 10
|
14 |
-
N_TRIALS =
|
15 |
-
TIMEOUT =
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
# Create a TensorBoard writer
|
18 |
writer = SummaryWriter(log_dir="output/tensorboard/tuning")
|
19 |
|
20 |
|
|
|
21 |
def create_data_loaders(batch_size):
|
22 |
-
# Create or modify data loaders with the specified batch size
|
23 |
train_loader, valid_loader = data_loader.load_data(
|
24 |
-
|
25 |
-
AUG_DATA_DIR + str(TASK),
|
26 |
-
EXTERNAL_DATA_DIR + str(TASK),
|
27 |
preprocess,
|
28 |
batch_size=batch_size,
|
29 |
)
|
30 |
return train_loader, valid_loader
|
31 |
|
32 |
|
33 |
-
def
|
34 |
-
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
-
# Suggest batch size for tuning.
|
38 |
-
batch_size = trial.suggest_categorical("batch_size", [16, 32, 64])
|
39 |
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
train_loader, valid_loader = create_data_loaders(batch_size)
|
42 |
|
43 |
-
|
44 |
-
lr = trial.suggest_float("lr", 1e-5, 1e-1, log=True)
|
45 |
optimizer = optim.Adam(model.parameters(), lr=lr)
|
46 |
criterion = nn.CrossEntropyLoss()
|
47 |
|
48 |
-
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
|
51 |
-
|
52 |
-
|
53 |
|
54 |
-
# Training of the model.
|
55 |
for epoch in range(EPOCHS):
|
56 |
-
print(f"[Epoch: {epoch} | Trial: {trial.number}]")
|
57 |
model.train()
|
58 |
for batch_idx, (data, target) in enumerate(train_loader, 0):
|
59 |
data, target = data.to(DEVICE), target.to(DEVICE)
|
60 |
optimizer.zero_grad()
|
61 |
-
if
|
62 |
-
model.__class__.__name__ == "GoogLeNet"
|
63 |
-
): # the shit GoogLeNet has a different output
|
64 |
output = model(data).logits
|
65 |
else:
|
66 |
output = model(data)
|
@@ -68,21 +124,22 @@ def objective(trial, model=MODEL):
|
|
68 |
loss.backward()
|
69 |
optimizer.step()
|
70 |
|
71 |
-
# Update the learning rate using the scheduler.
|
72 |
scheduler.step()
|
73 |
|
74 |
-
# Validation of the model.
|
75 |
model.eval()
|
76 |
correct = 0
|
77 |
with torch.no_grad():
|
78 |
for batch_idx, (data, target) in enumerate(valid_loader, 0):
|
79 |
data, target = data.to(DEVICE), target.to(DEVICE)
|
|
|
80 |
output = model(data)
|
81 |
-
# Get the index of the max log-probability.
|
82 |
pred = output.argmax(dim=1, keepdim=True)
|
83 |
correct += pred.eq(target.view_as(pred)).sum().item()
|
84 |
|
85 |
accuracy = correct / len(valid_loader.dataset)
|
|
|
|
|
|
|
86 |
|
87 |
# Log hyperparameters and accuracy to TensorBoard
|
88 |
writer.add_scalar("Accuracy", accuracy, trial.number)
|
@@ -91,36 +148,56 @@ def objective(trial, model=MODEL):
|
|
91 |
{"accuracy": accuracy},
|
92 |
)
|
93 |
|
94 |
-
|
95 |
-
|
96 |
-
print("Accuracy: ", accuracy)
|
97 |
trial.report(accuracy, epoch)
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
103 |
-
if trial.number > 10 and trial.params["lr"] < 1e-3 and
|
104 |
-
return float("inf")
|
105 |
|
106 |
-
|
|
|
|
|
107 |
|
108 |
|
109 |
if __name__ == "__main__":
|
110 |
-
|
|
|
|
|
|
|
|
|
|
|
111 |
study = optuna.create_study(
|
112 |
-
direction="maximize",
|
113 |
-
pruner=
|
114 |
study_name="hyperparameter_tuning",
|
|
|
115 |
)
|
116 |
|
117 |
-
|
118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
|
120 |
-
# Print the best trial
|
121 |
best_trial = study.best_trial
|
122 |
-
print("
|
123 |
-
print("
|
124 |
-
print("
|
|
|
125 |
for key, value in best_trial.params.items():
|
126 |
-
print(" {}: {}"
|
|
|
8 |
from configs import *
|
9 |
import data_loader
|
10 |
from torch.utils.tensorboard import SummaryWriter
|
11 |
+
import time
|
12 |
+
import numpy as np
|
13 |
+
|
14 |
+
torch.cuda.empty_cache()
|
15 |
+
|
16 |
+
print(f"Using device: {DEVICE}")
|
17 |
|
|
|
18 |
EPOCHS = 10
|
19 |
+
# N_TRIALS = 10
|
20 |
+
# TIMEOUT = 5000
|
21 |
+
|
22 |
+
EARLY_STOPPING_PATIENCE = (
|
23 |
+
4 # Number of epochs with no improvement to trigger early stopping
|
24 |
+
)
|
25 |
+
|
26 |
|
27 |
# Create a TensorBoard writer
|
28 |
writer = SummaryWriter(log_dir="output/tensorboard/tuning")
|
29 |
|
30 |
|
31 |
+
# Function to create or modify data loaders with the specified batch size
|
32 |
def create_data_loaders(batch_size):
|
|
|
33 |
train_loader, valid_loader = data_loader.load_data(
|
34 |
+
COMBINED_DATA_DIR + "1",
|
|
|
|
|
35 |
preprocess,
|
36 |
batch_size=batch_size,
|
37 |
)
|
38 |
return train_loader, valid_loader
|
39 |
|
40 |
|
41 |
+
def rand_bbox(size, lam):
|
42 |
+
W = size[2]
|
43 |
+
H = size[3]
|
44 |
+
cut_rat = np.sqrt(1.0 - lam)
|
45 |
+
cut_w = np.int_(W * cut_rat)
|
46 |
+
cut_h = np.int_(H * cut_rat)
|
47 |
+
|
48 |
+
# uniform
|
49 |
+
cx = np.random.randint(W)
|
50 |
+
cy = np.random.randint(H)
|
51 |
+
|
52 |
+
bbx1 = np.clip(cx - cut_w // 2, 0, W)
|
53 |
+
bby1 = np.clip(cy - cut_h // 2, 0, H)
|
54 |
+
bbx2 = np.clip(cx + cut_w // 2, 0, W)
|
55 |
+
bby2 = np.clip(cy + cut_h // 2, 0, H)
|
56 |
+
|
57 |
+
return bbx1, bby1, bbx2, bby2
|
58 |
+
|
59 |
+
|
60 |
+
def cutmix_data(input, target, alpha=1.0):
|
61 |
+
if alpha > 0:
|
62 |
+
lam = np.random.beta(alpha, alpha)
|
63 |
+
else:
|
64 |
+
lam = 1
|
65 |
+
|
66 |
+
batch_size = input.size()[0]
|
67 |
+
index = torch.randperm(batch_size)
|
68 |
+
rand_index = torch.randperm(input.size()[0])
|
69 |
+
|
70 |
+
bbx1, bby1, bbx2, bby2 = rand_bbox(input.size(), lam)
|
71 |
+
input[:, :, bbx1:bbx2, bby1:bby2] = input[rand_index, :, bbx1:bbx2, bby1:bby2]
|
72 |
+
|
73 |
+
lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (input.size()[-1] * input.size()[-2]))
|
74 |
+
targets_a = target
|
75 |
+
targets_b = target[rand_index]
|
76 |
+
|
77 |
+
return input, targets_a, targets_b, lam
|
78 |
|
|
|
|
|
79 |
|
80 |
+
def cutmix_criterion(criterion, outputs, targets_a, targets_b, lam):
|
81 |
+
return lam * criterion(outputs, targets_a) + (1 - lam) * criterion(
|
82 |
+
outputs, targets_b
|
83 |
+
)
|
84 |
+
|
85 |
+
|
86 |
+
# Objective function for optimization
|
87 |
+
def objective(trial, model=MODEL):
|
88 |
+
model = model.to(DEVICE)
|
89 |
+
batch_size = trial.suggest_categorical("batch_size", [16, 32])
|
90 |
train_loader, valid_loader = create_data_loaders(batch_size)
|
91 |
|
92 |
+
lr = trial.suggest_float("lr", 1e-5, 1e-3, log=True)
|
|
|
93 |
optimizer = optim.Adam(model.parameters(), lr=lr)
|
94 |
criterion = nn.CrossEntropyLoss()
|
95 |
|
96 |
+
gamma = trial.suggest_float("gamma", 0.1, 0.9, step=0.1)
|
97 |
+
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)
|
98 |
+
|
99 |
+
past_trials = 0 # Number of trials already completed
|
100 |
+
|
101 |
+
# Print best hyperparameters:
|
102 |
+
if past_trials > 0:
|
103 |
+
print("\nBest Hyperparameters:")
|
104 |
+
print(f"{study.best_trial.params}")
|
105 |
+
|
106 |
+
print(f"\n[INFO] Trial: {trial.number}")
|
107 |
+
print(f"Batch Size: {batch_size}")
|
108 |
+
print(f"Learning Rate: {lr}")
|
109 |
+
print(f"Gamma: {gamma}\n")
|
110 |
|
111 |
+
early_stopping_counter = 0
|
112 |
+
best_accuracy = 0.0
|
113 |
|
|
|
114 |
for epoch in range(EPOCHS):
|
|
|
115 |
model.train()
|
116 |
for batch_idx, (data, target) in enumerate(train_loader, 0):
|
117 |
data, target = data.to(DEVICE), target.to(DEVICE)
|
118 |
optimizer.zero_grad()
|
119 |
+
if model.__class__.__name__ == "GoogLeNet":
|
|
|
|
|
120 |
output = model(data).logits
|
121 |
else:
|
122 |
output = model(data)
|
|
|
124 |
loss.backward()
|
125 |
optimizer.step()
|
126 |
|
|
|
127 |
scheduler.step()
|
128 |
|
|
|
129 |
model.eval()
|
130 |
correct = 0
|
131 |
with torch.no_grad():
|
132 |
for batch_idx, (data, target) in enumerate(valid_loader, 0):
|
133 |
data, target = data.to(DEVICE), target.to(DEVICE)
|
134 |
+
data, targets_a, targets_b, lam = cutmix_data(data, target, alpha=1)
|
135 |
output = model(data)
|
|
|
136 |
pred = output.argmax(dim=1, keepdim=True)
|
137 |
correct += pred.eq(target.view_as(pred)).sum().item()
|
138 |
|
139 |
accuracy = correct / len(valid_loader.dataset)
|
140 |
+
if accuracy >= 1.0:
|
141 |
+
print(f"Desired accuracy of 1.0 achieved. Stopping early.")
|
142 |
+
return float("inf")
|
143 |
|
144 |
# Log hyperparameters and accuracy to TensorBoard
|
145 |
writer.add_scalar("Accuracy", accuracy, trial.number)
|
|
|
148 |
{"accuracy": accuracy},
|
149 |
)
|
150 |
|
151 |
+
print(f"[EPOCH {epoch + 1}] Accuracy: {accuracy:.4f}")
|
152 |
+
|
|
|
153 |
trial.report(accuracy, epoch)
|
154 |
|
155 |
+
if accuracy > best_accuracy:
|
156 |
+
best_accuracy = accuracy
|
157 |
+
early_stopping_counter = 0
|
158 |
+
else:
|
159 |
+
early_stopping_counter += 1
|
160 |
+
|
161 |
+
# Early stopping check
|
162 |
+
if early_stopping_counter >= EARLY_STOPPING_PATIENCE:
|
163 |
+
print(f"\nEarly stopping at epoch {epoch + 1}")
|
164 |
+
break
|
165 |
|
166 |
+
if trial.number > 10 and trial.params["lr"] < 1e-3 and best_accuracy < 0.7:
|
167 |
+
return float("inf")
|
168 |
|
169 |
+
past_trials += 1
|
170 |
+
|
171 |
+
return best_accuracy
|
172 |
|
173 |
|
174 |
if __name__ == "__main__":
|
175 |
+
hyperband_pruner = optuna.pruners.HyperbandPruner()
|
176 |
+
|
177 |
+
# Record the start time
|
178 |
+
start_time = time.time()
|
179 |
+
|
180 |
+
# storage = optuna.storages.InMemoryStorage()
|
181 |
study = optuna.create_study(
|
182 |
+
direction="maximize",
|
183 |
+
pruner=hyperband_pruner,
|
184 |
study_name="hyperparameter_tuning",
|
185 |
+
storage="sqlite:///" + MODEL.__class__.__name__ + ".sqlite3",
|
186 |
)
|
187 |
|
188 |
+
study.optimize(objective)
|
189 |
+
|
190 |
+
# Record the end time
|
191 |
+
end_time = time.time()
|
192 |
+
|
193 |
+
# Calculate the duration of hyperparameter tuning
|
194 |
+
tuning_duration = end_time - start_time
|
195 |
+
print(f"Hyperparameter tuning took {tuning_duration:.2f} seconds.")
|
196 |
|
|
|
197 |
best_trial = study.best_trial
|
198 |
+
print("\nBest Trial:")
|
199 |
+
print(f" Trial Number: {best_trial.number}")
|
200 |
+
print(f" Best Accuracy: {best_trial.value:.4f}")
|
201 |
+
print(" Hyperparameters:")
|
202 |
for key, value in best_trial.params.items():
|
203 |
+
print(f" {key}: {value}")
|
weight_averaging.py
ADDED
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import torch
|
3 |
+
import torch.nn as nn
|
4 |
+
from PIL import Image
|
5 |
+
import os
|
6 |
+
from configs import *
|
7 |
+
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
|
8 |
+
import matplotlib.pyplot as plt
|
9 |
+
import random
|
10 |
+
from itertools import product
|
11 |
+
|
12 |
+
random.seed(RANDOM_SEED)
|
13 |
+
torch.cuda.manual_seed(RANDOM_SEED)
|
14 |
+
torch.manual_seed(RANDOM_SEED)
|
15 |
+
print("PyTorch Seed:", torch.initial_seed())
|
16 |
+
print("Random Seed:", random.getstate()[1][0])
|
17 |
+
print("PyTorch CUDA Seed:", torch.cuda.initial_seed())
|
18 |
+
|
19 |
+
print("DEVICE:", DEVICE)
|
20 |
+
|
21 |
+
# Define your model paths
|
22 |
+
# Load your pre-trained models
|
23 |
+
model2 = EfficientNetB3WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
24 |
+
model2.load_state_dict(torch.load("output/checkpoints/EfficientNetB3WithDropout.pth"))
|
25 |
+
model1 = SqueezeNet1_0WithSE(num_classes=NUM_CLASSES).to(DEVICE)
|
26 |
+
model1.load_state_dict(torch.load("output/checkpoints/SqueezeNet1_0WithSE.pth"))
|
27 |
+
model3 = MobileNetV2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
28 |
+
model3.load_state_dict(torch.load("output\checkpoints\MobileNetV2WithDropout.pth"))
|
29 |
+
model4 = EfficientNetB2WithDropout(num_classes=NUM_CLASSES).to(DEVICE)
|
30 |
+
model4.load_state_dict(torch.load("output\checkpoints\EfficientNetB2WithDropout.pth"))
|
31 |
+
|
32 |
+
models = [model1, model2, model3, model4]
|
33 |
+
|
34 |
+
# Define the class labels
|
35 |
+
class_labels = CLASSES
|
36 |
+
|
37 |
+
# Define your test data folder path
|
38 |
+
test_data_folder = "data/test/Task 1/"
|
39 |
+
|
40 |
+
|
41 |
+
# Put models in evaluation mode
|
42 |
+
def set_models_eval(models):
|
43 |
+
for model in models:
|
44 |
+
model.eval()
|
45 |
+
|
46 |
+
|
47 |
+
# Define the ensemble model using a list of models
|
48 |
+
class WeightedVoteEnsemble(nn.Module):
|
49 |
+
def __init__(self, models, weights):
|
50 |
+
super(WeightedVoteEnsemble, self).__init__()
|
51 |
+
self.models = models
|
52 |
+
self.weights = weights
|
53 |
+
|
54 |
+
def forward(self, x):
|
55 |
+
predictions = [model(x) for model in self.models]
|
56 |
+
weighted_predictions = torch.stack(
|
57 |
+
[w * pred for w, pred in zip(self.weights, predictions)], dim=0
|
58 |
+
)
|
59 |
+
avg_predictions = weighted_predictions.sum(dim=0)
|
60 |
+
return avg_predictions
|
61 |
+
|
62 |
+
|
63 |
+
def ensemble_predictions(models, image):
|
64 |
+
all_predictions = []
|
65 |
+
|
66 |
+
with torch.no_grad():
|
67 |
+
for model in models:
|
68 |
+
output = model(image)
|
69 |
+
all_predictions.append(output)
|
70 |
+
|
71 |
+
return torch.stack(all_predictions, dim=0).mean(dim=0)
|
72 |
+
|
73 |
+
|
74 |
+
# Load a single image and make predictions
|
75 |
+
def evaluate_image(models, image_path, transform=preprocess):
|
76 |
+
image = Image.open(image_path).convert("RGB")
|
77 |
+
image = transform(image).unsqueeze(0)
|
78 |
+
image = image.to(DEVICE)
|
79 |
+
outputs = ensemble_predictions(models, image)
|
80 |
+
|
81 |
+
return outputs.argmax(dim=1).item()
|
82 |
+
|
83 |
+
|
84 |
+
# Evaluate and plot a confusion matrix for an ensemble of models
|
85 |
+
def evaluate_and_plot_confusion_matrix(models, test_data_folder):
|
86 |
+
all_predictions = []
|
87 |
+
true_labels = []
|
88 |
+
|
89 |
+
with torch.no_grad():
|
90 |
+
for class_label in class_labels:
|
91 |
+
class_path = os.path.join(test_data_folder, class_label)
|
92 |
+
for image_file in os.listdir(class_path):
|
93 |
+
image_path = os.path.join(class_path, image_file)
|
94 |
+
# print(image_path)
|
95 |
+
predicted_label = evaluate_image(models, image_path, preprocess)
|
96 |
+
all_predictions.append(predicted_label)
|
97 |
+
true_labels.append(class_labels.index(class_label))
|
98 |
+
|
99 |
+
# Print accuracy
|
100 |
+
accuracy = (
|
101 |
+
(torch.tensor(all_predictions) == torch.tensor(true_labels)).float().mean()
|
102 |
+
)
|
103 |
+
print("Accuracy:", accuracy)
|
104 |
+
|
105 |
+
# Create the confusion matrix
|
106 |
+
# cm = confusion_matrix(true_labels, all_predictions)
|
107 |
+
|
108 |
+
# # Plot the confusion matrix
|
109 |
+
# display = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_labels)
|
110 |
+
# display.plot(cmap=plt.cm.Blues, values_format="d")
|
111 |
+
|
112 |
+
# # Show the plot
|
113 |
+
# plt.show()
|
114 |
+
|
115 |
+
return accuracy
|
116 |
+
|
117 |
+
# Set the models to evaluation mode
|
118 |
+
set_models_eval(models)
|
119 |
+
|
120 |
+
# Define different weight configurations
|
121 |
+
# [SqueezeNet, EfficientNetB2WithDropout, MobileNetV2WithDropout]
|
122 |
+
weights_configurations = [
|
123 |
+
# Random set of weights using random.random() and all weights sum to 1
|
124 |
+
[
|
125 |
+
random.randrange(1, 10) / 10,
|
126 |
+
random.randrange(1, 10) / 10,
|
127 |
+
random.randrange(1, 10) / 10,
|
128 |
+
],
|
129 |
+
]
|
130 |
+
|
131 |
+
|
132 |
+
## NOTE OF PREVIOUS WEIGHTS
|
133 |
+
# Best weights: [0.2, 0.3, 0.5] with accuracy: 0.9428571462631226 at iteration: 15 with torch seed: 28434738589300 and random seed: 3188652458777471118 and torch cuda seed: None
|
134 |
+
|
135 |
+
|
136 |
+
best_weights = {
|
137 |
+
"weights": 0,
|
138 |
+
"accuracy": 0,
|
139 |
+
"iteration": 0,
|
140 |
+
"torch_seed": 0,
|
141 |
+
"random_seed": 0,
|
142 |
+
"torch_cuda_seed": 0,
|
143 |
+
}
|
144 |
+
|
145 |
+
i = 0
|
146 |
+
|
147 |
+
# weights_hist = []
|
148 |
+
|
149 |
+
target_sum = 1.0
|
150 |
+
number_of_numbers = 4
|
151 |
+
lower_limit = 0.2
|
152 |
+
upper_limit = 0.8
|
153 |
+
step = 0.01
|
154 |
+
|
155 |
+
valid_combinations = []
|
156 |
+
|
157 |
+
# Generate all unique combinations of four numbers with values to two decimal places
|
158 |
+
for combination in product(
|
159 |
+
*[range(int(lower_limit * 100), int(upper_limit * 100) + 1)] * number_of_numbers
|
160 |
+
):
|
161 |
+
# Convert the combination to a list of floats
|
162 |
+
combination = [float(number) / 100 for number in combination]
|
163 |
+
# Check if the sum of the combination is equal to the target sum
|
164 |
+
if sum(combination) == target_sum:
|
165 |
+
# Add the combination to the list of valid combinations
|
166 |
+
valid_combinations.append(combination)
|
167 |
+
|
168 |
+
|
169 |
+
# Calculate the total number of possibilities
|
170 |
+
total_possibilities = len(valid_combinations)
|
171 |
+
|
172 |
+
print("Total number of possibilities:", total_possibilities)
|
173 |
+
|
174 |
+
# valid_combinations = [[0.3, 0.5, 0.2]]
|
175 |
+
# 0.38 for SqueezeNet, 0.34 for EfficientNetB2WithDropout, 0.28 for MobileNetV2WithDropout
|
176 |
+
best_weighted_vote_ensemble_model = None
|
177 |
+
|
178 |
+
for weights in valid_combinations:
|
179 |
+
# while True:
|
180 |
+
print("---------------------------")
|
181 |
+
print("Iteration:", i)
|
182 |
+
# Should iterate until all possible weights are exhausted
|
183 |
+
# Create an ensemble model with weighted voting
|
184 |
+
|
185 |
+
random.seed(RANDOM_SEED)
|
186 |
+
torch.cuda.manual_seed(RANDOM_SEED)
|
187 |
+
torch.manual_seed(RANDOM_SEED)
|
188 |
+
# print("PyTorch Seed:", torch.initial_seed())
|
189 |
+
# weights_hist.append(weights)
|
190 |
+
weighted_vote_ensemble_model = WeightedVoteEnsemble(
|
191 |
+
# [model1, model2, model3], weights
|
192 |
+
models,
|
193 |
+
weights,
|
194 |
+
)
|
195 |
+
# print("Weights:", weights)
|
196 |
+
print("Weights:", weights)
|
197 |
+
# Call the evaluate_and_plot_confusion_matrix function with your models and test data folder
|
198 |
+
accuracy = evaluate_and_plot_confusion_matrix(
|
199 |
+
[weighted_vote_ensemble_model], test_data_folder
|
200 |
+
)
|
201 |
+
# Convert tensor to float
|
202 |
+
accuracy = accuracy.item()
|
203 |
+
if accuracy > best_weights["accuracy"]:
|
204 |
+
# best_weights["weights"] = weights
|
205 |
+
best_weights["weights"] = weights
|
206 |
+
best_weights["accuracy"] = accuracy
|
207 |
+
best_weights["iteration"] = i
|
208 |
+
best_weights["torch_seed"] = torch.initial_seed()
|
209 |
+
seed = random.randrange(sys.maxsize)
|
210 |
+
rng = random.Random(seed)
|
211 |
+
best_weights["random_seed"] = seed
|
212 |
+
best_weights["torch_cuda_seed"] = torch.cuda.initial_seed()
|
213 |
+
best_weighted_vote_ensemble_model = weighted_vote_ensemble_model
|
214 |
+
|
215 |
+
print(
|
216 |
+
"Best weights:",
|
217 |
+
best_weights["weights"],
|
218 |
+
"with accuracy:",
|
219 |
+
best_weights["accuracy"],
|
220 |
+
"at iteration:",
|
221 |
+
best_weights["iteration"],
|
222 |
+
"with torch seed:",
|
223 |
+
best_weights["torch_seed"],
|
224 |
+
"and random seed:",
|
225 |
+
best_weights["random_seed"],
|
226 |
+
"and torch cuda seed:",
|
227 |
+
best_weights["torch_cuda_seed"],
|
228 |
+
)
|
229 |
+
i += 1
|
230 |
+
|
231 |
+
|
232 |
+
torch.save(
|
233 |
+
best_weighted_vote_ensemble_model.state_dict(),
|
234 |
+
"output/checkpoints/WeightedVoteEnsemble.pth",
|
235 |
+
)
|