cycool29 commited on
Commit
97dcf92
·
1 Parent(s): 643d383
Files changed (25) hide show
  1. app.py +5 -5
  2. augment.py +8 -2
  3. calculate.py +18 -0
  4. combine_dats.py → combine_data.py +22 -21
  5. configs.py +187 -30
  6. convert.py +1 -1
  7. data-splitting.py +23 -0
  8. data_loader.py +19 -11
  9. ensemble.py +249 -0
  10. eval.py +137 -55
  11. eval_orig.py +181 -0
  12. extract-ensemble.py +110 -0
  13. extract.py +50 -0
  14. genetric_algorithm.py +248 -0
  15. lazy_predict.py +60 -0
  16. models.py +28 -0
  17. plot-gradcam.py +30 -0
  18. predict.py +20 -7
  19. shap_eval.py +49 -0
  20. test.py +213 -29
  21. testing.py +5 -0
  22. train-svm.py +101 -0
  23. train.py +103 -33
  24. tuning.py +123 -46
  25. 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="Prediction 1"),
54
- gr.outputs.Textbox(label="Prediction 2"),
55
- gr.outputs.Textbox(label="Prediction 3"),
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
- # Generate 100 - total of original images so that the total number of images in each class is 100
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 + '1/' + disease):
11
  print("Copying raw data for disease: ", disease)
12
- if not os.path.exists(COMBINED_DATA_DIR + '1/' + disease):
13
- os.makedirs(COMBINED_DATA_DIR + '1/' + disease)
14
- for file in os.listdir(RAW_DATA_DIR + '1/' + disease):
15
  random_name = str(uuid.uuid4()) + ".png"
16
  shutil.copy(
17
- RAW_DATA_DIR + '1/' + disease + '/' + file,
18
- COMBINED_DATA_DIR + '1/' + disease + '/' + random_name,
19
  )
20
-
21
- if os.path.exists(EXTERNAL_DATA_DIR + '1/' + disease):
22
  print("Copying external data for disease: ", disease)
23
- if not os.path.exists(COMBINED_DATA_DIR + '1/' + disease):
24
- os.makedirs(COMBINED_DATA_DIR + '1/' + disease)
25
- for file in os.listdir(EXTERNAL_DATA_DIR + '1/' + disease):
26
  random_name = str(uuid.uuid4()) + ".png"
27
  shutil.copy(
28
- EXTERNAL_DATA_DIR + '1/' + disease + '/' + file,
29
- COMBINED_DATA_DIR + '1/' + disease + '/' + random_name,
30
  )
31
-
32
- if os.path.exists(AUG_DATA_DIR + '1/' + disease):
33
  print("Copying augmented data for disease: ", disease)
34
- if not os.path.exists(COMBINED_DATA_DIR + '1/' + disease):
35
- os.makedirs(COMBINED_DATA_DIR + '1/' + disease)
36
- for file in os.listdir(AUG_DATA_DIR + '1/' + disease):
37
  random_name = str(uuid.uuid4()) + ".png"
38
  shutil.copy(
39
- AUG_DATA_DIR + '1/' + disease + '/' + file,
40
- COMBINED_DATA_DIR + '1/' + disease + '/' + random_name,
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 = 16
24
- NUM_EPOCHS = 40
25
- LEARNING_RATE = 0.00016662575248025378
 
26
  STEP_SIZE = 10
27
- GAMMA = 0.9
28
- DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
 
 
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
- TEMP_DATA_DIR = "data/temp/"
 
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 SqueezeNet1_0WithDropout(nn.Module):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  def __init__(self, num_classes, dropout_prob=0.5):
54
- super(SqueezeNet1_0WithDropout, self).__init__()
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 SqueezeNet1_1WithDropout(nn.Module):
76
- def __init__(self, num_classes, dropout_prob=0.5):
77
- super(SqueezeNet1_1WithDropout, self).__init__()
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 ShuffleNetV2WithDropout(nn.Module):
99
- def __init__(self, num_classes, dropout_prob=0.5):
100
- super(ShuffleNetV2WithDropout, self).__init__()
101
- shufflenet = shufflenet_v2_x2_0(weights=ShuffleNet_V2_X2_0_Weights.DEFAULT)
102
- self.features = shufflenet.features
 
103
  self.classifier = nn.Sequential(
104
- nn.Conv2d(1024, num_classes, kernel_size=1),
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 MobileNetV3SmallWithDropout(nn.Module):
122
- def __init__(self, num_classes, dropout_prob=0.5):
123
- super(MobileNetV3SmallWithDropout, self).__init__()
124
- mobilenet = mobilenet_v3_small(weights=MobileNet_V3_Small_Weights.DEFAULT)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  self.features = mobilenet.features
126
  self.classifier = nn.Sequential(
127
- nn.Conv2d(576, num_classes, kernel_size=1),
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
- MODEL = SqueezeNet1_0WithDropout(num_classes=7)
145
- # MODEL = ptcv_get_model("sqnxt23v5_w2", pretrained=False, num_classes=7)
146
- print(CLASSES)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
 
 
 
148
  preprocess = transforms.Compose(
149
  [
150
- transforms.Resize((274, 274)), # Resize to 112x112
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=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
- def load_data(raw_dir, augmented_dir, external_dir, preprocess, batch_size=BATCH_SIZE):
7
- # Load the dataset using ImageFolder
8
- raw_dataset = ImageFolder(root=raw_dir, transform=preprocess)
9
- external_dataset = ImageFolder(root=external_dir, transform=preprocess)
10
- augmented_dataset = ImageFolder(root=augmented_dir, transform=preprocess)
11
- dataset = raw_dataset + external_dataset + augmented_dataset
12
 
13
  # Classes
14
- classes = augmented_dataset.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
- from data_loader import load_data # Import the load_data function
 
 
 
 
20
 
21
  # Constants
22
  DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
 
 
 
 
 
 
 
 
 
 
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.eval()
 
 
 
 
 
 
 
 
 
28
 
29
- def predict_image(image_path, model, transform):
30
- model.eval()
31
- correct_predictions = 0
32
 
33
- # Get a list of image files
34
- images = list(pathlib.Path(image_path).rglob("*.png"))
 
35
 
36
- total_predictions = len(images)
 
 
 
 
 
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  true_classes = []
39
- predicted_labels = []
40
- predicted_scores = [] # To store predicted class probabilities
 
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
- # Check if the prediction is correct
64
- if predicted_class == true_class:
65
- correct_predictions += 1
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- # Calculate accuracy and f1 score
68
- accuracy = accuracy_score(true_classes, predicted_labels)
69
- print("Accuracy:", accuracy)
70
- f1 = f1_score(true_classes, predicted_labels, average="weighted")
71
- print("Weighted F1 Score:", f1)
72
 
73
- # Convert the lists to tensors
74
- predicted_labels_tensor = torch.tensor(predicted_labels)
75
- true_classes_tensor = torch.tensor(true_classes)
76
 
77
- # Calculate the confusion matrix
78
- conf_matrix = confusion_matrix(true_classes, predicted_labels)
79
-
80
- # Plot the confusion matrix
81
- ConfusionMatrixDisplay(confusion_matrix=conf_matrix, display_labels=CLASSES).plot(cmap=plt.cm.Blues)
82
- plt.title("Confusion Matrix")
83
- plt.show()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
  # Classification report
86
  class_names = [str(cls) for cls in range(NUM_CLASSES)]
87
  report = classification_report(
88
- true_classes, predicted_labels, target_names=class_names
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  )
90
- print("Classification Report:\n", report)
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(predicted_scores).ravel()
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
- MODEL.load_state_dict(
14
- torch.load(MODEL_SAVE_PATH, map_location=DEVICE)
15
- ) # Load the model on the same device
16
- MODEL.eval()
17
- MODEL = MODEL.to(DEVICE)
18
- MODEL.eval()
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  torch.set_grad_enabled(False)
20
 
21
 
22
- def predict_image(image_path, model=MODEL, transform=preprocess):
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.optim as optim
3
- import torchvision.models as models
4
- import torchvision.transforms as transforms
5
  from configs import *
 
 
 
 
6
 
7
- # Load a pre-trained model (e.g., VGG16)
8
- MODEL = MODEL.to(DEVICE)
9
- MODEL.load_state_dict(torch.load(MODEL_SAVE_PATH, map_location=DEVICE))
10
- MODEL.eval()
 
 
11
 
12
- # Prepare an initial image (e.g., a random noise image)
13
- image = torch.randn(1, 3, 224, 224, requires_grad=True).cuda()
 
 
 
 
 
 
14
 
15
- # Define a loss function to maximize a specific layer or neuron's activation
16
- loss_function = torch.nn.CrossEntropyLoss()
17
 
18
- # Create an optimizer (e.g., Adam) for updating the image
19
- optimizer = optim.Adam([image], lr=0.01)
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
- # Visualize the optimized image
30
- import matplotlib.pyplot as plt
31
- import torchvision.transforms as transforms
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- # Convert the tensor to an image
34
- image = transforms.ToPILImage()(image.squeeze())
35
 
36
- # Display the generated image
37
- plt.imshow(image)
38
- plt.axis('off')
39
- plt.show()
 
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
- class LabelSmoothingLoss(nn.Module):
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
- def forward(self, input, target):
21
- target_smooth = (1 - self.epsilon) * target + self.epsilon / self.num_classes
22
- return nn.CrossEntropyLoss()(input, target_smooth)
23
 
24
 
25
- def setup_tensorboard():
26
- return SummaryWriter(log_dir="output/tensorboard/training")
 
 
 
 
 
 
 
 
27
 
 
 
 
 
28
 
29
- def mixup_data(x, y, alpha=1.0):
30
- """Returns mixed inputs, pairs of targets, and lambda"""
 
 
31
  if alpha > 0:
32
  lam = np.random.beta(alpha, alpha)
33
  else:
34
  lam = 1
35
 
36
- batch_size = x.size()[0]
37
  index = torch.randperm(batch_size)
 
 
 
 
38
 
39
- mixed_x = lam * x + (1 - lam) * x[index, :]
40
- y_a, y_b = y, y[index]
41
- return mixed_x, y_a, y_b, lam
42
 
 
43
 
44
- def mixup_criterion(criterion, pred, y_a, y_b, lam):
45
- return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)
 
 
 
 
 
 
 
46
 
47
 
48
  def load_and_preprocess_data():
49
  return data_loader.load_data(
50
- RAW_DATA_DIR + str(TASK),
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 mixup
81
- inputs, targets_a, targets_b, lam = mixup_data(inputs, labels, alpha)
82
 
83
  outputs = model(inputs)
84
 
85
- # Calculate mixup loss
86
- loss = mixup_criterion(criterion, outputs, targets_a, targets_b, lam)
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 %d, Batch %d] Loss: %.6f"
95
- % (epoch + 1, i + 1, running_loss / NUM_PRINT)
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, MIXUP_ALPHA
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
- scheduler.step()
 
 
 
 
 
 
 
 
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(train_metrics, epoch, writer=writer, prefix="Train")
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
- MODEL_SAVE_PATH = "output/checkpoints/model.pth"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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("Model saved at", MODEL_SAVE_PATH)
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 = 1000
15
- TIMEOUT = 14400
 
 
 
 
 
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
- RAW_DATA_DIR + str(TASK),
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 objective(trial, model=MODEL):
34
- # Generate the model.
35
- model = model.to(DEVICE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- # Suggest batch size for tuning.
38
- batch_size = trial.suggest_categorical("batch_size", [16, 32, 64])
39
 
40
- # Create data loaders with the suggested batch size.
 
 
 
 
 
 
 
 
 
41
  train_loader, valid_loader = create_data_loaders(batch_size)
42
 
43
- # Generate the optimizer.
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
- # Suggest the gamma parameter for the learning rate scheduler.
49
- gamma = trial.suggest_float("gamma", 0.1, 1.0, step=0.1)
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
- # Create a learning rate scheduler with the suggested gamma.
52
- scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=gamma)
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
- # Print hyperparameters and accuracy
95
- print("Hyperparameters: ", trial.params)
96
- print("Accuracy: ", accuracy)
97
  trial.report(accuracy, epoch)
98
 
99
- # Handle pruning based on the intermediate value.
100
- if trial.should_prune():
101
- raise optuna.exceptions.TrialPruned()
 
 
 
 
 
 
 
102
 
103
- if trial.number > 10 and trial.params["lr"] < 1e-3 and accuracy < 0.7:
104
- return float("inf") # Prune the trial
105
 
106
- return accuracy
 
 
107
 
108
 
109
  if __name__ == "__main__":
110
- pruner = optuna.pruners.HyperbandPruner()
 
 
 
 
 
111
  study = optuna.create_study(
112
- direction="maximize", # Adjust the direction as per your optimization goal
113
- pruner=pruner,
114
  study_name="hyperparameter_tuning",
 
115
  )
116
 
117
- # Optimize the hyperparameters
118
- study.optimize(objective, n_trials=N_TRIALS, timeout=TIMEOUT)
 
 
 
 
 
 
119
 
120
- # Print the best trial
121
  best_trial = study.best_trial
122
- print("Best trial:")
123
- print(" Value: ", best_trial.value)
124
- print(" Params: ")
 
125
  for key, value in best_trial.params.items():
126
- print(" {}: {}".format(key, value))
 
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
+ )