aiyubali commited on
Commit
19ea92a
·
1 Parent(s): 16f73cf

FaceGNN Updated v1.1

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.json filter=lfs diff=lfs merge=lfs -text
37
+ *.png filter=lfs diff=lfs merge=lfs -text
CODE/Create ML Model.ipynb ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Run ML model"
8
+ ]
9
+ },
10
+ {
11
+ "cell_type": "code",
12
+ "execution_count": null,
13
+ "metadata": {},
14
+ "outputs": [],
15
+ "source": [
16
+ "from sklearn.preprocessing import LabelEncoder\n",
17
+ "import numpy as np\n",
18
+ "from sklearn.svm import SVC\n",
19
+ "\n",
20
+ "# Load the data\n",
21
+ "data = np.load('/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Embedding.npz')\n",
22
+ "EMBEDDED_X = data['embeddings']\n",
23
+ "Y = data['labels']\n",
24
+ "\n",
25
+ "# Encode the labels\n",
26
+ "encoder = LabelEncoder()\n",
27
+ "encoder.fit(Y)\n",
28
+ "Y = encoder.transform(Y)\n",
29
+ "\n",
30
+ "# Train the SVM model on the entire dataset\n",
31
+ "model = SVC(kernel='rbf', probability=True) # kernal='linear'\n",
32
+ "model.fit(EMBEDDED_X, Y)\n",
33
+ "\n",
34
+ "# Save the trained model\n",
35
+ "import pickle\n",
36
+ "with open('/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Model.pkl', 'wb') as f:\n",
37
+ " pickle.dump(model, f)\n"
38
+ ]
39
+ }
40
+ ],
41
+ "metadata": {
42
+ "kernelspec": {
43
+ "display_name": "shanin",
44
+ "language": "python",
45
+ "name": "python3"
46
+ },
47
+ "language_info": {
48
+ "codemirror_mode": {
49
+ "name": "ipython",
50
+ "version": 3
51
+ },
52
+ "file_extension": ".py",
53
+ "mimetype": "text/x-python",
54
+ "name": "python",
55
+ "nbconvert_exporter": "python",
56
+ "pygments_lexer": "ipython3",
57
+ "version": "3.12.9"
58
+ }
59
+ },
60
+ "nbformat": 4,
61
+ "nbformat_minor": 2
62
+ }
CODE/Data Handling.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
CODE/Features Extract with Augmentation.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import cv2 as cv
4
+ import tensorflow as tf
5
+ from keras_facenet import FaceNet
6
+ import random
7
+
8
+ # Disable GPU usage to avoid CuDNN errors
9
+ tf.config.set_visible_devices([], 'GPU') # Force TensorFlow to use only CPU
10
+ print("Running on CPU to avoid CuDNN errors.")
11
+
12
+ class FaceEmbeddingGenerator:
13
+ def __init__(self, directory, output_path):
14
+ self.directory = directory
15
+ self.output_path = output_path
16
+ self.target_size = (160, 160)
17
+ self.embedder = FaceNet()
18
+ self.embeddings = []
19
+ self.labels = []
20
+
21
+ def load_cropped_face(self, filename):
22
+ """Load an already cropped face image and apply augmentation."""
23
+ img = cv.imread(filename)
24
+ if img is None:
25
+ raise ValueError(f"Image {filename} not found or invalid format.")
26
+ img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
27
+
28
+ # Ensure the image is resized to the target size
29
+ img = cv.resize(img, self.target_size)
30
+
31
+ # Apply augmentation
32
+ img = self.augment_image(img)
33
+ return img
34
+
35
+ def augment_image(self, img):
36
+ """Apply random augmentations to the image."""
37
+ # Random horizontal flip
38
+ if random.random() < 0.5:
39
+ img = cv.flip(img, 1)
40
+
41
+ # Random rotation
42
+ angle = random.uniform(-10, 10)
43
+ h, w = img.shape[:2]
44
+ M = cv.getRotationMatrix2D((w // 2, h // 2), angle, 1)
45
+ img = cv.warpAffine(img, M, (w, h))
46
+
47
+ # Random brightness adjustment
48
+ brightness_factor = random.uniform(0.8, 1.2)
49
+ img = np.clip(img * brightness_factor, 0, 255).astype(np.uint8)
50
+
51
+ # Random noise
52
+ noise = np.random.normal(0, 5, img.shape).astype(np.uint8)
53
+ img = np.clip(img + noise, 0, 255).astype(np.uint8)
54
+
55
+ return img
56
+
57
+ def get_embedding(self, face_img):
58
+ """Generate an embedding from a face image."""
59
+ face_img = face_img.astype('float32')
60
+ face_img = np.expand_dims(face_img, axis=0)
61
+ return self.embedder.embeddings(face_img)[0] # 512D vector
62
+
63
+ def save_batch(self, folder_output_path, embeddings, labels):
64
+ """Save a batch of embeddings to a temporary file."""
65
+ temp_path = folder_output_path.replace(".npz", "_temp.npz")
66
+ np.savez_compressed(temp_path, embeddings=np.asarray(embeddings), labels=np.asarray(labels))
67
+ print(f"Saved intermediate embeddings to {temp_path}")
68
+
69
+ def process_folder(self, folder_path, label, batch_size=50):
70
+ """Process a single folder of cropped images in batches."""
71
+ batch_embeddings = []
72
+ batch_labels = []
73
+ count = 0
74
+
75
+ for filename in os.listdir(folder_path):
76
+ try:
77
+ filepath = os.path.join(folder_path, filename)
78
+ face = self.load_cropped_face(filepath)
79
+ embedding = self.get_embedding(face)
80
+ batch_embeddings.append(embedding)
81
+ batch_labels.append(label)
82
+ count += 1
83
+
84
+ if count % batch_size == 0:
85
+ # Save batch and clear memory
86
+ self.save_batch(self.output_path, batch_embeddings, batch_labels)
87
+ self.embeddings.extend(batch_embeddings)
88
+ self.labels.extend(batch_labels)
89
+ batch_embeddings = []
90
+ batch_labels = []
91
+
92
+ except Exception as e:
93
+ print(f"Error processing {filename}: {e}")
94
+
95
+ # Save remaining data
96
+ if batch_embeddings:
97
+ self.save_batch(self.output_path, batch_embeddings, batch_labels)
98
+ self.embeddings.extend(batch_embeddings)
99
+ self.labels.extend(batch_labels)
100
+
101
+ def process_all_classes(self):
102
+ """Process all folders and save embeddings for each folder separately."""
103
+ for sub_dir in os.listdir(self.directory):
104
+ sub_dir_path = os.path.join(self.directory, sub_dir)
105
+ if not os.path.isdir(sub_dir_path):
106
+ continue
107
+
108
+ # Define output file for this folder
109
+ folder_output_path = os.path.join(self.output_path, f"{sub_dir}_embeddings.npz")
110
+
111
+ # Skip folder if its embeddings already exist
112
+ if os.path.exists(folder_output_path):
113
+ print(f"Skipping folder {sub_dir} as embeddings already exist.")
114
+ continue
115
+
116
+ print(f"Processing folder: {sub_dir}")
117
+
118
+ # Clear previous embeddings and labels
119
+ self.embeddings = []
120
+ self.labels = []
121
+
122
+ # Process the current folder
123
+ self.process_folder(sub_dir_path, sub_dir)
124
+
125
+ # Save the embeddings and labels for the current folder
126
+ np.savez_compressed(folder_output_path, embeddings=np.asarray(self.embeddings), labels=np.asarray(self.labels))
127
+ print(f"Saved embeddings for folder {sub_dir} to {folder_output_path}")
128
+
129
+ def merge_all_embeddings(output_dir, final_output_file):
130
+ """Merge all folder embeddings into a single NPZ file."""
131
+ all_embeddings = []
132
+ all_labels = []
133
+
134
+ # Iterate over all files in the output directory
135
+ for filename in os.listdir(output_dir):
136
+ filepath = os.path.join(output_dir, filename)
137
+
138
+ # Skip non-NPZ files
139
+ if not filename.endswith(".npz"):
140
+ continue
141
+
142
+ # Load the embeddings and labels from the file
143
+ data = np.load(filepath)
144
+ embeddings = data['embeddings']
145
+ labels = data['labels']
146
+
147
+ # Append to the overall list
148
+ all_embeddings.append(embeddings)
149
+ all_labels.append(labels)
150
+
151
+ # Combine all embeddings and labels
152
+ all_embeddings = np.vstack(all_embeddings)
153
+ all_labels = np.hstack(all_labels)
154
+
155
+ # Save the merged embeddings and labels into a final NPZ file
156
+ np.savez_compressed(final_output_file, embeddings=all_embeddings, labels=all_labels)
157
+ print(f"Final merged embeddings saved to {final_output_file}")
158
+
159
+ # Usage
160
+ data_dir = "/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/shanin" # Replace with the path to your dataset
161
+ output_dir = "/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/tmp" # Replace with output directory
162
+
163
+ # Ensure output directory exists
164
+ os.makedirs(output_dir, exist_ok=True)
165
+
166
+ # Initialize and process all classes
167
+ face_generator = FaceEmbeddingGenerator(data_dir, output_dir)
168
+ face_generator.process_all_classes()
169
+
170
+ # Path for the final merged embeddings file
171
+ final_output_file = "/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Embedding_Augment.npz"
172
+
173
+ # Merge all embeddings
174
+ merge_all_embeddings(output_dir, final_output_file)
CODE/Features Extraction using FaceNet.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import cv2 as cv
4
+ from mtcnn.mtcnn import MTCNN
5
+ from keras_facenet import FaceNet
6
+
7
+ class FaceEmbeddingGenerator:
8
+ def __init__(self, directory, output_path):
9
+ self.directory = directory
10
+ self.output_path = output_path
11
+ self.target_size = (160, 160)
12
+ self.detector = MTCNN()
13
+ self.embedder = FaceNet()
14
+ self.embeddings = []
15
+ self.labels = []
16
+
17
+ def extract_face(self, filename):
18
+ img = cv.imread(filename)
19
+ if img is None:
20
+ raise ValueError(f"Image {filename} not found or invalid format.")
21
+ img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
22
+
23
+ # Resize large images to avoid excessive memory usage
24
+ if max(img.shape[:2]) > 1024:
25
+ scale_factor = 1024 / max(img.shape[:2])
26
+ img = cv.resize(img, None, fx=scale_factor, fy=scale_factor)
27
+
28
+ detection = self.detector.detect_faces(img)
29
+ if not detection:
30
+ raise ValueError(f"No face detected in {filename}.")
31
+
32
+ x, y, w, h = detection[0]['box']
33
+ x, y = abs(x), abs(y)
34
+ face = img[y:y + h, x:x + w]
35
+ face_arr = cv.resize(face, self.target_size)
36
+ return face_arr
37
+
38
+ def get_embedding(self, face_img):
39
+ """Generate an embedding from a face image."""
40
+ face_img = face_img.astype('float32')
41
+ face_img = np.expand_dims(face_img, axis=0)
42
+ return self.embedder.embeddings(face_img)[0] # 512D vector
43
+
44
+ def save_batch(self, folder_output_path, embeddings, labels):
45
+ """Save a batch of embeddings to a temporary file."""
46
+ temp_path = folder_output_path.replace(".npz", "_temp.npz")
47
+ np.savez_compressed(temp_path, embeddings=np.asarray(embeddings), labels=np.asarray(labels))
48
+ print(f"Saved intermediate embeddings to {temp_path}")
49
+
50
+ def process_folder(self, folder_path, label, batch_size=50):
51
+ """Process a single folder of images in batches."""
52
+ batch_embeddings = []
53
+ batch_labels = []
54
+ count = 0
55
+
56
+ for filename in os.listdir(folder_path):
57
+ try:
58
+ filepath = os.path.join(folder_path, filename)
59
+ face = self.extract_face(filepath)
60
+ embedding = self.get_embedding(face)
61
+ batch_embeddings.append(embedding)
62
+ batch_labels.append(label)
63
+ count += 1
64
+
65
+ if count % batch_size == 0:
66
+ # Save batch and clear memory
67
+ self.save_batch(self.output_path, batch_embeddings, batch_labels)
68
+ self.embeddings.extend(batch_embeddings)
69
+ self.labels.extend(batch_labels)
70
+ batch_embeddings = []
71
+ batch_labels = []
72
+
73
+ except Exception as e:
74
+ print(f"Error processing {filename}: {e}")
75
+
76
+ # Save remaining data
77
+ if batch_embeddings:
78
+ self.save_batch(self.output_path, batch_embeddings, batch_labels)
79
+ self.embeddings.extend(batch_embeddings)
80
+ self.labels.extend(batch_labels)
81
+
82
+ def process_all_classes(self):
83
+ """Process all folders and save embeddings for each folder separately."""
84
+ for sub_dir in os.listdir(self.directory):
85
+ sub_dir_path = os.path.join(self.directory, sub_dir)
86
+ if not os.path.isdir(sub_dir_path):
87
+ continue
88
+
89
+ # Define output file for this folder
90
+ folder_output_path = os.path.join(self.output_path, f"{sub_dir}_embeddings.npz")
91
+
92
+ # Skip folder if its embeddings already exist
93
+ if os.path.exists(folder_output_path):
94
+ print(f"Skipping folder {sub_dir} as embeddings already exist.")
95
+ continue
96
+
97
+ print(f"Processing folder: {sub_dir}")
98
+
99
+ # Clear previous embeddings and labels
100
+ self.embeddings = []
101
+ self.labels = []
102
+
103
+ # Process the current folder
104
+ self.process_folder(sub_dir_path, sub_dir)
105
+
106
+ # Save the embeddings and labels for the current folder
107
+ np.savez_compressed(folder_output_path, embeddings=np.asarray(self.embeddings), labels=np.asarray(self.labels))
108
+ print(f"Saved embeddings for folder {sub_dir} to {folder_output_path}")
109
+
110
+ def merge_all_embeddings(output_dir, final_output_file):
111
+ """Merge all folder embeddings into a single NPZ file."""
112
+ all_embeddings = []
113
+ all_labels = []
114
+
115
+ # Iterate over all files in the output directory
116
+ for filename in os.listdir(output_dir):
117
+ filepath = os.path.join(output_dir, filename)
118
+
119
+ # Skip non-NPZ files
120
+ if not filename.endswith(".npz"):
121
+ continue
122
+
123
+ # Load the embeddings and labels from the file
124
+ data = np.load(filepath)
125
+ embeddings = data['embeddings']
126
+ labels = data['labels']
127
+
128
+ # Append to the overall list
129
+ all_embeddings.append(embeddings)
130
+ all_labels.append(labels)
131
+
132
+ # Combine all embeddings and labels
133
+ all_embeddings = np.vstack(all_embeddings)
134
+ all_labels = np.hstack(all_labels)
135
+
136
+ # Save the merged embeddings and labels into a final NPZ file
137
+ np.savez_compressed(final_output_file, embeddings=all_embeddings, labels=all_labels)
138
+ print(f"Final merged embeddings saved to {final_output_file}")
139
+
140
+ # Usage
141
+ data_dir = "/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/DATASET" # Replace with the path to your dataset
142
+ output_dir = "/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/tmp" # Replace with your desired output directory
143
+
144
+ # Ensure output directory exists
145
+ os.makedirs(output_dir, exist_ok=True)
146
+
147
+ # Initialize and process all classes
148
+ face_generator = FaceEmbeddingGenerator(data_dir, output_dir)
149
+ face_generator.process_all_classes()
150
+
151
+ # Path for the final merged embeddings file
152
+ final_output_file = "/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Embedding_v5.npz"
153
+
154
+ # Merge all embeddings
155
+ merge_all_embeddings(output_dir, final_output_file)
156
+
CODE/Features Extraction using ViT.ipynb ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stderr",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "2025-01-27 16:42:47.893272: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered\n",
13
+ "WARNING: All log messages before absl::InitializeLog() is called are written to STDERR\n",
14
+ "E0000 00:00:1737974567.952170 6163 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered\n",
15
+ "E0000 00:00:1737974567.970030 6163 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered\n",
16
+ "2025-01-27 16:42:48.097247: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.\n",
17
+ "To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.\n"
18
+ ]
19
+ }
20
+ ],
21
+ "source": [
22
+ "import os\n",
23
+ "import torch\n",
24
+ "import torch.nn as nn\n",
25
+ "from torch.utils.data import DataLoader\n",
26
+ "from torchvision import transforms\n",
27
+ "from torchvision.datasets import ImageFolder\n",
28
+ "from transformers import ViTForImageClassification, ViTFeatureExtractor"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": 2,
34
+ "metadata": {},
35
+ "outputs": [
36
+ {
37
+ "name": "stdout",
38
+ "output_type": "stream",
39
+ "text": [
40
+ "Using device: cuda\n"
41
+ ]
42
+ }
43
+ ],
44
+ "source": [
45
+ "# Define device\n",
46
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
47
+ "print(f\"Using device: {device}\")"
48
+ ]
49
+ },
50
+ {
51
+ "cell_type": "code",
52
+ "execution_count": 3,
53
+ "metadata": {},
54
+ "outputs": [],
55
+ "source": [
56
+ "# Paths\n",
57
+ "dataset_path = \"/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/FACE_CROP\" # Replace with your dataset path\n",
58
+ "model_save_path = \"/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/v1.pth\""
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": 4,
64
+ "metadata": {},
65
+ "outputs": [
66
+ {
67
+ "name": "stderr",
68
+ "output_type": "stream",
69
+ "text": [
70
+ "Some weights of ViTForImageClassification were not initialized from the model checkpoint at google/vit-base-patch16-224 and are newly initialized because the shapes did not match:\n",
71
+ "- classifier.bias: found shape torch.Size([1000]) in the checkpoint and torch.Size([1364]) in the model instantiated\n",
72
+ "- classifier.weight: found shape torch.Size([1000, 768]) in the checkpoint and torch.Size([1364, 768]) in the model instantiated\n",
73
+ "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
74
+ ]
75
+ }
76
+ ],
77
+ "source": [
78
+ "# Data Transformations with 160x160 resize\n",
79
+ "transform = transforms.Compose([\n",
80
+ " transforms.Resize((224, 224)), # Resize to 160x160 as per your dataset\n",
81
+ " transforms.RandomHorizontalFlip(),\n",
82
+ " transforms.RandomRotation(15),\n",
83
+ " transforms.ToTensor(),\n",
84
+ " transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),\n",
85
+ "])\n",
86
+ "\n",
87
+ "# Load Dataset\n",
88
+ "dataset = ImageFolder(root=dataset_path, transform=transform)\n",
89
+ "\n",
90
+ "# DataLoader (using all images)\n",
91
+ "dataloader = DataLoader(dataset, batch_size=16, shuffle=True, num_workers=4)\n",
92
+ "\n",
93
+ "# Define ViT Model\n",
94
+ "model = ViTForImageClassification.from_pretrained(\n",
95
+ " \"google/vit-base-patch16-224\", num_labels=len(dataset.classes), \n",
96
+ " ignore_mismatched_sizes=True # This will ignore size mismatch warnings\n",
97
+ ")\n",
98
+ "\n",
99
+ "# Modify the classifier head to match the number of classes in your dataset\n",
100
+ "model.classifier = nn.Linear(model.config.hidden_size, len(dataset.classes))\n",
101
+ "\n",
102
+ "# Move model to device\n",
103
+ "model = model.to(device)\n",
104
+ "\n",
105
+ "# Define Optimizer, Loss Function, and Scheduler\n",
106
+ "optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5, weight_decay=1e-4)\n",
107
+ "criterion = nn.CrossEntropyLoss()\n",
108
+ "scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)"
109
+ ]
110
+ },
111
+ {
112
+ "cell_type": "code",
113
+ "execution_count": 6,
114
+ "metadata": {},
115
+ "outputs": [],
116
+ "source": [
117
+ "# Training Function\n",
118
+ "def train_model(model, dataloader, epochs=100):\n",
119
+ " model.train()\n",
120
+ "\n",
121
+ " for epoch in range(epochs):\n",
122
+ " print(f\"Epoch {epoch + 1}/{epochs}\")\n",
123
+ " epoch_loss = 0.0\n",
124
+ "\n",
125
+ " for images, labels in dataloader:\n",
126
+ " images, labels = images.to(device), labels.to(device)\n",
127
+ "\n",
128
+ " # Forward pass\n",
129
+ " optimizer.zero_grad()\n",
130
+ " outputs = model(images).logits\n",
131
+ " loss = criterion(outputs, labels)\n",
132
+ "\n",
133
+ " # Backward pass\n",
134
+ " loss.backward()\n",
135
+ " optimizer.step()\n",
136
+ "\n",
137
+ " epoch_loss += loss.item()\n",
138
+ "\n",
139
+ " # Step the scheduler\n",
140
+ " scheduler.step()\n",
141
+ "\n",
142
+ " print(f\"Epoch Loss: {epoch_loss / len(dataloader):.4f}\")\n",
143
+ "\n",
144
+ " # Save the trained model\n",
145
+ " torch.save(model.state_dict(), model_save_path)\n",
146
+ " print(\"Training complete! Model saved.\")"
147
+ ]
148
+ },
149
+ {
150
+ "cell_type": "code",
151
+ "execution_count": 7,
152
+ "metadata": {},
153
+ "outputs": [
154
+ {
155
+ "name": "stdout",
156
+ "output_type": "stream",
157
+ "text": [
158
+ "Epoch 1/100\n",
159
+ "Epoch Loss: 6.4940\n",
160
+ "Epoch 2/100\n",
161
+ "Epoch Loss: 4.4488\n",
162
+ "Epoch 3/100\n",
163
+ "Epoch Loss: 2.7864\n",
164
+ "Epoch 4/100\n",
165
+ "Epoch Loss: 1.6240\n",
166
+ "Epoch 5/100\n",
167
+ "Epoch Loss: 0.9264\n",
168
+ "Epoch 6/100\n",
169
+ "Epoch Loss: 0.5430\n",
170
+ "Epoch 7/100\n",
171
+ "Epoch Loss: 0.4927\n",
172
+ "Epoch 8/100\n",
173
+ "Epoch Loss: 0.4542\n",
174
+ "Epoch 9/100\n",
175
+ "Epoch Loss: 0.4172\n",
176
+ "Epoch 10/100\n",
177
+ "Epoch Loss: 0.3850\n",
178
+ "Epoch 11/100\n",
179
+ "Epoch Loss: 0.3539\n",
180
+ "Epoch 12/100\n",
181
+ "Epoch Loss: 0.3482\n",
182
+ "Epoch 13/100\n",
183
+ "Epoch Loss: 0.3455\n",
184
+ "Epoch 14/100\n",
185
+ "Epoch Loss: 0.3437\n",
186
+ "Epoch 15/100\n",
187
+ "Epoch Loss: 0.3408\n",
188
+ "Epoch 16/100\n",
189
+ "Epoch Loss: 0.3362\n",
190
+ "Epoch 17/100\n",
191
+ "Epoch Loss: 0.3362\n",
192
+ "Epoch 18/100\n",
193
+ "Epoch Loss: 0.3366\n",
194
+ "Epoch 19/100\n",
195
+ "Epoch Loss: 0.3359\n",
196
+ "Epoch 20/100\n",
197
+ "Epoch Loss: 0.3364\n",
198
+ "Epoch 21/100\n",
199
+ "Epoch Loss: 0.3349\n",
200
+ "Epoch 22/100\n",
201
+ "Epoch Loss: 0.3352\n",
202
+ "Epoch 23/100\n",
203
+ "Epoch Loss: 0.3356\n",
204
+ "Epoch 24/100\n",
205
+ "Epoch Loss: 0.3347\n",
206
+ "Epoch 25/100\n",
207
+ "Epoch Loss: 0.3343\n",
208
+ "Epoch 26/100\n",
209
+ "Epoch Loss: 0.3355\n",
210
+ "Epoch 27/100\n",
211
+ "Epoch Loss: 0.3347\n",
212
+ "Epoch 28/100\n",
213
+ "Epoch Loss: 0.3350\n",
214
+ "Epoch 29/100\n",
215
+ "Epoch Loss: 0.3354\n",
216
+ "Epoch 30/100\n",
217
+ "Epoch Loss: 0.3350\n",
218
+ "Epoch 31/100\n",
219
+ "Epoch Loss: 0.3356\n",
220
+ "Epoch 32/100\n",
221
+ "Epoch Loss: 0.3358\n",
222
+ "Epoch 33/100\n",
223
+ "Epoch Loss: 0.3349\n",
224
+ "Epoch 34/100\n",
225
+ "Epoch Loss: 0.3354\n",
226
+ "Epoch 35/100\n",
227
+ "Epoch Loss: 0.3347\n",
228
+ "Epoch 36/100\n",
229
+ "Epoch Loss: 0.3352\n",
230
+ "Epoch 37/100\n",
231
+ "Epoch Loss: 0.3351\n",
232
+ "Epoch 38/100\n",
233
+ "Epoch Loss: 0.3349\n",
234
+ "Epoch 39/100\n",
235
+ "Epoch Loss: 0.3343\n",
236
+ "Epoch 40/100\n",
237
+ "Epoch Loss: 0.3356\n",
238
+ "Epoch 41/100\n",
239
+ "Epoch Loss: 0.3349\n",
240
+ "Epoch 42/100\n",
241
+ "Epoch Loss: 0.3348\n",
242
+ "Epoch 43/100\n",
243
+ "Epoch Loss: 0.3348\n",
244
+ "Epoch 44/100\n",
245
+ "Epoch Loss: 0.3360\n",
246
+ "Epoch 45/100\n",
247
+ "Epoch Loss: 0.3352\n",
248
+ "Epoch 46/100\n",
249
+ "Epoch Loss: 0.3344\n",
250
+ "Epoch 47/100\n",
251
+ "Epoch Loss: 0.3351\n",
252
+ "Epoch 48/100\n",
253
+ "Epoch Loss: 0.3360\n",
254
+ "Epoch 49/100\n",
255
+ "Epoch Loss: 0.3351\n",
256
+ "Epoch 50/100\n",
257
+ "Epoch Loss: 0.3348\n",
258
+ "Epoch 51/100\n",
259
+ "Epoch Loss: 0.3344\n",
260
+ "Epoch 52/100\n",
261
+ "Epoch Loss: 0.3347\n",
262
+ "Epoch 53/100\n",
263
+ "Epoch Loss: 0.3349\n",
264
+ "Epoch 54/100\n",
265
+ "Epoch Loss: 0.3359\n",
266
+ "Epoch 55/100\n",
267
+ "Epoch Loss: 0.3353\n",
268
+ "Epoch 56/100\n",
269
+ "Epoch Loss: 0.3347\n",
270
+ "Epoch 57/100\n",
271
+ "Epoch Loss: 0.3355\n",
272
+ "Epoch 58/100\n",
273
+ "Epoch Loss: 0.3356\n",
274
+ "Epoch 59/100\n",
275
+ "Epoch Loss: 0.3352\n",
276
+ "Epoch 60/100\n",
277
+ "Epoch Loss: 0.3357\n",
278
+ "Epoch 61/100\n",
279
+ "Epoch Loss: 0.3359\n",
280
+ "Epoch 62/100\n",
281
+ "Epoch Loss: 0.3357\n",
282
+ "Epoch 63/100\n",
283
+ "Epoch Loss: 0.3349\n",
284
+ "Epoch 64/100\n",
285
+ "Epoch Loss: 0.3358\n",
286
+ "Epoch 65/100\n",
287
+ "Epoch Loss: 0.3349\n",
288
+ "Epoch 66/100\n",
289
+ "Epoch Loss: 0.3347\n",
290
+ "Epoch 67/100\n",
291
+ "Epoch Loss: 0.3359\n",
292
+ "Epoch 68/100\n",
293
+ "Epoch Loss: 0.3349\n",
294
+ "Epoch 69/100\n",
295
+ "Epoch Loss: 0.3338\n",
296
+ "Epoch 70/100\n",
297
+ "Epoch Loss: 0.3351\n",
298
+ "Epoch 71/100\n",
299
+ "Epoch Loss: 0.3358\n",
300
+ "Epoch 72/100\n",
301
+ "Epoch Loss: 0.3347\n",
302
+ "Epoch 73/100\n",
303
+ "Epoch Loss: 0.3353\n",
304
+ "Epoch 74/100\n",
305
+ "Epoch Loss: 0.3347\n",
306
+ "Epoch 75/100\n",
307
+ "Epoch Loss: 0.3344\n",
308
+ "Epoch 76/100\n",
309
+ "Epoch Loss: 0.3341\n",
310
+ "Epoch 77/100\n",
311
+ "Epoch Loss: 0.3352\n",
312
+ "Epoch 78/100\n",
313
+ "Epoch Loss: 0.3349\n",
314
+ "Epoch 79/100\n",
315
+ "Epoch Loss: 0.3344\n",
316
+ "Epoch 80/100\n",
317
+ "Epoch Loss: 0.3350\n",
318
+ "Epoch 81/100\n",
319
+ "Epoch Loss: 0.3351\n",
320
+ "Epoch 82/100\n",
321
+ "Epoch Loss: 0.3347\n",
322
+ "Epoch 83/100\n",
323
+ "Epoch Loss: 0.3358\n",
324
+ "Epoch 84/100\n",
325
+ "Epoch Loss: 0.3346\n",
326
+ "Epoch 85/100\n",
327
+ "Epoch Loss: 0.3351\n",
328
+ "Epoch 86/100\n",
329
+ "Epoch Loss: 0.3347\n",
330
+ "Epoch 87/100\n",
331
+ "Epoch Loss: 0.3364\n",
332
+ "Epoch 88/100\n",
333
+ "Epoch Loss: 0.3356\n",
334
+ "Epoch 89/100\n",
335
+ "Epoch Loss: 0.3349\n",
336
+ "Epoch 90/100\n",
337
+ "Epoch Loss: 0.3347\n",
338
+ "Epoch 91/100\n",
339
+ "Epoch Loss: 0.3346\n",
340
+ "Epoch 92/100\n",
341
+ "Epoch Loss: 0.3354\n",
342
+ "Epoch 93/100\n",
343
+ "Epoch Loss: 0.3362\n",
344
+ "Epoch 94/100\n",
345
+ "Epoch Loss: 0.3344\n",
346
+ "Epoch 95/100\n",
347
+ "Epoch Loss: 0.3351\n",
348
+ "Epoch 96/100\n",
349
+ "Epoch Loss: 0.3346\n",
350
+ "Epoch 97/100\n",
351
+ "Epoch Loss: 0.3352\n",
352
+ "Epoch 98/100\n",
353
+ "Epoch Loss: 0.3343\n",
354
+ "Epoch 99/100\n",
355
+ "Epoch Loss: 0.3352\n",
356
+ "Epoch 100/100\n",
357
+ "Epoch Loss: 0.3346\n",
358
+ "Training complete! Model saved.\n"
359
+ ]
360
+ }
361
+ ],
362
+ "source": [
363
+ "# Train the Model\n",
364
+ "train_model(model, dataloader, epochs=100)"
365
+ ]
366
+ }
367
+ ],
368
+ "metadata": {
369
+ "kernelspec": {
370
+ "display_name": "minibat",
371
+ "language": "python",
372
+ "name": "python3"
373
+ },
374
+ "language_info": {
375
+ "codemirror_mode": {
376
+ "name": "ipython",
377
+ "version": 3
378
+ },
379
+ "file_extension": ".py",
380
+ "mimetype": "text/x-python",
381
+ "name": "python",
382
+ "nbconvert_exporter": "python",
383
+ "pygments_lexer": "ipython3",
384
+ "version": "3.12.8"
385
+ }
386
+ },
387
+ "nbformat": 4,
388
+ "nbformat_minor": 2
389
+ }
CODE/GNN.ipynb ADDED
@@ -0,0 +1,686 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# GNN\n"
8
+ ]
9
+ },
10
+ {
11
+ "cell_type": "code",
12
+ "execution_count": 31,
13
+ "metadata": {},
14
+ "outputs": [
15
+ {
16
+ "name": "stdout",
17
+ "output_type": "stream",
18
+ "text": [
19
+ "Epoch 000, Loss: 6.2491\n",
20
+ "Train - Acc: 0.0021, Prec: 0.0004, Rec: 0.0021, F1: 0.0004\n",
21
+ "Test - Acc: 0.0065, Prec: 0.0007, Rec: 0.0065, F1: 0.0007\n",
22
+ "---\n",
23
+ "Epoch 001, Loss: 6.2427\n",
24
+ "Train - Acc: 0.0090, Prec: 0.0014, Rec: 0.0090, F1: 0.0020\n",
25
+ "Test - Acc: 0.0094, Prec: 0.0014, Rec: 0.0094, F1: 0.0020\n",
26
+ "---\n",
27
+ "Epoch 002, Loss: 6.2370\n",
28
+ "Train - Acc: 0.0148, Prec: 0.0068, Rec: 0.0148, F1: 0.0046\n",
29
+ "Test - Acc: 0.0135, Prec: 0.0021, Rec: 0.0135, F1: 0.0033\n",
30
+ "---\n",
31
+ "Epoch 003, Loss: 6.2300\n",
32
+ "Train - Acc: 0.0251, Prec: 0.0194, Rec: 0.0251, F1: 0.0128\n",
33
+ "Test - Acc: 0.0229, Prec: 0.0095, Rec: 0.0229, F1: 0.0096\n",
34
+ "---\n",
35
+ "Epoch 004, Loss: 6.2224\n",
36
+ "Train - Acc: 0.0372, Prec: 0.0354, Rec: 0.0372, F1: 0.0226\n",
37
+ "Test - Acc: 0.0370, Prec: 0.0252, Rec: 0.0370, F1: 0.0217\n",
38
+ "---\n",
39
+ "Epoch 005, Loss: 6.2134\n",
40
+ "Train - Acc: 0.0539, Prec: 0.0470, Rec: 0.0539, F1: 0.0342\n",
41
+ "Test - Acc: 0.0582, Prec: 0.0356, Rec: 0.0582, F1: 0.0366\n",
42
+ "---\n",
43
+ "Epoch 006, Loss: 6.2030\n",
44
+ "Train - Acc: 0.0689, Prec: 0.0651, Rec: 0.0689, F1: 0.0451\n",
45
+ "Test - Acc: 0.0711, Prec: 0.0366, Rec: 0.0711, F1: 0.0409\n",
46
+ "---\n",
47
+ "Epoch 007, Loss: 6.1911\n",
48
+ "Train - Acc: 0.0805, Prec: 0.0851, Rec: 0.0805, F1: 0.0546\n",
49
+ "Test - Acc: 0.0811, Prec: 0.0509, Rec: 0.0811, F1: 0.0498\n",
50
+ "---\n",
51
+ "Epoch 008, Loss: 6.1763\n",
52
+ "Train - Acc: 0.0917, Prec: 0.1023, Rec: 0.0917, F1: 0.0648\n",
53
+ "Test - Acc: 0.0870, Prec: 0.0625, Rec: 0.0870, F1: 0.0574\n",
54
+ "---\n",
55
+ "Epoch 009, Loss: 6.1598\n",
56
+ "Train - Acc: 0.1050, Prec: 0.1120, Rec: 0.1050, F1: 0.0773\n",
57
+ "Test - Acc: 0.0964, Prec: 0.0782, Rec: 0.0964, F1: 0.0671\n",
58
+ "---\n",
59
+ "Epoch 010, Loss: 6.1408\n",
60
+ "Train - Acc: 0.1152, Prec: 0.1343, Rec: 0.1152, F1: 0.0898\n",
61
+ "Test - Acc: 0.1105, Prec: 0.0905, Rec: 0.1105, F1: 0.0797\n",
62
+ "---\n",
63
+ "Epoch 011, Loss: 6.1200\n",
64
+ "Train - Acc: 0.1275, Prec: 0.1538, Rec: 0.1275, F1: 0.1012\n",
65
+ "Test - Acc: 0.1246, Prec: 0.1065, Rec: 0.1246, F1: 0.0939\n",
66
+ "---\n",
67
+ "Epoch 012, Loss: 6.0960\n",
68
+ "Train - Acc: 0.1406, Prec: 0.1613, Rec: 0.1406, F1: 0.1112\n",
69
+ "Test - Acc: 0.1381, Prec: 0.1138, Rec: 0.1381, F1: 0.1041\n",
70
+ "---\n",
71
+ "Epoch 013, Loss: 6.0684\n",
72
+ "Train - Acc: 0.1536, Prec: 0.1668, Rec: 0.1536, F1: 0.1208\n",
73
+ "Test - Acc: 0.1481, Prec: 0.1209, Rec: 0.1481, F1: 0.1119\n",
74
+ "---\n",
75
+ "Epoch 014, Loss: 6.0390\n",
76
+ "Train - Acc: 0.1679, Prec: 0.1776, Rec: 0.1679, F1: 0.1300\n",
77
+ "Test - Acc: 0.1651, Prec: 0.1279, Rec: 0.1651, F1: 0.1223\n",
78
+ "---\n",
79
+ "Epoch 015, Loss: 6.0067\n",
80
+ "Train - Acc: 0.1798, Prec: 0.1834, Rec: 0.1798, F1: 0.1365\n",
81
+ "Test - Acc: 0.1745, Prec: 0.1341, Rec: 0.1745, F1: 0.1277\n",
82
+ "---\n",
83
+ "Epoch 016, Loss: 5.9703\n",
84
+ "Train - Acc: 0.1968, Prec: 0.2095, Rec: 0.1968, F1: 0.1519\n",
85
+ "Test - Acc: 0.1904, Prec: 0.1465, Rec: 0.1904, F1: 0.1402\n",
86
+ "---\n",
87
+ "Epoch 017, Loss: 5.9334\n",
88
+ "Train - Acc: 0.2070, Prec: 0.2383, Rec: 0.2070, F1: 0.1668\n",
89
+ "Test - Acc: 0.2039, Prec: 0.1723, Rec: 0.2039, F1: 0.1565\n",
90
+ "---\n",
91
+ "Epoch 018, Loss: 5.8914\n",
92
+ "Train - Acc: 0.2264, Prec: 0.2631, Rec: 0.2264, F1: 0.1908\n",
93
+ "Test - Acc: 0.2174, Prec: 0.1855, Rec: 0.2174, F1: 0.1722\n",
94
+ "---\n",
95
+ "Epoch 019, Loss: 5.8434\n",
96
+ "Train - Acc: 0.2438, Prec: 0.2878, Rec: 0.2438, F1: 0.2103\n",
97
+ "Test - Acc: 0.2356, Prec: 0.2094, Rec: 0.2356, F1: 0.1915\n",
98
+ "---\n",
99
+ "Epoch 020, Loss: 5.7926\n",
100
+ "Train - Acc: 0.2648, Prec: 0.2996, Rec: 0.2648, F1: 0.2290\n",
101
+ "Test - Acc: 0.2585, Prec: 0.2400, Rec: 0.2585, F1: 0.2139\n",
102
+ "---\n",
103
+ "Epoch 021, Loss: 5.7404\n",
104
+ "Train - Acc: 0.2969, Prec: 0.3355, Rec: 0.2969, F1: 0.2567\n",
105
+ "Test - Acc: 0.2920, Prec: 0.2558, Rec: 0.2920, F1: 0.2400\n",
106
+ "---\n",
107
+ "Epoch 022, Loss: 5.6823\n",
108
+ "Train - Acc: 0.3406, Prec: 0.3640, Rec: 0.3406, F1: 0.2945\n",
109
+ "Test - Acc: 0.3396, Prec: 0.3007, Rec: 0.3396, F1: 0.2844\n",
110
+ "---\n",
111
+ "Epoch 023, Loss: 5.6144\n",
112
+ "Train - Acc: 0.3931, Prec: 0.4091, Rec: 0.3931, F1: 0.3409\n",
113
+ "Test - Acc: 0.4054, Prec: 0.3622, Rec: 0.4054, F1: 0.3441\n",
114
+ "---\n",
115
+ "Epoch 024, Loss: 5.5414\n",
116
+ "Train - Acc: 0.4461, Prec: 0.4553, Rec: 0.4461, F1: 0.3893\n",
117
+ "Test - Acc: 0.4495, Prec: 0.3992, Rec: 0.4495, F1: 0.3824\n",
118
+ "---\n",
119
+ "Epoch 025, Loss: 5.4667\n",
120
+ "Train - Acc: 0.4946, Prec: 0.4858, Rec: 0.4946, F1: 0.4318\n",
121
+ "Test - Acc: 0.4935, Prec: 0.4317, Rec: 0.4935, F1: 0.4219\n",
122
+ "---\n",
123
+ "Epoch 026, Loss: 5.3841\n",
124
+ "Train - Acc: 0.5333, Prec: 0.5170, Rec: 0.5333, F1: 0.4682\n",
125
+ "Test - Acc: 0.5353, Prec: 0.4590, Rec: 0.5353, F1: 0.4610\n",
126
+ "---\n",
127
+ "Epoch 027, Loss: 5.2927\n",
128
+ "Train - Acc: 0.5727, Prec: 0.5534, Rec: 0.5727, F1: 0.5083\n",
129
+ "Test - Acc: 0.5682, Prec: 0.4963, Rec: 0.5682, F1: 0.4947\n",
130
+ "---\n",
131
+ "Epoch 028, Loss: 5.1967\n",
132
+ "Train - Acc: 0.6071, Prec: 0.5735, Rec: 0.6071, F1: 0.5433\n",
133
+ "Test - Acc: 0.6028, Prec: 0.5295, Rec: 0.6028, F1: 0.5311\n",
134
+ "---\n",
135
+ "Epoch 029, Loss: 5.0865\n",
136
+ "Train - Acc: 0.6357, Prec: 0.5983, Rec: 0.6357, F1: 0.5732\n",
137
+ "Test - Acc: 0.6293, Prec: 0.5549, Rec: 0.6293, F1: 0.5585\n",
138
+ "---\n",
139
+ "Epoch 030, Loss: 4.9874\n",
140
+ "Train - Acc: 0.6633, Prec: 0.6220, Rec: 0.6633, F1: 0.6007\n",
141
+ "Test - Acc: 0.6592, Prec: 0.5892, Rec: 0.6592, F1: 0.5912\n",
142
+ "---\n",
143
+ "Epoch 031, Loss: 4.8584\n",
144
+ "Train - Acc: 0.6865, Prec: 0.6450, Rec: 0.6865, F1: 0.6254\n",
145
+ "Test - Acc: 0.6827, Prec: 0.6090, Rec: 0.6827, F1: 0.6151\n",
146
+ "---\n",
147
+ "Epoch 032, Loss: 4.7352\n",
148
+ "Train - Acc: 0.7150, Prec: 0.6837, Rec: 0.7150, F1: 0.6569\n",
149
+ "Test - Acc: 0.7103, Prec: 0.6472, Rec: 0.7103, F1: 0.6487\n",
150
+ "---\n",
151
+ "Epoch 033, Loss: 4.6055\n",
152
+ "Train - Acc: 0.7424, Prec: 0.7126, Rec: 0.7424, F1: 0.6891\n",
153
+ "Test - Acc: 0.7403, Prec: 0.6822, Rec: 0.7403, F1: 0.6826\n",
154
+ "---\n",
155
+ "Epoch 034, Loss: 4.4723\n",
156
+ "Train - Acc: 0.7637, Prec: 0.7376, Rec: 0.7637, F1: 0.7139\n",
157
+ "Test - Acc: 0.7591, Prec: 0.7013, Rec: 0.7591, F1: 0.7042\n",
158
+ "---\n",
159
+ "Epoch 035, Loss: 4.3249\n",
160
+ "Train - Acc: 0.7838, Prec: 0.7601, Rec: 0.7838, F1: 0.7379\n",
161
+ "Test - Acc: 0.7785, Prec: 0.7184, Rec: 0.7785, F1: 0.7254\n",
162
+ "---\n",
163
+ "Epoch 036, Loss: 4.1834\n",
164
+ "Train - Acc: 0.8035, Prec: 0.7918, Rec: 0.8035, F1: 0.7624\n",
165
+ "Test - Acc: 0.7949, Prec: 0.7460, Rec: 0.7949, F1: 0.7480\n",
166
+ "---\n",
167
+ "Epoch 037, Loss: 4.0259\n",
168
+ "Train - Acc: 0.8240, Prec: 0.8148, Rec: 0.8240, F1: 0.7885\n",
169
+ "Test - Acc: 0.8102, Prec: 0.7636, Rec: 0.8102, F1: 0.7661\n",
170
+ "---\n",
171
+ "Epoch 038, Loss: 3.8687\n",
172
+ "Train - Acc: 0.8418, Prec: 0.8277, Rec: 0.8418, F1: 0.8095\n",
173
+ "Test - Acc: 0.8331, Prec: 0.7901, Rec: 0.8331, F1: 0.7935\n",
174
+ "---\n",
175
+ "Epoch 039, Loss: 3.7094\n",
176
+ "Train - Acc: 0.8594, Prec: 0.8451, Rec: 0.8594, F1: 0.8318\n",
177
+ "Test - Acc: 0.8502, Prec: 0.8074, Rec: 0.8502, F1: 0.8135\n",
178
+ "---\n",
179
+ "Epoch 040, Loss: 3.5471\n",
180
+ "Train - Acc: 0.8743, Prec: 0.8658, Rec: 0.8743, F1: 0.8504\n",
181
+ "Test - Acc: 0.8678, Prec: 0.8308, Rec: 0.8678, F1: 0.8353\n",
182
+ "---\n",
183
+ "Epoch 041, Loss: 3.3771\n",
184
+ "Train - Acc: 0.8879, Prec: 0.8793, Rec: 0.8879, F1: 0.8668\n",
185
+ "Test - Acc: 0.8831, Prec: 0.8505, Rec: 0.8831, F1: 0.8544\n",
186
+ "---\n",
187
+ "Epoch 042, Loss: 3.2006\n",
188
+ "Train - Acc: 0.9025, Prec: 0.8967, Rec: 0.9025, F1: 0.8853\n",
189
+ "Test - Acc: 0.8948, Prec: 0.8660, Rec: 0.8948, F1: 0.8698\n",
190
+ "---\n",
191
+ "Epoch 043, Loss: 3.0434\n",
192
+ "Train - Acc: 0.9136, Prec: 0.9112, Rec: 0.9136, F1: 0.9000\n",
193
+ "Test - Acc: 0.9078, Prec: 0.8853, Rec: 0.9078, F1: 0.8871\n",
194
+ "---\n",
195
+ "Epoch 044, Loss: 2.8869\n",
196
+ "Train - Acc: 0.9246, Prec: 0.9241, Rec: 0.9246, F1: 0.9132\n",
197
+ "Test - Acc: 0.9177, Prec: 0.9008, Rec: 0.9177, F1: 0.9003\n",
198
+ "---\n",
199
+ "Epoch 045, Loss: 2.7065\n",
200
+ "Train - Acc: 0.9343, Prec: 0.9377, Rec: 0.9343, F1: 0.9255\n",
201
+ "Test - Acc: 0.9313, Prec: 0.9143, Rec: 0.9313, F1: 0.9164\n",
202
+ "---\n",
203
+ "Epoch 046, Loss: 2.5626\n",
204
+ "Train - Acc: 0.9439, Prec: 0.9472, Rec: 0.9439, F1: 0.9375\n",
205
+ "Test - Acc: 0.9365, Prec: 0.9223, Rec: 0.9365, F1: 0.9234\n",
206
+ "---\n",
207
+ "Epoch 047, Loss: 2.3942\n",
208
+ "Train - Acc: 0.9512, Prec: 0.9540, Rec: 0.9512, F1: 0.9462\n",
209
+ "Test - Acc: 0.9465, Prec: 0.9360, Rec: 0.9465, F1: 0.9365\n",
210
+ "---\n",
211
+ "Epoch 048, Loss: 2.2485\n",
212
+ "Train - Acc: 0.9571, Prec: 0.9610, Rec: 0.9571, F1: 0.9537\n",
213
+ "Test - Acc: 0.9536, Prec: 0.9488, Rec: 0.9536, F1: 0.9465\n",
214
+ "---\n",
215
+ "Epoch 049, Loss: 2.0896\n",
216
+ "Train - Acc: 0.9634, Prec: 0.9662, Rec: 0.9634, F1: 0.9611\n",
217
+ "Test - Acc: 0.9565, Prec: 0.9512, Rec: 0.9565, F1: 0.9499\n",
218
+ "---\n",
219
+ "Epoch 050, Loss: 1.9533\n",
220
+ "Train - Acc: 0.9677, Prec: 0.9720, Rec: 0.9677, F1: 0.9663\n",
221
+ "Test - Acc: 0.9636, Prec: 0.9601, Rec: 0.9636, F1: 0.9587\n",
222
+ "---\n",
223
+ "Epoch 051, Loss: 1.8189\n",
224
+ "Train - Acc: 0.9697, Prec: 0.9736, Rec: 0.9697, F1: 0.9686\n",
225
+ "Test - Acc: 0.9659, Prec: 0.9646, Rec: 0.9659, F1: 0.9620\n",
226
+ "---\n",
227
+ "Epoch 052, Loss: 1.6774\n",
228
+ "Train - Acc: 0.9725, Prec: 0.9757, Rec: 0.9725, F1: 0.9717\n",
229
+ "Test - Acc: 0.9677, Prec: 0.9664, Rec: 0.9677, F1: 0.9638\n",
230
+ "---\n",
231
+ "Epoch 053, Loss: 1.5600\n",
232
+ "Train - Acc: 0.9744, Prec: 0.9773, Rec: 0.9744, F1: 0.9737\n",
233
+ "Test - Acc: 0.9706, Prec: 0.9699, Rec: 0.9706, F1: 0.9675\n",
234
+ "---\n",
235
+ "Epoch 054, Loss: 1.4384\n",
236
+ "Train - Acc: 0.9772, Prec: 0.9794, Rec: 0.9772, F1: 0.9767\n",
237
+ "Test - Acc: 0.9694, Prec: 0.9691, Rec: 0.9694, F1: 0.9664\n",
238
+ "---\n",
239
+ "Epoch 055, Loss: 1.3362\n",
240
+ "Train - Acc: 0.9783, Prec: 0.9805, Rec: 0.9783, F1: 0.9778\n",
241
+ "Test - Acc: 0.9706, Prec: 0.9711, Rec: 0.9706, F1: 0.9676\n",
242
+ "---\n",
243
+ "Epoch 056, Loss: 1.2312\n",
244
+ "Train - Acc: 0.9794, Prec: 0.9815, Rec: 0.9794, F1: 0.9790\n",
245
+ "Test - Acc: 0.9718, Prec: 0.9723, Rec: 0.9718, F1: 0.9689\n",
246
+ "---\n",
247
+ "Epoch 057, Loss: 1.1255\n",
248
+ "Train - Acc: 0.9805, Prec: 0.9824, Rec: 0.9805, F1: 0.9801\n",
249
+ "Test - Acc: 0.9712, Prec: 0.9710, Rec: 0.9712, F1: 0.9682\n",
250
+ "---\n",
251
+ "Epoch 058, Loss: 1.0525\n",
252
+ "Train - Acc: 0.9821, Prec: 0.9837, Rec: 0.9821, F1: 0.9819\n",
253
+ "Test - Acc: 0.9712, Prec: 0.9713, Rec: 0.9712, F1: 0.9684\n",
254
+ "---\n",
255
+ "Epoch 059, Loss: 0.9754\n",
256
+ "Train - Acc: 0.9837, Prec: 0.9851, Rec: 0.9837, F1: 0.9835\n",
257
+ "Test - Acc: 0.9736, Prec: 0.9740, Rec: 0.9736, F1: 0.9711\n",
258
+ "---\n",
259
+ "Epoch 060, Loss: 0.9083\n",
260
+ "Train - Acc: 0.9846, Prec: 0.9859, Rec: 0.9846, F1: 0.9845\n",
261
+ "Test - Acc: 0.9771, Prec: 0.9789, Rec: 0.9771, F1: 0.9753\n",
262
+ "---\n",
263
+ "Epoch 061, Loss: 0.8354\n",
264
+ "Train - Acc: 0.9849, Prec: 0.9862, Rec: 0.9849, F1: 0.9848\n",
265
+ "Test - Acc: 0.9771, Prec: 0.9790, Rec: 0.9771, F1: 0.9753\n",
266
+ "---\n",
267
+ "Epoch 062, Loss: 0.7766\n",
268
+ "Train - Acc: 0.9853, Prec: 0.9866, Rec: 0.9853, F1: 0.9852\n",
269
+ "Test - Acc: 0.9777, Prec: 0.9802, Rec: 0.9777, F1: 0.9762\n",
270
+ "---\n",
271
+ "Epoch 063, Loss: 0.7299\n",
272
+ "Train - Acc: 0.9859, Prec: 0.9872, Rec: 0.9859, F1: 0.9858\n",
273
+ "Test - Acc: 0.9777, Prec: 0.9799, Rec: 0.9777, F1: 0.9762\n",
274
+ "---\n",
275
+ "Epoch 064, Loss: 0.6610\n",
276
+ "Train - Acc: 0.9868, Prec: 0.9880, Rec: 0.9868, F1: 0.9867\n",
277
+ "Test - Acc: 0.9783, Prec: 0.9807, Rec: 0.9783, F1: 0.9769\n",
278
+ "---\n",
279
+ "Epoch 065, Loss: 0.6329\n",
280
+ "Train - Acc: 0.9871, Prec: 0.9884, Rec: 0.9871, F1: 0.9871\n",
281
+ "Test - Acc: 0.9771, Prec: 0.9800, Rec: 0.9771, F1: 0.9758\n",
282
+ "---\n",
283
+ "Epoch 066, Loss: 0.5902\n",
284
+ "Train - Acc: 0.9875, Prec: 0.9886, Rec: 0.9875, F1: 0.9875\n",
285
+ "Test - Acc: 0.9777, Prec: 0.9805, Rec: 0.9777, F1: 0.9764\n",
286
+ "---\n",
287
+ "Epoch 067, Loss: 0.5649\n",
288
+ "Train - Acc: 0.9874, Prec: 0.9884, Rec: 0.9874, F1: 0.9873\n",
289
+ "Test - Acc: 0.9771, Prec: 0.9802, Rec: 0.9771, F1: 0.9757\n",
290
+ "---\n",
291
+ "Epoch 068, Loss: 0.5139\n",
292
+ "Train - Acc: 0.9872, Prec: 0.9883, Rec: 0.9872, F1: 0.9872\n",
293
+ "Test - Acc: 0.9765, Prec: 0.9795, Rec: 0.9765, F1: 0.9750\n",
294
+ "---\n",
295
+ "Epoch 069, Loss: 0.4956\n",
296
+ "Train - Acc: 0.9881, Prec: 0.9892, Rec: 0.9881, F1: 0.9881\n",
297
+ "Test - Acc: 0.9771, Prec: 0.9799, Rec: 0.9771, F1: 0.9757\n",
298
+ "---\n",
299
+ "Epoch 070, Loss: 0.4590\n",
300
+ "Train - Acc: 0.9885, Prec: 0.9896, Rec: 0.9885, F1: 0.9885\n",
301
+ "Test - Acc: 0.9783, Prec: 0.9809, Rec: 0.9783, F1: 0.9770\n",
302
+ "---\n",
303
+ "Epoch 071, Loss: 0.4314\n",
304
+ "Train - Acc: 0.9888, Prec: 0.9899, Rec: 0.9888, F1: 0.9888\n",
305
+ "Test - Acc: 0.9788, Prec: 0.9817, Rec: 0.9788, F1: 0.9778\n",
306
+ "---\n",
307
+ "Epoch 072, Loss: 0.4131\n",
308
+ "Train - Acc: 0.9896, Prec: 0.9904, Rec: 0.9896, F1: 0.9895\n",
309
+ "Test - Acc: 0.9800, Prec: 0.9832, Rec: 0.9800, F1: 0.9792\n",
310
+ "---\n",
311
+ "Epoch 073, Loss: 0.3992\n",
312
+ "Train - Acc: 0.9900, Prec: 0.9908, Rec: 0.9900, F1: 0.9900\n",
313
+ "Test - Acc: 0.9800, Prec: 0.9832, Rec: 0.9800, F1: 0.9792\n",
314
+ "---\n",
315
+ "Epoch 074, Loss: 0.3744\n",
316
+ "Train - Acc: 0.9899, Prec: 0.9907, Rec: 0.9899, F1: 0.9898\n",
317
+ "Test - Acc: 0.9800, Prec: 0.9830, Rec: 0.9800, F1: 0.9791\n",
318
+ "---\n",
319
+ "Epoch 075, Loss: 0.3555\n",
320
+ "Train - Acc: 0.9906, Prec: 0.9913, Rec: 0.9906, F1: 0.9905\n",
321
+ "Test - Acc: 0.9806, Prec: 0.9836, Rec: 0.9806, F1: 0.9796\n",
322
+ "---\n",
323
+ "Epoch 076, Loss: 0.3364\n",
324
+ "Train - Acc: 0.9910, Prec: 0.9918, Rec: 0.9910, F1: 0.9910\n",
325
+ "Test - Acc: 0.9812, Prec: 0.9841, Rec: 0.9812, F1: 0.9802\n",
326
+ "---\n",
327
+ "Epoch 077, Loss: 0.3325\n",
328
+ "Train - Acc: 0.9915, Prec: 0.9922, Rec: 0.9915, F1: 0.9915\n",
329
+ "Test - Acc: 0.9812, Prec: 0.9846, Rec: 0.9812, F1: 0.9804\n",
330
+ "---\n",
331
+ "Epoch 078, Loss: 0.3259\n",
332
+ "Train - Acc: 0.9922, Prec: 0.9928, Rec: 0.9922, F1: 0.9922\n",
333
+ "Test - Acc: 0.9800, Prec: 0.9837, Rec: 0.9800, F1: 0.9793\n",
334
+ "---\n",
335
+ "Epoch 079, Loss: 0.3057\n",
336
+ "Train - Acc: 0.9929, Prec: 0.9935, Rec: 0.9929, F1: 0.9929\n",
337
+ "Test - Acc: 0.9812, Prec: 0.9847, Rec: 0.9812, F1: 0.9805\n",
338
+ "---\n",
339
+ "Epoch 080, Loss: 0.2901\n",
340
+ "Train - Acc: 0.9932, Prec: 0.9938, Rec: 0.9932, F1: 0.9932\n",
341
+ "Test - Acc: 0.9812, Prec: 0.9845, Rec: 0.9812, F1: 0.9804\n",
342
+ "---\n",
343
+ "Epoch 081, Loss: 0.2780\n",
344
+ "Train - Acc: 0.9932, Prec: 0.9937, Rec: 0.9932, F1: 0.9932\n",
345
+ "Test - Acc: 0.9812, Prec: 0.9842, Rec: 0.9812, F1: 0.9803\n",
346
+ "---\n",
347
+ "Epoch 082, Loss: 0.2728\n",
348
+ "Train - Acc: 0.9932, Prec: 0.9938, Rec: 0.9932, F1: 0.9933\n",
349
+ "Test - Acc: 0.9812, Prec: 0.9843, Rec: 0.9812, F1: 0.9804\n",
350
+ "---\n",
351
+ "Epoch 083, Loss: 0.2707\n",
352
+ "Train - Acc: 0.9935, Prec: 0.9941, Rec: 0.9935, F1: 0.9936\n",
353
+ "Test - Acc: 0.9806, Prec: 0.9835, Rec: 0.9806, F1: 0.9797\n",
354
+ "---\n",
355
+ "Epoch 084, Loss: 0.2497\n",
356
+ "Train - Acc: 0.9934, Prec: 0.9939, Rec: 0.9934, F1: 0.9934\n",
357
+ "Test - Acc: 0.9806, Prec: 0.9837, Rec: 0.9806, F1: 0.9798\n",
358
+ "---\n",
359
+ "Epoch 085, Loss: 0.2457\n",
360
+ "Train - Acc: 0.9932, Prec: 0.9938, Rec: 0.9932, F1: 0.9933\n",
361
+ "Test - Acc: 0.9812, Prec: 0.9843, Rec: 0.9812, F1: 0.9804\n",
362
+ "---\n",
363
+ "Epoch 086, Loss: 0.2399\n",
364
+ "Train - Acc: 0.9934, Prec: 0.9939, Rec: 0.9934, F1: 0.9934\n",
365
+ "Test - Acc: 0.9812, Prec: 0.9844, Rec: 0.9812, F1: 0.9803\n",
366
+ "---\n",
367
+ "Epoch 087, Loss: 0.2374\n",
368
+ "Train - Acc: 0.9935, Prec: 0.9940, Rec: 0.9935, F1: 0.9936\n",
369
+ "Test - Acc: 0.9812, Prec: 0.9844, Rec: 0.9812, F1: 0.9804\n",
370
+ "---\n",
371
+ "Epoch 088, Loss: 0.2217\n",
372
+ "Train - Acc: 0.9940, Prec: 0.9944, Rec: 0.9940, F1: 0.9940\n",
373
+ "Test - Acc: 0.9812, Prec: 0.9844, Rec: 0.9812, F1: 0.9803\n",
374
+ "---\n",
375
+ "Epoch 089, Loss: 0.2196\n",
376
+ "Train - Acc: 0.9940, Prec: 0.9944, Rec: 0.9940, F1: 0.9940\n",
377
+ "Test - Acc: 0.9800, Prec: 0.9833, Rec: 0.9800, F1: 0.9792\n",
378
+ "---\n",
379
+ "Epoch 090, Loss: 0.2209\n",
380
+ "Train - Acc: 0.9943, Prec: 0.9947, Rec: 0.9943, F1: 0.9943\n",
381
+ "Test - Acc: 0.9812, Prec: 0.9841, Rec: 0.9812, F1: 0.9807\n",
382
+ "---\n",
383
+ "Epoch 091, Loss: 0.2090\n",
384
+ "Train - Acc: 0.9946, Prec: 0.9950, Rec: 0.9946, F1: 0.9946\n",
385
+ "Test - Acc: 0.9800, Prec: 0.9833, Rec: 0.9800, F1: 0.9796\n",
386
+ "---\n",
387
+ "Epoch 092, Loss: 0.2023\n",
388
+ "Train - Acc: 0.9949, Prec: 0.9952, Rec: 0.9949, F1: 0.9949\n",
389
+ "Test - Acc: 0.9806, Prec: 0.9837, Rec: 0.9806, F1: 0.9802\n",
390
+ "---\n",
391
+ "Epoch 093, Loss: 0.1959\n",
392
+ "Train - Acc: 0.9950, Prec: 0.9954, Rec: 0.9950, F1: 0.9950\n",
393
+ "Test - Acc: 0.9800, Prec: 0.9834, Rec: 0.9800, F1: 0.9796\n",
394
+ "---\n",
395
+ "Epoch 094, Loss: 0.1838\n",
396
+ "Train - Acc: 0.9952, Prec: 0.9955, Rec: 0.9952, F1: 0.9952\n",
397
+ "Test - Acc: 0.9806, Prec: 0.9838, Rec: 0.9806, F1: 0.9802\n",
398
+ "---\n",
399
+ "Epoch 095, Loss: 0.1898\n",
400
+ "Train - Acc: 0.9954, Prec: 0.9958, Rec: 0.9954, F1: 0.9955\n",
401
+ "Test - Acc: 0.9812, Prec: 0.9843, Rec: 0.9812, F1: 0.9808\n",
402
+ "---\n",
403
+ "Epoch 096, Loss: 0.1849\n",
404
+ "Train - Acc: 0.9954, Prec: 0.9958, Rec: 0.9954, F1: 0.9955\n",
405
+ "Test - Acc: 0.9824, Prec: 0.9855, Rec: 0.9824, F1: 0.9821\n",
406
+ "---\n",
407
+ "Epoch 097, Loss: 0.1798\n",
408
+ "Train - Acc: 0.9960, Prec: 0.9963, Rec: 0.9960, F1: 0.9960\n",
409
+ "Test - Acc: 0.9835, Prec: 0.9861, Rec: 0.9835, F1: 0.9831\n",
410
+ "---\n",
411
+ "Epoch 098, Loss: 0.1756\n",
412
+ "Train - Acc: 0.9962, Prec: 0.9964, Rec: 0.9962, F1: 0.9962\n",
413
+ "Test - Acc: 0.9830, Prec: 0.9857, Rec: 0.9830, F1: 0.9825\n",
414
+ "---\n",
415
+ "Epoch 099, Loss: 0.1683\n",
416
+ "Train - Acc: 0.9962, Prec: 0.9964, Rec: 0.9962, F1: 0.9962\n",
417
+ "Test - Acc: 0.9830, Prec: 0.9858, Rec: 0.9830, F1: 0.9825\n",
418
+ "---\n"
419
+ ]
420
+ }
421
+ ],
422
+ "source": [
423
+ "import torch\n",
424
+ "from torch_geometric.data import Data\n",
425
+ "from torch_geometric.nn import GCNConv\n",
426
+ "import torch.nn.functional as F\n",
427
+ "from sklearn.neighbors import NearestNeighbors\n",
428
+ "import numpy as np\n",
429
+ "from sklearn.preprocessing import LabelEncoder\n",
430
+ "from sklearn.model_selection import train_test_split\n",
431
+ "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score\n",
432
+ "import matplotlib.pyplot as plt\n",
433
+ "import seaborn as sns\n",
434
+ "from sklearn.metrics import confusion_matrix\n",
435
+ "\n",
436
+ "# Load and prepare data\n",
437
+ "data = np.load('/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Embedding_v1.npz')\n",
438
+ "embeddings = data['embeddings']\n",
439
+ "labels = data['labels']\n",
440
+ "\n",
441
+ "# Encode labels\n",
442
+ "encoder = LabelEncoder()\n",
443
+ "encoded_labels = encoder.fit_transform(labels)\n",
444
+ "\n",
445
+ "unique, counts = np.unique(encoded_labels, return_counts=True)\n",
446
+ "class_counts = dict(zip(unique, counts))\n",
447
+ "# Filter out classes with fewer than 6 samples\n",
448
+ "valid_classes = [cls for cls, count in class_counts.items() if count >= 10]\n",
449
+ "mask = np.isin(encoded_labels, valid_classes)\n",
450
+ "\n",
451
+ "filtered_embeddings = embeddings[mask]\n",
452
+ "filtered_labels = encoded_labels[mask]\n",
453
+ "\n",
454
+ "# Re-encode filtered labels to ensure contiguous indices\n",
455
+ "filtered_labels = LabelEncoder().fit_transform(filtered_labels) # Re-encode after filtering\n",
456
+ "\n",
457
+ "# Split data into train and test sets\n",
458
+ "X_train, X_test, y_train, y_test = train_test_split(filtered_embeddings, filtered_labels, \n",
459
+ " test_size=0.2, \n",
460
+ " random_state=42,\n",
461
+ " stratify=filtered_labels)\n",
462
+ "\n",
463
+ "# Convert to PyTorch tensors\n",
464
+ "X_train = torch.tensor(X_train, dtype=torch.float)\n",
465
+ "X_test = torch.tensor(X_test, dtype=torch.float)\n",
466
+ "y_train = torch.tensor(y_train, dtype=torch.long)\n",
467
+ "y_test = torch.tensor(y_test, dtype=torch.long)\n",
468
+ "\n",
469
+ "# Create k-NN graphs for train and test\n",
470
+ "def create_knn_graph(features, k=2):\n",
471
+ " nn = NearestNeighbors(n_neighbors=k)\n",
472
+ " nn.fit(features)\n",
473
+ " distances, indices = nn.kneighbors(features)\n",
474
+ " \n",
475
+ " edge_indices = []\n",
476
+ " for i in range(len(indices)):\n",
477
+ " for j in indices[i]:\n",
478
+ " edge_indices.append([i, j])\n",
479
+ " \n",
480
+ " edge_index = torch.tensor(edge_indices, dtype=torch.long).t().contiguous()\n",
481
+ " return edge_index\n",
482
+ "\n",
483
+ "train_edge_index = create_knn_graph(X_train.numpy(), k=2)\n",
484
+ "test_edge_index = create_knn_graph(X_test.numpy(), k=2)\n",
485
+ "\n",
486
+ "# Create PyG Data objects\n",
487
+ "train_data = Data(x=X_train, edge_index=train_edge_index, y=y_train)\n",
488
+ "test_data = Data(x=X_test, edge_index=test_edge_index, y=y_test)\n",
489
+ "\n",
490
+ "# Define GNN model\n",
491
+ "class FaceGNN(torch.nn.Module):\n",
492
+ " def __init__(self, input_dim, hidden_dim, output_dim):\n",
493
+ " super(FaceGNN, self).__init__()\n",
494
+ " self.conv1 = GCNConv(input_dim, hidden_dim)\n",
495
+ " self.conv2 = GCNConv(hidden_dim, hidden_dim)\n",
496
+ " self.conv3 = GCNConv(hidden_dim, hidden_dim)\n",
497
+ "\n",
498
+ " self.classifier = torch.nn.Linear(hidden_dim, output_dim)\n",
499
+ " \n",
500
+ " def forward(self, data):\n",
501
+ " x, edge_index = data.x, data.edge_index\n",
502
+ " \n",
503
+ " x = self.conv1(x, edge_index)\n",
504
+ " x = F.relu(x)\n",
505
+ " x = F.dropout(x, p=0.5, training=self.training)\n",
506
+ " \n",
507
+ " x = self.conv2(x, edge_index)\n",
508
+ " x = F.relu(x)\n",
509
+ " \n",
510
+ " return self.classifier(x)\n",
511
+ "\n",
512
+ "# Initialize model\n",
513
+ "input_dim = filtered_embeddings.shape[1]\n",
514
+ "hidden_dim = 256\n",
515
+ "output_dim = len(np.unique(filtered_labels)) # Number of unique classes after re-encoding\n",
516
+ "\n",
517
+ "model = FaceGNN(input_dim, hidden_dim, output_dim)\n",
518
+ "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
519
+ "criterion = torch.nn.CrossEntropyLoss()\n",
520
+ "\n",
521
+ "# Training and evaluation functions\n",
522
+ "def train():\n",
523
+ " model.train()\n",
524
+ " optimizer.zero_grad()\n",
525
+ " out = model(train_data)\n",
526
+ " loss = criterion(out, train_data.y)\n",
527
+ " loss.backward()\n",
528
+ " optimizer.step()\n",
529
+ " return loss.item()\n",
530
+ "\n",
531
+ "def evaluate(data, model, criterion):\n",
532
+ " model.eval()\n",
533
+ " with torch.no_grad():\n",
534
+ " out = model(data)\n",
535
+ " loss = criterion(out, data.y)\n",
536
+ " pred = out.argmax(dim=1)\n",
537
+ " y_true = data.y.numpy()\n",
538
+ " y_pred = pred.numpy()\n",
539
+ " \n",
540
+ " acc = accuracy_score(y_true, y_pred)\n",
541
+ " prec = precision_score(y_true, y_pred, average='weighted', zero_division=0)\n",
542
+ " rec = recall_score(y_true, y_pred, average='weighted', zero_division=0)\n",
543
+ " f1 = f1_score(y_true, y_pred, average='weighted', zero_division=0)\n",
544
+ " \n",
545
+ " return acc, prec, rec, f1, loss.item(), y_true, y_pred\n",
546
+ "\n",
547
+ "\n",
548
+ "train_losses = []\n",
549
+ "test_losses = []\n",
550
+ "train_accuracies = []\n",
551
+ "test_accuracies = []\n",
552
+ "epochs = []\n",
553
+ "\n",
554
+ "num_epochs = 100\n",
555
+ "for epoch in range(num_epochs):\n",
556
+ " loss = train()\n",
557
+ " \n",
558
+ " if epoch % 1 == 0:\n",
559
+ " epochs.append(epoch)\n",
560
+ " train_acc, train_prec, train_rec, train_f1, train_loss, _, _ = evaluate(train_data, model, criterion)\n",
561
+ " test_acc, test_prec, test_rec, test_f1, test_loss, _, _ = evaluate(test_data, model, criterion)\n",
562
+ " \n",
563
+ " train_losses.append(train_loss)\n",
564
+ " test_losses.append(test_loss)\n",
565
+ " train_accuracies.append(train_acc)\n",
566
+ " test_accuracies.append(test_acc)\n",
567
+ " \n",
568
+ " print(f'Epoch {epoch:03d}, Loss: {loss:.4f}')\n",
569
+ " print(f'Train - Acc: {train_acc:.4f}, Prec: {train_prec:.4f}, Rec: {train_rec:.4f}, F1: {train_f1:.4f}')\n",
570
+ " print(f'Test - Acc: {test_acc:.4f}, Prec: {test_prec:.4f}, Rec: {test_rec:.4f}, F1: {test_f1:.4f}')\n",
571
+ " print('---')\n",
572
+ "\n",
573
+ "# Save the model\n",
574
+ "# torch.save(model.state_dict(), '/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/GNN_v1.pth')\n",
575
+ "\n"
576
+ ]
577
+ },
578
+ {
579
+ "cell_type": "code",
580
+ "execution_count": 32,
581
+ "metadata": {},
582
+ "outputs": [
583
+ {
584
+ "name": "stdout",
585
+ "output_type": "stream",
586
+ "text": [
587
+ "Final Results:\n",
588
+ "Train - Acc: 0.9962, Prec: 0.9964, Rec: 0.9962, F1: 0.9962, Loss: 0.0584\n",
589
+ "Test - Acc: 0.9830, Prec: 0.9858, Rec: 0.9830, F1: 0.9825, Loss: 0.1429\n"
590
+ ]
591
+ }
592
+ ],
593
+ "source": [
594
+ "# Final evaluation\n",
595
+ "train_acc, train_prec, train_rec, train_f1, train_loss, train_y_true, train_y_pred = evaluate(train_data, model, criterion)\n",
596
+ "test_acc, test_prec, test_rec, test_f1, test_loss, test_y_true, test_y_pred = evaluate(test_data, model, criterion)\n",
597
+ "\n",
598
+ "print(\"Final Results:\")\n",
599
+ "print(f'Train - Acc: {train_acc:.4f}, Prec: {train_prec:.4f}, Rec: {train_rec:.4f}, F1: {train_f1:.4f}, Loss: {train_loss:.4f}')\n",
600
+ "print(f'Test - Acc: {test_acc:.4f}, Prec: {test_prec:.4f}, Rec: {test_rec:.4f}, F1: {test_f1:.4f}, Loss: {test_loss:.4f}')\n",
601
+ "\n",
602
+ "# # Confusion matrix\n",
603
+ "# cm = confusion_matrix(test_y_true, test_y_pred)\n",
604
+ "# plt.figure(figsize=(10, 7))\n",
605
+ "# sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=encoder.classes_, yticklabels=encoder.classes_)\n",
606
+ "# plt.title('Confusion Matrix')\n",
607
+ "# plt.xlabel('Predicted')\n",
608
+ "# plt.ylabel('True')\n",
609
+ "# plt.show()\n"
610
+ ]
611
+ },
612
+ {
613
+ "cell_type": "code",
614
+ "execution_count": 36,
615
+ "metadata": {},
616
+ "outputs": [
617
+ {
618
+ "data": {
619
+ "image/png": "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+fLlsTKfk7+/P8OGDaNPnz6cOnWKOnXq4OXlxcmTJ1mxYgXt2rWjR48ebN68mY4dO9KgQQNy5cpFWFgY8+bNw9HRkfr169v3V6xYMTZu3Mi4ceNInz492bJlo1SpUk/MsGnTJu7fvx+lvU6dOrRr145p06bRqlUrdu7cSdasWVm2bBlbt25lwoQJ9gkt27Rpw40bN6hcuTIZM2bk9OnTTJ48mSJFitjnn8qXLx+VKlWiWLFipEiRgr/++otly5bRsWPHJ+bz9fVlxYoV1KxZkyJFitCsWTOKFSsGwK5du/j2228pXbq0ff02bdrw4YcfUr9+fV5//XX27t3LunXronz7+TTOzs7Uq1ePRYsWcefOHcaMGRNlnS+++IJy5cpRsGBB2rZtS/bs2bl8+TLbtm3j3Llz7N2795meU0RE4ob6Q88uMfaHLly4wE8//UTnzp2jzeXq6kq1atVYunQpkyZN4rXXXqN58+ZMmjSJo0ePUr16dSIiIvj111957bXX6NixIzly5KBv374MHTqU8uXLU69ePVxdXdmxYwfp06dn5MiRQOz0V958803mzZuHj48P+fLlY9u2bWzcuJGUKVNGWu+TTz5h2bJlNGjQgPfee49ixYpx48YNVq1axdSpUylcuLB93SZNmtCzZ09WrFhB+/btcXZ2jlGWb775hjfeeIN69epRu3ZtqlSpgoeHB0ePHmXRokVcvHjR3pd67733GDduHNWqVeP999/nypUrTJ06lfz589snTY+phg0b0qxZM7788kuqVatG8uTJo7z2VatW8eabb9KqVSuKFSvGnTt32L9/P8uWLePUqVPP3EeUJCouLvEnklA97hLI+fPnj3b9rVu3Gq+++qqRLFkyI3369EbPnj3tl4H96aef7Os97rKso0ePjrJPHrms7eMugdyhQ4co2z566VvDMIxNmzYZRYsWNVxcXAx/f3/j66+/Nrp37264ubk95l34z8GDB42qVasanp6eRqpUqYy2bdvaL7X88OWKW7ZsaXh4eETZPrrs169fN5o3b254e3sbPj4+RvPmze2XJX6RSyA/8N133xnlypUzPDw8DA8PDyNPnjxGhw4djMOHDxuGYRgnTpww3nvvPcPf399wc3MzUqRIYbz22mvGxo0bI+3nn3/+MSpUqGAkS5bMAJ54OeQH/56PW+bNm2cYhmFcvnzZaN26tZEqVSrDxcXFKFiwYJTXvGzZMuONN94wUqdObbi4uBiZM2c2PvjgA+PixYv2dYYNG2aULFnSSJ48uZEsWTIjT548xvDhw42QkJAYvXcXLlwwunbtauTKlctwc3Mz3N3djWLFihnDhw83AgIC7OuFh4cbvXr1MlKlSmW4u7sb1apVM44dO/bYSyzv2LHjsc+5YcMGAzBsNptx9uzZaNc5fvy40aJFCyNt2rSGs7OzkSFDBuPNN980li1bFqPXJSIisUP9ocjUH3p6f2js2LEGYGzatOmxWefMmWMAxvfff28YhmGEhYUZo0ePNvLkyWO4uLgYfn5+Ro0aNYydO3dG2m7WrFlG0aJFDVdXV8PX19eoWLGisWHDBvvjsdFfuXnzpr2P5unpaVSrVs34559/ov1dun79utGxY0cjQ4YMhouLi5ExY0ajZcuWxrVr16Lst2bNmgZg/P777499X6Jz9+5dY8yYMUaJEiUMT09Pw8XFxciZM6fRqVMn49ixY5HWnT9/vpE9e3bDxcXFKFKkiLFu3bpnOtYeCAwMtP87z58/P9p1bt++bfTp08fIkSOH4eLiYqRKlcooU6aMMWbMmBj3Q0VshhGPvrYQEUvUqVOHAwcOcPToUaujiIiIiFhC/SF52erWrcv+/ftjNH+ZSFKhOaVEkph79+5Fun/06FHWrFlDpUqVrAkkIiIiEsfUH5K4dvHiRVavXk3z5s2tjiISr2iklEgSky5dOlq1akX27Nk5ffo0X331FcHBwezevTvay/WKiIiIJDbqD0lcOXnyJFu3buXrr79mx44dHD9+nLRp01odSyTe0ETnIklM9erV+fbbb7l06RKurq6ULl2aESNGqAMmIiIiSYb6QxJXfvnlF1q3bk3mzJmZO3euClIij9BIKRERERERERERiXOaU0pEREREREREROKcilIiIiIiIiIiIhLnEvScUhEREVy4cAEvLy9sNpvVcURERCSBMgyD27dvkz59ehwcku53dupbiYiISGyIad8qQRelLly4QKZMmayOISIiIonE2bNnyZgxo9UxLKO+lYiIiMSmp/WtEnRRysvLCzBfpLe3t8VpREREJKEKDAwkU6ZM9r5FUqW+lYiIiMSGmPatEnRR6sGwcm9vb3WcRERE5IUl9VPW1LcSERGR2PS0vlXSnTRBREREREREREQso6KUiIiIiIiI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eomvv/6arl27xhh16TZ37lzuuecemjVrVmS/92H3+/7772nXrh3ly5fnueeeY+HChdxyyy1Ur1694JybbrqJUaNGMXbsWN544w0qV65Mhw4d2LRpU4yRly4jRoxgzJgxjB49mkWLFjFixAhuuukm7rzzzoJzvA+73vr162nevDl33XXXDo/vzDXv2bMnH3zwATNnzmTatGm8/PLLnH/++cX1EfQj7FsVP/tXicW+VXzsWyUG+1bxSPi+VaQdOuyww6I+ffoUfJ2bmxvVr18/GjZsWIxRlR0rVqyIgOill16KoiiKVq9eHZUvXz6aMmVKwTmLFi2KgGj27NlxhVlqrV27Ntp3332jmTNnRkcddVTUt2/fKIq8D8Xlqquuio444ogfPZ6XlxfVrVs3uvnmmwv2rV69OkpLS4v+/e9/F0eIZcIJJ5wQnXPOOUX2de3aNerZs2cURd6H4gBETzzxRMHXO3PNFy5cGAHR3LlzC8557rnnoqSkpOirr74qttj1Q/at4mf/Kj72reJl3yox2LeKXyL2rRwptQObN29m3rx5tG/fvmBfcnIy7du3Z/bs2TFGVnasWbMGgBo1agAwb948cnJyityT/fffn0aNGnlPdoM+ffpwwgknFLne4H0oLk899RStWrXi1FNPpXbt2rRo0YLx48cXHP/ss8/IzMwsch8yMjJo06aN92EXOvzww5k1axYffvghAO+88w6vvvoqnTp1ArwPcdiZaz579myqVatGq1atCs5p3749ycnJvPHGG8UeswL7VonB/lV87FvFy75VYrBvlXgSoW+V8pu/Qym0cuVKcnNzqVOnTpH9derUYfHixTFFVXbk5eVx+eWX065dO5o2bQpAZmYmqampVKtWrci5derUITMzM4YoS69Jkybx9ttvM3fu3B8c8z4Uj08//ZQxY8bQr18/BgwYwNy5c7nssstITU2lV69eBdd6R/9GeR92nauvvpqsrCz2339/ypUrR25uLkOHDqVnz54A3ocY7Mw1z8zMpHbt2kWOp6SkUKNGDe9LjOxbxc/+VXzsW8XPvlVisG+VeBKhb2VSSgmnT58+vP/++7z66qtxh1LmLFu2jL59+zJz5kwqVKgQdzhlVl5eHq1ateLGG28EoEWLFrz//vuMHTuWXr16xRxd2fHoo4/yyCOPMHHiRA466CAWLFjA5ZdfTv369b0P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",
620
+ "text/plain": [
621
+ "<Figure size 1200x500 with 2 Axes>"
622
+ ]
623
+ },
624
+ "metadata": {},
625
+ "output_type": "display_data"
626
+ }
627
+ ],
628
+ "source": [
629
+ "# Plotting training and test loss curves\n",
630
+ "plt.figure(figsize=(12, 5))\n",
631
+ "\n",
632
+ "# Loss curve\n",
633
+ "plt.subplot(1, 2, 1)\n",
634
+ "plt.plot(epochs, train_losses, label='Training Loss', color='blue')\n",
635
+ "plt.plot(epochs, test_losses, label='Test Loss', color='red')\n",
636
+ "plt.title('Training and Test Loss Curve')\n",
637
+ "plt.xlabel('Epochs')\n",
638
+ "plt.ylabel('Loss')\n",
639
+ "plt.legend()\n",
640
+ "\n",
641
+ "# Accuracy curve\n",
642
+ "plt.subplot(1, 2, 2)\n",
643
+ "plt.plot(epochs, train_accuracies, label='Training Accuracy', color='blue')\n",
644
+ "plt.plot(epochs, test_accuracies, label='Test Accuracy', color='red')\n",
645
+ "plt.title('Training and Test Accuracy Curve')\n",
646
+ "plt.xlabel('Epochs')\n",
647
+ "plt.ylabel('Accuracy')\n",
648
+ "plt.legend()\n",
649
+ "\n",
650
+ "plt.tight_layout()\n",
651
+ "plt.show()\n"
652
+ ]
653
+ },
654
+ {
655
+ "cell_type": "code",
656
+ "execution_count": null,
657
+ "metadata": {},
658
+ "outputs": [],
659
+ "source": []
660
+ }
661
+ ],
662
+ "metadata": {
663
+ "colab": {
664
+ "provenance": []
665
+ },
666
+ "kernelspec": {
667
+ "display_name": "shanin",
668
+ "language": "python",
669
+ "name": "python3"
670
+ },
671
+ "language_info": {
672
+ "codemirror_mode": {
673
+ "name": "ipython",
674
+ "version": 3
675
+ },
676
+ "file_extension": ".py",
677
+ "mimetype": "text/x-python",
678
+ "name": "python",
679
+ "nbconvert_exporter": "python",
680
+ "pygments_lexer": "ipython3",
681
+ "version": "3.12.9"
682
+ }
683
+ },
684
+ "nbformat": 4,
685
+ "nbformat_minor": 0
686
+ }
CODE/ML_api.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import FastAPI
3
+ from pydantic import BaseModel
4
+ import uvicorn
5
+ import requests
6
+ from PIL import Image
7
+ from io import BytesIO
8
+ import numpy as np
9
+ import cv2 as cv
10
+ from keras_facenet import FaceNet
11
+ from mtcnn import MTCNN
12
+ import pickle
13
+ from sklearn.preprocessing import LabelEncoder
14
+
15
+ embedder = FaceNet()
16
+ detector = MTCNN()
17
+
18
+ # Load the face recognition model and labels
19
+ data = np.load('/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Embedding.npz')
20
+ Y = data['labels']
21
+ encoder = LabelEncoder()
22
+ encoder.fit(Y)
23
+
24
+ with open('/home/shanin/Desktop/SHANIN/MAIN/ALL_CODE/Face_Recognition/Face_Model.pkl', 'rb') as file:
25
+ model = pickle.load(file)
26
+
27
+ app = FastAPI()
28
+
29
+ # Function to get the face embedding
30
+ def get_embedding(face_img):
31
+ face_img = face_img.astype('float32')
32
+ face_img = np.expand_dims(face_img, axis=0)
33
+ yhat = embedder.embeddings(face_img)
34
+ return yhat[0]
35
+
36
+ # Function to resize large images
37
+ def resize_image(image, max_size=1024):
38
+ """
39
+ Resize image to prevent excessive memory usage for large images
40
+ """
41
+ h, w = image.shape[:2]
42
+ if max(h, w) > max_size:
43
+ scale_factor = max_size / max(h, w)
44
+ new_dim = (int(w * scale_factor), int(h * scale_factor))
45
+ image = cv.resize(image, new_dim)
46
+ return image
47
+
48
+ # Function to process the image URL and make predictions
49
+ def get_result(img_url, code):
50
+ response = requests.get(img_url)
51
+ img_data = BytesIO(response.content)
52
+ image = Image.open(img_data)
53
+ if image.mode != "RGB":
54
+ image = image.convert("RGB")
55
+
56
+ if code == 'CM_370009':
57
+ # Rotate the image 90 degrees (one time) clockwise
58
+ image = image.rotate(-90, expand=True)
59
+
60
+ # Convert PIL image to a NumPy array
61
+ image_np = np.array(image)
62
+
63
+ # Resize large images to avoid excessive memory usage
64
+ image_np = resize_image(image_np)
65
+
66
+ # Use the resized image for face detection
67
+ detection_results = detector.detect_faces(image_np)
68
+ if not detection_results:
69
+ raise ValueError("No face detected in the image.")
70
+
71
+ # Extract face from the detected bounding box
72
+ x, y, w, h = detection_results[0]['box']
73
+ face_image = image_np[y:y+h, x:x+w]
74
+
75
+ # Resize face image to the required input size for the model
76
+ face_image_resized = cv.resize(face_image, (160, 160))
77
+
78
+ # Get embedding for face and make prediction
79
+ test_im = get_embedding(face_image_resized)
80
+ test_im = [test_im]
81
+
82
+ ypreds = model.predict(test_im)
83
+ predicted_label = encoder.inverse_transform(ypreds)[0]
84
+ return predicted_label
85
+
86
+ # FastAPI Request Model
87
+ class ImageRequest(BaseModel):
88
+ code: str
89
+ img: str
90
+
91
+ @app.post("/face")
92
+ async def face_recognition(request_data: ImageRequest):
93
+ code = request_data.code
94
+ img_url = request_data.img
95
+
96
+ result = get_result(img_url, code)
97
+ print(result)
98
+
99
+ if result == code:
100
+ return True
101
+ else:
102
+ return False
103
+
104
+ if __name__ == "__main__":
105
+ uvicorn.run(app, host="127.0.0.1", port=4444)
CODE/pyTorch_FaceNet_api.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import asyncio
3
+ import traceback
4
+ import numpy as np
5
+ from fastapi import FastAPI, HTTPException
6
+ from pydantic import BaseModel
7
+ from aiohttp import ClientSession
8
+ from PIL import Image, ImageFilter
9
+ from io import BytesIO
10
+ from facenet_pytorch import MTCNN, InceptionResnetV1
11
+ import uvicorn
12
+ import os
13
+
14
+ class FaceMainExecutionPytorch:
15
+ def __init__(self):
16
+ self.MVL = 0.90
17
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
18
+ self.detector = MTCNN()
19
+ self.resnet = InceptionResnetV1(pretrained="vggface2").eval().to(self.device)
20
+
21
+ async def calculate_blurriness(self, img: Image.Image) -> float:
22
+ if img.mode != "L":
23
+ img = img.convert("L")
24
+ laplacian = img.filter(ImageFilter.FIND_EDGES)
25
+ laplacian_array = np.array(laplacian, dtype=np.float32)
26
+ return laplacian_array.var()
27
+
28
+ async def extract_face(self, image, detector, size=(160, 160)):
29
+ """Detect and extract the face from an image object."""
30
+ try:
31
+ boxes, _ = self.detector.detect(np.array(image))
32
+ if boxes is None or len(boxes) == 0:
33
+ print("No face detected in the image.")
34
+ return None
35
+
36
+ x, y, width, height = boxes[0]
37
+ x, y, width, height = int(x), int(y), int(width), int(height)
38
+
39
+ x, y = max(x, 0), max(y, 0)
40
+ face = image.crop((x, y, x + width, y + height))
41
+ face = face.resize(size)
42
+ return face
43
+ except Exception as e:
44
+ print(f"Error extracting face: {e}")
45
+ traceback.print_exc()
46
+ return None
47
+
48
+ async def calculate_embedding(self, face_image):
49
+ """Calculate the face embedding."""
50
+ try:
51
+ resnet = self.resnet
52
+ face_tensor = torch.tensor(np.array(face_image)).permute(2, 0, 1).unsqueeze(0).float() / 255.0
53
+ face_tensor = (face_tensor - 0.5) / 0.5 # Normalize to [-1, 1]
54
+ face_tensor = face_tensor.to(self.device)
55
+ embedding = resnet(face_tensor)
56
+ return embedding.detach().cpu().numpy()
57
+ except Exception as e:
58
+ print(f"Error calculating embedding: {e}")
59
+ traceback.print_exc()
60
+ return None
61
+
62
+ async def compare_faces(self, embedding1, embedding2):
63
+ """Compare two face embeddings and return similarity."""
64
+ distance = np.linalg.norm(embedding1 - embedding2)
65
+ distance = round((distance), 2)
66
+ print(f"Cosine Distance: {distance}")
67
+ return distance < self.MVL
68
+
69
+ async def get_image_from_url(self, img_path):
70
+ """Download or load an image from a local path or URL."""
71
+ try:
72
+ # Check if the input is a URL
73
+ if img_path.startswith("http://") or img_path.startswith("https://"):
74
+ async with ClientSession() as session:
75
+ async with session.get(img_path) as response:
76
+ if response.status != 200:
77
+ raise ValueError(f"HTTP Error: {response.status}")
78
+ img_data = await response.read()
79
+ image = Image.open(BytesIO(img_data))
80
+ image.verify()
81
+ image = Image.open(BytesIO(img_data)).convert("RGB")
82
+ elif os.path.isfile(img_path):
83
+ # Load image from a local file
84
+ image = Image.open(img_path).convert("RGB")
85
+ else:
86
+ raise ValueError("Invalid input path. Must be a valid URL or local file path.")
87
+
88
+ blur = await self.calculate_blurriness(image)
89
+ if blur and blur < 30:
90
+ print(f"===================>>>>>>>>>> Blurry: {blur}")
91
+ return None
92
+ else:
93
+ print('Good Image: ', blur)
94
+
95
+ return image
96
+ except Exception as e:
97
+ print(f"Error downloading or loading image: {e}")
98
+ traceback.print_exc()
99
+ return None
100
+
101
+ async def compare_faces_from_urls(self, url1, url2):
102
+ """Compare faces from two image URLs."""
103
+ try:
104
+ task = [
105
+ asyncio.create_task(self.get_image_from_url(url1)),
106
+ asyncio.create_task(self.get_image_from_url(url2))
107
+ ]
108
+ img1, img2 = await asyncio.gather(*task)
109
+
110
+ if img1 is None or img2 is None:
111
+ return False
112
+
113
+ face1 = await self.extract_face(img1, self.detector)
114
+ face2 = await self.extract_face(img2, self.detector)
115
+
116
+ if face1 is None or face2 is None:
117
+ return False
118
+
119
+ embedding1 = await self.calculate_embedding(face1)
120
+ embedding2 = await self.calculate_embedding(face2)
121
+
122
+ if embedding1 is None or embedding2 is None:
123
+ return False
124
+
125
+ return await self.compare_faces(embedding1, embedding2)
126
+ except Exception as e:
127
+ print(f"Error comparing faces from URLs: {e}")
128
+ traceback.print_exc()
129
+ return False
130
+
131
+ async def faceMain(self, BODY):
132
+ try:
133
+ result = await self.compare_faces_from_urls(BODY['img1'], BODY['img2'])
134
+ if result:
135
+ return True
136
+ else:
137
+ return False
138
+ except Exception as e:
139
+ traceback.print_exc()
140
+ return False
141
+
142
+ # FastAPI server setup
143
+ app = FastAPI()
144
+
145
+ class Body(BaseModel):
146
+ img1: str
147
+ img2: str
148
+
149
+ # Initialize the face comparison class
150
+ face_executor = FaceMainExecutionPytorch()
151
+
152
+ @app.post("/compare_faces")
153
+ async def compare_faces(body: Body):
154
+ try:
155
+ result = await face_executor.faceMain(body.dict())
156
+ print(result)
157
+ if result:
158
+ return True
159
+ else:
160
+ return False
161
+
162
+ except Exception as e:
163
+ traceback.print_exc()
164
+ raise HTTPException(status_code=500, detail="Internal Server Error")
165
+
166
+ if __name__ == "__main__":
167
+ try:
168
+ uvicorn.run(app, host="127.0.0.1", port=8888)
169
+ finally:
170
+ torch.cuda.empty_cache() # Clear CUDA memory after the server stops
FaceGNN.png ADDED

Git LFS Details

  • SHA256: ff5862bc8911f47c4b2af499122808f65b1846c984f79d9fb2ecb30d401e0188
  • Pointer size: 132 Bytes
  • Size of remote file: 1.64 MB
Features and Models/Face_Embedding.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeb4fe6e3a7c44400fff7a02538b5e7e2c438f644d2f1989def447def0826793
3
+ size 44886348
Features and Models/Face_Embedding_Augment.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:292af4ce606f5559547be9c34226ba180f2674b9a1e929eef0073b3c811a2e57
3
+ size 179453245
Features and Models/Face_Model.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ca9b1482349b6e8134562a63764ec20e2ee54da05a1b7b2d03c601ef32fcbe9
3
+ size 379890260
Features and Models/Face_Model_Augment.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c2327db51642489c219a0be04ea1290fdb93b606bab8d006c5c417379cd59216
3
+ size 1258149408
JSON Data/jan25.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c6c5e7743a3fb0f8adedbbf68e9dc969963fcf4488f5b3d27b8ec6fc9816c45a
3
+ size 577024
JSON Data/jan26.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:272cb91eaadabc8231b76217688b19d7ad7915e6eca22f445a4169adf5dd085e
3
+ size 582888
JSON Data/jan27.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2b71d47c099bf475172882b0ee52f9ec2c8c7df34e94f833774ccb84839760e5
3
+ size 583420
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.