rimasalshehri commited on
Commit
91fb813
1 Parent(s): b51d1fa

Creatapp.py

Browse files
Files changed (1) hide show
  1. app.py +1446 -0
app.py ADDED
@@ -0,0 +1,1446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """SkinToneClassification.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1l-efXmdbhvkKnkvTtvWlzNqunkONzr7i
8
+
9
+ # 1. Setup
10
+
11
+ ## 1.1 Installing Necessary Libraries
12
+ """
13
+
14
+ !pip install git+https://github.com/FacePerceiver/facer.git@main
15
+ !pip install timm
16
+
17
+ !git clone https://github.com/FacePerceiver/facer.git
18
+
19
+ pip install tensorflow-addons
20
+
21
+ pip install keras-tuner
22
+
23
+ pip install git+https://github.com/qubvel/classification_models.git
24
+
25
+ !pip install joblib
26
+
27
+ """## 1.2 Importing Libraries"""
28
+
29
+ from google.colab import drive
30
+ import os
31
+ from PIL import Image, ImageEnhance, ImageFilter
32
+ import shutil
33
+ import numpy as np
34
+ import matplotlib.pyplot as plt
35
+ import torch
36
+ from torch.utils.data import Dataset, DataLoader
37
+ from torchvision import transforms, utils
38
+ import facer
39
+ from torchvision.transforms.functional import to_pil_image, to_tensor
40
+ from torch import nn, optim
41
+ from torchvision import models
42
+ import torch.nn.functional as F
43
+ from sklearn.svm import SVC
44
+ from sklearn.metrics import accuracy_score
45
+ from sklearn.preprocessing import LabelEncoder
46
+ from sklearn.model_selection import GridSearchCV
47
+ import torch.nn as nn
48
+ import torch.optim as optim
49
+ import torch.nn.functional as F
50
+ from torch.optim.lr_scheduler import StepLR
51
+ import cupy as cp
52
+ from sklearn.metrics import classification_report, accuracy_score
53
+ from sklearn.metrics import f1_score
54
+ from torchsummary import summary
55
+ import seaborn as sns
56
+ from torch.optim.lr_scheduler import ReduceLROnPlateau
57
+ from sklearn.metrics import confusion_matrix, classification_report, accuracy_score
58
+ import cv2
59
+ from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
60
+ from torch.optim import Adam
61
+ from collections import defaultdict
62
+ import random
63
+ from sklearn.model_selection import train_test_split
64
+ import itertools
65
+ import tensorflow as tf
66
+ from tensorflow.keras.layers import Layer, Conv2D, BatchNormalization, ReLU, MaxPooling2D, GlobalAveragePooling2D, Dense, Dropout
67
+ from tensorflow.keras import Sequential
68
+ from tensorflow.keras.optimizers import Adam
69
+ from tensorflow.keras.callbacks import EarlyStopping
70
+ from tensorflow.keras.metrics import Precision, Recall
71
+ import tensorflow_addons as tfa
72
+ from tensorflow.keras.utils import to_categorical
73
+ from kerastuner import RandomSearch, Hyperband, Objective
74
+ from tensorflow.keras.callbacks import ReduceLROnPlateau
75
+ from tensorflow.keras.applications import ResNet50
76
+ from tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Input
77
+ from tensorflow.keras.models import Model
78
+ from keras_tuner.tuners import RandomSearch
79
+ from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score as f1_metric
80
+ import keras_tuner as kt
81
+ from tensorflow.keras.applications import VGG16
82
+ from keras_tuner import HyperParameters
83
+ from tensorflow.keras.regularizers import l2
84
+ from classification_models.tfkeras import Classifiers
85
+ from joblib import dump, load
86
+
87
+ """# 2. Data Loading
88
+
89
+ ## 2.1 Load each dataset
90
+ """
91
+
92
+ drive.mount('/content/drive')
93
+
94
+ SkinTone_Dataset_Path = '/content/drive/MyDrive/Senior Project/Dataset/Skin Tone Dataset' # SkinTone Dataset
95
+
96
+ """# 3. Data Preprocessing
97
+
98
+ ## 3.1 EDA, cleaning, and splitting.
99
+ """
100
+
101
+ def convert_to_jpg(directory):
102
+ # Walk through all files and subdirectories in the directory
103
+ for subdir, dirs, files in os.walk(directory):
104
+ for file in files:
105
+ filepath = os.path.join(subdir, file)
106
+ if not filepath.lower().endswith('.jpg'):
107
+ try:
108
+ img = Image.open(filepath)
109
+ # Define the new filename with .jpg extension
110
+ new_filepath = os.path.splitext(filepath)[0] + '.jpg'
111
+ # Convert and save the image under the new file name
112
+ img.convert('RGB').save(new_filepath, 'JPEG')
113
+ # Remove the original file and keep the .jpg version
114
+ os.remove(filepath)
115
+ print(f"Converted and saved {new_filepath}")
116
+ except Exception as e:
117
+ print(f"Failed to convert {filepath}: {e}")
118
+
119
+ #convert_to_jpg(SkinTone_Dataset_Path)
120
+
121
+ def rename_images(dataset_path):
122
+ img_index = 1 # Initialize the counter outside the loop to continue incrementing
123
+ for subdir, dirs, files in os.walk(dataset_path):
124
+ class_name = os.path.basename(subdir) # Get the class name from the directory name
125
+ if class_name:
126
+ print(f"Processing {subdir}...")
127
+ for file in files:
128
+ filepath = os.path.join(subdir, file)
129
+ if filepath.lower().endswith('.jpg'):
130
+ new_filename = f"{img_index}_{class_name}.jpg"
131
+ new_filepath = os.path.join(subdir, new_filename)
132
+ try:
133
+ os.rename(filepath, new_filepath) # Rename the file
134
+ print(f"Renamed {filepath} to {new_filepath}")
135
+ img_index += 1 # Increment the index for each file processed
136
+ except Exception as e:
137
+ print(f"Failed to rename {filepath}: {e}")
138
+ else:
139
+ print(f"Skipped {filepath} (not a .jpg)")
140
+
141
+ #rename_images(SkinTone_Dataset_Path)
142
+
143
+ def collect_images(base_path):
144
+ all_images = []
145
+ for root, dirs, files in os.walk(base_path):
146
+ for file in files:
147
+ if file.lower().endswith(('.png', '.jpg', '.jpeg')):
148
+ all_images.append(os.path.join(root, file))
149
+ return all_images
150
+
151
+ class SkinToneDataset(Dataset):
152
+ def __init__(self, image_paths, labels, transform=None):
153
+ self.image_paths = image_paths
154
+ self.labels = labels
155
+ self.transform = transform
156
+ self.class_to_index = {class_name: index for index, class_name in enumerate(sorted(set(labels)))}
157
+
158
+ def __getitem__(self, idx):
159
+ image_path = self.image_paths[idx]
160
+ image = Image.open(image_path).convert('RGB')
161
+ if self.transform:
162
+ image = self.transform(image)
163
+
164
+ label = self.class_to_index[self.labels[idx]] # Convert class name to integer
165
+ return image, label
166
+
167
+ def __len__(self):
168
+ return len(self.image_paths)
169
+
170
+ def get_train_transforms():
171
+ """Define and return a series of transformations for the training set."""
172
+ return transforms.Compose([
173
+ transforms.Resize((256, 256)), # Resize all images to the same size
174
+ transforms.ToTensor(), # Convert images to tensor
175
+ ])
176
+
177
+ def get_val_test_transforms():
178
+ """Define and return a series of transformations for the validation and test set."""
179
+ return transforms.Compose([
180
+ transforms.Resize((256, 256)), # resize to ensure consistency
181
+ transforms.ToTensor(), # Convert to tensor for model compatibility
182
+ ])
183
+
184
+ def get_label_from_filename(image_path):
185
+ # Split the filename and extract the class part
186
+ filename = os.path.basename(image_path)
187
+ label = filename.split('_')[1].split('.')[0]
188
+ return label
189
+
190
+ def plot_class_distribution(base_path, start_class=1, end_class=9):
191
+ class_counts = {}
192
+ classes = sorted(os.listdir(base_path))[start_class-1:end_class]
193
+
194
+ for class_name in classes:
195
+ class_dir = os.path.join(base_path, class_name)
196
+ class_counts[class_name] = len(os.listdir(class_dir))
197
+
198
+ plt.figure(figsize=(10, 8))
199
+ plt.bar(class_counts.keys(), class_counts.values(), color='skyblue')
200
+ plt.xlabel('Classes')
201
+ plt.ylabel('Number of Images')
202
+ plt.title('Distribution of Classes ')
203
+ plt.xticks(rotation=45)
204
+ plt.show()
205
+
206
+ plot_class_distribution(SkinTone_Dataset_Path)
207
+
208
+ def display_sample_images(base_path, start_class=1, end_class=9):
209
+ classes = sorted(os.listdir(base_path)) # Sort and list all classes
210
+ selected_classes = classes[start_class-1:end_class] # Select classes from 1 to 9
211
+ num_classes = len(selected_classes)
212
+ fig, axes = plt.subplots(nrows=1, ncols=num_classes, figsize=(num_classes * 2, 4))
213
+ fig.suptitle('One Sample Image from Each Class', fontsize=16)
214
+
215
+ for i, class_name in enumerate(selected_classes):
216
+ class_dir = os.path.join(base_path, class_name)
217
+ sample_image = np.random.choice(os.listdir(class_dir), 1)[0] # Randomly pick one sample image
218
+ img_path = os.path.join(class_dir, sample_image)
219
+ img = Image.open(img_path).convert('RGB')
220
+ ax = axes[i]
221
+ ax.imshow(img)
222
+ ax.axis('off')
223
+ ax.set_title(class_name)
224
+
225
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
226
+ plt.show()
227
+
228
+ display_sample_images(SkinTone_Dataset_Path)
229
+
230
+ processed_images_dir = '/content/drive/MyDrive/Senior Project/Dataset/processed_images'
231
+ os.makedirs(processed_images_dir, exist_ok=True)
232
+
233
+ """### Step 1: Set up the device and initialize face detection and parsing
234
+
235
+ ```
236
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
237
+ face_detector = facer.face_detector('retinaface/mobilenet', device=device)
238
+ face_parser = facer.face_parser('farl/lapa/448', device=device)
239
+ ```
240
+
241
+
242
+
243
+ ### Step 2: Collect all image paths
244
+
245
+ ```
246
+ all_images = collect_images(SkinTone_Dataset_Path)
247
+ ```
248
+
249
+
250
+ ### Step 3: Process images to detect and parse faces
251
+
252
+ ```
253
+ processed_images = []
254
+ dataset_path = SkinTone_Dataset_Path
255
+
256
+ for image_path in all_images:
257
+ image_name = os.path.basename(image_path)
258
+ image_data = facer.read_hwc(image_path) # Check what type of data this function returns
259
+
260
+ if image_data is None:
261
+ print(f"Could not read image {image_name}")
262
+ continue
263
+
264
+ # Check if the data is a tensor and adjust dimensions for PyTorch if needed
265
+ if torch.is_tensor(image_data):
266
+ # Assuming image_data is already in CHW format but check your facer.read_hwc() function documentation
267
+ if image_data.shape[0] != 3: # Expecting C, H, W format, C should be 3 for RGB
268
+ image_tensor = image_data.permute(2, 0, 1) # Convert from HWC to CHW if necessary
269
+ else:
270
+ image_tensor = image_data
271
+ image_tensor = image_tensor.unsqueeze(0).to(device) # Add batch dimension and move to device
272
+ elif isinstance(image_data, np.ndarray):
273
+ # If it's a numpy array, convert to tensor
274
+ image_tensor = torch.from_numpy(image_data.astype('float32')).permute(2, 0, 1).unsqueeze(0).to(device)
275
+ else:
276
+ print(f"Unknown data type for image {image_name}: {type(image_data)}")
277
+ continue
278
+
279
+ with torch.inference_mode():
280
+ try:
281
+ faces = face_detector(image_tensor) # Pass the correctly shaped tensor to the face detector
282
+
283
+ if faces:
284
+ parsed_faces = face_parser(image_tensor, faces)
285
+
286
+ if 'seg' in parsed_faces:
287
+ seg_logits = parsed_faces['seg']['logits']
288
+ seg_probs = torch.sigmoid(seg_logits)
289
+ binary_mask = seg_probs[0, 1, :, :] > 0.5 # Accessing first image, second channel
290
+ binary_mask = binary_mask.cpu().numpy()
291
+
292
+ # Create a 3-channel binary mask for the RGB image
293
+ binary_mask_3d = np.repeat(binary_mask[:, :, np.newaxis], 3, axis=2)
294
+
295
+ # Apply the mask to the original image to extract the skin region
296
+ skin_region = image_data.cpu().numpy() * binary_mask_3d # Convert tensor to numpy if needed
297
+
298
+ # Convert the result to uint8 format for saving as an image
299
+ skin_region_uint8 = skin_region.astype(np.uint8)
300
+
301
+ # Save the processed skin region image to disk
302
+ processed_image_path = os.path.join(processed_images_dir, image_name)
303
+ Image.fromarray(skin_region_uint8).save(processed_image_path)
304
+
305
+ # Add the path of the saved image to processed_images
306
+ processed_images.append(processed_image_path)
307
+ except RuntimeError as e:
308
+ print(f"Error processing {image_name}: {str(e)}")
309
+ ```
310
+ """
311
+
312
+ def stratified_split_dataset(all_images, train_size=0.8, val_size=0.1, test_size=0.1):
313
+ """Split the dataset into training, validation, and testing sets in a stratified manner."""
314
+ label_to_images = {}
315
+ for image in all_images:
316
+ label = get_label_from_filename(image)
317
+ if label in label_to_images:
318
+ label_to_images[label].append(image)
319
+ else:
320
+ label_to_images[label] = [image]
321
+
322
+ train_images = []
323
+ val_images = []
324
+ test_images = []
325
+ train_labels = []
326
+ val_labels = []
327
+ test_labels = []
328
+
329
+ for label, images in label_to_images.items():
330
+ np.random.shuffle(images)
331
+ total_images = len(images)
332
+ train_end = int(train_size * total_images)
333
+ val_end = train_end + int(val_size * total_images)
334
+
335
+ train_images.extend(images[:train_end])
336
+ val_images.extend(images[train_end:val_end])
337
+ test_images.extend(images[val_end:])
338
+
339
+ # Append corresponding labels
340
+ train_labels.extend([label] * len(images[:train_end]))
341
+ val_labels.extend([label] * len(images[train_end:val_end]))
342
+ test_labels.extend([label] * len(images[val_end:]))
343
+
344
+ return (train_images, train_labels), (val_images, val_labels), (test_images, test_labels)
345
+
346
+ # Step 4: Split the processed images
347
+ processed_images = collect_images(processed_images_dir)
348
+ (train_images, train_labels), (val_images, val_labels), (test_images, test_labels) = stratified_split_dataset(processed_images)
349
+
350
+
351
+ # Step 5: Define transformations for datasets
352
+ train_transforms = get_train_transforms()
353
+ val_test_transforms = get_val_test_transforms()
354
+
355
+ # Step 6: Create datasets with the face detector and parser
356
+ train_dataset = SkinToneDataset(train_images, train_labels, transform=train_transforms)
357
+ val_dataset = SkinToneDataset(val_images, val_labels, transform=val_test_transforms)
358
+ test_dataset = SkinToneDataset(test_images, test_labels, transform=val_test_transforms)
359
+
360
+ # Step 7: Create DataLoaders
361
+ train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
362
+ val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
363
+ test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
364
+
365
+ print(f"Total processed images: {len(processed_images)}")
366
+ print(f"Training images: {len(train_images)}")
367
+ print(f"Validation images: {len(val_images)}")
368
+ print(f"Testing images: {len(test_images)}")
369
+
370
+ def count_labels(image_paths):
371
+ """Count occurrences of each label in a list of image paths."""
372
+ label_count = {}
373
+ for path in image_paths:
374
+ label = get_label_from_filename(path)
375
+ if label in label_count:
376
+ label_count[label] += 1
377
+ else:
378
+ label_count[label] = 1
379
+ return label_count
380
+
381
+ train_counts = count_labels(train_images)
382
+ val_counts = count_labels(val_images)
383
+ test_counts = count_labels(test_images)
384
+
385
+ print("Training set counts:")
386
+ for label, count in train_counts.items():
387
+ print(f"Label: {label}, Count: {count}")
388
+
389
+ print("\nValidation set counts:")
390
+ for label, count in val_counts.items():
391
+ print(f"Label: {label}, Count: {count}")
392
+
393
+ print("\nTest set counts:")
394
+ for label, count in test_counts.items():
395
+ print(f"Label: {label}, Count: {count}")
396
+
397
+ def plot_images_from_loader(loader, num_images):
398
+ dataiter = iter(loader)
399
+ images, labels = next(dataiter)
400
+
401
+ figure_width = num_images * 2
402
+ figure_height = 3
403
+ fig = plt.figure(figsize=(figure_width, figure_height))
404
+
405
+ for i in range(num_images):
406
+ if i >= images.size(0):
407
+ break
408
+
409
+ left = i / num_images
410
+ bottom = 0.1
411
+ width = 1 / num_images
412
+ height = 0.8
413
+ ax = fig.add_axes([left, bottom, width, height])
414
+
415
+ img = to_pil_image(images[i])
416
+ ax.imshow(img)
417
+ ax.set_title(f'Image {i}, Label: {labels[i]}')
418
+ ax.axis('off')
419
+
420
+ plt.show()
421
+
422
+ plot_images_from_loader(train_loader, num_images=10)
423
+
424
+ """####Here is after reducing the number of classes from 9 to 4"""
425
+
426
+ def reorganize_dataset(source_dir, target_dir, class_mapping):
427
+ if not os.path.exists(target_dir):
428
+ os.makedirs(target_dir)
429
+
430
+ # a dictionary to hold the image paths for each new class
431
+ new_class_images = defaultdict(list)
432
+
433
+ # Iterate over all files in the source directory
434
+ for filename in os.listdir(source_dir):
435
+ if filename.endswith(('.png', '.jpg', '.jpeg')):
436
+ # Extract class from filename, stripping non-numeric characters
437
+ class_label = ''.join(filter(str.isdigit, filename.split('_')[1]))
438
+ # Determine new class based on mapping
439
+ for new_class, old_classes in class_mapping.items():
440
+ if int(class_label) in old_classes:
441
+ new_class_images[new_class].append(os.path.join(source_dir, filename))
442
+ break
443
+
444
+ # For each new class, copy images until we reach the desired number
445
+ for new_class, images in new_class_images.items():
446
+ class_dir = os.path.join(target_dir, str(new_class))
447
+ if not os.path.exists(class_dir):
448
+ os.makedirs(class_dir)
449
+
450
+ # Randomize the image list and copy the first 1000
451
+ random.shuffle(images)
452
+ for i in range(min(1000, len(images))):
453
+ shutil.copy2(images[i], class_dir)
454
+
455
+ # Mapping of original classes to new classes
456
+ class_mapping = {
457
+ '1': [2, 3, 4],
458
+ '2': [5, 6],
459
+ '3': [7, 8],
460
+ '4': [9, 10],
461
+ }
462
+
463
+ source_dataset_folder = processed_images_dir
464
+ target_dataset_folder = '/content/drive/MyDrive/Senior Project/Dataset/reorganized_dataset'
465
+ os.makedirs(target_dataset_folder, exist_ok=True)
466
+
467
+ #reorganize_dataset(source_dataset_folder, target_dataset_folder, class_mapping)
468
+
469
+ def rename_images_in_folders(target_dataset_folder):
470
+
471
+ for class_folder in os.listdir(target_dataset_folder):
472
+ class_folder_path = os.path.join(target_dataset_folder, class_folder)
473
+ if os.path.isdir(class_folder_path):
474
+ # New class is determined by the folder name
475
+ new_class_label = class_folder
476
+
477
+ # Rename each image in the class folder
478
+ for filename in os.listdir(class_folder_path):
479
+ if filename.endswith(('.png', '.jpg', '.jpeg')):
480
+
481
+ parts = filename.split('_')
482
+ # Check if there are sufficient parts to rename
483
+ if len(parts) == 2:
484
+ prefix = parts[0]
485
+ suffix = parts[1]
486
+ # Split the second part to isolate the extension
487
+ class_and_extension = suffix.split('.')
488
+ if len(class_and_extension) == 2:
489
+ extension = class_and_extension[1]
490
+ # Construct new filename using new class label and extension
491
+ new_filename = f"{prefix}_{new_class_label}.{extension}"
492
+ old_path = os.path.join(class_folder_path, filename)
493
+ new_path = os.path.join(class_folder_path, new_filename)
494
+ print(f"Renaming {old_path} to {new_path}") # Debugging output
495
+ os.rename(old_path, new_path)
496
+ else:
497
+ print(f"Error parsing extension from {filename}")
498
+ else:
499
+ print(f"Unexpected filename structure: {filename}")
500
+ else:
501
+ print(f"Skipping file due to incorrect format: {filename}")
502
+
503
+
504
+ #rename_images_in_folders(target_dataset_folder)
505
+
506
+ def stratified_split_dataset_after_red_classes(root_dir, train_size=0.8, val_size=0.1, test_size=0.1):
507
+ """
508
+ Split the dataset into training, validation, and testing sets in a stratified manner
509
+ after the classes have been reduced and organized into folders.
510
+ """
511
+ label_to_images = {}
512
+ train_images, val_images, test_images = [], [], []
513
+ train_labels, val_labels, test_labels = [], [], []
514
+
515
+ # Collect all image paths and their labels
516
+ for label in os.listdir(root_dir):
517
+ label_path = os.path.join(root_dir, label)
518
+ if os.path.isdir(label_path): # to make sure it's a directory
519
+ images = [os.path.join(label_path, img) for img in os.listdir(label_path)
520
+ if img.endswith(('.png', '.jpg', '.jpeg'))]
521
+ label_to_images[label] = images
522
+
523
+ # Split the images for each label
524
+ for label, images in label_to_images.items():
525
+
526
+ X_train, X_val_test = train_test_split(images, train_size=train_size, stratify=None, random_state=42)
527
+ X_val, X_test = train_test_split(X_val_test, train_size=val_size / (val_size + test_size), stratify=None, random_state=42)
528
+
529
+ train_images.extend(X_train)
530
+ val_images.extend(X_val)
531
+ test_images.extend(X_test)
532
+
533
+ # Append corresponding labels
534
+ train_labels.extend([label] * len(X_train))
535
+ val_labels.extend([label] * len(X_val))
536
+ test_labels.extend([label] * len(X_test))
537
+
538
+ return (train_images, train_labels), (val_images, val_labels), (test_images, test_labels)
539
+
540
+ # Split the processed images
541
+ target_dataset_folder = '/content/drive/MyDrive/Senior Project/Dataset/reorganized_dataset'
542
+ dir_AfterRedClasses = target_dataset_folder
543
+ (train_images2, train_labels2), (val_images2, val_labels2), (test_images2, test_labels2) = stratified_split_dataset_after_red_classes(dir_AfterRedClasses)
544
+
545
+
546
+ # Define transformations for datasets
547
+ train_transforms = get_train_transforms()
548
+ val_test_transforms = get_val_test_transforms()
549
+
550
+ # Create datasets with the face detector and parser
551
+
552
+ train_dataset2 = SkinToneDataset(train_images2, train_labels2, transform=train_transforms)
553
+ val_dataset2 = SkinToneDataset(val_images2, val_labels2, transform=val_test_transforms)
554
+ test_dataset2 = SkinToneDataset(test_images2, test_labels2, transform=val_test_transforms)
555
+
556
+ # Create DataLoaders
557
+ train_loader2 = DataLoader(train_dataset2, batch_size=32, shuffle=True)
558
+ val_loader2 = DataLoader(val_dataset2, batch_size=32, shuffle=False)
559
+ test_loader2 = DataLoader(test_dataset2, batch_size=32, shuffle=False)
560
+
561
+ print(f"Total processed images: {len(train_images2)+len(val_images2)+len(test_images2)}")
562
+ print(f"Training images: {len(train_images2)}")
563
+ print(f"Validation images: {len(val_images2)}")
564
+ print(f"Testing images: {len(test_images2)}")
565
+
566
+ def count_labels2(image_paths):
567
+ """Count occurrences of each label in a list of image paths."""
568
+ label_count = defaultdict(int)
569
+ for path in image_paths:
570
+ # Extract the class label from the filename
571
+ filename = os.path.basename(path)
572
+ label = filename.split('_')[1]
573
+ label_count[label] += 1
574
+ return label_count
575
+
576
+ train_counts2 = count_labels2(train_images2)
577
+ val_counts = count_labels2(val_images2)
578
+ test_counts = count_labels2(test_images2)
579
+
580
+ print("Training set counts:")
581
+ for label, count in train_counts2.items():
582
+ print(f"Label: {label}, Count: {count}")
583
+
584
+ print("\nValidation set counts:")
585
+ for label, count in val_counts.items():
586
+ print(f"Label: {label}, Count: {count}")
587
+
588
+ print("\nTest set counts:")
589
+ for label, count in test_counts.items():
590
+ print(f"Label: {label}, Count: {count}")
591
+
592
+ plot_images_from_loader(train_loader2, num_images=10)
593
+
594
+ def load_images_and_labels(image_paths, labels, target_size):
595
+ images = []
596
+ label_indices = []
597
+ unique_labels = sorted(set(labels))
598
+ num_classes = len(unique_labels)
599
+ label_to_index = {label: idx for idx, label in enumerate(unique_labels)}
600
+
601
+ for image_path, label in zip(image_paths, labels):
602
+ img = Image.open(image_path).convert('RGB')
603
+ img = img.resize(target_size)
604
+ images.append(np.array(img))
605
+ label_indices.append(label_to_index[label]) # store the label index in a separate list
606
+
607
+ images = np.array(images, dtype='float32') / 255.0
608
+ label_indices = np.array(label_indices, dtype='int32')
609
+ labels = to_categorical(label_indices, num_classes=num_classes)
610
+ return images, labels
611
+
612
+ """# 4. Model Training and Evaluation
613
+
614
+ ## 4.1 Define model architecture, train on datasets.
615
+
616
+ ### 4.1.1 First model (CNN: ResNet architecture "Transfer Learning")
617
+ """
618
+
619
+ (train_images2, train_labels2), (val_images2, val_labels2), (test_images2, test_labels2) = stratified_split_dataset_after_red_classes(dir_AfterRedClasses)
620
+ X_train, Y_train = load_images_and_labels(train_images2, train_labels2, target_size=(128, 128))
621
+ X_val, Y_val = load_images_and_labels(val_images2, val_labels2, target_size=(128, 128))
622
+
623
+ """####ResNet50
624
+
625
+ #####hyperparameter tuning
626
+ """
627
+
628
+ def build_model(hp):
629
+ base_model = ResNet50(include_top=False, input_shape=(128, 128, 3))
630
+ x = GlobalAveragePooling2D()(base_model.output)
631
+ # Apply L2 regularization to the new Dense layer
632
+ x = Dense(hp.Int('units', min_value=32, max_value=512, step=32), activation='relu', kernel_regularizer=l2(0.01))(x)
633
+ predictions = Dense(4, activation='softmax', kernel_regularizer=l2(0.01))(x)
634
+ model = Model(inputs=base_model.input, outputs=predictions)
635
+ model.compile(optimizer=tf.keras.optimizers.Adam(hp.Float('learning_rate', min_value=1e-5, max_value=1e-2, sampling='LOG')),
636
+ loss='categorical_crossentropy',
637
+ metrics=['accuracy', Precision(), Recall(), tfa.metrics.F1Score(num_classes=4, average='macro')])
638
+ return model
639
+
640
+ lr_scheduler = ReduceLROnPlateau(
641
+ monitor='val_loss',
642
+ factor=0.5,
643
+ patience=3
644
+ )
645
+
646
+ tuner = RandomSearch(
647
+ build_model,
648
+ objective='val_accuracy',
649
+ max_trials=20,
650
+ executions_per_trial=1,
651
+ directory='my_dir',
652
+ project_name='hyperparam_tuning'
653
+ )
654
+
655
+ tuner.search(
656
+ x=X_train,
657
+ y=Y_train,
658
+ epochs=10,
659
+ validation_data=(X_val, Y_val),
660
+ callbacks=[EarlyStopping(monitor='val_accuracy', patience=2), lr_scheduler]
661
+ )
662
+ best_model = tuner.get_best_models(num_models=1)[0]
663
+ best_hyperparameters = tuner.get_best_hyperparameters(num_trials=1)[0]
664
+
665
+ print("Best model summary:")
666
+ best_model.summary()
667
+ print("Best hyperparameters:", best_hyperparameters.values)
668
+
669
+ """#####Train with best hyperparameters"""
670
+
671
+ def rebuild_best_model(best_hyperparameters):
672
+ hp = HyperParameters()
673
+ hp.Int('units', min_value=32, max_value=512, step=32, default=best_hyperparameters['units'])
674
+ model = build_model(hp)
675
+ return model
676
+ # Rebuild the best model
677
+ best_hyperparameters= {'units': 480, 'learning_rate': 1.4547542034522853e-05}
678
+ best_model = rebuild_best_model(best_hyperparameters)
679
+
680
+ # Configure the callbacks
681
+ early_stopper = EarlyStopping(monitor='val_accuracy', patience=10, restore_best_weights=True)
682
+ lr_scheduler = ReduceLROnPlateau(
683
+ monitor='val_loss',
684
+ factor=0.1,
685
+ patience=5,
686
+ verbose=1
687
+ )
688
+
689
+ # Fit the model with a larger number of epochs
690
+ history = best_model.fit(
691
+ x=X_train,
692
+ y=Y_train,
693
+ epochs=50,
694
+ validation_data=(X_val, Y_val),
695
+ callbacks=[early_stopper, lr_scheduler]
696
+ )
697
+
698
+ print(history.history.keys())
699
+
700
+ # Retrieve metrics data using the correct keys
701
+ accuracy = history.history.get('accuracy', [])
702
+ val_accuracy = history.history.get('val_accuracy', [])
703
+ precision = history.history.get('precision_6', []) # Updated to match the key
704
+ val_precision = history.history.get('val_precision_6', [])
705
+ recall = history.history.get('recall_6', []) # Updated to match the key
706
+ val_recall = history.history.get('val_recall_6', [])
707
+ f1_score = history.history.get('f1_score', [])
708
+ val_f1_score = history.history.get('val_f1_score', [])
709
+
710
+ # Determine the range of epochs
711
+ epochs_range = range(1, len(accuracy) + 1)
712
+
713
+ plt.figure(figsize=(14, 10))
714
+ plt.suptitle('Training and Validation Metrics')
715
+
716
+ # Plot accuracy
717
+ if accuracy and val_accuracy:
718
+ plt.subplot(2, 2, 1)
719
+ plt.plot(epochs_range, accuracy, label='Training Accuracy')
720
+ plt.plot(epochs_range, val_accuracy, label='Validation Accuracy')
721
+ plt.title('Accuracy')
722
+ plt.xlabel('Epochs')
723
+ plt.ylabel('Accuracy')
724
+ plt.legend()
725
+
726
+ # Plot precision
727
+ if precision and val_precision:
728
+ plt.subplot(2, 2, 2)
729
+ plt.plot(epochs_range, precision, label='Training Precision')
730
+ plt.plot(epochs_range, val_precision, label='Validation Precision')
731
+ plt.title('Precision')
732
+ plt.xlabel('Epochs')
733
+ plt.ylabel('Precision')
734
+ plt.legend()
735
+
736
+ # Plot recall
737
+ if recall and val_recall:
738
+ plt.subplot(2, 2, 3)
739
+ plt.plot(epochs_range, recall, label='Training Recall')
740
+ plt.plot(epochs_range, val_recall, label='Validation Recall')
741
+ plt.title('Recall')
742
+ plt.xlabel('Epochs')
743
+ plt.ylabel('Recall')
744
+ plt.legend()
745
+
746
+ # Plot F1-score
747
+ if f1_score and val_f1_score:
748
+ plt.subplot(2, 2, 4)
749
+ plt.plot(epochs_range, f1_score, label='Training F1 Score')
750
+ plt.plot(epochs_range, val_f1_score, label='Validation F1 Score')
751
+ plt.title('F1 Score')
752
+ plt.xlabel('Epochs')
753
+ plt.ylabel('F1 Score')
754
+ plt.legend()
755
+
756
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
757
+ plt.show()
758
+
759
+ """#####Evaluate the model"""
760
+
761
+ X_test, Y_test = load_images_and_labels(test_images2, test_labels2, target_size=(128, 128))
762
+ scores = best_model.evaluate(X_test, Y_test, verbose=1)
763
+ print(f"Test Loss: {scores[0]}")
764
+ print(f"Test Accuracy: {scores[1]}")
765
+ print(f"Test Precision: {scores[2]}")
766
+ print(f"Test Recall: {scores[3]}")
767
+ print(f"Test F1 Score (Macro): {scores[4]}")
768
+ if len(scores) > 5:
769
+ print(f"Test F1 Score (Micro): {scores[5]}")
770
+ if len(scores) > 6:
771
+ print(f"Test F1 Score (Weighted): {scores[6]}")
772
+
773
+ predictions = best_model.predict(X_test)
774
+ predicted_classes = np.argmax(predictions, axis=1)
775
+ true_classes = np.argmax(Y_test, axis=1)
776
+
777
+ # Compute the confusion matrix
778
+ cm = confusion_matrix(true_classes, predicted_classes)
779
+ class_names=['1','2','3','4']
780
+
781
+ # Plot the confusion matrix
782
+ plt.figure(figsize=(10, 8))
783
+ sns.heatmap(cm, annot=True, fmt="d", cmap='Blues', xticklabels=class_names, yticklabels=class_names)
784
+ plt.title('Confusion Matrix')
785
+ plt.ylabel('Actual Class')
786
+ plt.xlabel('Predicted Class')
787
+ plt.show()
788
+
789
+ """#####Save the model"""
790
+
791
+ model_path = "/content/drive/My Drive/modelResNet50.h5"
792
+ best_model.save(model_path)
793
+
794
+ """####ResNet18
795
+
796
+ #####Train with best hyperparameters
797
+ """
798
+
799
+ ResNet18, preprocess_input = Classifiers.get('resnet18')
800
+
801
+ model = ResNet18(input_shape=(128, 128, 3), weights='imagenet', include_top=False)
802
+
803
+
804
+ x = GlobalAveragePooling2D()(model.output)
805
+ x = Dense(480, activation='relu', kernel_regularizer=l2(0.01))(x) # Apply L2 regularization here
806
+ predictions = Dense(4, activation='softmax', kernel_regularizer=l2(0.01))(x) # Also apply to the output layer
807
+
808
+ custom_model = Model(inputs=model.input, outputs=predictions)
809
+
810
+ custom_model.compile(
811
+ optimizer=Adam(learning_rate=1.4547542034522853e-05),
812
+ loss='categorical_crossentropy',
813
+ metrics=['accuracy',Precision(), Recall(), tf.metrics.F1Score(average='macro')])
814
+
815
+ custom_model.summary()
816
+
817
+ early_stopping = EarlyStopping(
818
+ monitor='val_loss',
819
+ min_delta=0.001,
820
+ patience=10,
821
+ verbose=1,
822
+ restore_best_weights=True )
823
+
824
+ tf.config.run_functions_eagerly(True)
825
+ history = custom_model.fit(
826
+ X_train, Y_train,
827
+ validation_data=(X_val, Y_val),
828
+ epochs=50, # Maximum number of epochs
829
+ batch_size=32,
830
+ callbacks=[early_stopping]
831
+ )
832
+ tf.config.run_functions_eagerly(False)
833
+
834
+ print(history.history.keys())
835
+
836
+ # Retrieve metrics data using the correct keys
837
+ accuracy = history.history.get('accuracy', [])
838
+ val_accuracy = history.history.get('val_accuracy', [])
839
+ precision = history.history.get('precision_7', []) # Updated to match the key
840
+ val_precision = history.history.get('val_precision_7', [])
841
+ recall = history.history.get('recall_7', []) # Updated to match the key
842
+ val_recall = history.history.get('val_recall_7', [])
843
+ f1_score = history.history.get('f1_score', [])
844
+ val_f1_score = history.history.get('val_f1_score', [])
845
+
846
+ # Determine the range of epochs
847
+ epochs_range = range(1, len(accuracy) + 1)
848
+
849
+ plt.figure(figsize=(14, 10))
850
+ plt.suptitle('Training and Validation Metrics')
851
+
852
+ # Plot accuracy
853
+ if accuracy and val_accuracy:
854
+ plt.subplot(2, 2, 1)
855
+ plt.plot(epochs_range, accuracy, label='Training Accuracy')
856
+ plt.plot(epochs_range, val_accuracy, label='Validation Accuracy')
857
+ plt.title('Accuracy')
858
+ plt.xlabel('Epochs')
859
+ plt.ylabel('Accuracy')
860
+ plt.legend()
861
+
862
+ # Plot precision
863
+ if precision and val_precision:
864
+ plt.subplot(2, 2, 2)
865
+ plt.plot(epochs_range, precision, label='Training Precision')
866
+ plt.plot(epochs_range, val_precision, label='Validation Precision')
867
+ plt.title('Precision')
868
+ plt.xlabel('Epochs')
869
+ plt.ylabel('Precision')
870
+ plt.legend()
871
+
872
+ # Plot recall
873
+ if recall and val_recall:
874
+ plt.subplot(2, 2, 3)
875
+ plt.plot(epochs_range, recall, label='Training Recall')
876
+ plt.plot(epochs_range, val_recall, label='Validation Recall')
877
+ plt.title('Recall')
878
+ plt.xlabel('Epochs')
879
+ plt.ylabel('Recall')
880
+ plt.legend()
881
+
882
+ # Plot F1-score
883
+ if f1_score and val_f1_score:
884
+ plt.subplot(2, 2, 4)
885
+ plt.plot(epochs_range, f1_score, label='Training F1 Score')
886
+ plt.plot(epochs_range, val_f1_score, label='Validation F1 Score')
887
+ plt.title('F1 Score')
888
+ plt.xlabel('Epochs')
889
+ plt.ylabel('F1 Score')
890
+ plt.legend()
891
+
892
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
893
+ plt.show()
894
+
895
+ """#####Evaluate the model"""
896
+
897
+ X_test, Y_test = load_images_and_labels(test_images2, test_labels2, target_size=(128, 128))
898
+ # Evaluate the model
899
+ performance = custom_model.evaluate(X_test, Y_test)
900
+ print(f"Test Loss: {performance[0]}")
901
+ print(f"Test Accuracy: {performance[1]}")
902
+ print(f"Test Precision: {performance[2]}")
903
+ print(f"Test Recall: {performance[3]}")
904
+ print(f"Test F1 Score: {performance[4]}")
905
+
906
+ predictions = custom_model.predict(X_test)
907
+ predicted_classes = np.argmax(predictions, axis=1)
908
+ true_classes = np.argmax(Y_test, axis=1)
909
+
910
+ # Compute the confusion matrix
911
+ cm = confusion_matrix(true_classes, predicted_classes)
912
+ class_names=['1','2','3','4']
913
+
914
+ # Plot the confusion matrix
915
+ plt.figure(figsize=(10, 8))
916
+ sns.heatmap(cm, annot=True, fmt="d", cmap='Blues', xticklabels=class_names, yticklabels=class_names)
917
+ plt.title('Confusion Matrix')
918
+ plt.ylabel('Actual Class')
919
+ plt.xlabel('Predicted Class')
920
+ plt.show()
921
+
922
+ """#####Save the model"""
923
+
924
+ model_path = "/content/drive/My Drive/modelResNet18.h5"
925
+ best_model.save(model_path)
926
+
927
+ """### 4.1.2 Second model (CNN: Simple architecture using Keras)
928
+
929
+ ####hyperparameter tuning
930
+ """
931
+
932
+ class ColorFocusLayer(Layer):
933
+ def __init__(self, **kwargs):
934
+ super(ColorFocusLayer, self).__init__(**kwargs)
935
+ self.conv = None # Conv layer will be set in build
936
+
937
+ def build(self, input_shape):
938
+ # Set the number of groups to a divisor of the number of input channels
939
+ input_channels = input_shape[-1]
940
+ possible_groups = [i for i in range(1, input_channels + 1) if input_channels % i == 0]
941
+ chosen_group = max(possible_groups) # Choose the largest divisor for better learning
942
+ self.conv = Conv2D(input_channels, kernel_size=1, groups=chosen_group, padding='same')
943
+ super(ColorFocusLayer, self).build(input_shape)
944
+
945
+ def call(self, inputs):
946
+ x = self.conv(inputs)
947
+ x = tf.keras.activations.sigmoid(x)
948
+ return inputs * x
949
+
950
+
951
+ def build_model(hp):
952
+ filters_1 = hp.Int('conv_1_filters', min_value=32, max_value=128, step=32, default=64)
953
+ model = Sequential([
954
+ Conv2D(
955
+ hp.Int('conv_1_filters', min_value=32, max_value=128, step=32, default=64),
956
+ kernel_size=hp.Choice('conv_1_kernel', values=[3, 5, 7], default=5),
957
+ padding='same',
958
+ activation='relu',
959
+ input_shape=(128, 128, 3)),
960
+ BatchNormalization(),
961
+ ColorFocusLayer(),
962
+ Conv2D(
963
+ hp.Int('conv_2_filters', min_value=64, max_value=256, step=32, default=96),
964
+ kernel_size=hp.Choice('conv_2_kernel', values=[3, 5], default=3),
965
+ padding='same',
966
+ activation='relu'),
967
+ BatchNormalization(),
968
+ MaxPooling2D(pool_size=2),
969
+ Conv2D(
970
+ hp.Int('conv_3_filters', min_value=128, max_value=256, step=32),
971
+ kernel_size=hp.Choice('conv_3_kernel', values=[3, 5]),
972
+ padding='same',
973
+ activation='relu'),
974
+ BatchNormalization(),
975
+ GlobalAveragePooling2D(),
976
+ Dense(
977
+ hp.Int('dense_units', min_value=64, max_value=256, step=64),
978
+ activation='relu'),
979
+ Dropout(hp.Float('dropout', min_value=0.0, max_value=0.5, step=0.1)),
980
+ Dense(4, activation='softmax')
981
+ ])
982
+
983
+ model.compile(
984
+ optimizer=Adam(hp.Float('learning_rate', min_value=1e-4, max_value=1e-2, sampling='LOG')),
985
+ loss='categorical_crossentropy',
986
+ metrics=['accuracy', Precision(), Recall(), tfa.metrics.F1Score(num_classes=4, average='macro')]
987
+ )
988
+
989
+ return model
990
+
991
+ lr_scheduler = ReduceLROnPlateau(
992
+ monitor='val_loss',
993
+ factor=0.1,
994
+ patience=5
995
+ )
996
+
997
+ tuner = Hyperband(
998
+ build_model,
999
+ objective=Objective("val_accuracy", direction="max"),
1000
+ max_epochs=10,
1001
+ hyperband_iterations=2,
1002
+ directory='my_dir',
1003
+ project_name='hyperparam_tuning'
1004
+ )
1005
+
1006
+ tuner.search(
1007
+ x=X_train,
1008
+ y=Y_train,
1009
+ epochs=10,
1010
+ validation_data=(X_val, Y_val),
1011
+ callbacks=[EarlyStopping(monitor='val_accuracy', patience=3), lr_scheduler]
1012
+ )
1013
+ best_model = tuner.get_best_models(num_models=1)[0]
1014
+ best_hyperparameters = tuner.get_best_hyperparameters(num_trials=1)[0]
1015
+
1016
+ print("Best model summary:")
1017
+ best_model.summary()
1018
+ print("Best hyperparameters:", best_hyperparameters.values)
1019
+
1020
+ """####Train with best hyperparameters"""
1021
+
1022
+ def rebuild_best_model(best_hyperparameters):
1023
+ hp = best_hyperparameters
1024
+ model = build_model(hp)
1025
+ return model
1026
+
1027
+ # Rebuild the best model
1028
+ best_model = rebuild_best_model(tuner.get_best_hyperparameters()[0])
1029
+
1030
+ # Configure the callbacks
1031
+ early_stopper = EarlyStopping(monitor='val_accuracy', patience=10, restore_best_weights=True)
1032
+ lr_scheduler = ReduceLROnPlateau(
1033
+ monitor='val_loss',
1034
+ factor=0.1,
1035
+ patience=5,
1036
+ verbose=1
1037
+ )
1038
+
1039
+ # Fit the model with a larger number of epochs
1040
+ history = best_model.fit(
1041
+ x=X_train,
1042
+ y=Y_train,
1043
+ epochs=50,
1044
+ validation_data=(X_val, Y_val),
1045
+ callbacks=[early_stopper, lr_scheduler]
1046
+ )
1047
+
1048
+ print(history.history.keys())
1049
+
1050
+ # Retrieve metrics data using the correct keys
1051
+ accuracy = history.history.get('accuracy', [])
1052
+ val_accuracy = history.history.get('val_accuracy', [])
1053
+ precision = history.history.get('precision_5', []) # Updated to match the key
1054
+ val_precision = history.history.get('val_precision_5', [])
1055
+ recall = history.history.get('recall_5', []) # Updated to match the key
1056
+ val_recall = history.history.get('val_recall_5', [])
1057
+ f1_score = history.history.get('f1_score', [])
1058
+ val_f1_score = history.history.get('val_f1_score', [])
1059
+
1060
+ # Determine the range of epochs
1061
+ epochs_range = range(1, len(accuracy) + 1)
1062
+
1063
+ plt.figure(figsize=(14, 10))
1064
+ plt.suptitle('Training and Validation Metrics')
1065
+
1066
+ # Plot accuracy
1067
+ if accuracy and val_accuracy:
1068
+ plt.subplot(2, 2, 1)
1069
+ plt.plot(epochs_range, accuracy, label='Training Accuracy')
1070
+ plt.plot(epochs_range, val_accuracy, label='Validation Accuracy')
1071
+ plt.title('Accuracy')
1072
+ plt.xlabel('Epochs')
1073
+ plt.ylabel('Accuracy')
1074
+ plt.legend()
1075
+
1076
+ # Plot precision
1077
+ if precision and val_precision:
1078
+ plt.subplot(2, 2, 2)
1079
+ plt.plot(epochs_range, precision, label='Training Precision')
1080
+ plt.plot(epochs_range, val_precision, label='Validation Precision')
1081
+ plt.title('Precision')
1082
+ plt.xlabel('Epochs')
1083
+ plt.ylabel('Precision')
1084
+ plt.legend()
1085
+
1086
+ # Plot recall
1087
+ if recall and val_recall:
1088
+ plt.subplot(2, 2, 3)
1089
+ plt.plot(epochs_range, recall, label='Training Recall')
1090
+ plt.plot(epochs_range, val_recall, label='Validation Recall')
1091
+ plt.title('Recall')
1092
+ plt.xlabel('Epochs')
1093
+ plt.ylabel('Recall')
1094
+ plt.legend()
1095
+
1096
+ # Plot F1-score
1097
+ if f1_score and val_f1_score:
1098
+ plt.subplot(2, 2, 4)
1099
+ plt.plot(epochs_range, f1_score, label='Training F1 Score')
1100
+ plt.plot(epochs_range, val_f1_score, label='Validation F1 Score')
1101
+ plt.title('F1 Score')
1102
+ plt.xlabel('Epochs')
1103
+ plt.ylabel('F1 Score')
1104
+ plt.legend()
1105
+
1106
+ plt.tight_layout(rect=[0, 0.03, 1, 0.95])
1107
+ plt.show()
1108
+
1109
+ """####Evaluate the model"""
1110
+
1111
+ X_test, Y_test = load_images_and_labels(test_images2, test_labels2, target_size=(128, 128))
1112
+ results = best_model.evaluate(X_test, Y_test, verbose=1)
1113
+
1114
+ print(f"Test Loss: {results[0]}")
1115
+ print(f"Test Accuracy: {results[1]}")
1116
+ print(f"Test Precision: {results[2]}")
1117
+ print(f"Test Recall: {results[3]}")
1118
+ print(f"Test F1 Score: {results[4]}")
1119
+
1120
+ predictions = best_model.predict(X_test)
1121
+ predicted_classes = np.argmax(predictions, axis=1)
1122
+ true_classes = np.argmax(Y_test, axis=1)
1123
+
1124
+ # Compute the confusion matrix
1125
+ cm = confusion_matrix(true_classes, predicted_classes)
1126
+ class_names=['1','2','3','4']
1127
+
1128
+ # Plot the confusion matrix
1129
+ plt.figure(figsize=(10, 8))
1130
+ sns.heatmap(cm, annot=True, fmt="d", cmap='Blues', xticklabels=class_names, yticklabels=class_names)
1131
+ plt.title('Confusion Matrix')
1132
+ plt.ylabel('Actual Class')
1133
+ plt.xlabel('Predicted Class')
1134
+ plt.show()
1135
+
1136
+ """####Save the model"""
1137
+
1138
+ model_path = "/content/drive/My Drive/modelCNN2.h5"
1139
+ best_model.save(model_path)
1140
+
1141
+ """### 4.1.3 Third model (SVM)"""
1142
+
1143
+ # Define the parameter grid
1144
+ param_grid = {
1145
+ 'C': [0.1, 1, 10, 100],
1146
+ 'gamma': [1, 0.1, 0.01, 0.001],
1147
+ 'kernel': ['rbf', 'poly', 'sigmoid']
1148
+ }
1149
+
1150
+ # Create a Support Vector Classifier
1151
+ svm = SVC(probability=True)
1152
+
1153
+ # Create a GridSearchCV object
1154
+ grid_search = GridSearchCV(svm, param_grid, cv=3, verbose=2, scoring='accuracy')
1155
+
1156
+ # Fit GridSearchCV
1157
+ grid_search.fit(np.array(train_features), np.array(train_labels))
1158
+
1159
+ print("Best parameters:", grid_search.best_params_)
1160
+ print("Best cross-validation score: {:.2f}".format(grid_search.best_score_))
1161
+
1162
+ # Train the model with the best parameters
1163
+ best_svm = grid_search.best_estimator_
1164
+
1165
+ # Predict on the test set
1166
+ predictions = best_svm.predict(np.array(test_features))
1167
+
1168
+ print(classification_report(test_labels, predictions))
1169
+ print("Accuracy:", accuracy_score(test_labels, predictions))
1170
+
1171
+ # Compute the confusion matrix
1172
+ cm = confusion_matrix(test_labels, predictions)
1173
+
1174
+ # Plot the confusion matrix
1175
+ plt.figure(figsize=(10, 7))
1176
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=np.unique(test_labels), yticklabels=np.unique(test_labels))
1177
+ plt.xlabel('Predicted Labels')
1178
+ plt.ylabel('True Labels')
1179
+ plt.title('Confusion Matrix')
1180
+ plt.show()
1181
+
1182
+ class CustomCNN(nn.Module):
1183
+ def __init__(self):
1184
+ super(CustomCNN, self).__init__()
1185
+ self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
1186
+ self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
1187
+ self.pool = nn.MaxPool2d(2, 2)
1188
+ self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
1189
+ self.conv4 = nn.Conv2d(128, 256, kernel_size=3, padding=1)
1190
+
1191
+ def forward(self, x):
1192
+ x = self.pool(F.relu(self.conv1(x)))
1193
+ x = self.pool(F.relu(self.conv2(x)))
1194
+ x = self.pool(F.relu(self.conv3(x)))
1195
+ x = self.pool(F.relu(self.conv4(x)))
1196
+ # Flatten the output for feature extraction
1197
+ x = x.view(x.size(0), -1)
1198
+ return x
1199
+
1200
+ # Initialize the model
1201
+ model_cnn = CustomCNN()
1202
+
1203
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
1204
+ model_cnn.to(device)
1205
+ model_cnn.eval()
1206
+
1207
+ def extract_features(data_loader):
1208
+ features = []
1209
+ labels = []
1210
+ with torch.no_grad():
1211
+ for inputs, targets in data_loader:
1212
+ inputs = inputs.to(device)
1213
+ outputs = model_cnn(inputs)
1214
+ outputs = outputs.view(outputs.size(0), -1) # Flatten the output
1215
+ features.append(outputs.cpu().numpy())
1216
+ labels.append(targets.numpy())
1217
+ features = np.concatenate(features, axis=0)
1218
+ labels = np.concatenate(labels, axis=0)
1219
+ return features, labels
1220
+
1221
+ train_features, train_labels = extract_features(train_loader2)
1222
+ val_features, val_labels = extract_features(val_loader2)
1223
+ test_features, test_labels = extract_features(test_loader2)
1224
+
1225
+ class_names = ['1', '2', '3', '4']
1226
+
1227
+ # Function to plot confusion matrix
1228
+ def plot_confusion_matrix(cm, class_names, title='Confusion Matrix'):
1229
+ plt.figure(figsize=(8, 6))
1230
+ sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', cbar=False, xticklabels=class_names, yticklabels=class_names)
1231
+ plt.xlabel('Predicted labels')
1232
+ plt.ylabel('True labels')
1233
+ plt.title(title)
1234
+ plt.show()
1235
+
1236
+ svm_model = SVC(kernel='rbf', C=10, gamma=0.1)
1237
+ svm_model.fit(train_features, train_labels)
1238
+
1239
+ # [ c 1, gamma 1]
1240
+ # Predict on the training set
1241
+ train_predictions = svm_model.predict(train_features)
1242
+
1243
+ # Evaluate on the training set
1244
+ train_accuracy = accuracy_score(train_labels, train_predictions)
1245
+ train_recall = recall_score(train_labels, train_predictions, average='macro')
1246
+ train_precision = precision_score(train_labels, train_predictions, average='macro')
1247
+ train_f1 = f1_metric(train_labels, train_predictions, average='macro')
1248
+
1249
+ print(f'Training Accuracy: {train_accuracy:.4f}')
1250
+ print(f'Training Recall: {train_recall:.4f}')
1251
+ print(f'Training Precision: {train_precision:.4f}')
1252
+ print(f'Training F1 Score: {train_f1:.4f}')
1253
+
1254
+ # Predict on the validation set
1255
+ val_predictions = svm_model.predict(val_features)
1256
+
1257
+ # Evaluate on the validation set
1258
+ val_accuracy = accuracy_score(val_labels, val_predictions)
1259
+ val_recall = recall_score(val_labels, val_predictions, average='macro')
1260
+ val_precision = precision_score(val_labels, val_predictions, average='macro')
1261
+ val_f1 = f1_metric(val_labels, val_predictions, average='macro')
1262
+
1263
+ print(f'Validation Accuracy: {val_accuracy:.4f}')
1264
+ print(f'Validation Recall: {val_recall:.4f}')
1265
+ print(f'Validation Precision: {val_precision:.4f}')
1266
+ print(f'Validation F1 Score: {val_f1:.4f}')
1267
+
1268
+ # Predict on the test set
1269
+ test_predictions = svm_model.predict(test_features)
1270
+
1271
+ # Evaluate on the test set
1272
+ test_accuracy = accuracy_score(test_labels, test_predictions)
1273
+ test_recall = recall_score(test_labels, test_predictions, average='macro')
1274
+ test_precision = precision_score(test_labels, test_predictions, average='macro')
1275
+ test_f1 = f1_metric(test_labels, test_predictions, average='macro')
1276
+
1277
+ print(f"Test Accuracy: {test_accuracy:.4f}")
1278
+ print(f"Test Recall: {test_recall:.4f}")
1279
+ print(f"Test Precision: {test_precision:.4f}")
1280
+ print(f"Test F1 Score: {test_f1:.4f}")
1281
+
1282
+ # Generate and plot confusion matrix for test data
1283
+ test_cm = confusion_matrix(test_labels, test_predictions)
1284
+ # Plot the confusion matrix
1285
+ plot_confusion_matrix(test_cm, class_names, title='Test Confusion Matrix')
1286
+
1287
+ # Save the model to disk
1288
+ dump(svm_model, 'svm_model1.joblib')
1289
+
1290
+ # [ c 10, gamma 0.1]
1291
+ # Predict on the training set
1292
+ train_predictions = svm_model.predict(train_features)
1293
+
1294
+ # Evaluate on the training set
1295
+ train_accuracy = accuracy_score(train_labels, train_predictions)
1296
+ train_recall = recall_score(train_labels, train_predictions, average='macro')
1297
+ train_precision = precision_score(train_labels, train_predictions, average='macro')
1298
+ train_f1 = f1_metric(train_labels, train_predictions, average='macro')
1299
+
1300
+ print(f'Training Accuracy: {train_accuracy:.4f}')
1301
+ print(f'Training Recall: {train_recall:.4f}')
1302
+ print(f'Training Precision: {train_precision:.4f}')
1303
+ print(f'Training F1 Score: {train_f1:.4f}')
1304
+
1305
+ # Predict on the validation set
1306
+ val_predictions = svm_model.predict(val_features)
1307
+
1308
+ # Evaluate on the validation set
1309
+ val_accuracy = accuracy_score(val_labels, val_predictions)
1310
+ val_recall = recall_score(val_labels, val_predictions, average='macro')
1311
+ val_precision = precision_score(val_labels, val_predictions, average='macro')
1312
+ val_f1 = f1_metric(val_labels, val_predictions, average='macro')
1313
+
1314
+ print(f'Validation Accuracy: {val_accuracy:.4f}')
1315
+ print(f'Validation Recall: {val_recall:.4f}')
1316
+ print(f'Validation Precision: {val_precision:.4f}')
1317
+ print(f'Validation F1 Score: {val_f1:.4f}')
1318
+
1319
+ # Predict on the test set
1320
+ test_predictions = svm_model.predict(test_features)
1321
+
1322
+ # Evaluate on the test set
1323
+ test_accuracy = accuracy_score(test_labels, test_predictions)
1324
+ test_recall = recall_score(test_labels, test_predictions, average='macro')
1325
+ test_precision = precision_score(test_labels, test_predictions, average='macro')
1326
+ test_f1 = f1_metric(test_labels, test_predictions, average='macro')
1327
+
1328
+ print(f"Test Accuracy: {test_accuracy:.4f}")
1329
+ print(f"Test Recall: {test_recall:.4f}")
1330
+ print(f"Test Precision: {test_precision:.4f}")
1331
+ print(f"Test F1 Score: {test_f1:.4f}")
1332
+
1333
+ # Generate and plot confusion matrix for test data
1334
+ test_cm = confusion_matrix(test_labels, test_predictions)
1335
+ # Plot the confusion matrix
1336
+ plot_confusion_matrix(test_cm, class_names, title='Test Confusion Matrix')
1337
+
1338
+ # Save the model to disk
1339
+ dump(svm_model, 'svm_model2.joblib')
1340
+
1341
+ svm_model = SVC(kernel='poly', C=10, gamma=0.1)
1342
+ svm_model.fit(train_features, train_labels)
1343
+
1344
+ # [ c 1, gamma 0.1]
1345
+
1346
+ # Predict on the training set
1347
+ train_predictions = svm_model.predict(train_features)
1348
+
1349
+ # Evaluate on the training set
1350
+ train_accuracy = accuracy_score(train_labels, train_predictions)
1351
+ train_recall = recall_score(train_labels, train_predictions, average='macro')
1352
+ train_precision = precision_score(train_labels, train_predictions, average='macro')
1353
+ train_f1 = f1_metric(train_labels, train_predictions, average='macro')
1354
+
1355
+ print(f'Training Accuracy: {train_accuracy:.4f}')
1356
+ print(f'Training Recall: {train_recall:.4f}')
1357
+ print(f'Training Precision: {train_precision:.4f}')
1358
+ print(f'Training F1 Score: {train_f1:.4f}')
1359
+
1360
+ # Predict on the validation set
1361
+ val_predictions = svm_model.predict(val_features)
1362
+
1363
+ # Evaluate on the validation set
1364
+ val_accuracy = accuracy_score(val_labels, val_predictions)
1365
+ val_recall = recall_score(val_labels, val_predictions, average='macro')
1366
+ val_precision = precision_score(val_labels, val_predictions, average='macro')
1367
+ val_f1 = f1_metric(val_labels, val_predictions, average='macro')
1368
+
1369
+ print(f'Validation Accuracy: {val_accuracy:.4f}')
1370
+ print(f'Validation Recall: {val_recall:.4f}')
1371
+ print(f'Validation Precision: {val_precision:.4f}')
1372
+ print(f'Validation F1 Score: {val_f1:.4f}')
1373
+
1374
+ # Predict on the test set
1375
+ test_predictions = svm_model.predict(test_features)
1376
+
1377
+ # Evaluate on the test set
1378
+ test_accuracy = accuracy_score(test_labels, test_predictions)
1379
+ test_recall = recall_score(test_labels, test_predictions, average='macro')
1380
+ test_precision = precision_score(test_labels, test_predictions, average='macro')
1381
+ test_f1 = f1_metric(test_labels, test_predictions, average='macro')
1382
+
1383
+ print(f"Test Accuracy: {test_accuracy:.4f}")
1384
+ print(f"Test Recall: {test_recall:.4f}")
1385
+ print(f"Test Precision: {test_precision:.4f}")
1386
+ print(f"Test F1 Score: {test_f1:.4f}")
1387
+
1388
+ # Save the model to disk
1389
+ dump(svm_model, 'svm_model3.joblib')
1390
+
1391
+ # Generate and plot confusion matrix for test data
1392
+ test_cm = confusion_matrix(test_labels, test_predictions)
1393
+ # Plot the confusion matrix
1394
+ plot_confusion_matrix(test_cm, class_names, title='Test Confusion Matrix')
1395
+
1396
+ # [ c 10, gamma 0.1]
1397
+
1398
+ # Predict on the training set
1399
+ train_predictions = svm_model.predict(train_features)
1400
+
1401
+ # Evaluate on the training set
1402
+ train_accuracy = accuracy_score(train_labels, train_predictions)
1403
+ train_recall = recall_score(train_labels, train_predictions, average='macro')
1404
+ train_precision = precision_score(train_labels, train_predictions, average='macro')
1405
+ train_f1 = f1_metric(train_labels, train_predictions, average='macro')
1406
+
1407
+ print(f'Training Accuracy: {train_accuracy:.4f}')
1408
+ print(f'Training Recall: {train_recall:.4f}')
1409
+ print(f'Training Precision: {train_precision:.4f}')
1410
+ print(f'Training F1 Score: {train_f1:.4f}')
1411
+
1412
+ # Predict on the validation set
1413
+ val_predictions = svm_model.predict(val_features)
1414
+
1415
+ # Evaluate on the validation set
1416
+ val_accuracy = accuracy_score(val_labels, val_predictions)
1417
+ val_recall = recall_score(val_labels, val_predictions, average='macro')
1418
+ val_precision = precision_score(val_labels, val_predictions, average='macro')
1419
+ val_f1 = f1_metric(val_labels, val_predictions, average='macro')
1420
+
1421
+ print(f'Validation Accuracy: {val_accuracy:.4f}')
1422
+ print(f'Validation Recall: {val_recall:.4f}')
1423
+ print(f'Validation Precision: {val_precision:.4f}')
1424
+ print(f'Validation F1 Score: {val_f1:.4f}')
1425
+
1426
+ # Predict on the test set
1427
+ test_predictions = svm_model.predict(test_features)
1428
+
1429
+ # Evaluate on the test set
1430
+ test_accuracy = accuracy_score(test_labels, test_predictions)
1431
+ test_recall = recall_score(test_labels, test_predictions, average='macro')
1432
+ test_precision = precision_score(test_labels, test_predictions, average='macro')
1433
+ test_f1 = f1_metric(test_labels, test_predictions, average='macro')
1434
+
1435
+ print(f"Test Accuracy: {test_accuracy:.4f}")
1436
+ print(f"Test Recall: {test_recall:.4f}")
1437
+ print(f"Test Precision: {test_precision:.4f}")
1438
+ print(f"Test F1 Score: {test_f1:.4f}")
1439
+
1440
+ # Save the model to disk
1441
+ dump(svm_model, 'svm_model4.joblib')
1442
+
1443
+ # Generate and plot confusion matrix for test data
1444
+ test_cm = confusion_matrix(test_labels, test_predictions)
1445
+ # Plot the confusion matrix
1446
+ plot_confusion_matrix(test_cm, class_names, title='Test Confusion Matrix')