FaceGNN Updated v1.1
Browse files- .gitattributes +2 -0
- CODE/Create ML Model.ipynb +62 -0
- CODE/Data Handling.ipynb +0 -0
- CODE/Features Extract with Augmentation.py +174 -0
- CODE/Features Extraction using FaceNet.py +156 -0
- CODE/Features Extraction using ViT.ipynb +389 -0
- CODE/GNN.ipynb +686 -0
- CODE/ML_api.py +105 -0
- CODE/pyTorch_FaceNet_api.py +170 -0
- FaceGNN.png +3 -0
- Features and Models/Face_Embedding.npz +3 -0
- Features and Models/Face_Embedding_Augment.npz +3 -0
- Features and Models/Face_Model.pkl +3 -0
- Features and Models/Face_Model_Augment.pkl +3 -0
- JSON Data/jan25.json +3 -0
- JSON Data/jan26.json +3 -0
- JSON Data/jan27.json +3 -0
- LICENSE +201 -0
.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
|
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.
|